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 pub session_id: Option<String>,
1449}
1450
1451impl std::fmt::Debug for ClaudeCodeOptions {
1452 #[allow(deprecated)]
1453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1454 f.debug_struct("ClaudeCodeOptions")
1455 .field("system_prompt", &self.system_prompt)
1456 .field("append_system_prompt", &self.append_system_prompt)
1457 .field("allowed_tools", &self.allowed_tools)
1458 .field("disallowed_tools", &self.disallowed_tools)
1459 .field("permission_mode", &self.permission_mode)
1460 .field("mcp_servers", &self.mcp_servers)
1461 .field("mcp_tools", &self.mcp_tools)
1462 .field("max_turns", &self.max_turns)
1463 .field("max_thinking_tokens", &self.max_thinking_tokens)
1464 .field("max_output_tokens", &self.max_output_tokens)
1465 .field("model", &self.model)
1466 .field("cwd", &self.cwd)
1467 .field("continue_conversation", &self.continue_conversation)
1468 .field("resume", &self.resume)
1469 .field("permission_prompt_tool_name", &self.permission_prompt_tool_name)
1470 .field("settings", &self.settings)
1471 .field("add_dirs", &self.add_dirs)
1472 .field("extra_args", &self.extra_args)
1473 .field("env", &self.env)
1474 .field("debug_stderr", &self.debug_stderr.is_some())
1475 .field("include_partial_messages", &self.include_partial_messages)
1476 .field("can_use_tool", &self.can_use_tool.is_some())
1477 .field("hooks", &self.hooks.is_some())
1478 .field("control_protocol_format", &self.control_protocol_format)
1479 .field("effort", &self.effort)
1480 .field("thinking", &self.thinking)
1481 .field("session_id", &self.session_id)
1482 .finish()
1483 }
1484}
1485
1486impl ClaudeCodeOptions {
1487 pub fn builder() -> ClaudeCodeOptionsBuilder {
1489 ClaudeCodeOptionsBuilder::default()
1490 }
1491}
1492
1493#[derive(Debug, Default)]
1495pub struct ClaudeCodeOptionsBuilder {
1496 options: ClaudeCodeOptions,
1497}
1498
1499impl ClaudeCodeOptionsBuilder {
1500 #[allow(deprecated)]
1502 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
1503 self.options.system_prompt = Some(prompt.into());
1504 self
1505 }
1506
1507 #[allow(deprecated)]
1509 pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1510 self.options.append_system_prompt = Some(prompt.into());
1511 self
1512 }
1513
1514 pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1522 self.options.allowed_tools = tools;
1523 self
1524 }
1525
1526 pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
1530 self.options.allowed_tools.push(tool.into());
1531 self
1532 }
1533
1534 pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1543 self.options.disallowed_tools = tools;
1544 self
1545 }
1546
1547 pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
1551 self.options.disallowed_tools.push(tool.into());
1552 self
1553 }
1554
1555 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
1557 self.options.permission_mode = mode;
1558 self
1559 }
1560
1561 pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
1563 self.options.mcp_servers.insert(name.into(), config);
1564 self
1565 }
1566
1567 pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
1569 self.options.mcp_servers = servers;
1570 self
1571 }
1572
1573 pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
1575 self.options.mcp_tools = tools;
1576 self
1577 }
1578
1579 pub fn max_turns(mut self, turns: i32) -> Self {
1581 self.options.max_turns = Some(turns);
1582 self
1583 }
1584
1585 pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
1587 self.options.max_thinking_tokens = Some(tokens);
1588 self
1589 }
1590
1591 pub fn max_output_tokens(mut self, tokens: u32) -> Self {
1593 self.options.max_output_tokens = Some(tokens.clamp(1, 32000));
1594 self
1595 }
1596
1597 pub fn model(mut self, model: impl Into<String>) -> Self {
1599 self.options.model = Some(model.into());
1600 self
1601 }
1602
1603 pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
1605 self.options.cwd = Some(path.into());
1606 self
1607 }
1608
1609 pub fn continue_conversation(mut self, enable: bool) -> Self {
1611 self.options.continue_conversation = enable;
1612 self
1613 }
1614
1615 pub fn resume(mut self, id: impl Into<String>) -> Self {
1617 self.options.resume = Some(id.into());
1618 self
1619 }
1620
1621 pub fn session_id(mut self, id: impl Into<String>) -> Self {
1627 self.options.session_id = Some(id.into());
1628 self
1629 }
1630
1631 pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
1633 self.options.permission_prompt_tool_name = Some(name.into());
1634 self
1635 }
1636
1637 pub fn settings(mut self, settings: impl Into<String>) -> Self {
1639 self.options.settings = Some(settings.into());
1640 self
1641 }
1642
1643 pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
1645 self.options.add_dirs = dirs;
1646 self
1647 }
1648
1649 pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
1651 self.options.add_dirs.push(dir.into());
1652 self
1653 }
1654
1655 pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
1657 self.options.extra_args = args;
1658 self
1659 }
1660
1661 pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
1663 self.options.extra_args.insert(key.into(), value);
1664 self
1665 }
1666
1667 pub fn control_protocol_format(mut self, format: ControlProtocolFormat) -> Self {
1669 self.options.control_protocol_format = format;
1670 self
1671 }
1672
1673 pub fn include_partial_messages(mut self, include: bool) -> Self {
1675 self.options.include_partial_messages = include;
1676 self
1677 }
1678
1679 pub fn fork_session(mut self, fork: bool) -> Self {
1681 self.options.fork_session = fork;
1682 self
1683 }
1684
1685 pub fn setting_sources(mut self, sources: Vec<SettingSource>) -> Self {
1687 self.options.setting_sources = Some(sources);
1688 self
1689 }
1690
1691 pub fn agents(mut self, agents: HashMap<String, AgentDefinition>) -> Self {
1693 self.options.agents = Some(agents);
1694 self
1695 }
1696
1697 pub fn cli_channel_buffer_size(mut self, size: usize) -> Self {
1715 self.options.cli_channel_buffer_size = Some(size);
1716 self
1717 }
1718
1719 pub fn tools(mut self, config: ToolsConfig) -> Self {
1736 self.options.tools = Some(config);
1737 self
1738 }
1739
1740 pub fn betas(mut self, betas: Vec<SdkBeta>) -> Self {
1744 self.options.betas = betas;
1745 self
1746 }
1747
1748 pub fn add_beta(mut self, beta: SdkBeta) -> Self {
1750 self.options.betas.push(beta);
1751 self
1752 }
1753
1754 pub fn max_budget_usd(mut self, budget: f64) -> Self {
1758 self.options.max_budget_usd = Some(budget);
1759 self
1760 }
1761
1762 pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
1766 self.options.fallback_model = Some(model.into());
1767 self
1768 }
1769
1770 pub fn output_format(mut self, format: serde_json::Value) -> Self {
1791 self.options.output_format = Some(format);
1792 self
1793 }
1794
1795 pub fn enable_file_checkpointing(mut self, enable: bool) -> Self {
1800 self.options.enable_file_checkpointing = enable;
1801 self
1802 }
1803
1804 pub fn sandbox(mut self, settings: SandboxSettings) -> Self {
1808 self.options.sandbox = Some(settings);
1809 self
1810 }
1811
1812 pub fn plugins(mut self, plugins: Vec<SdkPluginConfig>) -> Self {
1814 self.options.plugins = plugins;
1815 self
1816 }
1817
1818 pub fn add_plugin(mut self, plugin: SdkPluginConfig) -> Self {
1820 self.options.plugins.push(plugin);
1821 self
1822 }
1823
1824 pub fn user(mut self, user: impl Into<String>) -> Self {
1826 self.options.user = Some(user.into());
1827 self
1828 }
1829
1830 pub fn stderr_callback(mut self, callback: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
1834 self.options.stderr_callback = Some(callback);
1835 self
1836 }
1837
1838 pub fn auto_download_cli(mut self, enable: bool) -> Self {
1852 self.options.auto_download_cli = enable;
1853 self
1854 }
1855
1856 pub fn effort(mut self, effort: Effort) -> Self {
1869 self.options.effort = Some(effort);
1870 self
1871 }
1872
1873 pub fn thinking(mut self, config: ThinkingConfig) -> Self {
1886 self.options.thinking = Some(config);
1887 self
1888 }
1889
1890 pub fn build(self) -> ClaudeCodeOptions {
1892 self.options
1893 }
1894}
1895
1896#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1902pub struct TaskUsage {
1903 #[serde(default)]
1905 pub total_tokens: u64,
1906 #[serde(default)]
1908 pub tool_uses: u64,
1909 #[serde(default)]
1911 pub duration_ms: u64,
1912}
1913
1914#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1916#[serde(rename_all = "lowercase")]
1917pub enum TaskStatus {
1918 Completed,
1920 Failed,
1922 Stopped,
1924}
1925
1926#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1928pub struct TaskStartedMessage {
1929 pub task_id: String,
1931 pub description: String,
1933 pub uuid: String,
1935 pub session_id: String,
1937 #[serde(default, skip_serializing_if = "Option::is_none")]
1939 pub tool_use_id: Option<String>,
1940 #[serde(default, skip_serializing_if = "Option::is_none")]
1942 pub task_type: Option<String>,
1943}
1944
1945#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1947pub struct TaskProgressMessage {
1948 pub task_id: String,
1950 pub description: String,
1952 #[serde(default)]
1954 pub usage: TaskUsage,
1955 pub uuid: String,
1957 pub session_id: String,
1959 #[serde(default, skip_serializing_if = "Option::is_none")]
1961 pub tool_use_id: Option<String>,
1962 #[serde(default, skip_serializing_if = "Option::is_none")]
1964 pub last_tool_name: Option<String>,
1965}
1966
1967#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1969pub struct TaskNotificationMessage {
1970 pub task_id: String,
1972 pub status: TaskStatus,
1974 #[serde(default, skip_serializing_if = "Option::is_none")]
1976 pub output_file: Option<String>,
1977 #[serde(default, skip_serializing_if = "Option::is_none")]
1979 pub summary: Option<String>,
1980 pub uuid: String,
1982 pub session_id: String,
1984 #[serde(default, skip_serializing_if = "Option::is_none")]
1986 pub tool_use_id: Option<String>,
1987 #[serde(default, skip_serializing_if = "Option::is_none")]
1989 pub usage: Option<TaskUsage>,
1990}
1991
1992#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1994#[non_exhaustive]
1995#[serde(tag = "type", rename_all = "lowercase")]
1996pub enum Message {
1997 User {
1999 message: UserMessage,
2001 },
2002 Assistant {
2004 message: AssistantMessage,
2006 },
2007 System {
2009 subtype: String,
2011 data: serde_json::Value,
2013 },
2014 Result {
2016 subtype: String,
2018 duration_ms: i64,
2020 duration_api_ms: i64,
2022 is_error: bool,
2024 num_turns: i32,
2026 session_id: String,
2028 #[serde(skip_serializing_if = "Option::is_none")]
2030 total_cost_usd: Option<f64>,
2031 #[serde(skip_serializing_if = "Option::is_none")]
2033 usage: Option<serde_json::Value>,
2034 #[serde(skip_serializing_if = "Option::is_none")]
2036 result: Option<String>,
2037 #[serde(skip_serializing_if = "Option::is_none", alias = "structuredOutput")]
2040 structured_output: Option<serde_json::Value>,
2041 #[serde(default, skip_serializing_if = "Option::is_none")]
2043 stop_reason: Option<String>,
2044 },
2045
2046 #[serde(rename = "stream_event")]
2048 StreamEvent {
2049 uuid: String,
2051 session_id: String,
2053 event: serde_json::Value,
2055 #[serde(default, skip_serializing_if = "Option::is_none")]
2057 parent_tool_use_id: Option<String>,
2058 },
2059
2060 #[serde(rename = "rate_limit")]
2062 RateLimit {
2063 rate_limit_info: RateLimitInfo,
2065 uuid: String,
2067 session_id: String,
2069 },
2070
2071 #[serde(skip)]
2074 Unknown {
2075 msg_type: String,
2077 raw: serde_json::Value,
2079 },
2080}
2081
2082impl Message {
2083 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
2085 if let Message::System { subtype, data } = self {
2086 if subtype == "task_started" {
2087 return serde_json::from_value(data.clone()).ok();
2088 }
2089 }
2090 None
2091 }
2092
2093 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
2095 if let Message::System { subtype, data } = self {
2096 if subtype == "task_progress" {
2097 return serde_json::from_value(data.clone()).ok();
2098 }
2099 }
2100 None
2101 }
2102
2103 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
2105 if let Message::System { subtype, data } = self {
2106 if subtype == "task_notification" {
2107 return serde_json::from_value(data.clone()).ok();
2108 }
2109 }
2110 None
2111 }
2112}
2113
2114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2116pub struct UserMessage {
2117 pub content: String,
2119}
2120
2121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2123pub struct AssistantMessage {
2124 pub content: Vec<ContentBlock>,
2126 #[serde(default, skip_serializing_if = "Option::is_none")]
2128 pub model: Option<String>,
2129 #[serde(default, skip_serializing_if = "Option::is_none")]
2131 pub usage: Option<serde_json::Value>,
2132 #[serde(default, skip_serializing_if = "Option::is_none")]
2134 pub error: Option<AssistantMessageError>,
2135 #[serde(default, skip_serializing_if = "Option::is_none")]
2137 pub parent_tool_use_id: Option<String>,
2138}
2139
2140pub use Message::Result as ResultMessage;
2142pub use Message::System as SystemMessage;
2144
2145#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2147#[serde(untagged)]
2148pub enum ContentBlock {
2149 Text(TextContent),
2151 Thinking(ThinkingContent),
2153 ToolUse(ToolUseContent),
2155 ToolResult(ToolResultContent),
2157}
2158
2159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2161pub struct TextContent {
2162 pub text: String,
2164}
2165
2166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2168pub struct ThinkingContent {
2169 pub thinking: String,
2171 pub signature: String,
2173}
2174
2175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2177pub struct ToolUseContent {
2178 pub id: String,
2180 pub name: String,
2182 pub input: serde_json::Value,
2184}
2185
2186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2188pub struct ToolResultContent {
2189 pub tool_use_id: String,
2191 #[serde(skip_serializing_if = "Option::is_none")]
2193 pub content: Option<ContentValue>,
2194 #[serde(skip_serializing_if = "Option::is_none")]
2196 pub is_error: Option<bool>,
2197}
2198
2199#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2201#[serde(untagged)]
2202pub enum ContentValue {
2203 Text(String),
2205 Structured(Vec<serde_json::Value>),
2207}
2208
2209#[derive(Debug, Clone, Serialize, Deserialize)]
2211pub struct UserContent {
2212 pub role: String,
2214 pub content: String,
2216}
2217
2218#[derive(Debug, Clone, Serialize, Deserialize)]
2220pub struct AssistantContent {
2221 pub role: String,
2223 pub content: Vec<ContentBlock>,
2225}
2226
2227#[derive(Debug, Clone, Serialize, Deserialize)]
2229pub struct SDKControlInterruptRequest {
2230 pub subtype: String, }
2233
2234#[derive(Debug, Clone, Serialize, Deserialize)]
2236pub struct SDKControlPermissionRequest {
2237 pub subtype: String, #[serde(alias = "toolName")]
2241 pub tool_name: String,
2242 pub input: serde_json::Value,
2244 #[serde(skip_serializing_if = "Option::is_none", alias = "permissionSuggestions")]
2246 pub permission_suggestions: Option<Vec<PermissionUpdate>>,
2247 #[serde(skip_serializing_if = "Option::is_none", alias = "blockedPath")]
2249 pub blocked_path: Option<String>,
2250}
2251
2252#[derive(Debug, Clone, Serialize, Deserialize)]
2254pub struct SDKControlInitializeRequest {
2255 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
2259 pub hooks: Option<HashMap<String, serde_json::Value>>,
2260}
2261
2262#[derive(Debug, Clone, Serialize, Deserialize)]
2264#[serde(rename_all = "camelCase")]
2265pub struct SDKControlSetPermissionModeRequest {
2266 pub subtype: String, pub mode: String,
2270}
2271
2272#[derive(Debug, Clone, Serialize, Deserialize)]
2274#[serde(rename_all = "camelCase")]
2275pub struct SDKControlSetModelRequest {
2276 pub subtype: String, #[serde(skip_serializing_if = "Option::is_none")]
2280 pub model: Option<String>,
2281}
2282
2283#[derive(Debug, Clone, Serialize, Deserialize)]
2285pub struct SDKHookCallbackRequest {
2286 pub subtype: String, #[serde(alias = "callbackId")]
2290 pub callback_id: String,
2291 pub input: serde_json::Value,
2293 #[serde(skip_serializing_if = "Option::is_none", alias = "toolUseId")]
2295 pub tool_use_id: Option<String>,
2296}
2297
2298#[derive(Debug, Clone, Serialize, Deserialize)]
2300pub struct SDKControlMcpMessageRequest {
2301 pub subtype: String, #[serde(rename = "server_name", alias = "mcpServerName", alias = "mcp_server_name")]
2305 pub mcp_server_name: String,
2306 pub message: serde_json::Value,
2308}
2309
2310#[derive(Debug, Clone, Serialize, Deserialize)]
2315pub struct SDKControlRewindFilesRequest {
2316 pub subtype: String,
2318 #[serde(alias = "userMessageId")]
2320 pub user_message_id: String,
2321}
2322
2323impl SDKControlRewindFilesRequest {
2324 pub fn new(user_message_id: impl Into<String>) -> Self {
2326 Self {
2327 subtype: "rewind_files".to_string(),
2328 user_message_id: user_message_id.into(),
2329 }
2330 }
2331}
2332
2333#[derive(Debug, Clone, Serialize, Deserialize)]
2335pub struct SDKControlGetContextUsageRequest {
2336 pub subtype: String,
2338}
2339
2340impl SDKControlGetContextUsageRequest {
2341 pub fn new() -> Self {
2343 Self {
2344 subtype: "get_context_usage".to_string(),
2345 }
2346 }
2347}
2348
2349#[derive(Debug, Clone, Serialize, Deserialize)]
2351pub struct SDKControlStopTaskRequest {
2352 pub subtype: String,
2354 #[serde(alias = "taskId")]
2356 pub task_id: String,
2357}
2358
2359impl SDKControlStopTaskRequest {
2360 pub fn new(task_id: impl Into<String>) -> Self {
2362 Self {
2363 subtype: "stop_task".to_string(),
2364 task_id: task_id.into(),
2365 }
2366 }
2367}
2368
2369#[derive(Debug, Clone, Serialize, Deserialize)]
2371pub struct SDKControlMcpStatusRequest {
2372 pub subtype: String,
2374}
2375
2376impl SDKControlMcpStatusRequest {
2377 pub fn new() -> Self {
2379 Self {
2380 subtype: "mcp_status".to_string(),
2381 }
2382 }
2383}
2384
2385#[derive(Debug, Clone, Serialize, Deserialize)]
2387pub struct SDKControlMcpReconnectRequest {
2388 pub subtype: String,
2390 #[serde(alias = "serverName")]
2392 pub server_name: String,
2393}
2394
2395impl SDKControlMcpReconnectRequest {
2396 pub fn new(server_name: impl Into<String>) -> Self {
2398 Self {
2399 subtype: "mcp_reconnect".to_string(),
2400 server_name: server_name.into(),
2401 }
2402 }
2403}
2404
2405#[derive(Debug, Clone, Serialize, Deserialize)]
2407pub struct SDKControlMcpToggleRequest {
2408 pub subtype: String,
2410 #[serde(alias = "serverName")]
2412 pub server_name: String,
2413 pub enabled: bool,
2415}
2416
2417impl SDKControlMcpToggleRequest {
2418 pub fn new(server_name: impl Into<String>, enabled: bool) -> Self {
2420 Self {
2421 subtype: "mcp_toggle".to_string(),
2422 server_name: server_name.into(),
2423 enabled,
2424 }
2425 }
2426}
2427
2428#[derive(Debug, Clone, Serialize, Deserialize)]
2430#[serde(rename_all = "camelCase")]
2431pub struct ContextUsageCategory {
2432 pub name: String,
2434 pub token_count: u64,
2436 #[serde(default)]
2438 pub percentage: f64,
2439}
2440
2441#[derive(Debug, Clone, Serialize, Deserialize)]
2443#[serde(rename_all = "camelCase")]
2444pub struct ApiUsage {
2445 #[serde(default)]
2447 pub input_tokens: u64,
2448 #[serde(default)]
2450 pub output_tokens: u64,
2451 #[serde(default)]
2453 pub cache_read_input_tokens: u64,
2454 #[serde(default)]
2456 pub cache_creation_input_tokens: u64,
2457}
2458
2459#[derive(Debug, Clone, Serialize, Deserialize)]
2461#[serde(rename_all = "camelCase")]
2462pub struct ContextUsageResponse {
2463 #[serde(default)]
2465 pub categories: Vec<ContextUsageCategory>,
2466 #[serde(default)]
2468 pub total_tokens: u64,
2469 #[serde(default)]
2471 pub max_tokens: u64,
2472 #[serde(default)]
2474 pub percentage: f64,
2475 #[serde(default)]
2477 pub model: String,
2478 #[serde(default)]
2480 pub is_auto_compact_enabled: bool,
2481 #[serde(default, skip_serializing_if = "Option::is_none")]
2483 pub auto_compact_threshold: Option<u64>,
2484 #[serde(default)]
2486 pub memory_files: Vec<serde_json::Value>,
2487 #[serde(default)]
2489 pub mcp_tools: Vec<serde_json::Value>,
2490 #[serde(default, skip_serializing_if = "Option::is_none")]
2492 pub message_breakdown: Option<serde_json::Value>,
2493 #[serde(default, skip_serializing_if = "Option::is_none")]
2495 pub api_usage: Option<ApiUsage>,
2496}
2497
2498#[derive(Debug, Clone, Serialize, Deserialize)]
2500#[serde(rename_all = "camelCase")]
2501pub struct TaskBudget {
2502 #[serde(default, skip_serializing_if = "Option::is_none")]
2504 pub max_cost_usd: Option<f64>,
2505 #[serde(default, skip_serializing_if = "Option::is_none")]
2507 pub max_tokens: Option<u64>,
2508 #[serde(default, skip_serializing_if = "Option::is_none")]
2510 pub max_turns: Option<i32>,
2511}
2512
2513#[derive(Debug, Clone, Serialize, Deserialize)]
2515#[serde(rename_all = "camelCase")]
2516pub struct ForkSessionResult {
2517 pub session_id: String,
2519}
2520
2521#[derive(Debug, Clone, Serialize, Deserialize)]
2523#[serde(tag = "type", rename_all = "snake_case")]
2524pub enum SDKControlRequest {
2525 #[serde(rename = "interrupt")]
2527 Interrupt(SDKControlInterruptRequest),
2528 #[serde(rename = "can_use_tool")]
2530 CanUseTool(SDKControlPermissionRequest),
2531 #[serde(rename = "initialize")]
2533 Initialize(SDKControlInitializeRequest),
2534 #[serde(rename = "set_permission_mode")]
2536 SetPermissionMode(SDKControlSetPermissionModeRequest),
2537 #[serde(rename = "set_model")]
2539 SetModel(SDKControlSetModelRequest),
2540 #[serde(rename = "hook_callback")]
2542 HookCallback(SDKHookCallbackRequest),
2543 #[serde(rename = "mcp_message")]
2545 McpMessage(SDKControlMcpMessageRequest),
2546 #[serde(rename = "rewind_files")]
2548 RewindFiles(SDKControlRewindFilesRequest),
2549 #[serde(rename = "get_context_usage")]
2551 GetContextUsage(SDKControlGetContextUsageRequest),
2552 #[serde(rename = "stop_task")]
2554 StopTask(SDKControlStopTaskRequest),
2555 #[serde(rename = "mcp_status")]
2557 McpStatus(SDKControlMcpStatusRequest),
2558 #[serde(rename = "mcp_reconnect")]
2560 McpReconnect(SDKControlMcpReconnectRequest),
2561 #[serde(rename = "mcp_toggle")]
2563 McpToggle(SDKControlMcpToggleRequest),
2564}
2565
2566#[derive(Debug, Clone, Serialize, Deserialize)]
2568#[serde(tag = "type", rename_all = "lowercase")]
2569pub enum ControlRequest {
2570 Interrupt {
2572 request_id: String,
2574 },
2575}
2576
2577#[derive(Debug, Clone, Serialize, Deserialize)]
2579#[serde(tag = "type", rename_all = "lowercase")]
2580pub enum ControlResponse {
2581 InterruptAck {
2583 request_id: String,
2585 success: bool,
2587 },
2588}
2589
2590#[cfg(test)]
2591mod tests {
2592 use super::*;
2593
2594 #[test]
2595 fn test_permission_mode_serialization() {
2596 let mode = PermissionMode::AcceptEdits;
2597 let json = serde_json::to_string(&mode).unwrap();
2598 assert_eq!(json, r#""acceptEdits""#);
2599
2600 let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
2601 assert_eq!(deserialized, mode);
2602
2603 let plan_mode = PermissionMode::Plan;
2605 let plan_json = serde_json::to_string(&plan_mode).unwrap();
2606 assert_eq!(plan_json, r#""plan""#);
2607
2608 let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
2609 assert_eq!(plan_deserialized, plan_mode);
2610 }
2611
2612 #[test]
2613 fn test_message_serialization() {
2614 let msg = Message::User {
2615 message: UserMessage {
2616 content: "Hello".to_string(),
2617 },
2618 };
2619
2620 let json = serde_json::to_string(&msg).unwrap();
2621 assert!(json.contains(r#""type":"user""#));
2622 assert!(json.contains(r#""content":"Hello""#));
2623
2624 let deserialized: Message = serde_json::from_str(&json).unwrap();
2625 assert_eq!(deserialized, msg);
2626 }
2627
2628 #[test]
2629 #[allow(deprecated)]
2630 fn test_options_builder() {
2631 let options = ClaudeCodeOptions::builder()
2632 .system_prompt("Test prompt")
2633 .model("claude-3-opus")
2634 .permission_mode(PermissionMode::AcceptEdits)
2635 .allow_tool("read")
2636 .allow_tool("write")
2637 .max_turns(10)
2638 .build();
2639
2640 assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
2641 assert_eq!(options.model, Some("claude-3-opus".to_string()));
2642 assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
2643 assert_eq!(options.allowed_tools, vec!["read", "write"]);
2644 assert_eq!(options.max_turns, Some(10));
2645 }
2646
2647 #[test]
2648 fn test_extra_args() {
2649 let mut extra_args = HashMap::new();
2650 extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
2651 extra_args.insert("boolean-flag".to_string(), None);
2652
2653 let options = ClaudeCodeOptions::builder()
2654 .extra_args(extra_args.clone())
2655 .add_extra_arg("another-flag", Some("another-value".to_string()))
2656 .build();
2657
2658 assert_eq!(options.extra_args.len(), 3);
2659 assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
2660 assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
2661 assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
2662 }
2663
2664 #[test]
2665 fn test_thinking_content_serialization() {
2666 let thinking = ThinkingContent {
2667 thinking: "Let me think about this...".to_string(),
2668 signature: "sig123".to_string(),
2669 };
2670
2671 let json = serde_json::to_string(&thinking).unwrap();
2672 assert!(json.contains(r#""thinking":"Let me think about this...""#));
2673 assert!(json.contains(r#""signature":"sig123""#));
2674
2675 let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
2676 assert_eq!(deserialized.thinking, thinking.thinking);
2677 assert_eq!(deserialized.signature, thinking.signature);
2678 }
2679
2680 #[test]
2683 fn test_tools_config_list_serialization() {
2684 let tools = ToolsConfig::List(vec!["Read".to_string(), "Write".to_string(), "Bash".to_string()]);
2685 let json = serde_json::to_string(&tools).unwrap();
2686
2687 assert!(json.contains("Read"));
2689 assert!(json.contains("Write"));
2690 assert!(json.contains("Bash"));
2691
2692 let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2693 match deserialized {
2694 ToolsConfig::List(list) => {
2695 assert_eq!(list.len(), 3);
2696 assert!(list.contains(&"Read".to_string()));
2697 }
2698 _ => panic!("Expected List variant"),
2699 }
2700 }
2701
2702 #[test]
2703 fn test_tools_config_preset_serialization() {
2704 let preset = ToolsConfig::claude_code_preset();
2706 let json = serde_json::to_string(&preset).unwrap();
2707 assert!(json.contains("preset"));
2708 assert!(json.contains("claude_code"));
2709
2710 let custom_preset = ToolsConfig::Preset(ToolsPreset {
2712 preset_type: "preset".to_string(),
2713 preset: "custom".to_string(),
2714 });
2715 let json = serde_json::to_string(&custom_preset).unwrap();
2716 assert!(json.contains("custom"));
2717
2718 let deserialized: ToolsConfig = serde_json::from_str(&json).unwrap();
2720 match deserialized {
2721 ToolsConfig::Preset(p) => assert_eq!(p.preset, "custom"),
2722 _ => panic!("Expected Preset variant"),
2723 }
2724 }
2725
2726 #[test]
2727 fn test_tools_config_helper_methods() {
2728 let tools = ToolsConfig::list(vec!["Read".to_string(), "Write".to_string()]);
2730 match tools {
2731 ToolsConfig::List(list) => assert_eq!(list.len(), 2),
2732 _ => panic!("Expected List variant"),
2733 }
2734
2735 let empty = ToolsConfig::none();
2737 match empty {
2738 ToolsConfig::List(list) => assert!(list.is_empty()),
2739 _ => panic!("Expected empty List variant"),
2740 }
2741
2742 let preset = ToolsConfig::claude_code_preset();
2744 match preset {
2745 ToolsConfig::Preset(p) => {
2746 assert_eq!(p.preset_type, "preset");
2747 assert_eq!(p.preset, "claude_code");
2748 }
2749 _ => panic!("Expected Preset variant"),
2750 }
2751 }
2752
2753 #[test]
2754 fn test_sdk_beta_serialization() {
2755 let beta = SdkBeta::Context1M;
2756 let json = serde_json::to_string(&beta).unwrap();
2757 assert_eq!(json, r#""context-1m-2025-08-07""#);
2759
2760 let display = format!("{}", beta);
2762 assert_eq!(display, "context-1m-2025-08-07");
2763
2764 let deserialized: SdkBeta = serde_json::from_str(r#""context-1m-2025-08-07""#).unwrap();
2766 assert!(matches!(deserialized, SdkBeta::Context1M));
2767 }
2768
2769 #[test]
2770 fn test_sandbox_settings_serialization() {
2771 let sandbox = SandboxSettings {
2772 enabled: Some(true),
2773 auto_allow_bash_if_sandboxed: Some(true),
2774 excluded_commands: Some(vec!["git".to_string(), "docker".to_string()]),
2775 allow_unsandboxed_commands: Some(false),
2776 network: Some(SandboxNetworkConfig {
2777 allow_unix_sockets: Some(vec!["/tmp/ssh-agent.sock".to_string()]),
2778 allow_all_unix_sockets: Some(false),
2779 allow_local_binding: Some(true),
2780 http_proxy_port: Some(8080),
2781 socks_proxy_port: Some(1080),
2782 }),
2783 ignore_violations: Some(SandboxIgnoreViolations {
2784 file: Some(vec!["/tmp".to_string(), "/var/log".to_string()]),
2785 network: Some(vec!["localhost".to_string()]),
2786 }),
2787 enable_weaker_nested_sandbox: Some(false),
2788 };
2789
2790 let json = serde_json::to_string(&sandbox).unwrap();
2791 assert!(json.contains("enabled"));
2792 assert!(json.contains("autoAllowBashIfSandboxed")); assert!(json.contains("excludedCommands"));
2794 assert!(json.contains("httpProxyPort"));
2795 assert!(json.contains("8080"));
2796
2797 let deserialized: SandboxSettings = serde_json::from_str(&json).unwrap();
2798 assert!(deserialized.enabled.unwrap());
2799 assert!(deserialized.network.is_some());
2800 assert_eq!(deserialized.network.as_ref().unwrap().http_proxy_port, Some(8080));
2801 }
2802
2803 #[test]
2804 fn test_sandbox_network_config() {
2805 let config = SandboxNetworkConfig {
2806 allow_unix_sockets: Some(vec!["/run/user/1000/keyring/ssh".to_string()]),
2807 allow_all_unix_sockets: Some(false),
2808 allow_local_binding: Some(true),
2809 http_proxy_port: Some(3128),
2810 socks_proxy_port: Some(1080),
2811 };
2812
2813 let json = serde_json::to_string(&config).unwrap();
2814 let deserialized: SandboxNetworkConfig = serde_json::from_str(&json).unwrap();
2815
2816 assert_eq!(deserialized.http_proxy_port, Some(3128));
2817 assert_eq!(deserialized.socks_proxy_port, Some(1080));
2818 assert_eq!(deserialized.allow_local_binding, Some(true));
2819 }
2820
2821 #[test]
2822 fn test_sandbox_ignore_violations() {
2823 let violations = SandboxIgnoreViolations {
2824 file: Some(vec!["/tmp".to_string(), "/var/cache".to_string()]),
2825 network: Some(vec!["127.0.0.1".to_string(), "localhost".to_string()]),
2826 };
2827
2828 let json = serde_json::to_string(&violations).unwrap();
2829 assert!(json.contains("file"));
2830 assert!(json.contains("/tmp"));
2831
2832 let deserialized: SandboxIgnoreViolations = serde_json::from_str(&json).unwrap();
2833 assert_eq!(deserialized.file.as_ref().unwrap().len(), 2);
2834 assert_eq!(deserialized.network.as_ref().unwrap().len(), 2);
2835 }
2836
2837 #[test]
2838 fn test_sandbox_settings_default() {
2839 let sandbox = SandboxSettings::default();
2840 assert!(sandbox.enabled.is_none());
2841 assert!(sandbox.network.is_none());
2842 assert!(sandbox.ignore_violations.is_none());
2843 }
2844
2845 #[test]
2846 fn test_sdk_plugin_config_serialization() {
2847 let plugin = SdkPluginConfig::Local {
2848 path: "/path/to/plugin".to_string()
2849 };
2850
2851 let json = serde_json::to_string(&plugin).unwrap();
2852 assert!(json.contains("local")); assert!(json.contains("/path/to/plugin"));
2854
2855 let deserialized: SdkPluginConfig = serde_json::from_str(&json).unwrap();
2856 match deserialized {
2857 SdkPluginConfig::Local { path } => {
2858 assert_eq!(path, "/path/to/plugin");
2859 }
2860 }
2861 }
2862
2863 #[test]
2864 fn test_sdk_control_rewind_files_request() {
2865 let request = SDKControlRewindFilesRequest {
2866 subtype: "rewind_files".to_string(),
2867 user_message_id: "msg_12345".to_string(),
2868 };
2869
2870 let json = serde_json::to_string(&request).unwrap();
2871 assert!(json.contains("user_message_id"));
2872 assert!(json.contains("msg_12345"));
2873 assert!(json.contains("subtype"));
2874 assert!(json.contains("rewind_files"));
2875
2876 let deserialized: SDKControlRewindFilesRequest = serde_json::from_str(&json).unwrap();
2877 assert_eq!(deserialized.user_message_id, "msg_12345");
2878 assert_eq!(deserialized.subtype, "rewind_files");
2879 }
2880
2881 #[test]
2882 fn test_options_builder_with_new_fields() {
2883 let options = ClaudeCodeOptions::builder()
2884 .tools(ToolsConfig::claude_code_preset())
2885 .add_beta(SdkBeta::Context1M)
2886 .max_budget_usd(10.0)
2887 .fallback_model("claude-3-haiku")
2888 .output_format(serde_json::json!({"type": "object"}))
2889 .enable_file_checkpointing(true)
2890 .sandbox(SandboxSettings::default())
2891 .add_plugin(SdkPluginConfig::Local { path: "/plugin".to_string() })
2892 .auto_download_cli(true)
2893 .build();
2894
2895 assert!(options.tools.is_some());
2897 match options.tools.as_ref().unwrap() {
2898 ToolsConfig::Preset(preset) => assert_eq!(preset.preset, "claude_code"),
2899 _ => panic!("Expected Preset variant"),
2900 }
2901
2902 assert_eq!(options.betas.len(), 1);
2904 assert!(matches!(options.betas[0], SdkBeta::Context1M));
2905
2906 assert_eq!(options.max_budget_usd, Some(10.0));
2908
2909 assert_eq!(options.fallback_model, Some("claude-3-haiku".to_string()));
2911
2912 assert!(options.output_format.is_some());
2914
2915 assert!(options.enable_file_checkpointing);
2917
2918 assert!(options.sandbox.is_some());
2920
2921 assert_eq!(options.plugins.len(), 1);
2923
2924 assert!(options.auto_download_cli);
2926 }
2927
2928 #[test]
2929 fn test_options_builder_with_tools_list() {
2930 let options = ClaudeCodeOptions::builder()
2931 .tools(ToolsConfig::List(vec!["Read".to_string(), "Bash".to_string()]))
2932 .build();
2933
2934 match options.tools.as_ref().unwrap() {
2935 ToolsConfig::List(list) => {
2936 assert_eq!(list.len(), 2);
2937 assert!(list.contains(&"Read".to_string()));
2938 assert!(list.contains(&"Bash".to_string()));
2939 }
2940 _ => panic!("Expected List variant"),
2941 }
2942 }
2943
2944 #[test]
2945 fn test_options_builder_multiple_betas() {
2946 let options = ClaudeCodeOptions::builder()
2947 .add_beta(SdkBeta::Context1M)
2948 .betas(vec![SdkBeta::Context1M])
2949 .build();
2950
2951 assert_eq!(options.betas.len(), 1);
2953 }
2954
2955 #[test]
2956 fn test_options_builder_add_beta_accumulates() {
2957 let options = ClaudeCodeOptions::builder()
2958 .add_beta(SdkBeta::Context1M)
2959 .add_beta(SdkBeta::Context1M)
2960 .build();
2961
2962 assert_eq!(options.betas.len(), 2);
2964 }
2965
2966 #[test]
2967 fn test_options_builder_multiple_plugins() {
2968 let options = ClaudeCodeOptions::builder()
2969 .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2970 .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2971 .plugins(vec![SdkPluginConfig::Local { path: "/plugin3".to_string() }])
2972 .build();
2973
2974 assert_eq!(options.plugins.len(), 1);
2976 }
2977
2978 #[test]
2979 fn test_options_builder_add_plugin_accumulates() {
2980 let options = ClaudeCodeOptions::builder()
2981 .add_plugin(SdkPluginConfig::Local { path: "/plugin1".to_string() })
2982 .add_plugin(SdkPluginConfig::Local { path: "/plugin2".to_string() })
2983 .add_plugin(SdkPluginConfig::Local { path: "/plugin3".to_string() })
2984 .build();
2985
2986 assert_eq!(options.plugins.len(), 3);
2988 }
2989
2990 #[test]
2991 fn test_message_result_with_structured_output() {
2992 let json = r#"{
2994 "type": "result",
2995 "subtype": "success",
2996 "cost_usd": 0.05,
2997 "duration_ms": 1500,
2998 "duration_api_ms": 1200,
2999 "is_error": false,
3000 "num_turns": 3,
3001 "session_id": "session_123",
3002 "structured_output": {"answer": 42}
3003 }"#;
3004
3005 let msg: Message = serde_json::from_str(json).unwrap();
3006 match msg {
3007 Message::Result {
3008 structured_output,
3009 ..
3010 } => {
3011 assert!(structured_output.is_some());
3012 let output = structured_output.unwrap();
3013 assert_eq!(output["answer"], 42);
3014 }
3015 _ => panic!("Expected Result message"),
3016 }
3017 }
3018
3019 #[test]
3020 fn test_message_result_with_structured_output_camel_case() {
3021 let json = r#"{
3023 "type": "result",
3024 "subtype": "success",
3025 "cost_usd": 0.05,
3026 "duration_ms": 1500,
3027 "duration_api_ms": 1200,
3028 "is_error": false,
3029 "num_turns": 3,
3030 "session_id": "session_123",
3031 "structuredOutput": {"name": "test", "value": true}
3032 }"#;
3033
3034 let msg: Message = serde_json::from_str(json).unwrap();
3035 match msg {
3036 Message::Result {
3037 structured_output,
3038 ..
3039 } => {
3040 assert!(structured_output.is_some());
3041 let output = structured_output.unwrap();
3042 assert_eq!(output["name"], "test");
3043 assert_eq!(output["value"], true);
3044 }
3045 _ => panic!("Expected Result message"),
3046 }
3047 }
3048
3049 #[test]
3050 fn test_default_options_new_fields() {
3051 let options = ClaudeCodeOptions::default();
3052
3053 assert!(options.tools.is_none());
3055 assert!(options.betas.is_empty());
3056 assert!(options.max_budget_usd.is_none());
3057 assert!(options.fallback_model.is_none());
3058 assert!(options.output_format.is_none());
3059 assert!(!options.enable_file_checkpointing);
3060 assert!(options.sandbox.is_none());
3061 assert!(options.plugins.is_empty());
3062 assert!(options.user.is_none());
3063 assert!(!options.auto_download_cli);
3066 }
3067
3068 #[test]
3071 fn test_permission_mode_dont_ask_serialization() {
3072 let mode = PermissionMode::DontAsk;
3073 let json = serde_json::to_string(&mode).unwrap();
3074 assert_eq!(json, r#""dontAsk""#);
3075
3076 let deserialized: PermissionMode = serde_json::from_str(r#""dontAsk""#).unwrap();
3077 assert_eq!(deserialized, PermissionMode::DontAsk);
3078 }
3079
3080 #[test]
3081 fn test_permission_mode_all_variants_roundtrip() {
3082 let variants = vec![
3083 (PermissionMode::Default, r#""default""#),
3084 (PermissionMode::AcceptEdits, r#""acceptEdits""#),
3085 (PermissionMode::Plan, r#""plan""#),
3086 (PermissionMode::BypassPermissions, r#""bypassPermissions""#),
3087 (PermissionMode::DontAsk, r#""dontAsk""#),
3088 ];
3089 for (mode, expected_json) in variants {
3090 let json = serde_json::to_string(&mode).unwrap();
3091 assert_eq!(json, expected_json, "serialization failed for {:?}", mode);
3092 let back: PermissionMode = serde_json::from_str(&json).unwrap();
3093 assert_eq!(back, mode, "roundtrip failed for {:?}", mode);
3094 }
3095 }
3096
3097 #[test]
3100 fn test_agent_definition_all_new_fields() {
3101 let agent = AgentDefinition {
3102 description: "Coder".to_string(),
3103 prompt: "You write code".to_string(),
3104 tools: Some(vec!["Bash".to_string()]),
3105 disallowed_tools: Some(vec!["Write".to_string()]),
3106 model: Some("claude-sonnet-4-20250514".to_string()),
3107 skills: Some(vec!["tdd".to_string()]),
3108 memory: Some("project".to_string()),
3109 mcp_servers: None,
3110 initial_prompt: Some("Start coding".to_string()),
3111 max_turns: Some(50),
3112 background: Some(true),
3113 effort: Some(Effort::High),
3114 permission_mode: Some(PermissionMode::DontAsk),
3115 };
3116
3117 let json = serde_json::to_value(&agent).unwrap();
3118 assert_eq!(json["disallowedTools"], serde_json::json!(["Write"]));
3120 assert_eq!(json["initialPrompt"], "Start coding");
3121 assert_eq!(json["maxTurns"], 50);
3122 assert_eq!(json["background"], true);
3123 assert_eq!(json["effort"], "high");
3124 assert_eq!(json["permissionMode"], "dontAsk");
3125
3126 let back: AgentDefinition = serde_json::from_value(json).unwrap();
3128 assert_eq!(back.disallowed_tools, Some(vec!["Write".to_string()]));
3129 assert_eq!(back.initial_prompt, Some("Start coding".to_string()));
3130 assert_eq!(back.max_turns, Some(50));
3131 assert_eq!(back.background, Some(true));
3132 assert_eq!(back.effort, Some(Effort::High));
3133 assert_eq!(back.permission_mode, Some(PermissionMode::DontAsk));
3134 }
3135
3136 #[test]
3137 fn test_agent_definition_backward_compat_minimal() {
3138 let json = serde_json::json!({
3140 "description": "Agent",
3141 "prompt": "Do stuff"
3142 });
3143 let agent: AgentDefinition = serde_json::from_value(json).unwrap();
3144 assert!(agent.disallowed_tools.is_none());
3145 assert!(agent.initial_prompt.is_none());
3146 assert!(agent.max_turns.is_none());
3147 assert!(agent.background.is_none());
3148 assert!(agent.effort.is_none());
3149 assert!(agent.permission_mode.is_none());
3150 }
3151
3152 #[test]
3155 fn test_sdk_control_get_context_usage_request() {
3156 let req = SDKControlGetContextUsageRequest::new();
3157 assert_eq!(req.subtype, "get_context_usage");
3158 let json = serde_json::to_value(&req).unwrap();
3159 assert_eq!(json["subtype"], "get_context_usage");
3160 }
3161
3162 #[test]
3163 fn test_sdk_control_stop_task_request() {
3164 let req = SDKControlStopTaskRequest::new("task-abc-123");
3165 assert_eq!(req.subtype, "stop_task");
3166 assert_eq!(req.task_id, "task-abc-123");
3167 let json = serde_json::to_value(&req).unwrap();
3168 assert_eq!(json["task_id"], "task-abc-123");
3169 }
3170
3171 #[test]
3172 fn test_sdk_control_mcp_status_request() {
3173 let req = SDKControlMcpStatusRequest::new();
3174 assert_eq!(req.subtype, "mcp_status");
3175 }
3176
3177 #[test]
3178 fn test_sdk_control_mcp_reconnect_request() {
3179 let req = SDKControlMcpReconnectRequest::new("my-server");
3180 assert_eq!(req.subtype, "mcp_reconnect");
3181 assert_eq!(req.server_name, "my-server");
3182 }
3183
3184 #[test]
3185 fn test_sdk_control_mcp_toggle_request() {
3186 let req = SDKControlMcpToggleRequest::new("my-server", false);
3187 assert_eq!(req.subtype, "mcp_toggle");
3188 assert_eq!(req.server_name, "my-server");
3189 assert!(!req.enabled);
3190 }
3191
3192 #[test]
3195 fn test_context_usage_response_deserialize() {
3196 let json = serde_json::json!({
3197 "categories": [
3198 {"name": "system", "tokenCount": 500, "percentage": 10.0},
3199 {"name": "conversation", "tokenCount": 3000, "percentage": 60.0}
3200 ],
3201 "totalTokens": 5000,
3202 "maxTokens": 200000,
3203 "percentage": 2.5,
3204 "model": "claude-sonnet-4-20250514",
3205 "isAutoCompactEnabled": true,
3206 "autoCompactThreshold": 180000,
3207 "memoryFiles": [],
3208 "mcpTools": [],
3209 "apiUsage": {
3210 "inputTokens": 4000,
3211 "outputTokens": 1000,
3212 "cacheReadInputTokens": 2000,
3213 "cacheCreationInputTokens": 500
3214 }
3215 });
3216
3217 let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3218 assert_eq!(resp.categories.len(), 2);
3219 assert_eq!(resp.categories[0].name, "system");
3220 assert_eq!(resp.categories[0].token_count, 500);
3221 assert_eq!(resp.total_tokens, 5000);
3222 assert_eq!(resp.max_tokens, 200000);
3223 assert_eq!(resp.model, "claude-sonnet-4-20250514");
3224 assert!(resp.is_auto_compact_enabled);
3225 assert_eq!(resp.auto_compact_threshold, Some(180000));
3226
3227 let api = resp.api_usage.unwrap();
3228 assert_eq!(api.input_tokens, 4000);
3229 assert_eq!(api.output_tokens, 1000);
3230 assert_eq!(api.cache_read_input_tokens, 2000);
3231 assert_eq!(api.cache_creation_input_tokens, 500);
3232 }
3233
3234 #[test]
3235 fn test_context_usage_response_minimal() {
3236 let json = serde_json::json!({});
3238 let resp: ContextUsageResponse = serde_json::from_value(json).unwrap();
3239 assert_eq!(resp.total_tokens, 0);
3240 assert!(resp.categories.is_empty());
3241 assert!(resp.api_usage.is_none());
3242 }
3243
3244 #[test]
3245 fn test_task_budget_serialization() {
3246 let budget = TaskBudget {
3247 max_cost_usd: Some(5.0),
3248 max_tokens: Some(100_000),
3249 max_turns: Some(20),
3250 };
3251 let json = serde_json::to_value(&budget).unwrap();
3252 assert_eq!(json["maxCostUsd"], 5.0);
3253 assert_eq!(json["maxTokens"], 100_000);
3254 assert_eq!(json["maxTurns"], 20);
3255
3256 let back: TaskBudget = serde_json::from_value(json).unwrap();
3257 assert_eq!(back.max_cost_usd, Some(5.0));
3258 assert_eq!(back.max_tokens, Some(100_000));
3259 assert_eq!(back.max_turns, Some(20));
3260 }
3261
3262 #[test]
3263 fn test_fork_session_result_deserialize() {
3264 let json = serde_json::json!({"sessionId": "sess-forked-abc"});
3265 let result: ForkSessionResult = serde_json::from_value(json).unwrap();
3266 assert_eq!(result.session_id, "sess-forked-abc");
3267 }
3268
3269 #[test]
3272 fn test_sdk_control_request_new_variants_serialize() {
3273 let req = SDKControlRequest::GetContextUsage(SDKControlGetContextUsageRequest::new());
3274 let json = serde_json::to_value(&req).unwrap();
3275 assert_eq!(json["type"], "get_context_usage");
3276
3277 let req = SDKControlRequest::StopTask(SDKControlStopTaskRequest::new("t1"));
3278 let json = serde_json::to_value(&req).unwrap();
3279 assert_eq!(json["type"], "stop_task");
3280 assert_eq!(json["task_id"], "t1");
3281
3282 let req = SDKControlRequest::McpStatus(SDKControlMcpStatusRequest::new());
3283 let json = serde_json::to_value(&req).unwrap();
3284 assert_eq!(json["type"], "mcp_status");
3285
3286 let req = SDKControlRequest::McpReconnect(SDKControlMcpReconnectRequest::new("srv"));
3287 let json = serde_json::to_value(&req).unwrap();
3288 assert_eq!(json["type"], "mcp_reconnect");
3289
3290 let req = SDKControlRequest::McpToggle(SDKControlMcpToggleRequest::new("srv", true));
3291 let json = serde_json::to_value(&req).unwrap();
3292 assert_eq!(json["type"], "mcp_toggle");
3293 assert_eq!(json["enabled"], true);
3294 }
3295}