From 1fe996db6a98d4a6d3bc9f2427e57075015a05c7 Mon Sep 17 00:00:00 2001 From: Lek Huda Date: Thu, 3 Sep 2026 09:12:20 +0700 Subject: [PATCH] fix(translator): route Gemini thinking through reasoning_effort on OpenAI-compatible wire --- .../translator/concerns/thinkingUnified.js | 8 ++++++-- tests/translator/thinking-unified.test.js | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/open-sse/translator/concerns/thinkingUnified.js b/open-sse/translator/concerns/thinkingUnified.js index 5ea27b2e..a3035823 100644 --- a/open-sse/translator/concerns/thinkingUnified.js +++ b/open-sse/translator/concerns/thinkingUnified.js @@ -105,12 +105,16 @@ export function extractThinking(body) { // at the call-site where intent is snapshotted before format translation. export const captureThinking = extractThinking; -// Resolve thinking format: provider override > capability > derive(targetFormat). +const NATIVE_ONLY_FORMATS = new Set(["gemini-level", "gemini-budget", "claude-budget", "claude-adaptive", "kiro"]); + function resolveFormat(targetFormat, model, provider) { const providerFmt = provider ? PROVIDERS[provider]?.thinkingFormat : null; if (providerFmt) return providerFmt; const caps = getCapabilitiesForModel(provider, model); - if (caps.thinkingFormat) return caps.thinkingFormat; + const isOpenAIWire = targetFormat === "openai" || targetFormat === "openai-responses"; + if (caps.thinkingFormat && !(isOpenAIWire && NATIVE_ONLY_FORMATS.has(caps.thinkingFormat))) { + return caps.thinkingFormat; + } return FORMAT_TO_NATIVE[targetFormat] || "openai"; } diff --git a/tests/translator/thinking-unified.test.js b/tests/translator/thinking-unified.test.js index 9ce75db5..93d8b683 100644 --- a/tests/translator/thinking-unified.test.js +++ b/tests/translator/thinking-unified.test.js @@ -220,6 +220,26 @@ describe("applyThinking per provider format", () => { const out = apply("openai", "gpt-5.6-sol", { reasoning_effort: "max" }, "kiro"); expect(out.reasoning_effort).toBe("xhigh"); }); + it.each([ + ["gemini-3.5-flash-lite"], + ["gemini-3.7-flash"], + ["gemini-3-pro"], + ])("Gemini 3.x model %s (gemini-level) over a custom OpenAI-compatible provider → reasoning_effort, not generationConfig (regression: #3718)", (model) => { + const out = apply("openai", model, { reasoning_effort: "medium" }, "my-custom-gemini-openai"); + expect(out.reasoning_effort).toBe("medium"); + expect(out.generationConfig).toBeUndefined(); + expect(out.thinkingConfig).toBeUndefined(); + }); + it("Gemini 2.5 model (gemini-budget) over a custom OpenAI-compatible provider → reasoning_effort, not generationConfig (regression: #3718)", () => { + const out = apply("openai", "gemini-2.5-flash", { reasoning_effort: "high" }, "my-custom-gemini-openai"); + expect(out.reasoning_effort).toBe("high"); + expect(out.generationConfig).toBeUndefined(); + expect(out.thinkingConfig).toBeUndefined(); + }); + it("Gemini model over its native format (antigravity/gemini-cli/vertex) still gets generationConfig", () => { + const out = apply("gemini-cli", "gemini-3.5-flash-lite", { reasoning_effort: "medium" }, "gemini-cli"); + expect(out.generationConfig.thinkingConfig.thinkingLevel).toBe("medium"); + }); }); describe("extractReasoningText (response shapes)", () => {