pi mono操作开发指南

pi mono 操作开发指南

基于 pi-mono 官方文档(docs/ 目录)全面梳理,覆盖从入门到高级定制的完整知识体系。


第一部分:概览与快速入门

第 1 章 pi mono 简介

1.1 什么是 pi mono

pi mono 是一个极简的终端编码代理(terminal coding harness),它将编码 AI 的能力直接带入你的终端。与其他编码助手不同,pi mono 坚持一个核心理念:适配用户的工作流,而非让用户适应它

pi mono 不是一个大而全的产品,而是一个精巧的运行时平台。它通过 TypeScript Extension、Skill、Prompt Template 和 Theme 四种扩展机制,让开发者能够按需定制代理的行为。你想要的功能,pi mono 让你自己去构建------或者从社区安装第三方 Pi Package。

1.2 核心理念

pi mono 的设计哲学可以总结为:

  • 可扩展优于功能堆砌:核心保持极简,扩展点丰富。
  • 工具而非流程:只提供四个基础工具(read、write、edit、bash),模型决定如何组合它们。
  • 透明而非黑盒:每一次工具调用、每一次推理过程,用户都看得见。
  • Unix 哲学:做好一件事,与其他工具协作。

开发者可以将扩展、技能、提示词模板和主题打包成 Pi Package,通过 npm 或 git 与他人分享。

1.3 四种运行模式

pi mono 提供四种运行模式,适应不同的使用场景:

模式 启动方式 说明
交互模式 pi 完整的 TUI 交互界面,包含编辑器、消息历史、快捷键
打印模式 pi -p "prompt" 单次提示,输出结果后退出。支持 stdin 管道输入
JSON 模式 pi --mode json 输出所有事件为 JSON 行,适合工具集成
RPC 模式 pi --mode rpc 通过 stdin/stdout 的 JSONL 协议通信,用于子进程集成

在打印模式下,pi 也会读取管道 stdin 并将其合并到初始提示中:

bash 复制代码
cat README.md | pi -p "Summarize this text"

1.4 哲学原则------pi 不做什么

pi mono 明确拒绝了一些看似"必备"的功能,这不是因为懒惰,而是因为深思熟虑的设计选择:

功能 pi 的选择 理由
MCP 不内置 用 CLI 工具 + Skill 替代,或用 Extension 添加
子代理 不内置 可用 tmux 生成 pi 实例,或用 Extension 构建
权限弹窗 不内置 在容器中运行,或用 Extension 构建确认流
计划模式 不内置 写入 TODO.md,或用 Extension 构建
内置 TODO 不包含 会混淆模型;用 TODO.md 文件替代
后台 Bash 不包含 使用 tmux,完整可观测性和直接交互

这些选择的共同逻辑:每种"正确做法"取决于具体场景,强行内置等于限制用户。pi mono 把选择权交给用户和社区。

1.5 pi mono 能做什么

在这些限制之下,pi mono 依然强大:

  • 通过 readwriteeditbash 四个工具,模型可以执行几乎所有编码操作
  • 通过 Extension,你可以:添加自定义工具、拦截工具调用、构建子代理、实现 MCP 支持、创建交互式 UI、甚至运行游戏
  • 通过 Skill,你可以将工作流封装为可复用的技能包
  • 通过 Pi Package,你可以将一切打包分享

pi 可以创建 Extension。请它根据你的使用场景构建一个。


第 2 章 快速开始

2.1 安装

pi mono 通过 npm 全局安装:

bash 复制代码
npm install -g @mariozechner/pi-coding-agent

2.2 认证方式

pi mono 支持两种认证方式。

方式一:API Key(环境变量)

bash 复制代码
export ANTHROPIC_API_KEY=sk-ant-...
pi

你也可以将 API Key 存储在认证文件 ~/.pi/agent/auth.json 中:

json 复制代码
{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." }
}

方式二:订阅登录

bash 复制代码
pi
/login  # 然后选择提供商

支持的订阅:Anthropic Claude Pro/Max、OpenAI ChatGPT Plus/Pro (Codex)、GitHub Copilot、Google Gemini CLI、Google Antigravity。

2.3 首次对话

启动 pi 后,直接输入你的问题即可开始对话。默认情况下,pi 向模型提供四个工具:readwriteeditbash。模型会自动使用这些工具来完成你的请求。

例如:

bash 复制代码
pi "List all .ts files in src/"

2.4 默认工具集

pi mono 内置了 7 个工具,但默认只启用其中 4 个:

工具 功能 默认启用
read 读取文件内容
write 创建或覆写文件
edit 精确文本替换
bash 执行 Shell 命令
grep 内容搜索
find 文件查找
ls 目录列表

可通过 --tools 选项启用更多工具:

bash 复制代码
pi --tools read,bash,edit,write,grep,find,ls

或进入只读模式:

bash 复制代码
pi --tools read,grep,find,ls -p "Review the code"

2.5 命令行示例

bash 复制代码
# 交互模式 + 初始提示
pi "List all .ts files in src/"

# 非交互模式
pi -p "Summarize this codebase"

# 非交互模式 + 管道 stdin
cat README.md | pi -p "Summarize this text"

# 指定模型
pi --provider openai --model gpt-4o "Help me refactor"

# 使用 provider/id 模型模式
pi --model openai/gpt-4o "Help me refactor"

# 使用思维级别简写
pi --model sonnet:high "Solve this complex problem"

# 限制模型循环范围
pi --models "claude-*,gpt-4o"

# 只读模式
pi --tools read,grep,find,ls -p "Review the code"

# 高思维级别
pi --thinking high "Solve this complex problem"

# 使用 @ 引用文件
pi @code.ts @test.ts "Review these files"

第 3 章 平台适配

3.1 Kitty 键盘协议

pi mono 使用 Kitty 键盘协议 来可靠地检测修饰键组合。大多数现代终端支持此协议,但某些终端需要额外配置。

3.2 各终端适配方案

Kitty、iTerm2

开箱即用,无需额外配置。

Ghostty

在 Ghostty 配置文件中添加:

复制代码
keybind = alt+backspace=text:\x1b\x7f

配置文件位置:

  • macOS:~/Library/Application Support/com.mitchellh.ghostty/config
  • Linux:~/.config/ghostty/config

WezTerm

创建 ~/.wezterm.lua

lua 复制代码
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.enable_kitty_keyboard = true
return config

VS Code(集成终端)

keybindings.json 中添加以启用 Shift+Enter 多行输入:

json 复制代码
{
  "key": "shift+enter",
  "command": "workbench.action.terminal.sendSequence",
  "args": { "text": "\u001b[13;2u" },
  "when": "terminalFocus"
}

keybindings.json 位置:

  • macOS:~/Library/Application Support/Code/User/keybindings.json
  • Linux:~/.config/Code/User/keybindings.json
  • Windows:%APPDATA%\Code\User\keybindings.json

Windows Terminal

settings.json 中添加:

json 复制代码
{
  "actions": [
    {
      "command": { "action": "sendInput", "input": "\u001b[13;2u" },
      "keys": "shift+enter"
    },
    {
      "command": { "action": "sendInput", "input": "\u001b[13;3u" },
      "keys": "alt+enter"
    }
  ]
}

xfce4-terminal、terminator

这些终端对转义序列支持有限。Ctrl+EnterShift+Enter 无法与普通 Enter 区分。建议切换到支持 Kitty 键盘协议的终端。

IntelliJ IDEA(集成终端)

内置终端对转义序列支持有限。Shift+Enter 无法与 Enter 区分。建议使用独立终端模拟器。

3.3 Windows 注意事项

Windows 用户应注意:

  • Windows Terminal 中 Alt+Enter 默认绑定为全屏切换,会拦截 pi 的 follow-up 快捷键,需重新映射
  • Ctrl+Enter 代替 Shift+Enter 进行多行输入(在 Windows Terminal 中)
  • Alt+V 代替 Ctrl+V 粘贴图片

3.4 tmux 集成

pi 支持在 tmux 中运行。详细集成指南请参考 docs/tmux.md

3.5 Shell 别名

pi 支持配置 shell 别名,详见 docs/shell-aliases.md


第二部分:Provider 与模型

第 4 章 Provider 体系

4.1 概述

pi mono 支持两大类 Provider:订阅制 Provider (通过 OAuth 登录)和 API Key Provider(通过环境变量或认证文件配置)。每个内置 Provider 在 pi 的每次发布中都维护着一份可用模型列表,用户无需手动维护。

4.2 订阅制 Provider

通过 /login 命令进入认证流程,选择对应的 Provider 进行 OAuth 登录:

订阅来源 Provider 说明
Anthropic Claude Pro/Max Anthropic Claude 全系列模型
OpenAI ChatGPT Plus/Pro (Codex) OpenAI GPT 全系列,需订阅
GitHub Copilot GitHub 支持 GitHub Enterprise Server
Google Gemini CLI Google 标准 Gemini 模型,通过 Cloud Code Assist
Google Antigravity Google 含 Gemini 3、Claude、GPT-OSS 模型的沙箱

使用 /logout 清除凭证。Token 存储在 ~/.pi/agent/auth.json 中,过期时自动刷新。

GitHub Copilot 特殊说明:首次登录时按 Enter 使用 github.com,或输入 GitHub Enterprise Server 域名。如果遇到 "model not supported" 错误,需要在 VS Code 中启用:Copilot Chat → 模型选择器 → 选择模型 → "Enable"。

Google Provider 特殊说明

  • Gemini CLI:通过 Cloud Code Assist 访问标准 Gemini 模型
  • Antigravity:包含 Gemini 3、Claude 和 GPT-OSS 模型的沙箱环境
  • 两者均免费(需 Google 账户),受速率限制
  • 付费 Cloud Code Assist:设置 GOOGLE_CLOUD_PROJECT 环境变量

OpenAI Codex 特殊说明:需要 ChatGPT Plus 或 Pro 订阅;仅限个人使用,生产环境应使用 OpenAI Platform API。

4.3 API Key 制 Provider

通过 /login 交互式输入 API Key,或直接设置环境变量:

bash 复制代码
export ANTHROPIC_API_KEY=sk-ant-...
pi

以下是完整 Provider 映射表:

Provider 环境变量 auth.json Key
Anthropic ANTHROPIC_API_KEY anthropic
Azure OpenAI Responses AZURE_OPENAI_API_KEY azure-openai-responses
OpenAI OPENAI_API_KEY openai
DeepSeek DEEPSEEK_API_KEY deepseek
Google Gemini GEMINI_API_KEY google
Mistral MISTRAL_API_KEY mistral
Groq GROQ_API_KEY groq
Cerebras CEREBRAS_API_KEY cerebras
xAI XAI_API_KEY xai
OpenRouter OPENROUTER_API_KEY openrouter
Vercel AI Gateway AI_GATEWAY_API_KEY vercel-ai-gateway
ZAI ZAI_API_KEY zai
OpenCode Zen OPENCODE_API_KEY opencode
OpenCode Go OPENCODE_API_KEY opencode-go
Hugging Face HF_TOKEN huggingface
Fireworks FIREWORKS_API_KEY fireworks
Kimi For Coding KIMI_API_KEY kimi-coding
MiniMax MINIMAX_API_KEY minimax
MiniMax (China) MINIMAX_CN_API_KEY minimax-cn

4.4 认证文件 auth.json

将凭证存储在 ~/.pi/agent/auth.json 中:

json 复制代码
{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "openai": { "type": "api_key", "key": "sk-..." },
  "deepseek": { "type": "api_key", "key": "sk-..." },
  "google": { "type": "api_key", "key": "..." },
  "opencode": { "type": "api_key", "key": "..." },
  "opencode-go": { "type": "api_key", "key": "..." }
}

该文件以 0600 权限创建(仅用户可读写)。auth.json 中的凭证优先于环境变量。

key 字段支持三种格式:

  • Shell 命令 :以 ! 开头,执行后使用 stdout 输出作为 Key(整个进程生命周期内缓存)

    json 复制代码
    { "type": "api_key", "key": "!security find-generic-password -ws 'anthropic'" }
    { "type": "api_key", "key": "!op read 'op://vault/item/credential'" }
  • 环境变量 :使用命名变量的值

    json 复制代码
    { "type": "api_key", "key": "MY_ANTHROPIC_KEY" }
  • 字面值 :直接使用

    json 复制代码
    { "type": "api_key", "key": "sk-ant-..." }

OAuth 凭证在 /login 后也存储在此文件中,由 pi 自动管理。

4.5 云端 Provider

Azure OpenAI

bash 复制代码
export AZURE_OPENAI_API_KEY=...
export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com
# 或使用资源名称代替 Base URL
export AZURE_OPENAI_RESOURCE_NAME=your-resource
# 可选
export AZURE_OPENAI_API_VERSION=2024-02-01
export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4=my-gpt4,gpt-4o=my-gpt4o

Amazon Bedrock

bash 复制代码
# 方式 1:AWS Profile
export AWS_PROFILE=your-profile
# 方式 2:IAM Keys
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
# 方式 3:Bearer Token
export AWS_BEARER_TOKEN_BEDROCK=...
# 可选区域(默认 us-east-1)
export AWS_REGION=us-west-2

还支持 ECS 任务角色(AWS_CONTAINER_CREDENTIALS_*)和 IRSA(AWS_WEB_IDENTITY_TOKEN_FILE)。

对于应用推理配置(其 ARN 中不包含模型名称),设置 AWS_BEDROCK_FORCE_CACHE=1 以启用缓存点:

bash 复制代码
export AWS_BEDROCK_FORCE_CACHE=1
pi --provider amazon-bedrock --model arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123

Google Vertex AI

使用 Application Default Credentials:

bash 复制代码
gcloud auth application-default login
export GOOGLE_CLOUD_PROJECT=your-project
export GOOGLE_CLOUD_LOCATION=us-central1

或设置 GOOGLE_APPLICATION_CREDENTIALS 指向服务账号密钥文件。

4.6 凭证解析顺序

当 pi 解析 Provider 的凭证时,按以下优先级:

  1. CLI --api-key 标志
  2. auth.json 条目(API Key 或 OAuth Token)
  3. 环境变量
  4. 自定义 Provider 的 Key(来自 models.json

4.7 模型选择

启动后可通过以下方式切换模型:

  • /model 命令打开模型选择器
  • Ctrl+L 打开模型选择器
  • Ctrl+P / Shift+Ctrl+P 在作用域模型间向前/向后循环
  • --model 命令行参数指定
  • --models 限制可循环模型范围

第 5 章 自定义模型

5.1 概述

通过 ~/.pi/agent/models.json 文件,你可以添加自定义 Provider 和模型(如 Ollama、vLLM、LM Studio、代理等),而无需编写 Extension。该文件在每次打开 /model 时重新加载,无需重启 pi。

5.2 最简配置

对于本地模型(Ollama、LM Studio、vLLM),只需指定模型 id

json 复制代码
{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [
        { "id": "llama3.1:8b" },
        { "id": "qwen2.5-coder:7b" }
      ]
    }
  }
}

apiKey 是必填项,但 Ollama 会忽略它,所以任意值均可。

某些 OpenAI 兼容服务器不理解推理模型使用的 developer 角色。对于此类 Provider,将 compat.supportsDeveloperRole 设为 false,pi 会将系统提示词作为 system 消息发送。如果服务器也不支持 reasoning_effort,将 compat.supportsReasoningEffort 设为 false

compat 可在 Provider 级别设置(应用到所有模型)或在模型级别设置(覆盖特定模型)。这常见于 Ollama、vLLM、SGLang 及类似的 OpenAI 兼容服务器。

json 复制代码
{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "compat": {
        "supportsDeveloperRole": false,
        "supportsReasoningEffort": false
      },
      "models": [
        {
          "id": "gpt-oss:20b",
          "reasoning": true
        }
      ]
    }
  }
}

5.3 完整配置

当需要特定值时,覆盖默认值:

json 复制代码
{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [
        {
          "id": "llama3.1:8b",
          "name": "Llama 3.1 8B (Local)",
          "reasoning": false,
          "input": ["text"],
          "contextWindow": 128000,
          "maxTokens": 32000,
          "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
        }
      ]
    }
  }
}

5.4 支持的 API 类型

API 说明
openai-completions OpenAI Chat Completions(兼容性最广)
openai-responses OpenAI Responses API
anthropic-messages Anthropic Messages API
google-generative-ai Google Generative AI

api 字段可在 Provider 级别设置(对所有模型的默认值)或在模型级别设置(覆盖 Provider 的 API)。

5.5 Google AI Studio 示例

使用 google-generative-ai 配合 baseUrl 添加 Google AI Studio 的模型:

json 复制代码
{
  "providers": {
    "my-google": {
      "baseUrl": "https://generativelanguage.googleapis.com/v1beta",
      "api": "google-generative-ai",
      "apiKey": "GEMINI_API_KEY",
      "models": [
        {
          "id": "gemma-4-31b-it",
          "name": "Gemma 4 31B",
          "input": ["text", "image"],
          "contextWindow": 262144,
          "reasoning": true
        }
      ]
    }
  }
}

对于 google-generative-ai API 类型,添加自定义模型时 baseUrl 是必需的。

5.6 Provider 配置字段

字段 说明
baseUrl API 端点 URL
api API 类型
apiKey API Key(值解析见下文)
headers 自定义 HTTP 头(值解析见下文)
authHeader 设为 true 自动添加 Authorization: Bearer <apiKey>
models 模型配置数组
modelOverrides 对内置模型的逐模型覆盖

5.7 自定义 Header

json 复制代码
{
  "providers": {
    "custom-proxy": {
      "baseUrl": "https://proxy.example.com/v1",
      "apiKey": "MY_API_KEY",
      "api": "anthropic-messages",
      "headers": {
        "x-portkey-api-key": "PORTKEY_API_KEY",
        "x-secret": "!op read 'op://vault/item/secret'"
      },
      "models": [...]
    }
  }
}

5.8 模型配置字段

字段 必填 默认值 说明
id --- 模型标识符(传递给 API)
name id 人类可读标签,用于匹配和显示
api Provider 的 api 覆盖 Provider 的 API
reasoning false 是否支持扩展思维
input ["text"] 输入类型:["text"]["text", "image"]
contextWindow 128000 上下文窗口大小(tokens)
maxTokens 16384 最大输出 tokens
cost 全零 {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}(每百万 tokens)
compat Provider compat 兼容性覆盖,与 Provider 级别合并

当前行为:

  • /model--list-models 按模型 id 列出条目
  • 配置的 name 用于模型匹配和详情/状态文本

5.9 覆盖内置 Provider

将内置 Provider 路由到代理,无需重新定义模型:

json 复制代码
{
  "providers": {
    "anthropic": {
      "baseUrl": "https://my-proxy.example.com/v1"
    }
  }
}

所有内置 Anthropic 模型仍然可用。现有 OAuth 或 API Key 认证继续工作。

要将自定义模型合并到内置 Provider,包含 models 数组:

json 复制代码
{
  "providers": {
    "anthropic": {
      "baseUrl": "https://my-proxy.example.com/v1",
      "apiKey": "ANTHROPIC_API_KEY",
      "api": "anthropic-messages",
      "models": [...]
    }
  }
}

合并语义:

  • 保留内置模型
  • 自定义模型在 Provider 内按 id 进行 upsert
  • 如果自定义模型 id 匹配内置模型 id,自定义模型替换内置模型
  • 如果自定义模型 id 是新的,则与内置模型并列添加

5.10 逐模型覆盖(modelOverrides)

使用 modelOverrides 自定义特定内置模型,无需替换 Provider 的完整模型列表:

json 复制代码
{
  "providers": {
    "openrouter": {
      "modelOverrides": {
        "anthropic/claude-sonnet-4": {
          "name": "Claude Sonnet 4 (Bedrock Route)",
          "compat": {
            "openRouterRouting": {
              "only": ["amazon-bedrock"]
            }
          }
        }
      }
    }
  }
}

modelOverrides 支持的字段:namereasoninginputcost(部分)、contextWindowmaxTokensheaderscompat

行为说明:

  • modelOverrides 应用到内置 Provider 模型
  • 未知模型 ID 被忽略
  • 可以组合 Provider 级别的 baseUrl/headersmodelOverrides
  • 如果同时定义了 models,自定义模型在内置覆盖之后合并

5.11 OpenAI 兼容性设置

对于部分兼容 OpenAI 的 Provider,使用 compat 字段。Provider 级别的 compat 作为默认值应用于该 Provider 下所有模型;模型级别的 compat 覆盖 Provider 级别的值。

