其他所有连接指南都是把 Claude Code 路由到某个后端。这一篇的方向正好相反:把 OpenAI 的
Codex CLI 指向 shunt,让 shunt 在一个 ChatGPT/Codex OAuth 账户池上为它做负载均衡。shunt 会
逐字中继 Codex CLI 的 OpenAI Responses 流量 —— 没有任何 Anthropic 转换 —— 因此该 CLI 与 shunt
之间说的,和它直连 chatgpt.com 时说的是同一套线上协议。
这是入站 Codex 端点的实际用法;那一页是行为规范(故障转移表、 耗尽语义、重载规则),下面的步骤则是端到端的连接演练。这里引用的 Codex CLI 配置键来自 OpenAI 的 配置参考与 认证文档。
1. 在 shunt 上启用该端点
入站端点是可选启用的。添加该表并把它指向一个 chatgpt_oauth 提供方(默认的提供方名是
codex):
# shunt config.toml
[server.codex_endpoint]
provider = "codex" # must be a chatgpt_oauth provider; "codex" is the built-in defaultcodex 是内置的 chatgpt_oauth 提供方,因此启用该端点不需要任何 [providers.codex] 块。若要指向
一个名字不同的提供方,请把它声明为 [providers.<name>] 表(见配置参考),
并在上面设置 provider = "<name>"。
shunt check # validates the endpoint's provider exists + is chatgpt_oauth
shunt run[server.codex_endpoint] 不存在时,这些路由一个都不会注册,shunt 默认的 HTTP 暴露面保持不变。存在
时,启动校验会拒绝未知的 provider,或其 auth 不是 chatgpt_oauth 的提供方 —— 该端点注入的是
运营者的 Codex bearer,因此只有 chatgpt_oauth 提供方符合条件。每个键见配置参考。
2. 把 Codex CLI 指向 shunt
Codex CLI 会把 /responses 追加到它使用的任何 base URL 之后(Codex 说的是 OpenAI Responses 线上
协议 —— wire_api = "responses" 是它唯一支持的取值),因此 shunt 注册了三条路由,下面任何一种客户端
形态都会落到其中之一:
Codex CLI ~/.codex/config.toml |
命中的 shunt 路由 |
|---|---|
自定义提供方 base_url = ".../v1" |
POST /v1/responses |
openai_base_url = ".../v1" |
POST /v1/responses |
chatgpt_base_url = ".../backend-api/codex" |
POST /backend-api/codex/responses |
**推荐 —— 自定义模型提供方。**配合 requires_openai_auth = false,从 CLI 的视角看它是免认证的,因此
完全不需要本地的 codex login —— 账户由 shunt 提供 —— 而且两种呈递 shunt token 的方式(步骤 3)
它都支持:
# ~/.codex/config.toml
model_provider = "shunt" # select it as the active provider
model = "gpt-5.6-sol" # an entitled slug — see step 5
[model_providers.shunt]
name = "shunt"
base_url = "http://127.0.0.1:3001/v1"
wire_api = "responses" # the only supported value; also the default
requires_openai_auth = false # shunt handles auth; the CLI needs no ChatGPT/API login here更简单 —— 覆盖一个 base URL(不需要提供方块):
# ~/.codex/config.toml — pick one
openai_base_url = "http://127.0.0.1:3001/v1" # + OPENAI_API_KEY = your shunt token
chatgpt_base_url = "http://127.0.0.1:3001/backend-api/codex" # loopback only (ChatGPT login mode)openai_base_url + OPENAI_API_KEY=<shunt-token> 是 LiteLLM/llmgateway 的惯用法 —— CLI 会把该
token 以 Authorization: Bearer 发送,shunt 接受这种方式(步骤 3),所以这种形态即使配置了
[server.auth] 也能工作。chatgpt_base_url 会让 CLI 停留在 ChatGPT 登录模式并发送它自己的
ChatGPT token,而 shunt 无法把它当作客户端 token 接受 —— 因此只应在未设门控的(回环)shunt 上使用。
这两种方式都让 CLI 运行在它自己的 OpenAI/ChatGPT 认证模式下,因此都需要本地的 codex login;上面的
自定义提供方连这一步都省了。
CLI 自己本地的 ~/.codex/auth.json 登录与哪个账户来应答无关 —— 每个请求都从 shunt 的池中取用一个
账户。回环的 base_url 可以保持纯 http://;远程场景请使用 https://。不要在 shunt 提供方上设置
supports_websockets = true —— 这个端点只走 HTTP/SSE(见下文)。
3. 提供 shunt 客户端 token(当配置了 [server.auth] 时)
如果 shunt 配置了 [server.auth] —— 回环之外的场景都推荐配置 ——
请用两种方式之一提供 shunt 客户端 token;shunt 两种都接受。
A. 作为 OpenAI 风格的 Bearer key —— LiteLLM/llmgateway 的惯用法。把 shunt token 设为 API key,CLI
会以 Authorization: Bearer <shunt-token> 发送它:
# ~/.codex/config.toml — built-in openai provider (no provider block needed).
# Set the base URL here, NOT via the OPENAI_BASE_URL env var (see the caution in step 2).
openai_base_url = "http://127.0.0.1:3001/v1"# then present only the token via env — the CLI sends it as Authorization: Bearer
export OPENAI_API_KEY="<shunt-token>"# or a custom provider — env_key becomes the Bearer
[model_providers.shunt]
base_url = "http://127.0.0.1:3001/v1"
wire_api = "responses"
env_key = "SHUNT_TOKEN" # reads $SHUNT_TOKEN, sent as Authorization: BearerB. 作为 x-shunt-token 头部(或 [server.auth].header 所指定的任何名字)。只有自定义提供方才能附加
头部;用 env_http_headers 把密钥留在 config.toml 之外:
[model_providers.shunt]
name = "shunt"
base_url = "http://127.0.0.1:3001/v1"
wire_api = "responses"
requires_openai_auth = false
env_http_headers = { "x-shunt-token" = "SHUNT_TOKEN" } # reads $SHUNT_TOKENexport SHUNT_TOKEN="<token>"(或者硬编码 http_headers = { "x-shunt-token" = "<token>" }。)无论哪种方式,该 token 都会与
[server.auth] 的列表比对;缺失或错误的 token 返回 401 authentication_error。完全不配置
[server.auth] 时,该端点对任何能触达它的人开放 —— 回环场景没问题,共享网关则不行。客户端的
bearer/头部仅用于向 shunt 认证 —— 它会被剥除,绝不会转发给 Codex 后端。
4. 在 shunt 上预配账户池
该端点原封不动地复用 Codex 多账户的账户池 —— 请在 shunt 所在主机上预配账户,而不是 CLI 那一侧:
codex login # sign in to a ChatGPT account (browser flow)
shunt login codex --name main # capture it into shunt's store
# Each additional pool account needs its own login + capture (a separate ChatGPT
# account): `codex login` then `shunt login codex --name backup`, and so on.# shunt config.toml
[[providers.codex.accounts]]
name = "main"
[[providers.codex.accounts]]
name = "backup" # each declared name must be provisioned above; an unprovisioned
# account is skipped on failover (its credential fails to resolve)选择是会话粘性的:Codex CLI 自己的 session-id 请求头部决定账户的 key,因此一段对话只要账户保持
健康就一直留在同一个账户上,随后才会故障转移(429 → 轮换,401 → 刷新并重试,5xx → 冷却并轮换)。成功
的池化响应会带上 x-shunt-account: <name> 头部。在启用了 [server.auth]
的共享网关上,粘性 key 按已认证的客户端隔离,因此恰好发送相同 session-id 的不同客户端会使用不同的
粘性 key —— 从而避免跨客户端的粘性 key 碰撞。(这些 key 是不同的;两个不同的 key 仍可能哈希到同一个
账户。)
在没有配置 [[providers.codex.accounts]] 且存储为空时,该端点会回退到单个默认的
~/.codex/auth.json 凭据 —— 没有池,也没有故障转移 —— 因此只要设置了 [server.codex_endpoint],一个
Codex 登录就能立刻工作。
5. 选择一个有权限的模型
6. 验证
针对这三条路由中的任意一条发起一个原始 Responses 请求,应当被逐字中继,并(在池化时)返回一个
x-shunt-account 头部:
curl -N -i -X POST http://127.0.0.1:3001/v1/responses \
-H "content-type: application/json" \
-H "x-shunt-token: <token>" \
-d '{"model":"gpt-5.6-sol","input":"say hi","stream":true}'- 返回
200,带content-type: text/event-stream和x-shunt-account:头部 ⇒ 池已服务该请求,且 SSE 被原样中继。 - 返回
401 authentication_error⇒x-shunt-token缺失或无效(步骤 3)。 - 返回指明模型的
400⇒ 该账户对这个 slug 没有权限(步骤 5)。
然后用真正的 Codex CLI 跑一轮 —— 除了 base URL 之外,CLI 侧不需要任何代码改动就能经由账户池往返。关于
这条路径与 /v1/messages 路径的差异,见入站 Codex 端点;已注册的
路由见 HTTP 端点。
仅 HTTP/SSE
即使目标提供方设置了 websocket = true,这个端点也始终使用 HTTP 传输,因此请在 Codex CLI 的 shunt
提供方上保持 supports_websockets 关闭(其默认值)。实验性的
Codex WebSocket v2 传输
不在这个端点的范围内,已作为后续工作跟踪。