pi mono 操作开发指南
基于
pi-mono官方文档(docs/目录)全面梳理,覆盖从入门到高级定制的完整知识体系。
第一部分:概览与快速入门
第 1 章 pi mono 简介
1.1 什么是 pi mono
pi mono 是一个极简的终端编码代理(terminal coding harness),它将编码 AI 的能力直接带入你的终端。与其他编码助手不同,pi mono 坚持一个核心理念:适配用户的工作流,而非让用户适应它。
pi mono 不是一个大而全的产品,而是一个精巧的运行时平台。它通过 TypeScript Extension、Skill、Prompt Template 和 Theme 四种扩展机制,让开发者能够按需定制代理的行为。你想要的功能,pi mono 让你自己去构建------或者从社区安装第三方 Pi Package。
1.2 核心理念
pi mono 的设计哲学可以总结为:
- 可扩展优于功能堆砌:核心保持极简,扩展点丰富。
- 工具而非流程:只提供四个基础工具(read、write、edit、bash),模型决定如何组合它们。
- 透明而非黑盒:每一次工具调用、每一次推理过程,用户都看得见。
- Unix 哲学:做好一件事,与其他工具协作。
开发者可以将扩展、技能、提示词模板和主题打包成 Pi Package,通过 npm 或 git 与他人分享。
1.3 四种运行模式
pi mono 提供四种运行模式,适应不同的使用场景:
| 模式 | 启动方式 | 说明 |
|---|---|---|
| 交互模式 | pi |
完整的 TUI 交互界面,包含编辑器、消息历史、快捷键 |
| 打印模式 | pi -p "prompt" |
单次提示,输出结果后退出。支持 stdin 管道输入 |
| JSON 模式 | pi --mode json |
输出所有事件为 JSON 行,适合工具集成 |
| RPC 模式 | pi --mode rpc |
通过 stdin/stdout 的 JSONL 协议通信,用于子进程集成 |
在打印模式下,pi 也会读取管道 stdin 并将其合并到初始提示中:
bash
cat README.md | pi -p "Summarize this text"
1.4 哲学原则------pi 不做什么
pi mono 明确拒绝了一些看似"必备"的功能,这不是因为懒惰,而是因为深思熟虑的设计选择:
| 功能 | pi 的选择 | 理由 |
|---|---|---|
| MCP | 不内置 | 用 CLI 工具 + Skill 替代,或用 Extension 添加 |
| 子代理 | 不内置 | 可用 tmux 生成 pi 实例,或用 Extension 构建 |
| 权限弹窗 | 不内置 | 在容器中运行,或用 Extension 构建确认流 |
| 计划模式 | 不内置 | 写入 TODO.md,或用 Extension 构建 |
| 内置 TODO | 不包含 | 会混淆模型;用 TODO.md 文件替代 |
| 后台 Bash | 不包含 | 使用 tmux,完整可观测性和直接交互 |
这些选择的共同逻辑:每种"正确做法"取决于具体场景,强行内置等于限制用户。pi mono 把选择权交给用户和社区。
1.5 pi mono 能做什么
在这些限制之下,pi mono 依然强大:
- 通过
read、write、edit、bash四个工具,模型可以执行几乎所有编码操作 - 通过 Extension,你可以:添加自定义工具、拦截工具调用、构建子代理、实现 MCP 支持、创建交互式 UI、甚至运行游戏
- 通过 Skill,你可以将工作流封装为可复用的技能包
- 通过 Pi Package,你可以将一切打包分享
pi 可以创建 Extension。请它根据你的使用场景构建一个。
第 2 章 快速开始
2.1 安装
pi mono 通过 npm 全局安装:
bash
npm install -g @mariozechner/pi-coding-agent
2.2 认证方式
pi mono 支持两种认证方式。
方式一:API Key(环境变量)
bash
export ANTHROPIC_API_KEY=sk-ant-...
pi
你也可以将 API Key 存储在认证文件 ~/.pi/agent/auth.json 中:
json
{
"anthropic": { "type": "api_key", "key": "sk-ant-..." }
}
方式二:订阅登录
bash
pi
/login # 然后选择提供商
支持的订阅:Anthropic Claude Pro/Max、OpenAI ChatGPT Plus/Pro (Codex)、GitHub Copilot、Google Gemini CLI、Google Antigravity。
2.3 首次对话
启动 pi 后,直接输入你的问题即可开始对话。默认情况下,pi 向模型提供四个工具:read、write、edit 和 bash。模型会自动使用这些工具来完成你的请求。
例如:
bash
pi "List all .ts files in src/"
2.4 默认工具集
pi mono 内置了 7 个工具,但默认只启用其中 4 个:
| 工具 | 功能 | 默认启用 |
|---|---|---|
read |
读取文件内容 | ✅ |
write |
创建或覆写文件 | ✅ |
edit |
精确文本替换 | ✅ |
bash |
执行 Shell 命令 | ✅ |
grep |
内容搜索 | ❌ |
find |
文件查找 | ❌ |
ls |
目录列表 | ❌ |
可通过 --tools 选项启用更多工具:
bash
pi --tools read,bash,edit,write,grep,find,ls
或进入只读模式:
bash
pi --tools read,grep,find,ls -p "Review the code"
2.5 命令行示例
bash
# 交互模式 + 初始提示
pi "List all .ts files in src/"
# 非交互模式
pi -p "Summarize this codebase"
# 非交互模式 + 管道 stdin
cat README.md | pi -p "Summarize this text"
# 指定模型
pi --provider openai --model gpt-4o "Help me refactor"
# 使用 provider/id 模型模式
pi --model openai/gpt-4o "Help me refactor"
# 使用思维级别简写
pi --model sonnet:high "Solve this complex problem"
# 限制模型循环范围
pi --models "claude-*,gpt-4o"
# 只读模式
pi --tools read,grep,find,ls -p "Review the code"
# 高思维级别
pi --thinking high "Solve this complex problem"
# 使用 @ 引用文件
pi @code.ts @test.ts "Review these files"
第 3 章 平台适配
3.1 Kitty 键盘协议
pi mono 使用 Kitty 键盘协议 来可靠地检测修饰键组合。大多数现代终端支持此协议,但某些终端需要额外配置。
3.2 各终端适配方案
Kitty、iTerm2
开箱即用,无需额外配置。
Ghostty
在 Ghostty 配置文件中添加:
keybind = alt+backspace=text:\x1b\x7f
配置文件位置:
- macOS:
~/Library/Application Support/com.mitchellh.ghostty/config - Linux:
~/.config/ghostty/config
WezTerm
创建 ~/.wezterm.lua:
lua
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.enable_kitty_keyboard = true
return config
VS Code(集成终端)
在 keybindings.json 中添加以启用 Shift+Enter 多行输入:
json
{
"key": "shift+enter",
"command": "workbench.action.terminal.sendSequence",
"args": { "text": "\u001b[13;2u" },
"when": "terminalFocus"
}
keybindings.json 位置:
- macOS:
~/Library/Application Support/Code/User/keybindings.json - Linux:
~/.config/Code/User/keybindings.json - Windows:
%APPDATA%\Code\User\keybindings.json
Windows Terminal
在 settings.json 中添加:
json
{
"actions": [
{
"command": { "action": "sendInput", "input": "\u001b[13;2u" },
"keys": "shift+enter"
},
{
"command": { "action": "sendInput", "input": "\u001b[13;3u" },
"keys": "alt+enter"
}
]
}
xfce4-terminal、terminator
这些终端对转义序列支持有限。Ctrl+Enter 和 Shift+Enter 无法与普通 Enter 区分。建议切换到支持 Kitty 键盘协议的终端。
IntelliJ IDEA(集成终端)
内置终端对转义序列支持有限。Shift+Enter 无法与 Enter 区分。建议使用独立终端模拟器。
3.3 Windows 注意事项
Windows 用户应注意:
- Windows Terminal 中
Alt+Enter默认绑定为全屏切换,会拦截 pi 的 follow-up 快捷键,需重新映射 - Ctrl+Enter 代替 Shift+Enter 进行多行输入(在 Windows Terminal 中)
- Alt+V 代替 Ctrl+V 粘贴图片
3.4 tmux 集成
pi 支持在 tmux 中运行。详细集成指南请参考 docs/tmux.md。
3.5 Shell 别名
pi 支持配置 shell 别名,详见 docs/shell-aliases.md。
第二部分:Provider 与模型
第 4 章 Provider 体系
4.1 概述
pi mono 支持两大类 Provider:订阅制 Provider (通过 OAuth 登录)和 API Key Provider(通过环境变量或认证文件配置)。每个内置 Provider 在 pi 的每次发布中都维护着一份可用模型列表,用户无需手动维护。
4.2 订阅制 Provider
通过 /login 命令进入认证流程,选择对应的 Provider 进行 OAuth 登录:
| 订阅来源 | Provider | 说明 |
|---|---|---|
| Anthropic Claude Pro/Max | Anthropic | Claude 全系列模型 |
| OpenAI ChatGPT Plus/Pro (Codex) | OpenAI | GPT 全系列,需订阅 |
| GitHub Copilot | GitHub | 支持 GitHub Enterprise Server |
| Google Gemini CLI | 标准 Gemini 模型,通过 Cloud Code Assist | |
| Google Antigravity | 含 Gemini 3、Claude、GPT-OSS 模型的沙箱 |
使用 /logout 清除凭证。Token 存储在 ~/.pi/agent/auth.json 中,过期时自动刷新。
GitHub Copilot 特殊说明:首次登录时按 Enter 使用 github.com,或输入 GitHub Enterprise Server 域名。如果遇到 "model not supported" 错误,需要在 VS Code 中启用:Copilot Chat → 模型选择器 → 选择模型 → "Enable"。
Google Provider 特殊说明:
- Gemini CLI:通过 Cloud Code Assist 访问标准 Gemini 模型
- Antigravity:包含 Gemini 3、Claude 和 GPT-OSS 模型的沙箱环境
- 两者均免费(需 Google 账户),受速率限制
- 付费 Cloud Code Assist:设置
GOOGLE_CLOUD_PROJECT环境变量
OpenAI Codex 特殊说明:需要 ChatGPT Plus 或 Pro 订阅;仅限个人使用,生产环境应使用 OpenAI Platform API。
4.3 API Key 制 Provider
通过 /login 交互式输入 API Key,或直接设置环境变量:
bash
export ANTHROPIC_API_KEY=sk-ant-...
pi
以下是完整 Provider 映射表:
| Provider | 环境变量 | auth.json Key |
|---|---|---|
| Anthropic | ANTHROPIC_API_KEY |
anthropic |
| Azure OpenAI Responses | AZURE_OPENAI_API_KEY |
azure-openai-responses |
| OpenAI | OPENAI_API_KEY |
openai |
| DeepSeek | DEEPSEEK_API_KEY |
deepseek |
| Google Gemini | GEMINI_API_KEY |
google |
| Mistral | MISTRAL_API_KEY |
mistral |
| Groq | GROQ_API_KEY |
groq |
| Cerebras | CEREBRAS_API_KEY |
cerebras |
| xAI | XAI_API_KEY |
xai |
| OpenRouter | OPENROUTER_API_KEY |
openrouter |
| Vercel AI Gateway | AI_GATEWAY_API_KEY |
vercel-ai-gateway |
| ZAI | ZAI_API_KEY |
zai |
| OpenCode Zen | OPENCODE_API_KEY |
opencode |
| OpenCode Go | OPENCODE_API_KEY |
opencode-go |
| Hugging Face | HF_TOKEN |
huggingface |
| Fireworks | FIREWORKS_API_KEY |
fireworks |
| Kimi For Coding | KIMI_API_KEY |
kimi-coding |
| MiniMax | MINIMAX_API_KEY |
minimax |
| MiniMax (China) | MINIMAX_CN_API_KEY |
minimax-cn |
4.4 认证文件 auth.json
将凭证存储在 ~/.pi/agent/auth.json 中:
json
{
"anthropic": { "type": "api_key", "key": "sk-ant-..." },
"openai": { "type": "api_key", "key": "sk-..." },
"deepseek": { "type": "api_key", "key": "sk-..." },
"google": { "type": "api_key", "key": "..." },
"opencode": { "type": "api_key", "key": "..." },
"opencode-go": { "type": "api_key", "key": "..." }
}
该文件以 0600 权限创建(仅用户可读写)。auth.json 中的凭证优先于环境变量。
key 字段支持三种格式:
-
Shell 命令 :以
!开头,执行后使用 stdout 输出作为 Key(整个进程生命周期内缓存)json{ "type": "api_key", "key": "!security find-generic-password -ws 'anthropic'" } { "type": "api_key", "key": "!op read 'op://vault/item/credential'" } -
环境变量 :使用命名变量的值
json{ "type": "api_key", "key": "MY_ANTHROPIC_KEY" } -
字面值 :直接使用
json{ "type": "api_key", "key": "sk-ant-..." }
OAuth 凭证在 /login 后也存储在此文件中,由 pi 自动管理。
4.5 云端 Provider
Azure OpenAI
bash
export AZURE_OPENAI_API_KEY=...
export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com
# 或使用资源名称代替 Base URL
export AZURE_OPENAI_RESOURCE_NAME=your-resource
# 可选
export AZURE_OPENAI_API_VERSION=2024-02-01
export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4=my-gpt4,gpt-4o=my-gpt4o
Amazon Bedrock
bash
# 方式 1:AWS Profile
export AWS_PROFILE=your-profile
# 方式 2:IAM Keys
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
# 方式 3:Bearer Token
export AWS_BEARER_TOKEN_BEDROCK=...
# 可选区域(默认 us-east-1)
export AWS_REGION=us-west-2
还支持 ECS 任务角色(AWS_CONTAINER_CREDENTIALS_*)和 IRSA(AWS_WEB_IDENTITY_TOKEN_FILE)。
对于应用推理配置(其 ARN 中不包含模型名称),设置 AWS_BEDROCK_FORCE_CACHE=1 以启用缓存点:
bash
export AWS_BEDROCK_FORCE_CACHE=1
pi --provider amazon-bedrock --model arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123
Google Vertex AI
使用 Application Default Credentials:
bash
gcloud auth application-default login
export GOOGLE_CLOUD_PROJECT=your-project
export GOOGLE_CLOUD_LOCATION=us-central1
或设置 GOOGLE_APPLICATION_CREDENTIALS 指向服务账号密钥文件。
4.6 凭证解析顺序
当 pi 解析 Provider 的凭证时,按以下优先级:
- CLI
--api-key标志 auth.json条目(API Key 或 OAuth Token)- 环境变量
- 自定义 Provider 的 Key(来自
models.json)
4.7 模型选择
启动后可通过以下方式切换模型:
/model命令打开模型选择器Ctrl+L打开模型选择器Ctrl+P/Shift+Ctrl+P在作用域模型间向前/向后循环--model命令行参数指定--models限制可循环模型范围
第 5 章 自定义模型
5.1 概述
通过 ~/.pi/agent/models.json 文件,你可以添加自定义 Provider 和模型(如 Ollama、vLLM、LM Studio、代理等),而无需编写 Extension。该文件在每次打开 /model 时重新加载,无需重启 pi。
5.2 最简配置
对于本地模型(Ollama、LM Studio、vLLM),只需指定模型 id:
json
{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{ "id": "llama3.1:8b" },
{ "id": "qwen2.5-coder:7b" }
]
}
}
}
apiKey 是必填项,但 Ollama 会忽略它,所以任意值均可。
某些 OpenAI 兼容服务器不理解推理模型使用的 developer 角色。对于此类 Provider,将 compat.supportsDeveloperRole 设为 false,pi 会将系统提示词作为 system 消息发送。如果服务器也不支持 reasoning_effort,将 compat.supportsReasoningEffort 设为 false。
compat 可在 Provider 级别设置(应用到所有模型)或在模型级别设置(覆盖特定模型)。这常见于 Ollama、vLLM、SGLang 及类似的 OpenAI 兼容服务器。
json
{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
},
"models": [
{
"id": "gpt-oss:20b",
"reasoning": true
}
]
}
}
}
5.3 完整配置
当需要特定值时,覆盖默认值:
json
{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{
"id": "llama3.1:8b",
"name": "Llama 3.1 8B (Local)",
"reasoning": false,
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 32000,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
5.4 支持的 API 类型
| API | 说明 |
|---|---|
openai-completions |
OpenAI Chat Completions(兼容性最广) |
openai-responses |
OpenAI Responses API |
anthropic-messages |
Anthropic Messages API |
google-generative-ai |
Google Generative AI |
api 字段可在 Provider 级别设置(对所有模型的默认值)或在模型级别设置(覆盖 Provider 的 API)。
5.5 Google AI Studio 示例
使用 google-generative-ai 配合 baseUrl 添加 Google AI Studio 的模型:
json
{
"providers": {
"my-google": {
"baseUrl": "https://generativelanguage.googleapis.com/v1beta",
"api": "google-generative-ai",
"apiKey": "GEMINI_API_KEY",
"models": [
{
"id": "gemma-4-31b-it",
"name": "Gemma 4 31B",
"input": ["text", "image"],
"contextWindow": 262144,
"reasoning": true
}
]
}
}
}
对于 google-generative-ai API 类型,添加自定义模型时 baseUrl 是必需的。
5.6 Provider 配置字段
| 字段 | 说明 |
|---|---|
baseUrl |
API 端点 URL |
api |
API 类型 |
apiKey |
API Key(值解析见下文) |
headers |
自定义 HTTP 头(值解析见下文) |
authHeader |
设为 true 自动添加 Authorization: Bearer <apiKey> |
models |
模型配置数组 |
modelOverrides |
对内置模型的逐模型覆盖 |
5.7 自定义 Header
json
{
"providers": {
"custom-proxy": {
"baseUrl": "https://proxy.example.com/v1",
"apiKey": "MY_API_KEY",
"api": "anthropic-messages",
"headers": {
"x-portkey-api-key": "PORTKEY_API_KEY",
"x-secret": "!op read 'op://vault/item/secret'"
},
"models": [...]
}
}
}
5.8 模型配置字段
| 字段 | 必填 | 默认值 | 说明 |
|---|---|---|---|
id |
是 | --- | 模型标识符(传递给 API) |
name |
否 | id |
人类可读标签,用于匹配和显示 |
api |
否 | Provider 的 api |
覆盖 Provider 的 API |
reasoning |
否 | false |
是否支持扩展思维 |
input |
否 | ["text"] |
输入类型:["text"] 或 ["text", "image"] |
contextWindow |
否 | 128000 |
上下文窗口大小(tokens) |
maxTokens |
否 | 16384 |
最大输出 tokens |
cost |
否 | 全零 | {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}(每百万 tokens) |
compat |
否 | Provider compat |
兼容性覆盖,与 Provider 级别合并 |
当前行为:
/model和--list-models按模型id列出条目- 配置的
name用于模型匹配和详情/状态文本
5.9 覆盖内置 Provider
将内置 Provider 路由到代理,无需重新定义模型:
json
{
"providers": {
"anthropic": {
"baseUrl": "https://my-proxy.example.com/v1"
}
}
}
所有内置 Anthropic 模型仍然可用。现有 OAuth 或 API Key 认证继续工作。
要将自定义模型合并到内置 Provider,包含 models 数组:
json
{
"providers": {
"anthropic": {
"baseUrl": "https://my-proxy.example.com/v1",
"apiKey": "ANTHROPIC_API_KEY",
"api": "anthropic-messages",
"models": [...]
}
}
}
合并语义:
- 保留内置模型
- 自定义模型在 Provider 内按
id进行 upsert - 如果自定义模型
id匹配内置模型id,自定义模型替换内置模型 - 如果自定义模型
id是新的,则与内置模型并列添加
5.10 逐模型覆盖(modelOverrides)
使用 modelOverrides 自定义特定内置模型,无需替换 Provider 的完整模型列表:
json
{
"providers": {
"openrouter": {
"modelOverrides": {
"anthropic/claude-sonnet-4": {
"name": "Claude Sonnet 4 (Bedrock Route)",
"compat": {
"openRouterRouting": {
"only": ["amazon-bedrock"]
}
}
}
}
}
}
}
modelOverrides 支持的字段:name、reasoning、input、cost(部分)、contextWindow、maxTokens、headers、compat。
行为说明:
modelOverrides应用到内置 Provider 模型- 未知模型 ID 被忽略
- 可以组合 Provider 级别的
baseUrl/headers与modelOverrides - 如果同时定义了
models,自定义模型在内置覆盖之后合并
5.11 OpenAI 兼容性设置
对于部分兼容 OpenAI 的 Provider,使用 compat 字段。Provider 级别的 compat 作为默认值应用于该 Provider 下所有模型;模型级别的 compat 覆盖 Provider 级别的值。
| 字段 | 说明 |
|---|---|
supportsStore |
Provider 是否支持 store 字段 |
supportsDeveloperRole |
使用 developer 还是 system 角色 |
supportsReasoningEffort |
是否支持 reasoning_effort 参数 |
reasoningEffortMap |
将 pi 思维级别映射到 Provider 特定的 reasoning_effort 值 |
supportsUsageInStreaming |
是否支持 stream_options: { include_usage: true }(默认 true) |
maxTokensField |
使用 max_completion_tokens 还是 max_tokens |
requiresToolResultName |
工具结果是否需要 name 字段 |
requiresAssistantAfterToolResult |
工具结果后是否需要插入 assistant 消息 |
requiresThinkingAsText |
将 thinking 块转换为纯文本 |
requiresReasoningContentOnAssistantMessages |
推理启用时在重放的 assistant 消息中包含空 reasoning_content |
thinkingFormat |
使用 reasoning_effort、deepseek、zai、qwen、qwen-chat-template |
cacheControlFormat |
使用 Anthropic 风格的 cache_control 标记 |
supportsStrictMode |
在工具定义中包含 strict 字段 |
supportsLongCacheRetention |
支持长缓存保留(prompt_cache_retention: "24h") |
openRouterRouting |
OpenRouter Provider 路由偏好设置 |
vercelGatewayRouting |
Vercel AI Gateway 路由配置 |
thinkingFormat 说明:
deepseek:发送thinking: { type: "enabled" | "disabled" }和启用时的reasoning_effortqwen:DashScope 风格的顶级enable_thinkingqwen-chat-template:本地 Qwen 兼容服务器使用chat_template_kwargs.enable_thinking
cacheControlFormat: "anthropic" 用于暴露 Anthropic 风格 prompt caching 的 OpenAI 兼容 Provider,在系统提示词、最后一个工具定义以及最后一个用户/助手文本内容上附加 cache_control 标记。
5.12 Anthropic Messages 兼容性
对于使用 api: "anthropic-messages" 的 Provider 或代理,使用 compat 字段控制 Anthropic 细粒度工具流式兼容性:
| 字段 | 说明 |
|---|---|
supportsEagerToolInputStreaming |
是否接受每工具 eager_input_streaming。默认 true,设为 false 时省略该字段并使用遗留的 beta header |
supportsLongCacheRetention |
是否接受 Anthropic 长缓存保留。默认 true |
5.13 值解析规则
apiKey 和 headers 字段支持三种格式:
- Shell 命令 :
"!command"执行后使用 stdout - 环境变量:使用命名变量的值
- 字面值:直接使用
对于 models.json,Shell 命令在请求时解析。pi 故意不为任意命令应用内置 TTL、过期重用或恢复逻辑。不同命令需要不同的缓存和失败策略,pi 无法推断正确的策略。如果命令运行缓慢或受速率限制,请将其包装在自定义脚本中。
第 6 章 自定义 Provider(Extension)
6.1 概述
当 models.json 不足以满足需求(例如需要 OAuth/SSO 认证、自定义 API 实现、或动态模型发现)时,可以通过 Extension 注册自定义 Provider。
完整的 Provider 扩展示例:
examples/extensions/custom-provider-anthropic/--- Anthropic 代理示例examples/extensions/custom-provider-gitlab-duo/--- GitLab Duo OAuth 集成examples/extensions/custom-provider-qwen-cli/--- Qwen CLI 自定义 API
6.2 快速参考
typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
// 覆盖现有 Provider 的 baseUrl
pi.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// 注册新 Provider
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "MY_API_KEY",
api: "openai-completions",
models: [
{
id: "my-model",
name: "My Model",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096
}
]
});
}
Extension 工厂可以是 async 的。对于动态模型发现,应在工厂中获取并注册模型(而非在 session_start 中),因为 pi 会等待异步工厂完成后再继续启动,这样 Provider 在交互式启动期间和 pi --list-models 时都可用。
6.3 覆盖现有 Provider
最简用例:通过代理重定向现有 Provider。
typescript
// 所有 Anthropic 请求现在通过你的代理
pi.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// 为 OpenAI 请求添加自定义 Header
pi.registerProvider("openai", {
headers: {
"X-Custom-Header": "value"
}
});
// 同时设置 baseUrl 和 headers
pi.registerProvider("google", {
baseUrl: "https://ai-gateway.corp.com/google",
headers: {
"X-Corp-Auth": "CORP_AUTH_TOKEN" // 环境变量或字面值
}
});
当只提供 baseUrl 和/或 headers(无 models)时,该 Provider 的所有现有模型保持不变,仅更换端点。
6.4 注册新 Provider
要添加全新 Provider,需要指定 models 以及必需配置。
如果模型列表来自远程端点,使用异步 Extension 工厂:
typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default async function (pi: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = (await response.json()) as {
data: Array<{
id: string;
name?: string;
context_window?: number;
max_tokens?: number;
}>;
};
pi.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "LOCAL_OPENAI_API_KEY",
api: "openai-completions",
models: payload.data.map((model) => ({
id: model.id,
name: model.name ?? model.id,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: model.context_window ?? 128000,
maxTokens: model.max_tokens ?? 4096,
})),
});
}
重要 :当提供 models 时,它替换该 Provider 的所有现有模型。
6.5 注销 Provider
使用 pi.unregisterProvider(name) 移除之前注册的 Provider:
typescript
// 先注册
pi.registerProvider("my-llm", {
baseUrl: "https://api.my-llm.com/v1",
apiKey: "MY_LLM_API_KEY",
api: "openai-completions",
models: [/* ... */]
});
// 后移除
pi.unregisterProvider("my-llm");
注销将移除该 Provider 的动态模型、API Key 回退、OAuth Provider 注册以及自定义流处理程序注册。被覆盖的内置模型或 Provider 行为将恢复。
6.6 API 类型完整列表
| API | 用途 |
|---|---|
anthropic-messages |
Anthropic Claude API 及兼容 |
openai-completions |
OpenAI Chat Completions API 及兼容 |
openai-responses |
OpenAI Responses API |
azure-openai-responses |
Azure OpenAI Responses API |
openai-codex-responses |
OpenAI Codex Responses API |
mistral-conversations |
Mistral SDK Conversations/Chat 流式 |
google-generative-ai |
Google Generative AI API |
google-gemini-cli |
Google Cloud Code Assist API |
google-vertex |
Google Vertex AI API |
bedrock-converse-stream |
Amazon Bedrock Converse API |
如果 Provider 使用非标准 API 但期望 Authorization: Bearer <key>,设置 authHeader: true:
typescript
pi.registerProvider("custom-api", {
baseUrl: "https://api.example.com",
apiKey: "MY_API_KEY",
authHeader: true,
api: "openai-completions",
models: [...]
});
6.7 OAuth/SSO 支持
添加 OAuth/SSO 认证,集成到 /login 流程:
typescript
import type { OAuthCredentials, OAuthLoginCallbacks } from "@mariozechner/pi-ai";
pi.registerProvider("corporate-ai", {
baseUrl: "https://ai.corp.com/v1",
api: "openai-responses",
models: [...],
oauth: {
name: "Corporate AI (SSO)",
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
// 方式 1:浏览器 OAuth
callbacks.onAuth({ url: "https://sso.corp.com/authorize?..." });
// 方式 2:设备码流
callbacks.onDeviceCode({
userCode: "ABCD-1234",
verificationUri: "https://sso.corp.com/device"
});
// 方式 3:提示输入 Token/Code
const code = await callbacks.onPrompt({ message: "Enter SSO code:" });
const tokens = await exchangeCodeForTokens(code);
return {
refresh: tokens.refreshToken,
access: tokens.accessToken,
expires: Date.now() + tokens.expiresIn * 1000
};
},
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
const tokens = await refreshAccessToken(credentials.refresh);
return {
refresh: tokens.refreshToken ?? credentials.refresh,
access: tokens.accessToken,
expires: Date.now() + tokens.expiresIn * 1000
};
},
getApiKey(credentials: OAuthCredentials): string {
return credentials.access;
},
// 可选:根据用户订阅修改模型
modifyModels(models, credentials) {
const region = decodeRegionFromToken(credentials.access);
return models.map(m => ({
...m,
baseUrl: `https://${region}.ai.corp.com/v1`
}));
}
}
});
注册后,用户可通过 /login corporate-ai 认证。
OAuthLoginCallbacks 接口:
| 方法 | 说明 |
|---|---|
onAuth({ url }) |
在浏览器中打开 URL(OAuth 重定向) |
onDeviceCode({ userCode, verificationUri }) |
显示设备码(设备授权流) |
onPrompt({ message }) |
提示用户输入(手动 Token 输入) |
OAuthCredentials 接口:
| 字段 | 说明 |
|---|---|
refresh |
刷新 Token(用于 refreshToken) |
access |
访问 Token(getApiKey 返回值) |
expires |
过期时间戳(毫秒) |
凭证持久化在 ~/.pi/agent/auth.json 中。
6.8 自定义流式 API
对于非标准 API 的 Provider,实现 streamSimple。在编写自己的实现前,先研究现有的 Provider 实现:
流式模式:
所有 Provider 遵循相同的模式:
typescript
import {
type AssistantMessage,
type AssistantMessageEventStream,
type Context,
type Model,
type SimpleStreamOptions,
calculateCost,
createAssistantMessageEventStream,
} from "@mariozechner/pi-ai";
function streamMyProvider(
model: Model<any>,
context: Context,
options?: SimpleStreamOptions
): AssistantMessageEventStream {
const stream = createAssistantMessageEventStream();
(async () => {
const output: AssistantMessage = {
role: "assistant",
content: [],
api: model.api,
provider: model.provider,
model: model.id,
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
stopReason: "stop",
timestamp: Date.now(),
};
try {
stream.push({ type: "start", partial: output });
// ... 处理 API 响应,推送内容事件 ...
stream.push({ type: "done", reason: output.stopReason as "stop" | "length" | "toolUse", message: output });
stream.end();
} catch (error) {
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
output.errorMessage = error instanceof Error ? error.message : String(error);
stream.push({ type: "error", reason: output.stopReason, error: output });
stream.end();
}
})();
return stream;
}
事件推送顺序:
{ type: "start", partial }--- 流开始- 内容事件(可重复,跟踪
contentIndex):text_start/text_delta/text_endthinking_start/thinking_delta/thinking_endtoolcall_start/toolcall_delta/toolcall_end
{ type: "done", reason, message }或{ type: "error", reason, error }--- 流结束
注册自定义流函数:
typescript
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "MY_API_KEY",
api: "my-custom-api",
models: [...],
streamSimple: streamMyProvider
});
6.9 测试 Provider 实现
使用与内置 Provider 相同的测试套件验证你的实现。从 packages/ai/test/ 复制并适配这些测试文件:
| 测试 | 目的 |
|---|---|
stream.test.ts |
基础流式、文本输出 |
tokens.test.ts |
Token 计数和用量 |
abort.test.ts |
AbortSignal 处理 |
empty.test.ts |
空/最小响应 |
context-overflow.test.ts |
上下文窗口限制 |
image-limits.test.ts |
图片输入处理 |
unicode-surrogate.test.ts |
Unicode 边界情况 |
tool-call-without-result.test.ts |
工具调用边界情况 |
image-tool-result.test.ts |
工具结果中的图片 |
total-tokens.test.ts |
总 Token 计算 |
cross-provider-handoff.test.ts |
跨 Provider 上下文交接 |
用你的 Provider/模型对运行测试以验证兼容性。
第三部分:交互模式
第 7 章 交互界面
7.1 启动方式
直接运行 pi 即可进入交互模式。pi 会显示启动头、加载资源、然后进入交互循环。
7.2 界面布局
交互界面从上到下由以下部分组成:
| 区域 | 说明 |
|---|---|
| 启动头 | 显示快捷键(/hotkeys 查看全部)、加载的 AGENTS.md 文件、提示词模板、技能和扩展 |
| 消息区 | 用户消息、助手回复、工具调用和结果、通知、错误、扩展 UI |
| 编辑器 | 你输入的地方;边框颜色指示思维级别 |
| 页脚 | 工作目录、会话名称、总 Token/缓存用量、费用、上下文使用率、当前模型 |
编辑器可以被其他 UI 临时替换,比如内置的 /settings 或来自扩展的自定义 UI(例如一个让 LLM 提问用户回答的结构化问答工具)。扩展还可以替换编辑器,在编辑器上方/下方添加小组件、状态行、自定义页脚或浮层。
7.3 编辑器功能
| 功能 | 操作方式 |
|---|---|
| 文件引用 | 输入 @ 模糊搜索项目文件 |
| 路径补全 | Tab 键完成路径 |
| 多行输入 | Shift+Enter(Windows Terminal 中 Ctrl+Enter) |
| 图片 | Ctrl+V 粘贴(Windows 中 Alt+V),或拖拽到终端 |
| Bash 命令 | !command 执行并将输出发送给 LLM,!!command 执行但不发送 |
标准编辑键绑定:删除单词、撤销等。详见第 9 章快捷键。
7.4 页脚信息
页脚显示的关键信息:
- 工作目录:当前项目根路径
- 会话名称 :通过
/name设置的显示名 - Token 用量:输入、输出、缓存读写
- 费用:累计费用
- 上下文使用率:当前上下文窗口占用百分比
- 当前模型:Provider/Model ID
7.5 消息类型
在交互界面中,你会看到以下类型的消息:
- 用户消息:你输入的内容
- 助手回复:LLM 的响应,包括思考块(thinking blocks)和工具调用
- 工具调用与结果:模型调用的工具及其返回值
- 扩展消息 :来自 Extension 的注入消息(
customType标识) - 分支摘要 :来自
/tree导航的分支摘要 - 压缩摘要:压缩后的上下文摘要
- 通知:来自 Extension 或系统的通知
- 错误:API 错误、超时、重试信息
第 8 章 内置命令
8.1 命令系统
在编辑器中输入 / 触发命令。扩展可以注册自定义命令,技能以 /skill:name 形式可用,提示词模板以 /templatename 展开。
8.2 认证命令
| 命令 | 说明 |
|---|---|
/login |
OAuth 认证流程 |
/logout |
清除凭证 |
8.3 模型命令
| 命令 | 说明 |
|---|---|
/model |
切换模型 |
/scoped-models |
启用/禁用 Ctrl+P 循环的模型 |
8.4 设置命令
| 命令 | 说明 |
|---|---|
/settings |
思维级别、主题、消息投递、Transport 等设置 |
8.5 会话命令
| 命令 | 说明 |
|---|---|
/resume |
从历史会话中选择 |
/new |
开始新会话 |
/name <name> |
设置会话显示名称 |
/session |
显示会话信息(路径、Token、费用) |
/tree |
跳转到会话树中的任意节点 |
/fork |
从当前分支创建新会话 |
8.6 上下文与输出命令
| 命令 | 说明 |
|---|---|
/compact [prompt] |
手动压缩上下文,可选自定义指令 |
/copy |
复制最后一条助手消息到剪贴板 |
/export [file] |
导出会话为 HTML 文件 |
/share |
上传为私有 GitHub Gist 并生成可分享的 HTML 链接 |
8.7 系统命令
| 命令 | 说明 |
|---|---|
/reload |
重新加载快捷键、扩展、技能、提示词和上下文文件(主题自动热重载) |
/hotkeys |
显示所有键盘快捷键 |
/changelog |
显示版本历史 |
/quit |
退出 pi |
8.8 技能命令
技能注册为 /skill:name 命令:
bash
/skill:brave-search # 加载并执行技能
/skill:pdf-tools extract # 带参数加载技能
命令后的参数作为 User: <args> 追加到技能内容中。
通过 /settings 或 settings.json 中的 enableSkillCommands 控制技能命令的注册:
json
{
"enableSkillCommands": true
}
8.9 提示词模板命令
模板注册为 /name 命令,其中 name 是文件名去掉 .md 后缀:
/review # 展开 review.md
/component Button # 带参数展开
/component Button "click handler" # 多参数
第 9 章 快捷键
9.1 快捷键系统
所有键盘快捷键可通过 ~/.pi/agent/keybindings.json 自定义。每个操作可以绑定一个或多个按键。编辑配置文件后,运行 /reload 使更改生效,无需重启会话。
9.2 按键格式
modifier+key 格式,其中修饰键为 ctrl、shift、alt(可组合),按键包括:
- 字母 :
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、ctrl+1 等。
9.3 编辑器光标移动
| 按键 ID | 默认值 | 说明 |
|---|---|---|
tui.editor.cursorUp |
up |
向上移动光标 |
tui.editor.cursorDown |
down |
向下移动光标 |
tui.editor.cursorLeft |
left, ctrl+b |
向左移动光标 |
tui.editor.cursorRight |
right, ctrl+f |
向右移动光标 |
tui.editor.cursorWordLeft |
alt+left, ctrl+left, alt+b |
光标左移一个单词 |
tui.editor.cursorWordRight |
alt+right, ctrl+right, alt+f |
光标右移一个单词 |
tui.editor.cursorLineStart |
home, ctrl+a |
移到行首 |
tui.editor.cursorLineEnd |
end, ctrl+e |
移到行尾 |
tui.editor.jumpForward |
ctrl+] |
向前跳到字符 |
tui.editor.jumpBackward |
ctrl+alt+] |
向后跳到字符 |
tui.editor.pageUp |
pageUp |
向上翻页 |
tui.editor.pageDown |
pageDown |
向下翻页 |
9.4 编辑器删除操作
| 按键 ID | 默认值 | 说明 |
|---|---|---|
tui.editor.deleteCharBackward |
backspace |
向后删除字符 |
tui.editor.deleteCharForward |
delete, ctrl+d |
向前删除字符 |
tui.editor.deleteWordBackward |
ctrl+w, alt+backspace |
向后删除单词 |
tui.editor.deleteWordForward |
alt+d, alt+delete |
向前删除单词 |
tui.editor.deleteToLineStart |
ctrl+u |
删除到行首 |
tui.editor.deleteToLineEnd |
ctrl+k |
删除到行尾 |
9.5 输入控制
| 按键 ID | 默认值 | 说明 |
|---|---|---|
tui.input.newLine |
shift+enter |
插入新行 |
tui.input.submit |
enter |
提交输入 |
tui.input.tab |
tab |
Tab / 自动补全 |
9.6 Kill Ring
| 按键 ID | 默认值 | 说明 |
|---|---|---|
tui.editor.yank |
ctrl+y |
粘贴最近删除的文本 |
tui.editor.yankPop |
alt+y |
在 yank 后循环已删除文本 |
tui.editor.undo |
ctrl+- |
撤销上次编辑 |
9.7 剪贴板与选择
| 按键 ID | 默认值 | 说明 |
|---|---|---|
tui.input.copy |
ctrl+c |
复制选中内容 |
tui.select.up |
up |
选择向上 |
tui.select.down |
down |
选择向下 |
tui.select.pageUp |
pageUp |
列表中向上翻页 |
tui.select.pageDown |
pageDown |
列表中向下翻页 |
tui.select.confirm |
enter |
确认选择 |
tui.select.cancel |
escape, ctrl+c |
取消选择 |
9.8 应用控制
| 按键 ID | 默认值 | 说明 |
|---|---|---|
app.interrupt |
escape |
取消/中止 |
app.clear |
ctrl+c |
清空编辑器 |
app.exit |
ctrl+d |
退出(编辑器为空时) |
app.suspend |
ctrl+z(Windows 无默认值) |
挂起到后台 |
app.editor.external |
ctrl+g |
在外部编辑器中打开 |
app.clipboard.pasteImage |
ctrl+v(Windows 中 alt+v) |
从剪贴板粘贴图片 |
9.9 会话管理快捷键
| 按键 ID | 默认值 | 说明 |
|---|---|---|
app.session.new |
(无) | 开始新会话 |
app.session.tree |
(无) | 打开会话树导航 |
app.session.fork |
(无) | 分叉当前会话 |
app.session.resume |
(无) | 打开会话恢复选择器 |
app.session.togglePath |
ctrl+p |
切换路径显示 |
app.session.toggleSort |
ctrl+s |
切换排序模式 |
app.session.toggleNamedFilter |
ctrl+n |
切换仅命名过滤 |
app.session.rename |
ctrl+r |
重命名会话 |
app.session.delete |
ctrl+d |
删除会话 |
app.session.deleteNoninvasive |
ctrl+backspace |
查询为空时删除会话 |
9.10 模型与思维快捷键
| 按键 ID | 默认值 | 说明 |
|---|---|---|
app.model.select |
ctrl+l |
打开模型选择器 |
app.model.cycleForward |
ctrl+p |
循环到下一个模型 |
app.model.cycleBackward |
shift+ctrl+p |
循环到上一个模型 |
app.thinking.cycle |
shift+tab |
循环思维级别 |
app.thinking.toggle |
ctrl+t |
折叠/展开思维块 |
9.11 显示与消息队列快捷键
| 按键 ID | 默认值 | 说明 |
|---|---|---|
app.tools.expand |
ctrl+o |
折叠/展开工具输出 |
app.message.followUp |
alt+enter |
排队 follow-up 消息 |
app.message.dequeue |
alt+up |
将排队消息取回编辑器 |
9.12 Tree 导航快捷键
| 按键 ID | 默认值 | 说明 |
|---|---|---|
app.tree.foldOrUp |
ctrl+left, alt+left |
折叠当前分支段,或跳到上一段起始 |
app.tree.unfoldOrDown |
ctrl+right, alt+right |
展开当前分支段,或跳到下一段起始 |
app.tree.editLabel |
shift+l |
编辑选中节点的标签 |
app.tree.toggleLabelTimestamp |
shift+t |
切换树中标签时间戳 |
app.tree.filter.default |
ctrl+d |
设置树过滤为默认视图 |
app.tree.filter.noTools |
ctrl+t |
切换隐藏工具结果的过滤 |
app.tree.filter.userOnly |
ctrl+u |
切换仅显示用户消息 |
app.tree.filter.labeledOnly |
ctrl+l |
切换仅显示带标签条目 |
app.tree.filter.all |
ctrl+a |
切换显示所有条目 |
app.tree.filter.cycleForward |
ctrl+o |
向前循环过滤 |
app.tree.filter.cycleBackward |
shift+ctrl+o |
向后循环过滤 |
9.13 作用域模型选择器快捷键
| 按键 ID | 默认值 | 说明 |
|---|---|---|
app.models.save |
ctrl+s |
保存当前模型选择到设置 |
app.models.enableAll |
ctrl+a |
启用所有模型(或所有匹配搜索的) |
app.models.clearAll |
ctrl+x |
清空所有模型(或所有匹配搜索的) |
app.models.toggleProvider |
ctrl+p |
切换当前 Provider 的所有模型 |
app.models.reorderUp |
alt+up |
将选中模型上移 |
app.models.reorderDown |
alt+down |
将选中模型下移 |
9.14 自定义配置示例
Emacs 风格:
json
{
"tui.editor.cursorUp": ["up", "ctrl+p"],
"tui.editor.cursorDown": ["down", "ctrl+n"],
"tui.editor.cursorLeft": ["left", "ctrl+b"],
"tui.editor.cursorRight": ["right", "ctrl+f"],
"tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
"tui.editor.cursorWordRight": ["alt+right", "alt+f"],
"tui.editor.deleteCharForward": ["delete", "ctrl+d"],
"tui.editor.deleteCharBackward": ["backspace", "ctrl+h"],
"tui.input.newLine": ["shift+enter", "ctrl+j"]
}
Vim 风格:
json
{
"tui.editor.cursorUp": ["up", "alt+k"],
"tui.editor.cursorDown": ["down", "alt+j"],
"tui.editor.cursorLeft": ["left", "alt+h"],
"tui.editor.cursorRight": ["right", "alt+l"],
"tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
"tui.editor.cursorWordRight": ["alt+right", "alt+w"]
}
Windows 上的 binditng 示例:每个操作可以有单个按键或按键数组。用户配置覆盖默认值。
第 10 章 消息队列
10.1 概述
pi mono 支持在 Agent 工作时提交排队消息,实现非打断式对话引导。消息队列有两种类型:Steering 消息 和Follow-up 消息。
10.2 Steering 消息
Steering 消息用于在当前 Agent 执行期间"纠正航向":
- 提交方式 :在 Agent 流式输出时按 Enter
- 投递时机:当前助手 turn 执行完所有工具调用之后、下一个 LLM 调用之前
- 典型用途:"不要做 X,改做 Y"、"先检查一下错误再继续"
10.3 Follow-up 消息
Follow-up 消息用于在 Agent 完成后追加后续指令:
- 提交方式 :按 Alt+Enter
- 投递时机:Agent 完成所有工作、停止调用工具之后
- 典型用途:"做完之后,也检查一下 Z"、"然后写个总结"
10.4 排队消息管理
- Escape:中止并恢复排队消息到编辑器
- Alt+Up:将排队消息取回编辑器
10.5 投递模式配置
在 /settings 或 settings.json 中配置:
json
{
"steeringMode": "one-at-a-time",
"followUpMode": "one-at-a-time"
}
两种模式:
| 模式 | 说明 |
|---|---|
one-at-a-time |
默认。每次完成一个 turn 后投递一条排队消息,等待响应后再处理下一条 |
all |
一次投递所有排队消息 |
10.6 Transport 设置
transport 选项选择 Provider 传输偏好(适用于支持多种传输的 Provider):
json
{
"transport": "sse"
}
| 值 | 说明 |
|---|---|
sse |
Server-Sent Events(默认) |
websocket |
WebSocket |
auto |
由 Provider 决定 |
10.7 Windows 注意事项
Windows Terminal 默认将 Alt+Enter 绑定为全屏切换,会拦截 pi 的 follow-up 快捷键。需要在 Windows Terminal 的 settings.json 中重新映射:
json
{
"actions": [
{
"command": { "action": "sendInput", "input": "\u001b[13;3u" },
"keys": "alt+enter"
}
]
}
第四部分:会话管理
第 11 章 会话文件格式
11.1 JSONL 格式概述
会话以 JSONL(JSON Lines)格式存储,每个条目是一行 JSON 对象,具有 type 字段。会话条目通过 id/parentId 字段形成树形结构,支持原地分支而无需创建新文件。
11.2 文件位置
会话文件存储在:
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
其中 <path> 是工作目录路径(/ 替换为 -)。
可通过 --session-dir CLI 标志或 settings.json 中的 sessionDir 字段自定义存储目录:
json
{ "sessionDir": ".pi/sessions" }
当多个来源指定了会话目录时,--session-dir CLI 标志优先于 settings.json 中的 sessionDir。
11.3 会话版本
会话有三个版本,旧版本自动迁移:
| 版本 | 说明 |
|---|---|
| Version 1 | 线性条目序列(遗留格式,加载时自动迁移) |
| Version 2 | 树形结构,id/parentId 链接 |
| Version 3 | 重命名 hookMessage 角色为 custom(扩展统一) |
现有会话加载时自动迁移到当前版本(v3)。
11.4 删除会话
会话可通过删除其 .jsonl 文件来移除。pi 还支持从 /resume 交互式删除会话(选中会话后按 Ctrl+D,然后确认)。当可用时,pi 使用 trash CLI 避免永久删除。
11.5 创建与切换会话
bash
pi -c # 继续最近会话
pi -r # 浏览并选择历史会话
pi --no-session # 临时模式(不保存)
pi --session <path> # 使用指定会话文件或 ID
pi --fork <path> # Fork 指定会话文件或 ID 到新会话
第 12 章 消息类型
12.1 内容块
消息包含类型化的内容块数组:
TextContent:
typescript
interface TextContent {
type: "text";
text: string;
}
ImageContent:
typescript
interface ImageContent {
type: "image";
data: string; // base64 编码
mimeType: string; // 例如 "image/jpeg", "image/png"
}
ThinkingContent:
typescript
interface ThinkingContent {
type: "thinking";
thinking: string;
}
ToolCall:
typescript
interface ToolCall {
type: "toolCall";
id: string;
name: string;
arguments: Record<string, any>;
}
12.2 基础消息类型(来自 pi-ai)
UserMessage:
typescript
interface UserMessage {
role: "user";
content: string | (TextContent | ImageContent)[];
timestamp: number; // Unix 毫秒
}
AssistantMessage:
typescript
interface AssistantMessage {
role: "assistant";
content: (TextContent | ThinkingContent | ToolCall)[];
api: string;
provider: string;
model: string;
usage: Usage;
stopReason: "stop" | "length" | "toolUse" | "error" | "aborted";
errorMessage?: string;
timestamp: number;
}
ToolResultMessage:
typescript
interface ToolResultMessage {
role: "toolResult";
toolCallId: string;
toolName: string;
content: (TextContent | ImageContent)[];
details?: any;
isError: boolean;
timestamp: number;
}
Usage:
typescript
interface Usage {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
totalTokens: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
}
12.3 扩展消息类型(来自 pi-coding-agent)
BashExecutionMessage:
typescript
interface BashExecutionMessage {
role: "bashExecution";
command: string;
output: string;
exitCode: number | undefined;
cancelled: boolean;
truncated: boolean;
fullOutputPath?: string;
excludeFromContext?: boolean; // !! 前缀命令时为 true
timestamp: number;
}
CustomMessage:
typescript
interface CustomMessage {
role: "custom";
customType: string; // Extension 标识符
content: string | (TextContent | ImageContent)[];
display: boolean; // 是否在 TUI 中显示
details?: any; // Extension 特定元数据
timestamp: number;
}
BranchSummaryMessage:
typescript
interface BranchSummaryMessage {
role: "branchSummary";
summary: string;
fromId: string; // 分支来源的 Entry ID
timestamp: number;
}
CompactionSummaryMessage:
typescript
interface CompactionSummaryMessage {
role: "compactionSummary";
summary: string;
tokensBefore: number;
timestamp: number;
}
12.4 AgentMessage 联合类型
typescript
type AgentMessage =
| UserMessage
| AssistantMessage
| ToolResultMessage
| BashExecutionMessage
| CustomMessage
| BranchSummaryMessage
| CompactionSummaryMessage;
第 13 章 Entry 类型
13.1 Entry 基础
所有 Entry(除 SessionHeader)继承 SessionEntryBase:
typescript
interface SessionEntryBase {
type: string;
id: string; // 8 字符十六进制 ID
parentId: string | null; // 父 Entry ID(首条 Entry 为 null)
timestamp: string; // ISO 时间戳
}
13.2 SessionHeader
文件第一行。仅元数据,不属于树结构(无 id/parentId)。
json
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}
如果会话有父会话(通过 /fork、/clone 或 newSession({ parentSession }) 创建):
json
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}
13.3 SessionMessageEntry
对话中的消息。message 字段包含 AgentMessage。
json
{"type":"message","id":"a1b2c3d4","parentId":"prev1234","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}}
{"type":"message","id":"c3d4e5f6","parentId":"b2c3d4e5","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false}}
13.4 ModelChangeEntry
用户在会话中切换模型时记录。
json
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}
13.5 ThinkingLevelChangeEntry
用户更改思维/推理级别时记录。
json
{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}
13.6 CompactionEntry
上下文压缩时创建。存储较早消息的摘要。
json
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}
可选字段:
details:实现特定数据(如{ readFiles: string[], modifiedFiles: string[] })fromHook:如果由 Extension 生成则为true(遗留字段名)
13.7 BranchSummaryEntry
通过 /tree 切换分支时创建。捕获被放弃路径的上下文。
json
{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}
可选字段:
details:文件跟踪数据fromHook:Extension 生成标记
13.8 CustomEntry
扩展状态持久化。不参与 LLM 上下文。
json
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}
使用 customType 在重载时识别你的扩展条目。
13.9 CustomMessageEntry
扩展注入的参与 LLM 上下文的消息。
json
{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-extension","content":"Injected context...","display":true}
字段:
content:字符串或(TextContent | ImageContent)[]display:true= 在 TUI 中以独特样式显示,false= 隐藏details:可选扩展元数据(不发送给 LLM)
13.10 LabelEntry
用户定义的书签/标记。
json
{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}
设置 label 为 undefined 可清除标签。
13.11 SessionInfoEntry
会话元数据(如用户定义的显示名称)。通过 /name 命令或扩展中的 pi.setSessionName() 设置。
json
{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}
会话名称在会话选择器(/resume)中显示,替代第一条消息。
第 14 章 树形结构与会话分支
14.1 树形结构原理
会话条目形成树形结构:
-
首条条目
parentId: null -
每条后续条目通过
parentId指向其父条目 -
分支从较早的条目创建新的子条目
-
"叶"(leaf)是树中的当前位置
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← 当前叶
│
└─ [branch_summary] ─── [user msg] ← 替代分支
14.2 上下文构建
buildSessionContext() 从当前叶节点回溯到根节点,为 LLM 生成消息列表:
- 收集路径上的所有条目
- 提取当前模型和思维级别设置
- 如果路径上有
CompactionEntry:先发送摘要,然后是firstKeptEntryId之后的消息 - 将
BranchSummaryEntry和CustomMessageEntry转换为适当的消息格式
14.3 /tree 导航概述
/tree 命令提供会话历史的树形导航。与 /fork 的区别:
| 特性 | /fork |
/tree |
|---|---|---|
| 视图 | 用户消息扁平列表 | 完整树结构 |
| 动作 | 提取路径到新会话文件 | 在同一会话中更改叶节点 |
| 摘要 | 从不 | 可选(提示用户) |
| 事件 | session_before_fork / session_start (reason: "fork") |
session_before_tree / session_tree |
14.4 Tree UI
Tree UI 显示树形结构:
├─ user: "Hello, can you help..."
│ └─ assistant: "Of course! I can..."
│ ├─ user: "Let's try approach A..."
│ │ └─ assistant: "For approach A..."
│ │ └─ [compaction: 12k tokens]
│ │ └─ user: "That worked..." ← active
│ └─ user: "Actually, approach B..."
│ └─ assistant: "For approach B..."
高度为终端高度的一半,当前叶节点标记为 ← active。
14.5 Tree 控制键
| 按键 | 动作 |
|---|---|
| ↑/↓ | 导航(深度优先顺序) |
| ←/→ | 向上/向下翻页 |
| Ctrl+←/Ctrl+→ 或 Alt+←/Alt+→ | 折叠/展开分支段并跳转 |
| Shift+L | 设置或清除选中节点的标签 |
| Shift+T | 切换标签时间戳显示 |
| Enter | 选中节点 |
| Escape/Ctrl+C | 取消 |
| Ctrl+U | 切换:仅用户消息 |
| Ctrl+O | 切换:显示所有(含 custom/label 条目) |
折叠行为:
Ctrl+←或Alt+←:如果当前节点可折叠则折叠;否则跳到上一个可见分支段起始Ctrl+→或Alt+→:如果当前节点已折叠则展开;否则跳到下一个可见分支段起始或分支末端- 可折叠节点:根和分支段起始点
- 折叠的分支显示
⊞,展开的显示⊟ - 搜索和过滤更改重置所有折叠
14.6 选择行为
用户消息或自定义消息:
- 叶节点设置为选中节点的父节点 (根消息为
null) - 消息文本放入编辑器供重新提交
- 用户编辑后提交,创建新分支
非用户消息(助手、压缩等):
- 叶节点设置为选中节点
- 编辑器保持为空
- 用户从该点继续
选中根用户消息:
- 叶重置为
null(空对话) - 消息文本放入编辑器
- 用户实际从头开始
14.7 分支摘要
切换分支时,用户有三个选项:
- 无摘要 --- 立即切换,不总结
- 总结 --- 使用默认提示词生成摘要
- 带自定义提示词的总结 --- 打开编辑器输入额外聚焦指令
摘要范围:从旧叶节点回溯到与目标的共同祖先,遇到压缩节点时停止。
摘要存储 :以 BranchSummaryEntry 形式存储:
typescript
interface BranchSummaryEntry {
type: "branch_summary";
id: string;
parentId: string; // 新叶位置
timestamp: string;
fromId: string; // 放弃的旧叶
summary: string; // LLM 生成的摘要
details?: unknown; // 可选扩展数据
}
14.8 过滤模式
Tree 导航支持多种过滤模式:
| 模式 | 说明 |
|---|---|
| default | 隐藏 label 和 custom 条目 |
| no-tools | 隐藏工具结果 |
| user-only | 仅显示用户消息 |
| labeled-only | 仅显示带标签条目 |
| all | 显示所有条目 |
通过 Ctrl+O 循环切换过滤模式。
14.9 标签系统
- Shift+L:设置或清除选中节点的标签
- Shift+T:切换标签时间戳显示
- 标签在 Tree 选择器中显示为
[label-name] Shift+T后会在带标签节点旁显示最新的标签更改时间戳- 在每个分支点,活动子树首先显示;其他同级分支按时间戳排序(最旧优先)
第 15 章 压缩(Compaction)
15.1 概述
LLM 的上下文窗口有限。当对话变得过长时,pi mono 使用压缩(compaction)来总结较早的内容,同时保留最近的工作。
15.2 触发条件
压缩在以下情况触发:
contextTokens > contextWindow - reserveTokens
默认 reserveTokens 为 16384 个 token(可在 ~/.pi/agent/settings.json 或 <project-dir>/.pi/settings.json 中配置),为 LLM 响应保留空间。
也可通过 /compact [instructions] 手动触发,可选的指令用于聚焦摘要。
15.3 工作流程
-
查找切割点 :从最新消息向后回溯,累积 token 估计直到达到
keepRecentTokens(默认 20k) -
提取消息:收集从上一个保留边界(或会话开始)到切割点的消息
-
生成摘要:调用 LLM 生成结构化摘要
-
追加条目 :保存
CompactionEntry(包含摘要和firstKeptEntryId) -
重载 :会话重载,使用摘要 +
firstKeptEntryId之后的消息压缩前:
entry: 0 1 2 3 4 5 6 7 8 9
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
└────────┬───────┘ └──────────────┬──────────────┘
messagesToSummarize kept messages
↑
firstKeptEntryId (entry 4)压缩后(追加新条目):
entry: 0 1 2 3 4 5 6 7 8 9 10
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
└──────────┬──────┘ └──────────────────────┬───────────────────┘
不发送给 LLM 发送给 LLM
↑
从 firstKeptEntryId 开始
LLM 看到的内容:
┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
│ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
└────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑ └─────────────────┬────────────────┘
提示词 来自压缩 firstKeptEntryId 之后的消息
15.4 切割点规则
有效的切割点:
- 用户消息
- 助手消息
- BashExecution 消息
- 自定义消息(custom_message、branch_summary)
永远不要在工具结果处切割(它们必须与其工具调用在一起)。
15.5 Split Turn
一个 turn 从用户消息开始,包含所有助手响应和工具调用,直到下一个用户消息。通常压缩在 turn 边界处切割。
当单个 turn 超过 keepRecentTokens 时,切割点落在 turn 中间的助手消息处。这称为"split turn":
Split turn(单 turn 超预算):
entry: 0 1 2 3 4 5 6 7 8
┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐
│ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │
└─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑
turnStartIndex = 1 firstKeptEntryId = 7
│ │
└──── turnPrefixMessages (1-6) ───────┘
└── kept (7-8)
isSplitTurn = true
messagesToSummarize = [] (之前无完整 turn)
turnPrefixMessages = [usr, ass, tool, ass, tool, tool]
对于 split turn,pi 生成两个摘要并合并:
- 历史摘要:之前的上下文(如有)
- Turn 前缀摘要:split turn 的前半部分
15.6 摘要格式
压缩使用结构化摘要格式:
markdown
## Goal
[用户试图完成的目标]
## Constraints & Preferences
- [用户提到的需求]
## Progress
### Done
- [x] [已完成的任务]
### In Progress
- [ ] [当前工作]
### Blocked
- [问题(如有)]
## Key Decisions
- **[决策]**: [理由]
## Next Steps
1. [接下来应该发生什么]
## Critical Context
- [继续工作所需的数据]
<read-files>
path/to/file1.ts
path/to/file2.ts
</read-files>
<modified-files>
path/to/changed.ts
</modified-files>
15.7 消息序列化
摘要生成前,消息通过 serializeConversation() 转换为文本:
[User]: 用户说的话
[Assistant thinking]: 内部推理
[Assistant]: 响应文本
[Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
[Tool result]: 工具输出
工具结果在序列化时截断为 2000 个字符。超过限制的内容被替换为标记,指示截断了多少字符。这使摘要请求保持在合理的 token 预算内。
15.8 累积文件跟踪
压缩和分支摘要都累积地跟踪文件。当生成摘要时,pi 从以下来源提取文件操作:
- 被摘要消息中的工具调用
- 前一个压缩或分支摘要的
details(如有)
这意味着文件跟踪在多次压缩或嵌套分支摘要中累积,保留读取和修改文件的完整历史。
15.9 压缩设置
在 ~/.pi/agent/settings.json 或 <project-dir>/.pi/settings.json 中配置:
json
{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
| 设置 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
启用自动压缩 |
reserveTokens |
16384 |
为 LLM 响应保留的 tokens |
keepRecentTokens |
20000 |
保留的最近 tokens(不被摘要) |
设 "enabled": false 可禁用自动压缩。仍可通过 /compact 手动压缩。
15.10 自定义压缩(Extension)
扩展可以拦截并自定义压缩。监听 session_before_compact 事件:
typescript
pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, signal } = event;
// preparation.messagesToSummarize --- 待摘要的消息
// preparation.turnPrefixMessages --- split turn 前缀
// preparation.previousSummary --- 前一个压缩摘要
// preparation.fileOps --- 提取的文件操作
// preparation.tokensBefore --- 压缩前上下文 tokens
// preparation.firstKeptEntryId --- 保留消息起始点
// preparation.settings --- 压缩设置
// 取消:
return { cancel: true };
// 自定义摘要:
return {
compaction: {
summary: "你的摘要...",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
details: { /* 自定义数据 */ },
}
};
});
使用 serializeConversation 和 convertToLlm 将消息转换为文本后发送给自定义模型:
typescript
import { convertToLlm, serializeConversation } from "@mariozechner/pi-coding-agent";
pi.on("session_before_compact", async (event, ctx) => {
const { preparation } = event;
const conversationText = serializeConversation(
convertToLlm(preparation.messagesToSummarize)
);
const summary = await myModel.summarize(conversationText);
return {
compaction: {
summary,
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
}
};
});
第 16 章 分支摘要
16.1 触发条件
当使用 /tree 导航到不同分支时,pi 提供摘要离开的分支的选项。这将左分支的上下文注入到新分支中。
16.2 工作流程
-
查找共同祖先:旧位置和新位置共享的最深节点
-
收集条目:从旧叶节点回溯到共同祖先
-
预算分配:在 token 预算内包含消息(最新优先)
-
生成摘要:使用结构化格式调用 LLM
-
追加条目 :在导航点保存
BranchSummaryEntry导航前的树:
┌─ B ─ C ─ D (旧叶,被放弃) A ───┤ └─ E ─ F (目标)共同祖先:A
待摘要条目:B, C, D带摘要的导航后:
┌─ B ─ C ─ D ─ [B,C,D 的摘要] A ───┤ └─ E ─ F (新叶)
16.3 BranchSummaryEntry 结构
typescript
interface BranchSummaryEntry<T = unknown> {
type: "branch_summary";
id: string;
parentId: string;
timestamp: number;
summary: string;
fromId: string; // 导航来源的 Entry ID
fromHook?: boolean; // Extension 生成标记(遗留字段名)
details?: T; // 实现特定数据
}
16.4 分支摘要设置
json
{
"branchSummary": {
"reserveTokens": 16384,
"skipPrompt": false
}
}
| 设置 | 默认值 | 说明 |
|---|---|---|
reserveTokens |
16384 |
分支摘要保留的 tokens |
skipPrompt |
false |
跳过"总结分支?"提示(默认不生成摘要) |
16.5 自定义分支摘要(Extension)
监听 session_before_tree 事件:
typescript
pi.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
// preparation.targetId --- 目标位置
// preparation.oldLeafId --- 当前位置
// preparation.commonAncestorId --- 共享祖先
// preparation.entriesToSummarize --- 将被摘要的条目
// preparation.userWantsSummary --- 用户是否选择生成摘要
// 取消导航:
return { cancel: true };
// 提供自定义摘要(仅在 userWantsSummary 为 true 时使用):
if (preparation.userWantsSummary) {
return {
summary: {
summary: "你的摘要...",
details: { /* 自定义数据 */ },
}
};
}
});
第 17 章 SessionManager API
17.1 概述
SessionManager 提供了完整的会话操作 API,用于程序化管理会话。
17.2 静态创建方法
| 方法 | 说明 |
|---|---|
SessionManager.create(cwd, sessionDir?) |
创建新会话 |
SessionManager.open(path, sessionDir?) |
打开现有会话文件 |
SessionManager.continueRecent(cwd, sessionDir?) |
继续最近会话或创建新会话 |
SessionManager.inMemory(cwd?) |
内存会话(不持久化) |
SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) |
从另一项目的会话 fork |
17.3 静态列表方法
| 方法 | 说明 |
|---|---|
SessionManager.list(cwd, sessionDir?, onProgress?) |
列出目录的会话 |
SessionManager.listAll(onProgress?) |
列出所有项目的会话 |
17.4 实例方法 --- 会话管理
| 方法 | 说明 |
|---|---|
newSession(options?) |
开始新会话(选项:{ parentSession?: string }) |
setSessionFile(path) |
切换到不同会话文件 |
createBranchedSession(leafId) |
提取分支到新会话文件 |
17.5 实例方法 --- 追加条目
所有追加方法返回条目 ID。
| 方法 | 说明 |
|---|---|
appendMessage(message) |
添加消息 |
appendThinkingLevelChange(level) |
记录思维级别变更 |
appendModelChange(provider, modelId) |
记录模型变更 |
appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) |
添加压缩 |
appendCustomEntry(customType, data?) |
扩展状态(不在上下文中) |
appendSessionInfo(name) |
设置会话显示名称 |
appendCustomMessageEntry(customType, content, display, details?) |
扩展消息(在上下文中) |
appendLabelChange(targetId, label) |
设置/清除标签 |
17.6 实例方法 --- 树导航
| 方法 | 说明 |
|---|---|
getLeafId() |
当前位置 |
getLeafEntry() |
获取当前叶条目 |
getEntry(id) |
按 ID 获取条目 |
getBranch(fromId?) |
从条目回溯到根 |
getTree() |
获取完整树结构 |
getChildren(parentId) |
获取直接子条目 |
getLabel(id) |
获取条目的标签 |
branch(entryId) |
将叶移到较早的条目 |
resetLeaf() |
重置叶为 null(所有条目之前) |
branchWithSummary(id, summary, details?, fromHook?) |
带上下文摘要的分支 |
17.7 实例方法 --- 上下文与信息
| 方法 | 说明 |
|---|---|
buildSessionContext() |
获取消息、思维级别和模型 |
getEntries() |
所有条目(不含头部) |
getHeader() |
会话头部元数据 |
getSessionName() |
从最新 session_info 获取显示名称 |
getCwd() |
工作目录 |
getSessionDir() |
会话存储目录 |
getSessionId() |
会话 UUID |
getSessionFile() |
会话文件路径(内存会话为 undefined) |
isPersisted() |
是否保存到磁盘 |
17.8 解析示例
使用 TypeScript 解析会话文件:
typescript
import { readFileSync } from "fs";
const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");
for (const line of lines) {
const entry = JSON.parse(line);
switch (entry.type) {
case "session":
console.log(`Session v${entry.version ?? 1}: ${entry.id}`);
break;
case "message":
console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
break;
case "compaction":
console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`);
break;
case "branch_summary":
console.log(`[${entry.id}] Branch from ${entry.fromId}`);
break;
case "custom":
console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`);
break;
case "custom_message":
console.log(`[${entry.id}] Extension message (${entry.customType}): ${entry.content}`);
break;
case "label":
console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`);
break;
case "model_change":
console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`);
break;
case "thinking_level_change":
console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`);
break;
}
}
第五部分:设置与上下文
第 18 章 设置系统
18.1 设置文件位置与作用域
pi mono 使用 JSON 设置文件,项目设置覆盖全局设置:
| 位置 | 作用域 |
|---|---|
~/.pi/agent/settings.json |
全局(所有项目) |
.pi/settings.json |
项目(当前目录) |
直接编辑文件或通过 /settings 修改常用选项。
18.2 项目覆盖合并规则
项目设置(.pi/settings.json)覆盖全局设置。嵌套对象合并键:
json
// ~/.pi/agent/settings.json (全局)
{
"theme": "dark",
"compaction": { "enabled": true, "reserveTokens": 16384 }
}
// .pi/settings.json (项目)
{
"compaction": { "reserveTokens": 8192 }
}
// 合并结果
{
"theme": "dark",
"compaction": { "enabled": true, "reserveTokens": 8192 }
}
18.3 模型与思维级别
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
defaultProvider |
string | --- | 默认 Provider(如 "anthropic"、"openai") |
defaultModel |
string | --- | 默认模型 ID |
defaultThinkingLevel |
string | --- | "off"、"minimal"、"low"、"medium"、"high"、"xhigh" |
hideThinkingBlock |
boolean | false |
在输出中隐藏思维块 |
thinkingBudgets |
object | --- | 每个思维级别的自定义 token 预算 |
thinkingBudgets 配置:
json
{
"thinkingBudgets": {
"minimal": 1024,
"low": 4096,
"medium": 10240,
"high": 32768
}
}
18.4 UI 与显示
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
theme |
string | "dark" |
主题名称("dark"、"light" 或自定义) |
quietStartup |
boolean | false |
隐藏启动头 |
collapseChangelog |
boolean | false |
更新后显示精简 changelog |
enableInstallTelemetry |
boolean | true |
在 changelog 检测到更新后发送匿名版本/更新 ping |
doubleEscapeAction |
string | "tree" |
双 Escape 操作:"tree"、"fork" 或 "none" |
treeFilterMode |
string | "default" |
/tree 默认过滤:"default"、"no-tools"、"user-only"、"labeled-only"、"all" |
editorPaddingX |
number | 0 |
输入编辑器水平填充(0-3) |
autocompleteMaxVisible |
number | 5 |
自动补全下拉最大可见项(3-20) |
showHardwareCursor |
boolean | false |
显示终端光标 |
18.5 压缩设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
compaction.enabled |
boolean | true |
启用自动压缩 |
compaction.reserveTokens |
number | 16384 |
为 LLM 响应保留的 tokens |
compaction.keepRecentTokens |
number | 20000 |
保留的最近 tokens |
json
{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
18.6 分支摘要设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
branchSummary.reserveTokens |
number | 16384 |
分支摘要保留的 tokens |
branchSummary.skipPrompt |
boolean | false |
跳过 /tree 导航时的"总结分支?"提示 |
18.7 重试设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
retry.enabled |
boolean | true |
启用自动 Agent 级重试 |
retry.maxRetries |
number | 3 |
最大 Agent 级重试次数 |
retry.baseDelayMs |
number | 2000 |
Agent 级指数退避基础延迟(2s、4s、8s) |
retry.provider.timeoutMs |
number | SDK 默认 | Provider/SDK 请求超时(毫秒) |
retry.provider.maxRetries |
number | SDK 默认 | Provider/SDK 重试次数 |
retry.provider.maxRetryDelayMs |
number | 60000 |
服务端请求最大延迟上限(60s) |
当 Provider 请求的重试延迟超过 retry.provider.maxRetryDelayMs(例如 Google 的"配额将在 5 小时后重置"),请求立即失败并显示说明性错误,而不是静默等待。设为 0 可禁用上限。
json
{
"retry": {
"enabled": true,
"maxRetries": 3,
"baseDelayMs": 2000,
"provider": {
"timeoutMs": 3600000,
"maxRetries": 0,
"maxRetryDelayMs": 60000
}
}
}
18.8 消息投递设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
steeringMode |
string | "one-at-a-time" |
Steering 消息发送方式:"all" 或 "one-at-a-time" |
followUpMode |
string | "one-at-a-time" |
Follow-up 消息发送方式:"all" 或 "one-at-a-time" |
transport |
string | "sse" |
Provider 传输偏好:"sse"、"websocket" 或 "auto" |
18.9 终端与图片设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
terminal.showImages |
boolean | true |
在终端显示图片(如支持) |
terminal.imageWidthCells |
number | 60 |
内联图片首选宽度(终端单元) |
terminal.clearOnShrink |
boolean | false |
内容收缩时清空空行(可能闪烁) |
images.autoResize |
boolean | true |
将图片调整到最大 2000x2000 |
images.blockImages |
boolean | false |
阻止所有图片发送给 LLM |
18.10 Shell 设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
shellPath |
string | --- | 自定义 Shell 路径(如 Windows 上的 Cygwin) |
shellCommandPrefix |
string | --- | 每条 bash 命令的前缀(如 "shopt -s expand_aliases") |
npmCommand |
string[] | --- | npm 包管理操作使用的命令(如 ["mise", "exec", "node@20", "--", "npm"]) |
json
{
"npmCommand": ["mise", "exec", "node@20", "--", "npm"]
}
npmCommand 用于所有 npm 包管理操作,包括 npm root -g、安装、卸载和 git 包内的依赖安装。使用 argv 风格条目。配置 npmCommand 时,git 包依赖安装使用普通 install 以避免包装器中的 npm 特定标志。
18.11 会话目录设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sessionDir |
string | --- | 会话文件存储目录。接受绝对/相对路径和 ~ |
json
{ "sessionDir": ".pi/sessions" }
18.12 模型循环设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabledModels |
string[] | --- | Ctrl+P 循环的模型模式(与 --models CLI 格式相同) |
json
{
"enabledModels": ["claude-*", "gpt-4o", "gemini-2*"]
}
18.13 Markdown 设置
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
markdown.codeBlockIndent |
string | " " |
代码块缩进 |
18.14 资源设置
这些设置定义从哪里加载扩展、技能、提示词和主题。
~/.pi/agent/settings.json 中的路径相对于 ~/.pi/agent 解析。.pi/settings.json 中的路径相对于 .pi 解析。支持绝对路径和 ~。
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
packages |
array | [] |
npm/git 包 |
extensions |
string[] | [] |
本地扩展文件路径或目录 |
skills |
string[] | [] |
本地技能文件路径或目录 |
prompts |
string[] | [] |
本地提示词模板路径或目录 |
themes |
string[] | [] |
本地主题路径或目录 |
enableSkillCommands |
boolean | true |
将技能注册为 /skill:name 命令 |
数组支持 glob 模式和排除。使用 !pattern 排除。使用 +path 强制包含精确路径,-path 强制排除精确路径。
packages 字符串形式:加载包中的所有资源
json
{ "packages": ["pi-skills", "@org/my-extension"] }
packages 对象形式:过滤加载哪些资源
json
{
"packages": [
{
"source": "pi-skills",
"skills": ["brave-search", "transcribe"],
"extensions": []
}
]
}
18.15 完整配置示例
json
{
"defaultProvider": "anthropic",
"defaultModel": "claude-sonnet-4-20250514",
"defaultThinkingLevel": "medium",
"theme": "dark",
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
},
"retry": {
"enabled": true,
"maxRetries": 3
},
"enabledModels": ["claude-*", "gpt-4o"],
"packages": ["pi-skills"]
}
第 19 章 上下文文件
19.1 AGENTS.md 加载
pi 在启动时从以下位置加载 AGENTS.md(或 CLAUDE.md):
~/.pi/agent/AGENTS.md(全局)- 父目录(从 cwd 向上遍历)
- 当前目录
所有匹配的文件被连接。使用 AGENTS.md 放置项目指令、约定和常用命令。
19.2 遍历规则
pi 从当前工作目录向上遍历到 git 仓库根目录(或文件系统根目录),收集沿途所有目录中的 AGENTS.md 文件。每个目录中的文件按以下顺序加载:
AGENTS.md(首选)CLAUDE.md(备选)
这允许:
- 项目根目录放置项目级指令
- 子目录放置模块级指令
- 全局配置放置通用指令
19.3 系统提示词定制
替换默认系统提示词:
使用 .pi/SYSTEM.md(项目级)或 ~/.pi/agent/SYSTEM.md(全局级)替换默认系统提示词。
追加系统提示词:
使用 APPEND_SYSTEM.md 追加而不替换默认系统提示词。
通过 SDK 时,可通过 DefaultResourceLoader 的 systemPromptOverride 选项自定义:
typescript
const loader = new DefaultResourceLoader({
systemPromptOverride: () => "You are a helpful assistant.",
});
await loader.reload();
19.4 上下文文件的用途
AGENTS.md 典型内容:
markdown
# Project Guidelines
## Build Commands
- `npm run build` --- Build the project
- `npm test` --- Run all tests
- `npm run lint` --- Lint the code
## Conventions
- Use TypeScript strict mode
- Prefer `const` over `let`
- All functions must have JSDoc
## Architecture
- Source code in `src/`
- Tests in `test/`
- Configuration in `config/`
19.5 SDK 中的上下文文件覆盖
typescript
const loader = new DefaultResourceLoader({
agentsFilesOverride: (current) => ({
agentsFiles: [
...current.agentsFiles,
{ path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" },
],
}),
});
await loader.reload();
第六部分:定制体系
第 20 章 提示词模板
20.1 概述
提示词模板(Prompt Templates)是可复用的 Markdown 片段,在编辑器中输入 /name 即可展开为完整提示词。
20.2 加载位置
- 全局:
~/.pi/agent/prompts/*.md - 项目:
.pi/prompts/*.md - 包:
prompts/目录或package.json中的pi.prompts - 设置:
prompts数组指定文件或目录 - CLI:
--prompt-template <path>(可重复)
使用 --no-prompt-templates 禁用自动发现。
20.3 文件格式
markdown
---
description: Review staged git changes
argument-hint: "<PR-URL>"
---
Review the staged changes (`git diff --cached`). Focus on:
- Bugs and logic errors
- Security issues
- Error handling gaps
- 文件名即命令名。
review.md变为/review description可选。缺失时使用首个非空行argument-hint可选。在自动补全下拉中显示预期参数
20.4 参数支持
模板支持位置参数和切片:
| 语法 | 说明 |
|---|---|
$1, $2, ... |
位置参数 |
$@ 或 $ARGUMENTS |
所有参数拼接 |
${@:N} |
从第 N 个位置起的参数(1-indexed) |
${@:N:L} |
从第 N 个位置起的 L 个参数 |
示例模板:
markdown
---
description: Create a component
---
Create a React component named $1 with features: $@
使用方式:
/component Button # $1 = "Button"
/component Button "onClick handler" # $1 = "Button", $@ = all args
/component Button "onClick" "disabled" # 多参数
20.5 加载规则
prompts/目录中的发现是非递归的- 如果需要子目录中的模板,通过
prompts设置或包清单显式添加
20.6 argument-hint
使用 frontmatter 中的 argument-hint 显示自动补全中的预期参数。用 <尖括号> 表示必填参数,[方括号] 表示可选参数:
markdown
---
description: Review PRs from URLs with structured issue and code analysis
argument-hint: "<PR-URL>"
---
在自动补全下拉中渲染为:
→ pr <PR-URL> --- Review PRs from URLs with structured issue and code analysis
is <issue> --- Analyze GitHub issues (bugs or feature requests)
wr [instructions] --- Finish the current task end-to-end
cl --- Audit changelog entries before release
第 21 章 技能
21.1 概述
技能(Skills)是按需加载的自包含能力包,提供专业的工作流、设置说明、辅助脚本和参考文档。pi mono 实现了 Agent Skills 标准,对违规发出警告但保持宽容。
21.2 加载位置
pi 从以下位置加载技能:
- 全局:
~/.pi/agent/skills/~/.agents/skills/
- 项目:
.pi/skills/.agents/skills/(从 cwd 向上遍历到 git 仓库根目录)
- 包:
skills/目录或package.json中的pi.skills - 设置:
skills数组指定文件或目录 - CLI:
--skill <path>(可重复,即使--no-skills也生效)
发现规则:
- 在
~/.pi/agent/skills/和.pi/skills/中,直接根目录.md文件被发现为独立技能 - 在所有技能位置,包含
SKILL.md的目录被递归发现 - 在
~/.agents/skills/和项目.agents/skills/中,根目录.md文件被忽略
21.3 从其他工具共享技能
要使用 Claude Code 或 OpenAI Codex 的技能,将其目录添加到设置中:
json
{
"skills": [
"~/.claude/skills",
"~/.codex/skills"
]
}
对于项目级 Claude Code 技能,添加到 .pi/settings.json:
json
{
"skills": ["../.claude/skills"]
}
21.4 工作原理
- 启动时,pi 扫描技能位置并提取名称和描述
- 系统提示词以 XML 格式包含可用技能
- 任务匹配时,Agent 使用
read加载完整的 SKILL.md - Agent 遵循指令,使用相对路径引用脚本和资源
这是渐进式披露:只有描述始终在上下文中,完整指令按需加载。
21.5 技能目录结构
my-skill/
├── SKILL.md # 必需:frontmatter + 指令
├── scripts/ # 辅助脚本
│ └── process.sh
├── references/ # 按需加载的详细文档
│ └── api-reference.md
└── assets/
└── template.json
21.6 SKILL.md 格式
markdown
---
name: my-skill
description: What this skill does and when to use it. Be specific.
---
# My Skill
## Setup
Run once before first use:
```bash
cd /path/to/skill && npm install
```
## Usage
```bash
./scripts/process.sh <input>
```
使用相对于技能目录的路径:
markdown
See [the reference guide](references/REFERENCE.md) for details.
21.7 Frontmatter 字段
| 字段 | 必填 | 说明 |
|---|---|---|
name |
是 | 最大 64 字符。小写 a-z、0-9、连字符。必须匹配父目录名 |
description |
是 | 最大 1024 字符。技能功能和使用场景 |
license |
否 | 许可证名称或对捆绑文件的引用 |
compatibility |
否 | 最大 500 字符。环境要求 |
metadata |
否 | 任意键值映射 |
allowed-tools |
否 | 空格分隔的预批准工具列表(实验性) |
disable-model-invocation |
否 | 设为 true 时从系统提示词隐藏技能,用户必须使用 /skill:name |
21.8 名称规则
- 1-64 个字符
- 仅小写字母、数字、连字符
- 不允许前导/尾随连字符
- 不允许连续连字符
- 必须匹配父目录名
有效:pdf-processing、data-analysis、code-review
无效:PDF-Processing、-pdf、pdf--processing
21.9 描述最佳实践
描述决定 Agent 何时加载技能。要具体。
好的描述:
yaml
description: Extracts text and tables from PDF files, fills PDF forms, and merges multiple PDFs. Use when working with PDF documents.
差的描述:
yaml
description: Helps with PDFs.
21.10 验证
pi 根据 Agent Skills 标准验证技能。大多数问题产生警告但仍加载技能:
- 名称不匹配父目录
- 名称超过 64 个字符或包含无效字符
- 名称以连字符开头/结尾或有连续连字符
- 描述超过 1024 个字符
异常:缺少描述的技能不被加载。
名称冲突(不同位置同名)时发出警告并保留找到的第一个技能。
21.11 技能命令
技能注册为 /skill:name 命令:
bash
/skill:brave-search # 加载并执行技能
/skill:pdf-tools extract # 加载技能并带参数
命令后的参数作为 User: <args> 追加到技能内容中。
通过 enableSkillCommands 控制技能命令注册。
21.12 示例
markdown
---
name: brave-search
description: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content.
---
# Brave Search
## Setup
```bash
cd /path/to/brave-search && npm install
```
## Search
```bash
./search.js "query" # Basic search
./search.js "query" --content # Include page content
```
## Extract Page Content
```bash
./content.js https://example.com
```
21.13 推荐技能仓库
- Anthropic Skills --- 文档处理(docx、pdf、pptx、xlsx)、Web 开发
- Pi Skills --- Web 搜索、浏览器自动化、Google API、转录
第 22 章 主题
22.1 概述
主题(Themes)是定义 TUI 颜色的 JSON 文件。pi 内置 dark 和 light 两个主题。主题支持热重载:修改当前活动主题文件时,pi 立即应用更改。
22.2 加载位置
- 内置:
dark、light - 全局:
~/.pi/agent/themes/*.json - 项目:
.pi/themes/*.json - 包:
themes/目录或package.json中的pi.themes - 设置:
themes数组指定文件或目录 - CLI:
--theme <path>(可重复)
使用 --no-themes 禁用自动发现。
22.3 选择主题
通过 /settings 或 settings.json 中的 theme 字段选择:
json
{
"theme": "my-theme"
}
首次运行时,pi 检测终端背景并默认使用 dark 或 light。
22.4 创建自定义主题
- 创建主题文件:
bash
mkdir -p ~/.pi/agent/themes
vim ~/.pi/agent/themes/my-theme.json
- 定义包含所有必需颜色的主题:
json
{
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "my-theme",
"vars": {
"primary": "#00aaff",
"secondary": 242
},
"colors": {
"accent": "primary",
"border": "primary",
"borderAccent": "#00ffff",
"borderMuted": "secondary",
"success": "#00ff00",
"error": "#ff0000",
"warning": "#ffff00",
"muted": "secondary",
"dim": 240,
"text": "",
"thinkingText": "secondary",
"selectedBg": "#2d2d30",
"userMessageBg": "#2d2d30",
"userMessageText": "",
"customMessageBg": "#2d2d30",
"customMessageText": "",
"customMessageLabel": "primary",
"toolPendingBg": "#1e1e2e",
"toolSuccessBg": "#1e2e1e",
"toolErrorBg": "#2e1e1e",
"toolTitle": "primary",
"toolOutput": "",
"mdHeading": "#ffaa00",
"mdLink": "primary",
"mdLinkUrl": "secondary",
"mdCode": "#00ffff",
"mdCodeBlock": "",
"mdCodeBlockBorder": "secondary",
"mdQuote": "secondary",
"mdQuoteBorder": "secondary",
"mdHr": "secondary",
"mdListBullet": "#00ffff",
"toolDiffAdded": "#00ff00",
"toolDiffRemoved": "#ff0000",
"toolDiffContext": "secondary",
"syntaxComment": "secondary",
"syntaxKeyword": "primary",
"syntaxFunction": "#00aaff",
"syntaxVariable": "#ffaa00",
"syntaxString": "#00ff00",
"syntaxNumber": "#ff00ff",
"syntaxType": "#00aaff",
"syntaxOperator": "primary",
"syntaxPunctuation": "secondary",
"thinkingOff": "secondary",
"thinkingMinimal": "primary",
"thinkingLow": "#00aaff",
"thinkingMedium": "#00ffff",
"thinkingHigh": "#ff00ff",
"thinkingXhigh": "#ff0000",
"bashMode": "#ffaa00"
}
}
- 通过
/settings选择主题。
22.5 主题格式
json
{
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "my-theme",
"vars": {
"blue": "#0066cc",
"gray": 242
},
"colors": {
"accent": "blue",
"muted": "gray",
"text": "",
...
}
}
name必需且必须唯一vars可选。定义可复用颜色,在colors中引用colors必须定义全部 51 个必需令牌
$schema 字段启用编辑器自动补全和验证。
22.6 核心 UI 颜色(11 个)
| 令牌 | 用途 |
|---|---|
accent |
主要强调色(Logo、选中项、光标) |
border |
普通边框 |
borderAccent |
高亮边框 |
borderMuted |
微妙边框(编辑器) |
success |
成功状态 |
error |
错误状态 |
warning |
警告状态 |
muted |
次要文本 |
dim |
第三文本 |
text |
默认文本(通常 "") |
thinkingText |
思维块文本 |
22.7 背景与内容颜色(11 个)
| 令牌 | 用途 |
|---|---|
selectedBg |
选中行背景 |
userMessageBg |
用户消息背景 |
userMessageText |
用户消息文本 |
customMessageBg |
扩展消息背景 |
customMessageText |
扩展消息文本 |
customMessageLabel |
扩展消息标签 |
toolPendingBg |
工具框(待处理) |
toolSuccessBg |
工具框(成功) |
toolErrorBg |
工具框(错误) |
toolTitle |
工具标题 |
toolOutput |
工具输出文本 |
22.8 Markdown 颜色(10 个)
| 令牌 | 用途 |
|---|---|
mdHeading |
标题 |
mdLink |
链接文本 |
mdLinkUrl |
链接 URL |
mdCode |
行内代码 |
mdCodeBlock |
代码块内容 |
mdCodeBlockBorder |
代码块围栏 |
mdQuote |
引用文本 |
mdQuoteBorder |
引用边框 |
mdHr |
水平线 |
mdListBullet |
列表符号 |
22.9 工具差异颜色(3 个)
| 令牌 | 用途 |
|---|---|
toolDiffAdded |
添加的行 |
toolDiffRemoved |
删除的行 |
toolDiffContext |
上下行 |
22.10 语法高亮颜色(9 个)
| 令牌 | 用途 |
|---|---|
syntaxComment |
注释 |
syntaxKeyword |
关键字 |
syntaxFunction |
函数名 |
syntaxVariable |
变量 |
syntaxString |
字符串 |
syntaxNumber |
数字 |
syntaxType |
类型 |
syntaxOperator |
运算符 |
syntaxPunctuation |
标点 |
22.11 思维级别边框颜色(6 个)
编辑器边框颜色指示思维级别(从微妙到显著):
| 令牌 | 用途 |
|---|---|
thinkingOff |
思维关闭 |
thinkingMinimal |
最小思维 |
thinkingLow |
低思维 |
thinkingMedium |
中等思维 |
thinkingHigh |
高思维 |
thinkingXhigh |
极高思维 |
22.12 Bash 模式颜色(1 个)
| 令牌 | 用途 |
|---|---|
bashMode |
bash 模式编辑器边框(! 前缀时) |
22.13 颜色值格式
支持四种格式:
| 格式 | 示例 | 说明 |
|---|---|---|
| 十六进制 | "#ff0000" |
6 位十六进制 RGB |
| 256 色 | 39 |
xterm 256 色调色板索引(0-255) |
| 变量 | "primary" |
对 vars 条目的引用 |
| 默认 | "" |
终端默认颜色 |
22.14 HTML 导出颜色(可选)
export 部分控制 /export HTML 输出的颜色。如果省略,颜色从 userMessageBg 派生:
json
{
"export": {
"pageBg": "#18181e",
"cardBg": "#1e1e24",
"infoBg": "#3c3728"
}
}
22.15 调色建议
深色终端:使用明亮、饱和的颜色,高对比度。
浅色终端:使用较深、柔和的颜色,低对比度。
颜色和谐 :从基础调色板开始(Nord、Gruvbox、Tokyo Night),在 vars 中定义,在 colors 中一致引用。
测试:用不同消息类型、工具状态、Markdown 内容和长换行文本检查主题。
VS Code :设置 terminal.integrated.minimumContrastRatio 为 1 以获得准确颜色。
第 23 章 包管理
23.1 概述
Pi 包(Pi Packages)将扩展、技能、提示词模板和主题打包在一起,以便通过 npm 或 git 与他人分享。
安全警告:Pi 包以完整系统权限运行。扩展执行任意代码,技能可以指示模型执行任何操作包括运行可执行文件。安装第三方包前请审查源代码。
23.2 安装与管理
bash
pi install npm:@foo/bar@1.0.0
pi install git:github.com/user/repo@v1
pi install https://github.com/user/repo
pi install /absolute/path/to/package
pi remove npm:@foo/bar
pi uninstall npm:@foo/bar # remove 的别名
pi list # 显示已安装的包
pi update # 更新所有非固定的包
pi config # 启用/禁用包资源
默认情况下,install 和 remove 写入全局设置(~/.pi/agent/settings.json)。使用 -l 写入项目设置(.pi/settings.json)。
要试用包而不安装,使用 --extension 或 -e:
bash
pi -e npm:@foo/bar
pi -e git:github.com/user/repo
23.3 包来源类型
npm 包:
npm:@scope/pkg@1.2.3
npm:pkg
- 带版本规范的包被固定,
pi update跳过 - 全局安装使用
npm install -g - 项目安装在
.pi/npm/下 - 设置
npmCommand以固定 npm 操作
Git 包:
git:github.com/user/repo@v1
git:git@github.com:user/repo@v1
https://github.com/user/repo@v1
ssh://git@github.com/user/repo@v1
- 不带
git:前缀时,仅接受协议 URL(https://、http://、ssh://、git://) - 带
git:前缀时,接受简写格式(包括github.com/user/repo和git@github.com:user/repo) - HTTPS 和 SSH URL 都支持
- SSH URL 自动使用配置的 SSH 密钥
- 引用固定包并跳过
pi update - 克隆到
~/.pi/agent/git/<host>/<path>(全局)或.pi/git/<host>/<path>(项目) - 克隆或拉取后,如果存在
package.json则运行npm install
SSH 示例:
bash
pi install git:git@github.com:user/repo
pi install ssh://git@github.com/user/repo
pi install git:git@github.com:user/repo@v1.0.0
本地路径:
/absolute/path/to/package
./relative/path/to/package
本地路径指向磁盘上的文件或目录,直接添加到设置中不复制。如果是文件,加载为单个扩展;如果是目录,pi 使用包规则加载资源。
23.4 创建 Pi 包
在 package.json 中添加 pi 清单或使用约定目录。包含 pi-package 关键字以便发现:
json
{
"name": "my-package",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
"skills": ["./skills"],
"prompts": ["./prompts"],
"themes": ["./themes"]
}
}
路径相对于包根目录。数组支持 glob 模式和 ! 排除。
23.5 画廊元数据
包画廊 显示标记为 pi-package 的包。添加 video 或 image 字段显示预览:
json
{
"name": "my-package",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
"video": "https://example.com/demo.mp4",
"image": "https://example.com/screenshot.png"
}
}
- video:仅 MP4。桌面端悬停时自动播放,点击打开全屏播放器
- image:PNG、JPEG、GIF 或 WebP。显示为静态预览
两者同时设置时,video 优先。
23.6 约定目录
如果没有 pi 清单,pi 从以下约定目录自动发现资源:
extensions/--- 加载.ts和.js文件skills/--- 递归查找包含SKILL.md的目录,加载根目录.md文件prompts/--- 加载.md文件themes/--- 加载.json文件
23.7 依赖管理
第三方运行时依赖属于 package.json 中的 dependencies。安装包时 pi 运行 npm install,依赖自动安装。
pi 捆绑核心包。如果导入以下包,将它们列为 peerDependencies("*" 范围),不打包:@mariozechner/pi-ai、@mariozechner/pi-agent-core、@mariozechner/pi-coding-agent、@mariozechner/pi-tui、typebox。
其他 pi 包必须打包在 tarball 中。添加到 dependencies 和 bundledDependencies,然后通过 node_modules/ 路径引用其资源:
json
{
"dependencies": {
"shitty-extensions": "^1.0.1"
},
"bundledDependencies": ["shitty-extensions"],
"pi": {
"extensions": ["extensions", "node_modules/shitty-extensions/extensions"],
"skills": ["skills", "node_modules/shitty-extensions/skills"]
}
}
23.8 包过滤
使用设置中的对象形式过滤包加载的资源:
json
{
"packages": [
"npm:simple-pkg",
{
"source": "npm:my-package",
"extensions": ["extensions/*.ts", "!extensions/legacy.ts"],
"skills": [],
"prompts": ["prompts/review.md"],
"themes": ["+themes/legacy.json"]
}
]
}
- 省略键加载该类型的全部
- 使用
[]不加载该类型 !pattern排除匹配+path强制包含精确路径-path强制排除精确路径- 过滤层叠在清单之上
23.9 启用/禁用资源
使用 pi config 启用或禁用来自已安装包和本地目录的扩展、技能、提示词模板和主题。适用于全局(~/.pi/agent)和项目(.pi/)范围。
23.10 作用域与去重
包可以同时出现在全局和项目设置中。如果同一包在两者中出现,项目条目优先。身份确定方式:
- npm:包名
- git:不带 ref 的仓库 URL
- 本地:解析后的绝对路径
第七部分:Extension 开发
第 24 章 Extension 概览与快速开始
24.1 什么是 Extension
Extension 是 TypeScript 模块,扩展 pi 的行为。它们可以订阅生命周期事件、注册自定义工具供 LLM 调用、添加命令等。
pi 可以创建 Extension。请它根据你的使用场景构建一个。
24.2 核心能力
- 自定义工具 --- 通过
pi.registerTool()注册 LLM 可调用的工具 - 事件拦截 --- 阻塞或修改工具调用、注入上下文、自定义压缩
- 用户交互 --- 通过
ctx.ui提示用户(选择、确认、输入、通知) - 自定义 UI 组件 --- 通过
ctx.ui.custom()实现完整的 TUI 组件和键盘输入 - 自定义命令 --- 通过
pi.registerCommand()注册/mycommand命令 - 会话持久化 --- 通过
pi.appendEntry()存储重启后仍保留的状态 - 自定义渲染 --- 控制工具调用/结果和消息在 TUI 中的显示方式
24.3 典型用例
- 权限门控(确认
rm -rf、sudo等危险操作) - Git 检查点(每个 turn 暂存,分支时恢复)
- 路径保护(阻止写入
.env、node_modules/) - 自定义压缩(用自己的方式总结对话)
- 交互式工具(问答、向导、自定义对话框)
- 有状态工具(待办列表、连接池)
- 外部集成(文件监视、Webhook、CI 触发器)
- 等待时运行游戏(是的,Doom 可以跑)
24.4 快速开始
创建 ~/.pi/agent/extensions/my-extension.ts:
typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// 响应事件
pi.on("session_start", async (_event, ctx) => {
ctx.ui.notify("Extension loaded!", "info");
});
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
}
});
// 注册自定义工具
pi.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
parameters: Type.Object({
name: Type.String({ description: "Name to greet" }),
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
return {
content: [{ type: "text", text: `Hello, ${params.name}!` }],
details: {},
};
},
});
// 注册命令
pi.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
},
});
}
用 --extension(或 -e)标志测试:
bash
pi -e ./my-extension.ts
24.5 Extension 位置
安全警告:Extension 以完整系统权限运行,可以执行任意代码。仅从可信来源安装。
Extension 从以下位置自动发现:
| 位置 | 作用域 |
|---|---|
~/.pi/agent/extensions/*.ts |
全局(所有项目) |
~/.pi/agent/extensions/*/index.ts |
全局(子目录) |
.pi/extensions/*.ts |
项目级 |
.pi/extensions/*/index.ts |
项目级(子目录) |
通过 settings.json 添加额外路径:
json
{
"packages": [
"npm:@foo/bar@1.0.0",
"git:github.com/user/repo@v1"
],
"extensions": [
"/path/to/local/extension.ts",
"/path/to/local/extension/dir"
]
}
要在 /reload 时生效,将 Extension 放在 ~/.pi/agent/extensions/(全局)或 .pi/extensions/(项目级)。使用 pi -e ./path.ts 仅用于快速测试。
第 25 章 编写 Extension
25.1 可用导入
| 包 | 用途 |
|---|---|
@mariozechner/pi-coding-agent |
Extension 类型(ExtensionAPI、ExtensionContext、事件) |
typebox |
工具参数的 Schema 定义 |
@mariozechner/pi-ai |
AI 工具(StringEnum 用于 Google 兼容枚举) |
@mariozechner/pi-tui |
TUI 组件用于自定义渲染 |
npm 依赖也可用。在 Extension 旁添加 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 等)也可用。
25.2 Extension 结构
Extension 导出默认工厂函数,接收 ExtensionAPI。工厂可以是同步或异步的:
typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
// 订阅事件
pi.on("event_name", async (event, ctx) => {
// ctx.ui 用于用户交互
const ok = await ctx.ui.confirm("Title", "Are you sure?");
ctx.ui.notify("Done!", "success");
ctx.ui.setStatus("my-ext", "Processing...");
ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"]);
});
// 注册工具、命令、快捷键、标志
pi.registerTool({ ... });
pi.registerCommand("name", { ... });
pi.registerShortcut("ctrl+x", { ... });
pi.registerFlag("my-flag", { ... });
}
Extension 通过 jiti 加载,所以 TypeScript 无需编译即可工作。
如果工厂返回 Promise,pi 会在继续启动前等待它完成。这意味着异步初始化在 session_start 之前、resources_discover 之前、以及通过 pi.registerProvider() 排队的 Provider 注册之前完成。
25.3 异步工厂函数
使用异步工厂进行一次性启动工作,如获取远程配置或动态发现可用模型:
typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default async function (pi: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = (await response.json()) as {
data: Array<{
id: string;
name?: string;
context_window?: number;
max_tokens?: number;
}>;
};
pi.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "LOCAL_OPENAI_API_KEY",
api: "openai-completions",
models: payload.data.map((model) => ({
id: model.id,
name: model.name ?? model.id,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: model.context_window ?? 128000,
maxTokens: model.max_tokens ?? 4096,
})),
});
}
25.4 Extension 风格
单文件 --- 最简单,适合小扩展:
~/.pi/agent/extensions/
└── my-extension.ts
目录 + index.ts --- 适合多文件扩展:
~/.pi/agent/extensions/
└── my-extension/
├── index.ts # 入口点(导出默认函数)
├── tools.ts # 辅助模块
└── utils.ts # 辅助模块
包含依赖的目录 --- 需要 npm 包的扩展:
~/.pi/agent/extensions/
└── my-extension/
├── package.json
├── package-lock.json
├── node_modules/
└── src/
└── index.ts
json
{
"name": "my-extension",
"dependencies": {
"zod": "^3.0.0",
"chalk": "^5.0.0"
},
"pi": {
"extensions": ["./src/index.ts"]
}
}
第 26 章 事件系统
26.1 生命周期概览
pi 启动
│
├─► session_start { reason: "startup" }
└─► resources_discover { reason: "startup" }
│
▼
用户发送提示 ─────────────────────────────────────────┐
│ │
├─► input(可拦截、转换或处理) │
├─► before_agent_start(可注入消息、修改系统提示词) │
├─► agent_start │
├─► message_start / message_update / message_end │
│ │
│ ┌─── turn(LLM 调用工具时重复) ───┐ │
│ │ │ │
│ ├─► turn_start │ │
│ ├─► context(可修改消息) │ │
│ ├─► before_provider_request │ │
│ ├─► after_provider_response │ │
│ │ │ │
│ │ LLM 响应,可能调用工具: │ │
│ │ ├─► tool_execution_start │ │
│ │ ├─► tool_call(可阻塞) │ │
│ │ ├─► tool_execution_update │ │
│ │ ├─► tool_result(可修改) │ │
│ │ └─► tool_execution_end │ │
│ │ │ │
│ └─► turn_end │ │
│ │
└─► agent_end │
│
用户发送另一条提示 ◄──────────────────────────────────┘
26.2 资源事件
resources_discover
在 session_start 后触发,扩展可贡献额外的技能、提示词和主题路径。启动路径使用 reason: "startup",重载使用 reason: "reload"。
typescript
pi.on("resources_discover", async (event, _ctx) => {
// event.cwd --- 当前工作目录
// event.reason --- "startup" | "reload"
return {
skillPaths: ["/path/to/skills"],
promptPaths: ["/path/to/prompts"],
themePaths: ["/path/to/themes"],
};
});
26.3 会话事件
session_start
会话启动、加载或重载时触发。
typescript
pi.on("session_start", async (event, ctx) => {
// event.reason --- "startup" | "reload" | "new" | "resume" | "fork"
// event.previousSessionFile --- "new"、"resume"、"fork" 时存在
ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
});
session_before_switch
在开始新会话(/new)或切换会话(/resume)之前触发。
typescript
pi.on("session_before_switch", async (event, ctx) => {
// event.reason --- "new" 或 "resume"
// event.targetSessionFile --- 切换目标会话文件(仅 "resume")
if (event.reason === "new") {
const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
if (!ok) return { cancel: true };
}
});
session_before_fork
通过 /fork 分叉或 /clone 克隆时触发。
typescript
pi.on("session_before_fork", async (event, ctx) => {
// event.entryId --- 选中的条目 ID
// event.position --- /fork 为 "before",/clone 为 "at"
return { cancel: true }; // 取消分叉/克隆
});
session_before_compact / session_compact
压缩时触发。详见第 15 章。
typescript
pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, signal } = event;
return { cancel: true };
// 或提供自定义摘要
return {
compaction: {
summary: "...",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
}
};
});
session_before_tree / session_tree
/tree 导航时触发。详见第 14 章。
typescript
pi.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
return { cancel: true };
});
session_shutdown
Extension 运行时关闭前触发。
typescript
pi.on("session_shutdown", async (event, ctx) => {
// event.reason --- "quit" | "reload" | "new" | "resume" | "fork"
// event.targetSessionFile --- 会话替换流程的目标会话
});
26.4 Agent 事件
before_agent_start
用户提交提示后、Agent 循环前触发。可注入消息和/或修改系统提示词。
typescript
pi.on("before_agent_start", async (event, ctx) => {
// event.prompt --- 用户提示文本
// event.images --- 附加图片
// event.systemPrompt --- 当前链式系统提示词
// event.systemPromptOptions --- 构建系统提示词的结构化选项
return {
// 注入持久消息(存储在会话中,发送给 LLM)
message: {
customType: "my-extension",
content: "Additional context for the LLM",
display: true,
},
// 替换本次 turn 的系统提示词
systemPrompt: event.systemPrompt + "\n\nExtra instructions...",
};
});
systemPromptOptions 字段提供访问 pi 用于构建系统提示词的相同结构化数据------自定义提示词、指南、工具片段、上下文文件、技能------无需重新发现资源。
agent_start / agent_end
每个用户提示触发一次。
typescript
pi.on("agent_start", async (_event, ctx) => {});
pi.on("agent_end", async (event, ctx) => {
// event.messages --- 本次提示生成的消息
});
turn_start / turn_end
每个 turn(一个 LLM 响应 + 工具调用)触发。
typescript
pi.on("turn_start", async (event, ctx) => {
// event.turnIndex, event.timestamp
});
pi.on("turn_end", async (event, ctx) => {
// event.turnIndex, event.message, event.toolResults
});
message_start / message_update / message_end
消息生命周期更新触发。
typescript
pi.on("message_start", async (event, ctx) => { /* event.message */ });
pi.on("message_update", async (event, ctx) => { /* event.message, event.assistantMessageEvent */ });
pi.on("message_end", async (event, ctx) => { /* event.message */ });
tool_execution_start / tool_execution_update / tool_execution_end
工具执行生命周期更新触发。
typescript
pi.on("tool_execution_start", async (event, ctx) => {
// event.toolCallId, event.toolName, event.args
});
pi.on("tool_execution_update", async (event, ctx) => {
// event.toolCallId, event.toolName, event.args, event.partialResult
});
pi.on("tool_execution_end", async (event, ctx) => {
// event.toolCallId, event.toolName, event.result, event.isError
});
context
每次 LLM 调用前触发。非破坏性修改消息。
typescript
pi.on("context", async (event, ctx) => {
const filtered = event.messages.filter(m => !shouldPrune(m));
return { messages: filtered };
});
before_provider_request
Provider 特定 payload 构建后、请求发送前触发。返回 undefined 保持 payload 不变,返回其他值替换 payload。
typescript
pi.on("before_provider_request", (event, ctx) => {
console.log(JSON.stringify(event.payload, null, 2));
});
after_provider_response
HTTP 响应接收后、流体消费前触发。
typescript
pi.on("after_provider_response", (event, ctx) => {
// event.status --- HTTP 状态码
// event.headers --- 归一化响应头
});
26.5 模型事件
model_select
通过 /model 命令、模型循环(Ctrl+P)或会话恢复切换模型时触发。
typescript
pi.on("model_select", async (event, ctx) => {
// event.model --- 新选中的模型
// event.previousModel --- 前一个模型
// event.source --- "set" | "cycle" | "restore"
});
26.6 工具事件
tool_call
在 tool_execution_start 后、工具执行前触发。可阻塞 。使用 isToolCallEventType 收窄类型。
event.input 可变。就地修改以修补工具参数。
typescript
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
pi.on("tool_call", async (event, ctx) => {
if (isToolCallEventType("bash", event)) {
event.input.command = `source ~/.profile\n${event.input.command}`;
if (event.input.command.includes("rm -rf")) {
return { block: true, reason: "Dangerous command" };
}
}
});
tool_result
工具执行完成后、tool_execution_end 前触发。可修改结果。
tool_result 处理程序像中间件一样链式处理:按扩展加载顺序运行,每个处理程序看到前一个处理程序更改后的最新结果。
typescript
import { isBashToolResult } from "@mariozechner/pi-coding-agent";
pi.on("tool_result", async (event, ctx) => {
if (isBashToolResult(event)) {
// event.details 类型为 BashToolDetails
}
// 修改结果:
return { content: [...], details: {...}, isError: false };
});
26.7 用户 Bash 事件
user_bash
用户执行 ! 或 !! 命令时触发。可拦截。
typescript
import { createLocalBashOperations } from "@mariozechner/pi-coding-agent";
pi.on("user_bash", (event, ctx) => {
// event.command --- bash 命令
// event.excludeFromContext --- !! 前缀时为 true
// event.cwd --- 工作目录
// 选项 1:提供自定义操作(如 SSH)
return { operations: remoteBashOps };
// 选项 2:包装 pi 内置本地 bash 后端
const local = createLocalBashOperations();
return {
operations: {
exec(command, cwd, options) {
return local.exec(`source ~/.profile\n${command}`, cwd, options);
}
}
};
// 选项 3:完整替换 --- 直接返回结果
return { result: { output: "...", exitCode: 0, cancelled: false, truncated: false } };
});
26.8 输入事件
input
用户输入接收后、扩展命令检查之后、技能和模板展开之前触发。事件看到原始输入文本,/skill:foo 和 /template 尚未展开。
处理顺序:
- 扩展命令(
/cmd)先检查 input事件触发 --- 可拦截、转换或处理- 技能命令(
/skill:name)展开 - 提示词模板(
/template)展开 - Agent 处理开始
typescript
pi.on("input", async (event, ctx) => {
// event.text --- 原始输入
// event.images --- 附加图片
// event.source --- "interactive" | "rpc" | "extension"
// 转换:重写输入
if (event.text.startsWith("?quick "))
return { action: "transform", text: `Respond briefly: ${event.text.slice(7)}` };
// 处理:不经过 LLM 直接响应
if (event.text === "ping") {
ctx.ui.notify("pong", "info");
return { action: "handled" };
}
return { action: "continue" }; // 默认:透传
});
结果:
continue--- 不变透传(处理程序返回空时的默认值)transform--- 修改文本/图片,然后继续展开handled--- 跳过 Agent(第一个返回此值的处理程序获胜)
第 27 章 ExtensionContext
27.1 概述
所有处理程序接收 ctx: ExtensionContext。
27.2 ctx.ui
UI 方法,用于用户交互。详见第 31 章。
27.3 ctx.hasUI
在打印模式(-p)和 JSON 模式中为 false。在交互和 RPC 模式中为 true。在 RPC 模式中,对话方法(select、confirm、input、editor)通过扩展 UI 子协议工作。
27.4 ctx.cwd
当前工作目录。
27.5 ctx.sessionManager
对会话状态的只读访问。详见第 17 章。
27.6 ctx.modelRegistry / ctx.model
对模型和 API Key 的访问。
27.7 ctx.signal
当前 Agent 中断信号,或没有 Agent turn 活跃时为 undefined。
用于扩展处理程序启动的嵌套异步工作的中断感知:
typescript
pi.on("tool_result", async (event, ctx) => {
const response = await fetch("https://example.com/api", {
method: "POST",
body: JSON.stringify(event),
signal: ctx.signal,
});
});
27.8 控制流
| 方法 | 说明 |
|---|---|
ctx.isIdle() |
Agent 是否空闲 |
ctx.abort() |
中止当前操作 |
ctx.hasPendingMessages() |
是否有待处理消息 |
27.9 ctx.shutdown()
请求优雅关闭 pi。在交互模式中延迟到 Agent 空闲时执行。在所有上下文中可用(事件处理程序、工具、命令)。
typescript
pi.on("tool_call", (event, ctx) => {
if (isFatal(event.input)) {
ctx.shutdown();
}
});
27.10 ctx.getContextUsage()
返回当前模型的上下文使用率。
typescript
const usage = ctx.getContextUsage();
if (usage && usage.tokens > 100_000) {
// ...
}
27.11 ctx.compact()
触发压缩,不等待完成。
typescript
ctx.compact({
customInstructions: "Focus on recent changes",
onComplete: (result) => { ctx.ui.notify("Done", "info"); },
onError: (error) => { ctx.ui.notify(`Failed: ${error.message}`, "error"); },
});
27.12 ctx.getSystemPrompt()
返回 pi 当前的系统提示词字符串。
typescript
pi.on("before_agent_start", (event, ctx) => {
const prompt = ctx.getSystemPrompt();
console.log(`System prompt length: ${prompt.length}`);
});
第 28 章 ExtensionCommandContext
28.1 概述
命令处理程序接收 ExtensionCommandContext,它扩展了 ExtensionContext 并增加了会话控制方法。这些方法仅在命令中可用,因为从事件处理程序调用可能死锁。
28.2 ctx.waitForIdle()
等待 Agent 完成流式输出:
typescript
pi.registerCommand("my-cmd", {
handler: async (args, ctx) => {
await ctx.waitForIdle();
// Agent 现在空闲,安全修改会话
},
});
28.3 ctx.newSession()
创建新会话:
typescript
const parentSession = ctx.sessionManager.getSessionFile();
const kickoff = "Continue in the replacement session";
const result = await ctx.newSession({
parentSession,
setup: async (sm) => {
sm.appendMessage({
role: "user",
content: [{ type: "text", text: "Context from previous session..." }],
timestamp: Date.now(),
});
},
withSession: async (ctx) => {
await ctx.sendUserMessage(kickoff);
},
});
28.4 ctx.fork()
从特定条目分叉,创建新会话文件:
typescript
const result = await ctx.fork("entry-id-123", {
withSession: async (ctx) => {
ctx.ui.notify("Now in the forked session", "info");
},
});
const cloneResult = await ctx.fork("entry-id-456", { position: "at" });
选项:
position:"before"(默认)在选中用户消息前分叉,"at"复制当前分支
28.5 ctx.navigateTree()
导航到会话树中的不同点:
typescript
const result = await ctx.navigateTree("entry-id-456", {
summarize: true,
customInstructions: "Focus on error handling changes",
replaceInstructions: false,
label: "review-checkpoint",
});
28.6 ctx.switchSession()
切换到不同会话文件:
typescript
const result = await ctx.switchSession("/path/to/session.jsonl", {
withSession: async (ctx) => {
await ctx.sendUserMessage("Resume work");
},
});
28.7 ctx.reload()
运行与 /reload 相同的重载流程。
typescript
pi.registerCommand("reload-runtime", {
handler: async (_args, ctx) => {
await ctx.reload();
return;
},
});
28.8 会话替换生命周期
withSession 接收全新的 ReplacedSessionContext。关键行为:
withSession在旧会话发出session_shutdown、旧运行时拆除、新会话绑定、新 Extension 实例接收session_start之后运行- 回调仍在原始闭包中运行,不在新 Extension 实例中
- 捕获的旧
pi/ 旧命令ctx对象在替换后失效 - 只使用传递给
withSession的ctx
安全模式:
typescript
await ctx.newSession({
withSession: async (ctx) => {
await ctx.sendUserMessage(kickoff);
},
});
不安全模式:
typescript
const oldSessionManager = ctx.sessionManager;
await ctx.newSession({
withSession: async (_ctx) => {
// 错误:旧对象已失效
oldSessionManager.getSessionFile();
},
});
第 29 章 ExtensionAPI 方法
29.1 pi.on()
订阅事件。详见第 26 章。
29.2 pi.registerTool()
注册 LLM 可调用的自定义工具。
pi.registerTool() 在扩展加载期间和启动后都有效。可以在 session_start、命令处理程序或其他事件处理程序中调用。新工具在同一会话中立即刷新。
typescript
import { Type } from "typebox";
import { StringEnum } from "@mariozechner/pi-ai";
pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "What this tool does",
promptSnippet: "Summarize or transform text according to action",
promptGuidelines: ["Use my_tool when the user asks to summarize previously generated text."],
parameters: Type.Object({
action: StringEnum(["list", "add"] as const),
text: Type.Optional(Type.String()),
}),
prepareArguments(args) {
return args;
},
async execute(toolCallId, params, signal, onUpdate, ctx) {
onUpdate?.({ content: [{ type: "text", text: "Working..." }] });
return {
content: [{ type: "text", text: "Done" }],
details: { result: "..." },
};
},
renderCall(args, theme, context) { ... },
renderResult(result, options, theme, context) { ... },
});
promptGuidelines 要点:每个指南必须命名它所指的工具------避免"Use this tool when..."因为 LLM 无法分辨"this"指的是哪个工具。写"Use my_tool when..."。
29.3 pi.sendMessage()
注入自定义消息到会话。
typescript
pi.sendMessage({
customType: "my-extension",
content: "Message text",
display: true,
details: { ... },
}, {
triggerTurn: true,
deliverAs: "steer",
});
选项:
deliverAs:"steer"(默认)--- 流式期间排入队列,在当前 turn 工具调用后投递"followUp"--- 等待 Agent 完成"nextTurn"--- 排入下一次用户提示
triggerTurn: true--- Agent 空闲时立即触发 LLM 响应
29.4 pi.sendUserMessage()
向 Agent 发送用户消息。与发送自定义消息的 sendMessage() 不同,这发送一个实际的用户消息,如同用户输入的。
typescript
// 简单文本消息
pi.sendUserMessage("What is 2+2?");
// 带内容数组
pi.sendUserMessage([
{ type: "text", text: "Describe this image:" },
{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
]);
// 流式期间 --- 必须指定投递模式
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
29.5 pi.appendEntry()
持久化扩展状态(不参与 LLM 上下文)。
typescript
pi.appendEntry("my-state", { count: 42 });
pi.on("session_start", async (_event, ctx) => {
for (const entry of ctx.sessionManager.getEntries()) {
if (entry.type === "custom" && entry.customType === "my-state") {
// 从 entry.data 重建
}
}
});
29.6 pi.setSessionName() / pi.getSessionName()
设置/获取会话显示名称。
typescript
pi.setSessionName("Refactor auth module");
const name = pi.getSessionName();
29.7 pi.setLabel()
设置或清除条目标签。标签持久化在会话中。
typescript
pi.setLabel(entryId, "checkpoint-before-refactor");
pi.setLabel(entryId, undefined); // 清除
const label = ctx.sessionManager.getLabel(entryId);
29.8 pi.registerCommand()
注册命令。多个扩展注册同名命令时,pi 保留所有并分配数字后缀(如 /review:1、/review:2)。
typescript
pi.registerCommand("stats", {
description: "Show session statistics",
handler: async (args, ctx) => {
const count = ctx.sessionManager.getEntries().length;
ctx.ui.notify(`${count} entries`, "info");
}
});
可选:添加参数自动补全:
typescript
import type { AutocompleteItem } from "@mariozechner/pi-tui";
pi.registerCommand("deploy", {
description: "Deploy to an environment",
getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
const envs = ["dev", "staging", "prod"];
const items = envs.map((e) => ({ value: e, label: e }));
const filtered = items.filter((i) => i.value.startsWith(prefix));
return filtered.length > 0 ? filtered : null;
},
handler: async (args, ctx) => { ctx.ui.notify(`Deploying: ${args}`, "info"); },
});
29.9 pi.getCommands()
获取当前会话中可用的斜杠命令。
typescript
const commands = pi.getCommands();
const bySource = commands.filter((c) => c.source === "extension");
每个条目有:
name:可调用命令名(不含前导/)description:人类可读描述source:"extension"/"prompt"/"skill"sourceInfo:来源信息(path、source、scope、origin、baseDir)
29.10 pi.registerMessageRenderer()
注册自定义 TUI 渲染器用于特定 customType 的消息。
29.11 pi.registerShortcut()
注册键盘快捷键。
typescript
pi.registerShortcut("ctrl+shift+p", {
description: "Toggle plan mode",
handler: async (ctx) => {
ctx.ui.notify("Toggled!");
},
});
29.12 pi.registerFlag()
注册 CLI 标志。
typescript
pi.registerFlag("plan", {
description: "Start in plan mode",
type: "boolean",
default: false,
});
if (pi.getFlag("plan")) {
// Plan mode enabled
}
29.13 pi.exec()
执行 Shell 命令。
typescript
const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
// result.stdout, result.stderr, result.code, result.killed
29.14 工具管理
typescript
const active = pi.getActiveTools(); // 当前活跃工具
const all = pi.getAllTools(); // 所有工具(含 name, description, parameters, sourceInfo)
pi.setActiveTools(["read", "bash"]); // 切换到只读
sourceInfo.source 值:
builtin--- 内置工具sdk--- 通过createAgentSession({ customTools })传入的工具- 扩展源元数据 --- 扩展注册的工具
29.15 pi.setModel()
设置当前模型。
29.16 pi.registerProvider() / pi.unregisterProvider()
注册/注销 Provider。详见第 6 章。
第 30 章 自定义工具
30.1 工具定义
完整的工具定义包含以下字段:
| 字段 | 说明 |
|---|---|
name |
工具名称(LLM 用它调用) |
label |
TUI 显示标签 |
description |
工具描述(LLM 用来决定何时调用) |
promptSnippet |
可选,系统提示词中 Available tools 的一行描述 |
promptGuidelines |
可选,追加到 Guidelines 部分的指南 |
parameters |
TypeBox Schema 定义参数 |
prepareArguments |
可选,Schema 验证前的参数兼容性处理 |
execute |
工具执行函数 |
renderCall |
可选,自定义工具调用渲染 |
renderResult |
可选,自定义工具结果渲染 |
30.2 execute 函数签名
typescript
async execute(
toolCallId: string, // 工具调用 ID
params: TParams, // Schema 验证后的参数
signal: AbortSignal, // 中断信号
onUpdate?: (update: ToolUpdate) => void, // 流式进度回调
ctx: ExtensionContext // 扩展上下文
): Promise<ToolResult>
30.3 流式进度
通过 onUpdate 回调报告执行进度:
typescript
async execute(toolCallId, params, signal, onUpdate, ctx) {
onUpdate?.({ content: [{ type: "text", text: "Working..." }] });
// ... 执行操作 ...
onUpdate?.({ content: [{ type: "text", text: "Processing 50%..." }] });
return {
content: [{ type: "text", text: "Done" }],
details: { result: "..." },
};
}
30.4 返回值
typescript
interface ToolResult {
content: (TextContent | ImageContent)[]; // 工具输出内容
details?: Record<string, any>; // 扩展元数据(不发送给 LLM)
isError?: boolean; // 是否为错误
}
30.5 动态工具注册
pi.registerTool() 在启动后也有效。可以在 session_start、命令处理程序或其他事件处理程序中调用。新工具在会话中立即刷新,无需 /reload。
使用 pi.setActiveTools() 在运行时启用或禁用工具。
第 31 章 自定义 UI
31.1 ctx.ui 方法全览
对话方法(等待用户响应):
| 方法 | 说明 |
|---|---|
ctx.ui.select(title, options) |
选择列表 |
ctx.ui.confirm(title, message) |
是/否确认 |
ctx.ui.input(title, placeholder) |
自由文本输入 |
ctx.ui.editor(title, prefill) |
多行文本编辑器 |
即发即忘方法(不等待响应):
| 方法 | 说明 |
|---|---|
ctx.ui.notify(message, type) |
通知(info/warning/error) |
ctx.ui.setStatus(key, text) |
设置页脚状态 |
ctx.ui.setWidget(key, lines, options?) |
设置编辑器上方/下方小组件 |
ctx.ui.setTitle(title) |
设置终端窗口标题 |
ctx.ui.setEditorText(text) |
设置编辑器文本 |
TUI 方法(交互模式完整功能):
| 方法 | 说明 |
|---|---|
ctx.ui.custom(factory, options?) |
自定义 TUI 组件 |
ctx.ui.setWorkingIndicator(config?) |
自定义工作指示器 |
ctx.ui.setFooter(factory?) |
自定义页脚 |
ctx.ui.setHeader(factory?) |
自定义头部 |
ctx.ui.setEditorComponent(factory?) |
自定义编辑器 |
ctx.ui.setToolsExpanded(expanded) |
展开/折叠工具输出 |
ctx.ui.getEditorText() |
获取编辑器文本 |
ctx.ui.pasteToEditor(content) |
粘贴到编辑器 |
ctx.ui.setTheme(themeName) |
设置主题 |
ctx.ui.getAllThemes() |
获取所有可用主题 |
ctx.ui.getTheme() |
获取当前主题 |
31.2 自定义组件模式
选择对话框(SelectList + DynamicBorder)
typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
pi.registerCommand("pick", {
handler: async (_args, ctx) => {
const items: SelectItem[] = [
{ value: "opt1", label: "Option 1", description: "First option" },
{ value: "opt2", label: "Option 2" },
];
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
const container = new Container();
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
container.addChild(new Text(theme.fg("accent", theme.bold("Pick an Option")), 1, 0));
const selectList = new SelectList(items, Math.min(items.length, 10), {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => theme.fg("accent", t),
description: (t) => theme.fg("muted", t),
});
selectList.onSelect = (item) => done(item.value);
selectList.onCancel = () => done(null);
container.addChild(selectList);
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
return {
render: (w) => container.render(w),
invalidate: () => container.invalidate(),
handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); },
};
});
if (result) ctx.ui.notify(`Selected: ${result}`, "info");
},
});
异步操作与取消(BorderedLoader)
typescript
import { BorderedLoader } from "@mariozechner/pi-coding-agent";
pi.registerCommand("fetch", {
handler: async (_args, ctx) => {
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
const loader = new BorderedLoader(tui, theme, "Fetching data...");
loader.onAbort = () => done(null);
fetchData(loader.signal)
.then((data) => done(data))
.catch(() => done(null));
return loader;
});
},
});
持久状态指示器
typescript
// 设置状态(显示在页脚)
ctx.ui.setStatus("my-ext", ctx.ui.theme.fg("accent", "● active"));
// 清除状态
ctx.ui.setStatus("my-ext", undefined);
工作指示器自定义
typescript
// 静态指示器
ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] });
// 自定义动画指示器
ctx.ui.setWorkingIndicator({
frames: [
ctx.ui.theme.fg("dim", "·"),
ctx.ui.theme.fg("accent", "●"),
],
intervalMs: 120,
});
// 隐藏指示器
ctx.ui.setWorkingIndicator({ frames: [] });
// 恢复默认
ctx.ui.setWorkingIndicator();
编辑器上方/下方小组件
typescript
// 简单字符串数组
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
// 编辑器下方
ctx.ui.setWidget("my-widget", ["Line 1"], { placement: "belowEditor" });
// 带主题
ctx.ui.setWidget("my-widget", (_tui, theme) => {
return {
render: () => items.map(i => theme.fg(i.done ? "success" : "dim", i.text)),
invalidate: () => {},
};
});
// 清除
ctx.ui.setWidget("my-widget", undefined);
自定义页脚
typescript
ctx.ui.setFooter((tui, theme, footerData) => ({
invalidate() {},
render(width: number): string[] {
return [`${ctx.model?.id} (${footerData.getGitBranch() || "no git"})`];
},
dispose: footerData.onBranchChange(() => tui.requestRender()),
}));
ctx.ui.setFooter(undefined); // 恢复默认
自定义编辑器(Vim 模式)
typescript
import { CustomEditor, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
type Mode = "normal" | "insert";
class VimEditor extends CustomEditor {
private mode: Mode = "insert";
handleInput(data: string): void {
if (matchesKey(data, "escape")) {
if (this.mode === "insert") { this.mode = "normal"; return; }
super.handleInput(data); return;
}
if (this.mode === "insert") { super.handleInput(data); return; }
switch (data) {
case "i": this.mode = "insert"; return;
case "h": super.handleInput("\x1b[D"); return;
case "j": super.handleInput("\x1b[B"); return;
case "k": super.handleInput("\x1b[A"); return;
case "l": super.handleInput("\x1b[C"); return;
}
if (data.length === 1 && data.charCodeAt(0) >= 32) return;
super.handleInput(data);
}
render(width: number): string[] {
const lines = super.render(width);
if (lines.length > 0) {
const label = this.mode === "normal" ? " NORMAL " : " INSERT ";
const lastLine = lines[lines.length - 1]!;
lines[lines.length - 1] = truncateToWidth(lastLine, width - label.length, "") + label;
}
return lines;
}
}
export default function (pi: ExtensionAPI) {
pi.on("session_start", (_event, ctx) => {
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
new VimEditor(theme, keybindings)
);
});
}
关键点:
- 继承
CustomEditor(非基础Editor)以获得应用快捷键 - 对不处理的按键调用
super.handleInput(data) setEditorComponent接收接收tui、theme、keybindings的工厂函数- 传入
undefined恢复默认编辑器
第八部分:TUI 组件开发
第 32 章 TUI 组件系统
32.1 Component 接口
所有组件实现以下接口:
typescript
interface Component {
render(width: number): string[];
handleInput?(data: string): void;
wantsKeyRelease?: boolean;
invalidate(): void;
}
| 方法 | 说明 |
|---|---|
render(width) |
返回字符串数组(每行一个)。每行不得超过 width |
handleInput?(data) |
当组件有焦点时接收键盘输入 |
wantsKeyRelease? |
如果为 true,组件接收按键释放事件(Kitty 协议)。默认:false |
invalidate() |
清除缓存的渲染状态。主题更改时调用 |
TUI 在每行渲染输出末尾附加完整的 SGR 重置和 OSC 8 重置。样式不跨行传递。如果发出带样式的多行文本,请逐行重新应用样式或使用 wrapTextWithAnsi() 保留每行的样式。
32.2 Focusable 接口与 IME 支持
显示文本光标并需要 IME(输入法编辑器)支持的组件应实现 Focusable 接口:
typescript
import { CURSOR_MARKER, type Component, type Focusable } from "@mariozechner/pi-tui";
class MyInput implements Component, Focusable {
focused: boolean = false; // TUI 在焦点变化时设置
render(width: number): string[] {
const marker = this.focused ? CURSOR_MARKER : "";
return [`> ${beforeCursor}${marker}\x1b[7m${atCursor}\x1b[27m${afterCursor}`];
}
}
当 Focusable 组件有焦点时,TUI:
- 在组件上设置
focused = true - 扫描渲染输出中的
CURSOR_MARKER(零宽度 APC 转义序列) - 在该位置放置硬件终端光标
- 显示硬件光标
这使 IME 候选窗口出现在正确的位置,支持 CJK 输入法。
32.3 容器组件与嵌入式输入
当容器组件(对话框、选择器等)包含 Input 或 Editor 子组件时,容器必须实现 Focusable 并将焦点状态传播给子组件:
typescript
import { Container, type Focusable, Input } from "@mariozechner/pi-tui";
class SearchDialog extends Container implements Focusable {
private searchInput: Input;
private _focused = false;
get focused(): boolean { return this._focused; }
set focused(value: boolean) {
this._focused = value;
this.searchInput.focused = value; // 传播给子组件
}
constructor() {
super();
this.searchInput = new Input();
this.addChild(this.searchInput);
}
}
32.4 使用组件
在 Extension 中 通过 ctx.ui.custom():
typescript
pi.on("session_start", async (_event, ctx) => {
const handle = ctx.ui.custom(myComponent);
// handle.requestRender() --- 触发重新渲染
// handle.close() --- 恢复正常 UI
});
在自定义工具中 通过 pi.ui.custom():
typescript
async execute(toolCallId, params, onUpdate, ctx, signal) {
const handle = pi.ui.custom(myComponent);
handle.close();
}
32.5 Overlay
Overlay 在不清除屏幕的情况下在现有内容上渲染组件。传入 { overlay: true }:
typescript
const result = await ctx.ui.custom<string | null>(
(tui, theme, keybindings, done) => new MyDialog({ onClose: done }),
{ overlay: true }
);
定位和尺寸使用 overlayOptions:
typescript
const result = await ctx.ui.custom<string | null>(
(tui, theme, keybindings, done) => new SidePanel({ onClose: done }),
{
overlay: true,
overlayOptions: {
width: "50%",
minWidth: 40,
maxHeight: "80%",
anchor: "right-center",
offsetX: -2,
margin: 2,
visible: (termWidth, termHeight) => termWidth >= 80,
},
onHandle: (handle) => {
// handle.setHidden(true/false)
// handle.hide()
},
}
);
Overlay 组件在关闭时被处置。不要重用引用------创建新实例。
第 33 章 内置组件
33.1 Text
多行文本,带自动换行。
typescript
const text = new Text(
"Hello World", // 内容
1, // paddingX(默认:1)
1, // paddingY(默认:1)
(s) => bgGray(s) // 可选背景函数
);
text.setText("Updated");
33.2 Box
带填充和背景色的容器。
typescript
const box = new Box(1, 1, (s) => bgGray(s));
box.addChild(new Text("Content", 0, 0));
box.setBgFn((s) => bgBlue(s));
33.3 Container
垂直分组子组件。
typescript
const container = new Container();
container.addChild(component1);
container.addChild(component2);
container.removeChild(component1);
33.4 Spacer
空垂直空间。
typescript
const spacer = new Spacer(2); // 2 行空行
33.5 Markdown
渲染带语法高亮的 Markdown。
typescript
const md = new Markdown("# Title\n\nSome **bold** text", 1, 1, theme);
md.setText("Updated markdown");
33.6 Image
在支持的终端中渲染图片(Kitty、iTerm2、Ghostty、WezTerm)。
typescript
const image = new Image(
base64Data,
"image/png",
theme,
{ maxWidthCells: 80, maxHeightCells: 24 }
);
33.7 SelectList
选择列表组件,支持搜索和滚动。
typescript
const selectList = new SelectList(items, Math.min(items.length, 10), {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => theme.fg("accent", t),
description: (t) => theme.fg("muted", t),
scrollInfo: (t) => theme.fg("dim", t),
noMatch: (t) => theme.fg("warning", t),
});
selectList.onSelect = (item) => done(item.value);
selectList.onCancel = () => done(null);
33.8 SettingsList
设置开关列表。
typescript
const settingsList = new SettingsList(
items,
Math.min(items.length + 2, 15),
getSettingsListTheme(),
(id, newValue) => { /* 处理值变更 */ },
() => done(undefined),
{ enableSearch: true },
);
33.9 BorderedLoader
带边框的加载器,显示旋转器并处理 Escape 取消。
typescript
const loader = new BorderedLoader(tui, theme, "Fetching data...");
loader.onAbort = () => done(null);
第 34 章 键盘输入
34.1 matchesKey()
使用 matchesKey() 进行按键检测:
typescript
import { matchesKey, Key } from "@mariozechner/pi-tui";
handleInput(data: string) {
if (matchesKey(data, Key.up)) {
this.selectedIndex--;
} else if (matchesKey(data, Key.enter)) {
this.onSelect?.(this.selectedIndex);
} else if (matchesKey(data, Key.escape)) {
this.onCancel?.();
} else if (matchesKey(data, Key.ctrl("c"))) {
// Ctrl+C
}
}
34.2 Key 标识符
- 基础键:
Key.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"
第 35 章 行宽与工具函数
35.1 行宽限制
关键 :render() 返回的每行不得超过 width 参数。
typescript
import { visibleWidth, truncateToWidth } from "@mariozechner/pi-tui";
render(width: number): string[] {
return [truncateToWidth(this.text, width)];
}
35.2 工具函数
| 函数 | 说明 |
|---|---|
visibleWidth(str) |
获取显示宽度(忽略 ANSI 码) |
truncateToWidth(str, width, ellipsis?) |
截断到指定宽度,可选省略号 |
wrapTextWithAnsi(str, width) |
保留 ANSI 码的自动换行 |
第 36 章 主题集成
36.1 前景色
使用 theme.fg(color, text):
| 分类 | 颜色 |
|---|---|
| 通用 | text、accent、muted、dim |
| 状态 | 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 |
| 模式 | bashMode |
36.2 背景色
使用 theme.bg(color, text):selectedBg、userMessageBg、customMessageBg、toolPendingBg、toolSuccessBg、toolErrorBg
36.3 Markdown 主题
typescript
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
import { Markdown } from "@mariozechner/pi-tui";
renderResult(result, options, theme, context) {
const mdTheme = getMarkdownTheme();
return new Markdown(result.details.markdown, 0, 0, mdTheme);
}
36.4 失效与主题变更
当主题更改时,TUI 调用所有组件的 invalidate()。组件必须正确实现 invalidate() 以确保主题更改生效。
问题:如果组件预烘焙主题颜色到字符串并缓存,缓存的字符串包含旧主题的 ANSI 码。
解决方案 :使用主题颜色构建内容的组件必须在 invalidate() 被调用时重建内容:
typescript
class GoodComponent extends Container {
private message: string;
private content: Text;
constructor(message: string) {
super();
this.message = message;
this.content = new Text("", 1, 0);
this.addChild(this.content);
this.updateDisplay();
}
private updateDisplay(): void {
this.content.setText(theme.fg("accent", this.message));
}
override invalidate(): void {
super.invalidate();
this.updateDisplay();
}
}
不需要此模式的情况:使用主题回调时、简单容器时、无状态渲染时。
第 37 章 性能与缓存
37.1 渲染缓存
在可能时缓存渲染输出:
typescript
class CachedComponent {
private cachedWidth?: number;
private cachedLines?: string[];
render(width: number): string[] {
if (this.cachedLines && this.cachedWidth === width) {
return this.cachedLines;
}
// ... 计算行 ...
this.cachedWidth = width;
this.cachedLines = lines;
return lines;
}
invalidate(): void {
this.cachedWidth = undefined;
this.cachedLines = undefined;
}
}
37.2 触发重绘
状态更改后调用 invalidate(),然后 handle.requestRender() 触发重新渲染。
在 handleInput 中更新状态后调用 tui.requestRender():
typescript
handleInput(data: string): void {
if (matchesKey(data, Key.up)) {
this.selectedIndex--;
this.invalidate();
tui.requestRender(); // 触发重绘
}
}
第九部分:SDK 编程接口
第 38 章 SDK 快速开始
38.1 安装
bash
npm install @mariozechner/pi-coding-agent
SDK 包含在主包中,无需单独安装。
38.2 最小示例
typescript
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@mariozechner/pi-coding-agent";
const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
authStorage,
modelRegistry,
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("What files are in the current directory?");
38.3 核心概念
createAgentSession() 是创建单个 AgentSession 的主工厂函数。它使用 ResourceLoader 提供扩展、技能、提示词模板、主题和上下文文件。不提供时使用 DefaultResourceLoader 进行标准发现。
第 39 章 createAgentSession()
39.1 基本用法
typescript
// 最简:使用 DefaultResourceLoader
const { session } = await createAgentSession();
// 自定义:覆盖特定选项
const { session } = await createAgentSession({
model: myModel,
tools: [readTool, bashTool],
sessionManager: SessionManager.inMemory(),
});
39.2 返回值
typescript
interface CreateAgentSessionResult {
session: AgentSession;
extensionsResult: LoadExtensionsResult;
modelFallbackMessage?: string; // 如果会话模型无法恢复时的警告
}
第 40 章 AgentSession API
40.1 概述
AgentSession 管理 Agent 生命周期、消息历史、模型状态、压缩和事件流。
40.2 提示与消息排队
typescript
// 发送提示并等待完成
session.prompt("What files are here?");
// 带图片
session.prompt("What's in this image?", {
images: [{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } }]
});
// 流式期间:必须指定排队方式
session.prompt("Stop and do this", { streamingBehavior: "steer" });
session.prompt("After done, check X", { streamingBehavior: "followUp" });
PromptOptions 选项:
expandPromptTemplates:是否展开提示词模板images:附加图片streamingBehavior:"steer"或"followUp"source:输入源("interactive"、"rpc"、"extension")preflightResult:回调,提示被接受时为true,被拒绝时为false
显式排队:
typescript
await session.steer("New instruction");
await session.followUp("After you're done, also do this");
40.3 事件订阅
typescript
const unsubscribe = session.subscribe((event) => {
switch (event.type) {
case "message_update":
if (event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
break;
case "tool_execution_start":
console.log(`Tool: ${event.toolName}`);
break;
case "tool_execution_end":
console.log(`Result: ${event.isError ? "error" : "success"}`);
break;
case "agent_end":
console.log(`Messages: ${event.messages.length}`);
break;
}
});
40.4 模型控制
typescript
await session.setModel(newModel);
session.setThinkingLevel("medium");
const result = await session.cycleModel();
const level = session.cycleThinkingLevel();
40.5 状态访问
typescript
session.agent // Agent 实例
session.model // 当前模型
session.thinkingLevel // 当前思维级别
session.messages // AgentMessage[]
session.isStreaming // 是否正在流式输出
40.6 导航与压缩
typescript
await session.navigateTree("entry-id-456", {
summarize: true,
customInstructions: "Focus on error handling",
});
await session.compact("Focus on recent changes");
session.abortCompaction();
await session.abort();
session.dispose();
第 41 章 AgentSessionRuntime
41.1 概述
当你需要替换活动会话并重建 cwd 绑定的运行时状态时,使用运行时 API。这是内置交互、打印和 RPC 模式使用的同一层。
41.2 创建运行时
typescript
import {
type CreateAgentSessionRuntimeFactory,
createAgentSessionFromServices,
createAgentSessionRuntime,
createAgentSessionServices,
getAgentDir,
SessionManager,
} from "@mariozechner/pi-coding-agent";
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
const services = await createAgentSessionServices({ cwd });
return {
...(await createAgentSessionFromServices({
services,
sessionManager,
sessionStartEvent,
})),
services,
diagnostics: services.diagnostics,
};
};
const runtime = await createAgentSessionRuntime(createRuntime, {
cwd: process.cwd(),
agentDir: getAgentDir(),
sessionManager: SessionManager.create(process.cwd()),
});
41.3 会话替换
AgentSessionRuntime 拥有跨以下操作的活动会话替换:
newSession()switchSession()fork()- clone(通过
fork(entryId, { position: "at" })) importFromJsonl()
重要行为:
- 操作后
runtime.session改变 - 事件订阅绑定到特定
AgentSession,替换后需重新订阅 - 如使用扩展,为新会话调用
runtime.session.bindExtensions(...)再次绑定
typescript
let session = runtime.session;
let unsubscribe = session.subscribe(() => {});
await runtime.newSession();
unsubscribe();
session = runtime.session;
unsubscribe = session.subscribe(() => {});
第 42 章 配置选项
42.1 目录配置
typescript
const { session } = await createAgentSession({
cwd: process.cwd(), // 工作目录
agentDir: "~/.pi/agent", // 全局配置目录
});
cwd 用于 DefaultResourceLoader 的项目扩展发现、项目技能、项目提示词、上下文文件和会话目录命名。
agentDir 用于全局扩展、全局技能、全局提示词、设置、自定义模型、凭证和会话。
42.2 模型配置
typescript
import { getModel } from "@mariozechner/pi-ai";
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);
const opus = getModel("anthropic", "claude-opus-4-5");
const { session } = await createAgentSession({
model: opus,
thinkingLevel: "medium",
scopedModels: [
{ model: opus, thinkingLevel: "high" },
{ model: haiku, thinkingLevel: "off" },
],
authStorage,
modelRegistry,
});
如果未提供模型:尝试从会话恢复 → 使用设置中的默认值 → 回退到第一个可用模型。
42.3 API Key 与 OAuth
typescript
// 运行时 API Key 覆盖(不持久化)
authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
// 自定义存储位置
const customAuth = AuthStorage.create("/my/app/auth.json");
const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
// 无自定义 models.json(仅内置模型)
const simpleRegistry = ModelRegistry.inMemory(authStorage);
42.4 系统提示词覆盖
typescript
const loader = new DefaultResourceLoader({
systemPromptOverride: () => "You are a helpful assistant.",
});
await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });
42.5 工具配置
typescript
import {
codingTools, // read, bash, edit, write(默认)
readOnlyTools, // read, grep, find, ls
createCodingTools, // 特定 cwd 的工具工厂
createReadOnlyTools,
} from "@mariozechner/pi-coding-agent";
// 使用内置工具集
const { session } = await createAgentSession({ tools: readOnlyTools });
// 自定义 cwd 时必须使用工厂
const cwd = "/path/to/project";
const { session } = await createAgentSession({
cwd,
tools: createCodingTools(cwd),
});
自定义工具:
typescript
import { Type } from "typebox";
import { defineTool } from "@mariozechner/pi-coding-agent";
const myTool = defineTool({
name: "my_tool",
label: "My Tool",
description: "Does something useful",
parameters: Type.Object({
input: Type.String({ description: "Input value" }),
}),
execute: async (_toolCallId, params) => ({
content: [{ type: "text", text: `Result: ${params.input}` }],
details: {},
}),
});
const { session } = await createAgentSession({ customTools: [myTool] });
42.6 Extension 加载
typescript
const loader = new DefaultResourceLoader({
additionalExtensionPaths: ["/path/to/my-extension.ts"],
extensionFactories: [
(pi) => {
pi.on("agent_start", () => {
console.log("[Inline Extension] Agent starting");
});
},
],
});
await loader.reload();
const { session } = await createAgentSession({ resourceLoader: loader });
事件总线 :Extension 通过 pi.events 通信。传入共享 eventBus:
typescript
import { createEventBus, DefaultResourceLoader } from "@mariozechner/pi-coding-agent";
const eventBus = createEventBus();
const loader = new DefaultResourceLoader({ eventBus });
await loader.reload();
eventBus.on("my-extension:status", (data) => console.log(data));
42.7 技能覆盖
typescript
const customSkill: Skill = {
name: "my-skill",
description: "Custom instructions",
filePath: "/path/to/SKILL.md",
baseDir: "/path/to",
source: "custom",
};
const loader = new DefaultResourceLoader({
skillsOverride: (current) => ({
skills: [...current.skills, customSkill],
diagnostics: current.diagnostics,
}),
});
42.8 上下文文件覆盖
typescript
const loader = new DefaultResourceLoader({
agentsFilesOverride: (current) => ({
agentsFiles: [
...current.agentsFiles,
{ path: "/virtual/AGENTS.md", content: "# Guidelines\n\n- Be concise" },
],
}),
});
42.9 斜杠命令 / PromptTemplate
typescript
const customCommand: PromptTemplate = {
name: "deploy",
description: "Deploy the application",
source: "(custom)",
content: "# Deploy\n\n1. Build\n2. Test\n3. Deploy",
};
const loader = new DefaultResourceLoader({
promptsOverride: (current) => ({
prompts: [...current.prompts, customCommand],
diagnostics: current.diagnostics,
}),
});
42.10 会话管理
typescript
import { SessionManager } from "@mariozechner/pi-coding-agent";
// 内存(不持久化)
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
});
// 新持久化会话
const { session: persisted } = await createAgentSession({
sessionManager: SessionManager.create(process.cwd()),
});
// 继续最近会话
const { session: continued, modelFallbackMessage } = await createAgentSession({
sessionManager: SessionManager.continueRecent(process.cwd()),
});
// 打开指定文件
const { session: opened } = await createAgentSession({
sessionManager: SessionManager.open("/path/to/session.jsonl"),
});
// 列出会话
const sessions = await SessionManager.list(process.cwd());
const allSessions = await SessionManager.listAll();
42.11 设置管理
typescript
import { SettingsManager } from "@mariozechner/pi-coding-agent";
// 默认:从文件加载
const { session } = await createAgentSession({
settingsManager: SettingsManager.create(),
});
// 带覆盖
const settingsManager = SettingsManager.create();
settingsManager.applyOverrides({
compaction: { enabled: false },
});
const { session } = await createAgentSession({ settingsManager });
// 内存(无文件 I/O,用于测试)
const { session } = await createAgentSession({
settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
sessionManager: SessionManager.inMemory(),
});
设置从两个位置加载并合并:
- 全局:
~/.pi/agent/settings.json - 项目:
<cwd>/.pi/settings.json
项目覆盖全局。嵌套对象合并键。
第 43 章 ResourceLoader
使用 DefaultResourceLoader 发现扩展、技能、提示词、主题和上下文文件:
typescript
import { DefaultResourceLoader, getAgentDir } from "@mariozechner/pi-coding-agent";
const loader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: getAgentDir(),
});
await loader.reload();
const extensions = loader.getExtensions();
const skills = loader.getSkills();
const prompts = loader.getPrompts();
const themes = loader.getThemes();
const contextFiles = loader.getAgentsFiles().agentsFiles;
第 44 章 运行模式
44.1 InteractiveMode
完整的 TUI 交互模式,包含编辑器、聊天历史和所有内置命令:
typescript
import {
type CreateAgentSessionRuntimeFactory,
createAgentSessionFromServices,
createAgentSessionRuntime,
createAgentSessionServices,
getAgentDir,
InteractiveMode,
SessionManager,
} from "@mariozechner/pi-coding-agent";
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
const services = await createAgentSessionServices({ cwd });
return {
...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
services,
diagnostics: services.diagnostics,
};
};
const runtime = await createAgentSessionRuntime(createRuntime, {
cwd: process.cwd(),
agentDir: getAgentDir(),
sessionManager: SessionManager.create(process.cwd()),
});
const mode = new InteractiveMode(runtime, {
migratedProviders: [],
modelFallbackMessage: undefined,
initialMessage: "Hello",
initialImages: [],
initialMessages: [],
});
await mode.run();
44.2 runPrintMode
单次模式:发送提示,输出结果,退出:
typescript
import { runPrintMode } from "@mariozechner/pi-coding-agent";
await runPrintMode(runtime, {
mode: "text",
initialMessage: "Hello",
initialImages: [],
messages: ["Follow up"],
});
44.3 runRpcMode
JSON-RPC 模式,用于子进程集成:
typescript
import { runRpcMode } from "@mariozechner/pi-coding-agent";
await runRpcMode(runtime);
详见第 45-50 章的 RPC 文档。
44.4 SDK 导出清单
typescript
// 工厂
createAgentSession
createAgentSessionRuntime
AgentSessionRuntime
// 认证与模型
AuthStorage
ModelRegistry
// 资源加载
DefaultResourceLoader
type ResourceLoader
createEventBus
// 辅助
defineTool
// 会话管理
SessionManager
SettingsManager
// 内置工具
codingTools
readOnlyTools
readTool, bashTool, editTool, writeTool
grepTool, findTool, lsTool
// 工具工厂
createCodingTools
createReadOnlyTools
createReadTool, createBashTool, createEditTool, createWriteTool
createGrepTool, createFindTool, createLsTool
// 类型
type CreateAgentSessionOptions
type CreateAgentSessionResult
type ExtensionFactory
type ExtensionAPI
type ToolDefinition
type Skill
type PromptTemplate
type Tool
第十部分:RPC 模式与 JSON 模式
第 45 章 RPC 协议
45.1 启动方式
bash
pi --mode rpc [options]
常用选项:
--provider <name>:设置 LLM Provider--model <pattern>:模型模式或 ID--no-session:禁用会话持久化--session-dir <path>:自定义会话存储目录
45.2 协议概述
- 命令:发送到 stdin 的 JSON 对象,每行一个
- 响应 :带有
type: "response"的 JSON 对象,指示命令成功/失败 - 事件:Agent 事件以 JSON 行流式输出到 stdout
所有命令支持可选的 id 字段用于请求/响应关联。如果提供,对应响应将包含相同的 id。
45.3 帧定界
RPC 模式使用严格的 JSONL 语义,LF(\n)是唯一的记录分隔符。
客户端注意事项:
- 仅在
\n处分割记录 - 接受可选的
\r\n输入(剥离尾随\r) - 不要使用将 Unicode 分隔符视为新行的通用行读取器
特别地,Node readline 不符合 RPC 模式的协议规范,因为它也会在 U+2028 和 U+2029 处分割(它们在 JSON 字符串中是合法的)。
第 46 章 RPC 命令
46.1 提示命令
prompt --- 发送用户提示:
json
{"id": "req-1", "type": "prompt", "message": "Hello, world!"}
带图片:
json
{"type": "prompt", "message": "What's in this image?", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}
流式期间必须指定 streamingBehavior:
"steer":当前 turn 工具调用后投递"followUp":Agent 完成后投递
响应:
json
{"id": "req-1", "type": "response", "command": "prompt", "success": true}
steer --- 排队 steering 消息:
json
{"type": "steer", "message": "Stop and do this instead"}
follow_up --- 排队 follow-up 消息:
json
{"type": "follow_up", "message": "After you're done, also do this"}
abort --- 中止当前 Agent 操作:
json
{"type": "abort"}
new_session --- 开始新会话:
json
{"type": "new_session"}
{"type": "new_session", "parentSession": "/path/to/parent-session.jsonl"}
46.2 状态命令
get_state --- 获取当前会话状态:
json
{"type": "get_state"}
响应包含:model、thinkingLevel、isStreaming、isCompacting、steeringMode、followUpMode、sessionFile、sessionId、sessionName、autoCompactionEnabled、messageCount、pendingMessageCount。
get_messages --- 获取所有消息:
json
{"type": "get_messages"}
46.3 模型命令
set_model --- 切换到指定模型:
json
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}
cycle_model --- 循环到下一个可用模型:
json
{"type": "cycle_model"}
get_available_models --- 列出所有已配置模型:
json
{"type": "get_available_models"}
46.4 思维命令
set_thinking_level --- 设置推理/思维级别:
json
{"type": "set_thinking_level", "level": "high"}
级别:"off"、"minimal"、"low"、"medium"、"high"、"xhigh"
cycle_thinking_level --- 循环可用思维级别:
json
{"type": "cycle_thinking_level"}
46.5 队列模式命令
set_steering_mode --- 控制 steering 消息投递方式:
json
{"type": "set_steering_mode", "mode": "one-at-a-time"}
模式:"all" 或 "one-at-a-time"
set_follow_up_mode --- 控制 follow-up 消息投递方式:
json
{"type": "set_follow_up_mode", "mode": "one-at-a-time"}
46.6 压缩命令
compact --- 手动压缩上下文:
json
{"type": "compact"}
{"type": "compact", "customInstructions": "Focus on code changes"}
响应包含:summary、firstKeptEntryId、tokensBefore、details。
set_auto_compaction --- 启用/禁用自动压缩:
json
{"type": "set_auto_compaction", "enabled": true}
46.7 重试命令
set_auto_retry --- 启用/禁用自动重试:
json
{"type": "set_auto_retry", "enabled": true}
abort_retry --- 中止进行中的重试:
json
{"type": "abort_retry"}
46.8 Bash 命令
bash --- 执行 Shell 命令并添加输出到上下文:
json
{"type": "bash", "command": "ls -la"}
响应包含:output、exitCode、cancelled、truncated、fullOutputPath(截断时)。
bash 结果如何到达 LLM :bash 命令立即执行并返回 BashResult。内部创建 BashExecutionMessage 存储在 Agent 消息状态中。下次 prompt 命令时,所有消息(包括 BashExecutionMessage)被转换后发送给 LLM:
Ran `ls -la`
```
total 48
drwxr-xr-x ...
```
abort_bash --- 中止运行中的 bash 命令:
json
{"type": "abort_bash"}
46.9 会话命令
get_session_stats --- 获取 Token 用量和费用统计:
json
{"type": "get_session_stats"}
响应包含:sessionFile、sessionId、消息计数、tokens(input/output/cacheRead/cacheWrite/total)、cost、contextUsage(tokens/contextWindow/percent)。
export_html --- 导出会话为 HTML:
json
{"type": "export_html"}
{"type": "export_html", "outputPath": "/tmp/session.html"}
switch_session --- 加载不同会话文件:
json
{"type": "switch_session", "sessionPath": "/path/to/session.jsonl"}
fork --- 从之前的用户消息创建新分支:
json
{"type": "fork", "entryId": "abc123"}
clone --- 复制当前活动分支到新会话:
json
{"type": "clone"}
get_fork_messages --- 获取可用于分叉的用户消息:
json
{"type": "get_fork_messages"}
get_last_assistant_text --- 获取最后一条助手消息的文本:
json
{"type": "get_last_assistant_text"}
set_session_name --- 设置会话显示名称:
json
{"type": "set_session_name", "name": "my-feature-work"}
46.10 命令查询
get_commands --- 获取可用命令(扩展命令、提示词模板和技能):
json
{"type": "get_commands"}
响应包含:name、description、source("extension" / "prompt" / "skill")、sourceInfo。
第 47 章 RPC 事件流
47.1 事件类型
| 事件 | 说明 |
|---|---|
agent_start |
Agent 开始处理 |
agent_end |
Agent 完成(包含所有生成的消息) |
turn_start |
新 turn 开始 |
turn_end |
turn 完成(包含助手消息和工具结果) |
message_start |
消息开始 |
message_update |
流式更新(text/thinking/toolcall delta) |
message_end |
消息完成 |
tool_execution_start |
工具开始执行 |
tool_execution_update |
工具执行进度(流式输出) |
tool_execution_end |
工具完成 |
queue_update |
待处理 steering/follow-up 队列变更 |
compaction_start |
压缩开始 |
compaction_end |
压缩完成 |
auto_retry_start |
自动重试开始 |
auto_retry_end |
自动重试完成 |
extension_error |
Extension 抛出错误 |
47.2 message_update 流式事件
assistantMessageEvent 字段包含以下 delta 类型:
| 类型 | 说明 |
|---|---|
start |
消息生成开始 |
text_start |
文本内容块开始 |
text_delta |
文本内容块 |
text_end |
文本内容块结束 |
thinking_start |
思维块开始 |
thinking_delta |
思维内容块 |
thinking_end |
思维块结束 |
toolcall_start |
工具调用开始 |
toolcall_delta |
工具调用参数块 |
toolcall_end |
工具调用结束(包含完整 toolCall 对象) |
done |
消息完成(reason: "stop" / "length" / "toolUse") |
error |
错误发生(reason: "aborted" / "error") |
47.3 压缩事件
compaction_start 的 reason 字段:"manual"、"threshold"、"overflow"。
compaction_end 包含:reason、result(摘要详情)、aborted、willRetry、errorMessage。
如果 reason 为 "overflow" 且压缩成功,willRetry 为 true,Agent 将自动重试提示。
47.4 重试事件
auto_retry_start 包含:attempt、maxAttempts、delayMs、errorMessage。
auto_retry_end 包含:success、attempt、finalError(最终失败时)。
47.5 扩展错误事件
json
{
"type": "extension_error",
"extensionPath": "/path/to/extension.ts",
"event": "tool_call",
"error": "Error message..."
}
第 48 章 扩展 UI 子协议
48.1 概述
扩展通过 ctx.ui.select()、ctx.ui.confirm() 等方法请求用户交互。在 RPC 模式中,这些通过 stdin/stdout 上的请求/响应子协议实现。
两类方法:
- 对话方法 (
select、confirm、input、editor):在 stdout 发出extension_ui_request,阻塞直到客户端在 stdin 发回匹配id的extension_ui_response - 即发即忘方法 (
notify、setStatus、setWidget、setTitle、set_editor_text):在 stdout 发出请求,不期望响应
48.2 对话请求(stdout)
select --- 选择列表:
json
{
"type": "extension_ui_request",
"id": "uuid-1",
"method": "select",
"title": "Allow dangerous command?",
"options": ["Allow", "Block"],
"timeout": 10000
}
confirm --- 是/否确认:
json
{
"type": "extension_ui_request",
"id": "uuid-2",
"method": "confirm",
"title": "Clear session?",
"message": "All messages will be lost.",
"timeout": 5000
}
input --- 自由文本输入:
json
{
"type": "extension_ui_request",
"id": "uuid-3",
"method": "input",
"title": "Enter a value",
"placeholder": "type something..."
}
editor --- 多行文本编辑器:
json
{
"type": "extension_ui_request",
"id": "uuid-4",
"method": "editor",
"title": "Edit some text",
"prefill": "Line 1\nLine 2\nLine 3"
}
48.3 即发即忘请求(stdout)
notify --- 显示通知:
json
{"type": "extension_ui_request", "id": "uuid-5", "method": "notify", "message": "Done!", "notifyType": "info"}
setStatus --- 设置页脚状态:
json
{"type": "extension_ui_request", "id": "uuid-6", "method": "setStatus", "statusKey": "my-ext", "statusText": "Turn 3..."}
setWidget --- 设置编辑器上方/下方小组件:
json
{"type": "extension_ui_request", "id": "uuid-7", "method": "setWidget", "widgetKey": "my-ext", "widgetLines": ["Line 1", "Line 2"]}
setTitle --- 设置终端窗口标题:
json
{"type": "extension_ui_request", "id": "uuid-8", "method": "setTitle", "title": "pi - my project"}
set_editor_text --- 设置编辑器文本:
json
{"type": "extension_ui_request", "id": "uuid-9", "method": "set_editor_text", "text": "prefilled text"}
48.4 响应(stdin)
值响应(select、input、editor):
json
{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}
确认响应(confirm):
json
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}
取消响应(任意对话方法):
json
{"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}
48.5 RPC 模式下受限的方法
以下方法在 RPC 模式中不可用或退化:
custom()返回undefinedsetWorkingMessage()、setWorkingIndicator()、setFooter()、setHeader()、setEditorComponent()、setToolsExpanded()为空操作getEditorText()返回""getToolsExpanded()返回falsegetAllThemes()返回[]getTheme()返回undefinedsetTheme()返回{ success: false, error: "..." }
第 49 章 错误处理
49.1 命令错误
失败命令返回 success: false:
json
{
"type": "response",
"command": "set_model",
"success": false,
"error": "Model not found: invalid/model"
}
49.2 解析错误
json
{
"type": "response",
"command": "parse",
"success": false,
"error": "Failed to parse command: Unexpected token..."
}
第 50 章 RPC 类型定义
50.1 Model
json
{
"id": "claude-sonnet-4-20250514",
"name": "Claude Sonnet 4",
"api": "anthropic-messages",
"provider": "anthropic",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 200000,
"maxTokens": 16384,
"cost": { "input": 3.0, "output": 15.0, "cacheRead": 0.3, "cacheWrite": 3.75 }
}
50.2 UserMessage
json
{
"role": "user",
"content": "Hello!",
"timestamp": 1733234567890
}
50.3 AssistantMessage
json
{
"role": "assistant",
"content": [
{"type": "text", "text": "Hello! How can I help?"},
{"type": "thinking", "thinking": "User is greeting me..."},
{"type": "toolCall", "id": "call_123", "name": "bash", "arguments": {"command": "ls"}}
],
"api": "anthropic-messages",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"usage": { ... },
"stopReason": "stop",
"timestamp": 1733234567890
}
Stop reasons: "stop"、"length"、"toolUse"、"error"、"aborted"
50.4 ToolResultMessage
json
{
"role": "toolResult",
"toolCallId": "call_123",
"toolName": "bash",
"content": [{"type": "text", "text": "total 48\ndrwxr-xr-x ..."}],
"isError": false,
"timestamp": 1733234567890
}
50.5 BashExecutionMessage
由 bash RPC 命令创建(非 LLM 工具调用):
json
{
"role": "bashExecution",
"command": "ls -la",
"output": "total 48\ndrwxr-xr-x ...",
"exitCode": 0,
"cancelled": false,
"truncated": false,
"timestamp": 1733234567890
}
第 51 章 JSON 事件流模式
51.1 启动方式
bash
pi --mode json "Your prompt"
51.2 输出格式
输出所有会话事件为 JSON 行到 stdout。首行是会话头部,后跟事件:
json
{"type":"session","version":3,"id":"uuid","timestamp":"...","cwd":"/path"}
{"type":"agent_start"}
{"type":"turn_start"}
{"type":"message_start","message":{"role":"assistant","content":[],...}}
{"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","delta":"Hello",...}}
{"type":"message_end","message":{...}}
{"type":"turn_end","message":{...},"toolResults":[]}
{"type":"agent_end","messages":[...]}
51.3 使用示例
bash
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'
第十一部分:CLI 参考
第 52 章 完整 CLI 参考
52.1 命令格式
bash
pi [options] [@files...] [messages...]
52.2 包管理命令
bash
pi install <source> [-l] # 安装包,-l 为项目级安装
pi remove <source> [-l] # 移除包
pi uninstall <source> [-l] # remove 的别名
pi update [source] # 更新所有非固定的包
pi list # 列出已安装的包
pi config # 启用/禁用包资源
52.3 模式标志
| 标志 | 说明 |
|---|---|
| (默认) | 交互模式 |
-p, --print |
打印响应后退出 |
--mode json |
输出所有事件为 JSON 行 |
--mode rpc |
RPC 模式,用于进程集成 |
--export <in> [out] |
导出会话为 HTML |
打印模式下 pi 也读取管道 stdin 并合并到初始提示中:
bash
cat README.md | pi -p "Summarize this text"
52.4 模型选项
| 选项 | 说明 |
|---|---|
--provider <name> |
Provider(anthropic、openai、google 等) |
--model <pattern> |
模型模式或 ID(支持 provider/id 和可选 :<thinking>) |
--api-key <key> |
API Key(覆盖环境变量) |
--thinking <level> |
off、minimal、low、medium、high、xhigh |
--models <patterns> |
逗号分隔的 Ctrl+P 循环模型模式 |
--list-models [search] |
列出可用模型 |
52.5 会话选项
| 选项 | 说明 |
|---|---|
-c, --continue |
继续最近会话 |
-r, --resume |
浏览并选择会话 |
--session <path> |
使用指定会话文件或部分 UUID |
--fork <path> |
Fork 指定会话文件或部分 UUID |
--session-dir <dir> |
自定义会话存储目录 |
--no-session |
临时模式(不保存) |
52.6 工具选项
| 选项 | 说明 |
|---|---|
--tools <list> |
启用指定内置工具(默认:read,bash,edit,write) |
--no-tools |
禁用所有内置工具(扩展工具仍可用) |
可用内置工具:read、bash、edit、write、grep、find、ls
52.7 资源选项
| 选项 | 说明 |
|---|---|
-e, --extension <source> |
从路径、npm 或 git 加载扩展(可重复) |
--no-extensions |
禁用扩展发现 |
--skill <path> |
加载技能(可重复) |
--no-skills |
禁用技能发现 |
--prompt-template <path> |
加载提示词模板(可重复) |
--no-prompt-templates |
禁用提示词模板发现 |
--theme <path> |
加载主题(可重复) |
--no-themes |
禁用主题发现 |
组合 --no-* 与显式标志以精确加载所需内容,忽略 settings.json(如 --no-extensions -e ./my-ext.ts)。
52.8 其他选项
| 选项 | 说明 |
|---|---|
--system-prompt <text> |
替换默认提示词(上下文文件和技能仍追加) |
--append-system-prompt <text> |
追加到系统提示词 |
--verbose |
强制详细启动 |
-h, --help |
显示帮助 |
-v, --version |
显示版本 |
52.9 文件参数
用 @ 前缀引用文件:
bash
pi @prompt.md "Answer this"
pi -p @screenshot.png "What's in this image?"
pi @code.ts @test.ts "Review these files"
52.10 环境变量
| 变量 | 说明 |
|---|---|
PI_CODING_AGENT_DIR |
覆盖配置目录(默认:~/.pi/agent) |
PI_PACKAGE_DIR |
覆盖包目录 |
PI_SKIP_VERSION_CHECK |
跳过启动时的版本检查 |
PI_CACHE_RETENTION |
设为 long 以扩展 prompt cache(Anthropic: 1h, OpenAI: 24h) |
VISUAL, EDITOR |
Ctrl+G 使用的外部编辑器 |
52.11 命令示例集
bash
# 交互模式 + 初始提示
pi "List all .ts files in src/"
# 非交互
pi -p "Summarize this codebase"
# 非交互 + 管道 stdin
cat README.md | pi -p "Summarize this text"
# 不同模型
pi --provider openai --model gpt-4o "Help me refactor"
# 模型 + provider 前缀
pi --model openai/gpt-4o "Help me refactor"
# 模型 + 思维级别简写
pi --model sonnet:high "Solve this complex problem"
# 限制模型循环
pi --models "claude-*,gpt-4o"
# 只读模式
pi --tools read,grep,find,ls -p "Review the code"
# 高思维级别
pi --thinking high "Solve this complex problem"
第十二部分:开发与贡献
第 53 章 开发环境
53.1 仓库克隆与构建
bash
git clone https://github.com/badlogic/pi-mono
cd pi-mono
npm install
npm run build
从源码运行:
bash
/path/to/pi-mono/pi-test.sh
该脚本可从任意目录运行。pi 保留调用者的工作目录。
53.2 项目结构
packages/
ai/ # LLM Provider 抽象
agent/ # Agent 循环和消息类型
tui/ # 终端 UI 组件
coding-agent/ # CLI 和交互模式
53.3 测试
bash
./test.sh # 运行非 LLM 测试(不需要 API Key)
npm test # 运行所有测试
npm test -- test/specific.test.ts # 运行指定测试
53.4 调试
/debug(隐藏命令)写入 ~/.pi/agent/pi-debug.log:
- 渲染的 TUI 行(含 ANSI 码)
- 发送给 LLM 的最后消息
53.5 路径解析
三种执行模式:npm 安装、独立二进制、tsx 源码。
始终使用 src/config.ts 获取包资源:
typescript
import { getPackageDir, getThemeDir } from "./config.js";
不要直接使用 __dirname 获取包资源。
第 54 章 Fork 与品牌定制
54.1 package.json 配置
json
{
"piConfig": {
"name": "pi",
"configDir": ".pi"
}
}
更改 name、configDir 和 bin 字段以自定义 Fork。影响 CLI 横幅、配置路径和环境变量名称。
附录
附录 A:术语表
| 术语 | 英文 | 说明 |
|---|---|---|
| pi mono | pi mono | 极简终端编码代理运行时 |
| Provider | Provider | LLM 提供商(如 Anthropic、OpenAI、Google) |
| Extension | Extension | TypeScript 模块,扩展 pi 的行为 |
| Skill | Skill | 按需加载的自包含能力包 |
| Prompt Template | Prompt Template | 可复用的 Markdown 提示词片段 |
| Theme | Theme | 定义 TUI 颜色的 JSON 文件 |
| Pi Package | Pi Package | 将扩展、技能、模板、主题打包分发的单元 |
| Session | Session | 会话,存储为 JSONL 文件的对话历史 |
| Entry | Entry | 会话中的单条记录 |
| Leaf | Leaf | 树中的当前位置(叶节点) |
| Compaction | Compaction | 压缩,总结较早消息以释放上下文窗口 |
| Branch Summary | Branch Summary | 分支摘要,切换分支时保留被放弃路径的上下文 |
| Steering Message | Steering Message | 流式期间排队的纠正性消息 |
| Follow-up Message | Follow-up Message | Agent 完成后排入的后续消息 |
| Turn | Turn | 一个 LLM 响应 + 工具调用 + 工具结果的完整周期 |
| Split Turn | Split Turn | 压缩切割点落在 turn 中间的情况 |
| Context Window | Context Window | 模型的上下文 token 容量 |
| Thinking Level | Thinking Level | 推理/思维深度(off 到 xhigh) |
| Scope Models | Scope Models | Ctrl+P 可循环的模型集合 |
| Tool | Tool | LLM 可调用的操作(read、write、edit、bash 等) |
| Content Block | Content Block | 消息中的内容单元(Text、Image、Thinking、ToolCall) |
| Usage | Usage | Token 用量和费用统计 |
| ResourceLoader | ResourceLoader | 发现扩展、技能、模板、主题和上下文文件的组件 |
| CustomEntry | CustomEntry | 扩展持久化状态条目(不参与 LLM 上下文) |
| CustomMessageEntry | CustomMessageEntry | 扩展注入的消息条目(参与 LLM 上下文) |
| LabelEntry | LabelEntry | 用户定义的书签/标记 |
附录 B:文件路径速查表
B.1 全局配置路径(~/.pi/agent/)
| 路径 | 说明 |
|---|---|
~/.pi/agent/settings.json |
全局设置 |
~/.pi/agent/auth.json |
认证凭证(API Key / OAuth Token) |
~/.pi/agent/models.json |
自定义 Provider 和模型 |
~/.pi/agent/keybindings.json |
自定义快捷键 |
~/.pi/agent/AGENTS.md |
全局上下文文件 |
~/.pi/agent/CLAUDE.md |
全局上下文文件(备选) |
~/.pi/agent/SYSTEM.md |
自定义系统提示词 |
~/.pi/agent/APPEND_SYSTEM.md |
追加系统提示词 |
~/.pi/agent/extensions/*.ts |
全局扩展 |
~/.pi/agent/extensions/*/index.ts |
全局扩展(子目录) |
~/.pi/agent/skills/ |
全局技能 |
~/.pi/agent/prompts/*.md |
全局提示词模板 |
~/.pi/agent/themes/*.json |
全局主题 |
~/.pi/agent/sessions/ |
会话存储目录 |
~/.pi/agent/git/ |
Git 包安装目录 |
B.2 项目配置路径(.pi/)
| 路径 | 说明 |
|---|---|
.pi/settings.json |
项目设置 |
.pi/extensions/*.ts |
项目扩展 |
.pi/extensions/*/index.ts |
项目扩展(子目录) |
.pi/skills/ |
项目技能 |
.pi/prompts/*.md |
项目提示词模板 |
.pi/themes/*.json |
项目主题 |
.pi/npm/ |
项目级 npm 包安装目录 |
.pi/git/ |
项目级 git 包安装目录 |
.pi/sessions/ |
项目级会话存储(需配置 sessionDir) |
B.3 其他发现路径
| 路径 | 说明 |
|---|---|
~/.agents/skills/ |
全局 Agent Skills 标准目录 |
<cwd>/.agents/skills/ |
项目 Agent Skills 标准目录(向上遍历到 git 仓库根) |
附录 C:事件速查表
C.1 会话生命周期事件
| 事件 | 触发时机 | 关键字段 | 可取消 |
|---|---|---|---|
session_start |
会话启动/加载/重载 | reason, previousSessionFile |
❌ |
session_before_switch |
/new 或 /resume 前 |
reason, targetSessionFile |
✅ |
session_before_fork |
/fork 或 /clone 前 |
entryId, position |
✅ |
session_before_compact |
压缩前 | preparation, customInstructions, signal |
✅ |
session_compact |
压缩完成 | compactionEntry, fromExtension |
❌ |
session_before_tree |
/tree 导航前 |
preparation, signal |
✅ |
session_tree |
树导航完成 | newLeafId, oldLeafId, summaryEntry |
❌ |
session_shutdown |
运行时关闭前 | reason, targetSessionFile |
❌ |
C.2 Agent 事件
| 事件 | 触发时机 | 关键字段 |
|---|---|---|
before_agent_start |
用户提示后、Agent 循环前 | prompt, images, systemPrompt, systemPromptOptions |
agent_start |
Agent 开始处理 | --- |
agent_end |
Agent 完成 | messages |
turn_start |
Turn 开始 | turnIndex, timestamp |
turn_end |
Turn 完成 | turnIndex, message, toolResults |
C.3 消息事件
| 事件 | 触发时机 | 关键字段 |
|---|---|---|
message_start |
消息开始 | message |
message_update |
流式更新 | message, assistantMessageEvent |
message_end |
消息完成 | message |
C.4 工具事件
| 事件 | 触发时机 | 关键字段 | 可修改 |
|---|---|---|---|
tool_execution_start |
工具开始执行 | toolCallId, toolName, args |
❌ |
tool_execution_update |
工具执行进度 | toolCallId, toolName, args, partialResult |
❌ |
tool_execution_end |
工具完成 | toolCallId, toolName, result, isError |
❌ |
tool_call |
工具执行前 | toolName, toolCallId, input |
✅ 阻塞/修改 |
tool_result |
工具完成后 | toolName, toolCallId, input, content, details, isError |
✅ 修改 |
C.5 上下文与 Provider 事件
| 事件 | 触发时机 | 可修改 |
|---|---|---|
context |
每次 LLM 调用前 | ✅ 修改消息 |
before_provider_request |
请求发送前 | ✅ 替换 payload |
after_provider_response |
HTTP 响应接收后 | ❌ |
C.6 资源事件
| 事件 | 触发时机 | 关键字段 |
|---|---|---|
resources_discover |
session_start 后 | cwd, reason |
C.7 输入事件
| 事件 | 触发时机 | 关键字段 | 可拦截 |
|---|---|---|---|
input |
用户输入后 | text, images, source |
✅ 转换/处理 |
user_bash |
!/!! 命令时 |
command, excludeFromContext, cwd |
✅ 替换 |
C.8 模型事件
| 事件 | 触发时机 | 关键字段 |
|---|---|---|
model_select |
模型切换 | model, previousModel, source |
附录 D:扩展开发清单
从零创建 Extension 的步骤检查清单:
D.1 准备
- 确定扩展的目的(工具、命令、事件拦截、UI)
- 选择放置位置(
~/.pi/agent/extensions/全局 或.pi/extensions/项目级) - 创建
.ts文件或目录 +index.ts
D.2 基本结构
- 导出默认工厂函数(
export default function (pi: ExtensionAPI) { ... }) - 如需异步初始化,使用
async function - 导入所需类型(
ExtensionAPI、Type等)
D.3 事件处理
- 使用
pi.on()订阅所需事件 - 在
session_start中初始化状态 - 在
session_shutdown中清理资源 - 如需阻塞工具调用,监听
tool_call并返回{ block: true, reason } - 如需修改工具结果,监听
tool_result并返回补丁
D.4 自定义工具
- 使用
pi.registerTool()注册 - 定义 TypeBox Schema 参数
- 实现
execute函数 - 考虑
promptSnippet和promptGuidelines
D.5 自定义命令
- 使用
pi.registerCommand()注册 - 提供
description - 实现
handler函数 - 可选:添加
getArgumentCompletions
D.6 自定义 UI
- 使用
ctx.ui.custom()创建自定义组件 - 实现
render、handleInput、invalidate三方法对象 - 使用主题回调获取颜色
- 在
handleInput中调用tui.requestRender()
D.7 测试
- 使用
pi -e ./my-extension.ts测试 - 验证事件触发
- 验证工具/命令工作
- 验证 UI 渲染正确
D.8 打包分发
- 创建
package.json含pi清单 - 添加
pi-package关键字 - 运行时依赖放
dependencies - pi 核心包放
peerDependencies("*") - 发布到 npm 或推送到 git 仓库
附录 E:示例索引
E.1 Extension 示例(examples/extensions/)
| 示例 | 用途 |
|---|---|
custom-provider-anthropic/ |
Anthropic 代理 Provider |
custom-provider-gitlab-duo/ |
GitLab Duo OAuth 集成 |
custom-provider-qwen-cli/ |
Qwen CLI 自定义 API |
custom-compaction.ts |
使用不同模型的自定义压缩 |
custom-footer.ts |
自定义页脚(含 git 分支和状态) |
dynamic-tools.ts |
动态工具注册与激活 |
input-transform.ts |
输入转换 |
modal-editor.ts |
Vim 风格模态编辑器 |
overlay-qa-tests.ts |
Overlay 组件全面示例 |
plan-mode.ts |
计划模式(setWidget + setStatus) |
preset.ts |
SelectList 选择预设 |
qna.ts |
问答工具(BorderedLoader + LLM 调用) |
rpc-demo.ts |
RPC 模式扩展演示 |
send-user-message.ts |
sendUserMessage 完整示例 |
session.ts |
会话管理示例 |
snake.ts |
Snake 游戏 |
status-line.ts |
状态行指示器 |
summarize.ts |
会话摘要工具 |
todo.ts |
待办工具(自定义渲染) |
tools.ts |
SettingsList 和工具启用/禁用 |
working-indicator.ts |
自定义工作指示器 |
handoff.ts |
会话交接(BorderedLoader) |
E.2 SDK 示例(examples/sdk/)
| 示例 | 用途 |
|---|---|
01-minimal.ts |
最小 SDK 示例 |
02-custom-model.ts |
自定义模型配置 |
03-custom-prompt.ts |
自定义系统提示词 |
04-skills.ts |
技能覆盖 |
05-tools.ts |
工具配置和自定义工具 |
06-extensions.ts |
Extension 加载 |
07-context-files.ts |
上下文文件覆盖 |
08-prompt-templates.ts |
提示词模板覆盖 |
09-api-keys-and-oauth.ts |
API Key 和 OAuth 配置 |
10-settings.ts |
设置管理 |
11-sessions.ts |
会话管理 |
附录 F:推荐阅读
F.1 官方资源
| 资源 | 链接 |
|---|---|
| pi mono GitHub 仓库 | https://github.com/badlogic/pi-mono |
| pi.dev 官网 | https://pi.dev |
| npm 包 | https://www.npmjs.com/package/@mariozechner/pi-coding-agent |
| Discord 社区 | https://discord.com/invite/3cU7Bz4UPx |
| 包画廊 | https://pi.dev/packages |
F.2 标准与规范
| 资源 | 链接 |
|---|---|
| Agent Skills 标准 | https://agentskills.io/specification |
| Kitty 键盘协议 | https://sw.kovidgoyal.net/kitty/keyboard-protocol/ |
F.3 相关项目
| 项目 | 说明 |
|---|---|
| @mariozechner/pi-ai | 核心 LLM 工具包 |
| @mariozechner/pi-agent | Agent 框架 |
| @mariozechner/pi-tui | 终端 UI 组件 |
| openclaw/openclaw | SDK 集成的实际项目 |
F.4 博客文章
| 文章 | 链接 |
|---|---|
| pi Coding Agent 博文 | https://mariozechner.at/posts/2025-11-30-pi-coding-agent/ |
| What If You Don't Need MCP | https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/ |
F.5 技能仓库
| 仓库 | 说明 |
|---|---|
| Anthropic Skills | 文档处理、Web 开发 |
| Pi Skills | Web 搜索、浏览器自动化、Google API、转录 |
F.6 会话共享
| 资源 | 说明 |
|---|---|
| pi-share-hf | 将编码代理会话分享到 Hugging Face |
| badlogicgames/pi-mono on Hugging Face | pi-mono 开源工作会话数据集 |
全文完