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#[serde(rename_all = "camelCase")]
18pub enum PermissionMode {
19    /// Default mode - CLI prompts for dangerous tools
20    Default,
21    /// Auto-accept file edits
22    AcceptEdits,
23    /// Plan mode - for planning tasks
24    Plan,
25    /// Allow all tools without prompting (use with caution)
26    BypassPermissions,
27}
28
29// ============================================================================
30// SDK Beta Features (matching Python SDK v0.1.12+)
31// ============================================================================
32
33/// SDK Beta features - see https://docs.anthropic.com/en/api/beta-headers
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub enum SdkBeta {
36    /// Extended context window (1M tokens)
37    #[serde(rename = "context-1m-2025-08-07")]
38    Context1M,
39}
40
41impl std::fmt::Display for SdkBeta {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            SdkBeta::Context1M => write!(f, "context-1m-2025-08-07"),
45        }
46    }
47}
48
49// ============================================================================
50// Tools Configuration (matching Python SDK v0.1.12+)
51// ============================================================================
52
53/// Tools configuration for controlling available tools
54///
55/// This controls the base set of tools available to Claude, distinct from
56/// `allowed_tools` which only controls auto-approval permissions.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(untagged)]
59pub enum ToolsConfig {
60    /// List of specific tool names to enable
61    /// Example: `["Read", "Edit", "Bash"]`
62    List(Vec<String>),
63    /// Preset-based tools configuration
64    Preset(ToolsPreset),
65}
66
67/// Tools preset configuration
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ToolsPreset {
70    /// Type identifier (always "preset")
71    #[serde(rename = "type")]
72    pub preset_type: String,
73    /// Preset name (e.g., "claude_code")
74    pub preset: String,
75}
76
77impl ToolsConfig {
78    /// Create a new tools list
79    pub fn list(tools: Vec<String>) -> Self {
80        ToolsConfig::List(tools)
81    }
82
83    /// Create an empty tools list (disables all built-in tools)
84    pub fn none() -> Self {
85        ToolsConfig::List(vec![])
86    }
87
88    /// Create the claude_code preset
89    pub fn claude_code_preset() -> Self {
90        ToolsConfig::Preset(ToolsPreset {
91            preset_type: "preset".to_string(),
92            preset: "claude_code".to_string(),
93        })
94    }
95}
96
97// ============================================================================
98// Sandbox Configuration (matching Python SDK)
99// ============================================================================
100
101/// Network configuration for sandbox
102#[derive(Debug, Clone, Default, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct SandboxNetworkConfig {
105    /// Unix socket paths accessible in sandbox (e.g., SSH agents)
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub allow_unix_sockets: Option<Vec<String>>,
108    /// Allow all Unix sockets (less secure)
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub allow_all_unix_sockets: Option<bool>,
111    /// Allow binding to localhost ports (macOS only)
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub allow_local_binding: Option<bool>,
114    /// HTTP proxy port if bringing your own proxy
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub http_proxy_port: Option<u16>,
117    /// SOCKS5 proxy port if bringing your own proxy
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub socks_proxy_port: Option<u16>,
120}
121
122/// Violations to ignore in sandbox
123#[derive(Debug, Clone, Default, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct SandboxIgnoreViolations {
126    /// File paths for which violations should be ignored
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub file: Option<Vec<String>>,
129    /// Network hosts for which violations should be ignored
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub network: Option<Vec<String>>,
132}
133
134/// Sandbox settings configuration
135///
136/// Controls how Claude Code sandboxes bash commands for filesystem
137/// and network isolation.
138#[derive(Debug, Clone, Default, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct SandboxSettings {
141    /// Enable bash sandboxing (macOS/Linux only)
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub enabled: Option<bool>,
144    /// Auto-approve bash commands when sandboxed (default: true)
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub auto_allow_bash_if_sandboxed: Option<bool>,
147    /// Commands that should run outside the sandbox (e.g., ["git", "docker"])
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub excluded_commands: Option<Vec<String>>,
150    /// Allow commands to bypass sandbox via dangerouslyDisableSandbox
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub allow_unsandboxed_commands: Option<bool>,
153    /// Network configuration for sandbox
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub network: Option<SandboxNetworkConfig>,
156    /// Violations to ignore
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub ignore_violations: Option<SandboxIgnoreViolations>,
159    /// Enable weaker sandbox for unprivileged Docker environments (Linux only)
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub enable_weaker_nested_sandbox: Option<bool>,
162}
163
164// ============================================================================
165// Plugin Configuration (matching Python SDK v0.1.5+)
166// ============================================================================
167
168/// SDK plugin configuration
169#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(tag = "type", rename_all = "lowercase")]
171pub enum SdkPluginConfig {
172    /// Local plugin loaded from filesystem path
173    Local {
174        /// Path to the plugin directory
175        path: String,
176    },
177}
178
179impl Default for PermissionMode {
180    fn default() -> Self {
181        Self::Default
182    }
183}
184
185/// Control protocol format for sending messages
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub enum ControlProtocolFormat {
188    /// Legacy format: {"type":"sdk_control_request","request":{...}}
189    Legacy,
190    /// New format: {"type":"control","control":{...}}
191    Control,
192    /// Auto-detect based on CLI capabilities (default to Legacy for compatibility)
193    Auto,
194}
195
196impl Default for ControlProtocolFormat {
197    fn default() -> Self {
198        // Default to Legacy for maximum compatibility
199        Self::Legacy
200    }
201}
202
203/// MCP (Model Context Protocol) server configuration
204#[derive(Clone)]
205pub enum McpServerConfig {
206    /// Standard I/O based MCP server
207    Stdio {
208        /// Command to execute
209        command: String,
210        /// Command arguments
211        args: Option<Vec<String>>,
212        /// Environment variables
213        env: Option<HashMap<String, String>>,
214    },
215    /// Server-Sent Events based MCP server
216    Sse {
217        /// Server URL
218        url: String,
219        /// HTTP headers
220        headers: Option<HashMap<String, String>>,
221    },
222    /// HTTP-based MCP server
223    Http {
224        /// Server URL
225        url: String,
226        /// HTTP headers
227        headers: Option<HashMap<String, String>>,
228    },
229    /// SDK MCP server (in-process)
230    Sdk {
231        /// Server name
232        name: String,
233        /// Server instance
234        instance: Arc<dyn std::any::Any + Send + Sync>,
235    },
236}
237
238impl std::fmt::Debug for McpServerConfig {
239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240        match self {
241            Self::Stdio { command, args, env } => f
242                .debug_struct("Stdio")
243                .field("command", command)
244                .field("args", args)
245                .field("env", env)
246                .finish(),
247            Self::Sse { url, headers } => f
248                .debug_struct("Sse")
249                .field("url", url)
250                .field("headers", headers)
251                .finish(),
252            Self::Http { url, headers } => f
253                .debug_struct("Http")
254                .field("url", url)
255                .field("headers", headers)
256                .finish(),
257            Self::Sdk { name, .. } => f
258                .debug_struct("Sdk")
259                .field("name", name)
260                .field("instance", &"<Arc<dyn Any>>")
261                .finish(),
262        }
263    }
264}
265
266impl Serialize for McpServerConfig {
267    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
268    where
269        S: serde::Serializer,
270    {
271        use serde::ser::SerializeMap;
272        let mut map = serializer.serialize_map(None)?;
273
274        match self {
275            Self::Stdio { command, args, env } => {
276                map.serialize_entry("type", "stdio")?;
277                map.serialize_entry("command", command)?;
278                if let Some(args) = args {
279                    map.serialize_entry("args", args)?;
280                }
281                if let Some(env) = env {
282                    map.serialize_entry("env", env)?;
283                }
284            }
285            Self::Sse { url, headers } => {
286                map.serialize_entry("type", "sse")?;
287                map.serialize_entry("url", url)?;
288                if let Some(headers) = headers {
289                    map.serialize_entry("headers", headers)?;
290                }
291            }
292            Self::Http { url, headers } => {
293                map.serialize_entry("type", "http")?;
294                map.serialize_entry("url", url)?;
295                if let Some(headers) = headers {
296                    map.serialize_entry("headers", headers)?;
297                }
298            }
299            Self::Sdk { name, .. } => {
300                map.serialize_entry("type", "sdk")?;
301                map.serialize_entry("name", name)?;
302            }
303        }
304
305        map.end()
306    }
307}
308
309impl<'de> Deserialize<'de> for McpServerConfig {
310    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
311    where
312        D: serde::Deserializer<'de>,
313    {
314        #[derive(Deserialize)]
315        #[serde(tag = "type", rename_all = "lowercase")]
316        enum McpServerConfigHelper {
317            Stdio {
318                command: String,
319                #[serde(skip_serializing_if = "Option::is_none")]
320                args: Option<Vec<String>>,
321                #[serde(skip_serializing_if = "Option::is_none")]
322                env: Option<HashMap<String, String>>,
323            },
324            Sse {
325                url: String,
326                #[serde(skip_serializing_if = "Option::is_none")]
327                headers: Option<HashMap<String, String>>,
328            },
329            Http {
330                url: String,
331                #[serde(skip_serializing_if = "Option::is_none")]
332                headers: Option<HashMap<String, String>>,
333            },
334        }
335
336        let helper = McpServerConfigHelper::deserialize(deserializer)?;
337        Ok(match helper {
338            McpServerConfigHelper::Stdio { command, args, env } => {
339                McpServerConfig::Stdio { command, args, env }
340            }
341            McpServerConfigHelper::Sse { url, headers } => {
342                McpServerConfig::Sse { url, headers }
343            }
344            McpServerConfigHelper::Http { url, headers } => {
345                McpServerConfig::Http { url, headers }
346            }
347        })
348    }
349}
350
351/// Permission update destination
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub enum PermissionUpdateDestination {
355    /// User settings
356    UserSettings,
357    /// Project settings
358    ProjectSettings,
359    /// Local settings
360    LocalSettings,
361    /// Session
362    Session,
363}
364
365/// Permission behavior
366#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub enum PermissionBehavior {
369    /// Allow the action
370    Allow,
371    /// Deny the action
372    Deny,
373    /// Ask the user
374    Ask,
375}
376
377/// Permission rule value
378#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct PermissionRuleValue {
380    /// Tool name
381    pub tool_name: String,
382    /// Rule content
383    pub rule_content: Option<String>,
384}
385
386/// Permission update type
387#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub enum PermissionUpdateType {
390    /// Add rules
391    AddRules,
392    /// Replace rules
393    ReplaceRules,
394    /// Remove rules
395    RemoveRules,
396    /// Set mode
397    SetMode,
398    /// Add directories
399    AddDirectories,
400    /// Remove directories
401    RemoveDirectories,
402}
403
404/// Permission update
405#[derive(Debug, Clone, Serialize, Deserialize)]
406#[serde(rename_all = "camelCase")]
407pub struct PermissionUpdate {
408    /// Update type
409    #[serde(rename = "type")]
410    pub update_type: PermissionUpdateType,
411    /// Rules to update
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub rules: Option<Vec<PermissionRuleValue>>,
414    /// Behavior to set
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub behavior: Option<PermissionBehavior>,
417    /// Mode to set
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub mode: Option<PermissionMode>,
420    /// Directories to add/remove
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub directories: Option<Vec<String>>,
423    /// Destination for the update
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub destination: Option<PermissionUpdateDestination>,
426}
427
428/// Tool permission context
429#[derive(Debug, Clone)]
430pub struct ToolPermissionContext {
431    /// Abort signal (future support)
432    pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
433    /// Permission suggestions from CLI
434    pub suggestions: Vec<PermissionUpdate>,
435}
436
437/// Permission result - Allow
438#[derive(Debug, Clone)]
439pub struct PermissionResultAllow {
440    /// Updated input parameters
441    pub updated_input: Option<serde_json::Value>,
442    /// Updated permissions
443    pub updated_permissions: Option<Vec<PermissionUpdate>>,
444}
445
446/// Permission result - Deny
447#[derive(Debug, Clone)]
448pub struct PermissionResultDeny {
449    /// Denial message
450    pub message: String,
451    /// Whether to interrupt the conversation
452    pub interrupt: bool,
453}
454
455/// Permission result
456#[derive(Debug, Clone)]
457pub enum PermissionResult {
458    /// Allow the tool use
459    Allow(PermissionResultAllow),
460    /// Deny the tool use
461    Deny(PermissionResultDeny),
462}
463
464/// Tool permission callback trait
465#[async_trait]
466pub trait CanUseTool: Send + Sync {
467    /// Check if a tool can be used
468    async fn can_use_tool(
469        &self,
470        tool_name: &str,
471        input: &serde_json::Value,
472        context: &ToolPermissionContext,
473    ) -> PermissionResult;
474}
475
476/// Hook context
477#[derive(Debug, Clone)]
478pub struct HookContext {
479    /// Abort signal (future support)
480    pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
481}
482
483// ============================================================================
484// Hook Input Types (Strongly-typed hook inputs for type safety)
485// ============================================================================
486
487/// Base hook input fields present across many hook events
488#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct BaseHookInput {
490    /// Session ID for this conversation
491    pub session_id: String,
492    /// Path to the transcript file
493    pub transcript_path: String,
494    /// Current working directory
495    pub cwd: String,
496    /// Permission mode (optional)
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub permission_mode: Option<String>,
499}
500
501/// Input data for PreToolUse hook events
502#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct PreToolUseHookInput {
504    /// Session ID for this conversation
505    pub session_id: String,
506    /// Path to the transcript file
507    pub transcript_path: String,
508    /// Current working directory
509    pub cwd: String,
510    /// Permission mode (optional)
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub permission_mode: Option<String>,
513    /// Name of the tool being used
514    pub tool_name: String,
515    /// Input parameters for the tool
516    pub tool_input: serde_json::Value,
517    /// Tool use ID
518    pub tool_use_id: String,
519}
520
521/// Input data for PostToolUse hook events
522#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct PostToolUseHookInput {
524    /// Session ID for this conversation
525    pub session_id: String,
526    /// Path to the transcript file
527    pub transcript_path: String,
528    /// Current working directory
529    pub cwd: String,
530    /// Permission mode (optional)
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub permission_mode: Option<String>,
533    /// Name of the tool that was used
534    pub tool_name: String,
535    /// Input parameters that were passed to the tool
536    pub tool_input: serde_json::Value,
537    /// Response from the tool execution
538    pub tool_response: serde_json::Value,
539    /// Tool use ID
540    pub tool_use_id: String,
541}
542
543/// Input data for UserPromptSubmit hook events
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct UserPromptSubmitHookInput {
546    /// Session ID for this conversation
547    pub session_id: String,
548    /// Path to the transcript file
549    pub transcript_path: String,
550    /// Current working directory
551    pub cwd: String,
552    /// Permission mode (optional)
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub permission_mode: Option<String>,
555    /// The prompt submitted by the user
556    pub prompt: String,
557}
558
559/// Input data for Stop hook events
560#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct StopHookInput {
562    /// Session ID for this conversation
563    pub session_id: String,
564    /// Path to the transcript file
565    pub transcript_path: String,
566    /// Current working directory
567    pub cwd: String,
568    /// Permission mode (optional)
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub permission_mode: Option<String>,
571    /// Whether stop hook is active
572    pub stop_hook_active: bool,
573}
574
575/// Input data for SubagentStop hook events
576#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct SubagentStopHookInput {
578    /// Session ID for this conversation
579    pub session_id: String,
580    /// Path to the transcript file
581    pub transcript_path: String,
582    /// Current working directory
583    pub cwd: String,
584    /// Permission mode (optional)
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub permission_mode: Option<String>,
587    /// Whether stop hook is active
588    pub stop_hook_active: bool,
589    /// Agent ID
590    pub agent_id: String,
591    /// Path to the agent's transcript
592    pub agent_transcript_path: String,
593    /// Agent type
594    pub agent_type: String,
595}
596
597/// Input data for PreCompact hook events
598#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct PreCompactHookInput {
600    /// Session ID for this conversation
601    pub session_id: String,
602    /// Path to the transcript file
603    pub transcript_path: String,
604    /// Current working directory
605    pub cwd: String,
606    /// Permission mode (optional)
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub permission_mode: Option<String>,
609    /// Trigger type: "manual" or "auto"
610    pub trigger: String,
611    /// Custom instructions for compaction (optional)
612    #[serde(skip_serializing_if = "Option::is_none")]
613    pub custom_instructions: Option<String>,
614}
615
616/// Input data for PostToolUseFailure hook events
617#[derive(Debug, Clone, Serialize, Deserialize)]
618pub struct PostToolUseFailureHookInput {
619    /// Session ID for this conversation
620    pub session_id: String,
621    /// Path to the transcript file
622    pub transcript_path: String,
623    /// Current working directory
624    pub cwd: String,
625    /// Permission mode (optional)
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub permission_mode: Option<String>,
628    /// Name of the tool that failed
629    pub tool_name: String,
630    /// Input parameters that were passed to the tool
631    pub tool_input: serde_json::Value,
632    /// Tool use ID
633    pub tool_use_id: String,
634    /// Error message from the tool
635    pub error: String,
636    /// Whether this failure was due to an interrupt
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub is_interrupt: Option<bool>,
639}
640
641/// Input data for Notification hook events
642#[derive(Debug, Clone, Serialize, Deserialize)]
643pub struct NotificationHookInput {
644    /// Session ID for this conversation
645    pub session_id: String,
646    /// Path to the transcript file
647    pub transcript_path: String,
648    /// Current working directory
649    pub cwd: String,
650    /// Permission mode (optional)
651    #[serde(skip_serializing_if = "Option::is_none")]
652    pub permission_mode: Option<String>,
653    /// Notification message
654    pub message: String,
655    /// Notification title (optional)
656    #[serde(skip_serializing_if = "Option::is_none")]
657    pub title: Option<String>,
658    /// Notification type
659    pub notification_type: String,
660}
661
662/// Input data for SubagentStart hook events
663#[derive(Debug, Clone, Serialize, Deserialize)]
664pub struct SubagentStartHookInput {
665    /// Session ID for this conversation
666    pub session_id: String,
667    /// Path to the transcript file
668    pub transcript_path: String,
669    /// Current working directory
670    pub cwd: String,
671    /// Permission mode (optional)
672    #[serde(skip_serializing_if = "Option::is_none")]
673    pub permission_mode: Option<String>,
674    /// Agent ID
675    pub agent_id: String,
676    /// Agent type
677    pub agent_type: String,
678}
679
680/// Input data for PermissionRequest hook events
681#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct PermissionRequestHookInput {
683    /// Session ID for this conversation
684    pub session_id: String,
685    /// Path to the transcript file
686    pub transcript_path: String,
687    /// Current working directory
688    pub cwd: String,
689    /// Permission mode (optional)
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub permission_mode: Option<String>,
692    /// Name of the tool requesting permission
693    pub tool_name: String,
694    /// Input parameters for the tool
695    pub tool_input: serde_json::Value,
696    /// Permission suggestions (optional)
697    #[serde(skip_serializing_if = "Option::is_none")]
698    pub permission_suggestions: Option<Vec<serde_json::Value>>,
699}
700
701/// Union type for all hook inputs (discriminated by hook_event_name)
702#[derive(Debug, Clone, Serialize, Deserialize)]
703#[serde(tag = "hook_event_name")]
704pub enum HookInput {
705    /// PreToolUse hook input
706    #[serde(rename = "PreToolUse")]
707    PreToolUse(PreToolUseHookInput),
708    /// PostToolUse hook input
709    #[serde(rename = "PostToolUse")]
710    PostToolUse(PostToolUseHookInput),
711    /// PostToolUseFailure hook input
712    #[serde(rename = "PostToolUseFailure")]
713    PostToolUseFailure(PostToolUseFailureHookInput),
714    /// UserPromptSubmit hook input
715    #[serde(rename = "UserPromptSubmit")]
716    UserPromptSubmit(UserPromptSubmitHookInput),
717    /// Stop hook input
718    #[serde(rename = "Stop")]
719    Stop(StopHookInput),
720    /// SubagentStop hook input
721    #[serde(rename = "SubagentStop")]
722    SubagentStop(SubagentStopHookInput),
723    /// PreCompact hook input
724    #[serde(rename = "PreCompact")]
725    PreCompact(PreCompactHookInput),
726    /// Notification hook input
727    #[serde(rename = "Notification")]
728    Notification(NotificationHookInput),
729    /// SubagentStart hook input
730    #[serde(rename = "SubagentStart")]
731    SubagentStart(SubagentStartHookInput),
732    /// PermissionRequest hook input
733    #[serde(rename = "PermissionRequest")]
734    PermissionRequest(PermissionRequestHookInput),
735}
736
737// ============================================================================
738// Hook Output Types (Strongly-typed hook outputs for type safety)
739// ============================================================================
740
741/// Async hook output for deferred execution
742///
743/// When a hook returns this output, the hook execution is deferred and
744/// Claude continues without waiting for the hook to complete.
745#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct AsyncHookJSONOutput {
747    /// Must be true to indicate async execution
748    #[serde(rename = "async")]
749    pub async_: bool,
750    /// Optional timeout in milliseconds for async operation
751    #[serde(skip_serializing_if = "Option::is_none")]
752    #[serde(rename = "asyncTimeout")]
753    pub async_timeout: Option<u32>,
754}
755
756/// Synchronous hook output with control and decision fields
757///
758/// This defines the structure for hook callbacks to control execution and provide
759/// feedback to Claude.
760#[derive(Debug, Clone, Default, Serialize, Deserialize)]
761pub struct SyncHookJSONOutput {
762    // Common control fields
763    /// Whether Claude should proceed after hook execution (default: true)
764    #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
765    pub continue_: Option<bool>,
766    /// Hide stdout from transcript mode (default: false)
767    #[serde(rename = "suppressOutput", skip_serializing_if = "Option::is_none")]
768    pub suppress_output: Option<bool>,
769    /// Message shown when continue is false
770    #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
771    pub stop_reason: Option<String>,
772
773    // Decision fields
774    /// Set to "block" to indicate blocking behavior
775    #[serde(skip_serializing_if = "Option::is_none")]
776    pub decision: Option<String>, // "block" or "approve" (deprecated)
777    /// Warning message displayed to the user
778    #[serde(rename = "systemMessage", skip_serializing_if = "Option::is_none")]
779    pub system_message: Option<String>,
780    /// Feedback message for Claude about the decision
781    #[serde(skip_serializing_if = "Option::is_none")]
782    pub reason: Option<String>,
783
784    // Hook-specific outputs
785    /// Event-specific controls (e.g., permissionDecision for PreToolUse)
786    #[serde(rename = "hookSpecificOutput", skip_serializing_if = "Option::is_none")]
787    pub hook_specific_output: Option<HookSpecificOutput>,
788}
789
790/// Union type for hook outputs
791#[derive(Debug, Clone, Serialize, Deserialize)]
792#[serde(untagged)]
793pub enum HookJSONOutput {
794    /// Async hook output (deferred execution)
795    Async(AsyncHookJSONOutput),
796    /// Sync hook output (immediate execution)
797    Sync(SyncHookJSONOutput),
798}
799
800/// Hook-specific output for PreToolUse events
801#[derive(Debug, Clone, Serialize, Deserialize)]
802pub struct PreToolUseHookSpecificOutput {
803    /// Permission decision: "allow", "deny", or "ask"
804    #[serde(rename = "permissionDecision", skip_serializing_if = "Option::is_none")]
805    pub permission_decision: Option<String>,
806    /// Reason for the permission decision
807    #[serde(rename = "permissionDecisionReason", skip_serializing_if = "Option::is_none")]
808    pub permission_decision_reason: Option<String>,
809    /// Updated input parameters for the tool
810    #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
811    pub updated_input: Option<serde_json::Value>,
812    /// Additional context to provide to Claude
813    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
814    pub additional_context: Option<String>,
815}
816
817/// Hook-specific output for PostToolUse events
818#[derive(Debug, Clone, Serialize, Deserialize)]
819pub struct PostToolUseHookSpecificOutput {
820    /// Additional context to provide to Claude
821    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
822    pub additional_context: Option<String>,
823    /// Updated MCP tool output
824    #[serde(rename = "updatedMCPToolOutput", skip_serializing_if = "Option::is_none")]
825    pub updated_mcp_tool_output: Option<serde_json::Value>,
826}
827
828/// Hook-specific output for UserPromptSubmit events
829#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct UserPromptSubmitHookSpecificOutput {
831    /// Additional context to provide to Claude
832    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
833    pub additional_context: Option<String>,
834}
835
836/// Hook-specific output for SessionStart events
837#[derive(Debug, Clone, Serialize, Deserialize)]
838pub struct SessionStartHookSpecificOutput {
839    /// Additional context to provide to Claude
840    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
841    pub additional_context: Option<String>,
842}
843
844/// Hook-specific output for PostToolUseFailure events
845#[derive(Debug, Clone, Serialize, Deserialize)]
846pub struct PostToolUseFailureHookSpecificOutput {
847    /// Additional context to provide to Claude
848    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
849    pub additional_context: Option<String>,
850}
851
852/// Hook-specific output for Notification events
853#[derive(Debug, Clone, Serialize, Deserialize)]
854pub struct NotificationHookSpecificOutput {
855    /// Additional context to provide to Claude
856    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
857    pub additional_context: Option<String>,
858}
859
860/// Hook-specific output for SubagentStart events
861#[derive(Debug, Clone, Serialize, Deserialize)]
862pub struct SubagentStartHookSpecificOutput {
863    /// Additional context to provide to Claude
864    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
865    pub additional_context: Option<String>,
866}
867
868/// Hook-specific output for PermissionRequest events
869#[derive(Debug, Clone, Serialize, Deserialize)]
870pub struct PermissionRequestHookSpecificOutput {
871    /// Permission decision
872    pub decision: serde_json::Value,
873}
874
875/// Union type for hook-specific outputs (discriminated by hookEventName)
876#[derive(Debug, Clone, Serialize, Deserialize)]
877#[serde(tag = "hookEventName")]
878pub enum HookSpecificOutput {
879    /// PreToolUse-specific output
880    #[serde(rename = "PreToolUse")]
881    PreToolUse(PreToolUseHookSpecificOutput),
882    /// PostToolUse-specific output
883    #[serde(rename = "PostToolUse")]
884    PostToolUse(PostToolUseHookSpecificOutput),
885    /// PostToolUseFailure-specific output
886    #[serde(rename = "PostToolUseFailure")]
887    PostToolUseFailure(PostToolUseFailureHookSpecificOutput),
888    /// UserPromptSubmit-specific output
889    #[serde(rename = "UserPromptSubmit")]
890    UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
891    /// SessionStart-specific output
892    #[serde(rename = "SessionStart")]
893    SessionStart(SessionStartHookSpecificOutput),
894    /// Notification-specific output
895    #[serde(rename = "Notification")]
896    Notification(NotificationHookSpecificOutput),
897    /// SubagentStart-specific output
898    #[serde(rename = "SubagentStart")]
899    SubagentStart(SubagentStartHookSpecificOutput),
900    /// PermissionRequest-specific output
901    #[serde(rename = "PermissionRequest")]
902    PermissionRequest(PermissionRequestHookSpecificOutput),
903}
904
905// ============================================================================
906// Hook Callback Trait (Updated for strong typing)
907// ============================================================================
908
909/// Hook callback trait with strongly-typed inputs and outputs
910///
911/// This trait is used to implement custom hook callbacks that can intercept
912/// and modify Claude's behavior at various points in the conversation.
913#[async_trait]
914pub trait HookCallback: Send + Sync {
915    /// Execute the hook with strongly-typed input and output
916    ///
917    /// # Arguments
918    ///
919    /// * `input` - Strongly-typed hook input (discriminated union)
920    /// * `tool_use_id` - Optional tool use identifier
921    /// * `context` - Hook context with abort signal support
922    ///
923    /// # Returns
924    ///
925    /// A `HookJSONOutput` that controls Claude's behavior
926    async fn execute(
927        &self,
928        input: &HookInput,
929        tool_use_id: Option<&str>,
930        context: &HookContext,
931    ) -> Result<HookJSONOutput, crate::errors::SdkError>;
932}
933
934/// Legacy hook callback trait for backward compatibility
935///
936/// This trait is deprecated and will be removed in v0.4.0.
937/// Please migrate to the new `HookCallback` trait with strong typing.
938#[deprecated(
939    since = "0.3.0",
940    note = "Use the new HookCallback trait with HookInput/HookJSONOutput instead"
941)]
942#[allow(dead_code)]
943#[async_trait]
944pub trait HookCallbackLegacy: Send + Sync {
945    /// Execute the hook with JSON values (legacy)
946    async fn execute_legacy(
947        &self,
948        input: &serde_json::Value,
949        tool_use_id: Option<&str>,
950        context: &HookContext,
951    ) -> serde_json::Value;
952}
953
954/// Hook matcher configuration
955#[derive(Clone)]
956pub struct HookMatcher {
957    /// Matcher criteria
958    pub matcher: Option<serde_json::Value>,
959    /// Callbacks to invoke
960    pub hooks: Vec<Arc<dyn HookCallback>>,
961}
962
963/// Setting source for configuration loading
964#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
965#[serde(rename_all = "lowercase")]
966pub enum SettingSource {
967    /// User-level settings
968    User,
969    /// Project-level settings
970    Project,
971    /// Local settings
972    Local,
973}
974
975/// Agent definition for programmatic agents
976#[derive(Debug, Clone, Serialize, Deserialize)]
977pub struct AgentDefinition {
978    /// Agent description
979    pub description: String,
980    /// Agent prompt
981    pub prompt: String,
982    /// Allowed tools for this agent
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub tools: Option<Vec<String>>,
985    /// Model to use
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub model: Option<String>,
988}
989
990/// System prompt configuration
991#[derive(Debug, Clone, Serialize, Deserialize)]
992#[serde(untagged)]
993pub enum SystemPrompt {
994    /// Simple string prompt
995    String(String),
996    /// Preset-based prompt with optional append
997    Preset {
998        #[serde(rename = "type")]
999        preset_type: String,  // "preset"
1000        preset: String,       // e.g., "claude_code"
1001        #[serde(skip_serializing_if = "Option::is_none")]
1002        append: Option<String>,
1003    },
1004}
1005
1006/// Configuration options for Claude Code SDK
1007#[derive(Clone, Default)]
1008pub struct ClaudeCodeOptions {
1009    /// System prompt configuration (simplified in v0.1.12+)
1010    /// Can be either a string or a preset configuration
1011    /// Replaces the old system_prompt and append_system_prompt fields
1012    pub system_prompt_v2: Option<SystemPrompt>,
1013    /// [DEPRECATED] System prompt to prepend to all messages
1014    /// Use system_prompt_v2 instead
1015    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1016    pub system_prompt: Option<String>,
1017    /// [DEPRECATED] Additional system prompt to append
1018    /// Use system_prompt_v2 instead
1019    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1020    pub append_system_prompt: Option<String>,
1021    /// List of allowed tools (auto-approval permissions only)
1022    ///
1023    /// **IMPORTANT**: This only controls which tool invocations are auto-approved
1024    /// (bypass permission prompts). It does NOT disable or restrict which tools
1025    /// the AI can use. Use `disallowed_tools` to completely disable tools.
1026    ///
1027    /// Example: `allowed_tools: vec!["Bash(git:*)".to_string()]` allows auto-approval
1028    /// for git commands in Bash, but doesn't prevent AI from using other tools.
1029    pub allowed_tools: Vec<String>,
1030    /// List of disallowed tools (completely disabled)
1031    ///
1032    /// **IMPORTANT**: This completely disables the specified tools. The AI will
1033    /// not be able to use these tools at all. Use this to restrict which tools
1034    /// the AI has access to.
1035    ///
1036    /// Example: `disallowed_tools: vec!["Bash".to_string(), "WebSearch".to_string()]`
1037    /// prevents the AI from using Bash or WebSearch tools entirely.
1038    pub disallowed_tools: Vec<String>,
1039    /// Permission mode for tool execution
1040    pub permission_mode: PermissionMode,
1041    /// MCP server configurations
1042    pub mcp_servers: HashMap<String, McpServerConfig>,
1043    /// MCP tools to enable
1044    pub mcp_tools: Vec<String>,
1045    /// Maximum number of conversation turns
1046    pub max_turns: Option<i32>,
1047    /// Maximum thinking tokens
1048    pub max_thinking_tokens: Option<i32>,
1049    /// Maximum output tokens per response (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
1050    pub max_output_tokens: Option<u32>,
1051    /// Model to use
1052    pub model: Option<String>,
1053    /// Working directory
1054    pub cwd: Option<PathBuf>,
1055    /// Continue from previous conversation
1056    pub continue_conversation: bool,
1057    /// Resume from a specific conversation ID
1058    pub resume: Option<String>,
1059    /// Custom permission prompt tool name
1060    pub permission_prompt_tool_name: Option<String>,
1061    /// Settings file path for Claude Code CLI
1062    pub settings: Option<String>,
1063    /// Additional directories to add as working directories
1064    pub add_dirs: Vec<PathBuf>,
1065    /// Extra arbitrary CLI flags
1066    pub extra_args: HashMap<String, Option<String>>,
1067    /// Environment variables to pass to the process
1068    pub env: HashMap<String, String>,
1069    /// Debug output stream (e.g., stderr)
1070    pub debug_stderr: Option<Arc<Mutex<dyn Write + Send + Sync>>>,
1071    /// Include partial assistant messages in streaming output
1072    pub include_partial_messages: bool,
1073    /// Tool permission callback
1074    pub can_use_tool: Option<Arc<dyn CanUseTool>>,
1075    /// Hook configurations
1076    pub hooks: Option<HashMap<String, Vec<HookMatcher>>>,
1077    /// Control protocol format (defaults to Legacy for compatibility)
1078    pub control_protocol_format: ControlProtocolFormat,
1079
1080    // ========== Phase 2 Enhancements ==========
1081    /// Setting sources to load (user, project, local)
1082    /// When None, no filesystem settings are loaded (matches Python SDK v0.1.0 behavior)
1083    pub setting_sources: Option<Vec<SettingSource>>,
1084    /// Fork session when resuming instead of continuing
1085    /// When true, creates a new branch from the resumed session
1086    pub fork_session: bool,
1087    /// Programmatic agent definitions
1088    /// Define agents inline without filesystem dependencies
1089    pub agents: Option<HashMap<String, AgentDefinition>>,
1090    /// CLI channel buffer size for internal communication channels
1091    /// Controls the size of message, control, and stdin buffers (default: 100)
1092    /// Increase for high-throughput scenarios to prevent message lag
1093    pub cli_channel_buffer_size: Option<usize>,
1094
1095    // ========== Phase 3 Enhancements (Python SDK v0.1.12+ sync) ==========
1096    /// Tools configuration for controlling available tools
1097    ///
1098    /// This controls the base set of tools available to Claude, distinct from
1099    /// `allowed_tools` which only controls auto-approval permissions.
1100    ///
1101    /// # Examples
1102    /// ```rust
1103    /// use cc_sdk::{ClaudeCodeOptions, ToolsConfig};
1104    ///
1105    /// // Enable specific tools only
1106    /// let options = ClaudeCodeOptions::builder()
1107    ///     .tools(ToolsConfig::list(vec!["Read".into(), "Edit".into()]))
1108    ///     .build();
1109    ///
1110    /// // Disable all built-in tools
1111    /// let options = ClaudeCodeOptions::builder()
1112    ///     .tools(ToolsConfig::none())
1113    ///     .build();
1114    ///
1115    /// // Use claude_code preset
1116    /// let options = ClaudeCodeOptions::builder()
1117    ///     .tools(ToolsConfig::claude_code_preset())
1118    ///     .build();
1119    /// ```
1120    pub tools: Option<ToolsConfig>,
1121    /// SDK beta features to enable
1122    /// See https://docs.anthropic.com/en/api/beta-headers
1123    pub betas: Vec<SdkBeta>,
1124    /// Maximum spending limit in USD for the session
1125    /// When exceeded, the session will automatically terminate
1126    pub max_budget_usd: Option<f64>,
1127    /// Fallback model to use when primary model is unavailable
1128    pub fallback_model: Option<String>,
1129    /// Output format for structured outputs
1130    /// Example: `{"type": "json_schema", "schema": {"type": "object", "properties": {...}}}`
1131    pub output_format: Option<serde_json::Value>,
1132    /// Enable file checkpointing to track file changes during the session
1133    /// When enabled, files can be rewound to their state at any user message
1134    /// using `ClaudeSDKClient::rewind_files()`
1135    pub enable_file_checkpointing: bool,
1136    /// Sandbox configuration for bash command isolation
1137    /// Filesystem and network restrictions are derived from permission rules
1138    pub sandbox: Option<SandboxSettings>,
1139    /// Plugin configurations for custom plugins
1140    pub plugins: Vec<SdkPluginConfig>,
1141    /// Run the CLI subprocess as a specific OS user (Unix-only).
1142    ///
1143    /// This matches Python SDK behavior (`anyio.open_process(user=...)`).
1144    ///
1145    /// - Supported on Unix platforms only (non-Unix returns `SdkError::NotSupported`)
1146    /// - Typically requires elevated privileges to switch users
1147    /// - Accepts a username (e.g. `"nobody"`) or a numeric uid string (e.g. `"1000"`)
1148    pub user: Option<String>,
1149    /// Stderr callback (alternative to debug_stderr)
1150    /// Called with each line of stderr output from the CLI
1151    pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
1152    /// Automatically download Claude Code CLI if not found
1153    ///
1154    /// When enabled, the SDK will automatically download and cache the Claude Code
1155    /// CLI binary if it's not found in the system PATH or common installation locations.
1156    ///
1157    /// The CLI is cached in:
1158    /// - macOS: `~/Library/Caches/cc-sdk/cli/`
1159    /// - Linux: `~/.cache/cc-sdk/cli/`
1160    /// - Windows: `%LOCALAPPDATA%\cc-sdk\cli\`
1161    ///
1162    /// # Example
1163    ///
1164    /// ```rust
1165    /// # use cc_sdk::ClaudeCodeOptions;
1166    /// let options = ClaudeCodeOptions::builder()
1167    ///     .auto_download_cli(true)
1168    ///     .build();
1169    /// ```
1170    pub auto_download_cli: bool,
1171}
1172
1173impl std::fmt::Debug for ClaudeCodeOptions {
1174    #[allow(deprecated)]
1175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1176        f.debug_struct("ClaudeCodeOptions")
1177            .field("system_prompt", &self.system_prompt)
1178            .field("append_system_prompt", &self.append_system_prompt)
1179            .field("allowed_tools", &self.allowed_tools)
1180            .field("disallowed_tools", &self.disallowed_tools)
1181            .field("permission_mode", &self.permission_mode)
1182            .field("mcp_servers", &self.mcp_servers)
1183            .field("mcp_tools", &self.mcp_tools)
1184            .field("max_turns", &self.max_turns)
1185            .field("max_thinking_tokens", &self.max_thinking_tokens)
1186            .field("max_output_tokens", &self.max_output_tokens)
1187            .field("model", &self.model)
1188            .field("cwd", &self.cwd)
1189            .field("continue_conversation", &self.continue_conversation)
1190            .field("resume", &self.resume)
1191            .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
1192            .field("settings", &self.settings)
1193            .field("add_dirs", &self.add_dirs)
1194            .field("extra_args", &self.extra_args)
1195            .field("env", &self.env)
1196            .field("debug_stderr", &self.debug_stderr.is_some())
1197            .field("include_partial_messages", &self.include_partial_messages)
1198            .field("can_use_tool", &self.can_use_tool.is_some())
1199            .field("hooks", &self.hooks.is_some())
1200            .field("control_protocol_format", &self.control_protocol_format)
1201            .finish()
1202    }
1203}
1204
1205impl ClaudeCodeOptions {
1206    /// Create a new options builder
1207    pub fn builder() -> ClaudeCodeOptionsBuilder {
1208        ClaudeCodeOptionsBuilder::default()
1209    }
1210}
1211
1212/// Builder for ClaudeCodeOptions
1213#[derive(Debug, Default)]
1214pub struct ClaudeCodeOptionsBuilder {
1215    options: ClaudeCodeOptions,
1216}
1217
1218impl ClaudeCodeOptionsBuilder {
1219    /// Set system prompt
1220    #[allow(deprecated)]
1221    pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
1222        self.options.system_prompt = Some(prompt.into());
1223        self
1224    }
1225
1226    /// Set append system prompt
1227    #[allow(deprecated)]
1228    pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1229        self.options.append_system_prompt = Some(prompt.into());
1230        self
1231    }
1232
1233    /// Set allowed tools (auto-approval permissions only)
1234    ///
1235    /// **IMPORTANT**: This only controls which tool invocations bypass permission
1236    /// prompts. It does NOT disable or restrict which tools the AI can use.
1237    /// To completely disable tools, use `disallowed_tools()` instead.
1238    ///
1239    /// Example: `vec!["Bash(git:*)".to_string()]` auto-approves git commands.
1240    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1241        self.options.allowed_tools = tools;
1242        self
1243    }
1244
1245    /// Add a single allowed tool (auto-approval permission)
1246    ///
1247    /// See `allowed_tools()` for important usage notes.
1248    pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1249        self.options.allowed_tools.push(tool.into());
1250        self
1251    }
1252
1253    /// Set disallowed tools (completely disabled)
1254    ///
1255    /// **IMPORTANT**: This completely disables the specified tools. The AI will
1256    /// not be able to use these tools at all. This is the correct way to restrict
1257    /// which tools the AI has access to.
1258    ///
1259    /// Example: `vec!["Bash".to_string(), "WebSearch".to_string()]` prevents
1260    /// the AI from using Bash or WebSearch entirely.
1261    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1262        self.options.disallowed_tools = tools;
1263        self
1264    }
1265
1266    /// Add a single disallowed tool (completely disabled)
1267    ///
1268    /// See `disallowed_tools()` for important usage notes.
1269    pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1270        self.options.disallowed_tools.push(tool.into());
1271        self
1272    }
1273
1274    /// Set permission mode
1275    pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1276        self.options.permission_mode = mode;
1277        self
1278    }
1279
1280    /// Add MCP server
1281    pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
1282        self.options.mcp_servers.insert(name.into(), config);
1283        self
1284    }
1285
1286    /// Set all MCP servers from a map
1287    pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1288        self.options.mcp_servers = servers;
1289        self
1290    }
1291
1292    /// Set MCP tools
1293    pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1294        self.options.mcp_tools = tools;
1295        self
1296    }
1297
1298    /// Set max turns
1299    pub fn max_turns(mut self, turns: i32) -> Self {
1300        self.options.max_turns = Some(turns);
1301        self
1302    }
1303
1304    /// Set max thinking tokens
1305    pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1306        self.options.max_thinking_tokens = Some(tokens);
1307        self
1308    }
1309
1310    /// Set max output tokens (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
1311    pub fn max_output_tokens(mut self, tokens: u32) -> Self {
1312        self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
1313        self
1314    }
1315
1316    /// Set model
1317    pub fn model(mut self, model: impl Into<String>) -> Self {
1318        self.options.model = Some(model.into());
1319        self
1320    }
1321
1322    /// Set working directory
1323    pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1324        self.options.cwd = Some(path.into());
1325        self
1326    }
1327
1328    /// Enable continue conversation
1329    pub fn continue_conversation(mut self, enable: bool) -> Self {
1330        self.options.continue_conversation = enable;
1331        self
1332    }
1333
1334    /// Set resume conversation ID
1335    pub fn resume(mut self, id: impl Into<String>) -> Self {
1336        self.options.resume = Some(id.into());
1337        self
1338    }
1339
1340    /// Set permission prompt tool name
1341    pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
1342        self.options.permission_prompt_tool_name = Some(name.into());
1343        self
1344    }
1345
1346    /// Set settings file path
1347    pub fn settings(mut self, settings: impl Into<String>) -> Self {
1348        self.options.settings = Some(settings.into());
1349        self
1350    }
1351
1352    /// Add directories as working directories
1353    pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1354        self.options.add_dirs = dirs;
1355        self
1356    }
1357
1358    /// Add a single directory as working directory
1359    pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1360        self.options.add_dirs.push(dir.into());
1361        self
1362    }
1363
1364    /// Add extra CLI arguments
1365    pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1366        self.options.extra_args = args;
1367        self
1368    }
1369
1370    /// Add a single extra CLI argument
1371    pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
1372        self.options.extra_args.insert(key.into(), value);
1373        self
1374    }
1375
1376    /// Set control protocol format
1377    pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1378        self.options.control_protocol_format = format;
1379        self
1380    }
1381
1382    /// Include partial assistant messages in streaming output
1383    pub fn include_partial_messages(mut self, include: bool) -> Self {
1384        self.options.include_partial_messages = include;
1385        self
1386    }
1387
1388    /// Enable fork_session behavior
1389    pub fn fork_session(mut self, fork: bool) -> Self {
1390        self.options.fork_session = fork;
1391        self
1392    }
1393
1394    /// Set setting sources
1395    pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1396        self.options.setting_sources = Some(sources);
1397        self
1398    }
1399
1400    /// Define programmatic agents
1401    pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1402        self.options.agents = Some(agents);
1403        self
1404    }
1405
1406    /// Set CLI channel buffer size
1407    ///
1408    /// Controls the size of internal communication channels (message, control, stdin buffers).
1409    /// Default is 100. Increase for high-throughput scenarios to prevent message lag.
1410    ///
1411    /// # Arguments
1412    ///
1413    /// * `size` - Buffer size (number of messages that can be queued)
1414    ///
1415    /// # Example
1416    ///
1417    /// ```rust
1418    /// # use cc_sdk::ClaudeCodeOptions;
1419    /// let options = ClaudeCodeOptions::builder()
1420    ///     .cli_channel_buffer_size(500)
1421    ///     .build();
1422    /// ```
1423    pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1424        self.options.cli_channel_buffer_size = Some(size);
1425        self
1426    }
1427
1428    // ========== Phase 3 Builder Methods (Python SDK v0.1.12+ sync) ==========
1429
1430    /// Set tools configuration
1431    ///
1432    /// Controls the base set of tools available to Claude. This is distinct from
1433    /// `allowed_tools` which only controls auto-approval permissions.
1434    ///
1435    /// # Examples
1436    ///
1437    /// ```rust
1438    /// # use cc_sdk::{ClaudeCodeOptions, ToolsConfig};
1439    /// // Enable specific tools only
1440    /// let options = ClaudeCodeOptions::builder()
1441    ///     .tools(ToolsConfig::list(vec!["Read".into(), "Edit".into()]))
1442    ///     .build();
1443    /// ```
1444    pub fn tools(mut self, config: ToolsConfig) -> Self {
1445        self.options.tools = Some(config);
1446        self
1447    }
1448
1449    /// Add SDK beta features
1450    ///
1451    /// Enable Anthropic API beta features like extended context window.
1452    pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1453        self.options.betas = betas;
1454        self
1455    }
1456
1457    /// Add a single SDK beta feature
1458    pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1459        self.options.betas.push(beta);
1460        self
1461    }
1462
1463    /// Set maximum spending limit in USD
1464    ///
1465    /// When the budget is exceeded, the session will automatically terminate.
1466    pub fn max_budget_usd(mut self, budget: f64) -> Self {
1467        self.options.max_budget_usd = Some(budget);
1468        self
1469    }
1470
1471    /// Set fallback model
1472    ///
1473    /// Used when the primary model is unavailable.
1474    pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1475        self.options.fallback_model = Some(model.into());
1476        self
1477    }
1478
1479    /// Set output format for structured outputs
1480    ///
1481    /// Enables JSON schema validation for Claude's responses.
1482    ///
1483    /// # Example
1484    ///
1485    /// ```rust
1486    /// # use cc_sdk::ClaudeCodeOptions;
1487    /// let options = ClaudeCodeOptions::builder()
1488    ///     .output_format(serde_json::json!({
1489    ///         "type": "json_schema",
1490    ///         "schema": {
1491    ///             "type": "object",
1492    ///             "properties": {
1493    ///                 "answer": {"type": "string"}
1494    ///             }
1495    ///         }
1496    ///     }))
1497    ///     .build();
1498    /// ```
1499    pub fn output_format(mut self, format: serde_json::Value) -> Self {
1500        self.options.output_format = Some(format);
1501        self
1502    }
1503
1504    /// Enable file checkpointing
1505    ///
1506    /// When enabled, file changes are tracked and can be rewound to any
1507    /// user message using `ClaudeSDKClient::rewind_files()`.
1508    pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1509        self.options.enable_file_checkpointing = enable;
1510        self
1511    }
1512
1513    /// Set sandbox configuration
1514    ///
1515    /// Controls bash command sandboxing for filesystem and network isolation.
1516    pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1517        self.options.sandbox = Some(settings);
1518        self
1519    }
1520
1521    /// Set plugin configurations
1522    pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1523        self.options.plugins = plugins;
1524        self
1525    }
1526
1527    /// Add a single plugin
1528    pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1529        self.options.plugins.push(plugin);
1530        self
1531    }
1532
1533    /// Set user identifier
1534    pub fn user(mut self, user: impl Into<String>) -> Self {
1535        self.options.user = Some(user.into());
1536        self
1537    }
1538
1539    /// Set stderr callback
1540    ///
1541    /// Called with each line of stderr output from the CLI.
1542    pub fn stderr_callback(mut self, callback: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
1543        self.options.stderr_callback = Some(callback);
1544        self
1545    }
1546
1547    /// Enable automatic CLI download
1548    ///
1549    /// When enabled, the SDK will automatically download and cache the Claude Code
1550    /// CLI binary if it's not found in the system PATH or common installation locations.
1551    ///
1552    /// # Example
1553    ///
1554    /// ```rust
1555    /// # use cc_sdk::ClaudeCodeOptions;
1556    /// let options = ClaudeCodeOptions::builder()
1557    ///     .auto_download_cli(true)
1558    ///     .build();
1559    /// ```
1560    pub fn auto_download_cli(mut self, enable: bool) -> Self {
1561        self.options.auto_download_cli = enable;
1562        self
1563    }
1564
1565    /// Build the options
1566    pub fn build(self) -> ClaudeCodeOptions {
1567        self.options
1568    }
1569}
1570
1571/// Main message type enum
1572#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1573#[serde(tag = "type", rename_all = "lowercase")]
1574pub enum Message {
1575    /// User message
1576    User {
1577        /// Message content
1578        message: UserMessage,
1579    },
1580    /// Assistant message
1581    Assistant {
1582        /// Message content
1583        message: AssistantMessage,
1584    },
1585    /// System message
1586    System {
1587        /// Subtype of system message
1588        subtype: String,
1589        /// Additional data
1590        data: serde_json::Value,
1591    },
1592    /// Result message indicating end of turn
1593    Result {
1594        /// Result subtype
1595        subtype: String,
1596        /// Duration in milliseconds
1597        duration_ms: i64,
1598        /// API duration in milliseconds
1599        duration_api_ms: i64,
1600        /// Whether an error occurred
1601        is_error: bool,
1602        /// Number of turns
1603        num_turns: i32,
1604        /// Session ID
1605        session_id: String,
1606        /// Total cost in USD
1607        #[serde(skip_serializing_if = "Option::is_none")]
1608        total_cost_usd: Option<f64>,
1609        /// Usage statistics
1610        #[serde(skip_serializing_if = "Option::is_none")]
1611        usage: Option<serde_json::Value>,
1612        /// Result message
1613        #[serde(skip_serializing_if = "Option::is_none")]
1614        result: Option<String>,
1615        /// Structured output (when output_format is set)
1616        /// Contains the validated JSON response matching the schema
1617        #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
1618        structured_output: Option<serde_json::Value>,
1619    },
1620}
1621
1622/// User message content
1623#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1624pub struct UserMessage {
1625    /// Message content
1626    pub content: String,
1627}
1628
1629/// Assistant message content
1630#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1631pub struct AssistantMessage {
1632    /// Content blocks
1633    pub content: Vec<ContentBlock>,
1634}
1635
1636/// Result message (re-export for convenience)  
1637pub use Message::Result as ResultMessage;
1638/// System message (re-export for convenience)
1639pub use Message::System as SystemMessage;
1640
1641/// Content block types
1642#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1643#[serde(untagged)]
1644pub enum ContentBlock {
1645    /// Text content
1646    Text(TextContent),
1647    /// Thinking content
1648    Thinking(ThinkingContent),
1649    /// Tool use request
1650    ToolUse(ToolUseContent),
1651    /// Tool result
1652    ToolResult(ToolResultContent),
1653}
1654
1655/// Text content block
1656#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1657pub struct TextContent {
1658    /// Text content
1659    pub text: String,
1660}
1661
1662/// Thinking content block
1663#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1664pub struct ThinkingContent {
1665    /// Thinking content
1666    pub thinking: String,
1667    /// Signature
1668    pub signature: String,
1669}
1670
1671/// Tool use content block
1672#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1673pub struct ToolUseContent {
1674    /// Tool use ID
1675    pub id: String,
1676    /// Tool name
1677    pub name: String,
1678    /// Tool input parameters
1679    pub input: serde_json::Value,
1680}
1681
1682/// Tool result content block
1683#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1684pub struct ToolResultContent {
1685    /// Tool use ID this result corresponds to
1686    pub tool_use_id: String,
1687    /// Result content
1688    #[serde(skip_serializing_if = "Option::is_none")]
1689    pub content: Option<ContentValue>,
1690    /// Whether this is an error result
1691    #[serde(skip_serializing_if = "Option::is_none")]
1692    pub is_error: Option<bool>,
1693}
1694
1695/// Content value for tool results
1696#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1697#[serde(untagged)]
1698pub enum ContentValue {
1699    /// Text content
1700    Text(String),
1701    /// Structured content
1702    Structured(Vec<serde_json::Value>),
1703}
1704
1705/// User content structure for internal use
1706#[derive(Debug, Clone, Serialize, Deserialize)]
1707pub struct UserContent {
1708    /// Role (always "user")
1709    pub role: String,
1710    /// Message content
1711    pub content: String,
1712}
1713
1714/// Assistant content structure for internal use
1715#[derive(Debug, Clone, Serialize, Deserialize)]
1716pub struct AssistantContent {
1717    /// Role (always "assistant")
1718    pub role: String,
1719    /// Content blocks
1720    pub content: Vec<ContentBlock>,
1721}
1722
1723/// SDK Control Protocol - Interrupt request
1724#[derive(Debug, Clone, Serialize, Deserialize)]
1725pub struct SDKControlInterruptRequest {
1726    /// Subtype
1727    pub subtype: String,  // "interrupt"
1728}
1729
1730/// SDK Control Protocol - Permission request
1731#[derive(Debug, Clone, Serialize, Deserialize)]
1732pub struct SDKControlPermissionRequest {
1733    /// Subtype
1734    pub subtype: String,  // "can_use_tool"
1735    /// Tool name
1736    #[serde(alias = "toolName")]
1737    pub tool_name: String,
1738    /// Tool input
1739    pub input: serde_json::Value,
1740    /// Permission suggestions
1741    #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
1742    pub permission_suggestions: Option<Vec<PermissionUpdate>>,
1743    /// Blocked path
1744    #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
1745    pub blocked_path: Option<String>,
1746}
1747
1748/// SDK Control Protocol - Initialize request
1749#[derive(Debug, Clone, Serialize, Deserialize)]
1750pub struct SDKControlInitializeRequest {
1751    /// Subtype
1752    pub subtype: String,  // "initialize"
1753    /// Hooks configuration
1754    #[serde(skip_serializing_if = "Option::is_none")]
1755    pub hooks: Option<HashMap<String, serde_json::Value>>,
1756}
1757
1758/// SDK Control Protocol - Set permission mode request
1759#[derive(Debug, Clone, Serialize, Deserialize)]
1760#[serde(rename_all = "camelCase")]
1761pub struct SDKControlSetPermissionModeRequest {
1762    /// Subtype
1763    pub subtype: String,  // "set_permission_mode"
1764    /// Permission mode
1765    pub mode: String,
1766}
1767
1768/// SDK Control Protocol - Set model request
1769#[derive(Debug, Clone, Serialize, Deserialize)]
1770#[serde(rename_all = "camelCase")]
1771pub struct SDKControlSetModelRequest {
1772    /// Subtype
1773    pub subtype: String, // "set_model"
1774    /// Model to set (None to clear)
1775    #[serde(skip_serializing_if = "Option::is_none")]
1776    pub model: Option<String>,
1777}
1778
1779/// SDK Hook callback request
1780#[derive(Debug, Clone, Serialize, Deserialize)]
1781pub struct SDKHookCallbackRequest {
1782    /// Subtype
1783    pub subtype: String,  // "hook_callback"
1784    /// Callback ID
1785    #[serde(alias = "callbackId")]
1786    pub callback_id: String,
1787    /// Input data
1788    pub input: serde_json::Value,
1789    /// Tool use ID
1790    #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
1791    pub tool_use_id: Option<String>,
1792}
1793
1794/// SDK Control Protocol - MCP message request
1795#[derive(Debug, Clone, Serialize, Deserialize)]
1796pub struct SDKControlMcpMessageRequest {
1797    /// Subtype
1798    pub subtype: String,  // "mcp_message"
1799    /// MCP server name
1800    #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
1801    pub mcp_server_name: String,
1802    /// Message to send
1803    pub message: serde_json::Value,
1804}
1805
1806/// SDK Control Protocol - Rewind files request (Python SDK v0.1.14+)
1807///
1808/// Rewinds tracked files to their state at a specific user message.
1809/// Requires `enable_file_checkpointing` to be enabled.
1810#[derive(Debug, Clone, Serialize, Deserialize)]
1811pub struct SDKControlRewindFilesRequest {
1812    /// Subtype (always "rewind_files")
1813    pub subtype: String,
1814    /// UUID of the user message to rewind to
1815    #[serde(alias = "userMessageId")]
1816    pub user_message_id: String,
1817}
1818
1819impl SDKControlRewindFilesRequest {
1820    /// Create a new rewind files request
1821    pub fn new(user_message_id: impl Into<String>) -> Self {
1822        Self {
1823            subtype: "rewind_files".to_string(),
1824            user_message_id: user_message_id.into(),
1825        }
1826    }
1827}
1828
1829/// SDK Control Protocol request types
1830#[derive(Debug, Clone, Serialize, Deserialize)]
1831#[serde(tag = "type", rename_all = "snake_case")]
1832pub enum SDKControlRequest {
1833    /// Interrupt request
1834    #[serde(rename = "interrupt")]
1835    Interrupt(SDKControlInterruptRequest),
1836    /// Permission request
1837    #[serde(rename = "can_use_tool")]
1838    CanUseTool(SDKControlPermissionRequest),
1839    /// Initialize request
1840    #[serde(rename = "initialize")]
1841    Initialize(SDKControlInitializeRequest),
1842    /// Set permission mode
1843    #[serde(rename = "set_permission_mode")]
1844    SetPermissionMode(SDKControlSetPermissionModeRequest),
1845    /// Set model
1846    #[serde(rename = "set_model")]
1847    SetModel(SDKControlSetModelRequest),
1848    /// Hook callback
1849    #[serde(rename = "hook_callback")]
1850    HookCallback(SDKHookCallbackRequest),
1851    /// MCP message
1852    #[serde(rename = "mcp_message")]
1853    McpMessage(SDKControlMcpMessageRequest),
1854    /// Rewind files (Python SDK v0.1.14+)
1855    #[serde(rename = "rewind_files")]
1856    RewindFiles(SDKControlRewindFilesRequest),
1857}
1858
1859/// Control request types (legacy, keeping for compatibility)
1860#[derive(Debug, Clone, Serialize, Deserialize)]
1861#[serde(tag = "type", rename_all = "lowercase")]
1862pub enum ControlRequest {
1863    /// Interrupt the current operation
1864    Interrupt {
1865        /// Request ID
1866        request_id: String,
1867    },
1868}
1869
1870/// Control response types (legacy, keeping for compatibility)
1871#[derive(Debug, Clone, Serialize, Deserialize)]
1872#[serde(tag = "type", rename_all = "lowercase")]
1873pub enum ControlResponse {
1874    /// Interrupt acknowledged
1875    InterruptAck {
1876        /// Request ID
1877        request_id: String,
1878        /// Whether interrupt was successful
1879        success: bool,
1880    },
1881}
1882
1883#[cfg(test)]
1884mod tests {
1885    use super::*;
1886
1887    #[test]
1888    fn test_permission_mode_serialization() {
1889        let mode = PermissionMode::AcceptEdits;
1890        let json = serde_json::to_string(&mode).unwrap();
1891        assert_eq!(json, r#""acceptEdits""#);
1892
1893        let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
1894        assert_eq!(deserialized, mode);
1895
1896        // Test Plan mode
1897        let plan_mode = PermissionMode::Plan;
1898        let plan_json = serde_json::to_string(&plan_mode).unwrap();
1899        assert_eq!(plan_json, r#""plan""#);
1900
1901        let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
1902        assert_eq!(plan_deserialized, plan_mode);
1903    }
1904
1905    #[test]
1906    fn test_message_serialization() {
1907        let msg = Message::User {
1908            message: UserMessage {
1909                content: "Hello".to_string(),
1910            },
1911        };
1912
1913        let json = serde_json::to_string(&msg).unwrap();
1914        assert!(json.contains(r#""type":"user""#));
1915        assert!(json.contains(r#""content":"Hello""#));
1916
1917        let deserialized: Message = serde_json::from_str(&json).unwrap();
1918        assert_eq!(deserialized, msg);
1919    }
1920
1921    #[test]
1922    #[allow(deprecated)]
1923    fn test_options_builder() {
1924        let options = ClaudeCodeOptions::builder()
1925            .system_prompt("Test prompt")
1926            .model("claude-3-opus")
1927            .permission_mode(PermissionMode::AcceptEdits)
1928            .allow_tool("read")
1929            .allow_tool("write")
1930            .max_turns(10)
1931            .build();
1932
1933        assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
1934        assert_eq!(options.model, Some("claude-3-opus".to_string()));
1935        assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
1936        assert_eq!(options.allowed_tools, vec!["read", "write"]);
1937        assert_eq!(options.max_turns, Some(10));
1938    }
1939
1940    #[test]
1941    fn test_extra_args() {
1942        let mut extra_args = HashMap::new();
1943        extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
1944        extra_args.insert("boolean-flag".to_string(), None);
1945
1946        let options = ClaudeCodeOptions::builder()
1947            .extra_args(extra_args.clone())
1948            .add_extra_arg("another-flag", Some("another-value".to_string()))
1949            .build();
1950
1951        assert_eq!(options.extra_args.len(), 3);
1952        assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
1953        assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
1954        assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
1955    }
1956
1957    #[test]
1958    fn test_thinking_content_serialization() {
1959        let thinking = ThinkingContent {
1960            thinking: "Let me think about this...".to_string(),
1961            signature: "sig123".to_string(),
1962        };
1963
1964        let json = serde_json::to_string(&thinking).unwrap();
1965        assert!(json.contains(r#""thinking":"Let me think about this...""#));
1966        assert!(json.contains(r#""signature":"sig123""#));
1967
1968        let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
1969        assert_eq!(deserialized.thinking, thinking.thinking);
1970        assert_eq!(deserialized.signature, thinking.signature);
1971    }
1972
1973    // ============== v0.4.0 New Feature Tests ==============
1974
1975    #[test]
1976    fn test_tools_config_list_serialization() {
1977        let tools = ToolsConfig::List(vec!["Read".to_string(), "Write".to_string(), "Bash".to_string()]);
1978        let json = serde_json::to_string(&tools).unwrap();
1979
1980        // List variant serializes as JSON array
1981        assert!(json.contains("Read"));
1982        assert!(json.contains("Write"));
1983        assert!(json.contains("Bash"));
1984
1985        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
1986        match deserialized {
1987            ToolsConfig::List(list) => {
1988                assert_eq!(list.len(), 3);
1989                assert!(list.contains(&"Read".to_string()));
1990            }
1991            _ => panic!("Expected List variant"),
1992        }
1993    }
1994
1995    #[test]
1996    fn test_tools_config_preset_serialization() {
1997        // Test claude_code preset using the helper method
1998        let preset = ToolsConfig::claude_code_preset();
1999        let json = serde_json::to_string(&preset).unwrap();
2000        assert!(json.contains("preset"));
2001        assert!(json.contains("claude_code"));
2002
2003        // Test Preset variant with custom values
2004        let custom_preset = ToolsConfig::Preset(ToolsPreset {
2005            preset_type: "preset".to_string(),
2006            preset: "custom".to_string(),
2007        });
2008        let json = serde_json::to_string(&custom_preset).unwrap();
2009        assert!(json.contains("custom"));
2010
2011        // Test deserialization
2012        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2013        match deserialized {
2014            ToolsConfig::Preset(p) => assert_eq!(p.preset, "custom"),
2015            _ => panic!("Expected Preset variant"),
2016        }
2017    }
2018
2019    #[test]
2020    fn test_tools_config_helper_methods() {
2021        // Test list() helper
2022        let tools = ToolsConfig::list(vec!["Read".to_string(), "Write".to_string()]);
2023        match tools {
2024            ToolsConfig::List(list) => assert_eq!(list.len(), 2),
2025            _ => panic!("Expected List variant"),
2026        }
2027
2028        // Test none() helper (empty list)
2029        let empty = ToolsConfig::none();
2030        match empty {
2031            ToolsConfig::List(list) => assert!(list.is_empty()),
2032            _ => panic!("Expected empty List variant"),
2033        }
2034
2035        // Test claude_code_preset() helper
2036        let preset = ToolsConfig::claude_code_preset();
2037        match preset {
2038            ToolsConfig::Preset(p) => {
2039                assert_eq!(p.preset_type, "preset");
2040                assert_eq!(p.preset, "claude_code");
2041            }
2042            _ => panic!("Expected Preset variant"),
2043        }
2044    }
2045
2046    #[test]
2047    fn test_sdk_beta_serialization() {
2048        let beta = SdkBeta::Context1M;
2049        let json = serde_json::to_string(&beta).unwrap();
2050        // The enum uses rename = "context-1m-2025-08-07"
2051        assert_eq!(json, r#""context-1m-2025-08-07""#);
2052
2053        // Test Display trait
2054        let display = format!("{}", beta);
2055        assert_eq!(display, "context-1m-2025-08-07");
2056
2057        // Test deserialization
2058        let deserialized: SdkBeta = serde_json::from_str(r#""context-1m-2025-08-07""#).unwrap();
2059        assert!(matches!(deserialized, SdkBeta::Context1M));
2060    }
2061
2062    #[test]
2063    fn test_sandbox_settings_serialization() {
2064        let sandbox = SandboxSettings {
2065            enabled: Some(true),
2066            auto_allow_bash_if_sandboxed: Some(true),
2067            excluded_commands: Some(vec!["git".to_string(), "docker".to_string()]),
2068            allow_unsandboxed_commands: Some(false),
2069            network: Some(SandboxNetworkConfig {
2070                allow_unix_sockets: Some(vec!["/tmp/ssh-agent.sock".to_string()]),
2071                allow_all_unix_sockets: Some(false),
2072                allow_local_binding: Some(true),
2073                http_proxy_port: Some(8080),
2074                socks_proxy_port: Some(1080),
2075            }),
2076            ignore_violations: Some(SandboxIgnoreViolations {
2077                file: Some(vec!["/tmp".to_string(), "/var/log".to_string()]),
2078                network: Some(vec!["localhost".to_string()]),
2079            }),
2080            enable_weaker_nested_sandbox: Some(false),
2081        };
2082
2083        let json = serde_json::to_string(&sandbox).unwrap();
2084        assert!(json.contains("enabled"));
2085        assert!(json.contains("autoAllowBashIfSandboxed")); // camelCase
2086        assert!(json.contains("excludedCommands"));
2087        assert!(json.contains("httpProxyPort"));
2088        assert!(json.contains("8080"));
2089
2090        let deserialized: SandboxSettings = serde_json::from_str(&json).unwrap();
2091        assert!(deserialized.enabled.unwrap());
2092        assert!(deserialized.network.is_some());
2093        assert_eq!(deserialized.network.as_ref().unwrap().http_proxy_port, Some(8080));
2094    }
2095
2096    #[test]
2097    fn test_sandbox_network_config() {
2098        let config = SandboxNetworkConfig {
2099            allow_unix_sockets: Some(vec!["/run/user/1000/keyring/ssh".to_string()]),
2100            allow_all_unix_sockets: Some(false),
2101            allow_local_binding: Some(true),
2102            http_proxy_port: Some(3128),
2103            socks_proxy_port: Some(1080),
2104        };
2105
2106        let json = serde_json::to_string(&config).unwrap();
2107        let deserialized: SandboxNetworkConfig = serde_json::from_str(&json).unwrap();
2108
2109        assert_eq!(deserialized.http_proxy_port, Some(3128));
2110        assert_eq!(deserialized.socks_proxy_port, Some(1080));
2111        assert_eq!(deserialized.allow_local_binding, Some(true));
2112    }
2113
2114    #[test]
2115    fn test_sandbox_ignore_violations() {
2116        let violations = SandboxIgnoreViolations {
2117            file: Some(vec!["/tmp".to_string(), "/var/cache".to_string()]),
2118            network: Some(vec!["127.0.0.1".to_string(), "localhost".to_string()]),
2119        };
2120
2121        let json = serde_json::to_string(&violations).unwrap();
2122        assert!(json.contains("file"));
2123        assert!(json.contains("/tmp"));
2124
2125        let deserialized: SandboxIgnoreViolations = serde_json::from_str(&json).unwrap();
2126        assert_eq!(deserialized.file.as_ref().unwrap().len(), 2);
2127        assert_eq!(deserialized.network.as_ref().unwrap().len(), 2);
2128    }
2129
2130    #[test]
2131    fn test_sandbox_settings_default() {
2132        let sandbox = SandboxSettings::default();
2133        assert!(sandbox.enabled.is_none());
2134        assert!(sandbox.network.is_none());
2135        assert!(sandbox.ignore_violations.is_none());
2136    }
2137
2138    #[test]
2139    fn test_sdk_plugin_config_serialization() {
2140        let plugin = SdkPluginConfig::Local {
2141            path: "/path/to/plugin".to_string()
2142        };
2143
2144        let json = serde_json::to_string(&plugin).unwrap();
2145        assert!(json.contains("local")); // lowercase due to rename_all
2146        assert!(json.contains("/path/to/plugin"));
2147
2148        let deserialized: SdkPluginConfig = serde_json::from_str(&json).unwrap();
2149        match deserialized {
2150            SdkPluginConfig::Local { path } => {
2151                assert_eq!(path, "/path/to/plugin");
2152            }
2153        }
2154    }
2155
2156    #[test]
2157    fn test_sdk_control_rewind_files_request() {
2158        let request = SDKControlRewindFilesRequest {
2159            subtype: "rewind_files".to_string(),
2160            user_message_id: "msg_12345".to_string(),
2161        };
2162
2163        let json = serde_json::to_string(&request).unwrap();
2164        assert!(json.contains("user_message_id"));
2165        assert!(json.contains("msg_12345"));
2166        assert!(json.contains("subtype"));
2167        assert!(json.contains("rewind_files"));
2168
2169        let deserialized: SDKControlRewindFilesRequest = serde_json::from_str(&json).unwrap();
2170        assert_eq!(deserialized.user_message_id, "msg_12345");
2171        assert_eq!(deserialized.subtype, "rewind_files");
2172    }
2173
2174    #[test]
2175    fn test_options_builder_with_new_fields() {
2176        let options = ClaudeCodeOptions::builder()
2177            .tools(ToolsConfig::claude_code_preset())
2178            .add_beta(SdkBeta::Context1M)
2179            .max_budget_usd(10.0)
2180            .fallback_model("claude-3-haiku")
2181            .output_format(serde_json::json!({"type": "object"}))
2182            .enable_file_checkpointing(true)
2183            .sandbox(SandboxSettings::default())
2184            .add_plugin(SdkPluginConfig::Local { path: "/plugin".to_string() })
2185            .auto_download_cli(true)
2186            .build();
2187
2188        // Verify tools
2189        assert!(options.tools.is_some());
2190        match options.tools.as_ref().unwrap() {
2191            ToolsConfig::Preset(preset) => assert_eq!(preset.preset, "claude_code"),
2192            _ => panic!("Expected Preset variant"),
2193        }
2194
2195        // Verify betas
2196        assert_eq!(options.betas.len(), 1);
2197        assert!(matches!(options.betas[0], SdkBeta::Context1M));
2198
2199        // Verify max_budget_usd
2200        assert_eq!(options.max_budget_usd, Some(10.0));
2201
2202        // Verify fallback_model
2203        assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2204
2205        // Verify output_format
2206        assert!(options.output_format.is_some());
2207
2208        // Verify enable_file_checkpointing
2209        assert!(options.enable_file_checkpointing);
2210
2211        // Verify sandbox
2212        assert!(options.sandbox.is_some());
2213
2214        // Verify plugins
2215        assert_eq!(options.plugins.len(), 1);
2216
2217        // Verify auto_download_cli
2218        assert!(options.auto_download_cli);
2219    }
2220
2221    #[test]
2222    fn test_options_builder_with_tools_list() {
2223        let options = ClaudeCodeOptions::builder()
2224            .tools(ToolsConfig::List(vec!["Read".to_string(), "Bash".to_string()]))
2225            .build();
2226
2227        match options.tools.as_ref().unwrap() {
2228            ToolsConfig::List(list) => {
2229                assert_eq!(list.len(), 2);
2230                assert!(list.contains(&"Read".to_string()));
2231                assert!(list.contains(&"Bash".to_string()));
2232            }
2233            _ => panic!("Expected List variant"),
2234        }
2235    }
2236
2237    #[test]
2238    fn test_options_builder_multiple_betas() {
2239        let options = ClaudeCodeOptions::builder()
2240            .add_beta(SdkBeta::Context1M)
2241            .betas(vec![SdkBeta::Context1M])
2242            .build();
2243
2244        // betas() replaces, add_beta() appends - so only 1 from betas()
2245        assert_eq!(options.betas.len(), 1);
2246    }
2247
2248    #[test]
2249    fn test_options_builder_add_beta_accumulates() {
2250        let options = ClaudeCodeOptions::builder()
2251            .add_beta(SdkBeta::Context1M)
2252            .add_beta(SdkBeta::Context1M)
2253            .build();
2254
2255        // add_beta() accumulates
2256        assert_eq!(options.betas.len(), 2);
2257    }
2258
2259    #[test]
2260    fn test_options_builder_multiple_plugins() {
2261        let options = ClaudeCodeOptions::builder()
2262            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2263            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2264            .plugins(vec![SdkPluginConfig::Local { path: "/plugin3".to_string() }])
2265            .build();
2266
2267        // plugins() replaces previous, so only 1
2268        assert_eq!(options.plugins.len(), 1);
2269    }
2270
2271    #[test]
2272    fn test_options_builder_add_plugin_accumulates() {
2273        let options = ClaudeCodeOptions::builder()
2274            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2275            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2276            .add_plugin(SdkPluginConfig::Local { path: "/plugin3".to_string() })
2277            .build();
2278
2279        // add_plugin() accumulates
2280        assert_eq!(options.plugins.len(), 3);
2281    }
2282
2283    #[test]
2284    fn test_message_result_with_structured_output() {
2285        // Test parsing result message with structured_output (snake_case)
2286        let json = r#"{
2287            "type": "result",
2288            "subtype": "success",
2289            "cost_usd": 0.05,
2290            "duration_ms": 1500,
2291            "duration_api_ms": 1200,
2292            "is_error": false,
2293            "num_turns": 3,
2294            "session_id": "session_123",
2295            "structured_output": {"answer": 42}
2296        }"#;
2297
2298        let msg: Message = serde_json::from_str(json).unwrap();
2299        match msg {
2300            Message::Result {
2301                structured_output,
2302                ..
2303            } => {
2304                assert!(structured_output.is_some());
2305                let output = structured_output.unwrap();
2306                assert_eq!(output["answer"], 42);
2307            }
2308            _ => panic!("Expected Result message"),
2309        }
2310    }
2311
2312    #[test]
2313    fn test_message_result_with_structured_output_camel_case() {
2314        // Test parsing result message with structuredOutput (camelCase alias)
2315        let json = r#"{
2316            "type": "result",
2317            "subtype": "success",
2318            "cost_usd": 0.05,
2319            "duration_ms": 1500,
2320            "duration_api_ms": 1200,
2321            "is_error": false,
2322            "num_turns": 3,
2323            "session_id": "session_123",
2324            "structuredOutput": {"name": "test", "value": true}
2325        }"#;
2326
2327        let msg: Message = serde_json::from_str(json).unwrap();
2328        match msg {
2329            Message::Result {
2330                structured_output,
2331                ..
2332            } => {
2333                assert!(structured_output.is_some());
2334                let output = structured_output.unwrap();
2335                assert_eq!(output["name"], "test");
2336                assert_eq!(output["value"], true);
2337            }
2338            _ => panic!("Expected Result message"),
2339        }
2340    }
2341
2342    #[test]
2343    fn test_default_options_new_fields() {
2344        let options = ClaudeCodeOptions::default();
2345
2346        // Verify defaults for new fields
2347        assert!(options.tools.is_none());
2348        assert!(options.betas.is_empty());
2349        assert!(options.max_budget_usd.is_none());
2350        assert!(options.fallback_model.is_none());
2351        assert!(options.output_format.is_none());
2352        assert!(!options.enable_file_checkpointing);
2353        assert!(options.sandbox.is_none());
2354        assert!(options.plugins.is_empty());
2355        assert!(options.user.is_none());
2356        // Note: auto_download_cli defaults to false (Rust bool default)
2357        // Users should explicitly enable it with .auto_download_cli(true)
2358        assert!(!options.auto_download_cli);
2359    }
2360}