Proxy API 格式
opencodex 以多种客户端方言提供一个本地代理。Codex 客户端可以使用 Responses API,兼容 OpenAI 的应用可以使用 Chat Completions,而 Claude Code 可以使用 Anthropic Messages,而不需要每个上游提供方都实现每一种格式。
标准转换路径如下:
client dialect → internal Responses model → provider adapter → provider wire formatprovider events → internal adapter events → client dialectResponses 表示是这座桥的中心。原生兼容的路由可以跳过部分转换并直接透传请求, 但认证、路由、准入控制和响应安全仍然发生在代理边界。请在 Configuration 中配置监听器和准入密钥;当一个公开模型 ID 需要在多个目标之间选择时,请使用 Combos。
携带凭据的模型、图像、视频和搜索请求不会自动跟随 HTTP 重定向,包括同源重定向。请配置最终上游 API URL,而不是会重定向的别名。服务器不会向重定向目标重新发送凭据或请求正文。各响应处理路径保留原有的错误处理或转发行为;原生 Responses 和 compact 路径仍可向客户端返回原始 3xx 和 Location。客户端的重定向行为与此服务器传输策略是不同的边界。
| 客户端表面 | 端点 | 成功的非流式结果 | 成功的流式或套接字结果 |
|---|---|---|---|
| OpenAI Responses | POST /v1/responses |
Responses JSON | Responses SSE,或通过 WebSocket 传输的 Responses JSON 文本帧 |
| OpenAI Chat Completions | POST /v1/chat/completions |
chat.completion JSON |
以 chat.completion.chunk SSE 结尾并带 [DONE] |
| Anthropic Messages | POST /v1/messages |
Anthropic message JSON |
Anthropic Messages SSE |
| Anthropic token count | POST /v1/messages/count_tokens |
{ "input_tokens": number } |
不适用 |
| 模型发现 | GET /v1/models |
目录或显式 Desktop 快照 | 不适用 |
| 语音和 Realtime | POST /v1/live, POST /v1/realtime/calls |
转发的调用创建响应 | 独立的 sideband WebSocket 双向转发帧 |
| Responses compaction | POST /v1/responses/compact |
替换历史 JSON | 不适用 |
POST /v1/responses
Section titled “POST /v1/responses”这是 opencodex 原生的数据平面形状。请求体必须是一个包含非空 model 的 JSON 对象。input 可以是字符串,也可以是 Responses 项目数组。
接受的请求字段
Section titled “接受的请求字段”| 区域 | 接受的形态 |
|---|---|
| 模型和输入 | 必需的非空 model;可选的字符串 input 或项目数组 |
| 消息项 | user、developer、system 和 assistant 消息;字符串内容,或适用于该角色的类型化内容块 |
| 内容块 | 当其父项允许时,可包含文本、输入图像、输入文件、输出文本、拒绝,以及 reasoning summary/text 块 |
| 工具历史 | function_call、function_call_output、custom_tool_call 和 custom_tool_call_output 项 |
| 工具 | 函数工具以及宽松的内建或托管工具条目;tool_choice 接受 auto、none、required、命名的 function/custom 选择、托管选择,或 allowed_tools |
| 推理 | reasoning.effort 和 reasoning.summary(auto、concise、detailed 或 none) |
| 续接和缓存 | previous_response_id、store 和 prompt_cache_key |
| 生成控制 | max_output_tokens、temperature、top_p、stop、presence_penalty 和 frequency_penalty |
| 服务和执行 | stream、service_tier、parallel_tool_calls、instructions、metadata 和 user |
| 扩展 Responses 字段 | 兼容路由接受 background、include、prompt、text 和 truncation |
未知项目类型会作为宽松的类型化项被接受,以保证前向兼容。已翻译的适配器只处理它们能识别的项目类型,并且可能会拒绝其提供方无法表示的特性。
JSON 和 SSE 输出
Section titled “JSON 和 SSE 输出”当 stream: true 时,响应为 text/event-stream。桥会发出 Responses 事件,例如
response.created、输出项和文本/工具增量,以及且仅有一个终止的
response.completed、response.failed 或 response.incomplete 事件。正常的流以
data: [DONE] 结束。
当 stream: false 或未提供 stream 时,同样的适配器事件会被收集为一个 Responses JSON
对象。两种形式都会保留所选模型、输出项、终止状态和 usage。
面向客户端的 Responses SSE 帧按 SSE 块分隔符之前的原始字节计算,每帧限制为 4 MiB。对于 HTTP,未终止的上游帧一旦超过该限制,会以合成的 response.failed 事件并随后发送 data: [DONE] 的方式 fail closed。对于 Responses WebSocket 桥,相同情况会发送 502 websocket_protocol_error 并取消上游 reader。已经完整到达的 Responses 终止帧具有优先权;其后的超大或格式错误字节会被丢弃,而不会把已经完成的轮次替换为传输失败。
每个终止的 Responses usage 对象都包含两个 detail 对象,即使提供方没有报告这些细节:
{ "input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "input_tokens_details": { "cached_tokens": 0 }, "output_tokens_details": { "reasoning_tokens": 0 }}在可用时,input_tokens_details 还可以包含 cache_write_tokens。始终存在的 detail 对象是严格 Responses 客户端的兼容性保证;零可能表示“未报告”,不一定表示“提供方没有进行此类工作”。
将响应与其请求日志关联
Section titled “将响应与其请求日志关联”每个通过准入的 HTTP Responses 回复都带有 x-opencodex-request-id 标头,其中保存代理生成的 ocx-<32 hex> 形式 ID。它是将响应与请求日志及使用情况报告中对应记录关联起来的键。
代理始终生成此值,并覆盖调用方提供或上游返回的任何 ID,因此该值仅属于此代理,可安全地用作关联键。该标头列在 Access-Control-Expose-Headers 中,浏览器 JavaScript 因而可以跨源读取它;否则,即使自定义 x- 标头已在网络上传输,response.headers.get() 也无法看到它。
在身份验证或来源准入阶段被拒绝的 Responses 请求不会到达此包装层,也不会带有 ID。因此,缺少该标头意味着请求在写入日志之前已被拒绝。
同一路径上的 WebSocket 升级
Section titled “同一路径上的 WebSocket 升级”当启用 websockets 时,客户端可以升级 /v1/responses,而不是发起 HTTP POST。
认证和 origin 准入会在 WebSocket 握手期间发生,不会在每个帧内重复。
客户端发送 JSON 文本帧:
{ "type": "response.create", "model": "provider/model", "input": "Hello", "tools": [], "generate": true}除 type 之外的一切都会成为 Responses 请求体,并且代理会强制该轮为流式。新的
response.create 会取代并取消该 socket 上的上一轮。response.processed 被接受为无操作确认。无法解析或无关的帧类型会被忽略。
服务器帧是 JSON 文本帧。成功的流式输出使用与 SSE data: 行中相同的 JSON 负载,只是不带 SSE 封装或 [DONE]。非流式的内部结果会被重新封装为 response.created,接着是零个或多个 response.output_item.done 帧,然后是一个终止帧。错误使用以下封装:
{ "type": "error", "status": 502, "error": { "type": "upstream_error", "message": "..." }, "headers": {}}带 generate: false 的 warmup 帧不会调用上游。它会返回一个合成的
response.created,随后是 response.completed,两者都带空的 response id 且没有输出。
POST /v1/chat/completions
Section titled “POST /v1/chat/completions”该端点接受与 OpenAI 兼容的 Chat Completions 请求,要求提供 model 和一个非空的
messages 数组。它会把 system、user、assistant 和 tool 消息转换为内部 Responses 项;
转换 function tools、tool choice、图像、reasoning effort 和受支持的 response formats;
运行标准的 Responses 路由管线;然后再把结果转换回去。
非流式输出的 object: "chat.completion"。流式输出使用 SSE 对象,object: "chat.completion.chunk"、
choice 增量、带 finish_reason 的终止 choice,以及 data: [DONE]。工具调用和 usage 信息会在源事件携带它们时被转换回去。
由于内部执行路径基于 Responses,因此提供方适配器可以施加更窄的特性集。例如,若请求中的某个特性无法被所选适配器表示,它会作为错误返回,而不是静默改变其含义。
POST /v1/messages 和 count_tokens
Section titled “POST /v1/messages 和 count_tokens”这些端点使用 Claude Code 和兼容客户端所采用的 Anthropic Messages 方言。大多数请求会被转换为 Responses,按常规路由,然后再转换回 Anthropic JSON 或 Anthropic SSE。
转换后的 Messages 请求在重放推理数据时共享整个请求的转换预算,其中包含编码和解码产生的副本开销。
超出预算时返回 HTTP 413 和 translation_buffer_limit,不会为了满足限制而截断签名或不透明推理数据。
原生 Anthropic 透传使用独立的请求体大小限制。
只有在满足以下全部条件时,原生 Anthropic 透传才有资格启用:
- Claude Code 配置中尚未禁用原生透传;
- 请求的模型以
claude或anthropic开头; - 请求携带原生 Anthropic bearer 或
x-api-key凭证; - 在非回环监听器上,请求还仅通过
x-opencodex-api-key携带有效代理准入;并且 - 没有配置的别名或模型映射把该 model id 声明为一个被路由目标。
符合条件的请求会以 Anthropic 方言转发,因此原生 beta 头、thinking 签名和订阅身份都能端到端保留。否则它会走 Responses 往返。
专用准入请求头绝不会转发。Authorization 或 x-api-key 中的代理准入密钥也会被移除,
而另一个请求头中的真实 Anthropic 凭据会保留。含逗号拼接的歧义凭据请求头会 fail closed。
POST /v1/messages/count_tokens 采用相同的模型解析和透传决策。符合原生条件的请求会转发到 Anthropic 的 count 端点。其他请求会使用本地文档化的估算值,对 system 内容、messages 和 tools 进行统计,并返回:
{ "input_tokens": 123 }无法解析的日期型 Desktop ID 也可能是发现结果中缺失的真实原生模型 ID。现有信息不足以
解析该 ID 时,Messages 和 count-tokens 返回 HTTP 503 及固定错误 desktop_model_mapping_unavailable;这并不证明
模型无效。未知的旧版哈希别名仍返回 HTTP 400。两种情况都不会去除日期或回退到其他路由。
已知 ID、已注册映射、精确 modelMap 匹配及已识别的真实原生 ID 保持原有处理方式。
请刷新模型发现或重新应用已连接 hub 的配置后再试;仅重试本身不能保证解决。
GET /v1/models
Section titled “GET /v1/models”未指定 format=desktop-config 时,使用以下普通目录契约:
| 契约 | 触发条件 | 顶层形态 | 模型 ID 行为 |
|---|---|---|---|
| Anthropic model list | anthropic-version 头或 ?flavor=anthropic,且没有 client_version |
{ "data": [...] },包含 Anthropic model-info 条目 |
Claude Code 收到可读 ID;Desktop 可以收到其 profile-specific 别名族 |
| Codex catalog | client_version 查询参数 |
{ "models": [...] } |
原生和路由条目携带更丰富的 Codex catalog 字段、可见性、effort、WebSocket 和 multi-agent 元数据 |
| Plain OpenAI list | 两个触发条件都没有 | { "object": "list", "data": [...] } |
可见的原生 ID 是裸值;路由 ID 是别名或 provider/model |
Desktop 配置快照
Section titled “Desktop 配置快照”GET /v1/models?ids=desktop&format=desktop-config 显式选择 Desktop 快照,不依赖
user-agent。响应为 { "version": 1, "models": [...] },带有 Cache-Control: no-store。
客户端发送 Accept: application/json、anthropic-version: 2023-06-01 及现有数据访问凭证;
不需要管理员令牌,也不上传配置。条目是 hub 发放的 Desktop 配置模型,不是 Codex 目录行。
此格式与 ids=cli 或任意 client_version 一起使用时返回 HTTP 400。不指定格式时,上述普通
契约保持不变。Claude 关闭时返回 { "version": 1, "models": [] };已连接的 Desktop apply
会视为不可用,不写入替代配置。返回普通目录而非版本 1 的旧 hub 不受支持,客户端不会回退到
本地生成的 ID。
快照仍是只读模型列表,不是密钥轮换或配置上传 API。Desktop 密钥迁移、恢复与断开由现有
客户端连接流程处理。轮换保留模型条目和选择;CLI 的 rotation 区分 committed 与
rolled_back。断开会恢复管理设置,或对已确认的旧配置报告标准回退,同时保留用户字段和
后来有效的选择。冲突或未完成的恢复不会标为完成。需要重启 Desktop 才会读取磁盘变更;
断开不会自动撤销 hub 密钥。参见 Desktop 指南。
thinking 重放与提示缓存仍由独立的 #3719 跟进。
POST /v1/live 和 Realtime sideband
Section titled “POST /v1/live 和 Realtime sideband”下文的账户绑定说明适用于原生 Codex 客户端。通过外部 API 密钥使用语音转写和 GPT-Live,请参阅英文音频 API 规范。
Connections > API keys 包含独立的听写和实时语音区域。数据密钥仅保留在表单内存中。听写会上传所选文件;语音连接检查不使用麦克风,而是等待会话确认。已配置不代表连接成功。
POST /v1/live 接受 ChatGPT/Codex App 的 Frameless call-creation 表面。
POST /v1/realtime/calls 接受 OpenAI Realtime 的 call-creation 表面。opencodex 会选择
一个符合条件的 OpenAI 家族路由,将 call-creation 请求规范化为上游认证模式,并转发有界响应。
完成调用创建后,客户端可以使用任一受支持的入站形式加入 sideband WebSocket:
/v1/live/{callId}/v1/realtime/calls/{callId}/v1/realtime?call_id={callId}
代理会规范化上游 join URL,然后在双向透明转发文本和二进制帧。客户端协议头会被保留,而上游认证仍由代理拥有。
POST /v1/responses/compact
Section titled “POST /v1/responses/compact”Compaction 会为需要缩短长 Responses 会话的客户端返回替换历史。
| 路由类型 | 行为 |
|---|---|
| Canonical ChatGPT 或官方 OpenAI 路由 | 将请求转发到原生 /responses/compact 端点,并使用已解析的账号和模型认证 |
| 其他路由模型 | 运行一次内部的、非流式、无工具的 compaction 轮次,并带 compaction_trigger;要求且只允许一个 synthetic compaction 项,其 encrypted_content 是一个 ocx1: 封装;把该摘要解码为 v1 替换历史 |
原生 compact 响应会被缓冲,最大 32 MiB,即使其声明的 Content-Length 已经超过该限制也是如此。compact 专用失败包括:
| 状态 | 类型或 code | 含义 |
|---|---|---|
| 400 | invalid_request_error |
非法的 JSON/主体形状或缺少 model |
| 404 | invalid_request_error |
请求的 model 无法被路由 |
| 499 | client_cancelled |
客户端在转发或缓冲期间取消了请求 |
| 502 | compact_response_too_large |
原生 compact 输出超过 32 MiB |
| 502 | upstream_error |
连接、读取,或合成 compaction 轮次失败 |
| 502 | invalid_response_error |
合成轮次没有产出恰好一个有效、非空的 ocx1: compaction 项 |
在仅绑定到 loopback 的情况下,数据平面准入不需要配置密钥。在远程绑定上,请使用下表。“Dedicated” 指 X-OpenCodex-API-Key;其他列指 Authorization: Bearer ... 和 x-api-key。
| 表面 | Dedicated | Bearer | x-api-key |
|---|---|---|---|
/v1/responses HTTP 和 WebSocket |
接受 | 接受 | 被拒绝 |
/v1/responses/compact |
接受 | 接受 | 被拒绝 |
/v1/chat/completions |
接受 | 接受 | 被拒绝 |
/v1/messages 和 /v1/messages/count_tokens |
接受 | 接受 | 接受 |
/v1/models |
接受 | 接受 | 接受 |
/v1/live、/v1/realtime/calls 和 sideband join |
接受 | 接受 | 接受 |
Responses 系列和 Chat 请求接受专用标头或 Bearer 字段中的代理密钥。在原生路由上,所选的已保存 Codex 凭据会替换 admission bearer;其他路由会移除该 bearer。代理密钥绝不会用作 upstream 凭据。如果还要提供独立的 provider bearer,请将代理密钥放在专用标头中。
没有密钥且不使用 OAuth 的 Cursor 路由可以使用调用方单独提供的 bearer,但不能使用代理 secret 或自动补充的 ChatGPT main 凭据。Combo/policy 选择以及实际发生的 shadow/thread-spawn 路由改写不会将调用方的原始凭据传递给新目标。规范 OpenAI 路由仅在 JWT 包含 ChatGPT 账户声明,且任何显式账户标头都与该声明匹配时,才可在内部路由变更后恢复调用方的单个非代理密钥 bearer。 向可选的 OpenAI sidecar 转发调用方认证时,需要单个 JWT 以及显式提供且匹配的 chatgpt-account-id。即使提供了显式账户标头,opaque bearer 也不会跨路由变更恢复。 除此之外,最终目标必须拥有自己的配置、OAuth 或已保存凭据,否则请求会在本地失败。只有 thread-spawn 标记而没有路由变化时,不会移除凭据。
对于未配置密钥的 Cursor Chat 请求,只有实际计划了 OpenAI 辅助调用且存在规范的 Direct 候选时,才会补充已存储的 main 身份验证。无关的 Cursor 请求不会通过此流程占用 native main,因此不会延迟配置文件切换。辅助调用凭据仍受启动和切换保护限制,并与 Cursor bearer 分离。Pool 及明确指定账号的辅助调用保留现有账号选择。
Claude replay 只会以当前 turn 已取得所有权的内存 snapshot 保留 main 凭据,并且仅在最终目标为规范 ChatGPT 路由时恢复它。
常见错误词汇
Section titled “常见错误词汇”错误会在需要时使用客户端方言的封装,但这些状态/code 含义是稳定的:
| 状态 | 类型或 code | 含义 |
|---|---|---|
| 401 | authentication_error |
所需的代理准入凭证缺失或无效 |
| 403 | origin_rejected |
一条 Responses/OpenAI 数据平面请求或 WebSocket 升级来自不允许的 origin |
| 503 | combo_unavailable |
所选 combo 中的所有目标都不可用、处于冷却、已禁用或以其他方式不具备资格 |
| 400 | unreadable_encrypted_agent_task |
一个加密的 v2 worker task 没有任何可处理它的合格规范 ChatGPT 目标或明确信任的 Responses 目标 |
| 426 | upgrade_required |
Responses WebSocket 传输被禁用,或升级失败;请改用 HTTP |
Anthropic 来源的失败会以 Anthropic 的错误封装呈现,因此该方言中的 origin 拒绝会是
403 permission_error,而不是 OpenAI 风格的 origin_rejected body。
加密内容卫生
Section titled “加密内容卫生”代理把真正的后端密文视为不透明数据。结构有效的密文会逐字节保留:opencodex 不会对其解密、翻译其内容,或为另一个提供方重新加密。
某些 agent hook 历史上会把明文控制文本放进 encrypted_content 槽。为兼容起见,代理会把那部分明文拆分为文本片段,同时保持任何结构有效的 Fernet 片段不变。如果一个 agent_message 在该修复过程中失去了所有加密部分,它就会变成普通的 user message。如果当前的 v2 task 仍然真的是加密的,但所选路由目标无法读取原生 ChatGPT 密文,opencodex 会以
unreadable_encrypted_agent_task 失败,而不是把不可读字节发送给该提供方。有关 worker task 周边的客户端行为,请参见 Sub-agent Surface。

