genai_rs/
tools.rs

1// Shared types used by the Interactions API
2
3use serde::{Deserialize, Serialize};
4
5/// Represents a tool that can be used by the model (Interactions API format).
6///
7/// Tools in the Interactions API use a flat structure with the tool type and details
8/// at the top level, rather than nested in arrays.
9///
10/// # Forward Compatibility (Evergreen Philosophy)
11///
12/// This enum is marked `#[non_exhaustive]`, which means:
13/// - Match statements must include a wildcard arm (`_ => ...`)
14/// - New variants may be added in minor version updates without breaking your code
15///
16/// When the API returns a tool type that this library doesn't recognize, it will be
17/// captured as `Tool::Unknown` rather than causing a deserialization error.
18/// This follows the [Evergreen spec](https://github.com/google-deepmind/evergreen-spec)
19/// philosophy of graceful degradation.
20#[derive(Clone, Debug)]
21#[non_exhaustive]
22pub enum Tool {
23    /// A custom function that the model can call
24    Function {
25        name: String,
26        description: String,
27        parameters: FunctionParameters,
28    },
29    /// Built-in Google Search tool
30    GoogleSearch,
31    /// Built-in code execution tool
32    CodeExecution,
33    /// Built-in URL context tool
34    UrlContext,
35    /// Built-in computer use tool for browser automation.
36    ///
37    /// **Security Warning**: This tool allows the model to interact with web browsers
38    /// on your behalf. Only use with trusted models and carefully review excluded functions.
39    ///
40    /// # Fields
41    ///
42    /// - `environment`: The operating environment (currently only "browser" supported)
43    /// - `excluded_predefined_functions`: List of predefined functions to exclude from model access
44    ComputerUse {
45        /// The environment being operated (currently only "browser" supported)
46        environment: String,
47        /// List of predefined functions to exclude from model access.
48        ///
49        /// Note: This field name follows the API's `excludedPredefinedFunctions` camelCase naming.
50        excluded_predefined_functions: Vec<String>,
51    },
52    /// Model Context Protocol (MCP) server
53    McpServer { name: String, url: String },
54    /// Built-in file search tool for semantic retrieval over document stores
55    FileSearch {
56        /// Names of file search stores to query (wire: `file_search_store_names`)
57        store_names: Vec<String>,
58        /// Number of semantic retrieval chunks to retrieve
59        top_k: Option<i32>,
60        /// Metadata filter for documents and chunks
61        metadata_filter: Option<String>,
62    },
63    /// Unknown tool type for forward compatibility.
64    ///
65    /// This variant captures tool types that the library doesn't recognize yet.
66    /// This can happen when Google adds new built-in tools before this library
67    /// is updated to support them.
68    ///
69    /// The `tool_type` field contains the unrecognized type string from the API,
70    /// and `data` contains the full JSON object for inspection or debugging.
71    Unknown {
72        /// The unrecognized tool type name from the API
73        tool_type: String,
74        /// The full JSON data for this tool, preserved for debugging
75        data: serde_json::Value,
76    },
77}
78
79// Custom Serialize implementation for Tool.
80// This handles the Unknown variant by merging tool_type into the data.
81impl Serialize for Tool {
82    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83    where
84        S: serde::Serializer,
85    {
86        use serde::ser::SerializeMap;
87
88        match self {
89            Self::Function {
90                name,
91                description,
92                parameters,
93            } => {
94                let mut map = serializer.serialize_map(None)?;
95                map.serialize_entry("type", "function")?;
96                map.serialize_entry("name", name)?;
97                map.serialize_entry("description", description)?;
98                map.serialize_entry("parameters", parameters)?;
99                map.end()
100            }
101            Self::GoogleSearch => {
102                let mut map = serializer.serialize_map(None)?;
103                map.serialize_entry("type", "google_search")?;
104                map.end()
105            }
106            Self::CodeExecution => {
107                let mut map = serializer.serialize_map(None)?;
108                map.serialize_entry("type", "code_execution")?;
109                map.end()
110            }
111            Self::UrlContext => {
112                let mut map = serializer.serialize_map(None)?;
113                map.serialize_entry("type", "url_context")?;
114                map.end()
115            }
116            Self::ComputerUse {
117                environment,
118                excluded_predefined_functions,
119            } => {
120                let mut map = serializer.serialize_map(None)?;
121                map.serialize_entry("type", "computer_use")?;
122                map.serialize_entry("environment", environment)?;
123                if !excluded_predefined_functions.is_empty() {
124                    map.serialize_entry(
125                        "excludedPredefinedFunctions",
126                        excluded_predefined_functions,
127                    )?;
128                }
129                map.end()
130            }
131            Self::McpServer { name, url } => {
132                let mut map = serializer.serialize_map(None)?;
133                map.serialize_entry("type", "mcp_server")?;
134                map.serialize_entry("name", name)?;
135                map.serialize_entry("url", url)?;
136                map.end()
137            }
138            Self::FileSearch {
139                store_names,
140                top_k,
141                metadata_filter,
142            } => {
143                let mut map = serializer.serialize_map(None)?;
144                map.serialize_entry("type", "file_search")?;
145                map.serialize_entry("file_search_store_names", store_names)?;
146                if let Some(k) = top_k {
147                    map.serialize_entry("top_k", k)?;
148                }
149                if let Some(filter) = metadata_filter {
150                    map.serialize_entry("metadata_filter", filter)?;
151                }
152                map.end()
153            }
154            Self::Unknown { tool_type, data } => {
155                let mut map = serializer.serialize_map(None)?;
156                map.serialize_entry("type", tool_type)?;
157                // Flatten the data fields into the map if it's an object
158                if let serde_json::Value::Object(obj) = data {
159                    for (key, value) in obj {
160                        if key != "type" {
161                            map.serialize_entry(key, value)?;
162                        }
163                    }
164                } else if !data.is_null() {
165                    map.serialize_entry("data", data)?;
166                }
167                map.end()
168            }
169        }
170    }
171}
172
173// Custom Deserialize implementation to handle unknown tool types gracefully.
174impl<'de> Deserialize<'de> for Tool {
175    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
176    where
177        D: serde::Deserializer<'de>,
178    {
179        // First, deserialize into a raw JSON value
180        let value = serde_json::Value::deserialize(deserializer)?;
181
182        // Helper enum for deserializing known types
183        // Note: variant names must match the serialized "type" field values exactly
184        #[derive(Deserialize)]
185        #[serde(tag = "type")]
186        enum KnownTool {
187            #[serde(rename = "function")]
188            Function {
189                name: String,
190                description: String,
191                parameters: FunctionParameters,
192            },
193            #[serde(rename = "google_search")]
194            GoogleSearch,
195            #[serde(rename = "code_execution")]
196            CodeExecution,
197            #[serde(rename = "url_context")]
198            UrlContext,
199            #[serde(rename = "computer_use")]
200            ComputerUse {
201                environment: String,
202                #[serde(rename = "excludedPredefinedFunctions", default)]
203                excluded_predefined_functions: Vec<String>,
204            },
205            #[serde(rename = "mcp_server")]
206            McpServer { name: String, url: String },
207            #[serde(rename = "file_search")]
208            FileSearch {
209                #[serde(rename = "file_search_store_names")]
210                store_names: Vec<String>,
211                #[serde(default)]
212                top_k: Option<i32>,
213                #[serde(default)]
214                metadata_filter: Option<String>,
215            },
216        }
217
218        // Try to deserialize as a known type
219        match serde_json::from_value::<KnownTool>(value.clone()) {
220            Ok(known) => Ok(match known {
221                KnownTool::Function {
222                    name,
223                    description,
224                    parameters,
225                } => Tool::Function {
226                    name,
227                    description,
228                    parameters,
229                },
230                KnownTool::GoogleSearch => Tool::GoogleSearch,
231                KnownTool::CodeExecution => Tool::CodeExecution,
232                KnownTool::UrlContext => Tool::UrlContext,
233                KnownTool::ComputerUse {
234                    environment,
235                    excluded_predefined_functions,
236                } => Tool::ComputerUse {
237                    environment,
238                    excluded_predefined_functions,
239                },
240                KnownTool::McpServer { name, url } => Tool::McpServer { name, url },
241                KnownTool::FileSearch {
242                    store_names,
243                    top_k,
244                    metadata_filter,
245                } => Tool::FileSearch {
246                    store_names,
247                    top_k,
248                    metadata_filter,
249                },
250            }),
251            Err(parse_error) => {
252                // Unknown type - extract type name and preserve data
253                let tool_type = value
254                    .get("type")
255                    .and_then(|v| v.as_str())
256                    .unwrap_or("<missing type>")
257                    .to_string();
258
259                // Log the actual parse error for debugging - this helps distinguish
260                // between truly unknown types and malformed known types
261                tracing::warn!(
262                    "Encountered unknown Tool type '{}'. \
263                     Parse error: {}. \
264                     This may indicate a new API feature or a malformed response. \
265                     The tool will be preserved in the Unknown variant.",
266                    tool_type,
267                    parse_error
268                );
269
270                Ok(Tool::Unknown {
271                    tool_type,
272                    data: value,
273                })
274            }
275        }
276    }
277}
278
279impl Tool {
280    /// Check if this is an unknown tool type.
281    #[must_use]
282    pub const fn is_unknown(&self) -> bool {
283        matches!(self, Self::Unknown { .. })
284    }
285
286    /// Returns the tool type name if this is an unknown tool type.
287    ///
288    /// Returns `None` for known tool types.
289    #[must_use]
290    pub fn unknown_tool_type(&self) -> Option<&str> {
291        match self {
292            Self::Unknown { tool_type, .. } => Some(tool_type),
293            _ => None,
294        }
295    }
296
297    /// Returns the raw JSON data if this is an unknown tool type.
298    ///
299    /// Returns `None` for known tool types.
300    #[must_use]
301    pub fn unknown_data(&self) -> Option<&serde_json::Value> {
302        match self {
303            Self::Unknown { data, .. } => Some(data),
304            _ => None,
305        }
306    }
307}
308
309/// Represents a function that can be called by the model.
310#[derive(Clone, Serialize, Deserialize, Debug)]
311pub struct FunctionDeclaration {
312    name: String,
313    description: String,
314    parameters: FunctionParameters,
315}
316
317/// Represents the parameters schema for a function.
318#[derive(Clone, Serialize, Deserialize, Debug)]
319pub struct FunctionParameters {
320    #[serde(rename = "type")]
321    type_: String,
322    properties: serde_json::Value,
323    #[serde(skip_serializing_if = "Vec::is_empty", default)]
324    required: Vec<String>,
325}
326
327impl FunctionDeclaration {
328    /// Creates a new FunctionDeclaration with the given fields.
329    ///
330    /// This is primarily intended for internal use by the macro system.
331    /// For manual construction, prefer using `FunctionDeclaration::builder()`.
332    #[doc(hidden)]
333    pub fn new(name: String, description: String, parameters: FunctionParameters) -> Self {
334        Self {
335            name,
336            description,
337            parameters,
338        }
339    }
340
341    /// Creates a builder for ergonomic FunctionDeclaration construction
342    #[must_use]
343    pub fn builder(name: impl Into<String>) -> FunctionDeclarationBuilder {
344        FunctionDeclarationBuilder::new(name)
345    }
346
347    /// Returns the function name
348    #[must_use]
349    pub fn name(&self) -> &str {
350        &self.name
351    }
352
353    /// Returns the function description
354    #[must_use]
355    pub fn description(&self) -> &str {
356        &self.description
357    }
358
359    /// Returns a reference to the function parameters
360    #[must_use]
361    pub fn parameters(&self) -> &FunctionParameters {
362        &self.parameters
363    }
364
365    /// Converts this FunctionDeclaration into a Tool for API requests
366    pub fn into_tool(self) -> Tool {
367        Tool::Function {
368            name: self.name,
369            description: self.description,
370            parameters: self.parameters,
371        }
372    }
373}
374
375impl FunctionParameters {
376    /// Creates a new FunctionParameters with the given fields.
377    ///
378    /// This is primarily intended for internal use by the macro system.
379    /// For manual construction, prefer using `FunctionDeclaration::builder()`.
380    #[doc(hidden)]
381    pub fn new(type_: String, properties: serde_json::Value, required: Vec<String>) -> Self {
382        Self {
383            type_,
384            properties,
385            required,
386        }
387    }
388
389    /// Returns the parameter type (typically "object")
390    #[must_use]
391    pub fn type_(&self) -> &str {
392        &self.type_
393    }
394
395    /// Returns the properties schema
396    #[must_use]
397    pub fn properties(&self) -> &serde_json::Value {
398        &self.properties
399    }
400
401    /// Returns the list of required parameter names
402    #[must_use]
403    pub fn required(&self) -> &[String] {
404        &self.required
405    }
406}
407
408/// Builder for ergonomic FunctionDeclaration creation
409#[derive(Debug)]
410pub struct FunctionDeclarationBuilder {
411    name: String,
412    description: String,
413    properties: serde_json::Value,
414    required: Vec<String>,
415}
416
417impl FunctionDeclarationBuilder {
418    /// Creates a new builder with the given function name
419    pub fn new(name: impl Into<String>) -> Self {
420        Self {
421            name: name.into(),
422            description: String::new(),
423            properties: serde_json::Value::Object(serde_json::Map::new()),
424            required: Vec::new(),
425        }
426    }
427
428    /// Sets the function description
429    pub fn description(mut self, description: impl Into<String>) -> Self {
430        self.description = description.into();
431        self
432    }
433
434    /// Adds a parameter to the function schema
435    pub fn parameter(mut self, name: &str, schema: serde_json::Value) -> Self {
436        if let serde_json::Value::Object(ref mut map) = self.properties {
437            map.insert(name.to_string(), schema);
438        }
439        self
440    }
441
442    /// Sets the list of required parameter names
443    pub fn required(mut self, required: Vec<String>) -> Self {
444        self.required = required;
445        self
446    }
447
448    /// Builds the FunctionDeclaration
449    ///
450    /// # Validation
451    ///
452    /// This method performs validation and logs warnings for:
453    /// - Empty or whitespace-only function names
454    /// - Required parameters that don't exist in the properties schema
455    ///
456    /// These conditions may cause API errors but are allowed by the builder
457    /// for backwards compatibility.
458    pub fn build(self) -> FunctionDeclaration {
459        // Validate function name
460        if self.name.trim().is_empty() {
461            tracing::warn!(
462                "FunctionDeclaration built with empty or whitespace-only name. \
463                This will likely be rejected by the API."
464            );
465        }
466
467        // Validate required parameters exist in properties
468        if let serde_json::Value::Object(ref props) = self.properties {
469            for req in &self.required {
470                if !props.contains_key(req) {
471                    tracing::warn!(
472                        "FunctionDeclaration '{}' requires parameter '{}' which is not defined in properties. \
473                        This will likely cause API errors.",
474                        self.name,
475                        req
476                    );
477                }
478            }
479        }
480
481        FunctionDeclaration {
482            name: self.name,
483            description: self.description,
484            parameters: FunctionParameters {
485                type_: "object".to_string(),
486                properties: self.properties,
487                required: self.required,
488            },
489        }
490    }
491}
492
493/// Modes for function calling behavior.
494///
495/// This enum is marked `#[non_exhaustive]` for forward compatibility.
496/// New modes may be added in future versions.
497///
498/// # Forward Compatibility (Evergreen Philosophy)
499///
500/// When the API returns a mode value that this library doesn't recognize,
501/// it will be captured as `FunctionCallingMode::Unknown` rather than
502/// causing a deserialization error. This follows the
503/// [Evergreen spec](https://github.com/google-deepmind/evergreen-spec)
504/// philosophy of graceful degradation.
505///
506/// # Modes
507///
508/// - `Auto` (default): Model decides whether to call functions or respond naturally
509/// - `Any`: Model must call a function; guarantees schema adherence for calls
510/// - `None`: Prohibits function calling entirely
511/// - `Validated` (Preview): Ensures either function calls OR natural language adhere to schema
512#[derive(Clone, Debug, PartialEq)]
513#[non_exhaustive]
514pub enum FunctionCallingMode {
515    /// Model decides whether to call functions or respond with natural language.
516    Auto,
517    /// Model must call a function; guarantees schema adherence for calls.
518    Any,
519    /// Function calling is disabled.
520    None,
521    /// Ensures either function calls OR natural language adhere to schema.
522    ///
523    /// This is a preview mode that provides schema adherence guarantees
524    /// for both function call outputs and natural language responses.
525    Validated,
526    /// Unknown mode (for forward compatibility).
527    ///
528    /// This variant captures any unrecognized mode values from the API,
529    /// allowing the library to handle new modes gracefully.
530    ///
531    /// The `mode_type` field contains the unrecognized mode string,
532    /// and `data` contains the JSON value (typically the same string).
533    Unknown {
534        /// The unrecognized mode string from the API
535        mode_type: String,
536        /// The raw JSON value, preserved for debugging
537        data: serde_json::Value,
538    },
539}
540
541impl FunctionCallingMode {
542    /// Check if this is an unknown mode.
543    #[must_use]
544    pub const fn is_unknown(&self) -> bool {
545        matches!(self, Self::Unknown { .. })
546    }
547
548    /// Returns the mode type name if this is an unknown mode.
549    ///
550    /// Returns `None` for known modes.
551    #[must_use]
552    pub fn unknown_mode_type(&self) -> Option<&str> {
553        match self {
554            Self::Unknown { mode_type, .. } => Some(mode_type),
555            _ => None,
556        }
557    }
558
559    /// Returns the raw JSON data if this is an unknown mode.
560    ///
561    /// Returns `None` for known modes.
562    #[must_use]
563    pub fn unknown_data(&self) -> Option<&serde_json::Value> {
564        match self {
565            Self::Unknown { data, .. } => Some(data),
566            _ => None,
567        }
568    }
569}
570
571impl Serialize for FunctionCallingMode {
572    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
573    where
574        S: serde::Serializer,
575    {
576        match self {
577            Self::Auto => serializer.serialize_str("AUTO"),
578            Self::Any => serializer.serialize_str("ANY"),
579            Self::None => serializer.serialize_str("NONE"),
580            Self::Validated => serializer.serialize_str("VALIDATED"),
581            Self::Unknown { mode_type, .. } => serializer.serialize_str(mode_type),
582        }
583    }
584}
585
586impl<'de> Deserialize<'de> for FunctionCallingMode {
587    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
588    where
589        D: serde::Deserializer<'de>,
590    {
591        let value = serde_json::Value::deserialize(deserializer)?;
592
593        match value.as_str() {
594            Some("AUTO") => Ok(Self::Auto),
595            Some("ANY") => Ok(Self::Any),
596            Some("NONE") => Ok(Self::None),
597            Some("VALIDATED") => Ok(Self::Validated),
598            Some(other) => {
599                tracing::warn!(
600                    "Encountered unknown FunctionCallingMode '{}'. \
601                     This may indicate a new API feature. \
602                     The mode will be preserved in the Unknown variant.",
603                    other
604                );
605                Ok(Self::Unknown {
606                    mode_type: other.to_string(),
607                    data: value,
608                })
609            }
610            Option::None => {
611                // Non-string value - preserve it in Unknown
612                let mode_type = format!("<non-string: {}>", value);
613                tracing::warn!(
614                    "FunctionCallingMode received non-string value: {}. \
615                     Preserving in Unknown variant.",
616                    value
617                );
618                Ok(Self::Unknown {
619                    mode_type,
620                    data: value,
621                })
622            }
623        }
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use super::*;
630    use serde_json;
631
632    #[test]
633    fn test_serialize_function_declaration() {
634        let function = FunctionDeclaration::builder("get_weather")
635            .description("Get the current weather in a given location")
636            .parameter(
637                "location",
638                serde_json::json!({
639                    "type": "string",
640                    "description": "The city and state, e.g. San Francisco, CA"
641                }),
642            )
643            .required(vec!["location".to_string()])
644            .build();
645
646        let json_string = serde_json::to_string(&function).expect("Serialization failed");
647        let parsed: FunctionDeclaration =
648            serde_json::from_str(&json_string).expect("Deserialization failed");
649
650        assert_eq!(parsed.name(), "get_weather");
651        assert_eq!(
652            parsed.description(),
653            "Get the current weather in a given location"
654        );
655    }
656
657    #[test]
658    fn test_function_calling_mode_serialization() {
659        // Test all known modes
660        let test_cases = [
661            (FunctionCallingMode::Auto, "\"AUTO\""),
662            (FunctionCallingMode::Any, "\"ANY\""),
663            (FunctionCallingMode::None, "\"NONE\""),
664            (FunctionCallingMode::Validated, "\"VALIDATED\""),
665        ];
666
667        for (mode, expected_json) in test_cases {
668            let json = serde_json::to_string(&mode).expect("Serialization failed");
669            assert_eq!(json, expected_json);
670
671            let parsed: FunctionCallingMode =
672                serde_json::from_str(&json).expect("Deserialization failed");
673            assert_eq!(parsed, mode);
674        }
675    }
676
677    #[test]
678    fn test_function_calling_mode_unknown_roundtrip() {
679        // Test that unknown modes are preserved
680        let json = "\"FUTURE_MODE\"";
681        let parsed: FunctionCallingMode =
682            serde_json::from_str(json).expect("Deserialization failed");
683
684        assert!(parsed.is_unknown());
685        assert_eq!(parsed.unknown_mode_type(), Some("FUTURE_MODE"));
686
687        // Roundtrip should preserve the mode type
688        let reserialized = serde_json::to_string(&parsed).expect("Serialization failed");
689        assert_eq!(reserialized, json);
690    }
691
692    #[test]
693    fn test_function_calling_mode_helper_methods() {
694        // Known modes should not be unknown
695        assert!(!FunctionCallingMode::Auto.is_unknown());
696        assert!(!FunctionCallingMode::Any.is_unknown());
697        assert!(!FunctionCallingMode::None.is_unknown());
698        assert!(!FunctionCallingMode::Validated.is_unknown());
699
700        assert!(FunctionCallingMode::Auto.unknown_mode_type().is_none());
701        assert!(FunctionCallingMode::Auto.unknown_data().is_none());
702
703        // Unknown mode should report its type
704        let unknown = FunctionCallingMode::Unknown {
705            mode_type: "NEW_MODE".to_string(),
706            data: serde_json::json!("NEW_MODE"),
707        };
708        assert!(unknown.is_unknown());
709        assert_eq!(unknown.unknown_mode_type(), Some("NEW_MODE"));
710        assert!(unknown.unknown_data().is_some());
711    }
712
713    #[test]
714    fn test_function_calling_mode_non_string_value() {
715        // Test that non-string JSON values are handled gracefully
716        let json = "123";
717        let parsed: FunctionCallingMode =
718            serde_json::from_str(json).expect("Deserialization should succeed");
719
720        assert!(parsed.is_unknown());
721        // The mode_type should indicate it was a non-string value
722        assert!(parsed.unknown_mode_type().unwrap().contains("<non-string:"));
723    }
724
725    #[test]
726    fn test_tool_google_search_roundtrip() {
727        let tool = Tool::GoogleSearch;
728        let json = serde_json::to_string(&tool).expect("Serialization failed");
729        assert!(json.contains("\"type\":\"google_search\""));
730
731        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
732        assert!(matches!(parsed, Tool::GoogleSearch));
733    }
734
735    #[test]
736    fn test_tool_function_roundtrip() {
737        let tool = Tool::Function {
738            name: "get_weather".to_string(),
739            description: "Get weather".to_string(),
740            parameters: FunctionParameters::new(
741                "object".to_string(),
742                serde_json::json!({}),
743                vec![],
744            ),
745        };
746        let json = serde_json::to_string(&tool).expect("Serialization failed");
747        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
748
749        match parsed {
750            Tool::Function { name, .. } => assert_eq!(name, "get_weather"),
751            other => panic!("Expected Function variant, got {:?}", other),
752        }
753    }
754
755    #[test]
756    fn test_tool_mcp_server_roundtrip() {
757        let tool = Tool::McpServer {
758            name: "my-server".to_string(),
759            url: "https://mcp.example.com/api".to_string(),
760        };
761        let json = serde_json::to_string(&tool).expect("Serialization failed");
762        assert!(json.contains("\"type\":\"mcp_server\""));
763        assert!(json.contains("\"name\":\"my-server\""));
764        assert!(json.contains("\"url\":\"https://mcp.example.com/api\""));
765
766        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
767        match parsed {
768            Tool::McpServer { name, url } => {
769                assert_eq!(name, "my-server");
770                assert_eq!(url, "https://mcp.example.com/api");
771            }
772            other => panic!("Expected McpServer variant, got {:?}", other),
773        }
774    }
775
776    #[test]
777    fn test_tool_unknown_deserialization() {
778        // Simulate an unknown tool type from the API
779        let json = r#"{"type": "future_tool", "some_field": "value", "number": 42}"#;
780        let parsed: Tool = serde_json::from_str(json).expect("Deserialization failed");
781
782        match parsed {
783            Tool::Unknown { tool_type, data } => {
784                assert_eq!(tool_type, "future_tool");
785                assert_eq!(data.get("some_field").unwrap(), "value");
786                assert_eq!(data.get("number").unwrap(), 42);
787            }
788            _ => panic!("Expected Unknown variant"),
789        }
790    }
791
792    #[test]
793    fn test_tool_unknown_roundtrip() {
794        let tool = Tool::Unknown {
795            tool_type: "new_tool".to_string(),
796            data: serde_json::json!({"type": "new_tool", "config": {"enabled": true}}),
797        };
798        let json = serde_json::to_string(&tool).expect("Serialization failed");
799
800        // Should contain the type and config, but not duplicate "type"
801        assert!(json.contains("\"type\":\"new_tool\""));
802        assert!(json.contains("\"config\""));
803
804        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
805        match parsed {
806            Tool::Unknown { tool_type, .. } => assert_eq!(tool_type, "new_tool"),
807            _ => panic!("Expected Unknown variant"),
808        }
809    }
810
811    #[test]
812    fn test_tool_unknown_helper_methods() {
813        // Test Unknown variant
814        let unknown_tool = Tool::Unknown {
815            tool_type: "future_tool".to_string(),
816            data: serde_json::json!({"type": "future_tool", "setting": 123}),
817        };
818
819        assert!(unknown_tool.is_unknown());
820        assert_eq!(unknown_tool.unknown_tool_type(), Some("future_tool"));
821        let data = unknown_tool.unknown_data().expect("Should have data");
822        assert_eq!(data.get("setting").unwrap(), 123);
823    }
824
825    #[test]
826    fn test_tool_computer_use_roundtrip() {
827        let tool = Tool::ComputerUse {
828            environment: "browser".to_string(),
829            excluded_predefined_functions: vec!["submit_form".to_string(), "download".to_string()],
830        };
831        let json = serde_json::to_string(&tool).expect("Serialization failed");
832        assert!(json.contains("\"type\":\"computer_use\""));
833        assert!(json.contains("\"environment\":\"browser\""));
834        assert!(json.contains("\"excludedPredefinedFunctions\""));
835
836        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
837        match parsed {
838            Tool::ComputerUse {
839                environment,
840                excluded_predefined_functions,
841            } => {
842                assert_eq!(environment, "browser");
843                assert_eq!(excluded_predefined_functions.len(), 2);
844                assert!(excluded_predefined_functions.contains(&"submit_form".to_string()));
845            }
846            other => panic!("Expected ComputerUse variant, got {:?}", other),
847        }
848    }
849
850    #[test]
851    fn test_tool_computer_use_empty_exclusions() {
852        // Test that empty exclusions don't serialize the field
853        let tool = Tool::ComputerUse {
854            environment: "browser".to_string(),
855            excluded_predefined_functions: vec![],
856        };
857        let json = serde_json::to_string(&tool).expect("Serialization failed");
858        assert!(json.contains("\"type\":\"computer_use\""));
859        assert!(json.contains("\"environment\":\"browser\""));
860        assert!(!json.contains("excludedPredefinedFunctions"));
861
862        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
863        match parsed {
864            Tool::ComputerUse {
865                excluded_predefined_functions,
866                ..
867            } => {
868                assert!(excluded_predefined_functions.is_empty());
869            }
870            other => panic!("Expected ComputerUse variant, got {:?}", other),
871        }
872    }
873
874    #[test]
875    fn test_tool_known_types_helper_methods() {
876        // Test known types return None for unknown helpers
877        let google_search = Tool::GoogleSearch;
878        assert!(!google_search.is_unknown());
879        assert_eq!(google_search.unknown_tool_type(), None);
880        assert_eq!(google_search.unknown_data(), None);
881
882        let code_execution = Tool::CodeExecution;
883        assert!(!code_execution.is_unknown());
884        assert_eq!(code_execution.unknown_tool_type(), None);
885        assert_eq!(code_execution.unknown_data(), None);
886
887        let url_context = Tool::UrlContext;
888        assert!(!url_context.is_unknown());
889        assert_eq!(url_context.unknown_tool_type(), None);
890        assert_eq!(url_context.unknown_data(), None);
891
892        let computer_use = Tool::ComputerUse {
893            environment: "browser".to_string(),
894            excluded_predefined_functions: vec![],
895        };
896        assert!(!computer_use.is_unknown());
897        assert_eq!(computer_use.unknown_tool_type(), None);
898        assert_eq!(computer_use.unknown_data(), None);
899
900        let function = Tool::Function {
901            name: "test".to_string(),
902            description: "Test function".to_string(),
903            parameters: FunctionParameters::new(
904                "object".to_string(),
905                serde_json::json!({}),
906                vec![],
907            ),
908        };
909        assert!(!function.is_unknown());
910        assert_eq!(function.unknown_tool_type(), None);
911        assert_eq!(function.unknown_data(), None);
912    }
913
914    #[test]
915    fn test_tool_file_search_roundtrip() {
916        let tool = Tool::FileSearch {
917            store_names: vec!["store1".to_string(), "store2".to_string()],
918            top_k: Some(5),
919            metadata_filter: Some("category:technical".to_string()),
920        };
921        let json = serde_json::to_string(&tool).expect("Serialization failed");
922        assert!(json.contains("\"type\":\"file_search\""));
923        assert!(json.contains("\"file_search_store_names\"")); // Wire format uses full name
924        assert!(json.contains("\"top_k\":5"));
925        assert!(json.contains("\"metadata_filter\":\"category:technical\""));
926
927        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
928        match parsed {
929            Tool::FileSearch {
930                store_names,
931                top_k,
932                metadata_filter,
933            } => {
934                assert_eq!(store_names, vec!["store1", "store2"]);
935                assert_eq!(top_k, Some(5));
936                assert_eq!(metadata_filter, Some("category:technical".to_string()));
937            }
938            other => panic!("Expected FileSearch variant, got {:?}", other),
939        }
940    }
941
942    #[test]
943    fn test_tool_file_search_minimal() {
944        // Test with only required field (store names)
945        let tool = Tool::FileSearch {
946            store_names: vec!["my-store".to_string()],
947            top_k: None,
948            metadata_filter: None,
949        };
950        let json = serde_json::to_string(&tool).expect("Serialization failed");
951        assert!(json.contains("\"type\":\"file_search\""));
952        assert!(json.contains("\"file_search_store_names\"")); // Wire format uses full name
953        // Optional fields should not appear
954        assert!(!json.contains("\"top_k\""));
955        assert!(!json.contains("\"metadata_filter\""));
956
957        let parsed: Tool = serde_json::from_str(&json).expect("Deserialization failed");
958        match parsed {
959            Tool::FileSearch {
960                store_names,
961                top_k,
962                metadata_filter,
963            } => {
964                assert_eq!(store_names, vec!["my-store"]);
965                assert_eq!(top_k, None);
966                assert_eq!(metadata_filter, None);
967            }
968            other => panic!("Expected FileSearch variant, got {:?}", other),
969        }
970    }
971
972    #[test]
973    fn test_tool_file_search_helper_methods() {
974        let file_search = Tool::FileSearch {
975            store_names: vec!["store".to_string()],
976            top_k: None,
977            metadata_filter: None,
978        };
979        assert!(!file_search.is_unknown());
980        assert_eq!(file_search.unknown_tool_type(), None);
981        assert_eq!(file_search.unknown_data(), None);
982    }
983}