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}
283
284#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
286#[serde(untagged)]
287pub enum InputContent {
288 String(String),
289 Blocks(Vec<InputContentBlock>),
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
298#[serde(tag = "type", rename_all = "snake_case")]
299pub enum InputContentBlock {
300 Text(TextBlock),
302 Image(ImageBlock),
304 Document(DocumentBlock),
306 ToolUse(ToolUseBlock),
308 ToolResult(ToolResultBlock),
310 Thinking(ThinkingBlock),
312 RedactedThinking(RedactedThinkingBlock),
314 ServerToolUse(ServerToolUseBlock),
316 SearchResult(SearchResultBlock),
318 WebSearchToolResult(WebSearchToolResultBlock),
320 ToolSearchToolResult(ToolSearchToolResultBlock),
322 ToolReference(ToolReferenceBlock),
324}
325
326#[serde_with::skip_serializing_none]
328#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
329pub struct TextBlock {
330 pub text: String,
332
333 pub cache_control: Option<CacheControl>,
335
336 pub citations: Option<Vec<Citation>>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
342pub struct ImageBlock {
343 pub source: ImageSource,
345
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub cache_control: Option<CacheControl>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
353#[serde(tag = "type", rename_all = "snake_case")]
354pub enum ImageSource {
355 Base64 { media_type: String, data: String },
356 Url { url: String },
357}
358
359#[serde_with::skip_serializing_none]
361#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
362pub struct DocumentBlock {
363 pub source: DocumentSource,
365
366 pub cache_control: Option<CacheControl>,
368
369 pub title: Option<String>,
371
372 pub context: Option<String>,
374
375 pub citations: Option<CitationsConfig>,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
381#[serde(tag = "type", rename_all = "snake_case")]
382pub enum DocumentSource {
383 Base64 { media_type: String, data: String },
384 Text { data: String },
385 Url { url: String },
386 Content { content: Vec<InputContentBlock> },
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
391pub struct ToolUseBlock {
392 pub id: String,
394
395 pub name: String,
397
398 pub input: Value,
400
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub cache_control: Option<CacheControl>,
404}
405
406#[serde_with::skip_serializing_none]
408#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
409pub struct ToolResultBlock {
410 pub tool_use_id: String,
412
413 pub content: Option<ToolResultContent>,
415
416 pub is_error: Option<bool>,
418
419 pub cache_control: Option<CacheControl>,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
425#[serde(untagged)]
426pub enum ToolResultContent {
427 String(String),
428 Blocks(Vec<ToolResultContentBlock>),
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
433#[serde(tag = "type", rename_all = "snake_case")]
434pub enum ToolResultContentBlock {
435 Text(TextBlock),
436 Image(ImageBlock),
437 Document(DocumentBlock),
438 SearchResult(SearchResultBlock),
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
443pub struct ThinkingBlock {
444 pub thinking: String,
446
447 pub signature: String,
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
453pub struct RedactedThinkingBlock {
454 pub data: String,
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
460pub struct ServerToolUseBlock {
461 pub id: String,
463
464 pub name: String,
466
467 pub input: Value,
469
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub cache_control: Option<CacheControl>,
473}
474
475#[serde_with::skip_serializing_none]
477#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
478pub struct SearchResultBlock {
479 pub source: String,
481
482 pub title: String,
484
485 pub content: Vec<TextBlock>,
487
488 pub cache_control: Option<CacheControl>,
490
491 pub citations: Option<CitationsConfig>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
497pub struct WebSearchToolResultBlock {
498 pub tool_use_id: String,
500
501 pub content: WebSearchToolResultContent,
503
504 #[serde(skip_serializing_if = "Option::is_none")]
506 pub cache_control: Option<CacheControl>,
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
511#[serde(untagged)]
512pub enum WebSearchToolResultContent {
513 Results(Vec<WebSearchResultBlock>),
514 Error(WebSearchToolResultError),
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
519pub struct WebSearchResultBlock {
520 pub title: String,
522
523 pub url: String,
525
526 pub encrypted_content: String,
528
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub page_age: Option<String>,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
536pub struct WebSearchToolResultError {
537 #[serde(rename = "type")]
538 pub error_type: String,
539 pub error_code: WebSearchToolResultErrorCode,
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
544#[serde(rename_all = "snake_case")]
545pub enum WebSearchToolResultErrorCode {
546 InvalidToolInput,
547 Unavailable,
548 MaxUsesExceeded,
549 TooManyRequests,
550 QueryTooLong,
551}
552
553#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
555#[serde(tag = "type", rename_all = "snake_case")]
556pub enum CacheControl {
557 Ephemeral,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
562pub struct CitationsConfig {
563 #[serde(skip_serializing_if = "Option::is_none")]
564 pub enabled: Option<bool>,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
569#[serde(tag = "type", rename_all = "snake_case")]
570#[expect(
571 clippy::enum_variant_names,
572 reason = "variant names match the OpenAI API citation type discriminators (char_location, page_location, etc.)"
573)]
574pub enum Citation {
575 CharLocation(CharLocationCitation),
576 PageLocation(PageLocationCitation),
577 ContentBlockLocation(ContentBlockLocationCitation),
578 WebSearchResultLocation(WebSearchResultLocationCitation),
579 SearchResultLocation(SearchResultLocationCitation),
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
584pub struct CharLocationCitation {
585 pub cited_text: String,
586 pub document_index: u32,
587 pub document_title: Option<String>,
588 pub start_char_index: u32,
589 pub end_char_index: u32,
590 #[serde(skip_serializing_if = "Option::is_none")]
591 pub file_id: Option<String>,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
596pub struct PageLocationCitation {
597 pub cited_text: String,
598 pub document_index: u32,
599 pub document_title: Option<String>,
600 pub start_page_number: u32,
601 pub end_page_number: u32,
602}
603
604#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
606pub struct ContentBlockLocationCitation {
607 pub cited_text: String,
608 pub document_index: u32,
609 pub document_title: Option<String>,
610 pub start_block_index: u32,
611 pub end_block_index: u32,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
616pub struct WebSearchResultLocationCitation {
617 pub cited_text: String,
618 pub url: String,
619 pub title: Option<String>,
620 pub encrypted_index: String,
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
625pub struct SearchResultLocationCitation {
626 pub cited_text: String,
627 pub search_result_index: u32,
628 pub source: String,
629 pub title: Option<String>,
630 pub start_block_index: u32,
631 pub end_block_index: u32,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
640#[serde(untagged)]
641#[expect(
642 clippy::enum_variant_names,
643 reason = "ToolSearch matches Anthropic API naming"
644)]
645#[schemars(rename = "MessagesTool")]
646pub enum Tool {
647 McpToolset(McpToolset),
649 Custom(CustomTool),
654 ToolSearch(ToolSearchTool),
656 Bash(BashTool),
658 TextEditor(TextEditorTool),
660 WebSearch(WebSearchTool),
662}
663
664#[serde_with::skip_serializing_none]
666#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
667pub struct CustomTool {
668 pub name: String,
670
671 #[serde(rename = "type")]
673 pub tool_type: Option<String>,
674
675 pub description: Option<String>,
677
678 pub input_schema: InputSchema,
680
681 pub defer_loading: Option<bool>,
683
684 pub cache_control: Option<CacheControl>,
686}
687
688#[serde_with::skip_serializing_none]
690#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
691pub struct InputSchema {
692 #[serde(rename = "type")]
693 pub schema_type: String,
694
695 pub properties: Option<HashMap<String, Value>>,
696
697 pub required: Option<Vec<String>>,
698
699 #[serde(flatten)]
701 pub additional: HashMap<String, Value>,
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
706pub struct BashTool {
707 #[serde(rename = "type")]
708 pub tool_type: String, pub name: String, #[serde(skip_serializing_if = "Option::is_none")]
713 pub cache_control: Option<CacheControl>,
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
718pub struct TextEditorTool {
719 #[serde(rename = "type")]
720 pub tool_type: String, pub name: String, #[serde(skip_serializing_if = "Option::is_none")]
725 pub cache_control: Option<CacheControl>,
726}
727
728#[serde_with::skip_serializing_none]
730#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
731pub struct WebSearchTool {
732 #[serde(rename = "type")]
733 pub tool_type: String, pub name: String, pub allowed_domains: Option<Vec<String>>,
738
739 pub blocked_domains: Option<Vec<String>>,
740
741 pub max_uses: Option<u32>,
742
743 pub user_location: Option<UserLocation>,
744
745 pub cache_control: Option<CacheControl>,
746}
747
748#[serde_with::skip_serializing_none]
750#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
751pub struct UserLocation {
752 #[serde(rename = "type")]
753 pub location_type: String, pub city: Option<String>,
756
757 pub region: Option<String>,
758
759 pub country: Option<String>,
760
761 pub timezone: Option<String>,
762}
763
764#[serde_with::skip_serializing_none]
770#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
771#[serde(tag = "type", rename_all = "snake_case")]
772#[schemars(rename = "MessagesToolChoice")]
773pub enum ToolChoice {
774 Auto {
776 disable_parallel_tool_use: Option<bool>,
777 },
778 Any {
780 disable_parallel_tool_use: Option<bool>,
781 },
782 Tool {
784 name: String,
785 disable_parallel_tool_use: Option<bool>,
786 },
787 None,
789}
790
791#[serde_with::skip_serializing_none]
797#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
798#[serde(tag = "type", rename_all = "snake_case")]
799pub enum ThinkingConfig {
800 Enabled {
802 budget_tokens: u32,
804 display: Option<ThinkingDisplay>,
806 },
807 Disabled,
809 Adaptive {
811 display: Option<ThinkingDisplay>,
814 },
815}
816
817#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
819#[serde(rename_all = "snake_case")]
820pub enum ThinkingDisplay {
821 Summarized,
823 Omitted,
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
833pub struct Message {
834 pub id: String,
836
837 #[serde(rename = "type")]
839 pub message_type: String,
840
841 pub role: String,
843
844 pub content: Vec<ContentBlock>,
846
847 pub model: String,
849
850 pub stop_reason: Option<StopReason>,
852
853 pub stop_sequence: Option<String>,
855
856 pub usage: Usage,
858}
859
860#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
862#[serde(tag = "type", rename_all = "snake_case")]
863pub enum ContentBlock {
864 Text {
866 text: String,
867 #[serde(skip_serializing_if = "Option::is_none")]
868 citations: Option<Vec<Citation>>,
869 },
870 ToolUse {
872 id: String,
873 name: String,
874 input: Value,
875 },
876 Thinking { thinking: String, signature: String },
878 RedactedThinking { data: String },
880 ServerToolUse {
882 id: String,
883 name: String,
884 input: Value,
885 },
886 WebSearchToolResult {
888 tool_use_id: String,
889 content: WebSearchToolResultContent,
890 },
891 ToolSearchToolResult {
893 tool_use_id: String,
894 content: ToolSearchResultContent,
895 },
896 ToolReference {
898 tool_name: String,
899 #[serde(skip_serializing_if = "Option::is_none")]
900 description: Option<String>,
901 },
902 McpToolUse {
904 id: String,
905 name: String,
906 server_name: String,
907 input: Value,
908 },
909 McpToolResult {
911 tool_use_id: String,
912 content: Option<ToolResultContent>,
913 is_error: Option<bool>,
914 },
915}
916
917#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
919#[serde(rename_all = "snake_case")]
920pub enum StopReason {
921 EndTurn,
923 MaxTokens,
925 StopSequence,
927 ToolUse,
929 PauseTurn,
931 Refusal,
933}
934
935#[serde_with::skip_serializing_none]
937#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
938#[schemars(rename = "MessagesUsage")]
939pub struct Usage {
940 pub input_tokens: u32,
942
943 pub output_tokens: u32,
945
946 pub cache_creation_input_tokens: Option<u32>,
948
949 pub cache_read_input_tokens: Option<u32>,
951
952 pub cache_creation: Option<CacheCreation>,
954
955 pub server_tool_use: Option<ServerToolUsage>,
957
958 pub service_tier: Option<String>,
960}
961
962#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
964pub struct CacheCreation {
965 #[serde(flatten)]
966 pub tokens_by_ttl: HashMap<String, u32>,
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
971pub struct ServerToolUsage {
972 pub web_search_requests: u32,
973}
974
975#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
981#[serde(tag = "type", rename_all = "snake_case")]
982pub enum MessageStreamEvent {
983 MessageStart { message: Message },
985 MessageDelta {
987 delta: MessageDelta,
988 usage: MessageDeltaUsage,
989 },
990 MessageStop,
992 ContentBlockStart {
994 index: u32,
995 content_block: ContentBlock,
996 },
997 ContentBlockDelta {
999 index: u32,
1000 delta: ContentBlockDelta,
1001 },
1002 ContentBlockStop { index: u32 },
1004 Ping,
1006 Error { error: ErrorResponse },
1008}
1009
1010#[serde_with::skip_serializing_none]
1012#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1013pub struct MessageDelta {
1014 pub stop_reason: Option<StopReason>,
1015
1016 pub stop_sequence: Option<String>,
1017}
1018
1019#[serde_with::skip_serializing_none]
1021#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1022pub struct MessageDeltaUsage {
1023 pub output_tokens: u32,
1024
1025 pub input_tokens: Option<u32>,
1026
1027 pub cache_creation_input_tokens: Option<u32>,
1028
1029 pub cache_read_input_tokens: Option<u32>,
1030
1031 pub server_tool_use: Option<ServerToolUsage>,
1032}
1033
1034#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1036#[serde(tag = "type", rename_all = "snake_case")]
1037#[expect(
1038 clippy::enum_variant_names,
1039 reason = "variant names match the OpenAI/Anthropic streaming delta type discriminators (text_delta, input_json_delta, etc.)"
1040)]
1041pub enum ContentBlockDelta {
1042 TextDelta { text: String },
1044 InputJsonDelta { partial_json: String },
1046 ThinkingDelta { thinking: String },
1048 SignatureDelta { signature: String },
1050 CitationsDelta { citation: Citation },
1052}
1053
1054#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1060#[schemars(rename = "MessagesErrorResponse")]
1061pub struct ErrorResponse {
1062 #[serde(rename = "type")]
1063 pub error_type: String,
1064
1065 pub message: String,
1066}
1067
1068#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1070#[serde(tag = "type", rename_all = "snake_case")]
1071#[expect(
1072 clippy::enum_variant_names,
1073 reason = "variant names match the OpenAI API error type discriminators (invalid_request_error, authentication_error, etc.)"
1074)]
1075pub enum ApiError {
1076 InvalidRequestError { message: String },
1077 AuthenticationError { message: String },
1078 BillingError { message: String },
1079 PermissionError { message: String },
1080 NotFoundError { message: String },
1081 RateLimitError { message: String },
1082 TimeoutError { message: String },
1083 ApiError { message: String },
1084 OverloadedError { message: String },
1085}
1086
1087#[serde_with::skip_serializing_none]
1093#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1094pub struct CountMessageTokensRequest {
1095 pub model: String,
1097
1098 pub messages: Vec<InputMessage>,
1100
1101 pub system: Option<SystemContent>,
1103
1104 pub thinking: Option<ThinkingConfig>,
1106
1107 pub tool_choice: Option<ToolChoice>,
1109
1110 pub tools: Option<Vec<Tool>>,
1112}
1113
1114#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1116pub struct CountMessageTokensResponse {
1117 pub input_tokens: u32,
1118}
1119
1120#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1126pub struct ModelInfo {
1127 #[serde(rename = "type")]
1129 pub model_type: String,
1130
1131 pub id: String,
1133
1134 pub display_name: String,
1136
1137 pub created_at: String,
1139}
1140
1141#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1143pub struct ListModelsResponse {
1144 pub data: Vec<ModelInfo>,
1145 pub has_more: bool,
1146 pub first_id: Option<String>,
1147 pub last_id: Option<String>,
1148}
1149
1150#[serde_with::skip_serializing_none]
1156#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1157pub struct ContainerConfig {
1158 pub id: Option<String>,
1160}
1161
1162#[serde_with::skip_serializing_none]
1164#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1165pub struct McpServerConfig {
1166 #[serde(rename = "type", default = "McpServerConfig::default_type")]
1168 pub server_type: String,
1169
1170 pub name: String,
1172
1173 pub url: String,
1175
1176 pub authorization_token: Option<String>,
1178
1179 pub tool_configuration: Option<McpToolConfiguration>,
1181}
1182
1183impl McpServerConfig {
1184 fn default_type() -> String {
1185 "url".to_string()
1186 }
1187}
1188
1189#[serde_with::skip_serializing_none]
1191#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1192pub struct McpToolConfiguration {
1193 pub enabled: Option<bool>,
1195
1196 pub allowed_tools: Option<Vec<String>>,
1198}
1199
1200#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1206pub struct McpToolUseBlock {
1207 pub id: String,
1209
1210 pub name: String,
1212
1213 pub server_name: String,
1215
1216 pub input: Value,
1218
1219 #[serde(skip_serializing_if = "Option::is_none")]
1221 pub cache_control: Option<CacheControl>,
1222}
1223
1224#[serde_with::skip_serializing_none]
1226#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1227pub struct McpToolResultBlock {
1228 pub tool_use_id: String,
1230
1231 pub content: Option<ToolResultContent>,
1233
1234 pub is_error: Option<bool>,
1236
1237 pub cache_control: Option<CacheControl>,
1239}
1240
1241#[serde_with::skip_serializing_none]
1243#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1244pub struct McpToolset {
1245 #[serde(rename = "type")]
1246 pub toolset_type: String, pub mcp_server_name: String,
1250
1251 pub default_config: Option<McpToolDefaultConfig>,
1253
1254 pub configs: Option<HashMap<String, McpToolConfig>>,
1256
1257 pub cache_control: Option<CacheControl>,
1259}
1260
1261#[serde_with::skip_serializing_none]
1263#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1264pub struct McpToolDefaultConfig {
1265 pub enabled: Option<bool>,
1267
1268 pub defer_loading: Option<bool>,
1270}
1271
1272#[serde_with::skip_serializing_none]
1274#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1275pub struct McpToolConfig {
1276 pub enabled: Option<bool>,
1278
1279 pub defer_loading: Option<bool>,
1281}
1282
1283#[serde_with::skip_serializing_none]
1289#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1290pub struct CodeExecutionTool {
1291 #[serde(rename = "type")]
1292 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1298
1299 pub defer_loading: Option<bool>,
1301
1302 pub strict: Option<bool>,
1304
1305 pub cache_control: Option<CacheControl>,
1307}
1308
1309#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1311pub struct CodeExecutionResultBlock {
1312 pub stdout: String,
1314
1315 pub stderr: String,
1317
1318 pub return_code: i32,
1320
1321 pub content: Vec<CodeExecutionOutputBlock>,
1323}
1324
1325#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1327pub struct CodeExecutionOutputBlock {
1328 #[serde(rename = "type")]
1329 pub block_type: String, pub file_id: String,
1333}
1334
1335#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1337pub struct CodeExecutionToolResultBlock {
1338 pub tool_use_id: String,
1340
1341 pub content: CodeExecutionToolResultContent,
1343
1344 #[serde(skip_serializing_if = "Option::is_none")]
1346 pub cache_control: Option<CacheControl>,
1347}
1348
1349#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1351#[serde(untagged)]
1352pub enum CodeExecutionToolResultContent {
1353 Success(CodeExecutionResultBlock),
1354 Error(CodeExecutionToolResultError),
1355}
1356
1357#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1359pub struct CodeExecutionToolResultError {
1360 #[serde(rename = "type")]
1361 pub error_type: String, pub error_code: CodeExecutionToolResultErrorCode,
1364}
1365
1366#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1368#[serde(rename_all = "snake_case")]
1369pub enum CodeExecutionToolResultErrorCode {
1370 Unavailable,
1371 CodeExecutionExceededTimeout,
1372 ContainerExpired,
1373 InvalidToolInput,
1374}
1375
1376#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1378pub struct BashCodeExecutionResultBlock {
1379 pub stdout: String,
1381
1382 pub stderr: String,
1384
1385 pub return_code: i32,
1387
1388 pub content: Vec<BashCodeExecutionOutputBlock>,
1390}
1391
1392#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1394pub struct BashCodeExecutionOutputBlock {
1395 #[serde(rename = "type")]
1396 pub block_type: String, pub file_id: String,
1400}
1401
1402#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1404pub struct BashCodeExecutionToolResultBlock {
1405 pub tool_use_id: String,
1407
1408 pub content: BashCodeExecutionToolResultContent,
1410
1411 #[serde(skip_serializing_if = "Option::is_none")]
1413 pub cache_control: Option<CacheControl>,
1414}
1415
1416#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1418#[serde(untagged)]
1419pub enum BashCodeExecutionToolResultContent {
1420 Success(BashCodeExecutionResultBlock),
1421 Error(BashCodeExecutionToolResultError),
1422}
1423
1424#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1426pub struct BashCodeExecutionToolResultError {
1427 #[serde(rename = "type")]
1428 pub error_type: String, pub error_code: BashCodeExecutionToolResultErrorCode,
1431}
1432
1433#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1435#[serde(rename_all = "snake_case")]
1436pub enum BashCodeExecutionToolResultErrorCode {
1437 Unavailable,
1438 CodeExecutionExceededTimeout,
1439 ContainerExpired,
1440 InvalidToolInput,
1441}
1442
1443#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1445pub struct TextEditorCodeExecutionToolResultBlock {
1446 pub tool_use_id: String,
1448
1449 pub content: TextEditorCodeExecutionToolResultContent,
1451
1452 #[serde(skip_serializing_if = "Option::is_none")]
1454 pub cache_control: Option<CacheControl>,
1455}
1456
1457#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1459#[serde(untagged)]
1460pub enum TextEditorCodeExecutionToolResultContent {
1461 CreateResult(TextEditorCodeExecutionCreateResultBlock),
1462 StrReplaceResult(TextEditorCodeExecutionStrReplaceResultBlock),
1463 ViewResult(TextEditorCodeExecutionViewResultBlock),
1464 Error(TextEditorCodeExecutionToolResultError),
1465}
1466
1467#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1469pub struct TextEditorCodeExecutionCreateResultBlock {
1470 #[serde(rename = "type")]
1471 pub block_type: String, }
1473
1474#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1476pub struct TextEditorCodeExecutionStrReplaceResultBlock {
1477 #[serde(rename = "type")]
1478 pub block_type: String, #[serde(skip_serializing_if = "Option::is_none")]
1482 pub snippet: Option<String>,
1483}
1484
1485#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1487pub struct TextEditorCodeExecutionViewResultBlock {
1488 #[serde(rename = "type")]
1489 pub block_type: String, pub content: String,
1493}
1494
1495#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1497pub struct TextEditorCodeExecutionToolResultError {
1498 #[serde(rename = "type")]
1499 pub error_type: String,
1500
1501 pub error_code: TextEditorCodeExecutionToolResultErrorCode,
1502}
1503
1504#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1506#[serde(rename_all = "snake_case")]
1507pub enum TextEditorCodeExecutionToolResultErrorCode {
1508 Unavailable,
1509 InvalidToolInput,
1510 FileNotFound,
1511 ContainerExpired,
1512}
1513
1514#[serde_with::skip_serializing_none]
1520#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1521pub struct WebFetchTool {
1522 #[serde(rename = "type")]
1523 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1529
1530 pub max_uses: Option<u32>,
1532
1533 pub cache_control: Option<CacheControl>,
1535}
1536
1537#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1539pub struct WebFetchResultBlock {
1540 #[serde(rename = "type")]
1541 pub block_type: String, pub url: String,
1545
1546 pub content: DocumentBlock,
1548
1549 #[serde(skip_serializing_if = "Option::is_none")]
1551 pub retrieved_at: Option<String>,
1552}
1553
1554#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1556pub struct WebFetchToolResultBlock {
1557 pub tool_use_id: String,
1559
1560 pub content: WebFetchToolResultContent,
1562
1563 #[serde(skip_serializing_if = "Option::is_none")]
1565 pub cache_control: Option<CacheControl>,
1566}
1567
1568#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1570#[serde(untagged)]
1571pub enum WebFetchToolResultContent {
1572 Success(WebFetchResultBlock),
1573 Error(WebFetchToolResultError),
1574}
1575
1576#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1578pub struct WebFetchToolResultError {
1579 #[serde(rename = "type")]
1580 pub error_type: String, pub error_code: WebFetchToolResultErrorCode,
1583}
1584
1585#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1587#[serde(rename_all = "snake_case")]
1588pub enum WebFetchToolResultErrorCode {
1589 InvalidToolInput,
1590 Unavailable,
1591 MaxUsesExceeded,
1592 TooManyRequests,
1593 UrlNotAllowed,
1594 FetchFailed,
1595 ContentTooLarge,
1596}
1597
1598#[serde_with::skip_serializing_none]
1604#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1605pub struct ToolSearchTool {
1606 #[serde(rename = "type")]
1607 pub tool_type: String, pub name: String,
1610
1611 pub allowed_callers: Option<Vec<String>>,
1613
1614 pub cache_control: Option<CacheControl>,
1616}
1617
1618#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1620pub struct ToolReferenceBlock {
1621 #[serde(rename = "type")]
1622 pub block_type: String, pub tool_name: String,
1626
1627 #[serde(skip_serializing_if = "Option::is_none")]
1629 pub description: Option<String>,
1630}
1631
1632#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1634pub struct ToolSearchResultContent {
1635 #[serde(rename = "type")]
1636 pub block_type: String, pub tool_references: Vec<ToolReferenceBlock>,
1640}
1641
1642#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1644pub struct ToolSearchToolResultBlock {
1645 pub tool_use_id: String,
1647
1648 pub content: ToolSearchResultContent,
1650
1651 #[serde(skip_serializing_if = "Option::is_none")]
1653 pub cache_control: Option<CacheControl>,
1654}
1655
1656#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1662pub struct ContainerUploadBlock {
1663 #[serde(rename = "type")]
1664 pub block_type: String, pub file_id: String,
1668
1669 pub file_name: String,
1671
1672 #[serde(skip_serializing_if = "Option::is_none")]
1674 pub file_path: Option<String>,
1675}
1676
1677#[serde_with::skip_serializing_none]
1683#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1684pub struct MemoryTool {
1685 #[serde(rename = "type")]
1686 pub tool_type: String, pub name: String, pub allowed_callers: Option<Vec<String>>,
1692
1693 pub defer_loading: Option<bool>,
1695
1696 pub strict: Option<bool>,
1698
1699 pub input_examples: Option<Vec<Value>>,
1701
1702 pub cache_control: Option<CacheControl>,
1704}
1705
1706#[serde_with::skip_serializing_none]
1712#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1713pub struct ComputerUseTool {
1714 #[serde(rename = "type")]
1715 pub tool_type: String, pub name: String, pub display_width_px: u32,
1721
1722 pub display_height_px: u32,
1724
1725 pub display_number: Option<u32>,
1727
1728 pub allowed_callers: Option<Vec<String>>,
1730
1731 pub cache_control: Option<CacheControl>,
1733}
1734
1735#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1741#[serde(tag = "type", rename_all = "snake_case")]
1742pub enum BetaInputContentBlock {
1743 Text(TextBlock),
1745 Image(ImageBlock),
1746 Document(DocumentBlock),
1747 ToolUse(ToolUseBlock),
1748 ToolResult(ToolResultBlock),
1749 Thinking(ThinkingBlock),
1750 RedactedThinking(RedactedThinkingBlock),
1751 ServerToolUse(ServerToolUseBlock),
1752 SearchResult(SearchResultBlock),
1753 WebSearchToolResult(WebSearchToolResultBlock),
1754
1755 McpToolUse(McpToolUseBlock),
1757 McpToolResult(McpToolResultBlock),
1758
1759 CodeExecutionToolResult(CodeExecutionToolResultBlock),
1761 BashCodeExecutionToolResult(BashCodeExecutionToolResultBlock),
1762 TextEditorCodeExecutionToolResult(TextEditorCodeExecutionToolResultBlock),
1763
1764 WebFetchToolResult(WebFetchToolResultBlock),
1766
1767 ToolSearchToolResult(ToolSearchToolResultBlock),
1769 ToolReference(ToolReferenceBlock),
1770
1771 ContainerUpload(ContainerUploadBlock),
1773}
1774
1775#[serde_with::skip_serializing_none]
1777#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1778#[serde(tag = "type", rename_all = "snake_case")]
1779pub enum BetaContentBlock {
1780 Text {
1782 text: String,
1783 citations: Option<Vec<Citation>>,
1784 },
1785 ToolUse {
1786 id: String,
1787 name: String,
1788 input: Value,
1789 },
1790 Thinking {
1791 thinking: String,
1792 signature: String,
1793 },
1794 RedactedThinking {
1795 data: String,
1796 },
1797 ServerToolUse {
1798 id: String,
1799 name: String,
1800 input: Value,
1801 },
1802 WebSearchToolResult {
1803 tool_use_id: String,
1804 content: WebSearchToolResultContent,
1805 },
1806
1807 McpToolUse {
1809 id: String,
1810 name: String,
1811 server_name: String,
1812 input: Value,
1813 },
1814 McpToolResult {
1815 tool_use_id: String,
1816 content: Option<ToolResultContent>,
1817 is_error: Option<bool>,
1818 },
1819
1820 CodeExecutionToolResult {
1822 tool_use_id: String,
1823 content: CodeExecutionToolResultContent,
1824 },
1825 BashCodeExecutionToolResult {
1826 tool_use_id: String,
1827 content: BashCodeExecutionToolResultContent,
1828 },
1829 TextEditorCodeExecutionToolResult {
1830 tool_use_id: String,
1831 content: TextEditorCodeExecutionToolResultContent,
1832 },
1833
1834 WebFetchToolResult {
1836 tool_use_id: String,
1837 content: WebFetchToolResultContent,
1838 },
1839
1840 ToolSearchToolResult {
1842 tool_use_id: String,
1843 content: ToolSearchResultContent,
1844 },
1845 ToolReference {
1846 tool_name: String,
1847 description: Option<String>,
1848 },
1849
1850 ContainerUpload {
1852 file_id: String,
1853 file_name: String,
1854 file_path: Option<String>,
1855 },
1856}
1857
1858#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1860#[serde(untagged)]
1861pub enum BetaTool {
1862 Custom(CustomTool),
1864 Bash(BashTool),
1865 TextEditor(TextEditorTool),
1866 WebSearch(WebSearchTool),
1867
1868 CodeExecution(CodeExecutionTool),
1870 McpToolset(McpToolset),
1871 WebFetch(WebFetchTool),
1872 ToolSearch(ToolSearchTool),
1873 Memory(MemoryTool),
1874 ComputerUse(ComputerUseTool),
1875}
1876
1877#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1879#[serde(rename_all = "snake_case")]
1880pub enum BetaServerToolName {
1881 WebSearch,
1882 WebFetch,
1883 CodeExecution,
1884 BashCodeExecution,
1885 TextEditorCodeExecution,
1886 ToolSearchToolRegex,
1887 ToolSearchToolBm25,
1888}
1889
1890#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1892#[serde(tag = "type", rename_all = "snake_case")]
1893pub enum ServerToolCaller {
1894 Direct,
1896 #[serde(rename = "code_execution_20250825")]
1898 CodeExecution20250825,
1899}
1900
1901#[cfg(test)]
1902mod tests {
1903 use serde_json::{self, json};
1904
1905 use super::*;
1906
1907 #[test]
1908 fn test_system_blocks_preserve_type_field() {
1909 let input = json!({
1910 "model": "test",
1911 "messages": [{"role": "user", "content": "hi"}],
1912 "max_tokens": 100,
1913 "system": [
1914 {"type": "text", "text": "system prompt", "cache_control": {"type": "ephemeral"}}
1915 ]
1916 });
1917
1918 let req: CreateMessageRequest = serde_json::from_value(input).expect("should deserialize");
1919 let reserialized = serde_json::to_value(&req).expect("should serialize");
1920
1921 let system_blocks = reserialized.get("system").unwrap().as_array().unwrap();
1922 let first_block = &system_blocks[0];
1923 assert_eq!(
1924 first_block.get("type").and_then(|v| v.as_str()),
1925 Some("text"),
1926 "system block must retain 'type' field after round-trip: got {first_block:?}",
1927 );
1928 }
1929
1930 #[test]
1931 fn test_message_content_blocks_preserve_type_field() {
1932 let input = json!({
1933 "model": "test",
1934 "messages": [{
1935 "role": "user",
1936 "content": [
1937 {"type": "text", "text": "hello", "cache_control": {"type": "ephemeral"}}
1938 ]
1939 }],
1940 "max_tokens": 100
1941 });
1942
1943 let req: CreateMessageRequest = serde_json::from_value(input).expect("should deserialize");
1944 let reserialized = serde_json::to_value(&req).expect("should serialize");
1945
1946 let msg = &reserialized["messages"][0];
1947 let content_blocks = msg["content"].as_array().unwrap();
1948 let first_block = &content_blocks[0];
1949 assert_eq!(
1950 first_block.get("type").and_then(|v| v.as_str()),
1951 Some("text"),
1952 "content block must retain 'type' field: got {first_block:?}",
1953 );
1954 }
1955
1956 #[test]
1957 fn test_unknown_fields_preserved_via_flatten() {
1958 let input = json!({
1959 "model": "test-model",
1960 "messages": [{"role": "user", "content": "hello"}],
1961 "max_tokens": 100,
1962 "thinking": {"type": "adaptive"},
1963 "context_management": {"edits": [{"type": "clear_thinking", "keep": "all"}]},
1964 "output_config": {"effort": "high"},
1965 "stream": true
1966 });
1967
1968 let req: CreateMessageRequest =
1969 serde_json::from_value(input.clone()).expect("should deserialize");
1970 assert!(matches!(
1971 req.thinking,
1972 Some(ThinkingConfig::Adaptive { .. })
1973 ));
1974
1975 let reserialized = serde_json::to_value(&req).expect("should serialize");
1976 assert_eq!(
1977 reserialized.get("context_management"),
1978 input.get("context_management"),
1979 "context_management must survive round-trip"
1980 );
1981 assert_eq!(
1982 reserialized.get("output_config"),
1983 input.get("output_config"),
1984 "output_config must survive round-trip"
1985 );
1986 }
1987
1988 fn base_request() -> CreateMessageRequest {
1989 CreateMessageRequest {
1990 model: "claude-test".to_string(),
1991 messages: vec![InputMessage {
1992 role: Role::User,
1993 content: InputContent::String("hello".to_string()),
1994 }],
1995 max_tokens: 16,
1996 metadata: None,
1997 service_tier: None,
1998 stop_sequences: None,
1999 stream: None,
2000 system: None,
2001 temperature: None,
2002 thinking: None,
2003 tool_choice: None,
2004 tools: None,
2005 top_k: None,
2006 top_p: None,
2007 container: None,
2008 mcp_servers: None,
2009 other: Map::new(),
2010 }
2011 }
2012
2013 fn custom_tool(name: &str) -> Tool {
2014 Tool::Custom(CustomTool {
2015 name: name.to_string(),
2016 tool_type: None,
2017 description: Some("test tool".to_string()),
2018 input_schema: InputSchema {
2019 schema_type: "object".to_string(),
2020 properties: None,
2021 required: None,
2022 additional: HashMap::new(),
2023 },
2024 defer_loading: None,
2025 cache_control: None,
2026 })
2027 }
2028
2029 fn mcp_toolset(configs: Option<HashMap<String, McpToolConfig>>) -> Tool {
2030 Tool::McpToolset(McpToolset {
2031 toolset_type: "mcp_toolset".to_string(),
2032 mcp_server_name: "brave".to_string(),
2033 default_config: None,
2034 configs,
2035 cache_control: None,
2036 })
2037 }
2038
2039 fn mcp_server_config() -> McpServerConfig {
2040 McpServerConfig {
2041 server_type: "url".to_string(),
2042 name: "brave".to_string(),
2043 url: "https://example.com/mcp".to_string(),
2044 authorization_token: None,
2045 tool_configuration: None,
2046 }
2047 }
2048 #[test]
2049 fn test_tool_mcp_toolset_defer_loading_deserialization() {
2050 let json = r#"{
2051 "type": "mcp_toolset",
2052 "mcp_server_name": "brave",
2053 "default_config": {"defer_loading": true}
2054 }"#;
2055
2056 let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize McpToolset Tool");
2057 match tool {
2058 Tool::McpToolset(ts) => {
2059 assert_eq!(ts.mcp_server_name, "brave");
2060 let default_config = ts.default_config.expect("default_config should be Some");
2061 assert_eq!(default_config.defer_loading, Some(true));
2062 }
2063 other => panic!(
2064 "Expected McpToolset, got {:?}",
2065 std::mem::discriminant(&other)
2066 ),
2067 }
2068 }
2069
2070 #[test]
2071 fn test_tool_search_tool_deserialization() {
2072 let json = r#"{
2073 "type": "tool_search_tool_regex_20251119",
2074 "name": "tool_search_tool_regex"
2075 }"#;
2076
2077 let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize ToolSearch Tool");
2078 match tool {
2079 Tool::ToolSearch(ts) => {
2080 assert_eq!(ts.name, "tool_search_tool_regex");
2081 assert_eq!(ts.tool_type, "tool_search_tool_regex_20251119");
2082 }
2083 other => panic!(
2084 "Expected ToolSearch, got {:?}",
2085 std::mem::discriminant(&other)
2086 ),
2087 }
2088 }
2089
2090 #[test]
2091 fn test_content_block_tool_search_tool_result_deserialization() {
2092 let json = r#"{
2093 "type": "tool_search_tool_result",
2094 "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2095 "content": {
2096 "type": "tool_search_tool_search_result",
2097 "tool_references": [
2098 {"type": "tool_reference", "tool_name": "get_weather"}
2099 ]
2100 }
2101 }"#;
2102
2103 let block: ContentBlock = serde_json::from_str(json)
2104 .expect("Failed to deserialize tool_search_tool_result ContentBlock");
2105 match block {
2106 ContentBlock::ToolSearchToolResult {
2107 tool_use_id,
2108 content,
2109 } => {
2110 assert_eq!(tool_use_id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
2111 assert_eq!(content.tool_references.len(), 1);
2112 assert_eq!(content.tool_references[0].tool_name, "get_weather");
2113 }
2114 _ => panic!("Expected ToolSearchToolResult variant"),
2115 }
2116 }
2117
2118 #[test]
2119 fn test_content_block_server_tool_use_deserialization() {
2120 let json = r#"{
2121 "type": "server_tool_use",
2122 "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2123 "name": "tool_search_tool_regex",
2124 "input": {"query": "weather"}
2125 }"#;
2126
2127 let block: ContentBlock =
2128 serde_json::from_str(json).expect("Failed to deserialize server_tool_use ContentBlock");
2129 match block {
2130 ContentBlock::ServerToolUse { id, name, input: _ } => {
2131 assert_eq!(id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
2132 assert_eq!(name, "tool_search_tool_regex");
2133 }
2134 _ => panic!("Expected ServerToolUse variant"),
2135 }
2136 }
2137
2138 #[test]
2139 fn test_content_block_tool_reference_deserialization() {
2140 let json = r#"{
2141 "type": "tool_reference",
2142 "tool_name": "get_weather",
2143 "description": "Get the weather for a location"
2144 }"#;
2145
2146 let block: ContentBlock =
2147 serde_json::from_str(json).expect("Failed to deserialize tool_reference ContentBlock");
2148 match block {
2149 ContentBlock::ToolReference {
2150 tool_name,
2151 description,
2152 } => {
2153 assert_eq!(tool_name, "get_weather");
2154 assert_eq!(description.unwrap(), "Get the weather for a location");
2155 }
2156 _ => panic!("Expected ToolReference variant"),
2157 }
2158 }
2159
2160 #[test]
2161 fn test_tool_choice_auto_requires_tools() {
2162 let mut request = base_request();
2163 request.tool_choice = Some(ToolChoice::Auto {
2164 disable_parallel_tool_use: None,
2165 });
2166
2167 assert!(request.validate().is_err());
2168 }
2169
2170 #[test]
2171 fn test_tool_choice_any_requires_tools() {
2172 let mut request = base_request();
2173 request.tool_choice = Some(ToolChoice::Any {
2174 disable_parallel_tool_use: None,
2175 });
2176
2177 assert!(request.validate().is_err());
2178 }
2179
2180 #[test]
2181 fn test_tool_choice_auto_with_tools_is_valid() {
2182 let mut request = base_request();
2183 request.tool_choice = Some(ToolChoice::Auto {
2184 disable_parallel_tool_use: None,
2185 });
2186 request.tools = Some(vec![custom_tool("get_weather")]);
2187
2188 assert!(request.validate().is_ok());
2189 }
2190
2191 #[test]
2192 fn test_tool_choice_any_with_tools_is_valid() {
2193 let mut request = base_request();
2194 request.tool_choice = Some(ToolChoice::Any {
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_specific_tool_requires_tools() {
2204 let mut request = base_request();
2205 request.tool_choice = Some(ToolChoice::Tool {
2206 name: "get_weather".to_string(),
2207 disable_parallel_tool_use: None,
2208 });
2209
2210 assert!(request.validate().is_err());
2211 }
2212
2213 #[test]
2214 fn test_tool_choice_specific_tool_must_exist() {
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 request.tools = Some(vec![custom_tool("search_web")]);
2221
2222 assert!(request.validate().is_err());
2223 }
2224
2225 #[test]
2226 fn test_tool_choice_none_without_tools_is_valid() {
2227 let mut request = base_request();
2228 request.tool_choice = Some(ToolChoice::None);
2229
2230 assert!(request.validate().is_ok());
2231 }
2232
2233 #[test]
2234 fn test_tool_choice_specific_tool_is_valid_when_declared() {
2235 let mut request = base_request();
2236 request.tool_choice = Some(ToolChoice::Tool {
2237 name: "get_weather".to_string(),
2238 disable_parallel_tool_use: None,
2239 });
2240 request.tools = Some(vec![custom_tool("get_weather")]);
2241
2242 assert!(request.validate().is_ok());
2243 }
2244
2245 #[test]
2246 fn test_tool_choice_specific_tool_is_valid_with_mcp_toolset() {
2247 let mut request = base_request();
2248 request.tool_choice = Some(ToolChoice::Tool {
2249 name: "get_weather".to_string(),
2250 disable_parallel_tool_use: None,
2251 });
2252 request.tools = Some(vec![mcp_toolset(None)]);
2253 request.mcp_servers = Some(vec![mcp_server_config()]);
2254
2255 assert!(request.validate().is_ok());
2256 }
2257
2258 #[test]
2259 fn test_tool_choice_specific_tool_uses_mcp_toolset_default_when_override_missing() {
2260 let mut request = base_request();
2261 request.tool_choice = Some(ToolChoice::Tool {
2262 name: "get_weather".to_string(),
2263 disable_parallel_tool_use: None,
2264 });
2265 request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2266 "search_web".to_string(),
2267 McpToolConfig {
2268 enabled: Some(false),
2269 defer_loading: None,
2270 },
2271 )])))]);
2272 request.mcp_servers = Some(vec![mcp_server_config()]);
2273
2274 assert!(request.validate().is_ok());
2275 }
2276
2277 #[test]
2278 fn test_tool_choice_specific_tool_must_be_enabled_in_mcp_toolset_configs() {
2279 let mut request = base_request();
2280 request.tool_choice = Some(ToolChoice::Tool {
2281 name: "get_weather".to_string(),
2282 disable_parallel_tool_use: None,
2283 });
2284 request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2285 "get_weather".to_string(),
2286 McpToolConfig {
2287 enabled: Some(false),
2288 defer_loading: None,
2289 },
2290 )])))]);
2291 request.mcp_servers = Some(vec![mcp_server_config()]);
2292
2293 assert!(request.validate().is_err());
2294 }
2295
2296 #[test]
2297 fn test_thinking_config_adaptive_minimal() {
2298 let cfg: ThinkingConfig = serde_json::from_str(r#"{"type":"adaptive"}"#).unwrap();
2299 match cfg {
2300 ThinkingConfig::Adaptive { display } => assert_eq!(display, None),
2301 other => panic!("expected Adaptive, got {other:?}"),
2302 }
2303 }
2304
2305 #[test]
2306 fn test_thinking_config_adaptive_with_display() {
2307 let cfg: ThinkingConfig =
2308 serde_json::from_str(r#"{"type":"adaptive","display":"omitted"}"#).unwrap();
2309 match cfg {
2310 ThinkingConfig::Adaptive { display } => {
2311 assert_eq!(display, Some(ThinkingDisplay::Omitted));
2312 }
2313 other => panic!("expected Adaptive, got {other:?}"),
2314 }
2315
2316 let cfg: ThinkingConfig =
2317 serde_json::from_str(r#"{"type":"adaptive","display":"summarized"}"#).unwrap();
2318 match cfg {
2319 ThinkingConfig::Adaptive { display } => {
2320 assert_eq!(display, Some(ThinkingDisplay::Summarized));
2321 }
2322 other => panic!("expected Adaptive, got {other:?}"),
2323 }
2324 }
2325
2326 #[test]
2327 fn test_thinking_config_adaptive_round_trip_omits_null_display() {
2328 let cfg = ThinkingConfig::Adaptive { display: None };
2329 let json = serde_json::to_string(&cfg).unwrap();
2330 assert_eq!(json, r#"{"type":"adaptive"}"#);
2331 }
2332
2333 #[test]
2334 fn test_thinking_config_existing_variants_still_work() {
2335 let cfg: ThinkingConfig =
2336 serde_json::from_str(r#"{"type":"enabled","budget_tokens":1024}"#).unwrap();
2337 assert!(matches!(
2338 cfg,
2339 ThinkingConfig::Enabled {
2340 budget_tokens: 1024,
2341 display: None
2342 }
2343 ));
2344
2345 let cfg: ThinkingConfig = serde_json::from_str(r#"{"type":"disabled"}"#).unwrap();
2346 assert!(matches!(cfg, ThinkingConfig::Disabled));
2347 }
2348
2349 #[test]
2350 fn test_thinking_config_enabled_with_display() {
2351 let cfg: ThinkingConfig = serde_json::from_str(
2352 r#"{"type":"enabled","budget_tokens":2048,"display":"summarized"}"#,
2353 )
2354 .unwrap();
2355 match cfg {
2356 ThinkingConfig::Enabled {
2357 budget_tokens,
2358 display,
2359 } => {
2360 assert_eq!(budget_tokens, 2048);
2361 assert_eq!(display, Some(ThinkingDisplay::Summarized));
2362 }
2363 other => panic!("expected Enabled, got {other:?}"),
2364 }
2365 }
2366
2367 #[test]
2368 fn test_thinking_config_enabled_round_trip_omits_null_display() {
2369 let cfg = ThinkingConfig::Enabled {
2370 budget_tokens: 1024,
2371 display: None,
2372 };
2373 let json = serde_json::to_string(&cfg).unwrap();
2374 assert_eq!(json, r#"{"type":"enabled","budget_tokens":1024}"#);
2375 }
2376
2377 #[test]
2378 fn test_full_message_with_tool_search_flow_deserialization() {
2379 let json = r#"{
2381 "id": "msg_01TEST",
2382 "type": "message",
2383 "role": "assistant",
2384 "model": "claude-sonnet-4-5-20250929",
2385 "content": [
2386 {
2387 "type": "server_tool_use",
2388 "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2389 "name": "tool_search_tool_regex",
2390 "input": {"query": "weather"}
2391 },
2392 {
2393 "type": "tool_search_tool_result",
2394 "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2395 "content": {
2396 "type": "tool_search_tool_search_result",
2397 "tool_references": [
2398 {"type": "tool_reference", "tool_name": "get_weather"}
2399 ]
2400 }
2401 },
2402 {
2403 "type": "tool_use",
2404 "id": "toolu_01ABC",
2405 "name": "get_weather",
2406 "input": {"location": "San Francisco"}
2407 }
2408 ],
2409 "stop_reason": "tool_use",
2410 "stop_sequence": null,
2411 "usage": {
2412 "input_tokens": 100,
2413 "output_tokens": 50
2414 }
2415 }"#;
2416
2417 let msg: Message = serde_json::from_str(json)
2418 .expect("Failed to deserialize Message with tool search flow");
2419 assert_eq!(msg.content.len(), 3);
2420 assert!(matches!(msg.content[0], ContentBlock::ServerToolUse { .. }));
2421 assert!(matches!(
2422 msg.content[1],
2423 ContentBlock::ToolSearchToolResult { .. }
2424 ));
2425 assert!(matches!(msg.content[2], ContentBlock::ToolUse { .. }));
2426 }
2427}