跳到內容

代理 API 格式

opencodex 以多種客戶端方言呈現一個本機代理。Codex 客戶端可說 Responses API,OpenAI 相容的 app 可說 Chat Completions,而 Claude Code 可說 Anthropic Messages,而不需要每個上游供應商實作每種格式。

正常轉譯路徑為:

客戶端方言 → 內部 Responses 模型 → 供應商 adapter → 供應商 wire 格式
供應商事件 → 內部 adapter 事件 → 客戶端方言

Responses 表示是橋接的中心。原生相容的路由可跳過部分轉譯並 passthrough 請求,但認證、路由、許可控制與回應安全仍在代理邊界發生。在設定中設定監聽器與許可金鑰;當一個公開模型 id 應在多個目標間選擇時使用組合

攜帶憑證的模型、圖片、影片和搜尋請求不會自動跟隨 HTTP 重新導向,包括同源重新導向。請設定最終上游 API URL,而非會重新導向的別名。伺服器不會向重新導向目標再次傳送憑證或請求內文。各回應處理路徑保留原有的錯誤處理或轉送行為;原生 Responses 和 compact 路徑仍可向用戶端回傳原始 3xx 與 Location。用戶端的重新導向行為與此伺服器傳輸政策屬於不同邊界。

客戶端介面 端點 成功的非串流結果 成功的串流或 socket 結果
OpenAI Responses POST /v1/responses Responses JSON Responses SSE,或 WebSocket 上的 Responses JSON text frame
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 計數 POST /v1/messages/count_tokens { "input_tokens": number } 不適用
模型探索 GET /v1/models 目錄或明確指定的 Desktop 快照 不適用
語音與 Realtime POST /v1/live, POST /v1/realtime/calls 中繼的 call-creation 回應 一個獨立的 sideband WebSocket 雙向中繼 frame
Responses compaction POST /v1/responses/compact 取代歷史 JSON 不適用

這是原生 opencodex data-plane 結構。請求 body 必須是帶有非空 model 的 JSON 物件。input 可為字串或 Responses 項目陣列。

區域 接受的結構
模型與輸入 必填的非空 model;可選字串 input 或項目陣列
訊息項目 userdevelopersystemassistant 訊息;字串內容或適合該角色的型別內容區塊
內容區塊 Text、輸入圖片、輸入檔案、輸出文字、拒絕,以及 reasoning summary/text 區塊(在其父項目允許時)
工具歷史 function_callfunction_call_outputcustom_tool_callcustom_tool_call_output 項目
工具 Function 工具加上鬆散的內建或代管工具項目;tool_choice 接受 autononerequired、具名 function/custom 選擇、代管選擇或 allowed_tools
Reasoning reasoning.effortreasoning.summaryautoconcisedetailednone
接續與快取 previous_response_idstoreprompt_cache_key
生成控制 max_output_tokenstemperaturetop_pstoppresence_penaltyfrequency_penalty
服務與執行 streamservice_tierparallel_tool_callsinstructionsmetadatauser
延伸 Responses 欄位 backgroundincludeprompttexttruncation 對相容路由被接受

未知的項目型別被接受為鬆散的型別項目以向前相容。轉譯的 adapter 僅處理它識別的項目型別,且可能拒絕其供應商無法表示的功能。

stream: true 時,回應為 text/event-stream。橋接發出 Responses 事件如 response.created、output-item 與 text/tool delta,以及恰好一個終端 response.completedresponse.failedresponse.incomplete 事件。正常串流以 data: [DONE] 結束。

stream: false 或無 stream 時,相同的 adapter 事件被收集為一個 Responses JSON 物件。兩種形式都保留所選模型、輸出項目、終端狀態與 usage。

每個終端 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 客戶端的相容性保證;零可能意指「未回報」,不一定是「供應商未執行此類工作」。

每個通過准入的 HTTP Responses 回覆都帶有 x-opencodex-request-id 標頭,其中保存代理產生、格式為 ocx-<32 hex> 的識別碼。它是將回應連結至請求日誌及用量報告中對應列的關鍵。

代理一律產生此值,並覆寫呼叫端提供或上游傳回的任何識別碼,因此它專屬於此代理,可安全信任為關聯鍵。該標頭列於 Access-Control-Expose-Headers 中,這讓瀏覽器中的 JavaScript 能跨來源讀取它;否則即使自訂 x- 標頭已在實際傳輸中,response.headers.get() 仍看不到它。

在認證或來源准入階段遭拒的 Responses 請求不會進入此包裝層,也不會帶有識別碼,因此缺少此標頭表示該請求在寫入日誌前已遭拒。

websockets 啟用時,客戶端可升級 /v1/responses 而非開啟 HTTP POST。認證與來源許可在 WebSocket 握手期間發生。它們不在每個 frame 內重複。

