Skip to content

Commit 5e3aee1

Browse files
authored
fix(cli): map sonnet alias to Claude Sonnet 5.5 (EveryInc#1796)
1 parent dd1bf2b commit 5e3aee1

5 files changed

Lines changed: 17 additions & 15 deletions

File tree

‎docs/solutions/integrations/cross-platform-model-field-normalization.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Claude Code uses bare model aliases (`model: sonnet`) in agent and command front
2929

3030
| Target | Behavior | Why |
3131
|--------|----------|-----|
32-
| OpenCode | Resolve alias + add provider prefix (`anthropic/claude-sonnet-5`) | Multi-provider; routes on the `provider/model-id` prefix. Same rule for any future multi-provider target with that format (the removed Qwen and OpenClaw converters used it). |
32+
| OpenCode | Resolve alias + add provider prefix (`anthropic/claude-sonnet-5-5`) | Multi-provider; routes on the `provider/model-id` prefix. Same rule for any future multi-provider target with that format (the removed Qwen and OpenClaw converters used it). |
3333
| Droid (Factory) | Pass through as-is (`sonnet`) | Factory resolves Claude's bare aliases natively and also accepts dated IDs and `custom:<model>`; normalizing to a form it also accepts adds nothing. |
3434
| Copilot | Drop | Copilot's `model` field takes Copilot display names ("Claude Opus 4.5"), not Claude model IDs or aliases, and has no documented resolution for them. Spec: "If unset, inherits the default model." |
3535
| Codex | Drop | Skill frontmatter supports only `name` and `description` (Rust `SkillFrontmatter` struct). Model selection is global via `config.toml` or `/model`. |

‎src/converters/claude-to-opencode.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ function convertAgent(agent: ClaudeAgent, options: ClaudeToOpenCodeOptions) {
142142

143143
if (options.inferTemperature) {
144144
const temperature = inferTemperature(agent)
145-
// A written model that rejects non-default sampling params (Sonnet 5, Opus
145+
// A written model that rejects non-default sampling params (Sonnet 5+, Opus
146146
// 4.7+) returns HTTP 400 if paired with a temperature. We only write model
147147
// for primary agents, so suppression only applies there; subagents inherit
148148
// the parent session's model and are out of scope.

‎src/utils/model.ts‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
*/
1313
export const CLAUDE_FAMILY_ALIASES: Record<string, string> = {
1414
haiku: "claude-haiku-4-5",
15-
sonnet: "claude-sonnet-5",
15+
sonnet: "claude-sonnet-5-5",
1616
opus: "claude-opus-5-5",
1717
}
1818

@@ -21,10 +21,11 @@ export const CLAUDE_FAMILY_ALIASES: Record<string, string> = {
2121
* (`temperature`/`top_p`/`top_k`) with HTTP 400. Emitting an inferred
2222
* temperature alongside one of these produces a config that fails at runtime.
2323
* Keep in sync with CLAUDE_FAMILY_ALIASES when new generations are released.
24-
* See the Sonnet 5, Opus 4.7/4.8, and Opus 5.5 migration notes.
24+
* See the Sonnet 5/5.5, Opus 4.7/4.8, and Opus 5.5 migration notes.
2525
*/
2626
const SAMPLING_PARAM_REJECTING_MODELS: ReadonlySet<string> = new Set([
2727
"claude-sonnet-5",
28+
"claude-sonnet-5-5",
2829
"claude-opus-4-7",
2930
"claude-opus-4-8",
3031
"claude-opus-5",
@@ -35,7 +36,7 @@ const SAMPLING_PARAM_REJECTING_MODELS: ReadonlySet<string> = new Set([
3536
* Resolve a bare Claude family alias to its canonical model name.
3637
* Returns the input unchanged if not a recognized alias.
3738
*
38-
* "sonnet" -> "claude-sonnet-5"
39+
* "sonnet" -> "claude-sonnet-5-5"
3940
* "claude-sonnet-4-20250514" -> "claude-sonnet-4-20250514" (unchanged)
4041
*/
4142
export function resolveClaudeFamilyAlias(model: string): string {
@@ -46,7 +47,7 @@ export function resolveClaudeFamilyAlias(model: string): string {
4647
* Add a provider prefix based on model naming conventions.
4748
* Returns the input unchanged if already prefixed (contains "/").
4849
*
49-
* "claude-sonnet-5" -> "anthropic/claude-sonnet-5"
50+
* "claude-sonnet-5-5" -> "anthropic/claude-sonnet-5-5"
5051
* "gpt-6-sol" -> "openai/gpt-6-sol"
5152
* "gemini-2.0" -> "google/gemini-2.0"
5253
* "minimax-m3" -> "minimax/minimax-m3"
@@ -66,7 +67,7 @@ export function addProviderPrefix(model: string): string {
6667
* Normalize a model for targets that use provider-prefixed IDs.
6768
* Resolves bare aliases and adds provider prefix.
6869
*
69-
* "sonnet" -> "anthropic/claude-sonnet-5"
70+
* "sonnet" -> "anthropic/claude-sonnet-5-5"
7071
* "claude-sonnet-4-20250514" -> "anthropic/claude-sonnet-4-20250514"
7172
* "anthropic/claude-opus" -> "anthropic/claude-opus" (unchanged)
7273
*/
@@ -85,9 +86,9 @@ export function normalizeModelWithProvider(model: string): string {
8586
/**
8687
* Whether a model rejects non-default sampling params. Accepts a bare alias,
8788
* a canonical ID, or a provider-prefixed ID; resolves aliases and strips the
88-
* provider prefix so `sonnet` and `anthropic/claude-sonnet-5` both match.
89+
* provider prefix so `sonnet` and `anthropic/claude-sonnet-5-5` both match.
8990
*
90-
* "sonnet" -> true (resolves to claude-sonnet-5)
91+
* "sonnet" -> true (resolves to claude-sonnet-5-5)
9192
* "claude-sonnet-4-20250514" -> false (dated Sonnet 4 accepts sampling params)
9293
*/
9394
export function rejectsSamplingParams(model: string): boolean {

‎tests/converter.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,10 @@ describe("convertClaudeToOpenCode", () => {
278278
permissions: "none",
279279
})
280280

281-
// Sonnet 5 rejects non-default temperature with HTTP 400, so the converter
281+
// Sonnet 5.5 rejects non-default temperature with HTTP 400, so the converter
282282
// must write the model but omit the inferred temperature.
283283
const sonnetAgent = parseFrontmatter(bundle.agents.find((a) => a.name === "security-guardian")!.content)
284-
expect(sonnetAgent.data.model).toBe("anthropic/claude-sonnet-5")
284+
expect(sonnetAgent.data.model).toBe("anthropic/claude-sonnet-5-5")
285285
expect(sonnetAgent.data.temperature).toBeUndefined()
286286

287287
// Haiku still accepts sampling params, so temperature is inferred as usual.

‎tests/model-utils.test.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
describe("resolveClaudeFamilyAlias", () => {
1111
test("resolves bare aliases to full Claude model names", () => {
1212
expect(resolveClaudeFamilyAlias("haiku")).toBe("claude-haiku-4-5")
13-
expect(resolveClaudeFamilyAlias("sonnet")).toBe("claude-sonnet-5")
13+
expect(resolveClaudeFamilyAlias("sonnet")).toBe("claude-sonnet-5-5")
1414
expect(resolveClaudeFamilyAlias("opus")).toBe("claude-opus-5-5")
1515
})
1616

@@ -23,7 +23,7 @@ describe("resolveClaudeFamilyAlias", () => {
2323

2424
describe("addProviderPrefix", () => {
2525
test("prefixes Claude models with anthropic/", () => {
26-
expect(addProviderPrefix("claude-sonnet-5")).toBe("anthropic/claude-sonnet-5")
26+
expect(addProviderPrefix("claude-sonnet-5-5")).toBe("anthropic/claude-sonnet-5-5")
2727
expect(addProviderPrefix("claude-haiku-4-5")).toBe("anthropic/claude-haiku-4-5")
2828
})
2929

@@ -65,7 +65,7 @@ describe("addProviderPrefix", () => {
6565

6666
describe("normalizeModelWithProvider", () => {
6767
test("resolves bare aliases and adds provider prefix", () => {
68-
expect(normalizeModelWithProvider("sonnet")).toBe("anthropic/claude-sonnet-5")
68+
expect(normalizeModelWithProvider("sonnet")).toBe("anthropic/claude-sonnet-5-5")
6969
expect(normalizeModelWithProvider("haiku")).toBe("anthropic/claude-haiku-4-5")
7070
expect(normalizeModelWithProvider("opus")).toBe("anthropic/claude-opus-5-5")
7171
})
@@ -85,11 +85,12 @@ describe("rejectsSamplingParams", () => {
8585
expect(rejectsSamplingParams("sonnet")).toBe(true)
8686
expect(rejectsSamplingParams("opus")).toBe(true)
8787
expect(rejectsSamplingParams("claude-sonnet-5")).toBe(true)
88+
expect(rejectsSamplingParams("claude-sonnet-5-5")).toBe(true)
8889
expect(rejectsSamplingParams("claude-opus-5-5")).toBe(true)
8990
expect(rejectsSamplingParams("claude-opus-4-8")).toBe(true)
9091
expect(rejectsSamplingParams("claude-opus-5")).toBe(true)
9192
expect(rejectsSamplingParams("claude-opus-4-7")).toBe(true)
92-
expect(rejectsSamplingParams("anthropic/claude-sonnet-5")).toBe(true)
93+
expect(rejectsSamplingParams("anthropic/claude-sonnet-5-5")).toBe(true)
9394
})
9495

9596
test("passes models that still accept sampling params", () => {

0 commit comments

Comments
 (0)