Cấu hình, tùy chỉnh, thiết lập nền tảng và tham chiếu API cho Pi.

Định dạng tệp phiên

Phiên được lưu trữ dưới dạng tệp JSONL (JSON Lines). Mỗi dòng là một đối tượng JSON có trường type. Các mục nhập phiên tạo thành cấu trúc cây thông qua các trường id/parentId, cho phép phân nhánh tại chỗ mà không cần tạo tệp mới.

Vị trí tệp

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

Trong đó <path> là thư mục làm việc với / được thay thế bằng -.

Xóa phiên

Có thể xóa phiên bằng cách xóa tệp .jsonl của chúng trong ~/.pi/agent/sessions/.

Pi cũng hỗ trợ xóa các phiên tương tác từ /resume (chọn một phiên và nhấn Ctrl+D, sau đó xác nhận). Khi có sẵn, pi sử dụng trash CLI để tránh bị xóa vĩnh viễn.

Phiên bản phiên

Phiên có trường phiên bản trong tiêu đề:

  • Phiên bản 1: Trình tự nhập tuyến tính (cũ, tự động di chuyển khi tải)
  • Phiên bản 2: Cấu trúc cây với liên kết id/parentId
  • Phiên bản 3: Đã đổi tên vai trò hookMessage thành custom (thống nhất các tiện ích mở rộng)

Các phiên hiện tại sẽ tự động được di chuyển sang phiên bản hiện tại (v3) khi được tải.

Tệp nguồn

Nguồn trên GitHub (pi-mono):

Đối với các định nghĩa TypeScript trong dự án của bạn, hãy kiểm tra node_modules/@earendil-works/pi-coding-agent/dist/node_modules/@earendil-works/pi-ai/dist/.

Các loại tin nhắn

Các mục trong phiên chứa AgentMessage đối tượng. Hiểu các loại này là điều cần thiết cho các phiên phân tích cú pháp và viết phần mở rộng.

Khối nội dung

Tin nhắn chứa mảng các khối nội dung được gõ:

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

Các loại thông báo cơ sở (từ 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;
  };
}

Loại pi-ai StopReason đã xuất cũng bao gồm "pending", nhưng giá trị đó được dành riêng cho một phần tin nhắn trong các sự kiện phát trực tuyến. Thông báo Terminal done/error thay thế nó bằng lý do hoàn thành trước khi pi tiếp tục thông báo trợ lý, vì vậy "pending" sẽ không bao giờ xuất hiện trong phiên JSONL.

Các loại tin nhắn mở rộng (từ 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;
}

Đại lýTin nhắn Liên minh

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

Căn cứ vào

Tất cả các mục (ngoại trừ SessionHeader) mở rộng 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
}

Các loại mục nhập

Tiêu đề phiên

Dòng đầu tiên của tập tin. Chỉ siêu dữ liệu, không phải là một phần của cây (không có id/parentId).

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

