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

Format de fichier de session

Les sessions sont stockées sous forme de fichiers JSONL (JSON Lines). Chaque ligne est un objet JSON avec un champ type. Les entrées de session forment une arborescence via les champs id/parentId, permettant un branchement sur place sans créer de nouveaux fichiers.

Emplacement du fichier

~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl

<path> est le répertoire de travail avec / remplacé par -.

Suppression de sessions

Les sessions peuvent être supprimées en supprimant leurs fichiers .jsonl sous ~/.pi/agent/sessions/.

Pi prend également en charge la suppression interactive de sessions à partir de /resume (sélectionnez une session et appuyez sur Ctrl+D, puis confirmez). Lorsqu'il est disponible, pi utilise le trash CLI pour éviter une suppression permanente.

Version de la séance

Les sessions ont un champ de version dans l'en-tête:

  • Version 1: séquence d'entrée linéaire (héritée, migrée automatiquement au chargement)
  • Version 2: Arborescence avec liaison id/parentId
  • Version 3: rôle hookMessage renommé en custom (unification des extensions)

Les sessions existantes sont automatiquement migrées vers la version actuelle (v3) une fois chargées.

Fichiers sources

Source sur GitHub (pi-mono):

Pour les définitions TypeScript de votre projet, inspectez node_modules/@earendil-works/pi-coding-agent/dist/ et node_modules/@earendil-works/pi-ai/dist/.

Types de messages

Les entrées de session contiennent AgentMessage objets. Comprendre ces types est essentiel pour analyser les sessions et écrire des extensions.

Blocs de contenu

Les messages contiennent des tableaux de blocs de contenu typés:

interface TextContent {
  type: "text";
  text: string;
}

interface ImageContent {
  type: "image";
  data: string;      // base64 encoded
  mimeType: string;  // e.g., "image/jpeg", "image/png"
}

interface ThinkingContent {
  type: "thinking";
  thinking: string;
}

interface ToolCall {
  type: "toolCall";
  id: string;
  name: string;
  arguments: Record<string, any>;
}

Types de messages de base (de pi-ai)

interface UserMessage {
  role: "user";
  content: string | (TextContent | ImageContent)[];
  timestamp: number;  // Unix ms
}

interface AssistantMessage {
  role: "assistant";
  content: (TextContent | ThinkingContent | ToolCall)[];
  api: string;
  provider: string;
  model: string;
  usage: Usage;
  stopReason: "stop" | "length" | "toolUse" | "error" | "aborted";
  errorMessage?: string;
  timestamp: number;
}

interface ToolResultMessage {
  role: "toolResult";
  toolCallId: string;
  toolName: string;
  content: (TextContent | ImageContent)[];
  details?: any;      // Tool-specific metadata
  usage?: Usage;      // Nested LLM work performed by the tool
  isError: boolean;
  timestamp: number;
}

interface Usage {
  input: number;
  output: number;
  cacheRead: number;
  cacheWrite: number;
  totalTokens: number;
  cost: {
    input: number;
    output: number;
    cacheRead: number;
    cacheWrite: number;
    total: number;
  };
}

Le type pi-ai StopReason exporté inclut également "pending", mais cette valeur est réservée aux messages partiels dans les événements de streaming. Les messages du terminal done/error le remplacent par une raison d'achèvement avant que pi ne persiste le message de l'assistant, donc "pending" ne devrait jamais apparaître dans la session JSONL.

Types de messages étendus (de pi-coding-agent)

interface BashExecutionMessage {
  role: "bashExecution";
  command: string;
  output: string;
  exitCode: number | undefined;
  cancelled: boolean;
  truncated: boolean;
  fullOutputPath?: string;
  excludeFromContext?: boolean;  // true for !! prefix commands
  timestamp: number;
}

interface CustomMessage {
  role: "custom";
  customType: string;            // Extension identifier
  content: string | (TextContent | ImageContent)[];
  display: boolean;              // Show in TUI
  details?: any;                 // Extension-specific metadata
  timestamp: number;
}

interface BranchSummaryMessage {
  role: "branchSummary";
  summary: string;
  fromId: string;                // Entry we branched from
  timestamp: number;
}

interface CompactionSummaryMessage {
  role: "compactionSummary";
  summary: string;
  tokensBefore: number;
  timestamp: number;
}

AgentMessage Union

type AgentMessage =
  | UserMessage
  | AssistantMessage
  | ToolResultMessage
  | BashExecutionMessage
  | CustomMessage
  | BranchSummaryMessage
  | CompactionSummaryMessage;

Base d'entrée

Toutes les entrées (sauf SessionHeader) étendent SessionEntryBase:

interface SessionEntryBase {
  type: string;
  id: string;           // 8-char hex ID
  parentId: string | null;  // Parent entry ID (null for first entry)
  timestamp: string;    // ISO timestamp
}

