opencode — 架构与原理
30 秒导读: opencode 是一个跑在终端里的开源 AI 编码 agent。你打一句"帮我修这个 bug",它会自己读文件、改代码、跑命令,边干边问你要不要批准危险操作。它的真正本体是一个 Effect-TS 写的服务端(会话循环 + 工具 + 权限),终端 UI 只是众多客户端之一。
1. 这是什么(零基础也能懂)
-
一句话定义: opencode 是一个开源的、命令行里的 AI 编码助手——把大模型接上你的真实代码库,让它能读文件、改文件、跑 shell、查网页,自动把一个编码任务做到底。
-
解决什么问题 / 给谁用: 假设你在终端里想让 AI 帮你改一个大项目的代码。你不想把文件一个个复制粘贴进聊天框,也不想 AI 在你不知情时
rm -rf。opencode 给你:① 一个能直接操作你工作区的 agent;② 每个危险动作前的批准闸门;③ provider 无关(Anthropic / OpenAI / Google / 本地都行)。给的人:命令行重度用户、想要可审计/可自托管编码 agent 的工程师。 -
它能做什么(功能):
- 多轮自主循环:读 → 改 → 跑 → 看结果 → 再改,直到任务收敛。
- 内置工具:
read/edit/write/grep/glob/shell/webfetch/task(派生子 agent)等。 - 细粒度权限:对每个工具、每个文件路径、每条 bash 命令单独决定 allow/ask/deny。
- 客户端多样:终端 TUI、桌面 app、HTTP/SDK,共享同一个服务端。
- 长会话不爆:上下文快溢出时自动总结历史(compaction)。
-
用起来什么样: 一段最小的命令行交互——
# 安装后,在你的项目目录里
$ opencode
> 帮我把 src/auth.ts 里的回调改成 async/await
# opencode 会:读 auth.ts → 提出 edit → (若权限是 ask)弹出批准 → 写盘 → 可能跑测试
# 你也可以一次性跑(非交互):
$ opencode run "列出所有 TODO 注释并归类"
- 一句话直觉/类比: 把它想成"一个住在你终端里、有手有脚的结对程序员":模型是大脑(决定做什么),工具是手脚(真正动文件/跑命令),权限系统是"动手前先举手问一声"的礼貌,而服务端是把这一切编排起来的神经中枢。
2. 顶层全景(它大概怎么转)
2.1 client / server 拆分
opencode 最重要的架构决定:它不是一个单体 CLI,而是一个服务端 + 一群客户端。所有真正干活的逻辑(会话、工具、权限)都在 packages/opencode/src 的服务端;TUI、桌面、SDK 都通过 HTTP API 连上来。
┌─────────── 客户端们 ───────────┐
终端 TUI ──┐ │
桌面 App ──┼──► HTTP / WebSocket ──► opencode 服务端
SDK / 脚本 ┘ (packages/server) (packages/opencode/src)
│
┌──────────────────────┼───────────────────────┐
▼ ▼ ▼
会话循环 工具系统 权限闸门
(session/) (tool/) (permission/)
│ │ │
▼ ▼ ▼
LLM provider 文件/Shell/网页 ask/allow/deny
(AI SDK / 原生) (真实副作用)
怎么读这张图:左边是"谁在用它",中间是 HTTP 边界,右边是服务端三大支柱。客户端从不直接碰文件,一切经服务端。
2.2 核心部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
| CLI 入口 | 解析子命令(run/serve/tui…),启动对应流程 | packages/opencode/src/index.ts |
| 会话循环 | 反复"喂模型 → 处理工具 → 再喂"直到收敛 | packages/opencode/src/session/prompt.ts(runLoop) |
| 流处理器 | 把 LLM 的事件流(文本/推理/工具)落成会话 part | packages/opencode/src/session/processor.ts |
| LLM 适配 | 调 provider、把 AI SDK 流归一成 LLMEvent | packages/opencode/src/session/llm.ts |
| 工具注册表 | 收集内置 + 插件 + 自定义工具,按模型/agent 过滤 | packages/opencode/src/tool/registry.ts |
| 工具定义 | 统一的 Tool.define:schema 校验 + 截断 + 执行 | packages/opencode/src/tool/tool.ts |
| 容错编辑 | edit 工具的 9 级降级匹配 | packages/opencode/src/tool/edit.ts |
| 权限服务 | 工具执行前的 allow/ask/deny 裁决 | packages/opencode/src/permission/index.ts |
| 压缩服务 | 上下文溢出时总结/裁剪历史 | packages/opencode/src/session/compaction.ts |
2.3 主线走一遍(高层,不进代码)
一次"帮我改个文件"从头到尾:
- 入口:
opencode run "..."经index.ts派到RunCommand,创建一个会话并写入 user 消息。 - 进循环:
SessionPrompt.runLoop开始while(true),每一轮叫一个"step"。 - 组装上下文:取出历史消息、当前 agent 的系统提示、可用工具集,交给
processor.process。 - 流式调模型:
LLM.stream调 provider,吐回一串事件(文本增量、工具调用、推理…)。 - 处理工具:processor 遇到
tool-call就触发该工具执行;执行前permission.ask闸门可能拦下来问你。 - 回灌结果:工具结果作为新消息塞回历史,循环回到第 3 步,让模型看见结果继续。
- 收敛:当模型这一步既没工具调用、
finish又不是tool-calls时,循环break,返回最后一条 assistant 消息。
3. 阅读地图(建议顺序)
这是个大项目(client/server + Effect-TS + 多子系统),拆成 5 章由浅入深:
- 01-agent-loop.md — 先搞懂"循环怎么转"。这是项目名所指的价值核心:
runLoop+processor。读完你能讲清一次工具调用从请求到生效的全过程。 - 02-tool-system.md — 工具是 agent 的手脚。看
Tool.define的统一外壳、注册表怎么收集/过滤工具、插件工具怎么桥接。 - 03-fuzzy-edit.md — 招牌技术:模型给的
oldString几乎从不和磁盘一字不差,edit 用 9 级降级匹配兜住。最值得带走的精华。 - 04-permission.md — 安全闸门。通配规则裁决、bash 命令拆成"人类可懂前缀"、doom-loop 防呆。
- 05-context-compaction.md — 长会话怎么不爆 token:溢出检测 → 自动总结 → 历史裁剪。
4. 它的几个"不一样"(读前先有数)
-
Effect-TS 满地跑。 几乎每个服务都是
Layer.effect(Service, Effect.gen(...)),函数是Effect.fn("Name")(function*(){...})。yield*在这里类似await,但带依赖注入和可中断性。第一次看会懵,认准"yield*= 取一个能力/拿一个结果"就行。 -
provider 无关靠 AI SDK。 默认走 Vercel 的
ai包(streamText),opencode 把它的fullStream归一成自己的LLMEvent(见session/llm/ai-sdk.ts),还有一条实验性的"原生 runtime"旁路。 -
数据真相在 SQLite。 会话、消息、part 都落 drizzle ORM 管的 SQLite 表(
SessionTable/PartTable),不是纯内存。
5. 代码地图(导航索引)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| CLI 入口与子命令注册 | packages/opencode/src/index.ts | cli, RunCommand, ServeCommand |
| 会话主循环 | packages/opencode/src/session/prompt.ts | runLoop, loop |
| LLM 事件处理 | packages/opencode/src/session/processor.ts | Service, handleEvent, process |
| provider 调用 | packages/opencode/src/session/llm.ts | stream, run |
| 工具统一定义 | packages/opencode/src/tool/tool.ts | define, wrap, InvalidArgumentsError |
| 工具注册表 | packages/opencode/src/tool/registry.ts | Service, tools, fromPlugin |
| 容错编辑 | packages/opencode/src/tool/edit.ts | replace, BlockAnchorReplacer |
| 权限裁决 | packages/opencode/src/permission/index.ts | evaluate, ask, reply |
| 上下文压缩 | packages/opencode/src/session/compaction.ts | buildPrompt, PRUNE_PROTECT |