print 与 rpc 模式
pi 的非交互模式有两种:print(单次,文本或 JSON 输出)和 rpc(长连接,JSON-RPC over stdin/stdout)。print 模式用于脚本化调用 pi -p "fix this bug",rpc 模式用于把 pi 嵌到其他 GUI 应用(VS Code 扩展、web 前端)里。两者共用 AgentSessionRuntime,但订阅事件、输出格式、扩展 UI 集成的方式完全不同。
职责
- print 模式:
runPrintMode接mode: "text" | "json",text 模式只输出最终 assistant 消息文本,json 模式流式输出所有AgentSessionEvent。见packages/coding-agent/src/modes/print-mode.ts:32-66。 - rpc 模式:
runRpcMode用attachJsonlLineReader从 stdin 读 JSON 命令,通过handleCommand分派 29 种命令类型,事件和响应写到 stdout。见packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70、packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625。 - signal 处理:两种模式都注册 SIGTERM / SIGHUP handler,先
killTrackedDetachedChildren再disposeRuntime,保证 bash 子进程不残留。见packages/coding-agent/src/modes/print-mode.ts:47-63、packages/coding-agent/src/modes/rpc/rpc-mode.ts:351-365。 - 扩展 UI 桥接:rpc 模式实现
createExtensionUIContext,把扩展的 dialog / widget 请求通过extension_ui_request事件发到客户端,客户端响应extension_ui_response回来。见packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130。 - session 替换钩子:两种模式都通过
runtimeHost.setRebindSession注册 rebind 回调,session 切换后重新bindExtensions、重订阅事件。见packages/coding-agent/src/modes/print-mode.ts:67-108、packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349。
设计动机
为什么 print 和 rpc 不共用代码?因为输出契约不同:print 模式 stdout 是单向的(要么文本要么 JSON 行),rpc 模式 stdin/stdout 是双向的(命令进、事件+响应出)。rpc 还要处理扩展 UI 请求的 pending Promise map(等客户端响应),这套机制 print 模式完全不需要。共用会引入不必要的复杂度,所以两个文件各自实现自己的 rebindSession 和 handleEvent。
rpc 模式的 29 个命令类型(prompt、steer、follow_up、abort、new_session、get_state、set_model、cycle_model、bash、fork、clone、export_html 等)是 AgentSession 公共 API 的完整映射。客户端可以通过 RPC 调用 AgentSession 暴露的所有方法,这让 pi 可以被嵌入到任何语言写的 GUI 里,只要能讲 JSON-RPC。
print 模式的 mode: "text" 路径只看最后一条 assistant 消息:成功就写 content.text 到 stdout,失败(stopReason === "error" || "aborted")就写 stderr 并返回 exitCode 1。简单粗暴,适合 shell 脚本 pi -p "..." | jq 这种用法。
关键文件
packages/coding-agent/src/modes/print-mode.ts:14-26—PrintModeOptions:mode、messages、initialMessage、initialImages。packages/coding-agent/src/modes/print-mode.ts:32-70—runPrintMode函数签名、disposeRuntime、registerSignalHandlers、rebindSession。packages/coding-agent/src/modes/print-mode.ts:110-145— 主流程:发 initialMessage、循环发 messages、text 模式输出最后一条 assistant 消息。packages/coding-agent/src/modes/rpc/rpc-mode.ts:1-13— 文件头注释,描述 rpc 协议(命令、响应、事件、扩展 UI)。packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70—runRpcMode函数签名、output/success/errorhelper。packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130—createDialogPromise,扩展 UI 请求的 Promise + timeout + abort 管理。packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349—rebindSession:重绑扩展、session.subscribe把事件 output 出去。packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625—handleCommandswitch,29 个命令分支。packages/coding-agent/src/modes/rpc/rpc-types.ts— RPC 协议类型定义(RpcCommand、RpcResponse、RpcExtensionUIRequest等)。
print 模式 text 路径,只输出最后一条 assistant 消息的 text content:
// packages/coding-agent/src/modes/print-mode.ts:128-145
if (mode === "text") {
const state = session.state;
const lastMessage = state.messages[state.messages.length - 1];
if (lastMessage?.role === "assistant") {
const assistantMsg = lastMessage as AssistantMessage;
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
exitCode = 1;
} else {
for (const content of assistantMsg.content) {
if (content.type === "text") {
writeRawStdout(`${content.text}\n`);
}
}
}
}
}rpc 模式 prompt 命令,用 preflightResult 在 prompt 接受后立刻回 response:
// packages/coding-agent/src/modes/rpc/rpc-mode.ts:379-401
case "prompt": {
let preflightSucceeded = false;
void session
.prompt(command.message, {
images: command.images,
streamingBehavior: command.streamingBehavior,
source: "rpc",
preflightResult: (didSucceed) => {
if (didSucceed) {
preflightSucceeded = true;
output(success(id, "prompt"));
}
},
})
.catch((e) => {
if (!preflightSucceeded) {
output(error(id, "prompt", e.message));
}
});数据流
两种模式的输入输出对照:
边界与失败
- rpc 不支持 @file 参数:
main.ts在分派前检查parsed.mode === "rpc" && parsed.fileArgs.length > 0直接报错退出,见packages/coding-agent/src/main.ts:475-478。 - rpc 不支持主题切换:
setTheme返回{ success: false, error: "Theme switching not supported in RPC mode" },见packages/coding-agent/src/modes/rpc/rpc-mode.ts:291-294。 - rpc 不支持工具展开:
getToolsExpanded永远返回 false,因为 TUI 概念在 rpc 不存在,见packages/coding-agent/src/modes/rpc/rpc-mode.ts:296-303。 - 扩展 UI timeout / abort:
createDialogPromise支持opts.timeout和opts.signal,超时或 abort 时 resolve 默认值,不 reject,让扩展逻辑能继续,见packages/coding-agent/src/modes/rpc/rpc-mode.ts:90-130。 - print 模式 stdout 接管:
main.ts在非 interactive 模式调takeOverStdout(),所有console.log重定向到内部缓冲,最后flushRawStdout写出,避免 TUI 转义污染,见packages/coding-agent/src/modes/print-mode.ts:155-157。 - shutdown 信号:
rpc-mode的shutdownRequested标志由扩展shutdownHandler触发,主循环检测到后清理退出,见packages/coding-agent/src/modes/rpc/rpc-mode.ts:79-81、packages/coding-agent/src/modes/rpc/rpc-mode.ts:337-339。
小结
print 和 rpc 是 pi 的非交互模式,共用 AgentSessionRuntime 但输出契约不同。print 单向输出文本或 JSON 事件流,rpc 是双向 JSON-RPC 协议,把 AgentSession 所有方法暴露给外部客户端,并桥接扩展 UI 请求。装配入口看 CLI 入口与分发,runtime 替换看 会话 switch/fork/import。