Pi 官方文档
RPC 模式
RPC 模式
RPC 模式通过 stdin/stdout 上的 JSON 协议,让编程 agent 可以无头运行。这适合把 agent 嵌入其他应用、IDE 或自定义 UI 中。
给 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 模式
pi --mode rpc [options]
常用选项:
--provider <name>:设置 LLM Provider(模型提供方)(anthropic、openai、google 等)--model <pattern>:模型 pattern 或 ID(支持provider/id,以及可选的:<thinking>)--name <name>/-n <name>:在启动时设置会话显示名称--no-session:禁用会话持久化--session-dir <path>:自定义会话存储目录
协议概览
- Commands:发送到 stdin 的 JSON 对象,每行一个
- Responses:带有
type: "response"的 JSON 对象,用来表示命令成功或失败 - Events:agent 事件以 JSON 行的形式流式输出到 stdout
所有命令都支持可选的 id 字段,用于请求/响应关联。如果提供了该字段,对应的响应会带上相同的 id。
分帧
RPC 模式使用严格的 JSONL 语义,只有 LF(\n)是记录分隔符。
这对客户端很重要:
- 只按
\n拆分记录 - 允许输入里出现可选的
\r\n,做法是去掉末尾的\r - 不要使用通用行读取器,因为它们会把 Unicode 分隔符也当作换行
尤其要注意,Node 的 readline 不符合 RPC 模式的协议要求,因为它还会按 U+2028 和 U+2029 拆分,而这些字符在 JSON 字符串中是合法的。
命令
提示
prompt
向 agent 发送用户提示词。命令响应会在提示词被接受、排队或处理后发出。事件在接受后会继续异步流式输出。
{"id": "req-1", "type": "prompt", "message": "Hello, world!"}
带图片:
{"type": "prompt", "message": "What's in this image?", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}
在流式输出期间:如果 agent 已经在流式输出,你必须指定 streamingBehavior,才能把消息排队:
{"type": "prompt", "message": "New instruction", "streamingBehavior": "steer"}
"steer":在 agent 运行时把消息排队。当前 assistant 回合执行完它的 tool call 后、下一次 LLM 调用前送达。"followUp":等待 agent 完成。只有在 agent 停止后才会送达消息。
如果 agent 正在流式输出,但没有指定 streamingBehavior,命令会返回错误。
扩展命令:如果消息是扩展命令(例如 /mycommand),它会立即执行,即使在流式输出期间也是如此。扩展命令通过 pi.sendMessage() 自己管理与 LLM 的交互。
输入展开:Skill 命令(/skill:name)和 prompt templates(/template)会在发送或排队前展开。
响应:
{"id": "req-1", "type": "response", "command": "prompt", "success": true}
success: true 表示提示词已被接受、排队或立即处理。success: false 表示提示词在被接受前就被拒绝了。接受之后发生的失败,会通过正常的事件和消息流报告,而不会针对同一个 request id 再返回第二个 response。
images 字段是可选的。每张图片都使用 ImageContent 格式:{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}。
steer
在 agent 运行时排队一条引导消息。当前 assistant 回合执行完它的 tool call 后、下一次 LLM 调用前送达。Skill 命令和 prompt templates 会展开。扩展命令不允许使用(请改用 prompt)。
{"type": "steer", "message": "Stop and do this instead"}
带图片:
{"type": "steer", "message": "Look at this instead", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}
images 字段是可选的。每张图片都使用 ImageContent 格式(与 prompt 相同)。
响应:
{"type": "response", "command": "steer", "success": true}
参见 set_steering_mode,了解如何控制引导消息的处理方式。
follow_up
排队一条后续消息,在 agent 完成后处理。只有在 agent 没有更多 tool call 或引导消息时才会送达。Skill 命令和 prompt templates 会展开。扩展命令不允许使用(请改用 prompt)。
{"type": "follow_up", "message": "After you're done, also do this"}
带图片:
{"type": "follow_up", "message": "Also check this image", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}
images 字段是可选的。每张图片都使用 ImageContent 格式(与 prompt 相同)。
响应:
{"type": "response", "command": "follow_up", "success": true}
参见 set_follow_up_mode,了解如何控制后续消息的处理方式。
abort
中止当前 agent 操作。
{"type": "abort"}
响应:
{"type": "response", "command": "abort", "success": true}
new_session
启动一个全新的会话。可以被 session_before_switch 扩展事件处理器取消。
{"type": "new_session"}
带可选的父会话跟踪:
{"type": "new_session", "parentSession": "/path/to/parent-session.jsonl"}
响应:
{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": false}}
如果被扩展取消:
{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": true}}
状态
get_state
获取当前会话状态。
{"type": "get_state"}
响应:
{
"type": "response",
"command": "get_state",
"success": true,
"data": {
"model": {...},
"thinkingLevel": "medium",
"isStreaming": false,
"isCompacting": false,
"steeringMode": "all",
"followUpMode": "one-at-a-time",
"sessionFile": "/path/to/session.jsonl",
"sessionId": "abc123",
"sessionName": "my-feature-work",
"autoCompactionEnabled": true,
"messageCount": 5,
"pendingMessageCount": 0
}
}
model 字段要么是完整的 Model 对象,要么是 null。sessionName 字段是通过 set_session_name 设置的显示名称;如果未设置,则会省略。
get_messages
获取对话中的所有消息。
{"type": "get_messages"}
响应:
{
"type": "response",
"command": "get_messages",
"success": true,
"data": {"messages": [...]}
}
消息是 AgentMessage 对象(见 Message Types)。
模型
set_model
切换到指定模型。
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}
响应包含完整的 模型 对象:
{
"type": "response",
"command": "set_model",
"success": true,
"data": {...}
}
cycle_model
切换到下一个可用模型。如果只有一个模型可用,则返回 null data。
{"type": "cycle_model"}
响应:
{
"type": "response",
"command": "cycle_model",
"success": true,
"data": {
"model": {...},
"thinkingLevel": "medium",
"isScoped": false
}
}
model 字段是完整的 模型 对象。
get_available_models
列出所有已配置的模型。
{"type": "get_available_models"}
响应包含完整的 模型 对象数组:
{
"type": "response",
"command": "get_available_models",
"success": true,
"data": {
"models": [...]
}
}
思考
set_thinking_level
为支持它的模型设置推理/思考级别。
{"type": "set_thinking_level", "level": "high"}
级别:"off"、"minimal"、"low"、"medium"、"high"、"xhigh"
注意:"xhigh" 仅受 OpenAI codex-max 模型支持。
响应:
{"type": "response", "command": "set_thinking_level", "success": true}
cycle_thinking_level
在可用的思考级别之间轮换。如果模型不支持思考,则返回 null data。
{"type": "cycle_thinking_level"}
响应:
{
"type": "response",
"command": "cycle_thinking_level",
"success": true,
"data": {"level": "high"}
}
队列模式
set_steering_mode
控制 steering 消息(来自 steer)的发送方式。
{"type": "set_steering_mode", "mode": "one-at-a-time"}
模式:
"all":在当前 assistant 回合执行完所有 tool call 后,发送全部 steering 消息"one-at-a-time":每完成一个 assistant 回合发送一条 steering 消息(默认)
响应:
{"type": "response", "command": "set_steering_mode", "success": true}
set_follow_up_mode
控制 follow-up 消息(来自 follow_up)的发送方式。
{"type": "set_follow_up_mode", "mode": "one-at-a-time"}
模式:
"all":在 agent 结束时发送所有 follow-up 消息"one-at-a-time":每次 agent 完成时发送一条 follow-up 消息(默认)
响应:
{"type": "response", "command": "set_follow_up_mode", "success": true}
上下文压缩
compact
手动执行会话上下文的上下文压缩,以减少 token 用量。
{"type": "compact"}
使用自定义指令:
{"type": "compact", "customInstructions": "Focus on code changes"}
响应:
{
"type": "response",
"command": "compact",
"success": true,
"data": {
"summary": "Summary of conversation...",
"firstKeptEntryId": "abc123",
"tokensBefore": 150000,
"estimatedTokensAfter": 32000,
"details": {}
}
}
estimatedTokensAfter 是对上下文压缩后立即重建的消息上下文所做的启发式估算,不是 Provider 精确的 token 计数。
set_auto_compaction
在上下文接近满载时,启用或禁用自动上下文压缩。
{"type": "set_auto_compaction", "enabled": true}
响应:
{"type": "response", "command": "set_auto_compaction", "success": true}
重试
set_auto_retry
启用或禁用对临时错误(overloaded、rate limit、5xx)的自动重试。
{"type": "set_auto_retry", "enabled": true}
响应:
{"type": "response", "command": "set_auto_retry", "success": true}
abort_retry
中止正在进行的重试(取消等待并停止重试)。
{"type": "abort_retry"}
响应:
{"type": "response", "command": "abort_retry", "success": true}
Bash
bash
执行 shell 命令,并将输出加入会话上下文。
{"type": "bash", "command": "ls -la"}
响应:
{
"type": "response",
"command": "bash",
"success": true,
"data": {
"output": "total 48\ndrwxr-xr-x ...",
"exitCode": 0,
"cancelled": false,
"truncated": false
}
}
如果输出被截断,会包含 fullOutputPath:
{
"type": "response",
"command": "bash",
"success": true,
"data": {
"output": "truncated output...",
"exitCode": 0,
"cancelled": false,
"truncated": true,
"fullOutputPath": "/tmp/pi-bash-abc123.log"
}
}
bash 结果如何进入 LLM:
bash 命令会立即执行,并返回一个 BashResult。内部会创建一个 BashExecutionMessage,并存入 agent 的消息状态。这个消息不会发出事件。
当发送下一次 prompt 命令时,所有消息(包括 BashExecutionMessage)都会先被转换,再发送给 LLM。BashExecutionMessage 会被转换为以下格式的 UserMessage:
Ran `ls -la`
```
total 48
drwxr-xr-x ...
```
这意味着:
- bash 输出会在 下一次 prompt 时进入 LLM 上下文,不会立刻进入
- 在发送 prompt 之前可以执行多个 bash 命令;它们的输出都会被包含进去
BashExecutionMessage本身不会发出事件
abort_bash
中止正在运行的 bash 命令。
{"type": "abort_bash"}
响应:
{"type": "response", "command": "abort_bash", "success": true}
会话
get_session_stats
获取 token 用量、成本统计,以及当前上下文窗口用量。
{"type": "get_session_stats"}
响应:
{
"type": "response",
"command": "get_session_stats",
"success": true,
"data": {
"sessionFile": "/path/to/session.jsonl",
"sessionId": "abc123",
"userMessages": 5,
"assistantMessages": 5,
"toolCalls": 12,
"toolResults": 12,
"totalMessages": 22,
"tokens": {
"input": 50000,
"output": 10000,
"cacheRead": 40000,
"cacheWrite": 5000,
"total": 105000
},
"cost": 0.45,
"contextUsage": {
"tokens": 60000,
"contextWindow": 200000,
"percent": 30
}
}
}
tokens 包含当前会话状态下 assistant 的累计用量。contextUsage 包含用于上下文压缩和页脚显示的当前上下文窗口实际估算值。
当没有 model 或没有上下文窗口可用时,会省略 contextUsage。在上下文压缩刚完成后,直到新的压缩后 assistant 响应提供有效用量数据之前,contextUsage.tokens 和 contextUsage.percent 都是 null。
export_html
将会话导出为 HTML 文件。
{"type": "export_html"}
使用自定义路径:
{"type": "export_html", "outputPath": "/tmp/session.html"}
响应:
{
"type": "response",
"command": "export_html",
"success": true,
"data": {"path": "/tmp/session.html"}
}
switch_session
加载另一个会话文件。可被 session_before_switch 扩展事件处理器取消。
{"type": "switch_session", "sessionPath": "/path/to/session.jsonl"}
响应:
{"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": false}}
如果扩展取消了切换:
{"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": true}}
fork
基于当前分支上一条用户消息创建一个新的 fork。可被 session_before_fork 扩展事件处理器取消。返回被 fork 的那条消息的文本。
{"type": "fork", "entryId": "abc123"}
响应:
{
"type": "response",
"command": "fork",
"success": true,
"data": {"text": "The original prompt text...", "cancelled": false}
}
如果扩展取消了 fork:
{
"type": "response",
"command": "fork",
"success": true,
"data": {"text": "The original prompt text...", "cancelled": true}
}
clone
在当前位置,把当前活动分支复制到一个新会话中。可被 session_before_fork 扩展事件处理器取消。
{"type": "clone"}
响应:
{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": false}
}
如果扩展取消了 clone:
{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": true}
}
get_fork_messages
获取可用于 fork 的用户消息。
{"type": "get_fork_messages"}
响应:
{
"type": "response",
"command": "get_fork_messages",
"success": true,
"data": {
"messages": [
{"entryId": "abc123", "text": "First prompt..."},
{"entryId": "def456", "text": "Second prompt..."}
]
}
}
get_last_assistant_text
获取最后一条 assistant 消息的文本内容。
{"type": "get_last_assistant_text"}
响应:
{
"type": "response",
"command": "get_last_assistant_text",
"success": true,
"data": {"text": "The assistant's response..."}
}
如果不存在 assistant 消息,则返回 {\"text\": null}。
set_session_name
为当前会话设置显示名称。该名称会出现在会话列表中,有助于识别会话。
{"type": "set_session_name", "name": "my-feature-work"}
响应:
{
"type": "response",
"command": "set_session_name",
"success": true
}
当前会话名称可以通过 get_state 的 sessionName 字段获取。要在启动 RPC 模式时设置初始名称,请将 --name <name> 或 -n <name> 传给 pi --mode rpc 进程。
命令
get_commands
获取可用命令(扩展命令、提示词模板和 Skill)。在前面加上 /,就能通过 prompt 命令调用它们。
{"type": "get_commands"}
响应:
{
"type": "response",
"command": "get_commands",
"success": true,
"data": {
"commands": [
{"name": "session-name", "description": "Set or clear session name", "source": "extension", "path": "/home/user/.pi/agent/extensions/session.ts"},
{"name": "fix-tests", "description": "Fix failing tests", "source": "prompt", "location": "project", "path": "/home/user/myproject/.pi/agent/prompts/fix-tests.md"},
{"name": "skill:brave-search", "description": "Web search via Brave API", "source": "skill", "location": "user", "path": "/home/user/.pi/agent/skills/brave-search/SKILL.md"}
]
}
}
每个命令包含:
name: 命令名(使用/name调用)description: 人类可读的说明(扩展命令可选)source: 命令类型:- "extension": 通过扩展中的
pi.registerCommand()注册 - "prompt": 从提示词模板
.md文件加载 - "skill": 从 Skill 目录加载(name 会加上前缀
skill:)
- "extension": 通过扩展中的
location: 加载来源(可选,扩展命令没有此字段):- "user": 用户级别(
~/.pi/agent/) - "project": 项目级别(
./.pi/agent/) - "path": 通过 CLI 或设置指定的显式路径
- "user": 用户级别(
path: 命令源的绝对文件路径(可选)
注意:内置的 TUI 命令(/settings、/hotkeys 等)不包含在内。它们只会在交互模式下处理,作为 prompt 发送时不会执行。
事件
事件会在 agent 运行期间以 JSON Lines 的形式流式写到 stdout。事件不包含 id 字段(只有响应才有)。
事件类型
| 事件 | 说明 |
|---|---|
agent_start | Agent 开始处理 |
agent_end | Agent 完成(包括本次运行生成的所有消息) |
turn_start | 新轮次开始 |
turn_end | 轮次完成(包括 assistant 消息和 tool call 结果) |
message_start | 消息开始 |
message_update | 流式更新(文本 / 思考 / tool call 增量) |
message_end | 消息完成 |
tool_execution_start | 工具开始执行 |
tool_execution_update | 工具执行进度(流式输出) |
tool_execution_end | 工具完成 |
queue_update | 待处理的 steering / follow-up 队列发生变化 |
compaction_start | 上下文压缩开始 |
compaction_end | 上下文压缩完成 |
auto_retry_start | 自动重试开始(在临时错误之后) |
auto_retry_end | 自动重试完成(成功或最终失败) |
extension_error | 扩展抛出错误 |
agent_start
当 agent 开始处理 prompt 时发出。
{"type": "agent_start"}
agent_end
当 agent 完成时发出。包含本次运行生成的所有消息。
{
"type": "agent_end",
"messages": [...]
}
turn_start / turn_end
一个 turn 由一次 assistant 回复,以及随之产生的 tool call 和结果组成。
{"type": "turn_start"}
{
"type": "turn_end",
"message": {...},
"toolResults": [...]
}
message_start / message_end
当消息开始和完成时发出。message 字段包含一个 AgentMessage。
{"type": "message_start", "message": {...}}
{"type": "message_end", "message": {...}}
message_update(流式)
在 assistant 消息流式输出时发出。它同时包含部分消息和一个流式 delta 事件。
{
"type": "message_update",
"message": {...},
"assistantMessageEvent": {
"type": "text_delta",
"contentIndex": 0,
"delta": "Hello ",
"partial": {...}
}
}
assistantMessageEvent 字段包含以下一种 delta 类型:
| 类型 | 说明 |
|---|---|
start | 消息生成开始 |
text_start | 文本内容块开始 |
text_delta | 文本内容分片 |
text_end | 文本内容块结束 |
thinking_start | 思考块开始 |
thinking_delta | 思考内容分片 |
thinking_end | 思考块结束 |
toolcall_start | tool call 开始 |
toolcall_delta | tool call 参数分片 |
toolcall_end | tool call 结束(包含完整的 toolCall 对象) |
done | 消息完成(原因:"stop"、"length"、"toolUse") |
error | 发生错误(原因:"aborted"、"error") |
文本响应流式输出示例:
{"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_start","contentIndex":0,"partial":{...}}}
{"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello","partial":{...}}}
{"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":" world","partial":{...}}}
{"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Hello world","partial":{...}}}
tool_execution_start / tool_execution_update / tool_execution_end
当工具开始执行、流式输出进度并完成时发出。
{
"type": "tool_execution_start",
"toolCallId": "call_abc123",
"toolName": "bash",
"args": {"command": "ls -la"}
}
执行期间,tool_execution_update 事件会流式传递部分结果(例如,bash 输出到达时就发出):
{
"type": "tool_execution_update",
"toolCallId": "call_abc123",
"toolName": "bash",
"args": {"command": "ls -la"},
"partialResult": {
"content": [{"type": "text", "text": "partial output so far..."}],
"details": {"truncation": null, "fullOutputPath": null}
}
}
完成时:
{
"type": "tool_execution_end",
"toolCallId": "call_abc123",
"toolName": "bash",
"result": {
"content": [{"type": "text", "text": "total 48\n..."}],
"details": {...}
},
"isError": false
}
使用 toolCallId 关联这些事件。tool_execution_update 中的 partialResult 包含截至当前已累积的输出(不只是增量),这样客户端在每次更新时只需直接替换显示内容即可。
queue_update
每当待处理的 steering 或 follow-up 队列发生变化时发出。
{
"type": "queue_update",
"steering": ["Focus on error handling"],
"followUp": ["After that, summarize the result"]
}
compaction_start / compaction_end
在上下文压缩运行时触发,无论是手动还是自动。
{"type": "compaction_start", "reason": "threshold"}
reason 字段可以是 "manual"、"threshold" 或 "overflow"。
{
"type": "compaction_end",
"reason": "threshold",
"result": {
"summary": "Summary of conversation...",
"firstKeptEntryId": "abc123",
"tokensBefore": 150000,
"estimatedTokensAfter": 32000,
"details": {}
},
"aborted": false,
"willRetry": false
}
如果 reason 是 "overflow",且上下文压缩成功,willRetry 为 true,agent 会自动重试这个 prompt。
如果上下文压缩被中止,result 为 null,aborted 为 true。
如果上下文压缩失败(例如 API 配额超出),result 为 null,aborted 为 false,errorMessage 包含错误描述。
auto_retry_start / auto_retry_end
在临时错误(过载、速率限制、5xx)之后触发自动重试时发出。
{
"type": "auto_retry_start",
"attempt": 1,
"maxAttempts": 3,
"delayMs": 2000,
"errorMessage": "529 {\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded\"}}"
}
{
"type": "auto_retry_end",
"success": true,
"attempt": 2
}
在最终失败(超过最大重试次数)时:
{
"type": "auto_retry_end",
"success": false,
"attempt": 3,
"finalError": "529 overloaded_error: Overloaded"
}
extension_error
当扩展抛出错误时发出。
{
"type": "extension_error",
"extensionPath": "/path/to/extension.ts",
"event": "tool_call",
"error": "Error message..."
}
扩展 UI 协议
扩展可以通过 ctx.ui.select()、ctx.ui.confirm() 等请求用户交互。在 RPC mode 下,这些调用会被转换为建立在基础命令/事件流之上的请求/响应子协议。
扩展 UI 方法分为两类:
- 对话方法(
select、confirm、input、editor):在 stdout 上发出extension_ui_request,并阻塞等待客户端在 stdin 上返回带匹配id的extension_ui_response。 - 一次性发送方法(
notify、setStatus、setWidget、setTitle、set_editor_text):在 stdout 上发出extension_ui_request,但不期待响应。客户端可以展示这些信息,也可以忽略它。
如果对话方法包含 timeout 字段,agent 侧会在超时后自动返回默认值。客户端无需跟踪超时。
在 RPC mode 下,有些 ExtensionUIContext 方法不受支持,或会降级,因为它们需要直接访问 TUI:
custom()返回undefinedsetWorkingMessage()、setWorkingIndicator()、setFooter()、setHeader()、setEditorComponent()、setToolsExpanded()是无操作getEditorText()返回""getToolsExpanded()返回falsepasteToEditor()委托给setEditorText()(不处理粘贴/折叠逻辑)getAllThemes()返回[]getTheme()返回undefinedsetTheme()返回{ success: false, error: "..." }
注意:在 RPC mode 下,ctx.mode 是 "rpc",ctx.hasUI 是 true,因为对话方法和一次性发送方法可以通过扩展 UI 子协议正常工作。对于像 custom() 这样需要真实终端的 TUI 专属功能,请用 ctx.mode === "tui" 来做判断。
扩展 UI 请求(stdout)
所有请求都包含 type: "extension_ui_request"、唯一的 id 和一个 method 字段。
select
提示用户从列表中选择。带有 timeout 字段的对话方法会把超时值以毫秒为单位带上;如果客户端未及时响应,agent 会自动返回 undefined。
{
"type": "extension_ui_request",
"id": "uuid-1",
"method": "select",
"title": "Allow dangerous command?",
"options": ["Allow", "Block"],
"timeout": 10000
}
预期响应:extension_ui_response,包含 value(所选选项字符串)或 cancelled: true。
confirm
提示用户进行是/否确认。
{
"type": "extension_ui_request",
"id": "uuid-2",
"method": "confirm",
"title": "Clear session?",
"message": "All messages will be lost.",
"timeout": 5000
}
预期响应:extension_ui_response,包含 confirmed: true/false 或 cancelled: true。
input
提示用户输入任意文本。
{
"type": "extension_ui_request",
"id": "uuid-3",
"method": "input",
"title": "Enter a value",
"placeholder": "type something..."
}
预期响应:extension_ui_response,包含 value(输入的文本)或 cancelled: true。
editor
打开一个多行文本编辑器,可选地预填内容。
{
"type": "extension_ui_request",
"id": "uuid-4",
"method": "editor",
"title": "Edit some text",
"prefill": "Line 1\nLine 2\nLine 3"
}
预期响应:extension_ui_response,包含 value(编辑后的文本)或 cancelled: true。
notify
显示通知。一次性发送,不期待响应。
{
"type": "extension_ui_request",
"id": "uuid-5",
"method": "notify",
"message": "Command blocked by user",
"notifyType": "warning"
}
notifyType 字段可以是 "info"、"warning" 或 "error"。如果省略,默认是 "info"。
setStatus
在页脚/状态栏中设置或清除一条状态条目。一次性发送。
{
"type": "extension_ui_request",
"id": "uuid-6",
"method": "setStatus",
"statusKey": "my-ext",
"statusText": "Turn 3 running..."
}
发送 statusText: undefined(或省略它)即可清除该 key 对应的状态条目。
setWidget
设置或清除一个 widget(显示在编辑器上方或下方的一块文本行)。一次性发送。
{
"type": "extension_ui_request",
"id": "uuid-7",
"method": "setWidget",
"widgetKey": "my-ext",
"widgetLines": ["--- My Widget ---", "Line 1", "Line 2"],
"widgetPlacement": "aboveEditor"
}
发送 widgetLines: undefined(或省略它)即可清除 widget。widgetPlacement 字段可以是 "aboveEditor"(默认)或 "belowEditor"。在 RPC mode 下只支持字符串数组;component factories 会被忽略。
setTitle
设置终端窗口/标签页标题。一次性发送。
{
"type": "extension_ui_request",
"id": "uuid-8",
"method": "setTitle",
"title": "pi - my project"
}
set_editor_text
设置输入编辑器中的文本。一次性发送。
{
"type": "extension_ui_request",
"id": "uuid-9",
"method": "set_editor_text",
"text": "prefilled text for the user"
}
扩展 UI 响应(stdin)
响应只会发送给对话框方法(select、confirm、input、editor)。id 必须与请求一致。
值响应(select, input, editor)
{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}
确认响应(confirm)
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}
取消响应(任意对话框)
关闭任意对话框方法。扩展会收到 undefined(对应 select/input/editor)或 false(对应 confirm)。
{"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}
错误处理
执行失败的命令会返回一个 success: false 的响应:
{
"type": "response",
"command": "set_model",
"success": false,
"error": "Model not found: invalid/model"
}
解析错误:
{
"type": "response",
"command": "parse",
"success": false,
"error": "Failed to parse command: Unexpected token..."
}
类型
源文件:
packages/ai/src/types.ts-Model,UserMessage,AssistantMessage,ToolResultMessagepackages/agent/src/types.ts-AgentMessage,AgentEventsrc/core/messages.ts-BashExecutionMessagesrc/modes/rpc/rpc-types.ts- RPC command/response types, extension UI request/response types
Model
{
"id": "claude-sonnet-4-20250514",
"name": "Claude Sonnet 4",
"api": "anthropic-messages",
"provider": "anthropic",
"baseUrl": "https://api.anthropic.com",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 200000,
"maxTokens": 16384,
"cost": {
"input": 3.0,
"output": 15.0,
"cacheRead": 0.3,
"cacheWrite": 3.75
}
}
UserMessage
{
"role": "user",
"content": "Hello!",
"timestamp": 1733234567890,
"attachments": []
}
content 字段可以是字符串,也可以是 TextContent/ImageContent 块数组。
AssistantMessage
{
"role": "assistant",
"content": [
{"type": "text", "text": "Hello! How can I help?"},
{"type": "thinking", "thinking": "User is greeting me..."},
{"type": "toolCall", "id": "call_123", "name": "bash", "arguments": {"command": "ls"}}
],
"api": "anthropic-messages",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"usage": {
"input": 100,
"output": 50,
"cacheRead": 0,
"cacheWrite": 0,
"cost": {"input": 0.0003, "output": 0.00075, "cacheRead": 0, "cacheWrite": 0, "total": 0.00105}
},
"stopReason": "stop",
"timestamp": 1733234567890
}
停止原因:"stop"、"length"、"toolUse"、"error"、"aborted"
ToolResultMessage
{
"role": "toolResult",
"toolCallId": "call_123",
"toolName": "bash",
"content": [{"type": "text", "text": "total 48\ndrwxr-xr-x ..."}],
"isError": false,
"timestamp": 1733234567890
}
BashExecutionMessage
由 bash RPC command 创建(不是由 LLM 的 tool call 创建):
{
"role": "bashExecution",
"command": "ls -la",
"output": "total 48\ndrwxr-xr-x ...",
"exitCode": 0,
"cancelled": false,
"truncated": false,
"fullOutputPath": null,
"timestamp": 1733234567890
}
Attachment
{
"id": "img1",
"type": "image",
"fileName": "photo.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"content": "base64-encoded-data...",
"extractedText": null,
"preview": null
}
示例:基础客户端(Python)
import subprocess
import json
proc = subprocess.Popen(
["pi", "--mode", "rpc", "--no-session"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True
)
def send(cmd):
proc.stdin.write(json.dumps(cmd) + "\n")
proc.stdin.flush()
def read_events():
for line in proc.stdout:
yield json.loads(line)
# Send prompt
send({"type": "prompt", "message": "Hello!"})
# Process events
for event in read_events():
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_end":
print()
break
示例:交互式客户端(Node.js)
可查看 test/rpc-example.ts 获取完整的交互式示例,或查看 src/modes/rpc/rpc-client.ts 获取一个带类型的客户端实现。
如需查看处理扩展 UI 协议的完整示例,请参阅 examples/rpc-extension-ui.ts,它与 examples/extensions/rpc-demo.ts 扩展配套。
const { spawn } = require("child_process");
const { StringDecoder } = require("string_decoder");
const agent = spawn("pi", ["--mode", "rpc", "--no-session"]);
function attachJsonlReader(stream, onLine) {
const decoder = new StringDecoder("utf8");
let buffer = "";
stream.on("data", (chunk) => {
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
while (true) {
const newlineIndex = buffer.indexOf("\n");
if (newlineIndex === -1) break;
let line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
if (line.endsWith("\r")) line = line.slice(0, -1);
onLine(line);
}
});
stream.on("end", () => {
buffer += decoder.end();
if (buffer.length > 0) {
onLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
}
});
}
attachJsonlReader(agent.stdout, (line) => {
const event = JSON.parse(line);
if (event.type === "message_update") {
const { assistantMessageEvent } = event;
if (assistantMessageEvent.type === "text_delta") {
process.stdout.write(assistantMessageEvent.delta);
}
}
});
// Send prompt
agent.stdin.write(JSON.stringify({ type: "prompt", message: "Hello" }) + "\n");
// Abort on Ctrl+C
process.on("SIGINT", () => {
agent.stdin.write(JSON.stringify({ type: "abort" }) + "\n");
});