Pi Agent 扩展入门
Extensions(扩展)是 Pi Agent 最强大的定制方式——你可以用 TypeScript 编写自定义工具、命令和事件监听器。
什么是 Extension
Extension 是一个 TypeScript 模块,它可以:
- 注册自定义工具——AI 可以调用的新功能
- 注册自定义命令——用户通过 /命令名 调用
- 监听生命周期事件——在 AI 工作的各个阶段插入逻辑
- 注册键盘快捷键
- 展示自定义 UI——在终端中渲染交互界面
第一个 Extension:Hello World
实例
// 文件路径:~/.pi/agent/extensions/my-extension.ts
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("我的扩展已加载!", "info");
});
// 拦截危险命令
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash"
&& event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("危险操作!", "确认执行 rm -rf?");
if (!ok) return { block: true, reason: "用户阻止" };
}
});
// 注册一个自定义工具
pi.registerTool({
name: "greet",
label: "打招呼",
description: "向指定的人打招呼",
parameters: Type.Object({
name: Type.String({ description: "要打招呼的人名" }),
}),
async execute(toolCallId, params) {
return {
content: [{ type: "text", text: `你好,${params.name}!` }],
details: {},
};
},
});
// 注册一个自定义命令
pi.registerCommand("hello", {
description: "向世界问好",
handler: async (args, ctx) => {
ctx.ui.notify(`你好 ${args || "世界"}!`, "info");
},
});
}
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("我的扩展已加载!", "info");
});
// 拦截危险命令
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash"
&& event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("危险操作!", "确认执行 rm -rf?");
if (!ok) return { block: true, reason: "用户阻止" };
}
});
// 注册一个自定义工具
pi.registerTool({
name: "greet",
label: "打招呼",
description: "向指定的人打招呼",
parameters: Type.Object({
name: Type.String({ description: "要打招呼的人名" }),
}),
async execute(toolCallId, params) {
return {
content: [{ type: "text", text: `你好,${params.name}!` }],
details: {},
};
},
});
// 注册一个自定义命令
pi.registerCommand("hello", {
description: "向世界问好",
handler: async (args, ctx) => {
ctx.ui.notify(`你好 ${args || "世界"}!`, "info");
},
});
}
测试你的扩展:
$ pi -e ~/.pi/agent/extensions/my-extension.ts
扩展的加载位置
| 位置 | 作用范围 | 说明 |
|---|---|---|
| ~/.pi/agent/extensions/*.ts | 全局 | 对所有项目生效,自动发现 |
| ~/.pi/agent/extensions/*/index.ts | 全局 | 子目录中有 index.ts 的扩展 |
| .pi/extensions/*.ts | 项目 | 仅当前项目,需项目信任 |
| .pi/extensions/*/index.ts | 项目 | 项目子目录扩展 |
也可以通过 settings.json 或 CLI 参数加载扩展:
# 从路径加载(临时) $ pi -e ./my-extension.ts # 在 settings.json 中配置
实例
{
"extensions": [
"/path/to/local/extension.ts",
"/path/to/local/extension/dir"
]
}
"extensions": [
"/path/to/local/extension.ts",
"/path/to/local/extension/dir"
]
}
Extension 文件结构
单文件扩展
最简单的形式,适合小型扩展:
~/.pi/agent/extensions/ └── my-extension.ts
目录扩展(含 index.ts)
适合多文件扩展:
~/.pi/agent/extensions/
└── my-extension/
├── index.ts # 入口文件(导出默认函数)
├── tools.ts # 辅助模块
└── utils.ts # 工具函数
带依赖的扩展
适合需要 npm 包的大型扩展:
~/.pi/agent/extensions/
└── my-extension/
├── package.json # 声明依赖和入口
├── node_modules/ # npm install 后生成
└── src/
└── index.ts
扩展以你的系统权限运行,可以执行任意代码。只安装来自可信来源的扩展。
扩展的加载机制
扩展通过 jiti 加载,TypeScript 代码无需编译即可直接运行。
如果你的工厂函数返回 Promise,Pi Agent 会在继续启动流程之前等待它完成——这意味着你可以在扩展初始化时做异步操作,比如获取远程配置。
可用 Import 包
| 包名 | 用途 |
|---|---|
| @earendil-works/pi-coding-agent | 扩展类型(ExtensionAPI、ExtensionContext 和各种事件类型) |
| typebox | 工具参数的 Schema 定义 |
| @earendil-works/pi-ai | AI 工具(如 StringEnum,用于 Google 兼容的枚举) |
| @earendil-works/pi-tui | TUI 组件,用于自定义渲染 |
这些是 Pi Agent 的内置依赖,应该列为 peerDependencies 而不是打包到你的扩展中。
其他 npm 包也可以使用——在扩展目录中添加 package.json 并运行 npm install 即可。
开发提示
- 将扩展放在 ~/.pi/agent/extensions/ 目录中,支持 /reload 热重载
- 使用 pi -e ./path.ts 仅在快速测试时使用
- 不要在扩展工厂函数中启动后台资源(进程、socket、文件监听器、定时器等),这些应该在 session_start 事件中启动
- 在 session_shutdown 事件中清理会话级资源
