拆解openCode源码-搞定全平台AI编程助手
OpenCode 它是一款 100% 开源,完全免费的AI智能体,你可以理解为开源版的Claude Code,它可以在命令行、桌面应用和IDE中使用。本文将快速拆解openCode源码,让你对openCode核心有更好的认识
什么是 OpenCode?
OpenCode 它是一款 100% 开源,完全免费的AI智能体,你可以理解为开源版的Claude Code,它可以在命令行、桌面应用和IDE中使用。
核心特点
✅ 完全开源免费:代码全部托管在GitHub,无需登录即可使用
✅ 多模型支持:自由切换Claude、GPT-5、GLM-4.7、MiniMax等多种AI模型
✅ 终端原生体验:专为命令行设计的TUI界面,流畅高效
✅ 上下文感知:智能理解项目结构,提供精准的代码建议
✅ 跨平台支持:支持Windows、macOS、Linux,并提供桌面客户端
✅ LSP自动集成:自动为AI加载正确的语言服务协议
核心架构模块
1. System Prompt 系统 ⭐
System Prompt 是与大语言模型交互时传递给模型的第一条系统级消息,它定义了模型的身份定位、行为规范、工具权限和工作流程。
核心概念:在 OpenCode 中,System Prompt 不仅仅是一段静态文本,而是一个由多个组件动态组合而成的复杂系统,涵盖了 LLM Provider 适配、环境感知、自定义规则和 Agent 特定配置等多个维度
在 OpenCode 中,一个完整的 System Prompt 由以下 四个核心部分 组成:
| 部分 | 说明 |
|---|---|
| Provider 特定提示 | 根据模型提供商选择对应的提示模板 |
| 环境信息 | 动态注入运行环境的上下文 |
| 自定义规则 | 从多个来源加载用户/项目级别的配置指令 |
| Agent 特定提示 | 根据 Agent 类型注入额外的指令 |
System Prompt 构建流程 (
packages/opencode/src/session/prompt.ts)
System Prompt 组成:
├── Provider 特定 prompt (provider/model 相关)
│ ├── anthropic.txt - Claude 系列模型
│ ├── beast.txt - GPT-4/o 系列模型
│ ├── gemini.txt - Google 模型
│ ├── codex.txt - Codex/GPT-5 模型
│ └── qwen.txt - Qwen 等其他模型
│
├── 环境信息 (SystemPrompt.environment)
│ ├── 工作目录
│ ├── Git 仓库状态
│ ├── 平台信息
│ └── 当前日期
│
├── 自定义规则 (SystemPrompt.custom)
│ ├── 项目级: AGENTS.md, CLAUDE.md, CONTEXT.md
│ ├── 全局级: ~/.claude/CLAUDE.md
│ ├── 配置指令: config.instructions
│ └── URL 远程规则
│
└── Agent 特定 prompt
├── build: 默认开发 agent
├── plan: 只读计划模式
├── explore: 代码探索 agent
├── general: 通用子 agent
└── 自定义 agent
├── packages/opencode/src/session/system.ts # Prompt 组装逻辑
├── packages/opencode/src/session/prompt.ts # LLM 调用注入点
├── packages/opencode/src/agent/agent.ts # Agent 定义与 prompt
└── packages/opencode/src/session/prompt/*.txt # 专用 agent 提示词(如Claude 系统提示)
2. 权限审核系统⭐
权限流程概览:
┌─────────────────────────────────────────────────────────────────┐
│ 权限请求流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Tool.execute() │
│ │ │
│ ▼ │
│ ctx.ask({ permission, patterns, always, metadata }) │
│ │ │
│ ▼ │
│ PermissionNext.ask() │
│ │ │
│ ├─→ 检查 approved 规则 ──命中──→ 返回 Promise.resolve() │ │
│ ├─→ 检查 deny 规则 ──命中──→ 抛出 DeniedError │
│ │ │
│ └─→ action === "ask" │
│ │ │
│ ▼ │
│ 发布 permission.asked 事件 │
│ │ │
│ ▼ │
│ TUI 接收事件,显示权限对话框 │
│ │ │
│ ┌────────┼────────┐ │
│ ▼ ▼ ▼ │
│ Once Always Reject │
│ │ │ │ │
│ └────────┴────────┘ │
│ │ │
│ ▼ │
│ Bus.publish(Event.Replied) │
│ │ │
│ ▼ │
│ Promise resolve/reject │
│ │
└─────────────────────────────────────────────────────────────────┘
权限类型:
// 支持的权限类型
edit - // 文件编辑 (包含创建和修改)
read - // 文件读取
glob - // 文件 glob
grep - // 代码搜索
list - // 目录列表
bash - // 命令执行
task - // 子 agent 调用
todoread - // 待办事项读取
todowrite - // 待办事项写入
question - // 向用户提问
webfetch - // 网页抓取
websearch - // 网络搜索
codesearch - // 代码搜索
external_directory - // 外部目录
doom_loop - // 死循环检测
权限动作:
Action = "allow" | "deny" | "ask"
// Reply = "once" | "always" | "reject"
Agent 权限配置示例 (packages/opencode/src/agent/agent.ts):
const defaults = PermissionNext.fromConfig({
"*": "allow",
doom_loop: "ask",
external_directory: "ask",
read: {
"*": "allow",
"*.env": "deny", // 禁止读取 .env
"*.env.*": "deny",
"*.env.example": "allow",
},
})
工具调用权限请求示例 (packages/opencode/src/tool/bash.ts):
// 请求外部目录访问权限
await ctx.ask({
permission: "external_directory",
patterns: Array.from(directories),
always: Array.from(directories).map((x) => path.dirname(x) + "*"),
metadata: {},
})
// 请求 bash 执行权限
await ctx.ask({
permission: "bash",
patterns: Array.from(patterns),
always: Array.from(always),
metadata: {},
})
3. Agent 系统 (src/agent)
// Agent 定义示例 (packages/opencode/src/agent/agent.ts)
build: {
name: "build"
mode: "primary" // primary/subagent/all
native: true
permission: {...}
}
plan: {
name: "plan"
mode: "primary"
prompt: PROMPT_PLAN // 读-only 模式
permission: {
edit: "*": "deny" // 禁止编辑
}
}
4. Tool 系统 (src/tool)
工具定义模式:
export const ReadTool = Tool.define("read", async (ctx) => {
return {
description: "Read a file...",
parameters: z.object({
filePath: z.string(),
}),
async execute(params, ctx) {
// 工具实现
},
}
})
├── packages/opencode/src/tool/tool.ts # Tool 接口定义
├── packages/opencode/src/tool/registry.ts # 工具注册表
└── packages/opencode/src/tool/*.ts # 具体工具实现
5. 迭代信息收集模式 (src/session/processor.ts)
核心灵魂 - OpenCode 的智能决策循环,让 Agent 能够根据用户问题探索代码库、迭代收集信息、直到获取足够信息后再采取行动。
迭代决策流程:
┌─────────────────────────────────────────────────────────────────┐
│ 迭代信息收集流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户提问 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 步骤 1: LLM 分析问题 │ │
│ │ - 理解用户意图 │ │
│ │ - 决定:需要更多信息?还是直接行动? │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ 需要探索 │ │ 信息足够 │ │
│ └──────────┘ └──────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ 调用工具 │ │ 执行最终行动 │ │
│ │ 收集信息 │ │ (write/edit等) │ │
│ └──────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 步骤 2: 处理工具结果 │ │
│ │ - LLM 评估结果是否足够 │ │
│ │ - 决定:继续探索?还是结束? │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │
│ └────────────────────┘ │
│ (循环) │
│ │
└─────────────────────────────────────────────────────────────────┘
核心实现:
// processor.ts - 处理器主循环
async process(streamInput: LLM.StreamInput) {
while (true) { // 外层循环:迭代决策
const stream = await LLM.stream(streamInput)
for await (const value of stream.fullStream) { // 内层循环:工具调用
switch (value.type) {
case "tool-call":
// 执行工具,收集信息
break
case "tool-result":
// 处理结果,LLM 决定是否继续
break
}
}
// 根据上下文决定是否继续循环
}
}
// 死循环检测
const DOOM_LOOP_THRESHOLD = 3
if (lastThree.length === DOOM_LOOP_THRESHOLD &&
all_same_tool_calls &&
no_errors) {
throw new DoomLoopError() // 防止无限循环
}
Tool Chaining 模式:
// 典型探索链示例
1. glob("**/*.ts") // 发现文件
↓
2. grep("API|export") // 搜索关键内容
↓
3. read("file.ts") // 读取具体文件
↓
4. bash("npm test") // 验证发现
Explore Agent 专用探索:
// packages/opencode/src/agent/agent.ts - 专为快速探索优化的 Agent
explore: {
name: "explore",
mode: "subagent",
permission: PermissionNext.merge(defaults, {
"*": "deny", // 禁止所有操作
grep: "allow", // 显式允许搜索
glob: "allow", // 允许文件匹配
read: "allow", // 允许读取
list: "allow", // 允许目录列表
bash: "allow", // 允许命令
codesearch: "allow", // 允许代码搜索
external_directory: { // 允许外部目录访问
[Truncate.DIR]: "allow",
},
webfetch: "allow",
websearch: "allow",
}),
description: "Fast agent specialized for exploring codebases...",
prompt: PROMPT_EXPLORE,
}
| 文件 | 关键函数/行号 | 说明 |
|---|---|---|
packages/opencode/src/session/processor.ts |
process() 主循环 |
迭代决策核心 |
packages/opencode/src/session/processor.ts |
死循环检测 | DOOM_LOOP_THRESHOLD |
packages/opencode/src/tool/grep.ts |
grep 工具 | 代码搜索 |
packages/opencode/src/tool/glob.ts |
glob 工具 | 文件发现 |
packages/opencode/src/tool/read.ts |
read 工具 | 内容读取 |
packages/opencode/src/agent/agent.ts |
explore agent | 专用探索 |
6. Skill 系统 (src/skill)
Skill 定义格式 (.opencode/skill/test-skill/SKILL.md):
---
name: test-skill
description: use this when asked to test skill
---
# Skill 文档
这里是技能的详细说明和使用指南
├── packages/opencode/src/skill/skill.ts # Skill 发现
├── packages/opencode/src/tool/skill.ts # Skill 工具
└── .opencode/skill/*/SKILL.md # Skill 定义
7. Session 管理 (src/session)
- 会话创建与状态管理
- 消息流处理 (MessageV2)
- 上下文压缩与摘要
- 权限继承
8. Provider 系统 (src/provider)
- 多模型提供商适配
- 工具 schema 转换
- API 集成
Agent 特定 Prompt
源码位置:
packages/opencode/src/agent/agent.ts
在 OpenCode 中,Agent 是实际执行任务的主体。每个 Agent 都可以有自己的特定提示词(Prompt),这些提示词会在 System Prompt 之后追加注入。本节将深入解析 Agent 的定义机制、内置 Agent 的设计思路,以及如何自定义 Agent 配置。
7.1 Agent 的三种模式
理解 Agent 的第一步是理解它的模式(mode),这决定了 Agent 在整个系统中的角色定位。
| 模式 | 说明 | 使用场景 |
|---|---|---|
| primary | 主 Agent 模式,直接响应用户的顶层请求 | build、plan Agent |
| subagent | 子 Agent 模式,只能被其他 Agent 调用 | explore、general Agent |
| all | 兼具两种角色的能力 | 自定义 Agent 默认模式 |
primary 模式是主 Agent 模式。在这种模式下,Agent 直接响应用户的顶层请求,是用户交互的主要对象。一个会话中只能有一个 primary Agent 处于活动状态。
subagent 模式是子 Agent 模式。这种 Agent 不能独立响应用户请求,只能被其他 Agent 通过 Task 工具调用。它们通常用于执行特定领域的任务,如代码探索、日志分析等。
all 模式兼具两种角色的能力,既可以作为主 Agent 响应用户,也可以作为子 Agent 被调用。自定义 Agent 默认使用这种模式。
设计思路:将 Agent 分为 primary 和 subagent 两种模式,体现了"职责分离"的设计原则。primary Agent 负责整体规划和协调,subagent 负责具体执行特定任务。这种分层架构使得系统更加灵活,也便于权限控制。
7.2 Agent 定义结构
文件位置:packages/opencode/src/agent/agent.ts
每个 Agent 的核心配置包含以下几个关键维度:
| 配置维度 | 属性 | 说明 |
|---|---|---|
| 身份标识 | name |
Agent 的唯一标识符 |
| 能力边界 | mode |
角色类型(primary/subagent/all) |
| 能力边界 | steps |
最大执行步数限制 |
| 行为特征 | prompt |
Agent 专属提示词 |
| 行为特征 | temperature |
生成内容的随机性 |
| 行为特征 | topP |
采样阈值控制 |
| 权限控制 | permission |
可执行的操作范围 |
| 模型配置 | model |
指定的模型(providerID/modelID) |
7.3 内置 Agent 分析
OpenCode 内置了 7 个 Agent,分布在 packages/opencode/src/agent/agent.ts 中定义。通过分析它们的配置可以深入理解设计决策。
| Agent | 模式 | 用途 | 可见性 |
|---|---|---|---|
build |
primary | 默认的主要开发 Agent | 公开 |
plan |
primary | 只读计划模式 | 公开 |
general |
subagent | 通用多任务 Agent | 公开 |
explore |
subagent | 代码探索专用 Agent | 公开 |
compaction |
primary | 上下文压缩 | 隐藏 |
title |
primary | 会话标题生成 | 隐藏 |
summary |
primary | 会话摘要生成 | 隐藏 |
build Agent 是默认的主开发 Agent。它的设计哲学是"最小化配置"——不设置额外的 prompt 属性,完全依赖 System Prompt(Provider 提示、环境信息、自定义规则)来指导行为。这种设计的好处是简洁,同时保留了最大的灵活性。在权限方面,build Agent 具有完整的操作权限,但有两个例外:doom_loop 和 external_directory 需要用户确认才能执行。
plan Agent 采用了"限制性设计"策略。它的核心用途是规划和阅读,因此它的权限配置禁止了所有编辑操作,只允许在 .opencode/plans/*.md 目录下创建文件。这种设计确保了 plan Agent 只能用于制定计划,而不会意外修改代码库。plan Agent 的 Prompt 是通过 insertReminders 函数动态注入的,这种方式可以让提示词参与上下文压缩。
explore Agent 是 subagent 模式的典型代表。它专门用于代码探索,因此它的权限被严格限制为只读操作:允许 grep、glob、list、read、codesearch 等查找和阅读类工具,但禁止 edit、bash 等可能产生副作用的操作。explore Agent 有自己专属的提示词文件(prompt/explore.txt),这个提示词会指导 Agent 如何系统性地探索代码库。
general Agent 是通用子 Agent,用于执行复杂的研究任务和多步骤任务。它的权限配置禁止了 todoread 和 todowrite 操作,这是为了避免与主 Agent 的任务管理产生冲突。
设计思路:内置 Agent 的配置差异体现了"按需定制"的原则。每个 Agent 都根据其职责被赋予了适当的权限和提示词,既保证了功能完整性,又实现了最小权限原则。
权限请求流程
每个 Agent 的权限在定义时就被设定:
整体流程图
┌──────────────────────────────────────────────────────────────────────────┐
│ 权限请求完整流程 │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ Tool.execute() │
│ │ │
│ ▼ │
│ ctx.ask({ permission, patterns, always, metadata }) │
│ │ │
│ ▼ │
│ PermissionNext.ask() │
│ │ │
│ ├─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ evaluate() ──命中 deny ──→ DeniedError evaluate() │
│ │ │ │
│ │ 未命中 │
│ │◀──────────────────────────────────────────────────────┘ │
│ │ ▲ │
│ ├─────────────────────────────────────────────────────────────┼ │
│ │ │ │
│ ▼ │ │
│ 命中 ask ──→ Promise 阻塞 ──→ Bus.publish(Event.Asked) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ TUI 显示权限对话框 │ │
│ │ │ │ │
│ │ ┌───────────┼───────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ Once Always Reject │ │
│ │ │ │ │ │ │
│ │ └───────────┴───────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Bus.publish(Event.Replied) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Promise resolve/reject │ │
│ │ │ │ │
│ └─────────────────────┴─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 继续执行或抛出错误 │
│ │
└──────────────────────────────────────────────────────────────────────────┘
配置项目权限规则
目标:通过配置文件控制项目权限
步骤:在 opencode.json 中添加权限配置:
{
"permission": {
"bash": {
"rm -rf *": "deny",
"chmod 777 *": "deny"
},
"read": {
"*.key": "deny",
"*.pem": "deny",
"secrets/**": "deny"
}
}
}
测试效果:
- 执行被禁止的命令应该立即失败
- 执行敏感文件读取应该被拒绝
创建只读 Agent
目标:创建一个禁止所有编辑操作的 Agent
Agent 配置:
{
"agent": {
"readonly": {
"name": "readonly",
"description": "Read-only analysis agent",
"mode": "primary",
"permission": {
"edit": "*:deny",
"write": "*:deny",
"bash": {
"*": "ask",
"rm *": "deny",
"mv *": "deny"
}
}
}
}
}
权限继承链
┌─────────────────────────────────────────────────────────────────┐
│ 权限继承流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ defaults (硬编码) │
│ │ │
│ ▼ │
│ PermissionNext.fromConfig({ │
│ "*": "allow", │
│ "*.env": "deny", │
│ ... │
│ }) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Agent Specific Permission │ │
│ │ │ │
│ │ plan: merge(defaults, { edit: { "*": "deny" } }, user) │ │
│ │ explore: merge(defaults, { "*": "deny", grep: "allow" }) │ │
│ │ general: merge(defaults, { todoread: "deny" }) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ user (opencode.json [permission]) │
│ │ │
│ ▼ │
│ Final Ruleset │
│ │
└─────────────────────────────────────────────────────────────────┘
Agent系统
Agent 系统 是 OpenCode 的核心执行引擎,负责协调 LLM 与工具的交互、管理会话状态和执行用户指令。
核心职责:
| 职责 | 说明 |
|---|---|
| 会话管理 | 创建和管理 Agent 会话 |
| 指令执行 | 接收用户指令,调用工具执行 |
| 状态维护 | 维护会话上下文和历史消息 |
| 权限控制 | 结合权限系统控制操作范围 |
| 上下文压缩 | 管理长对话的上下文摘要 |
┌─────────────────────────────────────────────────────────────────┐
│ OpenCode 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ │
│ │ CLI/TUI │───▶│ Agent System │───▶│ Provider │ │
│ └─────────────┘ └────────┬─────────┘ └─────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ build │ │ plan │ │ explore │ │
│ │ agent │ │ agent │ │ agent │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Tool │ │ Permission │ │ Session │ │
│ │ Registry │ │ System │ │ Manager │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Agent Info 结构
类型定义
Agent 的核心数据结构定义在 agent.ts:18-42:
// packages/opencode/src/agent/agent.ts:18-42
export const Info = z.object({
name: z.string(), // Agent 标识符
description: z.string().optional(), // Agent 功能描述
mode: z.enum(["subagent", "primary", "all"]), // 运行模式
native: z.boolean().optional(), // 是否内置 Agent
hidden: z.boolean().optional(), // 是否隐藏(不显示在列表)
topP: z.number().optional(), // Top-P 采样参数
temperature: z.number().optional(), // 温度参数
color: z.string().optional(), // UI 颜色标识
permission: PermissionNext.Ruleset, // 权限规则集
model: z.object({
modelID: z.string(), // 模型 ID
providerID: z.string(), // 提供商 ID
}).optional(), // 指定模型
prompt: z.string().optional(), // System Prompt
options: z.record(z.string(), z.any()), // 其他选项
steps: z.number().int().positive().optional(), // 最大步数
})
内置 Agent 详解
OpenCode 内置了 7 个核心 Agent:
┌─────────────────────────────────────────────────────────────────┐
│ 内置 Agent 矩阵 │
├────────────┬────────┬────────┬──────────────────────────────────┤
│ Name │ Mode │ Hidden │ Purpose │
├────────────┼────────┼────────┼──────────────────────────────────┤
│ build │ primary│ ❌ │ 默认开发 Agent 承担主要的代码编写和修改任务。 │
│ plan │ primary│ ❌ │ 只读计划模式 用于制定开发计划,不允许修改文件。 │
│ general │ subagent│ ❌ │ 通用研究 Agent 专门用于快速搜索和分析代码库。 │
│ explore │ subagent│ ❌ │ 代码探索 Agent 用于执行多步骤的研究和复杂任务。 │
│ compaction │ primary│ ✅ │ 上下文压缩(内部使用) │
│ title │ primary│ ✅ │ 标题生成(内部使用) │
│ summary │ primary│ ✅ │ 摘要生成(内部使用) │
└────────────┴────────┴────────┴──────────────────────────────────┘
动态 Agent 生成
generate.txt 用于根据用户描述生成新 Agent 配置:
// agent.ts:224-260
export async function generate(input: { description: string }) {
const defaultModel = await Provider.defaultModel()
const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID)
// 加载系统 prompt
const system = SystemPrompt.header(defaultModel.providerID)
system.push(PROMPT_GENERATE)
// 调用 LLM 生成 Agent 配置
const result = await generateObject({
messages: [
...system.map((item) => ({ role: "system", content: item })),
{
role: "user",
content: `Create an agent configuration based on this request: "${input.description}"`,
},
],
model,
schema: z.object({
identifier: z.string(),
whenToUse: z.string(),
systemPrompt: z.string(),
}),
})
return result.object
}
生成输出示例:
{
"identifier": "code-reviewer",
"whenToUse": "Use this agent when you need to review code for bugs, security issues, or style violations. Example: Review the recently added authentication module.",
"systemPrompt": "You are an expert code reviewer..."
}
Tool 工具系统
工具是 OpenCode 中 Agent 与外部环境交互的桥梁。每个 Tool 都是一个封装了特定功能的可调用单元,Agent 通过调用 Tool 来执行文件操作、命令执行、代码搜索等任务。
每个 Tool 由以下 四个核心部分 组成:
| 部分 | 说明 | 代码位置 |
|---|---|---|
| ID | 工具唯一标识符 | Tool.Info.id |
| Description | 工具功能描述(供 LLM 理解) | Tool.Info.init().description |
| Parameters | 参数模式(Zod Schema) | Tool.Info.init().parameters |
| Execute | 执行逻辑 | Tool.Info.init().execute |
内置工具列表
| 工具 | 功能 | 权限类型 |
|---|---|---|
read |
读取文件内容 | read |
write |
写入文件(完整覆盖) | edit |
edit |
编辑文件(局部修改) | edit |
glob |
文件 glob 模式匹配 | glob |
grep |
代码内容搜索 | grep |
bash |
执行 Shell 命令 | bash |
task |
调用子 Agent | task |
skill |
加载 Skill 技能 | skill |
webfetch |
抓取网页内容 | webfetch |
websearch |
网络搜索 | websearch |
codesearch |
代码库搜索 | codesearch |
todowrite |
写入待办事项 | todowrite |
todoread |
读取待办事项 | todoread |
list |
列出目录内容 | list |
Tool 命名空间详解
Tool 命名空间是整个 Tool 系统的核心,位于 packages/opencode/src/tool/tool.ts。三个核心类型:
| 类型 | 说明 |
|---|---|
Tool.Info |
工具定义接口 |
Tool.Context |
工具执行上下文 |
Tool.InitContext |
工具初始化上下文 |
Context 接口:工具执行上下文
Context 是工具执行时接收的上下文对象,提供了与系统交互的能力。
export type Context<M extends Metadata = Metadata> = {
sessionID: string // 会话 ID
messageID: string // 消息 ID
agent: string // 当前 Agent 名称
abort: AbortSignal // 中止信号
callID?: string // 调用 ID(可选)
extra?: { [key: string]: any } // 额外数据
metadata(input: { title?: string; metadata?: M }): void // 更新元数据
ask(input: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">): Promise<void> // 请求权限
}
Info 接口:工具定义
Info 是工具的静态定义接口,描述了工具的元数据和初始化逻辑。
export interface Info<Parameters extends z.ZodType = z.ZodType, M extends Metadata = Metadata> {
id: string
init: (ctx?: InitContext) => Promise<{
description: string
parameters: Parameters
execute(
args: z.infer<Parameters>,
ctx: Context,
): Promise<{
title: string
metadata: M
output: string
attachments?: MessageV2.FilePart[]
}>
formatValidationError?(error: z.ZodError): string
}>
}
define 函数:工具定义核心
define 是定义工具的核心函数,封装了参数验证和输出截断逻辑。
export function define<Parameters extends z.ZodType, Result extends Metadata>(
id: string,
init: Info<Parameters, Result>["init"] | Awaited<ReturnType<Info<Parameters, Result>["init"]>>,
): Info<Parameters, Result> {
return {
id,
init: async (initCtx) => {
const toolInfo = init instanceof Function ? await init(initCtx) : init
const execute = toolInfo.execute
// 包装 execute 函数,添加参数验证和输出截断
toolInfo.execute = async (args, ctx) => {
try {
toolInfo.parameters.parse(args)
} catch (error) {
if (error instanceof z.ZodError && toolInfo.formatValidationError) {
throw new Error(toolInfo.formatValidationError(error), { cause: error })
}
throw new Error(
`The ${id} tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`,
{ cause: error },
)
}
const result = await execute(args, ctx)
// 如果工具自己处理了截断,跳过自动截断
if (result.metadata.truncated !== undefined) {
return result
}
// 自动截断输出
const truncated = await Truncate.output(result.output, {}, initCtx?.agent)
return {
...result,
output: truncated.content,
metadata: {
...result.metadata,
truncated: truncated.truncated,
...(truncated.truncated && { outputPath: truncated.outputPath }),
},
}
}
return toolInfo
},
}
}
define 函数的三层封装:
| 层级 | 功能 |
|---|---|
| 第一层 | 参数验证:调用 parameters.parse(args) 验证参数 |
| 第二层 | 错误格式化:调用 formatValidationError 自定义错误消息 |
| 第三层 | 输出截断:调用 Truncate.output 处理大输出 |
InitContext 接口:初始化上下文
InitContext 是工具初始化时接收的上下文,用于获取 Agent 信息。
export interface InitContext {
agent?: Agent.Info // 当前 Agent 信息(可选)
}
使用场景: 工具可以根据当前 Agent 调整行为,如 TaskTool 根据调用者权限过滤可用的子 Agent。
使用示例 (packages/opencode/src/tool/task.ts:27-30):
const caller = ctx?.agent
const accessibleAgents = caller
? agents.filter((a) => PermissionNext.evaluate("task", a.name, caller.permission).action !== "deny")
: agents
工具注册机制
ToolRegistry 负责管理所有工具的注册、发现和查询。
核心职责:
| 职责 | 说明 |
|---|---|
| 工具注册 | 注册自定义工具 |
| 工具发现 | 发现和加载自定义工具(从 tool/ 目录) |
| 工具过滤 | 根据 Provider 和 Agent 过滤可用工具 |
| 插件集成 | 从 Plugin 加载工具定义 |
工具注册流程
工具列表来源 (packages/opencode/src/tool/registry.ts:89-112):
async function all(): Promise<Tool.Info[]> {
const custom = await state().then((x) => x.custom)
const config = await Config.get()
return [
InvalidTool,
BashTool,
ReadTool,
GlobTool,
GrepTool,
EditTool,
WriteTool,
TaskTool,
WebFetchTool,
TodoWriteTool,
TodoReadTool,
WebSearchTool,
CodeSearchTool,
SkillTool,
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []),
...(config.experimental?.batch_tool === true ? [BatchTool] : []),
...custom,
]
}
工具来源优先级:
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 | 内置工具 | 核心功能工具(read、bash、edit 等) |
| 2 | 实验性工具 | 根据 Flag 启用的工具(lsp、batch) |
| 3 | 自定义工具 | 从 tool/ 目录加载的工具 |
| 4 | 插件工具 | 从 Plugin 加载的工具 |
工具查询接口
获取所有工具 ID (packages/opencode/src/tool/registry.ts:114-116):
export async function ids() {
return all().then((x) => x.map((t) => t.id))
}
获取可用工具 (packages/opencode/src/tool/registry.ts:118-138):
export async function tools(providerID: string, agent?: Agent.Info) {
const tools = await all()
const result = await Promise.all(
tools
.filter((t) => {
// 根据 Provider 过滤工具
if (t.id === "codesearch" || t.id === "websearch") {
return providerID === "opencode" || Flag.OPENCODE_ENABLE_EXA
}
return true
})
.map(async (t) => {
using _ = log.time(t.id)
return {
id: t.id,
...(await t.init({ agent })), // 初始化工具
},
}),
)
return result
}
迭代信息收集
迭代信息收集模式 是 OpenCode 的核心决策机制,让 Agent 能够:
- 理解用户意图 - 分析用户问题的本质
- 评估信息需求 - 判断当前信息是否足够
- 探索收集信息 - 调用工具搜索代码库
- 迭代决策 - 根据收集结果决定是否继续
- 采取行动 - 信息足够后执行最终任务
与传统编程的区别:
传统程序: 输入 → 处理 → 输出 (单次执行)
OpenCode: 输入 → 分析 → 探索? → 收集 → 分析 → 探索? → ... → 行动
↑___________|
(循环迭代)
核心架构
┌─────────────────────────────────────────────────────────────────┐
│ 迭代信息收集架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Session Processor │ │
│ │ │ │
│ │ while (true) { ← 外层循环: 迭代决策 │ │
│ │ LLM.stream() ← 调用 LLM │ │
│ │ ↓ │ │
│ │ for await (value) { ← 内层循环: 处理流 │ │
│ │ switch (value.type) { │ │
│ │ case "tool-call": 执行工具 │ │
│ │ case "tool-result": 处理结果 │ │
│ │ case "text-delta": 构建响应 │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 工具层 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ glob │→ │ grep │→ │ read │→ │ bash │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ ↓ ↓ ↓ ↓ │ │
│ │ 发现文件 搜索内容 读取代码 验证结果 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Agent 层 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Explore Agent │ │ │
│ │ │ - 专用探索优化的 Agent │ │ │
│ │ │ - 白名单权限模式 │ │ │
│ │ │ - 快速文件搜索专家 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
处理器主循环
循环结构解析
处理器是整个迭代模式的核心,包含双层循环结构:
// processor.ts:44-397
export function create(input: {...}) {
const result = {
async process(streamInput: LLM.StreamInput) {
// === 外层循环:迭代决策 ===
while (true) {
try {
let currentText: MessageV2.TextPart | undefined
let reasoningMap: Record<string, MessageV2.ReasoningPart> = {}
// 调用 LLM 获取响应流
const stream = await LLM.stream(streamInput)
// === 内层循环:处理流事件 ===
for await (const value of stream.fullStream) {
input.abort.throwIfAborted()
switch (value.type) {
case "start":
// 开始处理
break
case "reasoning-start/delta/end":
// 处理思考过程
break
case "tool-call":
// 执行工具调用
break
case "tool-result":
// 处理工具结果
break
case "text-delta/end":
// 构建文本响应
break
case "finish-step":
// 步骤完成
break
}
// 检查是否需要压缩上下文
if (needsCompaction) break
}
} catch (e) {
// 错误处理和重试
}
// === 循环退出条件 ===
if (needsCompaction) return "compact" // 需要压缩
if (blocked) return "stop" // 被阻止
if (input.assistantMessage.error) return "stop" // 有错误
return "continue" // 继续迭代
}
}
}
return result
}
外层循环功能
| 功能 | 说明 | 代码位置 |
|---|---|---|
| 迭代控制 | 控制是否继续收集信息 | :48 |
| 错误处理 | 捕获和处理执行错误 | :335-359 |
| 重试机制 | 自动重试可恢复错误 | :341-352 |
| 上下文检查 | 检查是否需要压缩 | :270-272 |
| 退出决策 | 决定继续或停止 | :393-396 |
内层循环事件处理
事件类型表:
| 事件类型 | 说明 | 处理逻辑 |
|---|---|---|
start |
会话开始 | 设置状态为 busy |
reasoning-start |
思考开始 | 创建推理 Part |
reasoning-delta |
思考内容 | 增量更新推理 |
reasoning-end |
思考结束 | 完成推理 Part |
tool-input-start |
工具输入开始 | 创建工具 Part |
tool-call |
工具调用 | 执行工具 |
tool-result |
工具结果 | 更新结果状态 |
tool-error |
工具错误 | 处理错误 |
text-start |
文本开始 | 创建文本 Part |
text-delta |
文本内容 | 增量更新文本 |
text-end |
文本结束 | 完成文本 Part |
finish-step |
步骤完成 | 统计成本 Token |
finish |
完成 | 结束处理 |
循环退出条件
// processor.ts:393-396
if (needsCompaction) return "compact" // 上下文超限,需要压缩
if (blocked) return "stop" // 权限被拒绝
if (input.assistantMessage.error) return "stop" // 发生错误
return "continue" // 继续下一次迭代
工具执行流程
工具调用状态机:
┌─────────────────────────────────────────────────────────────────┐
│ 工具调用状态机 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ pending (等待) │
│ │ │
│ ▼ │
│ running (执行中) │
│ │ │
│ ├─────────────────────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ completed (完成) error (错误)
│ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 结果反馈给 LLM │
│ │ │
│ ▼ │
│ LLM 决定:继续调用工具?还是结束? │
│ │
└─────────────────────────────────────────────────────────────────┘
死循环检测机制
检测条件详解
// 条件 1: 至少有 3 个最近的工具调用
lastThree.length === DOOM_LOOP_THRESHOLD
// 条件 2: 所有调用都是同一个工具
lastThree.every((p) => p.tool === value.toolName)
// 条件 3: 所有调用都已完成(非 pending)
p.state.status !== "pending"
// 条件 4: 所有调用的输入完全相同
JSON.stringify(p.state.input) === JSON.stringify(value.input)
处理流程
检测到死循环
│
▼
请求 doom_loop 权限
│
├── 允许 (allow) → 继续执行
│
├── 拒绝 (deny) → 抛出 DeniedError
│
└── 询问 (ask) → 用户决定
完整执行流程
端到端流程图
┌─────────────────────────────────────────────────────────────────┐
│ 完整执行流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户输入: "修复认证模块的漏洞" │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Session Processor (step 1) │ │
│ │ - LLM 分析用户问题 │ │
│ │ - 决定: 需要更多信息 │ │
│ │ - 调用 @explore subagent │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Explore Agent (step 1.1) │ │
│ │ - glob("**/auth*.ts") → 发现 5 个文件 │ │
│ │ - grep("auth|login|verify") → 找到关键函数 │ │
│ │ - read("auth/controller.ts") → 分析代码 │ │
│ │ - 返回分析结果 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Session Processor (step 2) │ │
│ │ - 接收 explore 结果 │ │
│ │ - LLM 评估: 信息足够? │ │
│ │ - 决定: 可以开始修复 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 工具执行 (step 3) │ │
│ │ - edit("auth/controller.ts") → 修复漏洞 │ │
│ │ - read("auth/service.ts") → 验证依赖 │ │
│ │ - bash("npm test") → 运行测试 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 结果返回 │ │
│ │ - 输出修复总结 │ │
│ │ - 更新文件差异 │ │
│ │ - 完成会话 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
详细时序图
用户 LLM SessionProcessor Explore Agent 工具
│ │ │ │ │
│ "修复漏洞" │ │ │ │
│─────────────→│ │ │ │
│ │ 分析问题 │ │ │
│ │→──需要探索───────→│ │ │
│ │ │ 调用 @explore │ │
│ │ │────────────────────→│ │
│ │ │ │ glob("auth*") │
│ │ │ │──────────────→│
│ │ │ │ 发现 5 文件 │
│ │ │ │←──────────────│
│ │ │ │ grep("auth") │
│ │ │ │──────────────→│
│ │ │ │ 找到 10 处 │
│ │ │ │←──────────────│
│ │ │ │ read(文件) │
│ │ │ │──────────────→│
│ │ │ │ 返回代码 │
│ │ │ │←──────────────│
│ │ │ 返回探索结果 │ │
│ │←──────────────────│←────────────────────│ │
│ │ │ │ │
│ │ 评估: 信息足够 │ │ │
│ │→──可以修复───────→│ │ │
│ │ │ edit("auth.ts") │ │
│ │ │──────────────────────────────────→│
│ │ │ │ │ 修复代码
│ │ │ │ │←─────────│
│ │ │ │ │
│ │ 总结修复结果 │ │ │
│←─────────────│ │ │ │
│ │ │ │ │
更多推荐




所有评论(0)