首页 > 教程攻略 > ai教程 >opencodex 解锁 Codex 任意模型,一个本地袋里打通 Claude/Kimi/GLM/DeepSeek

opencodex 解锁 Codex 任意模型,一个本地袋里打通 Claude/Kimi/GLM/DeepSeek

来源:互联网 时间:2026-07-23 07:22:10

如果说有什么事情,能让一个技术爱好者感到憋屈,打开Codex CLI或者Claude Code的时候,发现界面顺手、Agent跑任务流畅,但就是想换个模型试试,结果发现官方根本不给选项——这大概算其中之一。

你手里明明有Claude的Max订阅、有Gemini的API、本地还跑着一个DeepSeek,可Codex就是认死了它自己的后端。你敲下 codex -m deepseek-chat,它理都不理你。

官方没支持,你只能等。等OpenAI哪天心情好把别的模型接进来,或者等Anthropic哪天松口。

opencodex 干的事就这么直接——不等了。它在你本地起一个袋里,把Codex说的话翻译成任何模型听得懂的话,再把回答翻译回去。听起来像个普通的LLM网关,但你真去翻它的源码会发现,这玩意儿根本不是「转发器」那么简单。

一个项目,一个月,2553 颗星

先交代下背景。opencodex仓库是2026年6月18日才创建的,到现在刚满一个月,收获了2553个star、175个fork。TypeScript写的,MIT协议。一个人(lidge-jun)主导,17个贡献者。

迭代速度相当惊人。我去拉release记录,7月20号到21号两天就发了v2.7.27、v2.7.28、v2.7.29、v2.7.30、v2.7.31五个正式版,外加好几个preview tag。一天两三个版本是常态。从commit频率可以感觉到,作者几乎住在代码里。

这种项目有个特点:功能堆得快,但稳定性需要时间检验。后面会讲到它的几个已知坑,都是这种快速迭代留下的痕迹。

它到底解决什么

一句话概括,opencodex让你能用Codex CLI、Codex App、Codex SDK,甚至Claude Code,去调用任何后端的模型——Claude、Gemini、Grok、GLM、DeepSeek、Kimi、Qwen、Ollama都行,内置了40多个provider。

它的工作原理可以用一张图说清楚:

Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provider
│
Anthropic · Google · xAI · Kimi · Ollama Cloud · Groq
OpenRouter · Azure · DeepSeek · GLM · …以及 OpenAI 自己

关键点在于那句「use any LLM with Codex — and with Claude Code too」。它不只是给Codex当袋里,还能反过来给Claude Code当后端,让你在Claude Code里用GPT或者Gemini。双向都通。

翻译官的核心,是七个适配器

很多人把这类工具理解成「请求转发」,就像Nginx反向袋里一样,原封不动把请求搬过去。但Codex说的是OpenAI的Responses API,而Claude说的是Messages API,Gemini有自己的一套generateContent,Ollama又是OpenAI兼容的Chat Completions。这四种协议根本不是一回事。

opencodex的核心设计是适配器模式。每个provider对应一个适配器,适配器实现 ProviderAdapter 接口(定义在 src/adapters/base.ts),接口里最关键的两个方法是:buildRequest 把内部请求格式翻译成上游的HTTP请求,parseStream 把上游的流式回答翻译回内部事件。

interface ProviderAdapter {
    name: string;
    buildRequest(parsed, incoming?): AdapterRequest | Promise<AdapterRequest>;
    parseStream(response): AsyncGenerator<AdapterEvent>;
    // ...
}

有意思的是数量。README顶部写的是「5 protocol adapters cover Anthropic Messages, Google Gemini, Azure, OpenAI Responses passthrough, and every OpenAI-compatible Chat Completions endpoint」。但你翻它的 docs-site 适配器参考文档,第一行写的是「The seven provider adapters」。我去 src/adapters/ 目录数了一遍:openai-chat.tsopenai-responses.tsanthropic.tsgoogle.tsazure.tscursor.tskiro.ts,确实七个。README没跟上迭代,少算了Cursor和Kiro两个实验性适配器。

这种文档和代码的轻微错位,在一个一个月发30多个版本的项目里太正常了。

整条管线是怎么跑的

适配器只是其中一环。一个请求从Codex出来到上游provider,要经过一条完整的管线。官方文档画得很清楚,这里简化成这样的流程:

Codex ──▶ parser ──▶ router ──▶ [vision] ──▶ adapter ──▶ provider ──▶ Codex
         解析      路由      视觉旁路    翻译     上游(SSE)

