opencode的工作原理
2026-03-03
深度学习
00

目录

OpenCode 源码解析:从用户提问到生成回答的完整工作流程
整体架构
第一阶段:启动
1. CLI 入口
2. Worker 线程启动
3. TUI 渲染
第二阶段:用户输入
4. 输入捕获
5. 提交消息
第三阶段:Server 处理
6. HTTP 路由
7. 创建用户消息
8. 进入主循环(Agentic Loop)
第四阶段:LLM 调用
9. 流式请求
10. Provider 层
11. System Prompt 选择
第五阶段:流式处理
12. SessionProcessor
第六阶段:工具调用
13. 工具执行流程
14. 内置工具清单
15. Agent 与工具权限
第七阶段:UI 渲染
16. 实时更新链路
17. 消息渲染
完整数据流总览
关键设计特点
1. Agentic Loop(代理循环)
2. Provider 统一抽象
3. 流式全链路
4. 权限系统
5. Context 窗口管理
6. 错误恢复
7. 子 Agent 并行处理
核心源码文件索引

OpenCode 源码解析:从用户提问到生成回答的完整工作流程

本文基于 OpenCode 源码分析,带你深入理解这个 AI 编程助手从接收用户输入到返回结果的完整链路。

整体架构

OpenCode 采用客户端-服务端分离架构,即使在 CLI 模式下也是如此。TUI(终端 UI)运行在主线程,Server 运行在 Worker 线程,二者通过 HTTP + SSE 通信:

展开代码
┌─────────────────────┐ ┌─────────────────────────────────────┐ │ TUI (SolidJS) │ SSE/ │ Server (Hono) │ │ 运行在主线程 │ ◄────► │ 运行在 Worker 线程 │ │ │ HTTP │ │ │ prompt/input │ │ session / provider / tool / llm │ │ message render │ │ storage (SQLite + Drizzle) │ └─────────────────────┘ └─────────────────────────────────────┘

技术栈一览:

层级技术选型
CLI 框架yargs
TUI 渲染@opentui/solid (SolidJS)
HTTP 服务Hono
数据库SQLite + Drizzle ORM
LLM 调用Vercel AI SDK (ai 包)
运行时Bun
包管理Bun Workspaces + Turborepo

第一阶段:启动

当你在终端输入 opencode 命令时,发生了以下事情:

1. CLI 入口

packages/opencode/src/index.ts 是真正的入口文件,使用 yargs 注册所有 CLI 命令。默认命令(不带子命令直接运行 opencode)是 TuiThreadCommand,即交互式终端模式。

2. Worker 线程启动

src/cli/cmd/tui/thread.ts 中,主线程会启动一个 Worker 线程。Worker 负责:

  • 项目引导(数据库迁移、配置加载)
  • 运行 Hono HTTP 服务器
  • 暴露 RPC 方法(fetch、shutdown、reload 等)
  • 将 SSE 事件转发回主线程

3. TUI 渲染

src/cli/cmd/tui/app.tsx@opentui/solid 渲染 SolidJS 组件树。组件层级很深,包含一系列 Provider:

展开代码
ArgsProvider > ExitProvider > KVProvider > ToastProvider > RouteProvider > TuiConfigProvider > SDKProvider > SyncProvider > ThemeProvider > LocalProvider > KeybindProvider > PromptStashProvider > DialogProvider > CommandProvider > FrecencyProvider > PromptHistoryProvider > PromptRefProvider > App

其中 SDKProvider 负责创建与 Server 的连接,SyncProvider 负责将 SSE 事件同步到 SolidJS 响应式状态。


第二阶段:用户输入

4. 输入捕获

