Skip to content

Latest commit

 

History

History
205 lines (150 loc) · 6.96 KB

09-coding-agent-cli.md

File metadata and controls

205 lines (150 loc) · 6.96 KB

第 9 章 Coding Agent:五种接口,一套内核

一句话结论:pi 的交互模式、print、JSON、RPC、SDK 共用同一个 AgentSession。区别只在「谁驱动、输出什么」。选接口的标准是进程边界,不是功能多少。

9.1 安装与登录

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。

9.2 五种接口

接口 启动方式 进程边界 输出 适合
TUI pi 当前终端 差分渲染 人机协作
print pi -p "..." 当前进程 最终文本 脚本、CI
JSON pi --mode json "..." 当前进程 JSONL 事件 日志、管道
RPC pi --mode rpc 子进程 JSONL 命令/响应/事件 其他语言、IDE
SDK createAgentSession() 同进程 TypeScript 事件 Node/Bun 宿主

print 模式

pi -p "总结这个仓库的结构"
git diff | pi -p "review 这个改动"
pi -p @src/main.ts "解释这个文件的入口逻辑"

@path 会把文件内容塞进第一条消息;管道输入会前置到第一条消息。适合 CI 里做一次性检查。

JSON 模式

pi --mode json "检查依赖是否有已知漏洞" > events.jsonl

每行一个事件,结构见第 4 章和附录 B。stdout 只放协议记录,日志走 stderr。

RPC 模式

pi --mode rpc --no-session

stdin 写命令,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。

9.3 常用参数

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),整个列表变成白名单,默认工具全没了。

9.4 会话

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 章讲文件结构。

9.5 交互模式里的常用命令

命令 作用
/login 登录 provider
/model 切模型
/settings 改设置
/compact [instructions] 手动压缩
/tree 在当前会话里切分支
/fork 从某条用户消息分叉到新会话
/clone 复制当前分支到新会话
/session 看会话文件、ID、token、成本
/export 导出 HTML / JSONL
/reload 重载设置、扩展、提示

/tree 和 /fork 的区别:前者留在同一个文件里,后者生成新文件。分支不删除,切走时可以生成摘要带回来。

9.6 CI 里怎么用

- name: review
  run: |
    git diff origin/main...HEAD | pi -p "只指出会导致线上故障的问题,不要风格建议" > review.md
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

三条实践:

  1. 用 --no-session。 CI 不需要会话文件。
  2. 限制工具。 --tools read,grep,find,ls,不给写权限。
  3. 设置超时和预算。 用 timeout 包一层,模型侧用便宜的。

需要结构化结果时用 JSON 模式,再用 jq 取:

pi --mode json -p "输出 JSON:{risk, files}" | jq -c 'select(.type=="message_end") | .message.content[]? | select(.type=="text") | .text'

9.7 RPC 客户端最小实现

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(),可以照着写。

9.8 权限与信任

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 按工作目录找会话。 换了目录就找不到。

练习

  1. 用 --mode json 跑一次,统计 turn_end 的数量和 usage.totalTokens。
  2. 写一个 Python RPC 客户端,支持 prompt、abort 两个命令。
  3. 在 CI 里加一条「只读 review」任务,限制工具并设置 5 分钟超时。