# OpenCode 源码解读

> 逐层拆读开源编码 Agent「OpenCode」的 TypeScript 源码——连 TUI 也是客户端的服务端架构、Effect 化的内核、两代会话引擎并存，一边讲怎么用，一边讲怎么实现。

- 作者：David（道雾轩）
- 最后更新：2026-09-19
- 原文：https://daiw.net/manual/opencode
- 转载与引用：请注明出处并附原文链接（https://daiw.net/about/copyright）

[OpenCode](https://github.com/anomalyco/opencode)（命令行叫 `opencode`）自称“开源的 AI 编码 Agent”：默认形态是跑在终端里的全屏 TUI，能读写代码、跑 shell、搜文件、抓网页，并根据反馈决定下一步。它不绑定任何一家模型厂商——提供方与模型清单来自开放目录 models.dev，调用默认走 Vercel AI SDK。同一个核心还以 Web 界面、Electron 桌面端、VS Code 扩展、ACP 编辑器集成、GitHub Actions 里的 agent 等形态出现。

项目最早在 `sst/opencode` 下开发，现已迁到 `anomalyco/opencode`（旧地址自动跳转）。整个项目用 **TypeScript** 写成、跑在 **Bun** 上，是一个 36 个 workspace 包的 monorepo：全仓不含测试近 **50 万行**，其中与 agent 直接相关的十来个包（CLI 与服务端、Effect 内核、模型协议层、TUI、契约层等）合计约 **17 万行**。

这个专栏把它**逐层拆开读**。

## 源码基准

本专栏所有引用以下述快照为准：

| 项 | 值 |
| --- | --- |
| 仓库 | [github.com/anomalyco/opencode](https://github.com/anomalyco/opencode) |
| commit | `3104c1428ec91f809e5ab86631300de41eb6952e`（即 `v1.18.30` 标签） |
| 版本 | `opencode` v1.18.30 |
| 快照日期 | 2026-09-09 |
| 许可 | MIT |
| 运行要求 | 从源码开发需 Bun `1.3.14`；发行版是 Bun 编译出的独立可执行文件 |

所有路径都相对仓库根，例如 `packages/opencode/src/session/prompt.ts`；文中指向 GitHub 的源码链接都固定在 `v1.18.30` 标签上。

<Callout type="info">
  **读者预设**：默认你会读 TypeScript（`async`、泛型、联合类型、`interface`）。OpenCode 重度使用 [Effect](https://effect.website)，这套写法对多数读者是陌生的，[怎么读这份源码](https://daiw.net/manual/opencode/reading-the-source)会先把必需的惯例讲清楚，之后的篇目只在关键处就地补一句。读过[《从 LLM 到 Coding Agent》](https://daiw.net/manual/llm-to-agent)会更顺，但不是硬前提。
</Callout>

## 这个专栏怎么读

每一章尽量走**两条线**：

- **怎么用**——这个功能在 `opencode` 里长什么样、怎么配置；
- **怎么实现**——翻到对应包的源码，看它在底层到底怎么做。

侧重**结构与数据流**：讲清“一件事经过哪些包、数据怎么流、边界契约是什么”，而不是逐行贴代码——关键处才引一小段源码点睛。

## 和另外四本的关系

站里已有四本相关专栏，五本互为印证：

| 专栏 | 视角 |
| --- | --- |
| [《从 LLM 到 Coding Agent》](https://daiw.net/manual/llm-to-agent) | **教学最小实现**：一个 Coding Agent 在裸 LLM API 之上该做哪些事 |
| [《Grok Build 源码解读》](https://daiw.net/manual/grok-build) | **Rust 生产实现**：xAI 的终端 Agent 怎么做 |
| [《Kimi Code 源码解读》](https://daiw.net/manual/kimi-code) | **TypeScript 生产实现**：DI × Scope 引擎、vendored 的 pi-tui 与自研嵌入式数据库 |
| [《MiMo Code 源码解读》](https://daiw.net/manual/mimo-code) | **在 OpenCode 之上二次开发**：小米为长程任务加的检查点、分层记忆、Actor 与 Workflow |
| 本专栏 | **TypeScript + Effect 生产实现**：服务端优先、提供方中立 |

OpenCode 与 Kimi Code 同为 TypeScript，却走了两条很不一样的路：Kimi Code 用 DI 容器组织服务，TUI 框架和数据库都自己写；OpenCode 把几乎所有服务都写成 Effect 的 Service 与 Layer，TUI 基于 OpenTUI + SolidJS，存储直接用 SQLite + Drizzle。MiMo Code 则是小米从 OpenCode v1.14.19 分叉出来独立演进的版本，底座与本专栏相同，新加的是检查点、分层记忆、Actor 与 Workflow 等面向长程任务的机制。几本对照着读，最能看出**哪些是本质约束，哪些只是工程口味**。

## 它有什么特别的

读之前先知道这份源码值得看的地方：

- **连 TUI 也是客户端**。默认的 `opencode` 命令把服务端放进一个 Bun Worker 线程，TUI 只通过 SDK 发 HTTP 请求（进程内经 RPC 隧道转发）并订阅事件流；加上 `--port` 就变成真正对外监听的服务。Web、桌面端走的是同一套 API。
- **Effect 全面化**。依赖注入、并发、资源作用域、错误通道都交给 Effect v4（beta）：用 `Context.Service` 与 `Layer` 组装服务，用 `Effect.fn("Domain.method")` 给每个操作命名——读懂这套惯例是读懂源码的前提。
- **两代会话内核并存**。`packages/opencode/src/session` 里是现役的 V1 主循环；`packages/core/src/session` 里是正在迁移的 V2：持久化收件箱、“受理”与“执行”分离、Context Epoch。同一套能力的两种架构可以直接对比。
- **提供方中立**。模型目录来自 models.dev，调用默认走 Vercel AI SDK，再用近两千行的 `provider/transform.ts` 抹平各家差异；另有自研的 `packages/llm` 按“协议、端点、认证、分帧”四个维度重新建模模型调用，以实验开关的形式逐步接入。
- **契约驱动的 API 分层**。`schema → protocol → server / client` 的依赖方向写进了仓库规则，OpenAPI 文档与客户端代码都从 HttpApi 定义生成。
- **可扩展面很宽**。配置文件、用 Markdown 定义的 agent 与命令、Skills、MCP、LSP、格式化器、npm 插件（钩子与自定义工具）一应俱全。

## 路线图

- **第 0 部分 · 导论与全景**——它是什么、Monorepo 全景、怎么读这份源码、启动链路、一条消息的生命周期。
- **第 1 部分 · 会话引擎**——会话与消息模型、主循环、流处理器、系统提示词、上下文压缩、模型调用层、快照与撤销。
- **第 2 部分 · 工具系统**——工具契约与注册表、文件工具、Shell、子代理与待办、联网与 Code Mode、权限。
- **第 3 部分 · 模型与提供方**——models.dev 目录、提供方适配层、认证与提供方插件、`packages/llm` 协议层。
- **第 4 部分 · 扩展机制**——配置系统、Agent 与命令与 Skills、MCP、LSP 与格式化器、插件。
- **第 5 部分 · 服务形态与客户端**——HTTP 服务端、事件与同步、SDK 与协议、终端界面、ACP 与 GitHub 等其他入口。
- **第 6 部分 · V2 架构演进**——Effect 运行时与 Location、V2 会话内核、V2 工具与权限、SQLite 存储与事件投影、新 CLI 与守护进程、收尾复盘。

准备好了，就从 [OpenCode 是什么](https://daiw.net/manual/opencode/what-is-opencode) 开始。

## 本专栏篇目

- [OpenCode 源码解读](https://daiw.net/manual/opencode.md): 逐层拆读开源编码 Agent「OpenCode」的 TypeScript 源码——连 TUI 也是客户端的服务端架构、Effect 化的内核、两代会话引擎并存，一边讲怎么用，一边讲怎么实现。

### 第 0 部分 · 导论与全景

- [OpenCode 是什么](https://daiw.net/manual/opencode/what-is-opencode.md): 产品全貌：安装方式、七个内置 Agent 与各自的权限、从 TUI 到 ACP 的多种入口、不写死的模型提供方，以及与另外四本专栏的对照。
- [Monorepo 全景](https://daiw.net/manual/opencode/monorepo-map.md): 36 个 workspace 包的职责与规模、schema → protocol → server 的依赖方向、V1 与 V2 各住在哪些包，以及撑起这一切的关键外部依赖。
- [怎么读这份源码](https://daiw.net/manual/opencode/reading-the-source.md): 读 OpenCode 前要掌握的 Effect 惯例（Service、Layer、Effect.fn、InstanceState）、LayerNode 依赖图、实验开关表、代码风格与测试约定，以及一条推荐的阅读路线。
- [启动链路](https://daiw.net/manual/opencode/startup.md): 从 yargs 入口到 TuiThreadCommand：Worker 线程里的服务端、RPC fetch 隧道与事件通道，以及 attach、serve、web、run、--mini 各自怎么起。
- [一条消息的生命周期](https://daiw.net/manual/opencode/message-lifecycle.md): 在默认 TUI 里敲下回车之后：SDK 请求穿过 RPC 隧道进入 HttpApi，SessionPrompt 主循环调模型、跑工具、问权限，部件更新再经事件总线回到屏幕。

### 第 1 部分 · 会话引擎

- [会话与消息模型](https://daiw.net/manual/opencode/session-model.md): Session、消息与 12 种 Part 的结构，ID 生成规则，会话状态与待办，以及它们如何经事件落进 SQLite。
- [主循环 SessionPrompt](https://daiw.net/manual/opencode/prompt-loop.md): 四个入口、用户输入的展开、runLoop 的每一步，以及“一个会话只跑一个循环”的并发模型。
- [流处理器与工具调用生命周期](https://daiw.net/manual/opencode/processor.md): 处理器如何把 LLMEvent 流翻译成 Part：工具调用的生命周期、步末结算与溢出判定、重试、死循环检测与中断收尾。
- [系统提示词与指令文件](https://daiw.net/manual/opencode/system-prompt.md): 按模型家族挑选提示词模板，环境信息、Skills 与 MCP 说明如何拼接，AGENTS.md 等指令文件的发现规则，以及 plan 模式提醒的两种注入方式。
- [上下文压缩](https://daiw.net/manual/opencode/compaction.md): 修剪旧工具输出、由 compaction agent 生成锚定摘要、按预算原样保留最近几轮，以及压缩请求如何作为一条消息在主循环里流转。
- [模型调用层：AI SDK 与原生双轨](https://daiw.net/manual/opencode/llm-bridge.md): LLM.stream 如何组装请求、参数与请求头，默认的 Vercel AI SDK 链路与可选的 @opencode-ai/llm 原生链路如何分流，又如何汇合成同一种 LLMEvent 流。
- [快照与撤销](https://daiw.net/manual/opencode/snapshot-revert.md): 基于影子 git 仓库的文件快照：存放位置与各方法的实现，何时打快照，以及 revert、unrevert 与延迟清理如何配合完成撤销。

### 第 2 部分 · 工具系统

- [工具契约与注册表](https://daiw.net/manual/opencode/tool-contract.md): Tool.define 的契约与包装、ToolRegistry 的内置清单与过滤规则、每轮如何把工具交给模型，以及截断、无效调用和工作区边界。
- [文件工具：读、写、改](https://daiw.net/manual/opencode/file-tools.md): read 的分页、嗅探与嵌套指令文件，edit 的九级匹配级联，write 与 apply_patch 的写入流程，以及基于 ripgrep 的 glob 与 grep。
- [Shell 工具](https://daiw.net/manual/opencode/shell-tool.md): 工具 ID 为何仍叫 bash、shell 的选择与说明模板、超时中止与输出截断，以及用 tree-sitter 解析命令、按命令元数生成“总是允许”规则。
- [子代理、待办与提问](https://daiw.net/manual/opencode/task-and-todo.md): task 工具如何派生子会话、继承权限并回传结果，前台与后台子代理，以及 todowrite、question、plan_exit、skill 这几个组织与交互型工具。
- [联网工具与 Code Mode](https://daiw.net/manual/opencode/web-and-codemode.md): webfetch 的内容协商、大小与超时限制和 HTML 转 Markdown，websearch 的启用条件与 Exa、Parallel 分流，以及让模型写程序编排 MCP 工具的 Code Mode。
- [权限系统](https://daiw.net/manual/opencode/permission.md): V1 权限的规则模型与“最后匹配优先”的求值、ask 与 reply 的挂起和联动、规则的来源与叠加顺序、工具可见性过滤、doom_loop、--auto 的真实语义，以及 TUI 授权框。

### 第 3 部分 · 模型与提供方

- [提供方目录与 models.dev](https://daiw.net/manual/opencode/provider-catalog.md): models.dev 目录从哪来、怎么刷新，Provider 状态如何由目录、环境变量、凭据、插件与配置层层叠加，以及 getLanguage 如何拿到 AI SDK 模型。
- [提供方适配层](https://daiw.net/manual/opencode/provider-transform.md): transform.ts 如何按提供方规整消息、打缓存断点、设定采样与推理参数、清洗工具 schema、限定输出上限，以及 error.ts 如何识别上下文溢出。
- [认证与提供方插件](https://daiw.net/manual/opencode/auth-and-plugins.md): 凭据如何存进 auth.json，登录命令与 /connect 如何驱动插件的 auth 钩子，GitHub Copilot 与 ChatGPT 订阅等 OAuth 流程怎样实现，以及 OpenCode 自己的 console 账号做什么用。
- [packages/llm：模型协议层](https://daiw.net/manual/opencode/llm-package.md): Schema 优先的 @opencode-ai/llm 如何用 Protocol、Endpoint、Auth、Framing 四轴组装路由，统一 LLMEvent 事件流与提示缓存，opencode 怎样按开关接入，以及下一代 @opencode-ai/ai 的设计草案。

### 第 4 部分 · 扩展机制

- [配置系统](https://daiw.net/manual/opencode/config.md): 九层配置来源与合并顺序、JSONC 与变量替换、Markdown 定义的 agent 与命令、独立的 tui.json，以及通往 V2 的兼容层。
- [Agent、命令与 Skills](https://daiw.net/manual/opencode/agents-and-commands.md): 七个内置 agent 的权限设计、Markdown 定义与覆盖规则、命令模板的展开顺序，以及 SKILL.md 的发现与暴露方式。
- [MCP 集成](https://daiw.net/manual/opencode/mcp.md): 本地 stdio 与远程 HTTP / SSE 服务器的连接与五种状态、工具与资源如何交给模型、带 CSRF 校验的 OAuth 流程，以及 opencode mcp 命令。
- [LSP 与格式化器](https://daiw.net/manual/opencode/lsp-and-format.md): 两个默认关闭的子系统：38 个内置语言服务器的根目录探测与按需获取、推拉结合的诊断、诊断如何回到模型，以及 26 个格式化器的启用条件。
- [插件系统](https://daiw.net/manual/opencode/plugins.md): 插件的三个来源与加载流水线、服务端钩子清单与串行触发语义、自定义工具、TUI 插件，以及 V2 插件 API 的转向。

### 第 5 部分 · 服务形态与客户端

- [HTTP 服务端](https://daiw.net/manual/opencode/server.md): 所有前端共用的一套 Effect HttpApi：路由分组、实例定位、认证与 CORS、端口回退与 mDNS、生命周期，以及 TUI 如何不开端口复用同一个 handler。
- [事件与同步](https://daiw.net/manual/opencode/events-sync.md): EventV2 的定义、持久化与投影，EventV2Bridge 与 GlobalBus 的进程级广播，几条 SSE 路由的区别，以及 TUI 如何把事件流折叠成本地状态。
- [SDK、协议与客户端](https://daiw.net/manual/opencode/sdk-and-protocol.md): 旧 JS SDK 由 openapi.json 生成，新契约拆成 schema、protocol、server、client 四层；附一个用 SDK 驱动 opencode 的最小例子。
- [终端界面](https://daiw.net/manual/opencode/tui.md): 终端界面包 @opencode-ai/tui 的结构：OpenTUI 渲染器与 SolidJS 组件、Provider 组合、会话视图与输入框、键位主题与插件插槽，以及从主包拆出的十个阶段与 --mini 模式。
- [ACP、GitHub 与其他入口](https://daiw.net/manual/opencode/frontends.md): opencode 作为 ACP agent 被编辑器驱动、在 GitHub Actions 里响应评论、用 run 命令脚本化，以及会话分享、Web、桌面端与 VS Code 扩展的接入方式。

### 第 6 部分 · V2 架构演进

- [Effect 运行时与 Location](https://daiw.net/manual/opencode/effect-runtime.md): 为什么整体迁向 Effect，LayerNode 依赖图如何编译成 Layer，InstanceState 与 Location 如何按目录隔离服务，以及回调怎样安全地回到 Effect。
- [V2 会话内核](https://daiw.net/manual/opencode/session-v2.md): 持久化收件箱把提示的受理与执行拆开，协调器按会话串行调度，SessionRunner 每轮只发一次 llm.stream，再加上 Context Epoch 与自动压缩。
- [V2 工具与权限](https://daiw.net/manual/opencode/tools-v2.md): 只有一个执行体的 Tool.make 契约、进程级与 Location 级两层注册表、结算边界上的统一输出限界，以及无规则匹配时兜底为 ask、可保存项目级批准的 PermissionV2。
- [存储：SQLite 与事件投影](https://daiw.net/manual/opencode/storage.md): 数据库文件放在哪、bun 与 node 两套驱动如何切换、19 张表与 38 个迁移怎么组织，以及 EventV2 如何把事件、序号和投影放进同一个事务。
- [新 CLI 与守护进程](https://daiw.net/manual/opencode/cli-daemon.md): packages/cli 的 lildax 如何用 Effect CLI 声明命令并按需加载处理器，如何在后台拉起、认证并复用 V2 服务，以及它与旧 opencode CLI 共用同一套 TUI。
- [收尾复盘](https://daiw.net/manual/opencode/recap.md): 全书要点串联：一条消息穿过的各层、二十条值得记住的设计、与另外三本的四方对照，以及可迁移的工程经验和这份源码的短板。
