1#![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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub enum PermissionMode {
19 Default,
21 AcceptEdits,
23 Plan,
25 BypassPermissions,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub enum SdkBeta {
36 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(untagged)]
59pub enum ToolsConfig {
60 List(Vec<String>),
63 Preset(ToolsPreset),
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ToolsPreset {
70 #[serde(rename = "type")]
72 pub preset_type: String,
73 pub preset: String,
75}
76
77impl ToolsConfig {
78 pub fn list(tools: Vec<String>) -> Self {
80 ToolsConfig::List(tools)
81 }
82
83 pub fn none() -> Self {
85 ToolsConfig::List(vec![])
86 }
87
88 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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct SandboxNetworkConfig {
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub allow_unix_sockets: Option<Vec<String>>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub allow_all_unix_sockets: Option<bool>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub allow_local_binding: Option<bool>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub http_proxy_port: Option<u16>,
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub socks_proxy_port: Option<u16>,
120}
121
122#[derive(Debug, Clone, Default, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct SandboxIgnoreViolations {
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub file: Option<Vec<String>>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub network: Option<Vec<String>>,
132}
133
134#[derive(Debug, Clone, Default, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct SandboxSettings {
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub enabled: Option<bool>,
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub auto_allow_bash_if_sandboxed: Option<bool>,
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub excluded_commands: Option<Vec<String>>,
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub allow_unsandboxed_commands: Option<bool>,
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub network: Option<SandboxNetworkConfig>,
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub ignore_violations: Option<SandboxIgnoreViolations>,
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub enable_weaker_nested_sandbox: Option<bool>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(tag = "type", rename_all = "lowercase")]
171pub enum SdkPluginConfig {
172 Local {
174 path: String,
176 },
177}
178
179impl Default for PermissionMode {
180 fn default() -> Self {
181 Self::Default
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub enum ControlProtocolFormat {
188 Legacy,
190 Control,
192 Auto,
194}
195
196impl Default for ControlProtocolFormat {
197 fn default() -> Self {
198 Self::Legacy
200 }
201}
202
203#[derive(Clone)]
205pub enum McpServerConfig {
206 Stdio {
208 command: String,
210 args: Option<Vec<String>>,
212 env: Option<HashMap<String, String>>,
214 },
215 Sse {
217 url: String,
219 headers: Option<HashMap<String, String>>,
221 },
222 Http {
224 url: String,
226 headers: Option<HashMap<String, String>>,
228 },
229 Sdk {
231 name: String,
233 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub enum PermissionUpdateDestination {
355 UserSettings,
357 ProjectSettings,
359 LocalSettings,
361 Session,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub enum PermissionBehavior {
369 Allow,
371 Deny,
373 Ask,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct PermissionRuleValue {
380 pub tool_name: String,
382 pub rule_content: Option<String>,
384}
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub enum PermissionUpdateType {
390 AddRules,
392 ReplaceRules,
394 RemoveRules,
396 SetMode,
398 AddDirectories,
400 RemoveDirectories,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
406#[serde(rename_all = "camelCase")]
407pub struct PermissionUpdate {
408 #[serde(rename = "type")]
410 pub update_type: PermissionUpdateType,
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub rules: Option<Vec<PermissionRuleValue>>,
414 #[serde(skip_serializing_if = "Option::is_none")]
416 pub behavior: Option<PermissionBehavior>,
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub mode: Option<PermissionMode>,
420 #[serde(skip_serializing_if = "Option::is_none")]
422 pub directories: Option<Vec<String>>,
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub destination: Option<PermissionUpdateDestination>,
426}
427
428#[derive(Debug, Clone)]
430pub struct ToolPermissionContext {
431 pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
433 pub suggestions: Vec<PermissionUpdate>,
435}
436
437#[derive(Debug, Clone)]
439pub struct PermissionResultAllow {
440 pub updated_input: Option<serde_json::Value>,
442 pub updated_permissions: Option<Vec<PermissionUpdate>>,
444}
445
446#[derive(Debug, Clone)]
448pub struct PermissionResultDeny {
449 pub message: String,
451 pub interrupt: bool,
453}
454
455#[derive(Debug, Clone)]
457pub enum PermissionResult {
458 Allow(PermissionResultAllow),
460 Deny(PermissionResultDeny),
462}
463
464#[async_trait]
466pub trait CanUseTool: Send + Sync {
467 async fn can_use_tool(
469 &self,
470 tool_name: &str,
471 input: &serde_json::Value,
472 context: &ToolPermissionContext,
473 ) -> PermissionResult;
474}
475
476#[derive(Debug, Clone)]
478pub struct HookContext {
479 pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct BaseHookInput {
490 pub session_id: String,
492 pub transcript_path: String,
494 pub cwd: String,
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub permission_mode: Option<String>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct PreToolUseHookInput {
504 pub session_id: String,
506 pub transcript_path: String,
508 pub cwd: String,
510 #[serde(skip_serializing_if = "Option::is_none")]
512 pub permission_mode: Option<String>,
513 pub tool_name: String,
515 pub tool_input: serde_json::Value,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct PostToolUseHookInput {
522 pub session_id: String,
524 pub transcript_path: String,
526 pub cwd: String,
528 #[serde(skip_serializing_if = "Option::is_none")]
530 pub permission_mode: Option<String>,
531 pub tool_name: String,
533 pub tool_input: serde_json::Value,
535 pub tool_response: serde_json::Value,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize)]
541pub struct UserPromptSubmitHookInput {
542 pub session_id: String,
544 pub transcript_path: String,
546 pub cwd: String,
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub permission_mode: Option<String>,
551 pub prompt: String,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct StopHookInput {
558 pub session_id: String,
560 pub transcript_path: String,
562 pub cwd: String,
564 #[serde(skip_serializing_if = "Option::is_none")]
566 pub permission_mode: Option<String>,
567 pub stop_hook_active: bool,
569}
570
571#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct SubagentStopHookInput {
574 pub session_id: String,
576 pub transcript_path: String,
578 pub cwd: String,
580 #[serde(skip_serializing_if = "Option::is_none")]
582 pub permission_mode: Option<String>,
583 pub stop_hook_active: bool,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct PreCompactHookInput {
590 pub session_id: String,
592 pub transcript_path: String,
594 pub cwd: String,
596 #[serde(skip_serializing_if = "Option::is_none")]
598 pub permission_mode: Option<String>,
599 pub trigger: String,
601 #[serde(skip_serializing_if = "Option::is_none")]
603 pub custom_instructions: Option<String>,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
608#[serde(tag = "hook_event_name")]
609pub enum HookInput {
610 #[serde(rename = "PreToolUse")]
612 PreToolUse(PreToolUseHookInput),
613 #[serde(rename = "PostToolUse")]
615 PostToolUse(PostToolUseHookInput),
616 #[serde(rename = "UserPromptSubmit")]
618 UserPromptSubmit(UserPromptSubmitHookInput),
619 #[serde(rename = "Stop")]
621 Stop(StopHookInput),
622 #[serde(rename = "SubagentStop")]
624 SubagentStop(SubagentStopHookInput),
625 #[serde(rename = "PreCompact")]
627 PreCompact(PreCompactHookInput),
628}
629
630#[derive(Debug, Clone, Serialize, Deserialize)]
639pub struct AsyncHookJSONOutput {
640 #[serde(rename = "async")]
642 pub async_: bool,
643 #[serde(skip_serializing_if = "Option::is_none")]
645 #[serde(rename = "asyncTimeout")]
646 pub async_timeout: Option<u32>,
647}
648
649#[derive(Debug, Clone, Default, Serialize, Deserialize)]
654pub struct SyncHookJSONOutput {
655 #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
658 pub continue_: Option<bool>,
659 #[serde(rename = "suppressOutput", skip_serializing_if = "Option::is_none")]
661 pub suppress_output: Option<bool>,
662 #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
664 pub stop_reason: Option<String>,
665
666 #[serde(skip_serializing_if = "Option::is_none")]
669 pub decision: Option<String>, #[serde(rename = "systemMessage", skip_serializing_if = "Option::is_none")]
672 pub system_message: Option<String>,
673 #[serde(skip_serializing_if = "Option::is_none")]
675 pub reason: Option<String>,
676
677 #[serde(rename = "hookSpecificOutput", skip_serializing_if = "Option::is_none")]
680 pub hook_specific_output: Option<HookSpecificOutput>,
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize)]
685#[serde(untagged)]
686pub enum HookJSONOutput {
687 Async(AsyncHookJSONOutput),
689 Sync(SyncHookJSONOutput),
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
695pub struct PreToolUseHookSpecificOutput {
696 #[serde(rename = "permissionDecision", skip_serializing_if = "Option::is_none")]
698 pub permission_decision: Option<String>,
699 #[serde(rename = "permissionDecisionReason", skip_serializing_if = "Option::is_none")]
701 pub permission_decision_reason: Option<String>,
702 #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
704 pub updated_input: Option<serde_json::Value>,
705}
706
707#[derive(Debug, Clone, Serialize, Deserialize)]
709pub struct PostToolUseHookSpecificOutput {
710 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
712 pub additional_context: Option<String>,
713}
714
715#[derive(Debug, Clone, Serialize, Deserialize)]
717pub struct UserPromptSubmitHookSpecificOutput {
718 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
720 pub additional_context: Option<String>,
721}
722
723#[derive(Debug, Clone, Serialize, Deserialize)]
725pub struct SessionStartHookSpecificOutput {
726 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
728 pub additional_context: Option<String>,
729}
730
731#[derive(Debug, Clone, Serialize, Deserialize)]
733#[serde(tag = "hookEventName")]
734pub enum HookSpecificOutput {
735 #[serde(rename = "PreToolUse")]
737 PreToolUse(PreToolUseHookSpecificOutput),
738 #[serde(rename = "PostToolUse")]
740 PostToolUse(PostToolUseHookSpecificOutput),
741 #[serde(rename = "UserPromptSubmit")]
743 UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
744 #[serde(rename = "SessionStart")]
746 SessionStart(SessionStartHookSpecificOutput),
747}
748
749#[async_trait]
758pub trait HookCallback: Send + Sync {
759 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#[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 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#[derive(Clone)]
800pub struct HookMatcher {
801 pub matcher: Option<serde_json::Value>,
803 pub hooks: Vec<Arc<dyn HookCallback>>,
805}
806
807#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
809#[serde(rename_all = "lowercase")]
810pub enum SettingSource {
811 User,
813 Project,
815 Local,
817}
818
819#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct AgentDefinition {
822 pub description: String,
824 pub prompt: String,
826 #[serde(skip_serializing_if = "Option::is_none")]
828 pub tools: Option<Vec<String>>,
829 #[serde(skip_serializing_if = "Option::is_none")]
831 pub model: Option<String>,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
836#[serde(untagged)]
837pub enum SystemPrompt {
838 String(String),
840 Preset {
842 #[serde(rename = "type")]
843 preset_type: String, preset: String, #[serde(skip_serializing_if = "Option::is_none")]
846 append: Option<String>,
847 },
848}
849
850#[derive(Clone, Default)]
852pub struct ClaudeCodeOptions {
853 pub system_prompt_v2: Option<SystemPrompt>,
857 #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
860 pub system_prompt: Option<String>,
861 #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
864 pub append_system_prompt: Option<String>,
865 pub allowed_tools: Vec<String>,
874 pub disallowed_tools: Vec<String>,
883 pub permission_mode: PermissionMode,
885 pub mcp_servers: HashMap<String, McpServerConfig>,
887 pub mcp_tools: Vec<String>,
889 pub max_turns: Option<i32>,
891 pub max_thinking_tokens: i32,
893 pub max_output_tokens: Option<u32>,
895 pub model: Option<String>,
897 pub cwd: Option<PathBuf>,
899 pub continue_conversation: bool,
901 pub resume: Option<String>,
903 pub permission_prompt_tool_name: Option<String>,
905 pub settings: Option<String>,
907 pub add_dirs: Vec<PathBuf>,
909 pub extra_args: HashMap<String, Option<String>>,
911 pub env: HashMap<String, String>,
913 pub debug_stderr: Option<Arc<Mutex<dyn Write + Send + Sync>>>,
915 pub include_partial_messages: bool,
917 pub can_use_tool: Option<Arc<dyn CanUseTool>>,
919 pub hooks: Option<HashMap<String, Vec<HookMatcher>>>,
921 pub control_protocol_format: ControlProtocolFormat,
923
924 pub setting_sources: Option<Vec<SettingSource>>,
928 pub fork_session: bool,
931 pub agents: Option<HashMap<String, AgentDefinition>>,
934 pub cli_channel_buffer_size: Option<usize>,
938
939 pub tools: Option<ToolsConfig>,
965 pub betas: Vec<SdkBeta>,
968 pub max_budget_usd: Option<f64>,
971 pub fallback_model: Option<String>,
973 pub output_format: Option<serde_json::Value>,
976 pub enable_file_checkpointing: bool,
980 pub sandbox: Option<SandboxSettings>,
983 pub plugins: Vec<SdkPluginConfig>,
985 pub user: Option<String>,
993 pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
996 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 pub fn builder() -> ClaudeCodeOptionsBuilder {
1052 ClaudeCodeOptionsBuilder::default()
1053 }
1054}
1055
1056#[derive(Debug, Default)]
1058pub struct ClaudeCodeOptionsBuilder {
1059 options: ClaudeCodeOptions,
1060}
1061
1062impl ClaudeCodeOptionsBuilder {
1063 #[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 #[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 pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1085 self.options.allowed_tools = tools;
1086 self
1087 }
1088
1089 pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1093 self.options.allowed_tools.push(tool.into());
1094 self
1095 }
1096
1097 pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1106 self.options.disallowed_tools = tools;
1107 self
1108 }
1109
1110 pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1114 self.options.disallowed_tools.push(tool.into());
1115 self
1116 }
1117
1118 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1120 self.options.permission_mode = mode;
1121 self
1122 }
1123
1124 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 pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1132 self.options.mcp_servers = servers;
1133 self
1134 }
1135
1136 pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1138 self.options.mcp_tools = tools;
1139 self
1140 }
1141
1142 pub fn max_turns(mut self, turns: i32) -> Self {
1144 self.options.max_turns = Some(turns);
1145 self
1146 }
1147
1148 pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1150 self.options.max_thinking_tokens = tokens;
1151 self
1152 }
1153
1154 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 pub fn model(mut self, model: impl Into<String>) -> Self {
1162 self.options.model = Some(model.into());
1163 self
1164 }
1165
1166 pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1168 self.options.cwd = Some(path.into());
1169 self
1170 }
1171
1172 pub fn continue_conversation(mut self, enable: bool) -> Self {
1174 self.options.continue_conversation = enable;
1175 self
1176 }
1177
1178 pub fn resume(mut self, id: impl Into<String>) -> Self {
1180 self.options.resume = Some(id.into());
1181 self
1182 }
1183
1184 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 pub fn settings(mut self, settings: impl Into<String>) -> Self {
1192 self.options.settings = Some(settings.into());
1193 self
1194 }
1195
1196 pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1198 self.options.add_dirs = dirs;
1199 self
1200 }
1201
1202 pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1204 self.options.add_dirs.push(dir.into());
1205 self
1206 }
1207
1208 pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1210 self.options.extra_args = args;
1211 self
1212 }
1213
1214 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 pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1222 self.options.control_protocol_format = format;
1223 self
1224 }
1225
1226 pub fn include_partial_messages(mut self, include: bool) -> Self {
1228 self.options.include_partial_messages = include;
1229 self
1230 }
1231
1232 pub fn fork_session(mut self, fork: bool) -> Self {
1234 self.options.fork_session = fork;
1235 self
1236 }
1237
1238 pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1240 self.options.setting_sources = Some(sources);
1241 self
1242 }
1243
1244 pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1246 self.options.agents = Some(agents);
1247 self
1248 }
1249
1250 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 pub fn tools(mut self, config: ToolsConfig) -> Self {
1289 self.options.tools = Some(config);
1290 self
1291 }
1292
1293 pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1297 self.options.betas = betas;
1298 self
1299 }
1300
1301 pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1303 self.options.betas.push(beta);
1304 self
1305 }
1306
1307 pub fn max_budget_usd(mut self, budget: f64) -> Self {
1311 self.options.max_budget_usd = Some(budget);
1312 self
1313 }
1314
1315 pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1319 self.options.fallback_model = Some(model.into());
1320 self
1321 }
1322
1323 pub fn output_format(mut self, format: serde_json::Value) -> Self {
1344 self.options.output_format = Some(format);
1345 self
1346 }
1347
1348 pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1353 self.options.enable_file_checkpointing = enable;
1354 self
1355 }
1356
1357 pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1361 self.options.sandbox = Some(settings);
1362 self
1363 }
1364
1365 pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1367 self.options.plugins = plugins;
1368 self
1369 }
1370
1371 pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1373 self.options.plugins.push(plugin);
1374 self
1375 }
1376
1377 pub fn user(mut self, user: impl Into<String>) -> Self {
1379 self.options.user = Some(user.into());
1380 self
1381 }
1382
1383 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 pub fn auto_download_cli(mut self, enable: bool) -> Self {
1405 self.options.auto_download_cli = enable;
1406 self
1407 }
1408
1409 pub fn build(self) -> ClaudeCodeOptions {
1411 self.options
1412 }
1413}
1414
1415#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1417#[serde(tag = "type", rename_all = "lowercase")]
1418pub enum Message {
1419 User {
1421 message: UserMessage,
1423 },
1424 Assistant {
1426 message: AssistantMessage,
1428 },
1429 System {
1431 subtype: String,
1433 data: serde_json::Value,
1435 },
1436 Result {
1438 subtype: String,
1440 duration_ms: i64,
1442 duration_api_ms: i64,
1444 is_error: bool,
1446 num_turns: i32,
1448 session_id: String,
1450 #[serde(skip_serializing_if = "Option::is_none")]
1452 total_cost_usd: Option<f64>,
1453 #[serde(skip_serializing_if = "Option::is_none")]
1455 usage: Option<serde_json::Value>,
1456 #[serde(skip_serializing_if = "Option::is_none")]
1458 result: Option<String>,
1459 #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
1462 structured_output: Option<serde_json::Value>,
1463 },
1464}
1465
1466#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1468pub struct UserMessage {
1469 pub content: String,
1471}
1472
1473#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1475pub struct AssistantMessage {
1476 pub content: Vec<ContentBlock>,
1478}
1479
1480pub use Message::Result as ResultMessage;
1482pub use Message::System as SystemMessage;
1484
1485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1487#[serde(untagged)]
1488pub enum ContentBlock {
1489 Text(TextContent),
1491 Thinking(ThinkingContent),
1493 ToolUse(ToolUseContent),
1495 ToolResult(ToolResultContent),
1497}
1498
1499#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1501pub struct TextContent {
1502 pub text: String,
1504}
1505
1506#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1508pub struct ThinkingContent {
1509 pub thinking: String,
1511 pub signature: String,
1513}
1514
1515#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1517pub struct ToolUseContent {
1518 pub id: String,
1520 pub name: String,
1522 pub input: serde_json::Value,
1524}
1525
1526#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1528pub struct ToolResultContent {
1529 pub tool_use_id: String,
1531 #[serde(skip_serializing_if = "Option::is_none")]
1533 pub content: Option<ContentValue>,
1534 #[serde(skip_serializing_if = "Option::is_none")]
1536 pub is_error: Option<bool>,
1537}
1538
1539#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1541#[serde(untagged)]
1542pub enum ContentValue {
1543 Text(String),
1545 Structured(Vec<serde_json::Value>),
1547}
1548
1549#[derive(Debug, Clone, Serialize, Deserialize)]
1551pub struct UserContent {
1552 pub role: String,
1554 pub content: String,
1556}
1557
1558#[derive(Debug, Clone, Serialize, Deserialize)]
1560pub struct AssistantContent {
1561 pub role: String,
1563 pub content: Vec<ContentBlock>,
1565}
1566
1567#[derive(Debug, Clone, Serialize, Deserialize)]
1569pub struct SDKControlInterruptRequest {
1570 pub subtype: String, }
1573
1574#[derive(Debug, Clone, Serialize, Deserialize)]
1576pub struct SDKControlPermissionRequest {
1577 pub subtype: String, #[serde(alias = "toolName")]
1581 pub tool_name: String,
1582 pub input: serde_json::Value,
1584 #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
1586 pub permission_suggestions: Option<Vec<PermissionUpdate>>,
1587 #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
1589 pub blocked_path: Option<String>,
1590}
1591
1592#[derive(Debug, Clone, Serialize, Deserialize)]
1594pub struct SDKControlInitializeRequest {
1595 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
1599 pub hooks: Option<HashMap<String, serde_json::Value>>,
1600}
1601
1602#[derive(Debug, Clone, Serialize, Deserialize)]
1604#[serde(rename_all = "camelCase")]
1605pub struct SDKControlSetPermissionModeRequest {
1606 pub subtype: String, pub mode: String,
1610}
1611
1612#[derive(Debug, Clone, Serialize, Deserialize)]
1614#[serde(rename_all = "camelCase")]
1615pub struct SDKControlSetModelRequest {
1616 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
1620 pub model: Option<String>,
1621}
1622
1623#[derive(Debug, Clone, Serialize, Deserialize)]
1625pub struct SDKHookCallbackRequest {
1626 pub subtype: String, #[serde(alias = "callbackId")]
1630 pub callback_id: String,
1631 pub input: serde_json::Value,
1633 #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
1635 pub tool_use_id: Option<String>,
1636}
1637
1638#[derive(Debug, Clone, Serialize, Deserialize)]
1640pub struct SDKControlMcpMessageRequest {
1641 pub subtype: String, #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
1645 pub mcp_server_name: String,
1646 pub message: serde_json::Value,
1648}
1649
1650#[derive(Debug, Clone, Serialize, Deserialize)]
1655pub struct SDKControlRewindFilesRequest {
1656 pub subtype: String,
1658 #[serde(alias = "userMessageId")]
1660 pub user_message_id: String,
1661}
1662
1663impl SDKControlRewindFilesRequest {
1664 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#[derive(Debug, Clone, Serialize, Deserialize)]
1675#[serde(tag = "type", rename_all = "snake_case")]
1676pub enum SDKControlRequest {
1677 #[serde(rename = "interrupt")]
1679 Interrupt(SDKControlInterruptRequest),
1680 #[serde(rename = "can_use_tool")]
1682 CanUseTool(SDKControlPermissionRequest),
1683 #[serde(rename = "initialize")]
1685 Initialize(SDKControlInitializeRequest),
1686 #[serde(rename = "set_permission_mode")]
1688 SetPermissionMode(SDKControlSetPermissionModeRequest),
1689 #[serde(rename = "set_model")]
1691 SetModel(SDKControlSetModelRequest),
1692 #[serde(rename = "hook_callback")]
1694 HookCallback(SDKHookCallbackRequest),
1695 #[serde(rename = "mcp_message")]
1697 McpMessage(SDKControlMcpMessageRequest),
1698 #[serde(rename = "rewind_files")]
1700 RewindFiles(SDKControlRewindFilesRequest),
1701}
1702
1703#[derive(Debug, Clone, Serialize, Deserialize)]
1705#[serde(tag = "type", rename_all = "lowercase")]
1706pub enum ControlRequest {
1707 Interrupt {
1709 request_id: String,
1711 },
1712}
1713
1714#[derive(Debug, Clone, Serialize, Deserialize)]
1716#[serde(tag = "type", rename_all = "lowercase")]
1717pub enum ControlResponse {
1718 InterruptAck {
1720 request_id: String,
1722 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 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 #[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 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 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 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 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 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 let empty = ToolsConfig::none();
1874 match empty {
1875 ToolsConfig::List(list) => assert!(list.is_empty()),
1876 _ => panic!("Expected empty List variant"),
1877 }
1878
1879 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 assert_eq!(json, r#""context-1m-2025-08-07""#);
1896
1897 let display = format!("{}", beta);
1899 assert_eq!(display, "context-1m-2025-08-07");
1900
1901 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")); 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")); 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 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 assert_eq!(options.betas.len(), 1);
2041 assert!(matches!(options.betas[0], SdkBeta::Context1M));
2042
2043 assert_eq!(options.max_budget_usd, Some(10.0));
2045
2046 assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2048
2049 assert!(options.output_format.is_some());
2051
2052 assert!(options.enable_file_checkpointing);
2054
2055 assert!(options.sandbox.is_some());
2057
2058 assert_eq!(options.plugins.len(), 1);
2060
2061 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 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 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 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 assert_eq!(options.plugins.len(), 3);
2125 }
2126
2127 #[test]
2128 fn test_message_result_with_structured_output() {
2129 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 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 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 assert!(!options.auto_download_cli);
2203 }
2204}