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.askedAI 向用户提问显示问答界面
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

特性PermissionQuestion
目的安全授权获取信息
触发危险操作前需要用户输入时
选项允许/拒绝多选项/自由输入
示例"是否允许执行 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 支持的格式化工具

工具语言命令
prettierJS/TS/CSS/HTMLprettier --write $FILE
gofmtGogofmt -w $FILE
rustfmtRustrustfmt $FILE
black/ruffPythonruff format $FILE
clang-formatC/C++clang-format -i $FILE
ktlintKotlinktlint -F $FILE
rubocopRubyrubocop --autocorrect $FILE
dartDartdart format $FILE
mixElixirmix 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

特性grepCodesearch
搜索方式正则匹配语义理解
查询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 学习要点


十、其他高级功能

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 中实现简化版本。