feat: add Claude Sonnet 5.5 support for Anthropic
- commit
- 6237ce556a337e278c8bf6043d47c91932598df9
- author
- patriceckhart · · 2026-09-29 20:26 UTC
- parent
- 5ac0009
- signature
- unsigned
4 files · +93 -2
M README.md +2 -0
| @@ -752,6 +752,8 @@ Reasoning levels (`--reasoning off|minimum|low|medium|high|xhigh|max`, also conf | |||
| 752 | 752 | ||
| 753 | 753 | Claude Opus 5.5 is available through Anthropic (`--provider anthropic --model claude-opus-5-5`) and GitHub Copilot (`--provider github-copilot --model claude-opus-5.5`, subject to account availability). Both entries support a 1,000,000-token context window and up to 128,000 output tokens. Adaptive thinking is always on, with `low`, `medium`, `high`, `xhigh`, and `max` effort levels. The default, `off`, and `minimum` requests use `low` instead. The reasoning selector omits unsupported levels. Catalog prices per million tokens are $4 input, $20 output, $0.20 cache reads, and $5 cache writes. Copilot billing remains governed by your plan. | |
| 754 | 754 | ||
| 755 | + | Claude Sonnet 5.5 is available through Anthropic (`--provider anthropic --model claude-sonnet-5-5`). It supports a 1,000,000-token context window and up to 128,000 output tokens. Adaptive thinking is always on with the same `low` through `max` effort levels as Opus 5.5, and `off` or `minimum` requests use `low`. Temperature is not sent. Catalog prices per million tokens are $2 input, $10 output, $0.20 cache reads, and $2.50 cache writes. | |
| 756 | + | ||
| 755 | 757 | GPT-6 Sol and GPT-6 Luna are available through OpenAI API keys, OpenAI Codex subscriptions, and GitHub Copilot. Select `gpt-6-sol` or `gpt-6-luna` with `--provider openai`, `openai-responses`, `openai-codex`, or `github-copilot`. Both use the Responses API, support up to 128,000 output tokens, and expose `low` through `max` reasoning. OpenAI and Codex entries use a 272,000-token context window, while Copilot entries use 1,000,000 tokens. Access through subscription providers depends on account availability and organization policy. | |
| 756 | 758 | ||
| 757 | 759 | You can add additional Gemini model IDs to `models.json` under the `google` provider. | |
| @@ -752,6 +752,8 @@ Reasoning levels (`--reasoning off|minimum|low|medium|high|xhigh|max`, also conf | |||||
| 752 | 752 | ||||
| 753 | Claude Opus 5.5 is available through Anthropic (`--provider anthropic --model claude-opus-5-5`) and GitHub Copilot (`--provider github-copilot --model claude-opus-5.5`, subject to account availability). Both entries support a 1,000,000-token context window and up to 128,000 output tokens. Adaptive thinking is always on, with `low`, `medium`, `high`, `xhigh`, and `max` effort levels. The default, `off`, and `minimum` requests use `low` instead. The reasoning selector omits unsupported levels. Catalog prices per million tokens are $4 input, $20 output, $0.20 cache reads, and $5 cache writes. Copilot billing remains governed by your plan. | 753 | Claude Opus 5.5 is available through Anthropic (`--provider anthropic --model claude-opus-5-5`) and GitHub Copilot (`--provider github-copilot --model claude-opus-5.5`, subject to account availability). Both entries support a 1,000,000-token context window and up to 128,000 output tokens. Adaptive thinking is always on, with `low`, `medium`, `high`, `xhigh`, and `max` effort levels. The default, `off`, and `minimum` requests use `low` instead. The reasoning selector omits unsupported levels. Catalog prices per million tokens are $4 input, $20 output, $0.20 cache reads, and $5 cache writes. Copilot billing remains governed by your plan. | ||
| 754 | 754 | ||||
| 755 | + | Claude Sonnet 5.5 is available through Anthropic (`--provider anthropic --model claude-sonnet-5-5`). It supports a 1,000,000-token context window and up to 128,000 output tokens. Adaptive thinking is always on with the same `low` through `max` effort levels as Opus 5.5, and `off` or `minimum` requests use `low`. Temperature is not sent. Catalog prices per million tokens are $2 input, $10 output, $0.20 cache reads, and $2.50 cache writes. | |||
| 756 | + | ||||
| 755 | GPT-6 Sol and GPT-6 Luna are available through OpenAI API keys, OpenAI Codex subscriptions, and GitHub Copilot. Select `gpt-6-sol` or `gpt-6-luna` with `--provider openai`, `openai-responses`, `openai-codex`, or `github-copilot`. Both use the Responses API, support up to 128,000 output tokens, and expose `low` through `max` reasoning. OpenAI and Codex entries use a 272,000-token context window, while Copilot entries use 1,000,000 tokens. Access through subscription providers depends on account availability and organization policy. | 757 | GPT-6 Sol and GPT-6 Luna are available through OpenAI API keys, OpenAI Codex subscriptions, and GitHub Copilot. Select `gpt-6-sol` or `gpt-6-luna` with `--provider openai`, `openai-responses`, `openai-codex`, or `github-copilot`. Both use the Responses API, support up to 128,000 output tokens, and expose `low` through `max` reasoning. OpenAI and Codex entries use a 272,000-token context window, while Copilot entries use 1,000,000 tokens. Access through subscription providers depends on account availability and organization policy. | ||
| 756 | 758 | ||||
| 757 | You can add additional Gemini model IDs to `models.json` under the `google` provider. | 759 | You can add additional Gemini model IDs to `models.json` under the `google` provider. | ||
M packages/provider/models.go +5 -0
| @@ -344,6 +344,11 @@ var Catalog = []Model{ | |||
| 344 | 344 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | |
| 345 | 345 | PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.2, PriceCacheWrite: 2.5, | |
| 346 | 346 | }, | |
| 347 | + | { | |
| 348 | + | Provider: "anthropic", ID: "claude-sonnet-5-5", DisplayName: "Claude Sonnet 5.5", | |
| 349 | + | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | |
| 350 | + | PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.2, PriceCacheWrite: 2.5, | |
| 351 | + | }, | |
| 347 | 352 | { | |
| 348 | 353 | Provider: "anthropic", ID: "claude-fable-5", DisplayName: "Claude Fable 5", | |
| 349 | 354 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | |
| @@ -344,6 +344,11 @@ var Catalog = []Model{ | |||||
| 344 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | 344 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | ||
| 345 | PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.2, PriceCacheWrite: 2.5, | 345 | PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.2, PriceCacheWrite: 2.5, | ||
| 346 | }, | 346 | }, | ||
| 347 | + | { | |||
| 348 | + | Provider: "anthropic", ID: "claude-sonnet-5-5", DisplayName: "Claude Sonnet 5.5", | |||
| 349 | + | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | |||
| 350 | + | PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.2, PriceCacheWrite: 2.5, | |||
| 351 | + | }, | |||
| 347 | { | 352 | { | ||
| 348 | Provider: "anthropic", ID: "claude-fable-5", DisplayName: "Claude Fable 5", | 353 | Provider: "anthropic", ID: "claude-fable-5", DisplayName: "Claude Fable 5", | ||
| 349 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | 354 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | ||
M packages/provider/reasoning.go +14 -2
| @@ -44,8 +44,9 @@ func defaultReasoningLevels(model Model) []string { | |||
| 44 | 44 | } | |
| 45 | 45 | ||
| 46 | 46 | id := strings.ToLower(model.ID) | |
| 47 | - | // Opus 5.5 always uses adaptive thinking, including at its lowest effort. | |
| 48 | - | if id == "claude-opus-5-5" || id == "claude-opus-5.5" { | |
| 47 | + | // Opus 5.5 and Sonnet 5.5 always use adaptive thinking, including at | |
| 48 | + | // their lowest effort. | |
| 49 | + | if alwaysOnAdaptiveThinking(id) { | |
| 49 | 50 | return []string{"low", "medium", "high", "xhigh", "max"} | |
| 50 | 51 | } | |
| 51 | 52 | if (model.Provider == "google" || model.Provider == "google-vertex") && strings.Contains(id, "gemini-3") { | |
| @@ -86,6 +87,17 @@ func defaultReasoningLevels(model Model) []string { | |||
| 86 | 87 | return []string{"", "low", "medium", "high"} | |
| 87 | 88 | } | |
| 88 | 89 | ||
| 90 | + | // alwaysOnAdaptiveThinking reports whether a Claude model rejects thinking | |
| 91 | + | // being turned off. Both the hyphenated Anthropic ID and the dotted Copilot | |
| 92 | + | // ID are recognised. | |
| 93 | + | func alwaysOnAdaptiveThinking(id string) bool { | |
| 94 | + | switch id { | |
| 95 | + | case "claude-opus-5-5", "claude-opus-5.5", "claude-sonnet-5-5", "claude-sonnet-5.5": | |
| 96 | + | return true | |
| 97 | + | } | |
| 98 | + | return false | |
| 99 | + | } | |
| 100 | + | ||
| 89 | 101 | func containsReasoningLevel(levels []string, target string) bool { | |
| 90 | 102 | for _, level := range levels { | |
| 91 | 103 | if level == target { | |
| @@ -44,8 +44,9 @@ func defaultReasoningLevels(model Model) []string { | |||||
| 44 | } | 44 | } | ||
| 45 | 45 | ||||
| 46 | id := strings.ToLower(model.ID) | 46 | id := strings.ToLower(model.ID) | ||
| 47 | - | // Opus 5.5 always uses adaptive thinking, including at its lowest effort. | 47 | + | // Opus 5.5 and Sonnet 5.5 always use adaptive thinking, including at |
| 48 | - | if id == "claude-opus-5-5" || id == "claude-opus-5.5" { | 48 | + | // their lowest effort. |
| 49 | + | if alwaysOnAdaptiveThinking(id) { | |||
| 49 | return []string{"low", "medium", "high", "xhigh", "max"} | 50 | return []string{"low", "medium", "high", "xhigh", "max"} | ||
| 50 | } | 51 | } | ||
| 51 | if (model.Provider == "google" || model.Provider == "google-vertex") && strings.Contains(id, "gemini-3") { | 52 | if (model.Provider == "google" || model.Provider == "google-vertex") && strings.Contains(id, "gemini-3") { | ||
| @@ -86,6 +87,17 @@ func defaultReasoningLevels(model Model) []string { | |||||
| 86 | return []string{"", "low", "medium", "high"} | 87 | return []string{"", "low", "medium", "high"} | ||
| 87 | } | 88 | } | ||
| 88 | 89 | ||||
| 90 | + | // alwaysOnAdaptiveThinking reports whether a Claude model rejects thinking | |||
| 91 | + | // being turned off. Both the hyphenated Anthropic ID and the dotted Copilot | |||
| 92 | + | // ID are recognised. | |||
| 93 | + | func alwaysOnAdaptiveThinking(id string) bool { | |||
| 94 | + | switch id { | |||
| 95 | + | case "claude-opus-5-5", "claude-opus-5.5", "claude-sonnet-5-5", "claude-sonnet-5.5": | |||
| 96 | + | return true | |||
| 97 | + | } | |||
| 98 | + | return false | |||
| 99 | + | } | |||
| 100 | + | ||||
| 89 | func containsReasoningLevel(levels []string, target string) bool { | 101 | func containsReasoningLevel(levels []string, target string) bool { | ||
| 90 | for _, level := range levels { | 102 | for _, level := range levels { | ||
| 91 | if level == target { | 103 | if level == target { | ||
A packages/provider/sonnet55_test.go +72 -0
| @@ -0,0 +1,72 @@ | |||
| 1 | + | package provider | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "slices" | |
| 5 | + | "testing" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | func TestSonnet55CatalogAndThinking(t *testing.T) { | |
| 9 | + | for _, tc := range []struct { | |
| 10 | + | name string | |
| 11 | + | client *anthropicClient | |
| 12 | + | }{ | |
| 13 | + | {"api-key", NewAnthropic("synthetic-key", "").(*anthropicClient)}, | |
| 14 | + | {"oauth", NewAnthropicOAuth("synthetic-token", "").(*anthropicClient)}, | |
| 15 | + | } { | |
| 16 | + | t.Run(tc.name, func(t *testing.T) { | |
| 17 | + | const id = "claude-sonnet-5-5" | |
| 18 | + | m, err := FindModel("anthropic", id) | |
| 19 | + | if err != nil { | |
| 20 | + | t.Fatal(err) | |
| 21 | + | } | |
| 22 | + | if m.ContextWindow != 1000000 || m.MaxOutput != 128000 || !m.Reasoning || !m.AdaptiveThinking || m.Speculative { | |
| 23 | + | t.Fatalf("unexpected capabilities: %+v", m) | |
| 24 | + | } | |
| 25 | + | if m.PriceInput != 2 || m.PriceOutput != 10 || m.PriceCacheRead != 0.2 || m.PriceCacheWrite != 2.5 { | |
| 26 | + | t.Fatalf("unexpected pricing: %+v", m) | |
| 27 | + | } | |
| 28 | + | want := []string{"low", "medium", "high", "xhigh", "max"} | |
| 29 | + | if got := AvailableReasoningLevels(m); !slices.Equal(got, want) { | |
| 30 | + | t.Fatalf("levels = %q, want %q", got, want) | |
| 31 | + | } | |
| 32 | + | for _, level := range []string{"", "off", "minimum", "low", "medium", "high", "xhigh", "max"} { | |
| 33 | + | effort := level | |
| 34 | + | if level == "" || level == "off" || level == "minimum" { | |
| 35 | + | effort = "low" | |
| 36 | + | } | |
| 37 | + | if got := ClampReasoningForModel(m, level); got != effort { | |
| 38 | + | t.Errorf("clamp(%q) = %q, want %q", level, got, effort) | |
| 39 | + | } | |
| 40 | + | temperature := float32(0.5) | |
| 41 | + | wire, err := tc.client.buildRequest(Request{Model: id, Reasoning: level, Temperature: &temperature}) | |
| 42 | + | if err != nil { | |
| 43 | + | t.Fatal(err) | |
| 44 | + | } | |
| 45 | + | if wire.Model != id || wire.MaxTokens != 128000 || wire.Temperature != nil { | |
| 46 | + | t.Fatalf("unexpected request: %+v", wire) | |
| 47 | + | } | |
| 48 | + | if wire.Thinking == nil || wire.Thinking.Type != "adaptive" || wire.Thinking.BudgetTokens != 0 { | |
| 49 | + | t.Fatalf("thinking = %+v", wire.Thinking) | |
| 50 | + | } | |
| 51 | + | if wire.OutputConfig == nil || wire.OutputConfig.Effort != effort { | |
| 52 | + | t.Fatalf("output config = %+v, want effort %q", wire.OutputConfig, effort) | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | }) | |
| 56 | + | } | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | func TestSonnet55ReasoningOverrides(t *testing.T) { | |
| 60 | + | m, err := FindModel("anthropic", "claude-sonnet-5-5") | |
| 61 | + | if err != nil { | |
| 62 | + | t.Fatal(err) | |
| 63 | + | } | |
| 64 | + | m.ReasoningLevelMap = map[string]string{"max": "xhigh"} | |
| 65 | + | want := []string{"low", "medium", "high", "xhigh"} | |
| 66 | + | if got := AvailableReasoningLevels(m); !slices.Equal(got, want) { | |
| 67 | + | t.Fatalf("levels = %q, want %q", got, want) | |
| 68 | + | } | |
| 69 | + | if got := ClampReasoningForModel(m, "off"); got != "low" { | |
| 70 | + | t.Fatalf("off clamped to %q, want low", got) | |
| 71 | + | } | |
| 72 | + | } | |
| @@ -0,0 +1,72 @@ | |||||
| 1 | + | package provider | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "slices" | |||
| 5 | + | "testing" | |||
| 6 | + | ) | |||
| 7 | + | ||||
| 8 | + | func TestSonnet55CatalogAndThinking(t *testing.T) { | |||
| 9 | + | for _, tc := range []struct { | |||
| 10 | + | name string | |||
| 11 | + | client *anthropicClient | |||
| 12 | + | }{ | |||
| 13 | + | {"api-key", NewAnthropic("synthetic-key", "").(*anthropicClient)}, | |||
| 14 | + | {"oauth", NewAnthropicOAuth("synthetic-token", "").(*anthropicClient)}, | |||
| 15 | + | } { | |||
| 16 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 17 | + | const id = "claude-sonnet-5-5" | |||
| 18 | + | m, err := FindModel("anthropic", id) | |||
| 19 | + | if err != nil { | |||
| 20 | + | t.Fatal(err) | |||
| 21 | + | } | |||
| 22 | + | if m.ContextWindow != 1000000 || m.MaxOutput != 128000 || !m.Reasoning || !m.AdaptiveThinking || m.Speculative { | |||
| 23 | + | t.Fatalf("unexpected capabilities: %+v", m) | |||
| 24 | + | } | |||
| 25 | + | if m.PriceInput != 2 || m.PriceOutput != 10 || m.PriceCacheRead != 0.2 || m.PriceCacheWrite != 2.5 { | |||
| 26 | + | t.Fatalf("unexpected pricing: %+v", m) | |||
| 27 | + | } | |||
| 28 | + | want := []string{"low", "medium", "high", "xhigh", "max"} | |||
| 29 | + | if got := AvailableReasoningLevels(m); !slices.Equal(got, want) { | |||
| 30 | + | t.Fatalf("levels = %q, want %q", got, want) | |||
| 31 | + | } | |||
| 32 | + | for _, level := range []string{"", "off", "minimum", "low", "medium", "high", "xhigh", "max"} { | |||
| 33 | + | effort := level | |||
| 34 | + | if level == "" || level == "off" || level == "minimum" { | |||
| 35 | + | effort = "low" | |||
| 36 | + | } | |||
| 37 | + | if got := ClampReasoningForModel(m, level); got != effort { | |||
| 38 | + | t.Errorf("clamp(%q) = %q, want %q", level, got, effort) | |||
| 39 | + | } | |||
| 40 | + | temperature := float32(0.5) | |||
| 41 | + | wire, err := tc.client.buildRequest(Request{Model: id, Reasoning: level, Temperature: &temperature}) | |||
| 42 | + | if err != nil { | |||
| 43 | + | t.Fatal(err) | |||
| 44 | + | } | |||
| 45 | + | if wire.Model != id || wire.MaxTokens != 128000 || wire.Temperature != nil { | |||
| 46 | + | t.Fatalf("unexpected request: %+v", wire) | |||
| 47 | + | } | |||
| 48 | + | if wire.Thinking == nil || wire.Thinking.Type != "adaptive" || wire.Thinking.BudgetTokens != 0 { | |||
| 49 | + | t.Fatalf("thinking = %+v", wire.Thinking) | |||
| 50 | + | } | |||
| 51 | + | if wire.OutputConfig == nil || wire.OutputConfig.Effort != effort { | |||
| 52 | + | t.Fatalf("output config = %+v, want effort %q", wire.OutputConfig, effort) | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | }) | |||
| 56 | + | } | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | func TestSonnet55ReasoningOverrides(t *testing.T) { | |||
| 60 | + | m, err := FindModel("anthropic", "claude-sonnet-5-5") | |||
| 61 | + | if err != nil { | |||
| 62 | + | t.Fatal(err) | |||
| 63 | + | } | |||
| 64 | + | m.ReasoningLevelMap = map[string]string{"max": "xhigh"} | |||
| 65 | + | want := []string{"low", "medium", "high", "xhigh"} | |||
| 66 | + | if got := AvailableReasoningLevels(m); !slices.Equal(got, want) { | |||
| 67 | + | t.Fatalf("levels = %q, want %q", got, want) | |||
| 68 | + | } | |||
| 69 | + | if got := ClampReasoningForModel(m, "off"); got != "low" { | |||
| 70 | + | t.Fatalf("off clamped to %q, want low", got) | |||
| 71 | + | } | |||
| 72 | + | } | |||