1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::future::Future;
9use std::path::PathBuf;
10use std::pin::Pin;
11use std::sync::Arc;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
19#[serde(rename_all = "camelCase")]
20pub enum PermissionMode {
21 #[default]
23 #[serde(rename = "default")]
24 Default,
25 #[serde(rename = "acceptEdits")]
27 AcceptEdits,
28 #[serde(rename = "plan")]
30 Plan,
31 #[serde(rename = "bypassPermissions")]
33 BypassPermissions,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "lowercase")]
39pub enum PermissionBehavior {
40 Allow,
42 Deny,
44 Ask,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub enum PermissionUpdateDestination {
52 UserSettings,
54 ProjectSettings,
56 LocalSettings,
58 Session,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct PermissionRuleValue {
65 #[serde(rename = "toolName")]
67 pub tool_name: String,
68 #[serde(rename = "ruleContent", skip_serializing_if = "Option::is_none")]
70 pub rule_content: Option<String>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub enum PermissionUpdateType {
77 AddRules,
79 ReplaceRules,
81 RemoveRules,
83 SetMode,
85 AddDirectories,
87 RemoveDirectories,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct PermissionUpdate {
94 #[serde(rename = "type")]
96 pub update_type: PermissionUpdateType,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub rules: Option<Vec<PermissionRuleValue>>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub behavior: Option<PermissionBehavior>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub mode: Option<PermissionMode>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub directories: Option<Vec<String>>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub destination: Option<PermissionUpdateDestination>,
112}
113
114#[derive(Debug, Clone, Default)]
120pub struct ToolPermissionContext {
121 pub suggestions: Vec<PermissionUpdate>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct PermissionResultAllow {
128 pub behavior: String,
130 #[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
132 pub updated_input: Option<serde_json::Value>,
133 #[serde(rename = "updatedPermissions", skip_serializing_if = "Option::is_none")]
135 pub updated_permissions: Option<Vec<PermissionUpdate>>,
136}
137
138impl PermissionResultAllow {
139 pub fn new() -> Self {
141 Self {
142 behavior: "allow".to_string(),
143 updated_input: None,
144 updated_permissions: None,
145 }
146 }
147
148 pub fn with_updated_input(input: serde_json::Value) -> Self {
150 Self {
151 behavior: "allow".to_string(),
152 updated_input: Some(input),
153 updated_permissions: None,
154 }
155 }
156}
157
158impl Default for PermissionResultAllow {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct PermissionResultDeny {
167 pub behavior: String,
169 #[serde(default)]
171 pub message: String,
172 #[serde(default)]
174 pub interrupt: bool,
175}
176
177impl PermissionResultDeny {
178 pub fn new() -> Self {
180 Self {
181 behavior: "deny".to_string(),
182 message: String::new(),
183 interrupt: false,
184 }
185 }
186
187 pub fn with_message(message: impl Into<String>) -> Self {
189 Self {
190 behavior: "deny".to_string(),
191 message: message.into(),
192 interrupt: false,
193 }
194 }
195
196 pub fn with_interrupt(message: impl Into<String>) -> Self {
198 Self {
199 behavior: "deny".to_string(),
200 message: message.into(),
201 interrupt: true,
202 }
203 }
204}
205
206impl Default for PermissionResultDeny {
207 fn default() -> Self {
208 Self::new()
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(untagged)]
215pub enum PermissionResult {
216 Allow(PermissionResultAllow),
218 Deny(PermissionResultDeny),
220}
221
222impl PermissionResult {
223 pub fn allow() -> Self {
225 Self::Allow(PermissionResultAllow::new())
226 }
227
228 pub fn deny() -> Self {
230 Self::Deny(PermissionResultDeny::new())
231 }
232
233 pub fn deny_with_message(message: impl Into<String>) -> Self {
235 Self::Deny(PermissionResultDeny::with_message(message))
236 }
237}
238
239pub type CanUseToolFuture = Pin<Box<dyn Future<Output = PermissionResult> + Send>>;
241
242pub type CanUseTool =
268 Arc<dyn Fn(String, serde_json::Value, ToolPermissionContext) -> CanUseToolFuture + Send + Sync>;
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
276pub enum HookEvent {
277 PreToolUse,
279 PostToolUse,
281 PostToolUseFailure,
283 UserPromptSubmit,
285 Stop,
287 SubagentStop,
289 PreCompact,
291 Notification,
293 SubagentStart,
295 PermissionRequest,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct BaseHookInput {
302 pub session_id: String,
304 pub transcript_path: String,
306 pub cwd: String,
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub permission_mode: Option<String>,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct PreToolUseHookInput {
316 #[serde(flatten)]
318 pub base: BaseHookInput,
319 #[serde(default)]
321 pub hook_event_name: String,
322 pub tool_name: String,
324 pub tool_input: serde_json::Value,
326 #[serde(default)]
328 pub tool_use_id: String,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct PostToolUseHookInput {
334 #[serde(flatten)]
336 pub base: BaseHookInput,
337 #[serde(default)]
339 pub hook_event_name: String,
340 pub tool_name: String,
342 pub tool_input: serde_json::Value,
344 pub tool_response: serde_json::Value,
346 #[serde(default)]
348 pub tool_use_id: String,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct PostToolUseFailureHookInput {
354 #[serde(flatten)]
356 pub base: BaseHookInput,
357 #[serde(default)]
359 pub hook_event_name: String,
360 pub tool_name: String,
362 pub tool_input: serde_json::Value,
364 pub tool_use_id: String,
366 pub error: String,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub is_interrupt: Option<bool>,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct UserPromptSubmitHookInput {
376 #[serde(flatten)]
378 pub base: BaseHookInput,
379 #[serde(default)]
381 pub hook_event_name: String,
382 pub prompt: String,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct StopHookInput {
389 #[serde(flatten)]
391 pub base: BaseHookInput,
392 #[serde(default)]
394 pub hook_event_name: String,
395 pub stop_hook_active: bool,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct SubagentStopHookInput {
402 #[serde(flatten)]
404 pub base: BaseHookInput,
405 #[serde(default)]
407 pub hook_event_name: String,
408 pub stop_hook_active: bool,
410 #[serde(default)]
412 pub agent_id: String,
413 #[serde(default)]
415 pub agent_transcript_path: String,
416 #[serde(default)]
418 pub agent_type: String,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
423#[serde(rename_all = "lowercase")]
424pub enum CompactTrigger {
425 Manual,
427 Auto,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct PreCompactHookInput {
434 #[serde(flatten)]
436 pub base: BaseHookInput,
437 #[serde(default)]
439 pub hook_event_name: String,
440 pub trigger: CompactTrigger,
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub custom_instructions: Option<String>,
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
449pub struct NotificationHookInput {
450 #[serde(flatten)]
452 pub base: BaseHookInput,
453 #[serde(default)]
455 pub hook_event_name: String,
456 pub message: String,
458 #[serde(skip_serializing_if = "Option::is_none")]
460 pub title: Option<String>,
461 pub notification_type: String,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct SubagentStartHookInput {
468 #[serde(flatten)]
470 pub base: BaseHookInput,
471 #[serde(default)]
473 pub hook_event_name: String,
474 pub agent_id: String,
476 pub agent_type: String,
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
482pub struct PermissionRequestHookInput {
483 #[serde(flatten)]
485 pub base: BaseHookInput,
486 #[serde(default)]
488 pub hook_event_name: String,
489 pub tool_name: String,
491 pub tool_input: serde_json::Value,
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub permission_suggestions: Option<Vec<serde_json::Value>>,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(tag = "hook_event_name")]
501pub enum HookInput {
502 PreToolUse(PreToolUseHookInput),
504 PostToolUse(PostToolUseHookInput),
506 PostToolUseFailure(PostToolUseFailureHookInput),
508 UserPromptSubmit(UserPromptSubmitHookInput),
510 Stop(StopHookInput),
512 SubagentStop(SubagentStopHookInput),
514 PreCompact(PreCompactHookInput),
516 Notification(NotificationHookInput),
518 SubagentStart(SubagentStartHookInput),
520 PermissionRequest(PermissionRequestHookInput),
522}
523
524#[derive(Debug, Clone, Default, Serialize, Deserialize)]
526#[serde(rename_all = "camelCase")]
527pub struct PreToolUseHookSpecificOutput {
528 #[serde(rename = "hookEventName")]
530 pub hook_event_name: String,
531 #[serde(skip_serializing_if = "Option::is_none")]
533 pub permission_decision: Option<PermissionBehavior>,
534 #[serde(skip_serializing_if = "Option::is_none")]
536 pub permission_decision_reason: Option<String>,
537 #[serde(skip_serializing_if = "Option::is_none")]
539 pub updated_input: Option<serde_json::Value>,
540 #[serde(skip_serializing_if = "Option::is_none")]
542 pub additional_context: Option<String>,
543}
544
545#[derive(Debug, Clone, Default, Serialize, Deserialize)]
547#[serde(rename_all = "camelCase")]
548pub struct PostToolUseHookSpecificOutput {
549 #[serde(rename = "hookEventName")]
551 pub hook_event_name: String,
552 #[serde(skip_serializing_if = "Option::is_none")]
554 pub additional_context: Option<String>,
555 #[serde(skip_serializing_if = "Option::is_none")]
557 pub updated_mcp_tool_output: Option<serde_json::Value>,
558}
559
560#[derive(Debug, Clone, Default, Serialize, Deserialize)]
562#[serde(rename_all = "camelCase")]
563pub struct PostToolUseFailureHookSpecificOutput {
564 #[serde(rename = "hookEventName")]
566 pub hook_event_name: String,
567 #[serde(skip_serializing_if = "Option::is_none")]
569 pub additional_context: Option<String>,
570}
571
572#[derive(Debug, Clone, Default, Serialize, Deserialize)]
574#[serde(rename_all = "camelCase")]
575pub struct UserPromptSubmitHookSpecificOutput {
576 #[serde(rename = "hookEventName")]
578 pub hook_event_name: String,
579 #[serde(skip_serializing_if = "Option::is_none")]
581 pub additional_context: Option<String>,
582}
583
584#[derive(Debug, Clone, Default, Serialize, Deserialize)]
586#[serde(rename_all = "camelCase")]
587pub struct NotificationHookSpecificOutput {
588 #[serde(rename = "hookEventName")]
590 pub hook_event_name: String,
591 #[serde(skip_serializing_if = "Option::is_none")]
593 pub additional_context: Option<String>,
594}
595
596#[derive(Debug, Clone, Default, Serialize, Deserialize)]
598#[serde(rename_all = "camelCase")]
599pub struct SubagentStartHookSpecificOutput {
600 #[serde(rename = "hookEventName")]
602 pub hook_event_name: String,
603 #[serde(skip_serializing_if = "Option::is_none")]
605 pub additional_context: Option<String>,
606}
607
608#[derive(Debug, Clone, Default, Serialize, Deserialize)]
610#[serde(rename_all = "camelCase")]
611pub struct PermissionRequestHookSpecificOutput {
612 #[serde(rename = "hookEventName")]
614 pub hook_event_name: String,
615 pub decision: serde_json::Value,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
621#[serde(untagged)]
622pub enum HookSpecificOutput {
623 PreToolUse(PreToolUseHookSpecificOutput),
625 PostToolUse(PostToolUseHookSpecificOutput),
627 PostToolUseFailure(PostToolUseFailureHookSpecificOutput),
629 UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
631 Notification(NotificationHookSpecificOutput),
633 SubagentStart(SubagentStartHookSpecificOutput),
635 PermissionRequest(PermissionRequestHookSpecificOutput),
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
641pub struct AsyncHookOutput {
642 #[serde(rename = "async")]
644 pub async_: bool,
645 #[serde(rename = "asyncTimeout", skip_serializing_if = "Option::is_none")]
647 pub async_timeout: Option<u64>,
648}
649
650#[derive(Debug, Clone, Default, Serialize, Deserialize)]
652#[serde(rename_all = "camelCase")]
653pub struct SyncHookOutput {
654 #[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
656 pub continue_: Option<bool>,
657 #[serde(skip_serializing_if = "Option::is_none")]
659 pub suppress_output: Option<bool>,
660 #[serde(skip_serializing_if = "Option::is_none")]
662 pub stop_reason: Option<String>,
663 #[serde(skip_serializing_if = "Option::is_none")]
665 pub decision: Option<String>,
666 #[serde(skip_serializing_if = "Option::is_none")]
668 pub system_message: Option<String>,
669 #[serde(skip_serializing_if = "Option::is_none")]
671 pub reason: Option<String>,
672 #[serde(skip_serializing_if = "Option::is_none")]
674 pub hook_specific_output: Option<HookSpecificOutput>,
675}
676
677#[derive(Debug, Clone, Serialize, Deserialize)]
679#[serde(untagged)]
680pub enum HookOutput {
681 Async(AsyncHookOutput),
683 Sync(SyncHookOutput),
685}
686
687impl Default for HookOutput {
688 fn default() -> Self {
689 Self::Sync(SyncHookOutput::default())
690 }
691}
692
693#[derive(Debug, Clone, Default)]
695pub struct HookContext {
696 }
698
699pub type HookCallbackFuture = Pin<Box<dyn Future<Output = HookOutput> + Send>>;
701
702pub type HookCallback =
725 Arc<dyn Fn(HookInput, Option<String>, HookContext) -> HookCallbackFuture + Send + Sync>;
726
727#[derive(Clone, Default)]
729pub struct HookMatcher {
730 pub matcher: Option<String>,
732 pub hooks: Vec<HookCallback>,
734 pub timeout: Option<f64>,
736}
737
738impl std::fmt::Debug for HookMatcher {
739 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
740 f.debug_struct("HookMatcher")
741 .field("matcher", &self.matcher)
742 .field("hooks", &format!("[{} callbacks]", self.hooks.len()))
743 .field("timeout", &self.timeout)
744 .finish()
745 }
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize)]
754pub struct McpStdioServerConfig {
755 #[serde(rename = "type", default = "default_stdio", skip_serializing)]
758 pub server_type: String,
759 pub command: String,
761 #[serde(default, skip_serializing_if = "Vec::is_empty")]
763 pub args: Vec<String>,
764 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
766 pub env: HashMap<String, String>,
767}
768
769fn default_stdio() -> String {
770 "stdio".to_string()
771}
772
773fn default_sse() -> String {
774 "sse".to_string()
775}
776
777fn default_http() -> String {
778 "http".to_string()
779}
780
781#[derive(Debug, Clone, Serialize, Deserialize)]
783pub struct McpSSEServerConfig {
784 #[serde(rename = "type", default = "default_sse", skip_serializing)]
787 pub server_type: String,
788 pub url: String,
790 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
792 pub headers: HashMap<String, String>,
793}
794
795#[derive(Debug, Clone, Serialize, Deserialize)]
797pub struct McpHttpServerConfig {
798 #[serde(rename = "type", default = "default_http", skip_serializing)]
801 pub server_type: String,
802 pub url: String,
804 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
806 pub headers: HashMap<String, String>,
807}
808
809#[derive(Debug, Clone, Serialize, Deserialize)]
811#[serde(tag = "type")]
812pub enum McpServerConfig {
813 #[serde(rename = "stdio")]
815 Stdio(McpStdioServerConfig),
816 #[serde(rename = "sse")]
818 SSE(McpSSEServerConfig),
819 #[serde(rename = "http")]
821 Http(McpHttpServerConfig),
822}
823
824#[derive(Debug, Clone, Serialize, Deserialize)]
826pub struct SdkPluginConfig {
827 #[serde(rename = "type")]
829 pub plugin_type: String,
830 pub path: String,
832}
833
834#[derive(Debug, Clone, Default, Serialize, Deserialize)]
840#[serde(rename_all = "camelCase")]
841pub struct SandboxNetworkConfig {
842 #[serde(default, skip_serializing_if = "Vec::is_empty")]
844 pub allow_unix_sockets: Vec<String>,
845 #[serde(default)]
847 pub allow_all_unix_sockets: bool,
848 #[serde(default)]
850 pub allow_local_binding: bool,
851 #[serde(skip_serializing_if = "Option::is_none")]
853 pub http_proxy_port: Option<u16>,
854 #[serde(skip_serializing_if = "Option::is_none")]
856 pub socks_proxy_port: Option<u16>,
857}
858
859#[derive(Debug, Clone, Default, Serialize, Deserialize)]
861pub struct SandboxIgnoreViolations {
862 #[serde(default, skip_serializing_if = "Vec::is_empty")]
864 pub file: Vec<String>,
865 #[serde(default, skip_serializing_if = "Vec::is_empty")]
867 pub network: Vec<String>,
868}
869
870#[derive(Debug, Clone, Default, Serialize, Deserialize)]
872#[serde(rename_all = "camelCase")]
873pub struct SandboxSettings {
874 #[serde(default)]
876 pub enabled: bool,
877 #[serde(default = "default_true")]
879 pub auto_allow_bash_if_sandboxed: bool,
880 #[serde(default, skip_serializing_if = "Vec::is_empty")]
882 pub excluded_commands: Vec<String>,
883 #[serde(default = "default_true")]
885 pub allow_unsandboxed_commands: bool,
886 #[serde(default, skip_serializing_if = "Option::is_none")]
888 pub network: Option<SandboxNetworkConfig>,
889 #[serde(default, skip_serializing_if = "Option::is_none")]
891 pub ignore_violations: Option<SandboxIgnoreViolations>,
892 #[serde(default)]
894 pub enable_weaker_nested_sandbox: bool,
895}
896
897fn default_true() -> bool {
898 true
899}
900
901#[derive(Debug, Clone, Serialize, Deserialize)]
907pub struct TextBlock {
908 pub text: String,
910}
911
912#[derive(Debug, Clone, Serialize, Deserialize)]
914pub struct ThinkingBlock {
915 pub thinking: String,
917 pub signature: String,
919}
920
921#[derive(Debug, Clone, Serialize, Deserialize)]
923pub struct ToolUseBlock {
924 pub id: String,
926 pub name: String,
928 pub input: serde_json::Value,
930}
931
932#[derive(Debug, Clone, Serialize, Deserialize)]
934pub struct ToolResultBlock {
935 pub tool_use_id: String,
937 #[serde(skip_serializing_if = "Option::is_none")]
939 pub content: Option<serde_json::Value>,
940 #[serde(skip_serializing_if = "Option::is_none")]
942 pub is_error: Option<bool>,
943}
944
945#[derive(Debug, Clone, Serialize, Deserialize)]
947#[serde(tag = "type")]
948pub enum ContentBlock {
949 #[serde(rename = "text")]
951 Text(TextBlock),
952 #[serde(rename = "thinking")]
954 Thinking(ThinkingBlock),
955 #[serde(rename = "tool_use")]
957 ToolUse(ToolUseBlock),
958 #[serde(rename = "tool_result")]
960 ToolResult(ToolResultBlock),
961}
962
963impl ContentBlock {
964 pub fn as_text(&self) -> Option<&str> {
966 match self {
967 ContentBlock::Text(block) => Some(&block.text),
968 _ => None,
969 }
970 }
971
972 pub fn is_tool_use(&self) -> bool {
974 matches!(self, ContentBlock::ToolUse(_))
975 }
976}
977
978#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
984#[serde(rename_all = "snake_case")]
985pub enum AssistantMessageError {
986 AuthenticationFailed,
988 BillingError,
990 RateLimit,
992 InvalidRequest,
994 ServerError,
996 Unknown,
998}
999
1000#[derive(Debug, Clone, Serialize, Deserialize)]
1002pub struct UserMessage {
1003 pub content: UserMessageContent,
1005 #[serde(skip_serializing_if = "Option::is_none")]
1007 pub uuid: Option<String>,
1008 #[serde(skip_serializing_if = "Option::is_none")]
1010 pub parent_tool_use_id: Option<String>,
1011}
1012
1013#[derive(Debug, Clone, Serialize, Deserialize)]
1015#[serde(untagged)]
1016pub enum UserMessageContent {
1017 Text(String),
1019 Blocks(Vec<ContentBlock>),
1021}
1022
1023impl UserMessage {
1024 pub fn text(&self) -> Option<&str> {
1026 match &self.content {
1027 UserMessageContent::Text(s) => Some(s),
1028 UserMessageContent::Blocks(blocks) => {
1029 if blocks.len() == 1 {
1030 blocks[0].as_text()
1031 } else {
1032 None
1033 }
1034 }
1035 }
1036 }
1037}
1038
1039#[derive(Debug, Clone, Serialize, Deserialize)]
1041pub struct AssistantMessage {
1042 pub content: Vec<ContentBlock>,
1044 pub model: String,
1046 #[serde(skip_serializing_if = "Option::is_none")]
1048 pub parent_tool_use_id: Option<String>,
1049 #[serde(skip_serializing_if = "Option::is_none")]
1051 pub error: Option<AssistantMessageError>,
1052}
1053
1054impl AssistantMessage {
1055 pub fn text(&self) -> String {
1057 self.content
1058 .iter()
1059 .filter_map(|block| block.as_text())
1060 .collect::<Vec<_>>()
1061 .join("")
1062 }
1063
1064 pub fn tool_uses(&self) -> Vec<&ToolUseBlock> {
1066 self.content
1067 .iter()
1068 .filter_map(|block| match block {
1069 ContentBlock::ToolUse(tu) => Some(tu),
1070 _ => None,
1071 })
1072 .collect()
1073 }
1074}
1075
1076#[derive(Debug, Clone, Serialize, Deserialize)]
1078pub struct SystemMessage {
1079 pub subtype: String,
1081 pub data: serde_json::Value,
1083}
1084
1085#[derive(Debug, Clone, Serialize, Deserialize)]
1087pub struct ResultMessage {
1088 pub subtype: String,
1090 pub duration_ms: u64,
1092 pub duration_api_ms: u64,
1094 pub is_error: bool,
1096 pub num_turns: u32,
1098 pub session_id: String,
1100 #[serde(skip_serializing_if = "Option::is_none")]
1102 pub total_cost_usd: Option<f64>,
1103 #[serde(skip_serializing_if = "Option::is_none")]
1105 pub usage: Option<serde_json::Value>,
1106 #[serde(skip_serializing_if = "Option::is_none")]
1108 pub result: Option<String>,
1109 #[serde(skip_serializing_if = "Option::is_none")]
1111 pub structured_output: Option<serde_json::Value>,
1112}
1113
1114#[derive(Debug, Clone, Serialize, Deserialize)]
1116pub struct StreamEvent {
1117 pub uuid: String,
1119 pub session_id: String,
1121 pub event: serde_json::Value,
1123 #[serde(skip_serializing_if = "Option::is_none")]
1125 pub parent_tool_use_id: Option<String>,
1126}
1127
1128#[derive(Debug, Clone, Serialize, Deserialize)]
1130#[serde(tag = "type")]
1131pub enum Message {
1132 #[serde(rename = "user")]
1134 User(UserMessage),
1135 #[serde(rename = "assistant")]
1137 Assistant(AssistantMessage),
1138 #[serde(rename = "system")]
1140 System(SystemMessage),
1141 #[serde(rename = "result")]
1143 Result(ResultMessage),
1144 #[serde(rename = "stream_event")]
1146 StreamEvent(StreamEvent),
1147}
1148
1149impl Message {
1150 pub fn is_result(&self) -> bool {
1152 matches!(self, Message::Result(_))
1153 }
1154
1155 pub fn is_assistant(&self) -> bool {
1157 matches!(self, Message::Assistant(_))
1158 }
1159
1160 pub fn as_assistant(&self) -> Option<&AssistantMessage> {
1162 match self {
1163 Message::Assistant(msg) => Some(msg),
1164 _ => None,
1165 }
1166 }
1167
1168 pub fn as_result(&self) -> Option<&ResultMessage> {
1170 match self {
1171 Message::Result(msg) => Some(msg),
1172 _ => None,
1173 }
1174 }
1175}
1176
1177#[derive(Debug, Clone, Serialize, Deserialize)]
1183pub struct SystemPromptPreset {
1184 #[serde(rename = "type")]
1186 pub preset_type: String,
1187 pub preset: String,
1189 #[serde(skip_serializing_if = "Option::is_none")]
1191 pub append: Option<String>,
1192}
1193
1194#[derive(Debug, Clone, Serialize, Deserialize)]
1196pub struct ToolsPreset {
1197 #[serde(rename = "type")]
1199 pub preset_type: String,
1200 pub preset: String,
1202}
1203
1204#[derive(Debug, Clone, Serialize, Deserialize)]
1206#[serde(untagged)]
1207pub enum SystemPromptConfig {
1208 Text(String),
1210 Preset(SystemPromptPreset),
1212}
1213
1214#[derive(Debug, Clone, Serialize, Deserialize)]
1216#[serde(untagged)]
1217pub enum ToolsConfig {
1218 List(Vec<String>),
1220 Preset(ToolsPreset),
1222}
1223
1224#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1226#[serde(rename_all = "lowercase")]
1227pub enum AgentModel {
1228 Sonnet,
1230 Opus,
1232 Haiku,
1234 Inherit,
1236}
1237
1238#[derive(Debug, Clone, Serialize, Deserialize)]
1240pub struct AgentDefinition {
1241 pub description: String,
1243 pub prompt: String,
1245 #[serde(skip_serializing_if = "Option::is_none")]
1247 pub tools: Option<Vec<String>>,
1248 #[serde(skip_serializing_if = "Option::is_none")]
1250 pub model: Option<AgentModel>,
1251}
1252
1253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1255#[serde(rename_all = "lowercase")]
1256pub enum SettingSource {
1257 User,
1259 Project,
1261 Local,
1263}
1264
1265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1267#[serde(rename_all = "kebab-case")]
1268pub enum SdkBeta {
1269 #[serde(rename = "context-1m-2025-08-07")]
1271 Context1m,
1272}
1273
1274#[derive(Debug, Clone)]
1276pub enum McpServersConfig {
1277 Map(HashMap<String, McpServerConfig>),
1279 Path(PathBuf),
1281}
1282
1283impl Default for McpServersConfig {
1284 fn default() -> Self {
1285 Self::Map(HashMap::new())
1286 }
1287}
1288
1289#[derive(Debug, Clone, Serialize, Deserialize)]
1291#[serde(tag = "type", rename_all = "lowercase")]
1292pub enum ThinkingConfig {
1293 Adaptive,
1295 Enabled {
1297 budget_tokens: u32,
1299 },
1300 Disabled,
1302}
1303
1304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1306#[serde(rename_all = "lowercase")]
1307pub enum Effort {
1308 Low,
1310 Medium,
1312 High,
1314 Max,
1316}
1317
1318impl std::fmt::Display for Effort {
1319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1320 match self {
1321 Self::Low => write!(f, "low"),
1322 Self::Medium => write!(f, "medium"),
1323 Self::High => write!(f, "high"),
1324 Self::Max => write!(f, "max"),
1325 }
1326 }
1327}
1328
1329#[derive(Clone, Default)]
1331pub struct ClaudeAgentOptions {
1332 pub tools: Option<ToolsConfig>,
1334 pub allowed_tools: Vec<String>,
1336 pub system_prompt: Option<SystemPromptConfig>,
1338 pub mcp_servers: McpServersConfig,
1340 pub permission_mode: Option<PermissionMode>,
1342 pub continue_conversation: bool,
1344 pub resume: Option<String>,
1346 pub max_turns: Option<u32>,
1348 pub max_budget_usd: Option<f64>,
1350 pub disallowed_tools: Vec<String>,
1352 pub model: Option<String>,
1354 pub fallback_model: Option<String>,
1356 pub betas: Vec<SdkBeta>,
1358 pub permission_prompt_tool_name: Option<String>,
1360 pub cwd: Option<PathBuf>,
1362 pub cli_path: Option<PathBuf>,
1364 pub settings: Option<String>,
1366 pub add_dirs: Vec<PathBuf>,
1368 pub env: HashMap<String, String>,
1370 pub extra_args: HashMap<String, Option<String>>,
1372 pub max_buffer_size: Option<usize>,
1374 pub stderr: Option<Arc<dyn Fn(String) + Send + Sync>>,
1376 pub can_use_tool: Option<CanUseTool>,
1378 pub hooks: Option<HashMap<HookEvent, Vec<HookMatcher>>>,
1380 pub user: Option<String>,
1382 pub include_partial_messages: bool,
1384 pub fork_session: bool,
1386 pub agents: Option<HashMap<String, AgentDefinition>>,
1388 pub setting_sources: Option<Vec<SettingSource>>,
1390 pub sandbox: Option<SandboxSettings>,
1392 pub plugins: Vec<SdkPluginConfig>,
1394 pub max_thinking_tokens: Option<u32>,
1396 pub thinking: Option<ThinkingConfig>,
1398 pub effort: Option<Effort>,
1400 pub output_format: Option<serde_json::Value>,
1402 pub enable_file_checkpointing: bool,
1404 pub timeout_secs: Option<u64>,
1407}
1408
1409impl std::fmt::Debug for ClaudeAgentOptions {
1410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1411 f.debug_struct("ClaudeAgentOptions")
1412 .field("tools", &self.tools)
1413 .field("allowed_tools", &self.allowed_tools)
1414 .field("system_prompt", &self.system_prompt)
1415 .field("permission_mode", &self.permission_mode)
1416 .field("continue_conversation", &self.continue_conversation)
1417 .field("resume", &self.resume)
1418 .field("max_turns", &self.max_turns)
1419 .field("max_budget_usd", &self.max_budget_usd)
1420 .field("disallowed_tools", &self.disallowed_tools)
1421 .field("model", &self.model)
1422 .field(
1423 "can_use_tool",
1424 &self.can_use_tool.as_ref().map(|_| "<callback>"),
1425 )
1426 .field(
1427 "hooks",
1428 &self.hooks.as_ref().map(|h| format!("{} events", h.len())),
1429 )
1430 .field("stderr", &self.stderr.as_ref().map(|_| "<callback>"))
1431 .finish_non_exhaustive()
1432 }
1433}
1434
1435impl ClaudeAgentOptions {
1436 pub fn new() -> Self {
1438 Self::default()
1439 }
1440
1441 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
1443 self.system_prompt = Some(SystemPromptConfig::Text(prompt.into()));
1444 self
1445 }
1446
1447 pub fn with_model(mut self, model: impl Into<String>) -> Self {
1449 self.model = Some(model.into());
1450 self
1451 }
1452
1453 pub fn with_permission_mode(mut self, mode: PermissionMode) -> Self {
1455 self.permission_mode = Some(mode);
1456 self
1457 }
1458
1459 pub fn with_max_turns(mut self, turns: u32) -> Self {
1461 self.max_turns = Some(turns);
1462 self
1463 }
1464
1465 pub fn with_cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
1467 self.cwd = Some(cwd.into());
1468 self
1469 }
1470
1471 pub fn with_allowed_tools(mut self, tools: Vec<String>) -> Self {
1473 self.allowed_tools = tools;
1474 self
1475 }
1476
1477 pub fn with_partial_messages(mut self) -> Self {
1479 self.include_partial_messages = true;
1480 self
1481 }
1482
1483 pub fn with_thinking(mut self, thinking: ThinkingConfig) -> Self {
1485 self.thinking = Some(thinking);
1486 self
1487 }
1488
1489 pub fn with_effort(mut self, effort: Effort) -> Self {
1491 self.effort = Some(effort);
1492 self
1493 }
1494
1495 pub fn with_timeout_secs(mut self, timeout: u64) -> Self {
1503 self.timeout_secs = Some(timeout);
1504 self
1505 }
1506
1507 pub fn with_can_use_tool<F, Fut>(mut self, callback: F) -> Self
1509 where
1510 F: Fn(String, serde_json::Value, ToolPermissionContext) -> Fut + Send + Sync + 'static,
1511 Fut: Future<Output = PermissionResult> + Send + 'static,
1512 {
1513 self.can_use_tool = Some(Arc::new(move |name, input, ctx| {
1514 Box::pin(callback(name, input, ctx))
1515 }));
1516 self
1517 }
1518}
1519
1520#[derive(Debug, Clone, Serialize, Deserialize)]
1526#[serde(tag = "subtype")]
1527pub enum ControlRequestPayload {
1528 #[serde(rename = "interrupt")]
1530 Interrupt,
1531 #[serde(rename = "can_use_tool")]
1533 CanUseTool {
1534 tool_name: String,
1536 input: serde_json::Value,
1538 permission_suggestions: Option<Vec<serde_json::Value>>,
1540 blocked_path: Option<String>,
1542 },
1543 #[serde(rename = "initialize")]
1545 Initialize {
1546 hooks: Option<serde_json::Value>,
1548 #[serde(skip_serializing_if = "Option::is_none")]
1550 agents: Option<serde_json::Value>,
1551 },
1552 #[serde(rename = "set_permission_mode")]
1554 SetPermissionMode {
1555 mode: String,
1557 },
1558 #[serde(rename = "set_model")]
1560 SetModel {
1561 model: String,
1563 },
1564 #[serde(rename = "hook_callback")]
1566 HookCallback {
1567 callback_id: String,
1569 input: serde_json::Value,
1571 tool_use_id: Option<String>,
1573 },
1574 #[serde(rename = "mcp_message")]
1576 McpMessage {
1577 server_name: String,
1579 message: serde_json::Value,
1581 },
1582 #[serde(rename = "mcp_status")]
1584 McpStatus,
1585 #[serde(rename = "rewind_files")]
1587 RewindFiles {
1588 user_message_id: String,
1590 },
1591}
1592
1593#[derive(Debug, Clone, Serialize, Deserialize)]
1595pub struct ControlRequest {
1596 #[serde(rename = "type")]
1598 pub request_type: String,
1599 pub request_id: String,
1601 pub request: ControlRequestPayload,
1603}
1604
1605#[derive(Debug, Clone, Serialize, Deserialize)]
1607pub struct ControlSuccessResponse {
1608 pub subtype: String,
1610 pub request_id: String,
1612 pub response: Option<serde_json::Value>,
1614}
1615
1616#[derive(Debug, Clone, Serialize, Deserialize)]
1618pub struct ControlErrorResponse {
1619 pub subtype: String,
1621 pub request_id: String,
1623 pub error: String,
1625}
1626
1627#[derive(Debug, Clone, Serialize, Deserialize)]
1629#[serde(tag = "subtype")]
1630pub enum ControlResponsePayload {
1631 #[serde(rename = "success")]
1633 Success {
1634 request_id: String,
1636 response: Option<serde_json::Value>,
1638 },
1639 #[serde(rename = "error")]
1641 Error {
1642 request_id: String,
1644 error: String,
1646 },
1647}
1648
1649#[derive(Debug, Clone, Serialize, Deserialize)]
1651pub struct ControlResponse {
1652 #[serde(rename = "type")]
1654 pub response_type: String,
1655 pub response: ControlResponsePayload,
1657}
1658
1659impl ControlResponse {
1660 pub fn request_id(&self) -> &str {
1662 match &self.response {
1663 ControlResponsePayload::Success { request_id, .. } => request_id,
1664 ControlResponsePayload::Error { request_id, .. } => request_id,
1665 }
1666 }
1667
1668 pub fn is_success(&self) -> bool {
1670 matches!(&self.response, ControlResponsePayload::Success { .. })
1671 }
1672
1673 pub fn data(&self) -> Option<&serde_json::Value> {
1675 match &self.response {
1676 ControlResponsePayload::Success { response, .. } => response.as_ref(),
1677 ControlResponsePayload::Error { .. } => None,
1678 }
1679 }
1680
1681 pub fn error(&self) -> Option<&str> {
1683 match &self.response {
1684 ControlResponsePayload::Success { .. } => None,
1685 ControlResponsePayload::Error { error, .. } => Some(error),
1686 }
1687 }
1688}
1689
1690#[cfg(test)]
1691mod tests {
1692 use super::*;
1693
1694 #[test]
1695 fn test_permission_result_allow() {
1696 let result = PermissionResult::allow();
1697 let json = serde_json::to_string(&result).unwrap();
1698 assert!(json.contains("allow"));
1699 }
1700
1701 #[test]
1702 fn test_message_parsing() {
1703 let json = r#"{"type": "assistant", "content": [{"type": "text", "text": "Hello"}], "model": "claude-3"}"#;
1704 let msg: Message = serde_json::from_str(json).unwrap();
1705 assert!(msg.is_assistant());
1706 }
1707
1708 #[test]
1709 fn test_content_block_text() {
1710 let block = ContentBlock::Text(TextBlock {
1711 text: "Hello".to_string(),
1712 });
1713 assert_eq!(block.as_text(), Some("Hello"));
1714 }
1715
1716 #[test]
1717 fn test_options_builder() {
1718 let opts = ClaudeAgentOptions::new()
1719 .with_model("claude-3-sonnet")
1720 .with_max_turns(5)
1721 .with_permission_mode(PermissionMode::AcceptEdits);
1722
1723 assert_eq!(opts.model, Some("claude-3-sonnet".to_string()));
1724 assert_eq!(opts.max_turns, Some(5));
1725 assert_eq!(opts.permission_mode, Some(PermissionMode::AcceptEdits));
1726 }
1727}