# Wire mode

Wire mode is Pythinker Code's low-level communication protocol for structured bidirectional communication with external programs.

## What is Wire

Wire is the message-passing layer used internally by Pythinker Code. When you interact via terminal, the Shell UI receives AI output through Wire and displays it; when you integrate with IDEs via ACP, the ACP server also communicates with the agent core through Wire.

Wire mode (`--wire`) exposes this communication protocol, allowing external programs to interact directly with Pythinker Code. This is suitable for building custom UIs or embedding Pythinker Code into other applications.

```sh
pythinker --wire
```

## Use cases

Wire mode is mainly used for:

- **Custom UI**: Build web, desktop, or mobile frontends for Pythinker Code
- **Application integration**: Embed Pythinker Code into other applications
- **Automated testing**: Programmatic testing of agent behavior

::: tip
If you only need simple non-interactive input/output, [print mode](./print-mode.md) is simpler. Wire mode is for scenarios requiring full control and bidirectional communication.
:::

## Wire protocol

Wire uses a JSON-RPC 2.0 based protocol for bidirectional communication via stdin/stdout. The current protocol version is `1.9`. Each message is a single line of JSON conforming to the JSON-RPC 2.0 specification.

### Protocol type definitions

```typescript
/** JSON-RPC 2.0 request message base structure */
interface JSONRPCRequest<Method extends string, Params> {
  jsonrpc: "2.0"
  method: Method
  id: string
  params: Params
}

/** JSON-RPC 2.0 notification message (no id, no response needed) */
interface JSONRPCNotification<Method extends string, Params> {
  jsonrpc: "2.0"
  method: Method
  params: Params
}

/** JSON-RPC 2.0 success response */
interface JSONRPCSuccessResponse<Result> {
  jsonrpc: "2.0"
  id: string
  result: Result
}

/** JSON-RPC 2.0 error response */
interface JSONRPCErrorResponse {
  jsonrpc: "2.0"
  id: string
  error: JSONRPCError
}

interface JSONRPCError {
  code: number
  message: string
  data?: unknown
}
```

### `initialize`

::: info Added
Added in Wire 1.1. Legacy clients can skip this request and send `prompt` directly.
:::

- **Direction**: Client → Agent
- **Type**: Request (requires response)

Optional handshake request for negotiating protocol version, submitting external tool definitions, and retrieving the slash command list.

```typescript
/** initialize request parameters */
interface InitializeParams {
  /** Protocol version */
  protocol_version: string
  /** Client info, optional */
  client?: ClientInfo
  /** External tool definitions, optional */
  external_tools?: ExternalTool[]
  /** Client capabilities, optional */
  capabilities?: ClientCapabilities
  /** Hook subscriptions, optional. Declares hook events the client wants to handle */
  hooks?: WireHookSubscription[]
}

interface ClientCapabilities {
  /** Whether the client can handle QuestionRequest messages */
  supports_question?: boolean
  /** Whether the client supports plan mode */
  supports_plan_mode?: boolean
}

interface WireHookSubscription {
  /** Subscription ID, referenced in HookRequest */
  id: string
  /** Event type to subscribe to, e.g., 'PreToolUse', 'Stop' */
  event: string
  /** Regex filter, empty string matches all */
  matcher?: string
  /** Timeout for client response in seconds, default 30 */
  timeout?: number
}

interface ClientInfo {
  name: string
  version?: string
}

interface ExternalTool {
  /** Tool name, must not conflict with built-in tools */
  name: string
  /** Tool description */
  description: string
  /** Parameter definition in JSON Schema format */
  parameters: JSONSchema
}

/** initialize response result */
interface InitializeResult {
  /** Protocol version */
  protocol_version: string
  /** Server info */
  server: ServerInfo
  /** Available slash commands */
  slash_commands: SlashCommandInfo[]
  /** External tool registration result, only returned when request includes external_tools */
  external_tools?: ExternalToolsResult
  /** Server capabilities */
  capabilities?: ServerCapabilities
  /** Hook system info, optional */
  hooks?: HooksInfo
}

interface HooksInfo {
  /** List of all hook event types supported by the server */
  supported_events: string[]
  /** Currently configured hooks statistics, key is event type, value is count */
  configured: Record<string, number>
}

interface ServerCapabilities {
  /** Whether the server supports sending QuestionRequest messages */
  supports_question?: boolean
}

interface ServerInfo {
  name: string
  version: string
}

interface SlashCommandInfo {
  name: string
  description: string
  aliases: string[]
}

interface ExternalToolsResult {
  /** Successfully registered tool names */
  accepted: string[]
  /** Failed tool registrations with reasons */
  rejected: Array<{ name: string; reason: string }>
}
```

