Skip to content

连接 Codex CLI

把 OpenAI Codex CLI 指向 shunt,让它在 ChatGPT/Codex OAuth 账户池上做负载均衡。

Updated View as Markdown

其他所有连接指南都是把 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 default

codex 是内置的 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: Bearer

B. 作为 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_TOKEN
export 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-streamx-shunt-account: 头部 ⇒ 池已服务该请求,且 SSE 被原样中继。
  • 返回 401 authentication_errorx-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 传输 不在这个端点的范围内,已作为后续工作跟踪。

Navigation

Type to search…

↑↓ navigate↵ selectEsc close