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}
518
519/// Input data for PostToolUse hook events
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct PostToolUseHookInput {
522    /// Session ID for this conversation
523    pub session_id: String,
524    /// Path to the transcript file
525    pub transcript_path: String,
526    /// Current working directory
527    pub cwd: String,
528    /// Permission mode (optional)
529    #[serde(skip_serializing_if = "Option::is_none")]
530    pub permission_mode: Option<String>,
531    /// Name of the tool that was used
532    pub tool_name: String,
533    /// Input parameters that were passed to the tool
534    pub tool_input: serde_json::Value,
535    /// Response from the tool execution
536    pub tool_response: serde_json::Value,
537}
538
539/// Input data for UserPromptSubmit hook events
540#[derive(Debug, Clone, Serialize, Deserialize)]
541pub struct UserPromptSubmitHookInput {
542    /// Session ID for this conversation
543    pub session_id: String,
544    /// Path to the transcript file
545    pub transcript_path: String,
546    /// Current working directory
547    pub cwd: String,
548    /// Permission mode (optional)
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub permission_mode: Option<String>,
551    /// The prompt submitted by the user
552    pub prompt: String,
553}
554
555/// Input data for Stop hook events
556#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct StopHookInput {
558    /// Session ID for this conversation
559    pub session_id: String,
560    /// Path to the transcript file
561    pub transcript_path: String,
562    /// Current working directory
563    pub cwd: String,
564    /// Permission mode (optional)
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub permission_mode: Option<String>,
567    /// Whether stop hook is active
568    pub stop_hook_active: bool,
569}
570
571/// Input data for SubagentStop hook events
572#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct SubagentStopHookInput {
574    /// Session ID for this conversation
575    pub session_id: String,
576    /// Path to the transcript file
577    pub transcript_path: String,
578    /// Current working directory
579    pub cwd: String,
580    /// Permission mode (optional)
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub permission_mode: Option<String>,
583    /// Whether stop hook is active
584    pub stop_hook_active: bool,
585}
586
587/// Input data for PreCompact hook events
588#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct PreCompactHookInput {
590    /// Session ID for this conversation
591    pub session_id: String,
592    /// Path to the transcript file
593    pub transcript_path: String,
594    /// Current working directory
595    pub cwd: String,
596    /// Permission mode (optional)
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub permission_mode: Option<String>,
599    /// Trigger type: "manual" or "auto"
600    pub trigger: String,
601    /// Custom instructions for compaction (optional)
602    #[serde(skip_serializing_if = "Option::is_none")]
603    pub custom_instructions: Option<String>,
604}
605
606/// Union type for all hook inputs (discriminated by hook_event_name)
607#[derive(Debug, Clone, Serialize, Deserialize)]
608#[serde(tag = "hook_event_name")]
609pub enum HookInput {
610    /// PreToolUse hook input
611    #[serde(rename = "PreToolUse")]
612    PreToolUse(PreToolUseHookInput),
613    /// PostToolUse hook input
614    #[serde(rename = "PostToolUse")]
615    PostToolUse(PostToolUseHookInput),
616    /// UserPromptSubmit hook input
617    #[serde(rename = "UserPromptSubmit")]
618    UserPromptSubmit(UserPromptSubmitHookInput),
619    /// Stop hook input
620    #[serde(rename = "Stop")]
621    Stop(StopHookInput),
622    /// SubagentStop hook input
623    #[serde(rename = "SubagentStop")]
624    SubagentStop(SubagentStopHookInput),
625    /// PreCompact hook input
626    #[serde(rename = "PreCompact")]
627    PreCompact(PreCompactHookInput),
628}
629
630// ============================================================================
631// Hook Output Types (Strongly-typed hook outputs for type safety)
632// ============================================================================
633
634/// Async hook output for deferred execution
635///
636/// When a hook returns this output, the hook execution is deferred and
637/// Claude continues without waiting for the hook to complete.
638#[derive(Debug, Clone, Serialize, Deserialize)]
639pub struct AsyncHookJSONOutput {
640    /// Must be true to indicate async execution
641    #[serde(rename = "async")]
642    pub async_: bool,
643    /// Optional timeout in milliseconds for async operation
644    #[serde(skip_serializing_if = "Option::is_none")]
645    #[serde(rename = "asyncTimeout")]
646    pub async_timeout: Option<u32>,
647}
648
649/// Synchronous hook output with control and decision fields
650///
651/// This defines the structure for hook callbacks to control execution and provide
652/// feedback to Claude.
653#[derive(Debug, Clone, Default, Serialize, Deserialize)]
654pub struct SyncHookJSONOutput {
655    // Common control fields
656    /// Whether Claude should proceed after hook execution (default: true)
657    #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
658    pub continue_: Option<bool>,
659    /// Hide stdout from transcript mode (default: false)
660    #[serde(rename = "suppressOutput", skip_serializing_if = "Option::is_none")]
661    pub suppress_output: Option<bool>,
662    /// Message shown when continue is false
663    #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
664    pub stop_reason: Option<String>,
665
666    // Decision fields
667    /// Set to "block" to indicate blocking behavior
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub decision: Option<String>, // "block" or "approve" (deprecated)
670    /// Warning message displayed to the user
671    #[serde(rename = "systemMessage", skip_serializing_if = "Option::is_none")]
672    pub system_message: Option<String>,
673    /// Feedback message for Claude about the decision
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub reason: Option<String>,
676
677    // Hook-specific outputs
678    /// Event-specific controls (e.g., permissionDecision for PreToolUse)
679    #[serde(rename = "hookSpecificOutput", skip_serializing_if = "Option::is_none")]
680    pub hook_specific_output: Option<HookSpecificOutput>,
681}
682
683/// Union type for hook outputs
684#[derive(Debug, Clone, Serialize, Deserialize)]
685#[serde(untagged)]
686pub enum HookJSONOutput {
687    /// Async hook output (deferred execution)
688    Async(AsyncHookJSONOutput),
689    /// Sync hook output (immediate execution)
690    Sync(SyncHookJSONOutput),
691}
692
693/// Hook-specific output for PreToolUse events
694#[derive(Debug, Clone, Serialize, Deserialize)]
695pub struct PreToolUseHookSpecificOutput {
696    /// Permission decision: "allow", "deny", or "ask"
697    #[serde(rename = "permissionDecision", skip_serializing_if = "Option::is_none")]
698    pub permission_decision: Option<String>,
699    /// Reason for the permission decision
700    #[serde(rename = "permissionDecisionReason", skip_serializing_if = "Option::is_none")]
701    pub permission_decision_reason: Option<String>,
702    /// Updated input parameters for the tool
703    #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
704    pub updated_input: Option<serde_json::Value>,
705}
706
707/// Hook-specific output for PostToolUse events
708#[derive(Debug, Clone, Serialize, Deserialize)]
709pub struct PostToolUseHookSpecificOutput {
710    /// Additional context to provide to Claude
711    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
712    pub additional_context: Option<String>,
713}
714
715/// Hook-specific output for UserPromptSubmit events
716#[derive(Debug, Clone, Serialize, Deserialize)]
717pub struct UserPromptSubmitHookSpecificOutput {
718    /// Additional context to provide to Claude
719    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
720    pub additional_context: Option<String>,
721}
722
723/// Hook-specific output for SessionStart events
724#[derive(Debug, Clone, Serialize, Deserialize)]
725pub struct SessionStartHookSpecificOutput {
726    /// Additional context to provide to Claude
727    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
728    pub additional_context: Option<String>,
729}
730
731/// Union type for hook-specific outputs (discriminated by hookEventName)
732#[derive(Debug, Clone, Serialize, Deserialize)]
733#[serde(tag = "hookEventName")]
734pub enum HookSpecificOutput {
735    /// PreToolUse-specific output
736    #[serde(rename = "PreToolUse")]
737    PreToolUse(PreToolUseHookSpecificOutput),
738    /// PostToolUse-specific output
739    #[serde(rename = "PostToolUse")]
740    PostToolUse(PostToolUseHookSpecificOutput),
741    /// UserPromptSubmit-specific output
742    #[serde(rename = "UserPromptSubmit")]
743    UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
744    /// SessionStart-specific output
745    #[serde(rename = "SessionStart")]
746    SessionStart(SessionStartHookSpecificOutput),
747}
748
749// ============================================================================
750// Hook Callback Trait (Updated for strong typing)
751// ============================================================================
752
753/// Hook callback trait with strongly-typed inputs and outputs
754///
755/// This trait is used to implement custom hook callbacks that can intercept
756/// and modify Claude's behavior at various points in the conversation.
757#[async_trait]
758pub trait HookCallback: Send + Sync {
759    /// Execute the hook with strongly-typed input and output
760    ///
761    /// # Arguments
762    ///
763    /// * `input` - Strongly-typed hook input (discriminated union)
764    /// * `tool_use_id` - Optional tool use identifier
765    /// * `context` - Hook context with abort signal support
766    ///
767    /// # Returns
768    ///
769    /// A `HookJSONOutput` that controls Claude's behavior
770    async fn execute(
771        &self,
772        input: &HookInput,
773        tool_use_id: Option<&str>,
774        context: &HookContext,
775    ) -> Result<HookJSONOutput, crate::errors::SdkError>;
776}
777
778/// Legacy hook callback trait for backward compatibility
779///
780/// This trait is deprecated and will be removed in v0.4.0.
781/// Please migrate to the new `HookCallback` trait with strong typing.
782#[deprecated(
783    since = "0.3.0",
784    note = "Use the new HookCallback trait with HookInput/HookJSONOutput instead"
785)]
786#[allow(dead_code)]
787#[async_trait]
788pub trait HookCallbackLegacy: Send + Sync {
789    /// Execute the hook with JSON values (legacy)
790    async fn execute_legacy(
791        &self,
792        input: &serde_json::Value,
793        tool_use_id: Option<&str>,
794        context: &HookContext,
795    ) -> serde_json::Value;
796}
797
798/// Hook matcher configuration
799#[derive(Clone)]
800pub struct HookMatcher {
801    /// Matcher criteria
802    pub matcher: Option<serde_json::Value>,
803    /// Callbacks to invoke
804    pub hooks: Vec<Arc<dyn HookCallback>>,
805}
806
807/// Setting source for configuration loading
808#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
809#[serde(rename_all = "lowercase")]
810pub enum SettingSource {
811    /// User-level settings
812    User,
813    /// Project-level settings
814    Project,
815    /// Local settings
816    Local,
817}
818
819/// Agent definition for programmatic agents
820#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct AgentDefinition {
822    /// Agent description
823    pub description: String,
824    /// Agent prompt
825    pub prompt: String,
826    /// Allowed tools for this agent
827    #[serde(skip_serializing_if = "Option::is_none")]
828    pub tools: Option<Vec<String>>,
829    /// Model to use
830    #[serde(skip_serializing_if = "Option::is_none")]
831    pub model: Option<String>,
832}
833
834/// System prompt configuration
835#[derive(Debug, Clone, Serialize, Deserialize)]
836#[serde(untagged)]
837pub enum SystemPrompt {
838    /// Simple string prompt
839    String(String),
840    /// Preset-based prompt with optional append
841    Preset {
842        #[serde(rename = "type")]
843        preset_type: String,  // "preset"
844        preset: String,       // e.g., "claude_code"
845        #[serde(skip_serializing_if = "Option::is_none")]
846        append: Option<String>,
847    },
848}
849
850/// Configuration options for Claude Code SDK
851#[derive(Clone, Default)]
852pub struct ClaudeCodeOptions {
853    /// System prompt configuration (simplified in v0.1.12+)
854    /// Can be either a string or a preset configuration
855    /// Replaces the old system_prompt and append_system_prompt fields
856    pub system_prompt_v2: Option<SystemPrompt>,
857    /// [DEPRECATED] System prompt to prepend to all messages
858    /// Use system_prompt_v2 instead
859    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
860    pub system_prompt: Option<String>,
861    /// [DEPRECATED] Additional system prompt to append
862    /// Use system_prompt_v2 instead
863    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
864    pub append_system_prompt: Option<String>,
865    /// List of allowed tools (auto-approval permissions only)
866    ///
867    /// **IMPORTANT**: This only controls which tool invocations are auto-approved
868    /// (bypass permission prompts). It does NOT disable or restrict which tools
869    /// the AI can use. Use `disallowed_tools` to completely disable tools.
870    ///
871    /// Example: `allowed_tools: vec!["Bash(git:*)".to_string()]` allows auto-approval
872    /// for git commands in Bash, but doesn't prevent AI from using other tools.
873    pub allowed_tools: Vec<String>,
874    /// List of disallowed tools (completely disabled)
875    ///
876    /// **IMPORTANT**: This completely disables the specified tools. The AI will
877    /// not be able to use these tools at all. Use this to restrict which tools
878    /// the AI has access to.
879    ///
880    /// Example: `disallowed_tools: vec!["Bash".to_string(), "WebSearch".to_string()]`
881    /// prevents the AI from using Bash or WebSearch tools entirely.
882    pub disallowed_tools: Vec<String>,
883    /// Permission mode for tool execution
884    pub permission_mode: PermissionMode,
885    /// MCP server configurations
886    pub mcp_servers: HashMap<String, McpServerConfig>,
887    /// MCP tools to enable
888    pub mcp_tools: Vec<String>,
889    /// Maximum number of conversation turns
890    pub max_turns: Option<i32>,
891    /// Maximum thinking tokens
892    pub max_thinking_tokens: i32,
893    /// Maximum output tokens per response (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
894    pub max_output_tokens: Option<u32>,
895    /// Model to use
896    pub model: Option<String>,
897    /// Working directory
898    pub cwd: Option<PathBuf>,
899    /// Continue from previous conversation
900    pub continue_conversation: bool,
901    /// Resume from a specific conversation ID
902    pub resume: Option<String>,
903    /// Custom permission prompt tool name
904    pub permission_prompt_tool_name: Option<String>,
905    /// Settings file path for Claude Code CLI
906    pub settings: Option<String>,
907    /// Additional directories to add as working directories
908    pub add_dirs: Vec<PathBuf>,
909    /// Extra arbitrary CLI flags
910    pub extra_args: HashMap<String, Option<String>>,
911    /// Environment variables to pass to the process
912    pub env: HashMap<String, String>,
913    /// Debug output stream (e.g., stderr)
914    pub debug_stderr: Option<Arc<Mutex<dyn Write + Send + Sync>>>,
915    /// Include partial assistant messages in streaming output
916    pub include_partial_messages: bool,
917    /// Tool permission callback
918    pub can_use_tool: Option<Arc<dyn CanUseTool>>,
919    /// Hook configurations
920    pub hooks: Option<HashMap<String, Vec<HookMatcher>>>,
921    /// Control protocol format (defaults to Legacy for compatibility)
922    pub control_protocol_format: ControlProtocolFormat,
923
924    // ========== Phase 2 Enhancements ==========
925    /// Setting sources to load (user, project, local)
926    /// When None, no filesystem settings are loaded (matches Python SDK v0.1.0 behavior)
927    pub setting_sources: Option<Vec<SettingSource>>,
928    /// Fork session when resuming instead of continuing
929    /// When true, creates a new branch from the resumed session
930    pub fork_session: bool,
931    /// Programmatic agent definitions
932    /// Define agents inline without filesystem dependencies
933    pub agents: Option<HashMap<String, AgentDefinition>>,
934    /// CLI channel buffer size for internal communication channels
935    /// Controls the size of message, control, and stdin buffers (default: 100)
936    /// Increase for high-throughput scenarios to prevent message lag
937    pub cli_channel_buffer_size: Option<usize>,
938
939    // ========== Phase 3 Enhancements (Python SDK v0.1.12+ sync) ==========
940    /// Tools configuration for controlling available tools
941    ///
942    /// This controls the base set of tools available to Claude, distinct from
943    /// `allowed_tools` which only controls auto-approval permissions.
944    ///
945    /// # Examples
946    /// ```rust
947    /// use cc_sdk::{ClaudeCodeOptions, ToolsConfig};
948    ///
949    /// // Enable specific tools only
950    /// let options = ClaudeCodeOptions::builder()
951    ///     .tools(ToolsConfig::list(vec!["Read".into(), "Edit".into()]))
952    ///     .build();
953    ///
954    /// // Disable all built-in tools
955    /// let options = ClaudeCodeOptions::builder()
956    ///     .tools(ToolsConfig::none())
957    ///     .build();
958    ///
959    /// // Use claude_code preset
960    /// let options = ClaudeCodeOptions::builder()
961    ///     .tools(ToolsConfig::claude_code_preset())
962    ///     .build();
963    /// ```
964    pub tools: Option<ToolsConfig>,
965    /// SDK beta features to enable
966    /// See https://docs.anthropic.com/en/api/beta-headers
967    pub betas: Vec<SdkBeta>,
968    /// Maximum spending limit in USD for the session
969    /// When exceeded, the session will automatically terminate
970    pub max_budget_usd: Option<f64>,
971    /// Fallback model to use when primary model is unavailable
972    pub fallback_model: Option<String>,
973    /// Output format for structured outputs
974    /// Example: `{"type": "json_schema", "schema": {"type": "object", "properties": {...}}}`
975    pub output_format: Option<serde_json::Value>,
976    /// Enable file checkpointing to track file changes during the session
977    /// When enabled, files can be rewound to their state at any user message
978    /// using `ClaudeSDKClient::rewind_files()`
979    pub enable_file_checkpointing: bool,
980    /// Sandbox configuration for bash command isolation
981    /// Filesystem and network restrictions are derived from permission rules
982    pub sandbox: Option<SandboxSettings>,
983    /// Plugin configurations for custom plugins
984    pub plugins: Vec<SdkPluginConfig>,
985    /// Run the CLI subprocess as a specific OS user (Unix-only).
986    ///
987    /// This matches Python SDK behavior (`anyio.open_process(user=...)`).
988    ///
989    /// - Supported on Unix platforms only (non-Unix returns `SdkError::NotSupported`)
990    /// - Typically requires elevated privileges to switch users
991    /// - Accepts a username (e.g. `"nobody"`) or a numeric uid string (e.g. `"1000"`)
992    pub user: Option<String>,
993    /// Stderr callback (alternative to debug_stderr)
994    /// Called with each line of stderr output from the CLI
995    pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
996    /// Automatically download Claude Code CLI if not found
997    ///
998    /// When enabled, the SDK will automatically download and cache the Claude Code
999    /// CLI binary if it's not found in the system PATH or common installation locations.
1000    ///
1001    /// The CLI is cached in:
1002    /// - macOS: `~/Library/Caches/cc-sdk/cli/`
1003    /// - Linux: `~/.cache/cc-sdk/cli/`
1004    /// - Windows: `%LOCALAPPDATA%\cc-sdk\cli\`
1005    ///
1006    /// # Example
1007    ///
1008    /// ```rust
1009    /// # use cc_sdk::ClaudeCodeOptions;
1010    /// let options = ClaudeCodeOptions::builder()
1011    ///     .auto_download_cli(true)
1012    ///     .build();
1013    /// ```
1014    pub auto_download_cli: bool,
1015}
1016
1017impl std::fmt::Debug for ClaudeCodeOptions {
1018    #[allow(deprecated)]
1019    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1020        f.debug_struct("ClaudeCodeOptions")
1021            .field("system_prompt", &self.system_prompt)
1022            .field("append_system_prompt", &self.append_system_prompt)
1023            .field("allowed_tools", &self.allowed_tools)
1024            .field("disallowed_tools", &self.disallowed_tools)
1025            .field("permission_mode", &self.permission_mode)
1026            .field("mcp_servers", &self.mcp_servers)
1027            .field("mcp_tools", &self.mcp_tools)
1028            .field("max_turns", &self.max_turns)
1029            .field("max_thinking_tokens", &self.max_thinking_tokens)
1030            .field("max_output_tokens", &self.max_output_tokens)
1031            .field("model", &self.model)
1032            .field("cwd", &self.cwd)
1033            .field("continue_conversation", &self.continue_conversation)
1034            .field("resume", &self.resume)
1035            .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
1036            .field("settings", &self.settings)
1037            .field("add_dirs", &self.add_dirs)
1038            .field("extra_args", &self.extra_args)
1039            .field("env", &self.env)
1040            .field("debug_stderr", &self.debug_stderr.is_some())
1041            .field("include_partial_messages", &self.include_partial_messages)
1042            .field("can_use_tool", &self.can_use_tool.is_some())
1043            .field("hooks", &self.hooks.is_some())
1044            .field("control_protocol_format", &self.control_protocol_format)
1045            .finish()
1046    }
1047}
1048
1049impl ClaudeCodeOptions {
1050    /// Create a new options builder
1051    pub fn builder() -> ClaudeCodeOptionsBuilder {
1052        ClaudeCodeOptionsBuilder::default()
1053    }
1054}
1055
1056/// Builder for ClaudeCodeOptions
1057#[derive(Debug, Default)]
1058pub struct ClaudeCodeOptionsBuilder {
1059    options: ClaudeCodeOptions,
1060}
1061
1062impl ClaudeCodeOptionsBuilder {
1063    /// Set system prompt
1064    #[allow(deprecated)]
1065    pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
1066        self.options.system_prompt = Some(prompt.into());
1067        self
1068    }
1069
1070    /// Set append system prompt
1071    #[allow(deprecated)]
1072    pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1073        self.options.append_system_prompt = Some(prompt.into());
1074        self
1075    }
1076
1077    /// Set allowed tools (auto-approval permissions only)
1078    ///
1079    /// **IMPORTANT**: This only controls which tool invocations bypass permission
1080    /// prompts. It does NOT disable or restrict which tools the AI can use.
1081    /// To completely disable tools, use `disallowed_tools()` instead.
1082    ///
1083    /// Example: `vec!["Bash(git:*)".to_string()]` auto-approves git commands.
1084    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1085        self.options.allowed_tools = tools;
1086        self
1087    }
1088
1089    /// Add a single allowed tool (auto-approval permission)
1090    ///
1091    /// See `allowed_tools()` for important usage notes.
1092    pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1093        self.options.allowed_tools.push(tool.into());
1094        self
1095    }
1096
1097    /// Set disallowed tools (completely disabled)
1098    ///
1099    /// **IMPORTANT**: This completely disables the specified tools. The AI will
1100    /// not be able to use these tools at all. This is the correct way to restrict
1101    /// which tools the AI has access to.
1102    ///
1103    /// Example: `vec!["Bash".to_string(), "WebSearch".to_string()]` prevents
1104    /// the AI from using Bash or WebSearch entirely.
1105    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1106        self.options.disallowed_tools = tools;
1107        self
1108    }
1109
1110    /// Add a single disallowed tool (completely disabled)
1111    ///
1112    /// See `disallowed_tools()` for important usage notes.
1113    pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1114        self.options.disallowed_tools.push(tool.into());
1115        self
1116    }
1117
1118    /// Set permission mode
1119    pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1120        self.options.permission_mode = mode;
1121        self
1122    }
1123
1124    /// Add MCP server
1125    pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
1126        self.options.mcp_servers.insert(name.into(), config);
1127        self
1128    }
1129
1130    /// Set all MCP servers from a map
1131    pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1132        self.options.mcp_servers = servers;
1133        self
1134    }
1135
1136    /// Set MCP tools
1137    pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1138        self.options.mcp_tools = tools;
1139        self
1140    }
1141
1142    /// Set max turns
1143    pub fn max_turns(mut self, turns: i32) -> Self {
1144        self.options.max_turns = Some(turns);
1145        self
1146    }
1147
1148    /// Set max thinking tokens
1149    pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1150        self.options.max_thinking_tokens = tokens;
1151        self
1152    }
1153
1154    /// Set max output tokens (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
1155    pub fn max_output_tokens(mut self, tokens: u32) -> Self {
1156        self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
1157        self
1158    }
1159
1160    /// Set model
1161    pub fn model(mut self, model: impl Into<String>) -> Self {
1162        self.options.model = Some(model.into());
1163        self
1164    }
1165
1166    /// Set working directory
1167    pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1168        self.options.cwd = Some(path.into());
1169        self
1170    }
1171
1172    /// Enable continue conversation
1173    pub fn continue_conversation(mut self, enable: bool) -> Self {
1174        self.options.continue_conversation = enable;
1175        self
1176    }
1177
1178    /// Set resume conversation ID
1179    pub fn resume(mut self, id: impl Into<String>) -> Self {
1180        self.options.resume = Some(id.into());
1181        self
1182    }
1183
1184    /// Set permission prompt tool name
1185    pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
1186        self.options.permission_prompt_tool_name = Some(name.into());
1187        self
1188    }
1189
1190    /// Set settings file path
1191    pub fn settings(mut self, settings: impl Into<String>) -> Self {
1192        self.options.settings = Some(settings.into());
1193        self
1194    }
1195
1196    /// Add directories as working directories
1197    pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1198        self.options.add_dirs = dirs;
1199        self
1200    }
1201
1202    /// Add a single directory as working directory
1203    pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1204        self.options.add_dirs.push(dir.into());
1205        self
1206    }
1207
1208    /// Add extra CLI arguments
1209    pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1210        self.options.extra_args = args;
1211        self
1212    }
1213
1214    /// Add a single extra CLI argument
1215    pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
1216        self.options.extra_args.insert(key.into(), value);
1217        self
1218    }
1219
1220    /// Set control protocol format
1221    pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1222        self.options.control_protocol_format = format;
1223        self
1224    }
1225
1226    /// Include partial assistant messages in streaming output
1227    pub fn include_partial_messages(mut self, include: bool) -> Self {
1228        self.options.include_partial_messages = include;
1229        self
1230    }
1231
1232    /// Enable fork_session behavior
1233    pub fn fork_session(mut self, fork: bool) -> Self {
1234        self.options.fork_session = fork;
1235        self
1236    }
1237
1238    /// Set setting sources
1239    pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1240        self.options.setting_sources = Some(sources);
1241        self
1242    }
1243
1244    /// Define programmatic agents
1245    pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1246        self.options.agents = Some(agents);
1247        self
1248    }
1249
1250    /// Set CLI channel buffer size
1251    ///
1252    /// Controls the size of internal communication channels (message, control, stdin buffers).
1253    /// Default is 100. Increase for high-throughput scenarios to prevent message lag.
1254    ///
1255    /// # Arguments
1256    ///
1257    /// * `size` - Buffer size (number of messages that can be queued)
1258    ///
1259    /// # Example
1260    ///
1261    /// ```rust
1262    /// # use cc_sdk::ClaudeCodeOptions;
1263    /// let options = ClaudeCodeOptions::builder()
1264    ///     .cli_channel_buffer_size(500)
1265    ///     .build();
1266    /// ```
1267    pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1268        self.options.cli_channel_buffer_size = Some(size);
1269        self
1270    }
1271
1272    // ========== Phase 3 Builder Methods (Python SDK v0.1.12+ sync) ==========
1273
1274    /// Set tools configuration
1275    ///
1276    /// Controls the base set of tools available to Claude. This is distinct from
1277    /// `allowed_tools` which only controls auto-approval permissions.
1278    ///
1279    /// # Examples
1280    ///
1281    /// ```rust
1282    /// # use cc_sdk::{ClaudeCodeOptions, ToolsConfig};
1283    /// // Enable specific tools only
1284    /// let options = ClaudeCodeOptions::builder()
1285    ///     .tools(ToolsConfig::list(vec!["Read".into(), "Edit".into()]))
1286    ///     .build();
1287    /// ```
1288    pub fn tools(mut self, config: ToolsConfig) -> Self {
1289        self.options.tools = Some(config);
1290        self
1291    }
1292
1293    /// Add SDK beta features
1294    ///
1295    /// Enable Anthropic API beta features like extended context window.
1296    pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1297        self.options.betas = betas;
1298        self
1299    }
1300
1301    /// Add a single SDK beta feature
1302    pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1303        self.options.betas.push(beta);
1304        self
1305    }
1306
1307    /// Set maximum spending limit in USD
1308    ///
1309    /// When the budget is exceeded, the session will automatically terminate.
1310    pub fn max_budget_usd(mut self, budget: f64) -> Self {
1311        self.options.max_budget_usd = Some(budget);
1312        self
1313    }
1314
1315    /// Set fallback model
1316    ///
1317    /// Used when the primary model is unavailable.
1318    pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1319        self.options.fallback_model = Some(model.into());
1320        self
1321    }
1322
1323    /// Set output format for structured outputs
1324    ///
1325    /// Enables JSON schema validation for Claude's responses.
1326    ///
1327    /// # Example
1328    ///
1329    /// ```rust
1330    /// # use cc_sdk::ClaudeCodeOptions;
1331    /// let options = ClaudeCodeOptions::builder()
1332    ///     .output_format(serde_json::json!({
1333    ///         "type": "json_schema",
1334    ///         "schema": {
1335    ///             "type": "object",
1336    ///             "properties": {
1337    ///                 "answer": {"type": "string"}
1338    ///             }
1339    ///         }
1340    ///     }))
1341    ///     .build();
1342    /// ```
1343    pub fn output_format(mut self, format: serde_json::Value) -> Self {
1344        self.options.output_format = Some(format);
1345        self
1346    }
1347
1348    /// Enable file checkpointing
1349    ///
1350    /// When enabled, file changes are tracked and can be rewound to any
1351    /// user message using `ClaudeSDKClient::rewind_files()`.
1352    pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1353        self.options.enable_file_checkpointing = enable;
1354        self
1355    }
1356
1357    /// Set sandbox configuration
1358    ///
1359    /// Controls bash command sandboxing for filesystem and network isolation.
1360    pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1361        self.options.sandbox = Some(settings);
1362        self
1363    }
1364
1365    /// Set plugin configurations
1366    pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1367        self.options.plugins = plugins;
1368        self
1369    }
1370
1371    /// Add a single plugin
1372    pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1373        self.options.plugins.push(plugin);
1374        self
1375    }
1376
1377    /// Set user identifier
1378    pub fn user(mut self, user: impl Into<String>) -> Self {
1379        self.options.user = Some(user.into());
1380        self
1381    }
1382
1383    /// Set stderr callback
1384    ///
1385    /// Called with each line of stderr output from the CLI.
1386    pub fn stderr_callback(mut self, callback: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
1387        self.options.stderr_callback = Some(callback);
1388        self
1389    }
1390
1391    /// Enable automatic CLI download
1392    ///
1393    /// When enabled, the SDK will automatically download and cache the Claude Code
1394    /// CLI binary if it's not found in the system PATH or common installation locations.
1395    ///
1396    /// # Example
1397    ///
1398    /// ```rust
1399    /// # use cc_sdk::ClaudeCodeOptions;
1400    /// let options = ClaudeCodeOptions::builder()
1401    ///     .auto_download_cli(true)
1402    ///     .build();
1403    /// ```
1404    pub fn auto_download_cli(mut self, enable: bool) -> Self {
1405        self.options.auto_download_cli = enable;
1406        self
1407    }
1408
1409    /// Build the options
1410    pub fn build(self) -> ClaudeCodeOptions {
1411        self.options
1412    }
1413}
1414
1415/// Main message type enum
1416#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1417#[serde(tag = "type", rename_all = "lowercase")]
1418pub enum Message {
1419    /// User message
1420    User {
1421        /// Message content
1422        message: UserMessage,
1423    },
1424    /// Assistant message
1425    Assistant {
1426        /// Message content
1427        message: AssistantMessage,
1428    },
1429    /// System message
1430    System {
1431        /// Subtype of system message
1432        subtype: String,
1433        /// Additional data
1434        data: serde_json::Value,
1435    },
1436    /// Result message indicating end of turn
1437    Result {
1438        /// Result subtype
1439        subtype: String,
1440        /// Duration in milliseconds
1441        duration_ms: i64,
1442        /// API duration in milliseconds
1443        duration_api_ms: i64,
1444        /// Whether an error occurred
1445        is_error: bool,
1446        /// Number of turns
1447        num_turns: i32,
1448        /// Session ID
1449        session_id: String,
1450        /// Total cost in USD
1451        #[serde(skip_serializing_if = "Option::is_none")]
1452        total_cost_usd: Option<f64>,
1453        /// Usage statistics
1454        #[serde(skip_serializing_if = "Option::is_none")]
1455        usage: Option<serde_json::Value>,
1456        /// Result message
1457        #[serde(skip_serializing_if = "Option::is_none")]
1458        result: Option<String>,
1459        /// Structured output (when output_format is set)
1460        /// Contains the validated JSON response matching the schema
1461        #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
1462        structured_output: Option<serde_json::Value>,
1463    },
1464}
1465
1466/// User message content
1467#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1468pub struct UserMessage {
1469    /// Message content
1470    pub content: String,
1471}
1472
1473/// Assistant message content
1474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1475pub struct AssistantMessage {
1476    /// Content blocks
1477    pub content: Vec<ContentBlock>,
1478}
1479
1480/// Result message (re-export for convenience)  
1481pub use Message::Result as ResultMessage;
1482/// System message (re-export for convenience)
1483pub use Message::System as SystemMessage;
1484
1485/// Content block types
1486#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1487#[serde(untagged)]
1488pub enum ContentBlock {
1489    /// Text content
1490    Text(TextContent),
1491    /// Thinking content
1492    Thinking(ThinkingContent),
1493    /// Tool use request
1494    ToolUse(ToolUseContent),
1495    /// Tool result
1496    ToolResult(ToolResultContent),
1497}
1498
1499/// Text content block
1500#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1501pub struct TextContent {
1502    /// Text content
1503    pub text: String,
1504}
1505
1506/// Thinking content block
1507#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1508pub struct ThinkingContent {
1509    /// Thinking content
1510    pub thinking: String,
1511    /// Signature
1512    pub signature: String,
1513}
1514
1515/// Tool use content block
1516#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1517pub struct ToolUseContent {
1518    /// Tool use ID
1519    pub id: String,
1520    /// Tool name
1521    pub name: String,
1522    /// Tool input parameters
1523    pub input: serde_json::Value,
1524}
1525
1526/// Tool result content block
1527#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1528pub struct ToolResultContent {
1529    /// Tool use ID this result corresponds to
1530    pub tool_use_id: String,
1531    /// Result content
1532    #[serde(skip_serializing_if = "Option::is_none")]
1533    pub content: Option<ContentValue>,
1534    /// Whether this is an error result
1535    #[serde(skip_serializing_if = "Option::is_none")]
1536    pub is_error: Option<bool>,
1537}
1538
1539/// Content value for tool results
1540#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1541#[serde(untagged)]
1542pub enum ContentValue {
1543    /// Text content
1544    Text(String),
1545    /// Structured content
1546    Structured(Vec<serde_json::Value>),
1547}
1548
1549/// User content structure for internal use
1550#[derive(Debug, Clone, Serialize, Deserialize)]
1551pub struct UserContent {
1552    /// Role (always "user")
1553    pub role: String,
1554    /// Message content
1555    pub content: String,
1556}
1557
1558/// Assistant content structure for internal use
1559#[derive(Debug, Clone, Serialize, Deserialize)]
1560pub struct AssistantContent {
1561    /// Role (always "assistant")
1562    pub role: String,
1563    /// Content blocks
1564    pub content: Vec<ContentBlock>,
1565}
1566
1567/// SDK Control Protocol - Interrupt request
1568#[derive(Debug, Clone, Serialize, Deserialize)]
1569pub struct SDKControlInterruptRequest {
1570    /// Subtype
1571    pub subtype: String,  // "interrupt"
1572}
1573
1574/// SDK Control Protocol - Permission request
1575#[derive(Debug, Clone, Serialize, Deserialize)]
1576pub struct SDKControlPermissionRequest {
1577    /// Subtype
1578    pub subtype: String,  // "can_use_tool"
1579    /// Tool name
1580    #[serde(alias = "toolName")]
1581    pub tool_name: String,
1582    /// Tool input
1583    pub input: serde_json::Value,
1584    /// Permission suggestions
1585    #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
1586    pub permission_suggestions: Option<Vec<PermissionUpdate>>,
1587    /// Blocked path
1588    #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
1589    pub blocked_path: Option<String>,
1590}
1591
1592/// SDK Control Protocol - Initialize request
1593#[derive(Debug, Clone, Serialize, Deserialize)]
1594pub struct SDKControlInitializeRequest {
1595    /// Subtype
1596    pub subtype: String,  // "initialize"
1597    /// Hooks configuration
1598    #[serde(skip_serializing_if = "Option::is_none")]
1599    pub hooks: Option<HashMap<String, serde_json::Value>>,
1600}
1601
1602/// SDK Control Protocol - Set permission mode request
1603#[derive(Debug, Clone, Serialize, Deserialize)]
1604#[serde(rename_all = "camelCase")]
1605pub struct SDKControlSetPermissionModeRequest {
1606    /// Subtype
1607    pub subtype: String,  // "set_permission_mode"
1608    /// Permission mode
1609    pub mode: String,
1610}
1611
1612/// SDK Control Protocol - Set model request
1613#[derive(Debug, Clone, Serialize, Deserialize)]
1614#[serde(rename_all = "camelCase")]
1615pub struct SDKControlSetModelRequest {
1616    /// Subtype
1617    pub subtype: String, // "set_model"
1618    /// Model to set (None to clear)
1619    #[serde(skip_serializing_if = "Option::is_none")]
1620    pub model: Option<String>,
1621}
1622
1623/// SDK Hook callback request
1624#[derive(Debug, Clone, Serialize, Deserialize)]
1625pub struct SDKHookCallbackRequest {
1626    /// Subtype
1627    pub subtype: String,  // "hook_callback"
1628    /// Callback ID
1629    #[serde(alias = "callbackId")]
1630    pub callback_id: String,
1631    /// Input data
1632    pub input: serde_json::Value,
1633    /// Tool use ID
1634    #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
1635    pub tool_use_id: Option<String>,
1636}
1637
1638/// SDK Control Protocol - MCP message request
1639#[derive(Debug, Clone, Serialize, Deserialize)]
1640pub struct SDKControlMcpMessageRequest {
1641    /// Subtype
1642    pub subtype: String,  // "mcp_message"
1643    /// MCP server name
1644    #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
1645    pub mcp_server_name: String,
1646    /// Message to send
1647    pub message: serde_json::Value,
1648}
1649
1650/// SDK Control Protocol - Rewind files request (Python SDK v0.1.14+)
1651///
1652/// Rewinds tracked files to their state at a specific user message.
1653/// Requires `enable_file_checkpointing` to be enabled.
1654#[derive(Debug, Clone, Serialize, Deserialize)]
1655pub struct SDKControlRewindFilesRequest {
1656    /// Subtype (always "rewind_files")
1657    pub subtype: String,
1658    /// UUID of the user message to rewind to
1659    #[serde(alias = "userMessageId")]
1660    pub user_message_id: String,
1661}
1662
1663impl SDKControlRewindFilesRequest {
1664    /// Create a new rewind files request
1665    pub fn new(user_message_id: impl Into<String>) -> Self {
1666        Self {
1667            subtype: "rewind_files".to_string(),
1668            user_message_id: user_message_id.into(),
1669        }
1670    }
1671}
1672
1673/// SDK Control Protocol request types
1674#[derive(Debug, Clone, Serialize, Deserialize)]
1675#[serde(tag = "type", rename_all = "snake_case")]
1676pub enum SDKControlRequest {
1677    /// Interrupt request
1678    #[serde(rename = "interrupt")]
1679    Interrupt(SDKControlInterruptRequest),
1680    /// Permission request
1681    #[serde(rename = "can_use_tool")]
1682    CanUseTool(SDKControlPermissionRequest),
1683    /// Initialize request
1684    #[serde(rename = "initialize")]
1685    Initialize(SDKControlInitializeRequest),
1686    /// Set permission mode
1687    #[serde(rename = "set_permission_mode")]
1688    SetPermissionMode(SDKControlSetPermissionModeRequest),
1689    /// Set model
1690    #[serde(rename = "set_model")]
1691    SetModel(SDKControlSetModelRequest),
1692    /// Hook callback
1693    #[serde(rename = "hook_callback")]
1694    HookCallback(SDKHookCallbackRequest),
1695    /// MCP message
1696    #[serde(rename = "mcp_message")]
1697    McpMessage(SDKControlMcpMessageRequest),
1698    /// Rewind files (Python SDK v0.1.14+)
1699    #[serde(rename = "rewind_files")]
1700    RewindFiles(SDKControlRewindFilesRequest),
1701}
1702
1703/// Control request types (legacy, keeping for compatibility)
1704#[derive(Debug, Clone, Serialize, Deserialize)]
1705#[serde(tag = "type", rename_all = "lowercase")]
1706pub enum ControlRequest {
1707    /// Interrupt the current operation
1708    Interrupt {
1709        /// Request ID
1710        request_id: String,
1711    },
1712}
1713
1714/// Control response types (legacy, keeping for compatibility)
1715#[derive(Debug, Clone, Serialize, Deserialize)]
1716#[serde(tag = "type", rename_all = "lowercase")]
1717pub enum ControlResponse {
1718    /// Interrupt acknowledged
1719    InterruptAck {
1720        /// Request ID
1721        request_id: String,
1722        /// Whether interrupt was successful
1723        success: bool,
1724    },
1725}
1726
1727#[cfg(test)]
1728mod tests {
1729    use super::*;
1730
1731    #[test]
1732    fn test_permission_mode_serialization() {
1733        let mode = PermissionMode::AcceptEdits;
1734        let json = serde_json::to_string(&mode).unwrap();
1735        assert_eq!(json, r#""acceptEdits""#);
1736
1737        let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
1738        assert_eq!(deserialized, mode);
1739
1740        // Test Plan mode
1741        let plan_mode = PermissionMode::Plan;
1742        let plan_json = serde_json::to_string(&plan_mode).unwrap();
1743        assert_eq!(plan_json, r#""plan""#);
1744
1745        let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
1746        assert_eq!(plan_deserialized, plan_mode);
1747    }
1748
1749    #[test]
1750    fn test_message_serialization() {
1751        let msg = Message::User {
1752            message: UserMessage {
1753                content: "Hello".to_string(),
1754            },
1755        };
1756
1757        let json = serde_json::to_string(&msg).unwrap();
1758        assert!(json.contains(r#""type":"user""#));
1759        assert!(json.contains(r#""content":"Hello""#));
1760
1761        let deserialized: Message = serde_json::from_str(&json).unwrap();
1762        assert_eq!(deserialized, msg);
1763    }
1764
1765    #[test]
1766    #[allow(deprecated)]
1767    fn test_options_builder() {
1768        let options = ClaudeCodeOptions::builder()
1769            .system_prompt("Test prompt")
1770            .model("claude-3-opus")
1771            .permission_mode(PermissionMode::AcceptEdits)
1772            .allow_tool("read")
1773            .allow_tool("write")
1774            .max_turns(10)
1775            .build();
1776
1777        assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
1778        assert_eq!(options.model, Some("claude-3-opus".to_string()));
1779        assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
1780        assert_eq!(options.allowed_tools, vec!["read", "write"]);
1781        assert_eq!(options.max_turns, Some(10));
1782    }
1783
1784    #[test]
1785    fn test_extra_args() {
1786        let mut extra_args = HashMap::new();
1787        extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
1788        extra_args.insert("boolean-flag".to_string(), None);
1789
1790        let options = ClaudeCodeOptions::builder()
1791            .extra_args(extra_args.clone())
1792            .add_extra_arg("another-flag", Some("another-value".to_string()))
1793            .build();
1794
1795        assert_eq!(options.extra_args.len(), 3);
1796        assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
1797        assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
1798        assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
1799    }
1800
1801    #[test]
1802    fn test_thinking_content_serialization() {
1803        let thinking = ThinkingContent {
1804            thinking: "Let me think about this...".to_string(),
1805            signature: "sig123".to_string(),
1806        };
1807
1808        let json = serde_json::to_string(&thinking).unwrap();
1809        assert!(json.contains(r#""thinking":"Let me think about this...""#));
1810        assert!(json.contains(r#""signature":"sig123""#));
1811
1812        let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
1813        assert_eq!(deserialized.thinking, thinking.thinking);
1814        assert_eq!(deserialized.signature, thinking.signature);
1815    }
1816
1817    // ============== v0.4.0 New Feature Tests ==============
1818
1819    #[test]
1820    fn test_tools_config_list_serialization() {
1821        let tools = ToolsConfig::List(vec!["Read".to_string(), "Write".to_string(), "Bash".to_string()]);
1822        let json = serde_json::to_string(&tools).unwrap();
1823
1824        // List variant serializes as JSON array
1825        assert!(json.contains("Read"));
1826        assert!(json.contains("Write"));
1827        assert!(json.contains("Bash"));
1828
1829        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
1830        match deserialized {
1831            ToolsConfig::List(list) => {
1832                assert_eq!(list.len(), 3);
1833                assert!(list.contains(&"Read".to_string()));
1834            }
1835            _ => panic!("Expected List variant"),
1836        }
1837    }
1838
1839    #[test]
1840    fn test_tools_config_preset_serialization() {
1841        // Test claude_code preset using the helper method
1842        let preset = ToolsConfig::claude_code_preset();
1843        let json = serde_json::to_string(&preset).unwrap();
1844        assert!(json.contains("preset"));
1845        assert!(json.contains("claude_code"));
1846
1847        // Test Preset variant with custom values
1848        let custom_preset = ToolsConfig::Preset(ToolsPreset {
1849            preset_type: "preset".to_string(),
1850            preset: "custom".to_string(),
1851        });
1852        let json = serde_json::to_string(&custom_preset).unwrap();
1853        assert!(json.contains("custom"));
1854
1855        // Test deserialization
1856        let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
1857        match deserialized {
1858            ToolsConfig::Preset(p) => assert_eq!(p.preset, "custom"),
1859            _ => panic!("Expected Preset variant"),
1860        }
1861    }
1862
1863    #[test]
1864    fn test_tools_config_helper_methods() {
1865        // Test list() helper
1866        let tools = ToolsConfig::list(vec!["Read".to_string(), "Write".to_string()]);
1867        match tools {
1868            ToolsConfig::List(list) => assert_eq!(list.len(), 2),
1869            _ => panic!("Expected List variant"),
1870        }
1871
1872        // Test none() helper (empty list)
1873        let empty = ToolsConfig::none();
1874        match empty {
1875            ToolsConfig::List(list) => assert!(list.is_empty()),
1876            _ => panic!("Expected empty List variant"),
1877        }
1878
1879        // Test claude_code_preset() helper
1880        let preset = ToolsConfig::claude_code_preset();
1881        match preset {
1882            ToolsConfig::Preset(p) => {
1883                assert_eq!(p.preset_type, "preset");
1884                assert_eq!(p.preset, "claude_code");
1885            }
1886            _ => panic!("Expected Preset variant"),
1887        }
1888    }
1889
1890    #[test]
1891    fn test_sdk_beta_serialization() {
1892        let beta = SdkBeta::Context1M;
1893        let json = serde_json::to_string(&beta).unwrap();
1894        // The enum uses rename = "context-1m-2025-08-07"
1895        assert_eq!(json, r#""context-1m-2025-08-07""#);
1896
1897        // Test Display trait
1898        let display = format!("{}", beta);
1899        assert_eq!(display, "context-1m-2025-08-07");
1900
1901        // Test deserialization
1902        let deserialized: SdkBeta = serde_json::from_str(r#""context-1m-2025-08-07""#).unwrap();
1903        assert!(matches!(deserialized, SdkBeta::Context1M));
1904    }
1905
1906    #[test]
1907    fn test_sandbox_settings_serialization() {
1908        let sandbox = SandboxSettings {
1909            enabled: Some(true),
1910            auto_allow_bash_if_sandboxed: Some(true),
1911            excluded_commands: Some(vec!["git".to_string(), "docker".to_string()]),
1912            allow_unsandboxed_commands: Some(false),
1913            network: Some(SandboxNetworkConfig {
1914                allow_unix_sockets: Some(vec!["/tmp/ssh-agent.sock".to_string()]),
1915                allow_all_unix_sockets: Some(false),
1916                allow_local_binding: Some(true),
1917                http_proxy_port: Some(8080),
1918                socks_proxy_port: Some(1080),
1919            }),
1920            ignore_violations: Some(SandboxIgnoreViolations {
1921                file: Some(vec!["/tmp".to_string(), "/var/log".to_string()]),
1922                network: Some(vec!["localhost".to_string()]),
1923            }),
1924            enable_weaker_nested_sandbox: Some(false),
1925        };
1926
1927        let json = serde_json::to_string(&sandbox).unwrap();
1928        assert!(json.contains("enabled"));
1929        assert!(json.contains("autoAllowBashIfSandboxed")); // camelCase
1930        assert!(json.contains("excludedCommands"));
1931        assert!(json.contains("httpProxyPort"));
1932        assert!(json.contains("8080"));
1933
1934        let deserialized: SandboxSettings = serde_json::from_str(&json).unwrap();
1935        assert!(deserialized.enabled.unwrap());
1936        assert!(deserialized.network.is_some());
1937        assert_eq!(deserialized.network.as_ref().unwrap().http_proxy_port, Some(8080));
1938    }
1939
1940    #[test]
1941    fn test_sandbox_network_config() {
1942        let config = SandboxNetworkConfig {
1943            allow_unix_sockets: Some(vec!["/run/user/1000/keyring/ssh".to_string()]),
1944            allow_all_unix_sockets: Some(false),
1945            allow_local_binding: Some(true),
1946            http_proxy_port: Some(3128),
1947            socks_proxy_port: Some(1080),
1948        };
1949
1950        let json = serde_json::to_string(&config).unwrap();
1951        let deserialized: SandboxNetworkConfig = serde_json::from_str(&json).unwrap();
1952
1953        assert_eq!(deserialized.http_proxy_port, Some(3128));
1954        assert_eq!(deserialized.socks_proxy_port, Some(1080));
1955        assert_eq!(deserialized.allow_local_binding, Some(true));
1956    }
1957
1958    #[test]
1959    fn test_sandbox_ignore_violations() {
1960        let violations = SandboxIgnoreViolations {
1961            file: Some(vec!["/tmp".to_string(), "/var/cache".to_string()]),
1962            network: Some(vec!["127.0.0.1".to_string(), "localhost".to_string()]),
1963        };
1964
1965        let json = serde_json::to_string(&violations).unwrap();
1966        assert!(json.contains("file"));
1967        assert!(json.contains("/tmp"));
1968
1969        let deserialized: SandboxIgnoreViolations = serde_json::from_str(&json).unwrap();
1970        assert_eq!(deserialized.file.as_ref().unwrap().len(), 2);
1971        assert_eq!(deserialized.network.as_ref().unwrap().len(), 2);
1972    }
1973
1974    #[test]
1975    fn test_sandbox_settings_default() {
1976        let sandbox = SandboxSettings::default();
1977        assert!(sandbox.enabled.is_none());
1978        assert!(sandbox.network.is_none());
1979        assert!(sandbox.ignore_violations.is_none());
1980    }
1981
1982    #[test]
1983    fn test_sdk_plugin_config_serialization() {
1984        let plugin = SdkPluginConfig::Local {
1985            path: "/path/to/plugin".to_string()
1986        };
1987
1988        let json = serde_json::to_string(&plugin).unwrap();
1989        assert!(json.contains("local")); // lowercase due to rename_all
1990        assert!(json.contains("/path/to/plugin"));
1991
1992        let deserialized: SdkPluginConfig = serde_json::from_str(&json).unwrap();
1993        match deserialized {
1994            SdkPluginConfig::Local { path } => {
1995                assert_eq!(path, "/path/to/plugin");
1996            }
1997        }
1998    }
1999
2000    #[test]
2001    fn test_sdk_control_rewind_files_request() {
2002        let request = SDKControlRewindFilesRequest {
2003            subtype: "rewind_files".to_string(),
2004            user_message_id: "msg_12345".to_string(),
2005        };
2006
2007        let json = serde_json::to_string(&request).unwrap();
2008        assert!(json.contains("user_message_id"));
2009        assert!(json.contains("msg_12345"));
2010        assert!(json.contains("subtype"));
2011        assert!(json.contains("rewind_files"));
2012
2013        let deserialized: SDKControlRewindFilesRequest = serde_json::from_str(&json).unwrap();
2014        assert_eq!(deserialized.user_message_id, "msg_12345");
2015        assert_eq!(deserialized.subtype, "rewind_files");
2016    }
2017
2018    #[test]
2019    fn test_options_builder_with_new_fields() {
2020        let options = ClaudeCodeOptions::builder()
2021            .tools(ToolsConfig::claude_code_preset())
2022            .add_beta(SdkBeta::Context1M)
2023            .max_budget_usd(10.0)
2024            .fallback_model("claude-3-haiku")
2025            .output_format(serde_json::json!({"type": "object"}))
2026            .enable_file_checkpointing(true)
2027            .sandbox(SandboxSettings::default())
2028            .add_plugin(SdkPluginConfig::Local { path: "/plugin".to_string() })
2029            .auto_download_cli(true)
2030            .build();
2031
2032        // Verify tools
2033        assert!(options.tools.is_some());
2034        match options.tools.as_ref().unwrap() {
2035            ToolsConfig::Preset(preset) => assert_eq!(preset.preset, "claude_code"),
2036            _ => panic!("Expected Preset variant"),
2037        }
2038
2039        // Verify betas
2040        assert_eq!(options.betas.len(), 1);
2041        assert!(matches!(options.betas[0], SdkBeta::Context1M));
2042
2043        // Verify max_budget_usd
2044        assert_eq!(options.max_budget_usd, Some(10.0));
2045
2046        // Verify fallback_model
2047        assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2048
2049        // Verify output_format
2050        assert!(options.output_format.is_some());
2051
2052        // Verify enable_file_checkpointing
2053        assert!(options.enable_file_checkpointing);
2054
2055        // Verify sandbox
2056        assert!(options.sandbox.is_some());
2057
2058        // Verify plugins
2059        assert_eq!(options.plugins.len(), 1);
2060
2061        // Verify auto_download_cli
2062        assert!(options.auto_download_cli);
2063    }
2064
2065    #[test]
2066    fn test_options_builder_with_tools_list() {
2067        let options = ClaudeCodeOptions::builder()
2068            .tools(ToolsConfig::List(vec!["Read".to_string(), "Bash".to_string()]))
2069            .build();
2070
2071        match options.tools.as_ref().unwrap() {
2072            ToolsConfig::List(list) => {
2073                assert_eq!(list.len(), 2);
2074                assert!(list.contains(&"Read".to_string()));
2075                assert!(list.contains(&"Bash".to_string()));
2076            }
2077            _ => panic!("Expected List variant"),
2078        }
2079    }
2080
2081    #[test]
2082    fn test_options_builder_multiple_betas() {
2083        let options = ClaudeCodeOptions::builder()
2084            .add_beta(SdkBeta::Context1M)
2085            .betas(vec![SdkBeta::Context1M])
2086            .build();
2087
2088        // betas() replaces, add_beta() appends - so only 1 from betas()
2089        assert_eq!(options.betas.len(), 1);
2090    }
2091
2092    #[test]
2093    fn test_options_builder_add_beta_accumulates() {
2094        let options = ClaudeCodeOptions::builder()
2095            .add_beta(SdkBeta::Context1M)
2096            .add_beta(SdkBeta::Context1M)
2097            .build();
2098
2099        // add_beta() accumulates
2100        assert_eq!(options.betas.len(), 2);
2101    }
2102
2103    #[test]
2104    fn test_options_builder_multiple_plugins() {
2105        let options = ClaudeCodeOptions::builder()
2106            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2107            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2108            .plugins(vec![SdkPluginConfig::Local { path: "/plugin3".to_string() }])
2109            .build();
2110
2111        // plugins() replaces previous, so only 1
2112        assert_eq!(options.plugins.len(), 1);
2113    }
2114
2115    #[test]
2116    fn test_options_builder_add_plugin_accumulates() {
2117        let options = ClaudeCodeOptions::builder()
2118            .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2119            .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2120            .add_plugin(SdkPluginConfig::Local { path: "/plugin3".to_string() })
2121            .build();
2122
2123        // add_plugin() accumulates
2124        assert_eq!(options.plugins.len(), 3);
2125    }
2126
2127    #[test]
2128    fn test_message_result_with_structured_output() {
2129        // Test parsing result message with structured_output (snake_case)
2130        let json = r#"{
2131            "type": "result",
2132            "subtype": "success",
2133            "cost_usd": 0.05,
2134            "duration_ms": 1500,
2135            "duration_api_ms": 1200,
2136            "is_error": false,
2137            "num_turns": 3,
2138            "session_id": "session_123",
2139            "structured_output": {"answer": 42}
2140        }"#;
2141
2142        let msg: Message = serde_json::from_str(json).unwrap();
2143        match msg {
2144            Message::Result {
2145                structured_output,
2146                ..
2147            } => {
2148                assert!(structured_output.is_some());
2149                let output = structured_output.unwrap();
2150                assert_eq!(output["answer"], 42);
2151            }
2152            _ => panic!("Expected Result message"),
2153        }
2154    }
2155
2156    #[test]
2157    fn test_message_result_with_structured_output_camel_case() {
2158        // Test parsing result message with structuredOutput (camelCase alias)
2159        let json = r#"{
2160            "type": "result",
2161            "subtype": "success",
2162            "cost_usd": 0.05,
2163            "duration_ms": 1500,
2164            "duration_api_ms": 1200,
2165            "is_error": false,
2166            "num_turns": 3,
2167            "session_id": "session_123",
2168            "structuredOutput": {"name": "test", "value": true}
2169        }"#;
2170
2171        let msg: Message = serde_json::from_str(json).unwrap();
2172        match msg {
2173            Message::Result {
2174                structured_output,
2175                ..
2176            } => {
2177                assert!(structured_output.is_some());
2178                let output = structured_output.unwrap();
2179                assert_eq!(output["name"], "test");
2180                assert_eq!(output["value"], true);
2181            }
2182            _ => panic!("Expected Result message"),
2183        }
2184    }
2185
2186    #[test]
2187    fn test_default_options_new_fields() {
2188        let options = ClaudeCodeOptions::default();
2189
2190        // Verify defaults for new fields
2191        assert!(options.tools.is_none());
2192        assert!(options.betas.is_empty());
2193        assert!(options.max_budget_usd.is_none());
2194        assert!(options.fallback_model.is_none());
2195        assert!(options.output_format.is_none());
2196        assert!(!options.enable_file_checkpointing);
2197        assert!(options.sandbox.is_none());
2198        assert!(options.plugins.is_empty());
2199        assert!(options.user.is_none());
2200        // Note: auto_download_cli defaults to false (Rust bool default)
2201        // Users should explicitly enable it with .auto_download_cli(true)
2202        assert!(!options.auto_download_cli);
2203    }
2204}