**Request example**

```json
{"jsonrpc": "2.0", "method": "initialize", "id": "550e8400-e29b-41d4-a716-446655440000", "params": {"protocol_version": "1.7", "client": {"name": "my-ui", "version": "1.0.0"}, "capabilities": {"supports_question": true}, "external_tools": [{"name": "open_in_ide", "description": "Open file in IDE", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}]}}
```

**Success response example**

```json
{"jsonrpc": "2.0", "id": "550e8400-e29b-41d4-a716-446655440000", "result": {"protocol_version": "1.7", "server": {"name": "Pythinker Code", "version": "1.14.0"}, "slash_commands": [{"name": "init", "description": "Analyze the codebase ...", "aliases": []}], "capabilities": {"supports_question": true}, "external_tools": {"accepted": ["open_in_ide"], "rejected": []}}}
```

If the server does not support the `initialize` method, the client will receive a `-32601 method not found` error and should automatically fall back to no-handshake mode.

### `prompt`

- **Direction**: Client → Agent
- **Type**: Request (requires response)

Send user input and run an agent turn. After calling, the agent starts processing and sends `event` notifications and `request` messages during execution, returning a response only when the turn completes.

```typescript
/** prompt request parameters */
interface PromptParams {
  /** User input, can be plain text or array of content parts */
  user_input: string | ContentPart[]
}

/** prompt response result */
interface PromptResult {
  /** Turn end status */
  status: "finished" | "cancelled" | "max_steps_reached"
  /** Number of steps executed when status is max_steps_reached */
  steps?: number
}
```

**Request example**

```json
{"jsonrpc": "2.0", "method": "prompt", "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "params": {"user_input": "Hello"}}
```

**Success response example**

```json
{"jsonrpc": "2.0", "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "result": {"status": "finished"}}
```

**Error response example**

```json
{"jsonrpc": "2.0", "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "error": {"code": -32001, "message": "LLM is not set"}}
```

| code | Description |
|------|-------------|
| `-32000` | A turn is already in progress |
| `-32001` | LLM not configured |
| `-32002` | Specified LLM not supported |
| `-32003` | LLM service error |
| `-32004` | Authentication expired (OAuth session received a 401); user should re-login |

### `replay`

::: info Added
Added in Wire 1.3.
:::

- **Direction**: Client → Agent
- **Type**: Request (requires response)

Trigger a history replay. The server reads `wire.jsonl` from the session directory and re-sends the recorded `event` and `request` messages in order. Replay is read-only; clients should not respond to replayed `request` messages. If there is no history, the server returns `events: 0` and `requests: 0`.

```typescript
/** replay request has no parameters, params can be empty object or omitted */
type ReplayParams = Record<string, never>

/** replay response result */
interface ReplayResult {
  /** Replay end status */
  status: "finished" | "cancelled"
  /** Number of replayed events */
  events: number
  /** Number of replayed requests */
  requests: number
}
```

**Request example**

```json
{"jsonrpc": "2.0", "method": "replay", "id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8"}
```

**Success response example**

```json
{"jsonrpc": "2.0", "id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8", "result": {"status": "finished", "events": 42, "requests": 3}}
```

