Konfigurasi, kustomisasi, pengaturan platform, dan referensi API untuk Pi.

Format File Sesi

Sesi disimpan sebagai file JSONL (JSON Baris). Setiap baris adalah objek JSON dengan bidang type. Entri sesi membentuk struktur pohon melalui kolom id/parentId, memungkinkan percabangan di tempat tanpa membuat file baru.

Lokasi Berkas

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

Dimana <path> adalah direktori kerja dengan / digantikan oleh -.

Menghapus Sesi

Sesi dapat dihapus dengan menghapus file .jsonl di bawah ~/.pi/agent/sessions/.

Pi juga mendukung penghapusan sesi secara interaktif dari /resume (pilih sesi dan tekan Ctrl+D, lalu konfirmasi). Jika tersedia, pi menggunakan trash CLI untuk menghindari penghapusan permanen.

Versi Sesi

Sesi memiliki kolom versi di header:

  • Versi 1: Urutan entri linier (lama, dimigrasi otomatis saat dimuat)
  • Versi 2: Struktur pohon dengan tautan id/parentId
  • Versi 3: Mengganti nama peran hookMessage menjadi custom (penyatuan ekstensi)

Sesi yang ada secara otomatis dimigrasikan ke versi saat ini (v3) saat dimuat.

File Sumber

Sumber di GitHub (pi-mono):

Untuk definisi TypeScript dalam proyek Anda, periksa node_modules/@earendil-works/pi-coding-agent/dist/ dan node_modules/@earendil-works/pi-ai/dist/.

Jenis Pesan

Entri sesi berisi objek AgentMessage. Memahami jenis ini penting untuk sesi penguraian dan penulisan ekstensi.

Blok Konten

Pesan berisi array blok konten yang diketik:

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

Jenis Pesan Dasar (dari 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;
  };
}

Jenis pi-ai StopReason yang diekspor juga mencakup "pending", tetapi nilai tersebut dicadangkan untuk sebagian pesan dalam acara streaming. Pesan terminal done/error menggantinya dengan alasan penyelesaian sebelum pi mempertahankan pesan asisten, jadi "pending" tidak akan pernah muncul di sesi JSONL.

Jenis Pesan yang Diperluas (dari 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;
}

Persatuan AgenPesan

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

Basis Masuk

Semua entri (kecuali SessionHeader) diperpanjang 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
}

Jenis Entri

SessionHeader

Baris pertama file. Hanya metadata, bukan bagian dari pohon (tidak ada id/parentId).

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

Untuk sesi dengan orang tua (dibuat melalui /fork, /clone, atau 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"}

Entri Pesan Sesi

Sebuah pesan dalam percakapan. Bidang message berisi 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}}

Entri Perubahan Model

Dipancarkan saat pengguna mengganti model di tengah sesi.

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

Entri Perubahan Tingkat Berpikir

Dipancarkan ketika pengguna mengubah tingkat berpikir/penalaran.

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

Entri Pemadatan

Dibuat ketika konteks dipadatkan. Menyimpan ringkasan pesan sebelumnya.

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

Pemadatan yang dihasilkan oleh harness yang lebih baru menyematkan konteks pasca-pemadatan yang dipertahankan langsung pada entri, bukan 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"}]}

Bidang opsional:

  • usage: Penggunaan LLM dari pembuatan ringkasan; termasuk dalam token sesi dan total biaya
  • retainedTail: Terwujud AgentMessage[] disimpan setelah pemadatan. Ini opsional hanya untuk kompatibilitas dengan sesi yang lebih lama. Pemadatan yang dihasilkan oleh harness yang lebih baru menyertakannya sehingga kami dapat membangun kembali konteks dari pos pemeriksaan ini tanpa harus menjalankan entri lama sebelum entri pemadatan.
  • details: Data spesifik implementasi (misalnya, { readFiles: string[], modifiedFiles: string[] } untuk default, atau data khusus untuk ekstensi)
  • fromHook: true jika dihasilkan oleh ekstensi, false/undefined jika dihasilkan oleh pi (nama kolom lama)
  • firstKeptEntryId: untuk kompatibilitas dengan format entri lama.

Entri Ringkasan Cabang

Dibuat saat berpindah cabang melalui /tree dengan ringkasan yang dihasilkan LLM dari cabang kiri hingga nenek moyang yang sama. Menangkap konteks dari jalur yang ditinggalkan.

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

Bidang opsional:

  • usage: Penggunaan LLM dari pembuatan ringkasan; termasuk dalam token sesi dan total biaya
  • details: Data pelacakan file ({ readFiles: string[], modifiedFiles: string[] }) untuk default, atau data khusus untuk ekstensi
  • fromHook: true jika dihasilkan oleh ekstensi, false/undefined jika dihasilkan oleh pi (nama kolom lama)

Entri Kustom

Persistensi status ekstensi. TIDAK berpartisipasi dalam konteks LLM.

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

Gunakan customType untuk mengidentifikasi entri ekstensi Anda saat memuat ulang. Mode interaktif dapat merender entri khusus melalui pi.registerEntryRenderer(customType, renderer), tetapi entri tersebut tetap tidak berpartisipasi dalam konteks LLM.

Entri Pesan Khusus

Pesan yang dimasukkan ekstensi yang DO berpartisipasi dalam konteks LLM.

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

Bidang:

  • content: String atau (TextContent | ImageContent)[] (sama seperti UserMessage)
  • display: true = tampilkan di TUI dengan gaya berbeda, false = tersembunyi
  • details: Metadata khusus ekstensi opsional (tidak dikirim ke LLM)

