背景
opencode-a2a-serve 已对 message:stream 的流式输出契约做了升级(见上游 issue/PR):
本仓库(a2a-client-hub)需要完成消费端适配。
本需求明确:不考虑旧数据兼容。按新契约实施即可。
目标
将当前“把所有文本当单一流拼接”的消费方式,迁移为“按 channel 分流 + 正确处理 append + final snapshot 语义”的新实现,避免:
- 用户回显误展示
- reasoning / tool_call / final_answer 混杂
- 最终答复重复展示
上游新契约(消费端必须遵守)
1) 流事件通道化
TaskArtifactUpdateEvent.artifact.metadata.opencode.channel 取值:
reasoning
tool_call
final_answer
2) 多 artifact_id 并行
同一轮任务会出现多个 artifact:
final_answer:{task_id}:stream
reasoning:{task_id}:stream:reasoning
tool_call:{task_id}:stream:tool_call
3) append 语义必须按“每个 artifact/channel”独立处理
append=true:追加到该 artifact 当前缓冲
append=false:重置该 artifact 当前缓冲为本次文本
禁止跨 channel/跨 artifact 拼接。
4) final snapshot 语义
source=final_snapshot(位于 artifact.metadata.opencode.source)表示收尾补发快照:
- 仅在流内未形成等价 final answer 时才会出现
- 可能与
append=false、last_chunk=true 同时出现
消费端应将其视为 final_answer 通道的一次“覆盖更新”,而不是新建一条独立回答。
5) message_id 关联前提
上游已开始丢弃缺失 message_id 的流事件,消费端可按新契约实现,不再为“缺失 message_id 的 chunk”保留兼容逻辑。
实施要求(建议拆任务)
- 流事件解析层重构
- 统一提取:
channel/source/event_type/message_id/role
- 建立类型化内部模型(不要在 UI 层直接读原始 JSON)
- 渲染状态机重构
- 按
(task_id, artifact_id) 维护独立缓冲
- 对
final_answer、reasoning、tool_call 分通道渲染
final_snapshot 应覆盖 final_answer 当前视图,不重复追加
- 展示策略
final_answer:主展示区
reasoning:可折叠/次级展示
tool_call:结构化展示(至少与 final answer 分离)
- 删除旧路径
- 移除“单一文本流拼接”逻辑
- 移除对旧无 channel 数据的回退处理(本需求不做兼容)
验收标准
建议测试用例
reasoning -> tool_call -> final_answer 顺序流,验证三通道独立。
final_answer 增量后收到 final_snapshot,验证无重复。
- 同一任务多个 artifact 交错到达,验证状态机稳定。
append=false 后再 append=true,验证覆盖后续拼接正确。
备注
若上游 PR #73 在联调窗口内尚未合并,请先以 feature flag 或 mock stream fixture 落地,待上游合并后切实流验证。
背景
opencode-a2a-serve已对message:stream的流式输出契约做了升级(见上游 issue/PR):本仓库(
a2a-client-hub)需要完成消费端适配。目标
将当前“把所有文本当单一流拼接”的消费方式,迁移为“按 channel 分流 + 正确处理 append + final snapshot 语义”的新实现,避免:
上游新契约(消费端必须遵守)
1) 流事件通道化
TaskArtifactUpdateEvent.artifact.metadata.opencode.channel取值:reasoningtool_callfinal_answer2) 多 artifact_id 并行
同一轮任务会出现多个 artifact:
final_answer:{task_id}:streamreasoning:{task_id}:stream:reasoningtool_call:{task_id}:stream:tool_call3) append 语义必须按“每个 artifact/channel”独立处理
append=true:追加到该 artifact 当前缓冲append=false:重置该 artifact 当前缓冲为本次文本禁止跨 channel/跨 artifact 拼接。
4) final snapshot 语义
source=final_snapshot(位于artifact.metadata.opencode.source)表示收尾补发快照:append=false、last_chunk=true同时出现消费端应将其视为
final_answer通道的一次“覆盖更新”,而不是新建一条独立回答。5) message_id 关联前提
上游已开始丢弃缺失
message_id的流事件,消费端可按新契约实现,不再为“缺失 message_id 的 chunk”保留兼容逻辑。实施要求(建议拆任务)
channel/source/event_type/message_id/role(task_id, artifact_id)维护独立缓冲final_answer、reasoning、tool_call分通道渲染final_snapshot应覆盖final_answer当前视图,不重复追加final_answer:主展示区reasoning:可折叠/次级展示tool_call:结构化展示(至少与 final answer 分离)验收标准
final_answer不重复展示。reasoning、tool_call、final_answer可独立渲染,不互相污染。append=false被正确处理为覆盖,而非追加。source=final_snapshot时,不会额外产生重复答案卡片/消息气泡。建议测试用例
reasoning -> tool_call -> final_answer顺序流,验证三通道独立。final_answer增量后收到final_snapshot,验证无重复。append=false后再append=true,验证覆盖后续拼接正确。备注
若上游 PR #73 在联调窗口内尚未合并,请先以 feature flag 或 mock stream fixture 落地,待上游合并后切实流验证。