Configuration, personnalisation, paramètres de plateforme et références API pour Pi.

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 charge provider/id et :<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 \n uniquement
  • Acceptez l'entrée facultative \r\n en supprimant un \r final
  • 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:

  1. La sortie Bash est incluse dans le contexte LLM à l'invite suivante, pas immédiatement
  2. 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é via pi.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é par skill:)
  • 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 un extension_ui_request sur stdout et bloquent jusqu'à ce que le client renvoie un extension_ui_response sur stdin avec le id correspondant.
  • Méthodes de tir et d'oubli (notify, setStatus, setWidget, setTitle, set_editor_text): émet un extension_ui_request sur 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() renvoie undefined
  • setWorkingMessage(), setWorkingIndicator(), setFooter(), setHeader(), setEditorComponent(), setToolsExpanded() ne sont pas opérationnels
  • getEditorText() renvoie ""
  • getToolsExpanded() renvoie false
  • pasteToEditor() délégués à setEditorText() (pas de gestion du collage/réduction)
  • getAllThemes() renvoie []
  • getTheme() renvoie undefined
  • setTheme() 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:

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()
        break

Exemple: 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");
});