Skip to content

print 与 rpc 模式

源码版本v0.73.1

pi 的非交互模式有两种:print(单次,文本或 JSON 输出)和 rpc(长连接,JSON-RPC over stdin/stdout)。print 模式用于脚本化调用 pi -p "fix this bug",rpc 模式用于把 pi 嵌到其他 GUI 应用(VS Code 扩展、web 前端)里。两者共用 AgentSessionRuntime,但订阅事件、输出格式、扩展 UI 集成的方式完全不同。

职责

  1. print 模式:runPrintModemode: "text" | "json",text 模式只输出最终 assistant 消息文本,json 模式流式输出所有 AgentSessionEvent。见 packages/coding-agent/src/modes/print-mode.ts:32-66
  2. rpc 模式:runRpcModeattachJsonlLineReader 从 stdin 读 JSON 命令,通过 handleCommand 分派 29 种命令类型,事件和响应写到 stdout。见 packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625
  3. signal 处理:两种模式都注册 SIGTERM / SIGHUP handler,先 killTrackedDetachedChildrendisposeRuntime,保证 bash 子进程不残留。见 packages/coding-agent/src/modes/print-mode.ts:47-63packages/coding-agent/src/modes/rpc/rpc-mode.ts:351-365
  4. 扩展 UI 桥接:rpc 模式实现 createExtensionUIContext,把扩展的 dialog / widget 请求通过 extension_ui_request 事件发到客户端,客户端响应 extension_ui_response 回来。见 packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130
  5. session 替换钩子:两种模式都通过 runtimeHost.setRebindSession 注册 rebind 回调,session 切换后重新 bindExtensions、重订阅事件。见 packages/coding-agent/src/modes/print-mode.ts:67-108packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349

设计动机

为什么 print 和 rpc 不共用代码?因为输出契约不同:print 模式 stdout 是单向的(要么文本要么 JSON 行),rpc 模式 stdin/stdout 是双向的(命令进、事件+响应出)。rpc 还要处理扩展 UI 请求的 pending Promise map(等客户端响应),这套机制 print 模式完全不需要。共用会引入不必要的复杂度,所以两个文件各自实现自己的 rebindSessionhandleEvent

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 这种用法。

关键文件

print 模式 text 路径,只输出最后一条 assistant 消息的 text content:

typescript
// 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:

typescript
// 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));
			}
		});

数据流

两种模式的输入输出对照:

边界与失败

小结

print 和 rpc 是 pi 的非交互模式,共用 AgentSessionRuntime 但输出契约不同。print 单向输出文本或 JSON 事件流,rpc 是双向 JSON-RPC 协议,把 AgentSession 所有方法暴露给外部客户端,并桥接扩展 UI 请求。装配入口看 CLI 入口与分发,runtime 替换看 会话 switch/fork/import