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
29impl Default for PermissionMode {
30    fn default() -> Self {
31        Self::Default
32    }
33}
34
35/// Control protocol format for sending messages
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ControlProtocolFormat {
38    /// Legacy format: {"type":"sdk_control_request","request":{...}}
39    Legacy,
40    /// New format: {"type":"control","control":{...}}
41    Control,
42    /// Auto-detect based on CLI capabilities (default to Legacy for compatibility)
43    Auto,
44}
45
46impl Default for ControlProtocolFormat {
47    fn default() -> Self {
48        // Default to Legacy for maximum compatibility
49        Self::Legacy
50    }
51}
52
53/// MCP (Model Context Protocol) server configuration
54#[derive(Clone)]
55pub enum McpServerConfig {
56    /// Standard I/O based MCP server
57    Stdio {
58        /// Command to execute
59        command: String,
60        /// Command arguments
61        args: Option<Vec<String>>,
62        /// Environment variables
63        env: Option<HashMap<String, String>>,
64    },
65    /// Server-Sent Events based MCP server
66    Sse {
67        /// Server URL
68        url: String,
69        /// HTTP headers
70        headers: Option<HashMap<String, String>>,
71    },
72    /// HTTP-based MCP server
73    Http {
74        /// Server URL
75        url: String,
76        /// HTTP headers
77        headers: Option<HashMap<String, String>>,
78    },
79    /// SDK MCP server (in-process)
80    Sdk {
81        /// Server name
82        name: String,
83        /// Server instance
84        instance: Arc<dyn std::any::Any + Send + Sync>,
85    },
86}
87
88impl std::fmt::Debug for McpServerConfig {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Self::Stdio { command, args, env } => f
92                .debug_struct("Stdio")
93                .field("command", command)
94                .field("args", args)
95                .field("env", env)
96                .finish(),
97            Self::Sse { url, headers } => f
98                .debug_struct("Sse")
99                .field("url", url)
100                .field("headers", headers)
101                .finish(),
102            Self::Http { url, headers } => f
103                .debug_struct("Http")
104                .field("url", url)
105                .field("headers", headers)
106                .finish(),
107            Self::Sdk { name, .. } => f
108                .debug_struct("Sdk")
109                .field("name", name)
110                .field("instance", &"<Arc<dyn Any>>")
111                .finish(),
112        }
113    }
114}
115
116impl Serialize for McpServerConfig {
117    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
118    where
119        S: serde::Serializer,
120    {
121        use serde::ser::SerializeMap;
122        let mut map = serializer.serialize_map(None)?;
123
124        match self {
125            Self::Stdio { command, args, env } => {
126                map.serialize_entry("type", "stdio")?;
127                map.serialize_entry("command", command)?;
128                if let Some(args) = args {
129                    map.serialize_entry("args", args)?;
130                }
131                if let Some(env) = env {
132                    map.serialize_entry("env", env)?;
133                }
134            }
135            Self::Sse { url, headers } => {
136                map.serialize_entry("type", "sse")?;
137                map.serialize_entry("url", url)?;
138                if let Some(headers) = headers {
139                    map.serialize_entry("headers", headers)?;
140                }
141            }
142            Self::Http { url, headers } => {
143                map.serialize_entry("type", "http")?;
144                map.serialize_entry("url", url)?;
145                if let Some(headers) = headers {
146                    map.serialize_entry("headers", headers)?;
147                }
148            }
149            Self::Sdk { name, .. } => {
150                map.serialize_entry("type", "sdk")?;
151                map.serialize_entry("name", name)?;
152            }
153        }
154
155        map.end()
156    }
157}
158
159impl<'de> Deserialize<'de> for McpServerConfig {
160    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
161    where
162        D: serde::Deserializer<'de>,
163    {
164        #[derive(Deserialize)]
165        #[serde(tag = "type", rename_all = "lowercase")]
166        enum McpServerConfigHelper {
167            Stdio {
168                command: String,
169                #[serde(skip_serializing_if = "Option::is_none")]
170                args: Option<Vec<String>>,
171                #[serde(skip_serializing_if = "Option::is_none")]
172                env: Option<HashMap<String, String>>,
173            },
174            Sse {
175                url: String,
176                #[serde(skip_serializing_if = "Option::is_none")]
177                headers: Option<HashMap<String, String>>,
178            },
179            Http {
180                url: String,
181                #[serde(skip_serializing_if = "Option::is_none")]
182                headers: Option<HashMap<String, String>>,
183            },
184        }
185
186        let helper = McpServerConfigHelper::deserialize(deserializer)?;
187        Ok(match helper {
188            McpServerConfigHelper::Stdio { command, args, env } => {
189                McpServerConfig::Stdio { command, args, env }
190            }
191            McpServerConfigHelper::Sse { url, headers } => {
192                McpServerConfig::Sse { url, headers }
193            }
194            McpServerConfigHelper::Http { url, headers } => {
195                McpServerConfig::Http { url, headers }
196            }
197        })
198    }
199}
200
201/// Permission update destination
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub enum PermissionUpdateDestination {
205    /// User settings
206    UserSettings,
207    /// Project settings
208    ProjectSettings,
209    /// Local settings
210    LocalSettings,
211    /// Session
212    Session,
213}
214
215/// Permission behavior
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub enum PermissionBehavior {
219    /// Allow the action
220    Allow,
221    /// Deny the action
222    Deny,
223    /// Ask the user
224    Ask,
225}
226
227/// Permission rule value
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct PermissionRuleValue {
230    /// Tool name
231    pub tool_name: String,
232    /// Rule content
233    pub rule_content: Option<String>,
234}
235
236/// Permission update type
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub enum PermissionUpdateType {
240    /// Add rules
241    AddRules,
242    /// Replace rules
243    ReplaceRules,
244    /// Remove rules
245    RemoveRules,
246    /// Set mode
247    SetMode,
248    /// Add directories
249    AddDirectories,
250    /// Remove directories
251    RemoveDirectories,
252}
253
254/// Permission update
255#[derive(Debug, Clone, Serialize, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub struct PermissionUpdate {
258    /// Update type
259    #[serde(rename = "type")]
260    pub update_type: PermissionUpdateType,
261    /// Rules to update
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub rules: Option<Vec<PermissionRuleValue>>,
264    /// Behavior to set
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub behavior: Option<PermissionBehavior>,
267    /// Mode to set
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub mode: Option<PermissionMode>,
270    /// Directories to add/remove
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub directories: Option<Vec<String>>,
273    /// Destination for the update
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub destination: Option<PermissionUpdateDestination>,
276}
277
278/// Tool permission context
279#[derive(Debug, Clone)]
280pub struct ToolPermissionContext {
281    /// Abort signal (future support)
282    pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
283    /// Permission suggestions from CLI
284    pub suggestions: Vec<PermissionUpdate>,
285}
286
287/// Permission result - Allow
288#[derive(Debug, Clone)]
289pub struct PermissionResultAllow {
290    /// Updated input parameters
291    pub updated_input: Option<serde_json::Value>,
292    /// Updated permissions
293    pub updated_permissions: Option<Vec<PermissionUpdate>>,
294}
295
296/// Permission result - Deny
297#[derive(Debug, Clone)]
298pub struct PermissionResultDeny {
299    /// Denial message
300    pub message: String,
301    /// Whether to interrupt the conversation
302    pub interrupt: bool,
303}
304
305/// Permission result
306#[derive(Debug, Clone)]
307pub enum PermissionResult {
308    /// Allow the tool use
309    Allow(PermissionResultAllow),
310    /// Deny the tool use
311    Deny(PermissionResultDeny),
312}
313
314/// Tool permission callback trait
315#[async_trait]
316pub trait CanUseTool: Send + Sync {
317    /// Check if a tool can be used
318    async fn can_use_tool(
319        &self,
320        tool_name: &str,
321        input: &serde_json::Value,
322        context: &ToolPermissionContext,
323    ) -> PermissionResult;
324}
325
326/// Hook context
327#[derive(Debug, Clone)]
328pub struct HookContext {
329    /// Abort signal (future support)
330    pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
331}
332
333// ============================================================================
334// Hook Input Types (Strongly-typed hook inputs for type safety)
335// ============================================================================
336
337/// Base hook input fields present across many hook events
338#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct BaseHookInput {
340    /// Session ID for this conversation
341    pub session_id: String,
342    /// Path to the transcript file
343    pub transcript_path: String,
344    /// Current working directory
345    pub cwd: String,
346    /// Permission mode (optional)
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub permission_mode: Option<String>,
349}
350
351/// Input data for PreToolUse hook events
352#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct PreToolUseHookInput {
354    /// Session ID for this conversation
355    pub session_id: String,
356    /// Path to the transcript file
357    pub transcript_path: String,
358    /// Current working directory
359    pub cwd: String,
360    /// Permission mode (optional)
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub permission_mode: Option<String>,
363    /// Name of the tool being used
364    pub tool_name: String,
365    /// Input parameters for the tool
366    pub tool_input: serde_json::Value,
367}
368
369/// Input data for PostToolUse hook events
370#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct PostToolUseHookInput {
372    /// Session ID for this conversation
373    pub session_id: String,
374    /// Path to the transcript file
375    pub transcript_path: String,
376    /// Current working directory
377    pub cwd: String,
378    /// Permission mode (optional)
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub permission_mode: Option<String>,
381    /// Name of the tool that was used
382    pub tool_name: String,
383    /// Input parameters that were passed to the tool
384    pub tool_input: serde_json::Value,
385    /// Response from the tool execution
386    pub tool_response: serde_json::Value,
387}
388
389/// Input data for UserPromptSubmit hook events
390#[derive(Debug, Clone, Serialize, Deserialize)]
391pub struct UserPromptSubmitHookInput {
392    /// Session ID for this conversation
393    pub session_id: String,
394    /// Path to the transcript file
395    pub transcript_path: String,
396    /// Current working directory
397    pub cwd: String,
398    /// Permission mode (optional)
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub permission_mode: Option<String>,
401    /// The prompt submitted by the user
402    pub prompt: String,
403}
404
405/// Input data for Stop hook events
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct StopHookInput {
408    /// Session ID for this conversation
409    pub session_id: String,
410    /// Path to the transcript file
411    pub transcript_path: String,
412    /// Current working directory
413    pub cwd: String,
414    /// Permission mode (optional)
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub permission_mode: Option<String>,
417    /// Whether stop hook is active
418    pub stop_hook_active: bool,
419}
420
421/// Input data for SubagentStop hook events
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct SubagentStopHookInput {
424    /// Session ID for this conversation
425    pub session_id: String,
426    /// Path to the transcript file
427    pub transcript_path: String,
428    /// Current working directory
429    pub cwd: String,
430    /// Permission mode (optional)
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub permission_mode: Option<String>,
433    /// Whether stop hook is active
434    pub stop_hook_active: bool,
435}
436
437/// Input data for PreCompact hook events
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct PreCompactHookInput {
440    /// Session ID for this conversation
441    pub session_id: String,
442    /// Path to the transcript file
443    pub transcript_path: String,
444    /// Current working directory
445    pub cwd: String,
446    /// Permission mode (optional)
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub permission_mode: Option<String>,
449    /// Trigger type: "manual" or "auto"
450    pub trigger: String,
451    /// Custom instructions for compaction (optional)
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub custom_instructions: Option<String>,
454}
455
456/// Union type for all hook inputs (discriminated by hook_event_name)
457#[derive(Debug, Clone, Serialize, Deserialize)]
458#[serde(tag = "hook_event_name")]
459pub enum HookInput {
460    /// PreToolUse hook input
461    #[serde(rename = "PreToolUse")]
462    PreToolUse(PreToolUseHookInput),
463    /// PostToolUse hook input
464    #[serde(rename = "PostToolUse")]
465    PostToolUse(PostToolUseHookInput),
466    /// UserPromptSubmit hook input
467    #[serde(rename = "UserPromptSubmit")]
468    UserPromptSubmit(UserPromptSubmitHookInput),
469    /// Stop hook input
470    #[serde(rename = "Stop")]
471    Stop(StopHookInput),
472    /// SubagentStop hook input
473    #[serde(rename = "SubagentStop")]
474    SubagentStop(SubagentStopHookInput),
475    /// PreCompact hook input
476    #[serde(rename = "PreCompact")]
477    PreCompact(PreCompactHookInput),
478}
479
480// ============================================================================
481// Hook Output Types (Strongly-typed hook outputs for type safety)
482// ============================================================================
483
484/// Async hook output for deferred execution
485///
486/// When a hook returns this output, the hook execution is deferred and
487/// Claude continues without waiting for the hook to complete.
488#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct AsyncHookJSONOutput {
490    /// Must be true to indicate async execution
491    #[serde(rename = "async")]
492    pub async_: bool,
493    /// Optional timeout in milliseconds for async operation
494    #[serde(skip_serializing_if = "Option::is_none")]
495    #[serde(rename = "asyncTimeout")]
496    pub async_timeout: Option<u32>,
497}
498
499/// Synchronous hook output with control and decision fields
500///
501/// This defines the structure for hook callbacks to control execution and provide
502/// feedback to Claude.
503#[derive(Debug, Clone, Default, Serialize, Deserialize)]
504pub struct SyncHookJSONOutput {
505    // Common control fields
506    /// Whether Claude should proceed after hook execution (default: true)
507    #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
508    pub continue_: Option<bool>,
509    /// Hide stdout from transcript mode (default: false)
510    #[serde(rename = "suppressOutput", skip_serializing_if = "Option::is_none")]
511    pub suppress_output: Option<bool>,
512    /// Message shown when continue is false
513    #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
514    pub stop_reason: Option<String>,
515
516    // Decision fields
517    /// Set to "block" to indicate blocking behavior
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub decision: Option<String>, // "block" or "approve" (deprecated)
520    /// Warning message displayed to the user
521    #[serde(rename = "systemMessage", skip_serializing_if = "Option::is_none")]
522    pub system_message: Option<String>,
523    /// Feedback message for Claude about the decision
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub reason: Option<String>,
526
527    // Hook-specific outputs
528    /// Event-specific controls (e.g., permissionDecision for PreToolUse)
529    #[serde(rename = "hookSpecificOutput", skip_serializing_if = "Option::is_none")]
530    pub hook_specific_output: Option<HookSpecificOutput>,
531}
532
533/// Union type for hook outputs
534#[derive(Debug, Clone, Serialize, Deserialize)]
535#[serde(untagged)]
536pub enum HookJSONOutput {
537    /// Async hook output (deferred execution)
538    Async(AsyncHookJSONOutput),
539    /// Sync hook output (immediate execution)
540    Sync(SyncHookJSONOutput),
541}
542
543/// Hook-specific output for PreToolUse events
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct PreToolUseHookSpecificOutput {
546    /// Permission decision: "allow", "deny", or "ask"
547    #[serde(rename = "permissionDecision", skip_serializing_if = "Option::is_none")]
548    pub permission_decision: Option<String>,
549    /// Reason for the permission decision
550    #[serde(rename = "permissionDecisionReason", skip_serializing_if = "Option::is_none")]
551    pub permission_decision_reason: Option<String>,
552    /// Updated input parameters for the tool
553    #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
554    pub updated_input: Option<serde_json::Value>,
555}
556
557/// Hook-specific output for PostToolUse events
558#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct PostToolUseHookSpecificOutput {
560    /// Additional context to provide to Claude
561    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
562    pub additional_context: Option<String>,
563}
564
565/// Hook-specific output for UserPromptSubmit events
566#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct UserPromptSubmitHookSpecificOutput {
568    /// Additional context to provide to Claude
569    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
570    pub additional_context: Option<String>,
571}
572
573/// Hook-specific output for SessionStart events
574#[derive(Debug, Clone, Serialize, Deserialize)]
575pub struct SessionStartHookSpecificOutput {
576    /// Additional context to provide to Claude
577    #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
578    pub additional_context: Option<String>,
579}
580
581/// Union type for hook-specific outputs (discriminated by hookEventName)
582#[derive(Debug, Clone, Serialize, Deserialize)]
583#[serde(tag = "hookEventName")]
584pub enum HookSpecificOutput {
585    /// PreToolUse-specific output
586    #[serde(rename = "PreToolUse")]
587    PreToolUse(PreToolUseHookSpecificOutput),
588    /// PostToolUse-specific output
589    #[serde(rename = "PostToolUse")]
590    PostToolUse(PostToolUseHookSpecificOutput),
591    /// UserPromptSubmit-specific output
592    #[serde(rename = "UserPromptSubmit")]
593    UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
594    /// SessionStart-specific output
595    #[serde(rename = "SessionStart")]
596    SessionStart(SessionStartHookSpecificOutput),
597}
598
599// ============================================================================
600// Hook Callback Trait (Updated for strong typing)
601// ============================================================================
602
603/// Hook callback trait with strongly-typed inputs and outputs
604///
605/// This trait is used to implement custom hook callbacks that can intercept
606/// and modify Claude's behavior at various points in the conversation.
607#[async_trait]
608pub trait HookCallback: Send + Sync {
609    /// Execute the hook with strongly-typed input and output
610    ///
611    /// # Arguments
612    ///
613    /// * `input` - Strongly-typed hook input (discriminated union)
614    /// * `tool_use_id` - Optional tool use identifier
615    /// * `context` - Hook context with abort signal support
616    ///
617    /// # Returns
618    ///
619    /// A `HookJSONOutput` that controls Claude's behavior
620    async fn execute(
621        &self,
622        input: &HookInput,
623        tool_use_id: Option<&str>,
624        context: &HookContext,
625    ) -> Result<HookJSONOutput, crate::errors::SdkError>;
626}
627
628/// Legacy hook callback trait for backward compatibility
629///
630/// This trait is deprecated and will be removed in v0.4.0.
631/// Please migrate to the new `HookCallback` trait with strong typing.
632#[deprecated(
633    since = "0.3.0",
634    note = "Use the new HookCallback trait with HookInput/HookJSONOutput instead"
635)]
636#[allow(dead_code)]
637#[async_trait]
638pub trait HookCallbackLegacy: Send + Sync {
639    /// Execute the hook with JSON values (legacy)
640    async fn execute_legacy(
641        &self,
642        input: &serde_json::Value,
643        tool_use_id: Option<&str>,
644        context: &HookContext,
645    ) -> serde_json::Value;
646}
647
648/// Hook matcher configuration
649#[derive(Clone)]
650pub struct HookMatcher {
651    /// Matcher criteria
652    pub matcher: Option<serde_json::Value>,
653    /// Callbacks to invoke
654    pub hooks: Vec<Arc<dyn HookCallback>>,
655}
656
657/// Setting source for configuration loading
658#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
659#[serde(rename_all = "lowercase")]
660pub enum SettingSource {
661    /// User-level settings
662    User,
663    /// Project-level settings
664    Project,
665    /// Local settings
666    Local,
667}
668
669/// Agent definition for programmatic agents
670#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct AgentDefinition {
672    /// Agent description
673    pub description: String,
674    /// Agent prompt
675    pub prompt: String,
676    /// Allowed tools for this agent
677    #[serde(skip_serializing_if = "Option::is_none")]
678    pub tools: Option<Vec<String>>,
679    /// Model to use
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub model: Option<String>,
682}
683
684/// System prompt configuration
685#[derive(Debug, Clone, Serialize, Deserialize)]
686#[serde(untagged)]
687pub enum SystemPrompt {
688    /// Simple string prompt
689    String(String),
690    /// Preset-based prompt with optional append
691    Preset {
692        #[serde(rename = "type")]
693        preset_type: String,  // "preset"
694        preset: String,       // e.g., "claude_code"
695        #[serde(skip_serializing_if = "Option::is_none")]
696        append: Option<String>,
697    },
698}
699
700/// Configuration options for Claude Code SDK
701#[derive(Clone, Default)]
702pub struct ClaudeCodeOptions {
703    /// System prompt configuration (simplified in v0.1.12+)
704    /// Can be either a string or a preset configuration
705    /// Replaces the old system_prompt and append_system_prompt fields
706    pub system_prompt_v2: Option<SystemPrompt>,
707    /// [DEPRECATED] System prompt to prepend to all messages
708    /// Use system_prompt_v2 instead
709    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
710    pub system_prompt: Option<String>,
711    /// [DEPRECATED] Additional system prompt to append
712    /// Use system_prompt_v2 instead
713    #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
714    pub append_system_prompt: Option<String>,
715    /// List of allowed tools
716    pub allowed_tools: Vec<String>,
717    /// List of disallowed tools
718    pub disallowed_tools: Vec<String>,
719    /// Permission mode for tool execution
720    pub permission_mode: PermissionMode,
721    /// MCP server configurations
722    pub mcp_servers: HashMap<String, McpServerConfig>,
723    /// MCP tools to enable
724    pub mcp_tools: Vec<String>,
725    /// Maximum number of conversation turns
726    pub max_turns: Option<i32>,
727    /// Maximum thinking tokens
728    pub max_thinking_tokens: i32,
729    /// Maximum output tokens per response (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
730    pub max_output_tokens: Option<u32>,
731    /// Model to use
732    pub model: Option<String>,
733    /// Working directory
734    pub cwd: Option<PathBuf>,
735    /// Continue from previous conversation
736    pub continue_conversation: bool,
737    /// Resume from a specific conversation ID
738    pub resume: Option<String>,
739    /// Custom permission prompt tool name
740    pub permission_prompt_tool_name: Option<String>,
741    /// Settings file path for Claude Code CLI
742    pub settings: Option<String>,
743    /// Additional directories to add as working directories
744    pub add_dirs: Vec<PathBuf>,
745    /// Extra arbitrary CLI flags
746    pub extra_args: HashMap<String, Option<String>>,
747    /// Environment variables to pass to the process
748    pub env: HashMap<String, String>,
749    /// Debug output stream (e.g., stderr)
750    pub debug_stderr: Option<Arc<Mutex<dyn Write + Send + Sync>>>,
751    /// Include partial assistant messages in streaming output
752    pub include_partial_messages: bool,
753    /// Tool permission callback
754    pub can_use_tool: Option<Arc<dyn CanUseTool>>,
755    /// Hook configurations
756    pub hooks: Option<HashMap<String, Vec<HookMatcher>>>,
757    /// Control protocol format (defaults to Legacy for compatibility)
758    pub control_protocol_format: ControlProtocolFormat,
759
760    // ========== Phase 2 Enhancements ==========
761    /// Setting sources to load (user, project, local)
762    /// When None, no filesystem settings are loaded (matches Python SDK v0.1.0 behavior)
763    pub setting_sources: Option<Vec<SettingSource>>,
764    /// Fork session when resuming instead of continuing
765    /// When true, creates a new branch from the resumed session
766    pub fork_session: bool,
767    /// Programmatic agent definitions
768    /// Define agents inline without filesystem dependencies
769    pub agents: Option<HashMap<String, AgentDefinition>>,
770    /// CLI channel buffer size for internal communication channels
771    /// Controls the size of message, control, and stdin buffers (default: 100)
772    /// Increase for high-throughput scenarios to prevent message lag
773    pub cli_channel_buffer_size: Option<usize>,
774}
775
776impl std::fmt::Debug for ClaudeCodeOptions {
777    #[allow(deprecated)]
778    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
779        f.debug_struct("ClaudeCodeOptions")
780            .field("system_prompt", &self.system_prompt)
781            .field("append_system_prompt", &self.append_system_prompt)
782            .field("allowed_tools", &self.allowed_tools)
783            .field("disallowed_tools", &self.disallowed_tools)
784            .field("permission_mode", &self.permission_mode)
785            .field("mcp_servers", &self.mcp_servers)
786            .field("mcp_tools", &self.mcp_tools)
787            .field("max_turns", &self.max_turns)
788            .field("max_thinking_tokens", &self.max_thinking_tokens)
789            .field("max_output_tokens", &self.max_output_tokens)
790            .field("model", &self.model)
791            .field("cwd", &self.cwd)
792            .field("continue_conversation", &self.continue_conversation)
793            .field("resume", &self.resume)
794            .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
795            .field("settings", &self.settings)
796            .field("add_dirs", &self.add_dirs)
797            .field("extra_args", &self.extra_args)
798            .field("env", &self.env)
799            .field("debug_stderr", &self.debug_stderr.is_some())
800            .field("include_partial_messages", &self.include_partial_messages)
801            .field("can_use_tool", &self.can_use_tool.is_some())
802            .field("hooks", &self.hooks.is_some())
803            .field("control_protocol_format", &self.control_protocol_format)
804            .finish()
805    }
806}
807
808impl ClaudeCodeOptions {
809    /// Create a new options builder
810    pub fn builder() -> ClaudeCodeOptionsBuilder {
811        ClaudeCodeOptionsBuilder::default()
812    }
813}
814
815/// Builder for ClaudeCodeOptions
816#[derive(Debug, Default)]
817pub struct ClaudeCodeOptionsBuilder {
818    options: ClaudeCodeOptions,
819}
820
821impl ClaudeCodeOptionsBuilder {
822    /// Set system prompt
823    #[allow(deprecated)]
824    pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
825        self.options.system_prompt = Some(prompt.into());
826        self
827    }
828
829    /// Set append system prompt
830    #[allow(deprecated)]
831    pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
832        self.options.append_system_prompt = Some(prompt.into());
833        self
834    }
835
836    /// Add allowed tools
837    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
838        self.options.allowed_tools = tools;
839        self
840    }
841
842    /// Add a single allowed tool
843    pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
844        self.options.allowed_tools.push(tool.into());
845        self
846    }
847
848    /// Add disallowed tools
849    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
850        self.options.disallowed_tools = tools;
851        self
852    }
853
854    /// Add a single disallowed tool
855    pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
856        self.options.disallowed_tools.push(tool.into());
857        self
858    }
859
860    /// Set permission mode
861    pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
862        self.options.permission_mode = mode;
863        self
864    }
865
866    /// Add MCP server
867    pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
868        self.options.mcp_servers.insert(name.into(), config);
869        self
870    }
871
872    /// Set all MCP servers from a map
873    pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
874        self.options.mcp_servers = servers;
875        self
876    }
877
878    /// Set MCP tools
879    pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
880        self.options.mcp_tools = tools;
881        self
882    }
883
884    /// Set max turns
885    pub fn max_turns(mut self, turns: i32) -> Self {
886        self.options.max_turns = Some(turns);
887        self
888    }
889
890    /// Set max thinking tokens
891    pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
892        self.options.max_thinking_tokens = tokens;
893        self
894    }
895
896    /// Set max output tokens (1-32000, overrides CLAUDE_CODE_MAX_OUTPUT_TOKENS env var)
897    pub fn max_output_tokens(mut self, tokens: u32) -> Self {
898        self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
899        self
900    }
901
902    /// Set model
903    pub fn model(mut self, model: impl Into<String>) -> Self {
904        self.options.model = Some(model.into());
905        self
906    }
907
908    /// Set working directory
909    pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
910        self.options.cwd = Some(path.into());
911        self
912    }
913
914    /// Enable continue conversation
915    pub fn continue_conversation(mut self, enable: bool) -> Self {
916        self.options.continue_conversation = enable;
917        self
918    }
919
920    /// Set resume conversation ID
921    pub fn resume(mut self, id: impl Into<String>) -> Self {
922        self.options.resume = Some(id.into());
923        self
924    }
925
926    /// Set permission prompt tool name
927    pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
928        self.options.permission_prompt_tool_name = Some(name.into());
929        self
930    }
931
932    /// Set settings file path
933    pub fn settings(mut self, settings: impl Into<String>) -> Self {
934        self.options.settings = Some(settings.into());
935        self
936    }
937
938    /// Add directories as working directories
939    pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
940        self.options.add_dirs = dirs;
941        self
942    }
943
944    /// Add a single directory as working directory
945    pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
946        self.options.add_dirs.push(dir.into());
947        self
948    }
949
950    /// Add extra CLI arguments
951    pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
952        self.options.extra_args = args;
953        self
954    }
955
956    /// Add a single extra CLI argument
957    pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
958        self.options.extra_args.insert(key.into(), value);
959        self
960    }
961
962    /// Set control protocol format
963    pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
964        self.options.control_protocol_format = format;
965        self
966    }
967
968    /// Include partial assistant messages in streaming output
969    pub fn include_partial_messages(mut self, include: bool) -> Self {
970        self.options.include_partial_messages = include;
971        self
972    }
973
974    /// Enable fork_session behavior
975    pub fn fork_session(mut self, fork: bool) -> Self {
976        self.options.fork_session = fork;
977        self
978    }
979
980    /// Set setting sources
981    pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
982        self.options.setting_sources = Some(sources);
983        self
984    }
985
986    /// Define programmatic agents
987    pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
988        self.options.agents = Some(agents);
989        self
990    }
991
992    /// Set CLI channel buffer size
993    ///
994    /// Controls the size of internal communication channels (message, control, stdin buffers).
995    /// Default is 100. Increase for high-throughput scenarios to prevent message lag.
996    ///
997    /// # Arguments
998    ///
999    /// * `size` - Buffer size (number of messages that can be queued)
1000    ///
1001    /// # Example
1002    ///
1003    /// ```rust
1004    /// # use cc_sdk::ClaudeCodeOptions;
1005    /// let options = ClaudeCodeOptions::builder()
1006    ///     .cli_channel_buffer_size(500)
1007    ///     .build();
1008    /// ```
1009    pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1010        self.options.cli_channel_buffer_size = Some(size);
1011        self
1012    }
1013
1014    /// Build the options
1015    pub fn build(self) -> ClaudeCodeOptions {
1016        self.options
1017    }
1018}
1019
1020/// Main message type enum
1021#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1022#[serde(tag = "type", rename_all = "lowercase")]
1023pub enum Message {
1024    /// User message
1025    User {
1026        /// Message content
1027        message: UserMessage,
1028    },
1029    /// Assistant message
1030    Assistant {
1031        /// Message content
1032        message: AssistantMessage,
1033    },
1034    /// System message
1035    System {
1036        /// Subtype of system message
1037        subtype: String,
1038        /// Additional data
1039        data: serde_json::Value,
1040    },
1041    /// Result message indicating end of turn
1042    Result {
1043        /// Result subtype
1044        subtype: String,
1045        /// Duration in milliseconds
1046        duration_ms: i64,
1047        /// API duration in milliseconds
1048        duration_api_ms: i64,
1049        /// Whether an error occurred
1050        is_error: bool,
1051        /// Number of turns
1052        num_turns: i32,
1053        /// Session ID
1054        session_id: String,
1055        /// Total cost in USD
1056        #[serde(skip_serializing_if = "Option::is_none")]
1057        total_cost_usd: Option<f64>,
1058        /// Usage statistics
1059        #[serde(skip_serializing_if = "Option::is_none")]
1060        usage: Option<serde_json::Value>,
1061        /// Result message
1062        #[serde(skip_serializing_if = "Option::is_none")]
1063        result: Option<String>,
1064    },
1065}
1066
1067/// User message content
1068#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1069pub struct UserMessage {
1070    /// Message content
1071    pub content: String,
1072}
1073
1074/// Assistant message content
1075#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1076pub struct AssistantMessage {
1077    /// Content blocks
1078    pub content: Vec<ContentBlock>,
1079}
1080
1081/// Result message (re-export for convenience)  
1082pub use Message::Result as ResultMessage;
1083/// System message (re-export for convenience)
1084pub use Message::System as SystemMessage;
1085
1086/// Content block types
1087#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1088#[serde(untagged)]
1089pub enum ContentBlock {
1090    /// Text content
1091    Text(TextContent),
1092    /// Thinking content
1093    Thinking(ThinkingContent),
1094    /// Tool use request
1095    ToolUse(ToolUseContent),
1096    /// Tool result
1097    ToolResult(ToolResultContent),
1098}
1099
1100/// Text content block
1101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1102pub struct TextContent {
1103    /// Text content
1104    pub text: String,
1105}
1106
1107/// Thinking content block
1108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1109pub struct ThinkingContent {
1110    /// Thinking content
1111    pub thinking: String,
1112    /// Signature
1113    pub signature: String,
1114}
1115
1116/// Tool use content block
1117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1118pub struct ToolUseContent {
1119    /// Tool use ID
1120    pub id: String,
1121    /// Tool name
1122    pub name: String,
1123    /// Tool input parameters
1124    pub input: serde_json::Value,
1125}
1126
1127/// Tool result content block
1128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1129pub struct ToolResultContent {
1130    /// Tool use ID this result corresponds to
1131    pub tool_use_id: String,
1132    /// Result content
1133    #[serde(skip_serializing_if = "Option::is_none")]
1134    pub content: Option<ContentValue>,
1135    /// Whether this is an error result
1136    #[serde(skip_serializing_if = "Option::is_none")]
1137    pub is_error: Option<bool>,
1138}
1139
1140/// Content value for tool results
1141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1142#[serde(untagged)]
1143pub enum ContentValue {
1144    /// Text content
1145    Text(String),
1146    /// Structured content
1147    Structured(Vec<serde_json::Value>),
1148}
1149
1150/// User content structure for internal use
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1152pub struct UserContent {
1153    /// Role (always "user")
1154    pub role: String,
1155    /// Message content
1156    pub content: String,
1157}
1158
1159/// Assistant content structure for internal use
1160#[derive(Debug, Clone, Serialize, Deserialize)]
1161pub struct AssistantContent {
1162    /// Role (always "assistant")
1163    pub role: String,
1164    /// Content blocks
1165    pub content: Vec<ContentBlock>,
1166}
1167
1168/// SDK Control Protocol - Interrupt request
1169#[derive(Debug, Clone, Serialize, Deserialize)]
1170pub struct SDKControlInterruptRequest {
1171    /// Subtype
1172    pub subtype: String,  // "interrupt"
1173}
1174
1175/// SDK Control Protocol - Permission request
1176#[derive(Debug, Clone, Serialize, Deserialize)]
1177#[serde(rename_all = "camelCase")]
1178pub struct SDKControlPermissionRequest {
1179    /// Subtype
1180    pub subtype: String,  // "can_use_tool"
1181    /// Tool name
1182    pub tool_name: String,
1183    /// Tool input
1184    pub input: serde_json::Value,
1185    /// Permission suggestions
1186    #[serde(skip_serializing_if = "Option::is_none")]
1187    pub permission_suggestions: Option<Vec<PermissionUpdate>>,
1188    /// Blocked path
1189    #[serde(skip_serializing_if = "Option::is_none")]
1190    pub blocked_path: Option<String>,
1191}
1192
1193/// SDK Control Protocol - Initialize request
1194#[derive(Debug, Clone, Serialize, Deserialize)]
1195pub struct SDKControlInitializeRequest {
1196    /// Subtype
1197    pub subtype: String,  // "initialize"
1198    /// Hooks configuration
1199    #[serde(skip_serializing_if = "Option::is_none")]
1200    pub hooks: Option<HashMap<String, serde_json::Value>>,
1201}
1202
1203/// SDK Control Protocol - Set permission mode request
1204#[derive(Debug, Clone, Serialize, Deserialize)]
1205#[serde(rename_all = "camelCase")]
1206pub struct SDKControlSetPermissionModeRequest {
1207    /// Subtype
1208    pub subtype: String,  // "set_permission_mode"
1209    /// Permission mode
1210    pub mode: String,
1211}
1212
1213/// SDK Control Protocol - Set model request
1214#[derive(Debug, Clone, Serialize, Deserialize)]
1215#[serde(rename_all = "camelCase")]
1216pub struct SDKControlSetModelRequest {
1217    /// Subtype
1218    pub subtype: String, // "set_model"
1219    /// Model to set (None to clear)
1220    #[serde(skip_serializing_if = "Option::is_none")]
1221    pub model: Option<String>,
1222}
1223
1224/// SDK Hook callback request
1225#[derive(Debug, Clone, Serialize, Deserialize)]
1226#[serde(rename_all = "camelCase")]
1227pub struct SDKHookCallbackRequest {
1228    /// Subtype
1229    pub subtype: String,  // "hook_callback"
1230    /// Callback ID
1231    pub callback_id: String,
1232    /// Input data
1233    pub input: serde_json::Value,
1234    /// Tool use ID
1235    #[serde(skip_serializing_if = "Option::is_none")]
1236    pub tool_use_id: Option<String>,
1237}
1238
1239/// SDK Control Protocol - MCP message request
1240#[derive(Debug, Clone, Serialize, Deserialize)]
1241#[serde(rename_all = "camelCase")]
1242pub struct SDKControlMcpMessageRequest {
1243    /// Subtype
1244    pub subtype: String,  // "mcp_message"
1245    /// MCP server name
1246    pub mcp_server_name: String,
1247    /// Message to send
1248    pub message: serde_json::Value,
1249}
1250
1251/// SDK Control Protocol request types
1252#[derive(Debug, Clone, Serialize, Deserialize)]
1253#[serde(tag = "type", rename_all = "snake_case")]
1254pub enum SDKControlRequest {
1255    /// Interrupt request
1256    #[serde(rename = "interrupt")]
1257    Interrupt(SDKControlInterruptRequest),
1258    /// Permission request
1259    #[serde(rename = "can_use_tool")]
1260    CanUseTool(SDKControlPermissionRequest),
1261    /// Initialize request
1262    #[serde(rename = "initialize")]
1263    Initialize(SDKControlInitializeRequest),
1264    /// Set permission mode
1265    #[serde(rename = "set_permission_mode")]
1266    SetPermissionMode(SDKControlSetPermissionModeRequest),
1267    /// Set model
1268    #[serde(rename = "set_model")]
1269    SetModel(SDKControlSetModelRequest),
1270    /// Hook callback
1271    #[serde(rename = "hook_callback")]
1272    HookCallback(SDKHookCallbackRequest),
1273    /// MCP message
1274    #[serde(rename = "mcp_message")]
1275    McpMessage(SDKControlMcpMessageRequest),
1276}
1277
1278/// Control request types (legacy, keeping for compatibility)
1279#[derive(Debug, Clone, Serialize, Deserialize)]
1280#[serde(tag = "type", rename_all = "lowercase")]
1281pub enum ControlRequest {
1282    /// Interrupt the current operation
1283    Interrupt {
1284        /// Request ID
1285        request_id: String,
1286    },
1287}
1288
1289/// Control response types (legacy, keeping for compatibility)
1290#[derive(Debug, Clone, Serialize, Deserialize)]
1291#[serde(tag = "type", rename_all = "lowercase")]
1292pub enum ControlResponse {
1293    /// Interrupt acknowledged
1294    InterruptAck {
1295        /// Request ID
1296        request_id: String,
1297        /// Whether interrupt was successful
1298        success: bool,
1299    },
1300}
1301
1302#[cfg(test)]
1303mod tests {
1304    use super::*;
1305
1306    #[test]
1307    fn test_permission_mode_serialization() {
1308        let mode = PermissionMode::AcceptEdits;
1309        let json = serde_json::to_string(&mode).unwrap();
1310        assert_eq!(json, r#""acceptEdits""#);
1311
1312        let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
1313        assert_eq!(deserialized, mode);
1314
1315        // Test Plan mode
1316        let plan_mode = PermissionMode::Plan;
1317        let plan_json = serde_json::to_string(&plan_mode).unwrap();
1318        assert_eq!(plan_json, r#""plan""#);
1319
1320        let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
1321        assert_eq!(plan_deserialized, plan_mode);
1322    }
1323
1324    #[test]
1325    fn test_message_serialization() {
1326        let msg = Message::User {
1327            message: UserMessage {
1328                content: "Hello".to_string(),
1329            },
1330        };
1331
1332        let json = serde_json::to_string(&msg).unwrap();
1333        assert!(json.contains(r#""type":"user""#));
1334        assert!(json.contains(r#""content":"Hello""#));
1335
1336        let deserialized: Message = serde_json::from_str(&json).unwrap();
1337        assert_eq!(deserialized, msg);
1338    }
1339
1340    #[test]
1341    #[allow(deprecated)]
1342    fn test_options_builder() {
1343        let options = ClaudeCodeOptions::builder()
1344            .system_prompt("Test prompt")
1345            .model("claude-3-opus")
1346            .permission_mode(PermissionMode::AcceptEdits)
1347            .allow_tool("read")
1348            .allow_tool("write")
1349            .max_turns(10)
1350            .build();
1351
1352        assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
1353        assert_eq!(options.model, Some("claude-3-opus".to_string()));
1354        assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
1355        assert_eq!(options.allowed_tools, vec!["read", "write"]);
1356        assert_eq!(options.max_turns, Some(10));
1357    }
1358
1359    #[test]
1360    fn test_extra_args() {
1361        let mut extra_args = HashMap::new();
1362        extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
1363        extra_args.insert("boolean-flag".to_string(), None);
1364
1365        let options = ClaudeCodeOptions::builder()
1366            .extra_args(extra_args.clone())
1367            .add_extra_arg("another-flag", Some("another-value".to_string()))
1368            .build();
1369
1370        assert_eq!(options.extra_args.len(), 3);
1371        assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
1372        assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
1373        assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
1374    }
1375
1376    #[test]
1377    fn test_thinking_content_serialization() {
1378        let thinking = ThinkingContent {
1379            thinking: "Let me think about this...".to_string(),
1380            signature: "sig123".to_string(),
1381        };
1382
1383        let json = serde_json::to_string(&thinking).unwrap();
1384        assert!(json.contains(r#""thinking":"Let me think about this...""#));
1385        assert!(json.contains(r#""signature":"sig123""#));
1386
1387        let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
1388        assert_eq!(deserialized.thinking, thinking.thinking);
1389        assert_eq!(deserialized.signature, thinking.signature);
1390    }
1391}