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 모드 시작 중
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>: 사용자 정의 세션 저장 디렉터리
프로토콜 개요
- 명령: JSON 객체가 stdin로 전송됨(한 줄에 하나씩)
- 응답: JSON 개체(명령 성공/실패를 나타내는
type: "response"포함) - 이벤트: 에이전트 이벤트가 JSON 라인으로 stdout로 스트리밍됩니다.
모든 명령은 요청/응답 상관 관계에 대한 선택적 id 필드를 지원합니다. 제공된 경우 해당 응답에는 동일한 id이 포함됩니다. bash_execution_update 이벤트에는 원래 bash 명령의 id도 포함됩니다.
프레이밍
RPC 모드는 LF(\n)를 유일한 레코드 구분 기호로 사용하는 엄격한 JSONL 의미 체계를 사용합니다.
이는 고객에게 중요합니다.
\n에서만 기록 분할- 후행
\r을 제거하여 선택적\r\n입력을 허용합니다. - 유니코드 구분 기호를 줄 바꿈으로 처리하는 일반 줄 판독기를 사용하지 마세요.
특히 노드 readline는 JSON 문자열 내에서 유효한 U+2028 및 U+2029에서도 분할되기 때문에 RPC 모드에 대한 프로토콜을 준수하지 않습니다.
명령
격려
즉각적인
사용자 프롬프트를 에이전트에게 보냅니다. 프롬프트가 수락되거나 대기열에 추가되거나 처리된 후에 명령 응답이 내보내집니다. 이벤트는 승인 후에도 비동기식으로 계속 스트리밍됩니다.
{"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) 및 prompt templates(/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 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를 참조하세요.
후속 조치
에이전트가 완료된 후 처리할 후속 메시지를 대기열에 추가합니다. 상담원에게 더 이상 도구 호출이나 조정 메시지가 없는 경우에만 전달됩니다. 스킬 명령어와 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를 참조하세요.
중단하다
현재 에이전트 작업을 중단합니다.
{"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 참조).
모델
세트_모델
특정 모델로 전환하십시오.
{"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 개체입니다.
get_available_models
구성된 모델을 모두 나열합니다.
{"type": "get_available_models"}응답에는 전체 Model 객체 배열이 포함됩니다.
{
"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", "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"}
}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"]
}
}대기열 모드
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
후속 메시지(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 호출을 보고하며 사용자 정의 압축 처리기에 의해 생략될 수 있습니다.
set_auto_comaction
컨텍스트가 거의 가득 찼을 때 자동 압축을 활성화하거나 비활성화합니다.
{"type": "set_auto_compaction", "enabled": true}응답:
{"type": "response", "command": "set_auto_compaction", "success": true}다시 해 보다
set_auto_retry
일시적인 오류(오버로드, 속도 제한, 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_execution_update 이벤트로 스트리밍됩니다. 응답에는 최종 결과가 포함됩니다.
{"id": "req-1", "type": "bash", "command": "ls -la"}스트리밍된 bash_execution_update 이벤트를 이 명령과 연결하려면 id를 포함하세요.
응답:
{
"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`
```
총 48개
drwxr-xr-x...
```이는 다음을 의미합니다.
- Bash 출력은 즉시 포함되지 않고 다음 프롬프트의 LLM 컨텍스트에 포함됩니다.
- 프롬프트가 표시되기 전에 여러 bash 명령을 실행할 수 있습니다. 모든 출력이 포함됩니다
중단_bash
실행 중인 bash 명령을 중단합니다.
{"type": "abort_bash"}응답:
{"type": "response", "command": "abort_bash", "success": true}세션
get_session_stats
토큰 사용량, 비용 통계 및 현재 컨텍스트 창 사용량을 확인하세요.
{"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}
}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
모든 세션 항목을 추가 순서로 가져옵니다(세션 헤더 제외). 세션은 안정적인 ID가 있는 항목의 추가 전용 트리이므로 항목 ID는 내구성 있는 커서로 작동합니다. 클라이언트를 다시 시작해도 해당 항목 이후의 항목만 가져오려면 since로 표시된 마지막 항목 ID를 전달합니다. 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
세션을 항목 트리로 가져옵니다. 각 노드는 {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
마지막 보조 메시지의 텍스트 콘텐츠를 가져옵니다.
{"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
}현재 세션 이름은 sessionName 필드의 get_state를 통해 확인할 수 있습니다. RPC 모드 시작 시 초기 이름을 설정하려면 --name <name> 또는 -n <name>를 pi --mode rpc 프로세스에 전달합니다.
명령
get_commands
사용 가능한 명령(확장 명령, prompt templates 및 스킬)을 가져옵니다. / 접두어를 붙여 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에는 제공된 bash 명령의 id가 포함됩니다.
이벤트 유형
| 이벤트 | 설명 |
|---|---|
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"}턴_시작 / 턴_엔드
차례는 하나의 보조자 응답과 그에 따른 도구 호출 및 결과로 구성됩니다.
{"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_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
도구가 시작되고, 진행 상황을 스트리밍하고, 실행이 완료될 때 발생합니다.
{
"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에는 (델타뿐만 아니라) 지금까지 누적된 출력이 포함되어 클라이언트가 업데이트할 때마다 디스플레이를 간단히 교체할 수 있습니다.
대기열_업데이트
보류 중인 조정 또는 후속 조치 대기열이 변경될 때마다 발생합니다.
{
"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,
"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 프로토콜
Extensions는 ctx.ui.select(), ctx.ui.confirm() 등을 통해 사용자 상호 작용을 요청할 수 있습니다. RPC 모드에서는 이러한 상호 작용이 기본 명령/이벤트 흐름 위에 있는 요청/응답 하위 프로토콜로 변환됩니다.
확장 UI 메서드에는 두 가지 범주가 있습니다.
- 대화 상자 방법(
select,confirm,input,editor): stdout에서extension_ui_request를 내보내고 클라이언트가 일치하는id와 함께 stdin에서extension_ui_response를 다시 보낼 때까지 차단합니다. - Fire-and-forget 방법(
notify,setStatus,setWidget,setTitle,set_editor_text): stdout에서extension_ui_request를 내보내지만 응답을 기대하지 않습니다. 클라이언트는 정보를 표시하거나 무시할 수 있습니다.
대화 메서드에 timeout 필드가 포함된 경우 에이전트 측은 제한 시간이 만료되면 기본값을 사용하여 자동 해결됩니다. 클라이언트는 시간 초과를 추적할 필요가 없습니다.
일부 ExtensionUIContext 메소드는 직접 TUI 액세스가 필요하기 때문에 RPC 모드에서 지원되지 않거나 성능이 저하됩니다.
custom()반환undefinedsetWorkingMessage(),setWorkingIndicator(),setFooter(),setHeader(),setEditorComponent(),setToolsExpanded()는 작동하지 않습니다.getEditorText()반환""getToolsExpanded()반환falsepasteToEditor()setEditorText()에 위임(붙여넣기/접기 처리 없음)getAllThemes()반환[]getTheme()반환undefinedsetTheme()반환{ success: false, error: "..." }
참고: ctx.mode는 "rpc"이고 ctx.hasUI는 RPC 모드에서 true입니다. 왜냐하면 대화 상자 및 실행 후 잊어버리기 메서드가 확장 UI 하위 프로토콜을 통해 작동하기 때문입니다. 실제 터미널이 필요한 custom()와 같은 TUI 특정 기능을 보호하려면 ctx.mode === "tui"를 사용하세요.
확장 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 모드에서는 문자열 배열만 지원됩니다. 구성요소 팩토리는 무시됩니다.
제목 설정
터미널 창/탭 제목을 설정합니다. 실행 후 잊어버리세요.
{
"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는 요청과 일치해야 합니다.
값 응답(선택, 입력, 편집기)
{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}확인응답(확인)
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}취소 응답(모든 대화상자)
대화 상자 메서드를 닫습니다. 확장 프로그램은 undefined(선택/입력/편집용) 또는 false(확인용)을 수신합니다.
{"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/extensions/rpc-demo.ts 확장과 쌍을 이루는 examples/rpc-extension-ui.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");
});