1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use validator::Validate;
11
12use crate::validated::Normalizable;
13
14#[serde_with::skip_serializing_none]
22#[derive(Debug, Clone, Serialize, Deserialize, Validate, schemars::JsonSchema)]
23#[validate(schema(function = "validate_message_request"))]
24pub struct CreateMessageRequest {
25 #[validate(length(min = 1, message = "model field is required and cannot be empty"))]
27 pub model: String,
28
29 #[validate(length(min = 1, message = "messages array is required and cannot be empty"))]
31 pub messages: Vec<InputMessage>,
32
33 #[validate(range(min = 1, message = "max_tokens must be greater than 0"))]
35 pub max_tokens: u32,
36
37 pub metadata: Option<Metadata>,
39
40 pub service_tier: Option<ServiceTier>,
42
43 pub stop_sequences: Option<Vec<String>>,
45
46 pub stream: Option<bool>,
48
49 pub system: Option<SystemContent>,
51
52 pub temperature: Option<f64>,
54
55 pub thinking: Option<ThinkingConfig>,
57
58 pub tool_choice: Option<ToolChoice>,
60
61 pub tools: Option<Vec<Tool>>,
63
64 pub top_k: Option<u32>,
66
67 pub top_p: Option<f64>,
69
70 pub container: Option<ContainerConfig>,
73
74 pub mcp_servers: Option<Vec<McpServerConfig>>,
76}
77
78impl Normalizable for CreateMessageRequest {
79 }
81
82impl CreateMessageRequest {
83 pub fn is_stream(&self) -> bool {
85 self.stream.unwrap_or(false)
86 }
87
88 pub fn get_model(&self) -> &str {
90 &self.model
91 }
92
93 pub fn has_mcp_toolset(&self) -> bool {
95 self.tools
96 .as_ref()
97 .is_some_and(|tools| tools.iter().any(|t| matches!(t, Tool::McpToolset(_))))
98 }
99
100 pub fn mcp_server_configs(&self) -> Option<&[McpServerConfig]> {
102 self.mcp_servers
103 .as_deref()
104 .filter(|servers| !servers.is_empty())
105 }
106}
107
108impl Tool {
109 fn matches_tool_choice_name(&self, name: &str) -> bool {
110 match self {
111 Self::Custom(tool) => tool.name == name,
112 Self::ToolSearch(tool) => tool.name == name,
113 Self::Bash(tool) => tool.name == name,
114 Self::TextEditor(tool) => tool.name == name,
115 Self::WebSearch(tool) => tool.name == name,
116 Self::McpToolset(toolset) => {
117 let default_enabled = toolset
118 .default_config
119 .as_ref()
120 .and_then(|config| config.enabled)
121 .unwrap_or(true);
122
123 toolset
124 .configs
125 .as_ref()
126 .and_then(|configs| configs.get(name))
127 .and_then(|config| config.enabled)
128 .unwrap_or(default_enabled)
129 }
130 }
131 }
132}
133fn validate_message_request(req: &CreateMessageRequest) -> Result<(), validator::ValidationError> {
135 if req.has_mcp_toolset() && req.mcp_server_configs().is_none() {
136 let mut e = validator::ValidationError::new("mcp_servers_required");
137 e.message = Some("mcp_servers is required when mcp_toolset tools are present".into());
138 return Err(e);
139 }
140
141 let Some(tool_choice) = &req.tool_choice else {
142 return Ok(());
143 };
144
145 let has_tools = req.tools.as_ref().is_some_and(|tools| !tools.is_empty());
146 let requires_tools = !matches!(tool_choice, ToolChoice::None);
147
148 if requires_tools && !has_tools {
149 let mut e = validator::ValidationError::new("tool_choice_requires_tools");
150 e.message = Some(
151 "Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified."
152 .into(),
153 );
154 return Err(e);
155 }
156
157 if let ToolChoice::Tool { name, .. } = tool_choice {
158 let tool_exists = req
159 .tools
160 .as_ref()
161 .is_some_and(|tools| tools.iter().any(|tool| tool.matches_tool_choice_name(name)));
162
163 if !tool_exists {
164 let mut e = validator::ValidationError::new("tool_choice_tool_not_found");
165 e.message = Some(
166 format!("Invalid value for 'tool_choice': tool '{name}' not found in 'tools'.")
167 .into(),
168 );
169 return Err(e);
170 }
171 }
172
173 Ok(())
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
178pub struct Metadata {
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub user_id: Option<String>,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
186#[serde(rename_all = "snake_case")]
187pub enum ServiceTier {
188 Auto,
189 StandardOnly,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
194#[serde(untagged)]
195pub enum SystemContent {
196 String(String),
197 Blocks(Vec<TextBlock>),
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
202pub struct InputMessage {
203 pub role: Role,
205
206 pub content: InputContent,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
212#[serde(rename_all = "lowercase")]
213pub enum Role {
214 User,
215 Assistant,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
220#[serde(untagged)]
221pub enum InputContent {
222 String(String),
223 Blocks(Vec<InputContentBlock>),
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
232#[serde(tag = "type", rename_all = "snake_case")]
233pub enum InputContentBlock {
234 Text(TextBlock),
236 Image(ImageBlock),
238 Document(DocumentBlock),
240 ToolUse(ToolUseBlock),
242 ToolResult(ToolResultBlock),
244 Thinking(ThinkingBlock),
246 RedactedThinking(RedactedThinkingBlock),
248 ServerToolUse(ServerToolUseBlock),
250 SearchResult(SearchResultBlock),
252 WebSearchToolResult(WebSearchToolResultBlock),
254 ToolSearchToolResult(ToolSearchToolResultBlock),
256 ToolReference(ToolReferenceBlock),
258}
259
260#[serde_with::skip_serializing_none]
262#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
263pub struct TextBlock {
264 pub text: String,
266
267 pub cache_control: Option<CacheControl>,
269
270 pub citations: Option<Vec<Citation>>,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
276pub struct ImageBlock {
277 pub source: ImageSource,
279
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub cache_control: Option<CacheControl>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
287#[serde(tag = "type", rename_all = "snake_case")]
288pub enum ImageSource {
289 Base64 { media_type: String, data: String },
290 Url { url: String },
291}
292
293#[serde_with::skip_serializing_none]
295#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
296pub struct DocumentBlock {
297 pub source: DocumentSource,
299
300 pub cache_control: Option<CacheControl>,
302
303 pub title: Option<String>,
305
306 pub context: Option<String>,
308
309 pub citations: Option<CitationsConfig>,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
315#[serde(tag = "type", rename_all = "snake_case")]
316pub enum DocumentSource {
317 Base64 { media_type: String, data: String },
318 Text { data: String },
319 Url { url: String },
320 Content { content: Vec<InputContentBlock> },
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
325pub struct ToolUseBlock {
326 pub id: String,
328
329 pub name: String,
331
332 pub input: Value,
334
335 #[serde(skip_serializing_if = "Option::is_none")]
337 pub cache_control: Option<CacheControl>,
338}
339
340#[serde_with::skip_serializing_none]
342#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
343pub struct ToolResultBlock {
344 pub tool_use_id: String,
346
347 pub content: Option<ToolResultContent>,
349
350 pub is_error: Option<bool>,
352
353 pub cache_control: Option<CacheControl>,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
359#[serde(untagged)]
360pub enum ToolResultContent {
361 String(String),
362 Blocks(Vec<ToolResultContentBlock>),
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
367#[serde(tag = "type", rename_all = "snake_case")]
368pub enum ToolResultContentBlock {
369 Text(TextBlock),
370 Image(ImageBlock),
371 Document(DocumentBlock),
372 SearchResult(SearchResultBlock),
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
377pub struct ThinkingBlock {
378 pub thinking: String,
380
381 pub signature: String,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
387pub struct RedactedThinkingBlock {
388 pub data: String,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
394pub struct ServerToolUseBlock {
395 pub id: String,
397
398 pub name: String,
400
401 pub input: Value,
403
404 #[serde(skip_serializing_if = "Option::is_none")]
406 pub cache_control: Option<CacheControl>,
407}
408
409#[serde_with::skip_serializing_none]
411#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
412pub struct SearchResultBlock {
413 pub source: String,
415
416 pub title: String,
418
419 pub content: Vec<TextBlock>,
421
422 pub cache_control: Option<CacheControl>,
424
425 pub citations: Option<CitationsConfig>,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
431pub struct WebSearchToolResultBlock {
432 pub tool_use_id: String,
434
435 pub content: WebSearchToolResultContent,
437
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub cache_control: Option<CacheControl>,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
445#[serde(untagged)]
446pub enum WebSearchToolResultContent {
447 Results(Vec<WebSearchResultBlock>),
448 Error(WebSearchToolResultError),
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
453pub struct WebSearchResultBlock {
454 pub title: String,
456
457 pub url: String,
459
460 pub encrypted_content: String,
462
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub page_age: Option<String>,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
470pub struct WebSearchToolResultError {
471 #[serde(rename = "type")]
472 pub error_type: String,
473 pub error_code: WebSearchToolResultErrorCode,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
478#[serde(rename_all = "snake_case")]
479pub enum WebSearchToolResultErrorCode {
480 InvalidToolInput,
481 Unavailable,
482 MaxUsesExceeded,
483 TooManyRequests,
484 QueryTooLong,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
489#[serde(tag = "type", rename_all = "snake_case")]
490pub enum CacheControl {
491 Ephemeral,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
496pub struct CitationsConfig {
497 #[serde(skip_serializing_if = "Option::is_none")]
498 pub enabled: Option<bool>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
503#[serde(tag = "type", rename_all = "snake_case")]
504#[expect(
505 clippy::enum_variant_names,
506 reason = "variant names match the OpenAI API citation type discriminators (char_location, page_location, etc.)"
507)]
508pub enum Citation {
509 CharLocation(CharLocationCitation),
510 PageLocation(PageLocationCitation),
511 ContentBlockLocation(ContentBlockLocationCitation),
512 WebSearchResultLocation(WebSearchResultLocationCitation),
513 SearchResultLocation(SearchResultLocationCitation),
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
518pub struct CharLocationCitation {
519 pub cited_text: String,
520 pub document_index: u32,
521 pub document_title: Option<String>,
522 pub start_char_index: u32,
523 pub end_char_index: u32,
524 #[serde(skip_serializing_if = "Option::is_none")]
525 pub file_id: Option<String>,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
530pub struct PageLocationCitation {
531 pub cited_text: String,
532 pub document_index: u32,
533 pub document_title: Option<String>,
534 pub start_page_number: u32,
535 pub end_page_number: u32,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
540pub struct ContentBlockLocationCitation {
541 pub cited_text: String,
542 pub document_index: u32,
543 pub document_title: Option<String>,
544 pub start_block_index: u32,
545 pub end_block_index: u32,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
550pub struct WebSearchResultLocationCitation {
551 pub cited_text: String,
552 pub url: String,
553 pub title: Option<String>,
554 pub encrypted_index: String,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
559pub struct SearchResultLocationCitation {
560 pub cited_text: String,
561 pub search_result_index: u32,
562 pub source: String,
563 pub title: Option<String>,
564 pub start_block_index: u32,
565 pub end_block_index: u32,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
574#[serde(untagged)]
575#[expect(
576 clippy::enum_variant_names,
577 reason = "ToolSearch matches Anthropic API naming"
578)]
579#[schemars(rename = "MessagesTool")]
580pub enum Tool {
581 McpToolset(McpToolset),
583 Custom(CustomTool),
588 ToolSearch(ToolSearchTool),
590 Bash(BashTool),
592 TextEditor(TextEditorTool),
594 WebSearch(WebSearchTool),
596}
597
598#[serde_with::skip_serializing_none]
600#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
601pub struct CustomTool {
602 pub name: String,
604
605 #[serde(rename = "type")]
607 pub tool_type: Option<String>,
608
609 pub description: Option<String>,
611
612 pub input_schema: InputSchema,
614
615 pub defer_loading: Option<bool>,
617
618 pub cache_control: Option<CacheControl>,
620}
621
622#[serde_with::skip_serializing_none]
624#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
625pub struct InputSchema {
626 #[serde(rename = "type")]
627 pub schema_type: String,
628
629 pub properties: Option<HashMap<String, Value>>,
630
631 pub required: Option<Vec<String>>,
632
633 #[serde(flatten)]
635 pub additional: HashMap<String, Value>,
636}
637
638#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
640pub struct BashTool {
641 #[serde(rename = "type")]
642 pub tool_type: String, pub name: String, #[serde(skip_serializing_if = "Option::is_none")]
647 pub cache_control: Option<CacheControl>,
648}
649
650#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
652pub struct TextEditorTool {
653 #[serde(rename = "type")]
654 pub tool_type: String, pub name: String, #[serde(skip_serializing_if = "Option::is_none")]
659 pub cache_control: Option<CacheControl>,
660}
661
662#[serde_with::skip_serializing_none]
664#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
665pub struct WebSearchTool {
666 #[serde(rename = "type")]
667 pub tool_type: String, pub name: String, pub allowed_domains: Option<Vec<String>>,
672
673 pub blocked_domains: Option<Vec<String>>,
674
675 pub max_uses: Option<u32>,
676
677 pub user_location: Option<UserLocation>,
678
679 pub cache_control: Option<CacheControl>,
680}
681
682#[serde_with::skip_serializing_none]
684#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
685pub struct UserLocation {
686 #[serde(rename = "type")]
687 pub location_type: String, pub city: Option<String>,
690
691 pub region: Option<String>,
692
693 pub country: Option<String>,
694
695 pub timezone: Option<String>,
696}
697
698#[serde_with::skip_serializing_none]
704#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
705#[serde(tag = "type", rename_all = "snake_case")]
706#[schemars(rename = "MessagesToolChoice")]
707pub enum ToolChoice {
708 Auto {
710 disable_parallel_tool_use: Option<bool>,
711 },
712 Any {
714 disable_parallel_tool_use: Option<bool>,
715 },
716 Tool {
718 name: String,
719 disable_parallel_tool_use: Option<bool>,
720 },
721 None,
723}
724
725#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
731#[serde(tag = "type", rename_all = "snake_case")]
732pub enum ThinkingConfig {
733 Enabled {
735 budget_tokens: u32,
737 },
738 Disabled,
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
748pub struct Message {
749 pub id: String,
751
752 #[serde(rename = "type")]
754 pub message_type: String,
755
756 pub role: String,
758
759 pub content: Vec<ContentBlock>,
761
762 pub model: String,
764
765 pub stop_reason: Option<StopReason>,
767
768 pub stop_sequence: Option<String>,
770
771 pub usage: Usage,
773}
774
775#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
777#[serde(tag = "type", rename_all = "snake_case")]
778pub enum ContentBlock {
779 Text {
781 text: String,
782 #[serde(skip_serializing_if = "Option::is_none")]
783 citations: Option<Vec<Citation>>,
784 },
785 ToolUse {
787 id: String,
788 name: String,
789 input: Value,
790 },
791 Thinking { thinking: String, signature: String },
793 RedactedThinking { data: String },
795 ServerToolUse {
797 id: String,
798 name: String,
799 input: Value,
800 },
801 WebSearchToolResult {
803 tool_use_id: String,
804 content: WebSearchToolResultContent,
805 },
806 ToolSearchToolResult {
808 tool_use_id: String,
809 content: ToolSearchResultContent,
810 },
811 ToolReference {
813 tool_name: String,
814 #[serde(skip_serializing_if = "Option::is_none")]
815 description: Option<String>,
816 },
817 McpToolUse {
819 id: String,
820 name: String,
821 server_name: String,
822 input: Value,
823 },
824 McpToolResult {
826 tool_use_id: String,
827 content: Option<ToolResultContent>,
828 is_error: Option<bool>,
829 },
830}
831
832#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
834#[serde(rename_all = "snake_case")]
835pub enum StopReason {
836 EndTurn,
838 MaxTokens,
840 StopSequence,
842 ToolUse,
844 PauseTurn,
846 Refusal,
848}
849
850#[serde_with::skip_serializing_none]
852#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
853#[schemars(rename = "MessagesUsage")]
854pub struct Usage {
855 pub input_tokens: u32,
857
858 pub output_tokens: u32,
860
861 pub cache_creation_input_tokens: Option<u32>,
863
864 pub cache_read_input_tokens: Option<u32>,
866
867 pub cache_creation: Option<CacheCreation>,
869
870 pub server_tool_use: Option<ServerToolUsage>,
872
873 pub service_tier: Option<String>,
875}
876
877#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
879pub struct CacheCreation {
880 #[serde(flatten)]
881 pub tokens_by_ttl: HashMap<String, u32>,
882}
883
884#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
886pub struct ServerToolUsage {
887 pub web_search_requests: u32,
888}
889
890#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
896#[serde(tag = "type", rename_all = "snake_case")]
897pub enum MessageStreamEvent {
898 MessageStart { message: Message },
900 MessageDelta {
902 delta: MessageDelta,
903 usage: MessageDeltaUsage,
904 },
905 MessageStop,
907 ContentBlockStart {
909 index: u32,
910 content_block: ContentBlock,
911 },
912 ContentBlockDelta {
914 index: u32,
915 delta: ContentBlockDelta,
916 },
917 ContentBlockStop { index: u32 },
919 Ping,
921 Error { error: ErrorResponse },
923}
924
925#[serde_with::skip_serializing_none]
927#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
928pub struct MessageDelta {
929 pub stop_reason: Option<StopReason>,
930
931 pub stop_sequence: Option<String>,
932}
933
934#[serde_with::skip_serializing_none]
936#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
937pub struct MessageDeltaUsage {
938 pub output_tokens: u32,
939
940 pub input_tokens: Option<u32>,
941
942 pub cache_creation_input_tokens: Option<u32>,
943
944 pub cache_read_input_tokens: Option<u32>,
945
946 pub server_tool_use: Option<ServerToolUsage>,
947}
948
949#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
951#[serde(tag = "type", rename_all = "snake_case")]
952#[expect(
953 clippy::enum_variant_names,
954 reason = "variant names match the OpenAI/Anthropic streaming delta type discriminators (text_delta, input_json_delta, etc.)"
955)]
956pub enum ContentBlockDelta {
957 TextDelta { text: String },
959 InputJsonDelta { partial_json: String },
961 ThinkingDelta { thinking: String },
963 SignatureDelta { signature: String },
965 CitationsDelta { citation: Citation },
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
975#[schemars(rename = "MessagesErrorResponse")]
976pub struct ErrorResponse {
977 #[serde(rename = "type")]
978 pub error_type: String,
979
980 pub message: String,
981}
982
983#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
985#[serde(tag = "type", rename_all = "snake_case")]
986#[expect(
987 clippy::enum_variant_names,
988 reason = "variant names match the OpenAI API error type discriminators (invalid_request_error, authentication_error, etc.)"
989)]
990pub enum ApiError {
991 InvalidRequestError { message: String },
992 AuthenticationError { message: String },
993 BillingError { message: String },
994 PermissionError { message: String },
995 NotFoundError { message: String },
996 RateLimitError { message: String },
997 TimeoutError { message: String },
998 ApiError { message: String },
999 OverloadedError { message: String },
1000}
1001
1002#[serde_with::skip_serializing_none]
1008#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1009pub struct CountMessageTokensRequest {
1010 pub model: String,
1012
1013 pub messages: Vec<InputMessage>,
1015
1016 pub system: Option<SystemContent>,
1018
1019 pub thinking: Option<ThinkingConfig>,
1021
1022 pub tool_choice: Option<ToolChoice>,
1024
1025 pub tools: Option<Vec<Tool>>,
1027}
1028
1029#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1031pub struct CountMessageTokensResponse {
1032 pub input_tokens: u32,
1033}
1034
1035#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1041pub struct ModelInfo {
1042 #[serde(rename = "type")]
1044 pub model_type: String,
1045
1046 pub id: String,
1048
1049 pub display_name: String,
1051
1052 pub created_at: String,
1054}
1055
1056#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1058pub struct ListModelsResponse {
1059 pub data: Vec<ModelInfo>,
1060 pub has_more: bool,
1061 pub first_id: Option<String>,
1062 pub last_id: Option<String>,
1063}
1064
1065#[serde_with::skip_serializing_none]
1071#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1072pub struct ContainerConfig {
1073 pub id: Option<String>,
1075
1076 pub skills: Option<Vec<String>>,
1078}
1079
1080#[serde_with::skip_serializing_none]
1082#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1083pub struct McpServerConfig {
1084 #[serde(rename = "type", default = "McpServerConfig::default_type")]
1086 pub server_type: String,
1087
1088 pub name: String,
1090
1091 pub url: String,
1093
1094 pub authorization_token: Option<String>,
1096
1097 pub tool_configuration: Option<McpToolConfiguration>,
1099}
1100
1101impl McpServerConfig {
1102 fn default_type() -> String {
1103 "url".to_string()
1104 }
1105}
1106
1107#[serde_with::skip_serializing_none]
1109#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1110pub struct McpToolConfiguration {
1111 pub enabled: Option<bool>,
1113
1114 pub allowed_tools: Option<Vec<String>>,
1116}
1117
1118#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1124pub struct McpToolUseBlock {
1125 pub id: String,
1127
1128 pub name: String,
1130
1131 pub server_name: String,
1133
1134 pub input: Value,
1136
1137 #[serde(skip_serializing_if = "Option::is_none")]
1139 pub cache_control: Option<CacheControl>,
1140}
1141
1142#[serde_with::skip_serializing_none]
1144#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1145pub struct McpToolResultBlock {
1146 pub tool_use_id: String,
1148
1149 pub content: Option<ToolResultContent>,
1151
1152 pub is_error: Option<bool>,
1154
1155 pub cache_control: Option<CacheControl>,
1157}
1158
1159#[serde_with::skip_serializing_none]
1161#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1162pub struct McpToolset {
1163 #[serde(rename = "type")]
1164 pub toolset_type: String, pub mcp_server_name: String,
1168
1169 pub default_config: Option<McpToolDefaultConfig>,
1171
1172 pub configs: Option<HashMap<String, McpToolConfig>>,
1174
1175 pub cache_control: Option<CacheControl>,
1177}
1178
1179#[serde_with::skip_serializing_none]
1181#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1182pub struct McpToolDefaultConfig {
1183 pub enabled: Option<bool>,
1185
1186 pub defer_loading: Option<bool>,
1188}
1189
1190#[serde_with::skip_serializing_none]
1192#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1193pub struct McpToolConfig {
1194 pub enabled: Option<bool>,
1196
1197 pub defer_loading: Option<bool>,
1199}
1200
1201#[serde_with::skip_serializing_none]
1207#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1208pub struct CodeExecutionTool {
1209 #[serde(rename = "type")]
1210 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1216
1217 pub defer_loading: Option<bool>,
1219
1220 pub strict: Option<bool>,
1222
1223 pub cache_control: Option<CacheControl>,
1225}
1226
1227#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1229pub struct CodeExecutionResultBlock {
1230 pub stdout: String,
1232
1233 pub stderr: String,
1235
1236 pub return_code: i32,
1238
1239 pub content: Vec<CodeExecutionOutputBlock>,
1241}
1242
1243#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1245pub struct CodeExecutionOutputBlock {
1246 #[serde(rename = "type")]
1247 pub block_type: String, pub file_id: String,
1251}
1252
1253#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1255pub struct CodeExecutionToolResultBlock {
1256 pub tool_use_id: String,
1258
1259 pub content: CodeExecutionToolResultContent,
1261
1262 #[serde(skip_serializing_if = "Option::is_none")]
1264 pub cache_control: Option<CacheControl>,
1265}
1266
1267#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1269#[serde(untagged)]
1270pub enum CodeExecutionToolResultContent {
1271 Success(CodeExecutionResultBlock),
1272 Error(CodeExecutionToolResultError),
1273}
1274
1275#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1277pub struct CodeExecutionToolResultError {
1278 #[serde(rename = "type")]
1279 pub error_type: String, pub error_code: CodeExecutionToolResultErrorCode,
1282}
1283
1284#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1286#[serde(rename_all = "snake_case")]
1287pub enum CodeExecutionToolResultErrorCode {
1288 Unavailable,
1289 CodeExecutionExceededTimeout,
1290 ContainerExpired,
1291 InvalidToolInput,
1292}
1293
1294#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1296pub struct BashCodeExecutionResultBlock {
1297 pub stdout: String,
1299
1300 pub stderr: String,
1302
1303 pub return_code: i32,
1305
1306 pub content: Vec<BashCodeExecutionOutputBlock>,
1308}
1309
1310#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1312pub struct BashCodeExecutionOutputBlock {
1313 #[serde(rename = "type")]
1314 pub block_type: String, pub file_id: String,
1318}
1319
1320#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1322pub struct BashCodeExecutionToolResultBlock {
1323 pub tool_use_id: String,
1325
1326 pub content: BashCodeExecutionToolResultContent,
1328
1329 #[serde(skip_serializing_if = "Option::is_none")]
1331 pub cache_control: Option<CacheControl>,
1332}
1333
1334#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1336#[serde(untagged)]
1337pub enum BashCodeExecutionToolResultContent {
1338 Success(BashCodeExecutionResultBlock),
1339 Error(BashCodeExecutionToolResultError),
1340}
1341
1342#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1344pub struct BashCodeExecutionToolResultError {
1345 #[serde(rename = "type")]
1346 pub error_type: String, pub error_code: BashCodeExecutionToolResultErrorCode,
1349}
1350
1351#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1353#[serde(rename_all = "snake_case")]
1354pub enum BashCodeExecutionToolResultErrorCode {
1355 Unavailable,
1356 CodeExecutionExceededTimeout,
1357 ContainerExpired,
1358 InvalidToolInput,
1359}
1360
1361#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1363pub struct TextEditorCodeExecutionToolResultBlock {
1364 pub tool_use_id: String,
1366
1367 pub content: TextEditorCodeExecutionToolResultContent,
1369
1370 #[serde(skip_serializing_if = "Option::is_none")]
1372 pub cache_control: Option<CacheControl>,
1373}
1374
1375#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1377#[serde(untagged)]
1378pub enum TextEditorCodeExecutionToolResultContent {
1379 CreateResult(TextEditorCodeExecutionCreateResultBlock),
1380 StrReplaceResult(TextEditorCodeExecutionStrReplaceResultBlock),
1381 ViewResult(TextEditorCodeExecutionViewResultBlock),
1382 Error(TextEditorCodeExecutionToolResultError),
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1387pub struct TextEditorCodeExecutionCreateResultBlock {
1388 #[serde(rename = "type")]
1389 pub block_type: String, }
1391
1392#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1394pub struct TextEditorCodeExecutionStrReplaceResultBlock {
1395 #[serde(rename = "type")]
1396 pub block_type: String, #[serde(skip_serializing_if = "Option::is_none")]
1400 pub snippet: Option<String>,
1401}
1402
1403#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1405pub struct TextEditorCodeExecutionViewResultBlock {
1406 #[serde(rename = "type")]
1407 pub block_type: String, pub content: String,
1411}
1412
1413#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1415pub struct TextEditorCodeExecutionToolResultError {
1416 #[serde(rename = "type")]
1417 pub error_type: String,
1418
1419 pub error_code: TextEditorCodeExecutionToolResultErrorCode,
1420}
1421
1422#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1424#[serde(rename_all = "snake_case")]
1425pub enum TextEditorCodeExecutionToolResultErrorCode {
1426 Unavailable,
1427 InvalidToolInput,
1428 FileNotFound,
1429 ContainerExpired,
1430}
1431
1432#[serde_with::skip_serializing_none]
1438#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1439pub struct WebFetchTool {
1440 #[serde(rename = "type")]
1441 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1447
1448 pub max_uses: Option<u32>,
1450
1451 pub cache_control: Option<CacheControl>,
1453}
1454
1455#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1457pub struct WebFetchResultBlock {
1458 #[serde(rename = "type")]
1459 pub block_type: String, pub url: String,
1463
1464 pub content: DocumentBlock,
1466
1467 #[serde(skip_serializing_if = "Option::is_none")]
1469 pub retrieved_at: Option<String>,
1470}
1471
1472#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1474pub struct WebFetchToolResultBlock {
1475 pub tool_use_id: String,
1477
1478 pub content: WebFetchToolResultContent,
1480
1481 #[serde(skip_serializing_if = "Option::is_none")]
1483 pub cache_control: Option<CacheControl>,
1484}
1485
1486#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1488#[serde(untagged)]
1489pub enum WebFetchToolResultContent {
1490 Success(WebFetchResultBlock),
1491 Error(WebFetchToolResultError),
1492}
1493
1494#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1496pub struct WebFetchToolResultError {
1497 #[serde(rename = "type")]
1498 pub error_type: String, pub error_code: WebFetchToolResultErrorCode,
1501}
1502
1503#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1505#[serde(rename_all = "snake_case")]
1506pub enum WebFetchToolResultErrorCode {
1507 InvalidToolInput,
1508 Unavailable,
1509 MaxUsesExceeded,
1510 TooManyRequests,
1511 UrlNotAllowed,
1512 FetchFailed,
1513 ContentTooLarge,
1514}
1515
1516#[serde_with::skip_serializing_none]
1522#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1523pub struct ToolSearchTool {
1524 #[serde(rename = "type")]
1525 pub tool_type: String, pub name: String,
1528
1529 pub allowed_callers: Option<Vec<String>>,
1531
1532 pub cache_control: Option<CacheControl>,
1534}
1535
1536#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1538pub struct ToolReferenceBlock {
1539 #[serde(rename = "type")]
1540 pub block_type: String, pub tool_name: String,
1544
1545 #[serde(skip_serializing_if = "Option::is_none")]
1547 pub description: Option<String>,
1548}
1549
1550#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1552pub struct ToolSearchResultContent {
1553 #[serde(rename = "type")]
1554 pub block_type: String, pub tool_references: Vec<ToolReferenceBlock>,
1558}
1559
1560#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1562pub struct ToolSearchToolResultBlock {
1563 pub tool_use_id: String,
1565
1566 pub content: ToolSearchResultContent,
1568
1569 #[serde(skip_serializing_if = "Option::is_none")]
1571 pub cache_control: Option<CacheControl>,
1572}
1573
1574#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1580pub struct ContainerUploadBlock {
1581 #[serde(rename = "type")]
1582 pub block_type: String, pub file_id: String,
1586
1587 pub file_name: String,
1589
1590 #[serde(skip_serializing_if = "Option::is_none")]
1592 pub file_path: Option<String>,
1593}
1594
1595#[serde_with::skip_serializing_none]
1601#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1602pub struct MemoryTool {
1603 #[serde(rename = "type")]
1604 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1610
1611 pub defer_loading: Option<bool>,
1613
1614 pub strict: Option<bool>,
1616
1617 pub input_examples: Option<Vec<Value>>,
1619
1620 pub cache_control: Option<CacheControl>,
1622}
1623
1624#[serde_with::skip_serializing_none]
1630#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1631pub struct ComputerUseTool {
1632 #[serde(rename = "type")]
1633 pub tool_type: String, pub name: String, pub display_width_px: u32,
1639
1640 pub display_height_px: u32,
1642
1643 pub display_number: Option<u32>,
1645
1646 pub allowed_callers: Option<Vec<String>>,
1648
1649 pub cache_control: Option<CacheControl>,
1651}
1652
1653#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1659#[serde(tag = "type", rename_all = "snake_case")]
1660pub enum BetaInputContentBlock {
1661 Text(TextBlock),
1663 Image(ImageBlock),
1664 Document(DocumentBlock),
1665 ToolUse(ToolUseBlock),
1666 ToolResult(ToolResultBlock),
1667 Thinking(ThinkingBlock),
1668 RedactedThinking(RedactedThinkingBlock),
1669 ServerToolUse(ServerToolUseBlock),
1670 SearchResult(SearchResultBlock),
1671 WebSearchToolResult(WebSearchToolResultBlock),
1672
1673 McpToolUse(McpToolUseBlock),
1675 McpToolResult(McpToolResultBlock),
1676
1677 CodeExecutionToolResult(CodeExecutionToolResultBlock),
1679 BashCodeExecutionToolResult(BashCodeExecutionToolResultBlock),
1680 TextEditorCodeExecutionToolResult(TextEditorCodeExecutionToolResultBlock),
1681
1682 WebFetchToolResult(WebFetchToolResultBlock),
1684
1685 ToolSearchToolResult(ToolSearchToolResultBlock),
1687 ToolReference(ToolReferenceBlock),
1688
1689 ContainerUpload(ContainerUploadBlock),
1691}
1692
1693#[serde_with::skip_serializing_none]
1695#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1696#[serde(tag = "type", rename_all = "snake_case")]
1697pub enum BetaContentBlock {
1698 Text {
1700 text: String,
1701 citations: Option<Vec<Citation>>,
1702 },
1703 ToolUse {
1704 id: String,
1705 name: String,
1706 input: Value,
1707 },
1708 Thinking {
1709 thinking: String,
1710 signature: String,
1711 },
1712 RedactedThinking {
1713 data: String,
1714 },
1715 ServerToolUse {
1716 id: String,
1717 name: String,
1718 input: Value,
1719 },
1720 WebSearchToolResult {
1721 tool_use_id: String,
1722 content: WebSearchToolResultContent,
1723 },
1724
1725 McpToolUse {
1727 id: String,
1728 name: String,
1729 server_name: String,
1730 input: Value,
1731 },
1732 McpToolResult {
1733 tool_use_id: String,
1734 content: Option<ToolResultContent>,
1735 is_error: Option<bool>,
1736 },
1737
1738 CodeExecutionToolResult {
1740 tool_use_id: String,
1741 content: CodeExecutionToolResultContent,
1742 },
1743 BashCodeExecutionToolResult {
1744 tool_use_id: String,
1745 content: BashCodeExecutionToolResultContent,
1746 },
1747 TextEditorCodeExecutionToolResult {
1748 tool_use_id: String,
1749 content: TextEditorCodeExecutionToolResultContent,
1750 },
1751
1752 WebFetchToolResult {
1754 tool_use_id: String,
1755 content: WebFetchToolResultContent,
1756 },
1757
1758 ToolSearchToolResult {
1760 tool_use_id: String,
1761 content: ToolSearchResultContent,
1762 },
1763 ToolReference {
1764 tool_name: String,
1765 description: Option<String>,
1766 },
1767
1768 ContainerUpload {
1770 file_id: String,
1771 file_name: String,
1772 file_path: Option<String>,
1773 },
1774}
1775
1776#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1778#[serde(untagged)]
1779pub enum BetaTool {
1780 Custom(CustomTool),
1782 Bash(BashTool),
1783 TextEditor(TextEditorTool),
1784 WebSearch(WebSearchTool),
1785
1786 CodeExecution(CodeExecutionTool),
1788 McpToolset(McpToolset),
1789 WebFetch(WebFetchTool),
1790 ToolSearch(ToolSearchTool),
1791 Memory(MemoryTool),
1792 ComputerUse(ComputerUseTool),
1793}
1794
1795#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1797#[serde(rename_all = "snake_case")]
1798pub enum BetaServerToolName {
1799 WebSearch,
1800 WebFetch,
1801 CodeExecution,
1802 BashCodeExecution,
1803 TextEditorCodeExecution,
1804 ToolSearchToolRegex,
1805 ToolSearchToolBm25,
1806}
1807
1808#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1810#[serde(tag = "type", rename_all = "snake_case")]
1811pub enum ServerToolCaller {
1812 Direct,
1814 #[serde(rename = "code_execution_20250825")]
1816 CodeExecution20250825,
1817}
1818
1819#[cfg(test)]
1820mod tests {
1821 use serde_json;
1822
1823 use super::*;
1824
1825 fn base_request() -> CreateMessageRequest {
1826 CreateMessageRequest {
1827 model: "claude-test".to_string(),
1828 messages: vec![InputMessage {
1829 role: Role::User,
1830 content: InputContent::String("hello".to_string()),
1831 }],
1832 max_tokens: 16,
1833 metadata: None,
1834 service_tier: None,
1835 stop_sequences: None,
1836 stream: None,
1837 system: None,
1838 temperature: None,
1839 thinking: None,
1840 tool_choice: None,
1841 tools: None,
1842 top_k: None,
1843 top_p: None,
1844 container: None,
1845 mcp_servers: None,
1846 }
1847 }
1848
1849 fn custom_tool(name: &str) -> Tool {
1850 Tool::Custom(CustomTool {
1851 name: name.to_string(),
1852 tool_type: None,
1853 description: Some("test tool".to_string()),
1854 input_schema: InputSchema {
1855 schema_type: "object".to_string(),
1856 properties: None,
1857 required: None,
1858 additional: HashMap::new(),
1859 },
1860 defer_loading: None,
1861 cache_control: None,
1862 })
1863 }
1864
1865 fn mcp_toolset(configs: Option<HashMap<String, McpToolConfig>>) -> Tool {
1866 Tool::McpToolset(McpToolset {
1867 toolset_type: "mcp_toolset".to_string(),
1868 mcp_server_name: "brave".to_string(),
1869 default_config: None,
1870 configs,
1871 cache_control: None,
1872 })
1873 }
1874
1875 fn mcp_server_config() -> McpServerConfig {
1876 McpServerConfig {
1877 server_type: "url".to_string(),
1878 name: "brave".to_string(),
1879 url: "https://example.com/mcp".to_string(),
1880 authorization_token: None,
1881 tool_configuration: None,
1882 }
1883 }
1884 #[test]
1885 fn test_tool_mcp_toolset_defer_loading_deserialization() {
1886 let json = r#"{
1887 "type": "mcp_toolset",
1888 "mcp_server_name": "brave",
1889 "default_config": {"defer_loading": true}
1890 }"#;
1891
1892 let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize McpToolset Tool");
1893 match tool {
1894 Tool::McpToolset(ts) => {
1895 assert_eq!(ts.mcp_server_name, "brave");
1896 let default_config = ts.default_config.expect("default_config should be Some");
1897 assert_eq!(default_config.defer_loading, Some(true));
1898 }
1899 other => panic!(
1900 "Expected McpToolset, got {:?}",
1901 std::mem::discriminant(&other)
1902 ),
1903 }
1904 }
1905
1906 #[test]
1907 fn test_tool_search_tool_deserialization() {
1908 let json = r#"{
1909 "type": "tool_search_tool_regex_20251119",
1910 "name": "tool_search_tool_regex"
1911 }"#;
1912
1913 let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize ToolSearch Tool");
1914 match tool {
1915 Tool::ToolSearch(ts) => {
1916 assert_eq!(ts.name, "tool_search_tool_regex");
1917 assert_eq!(ts.tool_type, "tool_search_tool_regex_20251119");
1918 }
1919 other => panic!(
1920 "Expected ToolSearch, got {:?}",
1921 std::mem::discriminant(&other)
1922 ),
1923 }
1924 }
1925
1926 #[test]
1927 fn test_content_block_tool_search_tool_result_deserialization() {
1928 let json = r#"{
1929 "type": "tool_search_tool_result",
1930 "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
1931 "content": {
1932 "type": "tool_search_tool_search_result",
1933 "tool_references": [
1934 {"type": "tool_reference", "tool_name": "get_weather"}
1935 ]
1936 }
1937 }"#;
1938
1939 let block: ContentBlock = serde_json::from_str(json)
1940 .expect("Failed to deserialize tool_search_tool_result ContentBlock");
1941 match block {
1942 ContentBlock::ToolSearchToolResult {
1943 tool_use_id,
1944 content,
1945 } => {
1946 assert_eq!(tool_use_id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
1947 assert_eq!(content.tool_references.len(), 1);
1948 assert_eq!(content.tool_references[0].tool_name, "get_weather");
1949 }
1950 _ => panic!("Expected ToolSearchToolResult variant"),
1951 }
1952 }
1953
1954 #[test]
1955 fn test_content_block_server_tool_use_deserialization() {
1956 let json = r#"{
1957 "type": "server_tool_use",
1958 "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
1959 "name": "tool_search_tool_regex",
1960 "input": {"query": "weather"}
1961 }"#;
1962
1963 let block: ContentBlock =
1964 serde_json::from_str(json).expect("Failed to deserialize server_tool_use ContentBlock");
1965 match block {
1966 ContentBlock::ServerToolUse { id, name, input: _ } => {
1967 assert_eq!(id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
1968 assert_eq!(name, "tool_search_tool_regex");
1969 }
1970 _ => panic!("Expected ServerToolUse variant"),
1971 }
1972 }
1973
1974 #[test]
1975 fn test_content_block_tool_reference_deserialization() {
1976 let json = r#"{
1977 "type": "tool_reference",
1978 "tool_name": "get_weather",
1979 "description": "Get the weather for a location"
1980 }"#;
1981
1982 let block: ContentBlock =
1983 serde_json::from_str(json).expect("Failed to deserialize tool_reference ContentBlock");
1984 match block {
1985 ContentBlock::ToolReference {
1986 tool_name,
1987 description,
1988 } => {
1989 assert_eq!(tool_name, "get_weather");
1990 assert_eq!(description.unwrap(), "Get the weather for a location");
1991 }
1992 _ => panic!("Expected ToolReference variant"),
1993 }
1994 }
1995
1996 #[test]
1997 fn test_tool_choice_auto_requires_tools() {
1998 let mut request = base_request();
1999 request.tool_choice = Some(ToolChoice::Auto {
2000 disable_parallel_tool_use: None,
2001 });
2002
2003 assert!(request.validate().is_err());
2004 }
2005
2006 #[test]
2007 fn test_tool_choice_any_requires_tools() {
2008 let mut request = base_request();
2009 request.tool_choice = Some(ToolChoice::Any {
2010 disable_parallel_tool_use: None,
2011 });
2012
2013 assert!(request.validate().is_err());
2014 }
2015
2016 #[test]
2017 fn test_tool_choice_auto_with_tools_is_valid() {
2018 let mut request = base_request();
2019 request.tool_choice = Some(ToolChoice::Auto {
2020 disable_parallel_tool_use: None,
2021 });
2022 request.tools = Some(vec![custom_tool("get_weather")]);
2023
2024 assert!(request.validate().is_ok());
2025 }
2026
2027 #[test]
2028 fn test_tool_choice_any_with_tools_is_valid() {
2029 let mut request = base_request();
2030 request.tool_choice = Some(ToolChoice::Any {
2031 disable_parallel_tool_use: None,
2032 });
2033 request.tools = Some(vec![custom_tool("get_weather")]);
2034
2035 assert!(request.validate().is_ok());
2036 }
2037
2038 #[test]
2039 fn test_tool_choice_specific_tool_requires_tools() {
2040 let mut request = base_request();
2041 request.tool_choice = Some(ToolChoice::Tool {
2042 name: "get_weather".to_string(),
2043 disable_parallel_tool_use: None,
2044 });
2045
2046 assert!(request.validate().is_err());
2047 }
2048
2049 #[test]
2050 fn test_tool_choice_specific_tool_must_exist() {
2051 let mut request = base_request();
2052 request.tool_choice = Some(ToolChoice::Tool {
2053 name: "get_weather".to_string(),
2054 disable_parallel_tool_use: None,
2055 });
2056 request.tools = Some(vec![custom_tool("search_web")]);
2057
2058 assert!(request.validate().is_err());
2059 }
2060
2061 #[test]
2062 fn test_tool_choice_none_without_tools_is_valid() {
2063 let mut request = base_request();
2064 request.tool_choice = Some(ToolChoice::None);
2065
2066 assert!(request.validate().is_ok());
2067 }
2068
2069 #[test]
2070 fn test_tool_choice_specific_tool_is_valid_when_declared() {
2071 let mut request = base_request();
2072 request.tool_choice = Some(ToolChoice::Tool {
2073 name: "get_weather".to_string(),
2074 disable_parallel_tool_use: None,
2075 });
2076 request.tools = Some(vec![custom_tool("get_weather")]);
2077
2078 assert!(request.validate().is_ok());
2079 }
2080
2081 #[test]
2082 fn test_tool_choice_specific_tool_is_valid_with_mcp_toolset() {
2083 let mut request = base_request();
2084 request.tool_choice = Some(ToolChoice::Tool {
2085 name: "get_weather".to_string(),
2086 disable_parallel_tool_use: None,
2087 });
2088 request.tools = Some(vec![mcp_toolset(None)]);
2089 request.mcp_servers = Some(vec![mcp_server_config()]);
2090
2091 assert!(request.validate().is_ok());
2092 }
2093
2094 #[test]
2095 fn test_tool_choice_specific_tool_uses_mcp_toolset_default_when_override_missing() {
2096 let mut request = base_request();
2097 request.tool_choice = Some(ToolChoice::Tool {
2098 name: "get_weather".to_string(),
2099 disable_parallel_tool_use: None,
2100 });
2101 request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2102 "search_web".to_string(),
2103 McpToolConfig {
2104 enabled: Some(false),
2105 defer_loading: None,
2106 },
2107 )])))]);
2108 request.mcp_servers = Some(vec![mcp_server_config()]);
2109
2110 assert!(request.validate().is_ok());
2111 }
2112
2113 #[test]
2114 fn test_tool_choice_specific_tool_must_be_enabled_in_mcp_toolset_configs() {
2115 let mut request = base_request();
2116 request.tool_choice = Some(ToolChoice::Tool {
2117 name: "get_weather".to_string(),
2118 disable_parallel_tool_use: None,
2119 });
2120 request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2121 "get_weather".to_string(),
2122 McpToolConfig {
2123 enabled: Some(false),
2124 defer_loading: None,
2125 },
2126 )])))]);
2127 request.mcp_servers = Some(vec![mcp_server_config()]);
2128
2129 assert!(request.validate().is_err());
2130 }
2131
2132 #[test]
2133 fn test_full_message_with_tool_search_flow_deserialization() {
2134 let json = r#"{
2136 "id": "msg_01TEST",
2137 "type": "message",
2138 "role": "assistant",
2139 "model": "claude-sonnet-4-5-20250929",
2140 "content": [
2141 {
2142 "type": "server_tool_use",
2143 "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2144 "name": "tool_search_tool_regex",
2145 "input": {"query": "weather"}
2146 },
2147 {
2148 "type": "tool_search_tool_result",
2149 "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2150 "content": {
2151 "type": "tool_search_tool_search_result",
2152 "tool_references": [
2153 {"type": "tool_reference", "tool_name": "get_weather"}
2154 ]
2155 }
2156 },
2157 {
2158 "type": "tool_use",
2159 "id": "toolu_01ABC",
2160 "name": "get_weather",
2161 "input": {"location": "San Francisco"}
2162 }
2163 ],
2164 "stop_reason": "tool_use",
2165 "stop_sequence": null,
2166 "usage": {
2167 "input_tokens": 100,
2168 "output_tokens": 50
2169 }
2170 }"#;
2171
2172 let msg: Message = serde_json::from_str(json)
2173 .expect("Failed to deserialize Message with tool search flow");
2174 assert_eq!(msg.content.len(), 3);
2175 assert!(matches!(msg.content[0], ContentBlock::ServerToolUse { .. }));
2176 assert!(matches!(
2177 msg.content[1],
2178 ContentBlock::ToolSearchToolResult { .. }
2179 ));
2180 assert!(matches!(msg.content[2], ContentBlock::ToolUse { .. }));
2181 }
2182}