第一步是Parse。responses/parser.ts 用Zod schema(responses/schema.ts)校验请求,把它降级成内部的 OcxParsedRequest。那个schema文件定义得非常细致:input_image 块支持 auto|low|high|original 四种detail级别,reasoning item 带了 encrypted_content 字段——这是Codex协议里那种不透明的加密载荷,后面会讲到它带来的麻烦。

第二步是Route,这是整条管线里设计最讲究的一环。

router.ts 的七层优先级

src/router.ts 里的 routeModel 函数,负责把一个模型id映射到具体的provider。它不是简单的if-else,而是一个有明确优先级的七层决策。从源码里读出来的顺序是这样的:

  1. Combo模型:先查是不是组合模型(多个provider聚合的虚拟模型)
  2. 显式命名空间:形如 provider/model 的,比如 anthropic/claude-opus-4-8,只有当前缀匹配到已配置的provider才命中
  3. 裸OpenAI家族:gpt-o1-o3-o4- 开头的,走Codex登录的OpenAI provider
  4. defaultModel精确匹配:遍历所有provider,看谁把 defaultModel 设成了这个id
  5. 前缀模式匹配:routeByKnownModelPattern,比如 claude- 开头的路由到anthropic,llama-/mixtral-/gemma- 路由到groq
  6. models数组匹配:遍历provider的 models[] 列表
  7. defaultProvider兜底

这个设计有个很妙的细节。第2层显式命名空间里,它不是无脑split,而是先判断前缀是不是真的对应一个已配置的provider。源码注释写得很直白:「Only triggers when the prefix matches a CONFIGURED provider, so genuine slash-containing model ids fall through」。也就是说 anthropic/claude-... 这种天然的slash id不会误触发,只有当你真的配了一个叫anthropic的provider,它才会按命名空间解析。

这种边界处理看着不起眼,但它决定了一个袋里在复杂配置下会不会路由错乱。很多同类工具在这层就是一锅粥。

Design B,不re-tag你的历史

opencodex有个相当值得注意的设计决策,藏在 src/codex/inject.ts 里。

它要把Codex的请求导向自己,最直接的办法是改Codex的配置,把 model_provider 换成opencodex。但这样有个后果:你之前所有的对话历史都被打上了opencodex的provider标签,一旦你卸载opencodex,这些历史会因为找不到provider而出错。

作者的解法叫Design B(源码注释原话,2026-07-06定稿)。本地回环安装时,opencodex不再替换 model_provider,而是只改一个字段——openai_base_url,让Codex自己的 openai provider指向袋里。这样对话历史的provider标签始终是原生的 openai,永远不需要迁移或恢复。

注释里还提到了一个被修掉的bug:「Appending the bare key at EOF was the original bug, it nested under whatever table happened to be open last」。早期版本因为TOML追加位置不对,导致 model_provider 被塞进了最后一个打开的table里,Codex根本读不到,悄悄回退到了ChatGPT provider。这种bug的隐蔽性,用过Codex配置的人都懂。

非本地回环的绑定(比如LAN暴露)还是得用传统的table注入,因为原生provider没法带自定义的鉴权header。这是个务实的取舍。

流式传输里的失败合成

袋里最难处理的不是正常请求,是异常。尤其是流式请求:SSE已经开始吐token了,上游突然断了,客户端看到的是什么?一个光秃秃的socket断开,没有任何结束事件。

src/server/relay.ts 里有个函数 relaySseWithFailedTail,专门处理这种情况。它的逻辑是:一旦上游在流到一半时reset,它不会傻乎乎地直接重发(注释里明确写了「Deliberately NOT a resend, the upstream already committed the request」),而是合成一个干净的终结——先关闭可能残缺的SSE block,再注入一个 response.failed 事件和 data: [DONE],让客户端收到一个结构完整的失败响应。

controller.enqueue(encoder.encode(`nnevent: response.failedndata: ${payload}nndata: [DONE]nn`));

这个设计很克制。它知道上游已经「committed」了这个请求(可能已经计费),所以宁可告诉客户端「失败了」,也不去冒重复请求的风险。这种对幂等性的敬畏,在袋里类工具里不多见。

还有一个细节:sanitizePassthroughHeaders 函数。Bun的fetch会自动解压响应体,但把 content-encoding 和过期的 content-length 留在响应头里。如果袋里原样转发这些头,Codex会按头里的编码再解压一次,结果是每个gpt直通请求都报「stream error」。这个函数专门把这些hop-by-hop头剥掉。注释写得很到位:「Relaying those makes the caller double-decode / truncate」。

ChatGPT 账号池,一个有点野的功能

opencodex不止做协议翻译,它还管ChatGPT账号池。你可以加多个ChatGPT/Codex账号,袋里帮你自动轮换。

