pi Coding Agent SDK 完全指南:从 createAgentSession 到运行时替换的编程式集成
本文以 SDK 示例文档 为主体,系统讲解 pi coding-agent 的编程式(SDK)用法:如何通过 createAgentSession() 快速构建会话,以及如何通过 createAgentSessionRuntime() 构建可替换活动的运行时。文中所有示例均取自仓库内的真实示例文件,并对照 src/core/sdk.ts 的源码补充了参数默认值、事件模型与调用链细节,读完你可以独立把 pi agent 嵌入自己的 CLI、服务或自动化流水线。
SDK 的两个核心入口
SDK 的官方示例目录位于 examples/sdk/,围绕两个核心 API 展开:
createAgentSession():一行代码即可获得一个功能完整的 agent 会话,默认自动发现 skills、extensions、工具和上下文文件(AGENTS.md 等);createAgentSessionRuntime():面向需要"动态替换当前活动会话"的场景(新建会话、resume、fork、import),传入一个 recreate 函数,它闭包持有进程级的固定输入,并在活动会话的 cwd 变化时重建 cwd 绑定的服务与会话。
底层实现在 src/core/sdk.ts:createAgentSession() 接受 CreateAgentSessionOptions,返回 CreateAgentSessionResult,其中包含 session(AgentSession 实例)、extensionsResult(扩展加载结果,供交互模式做 UI 上下文)以及可选的 modelFallbackMessage(当恢复的会话与保存时的模型不一致时的提示)。该文件同时还做了两件重要的初始化工作:
- 通过
setDefaultStreamFn(streamSimple)为未显式传入streamFn的Agent实例提供默认流式函数(兼容旧版本扩展的兜底行为); - 导出模型/认证运行时
ModelRuntime、会话管理SessionManager、资源加载DefaultResourceLoader等全部构建块。
示例清单与运行方式
仓库内置了 13 个由浅入深的可运行示例(README 中的表格与文件名一一对应,注意文件名以仓库实际为准):
| 文件 | 说明 |
|---|---|
| 01-minimal.ts | 最简用法,全部使用默认值 |
| 02-custom-model.ts | 选择模型与 thinking 级别 |
| 03-custom-prompt.ts | 替换或修改系统提示词 |
| 04-skills.ts | 发现、过滤或替换 skills |
| 05-tools.ts | 内置工具白名单 |
| 06-extensions.ts | 日志、拦截、修改结果 |
| 07-context-files.ts | AGENTS.md 上下文文件 |
| 08-prompt-templates.ts | 文件型斜杠命令 / 提示模板 |
| 09-api-keys-and-oauth.ts | API key 解析与 OAuth 配置 |
| 10-settings.ts | 覆盖 compaction、retry、终端等设置 |
| 11-sessions.ts | 内存、持久化、继续、列举会话 |
| 12-full-control.ts | 全量替换,关闭一切自动发现 |
| 13-session-runtime.ts | 管理运行时级别的会话替换 |
运行方式(README 原文命令,以 Node 直接执行 TS 为例):
cd packages/coding-agent
npx tsx examples/sdk/01-minimal.ts
最小可用示例:一行创建会话
01-minimal.ts 展示了最短路径——不传任何配置:
import { createAgentSession } from "@earendil-works/pi-coding-agent";
const { session } = await createAgentSession();
try {
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("What files are in the current directory?");
session.state.messages.forEach((msg) => {
console.log(msg);
});
} finally {
session.dispose();
}
这里能学到三个基本纪律:
- 订阅事件流:
session.subscribe()是唯一的实时输出通道,message_update事件携带assistantMessageEvent,其中text_delta子类型表示增量文本; await session.prompt():prompt 是异步的,等到 agent 完成本轮(含工具循环)才 resolve;session.dispose():会话持有扩展运行时的资源,用完必须释放,示例统一用try/finally保证清理。
默认行为方面(对照 CreateAgentSessionOptions 源码注释):cwd 默认 process.cwd(),agentDir 默认 ~/.pi/agent,model 从设置或第一个可用模型解析,thinkingLevel 默认 medium 且会被钳制到模型能力范围内,未提供 tools 时启用默认内置工具 read, bash, edit, write(若设置了 defaultTools 设置则优先用它做初始选择)。
模型与 Thinking 级别:ModelRuntime 三种取法
02-custom-model.ts 演示了 ModelRuntime 的三种找模型方式,这也是自定义模型时的标准流程:
import { createAgentSession, ModelRuntime } from "@earendil-works/pi-coding-agent";
const modelRuntime = await ModelRuntime.create();
// Option 1: 按 provider/id 精确找内置模型
const opus = modelRuntime.getModel("anthropic", "claude-opus-4-5");
// Option 2: 通过 registry 查找(包含 models.json 中定义的自定义模型)
const customModel = modelRuntime.getModel("my-provider", "my-model");
// Option 3: 拿到所有"当前有有效 API key"的可用模型
const available = await modelRuntime.getAvailable();
if (available.length > 0) {
const { session } = await createAgentSession({
model: available[0],
thinkingLevel: "medium", // off, low, medium, high
modelRuntime,
});
// ...prompt / dispose
}
关键细节:
ModelRuntime是模型目录 + 认证凭据的规范运行时(canonical runtime),getModel()是同步查找、返回可空值(需要判空),getAvailable()是异步的,因为它要检查凭据有效性;thinkingLevel支持off / low / medium / high,最终会被clampThinkingLevel(来自 pi-ai 的 compat 层)按模型实际能力钳制,对不支持思考的模型不会报错而是自动降级;- 注意 02-custom-model.ts 使用的是
modelRuntime.getModel(),而 12-full-control.ts 用的是 pi-ai 包的getModel("anthropic", "claude-sonnet-4-5")——后者直接从模型目录取定义,不依赖运行时,两者可配合使用。
系统提示词:替换、追加与"静默"控制
03-custom-prompt.ts 展示了 DefaultResourceLoader 的两个提示词钩子,覆盖两个常见诉求:
// Option 1: 完全替换提示词
const loader1 = new DefaultResourceLoader({
cwd,
agentDir,
systemPromptOverride: () => `You are a helpful assistant that speaks like a pirate.
Always end responses with "Arrr!"`,
// 避免 DefaultResourceLoader 追加 ~/.pi/agent 或 <cwd>/.pi 中的 APPEND_SYSTEM.md
appendSystemPromptOverride: () => [],
});
await loader1.reload();
const { session: session1 } = await createAgentSession({
resourceLoader: loader1,
sessionManager: SessionMemory.inMemory(), // 实际示例中为 SessionManager.inMemory()
});
// Option 2: 在默认提示词基础上追加指令
const loader2 = new DefaultResourceLoader({
cwd,
agentDir,
appendSystemPromptOverride: (base) => [
...base,
"## Additional Instructions\n- Always be concise\n- Use bullet points when listing things",
],
});
await loader2.reload();
要点(来自示例源码的注释,值得牢记):
- 替换提示词时务必同时提供
appendSystemPromptOverride: () => []。否则即使systemPromptOverride替换了主提示词,loader 仍会扫描~/.pi/agent/和<cwd>/.pi/下的APPEND_SYSTEM.md并追加到尾部,造成"替换不彻底"; appendSystemPromptOverride的入参base是 loader 已发现的追加内容数组,追加自定义指令时要用展开运算符保留原有条目;- 每个 loader 修改后都需要
await loader.reload(),再传给createAgentSession()。
README 的 Quick Reference 还给出第三种更轻量的写法——用函数形式基于默认提示词做增量修改:systemPromptOverride: (base) => ${base}\n\nBe concise.``。
工具管理:白名单、只读模式与自定义 cwd
05-tools.ts 覆盖三种典型工具配置。tools 选项是跨内置工具、扩展工具、自定义工具的允许名单(按名称匹配所有可用工具):
// 只读模式(不含 edit/write)
const { session: readOnlySession } = await createAgentSession({
tools: ["read", "grep", "find", "ls"],
sessionManager: SessionManager.inMemory(),
});
// 自定义工具组合
const { session: customToolsSession } = await createAgentSession({
tools: ["read", "bash", "grep"],
sessionManager: SessionManager.inMemory(),
});
// 配合自定义 cwd:createAgentSession 会在构建实际内置工具时应用该 cwd
const customCwd = "/path/to/project";
const { session: customCwdSession } = await createAgentSession({
cwd: customCwd,
tools: ["read", "bash", "edit", "write"],
sessionManager: SessionManager.inMemory(customCwd),
});
源码侧还有两个 README 表格未列全、但实践中很有用的相关选项(见 src/core/sdk.ts 的 CreateAgentSessionOptions 注释):
noTools: "all" | "builtin":在未提供显式白名单时的默认抑制模式。"builtin"会禁用默认内置工具(read, bash, edit, write)但保留扩展/自定义工具;excludeTools:名字级拒绝名单,当tools与excludeTools同时提供时,excludeTools在后生效(先白名单后黑名单)。
示例文件的头部注释还明确了两点实现约束:工具名会与所有可用工具(包括扩展注册的)匹配;如果使用了自定义 cwd,createAgentSession() 会在真正构造内置工具时应用该 cwd(即文件类工具的作用域跟随会话 cwd)。自定义工具的注册方式则统一走扩展系统(见下节 pi.registerTool())。
扩展系统:拦截事件、注册工具与命令
06-extensions.ts 展示了扩展的两种注入方式,这也是日志、审计、安全拦截、结果修改的统一入口:
const resourceLoader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: getAgentDir(),
additionalExtensionPaths: ["./my-logging-extension.ts", "./my-safety-extension.ts"],
extensionFactories: [
(pi) => {
pi.on("agent_start", () => {
console.log("[Inline Extension] Agent starting");
});
},
],
});
await resourceLoader.reload();
扩展文件的默认发现位置(示例头注释):~/.pi/agent/extensions/、<cwd>/.pi/extensions/,以及 settings.json 中 "extensions" 数组指定的路径;SDK 侧则通过 additionalExtensionPaths 与 extensionFactories 追加。
示例文件内嵌了一份完整的扩展写法模板(my-logging-extension.ts 注释块),值得完整保留:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.on("agent_start", async () => {
console.log("[Extension] Agent starting");
});
pi.on("tool_call", async (event) => {
console.log(`[Extension] Tool: ${event.toolName}`);
// 返回 { block: true, reason: "..." } 即可阻止执行
return undefined;
});
pi.on("agent_end", async (event) => {
console.log(`[Extension] Low-level run ended, ${event.messages.length} messages`);
});
// 注册自定义工具
pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "Does something useful",
parameters: Type.Object({ input: Type.String() }),
execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => ({
content: [{ type: "text", text: `Processed: ${params.input}` }],
details: {},
}),
});
// 注册命令
pi.registerCommand("mycommand", {
description: "Do something",
handler: async (args, ctx) => {
ctx.ui.notify(`Command executed with: ${args}`);
},
});
}
其中 tool_call 事件处理器返回 { block: true, reason: "..." } 是拦截工具执行的标准手段——这是把"安全护栏"嵌入 agent 的关键机制;pi.registerTool() 注册的自定义工具随后可以用 tools 白名单里的名字(如 "my_tool")来启用,也可通过 customTools 选项直接传入 ToolDefinition 数组。
会话管理:内存、持久化、继续与会话列表
11-sessions.ts 覆盖 SessionManager 的四种工厂方法,全部是静态调用:
// 1) 纯内存,不落盘
const { session: inMemory } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
// 2) 新建持久化会话(默认目录 ~/.pi/agent/sessions,按 cwd 编码)
const { session: newSession } = await createAgentSession({
sessionManager: SessionManager.create(process.cwd()),
});
console.log("New session file:", newSession.sessionFile);
// 3) 继续最近一次会话(不存在则新建),模型不一致时会有 modelFallbackMessage
const { session: continued, modelFallbackMessage } = await createAgentSession({
sessionManager: SessionManager.continueRecent(process.cwd()),
});
// 4) 列举并打开指定会话
const sessions = await SessionManager.list(process.cwd());
const { session: opened } = await createAgentSession({
sessionManager: SessionManager.open(sessions[0].path),
});
补充细节(来自示例末尾的注释块):SessionManager.create(cwd, customDir)、SessionManager.list(cwd, customDir)、SessionManager.continueRecent(cwd, customDir) 均支持第二个参数指定自定义会话目录,便于把会话存储与项目目录解耦;恢复旧会话时返回的 modelFallbackMessage 值得展示给用户,它提示"会话保存时用的模型与当前不同"。
完全控制模式:关掉一切自动发现
12-full-control.ts 是生产环境中最有价值的示例——不依赖任何磁盘状态,全部显式注入:
import { getModel } from "@earendil-works/pi-ai/compat";
// 自定义凭据与模型目录路径
const modelRuntime = await ModelRuntime.create({
authPath: "/tmp/my-agent/auth.json",
modelsPath: "/tmp/my-agent/models.json",
});
if (process.env.MY_ANTHROPIC_KEY) {
await modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_ANTHROPIC_KEY);
}
const model = getModel("anthropic", "claude-sonnet-4-5");
// 内存设置 + 覆盖项
const settingsManager = SettingsManager.inMemory({
compaction: { enabled: false },
retry: { enabled: true, maxRetries: 2 },
});
// 手写 ResourceLoader 接口实现:所有资源返回空,系统提示词硬编码
const resourceLoader: ResourceLoader = {
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
getSkills: () => ({ skills: [], diagnostics: [] }),
getPrompts: () => ({ prompts: [], diagnostics: [] }),
getThemes: () => ({ themes: [], diagnostics: [] }),
getAgentsFiles: () => ({ agentsFiles: [] }),
getSystemPrompt: () => `You are a minimal assistant.\nAvailable: read, bash. Be concise.`,
getSystemPromptSource: () => undefined,
getAppendSystemPrompt: () => [],
getAppendSystemPromptSources: () => [],
extendResources: () => {},
reload: async () => {},
};
const { session } = await createAgentSession({
cwd,
agentDir: "/tmp/my-agent",
model,
thinkingLevel: "off",
modelRuntime,
resourceLoader,
tools: ["read", "bash"],
sessionManager: SessionManager.inMemory(cwd),
settingsManager,
});
从这个示例可以读出 ResourceLoader 接口的完整方法面:getExtensions / getSkills / getPrompts / getThemes / getAgentsFiles / getSystemPrompt(+Source) / getAppendSystemPrompt(+Sources) / extendResources / reload。当 DefaultResourceLoader 的发现机制不符合需求时,直接实现该接口即可"关掉一切发现"。同时注意 setRuntimeApiKey() 的存在——它把密钥只放入运行时内存而不落盘到 auth.json,是 SDK 集成中处理密钥的推荐姿势。
运行时与会话替换:createAgentSessionRuntime
13-session-runtime.ts 对应 README 首段描述的"recreate 函数"模式,专治 new-session / resume / fork / import 这类需要整体替换活动会话的流程:
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
const services = await createAgentSessionServices({ cwd });
return {
...(await createAgentSessionFromServices({
services,
sessionManager,
sessionStartEvent,
})),
services,
diagnostics: services.diagnostics,
};
};
const runtime = await createAgentSessionRuntime(createRuntime, {
cwd: process.cwd(),
agentDir: getAgentDir(),
sessionManager: SessionManager.create(process.cwd()),
});
// 关键模式:每次会话被替换后,重新绑定会话级订阅与扩展绑定
async function bindSession() {
unsubscribe?.();
const session = runtime.session;
await session.bindExtensions({});
unsubscribe = session.subscribe((event) => { /* ... */ });
return session;
}
let session = await bindSession();
const originalSessionFile = session.sessionFile;
await runtime.newSession(); // 新建会话
session = await bindSession(); // 必须重新绑定
await runtime.switchSession(originalSessionFile); // 切回原会话
session = await bindSession();
从 src/core/agent-session-runtime.ts 的源码结构看,AgentSessionRuntime 暴露 switchSession()、newSession()(内部调用 sessionManager.newSession({ parentSession }) 以支持分支/父会话链)等方法,createAgentSessionRuntime() 在第 414 行附近定义。该文件同时通过 src/core/sdk.ts 末尾的 export * from "./agent-session-runtime.ts" 对外透出,所以 SDK 使用者直接从 @earendil-works/pi-coding-agent 顶层 import 即可。
示例头注释强调了这条 API 最重要的使用纪律:运行时替换活动会话后,runtime.session 是一个新对象,所有会话级订阅(subscribe)与扩展绑定(bindExtensions)都必须对 runtime.session 重新执行,否则会静默丢失事件。
事件订阅:SDK 的实时输出契约
README 末尾的 Events 段落给出了会话事件的标准消费方式,四类事件覆盖了绝大多数集成需求:
session.subscribe((event) => {
switch (event.type) {
case "message_update":
if (event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
break;
case "tool_execution_start":
console.log(`Tool: ${event.toolName}`);
break;
case "tool_execution_end":
console.log(`Result: ${event.result}`);
break;
case "agent_settled":
console.log("Done");
break;
}
});
事件语义上:message_update 是流式增量(含 text_delta 等子类型,按 assistantMessageEvent.type 区分文本/思考增量);tool_execution_start / tool_execution_end 成对出现,end 事件携带 result;agent_settled 表示一轮 agent 运行收敛完成,适合驱动 UI 状态复位。此外 13-session-runtime.ts 还演示了 queue_update 事件(带 steering / followUp 队列长度),说明用户侧的排队提示也可通过订阅观察。
createAgentSession 选项速查(源码级补全)
综合 README 的 Options 表格与 src/core/sdk.ts 中 CreateAgentSessionOptions 的完整注释,全量选项如下:
| 选项 | 默认值 | 说明 |
|---|---|---|
modelRuntime |
使用 agentDir/auth.json 与 models.json 的运行时 |
模型与认证的规范运行时 |
cwd |
process.cwd() |
工作目录,也是项目级资源发现根 |
agentDir |
~/.pi/agent |
全局配置目录 |
model |
来自设置 / 第一个可用模型 | 使用的模型 |
thinkingLevel |
来自设置,否则 medium |
off, low, medium, high(按模型能力钳制) |
scopedModels |
无 | 可用于交互式轮换的模型列表(对应 Ctrl+P 场景) |
noTools |
无 | 未给白名单时的默认抑制模式:"all" 全部禁用 / "builtin" 仅禁用默认内置工具 |
tools |
未配置 defaultTools 时为 ["read", "bash", "edit", "write"] |
跨内置/扩展/自定义工具的名字允许名单 |
excludeTools |
无 | 名字级拒绝名单,与 tools 同用时在白名单之后生效 |
customTools |
[] |
直接注册的额外工具定义 |
resourceLoader |
DefaultResourceLoader |
扩展、skills、提示模板、主题与上下文文件的加载器 |
sessionManager |
SessionManager.create(cwd) |
会话持久化策略 |
settingsManager |
SettingsManager.create(cwd, agentDir) |
设置覆盖(如 compaction、retry) |
sessionStartEvent |
无 | 供扩展运行时启动使用的会话开始元数据 |
注意 README 表格中 thinkingLevel 的默认描述与源码注释存在细微差异:README 写 "off",源码注释写 medium(且会被钳制到模型能力)。以 src/core/sdk.ts 的当前注释为准,实际生效级别仍取决于具体模型的 reasoning 能力声明。
小结:推荐的集成路径
结合 13 个示例可以归纳出一条清晰的演进路线:
- 原型期:
01-minimal.ts,一行createAgentSession()验证链路; - 定制期:按需叠加
modelRuntime+thinkingLevel(02)、提示词钩子(03)、tools白名单 / 只读模式(05)、扩展拦截与自定义工具(06)、skills 与上下文文件(04/07)、设置覆盖(10); - 产品期:参考
12-full-control.ts关掉自动发现、用setRuntimeApiKey管理密钥、用SessionManager工厂方法控制会话生命周期;需要"新会话/恢复/切换"UI 时,按13-session-runtime.ts的模式实现 recreate 函数并严格遵守"替换后重新绑定订阅与扩展"的纪律。
全部示例可直接在仓库中查看并运行:cd packages/coding-agent && npx tsx examples/sdk/<file>.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00