### `steer`

::: info Added
Added in Wire 1.4.
:::

- **Direction**: Client → Agent
- **Type**: Request (requires response)

Inject a user message into an active agent turn. Unlike `prompt`, `steer` does not start a new turn but injects the message into the currently running turn. The injected message is appended to the context as a standard user message after the current step finishes, allowing you to "steer" the AI's behavior before the next step begins. A `SteerInput` event is emitted when the message is consumed.

```typescript
/** steer request parameters */
interface SteerParams {
  /** User input, can be plain text or array of content parts */
  user_input: string | ContentPart[]
}

/** steer response result */
interface SteerResult {
  /** Fixed as "steered" */
  status: "steered"
}
```

**Request example**

```json
{"jsonrpc": "2.0", "method": "steer", "id": "7ca7c810-9dad-11d1-80b4-00c04fd430c8", "params": {"user_input": "Use Python"}}
```

**Success response example**

```json
{"jsonrpc": "2.0", "id": "7ca7c810-9dad-11d1-80b4-00c04fd430c8", "result": {"status": "steered"}}
```

**Error response example**

If no turn is in progress:

```json
{"jsonrpc": "2.0", "id": "7ca7c810-9dad-11d1-80b4-00c04fd430c8", "error": {"code": -32000, "message": "No agent turn is in progress"}}
```

### `set_plan_mode`

::: info Added
Added in Wire 1.4.
:::

- **Direction**: Client → Agent
- **Type**: Request (requires response)

Set plan mode to a specific state. After calling, the agent updates plan mode and sends a `StatusUpdate` event with the new state.

This feature requires capability negotiation: the client must declare `capabilities.supports_plan_mode: true` during `initialize` for the agent to enable plan mode tools (`EnterPlanMode`, `ExitPlanMode`). If the client does not declare support, these tools are automatically hidden from the LLM's tool list.

Plan mode state is persisted to the session, so it survives process restarts and is restored when the session resumes.

```typescript
/** set_plan_mode request parameters */
interface SetPlanModeParams {
  /** Whether to enable plan mode */
  enabled: boolean
}

/** set_plan_mode response result */
interface SetPlanModeResult {
  /** Fixed as "ok" */
  status: "ok"
  /** Plan mode state after the call */
  plan_mode: boolean
}
```

**Request example**

```json
{"jsonrpc": "2.0", "method": "set_plan_mode", "id": "8da7d810-9dad-11d1-80b4-00c04fd430c8", "params": {"enabled": true}}
```

**Success response example**

```json
{"jsonrpc": "2.0", "id": "8da7d810-9dad-11d1-80b4-00c04fd430c8", "result": {"status": "ok", "plan_mode": true}}
```

**Error response example**

If plan mode is not supported in the current environment:

```json
{"jsonrpc": "2.0", "id": "8da7d810-9dad-11d1-80b4-00c04fd430c8", "error": {"code": -32000, "message": "Plan mode is not supported"}}
```

### `cancel`

- **Direction**: Client → Agent
- **Type**: Request (requires response)

Cancel the currently running agent turn or replay. After calling, the in-progress `prompt` request will return `{"status": "cancelled"}`, and replay will return `{"status": "cancelled"}` with the message counts sent so far.

```typescript
/** cancel request has no parameters, params can be empty object or omitted */
type CancelParams = Record<string, never>

/** cancel response result is empty object */
type CancelResult = Record<string, never>
```

**Request example**

```json
{"jsonrpc": "2.0", "method": "cancel", "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8"}
```

**Success response example**

```json
{"jsonrpc": "2.0", "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "result": {}}
```

**Error response example**

If no turn is in progress:

```json
{"jsonrpc": "2.0", "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "error": {"code": -32000, "message": "No agent turn is in progress"}}
```

### `event`

- **Direction**: Agent → Client
- **Type**: Notification (no response needed)