Types d'entrée

En-tête de session

Première ligne du fichier. Métadonnées uniquement, ne faisant pas partie de l'arborescence (pas de id/parentId).

{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}

Pour les sessions avec un parent (créées via /fork, /clone ou newSession({ parentSession })):

{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}

Entrée de message de session

Un message dans la conversation. Le champ message contient un AgentMessage.

{"type":"message","id":"a1b2c3d4","parentId":"prev1234","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}}
{"type":"message","id":"c3d4e5f6","parentId":"b2c3d4e5","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false}}

ModèleChangeEntry

Émis lorsque l'utilisateur change de modèle en cours de session.

{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}

RéflexionNiveauChangeEntrée

Émis lorsque l'utilisateur change le niveau de réflexion/raisonnement.

{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}

Entrée de compactage

Créé lorsque le contexte est compacté. Stocke un résumé des messages précédents.

{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}

Les compactages plus récents générés par le faisceau intègrent le contexte post-compactage conservé directement dans l'entrée, au lieu de firstKeptEntryId:

{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","tokensBefore":50000,"retainedTail":[{"role":"user","content":"latest request"},{"role":"assistant","content":[{"type":"text","text":"latest reply"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}]}

Champs facultatifs:

  • usage: utilisation du LLM à partir de la génération du résumé; inclus dans le jeton de session et les totaux des coûts
  • retainedTail: AgentMessage[] matérialisé conservé après compactage. Ceci est facultatif uniquement pour des raisons de compatibilité descendante avec les anciennes sessions. Les compactages plus récents générés par le faisceau l'incluent afin que nous puissions reconstruire le contexte à partir de ce point de contrôle sans parcourir les anciennes entrées avant l'entrée de compactage.
  • details: données spécifiques à l'implémentation (par exemple, { readFiles: string[], modifiedFiles: string[] } pour les données par défaut ou personnalisées pour les extensions)
  • fromHooktrue si généré par une extension, false/undefined si généré par pi (nom de champ hérité)
  • firstKeptEntryId: pour la compatibilité avec l'ancien format de saisie.

BranchSummaryEntrée

Créé lors du changement de branche via /tree avec un résumé généré par LLM de la branche gauche jusqu'à l'ancêtre commun. Capture le contexte du chemin abandonné.

{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}

Champs facultatifs:

  • usage: utilisation du LLM à partir de la génération du résumé; inclus dans le jeton de session et les totaux des coûts
  • details: données de suivi de fichiers ({ readFiles: string[], modifiedFiles: string[] }) pour les données par défaut ou personnalisées pour les extensions
  • fromHooktrue si généré par une extension, false/undefined si généré par pi (nom de champ hérité)

Entrée personnalisée

Persistance de l’état d’extension. Ne participe PAS au contexte LLM.

{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}

Utilisez customType pour identifier les entrées de votre extension lors du rechargement. Le mode interactif peut restituer les entrées personnalisées via pi.registerEntryRenderer(customType, renderer), mais elles ne participent toujours pas au contexte LLM.

Entrée de message personnalisé

Messages injectés par extension qui participent au contexte LLM.

{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-extension","content":"Injected context...","display":true}

Champs:

  • content: chaîne ou (TextContent | ImageContent)[] (identique à UserMessage)
  • displaytrue = afficher dans TUI avec un style distinct, false = masqué
  • details: métadonnées facultatives spécifiques à l'extension (non envoyées à LLM)

EntréeÉtiquette

Signet/marqueur défini par l'utilisateur sur une entrée.

{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}

Réglez label sur undefined pour effacer une étiquette.

EntréeInfoSession

Métadonnées de session (par exemple, nom d'affichage défini par l'utilisateur). Définissez via /name, --name / -n ou pi.setSessionName() dans les extensions.

{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}

Le nom de la session est affiché dans le sélecteur de session (/resume) au lieu du premier message lorsqu'il est défini.

Structure arborescente

Les entrées forment un arbre:

  • La première entrée a parentId: null
  • Chaque entrée suivante pointe vers son parent via parentId
  • Le branchement crée de nouveaux enfants à partir d'une entrée antérieure
  • La "feuille" est la position actuelle dans l'arborescence
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
                                                            │
                                                            └─ [branch_summary] ─── [user msg] ← alternate branch

Création de contexte

buildContextEntries() marche de la feuille actuelle à la racine, produisant la liste des entrées actives tout en respectant le compactage:

  1. Collecte toutes les entrées sur le chemin
  2. Si un CompactionEntry est sur le chemin:
    • Inclut d'abord l'entrée de compactage
    • Si retainedTail est présent, il agit comme un point de contrôle autonome et les entrées après le compactage sont incluses
    • Sinon les entrées de firstKeptEntryId au compactage sont incluses
    • Ensuite, les entrées après compactage sont incluses
  3. Préserve les entrées sans message dans la plage sélectionnée afin que le mode interactif puisse les restituer

buildSessionContext() s'appuie sur cette liste d'entrées pour produire la liste de messages pour le LLM:

  1. Extrait les paramètres actuels du modèle et du niveau de réflexion du chemin complet
  2. Convertit les entrées sélectionnées en messages:
    • message -> stocké AgentMessage
    • compaction -> compactionSummary plus retainedTail lorsqu'il est présent
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> pas de message contextuel

Cela fait que les compactages les plus récents agissent comme des points de contrôle autonomes. retainedTail est facultatif uniquement, donc les anciennes sessions qui stockent uniquement firstKeptEntryId continuent de se charger correctement.

Exemple d'analyse

import { readFileSync } from "fs";

const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");

for (const line of lines) {
  const entry = JSON.parse(line);

  switch (entry.type) {
    case "session":
      console.log(`Session v${entry.version ?? 1}: ${entry.id}`);
      break;
    case "message":
      console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
      break;
    case "compaction":
      console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`);
      break;
    case "branch_summary":
      console.log(`[${entry.id}] Branch from ${entry.fromId}`);
      break;
    case "custom":
      console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`);
      break;
    case "custom_message":
      console.log(`[${entry.id}] Extension message (${entry.customType}): ${entry.content}`);
      break;
    case "label":
      console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`);
      break;
    case "model_change":
      console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`);
      break;
    case "thinking_level_change":
      console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`);
      break;
  }
}

Gestionnaire de sessions API

Méthodes clés pour travailler avec des sessions par programmation.

Méthodes de création statique

  • SessionManager.create(cwd, sessionDir?) - Nouvelle séance
  • SessionManager.open(path, sessionDir?) - Ouvrir le fichier de session existant
  • SessionManager.continueRecent(cwd, sessionDir?) - Continuer le plus récent ou créer un nouveau
  • SessionManager.inMemory(cwd?) - Aucune persistance du fichier
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Session Fork d'un autre projet

Méthodes de liste statique

  • SessionManager.list(cwd, sessionDir?, onProgress?) - Liste des sessions pour un répertoire
  • SessionManager.listAll(onProgress?) - Répertorier toutes les sessions de tous les projets

Méthodes d'instance - Gestion de session

  • newSession(options?) - Démarrer une nouvelle session (options: { parentSession?: string })
  • setSessionFile(path) - Passer à un autre fichier de session
  • createBranchedSession(leafId) - Extraire la branche vers un nouveau fichier de session

Méthodes d'instance - Ajout (tous les ID d'entrée de retour)

  • appendMessage(message) - Ajouter un message
  • appendThinkingLevelChange(level) – Enregistrer le changement de pensée
  • appendModelChange(provider, modelId) - Enregistrer le changement de modèle
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - Ajouter un compactage
  • appendCustomEntry(customType, data?) - État de l'extension (pas dans son contexte)
  • appendSessionInfo(name) - Définir le nom d'affichage de la session
  • appendCustomMessageEntry(customType, content, display, details?) - Message d'extension (en contexte)
  • appendLabelChange(targetId, label) - Définir/effacer l'étiquette

Méthodes d'instance - Navigation dans l'arborescence

  • getLeafId() - Position actuelle
  • getLeafEntry() - Obtenir l'entrée de feuille actuelle
  • getEntry(id) - Obtenez une entrée par pièce d'identité
  • getBranch(fromId?) - Marcher de l'entrée à la racine
  • getTree() - Obtenez l'arborescence complète
  • getChildren(parentId) - Obtenez des enfants directs
  • getLabel(id) - Obtenir l'étiquette pour l'entrée
  • branch(entryId) - Déplacer la feuille vers l'entrée précédente
  • resetLeaf() - Réinitialiser la feuille à null (avant toute entrée)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Branche avec résumé du contexte

Méthodes d'instance - Contexte et informations

  • buildContextEntries() - Obtenez les entrées de branche actives avec le compactage appliqué
  • buildSessionContext() - Obtenez des messages, un niveau de réflexion et un modèle pour le LLM
  • getEntries() - Toutes les entrées (hors en-tête)
  • getHeader() - Métadonnées d'en-tête de session
  • getSessionName() - Obtenez le nom d'affichage de la dernière entrée session_info
  • getCwd() - Répertoire de travail
  • getSessionDir() - Répertoire de stockage de session
  • getSessionId() - UUID de session
  • getSessionFile() - Chemin du fichier de session (non défini pour la mémoire)
  • isPersisted() - Indique si la session est enregistrée sur le disque