Featured image of post OpenCode 源码深度拆解:Effect TS 代数效应系统构建的智能编码 Agent

OpenCode 源码深度拆解:Effect TS 代数效应系统构建的智能编码 Agent

在读完 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_modecompletion_requirementtoolset 等配置,通过 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 / onSessionEnd
  • onToolCall / onToolResult
  • onPromptBuild / onPromptComplete
  • onError / 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 架构方向。

相关阅读:

GitHub: https://github.com/sst/opencode

By AI博士 万戈