Skip to main content

copilot_sdk_supercharged/
types.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2
3//! Type definitions for the Copilot SDK.
4//!
5//! This module contains all the data structures used to communicate with the
6//! Copilot CLI server via JSON-RPC 2.0.
7
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::Arc;
11
12// ============================================================================
13// Connection State
14// ============================================================================
15
16/// Represents the connection state of the client.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ConnectionState {
19    Disconnected,
20    Connecting,
21    Connected,
22    Error,
23}
24
25// ============================================================================
26// Tool Types
27// ============================================================================
28
29/// Result type for tool execution.
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32pub enum ToolResultType {
33    Success,
34    Failure,
35    Rejected,
36    Denied,
37}
38
39/// Binary result from a tool execution.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct ToolBinaryResult {
43    pub data: String,
44    pub mime_type: String,
45    #[serde(rename = "type")]
46    pub result_type: String,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub description: Option<String>,
49}
50
51/// Structured tool result with metadata.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct ToolResultObject {
55    pub text_result_for_llm: String,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub binary_results_for_llm: Option<Vec<ToolBinaryResult>>,
58    pub result_type: ToolResultType,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub error: Option<String>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub session_log: Option<String>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub tool_telemetry: Option<HashMap<String, serde_json::Value>>,
65}
66
67/// A tool result can be either a simple string or a structured object.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(untagged)]
70pub enum ToolResult {
71    Text(String),
72    Object(ToolResultObject),
73}
74
75/// Information about a tool invocation.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct ToolInvocation {
79    pub session_id: String,
80    pub tool_call_id: String,
81    pub tool_name: String,
82    pub arguments: serde_json::Value,
83}
84
85/// Definition of a tool that can be exposed to the Copilot CLI.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct ToolDefinition {
89    pub name: String,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub description: Option<String>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub parameters: Option<serde_json::Value>,
94}
95
96/// Payload sent by the server when requesting a tool call.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct ToolCallRequestPayload {
100    pub session_id: String,
101    pub tool_call_id: String,
102    pub tool_name: String,
103    pub arguments: serde_json::Value,
104}
105
106/// Response payload for a tool call.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ToolCallResponsePayload {
109    pub result: ToolResultObject,
110}
111
112// ============================================================================
113// System Message Configuration
114// ============================================================================
115
116/// Known system prompt section identifiers for the "customize" mode.
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
118pub enum SystemPromptSection {
119    #[serde(rename = "identity")]
120    Identity,
121    #[serde(rename = "tone")]
122    Tone,
123    #[serde(rename = "tool_efficiency")]
124    ToolEfficiency,
125    #[serde(rename = "environment_context")]
126    EnvironmentContext,
127    #[serde(rename = "code_change_rules")]
128    CodeChangeRules,
129    #[serde(rename = "guidelines")]
130    Guidelines,
131    #[serde(rename = "safety")]
132    Safety,
133    #[serde(rename = "tool_instructions")]
134    ToolInstructions,
135    #[serde(rename = "custom_instructions")]
136    CustomInstructions,
137    #[serde(rename = "last_instructions")]
138    LastInstructions,
139}
140
141/// Override action for a system prompt section.
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
143#[serde(rename_all = "lowercase")]
144pub enum SectionOverrideAction {
145    Replace,
146    Remove,
147    Append,
148    Prepend,
149}
150
151/// Override operation for a single system prompt section.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct SectionOverride {
154    pub action: SectionOverrideAction,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub content: Option<String>,
157}
158
159/// System message configuration for session creation.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(tag = "mode", rename_all = "camelCase")]
162pub enum SystemMessageConfig {
163    /// Append mode: SDK foundation + optional custom content.
164    #[serde(rename = "append")]
165    Append {
166        #[serde(skip_serializing_if = "Option::is_none")]
167        content: Option<String>,
168    },
169    /// Replace mode: Full control, caller provides entire system message.
170    #[serde(rename = "replace")]
171    Replace { content: String },
172    /// Customize mode: Override individual sections of the system prompt.
173    #[serde(rename = "customize")]
174    Customize {
175        #[serde(skip_serializing_if = "Option::is_none")]
176        sections: Option<HashMap<SystemPromptSection, SectionOverride>>,
177        #[serde(skip_serializing_if = "Option::is_none")]
178        content: Option<String>,
179    },
180}
181
182// ============================================================================
183// Permission Types
184// ============================================================================
185
186/// Kind of permission being requested.
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
188#[serde(rename_all = "lowercase")]
189pub enum PermissionKind {
190    Shell,
191    Write,
192    Mcp,
193    Read,
194    Url,
195}
196
197/// Permission request from the server.
198#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct PermissionRequest {
201    pub kind: PermissionKind,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub tool_call_id: Option<String>,
204    /// Additional fields from the server.
205    #[serde(flatten)]
206    pub extra: HashMap<String, serde_json::Value>,
207}
208
209/// Result of a permission request.
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
211#[serde(rename_all = "kebab-case")]
212pub enum PermissionResultKind {
213    Approved,
214    DeniedByRules,
215    DeniedNoApprovalRuleAndCouldNotRequestFromUser,
216    DeniedInteractivelyByUser,
217}
218
219/// Permission result returned by the handler.
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct PermissionRequestResult {
222    pub kind: PermissionResultKind,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub rules: Option<Vec<serde_json::Value>>,
225}
226
227// ============================================================================
228// User Input Types
229// ============================================================================
230
231/// Request for user input from the agent (enables ask_user tool).
232#[derive(Debug, Clone, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct UserInputRequest {
235    pub question: String,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub choices: Option<Vec<String>>,
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub allow_freeform: Option<bool>,
240}
241
242/// Response to a user input request.
243#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct UserInputResponse {
246    pub answer: String,
247    pub was_freeform: bool,
248}
249
250// ============================================================================
251// Hook Types
252// ============================================================================
253
254/// Base fields common to all hook inputs.
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct BaseHookInput {
257    pub timestamp: f64,
258    pub cwd: String,
259}
260
261/// Input for pre-tool-use hook.
262#[derive(Debug, Clone, Serialize, Deserialize)]
263#[serde(rename_all = "camelCase")]
264pub struct PreToolUseHookInput {
265    pub timestamp: f64,
266    pub cwd: String,
267    pub tool_name: String,
268    pub tool_args: serde_json::Value,
269}
270
271/// Output for pre-tool-use hook.
272#[derive(Debug, Clone, Serialize, Deserialize, Default)]
273#[serde(rename_all = "camelCase")]
274pub struct PreToolUseHookOutput {
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub permission_decision: Option<String>,
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub permission_decision_reason: Option<String>,
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub modified_args: Option<serde_json::Value>,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub additional_context: Option<String>,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub suppress_output: Option<bool>,
285}
286
287/// Input for post-tool-use hook.
288#[derive(Debug, Clone, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase")]
290pub struct PostToolUseHookInput {
291    pub timestamp: f64,
292    pub cwd: String,
293    pub tool_name: String,
294    pub tool_args: serde_json::Value,
295    pub tool_result: ToolResultObject,
296}
297
298/// Output for post-tool-use hook.
299#[derive(Debug, Clone, Serialize, Deserialize, Default)]
300#[serde(rename_all = "camelCase")]
301pub struct PostToolUseHookOutput {
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub modified_result: Option<ToolResultObject>,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub additional_context: Option<String>,
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub suppress_output: Option<bool>,
308}
309
310/// Input for user-prompt-submitted hook.
311#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct UserPromptSubmittedHookInput {
313    pub timestamp: f64,
314    pub cwd: String,
315    pub prompt: String,
316}
317
318/// Output for user-prompt-submitted hook.
319#[derive(Debug, Clone, Serialize, Deserialize, Default)]
320#[serde(rename_all = "camelCase")]
321pub struct UserPromptSubmittedHookOutput {
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub modified_prompt: Option<String>,
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub additional_context: Option<String>,
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub suppress_output: Option<bool>,
328}
329
330/// Input for session-start hook.
331#[derive(Debug, Clone, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct SessionStartHookInput {
334    pub timestamp: f64,
335    pub cwd: String,
336    pub source: String,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub initial_prompt: Option<String>,
339}
340
341/// Output for session-start hook.
342#[derive(Debug, Clone, Serialize, Deserialize, Default)]
343#[serde(rename_all = "camelCase")]
344pub struct SessionStartHookOutput {
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub additional_context: Option<String>,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub modified_config: Option<HashMap<String, serde_json::Value>>,
349}
350
351/// Input for session-end hook.
352#[derive(Debug, Clone, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub struct SessionEndHookInput {
355    pub timestamp: f64,
356    pub cwd: String,
357    pub reason: String,
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub final_message: Option<String>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub error: Option<String>,
362}
363
364/// Output for session-end hook.
365#[derive(Debug, Clone, Serialize, Deserialize, Default)]
366#[serde(rename_all = "camelCase")]
367pub struct SessionEndHookOutput {
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub suppress_output: Option<bool>,
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub cleanup_actions: Option<Vec<String>>,
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub session_summary: Option<String>,
374}
375
376/// Input for error-occurred hook.
377#[derive(Debug, Clone, Serialize, Deserialize)]
378#[serde(rename_all = "camelCase")]
379pub struct ErrorOccurredHookInput {
380    pub timestamp: f64,
381    pub cwd: String,
382    pub error: String,
383    pub error_context: String,
384    pub recoverable: bool,
385}
386
387/// Output for error-occurred hook.
388#[derive(Debug, Clone, Serialize, Deserialize, Default)]
389#[serde(rename_all = "camelCase")]
390pub struct ErrorOccurredHookOutput {
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub suppress_output: Option<bool>,
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub error_handling: Option<String>,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub retry_count: Option<u32>,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub user_notification: Option<String>,
399}
400
401// ============================================================================
402// MCP Server Configuration Types
403// ============================================================================
404
405/// Configuration for a local/stdio MCP server.
406#[derive(Debug, Clone, Serialize, Deserialize)]
407#[serde(rename_all = "camelCase")]
408pub struct McpLocalServerConfig {
409    pub tools: Vec<String>,
410    #[serde(skip_serializing_if = "Option::is_none")]
411    #[serde(rename = "type")]
412    pub server_type: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub timeout: Option<u64>,
415    pub command: String,
416    pub args: Vec<String>,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub env: Option<HashMap<String, String>>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub cwd: Option<String>,
421}
422
423/// Configuration for a remote MCP server (HTTP or SSE).
424#[derive(Debug, Clone, Serialize, Deserialize)]
425#[serde(rename_all = "camelCase")]
426pub struct McpRemoteServerConfig {
427    pub tools: Vec<String>,
428    #[serde(rename = "type")]
429    pub server_type: String,
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub timeout: Option<u64>,
432    pub url: String,
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub headers: Option<HashMap<String, String>>,
435}
436
437/// Union type for MCP server configurations.
438#[derive(Debug, Clone, Serialize, Deserialize)]
439#[serde(untagged)]
440pub enum McpServerConfig {
441    Local(McpLocalServerConfig),
442    Remote(McpRemoteServerConfig),
443}
444
445// ============================================================================
446// Custom Agent Configuration
447// ============================================================================
448
449/// Configuration for a custom agent.
450#[derive(Debug, Clone, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct CustomAgentConfig {
453    pub name: String,
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub display_name: Option<String>,
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub description: Option<String>,
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub tools: Option<Vec<String>>,
460    pub prompt: String,
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub mcp_servers: Option<HashMap<String, McpServerConfig>>,
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub infer: Option<bool>,
465    /// List of skill names to preload into this agent's context.
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub skills: Option<Vec<String>>,
468}
469
470// ============================================================================
471// Infinite Session Configuration
472// ============================================================================
473
474/// Configuration for infinite sessions with automatic context compaction.
475#[derive(Debug, Clone, Serialize, Deserialize)]
476#[serde(rename_all = "camelCase")]
477pub struct InfiniteSessionConfig {
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub enabled: Option<bool>,
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub background_compaction_threshold: Option<f64>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub buffer_exhaustion_threshold: Option<f64>,
484}
485
486// ============================================================================
487// Provider Configuration
488// ============================================================================
489
490/// Azure-specific provider options.
491#[derive(Debug, Clone, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct AzureProviderOptions {
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub api_version: Option<String>,
496}
497
498/// Configuration for a custom API provider (BYOK).
499#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(rename_all = "camelCase")]
501pub struct ProviderConfig {
502    #[serde(skip_serializing_if = "Option::is_none")]
503    #[serde(rename = "type")]
504    pub provider_type: Option<String>,
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub wire_api: Option<String>,
507    pub base_url: String,
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub api_key: Option<String>,
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub bearer_token: Option<String>,
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub azure: Option<AzureProviderOptions>,
514}
515
516// ============================================================================
517// Reasoning Effort
518// ============================================================================
519
520/// Valid reasoning effort levels for models that support it.
521#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
522#[serde(rename_all = "lowercase")]
523pub enum ReasoningEffort {
524    Low,
525    Medium,
526    High,
527    Xhigh,
528}
529
530// ============================================================================
531// Session Configuration
532// ============================================================================
533
534/// Configuration for creating a session.
535///
536/// This struct holds the parameters sent to the server via `session.create`.
537/// Tool handlers, permission handlers, user input handlers, and hooks
538/// are registered separately on the Rust side and are not serialized.
539#[derive(Debug, Clone, Default, Serialize, Deserialize)]
540#[serde(rename_all = "camelCase")]
541pub struct SessionConfig {
542    #[serde(skip_serializing_if = "Option::is_none")]
543    pub session_id: Option<String>,
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub model: Option<String>,
546    #[serde(skip_serializing_if = "Option::is_none")]
547    pub reasoning_effort: Option<ReasoningEffort>,
548    #[serde(skip_serializing_if = "Option::is_none")]
549    pub config_dir: Option<String>,
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub tools: Option<Vec<ToolDefinition>>,
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub system_message: Option<SystemMessageConfig>,
554    #[serde(skip_serializing_if = "Option::is_none")]
555    pub available_tools: Option<Vec<String>>,
556    #[serde(skip_serializing_if = "Option::is_none")]
557    pub excluded_tools: Option<Vec<String>>,
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub provider: Option<ProviderConfig>,
560    #[serde(skip_serializing_if = "Option::is_none")]
561    pub working_directory: Option<String>,
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub streaming: Option<bool>,
564    /// Include sub-agent streaming events in the event stream. Defaults to true.
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub include_sub_agent_streaming_events: Option<bool>,
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub mcp_servers: Option<HashMap<String, McpServerConfig>>,
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub custom_agents: Option<Vec<CustomAgentConfig>>,
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub skill_directories: Option<Vec<String>>,
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub disabled_skills: Option<Vec<String>>,
575    #[serde(skip_serializing_if = "Option::is_none")]
576    pub infinite_sessions: Option<InfiniteSessionConfig>,
577    /// Per-property overrides for model capabilities, deep-merged over runtime defaults.
578    #[serde(skip_serializing_if = "Option::is_none")]
579    pub model_capabilities: Option<HashMap<String, serde_json::Value>>,
580    /// When true, automatically discovers MCP server configurations from the working directory. Defaults to false.
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub enable_config_discovery: Option<bool>,
583    /// GitHub token for authentication. When set on session config, overrides the client-level token for this session only.
584    #[serde(skip_serializing_if = "Option::is_none")]
585    pub github_token: Option<String>,
586    /// Set by the SDK based on whether handlers are registered (not user-set).
587    #[serde(skip_serializing_if = "Option::is_none")]
588    pub request_permission: Option<bool>,
589    /// Set by the SDK based on whether handlers are registered (not user-set).
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub request_user_input: Option<bool>,
592    /// Set by the SDK based on whether hooks are registered (not user-set).
593    #[serde(skip_serializing_if = "Option::is_none")]
594    pub hooks: Option<bool>,
595    /// Slash commands registered for this session (not serialized).
596    #[serde(skip)]
597    pub commands: Option<Vec<CommandDefinition>>,
598    /// Handler for elicitation requests from the server (not serialized).
599    #[serde(skip)]
600    pub on_elicitation_request: Option<ElicitationHandlerFn>,
601}
602
603// ============================================================================
604// Commands
605// ============================================================================
606
607/// Context for a slash-command invocation.
608#[derive(Debug, Clone, Serialize, Deserialize)]
609#[serde(rename_all = "camelCase")]
610pub struct CommandContext {
611    pub session_id: String,
612    pub command: String,
613    pub command_name: String,
614    pub args: String,
615}
616
617/// Clonable, debuggable wrapper for command handler functions.
618#[derive(Clone)]
619pub struct CommandHandlerFn(
620    pub Arc<dyn Fn(CommandContext) -> Result<(), Box<dyn std::error::Error + Send + Sync>> + Send + Sync>,
621);
622
623impl std::fmt::Debug for CommandHandlerFn {
624    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625        f.write_str("<command_handler>")
626    }
627}
628
629/// Definition of a slash command registered with the session.
630#[derive(Debug, Clone)]
631pub struct CommandDefinition {
632    /// Command name (without leading /).
633    pub name: String,
634    /// Human-readable description shown in command completion UI.
635    pub description: Option<String>,
636    /// Handler invoked when the command is executed.
637    pub handler: CommandHandlerFn,
638}
639
640// ============================================================================
641// UI Elicitation
642// ============================================================================
643
644/// Context for an elicitation request from the server.
645#[derive(Debug, Clone, Serialize, Deserialize)]
646#[serde(rename_all = "camelCase")]
647pub struct ElicitationContext {
648    pub session_id: String,
649    pub message: String,
650    #[serde(skip_serializing_if = "Option::is_none")]
651    pub requested_schema: Option<HashMap<String, serde_json::Value>>,
652    #[serde(skip_serializing_if = "Option::is_none")]
653    pub mode: Option<String>,
654    #[serde(skip_serializing_if = "Option::is_none")]
655    pub elicitation_source: Option<String>,
656    #[serde(skip_serializing_if = "Option::is_none")]
657    pub url: Option<String>,
658}
659
660/// Result returned from an elicitation handler.
661#[derive(Debug, Clone, Serialize, Deserialize)]
662#[serde(rename_all = "camelCase")]
663pub struct ElicitationResult {
664    /// User action: "accept", "decline", or "cancel".
665    pub action: String,
666    /// Form values submitted by the user (present when action is "accept").
667    #[serde(skip_serializing_if = "Option::is_none")]
668    pub content: Option<HashMap<String, serde_json::Value>>,
669}
670
671/// Clonable, debuggable wrapper for elicitation handler functions.
672#[derive(Clone)]
673pub struct ElicitationHandlerFn(
674    pub Arc<dyn Fn(ElicitationContext) -> Result<ElicitationResult, Box<dyn std::error::Error + Send + Sync>> + Send + Sync>,
675);
676
677impl std::fmt::Debug for ElicitationHandlerFn {
678    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
679        f.write_str("<elicitation_handler>")
680    }
681}
682
683/// Configuration for resuming a session.
684#[derive(Debug, Clone, Default, Serialize, Deserialize)]
685#[serde(rename_all = "camelCase")]
686pub struct ResumeSessionConfig {
687    pub session_id: String,
688    #[serde(skip_serializing_if = "Option::is_none")]
689    pub model: Option<String>,
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub reasoning_effort: Option<ReasoningEffort>,
692    #[serde(skip_serializing_if = "Option::is_none")]
693    pub config_dir: Option<String>,
694    #[serde(skip_serializing_if = "Option::is_none")]
695    pub tools: Option<Vec<ToolDefinition>>,
696    #[serde(skip_serializing_if = "Option::is_none")]
697    pub system_message: Option<SystemMessageConfig>,
698    #[serde(skip_serializing_if = "Option::is_none")]
699    pub available_tools: Option<Vec<String>>,
700    #[serde(skip_serializing_if = "Option::is_none")]
701    pub excluded_tools: Option<Vec<String>>,
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub provider: Option<ProviderConfig>,
704    #[serde(skip_serializing_if = "Option::is_none")]
705    pub working_directory: Option<String>,
706    #[serde(skip_serializing_if = "Option::is_none")]
707    pub streaming: Option<bool>,
708    /// Include sub-agent streaming events in the event stream. Defaults to true.
709    #[serde(skip_serializing_if = "Option::is_none")]
710    pub include_sub_agent_streaming_events: Option<bool>,
711    #[serde(skip_serializing_if = "Option::is_none")]
712    pub mcp_servers: Option<HashMap<String, McpServerConfig>>,
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub custom_agents: Option<Vec<CustomAgentConfig>>,
715    #[serde(skip_serializing_if = "Option::is_none")]
716    pub skill_directories: Option<Vec<String>>,
717    #[serde(skip_serializing_if = "Option::is_none")]
718    pub disabled_skills: Option<Vec<String>>,
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub infinite_sessions: Option<InfiniteSessionConfig>,
721    /// Per-property overrides for model capabilities, deep-merged over runtime defaults.
722    #[serde(skip_serializing_if = "Option::is_none")]
723    pub model_capabilities: Option<HashMap<String, serde_json::Value>>,
724    /// When true, automatically discovers MCP server configurations from the working directory. Defaults to false.
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub enable_config_discovery: Option<bool>,
727    /// GitHub token for authentication. When set on session config, overrides the client-level token for this session only.
728    #[serde(skip_serializing_if = "Option::is_none")]
729    pub github_token: Option<String>,
730    #[serde(skip_serializing_if = "Option::is_none")]
731    pub disable_resume: Option<bool>,
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub request_permission: Option<bool>,
734    #[serde(skip_serializing_if = "Option::is_none")]
735    pub request_user_input: Option<bool>,
736    #[serde(skip_serializing_if = "Option::is_none")]
737    pub hooks: Option<bool>,
738}
739
740// ============================================================================
741// Message Types
742// ============================================================================
743
744/// Attachment for a file.
745#[derive(Debug, Clone, Serialize, Deserialize)]
746#[serde(rename_all = "camelCase")]
747pub struct FileAttachment {
748    pub path: String,
749    #[serde(skip_serializing_if = "Option::is_none")]
750    pub display_name: Option<String>,
751}
752
753/// Attachment for a directory.
754#[derive(Debug, Clone, Serialize, Deserialize)]
755#[serde(rename_all = "camelCase")]
756pub struct DirectoryAttachment {
757    pub path: String,
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub display_name: Option<String>,
760}
761
762/// Selection range within a file.
763#[derive(Debug, Clone, Serialize, Deserialize)]
764pub struct SelectionRange {
765    pub start: Position,
766    pub end: Position,
767}
768
769/// A position in a file (line and character).
770#[derive(Debug, Clone, Serialize, Deserialize)]
771pub struct Position {
772    pub line: u32,
773    pub character: u32,
774}
775
776/// Attachment for a code selection.
777#[derive(Debug, Clone, Serialize, Deserialize)]
778#[serde(rename_all = "camelCase")]
779pub struct SelectionAttachment {
780    pub file_path: String,
781    pub display_name: String,
782    #[serde(skip_serializing_if = "Option::is_none")]
783    pub selection: Option<SelectionRange>,
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub text: Option<String>,
786}
787
788/// Union type for message attachments.
789#[derive(Debug, Clone, Serialize, Deserialize)]
790#[serde(tag = "type", rename_all = "camelCase")]
791pub enum Attachment {
792    File(FileAttachment),
793    Directory(DirectoryAttachment),
794    Selection(SelectionAttachment),
795}
796
797/// Response format for message responses.
798#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
799pub enum ResponseFormat {
800    #[serde(rename = "text")]
801    Text,
802    #[serde(rename = "image")]
803    Image,
804    #[serde(rename = "json_object")]
805    JsonObject,
806}
807
808/// Options for image generation.
809#[derive(Debug, Clone, Serialize, Deserialize)]
810#[serde(rename_all = "camelCase")]
811pub struct ImageOptions {
812    #[serde(skip_serializing_if = "Option::is_none")]
813    pub size: Option<String>,
814    #[serde(skip_serializing_if = "Option::is_none")]
815    pub quality: Option<String>,
816    #[serde(skip_serializing_if = "Option::is_none")]
817    pub style: Option<String>,
818}
819
820/// Image data from an assistant image response.
821#[derive(Debug, Clone, Serialize, Deserialize)]
822#[serde(rename_all = "camelCase")]
823pub struct AssistantImageData {
824    pub format: String,
825    pub base64: String,
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub url: Option<String>,
828    #[serde(skip_serializing_if = "Option::is_none")]
829    pub revised_prompt: Option<String>,
830    pub width: u32,
831    pub height: u32,
832}
833
834/// A content block in a mixed text+image response.
835#[derive(Debug, Clone, Serialize, Deserialize)]
836#[serde(tag = "type", rename_all = "camelCase")]
837pub enum ContentBlock {
838    #[serde(rename = "text")]
839    Text { text: String },
840    #[serde(rename = "image")]
841    Image { image: AssistantImageData },
842}
843
844/// Options for sending a message to a session.
845#[derive(Debug, Clone, Serialize, Deserialize)]
846#[serde(rename_all = "camelCase")]
847pub struct MessageOptions {
848    pub prompt: String,
849    #[serde(skip_serializing_if = "Option::is_none")]
850    pub attachments: Option<Vec<Attachment>>,
851    #[serde(skip_serializing_if = "Option::is_none")]
852    pub mode: Option<String>,
853    #[serde(skip_serializing_if = "Option::is_none")]
854    pub response_format: Option<ResponseFormat>,
855    #[serde(skip_serializing_if = "Option::is_none")]
856    pub image_options: Option<ImageOptions>,
857    /// Custom HTTP headers to include in outbound model requests for this turn.
858    #[serde(skip_serializing_if = "Option::is_none")]
859    pub request_headers: Option<HashMap<String, String>>,
860}
861
862// ============================================================================
863// Session Event Types
864// ============================================================================
865
866/// A session event received from the server.
867///
868/// Events are delivered via `session.event` JSON-RPC notifications.
869/// The `event_type` field determines which variant's `data` is populated.
870#[derive(Debug, Clone, Serialize, Deserialize)]
871pub struct SessionEvent {
872    pub id: String,
873    pub timestamp: String,
874    #[serde(rename = "parentId")]
875    pub parent_id: Option<String>,
876    #[serde(default)]
877    pub ephemeral: bool,
878    #[serde(rename = "type")]
879    pub event_type: String,
880    /// The event data, varying by event_type. Use helper methods or
881    /// deserialize into specific types based on event_type.
882    pub data: serde_json::Value,
883}
884
885impl SessionEvent {
886    /// Returns true if this is an `assistant.message` event.
887    pub fn is_assistant_message(&self) -> bool {
888        self.event_type == "assistant.message"
889    }
890
891    /// Returns true if this is a `session.idle` event.
892    pub fn is_session_idle(&self) -> bool {
893        self.event_type == "session.idle"
894    }
895
896    /// Returns true if this is a `session.error` event.
897    pub fn is_session_error(&self) -> bool {
898        self.event_type == "session.error"
899    }
900
901    /// Attempts to extract the assistant message content.
902    /// Returns `None` if this is not an `assistant.message` event.
903    pub fn assistant_message_content(&self) -> Option<&str> {
904        if self.is_assistant_message() {
905            self.data.get("content").and_then(|v| v.as_str())
906        } else {
907            None
908        }
909    }
910
911    /// Attempts to extract the error message from a session.error event.
912    pub fn error_message(&self) -> Option<&str> {
913        if self.is_session_error() {
914            self.data.get("message").and_then(|v| v.as_str())
915        } else {
916            None
917        }
918    }
919
920    /// Attempts to extract the error stack from a session.error event.
921    pub fn error_stack(&self) -> Option<&str> {
922        if self.is_session_error() {
923            self.data.get("stack").and_then(|v| v.as_str())
924        } else {
925            None
926        }
927    }
928}
929
930// ============================================================================
931// Ping Response
932// ============================================================================
933
934/// Response from a ping request.
935#[derive(Debug, Clone, Serialize, Deserialize)]
936#[serde(rename_all = "camelCase")]
937pub struct PingResponse {
938    pub message: String,
939    pub timestamp: f64,
940    #[serde(skip_serializing_if = "Option::is_none")]
941    pub protocol_version: Option<u32>,
942}
943
944// ============================================================================
945// Status Responses
946// ============================================================================
947
948/// Response from status.get.
949#[derive(Debug, Clone, Serialize, Deserialize)]
950#[serde(rename_all = "camelCase")]
951pub struct GetStatusResponse {
952    pub version: String,
953    pub protocol_version: u32,
954}
955
956/// Response from auth.getStatus.
957#[derive(Debug, Clone, Serialize, Deserialize)]
958#[serde(rename_all = "camelCase")]
959pub struct GetAuthStatusResponse {
960    pub is_authenticated: bool,
961    #[serde(skip_serializing_if = "Option::is_none")]
962    pub auth_type: Option<String>,
963    #[serde(skip_serializing_if = "Option::is_none")]
964    pub host: Option<String>,
965    #[serde(skip_serializing_if = "Option::is_none")]
966    pub login: Option<String>,
967    #[serde(skip_serializing_if = "Option::is_none")]
968    pub status_message: Option<String>,
969}
970
971// ============================================================================
972// Model Types
973// ============================================================================
974
975/// Vision capabilities for a model.
976#[derive(Debug, Clone, Serialize, Deserialize)]
977#[serde(rename_all = "camelCase")]
978pub struct VisionLimits {
979    pub supported_media_types: Vec<String>,
980    pub max_prompt_images: u32,
981    pub max_prompt_image_size: u64,
982}
983
984/// Model capability limits.
985#[derive(Debug, Clone, Serialize, Deserialize)]
986#[serde(rename_all = "camelCase")]
987pub struct ModelLimits {
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub max_prompt_tokens: Option<u64>,
990    pub max_context_window_tokens: u64,
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub vision: Option<VisionLimits>,
993}
994
995/// What a model supports.
996#[derive(Debug, Clone, Serialize, Deserialize)]
997#[serde(rename_all = "camelCase")]
998pub struct ModelSupports {
999    pub vision: bool,
1000    pub reasoning_effort: bool,
1001}
1002
1003/// Model capabilities and limits.
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub struct ModelCapabilities {
1006    pub supports: ModelSupports,
1007    pub limits: ModelLimits,
1008}
1009
1010/// Model policy state.
1011#[derive(Debug, Clone, Serialize, Deserialize)]
1012pub struct ModelPolicy {
1013    pub state: String,
1014    pub terms: String,
1015}
1016
1017/// Model billing information.
1018#[derive(Debug, Clone, Serialize, Deserialize)]
1019pub struct ModelBilling {
1020    pub multiplier: f64,
1021}
1022
1023/// Information about an available model.
1024#[derive(Debug, Clone, Serialize, Deserialize)]
1025#[serde(rename_all = "camelCase")]
1026pub struct ModelInfo {
1027    pub id: String,
1028    pub name: String,
1029    pub capabilities: ModelCapabilities,
1030    #[serde(skip_serializing_if = "Option::is_none")]
1031    pub policy: Option<ModelPolicy>,
1032    #[serde(skip_serializing_if = "Option::is_none")]
1033    pub billing: Option<ModelBilling>,
1034    #[serde(skip_serializing_if = "Option::is_none")]
1035    pub supported_reasoning_efforts: Option<Vec<ReasoningEffort>>,
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub default_reasoning_effort: Option<ReasoningEffort>,
1038}
1039
1040// ============================================================================
1041// Session Metadata
1042// ============================================================================
1043
1044/// Metadata about a session.
1045#[derive(Debug, Clone, Serialize, Deserialize)]
1046#[serde(rename_all = "camelCase")]
1047pub struct SessionMetadata {
1048    pub session_id: String,
1049    pub start_time: String,
1050    pub modified_time: String,
1051    #[serde(skip_serializing_if = "Option::is_none")]
1052    pub summary: Option<String>,
1053    pub is_remote: bool,
1054}
1055
1056// ============================================================================
1057// Session Lifecycle Events
1058// ============================================================================
1059
1060/// Types of session lifecycle events.
1061#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
1062pub enum SessionLifecycleEventType {
1063    #[serde(rename = "session.created")]
1064    SessionCreated,
1065    #[serde(rename = "session.deleted")]
1066    SessionDeleted,
1067    #[serde(rename = "session.updated")]
1068    SessionUpdated,
1069    #[serde(rename = "session.foreground")]
1070    SessionForeground,
1071    #[serde(rename = "session.background")]
1072    SessionBackground,
1073}
1074
1075/// Metadata included in lifecycle events.
1076#[derive(Debug, Clone, Serialize, Deserialize)]
1077#[serde(rename_all = "camelCase")]
1078pub struct LifecycleMetadata {
1079    pub start_time: String,
1080    pub modified_time: String,
1081    #[serde(skip_serializing_if = "Option::is_none")]
1082    pub summary: Option<String>,
1083}
1084
1085/// Session lifecycle event notification.
1086#[derive(Debug, Clone, Serialize, Deserialize)]
1087#[serde(rename_all = "camelCase")]
1088pub struct SessionLifecycleEvent {
1089    #[serde(rename = "type")]
1090    pub event_type: SessionLifecycleEventType,
1091    pub session_id: String,
1092    #[serde(skip_serializing_if = "Option::is_none")]
1093    pub metadata: Option<LifecycleMetadata>,
1094}
1095
1096// ============================================================================
1097// Foreground Session Info
1098// ============================================================================
1099
1100/// Information about the foreground session in TUI+server mode.
1101#[derive(Debug, Clone, Serialize, Deserialize)]
1102#[serde(rename_all = "camelCase")]
1103pub struct ForegroundSessionInfo {
1104    #[serde(skip_serializing_if = "Option::is_none")]
1105    pub session_id: Option<String>,
1106    #[serde(skip_serializing_if = "Option::is_none")]
1107    pub workspace_path: Option<String>,
1108}
1109
1110// ============================================================================
1111// Session Filesystem Types
1112// ============================================================================
1113
1114/// Configuration for a custom session filesystem provider.
1115#[derive(Debug, Clone, Serialize, Deserialize)]
1116#[serde(rename_all = "camelCase")]
1117pub struct SessionFsConfig {
1118    pub initial_cwd: String,
1119    pub session_state_path: String,
1120    pub conventions: String,
1121}
1122
1123/// File metadata returned by session filesystem operations.
1124#[derive(Debug, Clone, Serialize, Deserialize)]
1125#[serde(rename_all = "camelCase")]
1126pub struct SessionFsFileInfo {
1127    pub name: String,
1128    pub size: i64,
1129    pub is_directory: bool,
1130    pub is_file: bool,
1131    #[serde(skip_serializing_if = "Option::is_none")]
1132    pub created_at: Option<String>,
1133    #[serde(skip_serializing_if = "Option::is_none")]
1134    pub modified_at: Option<String>,
1135}
1136
1137/// Interface for session filesystem providers.
1138///
1139/// Implementors provide file operations scoped to a session. Methods use
1140/// idiomatic Rust error handling: return an error for failures.
1141pub trait SessionFsProvider: Send + Sync {
1142    fn read_file(&self, session_id: &str, path: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>>;
1143    fn write_file(&self, session_id: &str, path: &str, content: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
1144    fn append_file(&self, session_id: &str, path: &str, content: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
1145    fn exists(&self, session_id: &str, path: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>>;
1146    fn stat(&self, session_id: &str, path: &str) -> Result<SessionFsFileInfo, Box<dyn std::error::Error + Send + Sync>>;
1147    fn mkdir(&self, session_id: &str, path: &str, recursive: bool) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
1148    fn readdir(&self, session_id: &str, path: &str) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>>;
1149    fn readdir_with_types(&self, session_id: &str, path: &str) -> Result<Vec<SessionFsFileInfo>, Box<dyn std::error::Error + Send + Sync>>;
1150    fn rm(&self, session_id: &str, path: &str, recursive: bool) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
1151    fn rename(&self, session_id: &str, old_path: &str, new_path: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
1152}
1153
1154// ============================================================================
1155// Client Options
1156// ============================================================================
1157
1158/// Options for creating a CopilotClient.
1159#[derive(Debug, Clone)]
1160pub struct CopilotClientOptions {
1161    /// Path to the CLI executable.
1162    pub cli_path: Option<String>,
1163    /// Extra arguments to pass to the CLI executable.
1164    pub cli_args: Vec<String>,
1165    /// Working directory for the CLI process.
1166    pub cwd: Option<String>,
1167    /// Port for the CLI server (TCP mode only).
1168    pub port: u16,
1169    /// Use stdio transport instead of TCP (default: true).
1170    pub use_stdio: bool,
1171    /// URL of an existing Copilot CLI server to connect to over TCP.
1172    pub cli_url: Option<String>,
1173    /// Log level for the CLI server.
1174    pub log_level: String,
1175    /// Auto-start the CLI server on first use (default: true).
1176    pub auto_start: bool,
1177    /// Auto-restart the CLI server if it crashes (default: true).
1178    pub auto_restart: bool,
1179    /// Environment variables to pass to the CLI process.
1180    pub env: Option<HashMap<String, String>>,
1181    /// GitHub token for authentication.
1182    pub github_token: Option<String>,
1183    /// Whether to use the logged-in user for authentication.
1184    pub use_logged_in_user: Option<bool>,
1185    /// Server-wide idle timeout for sessions in seconds. Sessions without activity for this duration are automatically cleaned up.
1186    pub session_idle_timeout_seconds: Option<u64>,
1187    /// Custom session filesystem configuration.
1188    pub session_fs: Option<SessionFsConfig>,
1189}
1190
1191impl Default for CopilotClientOptions {
1192    fn default() -> Self {
1193        Self {
1194            cli_path: None,
1195            cli_args: Vec::new(),
1196            cwd: None,
1197            port: 0,
1198            use_stdio: true,
1199            cli_url: None,
1200            log_level: "info".to_string(),
1201            auto_start: true,
1202            auto_restart: true,
1203            env: None,
1204            github_token: None,
1205            use_logged_in_user: None,
1206            session_idle_timeout_seconds: None,
1207            session_fs: None,
1208        }
1209    }
1210}