Skip to main content

vtcode_core/llm/providers/gemini/
sanitize.rs

1use super::*;
2
3pub fn sanitize_function_parameters(parameters: Value) -> Value {
4    sanitize_function_parameters_impl(parameters, false)
5}
6
7fn should_alias_description_property(properties: &Map<String, Value>) -> bool {
8    properties.contains_key("description") && !properties.contains_key("details")
9}
10
11fn sanitize_property_name(name: &str, alias_description: bool) -> &str {
12    if alias_description && name == "description" {
13        "details"
14    } else {
15        name
16    }
17}
18
19fn sanitize_function_parameters_impl(parameters: Value, inside_properties_map: bool) -> Value {
20    match parameters {
21        Value::Object(map) => {
22            // List of unsupported fields for Gemini API
23            // Reference: https://ai.google.dev/gemini-api/docs/function-calling
24            const UNSUPPORTED_FIELDS: &[&str] = &[
25                "additionalProperties",
26                "oneOf",
27                "anyOf",
28                "allOf",
29                "exclusiveMaximum",
30                "exclusiveMinimum",
31                "minimum",
32                "maximum",
33                "$schema",
34                "$id",
35                "$ref",
36                "definitions",
37                "patternProperties",
38                "dependencies",
39                "const",
40                "if",
41                "then",
42                "else",
43                "not",
44                "contentMediaType",
45                "contentEncoding",
46            ];
47
48            let alias_description_property =
49                inside_properties_map && should_alias_description_property(&map);
50            let alias_required_description = map
51                .get("properties")
52                .and_then(Value::as_object)
53                .is_some_and(should_alias_description_property);
54
55            // Process all properties recursively, removing unsupported fields
56            let mut sanitized = Map::new();
57            for (key, value) in map {
58                let is_properties_map = key == "properties";
59                // Skip unsupported fields at this level
60                if !inside_properties_map
61                    && (UNSUPPORTED_FIELDS.contains(&key.as_str()) || key.starts_with("x-"))
62                {
63                    continue;
64                }
65                // Recursively sanitize nested values
66                let sanitized_key = if inside_properties_map {
67                    sanitize_property_name(&key, alias_description_property).to_string()
68                } else {
69                    key
70                };
71                sanitized.insert(
72                    sanitized_key,
73                    sanitize_function_parameters_impl(value, is_properties_map),
74                );
75            }
76
77            let property_names = sanitized
78                .get("properties")
79                .and_then(Value::as_object)
80                .map(|properties| properties.keys().cloned().collect::<Vec<_>>());
81            let drop_required = sanitized
82                .get_mut("required")
83                .and_then(Value::as_array_mut)
84                .map(|required| {
85                    let Some(property_names) = property_names.as_ref() else {
86                        return true;
87                    };
88
89                    for item in required.iter_mut() {
90                        if let Some(name) = item.as_str() {
91                            let sanitized_name =
92                                sanitize_property_name(name, alias_required_description);
93                            if sanitized_name != name {
94                                *item = Value::String(sanitized_name.to_string());
95                            }
96                        }
97                    }
98                    required.retain(|item| {
99                        item.as_str()
100                            .map(|name| property_names.iter().any(|property| property == name))
101                            .unwrap_or(false)
102                    });
103                    required.is_empty()
104                })
105                .unwrap_or(false);
106            if drop_required {
107                sanitized.remove("required");
108            }
109
110            Value::Object(sanitized)
111        }
112        Value::Array(values) => Value::Array(
113            values
114                .into_iter()
115                .map(|value| sanitize_function_parameters_impl(value, false))
116                .collect(),
117        ),
118        other => other,
119    }
120}