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 pub tool_use_id: String,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct PostToolUseHookInput {
524 pub session_id: String,
526 pub transcript_path: String,
528 pub cwd: String,
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub permission_mode: Option<String>,
533 pub tool_name: String,
535 pub tool_input: serde_json::Value,
537 pub tool_response: serde_json::Value,
539 pub tool_use_id: String,
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct UserPromptSubmitHookInput {
546 pub session_id: String,
548 pub transcript_path: String,
550 pub cwd: String,
552 #[serde(skip_serializing_if = "Option::is_none")]
554 pub permission_mode: Option<String>,
555 pub prompt: String,
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct StopHookInput {
562 pub session_id: String,
564 pub transcript_path: String,
566 pub cwd: String,
568 #[serde(skip_serializing_if = "Option::is_none")]
570 pub permission_mode: Option<String>,
571 pub stop_hook_active: bool,
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct SubagentStopHookInput {
578 pub session_id: String,
580 pub transcript_path: String,
582 pub cwd: String,
584 #[serde(skip_serializing_if = "Option::is_none")]
586 pub permission_mode: Option<String>,
587 pub stop_hook_active: bool,
589 pub agent_id: String,
591 pub agent_transcript_path: String,
593 pub agent_type: String,
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct PreCompactHookInput {
600 pub session_id: String,
602 pub transcript_path: String,
604 pub cwd: String,
606 #[serde(skip_serializing_if = "Option::is_none")]
608 pub permission_mode: Option<String>,
609 pub trigger: String,
611 #[serde(skip_serializing_if = "Option::is_none")]
613 pub custom_instructions: Option<String>,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize)]
618pub struct PostToolUseFailureHookInput {
619 pub session_id: String,
621 pub transcript_path: String,
623 pub cwd: String,
625 #[serde(skip_serializing_if = "Option::is_none")]
627 pub permission_mode: Option<String>,
628 pub tool_name: String,
630 pub tool_input: serde_json::Value,
632 pub tool_use_id: String,
634 pub error: String,
636 #[serde(skip_serializing_if = "Option::is_none")]
638 pub is_interrupt: Option<bool>,
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize)]
643pub struct NotificationHookInput {
644 pub session_id: String,
646 pub transcript_path: String,
648 pub cwd: String,
650 #[serde(skip_serializing_if = "Option::is_none")]
652 pub permission_mode: Option<String>,
653 pub message: String,
655 #[serde(skip_serializing_if = "Option::is_none")]
657 pub title: Option<String>,
658 pub notification_type: String,
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize)]
664pub struct SubagentStartHookInput {
665 pub session_id: String,
667 pub transcript_path: String,
669 pub cwd: String,
671 #[serde(skip_serializing_if = "Option::is_none")]
673 pub permission_mode: Option<String>,
674 pub agent_id: String,
676 pub agent_type: String,
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct PermissionRequestHookInput {
683 pub session_id: String,
685 pub transcript_path: String,
687 pub cwd: String,
689 #[serde(skip_serializing_if = "Option::is_none")]
691 pub permission_mode: Option<String>,
692 pub tool_name: String,
694 pub tool_input: serde_json::Value,
696 #[serde(skip_serializing_if = "Option::is_none")]
698 pub permission_suggestions: Option<Vec<serde_json::Value>>,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
703#[serde(tag = "hook_event_name")]
704pub enum HookInput {
705 #[serde(rename = "PreToolUse")]
707 PreToolUse(PreToolUseHookInput),
708 #[serde(rename = "PostToolUse")]
710 PostToolUse(PostToolUseHookInput),
711 #[serde(rename = "PostToolUseFailure")]
713 PostToolUseFailure(PostToolUseFailureHookInput),
714 #[serde(rename = "UserPromptSubmit")]
716 UserPromptSubmit(UserPromptSubmitHookInput),
717 #[serde(rename = "Stop")]
719 Stop(StopHookInput),
720 #[serde(rename = "SubagentStop")]
722 SubagentStop(SubagentStopHookInput),
723 #[serde(rename = "PreCompact")]
725 PreCompact(PreCompactHookInput),
726 #[serde(rename = "Notification")]
728 Notification(NotificationHookInput),
729 #[serde(rename = "SubagentStart")]
731 SubagentStart(SubagentStartHookInput),
732 #[serde(rename = "PermissionRequest")]
734 PermissionRequest(PermissionRequestHookInput),
735}
736
737#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct AsyncHookJSONOutput {
747 #[serde(rename = "async")]
749 pub async_: bool,
750 #[serde(skip_serializing_if = "Option::is_none")]
752 #[serde(rename = "asyncTimeout")]
753 pub async_timeout: Option<u32>,
754}
755
756#[derive(Debug, Clone, Default, Serialize, Deserialize)]
761pub struct SyncHookJSONOutput {
762 #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
765 pub continue_: Option<bool>,
766 #[serde(rename = "suppressOutput", skip_serializing_if = "Option::is_none")]
768 pub suppress_output: Option<bool>,
769 #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
771 pub stop_reason: Option<String>,
772
773 #[serde(skip_serializing_if = "Option::is_none")]
776 pub decision: Option<String>, #[serde(rename = "systemMessage", skip_serializing_if = "Option::is_none")]
779 pub system_message: Option<String>,
780 #[serde(skip_serializing_if = "Option::is_none")]
782 pub reason: Option<String>,
783
784 #[serde(rename = "hookSpecificOutput", skip_serializing_if = "Option::is_none")]
787 pub hook_specific_output: Option<HookSpecificOutput>,
788}
789
790#[derive(Debug, Clone, Serialize, Deserialize)]
792#[serde(untagged)]
793pub enum HookJSONOutput {
794 Async(AsyncHookJSONOutput),
796 Sync(SyncHookJSONOutput),
798}
799
800#[derive(Debug, Clone, Serialize, Deserialize)]
802pub struct PreToolUseHookSpecificOutput {
803 #[serde(rename = "permissionDecision", skip_serializing_if = "Option::is_none")]
805 pub permission_decision: Option<String>,
806 #[serde(rename = "permissionDecisionReason", skip_serializing_if = "Option::is_none")]
808 pub permission_decision_reason: Option<String>,
809 #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
811 pub updated_input: Option<serde_json::Value>,
812 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
814 pub additional_context: Option<String>,
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
819pub struct PostToolUseHookSpecificOutput {
820 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
822 pub additional_context: Option<String>,
823 #[serde(rename = "updatedMCPToolOutput", skip_serializing_if = "Option::is_none")]
825 pub updated_mcp_tool_output: Option<serde_json::Value>,
826}
827
828#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct UserPromptSubmitHookSpecificOutput {
831 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
833 pub additional_context: Option<String>,
834}
835
836#[derive(Debug, Clone, Serialize, Deserialize)]
838pub struct SessionStartHookSpecificOutput {
839 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
841 pub additional_context: Option<String>,
842}
843
844#[derive(Debug, Clone, Serialize, Deserialize)]
846pub struct PostToolUseFailureHookSpecificOutput {
847 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
849 pub additional_context: Option<String>,
850}
851
852#[derive(Debug, Clone, Serialize, Deserialize)]
854pub struct NotificationHookSpecificOutput {
855 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
857 pub additional_context: Option<String>,
858}
859
860#[derive(Debug, Clone, Serialize, Deserialize)]
862pub struct SubagentStartHookSpecificOutput {
863 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
865 pub additional_context: Option<String>,
866}
867
868#[derive(Debug, Clone, Serialize, Deserialize)]
870pub struct PermissionRequestHookSpecificOutput {
871 pub decision: serde_json::Value,
873}
874
875#[derive(Debug, Clone, Serialize, Deserialize)]
877#[serde(tag = "hookEventName")]
878pub enum HookSpecificOutput {
879 #[serde(rename = "PreToolUse")]
881 PreToolUse(PreToolUseHookSpecificOutput),
882 #[serde(rename = "PostToolUse")]
884 PostToolUse(PostToolUseHookSpecificOutput),
885 #[serde(rename = "PostToolUseFailure")]
887 PostToolUseFailure(PostToolUseFailureHookSpecificOutput),
888 #[serde(rename = "UserPromptSubmit")]
890 UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
891 #[serde(rename = "SessionStart")]
893 SessionStart(SessionStartHookSpecificOutput),
894 #[serde(rename = "Notification")]
896 Notification(NotificationHookSpecificOutput),
897 #[serde(rename = "SubagentStart")]
899 SubagentStart(SubagentStartHookSpecificOutput),
900 #[serde(rename = "PermissionRequest")]
902 PermissionRequest(PermissionRequestHookSpecificOutput),
903}
904
905#[async_trait]
914pub trait HookCallback: Send + Sync {
915 async fn execute(
927 &self,
928 input: &HookInput,
929 tool_use_id: Option<&str>,
930 context: &HookContext,
931 ) -> Result<HookJSONOutput, crate::errors::SdkError>;
932}
933
934#[deprecated(
939 since = "0.3.0",
940 note = "Use the new HookCallback trait with HookInput/HookJSONOutput instead"
941)]
942#[allow(dead_code)]
943#[async_trait]
944pub trait HookCallbackLegacy: Send + Sync {
945 async fn execute_legacy(
947 &self,
948 input: &serde_json::Value,
949 tool_use_id: Option<&str>,
950 context: &HookContext,
951 ) -> serde_json::Value;
952}
953
954#[derive(Clone)]
956pub struct HookMatcher {
957 pub matcher: Option<serde_json::Value>,
959 pub hooks: Vec<Arc<dyn HookCallback>>,
961}
962
963#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
965#[serde(rename_all = "lowercase")]
966pub enum SettingSource {
967 User,
969 Project,
971 Local,
973}
974
975#[derive(Debug, Clone, Serialize, Deserialize)]
977pub struct AgentDefinition {
978 pub description: String,
980 pub prompt: String,
982 #[serde(skip_serializing_if = "Option::is_none")]
984 pub tools: Option<Vec<String>>,
985 #[serde(skip_serializing_if = "Option::is_none")]
987 pub model: Option<String>,
988}
989
990#[derive(Debug, Clone, Serialize, Deserialize)]
992#[serde(untagged)]
993pub enum SystemPrompt {
994 String(String),
996 Preset {
998 #[serde(rename = "type")]
999 preset_type: String, preset: String, #[serde(skip_serializing_if = "Option::is_none")]
1002 append: Option<String>,
1003 },
1004}
1005
1006#[derive(Clone, Default)]
1008pub struct ClaudeCodeOptions {
1009 pub system_prompt_v2: Option<SystemPrompt>,
1013 #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1016 pub system_prompt: Option<String>,
1017 #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1020 pub append_system_prompt: Option<String>,
1021 pub allowed_tools: Vec<String>,
1030 pub disallowed_tools: Vec<String>,
1039 pub permission_mode: PermissionMode,
1041 pub mcp_servers: HashMap<String, McpServerConfig>,
1043 pub mcp_tools: Vec<String>,
1045 pub max_turns: Option<i32>,
1047 pub max_thinking_tokens: Option<i32>,
1049 pub max_output_tokens: Option<u32>,
1051 pub model: Option<String>,
1053 pub cwd: Option<PathBuf>,
1055 pub continue_conversation: bool,
1057 pub resume: Option<String>,
1059 pub permission_prompt_tool_name: Option<String>,
1061 pub settings: Option<String>,
1063 pub add_dirs: Vec<PathBuf>,
1065 pub extra_args: HashMap<String, Option<String>>,
1067 pub env: HashMap<String, String>,
1069 pub debug_stderr: Option<Arc<Mutex<dyn Write + Send + Sync>>>,
1071 pub include_partial_messages: bool,
1073 pub can_use_tool: Option<Arc<dyn CanUseTool>>,
1075 pub hooks: Option<HashMap<String, Vec<HookMatcher>>>,
1077 pub control_protocol_format: ControlProtocolFormat,
1079
1080 pub setting_sources: Option<Vec<SettingSource>>,
1084 pub fork_session: bool,
1087 pub agents: Option<HashMap<String, AgentDefinition>>,
1090 pub cli_channel_buffer_size: Option<usize>,
1094
1095 pub tools: Option<ToolsConfig>,
1121 pub betas: Vec<SdkBeta>,
1124 pub max_budget_usd: Option<f64>,
1127 pub fallback_model: Option<String>,
1129 pub output_format: Option<serde_json::Value>,
1132 pub enable_file_checkpointing: bool,
1136 pub sandbox: Option<SandboxSettings>,
1139 pub plugins: Vec<SdkPluginConfig>,
1141 pub user: Option<String>,
1149 pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
1152 pub auto_download_cli: bool,
1171}
1172
1173impl std::fmt::Debug for ClaudeCodeOptions {
1174 #[allow(deprecated)]
1175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1176 f.debug_struct("ClaudeCodeOptions")
1177 .field("system_prompt", &self.system_prompt)
1178 .field("append_system_prompt", &self.append_system_prompt)
1179 .field("allowed_tools", &self.allowed_tools)
1180 .field("disallowed_tools", &self.disallowed_tools)
1181 .field("permission_mode", &self.permission_mode)
1182 .field("mcp_servers", &self.mcp_servers)
1183 .field("mcp_tools", &self.mcp_tools)
1184 .field("max_turns", &self.max_turns)
1185 .field("max_thinking_tokens", &self.max_thinking_tokens)
1186 .field("max_output_tokens", &self.max_output_tokens)
1187 .field("model", &self.model)
1188 .field("cwd", &self.cwd)
1189 .field("continue_conversation", &self.continue_conversation)
1190 .field("resume", &self.resume)
1191 .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
1192 .field("settings", &self.settings)
1193 .field("add_dirs", &self.add_dirs)
1194 .field("extra_args", &self.extra_args)
1195 .field("env", &self.env)
1196 .field("debug_stderr", &self.debug_stderr.is_some())
1197 .field("include_partial_messages", &self.include_partial_messages)
1198 .field("can_use_tool", &self.can_use_tool.is_some())
1199 .field("hooks", &self.hooks.is_some())
1200 .field("control_protocol_format", &self.control_protocol_format)
1201 .finish()
1202 }
1203}
1204
1205impl ClaudeCodeOptions {
1206 pub fn builder() -> ClaudeCodeOptionsBuilder {
1208 ClaudeCodeOptionsBuilder::default()
1209 }
1210}
1211
1212#[derive(Debug, Default)]
1214pub struct ClaudeCodeOptionsBuilder {
1215 options: ClaudeCodeOptions,
1216}
1217
1218impl ClaudeCodeOptionsBuilder {
1219 #[allow(deprecated)]
1221 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
1222 self.options.system_prompt = Some(prompt.into());
1223 self
1224 }
1225
1226 #[allow(deprecated)]
1228 pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1229 self.options.append_system_prompt = Some(prompt.into());
1230 self
1231 }
1232
1233 pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1241 self.options.allowed_tools = tools;
1242 self
1243 }
1244
1245 pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1249 self.options.allowed_tools.push(tool.into());
1250 self
1251 }
1252
1253 pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1262 self.options.disallowed_tools = tools;
1263 self
1264 }
1265
1266 pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1270 self.options.disallowed_tools.push(tool.into());
1271 self
1272 }
1273
1274 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1276 self.options.permission_mode = mode;
1277 self
1278 }
1279
1280 pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
1282 self.options.mcp_servers.insert(name.into(), config);
1283 self
1284 }
1285
1286 pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1288 self.options.mcp_servers = servers;
1289 self
1290 }
1291
1292 pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1294 self.options.mcp_tools = tools;
1295 self
1296 }
1297
1298 pub fn max_turns(mut self, turns: i32) -> Self {
1300 self.options.max_turns = Some(turns);
1301 self
1302 }
1303
1304 pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1306 self.options.max_thinking_tokens = Some(tokens);
1307 self
1308 }
1309
1310 pub fn max_output_tokens(mut self, tokens: u32) -> Self {
1312 self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
1313 self
1314 }
1315
1316 pub fn model(mut self, model: impl Into<String>) -> Self {
1318 self.options.model = Some(model.into());
1319 self
1320 }
1321
1322 pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1324 self.options.cwd = Some(path.into());
1325 self
1326 }
1327
1328 pub fn continue_conversation(mut self, enable: bool) -> Self {
1330 self.options.continue_conversation = enable;
1331 self
1332 }
1333
1334 pub fn resume(mut self, id: impl Into<String>) -> Self {
1336 self.options.resume = Some(id.into());
1337 self
1338 }
1339
1340 pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
1342 self.options.permission_prompt_tool_name = Some(name.into());
1343 self
1344 }
1345
1346 pub fn settings(mut self, settings: impl Into<String>) -> Self {
1348 self.options.settings = Some(settings.into());
1349 self
1350 }
1351
1352 pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1354 self.options.add_dirs = dirs;
1355 self
1356 }
1357
1358 pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1360 self.options.add_dirs.push(dir.into());
1361 self
1362 }
1363
1364 pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1366 self.options.extra_args = args;
1367 self
1368 }
1369
1370 pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
1372 self.options.extra_args.insert(key.into(), value);
1373 self
1374 }
1375
1376 pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1378 self.options.control_protocol_format = format;
1379 self
1380 }
1381
1382 pub fn include_partial_messages(mut self, include: bool) -> Self {
1384 self.options.include_partial_messages = include;
1385 self
1386 }
1387
1388 pub fn fork_session(mut self, fork: bool) -> Self {
1390 self.options.fork_session = fork;
1391 self
1392 }
1393
1394 pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1396 self.options.setting_sources = Some(sources);
1397 self
1398 }
1399
1400 pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1402 self.options.agents = Some(agents);
1403 self
1404 }
1405
1406 pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1424 self.options.cli_channel_buffer_size = Some(size);
1425 self
1426 }
1427
1428 pub fn tools(mut self, config: ToolsConfig) -> Self {
1445 self.options.tools = Some(config);
1446 self
1447 }
1448
1449 pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1453 self.options.betas = betas;
1454 self
1455 }
1456
1457 pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1459 self.options.betas.push(beta);
1460 self
1461 }
1462
1463 pub fn max_budget_usd(mut self, budget: f64) -> Self {
1467 self.options.max_budget_usd = Some(budget);
1468 self
1469 }
1470
1471 pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1475 self.options.fallback_model = Some(model.into());
1476 self
1477 }
1478
1479 pub fn output_format(mut self, format: serde_json::Value) -> Self {
1500 self.options.output_format = Some(format);
1501 self
1502 }
1503
1504 pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1509 self.options.enable_file_checkpointing = enable;
1510 self
1511 }
1512
1513 pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1517 self.options.sandbox = Some(settings);
1518 self
1519 }
1520
1521 pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1523 self.options.plugins = plugins;
1524 self
1525 }
1526
1527 pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1529 self.options.plugins.push(plugin);
1530 self
1531 }
1532
1533 pub fn user(mut self, user: impl Into<String>) -> Self {
1535 self.options.user = Some(user.into());
1536 self
1537 }
1538
1539 pub fn stderr_callback(mut self, callback: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
1543 self.options.stderr_callback = Some(callback);
1544 self
1545 }
1546
1547 pub fn auto_download_cli(mut self, enable: bool) -> Self {
1561 self.options.auto_download_cli = enable;
1562 self
1563 }
1564
1565 pub fn build(self) -> ClaudeCodeOptions {
1567 self.options
1568 }
1569}
1570
1571#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1573#[serde(tag = "type", rename_all = "lowercase")]
1574pub enum Message {
1575 User {
1577 message: UserMessage,
1579 },
1580 Assistant {
1582 message: AssistantMessage,
1584 },
1585 System {
1587 subtype: String,
1589 data: serde_json::Value,
1591 },
1592 Result {
1594 subtype: String,
1596 duration_ms: i64,
1598 duration_api_ms: i64,
1600 is_error: bool,
1602 num_turns: i32,
1604 session_id: String,
1606 #[serde(skip_serializing_if = "Option::is_none")]
1608 total_cost_usd: Option<f64>,
1609 #[serde(skip_serializing_if = "Option::is_none")]
1611 usage: Option<serde_json::Value>,
1612 #[serde(skip_serializing_if = "Option::is_none")]
1614 result: Option<String>,
1615 #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
1618 structured_output: Option<serde_json::Value>,
1619 },
1620}
1621
1622#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1624pub struct UserMessage {
1625 pub content: String,
1627}
1628
1629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1631pub struct AssistantMessage {
1632 pub content: Vec<ContentBlock>,
1634}
1635
1636pub use Message::Result as ResultMessage;
1638pub use Message::System as SystemMessage;
1640
1641#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1643#[serde(untagged)]
1644pub enum ContentBlock {
1645 Text(TextContent),
1647 Thinking(ThinkingContent),
1649 ToolUse(ToolUseContent),
1651 ToolResult(ToolResultContent),
1653}
1654
1655#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1657pub struct TextContent {
1658 pub text: String,
1660}
1661
1662#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1664pub struct ThinkingContent {
1665 pub thinking: String,
1667 pub signature: String,
1669}
1670
1671#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1673pub struct ToolUseContent {
1674 pub id: String,
1676 pub name: String,
1678 pub input: serde_json::Value,
1680}
1681
1682#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1684pub struct ToolResultContent {
1685 pub tool_use_id: String,
1687 #[serde(skip_serializing_if = "Option::is_none")]
1689 pub content: Option<ContentValue>,
1690 #[serde(skip_serializing_if = "Option::is_none")]
1692 pub is_error: Option<bool>,
1693}
1694
1695#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1697#[serde(untagged)]
1698pub enum ContentValue {
1699 Text(String),
1701 Structured(Vec<serde_json::Value>),
1703}
1704
1705#[derive(Debug, Clone, Serialize, Deserialize)]
1707pub struct UserContent {
1708 pub role: String,
1710 pub content: String,
1712}
1713
1714#[derive(Debug, Clone, Serialize, Deserialize)]
1716pub struct AssistantContent {
1717 pub role: String,
1719 pub content: Vec<ContentBlock>,
1721}
1722
1723#[derive(Debug, Clone, Serialize, Deserialize)]
1725pub struct SDKControlInterruptRequest {
1726 pub subtype: String, }
1729
1730#[derive(Debug, Clone, Serialize, Deserialize)]
1732pub struct SDKControlPermissionRequest {
1733 pub subtype: String, #[serde(alias = "toolName")]
1737 pub tool_name: String,
1738 pub input: serde_json::Value,
1740 #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
1742 pub permission_suggestions: Option<Vec<PermissionUpdate>>,
1743 #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
1745 pub blocked_path: Option<String>,
1746}
1747
1748#[derive(Debug, Clone, Serialize, Deserialize)]
1750pub struct SDKControlInitializeRequest {
1751 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
1755 pub hooks: Option<HashMap<String, serde_json::Value>>,
1756}
1757
1758#[derive(Debug, Clone, Serialize, Deserialize)]
1760#[serde(rename_all = "camelCase")]
1761pub struct SDKControlSetPermissionModeRequest {
1762 pub subtype: String, pub mode: String,
1766}
1767
1768#[derive(Debug, Clone, Serialize, Deserialize)]
1770#[serde(rename_all = "camelCase")]
1771pub struct SDKControlSetModelRequest {
1772 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
1776 pub model: Option<String>,
1777}
1778
1779#[derive(Debug, Clone, Serialize, Deserialize)]
1781pub struct SDKHookCallbackRequest {
1782 pub subtype: String, #[serde(alias = "callbackId")]
1786 pub callback_id: String,
1787 pub input: serde_json::Value,
1789 #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
1791 pub tool_use_id: Option<String>,
1792}
1793
1794#[derive(Debug, Clone, Serialize, Deserialize)]
1796pub struct SDKControlMcpMessageRequest {
1797 pub subtype: String, #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
1801 pub mcp_server_name: String,
1802 pub message: serde_json::Value,
1804}
1805
1806#[derive(Debug, Clone, Serialize, Deserialize)]
1811pub struct SDKControlRewindFilesRequest {
1812 pub subtype: String,
1814 #[serde(alias = "userMessageId")]
1816 pub user_message_id: String,
1817}
1818
1819impl SDKControlRewindFilesRequest {
1820 pub fn new(user_message_id: impl Into<String>) -> Self {
1822 Self {
1823 subtype: "rewind_files".to_string(),
1824 user_message_id: user_message_id.into(),
1825 }
1826 }
1827}
1828
1829#[derive(Debug, Clone, Serialize, Deserialize)]
1831#[serde(tag = "type", rename_all = "snake_case")]
1832pub enum SDKControlRequest {
1833 #[serde(rename = "interrupt")]
1835 Interrupt(SDKControlInterruptRequest),
1836 #[serde(rename = "can_use_tool")]
1838 CanUseTool(SDKControlPermissionRequest),
1839 #[serde(rename = "initialize")]
1841 Initialize(SDKControlInitializeRequest),
1842 #[serde(rename = "set_permission_mode")]
1844 SetPermissionMode(SDKControlSetPermissionModeRequest),
1845 #[serde(rename = "set_model")]
1847 SetModel(SDKControlSetModelRequest),
1848 #[serde(rename = "hook_callback")]
1850 HookCallback(SDKHookCallbackRequest),
1851 #[serde(rename = "mcp_message")]
1853 McpMessage(SDKControlMcpMessageRequest),
1854 #[serde(rename = "rewind_files")]
1856 RewindFiles(SDKControlRewindFilesRequest),
1857}
1858
1859#[derive(Debug, Clone, Serialize, Deserialize)]
1861#[serde(tag = "type", rename_all = "lowercase")]
1862pub enum ControlRequest {
1863 Interrupt {
1865 request_id: String,
1867 },
1868}
1869
1870#[derive(Debug, Clone, Serialize, Deserialize)]
1872#[serde(tag = "type", rename_all = "lowercase")]
1873pub enum ControlResponse {
1874 InterruptAck {
1876 request_id: String,
1878 success: bool,
1880 },
1881}
1882
1883#[cfg(test)]
1884mod tests {
1885 use super::*;
1886
1887 #[test]
1888 fn test_permission_mode_serialization() {
1889 let mode = PermissionMode::AcceptEdits;
1890 let json = serde_json::to_string(&mode).unwrap();
1891 assert_eq!(json, r#""acceptEdits""#);
1892
1893 let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
1894 assert_eq!(deserialized, mode);
1895
1896 let plan_mode = PermissionMode::Plan;
1898 let plan_json = serde_json::to_string(&plan_mode).unwrap();
1899 assert_eq!(plan_json, r#""plan""#);
1900
1901 let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
1902 assert_eq!(plan_deserialized, plan_mode);
1903 }
1904
1905 #[test]
1906 fn test_message_serialization() {
1907 let msg = Message::User {
1908 message: UserMessage {
1909 content: "Hello".to_string(),
1910 },
1911 };
1912
1913 let json = serde_json::to_string(&msg).unwrap();
1914 assert!(json.contains(r#""type":"user""#));
1915 assert!(json.contains(r#""content":"Hello""#));
1916
1917 let deserialized: Message = serde_json::from_str(&json).unwrap();
1918 assert_eq!(deserialized, msg);
1919 }
1920
1921 #[test]
1922 #[allow(deprecated)]
1923 fn test_options_builder() {
1924 let options = ClaudeCodeOptions::builder()
1925 .system_prompt("Test prompt")
1926 .model("claude-3-opus")
1927 .permission_mode(PermissionMode::AcceptEdits)
1928 .allow_tool("read")
1929 .allow_tool("write")
1930 .max_turns(10)
1931 .build();
1932
1933 assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
1934 assert_eq!(options.model, Some("claude-3-opus".to_string()));
1935 assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
1936 assert_eq!(options.allowed_tools, vec!["read", "write"]);
1937 assert_eq!(options.max_turns, Some(10));
1938 }
1939
1940 #[test]
1941 fn test_extra_args() {
1942 let mut extra_args = HashMap::new();
1943 extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
1944 extra_args.insert("boolean-flag".to_string(), None);
1945
1946 let options = ClaudeCodeOptions::builder()
1947 .extra_args(extra_args.clone())
1948 .add_extra_arg("another-flag", Some("another-value".to_string()))
1949 .build();
1950
1951 assert_eq!(options.extra_args.len(), 3);
1952 assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
1953 assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
1954 assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
1955 }
1956
1957 #[test]
1958 fn test_thinking_content_serialization() {
1959 let thinking = ThinkingContent {
1960 thinking: "Let me think about this...".to_string(),
1961 signature: "sig123".to_string(),
1962 };
1963
1964 let json = serde_json::to_string(&thinking).unwrap();
1965 assert!(json.contains(r#""thinking":"Let me think about this...""#));
1966 assert!(json.contains(r#""signature":"sig123""#));
1967
1968 let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
1969 assert_eq!(deserialized.thinking, thinking.thinking);
1970 assert_eq!(deserialized.signature, thinking.signature);
1971 }
1972
1973 #[test]
1976 fn test_tools_config_list_serialization() {
1977 let tools = ToolsConfig::List(vec!["Read".to_string(), "Write".to_string(), "Bash".to_string()]);
1978 let json = serde_json::to_string(&tools).unwrap();
1979
1980 assert!(json.contains("Read"));
1982 assert!(json.contains("Write"));
1983 assert!(json.contains("Bash"));
1984
1985 let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
1986 match deserialized {
1987 ToolsConfig::List(list) => {
1988 assert_eq!(list.len(), 3);
1989 assert!(list.contains(&"Read".to_string()));
1990 }
1991 _ => panic!("Expected List variant"),
1992 }
1993 }
1994
1995 #[test]
1996 fn test_tools_config_preset_serialization() {
1997 let preset = ToolsConfig::claude_code_preset();
1999 let json = serde_json::to_string(&preset).unwrap();
2000 assert!(json.contains("preset"));
2001 assert!(json.contains("claude_code"));
2002
2003 let custom_preset = ToolsConfig::Preset(ToolsPreset {
2005 preset_type: "preset".to_string(),
2006 preset: "custom".to_string(),
2007 });
2008 let json = serde_json::to_string(&custom_preset).unwrap();
2009 assert!(json.contains("custom"));
2010
2011 let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2013 match deserialized {
2014 ToolsConfig::Preset(p) => assert_eq!(p.preset, "custom"),
2015 _ => panic!("Expected Preset variant"),
2016 }
2017 }
2018
2019 #[test]
2020 fn test_tools_config_helper_methods() {
2021 let tools = ToolsConfig::list(vec!["Read".to_string(), "Write".to_string()]);
2023 match tools {
2024 ToolsConfig::List(list) => assert_eq!(list.len(), 2),
2025 _ => panic!("Expected List variant"),
2026 }
2027
2028 let empty = ToolsConfig::none();
2030 match empty {
2031 ToolsConfig::List(list) => assert!(list.is_empty()),
2032 _ => panic!("Expected empty List variant"),
2033 }
2034
2035 let preset = ToolsConfig::claude_code_preset();
2037 match preset {
2038 ToolsConfig::Preset(p) => {
2039 assert_eq!(p.preset_type, "preset");
2040 assert_eq!(p.preset, "claude_code");
2041 }
2042 _ => panic!("Expected Preset variant"),
2043 }
2044 }
2045
2046 #[test]
2047 fn test_sdk_beta_serialization() {
2048 let beta = SdkBeta::Context1M;
2049 let json = serde_json::to_string(&beta).unwrap();
2050 assert_eq!(json, r#""context-1m-2025-08-07""#);
2052
2053 let display = format!("{}", beta);
2055 assert_eq!(display, "context-1m-2025-08-07");
2056
2057 let deserialized: SdkBeta = serde_json::from_str(r#""context-1m-2025-08-07""#).unwrap();
2059 assert!(matches!(deserialized, SdkBeta::Context1M));
2060 }
2061
2062 #[test]
2063 fn test_sandbox_settings_serialization() {
2064 let sandbox = SandboxSettings {
2065 enabled: Some(true),
2066 auto_allow_bash_if_sandboxed: Some(true),
2067 excluded_commands: Some(vec!["git".to_string(), "docker".to_string()]),
2068 allow_unsandboxed_commands: Some(false),
2069 network: Some(SandboxNetworkConfig {
2070 allow_unix_sockets: Some(vec!["/tmp/ssh-agent.sock".to_string()]),
2071 allow_all_unix_sockets: Some(false),
2072 allow_local_binding: Some(true),
2073 http_proxy_port: Some(8080),
2074 socks_proxy_port: Some(1080),
2075 }),
2076 ignore_violations: Some(SandboxIgnoreViolations {
2077 file: Some(vec!["/tmp".to_string(), "/var/log".to_string()]),
2078 network: Some(vec!["localhost".to_string()]),
2079 }),
2080 enable_weaker_nested_sandbox: Some(false),
2081 };
2082
2083 let json = serde_json::to_string(&sandbox).unwrap();
2084 assert!(json.contains("enabled"));
2085 assert!(json.contains("autoAllowBashIfSandboxed")); assert!(json.contains("excludedCommands"));
2087 assert!(json.contains("httpProxyPort"));
2088 assert!(json.contains("8080"));
2089
2090 let deserialized: SandboxSettings = serde_json::from_str(&json).unwrap();
2091 assert!(deserialized.enabled.unwrap());
2092 assert!(deserialized.network.is_some());
2093 assert_eq!(deserialized.network.as_ref().unwrap().http_proxy_port, Some(8080));
2094 }
2095
2096 #[test]
2097 fn test_sandbox_network_config() {
2098 let config = SandboxNetworkConfig {
2099 allow_unix_sockets: Some(vec!["/run/user/1000/keyring/ssh".to_string()]),
2100 allow_all_unix_sockets: Some(false),
2101 allow_local_binding: Some(true),
2102 http_proxy_port: Some(3128),
2103 socks_proxy_port: Some(1080),
2104 };
2105
2106 let json = serde_json::to_string(&config).unwrap();
2107 let deserialized: SandboxNetworkConfig = serde_json::from_str(&json).unwrap();
2108
2109 assert_eq!(deserialized.http_proxy_port, Some(3128));
2110 assert_eq!(deserialized.socks_proxy_port, Some(1080));
2111 assert_eq!(deserialized.allow_local_binding, Some(true));
2112 }
2113
2114 #[test]
2115 fn test_sandbox_ignore_violations() {
2116 let violations = SandboxIgnoreViolations {
2117 file: Some(vec!["/tmp".to_string(), "/var/cache".to_string()]),
2118 network: Some(vec!["127.0.0.1".to_string(), "localhost".to_string()]),
2119 };
2120
2121 let json = serde_json::to_string(&violations).unwrap();
2122 assert!(json.contains("file"));
2123 assert!(json.contains("/tmp"));
2124
2125 let deserialized: SandboxIgnoreViolations = serde_json::from_str(&json).unwrap();
2126 assert_eq!(deserialized.file.as_ref().unwrap().len(), 2);
2127 assert_eq!(deserialized.network.as_ref().unwrap().len(), 2);
2128 }
2129
2130 #[test]
2131 fn test_sandbox_settings_default() {
2132 let sandbox = SandboxSettings::default();
2133 assert!(sandbox.enabled.is_none());
2134 assert!(sandbox.network.is_none());
2135 assert!(sandbox.ignore_violations.is_none());
2136 }
2137
2138 #[test]
2139 fn test_sdk_plugin_config_serialization() {
2140 let plugin = SdkPluginConfig::Local {
2141 path: "/path/to/plugin".to_string()
2142 };
2143
2144 let json = serde_json::to_string(&plugin).unwrap();
2145 assert!(json.contains("local")); assert!(json.contains("/path/to/plugin"));
2147
2148 let deserialized: SdkPluginConfig = serde_json::from_str(&json).unwrap();
2149 match deserialized {
2150 SdkPluginConfig::Local { path } => {
2151 assert_eq!(path, "/path/to/plugin");
2152 }
2153 }
2154 }
2155
2156 #[test]
2157 fn test_sdk_control_rewind_files_request() {
2158 let request = SDKControlRewindFilesRequest {
2159 subtype: "rewind_files".to_string(),
2160 user_message_id: "msg_12345".to_string(),
2161 };
2162
2163 let json = serde_json::to_string(&request).unwrap();
2164 assert!(json.contains("user_message_id"));
2165 assert!(json.contains("msg_12345"));
2166 assert!(json.contains("subtype"));
2167 assert!(json.contains("rewind_files"));
2168
2169 let deserialized: SDKControlRewindFilesRequest = serde_json::from_str(&json).unwrap();
2170 assert_eq!(deserialized.user_message_id, "msg_12345");
2171 assert_eq!(deserialized.subtype, "rewind_files");
2172 }
2173
2174 #[test]
2175 fn test_options_builder_with_new_fields() {
2176 let options = ClaudeCodeOptions::builder()
2177 .tools(ToolsConfig::claude_code_preset())
2178 .add_beta(SdkBeta::Context1M)
2179 .max_budget_usd(10.0)
2180 .fallback_model("claude-3-haiku")
2181 .output_format(serde_json::json!({"type": "object"}))
2182 .enable_file_checkpointing(true)
2183 .sandbox(SandboxSettings::default())
2184 .add_plugin(SdkPluginConfig::Local { path: "/plugin".to_string() })
2185 .auto_download_cli(true)
2186 .build();
2187
2188 assert!(options.tools.is_some());
2190 match options.tools.as_ref().unwrap() {
2191 ToolsConfig::Preset(preset) => assert_eq!(preset.preset, "claude_code"),
2192 _ => panic!("Expected Preset variant"),
2193 }
2194
2195 assert_eq!(options.betas.len(), 1);
2197 assert!(matches!(options.betas[0], SdkBeta::Context1M));
2198
2199 assert_eq!(options.max_budget_usd, Some(10.0));
2201
2202 assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2204
2205 assert!(options.output_format.is_some());
2207
2208 assert!(options.enable_file_checkpointing);
2210
2211 assert!(options.sandbox.is_some());
2213
2214 assert_eq!(options.plugins.len(), 1);
2216
2217 assert!(options.auto_download_cli);
2219 }
2220
2221 #[test]
2222 fn test_options_builder_with_tools_list() {
2223 let options = ClaudeCodeOptions::builder()
2224 .tools(ToolsConfig::List(vec!["Read".to_string(), "Bash".to_string()]))
2225 .build();
2226
2227 match options.tools.as_ref().unwrap() {
2228 ToolsConfig::List(list) => {
2229 assert_eq!(list.len(), 2);
2230 assert!(list.contains(&"Read".to_string()));
2231 assert!(list.contains(&"Bash".to_string()));
2232 }
2233 _ => panic!("Expected List variant"),
2234 }
2235 }
2236
2237 #[test]
2238 fn test_options_builder_multiple_betas() {
2239 let options = ClaudeCodeOptions::builder()
2240 .add_beta(SdkBeta::Context1M)
2241 .betas(vec![SdkBeta::Context1M])
2242 .build();
2243
2244 assert_eq!(options.betas.len(), 1);
2246 }
2247
2248 #[test]
2249 fn test_options_builder_add_beta_accumulates() {
2250 let options = ClaudeCodeOptions::builder()
2251 .add_beta(SdkBeta::Context1M)
2252 .add_beta(SdkBeta::Context1M)
2253 .build();
2254
2255 assert_eq!(options.betas.len(), 2);
2257 }
2258
2259 #[test]
2260 fn test_options_builder_multiple_plugins() {
2261 let options = ClaudeCodeOptions::builder()
2262 .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2263 .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2264 .plugins(vec![SdkPluginConfig::Local { path: "/plugin3".to_string() }])
2265 .build();
2266
2267 assert_eq!(options.plugins.len(), 1);
2269 }
2270
2271 #[test]
2272 fn test_options_builder_add_plugin_accumulates() {
2273 let options = ClaudeCodeOptions::builder()
2274 .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2275 .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2276 .add_plugin(SdkPluginConfig::Local { path: "/plugin3".to_string() })
2277 .build();
2278
2279 assert_eq!(options.plugins.len(), 3);
2281 }
2282
2283 #[test]
2284 fn test_message_result_with_structured_output() {
2285 let json = r#"{
2287 "type": "result",
2288 "subtype": "success",
2289 "cost_usd": 0.05,
2290 "duration_ms": 1500,
2291 "duration_api_ms": 1200,
2292 "is_error": false,
2293 "num_turns": 3,
2294 "session_id": "session_123",
2295 "structured_output": {"answer": 42}
2296 }"#;
2297
2298 let msg: Message = serde_json::from_str(json).unwrap();
2299 match msg {
2300 Message::Result {
2301 structured_output,
2302 ..
2303 } => {
2304 assert!(structured_output.is_some());
2305 let output = structured_output.unwrap();
2306 assert_eq!(output["answer"], 42);
2307 }
2308 _ => panic!("Expected Result message"),
2309 }
2310 }
2311
2312 #[test]
2313 fn test_message_result_with_structured_output_camel_case() {
2314 let json = r#"{
2316 "type": "result",
2317 "subtype": "success",
2318 "cost_usd": 0.05,
2319 "duration_ms": 1500,
2320 "duration_api_ms": 1200,
2321 "is_error": false,
2322 "num_turns": 3,
2323 "session_id": "session_123",
2324 "structuredOutput": {"name": "test", "value": true}
2325 }"#;
2326
2327 let msg: Message = serde_json::from_str(json).unwrap();
2328 match msg {
2329 Message::Result {
2330 structured_output,
2331 ..
2332 } => {
2333 assert!(structured_output.is_some());
2334 let output = structured_output.unwrap();
2335 assert_eq!(output["name"], "test");
2336 assert_eq!(output["value"], true);
2337 }
2338 _ => panic!("Expected Result message"),
2339 }
2340 }
2341
2342 #[test]
2343 fn test_default_options_new_fields() {
2344 let options = ClaudeCodeOptions::default();
2345
2346 assert!(options.tools.is_none());
2348 assert!(options.betas.is_empty());
2349 assert!(options.max_budget_usd.is_none());
2350 assert!(options.fallback_model.is_none());
2351 assert!(options.output_format.is_none());
2352 assert!(!options.enable_file_checkpointing);
2353 assert!(options.sandbox.is_none());
2354 assert!(options.plugins.is_empty());
2355 assert!(options.user.is_none());
2356 assert!(!options.auto_download_cli);
2359 }
2360}