Đối với các phiên có phụ huynh (được tạo qua /fork, /clone hoặc 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"}

PhiênTin nhắnMục nhập

Một tin nhắn trong cuộc trò chuyện. Trường message chứa 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

Được phát ra khi người dùng chuyển đổi mô hình giữa phiên.

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

Cấp độ tư duyThay đổiMục nhập

Phát ra khi người dùng thay đổi mức độ suy nghĩ/lý luận.

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

NénĐầu vào

Được tạo khi bối cảnh được nén. Lưu trữ một bản tóm tắt các tin nhắn trước đó.

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

Các phép nén mới hơn do khai thác tạo nhúng trực tiếp bối cảnh sau nén được giữ lại vào mục nhập, thay vì 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"}]}

Các trường tùy chọn:

  • usage: Việc sử dụng LLM từ việc tạo bản tóm tắt; được bao gồm trong mã thông báo phiên và tổng chi phí
  • retainedTail: Vật liệu hóa AgentMessage[] được giữ lại sau khi nén chặt. Đây chỉ là tùy chọn để tương thích ngược với các phiên cũ hơn. Các phép nén mới hơn do khai thác tạo ra bao gồm nó để chúng tôi có thể xây dựng lại bối cảnh từ điểm kiểm tra này mà không cần xem các mục cũ hơn trước mục nhập nén.
  • details: Dữ liệu dành riêng cho việc triển khai (ví dụ: { readFiles: string[], modifiedFiles: string[] } cho dữ liệu mặc định hoặc dữ liệu tùy chỉnh cho tiện ích mở rộng)
  • fromHook: true nếu được tạo bởi tiện ích mở rộng, false/undefined nếu được tạo bằng pi (tên trường cũ)
  • firstKeptEntryId: để tương thích với định dạng mục nhập cũ.

Chi nhánhTóm tắtMục nhập

Được tạo khi chuyển nhánh thông qua /tree với bản tóm tắt được tạo LLM của nhánh bên trái lên đến tổ tiên chung. Ghi lại bối cảnh từ con đường bị bỏ hoang.

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

Các trường tùy chọn:

  • usage: Việc sử dụng LLM từ việc tạo bản tóm tắt; được bao gồm trong mã thông báo phiên và tổng chi phí
  • details: Dữ liệu theo dõi tệp ({ readFiles: string[], modifiedFiles: string[] }) cho mặc định hoặc dữ liệu tùy chỉnh cho tiện ích mở rộng
  • fromHook: true nếu được tạo bởi tiện ích mở rộng, false/undefined nếu được tạo bằng pi (tên trường cũ)

Mục nhập tùy chỉnh

Sự kiên trì của trạng thái mở rộng. KHÔNG tham gia vào bối cảnh LLM.

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

Sử dụng customType để xác định các mục nhập của tiện ích mở rộng của bạn khi tải lại. Chế độ tương tác có thể hiển thị các mục tùy chỉnh thông qua pi.registerEntryRenderer(customType, renderer), nhưng chúng vẫn không tham gia vào ngữ cảnh LLM.

Tin nhắn tùy chỉnhMục nhập

Các tin nhắn được đưa vào tiện ích mở rộng CÓ tham gia vào ngữ cảnh LLM.

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

Lĩnh vực:

  • content: Chuỗi hoặc (TextContent | ImageContent)[] (giống như UserMessage)
  • display: true = hiển thị trong TUI với kiểu dáng riêng biệt, false = ẩn
  • details: Siêu dữ liệu dành riêng cho tiện ích mở rộng tùy chọn (không được gửi tới LLM)

NhãnNhập

Dấu trang/điểm đánh dấu do người dùng xác định trên một mục nhập.

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

Đặt label thành undefined để xóa nhãn.

Thông tin phiênEntry

Siêu dữ liệu phiên (ví dụ: tên hiển thị do người dùng xác định). Đặt qua /name, --name / -n hoặc pi.setSessionName() trong tiện ích mở rộng.

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

Tên phiên được hiển thị trong bộ chọn phiên (/resume) thay vì thông báo đầu tiên khi được đặt.

Cấu trúc cây

Các mục tạo thành một cây:

  • Mục đầu tiên có parentId: null
  • Mỗi mục nhập tiếp theo trỏ tới mục gốc của nó thông qua parentId
  • Phân nhánh tạo ra các mục con mới từ mục nhập trước đó
  • "Chiếc lá" là vị trí hiện tại trong cây
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
                                                            │
                                                            └─ [branch_summary] ─── [user msg] ← alternate branch

Xây dựng bối cảnh

buildContextEntries() đi từ lá hiện tại đến thư mục gốc, tạo ra danh sách mục đang hoạt động đồng thời tôn trọng việc nén:

  1. Thu thập tất cả các mục trên đường dẫn
  2. Nếu có CompactionEntry trên đường dẫn:
    • Bao gồm mục nén đầu tiên
    • Nếu có retainedTail, nó hoạt động như một điểm kiểm tra độc lập và các mục sau khi nén sẽ được đưa vào
    • Nếu không thì các mục từ firstKeptEntryId đến phần nén sẽ được bao gồm
    • Sau đó, các mục sau khi nén được bao gồm
  3. Giữ nguyên các mục không có tin nhắn trong phạm vi đã chọn để chế độ tương tác có thể hiển thị chúng

buildSessionContext() dựa trên danh sách mục nhập đó để tạo danh sách thông báo cho LLM:

  1. Trích xuất các cài đặt mô hình và cấp độ tư duy hiện tại từ đường dẫn đầy đủ
  2. Chuyển đổi các mục đã chọn thành tin nhắn:
    • message -> được lưu trữ AgentMessage
    • compaction -> compactionSummary cộng retainedTail khi có mặt
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> không có thông báo ngữ cảnh

Điều này làm cho các lần nén mới hơn hoạt động giống như các điểm kiểm tra khép kín. retainedTail chỉ là tùy chọn để các phiên cũ hơn chỉ lưu trữ firstKeptEntryId tiếp tục tải chính xác.

Ví dụ phân tích cú pháp

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

Trình quản lý phiên API

Các phương pháp chính để làm việc với phiên theo chương trình.

Phương pháp tạo tĩnh

  • SessionManager.create(cwd, sessionDir?) - Phiên mới
  • SessionManager.open(path, sessionDir?) - Mở tệp phiên hiện có
  • SessionManager.continueRecent(cwd, sessionDir?) - Tiếp tục gần đây nhất hoặc tạo mới
  • SessionManager.inMemory(cwd?) - Không tồn tại tệp
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Phiên rẽ nhánh từ một dự án khác

Phương pháp liệt kê tĩnh

  • SessionManager.list(cwd, sessionDir?, onProgress?) - Liệt kê các phiên cho một thư mục
  • SessionManager.listAll(onProgress?) - Liệt kê tất cả các phiên trong tất cả các dự án

Phương thức phiên bản - Quản lý phiên

  • newSession(options?) - Bắt đầu phiên mới (tùy chọn: { parentSession?: string })
  • setSessionFile(path) - Chuyển sang tệp phiên khác
  • createBranchedSession(leafId) - Trích xuất nhánh sang tệp phiên mới

Phương thức phiên bản - Đang thêm (tất cả ID mục trả về)

  • appendMessage(message) - Thêm tin nhắn
  • appendThinkingLevelChange(level) - Ghi lại sự thay đổi suy nghĩ
  • appendModelChange(provider, modelId) - Ghi lại sự thay đổi mô hình
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - Thêm độ nén
  • appendCustomEntry(customType, data?) - Trạng thái mở rộng (không có trong ngữ cảnh)
  • appendSessionInfo(name) - Đặt tên hiển thị phiên
  • appendCustomMessageEntry(customType, content, display, details?) - Thông báo mở rộng (trong ngữ cảnh)
  • appendLabelChange(targetId, label) - Đặt/xóa nhãn

Phương thức phiên bản - Điều hướng dạng cây

  • getLeafId() - Vị trí hiện tại
  • getLeafEntry() - Nhận mục nhập lá hiện tại
  • getEntry(id) - Nhận mục nhập bằng ID
  • getBranch(fromId?) - Đi từ mục này đến mục gốc
  • getTree() - Nhận cấu trúc cây đầy đủ
  • getChildren(parentId) - Nhận con trực tiếp
  • getLabel(id) - Nhận nhãn để vào
  • branch(entryId) - Di chuyển lá tới mục nhập trước đó
  • resetLeaf() - Đặt lại lá thành null (trước bất kỳ mục nào)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Chi nhánh có tóm tắt ngữ cảnh

Phương thức phiên bản - Bối cảnh & thông tin

  • buildContextEntries() - Nhận các mục nhập nhánh đang hoạt động có áp dụng tính năng nén
  • buildSessionContext() - Nhận tin nhắn, cấp độ tư duy và mô hình cho LLM
  • getEntries() - Tất cả các mục (không bao gồm tiêu đề)
  • getHeader() - Siêu dữ liệu tiêu đề phiên
  • getSessionName() - Nhận tên hiển thị từ mục nhập session_info mới nhất
  • getCwd() - Thư mục làm việc
  • getSessionDir() - Thư mục lưu trữ phiên
  • getSessionId() - Phiên UUID
  • getSessionFile() - Đường dẫn tệp phiên (không xác định cho trong bộ nhớ)
  • isPersisted() - Phiên có được lưu vào đĩa hay không