pi-coding-agent SDK 与扩展系统

pi-coding-agent 既是交互式 CLI,也是 TypeScript SDK。

预计阅读
19分钟
全文字数
2,770字
资料截至
2026-10-09
Agent SDK · pi4.4
版本与时效声明
  • 本文基于 @earendil-works/pi-coding-agent 1.1.0(2026-10-07 发布,源码 commit 6fb2e78)。
  • 扩展 API 的事件、类型和工具暴露模式都还在快速演进。官方文档明确说:确切的事件、上下文、工具和返回值类型,以导出的 extensions/types.ts 为准1。
  • 文中代码都用 TypeScript 7.0.2 的 strict 模式对照 1.1.0 做过类型检查,但没有实际运行,因为运行需要模型凭据。资料截至 2026-10-09,请以 pi.dev 文档 为准。
本文要点
  1. pi-coding-agent 既是交互式 CLI,也是 TypeScript SDK。它在 pi-agent-core 的基础上加入了内置工具、会话持久化、上下文压缩、资源发现,以及扩展运行时2。
  2. 用 SDK 时,一个 createAgentSession() 调用就能得到一个和 CLI 使用同一套机制的 agent 会话23。
  3. 会话是一棵树:每条记录是 JSONL 中的一行,通过 id 和 parentId 连接,在同一个文件里就能分支4。
  4. 扩展是 pi 的灵魂:扩展是一个 TypeScript 模块,可以注册工具、命令、提供方、MCP server、事件处理器、UI。拦截危险命令、实现 plan mode、实现 subagent,都靠扩展15。

1. 定位#

  • 分层位置:L4 Harness,在你的 Node.js 或 Bun 进程内部运行。这一点不同于 Claude Agent SDK,后者会启动一个 CLI 子进程。
  • 与 CLI 的关系:交互模式、print 模式、JSON 模式、RPC 模式和 SDK,用的都是同一套 agent 和会话机制3。
  • 如果需要跨语言集成或进程隔离,可以改用 RPC 模式:启动一个子进程,通过 stdin 和 stdout 交换 JSONL6。

createAgentSession() → AgentSession

AgentSession

prompt / steer / followUp / subscribe / dispose

pi-agent-core Agent

SessionManager

(持久化或内存中的条目树)

ModelRuntime

(模型与鉴权,基于 pi-ai)

SettingsManager

ResourceLoader

扩展、skills、模板、主题、上下文文件

工具:read / bash / edit / write

+ grep / find / ls / 扩展工具

createAgentSession() → AgentSession

AgentSession

prompt / steer / followUp / subscribe / dispose

pi-agent-core Agent

SessionManager

(持久化或内存中的条目树)

ModelRuntime

(模型与鉴权,基于 pi-ai)

SettingsManager

ResourceLoader

扩展、skills、模板、主题、上下文文件

工具:read / bash / edit / write

+ grep / find / ls / 扩展工具

createAgentSession() 在不传参数时,会自动创建 ModelRuntime、基于文件的 SettingsManager、持久化的 SessionManager、DefaultResourceLoader,以及配置好的默认工具。每一项都可以由你显式替换2。


2. SDK 快速上手#

import { createAgentSession } from "@earendil-works/pi-coding-agent";
// 默认行为:使用当前工作目录,自动发现资源、读取已保存的设置和凭据
const { session } = await createAgentSession();
try {
await session.prompt("当前目录下有哪些文件?"); // prompt() 会在这次运行结束后返回
console.log(session.getLastAssistantText());
} finally {
session.dispose(); // 中止进行中的工作,并清理扩展上下文和事件监听器
}

这是官方 SDK 文档开头的例子2。

2.1 更常见的嵌入方式:内存会话、只读工具、流式输出#

import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
const { session } = await createAgentSession({
cwd: process.cwd(),
tools: ["read", "grep", "find", "ls"], // 只读模式:不启用 edit、write、bash
sessionManager: SessionManager.inMemory(), // 不写会话文件
});
const unsubscribe = session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta); // 流式输出文本
}
});
try {
await session.prompt("阅读 README,然后用三句话介绍这个项目。");
// 运行进行中可以这样插话:
// session.steer("..."):在当前这轮工具执行完之后插入
// session.followUp("..."):在这次运行结束后再追加一个任务
} finally {
unsubscribe();
session.dispose();
}

改编自官方的 SDK 文档和 examples/sdk/05-tools.ts27。