字段 说明
supportsStore Provider 是否支持 store 字段
supportsDeveloperRole 使用 developer 还是 system 角色
supportsReasoningEffort 是否支持 reasoning_effort 参数
reasoningEffortMap 将 pi 思维级别映射到 Provider 特定的 reasoning_effort
supportsUsageInStreaming 是否支持 stream_options: { include_usage: true }(默认 true
maxTokensField 使用 max_completion_tokens 还是 max_tokens
requiresToolResultName 工具结果是否需要 name 字段
requiresAssistantAfterToolResult 工具结果后是否需要插入 assistant 消息
requiresThinkingAsText 将 thinking 块转换为纯文本
requiresReasoningContentOnAssistantMessages 推理启用时在重放的 assistant 消息中包含空 reasoning_content
thinkingFormat 使用 reasoning_effortdeepseekzaiqwenqwen-chat-template
cacheControlFormat 使用 Anthropic 风格的 cache_control 标记
supportsStrictMode 在工具定义中包含 strict 字段
supportsLongCacheRetention 支持长缓存保留(prompt_cache_retention: "24h"
openRouterRouting OpenRouter Provider 路由偏好设置
vercelGatewayRouting Vercel AI Gateway 路由配置

thinkingFormat 说明:

  • deepseek:发送 thinking: { type: "enabled" | "disabled" } 和启用时的 reasoning_effort
  • qwen:DashScope 风格的顶级 enable_thinking
  • qwen-chat-template:本地 Qwen 兼容服务器使用 chat_template_kwargs.enable_thinking

cacheControlFormat: "anthropic" 用于暴露 Anthropic 风格 prompt caching 的 OpenAI 兼容 Provider,在系统提示词、最后一个工具定义以及最后一个用户/助手文本内容上附加 cache_control 标记。

5.12 Anthropic Messages 兼容性

对于使用 api: "anthropic-messages" 的 Provider 或代理,使用 compat 字段控制 Anthropic 细粒度工具流式兼容性:

字段 说明
supportsEagerToolInputStreaming 是否接受每工具 eager_input_streaming。默认 true,设为 false 时省略该字段并使用遗留的 beta header
supportsLongCacheRetention 是否接受 Anthropic 长缓存保留。默认 true

5.13 值解析规则

apiKeyheaders 字段支持三种格式:

  • Shell 命令"!command" 执行后使用 stdout
  • 环境变量:使用命名变量的值
  • 字面值:直接使用

对于 models.json,Shell 命令在请求时解析。pi 故意不为任意命令应用内置 TTL、过期重用或恢复逻辑。不同命令需要不同的缓存和失败策略,pi 无法推断正确的策略。如果命令运行缓慢或受速率限制,请将其包装在自定义脚本中。


第 6 章 自定义 Provider(Extension)

6.1 概述

models.json 不足以满足需求(例如需要 OAuth/SSO 认证、自定义 API 实现、或动态模型发现)时,可以通过 Extension 注册自定义 Provider。

完整的 Provider 扩展示例:

  • examples/extensions/custom-provider-anthropic/ --- Anthropic 代理示例
  • examples/extensions/custom-provider-gitlab-duo/ --- GitLab Duo OAuth 集成
  • examples/extensions/custom-provider-qwen-cli/ --- Qwen CLI 自定义 API

6.2 快速参考

typescript 复制代码
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  // 覆盖现有 Provider 的 baseUrl
  pi.registerProvider("anthropic", {
    baseUrl: "https://proxy.example.com"
  });

  // 注册新 Provider
  pi.registerProvider("my-provider", {
    baseUrl: "https://api.example.com",
    apiKey: "MY_API_KEY",
    api: "openai-completions",
    models: [
      {
        id: "my-model",
        name: "My Model",
        reasoning: false,
        input: ["text", "image"],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
        contextWindow: 128000,
        maxTokens: 4096
      }
    ]
  });
}

Extension 工厂可以是 async 的。对于动态模型发现,应在工厂中获取并注册模型(而非在 session_start 中),因为 pi 会等待异步工厂完成后再继续启动,这样 Provider 在交互式启动期间和 pi --list-models 时都可用。

6.3 覆盖现有 Provider

最简用例:通过代理重定向现有 Provider。

typescript 复制代码
// 所有 Anthropic 请求现在通过你的代理
pi.registerProvider("anthropic", {
  baseUrl: "https://proxy.example.com"
});

// 为 OpenAI 请求添加自定义 Header
pi.registerProvider("openai", {
  headers: {
    "X-Custom-Header": "value"
  }
});

// 同时设置 baseUrl 和 headers
pi.registerProvider("google", {
  baseUrl: "https://ai-gateway.corp.com/google",
  headers: {
    "X-Corp-Auth": "CORP_AUTH_TOKEN"  // 环境变量或字面值
  }
});

当只提供 baseUrl 和/或 headers(无 models)时,该 Provider 的所有现有模型保持不变,仅更换端点。

6.4 注册新 Provider

要添加全新 Provider,需要指定 models 以及必需配置。

如果模型列表来自远程端点,使用异步 Extension 工厂:

typescript 复制代码
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

export default async function (pi: ExtensionAPI) {
  const response = await fetch("http://localhost:1234/v1/models");
  const payload = (await response.json()) as {
    data: Array<{
      id: string;
      name?: string;
      context_window?: number;
      max_tokens?: number;
    }>;
  };

  pi.registerProvider("local-openai", {
    baseUrl: "http://localhost:1234/v1",
    apiKey: "LOCAL_OPENAI_API_KEY",
    api: "openai-completions",
    models: payload.data.map((model) => ({
      id: model.id,
      name: model.name ?? model.id,
      reasoning: false,
      input: ["text"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: model.context_window ?? 128000,
      maxTokens: model.max_tokens ?? 4096,
    })),
  });
}

重要 :当提供 models 时,它替换该 Provider 的所有现有模型。

6.5 注销 Provider

使用 pi.unregisterProvider(name) 移除之前注册的 Provider:

typescript 复制代码
// 先注册
pi.registerProvider("my-llm", {
  baseUrl: "https://api.my-llm.com/v1",
  apiKey: "MY_LLM_API_KEY",
  api: "openai-completions",
  models: [/* ... */]
});

// 后移除
pi.unregisterProvider("my-llm");

注销将移除该 Provider 的动态模型、API Key 回退、OAuth Provider 注册以及自定义流处理程序注册。被覆盖的内置模型或 Provider 行为将恢复。

6.6 API 类型完整列表

API 用途
anthropic-messages Anthropic Claude API 及兼容
openai-completions OpenAI Chat Completions API 及兼容
openai-responses OpenAI Responses API
azure-openai-responses Azure OpenAI Responses API
openai-codex-responses OpenAI Codex Responses API
mistral-conversations Mistral SDK Conversations/Chat 流式
google-generative-ai Google Generative AI API
google-gemini-cli Google Cloud Code Assist API
google-vertex Google Vertex AI API
bedrock-converse-stream Amazon Bedrock Converse API

如果 Provider 使用非标准 API 但期望 Authorization: Bearer <key>,设置 authHeader: true

typescript 复制代码
pi.registerProvider("custom-api", {
  baseUrl: "https://api.example.com",
  apiKey: "MY_API_KEY",
  authHeader: true,
  api: "openai-completions",
  models: [...]
});

6.7 OAuth/SSO 支持

添加 OAuth/SSO 认证,集成到 /login 流程:

typescript 复制代码
import type { OAuthCredentials, OAuthLoginCallbacks } from "@mariozechner/pi-ai";

pi.registerProvider("corporate-ai", {
  baseUrl: "https://ai.corp.com/v1",
  api: "openai-responses",
  models: [...],
  oauth: {
    name: "Corporate AI (SSO)",

    async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
      // 方式 1:浏览器 OAuth
      callbacks.onAuth({ url: "https://sso.corp.com/authorize?..." });

      // 方式 2:设备码流
      callbacks.onDeviceCode({
        userCode: "ABCD-1234",
        verificationUri: "https://sso.corp.com/device"
      });

      // 方式 3:提示输入 Token/Code
      const code = await callbacks.onPrompt({ message: "Enter SSO code:" });

      const tokens = await exchangeCodeForTokens(code);
      return {
        refresh: tokens.refreshToken,
        access: tokens.accessToken,
        expires: Date.now() + tokens.expiresIn * 1000
      };
    },

    async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
      const tokens = await refreshAccessToken(credentials.refresh);
      return {
        refresh: tokens.refreshToken ?? credentials.refresh,
        access: tokens.accessToken,
        expires: Date.now() + tokens.expiresIn * 1000
      };
    },

    getApiKey(credentials: OAuthCredentials): string {
      return credentials.access;
    },

    // 可选:根据用户订阅修改模型
    modifyModels(models, credentials) {
      const region = decodeRegionFromToken(credentials.access);
      return models.map(m => ({
        ...m,
        baseUrl: `https://${region}.ai.corp.com/v1`
      }));
    }
  }
});

注册后,用户可通过 /login corporate-ai 认证。

OAuthLoginCallbacks 接口

方法 说明
onAuth({ url }) 在浏览器中打开 URL(OAuth 重定向)
onDeviceCode({ userCode, verificationUri }) 显示设备码(设备授权流)
onPrompt({ message }) 提示用户输入(手动 Token 输入)

OAuthCredentials 接口

字段 说明
refresh 刷新 Token(用于 refreshToken)
access 访问 Token(getApiKey 返回值)
expires 过期时间戳(毫秒)

凭证持久化在 ~/.pi/agent/auth.json 中。

6.8 自定义流式 API

对于非标准 API 的 Provider,实现 streamSimple。在编写自己的实现前,先研究现有的 Provider 实现:

流式模式

所有 Provider 遵循相同的模式:

typescript 复制代码
import {
  type AssistantMessage,
  type AssistantMessageEventStream,
  type Context,
  type Model,
  type SimpleStreamOptions,
  calculateCost,
  createAssistantMessageEventStream,
} from "@mariozechner/pi-ai";

function streamMyProvider(
  model: Model<any>,
  context: Context,
  options?: SimpleStreamOptions
): AssistantMessageEventStream {
  const stream = createAssistantMessageEventStream();

  (async () => {
    const output: AssistantMessage = {
      role: "assistant",
      content: [],
      api: model.api,
      provider: model.provider,
      model: model.id,
      usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
               cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
      stopReason: "stop",
      timestamp: Date.now(),
    };

    try {
      stream.push({ type: "start", partial: output });
      // ... 处理 API 响应,推送内容事件 ...
      stream.push({ type: "done", reason: output.stopReason as "stop" | "length" | "toolUse", message: output });
      stream.end();
    } catch (error) {
      output.stopReason = options?.signal?.aborted ? "aborted" : "error";
      output.errorMessage = error instanceof Error ? error.message : String(error);
      stream.push({ type: "error", reason: output.stopReason, error: output });
      stream.end();
    }
  })();

  return stream;
}

事件推送顺序

  1. { type: "start", partial } --- 流开始
  2. 内容事件(可重复,跟踪 contentIndex):
    • text_start / text_delta / text_end
    • thinking_start / thinking_delta / thinking_end
    • toolcall_start / toolcall_delta / toolcall_end
  3. { type: "done", reason, message }{ type: "error", reason, error } --- 流结束

注册自定义流函数

typescript 复制代码
pi.registerProvider("my-provider", {
  baseUrl: "https://api.example.com",
  apiKey: "MY_API_KEY",
  api: "my-custom-api",
  models: [...],
  streamSimple: streamMyProvider
});

6.9 测试 Provider 实现

使用与内置 Provider 相同的测试套件验证你的实现。从 packages/ai/test/ 复制并适配这些测试文件:

测试 目的
stream.test.ts 基础流式、文本输出
tokens.test.ts Token 计数和用量
abort.test.ts AbortSignal 处理
empty.test.ts 空/最小响应
context-overflow.test.ts 上下文窗口限制
image-limits.test.ts 图片输入处理
unicode-surrogate.test.ts Unicode 边界情况
tool-call-without-result.test.ts 工具调用边界情况
image-tool-result.test.ts 工具结果中的图片
total-tokens.test.ts 总 Token 计算
cross-provider-handoff.test.ts 跨 Provider 上下文交接

用你的 Provider/模型对运行测试以验证兼容性。


第三部分:交互模式

第 7 章 交互界面

7.1 启动方式

直接运行 pi 即可进入交互模式。pi 会显示启动头、加载资源、然后进入交互循环。

7.2 界面布局

交互界面从上到下由以下部分组成:

区域 说明
启动头 显示快捷键(/hotkeys 查看全部)、加载的 AGENTS.md 文件、提示词模板、技能和扩展
消息区 用户消息、助手回复、工具调用和结果、通知、错误、扩展 UI
编辑器 你输入的地方;边框颜色指示思维级别
页脚 工作目录、会话名称、总 Token/缓存用量、费用、上下文使用率、当前模型

编辑器可以被其他 UI 临时替换,比如内置的 /settings 或来自扩展的自定义 UI(例如一个让 LLM 提问用户回答的结构化问答工具)。扩展还可以替换编辑器,在编辑器上方/下方添加小组件、状态行、自定义页脚或浮层。

7.3 编辑器功能

功能 操作方式
文件引用 输入 @ 模糊搜索项目文件
路径补全 Tab 键完成路径
多行输入 Shift+Enter(Windows Terminal 中 Ctrl+Enter)
图片 Ctrl+V 粘贴(Windows 中 Alt+V),或拖拽到终端
Bash 命令 !command 执行并将输出发送给 LLM,!!command 执行但不发送

标准编辑键绑定:删除单词、撤销等。详见第 9 章快捷键。

7.4 页脚信息

页脚显示的关键信息:

  • 工作目录:当前项目根路径
  • 会话名称 :通过 /name 设置的显示名
  • Token 用量:输入、输出、缓存读写
  • 费用:累计费用
  • 上下文使用率:当前上下文窗口占用百分比
  • 当前模型:Provider/Model ID

7.5 消息类型

在交互界面中,你会看到以下类型的消息:

  • 用户消息:你输入的内容
  • 助手回复:LLM 的响应,包括思考块(thinking blocks)和工具调用
  • 工具调用与结果:模型调用的工具及其返回值
  • 扩展消息 :来自 Extension 的注入消息(customType 标识)
  • 分支摘要 :来自 /tree 导航的分支摘要
  • 压缩摘要:压缩后的上下文摘要
  • 通知:来自 Extension 或系统的通知
  • 错误:API 错误、超时、重试信息

第 8 章 内置命令

8.1 命令系统

在编辑器中输入 / 触发命令。扩展可以注册自定义命令,技能以 /skill:name 形式可用,提示词模板以 /templatename 展开。

8.2 认证命令

命令 说明
/login OAuth 认证流程
/logout 清除凭证

8.3 模型命令

命令 说明
/model 切换模型
/scoped-models 启用/禁用 Ctrl+P 循环的模型

8.4 设置命令

命令 说明
/settings 思维级别、主题、消息投递、Transport 等设置

8.5 会话命令

命令 说明
/resume 从历史会话中选择
/new 开始新会话
/name <name> 设置会话显示名称
/session 显示会话信息(路径、Token、费用)
/tree 跳转到会话树中的任意节点
/fork 从当前分支创建新会话

8.6 上下文与输出命令

命令 说明
/compact [prompt] 手动压缩上下文,可选自定义指令
/copy 复制最后一条助手消息到剪贴板
/export [file] 导出会话为 HTML 文件
/share 上传为私有 GitHub Gist 并生成可分享的 HTML 链接

8.7 系统命令

命令 说明
/reload 重新加载快捷键、扩展、技能、提示词和上下文文件(主题自动热重载)
/hotkeys 显示所有键盘快捷键
/changelog 显示版本历史
/quit 退出 pi

8.8 技能命令

技能注册为 /skill:name 命令:

bash 复制代码
/skill:brave-search           # 加载并执行技能
/skill:pdf-tools extract      # 带参数加载技能

命令后的参数作为 User: <args> 追加到技能内容中。

通过 /settingssettings.json 中的 enableSkillCommands 控制技能命令的注册:

json 复制代码
{
  "enableSkillCommands": true
}

8.9 提示词模板命令

模板注册为 /name 命令,其中 name 是文件名去掉 .md 后缀:

复制代码
/review                           # 展开 review.md
/component Button                 # 带参数展开
/component Button "click handler" # 多参数

第 9 章 快捷键

9.1 快捷键系统

所有键盘快捷键可通过 ~/.pi/agent/keybindings.json 自定义。每个操作可以绑定一个或多个按键。编辑配置文件后,运行 /reload 使更改生效,无需重启会话。

9.2 按键格式

