PiPi
快速开始
使用 Pi
  • 简体中文
  • English
快速开始
使用 Pi
  • 简体中文
  • English
  • 从这里开始

    • 快速开始
    • 使用 Pi
    • 提供商
    • 安全
    • 容器化
    • 设置
    • 键位绑定
    • 会话
    • 压缩与分支总结
  • 自定义

    • 扩展
    • 技能
    • 提示词模板
    • 主题
    • Pi 包
    • 自定义模型
    • 自定义提供商
  • 参考

    • 会话文件格式
  • 编程式使用

    • SDK
    • RPC 模式
    • JSON 事件流模式
    • TUI 组件
  • 平台设置

    • Windows 设置
    • Termux(Android)设置
    • tmux 设置
    • 终端设置
    • Shell 别名
  • 开发

    • 开发

会话文件格式

会话以 JSONL(JSON Lines)文件存储。每一行都是一个带有 type 字段的 JSON 对象。会话条目通过 id / parentId 字段组成树结构,从而无需创建新文件也能在原位置进行分支。

文件位置

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

其中 <path> 是将工作目录中的 / 替换为 - 后得到的路径。

删除会话

可以通过删除 ~/.pi/agent/sessions/ 下的 .jsonl 文件来移除会话。

Pi 也支持在 /resume 中交互式删除会话(选择一个会话并按 Ctrl+D,然后确认)。可用时,Pi 会使用 trash CLI 以避免永久删除。

会话版本

会话头部包含版本字段:

  • Version 1:线性条目序列(旧版,加载时自动迁移)
  • Version 2:通过 id / parentId 连接的树结构
  • Version 3:将 hookMessage role 重命名为 custom(扩展统一化)

加载现有会话时,会自动迁移到当前版本(v3)。

源文件

GitHub 源码(pi-mono):

  • packages/coding-agent/src/core/session-manager.ts - 会话条目类型和 SessionManager
  • packages/coding-agent/src/core/messages.ts - 扩展消息类型(BashExecutionMessage、CustomMessage 等)
  • packages/ai/src/types.ts - 基础消息类型(UserMessage、AssistantMessage、ToolResultMessage)
  • packages/agent/src/types.ts - AgentMessage 联合类型

如需查看项目中的 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 编码
  mimeType: string // 例如 "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 毫秒时间戳
}

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 // 工具专属元数据
  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-coding-agent)

interface BashExecutionMessage {
  role: 'bashExecution'
  command: string
  output: string
  exitCode: number | undefined
  cancelled: boolean
  truncated: boolean
  fullOutputPath?: string
  excludeFromContext?: boolean // 对 !! 前缀命令为 true
  timestamp: number
}

interface CustomMessage {
  role: 'custom'
  customType: string // 扩展标识符
  content: string | (TextContent | ImageContent)[]
  display: boolean // 是否显示在 TUI 中
  details?: any // 扩展专属元数据
  timestamp: number
}

interface BranchSummaryMessage {
  role: 'branchSummary'
  summary: string
  fromId: string // 分支来源条目
  timestamp: number
}

interface CompactionSummaryMessage {
  role: 'compactionSummary'
  summary: string
  tokensBefore: number
  timestamp: number
}

AgentMessage 联合类型

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

条目基础结构

所有条目(SessionHeader 除外)都扩展自 SessionEntryBase:

interface SessionEntryBase {
  type: string
  id: string // 8 字符十六进制 ID
  parentId: string | null // 父条目 ID(第一个条目为 null)
  timestamp: string // ISO 时间戳
}

条目类型

SessionHeader

文件的第一行。只包含元数据,不属于树的一部分(没有 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"
}

SessionMessageEntry

对话中的一条消息。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

当用户更改 thinking/reasoning 级别时生成。

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

CompactionEntry

上下文被压缩时创建。用于存储早期消息的摘要。

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

可选字段:

  • details:实现专属数据(例如默认实现中的 { readFiles: string[], modifiedFiles: string[] },或扩展的自定义数据)
  • fromHook:如果由扩展生成则为 true;如果由 pi 生成则为 false / undefined(旧字段名)

BranchSummaryEntry

通过 /tree 切换分支时创建,包含由 LLM 生成的、从离开分支到共同祖先的摘要。用于捕获被放弃路径中的上下文。

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

可选字段:

  • details:文件跟踪数据(默认实现为 { readFiles: string[], modifiedFiles: string[] },或扩展的自定义数据)
  • fromHook:如果由扩展生成则为 true;如果由 pi 生成则为 false / undefined(旧字段名)

CustomEntry

扩展状态持久化。不参与 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 上下文。

CustomMessageEntry

由扩展注入、会参与 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)

LabelEntry

用户在某个条目上定义的书签/标记。

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

将 label 设为 undefined 可清除标签。

SessionInfoEntry

会话元数据(例如用户定义的显示名称)。可通过 /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] ← 当前叶子
                                                            │
                                                            └─ [branch_summary] ─── [user msg] ← 备用分支

构建上下文

buildContextEntries() 会从当前叶子回溯到根,生成活动条目列表,并处理压缩:

  1. 收集路径上的所有条目
  2. 如果路径上存在 CompactionEntry:
    • 先包含压缩条目
    • 然后包含从 firstKeptEntryId 到压缩条目的条目
    • 再包含压缩之后的条目
  3. 保留所选范围内的非消息条目,以便交互模式渲染

buildSessionContext() 基于该条目列表生成发送给 LLM 的消息列表:

  1. 从完整路径中提取当前模型和 thinking level 设置
  2. 将选中的条目转换为消息:
    • message -> 存储的 AgentMessage
    • compaction -> compactionSummary
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> 不生成上下文消息

解析示例

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

SessionManager 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?) - 开始新会话(options:{ parentSession?: string })
  • setSessionFile(path) - 切换到另一个会话文件
  • createBranchedSession(leafId) - 将分支提取为新的会话文件

实例方法 - 追加(全部返回条目 ID)

  • appendMessage(message) - 添加消息
  • appendThinkingLevelChange(level) - 记录 thinking 级别变更
  • 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 的消息、thinkingLevel 和模型
  • getEntries() - 所有条目(不包括头部)
  • getHeader() - 会话头部元数据
  • getSessionName() - 从最新的 session_info 条目获取显示名称
  • getCwd() - 工作目录
  • getSessionDir() - 会话存储目录
  • getSessionId() - 会话 UUID
  • getSessionFile() - 会话文件路径(内存会话为 undefined)
  • isPersisted() - 会话是否已保存到磁盘
最近更新:: 2026/7/6 09:32
Contributors: seepine