跳转到内容

RPC 模式

RPC 模式通过 stdin/stdout 上的 JSON 协议实现编码 agent 的无头运行。适用于将 agent 嵌入其他应用、IDE 或自定义 UI。

Node.js/TypeScript 用户注意:如果你在构建 Node.js 应用,建议直接使用 @earendil-works/pi-coding-agent 中的 AgentSession,而不是 spawn 子进程。API 见 src/core/agent-session.ts。基于子进程的 TypeScript 客户端见 src/modes/rpc/rpc-client.ts

Terminal window
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>:自定义会话存储目录
  • 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+2028U+2029 处分割,而这些字符在 JSON 字符串中是合法的。

向 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 轮次完成工具调用执行后、下一次 LLM 调用前投递。
  • "followUp":等待 agent 结束。仅在 agent 停止时投递消息。

若 agent 正在流式输出且未指定 streamingBehavior,命令将返回错误。

扩展命令:若消息是扩展命令(如 /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"}

在 agent 运行时将 steering 消息入队。在当前 assistant 轮次完成工具调用执行后、下一次 LLM 调用前投递。Skill 命令和提示模板会被展开。不允许扩展命令(请改用 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}

控制 steering 消息处理方式见 set_steering_mode

将 follow-up 消息入队,在 agent 结束后处理。仅在 agent 没有更多工具调用或 steering 消息时投递。Skill 命令和提示模板会被展开。不允许扩展命令(请改用 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}

控制 follow-up 消息处理方式见 set_follow_up_mode

中止当前 agent 操作。

{"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 字段是完整的 Model 对象或 nullsessionName 字段是通过 set_session_name 设置的显示名称,未设置时省略。

获取对话中的所有消息。

{"type": "get_messages"}

响应:

{
"type": "response",
"command": "get_messages",
"success": true,
"data": {"messages": [...]}
}

消息为 AgentMessage 对象(见 Message Types)。

切换到指定模型。

{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}

响应包含完整的 Model 对象:

{
"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 字段是完整的 Model 对象。

列出所有已配置的模型。

{"type": "get_available_models"}

响应包含完整 Model 对象数组:

{
"type": "response",
"command": "get_available_models",
"success": true,
"data": {
"models": [...]
}
}

为支持该功能的模型设置推理/思考级别。

{"type": "set_thinking_level", "level": "high"}

级别:"off""minimal""low""medium""high""xhigh"

注意:"xhigh" 仅 OpenAI codex-max 模型支持。

响应:

{"type": "response", "command": "set_thinking_level", "success": true}

循环切换可用思考级别。若模型不支持思考,返回 null 数据。

{"type": "cycle_thinking_level"}

响应:

{
"type": "response",
"command": "cycle_thinking_level",
"success": true,
"data": {"level": "high"}
}

控制 steering 消息(来自 steer)的投递方式。

{"type": "set_steering_mode", "mode": "one-at-a-time"}

模式:

  • "all":当前 assistant 轮次完成工具调用执行后投递所有 steering 消息
  • "one-at-a-time":每完成一个 assistant 轮次投递一条 steering 消息(默认)

响应:

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

控制 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}

手动压缩对话上下文以减少 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 是压缩后立即重建的消息上下文上的启发式估计,不是提供商精确的 token 计数。

在上下文接近满时启用或禁用自动压缩。

{"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}

执行 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 ...
```

这意味着:

  1. Bash 输出在下一次 prompt 时加入 LLM 上下文,而非立即
  2. 可在 prompt 前执行多个 bash 命令;所有输出都会被包含
  3. BashExecutionMessage 本身不会发出事件

中止正在运行的 bash 命令。

{"type": "abort_bash"}

响应:

{"type": "response", "command": "abort_bash", "success": true}

获取 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 包含用于压缩和页脚显示的实际当前上下文窗口估计。

无模型或上下文窗口时省略 contextUsage。压缩后 contextUsage.tokenscontextUsage.percentnull,直到压缩后的新 assistant 响应提供有效用量数据。

将会话导出为 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}}

从活动分支上的先前用户消息创建新 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}
}

在当前位置将当前活动分支复制到新会话。可被 session_before_fork 扩展事件处理器取消。

{"type": "clone"}

响应:

{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": false}
}

若扩展取消 clone:

{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": true}
}

获取可用于 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..."}
]
}
}

获取最后一条 assistant 消息的文本内容。

{"type": "get_last_assistant_text"}

响应:

{
"type": "response",
"command": "get_last_assistant_text",
"success": true,
"data": {"text": "The assistant's response..."}
}

无 assistant 消息时返回 {"text": null}

设置当前会话的显示名称。名称出现在会话列表中,便于识别会话。

{"type": "set_session_name", "name": "my-feature-work"}

响应:

{
"type": "response",
"command": "set_session_name",
"success": true
}

当前会话名称可通过 get_statesessionName 字段获取。启动 RPC 模式时设置初始名称,向 pi --mode rpc 进程传入 --name <name>-n <name>

获取可用命令(扩展命令、提示模板和 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 目录加载(名称前缀为 skill:
  • location:加载来源(可选,扩展命令无此字段):
    • "user":用户级(~/.pi/agent/
    • "project":项目级(./.pi/agent/
    • "path":通过 CLI 或设置指定的路径
  • path:命令源的绝对文件路径(可选)

注意:内置 TUI 命令(/settings/hotkeys 等)不包含在内。它们仅在交互模式下处理,通过 prompt 发送不会执行。

agent 运行期间,事件以 JSON 行形式流式输出到 stdout。事件不包含 id 字段(仅响应包含)。

EventDescription
agent_startAgent 开始处理
agent_endAgent 完成(包含所有生成的消息)
turn_start新轮次开始
turn_end轮次完成(包含 assistant 消息和工具结果)
message_start消息开始
message_update流式更新(text/thinking/toolcall 增量)
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 开始处理提示时发出。

{"type": "agent_start"}

Agent 完成时发出。包含此次运行生成的所有消息。

{
"type": "agent_end",
"messages": [...]
}

一个轮次包含一次 assistant 响应及由此产生的工具调用和结果。

{"type": "turn_start"}
{
"type": "turn_end",
"message": {...},
"toolResults": [...]
}

消息开始和完成时发出。message 字段包含 AgentMessage

{"type": "message_start", "message": {...}}
{"type": "message_end", "message": {...}}

assistant 消息流式输出期间发出。包含部分消息和流式增量事件。

{
"type": "message_update",
"message": {...},
"assistantMessageEvent": {
"type": "text_delta",
"contentIndex": 0,
"delta": "Hello ",
"partial": {...}
}
}

assistantMessageEvent 字段包含以下增量类型之一:

TypeDescription
start消息生成开始
text_start文本内容块开始
text_delta文本内容块
text_end文本内容块结束
thinking_start思考块开始
thinking_delta思考内容块
thinking_end思考块结束
toolcall_start工具调用开始
toolcall_delta工具调用参数块
toolcall_end工具调用结束(包含完整 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

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 包含迄今累积的输出(不仅是增量),客户端可在每次更新时直接替换显示。

待处理 steering 或 follow-up 队列变化时发出。

{
"type": "queue_update",
"steering": ["Focus on error handling"],
"followUp": ["After that, summarize the result"]
}

压缩运行(手动或自动)时发出。

{"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" 且压缩成功,willRetrytrue,agent 将自动重试提示。

若压缩被中止,resultnullabortedtrue

若压缩失败(如 API 配额超限),resultnullabortedfalseerrorMessage 包含错误描述。

瞬态错误(过载、速率限制、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"
}

扩展抛出错误时发出。

{
"type": "extension_error",
"extensionPath": "/path/to/extension.ts",
"event": "tool_call",
"error": "Error message..."
}

扩展可通过 ctx.ui.select()ctx.ui.confirm() 等请求用户交互。在 RPC 模式下,这些会转换为基础命令/事件流之上的请求/响应子协议。

扩展 UI 方法有两类:

  • Dialog 方法selectconfirminputeditor):在 stdout 发出 extension_ui_request,阻塞直到客户端在 stdin 发送匹配 idextension_ui_response
  • Fire-and-forget 方法notifysetStatussetWidgetsetTitleset_editor_text):在 stdout 发出 extension_ui_request 但不期望响应。客户端可显示信息或忽略。

若 dialog 方法包含 timeout 字段,超时后 agent 端会用默认值自动解析。客户端无需跟踪超时。

部分 ExtensionUIContext 方法在 RPC 模式下不支持或降级,因为它们需要直接 TUI 访问:

  • custom() 返回 undefined
  • setWorkingMessage()setWorkingIndicator()setFooter()setHeader()setEditorComponent()setToolsExpanded() 为空操作
  • getEditorText() 返回 ""
  • getToolsExpanded() 返回 false
  • pasteToEditor() 委托给 setEditorText()(无粘贴/折叠处理)
  • getAllThemes() 返回 []
  • getTheme() 返回 undefined
  • setTheme() 返回 { success: false, error: "..." }

注意:RPC 模式下 ctx.mode"rpc"ctx.hasUItrue,因为 dialog 和 fire-and-forget 方法通过扩展 UI 子协议可用。使用 ctx.mode === "tui" 保护需要真实终端的 TUI 特定功能(如 custom())。

所有请求具有 type: "extension_ui_request"、唯一 idmethod 字段。

提示用户从列表中选择。带 timeout 字段的 dialog 方法包含毫秒超时;客户端未及时响应时 agent 自动以 undefined 解析。

{
"type": "extension_ui_request",
"id": "uuid-1",
"method": "select",
"title": "Allow dangerous command?",
"options": ["Allow", "Block"],
"timeout": 10000
}

期望响应:带 value(所选选项字符串)或 cancelled: trueextension_ui_response

提示用户进行是/否确认。

{
"type": "extension_ui_request",
"id": "uuid-2",
"method": "confirm",
"title": "Clear session?",
"message": "All messages will be lost.",
"timeout": 5000
}

期望响应:带 confirmed: true/falsecancelled: trueextension_ui_response

提示用户输入自由文本。

{
"type": "extension_ui_request",
"id": "uuid-3",
"method": "input",
"title": "Enter a value",
"placeholder": "type something..."
}

期望响应:带 value(输入文本)或 cancelled: trueextension_ui_response

打开多行文本编辑器,可选预填内容。

{
"type": "extension_ui_request",
"id": "uuid-4",
"method": "editor",
"title": "Edit some text",
"prefill": "Line 1\nLine 2\nLine 3"
}

期望响应:带 value(编辑后文本)或 cancelled: trueextension_ui_response

显示通知。Fire-and-forget,不期望响应。

{
"type": "extension_ui_request",
"id": "uuid-5",
"method": "notify",
"message": "Command blocked by user",
"notifyType": "warning"
}

notifyType 字段为 "info""warning""error"。省略时默认为 "info"

设置或清除页脚/状态栏中的状态项。Fire-and-forget。

{
"type": "extension_ui_request",
"id": "uuid-6",
"method": "setStatus",
"statusKey": "my-ext",
"statusText": "Turn 3 running..."
}

发送 statusText: undefined(或省略)以清除该键的状态项。

设置或清除编辑器上方或下方显示的小部件(文本行块)。Fire-and-forget。

{
"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 模式仅支持字符串数组;组件工厂被忽略。

设置终端窗口/标签页标题。Fire-and-forget。

{
"type": "extension_ui_request",
"id": "uuid-8",
"method": "setTitle",
"title": "pi - my project"
}

设置输入编辑器中的文本。Fire-and-forget。

{
"type": "extension_ui_request",
"id": "uuid-9",
"method": "set_editor_text",
"text": "prefilled text for the user"
}

仅 dialog 方法(selectconfirminputeditor)发送响应。id 必须与请求匹配。

{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}

关闭任意 dialog 方法。扩展收到 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..."
}

源文件:

{
"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 ..."}],
"isError": false,
"timestamp": 1733234567890
}

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
}
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

完整交互示例见 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");
});