Skip to main content

cc_sdk/
types.rs

1//! Type definitions for the Claude Code SDK
2//!
3//! This module contains all the core types used throughout the SDK,
4//! including messages, configuration options, and content blocks.
5
6#![allow(missing_docs)]
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use async_trait::async_trait;
12use std::io::Write;
13use tokio::sync::Mutex;
14
15/// Permission mode for tool execution
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[non_exhaustive]
18#[serde(rename_all = "camelCase")]
19pub enum PermissionMode {
20    /// Default mode - CLI prompts for dangerous tools
21    Default,
22    /// Auto-accept file edits
23    AcceptEdits,
24    /// Plan mode - for planning tasks
25    Plan,
26    /// Allow all tools without prompting (use with caution)
27    BypassPermissions,
28    /// Don't ask — reject tools that aren't pre-approved (no interactive prompts)
29    DontAsk,
30}
31
32// ============================================================================
33// Effort Level (Python SDK parity)
34// ============================================================================
35
36/// Effort level for Claude's reasoning depth
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "lowercase")]
39pub enum Effort {
40    /// Minimal reasoning effort
41    Low,
42    /// Standard reasoning effort
43    Medium,
44    /// Higher reasoning effort
45    High,
46    /// Maximum reasoning effort
47    Max,
48}
49
50impl std::fmt::Display for Effort {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            Effort::Low => write!(f, "low"),
54            Effort::Medium => write!(f, "medium"),
55            Effort::High => write!(f, "high"),
56            Effort::Max => write!(f, "max"),
57        }
58    }
59}
60
61// ============================================================================
62// Rate Limit Types (Python SDK parity)
63// ============================================================================
64
65/// Rate limit status
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "snake_case")]
68pub enum RateLimitStatus {
69    /// Request allowed
70    Allowed,
71    /// Request allowed but approaching limit
72    AllowedWarning,
73    /// Request rejected due to rate limit
74    Rejected,
75}
76
77/// Rate limit type
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub enum RateLimitType {
80    /// 5-hour rolling window
81    #[serde(rename = "five_hour")]
82    FiveHour,
83    /// 7-day rolling window
84    #[serde(rename = "seven_day")]
85    SevenDay,
86    /// 7-day Opus-specific window
87    #[serde(rename = "seven_day_opus")]
88    SevenDayOpus,
89    /// 7-day Sonnet-specific window
90    #[serde(rename = "seven_day_sonnet")]
91    SevenDaySonnet,
92    /// Overage window
93    #[serde(rename = "overage")]
94    Overage,
95}
96
97/// Rate limit information
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
99pub struct RateLimitInfo {
100    /// Current rate limit status
101    pub status: RateLimitStatus,
102    /// When the rate limit resets (ISO 8601 or epoch)
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub resets_at: Option<String>,
105    /// Type of rate limit
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub rate_limit_type: Option<RateLimitType>,
108    /// Current utilization percentage (0.0 - 1.0)
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub utilization: Option<f64>,
111    /// Overage status
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub overage_status: Option<String>,
114    /// When overage resets
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub overage_resets_at: Option<String>,
117    /// Reason overage is disabled
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub overage_disabled_reason: Option<String>,
120    /// Raw rate limit data
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub raw: Option<serde_json::Value>,
123}
124
125// ============================================================================
126// Assistant Message Error (Python SDK parity)
127// ============================================================================
128
129/// Error types for assistant messages
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "snake_case")]
132pub enum AssistantMessageError {
133    /// Authentication failed
134    AuthenticationFailed,
135    /// Billing error
136    BillingError,
137    /// Rate limited
138    RateLimit,
139    /// Invalid request
140    InvalidRequest,
141    /// Server error
142    ServerError,
143    /// Unknown error
144    Unknown,
145}
146
147// ============================================================================
148// SDK Beta Features (matching Python SDK v0.1.12+)
149// ============================================================================
150
151/// SDK Beta features - see https://docs.anthropic.com/en/api/beta-headers
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub enum SdkBeta {
154    /// Extended context window (1M tokens)
155    #[serde(rename = "context-1m-2025-08-07")]
156    Context1M,
157}
158
159impl std::fmt::Display for SdkBeta {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        match self {
162            SdkBeta::Context1M => write!(f, "context-1m-2025-08-07"),
163        }
164    }
165}
166
167// ============================================================================
168// Tools Configuration (matching Python SDK v0.1.12+)
169// ============================================================================
170
171/// Tools configuration for controlling available tools
172///
173/// This controls the base set of tools available to Claude, distinct from
174/// `allowed_tools` which only controls auto-approval permissions.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(untagged)]
177pub enum ToolsConfig {
178    /// List of specific tool names to enable
179    /// Example: `["Read", "Edit", "Bash"]`
180    List(Vec<String>),
181    /// Preset-based tools configuration
182    Preset(ToolsPreset),
183}
184
185/// Tools preset configuration
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ToolsPreset {
188    /// Type identifier (always "preset")
189    #[serde(rename = "type")]
190    pub preset_type: String,
191    /// Preset name (e.g., "claude_code")
192    pub preset: String,
193}
194
195impl ToolsConfig {
196    /// Create a new tools list
197    pub fn list(tools: Vec<String>) -> Self {
198        ToolsConfig::List(tools)
199    }
200
201    /// Create an empty tools list (disables all built-in tools)
202    pub fn none() -> Self {
203        ToolsConfig::List(vec![])
204    }
205
206    /// Create the claude_code preset
207    pub fn claude_code_preset() -> Self {
208        ToolsConfig::Preset(ToolsPreset {
209            preset_type: "preset".to_string(),
210            preset: "claude_code".to_string(),
211        })
212    }
213}
214
215// ============================================================================
216// Sandbox Configuration (matching Python SDK)
217// ============================================================================
218
219/// Network configuration for sandbox
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct SandboxNetworkConfig {
223    /// Unix socket paths accessible in sandbox (e.g., SSH agents)
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub allow_unix_sockets: Option<Vec<String>>,
226    /// Allow all Unix sockets (less secure)
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub allow_all_unix_sockets: Option<bool>,
229    /// Allow binding to localhost ports (macOS only)
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub allow_local_binding: Option<bool>,
232    /// HTTP proxy port if bringing your own proxy
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub http_proxy_port: Option<u16>,
235    /// SOCKS5 proxy port if bringing your own proxy
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub socks_proxy_port: Option<u16>,
238}
239
240/// Violations to ignore in sandbox
241#[derive(Debug, Clone, Default, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct SandboxIgnoreViolations {
244    /// File paths for which violations should be ignored
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub file: Option<Vec<String>>,
247    /// Network hosts for which violations should be ignored
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub network: Option<Vec<String>>,
250}
251
252/// Sandbox settings configuration
253///
254/// Controls how Claude Code sandboxes bash commands for filesystem
255/// and network isolation.
256#[derive(Debug, Clone, Default, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct SandboxSettings {
259    /// Enable bash sandboxing (macOS/Linux only)
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub enabled: Option<bool>,
262    /// Auto-approve bash commands when sandboxed (default: true)
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub auto_allow_bash_if_sandboxed: Option<bool>,
265    /// Commands that should run outside the sandbox (e.g., ["git", "docker"])
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub excluded_commands: Option<Vec<String>>,
268    /// Allow commands to bypass sandbox via dangerouslyDisableSandbox
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub allow_unsandboxed_commands: Option<bool>,
271    /// Network configuration for sandbox
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub network: Option<SandboxNetworkConfig>,
274    /// Violations to ignore
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub ignore_violations: Option<SandboxIgnoreViolations>,
277    /// Enable weaker sandbox for unprivileged Docker environments (Linux only)
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub enable_weaker_nested_sandbox: Option<bool>,
280}
281
282// ============================================================================
283// Plugin Configuration (matching Python SDK v0.1.5+)
284// ============================================================================
285
286/// SDK plugin configuration
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(tag = "type", rename_all = "lowercase")]
289pub enum SdkPluginConfig {
290    /// Local plugin loaded from filesystem path
291    Local {
292        /// Path to the plugin directory
293        path: String,
294    },
295}
296
297impl Default for PermissionMode {
298    fn default() -> Self {
299        Self::Default
300    }
301}
302
303/// Control protocol format for sending messages
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
305pub enum ControlProtocolFormat {
306    /// Legacy format: {"type":"sdk_control_request","request":{...}}
307    Legacy,
308    /// New format: {"type":"control","control":{...}}
309    Control,
310    /// Auto-detect based on CLI capabilities (default to Legacy for compatibility)
311    Auto,
312}
313
314impl Default for ControlProtocolFormat {
315    fn default() -> Self {
316        // Default to Legacy for maximum compatibility
317        Self::Legacy
318    }
319}
320
321// ============================================================================
322// MCP Runtime Status Types (Python SDK parity)
323// ============================================================================
324
325/// MCP server connection status
326#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
327#[serde(rename_all = "kebab-case")]
328pub enum McpConnectionStatus {
329    /// Server is connected
330    Connected,
331    /// Connection failed
332    Failed,
333    /// Server needs authentication
334    NeedsAuth,
335    /// Connection pending
336    Pending,
337    /// Server is disabled
338    Disabled,
339}
340
341/// MCP tool annotations (capabilities/restrictions)
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct McpToolAnnotations {
344    /// Whether the tool is read-only
345    #[serde(default, skip_serializing_if = "Option::is_none")]
346    pub read_only: Option<bool>,
347    /// Whether the tool is destructive
348    #[serde(default, skip_serializing_if = "Option::is_none")]
349    pub destructive: Option<bool>,
350    /// Whether the tool makes open-world requests
351    #[serde(default, skip_serializing_if = "Option::is_none")]
352    pub open_world: Option<bool>,
353}
354
355/// MCP tool information
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct McpToolInfo {
358    /// Tool name
359    pub name: String,
360    /// Tool description
361    #[serde(default, skip_serializing_if = "Option::is_none")]
362    pub description: Option<String>,
363    /// Tool annotations
364    #[serde(default, skip_serializing_if = "Option::is_none")]
365    pub annotations: Option<McpToolAnnotations>,
366}
367
368/// MCP server info
369#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct McpServerInfo {
371    /// Server name
372    pub name: String,
373    /// Server version
374    pub version: String,
375}
376
377/// MCP server runtime status
378#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct McpServerStatus {
380    /// Server name
381    pub name: String,
382    /// Connection status
383    pub status: McpConnectionStatus,
384    /// Server info (when connected)
385    #[serde(default, skip_serializing_if = "Option::is_none")]
386    pub server_info: Option<McpServerInfo>,
387    /// Error message (when failed)
388    #[serde(default, skip_serializing_if = "Option::is_none")]
389    pub error: Option<String>,
390    /// Available tools
391    #[serde(default, skip_serializing_if = "Option::is_none")]
392    pub tools: Option<Vec<McpToolInfo>>,
393}
394
395// ============================================================================
396// Thinking Configuration (Python SDK parity)
397// ============================================================================
398
399/// Thinking configuration for Claude's reasoning
400#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
401#[serde(tag = "type", rename_all = "lowercase")]
402pub enum ThinkingConfig {
403    /// Adaptive thinking — Claude decides how much to think
404    Adaptive,
405    /// Enabled with a specific token budget
406    Enabled {
407        /// Maximum tokens for thinking
408        budget_tokens: i32,
409    },
410    /// Thinking disabled
411    Disabled,
412}
413
414/// MCP (Model Context Protocol) server configuration
415#[derive(Clone)]
416pub enum McpServerConfig {
417    /// Standard I/O based MCP server
418    Stdio {
419        /// Command to execute
420        command: String,
421        /// Command arguments
422        args: Option<Vec<String>>,
423        /// Environment variables
424        env: Option<HashMap<String, String>>,
425    },
426    /// Server-Sent Events based MCP server
427    Sse {
428        /// Server URL
429        url: String,
430        /// HTTP headers
431        headers: Option<HashMap<String, String>>,
432    },
433    /// HTTP-based MCP server
434    Http {
435        /// Server URL
436        url: String,
437        /// HTTP headers
438        headers: Option<HashMap<String, String>>,
439    },
440    /// SDK MCP server (in-process)
441    Sdk {
442        /// Server name
443        name: String,
444        /// Server instance
445        instance: Arc<dyn std::any::Any + Send + Sync>,
446    },
447}
448
449impl std::fmt::Debug for McpServerConfig {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        match self {
452            Self::Stdio { command, args, env } => f
453                .debug_struct("Stdio")
454                .field("command", command)
455                .field("args", args)
456                .field("env", env)
457                .finish(),
458            Self::Sse { url, headers } => f
459                .debug_struct("Sse")
460                .field("url", url)
461                .field("headers", headers)
462                .finish(),
463            Self::Http { url, headers } => f
464                .debug_struct("Http")
465                .field("url", url)
466                .field("headers", headers)
467                .finish(),
468            Self::Sdk { name, .. } => f
469                .debug_struct("Sdk")
470                .field("name", name)
471                .field("instance", &"<Arc<dyn Any>>")
472                .finish(),
473        }
474    }
475}
476
477impl Serialize for McpServerConfig {
478    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
479    where
480        S: serde::Serializer,
481    {
482        use serde::ser::SerializeMap;
483        let mut map = serializer.serialize_map(None)?;
484
485        match self {
486            Self::Stdio { command, args, env } => {
487                map.serialize_entry("type", "stdio")?;
488                map.serialize_entry("command", command)?;
489                if let Some(args) = args {
490                    map.serialize_entry("args", args)?;
491                }
492                if let Some(env) = env {
493                    map.serialize_entry("env", env)?;
494                }
495            }
496            Self::Sse { url, headers } => {
497                map.serialize_entry("type", "sse")?;
498                map.serialize_entry("url", url)?;
499                if let Some(headers) = headers {
500                    map.serialize_entry("headers", headers)?;
501                }
502            }
503            Self::Http { url, headers } => {
504                map.serialize_entry("type", "http")?;
505                map.serialize_entry("url", url)?;
506                if let Some(headers) = headers {
507                    map.serialize_entry("headers", headers)?;
508                }
509            }
510            Self::Sdk { name, .. } => {
511                map.serialize_entry("type", "sdk")?;
512                map.serialize_entry("name", name)?;
513            }
514        }
515
516        map.end()
517    }
518}
519
520impl<'de> Deserialize<'de> for McpServerConfig {
521    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
522    where
523        D: serde::Deserializer<'de>,
524    {
525        #[derive(Deserialize)]
526        #[serde(tag = "type", rename_all = "lowercase")]
527        enum McpServerConfigHelper {
528            Stdio {
529                command: String,
530                #[serde(skip_serializing_if = "Option::is_none")]
531                args: Option<Vec<String>>,
532                #[serde(skip_serializing_if = "Option::is_none")]
533                env: Option<HashMap<String, String>>,
534            },
535            Sse {
536                url: String,
537                #[serde(skip_serializing_if = "Option::is_none")]
538                headers: Option<HashMap<String, String>>,
539            },
540            Http {
541                url: String,
542                #[serde(skip_serializing_if = "Option::is_none")]
543                headers: Option<HashMap<String, String>>,
544            },
545        }
546
547        let helper = McpServerConfigHelper::deserialize(deserializer)?;
548        Ok(match helper {
549            McpServerConfigHelper::Stdio { command, args, env } => {
550                McpServerConfig::Stdio { command, args, env }
551            }
552            McpServerConfigHelper::Sse { url, headers } => {
553                McpServerConfig::Sse { url, headers }
554            }
555            McpServerConfigHelper::Http { url, headers } => {
556                McpServerConfig::Http { url, headers }
557            }
558        })
559    }
560}
561
562/// Permission update destination
563#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub enum PermissionUpdateDestination {
566    /// User settings
567    UserSettings,
568    /// Project settings
569    ProjectSettings,
570    /// Local settings
571    LocalSettings,
572    /// Session
573    Session,
574}
575
576/// Permission behavior
577#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub enum PermissionBehavior {
580    /// Allow the action
581    Allow,
582    /// Deny the action
583    Deny,
584    /// Ask the user
585    Ask,
586}
587
588/// Permission rule value
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct PermissionRuleValue {
591    /// Tool name
592    pub tool_name: String,
593    /// Rule content
594    pub rule_content: Option<String>,
595}
596
597/// Permission update type
598#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
599#[serde(rename_all = "camelCase")]
600pub enum PermissionUpdateType {
601    /// Add rules
602    AddRules,
603    /// Replace rules
604    ReplaceRules,
605    /// Remove rules
606    RemoveRules,
607    /// Set mode
608    SetMode,
609    /// Add directories
610    AddDirectories,
611    /// Remove directories
612    RemoveDirectories,
613}
614
615/// Permission update
616#[derive(Debug, Clone, Serialize, Deserialize)]
617#[serde(rename_all = "camelCase")]
618pub struct PermissionUpdate {
619    /// Update type
620    #[serde(rename = "type")]
621    pub update_type: PermissionUpdateType,
622    /// Rules to update
623    #[serde(skip_serializing_if = "Option::is_none")]
624    pub rules: Option<Vec<PermissionRuleValue>>,
625    /// Behavior to set
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub behavior: Option<PermissionBehavior>,
628    /// Mode to set
629    #[serde(skip_serializing_if = "Option::is_none")]
630    pub mode: Option<PermissionMode>,
631    /// Directories to add/remove
632    #[serde(skip_serializing_if = "Option::is_none")]
633    pub directories: Option<Vec<String>>,
634    /// Destination for the update
635    #[serde(skip_serializing_if = "Option::is_none")]
636    pub destination: Option<PermissionUpdateDestination>,
637}
638
639/// Tool permission context
640#[derive(Debug, Clone)]
641pub struct ToolPermissionContext {
642    /// Abort signal (future support)
643    pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
644    /// Permission suggestions from CLI
645    pub suggestions: Vec<PermissionUpdate>,
646}
647
648/// Permission result - Allow
649#[derive(Debug, Clone)]
650pub struct PermissionResultAllow {
651    /// Updated input parameters
652    pub updated_input: Option<serde_json::Value>,
653    /// Updated permissions
654    pub updated_permissions: Option<Vec<PermissionUpdate>>,
655}
656
657/// Permission result - Deny
658#[derive(Debug, Clone)]
659pub struct PermissionResultDeny {
660    /// Denial message
661    pub message: String,
662    /// Whether to interrupt the conversation
663    pub interrupt: bool,
664}
665
666/// Permission result
667#[derive(Debug, Clone)]
668pub enum PermissionResult {
669    /// Allow the tool use
670    Allow(PermissionResultAllow),
671    /// Deny the tool use
672    Deny(PermissionResultDeny),
673}
674
675/// Tool permission callback trait
676#[async_trait]
677pub trait CanUseTool: Send + Sync {
678    /// Check if a tool can be used
679    async fn can_use_tool(
680        &self,
681        tool_name: &str,
682        input: &serde_json::Value,
683        context: &ToolPermissionContext,
684    ) -> PermissionResult;
685}
686
687/// Hook context
688#[derive(Debug, Clone)]
689pub struct HookContext {
690    /// Abort signal (future support)
691    pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
692}
693
694// ============================================================================
695// Hook Input Types (Strongly-typed hook inputs for type safety)
696// ============================================================================
697
698/// Base hook input fields present across many hook events
699#[derive(Debug, Clone, Serialize, Deserialize)]
700pub struct BaseHookInput {
701    /// Session ID for this conversation
702    pub session_id: String,
703    /// Path to the transcript file
704    pub transcript_path: String,
705    /// Current working directory
706    pub cwd: String,
707    /// Permission mode (optional)
708    #[serde(skip_serializing_if = "Option::is_none")]
709    pub permission_mode: Option<String>,
710}
711
712/// Input data for PreToolUse hook events
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct PreToolUseHookInput {
715    /// Session ID for this conversation
716    pub session_id: String,
717    /// Path to the transcript file
718    pub transcript_path: String,
719    /// Current working directory
720    pub cwd: String,
721    /// Permission mode (optional)
722    #[serde(skip_serializing_if = "Option::is_none")]
723    pub permission_mode: Option<String>,
724    /// Name of the tool being used
725    pub tool_name: String,
726    /// Input parameters for the tool
727    pub tool_input: serde_json::Value,
728    /// Tool use ID
729    pub tool_use_id: String,
730    /// Agent ID (for subagent contexts)
731    #[serde(default, skip_serializing_if = "Option::is_none")]
732    pub agent_id: Option<String>,
733    /// Agent type (for subagent contexts)
734    #[serde(default, skip_serializing_if = "Option::is_none")]
735    pub agent_type: Option<String>,
736}
737
738/// Input data for PostToolUse hook events
739#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct PostToolUseHookInput {
741    /// Session ID for this conversation
742    pub session_id: String,
743    /// Path to the transcript file
744    pub transcript_path: String,
745    /// Current working directory
746    pub cwd: String,
747    /// Permission mode (optional)
748    #[serde(skip_serializing_if = "Option::is_none")]
749    pub permission_mode: Option<String>,
750    /// Name of the tool that was used
751    pub tool_name: String,
752    /// Input parameters that were passed to the tool
753    pub tool_input: serde_json::Value,
754    /// Response from the tool execution
755    pub tool_response: serde_json::Value,
756    /// Tool use ID
757    pub tool_use_id: String,
758    /// Agent ID (for subagent contexts)
759    #[serde(default, skip_serializing_if = "Option::is_none")]
760    pub agent_id: Option<String>,
761    /// Agent type (for subagent contexts)
762    #[serde(default, skip_serializing_if = "Option::is_none")]
763    pub agent_type: Option<String>,
764}
765
766/// Input data for UserPromptSubmit hook events
767#[derive(Debug, Clone, Serialize, Deserialize)]
768pub struct UserPromptSubmitHookInput {
769    /// Session ID for this conversation
770    pub session_id: String,
771    /// Path to the transcript file
772    pub transcript_path: String,
773    /// Current working directory
774    pub cwd: String,
775    /// Permission mode (optional)
776    #[serde(skip_serializing_if = "Option::is_none")]
777    pub permission_mode: Option<String>,
778    /// The prompt submitted by the user
779    pub prompt: String,
780}
781
782/// Input data for Stop hook events
783#[derive(Debug, Clone, Serialize, Deserialize)]
784pub struct StopHookInput {
785    /// Session ID for this conversation
786    pub session_id: String,
787    /// Path to the transcript file
788    pub transcript_path: String,
789    /// Current working directory
790    pub cwd: String,
791    /// Permission mode (optional)
792    #[serde(skip_serializing_if = "Option::is_none")]
793    pub permission_mode: Option<String>,
794    /// Whether stop hook is active
795    pub stop_hook_active: bool,
796}
797
798/// Input data for SubagentStop hook events
799#[derive(Debug, Clone, Serialize, Deserialize)]
800pub struct SubagentStopHookInput {
801    /// Session ID for this conversation
802    pub session_id: String,
803    /// Path to the transcript file
804    pub transcript_path: String,
805    /// Current working directory
806    pub cwd: String,
807    /// Permission mode (optional)
808    #[serde(skip_serializing_if = "Option::is_none")]
809    pub permission_mode: Option<String>,
810    /// Whether stop hook is active
811    pub stop_hook_active: bool,
812    /// Agent ID
813    pub agent_id: String,
814    /// Path to the agent's transcript
815    pub agent_transcript_path: String,
816    /// Agent type
817    pub agent_type: String,
818}
819
820/// Input data for PreCompact hook events
821#[derive(Debug, Clone, Serialize, Deserialize)]
822pub struct PreCompactHookInput {
823    /// Session ID for this conversation
824    pub session_id: String,
825    /// Path to the transcript file
826    pub transcript_path: String,
827    /// Current working directory
828    pub cwd: String,
829    /// Permission mode (optional)
830    #[serde(skip_serializing_if = "Option::is_none")]
831    pub permission_mode: Option<String>,
832    /// Trigger type: "manual" or "auto"
833    pub trigger: String,
834    /// Custom instructions for compaction (optional)
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub custom_instructions: Option<String>,
837}
838
839/// Input data for PostToolUseFailure hook events
840#[derive(Debug, Clone, Serialize, Deserialize)]
841pub struct PostToolUseFailureHookInput {
842    /// Session ID for this conversation
843    pub session_id: String,
844    /// Path to the transcript file
845    pub transcript_path: String,
846    /// Current working directory
847    pub cwd: String,
848    /// Permission mode (optional)
849    #[serde(skip_serializing_if = "Option::is_none")]
850    pub permission_mode: Option<String>,
851    /// Name of the tool that failed
852    pub tool_name: String,
853    /// Input parameters that were passed to the tool
854    pub tool_input: serde_json::Value,
855    /// Tool use ID
856    pub tool_use_id: String,
857    /// Error message from the tool
858    pub error: String,
859    /// Whether this failure was due to an interrupt
860    #[serde(skip_serializing_if = "Option::is_none")]
861    pub is_interrupt: Option<bool>,
862    /// Agent ID (for subagent contexts)
863    #[serde(default, skip_serializing_if = "Option::is_none")]
864    pub agent_id: Option<String>,
865    /// Agent type (for subagent contexts)
866    #[serde(default, skip_serializing_if = "Option::is_none")]
867    pub agent_type: Option<String>,
868}
869
870/// Input data for Notification hook events
871#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct NotificationHookInput {
873    /// Session ID for this conversation
874    pub session_id: String,
875    /// Path to the transcript file
876    pub transcript_path: String,
877    /// Current working directory
878    pub cwd: String,
879    /// Permission mode (optional)
880    #[serde(skip_serializing_if = "Option::is_none")]
881    pub permission_mode: Option<String>,
882    /// Notification message
883    pub message: String,
884    /// Notification title (optional)
885    #[serde(skip_serializing_if = "Option::is_none")]
886    pub title: Option<String>,
887    /// Notification type
888    pub notification_type: String,
889}
890
891/// Input data for SubagentStart hook events
892#[derive(Debug, Clone, Serialize, Deserialize)]
893pub struct SubagentStartHookInput {
894    /// Session ID for this conversation
895    pub session_id: String,
896    /// Path to the transcript file
897    pub transcript_path: String,
898    /// Current working directory
899    pub cwd: String,
900    /// Permission mode (optional)
901    #[serde(skip_serializing_if = "Option::is_none")]
902    pub permission_mode: Option<String>,
903    /// Agent ID
904    pub agent_id: String,
905    /// Agent type
906    pub agent_type: String,
907}
908
909/// Input data for PermissionRequest hook events
910#[derive(Debug, Clone, Serialize, Deserialize)]
911pub struct PermissionRequestHookInput {
912    /// Session ID for this conversation
913    pub session_id: String,
914    /// Path to the transcript file
915    pub transcript_path: String,
916    /// Current working directory
917    pub cwd: String,
918    /// Permission mode (optional)
919    #[serde(skip_serializing_if = "Option::is_none")]
920    pub permission_mode: Option<String>,
921    /// Name of the tool requesting permission
922    pub tool_name: String,
923    /// Input parameters for the tool
924    pub tool_input: serde_json::Value,
925    /// Permission suggestions (optional)
926    #[serde(skip_serializing_if = "Option::is_none")]
927    pub permission_suggestions: Option<Vec<serde_json::Value>>,
928    /// Agent ID (for subagent contexts)
929    #[serde(default, skip_serializing_if = "Option::is_none")]
930    pub agent_id: Option<String>,
931    /// Agent type (for subagent contexts)
932    #[serde(default, skip_serializing_if = "Option::is_none")]
933    pub agent_type: Option<String>,
934}
935
936/// Union type for all hook inputs (discriminated by hook_event_name)
937#[derive(Debug, Clone, Serialize, Deserialize)]
938#[non_exhaustive]
939#[serde(tag = "hook_event_name")]
940pub enum HookInput {
941    /// PreToolUse hook input
942    #[serde(rename = "PreToolUse")]
943    PreToolUse(PreToolUseHookInput),
944    /// PostToolUse hook input
945    #[serde(rename = "PostToolUse")]
946    PostToolUse(PostToolUseHookInput),
947    /// PostToolUseFailure hook input
948    #[serde(rename = "PostToolUseFailure")]
949    PostToolUseFailure(PostToolUseFailureHookInput),
950    /// UserPromptSubmit hook input
951    #[serde(rename = "UserPromptSubmit")]
952    UserPromptSubmit(UserPromptSubmitHookInput),
953    /// Stop hook input
954    #[serde(rename = "Stop")]
955    Stop(StopHookInput),
956    /// SubagentStop hook input
957    #[serde(rename = "SubagentStop")]
958    SubagentStop(SubagentStopHookInput),
959    /// PreCompact hook input
960    #[serde(rename = "PreCompact")]
961    PreCompact(PreCompactHookInput),
962    /// Notification hook input
963    #[serde(rename = "Notification")]
964    Notification(NotificationHookInput),
965    /// SubagentStart hook input
966    #[serde(rename = "SubagentStart")]
967    SubagentStart(SubagentStartHookInput),
968    /// PermissionRequest hook input
969    #[serde(rename = "PermissionRequest")]
970    PermissionRequest(PermissionRequestHookInput),
971}
972
973// ============================================================================
974// Hook Output Types (Strongly-typed hook outputs for type safety)
975// ============================================================================
976
977/// Async hook output for deferred execution
978///
979/// When a hook returns this output, the hook execution is deferred and
980/// Claude continues without waiting for the hook to complete.
981#[derive(Debug, Clone, Serialize, Deserialize)]
982pub struct AsyncHookJSONOutput {
983    /// Must be true to indicate async execution
984    #[serde(rename = "async")]
985    pub async_: bool,
986    /// Optional timeout in milliseconds for async operation
987    #[serde(skip_serializing_if = "Option::is_none")]
988    #[serde(rename = "asyncTimeout")]
989    pub async_timeout: Option<u32>,
990}
991
992/// Synchronous hook output with control and decision fields
993///
994/// This defines the structure for hook callbacks to control execution and provide
995/// feedback to Claude.
996#[derive(Debug, Clone, Default, Serialize, Deserialize)]
997pub struct SyncHookJSONOutput {
998    // Common control fields
999    /// Whether Claude should proceed after hook execution (default: true)
1000    #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
1001    pub continue_: Option<bool>,
1002    /// Hide stdout from transcript mode (default: false)
1003    #[serde(rename = "suppressOutput", skip_serializing_if = "Option::is_none")]
1004    pub suppress_output: Option<bool>,
1005    /// Message shown when continue is false
1006    #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
1007    pub stop_reason: Option<String>,
1008
1009    // Decision fields
1010    /// Set to "block" to indicate blocking behavior
1011    #[serde(skip_serializing_if = "Option::is_none")]
1012    pub decision: Option<String>, // "block" or "approve" (deprecated)
1013    /// Warning message displayed to the user
1014    #[serde(rename = "systemMessage", skip_serializing_if = "Option::is_none")]
1015    pub system_message: Option<String>,
1016    /// Feedback message for Claude about the decision
1017    #[serde(skip_serializing_if = "Option::is_none")]
1018    pub reason: Option<String>,
1019
1020    // Hook-specific outputs
1021    /// Event-specific controls (e.g., permissionDecision for PreToolUse)
1022    #[serde(rename = "hookSpecificOutput", skip_serializing_if = "Option::is_none")]
1023    pub hook_specific_output: Option<HookSpecificOutput>,
1024}
1025
1026/// Union type for hook outputs
1027#[derive(Debug, Clone, Serialize, Deserialize)]
1028#[serde(untagged)]
1029pub enum HookJSONOutput {
1030    /// Async hook output (deferred execution)
1031    Async(AsyncHookJSONOutput),
1032    /// Sync hook output (immediate execution)
1033    Sync(SyncHookJSONOutput),
1034}
1035
1036/// Hook-specific output for PreToolUse events
1037#[derive(Debug, Clone, Serialize, Deserialize)]
1038pub struct PreToolUseHookSpecificOutput {
1039    /// Permission decision: "allow", "deny", or "ask"
1040    #[serde(rename = "permissionDecision", skip_serializing_if = "Option::is_none")]
1041    pub permission_decision: Option<String>,
1042    /// Reason for the permission decision
1043    #[serde(rename = "permissionDecisionReason", skip_serializing_if = "Option::is_none")]
1044    pub permission_decision_reason: Option<String>,
1045    /// Updated input parameters for the tool
1046    #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
1047    pub updated_input: Option<serde_json::Value>,
1048    /// Additional context to provide to Claude
1049    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1050    pub additional_context: Option<String>,
1051}
1052
1053/// Hook-specific output for PostToolUse events
1054#[derive(Debug, Clone, Serialize, Deserialize)]
1055pub struct PostToolUseHookSpecificOutput {
1056    /// Additional context to provide to Claude
1057    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1058    pub additional_context: Option<String>,
1059    /// Updated MCP tool output
1060    #[serde(rename = "updatedMCPToolOutput", skip_serializing_if = "Option::is_none")]
1061    pub updated_mcp_tool_output: Option<serde_json::Value>,
1062}
1063
1064/// Hook-specific output for UserPromptSubmit events
1065#[derive(Debug, Clone, Serialize, Deserialize)]
1066pub struct UserPromptSubmitHookSpecificOutput {
1067    /// Additional context to provide to Claude
1068    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1069    pub additional_context: Option<String>,
1070}
1071
1072/// Hook-specific output for SessionStart events
1073#[derive(Debug, Clone, Serialize, Deserialize)]
1074pub struct SessionStartHookSpecificOutput {
1075    /// Additional context to provide to Claude
1076    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1077    pub additional_context: Option<String>,
1078}
1079
1080/// Hook-specific output for PostToolUseFailure events
1081#[derive(Debug, Clone, Serialize, Deserialize)]
1082pub struct PostToolUseFailureHookSpecificOutput {
1083    /// Additional context to provide to Claude
1084    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1085    pub additional_context: Option<String>,
1086}
1087
1088/// Hook-specific output for Notification events
1089#[derive(Debug, Clone, Serialize, Deserialize)]
1090pub struct NotificationHookSpecificOutput {
1091    /// Additional context to provide to Claude
1092    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1093    pub additional_context: Option<String>,
1094}
1095
1096/// Hook-specific output for SubagentStart events
1097#[derive(Debug, Clone, Serialize, Deserialize)]
1098pub struct SubagentStartHookSpecificOutput {
1099    /// Additional context to provide to Claude
1100    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1101    pub additional_context: Option<String>,
1102}
1103
1104/// Hook-specific output for PermissionRequest events
1105#[derive(Debug, Clone, Serialize, Deserialize)]
1106pub struct PermissionRequestHookSpecificOutput {
1107    /// Permission decision
1108    pub decision: serde_json::Value,
1109}
1110
1111/// Union type for hook-specific outputs (discriminated by hookEventName)
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113#[serde(tag = "hookEventName")]
1114pub enum HookSpecificOutput {
1115    /// PreToolUse-specific output
1116    #[serde(rename = "PreToolUse")]
1117    PreToolUse(PreToolUseHookSpecificOutput),
1118    /// PostToolUse-specific output
1119    #[serde(rename = "PostToolUse")]
1120    PostToolUse(PostToolUseHookSpecificOutput),
1121    /// PostToolUseFailure-specific output
1122    #[serde(rename = "PostToolUseFailure")]
1123    PostToolUseFailure(PostToolUseFailureHookSpecificOutput),
1124    /// UserPromptSubmit-specific output
1125    #[serde(rename = "UserPromptSubmit")]
1126    UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
1127    /// SessionStart-specific output
1128    #[serde(rename = "SessionStart")]
1129    SessionStart(SessionStartHookSpecificOutput),
1130    /// Notification-specific output
1131    #[serde(rename = "Notification")]
1132    Notification(NotificationHookSpecificOutput),
1133    /// SubagentStart-specific output
1134    #[serde(rename = "SubagentStart")]
1135    SubagentStart(SubagentStartHookSpecificOutput),
1136    /// PermissionRequest-specific output
1137    #[serde(rename = "PermissionRequest")]
1138    PermissionRequest(PermissionRequestHookSpecificOutput),
1139}
1140
1141// ============================================================================
1142// Hook Callback Trait (Updated for strong typing)
1143// ============================================================================
1144
1145/// Hook callback trait with strongly-typed inputs and outputs
1146///
1147/// This trait is used to implement custom hook callbacks that can intercept
1148/// and modify Claude's behavior at various points in the conversation.
1149#[async_trait]
1150pub trait HookCallback: Send + Sync {
1151    /// Execute the hook with strongly-typed input and output
1152    ///
1153    /// # Arguments
1154    ///
1155    /// * `input` - Strongly-typed hook input (discriminated union)
1156    /// * `tool_use_id` - Optional tool use identifier
1157    /// * `context` - Hook context with abort signal support
1158    ///
1159    /// # Returns
1160    ///
1161    /// A `HookJSONOutput` that controls Claude's behavior
1162    async fn execute(
1163        &self,
1164        input: &HookInput,
1165        tool_use_id: Option<&str>,
1166        context: &HookContext,
1167    ) -> Result<HookJSONOutput, crate::errors::SdkError>;
1168}
1169
1170/// Legacy hook callback trait for backward compatibility
1171///
1172/// This trait is deprecated and will be removed in v0.4.0.
1173/// Please migrate to the new `HookCallback` trait with strong typing.
1174#[deprecated(
1175    since = "0.3.0",
1176    note = "Use the new HookCallback trait with HookInput/HookJSONOutput instead"
1177)]
1178#[allow(dead_code)]
1179#[async_trait]
1180pub trait HookCallbackLegacy: Send + Sync {
1181    /// Execute the hook with JSON values (legacy)
1182    async fn execute_legacy(
1183        &self,
1184        input: &serde_json::Value,
1185        tool_use_id: Option<&str>,
1186        context: &HookContext,
1187    ) -> serde_json::Value;
1188}
1189
1190/// Hook matcher configuration
1191#[derive(Clone)]
1192pub struct HookMatcher {
1193    /// Matcher criteria
1194    pub matcher: Option<serde_json::Value>,
1195    /// Callbacks to invoke
1196    pub hooks: Vec<Arc<dyn HookCallback>>,
1197}
1198
1199/// Setting source for configuration loading
1200#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1201#[serde(rename_all = "lowercase")]
1202pub enum SettingSource {
1203    /// User-level settings
1204    User,
1205    /// Project-level settings
1206    Project,
1207    /// Local settings
1208    Local,
1209}
1210
1211/// Agent definition for programmatic agents
1212#[derive(Debug, Clone, Serialize, Deserialize)]
1213pub struct AgentDefinition {
1214    /// Agent description
1215    pub description: String,
1216    /// Agent prompt
1217    pub prompt: String,
1218    /// Allowed tools for this agent
1219    #[serde(skip_serializing_if = "Option::is_none")]
1220    pub tools: Option<Vec<String>>,
1221    /// Disallowed tools for this agent
1222    #[serde(default, skip_serializing_if = "Option::is_none", rename = "disallowedTools")]
1223    pub disallowed_tools: Option<Vec<String>>,
1224    /// Model to use
1225    #[serde(skip_serializing_if = "Option::is_none")]
1226    pub model: Option<String>,
1227    /// Skills available to this agent
1228    #[serde(default, skip_serializing_if = "Option::is_none")]
1229    pub skills: Option<Vec<String>>,
1230    /// Memory configuration ("user", "project", or "local")
1231    #[serde(default, skip_serializing_if = "Option::is_none")]
1232    pub memory: Option<String>,
1233    /// MCP server configurations for this agent
1234    #[serde(default, skip_serializing_if = "Option::is_none", rename = "mcpServers")]
1235    pub mcp_servers: Option<Vec<serde_json::Value>>,
1236    /// Initial prompt to send when the agent starts
1237    #[serde(default, skip_serializing_if = "Option::is_none", rename = "initialPrompt")]
1238    pub initial_prompt: Option<String>,
1239    /// Maximum number of turns for this agent
1240    #[serde(default, skip_serializing_if = "Option::is_none", rename = "maxTurns")]
1241    pub max_turns: Option<i32>,
1242    /// Whether this agent runs in the background
1243    #[serde(default, skip_serializing_if = "Option::is_none")]
1244    pub background: Option<bool>,
1245    /// Effort level for this agent's reasoning depth
1246    #[serde(default, skip_serializing_if = "Option::is_none")]
1247    pub effort: Option<Effort>,
1248    /// Permission mode for this agent
1249    #[serde(default, skip_serializing_if = "Option::is_none", rename = "permissionMode")]
1250    pub permission_mode: Option<PermissionMode>,
1251}
1252
1253/// System prompt configuration
1254#[derive(Debug, Clone, Serialize, Deserialize)]
1255#[serde(untagged)]
1256pub enum SystemPrompt {
1257    /// Simple string prompt
1258    String(String),
1259    /// Preset-based prompt with optional append
1260    Preset {
1261        #[serde(rename = "type")]
1262        preset_type: String,  // "preset"
1263        preset: String,       // e.g., "claude_code"
1264        #[serde(skip_serializing_if = "Option::is_none")]
1265        append: Option<String>,
1266    },
1267}
1268
1269/// Configuration options for Claude Code SDK
1270#[derive(Clone, Default)]
1271pub struct ClaudeCodeOptions {
1272    /// System prompt configuration (simplified in v0.1.12+)
1273    /// Can be either a string or a preset configuration
1274    /// Replaces the old system_prompt and append_system_prompt fields
1275    pub system_prompt_v2: Option<SystemPrompt>,
1276    /// [DEPRECATED] System prompt to prepend to all messages
1277    /// Use system_prompt_v2 instead
1278    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1279    pub system_prompt: Option<String>,
1280    /// [DEPRECATED] Additional system prompt to append
1281    /// Use system_prompt_v2 instead
1282    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1283    pub append_system_prompt: Option<String>,
1284    /// List of allowed tools (auto-approval permissions only)
1285    ///
1286    /// **IMPORTANT**: This only controls which tool invocations are auto-approved
1287    /// (bypass permission prompts). It does NOT disable or restrict which tools
1288    /// the AI can use. Use `disallowed_tools` to completely disable tools.
1289    ///
1290    /// Example: `allowed_tools: vec!["Bash(git:*)".to_string()]` allows auto-approval
1291    /// for git commands in Bash, but doesn't prevent AI from using other tools.
1292    pub allowed_tools: Vec<String>,
1293    /// List of disallowed tools (completely disabled)
1294    ///
1295    /// **IMPORTANT**: This completely disables the specified tools. The AI will
1296    /// not be able to use these tools at all. Use this to restrict which tools
1297    /// the AI has access to.
1298    ///
1299    /// Example: `disallowed_tools: vec!["Bash".to_string(), "WebSearch".to_string()]`
1300    /// prevents the AI from using Bash or WebSearch tools entirely.
1301    pub disallowed_tools: Vec<String>,
1302    /// Permission mode for tool execution
1303    pub permission_mode: PermissionMode,
1304    /// MCP server configurations
1305    pub mcp_servers: HashMap<String, McpServerConfig>,
1306    /// MCP tools to enable
1307    pub mcp_tools: Vec<String>,
1308    /// Maximum number of conversation turns
1309    pub max_turns: Option<i32>,
1310    /// Maximum thinking tokens
1311    pub max_thinking_tokens: Option<i32>,
1312    /// Maximum output tokens per response (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
1313    pub max_output_tokens: Option<u32>,
1314    /// Model to use
1315    pub model: Option<String>,
1316    /// Working directory
1317    pub cwd: Option<PathBuf>,
1318    /// Continue from previous conversation
1319    pub continue_conversation: bool,
1320    /// Resume from a specific conversation ID
1321    pub resume: Option<String>,
1322    /// Custom permission prompt tool name
1323    pub permission_prompt_tool_name: Option<String>,
1324    /// Settings file path for Claude Code CLI
1325    pub settings: Option<String>,
1326    /// Additional directories to add as working directories
1327    pub add_dirs: Vec<PathBuf>,
1328    /// Extra arbitrary CLI flags
1329    pub extra_args: HashMap<String, Option<String>>,
1330    /// Environment variables to pass to the process
1331    pub env: HashMap<String, String>,
1332    /// Debug output stream (e.g., stderr)
1333    pub debug_stderr: Option<Arc<Mutex<dyn Write + Send + Sync>>>,
1334    /// Include partial assistant messages in streaming output
1335    pub include_partial_messages: bool,
1336    /// Tool permission callback
1337    pub can_use_tool: Option<Arc<dyn CanUseTool>>,
1338    /// Hook configurations
1339    pub hooks: Option<HashMap<String, Vec<HookMatcher>>>,
1340    /// Control protocol format (defaults to Legacy for compatibility)
1341    pub control_protocol_format: ControlProtocolFormat,
1342
1343    // ========== Phase 2 Enhancements ==========
1344    /// Setting sources to load (user, project, local)
1345    /// When None, no filesystem settings are loaded (matches Python SDK v0.1.0 behavior)
1346    pub setting_sources: Option<Vec<SettingSource>>,
1347    /// Fork session when resuming instead of continuing
1348    /// When true, creates a new branch from the resumed session
1349    pub fork_session: bool,
1350    /// Programmatic agent definitions
1351    /// Define agents inline without filesystem dependencies
1352    pub agents: Option<HashMap<String, AgentDefinition>>,
1353    /// CLI channel buffer size for internal communication channels
1354    /// Controls the size of message, control, and stdin buffers (default: 100)
1355    /// Increase for high-throughput scenarios to prevent message lag
1356    pub cli_channel_buffer_size: Option<usize>,
1357
1358    // ========== Phase 3 Enhancements (Python SDK v0.1.12+ sync) ==========
1359    /// Tools configuration for controlling available tools
1360    ///
1361    /// This controls the base set of tools available to Claude, distinct from
1362    /// `allowed_tools` which only controls auto-approval permissions.
1363    ///
1364    /// # Examples
1365    /// ```rust
1366    /// use cc_sdk::{ClaudeCodeOptions, ToolsConfig};
1367    ///
1368    /// // Enable specific tools only
1369    /// let options = ClaudeCodeOptions::builder()
1370    ///     .tools(ToolsConfig::list(vec!["Read".into(), "Edit".into()]))
1371    ///     .build();
1372    ///
1373    /// // Disable all built-in tools
1374    /// let options = ClaudeCodeOptions::builder()
1375    ///     .tools(ToolsConfig::none())
1376    ///     .build();
1377    ///
1378    /// // Use claude_code preset
1379    /// let options = ClaudeCodeOptions::builder()
1380    ///     .tools(ToolsConfig::claude_code_preset())
1381    ///     .build();
1382    /// ```
1383    pub tools: Option<ToolsConfig>,
1384    /// SDK beta features to enable
1385    /// See https://docs.anthropic.com/en/api/beta-headers
1386    pub betas: Vec<SdkBeta>,
1387    /// Maximum spending limit in USD for the session
1388    /// When exceeded, the session will automatically terminate
1389    pub max_budget_usd: Option<f64>,
1390    /// Fallback model to use when primary model is unavailable
1391    pub fallback_model: Option<String>,
1392    /// Output format for structured outputs
1393    /// Example: `{"type": "json_schema", "schema": {"type": "object", "properties": {...}}}`
1394    pub output_format: Option<serde_json::Value>,
1395    /// Enable file checkpointing to track file changes during the session
1396    /// When enabled, files can be rewound to their state at any user message
1397    /// using `ClaudeSDKClient::rewind_files()`
1398    pub enable_file_checkpointing: bool,
1399    /// Sandbox configuration for bash command isolation
1400    /// Filesystem and network restrictions are derived from permission rules
1401    pub sandbox: Option<SandboxSettings>,
1402    /// Plugin configurations for custom plugins
1403    pub plugins: Vec<SdkPluginConfig>,
1404    /// Run the CLI subprocess as a specific OS user (Unix-only).
1405    ///
1406    /// This matches Python SDK behavior (`anyio.open_process(user=...)`).
1407    ///
1408    /// - Supported on Unix platforms only (non-Unix returns `SdkError::NotSupported`)
1409    /// - Typically requires elevated privileges to switch users
1410    /// - Accepts a username (e.g. `"nobody"`) or a numeric uid string (e.g. `"1000"`)
1411    pub user: Option<String>,
1412    /// Stderr callback (alternative to debug_stderr)
1413    /// Called with each line of stderr output from the CLI
1414    pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
1415    /// Automatically download Claude Code CLI if not found
1416    ///
1417    /// When enabled, the SDK will automatically download and cache the Claude Code
1418    /// CLI binary if it's not found in the system PATH or common installation locations.
1419    ///
1420    /// The CLI is cached in:
1421    /// - macOS: `~/Library/Caches/cc-sdk/cli/`
1422    /// - Linux: `~/.cache/cc-sdk/cli/`
1423    /// - Windows: `%LOCALAPPDATA%\cc-sdk\cli\`
1424    ///
1425    /// # Example
1426    ///
1427    /// ```rust
1428    /// # use cc_sdk::ClaudeCodeOptions;
1429    /// let options = ClaudeCodeOptions::builder()
1430    ///     .auto_download_cli(true)
1431    ///     .build();
1432    /// ```
1433    pub auto_download_cli: bool,
1434
1435    // ========== v0.7.0 Enhancements (Python SDK parity) ==========
1436    /// Effort level for Claude's reasoning depth
1437    pub effort: Option<Effort>,
1438    /// Thinking configuration (replaces max_thinking_tokens)
1439    /// When set, takes priority over max_thinking_tokens
1440    pub thinking: Option<ThinkingConfig>,
1441}
1442
1443impl std::fmt::Debug for ClaudeCodeOptions {
1444    #[allow(deprecated)]
1445    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1446        f.debug_struct("ClaudeCodeOptions")
1447            .field("system_prompt", &self.system_prompt)
1448            .field("append_system_prompt", &self.append_system_prompt)
1449            .field("allowed_tools", &self.allowed_tools)
1450            .field("disallowed_tools", &self.disallowed_tools)
1451            .field("permission_mode", &self.permission_mode)
1452            .field("mcp_servers", &self.mcp_servers)
1453            .field("mcp_tools", &self.mcp_tools)
1454            .field("max_turns", &self.max_turns)
1455            .field("max_thinking_tokens", &self.max_thinking_tokens)
1456            .field("max_output_tokens", &self.max_output_tokens)
1457            .field("model", &self.model)
1458            .field("cwd", &self.cwd)
1459            .field("continue_conversation", &self.continue_conversation)
1460            .field("resume", &self.resume)
1461            .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
1462            .field("settings", &self.settings)
1463            .field("add_dirs", &self.add_dirs)
1464            .field("extra_args", &self.extra_args)
1465            .field("env", &self.env)
1466            .field("debug_stderr", &self.debug_stderr.is_some())
1467            .field("include_partial_messages", &self.include_partial_messages)
1468            .field("can_use_tool", &self.can_use_tool.is_some())
1469            .field("hooks", &self.hooks.is_some())
1470            .field("control_protocol_format", &self.control_protocol_format)
1471            .field("effort", &self.effort)
1472            .field("thinking", &self.thinking)
1473            .finish()
1474    }
1475}
1476
1477impl ClaudeCodeOptions {
1478    /// Create a new options builder
1479    pub fn builder() -> ClaudeCodeOptionsBuilder {
1480        ClaudeCodeOptionsBuilder::default()
1481    }
1482}
1483
1484/// Builder for ClaudeCodeOptions
1485#[derive(Debug, Default)]
1486pub struct ClaudeCodeOptionsBuilder {
1487    options: ClaudeCodeOptions,
1488}
1489
1490impl ClaudeCodeOptionsBuilder {
1491    /// Set system prompt
1492    #[allow(deprecated)]
1493    pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
1494        self.options.system_prompt = Some(prompt.into());
1495        self
1496    }
1497
1498    /// Set append system prompt
1499    #[allow(deprecated)]
1500    pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1501        self.options.append_system_prompt = Some(prompt.into());
1502        self
1503    }
1504
1505    /// Set allowed tools (auto-approval permissions only)
1506    ///
1507    /// **IMPORTANT**: This only controls which tool invocations bypass permission
1508    /// prompts. It does NOT disable or restrict which tools the AI can use.
1509    /// To completely disable tools, use `disallowed_tools()` instead.
1510    ///
1511    /// Example: `vec!["Bash(git:*)".to_string()]` auto-approves git commands.
1512    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1513        self.options.allowed_tools = tools;
1514        self
1515    }
1516
1517    /// Add a single allowed tool (auto-approval permission)
1518    ///
1519    /// See `allowed_tools()` for important usage notes.
1520    pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1521        self.options.allowed_tools.push(tool.into());
1522        self
1523    }
1524
1525    /// Set disallowed tools (completely disabled)
1526    ///
1527    /// **IMPORTANT**: This completely disables the specified tools. The AI will
1528    /// not be able to use these tools at all. This is the correct way to restrict
1529    /// which tools the AI has access to.
1530    ///
1531    /// Example: `vec!["Bash".to_string(), "WebSearch".to_string()]` prevents
1532    /// the AI from using Bash or WebSearch entirely.
1533    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1534        self.options.disallowed_tools = tools;
1535        self
1536    }
1537
1538    /// Add a single disallowed tool (completely disabled)
1539    ///
1540    /// See `disallowed_tools()` for important usage notes.
1541    pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1542        self.options.disallowed_tools.push(tool.into());
1543        self
1544    }
1545
1546    /// Set permission mode
1547    pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1548        self.options.permission_mode = mode;
1549        self
1550    }
1551
1552    /// Add MCP server
1553    pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
1554        self.options.mcp_servers.insert(name.into(), config);
1555        self
1556    }
1557
1558    /// Set all MCP servers from a map
1559    pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1560        self.options.mcp_servers = servers;
1561        self
1562    }
1563
1564    /// Set MCP tools
1565    pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1566        self.options.mcp_tools = tools;
1567        self
1568    }
1569
1570    /// Set max turns
1571    pub fn max_turns(mut self, turns: i32) -> Self {
1572        self.options.max_turns = Some(turns);
1573        self
1574    }
1575
1576    /// Set max thinking tokens
1577    pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1578        self.options.max_thinking_tokens = Some(tokens);
1579        self
1580    }
1581
1582    /// Set max output tokens (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
1583    pub fn max_output_tokens(mut self, tokens: u32) -> Self {
1584        self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
1585        self
1586    }
1587
1588    /// Set model
1589    pub fn model(mut self, model: impl Into<String>) -> Self {
1590        self.options.model = Some(model.into());
1591        self
1592    }
1593
1594    /// Set working directory
1595    pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1596        self.options.cwd = Some(path.into());
1597        self
1598    }
1599
1600    /// Enable continue conversation
1601    pub fn continue_conversation(mut self, enable: bool) -> Self {
1602        self.options.continue_conversation = enable;
1603        self
1604    }
1605
1606    /// Set resume conversation ID
1607    pub fn resume(mut self, id: impl Into<String>) -> Self {
1608        self.options.resume = Some(id.into());
1609        self
1610    }
1611
1612    /// Set permission prompt tool name
1613    pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
1614        self.options.permission_prompt_tool_name = Some(name.into());
1615        self
1616    }
1617
1618    /// Set settings file path
1619    pub fn settings(mut self, settings: impl Into<String>) -> Self {
1620        self.options.settings = Some(settings.into());
1621        self
1622    }
1623
1624    /// Add directories as working directories
1625    pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1626        self.options.add_dirs = dirs;
1627        self
1628    }
1629
1630    /// Add a single directory as working directory
1631    pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1632        self.options.add_dirs.push(dir.into());
1633        self
1634    }
1635
1636    /// Add extra CLI arguments
1637    pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1638        self.options.extra_args = args;
1639        self
1640    }
1641
1642    /// Add a single extra CLI argument
1643    pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
1644        self.options.extra_args.insert(key.into(), value);
1645        self
1646    }
1647
1648    /// Set control protocol format
1649    pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1650        self.options.control_protocol_format = format;
1651        self
1652    }
1653
1654    /// Include partial assistant messages in streaming output
1655    pub fn include_partial_messages(mut self, include: bool) -> Self {
1656        self.options.include_partial_messages = include;
1657        self
1658    }
1659
1660    /// Enable fork_session behavior
1661    pub fn fork_session(mut self, fork: bool) -> Self {
1662        self.options.fork_session = fork;
1663        self
1664    }
1665
1666    /// Set setting sources
1667    pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1668        self.options.setting_sources = Some(sources);
1669        self
1670    }
1671
1672    /// Define programmatic agents
1673    pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1674        self.options.agents = Some(agents);
1675        self
1676    }
1677
1678    /// Set CLI channel buffer size
1679    ///
1680    /// Controls the size of internal communication channels (message, control, stdin buffers).
1681    /// Default is 100. Increase for high-throughput scenarios to prevent message lag.
1682    ///
1683    /// # Arguments
1684    ///
1685    /// * `size` - Buffer size (number of messages that can be queued)
1686    ///
1687    /// # Example
1688    ///
1689    /// ```rust
1690    /// # use cc_sdk::ClaudeCodeOptions;
1691    /// let options = ClaudeCodeOptions::builder()
1692    ///     .cli_channel_buffer_size(500)
1693    ///     .build();
1694    /// ```
1695    pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1696        self.options.cli_channel_buffer_size = Some(size);
1697        self
1698    }
1699
1700    // ========== Phase 3 Builder Methods (Python SDK v0.1.12+ sync) ==========
1701
1702    /// Set tools configuration
1703    ///
1704    /// Controls the base set of tools available to Claude. This is distinct from
1705    /// `allowed_tools` which only controls auto-approval permissions.
1706    ///
1707    /// # Examples
1708    ///
1709    /// ```rust
1710    /// # use cc_sdk::{ClaudeCodeOptions, ToolsConfig};
1711    /// // Enable specific tools only
1712    /// let options = ClaudeCodeOptions::builder()
1713    ///     .tools(ToolsConfig::list(vec!["Read".into(), "Edit".into()]))
1714    ///     .build();
1715    /// ```
1716    pub fn tools(mut self, config: ToolsConfig) -> Self {
1717        self.options.tools = Some(config);
1718        self
1719    }
1720
1721    /// Add SDK beta features
1722    ///
1723    /// Enable Anthropic API beta features like extended context window.
1724    pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1725        self.options.betas = betas;
1726        self
1727    }
1728
1729    /// Add a single SDK beta feature
1730    pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1731        self.options.betas.push(beta);
1732        self
1733    }
1734
1735    /// Set maximum spending limit in USD
1736    ///
1737    /// When the budget is exceeded, the session will automatically terminate.
1738    pub fn max_budget_usd(mut self, budget: f64) -> Self {
1739        self.options.max_budget_usd = Some(budget);
1740        self
1741    }
1742
1743    /// Set fallback model
1744    ///
1745    /// Used when the primary model is unavailable.
1746    pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1747        self.options.fallback_model = Some(model.into());
1748        self
1749    }
1750
1751    /// Set output format for structured outputs
1752    ///
1753    /// Enables JSON schema validation for Claude's responses.
1754    ///
1755    /// # Example
1756    ///
1757    /// ```rust
1758    /// # use cc_sdk::ClaudeCodeOptions;
1759    /// let options = ClaudeCodeOptions::builder()
1760    ///     .output_format(serde_json::json!({
1761    ///         "type": "json_schema",
1762    ///         "schema": {
1763    ///             "type": "object",
1764    ///             "properties": {
1765    ///                 "answer": {"type": "string"}
1766    ///             }
1767    ///         }
1768    ///     }))
1769    ///     .build();
1770    /// ```
1771    pub fn output_format(mut self, format: serde_json::Value) -> Self {
1772        self.options.output_format = Some(format);
1773        self
1774    }
1775
1776    /// Enable file checkpointing
1777    ///
1778    /// When enabled, file changes are tracked and can be rewound to any
1779    /// user message using `ClaudeSDKClient::rewind_files()`.
1780    pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1781        self.options.enable_file_checkpointing = enable;
1782        self
1783    }
1784
1785    /// Set sandbox configuration
1786    ///
1787    /// Controls bash command sandboxing for filesystem and network isolation.
1788    pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1789        self.options.sandbox = Some(settings);
1790        self
1791    }
1792
1793    /// Set plugin configurations
1794    pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1795        self.options.plugins = plugins;
1796        self
1797    }
1798
1799    /// Add a single plugin
1800    pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1801        self.options.plugins.push(plugin);
1802        self
1803    }
1804
1805    /// Set user identifier
1806    pub fn user(mut self, user: impl Into<String>) -> Self {
1807        self.options.user = Some(user.into());
1808        self
1809    }
1810
1811    /// Set stderr callback
1812    ///
1813    /// Called with each line of stderr output from the CLI.
1814    pub fn stderr_callback(mut self, callback: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
1815        self.options.stderr_callback = Some(callback);
1816        self
1817    }
1818
1819    /// Enable automatic CLI download
1820    ///
1821    /// When enabled, the SDK will automatically download and cache the Claude Code
1822    /// CLI binary if it's not found in the system PATH or common installation locations.
1823    ///
1824    /// # Example
1825    ///
1826    /// ```rust
1827    /// # use cc_sdk::ClaudeCodeOptions;
1828    /// let options = ClaudeCodeOptions::builder()
1829    ///     .auto_download_cli(true)
1830    ///     .build();
1831    /// ```
1832    pub fn auto_download_cli(mut self, enable: bool) -> Self {
1833        self.options.auto_download_cli = enable;
1834        self
1835    }
1836
1837    // ========== v0.7.0 Builder Methods (Python SDK parity) ==========
1838
1839    /// Set effort level for Claude's reasoning depth
1840    ///
1841    /// # Example
1842    ///
1843    /// ```rust
1844    /// # use cc_sdk::{ClaudeCodeOptions, Effort};
1845    /// let options = ClaudeCodeOptions::builder()
1846    ///     .effort(Effort::High)
1847    ///     .build();
1848    /// ```
1849    pub fn effort(mut self, effort: Effort) -> Self {
1850        self.options.effort = Some(effort);
1851        self
1852    }
1853
1854    /// Set thinking configuration
1855    ///
1856    /// When set, takes priority over `max_thinking_tokens`.
1857    ///
1858    /// # Example
1859    ///
1860    /// ```rust
1861    /// # use cc_sdk::{ClaudeCodeOptions, ThinkingConfig};
1862    /// let options = ClaudeCodeOptions::builder()
1863    ///     .thinking(ThinkingConfig::Enabled { budget_tokens: 10000 })
1864    ///     .build();
1865    /// ```
1866    pub fn thinking(mut self, config: ThinkingConfig) -> Self {
1867        self.options.thinking = Some(config);
1868        self
1869    }
1870
1871    /// Build the options
1872    pub fn build(self) -> ClaudeCodeOptions {
1873        self.options
1874    }
1875}
1876
1877// ============================================================================
1878// Task Message Types (Python SDK parity)
1879// ============================================================================
1880
1881/// Usage statistics for a task
1882#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1883pub struct TaskUsage {
1884    /// Total tokens used
1885    #[serde(default)]
1886    pub total_tokens: u64,
1887    /// Number of tool uses
1888    #[serde(default)]
1889    pub tool_uses: u64,
1890    /// Duration in milliseconds
1891    #[serde(default)]
1892    pub duration_ms: u64,
1893}
1894
1895/// Task completion status
1896#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1897#[serde(rename_all = "lowercase")]
1898pub enum TaskStatus {
1899    /// Task completed successfully
1900    Completed,
1901    /// Task failed
1902    Failed,
1903    /// Task was stopped
1904    Stopped,
1905}
1906
1907/// Task started message data
1908#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1909pub struct TaskStartedMessage {
1910    /// Task ID
1911    pub task_id: String,
1912    /// Task description
1913    pub description: String,
1914    /// Unique message ID
1915    pub uuid: String,
1916    /// Session ID
1917    pub session_id: String,
1918    /// Associated tool use ID
1919    #[serde(default, skip_serializing_if = "Option::is_none")]
1920    pub tool_use_id: Option<String>,
1921    /// Task type
1922    #[serde(default, skip_serializing_if = "Option::is_none")]
1923    pub task_type: Option<String>,
1924}
1925
1926/// Task progress message data
1927#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1928pub struct TaskProgressMessage {
1929    /// Task ID
1930    pub task_id: String,
1931    /// Task description/status
1932    pub description: String,
1933    /// Usage statistics
1934    #[serde(default)]
1935    pub usage: TaskUsage,
1936    /// Unique message ID
1937    pub uuid: String,
1938    /// Session ID
1939    pub session_id: String,
1940    /// Associated tool use ID
1941    #[serde(default, skip_serializing_if = "Option::is_none")]
1942    pub tool_use_id: Option<String>,
1943    /// Name of last tool used
1944    #[serde(default, skip_serializing_if = "Option::is_none")]
1945    pub last_tool_name: Option<String>,
1946}
1947
1948/// Task notification (completion) message data
1949#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1950pub struct TaskNotificationMessage {
1951    /// Task ID
1952    pub task_id: String,
1953    /// Task completion status
1954    pub status: TaskStatus,
1955    /// Output file path
1956    #[serde(default, skip_serializing_if = "Option::is_none")]
1957    pub output_file: Option<String>,
1958    /// Summary of task results
1959    #[serde(default, skip_serializing_if = "Option::is_none")]
1960    pub summary: Option<String>,
1961    /// Unique message ID
1962    pub uuid: String,
1963    /// Session ID
1964    pub session_id: String,
1965    /// Associated tool use ID
1966    #[serde(default, skip_serializing_if = "Option::is_none")]
1967    pub tool_use_id: Option<String>,
1968    /// Usage statistics
1969    #[serde(default, skip_serializing_if = "Option::is_none")]
1970    pub usage: Option<TaskUsage>,
1971}
1972
1973/// Main message type enum
1974#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1975#[non_exhaustive]
1976#[serde(tag = "type", rename_all = "lowercase")]
1977pub enum Message {
1978    /// User message
1979    User {
1980        /// Message content
1981        message: UserMessage,
1982    },
1983    /// Assistant message
1984    Assistant {
1985        /// Message content
1986        message: AssistantMessage,
1987    },
1988    /// System message
1989    System {
1990        /// Subtype of system message
1991        subtype: String,
1992        /// Additional data
1993        data: serde_json::Value,
1994    },
1995    /// Result message indicating end of turn
1996    Result {
1997        /// Result subtype
1998        subtype: String,
1999        /// Duration in milliseconds
2000        duration_ms: i64,
2001        /// API duration in milliseconds
2002        duration_api_ms: i64,
2003        /// Whether an error occurred
2004        is_error: bool,
2005        /// Number of turns
2006        num_turns: i32,
2007        /// Session ID
2008        session_id: String,
2009        /// Total cost in USD
2010        #[serde(skip_serializing_if = "Option::is_none")]
2011        total_cost_usd: Option<f64>,
2012        /// Usage statistics
2013        #[serde(skip_serializing_if = "Option::is_none")]
2014        usage: Option<serde_json::Value>,
2015        /// Result message
2016        #[serde(skip_serializing_if = "Option::is_none")]
2017        result: Option<String>,
2018        /// Structured output (when output_format is set)
2019        /// Contains the validated JSON response matching the schema
2020        #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
2021        structured_output: Option<serde_json::Value>,
2022        /// Reason the conversation stopped
2023        #[serde(default, skip_serializing_if = "Option::is_none")]
2024        stop_reason: Option<String>,
2025    },
2026
2027    /// Stream event from the CLI
2028    #[serde(rename = "stream_event")]
2029    StreamEvent {
2030        /// Unique message ID
2031        uuid: String,
2032        /// Session ID
2033        session_id: String,
2034        /// Event data
2035        event: serde_json::Value,
2036        /// Parent tool use ID (for subagent events)
2037        #[serde(default, skip_serializing_if = "Option::is_none")]
2038        parent_tool_use_id: Option<String>,
2039    },
2040
2041    /// Rate limit notification
2042    #[serde(rename = "rate_limit")]
2043    RateLimit {
2044        /// Rate limit details
2045        rate_limit_info: RateLimitInfo,
2046        /// Unique message ID
2047        uuid: String,
2048        /// Session ID
2049        session_id: String,
2050    },
2051
2052    /// Unknown message type (forward compatibility)
2053    /// Not deserialized by serde — constructed by message_parser
2054    #[serde(skip)]
2055    Unknown {
2056        /// Original message type string
2057        msg_type: String,
2058        /// Raw JSON data
2059        raw: serde_json::Value,
2060    },
2061}
2062
2063impl Message {
2064    /// Try to extract a TaskStartedMessage from a System message
2065    pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
2066        if let Message::System { subtype, data } = self {
2067            if subtype == "task_started" {
2068                return serde_json::from_value(data.clone()).ok();
2069            }
2070        }
2071        None
2072    }
2073
2074    /// Try to extract a TaskProgressMessage from a System message
2075    pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
2076        if let Message::System { subtype, data } = self {
2077            if subtype == "task_progress" {
2078                return serde_json::from_value(data.clone()).ok();
2079            }
2080        }
2081        None
2082    }
2083
2084    /// Try to extract a TaskNotificationMessage from a System message
2085    pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
2086        if let Message::System { subtype, data } = self {
2087            if subtype == "task_notification" {
2088                return serde_json::from_value(data.clone()).ok();
2089            }
2090        }
2091        None
2092    }
2093}
2094
2095/// User message content
2096#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2097pub struct UserMessage {
2098    /// Message content
2099    pub content: String,
2100}
2101
2102/// Assistant message content
2103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2104pub struct AssistantMessage {
2105    /// Content blocks
2106    pub content: Vec<ContentBlock>,
2107    /// Model that generated this message
2108    #[serde(default, skip_serializing_if = "Option::is_none")]
2109    pub model: Option<String>,
2110    /// Token usage statistics
2111    #[serde(default, skip_serializing_if = "Option::is_none")]
2112    pub usage: Option<serde_json::Value>,
2113    /// Error information if the message failed
2114    #[serde(default, skip_serializing_if = "Option::is_none")]
2115    pub error: Option<AssistantMessageError>,
2116    /// Parent tool use ID (for subagent messages)
2117    #[serde(default, skip_serializing_if = "Option::is_none")]
2118    pub parent_tool_use_id: Option<String>,
2119}
2120
2121/// Result message (re-export for convenience)  
2122pub use Message::Result as ResultMessage;
2123/// System message (re-export for convenience)
2124pub use Message::System as SystemMessage;
2125
2126/// Content block types
2127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2128#[serde(untagged)]
2129pub enum ContentBlock {
2130    /// Text content
2131    Text(TextContent),
2132    /// Thinking content
2133    Thinking(ThinkingContent),
2134    /// Tool use request
2135    ToolUse(ToolUseContent),
2136    /// Tool result
2137    ToolResult(ToolResultContent),
2138}
2139
2140/// Text content block
2141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2142pub struct TextContent {
2143    /// Text content
2144    pub text: String,
2145}
2146
2147/// Thinking content block
2148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2149pub struct ThinkingContent {
2150    /// Thinking content
2151    pub thinking: String,
2152    /// Signature
2153    pub signature: String,
2154}
2155
2156/// Tool use content block
2157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2158pub struct ToolUseContent {
2159    /// Tool use ID
2160    pub id: String,
2161    /// Tool name
2162    pub name: String,
2163    /// Tool input parameters
2164    pub input: serde_json::Value,
2165}
2166
2167/// Tool result content block
2168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2169pub struct ToolResultContent {
2170    /// Tool use ID this result corresponds to
2171    pub tool_use_id: String,
2172    /// Result content
2173    #[serde(skip_serializing_if = "Option::is_none")]
2174    pub content: Option<ContentValue>,
2175    /// Whether this is an error result
2176    #[serde(skip_serializing_if = "Option::is_none")]
2177    pub is_error: Option<bool>,
2178}
2179
2180/// Content value for tool results
2181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2182#[serde(untagged)]
2183pub enum ContentValue {
2184    /// Text content
2185    Text(String),
2186    /// Structured content
2187    Structured(Vec<serde_json::Value>),
2188}
2189
2190/// User content structure for internal use
2191#[derive(Debug, Clone, Serialize, Deserialize)]
2192pub struct UserContent {
2193    /// Role (always "user")
2194    pub role: String,
2195    /// Message content
2196    pub content: String,
2197}
2198
2199/// Assistant content structure for internal use
2200#[derive(Debug, Clone, Serialize, Deserialize)]
2201pub struct AssistantContent {
2202    /// Role (always "assistant")
2203    pub role: String,
2204    /// Content blocks
2205    pub content: Vec<ContentBlock>,
2206}
2207
2208/// SDK Control Protocol - Interrupt request
2209#[derive(Debug, Clone, Serialize, Deserialize)]
2210pub struct SDKControlInterruptRequest {
2211    /// Subtype
2212    pub subtype: String,  // "interrupt"
2213}
2214
2215/// SDK Control Protocol - Permission request
2216#[derive(Debug, Clone, Serialize, Deserialize)]
2217pub struct SDKControlPermissionRequest {
2218    /// Subtype
2219    pub subtype: String,  // "can_use_tool"
2220    /// Tool name
2221    #[serde(alias = "toolName")]
2222    pub tool_name: String,
2223    /// Tool input
2224    pub input: serde_json::Value,
2225    /// Permission suggestions
2226    #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
2227    pub permission_suggestions: Option<Vec<PermissionUpdate>>,
2228    /// Blocked path
2229    #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
2230    pub blocked_path: Option<String>,
2231}
2232
2233/// SDK Control Protocol - Initialize request
2234#[derive(Debug, Clone, Serialize, Deserialize)]
2235pub struct SDKControlInitializeRequest {
2236    /// Subtype
2237    pub subtype: String,  // "initialize"
2238    /// Hooks configuration
2239    #[serde(skip_serializing_if = "Option::is_none")]
2240    pub hooks: Option<HashMap<String, serde_json::Value>>,
2241}
2242
2243/// SDK Control Protocol - Set permission mode request
2244#[derive(Debug, Clone, Serialize, Deserialize)]
2245#[serde(rename_all = "camelCase")]
2246pub struct SDKControlSetPermissionModeRequest {
2247    /// Subtype
2248    pub subtype: String,  // "set_permission_mode"
2249    /// Permission mode
2250    pub mode: String,
2251}
2252
2253/// SDK Control Protocol - Set model request
2254#[derive(Debug, Clone, Serialize, Deserialize)]
2255#[serde(rename_all = "camelCase")]
2256pub struct SDKControlSetModelRequest {
2257    /// Subtype
2258    pub subtype: String, // "set_model"
2259    /// Model to set (None to clear)
2260    #[serde(skip_serializing_if = "Option::is_none")]
2261    pub model: Option<String>,
2262}
2263
2264/// SDK Hook callback request
2265#[derive(Debug, Clone, Serialize, Deserialize)]
2266pub struct SDKHookCallbackRequest {
2267    /// Subtype
2268    pub subtype: String,  // "hook_callback"
2269    /// Callback ID
2270    #[serde(alias = "callbackId")]
2271    pub callback_id: String,
2272    /// Input data
2273    pub input: serde_json::Value,
2274    /// Tool use ID
2275    #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
2276    pub tool_use_id: Option<String>,
2277}
2278
2279/// SDK Control Protocol - MCP message request
2280#[derive(Debug, Clone, Serialize, Deserialize)]
2281pub struct SDKControlMcpMessageRequest {
2282    /// Subtype
2283    pub subtype: String,  // "mcp_message"
2284    /// MCP server name
2285    #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
2286    pub mcp_server_name: String,
2287    /// Message to send
2288    pub message: serde_json::Value,
2289}
2290
2291/// SDK Control Protocol - Rewind files request (Python SDK v0.1.14+)
2292///
2293/// Rewinds tracked files to their state at a specific user message.
2294/// Requires `enable_file_checkpointing` to be enabled.
2295#[derive(Debug, Clone, Serialize, Deserialize)]
2296pub struct SDKControlRewindFilesRequest {
2297    /// Subtype (always "rewind_files")
2298    pub subtype: String,
2299    /// UUID of the user message to rewind to
2300    #[serde(alias = "userMessageId")]
2301    pub user_message_id: String,
2302}
2303
2304impl SDKControlRewindFilesRequest {
2305    /// Create a new rewind files request
2306    pub fn new(user_message_id: impl Into<String>) -> Self {
2307        Self {
2308            subtype: "rewind_files".to_string(),
2309            user_message_id: user_message_id.into(),
2310        }
2311    }
2312}
2313
2314/// SDK Control Protocol - Get context usage request
2315#[derive(Debug, Clone, Serialize, Deserialize)]
2316pub struct SDKControlGetContextUsageRequest {
2317    /// Subtype (always "get_context_usage")
2318    pub subtype: String,
2319}
2320
2321impl SDKControlGetContextUsageRequest {
2322    /// Create a new get context usage request
2323    pub fn new() -> Self {
2324        Self {
2325            subtype: "get_context_usage".to_string(),
2326        }
2327    }
2328}
2329
2330/// SDK Control Protocol - Stop task request
2331#[derive(Debug, Clone, Serialize, Deserialize)]
2332pub struct SDKControlStopTaskRequest {
2333    /// Subtype (always "stop_task")
2334    pub subtype: String,
2335    /// Task ID to stop
2336    #[serde(alias = "taskId")]
2337    pub task_id: String,
2338}
2339
2340impl SDKControlStopTaskRequest {
2341    /// Create a new stop task request
2342    pub fn new(task_id: impl Into<String>) -> Self {
2343        Self {
2344            subtype: "stop_task".to_string(),
2345            task_id: task_id.into(),
2346        }
2347    }
2348}
2349
2350/// SDK Control Protocol - Get MCP status request
2351#[derive(Debug, Clone, Serialize, Deserialize)]
2352pub struct SDKControlMcpStatusRequest {
2353    /// Subtype (always "mcp_status")
2354    pub subtype: String,
2355}
2356
2357impl SDKControlMcpStatusRequest {
2358    /// Create a new MCP status request
2359    pub fn new() -> Self {
2360        Self {
2361            subtype: "mcp_status".to_string(),
2362        }
2363    }
2364}
2365
2366/// SDK Control Protocol - Reconnect MCP server request
2367#[derive(Debug, Clone, Serialize, Deserialize)]
2368pub struct SDKControlMcpReconnectRequest {
2369    /// Subtype (always "mcp_reconnect")
2370    pub subtype: String,
2371    /// Server name to reconnect
2372    #[serde(alias = "serverName")]
2373    pub server_name: String,
2374}
2375
2376impl SDKControlMcpReconnectRequest {
2377    /// Create a new MCP reconnect request
2378    pub fn new(server_name: impl Into<String>) -> Self {
2379        Self {
2380            subtype: "mcp_reconnect".to_string(),
2381            server_name: server_name.into(),
2382        }
2383    }
2384}
2385
2386/// SDK Control Protocol - Toggle MCP server request
2387#[derive(Debug, Clone, Serialize, Deserialize)]
2388pub struct SDKControlMcpToggleRequest {
2389    /// Subtype (always "mcp_toggle")
2390    pub subtype: String,
2391    /// Server name to toggle
2392    #[serde(alias = "serverName")]
2393    pub server_name: String,
2394    /// Whether to enable (true) or disable (false)
2395    pub enabled: bool,
2396}
2397
2398impl SDKControlMcpToggleRequest {
2399    /// Create a new MCP toggle request
2400    pub fn new(server_name: impl Into<String>, enabled: bool) -> Self {
2401        Self {
2402            subtype: "mcp_toggle".to_string(),
2403            server_name: server_name.into(),
2404            enabled,
2405        }
2406    }
2407}
2408
2409/// Context usage category
2410#[derive(Debug, Clone, Serialize, Deserialize)]
2411#[serde(rename_all = "camelCase")]
2412pub struct ContextUsageCategory {
2413    /// Category name (e.g., "system", "conversation", "tools")
2414    pub name: String,
2415    /// Token count for this category
2416    pub token_count: u64,
2417    /// Percentage of total context
2418    #[serde(default)]
2419    pub percentage: f64,
2420}
2421
2422/// API usage statistics including cache information
2423#[derive(Debug, Clone, Serialize, Deserialize)]
2424#[serde(rename_all = "camelCase")]
2425pub struct ApiUsage {
2426    /// Input tokens consumed
2427    #[serde(default)]
2428    pub input_tokens: u64,
2429    /// Output tokens generated
2430    #[serde(default)]
2431    pub output_tokens: u64,
2432    /// Cache read input tokens (prompt caching hits)
2433    #[serde(default)]
2434    pub cache_read_input_tokens: u64,
2435    /// Cache creation input tokens (prompt caching writes)
2436    #[serde(default)]
2437    pub cache_creation_input_tokens: u64,
2438}
2439
2440/// Context usage response from get_context_usage()
2441#[derive(Debug, Clone, Serialize, Deserialize)]
2442#[serde(rename_all = "camelCase")]
2443pub struct ContextUsageResponse {
2444    /// Token usage categories
2445    #[serde(default)]
2446    pub categories: Vec<ContextUsageCategory>,
2447    /// Total tokens in context
2448    #[serde(default)]
2449    pub total_tokens: u64,
2450    /// Maximum context window size
2451    #[serde(default)]
2452    pub max_tokens: u64,
2453    /// Context usage percentage
2454    #[serde(default)]
2455    pub percentage: f64,
2456    /// Active model
2457    #[serde(default)]
2458    pub model: String,
2459    /// Whether auto-compact is enabled
2460    #[serde(default)]
2461    pub is_auto_compact_enabled: bool,
2462    /// Auto-compact threshold (if enabled)
2463    #[serde(default, skip_serializing_if = "Option::is_none")]
2464    pub auto_compact_threshold: Option<u64>,
2465    /// Memory files loaded
2466    #[serde(default)]
2467    pub memory_files: Vec<serde_json::Value>,
2468    /// MCP tools available
2469    #[serde(default)]
2470    pub mcp_tools: Vec<serde_json::Value>,
2471    /// Message breakdown
2472    #[serde(default, skip_serializing_if = "Option::is_none")]
2473    pub message_breakdown: Option<serde_json::Value>,
2474    /// API usage with cache information
2475    #[serde(default, skip_serializing_if = "Option::is_none")]
2476    pub api_usage: Option<ApiUsage>,
2477}
2478
2479/// Task budget configuration
2480#[derive(Debug, Clone, Serialize, Deserialize)]
2481#[serde(rename_all = "camelCase")]
2482pub struct TaskBudget {
2483    /// Maximum number of dollars to spend on a task
2484    #[serde(default, skip_serializing_if = "Option::is_none")]
2485    pub max_cost_usd: Option<f64>,
2486    /// Maximum number of tokens to consume
2487    #[serde(default, skip_serializing_if = "Option::is_none")]
2488    pub max_tokens: Option<u64>,
2489    /// Maximum number of turns
2490    #[serde(default, skip_serializing_if = "Option::is_none")]
2491    pub max_turns: Option<i32>,
2492}
2493
2494/// Result from forking a session
2495#[derive(Debug, Clone, Serialize, Deserialize)]
2496#[serde(rename_all = "camelCase")]
2497pub struct ForkSessionResult {
2498    /// The new session ID
2499    pub session_id: String,
2500}
2501
2502/// SDK Control Protocol request types
2503#[derive(Debug, Clone, Serialize, Deserialize)]
2504#[serde(tag = "type", rename_all = "snake_case")]
2505pub enum SDKControlRequest {
2506    /// Interrupt request
2507    #[serde(rename = "interrupt")]
2508    Interrupt(SDKControlInterruptRequest),
2509    /// Permission request
2510    #[serde(rename = "can_use_tool")]
2511    CanUseTool(SDKControlPermissionRequest),
2512    /// Initialize request
2513    #[serde(rename = "initialize")]
2514    Initialize(SDKControlInitializeRequest),
2515    /// Set permission mode
2516    #[serde(rename = "set_permission_mode")]
2517    SetPermissionMode(SDKControlSetPermissionModeRequest),
2518    /// Set model
2519    #[serde(rename = "set_model")]
2520    SetModel(SDKControlSetModelRequest),
2521    /// Hook callback
2522    #[serde(rename = "hook_callback")]
2523    HookCallback(SDKHookCallbackRequest),
2524    /// MCP message
2525    #[serde(rename = "mcp_message")]
2526    McpMessage(SDKControlMcpMessageRequest),
2527    /// Rewind files (Python SDK v0.1.14+)
2528    #[serde(rename = "rewind_files")]
2529    RewindFiles(SDKControlRewindFilesRequest),
2530    /// Get context usage
2531    #[serde(rename = "get_context_usage")]
2532    GetContextUsage(SDKControlGetContextUsageRequest),
2533    /// Stop a background task
2534    #[serde(rename = "stop_task")]
2535    StopTask(SDKControlStopTaskRequest),
2536    /// Get MCP server status
2537    #[serde(rename = "mcp_status")]
2538    McpStatus(SDKControlMcpStatusRequest),
2539    /// Reconnect an MCP server
2540    #[serde(rename = "mcp_reconnect")]
2541    McpReconnect(SDKControlMcpReconnectRequest),
2542    /// Toggle an MCP server on/off
2543    #[serde(rename = "mcp_toggle")]
2544    McpToggle(SDKControlMcpToggleRequest),
2545}
2546
2547/// Control request types (legacy, keeping for compatibility)
2548#[derive(Debug, Clone, Serialize, Deserialize)]
2549#[serde(tag = "type", rename_all = "lowercase")]
2550pub enum ControlRequest {
2551    /// Interrupt the current operation
2552    Interrupt {
2553        /// Request ID
2554        request_id: String,
2555    },
2556}
2557
2558/// Control response types (legacy, keeping for compatibility)
2559#[derive(Debug, Clone, Serialize, Deserialize)]
2560#[serde(tag = "type", rename_all = "lowercase")]
2561pub enum ControlResponse {
2562    /// Interrupt acknowledged
2563    InterruptAck {
2564        /// Request ID
2565        request_id: String,
2566        /// Whether interrupt was successful
2567        success: bool,
2568    },
2569}
2570
2571#[cfg(test)]
2572mod tests {
2573    use super::*;
2574
2575    #[test]
2576    fn test_permission_mode_serialization() {
2577        let mode = PermissionMode::AcceptEdits;
2578        let json = serde_json::to_string(&mode).unwrap();
2579        assert_eq!(json, r#""acceptEdits""#);
2580
2581        let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
2582        assert_eq!(deserialized, mode);
2583
2584        // Test Plan mode
2585        let plan_mode = PermissionMode::Plan;
2586        let plan_json = serde_json::to_string(&plan_mode).unwrap();
2587        assert_eq!(plan_json, r#""plan""#);
2588
2589        let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
2590        assert_eq!(plan_deserialized, plan_mode);
2591    }
2592
2593    #[test]
2594    fn test_message_serialization() {
2595        let msg = Message::User {
2596            message: UserMessage {
2597                content: "Hello".to_string(),
2598            },
2599        };
2600
2601        let json = serde_json::to_string(&msg).unwrap();
2602        assert!(json.contains(r#""type":"user""#));
2603        assert!(json.contains(r#""content":"Hello""#));
2604
2605        let deserialized: Message = serde_json::from_str(&json).unwrap();
2606        assert_eq!(deserialized, msg);
2607    }
2608
2609    #[test]
2610    #[allow(deprecated)]
2611    fn test_options_builder() {
2612        let options = ClaudeCodeOptions::builder()
2613            .system_prompt("Test prompt")
2614            .model("claude-3-opus")
2615            .permission_mode(PermissionMode::AcceptEdits)
2616            .allow_tool("read")
2617            .allow_tool("write")
2618            .max_turns(10)
2619            .build();
2620
2621        assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
2622        assert_eq!(options.model, Some("claude-3-opus".to_string()));
2623        assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
2624        assert_eq!(options.allowed_tools, vec!["read", "write"]);
2625        assert_eq!(options.max_turns, Some(10));
2626    }
2627
2628    #[test]
2629    fn test_extra_args() {
2630        let mut extra_args = HashMap::new();
2631        extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
2632        extra_args.insert("boolean-flag".to_string(), None);
2633
2634        let options = ClaudeCodeOptions::builder()
2635            .extra_args(extra_args.clone())
2636            .add_extra_arg("another-flag", Some("another-value".to_string()))
2637            .build();
2638
2639        assert_eq!(options.extra_args.len(), 3);
2640        assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
2641        assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
2642        assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
2643    }
2644
2645    #[test]
2646    fn test_thinking_content_serialization() {
2647        let thinking = ThinkingContent {
2648            thinking: "Let me think about this...".to_string(),
2649            signature: "sig123".to_string(),
2650        };
2651
2652        let json = serde_json::to_string(&thinking).unwrap();
2653        assert!(json.contains(r#""thinking":"Let me think about this...""#));
2654        assert!(json.contains(r#""signature":"sig123""#));
2655
2656        let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
2657        assert_eq!(deserialized.thinking, thinking.thinking);
2658        assert_eq!(deserialized.signature, thinking.signature);
2659    }
2660
2661    // ============== v0.4.0 New Feature Tests ==============
2662
2663    #[test]
2664    fn test_tools_config_list_serialization() {
2665        let tools = ToolsConfig::List(vec!["Read".to_string(), "Write".to_string(), "Bash".to_string()]);
2666        let json = serde_json::to_string(&tools).unwrap();
2667
2668        // List variant serializes as JSON array
2669        assert!(json.contains("Read"));
2670        assert!(json.contains("Write"));
2671        assert!(json.contains("Bash"));
2672
2673        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2674        match deserialized {
2675            ToolsConfig::List(list) => {
2676                assert_eq!(list.len(), 3);
2677                assert!(list.contains(&"Read".to_string()));
2678            }
2679            _ => panic!("Expected List variant"),
2680        }
2681    }
2682
2683    #[test]
2684    fn test_tools_config_preset_serialization() {
2685        // Test claude_code preset using the helper method
2686        let preset = ToolsConfig::claude_code_preset();
2687        let json = serde_json::to_string(&preset).unwrap();
2688        assert!(json.contains("preset"));
2689        assert!(json.contains("claude_code"));
2690
2691        // Test Preset variant with custom values
2692        let custom_preset = ToolsConfig::Preset(ToolsPreset {
2693            preset_type: "preset".to_string(),
2694            preset: "custom".to_string(),
2695        });
2696        let json = serde_json::to_string(&custom_preset).unwrap();
2697        assert!(json.contains("custom"));
2698
2699        // Test deserialization
2700        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2701        match deserialized {
2702            ToolsConfig::Preset(p) => assert_eq!(p.preset, "custom"),
2703            _ => panic!("Expected Preset variant"),
2704        }
2705    }
2706
2707    #[test]
2708    fn test_tools_config_helper_methods() {
2709        // Test list() helper
2710        let tools = ToolsConfig::list(vec!["Read".to_string(), "Write".to_string()]);
2711        match tools {
2712            ToolsConfig::List(list) => assert_eq!(list.len(), 2),
2713            _ => panic!("Expected List variant"),
2714        }
2715
2716        // Test none() helper (empty list)
2717        let empty = ToolsConfig::none();
2718        match empty {
2719            ToolsConfig::List(list) => assert!(list.is_empty()),
2720            _ => panic!("Expected empty List variant"),
2721        }
2722
2723        // Test claude_code_preset() helper
2724        let preset = ToolsConfig::claude_code_preset();
2725        match preset {
2726            ToolsConfig::Preset(p) => {
2727                assert_eq!(p.preset_type, "preset");
2728                assert_eq!(p.preset, "claude_code");
2729            }
2730            _ => panic!("Expected Preset variant"),
2731        }
2732    }
2733
2734    #[test]
2735    fn test_sdk_beta_serialization() {
2736        let beta = SdkBeta::Context1M;
2737        let json = serde_json::to_string(&beta).unwrap();
2738        // The enum uses rename = "context-1m-2025-08-07"
2739        assert_eq!(json, r#""context-1m-2025-08-07""#);
2740
2741        // Test Display trait
2742        let display = format!("{}", beta);
2743        assert_eq!(display, "context-1m-2025-08-07");
2744
2745        // Test deserialization
2746        let deserialized: SdkBeta = serde_json::from_str(r#""context-1m-2025-08-07""#).unwrap();
2747        assert!(matches!(deserialized, SdkBeta::Context1M));
2748    }
2749
2750    #[test]
2751    fn test_sandbox_settings_serialization() {
2752        let sandbox = SandboxSettings {
2753            enabled: Some(true),
2754            auto_allow_bash_if_sandboxed: Some(true),
2755            excluded_commands: Some(vec!["git".to_string(), "docker".to_string()]),
2756            allow_unsandboxed_commands: Some(false),
2757            network: Some(SandboxNetworkConfig {
2758                allow_unix_sockets: Some(vec!["/tmp/ssh-agent.sock".to_string()]),
2759                allow_all_unix_sockets: Some(false),
2760                allow_local_binding: Some(true),
2761                http_proxy_port: Some(8080),
2762                socks_proxy_port: Some(1080),
2763            }),
2764            ignore_violations: Some(SandboxIgnoreViolations {
2765                file: Some(vec!["/tmp".to_string(), "/var/log".to_string()]),
2766                network: Some(vec!["localhost".to_string()]),
2767            }),
2768            enable_weaker_nested_sandbox: Some(false),
2769        };
2770
2771        let json = serde_json::to_string(&sandbox).unwrap();
2772        assert!(json.contains("enabled"));
2773        assert!(json.contains("autoAllowBashIfSandboxed")); // camelCase
2774        assert!(json.contains("excludedCommands"));
2775        assert!(json.contains("httpProxyPort"));
2776        assert!(json.contains("8080"));
2777
2778        let deserialized: SandboxSettings = serde_json::from_str(&json).unwrap();
2779        assert!(deserialized.enabled.unwrap());
2780        assert!(deserialized.network.is_some());
2781        assert_eq!(deserialized.network.as_ref().unwrap().http_proxy_port, Some(8080));
2782    }
2783
2784    #[test]
2785    fn test_sandbox_network_config() {
2786        let config = SandboxNetworkConfig {
2787            allow_unix_sockets: Some(vec!["/run/user/1000/keyring/ssh".to_string()]),
2788            allow_all_unix_sockets: Some(false),
2789            allow_local_binding: Some(true),
2790            http_proxy_port: Some(3128),
2791            socks_proxy_port: Some(1080),
2792        };
2793
2794        let json = serde_json::to_string(&config).unwrap();
2795        let deserialized: SandboxNetworkConfig = serde_json::from_str(&json).unwrap();
2796
2797        assert_eq!(deserialized.http_proxy_port, Some(3128));
2798        assert_eq!(deserialized.socks_proxy_port, Some(1080));
2799        assert_eq!(deserialized.allow_local_binding, Some(true));
2800    }
2801
2802    #[test]
2803    fn test_sandbox_ignore_violations() {
2804        let violations = SandboxIgnoreViolations {
2805            file: Some(vec!["/tmp".to_string(), "/var/cache".to_string()]),
2806            network: Some(vec!["127.0.0.1".to_string(), "localhost".to_string()]),
2807        };
2808
2809        let json = serde_json::to_string(&violations).unwrap();
2810        assert!(json.contains("file"));
2811        assert!(json.contains("/tmp"));
2812
2813        let deserialized: SandboxIgnoreViolations = serde_json::from_str(&json).unwrap();
2814        assert_eq!(deserialized.file.as_ref().unwrap().len(), 2);
2815        assert_eq!(deserialized.network.as_ref().unwrap().len(), 2);
2816    }
2817
2818    #[test]
2819    fn test_sandbox_settings_default() {
2820        let sandbox = SandboxSettings::default();
2821        assert!(sandbox.enabled.is_none());
2822        assert!(sandbox.network.is_none());
2823        assert!(sandbox.ignore_violations.is_none());
2824    }
2825
2826    #[test]
2827    fn test_sdk_plugin_config_serialization() {
2828        let plugin = SdkPluginConfig::Local {
2829            path: "/path/to/plugin".to_string()
2830        };
2831
2832        let json = serde_json::to_string(&plugin).unwrap();
2833        assert!(json.contains("local")); // lowercase due to rename_all
2834        assert!(json.contains("/path/to/plugin"));
2835
2836        let deserialized: SdkPluginConfig = serde_json::from_str(&json).unwrap();
2837        match deserialized {
2838            SdkPluginConfig::Local { path } => {
2839                assert_eq!(path, "/path/to/plugin");
2840            }
2841        }
2842    }
2843
2844    #[test]
2845    fn test_sdk_control_rewind_files_request() {
2846        let request = SDKControlRewindFilesRequest {
2847            subtype: "rewind_files".to_string(),
2848            user_message_id: "msg_12345".to_string(),
2849        };
2850
2851        let json = serde_json::to_string(&request).unwrap();
2852        assert!(json.contains("user_message_id"));
2853        assert!(json.contains("msg_12345"));
2854        assert!(json.contains("subtype"));
2855        assert!(json.contains("rewind_files"));
2856
2857        let deserialized: SDKControlRewindFilesRequest = serde_json::from_str(&json).unwrap();
2858        assert_eq!(deserialized.user_message_id, "msg_12345");
2859        assert_eq!(deserialized.subtype, "rewind_files");
2860    }
2861
2862    #[test]
2863    fn test_options_builder_with_new_fields() {
2864        let options = ClaudeCodeOptions::builder()
2865            .tools(ToolsConfig::claude_code_preset())
2866            .add_beta(SdkBeta::Context1M)
2867            .max_budget_usd(10.0)
2868            .fallback_model("claude-3-haiku")
2869            .output_format(serde_json::json!({"type": "object"}))
2870            .enable_file_checkpointing(true)
2871            .sandbox(SandboxSettings::default())
2872            .add_plugin(SdkPluginConfig::Local { path: "/plugin".to_string() })
2873            .auto_download_cli(true)
2874            .build();
2875
2876        // Verify tools
2877        assert!(options.tools.is_some());
2878        match options.tools.as_ref().unwrap() {
2879            ToolsConfig::Preset(preset) => assert_eq!(preset.preset, "claude_code"),
2880            _ => panic!("Expected Preset variant"),
2881        }
2882
2883        // Verify betas
2884        assert_eq!(options.betas.len(), 1);
2885        assert!(matches!(options.betas[0], SdkBeta::Context1M));
2886
2887        // Verify max_budget_usd
2888        assert_eq!(options.max_budget_usd, Some(10.0));
2889
2890        // Verify fallback_model
2891        assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2892
2893        // Verify output_format
2894        assert!(options.output_format.is_some());
2895
2896        // Verify enable_file_checkpointing
2897        assert!(options.enable_file_checkpointing);
2898
2899        // Verify sandbox
2900        assert!(options.sandbox.is_some());
2901
2902        // Verify plugins
2903        assert_eq!(options.plugins.len(), 1);
2904
2905        // Verify auto_download_cli
2906        assert!(options.auto_download_cli);
2907    }
2908
2909    #[test]
2910    fn test_options_builder_with_tools_list() {
2911        let options = ClaudeCodeOptions::builder()
2912            .tools(ToolsConfig::List(vec!["Read".to_string(), "Bash".to_string()]))
2913            .build();
2914
2915        match options.tools.as_ref().unwrap() {
2916            ToolsConfig::List(list) => {
2917                assert_eq!(list.len(), 2);
2918                assert!(list.contains(&"Read".to_string()));
2919                assert!(list.contains(&"Bash".to_string()));
2920            }
2921            _ => panic!("Expected List variant"),
2922        }
2923    }
2924
2925    #[test]
2926    fn test_options_builder_multiple_betas() {
2927        let options = ClaudeCodeOptions::builder()
2928            .add_beta(SdkBeta::Context1M)
2929            .betas(vec![SdkBeta::Context1M])
2930            .build();
2931
2932        // betas() replaces, add_beta() appends - so only 1 from betas()
2933        assert_eq!(options.betas.len(), 1);
2934    }
2935
2936    #[test]
2937    fn test_options_builder_add_beta_accumulates() {
2938        let options = ClaudeCodeOptions::builder()
2939            .add_beta(SdkBeta::Context1M)
2940            .add_beta(SdkBeta::Context1M)
2941            .build();
2942
2943        // add_beta() accumulates
2944        assert_eq!(options.betas.len(), 2);
2945    }
2946
2947    #[test]
2948    fn test_options_builder_multiple_plugins() {
2949        let options = ClaudeCodeOptions::builder()
2950            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2951            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2952            .plugins(vec![SdkPluginConfig::Local { path: "/plugin3".to_string() }])
2953            .build();
2954
2955        // plugins() replaces previous, so only 1
2956        assert_eq!(options.plugins.len(), 1);
2957    }
2958
2959    #[test]
2960    fn test_options_builder_add_plugin_accumulates() {
2961        let options = ClaudeCodeOptions::builder()
2962            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2963            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2964            .add_plugin(SdkPluginConfig::Local { path: "/plugin3".to_string() })
2965            .build();
2966
2967        // add_plugin() accumulates
2968        assert_eq!(options.plugins.len(), 3);
2969    }
2970
2971    #[test]
2972    fn test_message_result_with_structured_output() {
2973        // Test parsing result message with structured_output (snake_case)
2974        let json = r#"{
2975            "type": "result",
2976            "subtype": "success",
2977            "cost_usd": 0.05,
2978            "duration_ms": 1500,
2979            "duration_api_ms": 1200,
2980            "is_error": false,
2981            "num_turns": 3,
2982            "session_id": "session_123",
2983            "structured_output": {"answer": 42}
2984        }"#;
2985
2986        let msg: Message = serde_json::from_str(json).unwrap();
2987        match msg {
2988            Message::Result {
2989                structured_output,
2990                ..
2991            } => {
2992                assert!(structured_output.is_some());
2993                let output = structured_output.unwrap();
2994                assert_eq!(output["answer"], 42);
2995            }
2996            _ => panic!("Expected Result message"),
2997        }
2998    }
2999
3000    #[test]
3001    fn test_message_result_with_structured_output_camel_case() {
3002        // Test parsing result message with structuredOutput (camelCase alias)
3003        let json = r#"{
3004            "type": "result",
3005            "subtype": "success",
3006            "cost_usd": 0.05,
3007            "duration_ms": 1500,
3008            "duration_api_ms": 1200,
3009            "is_error": false,
3010            "num_turns": 3,
3011            "session_id": "session_123",
3012            "structuredOutput": {"name": "test", "value": true}
3013        }"#;
3014
3015        let msg: Message = serde_json::from_str(json).unwrap();
3016        match msg {
3017            Message::Result {
3018                structured_output,
3019                ..
3020            } => {
3021                assert!(structured_output.is_some());
3022                let output = structured_output.unwrap();
3023                assert_eq!(output["name"], "test");
3024                assert_eq!(output["value"], true);
3025            }
3026            _ => panic!("Expected Result message"),
3027        }
3028    }
3029
3030    #[test]
3031    fn test_default_options_new_fields() {
3032        let options = ClaudeCodeOptions::default();
3033
3034        // Verify defaults for new fields
3035        assert!(options.tools.is_none());
3036        assert!(options.betas.is_empty());
3037        assert!(options.max_budget_usd.is_none());
3038        assert!(options.fallback_model.is_none());
3039        assert!(options.output_format.is_none());
3040        assert!(!options.enable_file_checkpointing);
3041        assert!(options.sandbox.is_none());
3042        assert!(options.plugins.is_empty());
3043        assert!(options.user.is_none());
3044        // Note: auto_download_cli defaults to false (Rust bool default)
3045        // Users should explicitly enable it with .auto_download_cli(true)
3046        assert!(!options.auto_download_cli);
3047    }
3048
3049    // === P0-1: DontAsk permission mode ===
3050
3051    #[test]
3052    fn test_permission_mode_dont_ask_serialization() {
3053        let mode = PermissionMode::DontAsk;
3054        let json = serde_json::to_string(&mode).unwrap();
3055        assert_eq!(json, r#""dontAsk""#);
3056
3057        let deserialized: PermissionMode = serde_json::from_str(r#""dontAsk""#).unwrap();
3058        assert_eq!(deserialized, PermissionMode::DontAsk);
3059    }
3060
3061    #[test]
3062    fn test_permission_mode_all_variants_roundtrip() {
3063        let variants = vec![
3064            (PermissionMode::Default, r#""default""#),
3065            (PermissionMode::AcceptEdits, r#""acceptEdits""#),
3066            (PermissionMode::Plan, r#""plan""#),
3067            (PermissionMode::BypassPermissions, r#""bypassPermissions""#),
3068            (PermissionMode::DontAsk, r#""dontAsk""#),
3069        ];
3070        for (mode, expected_json) in variants {
3071            let json = serde_json::to_string(&mode).unwrap();
3072            assert_eq!(json, expected_json, "serialization failed for {:?}", mode);
3073            let back: PermissionMode = serde_json::from_str(&json).unwrap();
3074            assert_eq!(back, mode, "roundtrip failed for {:?}", mode);
3075        }
3076    }
3077
3078    // === P1-2: AgentDefinition new fields ===
3079
3080    #[test]
3081    fn test_agent_definition_all_new_fields() {
3082        let agent = AgentDefinition {
3083            description: "Coder".to_string(),
3084            prompt: "You write code".to_string(),
3085            tools: Some(vec!["Bash".to_string()]),
3086            disallowed_tools: Some(vec!["Write".to_string()]),
3087            model: Some("claude-sonnet-4-20250514".to_string()),
3088            skills: Some(vec!["tdd".to_string()]),
3089            memory: Some("project".to_string()),
3090            mcp_servers: None,
3091            initial_prompt: Some("Start coding".to_string()),
3092            max_turns: Some(50),
3093            background: Some(true),
3094            effort: Some(Effort::High),
3095            permission_mode: Some(PermissionMode::DontAsk),
3096        };
3097
3098        let json = serde_json::to_value(&agent).unwrap();
3099        // Verify camelCase renames
3100        assert_eq!(json["disallowedTools"], serde_json::json!(["Write"]));
3101        assert_eq!(json["initialPrompt"], "Start coding");
3102        assert_eq!(json["maxTurns"], 50);
3103        assert_eq!(json["background"], true);
3104        assert_eq!(json["effort"], "high");
3105        assert_eq!(json["permissionMode"], "dontAsk");
3106
3107        // Roundtrip
3108        let back: AgentDefinition = serde_json::from_value(json).unwrap();
3109        assert_eq!(back.disallowed_tools, Some(vec!["Write".to_string()]));
3110        assert_eq!(back.initial_prompt, Some("Start coding".to_string()));
3111        assert_eq!(back.max_turns, Some(50));
3112        assert_eq!(back.background, Some(true));
3113        assert_eq!(back.effort, Some(Effort::High));
3114        assert_eq!(back.permission_mode, Some(PermissionMode::DontAsk));
3115    }
3116
3117    #[test]
3118    fn test_agent_definition_backward_compat_minimal() {
3119        // Old JSON without new fields should still parse
3120        let json = serde_json::json!({
3121            "description": "Agent",
3122            "prompt": "Do stuff"
3123        });
3124        let agent: AgentDefinition = serde_json::from_value(json).unwrap();
3125        assert!(agent.disallowed_tools.is_none());
3126        assert!(agent.initial_prompt.is_none());
3127        assert!(agent.max_turns.is_none());
3128        assert!(agent.background.is_none());
3129        assert!(agent.effort.is_none());
3130        assert!(agent.permission_mode.is_none());
3131    }
3132
3133    // === New control request types ===
3134
3135    #[test]
3136    fn test_sdk_control_get_context_usage_request() {
3137        let req = SDKControlGetContextUsageRequest::new();
3138        assert_eq!(req.subtype, "get_context_usage");
3139        let json = serde_json::to_value(&req).unwrap();
3140        assert_eq!(json["subtype"], "get_context_usage");
3141    }
3142
3143    #[test]
3144    fn test_sdk_control_stop_task_request() {
3145        let req = SDKControlStopTaskRequest::new("task-abc-123");
3146        assert_eq!(req.subtype, "stop_task");
3147        assert_eq!(req.task_id, "task-abc-123");
3148        let json = serde_json::to_value(&req).unwrap();
3149        assert_eq!(json["task_id"], "task-abc-123");
3150    }
3151
3152    #[test]
3153    fn test_sdk_control_mcp_status_request() {
3154        let req = SDKControlMcpStatusRequest::new();
3155        assert_eq!(req.subtype, "mcp_status");
3156    }
3157
3158    #[test]
3159    fn test_sdk_control_mcp_reconnect_request() {
3160        let req = SDKControlMcpReconnectRequest::new("my-server");
3161        assert_eq!(req.subtype, "mcp_reconnect");
3162        assert_eq!(req.server_name, "my-server");
3163    }
3164
3165    #[test]
3166    fn test_sdk_control_mcp_toggle_request() {
3167        let req = SDKControlMcpToggleRequest::new("my-server", false);
3168        assert_eq!(req.subtype, "mcp_toggle");
3169        assert_eq!(req.server_name, "my-server");
3170        assert!(!req.enabled);
3171    }
3172
3173    // === New response types ===
3174
3175    #[test]
3176    fn test_context_usage_response_deserialize() {
3177        let json = serde_json::json!({
3178            "categories": [
3179                {"name": "system", "tokenCount": 500, "percentage": 10.0},
3180                {"name": "conversation", "tokenCount": 3000, "percentage": 60.0}
3181            ],
3182            "totalTokens": 5000,
3183            "maxTokens": 200000,
3184            "percentage": 2.5,
3185            "model": "claude-sonnet-4-20250514",
3186            "isAutoCompactEnabled": true,
3187            "autoCompactThreshold": 180000,
3188            "memoryFiles": [],
3189            "mcpTools": [],
3190            "apiUsage": {
3191                "inputTokens": 4000,
3192                "outputTokens": 1000,
3193                "cacheReadInputTokens": 2000,
3194                "cacheCreationInputTokens": 500
3195            }
3196        });
3197
3198        let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3199        assert_eq!(resp.categories.len(), 2);
3200        assert_eq!(resp.categories[0].name, "system");
3201        assert_eq!(resp.categories[0].token_count, 500);
3202        assert_eq!(resp.total_tokens, 5000);
3203        assert_eq!(resp.max_tokens, 200000);
3204        assert_eq!(resp.model, "claude-sonnet-4-20250514");
3205        assert!(resp.is_auto_compact_enabled);
3206        assert_eq!(resp.auto_compact_threshold, Some(180000));
3207
3208        let api = resp.api_usage.unwrap();
3209        assert_eq!(api.input_tokens, 4000);
3210        assert_eq!(api.output_tokens, 1000);
3211        assert_eq!(api.cache_read_input_tokens, 2000);
3212        assert_eq!(api.cache_creation_input_tokens, 500);
3213    }
3214
3215    #[test]
3216    fn test_context_usage_response_minimal() {
3217        // Minimal JSON — all defaults should work
3218        let json = serde_json::json!({});
3219        let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3220        assert_eq!(resp.total_tokens, 0);
3221        assert!(resp.categories.is_empty());
3222        assert!(resp.api_usage.is_none());
3223    }
3224
3225    #[test]
3226    fn test_task_budget_serialization() {
3227        let budget = TaskBudget {
3228            max_cost_usd: Some(5.0),
3229            max_tokens: Some(100_000),
3230            max_turns: Some(20),
3231        };
3232        let json = serde_json::to_value(&budget).unwrap();
3233        assert_eq!(json["maxCostUsd"], 5.0);
3234        assert_eq!(json["maxTokens"], 100_000);
3235        assert_eq!(json["maxTurns"], 20);
3236
3237        let back: TaskBudget = serde_json::from_value(json).unwrap();
3238        assert_eq!(back.max_cost_usd, Some(5.0));
3239        assert_eq!(back.max_tokens, Some(100_000));
3240        assert_eq!(back.max_turns, Some(20));
3241    }
3242
3243    #[test]
3244    fn test_fork_session_result_deserialize() {
3245        let json = serde_json::json!({"sessionId": "sess-forked-abc"});
3246        let result: ForkSessionResult = serde_json::from_value(json).unwrap();
3247        assert_eq!(result.session_id, "sess-forked-abc");
3248    }
3249
3250    // === SDKControlRequest enum variants ===
3251
3252    #[test]
3253    fn test_sdk_control_request_new_variants_serialize() {
3254        let req = SDKControlRequest::GetContextUsage(SDKControlGetContextUsageRequest::new());
3255        let json = serde_json::to_value(&req).unwrap();
3256        assert_eq!(json["type"], "get_context_usage");
3257
3258        let req = SDKControlRequest::StopTask(SDKControlStopTaskRequest::new("t1"));
3259        let json = serde_json::to_value(&req).unwrap();
3260        assert_eq!(json["type"], "stop_task");
3261        assert_eq!(json["task_id"], "t1");
3262
3263        let req = SDKControlRequest::McpStatus(SDKControlMcpStatusRequest::new());
3264        let json = serde_json::to_value(&req).unwrap();
3265        assert_eq!(json["type"], "mcp_status");
3266
3267        let req = SDKControlRequest::McpReconnect(SDKControlMcpReconnectRequest::new("srv"));
3268        let json = serde_json::to_value(&req).unwrap();
3269        assert_eq!(json["type"], "mcp_reconnect");
3270
3271        let req = SDKControlRequest::McpToggle(SDKControlMcpToggleRequest::new("srv", true));
3272        let json = serde_json::to_value(&req).unwrap();
3273        assert_eq!(json["type"], "mcp_toggle");
3274        assert_eq!(json["enabled"], true);
3275    }
3276}