diff --git a/backend/internal/service/gateway_claude_oauth_body.go b/backend/internal/service/gateway_claude_oauth_body.go index 9fbbc674d8..9450ddbb11 100644 --- a/backend/internal/service/gateway_claude_oauth_body.go +++ b/backend/internal/service/gateway_claude_oauth_body.go @@ -1208,3 +1208,28 @@ func (s *GatewayService) claudeOAuthSystemPromptInjectionSettings(ctx context.Co } return s.settingService.GetClaudeOAuthSystemPromptInjectionSettings(ctx) } + +// systemHasBillingAttributionBlock 检查请求体的 system 字段中是否包含真实 Claude Code +// 客户端注入的 billing attribution block。该 block 格式稳定(见 gateway_billing_block.go), +// 仅由真实 Claude Code CLI 生成;第三方客户端(opencode 等)不会生成此 block。 +// +// 用于识别被上游 API 网关代理的真实 Claude Code 流量:此类请求的 User-Agent 被网关替换 +// 为 Go-http-client,但 body 保留了完整的客户端特征。如果不识别这类请求而走 mimicry +// 重写 system,会破坏 Anthropic prompt cache 的前缀一致性,导致 messages 级缓存永不命中。 +func systemHasBillingAttributionBlock(body []byte) bool { + system := gjson.GetBytes(body, "system") + if !system.IsArray() { + return false + } + found := false + system.ForEach(func(_, item gjson.Result) bool { + text := item.Get("text").String() + if strings.HasPrefix(text, claudeCodeBillingHeaderPrefix) && + strings.Contains(text, claudeCodeEntrypointMarker) { + found = true + return false + } + return true + }) + return found +} diff --git a/backend/internal/service/gateway_forward.go b/backend/internal/service/gateway_forward.go index 3005d93fa1..5a78a19817 100644 --- a/backend/internal/service/gateway_forward.go +++ b/backend/internal/service/gateway_forward.go @@ -170,6 +170,17 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A clientUserAgent = c.GetHeader("User-Agent") } isClaudeCode := IsClaudeCodeClient(ctx) || isClaudeCodeClient(clientUserAgent, parsed.MetadataUserID) + + // 补充判定:上游 API 网关(如 new-api)转发真实 Claude Code 流量时, + // UA 会变成 Go-http-client 但 body 保留了完整的 Claude Code 特征 + // (billing attribution block + metadata.user_id)。此时如果仍走 mimicry + // 重写 system prompt,会破坏 Anthropic prompt cache 的前缀匹配—— + // 导致 messages 级缓存永远 miss、cache_creation 每轮全量重写。 + // 通过检查 body 中的 billing attribution block 来识别被代理的真实 CC 流量。 + if !isClaudeCode && parsed.MetadataUserID != "" { + isClaudeCode = systemHasBillingAttributionBlock(body) + } + shouldMimicClaudeCode := account.IsOAuth() && !isClaudeCode if shouldMimicClaudeCode {