Chapter 14 · SDK Programmatic Embedding
Chapter goals: Use the
@earendil-works/pi-coding-agentSDK to create agent sessions in your own Node application; handle event streams; register custom tools; and完成一个可运行的嵌入示例.
14.1 The SDK's Positioning and Capability边界
The SDK shares the same核心 with the pi CLI: you directly拿到 the AgentSession in your own Node.js process;获得类型安全; in-process直连; and完全访问 to the agent state. Suitable for building custom UIs; automation pipelines; sub-agents etc.
The边界同样清晰:
- When needing cross-language (Python/Go clients) or process isolation; choose RPC mode (next chapter);
- Session replacement operations (new/resume/fork) are not on
AgentSession; but onAgentSessionRuntime; - The SDK is included in the主包;无需单独安装:
npm install @earendil-works/pi-coding-agent.
14.2 Ten-Line Minimal Runnable Example
import {
createAgentSession,
ModelRuntime,
SessionManager,
} from "@earendil-works/pi-coding-agent";
// ModelRuntime handles the model目录 and credentials resolution
const modelRuntime = await ModelRuntime.create();
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(), // Not persisted; use SessionManager.create(cwd) for持久化
modelRuntime,
});
// Subscribe to the event stream: text_delta is the逐 token streaming output
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?");Credential resolution优先级由 ModelRuntime处理: runtime overrides (setRuntimeApiKey; not persisted) → auth.json stored credentials → env vars (ANTHROPIC_API_KEY etc.) → custom provider fallback. For testing use ModelRuntime.create({ credentials: new InMemoryCredentialStore() }) to完全隔离.
14.3 Sending Messages and the Event Stream
// Regular prompt (wait for the完整运行 to end; including auto-retries)
await session.prompt("Summarize this repo's tech stack");
// Prompt with an image
await session.prompt("What's wrong with this screenshot?", {
images: [{ type: "image"; source: { type: "base64"; mediaType: "image/png"; data: b64 } }],
});
// Must declare排队语义 when streaming; otherwise an error is thrown:
await session.prompt("Switch to error-handling-first"; { streamingBehavior: "steer" }); // Insert after this轮's tool calls结束
await session.followUp("Also update the README when done"); // Delivered only after the agent stopsEvent type quick-reference (event.type in the session.subscribe callback): streaming message_update (内含 text_delta/thinking_delta/toolcall_*); tool three-segment tool_execution_start/update/end; turn turn_start/turn_end; lifecycle agent_start/agent_end/agent_settled; plus queue_update; compaction_*; auto_retry_* etc.
For精细控制工具集 and resources:
import { Type } from "typebox";
import { createAgentSession; defineTool; DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
const statusTool = defineTool({
name: "status",
description: "Return the service status",
parameters: Type.Object({}),
execute: async () => ({
content: [{ type: "text"; text: `uptime: ${process.uptime()}s` }],
details: {},
}),
});
const loader = new DefaultResourceLoader({
systemPromptOverride: () => "You are a极简运维助手.",
});
await loader.reload(); // Don't forget to reload to trigger resource discovery
const { session } = await createAgentSession({
tools: ["read"; "bash"; "status"]; // Built-in tool whitelist: read/bash/edit/write/grep/find/ls
customTools: [statusTool]; // Merged with扩展-registered tools
resourceLoader: loader,
});DefaultResourceLoader also supports injecting inline extensions (extensionFactories;可用 InlineExtension命名显示); skill overrides (skillsOverride); context file overrides (agentsFilesOverride) etc.; almost全部 programmatic-izing what can be配 in the TUI.
14.4 Embedding pi into a Node Service
A common production形态: an HTTP service receives a request → creates a session to跑 the task → streams回传:
import express from "express";
import { createAgentSession; ModelRuntime; SessionManager } from "@earendil-works/pi-coding-agent";
const app = express();
app.use(express.json());
const modelRuntime = await ModelRuntime.create();
app.post("/agent/tasks"; async (req; res) => {
res.setHeader("Content-Type"; "text/plain; charset=utf-8");
const { session } = await createAgentSession({
cwd: req.body.cwd ?? process.cwd();
sessionManager: SessionManager.inMemory(req.body.cwd ?? process.cwd());
modelRuntime,
});
const unsubscribe = session.subscribe((event) => {
if (event.type === "message_update" &&
event.assistantMessageEvent.type === "text_delta") {
res.write(event.assistantMessageEvent.delta); // Stream-write back to the client
}
});
try {
await session.prompt(req.body.task ?? "Check project health");
} finally {
unsubscribe();
session.dispose(); // Release session resources
res.end();
}
});
app.listen(8080; () => console.log("agent service on :8080"));工程化要点:
- Session persistence:
SessionManager.continueRecent(cwd)resume the最近 session;SessionManager.open(path)open a specified file;SessionManager.list/listAll(cwd)enumerate; - Setting injection:
SettingsManager.inMemory({ compaction: { enabled: false } })免文件 I/O; in file mode rememberawait settingsManager.flush()before退出 to保证持久化边界; - Test-friendly: memory-version SessionManager + SettingsManager + InMemoryCredentialStore组合可以完全离线跑单测;
- After each session replacement (the runtime API's new/fork/switch)要重新 subscribe and rebind extensions.
Chapter Summary
- The SDK = in-process; type-safe pi核心;换 RPC mode for跨-language or needing isolation;
- Three-piece startup:
ModelRuntime.create()+createAgentSession()+session.subscribe(); prompt()supports images and streamingBehavior; queue in-stream viasteer()/followUp();- Tool system programmable: built-in whitelists +
defineTool+各类 override fromDefaultResourceLoader; - Service embedding注意 dispose; flush; and re-subscribe三个生命周期细节.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. For a Node.js/TypeScript application wanting to embed pi capabilities; which approach does the官方更推荐?
2. What happens when calling session.prompt() without streamingBehavior while the agent is already streaming?
3. Regarding传入 custom tools; which is correct?
4. Why should the server code call session.dispose() after the prompt ends?
🛠️ Hands-on Practice
- Run through the 14.2 minimal example; then换
SessionManager.inMemory()tocreate(cwd); find the generated.jsonlsession file and重新加载续聊 viaSessionManager.open(). - Add a
POST /agent/abort接口 to the 14.4 HTTP service:持有当前请求的 session引用; executingawait session.abort()when the call is received. - Use
SettingsManager.inMemoryto disable auto-compaction; enable retries (maxRetries: 5);对比 default config下的长对话行为差异并记录观察结果.
After completing; continue to Chapter 15: RPC Mode and Process Integration.