Skip to main content

genai_rs/
tools.rs

1// Shared types used by the Interactions API
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Represents a tool that can be used by the model (Interactions API format).
7///
8/// Tools in the Interactions API use a flat structure with the tool type and details
9/// at the top level, rather than nested in arrays.
10///
11/// # Forward Compatibility (Evergreen Philosophy)
12///
13/// This enum is marked `#[non_exhaustive]`, which means:
14/// - Match statements must include a wildcard arm (`_ => ...`)
15/// - New variants may be added in minor version updates without breaking your code
16///
17/// When the API returns a tool type that this library doesn't recognize, it will be
18/// captured as `Tool::Unknown` rather than causing a deserialization error.
19/// This follows the [Evergreen spec](https://github.com/google-deepmind/evergreen-spec)
20/// philosophy of graceful degradation.
21#[derive(Clone, Debug)]
22#[non_exhaustive]
23pub enum Tool {
24    /// A custom function that the model can call
25    Function {
26        name: String,
27        description: String,
28        parameters: FunctionParameters,
29    },
30    /// Built-in Google Search tool
31    ///
32    /// Optionally configure `search_types` to enable web and/or image search.
33    GoogleSearch {
34        /// Types of search to perform (e.g., web search, image search).
35        /// When `None`, the API defaults to web search only.
36        search_types: Option<Vec<SearchType>>,
37    },
38    /// Built-in Google Maps tool for location-grounded responses
39    GoogleMaps {
40        /// Whether to enable the widget context token in the response
41        enable_widget: Option<bool>,
42        /// Latitude bias for location grounding
43        latitude: Option<f64>,
44        /// Longitude bias for location grounding
45        longitude: Option<f64>,
46    },
47    /// Built-in code execution tool
48    CodeExecution,
49    /// Built-in URL context tool
50    UrlContext,
51    /// Built-in computer use tool for browser automation.
52    ///
53    /// **Security Warning**: This tool allows the model to interact with web browsers
54    /// on your behalf. Only use with trusted models and carefully review excluded functions.
55    ComputerUse {
56        /// The environment being operated. Known values: `browser`, `mobile`, `desktop`.
57        environment: String,
58        /// List of predefined functions to exclude from model access
59        /// (wire: `excluded_predefined_functions`).
60        excluded_predefined_functions: Vec<String>,
61        /// Whether to enable prompt injection detection for this request.
62        enable_prompt_injection_detection: Option<bool>,
63        /// Safety policies to disable. Known values include
64        /// `financial_transactions`, `sensitive_data_modification`,
65        /// `communication_tool`, `account_creation`, `data_modification`,
66        /// `user_consent_management`, `legal_terms_and_agreements`.
67        disabled_safety_policies: Vec<String>,
68    },
69    /// Model Context Protocol (MCP) server
70    McpServer {
71        name: String,
72        url: String,
73        /// Optional per-mode restrictions on which server tools the model may
74        /// call (wire: `allowed_tools: [{mode, tools}]`).
75        allowed_tools: Option<Vec<AllowedTools>>,
76        /// Optional headers for authentication or configuration
77        headers: Option<HashMap<String, String>>,
78    },
79    /// Built-in file search tool for semantic retrieval over document stores
80    FileSearch {
81        /// Names of file search stores to query (wire: `file_search_store_names`)
82        store_names: Vec<String>,
83        /// Number of semantic retrieval chunks to retrieve
84        top_k: Option<i32>,
85        /// Metadata filter for documents and chunks
86        metadata_filter: Option<String>,
87    },
88    /// Built-in retrieval tool for grounding over external retrieval
89    /// backends (Vertex AI Search, RAG stores, Exa.ai, Parallel.ai).
90    ///
91    /// Prefer constructing via [`RetrievalConfig`], which keeps
92    /// `retrieval_types` in sync with the per-backend configs.
93    ///
94    /// Server-side constraint (verified live 2026-07): the Gemini API
95    /// rejects `type: "retrieval"` — "not supported ... on the Gemini API,
96    /// it is allowed on the Gemini Enterprise Agent Platform" (Vertex).
97    /// The Gemini API's supported tool types are `google_maps`,
98    /// `mcp_server`, `function`, `google_search`, `file_search`,
99    /// `computer_use`, `code_execution`, and `url_context`.
100    Retrieval {
101        /// The retrieval backends to enable.
102        retrieval_types: Option<Vec<RetrievalType>>,
103        /// Configuration for Vertex AI Search.
104        vertex_ai_search_config: Option<VertexAiSearchConfig>,
105        /// Configuration for Exa.ai search.
106        exa_ai_search_config: Option<ExaAiSearchConfig>,
107        /// Configuration for Parallel.ai search.
108        parallel_ai_search_config: Option<ParallelAiSearchConfig>,
109        /// Configuration for RAG Store retrieval.
110        ///
111        /// Boxed to keep the `Tool` enum small (this is the largest tool
112        /// configuration).
113        rag_store_config: Option<Box<RagStoreConfig>>,
114    },
115    /// Unknown tool type for forward compatibility.
116    ///
117    /// This variant captures tool types that the library doesn't recognize yet.
118    /// This can happen when Google adds new built-in tools before this library
119    /// is updated to support them.
120    ///
121    /// The `tool_type` field contains the unrecognized type string from the API,
122    /// and `data` contains the full JSON object for inspection or debugging.
123    Unknown {
124        /// The unrecognized tool type name from the API
125        tool_type: String,
126        /// The full JSON data for this tool, preserved for debugging
127        data: serde_json::Value,
128    },
129}
130
131// Custom Serialize implementation for Tool.
132// This handles the Unknown variant by merging tool_type into the data.
133impl 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                // Flatten the data fields into the map if it's an object
284                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
299// Custom Deserialize implementation to handle unknown tool types gracefully.
300impl<'de> Deserialize<'de> for Tool {
301    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
302    where
303        D: serde::Deserializer<'de>,
304    {
305        // First, deserialize into a raw JSON value
306        let value = serde_json::Value::deserialize(deserializer)?;
307
308        // Helper enum for deserializing known types
309        // Note: variant names must match the serialized "type" field values exactly
310        #[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                // Spec wire format is snake_case; accept the legacy camelCase
341                // alias for pre-revision payloads.
342                #[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        // Try to deserialize as a known type
383        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                // Unknown type - extract type name and preserve data
453                let tool_type = value
454                    .get("type")
455                    .and_then(|v| v.as_str())
456                    .unwrap_or("<missing type>")
457                    .to_string();
458
459                // Log the actual parse error for debugging - this helps distinguish
460                // between truly unknown types and malformed known types
461                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    /// Check if this is an unknown tool type.
481    #[must_use]
482    pub const fn is_unknown(&self) -> bool {
483        matches!(self, Self::Unknown { .. })
484    }
485
486    /// Returns the tool type name if this is an unknown tool type.
487    ///
488    /// Returns `None` for known tool types.
489    #[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    /// Returns the raw JSON data if this is an unknown tool type.
498    ///
499    /// Returns `None` for known tool types.
500    #[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/// Represents a function that can be called by the model.
510#[derive(Clone, Serialize, Deserialize, Debug)]
511pub struct FunctionDeclaration {
512    name: String,
513    description: String,
514    parameters: FunctionParameters,
515}
516
517/// Represents the parameters schema for a function.
518#[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    /// Creates a new FunctionDeclaration with the given fields.
529    ///
530    /// This is primarily intended for internal use by the macro system.
531    /// For manual construction, prefer using `FunctionDeclaration::builder()`.
532    #[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    /// Creates a builder for ergonomic FunctionDeclaration construction
542    #[must_use]
543    pub fn builder(name: impl Into<String>) -> FunctionDeclarationBuilder {
544        FunctionDeclarationBuilder::new(name)
545    }
546
547    /// Returns the function name
548    #[must_use]
549    pub fn name(&self) -> &str {
550        &self.name
551    }
552
553    /// Returns the function description
554    #[must_use]
555    pub fn description(&self) -> &str {
556        &self.description
557    }
558
559    /// Returns a reference to the function parameters
560    #[must_use]
561    pub fn parameters(&self) -> &FunctionParameters {
562        &self.parameters
563    }
564
565    /// Converts this FunctionDeclaration into a Tool for API requests
566    #[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    /// Creates a new FunctionParameters with the given fields.
578    ///
579    /// This is primarily intended for internal use by the macro system.
580    /// For manual construction, prefer using `FunctionDeclaration::builder()`.
581    #[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    /// Returns the parameter type (typically "object")
591    #[must_use]
592    pub fn type_(&self) -> &str {
593        &self.type_
594    }
595
596    /// Returns the properties schema
597    #[must_use]
598    pub fn properties(&self) -> &serde_json::Value {
599        &self.properties
600    }
601
602    /// Returns the list of required parameter names
603    #[must_use]
604    pub fn required(&self) -> &[String] {
605        &self.required
606    }
607}
608
609/// Builder for ergonomic FunctionDeclaration creation
610#[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    /// Creates a new builder with the given function name
620    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    /// Sets the function description
630    pub fn description(mut self, description: impl Into<String>) -> Self {
631        self.description = description.into();
632        self
633    }
634
635    /// Adds a parameter to the function schema
636    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    /// Sets the list of required parameter names
644    pub fn required(mut self, required: Vec<String>) -> Self {
645        self.required = required;
646        self
647    }
648
649    /// Builds the FunctionDeclaration
650    ///
651    /// # Validation
652    ///
653    /// This method performs validation and logs warnings for:
654    /// - Empty or whitespace-only function names
655    /// - Required parameters that don't exist in the properties schema
656    ///
657    /// These conditions may cause API errors but are allowed by the builder
658    /// for backwards compatibility.
659    pub fn build(self) -> FunctionDeclaration {
660        // Validate function name
661        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        // Validate required parameters exist in properties
669        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/// Modes for function calling behavior.
695///
696/// This enum is marked `#[non_exhaustive]` for forward compatibility.
697/// New modes may be added in future versions.
698///
699/// # Forward Compatibility (Evergreen Philosophy)
700///
701/// When the API returns a mode value that this library doesn't recognize,
702/// it will be captured as `FunctionCallingMode::Unknown` rather than
703/// causing a deserialization error. This follows the
704/// [Evergreen spec](https://github.com/google-deepmind/evergreen-spec)
705/// philosophy of graceful degradation.
706///
707/// # Modes
708///
709/// - `Auto` (default): Model decides whether to call functions or respond naturally
710/// - `Any`: Model must call a function; guarantees schema adherence for calls
711/// - `None`: Prohibits function calling entirely
712/// - `Validated` (Preview): Ensures either function calls OR natural language adhere to schema
713#[derive(Clone, Debug, PartialEq)]
714#[non_exhaustive]
715pub enum FunctionCallingMode {
716    /// Model decides whether to call functions or respond with natural language.
717    Auto,
718    /// Model must call a function; guarantees schema adherence for calls.
719    Any,
720    /// Function calling is disabled.
721    None,
722    /// Ensures either function calls OR natural language adhere to schema.
723    ///
724    /// This is a preview mode that provides schema adherence guarantees
725    /// for both function call outputs and natural language responses.
726    Validated,
727    /// Unknown mode (for forward compatibility).
728    ///
729    /// This variant captures any unrecognized mode values from the API,
730    /// allowing the library to handle new modes gracefully.
731    ///
732    /// The `mode_type` field contains the unrecognized mode string,
733    /// and `data` contains the JSON value (typically the same string).
734    Unknown {
735        /// The unrecognized mode string from the API
736        mode_type: String,
737        /// The raw JSON value, preserved for debugging
738        data: serde_json::Value,
739    },
740}
741
742impl FunctionCallingMode {
743    /// Check if this is an unknown mode.
744    #[must_use]
745    pub const fn is_unknown(&self) -> bool {
746        matches!(self, Self::Unknown { .. })
747    }
748
749    /// Returns the mode type name if this is an unknown mode.
750    ///
751    /// Returns `None` for known modes.
752    #[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    /// Returns the raw JSON data if this is an unknown mode.
761    ///
762    /// Returns `None` for known modes.
763    #[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        // Wire format is lowercase per API revision 2026-05-20.
778        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            // Spec wire format is lowercase; accept legacy UPPERCASE too.
797            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                // Non-string value - preserve it in Unknown
815                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/// Restriction on which tools the model may call.
831///
832/// Used both as the object form of [`ToolChoice`]
833/// (`{"allowed_tools": {"mode": ..., "tools": [...]}}`) and as the element
834/// type of the MCP server tool's `allowed_tools` list.
835#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
836#[serde(default)]
837pub struct AllowedTools {
838    /// Function calling mode applied to the listed tools.
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub mode: Option<FunctionCallingMode>,
841    /// Names of the tools the model is allowed to call.
842    #[serde(skip_serializing_if = "Vec::is_empty")]
843    pub tools: Vec<String>,
844}
845
846impl AllowedTools {
847    /// Creates a new tool restriction over the given tool names.
848    #[must_use]
849    pub fn new(tools: Vec<String>) -> Self {
850        Self { mode: None, tools }
851    }
852
853    /// Sets the function calling mode for the listed tools.
854    #[must_use]
855    pub fn with_mode(mut self, mode: FunctionCallingMode) -> Self {
856        self.mode = Some(mode);
857        self
858    }
859}
860
861/// The `generation_config.tool_choice` union.
862///
863/// Either a plain mode string (`"auto" | "any" | "none" | "validated"`) or an
864/// object restricting the model to a named set of tools:
865/// `{"allowed_tools": {"mode": ..., "tools": [...]}}`.
866///
867/// # Forward Compatibility
868///
869/// `#[non_exhaustive]`; unrecognized shapes deserialize into
870/// [`ToolChoice::Unknown`] with the data preserved.
871///
872/// # Example
873///
874/// ```
875/// use genai_rs::{FunctionCallingMode, ToolChoice};
876///
877/// // Plain mode
878/// let choice = ToolChoice::Mode(FunctionCallingMode::Any);
879/// assert_eq!(serde_json::to_string(&choice).unwrap(), "\"any\"");
880///
881/// // Restricted tool set
882/// let choice = ToolChoice::allowed_tools(
883///     Some(FunctionCallingMode::Any),
884///     vec!["get_weather".to_string()],
885/// );
886/// ```
887#[derive(Clone, Debug, PartialEq)]
888#[non_exhaustive]
889pub enum ToolChoice {
890    /// A plain function calling mode.
891    Mode(FunctionCallingMode),
892    /// Restriction to a named set of tools (wire: `{"allowed_tools": {...}}`).
893    AllowedTools(AllowedTools),
894    /// Unknown tool choice shape for forward compatibility.
895    Unknown {
896        /// A short description of the unrecognized shape (the string value or
897        /// object key encountered).
898        choice_type: String,
899        /// The raw JSON value, preserved for debugging and roundtrip.
900        data: serde_json::Value,
901    },
902}
903
904impl ToolChoice {
905    /// Creates a tool restriction choice from a mode and tool names.
906    #[must_use]
907    pub fn allowed_tools(mode: Option<FunctionCallingMode>, tools: Vec<String>) -> Self {
908        Self::AllowedTools(AllowedTools { mode, tools })
909    }
910
911    /// Check if this is an unknown tool choice shape.
912    #[must_use]
913    pub const fn is_unknown(&self) -> bool {
914        matches!(self, Self::Unknown { .. })
915    }
916
917    /// Returns the choice type descriptor if this is an unknown tool choice.
918    #[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    /// Returns the raw JSON data if this is an unknown tool choice.
927    #[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                // Delegates unknown strings to FunctionCallingMode::Unknown.
970                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/// Types of search to perform with the Google Search tool.
1006///
1007/// This enum is marked `#[non_exhaustive]` for forward compatibility.
1008///
1009/// # Wire Format
1010///
1011/// Values serialize as snake_case strings: `"web_search"`, `"image_search"`,
1012/// `"enterprise_web_search"`.
1013#[derive(Clone, Debug, PartialEq, Eq)]
1014#[non_exhaustive]
1015pub enum SearchType {
1016    /// Web search
1017    WebSearch,
1018    /// Image search (only available for specific models like `gemini-3.1-flash-image-preview`)
1019    ImageSearch,
1020    /// Enterprise web search
1021    EnterpriseWebSearch,
1022    /// Unknown search type for forward compatibility
1023    Unknown {
1024        /// The unrecognized search type string from the API
1025        search_type: String,
1026        /// The raw JSON value, preserved for debugging
1027        data: serde_json::Value,
1028    },
1029}
1030
1031impl SearchType {
1032    /// Check if this is an unknown search type.
1033    #[must_use]
1034    pub const fn is_unknown(&self) -> bool {
1035        matches!(self, Self::Unknown { .. })
1036    }
1037
1038    /// Returns the search type name if this is an unknown type.
1039    #[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    /// Returns the raw JSON data if this is an unknown search type.
1048    #[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/// Retrieval backends for the built-in `retrieval` tool.
1109///
1110/// This enum is marked `#[non_exhaustive]` for forward compatibility.
1111///
1112/// # Wire Format
1113///
1114/// Serializes as snake_case strings: `"vertex_ai_search"`, `"rag_store"`,
1115/// `"exa_ai_search"`, `"parallel_ai_search"`.
1116///
1117/// # Evergreen Pattern
1118///
1119/// Unknown values from the API deserialize into the `Unknown` variant,
1120/// preserving the original data for debugging and roundtrip serialization.
1121#[derive(Clone, Debug, PartialEq)]
1122#[non_exhaustive]
1123pub enum RetrievalType {
1124    /// Vertex AI Search engines and datastores.
1125    VertexAiSearch,
1126    /// Vertex RAG Store corpora.
1127    RagStore,
1128    /// Exa.ai search.
1129    ExaAiSearch,
1130    /// Parallel.ai search.
1131    ParallelAiSearch,
1132    /// Unknown retrieval type for forward compatibility
1133    Unknown {
1134        /// The unrecognized retrieval type string from the API
1135        retrieval_type: String,
1136        /// The raw JSON value, preserved for debugging
1137        data: serde_json::Value,
1138    },
1139}
1140
1141impl RetrievalType {
1142    /// Check if this is an unknown retrieval type.
1143    #[must_use]
1144    pub const fn is_unknown(&self) -> bool {
1145        matches!(self, Self::Unknown { .. })
1146    }
1147
1148    /// Returns the retrieval type name if this is an unknown type.
1149    #[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    /// Returns the raw JSON data if this is an unknown retrieval type.
1158    #[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/// Configuration for the Vertex AI Search retrieval backend.
1221#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1222#[serde(default)]
1223pub struct VertexAiSearchConfig {
1224    /// The Vertex AI Search engine to use.
1225    #[serde(skip_serializing_if = "Option::is_none")]
1226    pub engine: Option<String>,
1227    /// The Vertex AI Search datastores to use.
1228    #[serde(skip_serializing_if = "Option::is_none")]
1229    pub datastores: Option<Vec<String>>,
1230}
1231
1232impl VertexAiSearchConfig {
1233    /// Creates an empty Vertex AI Search config.
1234    #[must_use]
1235    pub fn new() -> Self {
1236        Self::default()
1237    }
1238
1239    /// Sets the Vertex AI Search engine.
1240    #[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    /// Sets the Vertex AI Search datastores.
1247    #[must_use]
1248    pub fn with_datastores(mut self, datastores: Vec<String>) -> Self {
1249        self.datastores = Some(datastores);
1250        self
1251    }
1252}
1253
1254/// Configuration for the Exa.ai search retrieval backend.
1255///
1256/// **Note**: `api_key` is your Exa.ai API key and is sent on the wire; treat
1257/// request logs accordingly.
1258#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1259#[serde(default)]
1260pub struct ExaAiSearchConfig {
1261    /// The Exa.ai API key (required by the API).
1262    pub api_key: String,
1263    /// Extra parameters passed through to the Exa.ai Search API.
1264    #[serde(skip_serializing_if = "Option::is_none")]
1265    pub custom_config: Option<serde_json::Value>,
1266}
1267
1268impl ExaAiSearchConfig {
1269    /// Creates a config with the given Exa.ai API key.
1270    #[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    /// Sets extra parameters passed through to the Exa.ai Search API.
1279    #[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/// Configuration for the Parallel.ai search retrieval backend.
1287///
1288/// **Note**: `api_key` is your Parallel.ai API key and is sent on the wire;
1289/// treat request logs accordingly.
1290#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1291#[serde(default)]
1292pub struct ParallelAiSearchConfig {
1293    /// The Parallel.ai API key.
1294    #[serde(skip_serializing_if = "Option::is_none")]
1295    pub api_key: Option<String>,
1296    /// Extra parameters for Parallel.ai search.
1297    #[serde(skip_serializing_if = "Option::is_none")]
1298    pub custom_config: Option<serde_json::Value>,
1299}
1300
1301impl ParallelAiSearchConfig {
1302    /// Creates an empty Parallel.ai search config.
1303    #[must_use]
1304    pub fn new() -> Self {
1305        Self::default()
1306    }
1307
1308    /// Sets the Parallel.ai API key.
1309    #[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    /// Sets extra parameters for Parallel.ai search.
1316    #[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/// A RAG resource reference (corpus + optional file restriction).
1324#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1325#[serde(default)]
1326pub struct RagResource {
1327    /// `RagCorpora` resource name.
1328    #[serde(skip_serializing_if = "Option::is_none")]
1329    pub rag_corpus: Option<String>,
1330    /// RAG file IDs; the files must belong to `rag_corpus`.
1331    #[serde(skip_serializing_if = "Option::is_none")]
1332    pub rag_file_ids: Option<Vec<String>>,
1333}
1334
1335impl RagResource {
1336    /// Creates a RAG resource for the given corpus.
1337    #[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    /// Restricts retrieval to the given file IDs within the corpus.
1346    #[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/// Hybrid-search configuration for RAG retrieval.
1354#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1355#[serde(default)]
1356pub struct HybridSearchConfig {
1357    /// Alpha value controlling the weight between dense and sparse vector
1358    /// search results.
1359    #[serde(skip_serializing_if = "Option::is_none")]
1360    pub alpha: Option<f32>,
1361}
1362
1363/// Filter configuration for RAG retrieval.
1364#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1365#[serde(default)]
1366pub struct RagFilter {
1367    /// Only return contexts with vector distance smaller than the threshold.
1368    #[serde(skip_serializing_if = "Option::is_none")]
1369    pub vector_distance_threshold: Option<f64>,
1370    /// Only return contexts with vector similarity larger than the threshold.
1371    #[serde(skip_serializing_if = "Option::is_none")]
1372    pub vector_similarity_threshold: Option<f64>,
1373    /// String for metadata filtering.
1374    #[serde(skip_serializing_if = "Option::is_none")]
1375    pub metadata_filter: Option<String>,
1376}
1377
1378/// Ranking configuration for RAG retrieval (Rank Service).
1379///
1380/// # Wire Format
1381///
1382/// `{"ranking_config": "rank_service", "model_name": "..."}` — the
1383/// `ranking_config` discriminator is always `"rank_service"`.
1384#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1385pub struct RagRanking {
1386    /// The ranking config discriminator (always `"rank_service"`).
1387    #[serde(default = "RagRanking::default_ranking_config")]
1388    pub ranking_config: String,
1389    /// The model name of the rank service.
1390    #[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    /// Creates a rank-service ranking config.
1400    #[must_use]
1401    pub fn rank_service() -> Self {
1402        Self::default()
1403    }
1404
1405    /// Sets the rank service model name.
1406    #[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/// Context-retrieval configuration for the RAG store backend
1423/// (wire: `rag_retrieval_config`).
1424#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1425#[serde(default)]
1426pub struct RagRetrievalConfig {
1427    /// The number of contexts to retrieve.
1428    #[serde(skip_serializing_if = "Option::is_none")]
1429    pub top_k: Option<i32>,
1430    /// Hybrid search configuration.
1431    #[serde(skip_serializing_if = "Option::is_none")]
1432    pub hybrid_search: Option<HybridSearchConfig>,
1433    /// Filter configuration (wire: `filter`).
1434    #[serde(rename = "filter", skip_serializing_if = "Option::is_none")]
1435    pub filter: Option<RagFilter>,
1436    /// Rank Service configuration.
1437    #[serde(skip_serializing_if = "Option::is_none")]
1438    pub ranking: Option<RagRanking>,
1439}
1440
1441impl RagRetrievalConfig {
1442    /// Creates an empty RAG retrieval config.
1443    #[must_use]
1444    pub fn new() -> Self {
1445        Self::default()
1446    }
1447
1448    /// Sets the number of contexts to retrieve.
1449    #[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    /// Sets the hybrid-search alpha (dense vs. sparse weighting).
1456    #[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    /// Sets the filter configuration.
1463    #[must_use]
1464    pub fn with_filter(mut self, filter: RagFilter) -> Self {
1465        self.filter = Some(filter);
1466        self
1467    }
1468
1469    /// Sets the ranking configuration.
1470    #[must_use]
1471    pub fn with_ranking(mut self, ranking: RagRanking) -> Self {
1472        self.ranking = Some(ranking);
1473        self
1474    }
1475}
1476
1477/// Configuration for the RAG Store retrieval backend.
1478#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1479#[serde(default)]
1480pub struct RagStoreConfig {
1481    /// The RAG sources to retrieve from.
1482    #[serde(skip_serializing_if = "Option::is_none")]
1483    pub rag_resources: Option<Vec<RagResource>>,
1484    /// Number of top-k results to return from the selected corpora.
1485    ///
1486    /// Deprecated by the API in favor of
1487    /// `rag_retrieval_config.top_k`; still sent when set.
1488    #[serde(skip_serializing_if = "Option::is_none")]
1489    pub similarity_top_k: Option<i32>,
1490    /// Only return results with vector distance smaller than the threshold.
1491    ///
1492    /// Deprecated by the API in favor of
1493    /// `rag_retrieval_config.filter.vector_distance_threshold`; still sent
1494    /// when set.
1495    #[serde(skip_serializing_if = "Option::is_none")]
1496    pub vector_distance_threshold: Option<f64>,
1497    /// Context-retrieval configuration.
1498    #[serde(skip_serializing_if = "Option::is_none")]
1499    pub rag_retrieval_config: Option<RagRetrievalConfig>,
1500}
1501
1502impl RagStoreConfig {
1503    /// Creates a RAG store config over the given resources.
1504    #[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    /// Sets the context-retrieval configuration.
1513    #[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// --- Tool Configuration Structs ---
1521//
1522// These provide ergonomic builders for constructing Tool variants with optional fields.
1523// Each implements `From<Config> for Tool` so they can be passed to `InteractionBuilder::add_tool()`.
1524
1525/// Configuration for the Google Search built-in tool.
1526///
1527/// # Example
1528///
1529/// ```no_run
1530/// use genai_rs::{GoogleSearchConfig, SearchType};
1531///
1532/// // Default (web search only)
1533/// let config = GoogleSearchConfig::new();
1534///
1535/// // With image search enabled
1536/// let config = GoogleSearchConfig::new()
1537///     .with_search_types(vec![SearchType::WebSearch, SearchType::ImageSearch]);
1538/// ```
1539#[derive(Clone, Debug, Default)]
1540pub struct GoogleSearchConfig {
1541    search_types: Option<Vec<SearchType>>,
1542}
1543
1544impl GoogleSearchConfig {
1545    /// Creates a new `GoogleSearchConfig` with default settings.
1546    #[must_use]
1547    pub fn new() -> Self {
1548        Self::default()
1549    }
1550
1551    /// Sets the search types to perform.
1552    #[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/// Configuration for the Google Maps built-in tool.
1568///
1569/// # Example
1570///
1571/// ```no_run
1572/// use genai_rs::GoogleMapsConfig;
1573///
1574/// // Default
1575/// let config = GoogleMapsConfig::new();
1576///
1577/// // With widget enabled
1578/// let config = GoogleMapsConfig::new().with_widget();
1579/// ```
1580#[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    /// Creates a new `GoogleMapsConfig` with default settings.
1589    #[must_use]
1590    pub fn new() -> Self {
1591        Self::default()
1592    }
1593
1594    /// Enables the widget context token in the response.
1595    #[must_use]
1596    pub fn with_widget(mut self) -> Self {
1597        self.enable_widget = Some(true);
1598        self
1599    }
1600
1601    /// Biases results toward the given coordinates.
1602    #[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/// Configuration for an MCP (Model Context Protocol) server tool.
1621///
1622/// # Example
1623///
1624/// ```no_run
1625/// use genai_rs::McpServerConfig;
1626/// use std::collections::HashMap;
1627///
1628/// let config = McpServerConfig::new("filesystem", "https://mcp.example.com/fs")
1629///     .with_allowed_tools(vec!["read_file".to_string(), "list_dir".to_string()])
1630///     .with_headers(HashMap::from([
1631///         ("Authorization".to_string(), "Bearer token".to_string()),
1632///     ]));
1633/// ```
1634#[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    /// Creates a new `McpServerConfig` with the given name and URL.
1644    #[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    /// Restricts the model to the given tool names (no explicit mode).
1655    ///
1656    /// For per-mode restrictions use
1657    /// [`with_allowed_tools_config`](Self::with_allowed_tools_config).
1658    #[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    /// Sets the full `[{mode, tools}]` allowed-tools restriction list.
1665    #[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    /// Sets authentication/configuration headers.
1672    #[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/// Configuration for the Computer Use built-in tool.
1691///
1692/// # Security Warning
1693///
1694/// Computer use allows the model to control a real browser. Use
1695/// [`ComputerUseConfig::excluding`] to restrict dangerous actions.
1696///
1697/// # Example
1698///
1699/// ```no_run
1700/// use genai_rs::ComputerUseConfig;
1701///
1702/// let config = ComputerUseConfig::new()
1703///     .excluding(vec!["submit_form".to_string(), "download_file".to_string()]);
1704/// ```
1705#[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    /// Creates a new `ComputerUseConfig` targeting the browser environment.
1715    #[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    /// Sets the operating environment. Known values: `browser`, `mobile`,
1726    /// `desktop`.
1727    #[must_use]
1728    pub fn with_environment(mut self, environment: impl Into<String>) -> Self {
1729        self.environment = environment.into();
1730        self
1731    }
1732
1733    /// Excludes specific predefined browser functions from model access.
1734    #[must_use]
1735    pub fn excluding(mut self, functions: Vec<String>) -> Self {
1736        self.excluded_predefined_functions = functions;
1737        self
1738    }
1739
1740    /// Enables (or disables) prompt injection detection.
1741    #[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    /// Disables the given safety policies.
1748    ///
1749    /// Known values include `financial_transactions`,
1750    /// `sensitive_data_modification`, `communication_tool`,
1751    /// `account_creation`, `data_modification`, `user_consent_management`,
1752    /// and `legal_terms_and_agreements`.
1753    #[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/// Configuration for the File Search built-in tool.
1778///
1779/// # Example
1780///
1781/// ```no_run
1782/// use genai_rs::FileSearchConfig;
1783///
1784/// let config = FileSearchConfig::new(vec!["my-store".to_string()])
1785///     .with_top_k(10)
1786///     .with_metadata_filter("category:technical");
1787/// ```
1788#[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    /// Creates a new `FileSearchConfig` with the given store names.
1797    #[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    /// Sets the maximum number of semantic retrieval chunks to return.
1807    #[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    /// Sets a metadata filter expression for document filtering.
1814    #[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/// Configuration for the built-in Retrieval tool.
1832///
1833/// Each `with_*` backend method enables the corresponding
1834/// [`RetrievalType`] and attaches its config, keeping `retrieval_types`
1835/// consistent with the per-backend configuration.
1836///
1837/// # Example
1838///
1839/// ```no_run
1840/// use genai_rs::{RagResource, RagStoreConfig, RetrievalConfig, VertexAiSearchConfig};
1841///
1842/// // Vertex AI Search grounding
1843/// let config = RetrievalConfig::new().with_vertex_ai_search(
1844///     VertexAiSearchConfig::new().with_engine("projects/p/locations/global/engines/e"),
1845/// );
1846///
1847/// // RAG store grounding
1848/// let config = RetrievalConfig::new().with_rag_store(RagStoreConfig::new(vec![
1849///     RagResource::new("projects/p/locations/us/ragCorpora/c"),
1850/// ]));
1851/// ```
1852#[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    /// Creates an empty retrieval config.
1863    #[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    /// Enables Vertex AI Search retrieval with the given config.
1875    #[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    /// Enables Exa.ai search retrieval with the given config.
1883    #[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    /// Enables Parallel.ai search retrieval with the given config.
1891    #[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    /// Enables RAG Store retrieval with the given config.
1899    #[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    /// Sets the enabled retrieval types explicitly (escape hatch).
1907    ///
1908    /// Replaces the types accumulated by the `with_*` backend methods.
1909    #[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        // Wire format is lowercase per API revision 2026-05-20
1965        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        // Legacy UPPERCASE values are still accepted on deserialize
1982        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        // Test that unknown modes are preserved
1995        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        // Roundtrip should preserve the mode type
2003        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        // Known modes should not be unknown
2010        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        // Unknown mode should report its type
2019        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        // Test that non-string JSON values are handled gracefully
2031        let json = "123";
2032        let parsed: FunctionCallingMode =
2033            serde_json::from_str(json).expect("Deserialization should succeed");
2034
2035        assert!(parsed.is_unknown());
2036        // The mode_type should indicate it was a non-string value
2037        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        // Simulate an unknown tool type from the API
2209        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        // Should contain the type and config, but not duplicate "type"
2231        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        // Test Unknown variant
2244        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        // Spec wire format is snake_case (fixed from legacy camelCase)
2267        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        // Pre-revision payloads used camelCase; the alias keeps them parseable.
2296        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        // Test that empty exclusions don't serialize the field
2310        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        // Test known types return None for unknown helpers
2336        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\"")); // Wire format uses full name
2394        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        // Test with only required field (store names)
2415        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\"")); // Wire format uses full name
2423        // Optional fields should not appear
2424        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        // Known types are not unknown
2608        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        // Wire field is `filter` (Rust field `filter`, spec alias `filter_`)
2672        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}