☰
opencodex 与 grok-build 桥接的 Usage 明细严格性兼容:零默认值归一化与 `[model_providers.opencodex]` 配置面
2026/9/26 15:01:07 网站建设 项目流程

【免费下载链接】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

项目地址:https://gitcode.com/gh_mirrors/ope/opencodex
点击查看免费下载

本篇技术指南聚焦 opencodex 在接入 grok-build(Grok 侧grokCLI 的构建版 harness)时的关键兼容性问题:pinnedasync-openaifork 将 Responses 协议的input_tokens_details/output_tokens_details定义为 required 字段,而 opencodex 桥接层此前会在上游未上报缓存/推理 token 时省略这些明细,导致调用方在response.completed之后硬性反序列化失败。读完本文,你将掌握 opencodex 的responsesUsage()与chatCompletionsUsage()双编码器零默认值归一化的实现原理、本地冒烟验证方法,以及 Grok 侧[model_providers.opencodex]复用块与/v1/models目录联动的推荐配置。

一、背景:一次只读源码分析 fold-in 的判定结论

devlog/_fin/260723_grok_build_bridge/001_sol_source_analysis.md记录了 Sol(medium) 子代理对grok-build a5727c5(树位于180_grok-build,SOURCE_REV30192d2eef5d)的只读源码分析。核心判定可归纳为五条:

  1. Responses usage details 是强制字段(non-Option):pinnedasync-openaifork(rev95b52ebd)的ResponseUsage将input_tokens_details/output_tokens_details定义为 required struct(无#[serde(default)]),嵌套字段cached_tokens、reasoning_tokens同样是 required。
  2. Chat Completions 客户端相对宽松:chunk 的usage是 Optional,details 也是 Optional,嵌套字段走 zero-default(见xai-grok-sampling-types/src/types.rs:535-587)。
  3. system 消息在 chat 后端按正常 role 发送(conversation.rs:1781-1784)——ocx chat 入站将其折叠为 instructions,因此不构成问题。
  4. 目录抓取在 loopback 场景下也无豁免、必须带 Bearer(remote/client.rs:686-738):空 key 报No API key for custom models endpoint;返回{data:[...]}形状,id 取自model|modelId<|begin▁of▁sentence|>- id|_meta.*,context_window缺失时默认 256k。
  5. 新增配置面:[model_providers.<id>]复用块 +[model.<id>]上的auth_provider(动态 Bearer 助手);后端枚举保持 3 种(chat_completions为默认)。
  6. custom-models 指南(11-custom-models.md)在旧/新 revision 下 blob 相同,契约未变。

这条分析的意义在于:Grok 侧三个后端/新配置面无论选用哪一个,只要 ocx 在桥接时省略 usage details,就会触发严格客户端的硬失败。这是一个发生在"响应已经完成"之后的反序列化错误,形态上表现为整轮 turn 以非零退出码收场。

二、根因拆解:required 的ResponseUsage结构

问题源头不在 opencodex 本身,而在 grok-build 锁定的async-openaifork 对 Responses 事件的结构定义。该 fork 中ResponseUsage把input_tokens_details、output_tokens_details声明为无#[serde(default)]的 required struct,且嵌套的cached_tokens、reasoning_tokens也非可选。这意味着反序列化器在解析response.completed事件时,若 usage 对象里缺少任一 details 键,整条事件流都会报错。

涉及的位置(文档记录,位于 grok-build 源码树,非本仓库):

  • crates/codegen/xai-grok-sampler/src/client.rs:99-129——SSE 反序列化入口,失败时抛SamplingError;
  • crates/codegen/xai-grok-sampler/src/stream/responses.rs:319-327, 482-488——流式 Responses 事件的解析路径。

对比之下,同一仓的 Chat Completions 采样器(xai-grok-sampling-types/src/types.rs:535-587)对 chunkusage、details 及嵌套字段全部放宽为 Optional / zero-default。这种"两套客户端严苛度不一致"的现状,正是下文双编码器修复的出发点。

三、本地冒烟交叉验证:raw_data 的决定性证据

主 agent 于 2026-07-23 的本地冒烟给出决定性证据(/tmp/grok-smoke-chat.err中的 raw_data):

Failed to deserialize ResponseStreamEvent … missing field `input_tokens_details` raw_data={"type":"response.completed", … "usage":{"input_tokens":0,"output_tokens":22,"total_tokens":22}}

三个关键观察:

  • 即便显式设置api_backend = "chat_completions",ocx-chat(cursor/grok-4.5)的实际 wire 仍是 Responses 事件,并在反序列化时失败。grok 0.2.101 harness 在这一 turn 走了 Responses 客户端——即 harness 内部存在与配置无关的路径,强制使用 Responses 词汇表(精确触发条件列为残留调查项)。
  • 原生gpt-5.4-mini退出码为 0:ChatGPT 上游总是携带 details,ocx 原样保留,反序列化自然通过。
  • routed 场景死亡的原因:ocx 的src/bridge.tsresponsesUsage()在上游未上报 cached/reasoning 时省略了*_tokens_details。

由此收敛出修复方向:无论 Grok 侧走哪条后端路径,只要 ocx 恒定输出 usage details,整个矩阵(native / chat / routed)即可全部打通。wp1 必须同时覆盖两个编码器(详见下一节)。

四、收敛结论:responsesUsage()与chatCompletionsUsage()双编码器修复

结合 Sol 建议(chat 默认桥接)与本地冒烟(事实上强制 Responses 路径),修复需覆盖两处:

4.1src/bridge.ts的responsesUsage()

src/bridge/internal.ts中responsesUsage()(internal.ts)实现了"details 恒常输出、缺省补零"的归一化:

  • input_tokens_details.cached_tokens恒常存在(上游无值时为 0);output_tokens_details.reasoning_tokens恒常存在(无值时为 0);usage 整体为 undefined 时,默认返回对象也包含两者。
  • cached_tokens只承载 cache 读(与 OpenAI 语义一致),并钳制在inputTokens之内,防止上游绝对 check-point 报出超过输入的 cache 读;cache_write_tokens仅在cacheCreationInputTokens已知时按inputTokens - cacheRead的下界输出。
  • 来自上游的未知 usage 字段(订阅元数据、未来计数器等)按 openai/codex#41980 的对齐方式透传,但cache_write_tokens只从归一化后的合法值发出,绝不从 raw 复制未知形状。
  • contextTotalTokens存在时,inputTokens按contextTotalTokens - outputTokens拆算,避免活跃上下文 check-point 被重复计入 output。

这段逻辑被严格客户端契约直接引用——注释里明确写着:pinned fork(rev95b52ebd)的response_usage.rs中InputTokenDetails/OutputTokenDetails均为非 Option,省略它们会把一次成功 turn 变成response.completed之后的硬退出(missing field input_tokens_details,2026-07-23 实测复现)。

4.2src/chat/outbound.ts的chatCompletionsUsage()

src/chat/outbound.ts的chatCompletionsUsage()(outbound.ts)做 Responses 内耗量到 Chat Completions 形状的换算:prompt_tokens/completion_tokens/total_tokens之外,prompt_tokens_details.cached_tokens与completion_tokens_details.reasoning_tokens也恒常输出。注释明确其动机:grok 的 chat 客户端虽然对 details 是 Optional,但为保持与responsesUsage()的对称性、并提前适配未来可能出现严格反序列化的客户端,始终补齐。

4.3 使用场景:编码器与投递链

responsesUsage()的调用面覆盖了所有对外出口,保证"任意出口的 usage 形状都一致":

  • sse.ts:completed/incomplete/failed各终止事件统一经responsesUsage()归一化后 emit;adapter_eof的兜底response.incomplete甚至以responsesUsage(undefined)输出全零。
  • client-encoder-delivery.ts:投递终端按terminal.usageOnWire选择responsesUsage(terminal.usage)或 null。
  • chat.ts 与 messages.ts:Chat Completions 与 Anthropic Messages 编码器在completed/incomplete终止时均走同一归一化。

chatCompletionsUsage()则用于 Chat Completions 出口(outbound.ts 的流式帧与 outbound.ts 的非流式响应体,以及 chat.ts 的终止帧)。

五、Grok 侧新配置面:[model_providers.opencodex]复用块

文档将 Grok 0.2.109 之后可用的 provider 继承能力列为 wp2 文档化对象,仓库内的 grok/inject.ts 印证了这一点:Grok 0.2.109(2026-07-21)起,[model_providers.<id>]上声明的base_url、api_backend、api_key、extra_headers会经由with_provider_defaults → resolve_model_list → sampling_config_for_model → SamplingClient链路应用到继承模型的路由。ocx 因此只发射一张共享的[model_providers.opencodex]表,每个[model.*]通过model_provider引用它,模型别名只做薄层追加。

推荐配置形态(一个块共享 base_url/backend,逐模型加薄别名):

[model_providers.opencodex] name = "opencodex" base_url = "http://127.0.0.1:10100/v1" api_backend = "chat_completions" # 后端枚举 3 种之一,chat_completions 为默认 api_key = "<ocx admission key>" extra_headers = { x-opencodex-grok = "1" } [model.grok-4.5] model_provider = "opencodex" name = "OCX grok-4.5"

值得注意的工程细节:ocx 在 grok/inject.ts 中区分"生成别名"与"人类手写条目"——手写[model.x]表、人类自设的api_key、指向远程主机的 loopback base_url、或仅凭 loopback base_url 本身,均不会被误判为 ocx 管理条目而清扫;只有命中确定性生成别名形状(chat_completions+name = "OCX <model>"+x-opencodex-grok = "1"标记)或model_provider = "opencodex"继承形状的条目才归属管理块。api_backend在 enum 层面仍是三选一、chat_completions为默认,auth_provider作为[model.<id>]上的动态 Bearer 助手出现,二者共同构成新配置表面。

六、模型目录联动:GROK_MODELS_BASE_URL与 ocx/v1/models

文档给出的目录联动方案:

GROK_MODELS_BASE_URL=http://127.0.0.1:10100/v1 XAI_API_KEY=<任意非空值>

loopback 场景下 ocx 忽略 admission key,因此XAI_API_KEY只需非空即可满足 grok 目录抓取的 Bearer 要求(该抓取对 loopback 也无豁免)。ocx 的/v1/models已经以{data:[{id,…}]}形状返回模型列表;若 ocx 能按模型携带api_backend/context_window字段,即可接近 zero-config(文档标注为选择性的 out-of-scope 项)。

仓库侧确有对应基础:server/models-capabilities.ts 把context_window/context_length/max_output_tokens镜像为每个模型行的顶层字段,供 pi-ai、DSH、LibreChat 等只读扁平属性的外部客户端发现,并针对grok-4.x与grok-build-latest提供 effort 阶梯(minimal/low/medium/high/xhigh)。这些模型行正是/v1/models响应的数据来源。

七、可观测性:零默认合成值与真实测量的区分

零默认归一化带来一个副作用:wire 上的cached_tokens: 0可能是"合成值"而非"实测值"。仓库在日志层对此做了明确隔离:

  • usage/log.ts:严格客户端归一化会在每条桥接 wire 上输出 zero-default 明细对象(responsesUsage),因此从 wire 回读的cached_tokens: 0属于兼容性产物,不是 cache miss 的测量结果;observed/synthesized/unknown三类值在全链路保持独立,避免池丢弃全部热前缀后仍报出看似合理的命中率(#4546)。
  • request-log.ts:桥接上报原始 adapter 用量时置usageFromBridge标记,SSE/JSON 二次解析不得覆盖原始来源——合成的cached_tokens: 0不是实测 cache 读,cache_detail_missing不得被静默吞掉。
  • sse.ts:responsesUsage()在终止事件处总是输出 zero-default 明细,因此 wire 不能作为 provenance 来源,调用方需在logCtx.usage保留原始上报值。

这一点对排障很重要:当你在日志里看到cached_tokens: 0时,需先确认它来自上游真实上报还是桥接合成,避免把兼容性产物当成容量/缓存策略的测量依据。

八、残留待办与适用前提

  • grok 0.2.101 在api_backend="chat_completions"custom model 上强制走 Responses wire 的精确触发条件(config 解析?harness goal-tracker 侧车?)尚未定位,需在 wp1 验证时用 grok 调试日志复现确认。
  • tool-call 往返冒烟划入 wp2,本次 bridge 修复仅覆盖 usage 形状。
  • 适用前提:本仓库内responsesUsage/chatCompletionsUsage的实现、[model_providers.opencodex]发射逻辑、/v1/models能力字段均以当前仓库为准;grok-build 侧的行号与 fork rev 来自关联文档的只读分析记录。

九、参考路径

  • 关联文档:001_sol_source_analysis.md
  • 桥接归一化:src/bridge/internal.ts
  • Chat 出口归一化:src/chat/outbound.ts
  • SSE 终止事件:src/bridge/sse.ts
  • Grok provider 继承注入:src/grok/inject.ts
  • 模型目录能力字段:src/server/models-capabilities.ts
  • 合成零值溯源隔离:src/usage/log.ts、src/server/request-log.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

项目地址:https://gitcode.com/gh_mirrors/ope/opencodex
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询