fix: 对齐 Claude/Codex UA 透传策略并完善 Claude cloaking#205
Conversation
📝 WalkthroughWalkthrough将 Codex 和 Claude 提供程序的 User-Agent 处理改为 CLI 感知的动态解析:保留符合 CLI 模式的客户端 UA,否则回退到默认 UA;同时调整头部透传逻辑以显式将 Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Adapter as Provider Adapter
participant Upstream
Client->>Adapter: 发起请求(含 User-Agent)
Adapter->>Adapter: isCodexCLIUserAgent / isClaudeCodeClient 检查 UA
alt UA 匹配 CLI 模式
Adapter->>Upstream: 透传客户端 User-Agent(保留 CLI UA)及其 headers/body
else 非 CLI
Adapter->>Adapter: 使用默认 codex/claude User-Agent
Adapter->>Upstream: 发送默认 User-Agent 与其它处理后的 headers/body
end
估计代码审查工作量🎯 3 (中等) | ⏱️ ~22 分钟 可能相关的 PR
推荐审查者
诗
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (3)
🧰 Additional context used🧬 Code graph analysis (1)internal/adapter/provider/custom/claude_body_test.go (1)
🔇 Additional comments (9)
✏️ Tip: You can disable this entire section by setting Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@internal/adapter/provider/custom/claude_body.go`:
- Around line 341-367: In checkSystemInstructionsWithMode, non-strict handling
currently only merges when system.IsArray(), so when "system" is a string its
original content is overwritten; update the function to detect when
system.IsString() (or otherwise non-array/non-empty) and build a new array that
prepends the claudeCodeInstructions while preserving the original string as a
text element, then write that array back with sjson.SetRawBytes (just like the
array branch does); keep the existing array branch logic for IsArray() and the
strictMode branch unchanged, and reference function
checkSystemInstructionsWithMode, the gjson.GetBytes result "system", and
sjson.SetRawBytes when implementing this fix.
In `@internal/adapter/provider/custom/claude_headers_test.go`:
- Around line 152-156: The test's userIDPattern in claude_headers_test.go is too
permissive (uses [\w-]+) and should match the production UUID v4 session format;
update the regex used by userIDPattern to enforce the UUID v4 pattern used in
claude_body.go (use [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
for the session portion after the fixed "user_[a-fA-F0-9]{64}_account__session_"
prefix), so the test validates the same strict format as the production
validation.
In `@internal/adapter/provider/custom/codex_headers.go`:
- Around line 56-59: The isCodexCLIUserAgent function uses strings.HasPrefix(ua,
"codex-cli") which is too broad; change the prefix check to require the
separator by using "codex-cli/" instead (i.e., replace "codex-cli" with
"codex-cli/") so it matches user agents like "codex-cli-custom/1.2.3" without
accidentally matching longer words like "codex-climax"; update this in the
isCodexCLIUserAgent implementation shown and make the same change in the
duplicate implementation in the other codex adapter file (the function named
isCodexCLIUserAgent in both locations).
🧹 Nitpick comments (3)
internal/adapter/provider/custom/codex_headers.go (1)
47-59:resolveCodexUserAgent和isCodexCLIUserAgent在custom和codex两个包中完全重复。
internal/adapter/provider/codex/adapter.go的 Lines 722-734 中定义了逻辑完全相同的resolveCodexUserAgent和isCodexCLIUserAgent(只是引用的默认 UA 常量不同)。建议将isCodexCLIUserAgent提取到共享的 utility 包中,避免 DRY 违反。resolveCodexUserAgent可接受默认 UA 作为参数来消除剩余的重复。♻️ 建议的重构方案
例如,在共享包中定义:
// pkg/uautil/codex.go package uautil import "strings" func IsCodexCLIUserAgent(userAgent string) bool { ua := strings.ToLower(strings.TrimSpace(userAgent)) return strings.HasPrefix(ua, "codex_cli_rs/") || strings.HasPrefix(ua, "codex-cli/") }然后两个包均引用此共享函数。
internal/adapter/provider/custom/claude_headers.go (1)
106-127:ensureHeader的三层优先级(source > target existing > default)与codex_headers.go中的ensureCodexHeader语义不同。Claude 的
ensureHeader支持三层优先级,而 Codex 的ensureCodexHeader(同一个custom包内)只有两层。这在当前场景下各自适用,但命名相近易产生混淆。如果未来有人误用,可能导致 header 覆盖行为不符预期。可考虑在函数名或文档中体现这种差异。internal/adapter/provider/custom/claude_headers_test.go (1)
131-162: 整体 cloaking 测试结构良好,端到端验证了 sub2api 兼容性。测试完整覆盖了以下关键路径:
- 非 CLI 客户端经 cloaking 后上游 UA 变为
claude-cli/x.y.z格式- 必要头部(
X-App、Anthropic-Beta、Anthropic-Version)均被设置metadata.user_id符合预期格式- 系统提示词包含 Claude Code 标识
一个小建议:Lines 146-150 循环中使用
t.Fatalf会在首个缺失头部时终止,改用t.Errorf可以一次暴露所有缺失项,提升调试效率。♻️ 建议使用 t.Errorf 替代 t.Fatalf 以显示所有缺失头部
for _, key := range []string{"X-App", "Anthropic-Beta", "Anthropic-Version"} { if strings.TrimSpace(upstreamReq.Header.Get(key)) == "" { - t.Fatalf("expected %s to be set", key) + t.Errorf("expected %s to be set", key) } }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
internal/adapter/provider/codex/adapter.gointernal/adapter/provider/codex/adapter_test.gointernal/adapter/provider/custom/claude_body.gointernal/adapter/provider/custom/claude_body_test.gointernal/adapter/provider/custom/claude_headers.gointernal/adapter/provider/custom/claude_headers_test.gointernal/adapter/provider/custom/codex_headers.gointernal/adapter/provider/custom/codex_headers_test.go
🧰 Additional context used
🧬 Code graph analysis (2)
internal/adapter/provider/codex/adapter_test.go (2)
internal/adapter/provider/codex/adapter.go (1)
CodexAdapter(55-61)internal/adapter/provider/codex/settings.go (1)
CodexUserAgent(29-29)
internal/adapter/provider/codex/adapter.go (1)
internal/adapter/provider/codex/settings.go (1)
CodexUserAgent(29-29)
🔇 Additional comments (14)
internal/adapter/provider/custom/codex_headers.go (1)
22-44: Header 设置逻辑清晰,passthrough + 显式控制 UA 的策略正确。
applyCodexHeaders先通过copyCodexPassthroughHeaders复制客户端 header(已排除user-agent),再通过resolveCodexUserAgent解析并设置 UA,避免了先复制再覆盖的不一致问题。整体逻辑合理。internal/adapter/provider/codex/adapter.go (1)
660-711:applyCodexHeaders整体逻辑清晰,header 过滤与 UA 解析策略与custom包保持一致。passthrough 逻辑正确跳过了
codexFilteredHeaders中的 header,UA 通过resolveCodexUserAgent动态解析,auth 在有 accessToken 时正确覆盖。ensureHeader的优先级(客户端 > 默认)合理。internal/adapter/provider/codex/adapter_test.go (2)
81-96: 新测试覆盖了非 CLI 客户端的默认 UA 回退行为,LGTM。建议额外添加一个
clientReq为nil的测试用例,确认当没有客户端请求时也能正确回退到默认 UA。
40-67: 测试中"codex-cli-custom/1.2.3"作为 CLI UA 的代表值得注意。
"codex-cli-custom/1.2.3"能通过isCodexCLIUserAgent检测是因为"codex-cli"前缀匹配过于宽泛(已在实现文件中标记)。如果收紧前缀为"codex-cli/",此测试用例需要同步更新为合法的 Codex CLI UA,例如"codex-cli/1.2.3"。internal/adapter/provider/custom/codex_headers_test.go (1)
8-26: 测试覆盖 CLI 和非 CLI 两种场景,逻辑正确。同样需要注意:如果
isCodexCLIUserAgent的前缀匹配从"codex-cli"收紧为"codex-cli/",Line 11 的测试用例"codex-cli-custom/1.2.3"将不再匹配,需同步更新为"codex-cli/1.2.3"之类的合法 UA。internal/adapter/provider/custom/claude_body.go (3)
28-31: Claude CLI UA 检测使用正则(?i)^claude-cli/\d+\.\d+\.\d+,精确合理。与 Codex 端使用宽泛的
strings.HasPrefix不同,Claude 端要求严格的语义化版本号匹配(x.y.z),能有效区分官方 CLI 与其他以claude-cli开头的非官方客户端。
369-400:hasClaudeCodeSystemPrompt覆盖了多种system字段格式,逻辑完备。处理了数组(含 text block 和纯字符串元素)、字符串和对象三种格式,使用精确字符串比较(带 TrimSpace),能有效防止重复注入。
96-100:isClaudeCodeClient改用正则匹配,与 PR 目标的"匹配官方版本模式"一致。
claudeCLIUserAgentPattern.MatchString配合strings.TrimSpace确保了空白符不会干扰匹配。internal/adapter/provider/custom/claude_body_test.go (3)
124-129: 新增的shouldCloak边界测试用例有效验证了正则匹配的严格性。
"claude-cli/dev"不匹配\d+\.\d+\.\d+,正确触发 cloaking;"Claude-CLI/2.1.17"通过大小写不敏感匹配,正确跳过 cloaking。这些用例与claudeCLIUserAgentPattern的设计意图完全对齐。
132-143: Haiku 测试更新正确反映了实现变更:cloaked 请求一律注入系统提示。移除了 Haiku 模型的特殊跳过逻辑后,此测试确认 Haiku 模型在 cloaking 模式下同样会注入系统提示和
user_id。
371-386:TestNoDuplicateSystemPromptInjection验证了hasClaudeCodeSystemPrompt的位置无关检测。Claude Code prompt 置于数组索引 1 而非 0,确认检测逻辑能在数组任意位置找到已有 prompt 并阻止重复注入。
internal/adapter/provider/custom/claude_headers.go (1)
83-92: Claude 的 UA 处理逻辑正确,与 Codex 端策略一致。CLI 客户端 UA 透传、非 CLI 回退到
defaultClaudeUserAgent,逻辑清晰。由于 Claude header 是从零构建(非 passthrough 复制),无需像 Codex 那样在 skip list 中显式排除user-agent。internal/adapter/provider/custom/claude_headers_test.go (2)
5-9: 新增导入合理,均在新测试中使用。
regexp用于 UA 和 metadata 格式校验,gjson用于 JSON 路径提取,引入合理。
102-129: 测试覆盖全面,逻辑清晰。三个子场景(官方 CLI UA 透传、浏览器 UA 回退、非官方 CLI UA 回退)准确覆盖了 PR 描述中的 UA 透传策略。
claude-cli/dev作为非官方格式被正确拦截,验证了 semver 模式匹配的严格性。
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
背景
本 PR 继续对齐 CLIProxyAPI 与 sub2api 的关键行为,重点修正 Claude/Codex 转发时的 User-Agent 与 Claude cloaking 细节。
主要改动
测试
影响范围
该改动不涉及凭证获取流程,仅调整请求头与请求体的兼容性/伪装策略。
Summary by CodeRabbit
发布说明
Bug 修复
功能改进
测试