RPC 模式
RPC 模式通过 stdin/stdout 上的 JSON 协议实现编码智能体的无头运行,便于将智能体嵌入其他应用、集成开发环境(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 模式
Section titled “启动 RPC 模式”pi --mode rpc [options]常用选项:
--provider <name>:设置 LLM 模型提供方(provider)(anthropic、openai、google 等)--model <pattern>:模型模式或 ID(支持provider/id和可选的:<thinking>)--name <name>/-n <name>:启动时设置会话(session)显示名称--no-session:禁用会话持久化--session-dir <path>:自定义会话存储目录
- 命令(Commands):发送到 stdin 的 JSON 对象,每行一条
- 响应(Responses):JSON 对象,
type: "response"表示命令成功或失败 - 事件(Events):智能体(agent)事件以 JSON 行形式流式输出到 stdout
所有命令都支持可选的 id 字段,用于请求/响应关联。若提供该字段,对应的响应会包含相同的 id。bash_execution_update 事件也会包含其来源 bash 命令的 id。
帧格式(Framing)
Section titled “帧格式(Framing)”RPC(远程过程调用)模式采用严格的 JSONL 语义,以 LF(\n)作为唯一记录分隔符。
这对客户端很重要:
- 只按
\n切分记录 - 接受可选的
\r\n输入,去掉末尾的\r - 不要使用会把 Unicode 分隔符当作换行的通用行读取器
特别地,Node 的 readline 不符合 RPC 模式的协议要求,因为它还会按 U+2028 和 U+2029 切分,而这两个字符在 JSON 字符串中是合法的。
命令(Commands)
Section titled “命令(Commands)”提示(Prompting)
Section titled “提示(Prompting)”prompt
Section titled “prompt”向智能体发送一条用户提示词。命令响应在提示词被接受、排队或处理之后发出。接受后事件会继续异步流式输出。
{"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"}]}流式输出期间:如果智能体正在流式输出,必须指定 streamingBehavior 来排队消息:
{"type": "prompt", "message": "New instruction", "streamingBehavior": "steer"}"steer":智能体运行期间将消息排队。它会在当前助手回合完成工具(tool)调用之后、下一次 LLM(大语言模型)调用之前投递。"followUp":等待智能体完成。只有在智能体停止时才投递消息。
如果智能体正在流式输出且未指定 streamingBehavior,命令返回错误。
扩展命令:如果消息是扩展(extension)命令(如 /mycommand),即使在流式输出期间也会立即执行。扩展命令通过 pi.sendMessage() 自行管理 LLM 交互。
输入展开:技能(skill)命令(/skill:name)和提示词模板(/template)会在发送或排队前展开。
响应:
{"id": "req-1", "type": "response", "command": "prompt", "success": true}success: true 表示提示词已立即被接受、排队或处理。success: false 表示提示词在接受前被拒绝。接受之后的失败通过正常的事件和消息流报告,而不会针对同一请求 id 返回第二条 response。
images 字段可选。每张图片使用 ImageContent 格式:{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}。
在智能体运行期间排队一条引导消息。它会在当前助手回合完成工具调用之后、下一次 LLM 调用之前投递。技能命令和提示词模板会被展开。不允许扩展命令(请改用 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
Section titled “follow_up”排队一条后续消息,在智能体完成后处理。只有在智能体没有更多工具调用或引导消息时才投递。技能命令和提示词模板会被展开。不允许扩展命令(请改用 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。
中止当前的智能体操作。
{"type": "abort"}响应:
{"type": "response", "command": "abort", "success": true}new_session
Section titled “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}}状态(State)
Section titled “状态(State)”get_state
Section titled “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
Section titled “get_messages”获取对话中的所有消息。
{"type": "get_messages"}响应:
{ "type": "response", "command": "get_messages", "success": true, "data": {"messages": [...]}}消息是 AgentMessage 对象(见 消息类型)。
模型(Model)
Section titled “模型(Model)”set_model
Section titled “set_model”切换到指定模型。
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}响应包含完整的 Model 对象:
{ "type": "response", "command": "set_model", "success": true, "data": {...}}cycle_model
Section titled “cycle_model”循环切换到下一个可用模型。如果只有一个可用模型,返回 null 数据。
{"type": "cycle_model"}响应:
{ "type": "response", "command": "cycle_model", "success": true, "data": { "model": {...}, "thinkingLevel": "medium", "isScoped": false }}model 字段是完整的 Model 对象。
get_available_models
Section titled “get_available_models”列出所有已配置的模型。
{"type": "get_available_models"}响应包含完整的 Model 对象数组:
{ "type": "response", "command": "get_available_models", "success": true, "data": { "models": [...] }}思考(Thinking)
Section titled “思考(Thinking)”set_thinking_level
Section titled “set_thinking_level”为支持的模型设置推理/思考级别。
{"type": "set_thinking_level", "level": "high"}级别:"off"、"minimal"、"low"、"medium"、"high"、"xhigh"、"max"
"xhigh" 和 "max" 仅在所选模型支持时暴露。有些模型(包括 GPT-5.6)两者都暴露。
响应:
{"type": "response", "command": "set_thinking_level", "success": true}cycle_thinking_level
Section titled “cycle_thinking_level”循环切换可用的思考级别。如果模型不支持思考,返回 null 数据。
{"type": "cycle_thinking_level"}响应:
{ "type": "response", "command": "cycle_thinking_level", "success": true, "data": {"level": "high"}}get_available_thinking_levels
Section titled “get_available_thinking_levels”列出当前模型支持的思考级别。对于不支持推理的模型,返回 ["off"]。
{"type": "get_available_thinking_levels"}响应:
{ "type": "response", "command": "get_available_thinking_levels", "success": true, "data": { "levels": ["off", "minimal", "low", "medium", "high"] }}队列模式(Queue Modes)
Section titled “队列模式(Queue Modes)”set_steering_mode
Section titled “set_steering_mode”控制引导消息(来自 steer)的投递方式。
{"type": "set_steering_mode", "mode": "one-at-a-time"}模式:
"all":在当前助手回合完成工具调用之后,一次性投递所有引导消息"one-at-a-time":每个完成的助手回合投递一条引导消息(默认)
响应:
{"type": "response", "command": "set_steering_mode", "success": true}set_follow_up_mode
Section titled “set_follow_up_mode”控制后续消息(来自 follow_up)的投递方式。
{"type": "set_follow_up_mode", "mode": "one-at-a-time"}模式:
"all":智能体完成时一次性投递所有后续消息"one-at-a-time":每次智能体完成投递一条后续消息(默认)
响应:
{"type": "response", "command": "set_follow_up_mode", "success": true}上下文压缩(Compaction)
Section titled “上下文压缩(Compaction)”compact
Section titled “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, "usage": { "input": 32000, "output": 1200, "cacheRead": 0, "cacheWrite": 0, "totalTokens": 33200, "cost": {"input": 0.01, "output": 0.02, "cacheRead": 0, "cacheWrite": 0, "total": 0.03} }, "details": {} }}estimatedTokensAfter 是压缩后立即对重建消息上下文做出的启发式估算,并非模型提供方精确的 token 数。usage 报告生成摘要的那一次或多次 LLM 调用,自定义压缩处理器可以省略它。
set_auto_compaction
Section titled “set_auto_compaction”启用或禁用上下文(context window)接近满载时的自动压缩。
{"type": "set_auto_compaction", "enabled": true}响应:
{"type": "response", "command": "set_auto_compaction", "success": true}重试(Retry)
Section titled “重试(Retry)”set_auto_retry
Section titled “set_auto_retry”启用或禁用瞬时错误(过载、速率限制、5xx)时的自动重试。
{"type": "set_auto_retry", "enabled": true}响应:
{"type": "response", "command": "set_auto_retry", "success": true}abort_retry
Section titled “abort_retry”中止正在进行的重试(取消延迟并停止重试)。
{"type": "abort_retry"}响应:
{"type": "response", "command": "abort_retry", "success": true}执行 shell 命令并把输出加入对话上下文。命令运行期间,输出以 bash_execution_update 事件流式传输;响应包含最终结果。
{"id": "req-1", "type": "bash", "command": "ls -la"}带上 id,以便把流式传输的 bash_execution_update 事件与此命令关联。
响应:
{ "id": "req-1", "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 并存入智能体的消息状态。
当发送下一条 prompt 命令时,所有消息(包括 BashExecutionMessage)在发送给 LLM 之前都会经过转换。BashExecutionMessage 会转换成如下格式的 UserMessage:
Ran `ls -la````total 48drwxr-xr-x ...```这意味着:
- bash 输出会在下一条 prompt 中进入 LLM 上下文,而非立即
- 可以在一条 prompt 之前执行多条 bash 命令,所有输出都会被包含
abort_bash
Section titled “abort_bash”中止正在运行的 bash 命令。
{"type": "abort_bash"}响应:
{"type": "response", "command": "abort_bash", "success": true}会话(Session)
Section titled “会话(Session)”get_session_stats
Section titled “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 和 cost 包含整个会话中的助手消息、工具上报的用量,以及压缩和分支(branch)摘要生成的用量。contextUsage 包含用于压缩和底部显示的实际当前上下文窗口估算值。
当没有模型或上下文窗口可用时,contextUsage 会被省略。压缩后,contextUsage.tokens 和 contextUsage.percent 为 null,直到压缩后的新助手响应提供有效的用量数据。
export_html
Section titled “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
Section titled “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}}从活动分支上的一条先前用户消息创建新分支。可以被 session_before_fork 扩展事件处理器取消。返回被分支的消息文本。
{"type": "fork", "entryId": "abc123"}响应:
{ "type": "response", "command": "fork", "success": true, "data": {"text": "The original prompt text...", "cancelled": false}}如果扩展取消了分支:
{ "type": "response", "command": "fork", "success": true, "data": {"text": "The original prompt text...", "cancelled": true}}把当前活动分支复制成一个位于当前位置的新会话。可以被 session_before_fork 扩展事件处理器取消。
{"type": "clone"}响应:
{ "type": "response", "command": "clone", "success": true, "data": {"cancelled": false}}如果扩展取消了克隆:
{ "type": "response", "command": "clone", "success": true, "data": {"cancelled": true}}get_fork_messages
Section titled “get_fork_messages”获取可用于分支的用户消息。
{"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_entries
Section titled “get_entries”按追加顺序获取所有会话条目(不含会话头部)。会话是一个只追加的条目树,条目 id 稳定,因此条目 id 可以用作持久游标:把最后看到的条目 id 作为 since 传入,即可只获取它之后的条目,即使客户端重启也有效。与 get_messages 不同,这里包含压缩前的历史和废弃分支。
{"type": "get_entries"}带游标:
{"type": "get_entries", "since": "abc123"}响应:
{ "type": "response", "command": "get_entries", "success": true, "data": { "entries": [ {"type": "message", "id": "def456", "parentId": "abc123", "timestamp": "...", "message": {"role": "user", "...": "..."}} ], "leafId": "def456" }}leafId 是当前叶子条目的 id(空会话为 null),客户端只需一次往返即可判断活动分支是否移动。如果 since 不匹配任何条目 id,响应为 success: false。
get_tree
Section titled “get_tree”将会话作为条目树获取。每个节点是 {entry, children, label?, labelTimestamp?}。格式良好的会话有单一根节点;孤立条目(父链断裂)也会作为根节点出现。
{"type": "get_tree"}响应:
{ "type": "response", "command": "get_tree", "success": true, "data": { "tree": [ { "entry": {"type": "message", "id": "abc123", "parentId": null, "...": "..."}, "children": [ {"entry": {"type": "message", "id": "def456", "parentId": "abc123", "...": "..."}, "children": []} ] } ], "leafId": "def456" }}get_last_assistant_text
Section titled “get_last_assistant_text”获取最后一条助手消息的文本内容。
{"type": "get_last_assistant_text"}响应:
{ "type": "response", "command": "get_last_assistant_text", "success": true, "data": {"text": "The assistant's response..."}}如果不存在助手消息,返回 {"text": null}。
set_session_name
Section titled “set_session_name”为当前会话设置显示名称。该名称会出现在会话列表中,便于识别会话。
{"type": "set_session_name", "name": "my-feature-work"}响应:
{ "type": "response", "command": "set_session_name", "success": true}当前会话名称可通过 get_state 的 sessionName 字段获取。要在启动 RPC 模式时设置初始名称,请向 pi --mode rpc 进程传入 --name <name> 或 -n <name>。
命令(Commands)
Section titled “命令(Commands)”get_commands
Section titled “get_commands”获取可用命令(扩展命令、提示词模板和技能)。可以通过 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:前缀)
location:加载位置(可选,扩展命令没有该字段):"user":用户级(~/.pi/agent/)"project":项目级(./.pi/agent/)"path":通过 CLI(命令行界面)或设置指定的显式路径
path:命令源文件的绝对路径(可选)
注意:内置 TUI(终端界面)命令(/settings、/hotkeys 等)不包含在内。它们只在交互式模式中处理,通过 prompt 发送不会执行。
事件(Events)
Section titled “事件(Events)”事件在智能体运行期间以 JSON 行的形式流式输出到 stdout。事件一般不含 id 字段;bash_execution_update 在其来源 bash 命令提供了 id 时会包含该 id。
事件类型(Event Types)
Section titled “事件类型(Event Types)”| 事件 | 说明 |
|---|---|
agent_start |
智能体开始处理 |
agent_end |
一次底层智能体运行完成(之后仍可能发生重试、压缩或排队的后续操作) |
agent_settled |
智能体运行完全平息;没有自动重试、压缩重试或排队的后续操作 |
turn_start |
新回合开始 |
turn_end |
回合完成(包含助手消息和工具结果) |
message_start |
消息开始 |
message_update |
流式更新(文本/思考/工具调用增量) |
message_end |
消息完成 |
bash_execution_update |
直接 RPC bash 命令的输出块 |
tool_execution_start |
工具开始执行 |
tool_execution_update |
工具执行进度(流式输出) |
tool_execution_end |
工具完成 |
queue_update |
待处理的引导/后续队列发生变化 |
compaction_start |
压缩开始 |
compaction_end |
压缩完成 |
auto_retry_start |
自动重试开始(瞬时错误之后) |
auto_retry_end |
自动重试完成(成功或最终失败) |
summarization_retry_scheduled |
已为瞬时压缩或分支摘要错误安排重试 |
summarization_retry_attempt_start |
重试的摘要请求开始 |
summarization_retry_finished |
摘要重试循环完成 |
extension_error |
扩展抛出了错误 |
agent_start
Section titled “agent_start”智能体开始处理提示词时发出。
{"type": "agent_start"}agent_end
Section titled “agent_end”一次底层智能体运行完成时发出。包含此次运行生成的所有消息。如果 willRetry 为 true,会自动重试。
{ "type": "agent_end", "messages": [...], "willRetry": false}agent_settled
Section titled “agent_settled”整个会话级运行平息后发出。此时 Pi 不会继续自动重试、压缩重试或处理排队的后续消息。
{"type": "agent_settled"}turn_start / turn_end
Section titled “turn_start / turn_end”一个回合包含一次助手响应及由此产生的工具调用和结果。
{"type": "turn_start"}{ "type": "turn_end", "message": {...}, "toolResults": [...]}message_start / message_end
Section titled “message_start / message_end”消息开始和完成时发出。message 字段包含一个 AgentMessage。
{"type": "message_start", "message": {...}}{"type": "message_end", "message": {...}}message_update(流式)
Section titled “message_update(流式)”助手消息流式输出期间发出。包含增量事件,不提供累积的消息快照。
{ "type": "message_update", "assistantMessageEvent": { "type": "text_delta", "contentIndex": 0, "delta": "Hello " }}assistantMessageEvent 字段包含以下增量类型之一:
| 类型 | 说明 |
|---|---|
text_start |
文本内容块开始 |
text_delta |
文本内容块片段 |
text_end |
文本内容块结束 |
thinking_start |
思考块开始 |
thinking_delta |
思考内容块片段 |
thinking_end |
思考块结束 |
toolcall_start |
工具调用开始 |
toolcall_delta |
工具调用参数片段 |
toolcall_end |
工具调用结束(包含完整的 toolCall 对象) |
流式输出文本响应的示例:
{"type":"message_update","assistantMessageEvent":{"type":"text_start","contentIndex":0}}{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":" world"}}{"type":"message_update","assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Hello world"}}message_update 有意省略了原先累积的 message 字段和 assistantMessageEvent.partial。需要实时部分消息的客户端必须根据 message_start 及后续事件用 contentIndex 自行组装。以 message_end.message 为准。对于工具调用,缓存 toolcall_delta.delta;toolcall_end.toolCall 包含完成的调用。
bash_execution_update
Section titled “bash_execution_update”直接 bash 命令的每个输出块都会发出一次。id 与命令的 id 匹配,便于客户端把输出关联到正确的命令。
命令运行期间事件会流式传输所有输出,即使最终 bash 响应的 output 被截断。
{ "type": "bash_execution_update", "id": "req-1", "delta": "total 48\n"}tool_execution_start / tool_execution_update / tool_execution_end
Section titled “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
Section titled “queue_update”待处理的引导或后续队列发生变化时发出。
{ "type": "queue_update", "steering": ["Focus on error handling"], "followUp": ["After that, summarize the result"]}compaction_start / compaction_end
Section titled “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, "usage": { "input": 32000, "output": 1200, "cacheRead": 0, "cacheWrite": 0, "totalTokens": 33200, "cost": {"input": 0.01, "output": 0.02, "cacheRead": 0, "cacheWrite": 0, "total": 0.03} }, "details": {} }, "aborted": false, "willRetry": false}如果 reason 为 "overflow" 且压缩成功,willRetry 为 true,智能体会自动重试该提示词。
如果压缩被中止,result 为 null 且 aborted 为 true。
如果压缩失败(例如 API 配额耗尽),result 为 null、aborted 为 false,errorMessage 包含错误描述。
auto_retry_start / auto_retry_end
Section titled “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"}summarization_retry_scheduled / summarization_retry_attempt_start / summarization_retry_finished
Section titled “summarization_retry_scheduled / summarization_retry_attempt_start / summarization_retry_finished”压缩或分支摘要在瞬时模型提供方错误后重试时发出。这些事件使用与助手回合自动重试相同的重试设置。
{ "type": "summarization_retry_scheduled", "attempt": 1, "maxAttempts": 3, "delayMs": 2000, "errorMessage": "terminated"}{ "type": "summarization_retry_attempt_start", "source": "compaction", "reason": "threshold"}对于分支摘要,source 为 "branchSummary",且没有 reason 字段。
{ "type": "summarization_retry_finished"}extension_error
Section titled “extension_error”扩展抛出错误时发出。
{ "type": "extension_error", "extensionPath": "/path/to/extension.ts", "event": "tool_call", "error": "Error message..."}扩展 UI 协议(Extension UI Protocol)
Section titled “扩展 UI 协议(Extension UI Protocol)”扩展可以通过 ctx.ui.select()、ctx.ui.confirm() 等请求用户交互。在 RPC 模式下,这些调用会被转换成基础命令/事件流之上的请求/响应子协议。
有两类扩展 UI 方法:
- 对话框方法(Dialog)(
select、confirm、input、editor):在 stdout 上发出extension_ui_request,并阻塞等待客户端在 stdin 上回传带相同id的extension_ui_response。 - 即发即忘方法(Fire-and-forget)(
notify、setStatus、setWidget、setTitle、set_editor_text):在 stdout 上发出extension_ui_request,但不期望响应。客户端可以显示这些信息,也可以忽略。
如果对话框方法包含 timeout 字段,超时后智能体侧会用默认值自动解析。客户端无需跟踪超时。
某些 ExtensionUIContext 方法在 RPC 模式下不受支持或功能降级,因为它们需要直接访问 TUI:
custom()返回undefinedsetWorkingMessage()、setWorkingIndicator()、setFooter()、setHeader()、setEditorComponent()、setToolsExpanded()是空操作getEditorText()返回""getToolsExpanded()返回falsepasteToEditor()委托给setEditorText()(无粘贴/折叠处理)getAllThemes()返回[]getTheme()返回undefinedsetTheme()返回{ success: false, error: "..." }
注意:RPC 模式下 ctx.mode 为 "rpc"、ctx.hasUI 为 true,因为对话框和即发即忘方法可通过扩展 UI 子协议正常使用。要用 ctx.mode === "tui" 来守卫依赖真实终端的 TUI 专属功能(如 custom())。
扩展 UI 请求(stdout)
Section titled “扩展 UI 请求(stdout)”所有请求都有 type: "extension_ui_request"、唯一的 id 以及 method 字段。
select
Section titled “select”提示用户从列表中选择。带 timeout 字段的对话框方法会包含以毫秒为单位的超时;如果客户端未及时响应,智能体会自动用 undefined 解析。
{ "type": "extension_ui_request", "id": "uuid-1", "method": "select", "title": "Allow dangerous command?", "options": ["Allow", "Block"], "timeout": 10000}期望的响应:带 value(所选选项字符串)或 cancelled: true 的 extension_ui_response。
confirm
Section titled “confirm”提示用户进行是/否确认。
{ "type": "extension_ui_request", "id": "uuid-2", "method": "confirm", "title": "Clear session?", "message": "All messages will be lost.", "timeout": 5000}期望的响应:带 confirmed: true/false 或 cancelled: true 的 extension_ui_response。
提示用户输入自由文本。
{ "type": "extension_ui_request", "id": "uuid-3", "method": "input", "title": "Enter a value", "placeholder": "type something..."}期望的响应:带 value(输入的文本)或 cancelled: true 的 extension_ui_response。
editor
Section titled “editor”打开一个带可选预填内容的多行文本编辑器。
{ "type": "extension_ui_request", "id": "uuid-4", "method": "editor", "title": "Edit some text", "prefill": "Line 1\nLine 2\nLine 3"}期望的响应:带 value(编辑后的文本)或 cancelled: true 的 extension_ui_response。
notify
Section titled “notify”显示通知。即发即忘,不期望响应。
{ "type": "extension_ui_request", "id": "uuid-5", "method": "notify", "message": "Command blocked by user", "notifyType": "warning"}notifyType 字段为 "info"、"warning" 或 "error"。省略时默认为 "info"。
setStatus
Section titled “setStatus”在底部/状态栏设置或清除一个状态条目。即发即忘。
{ "type": "extension_ui_request", "id": "uuid-6", "method": "setStatus", "statusKey": "my-ext", "statusText": "Turn 3 running..."}发送 statusText: undefined(或省略)来清除该键的状态条目。
setWidget
Section titled “setWidget”在编辑器上方或下方设置或清除一个部件(文本行块)。即发即忘。
{ "type": "extension_ui_request", "id": "uuid-7", "method": "setWidget", "widgetKey": "my-ext", "widgetLines": ["--- My Widget ---", "Line 1", "Line 2"], "widgetPlacement": "aboveEditor"}发送 widgetLines: undefined(或省略)来清除部件。widgetPlacement 字段为 "aboveEditor"(默认)或 "belowEditor"。RPC 模式只支持字符串数组;组件工厂会被忽略。
setTitle
Section titled “setTitle”设置终端窗口/标签页标题。即发即忘。
{ "type": "extension_ui_request", "id": "uuid-8", "method": "setTitle", "title": "pi - my project"}set_editor_text
Section titled “set_editor_text”在输入编辑器中设置文本。即发即忘。
{ "type": "extension_ui_request", "id": "uuid-9", "method": "set_editor_text", "text": "prefilled text for the user"}扩展 UI 响应(stdin)
Section titled “扩展 UI 响应(stdin)”响应只针对对话框方法(select、confirm、input、editor)发送。id 必须与请求匹配。
值响应(select、input、editor)
Section titled “值响应(select、input、editor)”{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}确认响应(confirm)
Section titled “确认响应(confirm)”{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}取消响应(任意对话框)
Section titled “取消响应(任意对话框)”关闭任意对话框方法。扩展会收到 undefined(select/input/editor)或 false(confirm)。
{"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}错误处理(Error Handling)
Section titled “错误处理(Error Handling)”失败的命令返回 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..."}类型(Types)
Section titled “类型(Types)”源文件:
packages/ai/src/types.ts-Model、UserMessage、AssistantMessage、ToolResultMessagepackages/agent/src/types.ts-AgentMessage、AgentEventsrc/core/messages.ts-BashExecutionMessagesrc/modes/json-event.ts-JsonAgentSessionEventsrc/modes/rpc/rpc-types.ts- RPC 命令/响应类型、扩展 UI 请求/响应类型
{ "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
Section titled “UserMessage”{ "role": "user", "content": "Hello!", "timestamp": 1733234567890, "attachments": []}content 字段可以是字符串,也可以是 TextContent/ImageContent 块组成的数组。
AssistantMessage
Section titled “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
Section titled “ToolResultMessage”{ "role": "toolResult", "toolCallId": "call_123", "toolName": "bash", "content": [{"type": "text", "text": "total 48\ndrwxr-xr-x ..."}], "usage": { "input": 100, "output": 50, "cacheRead": 0, "cacheWrite": 0, "totalTokens": 150, "cost": {"input": 0.0003, "output": 0.00075, "cacheRead": 0, "cacheWrite": 0, "total": 0.00105} }, "isError": false, "timestamp": 1733234567890}usage 可选,报告工具执行的内嵌 LLM 工作。存在时会计入会话 token 和成本总量。
BashExecutionMessage
Section titled “BashExecutionMessage”由 bash RPC 命令创建(而非 LLM 工具调用):
{ "role": "bashExecution", "command": "ls -la", "output": "total 48\ndrwxr-xr-x ...", "exitCode": 0, "cancelled": false, "truncated": false, "fullOutputPath": null, "timestamp": 1733234567890}Attachment
Section titled “Attachment”{ "id": "img1", "type": "image", "fileName": "photo.jpg", "mimeType": "image/jpeg", "size": 102400, "content": "base64-encoded-data...", "extractedText": null, "preview": null}示例:基础客户端(Python)
Section titled “示例:基础客户端(Python)”import subprocessimport 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({"type": "prompt", "message": "Hello!"})
# 处理事件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)
Section titled “示例:交互式客户端(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); } }});
// 发送提示词agent.stdin.write(JSON.stringify({ type: "prompt", message: "Hello" }) + "\n");
// 按 Ctrl+C 中止process.on("SIGINT", () => { agent.stdin.write(JSON.stringify({ type: "abort" }) + "\n");});