环境
QwenPaw:2.2.0b3(pip 安装,Linux x86_64)
codex-cli:0.144.4(codex_cli_bin,app-server 模式)
接入方式:第三方智能体,agent.json 中 backend: "codex"
模型提供商:某厂商 Responses 协议网关(下称"该网关";应公开提交要求对提供商做匿名化,细节可私信提供),公开模型 glm-5-3-flash
现象
console 频道与该 codex 智能体对话,每轮都得到"助手返回了空响应。",无任何错误日志。日志签名:
Usage for session ...: {'provider_id': '', 'model_name': '', 'prompt_tokens': 2, 'completion_tokens': 0, 'total_tokens': 2, 'estimated': True, ...}
console stream done: event_count=3 has_response=True
而以下两条路径均正常,说明问题在 QwenPaw 适配层而非上游:
直接 codex exec(同目录、同 provider)回复正常;
用与 QwenPaw 相同的 JSON-RPC 序列直接驱动 app-server(initialize → thread/start → turn/start),turn 正常完成并返回完整 agentMessage。
根因分析
三重证据指向同一结论:当后端不流式下发 item/agentMessage/delta 时,harnesses/codex/adapter.py 的 _convert_notification 会把最终文本整体丢弃。
实拍事件流(0.144.4 + 该网关) :全程没有任何 item/agentMessage/delta 事件;完整文本直接出现在 item/started / item/completed 的 item.text 中。turn/completed 为瘦负载:{"items": [], "itemsView": "notLoaded"}。codex 自身日志同时刷出 OutputTextDelta without active item / ReasoningSummaryDelta without active item(该网关的流式分片顺序触发了 codex 0.144 的流式状态机问题,文本最终以非流式 item 兜底送达)。
适配层代码 :_convert_notification 中 item/completed 分支只处理 _TOOL_ITEM_TYPES(commandExecution 等);agentMessage 类型的 item/completed 掉出分支返回 None。文本进入事件流的唯一 通道是 item/agentMessage/delta → TEXT_DELTA。零 delta ⇒ 零文本 ⇒ "空响应"。usage 全 0/estimated 同因(thread/tokenUsage/updated 未被消费)。
同类问题已有官方确认 :姊妹项目 sipeed/picoclaw 试了下微信功能-不完善 #2674 ("Codex OAuth: empty assistant response when ChatGPT backend streams items via response.output_item.done",签名同样是 token 有计数、content_chars=0),修复方案 PR add google tag to track copaw website traffic #2581 即"把流式 item 事件累积的文本嫁接回终结事件"。
复现步骤
将某 Responses 协议网关配置为 codex 的 provider(触发该问题的网关行为特征:不下发 item/agentMessage/delta、终结事件为瘦负载);
创建 backend: "codex" 的第三方智能体,模型选 glm-5-3-flash;
console 发送任意消息 → 每轮"助手返回了空响应。",usage 全 0。
(本地验过的旁证:deepseek 等正常流式 Responses 后端因 delta 正常下发,不触发此问题——这也解释了"换 provider 后突然全空"的现象。)
修复建议(已在本地验证)
在 _convert_notification 中按 picoclaw #2581 的模式补上"整段文本嫁接 + 按已流式量去重",不影响正常流式后端:
__init__ 增加实例状态 self._delta_chars: dict[str, int](itemId → 已流式字符数);
item/agentMessage/delta 分支累加 self._delta_chars[itemId];
item/completed 且 item_type == "agentMessage" 时,设 delivered = self._delta_chars.pop(itemId, 0),若 len(item.text) > delivered 则补发 TEXT_DELTA(text=item.text[delivered:]),否则返回 None(正常流式后端 delta 已发全量,天然去重不重复);
turn/completed 分支 self._delta_chars.clear();
由于需要访问实例状态,_convert_notification 从 @staticmethod 改为实例方法(现有调用点均为 self._convert_notification(...),无破坏)。
本地单元测试 5/5 通过:纯 completed 嫁接(该网关场景)、delta 全量去重、部分 delta 差额嫁接、turn 结束状态清理、工具事件路径不受影响。
期望行为
后端以非流式 item 形式(或瘦负载 turn/completed)返回内容时,codex 第三方智能体仍能正常产出回复,并正确记录 usage。
备注
若维护者已在新版修复,请告知对应版本号,以便受影响用户升级;
本地补丁可提供完整 diff 备选 PR,欢迎评论索引。
环境
codex_cli_bin,app-server 模式)agent.json中backend: "codex"现象
console 频道与该 codex 智能体对话,每轮都得到"助手返回了空响应。",无任何错误日志。日志签名:
而以下两条路径均正常,说明问题在 QwenPaw 适配层而非上游:
codex exec(同目录、同 provider)回复正常;agentMessage。根因分析
三重证据指向同一结论:当后端不流式下发
item/agentMessage/delta时,harnesses/codex/adapter.py的_convert_notification会把最终文本整体丢弃。item/agentMessage/delta事件;完整文本直接出现在item/started/item/completed的item.text中。turn/completed为瘦负载:{"items": [], "itemsView": "notLoaded"}。codex 自身日志同时刷出OutputTextDelta without active item/ReasoningSummaryDelta without active item(该网关的流式分片顺序触发了 codex 0.144 的流式状态机问题,文本最终以非流式 item 兜底送达)。_convert_notification中item/completed分支只处理_TOOL_ITEM_TYPES(commandExecution 等);agentMessage类型的 item/completed 掉出分支返回None。文本进入事件流的唯一通道是item/agentMessage/delta→TEXT_DELTA。零 delta ⇒ 零文本 ⇒ "空响应"。usage 全 0/estimated 同因(thread/tokenUsage/updated未被消费)。复现步骤
item/agentMessage/delta、终结事件为瘦负载);backend: "codex"的第三方智能体,模型选 glm-5-3-flash;(本地验过的旁证:deepseek 等正常流式 Responses 后端因 delta 正常下发,不触发此问题——这也解释了"换 provider 后突然全空"的现象。)
修复建议(已在本地验证)
在
_convert_notification中按 picoclaw #2581 的模式补上"整段文本嫁接 + 按已流式量去重",不影响正常流式后端:__init__增加实例状态self._delta_chars: dict[str, int](itemId → 已流式字符数);item/agentMessage/delta分支累加self._delta_chars[itemId];item/completed且item_type == "agentMessage"时,设delivered = self._delta_chars.pop(itemId, 0),若len(item.text) > delivered则补发TEXT_DELTA(text=item.text[delivered:]),否则返回None(正常流式后端 delta 已发全量,天然去重不重复);turn/completed分支self._delta_chars.clear();_convert_notification从@staticmethod改为实例方法(现有调用点均为self._convert_notification(...),无破坏)。本地单元测试 5/5 通过:纯 completed 嫁接(该网关场景)、delta 全量去重、部分 delta 差额嫁接、turn 结束状态清理、工具事件路径不受影响。
期望行为
后端以非流式 item 形式(或瘦负载 turn/completed)返回内容时,codex 第三方智能体仍能正常产出回复,并正确记录 usage。
备注