Skip to content

RPC 模式 ​

RPC 模式通过 stdin/stdout 上的 JSON 协议使编码助手能够在无头模式下运行。这对于将助手嵌入到其他应用程序、IDE 或自定义界面中非常有用。

Node.js/TypeScript 用户注意:如果您正在构建 Node.js 应用程序,建议使用 @earendil-works/pi-coding-agent 中提供的 AgentSession 直接创建会话,而不是启动子进程。有关 API 的详细信息,请查看 src/core/agent-session.ts。如需基于子进程的 TypeScript 客户端,请查看 src/modes/rpc/rpc-client.ts。

启动 RPC 模式 ​

bash
pi --mode rpc [选项]

常用选项:

  • --provider <名称>:设置 LLM 提供商(anthropic、openai、google 等)
  • --model <模式>:模型模式或 ID(支持 provider/id 和可选的 :<thinking>)
  • --no-session:禁用会话持久性
  • --session-dir <路径>:自定义会话存储目录

协议概述 ​

  • 命令:通过 stdin 发送的 JSON 对象,每行一个
  • 响应:带有 type: "response" 的 JSON 对象,表示命令成功/失败
  • 事件:作为 JSON 行流式传输到 stdout 的代理事件

所有命令都支持可选的 id 字段,用于请求/响应关联。如果提供了 id,相应的响应将包含相同的 id。

帧封装 ​

RPC 模式使用严格的 JSONL 语义,LF (\n) 是唯一的记录分隔符。

这对客户端很重要:

  • 仅按 \n 分割记录
  • 通过剥离尾随的 \r 接受可选的 \r\n 输入
  • 不要使用将 Unicode 分隔符视为换行符的通用行读取器

特别是,Node 的 readline 对于 RPC 模式来说不符合协议规范,因为它也会在 U+2028 和 U+2029 处分割,而这些字符在 JSON 字符串中是有效的。

命令 ​

提示 ​

prompt ​

向代理发送用户提示。命令响应在提示被接受、排队或处理后发出。接受后事件会继续异步流式传输。

json
{"id": "req-1", "type": "prompt", "message": "Hello, world!"}

带图片:

json
{"type": "prompt", "message": "这张图片里有什么?", "images": [{"type": "image", "data": "base64-编码的数据", "mimeType": "image/png"}]}

在流式传输期间:如果代理已经在流式传输,您必须指定 streamingBehavior 来排队消息:

json
{"type": "prompt", "message": "新指令", "streamingBehavior": "steer"}
  • "steer":在代理运行时排队消息。它在当前助手轮次完成工具调用之后、下一次 LLM 调用之前传递。
  • "followUp":等待代理完成。消息仅在代理停止时传递。

如果代理在流式传输且未指定 streamingBehavior,命令将返回错误。

扩展命令:如果消息是扩展命令(例如 /mycommand),它即使在流式传输期间也会立即执行。扩展命令通过 pi.sendMessage() 自行管理 LLM 交互。

输入扩展:技能命令(/skill:名称)和提示模板(/template)在发送/排队前会被扩展。

响应:

json
{"id": "req-1", "type": "response", "command": "prompt", "success": true}

success: true 表示提示已被接受、排队或立即处理。success: false 表示提示在接收入之前被拒绝。接受后发生的失败将通过正常的事件和消息流报告,而不是作为同一请求 id 的第二个 response。

images 字段是可选的。每个图片使用 ImageContent 格式:{"type": "image", "data": "base64-编码的数据", "mimeType": "image/png"}。

steer ​

在代理运行时排队一条转向消息。它在当前助手轮次完成工具调用之后、下一次 LLM 调用之前传递。技能命令和提示模板会被扩展。不允许扩展命令(使用 prompt 代替)。

json
{"type": "steer", "message": "停止并做这个"}

带图片:

json
{"type": "steer", "message": "看这个代替", "images": [{"type": "image", "data": "base64-编码的数据", "mimeType": "image/png"}]}

images 字段是可选的。每个图片使用与 prompt 相同的 ImageContent 格式。

响应:

json
{"type": "response", "command": "steer", "success": true}

参见 set_steering_mode 以控制转向消息的处理方式。

stop ​

终止当前会话。

响应:

json
{"id": "req-1", "type": "response", "command": "stop", "success": true}

set_steering_mode ​

设置转向模式。转向消息如何被处理取决于此设置。

json
{"type": "set_steering_mode", "mode": "queued"}

可能的值:

  • "queued":转向消息被排队,在下一个 LLM 调用前处理
  • "immediate":转向消息被立即处理,中断当前 LLM 调用

响应:

json
{"type": "response", "command": "set_steering_mode", "success": true}

session_info ​

返回当前会话信息。

json
{"type": "session_info"}

响应:

json
{"type": "response", "command": "session_info", "success": true, "data": {
  "sessionId": "uuid-1234",
  "cwd": "/path/to/project",
  "provider": "anthropic",
  "model": "claude-3-5-sonnet",
  "thinkingLevel": "high"
}}

事件 ​

事件是代理向客户端发出的异步消息。

model_change ​

模型更改事件。

json
{"type": "event", "name": "model_change", "data": {"provider": "openai", "modelId": "gpt-4o"}}

thinking_level_change ​

思考级别更改事件。

json
{"type": "event", "name": "thinking_level_change", "data": {"thinkingLevel": "high"}}

user_message ​

用户消息事件。

json
{"type": "event", "name": "user_message", "data": {"role": "user", "content": "你好", "timestamp": 1734512345678}}

assistant_message ​

助手消息事件。

