整体架构设计
约 1468 字大约 5 分钟
2026-03-21
基于 opencode-dev 源码分析
一、CLI 启动流程
1.1 开发模式 (bun --cwd packages/opencode dev)
bun dev
↓
package.json: "dev": "bun run --conditions=browser ./src/index.ts"
↓
src/index.ts ← 【第一个执行的文件】
↓
yargs(hideBin(process.argv)) ← 【命令行参数解析】
↓
.middleware() → 初始化日志系统 + 数据库迁移
↓
.command(RunCommand, GenerateCommand, ...) → 注册命令
↓
cli.parse() → 解析并执行命令详细启动序列:
src/index.ts
│
├─→ 全局异常处理 (unhandledRejection, uncaughtException)
│
├─→ yargs 配置
│ │
│ └─→ .middleware() — 核心初始化:
│ ├─→ Log.init() (日志系统)
│ ├─→ 设置环境变量 (AGENT=1, OPENCODE=1, OPENCODE_PID)
│ └─→ JsonMigration.run() (数据库迁移检查)
│
├─→ 注册命令
│
└─→ await cli.parse() — 解析并执行
│
└─→ RunCommand.handler() (默认命令)
│
└─→ bootstrap(cwd, callback)
│
└─→ Instance.provide()
│
├─→ Project.fromDirectory() (解析项目)
└─→ InstanceBootstrap() (核心服务初始化):
├─→ Plugin.init()
├─→ Format.init()
├─→ LSP.init()
├─→ File.init()
├─→ FileWatcher.init()
├─→ Vcs.init()
└─→ Snapshot.init()1.2 生产模式 (bin/opencode)
bin/opencode (Node.js 包装脚本)
↓
检测平台: darwin / linux / windows
↓
检测架构: x64 / arm64 / arm
↓
检测 AVX2 支持 (x64 平台)
↓
查找二进制文件:
- opencode-darwin-arm64
- opencode-linux-x64 / opencode-linux-x64-baseline
- opencode-windows-x64.exe
↓
childProcess.spawnSync(binary, process.argv.slice(2))1.3 参数解析机制
使用 yargs 库:
// src/index.ts:50
let cli = yargs(hideBin(process.argv))
.parserConfiguration({ "populate--": true })
.scriptName("opencode")
.option("print-logs", { ... })
.option("log-level", { ... })
.command(RunCommand)
.command(GenerateCommand)
// ... 更多命令hideBin(process.argv)移除process.argv[0]和process.argv[1]- 支持
--传递额外参数 - 每个命令使用
cmd()辅助函数定义
1.4 关键文件
| 文件 | 作用 |
|---|---|
bin/opencode | 生产入口,查找并执行预编译二进制 |
src/index.ts | 开发入口 + yargs CLI 定义 |
src/cli/cmd/run.ts | run 命令实现(默认命令) |
src/cli/bootstrap.ts | 实例引导封装 |
src/project/bootstrap.ts | 核心服务初始化 |
src/global/index.ts | 全局路径配置(模块加载时执行) |
二、核心数据流
用户发送 "帮我读取 README.md" 的完整流程:
2.1 完整数据流图
┌─────────────────────────────────────────────────────────────────────────┐
│ 阶段 1: 用户输入 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 用户输入 "帮我读取 README.md" │
│ ↓ │
│ [TUI Prompt 组件] submit() │
│ 文件: src/cli/cmd/tui/component/prompt/index.tsx:530-673 │
│ ↓ │
│ sdk.client.session.prompt({ │
│ parts: [{ type: "text", text: "帮我读取 README.md" }] │
│ }) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 阶段 2: 网络传输 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ HTTP POST /session/:sessionID/message │
│ 文件: src/server/routes/session.ts:781-821 │
│ ↓ │
│ SessionPrompt.prompt(input) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 阶段 3: 消息构建 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ SessionPrompt.prompt() → createUserMessage() │
│ 文件: src/session/prompt.ts:162-189, 966-1356 │
│ ↓ │
│ 构建 MessageV2.User 对象 │
│ ↓ │
│ Session.updateMessage() → 存储到数据库 │
│ ↓ │
│ SessionPrompt.loop() → 启动 AI 处理循环 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 阶段 4: AI 调用 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ SessionPrompt.loop() │
│ 文件: src/session/prompt.ts:278-736 │
│ ↓ │
│ resolveTools() → 获取可用工具列表 │
│ ↓ │
│ LLM.stream({ model, messages, tools }) │
│ 文件: src/session/llm.ts:48-286 │
│ ↓ │
│ streamText({ messages, tools, model }) │
│ ↓ │
│ Provider.getLanguage(model) → 获取 LLM SDK 实例 │
│ 文件: src/provider/provider.ts:1343-1368 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 阶段 5: 流式响应处理 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ SessionProcessor.process() │
│ 文件: src/session/processor.ts:46-426 │
│ ↓ │
│ for await (const value of stream.fullStream) { │
│ switch (value.type) { │
│ case "text-start": → 创建 TextPart │
│ case "text-delta": → 追加文本 + 发布事件 │
│ case "tool-call": → 执行工具 │
│ case "tool-result": → 返回结果 │
│ case "finish-step": → 计算 token 使用量 │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 阶段 6: 事件发布 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Bus.publish(MessageV2.Event.PartUpdated, { part }) │
│ Bus.publish(MessageV2.Event.PartDelta, { delta }) │
│ 文件: src/session/index.ts:755-789 │
│ ↓ │
│ TUI 通过 SSE/WebSocket 订阅事件 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 阶段 7: UI 渲染 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ TUI Sync Context → Session 组件 │
│ 文件: src/cli/cmd/tui/context/sync.tsx │
│ 文件: src/cli/cmd/tui/routes/session/index.tsx │
│ ↓ │
│ 渲染: │
│ > build · claude-sonnet-4 │
│ │
│ → Read README.md │
│ │
│ [AI 回复内容...] │
│ │
└─────────────────────────────────────────────────────────────────────────┘2.2 关键函数/类
| 模块 | 函数/类 | 作用 |
|---|---|---|
| Prompt | submit() | 接收用户输入,发送到后端 |
| SessionPrompt | prompt() | 创建用户消息,启动处理循环 |
| SessionPrompt | loop() | AI 响应主循环 |
| LLM | stream() | 调用 AI SDK streamText |
| SessionProcessor | process() | 处理流式响应事件 |
| Provider | getLanguage() | 获取 LLM SDK 实例 |
| Session | updatePart() | 存储 Part 到数据库 |
| Bus | publish() | 发布事件通知 UI |
三、工具调用机制
3.1 工具如何被 AI 知道
工具定义层 (src/tool/read.ts)
export const ReadTool = Tool.define("read", {
description: DESCRIPTION, // 从 read.txt 加载
parameters: z.object({ // Zod schema 定义参数
filePath: z.string().describe("The absolute path..."),
offset: z.coerce.number().optional(),
limit: z.coerce.number().optional(),
}),
async execute(params, ctx) { // 执行逻辑
const content = await Bun.file(params.filePath).text()
return { title, output, metadata }
}
})工具注册层 (src/tool/registry.ts)
async function all(): Promise<Tool.Info[]> {
return [
InvalidTool,
BashTool,
ReadTool, // ← read 工具在这里注册
GlobTool,
GrepTool,
EditTool,
WriteTool,
TaskTool,
WebFetchTool,
// ... 更多内置工具
...custom, // ← 用户自定义工具
]
}
export async function tools(model, agent) {
return all().then(tools => tools.map(t => t.init({ agent })))
}Schema 生成 / 暴露给 AI (src/session/prompt.ts)
async function resolveTools(input) {
const tools: Record<string, AITool> = {}
for (const item of await ToolRegistry.tools(model, agent)) {
// 1. Zod → JSON Schema
const schema = ProviderTransform.schema(
model,
z.toJSONSchema(item.parameters)
)
// 2. 包装成 Vercel AI SDK 格式
tools[item.id] = tool({
description: item.description, // ← AI 看到的描述
inputSchema: jsonSchema(schema), // ← AI 看到的参数 schema
async execute(args, options) {
const ctx = context(args, options)
return item.execute(args, ctx)
}
})
}
return tools // 传递给 LLM.stream()
}AI 看到的 tools 参数
{
"read": {
"description": "读取文件内容...",
"parameters": {
"type": "object",
"properties": {
"filePath": { "type": "string", "description": "..." },
"offset": { "type": "number" },
"limit": { "type": "number" }
},
"required": ["filePath"]
}
},
"bash": { ... },
"edit": { ... }
}3.2 工具执行流程
┌─────────────────────────────────────────────────────────────────────────┐
│ AI 返回 tool_call │
├─────────────────────────────────────────────────────────────────────────┤
│ { │
│ "type": "tool_call", │
│ "toolCallId": "call_123", │
│ "toolName": "read", │
│ "input": { "filePath": "README.md" } │
│ } │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ Vercel AI SDK 自动处理 │
├─────────────────────────────────────────────────────────────────────────┤
│ // streamText 内部逻辑 │
│ 1. 解析 tool_call │
│ 2. 查找 tools["read"].execute │
│ 3. 调用 execute({ filePath: "README.md" }, ctx) │
│ 4. 等待结果 │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ ReadTool.execute() 执行 │
├─────────────────────────────────────────────────────────────────────────┤
│ async execute(params, ctx) { │
│ // 1. 参数验证 (Zod) │
│ // 2. 读取文件 │
│ const content = await Bun.file(params.filePath).text() │
│ // 3. 输出截断 (防止 token 超限) │
│ const truncated = await Truncate.output(content, ...) │
│ // 4. 返回结果 │
│ return { │
│ title: "README.md", │
│ output: truncated.content, │
│ metadata: { truncated: truncated.truncated } │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 结果返回给 AI │
├─────────────────────────────────────────────────────────────────────────┤
│ { │
│ "type": "tool_result", │
│ "toolCallId": "call_123", │
│ "output": "# README\n\n文件内容..." │
│ } │
│ ↓ │
│ AI 继续生成回复 │
└─────────────────────────────────────────────────────────────────────────┘3.3 事件流处理 (src/session/processor.ts)
for await (const value of stream.fullStream) {
switch (value.type) {
case "tool-call":
// 工具开始执行
await Session.updatePart({
type: "tool",
tool: value.toolName,
state: { status: "running", input: value.input }
})
break
case "tool-result":
// 工具执行完成
await Session.updatePart({
state: {
status: "completed",
output: value.output.output,
metadata: value.output.metadata
}
})
break
case "tool-error":
// 工具执行失败
await Session.updatePart({
state: { status: "error", error: value.error.message }
})
break
}
}四、关键文件索引
| 领域 | 文件 | 作用 |
|---|---|---|
| 入口 | src/index.ts | CLI 入口,yargs 配置 |
| 入口 | bin/opencode | 生产入口,查找二进制 |
| 命令 | src/cli/cmd/run.ts | run 命令(默认) |
| 初始化 | src/project/bootstrap.ts | 核心服务初始化 |
| 会话 | src/session/index.ts | Session 数据层 |
| 会话 | src/session/prompt.ts | 消息处理核心 |
| 会话 | src/session/llm.ts | AI 调用层 |
| 会话 | src/session/processor.ts | 流式响应处理 |
| 工具 | src/tool/tool.ts | 工具定义框架 |
| 工具 | src/tool/registry.ts | 工具注册表 |
| 工具 | src/tool/read.ts | read 工具实现 |
| Provider | src/provider/provider.ts | LLM SDK 管理 |
| 事件 | src/bus/bus-event.ts | 事件总线 |
五、架构总览图
┌─────────────────────────────────────────────────────────────────────┐
│ CLI 入口 │
│ bin/opencode (生产) / src/index.ts (开发) │
│ ↓ yargs 解析 │
│ RunCommand │
└──────────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ SDK Client Layer │
│ @opencode-ai/sdk/v2 → createOpencodeClient() │
│ - session.create() - session.prompt() - event.subscribe() │
└──────────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Server Layer │
│ Server.Default() → Hono HTTP Server │
│ routes/session.ts → SessionPrompt │
└──────────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Session Processing │
│ SessionProcessor.create().process() │
│ ├── LLM.stream() → 调用 AI API │
│ ├── 处理流式事件 (text-delta, tool-call, etc.) │
│ └── 更新 Message/Part 到数据库 │
└──────────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Tool Layer │
│ ToolRegistry.tools() → 获取所有工具定义 │
│ Tool.define() → 定义工具 (Zod schema + execute) │
│ Tool.execute() → 执行工具 │
└─────────────────────────────────────────────────────────────────────┘