1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9use serde_json::{Map, Value};
10use validator::Validate;
11
12use crate::{common::GenerationRequest, 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 #[serde(flatten)]
80 pub other: Map<String, Value>,
81}
82
83impl Normalizable for CreateMessageRequest {
84 }
86
87impl CreateMessageRequest {
88 pub fn is_stream(&self) -> bool {
90 self.stream.unwrap_or(false)
91 }
92
93 pub fn get_model(&self) -> &str {
95 &self.model
96 }
97
98 pub fn has_mcp_toolset(&self) -> bool {
100 self.tools
101 .as_ref()
102 .is_some_and(|tools| tools.iter().any(|t| matches!(t, Tool::McpToolset(_))))
103 }
104
105 pub fn mcp_server_configs(&self) -> Option<&[McpServerConfig]> {
107 self.mcp_servers
108 .as_deref()
109 .filter(|servers| !servers.is_empty())
110 }
111}
112
113impl GenerationRequest for CreateMessageRequest {
114 fn is_stream(&self) -> bool {
115 self.stream.unwrap_or(false)
116 }
117
118 fn get_model(&self) -> Option<&str> {
119 Some(&self.model)
120 }
121
122 fn extract_text_for_routing(&self) -> String {
123 let mut buffer = String::new();
124 let mut has_content = false;
125
126 let push = |s: &str, has_content: &mut bool, buffer: &mut String| {
127 if s.is_empty() {
128 return;
129 }
130 if *has_content {
131 buffer.push(' ');
132 }
133 buffer.push_str(s);
134 *has_content = true;
135 };
136
137 if let Some(system) = &self.system {
138 match system {
139 SystemContent::String(s) => push(s, &mut has_content, &mut buffer),
140 SystemContent::Blocks(blocks) => {
141 for block in blocks {
142 let SystemContentBlock::Text(text_block) = block;
143 push(&text_block.text, &mut has_content, &mut buffer);
144 }
145 }
146 }
147 }
148
149 for msg in &self.messages {
150 match &msg.content {
151 InputContent::String(s) => push(s, &mut has_content, &mut buffer),
152 InputContent::Blocks(blocks) => {
153 for block in blocks {
154 if let InputContentBlock::Text(text_block) = block {
155 push(&text_block.text, &mut has_content, &mut buffer);
156 }
157 }
158 }
159 }
160 }
161
162 buffer
163 }
164}
165
166impl Tool {
167 fn matches_tool_choice_name(&self, name: &str) -> bool {
168 match self {
169 Self::Custom(tool) => tool.name == name,
170 Self::ToolSearch(tool) => tool.name == name,
171 Self::Bash(tool) => tool.name == name,
172 Self::TextEditor(tool) => tool.name == name,
173 Self::WebSearch(tool) => tool.name == name,
174 Self::McpToolset(toolset) => {
175 let default_enabled = toolset
176 .default_config
177 .as_ref()
178 .and_then(|config| config.enabled)
179 .unwrap_or(true);
180
181 toolset
182 .configs
183 .as_ref()
184 .and_then(|configs| configs.get(name))
185 .and_then(|config| config.enabled)
186 .unwrap_or(default_enabled)
187 }
188 }
189 }
190}
191fn validate_message_request(req: &CreateMessageRequest) -> Result<(), validator::ValidationError> {
193 if req.has_mcp_toolset() && req.mcp_server_configs().is_none() {
194 let mut e = validator::ValidationError::new("mcp_servers_required");
195 e.message = Some("mcp_servers is required when mcp_toolset tools are present".into());
196 return Err(e);
197 }
198
199 let Some(tool_choice) = &req.tool_choice else {
200 return Ok(());
201 };
202
203 let has_tools = req.tools.as_ref().is_some_and(|tools| !tools.is_empty());
204 let requires_tools = !matches!(tool_choice, ToolChoice::None);
205
206 if requires_tools && !has_tools {
207 let mut e = validator::ValidationError::new("tool_choice_requires_tools");
208 e.message = Some(
209 "Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified."
210 .into(),
211 );
212 return Err(e);
213 }
214
215 if let ToolChoice::Tool { name, .. } = tool_choice {
216 let tool_exists = req
217 .tools
218 .as_ref()
219 .is_some_and(|tools| tools.iter().any(|tool| tool.matches_tool_choice_name(name)));
220
221 if !tool_exists {
222 let mut e = validator::ValidationError::new("tool_choice_tool_not_found");
223 e.message = Some(
224 format!("Invalid value for 'tool_choice': tool '{name}' not found in 'tools'.")
225 .into(),
226 );
227 return Err(e);
228 }
229 }
230
231 Ok(())
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
236pub struct Metadata {
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub user_id: Option<String>,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
244#[serde(rename_all = "snake_case")]
245pub enum ServiceTier {
246 Auto,
247 StandardOnly,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
252#[serde(untagged)]
253pub enum SystemContent {
254 String(String),
255 Blocks(Vec<SystemContentBlock>),
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
261#[serde(tag = "type", rename_all = "snake_case")]
262pub enum SystemContentBlock {
263 Text(TextBlock),
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
268pub struct InputMessage {
269 pub role: Role,
271
272 pub content: InputContent,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
278#[serde(rename_all = "lowercase")]
279pub enum Role {
280 User,
281 Assistant,
282 System,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
297#[serde(untagged)]
298pub enum InputContent {
299 String(String),
300 Blocks(Vec<InputContentBlock>),
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
309#[serde(tag = "type", rename_all = "snake_case")]
310pub enum InputContentBlock {
311 Text(TextBlock),
313 Image(ImageBlock),
315 Document(DocumentBlock),
317 ToolUse(ToolUseBlock),
319 ToolResult(ToolResultBlock),
321 Thinking(ThinkingBlock),
323 RedactedThinking(RedactedThinkingBlock),
325 ServerToolUse(ServerToolUseBlock),
327 SearchResult(SearchResultBlock),
329 WebSearchToolResult(WebSearchToolResultBlock),
331 ToolSearchToolResult(ToolSearchToolResultBlock),
333 ToolReference(ToolReferenceBlock),
335}
336
337#[serde_with::skip_serializing_none]
339#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
340pub struct TextBlock {
341 pub text: String,
343
344 pub cache_control: Option<CacheControl>,
346
347 pub citations: Option<Vec<Citation>>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
353pub struct ImageBlock {
354 pub source: ImageSource,
356
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub cache_control: Option<CacheControl>,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
364#[serde(tag = "type", rename_all = "snake_case")]
365pub enum ImageSource {
366 Base64 { media_type: String, data: String },
367 Url { url: String },
368}
369
370#[serde_with::skip_serializing_none]
372#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
373pub struct DocumentBlock {
374 pub source: DocumentSource,
376
377 pub cache_control: Option<CacheControl>,
379
380 pub title: Option<String>,
382
383 pub context: Option<String>,
385
386 pub citations: Option<CitationsConfig>,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
392#[serde(tag = "type", rename_all = "snake_case")]
393pub enum DocumentSource {
394 Base64 { media_type: String, data: String },
395 Text { data: String },
396 Url { url: String },
397 Content { content: Vec<InputContentBlock> },
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
402pub struct ToolUseBlock {
403 pub id: String,
405
406 pub name: String,
408
409 pub input: Value,
411
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub cache_control: Option<CacheControl>,
415}
416
417#[serde_with::skip_serializing_none]
419#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
420pub struct ToolResultBlock {
421 pub tool_use_id: String,
423
424 pub content: Option<ToolResultContent>,
426
427 pub is_error: Option<bool>,
429
430 pub cache_control: Option<CacheControl>,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
436#[serde(untagged)]
437pub enum ToolResultContent {
438 String(String),
439 Blocks(Vec<ToolResultContentBlock>),
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
444#[serde(tag = "type", rename_all = "snake_case")]
445pub enum ToolResultContentBlock {
446 Text(TextBlock),
447 Image(ImageBlock),
448 Document(DocumentBlock),
449 SearchResult(SearchResultBlock),
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
454pub struct ThinkingBlock {
455 pub thinking: String,
457
458 pub signature: String,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
464pub struct RedactedThinkingBlock {
465 pub data: String,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
471pub struct ServerToolUseBlock {
472 pub id: String,
474
475 pub name: String,
477
478 pub input: Value,
480
481 #[serde(skip_serializing_if = "Option::is_none")]
483 pub cache_control: Option<CacheControl>,
484}
485
486#[serde_with::skip_serializing_none]
488#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
489pub struct SearchResultBlock {
490 pub source: String,
492
493 pub title: String,
495
496 pub content: Vec<TextBlock>,
498
499 pub cache_control: Option<CacheControl>,
501
502 pub citations: Option<CitationsConfig>,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
508pub struct WebSearchToolResultBlock {
509 pub tool_use_id: String,
511
512 pub content: WebSearchToolResultContent,
514
515 #[serde(skip_serializing_if = "Option::is_none")]
517 pub cache_control: Option<CacheControl>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
522#[serde(untagged)]
523pub enum WebSearchToolResultContent {
524 Results(Vec<WebSearchResultBlock>),
525 Error(WebSearchToolResultError),
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
530pub struct WebSearchResultBlock {
531 pub title: String,
533
534 pub url: String,
536
537 pub encrypted_content: String,
539
540 #[serde(skip_serializing_if = "Option::is_none")]
542 pub page_age: Option<String>,
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
547pub struct WebSearchToolResultError {
548 #[serde(rename = "type")]
549 pub error_type: String,
550 pub error_code: WebSearchToolResultErrorCode,
551}
552
553#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
555#[serde(rename_all = "snake_case")]
556pub enum WebSearchToolResultErrorCode {
557 InvalidToolInput,
558 Unavailable,
559 MaxUsesExceeded,
560 TooManyRequests,
561 QueryTooLong,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
566#[serde(tag = "type", rename_all = "snake_case")]
567pub enum CacheControl {
568 Ephemeral,
569}
570
571#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
573pub struct CitationsConfig {
574 #[serde(skip_serializing_if = "Option::is_none")]
575 pub enabled: Option<bool>,
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
580#[serde(tag = "type", rename_all = "snake_case")]
581#[expect(
582 clippy::enum_variant_names,
583 reason = "variant names match the OpenAI API citation type discriminators (char_location, page_location, etc.)"
584)]
585pub enum Citation {
586 CharLocation(CharLocationCitation),
587 PageLocation(PageLocationCitation),
588 ContentBlockLocation(ContentBlockLocationCitation),
589 WebSearchResultLocation(WebSearchResultLocationCitation),
590 SearchResultLocation(SearchResultLocationCitation),
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
595pub struct CharLocationCitation {
596 pub cited_text: String,
597 pub document_index: u32,
598 pub document_title: Option<String>,
599 pub start_char_index: u32,
600 pub end_char_index: u32,
601 #[serde(skip_serializing_if = "Option::is_none")]
602 pub file_id: Option<String>,
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
607pub struct PageLocationCitation {
608 pub cited_text: String,
609 pub document_index: u32,
610 pub document_title: Option<String>,
611 pub start_page_number: u32,
612 pub end_page_number: u32,
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
617pub struct ContentBlockLocationCitation {
618 pub cited_text: String,
619 pub document_index: u32,
620 pub document_title: Option<String>,
621 pub start_block_index: u32,
622 pub end_block_index: u32,
623}
624
625#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
627pub struct WebSearchResultLocationCitation {
628 pub cited_text: String,
629 pub url: String,
630 pub title: Option<String>,
631 pub encrypted_index: String,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
636pub struct SearchResultLocationCitation {
637 pub cited_text: String,
638 pub search_result_index: u32,
639 pub source: String,
640 pub title: Option<String>,
641 pub start_block_index: u32,
642 pub end_block_index: u32,
643}
644
645#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
651#[serde(untagged)]
652#[expect(
653 clippy::enum_variant_names,
654 reason = "ToolSearch matches Anthropic API naming"
655)]
656#[schemars(rename = "MessagesTool")]
657pub enum Tool {
658 McpToolset(McpToolset),
660 Custom(CustomTool),
665 ToolSearch(ToolSearchTool),
667 Bash(BashTool),
669 TextEditor(TextEditorTool),
671 WebSearch(WebSearchTool),
673}
674
675#[serde_with::skip_serializing_none]
677#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
678pub struct CustomTool {
679 pub name: String,
681
682 #[serde(rename = "type")]
684 pub tool_type: Option<String>,
685
686 pub description: Option<String>,
688
689 pub input_schema: InputSchema,
691
692 pub defer_loading: Option<bool>,
694
695 pub cache_control: Option<CacheControl>,
697}
698
699#[serde_with::skip_serializing_none]
701#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
702pub struct InputSchema {
703 #[serde(rename = "type")]
704 pub schema_type: String,
705
706 pub properties: Option<HashMap<String, Value>>,
707
708 pub required: Option<Vec<String>>,
709
710 #[serde(flatten)]
712 pub additional: HashMap<String, Value>,
713}
714
715#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
717pub struct BashTool {
718 #[serde(rename = "type")]
719 pub tool_type: String, pub name: String, #[serde(skip_serializing_if = "Option::is_none")]
724 pub cache_control: Option<CacheControl>,
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
729pub struct TextEditorTool {
730 #[serde(rename = "type")]
731 pub tool_type: String, pub name: String, #[serde(skip_serializing_if = "Option::is_none")]
736 pub cache_control: Option<CacheControl>,
737}
738
739#[serde_with::skip_serializing_none]
741#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
742pub struct WebSearchTool {
743 #[serde(rename = "type")]
744 pub tool_type: String, pub name: String, pub allowed_domains: Option<Vec<String>>,
749
750 pub blocked_domains: Option<Vec<String>>,
751
752 pub max_uses: Option<u32>,
753
754 pub user_location: Option<UserLocation>,
755
756 pub cache_control: Option<CacheControl>,
757}
758
759#[serde_with::skip_serializing_none]
761#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
762pub struct UserLocation {
763 #[serde(rename = "type")]
764 pub location_type: String, pub city: Option<String>,
767
768 pub region: Option<String>,
769
770 pub country: Option<String>,
771
772 pub timezone: Option<String>,
773}
774
775#[serde_with::skip_serializing_none]
781#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
782#[serde(tag = "type", rename_all = "snake_case")]
783#[schemars(rename = "MessagesToolChoice")]
784pub enum ToolChoice {
785 Auto {
787 disable_parallel_tool_use: Option<bool>,
788 },
789 Any {
791 disable_parallel_tool_use: Option<bool>,
792 },
793 Tool {
795 name: String,
796 disable_parallel_tool_use: Option<bool>,
797 },
798 None,
800}
801
802#[serde_with::skip_serializing_none]
808#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
809#[serde(tag = "type", rename_all = "snake_case")]
810pub enum ThinkingConfig {
811 Enabled {
813 budget_tokens: u32,
815 display: Option<ThinkingDisplay>,
817 },
818 Disabled,
820 Adaptive {
822 display: Option<ThinkingDisplay>,
825 },
826}
827
828#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
830#[serde(rename_all = "snake_case")]
831pub enum ThinkingDisplay {
832 Summarized,
834 Omitted,
836}
837
838#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
844pub struct Message {
845 pub id: String,
847
848 #[serde(rename = "type")]
850 pub message_type: String,
851
852 pub role: String,
854
855 pub content: Vec<ContentBlock>,
857
858 pub model: String,
860
861 pub stop_reason: Option<StopReason>,
863
864 pub stop_sequence: Option<String>,
866
867 pub usage: Usage,
869}
870
871#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
873#[serde(tag = "type", rename_all = "snake_case")]
874pub enum ContentBlock {
875 Text {
877 text: String,
878 #[serde(skip_serializing_if = "Option::is_none")]
879 citations: Option<Vec<Citation>>,
880 },
881 ToolUse {
883 id: String,
884 name: String,
885 input: Value,
886 },
887 Thinking { thinking: String, signature: String },
889 RedactedThinking { data: String },
891 ServerToolUse {
893 id: String,
894 name: String,
895 input: Value,
896 },
897 WebSearchToolResult {
899 tool_use_id: String,
900 content: WebSearchToolResultContent,
901 },
902 ToolSearchToolResult {
904 tool_use_id: String,
905 content: ToolSearchResultContent,
906 },
907 ToolReference {
909 tool_name: String,
910 #[serde(skip_serializing_if = "Option::is_none")]
911 description: Option<String>,
912 },
913 McpToolUse {
915 id: String,
916 name: String,
917 server_name: String,
918 input: Value,
919 },
920 McpToolResult {
922 tool_use_id: String,
923 content: Option<ToolResultContent>,
924 is_error: Option<bool>,
925 },
926}
927
928#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
930#[serde(rename_all = "snake_case")]
931pub enum StopReason {
932 EndTurn,
934 MaxTokens,
936 StopSequence,
938 ToolUse,
940 PauseTurn,
942 Refusal,
944}
945
946#[serde_with::skip_serializing_none]
948#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
949#[schemars(rename = "MessagesUsage")]
950pub struct Usage {
951 pub input_tokens: u32,
953
954 pub output_tokens: u32,
956
957 pub cache_creation_input_tokens: Option<u32>,
959
960 pub cache_read_input_tokens: Option<u32>,
962
963 pub cache_creation: Option<CacheCreation>,
965
966 pub server_tool_use: Option<ServerToolUsage>,
968
969 pub service_tier: Option<String>,
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
975pub struct CacheCreation {
976 #[serde(flatten)]
977 pub tokens_by_ttl: HashMap<String, u32>,
978}
979
980#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
982pub struct ServerToolUsage {
983 pub web_search_requests: u32,
984}
985
986#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
992#[serde(tag = "type", rename_all = "snake_case")]
993pub enum MessageStreamEvent {
994 MessageStart { message: Message },
996 MessageDelta {
998 delta: MessageDelta,
999 usage: MessageDeltaUsage,
1000 },
1001 MessageStop,
1003 ContentBlockStart {
1005 index: u32,
1006 content_block: ContentBlock,
1007 },
1008 ContentBlockDelta {
1010 index: u32,
1011 delta: ContentBlockDelta,
1012 },
1013 ContentBlockStop { index: u32 },
1015 Ping,
1017 Error { error: ErrorResponse },
1019}
1020
1021#[serde_with::skip_serializing_none]
1023#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1024pub struct MessageDelta {
1025 pub stop_reason: Option<StopReason>,
1026
1027 pub stop_sequence: Option<String>,
1028}
1029
1030#[serde_with::skip_serializing_none]
1032#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1033pub struct MessageDeltaUsage {
1034 pub output_tokens: u32,
1035
1036 pub input_tokens: Option<u32>,
1037
1038 pub cache_creation_input_tokens: Option<u32>,
1039
1040 pub cache_read_input_tokens: Option<u32>,
1041
1042 pub server_tool_use: Option<ServerToolUsage>,
1043}
1044
1045#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1047#[serde(tag = "type", rename_all = "snake_case")]
1048#[expect(
1049 clippy::enum_variant_names,
1050 reason = "variant names match the OpenAI/Anthropic streaming delta type discriminators (text_delta, input_json_delta, etc.)"
1051)]
1052pub enum ContentBlockDelta {
1053 TextDelta { text: String },
1055 InputJsonDelta { partial_json: String },
1057 ThinkingDelta { thinking: String },
1059 SignatureDelta { signature: String },
1061 CitationsDelta { citation: Citation },
1063}
1064
1065#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1071#[schemars(rename = "MessagesErrorResponse")]
1072pub struct ErrorResponse {
1073 #[serde(rename = "type")]
1074 pub error_type: String,
1075
1076 pub message: String,
1077}
1078
1079#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1081#[serde(tag = "type", rename_all = "snake_case")]
1082#[expect(
1083 clippy::enum_variant_names,
1084 reason = "variant names match the OpenAI API error type discriminators (invalid_request_error, authentication_error, etc.)"
1085)]
1086pub enum ApiError {
1087 InvalidRequestError { message: String },
1088 AuthenticationError { message: String },
1089 BillingError { message: String },
1090 PermissionError { message: String },
1091 NotFoundError { message: String },
1092 RateLimitError { message: String },
1093 TimeoutError { message: String },
1094 ApiError { message: String },
1095 OverloadedError { message: String },
1096}
1097
1098#[serde_with::skip_serializing_none]
1104#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1105pub struct CountMessageTokensRequest {
1106 pub model: String,
1108
1109 pub messages: Vec<InputMessage>,
1111
1112 pub system: Option<SystemContent>,
1114
1115 pub thinking: Option<ThinkingConfig>,
1117
1118 pub tool_choice: Option<ToolChoice>,
1120
1121 pub tools: Option<Vec<Tool>>,
1123}
1124
1125#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1127pub struct CountMessageTokensResponse {
1128 pub input_tokens: u32,
1129}
1130
1131#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1137pub struct ModelInfo {
1138 #[serde(rename = "type")]
1140 pub model_type: String,
1141
1142 pub id: String,
1144
1145 pub display_name: String,
1147
1148 pub created_at: String,
1150}
1151
1152#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1154pub struct ListModelsResponse {
1155 pub data: Vec<ModelInfo>,
1156 pub has_more: bool,
1157 pub first_id: Option<String>,
1158 pub last_id: Option<String>,
1159}
1160
1161#[serde_with::skip_serializing_none]
1167#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1168pub struct ContainerConfig {
1169 pub id: Option<String>,
1171}
1172
1173#[serde_with::skip_serializing_none]
1175#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1176pub struct McpServerConfig {
1177 #[serde(rename = "type", default = "McpServerConfig::default_type")]
1179 pub server_type: String,
1180
1181 pub name: String,
1183
1184 pub url: String,
1186
1187 pub authorization_token: Option<String>,
1189
1190 pub tool_configuration: Option<McpToolConfiguration>,
1192}
1193
1194impl McpServerConfig {
1195 fn default_type() -> String {
1196 "url".to_string()
1197 }
1198}
1199
1200#[serde_with::skip_serializing_none]
1202#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1203pub struct McpToolConfiguration {
1204 pub enabled: Option<bool>,
1206
1207 pub allowed_tools: Option<Vec<String>>,
1209}
1210
1211#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1217pub struct McpToolUseBlock {
1218 pub id: String,
1220
1221 pub name: String,
1223
1224 pub server_name: String,
1226
1227 pub input: Value,
1229
1230 #[serde(skip_serializing_if = "Option::is_none")]
1232 pub cache_control: Option<CacheControl>,
1233}
1234
1235#[serde_with::skip_serializing_none]
1237#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1238pub struct McpToolResultBlock {
1239 pub tool_use_id: String,
1241
1242 pub content: Option<ToolResultContent>,
1244
1245 pub is_error: Option<bool>,
1247
1248 pub cache_control: Option<CacheControl>,
1250}
1251
1252#[serde_with::skip_serializing_none]
1254#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1255pub struct McpToolset {
1256 #[serde(rename = "type")]
1257 pub toolset_type: String, pub mcp_server_name: String,
1261
1262 pub default_config: Option<McpToolDefaultConfig>,
1264
1265 pub configs: Option<HashMap<String, McpToolConfig>>,
1267
1268 pub cache_control: Option<CacheControl>,
1270}
1271
1272#[serde_with::skip_serializing_none]
1274#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1275pub struct McpToolDefaultConfig {
1276 pub enabled: Option<bool>,
1278
1279 pub defer_loading: Option<bool>,
1281}
1282
1283#[serde_with::skip_serializing_none]
1285#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1286pub struct McpToolConfig {
1287 pub enabled: Option<bool>,
1289
1290 pub defer_loading: Option<bool>,
1292}
1293
1294#[serde_with::skip_serializing_none]
1300#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1301pub struct CodeExecutionTool {
1302 #[serde(rename = "type")]
1303 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1309
1310 pub defer_loading: Option<bool>,
1312
1313 pub strict: Option<bool>,
1315
1316 pub cache_control: Option<CacheControl>,
1318}
1319
1320#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1322pub struct CodeExecutionResultBlock {
1323 pub stdout: String,
1325
1326 pub stderr: String,
1328
1329 pub return_code: i32,
1331
1332 pub content: Vec<CodeExecutionOutputBlock>,
1334}
1335
1336#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1338pub struct CodeExecutionOutputBlock {
1339 #[serde(rename = "type")]
1340 pub block_type: String, pub file_id: String,
1344}
1345
1346#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1348pub struct CodeExecutionToolResultBlock {
1349 pub tool_use_id: String,
1351
1352 pub content: CodeExecutionToolResultContent,
1354
1355 #[serde(skip_serializing_if = "Option::is_none")]
1357 pub cache_control: Option<CacheControl>,
1358}
1359
1360#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1362#[serde(untagged)]
1363pub enum CodeExecutionToolResultContent {
1364 Success(CodeExecutionResultBlock),
1365 Error(CodeExecutionToolResultError),
1366}
1367
1368#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1370pub struct CodeExecutionToolResultError {
1371 #[serde(rename = "type")]
1372 pub error_type: String, pub error_code: CodeExecutionToolResultErrorCode,
1375}
1376
1377#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1379#[serde(rename_all = "snake_case")]
1380pub enum CodeExecutionToolResultErrorCode {
1381 Unavailable,
1382 CodeExecutionExceededTimeout,
1383 ContainerExpired,
1384 InvalidToolInput,
1385}
1386
1387#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1389pub struct BashCodeExecutionResultBlock {
1390 pub stdout: String,
1392
1393 pub stderr: String,
1395
1396 pub return_code: i32,
1398
1399 pub content: Vec<BashCodeExecutionOutputBlock>,
1401}
1402
1403#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1405pub struct BashCodeExecutionOutputBlock {
1406 #[serde(rename = "type")]
1407 pub block_type: String, pub file_id: String,
1411}
1412
1413#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1415pub struct BashCodeExecutionToolResultBlock {
1416 pub tool_use_id: String,
1418
1419 pub content: BashCodeExecutionToolResultContent,
1421
1422 #[serde(skip_serializing_if = "Option::is_none")]
1424 pub cache_control: Option<CacheControl>,
1425}
1426
1427#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1429#[serde(untagged)]
1430pub enum BashCodeExecutionToolResultContent {
1431 Success(BashCodeExecutionResultBlock),
1432 Error(BashCodeExecutionToolResultError),
1433}
1434
1435#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1437pub struct BashCodeExecutionToolResultError {
1438 #[serde(rename = "type")]
1439 pub error_type: String, pub error_code: BashCodeExecutionToolResultErrorCode,
1442}
1443
1444#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1446#[serde(rename_all = "snake_case")]
1447pub enum BashCodeExecutionToolResultErrorCode {
1448 Unavailable,
1449 CodeExecutionExceededTimeout,
1450 ContainerExpired,
1451 InvalidToolInput,
1452}
1453
1454#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1456pub struct TextEditorCodeExecutionToolResultBlock {
1457 pub tool_use_id: String,
1459
1460 pub content: TextEditorCodeExecutionToolResultContent,
1462
1463 #[serde(skip_serializing_if = "Option::is_none")]
1465 pub cache_control: Option<CacheControl>,
1466}
1467
1468#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1470#[serde(untagged)]
1471pub enum TextEditorCodeExecutionToolResultContent {
1472 CreateResult(TextEditorCodeExecutionCreateResultBlock),
1473 StrReplaceResult(TextEditorCodeExecutionStrReplaceResultBlock),
1474 ViewResult(TextEditorCodeExecutionViewResultBlock),
1475 Error(TextEditorCodeExecutionToolResultError),
1476}
1477
1478#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1480pub struct TextEditorCodeExecutionCreateResultBlock {
1481 #[serde(rename = "type")]
1482 pub block_type: String, }
1484
1485#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1487pub struct TextEditorCodeExecutionStrReplaceResultBlock {
1488 #[serde(rename = "type")]
1489 pub block_type: String, #[serde(skip_serializing_if = "Option::is_none")]
1493 pub snippet: Option<String>,
1494}
1495
1496#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1498pub struct TextEditorCodeExecutionViewResultBlock {
1499 #[serde(rename = "type")]
1500 pub block_type: String, pub content: String,
1504}
1505
1506#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1508pub struct TextEditorCodeExecutionToolResultError {
1509 #[serde(rename = "type")]
1510 pub error_type: String,
1511
1512 pub error_code: TextEditorCodeExecutionToolResultErrorCode,
1513}
1514
1515#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1517#[serde(rename_all = "snake_case")]
1518pub enum TextEditorCodeExecutionToolResultErrorCode {
1519 Unavailable,
1520 InvalidToolInput,
1521 FileNotFound,
1522 ContainerExpired,
1523}
1524
1525#[serde_with::skip_serializing_none]
1531#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1532pub struct WebFetchTool {
1533 #[serde(rename = "type")]
1534 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1540
1541 pub max_uses: Option<u32>,
1543
1544 pub cache_control: Option<CacheControl>,
1546}
1547
1548#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1550pub struct WebFetchResultBlock {
1551 #[serde(rename = "type")]
1552 pub block_type: String, pub url: String,
1556
1557 pub content: DocumentBlock,
1559
1560 #[serde(skip_serializing_if = "Option::is_none")]
1562 pub retrieved_at: Option<String>,
1563}
1564
1565#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1567pub struct WebFetchToolResultBlock {
1568 pub tool_use_id: String,
1570
1571 pub content: WebFetchToolResultContent,
1573
1574 #[serde(skip_serializing_if = "Option::is_none")]
1576 pub cache_control: Option<CacheControl>,
1577}
1578
1579#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1581#[serde(untagged)]
1582pub enum WebFetchToolResultContent {
1583 Success(WebFetchResultBlock),
1584 Error(WebFetchToolResultError),
1585}
1586
1587#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1589pub struct WebFetchToolResultError {
1590 #[serde(rename = "type")]
1591 pub error_type: String, pub error_code: WebFetchToolResultErrorCode,
1594}
1595
1596#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1598#[serde(rename_all = "snake_case")]
1599pub enum WebFetchToolResultErrorCode {
1600 InvalidToolInput,
1601 Unavailable,
1602 MaxUsesExceeded,
1603 TooManyRequests,
1604 UrlNotAllowed,
1605 FetchFailed,
1606 ContentTooLarge,
1607}
1608
1609#[serde_with::skip_serializing_none]
1615#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1616pub struct ToolSearchTool {
1617 #[serde(rename = "type")]
1618 pub tool_type: String, pub name: String,
1621
1622 pub allowed_callers: Option<Vec<String>>,
1624
1625 pub cache_control: Option<CacheControl>,
1627}
1628
1629#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1631pub struct ToolReferenceBlock {
1632 #[serde(rename = "type")]
1633 pub block_type: String, pub tool_name: String,
1637
1638 #[serde(skip_serializing_if = "Option::is_none")]
1640 pub description: Option<String>,
1641}
1642
1643#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1645pub struct ToolSearchResultContent {
1646 #[serde(rename = "type")]
1647 pub block_type: String, pub tool_references: Vec<ToolReferenceBlock>,
1651}
1652
1653#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1655pub struct ToolSearchToolResultBlock {
1656 pub tool_use_id: String,
1658
1659 pub content: ToolSearchResultContent,
1661
1662 #[serde(skip_serializing_if = "Option::is_none")]
1664 pub cache_control: Option<CacheControl>,
1665}
1666
1667#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1673pub struct ContainerUploadBlock {
1674 #[serde(rename = "type")]
1675 pub block_type: String, pub file_id: String,
1679
1680 pub file_name: String,
1682
1683 #[serde(skip_serializing_if = "Option::is_none")]
1685 pub file_path: Option<String>,
1686}
1687
1688#[serde_with::skip_serializing_none]
1694#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1695pub struct MemoryTool {
1696 #[serde(rename = "type")]
1697 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1703
1704 pub defer_loading: Option<bool>,
1706
1707 pub strict: Option<bool>,
1709
1710 pub input_examples: Option<Vec<Value>>,
1712
1713 pub cache_control: Option<CacheControl>,
1715}
1716
1717#[serde_with::skip_serializing_none]
1723#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1724pub struct ComputerUseTool {
1725 #[serde(rename = "type")]
1726 pub tool_type: String, pub name: String, pub display_width_px: u32,
1732
1733 pub display_height_px: u32,
1735
1736 pub display_number: Option<u32>,
1738
1739 pub allowed_callers: Option<Vec<String>>,
1741
1742 pub cache_control: Option<CacheControl>,
1744}
1745
1746#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1752#[serde(tag = "type", rename_all = "snake_case")]
1753pub enum BetaInputContentBlock {
1754 Text(TextBlock),
1756 Image(ImageBlock),
1757 Document(DocumentBlock),
1758 ToolUse(ToolUseBlock),
1759 ToolResult(ToolResultBlock),
1760 Thinking(ThinkingBlock),
1761 RedactedThinking(RedactedThinkingBlock),
1762 ServerToolUse(ServerToolUseBlock),
1763 SearchResult(SearchResultBlock),
1764 WebSearchToolResult(WebSearchToolResultBlock),
1765
1766 McpToolUse(McpToolUseBlock),
1768 McpToolResult(McpToolResultBlock),
1769
1770 CodeExecutionToolResult(CodeExecutionToolResultBlock),
1772 BashCodeExecutionToolResult(BashCodeExecutionToolResultBlock),
1773 TextEditorCodeExecutionToolResult(TextEditorCodeExecutionToolResultBlock),
1774
1775 WebFetchToolResult(WebFetchToolResultBlock),
1777
1778 ToolSearchToolResult(ToolSearchToolResultBlock),
1780 ToolReference(ToolReferenceBlock),
1781
1782 ContainerUpload(ContainerUploadBlock),
1784}
1785
1786#[serde_with::skip_serializing_none]
1788#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1789#[serde(tag = "type", rename_all = "snake_case")]
1790pub enum BetaContentBlock {
1791 Text {
1793 text: String,
1794 citations: Option<Vec<Citation>>,
1795 },
1796 ToolUse {
1797 id: String,
1798 name: String,
1799 input: Value,
1800 },
1801 Thinking {
1802 thinking: String,
1803 signature: String,
1804 },
1805 RedactedThinking {
1806 data: String,
1807 },
1808 ServerToolUse {
1809 id: String,
1810 name: String,
1811 input: Value,
1812 },
1813 WebSearchToolResult {
1814 tool_use_id: String,
1815 content: WebSearchToolResultContent,
1816 },
1817
1818 McpToolUse {
1820 id: String,
1821 name: String,
1822 server_name: String,
1823 input: Value,
1824 },
1825 McpToolResult {
1826 tool_use_id: String,
1827 content: Option<ToolResultContent>,
1828 is_error: Option<bool>,
1829 },
1830
1831 CodeExecutionToolResult {
1833 tool_use_id: String,
1834 content: CodeExecutionToolResultContent,
1835 },
1836 BashCodeExecutionToolResult {
1837 tool_use_id: String,
1838 content: BashCodeExecutionToolResultContent,
1839 },
1840 TextEditorCodeExecutionToolResult {
1841 tool_use_id: String,
1842 content: TextEditorCodeExecutionToolResultContent,
1843 },
1844
1845 WebFetchToolResult {
1847 tool_use_id: String,
1848 content: WebFetchToolResultContent,
1849 },
1850
1851 ToolSearchToolResult {
1853 tool_use_id: String,
1854 content: ToolSearchResultContent,
1855 },
1856 ToolReference {
1857 tool_name: String,
1858 description: Option<String>,
1859 },
1860
1861 ContainerUpload {
1863 file_id: String,
1864 file_name: String,
1865 file_path: Option<String>,
1866 },
1867}
1868
1869#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1871#[serde(untagged)]
1872pub enum BetaTool {
1873 Custom(CustomTool),
1875 Bash(BashTool),
1876 TextEditor(TextEditorTool),
1877 WebSearch(WebSearchTool),
1878
1879 CodeExecution(CodeExecutionTool),
1881 McpToolset(McpToolset),
1882 WebFetch(WebFetchTool),
1883 ToolSearch(ToolSearchTool),
1884 Memory(MemoryTool),
1885 ComputerUse(ComputerUseTool),
1886}
1887
1888#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1890#[serde(rename_all = "snake_case")]
1891pub enum BetaServerToolName {
1892 WebSearch,
1893 WebFetch,
1894 CodeExecution,
1895 BashCodeExecution,
1896 TextEditorCodeExecution,
1897 ToolSearchToolRegex,
1898 ToolSearchToolBm25,
1899}
1900
1901#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1903#[serde(tag = "type", rename_all = "snake_case")]
1904pub enum ServerToolCaller {
1905 Direct,
1907 #[serde(rename = "code_execution_20250825")]
1909 CodeExecution20250825,
1910}
1911
1912#[cfg(test)]
1913mod tests {
1914 use serde_json::{self, json};
1915
1916 use super::*;
1917
1918 #[test]
1919 fn test_system_blocks_preserve_type_field() {
1920 let input = json!({
1921 "model": "test",
1922 "messages": [{"role": "user", "content": "hi"}],
1923 "max_tokens": 100,
1924 "system": [
1925 {"type": "text", "text": "system prompt", "cache_control": {"type": "ephemeral"}}
1926 ]
1927 });
1928
1929 let req: CreateMessageRequest = serde_json::from_value(input).expect("should deserialize");
1930 let reserialized = serde_json::to_value(&req).expect("should serialize");
1931
1932 let system_blocks = reserialized.get("system").unwrap().as_array().unwrap();
1933 let first_block = &system_blocks[0];
1934 assert_eq!(
1935 first_block.get("type").and_then(|v| v.as_str()),
1936 Some("text"),
1937 "system block must retain 'type' field after round-trip: got {first_block:?}",
1938 );
1939 }
1940
1941 #[test]
1942 fn test_message_content_blocks_preserve_type_field() {
1943 let input = json!({
1944 "model": "test",
1945 "messages": [{
1946 "role": "user",
1947 "content": [
1948 {"type": "text", "text": "hello", "cache_control": {"type": "ephemeral"}}
1949 ]
1950 }],
1951 "max_tokens": 100
1952 });
1953
1954 let req: CreateMessageRequest = serde_json::from_value(input).expect("should deserialize");
1955 let reserialized = serde_json::to_value(&req).expect("should serialize");
1956
1957 let msg = &reserialized["messages"][0];
1958 let content_blocks = msg["content"].as_array().unwrap();
1959 let first_block = &content_blocks[0];
1960 assert_eq!(
1961 first_block.get("type").and_then(|v| v.as_str()),
1962 Some("text"),
1963 "content block must retain 'type' field: got {first_block:?}",
1964 );
1965 }
1966
1967 #[test]
1968 fn test_unknown_fields_preserved_via_flatten() {
1969 let input = json!({
1970 "model": "test-model",
1971 "messages": [{"role": "user", "content": "hello"}],
1972 "max_tokens": 100,
1973 "thinking": {"type": "adaptive"},
1974 "context_management": {"edits": [{"type": "clear_thinking", "keep": "all"}]},
1975 "output_config": {"effort": "high"},
1976 "stream": true
1977 });
1978
1979 let req: CreateMessageRequest =
1980 serde_json::from_value(input.clone()).expect("should deserialize");
1981 assert!(matches!(
1982 req.thinking,
1983 Some(ThinkingConfig::Adaptive { .. })
1984 ));
1985
1986 let reserialized = serde_json::to_value(&req).expect("should serialize");
1987 assert_eq!(
1988 reserialized.get("context_management"),
1989 input.get("context_management"),
1990 "context_management must survive round-trip"
1991 );
1992 assert_eq!(
1993 reserialized.get("output_config"),
1994 input.get("output_config"),
1995 "output_config must survive round-trip"
1996 );
1997 }
1998
1999 fn base_request() -> CreateMessageRequest {
2000 CreateMessageRequest {
2001 model: "claude-test".to_string(),
2002 messages: vec![InputMessage {
2003 role: Role::User,
2004 content: InputContent::String("hello".to_string()),
2005 }],
2006 max_tokens: 16,
2007 metadata: None,
2008 service_tier: None,
2009 stop_sequences: None,
2010 stream: None,
2011 system: None,
2012 temperature: None,
2013 thinking: None,
2014 tool_choice: None,
2015 tools: None,
2016 top_k: None,
2017 top_p: None,
2018 container: None,
2019 mcp_servers: None,
2020 other: Map::new(),
2021 }
2022 }
2023
2024 fn custom_tool(name: &str) -> Tool {
2025 Tool::Custom(CustomTool {
2026 name: name.to_string(),
2027 tool_type: None,
2028 description: Some("test tool".to_string()),
2029 input_schema: InputSchema {
2030 schema_type: "object".to_string(),
2031 properties: None,
2032 required: None,
2033 additional: HashMap::new(),
2034 },
2035 defer_loading: None,
2036 cache_control: None,
2037 })
2038 }
2039
2040 fn mcp_toolset(configs: Option<HashMap<String, McpToolConfig>>) -> Tool {
2041 Tool::McpToolset(McpToolset {
2042 toolset_type: "mcp_toolset".to_string(),
2043 mcp_server_name: "brave".to_string(),
2044 default_config: None,
2045 configs,
2046 cache_control: None,
2047 })
2048 }
2049
2050 fn mcp_server_config() -> McpServerConfig {
2051 McpServerConfig {
2052 server_type: "url".to_string(),
2053 name: "brave".to_string(),
2054 url: "https://example.com/mcp".to_string(),
2055 authorization_token: None,
2056 tool_configuration: None,
2057 }
2058 }
2059 #[test]
2060 fn test_tool_mcp_toolset_defer_loading_deserialization() {
2061 let json = r#"{
2062 "type": "mcp_toolset",
2063 "mcp_server_name": "brave",
2064 "default_config": {"defer_loading": true}
2065 }"#;
2066
2067 let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize McpToolset Tool");
2068 match tool {
2069 Tool::McpToolset(ts) => {
2070 assert_eq!(ts.mcp_server_name, "brave");
2071 let default_config = ts.default_config.expect("default_config should be Some");
2072 assert_eq!(default_config.defer_loading, Some(true));
2073 }
2074 other => panic!(
2075 "Expected McpToolset, got {:?}",
2076 std::mem::discriminant(&other)
2077 ),
2078 }
2079 }
2080
2081 #[test]
2082 fn test_tool_search_tool_deserialization() {
2083 let json = r#"{
2084 "type": "tool_search_tool_regex_20251119",
2085 "name": "tool_search_tool_regex"
2086 }"#;
2087
2088 let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize ToolSearch Tool");
2089 match tool {
2090 Tool::ToolSearch(ts) => {
2091 assert_eq!(ts.name, "tool_search_tool_regex");
2092 assert_eq!(ts.tool_type, "tool_search_tool_regex_20251119");
2093 }
2094 other => panic!(
2095 "Expected ToolSearch, got {:?}",
2096 std::mem::discriminant(&other)
2097 ),
2098 }
2099 }
2100
2101 #[test]
2102 fn test_content_block_tool_search_tool_result_deserialization() {
2103 let json = r#"{
2104 "type": "tool_search_tool_result",
2105 "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2106 "content": {
2107 "type": "tool_search_tool_search_result",
2108 "tool_references": [
2109 {"type": "tool_reference", "tool_name": "get_weather"}
2110 ]
2111 }
2112 }"#;
2113
2114 let block: ContentBlock = serde_json::from_str(json)
2115 .expect("Failed to deserialize tool_search_tool_result ContentBlock");
2116 match block {
2117 ContentBlock::ToolSearchToolResult {
2118 tool_use_id,
2119 content,
2120 } => {
2121 assert_eq!(tool_use_id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
2122 assert_eq!(content.tool_references.len(), 1);
2123 assert_eq!(content.tool_references[0].tool_name, "get_weather");
2124 }
2125 _ => panic!("Expected ToolSearchToolResult variant"),
2126 }
2127 }
2128
2129 #[test]
2130 fn test_content_block_server_tool_use_deserialization() {
2131 let json = r#"{
2132 "type": "server_tool_use",
2133 "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2134 "name": "tool_search_tool_regex",
2135 "input": {"query": "weather"}
2136 }"#;
2137
2138 let block: ContentBlock =
2139 serde_json::from_str(json).expect("Failed to deserialize server_tool_use ContentBlock");
2140 match block {
2141 ContentBlock::ServerToolUse { id, name, input: _ } => {
2142 assert_eq!(id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
2143 assert_eq!(name, "tool_search_tool_regex");
2144 }
2145 _ => panic!("Expected ServerToolUse variant"),
2146 }
2147 }
2148
2149 #[test]
2150 fn test_content_block_tool_reference_deserialization() {
2151 let json = r#"{
2152 "type": "tool_reference",
2153 "tool_name": "get_weather",
2154 "description": "Get the weather for a location"
2155 }"#;
2156
2157 let block: ContentBlock =
2158 serde_json::from_str(json).expect("Failed to deserialize tool_reference ContentBlock");
2159 match block {
2160 ContentBlock::ToolReference {
2161 tool_name,
2162 description,
2163 } => {
2164 assert_eq!(tool_name, "get_weather");
2165 assert_eq!(description.unwrap(), "Get the weather for a location");
2166 }
2167 _ => panic!("Expected ToolReference variant"),
2168 }
2169 }
2170
2171 #[test]
2172 fn test_tool_choice_auto_requires_tools() {
2173 let mut request = base_request();
2174 request.tool_choice = Some(ToolChoice::Auto {
2175 disable_parallel_tool_use: None,
2176 });
2177
2178 assert!(request.validate().is_err());
2179 }
2180
2181 #[test]
2182 fn test_tool_choice_any_requires_tools() {
2183 let mut request = base_request();
2184 request.tool_choice = Some(ToolChoice::Any {
2185 disable_parallel_tool_use: None,
2186 });
2187
2188 assert!(request.validate().is_err());
2189 }
2190
2191 #[test]
2192 fn test_tool_choice_auto_with_tools_is_valid() {
2193 let mut request = base_request();
2194 request.tool_choice = Some(ToolChoice::Auto {
2195 disable_parallel_tool_use: None,
2196 });
2197 request.tools = Some(vec![custom_tool("get_weather")]);
2198
2199 assert!(request.validate().is_ok());
2200 }
2201
2202 #[test]
2203 fn test_tool_choice_any_with_tools_is_valid() {
2204 let mut request = base_request();
2205 request.tool_choice = Some(ToolChoice::Any {
2206 disable_parallel_tool_use: None,
2207 });
2208 request.tools = Some(vec![custom_tool("get_weather")]);
2209
2210 assert!(request.validate().is_ok());
2211 }
2212
2213 #[test]
2214 fn test_tool_choice_specific_tool_requires_tools() {
2215 let mut request = base_request();
2216 request.tool_choice = Some(ToolChoice::Tool {
2217 name: "get_weather".to_string(),
2218 disable_parallel_tool_use: None,
2219 });
2220
2221 assert!(request.validate().is_err());
2222 }
2223
2224 #[test]
2225 fn test_tool_choice_specific_tool_must_exist() {
2226 let mut request = base_request();
2227 request.tool_choice = Some(ToolChoice::Tool {
2228 name: "get_weather".to_string(),
2229 disable_parallel_tool_use: None,
2230 });
2231 request.tools = Some(vec![custom_tool("search_web")]);
2232
2233 assert!(request.validate().is_err());
2234 }
2235
2236 #[test]
2237 fn test_tool_choice_none_without_tools_is_valid() {
2238 let mut request = base_request();
2239 request.tool_choice = Some(ToolChoice::None);
2240
2241 assert!(request.validate().is_ok());
2242 }
2243
2244 #[test]
2245 fn test_tool_choice_specific_tool_is_valid_when_declared() {
2246 let mut request = base_request();
2247 request.tool_choice = Some(ToolChoice::Tool {
2248 name: "get_weather".to_string(),
2249 disable_parallel_tool_use: None,
2250 });
2251 request.tools = Some(vec![custom_tool("get_weather")]);
2252
2253 assert!(request.validate().is_ok());
2254 }
2255
2256 #[test]
2257 fn test_tool_choice_specific_tool_is_valid_with_mcp_toolset() {
2258 let mut request = base_request();
2259 request.tool_choice = Some(ToolChoice::Tool {
2260 name: "get_weather".to_string(),
2261 disable_parallel_tool_use: None,
2262 });
2263 request.tools = Some(vec![mcp_toolset(None)]);
2264 request.mcp_servers = Some(vec![mcp_server_config()]);
2265
2266 assert!(request.validate().is_ok());
2267 }
2268
2269 #[test]
2270 fn test_tool_choice_specific_tool_uses_mcp_toolset_default_when_override_missing() {
2271 let mut request = base_request();
2272 request.tool_choice = Some(ToolChoice::Tool {
2273 name: "get_weather".to_string(),
2274 disable_parallel_tool_use: None,
2275 });
2276 request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2277 "search_web".to_string(),
2278 McpToolConfig {
2279 enabled: Some(false),
2280 defer_loading: None,
2281 },
2282 )])))]);
2283 request.mcp_servers = Some(vec![mcp_server_config()]);
2284
2285 assert!(request.validate().is_ok());
2286 }
2287
2288 #[test]
2289 fn test_tool_choice_specific_tool_must_be_enabled_in_mcp_toolset_configs() {
2290 let mut request = base_request();
2291 request.tool_choice = Some(ToolChoice::Tool {
2292 name: "get_weather".to_string(),
2293 disable_parallel_tool_use: None,
2294 });
2295 request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2296 "get_weather".to_string(),
2297 McpToolConfig {
2298 enabled: Some(false),
2299 defer_loading: None,
2300 },
2301 )])))]);
2302 request.mcp_servers = Some(vec![mcp_server_config()]);
2303
2304 assert!(request.validate().is_err());
2305 }
2306
2307 #[test]
2308 fn test_thinking_config_adaptive_minimal() {
2309 let cfg: ThinkingConfig = serde_json::from_str(r#"{"type":"adaptive"}"#).unwrap();
2310 match cfg {
2311 ThinkingConfig::Adaptive { display } => assert_eq!(display, None),
2312 other => panic!("expected Adaptive, got {other:?}"),
2313 }
2314 }
2315
2316 #[test]
2317 fn test_thinking_config_adaptive_with_display() {
2318 let cfg: ThinkingConfig =
2319 serde_json::from_str(r#"{"type":"adaptive","display":"omitted"}"#).unwrap();
2320 match cfg {
2321 ThinkingConfig::Adaptive { display } => {
2322 assert_eq!(display, Some(ThinkingDisplay::Omitted));
2323 }
2324 other => panic!("expected Adaptive, got {other:?}"),
2325 }
2326
2327 let cfg: ThinkingConfig =
2328 serde_json::from_str(r#"{"type":"adaptive","display":"summarized"}"#).unwrap();
2329 match cfg {
2330 ThinkingConfig::Adaptive { display } => {
2331 assert_eq!(display, Some(ThinkingDisplay::Summarized));
2332 }
2333 other => panic!("expected Adaptive, got {other:?}"),
2334 }
2335 }
2336
2337 #[test]
2338 fn test_thinking_config_adaptive_round_trip_omits_null_display() {
2339 let cfg = ThinkingConfig::Adaptive { display: None };
2340 let json = serde_json::to_string(&cfg).unwrap();
2341 assert_eq!(json, r#"{"type":"adaptive"}"#);
2342 }
2343
2344 #[test]
2345 fn test_thinking_config_existing_variants_still_work() {
2346 let cfg: ThinkingConfig =
2347 serde_json::from_str(r#"{"type":"enabled","budget_tokens":1024}"#).unwrap();
2348 assert!(matches!(
2349 cfg,
2350 ThinkingConfig::Enabled {
2351 budget_tokens: 1024,
2352 display: None
2353 }
2354 ));
2355
2356 let cfg: ThinkingConfig = serde_json::from_str(r#"{"type":"disabled"}"#).unwrap();
2357 assert!(matches!(cfg, ThinkingConfig::Disabled));
2358 }
2359
2360 #[test]
2361 fn test_thinking_config_enabled_with_display() {
2362 let cfg: ThinkingConfig = serde_json::from_str(
2363 r#"{"type":"enabled","budget_tokens":2048,"display":"summarized"}"#,
2364 )
2365 .unwrap();
2366 match cfg {
2367 ThinkingConfig::Enabled {
2368 budget_tokens,
2369 display,
2370 } => {
2371 assert_eq!(budget_tokens, 2048);
2372 assert_eq!(display, Some(ThinkingDisplay::Summarized));
2373 }
2374 other => panic!("expected Enabled, got {other:?}"),
2375 }
2376 }
2377
2378 #[test]
2379 fn test_thinking_config_enabled_round_trip_omits_null_display() {
2380 let cfg = ThinkingConfig::Enabled {
2381 budget_tokens: 1024,
2382 display: None,
2383 };
2384 let json = serde_json::to_string(&cfg).unwrap();
2385 assert_eq!(json, r#"{"type":"enabled","budget_tokens":1024}"#);
2386 }
2387
2388 #[test]
2389 fn test_full_message_with_tool_search_flow_deserialization() {
2390 let json = r#"{
2392 "id": "msg_01TEST",
2393 "type": "message",
2394 "role": "assistant",
2395 "model": "claude-sonnet-4-5-20250929",
2396 "content": [
2397 {
2398 "type": "server_tool_use",
2399 "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2400 "name": "tool_search_tool_regex",
2401 "input": {"query": "weather"}
2402 },
2403 {
2404 "type": "tool_search_tool_result",
2405 "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2406 "content": {
2407 "type": "tool_search_tool_search_result",
2408 "tool_references": [
2409 {"type": "tool_reference", "tool_name": "get_weather"}
2410 ]
2411 }
2412 },
2413 {
2414 "type": "tool_use",
2415 "id": "toolu_01ABC",
2416 "name": "get_weather",
2417 "input": {"location": "San Francisco"}
2418 }
2419 ],
2420 "stop_reason": "tool_use",
2421 "stop_sequence": null,
2422 "usage": {
2423 "input_tokens": 100,
2424 "output_tokens": 50
2425 }
2426 }"#;
2427
2428 let msg: Message = serde_json::from_str(json)
2429 .expect("Failed to deserialize Message with tool search flow");
2430 assert_eq!(msg.content.len(), 3);
2431 assert!(matches!(msg.content[0], ContentBlock::ServerToolUse { .. }));
2432 assert!(matches!(
2433 msg.content[1],
2434 ContentBlock::ToolSearchToolResult { .. }
2435 ));
2436 assert!(matches!(msg.content[2], ContentBlock::ToolUse { .. }));
2437 }
2438
2439 #[test]
2440 fn test_system_role_in_messages_is_accepted_and_preserved() {
2441 let body = json!({
2445 "model": "m",
2446 "max_tokens": 16,
2447 "system": "main prompt",
2448 "messages": [
2449 {"role": "user", "content": "hi"},
2450 {"role": "system", "content": "mid-conversation system"}
2451 ]
2452 });
2453 let req: CreateMessageRequest = serde_json::from_value(body).unwrap();
2454 assert_eq!(req.messages.len(), 2);
2455 assert_eq!(req.messages[0].role, Role::User);
2456 assert_eq!(req.messages[1].role, Role::System); }
2458}