json
{"type": "event", "name": "assistant_message", "data": {
  "role": "assistant",
  "content": [{"type": "text", "text": "你好!"}],
  "provider": "anthropic",
  "model": "claude-3-5-sonnet",
  "usage": {...},
  "stopReason": "stop",
  "timestamp": 1734512346789
}}

tool_result ​

工具结果事件。

json
{"type": "event", "name": "tool_result", "data": {
  "role": "toolResult",
  "toolCallId": "call_abc123",
  "toolName": "bash",
  "content": [{"type": "text", "text": "输出..."}],
  "details": {...},
  "isError": false,
  "timestamp": 1734512347890
}}

bash_execution ​

Bash 执行事件。

json
{"type": "event", "name": "bash_execution", "data": {
  "role": "bashExecution",
  "command": "ls -la",
  "output": "输出内容\n",
  "exitCode": 0,
  "cancelled": false,
  "truncated": false,
  "timestamp": 1734512348901
}}

compaction ​

压缩事件。

json
{"type": "event", "name": "compaction", "data": {
  "summary": "用户讨论了 X、Y、Z...",
  "tokensBefore": 50000,
  "timestamp": 1734512349012
}}

custom ​

自定义扩展消息事件。

json
{"type": "event", "name": "custom", "data": {
  "role": "custom",
  "customType": "my-extension",
  "content": "扩展注入的内容",
  "display": true,
  "timestamp": 1734512350123
}}

branch_summary ​

分支摘要事件。

json
{"type": "event", "name": "branch_summary", "data": {
  "summary": "分支探索了方法 A...",
  "fromId": "prev-entry-id",
  "timestamp": 1734512351234
}}

扩展 UI 协议 ​

扩展可以通过 RPC 与 TUI 进行交互。

set_working_indicator ​

设置工作指示器(在流式响应时显示)。

json
{"type": "set_working_indicator", "data": {
  "frames": ["○", "●", "•"],
  "intervalMs": 100
}}

或隐藏指示器:

json
{"type": "set_working_indicator", "data": {"frames": []}}

set_status ​

设置状态文本(显示在页脚)。

json
{"type": "set_status", "data": {"text": "正在处理...", "key": "my-ext-status"}}

清除:

json
{"type": "set_status", "data": {"text": "", "key": "my-ext-status"}}

替换页脚组件。

json
{"type": "set_footer", "data": {
  "render": {
    "width": 80,
    "text": "扩展自定义页脚"
  }
}}

set_editor_component ​

设置自定义编辑器组件。

json
{"type": "set_editor_component", "data": {
  "factory": "my-editor-factory"
}}

工厂函数接收 (tui, theme, keybindings) 并返回组件。

register_entry_renderer ​

注册自定义条目渲染器。

json
{"type": "register_entry_renderer", "data": {
  "customType": "my-extension",
  "render": {
    "width": 80,
    "text": "自定义渲染"
  }
}}

notify ​

显示通知。

json
{"type": "notify", "data": {"text": "操作完成", "level": "info"}}

错误处理 ​

错误通过响应返回。

  • success: false 表示命令被拒绝
  • error 字段包含错误信息

常见错误:

  • 无效命令
  • 缺少必需参数
  • 代理正在流式传输且未指定 streamingBehavior
  • 会话不存在(对某些命令)

类型 ​

PromptRequest ​

typescript
{
  id?: string,
  type: "prompt",
  message: string | ({type: "text"; text: string} | {type: "image"; data: string; mimeType: string})[]
}

SteerRequest ​

typescript
{
  id?: string,
  type: "steer",
  message: string | ({type: "text"; text: string} | {type: "image"; data: string; mimeType: string})[]
}

SetSteeringModeRequest ​

typescript
{
  id?: string,
  type: "set_steering_mode",
  mode: "queued" | "immediate"
}

SessionInfoRequest ​

typescript
{
  type: "session_info"
}

Response ​

typescript
{
  type: "response",
  command: string,
  success: boolean,
  data?: any
}

示例 ​

基本客户端(Python) ​

python
import subprocess
import json
import sys

def rpc_call(cmd: dict) -> dict:
    proc = subprocess.Popen(
        [sys.executable, "-m", "pi", "--mode", "rpc"],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        text=True
    )
    stdin = proc.stdin
    stdout = proc.stdout

    stdin.write(json.dumps(cmd) + "\n")
    stdin.flush()

    for line in stdout:
        line = line.rstrip("\r\n")
        if not line:
            continue
        event = json.loads(line)
        if event["type"] == "event":
            print(f"Event: {event['name']}")
        elif event["type"] == "response":
            if event.get("success"):
                return event.get("data", {})
            else:
                raise ValueError(event.get("error", "Unknown error"))

# 发送提示
result = rpc_call({
    "type": "prompt",
    "message": "Hello, RPC world!"
})
print(f"Session info: {result}")

交互式客户端(Node.js) ​

typescript
import * as readline from "readline";
import { spawn } from "child_process";

const rpc = (cmd: object) => {
  return new Promise((resolve, reject) => {
    const proc = spawn("pi", ["--mode", "rpc"]);
    const rl = readline.createInterface({ input: proc.stdin, output: proc.stdout });

    rl.on("line", (line) => {
      const evt = JSON.parse(line);
      if (evt.type === "response" && evt.success) {
        resolve(evt.data);
      } else if (evt.type === "response" && !evt.success) {
        reject(new Error(evt.error || "failed"));
      }
    });

    rl.close();
    proc.stdin.write(JSON.stringify(cmd) + "\n");

    proc.on("error", reject);
    proc.on("close", () => proc.kill("SIGTERM"));
  });
};

(async () => {
  await rpc({ type: "prompt", message: "What's the weather like?" });
})();

Pi 编码助手中文文档