现象
同一 Codex 任务内先跑 DeepSeek,再切回 GPT 模型,下一次 GPT 请求立即 400:
Invalid 'input[112].content': array too long. Expected an array with maximum length 0, but got an array with length 1 instead.
错误 item 已落盘在任务历史,因此每轮重放都会再失败一次;切回 DeepSeek 可继续,再切 GPT 又炸。与 #17 描述完全一致,但在 v1.2.1 上仍可复现。
环境
- DSCodex:v1.2.1(
6e4b290)
- 切换方向:
gpt-5.6-terra(官方 GPT)→ deepseek/deepseek-flash → 切回 gpt-5.6-terra
- 同一 thread,Codex Desktop,macOS
- 路由日志对应行:
chatgpt /v1/responses -> 400 1827ms
根因:v1.2.0 的判据依赖「第三方不返回 encrypted_content」,该假设已不成立
src/proxy.mjs:150-156(buildChatGptBody)的清洗判据是:
if (item?.type === "reasoning"
&& !item.encrypted_content // ← 要求加密字段为空
&& Array.isArray(item.content)
&& item.content.some((part) => part?.type === "reasoning_text")) {
changed = true;
return [];
}
test/proxy.test.mjs:705-725 的断言印证了这一设计意图:带 encrypted_content 的 reasoning 被当作官方 native 保留,encrypted_content: null 的才当作 foreign 丢弃。
但 DeepSeek 现在返回的 reasoning item 带有非 null 的 encrypted_content。实测两方形状对比(同一任务历史内):
| 来源 |
content |
encrypted_content |
| DeepSeek 产 |
[{"type":"reasoning_text","text":"<omitted>"}] |
"7567b785-…-0" —— 38 字符 UUID 占位串(非 base64、非加密块形状) |
| 官方 GPT 产 |
字段不存在 |
"gAAAAAB…" —— 1594 字符真 base64 |
于是:
!item.encrypted_content 对 38 字符占位串求值为 false → 判据不命中;
- DeepSeek 的 reasoning 被误判为 native,原样转发给
chatgpt.com;
- 官方 schema 要求 reasoning 的
content 为空数组(官方自己的 reasoning 根本不带 content)→ array_above_max_length 400。
#17 报告当时的形状是 encrypted_content: null,v1.2.0 的修复与该形状严格对应。DeepSeek 侧补上该字段(假值)后,修复即失效——这属于外部行为变化引发的回归,不是原修复写错。
复现步骤
- 启动 DSCodex v1.2.1,路由正常(
node src/cli.mjs doctor 六项 ok)。
- 新建任务,模型选
gpt-5.6-terra,完成至少一轮响应,使历史中产生官方 reasoning item。
- 同一任务内切到
deepseek/deepseek-flash,完成至少一轮响应(历史中产生带 reasoning_text 的 reasoning item)。
- 同一任务内切回
gpt-5.6-terra,发送任意消息。
- GPT 请求返回 HTTP 400,
param 为 input[N].content。
- 切回 DeepSeek 恢复正常;再切 GPT 再次 400。
期望行为
同一任务在 DeepSeek 与 GPT 之间可安全切换,与 #17 的期望一致。
建议修复方向
把判据从「encrypted_content 为空」放宽为「reasoning 且 content 非空」:
if (item?.type === "reasoning"
&& Array.isArray(item.content)
&& item.content.length > 0) {
changed = true;
return [];
}
依据:官方 reasoning item 不含 content 字段(本次实测:历史中全部官方 reasoning 的 content 均为「字段缺失」,只带 summary + 真 base64 encrypted_content)。因此「reasoning 且 content 非空」只可能来自第三方,按此判据剥离不会误伤 native reasoning,也不再依赖 encrypted_content 的具体取值。
其余约束按 #17 原议保持不变:只在实际发生迁移时解码/重建请求体,不含此类 item 的普通 GPT 流量仍字节透传,不修改用户 rollout JSONL。
备注
- 本地仅做了诊断与形状核实,未改动任何上游文件;如果维护者需要,我可以补一个带回归测试的 PR(新增一条
encrypted_content 为非空占位串的 foreign reasoning 用例,断言它必须被剥离)。
- 本报告未包含任何对话内容、账号标识或 OAuth 信息。
现象
同一 Codex 任务内先跑 DeepSeek,再切回 GPT 模型,下一次 GPT 请求立即 400:
错误 item 已落盘在任务历史,因此每轮重放都会再失败一次;切回 DeepSeek 可继续,再切 GPT 又炸。与 #17 描述完全一致,但在 v1.2.1 上仍可复现。
环境
6e4b290)gpt-5.6-terra(官方 GPT)→deepseek/deepseek-flash→ 切回gpt-5.6-terrachatgpt /v1/responses -> 400 1827ms根因:v1.2.0 的判据依赖「第三方不返回
encrypted_content」,该假设已不成立src/proxy.mjs:150-156(buildChatGptBody)的清洗判据是:test/proxy.test.mjs:705-725的断言印证了这一设计意图:带encrypted_content的 reasoning 被当作官方 native 保留,encrypted_content: null的才当作 foreign 丢弃。但 DeepSeek 现在返回的 reasoning item 带有非 null 的
encrypted_content。实测两方形状对比(同一任务历史内):contentencrypted_content[{"type":"reasoning_text","text":"<omitted>"}]"7567b785-…-0"—— 38 字符 UUID 占位串(非 base64、非加密块形状)"gAAAAAB…"—— 1594 字符真 base64于是:
!item.encrypted_content对 38 字符占位串求值为false→ 判据不命中;chatgpt.com;content为空数组(官方自己的 reasoning 根本不带content)→array_above_max_length400。#17 报告当时的形状是
encrypted_content: null,v1.2.0 的修复与该形状严格对应。DeepSeek 侧补上该字段(假值)后,修复即失效——这属于外部行为变化引发的回归,不是原修复写错。复现步骤
node src/cli.mjs doctor六项 ok)。gpt-5.6-terra,完成至少一轮响应,使历史中产生官方 reasoning item。deepseek/deepseek-flash,完成至少一轮响应(历史中产生带reasoning_text的 reasoning item)。gpt-5.6-terra,发送任意消息。param为input[N].content。期望行为
同一任务在 DeepSeek 与 GPT 之间可安全切换,与 #17 的期望一致。
建议修复方向
把判据从「
encrypted_content为空」放宽为「reasoning 且content非空」:依据:官方 reasoning item 不含
content字段(本次实测:历史中全部官方 reasoning 的content均为「字段缺失」,只带summary+ 真 base64encrypted_content)。因此「reasoning 且content非空」只可能来自第三方,按此判据剥离不会误伤 native reasoning,也不再依赖encrypted_content的具体取值。其余约束按 #17 原议保持不变:只在实际发生迁移时解码/重建请求体,不含此类 item 的普通 GPT 流量仍字节透传,不修改用户 rollout JSONL。
备注
encrypted_content为非空占位串的 foreign reasoning 用例,断言它必须被剥离)。