pi coding-agent SDK 详解:用 createAgentSession 与 AgentSessionRuntime 构建可编程 Agent 应用
pi 的 coding-agent 包通过 SDK 将 pi 的 agent 能力(会话、工具、扩展、模型运行时)以类型安全的 API 暴露出来,让你可以在自己的应用中嵌入 pi、构建自定义界面或自动化流水线。本文基于仓库中的 SDK 文档 与 SDK 实现源码,完整覆盖 createAgentSession()、AgentSession、AgentSessionRuntime、ModelRuntime、ResourceLoader 等核心 API 的用法与底层行为,帮助你从零搭建一个最小 agent 会话,到实现全参数控制的完整集成。
安装与定位
SDK 包含在主包中,无需单独安装:
npm install @earendil-works/pi-coding-agent
典型使用场景包括:
- 构建自定义 UI(web、桌面、移动端)
- 将 agent 能力集成到既有应用
- 创建带 agent 推理的自动化流水线
- 构建会派生子 agent 的自定义工具
- 以编程方式测试 agent 行为
仓库提供了从最小化到完全控制的成套示例,位于 examples/sdk/,共 13 个由浅入深的 TypeScript 示例(如 01-minimal.ts 到 12-full-control.ts、13-session-runtime.ts),可配合本文对照阅读。
快速上手
最小可运行的 SDK 集成只需十几行:
import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
const modelRuntime = await ModelRuntime.create();
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
modelRuntime,
});
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?");
这段代码做了三件事:创建模型运行时(负责模型目录与凭据)、创建一个内存会话、订阅事件流把增量文本打到标准输出,最后发送一条 prompt 并等待整轮执行完成。
核心概念
createAgentSession():会话工厂
createAgentSession() 是创建单个 AgentSession 的主工厂函数。它会使用一个 ResourceLoader 来提供扩展、skills、prompt 模板、主题和上下文文件;如果不提供,就使用 DefaultResourceLoader 进行标准发现。
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
// 最简:默认值 + DefaultResourceLoader
const { session } = await createAgentSession();
// 自定义:覆盖特定选项
const { session } = await createAgentSession({
model: myModel,
tools: ["read", "bash"],
sessionManager: SessionManager.inMemory(),
});
从源码 sdk.ts 的 CreateAgentSessionOptions 接口可以看到全部可选参数及其默认值:
cwd:项目级发现的根目录,默认process.cwd();agentDir:全局配置目录,默认~/.pi/agent;modelRuntime:模型/认证运行时,默认基于agentDir下的auth.json和models.json创建;model/thinkingLevel/scopedModels:模型与思考级别(思考级别默认medium,会被钳制到模型能力范围内);noTools/tools/excludeTools/customTools:工具选择,源码注释明确tools为允许列表,excludeTools在tools之后生效;resourceLoader/sessionManager/settingsManager/sessionStartEvent。
工厂内部的模型决策链在 sdk.ts 中可见:若会话有历史数据则尝试恢复原模型(恢复失败会给出 modelFallbackMessage);否则调用 findInitialModel() 依次检查设置中的默认模型、provider 默认模型;thinkingLevel 还会先从会话树中的 thinking_level_change 条目恢复,再回退到按模型覆盖、全局默认,最后 clampThinkingLevel() 钳制到模型支持的范围。
AgentSession:会话对象
AgentSession 管理 agent 生命周期、消息历史、模型状态、压缩(compaction)和事件流:
interface AgentSession {
// 发送 prompt 并等待完成
prompt(text: string, options?: PromptOptions): Promise<void>;
// 流式期间入队消息
steer(text: string): Promise<void>;
followUp(text: string): Promise<void>;
// 订阅事件(返回取消订阅函数)
subscribe(listener: (event: AgentSessionEvent) => void): () => void;
// 会话信息
sessionFile: string | undefined;
sessionId: string;
// 模型控制
setModel(model: Model): Promise<void>;
setThinkingLevel(level: ThinkingLevel): void;
cycleModel(): Promise<ModelCycleResult | undefined>;
cycleThinkingLevel(): ThinkingLevel | undefined;
// 状态访问
agent: Agent;
model: Model | undefined;
thinkingLevel: ThinkingLevel;
messages: AgentMessage[];
isStreaming: boolean;
// 在当前会话文件内做原地树导航
navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;
// 压缩
compact(customInstructions?: string): Promise<CompactionResult>;
abortCompaction(): void;
// 中止当前操作
abort(): Promise<void>;
// 清理
dispose(): void;
}
注意:new-session、resume、fork、import 这类会话替换 API 不在 AgentSession 上,而在 AgentSessionRuntime 上(见下节)。AgentSession 的完整实现位于 agent-session.ts,其中 prompt() 的文档注释也写明了行为契约:非流式时直接处理,流式时根据 streamingBehavior 选项经 steer() 或 followUp() 入队,两者都缺失则抛错。
createAgentSessionRuntime() 与 AgentSessionRuntime
当你需要替换当前活跃会话并重建绑定 cwd 的运行时状态时,使用 runtime API。这也是内置 interactive、print、RPC 模式所用的同一层。
createAgentSessionRuntime() 接收一个 runtime 工厂函数和初始 cwd/会话目标。工厂函数闭包住进程级固定输入,为有效 cwd 重新创建绑定 cwd 的服务,解析会话选项,并返回完整的 runtime 结果:
import {
type CreateAgentSessionRuntimeFactory,
createAgentSessionFromServices,
createAgentSessionRuntime,
createAgentSessionServices,
getAgentDir,
SessionManager,
} from "@earendil-works/pi-coding-agent";
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()),
});
AgentSessionRuntime 拥有跨以下操作的活跃运行时替换逻辑(对应 agent-session-runtime.ts 中的 switchSession、newSession、fork、importFromJsonl 方法):
newSession()switchSession()fork()- 克隆流程
fork(entryId, { position: "at" }) importFromJsonl()
重要行为约定:
- 上述操作之后
runtime.session会变化; - 事件订阅绑定在具体的
AgentSession实例上,替换会话后必须重新订阅; - 如果使用了扩展,需对新会话再次调用
runtime.session.bindExtensions(...); - 创建结果会在
runtime.diagnostics上返回诊断信息; - runtime 创建或替换失败时方法直接抛出异常,由调用方决定如何处理。
let session = runtime.session;
let unsubscribe = session.subscribe(() => {});
await runtime.newSession();
unsubscribe();
session = runtime.session;
unsubscribe = session.subscribe(() => {});
Prompting 与消息入队
PromptOptions 控制 prompt 展开、流式期间的入队行为以及预检通知(接口定义见 agent-session.ts):
interface PromptOptions {
expandPromptTemplates?: boolean;
images?: ImageContent[];
streamingBehavior?: "steer" | "followUp";
source?: InputSource;
preflightResult?: (success: boolean) => void;
}
preflightResult 在每次 prompt() 调用中恰好触发一次:
true:prompt 被接受、入队或立即处理;false:prompt 在预检阶段被拒绝。
它在 prompt() 解析之前触发;而 prompt() 只有在被接受的完整运行结束(包括重试)后才解析。接受之后发生的失败通过正常的事件/消息流上报,而不是 preflightResult(false)。
prompt() 负责处理 prompt 模板、扩展命令和消息发送:
// 基本 prompt(非流式时)
await session.prompt("What files are here?");
// 带图片
await session.prompt("What's in this image?", {
images: [{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } }]
});
// 流式期间:必须指定消息的入队方式
await session.prompt("Stop and do this instead", { streamingBehavior: "steer" });
await session.prompt("After you're done, also check X", { streamingBehavior: "followUp" });
行为规则:
- 扩展命令(如
/mycommand):立即执行,即使在流式期间——它通过pi.sendMessage()自己管理 LLM 交互; - 基于文件的 prompt 模板(来自
.md文件):发送或入队前先展开为其内容; - 流式期间未指定
streamingBehavior:抛错。直接改用steer()/followUp()或指定该选项(源码在 agent-session.ts 中会抛出 "Agent is already processing. Specify streamingBehavior..." 异常); preflightResult(true)表示被接受/入队/立即处理;preflightResult(false)表示预检拒绝。
流式期间显式入队:
// 排队一条转向消息:当前 assistant 轮次完成其工具调用后送达
await session.steer("New instruction");
// 等待 agent 结束(只有 agent 停下来才送达)
await session.followUp("After you're done, also do this");
steer() 和 followUp() 都会展开基于文件的 prompt 模板,但遇到扩展命令会报错(扩展命令不能被入队)。
Agent 与 AgentState
Agent 类(来自 @earendil-works/pi-agent-core)负责核心 LLM 交互,通过 session.agent 访问:
// 访问当前状态
const state = session.agent.state;
// state.messages: AgentMessage[] - 对话历史
// state.model: Model - 当前模型
// state.thinkingLevel: ThinkingLevel - 当前思考级别
// state.systemPrompt: string - 系统提示词
// state.tools: AgentTool[] - 可用工具
// state.streamingMessage?: AgentMessage - 当前部分完成的 assistant 消息
// state.errorMessage?: string - 最近一次 assistant 错误
// 替换消息(分支或恢复时有用)
session.agent.state.messages = messages; // 复制顶层数组
// 替换工具
session.agent.state.tools = tools; // 复制顶层数组
// 等待 agent 处理完成
await session.agent.waitForIdle();
事件系统
订阅事件以获取流式输出和生命周期通知:
session.subscribe((event) => {
switch (event.type) {
// assistant 的流式文本
case "message_update":
if (event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
if (event.assistantMessageEvent.type === "thinking_delta") {
// 思考输出(如果启用了 thinking)
}
break;
// 工具执行
case "tool_execution_start":
console.log(`Tool: ${event.toolName}`);
break;
case "tool_execution_update":
// 流式工具输出
break;
case "tool_execution_end":
console.log(`Result: ${event.isError ? "error" : "success"}`);
break;
// 消息生命周期
case "message_start":
// 新消息开始
break;
case "message_end":
// 消息完成
break;
// Agent 生命周期
case "agent_start":
// agent 开始处理 prompt
break;
case "agent_end":
// agent 完成(event.messages 包含新增消息)
break;
// 轮次生命周期(一次 LLM 响应 + 工具调用)
case "turn_start":
break;
case "turn_end":
// event.message: assistant 响应
// event.toolResults: 本轮工具结果
break;
// 会话事件(队列、压缩、重试)
case "queue_update":
console.log(event.steering, event.followUp);
break;
case "compaction_start":
case "compaction_end":
case "auto_retry_start":
case "auto_retry_end":
case "summarization_retry_scheduled":
case "summarization_retry_attempt_start":
case "summarization_retry_finished":
break;
}
});
事件层次可以概括为:agent_start → turn_start → message_start → message_update(增量)→ message_end → turn_end → agent_end,工具执行事件(tool_execution_*)穿插在 turn 内部,压缩/重试等会话级事件独立上报。
Options 参考
目录:cwd 与 agentDir
const { session } = await createAgentSession({
// DefaultResourceLoader 发现所使用的工作目录
cwd: process.cwd(), // 默认
// 全局配置目录
agentDir: "~/.pi/agent", // 默认(会展开 ~)
});
cwd 被 DefaultResourceLoader 用于:
- 项目扩展(
.pi/extensions/) - 项目 skills:
.pi/skills/,以及cwd和祖先目录中的.agents/skills/(向上直到 git 仓库根目录;不在仓库中时直到文件系统根目录) - 项目 prompts(
.pi/prompts/) - 上下文文件(从 cwd 向上查找的
AGENTS.md) - 会话目录命名
agentDir 被 DefaultResourceLoader 用于:
- 全局扩展(
extensions/) - 全局 skills:
agentDir下的skills/(如~/.pi/agent/skills/)和~/.agents/skills/ - 全局 prompts(
prompts/) - 全局上下文文件(
AGENTS.md) - 设置(
settings.json) - 自定义模型(
models.json) - 凭据(
auth.json) - 会话(
sessions/)
当传入自定义 ResourceLoader 时,cwd 和 agentDir 不再控制资源发现,但仍影响会话命名和工具路径解析。
模型选择与 ModelRuntime
import { getModel } from "@earendil-works/pi-ai";
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
const modelRuntime = await ModelRuntime.create();
// create() 会恢复缓存目录,但默认不向 pi.dev 发起网络刷新。
// 如需在创建时联网刷新并限制其时长:
const refreshedRuntime = await ModelRuntime.create({
allowModelNetwork: true,
modelRefreshTimeoutMs: 15_000,
});
// 查找特定内置模型(不检查 API key 是否存在)
const opus = getModel("anthropic", "claude-opus-4-5");
if (!opus) throw new Error("Model not found");
// 按 provider/id 查找任意模型,包括 models.json 中的自定义模型
const customModel = modelRuntime.getModel("my-provider", "my-model");
// 只取已配置有效认证的模型
const available = await modelRuntime.getAvailable();
const { session } = await createAgentSession({
model: opus,
thinkingLevel: "medium", // off, minimal, low, medium, high, xhigh, max
// 供循环切换的模型(交互模式下 Ctrl+P)
scopedModels: [
{ model: opus, thinkingLevel: "high" },
{ model: haiku, thinkingLevel: "off" },
],
modelRuntime,
});
未提供 model 时的决策顺序(与 sdk.ts 源码一致):
- 尝试从会话恢复(如果是继续会话);
- 使用设置中的默认模型;
- 回退到第一个可用模型。
远端目录的本地持久化: 远端模型目录会被持久化到本地,后续运行时可以在无网络请求的情况下恢复。默认文件是 ~/.pi/agent/models-store.json;可用 modelsStorePath 选择其他位置,或注入 modelsStore 控制持久化方式。联网刷新默认按 provider 每四小时节流一次,除非强制刷新;强制刷新调用 await modelRuntime.refresh({ allowNetwork: true, force: true, signal })。设置 PI_OFFLINE 可禁用模型网络访问。
CLI 模型解析对齐: 要与 CLI 的模型解析行为保持一致,使用导出的解析器:
import {
resolveCliModel,
resolveModelScopeWithDiagnostics,
} from "@earendil-works/pi-coding-agent";
const cliModel = resolveCliModel({
cliModel: "anthropic/claude-opus-4-5:high",
modelRuntime,
});
if (cliModel.error) throw new Error(cliModel.error);
if (cliModel.warning) console.warn(cliModel.warning);
const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(
["anthropic/*:high", "gpt-5"],
modelRuntime,
);
for (const diagnostic of diagnostics) {
console.warn(diagnostic.message);
}
resolveCliModel() 使用所有已注册模型,因此 --api-key 式的初次配置可以在存储认证存在之前解析模型;resolveModelScopeWithDiagnostics() 匹配 --models 与 enabledModels 的语义,同时以返回值而非打印的方式给出警告。
API Keys 与 OAuth
认证解析优先级(由 ModelRuntime 处理):
- 运行时覆盖(
setRuntimeApiKey,不落盘持久化) auth.json中存储的凭据(API key 或 OAuth token)- 环境变量(
ANTHROPIC_API_KEY、OPENAI_API_KEY等) - 回退解析器(用于
models.json中自定义 provider 的 key)
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
import { createAgentSession, ModelRuntime } from "@earendil-works/pi-coding-agent";
// 默认:使用 ~/.pi/agent/auth.json 和 ~/.pi/agent/models.json
const modelRuntime = await ModelRuntime.create();
// 查看 provider 的认证方式与当前状态
for (const provider of modelRuntime.getProviders()) {
const status = await modelRuntime.checkAuth(provider.id);
console.log(provider.name, provider.auth, status);
}
// 运行时 API key 覆盖(不写盘)
await modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key");
// 自定义凭据与模型文件位置
const customRuntime = await ModelRuntime.create({
authPath: "/my/app/auth.json",
modelsPath: "/my/app/models.json",
});
// 或注入任意 pi-ai 的 CredentialStore
const credentials = new InMemoryCredentialStore();
const inMemoryRuntime = await ModelRuntime.create({ credentials });
const { session } = await createAgentSession({
modelRuntime: customRuntime,
});
凭据变更的同步语义: login()、logout()、setRuntimeApiKey()、removeRuntimeApiKey() 会在受影响 provider 的缓存/内置目录、组合和可用性快照在本地一致后解析,但不等待远端目录刷新。如果凭据已写入但本地同步失败,它们会以导出的 CredentialSynchronizationError 拒绝;此时应检查其 providerId、operation、credential 和 cause 字段,而不是盲目重试凭据变更。
超时与刷新控制: 公开的模型/认证操作和 ModelRuntime.create({ signal }) 都接受可选的 abort signal,省略时不限时。SDK 应用自己负责对远端目录新鲜度设定截止策略:
const signal = AbortSignal.timeout(15_000);
const result = await modelRuntime.refresh({
providers: ["anthropic"],
signal,
});
if (result.aborted) console.warn("Catalog refresh timed out; using cached models");
for (const [providerId, error] of result.errors) {
console.warn(`Could not refresh ${providerId}:`, error);
}
失败或超时的联网刷新不会撤销已经成功的凭据操作。refresh() 开启新的 provider 代际,因此不会排在更早的卡住刷新之后等待,旧代际也不能在其后发布。
System Prompt
通过 ResourceLoader 覆盖系统提示词:
import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({
systemPromptOverride: () => "You are a helpful assistant.",
});
await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });
Tools
指定要启用的内置工具:
- 内置工具名:
read、bash、powershell、edit、write、grep、find、ls - 默认内置工具:
read、bash、edit、write noTools: "all"禁用所有工具noTools: "builtin"禁用默认内置工具,同时保留扩展工具和自定义工具excludeTools在tools允许列表应用之后禁用指定的内置、扩展或自定义工具名
edit 工具会返回 details.diff(供 pi 的 TUI 展示)和 details.patch(标准 unified patch,供 SDK 消费者使用)。
import { createAgentSession } from "@earendil-works/pi-coding-agent";
// 只读模式
const { session } = await createAgentSession({
tools: ["read", "grep", "find", "ls"],
});
// 挑选特定工具
const { session } = await createAgentSession({
tools: ["read", "bash", "grep"],
});
// Windows 上用 PowerShell 替代 Bash
const { session } = await createAgentSession({
tools: ["read", "powershell", "edit", "write"],
});
// 禁用单个工具,其余保持可用
const { session } = await createAgentSession({
excludeTools: ["ask_question"],
});
源码默认工具列表在 sdk.ts 中:const defaultActiveToolNames: ToolName[] = ["read", "bash", "edit", "write"],且设置中的 defaultTools 可以覆盖初始内置工具选择。
自定义 cwd 下的工具
传入自定义 cwd 时,createAgentSession() 会针对该 cwd 构建所选内置工具:
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
const cwd = "/path/to/project";
// 为自定义 cwd 使用默认工具
const { session } = await createAgentSession({
cwd,
sessionManager: SessionManager.inMemory(cwd),
});
// 或为自定义 cwd 挑选特定工具
const { session } = await createAgentSession({
cwd,
tools: ["read", "bash", "grep"],
sessionManager: SessionManager.inMemory(cwd),
});
完整示例见 examples/sdk/05-tools.ts。
自定义工具
import { Type } from "typebox";
import { createAgentSession, defineTool } from "@earendil-works/pi-coding-agent";
// 内联自定义工具
const myTool = defineTool({
name: "my_tool",
label: "My Tool",
description: "Does something useful",
parameters: Type.Object({
input: Type.String({ description: "Input value" }),
}),
execute: async (_toolCallId, params) => ({
content: [{ type: "text", text: `Result: ${params.input}` }],
details: {},
}),
});
// 直接传入自定义工具
const { session } = await createAgentSession({
customTools: [myTool],
});
独立定义和 customTools: [myTool] 这类数组使用 defineTool();内联的 pi.registerTool({ ... }) 已经能正确推断参数类型。通过 customTools 传入的自定义工具会与扩展注册的工具合并;由 ResourceLoader 加载的扩展也可以通过 pi.registerTool() 注册工具。如果传了 tools 允许列表,记得把每个要启用的自定义/扩展工具名包含进去,例如 tools: ["read", "bash", "my_tool"]。
完整示例见 examples/sdk/05-tools.ts。
Extensions
扩展由 ResourceLoader 加载。DefaultResourceLoader 从 ~/.pi/agent/extensions/、.pi/extensions/ 以及 settings.json 中声明的扩展源发现扩展:
import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({
additionalExtensionPaths: ["/path/to/my-extension.ts"],
extensionFactories: [
(pi) => {
pi.on("agent_start", () => {
console.log("[Inline Extension] Agent starting");
});
},
],
});
await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });
扩展可以注册工具、订阅事件、添加命令等,完整 API 见 extensions.md。
命名内联扩展: 默认情况下,内联工厂在启动时的 Extensions 列表中显示为 <inline:1>、<inline:2> 等。如需显示描述性名称,用对象包装工厂:
import type { InlineExtension } from "@earendil-works/pi-coding-agent";
const myProvider: InlineExtension = {
name: "my-provider",
factory: (pi) => {
pi.on("agent_start", () => {
console.log("[my-provider] Agent starting");
});
},
};
const loader = new DefaultResourceLoader({
extensionFactories: [myProvider],
});
这样会显示为 <inline:my-provider> 而不是 <inline:1>。裸工厂函数出于向后兼容仍被接受。
事件总线: 扩展之间可通过 pi.events 通信。如果需要在外部发射/监听事件,向 DefaultResourceLoader 传入共享的 eventBus:
import { createEventBus, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const eventBus = createEventBus();
const loader = new DefaultResourceLoader({
eventBus,
});
await loader.reload();
eventBus.on("my-extension:status", (data) => console.log(data));
Skills
import {
createAgentSession,
DefaultResourceLoader,
type Skill,
} from "@earendil-works/pi-coding-agent";
const customSkill: Skill = {
name: "my-skill",
description: "Custom instructions",
filePath: "/path/to/SKILL.md",
baseDir: "/path/to",
source: "custom",
};
const loader = new DefaultResourceLoader({
skillsOverride: (current) => ({
skills: [...current.skills, customSkill],
diagnostics: current.diagnostics,
}),
});
await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });
完整示例见 examples/sdk/04-skills.ts。
Context Files(上下文文件)
import { createAgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({
agentsFilesOverride: (current) => ({
agentsFiles: [
...current.agentsFiles,
{ path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" },
],
}),
});
await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });
Slash Commands(Prompt 模板)
import {
createAgentSession,
DefaultResourceLoader,
type PromptTemplate,
} from "@earendil-works/pi-coding-agent";
const customCommand: PromptTemplate = {
name: "deploy",
description: "Deploy the application",
source: "(custom)",
content: "# Deploy\n\n1. Build\n2. Test\n3. Deploy",
};
const loader = new DefaultResourceLoader({
promptsOverride: (current) => ({
prompts: [...current.prompts, customCommand],
diagnostics: current.diagnostics,
}),
});
await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });
会话管理
会话采用树结构,通过 id/parentId 链接,支持原地分支。
import {
type CreateAgentSessionRuntimeFactory,
createAgentSession,
createAgentSessionFromServices,
createAgentSessionRuntime,
createAgentSessionServices,
getAgentDir,
SessionManager,
} from "@earendil-works/pi-coding-agent";
// 内存(无持久化)
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
// 新的持久会话
const { session: persisted } = await createAgentSession({
sessionManager: SessionManager.create(process.cwd()),
});
// 继续最近一次会话
const { session: continued, modelFallbackMessage } = await createAgentSession({
sessionManager: SessionManager.continueRecent(process.cwd()),
});
if (modelFallbackMessage) {
console.log("Note:", modelFallbackMessage);
}
// 打开指定文件
const { session: opened } = await createAgentSession({
sessionManager: SessionManager.open("/path/to/session.jsonl"),
});
// 列出会话
const currentProjectSessions = await SessionManager.list(process.cwd());
const allSessions = await SessionManager.listAll(process.cwd());
// 会话替换 API:用于 /new、/resume、/fork、/clone 和 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()),
});
// 用全新会话替换当前会话
await runtime.newSession();
// 用另一个已保存会话替换当前会话
await runtime.switchSession("/path/to/session.jsonl");
// 从指定用户条目分叉
await runtime.fork("entry-id");
// 沿指定条目克隆当前路径
await runtime.fork("entry-id", { position: "at" });
SessionManager 树 API:
const sm = SessionManager.open("/path/to/session.jsonl");
// 会话列表
const currentProjectSessions = await SessionManager.list(process.cwd());
const allSessions = await SessionManager.listAll(process.cwd());
// 树遍历
const entries = sm.getEntries(); // 所有条目(不含 header)
const tree = sm.getTree(); // 完整树结构
const path = sm.getPath(); // 从根到当前叶子的路径
const leaf = sm.getLeafEntry(); // 当前叶子条目
const entry = sm.getEntry(id); // 按 ID 取条目
const children = sm.getChildren(id); // 条目的直接子节点
// 标签
const label = sm.getLabel(id); // 取条目标签
sm.appendLabelChange(id, "checkpoint"); // 设置标签
// 分支
sm.branch(entryId); // 把叶子移回更早的条目
sm.branchWithSummary(id, "Summary..."); // 带上下文摘要的分支
sm.createBranchedSession(leafId); // 抽取路径为新文件
完整示例见 examples/sdk/11-sessions.ts 与会话文件格式说明 session-format.md。
设置管理
import { createAgentSession, SettingsManager, SessionManager } from "@earendil-works/pi-coding-agent";
// 默认:从文件加载(全局 + 项目合并)
const { session } = await createAgentSession({
settingsManager: SettingsManager.create(),
});
// 带覆盖
const settingsManager = SettingsManager.create();
settingsManager.applyOverrides({
compaction: { enabled: false },
retry: { enabled: true, maxRetries: 5 },
});
const { session } = await createAgentSession({ settingsManager });
// 内存模式(无文件 I/O,适合测试)
const { session } = await createAgentSession({
settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
sessionManager: SessionManager.inMemory(),
});
// 自定义目录
const { session } = await createAgentSession({
settingsManager: SettingsManager.create("/custom/cwd", "/custom/agent"),
});
静态工厂:
SettingsManager.create(cwd?, agentDir?)—— 从文件加载SettingsManager.inMemory(settings?)—— 无文件 I/O
项目级设置: 设置从两个位置加载并合并:
- 全局:
~/.pi/agent/settings.json - 项目:
<cwd>/.pi/settings.json
项目设置覆盖全局设置;嵌套对象按键合并;setter 默认修改全局设置。
持久化与错误处理语义:
- Settings 的 getter/setter 对内存状态是同步的;
- setter 会异步入队持久化写入;
- 需要持久化边界时(例如进程退出前、或测试中断言文件内容前)调用
await settingsManager.flush(); SettingsManager不打印设置 I/O 错误,使用settingsManager.drainErrors()在你的应用层报告。
完整示例见 examples/sdk/10-settings.ts。
ResourceLoader
用 DefaultResourceLoader 发现扩展、skills、prompts、主题和上下文文件:
import {
DefaultResourceLoader,
getAgentDir,
} from "@earendil-works/pi-coding-agent";
const loader = new DefaultResourceLoader({
cwd,
agentDir: getAgentDir(),
});
await loader.reload();
const extensions = loader.getExtensions();
const skills = loader.getSkills();
const prompts = loader.getPrompts();
const themes = loader.getThemes();
const contextFiles = loader.getAgentsFiles().agentsFiles;
reload() 必须在传给 createAgentSession() 之前显式调用(当你在 createAgentSession 中省略 resourceLoader 时,工厂内部会自动创建并 reload 一个 DefaultResourceLoader,见 sdk.ts)。
返回值结构
createAgentSession() 返回:
interface CreateAgentSessionResult {
// 会话
session: AgentSession;
// 扩展加载结果(供 runner 初始化用)
extensionsResult: LoadExtensionsResult;
// 会话模型无法恢复时的警告
modelFallbackMessage?: string;
}
interface LoadExtensionsResult {
extensions: Extension[];
errors: Array<{ path: string; error: string }>;
runtime: ExtensionRuntime;
}
extensionsResult.errors 让你可以在 UI 层展示加载失败的扩展;modelFallbackMessage 在继续会话但模型恢复失败时给出说明。
完整示例
下面这个示例整合了自定义认证位置、运行时 API key 覆盖、内联工具、内存设置、资源加载器覆盖和事件流:
import { getModel } from "@earendil-works/pi-ai";
import { Type } from "typebox";
import {
createAgentSession,
DefaultResourceLoader,
defineTool,
ModelRuntime,
SessionManager,
SettingsManager,
} from "@earendil-works/pi-coding-agent";
const modelRuntime = await ModelRuntime.create({
authPath: "/custom/agent/auth.json",
modelsPath: "/custom/agent/models.json",
});
if (process.env.MY_KEY) {
await modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_KEY);
}
// 内联工具
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");
if (!model) throw new Error("Model not found");
// 带覆盖的内存设置
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",
modelRuntime,
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.");
Run Modes:在 SDK 之上构建界面
SDK 导出了一组 run mode 工具函数,用于在 createAgentSession() 之上构建自定义界面。
InteractiveMode
完整的 TUI 交互模式,带编辑器、聊天历史和所有内置命令:
import {
type CreateAgentSessionRuntimeFactory,
createAgentSessionFromServices,
createAgentSessionRuntime,
createAgentSessionServices,
getAgentDir,
InteractiveMode,
SessionManager,
} from "@earendil-works/pi-coding-agent";
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()),
});
const mode = new InteractiveMode(runtime, {
migratedProviders: [],
modelFallbackMessage: undefined,
initialMessage: "Hello",
initialImages: [],
initialMessages: [],
});
await mode.run();
runPrintMode
单次模式:发送 prompt、输出结果、退出:
await runPrintMode(runtime, {
mode: "text",
initialMessage: "Hello",
initialImages: [],
messages: ["Follow up"],
});
runRpcMode
面向子进程集成的 JSON-RPC 模式:
await runRpcMode(runtime);
JSON 协议详见 RPC 文档。
SDK 与 RPC 模式的选择
如果不打算基于 SDK 构建,也可以直接以子进程方式使用 CLI 的 RPC 模式:
pi --mode rpc --no-session
倾向选择 SDK 的场景: 需要类型安全;在同一个 Node.js 进程内运行;需要直接访问 agent 状态;需要以编程方式定制工具/扩展。
倾向选择 RPC 模式的场景: 从其他语言集成;需要进程隔离;构建语言无关的客户端。
主入口导出清单
主入口点(实现见 src/index.ts)导出:
// 工厂
createAgentSession
createAgentSessionRuntime
AgentSessionRuntime
// 认证与模型
ModelRuntime // 实现 pi-ai 的 Models 接口并拥有凭据存储
ModelRegistry // 同步的扩展兼容 facade
CredentialSynchronizationError
resolveCliModel
resolveModelScopeWithDiagnostics
// 资源加载
DefaultResourceLoader
type ResourceLoader
createEventBus
// 常量与工具函数
CONFIG_DIR_NAME
defineTool
getAgentDir
getPackageDir
getReadmePath
getDocsPath
getExamplesPath
// 会话管理
SessionManager
SettingsManager
// 工具工厂
createCodingTools
createReadOnlyTools
createReadTool, createBashTool, createPowerShellTool, createEditTool, createWriteTool
createGrepTool, createFindTool, createLsTool
// 类型
type CreateAgentSessionOptions
type CreateAgentSessionResult
type ExtensionFactory
type InlineExtension
type ExtensionAPI
type ToolDefinition
type Skill
type PromptTemplate
type Tool
扩展相关类型的完整 API 见 extensions.md。
小结
pi coding-agent SDK 的分层设计可以概括为:ModelRuntime 管模型与认证,ResourceLoader 管扩展/skills/prompts/主题/上下文发现,SessionManager 管会话持久化与树结构,AgentSession 管单会话内的 prompt、事件流、工具与压缩,AgentSessionRuntime 管跨会话替换。从 examples/sdk/ 的 01-minimal.ts 起步,按需引入 customTools、extensionFactories、SettingsManager 覆盖,最终用 runtime 层实现 /new、/fork、/resume 式的应用内会话切换,就能得到一个既有完整类型安全又具备与 CLI 同等能力的可编程 agent 集成。
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 StartedRust0624
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