Skip to content

fix: 修复 Codex 多 Agent 消息私有字段导致 Responses 上游拒绝请求 - #2609

Merged
jlcodes99 merged 1 commit into
jlcodes99:mainfrom
lwwtl:fix/codex-agent-message-metadata
Sep 27, 2026
Merged

jlcodes99 merged 1 commit into
jlcodes99:mainfrom
lwwtl:fix/codex-agent-message-metadata

Conversation

@lwwtl

@lwwtl lwwtl commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

问题

Codex 多 Agent 会话经 Cockpit Provider Gateway 发往严格校验请求结构的 Responses 上游时,会遇到 400 unknown_parameter: Unknown parameter: 'input[n].author'。

rewriteCodexAgentMessageInput 已将 agent_message 改写为普通 message,但仍保留 author、recipient、内部消息 ID 和 internal_chat_message_metadata_passthrough。这些内部字段不应直接放在转换后的普通消息顶层。子 Agent 结果进入主会话历史后,后续请求也可能重复失败。

修复

  • 将上述 Agent 元数据序列化为额外的 input_text 内容块,保留发送者、接收者和内部关联信息。
  • 从转换后的消息顶层移除这些内部字段。
  • 保持原始正文、无关工具历史及原有转换启用条件不变;重复转换不会重复追加元数据。
  • 更新现有转换和网关测试,并添加字符串/对象形式 author、正文保留、工具历史保留和幂等性回归测试。

相关但不同的问题:#2382 处理 Responses → Chat Completions 分支的子代理任务丢失;本 PR 处理 Agent 消息归一化后,严格 Responses 上游拒绝私有字段的问题。

验证

在最新 main 基础上执行并通过:

cd sidecars/cockpit-cliproxy
go test ./... -count=1
cd third_party/CLIProxyAPI
go test ./internal/client/codex/optimize-multi-agent-v2 -count=1

此前在本地独立测试端口,包含上述私有字段的合成请求经修复版代理访问真实上游,返回 HTTP 200 并完成回复。该验证不等同于完整 Codex 多 Agent UI 端到端验收。

本 PR 仅包含源码和测试,不包含本地配置、凭据、真实项目内容或编译产物。

@lishimin8507-droid

Copy link
Copy Markdown

本机独立复现 + 定位到可稳定重现的触发条件

环境:Cockpit Tools 1.3.60,上游 https://sub-coco.org(Responses 协议),Codex 桌面版(codex-cli 0.158.0-alpha.2.1)。

我在本地独立复现了这个 400,并定位到一条关键触发条件,供维护者参考。

一、只有带 Codex 客户端 User-Agent 的请求才会进入这个改写路径

cockpit-cliproxy.exe 的 optimize-multi-agent-v2 包中包含 IsCodexClientUserAgent、isCodexMultiAgentClient、codexMultiAgentV2ClientEnabled 等判定函数。以此为线索做单变量对照:

请求 payload User-Agent 结果
agent_message(含 encrypted_content) codex-tui/0.153.4 (…) unknown_parameter: Unknown parameter: 'input[1].author'
完全相同的 payload curl/8.x 200 completed,无任何错误
普通 message(不含 author) codex-tui/0.153.4 (…) 200 completed,无任何错误

也就是说:用非 Codex UA 做探测会完全绕过这个缺陷。 我最初用 curl 直接探测了 8 次(含 stream / encrypted_content / metadata 等各种组合)全部通过,直到补上 Codex 客户端 UA 才稳定重现。这大概也是该问题不易被第三方复现的原因——如果 PR 的回归测试没有覆盖"Codex 客户端 UA"这个前置条件,可能测不到真实路径。

二、最小复现请求

curl -N http://127.0.0.1:<gateway-port>/v1/responses \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -H 'User-Agent: codex-tui/0.153.4 (Mac OS 26.5.0; arm64) iTerm.app/3.6.10 (codex-tui; 0.153.4)' \
  -H 'Authorization: Bearer <local-gateway-key>' \
  -d '{
    "model": "gpt-6-sol",
    "stream": true,
    "input": [
      {"type":"message","role":"user",
       "content":[{"type":"input_text","text":"ping"}]},
      {"type":"agent_message",
       "id":"amsg_01a0e08a-e4cb-7a30-8bdf-93a98fa303c0",
       "author":"/root",
       "recipient":"/root/repo_scan",
       "content":[
         {"type":"input_text","text":"Message Type: NEW_TASK\nTask name: /root/repo_scan\nSender: /root\nPayload:\n"},
         {"type":"encrypted_content","encrypted_content":"ping"}
       ],
       "internal_chat_message_metadata_passthrough":{"turn_id":"01a0e086-56fc-7242-9669-b8517e5098f4","create_time":1790473594.059782}}
    ]}'

HTTP 状态码是 200,错误发生在 SSE 流内部:

{"type":"response.failed","response":{"status":"failed","error":{
  "code":"unknown_parameter",
  "param":"input[1].author",
  "message":"Unknown parameter: 'input[1].author'.",
  "type":"invalid_request_error"}}}

注意 input[1] 与请求中 agent_message 的下标严格一致 —— 与"就地转换为普通 message、但顶层字段残留"的判断吻合。

三、一个容易误判的排查陷阱

这类失败在 sidecar 日志里 HTTP 状态码是 200,只看服务端日志会得出"请求全部成功"的错误结论。本机同日的两侧对照:

观测点 结果
sidecar 侧 request_completed 56 次中 52 次 HTTP 200、1×502、3×400
客户端 rollout 的 task_complete 7 次全部失败,错误均为 Unknown parameter: 'input[N].author'

即 93% 的 200 里藏着流内失败。建议以客户端侧为准。

四、补充验证:该上游其实接受原生 agent_message

在同一上游、同一模型下把改写关掉(codex.optimize-multi-agent-v2: false)后:

  • agent_message + Codex UA → completed,无错误
  • 负对照:普通 message 携带 author → 仍然 unknown_parameter: 'input[1].author'

负对照说明上游的严格校验没有变化,修复之所以生效,正是因为不再产出 message + author 这种畸形形状 —— 与本 PR 的方向完全一致。这可以视为本 PR 修复有效性的一个独立佐证。

五、一个想请教作者的问题

既然上游能原生处理 agent_message(甚至会对 encrypted_content 做服务端 hydration),那么这次改写是先判定上游能力再启用,还是对判定为 Codex 客户端的请求无条件启用?如果是后者,是否可以在检测到上游原生支持时直接跳过改写,从根上避免这类"转换后字段残留"的复发?

感谢修复。

@jlcodes99

Copy link
Copy Markdown
Owner

已合入 main,感谢 @lwwtl 提供多 Agent 消息兼容修复!

已在当前主线完成必要的冲突适配,并通过前端测试、类型检查、翻译检查和相关 Go 回归/sidecar 构建。本地中英文 Unreleased 更新日志已补充本 PR 及你的贡献署名,后续随宿主版本发布;本次尚未发版。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants