1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
11pub struct TextPart {
12 pub content: String,
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub id: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub provider_details: Option<serde_json::Map<String, serde_json::Value>>,
20}
21
22impl TextPart {
23 pub const PART_KIND: &'static str = "text";
25
26 #[must_use]
28 pub fn new(content: impl Into<String>) -> Self {
29 Self {
30 content: content.into(),
31 id: None,
32 provider_details: None,
33 }
34 }
35
36 #[must_use]
38 pub fn with_id(mut self, id: impl Into<String>) -> Self {
39 self.id = Some(id.into());
40 self
41 }
42
43 #[must_use]
45 pub fn with_provider_details(
46 mut self,
47 details: serde_json::Map<String, serde_json::Value>,
48 ) -> Self {
49 self.provider_details = Some(details);
50 self
51 }
52
53 #[must_use]
55 pub fn part_kind(&self) -> &'static str {
56 Self::PART_KIND
57 }
58
59 #[must_use]
61 pub fn is_empty(&self) -> bool {
62 self.content.is_empty()
63 }
64
65 #[must_use]
67 pub fn len(&self) -> usize {
68 self.content.len()
69 }
70}
71
72impl From<String> for TextPart {
76 fn from(s: String) -> Self {
77 Self::new(s)
78 }
79}
80
81impl From<&str> for TextPart {
82 fn from(s: &str) -> Self {
83 Self::new(s)
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89#[serde(untagged)]
90pub enum ToolCallArgs {
91 Json(serde_json::Value),
93 String(String),
95}
96
97fn repair_json(s: &str) -> Option<serde_json::Value> {
108 let s = s.trim();
109
110 if let Ok(v) = serde_json::from_str::<serde_json::Value>(s) {
112 return Some(v);
113 }
114
115 let mut repaired = s.to_string();
116
117 repaired = remove_trailing_commas(&repaired);
119
120 repaired = quote_unquoted_keys(&repaired);
122
123 if repaired.contains('\'') && !repaired.contains('"') {
125 repaired = repaired.replace('\'', "\"");
126 }
127
128 let open_braces = repaired.matches('{').count();
130 let close_braces = repaired.matches('}').count();
131 if open_braces > close_braces {
132 repaired.push_str(&"}".repeat(open_braces - close_braces));
133 }
134
135 let open_brackets = repaired.matches('[').count();
136 let close_brackets = repaired.matches(']').count();
137 if open_brackets > close_brackets {
138 repaired.push_str(&"]".repeat(open_brackets - close_brackets));
139 }
140
141 serde_json::from_str(&repaired).ok()
143}
144
145fn remove_trailing_commas(s: &str) -> String {
148 let mut result = String::with_capacity(s.len());
149 let chars: Vec<char> = s.chars().collect();
150 let len = chars.len();
151
152 let mut i = 0;
153 while i < len {
154 let c = chars[i];
155 if c == ',' {
156 let mut j = i + 1;
158 while j < len && chars[j].is_whitespace() {
159 j += 1;
160 }
161 if j < len && (chars[j] == '}' || chars[j] == ']') {
162 i += 1;
164 continue;
165 }
166 }
167 result.push(c);
168 i += 1;
169 }
170 result
171}
172
173fn quote_unquoted_keys(s: &str) -> String {
176 let mut result = String::with_capacity(s.len() + 32);
177 let chars: Vec<char> = s.chars().collect();
178 let len = chars.len();
179
180 let mut i = 0;
181 while i < len {
182 let c = chars[i];
183
184 if c == '{' || c == ',' {
186 result.push(c);
187 i += 1;
188
189 while i < len && chars[i].is_whitespace() {
191 result.push(chars[i]);
192 i += 1;
193 }
194
195 if i < len && is_ident_start(chars[i]) {
197 let key_start = i;
198 while i < len && is_ident_char(chars[i]) {
199 i += 1;
200 }
201 let key = &s[key_start..i];
202
203 while i < len && chars[i].is_whitespace() {
205 i += 1;
206 }
207
208 if i < len && chars[i] == ':' {
210 result.push('"');
211 result.push_str(key);
212 result.push('"');
213 } else {
214 result.push_str(key);
216 }
217 }
218 } else {
219 result.push(c);
220 i += 1;
221 }
222 }
223 result
224}
225
226#[inline]
228fn is_ident_start(c: char) -> bool {
229 c.is_ascii_alphabetic() || c == '_'
230}
231
232#[inline]
234fn is_ident_char(c: char) -> bool {
235 c.is_ascii_alphanumeric() || c == '_'
236}
237
238impl ToolCallArgs {
239 #[must_use]
241 pub fn json(value: serde_json::Value) -> Self {
242 Self::Json(value)
243 }
244
245 #[must_use]
247 pub fn string(s: impl Into<String>) -> Self {
248 Self::String(s.into())
249 }
250
251 #[must_use]
253 pub fn as_object(&self) -> Option<serde_json::Map<String, serde_json::Value>> {
254 match self {
255 Self::Json(serde_json::Value::Object(obj)) => Some(obj.clone()),
256 Self::String(s) => {
257 serde_json::from_str::<serde_json::Value>(s)
258 .ok()
259 .and_then(|value| match value {
260 serde_json::Value::Object(map) => Some(map),
261 _ => None,
262 })
263 }
264 _ => None,
265 }
266 }
267
268 #[must_use]
280 pub fn to_json(&self) -> serde_json::Value {
281 match self {
282 Self::Json(v) => {
283 if v.is_object() {
284 v.clone()
285 } else {
286 serde_json::json!({ "_value": v })
288 }
289 }
290 Self::String(s) => {
291 if let Ok(v) = serde_json::from_str::<serde_json::Value>(s) {
293 if v.is_object() {
294 return v;
295 }
296 return serde_json::json!({ "_value": v });
297 }
298
299 if let Some(v) = repair_json(s) {
301 if v.is_object() {
302 return v;
303 }
304 return serde_json::json!({ "_value": v });
305 }
306
307 serde_json::json!({
309 "_raw": s,
310 "_error": "parse_failed"
311 })
312 }
313 }
314 }
315
316 #[must_use]
321 pub fn to_json_object(&self) -> serde_json::Map<String, serde_json::Value> {
322 match self.to_json() {
323 serde_json::Value::Object(map) => map,
324 _ => unreachable!("to_json() guarantees an object"),
326 }
327 }
328
329 pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
335 match self {
336 Self::Json(v) => serde_json::to_string(v),
337 Self::String(s) => Ok(s.clone()),
338 }
339 }
340
341 #[must_use]
343 pub fn is_valid_json(&self) -> bool {
344 match self {
345 Self::Json(_) => true,
346 Self::String(s) => serde_json::from_str::<serde_json::Value>(s).is_ok(),
347 }
348 }
349}
350
351impl Default for ToolCallArgs {
352 fn default() -> Self {
353 Self::Json(serde_json::Value::Object(serde_json::Map::new()))
354 }
355}
356
357impl From<serde_json::Value> for ToolCallArgs {
358 fn from(v: serde_json::Value) -> Self {
359 Self::Json(v)
360 }
361}
362
363impl From<String> for ToolCallArgs {
364 fn from(s: String) -> Self {
365 match serde_json::from_str(&s) {
367 Ok(v) => Self::Json(v),
368 Err(_) => Self::String(s),
369 }
370 }
371}
372
373impl From<&str> for ToolCallArgs {
374 fn from(s: &str) -> Self {
375 Self::from(s.to_string())
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
381pub struct ToolCallPart {
382 pub tool_name: String,
384 pub args: ToolCallArgs,
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub tool_call_id: Option<String>,
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub id: Option<String>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub provider_details: Option<serde_json::Map<String, serde_json::Value>>,
395}
396
397impl ToolCallPart {
398 pub const PART_KIND: &'static str = "tool-call";
400
401 #[must_use]
403 pub fn new(tool_name: impl Into<String>, args: impl Into<ToolCallArgs>) -> Self {
404 Self {
405 tool_name: tool_name.into(),
406 args: args.into(),
407 tool_call_id: None,
408 id: None,
409 provider_details: None,
410 }
411 }
412
413 #[must_use]
415 pub fn part_kind(&self) -> &'static str {
416 Self::PART_KIND
417 }
418
419 #[must_use]
421 pub fn with_tool_call_id(mut self, id: impl Into<String>) -> Self {
422 self.tool_call_id = Some(id.into());
423 self
424 }
425
426 #[must_use]
428 #[deprecated(since = "0.2.0", note = "Use with_tool_call_id() instead for clarity")]
429 pub fn with_id(mut self, id: impl Into<String>) -> Self {
430 self.tool_call_id = Some(id.into());
431 self
432 }
433
434 #[must_use]
436 pub fn with_part_id(mut self, id: impl Into<String>) -> Self {
437 self.id = Some(id.into());
438 self
439 }
440
441 #[must_use]
443 pub fn with_provider_details(
444 mut self,
445 details: serde_json::Map<String, serde_json::Value>,
446 ) -> Self {
447 self.provider_details = Some(details);
448 self
449 }
450
451 #[must_use]
453 pub fn args_as_dict(&self) -> serde_json::Value {
454 self.args.to_json()
455 }
456
457 pub fn args_as_json_str(&self) -> Result<String, serde_json::Error> {
463 self.args.to_json_string()
464 }
465
466 pub fn parse_args<T: for<'de> Deserialize<'de>>(&self) -> Result<T, serde_json::Error> {
468 let json = self.args.to_json();
469 serde_json::from_value(json)
470 }
471}
472
473#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
478pub struct ThinkingPart {
479 pub content: String,
481 #[serde(skip_serializing_if = "Option::is_none")]
483 pub id: Option<String>,
484 #[serde(skip_serializing_if = "Option::is_none")]
486 pub signature: Option<String>,
487 #[serde(skip_serializing_if = "Option::is_none")]
489 pub provider_name: Option<String>,
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub provider_details: Option<serde_json::Map<String, serde_json::Value>>,
493}
494
495impl ThinkingPart {
496 pub const PART_KIND: &'static str = "thinking";
498
499 pub const REDACTED_THINKING_ID: &'static str = "redacted_thinking";
501
502 pub const REDACTED_CONTENT_ID: &'static str = "redacted_content";
504
505 #[must_use]
507 pub fn new(content: impl Into<String>) -> Self {
508 Self {
509 content: content.into(),
510 id: None,
511 signature: None,
512 provider_name: None,
513 provider_details: None,
514 }
515 }
516
517 #[must_use]
526 pub fn redacted(signature: impl Into<String>, provider_name: impl Into<String>) -> Self {
527 let provider = provider_name.into();
528 let id = if provider.contains("bedrock") {
529 Self::REDACTED_CONTENT_ID
530 } else {
531 Self::REDACTED_THINKING_ID
532 };
533 Self {
534 content: String::new(),
535 id: Some(id.to_string()),
536 signature: Some(signature.into()),
537 provider_name: Some(provider),
538 provider_details: None,
539 }
540 }
541
542 #[must_use]
544 pub fn redacted_with_id(
545 id: impl Into<String>,
546 signature: impl Into<String>,
547 provider_name: impl Into<String>,
548 ) -> Self {
549 Self {
550 content: String::new(),
551 id: Some(id.into()),
552 signature: Some(signature.into()),
553 provider_name: Some(provider_name.into()),
554 provider_details: None,
555 }
556 }
557
558 #[must_use]
560 pub fn part_kind(&self) -> &'static str {
561 Self::PART_KIND
562 }
563
564 #[must_use]
566 pub fn with_id(mut self, id: impl Into<String>) -> Self {
567 self.id = Some(id.into());
568 self
569 }
570
571 #[must_use]
573 pub fn with_signature(mut self, signature: impl Into<String>) -> Self {
574 self.signature = Some(signature.into());
575 self
576 }
577
578 #[must_use]
580 pub fn with_provider_name(mut self, name: impl Into<String>) -> Self {
581 self.provider_name = Some(name.into());
582 self
583 }
584
585 #[must_use]
587 pub fn with_provider_details(
588 mut self,
589 details: serde_json::Map<String, serde_json::Value>,
590 ) -> Self {
591 self.provider_details = Some(details);
592 self
593 }
594
595 #[must_use]
597 pub fn is_empty(&self) -> bool {
598 self.content.is_empty()
599 }
600
601 #[must_use]
607 pub fn is_redacted(&self) -> bool {
608 self.id
609 .as_ref()
610 .map(|id| id.starts_with("redacted"))
611 .unwrap_or(false)
612 }
613
614 #[must_use]
616 pub fn redacted_signature(&self) -> Option<&str> {
617 if self.is_redacted() {
618 self.signature.as_deref()
619 } else {
620 None
621 }
622 }
623}
624
625#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
630pub struct BinaryContent {
631 #[serde(with = "base64_serde")]
633 pub data: Vec<u8>,
634 pub media_type: String,
636}
637
638impl BinaryContent {
639 #[must_use]
641 pub fn new(data: Vec<u8>, media_type: impl Into<String>) -> Self {
642 Self {
643 data,
644 media_type: media_type.into(),
645 }
646 }
647
648 #[must_use]
650 pub fn is_empty(&self) -> bool {
651 self.data.is_empty()
652 }
653
654 #[must_use]
656 pub fn len(&self) -> usize {
657 self.data.len()
658 }
659}
660
661mod base64_serde {
663 use base64::{engine::general_purpose::STANDARD, Engine};
664 use serde::{Deserialize, Deserializer, Serializer};
665
666 pub fn serialize<S>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error>
667 where
668 S: Serializer,
669 {
670 serializer.serialize_str(&STANDARD.encode(data))
671 }
672
673 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
674 where
675 D: Deserializer<'de>,
676 {
677 let s = String::deserialize(deserializer)?;
678 STANDARD.decode(&s).map_err(serde::de::Error::custom)
679 }
680}
681
682#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
688pub struct FilePart {
689 pub content: BinaryContent,
691 #[serde(skip_serializing_if = "Option::is_none")]
693 pub id: Option<String>,
694 #[serde(skip_serializing_if = "Option::is_none")]
696 pub provider_name: Option<String>,
697 #[serde(skip_serializing_if = "Option::is_none")]
699 pub provider_details: Option<serde_json::Map<String, serde_json::Value>>,
700}
701
702impl FilePart {
703 pub const PART_KIND: &'static str = "file";
705
706 #[must_use]
708 pub fn new(content: BinaryContent) -> Self {
709 Self {
710 content,
711 id: None,
712 provider_name: None,
713 provider_details: None,
714 }
715 }
716
717 #[must_use]
719 pub fn from_bytes(data: Vec<u8>, media_type: impl Into<String>) -> Self {
720 Self::new(BinaryContent::new(data, media_type))
721 }
722
723 #[must_use]
725 pub fn part_kind(&self) -> &'static str {
726 Self::PART_KIND
727 }
728
729 #[must_use]
731 pub fn with_id(mut self, id: impl Into<String>) -> Self {
732 self.id = Some(id.into());
733 self
734 }
735
736 #[must_use]
738 pub fn with_provider_name(mut self, name: impl Into<String>) -> Self {
739 self.provider_name = Some(name.into());
740 self
741 }
742
743 #[must_use]
745 pub fn with_provider_details(
746 mut self,
747 details: serde_json::Map<String, serde_json::Value>,
748 ) -> Self {
749 self.provider_details = Some(details);
750 self
751 }
752
753 #[must_use]
755 pub fn has_content(&self) -> bool {
756 !self.content.is_empty()
757 }
758
759 #[must_use]
761 pub fn media_type(&self) -> &str {
762 &self.content.media_type
763 }
764
765 #[must_use]
767 pub fn data(&self) -> &[u8] {
768 &self.content.data
769 }
770
771 #[must_use]
773 pub fn size(&self) -> usize {
774 self.content.len()
775 }
776}
777
778#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
784pub struct BuiltinToolCallPart {
785 pub tool_name: String,
787 pub args: ToolCallArgs,
789 #[serde(skip_serializing_if = "Option::is_none")]
791 pub tool_call_id: Option<String>,
792 #[serde(skip_serializing_if = "Option::is_none")]
794 pub id: Option<String>,
795 #[serde(skip_serializing_if = "Option::is_none")]
797 pub provider_details: Option<serde_json::Map<String, serde_json::Value>>,
798}
799
800impl BuiltinToolCallPart {
801 pub const PART_KIND: &'static str = "builtin-tool-call";
803
804 #[must_use]
806 pub fn new(tool_name: impl Into<String>, args: impl Into<ToolCallArgs>) -> Self {
807 Self {
808 tool_name: tool_name.into(),
809 args: args.into(),
810 tool_call_id: None,
811 id: None,
812 provider_details: None,
813 }
814 }
815
816 #[must_use]
818 pub fn part_kind(&self) -> &'static str {
819 Self::PART_KIND
820 }
821
822 #[must_use]
824 pub fn with_tool_call_id(mut self, id: impl Into<String>) -> Self {
825 self.tool_call_id = Some(id.into());
826 self
827 }
828
829 #[must_use]
831 pub fn with_part_id(mut self, id: impl Into<String>) -> Self {
832 self.id = Some(id.into());
833 self
834 }
835
836 #[must_use]
838 pub fn with_provider_details(
839 mut self,
840 details: serde_json::Map<String, serde_json::Value>,
841 ) -> Self {
842 self.provider_details = Some(details);
843 self
844 }
845
846 #[must_use]
848 pub fn args_as_dict(&self) -> serde_json::Value {
849 self.args.to_json()
850 }
851
852 pub fn args_as_json_str(&self) -> Result<String, serde_json::Error> {
858 self.args.to_json_string()
859 }
860
861 pub fn parse_args<T: for<'de> Deserialize<'de>>(&self) -> Result<T, serde_json::Error> {
863 let json = self.args.to_json();
864 serde_json::from_value(json)
865 }
866}
867
868#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
870pub struct WebSearchResult {
871 pub title: String,
873 pub url: String,
875 #[serde(skip_serializing_if = "Option::is_none")]
877 pub snippet: Option<String>,
878 #[serde(skip_serializing_if = "Option::is_none")]
880 pub content: Option<String>,
881}
882
883impl WebSearchResult {
884 #[must_use]
886 pub fn new(title: impl Into<String>, url: impl Into<String>) -> Self {
887 Self {
888 title: title.into(),
889 url: url.into(),
890 snippet: None,
891 content: None,
892 }
893 }
894
895 #[must_use]
897 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
898 self.snippet = Some(snippet.into());
899 self
900 }
901
902 #[must_use]
904 pub fn with_content(mut self, content: impl Into<String>) -> Self {
905 self.content = Some(content.into());
906 self
907 }
908}
909
910#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
912pub struct WebSearchResults {
913 pub query: String,
915 pub results: Vec<WebSearchResult>,
917 #[serde(skip_serializing_if = "Option::is_none")]
919 pub total_results: Option<u64>,
920}
921
922impl WebSearchResults {
923 #[must_use]
925 pub fn new(query: impl Into<String>, results: Vec<WebSearchResult>) -> Self {
926 Self {
927 query: query.into(),
928 results,
929 total_results: None,
930 }
931 }
932
933 #[must_use]
935 pub fn with_total_results(mut self, total: u64) -> Self {
936 self.total_results = Some(total);
937 self
938 }
939
940 #[must_use]
942 pub fn is_empty(&self) -> bool {
943 self.results.is_empty()
944 }
945
946 #[must_use]
948 pub fn len(&self) -> usize {
949 self.results.len()
950 }
951}
952
953#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
955pub struct CodeExecutionResult {
956 pub code: String,
958 #[serde(skip_serializing_if = "Option::is_none")]
960 pub stdout: Option<String>,
961 #[serde(skip_serializing_if = "Option::is_none")]
963 pub stderr: Option<String>,
964 #[serde(skip_serializing_if = "Option::is_none")]
966 pub exit_code: Option<i32>,
967 #[serde(skip_serializing_if = "Vec::is_empty", default)]
969 pub output_files: Vec<BinaryContent>,
970 #[serde(skip_serializing_if = "Option::is_none")]
972 pub error: Option<String>,
973}
974
975impl CodeExecutionResult {
976 #[must_use]
978 pub fn new(code: impl Into<String>) -> Self {
979 Self {
980 code: code.into(),
981 stdout: None,
982 stderr: None,
983 exit_code: None,
984 output_files: Vec::new(),
985 error: None,
986 }
987 }
988
989 #[must_use]
991 pub fn with_stdout(mut self, stdout: impl Into<String>) -> Self {
992 self.stdout = Some(stdout.into());
993 self
994 }
995
996 #[must_use]
998 pub fn with_stderr(mut self, stderr: impl Into<String>) -> Self {
999 self.stderr = Some(stderr.into());
1000 self
1001 }
1002
1003 #[must_use]
1005 pub fn with_exit_code(mut self, code: i32) -> Self {
1006 self.exit_code = Some(code);
1007 self
1008 }
1009
1010 #[must_use]
1012 pub fn with_output_file(mut self, file: BinaryContent) -> Self {
1013 self.output_files.push(file);
1014 self
1015 }
1016
1017 #[must_use]
1019 pub fn with_error(mut self, error: impl Into<String>) -> Self {
1020 self.error = Some(error.into());
1021 self
1022 }
1023
1024 #[must_use]
1026 pub fn is_success(&self) -> bool {
1027 self.error.is_none() && self.exit_code.map_or(true, |c| c == 0)
1028 }
1029}
1030
1031#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1033pub struct FileSearchResult {
1034 pub file_name: String,
1036 pub content: String,
1038 #[serde(skip_serializing_if = "Option::is_none")]
1040 pub score: Option<f64>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1043 pub metadata: Option<serde_json::Map<String, serde_json::Value>>,
1044}
1045
1046impl FileSearchResult {
1047 #[must_use]
1049 pub fn new(file_name: impl Into<String>, content: impl Into<String>) -> Self {
1050 Self {
1051 file_name: file_name.into(),
1052 content: content.into(),
1053 score: None,
1054 metadata: None,
1055 }
1056 }
1057
1058 #[must_use]
1060 pub fn with_score(mut self, score: f64) -> Self {
1061 self.score = Some(score);
1062 self
1063 }
1064
1065 #[must_use]
1067 pub fn with_metadata(mut self, metadata: serde_json::Map<String, serde_json::Value>) -> Self {
1068 self.metadata = Some(metadata);
1069 self
1070 }
1071}
1072
1073#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1075pub struct FileSearchResults {
1076 pub query: String,
1078 pub results: Vec<FileSearchResult>,
1080}
1081
1082impl FileSearchResults {
1083 #[must_use]
1085 pub fn new(query: impl Into<String>, results: Vec<FileSearchResult>) -> Self {
1086 Self {
1087 query: query.into(),
1088 results,
1089 }
1090 }
1091
1092 #[must_use]
1094 pub fn is_empty(&self) -> bool {
1095 self.results.is_empty()
1096 }
1097
1098 #[must_use]
1100 pub fn len(&self) -> usize {
1101 self.results.len()
1102 }
1103}
1104
1105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1110#[serde(tag = "type", rename_all = "snake_case")]
1111pub enum BuiltinToolReturnContent {
1112 WebSearch(WebSearchResults),
1114 CodeExecution(CodeExecutionResult),
1116 FileSearch(FileSearchResults),
1118 Other {
1120 kind: String,
1122 data: serde_json::Value,
1124 },
1125}
1126
1127impl BuiltinToolReturnContent {
1128 #[must_use]
1130 pub fn web_search(results: WebSearchResults) -> Self {
1131 Self::WebSearch(results)
1132 }
1133
1134 #[must_use]
1136 pub fn code_execution(result: CodeExecutionResult) -> Self {
1137 Self::CodeExecution(result)
1138 }
1139
1140 #[must_use]
1142 pub fn file_search(results: FileSearchResults) -> Self {
1143 Self::FileSearch(results)
1144 }
1145
1146 #[must_use]
1148 pub fn other(kind: impl Into<String>, data: serde_json::Value) -> Self {
1149 Self::Other {
1150 kind: kind.into(),
1151 data,
1152 }
1153 }
1154
1155 #[must_use]
1157 pub fn content_type(&self) -> &str {
1158 match self {
1159 Self::WebSearch(_) => "web_search",
1160 Self::CodeExecution(_) => "code_execution",
1161 Self::FileSearch(_) => "file_search",
1162 Self::Other { kind, .. } => kind,
1163 }
1164 }
1165}
1166
1167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1172pub struct BuiltinToolReturnPart {
1173 pub tool_name: String,
1175 pub content: BuiltinToolReturnContent,
1177 pub tool_call_id: String,
1179 pub timestamp: DateTime<Utc>,
1181 #[serde(skip_serializing_if = "Option::is_none")]
1183 pub id: Option<String>,
1184 #[serde(skip_serializing_if = "Option::is_none")]
1186 pub provider_details: Option<serde_json::Map<String, serde_json::Value>>,
1187}
1188
1189impl BuiltinToolReturnPart {
1190 pub const PART_KIND: &'static str = "builtin-tool-return";
1192
1193 #[must_use]
1195 pub fn new(
1196 tool_name: impl Into<String>,
1197 content: BuiltinToolReturnContent,
1198 tool_call_id: impl Into<String>,
1199 ) -> Self {
1200 Self {
1201 tool_name: tool_name.into(),
1202 content,
1203 tool_call_id: tool_call_id.into(),
1204 timestamp: Utc::now(),
1205 id: None,
1206 provider_details: None,
1207 }
1208 }
1209
1210 #[must_use]
1212 pub fn part_kind(&self) -> &'static str {
1213 Self::PART_KIND
1214 }
1215
1216 #[must_use]
1218 pub fn with_timestamp(mut self, timestamp: DateTime<Utc>) -> Self {
1219 self.timestamp = timestamp;
1220 self
1221 }
1222
1223 #[must_use]
1225 pub fn with_id(mut self, id: impl Into<String>) -> Self {
1226 self.id = Some(id.into());
1227 self
1228 }
1229
1230 #[must_use]
1232 pub fn with_provider_details(
1233 mut self,
1234 details: serde_json::Map<String, serde_json::Value>,
1235 ) -> Self {
1236 self.provider_details = Some(details);
1237 self
1238 }
1239
1240 #[must_use]
1242 pub fn content_type(&self) -> &str {
1243 self.content.content_type()
1244 }
1245}
1246
1247#[cfg(test)]
1248mod tests {
1249 use super::*;
1250
1251 #[test]
1252 fn test_text_part() {
1253 let part = TextPart::new("Hello, world!");
1254 assert_eq!(part.content, "Hello, world!");
1255 assert_eq!(part.part_kind(), "text");
1256 assert!(!part.is_empty());
1257 assert_eq!(part.len(), 13);
1258 assert!(part.id.is_none());
1259 assert!(part.provider_details.is_none());
1260 }
1261
1262 #[test]
1263 fn test_text_part_with_builders() {
1264 let mut details = serde_json::Map::new();
1265 details.insert("model".to_string(), serde_json::json!("gpt-4"));
1266
1267 let part = TextPart::new("Hello!")
1268 .with_id("part-123")
1269 .with_provider_details(details.clone());
1270
1271 assert_eq!(part.id, Some("part-123".to_string()));
1272 assert_eq!(part.provider_details, Some(details));
1273 }
1274
1275 #[test]
1276 fn test_tool_call_args_from_json() {
1277 let args = ToolCallArgs::json(serde_json::json!({"location": "NYC"}));
1278 assert!(args.is_valid_json());
1279 assert_eq!(args.to_json_string().unwrap(), r#"{"location":"NYC"}"#);
1280 }
1281
1282 #[test]
1283 fn test_tool_call_args_from_string() {
1284 let args: ToolCallArgs = r#"{"x": 1}"#.into();
1285 assert!(args.is_valid_json());
1286 if let ToolCallArgs::Json(v) = &args {
1288 assert_eq!(v["x"], 1);
1289 } else {
1290 panic!("Expected Json variant");
1291 }
1292 }
1293
1294 #[test]
1297 fn test_to_json_always_returns_object() {
1298 let args = ToolCallArgs::json(serde_json::json!({"foo": "bar"}));
1300 let result = args.to_json();
1301 assert!(result.is_object());
1302 assert_eq!(result["foo"], "bar");
1303 }
1304
1305 #[test]
1306 fn test_to_json_wraps_array() {
1307 let args = ToolCallArgs::json(serde_json::json!([1, 2, 3]));
1309 let result = args.to_json();
1310 assert!(result.is_object());
1311 assert_eq!(result["_value"], serde_json::json!([1, 2, 3]));
1312 }
1313
1314 #[test]
1315 fn test_to_json_wraps_primitive() {
1316 let args = ToolCallArgs::json(serde_json::json!(42));
1318 let result = args.to_json();
1319 assert!(result.is_object());
1320 assert_eq!(result["_value"], 42);
1321
1322 let args = ToolCallArgs::json(serde_json::json!("hello"));
1323 let result = args.to_json();
1324 assert!(result.is_object());
1325 assert_eq!(result["_value"], "hello");
1326 }
1327
1328 #[test]
1329 fn test_to_json_repairs_trailing_comma() {
1330 let args = ToolCallArgs::string(r#"{"a": 1,}"#.to_string());
1332 let result = args.to_json();
1333 assert!(result.is_object());
1334 assert_eq!(result["a"], 1);
1335 }
1336
1337 #[test]
1338 fn test_to_json_repairs_unquoted_keys() {
1339 let args = ToolCallArgs::string(r#"{foo: "bar"}"#.to_string());
1341 let result = args.to_json();
1342 assert!(result.is_object());
1343 assert_eq!(result["foo"], "bar");
1344 }
1345
1346 #[test]
1347 fn test_to_json_repairs_single_quotes() {
1348 let args = ToolCallArgs::string("{'a': 'b'}".to_string());
1350 let result = args.to_json();
1351 assert!(result.is_object());
1352 assert_eq!(result["a"], "b");
1353 }
1354
1355 #[test]
1356 fn test_to_json_repairs_unclosed_braces() {
1357 let args = ToolCallArgs::string(r#"{"x": 1"#.to_string());
1359 let result = args.to_json();
1360 assert!(result.is_object());
1361 assert_eq!(result["x"], 1);
1362 }
1363
1364 #[test]
1365 fn test_to_json_handles_completely_invalid() {
1366 let args = ToolCallArgs::string("this is not json at all".to_string());
1368 let result = args.to_json();
1369 assert!(result.is_object());
1370 assert_eq!(result["_error"], "parse_failed");
1371 assert_eq!(result["_raw"], "this is not json at all");
1372 }
1373
1374 #[test]
1375 fn test_to_json_object_returns_map() {
1376 let args = ToolCallArgs::json(serde_json::json!({"key": "value"}));
1377 let map = args.to_json_object();
1378 assert_eq!(map.get("key"), Some(&serde_json::json!("value")));
1379 }
1380
1381 #[test]
1382 fn test_repair_json_valid_passthrough() {
1383 let result = repair_json(r#"{"valid": true}"#);
1385 assert!(result.is_some());
1386 assert_eq!(result.unwrap()["valid"], true);
1387 }
1388
1389 #[test]
1390 fn test_repair_json_nested_trailing_comma() {
1391 let result = repair_json(r#"{"outer": {"inner": 1,},}"#);
1392 assert!(result.is_some());
1393 let v = result.unwrap();
1394 assert_eq!(v["outer"]["inner"], 1);
1395 }
1396
1397 #[test]
1398 fn test_repair_json_array_trailing_comma() {
1399 let result = repair_json(r#"[1, 2, 3,]"#);
1400 assert!(result.is_some());
1401 assert_eq!(result.unwrap(), serde_json::json!([1, 2, 3]));
1402 }
1403
1404 #[test]
1405 fn test_repair_json_multiple_unquoted_keys() {
1406 let result = repair_json(r#"{foo: 1, bar: 2}"#);
1407 assert!(result.is_some());
1408 let v = result.unwrap();
1409 assert_eq!(v["foo"], 1);
1410 assert_eq!(v["bar"], 2);
1411 }
1412
1413 #[test]
1414 fn test_remove_trailing_commas_helper() {
1415 assert_eq!(remove_trailing_commas("{\"a\": 1,}"), "{\"a\": 1}");
1416 assert_eq!(remove_trailing_commas("[1, 2,]"), "[1, 2]");
1417 assert_eq!(remove_trailing_commas("{\"a\": 1, }"), "{\"a\": 1 }");
1418 }
1419
1420 #[test]
1421 fn test_quote_unquoted_keys_helper() {
1422 assert_eq!(quote_unquoted_keys("{foo: 1}"), "{\"foo\": 1}");
1423 assert_eq!(
1424 quote_unquoted_keys("{foo: 1, bar: 2}"),
1425 "{\"foo\": 1, \"bar\": 2}"
1426 );
1427 }
1428
1429 #[test]
1432 fn test_tool_call_part() {
1433 let part = ToolCallPart::new("get_weather", serde_json::json!({"city": "NYC"}))
1434 .with_tool_call_id("call_123");
1435 assert_eq!(part.tool_name, "get_weather");
1436 assert_eq!(part.tool_call_id, Some("call_123".to_string()));
1437 assert_eq!(part.part_kind(), "tool-call");
1438 assert!(part.id.is_none());
1439 assert!(part.provider_details.is_none());
1440 }
1441
1442 #[test]
1443 fn test_tool_call_part_with_all_fields() {
1444 let mut details = serde_json::Map::new();
1445 details.insert("temperature".to_string(), serde_json::json!(0.7));
1446
1447 let part = ToolCallPart::new("search", serde_json::json!({"query": "rust"}))
1448 .with_tool_call_id("call_456")
1449 .with_part_id("part-789")
1450 .with_provider_details(details.clone());
1451
1452 assert_eq!(part.tool_call_id, Some("call_456".to_string()));
1453 assert_eq!(part.id, Some("part-789".to_string()));
1454 assert_eq!(part.provider_details, Some(details));
1455 }
1456
1457 #[test]
1458 #[allow(deprecated)]
1459 fn test_tool_call_part_deprecated_with_id() {
1460 let part = ToolCallPart::new("test", serde_json::json!({})).with_id("call_compat");
1462 assert_eq!(part.tool_call_id, Some("call_compat".to_string()));
1463 }
1464
1465 #[test]
1466 fn test_tool_call_parse_args() {
1467 #[derive(Deserialize, PartialEq, Debug)]
1468 struct WeatherArgs {
1469 city: String,
1470 }
1471
1472 let part = ToolCallPart::new("get_weather", serde_json::json!({"city": "NYC"}));
1473 let args: WeatherArgs = part.parse_args().unwrap();
1474 assert_eq!(args.city, "NYC");
1475 }
1476
1477 #[test]
1478 fn test_thinking_part() {
1479 let part = ThinkingPart::new("Let me think about this...").with_signature("sig123");
1480 assert_eq!(part.content, "Let me think about this...");
1481 assert_eq!(part.signature, Some("sig123".to_string()));
1482 assert!(part.id.is_none());
1483 assert!(part.provider_name.is_none());
1484 assert!(part.provider_details.is_none());
1485 }
1486
1487 #[test]
1488 fn test_thinking_part_with_all_fields() {
1489 let mut details = serde_json::Map::new();
1490 details.insert("thinking_tokens".to_string(), serde_json::json!(1500));
1491
1492 let part = ThinkingPart::new("Deep thoughts...")
1493 .with_id("think-001")
1494 .with_signature("sig456")
1495 .with_provider_name("anthropic")
1496 .with_provider_details(details.clone());
1497
1498 assert_eq!(part.id, Some("think-001".to_string()));
1499 assert_eq!(part.signature, Some("sig456".to_string()));
1500 assert_eq!(part.provider_name, Some("anthropic".to_string()));
1501 assert_eq!(part.provider_details, Some(details));
1502 }
1503
1504 #[test]
1505 fn test_thinking_part_redacted_anthropic() {
1506 let part = ThinkingPart::redacted("encrypted_signature_data", "anthropic");
1507
1508 assert!(part.is_redacted());
1509 assert!(part.content.is_empty());
1510 assert_eq!(part.id, Some("redacted_thinking".to_string()));
1511 assert_eq!(part.signature, Some("encrypted_signature_data".to_string()));
1512 assert_eq!(part.provider_name, Some("anthropic".to_string()));
1513 assert_eq!(part.redacted_signature(), Some("encrypted_signature_data"));
1514 }
1515
1516 #[test]
1517 fn test_thinking_part_redacted_bedrock() {
1518 let part = ThinkingPart::redacted("base64_encoded_content", "aws-bedrock");
1519
1520 assert!(part.is_redacted());
1521 assert!(part.content.is_empty());
1522 assert_eq!(part.id, Some("redacted_content".to_string()));
1523 assert_eq!(part.signature, Some("base64_encoded_content".to_string()));
1524 assert_eq!(part.provider_name, Some("aws-bedrock".to_string()));
1525 }
1526
1527 #[test]
1528 fn test_thinking_part_redacted_with_custom_id() {
1529 let part = ThinkingPart::redacted_with_id(
1530 "redacted_custom_type",
1531 "my_signature",
1532 "custom-provider",
1533 );
1534
1535 assert!(part.is_redacted());
1536 assert_eq!(part.id, Some("redacted_custom_type".to_string()));
1537 assert_eq!(part.signature, Some("my_signature".to_string()));
1538 }
1539
1540 #[test]
1541 fn test_thinking_part_not_redacted() {
1542 let part = ThinkingPart::new("Regular thinking content").with_id("think-123");
1543
1544 assert!(!part.is_redacted());
1545 assert_eq!(part.redacted_signature(), None);
1546 }
1547
1548 #[test]
1549 fn test_thinking_part_redacted_constants() {
1550 assert_eq!(ThinkingPart::REDACTED_THINKING_ID, "redacted_thinking");
1551 assert_eq!(ThinkingPart::REDACTED_CONTENT_ID, "redacted_content");
1552 }
1553
1554 #[test]
1555 fn test_serde_roundtrip_redacted_thinking() {
1556 let part = ThinkingPart::redacted("encrypted_data_here", "anthropic");
1557
1558 let json = serde_json::to_string(&part).unwrap();
1559 let parsed: ThinkingPart = serde_json::from_str(&json).unwrap();
1560
1561 assert_eq!(part, parsed);
1562 assert!(parsed.is_redacted());
1563 assert_eq!(parsed.redacted_signature(), Some("encrypted_data_here"));
1564 }
1565
1566 #[test]
1567 fn test_serde_roundtrip_tool_call() {
1568 let mut details = serde_json::Map::new();
1569 details.insert("key".to_string(), serde_json::json!("value"));
1570
1571 let part = ToolCallPart::new("test", serde_json::json!({"a": 1}))
1572 .with_tool_call_id("call_1")
1573 .with_part_id("part_1")
1574 .with_provider_details(details);
1575
1576 let json = serde_json::to_string(&part).unwrap();
1577 let parsed: ToolCallPart = serde_json::from_str(&json).unwrap();
1578 assert_eq!(part, parsed);
1579 }
1580
1581 #[test]
1582 fn test_serde_roundtrip_text() {
1583 let mut details = serde_json::Map::new();
1584 details.insert("tokens".to_string(), serde_json::json!(42));
1585
1586 let part = TextPart::new("Hello")
1587 .with_id("text-1")
1588 .with_provider_details(details);
1589
1590 let json = serde_json::to_string(&part).unwrap();
1591 let parsed: TextPart = serde_json::from_str(&json).unwrap();
1592 assert_eq!(part, parsed);
1593 }
1594
1595 #[test]
1596 fn test_serde_roundtrip_thinking() {
1597 let mut details = serde_json::Map::new();
1598 details.insert("budget".to_string(), serde_json::json!(10000));
1599
1600 let part = ThinkingPart::new("Thinking...")
1601 .with_id("think-1")
1602 .with_signature("sig")
1603 .with_provider_name("anthropic")
1604 .with_provider_details(details);
1605
1606 let json = serde_json::to_string(&part).unwrap();
1607 let parsed: ThinkingPart = serde_json::from_str(&json).unwrap();
1608 assert_eq!(part, parsed);
1609 }
1610
1611 #[test]
1612 fn test_serde_skip_none_fields() {
1613 let part = TextPart::new("Hello");
1615 let json = serde_json::to_string(&part).unwrap();
1616
1617 assert!(json.contains("content"));
1619 assert!(!json.contains("id"));
1620 assert!(!json.contains("provider_details"));
1621 }
1622
1623 #[test]
1624 fn test_backward_compat_deserialization() {
1625 let old_json = r#"{"content":"Hello, world!"}"#;
1627 let part: TextPart = serde_json::from_str(old_json).unwrap();
1628 assert_eq!(part.content, "Hello, world!");
1629 assert!(part.id.is_none());
1630 assert!(part.provider_details.is_none());
1631 }
1632
1633 #[test]
1634 fn test_binary_content() {
1635 let data = vec![0x89, 0x50, 0x4E, 0x47]; let content = BinaryContent::new(data.clone(), "image/png");
1637
1638 assert_eq!(content.data, data);
1639 assert_eq!(content.media_type, "image/png");
1640 assert!(!content.is_empty());
1641 assert_eq!(content.len(), 4);
1642 }
1643
1644 #[test]
1645 fn test_binary_content_empty() {
1646 let content = BinaryContent::default();
1647 assert!(content.is_empty());
1648 assert_eq!(content.len(), 0);
1649 assert!(content.media_type.is_empty());
1650 }
1651
1652 #[test]
1653 fn test_file_part() {
1654 let data = vec![0xFF, 0xD8, 0xFF]; let part = FilePart::from_bytes(data.clone(), "image/jpeg");
1656
1657 assert_eq!(part.content.data, data);
1658 assert_eq!(part.media_type(), "image/jpeg");
1659 assert_eq!(part.part_kind(), "file");
1660 assert!(part.has_content());
1661 assert_eq!(part.size(), 3);
1662 assert!(part.id.is_none());
1663 assert!(part.provider_name.is_none());
1664 assert!(part.provider_details.is_none());
1665 }
1666
1667 #[test]
1668 fn test_file_part_with_builders() {
1669 let mut details = serde_json::Map::new();
1670 details.insert("model".to_string(), serde_json::json!("dall-e-3"));
1671 details.insert(
1672 "revised_prompt".to_string(),
1673 serde_json::json!("A cute puppy"),
1674 );
1675
1676 let data = vec![0x89, 0x50, 0x4E, 0x47];
1677 let part = FilePart::from_bytes(data, "image/png")
1678 .with_id("file-123")
1679 .with_provider_name("openai")
1680 .with_provider_details(details.clone());
1681
1682 assert_eq!(part.id, Some("file-123".to_string()));
1683 assert_eq!(part.provider_name, Some("openai".to_string()));
1684 assert_eq!(part.provider_details, Some(details));
1685 }
1686
1687 #[test]
1688 fn test_file_part_empty_content() {
1689 let part = FilePart::from_bytes(vec![], "application/octet-stream");
1690 assert!(!part.has_content());
1691 assert_eq!(part.size(), 0);
1692 }
1693
1694 #[test]
1695 fn test_serde_roundtrip_binary_content() {
1696 let data = vec![0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD];
1697 let content = BinaryContent::new(data.clone(), "application/octet-stream");
1698
1699 let json = serde_json::to_string(&content).unwrap();
1700 let parsed: BinaryContent = serde_json::from_str(&json).unwrap();
1701
1702 assert_eq!(content, parsed);
1703 assert_eq!(parsed.data, data);
1704 }
1705
1706 #[test]
1707 fn test_serde_roundtrip_file_part() {
1708 let mut details = serde_json::Map::new();
1709 details.insert("quality".to_string(), serde_json::json!("hd"));
1710
1711 let data = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
1712 let part = FilePart::from_bytes(data.clone(), "image/png")
1713 .with_id("img-001")
1714 .with_provider_name("openai")
1715 .with_provider_details(details);
1716
1717 let json = serde_json::to_string(&part).unwrap();
1718 let parsed: FilePart = serde_json::from_str(&json).unwrap();
1719
1720 assert_eq!(part, parsed);
1721 assert_eq!(parsed.data(), &data);
1722 }
1723
1724 #[test]
1725 fn test_file_part_serde_skip_none() {
1726 let part = FilePart::from_bytes(vec![0x00], "application/octet-stream");
1728 let json = serde_json::to_string(&part).unwrap();
1729
1730 assert!(json.contains("content"));
1731 assert!(!json.contains("id"));
1732 assert!(!json.contains("provider_name"));
1733 assert!(!json.contains("provider_details"));
1734 }
1735
1736 #[test]
1737 fn test_builtin_tool_call_part() {
1738 let part = BuiltinToolCallPart::new("web_search", serde_json::json!({"query": "rust"}))
1739 .with_tool_call_id("call_123");
1740
1741 assert_eq!(part.tool_name, "web_search");
1742 assert_eq!(part.tool_call_id, Some("call_123".to_string()));
1743 assert_eq!(part.part_kind(), "builtin-tool-call");
1744 }
1745
1746 #[test]
1747 fn test_builtin_tool_call_part_with_all_fields() {
1748 let mut details = serde_json::Map::new();
1749 details.insert("provider".to_string(), serde_json::json!("google"));
1750
1751 let part =
1752 BuiltinToolCallPart::new("code_execution", serde_json::json!({"code": "print(1)"}))
1753 .with_tool_call_id("call_456")
1754 .with_part_id("part-789")
1755 .with_provider_details(details.clone());
1756
1757 assert_eq!(part.tool_call_id, Some("call_456".to_string()));
1758 assert_eq!(part.id, Some("part-789".to_string()));
1759 assert_eq!(part.provider_details, Some(details));
1760 }
1761
1762 #[test]
1763 fn test_web_search_result() {
1764 let result = WebSearchResult::new("Rust Programming", "https://rust-lang.org")
1765 .with_snippet("A systems programming language")
1766 .with_content("Full article content...");
1767
1768 assert_eq!(result.title, "Rust Programming");
1769 assert_eq!(result.url, "https://rust-lang.org");
1770 assert_eq!(
1771 result.snippet,
1772 Some("A systems programming language".to_string())
1773 );
1774 assert!(result.content.is_some());
1775 }
1776
1777 #[test]
1778 fn test_web_search_results() {
1779 let results = WebSearchResults::new(
1780 "rust programming",
1781 vec![
1782 WebSearchResult::new("Rust", "https://rust-lang.org"),
1783 WebSearchResult::new("Crates.io", "https://crates.io"),
1784 ],
1785 )
1786 .with_total_results(1000);
1787
1788 assert_eq!(results.query, "rust programming");
1789 assert_eq!(results.len(), 2);
1790 assert!(!results.is_empty());
1791 assert_eq!(results.total_results, Some(1000));
1792 }
1793
1794 #[test]
1795 fn test_code_execution_result() {
1796 let result = CodeExecutionResult::new("print('hello')")
1797 .with_stdout("hello\n")
1798 .with_exit_code(0);
1799
1800 assert_eq!(result.code, "print('hello')");
1801 assert_eq!(result.stdout, Some("hello\n".to_string()));
1802 assert!(result.is_success());
1803 }
1804
1805 #[test]
1806 fn test_code_execution_result_with_error() {
1807 let result = CodeExecutionResult::new("invalid code")
1808 .with_stderr("SyntaxError")
1809 .with_exit_code(1)
1810 .with_error("Compilation failed");
1811
1812 assert!(!result.is_success());
1813 assert_eq!(result.error, Some("Compilation failed".to_string()));
1814 }
1815
1816 #[test]
1817 fn test_code_execution_result_with_output_file() {
1818 let image = BinaryContent::new(vec![0x89, 0x50, 0x4E, 0x47], "image/png");
1819 let result = CodeExecutionResult::new("plot()")
1820 .with_stdout("Plot saved")
1821 .with_output_file(image);
1822
1823 assert_eq!(result.output_files.len(), 1);
1824 assert_eq!(result.output_files[0].media_type, "image/png");
1825 }
1826
1827 #[test]
1828 fn test_file_search_result() {
1829 let mut metadata = serde_json::Map::new();
1830 metadata.insert("size".to_string(), serde_json::json!(1024));
1831
1832 let result = FileSearchResult::new("main.rs", "fn main() {}")
1833 .with_score(0.95)
1834 .with_metadata(metadata);
1835
1836 assert_eq!(result.file_name, "main.rs");
1837 assert_eq!(result.score, Some(0.95));
1838 assert!(result.metadata.is_some());
1839 }
1840
1841 #[test]
1842 fn test_file_search_results() {
1843 let results = FileSearchResults::new(
1844 "main function",
1845 vec![FileSearchResult::new("main.rs", "fn main() {}")],
1846 );
1847
1848 assert_eq!(results.query, "main function");
1849 assert_eq!(results.len(), 1);
1850 assert!(!results.is_empty());
1851 }
1852
1853 #[test]
1854 fn test_builtin_tool_return_content() {
1855 let web_content =
1856 BuiltinToolReturnContent::web_search(WebSearchResults::new("test", vec![]));
1857 assert_eq!(web_content.content_type(), "web_search");
1858
1859 let code_content =
1860 BuiltinToolReturnContent::code_execution(CodeExecutionResult::new("x = 1"));
1861 assert_eq!(code_content.content_type(), "code_execution");
1862
1863 let file_content =
1864 BuiltinToolReturnContent::file_search(FileSearchResults::new("query", vec![]));
1865 assert_eq!(file_content.content_type(), "file_search");
1866
1867 let other_content =
1868 BuiltinToolReturnContent::other("custom_tool", serde_json::json!({"result": "data"}));
1869 assert_eq!(other_content.content_type(), "custom_tool");
1870 }
1871
1872 #[test]
1873 fn test_builtin_tool_return_part() {
1874 let content = BuiltinToolReturnContent::web_search(WebSearchResults::new(
1875 "rust",
1876 vec![WebSearchResult::new("Rust", "https://rust-lang.org")],
1877 ));
1878
1879 let part =
1880 BuiltinToolReturnPart::new("web_search", content, "call_123").with_id("return-001");
1881
1882 assert_eq!(part.tool_name, "web_search");
1883 assert_eq!(part.tool_call_id, "call_123");
1884 assert_eq!(part.part_kind(), "builtin-tool-return");
1885 assert_eq!(part.content_type(), "web_search");
1886 assert_eq!(part.id, Some("return-001".to_string()));
1887 }
1888
1889 #[test]
1890 fn test_serde_roundtrip_builtin_tool_call() {
1891 let part = BuiltinToolCallPart::new("web_search", serde_json::json!({"q": "test"}))
1892 .with_tool_call_id("call_1");
1893
1894 let json = serde_json::to_string(&part).unwrap();
1895 let parsed: BuiltinToolCallPart = serde_json::from_str(&json).unwrap();
1896 assert_eq!(part, parsed);
1897 }
1898
1899 #[test]
1900 fn test_serde_roundtrip_web_search_results() {
1901 let results = WebSearchResults::new(
1902 "rust",
1903 vec![WebSearchResult::new("Rust", "https://rust-lang.org")
1904 .with_snippet("Systems programming")],
1905 )
1906 .with_total_results(100);
1907
1908 let json = serde_json::to_string(&results).unwrap();
1909 let parsed: WebSearchResults = serde_json::from_str(&json).unwrap();
1910 assert_eq!(results, parsed);
1911 }
1912
1913 #[test]
1914 fn test_serde_roundtrip_builtin_tool_return() {
1915 let content = BuiltinToolReturnContent::code_execution(
1916 CodeExecutionResult::new("print(1)")
1917 .with_stdout("1\n")
1918 .with_exit_code(0),
1919 );
1920
1921 let part =
1922 BuiltinToolReturnPart::new("code_execution", content, "call_xyz").with_id("ret-1");
1923
1924 let json = serde_json::to_string(&part).unwrap();
1925 let parsed: BuiltinToolReturnPart = serde_json::from_str(&json).unwrap();
1926
1927 assert_eq!(part.tool_name, parsed.tool_name);
1928 assert_eq!(part.tool_call_id, parsed.tool_call_id);
1929 assert_eq!(part.id, parsed.id);
1930 }
1931}