Mode RPC
Le mode RPC permet un fonctionnement sans tête de l'agent de codage via un protocole JSON sur stdin/stdout. Ceci est utile pour intégrer l'agent dans d'autres applications, IDE ou interfaces utilisateur personnalisées.
Remarque pour les utilisateurs Node.js/TypeScript: Si vous créez une application Node.js, envisagez d'utiliser AgentSession directement à partir de @earendil-works/pi-coding-agent au lieu de générer un sous-processus. Voir src/core/agent-session.ts pour le API. Pour un client TypeScript basé sur des sous-processus, voir src/modes/rpc/rpc-client.ts.
Démarrage du mode RPC
pi --mode rpc [options]Options courantes:
--provider <name>: Définissez le fournisseur LLM (anthropic, openai, google, etc.)--model <pattern>: modèle ou ID de modèle (prend en chargeprovider/idet:<thinking>en option)--name <name>/-n <name>: définissez le nom d'affichage de la session au démarrage--no-session: Désactiver la persistance de la session--session-dir <path>: répertoire de stockage de session personnalisé
Aperçu du protocole
- Commandes: JSON objets envoyés à stdin, un par ligne
- Réponses: JSON objets avec
type: "response"indiquant le succès/l'échec de la commande - Événements: événements d'agent diffusés vers stdout sous forme de lignes JSON
Toutes les commandes prennent en charge un champ facultatif id pour la corrélation demande/réponse. Si elle est fournie, la réponse correspondante inclura le même id. Les événements bash_execution_update incluent également le id de leur commande bash d'origine.
Encadrement
Le mode RPC utilise une sémantique JSONL stricte avec LF (\n) comme seul délimiteur d'enregistrement.
Ceci est important pour les clients:
- Fractionner les enregistrements sur
\nuniquement - Acceptez l'entrée facultative
\r\nen supprimant un\rfinal - N'utilisez pas de lecteurs de ligne génériques qui traitent les séparateurs Unicode comme des nouvelles lignes
En particulier, le nœud readline n'est pas conforme au protocole pour le mode RPC car il se divise également sur U+2028 et U+2029, qui sont valides à l'intérieur des chaînes JSON.
Commandes
Invite
rapide
Envoyez une invite utilisateur à l'agent. La réponse de la commande est émise une fois que l'invite est acceptée, mise en file d'attente ou gérée. Les événements continuent à être diffusés de manière asynchrone après acceptation.
{"id": "req-1", "type": "prompt", "message": "Hello, world!"}Avec des images:
{"type": "prompt", "message": "What's in this image?", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}Pendant le streaming: si l'agent diffuse déjà le message, vous devez spécifier streamingBehavior pour mettre le message en file d'attente:
{"type": "prompt", "message": "New instruction", "streamingBehavior": "steer"}"steer": mettez le message en file d'attente pendant l'exécution de l'agent. Il est délivré une fois que le tour d'assistant en cours a fini d'exécuter ses appels d'outil, avant le prochain appel LLM."followUp": attendez que l'agent ait terminé. Le message est délivré uniquement lorsque l'agent s'arrête.
Si l'agent diffuse et qu'aucun streamingBehavior n'est spécifié, la commande renvoie une erreur.
Commandes d'extension: si le message est une commande d'extension (par exemple, /mycommand), il s'exécute immédiatement même pendant la diffusion. Les commandes d'extension gèrent leur propre interaction LLM via pi.sendMessage().
Extension d'entrée: les commandes de compétences (/skill:name) et prompt templates (/template) sont développées avant l'envoi/la mise en file d'attente.
Réponse:
{"id": "req-1", "type": "response", "command": "prompt", "success": true}success: true signifie que l'invite a été acceptée, mise en file d'attente ou traitée immédiatement. success: false signifie que l'invite a été rejetée avant son acceptation. Les échecs après l'acceptation sont signalés via le flux normal d'événements et de messages, et non sous la forme d'un deuxième response pour le même identifiant de demande.
Le champ images est facultatif. Chaque image utilise le format ImageContent: {"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}.
diriger
Mettez en file d'attente un message de pilotage pendant que l'agent est en cours d'exécution. Il est délivré une fois que le tour d'assistant en cours a fini d'exécuter ses appels d'outil, avant le prochain appel LLM. Les commandes de compétences et prompt templates sont développées. Les commandes d'extension ne sont pas autorisées (utilisez plutôt prompt).
{"type": "steer", "message": "Stop and do this instead"}Avec des images:
{"type": "steer", "message": "Look at this instead", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}Le champ images est facultatif. Chaque image utilise le format ImageContent (identique à prompt).
Réponse:
{"type": "response", "command": "steer", "success": true}Voir set_steering_mode pour contrôler la manière dont les messages de pilotage sont traités.
suivi
Mettez en file d'attente un message de suivi à traiter une fois l'agent terminé. Distribué uniquement lorsque l'agent n'a plus d'appels d'outil ni de messages de pilotage. Les commandes de compétences et prompt templates sont développées. Les commandes d'extension ne sont pas autorisées (utilisez plutôt prompt).
{"type": "follow_up", "message": "After you're done, also do this"}Avec des images:
{"type": "follow_up", "message": "Also check this image", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}Le champ images est facultatif. Chaque image utilise le format ImageContent (identique à prompt).
Réponse:
{"type": "response", "command": "follow_up", "success": true}Voir set_follow_up_mode pour contrôler la façon dont les messages de suivi sont traités.
avorter
Abandonnez l’opération d’agent en cours.
{"type": "abort"}Réponse:
{"type": "response", "command": "abort", "success": true}nouvelle_session
Démarrez une nouvelle session. Peut être annulé par un gestionnaire d'événements d'extension session_before_switch.
{"type": "new_session"}Avec suivi facultatif de la session parent:
{"type": "new_session", "parentSession": "/path/to/parent-session.jsonl"}Réponse:
{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": false}}Si une prolongation est annulée:
{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": true}}État
get_state
Obtenez l’état actuel de la session.
{"type": "get_state"}Réponse:
{
"type": "response",
"command": "get_state",
"success": true,
"data": {
"model": {...},
"thinkingLevel": "medium",
"isStreaming": false,
"isCompacting": false,
"steeringMode": "all",
"followUpMode": "one-at-a-time",
"sessionFile": "/path/to/session.jsonl",
"sessionId": "abc123",
"sessionName": "my-feature-work",
"autoCompactionEnabled": true,
"messageCount": 5,
"pendingMessageCount": 0
}
}Le champ model est un objet Model complet ou null. Le champ sessionName est le nom d'affichage défini via set_session_name, ou omis s'il n'est pas défini.
get_messages
Recevez tous les messages de la conversation.
{"type": "get_messages"}Réponse:
{
"type": "response",
"command": "get_messages",
"success": true,
"data": {"messages": [...]}
}Les messages sont des objets AgentMessage (voir Message Types).
Modèle
set_model
Passez à un modèle spécifique.
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}La réponse contient l'objet Model complet:
{
"type": "response",
"command": "set_model",
"success": true,
"data": {...}
}cycle_model
Passez au prochain modèle disponible. Renvoie les données null si un seul modèle est disponible.
{"type": "cycle_model"}Réponse:
{
"type": "response",
"command": "cycle_model",
"success": true,
"data": {
"model": {...},
"thinkingLevel": "medium",
"isScoped": false
}
}Le champ model est un objet Model complet.
get_available_models
Répertoriez tous les modèles configurés.
{"type": "get_available_models"}La réponse contient un tableau d'objets Model complets:
{
"type": "response",
"command": "get_available_models",
"success": true,
"data": {
"models": [...]
}
}Pensée
set_thinking_level
Définissez le niveau de raisonnement/réflexion pour les modèles qui le prennent en charge.
{"type": "set_thinking_level", "level": "high"}Niveaux: "off", "minimal", "low", "medium", "high", "xhigh", "max"
"xhigh" et "max" sont exposés uniquement lorsqu'ils sont pris en charge par le modèle sélectionné. Certains modèles, dont GPT-5.6, exposent les deux.
Réponse:
{"type": "response", "command": "set_thinking_level", "success": true}cycle_thinking_level
Parcourez les niveaux de réflexion disponibles. Renvoie les données null si le modèle ne prend pas en charge la réflexion.
{"type": "cycle_thinking_level"}Réponse:
{
"type": "response",
"command": "cycle_thinking_level",
"success": true,
"data": {"level": "high"}
}get_available_thinking_levels
Énumérez les niveaux de réflexion pris en charge par le modèle actuel. Renvoie ["off"] pour un modèle sans support de raisonnement.
{"type": "get_available_thinking_levels"}Réponse:
{
"type": "response",
"command": "get_available_thinking_levels",
"success": true,
"data": {
"levels": ["off", "minimal", "low", "medium", "high"]
}
}Modes de file d'attente
set_steering_mode
Contrôlez la manière dont les messages de pilotage (à partir de steer) sont transmis.
{"type": "set_steering_mode", "mode": "one-at-a-time"}Modes:
"all": délivre tous les messages de direction une fois que le tour d'assistant en cours a fini d'exécuter ses appels d'outil"one-at-a-time": délivre un message de direction par tour d'assistant terminé (par défaut)
Réponse:
{"type": "response", "command": "set_steering_mode", "success": true}set_follow_up_mode
Contrôlez la manière dont les messages de suivi (à partir de follow_up) sont transmis.
{"type": "set_follow_up_mode", "mode": "one-at-a-time"}Modes:
"all": Envoyez tous les messages de suivi lorsque l'agent a terminé"one-at-a-time": envoyer un message de suivi par achèvement d'agent (par défaut)
Réponse:
{"type": "response", "command": "set_follow_up_mode", "success": true}Compactage
compact
Compactez manuellement le contexte de conversation pour réduire l’utilisation des jetons.
{"type": "compact"}Avec instructions personnalisées:
{"type": "compact", "customInstructions": "Focus on code changes"}Réponse:
{
"type": "response",
"command": "compact",
"success": true,
"data": {
"summary": "Summary of conversation...",
"firstKeptEntryId": "abc123",
"tokensBefore": 150000,
"estimatedTokensAfter": 32000,
"usage": {
"input": 32000,
"output": 1200,
"cacheRead": 0,
"cacheWrite": 0,
"totalTokens": 33200,
"cost": {"input": 0.01, "output": 0.02, "cacheRead": 0, "cacheWrite": 0, "total": 0.03}
},
"details": {}
}
}estimatedTokensAfter est une estimation heuristique du contexte de message reconstruit immédiatement après le compactage, et non un nombre de jetons exact du fournisseur. usage signale le ou les appels LLM qui ont généré le résumé et peut être omis par les gestionnaires de compactage personnalisés.
set_auto_compaction
Activez ou désactivez le compactage automatique lorsque le contexte est presque plein.
{"type": "set_auto_compaction", "enabled": true}Réponse:
{"type": "response", "command": "set_auto_compaction", "success": true}Réessayer
set_auto_retry
Activez ou désactivez les nouvelles tentatives automatiques en cas d'erreurs transitoires (surcharge, limite de débit, 5xx).
{"type": "set_auto_retry", "enabled": true}Réponse:
{"type": "response", "command": "set_auto_retry", "success": true}abort_retry
Abandonnez une nouvelle tentative en cours (annulez le délai et arrêtez de réessayer).
{"type": "abort_retry"}Réponse:
{"type": "response", "command": "abort_retry", "success": true}Frapper
bash
Exécutez une commande shell et ajoutez une sortie au contexte de conversation. Flux de sortie sous forme d'événements bash_execution_update pendant l'exécution de la commande; la réponse contient le résultat final.
{"id": "req-1", "type": "bash", "command": "ls -la"}Incluez un id pour associer les événements bash_execution_update diffusés en streaming à cette commande.
Réponse:
{
"id": "req-1",
"type": "response",
"command": "bash",
"success": true,
"data": {
"output": "total 48\ndrwxr-xr-x ...",
"exitCode": 0,
"cancelled": false,
"truncated": false
}
}Si la sortie a été tronquée, inclut fullOutputPath:
{
"type": "response",
"command": "bash",
"success": true,
"data": {
"output": "truncated output...",
"exitCode": 0,
"cancelled": false,
"truncated": true,
"fullOutputPath": "/tmp/pi-bash-abc123.log"
}
}Comment les résultats bash atteignent le LLM:
La commande bash s'exécute immédiatement et renvoie un BashResult. En interne, un BashExecutionMessage est créé et stocké dans l'état de message de l'agent.
Lorsque la prochaine commande prompt est envoyée, tous les messages (y compris BashExecutionMessage) sont transformés avant d'être envoyés au LLM. Le BashExecutionMessage est converti en UserMessage avec ce format:
Ran `ls -la`
```
total 48
dessinxr-xr-x...
```Cela signifie:
- La sortie Bash est incluse dans le contexte LLM à l'invite suivante, pas immédiatement
- Plusieurs commandes bash peuvent être exécutées avant une invite; toutes les sorties seront incluses
abort_bash
Abandonnez une commande bash en cours d'exécution.
{"type": "abort_bash"}Réponse:
{"type": "response", "command": "abort_bash", "success": true}Session
get_session_stats
Obtenez l'utilisation des jetons, les statistiques de coûts et l'utilisation actuelle de la fenêtre contextuelle.
{"type": "get_session_stats"}Réponse:
{
"type": "response",
"command": "get_session_stats",
"success": true,
"data": {
"sessionFile": "/path/to/session.jsonl",
"sessionId": "abc123",
"userMessages": 5,
"assistantMessages": 5,
"toolCalls": 12,
"toolResults": 12,
"totalMessages": 22,
"tokens": {
"input": 50000,
"output": 10000,
"cacheRead": 40000,
"cacheWrite": 5000,
"total": 105000
},
"cost": 0.45,
"contextUsage": {
"tokens": 60000,
"contextWindow": 200000,
"percent": 30
}
}
}tokens et cost incluent les messages de l'assistant, l'utilisation signalée par les outils et la génération de compactage/résumé de branche tout au long de la session complète. contextUsage contient l'estimation actuelle de la fenêtre contextuelle utilisée pour le compactage et l'affichage du pied de page.
contextUsage est omis lorsqu'aucun modèle ou fenêtre contextuelle n'est disponible. contextUsage.tokens et contextUsage.percent sont null immédiatement après le compactage jusqu'à ce qu'une nouvelle réponse de l'assistant de post-compactage fournisse des données d'utilisation valides.
export_html
Exportez la session vers un fichier HTML.
{"type": "export_html"}Avec chemin personnalisé:
{"type": "export_html", "outputPath": "/tmp/session.html"}Réponse:
{
"type": "response",
"command": "export_html",
"success": true,
"data": {"path": "/tmp/session.html"}
}switch_session
Chargez un autre fichier de session. Peut être annulé par un gestionnaire d'événements d'extension session_before_switch.
{"type": "switch_session", "sessionPath": "/path/to/session.jsonl"}Réponse:
{"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": false}}Si un poste a annulé le changement:
{"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": true}}fourchette
Créez un nouveau fork à partir d'un message utilisateur précédent sur la branche active. Peut être annulé par un gestionnaire d'événements d'extension session_before_fork. Renvoie le texte du message à partir duquel il est dérivé.
{"type": "fork", "entryId": "abc123"}Réponse:
{
"type": "response",
"command": "fork",
"success": true,
"data": {"text": "The original prompt text...", "cancelled": false}
}Si une extension annule le fork:
{
"type": "response",
"command": "fork",
"success": true,
"data": {"text": "The original prompt text...", "cancelled": true}
}cloner
Dupliquez la branche active actuelle dans une nouvelle session à la position actuelle. Peut être annulé par un gestionnaire d'événements d'extension session_before_fork.
{"type": "clone"}Réponse:
{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": false}
}Si une extension a annulé le clonage:
{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": true}
}get_fork_messages
Obtenez les messages utilisateur disponibles pour le forking.
{"type": "get_fork_messages"}Réponse:
{
"type": "response",
"command": "get_fork_messages",
"success": true,
"data": {
"messages": [
{"entryId": "abc123", "text": "First prompt..."},
{"entryId": "def456", "text": "Second prompt..."}
]
}
}get_entries
Obtenez toutes les entrées de session dans l’ordre d’ajout (à l’exclusion de l’en-tête de session). La session est une arborescence d'entrées en ajout uniquement avec des identifiants stables, donc un identifiant d'entrée fonctionne comme un curseur durable: transmettez le dernier identifiant d'entrée que vous avez vu comme since pour obtenir uniquement les entrées strictement après, même lors des redémarrages du client. Contrairement à get_messages, cela inclut l'historique de pré-compactage et les branches abandonnées.
{"type": "get_entries"}Avec un curseur:
{"type": "get_entries", "since": "abc123"}Réponse:
{
"type": "response",
"command": "get_entries",
"success": true,
"data": {
"entries": [
{"type": "message", "id": "def456", "parentId": "abc123", "timestamp": "...", "message": {"role": "user", "...": "..."}}
],
"leafId": "def456"
}
}leafId est l'identifiant de l'entrée feuille actuelle (null pour une session vide), afin qu'un client puisse savoir en un aller-retour si la branche active a bougé. Si since ne correspond à aucun identifiant d'entrée, la réponse est success: false.
get_tree
Obtenez la session sous forme d’arborescence d’entrées. Chaque nœud est {entry, children, label?, labelTimestamp?}. Une session bien formée a une seule racine; les entrées orphelines (chaîne parent brisée) apparaissent également comme racines.
{"type": "get_tree"}Réponse:
{
"type": "response",
"command": "get_tree",
"success": true,
"data": {
"tree": [
{
"entry": {"type": "message", "id": "abc123", "parentId": null, "...": "..."},
"children": [
{"entry": {"type": "message", "id": "def456", "parentId": "abc123", "...": "..."}, "children": []}
]
}
],
"leafId": "def456"
}
}get_last_assistant_text
Obtenez le contenu textuel du dernier message de l'assistant.
{"type": "get_last_assistant_text"}Réponse:
{
"type": "response",
"command": "get_last_assistant_text",
"success": true,
"data": {"text": "The assistant's response..."}
}Renvoie {"text": null} si aucun message d'assistant n'existe.
set_session_name
Définissez un nom d'affichage pour la session en cours. Le nom apparaît dans les listes de sessions et permet d'identifier les sessions.
{"type": "set_session_name", "name": "my-feature-work"}Réponse:
{
"type": "response",
"command": "set_session_name",
"success": true
}Le nom de la session actuelle est disponible via get_state dans le champ sessionName. Pour définir le nom initial lors du démarrage du mode RPC, passez --name <name> ou -n <name> au processus pi --mode rpc.
Commandes
get_commands
Obtenez les commandes disponibles (commandes d'extension, prompt templates et compétences). Ceux-ci peuvent être invoqués via la commande prompt en préfixant /.
{"type": "get_commands"}Réponse:
{
"type": "response",
"command": "get_commands",
"success": true,
"data": {
"commands": [
{"name": "session-name", "description": "Set or clear session name", "source": "extension", "path": "/home/user/.pi/agent/extensions/session.ts"},
{"name": "fix-tests", "description": "Fix failing tests", "source": "prompt", "location": "project", "path": "/home/user/myproject/.pi/agent/prompts/fix-tests.md"},
{"name": "skill:brave-search", "description": "Web search via Brave API", "source": "skill", "location": "user", "path": "/home/user/.pi/agent/skills/brave-search/SKILL.md"}
]
}
}Chaque commande possède:
name: nom de la commande (appelé avec/name)description: description lisible par l'homme (facultatif pour les commandes d'extension)source: Quel type de commande:"extension": Enregistré viapi.registerCommand()dans une extension"prompt": chargé à partir d'un fichier de modèle d'invite.md"skill": Chargé à partir d'un répertoire de compétences (le nom est préfixé parskill:)
location: D'où il a été chargé (facultatif, non présent pour les extensions):"user": niveau utilisateur (~/.pi/agent/)"project": niveau du projet (./.pi/agent/)"path": chemin explicite via CLI ou paramètres
path: chemin de fichier absolu vers la source de la commande (facultatif)
Remarque: Les commandes TUI intégrées (/settings, /hotkeys, etc.) ne sont pas incluses. Ils sont gérés uniquement en mode interactif et ne s'exécuteront pas s'ils sont envoyés via prompt.
Événements
Les événements sont diffusés vers stdout sous forme de lignes JSON pendant le fonctionnement de l'agent. Les événements n'incluent généralement pas de champ id; bash_execution_update inclut le id de sa commande bash d'origine lorsqu'elle est fournie.
Types d'événements
| Événement | Description |
|---|---|
agent_start |
L'agent commence le traitement |
agent_end |
Une exécution d'agent de bas niveau est terminée (peut encore être suivie d'une nouvelle tentative, d'un compactage ou de continuations en file d'attente) |
agent_settled |
L'exécution de l'agent est entièrement réglée; il ne reste aucune nouvelle tentative automatique, nouvelle tentative de compactage ou continuation en file d'attente |
turn_start |
Un nouveau tour commence |
turn_end |
Tour terminé (inclut le message de l'assistant et les résultats de l'outil) |
message_start |
Le message commence |
message_update |
Mise à jour en streaming (deltas texte/réflexion/appel d'outils) |
message_end |
Message terminé |
bash_execution_update |
Morceau de sortie de commande direct RPC bash |
tool_execution_start |
L'outil commence son exécution |
tool_execution_update |
Progression de l'exécution de l'outil (sortie en streaming) |
tool_execution_end |
Outil terminé |
queue_update |
Modification de la file d'attente de pilotage/suivi en attente |
compaction_start |
Le compactage commence |
compaction_end |
Le compactage est terminé |
auto_retry_start |
La nouvelle tentative automatique commence (après une erreur passagère) |
auto_retry_end |
La nouvelle tentative automatique est terminée (succès ou échec final) |
summarization_retry_scheduled |
Nouvelle tentative planifiée pour une erreur de compactage transitoire ou de résumé de branchement |
summarization_retry_attempt_start |
La demande de résumé réessayée démarre |
summarization_retry_finished |
La boucle de nouvelle tentative de synthèse est terminée |
extension_error |
L'extension a généré une erreur |
agent_start
Émis lorsque l'agent commence à traiter une invite.
{"type": "agent_start"}fin_agent
Émis lorsqu’une exécution d’agent de bas niveau est terminée. Contient tous les messages générés lors de cette exécution. Si willRetry est vrai, une nouvelle tentative automatique suivra.
{
"type": "agent_end",
"messages": [...],
"willRetry": false
}agent_installé
Émis après le règlement de l’exécution complète au niveau de la session. À ce stade, Pi ne continuera pas automatiquement via une nouvelle tentative, une nouvelle tentative de compactage ou des messages de suivi en file d'attente.
{"type": "agent_settled"}tour_début / tour_end
Un tour se compose d’une réponse d’assistant ainsi que de tous les appels d’outils et résultats qui en résultent.
{"type": "turn_start"}{
"type": "turn_end",
"message": {...},
"toolResults": [...]
}message_début / message_fin
Émis lorsqu'un message commence et se termine. Le champ message contient un AgentMessage.
{"type": "message_start", "message": {...}}
{"type": "message_end", "message": {...}}message_update (diffusion)
Émis lors du streaming des messages de l'assistant. Contient un événement delta sans instantané de message cumulatif.
{
"type": "message_update",
"assistantMessageEvent": {
"type": "text_delta",
"contentIndex": 0,
"delta": "Hello "
}
}Le champ assistantMessageEvent contient l'un de ces types delta:
| Taper | Description |
|---|---|
text_start |
Le bloc de contenu texte a démarré |
text_delta |
Morceau de contenu textuel |
text_end |
Le bloc de contenu texte est terminé |
thinking_start |
Le bloc de réflexion a commencé |
thinking_delta |
Contenu de réflexion |
thinking_end |
Le bloc de réflexion est terminé |
toolcall_start |
Appel d'outil lancé |
toolcall_delta |
Morceau d'arguments d'appel d'outil |
toolcall_end |
Appel d'outil terminé (inclut l'objet toolCall complet) |
Exemple de diffusion d'une réponse textuelle:
{"type":"message_update","assistantMessageEvent":{"type":"text_start","contentIndex":0}}
{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":" world"}}
{"type":"message_update","assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Hello world"}}message_update omet intentionnellement l'ancien champ cumulatif message et
assistantMessageEvent.partial. Les clients qui ont besoin d'un message partiel en direct doivent l'assembler
à partir de message_start et les événements suivants en utilisant contentIndex. Traiter message_end.message
comme faisant autorité. Pour les appels d'outils, tamponnez toolcall_delta.delta; toolcall_end.toolCall
contient l'appel terminé.
bash_execution_update
Émis une fois pour chaque morceau de sortie à partir d'une commande directe bash. id correspond au id de la commande, permettant aux clients d'associer la sortie à la commande correcte.
Les événements diffusent toutes les sorties pendant l'exécution de la commande, même si le output de la réponse finale bash est tronqué.
{
"type": "bash_execution_update",
"id": "req-1",
"delta": "total 48\n"
}tool_execution_start/tool_execution_update/tool_execution_end
Émis lorsqu'un outil démarre, diffuse la progression et termine l'exécution.
{
"type": "tool_execution_start",
"toolCallId": "call_abc123",
"toolName": "bash",
"args": {"command": "ls -la"}
}Pendant l'exécution, les événements tool_execution_update diffusent des résultats partiels (par exemple, la sortie bash à son arrivée):
{
"type": "tool_execution_update",
"toolCallId": "call_abc123",
"toolName": "bash",
"args": {"command": "ls -la"},
"partialResult": {
"content": [{"type": "text", "text": "partial output so far..."}],
"details": {"truncation": null, "fullOutputPath": null}
}
}Une fois terminé:
{
"type": "tool_execution_end",
"toolCallId": "call_abc123",
"toolName": "bash",
"result": {
"content": [{"type": "text", "text": "total 48\n..."}],
"details": {...}
},
"isError": false
}Utilisez toolCallId pour corréler les événements. Le partialResult dans tool_execution_update contient la sortie accumulée jusqu'à présent (pas seulement le delta), permettant aux clients de simplement remplacer leur affichage à chaque mise à jour.
mise à jour_file d'attente
Émis chaque fois que la file d'attente de pilotage ou de suivi en attente change.
{
"type": "queue_update",
"steering": ["Focus on error handling"],
"followUp": ["After that, summarize the result"]
}compaction_start / compaction_end
Émis lors du compactage, qu'il soit manuel ou automatique.
{"type": "compaction_start", "reason": "threshold"}Le champ reason est "manual", "threshold" ou "overflow".
{
"type": "compaction_end",
"reason": "threshold",
"result": {
"summary": "Summary of conversation...",
"firstKeptEntryId": "abc123",
"tokensBefore": 150000,
"estimatedTokensAfter": 32000,
"usage": {
"input": 32000,
"output": 1200,
"cacheRead": 0,
"cacheWrite": 0,
"totalTokens": 33200,
"cost": {"input": 0.01, "output": 0.02, "cacheRead": 0, "cacheWrite": 0, "total": 0.03}
},
"details": {}
},
"aborted": false,
"willRetry": false
}Si reason était "overflow" et que le compactage réussit, willRetry vaut true et l'agent réessayera automatiquement l'invite.
Si le compactage a été interrompu, result est null et aborted est true.
Si le compactage a échoué (par exemple, quota API dépassé), result est null, aborted est false et errorMessage contient la description de l'erreur.
auto_retry_start / auto_retry_end
Émis lorsqu'une nouvelle tentative automatique est déclenchée après une erreur transitoire (surcharge, limite de débit, 5xx).
{
"type": "auto_retry_start",
"attempt": 1,
"maxAttempts": 3,
"delayMs": 2000,
"errorMessage": "529 {\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded\"}}"
}{
"type": "auto_retry_end",
"success": true,
"attempt": 2
}En cas d'échec final (nombre maximal de tentatives dépassé):
{
"type": "auto_retry_end",
"success": false,
"attempt": 3,
"finalError": "529 overloaded_error: Overloaded"
}summarization_retry_scheduled / summarization_retry_attempt_start / summarization_retry_finished
Émis lors de nouvelles tentatives de compactage ou de résumé de branche après une erreur transitoire du fournisseur. Ces événements utilisent les mêmes paramètres de nouvelle tentative que les tentatives automatiques de tour d'assistant.
{
"type": "summarization_retry_scheduled",
"attempt": 1,
"maxAttempts": 3,
"delayMs": 2000,
"errorMessage": "terminated"
}{
"type": "summarization_retry_attempt_start",
"source": "compaction",
"reason": "threshold"
}Pour les résumés de branche, source est "branchSummary" et aucun reason n'est présent.
{
"type": "summarization_retry_finished"
}erreur_extension
Émis lorsqu'une extension génère une erreur.
{
"type": "extension_error",
"extensionPath": "/path/to/extension.ts",
"event": "tool_call",
"error": "Error message..."
}Protocole d'interface utilisateur d'extension
Extensions peut demander une interaction de l'utilisateur via ctx.ui.select(), ctx.ui.confirm(), etc. En mode RPC, ceux-ci sont traduits en un sous-protocole de demande/réponse au-dessus du flux de commande/d'événement de base.
Il existe deux catégories de méthodes d'extension de l'interface utilisateur:
- Méthodes de dialogue (
select,confirm,input,editor): émettent unextension_ui_requestsur stdout et bloquent jusqu'à ce que le client renvoie unextension_ui_responsesur stdin avec leidcorrespondant. - Méthodes de tir et d'oubli (
notify,setStatus,setWidget,setTitle,set_editor_text): émet unextension_ui_requestsur stdout mais n'attend pas de réponse. Le client peut afficher les informations ou les ignorer.
Si une méthode de dialogue inclut un champ timeout, le côté agent se résoudra automatiquement avec une valeur par défaut à l'expiration du délai d'attente. Le client n'a pas besoin de suivre les délais d'attente.
Certaines méthodes ExtensionUIContext ne sont pas prises en charge ou dégradées en mode RPC car elles nécessitent un accès direct TUI:
custom()renvoieundefinedsetWorkingMessage(),setWorkingIndicator(),setFooter(),setHeader(),setEditorComponent(),setToolsExpanded()ne sont pas opérationnelsgetEditorText()renvoie""getToolsExpanded()renvoiefalsepasteToEditor()délégués àsetEditorText()(pas de gestion du collage/réduction)getAllThemes()renvoie[]getTheme()renvoieundefinedsetTheme()renvoie{ success: false, error: "..." }
Remarque: ctx.mode est "rpc" et ctx.hasUI est true en mode RPC car les méthodes de dialogue et de déclenchement et d'oubli sont fonctionnelles via le sous-protocole d'extension de l'interface utilisateur. Utilisez ctx.mode === "tui" pour protéger les fonctionnalités spécifiques à TUI comme custom() qui nécessitent un vrai terminal.
Demandes d'extension de l'interface utilisateur (stdout)
Toutes les demandes ont un champ type: "extension_ui_request", un champ id unique et un champ method.
sélectionner
Inviter l'utilisateur à choisir dans une liste. Les méthodes de dialogue avec un champ timeout incluent le délai d'expiration en millisecondes; l'agent se résout automatiquement avec undefined si le client ne répond pas à temps.
{
"type": "extension_ui_request",
"id": "uuid-1",
"method": "select",
"title": "Allow dangerous command?",
"options": ["Allow", "Block"],
"timeout": 10000
}Réponse attendue: extension_ui_response avec value (la chaîne d'option sélectionnée) ou cancelled: true.
confirmer
Inviter l'utilisateur à confirmer oui/non.
{
"type": "extension_ui_request",
"id": "uuid-2",
"method": "confirm",
"title": "Clear session?",
"message": "All messages will be lost.",
"timeout": 5000
}Réponse attendue: extension_ui_response avec confirmed: true/false ou cancelled: true.
saisir
Inviter l'utilisateur à saisir un texte de forme libre.
{
"type": "extension_ui_request",
"id": "uuid-3",
"method": "input",
"title": "Enter a value",
"placeholder": "type something..."
}Réponse attendue: extension_ui_response avec value (le texte saisi) ou cancelled: true.
éditeur
Ouvrez un éditeur de texte multiligne avec du contenu prérempli facultatif.
{
"type": "extension_ui_request",
"id": "uuid-4",
"method": "editor",
"title": "Edit some text",
"prefill": "Line 1\nLine 2\nLine 3"
}Réponse attendue: extension_ui_response avec value (le texte édité) ou cancelled: true.
notifier
Afficher une notification. Tirer et oublier, aucune réponse attendue.
{
"type": "extension_ui_request",
"id": "uuid-5",
"method": "notify",
"message": "Command blocked by user",
"notifyType": "warning"
}Le champ notifyType est "info", "warning" ou "error". La valeur par défaut est "info" en cas d'omission.
setStatus
Définissez ou effacez une entrée d’état dans le pied de page/barre d’état. Tirez et oubliez.
{
"type": "extension_ui_request",
"id": "uuid-6",
"method": "setStatus",
"statusKey": "my-ext",
"statusText": "Turn 3 running..."
}Envoyez statusText: undefined (ou omettez-le) pour effacer l'entrée d'état de cette clé.
définirWidget
Définissez ou effacez un widget (bloc de lignes de texte) affiché au-dessus ou en dessous de l'éditeur. Tirez et oubliez.
{
"type": "extension_ui_request",
"id": "uuid-7",
"method": "setWidget",
"widgetKey": "my-ext",
"widgetLines": ["--- My Widget ---", "Line 1", "Line 2"],
"widgetPlacement": "aboveEditor"
}Envoyez widgetLines: undefined (ou omettez-le) pour effacer le widget. Le champ widgetPlacement est "aboveEditor" (par défaut) ou "belowEditor". Seuls les tableaux de chaînes sont pris en charge en mode RPC; les usines de composants sont ignorées.
définirTitre
Définissez le titre de la fenêtre/de l'onglet du terminal. Tirez et oubliez.
{
"type": "extension_ui_request",
"id": "uuid-8",
"method": "setTitle",
"title": "pi - my project"
}set_editor_text
Définissez le texte dans l'éditeur de saisie. Tirez et oubliez.
{
"type": "extension_ui_request",
"id": "uuid-9",
"method": "set_editor_text",
"text": "prefilled text for the user"
}Réponses de l'interface utilisateur de l'extension (stdin)
Les réponses sont envoyées uniquement pour les méthodes de dialogue (select, confirm, input, editor). Le id doit correspondre à la demande.
Réponse de valeur (sélection, saisie, éditeur)
{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}Réponse de confirmation (confirmer)
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}Réponse d'annulation (n'importe quelle boîte de dialogue)
Ignorez toute méthode de dialogue. L'extension reçoit undefined (pour sélectionner/saisir/éditeur) ou false (pour confirmer).
{"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}Gestion des erreurs
Les commandes ayant échoué renvoient une réponse avec success: false:
{
"type": "response",
"command": "set_model",
"success": false,
"error": "Model not found: invalid/model"
}Erreurs d'analyse:
{
"type": "response",
"command": "parse",
"success": false,
"error": "Failed to parse command: Unexpected token..."
}Espèces
Fichiers sources:
packages/ai/src/types.ts-Model,UserMessage,AssistantMessage,ToolResultMessagepackages/agent/src/types.ts-AgentMessage,AgentEventsrc/core/messages.ts-BashExecutionMessagesrc/modes/json-event.ts-JsonAgentSessionEventsrc/modes/rpc/rpc-types.ts- RPC types de commande/réponse, types de demande/réponse de l'interface utilisateur d'extension
Modèle
{
"id": "claude-sonnet-4-20250514",
"name": "Claude Sonnet 4",
"api": "anthropic-messages",
"provider": "anthropic",
"baseUrl": "https://api.anthropic.com",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 200000,
"maxTokens": 16384,
"cost": {
"input": 3.0,
"output": 15.0,
"cacheRead": 0.3,
"cacheWrite": 3.75
}
}Message utilisateur
{
"role": "user",
"content": "Hello!",
"timestamp": 1733234567890,
"attachments": []
}Le champ content peut être une chaîne ou un tableau de blocs TextContent/ImageContent.
AssistantMessage
{
"role": "assistant",
"content": [
{"type": "text", "text": "Hello! How can I help?"},
{"type": "thinking", "thinking": "User is greeting me..."},
{"type": "toolCall", "id": "call_123", "name": "bash", "arguments": {"command": "ls"}}
],
"api": "anthropic-messages",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"usage": {
"input": 100,
"output": 50,
"cacheRead": 0,
"cacheWrite": 0,
"cost": {"input": 0.0003, "output": 0.00075, "cacheRead": 0, "cacheWrite": 0, "total": 0.00105}
},
"stopReason": "stop",
"timestamp": 1733234567890
}Motifs d'arrêt: "stop", "length", "toolUse", "error", "aborted"
Message de résultat de l'outil
{
"role": "toolResult",
"toolCallId": "call_123",
"toolName": "bash",
"content": [{"type": "text", "text": "total 48\ndrwxr-xr-x ..."}],
"usage": {
"input": 100,
"output": 50,
"cacheRead": 0,
"cacheWrite": 0,
"totalTokens": 150,
"cost": {"input": 0.0003, "output": 0.00075, "cacheRead": 0, "cacheWrite": 0, "total": 0.00105}
},
"isError": false,
"timestamp": 1733234567890
}usage est facultatif et signale le travail LLM imbriqué effectué par l'outil. Lorsqu'il est présent, il contribue aux totaux des jetons de session et des coûts.
BashExecutionMessage
Créé par la commande bash RPC (et non par les appels de l'outil LLM):
{
"role": "bashExecution",
"command": "ls -la",
"output": "total 48\ndrwxr-xr-x ...",
"exitCode": 0,
"cancelled": false,
"truncated": false,
"fullOutputPath": null,
"timestamp": 1733234567890
}Pièce jointe
{
"id": "img1",
"type": "image",
"fileName": "photo.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"content": "base64-encoded-data...",
"extractedText": null,
"preview": null
}Exemple: client de base (Python)
import subprocess
import json
proc = subprocess.Popen(
["pi", "--mode", "rpc", "--no-session"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True
)
def send(cmd):
proc.stdin.write(json.dumps(cmd) + "\n")
proc.stdin.flush()
def read_events():
for line in proc.stdout:
yield json.loads(line)
# Send prompt
send({"type": "prompt", "message": "Hello!"})
# Process events
for event in read_events():
if event.get("type") == "message_update":
delta = event.get("assistantMessageEvent", {})
if delta.get("type") == "text_delta":
print(delta["delta"], end="", flush=True)
if event.get("type") == "agent_end":
print()
breakExemple: Client interactif (Node.js)
Voir test/rpc-example.ts pour un exemple interactif complet, ou src/modes/rpc/rpc-client.ts pour une implémentation client typée.
Pour un exemple complet de gestion du protocole d'interface utilisateur de l'extension, voir examples/rpc-extension-ui.ts qui s'associe à l'extension examples/extensions/rpc-demo.ts.
const { spawn } = require("child_process");
const { StringDecoder } = require("string_decoder");
const agent = spawn("pi", ["--mode", "rpc", "--no-session"]);
function attachJsonlReader(stream, onLine) {
const decoder = new StringDecoder("utf8");
let buffer = "";
stream.on("data", (chunk) => {
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
while (true) {
const newlineIndex = buffer.indexOf("\n");
if (newlineIndex === -1) break;
let line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
if (line.endsWith("\r")) line = line.slice(0, -1);
onLine(line);
}
});
stream.on("end", () => {
buffer += decoder.end();
if (buffer.length > 0) {
onLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
}
});
}
attachJsonlReader(agent.stdout, (line) => {
const event = JSON.parse(line);
if (event.type === "message_update") {
const { assistantMessageEvent } = event;
if (assistantMessageEvent.type === "text_delta") {
process.stdout.write(assistantMessageEvent.delta);
}
}
});
// Send prompt
agent.stdin.write(JSON.stringify({ type: "prompt", message: "Hello" }) + "\n");
// Abort on Ctrl+C
process.on("SIGINT", () => {
agent.stdin.write(JSON.stringify({ type: "abort" }) + "\n");
});