fix: 临时移除context-1m-2025-08-07以确保避免sonnet1m触发429

This commit is contained in:
shaw
2026-02-18 18:41:30 +08:00
parent b41fa5e15f
commit 074bd0dfda
3 changed files with 92 additions and 4 deletions

View File

@@ -10,6 +10,7 @@ const (
BetaInterleavedThinking = "interleaved-thinking-2025-05-14" BetaInterleavedThinking = "interleaved-thinking-2025-05-14"
BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14" BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14"
BetaTokenCounting = "token-counting-2024-11-01" BetaTokenCounting = "token-counting-2024-11-01"
BetaContext1M = "context-1m-2025-08-07"
) )
// DefaultBetaHeader Claude Code 客户端默认的 anthropic-beta header // DefaultBetaHeader Claude Code 客户端默认的 anthropic-beta header

View File

@@ -21,3 +21,72 @@ func TestMergeAnthropicBeta_EmptyIncoming(t *testing.T) {
) )
require.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14", got) require.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14", got)
} }
func TestStripBetaToken(t *testing.T) {
tests := []struct {
name string
header string
token string
want string
}{
{
name: "token in middle",
header: "oauth-2025-04-20,context-1m-2025-08-07,interleaved-thinking-2025-05-14",
token: "context-1m-2025-08-07",
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
},
{
name: "token at start",
header: "context-1m-2025-08-07,oauth-2025-04-20,interleaved-thinking-2025-05-14",
token: "context-1m-2025-08-07",
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
},
{
name: "token at end",
header: "oauth-2025-04-20,interleaved-thinking-2025-05-14,context-1m-2025-08-07",
token: "context-1m-2025-08-07",
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
},
{
name: "token not present",
header: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
token: "context-1m-2025-08-07",
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
},
{
name: "empty header",
header: "",
token: "context-1m-2025-08-07",
want: "",
},
{
name: "with spaces",
header: "oauth-2025-04-20, context-1m-2025-08-07 , interleaved-thinking-2025-05-14",
token: "context-1m-2025-08-07",
want: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
},
{
name: "only token",
header: "context-1m-2025-08-07",
token: "context-1m-2025-08-07",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := stripBetaToken(tt.header, tt.token)
require.Equal(t, tt.want, got)
})
}
}
func TestMergeAnthropicBetaDropping_Context1M(t *testing.T) {
required := []string{"oauth-2025-04-20", "interleaved-thinking-2025-05-14"}
incoming := "context-1m-2025-08-07,foo-beta,oauth-2025-04-20"
drop := map[string]struct{}{"context-1m-2025-08-07": {}}
got := mergeAnthropicBetaDropping(required, incoming, drop)
require.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14,foo-beta", got)
require.NotContains(t, got, "context-1m-2025-08-07")
}

View File

@@ -3553,12 +3553,12 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
// messages requests typically use only oauth + interleaved-thinking. // messages requests typically use only oauth + interleaved-thinking.
// Also drop claude-code beta if a downstream client added it. // Also drop claude-code beta if a downstream client added it.
requiredBetas := []string{claude.BetaOAuth, claude.BetaInterleavedThinking} requiredBetas := []string{claude.BetaOAuth, claude.BetaInterleavedThinking}
drop := map[string]struct{}{claude.BetaClaudeCode: {}} drop := map[string]struct{}{claude.BetaClaudeCode: {}, claude.BetaContext1M: {}}
req.Header.Set("anthropic-beta", mergeAnthropicBetaDropping(requiredBetas, incomingBeta, drop)) req.Header.Set("anthropic-beta", mergeAnthropicBetaDropping(requiredBetas, incomingBeta, drop))
} else { } else {
// Claude Code 客户端:尽量透传原始 header仅补齐 oauth beta // Claude Code 客户端:尽量透传原始 header仅补齐 oauth beta
clientBetaHeader := req.Header.Get("anthropic-beta") clientBetaHeader := req.Header.Get("anthropic-beta")
req.Header.Set("anthropic-beta", s.getBetaHeader(modelID, clientBetaHeader)) req.Header.Set("anthropic-beta", stripBetaToken(s.getBetaHeader(modelID, clientBetaHeader), claude.BetaContext1M))
} }
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" { } else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {
// API-key仅在请求显式使用 beta 特性且客户端未提供时,按需补齐(默认关闭) // API-key仅在请求显式使用 beta 特性且客户端未提供时,按需补齐(默认关闭)
@@ -3712,6 +3712,23 @@ func mergeAnthropicBetaDropping(required []string, incoming string, drop map[str
return strings.Join(out, ",") return strings.Join(out, ",")
} }
// stripBetaToken removes a single beta token from a comma-separated header value.
// It short-circuits when the token is not present to avoid unnecessary allocations.
func stripBetaToken(header, token string) string {
if !strings.Contains(header, token) {
return header
}
out := make([]string, 0, 8)
for _, p := range strings.Split(header, ",") {
p = strings.TrimSpace(p)
if p == "" || p == token {
continue
}
out = append(out, p)
}
return strings.Join(out, ",")
}
// applyClaudeCodeMimicHeaders forces "Claude Code-like" request headers. // applyClaudeCodeMimicHeaders forces "Claude Code-like" request headers.
// This mirrors opencode-anthropic-auth behavior: do not trust downstream // This mirrors opencode-anthropic-auth behavior: do not trust downstream
// headers when using Claude Code-scoped OAuth credentials. // headers when using Claude Code-scoped OAuth credentials.
@@ -5236,7 +5253,8 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
incomingBeta := req.Header.Get("anthropic-beta") incomingBeta := req.Header.Get("anthropic-beta")
requiredBetas := []string{claude.BetaClaudeCode, claude.BetaOAuth, claude.BetaInterleavedThinking, claude.BetaTokenCounting} requiredBetas := []string{claude.BetaClaudeCode, claude.BetaOAuth, claude.BetaInterleavedThinking, claude.BetaTokenCounting}
req.Header.Set("anthropic-beta", mergeAnthropicBeta(requiredBetas, incomingBeta)) drop := map[string]struct{}{claude.BetaContext1M: {}}
req.Header.Set("anthropic-beta", mergeAnthropicBetaDropping(requiredBetas, incomingBeta, drop))
} else { } else {
clientBetaHeader := req.Header.Get("anthropic-beta") clientBetaHeader := req.Header.Get("anthropic-beta")
if clientBetaHeader == "" { if clientBetaHeader == "" {
@@ -5246,7 +5264,7 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
if !strings.Contains(beta, claude.BetaTokenCounting) { if !strings.Contains(beta, claude.BetaTokenCounting) {
beta = beta + "," + claude.BetaTokenCounting beta = beta + "," + claude.BetaTokenCounting
} }
req.Header.Set("anthropic-beta", beta) req.Header.Set("anthropic-beta", stripBetaToken(beta, claude.BetaContext1M))
} }
} }
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" { } else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {