fix(gemini): preserve pattern in antigravity tool schema sanitizer (#4651)

Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-23 21:51:17 -03:00
committed by GitHub
parent a9368af7df
commit 35a3962cf0
3 changed files with 65 additions and 7 deletions

View File

@@ -10,7 +10,10 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYS = new Set([
"maxLength",
"exclusiveMinimum",
"exclusiveMaximum",
"pattern",
// NOTE: `pattern` is intentionally NOT in this set. Antigravity (Gemini-derived
// surface) accepts `pattern` on string constraints, and glob/grep/file-search
// tools depend on it to express their argument regex. Removing it produced
// upstream 400s and wrong-tool semantics (decolua/9router#1368).
"minItems",
"maxItems",
"format",

View File

@@ -0,0 +1,54 @@
// Regression test: `pattern` must be preserved in antigravity tool schemas.
//
// Upstream: decolua/9router @ f6c2f7ca / Fixes decolua/9router#1368.
//
// Tools such as glob/grep/file-search rely on a `pattern` constraint on
// string parameters. Antigravity (Gemini-derived) DOES accept `pattern`,
// so removing it for that surface drops critical tool semantics and
// produces upstream 400s / wrong-tool-call behavior.
//
// Two assertions, mirroring how the field is consumed downstream:
// 1) `pattern` must NOT live in the unsupported keyword set used by the
// antigravity schema cleaner.
// 2) End-to-end, `cleanJSONSchemaForAntigravity` must preserve a `pattern`
// constraint placed on a string property (typical glob/grep tool).
import test from "node:test";
import assert from "node:assert/strict";
import {
GEMINI_UNSUPPORTED_SCHEMA_KEYS,
cleanJSONSchemaForAntigravity,
} from "../../open-sse/translator/helpers/geminiHelper.ts";
test("GEMINI_UNSUPPORTED_SCHEMA_KEYS does not strip `pattern` for antigravity", () => {
assert.equal(
GEMINI_UNSUPPORTED_SCHEMA_KEYS.has("pattern"),
false,
"`pattern` is supported by antigravity and must be preserved on tool schemas"
);
});
test("cleanJSONSchemaForAntigravity preserves `pattern` on glob/grep-style tool schema", () => {
const schema = {
type: "object",
properties: {
glob: {
type: "string",
description: "Glob pattern to match files",
pattern: "^[A-Za-z0-9_\\-/*.]+$",
},
},
required: ["glob"],
};
const cleaned = cleanJSONSchemaForAntigravity(schema) as {
properties: { glob: { pattern?: string } };
};
assert.equal(
cleaned.properties.glob.pattern,
"^[A-Za-z0-9_\\-/*.]+$",
"`pattern` constraint on a string property must survive antigravity cleaning"
);
});

View File

@@ -41,9 +41,10 @@ test("#1368: a property named 'pattern' survives Gemini schema sanitization", ()
);
});
test("#1368: a string-level `pattern` CONSTRAINT is still stripped", () => {
// When `pattern` is an actual validation constraint on a string schema node,
// Gemini does not support it, so it must still be removed.
test("#1368: a string-level `pattern` CONSTRAINT is preserved for antigravity", () => {
// Antigravity (Gemini-derived) DOES accept `pattern` on string constraints.
// Stripping it broke glob/grep/file-search tools that express their argument
// regex via `pattern`. The sanitizer must keep it. (Ported from 9router @ f6c2f7ca.)
const schema = {
type: "object",
properties: {
@@ -58,8 +59,8 @@ test("#1368: a string-level `pattern` CONSTRAINT is still stripped", () => {
assert.ok(cleaned.properties.code, "the `code` property itself survives");
assert.equal(
Object.prototype.hasOwnProperty.call(cleaned.properties.code, "pattern"),
false,
"the string `pattern` constraint must be stripped"
(cleaned.properties.code as { pattern?: string }).pattern,
"^[A-Z]{3}$",
"the string `pattern` constraint must be preserved for antigravity"
);
});