第9章 编程接口与 RPC 协议

0 阅读3分钟

第9章 编程接口与 RPC 协议

9.1 CLI 参数完整分类速查

分类参数说明
运行模式-p "..."非交互打印模式
--mode jsonJSONL 事件流
--mode rpcRPC 服务模式
--offline关闭所有启动期联网操作
模型与思考--provider / --model指定 Provider / 模型
--model sonnet:high模型+思考等级快捷写法
--thinking <level>指定本次思考等级
工具与扩展--tools a,b,c工具白名单
--no-extensions关闭所有扩展
-e ./path.ts加载指定的单个 Extension
会话-c继续最近会话
--session-dir <path>自定义会话目录
--no-session不落盘
包管理pi install npm:<pkg> / git:<repo>安装社区包
pi list / pi update查看/升级
杂项-v / -h版本 / 帮助

9.2 三层编程接口对比:createAgentSession / createAgentSessionRuntime / RPC 子进程

Pi 真实的 SDK 文档明确指出:CLI 内置的交互模式、打印模式、RPC 模式,本质上用的是同一套底层 Runtime 层,可以选择在哪个层级接入:

层级关键 API适合场景
最简单createAgentSession() + ModelRuntime/SessionManager快速在 Node.js 里跑一个一次性 Session
进阶createAgentSessionRuntime() + createAgentSessionServices()需要自己控制多会话生命周期、接自定义传输层(如 HTTP+SSE 转发给浏览器)
跨语言pi --mode rpc非 Node.js 技术栈(Python、Go、Java 等)集成
import {
  type CreateAgentSessionRuntimeFactory,
  createAgentSessionFromServices,
  createAgentSessionRuntime,
  createAgentSessionServices,
  getAgentDir,
  runRpcMode,
  SessionManager,
} from '@earendil-works/pi-coding-agent';

const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
  const services = await createAgentSessionServices({ cwd });
  return {
    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
    services,
    diagnostics: services.diagnostics,
  };
};

const runtime = await createAgentSessionRuntime(createRuntime, {
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  sessionManager: SessionManager.create(process.cwd()),
});

// 甚至可以把这个 Runtime 直接跑成 RPC 服务,而不需要另开子进程
await runRpcMode(runtime);

💡 官方特别提示:Node.js 应用优先直接用 AgentSession/createAgentSessionRuntime(),不要为了用 RPC 而把 pi 当子进程 spawn 出来——只有非 Node.js 技术栈才需要走"子进程 + RPC"这条路。

9.3 RPC 协议细节:LF 分隔 JSONL、extension_ui_request/response 子协议

RPC 模式是严格的 LF(\n)分隔 JSONL 协议——每一行是一个独立 JSON 对象。

⚠️ 关键工程提醒:不要用 Node.js 的 readline 模块解析 RPC 输出——它会在 JSON 字符串内部合法的 Unicode 行分隔符 U+2028/U+2029 处误断行,把一个完整的 JSON 对象切成两半。正确做法是自己写只按 \n 分割的解析逻辑。

命令支持可选的 id 字段用于请求/响应关联:

{"id": "req-1", "type": "prompt", "message": "Hello, world!"}

Extension 触发的 UI 交互在 RPC 协议里转成一套子协议:

方法类型行为
对话框类(select/confirm/input/editor)发出 extension_ui_request,阻塞等待客户端在 stdin 回传匹配 id 的 extension_ui_response
即发即弃类(notify/setStatus/setWidget)发出 extension_ui_request,但不等待响应

Extension 内部报错作为独立事件类型上抛:

{ "type": "extension_error", "extensionPath": "/path/to/extension.ts", "event": "tool_call", "error": "..." }

9.4 流式输出:Agent.subscribe() / --mode json

编程接口场景:

agent.subscribe((event) => {
  if (event.type === 'message_update' && event.assistantMessageEvent.type === 'text_delta') {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});
await agent.prompt('...');

CLI 场景的等价方式:

pi --mode json -p "..." > events.jsonl

每一行是独立事件对象(文本增量、工具调用、工具结果),可以直接消费。

📌 校订:Pi 没有一个单独叫 generate() 的方法,真实调用是 prompt(),流式输出靠事件订阅而不是一个"流式开关配置项"。

9.5 自定义消息类型:声明合并 + convertToLlm

pi-agent-core 的 AgentMessage 类型支持通过 TypeScript 声明合并扩展自定义消息角色:

declare module '@earendil-works/pi-agent-core' {
  interface CustomAgentMessages {
    notification: { role: 'notification'; text: string; timestamp: number };
  }
}

再用 convertToLlm 在真正发给模型前过滤/转换这些自定义消息:

const agent = new Agent({
  convertToLlm: (messages) => messages.flatMap((m) => (m.role === 'notification' ? [] : [m])),
});

📌 校订:网传版本"from 参数三种用法"是虚构描述,真实的动态数据注入方式是这套"自定义消息类型 + convertToLlm 转换"机制,或者简单地把数据拼进 prompt 字符串(作为 stdin 管道输入也可以:cat file.md | pi -p "总结这段内容")。