写一个 Tool,再写一个 Extension

用当前 Pi API 注册类型安全的 hello tool,并扩展为带生命周期观察的项目能力。

ANALYSIS SNAPSHOTPi v0.82.15bc1c2c0

文件位置

把项目扩展放在 .pi/extensions/hello.ts。启动时 Pi 会要求确认 project trust;也可以用 pi -e ./hello.ts 临时加载。

最小类型安全工具

import { Type } from "@earendil-works/pi-ai";
import {
  defineTool,
  type ExtensionAPI,
} from "@earendil-works/pi-coding-agent";

const helloTool = defineTool({
  name: "hello",
  label: "Hello",
  description: "Greet a person by name",
  parameters: Type.Object({
    name: Type.String({ description: "Name to greet" }),
  }),
  async execute(_id, params) {
    return {
      content: [{ type: "text", text: `Hello, ${params.name}!` }],
      details: { greeted: params.name },
    };
  },
});

export default function setup(pi: ExtensionAPI) {
  pi.registerTool(helloTool);
}
packages/coding-agent/examples/extensions/hello.ts:5-26defineTool() · registerTool()

加入观察性

export default function setup(pi: ExtensionAPI) {
  pi.registerTool(helloTool);

  pi.on("tool_call", async (event, ctx) => {
    if (event.toolName === "hello") {
      ctx.ui.notify(`Calling ${event.toolName}`, "info");
    }
  });
}

如果目标是安全确认,应在可阻断的 tool-call hook 中返回明确 decision,而不是只显示通知。事件名、字段与返回契约应以固定版本的 docs/extensions.mdcore/extensions/types.ts 为准。

状态如何持久化

pi.appendEntry("hello-stats", { count: 1 });

这会产生 custom session entry,不进入模型 context。恢复时遍历当前 branch 的 entries,找到最近一条同 customType 的数据。需要让模型知道时,使用 custom message,而不是滥用 state entry。

从 Tool 到产品能力

一个成熟 extension 往往包含:

  1. 一个窄而清晰的 tool contract。
  2. before/after hooks 做政策和归一化。
  3. custom entry 保存不进 context 的状态。
  4. command 或 UI 让人类配置与检查。
  5. session start/tree handlers 恢复当前分支状态。
  6. 可在 faux provider 下运行的确定性测试。

这正是 Pi 的 extension-first 哲学:产品能力长在边界上,而不是不断改 core loop。