客戶端發送 JSON text frame:

{
"type": "response.create",
"model": "provider/model",
"input": "Hello",
"tools": [],
"generate": true
}

除了 type 之外的一切成為 Responses 請求 body,且代理為該回合強制串流。新的 response.create 取代並取消該 socket 上的前一個回合。 response.processed 被接受為 no-op 確認。無法解析或不相關的 frame 型別被忽略。

伺服器 frame 為 JSON text frame。成功的串流輸出使用會出現在 SSE data: 列中的相同 JSON payload,而無 SSE 封裝或 [DONE]。非串流的內部結果被重新框架為 response.created、零或多個 response.output_item.done frame,然後是終端 frame。錯誤使用此封裝:

{
"type": "error",
"status": 502,
"error": {
"type": "upstream_error",
"message": "..."
},
"headers": {}
}

帶有 generate: false 的暖機 frame 不呼叫上游。它回傳合成的 response.created 後接 response.completed,兩者皆有空的回應 id 且無輸出。

此端點接受帶有必填 model 與非空 messages 陣列的 OpenAI 相容 Chat Completions 請求。它將 system、user、assistant 與 tool 訊息轉譯為內部 Responses 項目;轉譯 function 工具、tool choice、圖片、reasoning effort 與支援的回應格式;執行正常 Responses 路由管線;然後將結果轉譯回來。

結構化輸出是該轉譯的一部分:帶 json_objectjson_schemaresponse_format 被轉發到路由的 openai-chat 模型。在 POST /v1/responses 上,等效請求欄位是 text.format:原生 Responses 路由在原始 Responses body 中保留它,並在模型路由到 openai-chat 供應商時轉譯為 response_format。列在供應商 noStructuredOutputModels 中的模型會在該 chat wire 上省略 response_format;同儕模型保留轉譯。未分類的後端收到該欄位並回傳自己的錯誤,而非由代理猜測其能力。

非串流輸出有 object: "chat.completion"。串流輸出使用帶有 object: "chat.completion.chunk"、choice delta、帶有 finish_reason 的終端 choice 與 data: [DONE] 的 SSE 物件。Tool-call 與 usage 資訊在來源事件帶有它們時被轉譯回來。

由於內部執行路徑基於 Responses,供應商 adapter 可施加較窄的功能集。例如,所選 adapter 無法表示的請求功能以錯誤回傳,而非靜默變更其意義。

這些端點說 Claude Code 與相容客戶端使用的 Anthropic Messages 方言。多數請求被轉譯為 Responses、正常路由,然後轉譯回 Anthropic JSON 或 Anthropic SSE。

原生 Anthropic passthrough 僅在以下全部為真時合格:

  • 原生 passthrough 未在 Claude Code 設定中被停用;
  • 請求的模型以 claudeanthropic 開頭;
  • 請求帶有原生 Anthropic bearer 或 x-api-key 憑證;
  • 在非回環 listener 上,請求還只透過 x-opencodex-api-key 攜帶有效代理許可;且
  • 無設定的別名或模型映射為路由目標聲明該模型 id。

合格的請求以 Anthropic 方言轉發,使原生 beta 標頭、thinking 簽章與訂閱身分保持端到端。否則它走 Responses 往返。

專用許可標頭絕不轉發。Authorizationx-api-key 中的代理許可密鑰也會被移除, 而另一標頭中的真正 Anthropic 憑證會保留。以逗號合併的模糊憑證標頭會 fail closed。

POST /v1/messages/count_tokens 遵循相同的模型解析與 passthrough 決策。原生合格的請求被轉發到 Anthropic 的計數端點。其他請求使用基於 system 內容、訊息與工具的本機檔案式估計並回傳:

{ "input_tokens": 123 }

無法解析的日期型 Desktop ID 也可能是探索結果中缺少的真實原生模型 ID。現有資訊不足以 解析該 ID 時,Messages 和 count-tokens 回傳 HTTP 503 及固定錯誤 desktop_model_mapping_unavailable;這不代表 模型無效。未知的舊版雜湊別名仍回傳 HTTP 400。兩種情況都不會移除日期或回退到其他路由。 已知 ID、已註冊映射、精確 modelMap 匹配及已識別的真實原生 ID 維持原有處理方式。 請重新整理模型探索或重新套用已連接 hub 的設定後再試;僅重試本身不能保證解決。

未指定 format=desktop-config 時,使用以下一般目錄契約:

