青蛙小白

Pi SDK

Pi SDKPi 的第四种入口(官方文档与导航只称之为「SDK」,「Pi SDK」为本库独立成页采用的惯例写法,非官方品牌名):它把终端编码 Agent 的会话管理、工具循环、资源加载、模型认证和设置系统以 TypeScript API 形式暴露在 @earendil-works/pi-coding-agent 主包中,不需要单独安装。CLI、TUI、RPC 与 SDK 共享同一套核心;本页专注 SDK 层的 API 结构与语义,项目整体设计与安全边界见 Pi 词条。

官方列出的典型用途:构建自定义 UI(web / 桌面 / 移动)、把 Agent 能力嵌入既有应用、自动化流水线、构建派生 Subagent(子智能体)的自定义工具,以及程序化测试 Agent 行为。仓库 examples/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?");

这个最小调用已经体现 SDK 的结构:ModelRuntime 负责模型注册与认证,SessionManager 负责会话持久化(这里是纯内存),createAgentSession() 把各部件组装成会话,之后全部交互就是事件订阅与 prompt()

正交部件而非黑盒

SDK 最值得注意的设计是不提供一个黑盒 run(),而是暴露一组可独立替换的部件:

部件职责可替换性
AgentSession单个会话生命周期:prompt、消息历史、模型状态、压缩、事件流createAgentSession() 工厂组装
AgentSessionRuntime替换活动会话(new / switch / fork / import),重建 cwd 绑定状态整个 runtime 由工厂闭包构建
SessionManager会话树持久化(JSONL、id/parentId)、列举、分支inMemory() / create() / continueRecent() / open()
SettingsManager全局+项目设置合并与持久化create() / inMemory(),测试免文件 IO
ModelRuntime模型注册表、凭证存储、认证检查自定义 authPath / modelsPath,或注入 pi-ai CredentialStore
ResourceLoader扩展、技能、提示模板、主题、上下文文件的发现与覆盖DefaultResourceLoader*Override 钩子,或完全自定义实现
flowchart LR
    App["嵌入方应用"] --> CAS["createAgentSession()"]
    MR["ModelRuntime
模型与认证"] --> CAS SM["SessionManager
会话树"] --> CAS ST["SettingsManager
设置"] --> CAS RL["ResourceLoader
扩展/技能/模板"] --> CAS CAS --> S["AgentSession"] S --> Core["pi-agent-core
Agent + 工具循环"]

所有部件都有 in-memory 变体,单元测试可以在无文件系统、无凭证的环境下构造完整 Agent 会话——这是把 Harness Engineering(驾驭工程)的各层显式对象化后的直接收益。

会话生命周期与替换语义

AgentSession 的核心接口(节选):

  • prompt(text, options?) 发送并等待整轮完成;流式期间用 steer(text) / followUp(text) 排队消息
  • subscribe(listener) 返回取消订阅函数
  • setModel() / setThinkingLevel() / cycleModel() 在会话中切换模型与 thinking level
  • navigateTree(targetId, ...) 会话树原地跳转(对应 CLI 的 /tree
  • compact(customInstructions?) / abortCompaction() 手动压缩
  • abort() / dispose() 中止与清理

会话替换 API 不在 AgentSession 上,而在 AgentSessionRuntime 上:newSession()switchSession(path)fork(entryId)、clone(fork(entryId, { position: "at" }))、importFromJsonl()。替换后有两个容易踩的坑:

  1. runtime.session 指向新对象,事件订阅绑定在具体 session 上,必须重新 subscribe
  2. 使用扩展时需对新 session 再次调用 runtime.session.bindExtensions(...)

这把"会话对象"与"运行时"显式分层:AgentSession 是一次性的具体对话,runtime 才是承载 /new/resume/fork 语义的那一层。创建与替换失败由方法抛出、调用方自行处理;创建结果还带 diagnostics

Prompt 队列:steer 与 followUp

PromptOptionsstreamingBehavior: "steer" | "followUp" 控制流式期间的入队行为;不传则 prompt() 在流式期间直接抛错。语义分层:

  • steer:当前 assistant turn 的工具调用完成后立即送达,用于中途改指令
  • followUp:等 Agent 完全停下才送达,用于追加任务
  • Extension 命令(如 /mycommand)总是立即执行、不能排队;文件型 prompt template 发送或入队前展开成内容

preflightResult 回调每次 prompt() 触发一次:true 表示被接受、入队或立即处理;false 表示 preflight 阶段即被拒绝。而 prompt() 本身的 resolve 仍需等整个被接受的 run(含自动重试)结束,接受后的失败走正常事件与消息流。

这组 API 把 CLI 交互层的排队行为变成显式契约:嵌入方必须自己为"用户中途输入"选择 steering 还是 followUp,而不是依赖某个 UI 层的隐式约定。

事件流

SDK 事件与 Agent Loop(智能体循环)的阶段一一对应:

  • 消息:message_start / message_updatetext_deltathinking_delta)/ message_end
  • 工具:tool_execution_start / tool_execution_update(流式工具输出)/ tool_execution_end(带 isError
  • turn:turn_start / turn_end(本轮 assistant message 与 toolResults)
  • Agent:agent_start / agent_endevent.messages 含新增消息)
  • 会话级:queue_updatecompaction_start/endauto_retry_start/endsummarization_retry_*

session.agent.state 暴露持久状态(messagesmodelthinkingLeveltoolsstreamingMessage),并允许直接替换 messages / tools 数组实现分支或恢复;waitForIdle() 等待处理完成。

资源发现与切断

DefaultResourceLoader 的发现路径对嵌入方很关键,因为嵌入场景经常需要切断 默认发现:

  • cwd:项目扩展 .pi/extensions/;技能 .pi/skills/.agents/skills/(沿目录向上至 git 根,非仓库则到文件系统根);.pi/prompts/;向上 walk 的 AGENTS.md;会话目录命名
  • agentDir(默认 ~/.pi/agent):全局扩展与技能(含 ~/.agents/skills/)、prompts、AGENTS.mdsettings.jsonmodels.jsonauth.jsonsessions/

传入自定义 ResourceLoader 后,cwd / agentDir 不再控制资源发现(仍影响会话命名与工具路径解析)。覆盖钩子包括 systemPromptOverrideskillsOverrideagentsFilesOverridepromptsOverrideadditionalExtensionPathsextensionFactories;命名 inline 扩展(InlineExtension 对象)在启动列表显示为 <inline:name> 而不是 <inline:1>,扩展间可通过共享 eventBus 通信。

安全含义同样明确:不切断发现,SDK 会读取宿主机真实的 ~/.pi/agent 凭证并加载项目级扩展,等于继承 CLI 的信任边界与风险面——参照 Agent Sandbox(Agent 沙箱)对 Pi 默认权限模型的说明。

模型与认证

ModelRuntime 拥有凭证存储,认证解析优先级为:运行时覆盖(setRuntimeApiKey,不落盘)→ auth.json(API key 或 OAuth token)→ 环境变量 → models.json 自定义供应商的 fallback resolver。可注入 InMemoryCredentialStore 实现完全进程内认证,getAvailable() 只返回认证有效的模型。

模型解析有两个对齐 CLI 的辅助函数:resolveCliModel() 处理 provider/model:thinking 字符串(首次配置、尚无存储凭证时也能解析);resolveModelScopeWithDiagnostics() 对齐 --models / enabledModels 语义并返回诊断而不是直接打印。scopedModels 配置交互模式 Ctrl+P 的模型循环列表。

工具面

  • 内置工具名:readbasheditwritegrepfindls;默认为前四个
  • tools 白名单、excludeTools 黑名单(白名单应用后再排除)、noTools: "all" | "builtin"(后者保留扩展与自定义工具)
  • edit 工具结果带双格式:details.diff 供 Pi TUI 展示,details.patch 是标准 unified patch 供 SDK 消费者——同一工具结果为不同客户端形态准备了不同投影,是 Agent Tool Design(Agent 工具设计)中结果投影的具体实例
  • 自定义工具用 defineTool() + typebox schema 定义,经 customTools 传入,与扩展注册的工具合并;若同时用 tools 白名单,自定义工具名也必须列入

一个易错细节:传自定义 cwdcreateAgentSession() 会为该 cwd 构建内置工具,SessionManager.inMemory(cwd) 也要带上同一个 cwd,两者必须一致。

运行模式复用与 SDK / RPC 选择

SDK 还导出 CLI 自身的三种运行模式实现:InteractiveMode(完整 TUI)、runPrintMode()(一次性输出)、runRpcMode()(JSON-RPC 子进程协议),自定义界面不必从零拼装。不使用 SDK 时,也可直接 pi --mode rpc --no-session 起子进程。官方给出的选择边界:

选 SDK选 RPC
需要类型安全从其他语言集成
同一 Node.js 进程需要进程隔离
直接访问 Agent 状态构建语言无关客户端
程序化定制工具与扩展

定位:组装件而非托管 runtime

Claude Agent SDK 相比,Pi SDK 暴露的部件更细碎——SessionManager、SettingsManager、ModelRuntime、ResourceLoader 都可独立替换,且没有内建权限审批层;与 OpenAI Agents SDK 相比,它不平台化 handoff、guardrail 与 tracing,而是保持 Pi 一贯的"极简核心 + 自行组合"路线。对嵌入方而言,这既意味着控制面大(内存化测试、自定义系统提示、细粒度事件、直接替换消息树),也意味着生产所需的安全边界、审计与持久化策略都要自己补齐:SDK 给的是部件,不是托管 runtime。

相关概念

参考来源
  1. 1. https://pi.dev/docs/latest/sdk
评论