/ entrypoints / sdk / controlSchemas.ts
controlSchemas.ts
  1  /**
  2   * SDK Control Schemas - Zod schemas for the control protocol.
  3   *
  4   * These schemas define the control protocol between SDK implementations and the CLI.
  5   * Used by SDK builders (e.g., Python SDK) to communicate with the CLI process.
  6   *
  7   * SDK consumers should use coreSchemas.ts instead.
  8   */
  9  
 10  import { z } from 'zod/v4'
 11  import { lazySchema } from '../../utils/lazySchema.js'
 12  import {
 13    AccountInfoSchema,
 14    AgentDefinitionSchema,
 15    AgentInfoSchema,
 16    FastModeStateSchema,
 17    HookEventSchema,
 18    HookInputSchema,
 19    McpServerConfigForProcessTransportSchema,
 20    McpServerStatusSchema,
 21    ModelInfoSchema,
 22    PermissionModeSchema,
 23    PermissionUpdateSchema,
 24    SDKMessageSchema,
 25    SDKPostTurnSummaryMessageSchema,
 26    SDKStreamlinedTextMessageSchema,
 27    SDKStreamlinedToolUseSummaryMessageSchema,
 28    SDKUserMessageSchema,
 29    SlashCommandSchema,
 30  } from './coreSchemas.js'
 31  
 32  // ============================================================================
 33  // External Type Placeholders
 34  // ============================================================================
 35  
 36  // JSONRPCMessage from @modelcontextprotocol/sdk - treat as unknown
 37  export const JSONRPCMessagePlaceholder = lazySchema(() => z.unknown())
 38  
 39  // ============================================================================
 40  // Hook Callback Types
 41  // ============================================================================
 42  
 43  export const SDKHookCallbackMatcherSchema = lazySchema(() =>
 44    z
 45      .object({
 46        matcher: z.string().optional(),
 47        hookCallbackIds: z.array(z.string()),
 48        timeout: z.number().optional(),
 49      })
 50      .describe('Configuration for matching and routing hook callbacks.'),
 51  )
 52  
 53  // ============================================================================
 54  // Control Request Types
 55  // ============================================================================
 56  
 57  export const SDKControlInitializeRequestSchema = lazySchema(() =>
 58    z
 59      .object({
 60        subtype: z.literal('initialize'),
 61        hooks: z
 62          .record(HookEventSchema(), z.array(SDKHookCallbackMatcherSchema()))
 63          .optional(),
 64        sdkMcpServers: z.array(z.string()).optional(),
 65        jsonSchema: z.record(z.string(), z.unknown()).optional(),
 66        systemPrompt: z.string().optional(),
 67        appendSystemPrompt: z.string().optional(),
 68        agents: z.record(z.string(), AgentDefinitionSchema()).optional(),
 69        promptSuggestions: z.boolean().optional(),
 70        agentProgressSummaries: z.boolean().optional(),
 71      })
 72      .describe(
 73        'Initializes the SDK session with hooks, MCP servers, and agent configuration.',
 74      ),
 75  )
 76  
 77  export const SDKControlInitializeResponseSchema = lazySchema(() =>
 78    z
 79      .object({
 80        commands: z.array(SlashCommandSchema()),
 81        agents: z.array(AgentInfoSchema()),
 82        output_style: z.string(),
 83        available_output_styles: z.array(z.string()),
 84        models: z.array(ModelInfoSchema()),
 85        account: AccountInfoSchema(),
 86        pid: z
 87          .number()
 88          .optional()
 89          .describe('@internal CLI process PID for tmux socket isolation'),
 90        fast_mode_state: FastModeStateSchema().optional(),
 91      })
 92      .describe(
 93        'Response from session initialization with available commands, models, and account info.',
 94      ),
 95  )
 96  
 97  export const SDKControlInterruptRequestSchema = lazySchema(() =>
 98    z
 99      .object({
100        subtype: z.literal('interrupt'),
101      })
102      .describe('Interrupts the currently running conversation turn.'),
103  )
104  
105  
106  export const SDKControlPermissionRequestSchema = lazySchema(() =>
107    z
108      .object({
109        subtype: z.literal('can_use_tool'),
110        tool_name: z.string(),
111        input: z.record(z.string(), z.unknown()),
112        permission_suggestions: z.array(PermissionUpdateSchema()).optional(),
113        blocked_path: z.string().optional(),
114        decision_reason: z.string().optional(),
115        title: z.string().optional(),
116        display_name: z.string().optional(),
117        tool_use_id: z.string(),
118        agent_id: z.string().optional(),
119        description: z.string().optional(),
120      })
121      .describe('Requests permission to use a tool with the given input.'),
122  )
123  
124  export const SDKControlSetPermissionModeRequestSchema = lazySchema(() =>
125    z
126      .object({
127        subtype: z.literal('set_permission_mode'),
128        mode: PermissionModeSchema(),
129        ultraplan: z
130          .boolean()
131          .optional()
132          .describe('@internal CCR ultraplan session marker.'),
133      })
134      .describe('Sets the permission mode for tool execution handling.'),
135  )
136  
137  export const SDKControlSetModelRequestSchema = lazySchema(() =>
138    z
139      .object({
140        subtype: z.literal('set_model'),
141        model: z.string().optional(),
142      })
143      .describe('Sets the model to use for subsequent conversation turns.'),
144  )
145  
146  export const SDKControlSetMaxThinkingTokensRequestSchema = lazySchema(() =>
147    z
148      .object({
149        subtype: z.literal('set_max_thinking_tokens'),
150        max_thinking_tokens: z.number().nullable(),
151      })
152      .describe(
153        'Sets the maximum number of thinking tokens for extended thinking.',
154      ),
155  )
156  
157  export const SDKControlMcpStatusRequestSchema = lazySchema(() =>
158    z
159      .object({
160        subtype: z.literal('mcp_status'),
161      })
162      .describe('Requests the current status of all MCP server connections.'),
163  )
164  
165  export const SDKControlMcpStatusResponseSchema = lazySchema(() =>
166    z
167      .object({
168        mcpServers: z.array(McpServerStatusSchema()),
169      })
170      .describe(
171        'Response containing the current status of all MCP server connections.',
172      ),
173  )
174  
175  export const SDKControlGetContextUsageRequestSchema = lazySchema(() =>
176    z
177      .object({
178        subtype: z.literal('get_context_usage'),
179      })
180      .describe(
181        'Requests a breakdown of current context window usage by category.',
182      ),
183  )
184  
185  const ContextCategorySchema = lazySchema(() =>
186    z.object({
187      name: z.string(),
188      tokens: z.number(),
189      color: z.string(),
190      isDeferred: z.boolean().optional(),
191    }),
192  )
193  
194  const ContextGridSquareSchema = lazySchema(() =>
195    z.object({
196      color: z.string(),
197      isFilled: z.boolean(),
198      categoryName: z.string(),
199      tokens: z.number(),
200      percentage: z.number(),
201      squareFullness: z.number(),
202    }),
203  )
204  
205  export const SDKControlGetContextUsageResponseSchema = lazySchema(() =>
206    z
207      .object({
208        categories: z.array(ContextCategorySchema()),
209        totalTokens: z.number(),
210        maxTokens: z.number(),
211        rawMaxTokens: z.number(),
212        percentage: z.number(),
213        gridRows: z.array(z.array(ContextGridSquareSchema())),
214        model: z.string(),
215        memoryFiles: z.array(
216          z.object({
217            path: z.string(),
218            type: z.string(),
219            tokens: z.number(),
220          }),
221        ),
222        mcpTools: z.array(
223          z.object({
224            name: z.string(),
225            serverName: z.string(),
226            tokens: z.number(),
227            isLoaded: z.boolean().optional(),
228          }),
229        ),
230        deferredBuiltinTools: z
231          .array(
232            z.object({
233              name: z.string(),
234              tokens: z.number(),
235              isLoaded: z.boolean(),
236            }),
237          )
238          .optional(),
239        systemTools: z
240          .array(z.object({ name: z.string(), tokens: z.number() }))
241          .optional(),
242        systemPromptSections: z
243          .array(z.object({ name: z.string(), tokens: z.number() }))
244          .optional(),
245        agents: z.array(
246          z.object({
247            agentType: z.string(),
248            source: z.string(),
249            tokens: z.number(),
250          }),
251        ),
252        slashCommands: z
253          .object({
254            totalCommands: z.number(),
255            includedCommands: z.number(),
256            tokens: z.number(),
257          })
258          .optional(),
259        skills: z
260          .object({
261            totalSkills: z.number(),
262            includedSkills: z.number(),
263            tokens: z.number(),
264            skillFrontmatter: z.array(
265              z.object({
266                name: z.string(),
267                source: z.string(),
268                tokens: z.number(),
269              }),
270            ),
271          })
272          .optional(),
273        autoCompactThreshold: z.number().optional(),
274        isAutoCompactEnabled: z.boolean(),
275        messageBreakdown: z
276          .object({
277            toolCallTokens: z.number(),
278            toolResultTokens: z.number(),
279            attachmentTokens: z.number(),
280            assistantMessageTokens: z.number(),
281            userMessageTokens: z.number(),
282            toolCallsByType: z.array(
283              z.object({
284                name: z.string(),
285                callTokens: z.number(),
286                resultTokens: z.number(),
287              }),
288            ),
289            attachmentsByType: z.array(
290              z.object({ name: z.string(), tokens: z.number() }),
291            ),
292          })
293          .optional(),
294        apiUsage: z
295          .object({
296            input_tokens: z.number(),
297            output_tokens: z.number(),
298            cache_creation_input_tokens: z.number(),
299            cache_read_input_tokens: z.number(),
300          })
301          .nullable(),
302      })
303      .describe(
304        'Breakdown of current context window usage by category (system prompt, tools, messages, etc.).',
305      ),
306  )
307  
308  export const SDKControlRewindFilesRequestSchema = lazySchema(() =>
309    z
310      .object({
311        subtype: z.literal('rewind_files'),
312        user_message_id: z.string(),
313        dry_run: z.boolean().optional(),
314      })
315      .describe('Rewinds file changes made since a specific user message.'),
316  )
317  
318  export const SDKControlRewindFilesResponseSchema = lazySchema(() =>
319    z
320      .object({
321        canRewind: z.boolean(),
322        error: z.string().optional(),
323        filesChanged: z.array(z.string()).optional(),
324        insertions: z.number().optional(),
325        deletions: z.number().optional(),
326      })
327      .describe('Result of a rewindFiles operation.'),
328  )
329  
330  export const SDKControlCancelAsyncMessageRequestSchema = lazySchema(() =>
331    z
332      .object({
333        subtype: z.literal('cancel_async_message'),
334        message_uuid: z.string(),
335      })
336      .describe(
337        'Drops a pending async user message from the command queue by uuid. No-op if already dequeued for execution.',
338      ),
339  )
340  
341  export const SDKControlCancelAsyncMessageResponseSchema = lazySchema(() =>
342    z
343      .object({
344        cancelled: z.boolean(),
345      })
346      .describe(
347        'Result of a cancel_async_message operation. cancelled=false means the message was not in the queue (already dequeued or never enqueued).',
348      ),
349  )
350  
351  export const SDKControlSeedReadStateRequestSchema = lazySchema(() =>
352    z
353      .object({
354        subtype: z.literal('seed_read_state'),
355        path: z.string(),
356        mtime: z.number(),
357      })
358      .describe(
359        'Seeds the readFileState cache with a path+mtime entry. Use when a prior Read was removed from context (e.g. by snip) so Edit validation would fail despite the client having observed the Read. The mtime lets the CLI detect if the file changed since the seeded Read — same staleness check as the normal path.',
360      ),
361  )
362  
363  export const SDKHookCallbackRequestSchema = lazySchema(() =>
364    z
365      .object({
366        subtype: z.literal('hook_callback'),
367        callback_id: z.string(),
368        input: HookInputSchema(),
369        tool_use_id: z.string().optional(),
370      })
371      .describe('Delivers a hook callback with its input data.'),
372  )
373  
374  export const SDKControlMcpMessageRequestSchema = lazySchema(() =>
375    z
376      .object({
377        subtype: z.literal('mcp_message'),
378        server_name: z.string(),
379        message: JSONRPCMessagePlaceholder(),
380      })
381      .describe('Sends a JSON-RPC message to a specific MCP server.'),
382  )
383  
384  export const SDKControlMcpSetServersRequestSchema = lazySchema(() =>
385    z
386      .object({
387        subtype: z.literal('mcp_set_servers'),
388        servers: z.record(z.string(), McpServerConfigForProcessTransportSchema()),
389      })
390      .describe('Replaces the set of dynamically managed MCP servers.'),
391  )
392  
393  export const SDKControlMcpSetServersResponseSchema = lazySchema(() =>
394    z
395      .object({
396        added: z.array(z.string()),
397        removed: z.array(z.string()),
398        errors: z.record(z.string(), z.string()),
399      })
400      .describe(
401        'Result of replacing the set of dynamically managed MCP servers.',
402      ),
403  )
404  
405  export const SDKControlReloadPluginsRequestSchema = lazySchema(() =>
406    z
407      .object({
408        subtype: z.literal('reload_plugins'),
409      })
410      .describe(
411        'Reloads plugins from disk and returns the refreshed session components.',
412      ),
413  )
414  
415  export const SDKControlReloadPluginsResponseSchema = lazySchema(() =>
416    z
417      .object({
418        commands: z.array(SlashCommandSchema()),
419        agents: z.array(AgentInfoSchema()),
420        plugins: z.array(
421          z.object({
422            name: z.string(),
423            path: z.string(),
424            source: z.string().optional(),
425          }),
426        ),
427        mcpServers: z.array(McpServerStatusSchema()),
428        error_count: z.number(),
429      })
430      .describe(
431        'Refreshed commands, agents, plugins, and MCP server status after reload.',
432      ),
433  )
434  
435  export const SDKControlMcpReconnectRequestSchema = lazySchema(() =>
436    z
437      .object({
438        subtype: z.literal('mcp_reconnect'),
439        serverName: z.string(),
440      })
441      .describe('Reconnects a disconnected or failed MCP server.'),
442  )
443  
444  export const SDKControlMcpToggleRequestSchema = lazySchema(() =>
445    z
446      .object({
447        subtype: z.literal('mcp_toggle'),
448        serverName: z.string(),
449        enabled: z.boolean(),
450      })
451      .describe('Enables or disables an MCP server.'),
452  )
453  
454  
455  export const SDKControlStopTaskRequestSchema = lazySchema(() =>
456    z
457      .object({
458        subtype: z.literal('stop_task'),
459        task_id: z.string(),
460      })
461      .describe('Stops a running task.'),
462  )
463  
464  export const SDKControlApplyFlagSettingsRequestSchema = lazySchema(() =>
465    z
466      .object({
467        subtype: z.literal('apply_flag_settings'),
468        settings: z.record(z.string(), z.unknown()),
469      })
470      .describe(
471        'Merges the provided settings into the flag settings layer, updating the active configuration.',
472      ),
473  )
474  
475  export const SDKControlGetSettingsRequestSchema = lazySchema(() =>
476    z
477      .object({
478        subtype: z.literal('get_settings'),
479      })
480      .describe(
481        'Returns the effective merged settings and the raw per-source settings.',
482      ),
483  )
484  
485  export const SDKControlGetSettingsResponseSchema = lazySchema(() =>
486    z
487      .object({
488        effective: z.record(z.string(), z.unknown()),
489        sources: z
490          .array(
491            z.object({
492              source: z.enum([
493                'userSettings',
494                'projectSettings',
495                'localSettings',
496                'flagSettings',
497                'policySettings',
498              ]),
499              settings: z.record(z.string(), z.unknown()),
500            }),
501          )
502          .describe(
503            'Ordered low-to-high priority — later entries override earlier ones.',
504          ),
505        applied: z
506          .object({
507            model: z.string(),
508            // String levels only — numeric effort is ant-only and the
509            // Zod→proto generator can't emit enum∪number unions.
510            effort: z.enum(['low', 'medium', 'high', 'max']).nullable(),
511          })
512          .optional()
513          .describe(
514            'Runtime-resolved values after env overrides, session state, and model-specific defaults are applied. Unlike `effective` (disk merge), these reflect what will actually be sent to the API.',
515          ),
516      })
517      .describe(
518        'Effective merged settings plus raw per-source settings in merge order.',
519      ),
520  )
521  
522  export const SDKControlElicitationRequestSchema = lazySchema(() =>
523    z
524      .object({
525        subtype: z.literal('elicitation'),
526        mcp_server_name: z.string(),
527        message: z.string(),
528        mode: z.enum(['form', 'url']).optional(),
529        url: z.string().optional(),
530        elicitation_id: z.string().optional(),
531        requested_schema: z.record(z.string(), z.unknown()).optional(),
532      })
533      .describe(
534        'Requests the SDK consumer to handle an MCP elicitation (user input request).',
535      ),
536  )
537  
538  export const SDKControlElicitationResponseSchema = lazySchema(() =>
539    z
540      .object({
541        action: z.enum(['accept', 'decline', 'cancel']),
542        content: z.record(z.string(), z.unknown()).optional(),
543      })
544      .describe('Response from the SDK consumer for an elicitation request.'),
545  )
546  
547  
548  // ============================================================================
549  // Control Request/Response Wrappers
550  // ============================================================================
551  
552  export const SDKControlRequestInnerSchema = lazySchema(() =>
553    z.union([
554      SDKControlInterruptRequestSchema(),
555      SDKControlPermissionRequestSchema(),
556      SDKControlInitializeRequestSchema(),
557      SDKControlSetPermissionModeRequestSchema(),
558      SDKControlSetModelRequestSchema(),
559      SDKControlSetMaxThinkingTokensRequestSchema(),
560      SDKControlMcpStatusRequestSchema(),
561      SDKControlGetContextUsageRequestSchema(),
562      SDKHookCallbackRequestSchema(),
563      SDKControlMcpMessageRequestSchema(),
564      SDKControlRewindFilesRequestSchema(),
565      SDKControlCancelAsyncMessageRequestSchema(),
566      SDKControlSeedReadStateRequestSchema(),
567      SDKControlMcpSetServersRequestSchema(),
568      SDKControlReloadPluginsRequestSchema(),
569      SDKControlMcpReconnectRequestSchema(),
570      SDKControlMcpToggleRequestSchema(),
571      SDKControlStopTaskRequestSchema(),
572      SDKControlApplyFlagSettingsRequestSchema(),
573      SDKControlGetSettingsRequestSchema(),
574      SDKControlElicitationRequestSchema(),
575    ]),
576  )
577  
578  export const SDKControlRequestSchema = lazySchema(() =>
579    z.object({
580      type: z.literal('control_request'),
581      request_id: z.string(),
582      request: SDKControlRequestInnerSchema(),
583    }),
584  )
585  
586  export const ControlResponseSchema = lazySchema(() =>
587    z.object({
588      subtype: z.literal('success'),
589      request_id: z.string(),
590      response: z.record(z.string(), z.unknown()).optional(),
591    }),
592  )
593  
594  export const ControlErrorResponseSchema = lazySchema(() =>
595    z.object({
596      subtype: z.literal('error'),
597      request_id: z.string(),
598      error: z.string(),
599      pending_permission_requests: z
600        .array(z.lazy(() => SDKControlRequestSchema()))
601        .optional(),
602    }),
603  )
604  
605  export const SDKControlResponseSchema = lazySchema(() =>
606    z.object({
607      type: z.literal('control_response'),
608      response: z.union([ControlResponseSchema(), ControlErrorResponseSchema()]),
609    }),
610  )
611  
612  export const SDKControlCancelRequestSchema = lazySchema(() =>
613    z
614      .object({
615        type: z.literal('control_cancel_request'),
616        request_id: z.string(),
617      })
618      .describe('Cancels a currently open control request.'),
619  )
620  
621  export const SDKKeepAliveMessageSchema = lazySchema(() =>
622    z
623      .object({
624        type: z.literal('keep_alive'),
625      })
626      .describe('Keep-alive message to maintain WebSocket connection.'),
627  )
628  
629  export const SDKUpdateEnvironmentVariablesMessageSchema = lazySchema(() =>
630    z
631      .object({
632        type: z.literal('update_environment_variables'),
633        variables: z.record(z.string(), z.string()),
634      })
635      .describe('Updates environment variables at runtime.'),
636  )
637  
638  // ============================================================================
639  // Aggregate Message Types
640  // ============================================================================
641  
642  export const StdoutMessageSchema = lazySchema(() =>
643    z.union([
644      SDKMessageSchema(),
645      SDKStreamlinedTextMessageSchema(),
646      SDKStreamlinedToolUseSummaryMessageSchema(),
647      SDKPostTurnSummaryMessageSchema(),
648      SDKControlResponseSchema(),
649      SDKControlRequestSchema(),
650      SDKControlCancelRequestSchema(),
651      SDKKeepAliveMessageSchema(),
652    ]),
653  )
654  
655  export const StdinMessageSchema = lazySchema(() =>
656    z.union([
657      SDKUserMessageSchema(),
658      SDKControlRequestSchema(),
659      SDKControlResponseSchema(),
660      SDKKeepAliveMessageSchema(),
661      SDKUpdateEnvironmentVariablesMessageSchema(),
662    ]),
663  )