diff --git a/agent/gemini_schema.py b/agent/gemini_schema.py index 904c99d31b8..1f179fb2af4 100644 --- a/agent/gemini_schema.py +++ b/agent/gemini_schema.py @@ -72,6 +72,18 @@ def sanitize_gemini_schema(schema: Any) -> Dict[str, Any]: if isinstance(item, dict) ] continue + if key == "enum": + # Gemini's Schema.enum requires TYPE_STRING values — it rejects + # integer/float/bool enum entries even when the schema's ``type`` + # is ``integer`` or ``number``. Stringify every value so tools + # like discord_server's auto_archive_duration=[60, 1440, ...] + # don't trigger "Invalid value at enum[0] (TYPE_STRING)" 400s. + if isinstance(value, list): + cleaned[key] = [ + str(v) if not isinstance(v, str) else v + for v in value + ] + continue cleaned[key] = value return cleaned diff --git a/tests/agent/test_gemini_schema.py b/tests/agent/test_gemini_schema.py new file mode 100644 index 00000000000..94f4538b72f --- /dev/null +++ b/tests/agent/test_gemini_schema.py @@ -0,0 +1,66 @@ +"""Tests for agent.gemini_schema.sanitize_gemini_schema.""" + +from agent.gemini_schema import ( + sanitize_gemini_schema, + sanitize_gemini_tool_parameters, +) + + +def test_integer_enum_is_stringified(): + """Gemini's Schema.enum only accepts TYPE_STRING values. + + Tools like discord_server's auto_archive_duration declare integer enums + ([60, 1440, 4320, 10080]). Those would raise Gemini HTTP 400 + 'Invalid value at enum[0] (TYPE_STRING)' if passed through unchanged. + """ + schema = { + "type": "integer", + "enum": [60, 1440, 4320, 10080], + "description": "Thread archive duration in minutes.", + } + cleaned = sanitize_gemini_schema(schema) + assert cleaned["enum"] == ["60", "1440", "4320", "10080"] + assert cleaned["type"] == "integer" + + +def test_string_enum_is_preserved(): + schema = {"type": "string", "enum": ["alpha", "beta", "gamma"]} + cleaned = sanitize_gemini_schema(schema) + assert cleaned["enum"] == ["alpha", "beta", "gamma"] + + +def test_mixed_enum_is_stringified(): + schema = {"enum": ["a", 1, 2.5, True]} + cleaned = sanitize_gemini_schema(schema) + assert cleaned["enum"] == ["a", "1", "2.5", "True"] + + +def test_nested_enum_inside_properties_is_stringified(): + schema = { + "type": "object", + "properties": { + "auto_archive_duration": { + "type": "integer", + "enum": [60, 1440], + }, + "name": {"type": "string"}, + }, + } + cleaned = sanitize_gemini_tool_parameters(schema) + assert cleaned["properties"]["auto_archive_duration"]["enum"] == ["60", "1440"] + assert "enum" not in cleaned["properties"]["name"] + + +def test_enum_in_items_is_stringified(): + schema = { + "type": "array", + "items": {"type": "integer", "enum": [1, 2, 3]}, + } + cleaned = sanitize_gemini_schema(schema) + assert cleaned["items"]["enum"] == ["1", "2", "3"] + + +def test_non_list_enum_is_dropped(): + schema = {"enum": "not-a-list"} + cleaned = sanitize_gemini_schema(schema) + assert "enum" not in cleaned