From 917314156e8124f54852acaedf4e9769c2689fa6 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Thu, 23 Jul 2026 00:45:56 -0300 Subject: [PATCH] fix(gemini): drop HARM_CATEGORY_CIVIC_INTEGRITY from the default Gemini safety settings (#8231) (#8238) Co-authored-by: Probe Test --- .../8231-gemini-civic-integrity-default.md | 1 + open-sse/translator/helpers/geminiHelper.ts | 14 +++- .../translator/request/claude-to-gemini.ts | 5 +- .../openai-to-gemini/basic-chat.json | 4 -- .../modern-gemini-default-thinking.json | 4 -- .../openai-to-gemini/with-system.json | 4 -- ...231-gemini-civic-integrity-default.test.ts | 68 +++++++++++++++++++ 7 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 changelog.d/fixes/8231-gemini-civic-integrity-default.md create mode 100644 tests/unit/8231-gemini-civic-integrity-default.test.ts diff --git a/changelog.d/fixes/8231-gemini-civic-integrity-default.md b/changelog.d/fixes/8231-gemini-civic-integrity-default.md new file mode 100644 index 0000000000..fc4159a0c0 --- /dev/null +++ b/changelog.d/fixes/8231-gemini-civic-integrity-default.md @@ -0,0 +1 @@ +- fix(gemini): drop HARM_CATEGORY_CIVIC_INTEGRITY from the default Gemini safety settings (#8231) diff --git a/open-sse/translator/helpers/geminiHelper.ts b/open-sse/translator/helpers/geminiHelper.ts index 7f45cf329d..19ce6387ee 100644 --- a/open-sse/translator/helpers/geminiHelper.ts +++ b/open-sse/translator/helpers/geminiHelper.ts @@ -90,13 +90,23 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYS = new Set([ export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [...GEMINI_UNSUPPORTED_SCHEMA_KEYS]; -// Default safety settings +// Default safety settings for the standard Gemini API surface. +// +// HARM_CATEGORY_CIVIC_INTEGRITY is intentionally NOT included here (#8231): the +// dynamic validation on some models/endpoints rejects it with a hard 400 +// (`safety_settings[N]: element predicate failed`), taking down every request +// through that model. The Antigravity/Cloud Code surface already worked around +// this for #5003 (see ANTIGRAVITY_UNSUPPORTED_SAFETY_CATEGORIES in +// open-sse/executors/antigravity.ts) — this drops the same category from the +// standard-path default so behavior is consistent across Gemini surfaces. A +// caller that explicitly supplies safetySettings (including one that itself +// requests CIVIC_INTEGRITY) is still honored as-is — only this unconditional +// default is scoped down. export const DEFAULT_SAFETY_SETTINGS = [ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "OFF" }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "OFF" }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "OFF" }, { category: "HARM_CATEGORY_HARASSMENT", threshold: "OFF" }, - { category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "OFF" }, ]; function normalizeAudioMimeType(format: unknown): string { diff --git a/open-sse/translator/request/claude-to-gemini.ts b/open-sse/translator/request/claude-to-gemini.ts index b3d987055d..7cdd8fa69f 100644 --- a/open-sse/translator/request/claude-to-gemini.ts +++ b/open-sse/translator/request/claude-to-gemini.ts @@ -41,7 +41,10 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) { model: model, contents: [], generationConfig: {}, - safetySettings: DEFAULT_SAFETY_SETTINGS, + // Honor an explicit caller-supplied safetySettings (including one that itself + // requests HARM_CATEGORY_CIVIC_INTEGRITY — the caller's explicit choice), matching + // the openai-to-gemini.ts standard-path behavior. See DEFAULT_SAFETY_SETTINGS (#8231). + safetySettings: body.safetySettings || DEFAULT_SAFETY_SETTINGS, }; // ── Generation config ────────────────────────────────────────── diff --git a/tests/snapshots/translation/openai-to-gemini/basic-chat.json b/tests/snapshots/translation/openai-to-gemini/basic-chat.json index 78877bfee7..9342678df5 100644 --- a/tests/snapshots/translation/openai-to-gemini/basic-chat.json +++ b/tests/snapshots/translation/openai-to-gemini/basic-chat.json @@ -33,10 +33,6 @@ { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF" - }, - { - "category": "HARM_CATEGORY_CIVIC_INTEGRITY", - "threshold": "OFF" } ] } diff --git a/tests/snapshots/translation/openai-to-gemini/modern-gemini-default-thinking.json b/tests/snapshots/translation/openai-to-gemini/modern-gemini-default-thinking.json index 78877bfee7..9342678df5 100644 --- a/tests/snapshots/translation/openai-to-gemini/modern-gemini-default-thinking.json +++ b/tests/snapshots/translation/openai-to-gemini/modern-gemini-default-thinking.json @@ -33,10 +33,6 @@ { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF" - }, - { - "category": "HARM_CATEGORY_CIVIC_INTEGRITY", - "threshold": "OFF" } ] } diff --git a/tests/snapshots/translation/openai-to-gemini/with-system.json b/tests/snapshots/translation/openai-to-gemini/with-system.json index 2c1f51acf1..06a6cfb2af 100644 --- a/tests/snapshots/translation/openai-to-gemini/with-system.json +++ b/tests/snapshots/translation/openai-to-gemini/with-system.json @@ -33,10 +33,6 @@ { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF" - }, - { - "category": "HARM_CATEGORY_CIVIC_INTEGRITY", - "threshold": "OFF" } ], "systemInstruction": { diff --git a/tests/unit/8231-gemini-civic-integrity-default.test.ts b/tests/unit/8231-gemini-civic-integrity-default.test.ts new file mode 100644 index 0000000000..561e2d588a --- /dev/null +++ b/tests/unit/8231-gemini-civic-integrity-default.test.ts @@ -0,0 +1,68 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { claudeToGeminiRequest } from "../../open-sse/translator/request/claude-to-gemini.ts"; +import { openaiToGeminiRequest } from "../../open-sse/translator/request/openai-to-gemini.ts"; + +// Regression guard for #8231: the standard Gemini API surface (non-Antigravity) +// unconditionally injected HARM_CATEGORY_CIVIC_INTEGRITY into the default +// safetySettings, which upstream dynamic validation rejects for some +// models/endpoints with `safety_settings[4]: element predicate failed` — a hard +// 400 on every request through that model. #5003 already fixed this for the +// Antigravity/Cloud Code surface (open-sse/executors/antigravity.ts); this test +// pins the same behavior for claude-to-gemini.ts and openai-to-gemini.ts. + +test("[repro #8231] claude-to-gemini default safetySettings must not force HARM_CATEGORY_CIVIC_INTEGRITY", () => { + const result = claudeToGeminiRequest( + "gemini-2.5-pro", + { messages: [{ role: "user", content: "hi" }] }, + false, + null + ); + const categories = (result.safetySettings as Array<{ category: string }>).map( + (s) => s.category + ); + assert.equal( + categories.includes("HARM_CATEGORY_CIVIC_INTEGRITY"), + false, + `got ${JSON.stringify(categories)}` + ); +}); + +test("[repro #8231] openai-to-gemini default safetySettings must not force HARM_CATEGORY_CIVIC_INTEGRITY", () => { + const result = openaiToGeminiRequest( + "gemini-2.5-pro", + { messages: [{ role: "user", content: "hi" }] }, + false, + null + ); + const categories = (result.safetySettings as Array<{ category: string }>).map( + (s) => s.category + ); + assert.equal( + categories.includes("HARM_CATEGORY_CIVIC_INTEGRITY"), + false, + `got ${JSON.stringify(categories)}` + ); +}); + +test("[repro #8231] claude-to-gemini preserves caller-supplied safetySettings that explicitly request HARM_CATEGORY_CIVIC_INTEGRITY", () => { + const explicit = [{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }]; + const result = claudeToGeminiRequest( + "gemini-2.5-pro", + { messages: [{ role: "user", content: "hi" }], safetySettings: explicit }, + false, + null + ); + assert.deepEqual(result.safetySettings, explicit); +}); + +test("[repro #8231] openai-to-gemini preserves caller-supplied safetySettings that explicitly request HARM_CATEGORY_CIVIC_INTEGRITY", () => { + const explicit = [{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }]; + const result = openaiToGeminiRequest( + "gemini-2.5-pro", + { messages: [{ role: "user", content: "hi" }], safetySettings: explicit }, + false, + null + ); + assert.deepEqual(result.safetySettings, explicit); +});