1. 一句话总结

- 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 哪些目录是主线。
- 不要一上来被 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.ts | yargs 命令注册 |
src/cli/cmd/run.ts | opencode run 分发 |
src/cli/cmd/run/runtime.ts | 非交互事件流 |
src/session/prompt.ts | Agent Loop |
src/session/llm/request.ts | prompt / history / tools 拼装 |
src/tool/registry.ts | 工具注册 |
src/tool/tool.ts | 工具接口 |
src/permission/ | 权限确认 |
顺序:
run.tsruntime.tssession/prompt.tstool/registry.tspermission/
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
- 这张表只保留对判断有用的差异:
| 维度 | Codex | opencode |
|---|---|---|
| 主语言 | Rust | TypeScript |
| 执行体 | Rust binary | Bun binary |
| 命令解析 | clap | yargs |
| Agent Loop | 多模块分散 | session/prompt.ts 更集中 |
| LLM 层 | 自研 client | Vercel AI SDK |
| 工具 schema | Rust / JSON | Zod / Effect |
| TUI | ratatui | opentui + SolidJS |
| Web UI | 无 | SolidJS app |
| Desktop | 无 | Electron |
| 持久化 | 文件为主 | SQLite + Drizzle |
| 沙箱 | OS 级更强 | 主要靠 permission |
- 迁移到
mini-code-agent-cli。 - Node shim 可以保留。
- Agent Loop 先集中到一个文件。
- tool registry 和 permission 必须拆开。
- UI 入口不要绑死主循环,统一回到 session。
10. 结论
- opencode 第一遍只要打穿四块。
run入口。prompt循环。tool注册。permission检查。- 真正有迁移价值的不是目录。
- 是
模型 -> 工具 -> 权限 -> 结果回填 -> 再问模型。