md-tools 变成 Pi 的一个「原生能力」打基础。你要能看懂一份扩展代码干了什么。扩展是什么
官方一句话:扩展(Extension)是 TypeScript 模块,用来扩展 Pi 的行为。 它们能订阅生命周期事件、注册模型可调用的自定义工具、添加命令,等等。
它能力很广,官方列了一串关键能力:
- 自定义工具:用
pi.registerTool()注册 - 事件拦截:阻塞/修改工具调用、注入上下文、自定义压缩
- 用户交互:用
ctx.ui提示用户(select / confirm / input / notify) - 自定义 UI:完整的 TUI 组件
- 自定义命令:注册
/mycommand - 会话持久化:用
pi.appendEntry()保存跨重启的状态 - 自定义渲染:控制工具调用/结果在 TUI 里的显示
一句话:扩展是给 Pi 写程序,不是给 Pi 写配置。
md-tools 的检查能力注册成 Pi 的工具和 /md 命令,还能在用户执行危险操作(比如 rename)时拦截确认。这就是「工具化」。一份最小扩展长什么样
官方给了一个非常清晰的完整示例,涵盖了事件、工具、命令三件事:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// 订阅事件
pi.on("session_start", async (_event, ctx) => {
ctx.ui.notify("Extension loaded!", "info");
});
// 注册自定义工具
pi.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
parameters: Type.Object({
name: Type.String({ description: "Name to greet" }),
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
return {
content: [{ type: "text", text: `Hello, ${params.name}!` }],
details: {},
};
},
});
// 注册命令
pi.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
},
});
}
注意核心三件事:pi.on 订阅事件、pi.registerTool 注册工具、pi.registerCommand 注册命令。一个扩展就是这三个动作的组合。
事件系统:Pi 运行到哪,你就能在哪插一脚
扩展最强大的部分就是事件系统。官方画了一整张生命周期图,几个关键节点:
| 事件 | 时机 |
|---|---|
project_trust |
信任决定前 |
session_start |
会话启动/加载 |
input |
收到用户输入后(可拦截/改写) |
before_agent_start |
提交提示词后、agent 循环前 |
tool_call |
工具执行前(可阻塞) |
tool_result |
工具执行后(可修改结果) |
message_end |
消息最终确定时 |
session_shutdown |
会话销毁前 |
一句话:从启动到退出,每个环节都有一扇「事件门」,扩展可以在门口做拦截、修改、注入。
你能用事件做什么
官方给的示例场景很实用:
- 权限门:
rm -rf、sudo前先确认 - Git 检查点:每个回合 stash,切分支恢复
- 路径保护:阻止写
.env、node_modules/ - 自定义压缩:用你自己的方式总结对话
- 有状态工具:待办列表、连接池
对 md-tools 最贴切的是权限门——在 tool_call 事件里,遇到 rename 或危险操作就弹出确认。
落地练习:读懂并试跑一份扩展
- 新建
~/.pi/agent/extensions/my-first.ts,把上面那份最小扩展完整复制进去 - 用
pi -e ./my-first.ts或放进自动发现目录后启动 - 观察:启动时有
notify,输入/hello有回应,让模型调用greet工具
怎么判断做对了?——启动提示扩展已加载,
/hello能触发通知,greet工具能被模型调用。
卡住了怎么办? 没反应 → 确认放进了被扫描目录或用 -e 指定了路径。语法错 → 检查 TypeScript 是否有报错。工具不出现 → 确认 registerTool 已调用、参数定义正确。
常见坑:在扩展里放「跑不完的后台任务」
官方专门提醒过:扩展工厂可能在根本不启动会话的调用里运行。不要在工厂里直接启动后台资源(进程、socket、文件监听、定时器)。
正确做法:把后台资源延迟到 session_start 或真正需要它的命令/工具/事件里再启动,并在 session_shutdown 里清理。否则你开的资源可能一直挂在那,甚至拖垮整个进程。
小结
- 扩展 = 写 TypeScript 程序,能订阅事件、注册工具、注册命令
- 核心三动作:
pi.on、pi.registerTool、pi.registerCommand - 事件系统覆盖全生命周期,每个环节都能拦截/修改/注入
- 典型场景:权限门、Git 检查点、路径保护、有状态工具
- 别在工厂里启动后台资源,延迟到需要时并记得清理
下一节,正式动手——注册自定义工具和斜杠命令。