Files
hive/core/tests/test_antigravity_schema.py
Hundao 4a9b22719b fix(antigravity): unblock Gemini chats — schema sanitizer + UA bump (#7170)
* fix(antigravity): translate JSON Schema unions to Gemini nullable

Tool parameter schemas using JSON Schema 2020-12 unions like
"type": ["string", "null"] crash Gemini's function_declarations parser
with HTTP 400. Two existing tools trip this:

- core/framework/tasks/tools/colony_tools.py:52 (owner in _update_schema)
- core/framework/tasks/tools/session_tools.py:84-87 (same shape)

Add an adapter-level sanitizer that walks the schema tree and converts
union-with-null to OpenAPI 3.0 "nullable": true (which Gemini accepts).
Recurses into properties, items, additionalProperties, and the
anyOf/oneOf/allOf combinators. Source schemas remain valid JSON Schema
so OpenAI/Anthropic backends are unaffected.

* fix(antigravity): bump spoofed UA past Google's deprecation cutoff

Google has deprecated client version "Antigravity/1.18.3" — chats now
return "This version of Antigravity is no longer supported" instead of
a real model response.

Bump the spoofed User-Agent to "Antigravity/1.23.2" + "Electron/39.2.3"
(current desktop release) and add a comment that this needs periodic
re-bumping. A more durable fix (auto-detect from the installed app's
Info.plist) is a follow-up.

* fix(antigravity): fail loud on multi-type non-null Gemini schema unions

Per review on PR #7170: silently picking the first type from a union
like ["string", "integer", "null"] changes the contract for callers
that rely on the other types, and the failure is hard to diagnose at
the Gemini side. Replace the silent narrowing with a ValueError that
points the schema author at anyOf or a single type.

A repo scan finds no current Gemini-bound schemas using multi-type
non-null unions, so this branch is preventative for future authors.

* chore(antigravity): drop em dash from test docstring
2026-05-05 01:16:48 +08:00

74 lines
2.3 KiB
Python

"""Tests for the Antigravity Gemini schema sanitizer.
Run with:
cd core
pytest tests/test_antigravity_schema.py -v
"""
import pytest
from framework.llm.antigravity import _sanitize_schema_for_gemini
def test_union_with_null_becomes_nullable():
assert _sanitize_schema_for_gemini({"type": ["string", "null"]}) == {
"type": "string",
"nullable": True,
}
def test_plain_schema_passthrough():
assert _sanitize_schema_for_gemini({"type": "string"}) == {"type": "string"}
def test_recurses_into_properties():
out = _sanitize_schema_for_gemini(
{
"type": "object",
"properties": {
"id": {"type": "integer"},
"owner": {"type": ["string", "null"]},
},
"required": ["id"],
}
)
assert out["properties"]["id"] == {"type": "integer"}
assert out["properties"]["owner"] == {"type": "string", "nullable": True}
assert out["required"] == ["id"]
def test_recurses_into_items():
assert _sanitize_schema_for_gemini({"type": "array", "items": {"type": ["integer", "null"]}}) == {
"type": "array",
"items": {"type": "integer", "nullable": True},
}
def test_recurses_into_combinators():
assert _sanitize_schema_for_gemini({"anyOf": [{"type": ["string", "null"]}, {"type": "integer"}]}) == {
"anyOf": [{"type": "string", "nullable": True}, {"type": "integer"}]
}
def test_does_not_mutate_input():
schema = {"type": "object", "properties": {"x": {"type": ["string", "null"]}}}
snapshot = {"type": "object", "properties": {"x": {"type": ["string", "null"]}}}
_sanitize_schema_for_gemini(schema)
assert schema == snapshot
def test_pure_null_type_falls_back_to_string():
assert _sanitize_schema_for_gemini({"type": ["null"]}) == {
"type": "string",
"nullable": True,
}
def test_multi_type_non_null_union_raises():
"""Silently picking one type would change the contract; fail loud instead."""
with pytest.raises(ValueError, match="Unsupported Gemini schema union"):
_sanitize_schema_for_gemini({"type": ["string", "integer", "null"]})
with pytest.raises(ValueError, match="Unsupported Gemini schema union"):
_sanitize_schema_for_gemini({"type": ["string", "integer"]})