Skip to main content

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:什么时候用哪个

维度SDKCLI RPC
类型安全完整 TypeScriptJSON 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 表面相当大,按用途分组:

分组导出
工厂函数createAgentSessioncreateAgentSessionRuntimeAgentSessionRuntime
Auth / ModelsAuthStorageModelRegistry
资源加载DefaultResourceLoaderResourceLoadercreateEventBus
常量 / 辅助defineToolgetAgentDirSessionManagerSettingsManager
工具工厂createCodingToolscreateReadOnlyTools、各工具独立工厂
类型CreateAgentSessionOptionsExtensionFactoryExtensionAPIToolDefinitionSkillPromptTemplateTool

createCodingToolsread / bash / edit / write 等内置工具的工厂,createReadOnlyTools 是只读版本(read / grep / find / ls)。SDK 用户可以基于这两个工厂自定义工具集。

createAgentSessionRuntime:session 替换场景

当你想替换活跃 session(比如 /new/resume/fork/cloneimportFromJsonl)时用 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 时,cwdagentDir 不控制资源发现,但仍影响 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 循环、工具、记忆、上下文管理串成完整的可运行形态。