要点说明
运行中再次 prompt()必须说明是 steer 还是 followUp,否则会被拒绝,不会替你猜2
agent_end 和 agent_settled 的区别agent_end 只表示一次底层运行结束,之后可能还有自动重试或排队的工作;agent_settled 才表示 pi 不会再自动继续2
会话的权威数据最终发给模型的上下文以 SessionManager 为准。直接给 session.agent.state.messages 赋值,并不会替换持久化的上下文2
内置扩展CLI 会把 codemode、tool_search、MCP 作为内置扩展加载,SDK 会话不会,需要手动加入 extensionFactories2

2.2 在 SDK 中启用 MCP 和 codemode#

import {
createAgentSession,
createCodemodeExtension,
createMcpExtension,
createToolSearchExtension,
DefaultResourceLoader,
getAgentDir,
SessionManager,
SettingsManager,
} from "@earendil-works/pi-coding-agent";
const cwd = process.cwd();
const resourceLoader = new DefaultResourceLoader({
cwd,
agentDir: getAgentDir(),
extensionFactories: [
createCodemodeExtension({ mode: "on" }),
createToolSearchExtension(),
createMcpExtension(), // 和 CLI 一样,从 agent 目录和受信任的项目中读取 mcp.json
],
});
await resourceLoader.reload();
const settingsManager = SettingsManager.create(cwd);
settingsManager.applyOverrides({ defaultTools: ["+codemode", "+tool_search"] }); // +name 表示在默认工具的基础上追加
const { session } = await createAgentSession({ resourceLoader, settingsManager, sessionManager: SessionManager.inMemory() });
try {
await session.bindExtensions({}); // 触发 session_start 事件,MCP server 会在后台连接
console.log("当前可用的工具:", session.getActiveToolNames().join(", "));
} finally {
session.dispose();
}

改编自 examples/sdk/14-codemode-mcp.ts7。

什么是 codemode

codemode 工具让模型编写一段 JavaScript 脚本来调用 pi 的其他工具。只有脚本的输出会进入模型的上下文,因此脚本可以并行调用工具,或者先过滤掉大量结果再返回。脚本运行在 QuickJS 沙箱中,不能访问 Node API、文件系统、网络和定时器,只能通过注入的工具与外界交互8。

它和 OpenAI 的 Programmatic Tool Calling、DSH 的 PTC 模式是同一种思路。


3. 会话:一棵 JSONL 树#

user 消息

assistant

user 消息

assistant

user 消息 ← 当前叶子

branch_summary(分支摘要)

user 消息 ← 另一条分支

user 消息

assistant

user 消息

assistant

user 消息 ← 当前叶子

branch_summary(分支摘要)

user 消息 ← 另一条分支

这棵树的结构来自官方 Session File Format 文档4:

  • 存储位置:~/.pi/agent/sessions/--<路径>--/<时间戳>_<会话ID>.jsonl。第一行是会话头,之后每行是一条记录,记录之间通过 id 和 parentId 构成一棵树。
  • 叶子是当前所在的位置。从某条较早的记录继续,就会在同一个文件里长出一条新分支;fork 或 clone 则会把选中的历史复制到一个新文件中。
  • 构建上下文时,从当前叶子一路走到根节点,再结合压缩记录,得出模型可见的消息列表。被压缩的原始记录仍然保留在树中。
  • 记录类型:message、model_change、thinking_level_change、compaction、context_edit、branch_summary、custom、custom_message、label、session_info 等。
  • 版本:v1 是线性结构;v2 改为树结构;v3 把 hookMessage 角色改名为 custom。加载旧会话时会自动迁移。
为什么树状会话对后端工程师有启发

它本质上是一个事件溯源的数据结构:只追加,从不修改;当前状态等于从根节点重放到叶子。分支、回滚、审计都变得很自然。DSH 的事件日志也是同样的思路,见 5.3 DSH 核心机制与插件开发。


4. 扩展系统#

4.1 扩展是什么#

  • 扩展是导出一个默认工厂函数的 TypeScript 模块,工厂函数接收 ExtensionAPI(通常命名为 pi),并通过它注册各种能力1。
  • 扩展的存放位置有三种:~/.pi/agent/extensions/(用户级)、<项目>/.pi/extensions/(项目级),以及 settings.json 中声明的路径。pi 借助 jiti 加载扩展,所以不需要单独编译 TypeScript1。
  • 扩展在 pi 进程内运行,拥有相同的权限,所以只加载可信来源的扩展1。
