一句话结论:
pi的交互模式、print、JSON、RPC、SDK 共用同一个AgentSession。区别只在「谁驱动、输出什么」。选接口的标准是进程边界,不是功能多少。
curl -fsSL https://pi.dev/install.sh | sh # 安装器会锁定依赖
# 或
npm install -g --ignore-scripts @earendil-works/pi-coding-agent需要 Node.js >= 22.19。进入项目目录后直接 pi:
cd /path/to/repo
pi首次使用在交互界面里跑 /login,选 provider,走订阅或 API key。凭据存在 ~/.pi/agent/auth.json。
| 接口 | 启动方式 | 进程边界 | 输出 | 适合 |
|---|---|---|---|---|
| TUI | pi |
当前终端 | 差分渲染 | 人机协作 |
pi -p "..." |
当前进程 | 最终文本 | 脚本、CI | |
| JSON | pi --mode json "..." |
当前进程 | JSONL 事件 | 日志、管道 |
| RPC | pi --mode rpc |
子进程 | JSONL 命令/响应/事件 | 其他语言、IDE |
| SDK | createAgentSession() |
同进程 | TypeScript 事件 | Node/Bun 宿主 |
pi -p "总结这个仓库的结构"
git diff | pi -p "review 这个改动"
pi -p @src/main.ts "解释这个文件的入口逻辑"@path 会把文件内容塞进第一条消息;管道输入会前置到第一条消息。适合 CI 里做一次性检查。
pi --mode json "检查依赖是否有已知漏洞" > events.jsonl每行一个事件,结构见第 4 章和附录 B。stdout 只放协议记录,日志走 stderr。
pi --mode rpc --no-sessionstdin 写命令,stdout 读响应和事件:
{"id":"req-1","type":"prompt","message":"检查这个仓库"}
{"id":"req-1","type":"response","command":"prompt","success":true,"data":{"disposition":"started"}}要点:
- 每条记录一行 JSON,以
\n结尾; id用来关联命令和响应,响应顺序不保证;prompt成功只代表「已接受/已排队/已处理」,不代表模型跑完;- 等
agent_settled,不要等agent_end(重试、压缩、队列可能继续); - 关闭 stdin 请求优雅退出。
不要用 Node 的 readline 读 stdout:它会把 U+2028 / U+2029 也当行分隔符,而这两个字符在 JSON 字符串里合法。按字节流切 \n。
pi --model sonnet:high
pi --provider anthropic --model claude-sonnet-4-6
pi --thinking high
pi --tools read,grep,find,ls -p "review 这个项目"
pi --continue
pi --resume
pi --session-id ops-2026-10-27
pi --no-session| 参数 | 作用 |
|---|---|
--model <pattern> |
精确 ID 或模糊匹配,支持 provider/id 和 :thinking 后缀 |
--models <patterns> |
启动时可选模型范围,逗号分隔 |
--list-models [search] |
列出模型后退出 |
--tools <list> |
替换默认工具集;+name / -name 是增量 |
--extension <path> |
加载单个扩展 |
--session-dir <dir> |
覆盖会话目录 |
--fork <path|id> |
从已有会话分叉 |
--export <in> [out] |
导出 HTML |
--tools 的规则容易出错:纯 +/- 列表是「在默认集上增删」,一旦出现裸名字(read,grep),整个列表变成白名单,默认工具全没了。
pi --continue # 最近一个会话
pi --resume # 选择器
pi --session <path|id> # 按路径/ID
pi --session-id <id> # 指定 ID,不存在就创建
pi --fork <path|id> # 分叉会话文件在:
~/.pi/agent/sessions/--<path>--/<timestamp>_<session-id>.jsonl
<path> 是工作目录,/、\、: 被替换成 -。第 10 章讲文件结构。
| 命令 | 作用 |
|---|---|
/login |
登录 provider |
/model |
切模型 |
/settings |
改设置 |
/compact [instructions] |
手动压缩 |
/tree |
在当前会话里切分支 |
/fork |
从某条用户消息分叉到新会话 |
/clone |
复制当前分支到新会话 |
/session |
看会话文件、ID、token、成本 |
/export |
导出 HTML / JSONL |
/reload |
重载设置、扩展、提示 |
/tree 和 /fork 的区别:前者留在同一个文件里,后者生成新文件。分支不删除,切走时可以生成摘要带回来。
- name: review
run: |
git diff origin/main...HEAD | pi -p "只指出会导致线上故障的问题,不要风格建议" > review.md
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}三条实践:
- 用
--no-session。 CI 不需要会话文件。 - 限制工具。
--tools read,grep,find,ls,不给写权限。 - 设置超时和预算。 用
timeout包一层,模型侧用便宜的。
需要结构化结果时用 JSON 模式,再用 jq 取:
pi --mode json -p "输出 JSON:{risk, files}" | jq -c 'select(.type=="message_end") | .message.content[]? | select(.type=="text") | .text'Python:
import json, subprocess
p = subprocess.Popen(["pi", "--mode", "rpc", "--no-session"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def send(obj):
p.stdin.write((json.dumps(obj) + "\n").encode())
p.stdin.flush()
send({"id": "1", "type": "prompt", "message": "检查这个仓库"})
for raw in p.stdout:
event = json.loads(raw)
if event.get("type") == "message_update":
delta = event.get("assistantMessageEvent", {})
if delta.get("type") == "text_delta":
print(delta["delta"], end="", flush=True)
if event.get("type") == "agent_settled":
break仓库里的 src/modes/rpc/rpc-client.ts 有一个 RpcClient 实现(未从包根导出),处理了响应关联、事件分发和 promptAndWait(),可以照着写。
pi 默认以当前用户权限运行,不逐次询问。项目级配置(.pi/)和项目扩展只有在信任后才加载:
- 首次进入一个仓库,pi 会问是否信任;
- 信任后加载
.pi/settings.json、.pi/extensions/、.pi/skills/; - 用户级配置在
~/.pi/agent/,不受项目信任影响。
在 CI 里,项目配置是仓库内容的一部分。不要在不可信的 PR 上自动加载项目扩展——扩展是进程内代码。
-p和--mode text不是一回事。--mode text在终端里仍会开 TUI;-p才保证跑一次就退。--mode json的 stdout 不能混日志。 一行日志就会破坏 JSONL。- RPC 的
prompt响应不代表完成。 等agent_settled。 --continue按工作目录找会话。 换了目录就找不到。
- 用
--mode json跑一次,统计turn_end的数量和usage.totalTokens。 - 写一个 Python RPC 客户端,支持
prompt、abort两个命令。 - 在 CI 里加一条「只读 review」任务,限制工具并设置 5 分钟超时。