130. opencode 工程概览与技术栈分析

2026.06.02

·opencodeagent

1. 一句话总结

20260602_1.webp

  • opencode 是 TypeScript 全栈式 Code Agent CLI。
  • npm 入口是 Node shim。
  • 真正执行体是 Bun binary。
  • UI 是 SolidJS / Electron。
  • 第一遍只读一条线:bin/opencode -> src/index.ts -> run.ts -> runtime.ts -> session/prompt.ts
  • session/prompt.ts 是 Agent Loop 本体:拼上下文、调模型、执行工具、过权限、回填工具结果。
  • 主战场是 packages/opencode/
    • CLI / TUI / server / session / tool / permission 都在这里。
  • 和 Codex 对照看:
    • Codex 更像系统软件
    • opencode 更像 TypeScript 产品工程。

2. 一图甚千言

opencode-project-structure-typescript-view.webp

  • 这张图只解决一个问题:先知道 opencode 哪些目录是主线。
  • 不要一上来被 app / desktop / sdk 分散。

3. 主线目录

  • 第一眼先看这几个入口:
text
opencode/
├── bin/opencode          # Node shim:找 binary + spawn
├── packages/opencode/    # 主程序:CLI / TUI / server / Agent Loop / tools
├── packages/core/        # fs / git / npm / spawn / event
├── packages/llm/         # provider / tool runtime / stream
├── packages/app/         # Web UI:SolidJS
├── packages/desktop/     # Electron
├── packages/ui/          # SolidJS + opentui
├── packages/plugin/      # 插件
└── packages/sdk/         # SDK
  • 第一遍不要散。
  • packages/opencode/ 是主线。
  • app / desktop 都是外层皮。
  • 最后都会回到 session。

4. 两个前置概念

4.1. Bun

  • 先当成“更适合打包 CLI 的 Node.js 替代品”。
  • 支持 TypeScript。
  • 启动快。
  • 能 compile 成 binary。
  • opencode 用 Bun 的核心价值:把 TypeScript 产品打成可分发 CLI。

4.2. Effect

  • 先不要被 Effect 拦住。
  • Effect.gen ≈ 更严格的 async function
  • yield* ≈ 更严格的 await
  • Layer ≈ 依赖注入容器。
  • 读业务名词就够:session / message / tool / permission / provider。

5. 启动链路

  • 启动链路按 ①-⑤ 看:
text
opencode run "帮我创建 React 组件"


bin/opencode
  ├─ ① 判断平台 / 架构 / AVX2 / musl
  ├─ ② 找 Bun binary
  └─ ③ spawn()


src/index.ts
  └─ ④ yargs 命中 run


cli/cmd/run.ts
  └─ ⑤ 分发非交互 / TUI / attach


cli/cmd/run/runtime.ts
  └─ ⑥ 非交互事件流


session/prompt.ts
  └─ ⑦ Agent Loop
  • 入口层:Node shim 只负责找到 binary,Bun binary 才进入真正程序。
  • 执行层:run/runtime.ts 把输入接到 session,session/prompt.ts 让模型和工具循环起来。

6. 第一遍读什么

  • 这些文件够支撑第一遍源码阅读:
路径读什么
src/index.tsyargs 命令注册
src/cli/cmd/run.tsopencode run 分发
src/cli/cmd/run/runtime.ts非交互事件流
src/session/prompt.tsAgent Loop
src/session/llm/request.tsprompt / history / tools 拼装
src/tool/registry.ts工具注册
src/tool/tool.ts工具接口
src/permission/权限确认

顺序:

  1. run.ts
  2. runtime.ts
  3. session/prompt.ts
  4. tool/registry.ts
  5. permission/

7. Agent Loop

7.1. Node 视角

typescript
while (step < maxSteps) {
  const request = await buildLLMRequest({
    messages: history,
    tools: await toolRegistry.list(),
  });

  const result = await model.generate(request);
  if (result.type === "text") return result.text;

  for (const toolCall of result.toolCalls) {
    if (!(await permission.check(toolCall))) continue;
    const output = await toolRegistry.execute(toolCall);
    history.push({ role: "tool", toolCall, output });
  }
}
  • 这就是 session/prompt.ts 的骨架。
  • 拼请求。
  • 调模型。
  • 文本就结束。
  • tool call 就过权限。
  • 执行工具。
  • 结果回填 history。

7.2. 稳定骨架

text
模型请求 -> tool call -> 权限检查 -> 工具执行 -> 结果回填 -> 再问模型
  • opencode 两个细节值得抄。
  • toolRegistry.list() 每轮重新拿工具列表。
  • permission.check(toolCall) 每个工具调用都过权限门。

8. 核心目录

  • 目录表只用于定位,不用于展开泛读:
目录角色
agent/agent 定义和 system prompt
tool/工具注册、schema、执行结果
session/会话、消息、上下文、压缩、摘要
permission/工具和 shell 确认规则
provider/模型 provider
config/provider / agent / permission
mcp/外部 MCP 工具
plugin/插件扩展
command/TUI 斜杠命令
  • 这张表只用于定位。
  • 当前主线仍然是 run -> prompt -> tool -> permission

9. Codex vs opencode

  • 这张表只保留对判断有用的差异:
维度Codexopencode
主语言RustTypeScript
执行体Rust binaryBun binary
命令解析clapyargs
Agent Loop多模块分散session/prompt.ts 更集中
LLM 层自研 clientVercel AI SDK
工具 schemaRust / JSONZod / Effect
TUIratatuiopentui + SolidJS
Web UISolidJS app
DesktopElectron
持久化文件为主SQLite + Drizzle
沙箱OS 级更强主要靠 permission
  • 迁移到 mini-code-agent-cli
  • Node shim 可以保留。
  • Agent Loop 先集中到一个文件。
  • tool registry 和 permission 必须拆开。
  • UI 入口不要绑死主循环,统一回到 session。

10. 结论

  • opencode 第一遍只要打穿四块。
  • run 入口。
  • prompt 循环。
  • tool 注册。
  • permission 检查。
  • 真正有迁移价值的不是目录。
  • 模型 -> 工具 -> 权限 -> 结果回填 -> 再问模型