1use serde::{Deserialize, Serialize};
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct Container {
34 pub id: String,
36 pub expires_at: String,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum Role {
44 User,
45 Assistant,
46}
47
48#[derive(Debug, Clone, Serialize)]
54#[serde(tag = "type", rename_all = "snake_case")]
55pub enum ContentBlock {
56 Text {
58 text: String,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 cache_control: Option<CacheControl>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 citations: Option<Vec<Citation>>,
64 },
65 Image {
67 source: ImageSource,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 cache_control: Option<CacheControl>,
70 },
71 Document {
75 source: DocumentSource,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 title: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 context: Option<String>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 citations: Option<CitationConfig>,
82 #[serde(skip_serializing_if = "Option::is_none")]
83 cache_control: Option<CacheControl>,
84 },
85 ToolUse {
87 id: String,
88 name: String,
89 input: serde_json::Value,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 caller: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 cache_control: Option<CacheControl>,
95 },
96 ToolResult {
98 tool_use_id: String,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 content: Option<ToolResultContent>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 is_error: Option<bool>,
103 },
104 Thinking {
109 thinking: String,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 signature: Option<String>,
112 },
113 RedactedThinking { data: String },
118 SearchResult {
122 source: String,
123 title: String,
124 content: Vec<TextBlock>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 citations: Option<CitationConfig>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 cache_control: Option<CacheControl>,
129 },
130 ServerToolUse {
132 id: String,
133 name: String,
134 input: serde_json::Value,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 cache_control: Option<CacheControl>,
137 },
138 WebSearchToolResult {
140 tool_use_id: String,
141 content: serde_json::Value,
142 #[serde(skip_serializing_if = "Option::is_none")]
143 cache_control: Option<CacheControl>,
144 },
145 CodeExecutionToolResult {
147 tool_use_id: String,
148 content: serde_json::Value,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 cache_control: Option<CacheControl>,
151 },
152 ContainerUpload {
154 file_id: String,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 cache_control: Option<CacheControl>,
157 },
158 MidConvSystem {
160 content: Vec<TextBlock>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 cache_control: Option<CacheControl>,
163 },
164 #[serde(untagged)]
169 Unknown {
170 block_type: String,
172 data: serde_json::Value,
174 },
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(untagged)]
180pub enum ToolResultContent {
181 Text(String),
183 Blocks(Vec<ContentBlock>),
185}
186
187#[derive(Deserialize)]
194#[serde(tag = "type", rename_all = "snake_case")]
195enum ContentBlockHelper {
196 Text {
197 text: String,
198 cache_control: Option<CacheControl>,
199 citations: Option<Vec<Citation>>,
200 },
201 Image {
202 source: ImageSource,
203 cache_control: Option<CacheControl>,
204 },
205 Document {
206 source: DocumentSource,
207 title: Option<String>,
208 context: Option<String>,
209 citations: Option<CitationConfig>,
210 cache_control: Option<CacheControl>,
211 },
212 ToolUse {
213 id: String,
214 name: String,
215 input: serde_json::Value,
216 caller: Option<String>,
217 cache_control: Option<CacheControl>,
218 },
219 ToolResult {
220 tool_use_id: String,
221 content: Option<ToolResultContent>,
222 is_error: Option<bool>,
223 },
224 Thinking {
225 thinking: String,
226 signature: Option<String>,
227 },
228 RedactedThinking {
229 data: String,
230 },
231 SearchResult {
232 source: String,
233 title: String,
234 content: Vec<TextBlock>,
235 citations: Option<CitationConfig>,
236 cache_control: Option<CacheControl>,
237 },
238 ServerToolUse {
239 id: String,
240 name: String,
241 input: serde_json::Value,
242 cache_control: Option<CacheControl>,
243 },
244 WebSearchToolResult {
245 tool_use_id: String,
246 content: serde_json::Value,
247 cache_control: Option<CacheControl>,
248 },
249 CodeExecutionToolResult {
250 tool_use_id: String,
251 content: serde_json::Value,
252 cache_control: Option<CacheControl>,
253 },
254 ContainerUpload {
255 file_id: String,
256 cache_control: Option<CacheControl>,
257 },
258 MidConvSystem {
259 content: Vec<TextBlock>,
260 cache_control: Option<CacheControl>,
261 },
262}
263
264impl<'de> serde::Deserialize<'de> for ContentBlock {
265 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
266 where
267 D: serde::Deserializer<'de>,
268 {
269 let value = serde_json::Value::deserialize(deserializer)?;
270
271 match serde_json::from_value::<ContentBlockHelper>(value.clone()) {
273 Ok(helper) => Ok(match helper {
274 ContentBlockHelper::Text {
275 text,
276 cache_control,
277 citations,
278 } => ContentBlock::Text {
279 text,
280 cache_control,
281 citations,
282 },
283 ContentBlockHelper::Image {
284 source,
285 cache_control,
286 } => ContentBlock::Image {
287 source,
288 cache_control,
289 },
290 ContentBlockHelper::Document {
291 source,
292 title,
293 context,
294 citations,
295 cache_control,
296 } => ContentBlock::Document {
297 source,
298 title,
299 context,
300 citations,
301 cache_control,
302 },
303 ContentBlockHelper::ToolUse {
304 id,
305 name,
306 input,
307 caller,
308 cache_control,
309 } => ContentBlock::ToolUse {
310 id,
311 name,
312 input,
313 caller,
314 cache_control,
315 },
316 ContentBlockHelper::ToolResult {
317 tool_use_id,
318 content,
319 is_error,
320 } => ContentBlock::ToolResult {
321 tool_use_id,
322 content,
323 is_error,
324 },
325 ContentBlockHelper::Thinking {
326 thinking,
327 signature,
328 } => ContentBlock::Thinking {
329 thinking,
330 signature,
331 },
332 ContentBlockHelper::RedactedThinking { data } => {
333 ContentBlock::RedactedThinking { data }
334 }
335 ContentBlockHelper::SearchResult {
336 source,
337 title,
338 content,
339 citations,
340 cache_control,
341 } => ContentBlock::SearchResult {
342 source,
343 title,
344 content,
345 citations,
346 cache_control,
347 },
348 ContentBlockHelper::ServerToolUse {
349 id,
350 name,
351 input,
352 cache_control,
353 } => ContentBlock::ServerToolUse {
354 id,
355 name,
356 input,
357 cache_control,
358 },
359 ContentBlockHelper::WebSearchToolResult {
360 tool_use_id,
361 content,
362 cache_control,
363 } => ContentBlock::WebSearchToolResult {
364 tool_use_id,
365 content,
366 cache_control,
367 },
368 ContentBlockHelper::CodeExecutionToolResult {
369 tool_use_id,
370 content,
371 cache_control,
372 } => ContentBlock::CodeExecutionToolResult {
373 tool_use_id,
374 content,
375 cache_control,
376 },
377 ContentBlockHelper::ContainerUpload {
378 file_id,
379 cache_control,
380 } => ContentBlock::ContainerUpload {
381 file_id,
382 cache_control,
383 },
384 ContentBlockHelper::MidConvSystem {
385 content,
386 cache_control,
387 } => ContentBlock::MidConvSystem {
388 content,
389 cache_control,
390 },
391 }),
392 Err(_) => {
393 let block_type = value
395 .get("type")
396 .and_then(|t| t.as_str())
397 .unwrap_or("unknown")
398 .to_string();
399 Ok(ContentBlock::Unknown {
400 block_type,
401 data: value,
402 })
403 }
404 }
405 }
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct TextBlock {
411 #[serde(rename = "type")]
412 pub block_type: String, pub text: String,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
418#[serde(tag = "type", rename_all = "snake_case")]
419pub enum ImageSource {
420 Base64 { media_type: String, data: String },
422 Url { url: String },
424 File { file_id: String },
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize)]
432#[serde(tag = "type", rename_all = "snake_case")]
433pub enum DocumentSource {
434 File { file_id: String },
436 Text { media_type: String, data: String },
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
442pub struct CitationConfig {
443 pub enabled: bool,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct Citation {
451 #[serde(rename = "type")]
452 pub citation_type: String, pub source: String,
455
456 #[serde(skip_serializing_if = "Option::is_none")]
457 pub title: Option<String>,
458
459 pub cited_text: String,
460
461 pub search_result_index: usize,
462
463 pub start_block_index: usize,
464
465 pub end_block_index: usize,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
470pub struct CacheControl {
471 #[serde(rename = "type")]
472 pub cache_type: CacheType,
473
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub ttl: Option<CacheTtl>,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
480#[serde(rename_all = "lowercase")]
481pub enum CacheType {
482 Ephemeral,
483}
484
485#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
487pub enum CacheTtl {
488 #[serde(rename = "5m")]
490 FiveMinutes,
491 #[serde(rename = "1h")]
493 OneHour,
494}
495
496impl CacheControl {
497 pub fn ephemeral() -> Self {
499 Self {
500 cache_type: CacheType::Ephemeral,
501 ttl: None,
502 }
503 }
504
505 pub fn ephemeral_with_ttl(ttl: CacheTtl) -> Self {
507 Self {
508 cache_type: CacheType::Ephemeral,
509 ttl: Some(ttl),
510 }
511 }
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct Message {
517 pub role: Role,
518 pub content: Vec<ContentBlock>,
519}
520
521impl Message {
522 pub fn user(text: impl Into<String>) -> Self {
524 Self {
525 role: Role::User,
526 content: vec![ContentBlock::Text {
527 text: text.into(),
528 cache_control: None,
529 citations: None,
530 }],
531 }
532 }
533
534 pub fn assistant(text: impl Into<String>) -> Self {
536 Self {
537 role: Role::Assistant,
538 content: vec![ContentBlock::Text {
539 text: text.into(),
540 cache_control: None,
541 citations: None,
542 }],
543 }
544 }
545
546 pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
548 Self {
549 role: Role::User,
550 content: vec![ContentBlock::ToolResult {
551 tool_use_id: tool_use_id.into(),
552 content: Some(ToolResultContent::Text(content.into())),
553 is_error: None,
554 }],
555 }
556 }
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize)]
561#[serde(untagged)]
562pub enum SystemPrompt {
563 String(String),
564 Blocks(Vec<SystemBlock>),
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
568pub struct SystemBlock {
569 #[serde(rename = "type")]
570 pub block_type: String,
571 pub text: String,
572 #[serde(skip_serializing_if = "Option::is_none")]
573 pub cache_control: Option<CacheControl>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct CustomTool {
602 pub name: String,
603 pub description: String,
604 pub input_schema: serde_json::Value,
605
606 #[serde(skip_serializing_if = "Option::is_none")]
608 pub disable_user_input: Option<bool>,
609
610 #[serde(skip_serializing_if = "Option::is_none")]
615 pub input_examples: Option<Vec<serde_json::Value>>,
616
617 #[serde(skip_serializing_if = "Option::is_none")]
619 pub cache_control: Option<CacheControl>,
620
621 #[serde(skip_serializing_if = "Option::is_none")]
623 pub defer_loading: Option<bool>,
624
625 #[serde(skip_serializing_if = "Option::is_none")]
627 pub eager_input_streaming: Option<bool>,
628
629 #[serde(skip_serializing_if = "Option::is_none")]
631 pub strict: Option<bool>,
632}
633
634impl CustomTool {
635 pub fn new(
637 name: impl Into<String>,
638 description: impl Into<String>,
639 input_schema: serde_json::Value,
640 ) -> Self {
641 Self {
642 name: name.into(),
643 description: description.into(),
644 input_schema,
645 disable_user_input: None,
646 input_examples: None,
647 cache_control: None,
648 defer_loading: None,
649 eager_input_streaming: None,
650 strict: None,
651 }
652 }
653
654 pub fn programmatic(mut self) -> Self {
656 self.disable_user_input = Some(true);
657 self
658 }
659
660 pub fn with_strict(mut self) -> Self {
662 self.strict = Some(true);
663 self
664 }
665}
666
667#[deprecated(
669 since = "2.0.0",
670 note = "Renamed to CustomTool. Use CustomTool directly."
671)]
672pub type Tool = CustomTool;
673
674#[derive(Debug, Clone, Serialize, Deserialize)]
702#[serde(untagged)]
703pub enum ToolDefinition {
704 Custom(CustomTool),
706 Server(serde_json::Value),
708}
709
710impl From<CustomTool> for ToolDefinition {
711 fn from(tool: CustomTool) -> Self {
712 ToolDefinition::Custom(tool)
713 }
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
718#[serde(tag = "type", rename_all = "lowercase")]
719pub enum ToolChoice {
720 Auto {
722 #[serde(skip_serializing_if = "Option::is_none")]
723 disable_parallel_tool_use: Option<bool>,
724 },
725 Any {
727 #[serde(skip_serializing_if = "Option::is_none")]
728 disable_parallel_tool_use: Option<bool>,
729 },
730 Tool {
732 name: String,
733 #[serde(skip_serializing_if = "Option::is_none")]
734 disable_parallel_tool_use: Option<bool>,
735 },
736 None,
738}
739
740impl ToolChoice {
741 pub fn auto() -> Self {
743 Self::Auto {
744 disable_parallel_tool_use: None,
745 }
746 }
747
748 pub fn any() -> Self {
750 Self::Any {
751 disable_parallel_tool_use: None,
752 }
753 }
754
755 pub fn tool(name: impl Into<String>) -> Self {
757 Self::Tool {
758 name: name.into(),
759 disable_parallel_tool_use: None,
760 }
761 }
762
763 pub fn none() -> Self {
765 Self::None
766 }
767}
768
769#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
771pub struct OutputTokensDetails {
772 #[serde(skip_serializing_if = "Option::is_none")]
774 pub thinking_tokens: Option<u32>,
775}
776
777#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
779pub struct ServerToolUsage {
780 #[serde(skip_serializing_if = "Option::is_none")]
782 pub web_search_requests: Option<u32>,
783 #[serde(skip_serializing_if = "Option::is_none")]
785 pub web_fetch_requests: Option<u32>,
786}
787
788#[derive(Debug, Clone, Serialize, Deserialize)]
790pub struct Usage {
791 pub input_tokens: u32,
792 pub output_tokens: u32,
793
794 #[serde(skip_serializing_if = "Option::is_none")]
796 pub cache_creation_input_tokens: Option<u32>,
797
798 #[serde(skip_serializing_if = "Option::is_none")]
800 pub cache_read_input_tokens: Option<u32>,
801
802 #[serde(skip_serializing_if = "Option::is_none")]
804 pub output_tokens_details: Option<OutputTokensDetails>,
805
806 #[serde(skip_serializing_if = "Option::is_none")]
808 pub server_tool_use: Option<ServerToolUsage>,
809
810 #[serde(skip_serializing_if = "Option::is_none")]
812 pub service_tier: Option<String>,
813
814 #[serde(skip_serializing_if = "Option::is_none")]
816 pub inference_geo: Option<String>,
817}
818
819#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct ExtendedUsage {
822 #[serde(flatten)]
823 pub base: Usage,
824
825 #[serde(skip_serializing_if = "Option::is_none")]
830 pub thinking_tokens: Option<u32>,
831}
832
833#[derive(Debug, Clone, Serialize, Deserialize)]
835pub struct MessagesRequest {
836 pub model: String,
838
839 pub max_tokens: u32,
841
842 pub messages: Vec<Message>,
844
845 #[serde(skip_serializing_if = "Option::is_none")]
847 pub system: Option<SystemPrompt>,
848
849 #[serde(skip_serializing_if = "Option::is_none")]
851 pub tools: Option<Vec<ToolDefinition>>,
852
853 #[serde(skip_serializing_if = "Option::is_none")]
861 pub tool_choice: Option<ToolChoice>,
862
863 #[serde(skip_serializing_if = "Option::is_none")]
865 pub temperature: Option<f32>,
866
867 #[serde(skip_serializing_if = "Option::is_none")]
869 pub top_p: Option<f32>,
870
871 #[serde(skip_serializing_if = "Option::is_none")]
873 pub top_k: Option<u32>,
874
875 #[serde(skip_serializing_if = "Option::is_none")]
877 pub stop_sequences: Option<Vec<String>>,
878
879 #[serde(skip_serializing_if = "Option::is_none")]
881 pub stream: Option<bool>,
882
883 #[serde(skip_serializing_if = "Option::is_none")]
888 pub output_config: Option<OutputConfig>,
889
890 #[serde(skip_serializing_if = "Option::is_none")]
895 pub thinking: Option<ThinkingConfig>,
896
897 #[serde(skip_serializing_if = "Option::is_none")]
899 pub metadata: Option<Metadata>,
900
901 #[serde(skip_serializing_if = "Option::is_none")]
903 pub service_tier: Option<ServiceTier>,
904
905 #[serde(skip_serializing_if = "Option::is_none")]
907 pub inference_geo: Option<String>,
908
909 #[serde(skip_serializing_if = "Option::is_none")]
911 pub container: Option<String>,
912}
913
914#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
916#[serde(tag = "type", rename_all = "lowercase")]
917pub enum ThinkingConfig {
918 Enabled {
920 budget_tokens: u32,
925 #[serde(skip_serializing_if = "Option::is_none")]
926 display: Option<ThinkingDisplay>,
927 },
928 Disabled,
930 Adaptive {
932 #[serde(skip_serializing_if = "Option::is_none")]
933 display: Option<ThinkingDisplay>,
934 },
935}
936
937#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
939#[serde(rename_all = "lowercase")]
940pub enum ThinkingDisplay {
941 Summarized,
943 Omitted,
945}
946
947#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
949pub struct OutputConfig {
950 #[serde(skip_serializing_if = "Option::is_none")]
959 pub effort: Option<EffortLevel>,
960
961 #[serde(skip_serializing_if = "Option::is_none")]
963 pub format: Option<OutputFormat>,
964}
965
966#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
968pub struct OutputFormat {
969 #[serde(rename = "type")]
970 pub format_type: String,
971 pub schema: serde_json::Value,
972}
973
974#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
976#[serde(rename_all = "lowercase")]
977pub enum EffortLevel {
978 High,
980 Medium,
982 Low,
984 #[serde(rename = "xhigh")]
986 XHigh,
987 Max,
989}
990
991#[derive(Debug, Clone, Serialize, Deserialize)]
993pub struct Metadata {
994 #[serde(skip_serializing_if = "Option::is_none")]
996 pub user_id: Option<String>,
997}
998
999#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1001#[serde(rename_all = "snake_case")]
1002pub enum ServiceTier {
1003 Auto,
1004 StandardOnly,
1005}
1006
1007impl MessagesRequest {
1008 pub fn new(model: impl Into<String>, max_tokens: u32, messages: Vec<Message>) -> Self {
1022 Self {
1023 model: model.into(),
1024 max_tokens,
1025 messages,
1026 system: None,
1027 tools: None,
1028 tool_choice: None,
1029 temperature: None,
1030 top_p: None,
1031 top_k: None,
1032 stop_sequences: None,
1033 stream: None,
1034 output_config: None,
1035 thinking: None,
1036 metadata: None,
1037 service_tier: None,
1038 inference_geo: None,
1039 container: None,
1040 }
1041 }
1042
1043 pub fn with_system(mut self, system: impl Into<String>) -> Self {
1060 self.system = Some(SystemPrompt::String(system.into()));
1061 self
1062 }
1063
1064 pub fn with_tools(mut self, tools: Vec<ToolDefinition>) -> Self {
1099 self.tools = Some(tools);
1100 self
1101 }
1102
1103 pub fn with_custom_tools(mut self, tools: Vec<CustomTool>) -> Self {
1123 self.tools = Some(tools.into_iter().map(ToolDefinition::Custom).collect());
1124 self
1125 }
1126
1127 pub fn with_tool_choice(mut self, choice: ToolChoice) -> Self {
1153 self.tool_choice = Some(choice);
1154 self
1155 }
1156
1157 pub fn with_temperature(mut self, temperature: f32) -> Self {
1186 self.temperature = Some(temperature);
1187 self
1188 }
1189
1190 pub fn with_effort(mut self, effort: EffortLevel) -> Self {
1214 let config = self.output_config.get_or_insert(OutputConfig {
1215 effort: None,
1216 format: None,
1217 });
1218 config.effort = Some(effort);
1219 self
1220 }
1221
1222 pub fn with_json_schema(mut self, schema: serde_json::Value) -> Self {
1224 let config = self.output_config.get_or_insert(OutputConfig {
1225 effort: None,
1226 format: None,
1227 });
1228 config.format = Some(OutputFormat {
1229 format_type: "json_schema".into(),
1230 schema,
1231 });
1232 self
1233 }
1234
1235 pub fn with_thinking(mut self, budget_tokens: u32) -> Self {
1259 self.thinking = Some(ThinkingConfig::Enabled {
1260 budget_tokens,
1261 display: None,
1262 });
1263 self
1264 }
1265
1266 pub fn with_adaptive_thinking(mut self) -> Self {
1268 self.thinking = Some(ThinkingConfig::Adaptive { display: None });
1269 self
1270 }
1271
1272 pub fn with_metadata(mut self, metadata: Metadata) -> Self {
1274 self.metadata = Some(metadata);
1275 self
1276 }
1277
1278 pub fn with_service_tier(mut self, tier: ServiceTier) -> Self {
1280 self.service_tier = Some(tier);
1281 self
1282 }
1283
1284 pub fn with_inference_geo(mut self, geo: impl Into<String>) -> Self {
1286 self.inference_geo = Some(geo.into());
1287 self
1288 }
1289
1290 pub fn with_container(mut self, container_id: impl Into<String>) -> Self {
1292 self.container = Some(container_id.into());
1293 self
1294 }
1295}
1296
1297#[derive(Debug, Clone, Serialize, Deserialize)]
1302pub struct TokenCount {
1303 pub input_tokens: u32,
1305 #[serde(skip_serializing_if = "Option::is_none")]
1307 pub cache_creation_input_tokens: Option<u32>,
1308 #[serde(skip_serializing_if = "Option::is_none")]
1310 pub cache_read_input_tokens: Option<u32>,
1311}
1312
1313#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1315#[serde(rename_all = "snake_case")]
1316pub enum StopReason {
1317 EndTurn,
1319 MaxTokens,
1321 StopSequence,
1323 ToolUse,
1325 PauseTurn,
1330 Refusal,
1332}
1333
1334#[derive(Debug, Clone, Serialize, Deserialize)]
1336pub struct StopDetails {
1337 #[serde(rename = "type")]
1338 pub stop_type: String,
1339
1340 #[serde(skip_serializing_if = "Option::is_none")]
1341 pub category: Option<RefusalCategory>,
1342
1343 #[serde(skip_serializing_if = "Option::is_none")]
1344 pub explanation: Option<String>,
1345}
1346
1347#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1349#[serde(rename_all = "snake_case")]
1350pub enum RefusalCategory {
1351 Cyber,
1352 Bio,
1353 ReasoningExtraction,
1354}
1355
1356#[derive(Debug, Clone, Serialize, Deserialize)]
1358pub struct MessagesResponse {
1359 pub id: String,
1360
1361 #[serde(rename = "type")]
1362 pub response_type: String,
1363
1364 pub role: Role,
1365
1366 pub content: Vec<ContentBlock>,
1367
1368 pub model: String,
1369
1370 #[serde(skip_serializing_if = "Option::is_none")]
1371 pub stop_reason: Option<StopReason>,
1372
1373 #[serde(skip_serializing_if = "Option::is_none")]
1374 pub stop_sequence: Option<String>,
1375
1376 #[serde(skip_serializing_if = "Option::is_none")]
1377 pub stop_details: Option<StopDetails>,
1378
1379 pub usage: Usage,
1380
1381 #[serde(skip_serializing_if = "Option::is_none")]
1383 pub container: Option<Container>,
1384
1385 #[serde(skip)]
1387 pub rate_limit_info: Option<RateLimitInfo>,
1388}
1389
1390#[derive(Debug, Clone, Default)]
1395pub struct RateLimitInfo {
1396 pub requests_remaining: Option<u32>,
1398 pub tokens_remaining: Option<u32>,
1400 pub requests_reset: Option<String>,
1402 pub tokens_reset: Option<String>,
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408 use super::*;
1409
1410 #[test]
1411 fn test_cache_control_ephemeral() {
1412 let cache = CacheControl::ephemeral();
1413 assert_eq!(cache.cache_type, CacheType::Ephemeral);
1414 }
1415
1416 #[test]
1417 fn test_cache_control_serialization() {
1418 let cache = CacheControl::ephemeral();
1419 let json = serde_json::to_string(&cache).unwrap();
1420 assert_eq!(json, r#"{"type":"ephemeral"}"#);
1421 }
1422
1423 #[test]
1424 fn test_text_content_with_cache() {
1425 let content = ContentBlock::Text {
1426 text: "test".into(),
1427 cache_control: Some(CacheControl::ephemeral()),
1428 citations: None,
1429 };
1430
1431 let json = serde_json::to_value(&content).unwrap();
1432 assert_eq!(json["type"], "text");
1433 assert_eq!(json["text"], "test");
1434 assert_eq!(json["cache_control"]["type"], "ephemeral");
1435 }
1436
1437 #[test]
1438 fn test_system_block_with_cache() {
1439 let block = SystemBlock {
1440 block_type: "text".into(),
1441 text: "You are helpful".into(),
1442 cache_control: Some(CacheControl::ephemeral()),
1443 };
1444
1445 let json = serde_json::to_value(&block).unwrap();
1446 assert_eq!(json["type"], "text");
1447 assert_eq!(json["cache_control"]["type"], "ephemeral");
1448 }
1449
1450 #[test]
1451 fn test_tool_with_cache() {
1452 let mut tool = CustomTool::new("test", "test tool", serde_json::json!({"type": "object"}))
1453 .programmatic();
1454 tool.cache_control = Some(CacheControl::ephemeral());
1455
1456 let json = serde_json::to_value(&tool).unwrap();
1457 assert_eq!(json["name"], "test");
1458 assert_eq!(json["cache_control"]["type"], "ephemeral");
1459 }
1460
1461 #[test]
1462 fn test_custom_tool_with_new_fields() {
1463 let mut tool =
1464 CustomTool::new("test", "test", serde_json::json!({"type": "object"})).with_strict();
1465 tool.defer_loading = Some(true);
1466 tool.eager_input_streaming = Some(true);
1467 let json = serde_json::to_value(&tool).unwrap();
1468 assert_eq!(json["defer_loading"], true);
1469 assert_eq!(json["eager_input_streaming"], true);
1470 assert_eq!(json["strict"], true);
1471 }
1472
1473 #[test]
1474 fn test_message_constructors() {
1475 let user_msg = Message::user("hello");
1476 assert_eq!(user_msg.role, Role::User);
1477 assert_eq!(user_msg.content.len(), 1);
1478
1479 let assistant_msg = Message::assistant("hi");
1480 assert_eq!(assistant_msg.role, Role::Assistant);
1481
1482 let tool_result = Message::tool_result("id_123", "result");
1483 assert_eq!(tool_result.role, Role::User);
1484 match &tool_result.content[0] {
1485 ContentBlock::ToolResult { tool_use_id, .. } => {
1486 assert_eq!(tool_use_id, "id_123");
1487 }
1488 _ => panic!("Expected ToolResult"),
1489 }
1490 }
1491
1492 #[test]
1493 fn test_messages_request_builder() {
1494 let request = MessagesRequest::new(
1495 "claude-sonnet-4-5-20250929",
1496 1024,
1497 vec![Message::user("test")],
1498 )
1499 .with_system("System prompt")
1500 .with_temperature(0.7);
1501
1502 assert_eq!(request.model, "claude-sonnet-4-5-20250929");
1503 assert_eq!(request.max_tokens, 1024);
1504 assert_eq!(request.messages.len(), 1);
1505 assert!(request.system.is_some());
1506 assert_eq!(request.temperature, Some(0.7));
1507 }
1508
1509 #[test]
1510 fn test_metadata_serialization() {
1511 let request = MessagesRequest::new(
1512 "claude-sonnet-4-5-20250929",
1513 1024,
1514 vec![Message::user("Hello")],
1515 )
1516 .with_metadata(Metadata {
1517 user_id: Some("user-abc-123".into()),
1518 });
1519
1520 let json = serde_json::to_value(&request).unwrap();
1521 assert_eq!(json["metadata"]["user_id"], "user-abc-123");
1522 }
1523
1524 #[test]
1525 fn test_service_tier_serialization() {
1526 let json = serde_json::to_string(&ServiceTier::StandardOnly).unwrap();
1527 assert_eq!(json, r#""standard_only""#);
1528
1529 let json = serde_json::to_string(&ServiceTier::Auto).unwrap();
1530 assert_eq!(json, r#""auto""#);
1531 }
1532
1533 #[test]
1534 fn test_refusal_stop_reason_deserialization() {
1535 let json = r#"{
1536 "id": "msg_123",
1537 "type": "message",
1538 "role": "assistant",
1539 "content": [],
1540 "model": "claude-sonnet-4-5-20250929",
1541 "stop_reason": "refusal",
1542 "stop_details": {
1543 "type": "refusal",
1544 "category": "cyber",
1545 "explanation": "Request involves prohibited content"
1546 },
1547 "usage": { "input_tokens": 10, "output_tokens": 0 }
1548 }"#;
1549
1550 let response: MessagesResponse = serde_json::from_str(json).unwrap();
1551 assert_eq!(response.stop_reason, Some(StopReason::Refusal));
1552 let details = response.stop_details.as_ref().unwrap();
1553 assert_eq!(details.category, Some(RefusalCategory::Cyber));
1554 assert_eq!(
1555 details.explanation.as_deref(),
1556 Some("Request involves prohibited content")
1557 );
1558 }
1559
1560 #[test]
1561 fn test_effort_xhigh_and_max() {
1562 let json_xhigh = serde_json::to_string(&EffortLevel::XHigh).unwrap();
1563 assert_eq!(json_xhigh, r#""xhigh""#);
1564
1565 let json_max = serde_json::to_string(&EffortLevel::Max).unwrap();
1566 assert_eq!(json_max, r#""max""#);
1567
1568 let parsed: EffortLevel = serde_json::from_str(r#""xhigh""#).unwrap();
1570 assert_eq!(parsed, EffortLevel::XHigh);
1571
1572 let parsed: EffortLevel = serde_json::from_str(r#""max""#).unwrap();
1573 assert_eq!(parsed, EffortLevel::Max);
1574 }
1575
1576 #[test]
1579 fn test_cache_control_with_ttl() {
1580 let cache = CacheControl::ephemeral_with_ttl(CacheTtl::OneHour);
1581 let json = serde_json::to_value(&cache).unwrap();
1582 assert_eq!(json["type"], "ephemeral");
1583 assert_eq!(json["ttl"], "1h");
1584 }
1585
1586 #[test]
1587 fn test_cache_control_ttl_deserialization() {
1588 let json = r#"{"type": "ephemeral", "ttl": "5m"}"#;
1589 let cache: CacheControl = serde_json::from_str(json).unwrap();
1590 assert_eq!(cache.ttl, Some(CacheTtl::FiveMinutes));
1591 }
1592
1593 #[test]
1594 fn test_cache_control_without_ttl_still_works() {
1595 let cache = CacheControl::ephemeral();
1597 let json = serde_json::to_string(&cache).unwrap();
1598 assert_eq!(json, r#"{"type":"ephemeral"}"#);
1599 assert_eq!(cache.ttl, None);
1600 }
1601
1602 #[test]
1605 fn test_usage_with_details_deserialization() {
1606 let json = r#"{
1607 "input_tokens": 100,
1608 "output_tokens": 50,
1609 "cache_creation_input_tokens": 10,
1610 "cache_read_input_tokens": 5,
1611 "output_tokens_details": {
1612 "thinking_tokens": 20
1613 },
1614 "server_tool_use": {
1615 "web_search_requests": 3
1616 },
1617 "service_tier": "priority",
1618 "inference_geo": "us"
1619 }"#;
1620
1621 let usage: Usage = serde_json::from_str(json).unwrap();
1622 assert_eq!(usage.input_tokens, 100);
1623 assert_eq!(usage.output_tokens, 50);
1624 let details = usage.output_tokens_details.unwrap();
1625 assert_eq!(details.thinking_tokens, Some(20));
1626 let server = usage.server_tool_use.unwrap();
1627 assert_eq!(server.web_search_requests, Some(3));
1628 assert_eq!(usage.service_tier.as_deref(), Some("priority"));
1629 }
1630
1631 #[test]
1632 fn test_usage_without_new_fields_still_works() {
1633 let json = r#"{"input_tokens": 10, "output_tokens": 5}"#;
1634 let usage: Usage = serde_json::from_str(json).unwrap();
1635 assert_eq!(usage.input_tokens, 10);
1636 assert!(usage.output_tokens_details.is_none());
1637 assert!(usage.server_tool_use.is_none());
1638 }
1639
1640 #[test]
1643 fn test_token_count_deserialization() {
1644 let json = r#"{
1645 "input_tokens": 1234,
1646 "cache_creation_input_tokens": 100,
1647 "cache_read_input_tokens": 50
1648 }"#;
1649 let count: TokenCount = serde_json::from_str(json).unwrap();
1650 assert_eq!(count.input_tokens, 1234);
1651 assert_eq!(count.cache_creation_input_tokens, Some(100));
1652 assert_eq!(count.cache_read_input_tokens, Some(50));
1653 }
1654
1655 #[test]
1656 fn test_token_count_minimal() {
1657 let json = r#"{"input_tokens": 42}"#;
1658 let count: TokenCount = serde_json::from_str(json).unwrap();
1659 assert_eq!(count.input_tokens, 42);
1660 assert!(count.cache_creation_input_tokens.is_none());
1661 }
1662
1663 #[test]
1666 fn test_rate_limit_info_default() {
1667 let info = RateLimitInfo::default();
1668 assert!(info.requests_remaining.is_none());
1669 assert!(info.tokens_remaining.is_none());
1670 assert!(info.requests_reset.is_none());
1671 assert!(info.tokens_reset.is_none());
1672 }
1673
1674 #[test]
1675 fn test_response_deserialization_with_skipped_rate_limit() {
1676 let json = r#"{
1677 "id": "msg_123",
1678 "type": "message",
1679 "role": "assistant",
1680 "content": [{"type": "text", "text": "hello"}],
1681 "model": "claude-sonnet-4-5-20250929",
1682 "stop_reason": "end_turn",
1683 "usage": {"input_tokens": 10, "output_tokens": 5}
1684 }"#;
1685
1686 let response: MessagesResponse = serde_json::from_str(json).unwrap();
1687 assert!(response.rate_limit_info.is_none()); }
1689
1690 #[test]
1693 fn test_unknown_content_block_deserializes() {
1694 let json = r#"{"type": "some_future_block", "id": "fb_123", "data": "test"}"#;
1695 let block: ContentBlock = serde_json::from_str(json).unwrap();
1696 match block {
1697 ContentBlock::Unknown { block_type, data } => {
1698 assert_eq!(block_type, "some_future_block");
1699 assert_eq!(data["id"], "fb_123");
1700 }
1701 _ => panic!("Expected Unknown variant"),
1702 }
1703 }
1704
1705 #[test]
1706 fn test_known_content_blocks_still_work() {
1707 let json = r#"{"type": "text", "text": "hello"}"#;
1708 let block: ContentBlock = serde_json::from_str(json).unwrap();
1709 match block {
1710 ContentBlock::Text { text, .. } => assert_eq!(text, "hello"),
1711 _ => panic!("Expected Text variant"),
1712 }
1713 }
1714
1715 #[test]
1716 fn test_content_block_roundtrip() {
1717 let original = ContentBlock::Text {
1718 text: "test".into(),
1719 cache_control: None,
1720 citations: None,
1721 };
1722 let json = serde_json::to_string(&original).unwrap();
1723 let deserialized: ContentBlock = serde_json::from_str(&json).unwrap();
1724 match deserialized {
1725 ContentBlock::Text { text, .. } => assert_eq!(text, "test"),
1726 _ => panic!("Expected Text variant after roundtrip"),
1727 }
1728 }
1729
1730 #[test]
1731 fn test_unknown_block_in_response() {
1732 let json = r#"{
1733 "id": "msg_123",
1734 "type": "message",
1735 "role": "assistant",
1736 "content": [
1737 {"type": "text", "text": "hello"},
1738 {"type": "some_future_block", "id": "fb_1", "data": "test"}
1739 ],
1740 "model": "claude-sonnet-4-5-20250929",
1741 "stop_reason": "end_turn",
1742 "usage": {"input_tokens": 10, "output_tokens": 5}
1743 }"#;
1744
1745 let response: MessagesResponse = serde_json::from_str(json).unwrap();
1746 assert_eq!(response.content.len(), 2);
1747 match &response.content[0] {
1748 ContentBlock::Text { text, .. } => assert_eq!(text, "hello"),
1749 _ => panic!("Expected Text variant"),
1750 }
1751 match &response.content[1] {
1752 ContentBlock::Unknown { block_type, .. } => {
1753 assert_eq!(block_type, "some_future_block");
1754 }
1755 _ => panic!("Expected Unknown variant"),
1756 }
1757 }
1758
1759 #[test]
1760 fn test_unknown_block_without_type_field() {
1761 let json = r#"{"foo": "bar"}"#;
1764 let block: ContentBlock = serde_json::from_str(json).unwrap();
1765 match block {
1766 ContentBlock::Unknown { block_type, data } => {
1767 assert_eq!(block_type, "unknown");
1768 assert_eq!(data["foo"], "bar");
1769 }
1770 _ => panic!("Expected Unknown variant"),
1771 }
1772 }
1773
1774 #[test]
1777 fn test_server_tool_use_deserialization() {
1778 let json = r#"{"type": "server_tool_use", "id": "stu_123", "name": "web_search", "input": {"query": "rust"}}"#;
1779 let block: ContentBlock = serde_json::from_str(json).unwrap();
1780 match block {
1781 ContentBlock::ServerToolUse {
1782 id, name, input, ..
1783 } => {
1784 assert_eq!(id, "stu_123");
1785 assert_eq!(name, "web_search");
1786 assert_eq!(input["query"], "rust");
1787 }
1788 _ => panic!("Expected ServerToolUse variant"),
1789 }
1790 }
1791
1792 #[test]
1793 fn test_web_search_tool_result_deserialization() {
1794 let json = r#"{"type": "web_search_tool_result", "tool_use_id": "stu_123", "content": [{"type": "web_page", "url": "https://example.com"}]}"#;
1795 let block: ContentBlock = serde_json::from_str(json).unwrap();
1796 match block {
1797 ContentBlock::WebSearchToolResult {
1798 tool_use_id,
1799 content,
1800 ..
1801 } => {
1802 assert_eq!(tool_use_id, "stu_123");
1803 assert!(content.is_array());
1804 }
1805 _ => panic!("Expected WebSearchToolResult variant"),
1806 }
1807 }
1808
1809 #[test]
1810 fn test_code_execution_tool_result_deserialization() {
1811 let json = r#"{"type": "code_execution_tool_result", "tool_use_id": "ce_123", "content": {"stdout": "hello"}}"#;
1812 let block: ContentBlock = serde_json::from_str(json).unwrap();
1813 match block {
1814 ContentBlock::CodeExecutionToolResult {
1815 tool_use_id,
1816 content,
1817 ..
1818 } => {
1819 assert_eq!(tool_use_id, "ce_123");
1820 assert_eq!(content["stdout"], "hello");
1821 }
1822 _ => panic!("Expected CodeExecutionToolResult variant"),
1823 }
1824 }
1825
1826 #[test]
1829 fn test_structured_output_serialization() {
1830 let schema = serde_json::json!({
1831 "type": "object",
1832 "properties": {
1833 "name": {"type": "string"}
1834 }
1835 });
1836 let request = MessagesRequest::new(
1837 "claude-sonnet-4-5-20250929",
1838 1024,
1839 vec![Message::user("test")],
1840 )
1841 .with_json_schema(schema.clone());
1842
1843 let json = serde_json::to_value(&request).unwrap();
1844 assert_eq!(json["output_config"]["format"]["type"], "json_schema");
1845 assert_eq!(json["output_config"]["format"]["schema"], schema);
1846 }
1847
1848 #[test]
1849 fn test_effort_and_schema_coexist() {
1850 let request = MessagesRequest::new(
1851 "claude-sonnet-4-5-20250929",
1852 1024,
1853 vec![Message::user("test")],
1854 )
1855 .with_effort(EffortLevel::Low)
1856 .with_json_schema(serde_json::json!({"type": "object"}));
1857
1858 let config = request.output_config.as_ref().unwrap();
1859 assert_eq!(config.effort, Some(EffortLevel::Low));
1860 assert!(config.format.is_some());
1861 }
1862
1863 #[test]
1864 fn test_adaptive_thinking_serialization() {
1865 let request = MessagesRequest::new(
1866 "claude-sonnet-4-5-20250929",
1867 1024,
1868 vec![Message::user("test")],
1869 )
1870 .with_adaptive_thinking();
1871
1872 let json = serde_json::to_value(&request).unwrap();
1873 assert_eq!(json["thinking"]["type"], "adaptive");
1874 }
1875
1876 #[test]
1877 fn test_thinking_config_enabled_with_display() {
1878 let config = ThinkingConfig::Enabled {
1879 budget_tokens: 4096,
1880 display: Some(ThinkingDisplay::Summarized),
1881 };
1882 let json = serde_json::to_value(&config).unwrap();
1883 assert_eq!(json["type"], "enabled");
1884 assert_eq!(json["budget_tokens"], 4096);
1885 assert_eq!(json["display"], "summarized");
1886 }
1887
1888 #[test]
1891 fn test_tool_choice_auto_serialization() {
1892 let choice = ToolChoice::auto();
1893 let json = serde_json::to_value(&choice).unwrap();
1894 assert_eq!(json["type"], "auto");
1895 assert!(json.get("disable_parallel_tool_use").is_none());
1897 }
1898
1899 #[test]
1900 fn test_tool_choice_with_disable_parallel() {
1901 let choice = ToolChoice::Auto {
1902 disable_parallel_tool_use: Some(true),
1903 };
1904 let json = serde_json::to_value(&choice).unwrap();
1905 assert_eq!(json["type"], "auto");
1906 assert_eq!(json["disable_parallel_tool_use"], true);
1907 }
1908
1909 #[test]
1910 fn test_tool_choice_tool_serialization() {
1911 let choice = ToolChoice::tool("my_tool");
1912 let json = serde_json::to_value(&choice).unwrap();
1913 assert_eq!(json["type"], "tool");
1914 assert_eq!(json["name"], "my_tool");
1915 assert!(json.get("disable_parallel_tool_use").is_none());
1916 }
1917
1918 #[test]
1919 fn test_tool_result_content_text_serialization() {
1920 let content = ToolResultContent::Text("hello".into());
1921 let json = serde_json::to_value(&content).unwrap();
1922 assert_eq!(json, serde_json::json!("hello"));
1923 }
1924
1925 #[test]
1926 fn test_tool_result_content_blocks_serialization() {
1927 let content = ToolResultContent::Blocks(vec![ContentBlock::Text {
1928 text: "result".into(),
1929 cache_control: None,
1930 citations: None,
1931 }]);
1932 let json = serde_json::to_value(&content).unwrap();
1933 assert!(json.is_array());
1934 assert_eq!(json[0]["type"], "text");
1935 assert_eq!(json[0]["text"], "result");
1936 }
1937
1938 #[test]
1939 fn test_tool_use_with_caller() {
1940 let block = ContentBlock::ToolUse {
1941 id: "tu_123".into(),
1942 name: "my_tool".into(),
1943 input: serde_json::json!({}),
1944 caller: Some("code_execution_20250825".into()),
1945 cache_control: None,
1946 };
1947 let json = serde_json::to_value(&block).unwrap();
1948 assert_eq!(json["caller"], "code_execution_20250825");
1949 }
1950
1951 #[test]
1952 fn test_tool_use_without_caller() {
1953 let block = ContentBlock::ToolUse {
1954 id: "tu_123".into(),
1955 name: "my_tool".into(),
1956 input: serde_json::json!({}),
1957 caller: None,
1958 cache_control: None,
1959 };
1960 let json = serde_json::to_value(&block).unwrap();
1961 assert!(json.get("caller").is_none());
1962 }
1963
1964 #[test]
1965 fn test_container_request_serialization() {
1966 let request = MessagesRequest::new(
1967 "claude-sonnet-4-5-20250929",
1968 1024,
1969 vec![Message::user("Hello")],
1970 )
1971 .with_container("container_123");
1972 let json = serde_json::to_value(&request).unwrap();
1973 assert_eq!(json["container"], "container_123");
1974 }
1975
1976 #[test]
1977 fn test_container_response_deserialization() {
1978 let json = r#"{
1979 "id": "msg_1",
1980 "type": "message",
1981 "role": "assistant",
1982 "content": [],
1983 "model": "claude-sonnet-4-5-20250929",
1984 "stop_reason": "end_turn",
1985 "usage": {"input_tokens": 10, "output_tokens": 5},
1986 "container": {"id": "ctr_abc", "expires_at": "2026-01-01T00:00:00Z"}
1987 }"#;
1988 let response: MessagesResponse = serde_json::from_str(json).unwrap();
1989 let container = response.container.unwrap();
1990 assert_eq!(container.id, "ctr_abc");
1991 }
1992
1993 #[test]
1994 fn test_container_upload_deserialization() {
1995 let json = r#"{"type": "container_upload", "file_id": "file_123"}"#;
1996 let block: ContentBlock = serde_json::from_str(json).unwrap();
1997 match block {
1998 ContentBlock::ContainerUpload { file_id, .. } => {
1999 assert_eq!(file_id, "file_123");
2000 }
2001 _ => panic!("Expected ContainerUpload"),
2002 }
2003 }
2004
2005 #[test]
2006 fn test_mid_conv_system_deserialization() {
2007 let json = r#"{
2008 "type": "mid_conv_system",
2009 "content": [{"type": "text", "text": "New instruction"}]
2010 }"#;
2011 let block: ContentBlock = serde_json::from_str(json).unwrap();
2012 match block {
2013 ContentBlock::MidConvSystem { content, .. } => {
2014 assert_eq!(content[0].text, "New instruction");
2015 }
2016 _ => panic!("Expected MidConvSystem"),
2017 }
2018 }
2019
2020 #[test]
2021 fn test_server_tool_use_in_response() {
2022 let json = r#"{
2023 "id": "msg_123",
2024 "type": "message",
2025 "role": "assistant",
2026 "content": [
2027 {"type": "text", "text": "searching..."},
2028 {"type": "server_tool_use", "id": "stu_1", "name": "web_search", "input": {"query": "rust"}}
2029 ],
2030 "model": "claude-sonnet-4-5-20250929",
2031 "stop_reason": "end_turn",
2032 "usage": {"input_tokens": 10, "output_tokens": 5}
2033 }"#;
2034
2035 let response: MessagesResponse = serde_json::from_str(json).unwrap();
2036 assert_eq!(response.content.len(), 2);
2037 match &response.content[1] {
2038 ContentBlock::ServerToolUse { id, name, .. } => {
2039 assert_eq!(id, "stu_1");
2040 assert_eq!(name, "web_search");
2041 }
2042 _ => panic!("Expected ServerToolUse variant"),
2043 }
2044 }
2045}