Skip to content

Modes print et rpc

源码版本v0.73.1

Les modes non interactifs de pi sont au nombre de deux : print (one-shot, sortie texte ou JSON) et rpc (connexion longue, JSON-RPC over stdin/stdout). Le mode print sert aux appels scriptés pi -p "fix this bug", le mode rpc sert à embarquer pi dans d'autres applications GUI (extension VS Code, frontend web). Les deux partagent AgentSessionRuntime, mais le mode de souscription aux événements, le format de sortie et l'intégration UI des extensions diffèrent totalement.

Responsabilités

  1. Mode print : runPrintMode reçoit mode: "text" | "json" ; le mode text ne sort que le texte du dernier message assistant, le mode json stream tous les AgentSessionEvent. Voir packages/coding-agent/src/modes/print-mode.ts:32-66.
  2. Mode rpc : runRpcMode utilise attachJsonlLineReader pour lire les commandes JSON depuis stdin, les dispatche via handleCommand sur 29 types de commande, et écrit événements et réponses sur stdout. Voir packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70 et packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625.
  3. Traitement des signaux : les deux modes enregistrent un handler SIGTERM / SIGHUP, qui fait d'abord killTrackedDetachedChildren puis disposeRuntime, pour éviter que des processus fils bash ne traînent. Voir packages/coding-agent/src/modes/print-mode.ts:47-63 et packages/coding-agent/src/modes/rpc/rpc-mode.ts:351-365.
  4. Pont UI pour extensions : le mode rpc implémente createExtensionUIContext, qui envoie les demandes dialog / widget des extensions via un événement extension_ui_request au client, et la réponse extension_ui_response revient. Voir packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130.
  5. Hook de remplacement de session : les deux modes enregistrent un callback rebind via runtimeHost.setRebindSession ; après un switch de session, ils rappellent bindExtensions et resouscrivent aux événements. Voir packages/coding-agent/src/modes/print-mode.ts:67-108 et packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349.

Motifs de conception

Pourquoi print et rpc ne partagent-ils pas de code ? Parce que le contrat de sortie diffère : en mode print, stdout est unidirectionnel (soit texte, soit lignes JSON) ; en mode rpc, stdin/stdout est bidirectionnel (commandes en entrée, événements + réponses en sortie). Le mode rpc doit aussi gérer une map de Promises pending pour les requêtes UI d'extension (en attente de réponse du client), un mécanisme dont le mode print n'a nul besoin. Partager introduirait une complexité inutile, donc chaque fichier implémente son propre rebindSession et handleEvent.

Les 29 types de commande du mode rpc (prompt, steer, follow_up, abort, new_session, get_state, set_model, cycle_model, bash, fork, clone, export_html…) constituent une projection complète de l'API publique d'AgentSession. Le client peut via RPC appeler toutes les méthodes exposées par AgentSession, ce qui permet d'embarquer pi dans une GUI écrite dans n'importe quel langage, pourvu qu'elle cause JSON-RPC.

Le chemin mode: "text" de print ne regarde que le dernier message assistant : en succès, il écrit content.text sur stdout ; en échec (stopReason === "error" || "aborted"), il écrit sur stderr et renvoie exitCode 1. Simple et direct, adapté à un usage shell pi -p "..." | jq.

Fichiers clés

Chemin text de print, ne sort que le text content du dernier message assistant :

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

Commande prompt du mode rpc, utilise preflightResult pour répondre immédiatement après l'acceptation du prompt :

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

Flux de données

Comparaison des entrées/sorties des deux modes :

Limites et cas d'échec

Synthèse

print et rpc sont les modes non interactifs de pi, ils partagent AgentSessionRuntime mais ont des contrats de sortie différents. print est une sortie unidirectionnelle de texte ou de flux d'événements JSON ; rpc est un protocole bidirectionnel JSON-RPC qui expose toutes les méthodes d'AgentSession à un client externe et fait le pont pour les requêtes UI d'extension. L'entrée d'assemblage est dans entrée CLI et dispatch, le remplacement de runtime dans switch/fork/import de session.