mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(sse): strip Gemini built-in tools when functionDeclarations present in Antigravity envelope (#4821)
Integrated into release/v3.8.37 — cherry-picked defining commit onto release tip; CHANGELOG re-merged; tests green.
This commit is contained in:
committed by
GitHub
parent
7ff7af585b
commit
c4e5d586fc
@@ -36,6 +36,16 @@ import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiTools
|
||||
// See: https://github.com/keisksw/antigravity-output-analysis
|
||||
const ANTIGRAVITY_CLAUDE_MAX_OUTPUT_TOKENS = 16_384;
|
||||
|
||||
// Gemini built-in tool names that Antigravity's v1internal endpoint rejects with a
|
||||
// 400 when they are mixed with functionDeclarations in the same request. These must
|
||||
// be stripped from the Cloud Code envelope's functionDeclarations.
|
||||
const GEMINI_BUILTIN_TOOL_NAMES = new Set<string>([
|
||||
"google_search",
|
||||
"web_search",
|
||||
"search_web",
|
||||
"googleSearch",
|
||||
]);
|
||||
|
||||
type GeminiPart = Record<string, unknown>;
|
||||
type GeminiContent = { role: string; parts: GeminiPart[] };
|
||||
|
||||
@@ -753,8 +763,34 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
||||
envelope.request.systemInstruction = { role: "system", parts: [defaultPart] };
|
||||
}
|
||||
|
||||
// Add toolConfig for Antigravity
|
||||
if (geminiCLI.tools?.some((tool) => Array.isArray(tool.functionDeclarations))) {
|
||||
// Strip Gemini built-in tool *names* out of functionDeclarations: Antigravity's
|
||||
// v1internal endpoint returns 400 when a built-in tool (google_search etc.) is
|
||||
// mixed with functionDeclarations in the same request. Native grounding entries
|
||||
// (e.g. `{ googleSearch: {} }`) are left intact; only the functionDeclarations
|
||||
// arrays are cleaned, and a declarations entry that becomes empty is dropped.
|
||||
if (envelope.request.tools && envelope.request.tools.length > 0) {
|
||||
const cleanedTools = envelope.request.tools
|
||||
.map((tool) => {
|
||||
if (!Array.isArray(tool.functionDeclarations)) {
|
||||
return tool;
|
||||
}
|
||||
const customDecls = tool.functionDeclarations.filter(
|
||||
(fn) => !GEMINI_BUILTIN_TOOL_NAMES.has(fn.name)
|
||||
);
|
||||
return { ...tool, functionDeclarations: customDecls };
|
||||
})
|
||||
.filter(
|
||||
(tool) =>
|
||||
!Array.isArray(tool.functionDeclarations) || tool.functionDeclarations.length > 0
|
||||
);
|
||||
envelope.request.tools = cleanedTools.length > 0 ? cleanedTools : undefined;
|
||||
}
|
||||
|
||||
// Add toolConfig for Antigravity only when custom functionDeclarations remain.
|
||||
const hasCustomTools = envelope.request.tools?.some(
|
||||
(tool) => (tool.functionDeclarations?.length ?? 0) > 0
|
||||
);
|
||||
if (hasCustomTools) {
|
||||
envelope.request.toolConfig = {
|
||||
functionCallingConfig: { mode: "VALIDATED" },
|
||||
};
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { openaiToAntigravityRequest } from "../../open-sse/translator/request/openai-to-gemini.ts";
|
||||
|
||||
// Regression guard for the Antigravity v1internal 400 that fires when a Gemini
|
||||
// built-in tool (google_search / web_search / googleSearch ...) is sent in the
|
||||
// same request as functionDeclarations. The Cloud Code envelope must strip the
|
||||
// built-in tool names out of functionDeclarations before dispatch.
|
||||
// Inspired-by decolua/9router#1095 (author @vanszs).
|
||||
|
||||
type EnvelopeTool = { functionDeclarations?: Array<{ name: string }> };
|
||||
|
||||
function declaredToolNames(result: { request: { tools?: EnvelopeTool[] } }): string[] {
|
||||
return (result.request.tools ?? []).flatMap((tool) =>
|
||||
(tool.functionDeclarations ?? []).map((fn) => fn.name)
|
||||
);
|
||||
}
|
||||
|
||||
const CREDS = { projectId: "proj-1" } as never;
|
||||
|
||||
test("Antigravity envelope strips built-in tool names mixed with custom functionDeclarations (#1095)", () => {
|
||||
const result = openaiToAntigravityRequest(
|
||||
"gemini-2.5-pro",
|
||||
{
|
||||
messages: [{ role: "user", content: "Search and read" }],
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: { name: "google_search", parameters: { type: "object", properties: {} } },
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: { name: "read_file", parameters: { type: "object", properties: {} } },
|
||||
},
|
||||
],
|
||||
},
|
||||
false,
|
||||
CREDS
|
||||
);
|
||||
|
||||
const names = declaredToolNames(result);
|
||||
// Built-in tool name must be gone from functionDeclarations...
|
||||
assert.ok(
|
||||
!names.includes("google_search") && !names.includes("googleSearch"),
|
||||
`expected built-in tool name stripped, got ${JSON.stringify(names)}`
|
||||
);
|
||||
// ...while the custom declaration survives.
|
||||
assert.ok(names.includes("read_file"), `expected custom tool kept, got ${JSON.stringify(names)}`);
|
||||
// toolConfig stays set because custom declarations remain.
|
||||
assert.deepEqual(result.request.toolConfig, {
|
||||
functionCallingConfig: { mode: "VALIDATED" },
|
||||
});
|
||||
});
|
||||
|
||||
test("Antigravity envelope keeps a normal custom-only tool request intact (#1095 regression)", () => {
|
||||
const result = openaiToAntigravityRequest(
|
||||
"gemini-2.5-pro",
|
||||
{
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: { name: "weather", parameters: { type: "object", properties: {} } },
|
||||
},
|
||||
],
|
||||
},
|
||||
false,
|
||||
CREDS
|
||||
);
|
||||
|
||||
const names = declaredToolNames(result);
|
||||
assert.deepEqual(names, ["weather"]);
|
||||
assert.deepEqual(result.request.toolConfig, {
|
||||
functionCallingConfig: { mode: "VALIDATED" },
|
||||
});
|
||||
});
|
||||
|
||||
test("Antigravity envelope drops tools + toolConfig when only built-in tools are present (#1095)", () => {
|
||||
const result = openaiToAntigravityRequest(
|
||||
"gemini-2.5-pro",
|
||||
{
|
||||
messages: [{ role: "user", content: "Search the web" }],
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: { name: "google_search", parameters: { type: "object", properties: {} } },
|
||||
},
|
||||
],
|
||||
},
|
||||
false,
|
||||
CREDS
|
||||
);
|
||||
|
||||
const names = declaredToolNames(result);
|
||||
assert.ok(
|
||||
!names.includes("google_search") && !names.includes("googleSearch"),
|
||||
`expected no built-in functionDeclarations, got ${JSON.stringify(names)}`
|
||||
);
|
||||
// No custom declarations remain -> no VALIDATED toolConfig is forced.
|
||||
assert.equal(result.request.toolConfig, undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user