Skip to content

Latest commit

 

History

History
107 lines (75 loc) · 4.46 KB

File metadata and controls

107 lines (75 loc) · 4.46 KB

第 8 章:事件与回放 —— UI 看到的不是 Runner,而是事件投影

1. 为什么需要 Event V2

如果 UI 直接订阅 Session Runner 内存对象,会绑定到:

  • 某个进程的 fiber;
  • 某个 server 实例;
  • 某个旧版消息结构;
  • 某个 provider SDK 的 stream event。

Event V2 让执行过程先变成 OpenCode 自己的领域事件,再由 projector、SSE、sync、SDK 和 UI 消费。事件因此同时承担两种职责:

  • 实时通知:当前 UI 立即显示 text delta / tool running;
  • 回放输入:断线后从 sequence / cursor 继续同步。

2. 三个事件层

Provider LLMEvent
  → Session runner publisher
  → EventV2 domain event
  → durable event / projector
  → public API event / SSE / Sync
  → TUI / App / Desktop / ACP

Provider event 是协议层,不能直接公开;Event V2 是领域层,应该跨 provider 稳定;public API event 是 transport projection,负责兼容和鉴权。

3. 事件不是“日志字符串”

一个有用的事件至少需要:

  • stable type;
  • event ID / sequence;
  • session / project / location scope;
  • typed properties;
  • 是否 durable / 是否可以重放;
  • 对应 projector 或 public API schema。

packages/opencode/src/event-manifest.ts 维护事件定义集合,API 会从 manifest 生成 Event 联合 schema。这样新事件必须在类型层被声明,不是偷偷发一个任意 JSON。

4. Stream 与 durable projection 的时序

sequenceDiagram
  participant P as Provider
  participant R as Runner
  participant E as EventV2
  participant D as DB / projector
  participant U as UI subscriber
  P->>R: text delta / reasoning / tool call
  R->>E: publish typed domain event
  E-->>U: realtime event
  E->>D: append / project durable state
  D-->>U: replay after reconnect
  R->>R: await local tool settlement
  R->>P: next provider turn
Loading

实时通知和数据库投影可能不是同一毫秒完成,因此 UI 不能只靠某一类消息判断最终状态。最终一致的事实来自 projected session state 和可重放序列。

5. Sync 为什么还需要存在

SSE 适合持续连接,但实际客户端会断线、切换项目或从历史 session 进入。Sync API 可以提供:

  • 当前 cursor 之后的 event;
  • session / project 的初始快照;
  • 增量事件与状态更新;
  • 对某些事件的过滤和重放。

这让 Web、TUI、Desktop 都能先 hydrate 状态,再订阅后续 event,而不是从零等待下一次变化。

6. Event V2 Bridge:迁移期的适配器

旧代码仍有 V1 的 Session.EventPermission.EventQuestion.EventEventV2Bridge 的价值是把旧 service 产生的事件接到新的 Event V2 体系中,给迁移留出时间。

阅读桥接代码时要问两个问题:

  1. 这个事件的 canonical source 是 V1 service 还是 V2 core service?
  2. 它是实时通知,还是已具备可回放的 durable record?

不要因为 UI 收到了 event,就假设数据库已经写入了同等事实。

7. 事件驱动下的错误处理

事件消费者可能失败,provider 也可能失败,工具可能被拒绝。好的事件边界会让错误具有:

  • 稳定 type;
  • session / call ID;
  • 模型可见的 message 或 tool result;
  • UI 可显示的详细信息;
  • 必要时可 retry / compact / stop 的分类。

Server 层的 SchemaErrorMiddleware 处理 transport decode error;Session 层的 LLMEvent.providerError、tool error 和 permission decline 处理业务执行错误。两种错误不能混为一个“500”。

本章小结

Event V2 是 OpenCode 多端体验的神经系统,但它不是单纯 pub/sub。它把 provider stream 变成领域事件,再通过投影、SSE、Sync 和 SDK 让实时显示与断线恢复同时成立。

源码锚点