书籍
书籍
书籍栏目收录以整本书形式组织的文档。每本书拥有独立的目录、章节编号与侧边栏。
收录书目
Pi 用户手册
Pi 是一个极简的终端编码智能体框架。它的核心保持小巧,通过 TypeScript 扩展、技能、提示词模板、主题和 pi 包来扩展能力。
本手册翻译自 pi 官方文档。
快速开始
使用 npm 安装 Pi:
--ignore-scripts 会在安装过程中禁用依赖的生命周期脚本。pi 在常规 npm 安装下不需要执行安装脚本。
在 Linux 或 macOS 上,也可以使用安装脚本:
卸载 pi 本体时(无论当初用 curl 还是 npm 安装),使用 npm:
如果使用 pnpm、Yarn 或 Bun 安装,请使用对应的全局卸载命令:pnpm remove -g @earendil-works/pi-coding-agent、yarn global remove @earendil-works/pi-coding-agent 或 bun uninstall -g @earendil-works/pi-coding-agent。
然后在项目目录中运行:
订阅制提供商通过 /login 完成认证,或在启动 pi 前设置 API 密钥(例如 ANTHROPIC_API_KEY)。
完整的首次运行流程参见快速开始。
目录
1 快速开始
本页带你从安装一路走到完成第一个实用的 pi 会话。
安装
Pi 以 npm 包的形式分发:
--ignore-scripts 会在安装过程中禁用依赖的生命周期脚本。在常规 npm 安装下,Pi 不需要安装脚本。
卸载
使用安装 pi 时所用的包管理器来卸载。curl 安装器底层使用全局 npm,因此 curl 和 npm 安装的版本都用 npm 卸载:
卸载 pi 后,设置、凭据、会话以及已安装的 pi 包会保留在 ~/.pi/agent/ 中。
然后在想让 pi 处理的项目目录中启动它:
认证
Pi 可以通过 /login 使用订阅型提供商,也可以通过环境变量或认证文件使用 API 密钥型提供商。
方式一:订阅登录
启动 pi 并运行:
然后选择一个提供商。内置的订阅登录包括 Claude Pro/Max、ChatGPT Plus/Pro(Codex)以及 GitHub Copilot。
方式二:API 密钥
在启动 pi 之前设置 API 密钥:
也可以运行 /login 并选择一个 API 密钥型提供商,把密钥存入 ~/.pi/agent/auth.json。
所有受支持的提供商、环境变量以及云端提供商的配置,请参阅提供商。
第一个会话
pi 启动后,输入一个请求并按回车:
默认情况下,pi 会为模型提供四个工具:
read- 读取文件write- 创建或覆盖文件edit- 修补文件bash- 运行 shell 命令
更多内置只读工具(grep、find、ls)可通过工具选项启用。Pi 在你当前的工作目录中运行,并且可以修改其中的文件。如果想要方便地回滚,请使用 git 或其他检查点工作流。
为 pi 提供项目指令
Pi 启动时会加载上下文文件。添加一个 AGENTS.md 文件,告诉它在这个项目中如何工作:
Pi 会加载:
~/.pi/agent/AGENTS.md:全局指令- 当前目录及父目录中的
AGENTS.md或CLAUDE.md
如果某个目录中存在 AGENTS.override.md,Pi 会加载它来取代该目录中的 AGENTS.md 或 CLAUDE.md。
修改上下文文件后,请重启 pi 或运行 /reload。
常用操作
引用文件
在编辑器中输入 @ 可以模糊搜索文件,也可以在命令行上直接传入文件:
图片或文本可以用 Ctrl+V 粘贴(Windows 上是 Alt+V);图片也可以直接拖入支持的终端。
运行 shell 命令
在交互模式下:
命令输出会发送给模型。使用 !!command 可以运行命令而不把输出加入模型上下文。
切换模型
使用 /model 或 Ctrl+L 为当前会话选择模型。在模型选择器中按 Ctrl+S,可以把高亮的模型保存为启动默认值。使用 /thinking 为当前会话选择思考级别;在该选择器中按 Ctrl+S 可保存启动默认的思考级别。使用 Shift+Tab 可以循环切换思考级别。使用 Ctrl+P / Shift+Ctrl+P 可以在限定范围(scoped)的模型之间循环切换。
稍后继续
会话会自动保存:
在 pi 内部,使用 /resume、/new、/tree、/fork 和 /clone 管理会话。
非交互模式
一次性提示可以这样运行:
使用 --mode json 输出 JSON 事件流,或使用 --mode rpc 进行进程集成。
后续步骤
2 使用 Pi
本页面收录了不适合放在快速开始页面中的日常使用细节。
交互模式

