Skip to content

第 14 章 · SDK 编程式嵌入

本章目标:用 @earendil-works/pi-coding-agent 的 SDK 在自己的 Node 应用里创建智能体会话、处理事件流、注册自定义工具,完成一个可运行的嵌入示例。

14.1 SDK 的定位与能力边界

SDK 与 pi CLI 共享同一个核心:你在自己的 Node.js 进程里直接拿到 AgentSession,获得类型安全、进程内直连、对 agent 状态的完全访问。适合构建自定义 UI、自动化流水线、子代理等场景。

边界同样清晰:

  • 需要跨语言(Python/Go 客户端)或进程隔离时,选 RPC 模式(下一章);
  • 会话替换类操作(new/resume/fork)不在 AgentSession 上,而在 AgentSessionRuntime 上;
  • SDK 包含在主包里,无需单独安装:npm install @earendil-works/pi-coding-agent

14.2 十行最小可用示例

typescript
import {
  createAgentSession,
  ModelRuntime,
  SessionManager,
} from "@earendil-works/pi-coding-agent";

// ModelRuntime 负责模型目录与凭据解析
const modelRuntime = await ModelRuntime.create();

const { session } = await createAgentSession({
  sessionManager: SessionManager.inMemory(),   // 不落盘;要持久化用 SessionManager.create(cwd)
  modelRuntime,
});

// 订阅事件流:text_delta 就是逐 token 的流式输出
session.subscribe((event) => {
  if (event.type === "message_update" &&
      event.assistantMessageEvent.type === "text_delta") {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});

await session.prompt("当前目录下有哪些文件?");

凭据解析优先级由 ModelRuntime 处理:运行时覆盖(setRuntimeApiKey,不落盘)→ auth.json 存储凭据 → 环境变量(ANTHROPIC_API_KEY 等)→ 自定义 provider 回退。测试时可用 ModelRuntime.create({ credentials: new InMemoryCredentialStore() }) 完全隔离。

14.3 发消息与事件流

typescript
// 常规提问(等待完整运行结束,包括自动重试)
await session.prompt("总结这个仓库的技术栈");

// 带图片提问
await session.prompt("这张截图里有什么问题?", {
  images: [{ type: "image", source: { type: "base64", mediaType: "image/png", data: b64 } }],
});

// 流式进行中必须声明排队语义,否则抛错:
await session.prompt("改用错误处理优先", { streamingBehavior: "steer" });   // 当前轮工具执行完后插入
await session.followUp("完成后顺便更新 README");                            // agent 停止后才投递

事件类型速查(session.subscribe 回调里的 event.type):流式 message_update(内含 text_delta/thinking_delta/toolcall_*)、工具三段 tool_execution_start/update/end、回合 turn_start/turn_end、生命周期 agent_start/agent_end/agent_settled,以及 queue_updatecompaction_*auto_retry_* 等。

需要精细控制工具集和资源时:

typescript
import { Type } from "typebox";
import { createAgentSession, defineTool, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";

const statusTool = defineTool({
  name: "status",
  description: "返回服务状态",
  parameters: Type.Object({}),
  execute: async () => ({
    content: [{ type: "text", text: `uptime: ${process.uptime()}s` }],
    details: {},
  }),
});

const loader = new DefaultResourceLoader({
  systemPromptOverride: () => "你是一个极简运维助手。",
});
await loader.reload();   // 别忘了 reload 触发资源发现

const { session } = await createAgentSession({
  tools: ["read", "bash", "status"],  // 内置工具白名单:read/bash/edit/write/grep/find/ls
  customTools: [statusTool],          // 与扩展注册的工具合并生效
  resourceLoader: loader,
});

DefaultResourceLoader 还支持注入内联扩展(extensionFactories,可用 InlineExtension 命名显示)、技能覆盖(skillsOverride)、上下文文件覆盖(agentsFilesOverride)等,几乎把 TUI 里能配的东西全部编程化。

14.4 把 pi 嵌进一个 Node 服务

一个常见的生产形态:HTTP 服务收到请求 → 创建会话跑任务 → 流式回传:

typescript
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);      // 流式写回客户端
    }
  });

  try {
    await session.prompt(req.body.task ?? "检查项目健康度");
  } finally {
    unsubscribe();
    session.dispose();                                   // 释放会话资源
    res.end();
  }
});

app.listen(8080, () => console.log("agent service on :8080"));

工程化要点:

  • 会话持久化SessionManager.continueRecent(cwd) 续接最近会话、SessionManager.open(path) 打开指定文件、SessionManager.list/listAll(cwd) 枚举;
  • 设置注入SettingsManager.inMemory({ compaction: { enabled: false } }) 免文件 I/O;文件模式记得在退出前 await settingsManager.flush() 保证持久化边界;
  • 测试友好:内存版 SessionManager + SettingsManager + InMemoryCredentialStore 组合可以完全离线跑单测;
  • 每次替换会话(runtime API 的 new/fork/switch)后要重新 subscribe 并重新绑定扩展。

本章小结

  • SDK = 进程内、类型安全的 pi 核心;跨语言/需隔离时换 RPC 模式;
  • 三件套启动:ModelRuntime.create() + createAgentSession() + session.subscribe()
  • prompt() 支持 images 与 streamingBehavior;流式中排队用 steer()/followUp()
  • 工具体系可编程:内置白名单 + defineTool + DefaultResourceLoader 各类 override;
  • 服务化嵌入注意 dispose、flush、重新订阅三个生命周期细节。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Node.js/TypeScript 应用想嵌入 pi 能力,官方更推荐哪种方式?

2. agent 正在流式输出时调用 session.prompt() 不带 streamingBehavior,会发生什么?

3. 关于自定义工具的传入方式,正确的是?

4. 为什么服务端代码要在 prompt 结束后调用 session.dispose()?

🛠️ 动手实践

  1. 跑通 14.2 的最小示例,然后把 SessionManager.inMemory() 换成 create(cwd),找到生成的 .jsonl 会话文件并用 SessionManager.open() 重新加载续聊。
  2. 给 14.4 的 HTTP 服务加一个 POST /agent/abort 接口:持有当前请求的 session 引用,收到调用后执行 await session.abort()
  3. SettingsManager.inMemory 关闭自动压缩、开启重试(maxRetries: 5),对比默认配置下长对话的行为差异并记录观察结果。

完成后继续第 15 章:RPC 模式与进程集成