Add Claude Fable 5.1 support
- commit
- fdc66430918ef021cad5f4404e033bcd2460b373
- author
- patriceckhart · · 2026-09-02 06:47 UTC
- parent
- 4441e91
- signature
- unsigned
- refs
- tag: v0.3.55
3 files · +67 -4
M packages/provider/anthropic.go +1 -1
| @@ -21,7 +21,7 @@ const anthropicAPIVersion = "2023-06-01" | |||
| 21 | 21 | // accepts the request; diverging from them causes 429 rate_limit_error | |
| 22 | 22 | // or 403 on the very first request. | |
| 23 | 23 | const ( | |
| 24 | - | claudeCodeVersion = "2.1.75" | |
| 24 | + | claudeCodeVersion = "2.1.258" | |
| 25 | 25 | claudeCodeIdentity = "You are Claude Code, Anthropic's official CLI for Claude." | |
| 26 | 26 | ) | |
| 27 | 27 | ||
| @@ -21,7 +21,7 @@ const anthropicAPIVersion = "2023-06-01" | |||||
| 21 | // accepts the request; diverging from them causes 429 rate_limit_error | 21 | // accepts the request; diverging from them causes 429 rate_limit_error | ||
| 22 | // or 403 on the very first request. | 22 | // or 403 on the very first request. | ||
| 23 | const ( | 23 | const ( | ||
| 24 | - | claudeCodeVersion = "2.1.75" | 24 | + | claudeCodeVersion = "2.1.258" |
| 25 | claudeCodeIdentity = "You are Claude Code, Anthropic's official CLI for Claude." | 25 | claudeCodeIdentity = "You are Claude Code, Anthropic's official CLI for Claude." | ||
| 26 | ) | 26 | ) | ||
| 27 | 27 | ||||
M packages/provider/models.go +7 -3
| @@ -272,7 +272,7 @@ var Catalog = []Model{ | |||
| 272 | 272 | BaseURL: gondolaDefaultBaseURL, | |
| 273 | 273 | }, | |
| 274 | 274 | ||
| 275 | - | // ---- Speculative: Anthropic ---- | |
| 275 | + | // ---- Anthropic / newer families ---- | |
| 276 | 276 | { | |
| 277 | 277 | Provider: "anthropic", ID: "claude-opus-4-5", DisplayName: "Claude Opus 4.5 (latest)", | |
| 278 | 278 | ContextWindow: 200000, MaxOutput: 64000, Reasoning: true, | |
| @@ -316,8 +316,12 @@ var Catalog = []Model{ | |||
| 316 | 316 | { | |
| 317 | 317 | Provider: "anthropic", ID: "claude-fable-5", DisplayName: "Claude Fable 5", | |
| 318 | 318 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | |
| 319 | - | PriceInput: 10, PriceOutput: 50, PriceCacheRead: 0.5, PriceCacheWrite: 6.25, | |
| 320 | - | Speculative: true, | |
| 319 | + | PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5, | |
| 320 | + | }, | |
| 321 | + | { | |
| 322 | + | Provider: "anthropic", ID: "claude-fable-5-1", DisplayName: "Claude Fable 5.1", | |
| 323 | + | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | |
| 324 | + | PriceInput: 10, PriceOutput: 50, PriceCacheRead: 0.25, PriceCacheWrite: 12.5, | |
| 321 | 325 | }, | |
| 322 | 326 | ||
| 323 | 327 | // ---- Speculative: OpenAI ---- | |
| @@ -272,7 +272,7 @@ var Catalog = []Model{ | |||||
| 272 | BaseURL: gondolaDefaultBaseURL, | 272 | BaseURL: gondolaDefaultBaseURL, | ||
| 273 | }, | 273 | }, | ||
| 274 | 274 | ||||
| 275 | - | // ---- Speculative: Anthropic ---- | 275 | + | // ---- Anthropic / newer families ---- |
| 276 | { | 276 | { | ||
| 277 | Provider: "anthropic", ID: "claude-opus-4-5", DisplayName: "Claude Opus 4.5 (latest)", | 277 | Provider: "anthropic", ID: "claude-opus-4-5", DisplayName: "Claude Opus 4.5 (latest)", | ||
| 278 | ContextWindow: 200000, MaxOutput: 64000, Reasoning: true, | 278 | ContextWindow: 200000, MaxOutput: 64000, Reasoning: true, | ||
| @@ -316,8 +316,12 @@ var Catalog = []Model{ | |||||
| 316 | { | 316 | { | ||
| 317 | Provider: "anthropic", ID: "claude-fable-5", DisplayName: "Claude Fable 5", | 317 | Provider: "anthropic", ID: "claude-fable-5", DisplayName: "Claude Fable 5", | ||
| 318 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | 318 | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | ||
| 319 | - | PriceInput: 10, PriceOutput: 50, PriceCacheRead: 0.5, PriceCacheWrite: 6.25, | 319 | + | PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5, |
| 320 | - | Speculative: true, | 320 | + | }, |
| 321 | + | { | |||
| 322 | + | Provider: "anthropic", ID: "claude-fable-5-1", DisplayName: "Claude Fable 5.1", | |||
| 323 | + | ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, AdaptiveThinking: true, | |||
| 324 | + | PriceInput: 10, PriceOutput: 50, PriceCacheRead: 0.25, PriceCacheWrite: 12.5, | |||
| 321 | }, | 325 | }, | ||
| 322 | 326 | ||||
| 323 | // ---- Speculative: OpenAI ---- | 327 | // ---- Speculative: OpenAI ---- | ||
M packages/provider/provider_test.go +59 -0
| @@ -96,6 +96,24 @@ func TestAnthropicErrorStatus(t *testing.T) { | |||
| 96 | 96 | } | |
| 97 | 97 | } | |
| 98 | 98 | ||
| 99 | + | func TestAnthropicOAuthUsesCurrentClaudeCodeVersion(t *testing.T) { | |
| 100 | + | var userAgent string | |
| 101 | + | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 102 | + | userAgent = r.Header.Get("user-agent") | |
| 103 | + | w.WriteHeader(http.StatusBadRequest) | |
| 104 | + | })) | |
| 105 | + | defer srv.Close() | |
| 106 | + | ||
| 107 | + | c := NewAnthropicOAuth("test-token", srv.URL) | |
| 108 | + | _, err := c.Stream(context.Background(), Request{Model: "claude-fable-5-1"}) | |
| 109 | + | if err == nil { | |
| 110 | + | t.Fatal("expected test server error") | |
| 111 | + | } | |
| 112 | + | if want := "claude-cli/2.1.258"; userAgent != want { | |
| 113 | + | t.Fatalf("user-agent = %q, want %q", userAgent, want) | |
| 114 | + | } | |
| 115 | + | } | |
| 116 | + | ||
| 99 | 117 | func TestOpenAIErrorStatus(t *testing.T) { | |
| 100 | 118 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 101 | 119 | w.WriteHeader(http.StatusBadRequest) | |
| @@ -382,6 +400,47 @@ func TestClaudeSonnet5Catalog(t *testing.T) { | |||
| 382 | 400 | } | |
| 383 | 401 | } | |
| 384 | 402 | ||
| 403 | + | func TestClaudeFable5Catalog(t *testing.T) { | |
| 404 | + | m, err := FindModel("anthropic", "claude-fable-5") | |
| 405 | + | if err != nil { | |
| 406 | + | t.Fatal(err) | |
| 407 | + | } | |
| 408 | + | if m.DisplayName != "Claude Fable 5" || m.ContextWindow != 1000000 || m.MaxOutput != 128000 || !m.Reasoning || !m.AdaptiveThinking || m.Speculative { | |
| 409 | + | t.Fatalf("unexpected Fable 5 model: %+v", m) | |
| 410 | + | } | |
| 411 | + | if m.PriceInput != 10 || m.PriceOutput != 50 || m.PriceCacheRead != 1 || m.PriceCacheWrite != 12.5 { | |
| 412 | + | t.Fatalf("unexpected Fable 5 pricing: %+v", m) | |
| 413 | + | } | |
| 414 | + | } | |
| 415 | + | ||
| 416 | + | func TestClaudeFable51Catalog(t *testing.T) { | |
| 417 | + | m, err := FindModel("anthropic", "claude-fable-5-1") | |
| 418 | + | if err != nil { | |
| 419 | + | t.Fatal(err) | |
| 420 | + | } | |
| 421 | + | if m.DisplayName != "Claude Fable 5.1" || m.ContextWindow != 1000000 || m.MaxOutput != 128000 || !m.Reasoning || !m.AdaptiveThinking || m.Speculative { | |
| 422 | + | t.Fatalf("unexpected Fable 5.1 model: %+v", m) | |
| 423 | + | } | |
| 424 | + | if m.PriceInput != 10 || m.PriceOutput != 50 || m.PriceCacheRead != 0.25 || m.PriceCacheWrite != 12.5 { | |
| 425 | + | t.Fatalf("unexpected Fable 5.1 pricing: %+v", m) | |
| 426 | + | } | |
| 427 | + | ||
| 428 | + | wire, err := NewAnthropic("x", "").(*anthropicClient).buildRequest(Request{ | |
| 429 | + | Model: m.ID, | |
| 430 | + | Reasoning: "max", | |
| 431 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |
| 432 | + | }) | |
| 433 | + | if err != nil { | |
| 434 | + | t.Fatal(err) | |
| 435 | + | } | |
| 436 | + | if wire.Thinking == nil || wire.Thinking.Type != "adaptive" || wire.Thinking.BudgetTokens != 0 { | |
| 437 | + | t.Fatalf("unexpected Fable 5.1 thinking config: %+v", wire.Thinking) | |
| 438 | + | } | |
| 439 | + | if wire.OutputConfig == nil || wire.OutputConfig.Effort != "max" { | |
| 440 | + | t.Fatalf("unexpected Fable 5.1 output config: %+v", wire.OutputConfig) | |
| 441 | + | } | |
| 442 | + | } | |
| 443 | + | ||
| 385 | 444 | func TestGondolaSeedCatalog(t *testing.T) { | |
| 386 | 445 | kimi, err := FindModel("gondola", "kimi-k3") | |
| 387 | 446 | if err != nil { | |
| @@ -96,6 +96,24 @@ func TestAnthropicErrorStatus(t *testing.T) { | |||||
| 96 | } | 96 | } | ||
| 97 | } | 97 | } | ||
| 98 | 98 | ||||
| 99 | + | func TestAnthropicOAuthUsesCurrentClaudeCodeVersion(t *testing.T) { | |||
| 100 | + | var userAgent string | |||
| 101 | + | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
| 102 | + | userAgent = r.Header.Get("user-agent") | |||
| 103 | + | w.WriteHeader(http.StatusBadRequest) | |||
| 104 | + | })) | |||
| 105 | + | defer srv.Close() | |||
| 106 | + | ||||
| 107 | + | c := NewAnthropicOAuth("test-token", srv.URL) | |||
| 108 | + | _, err := c.Stream(context.Background(), Request{Model: "claude-fable-5-1"}) | |||
| 109 | + | if err == nil { | |||
| 110 | + | t.Fatal("expected test server error") | |||
| 111 | + | } | |||
| 112 | + | if want := "claude-cli/2.1.258"; userAgent != want { | |||
| 113 | + | t.Fatalf("user-agent = %q, want %q", userAgent, want) | |||
| 114 | + | } | |||
| 115 | + | } | |||
| 116 | + | ||||
| 99 | func TestOpenAIErrorStatus(t *testing.T) { | 117 | func TestOpenAIErrorStatus(t *testing.T) { | ||
| 100 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 118 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| 101 | w.WriteHeader(http.StatusBadRequest) | 119 | w.WriteHeader(http.StatusBadRequest) | ||
| @@ -382,6 +400,47 @@ func TestClaudeSonnet5Catalog(t *testing.T) { | |||||
| 382 | } | 400 | } | ||
| 383 | } | 401 | } | ||
| 384 | 402 | ||||
| 403 | + | func TestClaudeFable5Catalog(t *testing.T) { | |||
| 404 | + | m, err := FindModel("anthropic", "claude-fable-5") | |||
| 405 | + | if err != nil { | |||
| 406 | + | t.Fatal(err) | |||
| 407 | + | } | |||
| 408 | + | if m.DisplayName != "Claude Fable 5" || m.ContextWindow != 1000000 || m.MaxOutput != 128000 || !m.Reasoning || !m.AdaptiveThinking || m.Speculative { | |||
| 409 | + | t.Fatalf("unexpected Fable 5 model: %+v", m) | |||
| 410 | + | } | |||
| 411 | + | if m.PriceInput != 10 || m.PriceOutput != 50 || m.PriceCacheRead != 1 || m.PriceCacheWrite != 12.5 { | |||
| 412 | + | t.Fatalf("unexpected Fable 5 pricing: %+v", m) | |||
| 413 | + | } | |||
| 414 | + | } | |||
| 415 | + | ||||
| 416 | + | func TestClaudeFable51Catalog(t *testing.T) { | |||
| 417 | + | m, err := FindModel("anthropic", "claude-fable-5-1") | |||
| 418 | + | if err != nil { | |||
| 419 | + | t.Fatal(err) | |||
| 420 | + | } | |||
| 421 | + | if m.DisplayName != "Claude Fable 5.1" || m.ContextWindow != 1000000 || m.MaxOutput != 128000 || !m.Reasoning || !m.AdaptiveThinking || m.Speculative { | |||
| 422 | + | t.Fatalf("unexpected Fable 5.1 model: %+v", m) | |||
| 423 | + | } | |||
| 424 | + | if m.PriceInput != 10 || m.PriceOutput != 50 || m.PriceCacheRead != 0.25 || m.PriceCacheWrite != 12.5 { | |||
| 425 | + | t.Fatalf("unexpected Fable 5.1 pricing: %+v", m) | |||
| 426 | + | } | |||
| 427 | + | ||||
| 428 | + | wire, err := NewAnthropic("x", "").(*anthropicClient).buildRequest(Request{ | |||
| 429 | + | Model: m.ID, | |||
| 430 | + | Reasoning: "max", | |||
| 431 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |||
| 432 | + | }) | |||
| 433 | + | if err != nil { | |||
| 434 | + | t.Fatal(err) | |||
| 435 | + | } | |||
| 436 | + | if wire.Thinking == nil || wire.Thinking.Type != "adaptive" || wire.Thinking.BudgetTokens != 0 { | |||
| 437 | + | t.Fatalf("unexpected Fable 5.1 thinking config: %+v", wire.Thinking) | |||
| 438 | + | } | |||
| 439 | + | if wire.OutputConfig == nil || wire.OutputConfig.Effort != "max" { | |||
| 440 | + | t.Fatalf("unexpected Fable 5.1 output config: %+v", wire.OutputConfig) | |||
| 441 | + | } | |||
| 442 | + | } | |||
| 443 | + | ||||
| 385 | func TestGondolaSeedCatalog(t *testing.T) { | 444 | func TestGondolaSeedCatalog(t *testing.T) { | ||
| 386 | kimi, err := FindModel("gondola", "kimi-k3") | 445 | kimi, err := FindModel("gondola", "kimi-k3") | ||
| 387 | if err != nil { | 446 | if err != nil { | ||