Skip to content

Latest commit

 

History

History
922 lines (644 loc) · 25.6 KB

File metadata and controls

922 lines (644 loc) · 25.6 KB

API 反代链路说明

本文说明当前项目中的 API 反代实现。目标是让你从入口到上游返回,完整看懂这条链路现在是怎么工作的。

当前版本已经只保留一种反代方式:

  • 本地对外暴露 OpenAI 兼容接口
  • 上游统一转到 Codex 的 chatgpt.com/backend-api/codex/responses
  • 不再保留旧的多模式反代
  • Axum 请求体大小上限默认放宽到 512 MiB

实现参考了 CLIProxyAPIPlus 的 Codex executor 思路,但不是把它整仓搬进来,而是把核心方法收敛到本项目现有架构里。

1. 当前反代的定位

本地入口

  • GET /health
  • GET /v1/models
  • POST /v1/chat/completions
  • POST /v1/responses
  • POST /v1/messages
  • POST /v1/images/generations
  • POST /v1/images/edits
  • POST /v1/images/variations

上游入口

  • POST https://chatgpt.com/backend-api/codex/responses

也就是说:

  • 客户端以为自己在调用 OpenAI 风格的 /v1/*
  • 实际上本地代理会把请求转换为 Codex responses 协议
  • 然后用账号池中的 ChatGPT/Codex 登录态去访问上游

2. 为什么这样设计

之前的问题有两个:

  • 直接打 api.openai.com/v1/* 时,很多导入的账号本质上只有 ChatGPT/Codex 登录态,不一定有公共 OpenAI API scope 或 quota
  • 旧的 conversation 直通方式过于贴近历史接口,不适合作为稳定代理基座

现在的方案本质上是:

  • 下游维持 /v1 兼容,方便接各种现成客户端
  • 上游切到 Codex 当前更合适的 responses 入口
  • 中间由本地代理负责协议转换

3. 整体时序

sequenceDiagram
    participant UI as "前端面板"
    participant Tauri as "Tauri Command"
    participant Local as "本地 Axum 代理"
    participant Store as "accounts.json / 账号池"
    participant Upstream as "chatgpt.com/backend-api/codex/responses"

    UI->>Tauri: start_api_proxy(port)
    Tauri->>Local: 启动 127.0.0.1:port
    Local->>Store: 读取账号池
    Store-->>Local: 候选账号列表

    Note over Local: 等待客户端请求

    UI->>Local: POST /v1/chat/completions
    Local->>Local: 校验 API Key
    Local->>Local: OpenAI 请求转 Codex responses 请求
    Local->>Store: 按排序取候选账号
    Local->>Upstream: Bearer access_token + ChatGPT-Account-Id
    Upstream-->>Local: SSE 响应
    Local->>Local: SSE 事件转 OpenAI 响应
    Local-->>UI: /v1/chat/completions 响应
Loading

4. 入口与生命周期

4.1 前端触发

前端入口在:

  • src/components/ApiProxyPanel.tsx
  • src/hooks/useCodexController.ts

用户在面板点击“启动 API 反代”后:

  1. 前端读取端口输入框
  2. 调用 Tauri 命令 start_api_proxy
  3. 成功后显示:
    • Base URL
    • API Key
    • 当前命中的账号
    • 最近错误

4.2 Tauri 命令

Tauri 命令入口在:

  • src-tauri/src/lib.rs

相关命令:

  • get_api_proxy_status
  • start_api_proxy
  • stop_api_proxy

4.3 后端运行态

后端运行态在:

  • src-tauri/src/state.rs

这里维护:

  • 监听端口
  • 当前代理 API Key
  • 运行任务句柄
  • 当前命中的账号 ID/标签
  • 最近一次错误

5. 启动时到底做了什么

启动逻辑在:

  • src-tauri/src/proxy_service.rs

start_api_proxy_internal(...) 做的事情是:

  1. 检查当前是否已经有代理在运行
  2. 从账号池加载可用账号
  3. 绑定本地端口,默认 8787
  4. 生成一个本地代理专用 sk-... API Key
  5. 创建 reqwest::Client
  6. 启动一个 axum HTTP 服务
  7. 注册以下路由:
    • /health
    • /v1/models
    • /v1/chat/completions
    • /v1/responses
    • /v1/messages
    • /v1/images/generations
    • /v1/images/edits
    • /v1/images/variations
    • 对请求体启用 512 MiB 默认上限,可用 CODEX_TOOLS_PROXY_MAX_BODY_MIB 覆盖
  8. 把运行状态写入全局 AppState

6. 本地暴露的接口

6.1 GET /health

用途:

  • 用于判断本地代理服务是否活着

返回:

{ "ok": true }

6.2 GET /v1/models

用途:

  • 给兼容客户端一个模型列表

特点:

  • 这里不是实时问上游拿模型
  • 目前返回本地静态模型列表:gpt-5.6-solgpt-5.6-terragpt-5.6-lunagpt-5.5gpt-5.4gpt-image-2
  • gpt-5.6gpt5.6gpt-5-6 会映射到 gpt-5.6-sol
  • 目的是让大多数依赖 /v1/models 的客户端能正常初始化

推理与速度参数:

  • 推理强度兼容值:noneminimallowmediumhighxhighmax
  • 推理速度:autodefaultfastflex
  • priorityfast 的上游 wire 别名,standarddefault 的兼容别名
  • 请求未指定时默认使用 xhighfast
  • ultra 属于 Codex 客户端的多代理编排模式;反代收到该兼容值时按实际推理 wire 值 max 发送
  • GPT-5.6 的官方推理强度为 nonelowmediumhighxhighmaxminimal 仅保留给旧模型兼容,GPT-5.6 请求会明确拒绝
  • 具体模型可用档位仍以上游能力为准;代理会拒绝未知值,不会静默降级

6.3 POST /v1/chat/completions

用途:

  • 兼容绝大多数 OpenAI Chat Completions 客户端

行为:

  • 本地接收 OpenAI 风格请求
  • 转为 Codex responses 请求
  • 上游始终按 SSE 模式请求
  • 如果下游请求 stream: true,本地再把上游 SSE 转成 OpenAI ChatCompletions SSE
  • 如果下游请求 stream: false,本地会先收完整个 SSE,再拼成普通 JSON 返回

6.4 POST /v1/responses

用途:

  • 给直接走 OpenAI Responses 风格的客户端使用

行为:

  • 非 GPT-5.6 请求体只做必要归一化;GPT-5.6 会转换为 Responses Lite 结构
  • 上游仍然统一发到 Codex responses
  • stream: true 时近似透传 SSE
  • stream: false 时从 SSE 中提取 response.completed,返回标准 JSON
  • Codex 0.144 WebSocket v2 会收到 426 并按客户端内置逻辑回退到 HTTP,避免破坏 warmup 和 previous_response_id 状态

6.5 POST /v1/messages

用途:

  • 给 Anthropic Messages 兼容客户端使用

行为:

  • 本地接收 Anthropic 风格请求
  • 转为 Codex responses 请求
  • 流式时转回 Anthropic SSE
  • 非流式时转回 Anthropic Message JSON
  • 认证推荐使用 x-api-key: sk-...
  • 需要带 anthropic-version: 2023-06-01

当前兼容字段:

  • model
  • system
  • messages
  • max_tokens
  • stream
  • temperature
  • top_p
  • stop_sequences
  • tools
  • tool_choice
  • thinking.type = enabled

其中 2023-06-01 是 Anthropic 当前公开的 API version,不是模型版本日期。Anthropic 官方版本文档目前仍把它作为 Messages API 的最新版本示例与版本历史项。

6.6 POST /v1/images/*

用途:

  • 给 OpenAI 图片兼容客户端使用

行为:

  • generations 会转成 Codex image generation tool
  • editsvariations 会把上传图片转成 input_image
  • 返回值使用 OpenAI 图片接口常见的 b64_json

7. 鉴权方式

本地代理有自己的一层鉴权,不直接暴露给任意本地程序。

支持两种传法:

  • X-API-Key: sk-...
  • x-api-key: sk-...
  • Authorization: Bearer sk-...

如果调用 /v1/messages,还需要带:

  • anthropic-version: 2023-06-01

校验逻辑:

  • 只认启动代理时生成的那一个本地 API Key
  • 这层 key 只是本地代理的门锁
  • 它不是上游 OpenAI API Key
  • 它也不是账号池里真实的 access_token

8. 账号池是怎么参与反代的

账号来源:

  • 应用数据目录下的 accounts.json

数据结构定义在:

  • src-tauri/src/models.rs

每个账号核心字段有:

  • label
  • account_id
  • auth_json
  • usage
  • plan_type

代理真正需要的认证信息来自 extract_auth(...)

  • access_token
  • account_id
  • 可选 plan_type

9. 账号排序与挑选规则

候选账号在每次请求时重新读取,并重新排序,不做固定缓存。代理运行中新增、删除、禁用账号后,不需要关闭代理进程,下一次请求就会按最新账号池选择。

排序规则:

  1. 优先 free 账号
  2. 再比较 1week 剩余额度
  3. 再比较 5h 剩余额度
  4. 最后按标签名排序

也就是:

  • free 计划会优先于其他计划
  • 在同一类计划中,优先挑“更有余量”的账号

对应逻辑:

  • load_proxy_candidates(...)
  • compare_proxy_candidates(...)

9.1 逐个模式与 session affinity

负载均衡选择“逐个”时,代理会优先复用当前会话已经命中的账号。

会话标识来源:

  • 请求头:x-codex-tools-session
  • 请求头:x-codex-session-id
  • 请求头:session_id
  • 请求头:session-id
  • 请求头:x-client-request-id
  • 请求头:openai-conversation-id
  • 请求体:sessionId
  • 请求体:session_id
  • 请求体:conversationId
  • 请求体:conversation_id
  • 请求体:threadId
  • 请求体:thread_id
  • 请求体:previous_response_id
  • 请求体:previousResponseId

规则:

  • 同一个会话继续用同一个账号,直到该账号达到 5h 阈值或被上游拒绝
  • 不同会话各自绑定账号,互不抢全局当前账号
  • 没有会话标识的旧客户端继续走原来的全局逐个模式
  • 绑定只保存在内存里,停止代理后会清空

10. 请求发到上游时带了什么

真正向上游发请求时,目标是:

  • https://chatgpt.com/backend-api/codex/responses

关键请求头:

  • Authorization: Bearer <candidate.access_token>
  • ChatGPT-Account-Id: <candidate.account_id>
  • Originator: codex_cli_rs
  • Version: 0.144.0
  • Session_id: <uuid>
  • User-Agent: codex_cli_rs/0.144.0
  • Accept: text/event-stream
  • Content-Type: application/json

这里有几个关键点:

  • 不是发到 api.openai.com/v1/*
  • 不是发到旧的 conversation
  • 默认模拟的是 Codex CLI 风格头
  • 上游固定按 SSE 返回

11. chat/completions 到 Codex responses 的转换

这是当前链路最核心的一层。

11.1 固定注入字段

本地会补这些字段:

  • stream: true
  • store: false
  • reasoning.effort: xhigh
  • reasoning.summary: auto
  • include: ["reasoning.encrypted_content"]
  • service_tier: priority(下游可写 fast

GPT-5.6 Sol/Terra/Luna 会额外使用 Responses Lite 契约:

  • 请求头补 x-openai-internal-codex-responses-lite: true
  • 顶层 tools 移到 input 首项 additional_tools
  • 非空 instructions 移到 developer message
  • 顶层 toolsinstructions 不再发送
  • parallel_tool_calls: false
  • reasoning.context: all_turns

这里 store: false 很关键。

实际验证里,上游 backend-api/codex/responses 明确要求:

  • 没带 store: false 会报错

11.2 message 角色映射

OpenAI 风格消息会被转换为 Codex input 数组。

角色映射:

  • system -> developer
  • developer -> developer
  • user -> user
  • assistant -> assistant
  • tool -> function_call_output

11.3 content 映射

文本:

  • text -> input_textoutput_text

图片:

  • image_url -> input_image

文件:

  • file -> input_file

11.4 tool 调用映射

OpenAI 里的:

  • assistant.tool_calls
  • tool 消息

会被拆成 Codex 里的:

  • function_call
  • function_call_output

11.5 结构化输出映射

如果下游传了:

  • response_format
  • text.verbosity

本地会转换到上游的:

  • text.format
  • text.verbosity

12. /v1/responses 的归一化

如果下游直接请求 /v1/responses,本地不会像 chat/completions 那样大幅转换,只做必要补丁:

  • 强制 stream: true
  • 强制 store: false
  • 缺失时补 instructions
  • 缺失时补 parallel_tool_calls
  • 缺失时补 reasoning.effort = xhigh
  • 缺失时补 reasoning.summary = auto
  • 缺失时补快速档,向上游发送 service_tier = priority
  • 确保 include 里有 reasoning.encrypted_content
  • 丢弃上游不接受的 metadata 字段,避免 Cursor 等客户端报 Unsupported parameter: metadata
  • GPT-5.6 请求继续转换为 Responses Lite 契约

13. 上游 SSE 是怎么转回 OpenAI 的

13.1 POST /v1/responses

如果下游本来就是 Responses 客户端:

  • stream: true 时,基本按 SSE 透回去
  • stream: false 时,从完整 SSE 中提取 response.completed

13.2 POST /v1/chat/completions

这是转换最多的地方。

上游返回的是 Codex SSE 事件流,本地会把关键事件转成 OpenAI ChatCompletions SSE。

事件映射

上游事件 本地输出
response.created 初始化状态,不立即输出
response.reasoning_summary_text.delta delta.reasoning_content
response.reasoning_summary_text.done 补一个换行分隔
response.output_text.delta delta.content
response.output_item.added with function_call delta.tool_calls 开始事件
response.function_call_arguments.delta delta.tool_calls[].function.arguments 增量
response.function_call_arguments.done 参数结束兜底
response.output_item.done with function_call 工具调用完成兜底
response.completed finish_reason + [DONE]

非流式时怎么处理

如果下游 stream: false

  1. 本地仍然向上游请求 SSE
  2. 读完整个 SSE
  3. 找到 response.completed
  4. response.output 里提取:
    • assistant 文本
    • reasoning summary
    • tool calls
    • usage
  5. 拼成一个普通的 OpenAI ChatCompletions JSON

14. 失败重试与运行中热切换

每次请求会按候选账号顺序尝试。

对单个账号的流程:

  1. 先用当前 token 发上游请求
  2. 如果命中“疑似 token 过期/失效”信号,则先 refresh
  3. refresh 成功后重试这个账号一次
  4. 如果仍失败,再判断是否属于可切下一个账号的错误

这条链路就是 runtime rotation:

  • 客户端只需要一直请求同一个 Base URL
  • 不需要改写 ~/.codex/auth.json
  • 不需要关闭 Codex App/CLI
  • 不需要重启反代服务
  • 代理会在请求发到上游前选择账号,并在收到可轮换错误时换到下一个账号
  • 流式请求在开始向下游输出前完成账号选择;已经开始输出的同一个流不会中途换账号

如果你要做 wrapper 或 app bind,推荐方式是让 Codex App/CLI 指向本工具的 /v1 反代,并为每个会话透传稳定的 session key。这样官方 App/CLI 进程可以保持打开,账号轮换发生在本地代理层。

14.1 wrapper 接入

wrapper 的职责很简单:

  1. 确保本工具 API 反代已经启动
  2. 把 Codex 的 provider/base_url 指到本工具显示的 Base URL
  3. 把本工具显示的 API Key 作为下游 key
  4. 为同一个 Codex 会话透传稳定 session key
  5. 启动官方 Codex CLI/App,不接管官方 codex 二进制

14.2 app bind 接入

app bind 不需要 patch 官方 App 文件。

面板已经提供按钮:

  1. 先启动 API 反代
  2. 点击“切到本机反代”
  3. 工具会备份当前 ~/.codex/config.toml~/.codex/auth.json
  4. 工具会把 openai_base_url 写成当前本机 Base URL
  5. 工具会把当前反代 API Key 写入 auth.json
  6. 点击“恢复正常地址”时,还原到绑定前的配置和认证文件

这个方式把“账号池 + token refresh + 运行中轮换”留在 codex-tools,把官方 App/CLI 只当普通 OpenAI/Responses 客户端。

14.3 会触发 refresh 的情况

  • 401
  • 错误体包含:
    • token expired
    • jwt expired
    • invalid token
    • session expired
    • login required

14.4 refresh 成功后还会做什么

刷新成功后会把新的认证状态写回两处:

  • 账号池里的 accounts.json
  • 如果它正好是当前激活账号,也会回写 ~/.codex/auth.json

15. 失败分类

如果某个账号失败,本地会尽量分类,而不是只报一个泛泛的 429

当前分类:

  • 额度用完
  • 频率限制
  • 模型受限
  • 鉴权失败
  • 权限不足

当所有候选账号都失败时,会返回类似:

  • 哪一类失败有多少个
  • 再附一个示例原因

这样你能更快判断是:

  • 账号整体没额度
  • 当前模型不支持
  • 还是登录态坏了

16. 最近错误和当前账号是怎么展示的

运行中的状态会保存在:

  • ApiProxyRuntimeSnapshot

主要字段:

  • active_account_id
  • active_account_label
  • sequential_session_affinity
  • last_error

前端轮询这些状态,所以面板上能看到:

  • 当前命中的账号
  • 最近一次错误

17. Cloudflared 和反代的关系

Cloudflared 不是第二套代理逻辑,它只是把当前本地代理继续暴露到公网。

关系是:

  1. 本地 API 反代先启动
  2. Cloudflared 再把这个本地端口转出去

所以链路是:

客户端 -> cloudflared 公网地址 -> 本地 127.0.0.1:8787/v1/* -> Codex 上游

Cloudflared 不参与:

  • 请求协议转换
  • 账号挑选
  • token refresh
  • SSE 到 OpenAI 的映射

它只负责公网入口。

18. 远程压缩 responses/compact

当前实现支持:

  • 入站:POST /v1/responses/compact
  • ChatGPT 上游:{chatgpt}/backend-api/codex/responses/compact
  • Relay 上游:{api_base}/responses/compact

行为要点:

  • 上游 compact 走 unary JSON(Accept: application/json),不走 SSE
  • 客户端 stream: true 时,把上游 JSON 合成最小 Responses SSE(output_item.done + response.completed
  • remote_compaction_v2compaction_trigger 保持普通 /v1/responses 流式协议,不降级到 unary compact
  • Responses(HTTP/WebSocket)和 compact 按窄白名单转发 turn-state、beta、turn metadata、window 与 trace 上下文;快速 SSE 响应也保留上游返回的状态头
  • 当入站已有 turn-state 且上游等待超过 10 秒时,发送可被客户端消费但忽略的 response.in_progress SSE 心跳;没有可安全回显的状态头时等待上游完整响应后再提交响应头
  • 心跳已发出后的失败改为流内 response.failed,并尽量保留上游 code/type/message 等错误字段
  • compact body 与 Codex CompactionInput 对齐,只保留 model/input/instructions/tools/parallel_tool_calls/reasoning/service_tier/prompt_cache_key/text,并统一规范化 service tier
  • ChatGPT 账号上 GPT-5.6 reasoning.effort=max 会降级为 xhigh
  • JSON 请求体支持 gzip、标准 zlib-wrapped deflate(兼容 raw deflate)与 zstd;解压在阻塞线程池执行,解压后仍受请求体上限约束
  • Relay 返回 404/405/501 时视为不支持 compact,并继续尝试后续候选账号

19. 当前的边界与限制

当前版本不是“完整 OpenAI API 网关”,而是“OpenAI 兼容的 Codex 代理”。

明确限制:

  • 只支持:
    • GET /v1/models
    • POST /v1/chat/completions
    • POST /v1/responses
    • POST /v1/responses/compact
    • GET /v1/responses(旧版 WebSocket;v2 返回 426 触发 HTTP 回退)
    • POST /v1/messages
    • POST /v1/images/generations
    • POST /v1/images/edits
    • POST /v1/images/variations
  • 其他 /v1/* 路径目前直接返回不支持
  • 模型列表是本地静态表,不是实时探测
  • /v1/responses 的流式是近似透传,不额外做深层语义改写
  • GPT-5.6 Responses Lite 不接收 hosted web_searchweb_search_previewimage_generation 工具;这类请求会明确拒绝,客户端扩展工具仍会透传
  • 远程图片 URL 会替换为占位文本,Lite 请求不会发送图片 detail
  • 核心目标是把最常用的聊天链路稳定打通

20. 代码位置索引

如果你要继续看代码,优先看这些文件:

  • 启动、路由、转换、账号轮换:
    • src-tauri/src/proxy_service.rs
  • Tauri 命令:
    • src-tauri/src/lib.rs
  • 运行态:
    • src-tauri/src/state.rs
  • 账号结构:
    • src-tauri/src/models.rs
  • 前端面板:
    • src/components/ApiProxyPanel.tsx
  • 前端控制器:
    • src/hooks/useCodexController.ts

21. 一个完整请求示例

以这个调用为例:

curl http://127.0.0.1:8787/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer sk-xxxx' \
  -d '{
    "model": "gpt-5.6-sol",
    "reasoning_effort": "xhigh",
    "service_tier": "fast",
    "stream": false,
    "messages": [
      { "role": "user", "content": "1+1 等于几?只回答结果。" }
    ]
  }'

内部实际流程是:

  1. 本地校验 sk-xxxx
  2. 找到可用账号列表
  3. 把请求改写成 Codex responses 请求
  4. 强制补 store: false
  5. 带着账号 access_token + account_id 去打上游
  6. 上游返回 SSE
  7. 本地抽取 response.completed
  8. 转成 OpenAI ChatCompletions JSON
  9. 返回类似:
{
  "object": "chat.completion",
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "2"
      },
      "finish_reason": "stop"
    }
  ]
}

22. 调试方式

看服务是否起来

curl http://127.0.0.1:8787/health

看模型列表

npm run test:codex-login-proxy -- --api-key 你的sk

看聊天结果

npm run test:codex-login-proxy -- --api-key 你的sk --prompt '1+1 等于几?只回答结果。'

保留原始响应

npm run test:codex-login-proxy -- --api-key 你的sk --prompt '1+1 等于几?只回答结果。' --raw

这会把:

  • 响应头
  • 原始 body
  • 请求元数据

都落到本地文件,方便继续排查。

23. 通过 CC Switch 接入 Codex

如果你想把本工具作为“账号池 + OpenAI 兼容反代”,再由 CC Switch 来统一管理 Codex provider,这条链路是支持的。

原因很简单:

  • 本工具下游暴露的是 OpenAI 兼容 /v1 接口
  • CC Switch 给 Codex 写入的自定义 provider 也是 OPENAI_API_KEY + base_url + wire_api = "responses" 这套配置

也就是说:

  • codex-tools -> CC Switch -> Codex
  • 协议层是能对上的

23.1 适用范围

当前这套接入方式,适用于:

  • CC Switch 中的 Codex 自定义 provider
  • 能按 OpenAI /v1 或 Anthropic /v1/messages 子集发请求的客户端

不适用于:

  • 把本工具当成完整 Claude 官方网关

原因是本工具当前只提供这些出口:

  • GET /v1/models
  • POST /v1/chat/completions
  • POST /v1/responses
  • GET /v1/responses(旧版 WebSocket;v2 使用 HTTP 回退)
  • POST /v1/messages
  • POST /v1/images/generations
  • POST /v1/images/edits
  • POST /v1/images/variations

其中 /v1/messages 是 Anthropic Messages 兼容子集,最终仍会转到 Codex responses 上游。

23.2 推荐配置

在 CC Switch 中新增一个 Codex 自定义 provider,可按下面填写。

auth.json

{
  "OPENAI_API_KEY": "这里填 codex-tools 面板里生成的 sk-..."
}

config.toml

model_provider = "codex_tools"
model = "gpt-5.6-sol"
model_reasoning_effort = "xhigh"
service_tier = "fast"
disable_response_storage = true

[model_providers.codex_tools]
name = "codex_tools"
base_url = "http://127.0.0.1:8787/v1"
wire_api = "responses"
requires_openai_auth = true

如果你不是本机直连,而是通过 cloudflared 公网域名接入,把 base_url 改成:

base_url = "https://你的公网域名/v1"

23.3 三个最容易配错的点

  1. base_url 要填到 /v1 为止

    • 对:http://127.0.0.1:8787/v1
    • 错:http://127.0.0.1:8787
    • 错:http://127.0.0.1:8787/v1/responses
  2. OPENAI_API_KEY 要填本工具生成的代理 key

    • 也就是面板显示的 sk-...
    • 不是 OpenAI 官方 API Key
    • 也不是账号池里真实的 access_token
  3. wire_api 要用 responses

    • 因为本工具上游统一走的是 Codex responses
    • CC Switch 的 Codex 自定义 provider 也应使用 responses

23.4 接不通时先排查什么

先直接测本工具自己的出口:

curl http://127.0.0.1:8787/health
curl http://127.0.0.1:8787/v1/models -H 'Authorization: Bearer 你的sk'
curl http://127.0.0.1:8787/v1/messages \
  -H 'x-api-key: 你的sk' \
  -H 'anthropic-version: 2023-06-01' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-5.6-sol",
    "max_tokens": 256,
    "messages": [{"role": "user", "content": "Say hi in one sentence."}]
  }'

如果这些请求都正常:

  • 说明 codex-tools 代理本身已经起来了
  • 后续问题通常就在 CC Switch 或客户端 provider 配置

如果本机地址可以通,但外网地址不通,优先检查:

  • cloudflared 是否真的映射到了当前反代端口
  • base_url 是否写成了公网域名加 /v1
  • OpenAI/Responses 客户端是否带上了 Authorization: Bearer sk-...
  • Anthropic Messages 客户端是否带上了 x-api-key: sk-...anthropic-version: 2023-06-01

23.5 关于 Claude provider

如果你在 CC Switch 里配置的是 Claude provider,而不是 Codex provider,需要注意:

  • 本工具现在可以直接接收 Anthropic Messages 兼容请求:POST /v1/messages
  • 认证头按 Anthropic 风格填 x-api-key: sk-...
  • 版本头填 anthropic-version: 2023-06-01
  • 这仍然是兼容转换出口,不是完整 Claude 官方网关
  • 上游还是 Codex responses,不是 Anthropic 官方模型服务

如果某个客户端的 Claude provider 需要填写 base URL:

  • 客户端会自动拼 /v1/messages 时,base URL 填到 http://127.0.0.1:8787
  • 客户端要求完整接口地址时,填 http://127.0.0.1:8787/v1/messages
  • 如果通过公网域名接入,把主机名换成 cloudflared 或远程反代域名

24. 一句话总结

当前反代的本质是:

  • 本地对客户端说“我是 OpenAI /v1,也能接 Anthropic /v1/messages
  • 对上游实际上说“我是 Codex CLI,在调用 backend-api/codex/responses
  • 中间靠账号池、协议转换、SSE 事件转换,把两边接起来