Events emitted by the agent during a turn. No `id` field, client doesn't need to respond.

```typescript
/** event notification parameters, contains serialized Wire message */
interface EventParams {
  type: string
  payload: object
}
```

**Example**

```json
{"jsonrpc": "2.0", "method": "event", "params": {"type": "ContentPart", "payload": {"type": "text", "text": "Hello"}}}
```

### `request`

- **Direction**: Agent → Client
- **Type**: Request (requires response)

Requests from the agent to the client, used for approval confirmation or external tool calls. The client must respond before the agent can continue execution.

```typescript
/** request parameters, contains serialized Wire message */
interface RequestParams {
  type: "ApprovalRequest" | "ToolCallRequest" | "QuestionRequest"
  payload: ApprovalRequest | ToolCallRequest | QuestionRequest
}
```

**Approval request example**

```json
{"jsonrpc": "2.0", "method": "request", "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "params": {"type": "ApprovalRequest", "payload": {"id": "approval-1", "tool_call_id": "tc-1", "sender": "Shell", "action": "run shell command", "description": "Run command `ls`", "display": []}}}
```

**Approval response example**

```json
{"jsonrpc": "2.0", "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "result": {"request_id": "approval-1", "response": "approve"}}
```

**External tool call request example**

```json
{"jsonrpc": "2.0", "method": "request", "id": "a3bb189e-8bf9-3888-9912-ace4e6543002", "params": {"type": "ToolCallRequest", "payload": {"id": "tc-1", "name": "open_in_ide", "arguments": "{\"path\":\"README.md\"}"}}}
```

**External tool call response example**

```json
{"jsonrpc": "2.0", "id": "a3bb189e-8bf9-3888-9912-ace4e6543002", "result": {"tool_call_id": "tc-1", "return_value": {"is_error": false, "output": "Opened", "message": "Opened README.md in IDE", "display": []}}}
```

### Standard error codes

All requests may return JSON-RPC 2.0 standard errors:

| code | Description |
|------|-------------|
| `-32700` | Invalid JSON format |
| `-32600` | Invalid request (e.g., missing required fields) |
| `-32601` | Method not found |
| `-32602` | Invalid method parameters |
| `-32603` | Internal error |

## Wire message types

Wire messages are transmitted via `event` and `request` methods, in format `{"type": "...", "payload": {...}}`. The following describes all message types using TypeScript-style type definitions.

```typescript
/** Union type of all Wire messages */
type WireMessage = Event | Request

/** Events: sent via event method, no response needed */
type Event =
  | TurnBegin
  | TurnEnd
  | StepBegin
  | StepInterrupted
  | StepRetry
  | ToolExecutionStarted
  | ToolOutputPart
  | CompactionBegin
  | CompactionEnd
  | MCPLoadingBegin
  | MCPLoadingEnd
  | StatusUpdate
  | Notification
  | ContentPart
  | ToolCall
  | ToolCallPart
  | ToolResult
  | ApprovalResponse
  | QuestionAnswered
  | ProgressNote
  | Suggestion
  | SubagentEvent
  | BtwBegin
  | BtwEnd
  | SteerInput
  | PlanDisplay
  | HookTriggered
  | HookResolved

/** Requests: sent via request method, require response */
type Request = ApprovalRequest | ToolCallRequest | QuestionRequest | HookRequest
```

### `TurnBegin`

Turn started.

```typescript
interface TurnBegin {
  /** User input, can be plain text or array of content parts */
  user_input: string | ContentPart[]
}
```

### `TurnEnd`

::: info Added
Added in Wire 1.2.
:::

Turn ended. This event is sent after all other events in the turn. If the turn is interrupted, this event may be omitted.

```typescript
interface TurnEnd {
  // No additional fields
}
```

### `StepBegin`

Step started.

```typescript
interface StepBegin {
  /** Step number, starting from 1 */
  n: number
}
```

### `StepInterrupted`

