【免费下载链接】opencodex
Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code
本文基于 opencodex 仓库开发日志 002_investigation_297_catalog_clamp.md 展开,深入剖析 issue #297 所述的"catalog 钳制(clamp)在所有模型上剥离max/ultra推理等级"问题的机制、证据与修复选项。读完本文,你将掌握 opencodex 如何探测已安装 Codex 二进制的推理等级词汇表、如何对最终落盘目录执行全局钳制,以及为何该机制被证明为真、当前触发器却无法在本地复现,并理解条件性修复方案(版本门控 Option B)的设计约束。
问题背景:issue #297 的指控
2026-07-23 的 issue #297 提出如下指控:
"bug(catalog): clamp in b7ce5aad strips max/ultra from all models regardless of Codex binary version."
即:引入钳制逻辑的提交b7ce5aad会不论 Codex 二进制版本如何,从所有模型目录条目中剥离max与ultra推理等级。这直接威胁到 opencodex 在 GPT-5.6 时代推广的"六档推理等级"(low/medium/high/xhigh/max/ultra)——尤其是路由模型(如openrouter/...、anthropic/...)与 GPT-5.6 原生模型的顶级档位。
调查结论(在codex/issue-triage-260723分支54e0bbf88cebe6a77e0a8584af193cf689680ad6上完成,与当时的origin/dev一致)分为两半:
- 机制(Mechanism)被证实:目录级全局钳制会从一个全局集合出发,过滤所有已发射模型(含路由模型),因此确实可以撤销此前 ensure 函数追加的
max/ultra; - 当前触发器(Trigger)未被证实:在本地安装的
codex-cli 0.144.5上,捆绑目录自带全部六个档位,钳制不会剥离任何东西,问题无法复现。
一、机制确认:探测函数如何推导"支持的档位集合"
1.1 扫描范围:所有无/的裸 slug
codexSupportedReasoningEfforts是整条钳制链的源头。它加载已安装二进制的捆绑目录,遍历所有slug为不含/的字符串的模型行(即原生/裸 slug),把每个模型supported_reasoning_levels[].effort与default_reasoning_level的字符串值全部并入一个Set,仅当并集为空时返回null。该探测中不存在硬编码的原生 slug 白名单。
// src/codex/catalog/effort.ts:349-368(当前源码) export function supportedCodexReasoningEffortsFromObservedCatalog( catalog: ReadonlyRawCatalog | null, ): ReadonlySet<string> | null { if (!catalog) return null; const efforts = new Set<string>(); for (const model of catalog.models ?? []) { if (typeof model.slug !== "string" || model.slug.includes("/")) continue; const levels = Array.isArray(model.supported_reasoning_levels) ? model.supported_reasoning_levels : []; for (const level of levels) { const effort = (level as { effort?: unknown })?.effort; if (typeof effort === "string") efforts.add(effort); } if (typeof model.default_reasoning_level === "string") efforts.add(model.default_reasoning_level); } return efforts.size > 0 ? efforts : null; } export function codexSupportedReasoningEfforts(deps: BundledCatalogDeps = {}): ReadonlySet<string> | null { return supportedCodexReasoningEffortsFromObservedCatalog(loadBundledCodexCatalog(deps)); }关键语义:如果某个 Codex 版本的捆绑目录中,裸 slug 行恰好没有声明max/ultra,那么这个全局集合就只到xhigh,后续一切包含max/ultra的条目都会被判定为"该二进制无法反序列化"而剥离。
1.2 生产环境如何拿到捆绑目录
生产探测通过调用codex debug models --bundled获取目录 JSON,要求解析成功**且包含原生模板(native template)**才算数;多个命令候选依次尝试,首次成功即返回:
// src/codex/catalog/bundled.ts:220-237 export function runCodexDebugModels( command: string, execFile: ExecFile, deps: Pick<BundledCatalogDeps, "env" | "platform" | "existsSync"> = {}, ): string { const args = ["debug", "models", "--bundled"]; const invocation = codexExecInvocation(command, args, deps.platform ?? process.platform, { env: deps.env, exists: deps.existsSync, }); return execFile(invocation.file, invocation.args, { encoding: "utf8" as const, stdio: ["ignore", "pipe", "ignore"] as ["ignore", "pipe", "ignore"], timeout: 10_000, windowsHide: true, ...invocation.options, }); }候选命令的来源codexCommandCandidates(bundled.ts)依次包括:环境变量CODEX_CLI_PATH指定的路径(若设置)、codex-shim.json记录的包装器/原路径/备份路径(codexShimCommandCandidates)、Windows 下 PATH 中的codex.exe/codex.cmd,最后兜底"codex"。loadBundledCodexCatalog(bundled.ts)在无注入依赖时优先使用"单一已解析运行时"(resolveAndPersistCodexRuntime)的 command 作为唯一候选,保证钳制探测与 OpenCodex 实际启动的二进制是同一个;结果按BUNDLED_CATALOG_CACHE_MS = 60_000(1 分钟)进程内缓存。
1.3 本地只读运行证据(0.144.5)
调查机器上该命令路径解析为/Users/jun/.nvm/versions/node/v24.17.0/bin/codex,无CODEX_CLI_PATH覆盖、无 shim 状态候选。实测输出:
$ codex --version codex-cli 0.144.5 $ codex debug models --bundled | jq <bare-slug projection and effort union> gpt-5.6-sol low medium high xhigh max ultra gpt-5.6-terra low medium high xhigh max ultra gpt-5.6-luna low medium high xhigh max gpt-5.5 low medium high xhigh gpt-5.4 low medium high xhigh gpt-5.4-mini low medium high xhigh gpt-5.2 low medium high xhigh codex-auto-review low medium high xhigh union low medium high xhigh max ultra即当前被扫描的裸 slug 恰好是上面 8 行,推导出的集合是{low, medium, high, xhigh, max, ultra}。报告者提出的"当前二进制上集合只到xhigh"的触发器,对本地 0.144.5 为假;但单次观测并不能证明所有已发布版本或 Desktop 内置版本的捆绑目录都如此。
二、机制确认:条目级钳制如何剥离档位
2.1 保留/兜底/默认修复三规则
给定非空 supported 集合,clampEntryToCodexSupportedEfforts对单条目执行三件事:
- 保留过滤:仅保留
effort字符串在 supported 集合中的档位; - 兜底:若一个都不剩,替换为通用
low/medium/high三档(而非保留无法解析的阶梯); - 默认修复:若默认档不受支持,则取"存活档位中不高于原始档位排名的最高者"(空存活列表回退
medium)。
// src/codex/catalog/effort.ts:384-434(当前源码,节选) export function clampEntryToCodexSupportedEfforts( entry: RawEntry, supported: ReadonlySet<string> | null, ): void { if (!supported) return; const levels = Array.isArray(entry.supported_reasoning_levels) ? entry.supported_reasoning_levels as Array<{ effort?: string }> : null; if (levels && levels.length > 0) { const kept = levels.filter(level => typeof level?.effort === "string" && (supported.has(level.effort) || UNCLAMPABLE_REASONING_EFFORTS.has(level.effort))); ... entry.supported_reasoning_levels = kept.length > 0 ? kept : CODEX_REASONING_LEVELS .filter(level => level.effort === "low" || level.effort === "medium" || level.effort === "high") .map(level => ({ ...level })); } ... }需要指出的是,当前仓库源码与调查时的实现已有演进:现版在保留过滤时加入了UNCLAMPABLE_REASONING_EFFORTS(max/ultra白名单豁免,见 effort.ts 注释"per the unconditional-emission ruling"),并对保留条目(reserve)行做了requiresExactReserveEfforts分支。这体现了后续版本对本文调查结论的落地——但机制本身(用全局集合过滤每条目)与调查时一致。
2.2 目录级钳制:对所有条目无差别套用
clampCatalogModelsToCodexSupport把同一个全局集合应用到每一个模型,而不检查该 slug 是原生还是路由。因此,若 supported 集合止于xhigh,openrouter/example、anthropic/...、GPT-5.6 等一切带推理能力的条目都会被剥掉max/ultra。这也正是 issue #297 指控的核心:路由模型本应由其适配器自行映射档位,却被目录钳制越俎代庖。
// src/codex/catalog/effort.ts:541-600(当前源码,节选) export function clampCatalogModelsToCodexSupport(models: RawEntry[], deps: BundledCatalogDeps = {}): RawEntry[] { const supported = codexSupportedReasoningEfforts(deps); if (!supported) { if (!deps.commandCandidates) persistEffortClamp(null, { configDir: deps.configDir }); return models; } const clamp = clampCatalogModelsToObservedCodexSupport(models, supported); ... }现版还会在发生剥离时输出运行日志行(formatClampLogLines)并持久化EffortClampDiagnostic(runtimePath、runtimeVersion、removedEfforts、affectedModels),这是调查后新增的可观测性设施(见 runtime 相关代码)。
2.3 在 sync 路径中的"最后一棒"位置
syncCatalogModels在mergeCatalogEntriesForSync完成之后才调用目录级钳制,随后立即atomicWriteFile写入。也就是说,钳制是同步路径上对最终落盘目录的最后一次模型变更,完全可以删除前面构建器刚追加的档位:
// src/codex/catalog.ts:2200-2208(调查时行号,逻辑沿用至 catalog/sync.ts) const wsEnabled = websocketsEnabled(config); catalog.models = mergeCatalogEntriesForSync(catalog.models ?? [], goEntries, baseline, featured, wsEnabled, goIds, template, disabledNativeSlugs(config), gatheredProviderNames, multiAgentMode, exactComboSlugs, hasPhysicalComboProvider); clampCatalogModelsToCodexSupport(catalog.models); atomicWriteFile(catalogPath, JSON.stringify(catalog, null, 2) + "\n"); return { added: goEntries.length, path: catalogPath };三、机制冲突:ensure 函数先追加、钳制后剥离
3.1 两个 ensure 函数显式追加顶级档位
GPT-5.6 兜底函数ensureGpt56ReasoningLevels明确追加max(真实原生档位)与ultra(始终广告);旧原生辅助函数ensureUltraReasoningLevel在存在阶梯时同样追加两者:
// src/codex/catalog/effort.ts:314-346(当前源码) export function ensureGpt56ReasoningLevels(entry: RawEntry): void { const levels = ...; const out = [...levels]; // max is a real native rung on the 5.6 family — always restored. ultra is advertised unless // the slug's pinned ladder (or its source's) stops short of it, as gpt-6-luna's does. const wanted = typeof entry.slug === "string" && !nativeLadderIncludesUltra(entry.slug) ? ["max"] : ["max", "ultra"]; for (const effort of wanted) { if (out.some(level => level.effort === effort)) continue; out.push(CODEX_REASONING_LEVELS.find(level => level.effort === effort) ?? { effort, description: `${effort} reasoning` }); } entry.supported_reasoning_levels = out; } export function ensureUltraReasoningLevel(entry: RawEntry): void { const levels = ...; if (levels.length === 0) return; const wanted = ["max", "ultra"]; ... }这些辅助函数在目录构建/合并路径中运行——原生条目推导返回前、或保留的旧原生条目在合并数组返回前,都会先调用它们。于是同一条目的max/ultra可能被"先加后删"。
3.2 路由条目的档位由谁负责
路由(命名空间)条目走的是另一条路:applyReasoningLevels(effort.ts)为推理能力路由模型广告max/ultra(除非该模型 opt-out 合成顶级档),这是"用户决策 260709:mock top tiers"的延续。因此路由条目的max/ultra是合成广告,其真值映射发生在请求期(见下文nativeEffortClamp与mapReasoningEffort),而目录钳制并不区分广告与真值,一律过滤。
四、测试证据:证明机制,而非证明今日触发器
仓库中钳制测试位于 tests/codex-integration/codex-catalog.test.ts(调查记录写的是tests/codex-catalog.test.ts,现已随测试布局迁移)。其捆绑目录是依赖注入的 JSON 固定数据,只含一个裸gpt-5.5,并非从已安装二进制捕获的真实输出:
// tests/codex-integration/codex-catalog.test.ts:7110-7125(当前源码) function bundledCatalogDeps(efforts: string[]) { return { commandCandidates: () => ["codex"], execFileSync: () => JSON.stringify({ models: [{ slug: "gpt-5.5", base_instructions: "test", supported_reasoning_levels: efforts.map(effort => ({ effort, description: effort })), default_reasoning_level: "medium", }], }), }; }两个分支测试分别证明:
- 剥离分支:注入的探测集合止于
xhigh时,路由条目openrouter/example(六档 + 默认max)被裁剪为四档; - 保留分支:注入六档集合时,六档被完整保留。
// tests/codex-integration/codex-catalog.test.ts:7160-7195(当前源码) test("strips max and ultra when the installed Codex ladder stops at xhigh", () => { const models = [routedEntry()]; clampCatalogModelsToCodexSupport(models, bundledCatalogDeps(["low", "medium", "high", "xhigh"])); expect(models[0]!.supported_reasoning_levels.map(level => level.effort)) .toEqual(["low", "medium", "high", "xhigh"]); }); test("preserves max and ultra when the installed Codex ladder includes them", () => { const models = [routedEntry()]; clampCatalogModelsToCodexSupport(models, bundledCatalogDeps(["low", "medium", "high", "xhigh", "max", "ultra"])); expect(models[0]!.supported_reasoning_levels.map(level => level.effort)) .toEqual(["low", "medium", "high", "xhigh", "max", "ultra"]); });结论:测试无条件支持机制声明 (a);而对特定二进制触发声明 (b),测试并不覆盖,仍需直接拿到二进制输出才能验证。
五、回归提交的意图:保护严格枚举解析器
git show b7ce5aad --stat显示该提交改动 1 个运行时文件与 2 个测试文件(src/codex/catalog.ts63 行、tests/codex-catalog.test.ts74 行、tests/google-models-listing.test.ts2 行;136 插入、3 删除)。提交信息原文:
fix(codex): clamp catalog reasoning efforts to installed binary's ladder Adopts the approach from PR #223 by @Bricol1982 with safe-fallback improvements: when ALL efforts are unsupported, fall back to universal [low,medium,high] instead of preserving the unsupported ladder. Prevents Codex 0.133.0 catalog parse failure.背景事实链:Codex 0.133.0 的目录解析器对未知枚举值(max/ultra)会直接解析失败,且失败发生在任何请求到达 OpenCodex 原生/路由 wire 钳制逻辑之前。因此该钳制的存在是为了兼容性安全——但兼容性边界必须精确:只该对"真正无法反序列化这些档位的二进制"生效。
六、三个修复选项的评估
Option A:从CODEX_REASONING_LEVELS常量播种 supported 集合
六档常量定义于 src/reasoning-effort.ts,描述与上游捆绑models.json官方措辞一致(对应 openai/codex PR #31684):
export const CODEX_REASONING_LEVELS: { effort: string; description: string }[] = [ { effort: "low", description: "Fast responses with lighter reasoning" }, { effort: "medium", description: "Balances speed and reasoning depth for everyday tasks" }, { effort: "high", description: "Greater reasoning depth for complex problems" }, { effort: "xhigh", description: "Extra high reasoning depth for complex problems" }, { effort: "max", description: "Maximum reasoning depth for the hardest problems" }, { effort: "ultra", description: "Maximum reasoning with automatic task delegation" }, ];判定:拒绝。播种全部已知标签会把兼容性钳制变成对"正是导致 0.133.0 崩溃的那两个标签"的 no-op。请求期nativeEffortClamp无法弥补,因为它在请求到达后才触发,且明确区分原生 wire 行为与路由适配器映射——目录解析失败发生在请求之前,wire 钳制鞭长莫及。
Option B:对目录钳制做版本门控(conditional 推荐)
门控可对已知严格枚举的二进制保留xhigh上限,同时允许"已被证明能反序列化这些档位、但捆绑原生行恰好未声明它们"的版本保留标签。这是唯一既能保住原兼容性边界、又能处理真实解析器/目录错配的选项。
约束要点(调查原文强调):
- 解析器接受阈值必须由真实二进制矩阵确立:
0.133.0已知严格,本地0.144.5声明全六档,但首个接受版本尚未确定; - 实现必须探测与捆绑目录同源的同一个命令候选的版本。当前
loadBundledCodexCatalog内部循环候选并只返回解析后的目录,若独立探测codex --version,可能门控到另一套安装(多安装候选错配)。
// src/codex/catalog/bundled.ts:239-303(当前源码,候选循环主体) for (const command of unique(candidates)) { try { const catalog = parseCatalogJson(runCodexDebugModels(command, execFile, deps)); if (catalog && findNativeTemplate(catalog)) { if (useCache && cacheKey) { publishBundledCatalogCache(cacheKey, Date.now() + BUNDLED_CATALOG_CACHE_MS, catalog); return cloneAndDeepFreeze(bundledCatalogCache!.value!); } return cloneAndDeepFreeze(catalog); } } catch { /* try next candidate */ } }与nativeEffortClamp的交互是干净的:版本门控只控制目录反序列化兼容性;客户端能接受max/ultra之后,旧阶梯原生请求仍被裁剪到快照中的最高真实档位,路由请求仍归各 provider 的映射表/适配器管。
// src/codex/catalog/effort.ts:52-75(当前源码,节选) export function nativeEffortClamp(slug: string, effort: string | undefined): string | null { if (!effort || (effort !== "max" && effort !== "ultra")) return null; if (slug.includes("/")) return null; // routed models map efforts in their adapters const entry = UPSTREAM_NATIVE_ENTRIES.get(slug); ... return highest ?? null; }路由侧由 src/reasoning-effort.ts 的mapReasoningEffort独立负责:先执行ultra -> max边界转换(与上游 codex-rs 的reasoning_effort_for_request一致),再应用 provider 别名映射,最后按配置的支持档位钳制得到 wire 值。
Option C:把 ensure 函数移到钳制之后运行
判定:拒绝。ensure 函数是原生条目辅助函数,不是通用的路由模型恢复通道。路由条目由applyReasoningLevels负责(见 effort.ts),ensure 函数只出现在原生分支,因此简单移动/重跑它们并不能为报告中所有路由 provider 恢复max/ultra。且回归风险高:在兼容性边界之后重新加标签,等于把未知枚举变体重新送给旧的严格客户端——wire 钳制无法阻止发生在请求之前的目录解析失败。若把 Option C 扩展成"钳制后全量恢复",本质上就是在更晚的代码行重演 Option A,并保留同样的安全性缺陷。
七、任何代码变更前的强制验证清单
- 获取报告者的精确
codex --version、可执行文件路径与codex debug models --bundled裸 slug 档位并集; - 演示错配条件:客户端能成功解析含
max/ultra的最小目录,而自身捆绑裸条目并集缺其一或全部; - 若走 Option B:用二进制矩阵确定首个解析器兼容版本,矩阵至少覆盖已知严格的
0.133.0、阈值的前一版本、阈值本身以及当前 CLI/Desktop 候选; - 新增把版本与目录探测结果绑定到同一命令候选的测试,并保留现有 synthetic strip / preserve / no-probe / fallback / default-repair 用例;
- 运行
bun run typecheck、聚焦的 catalog/reasoning 测试以及完整bun run test(最终发射边界全局影响原生与路由模型)。
八、结论与建议方向
判定:opencodex-bug —— 条件性成立,今日所检二进制上未确认。全局剥离机制从源码与测试均可证明;针对报告者的"当前触发"在本地codex-cli 0.144.5上被证伪,且因报告未附精确版本与捆绑目录输出而无法进一步支撑。该行为对0.133.0这类解析器拒绝未知档位的二进制是正确且必要的。
桶分类建议:将 #297 从 Bucket 2("立即调查")降级为 Bucket 1("答复 + 关闭 / 等待精确版本复现")。回复应展示 0.144.5 的全六档并集,并索取上述三个运行时工件;仅当他们演示出"解析器可解析 / 捆绑阶梯缺失"的错配时才重新打开或恢复 Bucket 2。
推荐方向:条件性 Option B,在缺少触发器证据前不做任何立即源码修改。若错配二进制被演示,则用经验证的解析器接受阈值对钳制做版本门控,并把版本/目录探测绑定到同一候选。不要使用 Option A 或 C:两者都绕过了目录反序列化保护,且 C 对路由条目还不完整。
工作量评估:在拿到可复现二进制/版本后约需0.5~1 个工程师日——代码改动应很小,但确立阈值、防止多安装候选错配、添加二进制版本固定数据矩阵、跑通全横切面测试才是大头。若无错配证据,剩余工作仅为一份有证据支撑的 issue 答复/关闭(约 30 分钟,零代码改动)。
延伸阅读
- 完整调查记录:002_investigation_297_catalog_clamp.md
- 钳制与 ensure 实现:src/codex/catalog/effort.ts
- 捆绑目录探测与缓存:src/codex/catalog/bundled.ts
- 六档常量与路由映射:src/reasoning-effort.ts
- 钳制合成测试:tests/codex-integration/codex-catalog.test.ts
【免费下载链接】opencodex
Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code
相关推荐
OpenCodex Reasoning Clamp 深度解析:用观测到的 Codex 运行时推理等级校准模型目录,修复 0.133.0 严格枚举破坏
OpenCodex Reasoning Clamp 深度解析:用观测到的 Codex 运行时推理等级校准模型目录,修复 0.133.0 严格枚举破坏 本篇文章围
drizzle-seed 0.1.2 缺陷修复解析:reset() 运行时依赖剥离与 schema 类型兼容性
drizzle seed 0.1.2 缺陷修复解析:reset 运行时依赖剥离与 schema 类型兼容性 本文基于 drizzle seed 0.1.2 版本
后端数据库ORMRabbitMQ 3.13.5 维护版本发布详解:关键缺陷修复、对等发现改进与升级路径
RabbitMQ 3.13.5 维护版本发布详解:关键缺陷修复、对等发现改进与升级路径 导读 本文基于当前仓库中 RabbitMQ 3.13.5 的官方发布说明
后端消息队列消息路由
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考