feat(gateway): add billing attribution block with cc_version fingerprint

Real Claude Code CLI always sends a 2-block system array:

  [0] {"type":"text", "text":"x-anthropic-billing-header: cc_version=X.Y.Z.{fp}; cc_entrypoint=cli; cch=00000;"}
  [1] {"type":"text", "text":"You are Claude Code...", "cache_control":{...}}

Before this commit, sub2api's mimicry path only produced block [1].
The missing billing block is one of the primary third-party detection
signals Anthropic uses for Claude-Code-scoped OAuth tokens.

New file gateway_billing_block.go ports the fingerprint algorithm
(byte-for-byte from Parrot cc_mimicry.py:compute_fingerprint):
pick chars at positions [4,7,20] of the first user text, then
`sha256(SALT + chars + cc_version)[:3]`.

  - claude/constants.go: CLICurrentVersion = "2.1.92" (must match UA)
  - gateway_billing_block.go: computeClaudeCodeFingerprint +
    buildBillingAttributionBlockJSON + extractFirstUserText
  - gateway_service.go: rewriteSystemForNonClaudeCode now emits both
    blocks in order; cch=00000 is filled in later by
    signBillingHeaderCCH in buildUpstreamRequest.

Downstream compat note: syncBillingHeaderVersion's regex
`cc_version=\d+\.\d+\.\d+` only matches the semver triple,
leaving the `.{fp}` suffix intact when rewriting in buildUpstreamRequest.
This commit is contained in:
keh4l
2026-04-24 20:41:15 +08:00
parent 66d6454535
commit 5862e2d8d9
3 changed files with 116 additions and 10 deletions

View File

@@ -3853,17 +3853,20 @@ func rewriteSystemForNonClaudeCode(body []byte, system any) []byte {
originalSystemText = strings.Join(parts, "\n\n")
}
// 2. system 替换为 Claude Code 标准提示词array 格式,与真实 Claude Code 一致)
// 真实 Claude Code 始终以 [{type: "text", text: "...", cache_control: {type: "ephemeral"}}] 发送 system。
// 使用 string 格式会被 Anthropic 检测为第三方应用。
claudeCodeSystemBlock := []map[string]any{
{
"type": "text",
"text": claudeCodeSystemPrompt,
"cache_control": map[string]string{"type": "ephemeral", "ttl": claude.DefaultCacheControlTTL},
},
// 2. 构造 system 数组,对齐真实 Claude Code CLI 的 2-block 形态:
// [0] billing attribution blockcc_version={cliVer}.{fp}; cc_entrypoint=cli; cch=00000;
// [1] "You are Claude Code..." prompt block带 cache_control 作为稳定缓存断点)
//
// billing block 的 cch=00000 是占位符,会被 buildUpstreamRequest 里的
// signBillingHeaderCCH 替换成 xxhash64 签名。缺失 billing block 的系统 payload
// 是 Anthropic 判定第三方的关键信号之一(真实 CLI 每个请求都带)。
billingBlock, billingErr := buildBillingAttributionBlockJSON(body, claude.CLICurrentVersion)
ccPromptBlock, ccErr := marshalAnthropicSystemTextBlock(claudeCodeSystemPrompt, true)
if billingErr != nil || ccErr != nil {
logger.LegacyPrintf("service.gateway", "Warning: failed to build system blocks (billing=%v, cc=%v)", billingErr, ccErr)
return body
}
out, ok := setJSONValueBytes(body, "system", claudeCodeSystemBlock)
out, ok := setJSONRawBytes(body, "system", buildJSONArrayRaw([][]byte{billingBlock, ccPromptBlock}))
if !ok {
logger.LegacyPrintf("service.gateway", "Warning: failed to set Claude Code system prompt")
return body