Pi の設定、拡張、プラットフォーム設定、API リファレンス。

セッションファイル形式

セッションは JSONL (JSON 行) ファイルとして保存されます。各行は、type フィールドを持つ JSON オブジェクトです。セッション エントリは、id/parentId フィールドを介してツリー構造を形成し、新しいファイルを作成せずにインプレース分岐を可能にします。

ファイルの場所

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

ここで、<path> は、/- に置き換えた作業ディレクトリです。

セッションの削除

セッションは、~/.pi/agent/sessions/ にある .jsonl ファイルを削除することで削除できます。

Pi は、/resume から対話的にセッションを削除することもサポートしています (セッションを選択し、Ctrl+D を押して確認します)。利用可能な場合、pi は永久的な削除を避けるために trash CLI を使用します。

セッションバージョン

セッションのヘッダーにはバージョン フィールドがあります。

  • バージョン 1: 線形エントリ シーケンス (レガシー、負荷時に自動移行)
  • バージョン 2: id/parentId リンクを備えたツリー構造
  • バージョン 3: hookMessage ロールの名前が custom に変更されました (拡張機能の統合)

既存のセッションは、ロード時に現在のバージョン (v3) に自動的に移行されます。

ソースファイル

GitHub (pi-mono) のソース:

プロジェクト内の TypeScript 定義については、node_modules/@earendil-works/pi-coding-agent/dist/node_modules/@earendil-works/pi-ai/dist/ を調べてください。

メッセージの種類

セッションエントリには AgentMessage オブジェクトが含まれています。これらの型を理解することは、セッションを解析したり拡張機能を作成したりするために不可欠です。

コンテンツブロック

メッセージには、型指定されたコンテンツ ブロックの配列が含まれます。

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

基本メッセージ タイプ (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;
  };
}

エクスポートされた pi-ai StopReason タイプには "pending" も含ま​​れていますが、その値はストリーミング イベントの部分メッセージ用に予約されています。ターミナル done/error メッセージは、pi がアシスタント メッセージを永続化する前に完了理由に置き換えられるため、"pending" はセッション JSONL に表示されるべきではありません。

拡張メッセージ タイプ (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;
}

エージェントメッセージユニオン

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

エントリーベース

すべてのエントリ (SessionHeader を除く) は 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
}

エントリの種類

セッションヘッダー

ファイルの最初の行。メタデータのみ。ツリーの一部ではありません (id/parentId なし)。

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

