记录 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 时会自动下载 fdripgrep,这两个是从 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/grep
  • pi install npm:pi-rtk-optimizer — rtk 命令改写 + 工具输出压缩降 token
  • pi 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 开关或清零重试计数。