SDK 与编程接口
Pi 不是「一个 CLI 加一个 RPC」——它是一个能在 CLI、Node.js 嵌入、子进程 RPC、JSONL 流四种场景下等价使用的 agent runtime。
createAgentSession()是统一入口,本章拆这四种使用姿势的 API 与取舍。
核心 API:createAgentSession()
SDK 的主工厂函数返回 AgentSession 实例。使用 ResourceLoader 加载 extensions、skills、prompt templates、themes 和 context files。
最小调用:
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
返回值结构:
interface CreateAgentSessionResult {
session: AgentSession;
extensionsResult: LoadExtensionsResult;
modelFallbackMessage?: string;
}
interface LoadExtensionsResult {
extensions: Extension[];
errors: Array<{ path: string; error: string }>;
runtime: ExtensionRuntime;
}
extensionsResult.errors 让你能检测「哪些扩展加载失败」——这对生产环境部署很关键,避免一个坏扩展默默拖累整个 session。
AgentSession 接口
AgentSession 是 SDK 的核心对象。它的方法分三类:
Prompt 与消息排队
// 阻塞式发送 prompt,等完成后返回
await session.prompt(text, options?);
// streaming 期间排队消息(不阻塞)
session.steer(text); // 当前助手回合工具调用完成后投递
session.followUp(text); // agent 全部工作完成后投递
PromptOptions:
interface PromptOptions {
expandPromptTemplates?: boolean;
images?: ImageContent[];
streamingBehavior?: "steer" | "followUp";
source?: InputSource;
preflightResult?: (success: boolean) => void;
}
preflightResult 在每次 prompt() 调用时触发一次(在 resolve 之前),true 表示接受/排队/立即处理;false 表示 preflight 拒绝。
关键行为规则:
- Extension commands(如
/mycommand):streaming 期间也立即执行,通过pi.sendMessage()管理自身 LLM 交互 - 基于文件的 prompt templates:在发送/排队前展开
- Streaming 期间无
streamingBehavior:抛错,需用steer()/followUp()或指定选项 steer()和followUp()展开基于文件的 prompt templates,但对 extension commands 报错
事件订阅
const unsubscribe = session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
// 之后取消订阅
unsubscribe();
事件是强类型的——message_update 下的 assistantMessageEvent 是 TypeScript discriminated union,写起代码来很自然。
模型与推理等级
session.setModel(model);
session.setThinkingLevel(level); // off | minimal | low | medium | high | xhigh
session.cycleModel(); // 切到 scopedModels 里的下一个
session.cycleThinkingLevel(); // 切到下一个推理等级
状态访问
session.agent // 内部 agent 对象(高级用法)
session.model // 当前 model
session.thinkingLevel // 当前推理等级
session.messages // 当前消息历史
session.isStreaming // 是否正在流式输出
session.sessionFile // 文件路径或 undefined
session.sessionId // session id
Session 操作
session.navigateTree(targetId, options?); // 当前 session 内树导航
session.compact(customInstructions?); // 手动压缩
session.abortCompaction(); // 取消正在进行的压缩
session.abort(); // 中止当前 turn
session.dispose(); // 释放资源
完整 SDK 示例
把各种 API 串起来的完整例子——一个自定义工具 + 自定义 system prompt + 自定义模型 + 内存 session + 事件订阅:
import { createAgentSession, SessionManager, SettingsManager, defineTool, DefaultResourceLoader, createEventBus } from "@earendil-works/pi-coding-agent";
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
import { getModel } from "@earendil-works/pi-ai";
import { Type } from "typebox";
const authStorage = AuthStorage.create("/custom/agent/auth.json");
if (process.env.MY_KEY) authStorage.setRuntimeApiKey("anthropic", process.env.MY_KEY);
const modelRegistry = ModelRegistry.create(authStorage);
const statusTool = defineTool({
name: "status",
label: "Status",
description: "Get system status",
parameters: Type.Object({}),
execute: async () => ({
content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }],
details: {},
}),
});
const model = getModel("anthropic", "claude-opus-4-5");
const settingsManager = SettingsManager.inMemory({
compaction: { enabled: false },
retry: { enabled: true, maxRetries: 2 },
});
const loader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: "/custom/agent",
settingsManager,
systemPromptOverride: () => "You are a minimal assistant. Be concise.",
});
await loader.reload();
const { session } = await createAgentSession({
cwd: process.cwd(),
agentDir: "/custom/agent",
model,
thinkingLevel: "off",
authStorage,
modelRegistry,
tools: ["read", "bash", "status"],
customTools: [statusTool],
resourceLoader: loader,
sessionManager: SessionManager.inMemory(),
settingsManager,
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("Get status and list files.");
四种运行模式
Pi 把「同一个 agent session」暴露给四种不同的运行环境。
1. Interactive Mode(TUI)
完整 TUI 交互模式——编辑器、聊天历史、全部内置命令:
import { InteractiveMode } from "@earendil-works/pi-coding-agent";
const mode = new InteractiveMode(runtime, {
migratedProviders: [],
modelFallbackMessage: undefined,
initialMessage: "Hello",
initialImages: [],
initialMessages: [],
});
await mode.run();
CLI 等价:直接运行 pi 命令。
界面分四区:
- Startup header — 快捷键、上下文文件、提示模板、技能、扩展
- Messages — 用户消息、助手回复、工具调用/结果、通知、错误、扩展 UI
- Editor — 输入区,边框颜色表示当前思考等级
- Footer — 工作目录、会话名、token/缓存用量、成本、上下文用量、当前模型
2. Print Mode(单次执行)
import { runPrintMode } from "@earendil-works/pi-coding-agent";
await runPrintMode({
mode: "text",
initialMessage: "Summarize this codebase",
initialImages: [],
messages: [],
});
CLI 等价:pi -p "Summarize this codebase"。Print 模式下 pi 还会读取管道 stdin 并合并到初始提示:
cat README.md | pi -p "Summarize this text"
适用场景:CI 集成、批处理、一次性问答。
3. RPC Mode(stdin/stdout JSONL)
import { runRpcMode } from "@earendil-works/pi-coding-agent";
await runRpcMode();
CLI 等价:pi --mode rpc --no-session。
通过 stdin/stdout 的 JSONL RPC 协议——客户端发命令、服务器回响应/事件。适用于子进程集成,从其他语言集成、需要进程隔离、构建语言无关的客户端。
这是 Pi 的「协议层」——它把整个 agent runtime 暴露成 JSONL 流,任何能读写 stdin/stdout 的进程都能成为客户端。
4. JSON Mode(结构化事件流)
CLI 等价:pi --mode json。
输出所有事件为 JSON 行——适合用 jq 之类工具做后处理或管道到其他系统。
SDK vs CLI RPC:什么时候用哪个
| 维度 | SDK | CLI RPC |
|---|---|---|
| 类型安全 | 完整 TypeScript | JSON schema |
| 进程模型 | 同一 Node.js 进程 | 子进程 |
| 直接访问 agent state | ✅ | ❌ |
| 多语言集成 | 仅 TypeScript / Node.js | 任意语言 |
| 隔离性 | 同进程(共享内存) | 进程隔离 |
| 性能 | 零 IPC 开销 | 序列化 + IPC |
| 自定义 tools/extensions | 编程式 | 仅通过文件 |
经验法则:
- 在自己的 Node.js 应用里嵌入 agent → SDK
- 需要从 Python / Go / Rust 集成 → CLI RPC
- CI 中调一次问问题 → CLI Print Mode
- 需要实时事件流给前端 → CLI RPC 或 SDK
主要导出
Pi 的 SDK 表面相当大,按用途分组:
| 分组 | 导出 |
|---|---|
| 工厂函数 | createAgentSession、createAgentSessionRuntime、AgentSessionRuntime |
| Auth / Models | AuthStorage、ModelRegistry |
| 资源加载 | DefaultResourceLoader、ResourceLoader、createEventBus |
| 常量 / 辅助 | defineTool、getAgentDir、SessionManager、SettingsManager |
| 工具工厂 | createCodingTools、createReadOnlyTools、各工具 独立工厂 |
| 类型 | CreateAgentSessionOptions、ExtensionFactory、ExtensionAPI、ToolDefinition、Skill、PromptTemplate、Tool |
createCodingTools 是 read / bash / edit / write 等内置工具的工厂,createReadOnlyTools 是只读版本(read / grep / find / ls)。SDK 用户可以基于这两个工厂自定义工具集。
createAgentSessionRuntime:session 替换场景
当你想替换活跃 session(比如 /new、/resume、/fork、/clone、importFromJsonl)时用 createAgentSessionRuntime()。它跟 createAgentSession() 的关键差异:
const { session, runtime } = await createAgentSessionRuntime(...);
// runtime.session 在这些操作后会变化:
await runtime.newSession();
await runtime.switchSession(path);
await runtime.fork(entryId);
await runtime.fork(entryId, { position: "at" });
几个 footgun:
runtime.session在这些操作后会变化——event 订阅绑定到特定AgentSession,替换后需重新订阅- 使用 extensions 时需重新调用
runtime.session.bindExtensions(...) runtime.diagnostics返回诊断信息——调试时先看这里
ResourceLoader
DefaultResourceLoader 是默认的 ResourceLoader 实现,发现 extensions、skills、prompts、themes、context files。
const loader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: "~/.pi/agent", // 默认,展开 ~
settingsManager,
systemPromptOverride: () => "You are a minimal assistant.",
});
await loader.reload();
await loader.reload() 后访问 loader.getExtensions()、loader.getSkills()、loader.getPrompts()、loader.getThemes()、loader.getAgentsFiles().agentsFiles。
自定义 ResourceLoader 时,cwd 和 agentDir 不控制资源发现,但仍影响 session 命名和工具路径解析。
SettingsManager
const settingsManager = SettingsManager.create();
settingsManager.applyOverrides({
compaction: { enabled: false },
retry: { enabled: true, maxRetries: 5 },
});
// 内存版(不持久化)
const inMemorySettings = SettingsManager.inMemory({ compaction: { enabled: false } });
设置合并:全局 ~/.pi/agent/settings.json + 项目 <cwd>/.pi/settings.json,项目覆盖全局,嵌套对象合并键。
持久化语义:setter 在内存中是同步的;setter 异步入队持久化写入;需要持久性边界时调用 await settingsManager.flush();通过 settingsManager.drainErrors() 报告错误。
写一个 CLI 包装
把上面 所有东西组装起来,就是一个最小的 Pi CLI 包装:
#!/usr/bin/env node
import { createAgentSession, SessionManager, createEventBus } from "@earendil-works/pi-coding-agent";
import { createInterface } from "readline";
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
if (event.type === "agent_end") {
console.log("\n[done]");
}
});
const rl = createInterface({ input: process.stdin, output: process.stdout });
const prompt = () => {
rl.question("> ", async (line) => {
if (line === "quit") process.exit(0);
await session.prompt(line);
prompt();
});
};
prompt();
40 行代码就是一个能跑的 Pi agent 包装——这就是 SDK 设计的目标:让「在应用里嵌入一个 agent」这件事降到最低成本。
收束:Pi 给 AI 产品开发的启示
回到第六章一开始的「minimal core」哲学。把 6 章放在一起看,Pi 给出的几条核心启发:
1. 核心要小、行为由扩展装配。 「把什么放核心」这件事没有一个普适答案,但有一个判断准则——如果某个能力是「工作流偏好」(plan mode、to-do、sub-agent),放在扩展;如果某个能力是「产品基础」(agent 循环、session 存储、provider 适配),放在核心。Pi 是后者的极小集合。
2. 数据结构是能力的来源。 Session 设计成树形 + JSONL 这一件事,就同时撑起了:分支、回滚、/tree 导航、压缩、分支摘要、扩展状态恢复、跨 session 类型化通信。很多看起来很复杂的能力,其实是数据结构的自然推论——不需要单独设计。
3. 把决策权推给用户,而不是替用户决定。 无沙箱、trust 决策、provider 列表、思考等级——Pi 在这些地方都明确说「你来决定」。这种克制换来的是核心的稳定性和 诚实的用户体验。
4. 一个 runtime,四种使用姿势。 CLI / SDK / RPC / JSON 不是四个产品,是一个 runtime 的四种调用方式。createAgentSession() 是统一入口,避免了「每个使用场景都要重新适配一遍」的浪费。
5. 扩展 API 要够广、够细。 30+ 种生命周期事件、registerTool / Command / Provider / Shortcut / Flag / MessageRenderer 全套注册 API、状态通过 session entry 持久化——这套 API 覆盖了「给 Pi 加功能」的几乎所有场景。核心不需要预测未来需要什么能力,扩展层会自然演化出来。
第七章进入 Agent Mini。前面 6 章我们从原理(Part 1-3)、工程(Part 4)、参考实现(Part 5、6)走完了整个 AI Agent 知识地图。第七章要做的是把这些概念落到一段能跑的代码里——搭一个 200 行的微缩版 agent,把 TAOR 循环、工具、记忆、上下文管理串成完整的可运行形态。