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    /// Session ID to use for conversations
1442    ///
1443    /// When set, this ID is used instead of the default "default" session identifier.
1444    /// This allows callers to control the session identity, which is important for
1445    /// resume/fork workflows and for aligning session IDs across different layers.
1446    ///
1447    /// If not set, falls back to "default" (preserving backward compatibility).
1448    pub session_id: Option<String>,
1449}
1450
1451impl std::fmt::Debug for ClaudeCodeOptions {
1452    #[allow(deprecated)]
1453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1454        f.debug_struct("ClaudeCodeOptions")
1455            .field("system_prompt", &self.system_prompt)
1456            .field("append_system_prompt", &self.append_system_prompt)
1457            .field("allowed_tools", &self.allowed_tools)
1458            .field("disallowed_tools", &self.disallowed_tools)
1459            .field("permission_mode", &self.permission_mode)
1460            .field("mcp_servers", &self.mcp_servers)
1461            .field("mcp_tools", &self.mcp_tools)
1462            .field("max_turns", &self.max_turns)
1463            .field("max_thinking_tokens", &self.max_thinking_tokens)
1464            .field("max_output_tokens", &self.max_output_tokens)
1465            .field("model", &self.model)
1466            .field("cwd", &self.cwd)
1467            .field("continue_conversation", &self.continue_conversation)
1468            .field("resume", &self.resume)
1469            .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
1470            .field("settings", &self.settings)
1471            .field("add_dirs", &self.add_dirs)
1472            .field("extra_args", &self.extra_args)
1473            .field("env", &self.env)
1474            .field("debug_stderr", &self.debug_stderr.is_some())
1475            .field("include_partial_messages", &self.include_partial_messages)
1476            .field("can_use_tool", &self.can_use_tool.is_some())
1477            .field("hooks", &self.hooks.is_some())
1478            .field("control_protocol_format", &self.control_protocol_format)
1479            .field("effort", &self.effort)
1480            .field("thinking", &self.thinking)
1481            .field("session_id", &self.session_id)
1482            .finish()
1483    }
1484}
1485
1486impl ClaudeCodeOptions {
1487    /// Create a new options builder
1488    pub fn builder() -> ClaudeCodeOptionsBuilder {
1489        ClaudeCodeOptionsBuilder::default()
1490    }
1491}
1492
1493/// Builder for ClaudeCodeOptions
1494#[derive(Debug, Default)]
1495pub struct ClaudeCodeOptionsBuilder {
1496    options: ClaudeCodeOptions,
1497}
1498
1499impl ClaudeCodeOptionsBuilder {
1500    /// Set system prompt
1501    #[allow(deprecated)]
1502    pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
1503        self.options.system_prompt = Some(prompt.into());
1504        self
1505    }
1506
1507    /// Set append system prompt
1508    #[allow(deprecated)]
1509    pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1510        self.options.append_system_prompt = Some(prompt.into());
1511        self
1512    }
1513
1514    /// Set allowed tools (auto-approval permissions only)
1515    ///
1516    /// **IMPORTANT**: This only controls which tool invocations bypass permission
1517    /// prompts. It does NOT disable or restrict which tools the AI can use.
1518    /// To completely disable tools, use `disallowed_tools()` instead.
1519    ///
1520    /// Example: `vec!["Bash(git:*)".to_string()]` auto-approves git commands.
1521    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1522        self.options.allowed_tools = tools;
1523        self
1524    }
1525
1526    /// Add a single allowed tool (auto-approval permission)
1527    ///
1528    /// See `allowed_tools()` for important usage notes.
1529    pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1530        self.options.allowed_tools.push(tool.into());
1531        self
1532    }
1533
1534    /// Set disallowed tools (completely disabled)
1535    ///
1536    /// **IMPORTANT**: This completely disables the specified tools. The AI will
1537    /// not be able to use these tools at all. This is the correct way to restrict
1538    /// which tools the AI has access to.
1539    ///
1540    /// Example: `vec!["Bash".to_string(), "WebSearch".to_string()]` prevents
1541    /// the AI from using Bash or WebSearch entirely.
1542    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1543        self.options.disallowed_tools = tools;
1544        self
1545    }
1546
1547    /// Add a single disallowed tool (completely disabled)
1548    ///
1549    /// See `disallowed_tools()` for important usage notes.
1550    pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1551        self.options.disallowed_tools.push(tool.into());
1552        self
1553    }
1554
1555    /// Set permission mode
1556    pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1557        self.options.permission_mode = mode;
1558        self
1559    }
1560
1561    /// Add MCP server
1562    pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
1563        self.options.mcp_servers.insert(name.into(), config);
1564        self
1565    }
1566
1567    /// Set all MCP servers from a map
1568    pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1569        self.options.mcp_servers = servers;
1570        self
1571    }
1572
1573    /// Set MCP tools
1574    pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1575        self.options.mcp_tools = tools;
1576        self
1577    }
1578
1579    /// Set max turns
1580    pub fn max_turns(mut self, turns: i32) -> Self {
1581        self.options.max_turns = Some(turns);
1582        self
1583    }
1584
1585    /// Set max thinking tokens
1586    pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1587        self.options.max_thinking_tokens = Some(tokens);
1588        self
1589    }
1590
1591    /// Set max output tokens (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
1592    pub fn max_output_tokens(mut self, tokens: u32) -> Self {
1593        self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
1594        self
1595    }
1596
1597    /// Set model
1598    pub fn model(mut self, model: impl Into<String>) -> Self {
1599        self.options.model = Some(model.into());
1600        self
1601    }
1602
1603    /// Set working directory
1604    pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1605        self.options.cwd = Some(path.into());
1606        self
1607    }
1608
1609    /// Enable continue conversation
1610    pub fn continue_conversation(mut self, enable: bool) -> Self {
1611        self.options.continue_conversation = enable;
1612        self
1613    }
1614
1615    /// Set resume conversation ID
1616    pub fn resume(mut self, id: impl Into<String>) -> Self {
1617        self.options.resume = Some(id.into());
1618        self
1619    }
1620
1621    /// Set session ID for conversations
1622    ///
1623    /// When set, this ID is used instead of the default "default" session identifier.
1624    /// This allows callers to control the session identity used in messages sent to
1625    /// Claude Code CLI.
1626    pub fn session_id(mut self, id: impl Into<String>) -> Self {
1627        self.options.session_id = Some(id.into());
1628        self
1629    }
1630
1631    /// Set permission prompt tool name
1632    pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
1633        self.options.permission_prompt_tool_name = Some(name.into());
1634        self
1635    }
1636
1637    /// Set settings file path
1638    pub fn settings(mut self, settings: impl Into<String>) -> Self {
1639        self.options.settings = Some(settings.into());
1640        self
1641    }
1642
1643    /// Add directories as working directories
1644    pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1645        self.options.add_dirs = dirs;
1646        self
1647    }
1648
1649    /// Add a single directory as working directory
1650    pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1651        self.options.add_dirs.push(dir.into());
1652        self
1653    }
1654
1655    /// Add extra CLI arguments
1656    pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1657        self.options.extra_args = args;
1658        self
1659    }
1660
1661    /// Add a single extra CLI argument
1662    pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
1663        self.options.extra_args.insert(key.into(), value);
1664        self
1665    }
1666
1667    /// Set control protocol format
1668    pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1669        self.options.control_protocol_format = format;
1670        self
1671    }
1672
1673    /// Include partial assistant messages in streaming output
1674    pub fn include_partial_messages(mut self, include: bool) -> Self {
1675        self.options.include_partial_messages = include;
1676        self
1677    }
1678
1679    /// Enable fork_session behavior
1680    pub fn fork_session(mut self, fork: bool) -> Self {
1681        self.options.fork_session = fork;
1682        self
1683    }
1684
1685    /// Set setting sources
1686    pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1687        self.options.setting_sources = Some(sources);
1688        self
1689    }
1690
1691    /// Define programmatic agents
1692    pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1693        self.options.agents = Some(agents);
1694        self
1695    }
1696
1697    /// Set CLI channel buffer size
1698    ///
1699    /// Controls the size of internal communication channels (message, control, stdin buffers).
1700    /// Default is 100. Increase for high-throughput scenarios to prevent message lag.
1701    ///
1702    /// # Arguments
1703    ///
1704    /// * `size` - Buffer size (number of messages that can be queued)
1705    ///
1706    /// # Example
1707    ///
1708    /// ```rust
1709    /// # use cc_sdk::ClaudeCodeOptions;
1710    /// let options = ClaudeCodeOptions::builder()
1711    ///     .cli_channel_buffer_size(500)
1712    ///     .build();
1713    /// ```
1714    pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1715        self.options.cli_channel_buffer_size = Some(size);
1716        self
1717    }
1718
1719    // ========== Phase 3 Builder Methods (Python SDK v0.1.12+ sync) ==========
1720
1721    /// Set tools configuration
1722    ///
1723    /// Controls the base set of tools available to Claude. This is distinct from
1724    /// `allowed_tools` which only controls auto-approval permissions.
1725    ///
1726    /// # Examples
1727    ///
1728    /// ```rust
1729    /// # use cc_sdk::{ClaudeCodeOptions, ToolsConfig};
1730    /// // Enable specific tools only
1731    /// let options = ClaudeCodeOptions::builder()
1732    ///     .tools(ToolsConfig::list(vec!["Read".into(), "Edit".into()]))
1733    ///     .build();
1734    /// ```
1735    pub fn tools(mut self, config: ToolsConfig) -> Self {
1736        self.options.tools = Some(config);
1737        self
1738    }
1739
1740    /// Add SDK beta features
1741    ///
1742    /// Enable Anthropic API beta features like extended context window.
1743    pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1744        self.options.betas = betas;
1745        self
1746    }
1747
1748    /// Add a single SDK beta feature
1749    pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1750        self.options.betas.push(beta);
1751        self
1752    }
1753
1754    /// Set maximum spending limit in USD
1755    ///
1756    /// When the budget is exceeded, the session will automatically terminate.
1757    pub fn max_budget_usd(mut self, budget: f64) -> Self {
1758        self.options.max_budget_usd = Some(budget);
1759        self
1760    }
1761
1762    /// Set fallback model
1763    ///
1764    /// Used when the primary model is unavailable.
1765    pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1766        self.options.fallback_model = Some(model.into());
1767        self
1768    }
1769
1770    /// Set output format for structured outputs
1771    ///
1772    /// Enables JSON schema validation for Claude's responses.
1773    ///
1774    /// # Example
1775    ///
1776    /// ```rust
1777    /// # use cc_sdk::ClaudeCodeOptions;
1778    /// let options = ClaudeCodeOptions::builder()
1779    ///     .output_format(serde_json::json!({
1780    ///         "type": "json_schema",
1781    ///         "schema": {
1782    ///             "type": "object",
1783    ///             "properties": {
1784    ///                 "answer": {"type": "string"}
1785    ///             }
1786    ///         }
1787    ///     }))
1788    ///     .build();
1789    /// ```
1790    pub fn output_format(mut self, format: serde_json::Value) -> Self {
1791        self.options.output_format = Some(format);
1792        self
1793    }
1794
1795    /// Enable file checkpointing
1796    ///
1797    /// When enabled, file changes are tracked and can be rewound to any
1798    /// user message using `ClaudeSDKClient::rewind_files()`.
1799    pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1800        self.options.enable_file_checkpointing = enable;
1801        self
1802    }
1803
1804    /// Set sandbox configuration
1805    ///
1806    /// Controls bash command sandboxing for filesystem and network isolation.
1807    pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1808        self.options.sandbox = Some(settings);
1809        self
1810    }
1811
1812    /// Set plugin configurations
1813    pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1814        self.options.plugins = plugins;
1815        self
1816    }
1817
1818    /// Add a single plugin
1819    pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1820        self.options.plugins.push(plugin);
1821        self
1822    }
1823
1824    /// Set user identifier
1825    pub fn user(mut self, user: impl Into<String>) -> Self {
1826        self.options.user = Some(user.into());
1827        self
1828    }
1829
1830    /// Set stderr callback
1831    ///
1832    /// Called with each line of stderr output from the CLI.
1833    pub fn stderr_callback(mut self, callback: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
1834        self.options.stderr_callback = Some(callback);
1835        self
1836    }
1837
1838    /// Enable automatic CLI download
1839    ///
1840    /// When enabled, the SDK will automatically download and cache the Claude Code
1841    /// CLI binary if it's not found in the system PATH or common installation locations.
1842    ///
1843    /// # Example
1844    ///
1845    /// ```rust
1846    /// # use cc_sdk::ClaudeCodeOptions;
1847    /// let options = ClaudeCodeOptions::builder()
1848    ///     .auto_download_cli(true)
1849    ///     .build();
1850    /// ```
1851    pub fn auto_download_cli(mut self, enable: bool) -> Self {
1852        self.options.auto_download_cli = enable;
1853        self
1854    }
1855
1856    // ========== v0.7.0 Builder Methods (Python SDK parity) ==========
1857
1858    /// Set effort level for Claude's reasoning depth
1859    ///
1860    /// # Example
1861    ///
1862    /// ```rust
1863    /// # use cc_sdk::{ClaudeCodeOptions, Effort};
1864    /// let options = ClaudeCodeOptions::builder()
1865    ///     .effort(Effort::High)
1866    ///     .build();
1867    /// ```
1868    pub fn effort(mut self, effort: Effort) -> Self {
1869        self.options.effort = Some(effort);
1870        self
1871    }
1872
1873    /// Set thinking configuration
1874    ///
1875    /// When set, takes priority over `max_thinking_tokens`.
1876    ///
1877    /// # Example
1878    ///
1879    /// ```rust
1880    /// # use cc_sdk::{ClaudeCodeOptions, ThinkingConfig};
1881    /// let options = ClaudeCodeOptions::builder()
1882    ///     .thinking(ThinkingConfig::Enabled { budget_tokens: 10000 })
1883    ///     .build();
1884    /// ```
1885    pub fn thinking(mut self, config: ThinkingConfig) -> Self {
1886        self.options.thinking = Some(config);
1887        self
1888    }
1889
1890    /// Build the options
1891    pub fn build(self) -> ClaudeCodeOptions {
1892        self.options
1893    }
1894}
1895
1896// ============================================================================
1897// Task Message Types (Python SDK parity)
1898// ============================================================================
1899
1900/// Usage statistics for a task
1901#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1902pub struct TaskUsage {
1903    /// Total tokens used
1904    #[serde(default)]
1905    pub total_tokens: u64,
1906    /// Number of tool uses
1907    #[serde(default)]
1908    pub tool_uses: u64,
1909    /// Duration in milliseconds
1910    #[serde(default)]
1911    pub duration_ms: u64,
1912}
1913
1914/// Task completion status
1915#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1916#[serde(rename_all = "lowercase")]
1917pub enum TaskStatus {
1918    /// Task completed successfully
1919    Completed,
1920    /// Task failed
1921    Failed,
1922    /// Task was stopped
1923    Stopped,
1924}
1925
1926/// Task started message data
1927#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1928pub struct TaskStartedMessage {
1929    /// Task ID
1930    pub task_id: String,
1931    /// Task description
1932    pub description: String,
1933    /// Unique message ID
1934    pub uuid: String,
1935    /// Session ID
1936    pub session_id: String,
1937    /// Associated tool use ID
1938    #[serde(default, skip_serializing_if = "Option::is_none")]
1939    pub tool_use_id: Option<String>,
1940    /// Task type
1941    #[serde(default, skip_serializing_if = "Option::is_none")]
1942    pub task_type: Option<String>,
1943}
1944
1945/// Task progress message data
1946#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1947pub struct TaskProgressMessage {
1948    /// Task ID
1949    pub task_id: String,
1950    /// Task description/status
1951    pub description: String,
1952    /// Usage statistics
1953    #[serde(default)]
1954    pub usage: TaskUsage,
1955    /// Unique message ID
1956    pub uuid: String,
1957    /// Session ID
1958    pub session_id: String,
1959    /// Associated tool use ID
1960    #[serde(default, skip_serializing_if = "Option::is_none")]
1961    pub tool_use_id: Option<String>,
1962    /// Name of last tool used
1963    #[serde(default, skip_serializing_if = "Option::is_none")]
1964    pub last_tool_name: Option<String>,
1965}
1966
1967/// Task notification (completion) message data
1968#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1969pub struct TaskNotificationMessage {
1970    /// Task ID
1971    pub task_id: String,
1972    /// Task completion status
1973    pub status: TaskStatus,
1974    /// Output file path
1975    #[serde(default, skip_serializing_if = "Option::is_none")]
1976    pub output_file: Option<String>,
1977    /// Summary of task results
1978    #[serde(default, skip_serializing_if = "Option::is_none")]
1979    pub summary: Option<String>,
1980    /// Unique message ID
1981    pub uuid: String,
1982    /// Session ID
1983    pub session_id: String,
1984    /// Associated tool use ID
1985    #[serde(default, skip_serializing_if = "Option::is_none")]
1986    pub tool_use_id: Option<String>,
1987    /// Usage statistics
1988    #[serde(default, skip_serializing_if = "Option::is_none")]
1989    pub usage: Option<TaskUsage>,
1990}
1991
1992/// Main message type enum
1993#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1994#[non_exhaustive]
1995#[serde(tag = "type", rename_all = "lowercase")]
1996pub enum Message {
1997    /// User message
1998    User {
1999        /// Message content
2000        message: UserMessage,
2001    },
2002    /// Assistant message
2003    Assistant {
2004        /// Message content
2005        message: AssistantMessage,
2006    },
2007    /// System message
2008    System {
2009        /// Subtype of system message
2010        subtype: String,
2011        /// Additional data
2012        data: serde_json::Value,
2013    },
2014    /// Result message indicating end of turn
2015    Result {
2016        /// Result subtype
2017        subtype: String,
2018        /// Duration in milliseconds
2019        duration_ms: i64,
2020        /// API duration in milliseconds
2021        duration_api_ms: i64,
2022        /// Whether an error occurred
2023        is_error: bool,
2024        /// Number of turns
2025        num_turns: i32,
2026        /// Session ID
2027        session_id: String,
2028        /// Total cost in USD
2029        #[serde(skip_serializing_if = "Option::is_none")]
2030        total_cost_usd: Option<f64>,
2031        /// Usage statistics
2032        #[serde(skip_serializing_if = "Option::is_none")]
2033        usage: Option<serde_json::Value>,
2034        /// Result message
2035        #[serde(skip_serializing_if = "Option::is_none")]
2036        result: Option<String>,
2037        /// Structured output (when output_format is set)
2038        /// Contains the validated JSON response matching the schema
2039        #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
2040        structured_output: Option<serde_json::Value>,
2041        /// Reason the conversation stopped
2042        #[serde(default, skip_serializing_if = "Option::is_none")]
2043        stop_reason: Option<String>,
2044    },
2045
2046    /// Stream event from the CLI
2047    #[serde(rename = "stream_event")]
2048    StreamEvent {
2049        /// Unique message ID
2050        uuid: String,
2051        /// Session ID
2052        session_id: String,
2053        /// Event data
2054        event: serde_json::Value,
2055        /// Parent tool use ID (for subagent events)
2056        #[serde(default, skip_serializing_if = "Option::is_none")]
2057        parent_tool_use_id: Option<String>,
2058    },
2059
2060    /// Rate limit notification
2061    #[serde(rename = "rate_limit")]
2062    RateLimit {
2063        /// Rate limit details
2064        rate_limit_info: RateLimitInfo,
2065        /// Unique message ID
2066        uuid: String,
2067        /// Session ID
2068        session_id: String,
2069    },
2070
2071    /// Unknown message type (forward compatibility)
2072    /// Not deserialized by serde — constructed by message_parser
2073    #[serde(skip)]
2074    Unknown {
2075        /// Original message type string
2076        msg_type: String,
2077        /// Raw JSON data
2078        raw: serde_json::Value,
2079    },
2080}
2081
2082impl Message {
2083    /// Try to extract a TaskStartedMessage from a System message
2084    pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
2085        if let Message::System { subtype, data } = self {
2086            if subtype == "task_started" {
2087                return serde_json::from_value(data.clone()).ok();
2088            }
2089        }
2090        None
2091    }
2092
2093    /// Try to extract a TaskProgressMessage from a System message
2094    pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
2095        if let Message::System { subtype, data } = self {
2096            if subtype == "task_progress" {
2097                return serde_json::from_value(data.clone()).ok();
2098            }
2099        }
2100        None
2101    }
2102
2103    /// Try to extract a TaskNotificationMessage from a System message
2104    pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
2105        if let Message::System { subtype, data } = self {
2106            if subtype == "task_notification" {
2107                return serde_json::from_value(data.clone()).ok();
2108            }
2109        }
2110        None
2111    }
2112}
2113
2114/// User message content
2115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2116pub struct UserMessage {
2117    /// Message content
2118    pub content: String,
2119}
2120
2121/// Assistant message content
2122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2123pub struct AssistantMessage {
2124    /// Content blocks
2125    pub content: Vec<ContentBlock>,
2126    /// Model that generated this message
2127    #[serde(default, skip_serializing_if = "Option::is_none")]
2128    pub model: Option<String>,
2129    /// Token usage statistics
2130    #[serde(default, skip_serializing_if = "Option::is_none")]
2131    pub usage: Option<serde_json::Value>,
2132    /// Error information if the message failed
2133    #[serde(default, skip_serializing_if = "Option::is_none")]
2134    pub error: Option<AssistantMessageError>,
2135    /// Parent tool use ID (for subagent messages)
2136    #[serde(default, skip_serializing_if = "Option::is_none")]
2137    pub parent_tool_use_id: Option<String>,
2138}
2139
2140/// Result message (re-export for convenience)  
2141pub use Message::Result as ResultMessage;
2142/// System message (re-export for convenience)
2143pub use Message::System as SystemMessage;
2144
2145/// Content block types
2146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2147#[serde(untagged)]
2148pub enum ContentBlock {
2149    /// Text content
2150    Text(TextContent),
2151    /// Thinking content
2152    Thinking(ThinkingContent),
2153    /// Tool use request
2154    ToolUse(ToolUseContent),
2155    /// Tool result
2156    ToolResult(ToolResultContent),
2157}
2158
2159/// Text content block
2160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2161pub struct TextContent {
2162    /// Text content
2163    pub text: String,
2164}
2165
2166/// Thinking content block
2167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2168pub struct ThinkingContent {
2169    /// Thinking content
2170    pub thinking: String,
2171    /// Signature
2172    pub signature: String,
2173}
2174
2175/// Tool use content block
2176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2177pub struct ToolUseContent {
2178    /// Tool use ID
2179    pub id: String,
2180    /// Tool name
2181    pub name: String,
2182    /// Tool input parameters
2183    pub input: serde_json::Value,
2184}
2185
2186/// Tool result content block
2187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2188pub struct ToolResultContent {
2189    /// Tool use ID this result corresponds to
2190    pub tool_use_id: String,
2191    /// Result content
2192    #[serde(skip_serializing_if = "Option::is_none")]
2193    pub content: Option<ContentValue>,
2194    /// Whether this is an error result
2195    #[serde(skip_serializing_if = "Option::is_none")]
2196    pub is_error: Option<bool>,
2197}
2198
2199/// Content value for tool results
2200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2201#[serde(untagged)]
2202pub enum ContentValue {
2203    /// Text content
2204    Text(String),
2205    /// Structured content
2206    Structured(Vec<serde_json::Value>),
2207}
2208
2209/// User content structure for internal use
2210#[derive(Debug, Clone, Serialize, Deserialize)]
2211pub struct UserContent {
2212    /// Role (always "user")
2213    pub role: String,
2214    /// Message content
2215    pub content: String,
2216}
2217
2218/// Assistant content structure for internal use
2219#[derive(Debug, Clone, Serialize, Deserialize)]
2220pub struct AssistantContent {
2221    /// Role (always "assistant")
2222    pub role: String,
2223    /// Content blocks
2224    pub content: Vec<ContentBlock>,
2225}
2226
2227/// SDK Control Protocol - Interrupt request
2228#[derive(Debug, Clone, Serialize, Deserialize)]
2229pub struct SDKControlInterruptRequest {
2230    /// Subtype
2231    pub subtype: String,  // "interrupt"
2232}
2233
2234/// SDK Control Protocol - Permission request
2235#[derive(Debug, Clone, Serialize, Deserialize)]
2236pub struct SDKControlPermissionRequest {
2237    /// Subtype
2238    pub subtype: String,  // "can_use_tool"
2239    /// Tool name
2240    #[serde(alias = "toolName")]
2241    pub tool_name: String,
2242    /// Tool input
2243    pub input: serde_json::Value,
2244    /// Permission suggestions
2245    #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
2246    pub permission_suggestions: Option<Vec<PermissionUpdate>>,
2247    /// Blocked path
2248    #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
2249    pub blocked_path: Option<String>,
2250}
2251
2252/// SDK Control Protocol - Initialize request
2253#[derive(Debug, Clone, Serialize, Deserialize)]
2254pub struct SDKControlInitializeRequest {
2255    /// Subtype
2256    pub subtype: String,  // "initialize"
2257    /// Hooks configuration
2258    #[serde(skip_serializing_if = "Option::is_none")]
2259    pub hooks: Option<HashMap<String, serde_json::Value>>,
2260}
2261
2262/// SDK Control Protocol - Set permission mode request
2263#[derive(Debug, Clone, Serialize, Deserialize)]
2264#[serde(rename_all = "camelCase")]
2265pub struct SDKControlSetPermissionModeRequest {
2266    /// Subtype
2267    pub subtype: String,  // "set_permission_mode"
2268    /// Permission mode
2269    pub mode: String,
2270}
2271
2272/// SDK Control Protocol - Set model request
2273#[derive(Debug, Clone, Serialize, Deserialize)]
2274#[serde(rename_all = "camelCase")]
2275pub struct SDKControlSetModelRequest {
2276    /// Subtype
2277    pub subtype: String, // "set_model"
2278    /// Model to set (None to clear)
2279    #[serde(skip_serializing_if = "Option::is_none")]
2280    pub model: Option<String>,
2281}
2282
2283/// SDK Hook callback request
2284#[derive(Debug, Clone, Serialize, Deserialize)]
2285pub struct SDKHookCallbackRequest {
2286    /// Subtype
2287    pub subtype: String,  // "hook_callback"
2288    /// Callback ID
2289    #[serde(alias = "callbackId")]
2290    pub callback_id: String,
2291    /// Input data
2292    pub input: serde_json::Value,
2293    /// Tool use ID
2294    #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
2295    pub tool_use_id: Option<String>,
2296}
2297
2298/// SDK Control Protocol - MCP message request
2299#[derive(Debug, Clone, Serialize, Deserialize)]
2300pub struct SDKControlMcpMessageRequest {
2301    /// Subtype
2302    pub subtype: String,  // "mcp_message"
2303    /// MCP server name
2304    #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
2305    pub mcp_server_name: String,
2306    /// Message to send
2307    pub message: serde_json::Value,
2308}
2309
2310/// SDK Control Protocol - Rewind files request (Python SDK v0.1.14+)
2311///
2312/// Rewinds tracked files to their state at a specific user message.
2313/// Requires `enable_file_checkpointing` to be enabled.
2314#[derive(Debug, Clone, Serialize, Deserialize)]
2315pub struct SDKControlRewindFilesRequest {
2316    /// Subtype (always "rewind_files")
2317    pub subtype: String,
2318    /// UUID of the user message to rewind to
2319    #[serde(alias = "userMessageId")]
2320    pub user_message_id: String,
2321}
2322
2323impl SDKControlRewindFilesRequest {
2324    /// Create a new rewind files request
2325    pub fn new(user_message_id: impl Into<String>) -> Self {
2326        Self {
2327            subtype: "rewind_files".to_string(),
2328            user_message_id: user_message_id.into(),
2329        }
2330    }
2331}
2332
2333/// SDK Control Protocol - Get context usage request
2334#[derive(Debug, Clone, Serialize, Deserialize)]
2335pub struct SDKControlGetContextUsageRequest {
2336    /// Subtype (always "get_context_usage")
2337    pub subtype: String,
2338}
2339
2340impl SDKControlGetContextUsageRequest {
2341    /// Create a new get context usage request
2342    pub fn new() -> Self {
2343        Self {
2344            subtype: "get_context_usage".to_string(),
2345        }
2346    }
2347}
2348
2349/// SDK Control Protocol - Stop task request
2350#[derive(Debug, Clone, Serialize, Deserialize)]
2351pub struct SDKControlStopTaskRequest {
2352    /// Subtype (always "stop_task")
2353    pub subtype: String,
2354    /// Task ID to stop
2355    #[serde(alias = "taskId")]
2356    pub task_id: String,
2357}
2358
2359impl SDKControlStopTaskRequest {
2360    /// Create a new stop task request
2361    pub fn new(task_id: impl Into<String>) -> Self {
2362        Self {
2363            subtype: "stop_task".to_string(),
2364            task_id: task_id.into(),
2365        }
2366    }
2367}
2368
2369/// SDK Control Protocol - Get MCP status request
2370#[derive(Debug, Clone, Serialize, Deserialize)]
2371pub struct SDKControlMcpStatusRequest {
2372    /// Subtype (always "mcp_status")
2373    pub subtype: String,
2374}
2375
2376impl SDKControlMcpStatusRequest {
2377    /// Create a new MCP status request
2378    pub fn new() -> Self {
2379        Self {
2380            subtype: "mcp_status".to_string(),
2381        }
2382    }
2383}
2384
2385/// SDK Control Protocol - Reconnect MCP server request
2386#[derive(Debug, Clone, Serialize, Deserialize)]
2387pub struct SDKControlMcpReconnectRequest {
2388    /// Subtype (always "mcp_reconnect")
2389    pub subtype: String,
2390    /// Server name to reconnect
2391    #[serde(alias = "serverName")]
2392    pub server_name: String,
2393}
2394
2395impl SDKControlMcpReconnectRequest {
2396    /// Create a new MCP reconnect request
2397    pub fn new(server_name: impl Into<String>) -> Self {
2398        Self {
2399            subtype: "mcp_reconnect".to_string(),
2400            server_name: server_name.into(),
2401        }
2402    }
2403}
2404
2405/// SDK Control Protocol - Toggle MCP server request
2406#[derive(Debug, Clone, Serialize, Deserialize)]
2407pub struct SDKControlMcpToggleRequest {
2408    /// Subtype (always "mcp_toggle")
2409    pub subtype: String,
2410    /// Server name to toggle
2411    #[serde(alias = "serverName")]
2412    pub server_name: String,
2413    /// Whether to enable (true) or disable (false)
2414    pub enabled: bool,
2415}
2416
2417impl SDKControlMcpToggleRequest {
2418    /// Create a new MCP toggle request
2419    pub fn new(server_name: impl Into<String>, enabled: bool) -> Self {
2420        Self {
2421            subtype: "mcp_toggle".to_string(),
2422            server_name: server_name.into(),
2423            enabled,
2424        }
2425    }
2426}
2427
2428/// Context usage category
2429#[derive(Debug, Clone, Serialize, Deserialize)]
2430#[serde(rename_all = "camelCase")]
2431pub struct ContextUsageCategory {
2432    /// Category name (e.g., "system", "conversation", "tools")
2433    pub name: String,
2434    /// Token count for this category
2435    pub token_count: u64,
2436    /// Percentage of total context
2437    #[serde(default)]
2438    pub percentage: f64,
2439}
2440
2441/// API usage statistics including cache information
2442#[derive(Debug, Clone, Serialize, Deserialize)]
2443#[serde(rename_all = "camelCase")]
2444pub struct ApiUsage {
2445    /// Input tokens consumed
2446    #[serde(default)]
2447    pub input_tokens: u64,
2448    /// Output tokens generated
2449    #[serde(default)]
2450    pub output_tokens: u64,
2451    /// Cache read input tokens (prompt caching hits)
2452    #[serde(default)]
2453    pub cache_read_input_tokens: u64,
2454    /// Cache creation input tokens (prompt caching writes)
2455    #[serde(default)]
2456    pub cache_creation_input_tokens: u64,
2457}
2458
2459/// Context usage response from get_context_usage()
2460#[derive(Debug, Clone, Serialize, Deserialize)]
2461#[serde(rename_all = "camelCase")]
2462pub struct ContextUsageResponse {
2463    /// Token usage categories
2464    #[serde(default)]
2465    pub categories: Vec<ContextUsageCategory>,
2466    /// Total tokens in context
2467    #[serde(default)]
2468    pub total_tokens: u64,
2469    /// Maximum context window size
2470    #[serde(default)]
2471    pub max_tokens: u64,
2472    /// Context usage percentage
2473    #[serde(default)]
2474    pub percentage: f64,
2475    /// Active model
2476    #[serde(default)]
2477    pub model: String,
2478    /// Whether auto-compact is enabled
2479    #[serde(default)]
2480    pub is_auto_compact_enabled: bool,
2481    /// Auto-compact threshold (if enabled)
2482    #[serde(default, skip_serializing_if = "Option::is_none")]
2483    pub auto_compact_threshold: Option<u64>,
2484    /// Memory files loaded
2485    #[serde(default)]
2486    pub memory_files: Vec<serde_json::Value>,
2487    /// MCP tools available
2488    #[serde(default)]
2489    pub mcp_tools: Vec<serde_json::Value>,
2490    /// Message breakdown
2491    #[serde(default, skip_serializing_if = "Option::is_none")]
2492    pub message_breakdown: Option<serde_json::Value>,
2493    /// API usage with cache information
2494    #[serde(default, skip_serializing_if = "Option::is_none")]
2495    pub api_usage: Option<ApiUsage>,
2496}
2497
2498/// Task budget configuration
2499#[derive(Debug, Clone, Serialize, Deserialize)]
2500#[serde(rename_all = "camelCase")]
2501pub struct TaskBudget {
2502    /// Maximum number of dollars to spend on a task
2503    #[serde(default, skip_serializing_if = "Option::is_none")]
2504    pub max_cost_usd: Option<f64>,
2505    /// Maximum number of tokens to consume
2506    #[serde(default, skip_serializing_if = "Option::is_none")]
2507    pub max_tokens: Option<u64>,
2508    /// Maximum number of turns
2509    #[serde(default, skip_serializing_if = "Option::is_none")]
2510    pub max_turns: Option<i32>,
2511}
2512
2513/// Result from forking a session
2514#[derive(Debug, Clone, Serialize, Deserialize)]
2515#[serde(rename_all = "camelCase")]
2516pub struct ForkSessionResult {
2517    /// The new session ID
2518    pub session_id: String,
2519}
2520
2521/// SDK Control Protocol request types
2522#[derive(Debug, Clone, Serialize, Deserialize)]
2523#[serde(tag = "type", rename_all = "snake_case")]
2524pub enum SDKControlRequest {
2525    /// Interrupt request
2526    #[serde(rename = "interrupt")]
2527    Interrupt(SDKControlInterruptRequest),
2528    /// Permission request
2529    #[serde(rename = "can_use_tool")]
2530    CanUseTool(SDKControlPermissionRequest),
2531    /// Initialize request
2532    #[serde(rename = "initialize")]
2533    Initialize(SDKControlInitializeRequest),
2534    /// Set permission mode
2535    #[serde(rename = "set_permission_mode")]
2536    SetPermissionMode(SDKControlSetPermissionModeRequest),
2537    /// Set model
2538    #[serde(rename = "set_model")]
2539    SetModel(SDKControlSetModelRequest),
2540    /// Hook callback
2541    #[serde(rename = "hook_callback")]
2542    HookCallback(SDKHookCallbackRequest),
2543    /// MCP message
2544    #[serde(rename = "mcp_message")]
2545    McpMessage(SDKControlMcpMessageRequest),
2546    /// Rewind files (Python SDK v0.1.14+)
2547    #[serde(rename = "rewind_files")]
2548    RewindFiles(SDKControlRewindFilesRequest),
2549    /// Get context usage
2550    #[serde(rename = "get_context_usage")]
2551    GetContextUsage(SDKControlGetContextUsageRequest),
2552    /// Stop a background task
2553    #[serde(rename = "stop_task")]
2554    StopTask(SDKControlStopTaskRequest),
2555    /// Get MCP server status
2556    #[serde(rename = "mcp_status")]
2557    McpStatus(SDKControlMcpStatusRequest),
2558    /// Reconnect an MCP server
2559    #[serde(rename = "mcp_reconnect")]
2560    McpReconnect(SDKControlMcpReconnectRequest),
2561    /// Toggle an MCP server on/off
2562    #[serde(rename = "mcp_toggle")]
2563    McpToggle(SDKControlMcpToggleRequest),
2564}
2565
2566/// Control request types (legacy, keeping for compatibility)
2567#[derive(Debug, Clone, Serialize, Deserialize)]
2568#[serde(tag = "type", rename_all = "lowercase")]
2569pub enum ControlRequest {
2570    /// Interrupt the current operation
2571    Interrupt {
2572        /// Request ID
2573        request_id: String,
2574    },
2575}
2576
2577/// Control response types (legacy, keeping for compatibility)
2578#[derive(Debug, Clone, Serialize, Deserialize)]
2579#[serde(tag = "type", rename_all = "lowercase")]
2580pub enum ControlResponse {
2581    /// Interrupt acknowledged
2582    InterruptAck {
2583        /// Request ID
2584        request_id: String,
2585        /// Whether interrupt was successful
2586        success: bool,
2587    },
2588}
2589
2590#[cfg(test)]
2591mod tests {
2592    use super::*;
2593
2594    #[test]
2595    fn test_permission_mode_serialization() {
2596        let mode = PermissionMode::AcceptEdits;
2597        let json = serde_json::to_string(&mode).unwrap();
2598        assert_eq!(json, r#""acceptEdits""#);
2599
2600        let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
2601        assert_eq!(deserialized, mode);
2602
2603        // Test Plan mode
2604        let plan_mode = PermissionMode::Plan;
2605        let plan_json = serde_json::to_string(&plan_mode).unwrap();
2606        assert_eq!(plan_json, r#""plan""#);
2607
2608        let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
2609        assert_eq!(plan_deserialized, plan_mode);
2610    }
2611
2612    #[test]
2613    fn test_message_serialization() {
2614        let msg = Message::User {
2615            message: UserMessage {
2616                content: "Hello".to_string(),
2617            },
2618        };
2619
2620        let json = serde_json::to_string(&msg).unwrap();
2621        assert!(json.contains(r#""type":"user""#));
2622        assert!(json.contains(r#""content":"Hello""#));
2623
2624        let deserialized: Message = serde_json::from_str(&json).unwrap();
2625        assert_eq!(deserialized, msg);
2626    }
2627
2628    #[test]
2629    #[allow(deprecated)]
2630    fn test_options_builder() {
2631        let options = ClaudeCodeOptions::builder()
2632            .system_prompt("Test prompt")
2633            .model("claude-3-opus")
2634            .permission_mode(PermissionMode::AcceptEdits)
2635            .allow_tool("read")
2636            .allow_tool("write")
2637            .max_turns(10)
2638            .build();
2639
2640        assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
2641        assert_eq!(options.model, Some("claude-3-opus".to_string()));
2642        assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
2643        assert_eq!(options.allowed_tools, vec!["read", "write"]);
2644        assert_eq!(options.max_turns, Some(10));
2645    }
2646
2647    #[test]
2648    fn test_extra_args() {
2649        let mut extra_args = HashMap::new();
2650        extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
2651        extra_args.insert("boolean-flag".to_string(), None);
2652
2653        let options = ClaudeCodeOptions::builder()
2654            .extra_args(extra_args.clone())
2655            .add_extra_arg("another-flag", Some("another-value".to_string()))
2656            .build();
2657
2658        assert_eq!(options.extra_args.len(), 3);
2659        assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
2660        assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
2661        assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
2662    }
2663
2664    #[test]
2665    fn test_thinking_content_serialization() {
2666        let thinking = ThinkingContent {
2667            thinking: "Let me think about this...".to_string(),
2668            signature: "sig123".to_string(),
2669        };
2670
2671        let json = serde_json::to_string(&thinking).unwrap();
2672        assert!(json.contains(r#""thinking":"Let me think about this...""#));
2673        assert!(json.contains(r#""signature":"sig123""#));
2674
2675        let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
2676        assert_eq!(deserialized.thinking, thinking.thinking);
2677        assert_eq!(deserialized.signature, thinking.signature);
2678    }
2679
2680    // ============== v0.4.0 New Feature Tests ==============
2681
2682    #[test]
2683    fn test_tools_config_list_serialization() {
2684        let tools = ToolsConfig::List(vec!["Read".to_string(), "Write".to_string(), "Bash".to_string()]);
2685        let json = serde_json::to_string(&tools).unwrap();
2686
2687        // List variant serializes as JSON array
2688        assert!(json.contains("Read"));
2689        assert!(json.contains("Write"));
2690        assert!(json.contains("Bash"));
2691
2692        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2693        match deserialized {
2694            ToolsConfig::List(list) => {
2695                assert_eq!(list.len(), 3);
2696                assert!(list.contains(&"Read".to_string()));
2697            }
2698            _ => panic!("Expected List variant"),
2699        }
2700    }
2701
2702    #[test]
2703    fn test_tools_config_preset_serialization() {
2704        // Test claude_code preset using the helper method
2705        let preset = ToolsConfig::claude_code_preset();
2706        let json = serde_json::to_string(&preset).unwrap();
2707        assert!(json.contains("preset"));
2708        assert!(json.contains("claude_code"));
2709
2710        // Test Preset variant with custom values
2711        let custom_preset = ToolsConfig::Preset(ToolsPreset {
2712            preset_type: "preset".to_string(),
2713            preset: "custom".to_string(),
2714        });
2715        let json = serde_json::to_string(&custom_preset).unwrap();
2716        assert!(json.contains("custom"));
2717
2718        // Test deserialization
2719        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2720        match deserialized {
2721            ToolsConfig::Preset(p) => assert_eq!(p.preset, "custom"),
2722            _ => panic!("Expected Preset variant"),
2723        }
2724    }
2725
2726    #[test]
2727    fn test_tools_config_helper_methods() {
2728        // Test list() helper
2729        let tools = ToolsConfig::list(vec!["Read".to_string(), "Write".to_string()]);
2730        match tools {
2731            ToolsConfig::List(list) => assert_eq!(list.len(), 2),
2732            _ => panic!("Expected List variant"),
2733        }
2734
2735        // Test none() helper (empty list)
2736        let empty = ToolsConfig::none();
2737        match empty {
2738            ToolsConfig::List(list) => assert!(list.is_empty()),
2739            _ => panic!("Expected empty List variant"),
2740        }
2741
2742        // Test claude_code_preset() helper
2743        let preset = ToolsConfig::claude_code_preset();
2744        match preset {
2745            ToolsConfig::Preset(p) => {
2746                assert_eq!(p.preset_type, "preset");
2747                assert_eq!(p.preset, "claude_code");
2748            }
2749            _ => panic!("Expected Preset variant"),
2750        }
2751    }
2752
2753    #[test]
2754    fn test_sdk_beta_serialization() {
2755        let beta = SdkBeta::Context1M;
2756        let json = serde_json::to_string(&beta).unwrap();
2757        // The enum uses rename = "context-1m-2025-08-07"
2758        assert_eq!(json, r#""context-1m-2025-08-07""#);
2759
2760        // Test Display trait
2761        let display = format!("{}", beta);
2762        assert_eq!(display, "context-1m-2025-08-07");
2763
2764        // Test deserialization
2765        let deserialized: SdkBeta = serde_json::from_str(r#""context-1m-2025-08-07""#).unwrap();
2766        assert!(matches!(deserialized, SdkBeta::Context1M));
2767    }
2768
2769    #[test]
2770    fn test_sandbox_settings_serialization() {
2771        let sandbox = SandboxSettings {
2772            enabled: Some(true),
2773            auto_allow_bash_if_sandboxed: Some(true),
2774            excluded_commands: Some(vec!["git".to_string(), "docker".to_string()]),
2775            allow_unsandboxed_commands: Some(false),
2776            network: Some(SandboxNetworkConfig {
2777                allow_unix_sockets: Some(vec!["/tmp/ssh-agent.sock".to_string()]),
2778                allow_all_unix_sockets: Some(false),
2779                allow_local_binding: Some(true),
2780                http_proxy_port: Some(8080),
2781                socks_proxy_port: Some(1080),
2782            }),
2783            ignore_violations: Some(SandboxIgnoreViolations {
2784                file: Some(vec!["/tmp".to_string(), "/var/log".to_string()]),
2785                network: Some(vec!["localhost".to_string()]),
2786            }),
2787            enable_weaker_nested_sandbox: Some(false),
2788        };
2789
2790        let json = serde_json::to_string(&sandbox).unwrap();
2791        assert!(json.contains("enabled"));
2792        assert!(json.contains("autoAllowBashIfSandboxed")); // camelCase
2793        assert!(json.contains("excludedCommands"));
2794        assert!(json.contains("httpProxyPort"));
2795        assert!(json.contains("8080"));
2796
2797        let deserialized: SandboxSettings = serde_json::from_str(&json).unwrap();
2798        assert!(deserialized.enabled.unwrap());
2799        assert!(deserialized.network.is_some());
2800        assert_eq!(deserialized.network.as_ref().unwrap().http_proxy_port, Some(8080));
2801    }
2802
2803    #[test]
2804    fn test_sandbox_network_config() {
2805        let config = SandboxNetworkConfig {
2806            allow_unix_sockets: Some(vec!["/run/user/1000/keyring/ssh".to_string()]),
2807            allow_all_unix_sockets: Some(false),
2808            allow_local_binding: Some(true),
2809            http_proxy_port: Some(3128),
2810            socks_proxy_port: Some(1080),
2811        };
2812
2813        let json = serde_json::to_string(&config).unwrap();
2814        let deserialized: SandboxNetworkConfig = serde_json::from_str(&json).unwrap();
2815
2816        assert_eq!(deserialized.http_proxy_port, Some(3128));
2817        assert_eq!(deserialized.socks_proxy_port, Some(1080));
2818        assert_eq!(deserialized.allow_local_binding, Some(true));
2819    }
2820
2821    #[test]
2822    fn test_sandbox_ignore_violations() {
2823        let violations = SandboxIgnoreViolations {
2824            file: Some(vec!["/tmp".to_string(), "/var/cache".to_string()]),
2825            network: Some(vec!["127.0.0.1".to_string(), "localhost".to_string()]),
2826        };
2827
2828        let json = serde_json::to_string(&violations).unwrap();
2829        assert!(json.contains("file"));
2830        assert!(json.contains("/tmp"));
2831
2832        let deserialized: SandboxIgnoreViolations = serde_json::from_str(&json).unwrap();
2833        assert_eq!(deserialized.file.as_ref().unwrap().len(), 2);
2834        assert_eq!(deserialized.network.as_ref().unwrap().len(), 2);
2835    }
2836
2837    #[test]
2838    fn test_sandbox_settings_default() {
2839        let sandbox = SandboxSettings::default();
2840        assert!(sandbox.enabled.is_none());
2841        assert!(sandbox.network.is_none());
2842        assert!(sandbox.ignore_violations.is_none());
2843    }
2844
2845    #[test]
2846    fn test_sdk_plugin_config_serialization() {
2847        let plugin = SdkPluginConfig::Local {
2848            path: "/path/to/plugin".to_string()
2849        };
2850
2851        let json = serde_json::to_string(&plugin).unwrap();
2852        assert!(json.contains("local")); // lowercase due to rename_all
2853        assert!(json.contains("/path/to/plugin"));
2854
2855        let deserialized: SdkPluginConfig = serde_json::from_str(&json).unwrap();
2856        match deserialized {
2857            SdkPluginConfig::Local { path } => {
2858                assert_eq!(path, "/path/to/plugin");
2859            }
2860        }
2861    }
2862
2863    #[test]
2864    fn test_sdk_control_rewind_files_request() {
2865        let request = SDKControlRewindFilesRequest {
2866            subtype: "rewind_files".to_string(),
2867            user_message_id: "msg_12345".to_string(),
2868        };
2869
2870        let json = serde_json::to_string(&request).unwrap();
2871        assert!(json.contains("user_message_id"));
2872        assert!(json.contains("msg_12345"));
2873        assert!(json.contains("subtype"));
2874        assert!(json.contains("rewind_files"));
2875
2876        let deserialized: SDKControlRewindFilesRequest = serde_json::from_str(&json).unwrap();
2877        assert_eq!(deserialized.user_message_id, "msg_12345");
2878        assert_eq!(deserialized.subtype, "rewind_files");
2879    }
2880
2881    #[test]
2882    fn test_options_builder_with_new_fields() {
2883        let options = ClaudeCodeOptions::builder()
2884            .tools(ToolsConfig::claude_code_preset())
2885            .add_beta(SdkBeta::Context1M)
2886            .max_budget_usd(10.0)
2887            .fallback_model("claude-3-haiku")
2888            .output_format(serde_json::json!({"type": "object"}))
2889            .enable_file_checkpointing(true)
2890            .sandbox(SandboxSettings::default())
2891            .add_plugin(SdkPluginConfig::Local { path: "/plugin".to_string() })
2892            .auto_download_cli(true)
2893            .build();
2894
2895        // Verify tools
2896        assert!(options.tools.is_some());
2897        match options.tools.as_ref().unwrap() {
2898            ToolsConfig::Preset(preset) => assert_eq!(preset.preset, "claude_code"),
2899            _ => panic!("Expected Preset variant"),
2900        }
2901
2902        // Verify betas
2903        assert_eq!(options.betas.len(), 1);
2904        assert!(matches!(options.betas[0], SdkBeta::Context1M));
2905
2906        // Verify max_budget_usd
2907        assert_eq!(options.max_budget_usd, Some(10.0));
2908
2909        // Verify fallback_model
2910        assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2911
2912        // Verify output_format
2913        assert!(options.output_format.is_some());
2914
2915        // Verify enable_file_checkpointing
2916        assert!(options.enable_file_checkpointing);
2917
2918        // Verify sandbox
2919        assert!(options.sandbox.is_some());
2920
2921        // Verify plugins
2922        assert_eq!(options.plugins.len(), 1);
2923
2924        // Verify auto_download_cli
2925        assert!(options.auto_download_cli);
2926    }
2927
2928    #[test]
2929    fn test_options_builder_with_tools_list() {
2930        let options = ClaudeCodeOptions::builder()
2931            .tools(ToolsConfig::List(vec!["Read".to_string(), "Bash".to_string()]))
2932            .build();
2933
2934        match options.tools.as_ref().unwrap() {
2935            ToolsConfig::List(list) => {
2936                assert_eq!(list.len(), 2);
2937                assert!(list.contains(&"Read".to_string()));
2938                assert!(list.contains(&"Bash".to_string()));
2939            }
2940            _ => panic!("Expected List variant"),
2941        }
2942    }
2943
2944    #[test]
2945    fn test_options_builder_multiple_betas() {
2946        let options = ClaudeCodeOptions::builder()
2947            .add_beta(SdkBeta::Context1M)
2948            .betas(vec![SdkBeta::Context1M])
2949            .build();
2950
2951        // betas() replaces, add_beta() appends - so only 1 from betas()
2952        assert_eq!(options.betas.len(), 1);
2953    }
2954
2955    #[test]
2956    fn test_options_builder_add_beta_accumulates() {
2957        let options = ClaudeCodeOptions::builder()
2958            .add_beta(SdkBeta::Context1M)
2959            .add_beta(SdkBeta::Context1M)
2960            .build();
2961
2962        // add_beta() accumulates
2963        assert_eq!(options.betas.len(), 2);
2964    }
2965
2966    #[test]
2967    fn test_options_builder_multiple_plugins() {
2968        let options = ClaudeCodeOptions::builder()
2969            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2970            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2971            .plugins(vec![SdkPluginConfig::Local { path: "/plugin3".to_string() }])
2972            .build();
2973
2974        // plugins() replaces previous, so only 1
2975        assert_eq!(options.plugins.len(), 1);
2976    }
2977
2978    #[test]
2979    fn test_options_builder_add_plugin_accumulates() {
2980        let options = ClaudeCodeOptions::builder()
2981            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2982            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2983            .add_plugin(SdkPluginConfig::Local { path: "/plugin3".to_string() })
2984            .build();
2985
2986        // add_plugin() accumulates
2987        assert_eq!(options.plugins.len(), 3);
2988    }
2989
2990    #[test]
2991    fn test_message_result_with_structured_output() {
2992        // Test parsing result message with structured_output (snake_case)
2993        let json = r#"{
2994            "type": "result",
2995            "subtype": "success",
2996            "cost_usd": 0.05,
2997            "duration_ms": 1500,
2998            "duration_api_ms": 1200,
2999            "is_error": false,
3000            "num_turns": 3,
3001            "session_id": "session_123",
3002            "structured_output": {"answer": 42}
3003        }"#;
3004
3005        let msg: Message = serde_json::from_str(json).unwrap();
3006        match msg {
3007            Message::Result {
3008                structured_output,
3009                ..
3010            } => {
3011                assert!(structured_output.is_some());
3012                let output = structured_output.unwrap();
3013                assert_eq!(output["answer"], 42);
3014            }
3015            _ => panic!("Expected Result message"),
3016        }
3017    }
3018
3019    #[test]
3020    fn test_message_result_with_structured_output_camel_case() {
3021        // Test parsing result message with structuredOutput (camelCase alias)
3022        let json = r#"{
3023            "type": "result",
3024            "subtype": "success",
3025            "cost_usd": 0.05,
3026            "duration_ms": 1500,
3027            "duration_api_ms": 1200,
3028            "is_error": false,
3029            "num_turns": 3,
3030            "session_id": "session_123",
3031            "structuredOutput": {"name": "test", "value": true}
3032        }"#;
3033
3034        let msg: Message = serde_json::from_str(json).unwrap();
3035        match msg {
3036            Message::Result {
3037                structured_output,
3038                ..
3039            } => {
3040                assert!(structured_output.is_some());
3041                let output = structured_output.unwrap();
3042                assert_eq!(output["name"], "test");
3043                assert_eq!(output["value"], true);
3044            }
3045            _ => panic!("Expected Result message"),
3046        }
3047    }
3048
3049    #[test]
3050    fn test_default_options_new_fields() {
3051        let options = ClaudeCodeOptions::default();
3052
3053        // Verify defaults for new fields
3054        assert!(options.tools.is_none());
3055        assert!(options.betas.is_empty());
3056        assert!(options.max_budget_usd.is_none());
3057        assert!(options.fallback_model.is_none());
3058        assert!(options.output_format.is_none());
3059        assert!(!options.enable_file_checkpointing);
3060        assert!(options.sandbox.is_none());
3061        assert!(options.plugins.is_empty());
3062        assert!(options.user.is_none());
3063        // Note: auto_download_cli defaults to false (Rust bool default)
3064        // Users should explicitly enable it with .auto_download_cli(true)
3065        assert!(!options.auto_download_cli);
3066    }
3067
3068    // === P0-1: DontAsk permission mode ===
3069
3070    #[test]
3071    fn test_permission_mode_dont_ask_serialization() {
3072        let mode = PermissionMode::DontAsk;
3073        let json = serde_json::to_string(&mode).unwrap();
3074        assert_eq!(json, r#""dontAsk""#);
3075
3076        let deserialized: PermissionMode = serde_json::from_str(r#""dontAsk""#).unwrap();
3077        assert_eq!(deserialized, PermissionMode::DontAsk);
3078    }
3079
3080    #[test]
3081    fn test_permission_mode_all_variants_roundtrip() {
3082        let variants = vec![
3083            (PermissionMode::Default, r#""default""#),
3084            (PermissionMode::AcceptEdits, r#""acceptEdits""#),
3085            (PermissionMode::Plan, r#""plan""#),
3086            (PermissionMode::BypassPermissions, r#""bypassPermissions""#),
3087            (PermissionMode::DontAsk, r#""dontAsk""#),
3088        ];
3089        for (mode, expected_json) in variants {
3090            let json = serde_json::to_string(&mode).unwrap();
3091            assert_eq!(json, expected_json, "serialization failed for {:?}", mode);
3092            let back: PermissionMode = serde_json::from_str(&json).unwrap();
3093            assert_eq!(back, mode, "roundtrip failed for {:?}", mode);
3094        }
3095    }
3096
3097    // === P1-2: AgentDefinition new fields ===
3098
3099    #[test]
3100    fn test_agent_definition_all_new_fields() {
3101        let agent = AgentDefinition {
3102            description: "Coder".to_string(),
3103            prompt: "You write code".to_string(),
3104            tools: Some(vec!["Bash".to_string()]),
3105            disallowed_tools: Some(vec!["Write".to_string()]),
3106            model: Some("claude-sonnet-4-20250514".to_string()),
3107            skills: Some(vec!["tdd".to_string()]),
3108            memory: Some("project".to_string()),
3109            mcp_servers: None,
3110            initial_prompt: Some("Start coding".to_string()),
3111            max_turns: Some(50),
3112            background: Some(true),
3113            effort: Some(Effort::High),
3114            permission_mode: Some(PermissionMode::DontAsk),
3115        };
3116
3117        let json = serde_json::to_value(&agent).unwrap();
3118        // Verify camelCase renames
3119        assert_eq!(json["disallowedTools"], serde_json::json!(["Write"]));
3120        assert_eq!(json["initialPrompt"], "Start coding");
3121        assert_eq!(json["maxTurns"], 50);
3122        assert_eq!(json["background"], true);
3123        assert_eq!(json["effort"], "high");
3124        assert_eq!(json["permissionMode"], "dontAsk");
3125
3126        // Roundtrip
3127        let back: AgentDefinition = serde_json::from_value(json).unwrap();
3128        assert_eq!(back.disallowed_tools, Some(vec!["Write".to_string()]));
3129        assert_eq!(back.initial_prompt, Some("Start coding".to_string()));
3130        assert_eq!(back.max_turns, Some(50));
3131        assert_eq!(back.background, Some(true));
3132        assert_eq!(back.effort, Some(Effort::High));
3133        assert_eq!(back.permission_mode, Some(PermissionMode::DontAsk));
3134    }
3135
3136    #[test]
3137    fn test_agent_definition_backward_compat_minimal() {
3138        // Old JSON without new fields should still parse
3139        let json = serde_json::json!({
3140            "description": "Agent",
3141            "prompt": "Do stuff"
3142        });
3143        let agent: AgentDefinition = serde_json::from_value(json).unwrap();
3144        assert!(agent.disallowed_tools.is_none());
3145        assert!(agent.initial_prompt.is_none());
3146        assert!(agent.max_turns.is_none());
3147        assert!(agent.background.is_none());
3148        assert!(agent.effort.is_none());
3149        assert!(agent.permission_mode.is_none());
3150    }
3151
3152    // === New control request types ===
3153
3154    #[test]
3155    fn test_sdk_control_get_context_usage_request() {
3156        let req = SDKControlGetContextUsageRequest::new();
3157        assert_eq!(req.subtype, "get_context_usage");
3158        let json = serde_json::to_value(&req).unwrap();
3159        assert_eq!(json["subtype"], "get_context_usage");
3160    }
3161
3162    #[test]
3163    fn test_sdk_control_stop_task_request() {
3164        let req = SDKControlStopTaskRequest::new("task-abc-123");
3165        assert_eq!(req.subtype, "stop_task");
3166        assert_eq!(req.task_id, "task-abc-123");
3167        let json = serde_json::to_value(&req).unwrap();
3168        assert_eq!(json["task_id"], "task-abc-123");
3169    }
3170
3171    #[test]
3172    fn test_sdk_control_mcp_status_request() {
3173        let req = SDKControlMcpStatusRequest::new();
3174        assert_eq!(req.subtype, "mcp_status");
3175    }
3176
3177    #[test]
3178    fn test_sdk_control_mcp_reconnect_request() {
3179        let req = SDKControlMcpReconnectRequest::new("my-server");
3180        assert_eq!(req.subtype, "mcp_reconnect");
3181        assert_eq!(req.server_name, "my-server");
3182    }
3183
3184    #[test]
3185    fn test_sdk_control_mcp_toggle_request() {
3186        let req = SDKControlMcpToggleRequest::new("my-server", false);
3187        assert_eq!(req.subtype, "mcp_toggle");
3188        assert_eq!(req.server_name, "my-server");
3189        assert!(!req.enabled);
3190    }
3191
3192    // === New response types ===
3193
3194    #[test]
3195    fn test_context_usage_response_deserialize() {
3196        let json = serde_json::json!({
3197            "categories": [
3198                {"name": "system", "tokenCount": 500, "percentage": 10.0},
3199                {"name": "conversation", "tokenCount": 3000, "percentage": 60.0}
3200            ],
3201            "totalTokens": 5000,
3202            "maxTokens": 200000,
3203            "percentage": 2.5,
3204            "model": "claude-sonnet-4-20250514",
3205            "isAutoCompactEnabled": true,
3206            "autoCompactThreshold": 180000,
3207            "memoryFiles": [],
3208            "mcpTools": [],
3209            "apiUsage": {
3210                "inputTokens": 4000,
3211                "outputTokens": 1000,
3212                "cacheReadInputTokens": 2000,
3213                "cacheCreationInputTokens": 500
3214            }
3215        });
3216
3217        let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3218        assert_eq!(resp.categories.len(), 2);
3219        assert_eq!(resp.categories[0].name, "system");
3220        assert_eq!(resp.categories[0].token_count, 500);
3221        assert_eq!(resp.total_tokens, 5000);
3222        assert_eq!(resp.max_tokens, 200000);
3223        assert_eq!(resp.model, "claude-sonnet-4-20250514");
3224        assert!(resp.is_auto_compact_enabled);
3225        assert_eq!(resp.auto_compact_threshold, Some(180000));
3226
3227        let api = resp.api_usage.unwrap();
3228        assert_eq!(api.input_tokens, 4000);
3229        assert_eq!(api.output_tokens, 1000);
3230        assert_eq!(api.cache_read_input_tokens, 2000);
3231        assert_eq!(api.cache_creation_input_tokens, 500);
3232    }
3233
3234    #[test]
3235    fn test_context_usage_response_minimal() {
3236        // Minimal JSON — all defaults should work
3237        let json = serde_json::json!({});
3238        let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3239        assert_eq!(resp.total_tokens, 0);
3240        assert!(resp.categories.is_empty());
3241        assert!(resp.api_usage.is_none());
3242    }
3243
3244    #[test]
3245    fn test_task_budget_serialization() {
3246        let budget = TaskBudget {
3247            max_cost_usd: Some(5.0),
3248            max_tokens: Some(100_000),
3249            max_turns: Some(20),
3250        };
3251        let json = serde_json::to_value(&budget).unwrap();
3252        assert_eq!(json["maxCostUsd"], 5.0);
3253        assert_eq!(json["maxTokens"], 100_000);
3254        assert_eq!(json["maxTurns"], 20);
3255
3256        let back: TaskBudget = serde_json::from_value(json).unwrap();
3257        assert_eq!(back.max_cost_usd, Some(5.0));
3258        assert_eq!(back.max_tokens, Some(100_000));
3259        assert_eq!(back.max_turns, Some(20));
3260    }
3261
3262    #[test]
3263    fn test_fork_session_result_deserialize() {
3264        let json = serde_json::json!({"sessionId": "sess-forked-abc"});
3265        let result: ForkSessionResult = serde_json::from_value(json).unwrap();
3266        assert_eq!(result.session_id, "sess-forked-abc");
3267    }
3268
3269    // === SDKControlRequest enum variants ===
3270
3271    #[test]
3272    fn test_sdk_control_request_new_variants_serialize() {
3273        let req = SDKControlRequest::GetContextUsage(SDKControlGetContextUsageRequest::new());
3274        let json = serde_json::to_value(&req).unwrap();
3275        assert_eq!(json["type"], "get_context_usage");
3276
3277        let req = SDKControlRequest::StopTask(SDKControlStopTaskRequest::new("t1"));
3278        let json = serde_json::to_value(&req).unwrap();
3279        assert_eq!(json["type"], "stop_task");
3280        assert_eq!(json["task_id"], "t1");
3281
3282        let req = SDKControlRequest::McpStatus(SDKControlMcpStatusRequest::new());
3283        let json = serde_json::to_value(&req).unwrap();
3284        assert_eq!(json["type"], "mcp_status");
3285
3286        let req = SDKControlRequest::McpReconnect(SDKControlMcpReconnectRequest::new("srv"));
3287        let json = serde_json::to_value(&req).unwrap();
3288        assert_eq!(json["type"], "mcp_reconnect");
3289
3290        let req = SDKControlRequest::McpToggle(SDKControlMcpToggleRequest::new("srv", true));
3291        let json = serde_json::to_value(&req).unwrap();
3292        assert_eq!(json["type"], "mcp_toggle");
3293        assert_eq!(json["enabled"], true);
3294    }
3295}