modifier+key 格式,其中修饰键为 ctrlshiftalt(可组合),按键包括:

  • 字母a-z
  • 数字0-9
  • 特殊键escape/escenter/returntabspacebackspacedeleteinsertclearhomeendpageUppageDownupdownleftright
  • 功能键f1-f12
  • 符号 :`````、-=[]\;',./!@#$%^&*()_+|~{}:<>?

修饰键组合:ctrl+shift+xalt+ctrl+xctrl+shift+alt+xctrl+1 等。

9.3 编辑器光标移动

按键 ID 默认值 说明
tui.editor.cursorUp up 向上移动光标
tui.editor.cursorDown down 向下移动光标
tui.editor.cursorLeft left, ctrl+b 向左移动光标
tui.editor.cursorRight right, ctrl+f 向右移动光标
tui.editor.cursorWordLeft alt+left, ctrl+left, alt+b 光标左移一个单词
tui.editor.cursorWordRight alt+right, ctrl+right, alt+f 光标右移一个单词
tui.editor.cursorLineStart home, ctrl+a 移到行首
tui.editor.cursorLineEnd end, ctrl+e 移到行尾
tui.editor.jumpForward ctrl+] 向前跳到字符
tui.editor.jumpBackward ctrl+alt+] 向后跳到字符
tui.editor.pageUp pageUp 向上翻页
tui.editor.pageDown pageDown 向下翻页

9.4 编辑器删除操作

按键 ID 默认值 说明
tui.editor.deleteCharBackward backspace 向后删除字符
tui.editor.deleteCharForward delete, ctrl+d 向前删除字符
tui.editor.deleteWordBackward ctrl+w, alt+backspace 向后删除单词
tui.editor.deleteWordForward alt+d, alt+delete 向前删除单词
tui.editor.deleteToLineStart ctrl+u 删除到行首
tui.editor.deleteToLineEnd ctrl+k 删除到行尾

9.5 输入控制

按键 ID 默认值 说明
tui.input.newLine shift+enter 插入新行
tui.input.submit enter 提交输入
tui.input.tab tab Tab / 自动补全

9.6 Kill Ring

按键 ID 默认值 说明
tui.editor.yank ctrl+y 粘贴最近删除的文本
tui.editor.yankPop alt+y 在 yank 后循环已删除文本
tui.editor.undo ctrl+- 撤销上次编辑

9.7 剪贴板与选择

按键 ID 默认值 说明
tui.input.copy ctrl+c 复制选中内容
tui.select.up up 选择向上
tui.select.down down 选择向下
tui.select.pageUp pageUp 列表中向上翻页
tui.select.pageDown pageDown 列表中向下翻页
tui.select.confirm enter 确认选择
tui.select.cancel escape, ctrl+c 取消选择

9.8 应用控制

按键 ID 默认值 说明
app.interrupt escape 取消/中止
app.clear ctrl+c 清空编辑器
app.exit ctrl+d 退出(编辑器为空时)
app.suspend ctrl+z(Windows 无默认值) 挂起到后台
app.editor.external ctrl+g 在外部编辑器中打开
app.clipboard.pasteImage ctrl+v(Windows 中 alt+v 从剪贴板粘贴图片

9.9 会话管理快捷键

按键 ID 默认值 说明
app.session.new (无) 开始新会话
app.session.tree (无) 打开会话树导航
app.session.fork (无) 分叉当前会话
app.session.resume (无) 打开会话恢复选择器
app.session.togglePath ctrl+p 切换路径显示
app.session.toggleSort ctrl+s 切换排序模式
app.session.toggleNamedFilter ctrl+n 切换仅命名过滤
app.session.rename ctrl+r 重命名会话
app.session.delete ctrl+d 删除会话
app.session.deleteNoninvasive ctrl+backspace 查询为空时删除会话

9.10 模型与思维快捷键

按键 ID 默认值 说明
app.model.select ctrl+l 打开模型选择器
app.model.cycleForward ctrl+p 循环到下一个模型
app.model.cycleBackward shift+ctrl+p 循环到上一个模型
app.thinking.cycle shift+tab 循环思维级别
app.thinking.toggle ctrl+t 折叠/展开思维块

9.11 显示与消息队列快捷键

按键 ID 默认值 说明
app.tools.expand ctrl+o 折叠/展开工具输出
app.message.followUp alt+enter 排队 follow-up 消息
app.message.dequeue alt+up 将排队消息取回编辑器

9.12 Tree 导航快捷键

按键 ID 默认值 说明
app.tree.foldOrUp ctrl+left, alt+left 折叠当前分支段,或跳到上一段起始
app.tree.unfoldOrDown ctrl+right, alt+right 展开当前分支段,或跳到下一段起始
app.tree.editLabel shift+l 编辑选中节点的标签
app.tree.toggleLabelTimestamp shift+t 切换树中标签时间戳
app.tree.filter.default ctrl+d 设置树过滤为默认视图
app.tree.filter.noTools ctrl+t 切换隐藏工具结果的过滤
app.tree.filter.userOnly ctrl+u 切换仅显示用户消息
app.tree.filter.labeledOnly ctrl+l 切换仅显示带标签条目
app.tree.filter.all ctrl+a 切换显示所有条目
app.tree.filter.cycleForward ctrl+o 向前循环过滤
app.tree.filter.cycleBackward shift+ctrl+o 向后循环过滤

9.13 作用域模型选择器快捷键

按键 ID 默认值 说明
app.models.save ctrl+s 保存当前模型选择到设置
app.models.enableAll ctrl+a 启用所有模型(或所有匹配搜索的)
app.models.clearAll ctrl+x 清空所有模型(或所有匹配搜索的)
app.models.toggleProvider ctrl+p 切换当前 Provider 的所有模型
app.models.reorderUp alt+up 将选中模型上移
app.models.reorderDown alt+down 将选中模型下移

9.14 自定义配置示例

Emacs 风格

json 复制代码
{
  "tui.editor.cursorUp": ["up", "ctrl+p"],
  "tui.editor.cursorDown": ["down", "ctrl+n"],
  "tui.editor.cursorLeft": ["left", "ctrl+b"],
  "tui.editor.cursorRight": ["right", "ctrl+f"],
  "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
  "tui.editor.cursorWordRight": ["alt+right", "alt+f"],
  "tui.editor.deleteCharForward": ["delete", "ctrl+d"],
  "tui.editor.deleteCharBackward": ["backspace", "ctrl+h"],
  "tui.input.newLine": ["shift+enter", "ctrl+j"]
}

Vim 风格

json 复制代码
{
  "tui.editor.cursorUp": ["up", "alt+k"],
  "tui.editor.cursorDown": ["down", "alt+j"],
  "tui.editor.cursorLeft": ["left", "alt+h"],
  "tui.editor.cursorRight": ["right", "alt+l"],
  "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
  "tui.editor.cursorWordRight": ["alt+right", "alt+w"]
}

Windows 上的 binditng 示例:每个操作可以有单个按键或按键数组。用户配置覆盖默认值。


第 10 章 消息队列

10.1 概述

pi mono 支持在 Agent 工作时提交排队消息,实现非打断式对话引导。消息队列有两种类型:Steering 消息Follow-up 消息

10.2 Steering 消息

Steering 消息用于在当前 Agent 执行期间"纠正航向":

  • 提交方式 :在 Agent 流式输出时按 Enter
  • 投递时机:当前助手 turn 执行完所有工具调用之后、下一个 LLM 调用之前
  • 典型用途:"不要做 X,改做 Y"、"先检查一下错误再继续"

10.3 Follow-up 消息

Follow-up 消息用于在 Agent 完成后追加后续指令:

  • 提交方式 :按 Alt+Enter
  • 投递时机:Agent 完成所有工作、停止调用工具之后
  • 典型用途:"做完之后,也检查一下 Z"、"然后写个总结"

10.4 排队消息管理

  • Escape:中止并恢复排队消息到编辑器
  • Alt+Up:将排队消息取回编辑器

10.5 投递模式配置

/settingssettings.json 中配置:

json 复制代码
{
  "steeringMode": "one-at-a-time",
  "followUpMode": "one-at-a-time"
}

两种模式:

模式 说明
one-at-a-time 默认。每次完成一个 turn 后投递一条排队消息,等待响应后再处理下一条
all 一次投递所有排队消息

10.6 Transport 设置

transport 选项选择 Provider 传输偏好(适用于支持多种传输的 Provider):

json 复制代码
{
  "transport": "sse"
}
说明
sse Server-Sent Events(默认)
websocket WebSocket
auto 由 Provider 决定

10.7 Windows 注意事项

Windows Terminal 默认将 Alt+Enter 绑定为全屏切换,会拦截 pi 的 follow-up 快捷键。需要在 Windows Terminal 的 settings.json 中重新映射:

json 复制代码
{
  "actions": [
    {
      "command": { "action": "sendInput", "input": "\u001b[13;3u" },
      "keys": "alt+enter"
    }
  ]
}

第四部分:会话管理

第 11 章 会话文件格式

11.1 JSONL 格式概述

会话以 JSONL(JSON Lines)格式存储,每个条目是一行 JSON 对象,具有 type 字段。会话条目通过 id/parentId 字段形成树形结构,支持原地分支而无需创建新文件。

11.2 文件位置

会话文件存储在:

复制代码
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl

其中 <path> 是工作目录路径(/ 替换为 -)。

可通过 --session-dir CLI 标志或 settings.json 中的 sessionDir 字段自定义存储目录:

json 复制代码
{ "sessionDir": ".pi/sessions" }

当多个来源指定了会话目录时,--session-dir CLI 标志优先于 settings.json 中的 sessionDir

11.3 会话版本

会话有三个版本,旧版本自动迁移:

版本 说明
Version 1 线性条目序列(遗留格式,加载时自动迁移)
Version 2 树形结构,id/parentId 链接
Version 3 重命名 hookMessage 角色为 custom(扩展统一)

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

11.4 删除会话

会话可通过删除其 .jsonl 文件来移除。pi 还支持从 /resume 交互式删除会话(选中会话后按 Ctrl+D,然后确认)。当可用时,pi 使用 trash CLI 避免永久删除。

11.5 创建与切换会话

bash 复制代码
pi -c                  # 继续最近会话
pi -r                  # 浏览并选择历史会话
pi --no-session        # 临时模式(不保存)
pi --session <path>    # 使用指定会话文件或 ID
pi --fork <path>       # Fork 指定会话文件或 ID 到新会话

第 12 章 消息类型

12.1 内容块

消息包含类型化的内容块数组:

TextContent

typescript 复制代码
interface TextContent {
  type: "text";
  text: string;
}

ImageContent

typescript 复制代码
interface ImageContent {
  type: "image";
  data: string;      // base64 编码
  mimeType: string;  // 例如 "image/jpeg", "image/png"
}

ThinkingContent

typescript 复制代码
interface ThinkingContent {
  type: "thinking";
  thinking: string;
}

ToolCall

typescript 复制代码
interface ToolCall {
  type: "toolCall";
  id: string;
  name: string;
  arguments: Record<string, any>;
}

12.2 基础消息类型(来自 pi-ai)

UserMessage

typescript 复制代码
interface UserMessage {
  role: "user";
  content: string | (TextContent | ImageContent)[];
  timestamp: number;  // Unix 毫秒
}

AssistantMessage

typescript 复制代码
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;
}

ToolResultMessage

typescript 复制代码
interface ToolResultMessage {
  role: "toolResult";
  toolCallId: string;
  toolName: string;
  content: (TextContent | ImageContent)[];
  details?: any;
  isError: boolean;
  timestamp: number;
}

Usage

typescript 复制代码
interface Usage {
  input: number;
  output: number;
  cacheRead: number;
  cacheWrite: number;
  totalTokens: number;
  cost: {
    input: number;
    output: number;
    cacheRead: number;
    cacheWrite: number;
    total: number;
  };
}

12.3 扩展消息类型(来自 pi-coding-agent)

BashExecutionMessage

typescript 复制代码
interface BashExecutionMessage {
  role: "bashExecution";
  command: string;
  output: string;
  exitCode: number | undefined;
  cancelled: boolean;
  truncated: boolean;
  fullOutputPath?: string;
  excludeFromContext?: boolean;  // !! 前缀命令时为 true
  timestamp: number;
}

CustomMessage

typescript 复制代码
interface CustomMessage {
  role: "custom";
  customType: string;            // Extension 标识符
  content: string | (TextContent | ImageContent)[];
  display: boolean;              // 是否在 TUI 中显示
  details?: any;                 // Extension 特定元数据
  timestamp: number;
}

BranchSummaryMessage

typescript 复制代码
interface BranchSummaryMessage {
  role: "branchSummary";
  summary: string;
  fromId: string;                // 分支来源的 Entry ID
  timestamp: number;
}

CompactionSummaryMessage

typescript 复制代码
interface CompactionSummaryMessage {
  role: "compactionSummary";
  summary: string;
  tokensBefore: number;
  timestamp: number;
}

12.4 AgentMessage 联合类型

typescript 复制代码
type AgentMessage =
  | UserMessage
  | AssistantMessage
  | ToolResultMessage
  | BashExecutionMessage
  | CustomMessage
  | BranchSummaryMessage
  | CompactionSummaryMessage;

第 13 章 Entry 类型

13.1 Entry 基础

所有 Entry(除 SessionHeader)继承 SessionEntryBase

typescript 复制代码
interface SessionEntryBase {
  type: string;
  id: string;             // 8 字符十六进制 ID
  parentId: string | null;  // 父 Entry ID(首条 Entry 为 null)
  timestamp: string;      // ISO 时间戳
}

13.2 SessionHeader

文件第一行。仅元数据,不属于树结构(无 id/parentId)。

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

如果会话有父会话(通过 /fork/clonenewSession({ parentSession }) 创建):

json 复制代码
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}

13.3 SessionMessageEntry

对话中的消息。message 字段包含 AgentMessage

json 复制代码
{"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}}

13.4 ModelChangeEntry

用户在会话中切换模型时记录。

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

13.5 ThinkingLevelChangeEntry

用户更改思维/推理级别时记录。

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

13.6 CompactionEntry

上下文压缩时创建。存储较早消息的摘要。

json 复制代码
{"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:如果由 Extension 生成则为 true(遗留字段名)

13.7 BranchSummaryEntry

通过 /tree 切换分支时创建。捕获被放弃路径的上下文。

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

可选字段:

  • details:文件跟踪数据
  • fromHook:Extension 生成标记

13.8 CustomEntry

扩展状态持久化。不参与 LLM 上下文

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

使用 customType 在重载时识别你的扩展条目。

13.9 CustomMessageEntry

扩展注入的参与 LLM 上下文的消息。

json 复制代码
{"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)[]
  • displaytrue = 在 TUI 中以独特样式显示,false = 隐藏
  • details:可选扩展元数据(不发送给 LLM)

13.10 LabelEntry

用户定义的书签/标记。

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

设置 labelundefined 可清除标签。

13.11 SessionInfoEntry

会话元数据(如用户定义的显示名称)。通过 /name 命令或扩展中的 pi.setSessionName() 设置。

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

会话名称在会话选择器(/resume)中显示,替代第一条消息。


第 14 章 树形结构与会话分支

14.1 树形结构原理

会话条目形成树形结构:

  • 首条条目 parentId: null

  • 每条后续条目通过 parentId 指向其父条目

  • 分支从较早的条目创建新的子条目

  • "叶"(leaf)是树中的当前位置

    [user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← 当前叶

    └─ [branch_summary] ─── [user msg] ← 替代分支

14.2 上下文构建

buildSessionContext() 从当前叶节点回溯到根节点,为 LLM 生成消息列表:

  1. 收集路径上的所有条目
  2. 提取当前模型和思维级别设置
  3. 如果路径上有 CompactionEntry:先发送摘要,然后是 firstKeptEntryId 之后的消息
  4. BranchSummaryEntryCustomMessageEntry 转换为适当的消息格式

14.3 /tree 导航概述

/tree 命令提供会话历史的树形导航。与 /fork 的区别:

特性 /fork /tree
视图 用户消息扁平列表 完整树结构
动作 提取路径到新会话文件 同一会话中更改叶节点
摘要 从不 可选(提示用户)
事件 session_before_fork / session_start (reason: "fork") session_before_tree / session_tree

14.4 Tree UI

Tree UI 显示树形结构:

复制代码
├─ user: "Hello, can you help..."
│  └─ assistant: "Of course! I can..."
│     ├─ user: "Let's try approach A..."
│     │  └─ assistant: "For approach A..."
│     │     └─ [compaction: 12k tokens]
│     │        └─ user: "That worked..."  ← active
│     └─ user: "Actually, approach B..."
│        └─ assistant: "For approach B..."

高度为终端高度的一半,当前叶节点标记为 ← active

14.5 Tree 控制键

按键 动作
↑/↓ 导航(深度优先顺序)
←/→ 向上/向下翻页
Ctrl+←/Ctrl+→ 或 Alt+←/Alt+→ 折叠/展开分支段并跳转
Shift+L 设置或清除选中节点的标签
Shift+T 切换标签时间戳显示
Enter 选中节点
Escape/Ctrl+C 取消
Ctrl+U 切换:仅用户消息
Ctrl+O 切换:显示所有(含 custom/label 条目)

折叠行为

  • Ctrl+←Alt+←:如果当前节点可折叠则折叠;否则跳到上一个可见分支段起始
  • Ctrl+→Alt+→:如果当前节点已折叠则展开;否则跳到下一个可见分支段起始或分支末端
  • 可折叠节点:根和分支段起始点
  • 折叠的分支显示 ,展开的显示
  • 搜索和过滤更改重置所有折叠

14.6 选择行为

用户消息或自定义消息

  1. 叶节点设置为选中节点的父节点 (根消息为 null
  2. 消息文本放入编辑器供重新提交
  3. 用户编辑后提交,创建新分支

非用户消息(助手、压缩等)

  1. 叶节点设置为选中节点
  2. 编辑器保持为空
  3. 用户从该点继续

选中根用户消息

  1. 叶重置为 null(空对话)
  2. 消息文本放入编辑器
  3. 用户实际从头开始

14.7 分支摘要

切换分支时,用户有三个选项:

  1. 无摘要 --- 立即切换,不总结
  2. 总结 --- 使用默认提示词生成摘要
  3. 带自定义提示词的总结 --- 打开编辑器输入额外聚焦指令

摘要范围:从旧叶节点回溯到与目标的共同祖先,遇到压缩节点时停止。

摘要存储 :以 BranchSummaryEntry 形式存储:

typescript 复制代码
interface BranchSummaryEntry {
  type: "branch_summary";
  id: string;
  parentId: string;      // 新叶位置
  timestamp: string;
  fromId: string;        // 放弃的旧叶
  summary: string;       // LLM 生成的摘要
  details?: unknown;     // 可选扩展数据
}

14.8 过滤模式

Tree 导航支持多种过滤模式:

模式 说明
default 隐藏 labelcustom 条目
no-tools 隐藏工具结果
user-only 仅显示用户消息
labeled-only 仅显示带标签条目
all 显示所有条目

通过 Ctrl+O 循环切换过滤模式。

14.9 标签系统

  • Shift+L:设置或清除选中节点的标签
  • Shift+T:切换标签时间戳显示
  • 标签在 Tree 选择器中显示为 [label-name]
  • Shift+T 后会在带标签节点旁显示最新的标签更改时间戳
  • 在每个分支点,活动子树首先显示;其他同级分支按时间戳排序(最旧优先)

第 15 章 压缩(Compaction)

15.1 概述

LLM 的上下文窗口有限。当对话变得过长时,pi mono 使用压缩(compaction)来总结较早的内容,同时保留最近的工作。

15.2 触发条件

压缩在以下情况触发:

复制代码
contextTokens > contextWindow - reserveTokens

默认 reserveTokens 为 16384 个 token(可在 ~/.pi/agent/settings.json<project-dir>/.pi/settings.json 中配置),为 LLM 响应保留空间。

也可通过 /compact [instructions] 手动触发,可选的指令用于聚焦摘要。

15.3 工作流程

  1. 查找切割点 :从最新消息向后回溯,累积 token 估计直到达到 keepRecentTokens(默认 20k)

  2. 提取消息:收集从上一个保留边界(或会话开始)到切割点的消息

  3. 生成摘要:调用 LLM 生成结构化摘要

  4. 追加条目 :保存 CompactionEntry(包含摘要和 firstKeptEntryId

  5. 重载 :会话重载,使用摘要 + firstKeptEntryId 之后的消息

    压缩前:

    entry: 0 1 2 3 4 5 6 7 8 9
    ┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┐
    │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
    └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
    └────────┬───────┘ └──────────────┬──────────────┘
    messagesToSummarize kept messages

    firstKeptEntryId (entry 4)

    压缩后(追加新条目):

    entry: 0 1 2 3 4 5 6 7 8 9 10
    ┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┐
    │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
    └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
    └──────────┬──────┘ └──────────────────────┬───────────────────┘
    不发送给 LLM 发送给 LLM

    从 firstKeptEntryId 开始

LLM 看到的内容:

复制代码
  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
       ↑         ↑      └─────────────────┬────────────────┘
    提示词   来自压缩              firstKeptEntryId 之后的消息

15.4 切割点规则

有效的切割点:

  • 用户消息
  • 助手消息
  • BashExecution 消息
  • 自定义消息(custom_message、branch_summary)

永远不要在工具结果处切割(它们必须与其工具调用在一起)。

15.5 Split Turn

一个 turn 从用户消息开始,包含所有助手响应和工具调用,直到下一个用户消息。通常压缩在 turn 边界处切割。

当单个 turn 超过 keepRecentTokens 时,切割点落在 turn 中间的助手消息处。这称为"split turn":

复制代码
Split turn(单 turn 超预算):

  entry:  0     1     2      3     4      5      6     7      8
        ┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐
        │ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │
        └─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘
                ↑                                     ↑
         turnStartIndex = 1                  firstKeptEntryId = 7
                │                                     │
                └──── turnPrefixMessages (1-6) ───────┘
                                                      └── kept (7-8)

  isSplitTurn = true
  messagesToSummarize = []  (之前无完整 turn)
  turnPrefixMessages = [usr, ass, tool, ass, tool, tool]

对于 split turn,pi 生成两个摘要并合并:

  1. 历史摘要:之前的上下文(如有)
  2. Turn 前缀摘要:split turn 的前半部分

15.6 摘要格式

压缩使用结构化摘要格式:

markdown 复制代码
## Goal
[用户试图完成的目标]

## Constraints & Preferences
- [用户提到的需求]

## Progress
### Done
- [x] [已完成的任务]

### In Progress
- [ ] [当前工作]

### Blocked
- [问题(如有)]

## Key Decisions
- **[决策]**: [理由]

## Next Steps
1. [接下来应该发生什么]

## Critical Context
- [继续工作所需的数据]

<read-files>
path/to/file1.ts
path/to/file2.ts
</read-files>

<modified-files>
path/to/changed.ts
</modified-files>

15.7 消息序列化

摘要生成前,消息通过 serializeConversation() 转换为文本:

复制代码
[User]: 用户说的话
[Assistant thinking]: 内部推理
[Assistant]: 响应文本
[Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
[Tool result]: 工具输出

工具结果在序列化时截断为 2000 个字符。超过限制的内容被替换为标记,指示截断了多少字符。这使摘要请求保持在合理的 token 预算内。

15.8 累积文件跟踪

压缩和分支摘要都累积地跟踪文件。当生成摘要时,pi 从以下来源提取文件操作:

  • 被摘要消息中的工具调用
  • 前一个压缩或分支摘要的 details(如有)

这意味着文件跟踪在多次压缩或嵌套分支摘要中累积,保留读取和修改文件的完整历史。

15.9 压缩设置

~/.pi/agent/settings.json<project-dir>/.pi/settings.json 中配置:

json 复制代码
{
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000
  }
}
设置 默认值 说明
enabled true 启用自动压缩
reserveTokens 16384 为 LLM 响应保留的 tokens
keepRecentTokens 20000 保留的最近 tokens(不被摘要)

"enabled": false 可禁用自动压缩。仍可通过 /compact 手动压缩。

15.10 自定义压缩(Extension)

扩展可以拦截并自定义压缩。监听 session_before_compact 事件:

typescript 复制代码
pi.on("session_before_compact", async (event, ctx) => {
  const { preparation, branchEntries, customInstructions, signal } = event;

  // preparation.messagesToSummarize --- 待摘要的消息
  // preparation.turnPrefixMessages --- split turn 前缀
  // preparation.previousSummary --- 前一个压缩摘要
  // preparation.fileOps --- 提取的文件操作
  // preparation.tokensBefore --- 压缩前上下文 tokens
  // preparation.firstKeptEntryId --- 保留消息起始点
  // preparation.settings --- 压缩设置

  // 取消:
  return { cancel: true };

  // 自定义摘要:
  return {
    compaction: {
      summary: "你的摘要...",
      firstKeptEntryId: preparation.firstKeptEntryId,
      tokensBefore: preparation.tokensBefore,
      details: { /* 自定义数据 */ },
    }
  };
});

使用 serializeConversationconvertToLlm 将消息转换为文本后发送给自定义模型:

typescript 复制代码
import { convertToLlm, serializeConversation } from "@mariozechner/pi-coding-agent";

pi.on("session_before_compact", async (event, ctx) => {
  const { preparation } = event;
  const conversationText = serializeConversation(
    convertToLlm(preparation.messagesToSummarize)
  );
  const summary = await myModel.summarize(conversationText);
  return {
    compaction: {
      summary,
      firstKeptEntryId: preparation.firstKeptEntryId,
      tokensBefore: preparation.tokensBefore,
    }
  };
});

第 16 章 分支摘要

16.1 触发条件

当使用 /tree 导航到不同分支时,pi 提供摘要离开的分支的选项。这将左分支的上下文注入到新分支中。

16.2 工作流程

  1. 查找共同祖先:旧位置和新位置共享的最深节点

  2. 收集条目:从旧叶节点回溯到共同祖先

  3. 预算分配:在 token 预算内包含消息(最新优先)

  4. 生成摘要:使用结构化格式调用 LLM

  5. 追加条目 :在导航点保存 BranchSummaryEntry

    导航前的树:

    复制代码
          ┌─ B ─ C ─ D (旧叶,被放弃)
     A ───┤
          └─ E ─ F (目标)

    共同祖先:A
    待摘要条目:B, C, D

    带摘要的导航后:

    复制代码
          ┌─ B ─ C ─ D ─ [B,C,D 的摘要]
     A ───┤
          └─ E ─ F (新叶)

16.3 BranchSummaryEntry 结构

typescript 复制代码
interface BranchSummaryEntry<T = unknown> {
  type: "branch_summary";
  id: string;
  parentId: string;
  timestamp: number;
  summary: string;
  fromId: string;      // 导航来源的 Entry ID
  fromHook?: boolean;  // Extension 生成标记(遗留字段名)
  details?: T;         // 实现特定数据
}

16.4 分支摘要设置

json 复制代码
{
  "branchSummary": {
    "reserveTokens": 16384,
    "skipPrompt": false
  }
}
设置 默认值 说明
reserveTokens 16384 分支摘要保留的 tokens
skipPrompt false 跳过"总结分支?"提示(默认不生成摘要)

16.5 自定义分支摘要(Extension)

监听 session_before_tree 事件:

typescript 复制代码
pi.on("session_before_tree", async (event, ctx) => {
  const { preparation, signal } = event;

  // preparation.targetId --- 目标位置
  // preparation.oldLeafId --- 当前位置
  // preparation.commonAncestorId --- 共享祖先
  // preparation.entriesToSummarize --- 将被摘要的条目
  // preparation.userWantsSummary --- 用户是否选择生成摘要

  // 取消导航:
  return { cancel: true };

  // 提供自定义摘要(仅在 userWantsSummary 为 true 时使用):
  if (preparation.userWantsSummary) {
    return {
      summary: {
        summary: "你的摘要...",
        details: { /* 自定义数据 */ },
      }
    };
  }
});

第 17 章 SessionManager API

17.1 概述

SessionManager 提供了完整的会话操作 API,用于程序化管理会话。

17.2 静态创建方法

方法 说明
SessionManager.create(cwd, sessionDir?) 创建新会话
SessionManager.open(path, sessionDir?) 打开现有会话文件
SessionManager.continueRecent(cwd, sessionDir?) 继续最近会话或创建新会话
SessionManager.inMemory(cwd?) 内存会话(不持久化)
SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) 从另一项目的会话 fork

17.3 静态列表方法

方法 说明
SessionManager.list(cwd, sessionDir?, onProgress?) 列出目录的会话
SessionManager.listAll(onProgress?) 列出所有项目的会话

17.4 实例方法 --- 会话管理

方法 说明
newSession(options?) 开始新会话(选项:{ parentSession?: string }
setSessionFile(path) 切换到不同会话文件
createBranchedSession(leafId) 提取分支到新会话文件

17.5 实例方法 --- 追加条目

所有追加方法返回条目 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) 设置/清除标签

17.6 实例方法 --- 树导航

方法 说明
getLeafId() 当前位置
getLeafEntry() 获取当前叶条目
getEntry(id) 按 ID 获取条目
getBranch(fromId?) 从条目回溯到根
getTree() 获取完整树结构
getChildren(parentId) 获取直接子条目
getLabel(id) 获取条目的标签
branch(entryId) 将叶移到较早的条目
resetLeaf() 重置叶为 null(所有条目之前)
branchWithSummary(id, summary, details?, fromHook?) 带上下文摘要的分支

17.7 实例方法 --- 上下文与信息

方法 说明
buildSessionContext() 获取消息、思维级别和模型
getEntries() 所有条目(不含头部)
getHeader() 会话头部元数据
getSessionName() 从最新 session_info 获取显示名称
getCwd() 工作目录
getSessionDir() 会话存储目录
getSessionId() 会话 UUID
getSessionFile() 会话文件路径(内存会话为 undefined)
isPersisted() 是否保存到磁盘

17.8 解析示例

使用 TypeScript 解析会话文件:

typescript 复制代码
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;
  }
}

第五部分:设置与上下文

第 18 章 设置系统

18.1 设置文件位置与作用域

pi mono 使用 JSON 设置文件,项目设置覆盖全局设置:

位置 作用域
~/.pi/agent/settings.json 全局(所有项目)
.pi/settings.json 项目(当前目录)

直接编辑文件或通过 /settings 修改常用选项。

18.2 项目覆盖合并规则

项目设置(.pi/settings.json)覆盖全局设置。嵌套对象合并键:

json 复制代码
// ~/.pi/agent/settings.json (全局)
{
  "theme": "dark",
  "compaction": { "enabled": true, "reserveTokens": 16384 }
}

// .pi/settings.json (项目)
{
  "compaction": { "reserveTokens": 8192 }
}

// 合并结果
{
  "theme": "dark",
  "compaction": { "enabled": true, "reserveTokens": 8192 }
}

18.3 模型与思维级别

设置 类型 默认值 说明
defaultProvider string --- 默认 Provider(如 "anthropic""openai"
defaultModel string --- 默认模型 ID
defaultThinkingLevel string --- "off""minimal""low""medium""high""xhigh"
hideThinkingBlock boolean false 在输出中隐藏思维块
thinkingBudgets object --- 每个思维级别的自定义 token 预算

thinkingBudgets 配置

json 复制代码
{
  "thinkingBudgets": {
    "minimal": 1024,
    "low": 4096,
    "medium": 10240,
    "high": 32768
  }
}

18.4 UI 与显示

设置 类型 默认值 说明
theme string "dark" 主题名称("dark""light" 或自定义)
quietStartup boolean false 隐藏启动头
collapseChangelog boolean false 更新后显示精简 changelog
enableInstallTelemetry boolean true 在 changelog 检测到更新后发送匿名版本/更新 ping
doubleEscapeAction string "tree" 双 Escape 操作:"tree""fork""none"
treeFilterMode string "default" /tree 默认过滤:"default""no-tools""user-only""labeled-only""all"
editorPaddingX number 0 输入编辑器水平填充(0-3)
autocompleteMaxVisible number 5 自动补全下拉最大可见项(3-20)
showHardwareCursor boolean false 显示终端光标

18.5 压缩设置

设置 类型 默认值 说明
compaction.enabled boolean true 启用自动压缩
compaction.reserveTokens number 16384 为 LLM 响应保留的 tokens
compaction.keepRecentTokens number 20000 保留的最近 tokens
json 复制代码
{
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000
  }
}

18.6 分支摘要设置

设置 类型 默认值 说明
branchSummary.reserveTokens number 16384 分支摘要保留的 tokens
branchSummary.skipPrompt boolean false 跳过 /tree 导航时的"总结分支?"提示

18.7 重试设置

设置 类型 默认值 说明
retry.enabled boolean true 启用自动 Agent 级重试
retry.maxRetries number 3 最大 Agent 级重试次数
retry.baseDelayMs number 2000 Agent 级指数退避基础延迟(2s、4s、8s)
retry.provider.timeoutMs number SDK 默认 Provider/SDK 请求超时(毫秒)
retry.provider.maxRetries number SDK 默认 Provider/SDK 重试次数
retry.provider.maxRetryDelayMs number 60000 服务端请求最大延迟上限(60s)

当 Provider 请求的重试延迟超过 retry.provider.maxRetryDelayMs(例如 Google 的"配额将在 5 小时后重置"),请求立即失败并显示说明性错误,而不是静默等待。设为 0 可禁用上限。

json 复制代码
{
  "retry": {
    "enabled": true,
    "maxRetries": 3,
    "baseDelayMs": 2000,
    "provider": {
      "timeoutMs": 3600000,
      "maxRetries": 0,
      "maxRetryDelayMs": 60000
    }
  }
}

18.8 消息投递设置

设置 类型 默认值 说明
steeringMode string "one-at-a-time" Steering 消息发送方式:"all""one-at-a-time"
followUpMode string "one-at-a-time" Follow-up 消息发送方式:"all""one-at-a-time"
transport string "sse" Provider 传输偏好:"sse""websocket""auto"

18.9 终端与图片设置

设置 类型 默认值 说明
terminal.showImages boolean true 在终端显示图片(如支持)
terminal.imageWidthCells number 60 内联图片首选宽度(终端单元)
terminal.clearOnShrink boolean false 内容收缩时清空空行(可能闪烁)
images.autoResize boolean true 将图片调整到最大 2000x2000
images.blockImages boolean false 阻止所有图片发送给 LLM

18.10 Shell 设置

设置 类型 默认值 说明
shellPath string --- 自定义 Shell 路径(如 Windows 上的 Cygwin)
shellCommandPrefix string --- 每条 bash 命令的前缀(如 "shopt -s expand_aliases"
npmCommand string[] --- npm 包管理操作使用的命令(如 ["mise", "exec", "node@20", "--", "npm"]
json 复制代码
{
  "npmCommand": ["mise", "exec", "node@20", "--", "npm"]
}

npmCommand 用于所有 npm 包管理操作,包括 npm root -g、安装、卸载和 git 包内的依赖安装。使用 argv 风格条目。配置 npmCommand 时,git 包依赖安装使用普通 install 以避免包装器中的 npm 特定标志。

18.11 会话目录设置

设置 类型 默认值 说明
sessionDir string --- 会话文件存储目录。接受绝对/相对路径和 ~
json 复制代码
{ "sessionDir": ".pi/sessions" }

18.12 模型循环设置

设置 类型 默认值 说明
enabledModels string[] --- Ctrl+P 循环的模型模式(与 --models CLI 格式相同)
json 复制代码
{
  "enabledModels": ["claude-*", "gpt-4o", "gemini-2*"]
}

18.13 Markdown 设置

设置 类型 默认值 说明
markdown.codeBlockIndent string " " 代码块缩进

18.14 资源设置

这些设置定义从哪里加载扩展、技能、提示词和主题。

~/.pi/agent/settings.json 中的路径相对于 ~/.pi/agent 解析。.pi/settings.json 中的路径相对于 .pi 解析。支持绝对路径和 ~

设置 类型 默认值 说明
packages array [] npm/git 包
extensions string[] [] 本地扩展文件路径或目录
skills string[] [] 本地技能文件路径或目录
prompts string[] [] 本地提示词模板路径或目录
themes string[] [] 本地主题路径或目录
enableSkillCommands boolean true 将技能注册为 /skill:name 命令

数组支持 glob 模式和排除。使用 !pattern 排除。使用 +path 强制包含精确路径,-path 强制排除精确路径。

packages 字符串形式:加载包中的所有资源

json 复制代码
{ "packages": ["pi-skills", "@org/my-extension"] }

packages 对象形式:过滤加载哪些资源

json 复制代码
{
  "packages": [
    {
      "source": "pi-skills",
      "skills": ["brave-search", "transcribe"],
      "extensions": []
    }
  ]
}

18.15 完整配置示例

json 复制代码
{
  "defaultProvider": "anthropic",
  "defaultModel": "claude-sonnet-4-20250514",
  "defaultThinkingLevel": "medium",
  "theme": "dark",
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000
  },
  "retry": {
    "enabled": true,
    "maxRetries": 3
  },
  "enabledModels": ["claude-*", "gpt-4o"],
  "packages": ["pi-skills"]
}

第 19 章 上下文文件

19.1 AGENTS.md 加载

pi 在启动时从以下位置加载 AGENTS.md(或 CLAUDE.md):

  1. ~/.pi/agent/AGENTS.md(全局)
  2. 父目录(从 cwd 向上遍历)
  3. 当前目录

所有匹配的文件被连接。使用 AGENTS.md 放置项目指令、约定和常用命令。

19.2 遍历规则

pi 从当前工作目录向上遍历到 git 仓库根目录(或文件系统根目录),收集沿途所有目录中的 AGENTS.md 文件。每个目录中的文件按以下顺序加载:

  • AGENTS.md(首选)
  • CLAUDE.md(备选)

这允许:

  • 项目根目录放置项目级指令
  • 子目录放置模块级指令
  • 全局配置放置通用指令

19.3 系统提示词定制

替换默认系统提示词

使用 .pi/SYSTEM.md(项目级)或 ~/.pi/agent/SYSTEM.md(全局级)替换默认系统提示词。

追加系统提示词

使用 APPEND_SYSTEM.md 追加而不替换默认系统提示词。

通过 SDK 时,可通过 DefaultResourceLoadersystemPromptOverride 选项自定义:

typescript 复制代码
const loader = new DefaultResourceLoader({
  systemPromptOverride: () => "You are a helpful assistant.",
});
await loader.reload();

19.4 上下文文件的用途

AGENTS.md 典型内容:

markdown 复制代码
# Project Guidelines

## Build Commands
- `npm run build` --- Build the project
- `npm test` --- Run all tests
- `npm run lint` --- Lint the code

## Conventions
- Use TypeScript strict mode
- Prefer `const` over `let`
- All functions must have JSDoc

## Architecture
- Source code in `src/`
- Tests in `test/`
- Configuration in `config/`

19.5 SDK 中的上下文文件覆盖

typescript 复制代码
const loader = new DefaultResourceLoader({
  agentsFilesOverride: (current) => ({
    agentsFiles: [
      ...current.agentsFiles,
      { path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" },
    ],
  }),
});
await loader.reload();

第六部分:定制体系

第 20 章 提示词模板

20.1 概述

提示词模板(Prompt Templates)是可复用的 Markdown 片段,在编辑器中输入 /name 即可展开为完整提示词。

20.2 加载位置

  • 全局:~/.pi/agent/prompts/*.md
  • 项目:.pi/prompts/*.md
  • 包:prompts/ 目录或 package.json 中的 pi.prompts
  • 设置:prompts 数组指定文件或目录
  • CLI:--prompt-template <path>(可重复)

使用 --no-prompt-templates 禁用自动发现。

20.3 文件格式

markdown 复制代码
---
description: Review staged git changes
argument-hint: "<PR-URL>"
---
Review the staged changes (`git diff --cached`). Focus on:
- Bugs and logic errors
- Security issues
- Error handling gaps
  • 文件名即命令名。review.md 变为 /review
  • description 可选。缺失时使用首个非空行
  • argument-hint 可选。在自动补全下拉中显示预期参数

20.4 参数支持

模板支持位置参数和切片:

语法 说明
$1, $2, ... 位置参数
$@$ARGUMENTS 所有参数拼接
${@:N} 从第 N 个位置起的参数(1-indexed)
${@:N:L} 从第 N 个位置起的 L 个参数

示例模板:

markdown 复制代码
---
description: Create a component
---
Create a React component named $1 with features: $@

使用方式:

复制代码
/component Button                           # $1 = "Button"
/component Button "onClick handler"         # $1 = "Button", $@ = all args
/component Button "onClick" "disabled"      # 多参数

20.5 加载规则

  • prompts/ 目录中的发现是非递归的
  • 如果需要子目录中的模板,通过 prompts 设置或包清单显式添加

20.6 argument-hint

使用 frontmatter 中的 argument-hint 显示自动补全中的预期参数。用 <尖括号> 表示必填参数,[方括号] 表示可选参数:

markdown 复制代码
---
description: Review PRs from URLs with structured issue and code analysis
argument-hint: "<PR-URL>"
---

在自动补全下拉中渲染为:

复制代码
→ pr   <PR-URL>       --- Review PRs from URLs with structured issue and code analysis
  is   <issue>        --- Analyze GitHub issues (bugs or feature requests)
  wr   [instructions] --- Finish the current task end-to-end
  cl   --- Audit changelog entries before release

第 21 章 技能

21.1 概述

技能(Skills)是按需加载的自包含能力包,提供专业的工作流、设置说明、辅助脚本和参考文档。pi mono 实现了 Agent Skills 标准,对违规发出警告但保持宽容。

21.2 加载位置

pi 从以下位置加载技能:

  • 全局:
    • ~/.pi/agent/skills/
    • ~/.agents/skills/
  • 项目:
    • .pi/skills/
    • .agents/skills/(从 cwd 向上遍历到 git 仓库根目录)
  • 包:skills/ 目录或 package.json 中的 pi.skills
  • 设置:skills 数组指定文件或目录
  • CLI:--skill <path>(可重复,即使 --no-skills 也生效)

发现规则:

  • ~/.pi/agent/skills/.pi/skills/ 中,直接根目录 .md 文件被发现为独立技能
  • 在所有技能位置,包含 SKILL.md 的目录被递归发现
  • ~/.agents/skills/ 和项目 .agents/skills/ 中,根目录 .md 文件被忽略

21.3 从其他工具共享技能

要使用 Claude Code 或 OpenAI Codex 的技能,将其目录添加到设置中:

json 复制代码
{
  "skills": [
    "~/.claude/skills",
    "~/.codex/skills"
  ]
}

对于项目级 Claude Code 技能,添加到 .pi/settings.json

json 复制代码
{
  "skills": ["../.claude/skills"]
}

21.4 工作原理

  1. 启动时,pi 扫描技能位置并提取名称和描述
  2. 系统提示词以 XML 格式包含可用技能
  3. 任务匹配时,Agent 使用 read 加载完整的 SKILL.md
  4. Agent 遵循指令,使用相对路径引用脚本和资源

这是渐进式披露:只有描述始终在上下文中,完整指令按需加载。

21.5 技能目录结构

复制代码
my-skill/
├── SKILL.md              # 必需:frontmatter + 指令
├── scripts/              # 辅助脚本
│   └── process.sh
├── references/           # 按需加载的详细文档
│   └── api-reference.md
└── assets/
    └── template.json

21.6 SKILL.md 格式

markdown 复制代码
---
name: my-skill
description: What this skill does and when to use it. Be specific.
---

# My Skill

## Setup

Run once before first use:
```bash
cd /path/to/skill && npm install
```

## Usage

```bash
./scripts/process.sh <input>
```

使用相对于技能目录的路径:

markdown 复制代码
See [the reference guide](references/REFERENCE.md) for details.

21.7 Frontmatter 字段

字段 必填 说明
name 最大 64 字符。小写 a-z、0-9、连字符。必须匹配父目录名
description 最大 1024 字符。技能功能和使用场景
license 许可证名称或对捆绑文件的引用
compatibility 最大 500 字符。环境要求
metadata 任意键值映射
allowed-tools 空格分隔的预批准工具列表(实验性)
disable-model-invocation 设为 true 时从系统提示词隐藏技能,用户必须使用 /skill:name

21.8 名称规则

  • 1-64 个字符
  • 仅小写字母、数字、连字符
  • 不允许前导/尾随连字符
  • 不允许连续连字符
  • 必须匹配父目录名

有效:pdf-processingdata-analysiscode-review

无效:PDF-Processing-pdfpdf--processing

21.9 描述最佳实践

描述决定 Agent 何时加载技能。要具体。

好的描述:

yaml 复制代码
description: Extracts text and tables from PDF files, fills PDF forms, and merges multiple PDFs. Use when working with PDF documents.

差的描述:

yaml 复制代码
description: Helps with PDFs.

21.10 验证

pi 根据 Agent Skills 标准验证技能。大多数问题产生警告但仍加载技能:

  • 名称不匹配父目录
  • 名称超过 64 个字符或包含无效字符
  • 名称以连字符开头/结尾或有连续连字符
  • 描述超过 1024 个字符

异常:缺少描述的技能不被加载。

名称冲突(不同位置同名)时发出警告并保留找到的第一个技能。

21.11 技能命令

技能注册为 /skill:name 命令:

bash 复制代码
/skill:brave-search           # 加载并执行技能
/skill:pdf-tools extract      # 加载技能并带参数

命令后的参数作为 User: <args> 追加到技能内容中。

通过 enableSkillCommands 控制技能命令注册。

21.12 示例

markdown 复制代码
---
name: brave-search
description: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content.
---

# Brave Search

## Setup

```bash
cd /path/to/brave-search && npm install
```

## Search

```bash
./search.js "query"              # Basic search
./search.js "query" --content    # Include page content
```

## Extract Page Content

```bash
./content.js https://example.com
```

21.13 推荐技能仓库

  • Anthropic Skills --- 文档处理(docx、pdf、pptx、xlsx)、Web 开发
  • Pi Skills --- Web 搜索、浏览器自动化、Google API、转录

第 22 章 主题

22.1 概述

主题(Themes)是定义 TUI 颜色的 JSON 文件。pi 内置 darklight 两个主题。主题支持热重载:修改当前活动主题文件时,pi 立即应用更改。

22.2 加载位置

  • 内置:darklight
  • 全局:~/.pi/agent/themes/*.json
  • 项目:.pi/themes/*.json
  • 包:themes/ 目录或 package.json 中的 pi.themes
  • 设置:themes 数组指定文件或目录
  • CLI:--theme <path>(可重复)

使用 --no-themes 禁用自动发现。

22.3 选择主题

通过 /settingssettings.json 中的 theme 字段选择:

json 复制代码
{
  "theme": "my-theme"
}

首次运行时,pi 检测终端背景并默认使用 darklight

22.4 创建自定义主题

  1. 创建主题文件:
bash 复制代码
mkdir -p ~/.pi/agent/themes
vim ~/.pi/agent/themes/my-theme.json
  1. 定义包含所有必需颜色的主题:
json 复制代码
{
  "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
  "name": "my-theme",
  "vars": {
    "primary": "#00aaff",
    "secondary": 242
  },
  "colors": {
    "accent": "primary",
    "border": "primary",
    "borderAccent": "#00ffff",
    "borderMuted": "secondary",
    "success": "#00ff00",
    "error": "#ff0000",
    "warning": "#ffff00",
    "muted": "secondary",
    "dim": 240,
    "text": "",
    "thinkingText": "secondary",
    "selectedBg": "#2d2d30",
    "userMessageBg": "#2d2d30",
    "userMessageText": "",
    "customMessageBg": "#2d2d30",
    "customMessageText": "",
    "customMessageLabel": "primary",
    "toolPendingBg": "#1e1e2e",
    "toolSuccessBg": "#1e2e1e",
    "toolErrorBg": "#2e1e1e",
    "toolTitle": "primary",
    "toolOutput": "",
    "mdHeading": "#ffaa00",
    "mdLink": "primary",
    "mdLinkUrl": "secondary",
    "mdCode": "#00ffff",
    "mdCodeBlock": "",
    "mdCodeBlockBorder": "secondary",
    "mdQuote": "secondary",
    "mdQuoteBorder": "secondary",
    "mdHr": "secondary",
    "mdListBullet": "#00ffff",
    "toolDiffAdded": "#00ff00",
    "toolDiffRemoved": "#ff0000",
    "toolDiffContext": "secondary",
    "syntaxComment": "secondary",
    "syntaxKeyword": "primary",
    "syntaxFunction": "#00aaff",
    "syntaxVariable": "#ffaa00",
    "syntaxString": "#00ff00",
    "syntaxNumber": "#ff00ff",
    "syntaxType": "#00aaff",
    "syntaxOperator": "primary",
    "syntaxPunctuation": "secondary",
    "thinkingOff": "secondary",
    "thinkingMinimal": "primary",
    "thinkingLow": "#00aaff",
    "thinkingMedium": "#00ffff",
    "thinkingHigh": "#ff00ff",
    "thinkingXhigh": "#ff0000",
    "bashMode": "#ffaa00"
  }
}
  1. 通过 /settings 选择主题。

22.5 主题格式

json 复制代码
{
  "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
  "name": "my-theme",
  "vars": {
    "blue": "#0066cc",
    "gray": 242
  },
  "colors": {
    "accent": "blue",
    "muted": "gray",
    "text": "",
    ...
  }
}
  • name 必需且必须唯一
  • vars 可选。定义可复用颜色,在 colors 中引用
  • colors 必须定义全部 51 个必需令牌

$schema 字段启用编辑器自动补全和验证。

22.6 核心 UI 颜色(11 个)

令牌 用途
accent 主要强调色(Logo、选中项、光标)
border 普通边框
borderAccent 高亮边框
borderMuted 微妙边框(编辑器)
success 成功状态
error 错误状态
warning 警告状态
muted 次要文本
dim 第三文本
text 默认文本(通常 ""
thinkingText 思维块文本

22.7 背景与内容颜色(11 个)

令牌 用途
selectedBg 选中行背景
userMessageBg 用户消息背景
userMessageText 用户消息文本
customMessageBg 扩展消息背景
customMessageText 扩展消息文本
customMessageLabel 扩展消息标签
toolPendingBg 工具框(待处理)
toolSuccessBg 工具框(成功)
toolErrorBg 工具框(错误)
toolTitle 工具标题
toolOutput 工具输出文本

22.8 Markdown 颜色(10 个)

令牌 用途
mdHeading 标题
mdLink 链接文本
mdLinkUrl 链接 URL
mdCode 行内代码
mdCodeBlock 代码块内容
mdCodeBlockBorder 代码块围栏
mdQuote 引用文本
mdQuoteBorder 引用边框
mdHr 水平线
mdListBullet 列表符号

22.9 工具差异颜色(3 个)

令牌 用途
toolDiffAdded 添加的行
toolDiffRemoved 删除的行
toolDiffContext 上下行

22.10 语法高亮颜色(9 个)

令牌 用途
syntaxComment 注释
syntaxKeyword 关键字
syntaxFunction 函数名
syntaxVariable 变量
syntaxString 字符串
syntaxNumber 数字
syntaxType 类型
syntaxOperator 运算符
syntaxPunctuation 标点

22.11 思维级别边框颜色(6 个)

编辑器边框颜色指示思维级别(从微妙到显著):

令牌 用途
thinkingOff 思维关闭
thinkingMinimal 最小思维
thinkingLow 低思维
thinkingMedium 中等思维
thinkingHigh 高思维
thinkingXhigh 极高思维

22.12 Bash 模式颜色(1 个)

令牌 用途
bashMode bash 模式编辑器边框(! 前缀时)

22.13 颜色值格式

支持四种格式:

格式 示例 说明
十六进制 "#ff0000" 6 位十六进制 RGB
256 色 39 xterm 256 色调色板索引(0-255)
变量 "primary" vars 条目的引用
默认 "" 终端默认颜色

22.14 HTML 导出颜色(可选)

export 部分控制 /export HTML 输出的颜色。如果省略,颜色从 userMessageBg 派生:

json 复制代码
{
  "export": {
    "pageBg": "#18181e",
    "cardBg": "#1e1e24",
    "infoBg": "#3c3728"
  }
}

22.15 调色建议

深色终端:使用明亮、饱和的颜色,高对比度。

浅色终端:使用较深、柔和的颜色,低对比度。

颜色和谐 :从基础调色板开始(Nord、Gruvbox、Tokyo Night),在 vars 中定义,在 colors 中一致引用。

测试:用不同消息类型、工具状态、Markdown 内容和长换行文本检查主题。

VS Code :设置 terminal.integrated.minimumContrastRatio1 以获得准确颜色。


第 23 章 包管理

23.1 概述

Pi 包(Pi Packages)将扩展、技能、提示词模板和主题打包在一起,以便通过 npm 或 git 与他人分享。

安全警告:Pi 包以完整系统权限运行。扩展执行任意代码,技能可以指示模型执行任何操作包括运行可执行文件。安装第三方包前请审查源代码。

23.2 安装与管理

bash 复制代码
pi install npm:@foo/bar@1.0.0
pi install git:github.com/user/repo@v1
pi install https://github.com/user/repo
pi install /absolute/path/to/package

pi remove npm:@foo/bar
pi uninstall npm:@foo/bar   # remove 的别名
pi list                      # 显示已安装的包
pi update                    # 更新所有非固定的包
pi config                    # 启用/禁用包资源

默认情况下,installremove 写入全局设置(~/.pi/agent/settings.json)。使用 -l 写入项目设置(.pi/settings.json)。

要试用包而不安装,使用 --extension-e

bash 复制代码
pi -e npm:@foo/bar
pi -e git:github.com/user/repo

23.3 包来源类型

npm 包

复制代码
npm:@scope/pkg@1.2.3
npm:pkg
  • 带版本规范的包被固定,pi update 跳过
  • 全局安装使用 npm install -g
  • 项目安装在 .pi/npm/
  • 设置 npmCommand 以固定 npm 操作

Git 包

复制代码
git:github.com/user/repo@v1
git:git@github.com:user/repo@v1
https://github.com/user/repo@v1
ssh://git@github.com/user/repo@v1
  • 不带 git: 前缀时,仅接受协议 URL(https://http://ssh://git://
  • git: 前缀时,接受简写格式(包括 github.com/user/repogit@github.com:user/repo
  • HTTPS 和 SSH URL 都支持
  • SSH URL 自动使用配置的 SSH 密钥
  • 引用固定包并跳过 pi update
  • 克隆到 ~/.pi/agent/git/<host>/<path>(全局)或 .pi/git/<host>/<path>(项目)
  • 克隆或拉取后,如果存在 package.json 则运行 npm install

SSH 示例

bash 复制代码
pi install git:git@github.com:user/repo
pi install ssh://git@github.com/user/repo
pi install git:git@github.com:user/repo@v1.0.0

本地路径

复制代码
/absolute/path/to/package
./relative/path/to/package

本地路径指向磁盘上的文件或目录,直接添加到设置中不复制。如果是文件,加载为单个扩展;如果是目录,pi 使用包规则加载资源。

23.4 创建 Pi 包

package.json 中添加 pi 清单或使用约定目录。包含 pi-package 关键字以便发现:

json 复制代码
{
  "name": "my-package",
  "keywords": ["pi-package"],
  "pi": {
    "extensions": ["./extensions"],
    "skills": ["./skills"],
    "prompts": ["./prompts"],
    "themes": ["./themes"]
  }
}

路径相对于包根目录。数组支持 glob 模式和 ! 排除。

23.5 画廊元数据

包画廊 显示标记为 pi-package 的包。添加 videoimage 字段显示预览:

json 复制代码
{
  "name": "my-package",
  "keywords": ["pi-package"],
  "pi": {
    "extensions": ["./extensions"],
    "video": "https://example.com/demo.mp4",
    "image": "https://example.com/screenshot.png"
  }
}
  • video:仅 MP4。桌面端悬停时自动播放,点击打开全屏播放器
  • image:PNG、JPEG、GIF 或 WebP。显示为静态预览

两者同时设置时,video 优先。

23.6 约定目录

如果没有 pi 清单,pi 从以下约定目录自动发现资源:

  • extensions/ --- 加载 .ts.js 文件
  • skills/ --- 递归查找包含 SKILL.md 的目录,加载根目录 .md 文件
  • prompts/ --- 加载 .md 文件
  • themes/ --- 加载 .json 文件

23.7 依赖管理

第三方运行时依赖属于 package.json 中的 dependencies。安装包时 pi 运行 npm install,依赖自动安装。

pi 捆绑核心包。如果导入以下包,将它们列为 peerDependencies"*" 范围),不打包:@mariozechner/pi-ai@mariozechner/pi-agent-core@mariozechner/pi-coding-agent@mariozechner/pi-tuitypebox

其他 pi 包必须打包在 tarball 中。添加到 dependenciesbundledDependencies,然后通过 node_modules/ 路径引用其资源:

json 复制代码
{
  "dependencies": {
    "shitty-extensions": "^1.0.1"
  },
  "bundledDependencies": ["shitty-extensions"],
  "pi": {
    "extensions": ["extensions", "node_modules/shitty-extensions/extensions"],
    "skills": ["skills", "node_modules/shitty-extensions/skills"]
  }
}

23.8 包过滤

使用设置中的对象形式过滤包加载的资源:

json 复制代码
{
  "packages": [
    "npm:simple-pkg",
    {
      "source": "npm:my-package",
      "extensions": ["extensions/*.ts", "!extensions/legacy.ts"],
      "skills": [],
      "prompts": ["prompts/review.md"],
      "themes": ["+themes/legacy.json"]
    }
  ]
}
  • 省略键加载该类型的全部
  • 使用 [] 不加载该类型
  • !pattern 排除匹配
  • +path 强制包含精确路径
  • -path 强制排除精确路径
  • 过滤层叠在清单之上

23.9 启用/禁用资源

使用 pi config 启用或禁用来自已安装包和本地目录的扩展、技能、提示词模板和主题。适用于全局(~/.pi/agent)和项目(.pi/)范围。

23.10 作用域与去重

包可以同时出现在全局和项目设置中。如果同一包在两者中出现,项目条目优先。身份确定方式:

  • npm:包名
  • git:不带 ref 的仓库 URL
  • 本地:解析后的绝对路径

第七部分:Extension 开发

第 24 章 Extension 概览与快速开始

24.1 什么是 Extension

Extension 是 TypeScript 模块,扩展 pi 的行为。它们可以订阅生命周期事件、注册自定义工具供 LLM 调用、添加命令等。

pi 可以创建 Extension。请它根据你的使用场景构建一个。

24.2 核心能力

  • 自定义工具 --- 通过 pi.registerTool() 注册 LLM 可调用的工具
  • 事件拦截 --- 阻塞或修改工具调用、注入上下文、自定义压缩
  • 用户交互 --- 通过 ctx.ui 提示用户(选择、确认、输入、通知)
  • 自定义 UI 组件 --- 通过 ctx.ui.custom() 实现完整的 TUI 组件和键盘输入
  • 自定义命令 --- 通过 pi.registerCommand() 注册 /mycommand 命令
  • 会话持久化 --- 通过 pi.appendEntry() 存储重启后仍保留的状态
  • 自定义渲染 --- 控制工具调用/结果和消息在 TUI 中的显示方式

24.3 典型用例

  • 权限门控(确认 rm -rfsudo 等危险操作)
  • Git 检查点(每个 turn 暂存,分支时恢复)
  • 路径保护(阻止写入 .envnode_modules/
  • 自定义压缩(用自己的方式总结对话)
  • 交互式工具(问答、向导、自定义对话框)
  • 有状态工具(待办列表、连接池)
  • 外部集成(文件监视、Webhook、CI 触发器)
  • 等待时运行游戏(是的,Doom 可以跑)

24.4 快速开始

创建 ~/.pi/agent/extensions/my-extension.ts

typescript 复制代码
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
  // 响应事件
  pi.on("session_start", async (_event, ctx) => {
    ctx.ui.notify("Extension loaded!", "info");
  });

  pi.on("tool_call", async (event, ctx) => {
    if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
      const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
      if (!ok) return { block: true, reason: "Blocked by user" };
    }
  });

  // 注册自定义工具
  pi.registerTool({
    name: "greet",
    label: "Greet",
    description: "Greet someone by name",
    parameters: Type.Object({
      name: Type.String({ description: "Name to greet" }),
    }),
    async execute(toolCallId, params, signal, onUpdate, ctx) {
      return {
        content: [{ type: "text", text: `Hello, ${params.name}!` }],
        details: {},
      };
    },
  });

  // 注册命令
  pi.registerCommand("hello", {
    description: "Say hello",
    handler: async (args, ctx) => {
      ctx.ui.notify(`Hello ${args || "world"}!`, "info");
    },
  });
}

--extension(或 -e)标志测试:

bash 复制代码
pi -e ./my-extension.ts

24.5 Extension 位置

安全警告:Extension 以完整系统权限运行,可以执行任意代码。仅从可信来源安装。

Extension 从以下位置自动发现:

位置 作用域
~/.pi/agent/extensions/*.ts 全局(所有项目)
~/.pi/agent/extensions/*/index.ts 全局(子目录)
.pi/extensions/*.ts 项目级
.pi/extensions/*/index.ts 项目级(子目录)

通过 settings.json 添加额外路径:

json 复制代码
{
  "packages": [
    "npm:@foo/bar@1.0.0",
    "git:github.com/user/repo@v1"
  ],
  "extensions": [
    "/path/to/local/extension.ts",
    "/path/to/local/extension/dir"
  ]
}

要在 /reload 时生效,将 Extension 放在 ~/.pi/agent/extensions/(全局)或 .pi/extensions/(项目级)。使用 pi -e ./path.ts 仅用于快速测试。


第 25 章 编写 Extension

25.1 可用导入

用途
@mariozechner/pi-coding-agent Extension 类型(ExtensionAPIExtensionContext、事件)
typebox 工具参数的 Schema 定义
@mariozechner/pi-ai AI 工具(StringEnum 用于 Google 兼容枚举)
@mariozechner/pi-tui TUI 组件用于自定义渲染

npm 依赖也可用。在 Extension 旁添加 package.json,运行 npm installnode_modules/ 中的导入自动解析。

对于通过 pi install 安装的分布式 pi 包(npm 或 git),运行时依赖必须在 dependencies 中。包安装默认使用生产安装(npm install --omit=dev),所以 devDependencies 在运行时不可用;配置 npmCommand 时,git 包使用普通 install 以兼容包装器。

Node.js 内置模块(node:fsnode:path 等)也可用。

25.2 Extension 结构

Extension 导出默认工厂函数,接收 ExtensionAPI。工厂可以是同步或异步的:

typescript 复制代码
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  // 订阅事件
  pi.on("event_name", async (event, ctx) => {
    // ctx.ui 用于用户交互
    const ok = await ctx.ui.confirm("Title", "Are you sure?");
    ctx.ui.notify("Done!", "success");
    ctx.ui.setStatus("my-ext", "Processing...");
    ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"]);
  });

  // 注册工具、命令、快捷键、标志
  pi.registerTool({ ... });
  pi.registerCommand("name", { ... });
  pi.registerShortcut("ctrl+x", { ... });
  pi.registerFlag("my-flag", { ... });
}

Extension 通过 jiti 加载,所以 TypeScript 无需编译即可工作。

如果工厂返回 Promise,pi 会在继续启动前等待它完成。这意味着异步初始化在 session_start 之前、resources_discover 之前、以及通过 pi.registerProvider() 排队的 Provider 注册之前完成。

25.3 异步工厂函数

使用异步工厂进行一次性启动工作,如获取远程配置或动态发现可用模型:

typescript 复制代码
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

export default async function (pi: ExtensionAPI) {
  const response = await fetch("http://localhost:1234/v1/models");
  const payload = (await response.json()) as {
    data: Array<{
      id: string;
      name?: string;
      context_window?: number;
      max_tokens?: number;
    }>;
  };

  pi.registerProvider("local-openai", {
    baseUrl: "http://localhost:1234/v1",
    apiKey: "LOCAL_OPENAI_API_KEY",
    api: "openai-completions",
    models: payload.data.map((model) => ({
      id: model.id,
      name: model.name ?? model.id,
      reasoning: false,
      input: ["text"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: model.context_window ?? 128000,
      maxTokens: model.max_tokens ?? 4096,
    })),
  });
}

25.4 Extension 风格

单文件 --- 最简单,适合小扩展:

复制代码
~/.pi/agent/extensions/
└── my-extension.ts

目录 + index.ts --- 适合多文件扩展:

复制代码
~/.pi/agent/extensions/
└── my-extension/
    ├── index.ts        # 入口点(导出默认函数)
    ├── tools.ts        # 辅助模块
    └── utils.ts        # 辅助模块

包含依赖的目录 --- 需要 npm 包的扩展:

复制代码
~/.pi/agent/extensions/
└── my-extension/
    ├── package.json
    ├── package-lock.json
    ├── node_modules/
    └── src/
        └── index.ts
json 复制代码
{
  "name": "my-extension",
  "dependencies": {
    "zod": "^3.0.0",
    "chalk": "^5.0.0"
  },
  "pi": {
    "extensions": ["./src/index.ts"]
  }
}

第 26 章 事件系统

26.1 生命周期概览

复制代码
pi 启动
  │
  ├─► session_start { reason: "startup" }
  └─► resources_discover { reason: "startup" }
      │
      ▼
用户发送提示 ─────────────────────────────────────────┐
  │                                                   │
  ├─► input(可拦截、转换或处理)                       │
  ├─► before_agent_start(可注入消息、修改系统提示词)  │
  ├─► agent_start                                     │
  ├─► message_start / message_update / message_end     │
  │                                                   │
  │   ┌─── turn(LLM 调用工具时重复) ───┐             │
  │   │                                  │             │
  │   ├─► turn_start                     │             │
  │   ├─► context(可修改消息)           │             │
  │   ├─► before_provider_request        │             │
  │   ├─► after_provider_response        │             │
  │   │                                  │             │
  │   │   LLM 响应,可能调用工具:        │             │
  │   │     ├─► tool_execution_start     │             │
  │   │     ├─► tool_call(可阻塞)       │             │
  │   │     ├─► tool_execution_update    │             │
  │   │     ├─► tool_result(可修改)     │             │
  │   │     └─► tool_execution_end       │             │
  │   │                                  │             │
  │   └─► turn_end                       │             │
  │                                                   │
  └─► agent_end                                      │
                                                     │
用户发送另一条提示 ◄──────────────────────────────────┘

26.2 资源事件

resources_discover

session_start 后触发,扩展可贡献额外的技能、提示词和主题路径。启动路径使用 reason: "startup",重载使用 reason: "reload"

typescript 复制代码
pi.on("resources_discover", async (event, _ctx) => {
  // event.cwd --- 当前工作目录
  // event.reason --- "startup" | "reload"
  return {
    skillPaths: ["/path/to/skills"],
    promptPaths: ["/path/to/prompts"],
    themePaths: ["/path/to/themes"],
  };
});

26.3 会话事件

session_start

会话启动、加载或重载时触发。

typescript 复制代码
pi.on("session_start", async (event, ctx) => {
  // event.reason --- "startup" | "reload" | "new" | "resume" | "fork"
  // event.previousSessionFile --- "new"、"resume"、"fork" 时存在
  ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
});
session_before_switch

在开始新会话(/new)或切换会话(/resume)之前触发。

typescript 复制代码
pi.on("session_before_switch", async (event, ctx) => {
  // event.reason --- "new" 或 "resume"
  // event.targetSessionFile --- 切换目标会话文件(仅 "resume")

  if (event.reason === "new") {
    const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
    if (!ok) return { cancel: true };
  }
});
session_before_fork

通过 /fork 分叉或 /clone 克隆时触发。

typescript 复制代码
pi.on("session_before_fork", async (event, ctx) => {
  // event.entryId --- 选中的条目 ID
  // event.position --- /fork 为 "before",/clone 为 "at"
  return { cancel: true }; // 取消分叉/克隆
});
session_before_compact / session_compact

压缩时触发。详见第 15 章。

typescript 复制代码
pi.on("session_before_compact", async (event, ctx) => {
  const { preparation, branchEntries, customInstructions, signal } = event;
  return { cancel: true };
  // 或提供自定义摘要
  return {
    compaction: {
      summary: "...",
      firstKeptEntryId: preparation.firstKeptEntryId,
      tokensBefore: preparation.tokensBefore,
    }
  };
});
session_before_tree / session_tree

/tree 导航时触发。详见第 14 章。

typescript 复制代码
pi.on("session_before_tree", async (event, ctx) => {
  const { preparation, signal } = event;
  return { cancel: true };
});
session_shutdown

Extension 运行时关闭前触发。

typescript 复制代码
pi.on("session_shutdown", async (event, ctx) => {
  // event.reason --- "quit" | "reload" | "new" | "resume" | "fork"
  // event.targetSessionFile --- 会话替换流程的目标会话
});

26.4 Agent 事件

before_agent_start

用户提交提示后、Agent 循环前触发。可注入消息和/或修改系统提示词。

typescript 复制代码
pi.on("before_agent_start", async (event, ctx) => {
  // event.prompt --- 用户提示文本
  // event.images --- 附加图片
  // event.systemPrompt --- 当前链式系统提示词
  // event.systemPromptOptions --- 构建系统提示词的结构化选项

  return {
    // 注入持久消息(存储在会话中,发送给 LLM)
    message: {
      customType: "my-extension",
      content: "Additional context for the LLM",
      display: true,
    },
    // 替换本次 turn 的系统提示词
    systemPrompt: event.systemPrompt + "\n\nExtra instructions...",
  };
});

systemPromptOptions 字段提供访问 pi 用于构建系统提示词的相同结构化数据------自定义提示词、指南、工具片段、上下文文件、技能------无需重新发现资源。

agent_start / agent_end

每个用户提示触发一次。

typescript 复制代码
pi.on("agent_start", async (_event, ctx) => {});
pi.on("agent_end", async (event, ctx) => {
  // event.messages --- 本次提示生成的消息
});
turn_start / turn_end

每个 turn(一个 LLM 响应 + 工具调用)触发。

typescript 复制代码
pi.on("turn_start", async (event, ctx) => {
  // event.turnIndex, event.timestamp
});
pi.on("turn_end", async (event, ctx) => {
  // event.turnIndex, event.message, event.toolResults
});
message_start / message_update / message_end

消息生命周期更新触发。

typescript 复制代码
pi.on("message_start", async (event, ctx) => { /* event.message */ });
pi.on("message_update", async (event, ctx) => { /* event.message, event.assistantMessageEvent */ });
pi.on("message_end", async (event, ctx) => { /* event.message */ });
tool_execution_start / tool_execution_update / tool_execution_end

工具执行生命周期更新触发。

typescript 复制代码
pi.on("tool_execution_start", async (event, ctx) => {
  // event.toolCallId, event.toolName, event.args
});
pi.on("tool_execution_update", async (event, ctx) => {
  // event.toolCallId, event.toolName, event.args, event.partialResult
});
pi.on("tool_execution_end", async (event, ctx) => {
  // event.toolCallId, event.toolName, event.result, event.isError
});
context

每次 LLM 调用前触发。非破坏性修改消息。

typescript 复制代码
pi.on("context", async (event, ctx) => {
  const filtered = event.messages.filter(m => !shouldPrune(m));
  return { messages: filtered };
});
before_provider_request

Provider 特定 payload 构建后、请求发送前触发。返回 undefined 保持 payload 不变,返回其他值替换 payload。

typescript 复制代码
pi.on("before_provider_request", (event, ctx) => {
  console.log(JSON.stringify(event.payload, null, 2));
});
after_provider_response

HTTP 响应接收后、流体消费前触发。

typescript 复制代码
pi.on("after_provider_response", (event, ctx) => {
  // event.status --- HTTP 状态码
  // event.headers --- 归一化响应头
});

26.5 模型事件

model_select

通过 /model 命令、模型循环(Ctrl+P)或会话恢复切换模型时触发。

typescript 复制代码
pi.on("model_select", async (event, ctx) => {
  // event.model --- 新选中的模型
  // event.previousModel --- 前一个模型
  // event.source --- "set" | "cycle" | "restore"
});

26.6 工具事件

tool_call

tool_execution_start 后、工具执行前触发。可阻塞 。使用 isToolCallEventType 收窄类型。

event.input 可变。就地修改以修补工具参数。

typescript 复制代码
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";

pi.on("tool_call", async (event, ctx) => {
  if (isToolCallEventType("bash", event)) {
    event.input.command = `source ~/.profile\n${event.input.command}`;
    if (event.input.command.includes("rm -rf")) {
      return { block: true, reason: "Dangerous command" };
    }
  }
});
tool_result

工具执行完成后、tool_execution_end 前触发。可修改结果

tool_result 处理程序像中间件一样链式处理:按扩展加载顺序运行,每个处理程序看到前一个处理程序更改后的最新结果。

typescript 复制代码
import { isBashToolResult } from "@mariozechner/pi-coding-agent";

pi.on("tool_result", async (event, ctx) => {
  if (isBashToolResult(event)) {
    // event.details 类型为 BashToolDetails
  }

  // 修改结果:
  return { content: [...], details: {...}, isError: false };
});

26.7 用户 Bash 事件

user_bash

用户执行 !!! 命令时触发。可拦截

typescript 复制代码
import { createLocalBashOperations } from "@mariozechner/pi-coding-agent";

pi.on("user_bash", (event, ctx) => {
  // event.command --- bash 命令
  // event.excludeFromContext --- !! 前缀时为 true
  // event.cwd --- 工作目录

  // 选项 1:提供自定义操作(如 SSH)
  return { operations: remoteBashOps };

  // 选项 2:包装 pi 内置本地 bash 后端
  const local = createLocalBashOperations();
  return {
    operations: {
      exec(command, cwd, options) {
        return local.exec(`source ~/.profile\n${command}`, cwd, options);
      }
    }
  };

  // 选项 3:完整替换 --- 直接返回结果
  return { result: { output: "...", exitCode: 0, cancelled: false, truncated: false } };
});

26.8 输入事件

input

用户输入接收后、扩展命令检查之后、技能和模板展开之前触发。事件看到原始输入文本,/skill:foo/template 尚未展开。

处理顺序

  1. 扩展命令(/cmd)先检查
  2. input 事件触发 --- 可拦截、转换或处理
  3. 技能命令(/skill:name)展开
  4. 提示词模板(/template)展开
  5. Agent 处理开始
typescript 复制代码
pi.on("input", async (event, ctx) => {
  // event.text --- 原始输入
  // event.images --- 附加图片
  // event.source --- "interactive" | "rpc" | "extension"

  // 转换:重写输入
  if (event.text.startsWith("?quick "))
    return { action: "transform", text: `Respond briefly: ${event.text.slice(7)}` };

  // 处理:不经过 LLM 直接响应
  if (event.text === "ping") {
    ctx.ui.notify("pong", "info");
    return { action: "handled" };
  }

  return { action: "continue" };  // 默认:透传
});

结果

  • continue --- 不变透传(处理程序返回空时的默认值)
  • transform --- 修改文本/图片,然后继续展开
  • handled --- 跳过 Agent(第一个返回此值的处理程序获胜)

第 27 章 ExtensionContext

27.1 概述

所有处理程序接收 ctx: ExtensionContext

27.2 ctx.ui

UI 方法,用于用户交互。详见第 31 章。

27.3 ctx.hasUI

在打印模式(-p)和 JSON 模式中为 false。在交互和 RPC 模式中为 true。在 RPC 模式中,对话方法(selectconfirminputeditor)通过扩展 UI 子协议工作。

27.4 ctx.cwd

当前工作目录。

27.5 ctx.sessionManager

对会话状态的只读访问。详见第 17 章。

27.6 ctx.modelRegistry / ctx.model

对模型和 API Key 的访问。

27.7 ctx.signal

当前 Agent 中断信号,或没有 Agent turn 活跃时为 undefined

用于扩展处理程序启动的嵌套异步工作的中断感知:

typescript 复制代码
pi.on("tool_result", async (event, ctx) => {
  const response = await fetch("https://example.com/api", {
    method: "POST",
    body: JSON.stringify(event),
    signal: ctx.signal,
  });
});

27.8 控制流

方法 说明
ctx.isIdle() Agent 是否空闲
ctx.abort() 中止当前操作
ctx.hasPendingMessages() 是否有待处理消息

27.9 ctx.shutdown()

请求优雅关闭 pi。在交互模式中延迟到 Agent 空闲时执行。在所有上下文中可用(事件处理程序、工具、命令)。

typescript 复制代码
pi.on("tool_call", (event, ctx) => {
  if (isFatal(event.input)) {
    ctx.shutdown();
  }
});

27.10 ctx.getContextUsage()

返回当前模型的上下文使用率。

typescript 复制代码
const usage = ctx.getContextUsage();
if (usage && usage.tokens > 100_000) {
  // ...
}

27.11 ctx.compact()

触发压缩,不等待完成。

typescript 复制代码
ctx.compact({
  customInstructions: "Focus on recent changes",
  onComplete: (result) => { ctx.ui.notify("Done", "info"); },
  onError: (error) => { ctx.ui.notify(`Failed: ${error.message}`, "error"); },
});

27.12 ctx.getSystemPrompt()

返回 pi 当前的系统提示词字符串。

typescript 复制代码
pi.on("before_agent_start", (event, ctx) => {
  const prompt = ctx.getSystemPrompt();
  console.log(`System prompt length: ${prompt.length}`);
});

第 28 章 ExtensionCommandContext

28.1 概述

命令处理程序接收 ExtensionCommandContext,它扩展了 ExtensionContext 并增加了会话控制方法。这些方法仅在命令中可用,因为从事件处理程序调用可能死锁。

28.2 ctx.waitForIdle()

等待 Agent 完成流式输出:

typescript 复制代码
pi.registerCommand("my-cmd", {
  handler: async (args, ctx) => {
    await ctx.waitForIdle();
    // Agent 现在空闲,安全修改会话
  },
});

28.3 ctx.newSession()

创建新会话:

typescript 复制代码
const parentSession = ctx.sessionManager.getSessionFile();
const kickoff = "Continue in the replacement session";

const result = await ctx.newSession({
  parentSession,
  setup: async (sm) => {
    sm.appendMessage({
      role: "user",
      content: [{ type: "text", text: "Context from previous session..." }],
      timestamp: Date.now(),
    });
  },
  withSession: async (ctx) => {
    await ctx.sendUserMessage(kickoff);
  },
});

28.4 ctx.fork()

从特定条目分叉,创建新会话文件:

typescript 复制代码
const result = await ctx.fork("entry-id-123", {
  withSession: async (ctx) => {
    ctx.ui.notify("Now in the forked session", "info");
  },
});

const cloneResult = await ctx.fork("entry-id-456", { position: "at" });

选项:

  • position: "before"(默认)在选中用户消息前分叉,"at" 复制当前分支

28.5 ctx.navigateTree()

导航到会话树中的不同点:

typescript 复制代码
const result = await ctx.navigateTree("entry-id-456", {
  summarize: true,
  customInstructions: "Focus on error handling changes",
  replaceInstructions: false,
  label: "review-checkpoint",
});

28.6 ctx.switchSession()

切换到不同会话文件:

typescript 复制代码
const result = await ctx.switchSession("/path/to/session.jsonl", {
  withSession: async (ctx) => {
    await ctx.sendUserMessage("Resume work");
  },
});

28.7 ctx.reload()

运行与 /reload 相同的重载流程。

typescript 复制代码
pi.registerCommand("reload-runtime", {
  handler: async (_args, ctx) => {
    await ctx.reload();
    return;
  },
});

28.8 会话替换生命周期

withSession 接收全新的 ReplacedSessionContext。关键行为:

  • withSession 在旧会话发出 session_shutdown、旧运行时拆除、新会话绑定、新 Extension 实例接收 session_start 之后运行
  • 回调仍在原始闭包中运行,不在新 Extension 实例中
  • 捕获的旧 pi / 旧命令 ctx 对象在替换后失效
  • 只使用传递给 withSessionctx

安全模式:

typescript 复制代码
await ctx.newSession({
  withSession: async (ctx) => {
    await ctx.sendUserMessage(kickoff);
  },
});

不安全模式:

typescript 复制代码
const oldSessionManager = ctx.sessionManager;
await ctx.newSession({
  withSession: async (_ctx) => {
    // 错误:旧对象已失效
    oldSessionManager.getSessionFile();
  },
});

第 29 章 ExtensionAPI 方法

29.1 pi.on()

订阅事件。详见第 26 章。

29.2 pi.registerTool()

注册 LLM 可调用的自定义工具。

pi.registerTool() 在扩展加载期间和启动后都有效。可以在 session_start、命令处理程序或其他事件处理程序中调用。新工具在同一会话中立即刷新。

typescript 复制代码
import { Type } from "typebox";
import { StringEnum } from "@mariozechner/pi-ai";

pi.registerTool({
  name: "my_tool",
  label: "My Tool",
  description: "What this tool does",
  promptSnippet: "Summarize or transform text according to action",
  promptGuidelines: ["Use my_tool when the user asks to summarize previously generated text."],
  parameters: Type.Object({
    action: StringEnum(["list", "add"] as const),
    text: Type.Optional(Type.String()),
  }),
  prepareArguments(args) {
    return args;
  },
  async execute(toolCallId, params, signal, onUpdate, ctx) {
    onUpdate?.({ content: [{ type: "text", text: "Working..." }] });
    return {
      content: [{ type: "text", text: "Done" }],
      details: { result: "..." },
    };
  },
  renderCall(args, theme, context) { ... },
  renderResult(result, options, theme, context) { ... },
});

promptGuidelines 要点:每个指南必须命名它所指的工具------避免"Use this tool when..."因为 LLM 无法分辨"this"指的是哪个工具。写"Use my_tool when..."。

29.3 pi.sendMessage()

注入自定义消息到会话。

typescript 复制代码
pi.sendMessage({
  customType: "my-extension",
  content: "Message text",
  display: true,
  details: { ... },
}, {
  triggerTurn: true,
  deliverAs: "steer",
});

选项

  • deliverAs
    • "steer"(默认)--- 流式期间排入队列,在当前 turn 工具调用后投递
    • "followUp" --- 等待 Agent 完成
    • "nextTurn" --- 排入下一次用户提示
  • triggerTurn: true --- Agent 空闲时立即触发 LLM 响应

29.4 pi.sendUserMessage()

向 Agent 发送用户消息。与发送自定义消息的 sendMessage() 不同,这发送一个实际的用户消息,如同用户输入的。

typescript 复制代码
// 简单文本消息
pi.sendUserMessage("What is 2+2?");

// 带内容数组
pi.sendUserMessage([
  { type: "text", text: "Describe this image:" },
  { type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
]);

// 流式期间 --- 必须指定投递模式
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });

29.5 pi.appendEntry()

持久化扩展状态(不参与 LLM 上下文)。

typescript 复制代码
pi.appendEntry("my-state", { count: 42 });

pi.on("session_start", async (_event, ctx) => {
  for (const entry of ctx.sessionManager.getEntries()) {
    if (entry.type === "custom" && entry.customType === "my-state") {
      // 从 entry.data 重建
    }
  }
});

29.6 pi.setSessionName() / pi.getSessionName()

设置/获取会话显示名称。

typescript 复制代码
pi.setSessionName("Refactor auth module");
const name = pi.getSessionName();

29.7 pi.setLabel()

设置或清除条目标签。标签持久化在会话中。

typescript 复制代码
pi.setLabel(entryId, "checkpoint-before-refactor");
pi.setLabel(entryId, undefined);  // 清除
const label = ctx.sessionManager.getLabel(entryId);

29.8 pi.registerCommand()

注册命令。多个扩展注册同名命令时,pi 保留所有并分配数字后缀(如 /review:1/review:2)。

typescript 复制代码
pi.registerCommand("stats", {
  description: "Show session statistics",
  handler: async (args, ctx) => {
    const count = ctx.sessionManager.getEntries().length;
    ctx.ui.notify(`${count} entries`, "info");
  }
});

可选:添加参数自动补全:

typescript 复制代码
import type { AutocompleteItem } from "@mariozechner/pi-tui";

pi.registerCommand("deploy", {
  description: "Deploy to an environment",
  getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
    const envs = ["dev", "staging", "prod"];
    const items = envs.map((e) => ({ value: e, label: e }));
    const filtered = items.filter((i) => i.value.startsWith(prefix));
    return filtered.length > 0 ? filtered : null;
  },
  handler: async (args, ctx) => { ctx.ui.notify(`Deploying: ${args}`, "info"); },
});

29.9 pi.getCommands()

获取当前会话中可用的斜杠命令。

typescript 复制代码
const commands = pi.getCommands();
const bySource = commands.filter((c) => c.source === "extension");

每个条目有:

  • name:可调用命令名(不含前导 /
  • description:人类可读描述
  • source"extension" / "prompt" / "skill"
  • sourceInfo:来源信息(pathsourcescopeoriginbaseDir

29.10 pi.registerMessageRenderer()

注册自定义 TUI 渲染器用于特定 customType 的消息。

29.11 pi.registerShortcut()

注册键盘快捷键。

typescript 复制代码
pi.registerShortcut("ctrl+shift+p", {
  description: "Toggle plan mode",
  handler: async (ctx) => {
    ctx.ui.notify("Toggled!");
  },
});

29.12 pi.registerFlag()

注册 CLI 标志。

typescript 复制代码
pi.registerFlag("plan", {
  description: "Start in plan mode",
  type: "boolean",
  default: false,
});
if (pi.getFlag("plan")) {
  // Plan mode enabled
}

29.13 pi.exec()

执行 Shell 命令。

typescript 复制代码
const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
// result.stdout, result.stderr, result.code, result.killed

29.14 工具管理

typescript 复制代码
const active = pi.getActiveTools();    // 当前活跃工具
const all = pi.getAllTools();           // 所有工具(含 name, description, parameters, sourceInfo)
pi.setActiveTools(["read", "bash"]);   // 切换到只读

sourceInfo.source 值:

  • builtin --- 内置工具
  • sdk --- 通过 createAgentSession({ customTools }) 传入的工具
  • 扩展源元数据 --- 扩展注册的工具

29.15 pi.setModel()

设置当前模型。

29.16 pi.registerProvider() / pi.unregisterProvider()

注册/注销 Provider。详见第 6 章。


第 30 章 自定义工具

30.1 工具定义

完整的工具定义包含以下字段:

字段 说明
name 工具名称(LLM 用它调用)
label TUI 显示标签
description 工具描述(LLM 用来决定何时调用)
promptSnippet 可选,系统提示词中 Available tools 的一行描述
promptGuidelines 可选,追加到 Guidelines 部分的指南
parameters TypeBox Schema 定义参数
prepareArguments 可选,Schema 验证前的参数兼容性处理
execute 工具执行函数
renderCall 可选,自定义工具调用渲染
renderResult 可选,自定义工具结果渲染

30.2 execute 函数签名

typescript 复制代码
async execute(
  toolCallId: string,           // 工具调用 ID
  params: TParams,              // Schema 验证后的参数
  signal: AbortSignal,          // 中断信号
  onUpdate?: (update: ToolUpdate) => void,  // 流式进度回调
  ctx: ExtensionContext         // 扩展上下文
): Promise<ToolResult>

30.3 流式进度

通过 onUpdate 回调报告执行进度:

typescript 复制代码
async execute(toolCallId, params, signal, onUpdate, ctx) {
  onUpdate?.({ content: [{ type: "text", text: "Working..." }] });
  // ... 执行操作 ...
  onUpdate?.({ content: [{ type: "text", text: "Processing 50%..." }] });
  return {
    content: [{ type: "text", text: "Done" }],
    details: { result: "..." },
  };
}

30.4 返回值

typescript 复制代码
interface ToolResult {
  content: (TextContent | ImageContent)[];  // 工具输出内容
  details?: Record<string, any>;             // 扩展元数据(不发送给 LLM)
  isError?: boolean;                         // 是否为错误
}

30.5 动态工具注册

pi.registerTool() 在启动后也有效。可以在 session_start、命令处理程序或其他事件处理程序中调用。新工具在会话中立即刷新,无需 /reload

使用 pi.setActiveTools() 在运行时启用或禁用工具。


第 31 章 自定义 UI

31.1 ctx.ui 方法全览

对话方法(等待用户响应):

方法 说明
ctx.ui.select(title, options) 选择列表
ctx.ui.confirm(title, message) 是/否确认
ctx.ui.input(title, placeholder) 自由文本输入
ctx.ui.editor(title, prefill) 多行文本编辑器

即发即忘方法(不等待响应):

方法 说明
ctx.ui.notify(message, type) 通知(info/warning/error)
ctx.ui.setStatus(key, text) 设置页脚状态
ctx.ui.setWidget(key, lines, options?) 设置编辑器上方/下方小组件
ctx.ui.setTitle(title) 设置终端窗口标题
ctx.ui.setEditorText(text) 设置编辑器文本

TUI 方法(交互模式完整功能):

方法 说明
ctx.ui.custom(factory, options?) 自定义 TUI 组件
ctx.ui.setWorkingIndicator(config?) 自定义工作指示器
ctx.ui.setFooter(factory?) 自定义页脚
ctx.ui.setHeader(factory?) 自定义头部
ctx.ui.setEditorComponent(factory?) 自定义编辑器
ctx.ui.setToolsExpanded(expanded) 展开/折叠工具输出
ctx.ui.getEditorText() 获取编辑器文本
ctx.ui.pasteToEditor(content) 粘贴到编辑器
ctx.ui.setTheme(themeName) 设置主题
ctx.ui.getAllThemes() 获取所有可用主题
ctx.ui.getTheme() 获取当前主题

31.2 自定义组件模式

选择对话框(SelectList + DynamicBorder)
typescript 复制代码
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";

pi.registerCommand("pick", {
  handler: async (_args, ctx) => {
    const items: SelectItem[] = [
      { value: "opt1", label: "Option 1", description: "First option" },
      { value: "opt2", label: "Option 2" },
    ];

    const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
      const container = new Container();
      container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
      container.addChild(new Text(theme.fg("accent", theme.bold("Pick an Option")), 1, 0));

      const selectList = new SelectList(items, Math.min(items.length, 10), {
        selectedPrefix: (t) => theme.fg("accent", t),
        selectedText: (t) => theme.fg("accent", t),
        description: (t) => theme.fg("muted", t),
      });
      selectList.onSelect = (item) => done(item.value);
      selectList.onCancel = () => done(null);
      container.addChild(selectList);

      container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
      return {
        render: (w) => container.render(w),
        invalidate: () => container.invalidate(),
        handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); },
      };
    });

    if (result) ctx.ui.notify(`Selected: ${result}`, "info");
  },
});
异步操作与取消(BorderedLoader)
typescript 复制代码
import { BorderedLoader } from "@mariozechner/pi-coding-agent";

pi.registerCommand("fetch", {
  handler: async (_args, ctx) => {
    const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
      const loader = new BorderedLoader(tui, theme, "Fetching data...");
      loader.onAbort = () => done(null);
      fetchData(loader.signal)
        .then((data) => done(data))
        .catch(() => done(null));
      return loader;
    });
  },
});
持久状态指示器
typescript 复制代码
// 设置状态(显示在页脚)
ctx.ui.setStatus("my-ext", ctx.ui.theme.fg("accent", "● active"));

// 清除状态
ctx.ui.setStatus("my-ext", undefined);
工作指示器自定义
typescript 复制代码
// 静态指示器
ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] });

// 自定义动画指示器
ctx.ui.setWorkingIndicator({
  frames: [
    ctx.ui.theme.fg("dim", "·"),
    ctx.ui.theme.fg("accent", "●"),
  ],
  intervalMs: 120,
});

// 隐藏指示器
ctx.ui.setWorkingIndicator({ frames: [] });

// 恢复默认
ctx.ui.setWorkingIndicator();
编辑器上方/下方小组件
typescript 复制代码
// 简单字符串数组
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);

// 编辑器下方
ctx.ui.setWidget("my-widget", ["Line 1"], { placement: "belowEditor" });

// 带主题
ctx.ui.setWidget("my-widget", (_tui, theme) => {
  return {
    render: () => items.map(i => theme.fg(i.done ? "success" : "dim", i.text)),
    invalidate: () => {},
  };
});

// 清除
ctx.ui.setWidget("my-widget", undefined);
自定义页脚
typescript 复制代码
ctx.ui.setFooter((tui, theme, footerData) => ({
  invalidate() {},
  render(width: number): string[] {
    return [`${ctx.model?.id} (${footerData.getGitBranch() || "no git"})`];
  },
  dispose: footerData.onBranchChange(() => tui.requestRender()),
}));

ctx.ui.setFooter(undefined); // 恢复默认
自定义编辑器(Vim 模式)
typescript 复制代码
import { CustomEditor, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";

type Mode = "normal" | "insert";

class VimEditor extends CustomEditor {
  private mode: Mode = "insert";

  handleInput(data: string): void {
    if (matchesKey(data, "escape")) {
      if (this.mode === "insert") { this.mode = "normal"; return; }
      super.handleInput(data); return;
    }
    if (this.mode === "insert") { super.handleInput(data); return; }
    switch (data) {
      case "i": this.mode = "insert"; return;
      case "h": super.handleInput("\x1b[D"); return;
      case "j": super.handleInput("\x1b[B"); return;
      case "k": super.handleInput("\x1b[A"); return;
      case "l": super.handleInput("\x1b[C"); return;
    }
    if (data.length === 1 && data.charCodeAt(0) >= 32) return;
    super.handleInput(data);
  }

  render(width: number): string[] {
    const lines = super.render(width);
    if (lines.length > 0) {
      const label = this.mode === "normal" ? " NORMAL " : " INSERT ";
      const lastLine = lines[lines.length - 1]!;
      lines[lines.length - 1] = truncateToWidth(lastLine, width - label.length, "") + label;
    }
    return lines;
  }
}

export default function (pi: ExtensionAPI) {
  pi.on("session_start", (_event, ctx) => {
    ctx.ui.setEditorComponent((tui, theme, keybindings) =>
      new VimEditor(theme, keybindings)
    );
  });
}

关键点:

  • 继承 CustomEditor(非基础 Editor)以获得应用快捷键
  • 对不处理的按键调用 super.handleInput(data)
  • setEditorComponent 接收接收 tuithemekeybindings 的工厂函数
  • 传入 undefined 恢复默认编辑器

第八部分:TUI 组件开发

第 32 章 TUI 组件系统

32.1 Component 接口

所有组件实现以下接口:

typescript 复制代码
interface Component {
  render(width: number): string[];
  handleInput?(data: string): void;
  wantsKeyRelease?: boolean;
  invalidate(): void;
}
方法 说明
render(width) 返回字符串数组(每行一个)。每行不得超过 width
handleInput?(data) 当组件有焦点时接收键盘输入
wantsKeyRelease? 如果为 true,组件接收按键释放事件(Kitty 协议)。默认:false
invalidate() 清除缓存的渲染状态。主题更改时调用

TUI 在每行渲染输出末尾附加完整的 SGR 重置和 OSC 8 重置。样式不跨行传递。如果发出带样式的多行文本,请逐行重新应用样式或使用 wrapTextWithAnsi() 保留每行的样式。

32.2 Focusable 接口与 IME 支持

显示文本光标并需要 IME(输入法编辑器)支持的组件应实现 Focusable 接口:

typescript 复制代码
import { CURSOR_MARKER, type Component, type Focusable } from "@mariozechner/pi-tui";

class MyInput implements Component, Focusable {
  focused: boolean = false;  // TUI 在焦点变化时设置

  render(width: number): string[] {
    const marker = this.focused ? CURSOR_MARKER : "";
    return [`> ${beforeCursor}${marker}\x1b[7m${atCursor}\x1b[27m${afterCursor}`];
  }
}

Focusable 组件有焦点时,TUI:

  1. 在组件上设置 focused = true
  2. 扫描渲染输出中的 CURSOR_MARKER(零宽度 APC 转义序列)
  3. 在该位置放置硬件终端光标
  4. 显示硬件光标

这使 IME 候选窗口出现在正确的位置,支持 CJK 输入法。

32.3 容器组件与嵌入式输入

当容器组件(对话框、选择器等)包含 InputEditor 子组件时,容器必须实现 Focusable 并将焦点状态传播给子组件:

typescript 复制代码
import { Container, type Focusable, Input } from "@mariozechner/pi-tui";

class SearchDialog extends Container implements Focusable {
  private searchInput: Input;

  private _focused = false;
  get focused(): boolean { return this._focused; }
  set focused(value: boolean) {
    this._focused = value;
    this.searchInput.focused = value;  // 传播给子组件
  }

  constructor() {
    super();
    this.searchInput = new Input();
    this.addChild(this.searchInput);
  }
}

32.4 使用组件

在 Extension 中 通过 ctx.ui.custom()

typescript 复制代码
pi.on("session_start", async (_event, ctx) => {
  const handle = ctx.ui.custom(myComponent);
  // handle.requestRender() --- 触发重新渲染
  // handle.close() --- 恢复正常 UI
});

在自定义工具中 通过 pi.ui.custom()

typescript 复制代码
async execute(toolCallId, params, onUpdate, ctx, signal) {
  const handle = pi.ui.custom(myComponent);
  handle.close();
}

32.5 Overlay

Overlay 在不清除屏幕的情况下在现有内容上渲染组件。传入 { overlay: true }

typescript 复制代码
const result = await ctx.ui.custom<string | null>(
  (tui, theme, keybindings, done) => new MyDialog({ onClose: done }),
  { overlay: true }
);

定位和尺寸使用 overlayOptions

typescript 复制代码
const result = await ctx.ui.custom<string | null>(
  (tui, theme, keybindings, done) => new SidePanel({ onClose: done }),
  {
    overlay: true,
    overlayOptions: {
      width: "50%",
      minWidth: 40,
      maxHeight: "80%",
      anchor: "right-center",
      offsetX: -2,
      margin: 2,
      visible: (termWidth, termHeight) => termWidth >= 80,
    },
    onHandle: (handle) => {
      // handle.setHidden(true/false)
      // handle.hide()
    },
  }
);

Overlay 组件在关闭时被处置。不要重用引用------创建新实例。


第 33 章 内置组件

33.1 Text

多行文本,带自动换行。

typescript 复制代码
const text = new Text(
  "Hello World",    // 内容
  1,                // paddingX(默认:1)
  1,                // paddingY(默认:1)
  (s) => bgGray(s)  // 可选背景函数
);
text.setText("Updated");

33.2 Box

带填充和背景色的容器。

typescript 复制代码
const box = new Box(1, 1, (s) => bgGray(s));
box.addChild(new Text("Content", 0, 0));
box.setBgFn((s) => bgBlue(s));

33.3 Container

垂直分组子组件。

typescript 复制代码
const container = new Container();
container.addChild(component1);
container.addChild(component2);
container.removeChild(component1);

33.4 Spacer

空垂直空间。

typescript 复制代码
const spacer = new Spacer(2);  // 2 行空行

33.5 Markdown

渲染带语法高亮的 Markdown。

typescript 复制代码
const md = new Markdown("# Title\n\nSome **bold** text", 1, 1, theme);
md.setText("Updated markdown");

33.6 Image

在支持的终端中渲染图片(Kitty、iTerm2、Ghostty、WezTerm)。

typescript 复制代码
const image = new Image(
  base64Data,
  "image/png",
  theme,
  { maxWidthCells: 80, maxHeightCells: 24 }
);

33.7 SelectList

选择列表组件,支持搜索和滚动。

typescript 复制代码
const selectList = new SelectList(items, Math.min(items.length, 10), {
  selectedPrefix: (t) => theme.fg("accent", t),
  selectedText: (t) => theme.fg("accent", t),
  description: (t) => theme.fg("muted", t),
  scrollInfo: (t) => theme.fg("dim", t),
  noMatch: (t) => theme.fg("warning", t),
});
selectList.onSelect = (item) => done(item.value);
selectList.onCancel = () => done(null);

33.8 SettingsList

设置开关列表。

typescript 复制代码
const settingsList = new SettingsList(
  items,
  Math.min(items.length + 2, 15),
  getSettingsListTheme(),
  (id, newValue) => { /* 处理值变更 */ },
  () => done(undefined),
  { enableSearch: true },
);

33.9 BorderedLoader

带边框的加载器,显示旋转器并处理 Escape 取消。

typescript 复制代码
const loader = new BorderedLoader(tui, theme, "Fetching data...");
loader.onAbort = () => done(null);

第 34 章 键盘输入

34.1 matchesKey()

使用 matchesKey() 进行按键检测:

typescript 复制代码
import { matchesKey, Key } from "@mariozechner/pi-tui";

handleInput(data: string) {
  if (matchesKey(data, Key.up)) {
    this.selectedIndex--;
  } else if (matchesKey(data, Key.enter)) {
    this.onSelect?.(this.selectedIndex);
  } else if (matchesKey(data, Key.escape)) {
    this.onCancel?.();
  } else if (matchesKey(data, Key.ctrl("c"))) {
    // Ctrl+C
  }
}

34.2 Key 标识符

  • 基础键:Key.enterKey.escapeKey.tabKey.spaceKey.backspaceKey.deleteKey.homeKey.end
  • 方向键:Key.upKey.downKey.leftKey.right
  • 修饰组合:Key.ctrl("c")Key.shift("tab")Key.alt("left")Key.ctrlShift("p")
  • 字符串格式也有效:"enter""ctrl+c""shift+tab""ctrl+shift+p"

第 35 章 行宽与工具函数

35.1 行宽限制

关键render() 返回的每行不得超过 width 参数。

typescript 复制代码
import { visibleWidth, truncateToWidth } from "@mariozechner/pi-tui";

render(width: number): string[] {
  return [truncateToWidth(this.text, width)];
}

35.2 工具函数

函数 说明
visibleWidth(str) 获取显示宽度(忽略 ANSI 码)
truncateToWidth(str, width, ellipsis?) 截断到指定宽度,可选省略号
wrapTextWithAnsi(str, width) 保留 ANSI 码的自动换行

第 36 章 主题集成

36.1 前景色

使用 theme.fg(color, text)

分类 颜色
通用 textaccentmuteddim
状态 successerrorwarning
边框 borderborderAccentborderMuted
消息 userMessageTextcustomMessageTextcustomMessageLabel
工具 toolTitletoolOutput
差异 toolDiffAddedtoolDiffRemovedtoolDiffContext
Markdown mdHeadingmdLinkmdLinkUrlmdCodemdCodeBlockmdCodeBlockBordermdQuotemdQuoteBordermdHrmdListBullet
语法 syntaxCommentsyntaxKeywordsyntaxFunctionsyntaxVariablesyntaxStringsyntaxNumbersyntaxTypesyntaxOperatorsyntaxPunctuation
思维 thinkingOffthinkingMinimalthinkingLowthinkingMediumthinkingHighthinkingXhigh
模式 bashMode

36.2 背景色

使用 theme.bg(color, text)selectedBguserMessageBgcustomMessageBgtoolPendingBgtoolSuccessBgtoolErrorBg

36.3 Markdown 主题

typescript 复制代码
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
import { Markdown } from "@mariozechner/pi-tui";

renderResult(result, options, theme, context) {
  const mdTheme = getMarkdownTheme();
  return new Markdown(result.details.markdown, 0, 0, mdTheme);
}

36.4 失效与主题变更

当主题更改时,TUI 调用所有组件的 invalidate()。组件必须正确实现 invalidate() 以确保主题更改生效。

问题:如果组件预烘焙主题颜色到字符串并缓存,缓存的字符串包含旧主题的 ANSI 码。

解决方案 :使用主题颜色构建内容的组件必须在 invalidate() 被调用时重建内容:

typescript 复制代码
class GoodComponent extends Container {
  private message: string;
  private content: Text;

  constructor(message: string) {
    super();
    this.message = message;
    this.content = new Text("", 1, 0);
    this.addChild(this.content);
    this.updateDisplay();
  }

  private updateDisplay(): void {
    this.content.setText(theme.fg("accent", this.message));
  }

  override invalidate(): void {
    super.invalidate();
    this.updateDisplay();
  }
}

不需要此模式的情况:使用主题回调时、简单容器时、无状态渲染时。


第 37 章 性能与缓存

37.1 渲染缓存

在可能时缓存渲染输出:

typescript 复制代码
class CachedComponent {
  private cachedWidth?: number;
  private cachedLines?: string[];

  render(width: number): string[] {
    if (this.cachedLines && this.cachedWidth === width) {
      return this.cachedLines;
    }
    // ... 计算行 ...
    this.cachedWidth = width;
    this.cachedLines = lines;
    return lines;
  }

  invalidate(): void {
    this.cachedWidth = undefined;
    this.cachedLines = undefined;
  }
}

37.2 触发重绘

状态更改后调用 invalidate(),然后 handle.requestRender() 触发重新渲染。

handleInput 中更新状态后调用 tui.requestRender()

typescript 复制代码
handleInput(data: string): void {
  if (matchesKey(data, Key.up)) {
    this.selectedIndex--;
    this.invalidate();
    tui.requestRender();  // 触发重绘
  }
}

第九部分:SDK 编程接口

第 38 章 SDK 快速开始

38.1 安装

bash 复制代码
npm install @mariozechner/pi-coding-agent

SDK 包含在主包中,无需单独安装。

38.2 最小示例

typescript 复制代码
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@mariozechner/pi-coding-agent";

const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);

const { session } = await createAgentSession({
  sessionManager: SessionManager.inMemory(),
  authStorage,
  modelRegistry,
});

session.subscribe((event) => {
  if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});

await session.prompt("What files are in the current directory?");

38.3 核心概念

createAgentSession() 是创建单个 AgentSession 的主工厂函数。它使用 ResourceLoader 提供扩展、技能、提示词模板、主题和上下文文件。不提供时使用 DefaultResourceLoader 进行标准发现。


第 39 章 createAgentSession()

39.1 基本用法

typescript 复制代码
// 最简:使用 DefaultResourceLoader
const { session } = await createAgentSession();

// 自定义:覆盖特定选项
const { session } = await createAgentSession({
  model: myModel,
  tools: [readTool, bashTool],
  sessionManager: SessionManager.inMemory(),
});

39.2 返回值

typescript 复制代码
interface CreateAgentSessionResult {
  session: AgentSession;
  extensionsResult: LoadExtensionsResult;
  modelFallbackMessage?: string;  // 如果会话模型无法恢复时的警告
}

第 40 章 AgentSession API

40.1 概述

AgentSession 管理 Agent 生命周期、消息历史、模型状态、压缩和事件流。

40.2 提示与消息排队

typescript 复制代码
// 发送提示并等待完成
session.prompt("What files are here?");

// 带图片
session.prompt("What's in this image?", {
  images: [{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } }]
});

// 流式期间:必须指定排队方式
session.prompt("Stop and do this", { streamingBehavior: "steer" });
session.prompt("After done, check X", { streamingBehavior: "followUp" });

PromptOptions 选项:

  • expandPromptTemplates:是否展开提示词模板
  • images:附加图片
  • streamingBehavior"steer""followUp"
  • source:输入源("interactive""rpc""extension"
  • preflightResult:回调,提示被接受时为 true,被拒绝时为 false

显式排队:

typescript 复制代码
await session.steer("New instruction");
await session.followUp("After you're done, also do this");

40.3 事件订阅

typescript 复制代码
const unsubscribe = session.subscribe((event) => {
  switch (event.type) {
    case "message_update":
      if (event.assistantMessageEvent.type === "text_delta") {
        process.stdout.write(event.assistantMessageEvent.delta);
      }
      break;
    case "tool_execution_start":
      console.log(`Tool: ${event.toolName}`);
      break;
    case "tool_execution_end":
      console.log(`Result: ${event.isError ? "error" : "success"}`);
      break;
    case "agent_end":
      console.log(`Messages: ${event.messages.length}`);
      break;
  }
});

40.4 模型控制

typescript 复制代码
await session.setModel(newModel);
session.setThinkingLevel("medium");
const result = await session.cycleModel();
const level = session.cycleThinkingLevel();

40.5 状态访问

typescript 复制代码
session.agent         // Agent 实例
session.model         // 当前模型
session.thinkingLevel // 当前思维级别
session.messages      // AgentMessage[]
session.isStreaming   // 是否正在流式输出

40.6 导航与压缩

typescript 复制代码
await session.navigateTree("entry-id-456", {
  summarize: true,
  customInstructions: "Focus on error handling",
});

await session.compact("Focus on recent changes");
session.abortCompaction();
await session.abort();
session.dispose();

第 41 章 AgentSessionRuntime

41.1 概述

当你需要替换活动会话并重建 cwd 绑定的运行时状态时,使用运行时 API。这是内置交互、打印和 RPC 模式使用的同一层。

41.2 创建运行时

typescript 复制代码
import {
  type CreateAgentSessionRuntimeFactory,
  createAgentSessionFromServices,
  createAgentSessionRuntime,
  createAgentSessionServices,
  getAgentDir,
  SessionManager,
} from "@mariozechner/pi-coding-agent";

const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
  const services = await createAgentSessionServices({ cwd });
  return {
    ...(await createAgentSessionFromServices({
      services,
      sessionManager,
      sessionStartEvent,
    })),
    services,
    diagnostics: services.diagnostics,
  };
};

const runtime = await createAgentSessionRuntime(createRuntime, {
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  sessionManager: SessionManager.create(process.cwd()),
});

41.3 会话替换

AgentSessionRuntime 拥有跨以下操作的活动会话替换:

  • newSession()
  • switchSession()
  • fork()
  • clone(通过 fork(entryId, { position: "at" })
  • importFromJsonl()

重要行为

  • 操作后 runtime.session 改变
  • 事件订阅绑定到特定 AgentSession,替换后需重新订阅
  • 如使用扩展,为新会话调用 runtime.session.bindExtensions(...) 再次绑定
typescript 复制代码
let session = runtime.session;
let unsubscribe = session.subscribe(() => {});

await runtime.newSession();

unsubscribe();
session = runtime.session;
unsubscribe = session.subscribe(() => {});

第 42 章 配置选项

42.1 目录配置

typescript 复制代码
const { session } = await createAgentSession({
  cwd: process.cwd(),           // 工作目录
  agentDir: "~/.pi/agent",      // 全局配置目录
});

cwd 用于 DefaultResourceLoader 的项目扩展发现、项目技能、项目提示词、上下文文件和会话目录命名。

agentDir 用于全局扩展、全局技能、全局提示词、设置、自定义模型、凭证和会话。

42.2 模型配置

typescript 复制代码
import { getModel } from "@mariozechner/pi-ai";
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";

const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);

const opus = getModel("anthropic", "claude-opus-4-5");

const { session } = await createAgentSession({
  model: opus,
  thinkingLevel: "medium",
  scopedModels: [
    { model: opus, thinkingLevel: "high" },
    { model: haiku, thinkingLevel: "off" },
  ],
  authStorage,
  modelRegistry,
});

如果未提供模型:尝试从会话恢复 → 使用设置中的默认值 → 回退到第一个可用模型。

42.3 API Key 与 OAuth

typescript 复制代码
// 运行时 API Key 覆盖(不持久化)
authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");

// 自定义存储位置
const customAuth = AuthStorage.create("/my/app/auth.json");
const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");

// 无自定义 models.json(仅内置模型)
const simpleRegistry = ModelRegistry.inMemory(authStorage);

42.4 系统提示词覆盖

typescript 复制代码
const loader = new DefaultResourceLoader({
  systemPromptOverride: () => "You are a helpful assistant.",
});
await loader.reload();

const { session } = await createAgentSession({ resourceLoader: loader });

42.5 工具配置

typescript 复制代码
import {
  codingTools,   // read, bash, edit, write(默认)
  readOnlyTools, // read, grep, find, ls
  createCodingTools,   // 特定 cwd 的工具工厂
  createReadOnlyTools,
} from "@mariozechner/pi-coding-agent";

// 使用内置工具集
const { session } = await createAgentSession({ tools: readOnlyTools });

// 自定义 cwd 时必须使用工厂
const cwd = "/path/to/project";
const { session } = await createAgentSession({
  cwd,
  tools: createCodingTools(cwd),
});

自定义工具:

typescript 复制代码
import { Type } from "typebox";
import { defineTool } from "@mariozechner/pi-coding-agent";

const myTool = defineTool({
  name: "my_tool",
  label: "My Tool",
  description: "Does something useful",
  parameters: Type.Object({
    input: Type.String({ description: "Input value" }),
  }),
  execute: async (_toolCallId, params) => ({
    content: [{ type: "text", text: `Result: ${params.input}` }],
    details: {},
  }),
});

const { session } = await createAgentSession({ customTools: [myTool] });

42.6 Extension 加载

typescript 复制代码
const loader = new DefaultResourceLoader({
  additionalExtensionPaths: ["/path/to/my-extension.ts"],
  extensionFactories: [
    (pi) => {
      pi.on("agent_start", () => {
        console.log("[Inline Extension] Agent starting");
      });
    },
  ],
});
await loader.reload();

const { session } = await createAgentSession({ resourceLoader: loader });

事件总线 :Extension 通过 pi.events 通信。传入共享 eventBus

typescript 复制代码
import { createEventBus, DefaultResourceLoader } from "@mariozechner/pi-coding-agent";

const eventBus = createEventBus();
const loader = new DefaultResourceLoader({ eventBus });
await loader.reload();

eventBus.on("my-extension:status", (data) => console.log(data));

42.7 技能覆盖

typescript 复制代码
const customSkill: Skill = {
  name: "my-skill",
  description: "Custom instructions",
  filePath: "/path/to/SKILL.md",
  baseDir: "/path/to",
  source: "custom",
};

const loader = new DefaultResourceLoader({
  skillsOverride: (current) => ({
    skills: [...current.skills, customSkill],
    diagnostics: current.diagnostics,
  }),
});

42.8 上下文文件覆盖

typescript 复制代码
const loader = new DefaultResourceLoader({
  agentsFilesOverride: (current) => ({
    agentsFiles: [
      ...current.agentsFiles,
      { path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" },
    ],
  }),
});

42.9 斜杠命令 / PromptTemplate

typescript 复制代码
const customCommand: PromptTemplate = {
  name: "deploy",
  description: "Deploy the application",
  source: "(custom)",
  content: "# Deploy\n\n1. Build\n2. Test\n3. Deploy",
};

const loader = new DefaultResourceLoader({
  promptsOverride: (current) => ({
    prompts: [...current.prompts, customCommand],
    diagnostics: current.diagnostics,
  }),
});

42.10 会话管理

typescript 复制代码
import { SessionManager } from "@mariozechner/pi-coding-agent";

// 内存(不持久化)
const { session } = await createAgentSession({
  sessionManager: SessionManager.inMemory(),
});

// 新持久化会话
const { session: persisted } = await createAgentSession({
  sessionManager: SessionManager.create(process.cwd()),
});

// 继续最近会话
const { session: continued, modelFallbackMessage } = await createAgentSession({
  sessionManager: SessionManager.continueRecent(process.cwd()),
});

// 打开指定文件
const { session: opened } = await createAgentSession({
  sessionManager: SessionManager.open("/path/to/session.jsonl"),
});

// 列出会话
const sessions = await SessionManager.list(process.cwd());
const allSessions = await SessionManager.listAll();

42.11 设置管理

typescript 复制代码
import { SettingsManager } from "@mariozechner/pi-coding-agent";

// 默认:从文件加载
const { session } = await createAgentSession({
  settingsManager: SettingsManager.create(),
});

// 带覆盖
const settingsManager = SettingsManager.create();
settingsManager.applyOverrides({
  compaction: { enabled: false },
});
const { session } = await createAgentSession({ settingsManager });

// 内存(无文件 I/O,用于测试)
const { session } = await createAgentSession({
  settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
  sessionManager: SessionManager.inMemory(),
});

设置从两个位置加载并合并:

  1. 全局:~/.pi/agent/settings.json
  2. 项目:<cwd>/.pi/settings.json

项目覆盖全局。嵌套对象合并键。


第 43 章 ResourceLoader

使用 DefaultResourceLoader 发现扩展、技能、提示词、主题和上下文文件:

typescript 复制代码
import { DefaultResourceLoader, getAgentDir } from "@mariozechner/pi-coding-agent";

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
});
await loader.reload();

const extensions = loader.getExtensions();
const skills = loader.getSkills();
const prompts = loader.getPrompts();
const themes = loader.getThemes();
const contextFiles = loader.getAgentsFiles().agentsFiles;

第 44 章 运行模式

44.1 InteractiveMode

完整的 TUI 交互模式,包含编辑器、聊天历史和所有内置命令:

typescript 复制代码
import {
  type CreateAgentSessionRuntimeFactory,
  createAgentSessionFromServices,
  createAgentSessionRuntime,
  createAgentSessionServices,
  getAgentDir,
  InteractiveMode,
  SessionManager,
} from "@mariozechner/pi-coding-agent";

const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
  const services = await createAgentSessionServices({ cwd });
  return {
    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
    services,
    diagnostics: services.diagnostics,
  };
};

const runtime = await createAgentSessionRuntime(createRuntime, {
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  sessionManager: SessionManager.create(process.cwd()),
});

const mode = new InteractiveMode(runtime, {
  migratedProviders: [],
  modelFallbackMessage: undefined,
  initialMessage: "Hello",
  initialImages: [],
  initialMessages: [],
});

await mode.run();

44.2 runPrintMode

单次模式:发送提示,输出结果,退出:

typescript 复制代码
import { runPrintMode } from "@mariozechner/pi-coding-agent";

await runPrintMode(runtime, {
  mode: "text",
  initialMessage: "Hello",
  initialImages: [],
  messages: ["Follow up"],
});

44.3 runRpcMode

JSON-RPC 模式,用于子进程集成:

typescript 复制代码
import { runRpcMode } from "@mariozechner/pi-coding-agent";

await runRpcMode(runtime);

详见第 45-50 章的 RPC 文档。

44.4 SDK 导出清单

typescript 复制代码
// 工厂
createAgentSession
createAgentSessionRuntime
AgentSessionRuntime

// 认证与模型
AuthStorage
ModelRegistry

// 资源加载
DefaultResourceLoader
type ResourceLoader
createEventBus

// 辅助
defineTool

// 会话管理
SessionManager
SettingsManager

// 内置工具
codingTools
readOnlyTools
readTool, bashTool, editTool, writeTool
grepTool, findTool, lsTool

// 工具工厂
createCodingTools
createReadOnlyTools
createReadTool, createBashTool, createEditTool, createWriteTool
createGrepTool, createFindTool, createLsTool

// 类型
type CreateAgentSessionOptions
type CreateAgentSessionResult
type ExtensionFactory
type ExtensionAPI
type ToolDefinition
type Skill
type PromptTemplate
type Tool

第十部分:RPC 模式与 JSON 模式

第 45 章 RPC 协议

45.1 启动方式

bash 复制代码
pi --mode rpc [options]

常用选项:

  • --provider <name>:设置 LLM Provider
  • --model <pattern>:模型模式或 ID
  • --no-session:禁用会话持久化
  • --session-dir <path>:自定义会话存储目录

45.2 协议概述

  • 命令:发送到 stdin 的 JSON 对象,每行一个
  • 响应 :带有 type: "response" 的 JSON 对象,指示命令成功/失败
  • 事件:Agent 事件以 JSON 行流式输出到 stdout

所有命令支持可选的 id 字段用于请求/响应关联。如果提供,对应响应将包含相同的 id

45.3 帧定界

RPC 模式使用严格的 JSONL 语义,LF(\n)是唯一的记录分隔符。

客户端注意事项:

  • 仅在 \n 处分割记录
  • 接受可选的 \r\n 输入(剥离尾随 \r
  • 不要使用将 Unicode 分隔符视为新行的通用行读取器

特别地,Node readline 不符合 RPC 模式的协议规范,因为它也会在 U+2028U+2029 处分割(它们在 JSON 字符串中是合法的)。


第 46 章 RPC 命令

46.1 提示命令

prompt --- 发送用户提示:

json 复制代码
{"id": "req-1", "type": "prompt", "message": "Hello, world!"}

带图片:

json 复制代码
{"type": "prompt", "message": "What's in this image?", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}

流式期间必须指定 streamingBehavior

  • "steer":当前 turn 工具调用后投递
  • "followUp":Agent 完成后投递

响应:

json 复制代码
{"id": "req-1", "type": "response", "command": "prompt", "success": true}

steer --- 排队 steering 消息:

json 复制代码
{"type": "steer", "message": "Stop and do this instead"}

follow_up --- 排队 follow-up 消息:

json 复制代码
{"type": "follow_up", "message": "After you're done, also do this"}

abort --- 中止当前 Agent 操作:

json 复制代码
{"type": "abort"}

new_session --- 开始新会话:

json 复制代码
{"type": "new_session"}
{"type": "new_session", "parentSession": "/path/to/parent-session.jsonl"}

46.2 状态命令

get_state --- 获取当前会话状态:

json 复制代码
{"type": "get_state"}

响应包含:modelthinkingLevelisStreamingisCompactingsteeringModefollowUpModesessionFilesessionIdsessionNameautoCompactionEnabledmessageCountpendingMessageCount

get_messages --- 获取所有消息:

json 复制代码
{"type": "get_messages"}

46.3 模型命令

set_model --- 切换到指定模型:

json 复制代码
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}

cycle_model --- 循环到下一个可用模型:

json 复制代码
{"type": "cycle_model"}

get_available_models --- 列出所有已配置模型:

json 复制代码
{"type": "get_available_models"}

46.4 思维命令

set_thinking_level --- 设置推理/思维级别:

json 复制代码
{"type": "set_thinking_level", "level": "high"}

级别:"off""minimal""low""medium""high""xhigh"

cycle_thinking_level --- 循环可用思维级别:

json 复制代码
{"type": "cycle_thinking_level"}

46.5 队列模式命令

set_steering_mode --- 控制 steering 消息投递方式:

json 复制代码
{"type": "set_steering_mode", "mode": "one-at-a-time"}

模式:"all""one-at-a-time"

set_follow_up_mode --- 控制 follow-up 消息投递方式:

json 复制代码
{"type": "set_follow_up_mode", "mode": "one-at-a-time"}

46.6 压缩命令

compact --- 手动压缩上下文:

json 复制代码
{"type": "compact"}
{"type": "compact", "customInstructions": "Focus on code changes"}

响应包含:summaryfirstKeptEntryIdtokensBeforedetails

set_auto_compaction --- 启用/禁用自动压缩:

json 复制代码
{"type": "set_auto_compaction", "enabled": true}

46.7 重试命令

set_auto_retry --- 启用/禁用自动重试:

json 复制代码
{"type": "set_auto_retry", "enabled": true}

abort_retry --- 中止进行中的重试:

json 复制代码
{"type": "abort_retry"}

46.8 Bash 命令

bash --- 执行 Shell 命令并添加输出到上下文:

json 复制代码
{"type": "bash", "command": "ls -la"}

响应包含:outputexitCodecancelledtruncatedfullOutputPath(截断时)。

bash 结果如何到达 LLM :bash 命令立即执行并返回 BashResult。内部创建 BashExecutionMessage 存储在 Agent 消息状态中。下次 prompt 命令时,所有消息(包括 BashExecutionMessage)被转换后发送给 LLM:

复制代码
Ran `ls -la`
```
total 48
drwxr-xr-x ...
```

abort_bash --- 中止运行中的 bash 命令:

json 复制代码
{"type": "abort_bash"}

46.9 会话命令

get_session_stats --- 获取 Token 用量和费用统计:

json 复制代码
{"type": "get_session_stats"}

响应包含:sessionFilesessionId、消息计数、tokens(input/output/cacheRead/cacheWrite/total)、costcontextUsage(tokens/contextWindow/percent)。

export_html --- 导出会话为 HTML:

json 复制代码
{"type": "export_html"}
{"type": "export_html", "outputPath": "/tmp/session.html"}

switch_session --- 加载不同会话文件:

json 复制代码
{"type": "switch_session", "sessionPath": "/path/to/session.jsonl"}

fork --- 从之前的用户消息创建新分支:

json 复制代码
{"type": "fork", "entryId": "abc123"}

clone --- 复制当前活动分支到新会话:

json 复制代码
{"type": "clone"}

get_fork_messages --- 获取可用于分叉的用户消息:

json 复制代码
{"type": "get_fork_messages"}

get_last_assistant_text --- 获取最后一条助手消息的文本:

json 复制代码
{"type": "get_last_assistant_text"}

set_session_name --- 设置会话显示名称:

json 复制代码
{"type": "set_session_name", "name": "my-feature-work"}

46.10 命令查询

get_commands --- 获取可用命令(扩展命令、提示词模板和技能):

json 复制代码
{"type": "get_commands"}

响应包含:namedescriptionsource"extension" / "prompt" / "skill")、sourceInfo


第 47 章 RPC 事件流

47.1 事件类型

事件 说明
agent_start Agent 开始处理
agent_end Agent 完成(包含所有生成的消息)
turn_start 新 turn 开始
turn_end turn 完成(包含助手消息和工具结果)
message_start 消息开始
message_update 流式更新(text/thinking/toolcall delta)
message_end 消息完成
tool_execution_start 工具开始执行
tool_execution_update 工具执行进度(流式输出)
tool_execution_end 工具完成
queue_update 待处理 steering/follow-up 队列变更
compaction_start 压缩开始
compaction_end 压缩完成
auto_retry_start 自动重试开始
auto_retry_end 自动重试完成
extension_error Extension 抛出错误

47.2 message_update 流式事件

assistantMessageEvent 字段包含以下 delta 类型:

类型 说明
start 消息生成开始
text_start 文本内容块开始
text_delta 文本内容块
text_end 文本内容块结束
thinking_start 思维块开始
thinking_delta 思维内容块
thinking_end 思维块结束
toolcall_start 工具调用开始
toolcall_delta 工具调用参数块
toolcall_end 工具调用结束(包含完整 toolCall 对象)
done 消息完成(reason: "stop" / "length" / "toolUse"
error 错误发生(reason: "aborted" / "error"

47.3 压缩事件

compaction_startreason 字段:"manual""threshold""overflow"

compaction_end 包含:reasonresult(摘要详情)、abortedwillRetryerrorMessage

如果 reason"overflow" 且压缩成功,willRetrytrue,Agent 将自动重试提示。

47.4 重试事件

auto_retry_start 包含:attemptmaxAttemptsdelayMserrorMessage

auto_retry_end 包含:successattemptfinalError(最终失败时)。

47.5 扩展错误事件

json 复制代码
{
  "type": "extension_error",
  "extensionPath": "/path/to/extension.ts",
  "event": "tool_call",
  "error": "Error message..."
}

第 48 章 扩展 UI 子协议

48.1 概述

扩展通过 ctx.ui.select()ctx.ui.confirm() 等方法请求用户交互。在 RPC 模式中,这些通过 stdin/stdout 上的请求/响应子协议实现。

两类方法:

  • 对话方法selectconfirminputeditor):在 stdout 发出 extension_ui_request,阻塞直到客户端在 stdin 发回匹配 idextension_ui_response
  • 即发即忘方法notifysetStatussetWidgetsetTitleset_editor_text):在 stdout 发出请求,不期望响应

48.2 对话请求(stdout)

select --- 选择列表:

json 复制代码
{
  "type": "extension_ui_request",
  "id": "uuid-1",
  "method": "select",
  "title": "Allow dangerous command?",
  "options": ["Allow", "Block"],
  "timeout": 10000
}

confirm --- 是/否确认:

json 复制代码
{
  "type": "extension_ui_request",
  "id": "uuid-2",
  "method": "confirm",
  "title": "Clear session?",
  "message": "All messages will be lost.",
  "timeout": 5000
}

input --- 自由文本输入:

json 复制代码
{
  "type": "extension_ui_request",
  "id": "uuid-3",
  "method": "input",
  "title": "Enter a value",
  "placeholder": "type something..."
}

editor --- 多行文本编辑器:

json 复制代码
{
  "type": "extension_ui_request",
  "id": "uuid-4",
  "method": "editor",
  "title": "Edit some text",
  "prefill": "Line 1\nLine 2\nLine 3"
}

48.3 即发即忘请求(stdout)

notify --- 显示通知:

json 复制代码
{"type": "extension_ui_request", "id": "uuid-5", "method": "notify", "message": "Done!", "notifyType": "info"}

setStatus --- 设置页脚状态:

json 复制代码
{"type": "extension_ui_request", "id": "uuid-6", "method": "setStatus", "statusKey": "my-ext", "statusText": "Turn 3..."}

setWidget --- 设置编辑器上方/下方小组件:

json 复制代码
{"type": "extension_ui_request", "id": "uuid-7", "method": "setWidget", "widgetKey": "my-ext", "widgetLines": ["Line 1", "Line 2"]}

setTitle --- 设置终端窗口标题:

json 复制代码
{"type": "extension_ui_request", "id": "uuid-8", "method": "setTitle", "title": "pi - my project"}

set_editor_text --- 设置编辑器文本:

json 复制代码
{"type": "extension_ui_request", "id": "uuid-9", "method": "set_editor_text", "text": "prefilled text"}

48.4 响应(stdin)

值响应(select、input、editor):

json 复制代码
{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}

确认响应(confirm):

json 复制代码
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}

取消响应(任意对话方法):

json 复制代码
{"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}

48.5 RPC 模式下受限的方法

以下方法在 RPC 模式中不可用或退化:

  • custom() 返回 undefined
  • setWorkingMessage()setWorkingIndicator()setFooter()setHeader()setEditorComponent()setToolsExpanded() 为空操作
  • getEditorText() 返回 ""
  • getToolsExpanded() 返回 false
  • getAllThemes() 返回 []
  • getTheme() 返回 undefined
  • setTheme() 返回 { success: false, error: "..." }

第 49 章 错误处理

49.1 命令错误

失败命令返回 success: false

json 复制代码
{
  "type": "response",
  "command": "set_model",
  "success": false,
  "error": "Model not found: invalid/model"
}

49.2 解析错误

json 复制代码
{
  "type": "response",
  "command": "parse",
  "success": false,
  "error": "Failed to parse command: Unexpected token..."
}

第 50 章 RPC 类型定义

50.1 Model

json 复制代码
{
  "id": "claude-sonnet-4-20250514",
  "name": "Claude Sonnet 4",
  "api": "anthropic-messages",
  "provider": "anthropic",
  "reasoning": true,
  "input": ["text", "image"],
  "contextWindow": 200000,
  "maxTokens": 16384,
  "cost": { "input": 3.0, "output": 15.0, "cacheRead": 0.3, "cacheWrite": 3.75 }
}

50.2 UserMessage

json 复制代码
{
  "role": "user",
  "content": "Hello!",
  "timestamp": 1733234567890
}

50.3 AssistantMessage

json 复制代码
{
  "role": "assistant",
  "content": [
    {"type": "text", "text": "Hello! How can I help?"},
    {"type": "thinking", "thinking": "User is greeting me..."},
    {"type": "toolCall", "id": "call_123", "name": "bash", "arguments": {"command": "ls"}}
  ],
  "api": "anthropic-messages",
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514",
  "usage": { ... },
  "stopReason": "stop",
  "timestamp": 1733234567890
}

Stop reasons: "stop""length""toolUse""error""aborted"

50.4 ToolResultMessage

json 复制代码
{
  "role": "toolResult",
  "toolCallId": "call_123",
  "toolName": "bash",
  "content": [{"type": "text", "text": "total 48\ndrwxr-xr-x ..."}],
  "isError": false,
  "timestamp": 1733234567890
}

50.5 BashExecutionMessage

bash RPC 命令创建(非 LLM 工具调用):

json 复制代码
{
  "role": "bashExecution",
  "command": "ls -la",
  "output": "total 48\ndrwxr-xr-x ...",
  "exitCode": 0,
  "cancelled": false,
  "truncated": false,
  "timestamp": 1733234567890
}

第 51 章 JSON 事件流模式

51.1 启动方式

bash 复制代码
pi --mode json "Your prompt"

51.2 输出格式

输出所有会话事件为 JSON 行到 stdout。首行是会话头部,后跟事件:

json 复制代码
{"type":"session","version":3,"id":"uuid","timestamp":"...","cwd":"/path"}
{"type":"agent_start"}
{"type":"turn_start"}
{"type":"message_start","message":{"role":"assistant","content":[],...}}
{"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","delta":"Hello",...}}
{"type":"message_end","message":{...}}
{"type":"turn_end","message":{...},"toolResults":[]}
{"type":"agent_end","messages":[...]}

51.3 使用示例

bash 复制代码
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'

第十一部分:CLI 参考

第 52 章 完整 CLI 参考

52.1 命令格式

bash 复制代码
pi [options] [@files...] [messages...]

52.2 包管理命令

bash 复制代码
pi install <source> [-l]     # 安装包,-l 为项目级安装
pi remove <source> [-l]      # 移除包
pi uninstall <source> [-l]   # remove 的别名
pi update [source]           # 更新所有非固定的包
pi list                      # 列出已安装的包
pi config                    # 启用/禁用包资源

52.3 模式标志

标志 说明
(默认) 交互模式
-p, --print 打印响应后退出
--mode json 输出所有事件为 JSON 行
--mode rpc RPC 模式,用于进程集成
--export <in> [out] 导出会话为 HTML

打印模式下 pi 也读取管道 stdin 并合并到初始提示中:

bash 复制代码
cat README.md | pi -p "Summarize this text"

52.4 模型选项

选项 说明
--provider <name> Provider(anthropic、openai、google 等)
--model <pattern> 模型模式或 ID(支持 provider/id 和可选 :<thinking>
--api-key <key> API Key(覆盖环境变量)
--thinking <level> offminimallowmediumhighxhigh
--models <patterns> 逗号分隔的 Ctrl+P 循环模型模式
--list-models [search] 列出可用模型

52.5 会话选项

选项 说明
-c, --continue 继续最近会话
-r, --resume 浏览并选择会话
--session <path> 使用指定会话文件或部分 UUID
--fork <path> Fork 指定会话文件或部分 UUID
--session-dir <dir> 自定义会话存储目录
--no-session 临时模式(不保存)

52.6 工具选项

选项 说明
--tools <list> 启用指定内置工具(默认:read,bash,edit,write
--no-tools 禁用所有内置工具(扩展工具仍可用)

可用内置工具:readbasheditwritegrepfindls

52.7 资源选项

选项 说明
-e, --extension <source> 从路径、npm 或 git 加载扩展(可重复)
--no-extensions 禁用扩展发现
--skill <path> 加载技能(可重复)
--no-skills 禁用技能发现
--prompt-template <path> 加载提示词模板(可重复)
--no-prompt-templates 禁用提示词模板发现
--theme <path> 加载主题(可重复)
--no-themes 禁用主题发现

组合 --no-* 与显式标志以精确加载所需内容,忽略 settings.json(如 --no-extensions -e ./my-ext.ts)。

52.8 其他选项

选项 说明
--system-prompt <text> 替换默认提示词(上下文文件和技能仍追加)
--append-system-prompt <text> 追加到系统提示词
--verbose 强制详细启动
-h, --help 显示帮助
-v, --version 显示版本

52.9 文件参数

@ 前缀引用文件:

bash 复制代码
pi @prompt.md "Answer this"
pi -p @screenshot.png "What's in this image?"
pi @code.ts @test.ts "Review these files"

52.10 环境变量

变量 说明
PI_CODING_AGENT_DIR 覆盖配置目录(默认:~/.pi/agent
PI_PACKAGE_DIR 覆盖包目录
PI_SKIP_VERSION_CHECK 跳过启动时的版本检查
PI_CACHE_RETENTION 设为 long 以扩展 prompt cache(Anthropic: 1h, OpenAI: 24h)
VISUAL, EDITOR Ctrl+G 使用的外部编辑器

52.11 命令示例集

bash 复制代码
# 交互模式 + 初始提示
pi "List all .ts files in src/"

# 非交互
pi -p "Summarize this codebase"

# 非交互 + 管道 stdin
cat README.md | pi -p "Summarize this text"

# 不同模型
pi --provider openai --model gpt-4o "Help me refactor"

# 模型 + provider 前缀
pi --model openai/gpt-4o "Help me refactor"

# 模型 + 思维级别简写
pi --model sonnet:high "Solve this complex problem"

# 限制模型循环
pi --models "claude-*,gpt-4o"

# 只读模式
pi --tools read,grep,find,ls -p "Review the code"

# 高思维级别
pi --thinking high "Solve this complex problem"

第十二部分:开发与贡献

第 53 章 开发环境

53.1 仓库克隆与构建

bash 复制代码
git clone https://github.com/badlogic/pi-mono
cd pi-mono
npm install
npm run build

从源码运行:

bash 复制代码
/path/to/pi-mono/pi-test.sh

该脚本可从任意目录运行。pi 保留调用者的工作目录。

53.2 项目结构

复制代码
packages/
  ai/           # LLM Provider 抽象
  agent/        # Agent 循环和消息类型
  tui/          # 终端 UI 组件
  coding-agent/ # CLI 和交互模式

53.3 测试

bash 复制代码
./test.sh                         # 运行非 LLM 测试(不需要 API Key)
npm test                          # 运行所有测试
npm test -- test/specific.test.ts # 运行指定测试

53.4 调试

/debug(隐藏命令)写入 ~/.pi/agent/pi-debug.log

  • 渲染的 TUI 行(含 ANSI 码)
  • 发送给 LLM 的最后消息

53.5 路径解析

三种执行模式:npm 安装、独立二进制、tsx 源码。

始终使用 src/config.ts 获取包资源:

typescript 复制代码
import { getPackageDir, getThemeDir } from "./config.js";

不要直接使用 __dirname 获取包资源。


第 54 章 Fork 与品牌定制

54.1 package.json 配置

json 复制代码
{
  "piConfig": {
    "name": "pi",
    "configDir": ".pi"
  }
}

更改 nameconfigDirbin 字段以自定义 Fork。影响 CLI 横幅、配置路径和环境变量名称。


附录


附录 A:术语表

术语 英文 说明
pi mono pi mono 极简终端编码代理运行时
Provider Provider LLM 提供商(如 Anthropic、OpenAI、Google)
Extension Extension TypeScript 模块,扩展 pi 的行为
Skill Skill 按需加载的自包含能力包
Prompt Template Prompt Template 可复用的 Markdown 提示词片段
Theme Theme 定义 TUI 颜色的 JSON 文件
Pi Package Pi Package 将扩展、技能、模板、主题打包分发的单元
Session Session 会话,存储为 JSONL 文件的对话历史
Entry Entry 会话中的单条记录
Leaf Leaf 树中的当前位置(叶节点)
Compaction Compaction 压缩,总结较早消息以释放上下文窗口
Branch Summary Branch Summary 分支摘要,切换分支时保留被放弃路径的上下文
Steering Message Steering Message 流式期间排队的纠正性消息
Follow-up Message Follow-up Message Agent 完成后排入的后续消息
Turn Turn 一个 LLM 响应 + 工具调用 + 工具结果的完整周期
Split Turn Split Turn 压缩切割点落在 turn 中间的情况
Context Window Context Window 模型的上下文 token 容量
Thinking Level Thinking Level 推理/思维深度(off 到 xhigh)
Scope Models Scope Models Ctrl+P 可循环的模型集合
Tool Tool LLM 可调用的操作(read、write、edit、bash 等)
Content Block Content Block 消息中的内容单元(Text、Image、Thinking、ToolCall)
Usage Usage Token 用量和费用统计
ResourceLoader ResourceLoader 发现扩展、技能、模板、主题和上下文文件的组件
CustomEntry CustomEntry 扩展持久化状态条目(不参与 LLM 上下文)
CustomMessageEntry CustomMessageEntry 扩展注入的消息条目(参与 LLM 上下文)
LabelEntry LabelEntry 用户定义的书签/标记

附录 B:文件路径速查表

B.1 全局配置路径(~/.pi/agent/

路径 说明
~/.pi/agent/settings.json 全局设置
~/.pi/agent/auth.json 认证凭证(API Key / OAuth Token)
~/.pi/agent/models.json 自定义 Provider 和模型
~/.pi/agent/keybindings.json 自定义快捷键
~/.pi/agent/AGENTS.md 全局上下文文件
~/.pi/agent/CLAUDE.md 全局上下文文件(备选)
~/.pi/agent/SYSTEM.md 自定义系统提示词
~/.pi/agent/APPEND_SYSTEM.md 追加系统提示词
~/.pi/agent/extensions/*.ts 全局扩展
~/.pi/agent/extensions/*/index.ts 全局扩展(子目录)
~/.pi/agent/skills/ 全局技能
~/.pi/agent/prompts/*.md 全局提示词模板
~/.pi/agent/themes/*.json 全局主题
~/.pi/agent/sessions/ 会话存储目录
~/.pi/agent/git/ Git 包安装目录

B.2 项目配置路径(.pi/

路径 说明
.pi/settings.json 项目设置
.pi/extensions/*.ts 项目扩展
.pi/extensions/*/index.ts 项目扩展(子目录)
.pi/skills/ 项目技能
.pi/prompts/*.md 项目提示词模板
.pi/themes/*.json 项目主题
.pi/npm/ 项目级 npm 包安装目录
.pi/git/ 项目级 git 包安装目录
.pi/sessions/ 项目级会话存储(需配置 sessionDir)

B.3 其他发现路径

路径 说明
~/.agents/skills/ 全局 Agent Skills 标准目录
<cwd>/.agents/skills/ 项目 Agent Skills 标准目录(向上遍历到 git 仓库根)

附录 C:事件速查表

C.1 会话生命周期事件

事件 触发时机 关键字段 可取消
session_start 会话启动/加载/重载 reason, previousSessionFile
session_before_switch /new/resume reason, targetSessionFile
session_before_fork /fork/clone entryId, position
session_before_compact 压缩前 preparation, customInstructions, signal
session_compact 压缩完成 compactionEntry, fromExtension
session_before_tree /tree 导航前 preparation, signal
session_tree 树导航完成 newLeafId, oldLeafId, summaryEntry
session_shutdown 运行时关闭前 reason, targetSessionFile

C.2 Agent 事件

事件 触发时机 关键字段
before_agent_start 用户提示后、Agent 循环前 prompt, images, systemPrompt, systemPromptOptions
agent_start Agent 开始处理 ---
agent_end Agent 完成 messages
turn_start Turn 开始 turnIndex, timestamp
turn_end Turn 完成 turnIndex, message, toolResults

C.3 消息事件

事件 触发时机 关键字段
message_start 消息开始 message
message_update 流式更新 message, assistantMessageEvent
message_end 消息完成 message

C.4 工具事件

事件 触发时机 关键字段 可修改
tool_execution_start 工具开始执行 toolCallId, toolName, args
tool_execution_update 工具执行进度 toolCallId, toolName, args, partialResult
tool_execution_end 工具完成 toolCallId, toolName, result, isError
tool_call 工具执行前 toolName, toolCallId, input ✅ 阻塞/修改
tool_result 工具完成后 toolName, toolCallId, input, content, details, isError ✅ 修改

C.5 上下文与 Provider 事件

事件 触发时机 可修改
context 每次 LLM 调用前 ✅ 修改消息
before_provider_request 请求发送前 ✅ 替换 payload
after_provider_response HTTP 响应接收后

C.6 资源事件

事件 触发时机 关键字段
resources_discover session_start 后 cwd, reason

C.7 输入事件

事件 触发时机 关键字段 可拦截
input 用户输入后 text, images, source ✅ 转换/处理
user_bash !/!! 命令时 command, excludeFromContext, cwd ✅ 替换

C.8 模型事件

事件 触发时机 关键字段
model_select 模型切换 model, previousModel, source

附录 D:扩展开发清单

从零创建 Extension 的步骤检查清单:

D.1 准备

  • 确定扩展的目的(工具、命令、事件拦截、UI)
  • 选择放置位置(~/.pi/agent/extensions/ 全局 或 .pi/extensions/ 项目级)
  • 创建 .ts 文件或目录 + index.ts

D.2 基本结构

  • 导出默认工厂函数(export default function (pi: ExtensionAPI) { ... }
  • 如需异步初始化,使用 async function
  • 导入所需类型(ExtensionAPIType 等)

D.3 事件处理

  • 使用 pi.on() 订阅所需事件
  • session_start 中初始化状态
  • session_shutdown 中清理资源
  • 如需阻塞工具调用,监听 tool_call 并返回 { block: true, reason }
  • 如需修改工具结果,监听 tool_result 并返回补丁

D.4 自定义工具

  • 使用 pi.registerTool() 注册
  • 定义 TypeBox Schema 参数
  • 实现 execute 函数
  • 考虑 promptSnippetpromptGuidelines

D.5 自定义命令

  • 使用 pi.registerCommand() 注册
  • 提供 description
  • 实现 handler 函数
  • 可选:添加 getArgumentCompletions

D.6 自定义 UI

  • 使用 ctx.ui.custom() 创建自定义组件
  • 实现 renderhandleInputinvalidate 三方法对象
  • 使用主题回调获取颜色
  • handleInput 中调用 tui.requestRender()

D.7 测试

  • 使用 pi -e ./my-extension.ts 测试
  • 验证事件触发
  • 验证工具/命令工作
  • 验证 UI 渲染正确

D.8 打包分发

  • 创建 package.jsonpi 清单
  • 添加 pi-package 关键字
  • 运行时依赖放 dependencies
  • pi 核心包放 peerDependencies"*"
  • 发布到 npm 或推送到 git 仓库

附录 E:示例索引

E.1 Extension 示例(examples/extensions/

示例 用途
custom-provider-anthropic/ Anthropic 代理 Provider
custom-provider-gitlab-duo/ GitLab Duo OAuth 集成
custom-provider-qwen-cli/ Qwen CLI 自定义 API
custom-compaction.ts 使用不同模型的自定义压缩
custom-footer.ts 自定义页脚(含 git 分支和状态)
dynamic-tools.ts 动态工具注册与激活
input-transform.ts 输入转换
modal-editor.ts Vim 风格模态编辑器
overlay-qa-tests.ts Overlay 组件全面示例
plan-mode.ts 计划模式(setWidget + setStatus)
preset.ts SelectList 选择预设
qna.ts 问答工具(BorderedLoader + LLM 调用)
rpc-demo.ts RPC 模式扩展演示
send-user-message.ts sendUserMessage 完整示例
session.ts 会话管理示例
snake.ts Snake 游戏
status-line.ts 状态行指示器
summarize.ts 会话摘要工具
todo.ts 待办工具(自定义渲染)
tools.ts SettingsList 和工具启用/禁用
working-indicator.ts 自定义工作指示器
handoff.ts 会话交接(BorderedLoader)

E.2 SDK 示例(examples/sdk/

示例 用途
01-minimal.ts 最小 SDK 示例
02-custom-model.ts 自定义模型配置
03-custom-prompt.ts 自定义系统提示词
04-skills.ts 技能覆盖
05-tools.ts 工具配置和自定义工具
06-extensions.ts Extension 加载
07-context-files.ts 上下文文件覆盖
08-prompt-templates.ts 提示词模板覆盖
09-api-keys-and-oauth.ts API Key 和 OAuth 配置
10-settings.ts 设置管理
11-sessions.ts 会话管理

附录 F:推荐阅读

F.1 官方资源

资源 链接
pi mono GitHub 仓库 https://github.com/badlogic/pi-mono
pi.dev 官网 https://pi.dev
npm 包 https://www.npmjs.com/package/@mariozechner/pi-coding-agent
Discord 社区 https://discord.com/invite/3cU7Bz4UPx
包画廊 https://pi.dev/packages

F.2 标准与规范

资源 链接
Agent Skills 标准 https://agentskills.io/specification
Kitty 键盘协议 https://sw.kovidgoyal.net/kitty/keyboard-protocol/

F.3 相关项目

项目 说明
@mariozechner/pi-ai 核心 LLM 工具包
@mariozechner/pi-agent Agent 框架
@mariozechner/pi-tui 终端 UI 组件
openclaw/openclaw SDK 集成的实际项目

F.4 博客文章

文章 链接
pi Coding Agent 博文 https://mariozechner.at/posts/2025-11-30-pi-coding-agent/
What If You Don't Need MCP https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/

F.5 技能仓库

仓库 说明
Anthropic Skills 文档处理、Web 开发
Pi Skills Web 搜索、浏览器自动化、Google API、转录

F.6 会话共享

资源 说明
pi-share-hf 将编码代理会话分享到 Hugging Face
badlogicgames/pi-mono on Hugging Face pi-mono 开源工作会话数据集

全文完

相关推荐
蜡笔小新拯救世界4 小时前
部分安全笔记总结
linux·网络·web安全
无籽西瓜a5 小时前
RAG 中的幻觉是什么?原因分析与防范措施
人工智能·ai·rag
一切皆是因缘际会5 小时前
通用人工智能底层原理:从记忆结构视角解析大模型行为与意识涌现
人工智能·安全·ai·架构·系统架构
身如柳絮随风扬5 小时前
使用 Docker 部署禅道并实现自动化部署——从项目搭建到运维自动化的完整指南
运维·docker·自动化
日取其半万世不竭5 小时前
WordPress建站 + 免费SSL证书配置完整教程
网络·网络协议·ssl
@insist1235 小时前
信息安全工程师-防火墙核心技术深度解析:包过滤与状态检测
网络·安全·软考·信息安全工程师·软件水平考试
Prannt5 小时前
星朗智能语音——语音合成——上传文件配音
ai·音视频·语音识别
其实防守也摸鱼5 小时前
CTF密码学综合教学指南--第一章
网络·安全·网络安全·密码学·ctf·法律
lpfasd1235 小时前
Playwright 网页自动化交互:滑块安全校验优雅处理方案
运维·自动化·交互