# 第 6 章:编程方式——从 CLI 到 SDK
> 能被 import 的工具才叫平台,命令行只是它的第一张脸。
## 设计思想
为了让 Pi 能够被其他程序嵌入、作为服务使用或在非交互环境中驱动,Pi 提供了两种标准的编程接口:**TypeScript SDK** 和 **RPC 模式**。这使得 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": "..."}` —— 注意字段是 `message`;`id` 可选,用于请求/响应关联。
- 支持带图片:`"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)
```ts
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 示例
```bash
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.md` 与 `docs/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` 避免会话落盘。