Skip to content

Commit eb55f83

Browse files
committed
docs: add OpenCode source breakdown
0 parents  commit eb55f83

20 files changed

Lines changed: 1585 additions & 0 deletions

.github/workflows/pages.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Publish MkDocs site
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
- name: Setup Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.x"
27+
cache: pip
28+
- name: Install dependencies
29+
run: pip install -r requirements.txt
30+
- name: Build site
31+
run: mkdocs build --strict
32+
- name: Upload Pages artifact
33+
uses: actions/upload-pages-artifact@v3
34+
with:
35+
path: site
36+
deploy:
37+
environment:
38+
name: github-pages
39+
url: ${{ steps.deployment.outputs.page_url }}
40+
runs-on: ubuntu-latest
41+
needs: build
42+
steps:
43+
- name: Deploy to GitHub Pages
44+
id: deployment
45+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
site/
2+
.venv/
3+
__pycache__/
4+
.DS_Store

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# OpenCode Dev 源码拆解
2+
3+
> 以本地 `opencode-dev` 源码快照为对象,参考 `dg-ai-notes` 对 Pi-Agent 的“概念 → 源码 → 设计取舍”方式,拆解 OpenCode 的运行时、Session V2、上下文工程、工具系统、Provider、HTTP API、事件回放和多端 UI。
4+
5+
在线阅读:<https://hanqing.github.io/opencode-dev-notes/>
6+
7+
## 文档定位
8+
9+
OpenCode 是一个完整的开源 AI coding agent 产品,而不是只有一个 SDK。它同时处理:
10+
11+
- 多项目 / 多工作区 / 多 worktree 的会话隔离;
12+
- 可恢复、可重放的 Session 与消息投影;
13+
- System Context、Context Epoch、自动压缩和工具输出治理;
14+
- Provider / model catalog / native continuation / AI SDK 的兼容;
15+
- TUI、Web、Desktop、ACP、SDK 和远程 HTTP Server;
16+
- 权限、插件、MCP、LSP、skills、commands、subagents 和发布系统。
17+
18+
因此本拆解不把 OpenCode 简化成“模型 + 工具循环”,而是把它看成一条由持久化事实驱动的产品运行链:
19+
20+
```text
21+
用户输入
22+
→ HttpApi / CLI / TUI / SDK
23+
→ Session 输入箱(durable inbox)
24+
→ Session Runner 在安全边界提升 prompt
25+
→ System Context + Session History
26+
→ LLM provider turn
27+
→ assistant / tool / event 持久化
28+
→ 工具结算与继续运行
29+
→ SSE / sync / SDK / TUI / Web 投影
30+
```
31+
32+
## 源码基线
33+
34+
- 分析对象:`/Users/hanqing/CliX/opencode-dev` 工作区快照。
35+
- 采集时间:2026-08-02(Asia/Shanghai)。
36+
- 上游仓库:[`anomalyco/opencode`](https://github.com/anomalyco/opencode),默认开发分支为 `dev`
37+
- 说明:OpenCode 的 `dev` 分支持续演进;文中路径和解释以本地快照为准,源码链接默认指向上游 `dev` 分支,可能随上游移动。
38+
39+
## 章节导航
40+
41+
| 章节 | 主题 | 关键问题 |
42+
| --- | --- | --- |
43+
| [01 总览](docs/01-overview.md) | 产品与运行时全景 | OpenCode 到底由哪些边界组成? |
44+
| [02 分层](docs/02-layers.md) | Workspace packages 与依赖漏斗 | 为什么 Schema、Core、Server、Client 要分开? |
45+
| [03 Server 与 API](docs/03-server-api.md) | Effect HTTP、Location、SDK | UI 如何调用同一个后端? |
46+
| [04 Session V2](docs/04-session-v2.md) | 输入箱、Runner、Provider Turn | 一次 prompt 如何变成可恢复的执行? |
47+
| [05 上下文工程](docs/05-context.md) | System Context、Epoch、History、Compaction | 窗口有限时系统如何保持正确? |
48+
| [06 LLM 与 Provider](docs/06-llm.md) | Canonical LLM IR、协议适配、模型选择 | 多供应商差异被隔离在哪里? |
49+
| [07 工具与安全](docs/07-tools.md) | Tool Registry、权限、输出、MCP/LSP | Agent 的“手脚”如何被约束? |
50+
| [08 事件与回放](docs/08-events.md) | Event V2、投影、SSE、Sync | 为什么 UI 不直接读取执行过程? |
51+
| [09 持久化与工作区](docs/09-storage-workspace.md) | SQLite、Project、Location、Snapshot | 数据和运行时状态如何落盘? |
52+
| [10 多端 UI](docs/10-clients.md) | TUI、App、Web、Desktop、ACP | 同一 API 如何支撑多个宿主? |
53+
| [11 扩展与发布](docs/11-extensibility-release.md) | Config、Agent、Plugin、Skill、构建 | OpenCode 如何变成可扩展产品? |
54+
| [12 阅读方法](docs/12-reading-map.md) | 源码导航、设计决策、术语表 | 下一次改代码从哪里开始? |
55+
56+
## 参考资料
57+
58+
- [`AGENTS.md`](https://github.com/anomalyco/opencode/blob/dev/AGENTS.md):依赖方向、测试、V2 Session 约束。
59+
- [`CONTEXT.md`](https://github.com/anomalyco/opencode/blob/dev/CONTEXT.md):System Context、Context Epoch、Session Drain 等术语。
60+
- [`packages/core/src/session/runner/llm.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/core/src/session/runner/llm.ts):Session V2 的 provider-turn 编排。
61+
- [`packages/opencode/src/server/routes/instance/httpapi/api.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/server/routes/instance/httpapi/api.ts):HTTP API 组合入口。
62+
- [OpenCode 官方 Agents 文档](https://opencode.ai/docs/agents):用户可见的 agent / permission 语义。
63+
64+
## License
65+
66+
本仓库文档内容采用 CC BY-SA 4.0;OpenCode 源码版权和许可证归上游项目所有。本仓库只复述和引用必要的代码结构,不重新分发 OpenCode 源码。

docs/01-overview.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# 第 1 章:总览 —— OpenCode 不是一个 Loop,而是一台 Agent 产品运行时
2+
3+
## 1. 三种看 OpenCode 的视角
4+
5+
打开仓库时很容易被 `packages/app``packages/opencode``packages/core` 和一大组 workspace package 分散注意力。先固定三个视角:
6+
7+
### 视角一:产品
8+
9+
OpenCode 是一个开源 coding agent。用户可以从 CLI 启动 TUI,可以直接 `run` 一次任务,也可以运行 Server,让 Web、Desktop、远程 SDK 或 ACP 连接进来。README 中的 `build``plan``general` agent 只是产品层入口,真正的执行能力在服务层。
10+
11+
### 视角二:运行时
12+
13+
运行时由 Effect Layer 组装。`packages/opencode/src/effect/app-runtime.ts``AppLayer` 把 Database、Config、Provider、Session、LLM、LSP、MCP、ToolRegistry、Project、Workspace 等服务组合起来,再由 `ManagedRuntime` 提供 `runPromise` 等执行出口。
14+
15+
### 视角三:可恢复系统
16+
17+
Session 不是一组内存消息,而是由数据库、事件、消息投影、输入箱和 Context Epoch 组成的长期对象。`SessionRunner` 只负责当前进程拥有的 drain;它不拥有 Session 的永久身份,也不把“正在运行”写成一条不可恢复的黑盒状态。
18+
19+
## 2. 顶层数据流
20+
21+
```mermaid
22+
flowchart TB
23+
subgraph Hosts[宿主]
24+
CLI[packages/opencode CLI]
25+
TUI[packages/tui]
26+
APP[packages/app]
27+
DESKTOP[packages/desktop]
28+
ACP[ACP adapter]
29+
end
30+
subgraph Boundary[稳定边界]
31+
API[Effect HttpApi]
32+
CLIENT[client / sdk-next]
33+
EVENTS[Event V2 / Sync / SSE]
34+
end
35+
subgraph Runtime[OpenCode runtime]
36+
SERVER[packages/opencode server]
37+
CORE[packages/core services]
38+
LLM[packages/llm]
39+
end
40+
subgraph State[状态]
41+
DB[(SQLite)]
42+
FS[(project filesystem)]
43+
PROVIDER[Provider APIs]
44+
end
45+
CLI --> SERVER
46+
TUI --> CLIENT
47+
APP --> CLIENT
48+
DESKTOP --> CLIENT
49+
ACP --> SERVER
50+
CLIENT --> API
51+
API --> SERVER
52+
SERVER --> CORE
53+
CORE --> LLM
54+
CORE --> DB
55+
CORE --> FS
56+
LLM --> PROVIDER
57+
SERVER --> EVENTS
58+
EVENTS --> TUI
59+
EVENTS --> APP
60+
```
61+
62+
## 3. 最值得先记住的五个边界
63+
64+
| 边界 | 责任 | 不应该做什么 |
65+
| --- | --- | --- |
66+
| Schema | 定义 ID、消息、Part、Event、Prompt 的可编码形状 | 不放 UI 行为和 provider 具体调用 |
67+
| Core | 领域服务、数据库、Session V2、工具和位置作用域 | 不依赖具体 UI 宿主 |
68+
| Server | 启动 runtime、鉴权、路由、HTTP/WebSocket | 不让客户端导入后端私有模块 |
69+
| LLM | canonical request / event 与 provider wire format | 不决定 Session 如何落盘 |
70+
| Client / SDK | 把 HttpApi 变成调用者可消费的 API | 不复制 server orchestration |
71+
72+
这种分界让同一个 Session 能被 TUI 和 Web 同时观察;也让 provider 适配器可以替换,而不要求 TUI 知道 Anthropic 或 OpenAI 的请求格式。
73+
74+
## 4. OpenCode 的中心不是 `prompt()`,而是“事实 + 投影”
75+
76+
旧式 Agent 通常是:
77+
78+
```ts
79+
const messages = [...history, userMessage]
80+
const result = await model.generate(messages, tools)
81+
```
82+
83+
OpenCode 的现实更接近:
84+
85+
```text
86+
durable input
87+
→ promotion event
88+
→ projected session history
89+
→ canonical LLM request
90+
→ streamed domain events
91+
→ durable message / part projection
92+
→ tool settlement
93+
→ next provider turn
94+
```
95+
96+
这里有两个重要推论:
97+
98+
1. **模型请求是投影,不是事实本身。** 需要重新请求时,系统可以从 durable history 和当前 Context Epoch 重建。
99+
2. **执行状态是局部的。** 某个进程正在跑,并不意味着 Session 的全部语义只存在于这个进程的内存里。
100+
101+
## 5. 代码规模带来的阅读策略
102+
103+
OpenCode 的 `packages/opencode/src` 同时包含 CLI、V1 服务、V2 路由、插件、MCP、LSP、分享、安装和迁移代码。如果按目录顺序读,会把“产品功能”误认为“核心执行链”。建议先走这些入口:
104+
105+
1. `packages/opencode/src/index.ts`:CLI 命令地图。
106+
2. `packages/opencode/src/effect/app-runtime.ts`:运行时依赖地图。
107+
3. `packages/opencode/src/server/server.ts`:HTTP listener 和 runtime 生命周期。
108+
4. `packages/opencode/src/server/routes/instance/httpapi/api.ts`:API 组合边界。
109+
5. `packages/core/src/session/runner/llm.ts`:Session V2 的真实 provider-turn。
110+
6. `packages/core/src/system-context/*`:上下文的稳定基线和更新机制。
111+
7. `packages/opencode/src/tool/registry.ts` / `packages/core/src/tool/*`:两代工具系统的连接处。
112+
113+
!!! tip "不要从 UI 开始"
114+
UI 文件很多,但它们主要告诉你“服务器暴露了什么”。要理解“为什么状态会这样变化”,先读 Schema、Session、Event 和 API,再回到 TUI / App。
115+
116+
## 6. 本章小结
117+
118+
- OpenCode 是一个带 Server、数据库、SDK 和多端宿主的产品运行时。
119+
- Session V2 把 prompt admission、provider turn、tool settlement 和事件回放拆开。
120+
- Effect Layer 是依赖装配图,不只是异步语法。
121+
- API 和 Event 是客户端与后端之间的主边界。
122+
123+
### 源码锚点
124+
125+
- [`packages/opencode/src/index.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/index.ts)
126+
- [`packages/opencode/src/effect/app-runtime.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/effect/app-runtime.ts)
127+
- [`packages/opencode/src/server/server.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/server/server.ts)
128+
- [`packages/core/src/session/runner/llm.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/core/src/session/runner/llm.ts)

docs/02-layers.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# 第 2 章:分层与依赖 —— 从 Schema 原子到多端产品
2+
3+
## 1. Workspace 不是按“业务页面”切包
4+
5+
OpenCode 的 package 划分更像一条依赖漏斗:越靠下越稳定、越靠上越接近产品宿主。
6+
7+
```mermaid
8+
flowchart BT
9+
S["schema: 可编码领域形状"]
10+
P["protocol: API / error / transport contract"]
11+
L["llm: canonical message / provider event"]
12+
C["core: Effect services + DB + Session V2"]
13+
SV["server: middleware / HTTP / location"]
14+
OC["opencode: CLI + product orchestration"]
15+
CL["client: generated promise API"]
16+
SDK["sdk-next: client + core + server composition"]
17+
UI["tui / app / web / desktop"]
18+
S --> P
19+
S --> L
20+
P --> SV
21+
L --> C
22+
S --> C
23+
C --> SV
24+
C --> OC
25+
SV --> OC
26+
P --> CL
27+
CL --> UI
28+
C --> SDK
29+
SV --> SDK
30+
SDK --> UI
31+
```
32+
33+
仓库 `AGENTS.md` 给出的原则可以压缩成一句话:**Schema → Core / Protocol → Server;Client 只能依赖 Schema / Protocol,不能反向依赖 Core / Server;sdk-next 才组合 Client、Core、Server。**
34+
35+
## 2. 每一层的“原子”和“分子”
36+
37+
### 2.1 Schema:可以存、传、回放的形状
38+
39+
`packages/schema/src` 定义 Session、Message、Part、Prompt、Event、Permission、Project、Workspace 等数据。它通过 Effect Schema 建立编码与解码关系,ID 也有前缀和格式约束。
40+
41+
Schema 的价值不只是类型检查:
42+
43+
- HTTP body 和 query 可以由同一份定义解码;
44+
- SQLite projector 可以把事件转成同样的结构;
45+
- SDK codegen 可以从 API 定义生成客户端;
46+
- Event Manifest 可以把事件变成可订阅的联合类型。
47+
48+
### 2.2 Protocol:把领域形状变成 API 契约
49+
50+
`packages/protocol/src` 放跨 server/client 的错误和 API 描述。它不应该知道具体的 `SessionRunner`,但可以描述 `SessionNotFoundError` 这样的稳定错误边界。
51+
52+
### 2.3 LLM:给模型世界一个中间表示
53+
54+
`packages/llm/src/schema/messages.ts` 把消息、文本、reasoning、tool-call、tool-result、ToolDefinition 和 LLMRequest 标准化。provider 适配器只需要把 canonical request 翻译成自己的 wire format,再把流翻译回 LLMEvent。
55+
56+
### 2.4 Core:可复用的领域运行时
57+
58+
Core 是真正的“骨骼”:
59+
60+
- `packages/core/src/session`:Session V2、History、Input、Runner、Compaction;
61+
- `packages/core/src/system-context`:上下文 Source、Registry、Baseline;
62+
- `packages/core/src/tool`:跨宿主可用的工具与输出治理;
63+
- `packages/core/src/database`:Effect + Drizzle + SQLite;
64+
- `packages/core/src/project``location``workspace`:作用域与持久化;
65+
- `packages/core/src/provider``catalog`:模型和 provider 的领域表示。
66+
67+
### 2.5 Server / OpenCode:产品化装配
68+
69+
`packages/server` 里是通用 HTTP / Location 中间件;`packages/opencode` 里则是具体的 CLI、旧版服务、配置发现、插件加载、MCP、LSP、路由 handlers 和应用 runtime。这样核心服务可被不同宿主使用,但产品入口仍然集中管理。
70+
71+
## 3. 为什么 TUI 不直接 import 后端
72+
73+
`specs/tui-package.md` 把 TUI 抽取的目标写得很清楚:TUI 通过 `@opencode-ai/sdk` 获取 Session、Message、File、Provider、Agent、Permission 等数据,缺少的能力要先加到 server API 和 generated SDK,而不是直接 import `packages/opencode` 内部实现。
74+
75+
这是一个很实用的架构测试:如果一个 UI 功能必须直接读取后端 service,说明 API 边界还没有表达完整的产品能力。
76+
77+
## 4. Effect Layer 是依赖注入图
78+
79+
`AppLayer` 不是“把很多服务放进数组”。每一个 `LayerNode` 表示一个服务的构造函数和依赖,`Layer.provideMerge` 让共享的 Node / observability / runtime 被合并。效果是:
80+
81+
```text
82+
数据库、FS、配置、认证
83+
→ Project / Provider / Agent
84+
→ Session / Context / LLM / Tools
85+
→ HTTP handlers / CLI / TUI host
86+
```
87+
88+
当测试要替换 Database、LLM 或 Provider 时,不需要改业务函数签名,只需要提供另一层实现。这也是 `Effect.Service` 比全局单例更适合 OpenCode 的原因。
89+
90+
## 5. V1 / V2 为什么会在依赖图中同时出现
91+
92+
当前仓库不是一次性重写,而是在迁移:
93+
94+
- V1 的 `packages/opencode/src/session/*` 仍承担大量产品功能和兼容入口;
95+
- V2 的 `packages/core/src/session/*` 把 durable inbox、Location-scoped runner、Context Epoch 和 Event V2 作为新的规范化方向;
96+
- `packages/opencode/src/event-v2-bridge.ts`、projector 和 API 层负责把两边逐步接起来。
97+
98+
这解释了为什么阅读时会遇到 `SessionV1``SessionV2``MessageV2` 和两套 event schema。正确做法不是把重复代码马上合并,而是先确认每个类型属于哪个状态模型。
99+
100+
## 6. 三个可迁移的设计方法
101+
102+
### 方法 1:依赖漏斗
103+
104+
让底层只依赖稳定数据和小接口,让上层决定宿主、显示和网络。越靠近 UI,依赖越多;越靠近 Schema,依赖越少。
105+
106+
### 方法 2:语义边界优先于文件边界
107+
108+
`SessionRunner``SystemContextRegistry``ToolRegistry` 都不是单个文件,而是由 schema、store、service、event 和测试共同组成的边界。看源码要按语义组,而不是只看文件名。
109+
110+
### 方法 3:用 API 反推领域模型
111+
112+
`SessionApi` 的路由反向看:如果 API 要支持 prompt、abort、compact、revert、permission、find file、share,那么后端必然需要对应的 Session、Input、Compaction、Revert、Permission、File 和 Share 服务。
113+
114+
## 本章小结
115+
116+
OpenCode 的分层不是“为了好看”,而是为了解决三个变化维度:模型供应商会变、客户端宿主会变、Session 执行语义会迁移。Schema / Protocol 稳定跨边界,Core 承载领域状态,Server 提供运行时,UI 只消费契约。
117+
118+
### 源码锚点
119+
120+
- [`AGENTS.md`](https://github.com/anomalyco/opencode/blob/dev/AGENTS.md)
121+
- [`specs/tui-package.md`](https://github.com/anomalyco/opencode/blob/dev/specs/tui-package.md)
122+
- [`packages/opencode/src/effect/app-runtime.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/effect/app-runtime.ts)
123+
- [`packages/schema/src/session-event.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/schema/src/session-event.ts)

0 commit comments

Comments
 (0)