親とのセッションの場合 (/fork/clone、または 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"}

セッションメッセージエントリ

会話中のメッセージ。 message フィールドには 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

ユーザーがセッション中にモデルを切り替えると発生します。

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

ThinkingLevelChangeEntry

ユーザーが思考/推論レベルを変更したときに発生します。

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

圧縮エントリ

コンテキストが圧縮されたときに作成されます。以前のメッセージの概要を保存します。

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

新しいハーネス生成の圧縮では、保持された圧縮後のコンテキストが、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"}]}

オプションのフィールド:

  • usage: 概要の生成による LLM の使用。セッショントークンとコストの合計に含まれます
  • retainedTail: 圧縮後に実体化された AgentMessage[] が保持されます。これは、古いセッションとの下位互換性のためのみのオプションです。新しいハーネスによって生成された圧縮にはこれが含まれているため、圧縮エントリの前に古いエントリをたどることなく、このチェックポイントからコンテキストを再構築できます。
  • details: 実装固有のデータ (例: デフォルトの場合は { readFiles: string[], modifiedFiles: string[] }、拡張機能の場合はカスタム データ)
  • fromHook: 拡張機能によって生成された場合は true、pi で生成された場合は false/undefined (従来のフィールド名)
  • firstKeptEntryId: 古いエントリ形式との互換性のため。

ブランチ概要エントリ

共通の祖先までの左ブランチの LLM 生成サマリーを使用して、/tree を介してブランチを切り替えるときに作成されます。放棄されたパスからコンテキストをキャプチャします。

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

オプションのフィールド:

  • usage: 概要の生成による LLM の使用。セッショントークンとコストの合計に含まれます
  • details: デフォルトのファイル追跡データ ({ readFiles: string[], modifiedFiles: string[] })、または拡張子のカスタム データ
  • fromHook: 拡張機能によって生成された場合は true、pi で生成された場合は false/undefined (従来のフィールド名)

カスタムエントリー

拡張機能の状態の永続性。 LLM コンテキストには参加しません。

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

リロード時に拡張機能のエントリを識別するには、customType を使用します。インタラクティブ モードでは、pi.registerEntryRenderer(customType, renderer) を介してカスタム エントリをレンダリングできますが、それでも LLM コンテキストには参加しません。

カスタムメッセージエントリ

LLM コンテキストに参加する、拡張機能によって挿入されたメッセージ。

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

フィールド:

  • content: 文字列または (TextContent | ImageContent)[] (UserMessage と同じ)
  • display: true = 個別のスタイルで TUI に表示、false = 非表示
  • details: オプションの拡張機能固有のメタデータ (LLM には送信されません)

ラベルエントリ

エントリ上のユーザー定義のブックマーク/マーカー。

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

ラベルをクリアするには、labelundefined に設定します。

セッション情報エントリ

セッションのメタデータ (ユーザー定義の表示名など)。拡張機能の/name--name / -n、または pi.setSessionName() で設定します。

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

セッション名は、設定すると最初のメッセージの代わりにセッション セレクター (/resume) に表示されます。

ツリー構造

エントリはツリーを形成します。

  • 最初のエントリには parentId: null が付いています
  • 後続の各エントリは、parentId を介してその親を指します。
  • 分岐すると、以前のエントリから新しい子が作成されます
  • 「リーフ」はツリー内の現在の位置です。
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
                                                            │
                                                            └─ [branch_summary] ─── [user msg] ← alternate branch

コンテキストの構築

buildContextEntries() は現在のリーフからルートまで歩き、圧縮を尊重しながらアクティブなエントリ リストを生成します。

  1. パス上のすべてのエントリを収集します
  2. CompactionEntry がパス上にある場合:
    • 最初に圧縮エントリを含めます
    • retainedTail が存在する場合、自己完結型チェックポイントとして機能し、圧縮後のエントリが含まれます。
    • それ以外の場合は、firstKeptEntryId から圧縮までのエントリが含まれます
    • 圧縮後のエントリが含まれます
  3. 選択した範囲内の非メッセージ エントリを保持して、対話モードでレンダリングできるようにします。

buildSessionContext() は、そのエントリ リストに基づいて、LLM のメッセージ リストを生成します。

  1. フルパスから現在のモデルと思考レベルの設定を抽出します。
  2. 選択したエントリをメッセージに変換します。
    • message -> 保存 AgentMessage
    • compaction -> compactionSummary プラス retainedTail (存在する場合)
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> コンテキストメッセージなし

これにより、新しい圧縮が自己完結型チェックポイントのように機能します。 retainedTail は、firstKeptEntryId のみを保存する古いセッションが引き続き正しく読み込まれるようにするためのオプションです。

解析例

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

セッションマネージャー API

プログラムでセッションを操作するための主要なメソッド。

静的作成方法

  • SessionManager.create(cwd, sessionDir?) - 新しいセッション
  • SessionManager.open(path, sessionDir?) - 既存のセッション ファイルを開きます
  • SessionManager.continueRecent(cwd, sessionDir?) - 最新のものを継続するか、新しく作成します
  • SessionManager.inMemory(cwd?) - ファイルの永続性なし
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - 別のプロジェクトからセッションをフォークする

静的リスト方法

  • SessionManager.list(cwd, sessionDir?, onProgress?) - ディレクトリのセッションをリストします。
  • SessionManager.listAll(onProgress?) - すべてのプロジェクトにわたるすべてのセッションをリストします。

インスタンスメソッド - セッション管理

  • newSession(options?) - 新しいセッションを開始します (オプション: { parentSession?: string })
  • setSessionFile(path) - 別のセッション ファイルに切り替えます
  • createBranchedSession(leafId) - ブランチを新しいセッション ファイルに抽出します

インスタンス メソッド - 追加 (すべてエントリ ID を返します)

  • appendMessage(message) - メッセージを追加
  • appendThinkingLevelChange(level) - 思考の変化を記録する
  • appendModelChange(provider, modelId) - 機種変更の記録
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - 圧縮を追加します
  • appendCustomEntry(customType, data?) - 拡張状態 (コンテキスト内ではない)
  • appendSessionInfo(name) - セッション表示名を設定します
  • appendCustomMessageEntry(customType, content, display, details?) - 拡張メッセージ (コンテキスト内)
  • appendLabelChange(targetId, label) - ラベルの設定/クリア

インスタンス メソッド - ツリー ナビゲーション

  • getLeafId() - 現在の位置
  • getLeafEntry() - 現在のリーフ エントリを取得します
  • getEntry(id) - ID によるエントリーの取得
  • getBranch(fromId?) - 入り口から根まで歩きます
  • getTree() - 完全なツリー構造を取得する
  • getChildren(parentId) - 直接の子を取得する
  • getLabel(id) - エントリ用のラベルを取得します
  • branch(entryId) - リーフを前のエントリに移動します
  • resetLeaf() - リーフを null にリセットします (エントリの前)
  • branchWithSummary(entryId, summary, details?, fromHook?) - コンテキストの概要を含む分岐

インスタンス メソッド - コンテキストと情報

  • buildContextEntries() - 圧縮が適用されたアクティブなブランチ エントリを取得します
  • buildSessionContext() - LLM のメッセージ、思考レベル、モデルを取得します
  • getEntries() - すべてのエントリ (ヘッダーを除く)
  • getHeader() - セッションヘッダーのメタデータ
  • getSessionName() - 最新の session_info エントリから表示名を取得します
  • getCwd() - 作業ディレクトリ
  • getSessionDir() - セッション保存ディレクトリ
  • getSessionId() - セッション UUID
  • getSessionFile() - セッション ファイル パス (メモリ内では未定義)
  • isPersisted() - セッションがディスクに保存されるかどうか