fix(codex): 改写超过 64 字符的 Responses 历史 item id,切引擎后不再撞 string_above_max_length(#4227) - #4783
Conversation
…_length(makecindy#4227) 同一会话从 Gateway 模型切到 OpenAI Responses 后,历史里 ws_<uuid>_call-<uuid>-<n> / tco_… 这类 84–85 字符 item id 被原样回放,上游按 64 上限 400,此后每轮、重试与 compact 都被同一条历史拦住。 - model-compat 新增 shortenResponsesItemId / shortenOversizedResponsesItemIdsFromBody: 只改顶层 input 中 > 64 的 id,保留类型前缀 + 稳定 sha256 截断;call_id 与正文不动。 - 新增 recovery rule responses_item_id_length,匹配两种实测原文;codex-proxy-host 注册并 加独立 activeStrip,恢复一次后该 thread 发送前预改写;WS 内命中由 armCodexHttpRecovery 识别后回落 HTTP。 Closes makecindy#4227 Signed-off-by: ficowang <fico@xd.com>
|
MagicLizi
left a comment
There was a problem hiding this comment.
PR #4783 审查报告
PR: #4783
Head: 0397498fcf9798f7b75bff27263279979382fe81
Base: 1dfe609d852dbc19b3b4e43de38cf965fc56ffb5 (main)
Author: FicoHub
Tier: standard
Title: fix(codex): 改写超过 64 字符的 Responses 历史 item id,切引擎后不再撞 string_above_max_length(#4227)
Findings
无 P0/P1。
(P2 不报告:call_id 超长未覆盖已在 Description 明确排除且未见实测;host 测试 mock 的 matches 正则与生产略有差异,生产正则由 transform.test.ts 覆盖。)
Signoff semantic fallback(arch)
脚本触发 arch: core-paths(205 行 ≥ 150),作者非维护者、无维护者 Approve。按 product/arch/rules 语义 fallback 逐文件定性后不 hold,执行 auto.fallback.action=review。
结构问句:是否改变模块边界、依赖方向、数据契约或跨包 API 形状?
| 文件 | 结论 |
|---|---|
packages/model-compat/src/cindy/proxy/transform.ts |
在既有 recovery-rule / body-strip 结构内新增超长 id 改写,不改模块边界或依赖方向 |
packages/anthropic-compat-proxy/src/transform.ts / index.ts |
既有 re-export 合同的加法导出,不改现有符号语义 |
packages/anthropic-compat-proxy/src/transform.test.ts |
测试 |
apps/desktop/src/main/maker-host/codex-proxy-host.ts |
按 #4738 同一模式注册 recovery + activeStrip |
apps/desktop/src/main/maker-host/thread-strip-controllers.ts |
新增独立 controller 实例,避免与其它 strip 交叉 |
apps/desktop/src/main/maker-host/__tests__/codexProxyHost.test.ts |
测试 |
这是同结构内的 bugfix / 已有兼容恢复能力补充,不是较大架构调整。未创建 discussion issue,未挂 awaiting-discussion(当时也没有该标签)。
Rule coverage
AGENTS.md/docs/dev-rules/development-workflow.md/ PR 模板:fix 模板三段齐全;描述与 diff 一致;未夹带无关重构或依赖。docs/dev-rules/architecture-invariants.md:package 未反向依赖 Desktop Main/Renderer;main 无动态import();不改布局树。docs/dev-rules/desktop-development.md:改动限于 Codex 代理兼容路径,已跑定向测试与相关 typecheck。docs/dev-rules/electron-security-and-process-boundaries.md:不涉及 Renderer 特权、preload、IPC、CSP、窗口生命周期。docs/dev-rules/engineering-conventions.md:无裸console.log;main 业务路径有宿主测试;核心改写/匹配有单测;无 UI/i18n。
审查固定问题:
- 单一目的(#4227 超长 Responses item id),Description 与 diff/测试/风险一致。
- 共享符号:实现落在
model-compat,经anthropic-compat-proxyre-export;Desktop Codex host 是当前唯一注册前缀同类规则的调用方,已同步注册长度规则与独立 controller。 - 只在上游明确返回
input[n].id长度错误后改写并重试;失败则原 400 透出。不写会话历史。 - 无新依赖、无凭证、无安全边界放松。
createHash('sha256')仅用于稳定短 id。 - 测试覆盖改写范围、幂等、规则匹配/排除、host 注册与 WS 武装。
- 无 Core 能力重复实现。
- 无 UI。
- 无 Mobile 冷更。
- 0 P0/P1。
PR 描述吻合度:最终 diff 均在声称范围内。无讨论 issue 需落实。
Verification
日志:verify-pr-4783.log(命令自身退出码,未使用会吞码的管道)。
pnpm --filter @cindy/anthropic-compat-proxy test -- src/transform.test.ts→ EXIT=0(该包 18 files / 503 tests passed)pnpm --filter desktop exec vitest run src/main/maker-host/__tests__/codexProxyHost.test.ts→ EXIT=0(1 file / 237 tests passed)pnpm --filter @cindy/model-compat run typecheck→ EXIT=0pnpm --filter @cindy/anthropic-compat-proxy run typecheck→ EXIT=0pnpm --filter desktop run typecheck→ EXIT=0- GitHub CI:
client-cisuccess,pr-design-basissuccess - 未执行:真实 grok → OpenAI Responses 端到端(作者已声明无 Gateway 凭证);
/responses/compact未单独复现(经同一代理,命中相同错误文本会生效) - 未跑
security-scan.mjs:standard 档,非 heavy
Overall
pass。没有 P0/P1。
botSettle 已超时放行:copilot-pull-request-reviewer / chatgpt-codex-connector 仍未对当前 head 表态,意见可能在合并后到达。
这次改了什么
修复 #4227:同一会话从 Gateway 模型(如 grok)切到 OpenAI Responses 模型后,历史里
ws_<uuid>_call-<uuid>-<n>/tco_…这类 84–85 字符的 item id 被原样回放,上游按 64 字符上限拒绝(string_above_max_length),此后每轮、重试与 compact 都被同一条历史拦住。packages/model-compat/src/cindy/proxy/transform.tsshortenResponsesItemId():超过 64 字符的 id 改写为<类型前缀>_<sha256 hex 截断>、总长 ≤ 64;前缀取原 id 第一个下划线前的字母段(ws_/tco_/fc_…),上游据此判定 item 类型;同一原 id 每轮映射一致,不同原 id 不碰撞。shortenOversizedResponsesItemIdsFromBody():只扫顶层body.input,只改id;call_id、正文、metadata、嵌套业务 payload 不动;没有可改的返回 null(字节透传)。createResponsesItemIdLengthRecoveryRule()(idresponses_item_id_length):匹配Invalid 'input[N].id': string too long … maximum length 64与[string_above_max_length] … input[N].id两种实测原文;call_id的长度错误、前缀错误、密文错误不接管。packages/anthropic-compat-proxy:re-export。apps/desktop/src/main/maker-hostCODEX_BODY_RECOVERY_RULES(HTTP 路径命中即改写重试一次;WS 内首次命中由既有armCodexHttpRecovery识别本规则,下一次 upgrade 回 426 落回 HTTP——覆盖「WS 返回长度错误不触发 HTTP 回退」)。responsesItemIdLengthStripController与 activeStrip(紧随 同一会话从 Gemini 切换到 GPT 后,历史消息 ID 校验失败,无法继续对话 #4738 的 activeStrip 之后),恢复一次后该 thread 后续发送前预改写,避免每轮先撞 400。变更类型
fix缺陷修复范围
call_id超长(未见实测报错);/responses/compact路径的独立处理(compact 请求经同一代理与规则,若命中同一错误文本同样生效,但未单独验证)。同一会话从 Gemini 切换到 GPT 后,历史消息 ID 校验失败,无法继续对话 #4738 的前缀问题已由 fix(codex): 跨供应商切模型后剔除 Responses 拒绝的历史 message/reasoning id(#4738) #4752 处理。UI 变化
怎么验证的
armCodexHttpRecovery对长度错误返回responses_item_id_length并拒绝该 thread 的下一次 upgrade)在修改前的codex-proxy-host.ts上失败。packages/anthropic-compat-proxytransform.test.ts:120/120,新增id,call_id、function_call_output.output内嵌同名 id、metadata、正文不动;改写后再跑返回 null(幂等);call_id长度错误、前缀错误、密文错误。apps/desktopcodexProxyHost.test.ts:237/237,recoveryRules含responses_item_id_length、transform 链长度 25、WS 武装用例。pnpm --filter @cindy/model-compat / @cindy/anthropic-compat-proxy / desktop run typecheck通过;根pnpm test:unit:related通过(desktop、mobile、anthropic-compat-proxy、anthropic-responses-bridge、lizi-mcps、maker-core、model-compat、model-providers、orca-workflow、responses-chat-bridge 十个 unit 套件全部 PASS)。/responses/compact路径未单独复现。风险
影响与回滚
input[N].id长度错误后生效;正常请求字节透传。改写只动id,工具配对靠call_id不受影响。call_id与非顶层字段不动;规则不接管其他字段的长度错误。