第 6 章:编程方式——从 CLI 到 SDK

能被 import 的工具才叫平台,命令行只是它的第一张脸。

设计思想

为了让 Pi 能够被其他程序嵌入、作为服务使用或在非交互环境中驱动,Pi 提供了两种标准的编程接口:TypeScript SDKRPC 模式。这使得 Pi 的功能不再局限于终端交互,而是可以被任何语言或框架调用,从而实现自定义工作流、IDE 插件、CI/CD 集成等场景。

原理

四种运行模式

模式 入口 适用
Interactive pi 终端日常使用
Print / JSON pi -p "..."pi --mode json 一次性任务、shell pipeline
RPC pi --mode rpc 非 Node 调用方,stdin/stdout JSONL 协议
SDK import Node.js 进程内嵌入

SDK(TypeScript)

  • 三个核心构件:
    • ModelRuntime:负责与模型提供商通信(认证、模型目录、思考级别),用 ModelRuntime.create() 创建。
    • SessionManager:会话持久化;SessionManager.inMemory() 为内存态(不落盘),文件态用于真实会话。
    • createAgentSession:工厂函数,组装出可直接使用的 session。不传参数时用 DefaultResourceLoader 做标准发现(extensions、skills、prompts、themes、上下文文件)。
  • session.prompt(text) 发送消息;session.subscribe(listener) 订阅事件流(返回取消订阅函数),可拿到 message_update / text_delta 等流式增量。
  • 会话替换类操作在 AgentSessionRuntime 上,不在 AgentSession(对应 TUI 的 /new /resume /fork /clone):
    • runtime.newSession()runtime.switchSession(target)
    • runtime.fork(entryId)(从某条 user message 分叉)
    • runtime.fork(entryId, { position: "at" })(= clone,复制当前活跃分支)
  • 自定义工具不走 ModelRuntime:用 defineTool() 定义,经 customTools: [myTool] 传入;扩展加载的工具仍由 pi.registerTool() 注册。
  • 注意:会话替换后事件订阅会失效,需 unsubscribe 后重新 subscribe。

RPC 模式

  • pi --mode rpc 进入基于 stdin/stdout 的 JSONL 服务。
  • 命令(写入 stdin,每行一个 JSON 对象):
    • {"id": "req-1", "type": "prompt", "message": "..."} —— 注意字段是 messageid 可选,用于请求/响应关联。
    • 支持带图片:"images": [{"type": "image", "data": "<base64>", "mimeType": "image/png"}]
    • 流式中追加消息必须带 streamingBehavior"steer"(当前 turn 工具执行完就送达)或 "followUp"(全部干完才送达),否则命令报错;也有独立的 {"type": "steer", "message": "..."} 命令。
    • /skill:name 和提示模板会在发送前展开;扩展命令流式中也可立即执行。
  • 响应{"id": "req-1", "type": "response", "command": "prompt", "success": true};接受后的失败走正常事件流,不会对同一 id 二次 response。
  • 事件:agent 事件以 JSON 行流式输出到 stdout。
  • 分帧铁律:严格按 \n 切分记录(可剥离行尾 \r)。Node readline 不合规——它还会在 U+2028/U+2029 处切分,而这在 JSON 字符串里是合法字符。
  • 详细协议见 docs/rpc.md

用法

SDK 最小示例(官方 Quick Start)

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?");

RPC one-shot 示例

echo '{"id":"1","type":"prompt","message":"用一句话总结泰勒级数的作用。"}' | pi --mode rpc
# stdout 会依次输出 response 和流式事件(每行一个 JSON)

长连接场景:保持进程 stdin 打开,逐行写入命令、持续读 stdout 事件;用 id 关联请求与响应。

高级用法

  • 自定义工具defineTool() 定义 + customTools: [myTool] 传入 createAgentSession();与扩展注册的工具合并生效。
  • 会话分支await runtime.fork("entry-id"),clone 用 runtime.fork(entryId, { position: "at" })
  • 资源控制:默认 DefaultResourceLoader 可换成自定义 ResourceLoader,精确控制加载哪些 extensions/skills/prompts/themes/上下文文件。
  • 多实例/多租户:每个聊天独立 SessionManager 与 cwd;凭据与模型目录的隔离参见 docs/sdk.mddocs/environment-variables.md(如 PI_CODING_AGENT_DIR)。

在项目中的应用

  • 作为库npm install @earendil-works/pi-coding-agent 后用 SDK 构建内部 AI 助手或代码审查工具(SDK 就在主包里,无需单独安装)。
  • 作为服务:部署 pi --mode rpc 为长运行进程,任意语言客户端通过管道/socket 发 JSONL 指令。
  • 自动化流水线pi -p 适合脚本中的一次性调用,还支持管道合并 stdin(cat README.md | pi -p "Summarize this text")。

注意事项

  • 会话替换(new/fork/resume)后必须重新订阅事件。
  • RPC 客户端切勿用通用行读取器(如 Node readline);只按 \n 切分。
  • RPC/非交互模式同样受项目信任机制约束:项目未受信时不加载 .pi/ 资源(非交互模式不弹询问,按 defaultProjectTrust--approve/--no-approve 处理)。
  • 配置来源:环境变量(ANTHROPIC_API_KEY 等)、--api-key--provider--model--thinking 均可用于两种编程方式。
  • 一次性任务可用 --no-session 避免会话落盘。