fix(claude): gate fallback-credit beta on fallback tokens or explicit fallbacks

This commit is contained in:
sususu
2026-09-21 10:17:02 +08:00
parent fd5cd228a8
commit ed751ea08a
3 changed files with 49 additions and 21 deletions

View File

@@ -135,7 +135,7 @@ func isManagedClaudeBeta(beta string) bool {
// 11 advanced-tool-use-2025-11-20 requests using tool search or another advanced tool-use feature
// 12 effort-2025-11-24 effort-supporting models with active thinking
// 13 server-side-fallback-2026-06-01 requests with fallbacks or requested
// 14 fallback-credit-2026-06-01 OAuth credentials
// 14 fallback-credit-2026-06-01 requests with fallback tokens, fallbacks, or requested
// 15 structured-outputs-2025-12-15 structured output requests
// 16 thinking-display-updates-2026-08-18 requests with thinking.display=updates
// 17 fast-mode-2026-02-01 speed:fast requests only
@@ -177,7 +177,10 @@ func claudeCodeCLIBetas(body []byte, requested map[string]bool, oauthToken bool)
if !isProbeOrHelper && (requested[claudeServerSideFallbackBeta] || gjson.GetBytes(body, "fallbacks").Exists()) {
betas = append(betas, claudeServerSideFallbackBeta)
}
if requested[claudeFallbackCreditBeta] || oauthToken {
shouldIncludeFallbackCredit := requested[claudeFallbackCreditBeta] ||
gjson.GetBytes(body, "fallback_credit_token").Exists() ||
(oauthToken && gjson.GetBytes(body, "fallbacks").Exists())
if shouldIncludeFallbackCredit {
betas = append(betas, claudeFallbackCreditBeta)
}
for _, beta := range claudeCodeTrailingBetas {

View File

@@ -7990,8 +7990,7 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,advanced-tool-use-2025-11-20," +
"effort-2025-11-24,fallback-credit-2026-06-01," +
"extended-cache-ttl-2025-04-11",
"effort-2025-11-24,extended-cache-ttl-2025-04-11",
},
{
name: "oauth precedes context-1m",
@@ -8122,7 +8121,7 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"advisor-tool-2026-03-01,effort-2025-11-24,fallback-credit-2026-06-01," +
"advisor-tool-2026-03-01,effort-2025-11-24," +
"afk-mode-2026-01-31,extended-cache-ttl-2025-04-11",
},
{
@@ -8134,7 +8133,7 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"effort-2025-11-24,fallback-credit-2026-06-01,fast-mode-2026-02-01," +
"effort-2025-11-24,fast-mode-2026-02-01," +
"afk-mode-2026-01-31,extended-cache-ttl-2025-04-11",
},
{
@@ -8145,7 +8144,7 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"effort-2025-11-24,fallback-credit-2026-06-01,extended-cache-ttl-2025-04-11",
"effort-2025-11-24,extended-cache-ttl-2025-04-11",
},
{
name: "thinking display updates emits thinking-display-updates beta and drops redact-thinking",
@@ -8168,7 +8167,7 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"effort-2025-11-24,fallback-credit-2026-06-01",
"effort-2025-11-24",
},
{
name: "probe request max_tokens=1 omits effort and extended-cache-ttl betas",
@@ -8177,8 +8176,7 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
want: "claude-code-20250219,oauth-2025-04-20," +
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"fallback-credit-2026-06-01",
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07",
},
{
name: "haiku model omits effort beta even if requested",
@@ -8189,7 +8187,7 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05," +
"fallback-credit-2026-06-01,extended-cache-ttl-2025-04-11",
"extended-cache-ttl-2025-04-11",
},
{
name: "disabled thinking omits effort beta even if requested",
@@ -8200,7 +8198,39 @@ func TestClaudeCodeCLIBetas_MatchesObservedClientMatrix(t *testing.T) {
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"fallback-credit-2026-06-01,extended-cache-ttl-2025-04-11",
"extended-cache-ttl-2025-04-11",
},
{
name: "body with fallback_credit_token automatically adds fallback-credit beta",
body: `{"model":"claude-sonnet-5","fallback_credit_token":"fct_12345"}`,
oauth: true,
want: "claude-code-20250219,oauth-2025-04-20," +
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"effort-2025-11-24,fallback-credit-2026-06-01,extended-cache-ttl-2025-04-11",
},
{
name: "oauth body with fallbacks automatically adds fallback-credit beta",
body: `{"model":"claude-fable-5-1","fallbacks":[{"model":"claude-opus-5"}]}`,
oauth: true,
want: "claude-code-20250219,oauth-2025-04-20," +
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"effort-2025-11-24,server-side-fallback-2026-06-01,fallback-credit-2026-06-01," +
"extended-cache-ttl-2025-04-11",
},
{
name: "requested fallback-credit beta is honored",
body: `{"model":"claude-sonnet-5"}`,
requested: map[string]bool{claudeFallbackCreditBeta: true},
oauth: true,
want: "claude-code-20250219,oauth-2025-04-20," +
"interleaved-thinking-2025-05-14,redact-thinking-2026-02-12," +
"thinking-token-count-2026-05-13,context-management-2025-06-27," +
"prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07," +
"effort-2025-11-24,fallback-credit-2026-06-01,extended-cache-ttl-2025-04-11",
},
}

View File

@@ -235,7 +235,8 @@ func TestApplyClaudeHeaders_ClaudeCodeCLIProfileUsesOAuthBetasWithoutPretendingT
if errReq != nil {
t.Fatalf("NewRequest() error = %v", errReq)
}
if errHeaders := applyClaudeHeaders(req, auth, "key-third-party", false, nil, []byte(`{"model":"claude-sonnet-5"}`), &config.Config{}, nil, false, "11111111-2222-4333-8444-555555555555"); errHeaders != nil {
payload := []byte(`{"model":"claude-sonnet-5"}`)
if errHeaders := applyClaudeHeaders(req, auth, "key-third-party", false, nil, payload, &config.Config{}, nil, false, "11111111-2222-4333-8444-555555555555"); errHeaders != nil {
t.Fatalf("applyClaudeHeaders() error = %v", errHeaders)
}
if got := req.Header.Get("Authorization"); got != "Bearer key-third-party" {
@@ -245,14 +246,8 @@ func TestApplyClaudeHeaders_ClaudeCodeCLIProfileUsesOAuthBetasWithoutPretendingT
t.Fatalf("x-api-key = %q, want empty on third-party gateway", got)
}
betas := req.Header.Get("Anthropic-Beta")
if !strings.Contains(betas, "oauth-2025-04-20") {
t.Fatalf("Anthropic-Beta = %q, want oauth beta", betas)
}
if !strings.Contains(betas, "extended-cache-ttl-2025-04-11") {
t.Fatalf("Anthropic-Beta = %q, want extended-cache-ttl", betas)
}
if !strings.Contains(betas, "fallback-credit-2026-06-01") {
t.Fatalf("Anthropic-Beta = %q, want fallback-credit", betas)
if want := claudeCodeCLIBetas(payload, nil, true); betas != want {
t.Fatalf("Anthropic-Beta = %q, want %q", betas, want)
}
}