契約 觸發 頂層結構 模型 id 行為
Anthropic 模型清單 anthropic-version 標頭或 ?flavor=anthropic,無 client_version { "data": [...] } 含 Anthropic model-info 項目 Claude Code 收到可讀 id;Desktop 可收到其設定檔專屬的別名家族
Codex 目錄 client_version query 參數 { "models": [...] } 原生與路由項目帶有更豐富的 Codex 目錄欄位、可見性、effort、WebSocket 與多代理中繼資料
普通 OpenAI 清單 無觸發 { "object": "list", "data": [...] } 可見的原生 id 為裸 id;路由 id 為別名或 provider/model

GET /v1/models?ids=desktop&format=desktop-config 明確選擇 Desktop 快照,不依賴 user-agent。回應為 { "version": 1, "models": [...] },帶有 Cache-Control: no-store。 客戶端送出 Accept: application/jsonanthropic-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 區分 committedrolled_back。中斷會還原管理設定,或對已確認的舊設定檔回報標準回退,同時保留使用者欄位和 後來有效的選擇。衝突或未完成的復原不會標為完成。需要重新啟動 Desktop 才會讀取磁碟變更; 中斷不會自動撤銷 hub 金鑰。參見 Desktop 指南。 thinking 重播與提示快取仍由獨立的 #3719 跟進。

POST /v1/live 接受 ChatGPT/Codex App Frameless call-creation 介面。 POST /v1/realtime/calls 接受 OpenAI Realtime call-creation 介面。opencodex 選擇一個合格的 OpenAI 家族路由、為上游認證模式正規化 call-creation 請求,並中繼有界的回應。

在 call 建立後,客戶端可使用任何支援的入站形式加入 sideband WebSocket:

  • /v1/live/{callId}
  • /v1/realtime/calls/{callId}
  • /v1/realtime?call_id={callId}

代理正規化上游 join URL,然後雙向透明中繼 text 與 binary frame。客戶端協定標頭被保留,而上游認證保持代理擁有。

Compaction 為需要縮短長 Responses 對話的客戶端回傳取代歷史。

路由型別 行為
規範 ChatGPT 或官方 OpenAI 路由 以解析的帳號與模型認證將請求轉發到原生 /responses/compact 端點
其他路由模型 執行一個內部、非串流、無工具的 compaction 回合,帶有 compaction_trigger;需要恰好一個合成的 compaction 項目,其 encrypted_contentocx1: 封裝;將該摘要解碼為 v1 取代歷史

原生 compact 回應以 32 MiB 上限緩衝,包含其宣告的 Content-Length 已超過限制的回應。Compact 專屬失敗包含:

狀態 型別或代碼 意義
400 invalid_request_error 無效 JSON/body 結構或缺失模型
404 invalid_request_error 請求的模型無法被路由
499 client_cancelled 客戶端在中繼或緩衝時取消
502 compact_response_too_large 原生 compact 輸出超過 32 MiB
502 upstream_error 連線、讀取或合成 compaction 回合失敗
502 invalid_response_error 合成回合未產生恰好一個有效、非空的 ocx1: compaction 項目

在僅回送綁定上,data-plane 許可不需要設定的金鑰。在遠端綁定上,請使用下方矩陣。「專屬」意指 X-OpenCodex-API-Key;其他欄意指 Authorization: Bearer ...x-api-key

介面 專屬 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 路由時還原它。

錯誤在需要時使用客戶端方言的封裝,但這些狀態/代碼意義是穩定的:

狀態 型別或代碼 意義
401 authentication_error 必填的代理許可憑證缺失或無效
403 origin_rejected Responses/OpenAI data-plane 請求或 WebSocket 升級來自不允許的來源
503 combo_unavailable 所選組合中的每個目標都不可用、在冷卻中、停用或因其他原因不合格
400 unreadable_encrypted_agent_task 加密的 v2 worker task 沒有可處理它的合格規範 ChatGPT 目標或明確信任的 Responses 目標
426 upgrade_required Responses WebSocket 傳輸被停用或升級失敗;請使用 HTTP

Anthropic 來源的失敗以 Anthropic 的錯誤封裝渲染,因此該方言上的來源拒絕是 403 permission_error,而非 OpenAI 風格的 origin_rejected body。

代理將真實的後端密文視為不透明。結構有效的密文被逐位元組保留:opencodex 不解密它、轉譯其內容,或為另一個供應商重新加密它。

某些 agent hook 在歷史上曾將明文控制文字放入 encrypted_content 插槽。為相容性,代理將該明文分離為 text 部分,同時保留任何結構有效的 Fernet run 不變。若 agent_message 在該修復期間失去所有加密部分,它成為普通使用者訊息。若目前的 v2 task 保持真正加密但所選路由目標無法讀取原生 ChatGPT 密文,opencodex 以 unreadable_encrypted_agent_task 失敗,而非發送不可讀的位元組給該供應商。關於 worker task 周圍的客戶端行為,請見子代理介面