Skip to content

Repository files navigation

codex-opencode-proxy

一个零依赖的 Python 代理:把 Codex 发出的 Responses API 请求转发到上游 (默认 https://opencode.ai/zen/go/v1),API key 直接从 Codex 请求里透传, 本地不保存。

默认启动是纯透传,不做任何改写;另外提供两个可选功能,按需开启:

  • --fix-agent-message:把 Codex 多智能体(V2)私有的 agent_message / encrypted_content 改写成标准 Responses message item;
  • --chat-models <前缀>:命中前缀的模型走 Responses → Chat Completions 协议转换,把只提供 Chat 协议的上游(如 DeepSeek 官方、部分第三方网关) 接入 Codex。

特性

  • 纯 Python 标准库实现,无第三方依赖,Python 3.8+ 即可运行;
  • 默认纯透传,行为最干净;两个可选功能相互独立,互不影响;
  • 完整的 Responses ↔ Chat 双向转换:普通 JSON 与 SSE 流式都支持;
  • API key 透传,本地不落盘;日志自动脱敏(sk-* token、Bearer 值);
  • 上游错误透传、错误体截断、连接与超时控制、并发连接上限;
  • 70 个单元 + 端到端测试。

文件结构

.
├── proxy.py            # HTTP 服务器、路由、转发、模型路由、错误处理
├── responses_chat.py   # Responses ↔ Chat Completions 双向转换(纯数据函数)
├── test_proxy.py       # 单元 + 端到端测试
├── README.md
└── LICENSE             # Apache 2.0

启动语义

默认启动是纯透传,不做任何改写。 两个功能完全独立,按需开启:

启动参数 作用
无(默认) 纯透传。上游官方修复 agent_message 后,这样启动最干净
--fix-agent-message 把 Codex 多智能体(V2)私有的 agent_message / encrypted_content 改写成标准 Responses message item
--chat-models <前缀> 模型前缀命中时,把 Responses 转成 Chat Completions 发给 chat 上游,再把响应(JSON/SSE)还原成 Responses

行为矩阵:

启动方式 agent_message 请求 命中 chat-models 的模型 其他模型
python3 proxy.py 原样透传 原样透传 原样透传
--fix-agent-message 改写后透传 改写后透传(未配 chat-models) 改写后透传
--chat-models mimo 转换路径隐式改写 转换 透传
两个都加 改写 → 转换(mimo)或透传(其他) 转换 改写后透传

注意:转换路径始终会先改写 agent_message——Chat 协议无法表达这个 OpenAI 私有 item,不改写会导致子代理任务内容丢失。--fix-agent-message 只决定透传路径是否改写。

转换规则

--chat-models 的转换规则参考了 OpenAI 官方迁移指南、cc-switch、new-api、 LiteLLM、llama.cpp 以及多个 Codex 转接项目,核心映射:

Responses Chat Completions
instructions 首条 system 消息
input 字符串 一条 user 消息
message item 同 role 消息(developer 折叠为 system)
function_call 连续项 合并进同一条 assistant 消息的 tool_calls[]
function_call_output role=tool 消息,tool_call_id=call_id
reasoning item 累加挂到同一回合 assistant 消息的 reasoning_content(DeepSeek 带 tools 多轮要求完整回传;content 与 tool_calls 同回合时合并成一条 assistant 消息)
max_output_tokens max_tokens(DeepSeek 系只认 max_tokens)
reasoning.effort deepseek: reasoning_effort(flash: low→low、xhigh→high、max→max,其余→high;pro 等: max/xhigh→max,其余→high)+ thinking;mimo: 只发 thinking:{type:enabled/disabled},不发 reasoning_effort
text.format response_format(json_schema 展平为嵌套结构)
tools[].function / custom Chat function tools;其余类型丢弃
tool_choice {type:"function",name} 展平为嵌套 function.name
stream=true 附加 stream_options.include_usage=true

响应方向:Chat 的 finish_reason 映射为 Responses 的 completed / incomplete(+incomplete_details) / failed;reasoning_content 还原为 reasoning item,content 还原为 message item,tool_calls 还原为 function_call item;usage 做双字段名兼容换算 (prompt_tokens→input_tokenscompletion_tokens→output_tokenscached_tokens / reasoning_tokens 进 details)。

流式响应按 Responses SSE 事件序列还原: response.created → response.in_progress → response.output_item.added → response.content_part.added → response.output_text.delta / response.reasoning_text.delta / response.function_call_arguments.delta → 各 done 事件 → response.completed / response.incomplete / response.failed。不发送 Chat 协议的 [DONE] 哨兵;上游流被截断时会如实 归因为 incomplete/failed,不伪装成成功。

使用

默认:纯透传

python3 proxy.py --host 127.0.0.1 --port 8787

需要 agent_message 改写(上游还没修复时)

python3 proxy.py --port 8787 --fix-agent-message

需要 mimo 走转换、deepseek 透传

python3 proxy.py --port 8787 --chat-models mimo --fix-agent-message

~/.codex/config.toml 保持:

[model_providers.deepseek]
name = "deepseek"
base_url = "http://127.0.0.1:8787/v1"
wire_api = "responses"
experimental_bearer_token = "sk-..."    # 保持不变

http://127.0.0.1:8787(不带 /v1)也可以,代理会自动拼接。

直连 DeepSeek 官方 Chat API

python3 proxy.py --port 8787 \
  --chat-models deepseek \
  --chat-upstream https://api.deepseek.com/v1

此时 deepseek-* 请求会由代理转成 Chat Completions 发往 https://api.deepseek.com/v1/chat/completions;其他模型仍走 --upstream 的 Responses 通道。Codex 的 experimental_bearer_token 换成 DeepSeek 的 API key 即可。

DeepSeek 官方目前已原生支持 Responses API(仅 deepseek-v4-flash)。 如果上游直连 v4-flash 的 /responses,可以不开转换走纯透传;v4-pro 及 mimo 等仍走转换路径。

完整参数

python3 proxy.py --help

--host 127.0.0.1          监听地址
--port 8787               监听端口
--upstream https://opencode.ai/zen/go/v1
                          透传上游 base_url(Responses 通道)
--fix-agent-message       把 agent_message/encrypted_content 改写成标准
                          Responses message(透传路径;转换路径始终会改写)
--chat-models mimo        需要转成 Chat Completions 的模型前缀(逗号分隔,
                          前缀匹配);默认空,即关闭转换
--chat-upstream https://api.deepseek.com/v1
                          Chat 上游 base_url,默认与 --upstream 相同,
                          即拼 .../chat/completions;
                          也可直接写完整 .../chat/completions 地址
--timeout 600             上游超时(秒)
--client-timeout 120      客户端读请求超时(秒,0 表示不设超时)
--max-connections 128     最大并发连接数(超出返回 503,0 表示不限)
-v                        调试日志
--log-body                打印请求体(任务内容,按需开启)

日志

所有日志统一为「时间戳 + 消息」,一行一条,不带级别和 logger 名:

2026-08-08 21:41:40,953 丢弃 Chat 不支持的 tool: web_search
2026-08-08 21:41:40,953 POST /v1/responses -> 200 (98 ms) agent=main proto=responses
2026-08-08 21:41:40,953 POST /v1/responses -> 400 (981 ms) agent=/root/test_multimodal proto=chat error=bad request

字段说明:

  • agent=main:主代理请求;agent=/root/xxx:子代理请求(从请求里的 agent_message 任务信封收件人推断,例如 /root/worker)。
  • proto=responses:请求走透传(Responses 通道);proto=chat:请求走了 Responses→Chat 转换。

注意:多智能体 V2 下,主代理的上下文里也包含子代理的任务信封和回传结果 (agent_message item),所以不能靠"请求体是否被改写"判断主/子代理, 而是直接看任务信封的收件人。

默认(-v 不加)只输出有问题的请求,正常请求静默;以下关键失败都会在 控制台留下一行 error=...:

  • 连接上游失败 / 超时(502)
  • 上游返回 4xx/5xx(错误体里的一行消息)
  • 上游流式响应中途断开、SSE 解析失败、流被截断
  • Chat 转换失败 / 上游返回 200 + error envelope
  • 代理内部异常(500)

上游错误体是压缩/图片等二进制内容时,日志不会原样打印字节,只输出 一行摘要,例如 error=upstream returned HTTP 404 (gzip-compressed body, 1234 bytes)

错误响应透传会保留上游关键头(Retry-AfterWWW-AuthenticateContent-Encoding 等);错误体超过 1MB 会被截断并带 X-Upstream-Body-Truncated: true 标记。Chat 上游返回 200 + error envelope(或 200 但无 choices)时,流式和非流式都会还原成 Responses 的 failed 状态,不再把 Chat 错误体原样回给客户端、也不伪装成 completed。

需要看全部请求时加 -v

Docker

仓库通过 GitHub Actions 管理镜像构建与发布:

  • 普通 push 到 main:自动构建镜像(amd64 + arm64),只验证能编译, 不会发布;
  • 创建并发布 Release(如 v0.1.0):自动生成修复记录 (写入 Release Notes 并更新仓库 CHANGELOG.md),然后构建并发布到 GitHub Container Registry,生成 v0.1.0v0.1v0latest 标签。

发布后的镜像地址:

docker pull ghcr.io/thisdk/codex-opencode-proxy:latest

镜像的启动参数与 python3 proxy.py 完全一致、原样透传,比如本地的

python3 proxy.py --port 8787 --chat-models mimo --fix-agent-message -v

对应容器启动方式:

docker run --rm -p 8787:8787 \
  ghcr.io/thisdk/codex-opencode-proxy:latest \
  --port 8787 --chat-models mimo --fix-agent-message -v

规则:

  • 所有参数原样传给 proxy.py,与直接跑脚本没有区别;
  • 容器内默认绑定 0.0.0.0(入口脚本自动补 --host 0.0.0.0),这样 -p 8787:8787 端口映射才有效;如果你显式传了 --host,以你的值为准;
  • 不带任何参数时等价于 python3 proxy.py,默认监听 8787 端口;
  • 镜像包含 linux/amd64linux/arm64 两种架构;

发布方式(命令行示例):

gh release create v0.1.0 --target main --title "v0.1.0" --notes "发布 v0.1.0"

也可以在仓库 Release 页面手动创建并发布 Release。发布后 Actions 会自动 完成修复记录生成与 ghcr.io 推送;之后随时可以用 docker pull ghcr.io/thisdk/codex-opencode-proxy:v0.1.0 拉取对应版本。

本地构建:

docker build -t codex-opencode-proxy .

测试

python3 -m unittest test_proxy -v

共 70 个测试,覆盖:agent_message 改写开关、默认纯透传、mimo/deepseek 路由分离、Responses→Chat 请求映射、Chat→Responses 非流式还原、SSE 事件 序列与截断/错误路径、经本地 mock 上游的端到端 JSON 与流式转换、上游错误 透传、并发连接与超时处理。

环境要求

  • Python 3.8+
  • 无第三方依赖,直接 python3 proxy.py 即可运行

注意

  • 上游 opencode.ai 会按 User-Agent 拦截,普通 Python/curl 请求会被 403 (error code: 1010)。Codex 自己发出的请求带有 Codex 的 User-Agent,可以 正常通过,因此代理默认保持请求头原样透传。
  • 如果 encrypted_content 里是真正的密文(而非明文),转成 input_text 后模型看到的是密文。目前 Codex 对第三方 provider 的普通子代理任务发送 的是明文,可直接转换;若遇到真密文场景需要配合上游解密方案。
  • 客户端必须完整发送请求体:声明 Content-Length 但提前断开会收到 400, 慢连接超过 --client-timeout 会收到 408;Content-LengthTransfer-Encoding 并存会被拒绝。
  • close-delimited(无 Content-Length、无 chunked)的上游流式响应会逐块 立即转发,不会等攒满 64KB。
  • Chat 转换是无状态的:previous_response_idstoremetadata 等 Responses 状态字段会被丢弃;DeepSeek 在带 tools 的多轮对话里要求完整 回传 reasoning_content,代理会把输入里的 reasoning item 挂回 assistant 消息,但如果你遇到 400,请检查上一轮上游返回的 reasoning_content 是否 完整回传。
  • 非 function 类工具(web_search、file_search、computer_use 等)在 Chat 转换路径会被丢弃,只有 function / custom 工具可转换。
  • 如果 tool_choice 引用的是被丢弃的工具,转换会降级为 auto,避免上游 返回 400;namespace / tool_search 工具的完整双向还原仍在规划中。
  • 转换后的流式响应没有 [DONE] 哨兵(Responses 协议本身没有),不要用 Chat 客户端的解析方式去读。

License

Apache License 2.0

About

把 Codex 发出的 Responses API 请求转发到上游

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages