主题
第二章 Opencode 核心架构
← 返回目录 | 上一章:概述 | 下一章:Opencode Agent 系统 →
2.1 项目结构:Effect-TS 驱动的 Monorepo
Opencode 采用 pnpm monorepo 组织,核心包位于 packages/opencode/:
opencode/
├── packages/
│ ├── opencode/ # 核心 CLI(本文重点)
│ │ └── src/
│ │ ├── agent/ # Agent 定义与模型映射
│ │ ├── session/ # 会话管理、消息处理、压缩
│ │ ├── tool/ # 工具注册与执行
│ │ ├── provider/ # 20+ LLM 提供商集成
│ │ ├── mcp/ # MCP 协议客户端
│ │ ├── lsp/ # 22+ 语言服务器管理
│ │ ├── skill/ # Skill 文件发现
│ │ ├── config/ # 配置加载与合并
│ │ └── plugin/ # 插件加载器
│ ├── app/ # Web 客户端 (Next.js)
│ ├── console/ # 后端 API 服务
│ ├── desktop/ # Electron 桌面客户端
│ ├── plugin/ # 插件 SDK 类型定义
│ └── sdk/ # JavaScript SDK2.2 Effect-TS:为什么选择它?
Opencode 的一个显著技术决策是全面采用 Effect-TS 作为核心运行时框架,而非传统的 async/await 风格。这决定了整个代码库的编程范式。
Effect-TS 是一个 TypeScript 函数式副作用管理库,核心理念是将副作用(IO、错误、依赖)编码在类型系统中。你会在源码中大量看到以下模式:
typescript
// 典型的 Effect-TS 生成器模式
const result = yield* Effect.gen(function* () {
const config = yield* Config; // 依赖注入
const session = yield* SessionService; // 服务解析
const result = yield* session.create(); // 副作用操作
return result;
});为什么这很重要? 因为 AI 编程工具的核心挑战恰好是:
- 多层依赖管理:LLM 提供商、MCP 服务器、LSP 进程、文件系统、数据库——每一层都可能失败
- 可组合的错误处理:工具执行失败不应该崩溃整个会话,而是需要优雅降级
- 生命周期管理:LSP 服务器、MCP 连接、子进程都需要启动和清理
- 可测试性:Effect 的 Layer 机制天然支持依赖替换
Opencode 使用 Effect 的 Layer 系统构建服务依赖图:
typescript
// 简化的依赖注入示例
const AppLayer = Layer.mergeAll(
ConfigLayer, // 配置服务
DatabaseLayer, // SQLite 数据库
ProviderLayer, // LLM 提供商
SessionLayer, // 会话管理
ToolLayer, // 工具注册
McpLayer, // MCP 服务器管理
LspLayer, // LSP 服务器管理
PluginLayer, // 插件加载
);2.3 Provider 系统:统一的模型接入层
Opencode 通过 Vercel AI SDK 统一了 20+ LLM 提供商的接入。每个提供商以 loader 函数注册:
typescript
// provider/provider.ts 中的提供商列表(部分)
const BUNDLED_PROVIDERS = [
"anthropic", // Claude 系列
"openai", // GPT 系列
"google", // Gemini 系列
"aws", // Bedrock
"azure", // Azure OpenAI
"xai", // Grok 系列
"groq", // Groq 加速推理
"mistral", // Mistral 系列
"deepseek", // DeepSeek
"fireworks", // Fireworks AI
// ... 共 20+ 个
];每个提供商实现统一接口,包括:
- 模型发现:
models()返回可用模型列表(含 ID、名称、价格) - 聊天创建:
chat()返回 AI SDK 兼容的语言模型实例 - 成本追踪:输入/输出 token 的单价,用于实时费用计算
模型选择链路:用户配置 → Agent 默认模型 → Provider 解析 → AI SDK 实例化。每个 Agent 可以绑定不同的模型,OmO 正是利用这一点为每个 Agent 分配最适合的模型。
2.4 插件系统:8 个 Hook 接口
Opencode 的插件系统是 OmO 存在的基础。插件通过 npm 包分发,暴露一个工厂函数:
typescript
// 插件接口(来自 packages/plugin/)
interface OpenCodePlugin {
name: string;
config?: (input) => config; // 修改配置
tool?: () => Tool[]; // 注册自定义工具
"chat.message"?: (msg) => msg; // 拦截消息
"chat.params"?: (params) => params; // 修改 LLM 请求参数
"chat.headers"?: (headers) => headers; // 修改 HTTP 头
event?: (event) => void; // 监听事件流
"tool.execute.before"?: (tool, input) => any; // 工具执行前拦截
"tool.execute.after"?: (tool, result) => any; // 工具执行后拦截
"experimental.chat.messages.transform"?: (...) => messages; // 变换消息序列
}关键设计:插件的 config Hook 可以完全重写 Opencode 的配置——包括 Agent 定义、模型绑定、工具白名单。OmO 正是通过这个 Hook 注入了 12 个 Agent 和 26 个工具的完整定义。
2.5 事件总线架构
2.5.1 Effect PubSub——类型安全的事件系统
Opencode 的事件系统基于 Effect-TS 的 PubSub 原语构建,提供了类型安全的发布-订阅机制。这是整个平台的"神经系统"——所有组件间通信都通过事件总线完成。
架构设计:
typescript
// 双通道设计
type State = {
wildcard: PubSub<Payload> // 通配订阅:接收所有事件
typed: Map<string, PubSub<Payload>> // 类型订阅:按事件类型分发
}每个事件发布时同时推送到两个通道:
typescript
function publish(def, properties) {
const payload = { type: def.type, properties }
// 1. 推送到类型特定的 PubSub(如果有订阅者)
const ps = state.typed.get(def.type)
if (ps) yield* PubSub.publish(ps, payload)
// 2. 推送到通配 PubSub(所有订阅者都能收到)
yield* PubSub.publish(state.wildcard, payload)
// 3. 推送到全局 EventEmitter(跨实例)
GlobalBus.emit("event", { directory, payload })
}2.5.2 事件定义——Zod Schema 驱动
每个事件通过 BusEvent.define() 工厂函数定义,使用 Zod Schema 确保类型安全:
typescript
// 事件定义示例
const Idle = BusEvent.define("session.idle", z.object({
info: SessionInfo,
}))
const Error = BusEvent.define("session.error", z.object({
info: SessionInfo,
error: z.unknown(),
}))所有已定义的事件会注册到全局 registry,并通过 BusEvent.payloads() 生成 Zod 判别联合类型(Discriminated Union),用于运行时验证和 TypeScript 类型推导。
2.5.3 事件全景——42 个事件类型
Opencode 定义了 42 个事件类型,覆盖系统各层面:
| 领域 | 事件 | 用途 |
|---|---|---|
| Session | session.status, session.idle, session.diff, session.error, session.compacted | Session 状态机,驱动 OmO 续航/恢复 |
| 消息 | message.partDelta | 流式消息增量(用于 TUI 实时渲染) |
| Todo | todo.updated | Todo 状态变更(驱动 Boulder 续航) |
| 文件 | file.edited, file.watcher.updated | 文件修改事件(驱动 LSP 诊断) |
| PTY | pty.created, pty.updated, pty.exited, pty.deleted | 终端进程生命周期 |
| 权限 | permission.asked, permission.replied | 用户权限交互 |
| 问题 | question.asked, question.replied, question.rejected | Agent 向用户提问 |
| MCP | mcp.tools.changed, mcp.browser.open.failed | MCP 工具变更 |
| LSP | lsp.updated, lsp.diagnostics | 语言服务器状态 |
| 项目 | project.updated, vcs.branch.updated | 项目和版本控制 |
| 工作树 | worktree.ready, worktree.failed | Git 工作树准备状态 |
| 命令 | command.executed | 命令执行记录 |
| TUI | tui.prompt.append, tui.command.execute, tui.toast.show, tui.session.select | UI 交互 |
| 安装 | installation.updated, installation.update.available | 自动更新 |
| 服务器 | server.connected, server.instance.disposed, global.disposed | 服务器生命周期 |
| 控制面板 | workspace.ready, workspace.failed | 工作区状态 |
| IDE | ide.installed | IDE 扩展安装 |
2.5.4 三层传播机制
┌─────────────────────────────────────────────┐
│ 第1层: Effect PubSub (实例内) │
│ typed Map + wildcard → 同实例订阅者 │
├─────────────────────────────────────────────┤
│ 第2层: GlobalBus (跨实例) │
│ Node.js EventEmitter → 所有打开的项目实例 │
├─────────────────────────────────────────────┤
│ 第3层: SSE Endpoint (跨进程) │
│ HTTP Server-Sent Events → Web/Desktop 客户端│
└─────────────────────────────────────────────┘- 实例内:通过
InstanceState确保每个项目目录有独立的 PubSub 实例,项目 A 的事件不会干扰项目 B - 跨实例:
GlobalBus(基于 Node.js EventEmitter)将事件广播到同进程的所有实例 - 跨进程:SSE 端点将所有事件序列化为 JSON 流,推送给远程客户端
2.5.5 OmO 如何利用事件总线
OmO 的 event Hook 订阅 Opencode 的通配事件流,是整个插件系统的"感知层":
typescript
// OmO 的 plugin event handler 接收所有 Opencode 事件
event: async ({ event }) => {
// 分发给 48 个 Hook 中所有注册了 event 处理器的 Hook
for (const hook of eventHooks) {
await hook.event({ event })
}
}关键的 OmO 事件消费者:
- Atlas Hook:监听
session.idle驱动任务续航 - Ralph Loop:监听
session.idle驱动迭代循环 - Runtime Fallback:监听
message.updated和session.status检测 API 错误 - Session Recovery:监听
session.error触发自动恢复 - Background Notification:监听后台任务完成事件通知用户
- Think Mode:监听
session.deleted清理状态
2.6 基础设施补充
前文聚焦于事件总线和 Provider 等上层系统。本节补充 Opencode 平台层中同样重要的基础设施模块。
2.6.1 配置系统
Opencode 的配置采用三层合并架构(config/config.ts,1576 行),优先级从低到高:
层级 1: 内置默认值(代码中的 Zod schema 默认值)
↓ 被覆盖
层级 2: 全局用户配置(~/.config/opencode/config.jsonc)
↓ 被覆盖
层级 3: 项目配置(项目根目录 .opencode/config.jsonc)
↓ 被覆盖
层级 4: 企业托管配置(/Library/Application Support/opencode/ 或 /etc/opencode/)关键设计:
- JSONC 格式:使用
jsonc-parser解析,允许注释——对复杂配置友好 - Zod Schema 验证:所有配置字段都有 Zod schema 定义,确保类型安全
- 数组合并策略:
plugin数组在多层间拼接(而非替换),确保全局和项目的插件都被加载 - 深度合并:对象字段递归合并(
mergeDeep),数组字段合并去重 - 热重载:配置变更通过事件总线广播,相关服务自动响应
配置管理的是几乎所有可变行为:Agent 定义、Provider 认证、工具开关、MCP 服务器、Skill 路径、LSP 服务器映射、权限策略、TUI 主题等。
2.6.2 TUI/CLI 客户端架构
Opencode 的终端界面(cli/cmd/tui/app.tsx,913 行)基于 SolidJS + Ink 构建——这是一个非常规但高效的选择:
- SolidJS:用于状态管理和响应式 UI(
createSignal,createEffect,createMemo) - Ink/opentui:将 SolidJS 组件渲染到终端(而非浏览器 DOM)
- 路由系统:
RouteProvider管理页面导航(Home、Session 等) - 对话框系统:模型选择、MCP 配置、主题切换、命令面板等弹窗
- 键盘快捷键:
KeybindProvider统一的快捷键管理
TUI 架构层次:
┌──────────────────────────────────────┐
│ App.tsx (根组件) │
├─ ArgsProvider → SDKProvider │
├─ SyncProvider → LocalProvider │
├─ ThemeProvider → TuiConfigProvider │
├─ DialogProvider → CommandProvider │
├─ KeybindProvider → ExitProvider │
├─ ToastProvider → PromptHistoryProv │
└─ RouteProvider → Home | Session │Worker 架构:TUI 运行在一个独立的 Worker 线程(worker.ts)中,通过 RPC 与主进程通信。这隔离了 UI 渲染和 LLM 处理,防止长时间运行的 LLM 调用阻塞 UI 更新。
2.6.3 Provider 适配与消息变换
不同 LLM 提供商的 API 存在细微但关键的差异。ProviderTransform(provider/transform.ts,1045 行)负责统一处理这些差异:
消息规范化(normalizeMessages):
- Anthropic:拒绝空内容消息——系统自动过滤空字符串和空 text/reasoning 部分
- Claude:工具调用 ID 中不允许特殊字符——系统自动替换为下划线
- 通用:清理尾部空助手消息、合并连续同角色消息
Schema 适配:
- 按提供商映射 SDK key(
@ai-sdk/anthropic→"anthropic"、@ai-sdk/openai→"openai"等) - 设置
OUTPUT_TOKEN_MAX(默认 32,000)限制最大输出 token 数 - 模态能力检测(image/audio/video/pdf),对不支持的模态自动降级
为什么这很重要:没有这一层,Agent 在切换模型时会频繁遇到 API 错误。Provider Transform 让 Agent 系统可以透明地使用 20+ 个提供商的任意模型,而不需要 Agent 自身关心提供商差异。
2.6.4 文件监视器
FileWatcher(file/watcher.ts)使用 @parcel/watcher 原生绑定监控项目文件变化:
- 平台适配:macOS 使用 FSEvents、Linux 使用 inotify、Windows 使用 Windows 原生 API
- 事件类型:add(新建)、change(修改)、unlink(删除)
- 事件总线集成:文件变化通过
file.watcher.updated事件发布到全局 Bus - Git 忽略支持:自动排除
.gitignore规则匹配的文件 - 受保护路径:对 Opencode 自身的管理文件(如 SQLite 数据库)不触发变更事件
- InstanceState 集成:每个打开的项目维护独立的 watcher 实例,项目关闭时自动清理
文件监视器的输出驱动了多个下游功能:LSP 服务器的文件同步、VCS 状态更新、TUI 文件树刷新等。
2.6.5 Git/VCS 集成
Opencode 通过多个模块实现 Git 集成(非通过 Git 工具给 Agent 用,而是平台自身需要的 VCS 感知能力):
- VCS Service(
project/vcs.ts):检测当前分支、监听分支变化(通过 FileWatcher 间接触发)、发布vcs.branch.updated事件 - Git Utility(
util/git.ts):底层 git 命令执行封装 - Worktree 管理(
worktree/index.ts):管理 git worktree(多分支并行开发场景),创建/列出/删除 worktree 目录
这些集成使得 Opencode 能够在系统提示中动态注入当前 Git 状态(分支名、是否有未提交变更等),帮助 Agent 理解项目上下文。
2.6.6 自动更新与安装管理
Installation(installation/index.ts)管理 Opencode 自身的版本更新:
- 支持多种安装方式检测(npm、bun、homebrew、手动安装等)
- 版本更新检查与通知
- 发布类型区分(stable、canary 等)
- 更新事件通过 Bus 广播,TUI 可显示更新提示