mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-20 00:18:55 +00:00
fix(gateway): detect proxied Claude Code traffic by body to preserve prompt cache
When an upstream API gateway (e.g. new-api) relays real Claude Code requests, the User-Agent becomes Go-http-client while the body retains the full Claude Code fingerprint (billing attribution block + metadata.user_id + cache_control breakpoints). Previously, the OAuth mimicry path relied solely on UA matching to detect Claude Code clients. Without a matching UA, the gateway would rewrite the system prompt — replacing the client's carefully structured system blocks and cache_control breakpoints with its own injection. This breaks Anthropic's prefix-based prompt cache: since the cache key evaluates tools → system → messages in order, a changed system invalidates all downstream message caching. Symptoms observed: - cache_read permanently locked at ~25K (only system prompt cached) - cache_creation growing monotonically every turn (full messages rewrite) - Single-request costs $17-27 instead of normal $1-2 Fix: when UA does not match but the body contains a valid billing attribution block (x-anthropic-billing-header with cc_entrypoint=), treat the request as proxied Claude Code traffic and skip mimicry. This preserves the client's original system structure and cache_control breakpoints, allowing Anthropic's prompt cache to function correctly.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user