Step interrupted, no additional fields.

### `CompactionBegin`

Context compaction started, no additional fields.

### `CompactionEnd`

Context compaction ended, no additional fields.

### `StatusUpdate`

Status update.

```typescript
interface StatusUpdate {
  /** Context usage ratio, float between 0-1, may be absent in JSON */
  context_usage?: number | null
  /** Number of tokens currently in the context, may be absent in JSON */
  context_tokens?: number | null
  /** Maximum number of tokens the context can hold, may be absent in JSON */
  max_context_tokens?: number | null
  /** Token usage stats for current step, may be absent in JSON */
  token_usage?: TokenUsage | null
  /** Message ID for current step, may be absent in JSON */
  message_id?: string | null
  /** Model ID that produced this step (e.g. "claude-sonnet-4-5"), may be absent in JSON */
  model_name?: string | null
  /** Provider config key for this step (e.g. "managed:openai-chatgpt"), may be absent in JSON */
  provider_key?: string | null
  /** Whether plan mode (read-only) is active, null means no change, may be absent in JSON */
  plan_mode?: boolean | null
  /** Current MCP startup snapshot, null means no change, may be absent in JSON */
  mcp_status?: MCPStatusSnapshot | null
}

interface TokenUsage {
  /** Input tokens excluding input_cache_read and input_cache_creation */
  input_other: number
  /** Total output tokens */
  output: number
  /** Cached input tokens */
  input_cache_read: number
  /** Input tokens used for cache creation, currently only Anthropic API supports this field */
  input_cache_creation: number
}
```

### `ContentPart`

Message content part. Serialized with `type` as `"ContentPart"`, specific type distinguished by `payload.type`.

```typescript
type ContentPart =
  | TextPart
  | ThinkPart
  | ImageURLPart
  | AudioURLPart
  | VideoURLPart

interface TextPart {
  type: "text"
  /** Text content */
  text: string
}

interface ThinkPart {
  type: "think"
  /** Thinking content */
  think: string
  /** Encrypted thinking content or signature, may be absent in JSON */
  encrypted?: string | null
}

interface ImageURLPart {
  type: "image_url"
  image_url: {
    /** Image URL, can be data URI (e.g., data:image/png;base64,...) */
    url: string
    /** Image ID for distinguishing different images, may be absent in JSON */
    id?: string | null
  }
}

interface AudioURLPart {
  type: "audio_url"
  audio_url: {
    /** Audio URL, can be data URI (e.g., data:audio/aac;base64,...) */
    url: string
    /** Audio ID for distinguishing different audio, may be absent in JSON */
    id?: string | null
  }
}

interface VideoURLPart {
  type: "video_url"
  video_url: {
    /** Video URL, can be data URI (e.g., data:video/mp4;base64,...) */
    url: string
    /** Video ID for distinguishing different video, may be absent in JSON */
    id?: string | null
  }
}
```

### `ToolCall`

Tool call.

```typescript
interface ToolCall {
  /** Fixed as "function" */
  type: "function"
  /** Tool call ID */
  id: string
  function: {
    /** Tool name */
    name: string
    /** JSON-format argument string, may be absent in JSON */
    arguments?: string | null
  }
  /** Extra info, may be absent in JSON */
  extras?: object | null
}
```

### `ToolCallPart`

Tool call argument fragment (streaming).

```typescript
interface ToolCallPart {
  /** Argument fragment for streaming tool call arguments, may be absent in JSON */
  arguments_part?: string | null
}
```

### `ToolResult`

Tool execution result.

```typescript
interface ToolResult {
  /** Corresponding tool call ID */
  tool_call_id: string
  return_value: ToolReturnValue
}

interface ToolReturnValue {
  /** Whether this is an error */
  is_error: boolean
  /** Output content returned to model */
  output: string | ContentPart[]
  /** Explanatory message for model */
  message: string
  /** Display blocks shown to user */
  display: DisplayBlock[]
  /** Extra debug info, may be absent in JSON */
  extras?: object | null
}
```

