Opencode 多 Provider 架构与共享 chat_event 事件平面:从 marker 兼容层到结构化流式渲染
发布时间:2026/10/9 5:26:09 锦皓数字建站

开发工具AI 应用代码智能体【免费下载链接】idea-claude-code-gui一个功能强大的 IntelliJ IDEA 插件为开发者提供 Claude Code 和 OpenAI Codex 双 AI 工具的可视化操作界面让 AI 辅助编程变得更加高效和直观。项目地址https://gitcode.com/zhukunpenglinyutong/idea-claude-code-gui点击查看免费下载本篇技术指南以 docs/opencode/MULTI-PROVIDER-ARCHITECTURE.md 为骨架结合本仓库idea-claude-code-guiIntelliJ IDEA 插件中 ai-bridge、Java 桥接层与 webview 的实际源码系统讲解 opencode 时代的 Provider 架构演进为什么 opencode 不能当作普通文本 Provider 接入、chat_event共享事件平面如何定义与落地、流式事件捕获与重放夹具如何支撑调试与测试。读完本文你将掌握结构化 Agent 事件turn / step / part / block / tool 生命周期 / 权限 / diff / 子任务在Java 桥接层 → Node ai-bridge → webview 渲染链路中的规范化模型以及如何在保持 Claude/Codex 兼容的同时新增未来 Provider。一、背景为什么 opencode 不能只是又一个文本 Provider1.1 旧架构擅长的事情此前的 Codex 时代多 Provider 架构见 docs/codex/MULTI-PROVIDER-ARCHITECTURE.md擅长把 Provider 输出投影到一组有限的 message marker上Claude 用sessionIdCodex 用threadId通过channel-manager.js按 Provider 路由各 Provider 服务统一向 stdout 输出[MESSAGE_START]、[CONTENT_DELTA]、[MESSAGE_END]等 markerJava 逐行读取 stdout把 marker 回调为 UI 更新。这套模型对形如prompt - assistant text - done的简单文本 Provider 非常有效。仓库中 ai-bridge/channel-manager.js 的providerHandlers路由表就是该思想的现成体现一个入口node channel-manager.js provider command分派到 claude、codex、grok、kimi、opencode、pi、omp、dsh、minimax、zcode 等通道。1.2 opencode 是另一种形状opencode 对外暴露的是结构化的事件面而不是一串纯文本session - turn - message - part - tool lifecycle - permission/question - diff - task/subagent - restored history如果直接把这类事件压进 Claude 形状的渲染契约第一个 demo 可能更快但会把复杂度转移到 Provider 专属的 merge、dedupe、history、permission、diff-recovery 代码里。文档明确指出本仓库此前的 opencode 支持实验已回滚正是踩了这个坑——Provider 代码、Java handler、webview 流式钩子为同一个 turn 各自累积了特判合并与恢复逻辑反复修复 role 门控文本、stream-end 恢复、tool 后文本排序、diff baseline 过滤、历史归一化、子任务重建等问题。1.3 结论先立契约再落 Provider核心决策是不要让 opencode 变成又一个只发 Claude/Codex 兼容 marker 的适配器而是保留显式 Provider 适配器与 Provider 路由保留集中式权限模式映射引入 Provider 中立的chat_event流承载结构化运行时事件增加可重放的流式事件捕获日志可提升为回归测试夹具现有 Provider 在新路径引入期间继续输出当前 marker以 opencode 为主要设计目标但用夹具和至少一个现有 Provider如 Codex来证明契约。二、架构总览与设计原则2.1 分层架构原文档给出了三层架构图结合当前仓库实现可对应为Java Layer ClaudeSDKBridge / CodexSDKBridge / OpenCodeCliBridge provider: claude | codex | opencode session identity: sessionId / threadId / opencode sessionID runtime: Claude Agent SDK / Codex 桥 / opencode CLI(serve) | | stdin JSON / process output / callback protocol v Node ai-bridge channel-manager.js - 按 provider 路由claude | codex | opencode | ... channels/ claude-channel.js codex-channel.js opencode-channel.js services/ claude/ codex/ opencode/ ... | | legacy markers chat_event records v Java message handlers legacy marker 处理现有 UI 路径 chat_event 转发结构化渲染路径 | v Webview legacy ClaudeMessage 渲染器迁移期 chat_event accumulator 与结构化渲染器在真实仓库中这一层已经部分落地Java 侧存在 OpenCodeCliBridge.javaNode 侧存在 opencode-channel.js 与 ai-bridge/services/opencode/ 目录message-service.js、models-service.js。详见本文第十一章当前仓库落地现状对照。2.2 六条设计原则保持 Provider 边界显式——每个 Provider 的适配器自包含不互相渗透Provider 专属运行时所有权留在适配器内——CLI/SDK 解析、进程生命周期、auth 配置发现都属于适配器把结构化运行时事件归一化为共享chat_event流——这是本文的核心把流式事件捕获为可重放日志——用于调试与测试生成迁移期间保持ClaudeMessage兼容——不一夜推翻现有 UI让实时流与恢复的历史使用同一套逻辑事件模型——live 与 restored 双路径最终收敛到同一渲染结果。三、Provider 职责边界3.1 适配器该拥有什么每个 Provider 适配器负责SDK 或 CLI 解析运行时进程生命周期spawn、销毁、重启Provider 专属的 auth / 配置发现会话的创建、恢复、中止、删除与历史查询支持情况下的模型与 Agent 发现权限请求/回复的传输从 Provider 原生事件到规范化chat_event记录的转换。3.2 适配器不该拥有什么共享 UI 合并策略不属于适配器。原生事件一旦被归一化排序与分组就应该交给共享的 event accumulator 处理。此前 opencode 支持实验的教训是一旦违反这条边界Provider 代码、Java handler 和 webview 流式钩子就会各自为同一个 turn 维护特判合并/恢复逻辑最终导致错误难以隔离、修复相互牵连。四、共享事件平面chat_event 契约4.1 核心字段共享事件平面使用带稳定身份字段的chat_event记录字段含义provider来源 Provider如claude/codex/opencodesessionIdProvider 会话/线程 IDturnId一次用户提示或恢复的提示对应的 turnrunId一次运行run的 IDmessageId原生 message 身份Provider 暴露时stepIdProvider 生命周期单元opencode 的 message/part 组、Codex item、ACP update 组等partId原生 part 身份blockId一个可渲染块文本/思考/diff/状态/终端/计划/todo的身份跨 delta 与快照保持稳定toolCallId一次工具生命周期的身份关联 start / 输入更新 / 权限请求 / 结果 / diff / 终端输出sequence适配器边界分配的单调序号重放唯一排序依据parentId嵌套实体子任务会话、终端流、权限请求、diff回连到触发它的块/工具/step/turnphase生命周期阶段kind事件种类4.2 生命周期阶段phasestarted—— 开始delta—— 增量updated—— 快照更新completed—— 完成failed—— 失败4.3 事件种类kindtext、thinking、tool、diff、status、permission、question、plan、terminal、task、todo、usage、mode。kind集合刻意比第一批 opencode slice 更宽目的就是让 Provider Compatibility Matrix 能在 ACP、Cursor、opencode、Codex 之间对齐同样的补充事件使共享契约可作为 Provider 中立的基础设施被评审。4.4 关键变化与最小不变量与旧只有 marker架构最重要的区别Provider 事件保留自己的身份与生命周期前端不应事后靠合并 assistant message来推断顺序。共享事件平面的最小不变量sequence只在适配器边界分配一次重放按sequence而非时间戳turnId、stepId、partId、blockId、toolCallId在 delta、快照、恢复历史中保持稳定用parentId把嵌套权限、问题、diff、终端、任务/子代理会话连到引发它们的事件把生命周期phase视为同一身份的状态而不是重建无关块的理由迁移期让 legacy marker 输出从chat_event派生而不是把 marker 当成唯一事实来源。五、兼容层[CHAT_EVENT]标记行与迁移策略chat_event记录复用现有 marker 的 stdout 传输每个事件一行[CHAT_EVENT] json由 Java 原样转发给 webview不做整形Java 可按需额外解析permission等特定 kind 以驱动原生对话框。传输规则每个事件一行JSON 编码且不内嵌换行Java 只转发、不重塑schema 携带显式schemaVersion字段使重放夹具与 accumulator 能拒绝或适配不兼容事件。当前 UI 仍然依赖 Claude 兼容消息与这些 marker[MESSAGE_START][CONTENT_DELTA][THINKING_DELTA][MESSAGE][STREAM_END][MESSAGE_END]迁移期间 Provider 可同时输出两种产物legacy markers 供现有 handler 使用chat_event记录供结构化路径使用。长期目标是富 Provider 输出内部首先表示为chat_event仅当兼容性需要时才派生 legacy 消息。源码佐证当前仓库的 marker 协议实现在 ai-bridge/utils/marker-protocol.js提供emitJsonStringMarker、emitSessionId、beginStream、emitToolUseMessage、emitToolResultMessage等原语opencode 的 MVP 通道正是通过这些原语向现有 UI 输出[CONTENT_DELTA]、[THINKING_DELTA]、tool use/result 等 marker见 message-service.js。六、流式事件捕获可重放日志与重放夹具6.1 为什么需要捕获流式渲染 bug 很难靠截图或最终消息诊断。每个 Provider 都应能写出可重放的流式事件日志捕获足以复现渲染问题的事件流无需对真实 Provider 运行挂调试器。6.2 捕获阶段capture stages阶段含义示例native_in桥接层收到的 Provider 原生事件opencode/event、Codexitem.completed、Claude SDK 事件normalized_out映射为chat_event后的事件kind: tool, phase: completedlegacy_out为现有 handler 发出的兼容 marker[CONTENT_DELTA]、[MESSAGE]handler_inJava handler 收到 marker/事件可选回调行或解析后的消息render_inwebview accumulator 收到事件可选前端夹具捕获第一批基础设施中native_in、normalized_out、legacy_out价值最高。6.3 日志信封格式使用换行分隔 JSONNDJSON每行一个信封可流式读写、切片、脱敏、重放{ schemaVersion: 1, captureId: cap_2026_06_14_001, sequence: 42, timestamp: 2026-06-14T12:00:00.000Z, provider: opencode, sessionId: ses_123, turnId: turn_1, stage: normalized_out, eventType: chat_event, payload: { type: chat_event, kind: tool, phase: completed, toolCallId: call_abc }, redactions: [] }推荐信封字段schemaVersion日志格式版本、captureId一次捕获共享的稳定 ID、sequence桥接捕获工具分配的单调整数重放唯一依据、timestamp仅诊断用、provider、sessionId、turnId、stage、eventType、payload脱敏后负载、redactions被脱敏字段清单。可选字段model、agent、permissionMode、cwdHash、projectHash、source、notes。6.4 默认脱敏规则默认脱敏移除或归一化API 密钥、auth token、cookie、密码、请求头完整环境变量映射绝对 home 路径替换为HOME项目根路径替换为PROJECT临时目录替换为TMP大二进制附件替换为元数据超大 tool 输出截断并保留原长度。注意文本内容不应盲目删除因为渲染 bug 往往依赖精确文本、空白与顺序fixture 晋升时应提供显式 sanitization 步骤处理私有内容。6.5 两种重放模式与 fixture 晋升捕获日志至少支持两种重放native_in重放把原始 Provider 事件喂给 Provider normalizer断言产出的chat_event记录normalized_out重放把chat_event记录喂给前端 accumulator断言渲染组render groups。迁移期间保留第三种legacy_out重放把兼容 marker 喂给现有 Java/webview handler断言无回归。fixture 晋升工作流开启捕获 → 复现渲染问题 → 保存 JSONL → 运行 sanitizer移除时间戳、机器路径、captureId、必要时移除私有文本→ 在 Provider 专属测试下签入最小夹具 → 添加把夹具重放进 normalizer 或 accumulator 的回归测试。示例夹具形状见 STREAMING-EVENT-LOGS.mdnative_innormalized_out成对事件仅保留sequence、stage、eventType、payload。七、权限架构mode 映射与请求/回复协议权限 mode 映射保持 Provider 专属但UI 事件形状应当共享Plugin mode - provider permission mapper - native provider request/reply transport - chat_event kind: permission - shared permission UI - reply correlated by provider request ID7.1 Opencode 专属的 mode 指导插件 mode预期的 opencode 行为plan使用 opencode 的 planning agent 或等价物对编辑、shell、外部目录、网络类操作 deny 或 askdefault对编辑、shell、外部目录、网络类操作 askacceptEdits允许工作区编辑shell 与外部操作保持 askautoEdit允许自主变更所需的读取与工作区编辑危险的 shell 与外部操作保持 askbypassPermissions仅当用户显式选择时才允许广泛工作区操作规范化的权限事件必须保留 Provider 请求 ID、请求的工具/动作、可用选项。只有这样才能把 UI 决策回连到正确的 Provider 请求上——这是权限对话框能 resolve 的关键若 request ID 丢失权限对话框将无法闭合。八、会话与历史架构live / restored 双路径汇聚每个 Provider 用自己的历史事实来源ClaudeClaude session/history API 或 readerCodexCodex session 文件与现行 Codex 历史归一化Opencodeopencode session API。共享目标是让 live 与 restored 两条路径产出等价的渲染组live provider events - chat_event stream - accumulator - render groups restored history - chat_event list - accumulator - render groups恢复历史允许输出更少的中间 delta但必须保留 turn、块、工具、diff、权限、问题、任务链接的同一逻辑身份。测试应当比较累积后的渲染组而不是比较原始事件数量。这正是为了避免 Codex 的失败模式live 流与历史重放需要各自维护同一逻辑 tool call 的不同字段映射。九、Opencode 服务布局、通道命令与消息流9.1 预期的 Node 服务布局ai-bridge/services/opencode/ sdk-loader.js server-manager.js message-service.js event-normalizer.js permission-mapper.js model-service.js agent-service.js history-service.js error-normalizer.js event-capture.js9.2 预期的通道命令sendabortdeleteSessiongetSessionMessageslistSessionslistModelslistAgents9.3 Opencode 消息流10 步1. 用户以 provider opencode 发送提示。 2. Java OpenCodeSDKBridge 序列化 message、session ID、cwd、mode、model、agent、attachments。 3. channel-manager.js 路由到 opencode-channel.js。 4. opencode-channel.js 启动或连接 opencode serve。 5. message-service 在提交提示前订阅 /event。 6. message-service 创建或恢复 opencode session。 7. message-service 向 session message endpoint 发送 text/file parts。 8. event-normalizer 按 sessionID 过滤事件。 9. event-normalizer 发出 chat_event 记录与迁移兼容 marker。 10. Java/webview 依据稳定事件身份渲染。源码佐证当前仓库 opencode-channel.js 已实现send与listModels两个命令路由表注册于 channel-manager.jsJava 侧 OpenCodeCliBridge.java 提供会话消息读取。当前落地的是 CLI 模式opencode run --format json与文档规划的serve/SDK 模式存在差异见第十一章。十、新增未来 Provider切分法与最小事件示例10.1 未来 Provider 的固定拆分只为实现运行时分派添加一个 Java bridge添加一个 Node channel 与 service 目录承载 Provider 专属运行时逻辑把原生事件归一化为chat_event记录为 live 流与恢复历史的一致性添加夹具仅当现有 UI 路径确实需要时才添加兼容 marker。10.2 简单 Provider 的最小事件子集简单 Provider 可只发射很小的子集{ type: chat_event, provider: example, sessionId: session_1, turnId: turn_1, blockId: text_1, sequence: 1, phase: delta, kind: text, text: Hello }结构化 Provider 则应保留更丰富的身份而不是把一切都拍扁成一条 assistant message。10.3 推荐的切分顺序宽泛的 Provider 功能面应作为独立 slice逐个落地此前 opencode 实验把流式、历史、模型发现、Agent 发现、slash 命令、MCP 展示、用量统计、上下文恢复、菜单 UX 一次打包导致失败难以隔离。推荐顺序共享chat_eventschema、accumulator 与捕获工具Provider-normalizer 与前端-accumulator 两条路径的重放夹具用 Codex 或其他现有 Provider 作为证明适配器保持当前行为兼容使用结构化事件路径的最小 opencode send/stream/history 适配器opencode 模型/Agent 发现与可选的 Provider 专属 UX 面。10.4 测试策略契约边界验收清单捕获的流式日志可脱敏并作为夹具重放Provider 事件夹具映射为chat_event时不丢身份交错的文本、思考、工具事件保持顺序工具生命周期状态渲染为 pending/running/completed/failed权限与问题请求保留 Provider 请求 IDdiff 保留文件作用域与当前 turn 作用域task/subagent 事件在可用时保留子会话链接恢复历史夹具与 live 夹具产出相同渲染组迁移期间 legacy Claude/Codex 渲染保持兼容。十一、当前仓库落地现状对照源码证据需要说明docs/opencode/系列文档本身标注为规划文档见 docs/opencode/README.md描述的是共享chat_event基础设施与 opencode 集成应当具备的最终形状而本仓库当前已存在一份更早期的 MVP 实现走的是CLI JSON marker 兼容路径尚未切换到 serve/SDK chat_event路径。两者对照如下已存在的实现可从源码直接确认路由ai-bridge/channel-manager.js的providerHandlers中已注册opencode: handleOpenCodeCommand调用形式为node channel-manager.js opencode send/listModelschannel-manager.js通道ai-bridge/channels/opencode-channel.js实现send透传 message/sessionId/cwd/model/reasoningEffort/attachments 到 message-service与listModels消息服务ai-bridge/services/opencode/message-service.js通过opencode run --format json拉起用户本机 CLI把事件映射为共享 marker[CONTENT_DELTA]、[THINKING_DELTA]、tool use/result并实现buildOpenCodeArgs保证 prompt 在-f之前避免 yargs 把尾随位置参数吞成 file 路径、opencode-default等模型别名不传--model、图片附件临时物化后清理message-service.js模型发现ai-bridge/services/opencode/models-service.js通过opencode models解析provider/model形式 token含 Windows 管道兜底临时文件重定向与opencode-default兜底条目Java 桥接src/main/java/com/github/claudecodegui/provider/opencode/OpenCodeCliBridge.java继承MarkerCliBridgeProvider 名为opencodeMVP 使用 CLI 模式注释明确Managedopencode serve/ SDK 可以后续叠加而不改变 Java marker 契约历史读取src/main/java/com/github/claudecodegui/provider/opencode/OpenCodeHistoryReader.java优先读 SQLite~/.local/share/opencode/opencode.db或$XDG_DATA_HOME/opencode/opencode.db回退到旧 JSON storage 布局storage/session/projectHash/ses_xxx.json、storage/message/ses_xxx/msg_yyy.json、storage/part/msg_yyy/prt_zzz.json并按directory字段归一化、大小写不敏感过滤会话Webviewwebview/src/components/ChatInputBox/types.ts定义OPENCODE_DEFAULT_MODEL_ID opencode-default选择该默认模型时省略--model让 CLI 自行解析并在 Provider 列表注册{ id: opencode, label: OpenCode, icon: codicon-terminal, enabled: true, beta: true }types.ts测试ai-bridge/services/opencode/下存在message-service.test.js与models-service.test.js覆盖事件解析、参数构建、模型输出解析等单元行为。与规划的差距可推断的演进方向当前实现通过opencode run --format json一次性输出事件流尚未实现opencode serve的持久化订阅/event会话式模式也未引入opencode-ai/sdk依赖当前输出仍是 legacy marker 协议尚未落地文档规划的[CHAT_EVENT] json行、schemaVersion、前端 accumulator 与 live/restored 渲染组对等夹具文档规划的abort、deleteSession、listSessions、listAgents、getSessionMessages等通道命令当前尚未全部在 channel 中实现当前仅send与listModels。因此本文描述的chat_event架构是目标态当前仓库的 opencode MVP 是marker 兼容路径的过渡态二者并存恰好验证了架构文档迁移期允许双轨输出的设计前提。十二、相关文档导航Opencode 准备文档Provider-Neutral Chat Events——基础设施提案、事件契约不变量、累积器规则、验收标准与非目标Opencode 集成快速开始——预期的 Provider 形状、实现清单、手工冒烟测试与故障排查表Provider 兼容性矩阵——ACP / Cursor / opencode / Codex 对每个事件 kind 与接口字段的覆盖视图流式事件日志与重放夹具——捕获信封格式与 fixture 晋升工作流Opencode 支持实验的教训——回滚实验的失败模式与下次怎么做清单Codex 多 Provider 架构旧文档——被本文取代的 marker 时代架构基线注本文所述chat_event契约与 serve/SDK 模式为规划形态仓库当前落地为 CLI marker 路径涉及版本、命令与配置时均以当前仓库源码实际内容为准。赞分享开发工具AI 应用代码智能体【免费下载链接】idea-claude-code-gui一个功能强大的 IntelliJ IDEA 插件为开发者提供 Claude Code 和 OpenAI Codex 双 AI 工具的可视化操作界面让 AI 辅助编程变得更加高效和直观。项目地址https://gitcode.com/zhukunpenglinyutong/idea-claude-code-gui点击查看免费下载相关推荐GhostTrack 完整指南快速查询 IP 归属地、手机号归属与用户名注册情况GhostTrack 完整指南快速查询 IP 归属地、手机号归属与用户名注册情况 GhostTrack 是一款免费的命令行公开信息小工具OSINT即从公开网络安全CLIReact Router v6 精读从 Routes 重构、element 渲染到多层 Context Provider 架构设计React Router v6 精读从 Routes 重构、element 渲染到多层 Context Provider 架构设计 React Router文档技术博客教程Opencode 支持实验的教训JetBrains 插件如何以 Provider-Neutral 事件模型重构流式渲染与历史恢复Opencode 支持实验的教训JetBrains 插件如何以 Provider Neutral 事件模型重构流式渲染与历史恢复 本文基于 idea clau开发工具AI 应用代码智能体上一篇Flow 抽象枚举Abstract Enums用 Enum 与 EnumValue 编写通用枚举工具函数下一篇使用 Ray Tune 的 PB2 调度器优化 PPO 强化学习超参数PB2 PPO 示例全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。