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 session

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.

Content Blocks

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

Base Message Types (from 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.

Extended Message Types (from 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

SessionHeader

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"}

SessionMessageEntry

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

ModelChangeEntry

É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"}

ThinkingLevelChangeEntry

É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"}

CompactionEntry

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 générés par les versions récentes de Pi 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. Ce champ est facultatif uniquement pour la compatibilité descendante avec les anciennes sessions. Les compactages générés par les versions récentes de Pi l'incluent afin de 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.

BranchSummaryEntry

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é)

CustomEntry

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.

CustomMessageEntry

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)

LabelEntry

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.

SessionInfoEntry

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.

Static Creation Methods

  • SessionManager.create(cwd, sessionDir?) - Nouvelle session
  • 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

Static Listing Methods

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

Instance Methods - Session Management

  • 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

Instance Methods - Appending (all return entry ID)

  • 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

Instance Methods - Tree Navigation

  • 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

Instance Methods - Context & Info

  • 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