import type { WireImage, ThinkingLevel, WireModel } from "../vendor/protocol/session/index.js"; export type { WireImage, ThinkingLevel, WireModel } from "../vendor/protocol/session/index.js"; export type PairErrorCode = "token_expired" | "token_consumed" | "token_unknown" | "internal_error"; export type StreamingBehavior = "steer"; export type QueuedMessageItem = { id: string; text: string; /** Omitted for text-only items so the existing no-image wire path is unchanged. */ images?: WireImage[]; sender_ref?: string; editable: boolean; created_at: number; }; export type ExtensionUiMethod = "select" | "confirm" | "input" | "editor" | "notify"; export type AskQuestionWireType = "single" | "multi" | "preview"; export interface AskOptionWire { value: string; label: string; description?: string; /** Preview-pane content (preview questions only). */ preview?: string; /** pi-ask addition: option allows freeform custom entry. */ freeform?: boolean; } export interface AskQuestionWire { id: string; label: string; prompt: string; type: AskQuestionWireType; required: boolean; /** pi-ask addition: type actually presented after live toggle / policy. */ presentedType?: AskQuestionWireType; /** pi-ask addition: type originally requested by the model. */ requestedType?: AskQuestionWireType; options: AskOptionWire[]; } /** Optional pi-ask enrichment on an extension_ui_request — lets the app render * the full flow (multi/preview/notes) instead of the degraded SDK select. A * flow maps to ONE request carrying every question; the app renders a * full-screen modal and submits ONE response with all answers (pi-ask resolves * a flow in a single submit). When `ask` is absent the SDK method/options * drive rendering (future generic prompts). */ export interface AskEnrichmentWire { flow_id: string; tool_call_id: string | null; /** pi-ask RemoteAskSource: "tool" | "answer" | "answer:again" | "ask:replay". */ source: string; title: string | null; questions: AskQuestionWire[]; } /** pi-ask RemoteAskAnswer — one question's answered parts. * * CASING EXCEPTION: inside the `ask` envelope the keys mirror pi-ask's own * schema VERBATIM (camelCase: `presentedType`, `requestedType`, `customText`, * `optionNotes`) so the bridge can forward the response to pi-ask's submit * event without a remap pass. The snake_case convention of this protocol * applies at the frame level (`flow_id`, `tool_call_id`, `notify_type`). */ export interface AskAnswerWire { values?: string[]; customText?: string; note?: string; optionNotes?: Record; } /** Optional pi-ask enrichment on an extension_ui_response — carries the * structured answer so multi/preview/notes survive the round-trip. */ export type AskResponseEnrichmentWire = { flow_id: string; kind: "answer"; mode?: "submit" | "elaborate"; answers: Record; } | { flow_id: string; kind: "cancel"; }; /** ServerMessage: interactive extension prompt. Mirrors RpcExtensionUIRequest * (select/confirm/input/editor/notify). The `ask` envelope is present when the * prompt originates from a pi-ask flow, carrying the full question schema. */ export type ExtensionUiRequestWire = { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[]; ask?: AskEnrichmentWire; } | { type: "extension_ui_request"; id: string; method: "confirm"; title: string; message: string; ask?: AskEnrichmentWire; } | { type: "extension_ui_request"; id: string; method: "input"; title: string; placeholder?: string; ask?: AskEnrichmentWire; } | { type: "extension_ui_request"; id: string; method: "editor"; title: string; prefill?: string; ask?: AskEnrichmentWire; } | { type: "extension_ui_request"; id: string; method: "notify"; message: string; notify_type?: "info" | "warning" | "error"; }; /** ClientMessage: response to an extension_ui_request. Mirrors * RpcExtensionUIResponse (value / confirmed / cancelled). The `ask` envelope * carries pi-ask's structured answer when the app rendered the rich flow. */ export type ExtensionUiResponseWire = { type: "extension_ui_response"; id: string; value: string; ask?: AskResponseEnrichmentWire; } | { type: "extension_ui_response"; id: string; confirmed: boolean; ask?: AskResponseEnrichmentWire; } | { type: "extension_ui_response"; id: string; cancelled: true; ask?: AskResponseEnrichmentWire; } | { type: "extension_ui_response"; id: string; ask: AskResponseEnrichmentWire; }; export type ClientMessage = { type: "pair_request"; id: string; code: string; device_name: string; } | { type: "user_message"; id: string; text: string; images?: WireImage[]; streaming_behavior?: StreamingBehavior; } | { type: "queued_message_set"; id: string; text: string; images?: WireImage[]; } | { type: "queued_message_clear"; id: string; target_id?: string; } | { type: "approve_tool"; id: string; tool_call_id: string; decision: "allow" | "deny"; } | { type: "cancel"; id: string; target_id: string; } | { type: "ping"; id: string; } | { type: "session_sync"; id: string; limit?: number; } | { type: "session_new"; id: string; } | { type: "session_compact"; id: string; } | { type: "model_set"; id: string; provider: string; model_id: string; } | { type: "thinking_set"; id: string; level: ThinkingLevel; } | { type: "list_models"; id: string; } | ExtensionUiResponseWire; export type Usage = { input_tokens: number; output_tokens: number; }; export type KnownErrorCode = "tool_approval_required" | "invalid_message" | "unsupported_type" | "too_large" | "rate_limited" | "timeout" | "internal_error"; export type ErrorCode = KnownErrorCode | (string & {}); export type SessionHistoryEvent = { ts: number; type: "user_input"; id: string; text: string; images?: WireImage[]; } | { ts: number; type: "tool_request"; tool_call_id: string; tool: string; args: Record; } | { ts: number; type: "tool_result"; tool_call_id: string; result?: unknown; error?: string; } | { ts: number; type: "agent_message"; in_reply_to: string; text: string; usage?: Usage; } | { ts: number; type: "compaction"; summary: string; tokens_before: number; }; export type ServerMessage = { type: "pair_ok"; in_reply_to: string; session_name: string; session_started_at: number; endpoint_id: string; /** * Plan/27 Wave A: identifies the host coding agent driving this * pi-extension instance. `name` is hardcoded to "Pi coding agent" * today; future Pi forks (Claude Code, OpenCode) populate their own * here. `version` is the pi-extension `package.json` version. * Optional in the wire schema so app-side parsing tolerates older * Pi builds that predate this field — every new pairing emits both. */ harness?: { name: string; version: string; }; /** * Plan/27 Wave A: `os.hostname()` of the machine the Pi runs on. * App displays it in the device list so the user can distinguish * two paired PCs that happen to share a nickname or sit in the * same project folder. */ hostname?: string; } | { type: "pair_error"; in_reply_to: string; code: PairErrorCode; message: string; } | { type: "user_input"; id: string; text: string; streaming_behavior?: StreamingBehavior; } | { type: "user_message"; id: string; text: string; images?: WireImage[]; streaming_behavior?: StreamingBehavior; } | { type: "queued_message_state"; id?: string; text?: string; items?: QueuedMessageItem[]; } | { type: "steer_consumed"; id: string; } | { type: "agent_chunk"; in_reply_to: string; delta: string; } | { type: "agent_done"; in_reply_to: string; usage?: Usage; } | { type: "agent_message"; in_reply_to: string; text: string; usage?: Usage; } | { type: "compaction"; summary: string; tokens_before: number; ts?: number; } | { type: "tool_request"; tool_call_id: string; tool: string; args: Record; } | { type: "tool_result"; tool_call_id: string; result?: unknown; error?: string; } | { type: "error"; in_reply_to?: string; code: ErrorCode; message: string; } | { type: "cancelled"; in_reply_to: string; target_id: string; } | { type: "pong"; in_reply_to: string; } | { type: "bye"; reason: ByeReason; } | { type: "session_history"; in_reply_to: string; session_started_at: number; events: SessionHistoryEvent[]; eos: boolean; truncated: boolean; } | { type: "action_ok"; in_reply_to: string; action: ActionName; } | { type: "action_error"; in_reply_to: string; action: ActionName; error: string; } | { type: "models_list"; in_reply_to: string; models: WireModel[]; current?: WireModel; } | ExtensionUiRequestWire; /** * Plan/28 — Stable names for the typed actions the app can request. Kept * as a closed string union so a switch in either side gets exhaustiveness * checking from the compiler. */ export type ActionName = "session_new" | "session_compact" | "model_set" | "thinking_set"; export type ByeReason = "peer_stop" | "session_replaced" | "shutdown";