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 ハンドラを登録し、まず killTrackedDetachedChildren してから disposeRuntime で 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 は JSON-RPC を喋れる限り、どんな言語で書かれた GUI にも組み込める。

print モードの mode: "text" パスは最後の assistant メッセージだけを見る。成功なら content.text を stdout に書き、失敗(stopReason === "error" || "aborted")なら stderr に書いて exitCode 1 を返す。シンプルで、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 参照。