能力API
观察或修改生命周期pi.on(event, handler)
添加模型可以调用的工具pi.registerTool()
添加 / 斜杠命令pi.registerCommand()
添加快捷键或 CLI 参数pi.registerShortcut() / pi.registerFlag()
发送用户消息或自定义消息pi.sendUserMessage() / pi.sendMessage()
持久化不进入上下文的数据pi.appendEntry()
添加模型提供方或 MCP serverpi.registerProvider() / pi.registerMcpServer()
按请求路由到不同模型pi.registerVirtualModel()
和其他扩展通信pi.events

以上取自官方文档1。

4.2 示例:一个最小的安全扩展#

下面是官方示例 permission-gate.ts 的完整代码,它在执行危险的 bash 命令前要求用户确认:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return undefined;
const command = event.input.command as string;
const isDangerous = dangerousPatterns.some((p) => p.test(command));
if (isDangerous) {
if (!ctx.hasUI) {
// 非交互模式下默认拦截
return { block: true, reason: "Dangerous command blocked (no UI for confirmation)" };
}
const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]);
if (choice !== "Yes") {
return { block: true, reason: "Blocked by user" };
}
}
return undefined;
});
}

源码见 examples/extensions/permission-gate.ts5。把它保存到 ~/.pi/agent/extensions/ 目录,或者用 pi --extension ./permission-gate.ts 加载。

4.3 示例:在 SDK 中用内联扩展注册工具#

import { Type } from "@earendil-works/pi-ai";
import {
createAgentSession,
DefaultResourceLoader,
getAgentDir,
SessionManager,
} from "@earendil-works/pi-coding-agent";
const resourceLoader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: getAgentDir(),
extensionFactories: [
(pi) => {
// 1) 注册一个工具,模型可以直接调用
pi.registerTool({
name: "get_weather",
label: "查询天气",
description: "查询城市今天的天气",
parameters: Type.Object({ city: Type.String({ description: "城市名" }) }),
execute: async (_toolCallId, params) => ({
content: [{ type: "text", text: params.city === "北京" ? "晴,25°C" : "暂无数据" }],
details: { city: params.city }, // details 用于界面渲染或状态重建,不会发给模型
}),
});
// 2) 审计日志:观察每一次工具调用。返回 { block: true, reason } 可以拦截
pi.on("tool_call", async (event) => {
console.log(`[audit] ${event.toolName}`);
return undefined;
});
// 3) 注册一个斜杠命令
pi.registerCommand("weather-help", {
description: "显示天气工具的用法",
handler: async (_args, ctx) => ctx.ui.notify("直接问:北京今天天气怎么样?", "info"),
});
},
],
});
await resourceLoader.reload();
const { session } = await createAgentSession({ resourceLoader, sessionManager: SessionManager.inMemory() });
try {
await session.prompt("北京今天适合跑步吗?");
console.log(session.getLastAssistantText());
} finally {
session.dispose();
}

改编自 examples/sdk/06-extensions.ts7。

4.4 关键事件与约定#

事件能做什么
before_agent_start查看或修改提示词的各个 section 和选中的工具,也可以强制替换整个提示词
tool_call修改输入,或者拦截这次执行
tool_result依次改写工具结果,每个处理器都能看到前一个处理器的修改
context / context_with_system在发出请求前改写本次请求的对话消息
turn_end / agent_before_settle可以追加记录,并请求再进行一次模型请求。要做好条件判断,避免无限循环
provider_stream_event只读地观察提供方的原始流事件

以上取自官方文档1。

工具的**暴露模式(exposure)**决定了模型如何接触到一个工具1:

模式含义
direct(默认)声明给模型,可以直接调用
model-only只声明给模型,其他工具不能通过 ctx.executeTool() 调用它
codemode只能被 codemode 脚本调用
deferred由 tool_search 按需激活
hidden已注册但不可达
从官方 examples 目录看扩展能做什么

examples/extensions/ 下有数十个示例5:

  • 安全:permission-gate、protected-paths、confirm-destructive、sandbox
  • 工作流:plan-mode、subagent、git-checkpoint、auto-commit-on-exit、handoff
  • 定制:custom-compaction、custom-provider-anthropic、claude-rules
  • 娱乐:snake、space-invaders、doom-overlay

这些例子正好对应 “不做”清单里的大部分功能:核心不内置,需要的话用扩展来实现。


5. Skills、Prompt Templates 与 Packages#

