记录 Pi(终端里的 AI 编码助手)的安装、模型配置、常用命令与扩展玩法。 文中所有 API Key、自建代理地址均已脱敏,实际使用时替换成自己的。
一、安装使用
1. 安装
确保本地有 node 和 git。
使用 npm 安装:
# 安装(走腾讯云 npm 镜像)
npm install -g @earendil-works/pi-coding-agent --registry=https://mirrors.cloud.tencent.com/npm/
# 验证
pi --version
2. 使用(Windows)
配置文件路径:
C:\Users\adminUser\.pi\agent\models.json
OpenCode 的公共 API
apiKey 就是字面量 public,不需要申请:
{
"providers": {
"OpenCodeZen": {
"baseUrl": "https://opencode.ai/zen/v1",
"api": "openai-completions",
"apiKey": "public",
"compat": {
"sendSessionAffinityHeaders": true
},
"headers": {
"user-agent": "opencode/1.18.16 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14",
"x-opencode-client": "cli"
},
"models": [
{"id": "mimo-v2.5-free", "reasoning": true, "input": ["text", "image"], "contextWindow": 1000000, "maxTokens": 131072},
{"id": "nemotron-3-ultra-free", "reasoning": true, "input": ["text"], "contextWindow": 1000000, "maxTokens": 128000},
{"id": "laguna-s-2.1-free", "reasoning": true, "input": ["text", "image"], "contextWindow": 1000000, "maxTokens": 128000},
{"id": "hy3-free", "reasoning": true, "input": ["text"], "contextWindow": 262144, "maxTokens": 128000},
{"id": "nemotron-3.5-lightning-free", "reasoning": true, "input": ["text"], "contextWindow": 262144, "maxTokens": 128000},
{"id": "x-preview-f-free", "reasoning": true, "input": ["text", "image"], "contextWindow": 1000000, "maxTokens": 128000},
{"id": "muse-spark-1.2-contributor-free", "reasoning": true, "input": ["text"], "contextWindow": 1000000, "maxTokens": 128000}
]
}
}
}
CLIProxyAPI(自建代理)
地址与 Key 已脱敏:
{
"providers": {
"CLIProxyAPI": {
"baseUrl": "http://<你的服务器IP>:8317/v1",
"api": "openai-completions",
"apiKey": "sk-********",
"compat": {
"sendSessionAffinityHeaders": true
},
"headers": {
"user-agent": "opencode/1.18.16 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14",
"x-opencode-client": "cli"
},
"models": [
{"id": "deepseek-v4-flash", "reasoning": true, "input": ["text"], "contextWindow":128000, "maxTokens": 8192},
{"id": "deepseek-v4-flash-vision-exp", "reasoning": true, "input": ["text", "image"],"contextWindow": 128000, "maxTokens": 8192},
{"id": "hy3", "reasoning": true, "input": ["text"], "contextWindow": 262144,"maxTokens": 128000},
{"id": "mimo-v2.5", "reasoning": true, "input": ["text", "image"], "contextWindow":262144, "maxTokens": 128000}
]
}
}
}
CloseAI(anthropic-messages 模式)
换成 anthropic-messages 协议的中转,顺带补上 cost 字段(免费模型全填 0):
{
"providers": {
"CloseAI": {
"baseUrl": "http://<你的服务器IP>:8090",
"api": "anthropic-messages",
"apiKey": "ah-********",
"models": [
{
"id": "mimo-v2.5-free",
"name": "mimo-v2.5-free",
"reasoning": true,
"input": [
"text"
],
"contextWindow": 1000000,
"maxTokens": 128000,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
多供应商模板
多个中转站并存时的写法:
{
"providers": {
"my-provider-1": {
"baseUrl": "https://my-provider-1.com/v1",
"api": "openai-completions",
"apiKey": "sk-******",
"headers": {
"User-Agent": "claude-cli/2.1.217"
},
"compat": {
"sendSessionAffinityHeaders": true // openai-completions 模式要打开,跟缓存相关
},
"models": [
{
"id": "grok-4.5",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 500000,
"maxTokens": 128000
},
{
"id": "glm-5.2",
"reasoning": true,
"input": ["text"],
"contextWindow": 1000000,
"maxTokens": 128000
}
]
},
"my-provider-2": {
"baseUrl": "https://my-provider-2.com/v1",
"api": "openai-completions",
"apiKey": "sk-******",
"headers": {
"User-Agent": "claude-cli/2.1.217"
},
"compat": {
"sendSessionAffinityHeaders": true
},
"models": [
{
"id": "minimax-m3",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 500000,
"maxTokens": 128000
},
{
"id": "deepseek-v4-pro",
"reasoning": true,
"input": ["text"],
"contextWindow": 1000000,
"maxTokens": 128000
}
]
}
}
}
3. 工具依赖(fd 与 ripgrep)
第一次打开 Pi 时会自动下载 fd 和 ripgrep,这两个是从 GitHub 拉的,很容易下载失败,可以手动下载:
下载后把可执行文件放到:
C:\Users\adminUser\.pi\agent\bin
4. 安全权限
Pi 没有沙盒,基本全开放:
- Pi 以启动它的用户身份运行,权限等于该用户账号的权限;
- 内置工具可以读文件、写文件、编辑文件、执行 shell 命令,扩展也是同样的权限;
- 默认没有任何内置限制或确认机制,删除文件、执行命令都不会弹确认框。
也就是说:模型让你删的东西,它真的会删。重要目录请自己做好备份 / 用 git 兜底。
5. 常用管理命令
会话切换
/resume 浏览并选择之前的会话(可搜索、按 Ctrl+P 切换路径显示、Ctrl+D 删除)
/new 开启新会话
/name <名字> 给当前会话设置显示名(方便 /resume 里找)
/import <file> 导入会话(JSONL / HTML)
/export [file] 导出会话(JSONL / HTML)
回滚和分支
Pi 的机制是会话是一棵树,通过分支回到历史点,不丢现有进度:
- 核心:
/tree只管"对话 / 上下文",不管磁盘文件,不会回滚已经修改的文件,需要自己用 git; /tree跳回会话中任意历史节点继续(最接近"回滚")。每个条目有id+parentId,在叶节点跳到旧点就等于回滚到那时,且不新建文件;/fork从某条历史消息分叉出一个新会话文件;/clone把当前活动分支复制成新会话文件。
上下文压缩和查看
/compact [提示] 手动压缩:把旧消息汇总成摘要,腾出上下文;可选 [提示] 聚焦摘要重点
/session 显示当前会话文件、会话 ID、消息数、token 数、成本
/session 是最直接的上下文 / 用量查看命令。
6. 解决"跳回顶部"的 bug
参考:https://linux.do/t/topic/2758931
在 /settings 里设置一下 TUI mode 应该就行:
/settings
7. 全局提示词 AGENTS.md
默认没有创建,需要自己建:~/.pi/agent/AGENTS.md
# 全局指令(AGENTS.md)
适用于所有项目的全局约定:
- **语言**:始终使用中文输出(含解释、总结、报错说明)。
- **危险操作需确认**:任何删除(`rm`、清空、卸载等)、修改系统配置、重写文件、执行不可逆命令前,必须先向用户说明并征得明确同意,不得擅自执行。
- **善用搜索 MCP**:环境中已配置搜索类 MCP 工具(如 `keenable`、`exa`)。遇到时效性、版本、外部事实类问题时,优先调用搜索 MCP 获取最新信息,而非依赖训练记忆。
二、扩展
参考资料:
1. 扩展安装方式
方式一:本地 .ts 文件
把 .ts 扩展文件放到自动发现目录,启动时自动加载。快速测试可以用 pi -e ./my-extension.ts;放到自动发现目录后,可用 /reload 热重载,不用重启。
| 位置 | 作用域 |
|---|---|
~/.pi/agent/extensions/*.ts | 全局(所有项目) |
~/.pi/agent/extensions/*/index.ts | 全局(子目录形式) |
.pi/extensions/*.ts | 项目本地 |
.pi/extensions/*/index.ts | 项目本地 |
快速上手示例,创建 ~/.pi/agent/extensions/my-extension.ts:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
pi.on("session_start", async (_event, ctx) => {
ctx.ui.notify("扩展已加载!", "info");
});
pi.registerTool({
name: "greet",
label: "Greet",
description: "问候某人",
parameters: Type.Object({
name: Type.String({ description: "要问候的名字" }),
}),
async execute(toolCallId, params) {
return { content: [{ type: "text", text: `你好, ${params.name}!` }], details: {} };
},
});
}
⚠️ 注意:扩展拥有完整系统权限、可执行任意代码,只安装可信来源的扩展。
方式二:pi install 安装包(npm / git / 本地路径)
默认写入用户设置(~/.pi/agent/settings.json);加 -l 写入项目设置(.pi/settings.json,可和团队共享)。
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 install ./relative/path/to/package
方式三:手动声明
在 settings.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"
]
}
依赖 npm 包时:在扩展目录(或其父目录)放 package.json,运行 npm install,然后 node_modules/ 里的包可自动解析。
2. 扩展网站
LazyPi 收录的 PI 扩展:https://lazypi.org/
3. 推荐扩展
基础 / 配置
pi install npm:@juanibiapina/pi-extension-settings— 统一设置 UI 层,给其他扩展当底座(/extension-settings)pi install npm:@juicesharp/rpiv-ask-user-question— 结构化问卷,让模型把拿不准的事通过"带选项"的形式问你,而不是含糊猜测
读写准确性
pi install npm:pi-hashline-edit-pro— 哈希锚点版本替换内置 read/edit,提升读写准确性pi install npm:@ff-labs/pi-fff— 用模糊搜索替换内置 find/greppi install npm:pi-rtk-optimizer— rtk 命令改写 + 工具输出压缩降 tokenpi install npm:pi-cache-optimizer— 提升 prompt/KV 缓存命中率:稳定 prompt、OpenAI 兼容 cache key、代理兼容告警,footer 缓存统计
自动化 / 委派
pi install npm:@narumitw/pi-subagents— subagent 工具,隔离子代理并行执行pi install npm:pi-agent-browser-native— agent_browser 浏览器自动化(点击、截图、填表)pi install npm:pi-mcp-adapter— 接入任意 MCP 工具服务器
工作流 / 目标
pi install npm:@narumitw/pi-goal— 添加/goal自主目标完成pi install npm:@narumitw/pi-plan-mode— 添加/plan只读规划模式pi install npm:pi-memory-md— Markdown 文件 + git 持久记忆pi install npm:pi-add-dir— 载入外部目录的上下文pi install npm:pi-simplify— 审查最近改动代码pi install npm:pi-web-access— Web 搜索、URL 抓取、GitHub 克隆、PDF/视频理解pi install npm:@juicesharp/rpiv-todo— 给模型一个结构化待办清单工具,并在编辑器上方渲染常驻实时窗口,替代 Claude Code 的 TaskCreate/TaskUpdate 那套功能
另外,在系统终端里运行下面这条命令装缓存优化,装完重启 Pi,底部出现 opencode-go-cache: enabled 就成功了:
pi install npm:pi-opencode-go-cache
4. 自写扩展:400 错误重试插件
放到 .pi/agent/extensions 目录即可。
Pi 内置的自动重试只处理"瞬时错误"(429 / 限流 / 5xx),客户端错误 400 会被直接判为失败,不再重试。下面这个扩展监听 agent_end,当识别到本轮因 400 失败时,向模型重发"继续"触发新的一轮,等效于重试,最多 6 次;6 次后仍失败则停止并通知用户。
retry400.ts:
/**
* retry400 —— 对 HTTP 400 错误自动重试
*
* pi 内置自动重试只处理"瞬时错误"(429/限流/5xx),客户端错误 400 会被直接判为失败,
* 不再重试。本扩展监听 `agent_end`,当识别到本轮因 400 失败时,向模型重发 "继续"
* 触发新的一轮,等效于重试。最多重试 6 次;6 次后仍失败则停止并通知用户。
*
* 安装:
* - 全局: 复制到 ~/.pi/agent/extensions/retry400.ts (自动加载, /reload 热重载)
* - 项目: 复制到 .pi/extensions/retry400.ts (需项目受信任)
* - 临时测试: pi -e ./retry400.ts
*
* 命令:
* /retry400 on|off|reset -- 开关 / 清零重试计数
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
/** 最大重试次数 */
const MAX_RETRIES = 6;
/** 重试时投递给模型的消息 */
const RETRY_MESSAGE = "继续";
/** 匹配任意 HTTP 400 (400 / invalid_request_error / bad_request) */
const HTTP_400_RE = /(^|[^0-9])400([^0-9]|$)|invalid_request_error|bad[-\s_]?request/i;
interface AgentMessage {
role?: string;
stopReason?: string;
errorMessage?: unknown;
content?: unknown;
}
export default function (pi: ExtensionAPI) {
let retryCount = 0;
let enabled = true;
// 用户新输入一个真正的新请求 -> 视为重新开始,清零重试计数。
// (扩展自己投递的 "继续" source 是 "extension",不会触发这里)
pi.on("input", (event) => {
if (event.source === "interactive") retryCount = 0;
});
pi.on("agent_end", async (event, ctx) => {
if (!enabled) return;
const last = lastAssistantMessage(event.messages);
const is400 = last && isHttp400Error(last);
if (!is400) {
// 成功/中断/其它错误 -> 归零,避免把上一次失败的计数带到新的一轮
if (retryCount !== 0) retryCount = 0;
return;
}
// —— 本轮确因 HTTP 400 失败 ——
if (retryCount >= MAX_RETRIES) {
// 已重试满 6 次仍失败: 停止重试并提示
if (ctx.hasUI) {
ctx.ui.notify(`HTTP 400,已自动重试 ${MAX_RETRIES} 次仍未成功,已停止`, "error");
}
return;
}
retryCount++;
if (ctx.hasUI) {
ctx.ui.notify(`HTTP 400,正在第 ${retryCount}/${MAX_RETRIES} 次重试…`, "warning");
}
// 重发 "继续" 让模型重新跑一轮
pi.sendUserMessage(RETRY_MESSAGE, { deliverAs: "followUp" });
});
pi.registerCommand("retry400", {
description: "切换/清零 HTTP 400 自动重试。用法: /retry400 on|off|reset",
handler: async (args, ctx) => {
const arg = (args ?? "").trim().toLowerCase();
if (arg === "off") enabled = false;
else if (arg === "on") enabled = true;
else if (arg === "reset") retryCount = 0;
if (ctx.hasUI) {
ctx.ui.notify(
`retry400: ${enabled ? "已开启" : "已关闭"},重试计数 ${retryCount}/${MAX_RETRIES}`,
"info"
);
}
},
});
}
/** 取该轮 agent 运行里最后一条 assistant 消息 */
function lastAssistantMessage(messages: AgentMessage[] | undefined): AgentMessage | undefined {
if (!Array.isArray(messages)) return undefined;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i]?.role === "assistant") return messages[i];
}
return undefined;
}
/** 判断助理消息是否是因 HTTP 400 失败 */
function isHttp400Error(m: AgentMessage): boolean {
if (m.stopReason !== "error") return false;
let text = "";
if (typeof m.errorMessage === "string") text += m.errorMessage;
// 兜底: 若 errorMessage 为空,再扫 content 文本
if (!text && typeof m.content === "string") text = m.content;
else if (!text && Array.isArray(m.content)) {
text = m.content
.map((c) => (c && typeof c === "object" && "text" in c ? (c as { text?: string }).text : ""))
.join(" ");
}
return HTTP_400_RE.test(text);
}
装好后可用 /retry400 on|off|reset 开关或清零重试计数。
