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#[non_exhaustive]
18#[serde(rename_all = "camelCase")]
19pub enum PermissionMode {
20 Default,
22 AcceptEdits,
24 Plan,
26 BypassPermissions,
28 DontAsk,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "lowercase")]
39pub enum Effort {
40 Low,
42 Medium,
44 High,
46 Max,
48}
49
50impl std::fmt::Display for Effort {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Effort::Low => write!(f, "low"),
54 Effort::Medium => write!(f, "medium"),
55 Effort::High => write!(f, "high"),
56 Effort::Max => write!(f, "max"),
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "snake_case")]
68pub enum RateLimitStatus {
69 Allowed,
71 AllowedWarning,
73 Rejected,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub enum RateLimitType {
80 #[serde(rename = "five_hour")]
82 FiveHour,
83 #[serde(rename = "seven_day")]
85 SevenDay,
86 #[serde(rename = "seven_day_opus")]
88 SevenDayOpus,
89 #[serde(rename = "seven_day_sonnet")]
91 SevenDaySonnet,
92 #[serde(rename = "overage")]
94 Overage,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
99pub struct RateLimitInfo {
100 pub status: RateLimitStatus,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub resets_at: Option<String>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub rate_limit_type: Option<RateLimitType>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub utilization: Option<f64>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub overage_status: Option<String>,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub overage_resets_at: Option<String>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub overage_disabled_reason: Option<String>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub raw: Option<serde_json::Value>,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "snake_case")]
132pub enum AssistantMessageError {
133 AuthenticationFailed,
135 BillingError,
137 RateLimit,
139 InvalidRequest,
141 ServerError,
143 Unknown,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub enum SdkBeta {
154 #[serde(rename = "context-1m-2025-08-07")]
156 Context1M,
157}
158
159impl std::fmt::Display for SdkBeta {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 match self {
162 SdkBeta::Context1M => write!(f, "context-1m-2025-08-07"),
163 }
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(untagged)]
177pub enum ToolsConfig {
178 List(Vec<String>),
181 Preset(ToolsPreset),
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ToolsPreset {
188 #[serde(rename = "type")]
190 pub preset_type: String,
191 pub preset: String,
193}
194
195impl ToolsConfig {
196 pub fn list(tools: Vec<String>) -> Self {
198 ToolsConfig::List(tools)
199 }
200
201 pub fn none() -> Self {
203 ToolsConfig::List(vec![])
204 }
205
206 pub fn claude_code_preset() -> Self {
208 ToolsConfig::Preset(ToolsPreset {
209 preset_type: "preset".to_string(),
210 preset: "claude_code".to_string(),
211 })
212 }
213}
214
215#[derive(Debug, Clone, Default, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct SandboxNetworkConfig {
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub allow_unix_sockets: Option<Vec<String>>,
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub allow_all_unix_sockets: Option<bool>,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub allow_local_binding: Option<bool>,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub http_proxy_port: Option<u16>,
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub socks_proxy_port: Option<u16>,
238}
239
240#[derive(Debug, Clone, Default, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct SandboxIgnoreViolations {
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub file: Option<Vec<String>>,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub network: Option<Vec<String>>,
250}
251
252#[derive(Debug, Clone, Default, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct SandboxSettings {
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub enabled: Option<bool>,
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub auto_allow_bash_if_sandboxed: Option<bool>,
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub excluded_commands: Option<Vec<String>>,
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub allow_unsandboxed_commands: Option<bool>,
271 #[serde(skip_serializing_if = "Option::is_none")]
273 pub network: Option<SandboxNetworkConfig>,
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub ignore_violations: Option<SandboxIgnoreViolations>,
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub enable_weaker_nested_sandbox: Option<bool>,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(tag = "type", rename_all = "lowercase")]
289pub enum SdkPluginConfig {
290 Local {
292 path: String,
294 },
295}
296
297impl Default for PermissionMode {
298 fn default() -> Self {
299 Self::Default
300 }
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
305pub enum ControlProtocolFormat {
306 Legacy,
308 Control,
310 Auto,
312}
313
314impl Default for ControlProtocolFormat {
315 fn default() -> Self {
316 Self::Legacy
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
327#[serde(rename_all = "kebab-case")]
328pub enum McpConnectionStatus {
329 Connected,
331 Failed,
333 NeedsAuth,
335 Pending,
337 Disabled,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct McpToolAnnotations {
344 #[serde(default, skip_serializing_if = "Option::is_none")]
346 pub read_only: Option<bool>,
347 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub destructive: Option<bool>,
350 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub open_world: Option<bool>,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct McpToolInfo {
358 pub name: String,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
362 pub description: Option<String>,
363 #[serde(default, skip_serializing_if = "Option::is_none")]
365 pub annotations: Option<McpToolAnnotations>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct McpServerInfo {
371 pub name: String,
373 pub version: String,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct McpServerStatus {
380 pub name: String,
382 pub status: McpConnectionStatus,
384 #[serde(default, skip_serializing_if = "Option::is_none")]
386 pub server_info: Option<McpServerInfo>,
387 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub error: Option<String>,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
392 pub tools: Option<Vec<McpToolInfo>>,
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
401#[serde(tag = "type", rename_all = "lowercase")]
402pub enum ThinkingConfig {
403 Adaptive,
405 Enabled {
407 budget_tokens: i32,
409 },
410 Disabled,
412}
413
414#[derive(Clone)]
416pub enum McpServerConfig {
417 Stdio {
419 command: String,
421 args: Option<Vec<String>>,
423 env: Option<HashMap<String, String>>,
425 },
426 Sse {
428 url: String,
430 headers: Option<HashMap<String, String>>,
432 },
433 Http {
435 url: String,
437 headers: Option<HashMap<String, String>>,
439 },
440 Sdk {
442 name: String,
444 instance: Arc<dyn std::any::Any + Send + Sync>,
446 },
447}
448
449impl std::fmt::Debug for McpServerConfig {
450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451 match self {
452 Self::Stdio { command, args, env } => f
453 .debug_struct("Stdio")
454 .field("command", command)
455 .field("args", args)
456 .field("env", env)
457 .finish(),
458 Self::Sse { url, headers } => f
459 .debug_struct("Sse")
460 .field("url", url)
461 .field("headers", headers)
462 .finish(),
463 Self::Http { url, headers } => f
464 .debug_struct("Http")
465 .field("url", url)
466 .field("headers", headers)
467 .finish(),
468 Self::Sdk { name, .. } => f
469 .debug_struct("Sdk")
470 .field("name", name)
471 .field("instance", &"<Arc<dyn Any>>")
472 .finish(),
473 }
474 }
475}
476
477impl Serialize for McpServerConfig {
478 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
479 where
480 S: serde::Serializer,
481 {
482 use serde::ser::SerializeMap;
483 let mut map = serializer.serialize_map(None)?;
484
485 match self {
486 Self::Stdio { command, args, env } => {
487 map.serialize_entry("type", "stdio")?;
488 map.serialize_entry("command", command)?;
489 if let Some(args) = args {
490 map.serialize_entry("args", args)?;
491 }
492 if let Some(env) = env {
493 map.serialize_entry("env", env)?;
494 }
495 }
496 Self::Sse { url, headers } => {
497 map.serialize_entry("type", "sse")?;
498 map.serialize_entry("url", url)?;
499 if let Some(headers) = headers {
500 map.serialize_entry("headers", headers)?;
501 }
502 }
503 Self::Http { url, headers } => {
504 map.serialize_entry("type", "http")?;
505 map.serialize_entry("url", url)?;
506 if let Some(headers) = headers {
507 map.serialize_entry("headers", headers)?;
508 }
509 }
510 Self::Sdk { name, .. } => {
511 map.serialize_entry("type", "sdk")?;
512 map.serialize_entry("name", name)?;
513 }
514 }
515
516 map.end()
517 }
518}
519
520impl<'de> Deserialize<'de> for McpServerConfig {
521 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
522 where
523 D: serde::Deserializer<'de>,
524 {
525 #[derive(Deserialize)]
526 #[serde(tag = "type", rename_all = "lowercase")]
527 enum McpServerConfigHelper {
528 Stdio {
529 command: String,
530 #[serde(skip_serializing_if = "Option::is_none")]
531 args: Option<Vec<String>>,
532 #[serde(skip_serializing_if = "Option::is_none")]
533 env: Option<HashMap<String, String>>,
534 },
535 Sse {
536 url: String,
537 #[serde(skip_serializing_if = "Option::is_none")]
538 headers: Option<HashMap<String, String>>,
539 },
540 Http {
541 url: String,
542 #[serde(skip_serializing_if = "Option::is_none")]
543 headers: Option<HashMap<String, String>>,
544 },
545 }
546
547 let helper = McpServerConfigHelper::deserialize(deserializer)?;
548 Ok(match helper {
549 McpServerConfigHelper::Stdio { command, args, env } => {
550 McpServerConfig::Stdio { command, args, env }
551 }
552 McpServerConfigHelper::Sse { url, headers } => {
553 McpServerConfig::Sse { url, headers }
554 }
555 McpServerConfigHelper::Http { url, headers } => {
556 McpServerConfig::Http { url, headers }
557 }
558 })
559 }
560}
561
562#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub enum PermissionUpdateDestination {
566 UserSettings,
568 ProjectSettings,
570 LocalSettings,
572 Session,
574}
575
576#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub enum PermissionBehavior {
580 Allow,
582 Deny,
584 Ask,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct PermissionRuleValue {
591 pub tool_name: String,
593 pub rule_content: Option<String>,
595}
596
597#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
599#[serde(rename_all = "camelCase")]
600pub enum PermissionUpdateType {
601 AddRules,
603 ReplaceRules,
605 RemoveRules,
607 SetMode,
609 AddDirectories,
611 RemoveDirectories,
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
617#[serde(rename_all = "camelCase")]
618pub struct PermissionUpdate {
619 #[serde(rename = "type")]
621 pub update_type: PermissionUpdateType,
622 #[serde(skip_serializing_if = "Option::is_none")]
624 pub rules: Option<Vec<PermissionRuleValue>>,
625 #[serde(skip_serializing_if = "Option::is_none")]
627 pub behavior: Option<PermissionBehavior>,
628 #[serde(skip_serializing_if = "Option::is_none")]
630 pub mode: Option<PermissionMode>,
631 #[serde(skip_serializing_if = "Option::is_none")]
633 pub directories: Option<Vec<String>>,
634 #[serde(skip_serializing_if = "Option::is_none")]
636 pub destination: Option<PermissionUpdateDestination>,
637}
638
639#[derive(Debug, Clone)]
641pub struct ToolPermissionContext {
642 pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
644 pub suggestions: Vec<PermissionUpdate>,
646}
647
648#[derive(Debug, Clone)]
650pub struct PermissionResultAllow {
651 pub updated_input: Option<serde_json::Value>,
653 pub updated_permissions: Option<Vec<PermissionUpdate>>,
655}
656
657#[derive(Debug, Clone)]
659pub struct PermissionResultDeny {
660 pub message: String,
662 pub interrupt: bool,
664}
665
666#[derive(Debug, Clone)]
668pub enum PermissionResult {
669 Allow(PermissionResultAllow),
671 Deny(PermissionResultDeny),
673}
674
675#[async_trait]
677pub trait CanUseTool: Send + Sync {
678 async fn can_use_tool(
680 &self,
681 tool_name: &str,
682 input: &serde_json::Value,
683 context: &ToolPermissionContext,
684 ) -> PermissionResult;
685}
686
687#[derive(Debug, Clone)]
689pub struct HookContext {
690 pub signal: Option<Arc<dyn std::any::Any + Send + Sync>>,
692}
693
694#[derive(Debug, Clone, Serialize, Deserialize)]
700pub struct BaseHookInput {
701 pub session_id: String,
703 pub transcript_path: String,
705 pub cwd: String,
707 #[serde(skip_serializing_if = "Option::is_none")]
709 pub permission_mode: Option<String>,
710}
711
712#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct PreToolUseHookInput {
715 pub session_id: String,
717 pub transcript_path: String,
719 pub cwd: String,
721 #[serde(skip_serializing_if = "Option::is_none")]
723 pub permission_mode: Option<String>,
724 pub tool_name: String,
726 pub tool_input: serde_json::Value,
728 pub tool_use_id: String,
730 #[serde(default, skip_serializing_if = "Option::is_none")]
732 pub agent_id: Option<String>,
733 #[serde(default, skip_serializing_if = "Option::is_none")]
735 pub agent_type: Option<String>,
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct PostToolUseHookInput {
741 pub session_id: String,
743 pub transcript_path: String,
745 pub cwd: String,
747 #[serde(skip_serializing_if = "Option::is_none")]
749 pub permission_mode: Option<String>,
750 pub tool_name: String,
752 pub tool_input: serde_json::Value,
754 pub tool_response: serde_json::Value,
756 pub tool_use_id: String,
758 #[serde(default, skip_serializing_if = "Option::is_none")]
760 pub agent_id: Option<String>,
761 #[serde(default, skip_serializing_if = "Option::is_none")]
763 pub agent_type: Option<String>,
764}
765
766#[derive(Debug, Clone, Serialize, Deserialize)]
768pub struct UserPromptSubmitHookInput {
769 pub session_id: String,
771 pub transcript_path: String,
773 pub cwd: String,
775 #[serde(skip_serializing_if = "Option::is_none")]
777 pub permission_mode: Option<String>,
778 pub prompt: String,
780}
781
782#[derive(Debug, Clone, Serialize, Deserialize)]
784pub struct StopHookInput {
785 pub session_id: String,
787 pub transcript_path: String,
789 pub cwd: String,
791 #[serde(skip_serializing_if = "Option::is_none")]
793 pub permission_mode: Option<String>,
794 pub stop_hook_active: bool,
796}
797
798#[derive(Debug, Clone, Serialize, Deserialize)]
800pub struct SubagentStopHookInput {
801 pub session_id: String,
803 pub transcript_path: String,
805 pub cwd: String,
807 #[serde(skip_serializing_if = "Option::is_none")]
809 pub permission_mode: Option<String>,
810 pub stop_hook_active: bool,
812 pub agent_id: String,
814 pub agent_transcript_path: String,
816 pub agent_type: String,
818}
819
820#[derive(Debug, Clone, Serialize, Deserialize)]
822pub struct PreCompactHookInput {
823 pub session_id: String,
825 pub transcript_path: String,
827 pub cwd: String,
829 #[serde(skip_serializing_if = "Option::is_none")]
831 pub permission_mode: Option<String>,
832 pub trigger: String,
834 #[serde(skip_serializing_if = "Option::is_none")]
836 pub custom_instructions: Option<String>,
837}
838
839#[derive(Debug, Clone, Serialize, Deserialize)]
841pub struct PostToolUseFailureHookInput {
842 pub session_id: String,
844 pub transcript_path: String,
846 pub cwd: String,
848 #[serde(skip_serializing_if = "Option::is_none")]
850 pub permission_mode: Option<String>,
851 pub tool_name: String,
853 pub tool_input: serde_json::Value,
855 pub tool_use_id: String,
857 pub error: String,
859 #[serde(skip_serializing_if = "Option::is_none")]
861 pub is_interrupt: Option<bool>,
862 #[serde(default, skip_serializing_if = "Option::is_none")]
864 pub agent_id: Option<String>,
865 #[serde(default, skip_serializing_if = "Option::is_none")]
867 pub agent_type: Option<String>,
868}
869
870#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct NotificationHookInput {
873 pub session_id: String,
875 pub transcript_path: String,
877 pub cwd: String,
879 #[serde(skip_serializing_if = "Option::is_none")]
881 pub permission_mode: Option<String>,
882 pub message: String,
884 #[serde(skip_serializing_if = "Option::is_none")]
886 pub title: Option<String>,
887 pub notification_type: String,
889}
890
891#[derive(Debug, Clone, Serialize, Deserialize)]
893pub struct SubagentStartHookInput {
894 pub session_id: String,
896 pub transcript_path: String,
898 pub cwd: String,
900 #[serde(skip_serializing_if = "Option::is_none")]
902 pub permission_mode: Option<String>,
903 pub agent_id: String,
905 pub agent_type: String,
907}
908
909#[derive(Debug, Clone, Serialize, Deserialize)]
911pub struct PermissionRequestHookInput {
912 pub session_id: String,
914 pub transcript_path: String,
916 pub cwd: String,
918 #[serde(skip_serializing_if = "Option::is_none")]
920 pub permission_mode: Option<String>,
921 pub tool_name: String,
923 pub tool_input: serde_json::Value,
925 #[serde(skip_serializing_if = "Option::is_none")]
927 pub permission_suggestions: Option<Vec<serde_json::Value>>,
928 #[serde(default, skip_serializing_if = "Option::is_none")]
930 pub agent_id: Option<String>,
931 #[serde(default, skip_serializing_if = "Option::is_none")]
933 pub agent_type: Option<String>,
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize)]
938#[non_exhaustive]
939#[serde(tag = "hook_event_name")]
940pub enum HookInput {
941 #[serde(rename = "PreToolUse")]
943 PreToolUse(PreToolUseHookInput),
944 #[serde(rename = "PostToolUse")]
946 PostToolUse(PostToolUseHookInput),
947 #[serde(rename = "PostToolUseFailure")]
949 PostToolUseFailure(PostToolUseFailureHookInput),
950 #[serde(rename = "UserPromptSubmit")]
952 UserPromptSubmit(UserPromptSubmitHookInput),
953 #[serde(rename = "Stop")]
955 Stop(StopHookInput),
956 #[serde(rename = "SubagentStop")]
958 SubagentStop(SubagentStopHookInput),
959 #[serde(rename = "PreCompact")]
961 PreCompact(PreCompactHookInput),
962 #[serde(rename = "Notification")]
964 Notification(NotificationHookInput),
965 #[serde(rename = "SubagentStart")]
967 SubagentStart(SubagentStartHookInput),
968 #[serde(rename = "PermissionRequest")]
970 PermissionRequest(PermissionRequestHookInput),
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize)]
982pub struct AsyncHookJSONOutput {
983 #[serde(rename = "async")]
985 pub async_: bool,
986 #[serde(skip_serializing_if = "Option::is_none")]
988 #[serde(rename = "asyncTimeout")]
989 pub async_timeout: Option<u32>,
990}
991
992#[derive(Debug, Clone, Default, Serialize, Deserialize)]
997pub struct SyncHookJSONOutput {
998 #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
1001 pub continue_: Option<bool>,
1002 #[serde(rename = "suppressOutput", skip_serializing_if = "Option::is_none")]
1004 pub suppress_output: Option<bool>,
1005 #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
1007 pub stop_reason: Option<String>,
1008
1009 #[serde(skip_serializing_if = "Option::is_none")]
1012 pub decision: Option<String>, #[serde(rename = "systemMessage", skip_serializing_if = "Option::is_none")]
1015 pub system_message: Option<String>,
1016 #[serde(skip_serializing_if = "Option::is_none")]
1018 pub reason: Option<String>,
1019
1020 #[serde(rename = "hookSpecificOutput", skip_serializing_if = "Option::is_none")]
1023 pub hook_specific_output: Option<HookSpecificOutput>,
1024}
1025
1026#[derive(Debug, Clone, Serialize, Deserialize)]
1028#[serde(untagged)]
1029pub enum HookJSONOutput {
1030 Async(AsyncHookJSONOutput),
1032 Sync(SyncHookJSONOutput),
1034}
1035
1036#[derive(Debug, Clone, Serialize, Deserialize)]
1038pub struct PreToolUseHookSpecificOutput {
1039 #[serde(rename = "permissionDecision", skip_serializing_if = "Option::is_none")]
1041 pub permission_decision: Option<String>,
1042 #[serde(rename = "permissionDecisionReason", skip_serializing_if = "Option::is_none")]
1044 pub permission_decision_reason: Option<String>,
1045 #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
1047 pub updated_input: Option<serde_json::Value>,
1048 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1050 pub additional_context: Option<String>,
1051}
1052
1053#[derive(Debug, Clone, Serialize, Deserialize)]
1055pub struct PostToolUseHookSpecificOutput {
1056 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1058 pub additional_context: Option<String>,
1059 #[serde(rename = "updatedMCPToolOutput", skip_serializing_if = "Option::is_none")]
1061 pub updated_mcp_tool_output: Option<serde_json::Value>,
1062}
1063
1064#[derive(Debug, Clone, Serialize, Deserialize)]
1066pub struct UserPromptSubmitHookSpecificOutput {
1067 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1069 pub additional_context: Option<String>,
1070}
1071
1072#[derive(Debug, Clone, Serialize, Deserialize)]
1074pub struct SessionStartHookSpecificOutput {
1075 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1077 pub additional_context: Option<String>,
1078}
1079
1080#[derive(Debug, Clone, Serialize, Deserialize)]
1082pub struct PostToolUseFailureHookSpecificOutput {
1083 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1085 pub additional_context: Option<String>,
1086}
1087
1088#[derive(Debug, Clone, Serialize, Deserialize)]
1090pub struct NotificationHookSpecificOutput {
1091 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1093 pub additional_context: Option<String>,
1094}
1095
1096#[derive(Debug, Clone, Serialize, Deserialize)]
1098pub struct SubagentStartHookSpecificOutput {
1099 #[serde(rename = "additionalContext", skip_serializing_if = "Option::is_none")]
1101 pub additional_context: Option<String>,
1102}
1103
1104#[derive(Debug, Clone, Serialize, Deserialize)]
1106pub struct PermissionRequestHookSpecificOutput {
1107 pub decision: serde_json::Value,
1109}
1110
1111#[derive(Debug, Clone, Serialize, Deserialize)]
1113#[serde(tag = "hookEventName")]
1114pub enum HookSpecificOutput {
1115 #[serde(rename = "PreToolUse")]
1117 PreToolUse(PreToolUseHookSpecificOutput),
1118 #[serde(rename = "PostToolUse")]
1120 PostToolUse(PostToolUseHookSpecificOutput),
1121 #[serde(rename = "PostToolUseFailure")]
1123 PostToolUseFailure(PostToolUseFailureHookSpecificOutput),
1124 #[serde(rename = "UserPromptSubmit")]
1126 UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
1127 #[serde(rename = "SessionStart")]
1129 SessionStart(SessionStartHookSpecificOutput),
1130 #[serde(rename = "Notification")]
1132 Notification(NotificationHookSpecificOutput),
1133 #[serde(rename = "SubagentStart")]
1135 SubagentStart(SubagentStartHookSpecificOutput),
1136 #[serde(rename = "PermissionRequest")]
1138 PermissionRequest(PermissionRequestHookSpecificOutput),
1139}
1140
1141#[async_trait]
1150pub trait HookCallback: Send + Sync {
1151 async fn execute(
1163 &self,
1164 input: &HookInput,
1165 tool_use_id: Option<&str>,
1166 context: &HookContext,
1167 ) -> Result<HookJSONOutput, crate::errors::SdkError>;
1168}
1169
1170#[deprecated(
1175 since = "0.3.0",
1176 note = "Use the new HookCallback trait with HookInput/HookJSONOutput instead"
1177)]
1178#[allow(dead_code)]
1179#[async_trait]
1180pub trait HookCallbackLegacy: Send + Sync {
1181 async fn execute_legacy(
1183 &self,
1184 input: &serde_json::Value,
1185 tool_use_id: Option<&str>,
1186 context: &HookContext,
1187 ) -> serde_json::Value;
1188}
1189
1190#[derive(Clone)]
1192pub struct HookMatcher {
1193 pub matcher: Option<serde_json::Value>,
1195 pub hooks: Vec<Arc<dyn HookCallback>>,
1197}
1198
1199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1201#[serde(rename_all = "lowercase")]
1202pub enum SettingSource {
1203 User,
1205 Project,
1207 Local,
1209}
1210
1211#[derive(Debug, Clone, Serialize, Deserialize)]
1213pub struct AgentDefinition {
1214 pub description: String,
1216 pub prompt: String,
1218 #[serde(skip_serializing_if = "Option::is_none")]
1220 pub tools: Option<Vec<String>>,
1221 #[serde(default, skip_serializing_if = "Option::is_none", rename = "disallowedTools")]
1223 pub disallowed_tools: Option<Vec<String>>,
1224 #[serde(skip_serializing_if = "Option::is_none")]
1226 pub model: Option<String>,
1227 #[serde(default, skip_serializing_if = "Option::is_none")]
1229 pub skills: Option<Vec<String>>,
1230 #[serde(default, skip_serializing_if = "Option::is_none")]
1232 pub memory: Option<String>,
1233 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mcpServers")]
1235 pub mcp_servers: Option<Vec<serde_json::Value>>,
1236 #[serde(default, skip_serializing_if = "Option::is_none", rename = "initialPrompt")]
1238 pub initial_prompt: Option<String>,
1239 #[serde(default, skip_serializing_if = "Option::is_none", rename = "maxTurns")]
1241 pub max_turns: Option<i32>,
1242 #[serde(default, skip_serializing_if = "Option::is_none")]
1244 pub background: Option<bool>,
1245 #[serde(default, skip_serializing_if = "Option::is_none")]
1247 pub effort: Option<Effort>,
1248 #[serde(default, skip_serializing_if = "Option::is_none", rename = "permissionMode")]
1250 pub permission_mode: Option<PermissionMode>,
1251}
1252
1253#[derive(Debug, Clone, Serialize, Deserialize)]
1255#[serde(untagged)]
1256pub enum SystemPrompt {
1257 String(String),
1259 Preset {
1261 #[serde(rename = "type")]
1262 preset_type: String, preset: String, #[serde(skip_serializing_if = "Option::is_none")]
1265 append: Option<String>,
1266 },
1267}
1268
1269#[derive(Clone, Default)]
1271pub struct ClaudeCodeOptions {
1272 pub system_prompt_v2: Option<SystemPrompt>,
1276 #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1279 pub system_prompt: Option<String>,
1280 #[deprecated(since = "0.1.12", note = "Use system_prompt_v2 instead")]
1283 pub append_system_prompt: Option<String>,
1284 pub allowed_tools: Vec<String>,
1293 pub disallowed_tools: Vec<String>,
1302 pub permission_mode: PermissionMode,
1304 pub mcp_servers: HashMap<String, McpServerConfig>,
1306 pub mcp_tools: Vec<String>,
1308 pub max_turns: Option<i32>,
1310 pub max_thinking_tokens: Option<i32>,
1312 pub max_output_tokens: Option<u32>,
1314 pub model: Option<String>,
1316 pub cwd: Option<PathBuf>,
1318 pub continue_conversation: bool,
1320 pub resume: Option<String>,
1322 pub permission_prompt_tool_name: Option<String>,
1324 pub settings: Option<String>,
1326 pub add_dirs: Vec<PathBuf>,
1328 pub extra_args: HashMap<String, Option<String>>,
1330 pub env: HashMap<String, String>,
1332 pub debug_stderr: Option<Arc<Mutex<dyn Write + Send + Sync>>>,
1334 pub include_partial_messages: bool,
1336 pub can_use_tool: Option<Arc<dyn CanUseTool>>,
1338 pub hooks: Option<HashMap<String, Vec<HookMatcher>>>,
1340 pub control_protocol_format: ControlProtocolFormat,
1342
1343 pub setting_sources: Option<Vec<SettingSource>>,
1347 pub fork_session: bool,
1350 pub agents: Option<HashMap<String, AgentDefinition>>,
1353 pub cli_channel_buffer_size: Option<usize>,
1357
1358 pub tools: Option<ToolsConfig>,
1384 pub betas: Vec<SdkBeta>,
1387 pub max_budget_usd: Option<f64>,
1390 pub fallback_model: Option<String>,
1392 pub output_format: Option<serde_json::Value>,
1395 pub enable_file_checkpointing: bool,
1399 pub sandbox: Option<SandboxSettings>,
1402 pub plugins: Vec<SdkPluginConfig>,
1404 pub user: Option<String>,
1412 pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
1415 pub auto_download_cli: bool,
1434
1435 pub effort: Option<Effort>,
1438 pub thinking: Option<ThinkingConfig>,
1441}
1442
1443impl std::fmt::Debug for ClaudeCodeOptions {
1444 #[allow(deprecated)]
1445 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1446 f.debug_struct("ClaudeCodeOptions")
1447 .field("system_prompt", &self.system_prompt)
1448 .field("append_system_prompt", &self.append_system_prompt)
1449 .field("allowed_tools", &self.allowed_tools)
1450 .field("disallowed_tools", &self.disallowed_tools)
1451 .field("permission_mode", &self.permission_mode)
1452 .field("mcp_servers", &self.mcp_servers)
1453 .field("mcp_tools", &self.mcp_tools)
1454 .field("max_turns", &self.max_turns)
1455 .field("max_thinking_tokens", &self.max_thinking_tokens)
1456 .field("max_output_tokens", &self.max_output_tokens)
1457 .field("model", &self.model)
1458 .field("cwd", &self.cwd)
1459 .field("continue_conversation", &self.continue_conversation)
1460 .field("resume", &self.resume)
1461 .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
1462 .field("settings", &self.settings)
1463 .field("add_dirs", &self.add_dirs)
1464 .field("extra_args", &self.extra_args)
1465 .field("env", &self.env)
1466 .field("debug_stderr", &self.debug_stderr.is_some())
1467 .field("include_partial_messages", &self.include_partial_messages)
1468 .field("can_use_tool", &self.can_use_tool.is_some())
1469 .field("hooks", &self.hooks.is_some())
1470 .field("control_protocol_format", &self.control_protocol_format)
1471 .field("effort", &self.effort)
1472 .field("thinking", &self.thinking)
1473 .finish()
1474 }
1475}
1476
1477impl ClaudeCodeOptions {
1478 pub fn builder() -> ClaudeCodeOptionsBuilder {
1480 ClaudeCodeOptionsBuilder::default()
1481 }
1482}
1483
1484#[derive(Debug, Default)]
1486pub struct ClaudeCodeOptionsBuilder {
1487 options: ClaudeCodeOptions,
1488}
1489
1490impl ClaudeCodeOptionsBuilder {
1491 #[allow(deprecated)]
1493 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
1494 self.options.system_prompt = Some(prompt.into());
1495 self
1496 }
1497
1498 #[allow(deprecated)]
1500 pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1501 self.options.append_system_prompt = Some(prompt.into());
1502 self
1503 }
1504
1505 pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1513 self.options.allowed_tools = tools;
1514 self
1515 }
1516
1517 pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1521 self.options.allowed_tools.push(tool.into());
1522 self
1523 }
1524
1525 pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1534 self.options.disallowed_tools = tools;
1535 self
1536 }
1537
1538 pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1542 self.options.disallowed_tools.push(tool.into());
1543 self
1544 }
1545
1546 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1548 self.options.permission_mode = mode;
1549 self
1550 }
1551
1552 pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
1554 self.options.mcp_servers.insert(name.into(), config);
1555 self
1556 }
1557
1558 pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1560 self.options.mcp_servers = servers;
1561 self
1562 }
1563
1564 pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1566 self.options.mcp_tools = tools;
1567 self
1568 }
1569
1570 pub fn max_turns(mut self, turns: i32) -> Self {
1572 self.options.max_turns = Some(turns);
1573 self
1574 }
1575
1576 pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1578 self.options.max_thinking_tokens = Some(tokens);
1579 self
1580 }
1581
1582 pub fn max_output_tokens(mut self, tokens: u32) -> Self {
1584 self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
1585 self
1586 }
1587
1588 pub fn model(mut self, model: impl Into<String>) -> Self {
1590 self.options.model = Some(model.into());
1591 self
1592 }
1593
1594 pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1596 self.options.cwd = Some(path.into());
1597 self
1598 }
1599
1600 pub fn continue_conversation(mut self, enable: bool) -> Self {
1602 self.options.continue_conversation = enable;
1603 self
1604 }
1605
1606 pub fn resume(mut self, id: impl Into<String>) -> Self {
1608 self.options.resume = Some(id.into());
1609 self
1610 }
1611
1612 pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
1614 self.options.permission_prompt_tool_name = Some(name.into());
1615 self
1616 }
1617
1618 pub fn settings(mut self, settings: impl Into<String>) -> Self {
1620 self.options.settings = Some(settings.into());
1621 self
1622 }
1623
1624 pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1626 self.options.add_dirs = dirs;
1627 self
1628 }
1629
1630 pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1632 self.options.add_dirs.push(dir.into());
1633 self
1634 }
1635
1636 pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1638 self.options.extra_args = args;
1639 self
1640 }
1641
1642 pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
1644 self.options.extra_args.insert(key.into(), value);
1645 self
1646 }
1647
1648 pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1650 self.options.control_protocol_format = format;
1651 self
1652 }
1653
1654 pub fn include_partial_messages(mut self, include: bool) -> Self {
1656 self.options.include_partial_messages = include;
1657 self
1658 }
1659
1660 pub fn fork_session(mut self, fork: bool) -> Self {
1662 self.options.fork_session = fork;
1663 self
1664 }
1665
1666 pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1668 self.options.setting_sources = Some(sources);
1669 self
1670 }
1671
1672 pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1674 self.options.agents = Some(agents);
1675 self
1676 }
1677
1678 pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1696 self.options.cli_channel_buffer_size = Some(size);
1697 self
1698 }
1699
1700 pub fn tools(mut self, config: ToolsConfig) -> Self {
1717 self.options.tools = Some(config);
1718 self
1719 }
1720
1721 pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1725 self.options.betas = betas;
1726 self
1727 }
1728
1729 pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1731 self.options.betas.push(beta);
1732 self
1733 }
1734
1735 pub fn max_budget_usd(mut self, budget: f64) -> Self {
1739 self.options.max_budget_usd = Some(budget);
1740 self
1741 }
1742
1743 pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1747 self.options.fallback_model = Some(model.into());
1748 self
1749 }
1750
1751 pub fn output_format(mut self, format: serde_json::Value) -> Self {
1772 self.options.output_format = Some(format);
1773 self
1774 }
1775
1776 pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1781 self.options.enable_file_checkpointing = enable;
1782 self
1783 }
1784
1785 pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1789 self.options.sandbox = Some(settings);
1790 self
1791 }
1792
1793 pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1795 self.options.plugins = plugins;
1796 self
1797 }
1798
1799 pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1801 self.options.plugins.push(plugin);
1802 self
1803 }
1804
1805 pub fn user(mut self, user: impl Into<String>) -> Self {
1807 self.options.user = Some(user.into());
1808 self
1809 }
1810
1811 pub fn stderr_callback(mut self, callback: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
1815 self.options.stderr_callback = Some(callback);
1816 self
1817 }
1818
1819 pub fn auto_download_cli(mut self, enable: bool) -> Self {
1833 self.options.auto_download_cli = enable;
1834 self
1835 }
1836
1837 pub fn effort(mut self, effort: Effort) -> Self {
1850 self.options.effort = Some(effort);
1851 self
1852 }
1853
1854 pub fn thinking(mut self, config: ThinkingConfig) -> Self {
1867 self.options.thinking = Some(config);
1868 self
1869 }
1870
1871 pub fn build(self) -> ClaudeCodeOptions {
1873 self.options
1874 }
1875}
1876
1877#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1883pub struct TaskUsage {
1884 #[serde(default)]
1886 pub total_tokens: u64,
1887 #[serde(default)]
1889 pub tool_uses: u64,
1890 #[serde(default)]
1892 pub duration_ms: u64,
1893}
1894
1895#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1897#[serde(rename_all = "lowercase")]
1898pub enum TaskStatus {
1899 Completed,
1901 Failed,
1903 Stopped,
1905}
1906
1907#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1909pub struct TaskStartedMessage {
1910 pub task_id: String,
1912 pub description: String,
1914 pub uuid: String,
1916 pub session_id: String,
1918 #[serde(default, skip_serializing_if = "Option::is_none")]
1920 pub tool_use_id: Option<String>,
1921 #[serde(default, skip_serializing_if = "Option::is_none")]
1923 pub task_type: Option<String>,
1924}
1925
1926#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1928pub struct TaskProgressMessage {
1929 pub task_id: String,
1931 pub description: String,
1933 #[serde(default)]
1935 pub usage: TaskUsage,
1936 pub uuid: String,
1938 pub session_id: String,
1940 #[serde(default, skip_serializing_if = "Option::is_none")]
1942 pub tool_use_id: Option<String>,
1943 #[serde(default, skip_serializing_if = "Option::is_none")]
1945 pub last_tool_name: Option<String>,
1946}
1947
1948#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1950pub struct TaskNotificationMessage {
1951 pub task_id: String,
1953 pub status: TaskStatus,
1955 #[serde(default, skip_serializing_if = "Option::is_none")]
1957 pub output_file: Option<String>,
1958 #[serde(default, skip_serializing_if = "Option::is_none")]
1960 pub summary: Option<String>,
1961 pub uuid: String,
1963 pub session_id: String,
1965 #[serde(default, skip_serializing_if = "Option::is_none")]
1967 pub tool_use_id: Option<String>,
1968 #[serde(default, skip_serializing_if = "Option::is_none")]
1970 pub usage: Option<TaskUsage>,
1971}
1972
1973#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1975#[non_exhaustive]
1976#[serde(tag = "type", rename_all = "lowercase")]
1977pub enum Message {
1978 User {
1980 message: UserMessage,
1982 },
1983 Assistant {
1985 message: AssistantMessage,
1987 },
1988 System {
1990 subtype: String,
1992 data: serde_json::Value,
1994 },
1995 Result {
1997 subtype: String,
1999 duration_ms: i64,
2001 duration_api_ms: i64,
2003 is_error: bool,
2005 num_turns: i32,
2007 session_id: String,
2009 #[serde(skip_serializing_if = "Option::is_none")]
2011 total_cost_usd: Option<f64>,
2012 #[serde(skip_serializing_if = "Option::is_none")]
2014 usage: Option<serde_json::Value>,
2015 #[serde(skip_serializing_if = "Option::is_none")]
2017 result: Option<String>,
2018 #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
2021 structured_output: Option<serde_json::Value>,
2022 #[serde(default, skip_serializing_if = "Option::is_none")]
2024 stop_reason: Option<String>,
2025 },
2026
2027 #[serde(rename = "stream_event")]
2029 StreamEvent {
2030 uuid: String,
2032 session_id: String,
2034 event: serde_json::Value,
2036 #[serde(default, skip_serializing_if = "Option::is_none")]
2038 parent_tool_use_id: Option<String>,
2039 },
2040
2041 #[serde(rename = "rate_limit")]
2043 RateLimit {
2044 rate_limit_info: RateLimitInfo,
2046 uuid: String,
2048 session_id: String,
2050 },
2051
2052 #[serde(skip)]
2055 Unknown {
2056 msg_type: String,
2058 raw: serde_json::Value,
2060 },
2061}
2062
2063impl Message {
2064 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
2066 if let Message::System { subtype, data } = self {
2067 if subtype == "task_started" {
2068 return serde_json::from_value(data.clone()).ok();
2069 }
2070 }
2071 None
2072 }
2073
2074 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
2076 if let Message::System { subtype, data } = self {
2077 if subtype == "task_progress" {
2078 return serde_json::from_value(data.clone()).ok();
2079 }
2080 }
2081 None
2082 }
2083
2084 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
2086 if let Message::System { subtype, data } = self {
2087 if subtype == "task_notification" {
2088 return serde_json::from_value(data.clone()).ok();
2089 }
2090 }
2091 None
2092 }
2093}
2094
2095#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2097pub struct UserMessage {
2098 pub content: String,
2100}
2101
2102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2104pub struct AssistantMessage {
2105 pub content: Vec<ContentBlock>,
2107 #[serde(default, skip_serializing_if = "Option::is_none")]
2109 pub model: Option<String>,
2110 #[serde(default, skip_serializing_if = "Option::is_none")]
2112 pub usage: Option<serde_json::Value>,
2113 #[serde(default, skip_serializing_if = "Option::is_none")]
2115 pub error: Option<AssistantMessageError>,
2116 #[serde(default, skip_serializing_if = "Option::is_none")]
2118 pub parent_tool_use_id: Option<String>,
2119}
2120
2121pub use Message::Result as ResultMessage;
2123pub use Message::System as SystemMessage;
2125
2126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2128#[serde(untagged)]
2129pub enum ContentBlock {
2130 Text(TextContent),
2132 Thinking(ThinkingContent),
2134 ToolUse(ToolUseContent),
2136 ToolResult(ToolResultContent),
2138}
2139
2140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2142pub struct TextContent {
2143 pub text: String,
2145}
2146
2147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2149pub struct ThinkingContent {
2150 pub thinking: String,
2152 pub signature: String,
2154}
2155
2156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2158pub struct ToolUseContent {
2159 pub id: String,
2161 pub name: String,
2163 pub input: serde_json::Value,
2165}
2166
2167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2169pub struct ToolResultContent {
2170 pub tool_use_id: String,
2172 #[serde(skip_serializing_if = "Option::is_none")]
2174 pub content: Option<ContentValue>,
2175 #[serde(skip_serializing_if = "Option::is_none")]
2177 pub is_error: Option<bool>,
2178}
2179
2180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2182#[serde(untagged)]
2183pub enum ContentValue {
2184 Text(String),
2186 Structured(Vec<serde_json::Value>),
2188}
2189
2190#[derive(Debug, Clone, Serialize, Deserialize)]
2192pub struct UserContent {
2193 pub role: String,
2195 pub content: String,
2197}
2198
2199#[derive(Debug, Clone, Serialize, Deserialize)]
2201pub struct AssistantContent {
2202 pub role: String,
2204 pub content: Vec<ContentBlock>,
2206}
2207
2208#[derive(Debug, Clone, Serialize, Deserialize)]
2210pub struct SDKControlInterruptRequest {
2211 pub subtype: String, }
2214
2215#[derive(Debug, Clone, Serialize, Deserialize)]
2217pub struct SDKControlPermissionRequest {
2218 pub subtype: String, #[serde(alias = "toolName")]
2222 pub tool_name: String,
2223 pub input: serde_json::Value,
2225 #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
2227 pub permission_suggestions: Option<Vec<PermissionUpdate>>,
2228 #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
2230 pub blocked_path: Option<String>,
2231}
2232
2233#[derive(Debug, Clone, Serialize, Deserialize)]
2235pub struct SDKControlInitializeRequest {
2236 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
2240 pub hooks: Option<HashMap<String, serde_json::Value>>,
2241}
2242
2243#[derive(Debug, Clone, Serialize, Deserialize)]
2245#[serde(rename_all = "camelCase")]
2246pub struct SDKControlSetPermissionModeRequest {
2247 pub subtype: String, pub mode: String,
2251}
2252
2253#[derive(Debug, Clone, Serialize, Deserialize)]
2255#[serde(rename_all = "camelCase")]
2256pub struct SDKControlSetModelRequest {
2257 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
2261 pub model: Option<String>,
2262}
2263
2264#[derive(Debug, Clone, Serialize, Deserialize)]
2266pub struct SDKHookCallbackRequest {
2267 pub subtype: String, #[serde(alias = "callbackId")]
2271 pub callback_id: String,
2272 pub input: serde_json::Value,
2274 #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
2276 pub tool_use_id: Option<String>,
2277}
2278
2279#[derive(Debug, Clone, Serialize, Deserialize)]
2281pub struct SDKControlMcpMessageRequest {
2282 pub subtype: String, #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
2286 pub mcp_server_name: String,
2287 pub message: serde_json::Value,
2289}
2290
2291#[derive(Debug, Clone, Serialize, Deserialize)]
2296pub struct SDKControlRewindFilesRequest {
2297 pub subtype: String,
2299 #[serde(alias = "userMessageId")]
2301 pub user_message_id: String,
2302}
2303
2304impl SDKControlRewindFilesRequest {
2305 pub fn new(user_message_id: impl Into<String>) -> Self {
2307 Self {
2308 subtype: "rewind_files".to_string(),
2309 user_message_id: user_message_id.into(),
2310 }
2311 }
2312}
2313
2314#[derive(Debug, Clone, Serialize, Deserialize)]
2316pub struct SDKControlGetContextUsageRequest {
2317 pub subtype: String,
2319}
2320
2321impl SDKControlGetContextUsageRequest {
2322 pub fn new() -> Self {
2324 Self {
2325 subtype: "get_context_usage".to_string(),
2326 }
2327 }
2328}
2329
2330#[derive(Debug, Clone, Serialize, Deserialize)]
2332pub struct SDKControlStopTaskRequest {
2333 pub subtype: String,
2335 #[serde(alias = "taskId")]
2337 pub task_id: String,
2338}
2339
2340impl SDKControlStopTaskRequest {
2341 pub fn new(task_id: impl Into<String>) -> Self {
2343 Self {
2344 subtype: "stop_task".to_string(),
2345 task_id: task_id.into(),
2346 }
2347 }
2348}
2349
2350#[derive(Debug, Clone, Serialize, Deserialize)]
2352pub struct SDKControlMcpStatusRequest {
2353 pub subtype: String,
2355}
2356
2357impl SDKControlMcpStatusRequest {
2358 pub fn new() -> Self {
2360 Self {
2361 subtype: "mcp_status".to_string(),
2362 }
2363 }
2364}
2365
2366#[derive(Debug, Clone, Serialize, Deserialize)]
2368pub struct SDKControlMcpReconnectRequest {
2369 pub subtype: String,
2371 #[serde(alias = "serverName")]
2373 pub server_name: String,
2374}
2375
2376impl SDKControlMcpReconnectRequest {
2377 pub fn new(server_name: impl Into<String>) -> Self {
2379 Self {
2380 subtype: "mcp_reconnect".to_string(),
2381 server_name: server_name.into(),
2382 }
2383 }
2384}
2385
2386#[derive(Debug, Clone, Serialize, Deserialize)]
2388pub struct SDKControlMcpToggleRequest {
2389 pub subtype: String,
2391 #[serde(alias = "serverName")]
2393 pub server_name: String,
2394 pub enabled: bool,
2396}
2397
2398impl SDKControlMcpToggleRequest {
2399 pub fn new(server_name: impl Into<String>, enabled: bool) -> Self {
2401 Self {
2402 subtype: "mcp_toggle".to_string(),
2403 server_name: server_name.into(),
2404 enabled,
2405 }
2406 }
2407}
2408
2409#[derive(Debug, Clone, Serialize, Deserialize)]
2411#[serde(rename_all = "camelCase")]
2412pub struct ContextUsageCategory {
2413 pub name: String,
2415 pub token_count: u64,
2417 #[serde(default)]
2419 pub percentage: f64,
2420}
2421
2422#[derive(Debug, Clone, Serialize, Deserialize)]
2424#[serde(rename_all = "camelCase")]
2425pub struct ApiUsage {
2426 #[serde(default)]
2428 pub input_tokens: u64,
2429 #[serde(default)]
2431 pub output_tokens: u64,
2432 #[serde(default)]
2434 pub cache_read_input_tokens: u64,
2435 #[serde(default)]
2437 pub cache_creation_input_tokens: u64,
2438}
2439
2440#[derive(Debug, Clone, Serialize, Deserialize)]
2442#[serde(rename_all = "camelCase")]
2443pub struct ContextUsageResponse {
2444 #[serde(default)]
2446 pub categories: Vec<ContextUsageCategory>,
2447 #[serde(default)]
2449 pub total_tokens: u64,
2450 #[serde(default)]
2452 pub max_tokens: u64,
2453 #[serde(default)]
2455 pub percentage: f64,
2456 #[serde(default)]
2458 pub model: String,
2459 #[serde(default)]
2461 pub is_auto_compact_enabled: bool,
2462 #[serde(default, skip_serializing_if = "Option::is_none")]
2464 pub auto_compact_threshold: Option<u64>,
2465 #[serde(default)]
2467 pub memory_files: Vec<serde_json::Value>,
2468 #[serde(default)]
2470 pub mcp_tools: Vec<serde_json::Value>,
2471 #[serde(default, skip_serializing_if = "Option::is_none")]
2473 pub message_breakdown: Option<serde_json::Value>,
2474 #[serde(default, skip_serializing_if = "Option::is_none")]
2476 pub api_usage: Option<ApiUsage>,
2477}
2478
2479#[derive(Debug, Clone, Serialize, Deserialize)]
2481#[serde(rename_all = "camelCase")]
2482pub struct TaskBudget {
2483 #[serde(default, skip_serializing_if = "Option::is_none")]
2485 pub max_cost_usd: Option<f64>,
2486 #[serde(default, skip_serializing_if = "Option::is_none")]
2488 pub max_tokens: Option<u64>,
2489 #[serde(default, skip_serializing_if = "Option::is_none")]
2491 pub max_turns: Option<i32>,
2492}
2493
2494#[derive(Debug, Clone, Serialize, Deserialize)]
2496#[serde(rename_all = "camelCase")]
2497pub struct ForkSessionResult {
2498 pub session_id: String,
2500}
2501
2502#[derive(Debug, Clone, Serialize, Deserialize)]
2504#[serde(tag = "type", rename_all = "snake_case")]
2505pub enum SDKControlRequest {
2506 #[serde(rename = "interrupt")]
2508 Interrupt(SDKControlInterruptRequest),
2509 #[serde(rename = "can_use_tool")]
2511 CanUseTool(SDKControlPermissionRequest),
2512 #[serde(rename = "initialize")]
2514 Initialize(SDKControlInitializeRequest),
2515 #[serde(rename = "set_permission_mode")]
2517 SetPermissionMode(SDKControlSetPermissionModeRequest),
2518 #[serde(rename = "set_model")]
2520 SetModel(SDKControlSetModelRequest),
2521 #[serde(rename = "hook_callback")]
2523 HookCallback(SDKHookCallbackRequest),
2524 #[serde(rename = "mcp_message")]
2526 McpMessage(SDKControlMcpMessageRequest),
2527 #[serde(rename = "rewind_files")]
2529 RewindFiles(SDKControlRewindFilesRequest),
2530 #[serde(rename = "get_context_usage")]
2532 GetContextUsage(SDKControlGetContextUsageRequest),
2533 #[serde(rename = "stop_task")]
2535 StopTask(SDKControlStopTaskRequest),
2536 #[serde(rename = "mcp_status")]
2538 McpStatus(SDKControlMcpStatusRequest),
2539 #[serde(rename = "mcp_reconnect")]
2541 McpReconnect(SDKControlMcpReconnectRequest),
2542 #[serde(rename = "mcp_toggle")]
2544 McpToggle(SDKControlMcpToggleRequest),
2545}
2546
2547#[derive(Debug, Clone, Serialize, Deserialize)]
2549#[serde(tag = "type", rename_all = "lowercase")]
2550pub enum ControlRequest {
2551 Interrupt {
2553 request_id: String,
2555 },
2556}
2557
2558#[derive(Debug, Clone, Serialize, Deserialize)]
2560#[serde(tag = "type", rename_all = "lowercase")]
2561pub enum ControlResponse {
2562 InterruptAck {
2564 request_id: String,
2566 success: bool,
2568 },
2569}
2570
2571#[cfg(test)]
2572mod tests {
2573 use super::*;
2574
2575 #[test]
2576 fn test_permission_mode_serialization() {
2577 let mode = PermissionMode::AcceptEdits;
2578 let json = serde_json::to_string(&mode).unwrap();
2579 assert_eq!(json, r#""acceptEdits""#);
2580
2581 let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
2582 assert_eq!(deserialized, mode);
2583
2584 let plan_mode = PermissionMode::Plan;
2586 let plan_json = serde_json::to_string(&plan_mode).unwrap();
2587 assert_eq!(plan_json, r#""plan""#);
2588
2589 let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
2590 assert_eq!(plan_deserialized, plan_mode);
2591 }
2592
2593 #[test]
2594 fn test_message_serialization() {
2595 let msg = Message::User {
2596 message: UserMessage {
2597 content: "Hello".to_string(),
2598 },
2599 };
2600
2601 let json = serde_json::to_string(&msg).unwrap();
2602 assert!(json.contains(r#""type":"user""#));
2603 assert!(json.contains(r#""content":"Hello""#));
2604
2605 let deserialized: Message = serde_json::from_str(&json).unwrap();
2606 assert_eq!(deserialized, msg);
2607 }
2608
2609 #[test]
2610 #[allow(deprecated)]
2611 fn test_options_builder() {
2612 let options = ClaudeCodeOptions::builder()
2613 .system_prompt("Test prompt")
2614 .model("claude-3-opus")
2615 .permission_mode(PermissionMode::AcceptEdits)
2616 .allow_tool("read")
2617 .allow_tool("write")
2618 .max_turns(10)
2619 .build();
2620
2621 assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
2622 assert_eq!(options.model, Some("claude-3-opus".to_string()));
2623 assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
2624 assert_eq!(options.allowed_tools, vec!["read", "write"]);
2625 assert_eq!(options.max_turns, Some(10));
2626 }
2627
2628 #[test]
2629 fn test_extra_args() {
2630 let mut extra_args = HashMap::new();
2631 extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
2632 extra_args.insert("boolean-flag".to_string(), None);
2633
2634 let options = ClaudeCodeOptions::builder()
2635 .extra_args(extra_args.clone())
2636 .add_extra_arg("another-flag", Some("another-value".to_string()))
2637 .build();
2638
2639 assert_eq!(options.extra_args.len(), 3);
2640 assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
2641 assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
2642 assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
2643 }
2644
2645 #[test]
2646 fn test_thinking_content_serialization() {
2647 let thinking = ThinkingContent {
2648 thinking: "Let me think about this...".to_string(),
2649 signature: "sig123".to_string(),
2650 };
2651
2652 let json = serde_json::to_string(&thinking).unwrap();
2653 assert!(json.contains(r#""thinking":"Let me think about this...""#));
2654 assert!(json.contains(r#""signature":"sig123""#));
2655
2656 let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
2657 assert_eq!(deserialized.thinking, thinking.thinking);
2658 assert_eq!(deserialized.signature, thinking.signature);
2659 }
2660
2661 #[test]
2664 fn test_tools_config_list_serialization() {
2665 let tools = ToolsConfig::List(vec!["Read".to_string(), "Write".to_string(), "Bash".to_string()]);
2666 let json = serde_json::to_string(&tools).unwrap();
2667
2668 assert!(json.contains("Read"));
2670 assert!(json.contains("Write"));
2671 assert!(json.contains("Bash"));
2672
2673 let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2674 match deserialized {
2675 ToolsConfig::List(list) => {
2676 assert_eq!(list.len(), 3);
2677 assert!(list.contains(&"Read".to_string()));
2678 }
2679 _ => panic!("Expected List variant"),
2680 }
2681 }
2682
2683 #[test]
2684 fn test_tools_config_preset_serialization() {
2685 let preset = ToolsConfig::claude_code_preset();
2687 let json = serde_json::to_string(&preset).unwrap();
2688 assert!(json.contains("preset"));
2689 assert!(json.contains("claude_code"));
2690
2691 let custom_preset = ToolsConfig::Preset(ToolsPreset {
2693 preset_type: "preset".to_string(),
2694 preset: "custom".to_string(),
2695 });
2696 let json = serde_json::to_string(&custom_preset).unwrap();
2697 assert!(json.contains("custom"));
2698
2699 let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2701 match deserialized {
2702 ToolsConfig::Preset(p) => assert_eq!(p.preset, "custom"),
2703 _ => panic!("Expected Preset variant"),
2704 }
2705 }
2706
2707 #[test]
2708 fn test_tools_config_helper_methods() {
2709 let tools = ToolsConfig::list(vec!["Read".to_string(), "Write".to_string()]);
2711 match tools {
2712 ToolsConfig::List(list) => assert_eq!(list.len(), 2),
2713 _ => panic!("Expected List variant"),
2714 }
2715
2716 let empty = ToolsConfig::none();
2718 match empty {
2719 ToolsConfig::List(list) => assert!(list.is_empty()),
2720 _ => panic!("Expected empty List variant"),
2721 }
2722
2723 let preset = ToolsConfig::claude_code_preset();
2725 match preset {
2726 ToolsConfig::Preset(p) => {
2727 assert_eq!(p.preset_type, "preset");
2728 assert_eq!(p.preset, "claude_code");
2729 }
2730 _ => panic!("Expected Preset variant"),
2731 }
2732 }
2733
2734 #[test]
2735 fn test_sdk_beta_serialization() {
2736 let beta = SdkBeta::Context1M;
2737 let json = serde_json::to_string(&beta).unwrap();
2738 assert_eq!(json, r#""context-1m-2025-08-07""#);
2740
2741 let display = format!("{}", beta);
2743 assert_eq!(display, "context-1m-2025-08-07");
2744
2745 let deserialized: SdkBeta = serde_json::from_str(r#""context-1m-2025-08-07""#).unwrap();
2747 assert!(matches!(deserialized, SdkBeta::Context1M));
2748 }
2749
2750 #[test]
2751 fn test_sandbox_settings_serialization() {
2752 let sandbox = SandboxSettings {
2753 enabled: Some(true),
2754 auto_allow_bash_if_sandboxed: Some(true),
2755 excluded_commands: Some(vec!["git".to_string(), "docker".to_string()]),
2756 allow_unsandboxed_commands: Some(false),
2757 network: Some(SandboxNetworkConfig {
2758 allow_unix_sockets: Some(vec!["/tmp/ssh-agent.sock".to_string()]),
2759 allow_all_unix_sockets: Some(false),
2760 allow_local_binding: Some(true),
2761 http_proxy_port: Some(8080),
2762 socks_proxy_port: Some(1080),
2763 }),
2764 ignore_violations: Some(SandboxIgnoreViolations {
2765 file: Some(vec!["/tmp".to_string(), "/var/log".to_string()]),
2766 network: Some(vec!["localhost".to_string()]),
2767 }),
2768 enable_weaker_nested_sandbox: Some(false),
2769 };
2770
2771 let json = serde_json::to_string(&sandbox).unwrap();
2772 assert!(json.contains("enabled"));
2773 assert!(json.contains("autoAllowBashIfSandboxed")); assert!(json.contains("excludedCommands"));
2775 assert!(json.contains("httpProxyPort"));
2776 assert!(json.contains("8080"));
2777
2778 let deserialized: SandboxSettings = serde_json::from_str(&json).unwrap();
2779 assert!(deserialized.enabled.unwrap());
2780 assert!(deserialized.network.is_some());
2781 assert_eq!(deserialized.network.as_ref().unwrap().http_proxy_port, Some(8080));
2782 }
2783
2784 #[test]
2785 fn test_sandbox_network_config() {
2786 let config = SandboxNetworkConfig {
2787 allow_unix_sockets: Some(vec!["/run/user/1000/keyring/ssh".to_string()]),
2788 allow_all_unix_sockets: Some(false),
2789 allow_local_binding: Some(true),
2790 http_proxy_port: Some(3128),
2791 socks_proxy_port: Some(1080),
2792 };
2793
2794 let json = serde_json::to_string(&config).unwrap();
2795 let deserialized: SandboxNetworkConfig = serde_json::from_str(&json).unwrap();
2796
2797 assert_eq!(deserialized.http_proxy_port, Some(3128));
2798 assert_eq!(deserialized.socks_proxy_port, Some(1080));
2799 assert_eq!(deserialized.allow_local_binding, Some(true));
2800 }
2801
2802 #[test]
2803 fn test_sandbox_ignore_violations() {
2804 let violations = SandboxIgnoreViolations {
2805 file: Some(vec!["/tmp".to_string(), "/var/cache".to_string()]),
2806 network: Some(vec!["127.0.0.1".to_string(), "localhost".to_string()]),
2807 };
2808
2809 let json = serde_json::to_string(&violations).unwrap();
2810 assert!(json.contains("file"));
2811 assert!(json.contains("/tmp"));
2812
2813 let deserialized: SandboxIgnoreViolations = serde_json::from_str(&json).unwrap();
2814 assert_eq!(deserialized.file.as_ref().unwrap().len(), 2);
2815 assert_eq!(deserialized.network.as_ref().unwrap().len(), 2);
2816 }
2817
2818 #[test]
2819 fn test_sandbox_settings_default() {
2820 let sandbox = SandboxSettings::default();
2821 assert!(sandbox.enabled.is_none());
2822 assert!(sandbox.network.is_none());
2823 assert!(sandbox.ignore_violations.is_none());
2824 }
2825
2826 #[test]
2827 fn test_sdk_plugin_config_serialization() {
2828 let plugin = SdkPluginConfig::Local {
2829 path: "/path/to/plugin".to_string()
2830 };
2831
2832 let json = serde_json::to_string(&plugin).unwrap();
2833 assert!(json.contains("local")); assert!(json.contains("/path/to/plugin"));
2835
2836 let deserialized: SdkPluginConfig = serde_json::from_str(&json).unwrap();
2837 match deserialized {
2838 SdkPluginConfig::Local { path } => {
2839 assert_eq!(path, "/path/to/plugin");
2840 }
2841 }
2842 }
2843
2844 #[test]
2845 fn test_sdk_control_rewind_files_request() {
2846 let request = SDKControlRewindFilesRequest {
2847 subtype: "rewind_files".to_string(),
2848 user_message_id: "msg_12345".to_string(),
2849 };
2850
2851 let json = serde_json::to_string(&request).unwrap();
2852 assert!(json.contains("user_message_id"));
2853 assert!(json.contains("msg_12345"));
2854 assert!(json.contains("subtype"));
2855 assert!(json.contains("rewind_files"));
2856
2857 let deserialized: SDKControlRewindFilesRequest = serde_json::from_str(&json).unwrap();
2858 assert_eq!(deserialized.user_message_id, "msg_12345");
2859 assert_eq!(deserialized.subtype, "rewind_files");
2860 }
2861
2862 #[test]
2863 fn test_options_builder_with_new_fields() {
2864 let options = ClaudeCodeOptions::builder()
2865 .tools(ToolsConfig::claude_code_preset())
2866 .add_beta(SdkBeta::Context1M)
2867 .max_budget_usd(10.0)
2868 .fallback_model("claude-3-haiku")
2869 .output_format(serde_json::json!({"type": "object"}))
2870 .enable_file_checkpointing(true)
2871 .sandbox(SandboxSettings::default())
2872 .add_plugin(SdkPluginConfig::Local { path: "/plugin".to_string() })
2873 .auto_download_cli(true)
2874 .build();
2875
2876 assert!(options.tools.is_some());
2878 match options.tools.as_ref().unwrap() {
2879 ToolsConfig::Preset(preset) => assert_eq!(preset.preset, "claude_code"),
2880 _ => panic!("Expected Preset variant"),
2881 }
2882
2883 assert_eq!(options.betas.len(), 1);
2885 assert!(matches!(options.betas[0], SdkBeta::Context1M));
2886
2887 assert_eq!(options.max_budget_usd, Some(10.0));
2889
2890 assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2892
2893 assert!(options.output_format.is_some());
2895
2896 assert!(options.enable_file_checkpointing);
2898
2899 assert!(options.sandbox.is_some());
2901
2902 assert_eq!(options.plugins.len(), 1);
2904
2905 assert!(options.auto_download_cli);
2907 }
2908
2909 #[test]
2910 fn test_options_builder_with_tools_list() {
2911 let options = ClaudeCodeOptions::builder()
2912 .tools(ToolsConfig::List(vec!["Read".to_string(), "Bash".to_string()]))
2913 .build();
2914
2915 match options.tools.as_ref().unwrap() {
2916 ToolsConfig::List(list) => {
2917 assert_eq!(list.len(), 2);
2918 assert!(list.contains(&"Read".to_string()));
2919 assert!(list.contains(&"Bash".to_string()));
2920 }
2921 _ => panic!("Expected List variant"),
2922 }
2923 }
2924
2925 #[test]
2926 fn test_options_builder_multiple_betas() {
2927 let options = ClaudeCodeOptions::builder()
2928 .add_beta(SdkBeta::Context1M)
2929 .betas(vec![SdkBeta::Context1M])
2930 .build();
2931
2932 assert_eq!(options.betas.len(), 1);
2934 }
2935
2936 #[test]
2937 fn test_options_builder_add_beta_accumulates() {
2938 let options = ClaudeCodeOptions::builder()
2939 .add_beta(SdkBeta::Context1M)
2940 .add_beta(SdkBeta::Context1M)
2941 .build();
2942
2943 assert_eq!(options.betas.len(), 2);
2945 }
2946
2947 #[test]
2948 fn test_options_builder_multiple_plugins() {
2949 let options = ClaudeCodeOptions::builder()
2950 .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2951 .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2952 .plugins(vec![SdkPluginConfig::Local { path: "/plugin3".to_string() }])
2953 .build();
2954
2955 assert_eq!(options.plugins.len(), 1);
2957 }
2958
2959 #[test]
2960 fn test_options_builder_add_plugin_accumulates() {
2961 let options = ClaudeCodeOptions::builder()
2962 .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2963 .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2964 .add_plugin(SdkPluginConfig::Local { path: "/plugin3".to_string() })
2965 .build();
2966
2967 assert_eq!(options.plugins.len(), 3);
2969 }
2970
2971 #[test]
2972 fn test_message_result_with_structured_output() {
2973 let json = r#"{
2975 "type": "result",
2976 "subtype": "success",
2977 "cost_usd": 0.05,
2978 "duration_ms": 1500,
2979 "duration_api_ms": 1200,
2980 "is_error": false,
2981 "num_turns": 3,
2982 "session_id": "session_123",
2983 "structured_output": {"answer": 42}
2984 }"#;
2985
2986 let msg: Message = serde_json::from_str(json).unwrap();
2987 match msg {
2988 Message::Result {
2989 structured_output,
2990 ..
2991 } => {
2992 assert!(structured_output.is_some());
2993 let output = structured_output.unwrap();
2994 assert_eq!(output["answer"], 42);
2995 }
2996 _ => panic!("Expected Result message"),
2997 }
2998 }
2999
3000 #[test]
3001 fn test_message_result_with_structured_output_camel_case() {
3002 let json = r#"{
3004 "type": "result",
3005 "subtype": "success",
3006 "cost_usd": 0.05,
3007 "duration_ms": 1500,
3008 "duration_api_ms": 1200,
3009 "is_error": false,
3010 "num_turns": 3,
3011 "session_id": "session_123",
3012 "structuredOutput": {"name": "test", "value": true}
3013 }"#;
3014
3015 let msg: Message = serde_json::from_str(json).unwrap();
3016 match msg {
3017 Message::Result {
3018 structured_output,
3019 ..
3020 } => {
3021 assert!(structured_output.is_some());
3022 let output = structured_output.unwrap();
3023 assert_eq!(output["name"], "test");
3024 assert_eq!(output["value"], true);
3025 }
3026 _ => panic!("Expected Result message"),
3027 }
3028 }
3029
3030 #[test]
3031 fn test_default_options_new_fields() {
3032 let options = ClaudeCodeOptions::default();
3033
3034 assert!(options.tools.is_none());
3036 assert!(options.betas.is_empty());
3037 assert!(options.max_budget_usd.is_none());
3038 assert!(options.fallback_model.is_none());
3039 assert!(options.output_format.is_none());
3040 assert!(!options.enable_file_checkpointing);
3041 assert!(options.sandbox.is_none());
3042 assert!(options.plugins.is_empty());
3043 assert!(options.user.is_none());
3044 assert!(!options.auto_download_cli);
3047 }
3048
3049 #[test]
3052 fn test_permission_mode_dont_ask_serialization() {
3053 let mode = PermissionMode::DontAsk;
3054 let json = serde_json::to_string(&mode).unwrap();
3055 assert_eq!(json, r#""dontAsk""#);
3056
3057 let deserialized: PermissionMode = serde_json::from_str(r#""dontAsk""#).unwrap();
3058 assert_eq!(deserialized, PermissionMode::DontAsk);
3059 }
3060
3061 #[test]
3062 fn test_permission_mode_all_variants_roundtrip() {
3063 let variants = vec![
3064 (PermissionMode::Default, r#""default""#),
3065 (PermissionMode::AcceptEdits, r#""acceptEdits""#),
3066 (PermissionMode::Plan, r#""plan""#),
3067 (PermissionMode::BypassPermissions, r#""bypassPermissions""#),
3068 (PermissionMode::DontAsk, r#""dontAsk""#),
3069 ];
3070 for (mode, expected_json) in variants {
3071 let json = serde_json::to_string(&mode).unwrap();
3072 assert_eq!(json, expected_json, "serialization failed for {:?}", mode);
3073 let back: PermissionMode = serde_json::from_str(&json).unwrap();
3074 assert_eq!(back, mode, "roundtrip failed for {:?}", mode);
3075 }
3076 }
3077
3078 #[test]
3081 fn test_agent_definition_all_new_fields() {
3082 let agent = AgentDefinition {
3083 description: "Coder".to_string(),
3084 prompt: "You write code".to_string(),
3085 tools: Some(vec!["Bash".to_string()]),
3086 disallowed_tools: Some(vec!["Write".to_string()]),
3087 model: Some("claude-sonnet-4-20250514".to_string()),
3088 skills: Some(vec!["tdd".to_string()]),
3089 memory: Some("project".to_string()),
3090 mcp_servers: None,
3091 initial_prompt: Some("Start coding".to_string()),
3092 max_turns: Some(50),
3093 background: Some(true),
3094 effort: Some(Effort::High),
3095 permission_mode: Some(PermissionMode::DontAsk),
3096 };
3097
3098 let json = serde_json::to_value(&agent).unwrap();
3099 assert_eq!(json["disallowedTools"], serde_json::json!(["Write"]));
3101 assert_eq!(json["initialPrompt"], "Start coding");
3102 assert_eq!(json["maxTurns"], 50);
3103 assert_eq!(json["background"], true);
3104 assert_eq!(json["effort"], "high");
3105 assert_eq!(json["permissionMode"], "dontAsk");
3106
3107 let back: AgentDefinition = serde_json::from_value(json).unwrap();
3109 assert_eq!(back.disallowed_tools, Some(vec!["Write".to_string()]));
3110 assert_eq!(back.initial_prompt, Some("Start coding".to_string()));
3111 assert_eq!(back.max_turns, Some(50));
3112 assert_eq!(back.background, Some(true));
3113 assert_eq!(back.effort, Some(Effort::High));
3114 assert_eq!(back.permission_mode, Some(PermissionMode::DontAsk));
3115 }
3116
3117 #[test]
3118 fn test_agent_definition_backward_compat_minimal() {
3119 let json = serde_json::json!({
3121 "description": "Agent",
3122 "prompt": "Do stuff"
3123 });
3124 let agent: AgentDefinition = serde_json::from_value(json).unwrap();
3125 assert!(agent.disallowed_tools.is_none());
3126 assert!(agent.initial_prompt.is_none());
3127 assert!(agent.max_turns.is_none());
3128 assert!(agent.background.is_none());
3129 assert!(agent.effort.is_none());
3130 assert!(agent.permission_mode.is_none());
3131 }
3132
3133 #[test]
3136 fn test_sdk_control_get_context_usage_request() {
3137 let req = SDKControlGetContextUsageRequest::new();
3138 assert_eq!(req.subtype, "get_context_usage");
3139 let json = serde_json::to_value(&req).unwrap();
3140 assert_eq!(json["subtype"], "get_context_usage");
3141 }
3142
3143 #[test]
3144 fn test_sdk_control_stop_task_request() {
3145 let req = SDKControlStopTaskRequest::new("task-abc-123");
3146 assert_eq!(req.subtype, "stop_task");
3147 assert_eq!(req.task_id, "task-abc-123");
3148 let json = serde_json::to_value(&req).unwrap();
3149 assert_eq!(json["task_id"], "task-abc-123");
3150 }
3151
3152 #[test]
3153 fn test_sdk_control_mcp_status_request() {
3154 let req = SDKControlMcpStatusRequest::new();
3155 assert_eq!(req.subtype, "mcp_status");
3156 }
3157
3158 #[test]
3159 fn test_sdk_control_mcp_reconnect_request() {
3160 let req = SDKControlMcpReconnectRequest::new("my-server");
3161 assert_eq!(req.subtype, "mcp_reconnect");
3162 assert_eq!(req.server_name, "my-server");
3163 }
3164
3165 #[test]
3166 fn test_sdk_control_mcp_toggle_request() {
3167 let req = SDKControlMcpToggleRequest::new("my-server", false);
3168 assert_eq!(req.subtype, "mcp_toggle");
3169 assert_eq!(req.server_name, "my-server");
3170 assert!(!req.enabled);
3171 }
3172
3173 #[test]
3176 fn test_context_usage_response_deserialize() {
3177 let json = serde_json::json!({
3178 "categories": [
3179 {"name": "system", "tokenCount": 500, "percentage": 10.0},
3180 {"name": "conversation", "tokenCount": 3000, "percentage": 60.0}
3181 ],
3182 "totalTokens": 5000,
3183 "maxTokens": 200000,
3184 "percentage": 2.5,
3185 "model": "claude-sonnet-4-20250514",
3186 "isAutoCompactEnabled": true,
3187 "autoCompactThreshold": 180000,
3188 "memoryFiles": [],
3189 "mcpTools": [],
3190 "apiUsage": {
3191 "inputTokens": 4000,
3192 "outputTokens": 1000,
3193 "cacheReadInputTokens": 2000,
3194 "cacheCreationInputTokens": 500
3195 }
3196 });
3197
3198 let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3199 assert_eq!(resp.categories.len(), 2);
3200 assert_eq!(resp.categories[0].name, "system");
3201 assert_eq!(resp.categories[0].token_count, 500);
3202 assert_eq!(resp.total_tokens, 5000);
3203 assert_eq!(resp.max_tokens, 200000);
3204 assert_eq!(resp.model, "claude-sonnet-4-20250514");
3205 assert!(resp.is_auto_compact_enabled);
3206 assert_eq!(resp.auto_compact_threshold, Some(180000));
3207
3208 let api = resp.api_usage.unwrap();
3209 assert_eq!(api.input_tokens, 4000);
3210 assert_eq!(api.output_tokens, 1000);
3211 assert_eq!(api.cache_read_input_tokens, 2000);
3212 assert_eq!(api.cache_creation_input_tokens, 500);
3213 }
3214
3215 #[test]
3216 fn test_context_usage_response_minimal() {
3217 let json = serde_json::json!({});
3219 let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3220 assert_eq!(resp.total_tokens, 0);
3221 assert!(resp.categories.is_empty());
3222 assert!(resp.api_usage.is_none());
3223 }
3224
3225 #[test]
3226 fn test_task_budget_serialization() {
3227 let budget = TaskBudget {
3228 max_cost_usd: Some(5.0),
3229 max_tokens: Some(100_000),
3230 max_turns: Some(20),
3231 };
3232 let json = serde_json::to_value(&budget).unwrap();
3233 assert_eq!(json["maxCostUsd"], 5.0);
3234 assert_eq!(json["maxTokens"], 100_000);
3235 assert_eq!(json["maxTurns"], 20);
3236
3237 let back: TaskBudget = serde_json::from_value(json).unwrap();
3238 assert_eq!(back.max_cost_usd, Some(5.0));
3239 assert_eq!(back.max_tokens, Some(100_000));
3240 assert_eq!(back.max_turns, Some(20));
3241 }
3242
3243 #[test]
3244 fn test_fork_session_result_deserialize() {
3245 let json = serde_json::json!({"sessionId": "sess-forked-abc"});
3246 let result: ForkSessionResult = serde_json::from_value(json).unwrap();
3247 assert_eq!(result.session_id, "sess-forked-abc");
3248 }
3249
3250 #[test]
3253 fn test_sdk_control_request_new_variants_serialize() {
3254 let req = SDKControlRequest::GetContextUsage(SDKControlGetContextUsageRequest::new());
3255 let json = serde_json::to_value(&req).unwrap();
3256 assert_eq!(json["type"], "get_context_usage");
3257
3258 let req = SDKControlRequest::StopTask(SDKControlStopTaskRequest::new("t1"));
3259 let json = serde_json::to_value(&req).unwrap();
3260 assert_eq!(json["type"], "stop_task");
3261 assert_eq!(json["task_id"], "t1");
3262
3263 let req = SDKControlRequest::McpStatus(SDKControlMcpStatusRequest::new());
3264 let json = serde_json::to_value(&req).unwrap();
3265 assert_eq!(json["type"], "mcp_status");
3266
3267 let req = SDKControlRequest::McpReconnect(SDKControlMcpReconnectRequest::new("srv"));
3268 let json = serde_json::to_value(&req).unwrap();
3269 assert_eq!(json["type"], "mcp_reconnect");
3270
3271 let req = SDKControlRequest::McpToggle(SDKControlMcpToggleRequest::new("srv", true));
3272 let json = serde_json::to_value(&req).unwrap();
3273 assert_eq!(json["type"], "mcp_toggle");
3274 assert_eq!(json["enabled"], true);
3275 }
3276}