### `ApprovalResponse`

::: info Changed
Renamed in Wire 1.1. Formerly `ApprovalRequestResolved`. The old name is still accepted for backwards compatibility.
:::

Approval response event, indicates an approval request has been completed.

```typescript
interface ApprovalResponse {
  /** Approval request ID */
  request_id: string
  /** Approval result */
  response: "approve" | "approve_for_session" | "reject"
  /** Optional feedback text when rejecting, may be absent in JSON */
  feedback?: string
}
```

### `QuestionAnswered`

::: info Added
Added in Wire 1.9.
:::

Transcript event emitted after a `QuestionRequest` is resolved, so clients can show the user's choice in the conversation flow.

```typescript
interface QuestionAnswered {
  /** Corresponding question request ID */
  request_id: string
  /** Associated tool call ID */
  tool_call_id: string
  /** Answer mapping; empty when dismissed */
  answers: Record<string, string>
  /** True when the user dismissed the question without answering */
  dismissed: boolean
}
```

### `ProgressNote`

::: info Added
Added in Wire 1.9.
:::

Compact checkpoint/progress note for transcript UIs.

```typescript
interface ProgressNote {
  /** Short note title, for example "Checkpoint" */
  title: string
  /** Optional markdown body shown below the title */
  body: string
}
```

### `BtwBegin`

::: info Added
Added in Wire 1.9.
:::

A side question (`/btw`) has started processing.

```typescript
interface BtwBegin {
  /** Unique ID to pair with the corresponding BtwEnd */
  id: string
  /** The user's original side question text */
  question: string
}
```

### `BtwEnd`

::: info Added
Added in Wire 1.9.
:::

A side question (`/btw`) has finished processing.

```typescript
interface BtwEnd {
  /** Unique ID matching the corresponding BtwBegin */
  id: string
  /** The LLM's response text, or null if it failed */
  response?: string | null
  /** Error message if the side question failed */
  error?: string | null
}
```

### `SubagentEvent`

::: info Changed
Changed in Wire 1.6. `task_tool_call_id` renamed to `parent_tool_call_id`; added `agent_id` and `subagent_type` fields.
:::

Subagent event.

```typescript
interface SubagentEvent {
  /** Associated parent Agent tool call ID, may be absent in JSON */
  parent_tool_call_id?: string | null
  /** Subagent instance ID, may be absent in JSON */
  agent_id?: string | null
  /** Built-in subagent type used by this instance, may be absent in JSON */
  subagent_type?: string | null
  /** Event from subagent, nested Wire message format */
  event: { type: string; payload: object }
}
```

### `SteerInput`

::: info Added
Added in Wire 1.5.
:::

Indicates that the user appended follow-up input to the current running turn. This event is emitted after the current step finishes and the input is appended to context, before the next step begins.

```typescript
interface SteerInput {
  /** User input, can be plain text or array of content parts */
  user_input: string | ContentPart[]
}
```

### `PlanDisplay`

::: info Added
Added in Wire 1.7.
:::

Plan content display event. When the agent calls `ExitPlanMode` to submit a plan for user approval in plan mode, this event is sent first to display the plan content inline in the chat. Clients should render it as a bordered panel or similar visually distinct element, and show the file path for reference.

```typescript
interface PlanDisplay {
  /** Full markdown content of the plan */
  content: string
  /** Path to the plan file */
  file_path: string
}
```

### `HookTriggered`

::: info Added
Added in Wire 1.7.
:::

Hook execution started event. Sent when configured hooks are triggered and begin executing, to notify the client that hooks are running.

```typescript
interface HookTriggered {
  /** Hook event type, e.g., 'PreToolUse', 'Stop' */
  event: string
  /** Target of the hook: tool name for tool hooks, agent name for subagent hooks, etc. */
  target: string
  /** Number of matched hooks running in parallel */
  hook_count: number
}
```