LabelEntri

Bookmark/penanda yang ditentukan pengguna pada sebuah entri.

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

Setel label ke undefined untuk menghapus label.

Entri Info Sesi

Metadata sesi (misalnya, nama tampilan yang ditentukan pengguna). Diatur melalui /name, --name / -n, atau pi.setSessionName() dalam ekstensi.

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

Nama sesi ditampilkan di pemilih sesi (/resume) dan bukan di pesan pertama saat disetel.

Struktur Pohon

Entri membentuk pohon:

  • Entri pertama memiliki parentId: null
  • Setiap entri berikutnya menunjuk ke induknya melalui parentId
  • Percabangan menciptakan anak baru dari entri sebelumnya
  • "Daun" adalah posisi saat ini di pohon
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
                                                            │
                                                            └─ [branch_summary] ─── [user msg] ← alternate branch

Membangun Konteks

buildContextEntries() berjalan dari daun saat ini ke akar, menghasilkan daftar entri aktif sambil melakukan pemadatan:

  1. Mengumpulkan semua entri di jalur
  2. Jika CompactionEntry ada di jalur:
    • Termasuk entri pemadatan terlebih dahulu
    • Jika ada retainedTail, maka pos tersebut berfungsi sebagai pos pemeriksaan mandiri dan entri setelah pemadatan disertakan
    • Jika tidak, entri dari firstKeptEntryId hingga pemadatan akan disertakan
    • Kemudian entri setelah pemadatan dimasukkan
  3. Mempertahankan entri non-pesan dalam rentang yang dipilih sehingga mode interaktif dapat merendernya

buildSessionContext() dibangun berdasarkan daftar entri tersebut untuk menghasilkan daftar pesan untuk LLM:

  1. Mengekstrak model saat ini dan pengaturan tingkat pemikiran dari jalur lengkap
  2. Mengonversi entri yang dipilih menjadi pesan:
    • message -> disimpan AgentMessage
    • compaction -> compactionSummary ditambah retainedTail saat ada
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> tidak ada pesan konteks

Hal ini membuat pemadatan yang lebih baru berfungsi seperti pos pemeriksaan mandiri. retainedTail bersifat opsional sehingga sesi lama yang hanya menyimpan firstKeptEntryId terus dimuat dengan benar.

Contoh Penguraian

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

Manajer Sesi API

Metode utama untuk bekerja dengan sesi secara terprogram.

Metode Penciptaan Statis

  • SessionManager.create(cwd, sessionDir?) - Sesi baru
  • SessionManager.open(path, sessionDir?) - Buka file sesi yang ada
  • SessionManager.continueRecent(cwd, sessionDir?) - Lanjutkan yang terbaru atau buat yang baru
  • SessionManager.inMemory(cwd?) - Tidak ada persistensi file
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Sesi fork dari proyek lain

Metode Daftar Statis

  • SessionManager.list(cwd, sessionDir?, onProgress?) - Daftar sesi untuk direktori
  • SessionManager.listAll(onProgress?) - Daftar semua sesi di semua proyek

Metode Instance - Manajemen Sesi

  • newSession(options?) - Memulai sesi baru (opsi: { parentSession?: string })
  • setSessionFile(path) - Beralih ke file sesi lain
  • createBranchedSession(leafId) - Ekstrak cabang ke file sesi baru

Metode Instance - Menambahkan (semua ID entri kembali)

  • appendMessage(message) - Tambahkan pesan
  • appendThinkingLevelChange(level) - Rekam perubahan pemikiran
  • appendModelChange(provider, modelId) - Rekam perubahan model
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - Tambahkan pemadatan
  • appendCustomEntry(customType, data?) - Status ekstensi (tidak dalam konteks)
  • appendSessionInfo(name) - Tetapkan nama tampilan sesi
  • appendCustomMessageEntry(customType, content, display, details?) - Pesan ekstensi (dalam konteks)
  • appendLabelChange(targetId, label) - Setel/hapus label

Metode Instance - Navigasi Pohon

  • getLeafId() - Posisi saat ini
  • getLeafEntry() - Dapatkan entri daun saat ini
  • getEntry(id) - Dapatkan entri berdasarkan ID
  • getBranch(fromId?) - Berjalan dari entri ke root
  • getTree() - Dapatkan struktur pohon lengkap
  • getChildren(parentId) - Dapatkan anak langsung
  • getLabel(id) - Dapatkan label untuk masuk
  • branch(entryId) - Pindahkan daun ke entri sebelumnya
  • resetLeaf() - Setel ulang daun ke nol (sebelum entri apa pun)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Cabang dengan ringkasan konteks

Metode Instance - Konteks & Info

  • buildContextEntries() - Dapatkan entri cabang aktif dengan pemadatan diterapkan
  • buildSessionContext() - Dapatkan pesan, Tingkat berpikir, dan model untuk LLM
  • getEntries() - Semua entri (tidak termasuk header)
  • getHeader() - Metadata header sesi
  • getSessionName() - Dapatkan nama tampilan dari entri session_info terbaru
  • getCwd() - Direktori kerja
  • getSessionDir() - Direktori penyimpanan sesi
  • getSessionId() - Sesi UUID
  • getSessionFile() - Jalur file sesi (tidak ditentukan untuk dalam memori)
  • isPersisted() - Apakah sesi disimpan ke disk