mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-08 08:12:20 +03:00
fix(antigravity): sanitize Cloud Code safety settings (#6839)
Co-authored-by: kfiramar <83420275+kfiramar@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a3e38a2c0c
commit
112b1499df
@@ -87,6 +87,9 @@ const ANTIGRAVITY_TRANSIENT_STATUSES = new Set([
|
||||
HTTP_STATUS.SERVICE_UNAVAILABLE,
|
||||
HTTP_STATUS.GATEWAY_TIMEOUT,
|
||||
]);
|
||||
const ANTIGRAVITY_UNSUPPORTED_SAFETY_CATEGORIES = new Set<string>([
|
||||
"HARM_CATEGORY_CIVIC_INTEGRITY",
|
||||
]);
|
||||
// The upstream API uses plain model IDs (no -high/-low suffix).
|
||||
// Tier suffixes were speculative and caused 404 for gemini-3.x models — the
|
||||
// bare-Pro→Low normalization was retired (the set stayed empty, making the guard
|
||||
@@ -440,6 +443,14 @@ function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
: null;
|
||||
}
|
||||
|
||||
function getAntigravitySafetySettings(safetySettings: unknown): unknown[] {
|
||||
const source = Array.isArray(safetySettings) ? safetySettings : DEFAULT_SAFETY_SETTINGS;
|
||||
return source.filter((setting) => {
|
||||
const category = asRecord(setting)?.category;
|
||||
return typeof category !== "string" || !ANTIGRAVITY_UNSUPPORTED_SAFETY_CATEGORIES.has(category);
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizeAntigravityGeminiRequest(
|
||||
request: Record<string, unknown>
|
||||
): Record<string, unknown> {
|
||||
@@ -687,12 +698,10 @@ export class AntigravityExecutor extends BaseExecutor {
|
||||
credentials,
|
||||
typeof normalizedRequest?.sessionId === "string" ? normalizedRequest.sessionId : undefined
|
||||
),
|
||||
// #5003: default to all-OFF safety for parity with the native Gemini paths
|
||||
// (claude-to-gemini / openai-to-gemini both default to DEFAULT_SAFETY_SETTINGS).
|
||||
// Previously this was `undefined`, which JSON.stringify drops, so Google Cloud Code
|
||||
// applied its server-side defaults that false-flag benign technical prompts as
|
||||
// `prohibited_content` (HTTP 200 + blocked body → terminal combo failover).
|
||||
safetySettings: normalizedRequest?.safetySettings ?? DEFAULT_SAFETY_SETTINGS,
|
||||
// #5003: send explicit all-OFF safety entries that Cloud Code accepts. Omitting the
|
||||
// field lets Cloud Code apply server-side defaults that false-flag benign technical
|
||||
// prompts as `prohibited_content`.
|
||||
safetySettings: getAntigravitySafetySettings(normalizedRequest?.safetySettings),
|
||||
toolConfig:
|
||||
Array.isArray(normalizedRequest?.tools) && normalizedRequest.tools.length > 0
|
||||
? { functionCallingConfig: { mode: "VALIDATED" } }
|
||||
@@ -700,7 +709,9 @@ export class AntigravityExecutor extends BaseExecutor {
|
||||
};
|
||||
|
||||
const transformedRequest = isClaude
|
||||
? stripTrailingAntigravityAssistantTurn(sanitizeAntigravityGeminiRequest(rawTransformedRequest))
|
||||
? stripTrailingAntigravityAssistantTurn(
|
||||
sanitizeAntigravityGeminiRequest(rawTransformedRequest)
|
||||
)
|
||||
: rawTransformedRequest;
|
||||
|
||||
// Obfuscate sensitive client names in user content (e.g. "OpenCode", "Cursor")
|
||||
|
||||
@@ -633,6 +633,7 @@ function wrapInCloudCodeEnvelope(model, cloudCodeRequest, credentials = null) {
|
||||
systemInstruction: cloudCodeRequest.systemInstruction,
|
||||
generationConfig: applyAntigravityGenerationDefaults(cloudCodeRequest.generationConfig),
|
||||
tools: cloudCodeRequest.tools,
|
||||
safetySettings: cloudCodeRequest.safetySettings,
|
||||
},
|
||||
model: cleanModel,
|
||||
userAgent: getAntigravityEnvelopeUserAgent(credentials),
|
||||
|
||||
@@ -3,13 +3,14 @@ import assert from "node:assert/strict";
|
||||
|
||||
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.ts";
|
||||
import { DEFAULT_SAFETY_SETTINGS } from "../../open-sse/translator/helpers/geminiHelper.ts";
|
||||
import { openaiToAntigravityRequest } from "../../open-sse/translator/request/openai-to-gemini.ts";
|
||||
|
||||
// Regression for #5003: the Antigravity (Google Cloud Code) request builder explicitly set
|
||||
// `safetySettings: undefined`, which `JSON.stringify` drops entirely. With no safetySettings
|
||||
// reaching Cloud Code, Google applies its server-side safety defaults that false-flag benign
|
||||
// technical prompts as `prohibited_content` (HTTP 200 with a blocked body that combo failover
|
||||
// treats as terminal). The native Gemini paths all default to all-OFF
|
||||
// (DEFAULT_SAFETY_SETTINGS); Antigravity must match for parity.
|
||||
// treats as terminal). Antigravity still needs explicit all-OFF safety settings,
|
||||
// but Cloud Code rejects HARM_CATEGORY_CIVIC_INTEGRITY on the v1internal endpoint.
|
||||
|
||||
test("transformRequest defaults safetySettings to all-OFF when none supplied (#5003)", async () => {
|
||||
const executor = new AntigravityExecutor();
|
||||
@@ -26,17 +27,21 @@ test("transformRequest defaults safetySettings to all-OFF when none supplied (#5
|
||||
|
||||
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
|
||||
const innerRequest = result.request as Record<string, unknown>;
|
||||
const antigravitySafetySettings = DEFAULT_SAFETY_SETTINGS.filter(
|
||||
(setting) => setting.category !== "HARM_CATEGORY_CIVIC_INTEGRITY"
|
||||
);
|
||||
assert.deepEqual(
|
||||
innerRequest.safetySettings,
|
||||
DEFAULT_SAFETY_SETTINGS,
|
||||
"safetySettings must default to all-OFF for parity with native Gemini paths"
|
||||
antigravitySafetySettings,
|
||||
"safetySettings must default to all-OFF entries accepted by Cloud Code"
|
||||
);
|
||||
});
|
||||
|
||||
test("transformRequest honors a caller-supplied safetySettings (#5003)", async () => {
|
||||
test("transformRequest honors caller-supplied safetySettings accepted by Cloud Code (#5003)", async () => {
|
||||
const executor = new AntigravityExecutor();
|
||||
const callerSafety = [
|
||||
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE" },
|
||||
{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "OFF" },
|
||||
];
|
||||
const body = {
|
||||
request: {
|
||||
@@ -54,7 +59,34 @@ test("transformRequest honors a caller-supplied safetySettings (#5003)", async (
|
||||
const innerRequest = result.request as Record<string, unknown>;
|
||||
assert.deepEqual(
|
||||
innerRequest.safetySettings,
|
||||
callerSafety,
|
||||
"a caller-supplied safetySettings must not be clobbered"
|
||||
[{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE" }],
|
||||
"caller-supplied safetySettings should preserve accepted entries and drop rejected ones"
|
||||
);
|
||||
});
|
||||
|
||||
test("OpenAI Antigravity translation preserves caller-supplied safetySettings (#5003)", async () => {
|
||||
const executor = new AntigravityExecutor();
|
||||
const callerSafety = [
|
||||
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE" },
|
||||
{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "OFF" },
|
||||
];
|
||||
const translated = openaiToAntigravityRequest(
|
||||
"gemini-2.5-flash",
|
||||
{
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: callerSafety,
|
||||
},
|
||||
true,
|
||||
{ projectId: "project-1" }
|
||||
);
|
||||
|
||||
const result = await executor.transformRequest("antigravity/gemini-2.5-flash", translated, true, {
|
||||
projectId: "project-1",
|
||||
});
|
||||
|
||||
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
|
||||
const innerRequest = result.request as Record<string, unknown>;
|
||||
assert.deepEqual(innerRequest.safetySettings, [
|
||||
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE" },
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user