### `HookResolved`

::: info Added
Added in Wire 1.7.
:::

Hook execution completed event. Sent when hooks finish executing, containing the result and duration information.

```typescript
interface HookResolved {
  /** Hook event type, e.g., 'PreToolUse', 'Stop' */
  event: string
  /** Same as HookTriggered.target */
  target: string
  /** Aggregate decision: 'block' if any hook blocked, 'allow' otherwise */
  action: "allow" | "block"
  /** Reason for blocking, empty if allowed */
  reason: string
  /** Wall-clock time for the entire batch in milliseconds */
  duration_ms: number
}
```

### `ApprovalRequest`

::: info Changed
Changed in Wire 1.6. Added `source_kind`, `source_id`, `agent_id`, `subagent_type`, and `source_description` fields.
:::

Approval request, sent via `request` method, client must respond before agent can continue.

```typescript
interface ApprovalRequest {
  /** Request ID, used when responding */
  id: string
  /** Associated tool call ID */
  tool_call_id: string
  /** Sender (tool name) */
  sender: string
  /** Action description */
  action: string
  /** Detailed description */
  description: string
  /** Display blocks shown to user, may be absent in JSON, defaults to [] */
  display?: DisplayBlock[]
  /** Where the request originated: foreground turn or background agent, may be absent in JSON */
  source_kind?: "foreground_turn" | "background_agent" | null
  /** Source identifier (e.g. background agent ID), may be absent in JSON */
  source_id?: string | null
  /** Subagent instance ID if from a subagent, may be absent in JSON */
  agent_id?: string | null
  /** Subagent type if from a subagent, may be absent in JSON */
  subagent_type?: string | null
  /** Human-readable source description, may be absent in JSON */
  source_description?: string | null
}
```

**Response format**

::: info Changed
Changed in Wire 1.6. Added optional `feedback` field.
:::

Client needs to return `ApprovalResponse` as the response result:

```typescript
interface ApprovalResponse {
  request_id: string
  response: "approve" | "approve_for_session" | "reject"
  /** Optional feedback text when rejecting, may be absent in JSON */
  feedback?: string
}
```

| response | Description |
|----------|-------------|
| `approve` | Approve this operation |
| `approve_for_session` | Approve similar operations for this session |
| `reject` | Reject operation; optionally include `feedback` to instruct the model on what to do instead |

### `ToolCallRequest`

External tool call request, sent via `request` method. When the agent calls an external tool registered via `initialize`, this request is sent. The client must execute the tool and return a `ToolResult`.

```typescript
interface ToolCallRequest {
  /** Tool call ID */
  id: string
  /** Tool name */
  name: string
  /** JSON-format argument string, may be absent in JSON */
  arguments?: string | null
}
```

**Response format**

Client needs to return `ToolResult` as the response result:

```typescript
interface ToolResult {
  tool_call_id: string
  return_value: ToolReturnValue
}
```

### `QuestionRequest`

::: info Added
Added in Wire 1.4.
:::

Structured question request, sent via `request` method. When the agent uses the `AskUserQuestion` tool, this request is sent. The client must respond before the agent can continue execution.

This feature requires capability negotiation: the client must declare `capabilities.supports_question: true` during `initialize` for the agent to send `QuestionRequest`. If the client does not declare support, the `AskUserQuestion` tool is automatically hidden from the LLM's tool list, preventing the LLM from invoking unsupported interactions.

```typescript
interface QuestionRequest {
  /** Request ID, used when responding */
  id: string
  /** Associated tool call ID */
  tool_call_id: string
  /** Questions list (1–4 questions) */
  questions: QuestionItem[]
}

interface QuestionItem {
  /** Question text */
  question: string
  /** Short label, max 12 characters */
  header?: string
  /** Available options (2–4) */
  options: QuestionOption[]
  /** Whether multiple options can be selected */
  multi_select?: boolean
  /** Optional markdown body displayed above the options, may be absent in JSON */
  body?: string
  /** Custom label for the synthetic "Other" free-text option; empty uses default */
  other_label?: string
  /** Custom description for the synthetic "Other" option; empty uses default */
  other_description?: string
}

interface QuestionOption {
  /** Option label */
  label: string
  /** Option description */
  description?: string
}
```

