扩展入门 101
最后更新: 2026年7月27日
$ pi –extend
Pi 的设计哲学是保持内核小巧,把工作流相关的行为交给扩展。Pi 的扩展不是沙箱市场里那种插件——它是一个以你的完整系统权限运行在 Pi 进程内的 TypeScript 模块,可以订阅生命周期事件、注册 LLM 可调用的工具、添加斜杠命令、渲染自定义 UI、拦截工具调用。本指南依据仓库中的 docs/extensions.md 介绍真实机制。
扩展是什么
扩展是一个 .ts 文件,其默认导出是一个工厂函数,接收一个 ExtensionAPI 对象(惯例命名为 pi)。Pi 使用 jiti 加载扩展,因此 TypeScript 无需编译即可运行:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
// 订阅事件,注册工具/命令/快捷键/CLI 标志
}
工厂函数可以是 async;Pi 会等待它完成后才继续启动(先于 session_start,也先于提供商注册的冲刷)。不要在工厂函数里启动长期存活的后台资源——把它们推迟到 session_start,并在 session_shutdown 处理器中关闭。
扩展放在哪里
自动发现的位置(这些位置的扩展可用 /reload 热重载):
| 位置 | 作用域 |
|---|---|
~/.pi/agent/extensions/*.ts |
全局 |
~/.pi/agent/extensions/*/index.ts |
全局(目录形式) |
.pi/extensions/*.ts |
项目级 |
.pi/extensions/*/index.ts |
项目级(目录形式) |
项目级扩展只在项目被信任后加载(见信任提示或 /trust)。快速测试时用 pi -e ./path.ts——可重复,并可与 --no-extensions 组合以隔离环境:
pi --no-extensions -e ./my-extension.ts
扩展也可以作为 pi 包从 npm 或 git 分享(pi install <source>),额外路径可以列在 settings.json 的 packages / extensions 字段中。如果扩展需要 npm 依赖,在旁边放一个 package.json,运行 npm install,即可自动解析 node_modules/ 中的导入。
安全提示: 扩展以你的权限执行任意代码。只安装你信任的来源。
扩展能做什么
pi.on(event, handler)—— 订阅生命周期事件;许多处理器可以阻断、修改或注入内容pi.registerTool({...})—— 添加 LLM 可调用的工具(注册与内置工具同名即覆盖)pi.registerCommand("name", {...})—— 添加/name斜杠命令pi.registerShortcut(...)、pi.registerFlag(...)—— 键位绑定与 CLI 标志pi.registerProvider(...)—— 添加自定义模型提供商pi.appendEntry(...)—— 将状态持久化到会话文件ctx.ui——select、confirm、input、notify、用custom()构建完整 TUI 组件、setStatus、setWidget- 工具调用/结果与消息的自定义渲染器
事件生命周期
事件与智能体循环一一对应。核心序列:
pi 启动
├─► project_trust (仅用户/全局扩展参与)
├─► session_start { reason: "startup" }
└─► resources_discover
用户发送提示
├─► input (可拦截/转换)
├─► before_agent_start (可注入消息、修改系统提示词)
├─► agent_start
│ 每个 turn:
│ turn_start → context(可修改消息)
│ → before_provider_request → after_provider_response
│ → tool_execution_start → tool_call(可阻断)
│ → tool_result(可修改)→ tool_execution_end → turn_end
├─► agent_end
└─► agent_settled
会话操作有自己的事件:/new 与 /resume → session_before_switch、session_shutdown、session_start;/compact → session_before_compact、session_compact;模型切换 → model_select、thinking_level_select;退出 → session_shutdown。
最小可运行示例
改编自官方 quick start。保存为 ~/.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("Extension loaded!", "info");
});
// 拦截危险工具调用
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
}
});
// 注册一个 LLM 可调用的自定义工具
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: {},
};
},
});
// 注册 /hello 命令
pi.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
},
});
}
重启 Pi(或 /reload)后:模型可以调用 greet 工具,/hello world 可用,任何 rm -rf 尝试都会触发确认对话框。
扩展中可用的导入:@earendil-works/pi-coding-agent(类型)、typebox(参数 schema)、@earendil-works/pi-ai、@earendil-works/pi-tui(自定义 UI)、Node.js 内置模块,以及本地 node_modules/ 中的任何包。
值得研读的真实示例
仓库在 packages/coding-agent/examples/extensions/ 中附带了数十个可运行的扩展。推荐入口:
hello.ts—— 最小工具注册confirm-destructive.ts—— 权限门模式git-checkpoint.ts—— 每个 turn 做 stash,分支切换时恢复question.ts/questionnaire.ts—— 通过ui.select/ui.custom构建交互式工具todo.ts—— 带会话持久化的有状态工具custom-compaction.ts—— 替换压缩摘要逻辑summarize.ts—— 会话总结命令snake.ts—— 在 TUI 里做游戏,何乐而不为
按照官方文档的说法,写扩展最快的方式是:pi 自己会写扩展——直接让它为你的场景构建一个。
延伸阅读
- Extensions —— 官方文档 —— 完整事件参考、
ExtensionContext、自定义 UI、错误处理 - GitHub 上的 examples/extensions/
- Pi Packages —— 通过 npm/git 分发扩展
- Custom providers —— 深入
pi.registerProvider()