src/cli/cmd/tui/component/prompt/index.tsx 是核心的输入组件,包含一个 textarea,支持:

  • 快捷键绑定(Enter 提交、上下键历史记录)
  • 自动补全(@agent、文件路径、/command
  • 粘贴处理(图片、文本、文件)
  • Shell 模式(! 前缀)
  • Slash 命令

5. 提交消息

当用户按下 Enter,submit() 函数触发(约在 prompt/index.tsx:528):

ts
展开代码
// 简化的逻辑流程 async function submit() { // 1. 验证输入非空、模型已连接 // 2. 如果还没有 session,创建一个 if (!session) { session = await sdk.client.session.create({}) } // 3. 根据模式发送不同请求 if (shellMode) { sdk.client.session.shell(...) } else if (slashCommand) { sdk.client.session.command(...) } else { // 普通提问 sdk.client.session.prompt({ sessionID, parts, model, agent }) } // 4. 清空输入框,导航到 session 页面 }

第三阶段:Server 处理

6. HTTP 路由

SDK 客户端发送 POST /session/:sessionID/message 到 Worker 中的 Hono 服务。

src/server/routes/session.ts 接收请求,验证参数后调用核心函数 SessionPrompt.prompt()

7. 创建用户消息

src/session/prompt.ts 中的 createUserMessage() 负责:

  • 解析 agent 引用(如 @explore
  • 解析 model 和 variant
  • 处理输入 parts:文本部分直接保存;文件引用会通过 ReadTool 读取文件内容
  • 将用户消息和 parts 持久化到 SQLite 数据库
  • 通过 Bus 发布 MessageV2.Event.Updated 事件,经 SSE 推送到 TUI 显示

8. 进入主循环(Agentic Loop)

这是 OpenCode 最核心的部分。src/session/prompt.ts 中的 loop() 函数实现了一个 Agent 循环:

ts
展开代码
// 简化的 loop() 逻辑 async function loop() { while (true) { // ① 从数据库加载完整消息历史 const msgs = await MessageV2.stream(sessionID) // ② 转换为 AI SDK 格式(包含历史的 tool_calls 和 tool_results) const messages = MessageV2.toModelMessages(msgs, model) // ③ 解析 agent,确定可用的 tools const tools = await resolveTools(agent, model) // ④ 构建 system prompt(根据模型选择模板) const system = buildSystemPrompt(agent, model) // ⑤ 创建 processor,调用 LLM const processor = SessionProcessor.create(...) const result = await processor.process(messages, tools, system) // ⑥ 根据返回值决定下一步 if (result === "stop") break // 模型回答完毕 if (result === "compact") compact() // 上下文溢出,压缩消息 if (result === "continue") continue // 有 tool_calls,继续循环 } }

关键点:这不是单次 LLM 调用,而是一个循环。每当 LLM 请求调用工具,工具执行完毕后,循环会将结果连同历史消息重新送入 LLM,直到模型自行决定停止。


第四阶段:LLM 调用

9. 流式请求

src/session/llm.ts 中的 LLM.stream() 是实际发起 LLM 请求的地方:

ts
展开代码
// 简化逻辑 function stream({ model, messages, tools, system }) { // 1. 获取 LanguageModelV2 实例 const languageModel = Provider.getLanguage(model) // 2. 组装 system prompt // agent prompt + 环境信息 + 自定义指令(AGENTS.md) const systemPrompt = [agentPrompt, envPrompt, customPrompt] // 3. 应用 provider 特定的 transform // 消息格式化、prompt caching、reasoning effort 等 const transformedMessages = ProviderTransform.message(messages, model) // 4. 触发 plugin hooks await plugins.trigger("chat.params", params) // 5. 调用 Vercel AI SDK 的 streamText() return streamText({ model: languageModel, messages: transformedMessages, tools, system: systemPrompt, temperature, maxTokens, // ... }) }

10. Provider 层

src/provider/provider.ts 管理 20+ 家 LLM 提供商的统一接入:

ProviderSDK 包
Anthropic (Claude)@ai-sdk/anthropic
OpenAI (GPT)@ai-sdk/openai
Google (Gemini)@ai-sdk/google
Amazon Bedrock@ai-sdk/amazon-bedrock
Azure OpenAI@ai-sdk/azure
xAI (Grok)@ai-sdk/xai
Groq@ai-sdk/groq
Mistral@ai-sdk/mistral
DeepInfra@ai-sdk/deepinfra
OpenRouter@openrouter/ai-sdk-provider
GitHub Copilot自定义 SDK(src/provider/sdk/copilot/
......

Provider 层负责:

  • 自动检测环境变量中的 API key
  • 缓存 SDK 实例
  • 应用 baseURL、timeout、custom headers
  • 通过 ProviderTransform 处理各 provider 的差异(消息格式、prompt caching、token 限制等)

11. System Prompt 选择

src/session/system.ts 根据不同的模型选择不同的 system prompt 模板:

模型Prompt 模板
Claude 系列anthropic.txt
GPT-3.5/4/o1/o3beast.txt
GPT-5/Codexcodex_header.txt
Gemini 系列gemini.txt
Qwen 及其他qwen.txt(默认兜底)

这些模板定义了 AI 的角色、行为规范、工具使用策略等。


第五阶段:流式处理

12. SessionProcessor

src/session/processor.ts 消费 LLM 返回的流式响应,处理各类事件:

流事件处理逻辑
text-delta创建/更新 text part,实时保存到数据库
reasoning-delta创建/更新 reasoning part(模型的思考过程)
tool-input-start创建 tool part,状态标记为 pending
tool-call状态标记为 running,检测 doom loop(同一工具+参数连续调用 3 次)
tool-result状态标记为 completed,保存工具输出
tool-error状态标记为 error
finish-step记录 token usage 和费用
error分类错误:context_overflow → 触发压缩;api_error → 触发重试

每次数据库更新都会通过 Bus 发布事件 → 经 SSE 推送到 TUI 客户端 → SolidJS 响应式更新 UI。这就是你看到回答"一个字一个字蹦出来"的原因。


第六阶段:工具调用

13. 工具执行流程

当 LLM 返回 tool_calls 时,Vercel AI SDK 自动调用 resolveTools() 中注册的 execute() 函数:

展开代码
LLM 返回: "我需要调用 bash 工具执行 git status" │ ▼ resolveTools() 中的 wrapper: ├── ① 权限检查 → PermissionNext.ask() │ (可能暂停,等待用户在 TUI 中点击"允许") ├── ② 触发 plugin hooks ├── ③ 调用 Tool.execute(args, context) │ (实际执行 shell 命令 / 文件读写 / 网络请求等) └── ④ 返回 {output, title, metadata, attachments} │ ▼ AI SDK 将结果作为 tool_result 送回 LLM │ ▼ loop() 继续下一轮迭代(带着工具结果再次请求 LLM)

14. 内置工具清单

OpenCode 内置了 18+ 种工具,定义在 src/tool/ 目录下:

工具文件功能
bashbash.ts执行 shell 命令,支持 AST 解析提取路径
readread.ts读取文件/目录,支持行号偏移、图片/PDF 处理
writewrite.ts写入完整文件,生成 diff 供权限审查
editedit.ts搜索替换编辑,9 种匹配策略级联
multieditmultiedit.ts单文件多处编辑
globglob.ts文件名模式匹配(基于 ripgrep)
grepgrep.ts内容正则搜索(基于 ripgrep)
listls.ts目录树展示
tasktask.ts派生子 Agent(创建子 session 并行处理)
batchbatch.ts并行执行最多 25 个工具调用
webfetchwebfetch.ts抓取 URL 内容,HTML 转 Markdown
websearchwebsearch.ts网络搜索(通过 Exa)
codesearchcodesearch.ts代码/API 文档搜索
questionquestion.ts向用户提问(带选项)
skillskill.ts加载领域特定工作流指令
todotodo.ts会话级任务列表管理
apply_patchapply_patch.ts应用 unified diff 补丁(GPT 模型专用)
lsplsp.tsLSP 操作(跳转定义、查引用等,实验性)

此外,通过 MCP(Model Context Protocol) 还可以接入外部工具服务器,扩展工具能力。

15. Agent 与工具权限

src/agent/agent.ts 定义了不同的 Agent,每个 Agent 有不同的工具权限:

Agent特点
build(默认)全部工具可用,完整的编码能力
plan只读 + 规划,禁止编辑(除 plan 文件)
explore只读工具(grep, glob, list, bash, read, webfetch)
compaction用于消息压缩的内部 agent
title用于生成会话标题的内部 agent

第七阶段:UI 渲染

16. 实时更新链路

展开代码
Server: Session.updatePart() → DB 写入 → Bus.publish() │ ▼ SSE 推送 │ ▼ TUI: SDKProvider 接收事件 → SyncProvider 状态同步 → SolidJS 响应式更新 │ ▼ 组件重新渲染(增量更新)

17. 消息渲染

src/cli/cmd/tui/routes/session/index.tsx 负责渲染会话页面:

  • <UserMessage> — 渲染用户消息
  • <AssistantMessage> — 渲染助手回复(实时流式更新)
    • 每个 text part 实时追加显示
    • 每个 tool part 渲染为可展开的工具调用卡片(显示工具名、参数、输出)
    • reasoning part 显示模型的思考过程
  • <PermissionPrompt> — 工具权限请求弹窗
  • <QuestionPrompt> — AI 向用户提问的弹窗
  • <Prompt> — 底部输入框

完整数据流总览

展开代码
用户输入 "帮我分析这个 bug" │ ▼ Prompt.submit() [TUI 主线程] │ sdk.client.session.prompt(...) ▼ POST /session/:id/message [HTTP → Worker 线程] │ ▼ SessionPrompt.prompt() [Server Worker] ├── createUserMessage() │ └── 消息入库 → Bus 事件 → SSE → TUI 显示用户消息 │ ▼ loop() ────────────────────────────── [Agentic Loop 开始] │ ├── 加载消息历史 ├── 构建工具集(ToolRegistry + MCP) ├── 构建 system prompt │ ▼ LLM.stream() → streamText() [请求 LLM API] │ ▼ SessionProcessor.process() [消费流式响应] │ ├── text-delta ────→ DB → Bus → SSE → TUI 实时显示文字 │ ├── tool-call("read", {filePath: "src/bug.ts"}) │ ├── 权限检查 ✓ │ ├── 执行 ReadTool → 返回文件内容 │ └── tool-result → DB → Bus → SSE → TUI 显示工具卡片 │ ├── tool-call("grep", {pattern: "error", path: "src/"}) │ ├── 执行 GrepTool → 返回匹配结果 │ └── tool-result → DB → Bus → SSE → TUI 显示工具卡片 │ ├── text-delta ────→ "根据分析,这个 bug 的原因是..." │ ├── finish_reason == "tool-calls" → loop() 继续 ↩ ├── finish_reason == "stop" → loop() 退出 ✓ │ ▼ 最终消息发布 → TUI 显示完整回答

关键设计特点

1. Agentic Loop(代理循环)

不是简单的"一问一答",而是 LLM → tool → LLM → tool → ... → 最终回答 的循环。模型可以自主决定调用哪些工具、调用多少次,直到认为已经有足够信息来回答问题。

2. Provider 统一抽象

通过 Vercel AI SDK + 自定义 ProviderTransform 层,统一支持 20+ 家 LLM 提供商。每个 provider 的差异(消息格式、prompt caching 策略、reasoning effort 配置等)都在 transform 层处理,上层代码无需感知。

3. 流式全链路

从 LLM API 的每个 token → SessionProcessor 处理 → 数据库持久化 → Bus 事件 → SSE 推送 → SolidJS 响应式更新 → 终端渲染,整条链路都是流式的,用户可以实时看到回答逐字生成。

4. 权限系统

工具执行前会检查权限。对于敏感操作(文件写入、shell 命令等),会暂停执行并在 TUI 中弹出权限请求,等待用户明确授权后才继续。

5. Context 窗口管理

自动检测上下文溢出。当 token 用量接近模型的 context limit 时,触发 compaction(压缩):用一个小模型将历史消息摘要化,裁剪旧的工具输出,从而释放上下文空间继续对话。

6. 错误恢复

  • 速率限制 / 5xx 错误:自动重试 + 指数退避,支持读取 retry-after 响应头
  • 上下文溢出:自动触发 compaction 后重试
  • 工具调用格式错误:通过 experimental_repairToolCall 自动修复(小写化工具名、路由到 invalid tool)

7. 子 Agent 并行处理

通过 task 工具,主 Agent 可以派生子会话,让子 Agent 独立执行任务。子 Agent 拥有独立的消息历史和上下文,完成后将结果返回给主 Agent。这实现了复杂任务的分治和并行处理。


核心源码文件索引

如果你想深入阅读源码,以下是最关键的文件:

文件作用
src/index.tsCLI 入口,yargs 命令注册
src/cli/cmd/tui/thread.tsTUI 主线程,启动 Worker
src/cli/cmd/tui/app.tsxTUI 根组件,Provider 树
src/cli/cmd/tui/component/prompt/index.tsx用户输入组件,submit()
src/server/server.tsHono HTTP 服务器
src/server/routes/session.tsSession API 路由
src/session/prompt.ts核心:消息处理 + Agentic Loop
src/session/llm.tsLLM 流式调用桥接
src/session/processor.ts流式响应处理状态机
src/session/message-v2.ts消息/Part 类型定义与转换
src/session/system.tsSystem prompt 选择
src/provider/provider.tsProvider 注册与模型解析
src/provider/transform.tsProvider 特定转换
src/tool/tool.tsTool 基础框架
src/tool/registry.tsTool 注册表
src/agent/agent.tsAgent 定义与权限
src/bus/index.ts事件总线
src/storage/db.tsSQLite 数据库连接
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:Dong

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!