Repository navigation
fix(openai): 改写 Codex turn metadata 时保留非 ASCII 转义 - #7426
Merged
Wei-Shaw merged 1 commit intoSep 22, 2026
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
目的
网关改写
x-codex-turn-metadata(账号身份、设备指纹)之后,输出仍保持可见 ASCII,避免工作区路径含中文等非 ASCII 字符的 Codex 会话被上游中途断开。解决的问题
x-codex-turn-metadata是一段内嵌 JSON 字符串,既作为 HTTP 头发送,也放在 Responses WebSocket 请求体的client_metadata里。它的workspaces字段以工作区路径为键,路径里有中文目录名很常见;客户端发出时非 ASCII 字符是\uXXXX转义形式,整段是可见 ASCII。网关有四处会改写这段元数据,流程都是
json.Unmarshal→ 改字段 →json.Marshal:client_metadataapplyCodexAccountIdentityHeadersapplyCodexAccountIdentityEmbeddedMetadatarewriteCodexTurnMetadataFieldsrewriteClientMetadataEmbeddedTurnMetadatajson.Marshal不保留转义形式:\u4e2d(“中”)会被写成原始 UTF-8 字节,DEL(0x7f)也原样输出。两种写法在 JSON 语义上等价,但这段字符串有请求头用途,上游对其中的原始非 ASCII 字符不兼容。用户可见的表现(WS v2 passthrough,工作区路径含中文目录名):上游只回
codex.rate_limits和codex.response.metadata两条元事件,随后以 1000、空 reason 关闭,业务轮次没有终态;网关侧报upstream websocket closed before terminal event,客户端收到 1011。它是确定性的报文编码问题:同一请求换到别的账号照样复现,换号解决不了。这不是“WS 不支持中文”:任务指令和上下文里的中文原样发送都正常,问题只在这一段元数据字符串。ctx_pool 路径平时不暴露,是因为
openai_ws_forwarder_ingress.go会先用握手头里的x-codex-turn-metadata覆盖每帧元数据,而握手元数据不含workspaces;passthrough 保留每轮元数据,所以暴露。效果
真实上游:修复前的单变量对照
抓取真实 Codex 客户端的一个完整失败请求帧(107713 字节,含全部工具声明),对真实上游逐项重放,每次只改一个变量:
workspaces,中文保持\uXXXX转义workspaces保持客户端转义形式,再加installation_id触发网关身份改写共 23 次重放,11 次完成、12 次按预期复现关闭(这是诊断样本分布,不是业务失败率)。单独去掉工具、缩短上下文、去掉 reasoning 配置都不能消除故障。
真实上游:修复后的验收
部署本修复后直接访问网关,不经任何转发器或元数据变换:
response.completed,产生 exec 工具调用该修复自 2026-09-13 起在我们的自建实例上运行。
单测:改前改后对比
新增用例
TestCodexTurnMetadataRewritePreservesHeaderSafeJSON(4 组输入 × 6 个改写入口 = 24 个子用例,断言输出每个字节都在 0x20–0x7e,且解码后除被改写字段外与输入逐字段相等):0xe4等原始 UTF-8 字节或0x7f改动
marshalCodexTurnMetadata(openai_codex_turn_metadata.go):先json.Marshal,再把 ≥ 0x7f 的字符写成\uXXXX,BMP 之外的字符写成 UTF-16 代理对;不含此类字符时直接返回json.Marshal的结果,不多分配。处理的是所有非 ASCII 字符,不按“是否中文”判断。json.Marshal换成该函数,其余逻辑不动。只改这一段元数据的序列化方式;请求正文、工具定义、路由、换号与冷却行为都不变,解码后的字段名、取值、字面反斜杠与原有转义语义保持不变。没有采用“用握手元数据覆盖每轮元数据”的做法,那样会丢掉每轮新增的工作区信息。
测试
./internal/service全量-tags=unit:除TestOllamaProbeCallback_StaleLongDoesNotOverrideNewShort外全部通过。该用例是上游既有用例,本机 Windows 上因时钟步进固定失败,本 PR 未触及相关限流代码。golangci-lint run ./internal/service/:0 issues。代价与保留项