RPC 模式
RPC 模式通过 stdin/stdout 上的 JSON 协议实现编码智能体的无头操作。这对于将智能体嵌入其他应用程序、IDE 或自定义 UI 非常有用。
Node.js/TypeScript 用户注意事项:如果您正在构建 Node.js 应用程序,请考虑直接使用 AgentSession ,而不是生成子进程。请参阅 @earendil-works/pi-coding-agent 了解 API。有关基于子进程的 TypeScript 客户端,请参阅 src/core/agent-session.ts 了解 API。有关基于子进程的 TypeScript 客户端,请参阅 src/modes/rpc/rpc-client.ts.
启动 RPC 模式
pi --mode rpc [options]常用选项:
--provider <name>:设置 LLM 提供商(anthropic、openai、google 等)--model <pattern>:模型模式或 ID(支持provider/id和可选的:<thinking>)--name <name>/-n <name>:在启动时设置会话显示名称--no-session:禁用会话持久化--session-dir <path>:自定义会话存储目录
协议概述
- 命令:发送到 stdin 的 JSON 对象,每行一个
- 响应:带有
type: "response"的 JSON 对象,指示命令成功/失败 - 事件:以 JSON 行形式流式传输到 stdout 的智能体事件
所有命令都支持可选的 id 字段,用于请求/响应关联。如果提供,相应的响应将包含相同的 id. bash_execution_update 事件也包含其发起 id 命令的 bash 命令。
帧格式
RPC 模式使用严格的 JSONL 语义,以 LF(\n)作为唯一的记录分隔符。
这对客户端很重要:
- 仅根据
\n分割记录 - 接受可选的
\r\n输入,通过去除尾随的\r - 不要使用将 Unicode 分隔符视为换行符的通用行读取器
特别是,Node readline 对于 RPC 模式不符合协议,因为它也会在 U+2028 和 U+2029上分割,而这些在 JSON 字符串中是有效的。
命令
提示
提示词
向智能体发送用户提示。命令响应在提示被接受、排队或处理后发出。接受后,事件继续异步流式传输。
{"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":在智能体运行时排队消息。它在当前助手回合完成执行其工具调用后、下一次 LLM 调用之前传递。"followUp":等待智能体完成。消息仅在智能体停止时传递。
如果智能体正在流式传输且未指定 streamingBehavior ,命令将返回错误。
扩展命令:如果消息是扩展命令(例如 /mycommand),即使在流式传输期间也会立即执行。扩展命令通过 pi.sendMessage().
管理自己的 LLM 交互。输入扩展:技能命令(/skill:name)和提示词模板(/template)在发送/排队前展开。
响应:
{"id": "req-1", "type": "response", "command": "prompt", "success": true}success: true 表示提示词已被接受、排队或立即处理。 success: false 表示提示词在接受前被拒绝。接受后的失败通过正常的事件和消息流报告,而不是对同一请求 ID 再次返回 response 用于相同的请求 ID。
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}请参阅 设置转向模式 了解如何控制引导消息的处理。
后续
排队一条后续消息,在智能体完成后处理。仅当智能体没有更多工具调用或引导消息时传递。技能命令和提示词模板会被展开。不允许扩展命令(请改用 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}请参阅 设置跟进模式 了解如何控制后续消息的处理。
中止
中止当前的智能体操作。
{"type": "abort"}响应:
{"type": "response", "command": "abort", "success": true}新建会话
开始一个新会话。可以被 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}}状态
获取状态
获取当前会话状态。
{"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 字段是一个完整的 模型 对象或 null。 sessionName 字段是通过 set_session_name设置的显示名称,如果未设置则省略。
获取消息
获取对话中的所有消息。
{"type": "get_messages"}响应:
{
"type": "response",
"command": "get_messages",
"success": true,
"data": {"messages": [...]}
}消息是 AgentMessage 对象(请参阅 消息类型).
模型
设置模型
切换到特定模型。
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}响应包含完整的 模型 对象:
{
"type": "response",
"command": "set_model",
"success": true,
"data": {...}
}循环模型
循环到下一个可用模型。如果只有一个模型可用,则返回 null 数据。
{"type": "cycle_model"}响应:
{
"type": "response",
"command": "cycle_model",
"success": true,
"data": {
"model": {...},
"thinkingLevel": "medium",
"isScoped": false
}
}model 字段是一个完整的 模型 对象。
获取可用模型
列出所有已配置的模型。
{"type": "get_available_models"}响应包含一个完整的 模型 对象数组:
{
"type": "response",
"command": "get_available_models",
"success": true,
"data": {
"models": [...]
}
}思考
设置思考级别
为支持推理/思考的模型设置推理/思考级别。
{"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}循环思考层级
循环切换可用的思考级别。如果模型不支持思考,则返回 null 数据。
{"type": "cycle_thinking_level"}响应:
{
"type": "response",
"command": "cycle_thinking_level",
"success": true,
"data": {"level": "high"}
}获取可用的思考层级
列出当前模型支持的思考级别。对于不支持推理的模型,返回 ["off"] 用于不支持推理的模型。
{"type": "get_available_thinking_levels"}响应:
{
"type": "response",
"command": "get_available_thinking_levels",
"success": true,
"data": {
"levels": ["off", "minimal", "low", "medium", "high"]
}
}队列模式
设置转向模式
控制来自 steer的引导消息的传递方式。
{"type": "set_steering_mode", "mode": "one-at-a-time"}模式:
"all":在当前助手回合完成执行其工具调用后传递所有引导消息"one-at-a-time":每个完成的助手回合传递一条引导消息(默认)
响应:
{"type": "response", "command": "set_steering_mode", "success": true}设置跟进模式
控制来自 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}压缩
压缩
手动压缩对话上下文以减少令牌使用量。
{"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 是压缩后立即对重建的消息上下文进行的启发式估计,并非提供商精确的令牌计数。 usage 报告生成摘要的 LLM 调用,自定义压缩处理程序可能会省略此项。
设置自动压缩
启用或禁用在上下文接近满载时的自动压缩。
{"type": "set_auto_compaction", "enabled": true}响应:
{"type": "response", "command": "set_auto_compaction", "success": true}重试
设置自动重试
启用或禁用在瞬时错误(过载、速率限制、5xx)时的自动重试。
{"type": "set_auto_retry", "enabled": true}响应:
{"type": "response", "command": "set_auto_retry", "success": true}中止重试
中止正在进行的重试(取消延迟并停止重试)。
{"type": "abort_retry"}响应:
{"type": "response", "command": "abort_retry", "success": true}Bash
bash
执行 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 48
drwxr-xr-x ...
```
这意味着:
- Bash 输出在 下一次提示时包含在 LLM 上下文中,而不是立即包含
- 可以在一次提示之前执行多个 bash 命令;所有输出都将被包含
终止_bash
中止正在运行的 bash 命令。
{"type": "abort_bash"}响应:
{"type": "response", "command": "abort_bash", "success": true}会话
获取会话统计信息
获取令牌使用量、成本统计以及当前上下文窗口使用情况。
{"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 包括助手消息、工具报告的使用量,以及整个会话中的压缩/分支摘要生成。 contextUsage 包含用于压缩和页脚显示的实际当前上下文窗口估计值。
contextUsage 在没有模型或上下文窗口可用时被省略。 contextUsage.tokens 和 contextUsage.percent 在压缩后立即 null ,直到压缩后的新助手响应提供有效的使用数据。
导出HTML
将会话导出为 HTML 文件。
{"type": "export_html"}使用自定义路径:
{"type": "export_html", "outputPath": "/tmp/session.html"}响应:
{
"type": "response",
"command": "export_html",
"success": true,
"data": {"path": "/tmp/session.html"}
}切换会话
加载不同的会话文件。可以被 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}
}获取分叉消息
获取可用于分叉的用户消息。
{"type": "get_fork_messages"}响应:
{
"type": "response",
"command": "get_fork_messages",
"success": true,
"data": {
"messages": [
{"entryId": "abc123", "text": "First prompt..."},
{"entryId": "def456", "text": "Second prompt..."}
]
}
}获取条目
按追加顺序获取所有会话条目(不包括会话头)。会话是一个具有稳定 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.
获取树
将会话作为条目树获取。每个节点是 {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"
}
}获取最后一条助手文本
获取最后一条助手消息的文本内容。
{"type": "get_last_assistant_text"}响应:
{
"type": "response",
"command": "get_last_assistant_text",
"success": true,
"data": {"text": "The assistant's response..."}
}返回 {"text": null} 如果不存在助手消息。
设置会话名称
为当前会话设置显示名称。该名称会出现在会话列表中,有助于识别会话。
{"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 进程。
命令
获取命令
获取可用命令(扩展命令、提示词模板和技能)。这些可以通过 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.
事件
事件在智能体操作期间以 JSON 行形式流式传输到 stdout。事件通常不包含 id 字段; bash_execution_update 包含其原始 id 的 bash 命令(如果提供了的话)。
事件类型
| 事件 | 描述 |
|---|---|
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 | 扩展抛出了一个错误 |
代理启动
当智能体开始处理提示词时发出。
{"type": "agent_start"}代理结束
当一次低级智能体运行完成时发出。包含此次运行中生成的所有消息。如果 willRetry 为 true,将自动进行重试。
{
"type": "agent_end",
"messages": [...],
"willRetry": false
}代理已结算
在完整的会话级运行稳定后发出。此时 Pi 不会通过重试、压缩重试或排队的后续消息自动继续。
{"type": "agent_settled"}turn_start / turn_end
一个回合包含一个助手响应以及由此产生的任何工具调用和结果。
{"type": "turn_start"}{
"type": "turn_end",
"message": {...},
"toolResults": [...]
}message_start / message_end
当消息开始和完成时发出。 message 字段包含一个 AgentMessage.
{"type": "message_start", "message": {...}}
{"type": "message_end", "message": {...}}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 执行更新
为直接 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
当工具开始、流式传输进度并完成执行时发出。
{
"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 来关联事件。 partialResult 在 tool_execution_update 中包含到目前为止的累积输出(而不仅仅是增量),允许客户端在每次更新时简单地替换其显示。
队列更新
每当待处理的引导或后续队列发生变化时发出。
{
"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,
"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
当在瞬时错误(过载、速率限制、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
当压缩或分支摘要总结在瞬时提供商错误后重试时发出。这些事件使用与自动助手轮次重试相同的重试设置。
{
"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"
}扩展错误
当扩展抛出错误时发出。
{
"type": "extension_error",
"extensionPath": "/path/to/extension.ts",
"event": "tool_call",
"error": "Error message..."
}扩展 UI 协议
扩展可以通过 ctx.ui.select(), ctx.ui.confirm()等请求用户交互。在 RPC 模式下,这些被转换为基于基本命令/事件流的请求/响应子协议。
扩展 UI 方法有两类:
- 对话框方法 (
select,confirm,input,editor):在 stdout 上发出一个extension_ui_request并阻塞,直到客户端在 stdin 上发回一个带有匹配extension_ui_response的id. - 即发即弃方法 (
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: "..." }
注意: ctx.mode 是 "rpc" 和 ctx.hasUI 是 true 在 RPC 模式下,因为对话框和即发即弃方法通过扩展 UI 子协议实现功能。使用 ctx.mode === "tui" 来保护需要真实终端的 TUI 特定功能,例如 custom() 需要真实终端。
扩展 UI 请求(stdout)
所有请求都有 type: "extension_ui_request"、一个唯一的 id和一个 method 字段。
选择
提示用户从列表中选择。带有 timeout 字段的对话框方法包含以毫秒为单位的超时时间;如果客户端未及时响应,智能体会自动以 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.
确认
提示用户进行是/否确认。
{
"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.
输入
提示用户输入自由格式文本。
{
"type": "extension_ui_request",
"id": "uuid-3",
"method": "input",
"title": "Enter a value",
"placeholder": "type something..."
}预期响应: extension_ui_response 带有 value (输入的文本)或 cancelled: true.
编辑器
打开一个多行文本编辑器,可选预填充内容。
{
"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.
通知
显示通知。即发即弃,无需响应。
{
"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 (或省略它)以清除该键的状态条目。
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
设置终端窗口/标签标题。即发即弃。
{
"type": "extension_ui_request",
"id": "uuid-8",
"method": "setTitle",
"title": "pi - my project"
}设置编辑器文本
设置输入编辑器中的文本。即发即弃。
{
"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/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
}
}用户消息
{
"role": "user",
"content": "Hello!",
"timestamp": 1733234567890,
"attachments": []
}content 字段可以是一个字符串或一个 TextContent/ImageContent 块的数组。
助手消息
{
"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"
工具结果消息
{
"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 工作。当存在时,它会贡献到会话令牌和成本总计中。
Bash执行消息
由 bash RPC 命令创建(不是由 LLM 工具调用):
{
"role": "bashExecution",
"command": "ls -la",
"output": "total 48\ndrwxr-xr-x ...",
"exitCode": 0,
"cancelled": false,
"truncated": false,
"fullOutputPath": null,
"timestamp": 1733234567890
}附件
{
"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");
});