OpenCode — 开源 AI 编程 Agent
OpenCode 是由 Anomaly 开发的开源 AI 编程 Agent, 支持终端(TUI)、桌面应用、IDE 扩展三种使用形态。 GitHub 190K+ Stars,月活开发者 750 万+, 支持 75+ LLM 提供商及本地模型,是目前社区最活跃的 AI 编程工具之一。
| 仓库 | github.com/anomalyco/opencode |
|---|---|
| 官网 | opencode.ai |
| 许可证 | MIT |
| 技术栈 | TypeScript, Bun, SST (Serverless Stack) |
| 安装 | npm / brew / pacman / scoop / choco / curl install script |
1. 概述
OpenCode 是一个AI 编码代理(Coding Agent),能理解自然语言指令, 自主调用工具(读写文件、执行命令、搜索代码、获取网页等)来完成软件工程任务。 它不只是一个代码补全工具,而是一个能理解项目上下文、制定计划、执行多步操作的智能体。
核心特性
- LSP 集成 — 自动加载语言服务器,为 LLM 提供代码智能(跳转定义、引用查找等)
- 多会话并行 — 在同一项目中启动多个 Agent 并行工作
- 会话分享 — 生成可分享的会话链接,用于协作或调试
- GitHub Copilot 兼容 — 用 GitHub 账号登录即可使用 Copilot 配额
- ChatGPT Plus/Pro 兼容 — 用 OpenAI 账号登录即可使用订阅模型
- 75+ 提供商 — 通过 Models.dev 支持任意 LLM 提供商,包括本地模型
- 多种界面 — 终端界面(TUI)、桌面应用、IDE 扩展(VS Code 等)
- 隐私优先 — 不存储任何代码或上下文数据
AI Agent 能力边界
- 能做的:读/写/编辑文件、执行 bash 命令、搜索代码、获取网页、管理 TODO、加载 Skills、启动子 Agent
- 受控的:通过权限系统(permission)精细控制每个工具的执行策略(allow/ask/deny)
- 不能做的:无法突破权限限制、无法访问被 deny 的外部目录、无法绕过企业策略
OpenCode 这类工具叫什么
OpenCode 这类工具在业界通常被称为 AI Agent(AI 智能体), 更精确地说是 AI 编程 Agent(AI Coding Agent / 编程代理)。 它与普通 Chatbot 的本质区别在于能自主完成任务—— 读代码、执行命令、改文件、验证结果,而不只是做单轮问答(那种只叫 Chatbot/Assistant)。
| 叫法 | 说明 | 典型工具 |
|---|---|---|
| AI Coding Agent / 编程代理 | 最通用的说法,强调自主完成软件工程任务 | OpenCode、Claude Code、Devin |
| Agentic IDE / Agent 化编辑器 | 内嵌在 IDE 里的形态 | Cursor、Windsurf |
| CLI Agent / 终端智能体 | 命令行形态 | OpenCode、Claude Code CLI |
Harness Engineering(驾驭工程)
Harness(缰绳/驾驭)工程指在构建 AI Agent 时, 把模型自由度较大的输出"驾驭"到一个安全、可控、可审计、可终止的交互框架里的工程实践。 模型本身能力不稳定,不能让它随意执行,因此需要设计一层约束,让 Agent 的行为可预测、可控、可终止。
Harness 层典型内容:
- 工具调用约束 — 只允许调用白名单工具,参数必须符合 schema(如 OpenCode 的 Bash/Edit/Write)
- 权限与批准机制 — 危险操作需用户确认(permission rules)
- 循环控制 — 限制最大步数、超时、防死循环(如 OpenCode 的 maxSteps)
- 沙箱/隔离 — 命令在受限环境执行,防止模型失控
- 状态持久化 — 会话管理,跨步骤保持上下文
- 错误恢复 — 工具失败时引导模型重新规划
1.1 CLI 前端技术栈
OpenCode 的终端界面(TUI)基于自研框架 OpenTUI,使用 SolidJS 作为响应式 UI 层,直接渲染到终端。
| 层级 | 技术 | 说明 |
|---|---|---|
| TUI 框架 | @opentui/core + @opentui/solid + @opentui/keymap |
Anomaly 自研的终端 UI 框架,将 SolidJS 组件渲染为 ANSI 终端输出,支持键盘映射 |
| UI 框架 | SolidJS | 细粒度响应式 UI 库,编译后无虚拟 DOM,性能优于 React/Vue |
| 运行时 | Bun | JavaScript/TypeScript 运行时 + 包管理器 + 打包器,取代 Node.js |
| 语言 | TypeScript | 全栈 TypeScript,统一前后端类型 |
| 终端模拟 | @lydell/node-pty |
在 TUI 内运行子进程(shell 命令、git 操作等)并捕获输出 |
| Markdown 渲染 | marked + shiki |
将 LLM 返回的 Markdown 转换并高亮渲染到终端 |
| 模糊搜索 | fuzzysort |
@ 文件引用、命令面板等场景的即时模糊匹配 |
| 状态管理 | effect (Effect-TS) |
函数式 Effect 系统,管理异步操作、错误处理、资源生命周期 |
| Diff 算法 | @pierre/diffs + diff |
代码变更的差异计算与终端内 diff 预览 |
2. 行为分析(做什么)
以下从行为视角分析 OpenCode 的功能边界与运行流程。
2.1 用例图 — 用户与 OpenCode 的交互全景
展示三类主要参与者与 OpenCode 系统的功能交互边界。
2.2 活动图 — 一次典型 Session 的执行流程
展示从用户输入到代码变更的完整活动链,覆盖 Plan 模式与 Build 模式的分支。
2.3 序列图 — System Prompt 组装与 ReAct 循环(以 DeepSeek V4 Pro 为例)
以 DeepSeek V4 Pro 为例(模型 ID 不命中 claude/gpt/gemini/kimi/trinity 等关键词,兜底使用 default.txt)。该 Prompt 中无显式 ReAct 自动继续指令,循环全由代码层驱动。序列图分两阶段:System Prompt 组装 + ReAct 循环的完整退出决策链。
ReAct 循环退出条件总览(prompt.ts:1088 while(true))
OpenCode 的 ReAct 循环不只依赖 LLM 有无 tool_call 一个条件来判定是否终止,完整的退出决策包含 7 个判定条件,分布在循环体头部、尾部和 Processor 内部:
| # | 位置 | 判定条件 | 行为 |
|---|---|---|---|
| 1 | prompt.ts:1111-1130(循环体头部) |
上一轮 assistant 已标记 finish 且 finish ≠ "tool-calls",且当前 assistant 消息中没有待执行的 tool_call |
break — 正常结束。若存在孤儿中断工具则记录 warn 日志后退出 |
| 2 | prompt.ts:1157(compaction 任务) |
上下文压缩处理的返回值为 "stop"(压缩过程出错/无意义) |
break — 中止会话。正常压缩则 continue |
| 3 | prompt.ts:1280 + runner/llm.ts:198(maxSteps 上限) |
step >= agent.steps:达该 Agent 的步数上限 |
注入 MAX_STEPS_PROMPT 强制 LLM 用纯文本总结;V2 额外设 toolChoice:"none" 协议层禁用工具 |
| 4 | prompt.ts:1287-1292(structured output 已捕获) |
用户请求了 json_schema 格式输出,LLM 调用 StructuredOutput 工具成功返回 |
break — 结构化输出已收到,不需要继续循环 |
| 5 | prompt.ts:1300-1307(content-filter 过滤) |
助手已完成(finish 存在且不是 "tool-calls"/"unknown"),且 finish === "content-filter" |
break — 提供商内容过滤器拦截,返回错误给用户 |
| 6 | prompt.ts:1318+ processor.ts:631(工具被拒/被阻断) |
Processor 返回 "stop":工具的权限请求被用户 deny/reject,且 experimental.continue_loop_on_deny !== true(默认 false) |
break — 工具被拒后循环终止。若开启 continue_loop_on_deny 实验性开关则 shouldBreak = false,LLM 会看到 error tool_result 并尝试替代方案 |
| 7 | prompt.ts:1319-1327(上下文溢出→压缩) |
Processor 返回 "compact" — 上下文 token 超过模型窗口上限 |
创建 compaction 任务 → continue。下一轮循环头部会处理 compaction,压缩后 自动继续(注入 "Continue if you have next steps...") |
3. 结构分析(由什么组成)
以下从结构视角分析 OpenCode 的组件架构与核心抽象。
3.1 组件图 — 系统宏观架构
展示 OpenCode 的分层组件架构及组件间的依赖关系。
3.2 概念类图 — 核心抽象与关系
展示 OpenCode 中关键概念实体及其关系,不涉及具体实现细节。
4. 配置示例与 Demo
4.1 基础配置(opencode.json)
项目级配置,设置模型、权限、自动更新等。
{
"$schema": "https://opencode.ai/config.json",
"model": "anthropic/claude-sonnet-4-5",
"small_model": "anthropic/claude-haiku-4-5",
"autoupdate": true,
"share": "manual",
"snapshot": true,
"instructions": ["CONTRIBUTING.md", "docs/guidelines.md"],
"permission": {
"edit": "allow",
"bash": {
"*": "ask",
"git *": "allow",
"npm *": "allow",
"rm *": "deny"
}
},
"provider": {
"anthropic": {
"options": {
"timeout": 600000,
"setCacheKey": true
}
}
},
"disabled_providers": ["openai"],
"compaction": {
"auto": true,
"prune": false,
"reserved": 10000
}
}
4.2 自定义 Agent
JSON 方式:
{
"agent": {
"code-reviewer": {
"description": "Reviews code for best practices and potential issues",
"mode": "subagent",
"model": "anthropic/claude-sonnet-4-5",
"temperature": 0.1,
"steps": 10,
"permission": {
"edit": "deny",
"bash": "deny"
}
}
}
}
Markdown 方式(.opencode/agents/docs-writer.md):
---
description: Writes and maintains project documentation
mode: subagent
permission:
bash: deny
---
You are a technical writer. Create clear, comprehensive documentation.
Focus on:
- Clear explanations
- Proper structure
- Code examples
- User-friendly language
4.3 自定义 Skill
在 .opencode/skills/git-release/SKILL.md 中定义可复用技能:
---
name: git-release
description: Create consistent releases and changelogs
license: MIT
compatibility: opencode
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command
## When to use me
Use this when you are preparing a tagged release.
Ask clarifying questions if the target versioning scheme is unclear.
4.4 自定义 Command
{
"command": {
"test": {
"template": "Run the full test suite with coverage report.\nShow any failures and suggest fixes.",
"description": "Run tests with coverage",
"agent": "build"
},
"component": {
"template": "Create a new React component named $ARGUMENTS with TypeScript.",
"description": "Create a new component"
}
}
}
5. 实战速查手册
5.1 CLI 命令速查
| 命令 | 说明 | 常用参数 |
|---|---|---|
opencode | 启动 TUI | -c 继续上次会话, -m model |
opencode run "msg" | 非交互模式执行 | --auto, --agent, --model |
opencode serve | 启动 HTTP API 服务 | --port, --hostname |
opencode web | 启动 Web 界面 | --port |
opencode attach <url> | 连接远程 OpenCode 后端 | -c, -s sessionID |
opencode agent create | 交互式创建 Agent | --path, --description |
opencode agent list | 列出所有 Agent | |
opencode auth login | 配置 LLM 提供商 | -p provider |
opencode models | 列出可用模型 | --refresh, --verbose |
opencode session list | 列出会话历史 | -n N |
opencode stats | 查看 Token 用量/费用 | --days N |
opencode export [id] | 导出会话 | --sanitize |
opencode import <file> | 导入会话 | |
opencode mcp add | 添加 MCP 服务器 | |
opencode upgrade [ver] | 升级版本 | -m method |
opencode debug config | 调试配置解析 | |
opencode uninstall | 卸载 | --keep-config |
5.2 内置工具速查
| 工具 | 功能 | 权限键 |
|---|---|---|
bash | 执行 shell 命令 | bash |
edit | 精确字符串替换编辑文件 | edit |
write | 创建/覆写文件 | edit(共用) |
apply_patch | 应用 patch 文件 | edit(共用) |
read | 读取文件(支持行范围) | read |
grep | 正则搜索文件内容 | grep |
glob | 通配符匹配文件路径 | glob |
task | 启动子 Agent | task |
skill | 加载 SKILL.md | skill |
todowrite | 管理 TODO 列表 | todowrite |
webfetch | 获取网页内容 | webfetch |
websearch | 网络搜索(Exa) | websearch |
question | 向用户提问 | question |
lsp | LSP 代码智能(实验性) | lsp |
external_directory(默认 ask)控制访问工作区外目录;doom_loop(默认 ask)检测同一工具调用重复 3 次时触发。
5.3 内置 Agent 速查
| Agent | 类型 | 能力 | 典型用途 |
|---|---|---|---|
| Build | Primary(默认) | 全部工具启用 | 日常开发、编写代码 |
| Plan | Primary | 只读分析,edit/bash 需审批 | 代码分析、制定计划、探索未知代码库 |
| General | Subagent | 全工具(无 todo),可写文件 | 复杂搜索、多步任务、并行工作 |
| Explore | Subagent | 只读,不可修改文件 | 快速搜索文件/关键词、理解代码库 |
| Scout | Subagent | 只读,用于外部依赖研究 | 克隆依赖仓库、检查库源码、对照上游实现 |
| Compaction | Primary(隐藏) | 自动压缩长上下文 | 系统自动触发 |
| Title | Primary(隐藏) | 生成会话标题 | 系统自动触发 |
| Summary | Primary(隐藏) | 生成会话摘要 | 系统自动触发 |
@agent-name 手动调用 Subagent。可通过 opencode.json 中的 default_agent 设置默认 Agent。
5.4 配置键速查
| 配置键 | 类型 | 说明 |
|---|---|---|
model | string | 主模型 (provider/model-id) |
small_model | string | 轻量任务模型(标题生成等) |
default_agent | string | 默认 Agent (build / plan) |
subagent_depth | int | 子 Agent 嵌套深度(默认 1) |
provider | object | 提供商配置(timeout, baseURL, models 等) |
permission | object / string | 工具权限(allow / ask / deny) |
agent | object | 自定义 Agent 定义 |
command | object | 自定义命令模板 |
tools | object | 全局工具开关(废弃,建议用 permission) |
instructions | string[] | 额外指令文件路径(支持 glob / URL) |
plugin | string[] | 插件 npm 包列表 |
mcp | object | MCP 服务器配置 |
lsp | bool / object | LSP 语言服务器配置 |
formatter | bool / object | 代码格式化器配置 |
shell | string | Shell 类型 (zsh / bash / pwsh) |
server | object | HTTP 服务器设置(port, hostname, cors, mdns) |
share | string | 分享模式(manual / auto / disabled) |
snapshot | bool | 启用文件快照(用于 /undo) |
autoupdate | bool / "notify" | 自动更新 |
compaction | object | 上下文压缩(auto, prune, reserved) |
watcher | object | 文件监控忽略模式 |
attachment.image | object | 图片附件尺寸限制 |
disabled_providers | string[] | 禁用的提供商 ID |
enabled_providers | string[] | 启用的提供商白名单 |
experimental | object | 实验性功能开关 |
配置加载优先级
- Remote config —
.well-known/opencode(组织默认) - Global config —
~/.config/opencode/opencode.json(用户偏好) - Custom config —
OPENCODE_CONFIG环境变量 - Project config — 项目根
opencode.json - .opencode 目录 — agents、commands、plugins、skills
- Inline config —
OPENCODE_CONFIG_CONTENT环境变量 - Managed config — 系统管理路径(macOS/Linux/Windows)
- macOS MDM —
.mobileconfig托管(最高优先级)
权限模型速查
| 值 | 行为 |
|---|---|
"allow" | 无需审批,自动执行 |
"ask" | 弹出审批提示(once / always / reject) |
"deny" | 完全阻止 |
权限键支持通配符匹配,最后匹配规则生效。典型模式:
{
"permission": {
"bash": {
"*": "ask",
"git status *": "allow",
"rm *": "deny"
}
}
}
TUI 常用按键
| 操作 | 快捷键 |
|---|---|
| 切换 Agent(Plan ↔ Build) | Tab |
| 提交消息 | Enter |
| 命令面板 | Ctrl+P(可配置) |
| 撤销变更 | /undo |
| 重做变更 | /redo |
| 初始化项目 | /init |
| 连接提供商 | /connect |
| 选择模型 | /models |
| 分享会话 | /share |
| @ 文件引用 | @ + 模糊搜索 |
6. Agent System Prompt 深度梳理
以下从 ReAct 范式 出发,梳理 OpenCode 如何通过 Prompt 设计实现 Reasoning + Acting 的自主循环。源码路径:packages/opencode/src/session/prompt/、packages/opencode/src/agent/prompt/、packages/core/src/session/runner/。
6.1 ReAct 循环总览
while(true) 循环
反复执行 推理(Thought) → 行动(Tool Call) → 观察(Tool Result) → 继续推理 → ... 直到任务完成或达到步数上限。
V1 循环(packages/opencode/src/session/prompt.ts:1081-1338 runLoop())和 V2 循环(packages/core/src/session/runner/llm.ts:378-401)共享同一核心模式:
- LLM 推理 → 产生文本或工具调用(tool_call)
- 权限检查 → allow/ask/deny
- 执行工具 → 产生输出(tool_result)
- 工具结果发送回 LLM → LLM 观察结果,决定下一步
- 继续循环 → 直到 LLM 输出纯文本(无工具调用),或到达 maxSteps 上限
6.2 Prompt 中驱动 ReAct 循环的关键文本
不同模型版本的系统提示词中,编码了不同强度的 ReAct 循环指令。以下是各模型版本中明确驱回 ReAct 行为的原文:
| 模型 | 文件 | ReAct 强度 | 关键驱动文本(原文摘录) |
|---|---|---|---|
| beast (gpt-4/o1/o3) |
beast.txt |
最强 ★★★★★ |
"You are opencode, an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user." "You MUST iterate and keep going until the problem is solved." "Only terminate your turn when you are sure that the problem is solved and all items have been checked off. Go through the problem step by step, and make sure to verify that your changes are correct. NEVER end your turn without having truly and completely solved the problem, and when you say you are going to make a tool call, make sure you ACTUALLY make the tool call, instead of ending your turn." "You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully." "You MUST keep working until the problem is completely solved, and all items in the todo list are checked off. Do not end your turn until you have completed all steps..." "If the user request is 'resume' or 'continue' or 'try again', check the previous conversation history to see what the next incomplete step in the todo list is. Continue from that step, and do not hand back control to the user until the entire todo list is complete..." |
| copilot-gpt-5 | copilot-gpt-5.txt |
最强 ★★★★★ |
"You are an agent - you must keep going until the user's query is completely resolved, before ending your turn and yielding back to the user." "You MUST iterate and keep going until the problem is solved." "Do not repeat yourself after tool calls; continue from where you left off." |
| kimi | kimi.txt |
较强 ★★★★ |
"The results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information." "Do not give up too early." |
| gemini | gemini.txt |
较强 ★★★★ |
"Finally, you are an agent - please keep going until the user's query is completely resolved." |
| anthropic (claude) |
anthropic.txt |
中等 ★★★ |
"You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress." — 通过 TodoWrite 间接驱动多步执行。 "It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed." "If the user asks for a 'resume' or 'continue'..." — 支持续跑指令。 |
| default | default.txt |
隐式 ★★ |
没有显式的"必须循环/不走好不停"指令。依靠工具使用示例(如 write tests example)隐式展示多步工作流,依赖模型自身的推理能力驱动循环。 |
| trinity | trinity.txt |
隐式 ★★ |
"Use exactly one tool per assistant message. After each tool call, wait for the result before continuing." — 强制串行单步,天然的 ReAct 节奏但无循环驱动力。 |
| gpt | gpt.txt |
隐式 ★★ |
"Persist until the task is fully handled end-to-end within the current turn whenever feasible: do not stop at analysis or partial fixes; carry changes through implementation, verification, and a clear explanation of outcomes..." |
| codex | codex.txt |
隐式 ★★ |
"Default: do the work without asking questions. Treat short tasks as sufficient direction; infer missing details by reading the codebase and following existing conventions." |
6.3 消息结构:ReAct 循环的物质基础
ReAct 循环之所以能运行,核心在于 工具调用/结果的往返消息格式。每一次 ReAct 循环步(step)在协议层就是一组消息:
┌─────────────────────────────────────────────┐
│ Step N: assistant(tool_call: read "foo.ts")│ ← LLM 推理出"需要读文件" → 行动
│ ↓ │
│ tool_result("foo.ts" 的内容) │ ← 工具执行完毕 → 观察
│ ↓ │
│ Step N+1: assistant(tool_call: edit "foo.ts")│ ← LLM 根据结果决定下一步 → 再行动
│ ↓ │
│ tool_result("edit OK") │
│ ↓ │
│ Step N+2: assistant(text: "修改完成") │ ← LLM 判断任务完成 → 输出纯文本 → 循环结束
└─────────────────────────────────────────────┘
消息转换源码:
| 文件 | 功能 |
|---|---|
packages/opencode/src/session/message-v2.ts:131-415 | toModelMessagesEffect() — 将内部 ToolPart 状态(pending/running/completed/error)转换为 AI SDK 的 tool_call/tool_result 消息对 |
packages/core/src/session/runner/to-llm-message.ts:30-113 | V2 的消息转换:ToolCallPart.make() + ToolResultPart.make() |
packages/opencode/src/session/llm/ai-sdk.ts:220-247 | tool-call / tool-result 事件格式定义 |
message-v2.ts:349-360 确保每个 tool_use 都有对应的 tool_result — 如果工具未完成就被中断(pending/running),会生成 error tool_result:"[Tool execution was interrupted]"。这是因为 Anthropic/Claude 协议要求每个 tool_use 必须有配套 tool_result。
6.4 ReAct 循环的工程约束
6.4.1 Max Steps 上限
循环不是无限的。每个 Agent 有 steps 配置(默认 Infinity),当到达上限时注入 MAX_STEPS_PROMPT:
CRITICAL - MAXIMUM STEPS REACHED
The maximum number of steps allowed for this task has been reached. Tools are
disabled until next user input. Respond with text only.
STRICT REQUIREMENTS:
1. Do NOT make any tool calls
2. MUST provide a text response summarizing work done so far
3. This constraint overrides ALL other instructions
Response must include:
- Statement that maximum steps have been reached
- Summary of what has been accomplished
- List of any remaining tasks not completed
- Recommendations for next steps
prompt.ts:1280)到达上限时注入该 Prompt 作为 assistant 消息让 LLM 总结;V2(runner/llm.ts:206)除此还设置 toolChoice: "none" 在协议层禁用工具调用。
6.4.2 Doom Loop 检测
packages/opencode/src/session/processor.ts:354-377 — 如果同一 assistant 消息的最后 3 次工具调用(DOOM_LOOP_THRESHOLD = 3)是相同工具+相同参数,触发 doom_loop 权限请求,阻止 LLM 陷入死循环。
6.4.3 续跑提示
过程中多处注入提示促使 LLM 继续循环:
| 场景 | 位置 | 文本 |
|---|---|---|
| 子 Agent 返回 | prompt.ts:446 | "Summarize the task tool output above and continue with your task." |
| 上下文压缩后 | compaction.ts:481-485 | "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed." |
| Plan→Build 切换 | build-switch.txt | "Your operational mode has changed from plan to build. You are no longer in read-only mode. You are permitted to make file changes, run shell commands..." |
6.5 Prompt 组装流程
组装公式(V1 路径):
// 1. 选择基础 System Prompt(根据 model ID)
system = agent.customPrompt || model-base.txt
// 2. 拼接用户自定义 system(来自 opencode.json config 的 system 字段)
system += user.system
// 3. 拼接环境上下文
system += env_info // model, workspace, git status, platform, date
// 4. 拼接指令文件
system += AGENTS.md / CLAUDE.md / project instructions
// 5. 拼接工具上下文
system += MCP tools + available skills list
关键源码位置:
| 文件 | 行 | 功能 |
|---|---|---|
packages/opencode/src/session/system.ts | 26-40 | provider() — 根据 model ID 选择 .txt 提示词 |
packages/opencode/src/session/system.ts | 58-126 | environment() / skills() / mcp() — 生成环境/技能/MCP 上下文 |
packages/opencode/src/session/llm/request.ts | 58-66 | LLM 请求准备:拼接 agent.prompt + model-base + user.system |
packages/opencode/src/session/prompt.ts | 1081-1338 | V1 ReAct while(true) 循环(核心!)— runLoop() |
packages/core/src/session/runner/llm.ts | 378-401 | V2 ReAct 循环 — while(needsContinuation) |
packages/opencode/src/session/instruction.ts | 155-169 | Instruction.system() — 加载 AGENTS.md / CLAUDE.md |
packages/opencode/src/session/reminders.ts | 15-90 | SessionReminders.apply() — 注入 plan/build mode 提示 |
packages/core/src/session/runner/max-steps.ts | 1-16 | MAX_STEPS_PROMPT — 步数上限提示 |
packages/opencode/src/session/processor.ts | 354-377 | DOOM_LOOP_THRESHOLD — 死循环检测 |
6.6 模型专属 System Prompt 汇总
位于 packages/opencode/src/session/prompt/*.txt,由 SystemPrompt.provider(model) 选择。
| Match | 文件 | 行数 | 身份定义(第一句话) |
|---|---|---|---|
| 默认 | default.txt | 95 | "You are opencode, an interactive CLI tool that helps users with software engineering tasks." |
claude | anthropic.txt | 105 | "You are OpenCode, the best coding agent on the planet." |
gpt-4/o1/o3 | beast.txt | 147 | "You are opencode, an agent - please keep going until the user's query is completely resolved..." |
gemini- | gemini.txt | 155 | "You are opencode, an interactive CLI agent specializing in software engineering tasks." |
gpt(其他) | gpt.txt | 107 | "You are OpenCode, You and the user share the same workspace and collaborate to achieve the user's goals." |
gpt+codex | codex.txt | 79 | "You are OpenCode, the best coding agent on the planet." |
trinity | trinity.txt | 97 | "You are opencode, an interactive CLI tool..." |
kimi | kimi.txt | 95 | "You are OpenCode, an interactive general AI agent running on a user's computer." |
copilot-gpt-5 | copilot-gpt-5.txt | 143 | "You are an expert AI programming assistant. Your name is opencode." — 含 GPT-specific 工具(semantic_search, run_in_terminal) |
packages/opencode/src/session/system.ts:26-40):按优先级 claude > gpt-4/o1/o3 > gemini- > gpt/codex > trinity > kimi > default 依次匹配 model ID。
6.7 子 Agent 专用 Prompt
子 Agent 启动时用自己的 System Prompt 替换主 Agent 的模型专属 Prompt。位于 packages/opencode/src/agent/prompt/*.txt,packages/core/src/plugin/agent.ts 中有等价 TypeScript 常量。
| Agent | 文件 | System Prompt | 约束 |
|---|---|---|---|
| Build | agent.ts:12-13 | "You are an AI coding agent. Help the user accomplish software engineering tasks by inspecting the workspace, making targeted changes, and using tools according to the configured permissions." | 13 词超短指令 |
| Explore | explore.txt | "You are a file search specialist. You excel at thoroughly navigating and exploring codebases." | 只读,不可修改文件 |
| Compaction | compaction.txt | "You are an anchored context summarization assistant for coding sessions." | 只总结,不回答对话 |
| Title | title.txt | "You are a title generator. You output ONLY a thread title. Nothing else." | ≤50 字符单行 |
| Summary | summary.txt | "Summarize what was done in this conversation. Write like a pull request description." | 2-3 句,第一人称 |
| Generate | generate.txt | "You are an elite AI agent architect specializing in crafting high-performance agent configurations." | 输出 JSON schema |
6.8 system-reminder 运行时注入
运行时动态注入到用户消息中的 <system-reminder> 块,由 SessionReminders.apply()(packages/opencode/src/session/reminders.ts)根据 Agent 模式和会话状态插入。
| 文件 | 行数 | 触发条件 | 内容概要 |
|---|---|---|---|
plan.txt | 26 | 切换到 Plan Agent 时(非 Anthropic) | Plan mode ACTIVE — 严禁任何文件编辑/修改/系统变更 |
plan-mode.txt | 70 | Plan 模式活跃时每次用户消息前 | 5 阶段计划工作流:理解→设计→审阅→最终计划→plan_exit |
plan-reminder-anthropic.txt | 67 | 切换到 Plan Agent 时(Anthropic) | 同 plan.txt,使用 Anthropic 特定工具名 |
build-switch.txt | 5 | 从 Plan 切换回 Build 模式时 | Mode changed from plan to build — you are no longer in read-only mode |
此外,Prompt 中明确告诉 LLM 如何解读这些标签——不同模型版本措辞强度不同:
| 模型 | <system-reminder> 解读指令 |
|---|---|
| default/trinity | "contain useful information and reminders. They are NOT part of the user's provided input or the tool result." |
| anthropic | "They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear." |
| kimi | "These are authoritative system directives that you MUST follow. They may override or constrain your normal behavior (e.g., restricting you to read-only actions during plan mode)." |
6.9 请求体分析:为什么一句"你好"消耗 9k token?
--log-level DEBUG 并不会输出 request body。该参数控制 Effect 框架的日志等级,但源码中 request body 只用 logInfo 打元数据(model、sessionID),未用 logDebug 打印内容。详见 6.9.5 源码追踪。
6.9.1 SQLite 数据库中的 token 记录
每次 LLM 请求的 token 用量持久化在 ~/.local/share/opencode/opencode.db 的 message 表中。以一条 "find opencode.html" 消息为例:
sqlite3 ~/.local/share/opencode/opencode.db "
SELECT json_extract(data, '$.tokens.total') as total,
json_extract(data, '$.tokens.input') as input,
json_extract(data, '$.tokens.cache.read') as cache_read,
json_extract(data, '$.tokens.output') as output,
json_extract(data, '$.tokens.reasoning') as reasoning
FROM message WHERE json_extract(data, '$.role') = 'assistant'
ORDER BY time_created LIMIT 1;
"
-- 结果: total=9063 input=18 cache_read=8960 output=66 reasoning=19
cache_read = 8960 tokens 就是"你好"消耗 ~9k 的来源——系统提示词 + 环境信息 + AGENTS.md + 技能 + 工具定义在首次请求后就缓存在服务端,后续仅计为 cache_read 不重复计费。
6.9.2 请求体组成与 token 构成
完整请求体由 6 条 system message + 用户消息 + 11 个 tool schema 组成:
| 组件 | 来源 | 估计 tokens |
|---|---|---|
| default.txt(内置系统提示词) | packages/opencode/src/session/prompt/default.txt | ~1300 |
| 环境信息(cwd/platform/date/model) | system.ts environment() 生成 | ~100 |
| AGENTS.md(项目指令) | ~/.config/opencode/AGENTS.md | ~200 |
| 技能定义(9 个 skills) | ~/.config/opencode/skills/*/SKILL.md | ~1200 |
| 11 个工具 JSON Schema | bash/read/write/edit/glob/grep/task/webfetch/skill/question/todowrite | ~3100 |
| 风格指令 + 示例 + policy 等 | default.txt 后半部分 | ~3060 |
| cache_read 合计 | ~8960 | |
| 用户消息 "你好" | input tokens | ~5 |
| 首轮 total | ~9000 |
cache_read 9600 tokens 构成:
default.txt ████████████████░░░░░ 1300 (14.5%)
风格指令+示例 ██████████████████████████████████████░░ 3060 (34.1%)
工具 JSON Schema ███████████████████████████████████████ 3100 (34.6%)
技能定义 ██████████████░░ 1200 (13.4%)
AGENTS.md ██░ 200 (2.2%)
环境信息 █░ 100 (1.1%)
6.9.3 请求组装流程(源码级)
每次 LLM 调用的请求体在 llm/request.ts 的 LLMRequestPrep.prepare() 中内存组装,组装完直接传给 streamText(),中间无任何日志或持久化:
// prompt.ts:1257-1286 — 每个 loop 迭代组装 system + messages + tools
const [skills, env, instructions, mcpInstructions, modelMsgs] =
yield* Effect.all([
sys.skills(agent), // 技能描述
sys.environment(model), // 环境信息 + AGENTS.md 内容
instruction.system().pipe(Effect.orDie), // AGENTS.md 路径
sys.mcp(agent, session.permission), // MCP 指令(如有)
MessageV2.toModelMessagesEffect(msgs, model), // 对话历史
])
const system = [...env, ...instructions, ...(mcpInstructions ? [mcpInstructions] : []), ...(skills ? [skills] : [])]
// llm/request.ts:104 — 拼接 final messages
const messages = [
...system.map((x) => ({ role: "system", content: x })),
...input.messages,
]
// 然后直接 streamText({ messages, tools, ... }) → HTTP POST 到 API
6.9.4 真正能看到请求体的方法
| 方法 | 说明 | 推荐度 |
|---|---|---|
| 抓包工具 | macOS: Proxyman / Charles;通用: mitmproxy。设置 HTTPS_PROXY=http://localhost:8080 后启动 opencode,可捕获完整 HTTP request/response body |
推荐 |
| 改源码 | 在 llm/request.ts 的 prepare() 函数 return 前加 console.log(JSON.stringify({ messages, tools })),重新编译运行 |
适合开发者 |
| 数据库查询 | opencode.db 的 message 表记录每轮的 token 用量(total/input/cache/output),part 表记录完整对话内容,但 system prompt 不落库 |
做 token 分析 |
| 还原估算 | 根据源码 + 本地配置文件手动拼装,参见 6.9.2 的 token 分解表 | 理解结构 |
6.9.5 源码级追踪
对于 DeepSeek 等非 GPT/Claude/Gemini 模型,使用的默认 system prompt:
| 文件 | 内容 |
|---|---|
packages/opencode/src/session/prompt/default.txt | 默认 System Prompt(适用 DeepSeek、Mistral 等) |
packages/opencode/src/session/system.ts | provider(model) 根据模型类型选 prompt 文件;environment() 生成环境信息;skills() 格式化技能列表 |
packages/opencode/src/session/instruction.ts | 读取 AGENTS.md / CLAUDE.md,system() 返回全局 + 项目指令文本 |
packages/opencode/src/session/llm/request.ts | LLMRequestPrep.prepare():拼接 system[] + messages[] → 完整请求体 |
packages/opencode/src/session/prompt.ts | loop() 中的主循环:每次迭代调用上述流程,将 system + messages + tools 传给 LLM |
6.9.6 还原演示:完整请求体 JSON
根据源码 + AGENTS.md + 技能列表 + 工具 schema 还原的"你好"请求体(下载 JSON):
点击展开完整 JSON(6 条 system message + 1 条 user message + 11 个 tool schema)
{
"model": "deepseek/deepseek-v4-pro",
"temperature": 0.3,
"messages": [
{
"role": "system",
"content": "You are opencode, an interactive CLI tool that helps users with software engineering tasks..."
},
{
"role": "system",
"content": "You are powered by the model named deepseek-v4-pro...\n<env>\n Working directory: /Users/brian/microarch\n Platform: darwin\n</env>\nInstructions from: /Users/brian/.config/opencode/AGENTS.md\n# AGENTS\n## Microarch 公司结构\n..."
},
{
"role": "system",
"content": "Skills provide specialized instructions...\n<available_skills>\n <skill><name>find-file</name>...</skill>\n <skill><name>fix-uml</name>...</skill>\n <skill><name>local-ocr</name>...</skill>\n <skill><name>opencloudos-restart-bot</name>...</skill>\n <skill><name>pull-all</name>...</skill>\n <skill><name>push-all</name>...</skill>\n <skill><name>send-email</name>...</skill>\n <skill><name>teardown</name>...</skill>\n <skill><name>ubuntu-restart-bot</name>...</skill>\n <skill><name>customize-opencode</name>...</skill>\n</available_skills>"
},
{
"role": "system",
"content": "## Tools\n\nYou have access to a set of tools to help answer the user's questions..."
},
{
"role": "user",
"content": "你好"
}
],
"tools": [
{ "type": "function", "function": { "name": "bash", "description": "Executes a given bash command...", "parameters": { "type":"object", "properties":{ "command":{},"workdir":{},"timeout":{} }, "required":["command"] } } },
{ "type": "function", "function": { "name": "edit", "description": "Performs exact string replacements...", "parameters": { "type":"object", "properties":{ "filePath":{},"oldString":{},"newString":{},"replaceAll":{} }, "required":["filePath","oldString","newString"] } } },
{ "type": "function", "function": { "name": "glob", "description": "Fast file pattern matching tool...", "parameters": { "type":"object", "properties":{ "pattern":{},"path":{} }, "required":["pattern"] } } },
{ "type": "function", "function": { "name": "grep", "description": "Fast content search tool...", "parameters": { "type":"object", "properties":{ "pattern":{},"path":{},"include":{} }, "required":["pattern"] } } },
{ "type": "function", "function": { "name": "question", "description": "Use this tool when you need to ask the user...", "parameters": { "type":"object", "properties":{ "questions":{} }, "required":["questions"] } } },
{ "type": "function", "function": { "name": "read", "description": "Read a file or directory...", "parameters": { "type":"object", "properties":{ "filePath":{},"offset":{},"limit":{} }, "required":["filePath"] } } },
{ "type": "function", "function": { "name": "skill", "description": "Load a specialized skill...", "parameters": { "type":"object", "properties":{ "name":{} }, "required":["name"] } } },
{ "type": "function", "function": { "name": "task", "description": "Launch a new agent...", "parameters": { "type":"object", "properties":{ "description":{},"prompt":{},"subagent_type":{} }, "required":["description","prompt","subagent_type"] } } },
{ "type": "function", "function": { "name": "todowrite", "description": "Create and maintain a structured task list...", "parameters": { "type":"object", "properties":{ "todos":{} }, "required":["todos"] } } },
{ "type": "function", "function": { "name": "webfetch", "description": "Fetches content from a specified URL...", "parameters": { "type":"object", "properties":{ "url":{} }, "required":["url"] } } },
{ "type": "function", "function": { "name": "write", "description": "Writes a file to the local filesystem...", "parameters": { "type":"object", "properties":{ "content":{},"filePath":{} }, "required":["content","filePath"] } } }
],
"total_estimated_tokens": 9063,
"token_breakdown": {
"cache_read": 8960,
"input": 18,
"output": 66,
"reasoning": 19
},
"cache_read_breakdown": {
"default_txt_system_prompt": "~1300 tokens",
"AGENTS_md": "~200 tokens",
"environment_info": "~100 tokens",
"skills_definitions": "~1200 tokens",
"tool_definitions_schemas": "~3100 tokens",
"other_system_instructions": "~3060 tokens"
}
}
注:工具 schema 中每个 parameters.properties 仅保留了 key,省略了 type/description 以节省篇幅。真实请求中的 tool schema 更冗长(含完整的 JSON Schema description),占 cache_read 的大头(~34.6%)。