第 12 章:阅读方法 —— 如何继续深入 OpenCode 源码¶
1. 推荐的源码阅读路线¶
路线 A:理解一次 prompt¶
httpapi/groups/session.ts
→ handlers/session.ts
→ core/session/input.ts
→ core/session/execution.ts
→ core/session/runner/llm.ts
→ core/session/runner/to-llm-message.ts
→ llm/route/client.ts
→ core/session/runner/publish-llm-event.ts
→ schema/session-event.ts
路线 B:理解一次工具调用¶
tool/registry.ts
→ tool/tool.ts
→ permission/index.ts
→ concrete tool (read / edit / shell / task)
→ truncate / tool-output-store
→ tool part event
→ next SessionRunner turn
路线 C:理解一次上下文变化¶
system-context/builtins.ts
→ system-context/registry.ts
→ session/context-epoch.ts
→ session/history.ts
→ mid-conversation system message
→ LLMRequest.system + messages
路线 D:理解 UI 为什么更新¶
LLMEvent
→ EventV2 publisher / bridge
→ public event manifest
→ SSE / Sync
→ client / sdk
→ app / tui session store
→ timeline / tool renderer
2. 读一个新模块时的五个问题¶
- 输入是 durable fact、runtime state 还是 user configuration?
- 输出是领域状态、LLM projection、HTTP contract 还是 UI projection?
- 作用域是 global、workspace、Location、project 还是 session?
- 失败时应该 throw、变成 event、变成 tool result,还是被降级?
- 是否必须可 replay,还是纯实时通知就够?
3. 关键设计决策清单¶
| 决策 | 解决的问题 | 代价 |
|---|---|---|
| Effect Layer | 依赖可替换、资源有生命周期 | 初读需要理解 Layer / Service |
| Location scope | 多项目、多 worktree、单 server | 每个服务不能偷读全局目录 |
| Durable input | admission 与 execution 解耦 | 需要 inbox、promotion 和 cursor |
| Event V2 | 实时与重放统一 | 需要 manifest、projector、bridge |
| Context Epoch | 稳定 baseline + 动态更新 | 需要 snapshot、epoch、update message |
| Canonical LLM IR | 吸收 provider 差异 | 需要 adapter 和 metadata 保真 |
| Tool Registry | 权限、schema、输出治理集中 | 工具不能随手直接调用系统 API |
| SDK boundary | 多端共享契约 | API 缺能力时必须先扩展后端 |
4. 常见误读¶
误读 1:session/prompt.ts 就是全部 Agent Loop¶
它是重要的 legacy orchestration,但 V2 的规范化执行链在 packages/core/src/session/runner。如果只读前者,会漏掉 durable input、Context Epoch 和 Location ownership。
误读 2:SSE 事件就是数据库事实¶
事件可能先到达订阅者,再被 projector 落盘。要判断最终状态,要追踪 event durable semantics 和 projected table。
误读 3:Provider plugin 等于模型适配器¶
Provider 还涉及 catalog、credential、auth、transform、native metadata、usage 和错误。只加一个 createOpenAI() 并不能保证 Session continuation 正确。
误读 4:TUI 复杂,所以 backend 逻辑应该搬进 TUI¶
相反,复杂 TUI 更需要清晰 SDK wire contract 和 tolerant renderer。specs/tui-package.md 明确禁止 TUI 依赖 backend implementation。
5. 术语速查¶
| 术语 | 一句话解释 |
|---|---|
| Session History | 经过 Epoch / compaction 选择后,发给 provider 的时间顺序消息投影 |
| Session Drain | 当前进程从 durable work 执行到 settle 的本地连续运行段 |
| Safe Provider-Turn Boundary | durable promotion、tool settlement 完成后,允许接纳上下文变化的位置 |
| Context Source | 有 stable key、codec、loader 和 renderer 的 typed context 单元 |
| Context Snapshot | 用于比较 source 上次有效值的模型隐藏 JSON 状态 |
| Model Tool Output | 写入 Session、受大小约束的工具输出投影 |
| Managed Tool Output File | 保存完整工具输出的临时文件 |
| Native Continuation Metadata | provider 要求下一轮继续使用的 opaque protocol metadata |
| Embedded OpenCode | 复用同一 router,通过内存 HttpClient 运行的同进程 host |
6. 最后一个架构判断¶
如果把 OpenCode 压缩成一句话:
OpenCode 是一个 Location-scoped、Effect-native、事件驱动的 coding-agent server;它把用户输入、模型请求、工具副作用和 UI 状态都放到可投影、可重建的边界上。
这比“支持很多 provider 的 CLI”更接近源码真实复杂度,也解释了为什么项目需要 Core、Server、SDK、Session V2 和多个客户端。
源码锚点总表¶
- 入口:
packages/opencode/src/index.ts - 运行时:
packages/opencode/src/effect/app-runtime.ts - Session V2:
packages/core/src/session - 上下文:
packages/core/src/system-context - LLM:
packages/llm/src - API:
packages/opencode/src/server/routes/instance/httpapi - 工具:
packages/core/src/tool与packages/opencode/src/tool - UI:
packages/tui、packages/app