第三方 Provider / Gateway
CC 是封闭四选一枚举(firstParty/bedrock/vertex/foundry)+ BASE_URL 重定向;Codex 是开放 model_providers 注册表——但 wire_api=chat 已被移除
第三方 Provider / Gateway
结论
两家的 provider 抽象走了相反的路:CC 是封闭枚举——getAPIProvider() 只认 firstParty | bedrock | vertex | foundry 四个值,第三方路由只能靠 ANTHROPIC_BASE_URL 把"伪装成 Anthropic API 的 gateway"接进来(claude-code-router 即此模式);Codex 是开放注册表——config.toml 的 model_providers map 可以声明任意 OpenAI 兼容端点,每个 provider 自带 env_key(BYOK)、自定义 header、重试参数。但要注意一个已坐实的变化:Codex 的 wire_api = "chat"(Chat Completions 协议)已被移除,目前 enum 只剩 responses——"Codex 可接任意 Chat Completions 服务"的旧认知已过期。harness 做多 provider 路由时,CC 侧注入 env(BASE_URL + AUTH_TOKEN),Codex 侧注入 provider 条目 + -c model_provider=...。
研究问题
- 各 CLI 的 provider 抽象层在哪?可扩展性边界是什么?
- BYOK(自带 key)如何配置?key 从哪个 env 读?
- gateway 模式下认证与模型发现如何穿透?
各 Agent 设计与实现
Claude Code
CC 源码为社区重建版(
src_2026-03-31),以下仅作架构描述与短引用。
Provider 是四值枚举,由三个互斥环境变量切换 [一手源码 utils/model/providers.ts:4-14]:
export type APIProvider = 'firstParty' | 'bedrock' | 'vertex' | 'foundry'
// CLAUDE_CODE_USE_BEDROCK → bedrock;CLAUDE_CODE_USE_VERTEX → vertex;
// CLAUDE_CODE_USE_FOUNDRY → foundry;否则 firstParty切到云厂商后认证交给云 SDK(Bedrock 用 AWS 凭证链、AWS_REGION 必填且不读 .aws config 的 region [一手文档 amazon-bedrock.md:225];Vertex 用 GCP ADC),并自动关闭一方 OAuth [一手源码 utils/auth.ts:115-148]。端点覆盖:ANTHROPIC_BEDROCK_BASE_URL、ANTHROPIC_VERTEX_BASE_URL、Bedrock Mantle 用 ANTHROPIC_BEDROCK_MANTLE_BASE_URL [一手文档 amazon-bedrock.md:220, 405,google-vertex-ai.md:188];Vertex 还支持 per-model region(VERTEX_REGION_CLAUDE_*)[一手文档 google-vertex-ai.md:197-201]。
Gateway 路由 = BASE_URL 重定向:ANTHROPIC_BASE_URL 改变请求去向但不改 provider 枚举;源码用 isFirstPartyAnthropicBaseUrl() 判断是否还指向 api.anthropic.com [一手源码 providers.ts:25-40]。gateway 认证推荐 ANTHROPIC_AUTH_TOKEN(Bearer),apiKeyHelper 优先级更低 [一手文档 llm-gateway.md:98-145]。
Gateway 模型发现:CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 时启动查询 gateway 的 /v1/models,仅收 claude*/anthropic* 前缀模型,结果缓存到 ~/.claude/cache/gateway-models.json;默认关闭,避免共享 key 的 gateway 把所有可见模型暴露给每个用户 [一手文档 llm-gateway.md:60-64]。配合 ANTHROPIC_CUSTOM_MODEL_OPTION(手动加一条 picker 项,跳过模型 ID 校验)[一手文档 model-config.md:284-298]。
没有 provider 插件机制:CC 不存在"注册一个新 provider"的入口。第三方路由(claude-code-router、各类中转站)全部走"gateway 伪装 Anthropic Messages API + ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN"这一条路 [推断,基于源码中 provider 枚举封闭 + 文档只给 gateway 路径]。代价:CC 的订阅 OAuth、/usage 配额、fast mode 等一方能力在 gateway 模式下全部失效(isAnthropicAuthEnabled() 返回 false)。
Codex CLI
开放注册表:model_providers map(~/.codex/config.toml)+ 内建默认值合并 [一手源码 codex-rs/model-provider-info/src/lib.rs:1-6, 443-479]。内建四个:openai(requires_openai_auth: true,订阅时 base 切到 https://chatgpt.com/backend-api/codex)、amazon-bedrock(OpenAI 兼容 Mantle 端点 bedrock-mantle.us-east-1.api.aws/openai/v1,AWS SigV4 签名)、ollama、lmstudio [同文件 :37-43, 237-254, 324-441]。内建项不可被用户覆盖,唯一例外是 bedrock 的 aws.profile/aws.region [同文件 :443-479]。
ModelProviderInfo 字段(自定义 provider 的全部可配置面)[一手源码 lib.rs:85-137]:
[model_providers.example]
name = "Example"
base_url = "https://example.com/v1"
env_key = "EXAMPLE_API_KEY" # BYOK:key 从该 env 变量读
env_key_instructions = "..." # 缺 key 时给用户的提示
wire_api = "responses" # 唯一合法值(见下)
query_params = { api-version = "..." } # Azure 风格
http_headers = { x-foo = "bar" }
env_http_headers = { x-org = "ORG_ENV" } # header 值取自 env
request_max_retries = 4 # 默认 4,上限 100
stream_idle_timeout_ms = 300000另有 auth = { command = "..." }(命令式 bearer token,与 env_key 互斥)和 experimental_bearer_token(直写 token,源码注明 discouraged)[同文件 :97-104, 181-208]。
wire_api = "chat" 已移除:反序列化遇到 "chat" 直接报错——"wire_api = \"chat\" is no longer supported... set wire_api = \"responses\"",并指向 openai/codex discussion #7782;ollama-chat provider 同步移除 [一手源码 lib.rs:46-48, 68-80]。WireApi enum 现在只有 Responses 一个变体 [同文件 :51-57]。意味着自定义 provider 必须实现 Responses API,纯 Chat Completions 端点已接不进 Codex。
Key 解析:api_key() 读 env_key 指定的环境变量,缺失/为空返回带 env_key_instructions 的错误 [一手源码 lib.rs:278-294]。requires_openai_auth: false(默认)时跳过登录界面,key 全靠 env [同文件 :128-133]。本地 OSS provider 的端口/地址可用 CODEX_OSS_PORT / CODEX_OSS_BASE_URL 覆盖 [同文件 :481-497]。
差异矩阵
| 维度 | Claude Code | Codex CLI |
|---|---|---|
| Provider 模型 | 封闭枚举:firstParty/bedrock/vertex/foundry | 开放 map:内建 4 个 + 任意自定义 |
| 切换方式 | 互斥 env 开关(CLAUDE_CODE_USE_*) | model_provider = "<key>" 指向 map |
| 自定义端点 | ANTHROPIC_BASE_URL(协议必须是 Anthropic Messages) | base_url(协议必须是 OpenAI Responses) |
| Chat Completions 兼容 | 不适用 | 已移除(wire_api=chat,discussion #7782) |
| BYOK key 来源 | ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / apiKeyHelper | per-provider env_key 指定的 env 变量 |
| 命令式凭证 | apiKeyHelper 脚本(全局一个) | auth.command(per-provider) |
| 自定义 header | ANTHROPIC_CUSTOM_HEADERS env | http_headers + env_http_headers(per-provider) |
| 云厂商原生签名 | Bedrock(AWS SDK)/Vertex(ADC)/Foundry | Bedrock Mantle(SigV4,aws.profile/region) |
| Gateway 模型发现 | /v1/models + CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 | 无自动发现(模型名手写) |
| 重试/超时配置 | 全局(不暴露 per-provider) | per-provider:request_max_retries、stream_idle_timeout_ms 等 |
| 订阅能力穿透 | gateway 模式下 OAuth/配额/fast mode 全部失效 | 自定义 provider 同样无 ChatGPT 配额(requires_openai_auth=false) |
最小复现
# CC:gateway 路由(claude-code-router 风格)
export ANTHROPIC_BASE_URL=http://127.0.0.1:3456
export ANTHROPIC_AUTH_TOKEN=<gateway-token> # Bearer 头
claude # /status 应显示非一方 base url;订阅 OAuth 被禁用
# CC:Bedrock 切换
export CLAUDE_CODE_USE_BEDROCK=1 AWS_REGION=us-east-1
claude
# Codex:自定义 OpenAI 兼容 provider(必须支持 Responses API)
cat >> ~/.codex/config.toml <<'EOF'
model_provider = "myproxy"
[model_providers.myproxy]
name = "My Proxy"
base_url = "https://proxy.example.com/v1"
env_key = "MYPROXY_API_KEY"
wire_api = "responses"
EOF
MYPROXY_API_KEY=<key> codex -m <model-name>
# 若写 wire_api = "chat" 会直接报配置错误并指向 discussion 7782Harness 接入建议(Yoda 实践)
- 统一三类供给源:Yoda 把每个 runtime 的账号供给抽象为
official-subscription / official-api / yoda-maas三个AgentAccountProviderId[一手源码yoda/src/shared/runtime-registry.ts:127-133],对应「订阅 OAuth / BYOK env / Yoda 托管网关」。usage 统计按此维度归因(见 usage-sync 章)。 - Env 解析顺序要和 CLI 一致:
official-api-probe-service解析 key/base URL 时先查 Yoda 注入的 custom env、再查继承的 process env,与会话启动逻辑同源——探测结果才能代表真实会话行为 [一手源码yoda/src/main/core/settings/official-api-probe-service.ts:12-47]。探测用 provider 注册的真实端点 + 真实 key 发请求,按bearer / x-api-key / x-goog-api-key三种 auth 风格组装 header。 - 注入面选择:给 CC 路由第三方时注入
ANTHROPIC_BASE_URL+ANTHROPIC_AUTH_TOKEN(注意这会关闭订阅 OAuth,UI 上要明示用户"本会话不走订阅额度");给 Codex 时写独立 profile 或临时-c model_provider=...,不要篡改用户已有 provider 条目(内建项本来就不可覆盖)。 - Codex 接入第三方的硬约束:目标端点必须支持 Responses API。harness 如果想把 Chat-Completions-only 的供应商(多数国产中转)接给 Codex,需要在中间加一层 chat→responses 协议转换网关——这正是
wire_api=chat移除后社区网关(如各类 codex proxy)的新角色 [推断,基于lib.rs:46的移除事实]。 - 配额穿透问题:gateway 模式下两家 CLI 都拿不到官方配额接口,harness 的用量面板应自动降级为"本地 transcript 统计"(usage-sync 章方案),不要对 gateway 账户显示订阅窗口。
失效条件
- Codex 恢复或以新形式重加 Chat Completions 支持(关注 discussion #7782 后续)——本章"Responses-only"结论失效
- Codex 内建 provider 列表变化(本快照 b89ce9a 已含 amazon-bedrock,较早版本没有;后续可能增删)
- CC 增加正式的 provider 插件机制或放开
APIProvider枚举 - CC gateway 模型发现从 opt-in 变默认开启,或缓存路径
~/.claude/cache/gateway-models.json迁移 -
ANTHROPIC_BASE_URL对一方域名的判定列表(api.anthropic.com)扩充
参考资料
- CC 文档:
claude-code-docs/docs/llm-gateway.md、amazon-bedrock.md、google-vertex-ai.md、microsoft-foundry.md、model-config.md(custom model option / 三方 pin) - CC 重建源码:
src_2026-03-31/utils/model/providers.ts、utils/auth.ts(isAnthropicAuthEnabled) - Codex 源码:
codex-rs/model-provider-info/src/lib.rs(全部 provider 抽象)、config/src/config_toml.rs(model_provider 字段) - wire_api=chat 移除公告:https://github.com/openai/codex/discussions/7782(源码错误信息内嵌链接)
- Yoda:
src/shared/runtime-registry.ts、src/main/core/settings/official-api-probe-service.ts