在读完 Grok Build 的 Rust 巨构和 Pi Agent 的 Steering 双队列之后,今天来看第三个编码 Agent——OpenCode。
OpenCode 由 SST/Anomaly 团队开发,是一个 TypeScript monorepo,2499 个 TS 源文件,20+ 个独立包。它的技术栈选择非常激进:Effect TS 不是「用了一下」,而是深嵌在每一层代码的核心。
TL;DR:OpenCode 的「四轴 LLM 路由」和「Schema First 协议层」设计,是三者中最工程化的。如果你追求代码的可测试性和架构的可维护性,OpenCode 的代码最值得读。
一、Monorepo 全景
packages/
├── opencode/ # 主 CLI Agent —— 双 Agent 系统 (build + plan)
├── core/ # 核心运行时 —— Session, Tool, Config, Permission
├── protocol/ # HTTP API 协议层 —— Effect HttpApi
├── server/ # 服务器实现
├── tui/ # 终端 UI
├── schema/ # 浏览器安全的序列化合约
├── plugin/ # 插件系统(V2 Effect 版 + V1 Promise 版)
├── llm/ # LLM 集成 —— 四轴路由体系
├── console/ # 桌面应用
└── web/ # Web 前端
对比前两篇的架构规模:
| 项目 | 源文件数 | 语言 | 包数 | 核心设计哲学 |
|---|---|---|---|---|
| Grok Build | 2804 .rs | Rust | 50+ crate | 编译期类型安全 + Actor 模型 |
| OpenCode | 2499 .ts | TypeScript | 20+ 包 | Effect TS 代数效应 + Schema First |
| Pi Agent | 859 .ts | TypeScript | 5 包 | 事件驱动 + 分层抽象 |
二、Effect TS:不是「用了」,是「深嵌」
OpenCode 对 Effect TS 的使用深度,是我见过的 TypeScript 项目中最彻底的。
2.1 Context.Service 依赖注入
每一层都通过 Context.Tag 声明服务接口:
// packages/opencode/src/session/llm.ts
class LLM extends Context.Tag("session/LLM")<
LLM,
{ stream: (input: StreamInput) => Effect<StreamResult> }
>() {}
2.2 Effect.gen 代数效应
核心逻辑用 Effect.gen 编写,而不是 async/await:
const program = Effect.gen(function* () {
const llm = yield* LLM;
const session = yield* Session;
const result = yield* llm.stream(input);
return yield* session.save(result);
});
2.3 Schema 优先验证
packages/schema/ 定义了所有数据结构的 Schema,运行时验证 + 编译时类型推导:
export const AgentInfo = Schema.Struct({
name: Schema.String,
mode: Schema.Literals(["subagent", "primary", "all"]),
permission: PermissionV1.Ruleset,
});
2.4 为什么这很重要?
对比 Grok Build 的 Rust 方案:Rust 的类型系统在编译期保证正确性,Effect TS 的 Schema 在运行时保证正确性。两者达到的最终效果类似——数据结构不会在管道中变形——但路径完全不同。
三、四轴 LLM 路由体系
这是 OpenCode 最独特的架构设计。packages/llm/ 将 LLM 调用拆成四个正交维度:
Protocol → 协议层(OpenAI Chat、Anthropic Messages、Google Gemini...)
Endpoint → 端点层(api.openai.com、api.anthropic.com、自定义代理...)
Auth → 认证层(API Key、OAuth、Bearer Token...)
Framing → 框架层(AI SDK、原生 HTTP、流式/非流式...)
3.1 Protocol 复用
多个 Provider 共享同一个 Protocol 实现:
// 同一个 OpenAIChat.protocol 被多个 provider 复用
const deepseek = OpenAIChat.protocol({ baseURL: "https://api.deepseek.com" });
const together = OpenAIChat.protocol({ baseURL: "https://api.together.xyz" });
const fireworks = OpenAIChat.protocol({ baseURL: "https://api.fireworks.ai" });
const openai = OpenAIChat.protocol({ baseURL: "https://api.openai.com/v1" });
对比 Pi Agent 的 30+ Provider:Pi 为每个 provider 写一个适配文件,而 OpenCode 只需要写一个 OpenAIChat.protocol,然后通过配置不同的 baseURL 复用。这是架构抽象层次的差异。
3.2 RequestExecutor 重试逻辑
在 packages/llm/src/ 中,RequestExecutor 封装了完整的重试策略:
- 指数退避 + 抖动
- 按错误类型分类(4xx 不重试,5xx 重试,429 限速重试)
- 请求超时控制
- 流式响应中断恢复
对比 Grok Build 的 xai-grok-sampler:Grok Build 的 samper 有三层架构(SamplingClient → stream → SamplerHandle),重试逻辑在 retry.rs 中,支持 doom_loop 检测。OpenCode 的 RequestExecutor 功能类似但更轻量。
四、双 Agent 架构:build vs plan
OpenCode 内置了两个 Agent,定义在 packages/opencode/src/agent/agent.ts 中:
const AgentInfo = Schema.Struct({
name: Schema.String,
description: Schema.optional(Schema.String),
mode: Schema.Literals(["subagent", "primary", "all"]),
permission: PermissionV1.Ruleset, // 权限系统
});
| Agent | 模式 | 权限 | 用途 |
|---|---|---|---|
| build | primary | 完整文件读写 | 开发工作 |
| plan | primary | 只读 | 代码分析、探索 |
两者通过 Tab 键在 TUI 中切换。核心差异只是 permission 级别不同——同一个 Agent Loop,不同的权限配置。
对比 Grok Build:Grok Build 的 AgentDefinition 系统更复杂,除了 permission 还支持 prompt_mode、completion_requirement、toolset 等配置,通过 AgentBuilder 构建。
对比 Pi Agent:Pi 没有内置的多 Agent 概念,但通过扩展系统可以实现。
五、Session 架构:V2 的持久化改革
OpenCode 的 Session 系统在 packages/opencode/src/session/ 下,约 24 个文件。V2 版本引入了 Durable Prompt Admission 模式:
SessionV2.prompt(input)
├── durable admission: 将 prompt 持久化到 SQLite
├── schedule wake: 通知 SessionRunner 有新工作
└── return sessionID
关键设计:prompt 在发送给 LLM 之前先持久化。即使进程崩溃,prompt 不会丢失。
5.1 Session 处理循环(Processor)
processor.ts 是实现 Agent 循环的核心——一个事件驱动的状态机:
create(input) → LLM.Service.stream(input) → handleEvent(event)
├── reasoning-start → 管理推理过程
├── tool-call-start → 执行工具调用
├── tool-result → 处理工具结果
├── text-delta → 文本增量更新
└── error → 错误处理
5.2 双运行时策略
OpenCode 同时支持两种 LLM 运行时:
- AI SDK 路径(默认):通过 Vercel AI SDK 调用
- Native LLM 路径(新):通过
native-runtime.ts直接调用
双运行时通过 session/llm.ts 中的 LLM.Service 统一管理,根据配置自动选择。这种渐进式迁移策略——先通过成熟 SDK 上线,再逐步替换为原生实现——是工程化团队的典型做法。
六、插件系统:V1 + V2 双轨制
OpenCode 的插件系统在 packages/plugin/ 中,同时维护两套 API:
| 版本 | 架构 | 使用场景 |
|---|---|---|
| V1 (Promise) | 简单的 () => Promise<void> |
快速原型、简单插件 |
| V2 (Effect) | Effect 的 Context/Layer/Scope | 复杂插件、生产环境 |
V2 插件支持 20+ 生命周期钩子:
onSessionStart/onSessionEndonToolCall/onToolResultonPromptBuild/onPromptCompleteonError/onConfigLoad
对比 Pi Agent 的扩展系统:Pi 的扩展机制更丰富(注册工具、命令、UI 组件),但 OpenCode 的插件系统在类型安全上更好——Effect 的 Context 保证了插件依赖在编译期可验证。
七、和 Grok Build 的深层对比
7.1 哲学差异
| 维度 | Grok Build (Rust) | OpenCode (Effect TS) |
|---|---|---|
| 正确性保证 | 编译期类型系统 | 运行时 Schema 验证 |
| 并发模型 | Tokio Actor + mpsc | Effect Fiber + Scope |
| 错误处理 | Result + thiserror | Effect.Either + Cause |
| 依赖注入 | 手动 Resource 注入 | Context.Service 自动 |
| 协议层 | ACP (Gateway + Session) | Effect HttpApi |
7.2 工具系统
Grok Build 的 ToolBridge + FinalizedToolset 通过类型安全的 Resource 注入管理工具依赖。OpenCode 的工具注册通过 Tool.Registry 的 Effect Service 实现。两者都在做同一件事——让工具不依赖全局状态——但 Grok Build 的 Rust 版本在编译期保证,而 OpenCode 在运行时保证。
7.3 MCP 集成
Grok Build 有完整的 xai-grok-mcp crate,支持 Streamable HTTP 和子进程两种传输,OAuth 认证,Managed MCP 自动刷新。OpenCode 的 MCP 支持在 packages/plugin/src/v2/ 中,通过插件系统实现。
八、三篇编码 Agent 的横向对比
| 维度 | Grok Build | OpenCode | Pi Agent |
|---|---|---|---|
| 语言 | Rust | TypeScript/Effect | TypeScript |
| 包数 | 50+ crate | 20+ 包 | 5 包 |
| 文件数 | 2804 | 2499 | 859 |
| 会话模式 | 5 状态 + ACP 协议 | Durable SQLite | JSONL 树形 + 分支 |
| 工具系统 | ToolBridge 资源注入 | Effect Service 注册 | 定义优先双层注册 |
| Provider | 少(委托 xAI) | 四轴路由 | 30+ Provider |
| 验证系统 | ✅ Skeptic + Goal | ❌ | ❌ |
| 权限系统 | ✅ 完整 | ✅ Ruleset | ❌ 容器化 |
| 子 Agent | 进程内 1 层 | ❌ 未知 | RPC 进程级 |
| 协议 | ACP | Effect HttpApi | ❌ CLI 原生 |
| MCP | ✅ 完整 | ✅ 插件化 | ❌ |
写在最后
OpenCode 的代码质量是三者中最高的。Effect TS 的选择让它的架构非常干净——每一层都有明确的边界,每个依赖都有显式的声明。
但代价也很明显:学习曲线陡峭。如果你不熟悉 Effect TS,读 OpenCode 的代码会非常吃力。而 Grok Build 的 Rust 代码虽然复杂,但 Rust 类型系统的表现力让代码「自文档化」程度更高。
如果你对 Effect TS 的代数效应系统在工程中的落地感兴趣,OpenCode 是最值得读的参考实现。它的「四轴 LLM 路由」和「Schema First 协议层」设计,是面向未来的 Agent 架构方向。
相关阅读:
- Grok Build 开源!万字拆解 40 万行 Rust 构建的 AI Coding Agent 内部架构
- Pi Agent 深度拆解:TypeScript 多包架构下的 Steering + Follow-up 双队列设计
GitHub: https://github.com/sst/opencode