1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Clone, Debug)]
22#[non_exhaustive]
23pub enum Tool {
24 Function {
26 name: String,
27 description: String,
28 parameters: FunctionParameters,
29 },
30 GoogleSearch {
34 search_types: Option<Vec<SearchType>>,
37 },
38 GoogleMaps {
40 enable_widget: Option<bool>,
42 latitude: Option<f64>,
44 longitude: Option<f64>,
46 },
47 CodeExecution,
49 UrlContext,
51 ComputerUse {
56 environment: String,
58 excluded_predefined_functions: Vec<String>,
61 enable_prompt_injection_detection: Option<bool>,
63 disabled_safety_policies: Vec<String>,
68 },
69 McpServer {
71 name: String,
72 url: String,
73 allowed_tools: Option<Vec<AllowedTools>>,
76 headers: Option<HashMap<String, String>>,
78 },
79 FileSearch {
81 store_names: Vec<String>,
83 top_k: Option<i32>,
85 metadata_filter: Option<String>,
87 },
88 Retrieval {
101 retrieval_types: Option<Vec<RetrievalType>>,
103 vertex_ai_search_config: Option<VertexAiSearchConfig>,
105 exa_ai_search_config: Option<ExaAiSearchConfig>,
107 parallel_ai_search_config: Option<ParallelAiSearchConfig>,
109 rag_store_config: Option<Box<RagStoreConfig>>,
114 },
115 Unknown {
124 tool_type: String,
126 data: serde_json::Value,
128 },
129}
130
131impl Serialize for Tool {
134 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135 where
136 S: serde::Serializer,
137 {
138 use serde::ser::SerializeMap;
139
140 match self {
141 Self::Function {
142 name,
143 description,
144 parameters,
145 } => {
146 let mut map = serializer.serialize_map(None)?;
147 map.serialize_entry("type", "function")?;
148 map.serialize_entry("name", name)?;
149 map.serialize_entry("description", description)?;
150 map.serialize_entry("parameters", parameters)?;
151 map.end()
152 }
153 Self::GoogleSearch { search_types } => {
154 let mut map = serializer.serialize_map(None)?;
155 map.serialize_entry("type", "google_search")?;
156 if let Some(types) = search_types
157 && !types.is_empty()
158 {
159 map.serialize_entry("search_types", types)?;
160 }
161 map.end()
162 }
163 Self::GoogleMaps {
164 enable_widget,
165 latitude,
166 longitude,
167 } => {
168 let mut map = serializer.serialize_map(None)?;
169 map.serialize_entry("type", "google_maps")?;
170 if let Some(ew) = enable_widget {
171 map.serialize_entry("enable_widget", ew)?;
172 }
173 if let Some(lat) = latitude {
174 map.serialize_entry("latitude", lat)?;
175 }
176 if let Some(lng) = longitude {
177 map.serialize_entry("longitude", lng)?;
178 }
179 map.end()
180 }
181 Self::CodeExecution => {
182 let mut map = serializer.serialize_map(None)?;
183 map.serialize_entry("type", "code_execution")?;
184 map.end()
185 }
186 Self::UrlContext => {
187 let mut map = serializer.serialize_map(None)?;
188 map.serialize_entry("type", "url_context")?;
189 map.end()
190 }
191 Self::ComputerUse {
192 environment,
193 excluded_predefined_functions,
194 enable_prompt_injection_detection,
195 disabled_safety_policies,
196 } => {
197 let mut map = serializer.serialize_map(None)?;
198 map.serialize_entry("type", "computer_use")?;
199 map.serialize_entry("environment", environment)?;
200 if !excluded_predefined_functions.is_empty() {
201 map.serialize_entry(
202 "excluded_predefined_functions",
203 excluded_predefined_functions,
204 )?;
205 }
206 if let Some(detect) = enable_prompt_injection_detection {
207 map.serialize_entry("enable_prompt_injection_detection", detect)?;
208 }
209 if !disabled_safety_policies.is_empty() {
210 map.serialize_entry("disabled_safety_policies", disabled_safety_policies)?;
211 }
212 map.end()
213 }
214 Self::McpServer {
215 name,
216 url,
217 allowed_tools,
218 headers,
219 } => {
220 let mut map = serializer.serialize_map(None)?;
221 map.serialize_entry("type", "mcp_server")?;
222 map.serialize_entry("name", name)?;
223 map.serialize_entry("url", url)?;
224 if let Some(tools) = allowed_tools
225 && !tools.is_empty()
226 {
227 map.serialize_entry("allowed_tools", tools)?;
228 }
229 if let Some(hdrs) = headers
230 && !hdrs.is_empty()
231 {
232 map.serialize_entry("headers", hdrs)?;
233 }
234 map.end()
235 }
236 Self::FileSearch {
237 store_names,
238 top_k,
239 metadata_filter,
240 } => {
241 let mut map = serializer.serialize_map(None)?;
242 map.serialize_entry("type", "file_search")?;
243 map.serialize_entry("file_search_store_names", store_names)?;
244 if let Some(k) = top_k {
245 map.serialize_entry("top_k", k)?;
246 }
247 if let Some(filter) = metadata_filter {
248 map.serialize_entry("metadata_filter", filter)?;
249 }
250 map.end()
251 }
252 Self::Retrieval {
253 retrieval_types,
254 vertex_ai_search_config,
255 exa_ai_search_config,
256 parallel_ai_search_config,
257 rag_store_config,
258 } => {
259 let mut map = serializer.serialize_map(None)?;
260 map.serialize_entry("type", "retrieval")?;
261 if let Some(types) = retrieval_types
262 && !types.is_empty()
263 {
264 map.serialize_entry("retrieval_types", types)?;
265 }
266 if let Some(config) = vertex_ai_search_config {
267 map.serialize_entry("vertex_ai_search_config", config)?;
268 }
269 if let Some(config) = exa_ai_search_config {
270 map.serialize_entry("exa_ai_search_config", config)?;
271 }
272 if let Some(config) = parallel_ai_search_config {
273 map.serialize_entry("parallel_ai_search_config", config)?;
274 }
275 if let Some(config) = rag_store_config {
276 map.serialize_entry("rag_store_config", config)?;
277 }
278 map.end()
279 }
280 Self::Unknown { tool_type, data } => {
281 let mut map = serializer.serialize_map(None)?;
282 map.serialize_entry("type", tool_type)?;
283 if let serde_json::Value::Object(obj) = data {
285 for (key, value) in obj {
286 if key != "type" {
287 map.serialize_entry(key, value)?;
288 }
289 }
290 } else if !data.is_null() {
291 map.serialize_entry("data", data)?;
292 }
293 map.end()
294 }
295 }
296 }
297}
298
299impl<'de> Deserialize<'de> for Tool {
301 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
302 where
303 D: serde::Deserializer<'de>,
304 {
305 let value = serde_json::Value::deserialize(deserializer)?;
307
308 #[derive(Deserialize)]
311 #[serde(tag = "type")]
312 enum KnownTool {
313 #[serde(rename = "function")]
314 Function {
315 name: String,
316 description: String,
317 parameters: FunctionParameters,
318 },
319 #[serde(rename = "google_search")]
320 GoogleSearch {
321 #[serde(default)]
322 search_types: Option<Vec<SearchType>>,
323 },
324 #[serde(rename = "google_maps")]
325 GoogleMaps {
326 #[serde(default)]
327 enable_widget: Option<bool>,
328 #[serde(default)]
329 latitude: Option<f64>,
330 #[serde(default)]
331 longitude: Option<f64>,
332 },
333 #[serde(rename = "code_execution")]
334 CodeExecution,
335 #[serde(rename = "url_context")]
336 UrlContext,
337 #[serde(rename = "computer_use")]
338 ComputerUse {
339 environment: String,
340 #[serde(default, alias = "excludedPredefinedFunctions")]
343 excluded_predefined_functions: Vec<String>,
344 #[serde(default)]
345 enable_prompt_injection_detection: Option<bool>,
346 #[serde(default)]
347 disabled_safety_policies: Vec<String>,
348 },
349 #[serde(rename = "mcp_server")]
350 McpServer {
351 name: String,
352 url: String,
353 #[serde(default)]
354 allowed_tools: Option<Vec<AllowedTools>>,
355 #[serde(default)]
356 headers: Option<HashMap<String, String>>,
357 },
358 #[serde(rename = "file_search")]
359 FileSearch {
360 #[serde(rename = "file_search_store_names")]
361 store_names: Vec<String>,
362 #[serde(default)]
363 top_k: Option<i32>,
364 #[serde(default)]
365 metadata_filter: Option<String>,
366 },
367 #[serde(rename = "retrieval")]
368 Retrieval {
369 #[serde(default)]
370 retrieval_types: Option<Vec<RetrievalType>>,
371 #[serde(default)]
372 vertex_ai_search_config: Option<VertexAiSearchConfig>,
373 #[serde(default)]
374 exa_ai_search_config: Option<ExaAiSearchConfig>,
375 #[serde(default)]
376 parallel_ai_search_config: Option<ParallelAiSearchConfig>,
377 #[serde(default)]
378 rag_store_config: Option<Box<RagStoreConfig>>,
379 },
380 }
381
382 match serde_json::from_value::<KnownTool>(value.clone()) {
384 Ok(known) => Ok(match known {
385 KnownTool::Function {
386 name,
387 description,
388 parameters,
389 } => Tool::Function {
390 name,
391 description,
392 parameters,
393 },
394 KnownTool::GoogleSearch { search_types } => Tool::GoogleSearch { search_types },
395 KnownTool::GoogleMaps {
396 enable_widget,
397 latitude,
398 longitude,
399 } => Tool::GoogleMaps {
400 enable_widget,
401 latitude,
402 longitude,
403 },
404 KnownTool::CodeExecution => Tool::CodeExecution,
405 KnownTool::UrlContext => Tool::UrlContext,
406 KnownTool::ComputerUse {
407 environment,
408 excluded_predefined_functions,
409 enable_prompt_injection_detection,
410 disabled_safety_policies,
411 } => Tool::ComputerUse {
412 environment,
413 excluded_predefined_functions,
414 enable_prompt_injection_detection,
415 disabled_safety_policies,
416 },
417 KnownTool::McpServer {
418 name,
419 url,
420 allowed_tools,
421 headers,
422 } => Tool::McpServer {
423 name,
424 url,
425 allowed_tools,
426 headers,
427 },
428 KnownTool::FileSearch {
429 store_names,
430 top_k,
431 metadata_filter,
432 } => Tool::FileSearch {
433 store_names,
434 top_k,
435 metadata_filter,
436 },
437 KnownTool::Retrieval {
438 retrieval_types,
439 vertex_ai_search_config,
440 exa_ai_search_config,
441 parallel_ai_search_config,
442 rag_store_config,
443 } => Tool::Retrieval {
444 retrieval_types,
445 vertex_ai_search_config,
446 exa_ai_search_config,
447 parallel_ai_search_config,
448 rag_store_config,
449 },
450 }),
451 Err(parse_error) => {
452 let tool_type = value
454 .get("type")
455 .and_then(|v| v.as_str())
456 .unwrap_or("<missing type>")
457 .to_string();
458
459 tracing::warn!(
462 "Encountered unknown Tool type '{}'. \
463 Parse error: {}. \
464 This may indicate a new API feature or a malformed response. \
465 The tool will be preserved in the Unknown variant.",
466 tool_type,
467 parse_error
468 );
469
470 Ok(Tool::Unknown {
471 tool_type,
472 data: value,
473 })
474 }
475 }
476 }
477}
478
479impl Tool {
480 #[must_use]
482 pub const fn is_unknown(&self) -> bool {
483 matches!(self, Self::Unknown { .. })
484 }
485
486 #[must_use]
490 pub fn unknown_tool_type(&self) -> Option<&str> {
491 match self {
492 Self::Unknown { tool_type, .. } => Some(tool_type),
493 _ => None,
494 }
495 }
496
497 #[must_use]
501 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
502 match self {
503 Self::Unknown { data, .. } => Some(data),
504 _ => None,
505 }
506 }
507}
508
509#[derive(Clone, Serialize, Deserialize, Debug)]
511pub struct FunctionDeclaration {
512 name: String,
513 description: String,
514 parameters: FunctionParameters,
515}
516
517#[derive(Clone, Serialize, Deserialize, Debug)]
519pub struct FunctionParameters {
520 #[serde(rename = "type")]
521 type_: String,
522 properties: serde_json::Value,
523 #[serde(skip_serializing_if = "Vec::is_empty", default)]
524 required: Vec<String>,
525}
526
527impl FunctionDeclaration {
528 #[doc(hidden)]
533 pub fn new(name: String, description: String, parameters: FunctionParameters) -> Self {
534 Self {
535 name,
536 description,
537 parameters,
538 }
539 }
540
541 #[must_use]
543 pub fn builder(name: impl Into<String>) -> FunctionDeclarationBuilder {
544 FunctionDeclarationBuilder::new(name)
545 }
546
547 #[must_use]
549 pub fn name(&self) -> &str {
550 &self.name
551 }
552
553 #[must_use]
555 pub fn description(&self) -> &str {
556 &self.description
557 }
558
559 #[must_use]
561 pub fn parameters(&self) -> &FunctionParameters {
562 &self.parameters
563 }
564
565 #[must_use]
567 pub fn into_tool(self) -> Tool {
568 Tool::Function {
569 name: self.name,
570 description: self.description,
571 parameters: self.parameters,
572 }
573 }
574}
575
576impl FunctionParameters {
577 #[doc(hidden)]
582 pub fn new(type_: String, properties: serde_json::Value, required: Vec<String>) -> Self {
583 Self {
584 type_,
585 properties,
586 required,
587 }
588 }
589
590 #[must_use]
592 pub fn type_(&self) -> &str {
593 &self.type_
594 }
595
596 #[must_use]
598 pub fn properties(&self) -> &serde_json::Value {
599 &self.properties
600 }
601
602 #[must_use]
604 pub fn required(&self) -> &[String] {
605 &self.required
606 }
607}
608
609#[derive(Debug)]
611pub struct FunctionDeclarationBuilder {
612 name: String,
613 description: String,
614 properties: serde_json::Value,
615 required: Vec<String>,
616}
617
618impl FunctionDeclarationBuilder {
619 pub fn new(name: impl Into<String>) -> Self {
621 Self {
622 name: name.into(),
623 description: String::new(),
624 properties: serde_json::Value::Object(serde_json::Map::new()),
625 required: Vec::new(),
626 }
627 }
628
629 pub fn description(mut self, description: impl Into<String>) -> Self {
631 self.description = description.into();
632 self
633 }
634
635 pub fn parameter(mut self, name: &str, schema: serde_json::Value) -> Self {
637 if let serde_json::Value::Object(ref mut map) = self.properties {
638 map.insert(name.to_string(), schema);
639 }
640 self
641 }
642
643 pub fn required(mut self, required: Vec<String>) -> Self {
645 self.required = required;
646 self
647 }
648
649 pub fn build(self) -> FunctionDeclaration {
660 if self.name.trim().is_empty() {
662 tracing::warn!(
663 "FunctionDeclaration built with empty or whitespace-only name. \
664 This will likely be rejected by the API."
665 );
666 }
667
668 if let serde_json::Value::Object(ref props) = self.properties {
670 for req in &self.required {
671 if !props.contains_key(req) {
672 tracing::warn!(
673 "FunctionDeclaration '{}' requires parameter '{}' which is not defined in properties. \
674 This will likely cause API errors.",
675 self.name,
676 req
677 );
678 }
679 }
680 }
681
682 FunctionDeclaration {
683 name: self.name,
684 description: self.description,
685 parameters: FunctionParameters {
686 type_: "object".to_string(),
687 properties: self.properties,
688 required: self.required,
689 },
690 }
691 }
692}
693
694#[derive(Clone, Debug, PartialEq)]
714#[non_exhaustive]
715pub enum FunctionCallingMode {
716 Auto,
718 Any,
720 None,
722 Validated,
727 Unknown {
735 mode_type: String,
737 data: serde_json::Value,
739 },
740}
741
742impl FunctionCallingMode {
743 #[must_use]
745 pub const fn is_unknown(&self) -> bool {
746 matches!(self, Self::Unknown { .. })
747 }
748
749 #[must_use]
753 pub fn unknown_mode_type(&self) -> Option<&str> {
754 match self {
755 Self::Unknown { mode_type, .. } => Some(mode_type),
756 _ => None,
757 }
758 }
759
760 #[must_use]
764 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
765 match self {
766 Self::Unknown { data, .. } => Some(data),
767 _ => None,
768 }
769 }
770}
771
772impl Serialize for FunctionCallingMode {
773 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
774 where
775 S: serde::Serializer,
776 {
777 match self {
779 Self::Auto => serializer.serialize_str("auto"),
780 Self::Any => serializer.serialize_str("any"),
781 Self::None => serializer.serialize_str("none"),
782 Self::Validated => serializer.serialize_str("validated"),
783 Self::Unknown { mode_type, .. } => serializer.serialize_str(mode_type),
784 }
785 }
786}
787
788impl<'de> Deserialize<'de> for FunctionCallingMode {
789 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
790 where
791 D: serde::Deserializer<'de>,
792 {
793 let value = serde_json::Value::deserialize(deserializer)?;
794
795 match value.as_str() {
796 Some("auto") | Some("AUTO") => Ok(Self::Auto),
798 Some("any") | Some("ANY") => Ok(Self::Any),
799 Some("none") | Some("NONE") => Ok(Self::None),
800 Some("validated") | Some("VALIDATED") => Ok(Self::Validated),
801 Some(other) => {
802 tracing::warn!(
803 "Encountered unknown FunctionCallingMode '{}'. \
804 This may indicate a new API feature. \
805 The mode will be preserved in the Unknown variant.",
806 other
807 );
808 Ok(Self::Unknown {
809 mode_type: other.to_string(),
810 data: value,
811 })
812 }
813 Option::None => {
814 let mode_type = format!("<non-string: {}>", value);
816 tracing::warn!(
817 "FunctionCallingMode received non-string value: {}. \
818 Preserving in Unknown variant.",
819 value
820 );
821 Ok(Self::Unknown {
822 mode_type,
823 data: value,
824 })
825 }
826 }
827 }
828}
829
830#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
836#[serde(default)]
837pub struct AllowedTools {
838 #[serde(skip_serializing_if = "Option::is_none")]
840 pub mode: Option<FunctionCallingMode>,
841 #[serde(skip_serializing_if = "Vec::is_empty")]
843 pub tools: Vec<String>,
844}
845
846impl AllowedTools {
847 #[must_use]
849 pub fn new(tools: Vec<String>) -> Self {
850 Self { mode: None, tools }
851 }
852
853 #[must_use]
855 pub fn with_mode(mut self, mode: FunctionCallingMode) -> Self {
856 self.mode = Some(mode);
857 self
858 }
859}
860
861#[derive(Clone, Debug, PartialEq)]
888#[non_exhaustive]
889pub enum ToolChoice {
890 Mode(FunctionCallingMode),
892 AllowedTools(AllowedTools),
894 Unknown {
896 choice_type: String,
899 data: serde_json::Value,
901 },
902}
903
904impl ToolChoice {
905 #[must_use]
907 pub fn allowed_tools(mode: Option<FunctionCallingMode>, tools: Vec<String>) -> Self {
908 Self::AllowedTools(AllowedTools { mode, tools })
909 }
910
911 #[must_use]
913 pub const fn is_unknown(&self) -> bool {
914 matches!(self, Self::Unknown { .. })
915 }
916
917 #[must_use]
919 pub fn unknown_choice_type(&self) -> Option<&str> {
920 match self {
921 Self::Unknown { choice_type, .. } => Some(choice_type),
922 _ => None,
923 }
924 }
925
926 #[must_use]
928 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
929 match self {
930 Self::Unknown { data, .. } => Some(data),
931 _ => None,
932 }
933 }
934}
935
936impl From<FunctionCallingMode> for ToolChoice {
937 fn from(mode: FunctionCallingMode) -> Self {
938 Self::Mode(mode)
939 }
940}
941
942impl Serialize for ToolChoice {
943 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
944 where
945 S: serde::Serializer,
946 {
947 use serde::ser::SerializeMap;
948
949 match self {
950 Self::Mode(mode) => mode.serialize(serializer),
951 Self::AllowedTools(allowed) => {
952 let mut map = serializer.serialize_map(Some(1))?;
953 map.serialize_entry("allowed_tools", allowed)?;
954 map.end()
955 }
956 Self::Unknown { data, .. } => data.serialize(serializer),
957 }
958 }
959}
960
961impl<'de> Deserialize<'de> for ToolChoice {
962 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
963 where
964 D: serde::Deserializer<'de>,
965 {
966 let value = serde_json::Value::deserialize(deserializer)?;
967 match &value {
968 serde_json::Value::String(_) => {
969 let mode: FunctionCallingMode =
971 serde_json::from_value(value).map_err(serde::de::Error::custom)?;
972 Ok(Self::Mode(mode))
973 }
974 serde_json::Value::Object(obj) if obj.contains_key("allowed_tools") => {
975 match serde_json::from_value::<AllowedTools>(obj["allowed_tools"].clone()) {
976 Ok(allowed) => Ok(Self::AllowedTools(allowed)),
977 Err(e) => {
978 tracing::warn!(
979 "Failed to parse tool_choice.allowed_tools: {}. \
980 Preserving in Unknown variant.",
981 e
982 );
983 Ok(Self::Unknown {
984 choice_type: "allowed_tools".to_string(),
985 data: value,
986 })
987 }
988 }
989 }
990 _ => {
991 tracing::warn!(
992 "Encountered unknown ToolChoice shape: {}. \
993 Preserving in Unknown variant.",
994 value
995 );
996 Ok(Self::Unknown {
997 choice_type: format!("<unrecognized: {}>", value),
998 data: value,
999 })
1000 }
1001 }
1002 }
1003}
1004
1005#[derive(Clone, Debug, PartialEq, Eq)]
1014#[non_exhaustive]
1015pub enum SearchType {
1016 WebSearch,
1018 ImageSearch,
1020 EnterpriseWebSearch,
1022 Unknown {
1024 search_type: String,
1026 data: serde_json::Value,
1028 },
1029}
1030
1031impl SearchType {
1032 #[must_use]
1034 pub const fn is_unknown(&self) -> bool {
1035 matches!(self, Self::Unknown { .. })
1036 }
1037
1038 #[must_use]
1040 pub fn unknown_search_type(&self) -> Option<&str> {
1041 match self {
1042 Self::Unknown { search_type, .. } => Some(search_type),
1043 _ => None,
1044 }
1045 }
1046
1047 #[must_use]
1049 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
1050 match self {
1051 Self::Unknown { data, .. } => Some(data),
1052 _ => None,
1053 }
1054 }
1055}
1056
1057impl Serialize for SearchType {
1058 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1059 where
1060 S: serde::Serializer,
1061 {
1062 match self {
1063 Self::WebSearch => serializer.serialize_str("web_search"),
1064 Self::ImageSearch => serializer.serialize_str("image_search"),
1065 Self::EnterpriseWebSearch => serializer.serialize_str("enterprise_web_search"),
1066 Self::Unknown { search_type, .. } => serializer.serialize_str(search_type),
1067 }
1068 }
1069}
1070
1071impl<'de> Deserialize<'de> for SearchType {
1072 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1073 where
1074 D: serde::Deserializer<'de>,
1075 {
1076 let value = serde_json::Value::deserialize(deserializer)?;
1077 match value.as_str() {
1078 Some("web_search") => Ok(Self::WebSearch),
1079 Some("image_search") => Ok(Self::ImageSearch),
1080 Some("enterprise_web_search") => Ok(Self::EnterpriseWebSearch),
1081 Some(other) => {
1082 tracing::warn!(
1083 "Encountered unknown SearchType '{}'. \
1084 Preserving in Unknown variant.",
1085 other
1086 );
1087 Ok(Self::Unknown {
1088 search_type: other.to_string(),
1089 data: value,
1090 })
1091 }
1092 None => {
1093 let search_type = format!("<non-string: {}>", value);
1094 tracing::warn!(
1095 "SearchType received non-string value: {}. \
1096 Preserving in Unknown variant.",
1097 value
1098 );
1099 Ok(Self::Unknown {
1100 search_type,
1101 data: value,
1102 })
1103 }
1104 }
1105 }
1106}
1107
1108#[derive(Clone, Debug, PartialEq)]
1122#[non_exhaustive]
1123pub enum RetrievalType {
1124 VertexAiSearch,
1126 RagStore,
1128 ExaAiSearch,
1130 ParallelAiSearch,
1132 Unknown {
1134 retrieval_type: String,
1136 data: serde_json::Value,
1138 },
1139}
1140
1141impl RetrievalType {
1142 #[must_use]
1144 pub const fn is_unknown(&self) -> bool {
1145 matches!(self, Self::Unknown { .. })
1146 }
1147
1148 #[must_use]
1150 pub fn unknown_retrieval_type(&self) -> Option<&str> {
1151 match self {
1152 Self::Unknown { retrieval_type, .. } => Some(retrieval_type),
1153 _ => None,
1154 }
1155 }
1156
1157 #[must_use]
1159 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
1160 match self {
1161 Self::Unknown { data, .. } => Some(data),
1162 _ => None,
1163 }
1164 }
1165}
1166
1167impl Serialize for RetrievalType {
1168 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1169 where
1170 S: serde::Serializer,
1171 {
1172 match self {
1173 Self::VertexAiSearch => serializer.serialize_str("vertex_ai_search"),
1174 Self::RagStore => serializer.serialize_str("rag_store"),
1175 Self::ExaAiSearch => serializer.serialize_str("exa_ai_search"),
1176 Self::ParallelAiSearch => serializer.serialize_str("parallel_ai_search"),
1177 Self::Unknown { retrieval_type, .. } => serializer.serialize_str(retrieval_type),
1178 }
1179 }
1180}
1181
1182impl<'de> Deserialize<'de> for RetrievalType {
1183 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1184 where
1185 D: serde::Deserializer<'de>,
1186 {
1187 let value = serde_json::Value::deserialize(deserializer)?;
1188 match value.as_str() {
1189 Some("vertex_ai_search") => Ok(Self::VertexAiSearch),
1190 Some("rag_store") => Ok(Self::RagStore),
1191 Some("exa_ai_search") => Ok(Self::ExaAiSearch),
1192 Some("parallel_ai_search") => Ok(Self::ParallelAiSearch),
1193 Some(other) => {
1194 tracing::warn!(
1195 "Encountered unknown RetrievalType '{}'. \
1196 Preserving in Unknown variant.",
1197 other
1198 );
1199 Ok(Self::Unknown {
1200 retrieval_type: other.to_string(),
1201 data: value,
1202 })
1203 }
1204 None => {
1205 let retrieval_type = format!("<non-string: {}>", value);
1206 tracing::warn!(
1207 "RetrievalType received non-string value: {}. \
1208 Preserving in Unknown variant.",
1209 value
1210 );
1211 Ok(Self::Unknown {
1212 retrieval_type,
1213 data: value,
1214 })
1215 }
1216 }
1217 }
1218}
1219
1220#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1222#[serde(default)]
1223pub struct VertexAiSearchConfig {
1224 #[serde(skip_serializing_if = "Option::is_none")]
1226 pub engine: Option<String>,
1227 #[serde(skip_serializing_if = "Option::is_none")]
1229 pub datastores: Option<Vec<String>>,
1230}
1231
1232impl VertexAiSearchConfig {
1233 #[must_use]
1235 pub fn new() -> Self {
1236 Self::default()
1237 }
1238
1239 #[must_use]
1241 pub fn with_engine(mut self, engine: impl Into<String>) -> Self {
1242 self.engine = Some(engine.into());
1243 self
1244 }
1245
1246 #[must_use]
1248 pub fn with_datastores(mut self, datastores: Vec<String>) -> Self {
1249 self.datastores = Some(datastores);
1250 self
1251 }
1252}
1253
1254#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1259#[serde(default)]
1260pub struct ExaAiSearchConfig {
1261 pub api_key: String,
1263 #[serde(skip_serializing_if = "Option::is_none")]
1265 pub custom_config: Option<serde_json::Value>,
1266}
1267
1268impl ExaAiSearchConfig {
1269 #[must_use]
1271 pub fn new(api_key: impl Into<String>) -> Self {
1272 Self {
1273 api_key: api_key.into(),
1274 custom_config: None,
1275 }
1276 }
1277
1278 #[must_use]
1280 pub fn with_custom_config(mut self, custom_config: serde_json::Value) -> Self {
1281 self.custom_config = Some(custom_config);
1282 self
1283 }
1284}
1285
1286#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1291#[serde(default)]
1292pub struct ParallelAiSearchConfig {
1293 #[serde(skip_serializing_if = "Option::is_none")]
1295 pub api_key: Option<String>,
1296 #[serde(skip_serializing_if = "Option::is_none")]
1298 pub custom_config: Option<serde_json::Value>,
1299}
1300
1301impl ParallelAiSearchConfig {
1302 #[must_use]
1304 pub fn new() -> Self {
1305 Self::default()
1306 }
1307
1308 #[must_use]
1310 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
1311 self.api_key = Some(api_key.into());
1312 self
1313 }
1314
1315 #[must_use]
1317 pub fn with_custom_config(mut self, custom_config: serde_json::Value) -> Self {
1318 self.custom_config = Some(custom_config);
1319 self
1320 }
1321}
1322
1323#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1325#[serde(default)]
1326pub struct RagResource {
1327 #[serde(skip_serializing_if = "Option::is_none")]
1329 pub rag_corpus: Option<String>,
1330 #[serde(skip_serializing_if = "Option::is_none")]
1332 pub rag_file_ids: Option<Vec<String>>,
1333}
1334
1335impl RagResource {
1336 #[must_use]
1338 pub fn new(rag_corpus: impl Into<String>) -> Self {
1339 Self {
1340 rag_corpus: Some(rag_corpus.into()),
1341 rag_file_ids: None,
1342 }
1343 }
1344
1345 #[must_use]
1347 pub fn with_rag_file_ids(mut self, rag_file_ids: Vec<String>) -> Self {
1348 self.rag_file_ids = Some(rag_file_ids);
1349 self
1350 }
1351}
1352
1353#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1355#[serde(default)]
1356pub struct HybridSearchConfig {
1357 #[serde(skip_serializing_if = "Option::is_none")]
1360 pub alpha: Option<f32>,
1361}
1362
1363#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1365#[serde(default)]
1366pub struct RagFilter {
1367 #[serde(skip_serializing_if = "Option::is_none")]
1369 pub vector_distance_threshold: Option<f64>,
1370 #[serde(skip_serializing_if = "Option::is_none")]
1372 pub vector_similarity_threshold: Option<f64>,
1373 #[serde(skip_serializing_if = "Option::is_none")]
1375 pub metadata_filter: Option<String>,
1376}
1377
1378#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1385pub struct RagRanking {
1386 #[serde(default = "RagRanking::default_ranking_config")]
1388 pub ranking_config: String,
1389 #[serde(skip_serializing_if = "Option::is_none", default)]
1391 pub model_name: Option<String>,
1392}
1393
1394impl RagRanking {
1395 fn default_ranking_config() -> String {
1396 "rank_service".to_string()
1397 }
1398
1399 #[must_use]
1401 pub fn rank_service() -> Self {
1402 Self::default()
1403 }
1404
1405 #[must_use]
1407 pub fn with_model_name(mut self, model_name: impl Into<String>) -> Self {
1408 self.model_name = Some(model_name.into());
1409 self
1410 }
1411}
1412
1413impl Default for RagRanking {
1414 fn default() -> Self {
1415 Self {
1416 ranking_config: Self::default_ranking_config(),
1417 model_name: None,
1418 }
1419 }
1420}
1421
1422#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1425#[serde(default)]
1426pub struct RagRetrievalConfig {
1427 #[serde(skip_serializing_if = "Option::is_none")]
1429 pub top_k: Option<i32>,
1430 #[serde(skip_serializing_if = "Option::is_none")]
1432 pub hybrid_search: Option<HybridSearchConfig>,
1433 #[serde(rename = "filter", skip_serializing_if = "Option::is_none")]
1435 pub filter: Option<RagFilter>,
1436 #[serde(skip_serializing_if = "Option::is_none")]
1438 pub ranking: Option<RagRanking>,
1439}
1440
1441impl RagRetrievalConfig {
1442 #[must_use]
1444 pub fn new() -> Self {
1445 Self::default()
1446 }
1447
1448 #[must_use]
1450 pub fn with_top_k(mut self, top_k: i32) -> Self {
1451 self.top_k = Some(top_k);
1452 self
1453 }
1454
1455 #[must_use]
1457 pub fn with_hybrid_search_alpha(mut self, alpha: f32) -> Self {
1458 self.hybrid_search = Some(HybridSearchConfig { alpha: Some(alpha) });
1459 self
1460 }
1461
1462 #[must_use]
1464 pub fn with_filter(mut self, filter: RagFilter) -> Self {
1465 self.filter = Some(filter);
1466 self
1467 }
1468
1469 #[must_use]
1471 pub fn with_ranking(mut self, ranking: RagRanking) -> Self {
1472 self.ranking = Some(ranking);
1473 self
1474 }
1475}
1476
1477#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1479#[serde(default)]
1480pub struct RagStoreConfig {
1481 #[serde(skip_serializing_if = "Option::is_none")]
1483 pub rag_resources: Option<Vec<RagResource>>,
1484 #[serde(skip_serializing_if = "Option::is_none")]
1489 pub similarity_top_k: Option<i32>,
1490 #[serde(skip_serializing_if = "Option::is_none")]
1496 pub vector_distance_threshold: Option<f64>,
1497 #[serde(skip_serializing_if = "Option::is_none")]
1499 pub rag_retrieval_config: Option<RagRetrievalConfig>,
1500}
1501
1502impl RagStoreConfig {
1503 #[must_use]
1505 pub fn new(rag_resources: Vec<RagResource>) -> Self {
1506 Self {
1507 rag_resources: Some(rag_resources),
1508 ..Default::default()
1509 }
1510 }
1511
1512 #[must_use]
1514 pub fn with_rag_retrieval_config(mut self, config: RagRetrievalConfig) -> Self {
1515 self.rag_retrieval_config = Some(config);
1516 self
1517 }
1518}
1519
1520#[derive(Clone, Debug, Default)]
1540pub struct GoogleSearchConfig {
1541 search_types: Option<Vec<SearchType>>,
1542}
1543
1544impl GoogleSearchConfig {
1545 #[must_use]
1547 pub fn new() -> Self {
1548 Self::default()
1549 }
1550
1551 #[must_use]
1553 pub fn with_search_types(mut self, search_types: Vec<SearchType>) -> Self {
1554 self.search_types = Some(search_types);
1555 self
1556 }
1557}
1558
1559impl From<GoogleSearchConfig> for Tool {
1560 fn from(config: GoogleSearchConfig) -> Self {
1561 Tool::GoogleSearch {
1562 search_types: config.search_types,
1563 }
1564 }
1565}
1566
1567#[derive(Clone, Debug, Default)]
1581pub struct GoogleMapsConfig {
1582 enable_widget: Option<bool>,
1583 latitude: Option<f64>,
1584 longitude: Option<f64>,
1585}
1586
1587impl GoogleMapsConfig {
1588 #[must_use]
1590 pub fn new() -> Self {
1591 Self::default()
1592 }
1593
1594 #[must_use]
1596 pub fn with_widget(mut self) -> Self {
1597 self.enable_widget = Some(true);
1598 self
1599 }
1600
1601 #[must_use]
1603 pub fn with_location(mut self, latitude: f64, longitude: f64) -> Self {
1604 self.latitude = Some(latitude);
1605 self.longitude = Some(longitude);
1606 self
1607 }
1608}
1609
1610impl From<GoogleMapsConfig> for Tool {
1611 fn from(config: GoogleMapsConfig) -> Self {
1612 Tool::GoogleMaps {
1613 enable_widget: config.enable_widget,
1614 latitude: config.latitude,
1615 longitude: config.longitude,
1616 }
1617 }
1618}
1619
1620#[derive(Clone, Debug)]
1635pub struct McpServerConfig {
1636 name: String,
1637 url: String,
1638 allowed_tools: Option<Vec<AllowedTools>>,
1639 headers: Option<HashMap<String, String>>,
1640}
1641
1642impl McpServerConfig {
1643 #[must_use]
1645 pub fn new(name: impl Into<String>, url: impl Into<String>) -> Self {
1646 Self {
1647 name: name.into(),
1648 url: url.into(),
1649 allowed_tools: None,
1650 headers: None,
1651 }
1652 }
1653
1654 #[must_use]
1659 pub fn with_allowed_tools(mut self, allowed_tools: Vec<String>) -> Self {
1660 self.allowed_tools = Some(vec![AllowedTools::new(allowed_tools)]);
1661 self
1662 }
1663
1664 #[must_use]
1666 pub fn with_allowed_tools_config(mut self, allowed_tools: Vec<AllowedTools>) -> Self {
1667 self.allowed_tools = Some(allowed_tools);
1668 self
1669 }
1670
1671 #[must_use]
1673 pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
1674 self.headers = Some(headers);
1675 self
1676 }
1677}
1678
1679impl From<McpServerConfig> for Tool {
1680 fn from(config: McpServerConfig) -> Self {
1681 Tool::McpServer {
1682 name: config.name,
1683 url: config.url,
1684 allowed_tools: config.allowed_tools,
1685 headers: config.headers,
1686 }
1687 }
1688}
1689
1690#[derive(Clone, Debug)]
1706pub struct ComputerUseConfig {
1707 environment: String,
1708 excluded_predefined_functions: Vec<String>,
1709 enable_prompt_injection_detection: Option<bool>,
1710 disabled_safety_policies: Vec<String>,
1711}
1712
1713impl ComputerUseConfig {
1714 #[must_use]
1716 pub fn new() -> Self {
1717 Self {
1718 environment: "browser".to_string(),
1719 excluded_predefined_functions: Vec::new(),
1720 enable_prompt_injection_detection: None,
1721 disabled_safety_policies: Vec::new(),
1722 }
1723 }
1724
1725 #[must_use]
1728 pub fn with_environment(mut self, environment: impl Into<String>) -> Self {
1729 self.environment = environment.into();
1730 self
1731 }
1732
1733 #[must_use]
1735 pub fn excluding(mut self, functions: Vec<String>) -> Self {
1736 self.excluded_predefined_functions = functions;
1737 self
1738 }
1739
1740 #[must_use]
1742 pub fn with_prompt_injection_detection(mut self, enabled: bool) -> Self {
1743 self.enable_prompt_injection_detection = Some(enabled);
1744 self
1745 }
1746
1747 #[must_use]
1754 pub fn disabling_safety_policies(mut self, policies: Vec<String>) -> Self {
1755 self.disabled_safety_policies = policies;
1756 self
1757 }
1758}
1759
1760impl Default for ComputerUseConfig {
1761 fn default() -> Self {
1762 Self::new()
1763 }
1764}
1765
1766impl From<ComputerUseConfig> for Tool {
1767 fn from(config: ComputerUseConfig) -> Self {
1768 Tool::ComputerUse {
1769 environment: config.environment,
1770 excluded_predefined_functions: config.excluded_predefined_functions,
1771 enable_prompt_injection_detection: config.enable_prompt_injection_detection,
1772 disabled_safety_policies: config.disabled_safety_policies,
1773 }
1774 }
1775}
1776
1777#[derive(Clone, Debug)]
1789pub struct FileSearchConfig {
1790 store_names: Vec<String>,
1791 top_k: Option<i32>,
1792 metadata_filter: Option<String>,
1793}
1794
1795impl FileSearchConfig {
1796 #[must_use]
1798 pub fn new(store_names: Vec<String>) -> Self {
1799 Self {
1800 store_names,
1801 top_k: None,
1802 metadata_filter: None,
1803 }
1804 }
1805
1806 #[must_use]
1808 pub fn with_top_k(mut self, top_k: i32) -> Self {
1809 self.top_k = Some(top_k);
1810 self
1811 }
1812
1813 #[must_use]
1815 pub fn with_metadata_filter(mut self, filter: impl Into<String>) -> Self {
1816 self.metadata_filter = Some(filter.into());
1817 self
1818 }
1819}
1820
1821impl From<FileSearchConfig> for Tool {
1822 fn from(config: FileSearchConfig) -> Self {
1823 Tool::FileSearch {
1824 store_names: config.store_names,
1825 top_k: config.top_k,
1826 metadata_filter: config.metadata_filter,
1827 }
1828 }
1829}
1830
1831#[derive(Clone, Debug, Default)]
1853pub struct RetrievalConfig {
1854 retrieval_types: Vec<RetrievalType>,
1855 vertex_ai_search_config: Option<VertexAiSearchConfig>,
1856 exa_ai_search_config: Option<ExaAiSearchConfig>,
1857 parallel_ai_search_config: Option<ParallelAiSearchConfig>,
1858 rag_store_config: Option<Box<RagStoreConfig>>,
1859}
1860
1861impl RetrievalConfig {
1862 #[must_use]
1864 pub fn new() -> Self {
1865 Self::default()
1866 }
1867
1868 fn enable(&mut self, retrieval_type: RetrievalType) {
1869 if !self.retrieval_types.contains(&retrieval_type) {
1870 self.retrieval_types.push(retrieval_type);
1871 }
1872 }
1873
1874 #[must_use]
1876 pub fn with_vertex_ai_search(mut self, config: VertexAiSearchConfig) -> Self {
1877 self.enable(RetrievalType::VertexAiSearch);
1878 self.vertex_ai_search_config = Some(config);
1879 self
1880 }
1881
1882 #[must_use]
1884 pub fn with_exa_ai_search(mut self, config: ExaAiSearchConfig) -> Self {
1885 self.enable(RetrievalType::ExaAiSearch);
1886 self.exa_ai_search_config = Some(config);
1887 self
1888 }
1889
1890 #[must_use]
1892 pub fn with_parallel_ai_search(mut self, config: ParallelAiSearchConfig) -> Self {
1893 self.enable(RetrievalType::ParallelAiSearch);
1894 self.parallel_ai_search_config = Some(config);
1895 self
1896 }
1897
1898 #[must_use]
1900 pub fn with_rag_store(mut self, config: RagStoreConfig) -> Self {
1901 self.enable(RetrievalType::RagStore);
1902 self.rag_store_config = Some(Box::new(config));
1903 self
1904 }
1905
1906 #[must_use]
1910 pub fn with_retrieval_types(mut self, retrieval_types: Vec<RetrievalType>) -> Self {
1911 self.retrieval_types = retrieval_types;
1912 self
1913 }
1914}
1915
1916impl From<RetrievalConfig> for Tool {
1917 fn from(config: RetrievalConfig) -> Self {
1918 Tool::Retrieval {
1919 retrieval_types: if config.retrieval_types.is_empty() {
1920 None
1921 } else {
1922 Some(config.retrieval_types)
1923 },
1924 vertex_ai_search_config: config.vertex_ai_search_config,
1925 exa_ai_search_config: config.exa_ai_search_config,
1926 parallel_ai_search_config: config.parallel_ai_search_config,
1927 rag_store_config: config.rag_store_config,
1928 }
1929 }
1930}
1931
1932#[cfg(test)]
1933mod tests {
1934 use super::*;
1935 use serde_json;
1936
1937 #[test]
1938 fn test_serialize_function_declaration() {
1939 let function = FunctionDeclaration::builder("get_weather")
1940 .description("Get the current weather in a given location")
1941 .parameter(
1942 "location",
1943 serde_json::json!({
1944 "type": "string",
1945 "description": "The city and state, e.g. San Francisco, CA"
1946 }),
1947 )
1948 .required(vec!["location".to_string()])
1949 .build();
1950
1951 let json_string = serde_json::to_string(&function).expect("Serialization failed");
1952 let parsed: FunctionDeclaration =
1953 serde_json::from_str(&json_string).expect("Deserialization failed");
1954
1955 assert_eq!(parsed.name(), "get_weather");
1956 assert_eq!(
1957 parsed.description(),
1958 "Get the current weather in a given location"
1959 );
1960 }
1961
1962 #[test]
1963 fn test_function_calling_mode_serialization() {
1964 let test_cases = [
1966 (FunctionCallingMode::Auto, "\"auto\""),
1967 (FunctionCallingMode::Any, "\"any\""),
1968 (FunctionCallingMode::None, "\"none\""),
1969 (FunctionCallingMode::Validated, "\"validated\""),
1970 ];
1971
1972 for (mode, expected_json) in test_cases {
1973 let json = serde_json::to_string(&mode).expect("Serialization failed");
1974 assert_eq!(json, expected_json);
1975
1976 let parsed: FunctionCallingMode =
1977 serde_json::from_str(&json).expect("Deserialization failed");
1978 assert_eq!(parsed, mode);
1979 }
1980
1981 for (raw, expected) in [
1983 ("\"AUTO\"", FunctionCallingMode::Auto),
1984 ("\"VALIDATED\"", FunctionCallingMode::Validated),
1985 ] {
1986 let parsed: FunctionCallingMode =
1987 serde_json::from_str(raw).expect("Deserialization failed");
1988 assert_eq!(parsed, expected);
1989 }
1990 }
1991
1992 #[test]
1993 fn test_function_calling_mode_unknown_roundtrip() {
1994 let json = "\"FUTURE_MODE\"";
1996 let parsed: FunctionCallingMode =
1997 serde_json::from_str(json).expect("Deserialization failed");
1998
1999 assert!(parsed.is_unknown());
2000 assert_eq!(parsed.unknown_mode_type(), Some("FUTURE_MODE"));
2001
2002 let reserialized = serde_json::to_string(&parsed).expect("Serialization failed");
2004 assert_eq!(reserialized, json);
2005 }
2006
2007 #[test]
2008 fn test_function_calling_mode_helper_methods() {
2009 assert!(!FunctionCallingMode::Auto.is_unknown());
2011 assert!(!FunctionCallingMode::Any.is_unknown());
2012 assert!(!FunctionCallingMode::None.is_unknown());
2013 assert!(!FunctionCallingMode::Validated.is_unknown());
2014
2015 assert!(FunctionCallingMode::Auto.unknown_mode_type().is_none());
2016 assert!(FunctionCallingMode::Auto.unknown_data().is_none());
2017
2018 let unknown = FunctionCallingMode::Unknown {
2020 mode_type: "NEW_MODE".to_string(),
2021 data: serde_json::json!("NEW_MODE"),
2022 };
2023 assert!(unknown.is_unknown());
2024 assert_eq!(unknown.unknown_mode_type(), Some("NEW_MODE"));
2025 assert!(unknown.unknown_data().is_some());
2026 }
2027
2028 #[test]
2029 fn test_function_calling_mode_non_string_value() {
2030 let json = "123";
2032 let parsed: FunctionCallingMode =
2033 serde_json::from_str(json).expect("Deserialization should succeed");
2034
2035 assert!(parsed.is_unknown());
2036 assert!(parsed.unknown_mode_type().unwrap().contains("<non-string:"));
2038 }
2039
2040 #[test]
2041 fn test_tool_google_search_roundtrip() {
2042 let tool = Tool::GoogleSearch { search_types: None };
2043 let json = serde_json::to_string(&tool).expect("Serialization failed");
2044 assert!(json.contains("\"type\":\"google_search\""));
2045 assert!(!json.contains("search_types"));
2046
2047 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2048 assert!(matches!(parsed, Tool::GoogleSearch { .. }));
2049 }
2050
2051 #[test]
2052 fn test_tool_google_search_with_search_types_roundtrip() {
2053 let tool = Tool::GoogleSearch {
2054 search_types: Some(vec![SearchType::WebSearch, SearchType::ImageSearch]),
2055 };
2056 let json = serde_json::to_string(&tool).expect("Serialization failed");
2057 assert!(json.contains("\"search_types\""));
2058 assert!(json.contains("\"web_search\""));
2059 assert!(json.contains("\"image_search\""));
2060
2061 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2062 match parsed {
2063 Tool::GoogleSearch { search_types } => {
2064 let types = search_types.expect("Should have search_types");
2065 assert_eq!(types.len(), 2);
2066 assert_eq!(types[0], SearchType::WebSearch);
2067 assert_eq!(types[1], SearchType::ImageSearch);
2068 }
2069 other => panic!("Expected GoogleSearch variant, got {:?}", other),
2070 }
2071 }
2072
2073 #[test]
2074 fn test_tool_google_maps_roundtrip() {
2075 let tool = Tool::GoogleMaps {
2076 enable_widget: None,
2077 latitude: None,
2078 longitude: None,
2079 };
2080 let json = serde_json::to_string(&tool).expect("Serialization failed");
2081 assert!(json.contains("\"type\":\"google_maps\""));
2082 assert!(!json.contains("enable_widget"));
2083
2084 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2085 match parsed {
2086 Tool::GoogleMaps { enable_widget, .. } => assert_eq!(enable_widget, None),
2087 other => panic!("Expected GoogleMaps variant, got {:?}", other),
2088 }
2089 }
2090
2091 #[test]
2092 fn test_tool_google_maps_with_widget_roundtrip() {
2093 let tool = Tool::GoogleMaps {
2094 enable_widget: Some(true),
2095 latitude: Some(40.758),
2096 longitude: Some(-73.9855),
2097 };
2098 let json = serde_json::to_string(&tool).expect("Serialization failed");
2099 assert!(json.contains("\"enable_widget\":true"));
2100 assert!(json.contains("\"latitude\":40.758"));
2101 assert!(json.contains("\"longitude\":-73.9855"));
2102
2103 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2104 match parsed {
2105 Tool::GoogleMaps {
2106 enable_widget,
2107 latitude,
2108 longitude,
2109 } => {
2110 assert_eq!(enable_widget, Some(true));
2111 assert_eq!(latitude, Some(40.758));
2112 assert_eq!(longitude, Some(-73.9855));
2113 }
2114 other => panic!("Expected GoogleMaps variant, got {:?}", other),
2115 }
2116 }
2117
2118 #[test]
2119 fn test_tool_function_roundtrip() {
2120 let tool = Tool::Function {
2121 name: "get_weather".to_string(),
2122 description: "Get weather".to_string(),
2123 parameters: FunctionParameters::new(
2124 "object".to_string(),
2125 serde_json::json!({}),
2126 vec![],
2127 ),
2128 };
2129 let json = serde_json::to_string(&tool).expect("Serialization failed");
2130 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2131
2132 match parsed {
2133 Tool::Function { name, .. } => assert_eq!(name, "get_weather"),
2134 other => panic!("Expected Function variant, got {:?}", other),
2135 }
2136 }
2137
2138 #[test]
2139 fn test_tool_mcp_server_roundtrip() {
2140 let tool = Tool::McpServer {
2141 name: "my-server".to_string(),
2142 url: "https://mcp.example.com/api".to_string(),
2143 allowed_tools: None,
2144 headers: None,
2145 };
2146 let json = serde_json::to_string(&tool).expect("Serialization failed");
2147 assert!(json.contains("\"type\":\"mcp_server\""));
2148 assert!(json.contains("\"name\":\"my-server\""));
2149 assert!(json.contains("\"url\":\"https://mcp.example.com/api\""));
2150 assert!(!json.contains("allowed_tools"));
2151 assert!(!json.contains("headers"));
2152
2153 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2154 match parsed {
2155 Tool::McpServer {
2156 name,
2157 url,
2158 allowed_tools,
2159 headers,
2160 } => {
2161 assert_eq!(name, "my-server");
2162 assert_eq!(url, "https://mcp.example.com/api");
2163 assert_eq!(allowed_tools, None);
2164 assert_eq!(headers, None);
2165 }
2166 other => panic!("Expected McpServer variant, got {:?}", other),
2167 }
2168 }
2169
2170 #[test]
2171 fn test_tool_mcp_server_with_optional_fields_roundtrip() {
2172 let tool = Tool::McpServer {
2173 name: "my-server".to_string(),
2174 url: "https://mcp.example.com/api".to_string(),
2175 allowed_tools: Some(vec![
2176 AllowedTools::new(vec!["read_file".to_string(), "list_dir".to_string()])
2177 .with_mode(FunctionCallingMode::Auto),
2178 ]),
2179 headers: Some(HashMap::from([(
2180 "Authorization".to_string(),
2181 "Bearer token".to_string(),
2182 )])),
2183 };
2184 let json = serde_json::to_string(&tool).expect("Serialization failed");
2185 assert!(json.contains("\"allowed_tools\""));
2186 assert!(json.contains("\"headers\""));
2187
2188 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2189 match parsed {
2190 Tool::McpServer {
2191 allowed_tools,
2192 headers,
2193 ..
2194 } => {
2195 let tools = allowed_tools.expect("Should have allowed_tools");
2196 assert_eq!(tools.len(), 1);
2197 assert_eq!(tools[0].tools.len(), 2);
2198 assert_eq!(tools[0].mode, Some(FunctionCallingMode::Auto));
2199 let hdrs = headers.expect("Should have headers");
2200 assert_eq!(hdrs.get("Authorization").unwrap(), "Bearer token");
2201 }
2202 other => panic!("Expected McpServer variant, got {:?}", other),
2203 }
2204 }
2205
2206 #[test]
2207 fn test_tool_unknown_deserialization() {
2208 let json = r#"{"type": "future_tool", "some_field": "value", "number": 42}"#;
2210 let parsed: Tool = serde_json::from_str(json).expect("Deserialization failed");
2211
2212 match parsed {
2213 Tool::Unknown { tool_type, data } => {
2214 assert_eq!(tool_type, "future_tool");
2215 assert_eq!(data.get("some_field").unwrap(), "value");
2216 assert_eq!(data.get("number").unwrap(), 42);
2217 }
2218 _ => panic!("Expected Unknown variant"),
2219 }
2220 }
2221
2222 #[test]
2223 fn test_tool_unknown_roundtrip() {
2224 let tool = Tool::Unknown {
2225 tool_type: "new_tool".to_string(),
2226 data: serde_json::json!({"type": "new_tool", "config": {"enabled": true}}),
2227 };
2228 let json = serde_json::to_string(&tool).expect("Serialization failed");
2229
2230 assert!(json.contains("\"type\":\"new_tool\""));
2232 assert!(json.contains("\"config\""));
2233
2234 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2235 match parsed {
2236 Tool::Unknown { tool_type, .. } => assert_eq!(tool_type, "new_tool"),
2237 _ => panic!("Expected Unknown variant"),
2238 }
2239 }
2240
2241 #[test]
2242 fn test_tool_unknown_helper_methods() {
2243 let unknown_tool = Tool::Unknown {
2245 tool_type: "future_tool".to_string(),
2246 data: serde_json::json!({"type": "future_tool", "setting": 123}),
2247 };
2248
2249 assert!(unknown_tool.is_unknown());
2250 assert_eq!(unknown_tool.unknown_tool_type(), Some("future_tool"));
2251 let data = unknown_tool.unknown_data().expect("Should have data");
2252 assert_eq!(data.get("setting").unwrap(), 123);
2253 }
2254
2255 #[test]
2256 fn test_tool_computer_use_roundtrip() {
2257 let tool = Tool::ComputerUse {
2258 environment: "browser".to_string(),
2259 excluded_predefined_functions: vec!["submit_form".to_string(), "download".to_string()],
2260 enable_prompt_injection_detection: Some(true),
2261 disabled_safety_policies: vec!["data_modification".to_string()],
2262 };
2263 let json = serde_json::to_string(&tool).expect("Serialization failed");
2264 assert!(json.contains("\"type\":\"computer_use\""));
2265 assert!(json.contains("\"environment\":\"browser\""));
2266 assert!(json.contains("\"excluded_predefined_functions\""));
2268 assert!(!json.contains("excludedPredefinedFunctions"));
2269 assert!(json.contains("\"enable_prompt_injection_detection\":true"));
2270 assert!(json.contains("\"disabled_safety_policies\""));
2271
2272 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2273 match parsed {
2274 Tool::ComputerUse {
2275 environment,
2276 excluded_predefined_functions,
2277 enable_prompt_injection_detection,
2278 disabled_safety_policies,
2279 } => {
2280 assert_eq!(environment, "browser");
2281 assert_eq!(excluded_predefined_functions.len(), 2);
2282 assert!(excluded_predefined_functions.contains(&"submit_form".to_string()));
2283 assert_eq!(enable_prompt_injection_detection, Some(true));
2284 assert_eq!(
2285 disabled_safety_policies,
2286 vec!["data_modification".to_string()]
2287 );
2288 }
2289 other => panic!("Expected ComputerUse variant, got {:?}", other),
2290 }
2291 }
2292
2293 #[test]
2294 fn test_tool_computer_use_legacy_camel_case_accepted() {
2295 let json = r#"{"type":"computer_use","environment":"browser","excludedPredefinedFunctions":["a"]}"#;
2297 let parsed: Tool = serde_json::from_str(json).expect("Deserialization failed");
2298 match parsed {
2299 Tool::ComputerUse {
2300 excluded_predefined_functions,
2301 ..
2302 } => assert_eq!(excluded_predefined_functions, vec!["a".to_string()]),
2303 other => panic!("Expected ComputerUse variant, got {:?}", other),
2304 }
2305 }
2306
2307 #[test]
2308 fn test_tool_computer_use_empty_exclusions() {
2309 let tool = Tool::ComputerUse {
2311 environment: "browser".to_string(),
2312 excluded_predefined_functions: vec![],
2313 enable_prompt_injection_detection: None,
2314 disabled_safety_policies: vec![],
2315 };
2316 let json = serde_json::to_string(&tool).expect("Serialization failed");
2317 assert!(json.contains("\"type\":\"computer_use\""));
2318 assert!(json.contains("\"environment\":\"browser\""));
2319 assert!(!json.contains("excluded_predefined_functions"));
2320
2321 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2322 match parsed {
2323 Tool::ComputerUse {
2324 excluded_predefined_functions,
2325 ..
2326 } => {
2327 assert!(excluded_predefined_functions.is_empty());
2328 }
2329 other => panic!("Expected ComputerUse variant, got {:?}", other),
2330 }
2331 }
2332
2333 #[test]
2334 fn test_tool_known_types_helper_methods() {
2335 let google_search = Tool::GoogleSearch { search_types: None };
2337 assert!(!google_search.is_unknown());
2338 assert_eq!(google_search.unknown_tool_type(), None);
2339 assert_eq!(google_search.unknown_data(), None);
2340
2341 let google_maps = Tool::GoogleMaps {
2342 enable_widget: None,
2343 latitude: None,
2344 longitude: None,
2345 };
2346 assert!(!google_maps.is_unknown());
2347 assert_eq!(google_maps.unknown_tool_type(), None);
2348 assert_eq!(google_maps.unknown_data(), None);
2349
2350 let code_execution = Tool::CodeExecution;
2351 assert!(!code_execution.is_unknown());
2352 assert_eq!(code_execution.unknown_tool_type(), None);
2353 assert_eq!(code_execution.unknown_data(), None);
2354
2355 let url_context = Tool::UrlContext;
2356 assert!(!url_context.is_unknown());
2357 assert_eq!(url_context.unknown_tool_type(), None);
2358 assert_eq!(url_context.unknown_data(), None);
2359
2360 let computer_use = Tool::ComputerUse {
2361 environment: "browser".to_string(),
2362 excluded_predefined_functions: vec![],
2363 enable_prompt_injection_detection: None,
2364 disabled_safety_policies: vec![],
2365 };
2366 assert!(!computer_use.is_unknown());
2367 assert_eq!(computer_use.unknown_tool_type(), None);
2368 assert_eq!(computer_use.unknown_data(), None);
2369
2370 let function = Tool::Function {
2371 name: "test".to_string(),
2372 description: "Test function".to_string(),
2373 parameters: FunctionParameters::new(
2374 "object".to_string(),
2375 serde_json::json!({}),
2376 vec![],
2377 ),
2378 };
2379 assert!(!function.is_unknown());
2380 assert_eq!(function.unknown_tool_type(), None);
2381 assert_eq!(function.unknown_data(), None);
2382 }
2383
2384 #[test]
2385 fn test_tool_file_search_roundtrip() {
2386 let tool = Tool::FileSearch {
2387 store_names: vec!["store1".to_string(), "store2".to_string()],
2388 top_k: Some(5),
2389 metadata_filter: Some("category:technical".to_string()),
2390 };
2391 let json = serde_json::to_string(&tool).expect("Serialization failed");
2392 assert!(json.contains("\"type\":\"file_search\""));
2393 assert!(json.contains("\"file_search_store_names\"")); assert!(json.contains("\"top_k\":5"));
2395 assert!(json.contains("\"metadata_filter\":\"category:technical\""));
2396
2397 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2398 match parsed {
2399 Tool::FileSearch {
2400 store_names,
2401 top_k,
2402 metadata_filter,
2403 } => {
2404 assert_eq!(store_names, vec!["store1", "store2"]);
2405 assert_eq!(top_k, Some(5));
2406 assert_eq!(metadata_filter, Some("category:technical".to_string()));
2407 }
2408 other => panic!("Expected FileSearch variant, got {:?}", other),
2409 }
2410 }
2411
2412 #[test]
2413 fn test_tool_file_search_minimal() {
2414 let tool = Tool::FileSearch {
2416 store_names: vec!["my-store".to_string()],
2417 top_k: None,
2418 metadata_filter: None,
2419 };
2420 let json = serde_json::to_string(&tool).expect("Serialization failed");
2421 assert!(json.contains("\"type\":\"file_search\""));
2422 assert!(json.contains("\"file_search_store_names\"")); assert!(!json.contains("\"top_k\""));
2425 assert!(!json.contains("\"metadata_filter\""));
2426
2427 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2428 match parsed {
2429 Tool::FileSearch {
2430 store_names,
2431 top_k,
2432 metadata_filter,
2433 } => {
2434 assert_eq!(store_names, vec!["my-store"]);
2435 assert_eq!(top_k, None);
2436 assert_eq!(metadata_filter, None);
2437 }
2438 other => panic!("Expected FileSearch variant, got {:?}", other),
2439 }
2440 }
2441
2442 #[test]
2443 fn test_tool_file_search_helper_methods() {
2444 let file_search = Tool::FileSearch {
2445 store_names: vec!["store".to_string()],
2446 top_k: None,
2447 metadata_filter: None,
2448 };
2449 assert!(!file_search.is_unknown());
2450 assert_eq!(file_search.unknown_tool_type(), None);
2451 assert_eq!(file_search.unknown_data(), None);
2452 }
2453
2454 #[test]
2455 fn test_search_type_roundtrip() {
2456 let types = vec![SearchType::WebSearch, SearchType::ImageSearch];
2457 let json = serde_json::to_string(&types).expect("Serialization failed");
2458 assert_eq!(json, r#"["web_search","image_search"]"#);
2459
2460 let parsed: Vec<SearchType> = serde_json::from_str(&json).expect("Deserialization failed");
2461 assert_eq!(parsed, types);
2462 }
2463
2464 #[test]
2465 fn test_search_type_unknown_roundtrip() {
2466 let json = r#""future_search""#;
2467 let parsed: SearchType = serde_json::from_str(json).expect("Deserialization failed");
2468 assert!(parsed.is_unknown());
2469 assert_eq!(parsed.unknown_search_type(), Some("future_search"));
2470 assert_eq!(
2471 parsed.unknown_data(),
2472 Some(&serde_json::Value::String("future_search".to_string()))
2473 );
2474
2475 let reserialized = serde_json::to_string(&parsed).expect("Serialization failed");
2476 assert_eq!(reserialized, json);
2477 }
2478
2479 #[test]
2480 fn test_google_search_config_into_tool() {
2481 let tool: Tool = GoogleSearchConfig::new().into();
2482 assert!(matches!(tool, Tool::GoogleSearch { search_types: None }));
2483
2484 let tool: Tool = GoogleSearchConfig::new()
2485 .with_search_types(vec![SearchType::ImageSearch])
2486 .into();
2487 match tool {
2488 Tool::GoogleSearch { search_types } => {
2489 let types = search_types.expect("Should have search_types");
2490 assert_eq!(types, vec![SearchType::ImageSearch]);
2491 }
2492 other => panic!("Expected GoogleSearch, got {:?}", other),
2493 }
2494 }
2495
2496 #[test]
2497 fn test_google_maps_config_into_tool() {
2498 let tool: Tool = GoogleMapsConfig::new().into();
2499 assert!(matches!(
2500 tool,
2501 Tool::GoogleMaps {
2502 enable_widget: None,
2503 ..
2504 }
2505 ));
2506
2507 let tool: Tool = GoogleMapsConfig::new().with_widget().into();
2508 assert!(matches!(
2509 tool,
2510 Tool::GoogleMaps {
2511 enable_widget: Some(true),
2512 ..
2513 }
2514 ));
2515 }
2516
2517 #[test]
2518 fn test_mcp_server_config_into_tool() {
2519 let tool: Tool = McpServerConfig::new("server", "https://example.com").into();
2520 match tool {
2521 Tool::McpServer {
2522 name,
2523 url,
2524 allowed_tools,
2525 headers,
2526 } => {
2527 assert_eq!(name, "server");
2528 assert_eq!(url, "https://example.com");
2529 assert_eq!(allowed_tools, None);
2530 assert_eq!(headers, None);
2531 }
2532 other => panic!("Expected McpServer, got {:?}", other),
2533 }
2534 }
2535
2536 #[test]
2537 fn test_computer_use_config_into_tool() {
2538 let tool: Tool = ComputerUseConfig::new().into();
2539 match tool {
2540 Tool::ComputerUse {
2541 environment,
2542 excluded_predefined_functions,
2543 ..
2544 } => {
2545 assert_eq!(environment, "browser");
2546 assert!(excluded_predefined_functions.is_empty());
2547 }
2548 other => panic!("Expected ComputerUse, got {:?}", other),
2549 }
2550
2551 let tool: Tool = ComputerUseConfig::new()
2552 .excluding(vec!["download".to_string()])
2553 .into();
2554 match tool {
2555 Tool::ComputerUse {
2556 excluded_predefined_functions,
2557 ..
2558 } => {
2559 assert_eq!(excluded_predefined_functions, vec!["download"]);
2560 }
2561 other => panic!("Expected ComputerUse, got {:?}", other),
2562 }
2563 }
2564
2565 #[test]
2566 fn test_file_search_config_into_tool() {
2567 let tool: Tool = FileSearchConfig::new(vec!["store".to_string()])
2568 .with_top_k(5)
2569 .with_metadata_filter("cat:tech")
2570 .into();
2571 match tool {
2572 Tool::FileSearch {
2573 store_names,
2574 top_k,
2575 metadata_filter,
2576 } => {
2577 assert_eq!(store_names, vec!["store"]);
2578 assert_eq!(top_k, Some(5));
2579 assert_eq!(metadata_filter, Some("cat:tech".to_string()));
2580 }
2581 other => panic!("Expected FileSearch, got {:?}", other),
2582 }
2583 }
2584
2585 #[test]
2586 fn test_retrieval_type_wire_roundtrip() {
2587 for (retrieval_type, wire) in [
2588 (RetrievalType::VertexAiSearch, "\"vertex_ai_search\""),
2589 (RetrievalType::RagStore, "\"rag_store\""),
2590 (RetrievalType::ExaAiSearch, "\"exa_ai_search\""),
2591 (RetrievalType::ParallelAiSearch, "\"parallel_ai_search\""),
2592 ] {
2593 assert_eq!(serde_json::to_string(&retrieval_type).unwrap(), wire);
2594 let parsed: RetrievalType = serde_json::from_str(wire).unwrap();
2595 assert_eq!(parsed, retrieval_type);
2596 }
2597 }
2598
2599 #[test]
2600 fn test_retrieval_type_unknown_roundtrip() {
2601 let unknown: RetrievalType = serde_json::from_str("\"bing_search\"").unwrap();
2602 assert!(unknown.is_unknown());
2603 assert_eq!(unknown.unknown_retrieval_type(), Some("bing_search"));
2604 assert!(unknown.unknown_data().is_some());
2605 assert_eq!(serde_json::to_string(&unknown).unwrap(), "\"bing_search\"");
2606
2607 assert!(!RetrievalType::RagStore.is_unknown());
2609 assert_eq!(RetrievalType::RagStore.unknown_retrieval_type(), None);
2610 assert_eq!(RetrievalType::RagStore.unknown_data(), None);
2611 }
2612
2613 #[test]
2614 fn test_tool_retrieval_vertex_ai_search_wire_shape() {
2615 let tool: Tool = RetrievalConfig::new()
2616 .with_vertex_ai_search(
2617 VertexAiSearchConfig::new()
2618 .with_engine("projects/p/locations/global/engines/e")
2619 .with_datastores(vec!["ds-1".to_string()]),
2620 )
2621 .into();
2622
2623 let value = serde_json::to_value(&tool).unwrap();
2624 assert_eq!(
2625 value,
2626 serde_json::json!({
2627 "type": "retrieval",
2628 "retrieval_types": ["vertex_ai_search"],
2629 "vertex_ai_search_config": {
2630 "engine": "projects/p/locations/global/engines/e",
2631 "datastores": ["ds-1"]
2632 }
2633 })
2634 );
2635 }
2636
2637 #[test]
2638 fn test_tool_retrieval_rag_store_wire_shape() {
2639 let tool: Tool = RetrievalConfig::new()
2640 .with_rag_store(
2641 RagStoreConfig::new(vec![
2642 RagResource::new("projects/p/locations/us/ragCorpora/c")
2643 .with_rag_file_ids(vec!["f1".to_string()]),
2644 ])
2645 .with_rag_retrieval_config(
2646 RagRetrievalConfig::new()
2647 .with_top_k(8)
2648 .with_hybrid_search_alpha(0.5)
2649 .with_filter(RagFilter {
2650 vector_distance_threshold: Some(0.7),
2651 vector_similarity_threshold: None,
2652 metadata_filter: Some("category = \"tech\"".to_string()),
2653 })
2654 .with_ranking(RagRanking::rank_service().with_model_name("ranker-v2")),
2655 ),
2656 )
2657 .into();
2658
2659 let value = serde_json::to_value(&tool).unwrap();
2660 assert_eq!(value["type"], "retrieval");
2661 assert_eq!(value["retrieval_types"], serde_json::json!(["rag_store"]));
2662 let rag = &value["rag_store_config"];
2663 assert_eq!(
2664 rag["rag_resources"][0]["rag_corpus"],
2665 "projects/p/locations/us/ragCorpora/c"
2666 );
2667 assert_eq!(rag["rag_resources"][0]["rag_file_ids"][0], "f1");
2668 let retrieval = &rag["rag_retrieval_config"];
2669 assert_eq!(retrieval["top_k"], 8);
2670 assert_eq!(retrieval["hybrid_search"]["alpha"], 0.5);
2671 assert_eq!(retrieval["filter"]["vector_distance_threshold"], 0.7);
2673 assert_eq!(
2674 retrieval["filter"]["metadata_filter"],
2675 "category = \"tech\""
2676 );
2677 assert_eq!(retrieval["ranking"]["ranking_config"], "rank_service");
2678 assert_eq!(retrieval["ranking"]["model_name"], "ranker-v2");
2679 }
2680
2681 #[test]
2682 fn test_tool_retrieval_exa_and_parallel_wire_shape() {
2683 let tool: Tool = RetrievalConfig::new()
2684 .with_exa_ai_search(
2685 ExaAiSearchConfig::new("exa-key")
2686 .with_custom_config(serde_json::json!({"num_results": 5})),
2687 )
2688 .with_parallel_ai_search(ParallelAiSearchConfig::new().with_api_key("par-key"))
2689 .into();
2690
2691 let value = serde_json::to_value(&tool).unwrap();
2692 assert_eq!(
2693 value["retrieval_types"],
2694 serde_json::json!(["exa_ai_search", "parallel_ai_search"])
2695 );
2696 assert_eq!(value["exa_ai_search_config"]["api_key"], "exa-key");
2697 assert_eq!(
2698 value["exa_ai_search_config"]["custom_config"]["num_results"],
2699 5
2700 );
2701 assert_eq!(value["parallel_ai_search_config"]["api_key"], "par-key");
2702 }
2703
2704 #[test]
2705 fn test_tool_retrieval_roundtrip() {
2706 let tool: Tool = RetrievalConfig::new()
2707 .with_rag_store(RagStoreConfig::new(vec![RagResource::new("corpora/c")]))
2708 .with_vertex_ai_search(VertexAiSearchConfig::new().with_engine("engines/e"))
2709 .into();
2710
2711 let json = serde_json::to_string(&tool).expect("Serialization failed");
2712 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2713 match parsed {
2714 Tool::Retrieval {
2715 retrieval_types,
2716 vertex_ai_search_config,
2717 rag_store_config,
2718 exa_ai_search_config,
2719 parallel_ai_search_config,
2720 } => {
2721 assert_eq!(
2722 retrieval_types,
2723 Some(vec![RetrievalType::RagStore, RetrievalType::VertexAiSearch])
2724 );
2725 assert_eq!(
2726 vertex_ai_search_config.unwrap().engine.as_deref(),
2727 Some("engines/e")
2728 );
2729 assert!(rag_store_config.is_some());
2730 assert_eq!(exa_ai_search_config, None);
2731 assert_eq!(parallel_ai_search_config, None);
2732 }
2733 other => panic!("Expected Retrieval variant, got {:?}", other),
2734 }
2735 }
2736
2737 #[test]
2738 fn test_tool_retrieval_minimal_serializes_type_only() {
2739 let tool = Tool::Retrieval {
2740 retrieval_types: None,
2741 vertex_ai_search_config: None,
2742 exa_ai_search_config: None,
2743 parallel_ai_search_config: None,
2744 rag_store_config: None,
2745 };
2746 let json = serde_json::to_string(&tool).expect("Serialization failed");
2747 assert_eq!(json, r#"{"type":"retrieval"}"#);
2748
2749 let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
2750 assert!(matches!(parsed, Tool::Retrieval { .. }));
2751 assert!(!parsed.is_unknown());
2752 }
2753
2754 #[test]
2755 fn test_retrieval_config_unknown_types_escape_hatch() {
2756 let tool: Tool = RetrievalConfig::new()
2757 .with_retrieval_types(vec![RetrievalType::Unknown {
2758 retrieval_type: "future_backend".to_string(),
2759 data: serde_json::json!("future_backend"),
2760 }])
2761 .into();
2762 let value = serde_json::to_value(&tool).unwrap();
2763 assert_eq!(value["retrieval_types"][0], "future_backend");
2764 }
2765}