资源是什么怎么加载
Skills一个包含 SKILL.md 的目录,可以附带脚本、参考资料和素材文件。pi 实现了 Agent Skills specification启动时只向模型列出每个 skill 的名称和描述,真正需要时才加载完整的说明9
Prompt templates可复用的提示词文本在用户输入变成消息之前展开3
Themes终端的配色方案—
Pi packages把扩展、skills、模板、主题打包在一起,通过 npm 或 git 分发pi install npm:@example/pi-tools@1.0.0,或者 pi install git:github.com/example/pi-tools@v110
项目信任

项目级的包和设置只有在用户授予项目信任之后才会加载。包可以执行扩展代码,也可以通过 skill 指示模型运行程序,因此安装第三方包之前要先审查它的源码10。


6. RPC 模式:跨语言集成#

Terminal window
pi --mode rpc --no-session
  • 协议有四类记录:stdin 上的命令;stdout 上的 response;stdout 上的会话事件;双向传递的扩展 UI 记录6。
  • 每条命令可以带一个可选的 id,对应的响应会原样带回这个 id,用来关联请求和响应6。
  • TypeScript 中可以直接使用导出的 RpcClient,它负责启动 pi 进程、关联响应,并提供类型化的命令方法6。
接口进程边界适合什么
SDK同一进程Node.js 或 Bun 应用,需要完整的 API
RPC子进程其他语言、需要进程隔离、IDE、自定义客户端

以上取自官方文档6。


7. 与 Claude Agent SDK 的对比#

维度pi-coding-agent SDK3.3 Claude Agent SDK
进程模型同一进程(Node 或 Bun);RPC 是可选项启动 CLI 子进程,通过 stdio 通信
默认工具4 个:read、bash、edit、write一整套:Read、Edit、Write、Bash、Glob、Grep、Web 等
权限没有内置;用扩展或容器实现多层权限判定、权限模式、can_use_tool
会话树状 JSONL,同一个文件里就能分支JSONL,支持 resume、fork
扩展方式扩展(TypeScript 模块)、skills、packageshooks、subagents、plugins、skills、MCP
模型数十家,经由 pi-aiClaude
设计取向最小核心,由你构建能力完整,开箱即用

这张表是笔者根据两篇笔记归纳的。


小结#

  • pi-coding-agent 等于 pi-agent-core 加上工具、树状会话、压缩、资源发现、扩展运行时。SDK、CLI 和 RPC 共享同一套机制。
  • 它真正的力量在于扩展系统:pi 不打算预设所有需求,而是给你一套足够强的钩子,让你把 pi 改造成自己的 agent。
  • 对后端工程师来说,它的树状 JSONL 会话、进程边界的选择(SDK 还是 RPC),以及供应链加固实践都很值得借鉴。

相关笔记#

参考资料#

注释与出处#

  1. earendil-works/pi,packages/coding-agent/docs/extensions.md,https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/extensions.md ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  2. earendil-works/pi,packages/coding-agent/docs/sdk.md(commit 6fb2e78),https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/sdk.md ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  3. earendil-works/pi,packages/coding-agent/docs/how-pi-works.md,https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/how-pi-works.md ↩ ↩2 ↩3

  4. earendil-works/pi,packages/coding-agent/docs/session-format.md,https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/session-format.md ↩ ↩2

  5. earendil-works/pi,packages/coding-agent/examples/extensions/(含 permission-gate.ts),https://github.com/earendil-works/pi/tree/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/examples/extensions ↩ ↩2 ↩3

  6. earendil-works/pi,packages/coding-agent/docs/rpc.md,https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/rpc.md ↩ ↩2 ↩3 ↩4 ↩5

  7. earendil-works/pi,packages/coding-agent/examples/sdk/(01-minimal、05-tools、06-extensions、14-codemode-mcp 等),https://github.com/earendil-works/pi/tree/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/examples/sdk ↩ ↩2 ↩3

  8. earendil-works/pi,packages/coding-agent/docs/codemode.md,https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/codemode.md ↩

  9. earendil-works/pi,packages/coding-agent/docs/skills.md,https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/skills.md ↩

  10. earendil-works/pi,packages/coding-agent/docs/packages.md,https://github.com/earendil-works/pi/blob/6fb2e7815167e6b19006fc526d1a5d0f5f998787/packages/coding-agent/docs/packages.md ↩ ↩2

输入关键词开始搜索。多个关键词用空格分隔。