界面分为四个主要区域:
- 启动头部——快捷方式、已加载的上下文文件、提示词模板、技能和扩展
- 消息区——用户消息、助手回复、工具调用、工具结果、通知、错误和扩展 UI
- 编辑器——你输入内容的地方;边框颜色指示当前的思考级别
- 底部栏——工作目录、会话名称、token/缓存用量、费用、上下文用量和当前模型。总计包含助手回复、工具报告的用量以及摘要生成。
编辑器可以被内置 UI(如 /settings)或自定义扩展 UI 临时替换。
编辑器功能
| 功能 | 用法 |
|---|---|
| 文件引用 | 输入 @ 对项目文件进行模糊搜索 |
| 路径补全 | 按 Tab 补全路径 |
| 多行输入 | Shift+Enter,Windows Terminal 上为 Ctrl+Enter |
| 复制回复 | 在 /tree 中 Ctrl+X 复制选中的消息;否则复制上一条助手消息;当 fullscreenCopyOnSelect 禁用时,复制全屏模式下活动的文本选区 |
| 图片 | 使用 Ctrl+V 粘贴(Windows 上为 Alt+V),或拖拽到终端中 |
| Shell 命令 | !command 运行命令并将输出发送给模型 |
| 隐藏 Shell 命令 | !!command 运行命令但不将输出发送给模型 |
| 外部编辑器 | Ctrl+G 打开 externalEditor、$VISUAL、$EDITOR、Windows 上的记事本或其他平台上的 nano |
所有快捷键及自定义方式参见快捷键。
斜杠命令
在编辑器中输入 / 打开命令补全。扩展可以注册自定义命令,技能以 /skill:name 的形式提供,提示词模板通过 /templatename 展开。
| 命令 | 说明 |
|---|---|
/login、/logout | 管理 OAuth 或 API 密钥凭证 |
/llama | 下载、加载和卸载 llama.cpp 路由器模型 |
/model | 切换模型;在选择器中按 Ctrl+S 保存为启动默认值 |
/thinking | 切换思考级别;在选择器中按 Ctrl+S 保存为启动默认值 |
/scoped-models | 启用/禁用用于 Ctrl+P 循环切换的模型 |
/settings | 主题、消息送达、传输方式等偏好设置 |
/resume | 从之前的会话中选择 |
/new | 开始新会话 |
/name <name> | 设置会话显示名称 |
/session | 显示会话文件、ID、消息、token 和费用 |
/tree | 跳转到会话中的任意位置并从那里继续 |
/trust | 保存项目信任决定,供未来会话使用 |
/fork | 从之前的用户消息创建新会话 |
/clone | 将当前活动分支复制为一个新会话 |
/compact [prompt] | 手动压缩上下文,可选附带自定义指令 |
/copy | 将上一条助手消息复制到剪贴板 |
/export [file] | 将会话导出为 HTML 或 JSONL |
/import <file> | 从 JSONL 文件导入并恢复会话 |
/share | 上传为私有 GitHub gist,并生成可分享的 HTML 链接 |
/reload | 重新加载快捷键、扩展、技能、提示词、主题和上下文文件 |
/hotkeys | 显示所有键盘快捷键 |
/changelog | 显示版本历史 |
/quit | 退出 pi |
消息队列
你可以在智能体仍在工作时提交消息:
- Enter 将一条转向消息加入队列,在当前助手回合执行完其工具调用后送达。
- Alt+Enter 将一条后续消息加入队列,在智能体完成所有工作后送达。
- Escape 中止操作,并将排队的消息恢复到编辑器中。
- Alt+Up 将排队的消息取回编辑器。
在 Windows Terminal 中,Alt+Enter 默认是全屏快捷键。如果希望 pi 接收到该快捷键,请按终端设置中的说明重新映射。
可在设置中通过 steeringMode 和 followUpMode 配置送达方式。
会话
会话会自动保存到 ~/.pi/agent/sessions/,按工作目录组织。
常用的会话命令:
/session显示当前会话文件和 ID。/tree导航会话文件内的会话树,并可以总结被放弃的分支。/fork从更早的用户消息创建新会话。/clone将当前活动分支复制到一个新的会话文件。/compact总结较旧的消息以释放上下文。
上下文文件
Pi 启动时会从以下位置加载 AGENTS.md 或 CLAUDE.md:
~/.pi/agent/AGENTS.md用于全局指令- 从当前工作目录向上遍历的父目录
- 当前目录
如果某个目录包含 AGENTS.override.md,Pi 会改为加载它,而不再加载该目录中的 AGENTS.md 或 CLAUDE.md。来自其他目录的上下文文件仍照常分层叠加。
可以用上下文文件记录项目约定、命令、安全规则和偏好。使用 --no-context-files 或 -nc 禁用加载。
系统提示词文件
替换默认系统提示词:
- 项目级:
.pi/SYSTEM.md - 全局:
~/.pi/agent/SYSTEM.md
在上述任一位置放置 APPEND_SYSTEM.md,可以在默认提示词后追加内容而不替换它。
项目信任
在交互式启动时,如果项目文件夹包含项目本地设置、资源或项目级 .agents/skills,且在 ~/.pi/agent/trust.json 中没有针对该文件夹或其父文件夹的已保存决定,pi 会先询问是否信任该项目。信任项目后,pi 才能加载 .pi/settings.json 和 .pi 资源、安装缺失的项目包,并执行项目扩展。
在做出信任决定之前,pi 只加载上下文文件、用户/全局扩展以及 CLI -e 扩展,以便它们能够处理 project_trust 事件。项目本地扩展、项目包管理的扩展和项目设置只有在项目被信任后才会加载。当切换到来自其他 cwd、且信任尚未在当前进程中确定的会话时,同样适用这一划分。
非交互模式(-p、--mode json 和 --mode rpc)不显示信任提示。在没有适用的已保存信任决定时,它们使用全局设置中的 defaultProjectTrust:ask(默认)和 never 会忽略这些项目资源,而 always 则信任它们。传入 --approve/-a 或 --no-approve/-na 可为单次运行覆盖项目信任。
如果没有扩展或已保存决定适用,则由 defaultProjectTrust 控制回退行为。在 ~/.pi/agent/settings.json 中将其设为 "ask"、"always" 或 "never",或通过 /settings 修改。
pi config 和包管理命令使用相同的项目信任流程,但 pi update 从不弹出提示。传入 --approve 可为单条命令信任项目本地设置,传入 --no-approve 则忽略它们。
在交互模式下使用 /trust 可以为未来会话保存项目信任决定,包括对直接父文件夹的信任。它只写入 ~/.pi/agent/trust.json;当前会话不会重新加载,因此需重启 pi 才能生效。
导出与分享会话
使用 /export [file] 将会话写入 HTML 文件。
使用 /share 上传一个私有 GitHub gist,并附带可分享的 HTML 链接。
如果你将 pi 用于开源工作,并希望发布会话以供模型、提示词、工具和评估研究使用,请参见 badlogic/pi-share-hf。它可以将会话发布到 Hugging Face 数据集。
CLI 参考
包管理命令
这些命令用于管理 pi 包,pi update 还可以更新 pi CLI 本体。要卸载 pi 本体,请参见快速开始。pi config 和项目包命令接受 --approve/--no-approve,用于为单条命令信任或忽略项目本地设置。pi update 从不因项目信任而弹出提示。
包来源与安全说明参见 Pi 包。
模式
| 标志 | 说明 |
|---|---|
| default | 交互模式 |
-p、--print | 打印回复后退出 |
--mode json | 以 JSON 行输出所有事件;参见 JSON 模式 |
--mode rpc | 通过 stdin/stdout 的 RPC 模式;参见 RPC 模式 |
--export <in> [out] | 将会话导出为 HTML |
在打印模式下,pi 还会读取管道传入的 stdin 并将其合并到初始提示词中:
模型选项
| 选项 | 说明 |
|---|---|
--provider <name> | 提供商,例如 anthropic、openai 或 google |
--model <pattern> | 模型模式或 ID;支持 provider/id 以及可选的 :<thinking> |
--api-key <key> | API 密钥,覆盖环境变量 |
--thinking <level> | off、minimal、low、medium、high、xhigh、max |
--models <patterns> | 用于 Ctrl+P 循环切换的逗号分隔模式 |
--list-models [search] | 列出可用模型 |
会话选项
| 选项 | 说明 |
|---|---|
-c、--continue | 继续最近的会话 |
-r、--resume | 浏览并选择一个会话 |
--session <path|id> | 使用指定的会话文件或部分 UUID |
--fork <path|id> | 将会话文件或部分 UUID 分叉为一个新会话 |
--session-dir <dir> | 自定义会话存储目录 |
--no-session | 临时模式;不保存 |
--name <name>、-n <name> | 启动时设置会话显示名称 |
工具选项
| 选项 | 说明 |
|---|---|
--tools <list>、-t <list> | 以白名单方式指定可用的内置、扩展和自定义工具 |
--exclude-tools <list>、-xt <list> | 禁用指定的内置、扩展和自定义工具 |
--no-builtin-tools、-nbt | 禁用内置工具,但保留扩展/自定义工具 |
--no-tools、-nt | 禁用所有工具 |
内置工具包括:read、bash、powershell(Windows)、edit、write、grep、find、ls。
资源选项
| 选项 | 说明 |
|---|---|
-e、--extension <source> | 从路径、npm 或 git 加载扩展;可重复使用 |
--no-extensions | 禁用扩展发现 |
--skill <path> | 加载技能;可重复使用 |
--no-skills | 禁用技能发现 |
--prompt-template <path> | 加载提示词模板;可重复使用 |
--no-prompt-templates | 禁用提示词模板发现 |
--theme <path> | 加载主题;可重复使用 |
--no-themes | 禁用主题发现 |
--no-context-files、-nc | 禁用 AGENTS.md 和 CLAUDE.md 发现 |
将 --no-* 与显式标志组合使用,可以忽略设置、精确加载所需资源。示例:
其他选项
| 选项 | 说明 |
|---|---|
--system-prompt <text> | 替换默认提示词;上下文文件和技能仍会追加 |
--append-system-prompt <text> | 在系统提示词后追加内容 |
--tui-mode <mode> | TUI 模式:regular(默认)或实验性 fullscreen |
--use-theme <name[/name]> | 为本次运行设置初始交互主题,不修改设置 |
--verbose | 强制显示详细启动信息 |
-a、--approve | 本次运行信任项目本地文件 |
-na、--no-approve | 本次运行忽略项目本地文件 |
-- | 停止解析选项;其余参数作为提示词或 @file 输入 |
-h、--help | 显示帮助 |
-v、--version | 显示版本 |
在 fullscreen 模式下,对话记录在终端视口内滚动,而排队消息、工作状态、扩展小部件、编辑器和底部栏固定在底部。鼠标/触控板输入会滚动指针所在区域;键盘视口操作始终可用。行内图片在支持 Kitty 图形协议的终端(包括 Kitty 和 Ghostty)中可以正常显示。在 iTerm2 中,它们会渲染为文本占位符,因为 iTerm2 的行内图片协议无法在应用接管滚动期间删除或裁剪已放置的图片。在 regular 模式下,pi 使用主屏幕和终端自带的回滚缓冲区,iTerm2 中的行内图片可继续正常渲染。各终端的专属设置与变通方案参见终端设置。
在 /settings 中设置 TUI 模式,可以立即在 regular 和 fullscreen 之间切换,并为未来会话选择默认值。全屏退出输出控制退出全屏时是打印最终对话记录,还是恢复之前的屏幕并只打印会话恢复提示。
文件参数
为文件名加上 @ 前缀可将其包含在消息中:
示例
设计原则
Pi 保持核心小巧,将特定工作流的行为推送到扩展、技能、提示词模板和包中。
它有意不内置 MCP、子智能体、权限弹窗、计划模式、待办事项或后台 bash。你可以将这些工作流构建或安装为扩展或包,也可以使用容器和 tmux 等外部工具。
完整的设计理由请阅读博客文章。
3 提供商
Pi 通过 OAuth 支持订阅制提供商,通过环境变量或认证文件支持 API 密钥提供商。内置模型目录随 pi 一起发布;已配置的提供商可以刷新更新的目录,并将其缓存到 ~/.pi/agent/models-store.json 以供离线使用。
目录
订阅
在交互模式下使用 /login,然后选择一个提供商:
- ChatGPT Plus/Pro(Codex)
- Claude Pro/Max
- GitHub Copilot
- xAI(Grok/X 订阅)
- OpenRouter(通过 OAuth 铸造的 API 密钥,从 OpenRouter 余额计费)
- Radius
使用 /logout 清除凭证。令牌存储在 ~/.pi/agent/auth.json 中,过期时自动刷新。OpenRouter 则会铸造一个由用户控制的 API 密钥,它不会自动过期。
OpenAI Codex
- 需要 ChatGPT Plus 或 Pro 订阅
- 由 OpenAI 官方背书:Codex for OSS
Claude Pro/Max
Anthropic 订阅认证适用于 Claude Pro/Max 账户。第三方工具的使用会消耗额外用量,并按 token 计费,而不计入 Claude 套餐限额。
GitHub Copilot
- 在 github.com 上直接按 Enter,或输入你的 GitHub Enterprise Server 域名
- 如果出现 “model not supported”,请在 VS Code 中启用该模型:Copilot Chat → 模型选择器 → 选择模型 → “Enable”
xAI(Grok/X 订阅)
- 运行
/login xai,然后选择 Use a subscription XAI_API_KEY仍可通过 Use an API key 使用
OpenRouter
- 运行
/login openrouter,然后选择 Sign in with OpenRouter,打开 OpenRouter PKCE 授权流程 - 授权会创建一个由用户控制的 OpenRouter API 密钥,从你的 OpenRouter 余额中扣费
- 在远程/无头机器上(例如通过 SSH 连接),浏览器无法访问本地回环回调;此时请将最终重定向 URL(或授权码)粘贴到登录提示中
OPENROUTER_API_KEY仍可通过 Use an API key 使用
Radius
Radius 是一个动态 pi-messages 网关。/login radius 会将 OAuth 令牌存储到 auth.json;网关目录会独立刷新并缓存到 models-store.json。自定义 Radius 网关可以在 models.json 中通过 "oauth": "radius" 和网关 baseUrl 声明。
API 密钥
环境变量或认证文件
在交互模式下使用 /login 并选择一个提供商,即可将 API 密钥存入 auth.json;也可以通过环境变量设置凭证:
| 提供商 | 环境变量 | auth.json 键 |
|---|---|---|
| Anthropic | ANTHROPIC_API_KEY | anthropic |
| Ant Ling | ANT_LING_API_KEY | ant-ling |
| Azure OpenAI Responses | AZURE_OPENAI_API_KEY | azure-openai-responses |
| OpenAI | OPENAI_API_KEY | openai |
| DeepSeek | DEEPSEEK_API_KEY | deepseek |
| NVIDIA NIM | NVIDIA_API_KEY | nvidia |
| Google Gemini | GEMINI_API_KEY | google |
| Amazon Bedrock | AWS_BEARER_TOKEN_BEDROCK | amazon-bedrock |
| Mistral | MISTRAL_API_KEY | mistral |
| Groq | GROQ_API_KEY | groq |
| Cerebras | CEREBRAS_API_KEY | cerebras |
| Cloudflare AI Gateway | CLOUDFLARE_API_KEY(另需 CLOUDFLARE_ACCOUNT_ID、CLOUDFLARE_GATEWAY_ID) | cloudflare-ai-gateway |
| Cloudflare Workers AI | CLOUDFLARE_API_KEY(另需 CLOUDFLARE_ACCOUNT_ID) | cloudflare-workers-ai |
| xAI | XAI_API_KEY | xai |
| OpenRouter | OPENROUTER_API_KEY | openrouter |
| Vercel AI Gateway | AI_GATEWAY_API_KEY | vercel-ai-gateway |
| ZAI Coding Plan (Global) | ZAI_API_KEY | zai |
| ZAI Coding Plan (China) | ZAI_CODING_CN_API_KEY | zai-coding-cn |
| OpenCode Zen | OPENCODE_API_KEY | opencode |
| OpenCode Go | OPENCODE_API_KEY | opencode-go |
| Radius | RADIUS_API_KEY | radius |
| Hugging Face | HF_TOKEN | huggingface |
| Fireworks | FIREWORKS_API_KEY | fireworks |
| Together AI | TOGETHER_API_KEY | together |
| Baseten | BASETEN_API_KEY | baseten |
| Kimi For Coding | KIMI_API_KEY | kimi-coding |
| MiniMax | MINIMAX_API_KEY | minimax |
| MiniMax (China) | MINIMAX_CN_API_KEY | minimax-cn |
| Qwen Token Plan (existing catalog) | QWEN_TOKEN_PLAN_API_KEY | qwen-token-plan |
| Qwen Token Plan (Individual) | QWEN_TOKEN_PLAN_API_KEY | qwen-token-plan-individual |
| Qwen Token Plan (China) | QWEN_TOKEN_PLAN_CN_API_KEY | qwen-token-plan-cn |
| Xiaomi MiMo | XIAOMI_API_KEY | xiaomi |
| Xiaomi MiMo Token Plan (China) | XIAOMI_TOKEN_PLAN_CN_API_KEY | xiaomi-token-plan-cn |
| Xiaomi MiMo Token Plan (Amsterdam) | XIAOMI_TOKEN_PLAN_AMS_API_KEY | xiaomi-token-plan-ams |
| Xiaomi MiMo Token Plan (Singapore) | XIAOMI_TOKEN_PLAN_SGP_API_KEY | xiaomi-token-plan-sgp |
环境变量与 auth.json 键的对照参见 packages/ai/src/env-api-keys.ts 中的 const envMap。
认证文件
将凭证存储在 ~/.pi/agent/auth.json:
qwen-token-plan-individual 使用与 qwen-token-plan 相同的国际端点和 QWEN_TOKEN_PLAN_API_KEY,但会将选择器限制为个人(Individual)订阅文档所列的模型。已有提供商出于向后兼容保留了更宽的目录。使用 auth.json 时,请将凭证存储在你所选的提供商名下;环境变量则由两个国际版提供商共用。
该文件以 0600 权限创建(仅用户可读写)。认证文件中的凭证优先于环境变量。
API 密钥凭证还可以包含提供商作用域的环境值。在解析凭证密钥、提供商/模型请求头以及提供商配置(如 Cloudflare 账户 ID、Azure OpenAI 设置、Vertex 项目/位置、Bedrock 设置、PI_CACHE_RETENTION 和 HTTP_PROXY/HTTPS_PROXY)时,这些值优先于进程环境变量。
当希望 pi 使用与项目 shell 环境不同的提供商设置时,可以使用这种方式。
密钥解析
key 字段支持命令执行、环境变量插值和字面量:
- Shell 命令: 以
"!command"开头时,整个值会作为命令执行,并使用其 stdout(在进程生命周期内缓存) - 环境变量插值:
"$ENV_VAR"或"${ENV_VAR}"使用指定变量的值。插值也可以在更长的字面量中使用。$FOO_BAR指向变量FOO_BAR;如果BAR是字面文本,请使用${FOO}_BAR。环境变量缺失时该值视为未解析。 - 转义:
"$$"输出字面量"$";"$!"输出字面量"!"而不触发命令执行。 - 字面量: 直接使用。纯大写字符串(如
MY_API_KEY)是字面量;要引用环境变量请使用$MY_API_KEY。
OAuth 凭证在 /login 之后也会存储在这里,并自动管理。
云端提供商
Azure OpenAI
Amazon Bedrock
使用 /login amazon-bedrock 存储 Bedrock API 密钥,或配置以下任一 AWS 环境凭证来源:
还支持 ECS 任务角色(AWS_CONTAINER_CREDENTIALS_*)和 IRSA(AWS_WEB_IDENTITY_TOKEN_FILE)。
对于 ID 中包含可识别模型名称的 Claude 模型(基础模型和系统定义的推理配置文件),提示词缓存会自动启用。对于应用推理配置文件(其 ARN 不包含模型名称),请设置 AWS_BEDROCK_FORCE_CACHE=1 以启用缓存点:
如果你要连接 Bedrock API 代理,可以使用以下环境变量:
Cloudflare AI Gateway
CLOUDFLARE_API_KEY 可以通过 /login 设置。账户 ID 和网关 slug 可以设置为环境变量,也可以放在 auth.json 中 API 密钥凭证的 env 对象里。
通过 Cloudflare AI Gateway 路由到 OpenAI、Anthropic 和 Workers AI。Workers AI 使用 Unified API(/compat)和带前缀的模型 ID(workers-ai/@cf/...)。OpenAI 使用 OpenAI 透传路由(/openai)及原生 OpenAI 模型 ID(如 gpt-5.1)。Anthropic 使用 Anthropic 透传路由(/anthropic)及原生 Anthropic 模型 ID(如 claude-sonnet-4-5)。
AI Gateway 认证将 CLOUDFLARE_API_KEY 用作 cf-aig-authorization。上游认证可以是以下之一:
| 模式 | 请求认证 | 上游认证 |
|---|---|---|
| Workers AI | 仅 Cloudflare 令牌 | Cloudflare 原生 |
| 统一计费 | 仅 Cloudflare 令牌 | 由 Cloudflare 处理上游认证并扣除额度 |
| 存储式 BYOK | 仅 Cloudflare 令牌 | Cloudflare 注入存储在 AI Gateway 控制台中的提供商密钥 |
| 行内 BYOK | Cloudflare 令牌加上游 Authorization 请求头 | 由请求提供上游提供商密钥 |
对于普通的 pi 用法,优先选择统一计费或存储式 BYOK。行内 BYOK 需要为 Cloudflare AI Gateway 提供商配置额外的上游 Authorization 请求头,例如通过 models.json 的提供商/模型覆盖来实现。
Cloudflare Workers AI
CLOUDFLARE_API_KEY 可以通过 /login 设置。CLOUDFLARE_ACCOUNT_ID 可以设置为环境变量,也可以放在 auth.json 中 API 密钥凭证的 env 对象里。
Pi 会自动设置 x-session-affinity,以享受前缀缓存折扣。
Google Vertex AI
使用应用默认凭证(Application Default Credentials):
或者将 GOOGLE_APPLICATION_CREDENTIALS 指向服务账号密钥文件。
llama.cpp
Pi 支持 llama.cpp 路由服务器。使用 /login llama.cpp 进行配置,用 /llama 管理已加载的模型,并用 /model 选择一个已加载的模型。
服务器搭建、模型目录布局、环境变量和命令用法参见 llama.cpp。
自定义提供商
通过 models.json: 添加 Ollama、LM Studio、vLLM,或任何使用受支持 API(OpenAI Completions、OpenAI Responses、Anthropic Messages、Google Generative AI)的提供商。参见 models.md。
通过扩展: 对于需要自定义 API 实现或 OAuth 流程的提供商,可以创建扩展。参见 custom-provider.md 和 examples/extensions/custom-provider-gitlab-duo。
解析顺序
解析某个提供商的凭证时,顺序如下:
- CLI
--api-key标志 auth.json条目(API 密钥或 OAuth 令牌)- 环境变量
- 来自
models.json的自定义提供商密钥
4 llama.cpp
Pi 支持 llama.cpp 路由服务器。路由器会发现多个 GGUF 模型,并按需加载或卸载它们。
请使用支持路由器功能的较新版本 llama.cpp。可以按照构建说明自行构建,或为你的平台安装预编译版本。
启动路由器
启动 llama-server 时不要带 --model 或 -m。如果传入模型,则会进入单模型模式而非路由器模式。
重要选项:
--models-dir ~/models用于发现本地 GGUF 文件。--no-models-autoload让模型加载必须通过/llama显式进行。--jinja启用兼容的聊天模板和工具调用。-ngl 999将尽可能多的层卸载到 GPU。-c 32768为每个已加载的模型设置上下文窗口。如果省略,则使用模型的原生上下文,这可能会显著增加内存占用。
单文件模型可以直接放在模型目录中。多模态和多分片模型请放在单独的子目录中:
手动添加文件后请重启路由器。如需为每个模型设置上下文大小等选项,可以使用 llama.cpp 模型预设。
配置 Pi
启动 Pi 并配置提供商:
输入路由器 URL 和可选的 API 密钥。默认 URL 为 http://127.0.0.1:8080。
如果启动路由器时使用了 --no-models-autoload,/login llama.cpp 只会保存连接信息。运行 /llama 加载模型,再运行 /model 为当前会话选择已加载的模型。
也可以通过环境变量配置相同的值,无需 /login:
如果服务器使用了 API 密钥,请在启动 llama-server 时传入相同的 --api-key 值。保持 --host 127.0.0.1 可将访问限制在本地。
管理模型
运行:
- 选择一个未加载的模型即可加载它。
- 选择一个已加载的模型即可卸载它。
- 选择 Download model…,搜索 Hugging Face,然后选择仓库和量化版本。直接输入精确的
owner/repository[:quant]值也可以。 - 在加载或下载过程中按 Escape 可确认取消。
Hugging Face 搜索在设置了 HF_TOKEN 时会使用它,然后依次检查 $HF_TOKEN_PATH、$HF_HOME/token、$XDG_CACHE_HOME/huggingface/token 和 ~/.cache/huggingface/token。未认证时也可以搜索,但会受到更低的速率限制。Pi 在下载受限仓库前会发出警告并链接到其访问页面。下载由 llama.cpp 服务器执行,因此当所选仓库需要访问权限时,该服务器进程也必须持有 HF_TOKEN。
如果已有其他模型处于加载状态,Pi 会询问是先卸载它们还是保持加载。Pi 不会静默卸载模型,也绝不会删除模型文件。路由器可能被其他客户端共享,因此 /llama 始终显示路由器的当前状态。
只有已加载的模型才会出现在 /model 中。加载模型后,运行 /model 为当前 Pi 会话选择它。
如果路由器连接断开,/llama 会显示 Retry 和 Close。重试会重新连接并刷新模型状态,但不会重放被中断的操作。
故障排除
检查路由器是否可达:
/llama中没有模型: 检查--models-dir和目录布局,并重启路由器。/model中缺少模型: 先用/llama加载它。- 加载失败或内存占用过高: 降低
-c或卸载其他模型。 - 服务器未处于路由器模式: 启动时不要带
--model、-m或-hf。
5 安全
Pi 是一个本地编码智能体。它以启动它的用户账户的权限运行,并把该用户可写的文件视为处于同一个本地信任边界之内。
项目信任
项目信任决定 pi 是否加载项目级设置、资源、包和扩展。它不是沙箱,也不会在你开始于某个目录中工作之后限制模型让工具执行的操作。
当 pi 从当前工作目录发现以下任意一项时,就会认为该项目包含需要信任的资源:
.pi/settings.json.pi/extensions、.pi/skills、.pi/prompts或.pi/themes.pi/SYSTEM.md或.pi/APPEND_SYSTEM.md- 当前目录或祖先目录中的项目级
.agents/skills
一个空的 .pi 目录不算作需要信任的项目资源。
当一个交互式会话在包含需要信任的资源、且当前目录或父目录没有已保存决定的项目中启动时,pi 会遵循全局设置中的 defaultProjectTrust。默认值为 "ask",即在有可用 UI 时询问是否信任该项目。已保存的决定按规范化目录存储在 ~/.pi/agent/trust.json 中;当前路径或父路径上距离最近的已保存决定,会先于全局默认值生效。
信任一个项目后,pi 就可以加载需要信任的项目资源,包括:
.pi/settings.json.pi资源,例如扩展、技能、提示词模板、主题和系统提示词文件- 通过项目设置配置的缺失项目包
- 项目级扩展和由项目包管理的扩展
拒绝信任则会跳过受保护的资源。AGENTS.override.md、AGENTS.md 和 CLAUDE.md 等上下文文件无论项目信任与否都会加载,除非上下文加载被禁用。在信任状态确定之前,pi 只加载上下文文件、用户/全局扩展以及 CLI -e 扩展。用户/全局扩展和 CLI 扩展可以处理 project_trust 事件;第一个返回是/否决定的扩展拥有该决定权。
非交互模式(-p、--mode json 和 --mode rpc)不会显示信任提示。在没有适用的已保存信任决定时,defaultProjectTrust: "ask" 和 "never" 会忽略这类资源,而 "always" 会信任它们。使用 --approve/-a 或 --no-approve/-na 可以为单次运行覆盖项目信任。
没有内置沙箱
Pi 不包含内置沙箱。内置工具可以读取文件、写入文件、编辑文件,并以 pi 进程自身的权限运行 shell 命令。扩展是以相同权限运行的 TypeScript 模块。包安装、shell 命令、语言服务器、测试命令和其他开发工具都表现为普通的本地进程。
这是有意为之。Pi 的设计目标是在本地源码树上操作、调用项目工具链,并与用户现有的开发环境集成。一个进程内的部分沙箱很容易被误解为安全边界,但它仍然要依赖宿主机的 shell、文件系统、包管理器、凭据和扩展代码。真正的隔离必须来自操作系统或虚拟化/容器边界。
项目信任只是一道输入加载防线。它可以防止仓库在你批准之前悄悄更改 pi 的设置或扩展。它无法让不受信任的代码、不受信任的提示词或不受信任的模型输出变得安全。来自仓库文件、注释、文档、上下文文件或构建输出的提示词注入,属于本地智能体预期之中且无法避免的风险,pi 无法可靠地防范。
运行不受信任或无人监督的工作
对于不受信任的仓库、你不打算密切监督的生成代码,或无人值守的自动化,请在受控环境中运行 pi。使用容器、虚拟机、微虚拟机、远程沙箱或受策略控制的沙箱,并且只提供任务所需的文件和凭据。
常见模式记录在容器化一章中:
- 将整个
pi进程放入容器/沙箱中运行 - 在宿主机上运行 pi,同时把内置工具的执行路由到 Gondolin 微虚拟机
- 只挂载智能体应该访问的工作区路径
- 避免挂载宿主机的
~/.pi/agent,除非容器需要访问宿主机的会话、设置和凭据 - 只传入必需的最少 API 密钥,或使用短期凭据
- 在任务不需要网络时限制网络访问
- 在把结果复制回受信任系统之前,先审查 diff 和输出
如果你以读写方式绑定挂载宿主机工作区,容器或虚拟机内部的写入仍然可以修改宿主机文件。当你需要更强的保护、防止意外写入时,请使用只读挂载,或者以复制文件的方式把内容移入和移出沙箱。
报告安全问题
要报告安全问题,请遵循仓库的安全策略。不要为安全敏感的报告创建公开 issue。
预期中的本地智能体行为、缺少内置沙箱、来自不受信任内容的提示词注入,以及用户安装的扩展或技能的行为,通常不在安全边界范围之内,除非报告演示了真实的权限边界绕过,或表明 pi 授予了本地用户原本并不拥有的访问权限。
6 容器化
Pi 默认以全部权限运行,但在某些情况下,你会希望更好地控制 Pi 可以写入哪些目录、拥有哪些访问权限。
总体上有两种选择:
- 将整个
pi进程放在隔离环境中运行,或者 - 在宿主机上运行
pi,把工具执行路由到隔离环境中。
选择模式
| 模式 | 隔离内容 | 适用场景 | 备注 |
|---|---|---|---|
| Gondolin 扩展 | 内置工具和 ! 命令 | 本地微虚拟机隔离,同时把认证保留在宿主机上 | 见 examples/extensions/gondolin/。 |
| 普通 Docker | 整个 pi 进程在本地容器中 | 简单的本地隔离 | 提供商 API 密钥会进入容器。 |
| OpenShell | 整个 pi 进程在受策略控制的沙箱中 | 本地或远程的托管沙箱 | 需要 OpenShell 网关 |
| Docker Sandboxes | 整个 pi 进程在托管沙箱中 | 本地隔离,提供商密钥保留在宿主机上 | 需要 Docker Sandboxes(sbx)。 |
扩展在 pi 进程所在的位置运行。如果你在宿主机上运行 pi 并使用工具路由扩展,其他自定义扩展工具仍会在宿主机上运行,除非它们也把自己的操作委托出去。
Gondolin
Gondolin 是一个本地 Linux 微虚拟机。
当你想让 pi 留在宿主机上、而所有内置工具都路由到虚拟机内执行时,可以使用示例扩展。
安装:
在要挂载的项目中运行:
该扩展会把宿主机当前工作目录挂载到虚拟机内的 /workspace,并接管 read、write、edit、bash、grep、find 和 ls。
用户的 ! 命令同样会被路由到虚拟机中。
/workspace 下的文件改动会直接写回宿主机。
环境要求:@earendil-works/gondolin 需要 Node.js >= 23.6.0,另外需要 QEMU(须通过你的包管理器安装)。
普通 Docker
当你想要最简单的本地容器边界时,可以把整个 pi 进程放进 Docker 中运行。
Dockerfile.pi:
构建并运行:
-v "$PWD:/workspace" 会把你当前所在的目录挂载到容器内的 /workspace,这样 Docker 内部对 /workspace 的读写会直接影响宿主机文件,效果与 Gondolin 示例相同。
如果希望容器内有独立的设置和会话,可以为 /root/.pi/agent 使用命名卷。挂载宿主机的 ~/.pi/agent 会把宿主机的认证和会话文件暴露给容器。
OpenShell
当你需要带有文件系统、进程、网络、凭据和推理控制的策略型沙箱时,可以使用 NVIDIA OpenShell。 OpenShell 可以通过由 Docker、Podman 或虚拟机运行时支撑的本地网关运行沙箱,也可以通过远程 Kubernetes 网关运行。
每个沙箱都需要一个活跃的网关。 在创建沙箱之前,先注册并选择一个:
在 OpenShell 沙箱内启动 pi:
在这种模式下,整个 pi 进程都在沙箱内运行。
内置工具、! 命令和扩展工具都在 OpenShell 边界内执行。
如果网关是远程的,项目文件不会从宿主机绑定挂载,也就是说沙箱内的写入不会反映到你的机器上。 请在沙箱内克隆仓库,或使用 OpenShell 的文件传输命令:
OpenShell 提供商可以让原始的模型 API 密钥留在沙箱之外。
配置推理路由之后,沙箱内的代码可以调用 https://inference.local,由网关在上游注入配置好的提供商凭据。
如果你希望模型流量走这条路线,可以将 Pi 配置为使用对应的 OpenAI 兼容或 Anthropic 兼容端点。
Docker Sandboxes
Docker Sandboxes 是 Docker 提供的托管沙箱运行时,会把整个 pi 进程放进沙箱中运行。
它是没有内置沙箱所指的那种容器边界之一。
与上面的普通 Docker 模式不同,提供商凭据不会传入容器。
沙箱收到的是一个哨兵值(sentinel),由 sbx 代理在出口处向 api.anthropic.com 替换为真实凭据。
凭据在创建时接线,因此在创建沙箱之前,要先把凭据存放在宿主机上。
对于 Claude Pro/Max 订阅,先在一台装有 Claude Code 的机器上运行 claude setup-token,然后把结果存储到宿主机上。
如果已经绑定了 anthropic secret,请先移除它:否则代理会在 Bearer token 之外再附加一个 x-api-key 头,Anthropic 会拒绝该请求。
sbx secret set-custom 从 stdin 读取 token,因此不会留在 shell 历史记录中。
沙箱拿到的是 OAuth 形式的占位符而非真实 token,由代理在出口到该主机时替换;ANTHROPIC_OAUTH_TOKEN 是 pi 本身就会读取并优先于 API 密钥使用的变量,因此无需额外的 pi 配置。
如果是 API 密钥,则改用 sbx secret set anthropic 存储。该套件(kit)以相同的方式接线:作为一个由代理在出口处替换的哨兵值。
凭据存放好之后,在要挂载的项目中启动 pi:
该套件已把 pi 预置到镜像中,因此沙箱启动时无需安装任何东西,当前目录就是沙箱工作区。
不要在沙箱内认证:在里面运行 /login 会把真实 token 写入容器,破坏代理模型。
脚本化使用的方式相同:
完整的凭据矩阵、故障排除和版本固定,请参阅套件文档。
7 设置
Pi 使用 JSON 设置文件,项目设置会覆盖全局设置。
| 位置 | 作用域 |
|---|---|
~/.pi/agent/settings.json | 全局(所有项目) |
.pi/settings.json | 项目(当前目录) |
可以直接编辑,也可以使用 /settings 调整常用选项。要以交互方式保存启动时的默认模型,请使用 /model 并在所需模型上按 Ctrl+S。要保存启动时的思考级别,请使用 /thinking 并按 Ctrl+S。
项目信任
在交互式启动时,如果项目文件夹包含项目本地设置、资源或项目级 .agents/skills,且在 ~/.pi/agent/trust.json 中没有针对该文件夹或其父文件夹的已保存决定,pi 会先询问是否信任该项目。信任项目后,pi 才能加载 .pi/settings.json 和 .pi 资源、安装缺失的项目包,并执行项目扩展。
非交互模式(-p、--mode json 和 --mode rpc)不显示信任提示。在没有适用的已保存信任决定时,它们使用全局设置中的 defaultProjectTrust:ask(默认)和 never 会忽略这些项目资源,而 always 则信任它们。传入 --approve/-a 或 --no-approve/-na 可为单次运行覆盖项目信任。
如果没有扩展或已保存决定适用,则由 defaultProjectTrust 控制回退行为。在 ~/.pi/agent/settings.json 中将其设为 "ask"、"always" 或 "never",或通过 /settings 修改。
pi config 和包管理命令使用相同的项目信任流程,但 pi update 从不弹出提示。传入 --approve 可为单条命令信任项目本地设置,传入 --no-approve 则忽略它们。
在交互模式下使用 /trust 可以为未来会话保存项目信任决定,包括对直接父文件夹的信任。它只写入 ~/.pi/agent/trust.json;当前会话不会重新加载,因此需重启 pi 才能生效。
全部设置
模型与思考
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
defaultProvider | 字符串 | - | 启动提供商(如 "anthropic"、"openai";通过 /model 中按 Ctrl+S 保存,或手动编辑) |
defaultModel | 字符串 | - | 启动模型 ID(通过 /model 中按 Ctrl+S 保存,或手动编辑) |
defaultThinkingLevel | 字符串 | - | 启动思考级别(通过 /thinking 中按 Ctrl+S 保存,或手动编辑):"off"、"minimal"、"low"、"medium"、"high"、"xhigh"、"max" |
modelThinkingLevels | 对象 | - | 以 "provider/modelId" 为键的每模型启动思考级别;可在 /settings → Default thinking level per model 中配置,或手动编辑 |
hideThinkingBlock | 布尔值 | false | 在输出中隐藏思考块 |
showCacheMissNotices | 布尔值 | false | 为显著的提示词缓存未命中、上下文压缩或分支摘要用量,以及提供商恢复诊断(如被丢弃的 Anthropic 思考块)显示对话记录通知 |
thinkingBudgets | 对象 | - | 每个思考级别的自定义 token 预算。Anthropic、Google 和 Bedrock 原生使用这些预算。兼容 OpenAI 的模型在设置了 compat.thinkingTokenBudget(或 supportsThinkingTokenBudget)时使用它们。 |
thinkingBudgets
UI 与显示
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
theme | 字符串 | "dark" | 主题名称("dark"、"light" 或自定义) |
externalEditor | 字符串 | $VISUAL,然后 $EDITOR,再然后 Windows 上的记事本或其他平台上的 nano | Ctrl+G 外部编辑器命令;优先于环境变量 |
quietStartup | 布尔值 | false | 隐藏启动头部 |
defaultProjectTrust | 字符串 | "ask" | 回退的项目信任行为:"ask"、"always" 或 "never"。仅限全局设置 |
collapseChangelog | 布尔值 | false | 更新后显示精简版更新日志 |
enableInstallTelemetry | 布尔值 | true | 发送匿名安装/更新 ping 以及所选提供商的归因请求头。它不控制更新检查 |
enableAnalytics | 布尔值 | false | 选择加入的分析数据共享。目前仅在实验性首次设置(PI_EXPERIMENTAL=1)期间询问 |
trackingId | 字符串 | - | 分析跟踪标识符,在开启 enableAnalytics 时生成 |
doubleEscapeAction | 字符串 | "tree" | 连按两次 Escape 的动作:"tree"、"fork" 或 "none" |
treeFilterMode | 字符串 | "default" | /tree 的默认过滤器:"default"、"no-tools"、"user-only"、"labeled-only"、"all" |
editorPaddingX | 数字 | 0 | 输入编辑器的水平内边距(0-3) |
outputPad | 数字 | 1 | 用户消息、助手消息和思考内容的水平内边距(0 或 1) |
autocompleteMaxVisible | 数字 | 5 | 自动补全下拉菜单的最大可见项数(3-20) |
showHardwareCursor | 布尔值 | false | 在 TUI 为输入法支持定位光标时显示终端光标 |
tuiMode | 字符串 | "regular" | 交互式 TUI 模式:"regular" 或实验性 "fullscreen"。在 /settings 中更改后立即生效;--tui-mode 会在启动时覆盖该设置 |
fullscreenExitOutput | 字符串 | "transcript" | 全屏退出输出:"transcript" 打印最终对话记录和恢复提示,"resume-hint" 则恢复之前的屏幕并只打印恢复提示。在常规 TUI 模式下无效 |
fullscreenScrollbar | 字符串 | "auto" | 全屏对话记录滚动条:"auto" 在滚动期间或指针悬停在最右侧轨道列时临时显示,"always" 保留该列并始终显示,"hidden" 将其隐藏。在常规 TUI 模式下无效 |
fullscreenCopyOnSelect | 布尔值 | true | 在全屏模式下自动复制选中的文本。禁用时,选区保持高亮,由 Ctrl+X 复制活动选区 |
对于 VS Code,请加上 --wait,这样编辑器退出后 pi 会继续运行:
遥测与更新检查
enableInstallTelemetry 控制发送到 https://pi.dev/api/report-install 的匿名安装/更新 ping,以及 OpenRouter、NVIDIA NIM 和 Cloudflare 提供商请求中的 Pi 归因请求头。关闭后会同时禁用两者。它不会禁用更新检查;Pi 仍可能获取 https://pi.dev/api/latest-version 以查询最新版本。
设置 PI_SKIP_VERSION_CHECK=1 可禁用 Pi 版本更新检查。使用 --offline 或 PI_OFFLINE=1 可禁用本文所述的所有启动网络操作,包括更新检查、包更新检查和安装/更新遥测。
网络
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
httpProxy | 字符串 | - | HTTP 代理 URL,应用为 HTTP_PROXY 和 HTTPS_PROXY。仅限全局设置。 |
警告
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
warnings.anthropicExtraUsage | 布尔值 | true | 当 Anthropic 订阅认证可能使用付费的额外用量时显示警告 |
上下文压缩
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
compaction.enabled | 布尔值 | true | 启用自动压缩 |
compaction.reserveTokens | 数字 | 16384 | 为 LLM 回复保留的 token |
compaction.keepRecentTokens | 数字 | 20000 | 保留的近期 token(不被摘要) |
compaction.modelOverrides | 对象 | - | 以精确 "provider/modelId" 为键的每模型 reserveTokens 和 keepRecentTokens 覆盖 |
每模型压缩覆盖
键必须精确匹配区分大小写的 provider/modelId 值,而不是名称或 glob 模式。模型 ID 可能包含斜杠(例如 openrouter/anthropic/claude-sonnet-4)。
每个 token 设置独立解析:匹配的模型覆盖 → 普通 compaction 设置 → 内置默认值。在上面的例子中,some-provider/big-model 仍保留普通的 20000 近期 token。token 值必须是非负的安全整数。匹配的模型覆盖中存在无效值时会在读取时报错;只有省略的字段才会回退到普通设置。模型覆盖条目必须是对象。普通 token 设置存在无效值时同样会在读取时报错,即使当前模型有有效的覆盖。只有省略的普通值才使用内置默认值。零是允许的,但 reserveTokens: 0 会失去回复余量,并将摘要输出预算也设为零。
全局和项目设置在模型查找之前递归合并。项目可以只覆盖某个模型的某一个字段,而不替换它的其他字段或其他模型。全局的模型专属值优先于项目范围的回退值;要更改它,需在项目中覆盖同一模型条目。
enabled 不区分模型。当前模型的 token 设置适用于手动压缩、自动阈值检查(包括助手回合之间)以及溢出恢复。切换模型会在下一次检查或压缩时生效。覆盖请在 JSON 中配置;/settings 只保留普通的自动压缩开关。
触发和摘要行为参见 compaction.md。
分支摘要
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
branchSummary.reserveTokens | 数字 | 16384 | 选择分支历史时保留的 token;输出上限为 4096 token |
branchSummary.skipPrompt | 布尔值 | false | 在 /tree 导航时跳过 “Summarize branch?” 提示(默认不做摘要) |
重试
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
retry.enabled | 布尔值 | true | 在瞬时错误上启用自动的智能体级重试 |
retry.maxRetries | 数字 | 3 | 智能体级重试的最大次数 |
retry.baseDelayMs | 数字 | 2000 | 智能体级指数退避的基础延迟(2 秒、4 秒、8 秒) |
retry.maxAgentDelayMs | 数字 | 60000 | 智能体级重试的最大延迟(60 秒) |
retry.provider.timeoutMs | 数字 | SDK 默认值 | 提供商/SDK 请求超时(毫秒) |
retry.provider.maxRetries | 数字 | 0 | 提供商/SDK 重试次数 |
retry.provider.maxRetryDelayMs | 数字 | 60000 | 失败前允许的服务器请求延迟上限(60 秒) |
智能体级重试使用以 retry.maxAgentDelayMs 为上限的指数退避,因此即使长时间故障,较长的重试过程也能保持响应。
当提供商请求的重试延迟超过 retry.provider.maxRetryDelayMs 时,请求会立即失败并给出有说明的错误,而不是静默等待。将其设为 0 可禁用该限制。
除非确实需要提供商级重试,否则请将 retry.provider.maxRetries 保持为 0。将其设为大于 0 可能让 SDK/提供商重试在 Pi 察觉之前处理超出用量限额的错误,某些情况下这会阻塞智能体,直到提供商配额重置。
消息送达
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
steeringMode | 字符串 | "one-at-a-time" | 转向消息的发送方式:"all" 或 "one-at-a-time" |
followUpMode | 字符串 | "one-at-a-time" | 后续消息的发送方式:"all" 或 "one-at-a-time" |
transport | 字符串 | "auto" | 支持多种传输方式的提供商的首选传输:"sse"、"websocket"、"websocket-cached" 或 "auto" |
httpIdleTimeoutMs | 数字 | 300000 | HTTP 头部/正文空闲超时(毫秒),同样适用于有显式流空闲超时的提供商。设为 0 可禁用。 |
websocketConnectTimeoutMs | 数字 | 15000 | 支持 WebSocket 传输的提供商的 WebSocket 连接/打开握手超时(毫秒)。设为 0 可禁用。 |
终端与图片
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
terminal.showImages | 布尔值 | true | 在终端中显示图片(如果支持) |
terminal.imageWidthCells | 数字 | 60 | 终端中行内图片的首选宽度(以单元格计) |
terminal.clearOnShrink | 布尔值 | false | 内容收缩时清除空行(可能导致闪烁) |
terminal.hyperlinks | 布尔值或 "auto" | "auto" | 覆盖 OSC 8 超链接支持(高级,仅 JSON) |
terminal.images | 字符串或布尔值 | "auto" | 以 "kitty"、"iterm2"、false 或 "auto" 覆盖图片协议支持(高级,仅 JSON) |
terminal.trueColor | 布尔值或 "auto" | "auto" | 覆盖真彩色支持(高级,仅 JSON) |
images.autoResize | 布尔值 | true | 将图片缩放到最大 2000x2000。适用于 @file 附件、read 以及工具返回的图片 |
images.blockImages | 布尔值 | false | 阻止所有图片发送给 LLM |
Shell
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
shellPath | 字符串 | - | 自定义 shell 路径(例如 Windows 上的 Cygwin);支持开头的 ~ 表示主目录 |
shellCommandPrefix | 字符串 | - | 每条 bash 命令的前缀(例如 "shopt -s expand_aliases") |
npmCommand | 字符串数组 | - | npm 包查找/安装操作使用的命令 argv(例如 ["mise", "exec", "node@20", "--", "npm"]) |
JSON 中的 Windows 路径必须使用正斜杠或转义的反斜杠:
npmCommand 用于所有 npm 包管理器操作,包括安装、卸载以及 git 包内部的依赖安装。用户范围的 npm 包安装在 ~/.pi/agent/npm/ 下;项目范围的 npm 包安装在 .pi/npm/ 下。请按进程的实际启动方式使用 argv 形式的条目。配置了 npmCommand 后,git 包的依赖安装会使用纯 install,以避免包装器或替代包管理器中的 npm 专属标志。
工具
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
defaultTools | 字符串数组 | - | 启动时启用的内置工具。省略时,Pi 使用其标准默认值 |
defaultTools 选择启动时启用的内置工具。扩展和 SDK 自定义工具仍然保持启用。可用的内置工具有 read、bash、powershell、edit、write、grep、find 和 ls:
在 Windows 上,请选择 powershell 而不是 bash,或者两者都包含:
空数组表示启动时不启用任何内置工具,同时保留扩展和 SDK 自定义工具。--tools 会以针对所有工具的严格白名单取代这一行为,--no-tools 禁用所有工具,--no-builtin-tools 禁用内置默认工具。--exclude-tools 会在结果列表上再做过滤。项目的 defaultTools 数组会替换全局数组。
会话
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sessionDir | 字符串 | - | 会话文件的存储目录。接受绝对或相对路径,也支持 ~。 |
当多个来源指定会话目录时,优先级依次为 --session-dir、PI_CODING_AGENT_SESSION_DIR、settings.json 中的 sessionDir。
模型循环
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabledModels | 字符串数组 | - | 用于 Ctrl+P 循环切换的模型模式(格式与 --models CLI 标志相同) |
Markdown
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
markdown.codeBlockIndent | 字符串 | " " | 代码块的缩进 |
markdown.mermaid | 字符串 | "streaming" | Mermaid 渲染模式:"off"、"final" 或 "streaming" |
资源
这些设置定义从哪里加载扩展、技能、提示词和主题。
~/.pi/agent/settings.json 中的路径相对于 ~/.pi/agent 解析。.pi/settings.json 中的路径相对于 .pi 解析。支持绝对路径和 ~。
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
packages | 数组 | [] | 从中加载资源的 npm/git 包 |
extensions | 字符串数组 | [] | 本地扩展文件路径或目录 |
skills | 字符串数组 | [] | 本地技能文件路径或目录 |
prompts | 字符串数组 | [] | 本地提示词模板路径或目录 |
themes | 字符串数组 | [] | 本地主题文件路径或目录 |
enableSkillCommands | 布尔值 | true | 将技能注册为 /skill:name 命令 |
数组支持 glob 模式和排除项。使用 !pattern 排除。使用 +path 强制包含某个精确路径,使用 -path 强制排除某个精确路径。
packages
字符串形式会加载包中的所有资源:
对象形式可以过滤要加载的资源:
包管理详情参见 packages.md。
示例
项目覆盖
项目设置(.pi/settings.json)会覆盖全局设置。嵌套对象会被合并:
8 快捷键
所有键盘快捷键都可以通过 ~/.pi/agent/keybindings.json 自定义。每个动作可以绑定一个或多个按键。
该配置文件使用与 pi 内部以及扩展作者在 keyHint() 和注入的 keybindings 管理器中相同的带命名空间快捷键 ID。
使用 cursorUp 或 expandTools 等旧版无命名空间 ID 的配置会在启动时自动迁移为带命名空间的 ID。
编辑 keybindings.json 后,在 pi 中运行 /reload 即可应用更改,无需重启会话。
按键格式
格式为 modifier+key,其中修饰键为 ctrl、shift、alt、super(可组合),按键包括:
- 字母:
a-z - 数字:
0-9 - 特殊键:
escape、esc、enter、return、tab、space、backspace、delete、insert、clear、home、end、pageUp、pageDown、up、down、left、right - 功能键:
f1-f12 - 符号:
`、-、=、[、]、\、;、'、,、.、/、!、@、#、$、%、^、&、*、(、)、_、+、|、~、{、}、:、<、>、?
修饰键组合:ctrl+shift+x、alt+ctrl+x、ctrl+shift+alt+x、super+k、ctrl+super+k、ctrl+1 等。
super 绑定要求终端能够单独上报该修饰键,通常需要 Kitty 键盘协议。在不支持的终端中可能无法工作。
全部动作
TUI 编辑器光标移动
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
tui.editor.cursorUp | up | 光标上移,到达顶部后浏览更早的历史 |
tui.editor.cursorDown | down | 光标下移,到达底部后浏览更新的历史 |
tui.editor.historyPrevious | (无) | 选择上一条提示词历史 |
tui.editor.historyNext | (无) | 选择下一条提示词历史 |
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+home、ctrl+a | 移动到行首 |
tui.editor.cursorLineEnd | end、ctrl+end、ctrl+e | 移动到行尾 |
tui.editor.jumpForward | ctrl+] | 向前跳转到指定字符 |
tui.editor.jumpBackward | ctrl+alt+] | 向后跳转到指定字符 |
tui.editor.pageUp | pageUp、ctrl+pageUp | 向上翻页 |
tui.editor.pageDown | pageDown、ctrl+pageDown | 向下翻页 |
专用的历史动作始终切换历史条目,与多行提示词中的光标位置无关。当主编辑器获得焦点时,显式的历史绑定优先于应用动作,因此将 tui.editor.historyPrevious 绑定到 ctrl+p 只会在该上下文中覆盖模型循环切换,而不会改变选择器中的 Ctrl+P。
TUI 编辑器删除
| 快捷键 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 | 删除到行尾 |
TUI 输入
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
tui.input.newLine | shift+enter、ctrl+j | 插入新行 |
tui.input.submit | enter | 提交输入 |
tui.input.tab | tab | Tab / 自动补全 |
TUI 剪切环
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
tui.editor.yank | ctrl+y | 粘贴最近删除的文本 |
tui.editor.yankPop | alt+y | 在粘贴后循环切换已删除的文本 |
tui.editor.undo | ctrl+-(Windows 上为 ctrl+z;WSL 上为 alt+z) | 撤销上次编辑 |
TUI 剪贴板与选择
| 快捷键 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 | 取消选择 |
TUI 全屏视口
这些动作在交互模式使用 --tui-mode fullscreen 时生效,作用于主对话记录滚动区域。双指触控板和鼠标滚轮输入会滚动指针所在区域;当指针位于固定的编辑器/状态栏/底部栏停靠区上时,则回退为滚动对话记录。点击 OSC 8 超链接会用默认处理器打开它。按住主鼠标键拖动会选择文本并复制到剪贴板;在对话记录的上、下边缘按住不动时会自动滚动到屏幕外的内容。对话记录向上滚动时,其底部行会显示可点击的 “Jump to latest message” 标签,展示 tui.altScreen.bottom 快捷键。各终端专属的鼠标与触控行为参见终端设置。
全屏对话记录绑定优先于编辑器绑定。因此在全屏模式下,默认的无修饰导航键控制对话记录,而它们的 ctrl 变体继续控制编辑器。在非全屏模式下,两种变体都控制编辑器。
对话记录搜索面板会显示已配置的上一个/下一个快捷键以及可点击的箭头控件。再次按下 tui.altScreen.search,或使用 tui.altScreen.searchClose,即可关闭它。
| 按键 | 默认模式 | 全屏模式 |
|---|---|---|
home、end | 编辑器 | 对话记录 |
ctrl+home、ctrl+end | 编辑器 | 编辑器 |
pageUp、pageDown | 编辑器 | 对话记录 |
ctrl+pageUp、ctrl+pageDown | 编辑器 | 编辑器 |
这一路由仍可通过常规的动作绑定进行配置。例如,"tui.altScreen.pageUp": "ctrl+pageUp" 会让全屏模式下的 pageUp 控制编辑器、ctrl+pageUp 控制对话记录。绑定 tui.altScreen.halfPageUp 和 tui.altScreen.halfPageDown 可以按半页步进,绑定 tui.altScreen.lineUp 和 tui.altScreen.lineDown 可以按单行步进。设置 "tui.altScreen.pageUp": [] 会完全禁用该对话记录快捷键。用户绑定会替换该动作的默认值。
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
tui.altScreen.pageUp | pageUp | 将对话记录向上滚动一页 |
tui.altScreen.pageDown | pageDown | 将对话记录向下滚动一页 |
tui.altScreen.halfPageUp | (无) | 将对话记录向上滚动半页 |
tui.altScreen.halfPageDown | (无) | 将对话记录向下滚动半页 |
tui.altScreen.lineUp | (无) | 将对话记录向上滚动一行 |
tui.altScreen.lineDown | (无) | 将对话记录向下滚动一行 |
tui.altScreen.previousPrompt | ctrl+shift+up、ctrl+up(Windows 和 WSL 上仅为 ctrl+up) | 跳转到上一个标记消息 |
tui.altScreen.nextPrompt | ctrl+shift+down、ctrl+down(Windows 和 WSL 上仅为 ctrl+down) | 跳转到下一个标记消息 |
tui.altScreen.search | ctrl+shift+f(Windows 和 WSL 上为 ctrl+f) | 搜索渲染后的对话记录 |
tui.altScreen.searchNext | enter、ctrl+g | 搜索时选择下一个匹配项 |
tui.altScreen.searchPrevious | shift+enter、ctrl+shift+g | 搜索时选择上一个匹配项 |
tui.altScreen.searchClose | escape | 关闭对话记录搜索 |
tui.altScreen.top | home | 滚动到对话记录开头 |
tui.altScreen.bottom | end | 滚动到对话记录末尾并跟随新输出 |
应用
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
app.interrupt | escape | 取消 / 中止 |
app.clear | ctrl+c | 清空编辑器(第一次)/ 退出(第二次) |
app.exit | ctrl+d | 退出(编辑器为空时) |
app.suspend | ctrl+z(Windows 上无) | 挂起到后台 |
app.editor.external | ctrl+g | 在外部编辑器中打开(externalEditor、$VISUAL、$EDITOR、Windows 上的记事本或其他平台上的 nano) |
app.clipboard.pasteImage | ctrl+v(Windows 和 WSL 上为 alt+v) | 从剪贴板粘贴图片或文本 |
会话
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
app.session.new | (无) | 开始新会话(/new) |
app.session.tree | (无) | 打开会话树导航器(/tree) |
app.session.fork | (无) | 分叉当前会话(/fork) |
app.session.resume | (无) | 打开会话恢复选择器(/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 | 查询为空时删除会话 |
模型与思考
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
app.model.select | ctrl+l | 打开模型选择器 |
app.model.cycleForward | ctrl+p | 循环切换到下一个模型 |
app.model.cycleBackward | shift+ctrl+p(Windows 和 WSL 上为 alt+p) | 循环切换到上一个模型 |
app.models.save | ctrl+s | 将选定的默认模型或范围模型配置保存到设置 |
app.thinking.cycle | shift+tab | 循环切换思考级别 |
app.thinking.save | ctrl+s | 将当前思考级别保存到设置 |
app.thinking.toggle | ctrl+t | 折叠或展开思考块 |
显示与消息队列
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
app.tools.expand | ctrl+o | 折叠或展开工具输出 |
app.message.copy | ctrl+x | 在 /tree 中复制选中的消息;否则复制上一条助手消息;当 fullscreenCopyOnSelect 禁用时,复制全屏模式下活动的文本选区 |
app.message.followUp | alt+enter(Windows 和 WSL 上为 ctrl+q) | 将后续消息加入队列 |
app.message.dequeue | alt+up(Windows 和 WSL 上为 alt+q) | 将排队消息恢复到编辑器 |
树状导航
| 快捷键 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 | 向后循环切换树过滤器 |
作用域模型选择器
用于作用域模型选择器内部(通过 /scoped-models 打开)。
| 快捷键 ID | 默认值 | 说明 |
|---|---|---|
app.models.enableAll | ctrl+a | 启用所有模型(或当前搜索匹配的所有模型) |
app.models.clearAll | ctrl+x | 清除所有模型(或当前搜索匹配的所有模型) |
app.models.toggleProvider | ctrl+p | 切换当前提供商的所有模型 |
app.models.reorderUp | alt+up | 在循环顺序中将所选模型上移 |
app.models.reorderDown | alt+down | 在循环顺序中将所选模型下移 |
自定义配置
创建 ~/.pi/agent/keybindings.json:
每个动作可以绑定单个按键或一个按键数组。用户配置会覆盖默认值。
在原生 Windows 上,app.suspend 没有默认绑定,因为 Windows 终端不支持 Unix 作业控制。如果你手动绑定它,pi 会显示一条状态消息而不是挂起进程。在 WSL 中,正常的 Linux ctrl+z/fg 行为仍然适用。
Emacs 示例
Vim 示例
9 会话
Pi 会把对话保存为会话,让你可以继续之前的工作、从更早的回合分叉,或者回顾之前走过的路径。
会话存储
会话自动保存到 ~/.pi/agent/sessions/,按工作目录组织。每个会话都是一个具有树状结构的 JSONL 文件。
在交互模式下使用 /session 可以查看当前会话文件、会话 ID、消息数、token 数和费用。
关于 JSONL 文件格式和 SessionManager API,请参阅会话格式。
会话命令
| 命令 | 说明 |
|---|---|
/resume | 浏览并选择之前的会话 |
/new | 开始一个新会话 |
/name <name> | 设置当前会话的显示名称 |
/session | 显示会话信息 |
/tree | 在当前会话树中导航 |
/fork | 从之前的某条用户消息创建新会话 |
/clone | 把当前活动分支复制为一个新会话 |
/compact [prompt] | 汇总较早的上下文;见上下文压缩 |
/export [file] | 将会话导出为 HTML |
/share | 上传为私有 GitHub gist,并生成可分享的 HTML 链接 |
恢复和删除会话
/resume 会打开当前项目的交互式会话选择器。pi -r 则在启动时打开同一个选择器。
在选择器中你可以:
- 输入内容进行搜索
- 用 Ctrl+P 切换路径显示
- 用 Ctrl+S 切换排序方式
- 用 Ctrl+N 只显示命名会话
- 用 Ctrl+R 重命名
- 用 Ctrl+D 删除,然后确认
可用时,pi 会使用 trash CLI 执行删除,而不是永久移除文件。
命名会话
使用 /name <name> 设置人类可读的会话名称:
也可以在启动时用 --name 或 -n 设置名称:
命名会话在 /resume 和 pi -r 中更容易找到。
使用 /tree 进行分支
会话以树的形式存储。每个条目都有 id 和 parentId,当前位置就是活动叶子。/tree 让你跳到之前的任意节点并从那里继续,而不必创建新文件。

示例结构:
树状视图操作
| 按键 | 操作 |
|---|---|
| ↑/↓ | 在可见条目间移动 |
| ←/→ | 向上/向下翻页 |
| Ctrl+←/Ctrl+→ 或 Alt+←/Alt+→ | 折叠/展开,或在分支段之间跳转 |
| Shift+L | 为选中条目设置或清除标签 |
| Shift+T | 切换标签时间戳 |
| Enter | 选中条目 |
| Escape/Ctrl+C | 取消 |
| Ctrl+O | 循环切换过滤模式 |
过滤模式包括:default、no-tools、user-only、labeled-only 和 all。可在设置中通过 treeFilterMode 配置默认值。
选择行为
选中一条用户消息或自定义消息时:
- 把叶子移动到所选消息的父节点。
- 把所选消息的文本放入编辑器。
- 让你编辑并重新提交,从而创建一个新分支。
选中助手、工具、压缩或其他非用户条目时:
- 把叶子移动到该条目。
- 编辑器保持为空。
- 让你从该位置继续。
选中根用户消息会把叶子重置为空对话,并把原始提示词放入编辑器。
/tree、/fork 与 /clone
| 功能 | /tree | /fork | /clone |
|---|---|---|---|
| 输出 | 同一个会话文件 | 新会话文件 | 新会话文件 |
| 视图 | 完整树 | 用户消息选择器 | 当前活动分支 |
| 典型用途 | 在原位探索不同方案 | 从较早的提示词开启新会话 | 在继续之前复制当前工作 |
| 摘要 | 可选的分支摘要 | 无 | 无 |
当你想把不同方案保留在同一个会话中时,使用 /tree。当你想要一个独立的会话文件时,使用 /fork 或 /clone。
分支摘要
当 /tree 从一个分支切换到另一个分支时,pi 可以对被放弃的分支生成摘要,并把该摘要附加到新位置。这样既保留了你所离开路径上的重要上下文,又不必重放整个分支。
出现提示时,从以下选项中选择:
- 不生成摘要
- 使用默认提示词生成摘要
- 使用自定义侧重点指令生成摘要
关于分支汇总的内部机制和扩展钩子,请参阅上下文压缩。
会话格式
会话文件为 JSONL 格式,包含消息条目、模型变更、思考级别变更、标签、压缩记录、分支摘要和扩展条目。
关于解析器、扩展、SDK 用法和完整的 SessionManager API,请参阅会话格式。
10 上下文压缩与分支摘要
LLM 的上下文窗口是有限的。当对话变得过长时,Pi 会使用上下文压缩来总结较早的内容,同时保留近期的工作。本页面同时介绍自动压缩和分支摘要。
源码文件(pi):
packages/coding-agent/src/core/compaction/compaction.ts- 自动压缩逻辑packages/coding-agent/src/core/compaction/branch-summarization.ts- 分支摘要packages/coding-agent/src/core/compaction/utils.ts- 共享工具(文件跟踪、序列化)packages/coding-agent/src/core/session-manager.ts- 条目类型(CompactionEntry、BranchSummaryEntry)packages/coding-agent/src/core/extensions/types.ts- 扩展事件类型
如需项目中的 TypeScript 定义,请查看 node_modules/@earendil-works/pi-coding-agent/dist/。
概述
Pi 有两种摘要机制:
| 机制 | 触发条件 | 目的 |
|---|---|---|
| 上下文压缩 | 上下文超过阈值,或 /compact | 总结旧消息以释放上下文 |
| 分支摘要 | /tree 导航 | 切换分支时保留上下文 |
两者使用相同的结构化摘要格式,并累积跟踪文件操作。压缩和分支摘要请求使用全新的路由会话 ID,并且在提供商支持的情况下禁用提示词缓存写入,因为这些一次性提示词不太可能被复用。
上下文压缩
触发时机
自动压缩在以下条件满足时触发:
默认情况下,reserveTokens 为 16384 token(可在 ~/.pi/agent/settings.json 或 <project-dir>/.pi/settings.json 中配置)。这为 LLM 的回复留出空间。
在多回合的智能体运行过程中,Pi 会在工具完成且结果被追加之后、开始下一条助手回复之前检查该阈值。如果超过阈值,Pi 会在同一次智能体运行内完成压缩,并带着摘要和保留的消息继续。当已完成的工具批次结束了本次运行、且没有排队消息需要再次回复时,Pi 会跳过这次回合间检查。Pi 还会在新的用户提示词之前以及底层智能体运行结束后检查该阈值。
也可以使用 /compact [instructions] 手动触发,可选的指令用于聚焦摘要内容。
工作原理
- 确定切割点:从最新消息向前回溯,累积 token 估算值,直到达到
keepRecentTokens(默认 20k,可在~/.pi/agent/settings.json或<project-dir>/.pi/settings.json中配置) - 提取消息:收集从上一次保留边界(或会话起点)到切割点之间的消息
- 生成摘要:调用 LLM 以结构化格式进行总结,存在前一份摘要时将其作为迭代上下文传入
- 追加条目:保存包含摘要和
firstKeptEntryId的CompactionEntry - 重建上下文:会话为下一次请求重建上下文,使用摘要 + 从
firstKeptEntryId起的消息
在多次压缩中,被摘要的范围从上一次压缩的保留边界(firstKeptEntryId)开始,而不是从压缩条目本身开始;如果该保留条目在路径中找不到,则回退到上一次压缩之后的那条条目。这样,在上一次压缩中幸存的消息也会被纳入下一次摘要过程,从而得到保留。Pi 还会在写入新的 CompactionEntry 之前,根据重建后的会话上下文重新计算 tokensBefore,使 token 计数反映被替换的实际压缩前上下文。
拆分回合
“回合"从一条用户消息开始,包含直到下一条用户消息之前的所有助手回复和工具调用。通常,压缩在回合边界处切割。
当单个回合超过 keepRecentTokens 时,切割点会落在回合中间的某条助手消息上。这就是"拆分回合”:
对于拆分回合,Pi 会生成两份摘要并合并它们:
- 历史摘要:之前的上下文(如果有)
- 回合前缀摘要:拆分回合的前半部分
切割点规则
有效的切割点是:
- 用户消息
- 助手消息
- BashExecution 消息
- 自定义消息(custom_message、branch_summary)
绝不在工具结果处切割(它们必须与对应的工具调用待在一起)。
CompactionEntry 结构
定义于 session-manager.ts:
扩展可以在 details 中存储任何可 JSON 序列化的数据。默认压缩会跟踪文件操作,但自定义的扩展实现可以使用自己的结构。生成和扩展提供的摘要会在可用时存储其 LLM usage,使会话总计包含摘要工作。
实现参见 prepareCompaction() 和 compact()。如需直接以编程方式生成摘要,generateSummary() 返回摘要文本,generateSummaryWithUsage() 返回 { text, usage }。
分支摘要
触发时机
当你使用 /tree 导航到另一个分支时,Pi 会提议对你正在离开的工作进行摘要。这会把被离开分支的上下文注入新分支。
工作原理
- 寻找共同祖先:旧位置与新位置共享的最深节点
- 收集条目:从旧叶子回溯到共同祖先
- 在预算内准备:包含不超过 token 预算的消息(优先最新)
- 生成摘要:以结构化格式调用 LLM
- 追加条目:在导航位置保存
BranchSummaryEntry
累积文件跟踪
压缩和分支摘要都会累积跟踪文件。生成摘要时,pi 会从以下来源提取文件操作:
- 正在被摘要的消息中的工具调用
- 之前的压缩或分支摘要
details(如果有)
这意味着文件跟踪会在多次压缩或嵌套的分支摘要之间累积,保留已读取和已修改文件的完整历史。
BranchSummaryEntry 结构
定义于 session-manager.ts:
与压缩相同,扩展可以在 details 中存储自定义数据。
实现参见 collectEntriesForBranchSummary()、prepareBranchEntries() 和 generateBranchSummary()。
摘要格式
压缩和分支摘要使用相同的结构化格式:
消息序列化
在摘要之前,消息会通过 serializeConversation() 序列化为文本:
这样可以避免模型将其视为需要继续的对话。
序列化时,工具结果会被截断到 2000 个字符。超出该限制的内容会替换为一个标记,指明被截断的字符数。这样可以让摘要请求保持在合理的 token 预算内,因为工具结果(尤其是来自 read 和 bash 的)通常是上下文大小的最大来源。
通过扩展自定义摘要
扩展可以拦截并自定义压缩和分支摘要。事件类型定义参见 extensions/types.ts。
session_before_compact
在自动压缩或 /compact 之前触发。可以取消操作或提供自定义摘要。参见类型文件中的 SessionBeforeCompactEvent 和 CompactionPreparation。
将消息转换为文本
要用你自己的模型生成摘要,请使用 serializeConversation 将消息转换为文本:
使用不同模型的完整示例参见 custom-compaction.ts。
session_compact_failed
在手动或自动压缩失败或中止时触发。对于需要将 session_before_compact 尝试与最终结果配对的遥测扩展来说非常有用。
session_before_tree
在 /tree 导航之前触发。无论用户是否选择摘要都会触发。可以取消导航或提供自定义摘要。
参见类型文件中的 SessionBeforeTreeEvent 和 TreePreparation。
设置
在 ~/.pi/agent/settings.json 或 <project-dir>/.pi/settings.json 中配置压缩:
| 设置 | 默认值 | 说明 |
|---|---|---|
enabled | true | 启用自动压缩 |
reserveTokens | 16384 | 为 LLM 回复保留的 token |
keepRecentTokens | 20000 | 保留的近期 token(不被摘要) |
使用 "enabled": false 禁用自动压缩。你仍可以使用 /compact 手动压缩。
每模型覆盖
使用 compaction.modelOverrides 为不同模型调整 token 预算:
对于上下文窗口为 1M 的模型,这个覆盖会在超过 600K token 时触发压缩,并保留普通的 20000 近期 token。其他模型仍使用普通的 16384 token 保留量。reserveTokens 还会影响摘要输出上限(以模型的最大输出 token 为上限);它并不只是一个触发阈值。
键必须是精确、区分大小写的 provider/modelId 值,包括模型 ID 中的任何斜杠。每个 reserveTokens 和 keepRecentTokens 值都独立回退:从模型覆盖到普通设置,再到内置默认值。值必须是非负的安全整数。匹配的模型覆盖中存在无效值时会在读取时报错;只有省略的字段才回退到普通设置。模型覆盖条目必须是对象。普通 token 设置存在无效值时同样会在读取时报错,即使当前模型有有效的覆盖。只有省略的普通值才使用内置默认值。enabled 始终是全局的,不区分模型。
这些解析后的值用于手动压缩、所有自动阈值检查、溢出恢复,以及扩展可见的 preparation.settings。切换模型会影响后续的检查和压缩,但不会更改普通设置。已在进行中的压缩使用为该操作捕获的模型和设置。分支摘要设置不受影响。
覆盖在全局和项目设置中都可以使用。两个文件在查找之前递归合并,因此全局的模型专属值优先于项目范围的回退值;项目必须覆盖该模型条目才能更改它。详情参见 settings.md。
11 扩展
pi 自己也能创建扩展。可以让它为你的使用场景构建一个。
扩展是用于扩展 pi 行为的 TypeScript 模块。它们可以订阅生命周期事件、注册可由 LLM 调用的自定义工具、添加命令等。
/reload 的存放位置: 若要被自动发现,请将扩展放在
~/.pi/agent/extensions/(全局)或.pi/extensions/(项目本地)。pi -e ./path.ts仅用于快速测试。位于自动发现位置的扩展可以通过/reload热重载。
核心能力:
- 自定义工具 - 通过
pi.registerTool()注册可由 LLM 调用的工具 - 事件拦截 - 阻止或修改工具调用、注入上下文、自定义上下文压缩
- 用户交互 - 通过
ctx.ui提示用户(select、confirm、input、notify) - 自定义 UI 组件 - 通过
ctx.ui.custom()使用带键盘输入的完整 TUI 组件,应对复杂交互 - 自定义命令 - 通过
pi.registerCommand()注册/mycommand之类的命令 - 会话持久化 - 通过
pi.appendEntry()存储可在重启后保留的状态 - 自定义渲染 - 控制工具调用/结果和消息在 TUI 中的呈现方式
典型用例:
- 权限门禁(在
rm -rf、sudo等操作前确认) - Git 检查点(每轮 stash,在分支上恢复)
- 路径保护(阻止写入
.env、node_modules/) - 自定义上下文压缩(按你的方式总结对话)
- 对话摘要(参见
summarize.ts示例) - 交互式工具(提问、向导、自定义对话框)
- 有状态工具(待办列表、连接池)
- 外部集成(文件监视器、webhook、CI 触发器)
- 等待期间的小游戏(参见
snake.ts示例)
可运行的实现参见 examples/extensions/。
目录
- 快速开始
- 扩展位置
- 可用的导入
- 编写扩展
- 事件
- ExtensionContext
- ExtensionCommandContext
- ExtensionAPI 方法
- 状态管理
- 自定义工具
- 自定义 UI
- 错误处理
- 模式行为
- 示例参考
快速开始
创建 ~/.pi/agent/extensions/my-extension.ts:
使用 --extension(或 -e)标志进行测试:
扩展位置
安全提示: 扩展以你的完整系统权限运行,可以执行任意代码。只从你信任的来源安装。
扩展会从受信任的位置自动发现。项目本地的 .pi/extensions 条目只会在项目获得信任后加载。
| 位置 | 作用域 |
|---|---|
~/.pi/agent/extensions/*.ts | 全局(所有项目) |
~/.pi/agent/extensions/*/index.ts | 全局(子目录) |
.pi/extensions/*.ts | 项目本地 |
.pi/extensions/*/index.ts | 项目本地(子目录) |
还可以通过 settings.json 添加额外路径:
要通过 npm 或 git 以 pi 包的形式分享扩展,参见 packages.md。
可用的导入
| 包 | 用途 |
|---|---|
@earendil-works/pi-coding-agent | 扩展类型(ExtensionAPI、ExtensionContext、事件) |
typebox | 工具参数的 schema 定义 |
@earendil-works/pi-ai | AI 工具(StringEnum,用于 Google 兼容的枚举) |
@earendil-works/pi-tui | 用于自定义渲染的 TUI 组件 |
npm 依赖也可以使用。在扩展旁边(或其上级目录中)添加一个 package.json,运行 npm install,来自 node_modules/ 的导入就会被自动解析。
对于通过 pi install 安装的分布式 pi 包(npm 或 git),运行时依赖必须位于 dependencies 中。包安装默认使用生产安装(npm install --omit=dev),因此 devDependencies 在运行时不可用;配置了 npmCommand 时,git 包会使用普通的 install,以兼容各类包装器。
Node.js 内置模块(node:fs、node:path 等)同样可用。
编写扩展
扩展导出一个接收 ExtensionAPI 的默认工厂函数。该工厂函数可以是同步的,也可以是异步的:
扩展通过 jiti 加载,因此 TypeScript 无需编译即可使用。
如果工厂函数返回 Promise,pi 会在继续启动之前等待它完成。也就是说,异步初始化会在 session_start 之前、resources_discover 之前完成,也会先于通过 pi.registerProvider() 排队的提供商注册被刷新。
异步工厂函数
如果需要一次性的启动工作,例如获取远程配置或动态发现可用模型,可以使用异步工厂函数。
这种模式让获取到的模型在正常启动期间可用,也能被 pi --list-models 列出。
长生命周期资源与关闭
扩展工厂可能在从不启动会话的调用中运行。不要在工厂函数中启动后台资源,例如进程、socket、文件监视器或定时器。
把后台资源的启动推迟到 session_start 或需要该资源的命令/工具/事件。注册一个幂等的 session_shutdown 处理器,用于关闭你启动的所有会话级资源。
扩展形式
单文件 - 最简单,适合小型扩展:
带 index.ts 的目录 - 适合多文件扩展:
带依赖的包 - 适合需要 npm 包的扩展:
在扩展目录中运行 npm install,之后来自 node_modules/ 的导入就会自动生效。
事件
生命周期概览
启动事件
project_trust
在 pi 决定是否信任一个带有动态配置(.pi 或 .agents/skills)的项目之前触发。它在启动期间触发,也会在会话替换(例如 /resume)进入一个其信任状态尚未在当前进程中解析的 cwd 时触发。只有用户/全局扩展和 CLI -e 扩展会参与;项目本地扩展要等到信任解析之后才会加载。
project_trust 处理器必须返回 { trusted: "yes" | "no" | "undecided" }。返回 "yes" 或 "no" 的用户/全局扩展或 CLI 扩展即拥有决定权;第一个 yes/no 决定会胜出并抑制内置的信任提示。使用 remember: true 可持久化 yes/no 决定;否则它只对当前进程生效。返回 "undecided" 则交给后续处理器或内置信任流程决定。弹出提示前先检查 ctx.hasUI。如果没有处理器返回 yes/no,则继续正常的信任解析:先应用已保存的 trust.json 决定,再由 defaultProjectTrust 控制 pi 默认是询问、信任还是拒绝。
资源事件
resources_discover
在 session_start 之后触发,让扩展可以贡献额外的技能、提示词和主题路径。启动路径使用 reason: "startup",重载使用 reason: "reload"。
会话事件
会话存储内部结构和 SessionManager API 参见 会话格式。
session_start
在会话启动、加载或重载时触发。
session_info_changed
当通过 /name、RPC 或 pi.setSessionName() 设置当前会话的显示名称时触发。
session_before_switch
在启动新会话(/new)或切换会话(/resume)之前触发。
成功切换或新建会话之后,pi 会为旧的扩展实例发出 session_shutdown,为新会话重新加载并重新绑定扩展,然后发出带有 reason: "new" | "resume" 和 previousSessionFile 的 session_start。
清理工作放在 session_shutdown 中完成,然后在 session_start 中重建任何内存状态。
session_before_fork
在通过 /fork 分叉或通过 /clone 克隆时触发。
成功分叉或克隆之后,pi 会为旧的扩展实例发出 session_shutdown,为新会话重新加载并重新绑定扩展,然后发出带有 reason: "fork" 和 previousSessionFile 的 session_start。
清理工作放在 session_shutdown 中完成,然后在 session_start 中重建任何内存状态。
session_before_compact / session_compact / session_compact_failed
在上下文压缩时触发。详情参见 compaction.md。
session_before_tree / session_tree
在 /tree 导航时触发。树状导航的概念参见 会话。
session_shutdown
在已启动的会话运行时被销毁之前触发。用它清理在 session_start 或其他会话级钩子中打开的资源。
智能体事件
before_agent_start
在用户提交提示词之后、智能体循环开始之前触发。可以注入消息和/或修改系统提示词。
systemPromptOptions 字段让扩展可以访问 pi 构建系统提示词所用的同一份结构化数据。这样你无需重新发现资源或重新解析标志,就能查看 pi 加载了什么——自定义提示词、指南、工具摘要、上下文文件、技能。当你的扩展需要在尊重用户配置的前提下对系统提示词做深入而知情的修改时,可以使用它。
在 before_agent_start 内部,event.systemPrompt 和 ctx.getSystemPrompt() 都反映截至当前处理器的链式系统提示词。后面的 before_agent_start 处理器仍然可以再次修改它。
agent_start / agent_end / agent_settled
agent_start 在低层智能体运行开始时触发。agent_end 在该运行结束时触发,但 pi 之后仍可能自动重试、自动上下文压缩后重试,或继续处理排队的后续消息。需要知道 pi 不会自动继续运行的集成(例如状态集成)请使用 agent_settled。
ui_prompt_start / ui_prompt_end
针对阻塞式用户界面提示的通知型生命周期事件。它们在 ctx.ui.select()、ctx.ui.confirm()、ctx.ui.input()、ctx.ui.editor() 和 ctx.ui.custom() 周围触发,让宿主/状态集成可以报告「正在等待用户」,而不只是「运行中」。
嵌套或重叠的提示会被合并为一个外层等待区间。处理器以尽力而为的方式调用,不会在提示显示或关闭之前被等待。
turn_start / turn_end
每一轮(一次 LLM 响应 + 工具调用)都会触发。
message_start / message_update / message_end
在消息生命周期更新时触发。
message_start和message_end针对 user、assistant 和 toolResult 消息触发。message_update针对 assistant 流式更新触发。message_end处理器可以返回{ message }来替换最终确定的消息。替换后的消息必须保持相同的role。
tool_execution_start / tool_execution_update / tool_execution_end
在工具执行生命周期更新时触发。
在并行工具模式下:
tool_execution_start在预检阶段按 assistant 源顺序发出tool_execution_update事件可能在多个工具之间交错tool_execution_end在每个工具定稿之后按工具完成顺序发出- 最终的
toolResult消息事件稍后仍按 assistant 源顺序发出
context
在每次 LLM 调用之前触发。以非破坏性的方式修改消息。消息类型参见 会话格式。
before_provider_headers
在传出 HTTP 头组装完成之后触发。用它添加、覆盖或删除请求头。
处理器就地修改 event.headers。把某个键设为字符串即可添加或覆盖它,设为 null 即可删除它。
每次提供商请求运行一次;重试会复用相同的头,而不会重新触发该钩子。
before_provider_request
在构建完提供商特定的 payload 之后、请求发出之前触发。处理器按扩展加载顺序运行。返回 undefined 保持 payload 不变;返回任何其他值都会为后续处理器和实际请求替换 payload。
这个钩子可以改写提供商层面的系统指令,或将其完全移除。这些 payload 层面的修改不会反映在 ctx.getSystemPrompt() 中,后者报告的是 pi 的系统提示词字符串,而不是最终序列化的提供商 payload。
这主要用于调试提供商的序列化和缓存行为。
after_provider_response
在收到 HTTP 响应之后、消费其流式主体之前触发。处理器按扩展加载顺序运行。
头的可用性取决于提供商和传输方式。对 HTTP 响应做了抽象的提供商可能不会暴露头。
模型事件
model_select
当模型通过 /model 命令、模型循环切换(Ctrl+P)或会话恢复而变化时触发。
当活动模型变化时,用它来更新 UI 元素(状态栏、页脚)或执行特定于模型的初始化。
thinking_level_select
当思考级别变化时触发。它仅用于通知;处理器返回值会被忽略。
当 pi.setThinkingLevel()、模型变化或内置思考级别控制改变了活动思考级别时,用它来更新扩展 UI。
工具事件
tool_call
在 tool_execution_start 之后、工具执行之前触发。可以阻止。 使用 isToolCallEventType 收窄类型并获得有类型的输入。
在 tool_call 运行之前,pi 会等待先前发出的智能体事件在 AgentSession 中排空完毕。这意味着 ctx.sessionManager 已同步到当前的 assistant 工具调用消息。
在默认的并行工具执行模式下,同一条 assistant 消息中的兄弟工具调用会先按顺序预检,再并发执行。tool_call 不保证能在 ctx.sessionManager 中看到同一 assistant 消息的兄弟工具结果。
event.input 是可变的。就地修改它即可在执行前修补工具参数。
行为保证:
- 对
event.input的修改会影响实际的工具执行 - 后面的
tool_call处理器能看到前面处理器所做的修改 - 修改之后不会重新校验
tool_call的返回值通过{ block: true, reason?: string, terminate?: boolean }控制阻止terminate只对被阻止的调用生效;只有当批次中每个已定稿的结果都是终止性的,智能体才会提前停止
为自定义工具输入标注类型
自定义工具应当导出其输入类型:
使用带有显式类型参数的 isToolCallEventType:
tool_result
在工具执行完成之后、tool_execution_end 与最终工具结果消息事件发出之前触发。可以修改结果。
在并行工具模式下,tool_result 和 tool_execution_end 可能按工具完成顺序交错,而最终的 toolResult 消息事件稍后仍按 assistant 源顺序发出。
tool_result 处理器像中间件一样链式执行:
- 处理器按扩展加载顺序运行
- 每个处理器看到的是前一个处理器修改之后的最新结果
- 处理器可以返回部分补丁(
content、details、isError或usage);省略的字段保持当前值
在处理器内部做嵌套异步工作时使用 ctx.signal。这样 Esc 就能取消由扩展发起的模型调用、fetch() 和其他支持中止的操作。
用户 Bash 事件
user_bash
当用户执行 ! 或 !! 命令时触发。可以拦截。
输入事件
input
在收到用户输入时触发,此时扩展命令已检查完毕,但技能和模板尚未展开。事件看到的是原始输入文本,因此 /skill:foo 和 /template 还没有被展开。
处理顺序:
- 先检查扩展命令(
/cmd)- 如果找到,则运行其处理器并跳过 input 事件 - 触发
input事件 - 可以拦截、转换或处理 - 若未被处理:技能命令(
/skill:name)展开为技能内容 - 若未被处理:提示词模板(
/template)展开为模板内容 - 开始智能体处理(
before_agent_start等)
结果:
continue- 原样放行(处理器不返回任何值时的默认行为)transform- 修改 text/images,然后继续展开handled- 完全跳过智能体(第一个返回此值的处理器获胜)
转换会在多个处理器之间链式生效。感知 streamingBehavior 的路由参见 input-transform.ts 和 input-transform-streaming.ts。
ExtensionContext
所有处理器都会收到 ctx: ExtensionContext。
ctx.ui
用于用户交互的 UI 方法。完整细节参见 自定义 UI。
ctx.mode
当前运行模式:"tui"、"rpc"、"json" 或 "print"。用 ctx.mode === "tui" 来保护仅限终端的功能,例如 custom()、组件工厂、终端输入和直接 TUI 渲染。
ctx.hasUI
在 TUI 和 RPC 模式下为 true;在 print 模式(-p)和 JSON 模式下为 false。用它来保护对话框方法(select、confirm、input、editor),以及同时适用于 TUI 和 RPC 模式的即发即忘方法(notify、setStatus、setWidget、setTitle、setEditorText)。在 RPC 模式下,某些 TUI 特有的方法是无操作或返回默认值(参见 rpc.md)。
ctx.cwd
当前工作目录。
构造项目本地配置路径时,请使用 CONFIG_DIR_NAME,而不要硬编码 .pi。改牌发行版可以使用不同的配置目录名。
ctx.isProjectTrusted()
返回当前会话上下文是否启用了项目本地信任。这包括临时信任决定和 CLI 信任覆盖,而不仅仅是全局信任存储中保存的决定。
在读取只应对受信任项目生效的项目本地扩展配置之前,先做这个检查。
ctx.sessionManager
对会话状态的只读访问。完整的 SessionManager API 和条目类型参见 会话格式。
对于 tool_call,该状态在处理器运行之前已同步到当前的 assistant 消息。在并行工具执行模式下,它仍不保证包含同一 assistant 消息的兄弟工具结果。
ctx.modelRegistry / ctx.model / ctx.thinkingLevel / ctx.scopedModels
访问模型、提供商和已解析的认证信息。ctx.modelRegistry.getProvider(id) 返回生效的 pi-ai 提供商,而 getProviderAuth(id) 会解析其当前 API 密钥、请求头、base URL 和提供商级环境变量,且无需加载模型。ctx.model 是活动模型,ctx.thinkingLevel 是其当前生效的思考级别。
ctx.scopedModels 是限定到当前会话的只读模型列表——与 /scoped-models 命令显示的是同一个集合。它在会话启动时由 --models CLI 标志和 enabledModels 设置解析得出(用 minimatch 按 provider/modelId 或裸 modelId 与可用目录匹配)。未配置限定时它为空,表示所有可用模型都可以使用。每个条目是 { model, thinkingLevel? },其中 thinkingLevel 只有在某个模式固定了它时才会被设置(例如 anthropic/*:high)。用它来填充一个与内置选择器一致的模型选择器,而不是通过 ctx.modelRegistry.getAvailable() 枚举整个目录。
流式模型调用
需要 reasoning 这类提供商中立选项时,使用 ctx.modelRegistry.streamSimple(model, context, options);需要 API 特定选项时,使用 stream()。两者都使用已配置的提供商并解析认证,包括通过 pi.registerProvider() 注册的提供商。请使用它们,而不是 pi-ai/compat 的流式函数,后者看不到扩展的提供商注册。
两者都返回 AssistantMessageEventStream。迭代它以获得响应事件,并等待 .result() 获得最终消息。初始化失败会产生错误事件和错误结果。
ctx.signal
当前的智能体中止信号;没有处于活动状态的智能体轮次时为 undefined。
为扩展处理器发起的可中止嵌套工作使用它,例如:
fetch(..., { signal: ctx.signal })- 接受
signal的模型调用 - 接受
AbortSignal的文件或进程辅助函数
ctx.signal 通常在活动轮次事件(如 tool_call、tool_result、message_update 和 turn_end)期间有定义。
在空闲或非轮次上下文(如会话事件、扩展命令,以及 pi 空闲时触发的快捷键)中通常为 undefined。
ctx.isIdle() / ctx.abort() / ctx.hasPendingMessages()
控制流辅助方法。当 pi 正在处理智能体运行、自动重试、自动上下文压缩重试或排队延续时,ctx.isIdle() 为 false。
ctx.shutdown()
请求 pi 优雅关闭。
- 交互模式: 推迟到智能体空闲之后(处理完所有排队的引导和后续消息)。
- RPC 模式: 推迟到下一个空闲状态(完成当前命令响应、等待下一条命令时)。
- print 模式: 无操作。处理完所有提示词后进程会自动退出。
退出前会向所有扩展发出 session_shutdown 事件。在所有上下文中都可用(事件处理器、工具、命令、快捷键)。
ctx.getContextUsage()
返回活动模型的当前上下文使用量。可用时使用最后一次 assistant 使用量,否则估算尾部消息的 token 数。
ctx.compact()
触发上下文压缩,但不等待完成。用 onComplete 和 onError 执行后续动作。
ctx.getSystemPrompt()
返回 pi 当前的系统提示词字符串。
- 在
before_agent_start期间,它反映当前轮次到目前为止的链式系统提示词修改。 - 它不包含后续
context的消息修改。 - 它不包含
before_provider_request的 payload 改写。 - 如果在你之后加载的扩展晚于你运行,它们仍然可以改变最终发送的内容。
ExtensionCommandContext
命令处理器接收 ExtensionCommandContext,它在 ExtensionContext 的基础上扩展了会话控制方法。这些方法只在命令中可用,因为从事件处理器调用它们可能导致死锁。
ctx.getSystemPromptOptions()
返回 pi 当前用来构建系统提示词的基础输入。
它与 before_agent_start 的 event.systemPromptOptions 形状和可变性相同:自定义提示词、活动工具、工具摘要、提示词指南、追加的系统提示词文本、cwd、已加载的上下文文件和已加载的技能。它可能包含完整的上下文文件内容,因此请将其视为敏感的扩展本地数据,避免通过命令列表、日志或自动补全元数据将其暴露。
它报告的是当前的基础提示词输入。它不包含每轮 before_agent_start 的链式系统提示词修改、后续 context 事件的消息修改,也不包含 before_provider_request 的 payload 改写。
ctx.waitForIdle()
等待智能体完全安定,包括自动重试、自动上下文压缩重试和排队延续:
ctx.newSession(options?)
创建新会话:
选项:
parentSession:记录到新会话头中的父会话文件setup:在withSession运行之前修改新会话的SessionManagerwithSession:在全新的替换会话上下文中执行切换后的工作。不要使用捕获的旧pi/ 命令ctx;参见 会话替换的生命周期与易错点。
ctx.fork(entryId, options?)
从特定条目分叉,创建新的会话文件:
选项:
position:"before"(默认)在所选用户消息之前分叉,并把该提示词恢复到编辑器中position:"at"复制穿过所选条目的活动路径,但不恢复编辑器文本withSession:在全新的替换会话上下文中执行切换后的工作。不要使用捕获的旧pi/ 命令ctx;参见 会话替换的生命周期与易错点。
ctx.navigateTree(targetId, options?)
导航到会话树中的另一个节点。当智能体响应、手动或自动上下文压缩、或另一次树状导航处于活动状态时会拒绝,即使传入了 summarize: false 也是如此。这类冲突会让活动分支保持不变,并直接拒绝 promise,而不是返回 { cancelled: true }。请等待活动操作完成(例如在命令处理器中 await ctx.waitForIdle())后重试:
选项:
summarize:是否为被放弃的分支生成摘要customInstructions:给摘要器的自定义指令replaceInstructions:为 true 时,customInstructions会完全替换默认提示词,而不是追加label:附加到分支摘要条目(或不生成摘要时的目标条目)上的标签
ctx.switchSession(sessionPath, options?)
切换到另一个会话文件:
选项:
withSession:在全新的替换会话上下文中执行切换后的工作。不要使用捕获的旧pi/ 命令ctx;参见 会话替换的生命周期与易错点。
要发现可用会话,请使用静态方法 SessionManager.list() 或 SessionManager.listAll():
会话替换的生命周期与易错点
withSession 接收一个全新的 ReplacedSessionContext,它在 ExtensionCommandContext 的基础上扩展了绑定到替换会话的异步 sendMessage() 和 sendUserMessage() 辅助方法。
生命周期与易错点:
withSession只在旧会话发出session_shutdown、旧运行时被销毁、替换会话被重新绑定、且新扩展实例已收到session_start之后才会运行。- 回调仍在原始闭包中执行,而不是在新扩展实例内部。这意味着你的旧扩展实例可能早在
withSession开始之前就完成了关闭清理。 - 捕获的旧
pi/ 旧命令ctx中绑定会话的对象在替换之后已失效,使用时会抛出异常。绑定会话的工作只使用传给withSession的ctx。 - 之前提取出的原始对象仍由你负责。例如,如果在替换前捕获了
const sm = ctx.sessionManager,那么sm仍是旧的SessionManager对象。替换后不要复用它。 withSession中的代码应当假定你的session_shutdown处理器使其失效的任何状态都已消失。只捕获能干净度过关闭过程的纯数据,例如字符串、id 和序列化的配置。
安全的写法:
不安全的写法:
ctx.reload()
运行与 /reload 相同的重载流程。
重要行为:
await ctx.reload()会为当前扩展运行时发出session_shutdown- 然后重载资源,并发出带
reason: "reload"的session_start,以及带 reason"reload"的resources_discover - 当前正在运行的命令处理器仍继续在旧的调用帧中执行
await ctx.reload()之后的代码运行的仍是重载前的版本await ctx.reload()之后的代码不得假定旧的内存扩展状态仍然有效- 处理器返回后,后续的命令/事件/工具调用将使用新版本的扩展
为了行为可预测,请把重载视为该处理器的终点(await ctx.reload(); return;)。
工具使用 ExtensionContext 运行,因此不能直接调用 ctx.reload()。可以先用一个命令作为重载入口,再暴露一个把该命令作为后续用户消息排队的工具。
LLM 可调用以触发重载的示例工具:
ExtensionAPI 方法
pi.on(event, handler)
订阅事件。事件类型与返回值见事件。
pi.registerTool(definition)
注册一个可由 LLM 调用的自定义工具。完整说明见自定义工具。
pi.registerTool() 在扩展加载期间和启动之后都可以调用。你可以在 session_start、命令处理器或其他事件处理器里调用它。新工具会在同一会话中立即刷新,因此它们会出现在 pi.getAllTools() 中,并且无需 /reload 即可被 LLM 调用。
使用 pi.setActiveTools() 可以在运行时启用或禁用工具(包括动态添加的工具)。
使用 promptSnippet 可以为自定义工具在 Available tools 中加入一行简介;使用 promptGuidelines 可以在该工具处于激活状态时,向默认的 Guidelines 小节追加针对该工具的条目。
重要: promptGuidelines 条目会平铺地追加到 Guidelines 小节,不带工具名前缀。每条准则都必须指明它所针对的工具——避免写成 “Use this tool when…",因为 LLM 无法判断 “this” 指的是哪个工具。应写成 “Use my_tool when…"。
完整示例见 dynamic-tools.ts。
pi.sendMessage(message, options?)
向会话注入一条自定义消息。自定义消息会参与 LLM 上下文。对于不应发送给 LLM 的持久化 TUI 专属内容,请使用 pi.appendEntry() 配合 pi.registerEntryRenderer()。
选项:
deliverAs- 投递模式:"steer"(默认)- 在流式输出期间将消息排队,在当前助手回合执行完其工具调用之后、下一次 LLM 调用之前投递。"followUp"- 等待智能体完成。仅当智能体不再有工具调用时才投递。"nextTurn"- 排队到下一条用户提示。不会中断或触发任何内容。
triggerTurn: true- 若智能体处于空闲状态,则立即触发一次 LLM 响应。仅适用于"steer"和"followUp"模式("nextTurn"时忽略)。
pi.sendUserMessage(content, options?)
向智能体发送一条用户消息。与发送自定义消息的 sendMessage() 不同,它发送的是一条真实的用户消息,看起来就像用户亲手输入的一样。始终会触发一个回合。
选项:
deliverAs- 智能体正在流式输出时必填:"steer"- 将消息排队,在当前助手回合执行完其工具调用之后投递"followUp"- 等待智能体完成所有工具
expandPromptTemplates- 分发扩展命令,并展开技能命令和提示词模板。默认为false。
非流式输出时,消息会立即发送并触发一个新回合。流式输出时若未指定 deliverAs,会抛出错误。
完整示例见 send-user-message.ts。
pi.appendEntry(customType, data?)
持久化扩展数据。自定义条目不参与 LLM 上下文。在交互模式下,配合 pi.registerEntryRenderer() 它们还可以渲染在聊天记录里。
pi.setSessionName(name)
设置会话的显示名称(显示在会话选择器中,代替第一条消息)。
pi.getSessionName()
获取当前会话名称(如果已设置)。
pi.setLabel(entryId, label)
设置或清除条目上的标签。标签是用户自定义的标记,用于书签和导航(显示在 /tree 选择器中)。
标签会随会话持久保存,重启后依然存在。可以用它们标记对话树中的重要位置(回合、检查点)。
pi.registerCommand(name, options)
注册一个命令。
如果多个扩展注册了同名命令,pi 会全部保留,并按加载顺序分配数字调用后缀,例如 /review:1 和 /review:2。
可选:为 /command ... 添加参数自动补全:
pi.getCommands()
获取当前会话中可通过 prompt 调用的斜杠命令,包括扩展命令、提示词模板和技能命令。
列表顺序与 RPC get_commands 一致:先是扩展,然后是模板,最后是技能。
每个条目的形状如下:
请将 sourceInfo 作为权威的来源字段。不要从命令名称或临时的路径解析中推断归属。
内置的交互式命令(如 /model 和 /settings)不包含在内。它们只在交互模式下处理,通过 prompt 发送不会执行。
pi.registerMessageRenderer(customType, renderer)
为带有你的 customType 的自定义消息注册自定义 TUI 渲染器。自定义消息通过 pi.sendMessage() 创建,并参与 LLM 上下文。见自定义 UI。
pi.registerMarkdownTransformer(transformer)
为普通用户文本、助手文本和思考块中的 Markdown 注册一个转换器。转换器按扩展加载顺序运行,每个转换器都会接收上一个转换器返回的 Markdown。整条链结束后,pi 会用内置渲染器渲染转换后的内容。
转换器接收 Markdown 字符串和一个包含以下字段的上下文:
messageType—"user"、"assistant"或"assistant-thinking"isStreaming— 助手部分更新时为true;用户消息、已完成的助手消息和恢复的消息为falseavailableWidth— 转换后的 Markdown 内容可用的精确终端列数
返回转换后的 Markdown:
如果某个转换器抛出异常,pi 会保留到目前为止生成的 Markdown,并继续执行下一个转换器。这个钩子只影响显示:会话和模型上下文中的原始消息保持不变。它会在新用户消息、助手流式更新、恢复的会话消息以及终端宽度变化时运行,因此转换器应保持同步且开销低。
pi.registerEntryRenderer(customType, renderer)
为带有你的 customType 的自定义条目注册自定义 TUI 渲染器。自定义条目通过 pi.appendEntry() 创建,不参与 LLM 上下文。
pi.registerShortcut(shortcut, options)
注册一个键盘快捷键。快捷键格式与内置快捷键见 keybindings.md。
pi.registerFlag(name, options)
注册一个 CLI 标志。
pi.exec(command, args, options?)
执行一条 shell 命令。
pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
管理激活的工具。内置工具和动态注册的工具都适用。pi.getActiveTools() 以 string[] 形式返回激活的工具名;pi.getAllTools() 返回所有已配置工具的元数据。
pi.getAllTools() 返回 name、description、parameters、promptGuidelines 和 sourceInfo。
典型的 sourceInfo.source 值:
builtin表示内置工具sdk表示通过createAgentSession({ customTools })传入的工具- 扩展注册的工具则使用该扩展的来源元数据
pi.setModel(model)
设置当前会话的模型。该更改会记录在会话历史中,恢复该会话时会还原,但它不会改变新会话所使用的已配置的 defaultProvider 或 defaultModel。如果未为该模型的提供商配置身份验证,则返回 false。配置自定义模型见 models.md。
pi.getThinkingLevel() / pi.setThinkingLevel(level)
获取当前思考级别。级别会被收敛到模型能力范围内(非推理模型始终为 “off”)。变更会发出 thinking_level_select 事件。
pi.setThinkingLevel() 更改当前会话的思考级别。该更改会记录在会话历史中,恢复该会话时会还原,但它不会改变新会话所使用的已配置默认值。
pi.events
用于扩展之间通信的共享事件总线:
pi.registerProvider(name, config)
动态注册或覆盖一个模型提供商。适用于代理、自定义端点或团队统一的模型配置。
在扩展工厂函数中进行的调用会排队,待运行器(runner)初始化后一次性应用。之后再进行的调用——例如在用户设置流程之后的命令处理器中——会立即生效,无需 /reload。
动态提供商可以实现 refreshModels。pi 会在模型刷新期间调用它,通过该提供商同步发布返回的列表,并传入规范的凭证/已存储目录/网络/信号上下文。是否通过经过世代检查(generation-checked)的 context.publish({ persist: entry }) 持久化目录元数据,由扩展自行决定;llama.cpp 这类实时服务器可以直接返回模型而不做持久化。
context.signal 始终是一个具体的 signal,提供商回调必须把它传递给阻塞式 I/O。公开的 ModelRuntime.refresh() 和 ModelRegistry.refresh() 调用接受可选的 signal,省略时不设时限;扩展和应用程序自行选择自己的截止时间。即使提供商忽略 signal,取消也会让调用方停止等待,但要停止底层工作仍需要它的配合。
需要原生提供商身份验证、过滤、刷新或流式行为的扩展,可以从 @earendil-works/pi-ai 注册一个完整的 Provider。该提供商会成为组合基座,models.json 的覆盖仍会在其上生效。
对象形式接受完整的 pi-ai Provider,包括原生的 auth、getModels、refreshModels、filterModels、stream 和 streamSimple 行为。
旧版配置选项:
name- 提供商在/login等 UI 中的显示名称。baseUrl- API 端点 URL。定义模型时必填。apiKey- API 密钥字面量、环境变量插值($ENV_VAR或${ENV_VAR}),或以!command开头的命令调用。定义模型时必填(除非提供了oauth)。$$转义$,$!转义字面量!而不会触发命令执行。api- API 类型:"anthropic-messages"、"openai-completions"、"openai-responses"等。headers- 请求中附带的自定义请求头。authHeader- 若为 true,自动添加Authorization: Bearer请求头。models- 模型定义数组。若提供,会替换该提供商的全部现有模型。模型定义可以设置baseUrl,为该模型覆盖提供商端点。refreshModels- 异步动态发现回调。其返回的模型会替换扩展提供的模型。context.stored包含持久化的提供商快照;仅当更新后的目录数据应当持久化时,才使用经过世代检查的context.publish({ persist: entry })。使用persist: null可删除该快照。oauth- 用于支持/login的 OAuth 提供商配置。提供后,该提供商会出现在登录菜单中。streamSimple- 面向非标准 API 的自定义流式实现。
高级主题(自定义流式 API、OAuth 细节、模型定义参考)见 custom-provider.md。
pi.unregisterProvider(name)
移除先前注册的提供商及其模型。被该提供商覆盖的内置模型会恢复原状。如果该提供商未被注册,则不会有任何效果。
与 registerProvider 一样,在初始加载阶段之后调用会立即生效,因此不需要 /reload。
状态管理
有状态的扩展应把状态存储在工具结果的 details 中,以便正确支持分支:
自定义工具
注册 LLM 可通过 pi.registerTool() 调用的工具。这些工具会出现在系统提示词中,并且可以自定义渲染。
使用 promptSnippet 可以在默认系统提示词的 Available tools 小节中生成一条简短的单行条目。如果省略,自定义工具不会出现在该小节中。
使用 promptGuidelines 向默认系统提示词的 Guidelines 小节添加针对特定工具的条目。这些条目只在工具处于激活状态时(例如在 pi.setActiveTools([...]) 之后)才会被包含。
重要: promptGuidelines 条目会平铺地追加到 Guidelines 小节,不带工具名前缀,也不分组。每条准则都必须指明它所针对的工具——避免写成 “Use this tool when…",因为 LLM 无法判断 “this” 指的是哪个工具。应写成 “Use my_tool when…"。
注意:有些模型不太聪明,会在工具的路径参数中带上 @ 前缀。内置工具在解析路径之前会剥掉开头的 @。如果你的自定义工具接受路径,也要对开头的 @ 做同样的规范化。
如果你的自定义工具会修改文件,请使用 withFileMutationQueue(),让它与内置的 edit 和 write 参与同一个按文件划分的队列。这一点很重要,因为默认情况下工具调用是并行运行的。没有这个队列,两个工具可能读到同一份旧文件内容,计算出不同的更新,而后落盘的那个写入会覆盖另一个。
失败场景举例:你的自定义工具编辑 foo.ts,而内置 edit 在同一助手回合中也修改了 foo.ts。如果你的工具不参与队列,两者都可能读到原始的 foo.ts,分别应用各自的修改,其中一处修改就会丢失。
请把真实的目标文件路径传给 withFileMutationQueue(),而不是原始的用户参数。先把它解析为绝对路径(相对于 ctx.cwd 或你工具的工作目录)。对于已存在的文件,该辅助函数会通过 realpath() 做规范化,因此指向同一文件的符号链接别名会共享一个队列。对于新文件,它会退回到解析后的绝对路径,因为此时还没有可 realpath() 的东西。
把整个修改窗口都排队到该目标路径上,这包括读取-修改-写入的完整逻辑,而不只是最后的写入。
工具定义
用量统计: 如果工具进行了嵌套 LLM 调用,请把它们合并后的 Usage 作为 usage 返回。pi 会将其持久化到工具结果上,并计入页脚、/session 和 RPC 会话统计。tool_result 处理器可以检查或替换该值。
发出错误信号: 要把一次工具执行标记为失败(在结果上设置 isError: true 并报告给 LLM),请在 execute 中抛出错误。无论返回对象里包含什么属性,返回值都不会设置错误标志。
提前终止: 从 execute() 返回 terminate: true,可以提示在当前工具批次之后跳过自动的后续 LLM 调用。只有当该批次中每个已完成的工具结果都是终止性的,这才会生效。最小示例见 examples/extensions/structured-output.ts,其中智能体以一次最终的结构化输出工具调用收尾。
重要: 字符串枚举请使用 @earendil-works/pi-ai 的 StringEnum。Type.Union/Type.Literal 在 Google 的 API 上无法工作。
参数准备: prepareArguments(args) 是可选的。如果定义了,它会在 schema 校验之前、execute() 之前运行。当 pi 恢复一个旧会话、其中存储的工具调用参数与当前 schema 不再匹配时,可以用它来模拟旧的接受输入形状。返回你希望按 parameters 校验的对象。保持公开 schema 严格,不要仅为让旧会话能恢复而向 parameters 添加已废弃的兼容字段。
例如:一个旧会话可能包含带顶层 oldText 和 newText 的 edit 工具调用,而当前 schema 只接受 edits: [{ oldText, newText }]。
覆盖内置工具
扩展可以通过注册同名工具来覆盖内置工具(read、bash、powershell、edit、write、grep、find、ls)。发生这种情况时,交互模式会显示警告。
或者,使用 --no-builtin-tools 在不加载任何内置工具的情况下启动,同时保持扩展工具可用:
完整示例见 examples/extensions/tool-override.ts,它为 read 加上了日志和访问控制。
渲染: 内置渲染器的继承按槽位解析。执行覆盖与渲染覆盖是相互独立的。如果你的覆盖省略了 renderCall,就使用内置的 renderCall;如果省略了 renderResult,就使用内置的 renderResult;如果两者都省略,会自动使用内置渲染器(语法高亮、diff 等)。这样你就可以在不重新实现 UI 的情况下,为日志或访问控制包装内置工具。
提示词元数据: promptSnippet 和 promptGuidelines 不会从内置工具继承。如果你的覆盖需要保留这些提示词指令,请在覆盖中显式定义。
你的实现必须与精确的结果形状匹配,包括 details 类型。UI 和会话逻辑依赖这些形状进行渲染和状态跟踪。
内置工具实现:
- read.ts -
ReadToolDetails - bash.ts -
BashToolDetails - powershell.ts -
PowerShellToolDetails - edit.ts
- write.ts
- grep.ts -
GrepToolDetails - find.ts -
FindToolDetails - ls.ts -
LsToolDetails
远程执行
内置工具支持以可插拔的操作(operations)委托给远程系统(SSH、容器等):
操作接口: ReadOperations、WriteOperations、EditOperations、BashOperations、PowerShellOperations、LsOperations、GrepOperations、FindOperations
对 user_bash 而言,扩展可以通过 createLocalBashOperations() 复用 pi 的本地 shell 后端,而无需重新实现本地进程生成、shell 解析和进程树终止。
bash 和 powershell 工具还支持一个 spawn 钩子,用于在执行前调整命令、cwd 或 env:
createBashTool() 和 createPowerShellTool() 会通过 PI_SESSION_ID、PI_SESSION_FILE、PI_PROVIDER、PI_MODEL 和 PI_REASONING_LEVEL 把当前会话暴露给命令。注入发生在 spawnHook 之前,因此钩子会在 env 中收到这些值,并像上面那样在展开现有环境时保留它们。设置 exposeSessionEnvironment: false 可以禁用:
变量语义见 Shell 工具会话环境。带 --ssh 标志的完整 SSH 示例见 examples/extensions/ssh.ts。
输出截断
工具必须截断自己的输出,避免撑爆 LLM 上下文。过大的输出可能导致:
- 上下文溢出错误(提示词过长)
- 上下文压缩失败
- 模型性能下降
内置限制为 50KB(约 10k token)和 2000 行,以先达到者为准。请使用导出的截断工具函数:
要点:
- 对开头重要的内容(搜索结果、文件读取)使用
truncateHead - 对结尾重要的内容(日志、命令输出)使用
truncateTail - 输出被截断时,始终告知 LLM,以及在哪里可以找到完整版本
- 在工具描述中记录截断限制
完整示例见 examples/extensions/truncated-tool.ts,它以正确的截断方式包装了 rg(ripgrep)。
多个工具
一个扩展可以注册多个共享状态的工具:
自定义渲染
工具可以提供 renderCall 和 renderResult 来实现自定义 TUI 显示。完整组件 API 见 tui.md,工具行的组合方式见 tool-execution.ts。
默认情况下,工具输出被包裹在一个负责内边距和背景的 Box 中。已定义的 renderCall 或 renderResult 必须返回一个 Component。如果某个槽位没有定义渲染器,tool-execution.ts 会为该槽位使用回退渲染。
当工具需要渲染自己的外壳而不是使用默认 Box 时,设置 renderShell: "self"。这适用于需要完全控制边框或背景行为的工具,例如在工具完成后必须保持视觉稳定的大型预览。
renderCall 和 renderResult 各接收一个 context 对象,包含:
args- 当前工具调用的参数state- 在renderCall和renderResult之间共享的行局部状态lastComponent- 该槽位先前返回的组件(如果有)invalidate()- 请求重新渲染此工具行toolCallId、cwd、executionStarted、argsComplete、isPartial、expanded、showImages、isError
需要跨槽位共享的状态请使用 context.state。当你想在多次渲染之间复用并原地修改同一组件时,把槽位局部缓存放在返回的组件实例上。
renderCall
渲染工具调用或头部:
renderResult
渲染工具结果或输出:
如果某个槽位刻意没有可见内容,返回一个空的 Component,例如空的 Container。
快捷键提示
使用 keyHint() 显示遵循当前快捷键配置的快捷键提示:
可用函数:
keyHint(keybinding, description)- 格式化一个已配置的快捷键 id,例如"app.tools.expand"或"tui.select.confirm"keyText(keybinding)- 返回快捷键 id 对应的原始按键文本rawKeyHint(key, description)- 格式化一个原始按键字符串
使用带命名空间的快捷键 id:
- 编码智能体的 id 使用
app.*命名空间,例如app.tools.expand、app.editor.external、app.session.rename - 共享 TUI 的 id 使用
tui.*命名空间,例如tui.select.confirm、tui.select.cancel、tui.input.tab
快捷键 id 和默认值的完整列表见 keybindings.md。keybindings.json 使用的就是这些带命名空间的 id。
自定义编辑器和 ctx.ui.custom() 组件会收到 keybindings: KeybindingsManager 作为注入参数。它们应直接使用该注入的管理器,而不是调用 getKeybindings() 或 setKeybindings()。
最佳实践
- 使用带
(0, 0)内边距的Text,默认 Box 负责内边距。 - 多行内容使用
\n。 - 处理
isPartial以展示流式进度。 - 支持
expanded以按需展开详情。 - 保持默认视图紧凑。
- 在
renderResult中读取context.args,而不是把参数复制进context.state。 context.state只用于必须跨调用槽位和结果槽位共享的数据。- 当同一组件实例可以原地更新时,复用
context.lastComponent。 - 仅当默认的带框外壳碍事时才使用
renderShell: "self"。在自外壳模式下,工具要自己负责边框、内边距和背景。
回退
如果某个槽位的渲染器未定义或抛出异常:
renderCall:显示工具名renderResult:显示content中的原始文本
动态加载工具
扩展可以注册很多工具,但只让一小部分初始激活。之后工具可以在执行期间通过 pi.setActiveTools() 添加更多工具。pi 会检测纯增量变更,把新可用的工具名记录到该工具结果上,并在下一次模型请求之前应用更新后的激活集合。
这适用于所有模型。原生支持延迟加载的模型会保持稳定的提示词前缀,并在工具结果的位置加载新定义。其他模型使用下文描述的回退机制。
生命周期如下:
- 用
pi.registerTool()注册每一个工具,使其出现在pi.getAllTools()中。 - 保持加载器工具(例如
search_tools)激活,让可搜索工具保持未激活。 - 在加载器执行期间,调用
pi.setActiveTools([...currentTools, ...matchingTools])。变更必须是增量的:不要在同一次调用中移除当前激活的工具。 - pi 在加载器的工具结果上记录添加了哪些工具。
- 在下一次模型响应之前,pi 会公开新增的定义——在支持时使用原生延迟加载,否则使用正常的激活工具列表。
你不需要返回提供商特定的工具引用,也不需要把加载器标记为特殊的搜索工具。激活工具集合的变更本身就是信号。传给 pi.setActiveTools() 的名称必须已经注册;未知名称会被忽略。
原生支持延迟加载的模型
- Anthropic
- 模型: Sonnet、Opus、Fable 4.5 或更新版本(不含 Haiku)
- 原生表示: 延迟定义使用
defer_loading;加载点使用tool_reference内容。
- Fireworks Messages API
- 原生表示: 延迟定义使用
defer_loading;加载点使用tool_reference内容。 - 加载器名称: 要获得前缀延迟效果,加载器需命名为
ToolSearch或tool_search。其他加载器名称也能工作,但 Fireworks 会把已加载的 schema 包含在初始工具前缀中,从而失去缓存优势。 - 这不改变 API 路由:Fireworks GLM 模型和 Kimi K3 使用 Chat Completions,而不是 Messages。
- 原生表示: 延迟定义使用
- OpenAI
- 模型:
gpt-5.4及更新的家族 - 原生表示: pi 在加载点添加已完成的客户端
tool_search_call和tool_search_output条目。
- 模型:
对于经过验证的自定义模型或代理,可以为 anthropic-messages 启用 compat.supportsToolReferences: true,或为 openai-responses 和 openai-codex-responses 启用 compat.supportsToolSearch: true,以启用原生处理。除非端点和模型支持相应的原生协议,否则请保持禁用。
回退行为
对于所有其他模型和提供商,动态激活仍然有效:pi 会在下一次请求中正常发送完整的当前激活工具列表。模型可以调用新激活的工具,但添加它们的定义可能使提供商缓存的提示词前缀失效。
当激活集合不是纯增量时(例如用一组工具替换另一组),pi 也会使用这种安全回退。因此工具移除也能工作,但不使用延迟加载。
为了获得最佳缓存行为,请让加载器工具在整个会话期间保持激活,并以添加工具为主,而不是替换激活集合。还要注意,激活一个带 promptSnippet 或 promptGuidelines 的工具会重建系统提示词;即使提供商支持延迟 schema,这种系统提示词变更也可能使前缀失效。延迟加载的工具通常应依赖其工具 description,并省略仅激活时生效的提示词元数据。
搜索工具示例
下面的扩展注册两个可搜索工具,把它们从初始激活集合中移除,只保留 search_tools 作为它们的加载器。示例使用简单的关键词匹配,但搜索实现也可以改用 BM25、嵌入、远程目录或特定于项目的路由。
当 search_tools 添加了匹配项时,模型会在紧随其后的那次请求中收到相应定义。在支持原生能力的模型上,定义锚定在搜索结果之后,初始工具 schema 前缀保持不变。在其他模型上,它出现在同一次后续请求的正常工具列表中。
自定义 UI
扩展可以通过 ctx.ui 的方法与用户交互,并自定义消息/工具的渲染方式。
自定义组件请参见 tui.md,其中提供了可直接复制的模式:
- 选择对话框(SelectList)
- 可取消的异步操作(BorderedLoader)
- 设置开关(SettingsList)
- 状态指示器(setStatus)
- 流式输出期间的工作消息、可见性与指示器(
setWorkingMessage、setWorkingVisible、setWorkingIndicator) - 编辑器上方/下方的小部件(setWidget)
- 在内置斜杠/路径补全之上叠加的自动补全提供器(addAutocompleteProvider)
- 自定义页脚(setFooter)
对话框
带倒计时的定时对话框
对话框支持 timeout 选项,会显示实时倒计时并在归零时自动关闭:
超时时的返回值:
select()返回undefinedconfirm()返回falseinput()返回undefined
使用 AbortSignal 手动关闭
需要更多控制时(例如区分超时与用户取消),可以使用 AbortSignal:
完整示例见 examples/extensions/timed-confirm.ts。
小部件、状态与页脚
自定义的工作指示器帧会按原样渲染。如果想要颜色,请自行添加到帧字符串中,例如使用 ctx.ui.theme.fg(...)。
自动补全提供器
使用 ctx.ui.addAutocompleteProvider() 在内置斜杠命令和路径提供器之上叠加自定义自动补全逻辑。为 $ 这类自定义触发字符设置 triggerCharacters。
典型模式:
- 检查光标之前的文本
- 当匹配到你扩展特有的语法时,返回你自己的建议
- 否则委托给
current.getSuggestions(...) - 除非需要自定义插入行为,否则委托
applyCompletion(...)
完整示例见 github-issue-autocomplete.ts,它用 gh issue list 预加载最新的打开 GitHub issue,并在本地过滤以实现快速的 #... 补全。它需要 GitHub CLI(gh)和一个 GitHub 仓库检出。
自定义组件
对于复杂 UI,请使用 ctx.ui.custom()。它会临时用你的组件替换编辑器,直到调用 done():
回调会接收:
tui- TUI 实例(用于屏幕尺寸、焦点管理)theme- 当前主题,用于样式keybindings- 应用快捷键管理器(用于检查快捷键)done(value)- 调用以关闭组件并返回值
完整组件 API 见 tui.md。
覆盖层模式(实验性)
传入 { overlay: true } 可以把组件渲染为悬浮在现有内容之上的浮动模态层,而不清屏:
高级定位(锚点、边距、百分比、响应式可见性)请传 overlayOptions。使用 onHandle 以编程方式控制焦点或可见性:
已聚焦的可见覆盖层可以在临时的非覆盖层自定义 UI 关闭后重新接管输入。如果你有意让另一个组件在覆盖层保持可见的同时继续持有输入,请调用 handle.unfocus({ target })。传入 { target: null } 会在不聚焦其他组件的情况下释放覆盖层。
完整的 OverlayOptions 和 OverlayHandle API 见 tui.md,示例见 overlay-qa-tests.ts。
自定义编辑器
用自定义实现替换主输入编辑器(vim 模式、emacs 模式等):
要点:
- 继承
CustomEditor(而不是基础Editor),以获得应用快捷键(escape 中止、ctrl+d、模型切换) - 对你不处理的按键调用
super.handleInput(data) - 自定义编辑器默认保留独立的工作行。若想改用内置的编辑器边框加载动画,请将
{ embedWorkingStatus: true }作为CustomEditor构造函数的第四个参数传入 - 工厂函数从应用接收
tui、theme和keybindings - 在
setEditorComponent()之前使用ctx.ui.getEditorComponent(),可以包装先前配置的自定义编辑器 - 传入
undefined恢复默认:ctx.ui.setEditorComponent(undefined)
要与其他已经替换了编辑器的扩展组合,请先捕获之前的工厂,再设置你自己的:
带模式指示器的完整示例见 tui.md 的模式 7。
消息与条目渲染
为带有你的 customType 的消息注册自定义渲染器。对于应参与 LLM 上下文的内容,请使用消息渲染器:
消息通过 pi.sendMessage() 发送:
对于不应发送给 LLM 的仅 TUI 内容,请改为渲染自定义条目:
主题颜色
所有渲染函数都会接收一个 theme 对象。创建自定义主题与完整调色板见 themes.md。
在自定义工具渲染器中使用语法高亮:
错误处理
- 扩展错误会被记录日志,智能体继续运行
tool_call错误会阻止该工具(故障安全)- 工具
execute的错误必须通过抛出来发出信号;抛出的错误会被捕获,并以isError: true报告给 LLM,执行继续
模式行为
| 模式 | ctx.mode | ctx.hasUI | 说明 |
|---|---|---|---|
| 交互 | "tui" | true | 完整 TUI 与终端渲染 |
RPC(--mode rpc) | "rpc" | true | 对话框与通知通过 JSON 协议传递;custom() 返回 undefined。见 rpc.md |
JSON(--mode json) | "json" | false | 事件流输出到 stdout;UI 方法为空操作 |
打印(-p) | "print" | false | 扩展会运行,但无法提示用户 |
在使用 TUI 特有功能(custom()、组件工厂、终端输入)之前,先检查 ctx.mode === "tui"。在同时适用于 TUI 和 RPC 模式的对话框和通知方法之前,先检查 ctx.hasUI。
示例参考
所有示例位于 examples/extensions/。
| 示例 | 描述 | 关键 API |
|---|---|---|
| 工具 | ||
hello.ts | 最小化的工具注册 | registerTool |
question.ts | 带用户交互的工具 | registerTool, ui.select |
questionnaire.ts | 多步骤向导工具 | registerTool, ui.custom |
todo.ts | 带持久化的有状态工具 | registerTool, appendEntry, renderResult, 会话事件 |
dynamic-tools.ts | 启动后及命令执行期间注册工具 | registerTool, session_start, registerCommand |
structured-output.ts | 带 terminate: true 的最终结构化输出工具 | registerTool, 终止性工具结果 |
truncated-tool.ts | 输出截断示例 | registerTool, truncateHead |
tool-override.ts | 覆盖内置 read 工具 | registerTool(与内置工具同名) |
| 命令 | ||
pirate.ts | 按回合修改系统提示词 | registerCommand, before_agent_start |
summarize.ts | 会话摘要命令 | registerCommand, ui.custom |
handoff.ts | 跨提供商模型交接 | registerCommand, ui.editor, ui.custom |
qna.ts | 带自定义 UI 的问答 | registerCommand, ui.custom, setEditorText |
send-user-message.ts | 注入用户消息 | registerCommand, sendUserMessage |
reload-runtime.ts | 重载命令与 LLM 工具交接 | registerCommand, ctx.reload(), sendUserMessage |
shutdown-command.ts | 优雅关机命令 | registerCommand, shutdown() |
| 事件与门控 | ||
permission-gate.ts | 阻止危险命令 | on("tool_call"), ui.confirm |
project-trust.ts | 从用户级/全局或 CLI 扩展决定或推迟项目信任 | on("project_trust"), 信任 UI, 必需的信任结果 |
protected-paths.ts | 阻止向特定路径写入 | on("tool_call") |
confirm-destructive.ts | 确认破坏性的会话变更 | on("session_before_switch"), on("session_before_fork") |
dirty-repo-guard.ts | 在 git 仓库有未提交更改时发出警告 | on("session_before_*"), exec |
input-transform.ts | 转换用户输入 | on("input") |
input-transform-streaming.ts | 感知流式输出的输入转换 | on("input"), streamingBehavior |
model-status.ts | 响应模型变更 | on("model_select"), setStatus |
provider-payload.ts | 检查载荷与提供商响应头 | on("before_provider_request"), on("after_provider_response") |
system-prompt-header.ts | 显示系统提示词信息 | on("agent_start"), getSystemPrompt |
claude-rules.ts | 从文件加载规则 | on("session_start"), on("before_agent_start") |
prompt-customizer.ts | 使用 systemPromptOptions 添加上下文感知的工具指引 | on("before_agent_start"), BuildSystemPromptOptions |
file-trigger.ts | 文件监视器触发消息 | sendMessage |
| 上下文压缩与会话 | ||
custom-compaction.ts | 自定义上下文压缩摘要 | on("session_before_compact") |
trigger-compact.ts | 手动触发上下文压缩 | compact() |
git-checkpoint.ts | 按回合执行 git stash | on("turn_start"), on("session_before_fork"), exec |
git-merge-and-resolve.ts | 拉取、合并并解决冲突 | on("agent_end"), exec, sendUserMessage |
auto-commit-on-exit.ts | 关闭时提交 | on("session_shutdown"), exec |
| UI 组件 | ||
status-line.ts | 页脚状态指示器 | setStatus, 会话事件 |
working-indicator.ts | 自定义流式工作指示器 | setWorkingIndicator, registerCommand |
github-issue-autocomplete.ts | 通过 gh issue list 预加载最近打开的 issue,在内置自动补全之上添加 #1234 issue 补全 | addAutocompleteProvider, on("session_start"), exec |
custom-footer.ts | 完全替换页脚 | registerCommand, setFooter |
custom-header.ts | 替换启动头部 | on("session_start"), setHeader |
modal-editor.ts | Vim 风格模态编辑器 | setEditorComponent, CustomEditor |
rainbow-editor.ts | 自定义编辑器样式 | setEditorComponent |
widget-placement.ts | 编辑器上方/下方的小部件 | setWidget |
overlay-test.ts | 覆盖层组件 | ui.custom 及 overlay 选项 |
overlay-qa-tests.ts | 全面的覆盖层测试 | ui.custom, 所有 overlay 选项 |
notify.ts | 简单通知 | ui.notify |
timed-confirm.ts | 带超时的对话框 | ui.confirm 及 timeout/signal |
mac-system-theme.ts | 自动切换主题 | setTheme, exec |
| 复杂扩展 | ||
plan-mode/ | 完整的计划模式实现 | 所有事件类型, registerCommand, registerShortcut, registerFlag, setStatus, setWidget, sendMessage, setActiveTools |
preset.ts | 可保存的预设(模型、工具、思考) | registerCommand, registerShortcut, registerFlag, setModel, setActiveTools, setThinkingLevel, appendEntry |
tools.ts | 工具开关 UI | registerCommand, setActiveTools, SettingsList, 会话事件 |
| 远程与沙箱 | ||
ssh.ts | SSH 远程执行 | registerFlag, on("user_bash"), on("before_agent_start"), 工具操作 |
interactive-shell.ts | 持久 shell 会话 | on("user_bash") |
sandbox/ | 沙箱化的工具执行 | 工具操作 |
gondolin/ | 将内置工具和 ! 命令路由进 Gondolin 微型 VM | 工具操作, 内置工具覆盖, on("user_bash") |
subagent/ | 生成子智能体 | registerTool, exec |
| 游戏 | ||
snake.ts | 贪吃蛇游戏 | registerCommand, ui.custom, 键盘处理 |
space-invaders.ts | Space Invaders 游戏 | registerCommand, ui.custom |
doom-overlay/ | 覆盖层中的 Doom | ui.custom 及 overlay 选项 |
| 提供商 | ||
custom-provider-anthropic/ | 自定义 Anthropic 代理 | registerProvider |
custom-provider-gitlab-duo/ | GitLab Duo 集成 | 带 OAuth 的 registerProvider |
| 消息与通信 | ||
message-renderer.ts | 自定义消息渲染 | registerMessageRenderer, sendMessage |
entry-renderer.ts | 仅 TUI 的自定义条目渲染 | registerEntryRenderer, appendEntry |
event-bus.ts | 扩展间事件 | pi.events |
| 会话元数据 | ||
session-name.ts | 为选择器命名会话 | setSessionName, getSessionName |
bookmark.ts | 为 /tree 标记条目书签 | setLabel |
| 其他 | ||
inline-bash.ts | 在工具调用中内联 bash | on("tool_call") |
bash-spawn-hook.ts | 执行前调整 bash 命令、cwd 和 env | createBashTool, spawnHook |
with-deps/ | 带 npm 依赖的扩展 | 含 package.json 的包结构 |
12 技能
pi 可以创建技能。可以让它为你的用例构建一个。
技能是智能体按需加载的自包含能力包。一个技能为特定任务提供专门的工作流、安装说明、辅助脚本和参考文档。
Pi 实现了 Agent Skills 标准,对大多数违规情况只给出警告但保持宽容。Pi 允许技能名称与其父目录不同,尽管标准不允许;对于在多个智能体框架之间共享的技能目录来说,那条规则并不理想。
目录
位置
安全提示: 技能可以指示模型执行任何操作,还可能包含会被模型调用的可执行代码。使用前请审查技能内容。
Pi 从以下位置加载技能:
- 全局:
~/.pi/agent/skills/~/.agents/skills/
- 项目(仅在项目受信任之后):
.pi/skills/cwd及祖先目录中的.agents/skills/(向上直到 git 仓库根目录;不在仓库中时则到文件系统根目录)
- 包:
skills/目录或package.json中的pi.skills条目 - 设置:
skills数组,元素为文件或目录 - CLI:
--skill <path>(可重复,即使使用--no-skills也会叠加加载)
发现规则:
- 在
~/.pi/agent/skills/和.pi/skills/中,根目录下的.md文件只要带有合法的技能 frontmatter 且description非空,就会被识别为独立技能 - 在所有技能位置中,包含
SKILL.md的目录会被递归发现 - 在
~/.agents/skills/和项目级.agents/skills/中,根目录下的.md文件会被忽略,但分组文件夹中嵌套的.md文件在声明了技能 frontmatter 后会被发现 - 除
SKILL.md外,其他看起来不像技能的根级 Markdown 文件会被静默忽略
使用 --no-skills 可以禁用发现(显式指定的 --skill 路径仍会加载)。
使用来自其他智能体框架的技能
要使用来自 Claude Code 或 OpenAI Codex 的技能,把它们的目录加入设置:
对于项目级的 Claude Code 技能,在 .pi/settings.json 中添加:
技能的工作原理
- 启动时,pi 扫描技能位置并提取名称和描述
- 系统提示词按照规范以 XML 格式包含可用技能
- 当任务匹配时,智能体使用
read(在read不可用时使用bash)加载完整的 SKILL.md(模型并不总会这样做;可以用提示词或/skill:name强制加载) - 智能体遵循其中的指令,使用相对路径引用脚本和资源
这就是渐进式披露:上下文中始终只有描述,完整指令按需加载。
技能命令
技能会注册为 /skill:name 命令:
命令之后的参数会以 User: <args> 的形式附加到技能内容后面。
在交互模式下通过 /settings,或在 settings.json 中切换技能命令:
技能结构
一个技能就是一个包含 SKILL.md 文件的目录,其余内容完全自由。
SKILL.md 格式
使用技能目录中的相对路径:
Frontmatter
依据 Agent Skills 规范:
| 字段 | 必填 | 说明 |
|---|---|---|
name | 是 | 最多 64 个字符。仅限小写 a-z、0-9 和连字符。与标准不同,Pi 不要求它与父目录同名,因为该标准要求对共享技能目录而言并不理想。 |
description | 是 | 最多 1024 个字符。说明技能做什么以及何时使用。 |
license | 否 | 许可证名称,或指向包内许可证文件的引用。 |
compatibility | 否 | 最多 500 个字符。环境要求。 |
metadata | 否 | 任意键值映射。 |
allowed-tools | 否 | 以空格分隔的预批准工具列表(实验性)。 |
disable-model-invocation | 否 | 为 true 时,技能不会出现在系统提示词中。用户必须使用 /skill:name。 |
名称规则
- 1-64 个字符
- 仅限小写字母、数字和连字符
- 不能以连字符开头或结尾
- 不能有连续的连字符
Pi 不要求名称与父目录一致。Agent Skills 标准有此要求,但对被多个工具共享的技能目录来说,这条要求并不理想。
有效:pdf-processing、data-analysis、code-review
无效:PDF-Processing、-pdf、pdf--processing
描述的最佳实践
描述决定了智能体何时加载技能,请写得具体。
好的示例:
差的示例:
校验
Pi 会按照 Agent Skills 标准校验技能。大多数问题只产生警告,技能仍会加载:
- 名称超过 64 个字符或包含非法字符
- 名称以连字符开头/结尾,或存在连续连字符
- 描述超过 1024 个字符
未知的 frontmatter 字段会被忽略。
声明为技能但缺少描述的不会被加载。格式错误的 SKILL.md 文件以及没有描述的 SKILL.md 文件会产生警告且不会被加载。其他不带合法技能 frontmatter 的 Markdown 文件会被忽略。
名称冲突(不同位置出现同名)会产生警告,并保留最先找到的技能。
示例
SKILL.md:
技能仓库
- Anthropic Skills——文档处理(docx、pdf、pptx、xlsx)、Web 开发
- Pi Skills——Web 搜索、浏览器自动化、Google API、语音转写
13 提示词模板
pi 可以创建提示词模板。让它为你的工作流构建一个吧。
提示词模板是可以展开为完整提示词的 Markdown 片段。在编辑器中输入 /name 即可调用模板,其中 name 是不含 .md 扩展名的文件名。
存放位置
Pi 从以下位置加载提示词模板:
- 全局:
~/.pi/agent/prompts/*.md - 项目:
.pi/prompts/*.md(仅在项目受信任后生效) - 包:
prompts/目录或package.json中的pi.prompts条目 - 设置:
prompts数组,包含文件或目录 - CLI:
--prompt-template <path>(可重复指定)
使用 --no-prompt-templates 可禁用模板自动发现。
格式
- 文件名即为命令名。
review.md对应/review。 description是可选的。如果缺失,则使用第一个非空行。argument-hint也是可选的。设置后,该提示会显示在自动补全下拉列表中描述的前面。
参数提示
在 frontmatter 中使用 argument-hint 可以在自动补全中显示预期的参数。必选参数使用 <angle brackets> 表示,可选参数使用 [square brackets] 表示:
在自动补全下拉列表中的渲染效果如下:
用法
在编辑器中输入 / 加模板名。自动补全会显示可用模板及其描述。
参数
模板支持位置参数、默认值和简单的切片操作:
$1、$2……位置参数$@或$ARGUMENTS表示所有参数拼接后的内容${1:-default}在第 1 个参数存在且非空时使用该参数,否则使用default${@:-default}或${ARGUMENTS:-default}在参数存在且非空时使用全部参数,否则使用default${@:N}表示从第 N 个位置开始的参数(从 1 计数)${@:N:L}表示从第 N 个开始的L个参数
示例:
默认值对可选参数很有用:
用法:/component Button "onClick handler" "disabled support"
加载规则
- 在
prompts/目录中发现模板的过程是非递归的。 - 如果要把模板放在子目录中,请通过
prompts设置或包清单显式添加。
14 主题
pi 可以创建主题。让它为你的环境构建一个吧。
主题是定义 TUI 颜色的 JSON 文件。
目录
加载位置
Pi 从以下位置加载主题:
- 内置:
dark、light - 全局:
~/.pi/agent/themes/*.json - 项目:
.pi/themes/*.json(仅在项目被信任后) - 包:
themes/目录或package.json中的pi.themes条目 - 设置:包含文件或目录的
themes数组 - CLI:
--theme <path>(可重复使用)
使用 --no-themes 禁用发现。
选择主题
通过 /settings 或在 settings.json 中选择主题:
首次运行时,pi 会检测你的终端背景,并默认使用 dark 或 light。
初始主题
以某个主题启动交互式运行,而不更改已保存的设置:
若要跟随终端外观,请使用 lightTheme/darkTheme 语法:
CLI 传入的值是本次运行的初始主题。之后在 /settings 中选择其他主题会立即生效,并照常保存。
创建自定义主题
- 创建主题文件:
- 定义包含所有必需颜色的主题(参见颜色令牌):
- 通过
/settings选择主题。
热重载: 当你编辑当前启用的自定义主题文件时,pi 会自动重新加载它,以获得即时的视觉反馈。
主题格式
name是必需的,必须唯一,且不能包含/。vars是可选的。在这里定义可复用的颜色,然后在colors中引用它们。colors必须定义全部 53 个必需令牌。thinkingMax和两个搜索高亮令牌是可选的,使用下面列出的回退值。
$schema 字段可以启用编辑器自动补全和验证。
颜色令牌
每个主题都必须定义全部 53 个必需的颜色令牌。可选令牌用于保持与现有主题的兼容:thinkingMax 回退到 thinkingXhigh,searchMatchBg 回退到 selectedBg,searchMatchText 回退到 text。其他搜索匹配项在 searchMatchBg 背景上使用带下划线的 searchMatchText;当前匹配项则反转该前景/背景对并使用粗体文本。
核心 UI(13 色)
| 令牌 | 用途 |
|---|---|
accent | 主强调色(徽标、选中项、光标) |
border | 普通边框 |
borderAccent | 高亮边框 |
borderMuted | 弱化边框(编辑器) |
success | 成功状态 |
error | 错误状态 |
warning | 警告状态 |
muted | 次要文本 |
dim | 三级文本 |
text | 默认文本(通常为 "") |
thinkingText | 思考块文本 |
scrollbarTrack | 全屏滚动条轨道前景色 |
scrollbarThumb | 全屏滚动条滑块前景色,普通和展开状态共用 |
背景与内容(11 个必需,2 个可选)
| 令牌 | 用途 |
|---|---|
selectedBg | 选中行背景 |
searchMatchBg | 对话记录搜索匹配背景及当前匹配文本;可选,回退到 selectedBg |
searchMatchText | 对话记录搜索匹配文本及当前匹配背景;可选,回退到 text |
userMessageBg | 用户消息背景 |
userMessageText | 用户消息文本 |
customMessageBg | 扩展消息背景 |
customMessageText | 扩展消息文本 |
customMessageLabel | 扩展消息标签 |
toolPendingBg | 工具框(等待中) |
toolSuccessBg | 工具框(成功) |
toolErrorBg | 工具框(错误) |
toolTitle | 工具标题 |
toolOutput | 工具输出文本 |
Markdown(10 色)
| 令牌 | 用途 |
|---|---|
mdHeading | 标题 |
mdLink | 链接文本 |
mdLinkUrl | 链接 URL |
mdCode | 行内代码 |
mdCodeBlock | 代码块内容 |
mdCodeBlockBorder | 代码块围栏 |
mdQuote | 引用块文本 |
mdQuoteBorder | 引用块边框 |
mdHr | 水平分割线 |
mdListBullet | 列表符号 |
工具 Diff(3 色)
| 令牌 | 用途 |
|---|---|
toolDiffAdded | 新增行 |
toolDiffRemoved | 删除行 |
toolDiffContext | 上下文行 |
语法高亮(9 色)
| 令牌 | 用途 |
|---|---|
syntaxComment | 注释 |
syntaxKeyword | 关键字 |
syntaxFunction | 函数名 |
syntaxVariable | 变量 |
syntaxString | 字符串 |
syntaxNumber | 数字 |
syntaxType | 类型 |
syntaxOperator | 运算符 |
syntaxPunctuation | 标点符号 |
思考级别边框(6 个必需,1 个可选)
指示思考级别的编辑器边框颜色(视觉层级从弱到强):
| 令牌 | 用途 |
|---|---|
thinkingOff | 关闭思考 |
thinkingMinimal | 极少思考 |
thinkingLow | 低思考 |
thinkingMedium | 中等思考 |
thinkingHigh | 高思考 |
thinkingXhigh | 超高思考 |
thinkingMax | 最高思考;可选,回退到 thinkingXhigh |
Bash 模式(1 色)
| 令牌 | 用途 |
|---|---|
bashMode | bash 模式下的编辑器边框(! 前缀) |
HTML 导出(可选)
export 部分控制 /export HTML 输出的颜色。如果省略,颜色将从 userMessageBg 派生。
颜色取值
支持四种格式:
| 格式 | 示例 | 说明 |
|---|---|---|
| 十六进制 | "#ff0000" | 6 位十六进制 RGB |
| 256 色 | 39 | xterm 256 色调色板索引(0-255) |
| 变量 | "primary" | 对 vars 条目的引用 |
| 默认 | "" | 终端默认颜色 |
256 色调色板
0-15:基本 ANSI 颜色(因终端而异)16-231:6×6×6 RGB 立方体(16 + 36×R + 6×G + B,其中 R、G、B 为 0-5)232-255:灰度渐变
终端兼容性
Pi 使用 24 位 RGB 颜色。大多数现代终端都支持(iTerm2、Kitty、WezTerm、Windows Terminal、VS Code)。对于只支持 256 色的旧终端,pi 会回退到最接近的近似值。
检查真彩色支持:
技巧
深色终端: 使用明亮、饱和度高、对比更强的颜色。
浅色终端: 使用更暗、更柔和、对比更低的颜色。
颜色和谐: 从一个基础调色板(Nord、Gruvbox、Tokyo Night)开始,在 vars 中定义它,并保持一致的引用。
测试: 用不同的消息类型、工具状态、Markdown 内容和长换行文本检查你的主题。
VS Code: 将 terminal.integrated.minimumContrastRatio 设为 1 以获得准确的颜色。
示例
参见内置主题:
15 Pi 包
pi 可以帮你创建 pi 包。可以让它把你的扩展、技能、提示词模板或主题打包。
Pi 包把扩展、技能、提示词模板和主题打包在一起,让你可以通过 npm 或 git 分享它们。一个包可以在 package.json 的 pi 键下声明资源,也可以使用约定目录。
目录
安装与管理
安全提示: Pi 包以完整的系统权限运行。扩展会执行任意代码,技能可以指示模型执行任何操作,包括运行可执行文件。安装第三方包之前,请先审查其源代码。
这些命令管理 pi 包,其中 pi update 还能更新 pi CLI 本身。对于实验性的安装器托管安装,pi update 会把确切的已检出版本安装到一个分阶段的、由锁文件支撑的发布中,并且只有在验证通过后才激活它;更新失败时当前发布保持不变。托管安装不支持 --force;要修复请重新运行安装器。要卸载 pi 本身,请参阅快速开始。
默认情况下,install 和 remove 写入用户设置(~/.pi/agent/settings.json)。使用 -l 可以改为写入项目设置(.pi/settings.json)。项目设置可以与团队共享;项目受信任之后,pi 会在启动时自动安装缺失的包。
要临时试用一个包而不正式安装,可以使用 --extension 或 -e。这只会在本次运行中安装到临时目录:
包来源
Pi 在设置和 pi install 中接受三种来源类型。
npm
- 带版本号的规格会被固定,包更新(
pi update --extensions、pi update --all)会跳过它们。 - 用户级安装位于
~/.pi/agent/npm/之下。 - 项目级安装位于
.pi/npm/之下。 - 在
settings.json中设置npmCommand,可以把 npm 包查找和安装操作固定到某个特定的包装命令,例如mise或asdf。
示例:
git
- 不带
git:前缀时,只接受带协议的 URL(https://、http://、ssh://、git://)。 - 带
git:前缀时,接受简写格式,包括github.com/user/repo和git@github.com:user/repo。 - HTTPS 和 SSH URL 都受支持。
- SSH URL 会自动使用你配置的 SSH 密钥(遵循
~/.ssh/config)。 - 在非交互运行(例如 CI)中,可以设置
GIT_TERMINAL_PROMPT=0来禁用凭据提示,并设置GIT_SSH_COMMAND(例如ssh -o BatchMode=yes -o ConnectTimeout=5)以便快速失败。 - ref 是固定的 tag 或 commit。
pi update --extensions和pi update --all不会把它们移动到更新的 ref,但会把已有克隆对账到配置的 ref。 - 使用
pi install git:host/user/repo@new-ref可以更新设置,并把已有包移动到新的固定 ref。 - 克隆到
~/.pi/agent/git/<host>/<path>(全局)或.pi/git/<host>/<path>(项目)。 - 当对账改变了检出内容时,pi 会重置并清理克隆,然后在存在
package.json时运行npm install。
SSH 示例:
本地路径
本地路径指向磁盘上的文件或目录,会被加入设置而不做复制。相对路径相对于其所在的设置文件解析。如果路径是文件,则作为单个扩展加载;如果是目录,pi 会按照包规则加载其中的资源。
创建 Pi 包
在 package.json 中添加 pi 清单,或使用约定目录。加上 pi-package 关键字便于被发现。
路径相对于包根目录。数组支持 glob 模式和 ! 排除项。清单中的正向 glob 会按字典序发现可见路径。以点开头的路径需要直接列出。如果某个 glob 需要穿越符号链接继续匹配,请直接列出该符号链接指向的资源根目录。
展示页元数据
包展示页会展示带有 pi-package 标签的包。添加 video 或 image 字段可以显示预览:
- video:仅限 MP4。桌面端悬停时自动播放,点击后打开全屏播放器。
- image:PNG、JPEG、GIF 或 WebP。以静态预览的形式展示。
两者同时设置时,video 优先。
包结构
约定目录
如果没有 pi 清单,pi 会从这些目录自动发现资源:
extensions/加载.ts和.js文件skills/递归查找SKILL.md文件夹,并把顶层的.md文件作为技能加载prompts/加载.md文件themes/加载.json文件
依赖
第三方运行时依赖应放在 package.json 的 dependencies 中。不注册扩展、技能、提示词模板或主题的依赖也属于 dependencies。当 pi 从 npm 或 git 安装一个包时,会运行 npm install,因此这些依赖会被自动安装。
Pi 为扩展和技能打包了核心包。如果你导入了其中任何一个,请把它们列在 peerDependencies 中并使用 "*" 范围,而且不要打包它们:@earendil-works/pi-ai、@earendil-works/pi-agent-core、@earendil-works/pi-coding-agent、@earendil-works/pi-tui、typebox。
其他 pi 包必须打包进你的 tarball。把它们加入 dependencies 和 bundledDependencies,然后通过 node_modules/ 路径引用它们的资源。Pi 加载包时使用各自独立的模块根,因此分开安装的包不会冲突,也不会共享模块。
示例:
包过滤
在设置中使用对象形式,可以过滤一个包加载的内容:
+path 和 -path 是相对于包根目录的精确路径。
- 省略某个键表示加载该类型的全部资源。
- 使用
[]表示不加载该类型的任何资源。 !pattern排除匹配项。+path强制包含某个精确路径。-path强制排除某个精确路径。- 过滤器叠加在清单之上,只会收窄已经允许的范围。
启用与禁用资源
使用 pi config 可以启用或禁用来自已安装包和本地目录的扩展、技能、提示词模板和主题。pi config 默认从全局设置(~/.pi/agent/settings.json)开始;按 Tab 可以在全局与项目级模式之间切换。使用 pi config -l 可以从项目覆盖(.pi/settings.json)开始,继承自全局的资源会以暗淡方式显示。
作用域与去重
包可以同时出现在全局和项目设置中。如果同一个包同时出现在两处,项目条目优先,除非项目条目设置了 autoload: false,此时它作为对全局条目的增量应用。包的同一性由以下方式确定:
- npm:包名
- git:不带 ref 的仓库 URL
- 本地路径:解析后的绝对路径
16 自定义模型
通过 ~/.pi/agent/models.json 添加自定义提供商与模型(Ollama、vLLM、LM Studio、代理)。
目录
最小示例
对于本地模型(Ollama、LM Studio、vLLM),每个模型只需提供 id:
apiKey 的值只是一个占位符,因为 Ollama 会忽略它。pi 仍要求模型在通过认证之后才会出现在 /model 中,因此不带密钥的本地服务器应保留一个虚拟值,用 /login 为该提供商保存一个密钥,或在选择模型时传入 --api-key。
一些 OpenAI 兼容服务器无法理解推理模型所用的 developer 角色。对于这类提供商,将 compat.supportsDeveloperRole 设为 false,pi 便会改用 system 消息来发送系统提示词。如果服务器同样不支持 reasoning_effort,请将 compat.supportsReasoningEffort 也设为 false。
compat 可以设在提供商级别,作用于所有模型;也可以设在模型级别,为特定模型做覆盖。这常见于 Ollama、vLLM、SGLang 以及类似的 OpenAI 兼容服务器。
完整示例
在需要具体数值时覆盖默认值:
每次打开 /model 时该文件都会重新加载。可以在会话中随时编辑,无需重启。
Google AI Studio 示例
配合 baseUrl 使用 google-generative-ai,即可添加来自 Google AI Studio 的模型,包括自定义的 Gemma 4 条目:
为 google-generative-ai API 类型添加自定义模型时,必须提供 baseUrl。
支持的 API
| API | 说明 |
|---|---|
openai-completions | OpenAI Chat Completions(兼容性最好) |
openai-responses | OpenAI Responses API |
anthropic-messages | Anthropic Messages API |
google-generative-ai | Google Generative AI |
在提供商级别设置 api(作为所有模型的默认值),或在模型级别设置(按模型覆盖)。
提供商配置
| 字段 | 说明 |
|---|---|
baseUrl | API 端点 URL |
api | API 类型(见上文) |
apiKey | 可选的 API 密钥配置(取值解析见下文)。当认证由 /login/auth.json 或 CLI 的 --api-key 提供时,可将其省略。 |
oauth | 动态 OAuth 提供商类型。目前支持 "radius";需要网关的 baseUrl。 |
headers | 自定义请求头(取值解析见下文) |
authHeader | 设为 true 时自动添加 Authorization: Bearer <apiKey> |
models | 模型配置数组 |
modelOverrides | 针对该提供商的内置模型或扩展注册模型进行逐模型覆盖 |
对于带 models 的提供商,非内置的提供商配置需要在提供商级别或模型级别提供 baseUrl 和 api 值。加载该文件本身并不要求 apiKey:当认证通过 /login/auth.json、CLI --api-key 或提供商 apiKey 配置好之后,模型即可使用。如果未配置任何认证,模型仍会加载,但在 /model 和 --list-models 中不可用。
取值解析
apiKey 与 headers 字段支持命令执行、环境变量插值和字面量:
- Shell 命令: 值以
"!command"开头时,整个值会作为命令执行,并使用其 stdout - 环境变量插值:
"$ENV_VAR"或"${ENV_VAR}"使用指定变量的值。插值也可以在更长的字面量内部使用。$FOO_BAR指向变量FOO_BAR;如果BAR是字面文本,应使用${FOO}_BAR。环境变量缺失时,该值视为未解析。 - 转义:
"$$"输出字面量"$";"$!"输出字面量"!",且不会触发命令执行。 - 字面量: 直接使用原值。
MY_API_KEY这类纯大写字符串就是字面量;要引用环境变量请写$MY_API_KEY。
对于 models.json,shell 命令在请求时解析。pi 有意不为任意命令应用内置的 TTL、过期复用或恢复逻辑——不同的命令需要不同的缓存与失败策略,pi 无法推断出正确的做法。
如果你的命令较慢、开销大、受速率限制,或希望在偶发失败时沿用先前的值,请把它包装进你自己的脚本或命令中,实现你想要的缓存或 TTL 行为。
/model 的可用性检查只依据已配置的认证信息,不会执行 shell 命令。
自定义请求头
模型配置
| 字段 | 必填 | 默认值 | 说明 |
|---|---|---|---|
id | 是 | — | 模型标识符(传给 API) |
name | 否 | id | 人类可读的模型标签。用于模型匹配(--model 的模式),并作为次要模型详情文本显示。 |
api | 否 | 提供商的 api | 为该模型覆盖提供商的 API |
reasoning | 否 | false | 支持扩展思考 |
thinkingLevelMap | 否 | 省略 | 将 pi 的思考级别映射为提供商取值,并标记不支持的级别(见下文) |
input | 否 | ["text"] | 输入类型:["text"] 或 ["text", "image"] |
contextWindow | 否 | 128000 | 上下文窗口大小(token 数) |
maxTokens | 否 | 16384 | 最大输出 token 数 |
samplingParams | 否 | 省略 | 逐字合并进每个请求体的采样参数(见下文) |
cost | 否 | 全为零 | 每百万 token 费率,可选按请求整体生效的输入定价分层 |
compat | 否 | 提供商的 compat | 提供商兼容性覆盖。两者都设置时会与提供商级别的 compat 合并。 |
费用分层提供一套完整的替代费率;当总输入用量(input + cacheRead + cacheWrite)超过 inputTokensAbove 时,对整个请求生效。多个分层同时命中时,以阈值最高的为准。
当前行为:
/model、--list-models和交互式页脚均按模型id显示条目。- 配置的
name用于模型匹配和次要模型详情文本,不会替换页脚/状态栏中的模型 id。
采样参数
samplingParams 是一个自由格式的对象,会逐字合并进该模型的每个请求体,并且合并在 pi 自身设置的字段之后,因此它的键优先。可用它发送 pi 未建模的采样参数——包括服务器特有的参数,比如 llama.cpp 的 min_p 或 vLLM 的 top_k:
只有 OpenAI 兼容 API 会应用它(openai-completions、openai-responses、azure-openai-responses);其他 API 会忽略它。其中的键会覆盖 pi 的具名请求字段(例如这里的 temperature 键优先于请求级别的 temperature),因此建议将它作为模型采样参数的唯一事实来源。在 modelOverrides 中,samplingParams 会按键与基础模型的值合并。
固定的思考 token 上限也可以放在这里,但它不会跟随 thinkingBudgets 变化,也不会为回答预留空间。这类需求请优先使用 compat.thinkingTokenBudgetField(或其别名 supportsThinkingTokenBudget)。
思考级别映射
在模型上使用 thinkingLevelMap 来描述该模型特有的思考控制。键是 pi 的思考级别:off、minimal、low、medium、high、xhigh、max。映射可以留有空档,例如某模型可以提供 high 和 max,而不提供 xhigh。
取值有三态:
| 取值 | 含义 |
|---|---|
| 省略 | high 及以下的标准级别使用提供商的默认映射;扩展的 xhigh 和 max 级别视为不支持 |
| string | 该级别受支持,此值会发送给提供商 |
null | 该级别不受支持,会被隐藏/跳过/钳制掉 |
仅支持 off、high 和 max 推理的模型示例:
思考无法被禁用的模型示例:
迁移:旧配置中使用的 compat.reasoningEffortMap 应迁移到模型级别的 thinkingLevelMap。对不应出现在 UI 中的级别使用 null。
覆盖内置提供商
将内置提供商的请求路由到代理,而无需重新定义模型:
所有内置 Anthropic 模型仍然可用。已有的 OAuth 或 API 密钥认证继续有效。
要将自定义模型合并进内置提供商,请加上 models 数组:
合并语义:
- 保留内置模型。
- 自定义模型按
id在该提供商内执行 upsert。 - 如果自定义模型的
id与某个内置模型相同,该自定义模型会替换对应的内置模型。 - 如果自定义模型的
id是新的,会与内置模型并列添加。
按模型覆盖
使用 modelOverrides 定制内置模型以及匹配的扩展注册模型,而无需替换该提供商的完整模型列表。
modelOverrides 支持按模型设置以下字段:name、reasoning、thinkingLevelMap、input、cost(部分)、contextWindow、maxTokens、samplingParams(按键合并)、headers、compat。
OpenAI 直连的 GPT-5.6 Sol、Terra 和 Luna 默认使用 272000 上下文窗口,以便请求保持在 OpenAI 的短上下文定价档内。要启用 OpenAI 的 1.05M 上下文窗口,请为你使用的每个模型调大该值:
该覆盖会保留内置的定价元数据。总输入 token 超过 272K 的请求,将按 GPT-5.6 的长上下文费率对整个请求计费。需要时可对 gpt-5.6-terra 或 gpt-5.6-luna 应用相同的覆盖。
行为说明:
modelOverrides应用于内置提供商模型以及匹配的扩展注册提供商模型。- 未知的模型 ID 会被忽略。
- 可以将提供商级别的
baseUrl/headers与modelOverrides组合使用。 - 覆盖
name只影响模型匹配和次要详情文本;页脚和主模型列表仍显示模型id。 - 如果提供商还定义了
models,自定义模型会在内置覆盖之后合并。id相同的自定义模型会替换已被覆盖的内置模型条目。
Anthropic Messages 兼容性
对于使用 api: "anthropic-messages" 的提供商或代理,可用 compat 控制 Anthropic 特有的请求兼容性。
默认情况下,pi 会为每个工具发送 eager_input_streaming: true。如果代理或 Anthropic 兼容后端拒绝该字段,请将 supportsEagerToolInputStreaming 设为 false。这样 pi 会省略 tools[].eager_input_streaming,并在带工具的请求中改用旧版 fine-grained-tool-streaming-2025-05-14 beta 头。
某些 Anthropic 模型需要自适应思考(thinking.type: "adaptive" 加 output_config.effort),而不是传统的基于预算的思考负载。内置模型会自动设置。对于路由到这些模型的自定义提供商或别名,请将 forceAdaptiveThinking 设为 true。
支持逐轮 effort 的 Claude 模型使用 supportsMidConvoEffort。启用后,pi 会持久化每个响应中提供商返回的 effort,在后续请求中重建仅含 effort 的系统消息,并发送带 prefix_mismatch_behavior: "drop_block" 的思考绑定控制,以避免过期的已签名思考前缀导致持续的 400 响应。请只对忠实实现 Anthropic Messages 传输且确切受支持的 Claude 模型启用它;不要为仅模仿 Messages 结构的 API 启用。
某些 Anthropic 兼容提供商会输出签名为空的思考块,并且期望在回放时原样传回。只应对这类提供商将 allowEmptySignature 设为 true;真正的 Anthropic 会拒绝空的思考签名。
内置 Anthropic 模型在模型元数据中启用了 supportsStrictTools。自定义的 Anthropic 兼容模型,在其端点接受严格 JSON schema 工具定义时,必须将它设为 true。
| 字段 | 说明 |
|---|---|
supportsEagerToolInputStreaming | 提供商是否接受逐工具的 eager_input_streaming。默认:true。设为 false 可省略该字段,并在带工具的请求中使用旧版细粒度工具流式 beta 头。 |
supportsLongCacheRetention | 当缓存保留策略为 long 时,提供商是否接受 Anthropic 长缓存保留(cache_control.ttl: "1h")。默认:true。 |
sendSessionAffinityHeaders | 启用缓存时,是否根据会话 id 发送 x-session-affinity。默认:对已知提供商自动检测。 |
supportsCacheControlOnTools | 提供商是否接受工具定义上的 Anthropic 风格 cache_control 标记。默认:true。 |
forceAdaptiveThinking | 是否为该模型发送自适应思考(thinking.type: "adaptive" 加 output_config.effort)。内置自适应模型会自动设置。默认:false。 |
supportsMidConvoEffort | 确切的 Claude 模型传输是否支持逐轮 effort 系统消息与思考绑定控制。启用后,pi 会持久化原生 effort 级别并始终发送 drop_block。默认:false。 |
allowEmptySignature | 回放时是否将空思考签名保持为 signature: "",而不是把思考转换为文本。默认:false。 |
supportsStrictTools | 提供商是否接受严格 JSON schema 工具定义。默认:false;内置 Anthropic 模型在生成的元数据中启用了它。 |
OpenAI 兼容性
对于只部分兼容 OpenAI 的提供商,使用 compat 字段。
- 提供商级别的
compat为该提供商下的所有模型提供默认值。 - 模型级别的
compat为该模型覆盖提供商级别的值。
| 字段 | 说明 |
|---|---|
supportsStore | 提供商支持 store 字段 |
supportsDeveloperRole | 使用 developer 还是 system 角色 |
supportsReasoningEffort | 是否支持 reasoning_effort 参数 |
supportsUsageInStreaming | 是否支持 stream_options: { include_usage: true }(默认:true) |
supportsFinishReason | 流式响应是否包含 finish_reason。为 false 时,pi 会在流结束时推断 stop 或 toolUse。默认:true。 |
maxTokensField | 使用 max_completion_tokens 还是 max_tokens |
requiresToolResultName | 在工具结果消息中包含 name |
requiresAssistantAfterToolResult | 在工具结果之后的用户消息前插入一条 assistant 消息 |
requiresThinkingAsText | 将思考块转换为纯文本 |
requiresReasoningContentOnAssistantMessages | 启用推理时,在所有回放的 assistant 消息上包含空的 reasoning_content |
thinkingFormat | 使用 reasoning_effort、openrouter、deepseek、together、baseten、zai、qwen、chat-template 或 qwen-chat-template 思考参数 |
chatTemplateKwargs | thinkingFormat: "chat-template" 时的 chat_template_kwargs 取值;使用 { "$var": "thinking.enabled" }、{ "$var": "thinking.effort" } 或 { "$var": "thinking.budget" } 表示由 pi 控制的思考取值 |
chatTemplateArgs | thinkingFormat: "baseten" 时的 chat_template_args 取值;使用 { "$var": "thinking.enabled" }、{ "$var": "thinking.effort" } 或 { "$var": "thinking.budget" } 表示由 pi 控制的思考取值 |
thinkingTokenBudgetField | 用于根据 thinkingBudgets 限制推理 token 的顶层请求字段,并会钳制以确保至少为回答保留 1024 个 token。"thinking_token_budget"(vLLM)、"thinking_budget"(Qwen/DashScope/SGLang)、"thinking_budget_tokens"(llama.cpp)。默认关闭;不会在生成的模型目录上设置。 |
supportsThinkingTokenBudget | thinkingTokenBudgetField: "thinking_token_budget"(vLLM)的别名。建议优先使用 thinkingTokenBudgetField。默认:false。 |
cacheControlFormat | 在系统提示词、最后一个工具定义以及最后一条 user、assistant 或工具结果文本内容上使用 Anthropic 风格的 cache_control 标记。目前仅支持 anthropic。 |
sendSessionAffinityHeaders | 对 openai-completions,启用缓存时根据会话 id 发送会话亲和头。默认:false。 |
sessionAffinityFormat | 对 openai-completions 和 openai-responses 的会话亲和头格式:openai 发送 session_id/x-client-request-id(completions 还会发送 x-session-affinity);openai-nosession 省略包含下划线的 session_id 头;openrouter 发送 x-session-id。不影响 prompt_cache_key 请求体参数。默认:自动检测。 |
supportsStrictMode | 提供商是否接受严格 JSON schema 的函数工具定义。默认值取决于 API;内置 OpenAI 模型带有显式的能力元数据。 |
supportsOpenAIGrammarTools | OpenAI 兼容 API 是否输出自定义 Lark/正则文法工具。为 false 时,文法约束的工具会退回为普通函数工具。默认:false;内置模型目录为 OpenAI、OpenAI Codex、Azure OpenAI、GitHub Copilot、opencode 和 Cloudflare AI Gateway 上的 GPT-5+ 模型启用了它。 |
deferredToolsMode | 使用提供商特有的延迟工具序列化。目前仅支持 "kimi",用于 Kimi 的 OpenAI 兼容 Chat Completions 格式。 |
supportsLongCacheRetention | 当缓存保留策略为 long 时,提供商是否接受长缓存保留:GPT-5.6+ Responses 模型使用 prompt_cache_options.ttl: "30m",较早的 OpenAI 模型使用 prompt_cache_retention: "24h",cacheControlFormat 为 anthropic 时使用 cache_control.ttl: "1h"。默认:true。 |
openRouterRouting | OpenRouter 提供商路由偏好。该对象会原样放入 OpenRouter API 请求的 provider 字段中发送。 |
vercelGatewayRouting | Vercel AI Gateway 用于提供商选择的路由配置(only、order) |
openrouter 使用 reasoning: { effort }。together 使用 reasoning: { enabled },并在启用 supportsReasoningEffort 时同时发送 reasoning_effort。qwen 使用顶层的 enable_thinking。对于需要 chat_template_kwargs.enable_thinking 和 preserve_thinking 的本地 Qwen 兼容服务器,使用 qwen-chat-template。对于需要可配置 chat_template_kwargs 的 vLLM/Hugging Face 聊天模板,使用 chat-template,例如 DeepSeek V3.x 模板可用 chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }。对于通过 chat_template_args 暴露开关控制并可选支持顶层 reasoning_effort 的提供商,将 thinkingFormat: "baseten" 与 chatTemplateArgs 搭配使用。
thinkingTokenBudgetField 与 thinkingFormat 相互独立。不要在生成的 Qwen 模型目录上启用它:这些模型已经发送 reasoning_effort,而 DashScope 会拒绝 thinking_budget 与 reasoning_effort 同时出现。
cacheControlFormat: "anthropic" 适用于通过文本内容和工具定义上的 cache_control 标记暴露 Anthropic 风格提示词缓存的 OpenAI 兼容提供商。
示例:
Vercel AI Gateway 示例:
17 自定义提供商
扩展可以通过 pi.registerProvider() 注册自定义模型提供商。它可以实现:
- 代理 —— 将请求路由到企业代理或 API 网关
- 自定义端点 —— 使用自托管或私有的模型部署
- OAuth/SSO —— 为企业级提供商添加认证流程
- 自定义 API —— 为非标准 LLM API 实现流式传输
示例扩展
请参阅以下完整的提供商示例:
目录
快速参考
扩展既可以注册一个完整的 pi-ai Provider,也可以使用旧版的提供商配置形式。当需要自定义认证、过滤、刷新或流式行为时,建议使用完整的 Provider。pi 会将 models.json 覆盖叠加在已注册的原生提供商之上。
扩展工厂函数也可以是 async 的。要实现动态模型发现,请在工厂函数中(而不是在 session_start 里)获取并注册模型。pi 会在继续启动之前等待工厂函数完成,因此该提供商在交互式启动期间以及 pi --list-models 中都可用。
覆盖已有提供商
最简单的用例:将已有提供商的请求重定向到代理。
当只提供 baseUrl 和/或 headers(不含 models)时,该提供商的所有既有模型都会保留,并使用新的端点。
注册新提供商
要添加一个全新的提供商,请在必要配置之外指定 models。
如果模型列表来自远程端点,请使用 async 扩展工厂函数:
这会在启动完成之前注册获取到的模型。
提供 models 时,它会替换该提供商的所有既有模型。
apiKey 与自定义请求头的取值使用与 models.json 相同的配置值语法:值以 !command 开头时将整个值作为命令执行;$ENV_VAR 和 ${ENV_VAR} 插值环境变量;$$ 输出字面量 $;$! 输出字面量 !。
注销提供商
使用 pi.unregisterProvider(name) 移除之前通过 pi.registerProvider(name, ...) 注册的提供商:
注销会移除该提供商的动态模型、API 密钥回退、OAuth 提供商注册以及自定义流处理器注册。之前被覆盖的内置模型或提供商行为都会恢复。
在扩展初始加载阶段之后发起的调用会立即生效,因此无需 /reload。
API 类型
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 原生 Chat Completions 流式 |
google-generative-ai | Google Generative AI API |
google-vertex | Google Vertex AI API |
bedrock-converse-stream | Amazon Bedrock Converse API |
大多数 OpenAI 兼容提供商都可以使用 openai-completions。模型特有的思考级别用模型级别的 thinkingLevelMap 设置,提供商的怪癖用 compat 处理。xhigh 和 max 级别需要显式启用,要求映射条目为非 null 值,且中间可能隔着不支持的空档:
OpenRouter 风格的 reasoning: { effort } 控制使用 openrouter。Together 风格的 reasoning: { enabled } 控制使用 together;配合 supportsReasoningEffort 时,它还会发送 reasoning_effort。读取 chat_template_kwargs.enable_thinking 且需要 preserve_thinking 的本地 Qwen 兼容服务器使用 qwen-chat-template。
对于通过在系统提示词、最后一个工具定义以及最后一条 user、assistant 或工具结果文本内容上添加 cache_control 来暴露 Anthropic 风格提示词缓存的 OpenAI 兼容提供商,使用 cacheControlFormat: "anthropic"。
对于使用 api: "anthropic-messages" 的 Anthropic 兼容提供商,若其上游模型需要自适应思考(thinking.type: "adaptive" 加 output_config.effort),请在相应的模型或提供商上设置 compat.forceAdaptiveThinking: true。内置的自适应 Claude 模型会自动设置。compat.allowEmptySignature: true 只应用于会输出空思考签名并期望回放时传回 signature: "" 的提供商。
迁移说明:Mistral 已从
openai-completions迁移到mistral-conversations。 原生 Mistral 模型请使用mistral-conversations。 如果你有意让 Mistral 兼容/自定义端点走openai-completions,请按需显式设置compat标志。
认证请求头
如果提供商期望 Authorization: Bearer <key> 但并不使用标准 API,请设置 authHeader: true:
密钥会按请求逐次解析。显式设置的请求 Authorization 头优先于生成的值。
OAuth 支持
添加可与 /login 集成的 OAuth/SSO 认证:
注册后,用户即可通过 /login corporate-ai 完成认证。
OAuthLoginCallbacks
callbacks 对象为提供商自己的登录流程提供与 UI 无关的交互方式:
OAuthCredentials
凭据会持久化到 ~/.pi/agent/auth.json:
自定义流式 API
对于使用非标准 API 的提供商,可实现 streamSimple。编写自己的实现之前,请先研究现有的 API 实现:
参考实现:
- anthropic-messages.ts - Anthropic Messages API
- mistral-conversations.ts - Mistral Conversations API
- openai-completions.ts - OpenAI Chat Completions
- openai-responses.ts - OpenAI Responses API
- google-generative-ai.ts - Google Generative AI
- bedrock-converse-stream.ts - AWS Bedrock
流式模式
所有提供商都遵循相同的模式:
事件类型
按以下顺序通过 stream.push() 推送事件:
{ type: "start", partial: output }- 流开始内容事件(可重复,为每个块跟踪
contentIndex):{ type: "text_start", contentIndex, partial }- 文本块开始{ type: "text_delta", contentIndex, delta, partial }- 文本分片{ type: "text_end", contentIndex, content, partial }- 文本块结束{ type: "thinking_start", contentIndex, partial }- 思考开始{ type: "thinking_delta", contentIndex, delta, partial }- 思考分片{ type: "thinking_end", contentIndex, content, partial }- 思考结束{ type: "toolcall_start", contentIndex, partial }- 工具调用开始{ type: "toolcall_delta", contentIndex, delta, partial }- 工具调用 JSON 分片{ type: "toolcall_end", contentIndex, toolCall, partial }- 工具调用结束
{ type: "done", reason, message }或{ type: "error", reason, error }- 流结束
每个事件的 partial 字段包含当前 AssistantMessage 的状态。收到数据时更新 output.content,然后把 output 作为 partial 传入。
内容块
内容块到达时将其添加到 output.content:
工具调用
工具调用需要累积 JSON 并解析:
用量与费用
根据 API 响应更新用量并计算费用:
上下文溢出错误
当请求超出模型的上下文窗口时,pi 可以通过对会话进行上下文压缩并重试来自动恢复。只有当 pi 把失败识别为溢出时,该恢复机制才会生效。
检测在定稿的 assistant 消息上进行:
stopReason === "error"errorMessage匹配 pi 已知的某个溢出模式(参见packages/ai/src/utils/overflow.ts)
如果你的提供商返回的溢出错误消息 pi 无法识别,请在注册该提供商的同一个扩展中规范化错误。使用 message_end 处理器改写 assistant 消息,使其 errorMessage 以 pi 能识别的短语开头。通用回退值 context_length_exceeded 是最安全的选择。
message_end 在 pi 为自动上下文压缩记录该 assistant 消息之前运行,因此 pi 检查的是改写后的 errorMessage。有了这一处理,pi 将:
- 从
errorMessage检测溢出。 - 将失败的 assistant 消息从活跃上下文中丢弃。
- 执行上下文压缩。
- 重试该请求一次。
请谨慎地对改写加保护条件:
- 将作用域限定在你的提供商(
message.provider与ctx.model?.provider),避免影响其他提供商的无关错误。 - 匹配提供商特有的模式,而不是 pi 的通用溢出模式。如果改写了限流或节流错误(
rate limit、too many requests),会错误地触发上下文压缩,而不是走 pi 正常的退避重试路径。 - 当
errorMessage已包含context_length_exceeded时跳过,以保证处理器的幂等性。
注册
注册你的流式函数:
测试你的实现
使用内置提供商所用的同一套测试来测试你的提供商。从 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 | 提供商之间的上下文交接 |
用你的提供商/模型组合运行测试以验证兼容性。
配置参考
模型定义参考
openrouter 发送 reasoning: { effort }。deepseek 发送 thinking: { type: "enabled" | "disabled" },启用时还会发送 reasoning_effort。together 发送 reasoning: { enabled },并在启用 supportsReasoningEffort 时同时发送 reasoning_effort。qwen 用于 DashScope 风格的顶层 enable_thinking。读取 chat_template_kwargs.enable_thinking 且需要 preserve_thinking 的本地 Qwen 兼容服务器使用 qwen-chat-template。chat-template 用于可配置的 chat_template_kwargs,例如由 vLLM 提供服务的 DeepSeek V3.x 可用 chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }。当提供商期望在 chat_template_args 下传递开关取值并可选支持顶层 reasoning_effort 时,将 thinkingFormat: "baseten" 与 chatTemplateArgs 搭配使用。
thinkingTokenBudgetField 将按级别钳制的思考预算作为顶层请求字段发送(vLLM 用 thinking_token_budget,Qwen/SGLang 用 thinking_budget,llama.cpp 用 thinking_budget_tokens)。supportsThinkingTokenBudget: true 是 vLLM 字段名的别名。不要在 DashScope 的 Qwen 模型上将它与 reasoning_effort 组合使用。
cacheControlFormat: "anthropic" 会将 Anthropic 风格的 cache_control 标记应用到系统提示词、最后一个工具定义以及最后一条 user、assistant 或工具结果文本内容上。
18 环境变量
Pi 通过三种方式使用环境变量:
PI_OFFLINE等变量用于配置 Pi 进程。- Pi 会设置进程标记,让子进程能够识别出启动它们的智能体是 Pi。
- 由 LLM 可调用的 shell 工具执行的命令会收到描述当前会话的
PI_*变量。
提供商 API 密钥变量在提供商中单独说明。
进程标记
CLI 和 RPC 入口会设置两个进程标记:
AI_AGENT=pi是一个通用标记,让各类工具能够识别 Pi 是启动该进程的智能体。PI_CODING_AGENT=true是 Pi 专用的标记,让子进程检测到自己运行在 Pi 内部。
子进程会继承这两个标记。它们与会话无关,并且在通过 SDK 嵌入 Pi 时不会自动设置。
Shell 工具会话环境
由 bash 和 powershell 工具执行的命令会收到当前 Pi 会话的状态:
| 变量 | 说明 |
|---|---|
PI_SESSION_ID | 当前会话 ID |
PI_SESSION_FILE | 当前会话 JSONL 文件的绝对路径;临时会话未设置 |
PI_PROVIDER | 当前选定的模型提供商 |
PI_MODEL | 当前选定的模型 ID |
PI_REASONING_LEVEL | 当前生效的推理级别:off、minimal、low、medium、high、xhigh 或 max |
这些值在每条命令启动时解析。因此,切换模型或更改推理级别无需重启 Pi 即可影响下一条 shell 命令。PI_PROVIDER 和 PI_MODEL 标识的是所选的 Pi 模型,而不是路由器内部可能选择的其他上游模型。
当被问到当前运行的是哪个模型或提供商时,应检查这些变量,而不是从系统提示词中推断答案:
当会话是持久化会话时,可以直接查看会话文件:
这些变量会注入到 LLM 可调用的 bash 和 powershell 工具中,但不会注入到用户手动输入的 ! 或 !! 命令中。
自定义 Shell 工具
使用 createBashTool() 或 createPowerShellTool() 创建的工具在注册到 Pi 后,默认会暴露会话环境。注入发生在 spawnHook 之前,因此钩子会在 ctx.env 中收到这些变量:
可以独立于 spawn 钩子禁用会话元数据:
禁用后,Pi 会移除这些变量的继承值,避免嵌套的 Pi 进程暴露过时的父会话元数据。
Pi 进程配置
以下变量由 Pi 自身读取:
| 变量 | 说明 |
|---|---|
PI_CODING_AGENT_DIR | 覆盖配置目录;默认为 ~/.pi/agent |
PI_CODING_AGENT_SESSION_DIR | 覆盖会话存储;会被 --session-dir 覆盖 |
PI_PACKAGE_DIR | 覆盖包目录,适用于 Nix/Guix store 路径 |
PI_OFFLINE | 禁用启动时的网络操作,包括更新检查、包更新和安装/更新遥测 |
PI_SKIP_VERSION_CHECK | 禁用 pi.dev 的最新版本请求 |
PI_TELEMETRY | 覆盖安装/更新遥测和提供商归因标头:1/true/yes 或 0/false/no |
PI_CACHE_RETENTION | 在支持的提供商上设为 long 可启用扩展提示词缓存 |
PI_SHARE_VIEWER_URL | 覆盖 /share 使用的基础 URL |
PI_HARDWARE_CURSOR | 设为 1 显示硬件光标;参见终端设置 |
PI_HYPERLINKS | 以 1、0 或 auto 覆盖 OSC 8 超链接检测 |
PI_IMAGE_PROTOCOL | 以 kitty、iterm2、none 或 auto 覆盖内联图片检测 |
PI_TRUE_COLOR | 以 1、0 或 auto 覆盖真彩色检测 |
PI_TUI_ESC_TIMEOUT | 在单独按下 ESC 后等待多久才将其视为 Escape 键,单位为毫秒;SSH 下默认为 100,其他情况为 10。如果 Alt 键输入被误判为 Escape,请增大该值 |
VISUAL、EDITOR | 未设置 externalEditor 时使用的外部编辑器回退 |
HTTP_PROXY、HTTPS_PROXY | 为出站 HTTP 请求设置代理 |
ANTHROPIC_API_KEY、OPENAI_API_KEY 等提供商凭据以及云提供商配置详见提供商。
PI_SERVER_DIR 和 PI_SERVER_ID 仅适用于只随源码提供的实验性远程测试框架,不适用于分发的构建。
19 会话文件格式
会话以 JSONL(JSON Lines)文件的形式存储。每一行都是一个带有 type 字段的 JSON 对象。会话条目通过 id/parentId 字段构成树形结构,从而无需创建新文件即可就地分叉。
文件位置
默认情况下,<session-id> 是一个 UUID。调用者可以通过 SDK 或 --session-id 提供自定义 ID。对于 <path>,Pi 会去掉开头的路径分隔符,并将 /、\\ 和 : 替换为 -。
删除会话
删除 ~/.pi/agent/sessions/ 下对应的 .jsonl 文件即可移除会话。
Pi 还支持在 /resume 中交互式删除会话(选中会话后按 Ctrl+D,然后确认)。在可用时,pi 会使用 trash CLI 以避免永久删除。
会话版本
会话在头部包含一个版本字段:
- 版本 1:线性条目序列(旧版,加载时自动迁移)
- 版本 2:通过
id/parentId连接的树形结构 - 版本 3:将
hookMessage角色重命名为custom(扩展统一)
已有会话在加载时会自动迁移到当前版本(v3)。
源码文件
GitHub 上的源码(pi):
packages/coding-agent/src/core/session-manager.ts- 会话条目类型与 SessionManagerpackages/coding-agent/src/core/messages.ts- 扩展消息类型(BashExecutionMessage、CustomMessage 等)packages/ai/src/types.ts- 基础消息类型(UserMessage、AssistantMessage、ToolResultMessage)packages/agent/src/types.ts- AgentMessage 联合类型
如需项目中的 TypeScript 定义,请查看 node_modules/@earendil-works/pi-coding-agent/dist/ 和 node_modules/@earendil-works/pi-ai/dist/。
消息类型
会话条目中包含 AgentMessage 对象。理解这些类型对于解析会话和编写扩展至关重要。
内容块
消息包含类型化内容块的数组:
基础消息类型(来自 pi-ai)
"pending" 保留给流式事件中的部分消息。在 Pi 持久化助手消息之前,终止事件会将其替换为完成原因,因此 "pending" 不应出现在会话 JSONL 中。"deferred" 是一种终止原因,表示提供商响应将在稍后完成;其 deferred 句柄包含取回该响应所需的提供商数据。
扩展消息类型(来自 pi-coding-agent)
AgentMessage 联合类型
条目基础
所有条目(SessionHeader 除外)都扩展自 SessionEntryBase:
条目类型
SessionHeader
文件的第一行。仅包含元数据,不属于树的一部分(没有 id/parentId)。
对于有父会话的会话(通过 /fork、/clone 或 newSession({ parentSession }) 创建):
SessionMessageEntry
对话中的一条消息。message 字段包含一个 AgentMessage。
ModelChangeEntry
在用户于会话中途切换模型时发出。
ThinkingLevelChangeEntry
在用户更改思考/推理级别时发出。
CompactionEntry
在上下文被压缩时创建。存储较早消息的摘要。
firstKeptEntryId 是必需的。它标识压缩条目之前被保留的第一条条目。重建上下文时,Pi 会用压缩摘要替换较早的被摘要条目,并保留从该条目开始的范围。
可选字段:
usage:生成摘要的 LLM 用量;计入会话的 token 与费用总计details:实现专属数据(默认为{ readFiles: string[], modifiedFiles: string[] },扩展可使用自定义数据)fromHook:由扩展生成时为true,由 pi 生成时为false/undefined(旧版字段名)
BranchSummaryEntry
在通过 /tree 切换分支时创建,附带由 LLM 生成的、从被离开分支到共同祖先的摘要。捕获被放弃路径中的上下文。
parentId 是新分支延续自的条目。fromId 是被放弃路径被摘要的前一个叶子。
可选字段:
usage:生成摘要的 LLM 用量;计入会话的 token 与费用总计details:文件跟踪数据(默认为{ readFiles: string[], modifiedFiles: string[] }),扩展可使用自定义数据fromHook:由扩展生成时为true,由 pi 生成时为false/undefined(旧版字段名)
CustomEntry
扩展状态持久化。不参与 LLM 上下文。
使用 customType 在重新加载时识别你扩展的条目。交互模式可以通过 pi.registerEntryRenderer(customType, renderer) 渲染自定义条目,但它们仍然不参与 LLM 上下文。
CustomMessageEntry
扩展注入的、会参与 LLM 上下文的消息。
字段:
content:字符串或(TextContent | ImageContent)[](与 UserMessage 相同)display:true= 以独特样式显示在 TUI 中,false= 隐藏details:可选的扩展专属元数据(不发送给 LLM)
LabelEntry
条目上的用户定义书签/标记。
将 label 设为 undefined 即可清除标签。
SessionInfoEntry
会话元数据(例如用户定义的显示名称)。通过 /name、--name / -n 或扩展中的 pi.setSessionName() 设置。
设置后,会话名称会在会话选择器(/resume)中显示,取代第一条消息。
树形结构
条目通常构成一棵树,但导航 API 可以创建多个根:
- 根条目的
parentId为null;第一条条目最初就是根 - 每个非根条目通过
parentId指向其父条目 - 分叉会从更早的条目创建新的子条目
- “叶子"是树中的当前位置
- 调用
resetLeaf()或branchWithSummary(null, ...)可以让后续条目成为另一个根
上下文构建
buildContextEntries() 从当前叶子走到根,生成活动条目列表,同时处理压缩:
- 收集路径上的所有条目
- 如果路径上有一个或多个
CompactionEntry,使用最新的那一个:- 首先包含该压缩条目
- 包含从
firstKeptEntryId到(但不包含)压缩条目之间的条目 - 包含压缩条目之后的条目
- 在所选范围内保留非消息条目,以便交互模式渲染它们
buildSessionContext() 在该条目列表的基础上生成给 LLM 的消息列表:
- 从完整路径中提取当前模型和思考级别设置
- 将选中的条目转换为消息:
message-> 存储的AgentMessagecompaction->compactionSummarybranch_summary->branchSummarycustom_message->CustomMessagecustom-> 不生成上下文消息
压缩摘要会替换 firstKeptEntryId 之前的条目。保留的条目以及压缩之后的所有条目仍对 LLM 可用。
解析示例
SessionManager API
以编程方式使用会话的关键方法。
静态创建方法
SessionManager.create(cwd, sessionDir?, options?)- 新建会话;options可设置id和parentSessionSessionManager.open(path, sessionDir?, cwdOverride?)- 打开已有的会话文件SessionManager.continueRecent(cwd, sessionDir?)- 继续最近的会话或新建SessionManager.inMemory(cwd?, options?, entries?)- 不持久化到文件,可选择从条目初始化SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?, options?)- 从另一个项目分叉会话
静态列表方法
SessionManager.list(cwd, sessionDir?, onProgress?)- 列出某个目录的会话SessionManager.listAll(onProgress?)- 列出所有项目的所有会话SessionManager.listAll(sessionDir?, onProgress?)- 列出自定义会话根目录下的会话
实例方法 - 会话管理
newSession(options?)- 开始新会话(options:{ id?: string, parentSession?: string })setSessionFile(path)- 切换到另一个会话文件createBranchedSession(leafId)- 将分支提取到新的会话文件
实例方法 - 追加(均返回条目 ID)
appendMessage(message)- 添加消息appendThinkingLevelChange(level)- 记录思考级别变化appendModelChange(provider, modelId)- 记录模型变化appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?, usage?)- 添加压缩appendCustomEntry(customType, data?)- 扩展状态(不进入上下文)appendSessionInfo(name)- 设置会话显示名称appendCustomMessageEntry(customType, content, display, details?)- 扩展消息(进入上下文)appendLabelChange(targetId, label)- 设置/清除标签
实例方法 - 树形导航
getLeafId()- 当前位置getLeafEntry()- 获取当前叶子条目getEntry(id)- 按 ID 获取条目getBranch(fromId?)- 从条目走到根getTree()- 获取完整树形结构getChildren(parentId)- 获取直接子条目getLabel(id)- 获取条目标签branch(entryId)- 将叶子移到更早的条目resetLeaf()- 将叶子重置为 null(所有条目之前)branchWithSummary(entryId, summary, details?, fromHook?, usage?)- 带上下文摘要的分叉;entryId可为null以从根分叉
实例方法 - 上下文与信息
buildContextEntries()- 获取应用了压缩的活动分支条目buildSessionContext()- 获取给 LLM 的消息、thinkingLevel 和模型getEntries()- 所有条目(不含头部)getHeader()- 会话头部元数据getSessionName()- 从最新的 session_info 条目获取显示名称getCwd()- 工作目录getSessionDir()- 会话存储目录getSessionId()- 会话 UUIDgetSessionFile()- 会话文件路径(内存会话为 undefined)isPersisted()- 会话是否已保存到磁盘
20 SDK
pi 可以帮你使用 SDK。可以请它为你的使用场景构建一个集成。
SDK 提供了对 pi 智能体能力的编程式访问。你可以用它把 pi 嵌入其他应用、构建自定义界面,或与自动化流程集成。
典型使用场景:
- 构建自定义 UI(Web、桌面、移动端)
- 将智能体能力集成到现有应用中
- 借助智能体推理能力创建自动化流水线
- 构建能派生子智能体的自定义工具
- 以编程方式测试智能体行为
从最小化接入到完全控制的可运行示例,请参见 examples/sdk/。
快速开始
安装
SDK 已包含在主包中,无需单独安装。
核心概念
createAgentSession()
创建单个 AgentSession 的主工厂函数。
createAgentSession() 使用 ResourceLoader 来提供扩展、技能、提示词模板、主题和上下文文件。如果你没有提供,它会使用带标准发现机制的 DefaultResourceLoader。
AgentSession
会话负责管理智能体生命周期、消息历史、模型状态、上下文压缩和事件流。
当智能体正在响应、手动或自动上下文压缩正在进行、或另一次树状导航处于活动状态时,session.navigateTree() 会直接拒绝,即使设置了 summarize: false 也不例外。对于这类冲突,它既不会把导航排入队列,也不会返回 { cancelled: true }。请等待当前操作完成(例如使用 await session.waitForIdle())后重试。被拒绝时,当前活动分支保持不变。
新建会话、恢复、分叉和导入等会话替换 API 位于 AgentSessionRuntime 上,而不是 AgentSession 上。
createAgentSessionRuntime() 与 AgentSessionRuntime
当你需要替换当前活动会话并重建与 cwd 绑定的运行时状态时,请使用运行时 API。内置的交互模式、print 模式和 RPC 模式使用的就是这一层。
createAgentSessionRuntime() 接受一个运行时工厂,以及初始的 cwd/会话目标。该工厂闭包捕获进程级固定输入,为实际生效的 cwd 重建与 cwd 绑定的服务,基于这些服务解析会话选项,并返回完整的运行时结果。
AgentSessionRuntime 负责在以下操作中替换活动运行时:
newSession()switchSession()fork()- 通过
fork(entryId, { position: "at" })实现的克隆流程 importFromJsonl()
重要行为:
- 这些操作之后
runtime.session会发生变化 - 事件订阅绑定在具体的
AgentSession上,替换后需要重新订阅 - 如果使用了扩展,需要为新会话再次调用
runtime.session.bindExtensions(...) - 创建结果的诊断信息位于
runtime.diagnostics - 如果运行时创建或替换失败,方法会抛出异常,由调用方决定如何处理
提示与消息排队
PromptOptions 用于控制提示词模板展开、流式期间的排队行为,以及提示词预检(preflight)通知:
preflightResult 在每次 prompt() 调用中只触发一次:
- 提示词被接受、进入队列或被立即处理时为
true - 提示词预检在接受之前被拒绝时为
false
它在 prompt() 完成之前触发。prompt() 仍要等到整个被接受的运行(包括重试)结束后才会完成。接受之后的失败会通过正常的事件和消息流上报,而不会通过 preflightResult(false) 体现。
prompt() 方法负责处理提示词模板、扩展命令和消息发送:
行为:
- 扩展命令(例如
/mycommand):立即执行,流式期间也不例外。它们通过pi.sendMessage()自行管理 LLM 交互。 - 基于文件的提示词模板(来自
.md文件):在发送或排队之前展开为其内容。 - 流式期间未指定
streamingBehavior:会抛出错误。请直接使用steer()或followUp(),或者显式指定该选项。 preflightResult(true):表示提示词已被接受、进入队列或被立即处理。preflightResult(false):表示预检在接受之前被拒绝。
要在流式期间显式排队:
steer() 和 followUp() 都会展开基于文件的提示词模板,但遇到扩展命令会报错(扩展命令无法排队)。
Agent 与 AgentState
Agent 类(来自 @earendil-works/pi-agent-core)负责核心的 LLM 交互,可通过 session.agent 访问。
事件
订阅事件以接收流式输出和生命周期通知。
选项参考
目录
DefaultResourceLoader 将 cwd 用于:
- 项目扩展(
.pi/extensions/) - 项目技能:
.pi/skills/cwd及其祖先目录中的.agents/skills/(向上直到 git 仓库根目录;不在仓库中时则到文件系统根目录)
- 项目提示词模板(
.pi/prompts/) - 上下文文件(从
cwd向上逐级查找AGENTS.md) - 会话目录命名
DefaultResourceLoader 将 agentDir 用于:
- 全局扩展(
extensions/) - 全局技能:
agentDir下的skills/(例如~/.pi/agent/skills/)~/.agents/skills/
- 全局提示词模板(
prompts/) - 全局上下文文件(
AGENTS.md) - 设置(
settings.json) - 自定义模型(
models.json) - 凭据(
auth.json) - 会话(
sessions/)
当你传入自定义 ResourceLoader 时,cwd 和 agentDir 不再控制资源发现,但它们仍会影响会话命名和工具路径解析。
模型
如果没有提供模型:
- 尝试从会话恢复(如果是继续已有会话)
- 使用设置中的默认值
- 回退到第一个可用模型
远程目录会持久化到本地,后续创建的运行时无需网络请求即可恢复它们。默认文件是 ~/.pi/agent/models-store.json;可设置 modelsStorePath 选择其他位置,或注入 modelsStore 来控制持久化。网络刷新有节流限制:除非强制,每个提供商每四小时最多刷新一次。要强制立即刷新,可调用 await modelRuntime.refresh({ allowNetwork: true, force: true, signal })。设置 PI_OFFLINE 会禁用模型网络访问。
要与 CLI 的模型解析行为保持一致,请使用导出的解析辅助函数:
resolveCliModel() 会使用所有已注册的模型,因此 --api-key 式的首次设置流程可以在存储的凭据存在之前就解析出模型。resolveModelScopeWithDiagnostics() 与 --models 和 enabledModels 的语义一致,但以返回值形式给出警告而不是直接打印。
API 密钥与 OAuth
认证解析优先级(由 ModelRuntime 处理):
- 运行时覆盖(通过
setRuntimeApiKey设置,不持久化) auth.json中存储的凭据(API 密钥或 OAuth 令牌)- 环境变量(
ANTHROPIC_API_KEY、OPENAI_API_KEY等) - 回退解析器(用于
models.json中自定义提供商的密钥)
login()、logout()、setRuntimeApiKey() 和 removeRuntimeApiKey() 会在受影响提供商的缓存/内置目录、组合信息以及可用性快照在本地达成一致后再完成,不会等待远程目录刷新到最新。如果凭据已提交但本地同步失败,这些方法会以导出的 CredentialSynchronizationError 拒绝;此时应检查其 providerId、operation、credential 和 cause 字段,而不是盲目重试凭据变更。
公开的模型/认证操作以及 ModelRuntime.create({ signal }) 都接受可选的中止信号,省略时不设时限。远程目录新鲜度的截止时间策略由 SDK 应用自己掌握:
失败或超时的网络刷新不会撤销已成功的凭据操作。refresh() 会开启新的提供商代(generation),因此不会卡在之前停滞的刷新后面,过期的代此后也无法再发布结果。
系统提示词
使用 ResourceLoader 覆盖系统提示词:
工具
指定要启用哪些内置工具:
- 内置工具名称:
read、bash、powershell、edit、write、grep、find、ls - 默认内置:
read、bash、edit、write noTools: "all"禁用所有工具noTools: "builtin"禁用默认内置工具,同时保留扩展和自定义工具excludeTools在tools白名单生效之后禁用指定的内置、扩展或自定义工具名
edit 工具会返回 details.diff 供 pi 的 TUI 显示,并返回 details.patch 作为标准 unified patch 供 SDK 使用方使用。
使用自定义 cwd 的工具
当你传入自定义 cwd 时,createAgentSession() 会为该 cwd 构建所选的内置工具。
自定义工具
对于独立定义以及 customTools: [myTool] 这类数组,请使用 defineTool()。内联的 pi.registerTool({ ... }) 已经能正确推断参数类型。
通过 customTools 传入的自定义工具会与扩展注册的工具合并。ResourceLoader 加载的扩展也可以通过 pi.registerTool() 注册工具。
如果你传入了 tools,请把想要启用的每个自定义或扩展工具名都列进去,例如 tools: ["read", "bash", "my_tool"]。
扩展
扩展由 ResourceLoader 加载。DefaultResourceLoader 会从 ~/.pi/agent/extensions/、.pi/extensions/ 以及 settings.json 中配置的扩展来源发现扩展。
扩展可以注册工具、订阅事件、添加命令等。完整 API 请参见 extensions.md。
具名内联扩展: 默认情况下,内联工厂在启动时的扩展列表中显示为 <inline:1>、<inline:2> 等。要改为显示一个描述性名称,可以这样包装工厂函数:
这样就会显示为 <inline:my-provider> 而不是 <inline:1>。为了向后兼容,裸工厂函数仍然可用。
事件总线: 扩展之间可以通过 pi.events 通信。如果需要从外部发送或监听事件,可以向 DefaultResourceLoader 传入一个共享的 eventBus:
技能
上下文文件
斜杠命令
会话管理
会话采用树状结构,通过 id/parentId 相互链接,支持原地分支。
SessionManager 树 API:
设置管理
静态工厂:
SettingsManager.create(cwd?, agentDir?)- 从文件加载SettingsManager.inMemory(settings?)- 无文件 I/O
项目级设置:
设置从两个位置加载并合并:
- 全局:
~/.pi/agent/settings.json - 项目:
<cwd>/.pi/settings.json
项目设置覆盖全局设置,嵌套对象按键合并。setter 默认修改全局设置。
持久化与错误处理语义:
- 设置的 getter/setter 对内存中的状态是同步的。
- setter 会异步地把持久化写入排入队列。
- 当需要一个持久化边界时(例如进程退出前,或测试中断言文件内容之前),请调用
await settingsManager.flush()。 SettingsManager不会打印设置 I/O 错误。请使用settingsManager.drainErrors()并在你的应用层上报。
ResourceLoader
使用 DefaultResourceLoader 发现扩展、技能、提示词模板、主题和上下文文件。
返回值
createAgentSession() 返回:
完整示例
运行模式
SDK 导出了一组运行模式工具,用于在 createAgentSession() 之上构建自定义界面:
InteractiveMode
完整的 TUI 交互模式,带编辑器、聊天历史和所有内置命令:
runPrintMode
单次模式:发送提示词、输出结果、退出:
runRpcMode
用于子进程集成的 JSON-RPC 模式:
JSON 协议请参见 RPC 文档。
RPC 模式替代方案
如果要做基于子进程的集成,又不想基于 SDK 构建,可以直接使用 CLI:
JSON 协议请参见 RPC 文档。
以下情况优先选择 SDK:
- 需要类型安全
- 你在同一个 Node.js 进程中
- 需要直接访问智能体状态
- 想以编程方式定制工具/扩展
以下情况优先选择 RPC 模式:
- 从其他语言集成
- 需要进程隔离
- 在构建与语言无关的客户端
导出
主入口导出:
关于扩展类型,完整 API 请参见 extensions.md。
21 RPC 模式
RPC 模式让你能够通过 stdin/stdout 上的 JSON 协议以无头(headless)方式运行编码智能体。它适合把智能体嵌入其他应用、IDE 或自定义 UI。
Node.js/TypeScript 用户请注意:如果你在构建 Node.js 应用,可以考虑直接使用 @earendil-works/pi-coding-agent 中的 AgentSession,而不是启动子进程。API 参见 src/core/agent-session.ts。基于子进程的 TypeScript 客户端参见 src/modes/rpc/rpc-client.ts。
启动 RPC 模式
常用选项:
--provider <name>:设置 LLM 提供商(anthropic、openai、google 等)--model <pattern>:模型模式或 ID(支持provider/id以及可选的:<thinking>)--name <name>/-n <name>:启动时设置会话显示名称--no-session:禁用会话持久化--session-dir <path>:自定义会话存储目录
协议概览
- 命令:发送到 stdin 的 JSON 对象,每行一个
- 响应:带有
type: "response"的 JSON 对象,表示命令执行成功或失败 - 事件:以 JSON 行形式流式输出到 stdout 的智能体事件
所有命令都支持可选的 id 字段,用于请求/响应关联。如果提供了 id,对应的响应会包含相同的 id。bash_execution_update 事件也会包含其来源 bash 命令的 id。
分帧
RPC 模式采用严格的 JSONL 语义,仅以 LF(\n)作为记录分隔符。
这一点对客户端很重要:
- 只按
\n拆分记录 - 接受可选的
\r\n输入,方式是去掉末尾的\r - 不要使用会把 Unicode 分隔符当作换行符的通用行读取器
尤其要注意,Node 的 readline 并不符合 RPC 模式协议的要求,因为它还会在 U+2028 和 U+2029 处拆分,而这两个字符在 JSON 字符串中是合法的。
命令
发送提示
prompt
向智能体发送一条用户提示。命令响应在提示被接受、入队或处理完毕后发出;接受之后,事件仍会继续异步流式输出。
带图片:
流式输出期间:如果智能体已在流式输出中,你必须指定 streamingBehavior 才能把消息入队:
"steer":在智能体运行期间把消息加入队列。当前助手回合执行完其工具调用之后、下一次 LLM 调用之前,消息会被投递。"followUp":等到智能体完成。只有在智能体停止后,消息才会被投递。
如果智能体正在流式输出而未指定 streamingBehavior,命令会返回错误。
扩展命令:如果消息是扩展命令(例如 /mycommand),即使在流式输出期间也会立即执行。扩展命令通过 pi.sendMessage() 自行管理其 LLM 交互。
输入展开:技能命令(/skill:name)和提示词模板(/template)会在发送/入队之前先被展开。
响应:
success: true 表示提示已被接受、入队或立即处理;success: false 表示提示在接受之前即被拒绝。接受之后的失败会通过正常的事件和消息流报告,而不会针对同一请求 id 再发出一个 response。
images 字段可选。每张图片采用 ImageContent 格式:{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}。
steer
在智能体运行期间把一条转向消息入队。当前助手回合执行完其工具调用之后、下一次 LLM 调用之前,消息会被投递。技能命令和提示词模板会被展开。不允许扩展命令(请改用 prompt)。
带图片:
images 字段可选。每张图片采用 ImageContent 格式(与 prompt 相同)。
响应:
关于如何控制转向消息的处理方式,参见 set_steering_mode。
follow_up
把一条后续消息入队,待智能体完成后再处理。只有当智能体不再有工具调用或转向消息时,消息才会被投递。技能命令和提示词模板会被展开。不允许扩展命令(请改用 prompt)。
带图片:
images 字段可选。每张图片采用 ImageContent 格式(与 prompt 相同)。
响应:
关于如何控制后续消息的处理方式,参见 set_follow_up_mode。
abort
中止当前操作,并等待会话进入空闲状态后再返回响应。
响应:
clear_queue
移除已排队的转向和后续消息,并返回它们的文本。
响应:
要实现交互式的 Esc 行为,可以先发送 clear_queue 再发送 abort,然后把返回的文本恢复到客户端编辑器里。若排队消息仍保留在会话中,abort 会继续处理它们。
new_session
开启一个全新会话。该操作可以被 session_before_switch 扩展事件处理器取消。
带可选的父会话跟踪:
响应:
如果被扩展取消:
状态
get_state
获取当前会话状态。
响应:
model 字段是完整的 Model 对象或 null。sessionName 字段是通过 set_session_name 设置的显示名称,未设置时省略。
get_messages
获取会话中的所有消息。
响应:
消息为 AgentMessage 对象(参见消息类型)。
模型
set_model
切换到特定模型。
响应包含完整的 Model 对象:
cycle_model
切换到下一个可用模型。如果只有一个可用模型,则返回 null 数据。
响应:
model 字段是完整的 Model 对象。
get_available_models
列出所有已配置的模型。
响应包含一个由完整 Model 对象组成的数组:
思考
set_thinking_level
为支持该功能的模型设置推理/思考级别。
级别:"off"、"minimal"、"low"、"medium"、"high"、"xhigh"、"max"
"xhigh" 和 "max" 仅在所选模型支持时才会暴露。部分模型(包括 GPT-5.6)两者都支持。
响应:
cycle_thinking_level
在可用的思考级别之间循环切换。模型不支持思考时返回 null 数据。
响应:
get_available_thinking_levels
列出当前模型支持的思考级别。不支持推理的模型返回 ["off"]。
响应:
队列模式
set_steering_mode
控制转向消息(来自 steer)的投递方式。
模式:
"all":当前助手回合执行完其工具调用之后,投递所有转向消息"one-at-a-time":每个完成的助手回合投递一条转向消息(默认)
响应:
set_follow_up_mode
控制后续消息(来自 follow_up)的投递方式。
模式:
"all":智能体完成时投递所有后续消息"one-at-a-time":每次智能体完成时投递一条后续消息(默认)
响应:
上下文压缩
compact
手动压缩会话上下文,以减少 token 用量。
使用自定义指令:
响应:
estimatedTokensAfter 是对压缩后立即重建的消息上下文的启发式估算,并不是提供商给出的精确 token 计数。usage 报告的是生成摘要的那一次或多次 LLM 调用,自定义压缩处理器可能会省略该字段。
set_auto_compaction
在上下文即将占满时启用或禁用自动压缩。
响应:
重试
set_auto_retry
在出现瞬时错误(过载、限流、5xx)时启用或禁用自动重试。
响应:
abort_retry
中止正在进行的重试(取消等待并停止重试)。
响应:
Bash
bash
执行 shell 命令并把输出加入会话上下文。命令运行期间,输出以 bash_execution_update 事件流式输出;响应中包含最终结果。
包含 id 可以把流式的 bash_execution_update 事件与该命令关联起来。
响应:
如果输出被截断,还会包含 fullOutputPath:
bash 结果如何进入 LLM:
bash 命令立即执行并返回一个 BashResult。在内部,会创建一个 BashExecutionMessage 并存入智能体的消息状态。
当下一个 prompt 命令发送时,所有消息(包括 BashExecutionMessage)都会在发送给 LLM 之前先被转换。BashExecutionMessage 会被转换成如下格式的 UserMessage:
这意味着:
- bash 输出在下一次 prompt 时才进入 LLM 上下文,而不是立即进入
- 在一次 prompt 之前可以执行多条 bash 命令;所有输出都会被包含
abort_bash
中止正在运行的 bash 命令。
响应:
会话
get_session_stats
获取 token 用量、成本统计以及当前上下文窗口的使用情况。
响应:
tokens 和 cost 涵盖整个会话中的助手消息、工具报告的用量,以及压缩和分支摘要的生成。contextUsage 包含用于压缩和底部状态栏显示的实际当前上下文窗口估算。
当没有可用模型或上下文窗口时,contextUsage 会被省略。刚完成压缩后,直到新的压缩后助手响应提供有效用量数据之前,contextUsage.tokens 和 contextUsage.percent 均为 null。
export_html
将会话导出为 HTML 文件。
使用自定义路径:
响应:
switch_session
加载另一个会话文件。该操作可以被 session_before_switch 扩展事件处理器取消。
响应:
如果切换被扩展取消:
fork
从活动分支上较早的某条用户消息处创建新的分叉。该操作可以被 session_before_fork 扩展事件处理器取消。返回作为分叉起点的那条消息的文本。
响应:
如果分叉被扩展取消:
clone
把当前活动分支在当前位置复制为一个新会话。该操作可以被 session_before_fork 扩展事件处理器取消。
响应:
如果克隆被扩展取消:
get_fork_messages
获取可用于分叉的用户消息。
响应:
get_entries
按追加顺序获取所有会话条目(不含会话头)。会话是一棵具有稳定 id 的只追加条目树,因此条目 id 可以充当持久游标:把你已见过的最后一个条目 id 作为 since 传入,即可只获取严格位于其后的条目,即使客户端重启也一样有效。与 get_messages 不同,它包含压缩前的历史和被放弃的分支。
使用游标:
响应:
leafId 是当前叶子条目的 id(空会话时为 null),客户端一次往返即可判断活动分支是否移动。如果 since 不匹配任何条目 id,响应为 success: false。
get_tree
以条目树的形式获取会话。每个节点为 {entry, children, label?, labelTimestamp?}。格式良好的会话只有一个根;孤儿条目(父链断裂的条目)也会作为根出现。
响应:
get_last_assistant_text
获取最后一条助手消息的文本内容。
响应:
如果没有助手消息,则返回 {"text": null}。
set_session_name
为当前会话设置显示名称。该名称会出现在会话列表中,便于识别会话。
响应:
当前会话名称可通过 get_state 的 sessionName 字段获取。要在启动 RPC 模式时设置初始名称,请为 pi --mode rpc 进程传入 --name <name> 或 -n <name>。
可用命令
get_commands
获取可用命令(扩展命令、提示词模板和技能)。这些命令可以通过 prompt 命令调用,只需加上 / 前缀。
响应:
每条命令包含:
name:命令名(用/name调用)description:人类可读的描述(扩展命令可选)source:命令的种类:"extension":由扩展中的pi.registerCommand()注册"prompt":从提示词模板.md文件加载"skill":从技能目录加载(名称带skill:前缀)
location:加载来源(可选,扩展没有该字段):"user":用户级(~/.pi/agent/)"project":项目级(./.pi/agent/)"path":通过 CLI 或设置显式指定的路径
path:命令源文件的绝对路径(可选)
注意:不包含内置 TUI 命令(/settings、/hotkeys 等)。它们只在交互模式下处理,如果通过 prompt 发送并不会执行。
事件
事件在智能体运行期间以 JSON 行的形式流式输出到 stdout。事件通常不包含 id 字段;当来源 bash 命令提供了 id 时,bash_execution_update 会包含该 id。
事件类型
| 事件 | 说明 |
|---|---|
agent_start | 智能体开始处理 |
agent_end | 一次低层智能体运行完成(之后可能仍有重试、压缩或排队的延续) |
agent_settled | 智能体运行已完全稳定;不再有自动重试、压缩重试或排队的延续 |
turn_start | 新回合开始 |
turn_end | 回合完成(包含助手消息和工具结果) |
message_start | 消息开始 |
message_update | 流式更新(文本/思考/工具调用增量) |
message_end | 消息完成 |
bash_execution_update | 直接 RPC bash 命令的输出块 |
tool_execution_start | 工具开始执行 |
tool_execution_update | 工具执行进度(流式输出) |
tool_execution_end | 工具执行完成 |
queue_update | 待处理的转向/后续队列发生变化 |
compaction_start | 压缩开始 |
compaction_end | 压缩完成 |
auto_retry_start | 自动重试开始(发生在瞬时错误之后) |
auto_retry_end | 自动重试结束(成功或最终失败) |
summarization_retry_scheduled | 已为瞬时的压缩或分支摘要生成错误安排重试 |
summarization_retry_attempt_start | 重试的摘要生成请求开始 |
summarization_retry_finished | 摘要生成重试循环结束 |
extension_error | 扩展抛出了错误 |
agent_start
当智能体开始处理一条提示时发出。
agent_end
当一次低层智能体运行完成时发出。包含这次运行期间生成的所有消息。如果 willRetry 为 true,随后会进行自动重试。
agent_settled
在完整的会话级运行稳定之后发出。此时,pi 不会再通过重试、压缩重试或排队的后续消息自动继续。
turn_start / turn_end
一个回合(turn)由一次助手响应及其产生的工具调用和结果组成。
message_start / message_end
在消息开始和完成时发出。message 字段包含一个 AgentMessage。
message_update(流式)
在助手消息流式输出期间发出。包含一个增量(delta)事件,不含累积的消息快照。
assistantMessageEvent 字段包含以下增量类型之一:
| 类型 | 说明 |
|---|---|
text_start | 文本内容块开始 |
text_delta | 文本内容块 |
text_end | 文本内容块结束 |
thinking_start | 思考块开始 |
thinking_delta | 思考内容块 |
thinking_end | 思考块结束 |
toolcall_start | 工具调用开始(包含 id 和 toolName) |
toolcall_delta | 工具调用参数块 |
toolcall_end | 工具调用结束(包含完整的 toolCall 对象) |
流式输出文本响应的示例:
顶层的 usage 字段包含提供商报告的最新累计用量。如果提供商在流式输出期间不报告用量,该字段可能一直为零,直到完成。
启动工具调用的示例:
message_update 有意省略了以往的累积 message 字段和 assistantMessageEvent.partial。需要实时部分消息的客户端必须利用 contentIndex,从 message_start 及后续事件自行组装。应以 message_end.message 为准。对于工具调用,toolcall_start 会提供调用 id 和 toolName;请为参数缓冲 toolcall_delta.delta。toolcall_end.toolCall 包含已完成的调用。
bash_execution_update
直接 bash 命令的每个输出块发出一次。id 与命令的 id 匹配,客户端可据此将输出关联到正确的命令。
命令运行期间,事件会流式输出全部输出,即使最终 bash 响应的 output 被截断。
tool_execution_start / tool_execution_update / tool_execution_end
在工具开始执行、流式输出进度以及执行完成时发出。
执行期间,tool_execution_update 事件会流式输出部分结果(例如随到随发的 bash 输出):
完成时:
使用 toolCallId 关联事件。tool_execution_update 中的 partialResult 包含到目前为止累积的输出(而不只是增量),客户端可以在每次更新时直接替换自己的显示。
queue_update
每当待处理的转向或后续队列发生变化时发出。
compaction_start / compaction_end
在压缩运行时发出,无论是手动还是自动。
reason 字段为 "manual"、"threshold" 或 "overflow"。
如果 reason 为 "overflow" 且压缩成功,则 willRetry 为 true,智能体会自动重试该提示。
如果压缩被中止,result 为 null 且 aborted 为 true。
如果压缩失败(例如 API 配额超限),result 为 null、aborted 为 false,errorMessage 包含错误描述。
auto_retry_start / auto_retry_end
在瞬时错误(过载、限流、5xx)之后触发自动重试时发出。
最终失败(超过最大重试次数)时:
summarization_retry_scheduled / summarization_retry_attempt_start / summarization_retry_finished
当压缩或分支摘要生成在瞬时提供商错误后重试时发出。这些事件使用与助手回合自动重试相同的重试设置。
对于分支摘要,source 为 "branchSummary",且没有 reason 字段。
extension_error
当扩展抛出错误时发出。
扩展 UI 协议
扩展可以通过 ctx.ui.select()、ctx.ui.confirm() 等方法请求用户交互。在 RPC 模式中,这些调用会被转换为一个构建在基础命令/事件流之上的请求/响应子协议。
扩展 UI 方法分为两类:
- 对话框方法(
select、confirm、input、editor):在 stdout 上发出一个extension_ui_request,并阻塞等待客户端在 stdin 上发回带有匹配id的extension_ui_response。 - 即发即弃方法(
notify、setStatus、setWidget、setTitle、set_editor_text):在 stdout 上发出extension_ui_request,但不期待响应。客户端可以显示这些信息,也可以忽略。
如果对话框方法带有 timeout 字段,超时到期时智能体一侧会自动以默认值解决该请求。客户端不需要跟踪超时。
一些 ExtensionUIContext 方法在 RPC 模式中不受支持或被降级,因为它们需要直接访问 TUI:
custom()返回undefinedsetWorkingMessage()、setWorkingIndicator()、setFooter()、setHeader()、setEditorComponent()、setToolsExpanded()是空操作getEditorText()返回""getToolsExpanded()返回falsepasteToEditor()委托给setEditorText()(不做粘贴/收起处理)getAllThemes()返回[]getTheme()返回undefinedsetTheme()返回{ success: false, error: "..." }
注意:RPC 模式下 ctx.mode 为 "rpc",且 ctx.hasUI 为 true,因为对话框方法和即发即弃方法都能通过扩展 UI 子协议正常工作。要保护 custom() 这类需要真实终端的 TUI 专属功能,请使用 ctx.mode === "tui" 判断。
扩展 UI 请求(stdout)
所有请求都带有 type: "extension_ui_request"、唯一的 id 和 method 字段。
select
提示用户从列表中选择。带 timeout 字段的对话框方法会包含以毫秒为单位的超时值;如果客户端没有及时响应,智能体会自动以 undefined 作为结果。
预期响应:带有 value(选中的选项字符串)或 cancelled: true 的 extension_ui_response。
confirm
提示用户进行是/否确认。
预期响应:带有 confirmed: true/false 或 cancelled: true 的 extension_ui_response。
input
提示用户输入自由格式的文本。
预期响应:带有 value(输入的文本)或 cancelled: true 的 extension_ui_response。
editor
打开一个多行文本编辑器,可选预填内容。
预期响应:带有 value(编辑后的文本)或 cancelled: true 的 extension_ui_response。
notify
显示一条通知。即发即弃,不期待响应。
notifyType 字段为 "info"、"warning" 或 "error"。省略时默认为 "info"。
setStatus
在底部/状态栏中设置或清除一个状态项。即发即弃。
发送 statusText: undefined(或省略该字段)即可清除该键的状态项。
setWidget
设置或清除显示在编辑器上方或下方的小组件(widget,多行文本块)。即发即弃。
发送 widgetLines: undefined(或省略该字段)即可清除小组件。widgetPlacement 字段为 "aboveEditor"(默认)或 "belowEditor"。RPC 模式只支持字符串数组;组件工厂会被忽略。
setTitle
设置终端窗口/标签页标题。即发即弃。
set_editor_text
在输入编辑器中设置文本。即发即弃。
扩展 UI 响应(stdin)
只有对话框方法(select、confirm、input、editor)需要发送响应。id 必须与请求匹配。
值响应(select、input、editor)
确认响应(confirm)
取消响应(任意对话框)
关闭任意对话框方法。扩展会收到 undefined(select/input/editor)或 false(confirm)。
错误处理
失败的命令会返回 success: false 的响应:
解析错误:
类型
源码文件:
packages/ai/src/types.ts-Model、UserMessage、AssistantMessage、ToolResultMessagepackages/agent/src/types.ts-AgentMessage、AgentEventsrc/core/messages.ts-BashExecutionMessagesrc/modes/json-event.ts-JsonAgentSessionEventsrc/modes/rpc/rpc-types.ts- RPC 命令/响应类型、扩展 UI 请求/响应类型
Model
UserMessage
content 字段可以是字符串,也可以是 TextContent/ImageContent 块的数组。
AssistantMessage
停止原因:"stop"、"length"、"toolUse"、"error"、"aborted"
ToolResultMessage
usage 为可选字段,报告由工具执行的嵌套 LLM 工作。存在时,它会计入会话的 token 和成本总计。
BashExecutionMessage
由 bash RPC 命令创建(而不是由 LLM 工具调用创建):
Attachment
示例:基础客户端(Python)
示例:交互式客户端(Node.js)
完整的交互式示例参见 test/rpc-example.ts,类型化客户端实现参见 src/modes/rpc/rpc-client.ts。
处理扩展 UI 协议的完整示例参见 examples/rpc-extension-ui.ts,它与扩展 examples/extensions/rpc-demo.ts 配套使用。
22 JSON 事件流模式
该模式将所有会话事件以 JSON 行的形式输出到 stdout。适合将 pi 集成到其他工具或自定义界面。
事件类型
线上传输的事件使用 JsonAgentSessionEvent。它与
AgentSessionEvent
一致,区别在于流式消息更新会省略累积快照:
queue_update 会在待处理的转向队列和后续任务队列发生变化时输出完整队列。compaction_start 和 compaction_end 同时覆盖手动和自动上下文压缩。
其他基础事件来自
AgentEvent:
消息类型
基础消息定义于 packages/ai/src/types.ts:
UserMessage(第 134 行)AssistantMessage(第 140 行)ToolResultMessage(第 152 行)
扩展消息定义于 packages/coding-agent/src/core/messages.ts:
BashExecutionMessage(第 29 行)CustomMessage(第 46 行)BranchSummaryMessage(第 55 行)CompactionSummaryMessage(第 62 行)
输出格式
每一行都是一个 JSON 对象。第一行是会话头:
随后是按发生顺序输出的事件:
message_update 记录仅包含增量。它们同时省略了累积的 message 字段和
assistantMessageEvent.partial,以保持流大小的线性增长。顶层的 usage 字段包含
提供商报告的最新累计用量;如果提供商只在完成时上报用量,该字段可能一直为零。如有需要,
可以使用 contentIndex 和 delta 拼装实时的文本、思考内容或工具调用参数。toolcall_start
事件还包含大小固定的 id 和 toolName 字段。message_end 包含最终权威的消息。
示例
23 TUI 组件
pi 可以帮你创建 TUI 组件。可以请它为你的使用场景构建一个。
扩展和自定义工具可以渲染自定义 TUI 组件,构建交互式用户界面。本页介绍组件系统以及可用的小组件。
组件接口
所有组件都要实现:
| 方法 | 说明 |
|---|---|
render(width) | 返回字符串数组(每行一个)。每行不得超过 width。 |
handleInput?(data) | 在组件获得焦点时接收键盘输入。 |
handleMouse?(event) | 在全屏模式下接收归一化的指针输入。 |
wantsKeyRelease? | 若为 true,组件会接收按键释放事件(Kitty 协议)。默认:false。 |
invalidate() | 清除缓存的渲染状态。在主题变更时调用。 |
TUI 会在每一行渲染输出的末尾附加完整的 SGR 重置和 OSC 8 重置。样式不会跨行保留。如果你要输出带样式的多行文本,请逐行重新应用样式,或使用 wrapTextWithAnsi(),让折行后的每一行都保留样式。
Focusable 接口(IME 支持)
显示文本光标并需要 IME(输入法编辑器)支持的组件应当实现 Focusable 接口:
当 Focusable 组件获得焦点时,TUI 会:
- 将组件的
focused设置为 true - 扫描渲染输出中的
CURSOR_MARKER(一个零宽 APC 转义序列) - 将硬件终端光标定位到该位置
- 仅在启用了
showHardwareCursor时才显示硬件光标
光标默认保持隐藏。这样既能继续渲染伪造的光标,又能为那些在光标隐藏时仍跟踪 IME 候选窗口的终端定位硬件光标。某些终端要求 IME 定位必须有可见的硬件光标;可以通过渲染器的 showHardwareCursor 构造参数或 setShowHardwareCursor(true) 来启用。pi 在创建渲染器之前还会把 PI_HARDWARE_CURSOR=1 映射到该设置。内置的 Editor 和 Input 组件已经实现了该接口。
内嵌输入组件的容器
当容器组件(对话框、选择器等)包含 Input 或 Editor 子组件时,容器必须实现 Focusable,并把焦点状态传递给子组件。否则,硬件光标将无法为 IME 输入正确定位。
如果没有这个传递,使用输入法(中文、日文、韩文等)输入时,候选窗口会显示在屏幕上错误的位置。
使用组件
在扩展中通过 ctx.ui.custom() 使用:
在自定义工具中通过 ctx.ui.custom() 使用:
覆盖层
覆盖层(overlay)在现有内容之上渲染组件,而不会清空屏幕。向 ctx.ui.custom() 传入 { overlay: true }:
要控制定位和尺寸,可使用 overlayOptions:
覆盖层焦点
获得焦点的可见覆盖层会在临时的非覆盖层 UI 期间保持输入所有权。如果覆盖层打开另一个未带 { overlay: true } 的 ctx.ui.custom() 组件,那么在这个替换 UI 处于活动状态期间由它接收输入;当它关闭后,获得焦点的覆盖层可以收回输入。
当某个可见覆盖层应当停止持有输入、让 TUI 回退到另一个可见的捕获型覆盖层或之前的焦点目标时,使用 handle.unfocus()。当某个特定组件应当在覆盖层保持可见的同时接收输入时,使用 handle.unfocus({ target })。传入 { target: null } 会刻意让焦点悬空,直到再次设置焦点。
覆盖层生命周期
覆盖层组件在关闭时会被销毁。不要复用旧引用——请创建新的实例:
涵盖锚点定位、边距、堆叠、响应式可见性和动画的完整示例,请参见 overlay-qa-tests.ts。
内置组件
从 @earendil-works/pi-tui 导入:
Text
带自动换行的多行文本。
Box
带内边距和背景色的容器。
Container
垂直排列子组件。
Spacer
垂直空白区域。
Markdown
渲染 markdown 并带语法高亮。
Image
在支持的终端(Kitty、iTerm2、Ghostty、WezTerm、Warp)中渲染图片。
键盘输入
使用 matchesKey() 进行按键检测:
按键标识符(使用 Key.* 可获得自动补全,也可使用字符串字面量):
- 基本按键:
Key.enter、Key.escape、Key.tab、Key.space、Key.backspace、Key.delete、Key.home、Key.end - 方向键:
Key.up、Key.down、Key.left、Key.right - 带修饰键:
Key.ctrl("c")、Key.shift("tab")、Key.alt("left")、Key.ctrlShift("p") - 字符串格式同样有效:
"enter"、"ctrl+c"、"shift+tab"、"ctrl+shift+p"
鼠标输入
全屏模式会把归一化的按下、释放、点击、移动、拖拽和滚轮事件路由给组件和覆盖层。返回 { handled: true } 可抑制默认行为,capture: true 可保留拖拽/释放所有权,focus: true 可请求键盘焦点,悬停或释放导致组件外观发生可见变化时返回 render: true。按下、点击、拖拽和滚轮事件默认会触发渲染;无实际作用的移动/释放事件则不会。
未处理的滚轮输入会滚动最近的 ScrollView;未处理的主键拖拽会保留会话记录的选区。OSC 8 链接的优先级高于父级点击区域。Input、Editor、SelectList 和 SettingsList 自带全屏鼠标行为。普通模式不会捕获鼠标输入,因为滚动回溯区由终端自己管理。
行宽
关键要求: render() 返回的每一行都不得超过 width 参数。
工具函数:
visibleWidth(str)- 获取显示宽度(忽略 ANSI 码)truncateToWidth(str, width, ellipsis?)- 截断文本,可选省略号wrapTextWithAnsi(str, width)- 保留 ANSI 码的自动换行
创建自定义组件
示例:交互式选择器
在扩展中的用法:
主题化
组件接受主题对象来进行样式设置。
在 renderCall/renderResult 中,使用 theme 参数:
前景色(theme.fg(color, text)):
| 类别 | 颜色 |
|---|---|
| 常规 | text、accent、muted、dim、searchMatchText |
| 状态 | success、error、warning |
| 边框 | border、borderAccent、borderMuted |
| 消息 | userMessageText、customMessageText、customMessageLabel |
| 工具 | toolTitle、toolOutput |
| 差异 | toolDiffAdded、toolDiffRemoved、toolDiffContext |
| Markdown | mdHeading、mdLink、mdLinkUrl、mdCode、mdCodeBlock、mdCodeBlockBorder、mdQuote、mdQuoteBorder、mdHr、mdListBullet |
| 语法 | syntaxComment、syntaxKeyword、syntaxFunction、syntaxVariable、syntaxString、syntaxNumber、syntaxType、syntaxOperator、syntaxPunctuation |
| 思考强度 | thinkingOff、thinkingMinimal、thinkingLow、thinkingMedium、thinkingHigh、thinkingXhigh、thinkingMax |
| 模式 | bashMode |
背景色(theme.bg(color, text)):
selectedBg、searchMatchBg、userMessageBg、customMessageBg、toolPendingBg、toolSuccessBg、toolErrorBg
对 Markdown,使用 getMarkdownTheme():
对自定义组件,定义你自己的主题接口:
调试日志
设置 PI_TUI_WRITE_LOG 可以捕获写入 stdout 的原始 ANSI 流。
性能
尽可能缓存渲染输出:
状态变化时调用 invalidate(),然后使用注入的 tui.requestRender() 触发重新渲染。
失效与主题变更
主题变更时,TUI 会对所有组件调用 invalidate() 来清除其缓存。组件必须正确实现 invalidate(),主题变更才能生效。
问题所在
如果组件把主题颜色预先烘焙进字符串(通过 theme.fg()、theme.bg() 等)并缓存下来,那么缓存字符串中就包含旧主题的 ANSI 转义码。如果组件把带主题的内容单独存储,仅清除渲染缓存是不够的。
错误做法(主题颜色不会更新):
解决方案
用主题颜色构建内容的组件,必须在 invalidate() 被调用时重建该内容:
模式:失效时重建
对于内容复杂的组件:
何时需要该模式
以下情况需要该模式:
- 预先烘焙主题颜色 - 使用
theme.fg()或theme.bg()创建带样式的字符串,并存储在子组件中 - 语法高亮 - 使用
highlightCode(),它会应用基于主题的语法颜色 - 复杂布局 - 构建嵌入了主题颜色的子组件树
以下情况不需要该模式:
- 使用主题回调 - 传入在渲染期间调用的函数,如
(text) => theme.fg("accent", text) - 简单容器 - 只是分组其他组件,不添加带主题的内容
- 无状态渲染 - 在每次
render()调用中重新计算带主题的输出(无缓存)
常用模式
这些模式覆盖了扩展中最常见的 UI 需求。请直接复制这些模式,而不是从零开始构建。
模式 1:选择对话框(SelectList)
用于让用户从选项列表中挑选。使用 @earendil-works/pi-tui 的 SelectList,配合 DynamicBorder 做边框。
模式 2:可取消的异步操作(BorderedLoader)
用于耗时且应可取消的操作。BorderedLoader 会显示一个 spinner,并处理 escape 取消。
示例: qna.ts、handoff.ts
模式 3:设置与开关(SettingsList)
用于切换多项设置。使用 @earendil-works/pi-tui 的 SettingsList,配合 getSettingsListTheme()。
示例: tools.ts
模式 4:持久状态指示器
在页脚显示跨渲染持久保留的状态。适合用作模式指示器。
示例: status-line.ts、plan-mode/index.ts、preset.ts
模式 4b:自定义工作指示器
自定义 pi 流式输出响应期间显示的内联工作指示器。
这只影响普通的流式工作指示器。上下文压缩和重试的加载动画保持其内置样式。自定义帧会原样渲染,因此扩展需要在需要时自行添加颜色。
模式 5:编辑器上方/下方的小组件
在输入编辑器上方或下方显示持久内容。适合待办列表、进度展示。
模式 6:自定义页脚
替换页脚。footerData 暴露了扩展无法通过其他途径访问的数据。
token 统计信息可通过 ctx.sessionManager.getBranch() 和 ctx.model 获取。
示例: custom-footer.ts
模式 7:自定义编辑器(vim 模式等)
用自定义实现替换主输入编辑器。适合模态编辑(vim)、不同的快捷键方案(emacs)或特殊的输入处理。
要点:
- 继承
CustomEditor(而不是基础Editor),以获得应用快捷键(escape 中止、ctrl+d 退出、模型切换等) - 对你不处理的按键,调用
super.handleInput(data) - 状态 spinner:自定义编辑器默认保留独立的状态行。传入
{ embedWorkingStatus: true }作为CustomEditor构造函数的第四个参数,可将工作、上下文压缩、分支摘要和重试的 spinner 嵌入编辑器边框。 - 工厂模式:
setEditorComponent接收一个工厂函数,其参数为tui、theme和keybindings - 传入
undefined可恢复默认编辑器:ctx.ui.setEditorComponent(undefined)
示例: modal-editor.ts
关键规则
始终使用回调传入的 theme - 不要直接导入主题。使用
ctx.ui.custom((tui, theme, keybindings, done) => ...)回调中的theme。始终为 DynamicBorder 的颜色参数标注类型 - 写
(s: string) => theme.fg("accent", s),而不是(s) => theme.fg("accent", s)。状态变化后调用 tui.requestRender() - 在
handleInput中更新状态后调用tui.requestRender()。返回三方法对象 - 自定义组件需要
{ render, invalidate, handleInput }。使用现有组件 -
SelectList、SettingsList、BorderedLoader已覆盖 90% 的场景。不要重复造轮子。
示例
- 选择 UI:examples/extensions/preset.ts - 带 DynamicBorder 边框的 SelectList
- 可取消的异步操作:examples/extensions/qna.ts - 用于 LLM 调用的 BorderedLoader
- 设置开关:examples/extensions/tools.ts - 用于工具启用/禁用的 SettingsList
- 状态指示器:examples/extensions/plan-mode/index.ts - setStatus 和 setWidget
- 工作指示器:examples/extensions/working-indicator.ts - setWorkingIndicator
- 自定义页脚:examples/extensions/custom-footer.ts - 带统计信息的 setFooter
- 自定义编辑器:examples/extensions/modal-editor.ts - 类 vim 的模态编辑
- 贪吃蛇游戏:examples/extensions/snake.ts - 完整游戏,含键盘输入和游戏循环
- 自定义工具渲染:examples/extensions/todo.ts - renderCall 和 renderResult
24 Windows 配置
Pi 在 Windows 上默认使用 Git Bash。查找顺序如下:
~/.pi/agent/settings.json中的自定义路径- Git Bash(
C:\Program Files\Git\bin\bash.exe) - PATH 上的
bash.exe(Cygwin、MSYS2、WSL)
对大多数用户来说,安装 Git for Windows 就足够了。
PowerShell 工具
可选的 powershell 工具在 pwsh.exe 可用时通过它运行命令,否则使用 Windows PowerShell。它会以 -NoProfile -NonInteractive -ExecutionPolicy Bypass 参数启动 PowerShell。管理员强制执行的执行策略仍然可能优先生效。
使用 defaultTools 替换面向模型的 bash 工具:
也可以同时启用两者来对比行为:
! 和 !! 编辑器命令仍然使用 Bash。
自定义 Bash 路径
25 Termux(Android)配置
Pi 可以通过 Termux 在 Android 上运行。Termux 是一个面向 Android 的终端模拟器和 Linux 环境。
前置条件
- 从 GitHub 或 F-Droid 安装 Termux(不要从 Google Play 安装,那个版本已弃用)
- 从 GitHub 或 F-Droid 安装 Termux:API,用于剪贴板和其他设备集成
安装
剪贴板支持
在 Termux 中运行时,剪贴板操作使用 termux-clipboard-set 和 termux-clipboard-get。这些功能需要安装 Termux:API 应用。
Termux 不支持图片剪贴板(ctrl+v 图片粘贴功能无法使用)。
Termux 的 AGENTS.md 示例
创建 ~/.pi/agent/AGENTS.md,帮助智能体了解 Termux 环境:
限制
- 没有图片剪贴板:Termux 剪贴板 API 只支持文本
- 存储访问:要访问
/storage/emulated/0中的文件(Downloads 等),请先运行一次termux-setup-storage授予权限
故障排除
剪贴板不可用
确保两个应用都已安装:
- Termux(来自 GitHub 或 F-Droid)
- Termux:API(来自 GitHub 或 F-Droid)
然后安装 CLI 工具:
共享存储权限被拒绝
运行一次以下命令以授予存储权限:
Node.js 安装问题
如果 npm 安装失败,请尝试清除缓存:
26 tmux 配置
Pi 可以在 tmux 中运行,但 tmux 默认会剥离某些按键的修饰键信息。如果不进行配置,Shift+Enter 和 Ctrl+Enter 通常无法与普通的 Enter 区分开。
推荐配置
在 ~/.tmux.conf 中添加:
然后完全重启 tmux:
当 Kitty 键盘协议不可用时,Pi 会自动请求扩展按键上报。设置 extended-keys-format csi-u 后,tmux 会以 CSI-u 格式转发带修饰键的按键,这是最可靠的配置。extended-keys-format 选项要求 tmux 3.5 或更高版本。
为什么推荐使用 csi-u
如果只设置:
tmux 会默认使用 extended-keys-format xterm。当应用程序请求扩展按键上报时,带修饰键的按键会以 xterm modifyOtherKeys 格式转发,例如:
Ctrl+C→\x1b[27;5;99~Ctrl+D→\x1b[27;5;100~Ctrl+Enter→\x1b[27;5;13~
设置 extended-keys-format csi-u 后,同样的按键会以如下方式转发:
Ctrl+C→\x1b[99;5uCtrl+D→\x1b[100;5uCtrl+Enter→\x1b[13;5u
Pi 支持这两种格式,但在 tmux 环境下推荐使用 csi-u。
这解决了什么问题
如果没有 tmux 扩展按键,带修饰键的 Enter 会退化为传统序列:
| 按键 | 未启用扩展按键 | 使用 csi-u |
|---|---|---|
| Enter | \r | \r |
| Shift+Enter | \r | \x1b[13;2u |
| Ctrl+Enter | \r | \x1b[13;5u |
| Alt/Option+Enter | \x1b\r | \x1b[13;3u |
这会影响默认快捷键(Enter 提交、Shift+Enter 换行),以及所有使用带修饰键 Enter 的自定义快捷键。
环境要求
- tmux 3.5 或更高版本(用于
extended-keys-format csi-u,运行tmux -V可检查版本) - 支持扩展按键的终端模拟器(Ghostty、Kitty、iTerm2、WezTerm、Windows Terminal)
对于 tmux 3.2 到 3.4,请省略 extended-keys-format csi-u;Pi 仍然支持 tmux 默认的 xterm modifyOtherKeys 格式。
27 终端配置
Pi 使用 Kitty 键盘协议来可靠地检测修饰键。大多数现代终端都支持该协议,但有些需要配置。
能力覆盖
Pi 会自动检测 OSC 8 超链接、内联图片协议和真彩色。如果在终端代理或多路复用器背后检测失败,可以使用这些高级覆盖项:
| 能力 | 环境变量 | JSON 设置 |
|---|---|---|
| OSC 8 超链接 | PI_HYPERLINKS=1|0|auto | terminal.hyperlinks: true|false|"auto" |
| 内联图片 | PI_IMAGE_PROTOCOL=kitty|iterm2|none|auto | terminal.images: "kitty"|"iterm2"|false|"auto" |
| 真彩色 | PI_TRUE_COLOR=1|0|auto | terminal.trueColor: true|false|"auto" |
设置的优先级高于环境变量;不设置或设为 auto 则保持自动检测。只强制启用完整终端链路都支持的能力,因为不支持的转义序列可能破坏渲染。
Kitty
开箱即用。
iTerm2
常规 TUI 模式
开箱即用。
全屏 TUI 模式
Pi 接管整个视口,因此 iTerm2 会发送鼠标滚轮报告,而不是滚动它原生的回滚缓冲区。在 iTerm2 默认的快速触控板行为下,这些报告可能丢失大部分加速滚轮增量,使全屏滚动比常规滚动慢得多。
如果在全屏模式下,快速的滚轮手势每次只能移动大约一行:
- 打开 iTerm2 → Settings → Advanced。
- 搜索 Trackpad scrolls fast? 并把它设为 No。
这是对整个 iTerm2 生效的折中方案,也可能改变原生触控板滚动。底层行为见 iTerm2 issue 9619。
Apple Terminal
可用时,Pi 会启用增强按键上报。如果 Terminal.app 仍为 Shift+Enter 发送普通回车,pi 会使用本地 macOS 修饰键回退,把该回车当作 Shift+Enter 处理。
这个回退只在 pi 与 Terminal.app 运行在同一台 Mac 上时才有效。它无法通过远程 SSH 检测本地键盘。
Ghostty
在你的 Ghostty 配置中添加(macOS 上是 ~/Library/Application Support/com.mitchellh.ghostty/config,Linux 上是 ~/.config/ghostty/config):
较旧版本的 Claude Code 可能添加过这条 Ghostty 映射:
该映射会发送原始换行字节。在 pi 内部,它与 Ctrl+J 无法区分,因此 tmux 和 pi 都看不到真正的 shift+enter 键事件。
如果你添加这条映射仅仅是为了 Claude Code 2.x 或更新版本,可以把它删除;除非你想在 tmux 中使用 Claude Code——那种情况下仍然需要这条 Ghostty 映射。
Pi 已将 Ctrl+J 绑定为默认的换行别名,因此 Shift+Enter 在 tmux 中可以经由该重映射继续工作,无需额外的 pi 配置。
全屏 TUI 模式
在全屏模式下,链接仍然可以点击,但当 pi 捕获鼠标输入时,Ghostty 不会显示悬停下划线,也不会显示左下角的 URL 预览。在 macOS 上按住 Shift+Command、在 Linux 上按住 Shift+Ctrl,可以使用 Ghostty 的原生链接处理。
WezTerm
WezTerm 通常可以通过 xterm modifyOtherKeys 开箱即用地支持 Shift+Enter。要显式使用 Kitty 键盘协议,请创建 ~/.wezterm.lua:
在 macOS 上,WezTerm 默认把 Option+Enter 绑定为全屏。要把 Option+Enter 用作 pi 的后续消息排队,请添加这个按键覆盖:
如果你已经有 config.keys 表,把这个条目加进去即可。
在 WSL 上,WezTerm 可能需要可见的硬件光标来定位输入法候选窗口。如果 CJK 输入法候选没有跟随文本光标,请在运行 pi 前设置 PI_HARDWARE_CURSOR=1,或在设置中把 showHardwareCursor 设为 true。
Alacritty
Alacritty 通常可以开箱即用地支持 Shift+Enter。在 macOS 上,Option+Enter 到达时可能变成普通的 Enter。要把 Option+Enter 用作 pi 的后续消息排队,请在 ~/.config/alacritty/alacritty.toml 中添加:
修改配置后请重启 Alacritty。
VS Code(集成终端)
VS Code 1.109.5 及更新版本默认在集成终端中启用 Kitty 键盘协议,因此 Shift+Enter 应当开箱即用。
早于 1.109.5 的 VS Code 版本需要为 Shift+Enter 配置显式的终端按键绑定。
keybindings.json 的位置:
- macOS:
~/Library/Application Support/Code/User/keybindings.json - Linux:
~/.config/Code/User/keybindings.json - Windows:
%APPDATA%\\Code\\User\\keybindings.json
在 keybindings.json 中添加:
Zed(集成终端)
把这些按键绑定添加到你的 Zed keymap.json:
Windows Terminal
Pi 在原生 Windows 或 WSL 上运行时使用 Windows 风格的快捷键:
Alt+V粘贴图片或剪贴板文本。Ctrl+F在全屏模式下搜索会话记录,Ctrl+Up/Ctrl+Down在标记的消息之间跳转。Alt+P切换到上一个模型。Ctrl+Z在原生 Windows 上撤销编辑;WSL 使用Alt+Z,以便Ctrl+Z可以挂起 pi。Ctrl+Q排队一条后续消息,Alt+Q恢复排队的消息。
在 settings.json 中添加(Ctrl+Shift+, 或 Settings → Open JSON file),以转发用于插入新行的 Shift+Enter:
Windows Terminal 默认把 Alt+Enter 绑定为全屏。如果想用它代替 pi 默认的 Ctrl+Q 来排队后续消息,请配置 Windows Terminal 发送该键,并在 pi 中把 app.message.followUp 绑定到 alt+enter。
如果你已经有 actions 数组,把这个对象加进去即可。修改设置后,请完全关闭并重新打开 Windows Terminal。
xfce4-terminal、terminator
这些终端对转义序列的支持有限。Ctrl+Enter 和 Shift+Enter 这类带修饰的回车键无法与普通 Enter 区分,导致 submit: ["ctrl+enter"] 这类自定义快捷键无法工作。
为获得最佳体验,请使用支持 Kitty 键盘协议的终端:
IntelliJ IDEA(集成终端)
内置终端对转义序列的支持有限。在 IntelliJ 的终端中,Shift+Enter 无法与 Enter 区分。
如果你希望显示硬件光标,请在运行 pi 前设置 PI_HARDWARE_CURSOR=1(默认关闭以保持兼容)。
为获得最佳体验,建议使用专门的终端模拟器。
28 Shell 别名
Pi 以非交互模式(bash -c)运行 bash,默认不会展开别名。
要启用你的 shell 别名,请在 ~/.pi/agent/settings.json 中添加:
请根据你的 shell 配置调整路径(~/.zshrc、~/.bashrc 等)。
29 开发
更多准则请参阅 AGENTS.md。
环境搭建
从源码运行:
该脚本可以在任意目录下运行。Pi 会保留调用者当前的工作目录。
实验性远程测试框架
远程测试框架(remote harness)的服务器/客户端集成仅用于开发。在仓库中运行:
PI_SERVER_DIR 覆盖服务器配置档案和套接字目录(默认为 ~/.pi/server)。当未指定 --server-id 时,PI_SERVER_ID 用于选择逻辑服务器 ID。
client 和 experimental/plugin 包子路径只在源码检出中且 source 条件下才能解析。它们的实现以及 server/client 命令不会包含在 npm 包和独立二进制文件中。pi-client、pi-protocol 和 pi-server 是 coding-agent 的开发依赖,而非运行时依赖。本地 SDK 和 stdio RPC API 保持不变。
Fork 与品牌重塑
通过 package.json 配置:
在你的 fork 中修改 name、configDir 和 bin 字段。这会影响 CLI 横幅、配置路径和环境变量名称。
路径解析
三种执行模式:npm 安装、独立二进制、从源码以 tsx 运行。
对于包内资源,始终使用 src/config.ts:
对于包内资源,切勿直接使用 __dirname。
调试命令
/debug(隐藏命令)会将以下内容写入 ~/.pi/agent/pi-debug.log:
- 渲染后的 TUI 行(含 ANSI 码)
- 最近发送给 LLM 的消息
测试
已发布包冒烟测试
构建完成后,运行 npm run check:package-install。它会打包公开的各个包,并在仓库之外的临时目录中仅将 coding-agent 作为直接依赖安装。本地 tarball 会有选择地覆盖已声明的传递依赖,而不会安装仅用于开发的包。该检查验证 SDK 导入和 CLI 启动,不需要凭据,也不会发起模型请求。
npm run check 还会检查运行时依赖声明,并拒绝通过导入将被排除的开发源码带进某个包的构建产物。