**Request example**

```json
{"jsonrpc": "2.0", "method": "request", "id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890", "params": {"type": "QuestionRequest", "payload": {"id": "q-1", "tool_call_id": "tc-1", "questions": [{"question": "Which language should I use?", "header": "Lang", "options": [{"label": "Python", "description": "Widely used, large ecosystem"}, {"label": "Rust", "description": "High performance, memory safe"}], "multi_select": false}]}}}
```

**Response format**

Client needs to return `QuestionResponse` as the response result:

```typescript
interface QuestionResponse {
  /** Corresponding request ID */
  request_id: string
  /** Answer mapping, key is question text, value is selected option label(s) (comma-separated for multi-select) */
  answers: Record<string, string>
}
```

**Response example**

```json
{"jsonrpc": "2.0", "id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890", "result": {"request_id": "q-1", "answers": {"Which language should I use?": "Python"}}}
```

If the client does not support structured questions or the user dismisses the question panel, return empty `answers`:

```json
{"jsonrpc": "2.0", "id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890", "result": {"request_id": "q-1", "answers": {}}}
```

### `HookRequest`

::: info Added
Added in Wire 1.7.
:::

Hook handling request, sent via `request` method. When a Wire client subscribes to hook events, the server sends this request to let the client handle the hook logic and return an allow/block decision.

This feature requires capability negotiation: the server only sends corresponding `HookRequest` messages after the client declares subscriptions via `hooks` in `initialize`.

```typescript
interface HookRequest {
  /** Request ID, used when responding */
  id: string
  /** Subscription ID, identifies which subscription triggered this request */
  subscription_id: string
  /** Hook event type, e.g., 'PreToolUse', 'Stop' */
  event: string
  /** Target that triggered the hook: tool name, agent name, etc. */
  target: string
  /** Full event payload (same as what shell hooks receive on stdin) */
  input_data: object
}
```

**Response format**

The client should return a `HookResponse` as the response result:

```typescript
interface HookResponse {
  /** Corresponding request ID */
  request_id: string
  /** Decision: allow or block */
  action: "allow" | "block"
  /** Reason for blocking */
  reason: string
}
```

### `DisplayBlock`

Display block types used in the `display` field of `ToolResult` and `ApprovalRequest`.

```typescript
type DisplayBlock =
  | UnknownDisplayBlock
  | BriefDisplayBlock
  | DiffDisplayBlock
  | TodoDisplayBlock
  | ShellDisplayBlock

/** Fallback for unrecognized display block types */
interface UnknownDisplayBlock {
  /** Any type identifier */
  type: string
  /** Raw data */
  data: object
}

interface BriefDisplayBlock {
  type: "brief"
  /** Brief text content */
  text: string
}

interface DiffDisplayBlock {
  type: "diff"
  /** File path */
  path: string
  /** Original content */
  old_text: string
  /** New content */
  new_text: string
  /** Whether this is a summary block (shows line count summary instead of actual diff for large files). May not be present in JSON. Added in Wire 1.8 */
  is_summary?: boolean
}

interface TodoDisplayBlock {
  type: "todo"
  /** Todo list items */
  items: TodoDisplayItem[]
}

interface TodoDisplayItem {
  /** Todo item title */
  title: string
  /** Status */
  status: "pending" | "in_progress" | "done"
}

interface ShellDisplayBlock {
  type: "shell"
  /** Language identifier for syntax highlighting (e.g., "sh", "powershell") */
  language: string
  /** Shell command content */
  command: string
}
```
