Skip to content

print and rpc Modes

源码版本v0.73.1

pi has two non-interactive modes: print (one-shot, text or JSON output) and rpc (long-lived connection, JSON-RPC over stdin/stdout). print is for scripted invocations like pi -p "fix this bug"; rpc is for embedding pi into other GUI apps (VS Code extensions, web frontends). Both share AgentSessionRuntime, but their event subscription, output format, and extension UI integration are completely different.

Responsibilities

  1. print mode: runPrintMode takes mode: "text" | "json". text mode outputs only the final assistant message text; json mode streams all AgentSessionEvents. See packages/coding-agent/src/modes/print-mode.ts:32-66.
  2. rpc mode: runRpcMode uses attachJsonlLineReader to read JSON commands from stdin, dispatches 29 command types via handleCommand, and writes events and responses to stdout. See packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70 and packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625.
  3. Signal handling: both modes register SIGTERM / SIGHUP handlers that killTrackedDetachedChildren before disposeRuntime, so bash subprocesses don't leak. See packages/coding-agent/src/modes/print-mode.ts:47-63 and packages/coding-agent/src/modes/rpc/rpc-mode.ts:351-365.
  4. Extension UI bridge: rpc mode implements createExtensionUIContext, forwarding extension dialog/widget requests to the client via extension_ui_request events, with the client responding via extension_ui_response. See packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130.
  5. Session replacement hooks: both modes register a rebind callback via runtimeHost.setRebindSession, which re-runs bindExtensions and resubscribes to events after a session switch. See packages/coding-agent/src/modes/print-mode.ts:67-108 and packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349.

Design Motivation

Why don't print and rpc share code? Their output contracts differ: print's stdout is one-way (either text or JSON lines), while rpc's stdin/stdout is bidirectional (commands in, events and responses out). rpc also has to manage a pending-Promise map for extension UI requests (waiting on client responses), a mechanism print doesn't need at all. Sharing would introduce unnecessary complexity, so each file implements its own rebindSession and handleEvent.

The 29 rpc command types (prompt, steer, follow_up, abort, new_session, get_state, set_model, cycle_model, bash, fork, clone, export_html, etc.) are a complete mapping of AgentSession's public API. A client can invoke any method AgentSession exposes via RPC, so pi can be embedded into a GUI written in any language, as long as it speaks JSON-RPC.

The print mode: "text" path only looks at the last assistant message: on success it writes content.text to stdout; on failure (stopReason === "error" || "aborted") it writes to stderr and returns exitCode 1. Simple and direct, suited to shell usage like pi -p "..." | jq.

Key Files

print mode text path, only outputting the text content of the last assistant message:

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 mode prompt command, using preflightResult to send a response right after the prompt is accepted:

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

Data Flow

Input/output comparison of the two modes:

Edges and Failures

Summary

print and rpc are pi's non-interactive modes. They share AgentSessionRuntime but have different output contracts: print is one-way text or JSON event stream output, while rpc is a bidirectional JSON-RPC protocol that exposes all AgentSession methods to external clients and bridges extension UI requests. For the assembly entry, see CLI Entry and Dispatch; for runtime replacement, see Session switch/fork/import.