feat: Added RPC protocol v2 types, init handshake with version detectio…
- "packages/pi-coding-agent/src/modes/rpc/rpc-types.ts" - "packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts" - "packages/pi-coding-agent/src/modes/rpc/rpc-client.ts" - "packages/pi-coding-agent/src/modes/index.ts" - "packages/pi-coding-agent/src/index.ts" GSD-Task: S01/T01
This commit is contained in:
parent
0db5edd7fe
commit
01e37670e1
5 changed files with 130 additions and 7 deletions
|
|
@ -314,8 +314,11 @@ export {
|
|||
type RpcClientOptions,
|
||||
type RpcEventListener,
|
||||
type RpcCommand,
|
||||
type RpcInitResult,
|
||||
type RpcProtocolVersion,
|
||||
type RpcResponse,
|
||||
type RpcSessionState,
|
||||
type RpcV2Event,
|
||||
} from "./modes/index.js";
|
||||
// RPC JSONL utilities
|
||||
export { attachJsonlLineReader, serializeJsonLine } from "./modes/rpc/jsonl.js";
|
||||
|
|
|
|||
|
|
@ -6,4 +6,11 @@ export { InteractiveMode, type InteractiveModeOptions } from "./interactive/inte
|
|||
export { type PrintModeOptions, runPrintMode } from "./print-mode.js";
|
||||
export { type ModelInfo, RpcClient, type RpcClientOptions, type RpcEventListener } from "./rpc/rpc-client.js";
|
||||
export { runRpcMode } from "./rpc/rpc-mode.js";
|
||||
export type { RpcCommand, RpcResponse, RpcSessionState } from "./rpc/rpc-types.js";
|
||||
export type {
|
||||
RpcCommand,
|
||||
RpcInitResult,
|
||||
RpcProtocolVersion,
|
||||
RpcResponse,
|
||||
RpcSessionState,
|
||||
RpcV2Event,
|
||||
} from "./rpc/rpc-types.js";
|
||||
|
|
|
|||
|
|
@ -398,6 +398,21 @@ export class RpcClient {
|
|||
return this.getData<{ commands: RpcSlashCommand[] }>(response).commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a UI response to a pending extension_ui_request.
|
||||
* Fire-and-forget — no request/response correlation.
|
||||
*/
|
||||
sendUIResponse(id: string, response: { value?: string; values?: string[]; confirmed?: boolean; cancelled?: boolean }): void {
|
||||
if (!this.process?.stdin) {
|
||||
throw new Error("Client not started");
|
||||
}
|
||||
this.process.stdin.write(serializeJsonLine({
|
||||
type: "extension_ui_response",
|
||||
id,
|
||||
...response,
|
||||
}));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helpers
|
||||
// =========================================================================
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import type {
|
|||
RpcCommand,
|
||||
RpcExtensionUIRequest,
|
||||
RpcExtensionUIResponse,
|
||||
RpcInitResult,
|
||||
RpcResponse,
|
||||
RpcSessionState,
|
||||
RpcSlashCommand,
|
||||
|
|
@ -37,8 +38,11 @@ export type {
|
|||
RpcCommand,
|
||||
RpcExtensionUIRequest,
|
||||
RpcExtensionUIResponse,
|
||||
RpcInitResult,
|
||||
RpcProtocolVersion,
|
||||
RpcResponse,
|
||||
RpcSessionState,
|
||||
RpcV2Event,
|
||||
} from "./rpc-types.js";
|
||||
|
||||
/**
|
||||
|
|
@ -74,6 +78,10 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
// Shutdown request flag
|
||||
let shutdownRequested = false;
|
||||
|
||||
// v2 protocol version detection state
|
||||
let protocolVersion: 1 | 2 = 1;
|
||||
let protocolLocked = false;
|
||||
|
||||
const embeddedTerminalEnabled = process.env.GSD_WEB_BRIDGE_TUI === "1";
|
||||
const remoteTerminal = embeddedTerminalEnabled
|
||||
? new RemoteTerminal({
|
||||
|
|
@ -709,6 +717,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
return success(id, "terminal_redraw");
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// v2 Protocol: shutdown
|
||||
// =================================================================
|
||||
|
||||
case "shutdown": {
|
||||
shutdownRequested = true;
|
||||
return success(id, "shutdown");
|
||||
}
|
||||
|
||||
default: {
|
||||
const unknownCommand = command as { type: string; id?: string };
|
||||
return error(unknownCommand.id, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
|
||||
|
|
@ -741,7 +758,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
try {
|
||||
const parsed = JSON.parse(line);
|
||||
|
||||
// Handle extension UI responses
|
||||
// Handle extension UI responses (bypass protocol detection)
|
||||
if (parsed.type === "extension_ui_response") {
|
||||
const response = parsed as RpcExtensionUIResponse;
|
||||
const pending = pendingExtensionRequests.get(response.id);
|
||||
|
|
@ -752,8 +769,33 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle regular commands
|
||||
const command = parsed as RpcCommand;
|
||||
|
||||
// Protocol version detection: first non-UI-response command locks the version
|
||||
if (!protocolLocked) {
|
||||
protocolLocked = true;
|
||||
if (command.type === "init") {
|
||||
protocolVersion = 2;
|
||||
const initResult: RpcInitResult = {
|
||||
protocolVersion: 2,
|
||||
sessionId: session.sessionId,
|
||||
capabilities: {
|
||||
events: ["execution_complete", "cost_update"],
|
||||
commands: ["init", "shutdown", "subscribe"],
|
||||
},
|
||||
};
|
||||
output(success(command.id, "init", initResult));
|
||||
return;
|
||||
}
|
||||
// Non-init first message: lock to v1, fall through to normal handling
|
||||
protocolVersion = 1;
|
||||
} else if (command.type === "init") {
|
||||
// Already locked — reject re-init
|
||||
output(error(command.id, "init", "Protocol version already locked. init must be the first command."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle regular commands
|
||||
const response = await handleCommand(command);
|
||||
output(response);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ import type { SessionStats } from "../../core/agent-session.js";
|
|||
import type { BashResult } from "../../core/bash-executor.js";
|
||||
import type { CompactionResult } from "../../core/compaction/index.js";
|
||||
|
||||
// ============================================================================
|
||||
// RPC Protocol Versioning
|
||||
// ============================================================================
|
||||
|
||||
/** Supported protocol versions. v1 is the implicit default; v2 requires an init handshake. */
|
||||
export type RpcProtocolVersion = 1 | 2;
|
||||
|
||||
// ============================================================================
|
||||
// RPC Commands (stdin)
|
||||
// ============================================================================
|
||||
|
|
@ -69,7 +76,12 @@ export type RpcCommand =
|
|||
// Bridge-hosted native terminal
|
||||
| { id?: string; type: "terminal_input"; data: string }
|
||||
| { id?: string; type: "terminal_resize"; cols: number; rows: number }
|
||||
| { id?: string; type: "terminal_redraw" };
|
||||
| { id?: string; type: "terminal_redraw" }
|
||||
|
||||
// v2 Protocol
|
||||
| { id?: string; type: "init"; protocolVersion: 2; clientId?: string }
|
||||
| { id?: string; type: "shutdown"; graceful?: boolean }
|
||||
| { id?: string; type: "subscribe"; events: string[] };
|
||||
|
||||
// ============================================================================
|
||||
// RPC Slash Command (for get_commands response)
|
||||
|
|
@ -120,9 +132,9 @@ export interface RpcSessionState {
|
|||
// Success responses with data
|
||||
export type RpcResponse =
|
||||
// Prompting (async - events follow)
|
||||
| { id?: string; type: "response"; command: "prompt"; success: true }
|
||||
| { id?: string; type: "response"; command: "steer"; success: true }
|
||||
| { id?: string; type: "response"; command: "follow_up"; success: true }
|
||||
| { id?: string; type: "response"; command: "prompt"; success: true; runId?: string }
|
||||
| { id?: string; type: "response"; command: "steer"; success: true; runId?: string }
|
||||
| { id?: string; type: "response"; command: "follow_up"; success: true; runId?: string }
|
||||
| { id?: string; type: "response"; command: "abort"; success: true }
|
||||
| { id?: string; type: "response"; command: "new_session"; success: true; data: { cancelled: boolean } }
|
||||
|
||||
|
|
@ -216,9 +228,53 @@ export type RpcResponse =
|
|||
| { id?: string; type: "response"; command: "terminal_resize"; success: true }
|
||||
| { id?: string; type: "response"; command: "terminal_redraw"; success: true }
|
||||
|
||||
// v2 Protocol
|
||||
| { id?: string; type: "response"; command: "init"; success: true; data: RpcInitResult }
|
||||
| { id?: string; type: "response"; command: "shutdown"; success: true }
|
||||
|
||||
// Error response (any command can fail)
|
||||
| { id?: string; type: "response"; command: string; success: false; error: string };
|
||||
|
||||
// ============================================================================
|
||||
// v2 Protocol Types
|
||||
// ============================================================================
|
||||
|
||||
/** Result of the init handshake (v2 only) */
|
||||
export interface RpcInitResult {
|
||||
protocolVersion: 2;
|
||||
sessionId: string;
|
||||
capabilities: {
|
||||
events: string[];
|
||||
commands: string[];
|
||||
};
|
||||
}
|
||||
|
||||
/** v2 execution_complete event — emitted when a prompt/steer/follow_up finishes */
|
||||
export interface RpcExecutionCompleteEvent {
|
||||
type: "execution_complete";
|
||||
runId: string;
|
||||
status: "completed" | "error" | "cancelled";
|
||||
reason?: string;
|
||||
stats: SessionStats;
|
||||
}
|
||||
|
||||
/** v2 cost_update event — emitted per-turn with running cost data */
|
||||
export interface RpcCostUpdateEvent {
|
||||
type: "cost_update";
|
||||
runId: string;
|
||||
turnCost: number;
|
||||
cumulativeCost: number;
|
||||
tokens: {
|
||||
input: number;
|
||||
output: number;
|
||||
cacheRead: number;
|
||||
cacheWrite: number;
|
||||
};
|
||||
}
|
||||
|
||||
/** Discriminated union of all v2-only event types */
|
||||
export type RpcV2Event = RpcExecutionCompleteEvent | RpcCostUpdateEvent;
|
||||
|
||||
// ============================================================================
|
||||
// Extension UI Events (stdout)
|
||||
// ============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue