Skip to content

fix(codex): 改写超过 64 字符的 Responses 历史 item id,切引擎后不再撞 string_above_max_length(#4227) - #4783

Merged
MagicLizi merged 1 commit into
makecindy:mainfrom
FicoHub:fix/4227-responses-item-id-length
Sep 20, 2026
Merged

MagicLizi merged 1 commit into
makecindy:mainfrom
FicoHub:fix/4227-responses-item-id-length

Conversation

@FicoHub

@FicoHub FicoHub commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

这次改了什么

修复 #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.ts
    • shortenResponsesItemId():超过 64 字符的 id 改写为 <类型前缀>_<sha256 hex 截断>、总长 ≤ 64;前缀取原 id 第一个下划线前的字母段(ws_ / tco_ / fc_ …),上游据此判定 item 类型;同一原 id 每轮映射一致,不同原 id 不碰撞。
    • shortenOversizedResponsesItemIdsFromBody():只扫顶层 body.input,只改 id;call_id、正文、metadata、嵌套业务 payload 不动;没有可改的返回 null(字节透传)。
    • createResponsesItemIdLengthRecoveryRule()(id responses_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-host
    • 注册到 CODEX_BODY_RECOVERY_RULES(HTTP 路径命中即改写重试一次;WS 内首次命中由既有 armCodexHttpRecovery 识别本规则,下一次 upgrade 回 426 落回 HTTP——覆盖「WS 返回长度错误不触发 HTTP 回退」)。
    • 新增独立 responsesItemIdLengthStripController 与 activeStrip(紧随 同一会话从 Gemini 切换到 GPT 后,历史消息 ID 校验失败,无法继续对话 #4738 的 activeStrip 之后),恢复一次后该 thread 后续发送前预改写,避免每轮先撞 400。

变更类型

  • fix 缺陷修复

范围

UI 变化

  • 无 UI 变化。
  • 引用的设计规范:不适用。

怎么验证的

  • 反证:宿主新增用例(armCodexHttpRecovery 对长度错误返回 responses_item_id_length 并拒绝该 thread 的下一次 upgrade)在修改前的 codex-proxy-host.ts 上失败。
  • packages/anthropic-compat-proxy transform.test.ts:120/120,新增
    • 改写只作用于 > 64 的 id,保留前缀、结果 64 字符且为 hex;同一原 id 稳定、不同原 id 不同;无字母前缀的只用 hash;未超长原样;
    • 只改顶层 input 的 id,call_id、function_call_output.output 内嵌同名 id、metadata、正文不动;改写后再跑返回 null(幂等);
    • 无超长 / 非 JSON / 非 Responses 体返回 null;
    • 规则匹配两种实测原文与 JSON 包裹形态,不匹配 call_id 长度错误、前缀错误、密文错误。
  • apps/desktop codexProxyHost.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)。
  • 未验证:真实 grok → gpt-5.6-luna 切换端到端(本机无对应 Gateway 凭证);/responses/compact 路径未单独复现。

风险

影响与回滚

  • 影响面:只在上游明确返回 input[N].id 长度错误后生效;正常请求字节透传。改写只动 id,工具配对靠 call_id 不受影响。
  • 保守方向:前缀保留、hash 稳定;call_id 与非顶层字段不动;规则不接管其他字段的长度错误。
  • 回滚:revert 本 PR 即恢复原行为(超长 id 400 继续裸抛)。

…_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>
@greptile-apps

greptile-apps Bot commented Sep 20, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

未发现由本 PR 引入的具体功能、安全或仓库规则问题,当前变更看起来可以安全合并。

Summary

本 PR 为跨模型切换后的 OpenAI Responses 历史项增加超长 ID 恢复机制,并将其接入 HTTP 重试、WebSocket 降级和 thread 级主动预处理。

  • 对顶层 input[].id 中超过 64 字符的值生成保留类型前缀的稳定 SHA-256 短 ID。
  • 新增精确匹配 input[N].id 长度错误的 recovery rule,并通过独立 controller 避免与其他清洗条件交叉污染。
  • 在 desktop Codex 代理的共享、控制面及自定义上下文链路中统一注册规则和主动 transform。
  • 补充导出及单元测试,覆盖稳定性、幂等性、字段作用域、错误匹配和 WebSocket 回落接线。
Diagram
sequenceDiagram
    participant C as Codex 客户端
    participant P as 本地兼容代理
    participant U as Responses 上游
    C->>P: 发送含超长 input[].id 的请求
    P->>U: 首次透传
    U-->>P: 400 input[N].id 超过 64 字符
    P->>P: 保留类型前缀并生成稳定短 ID
    P->>P: 标记该 thread 的主动改写状态
    P->>U: 使用改写后的 body 重试
    U-->>C: 正常响应
    Note over C,P: 后续同 thread、同 model 请求会在发送前预改写
Loading

Reviews (1) · Last reviewed commit: "fix(codex): 改写超过 64 字符的 Responses 历史 ite..."

@MagicLizi MagicLizi added the touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) label Sep 20, 2026

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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。

审查固定问题:

  1. 单一目的(#4227 超长 Responses item id),Description 与 diff/测试/风险一致。
  2. 共享符号:实现落在 model-compat,经 anthropic-compat-proxy re-export;Desktop Codex host 是当前唯一注册前缀同类规则的调用方,已同步注册长度规则与独立 controller。
  3. 只在上游明确返回 input[n].id 长度错误后改写并重试;失败则原 400 透出。不写会话历史。
  4. 无新依赖、无凭证、无安全边界放松。createHash('sha256') 仅用于稳定短 id。
  5. 测试覆盖改写范围、幂等、规则匹配/排除、host 注册与 WS 武装。
  6. 无 Core 能力重复实现。
  7. 无 UI。
  8. 无 Mobile 冷更。
  9. 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=0
  • pnpm --filter @cindy/anthropic-compat-proxy run typecheck → EXIT=0
  • pnpm --filter desktop run typecheck → EXIT=0
  • GitHub CI:client-ci success,pr-design-basis success
  • 未执行:真实 grok → OpenAI Responses 端到端(作者已声明无 Gateway 凭证);/responses/compact 未单独复现(经同一代理,命中相同错误文本会生效)
  • 未跑 security-scan.mjs:standard 档,非 heavy

Overall

pass。没有 P0/P1。

botSettle 已超时放行:copilot-pull-request-reviewer / chatgpt-codex-connector 仍未对当前 head 表态,意见可能在合并后到达。

@MagicLizi
MagicLizi merged commit 773631f into makecindy:main Sep 20, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

切引擎到 Codex/GPT-5.5 时,历史 input[].id 超过 64 字符导致 400 string_above_max_length

2 participants