Đị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>.jsonlTrong đó <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ò
hookMessagethànhcustom(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):
packages/coding-agent/src/core/session-manager.ts- Các loại mục nhập phiên và Trình quản lý phiênpackages/coding-agent/src/core/messages.ts- Các loại thông báo mở rộng (BashExecutionMessage, CustomMessage, v.v.)packages/ai/src/types.ts- Các loại thông báo cơ bản (UserMessage, AssistantMessage, ToolResultMessage)packages/agent/src/types.ts- Loại kết hợp AgentMessage
Đố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/ và 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óaAgentMessage[]đượ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:truenếu được tạo bởi tiện ích mở rộng,false/undefinednế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ộngfromHook:truenếu được tạo bởi tiện ích mở rộng,false/undefinednế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= ẩndetails: 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 branchXâ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:
- Thu thập tất cả các mục trên đường dẫn
- Nếu có
CompactionEntrytrê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
- 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:
- 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 đủ
- Chuyển đổi các mục đã chọn thành tin nhắn:
message-> được lưu trữAgentMessagecompaction->compactionSummarycộngretainedTailkhi có mặtbranch_summary->branchSummarycustom_message->CustomMessagecustom-> 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ớiSessionManager.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ớiSessionManager.inMemory(cwd?)- Không tồn tại tệpSessionManager.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ụcSessionManager.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áccreateBranchedSession(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ắnappendThinkingLevelChange(level)- Ghi lại sự thay đổi suy nghĩappendModelChange(provider, modelId)- Ghi lại sự thay đổi mô hìnhappendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)- Thêm độ nénappendCustomEntry(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ênappendCustomMessageEntry(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ạigetLeafEntry()- Nhận mục nhập lá hiện tạigetEntry(id)- Nhận mục nhập bằng IDgetBranch(fromId?)- Đi từ mục này đến mục gốcgetTree()- Nhận cấu trúc cây đầy đủgetChildren(parentId)- Nhận con trực tiếpgetLabel(id)- Nhận nhãn để vàobranch(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énbuildSessionContext()- Nhận tin nhắn, cấp độ tư duy và mô hình cho LLMgetEntries()- Tất cả các mục (không bao gồm tiêu đề)getHeader()- Siêu dữ liệu tiêu đề phiêngetSessionName()- Nhận tên hiển thị từ mục nhập session_info mới nhấtgetCwd()- Thư mục làm việcgetSessionDir()- Thư mục lưu trữ phiêngetSessionId()- Phiên UUIDgetSessionFile()- Đườ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