OpenCode 学习笔记 06:深入探索
事件总线、语义搜索、格式化、会话分享与更多高级功能
January 22, 2026·10 min read·Yimin
#AI#Agent#OpenCode#TypeScript#Advanced#Deep Dive
冰山之下,还有更多精彩。这些功能让 OpenCode 从"能用"变成"好用"。
本篇目标
| 目标 | 状态 |
|---|---|
| Bus 事件总线 | 📖 待学习 |
| Question 系统 | 📖 待学习 |
| Format 格式化 | 📖 待学习 |
| Share 会话分享 | 📖 待学习 |
| Todo 工具 | 📖 待学习 |
| Multiedit 多文件编辑 | 📖 待学习 |
| ACP 协议 | 📖 待学习 |
一、Bus 事件总线
1.1 为什么需要事件总线?
问题:组件之间如何通信?
┌─────────────────────────────────────────────────────────────┐
│ 没有事件总线:组件直接调用,耦合严重 │
│ │
│ Session ──────▶ Permission ──────▶ TUI │
│ │ │ │ │
│ └─────────────────┴────────────────┘ │
│ 谁调用谁?顺序?依赖? │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 有事件总线:发布/订阅,解耦 │
│ │
│ Session ──publish──▶ ┌─────────┐ ◀──subscribe── TUI │
│ │ Bus │ │
│ Permission ─publish─▶ └─────────┘ ◀──subscribe── Plugin │
│ │
│ 组件不需要知道谁在监听 │
└─────────────────────────────────────────────────────────────┘
1.2 OpenCode 的事件总线
// 定义事件
export const Event = {
Asked: BusEvent.define("permission.asked", PermissionInfo),
Updated: BusEvent.define("session.updated", SessionInfo),
Error: BusEvent.define("session.error", ErrorInfo),
}
// 发布事件
Bus.publish(Session.Event.Updated, { info: sessionInfo })
// 订阅事件
Bus.subscribe(Session.Event.Updated, async (evt) => {
// 更新 UI、同步状态等
await updateUI(evt.properties.info)
})
1.3 核心事件类型
| 事件 | 触发时机 | 用途 |
|---|---|---|
session.updated | 会话状态变更 | UI 刷新、持久化 |
permission.asked | 需要用户授权 | TUI 弹窗 |
file.edited | 文件被修改 | 自动格式化 |
question.asked | AI 向用户提问 | 显示问答界面 |
tool.executed | 工具执行完成 | 日志、统计 |
1.4 学习要点
-
bus/bus-event.ts- 事件定义 -
bus/index.ts- 发布/订阅实现 - 各模块如何使用 Bus
二、Question 系统
2.1 什么是 Question?
AI 主动向用户提问的机制
普通工具调用:
User: 帮我创建一个项目
AI: [调用 bash] mkdir project && cd project && npm init -y
AI: 项目已创建!
Question 机制:
User: 帮我创建一个项目
AI: [Question] 请选择项目类型:
[ ] React
[ ] Vue
[ ] Node.js
User: 选择 React
AI: [继续执行] 创建 React 项目...
2.2 Question vs Permission
| 特性 | Permission | Question |
|---|---|---|
| 目的 | 安全授权 | 获取信息 |
| 触发 | 危险操作前 | 需要用户输入时 |
| 选项 | 允许/拒绝 | 多选项/自由输入 |
| 示例 | "是否允许执行 rm -rf?" | "选择部署环境?" |
2.3 OpenCode 实现
// question/index.ts
export namespace Question {
export const Info = z.object({
question: z.string(),
options: z.array(Option).optional(),
allowsMultiple: z.boolean().optional(),
allowsFreeform: z.boolean().optional(),
})
export async function ask(input: {
sessionID: string
questions: Info[]
}): Promise<Answer[]>
}
2.4 学习要点
-
question/index.ts- Question 定义和实现 -
tool/question.ts- question 工具 - TUI 中如何渲染问题
三、Format 代码格式化
3.1 自动格式化流程
┌─────────────────────────────────────────────────────────────┐
│ 自动格式化流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. AI 修改文件 │
│ └── edit/write 工具执行 │
│ │
│ 2. 触发 file.edited 事件 │
│ └── Bus.publish(File.Event.Edited, { path }) │
│ │
│ 3. Format 模块监听事件 │
│ └── Bus.subscribe(File.Event.Edited, ...) │
│ │
│ 4. 根据文件类型选择格式化工具 │
│ └── .ts → prettier │
│ └── .go → gofmt │
│ └── .rs → rustfmt │
│ │
│ 5. 执行格式化 │
│ └── spawn(["prettier", "--write", file]) │
│ │
└─────────────────────────────────────────────────────────────┘
3.2 支持的格式化工具
| 工具 | 语言 | 命令 |
|---|---|---|
| prettier | JS/TS/CSS/HTML | prettier --write $FILE |
| gofmt | Go | gofmt -w $FILE |
| rustfmt | Rust | rustfmt $FILE |
| black/ruff | Python | ruff format $FILE |
| clang-format | C/C++ | clang-format -i $FILE |
| ktlint | Kotlin | ktlint -F $FILE |
| rubocop | Ruby | rubocop --autocorrect $FILE |
| dart | Dart | dart format $FILE |
| mix | Elixir | mix format $FILE |
| ... | 20+ 种 | ... |
3.3 配置格式化
// opencode.json
{
"formatter": {
"prettier": {
"extensions": [".ts", ".tsx", ".js", ".jsx"],
"command": ["npx", "prettier", "--write", "$FILE"]
},
"custom": {
"extensions": [".xyz"],
"command": ["./my-formatter.sh", "$FILE"]
}
}
}
3.4 学习要点
-
format/formatter.ts- 格式化工具定义 -
format/index.ts- 格式化调度 - 如何添加自定义格式化工具
四、Codesearch 语义搜索
4.1 grep vs Codesearch
| 特性 | grep | Codesearch |
|---|---|---|
| 搜索方式 | 正则匹配 | 语义理解 |
| 查询 | getUserById | "获取用户的函数" |
| 结果 | 精确匹配 | 相关代码 |
| 适用场景 | 知道确切名称 | 探索陌生代码库 |
4.2 使用示例
User: 这个项目怎么处理用户认证的?
AI 使用 grep: grep -r "auth" .
→ 返回所有包含 "auth" 的行(可能很多噪音)
AI 使用 codesearch: codesearch("用户认证流程")
→ 返回:
- src/auth/login.ts (登录逻辑)
- src/middleware/jwt.ts (JWT 验证)
- src/routes/auth.ts (认证路由)
4.3 OpenCode 实现
// tool/codesearch.ts
export const CodesearchTool = Tool.define("codesearch", {
description: "Semantic code search using natural language",
parameters: z.object({
query: z.string().describe("Natural language search query"),
scope: z.string().optional().describe("Directory to search in"),
}),
async execute(params, ctx) {
// 调用 Exa API 或本地向量搜索
const results = await semanticSearch(params.query, params.scope)
return { output: formatResults(results) }
},
})
4.4 学习要点
-
tool/codesearch.ts- 实现原理 - 语义搜索 API(Exa 等)
- 本地向量索引方案
五、Share 会话分享
5.1 分享流程
┌─────────────────────────────────────────────────────────────┐
│ 会话分享流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 用户触发分享 │
│ └── 快捷键或命令 │
│ │
│ 2. 上传会话数据 │
│ └── POST api.opencode.ai/share_create │
│ └── 返回 { url, secret } │
│ │
│ 3. 实时同步 │
│ └── 监听 session.updated 事件 │
│ └── 增量更新到云端 │
│ │
│ 4. 生成分享链接 │
│ └── https://opencode.ai/share/xxx │
│ │
└─────────────────────────────────────────────────────────────┘
5.2 配置选项
// opencode.json
{
// 分享模式
"share": "manual", // manual | auto | disabled
// 旧配置(已废弃)
"autoshare": true
}
5.3 学习要点
-
share/share.ts- 分享逻辑 -
share/share-next.ts- 新版分享 - 实时同步机制
六、Todo 工具
6.1 什么是 Todo 工具?
AI 维护的任务列表,跟踪复杂任务进度
User: 帮我重构这个模块
AI: 我来规划一下任务:
[todowrite]
- [ ] 分析现有代码结构
- [ ] 设计新的接口
- [ ] 重构核心逻辑
- [ ] 更新测试用例
- [ ] 更新文档
AI: 开始第一个任务...
[todowrite] 更新状态
- [x] 分析现有代码结构 ✓
- [ ] 设计新的接口 ← 进行中
- [ ] 重构核心逻辑
- [ ] 更新测试用例
- [ ] 更新文档
6.2 Todo 结构
// session/todo.ts
export namespace Todo {
export const Info = z.object({
id: z.string(),
content: z.string(),
status: z.enum(["pending", "in_progress", "completed", "cancelled"]),
})
}
6.3 学习要点
-
tool/todo.ts- todowrite/todoread 工具 -
session/todo.ts- Todo 状态管理 - TUI 中如何显示任务列表
七、Websearch / Webfetch
7.1 Websearch - 网页搜索
// tool/websearch.ts
export const WebsearchTool = Tool.define("websearch", {
description: "Search the web for information",
parameters: z.object({
query: z.string().describe("Search query"),
}),
async execute(params) {
// 调用搜索 API (Exa, SerpAPI, etc.)
const results = await searchWeb(params.query)
return { output: formatSearchResults(results) }
},
})
7.2 Webfetch - 网页抓取
// tool/webfetch.ts
export const WebfetchTool = Tool.define("webfetch", {
description: "Fetch and extract content from a URL",
parameters: z.object({
url: z.string().describe("URL to fetch"),
}),
async execute(params) {
// 抓取并提取正文
const content = await fetchAndParse(params.url)
return { output: content }
},
})
7.3 学习要点
-
tool/websearch.ts- 搜索实现 -
tool/webfetch.ts- 抓取实现 - 如何处理反爬虫
八、Multiedit 多文件编辑
8.1 为什么需要 Multiedit?
普通编辑:一次只能改一个文件
AI: [edit] 修改 src/a.ts
AI: [edit] 修改 src/b.ts
AI: [edit] 修改 src/c.ts
→ 3 次工具调用
Multiedit:一次修改多个文件
AI: [multiedit] 同时修改 a.ts, b.ts, c.ts
→ 1 次工具调用,原子操作
8.2 使用场景
- 重命名:同时修改所有引用
- 重构:批量更新接口签名
- 配置:同步修改多个配置文件
8.3 学习要点
-
tool/multiedit.ts- 实现原理 - 如何保证原子性
- 与 Snapshot 的配合
九、ACP (Agent Client Protocol)
9.1 什么是 ACP?
IDE 与 AI Agent 通信的标准协议
┌─────────────────────────────────────────────────────────────┐
│ 类比: │
│ - MCP = AI Agent 连接外部工具的协议 │
│ - ACP = IDE 连接 AI Agent 的协议 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ │
│ Zed / VS Code / Other IDE │
│ │ │
│ │ ACP (JSON-RPC over stdio) │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ OpenCode │ │
│ │ ACP Server │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
9.2 Zed 集成示例
// ~/.config/zed/settings.json
{
"agent_servers": {
"OpenCode": {
"command": "opencode",
"args": ["acp"]
}
}
}
9.3 ACP 协议方法
| 方法 | 说明 |
|---|---|
initialize | 初始化连接 |
session/new | 创建会话 |
session/load | 加载会话 |
session/prompt | 发送消息 |
9.4 学习要点
-
acp/- ACP 实现 - ACP 协议规范
- 与 MCP 的区别
十、其他高级功能
10.1 File Watcher
// file/watcher.ts
// 监听文件变更,触发事件
export namespace Watcher {
export function watch(patterns: string[], callback: (event) => void)
}
10.2 LSP 集成
// lsp/
// 语言服务器协议
export namespace LSP {
export function getSymbols(file: string) // 获取符号
export function getDiagnostics(file: string) // 获取诊断
export function getDefinition(symbol: string) // 跳转定义
}
10.3 PTY 伪终端
// pty/
// 交互式终端(如 vim, htop)
export namespace PTY {
export function spawn(command: string)
export function resize(cols: number, rows: number)
}
10.4 GitHub PR
// cli/cmd/pr.ts
// PR 创建和审查
export const PrCommand = {
create: "创建 PR",
review: "审查 PR",
}
10.5 Command 模板
<!-- .opencode/commands/review.md -->
---
description: 代码审查
agent: explore
---
请审查以下文件的代码质量:
@{{files}}
关注点:
- 代码风格
- 潜在 bug
- 性能问题
学习路线图
┌─────────────────────────────────────────────────────────────┐
│ Phase 6 学习路线 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第一优先级(核心机制) │
│ ├── Bus 事件总线 - 理解系统如何协作 │
│ └── Question 系统 - AI 主动交互 │
│ │
│ 第二优先级(实用功能) │
│ ├── Format 格式化 - 代码质量 │
│ ├── Codesearch - 智能搜索 │
│ └── Todo 工具 - 任务管理 │
│ │
│ 第三优先级(扩展功能) │
│ ├── Websearch/Webfetch - 外部信息 │
│ ├── Multiedit - 批量编辑 │
│ └── Share - 协作分享 │
│ │
│ 第四优先级(生态集成) │
│ └── ACP 协议 - IDE 集成 │
│ │
└─────────────────────────────────────────────────────────────┘
总结
Phase 6 涵盖了 OpenCode 的"冰山之下"的功能:
| 类别 | 功能 | 作用 |
|---|---|---|
| 系统机制 | Bus, Question | 组件通信、用户交互 |
| 代码质量 | Format, Codesearch | 自动格式化、智能搜索 |
| 任务管理 | Todo | 跟踪复杂任务 |
| 外部集成 | Websearch, Share, ACP | 获取信息、协作、IDE |
| 批量操作 | Multiedit | 高效编辑 |
这些功能让 OpenCode 从一个"能用"的工具变成"好用"的生产级系统。
下一步:逐个深入学习,在 tiny-agent 中实现简化版本。