这个功能的核心在 src/codex/quota.ts。它会追踪每个账号在三个时间窗口的配额使用率:5小时、每周、30天。每个窗口对应OpenAI那套 primary_window/secondary_window/tertiary_window 的配额机制。

轮换规则分两种情况。已有的会话保持「亲和性」:一个thread绑死在启动它的那个账号上,这样你SSH或者tmux挂着的长会话不会被中途换号。新的会话可以自动路由:袋里会对比当前账号在最热那个窗口的使用率,超过阈值就挑一个用量更低、健康的账号顶上。

失败处理也很明确。token失败标记为「需要重新认证」,而不是悄悄降级到别的凭证。429配额超限把账号扔进冷却期,后续请求可以fail-over到池里其他账号。

坦白讲,这个功能技术上不复杂,但它踩在了一个灰色地带。README顶部的Disclaimer写得很直:opencodex和OpenAI、Anthropic没有任何关系,而且「some providers may suspend or restrict accounts that route API traffic through third-party proxies, Use at your own risk」。把多个订阅账号池化轮换,到底算不算违反ToS,这个得你自己掂量。

一条没修好的跨模型 sub-agent 链路

说完亮点,必须讲一个实打实的坑。这是issue #92,9个点赞,是作者自己开出来标记的已知限制。

场景是这样的:你用v2多Agent模式,父Agent跑在原生的 gpt-5.6-sol 上,调用 spawn_agent 派一个子Agent去跑 xai/grok-4.5。模型覆盖是成功的,子Agent确实用上了grok,但问题是子Agent收不到任务内容。

子Agent的 NEW_TASK 消息里,明文payload是空的,后面跟着一个Fernet加密的 encrypted_content 块。路由到非OpenAI模型的子Agent解不开这个加密块,于是报告「没有具体任务」,或者从上下文里瞎猜一个任务来跑。

根因在于前面提到的那个 encrypted_content 字段。Codex原生协议里,reasoning item可以携带这种不透明的加密载荷,只有OpenAI自己的后端能解开。一旦跨provider委派,这个加密块就成了死信。

README里这个限制写得挺诚实:「when a native parent spawns a routed child, the task body can currently arrive backend-encrypted and be lost, use the v1 surface for reliable cross-provider delegation」。翻译过来就是:想跨模型委派任务,现在老老实实用v1接口,别碰v2。

这里值得单独拿出来讲,是因为这个bug暴露了「协议翻译」类工具的根本困境。你能翻译消息格式,能翻译工具调用,但你翻译不了别人加密的载荷。加密是个信任边界,袋里站在边界外面,天然进不去。这不是opencodex写得不好,是这个品类注定要面对的限制。

作者在追着三个对手跑

最后一件事,挺有意思的。在仓库里发现一个 devlog/_chase/ 目录,是作者用韩语写的竞品追踪笔记。它明确把三个项目当成「upstream」在追赶:

  • jawcode:一个同类袋里,opencodex大量代码是从它port过来的
  • cli-proxy-api:另一个袋里,作者认为它在协议和认证层处理得更深
  • litellm:那个老牌的LLM网关,长尾provider覆盖最全

作者把能力差距分成四类。G1是jawcode新增的provider/模型自己还没跟上,G2是cli-proxy-api在wire/auth/replay上更硬核,G3是litellm的长尾provider覆盖,G4是opencodex独有的(Kiro、Codex WebSocket、订阅IDE后端)。

这能解释它为什么迭代这么疯。它不是在闭门造车,是在一场明确的多方军备竞赛里抢位置。2553颗星一个月拿下来,背后是这种追赶压力。

这个项目该不该用

聊到这你大概也看出来了,opencodex是那种「方向很对、执行很猛、但还很年轻」的项目。

它的适配器架构和路由设计是真有东西的:七层路由优先级、Design B的历史安全注入、流式失败合成、账号池配额管理,这些都不是玩具代码能写出来的。如果你受够了Codex锁死单一后端,想用自己手里的模型订阅,它是目前最完整的解法之一。

但你也得清楚它的状态。一个月的项目,文档和代码还在错位,跨模型sub-agent这条链路有硬伤,账号池化功能踩在ToS灰区。它适合愿意折腾、能接受快速迭代(和偶尔被新版本带坑)的人。如果你要的是开箱即用、稳定到能上生产的网关,LiteLLM这种沉淀了更久的项目可能更稳妥。

从这套适配器设计里能学到的最有价值的东西,是那个翻译边界的认知。协议可以翻译,消息可以翻译,工具调用可以翻译,但加密载荷翻译不了。做任何中间层——不管是LLM袋里、API网关还是消息队列桥接——都要想清楚,你的翻译能力在哪个边界戛然而止。opencodex把这个边界诚实地写进了issue区,这种工程坦诚比功能数量更值得信赖。