From 0cd88ec9987c3c508a52082a30f882614e68222d Mon Sep 17 00:00:00 2001 From: "Bob.Hou" Date: Fri, 11 Sep 2026 18:28:13 -0400 Subject: [PATCH] fix(models): give Gemini 3.8 Flash its own output spec (#13195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Straightforward and well-scoped: a live catalog model falling through to the conservative 16384 clamp because `MODEL_SPECS` never got a 3.8 entry. Keeping the fallback for genuinely unknown ids is right, and the note on how this relates to #12663, #12499 and #12672 saved the review. --- Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the other 19 PRs of this batch. Two in-batch conflicts, both additive and resolved by keeping each side: the `ENVIRONMENT.md` table (#13035 + #13011) and the `chatHelpers.ts` import block (#12975 on the tip + #13017). - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK; `check:docs-counts` migrations ✓ - complexity 2816 / baseline 3218 and cognitive-complexity 1271 / baseline 1437 — both under baseline - 531 of 532 focused assertions green across the batch's 46 test files - `check-file-size` rebaselined for the batch's real growth (annotation `_rebaseline_2026_09_11_mergebatch_v3851_houminxi`, landed on #13038), attributed per PR The single red is **not this batch**: `tests/unit/combo/quota-weighted-strategy.test.ts` → "A/B isolation: 7 hard-empty + 2 at 0.5% + 1 at 40%, floor=1" asserts an order between two connections of identical weight and flakes on the pure tip too — 2 failures in 4 runs at `origin/release/v3.8.51` with nothing from this batch applied. ⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, untouched here). Thanks @HouMinXi — the live evidence on these (X500 logs, `storage.sqlite` state, real `/v1/models` probes, the 36-minute outage write-up) is what let a 20-PR batch be reviewed as a unit. --- .../fixes/13195-gemini-38-output-spec.md | 1 + src/shared/constants/modelSpecs.ts | 53 +++++++++++++++++-- .../antigravity-per-model-output-cap.test.ts | 53 +++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 changelog.d/fixes/13195-gemini-38-output-spec.md diff --git a/changelog.d/fixes/13195-gemini-38-output-spec.md b/changelog.d/fixes/13195-gemini-38-output-spec.md new file mode 100644 index 0000000000..ee4772c47e --- /dev/null +++ b/changelog.d/fixes/13195-gemini-38-output-spec.md @@ -0,0 +1 @@ +- **fix(models):** give discoverable Gemini 3.8 Flash ids their own 65536 output spec so Antigravity no longer clamps them to 16384 ([#13195](https://github.com/diegosouzapw/OmniRoute/pull/13195)) — thanks @HouMinXi diff --git a/src/shared/constants/modelSpecs.ts b/src/shared/constants/modelSpecs.ts index c8b6faaf4b..1260e26f7f 100644 --- a/src/shared/constants/modelSpecs.ts +++ b/src/shared/constants/modelSpecs.ts @@ -181,9 +181,56 @@ export const MODEL_SPECS: Record = { supportsTools: true, supportsVision: true, }, - // ── Gemini 3.7 Flash (current Antigravity/AGY live tiers) ───────── - // The tier suffix configures the thinking budget passed to the upstream - // gemini-3.7-flash-tiered backend (high: 24.5k, medium: 8k, low: 1k). + // Output limit published at https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash. + // Thinking budgets follow the 3.7 Flash high/medium/low/tiered split. + "gemini-3.8-flash-high": { + maxOutputTokens: 65536, + contextWindow: 1048576, + defaultThinkingBudget: 24576, + thinkingBudgetCap: 24576, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + }, + "gemini-3.8-flash-medium": { + maxOutputTokens: 65536, + contextWindow: 1048576, + defaultThinkingBudget: 8192, + thinkingBudgetCap: 24576, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + }, + "gemini-3.8-flash-low": { + maxOutputTokens: 65536, + contextWindow: 1048576, + defaultThinkingBudget: 1024, + thinkingBudgetCap: 24576, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + }, + "gemini-3.8-flash": { + maxOutputTokens: 65536, + contextWindow: 1048576, + defaultThinkingBudget: 8192, + thinkingBudgetCap: 24576, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + aliases: ["gemini-3.8-flash-tiered"], + }, + "gemini-3.8-flash-tiered": { + maxOutputTokens: 65536, + contextWindow: 1048576, + defaultThinkingBudget: 8192, + thinkingBudgetCap: 24576, + supportsThinking: true, + supportsTools: true, + supportsVision: true, + }, + + // Gemini 3.7 Flash tiers: high 24.5k, medium 8k, low 1k thinking tokens. "gemini-3.7-flash-high": { maxOutputTokens: 65536, contextWindow: 1048576, diff --git a/tests/unit/antigravity-per-model-output-cap.test.ts b/tests/unit/antigravity-per-model-output-cap.test.ts index c194f66910..ab2571d992 100644 --- a/tests/unit/antigravity-per-model-output-cap.test.ts +++ b/tests/unit/antigravity-per-model-output-cap.test.ts @@ -20,6 +20,7 @@ import { ANTIGRAVITY_MODEL_ALIASES, ANTIGRAVITY_PUBLIC_MODELS, } from "../../open-sse/config/antigravityModelAliases.ts"; +import { getResolvedModelCapabilities } from "../../src/lib/modelCapabilities.ts"; function generationConfigOf(request: unknown): Record { const gc = (request as Record)?.generationConfig; @@ -197,6 +198,57 @@ test("an aliased id is capped by the model it resolves to", async () => { } }); +test("Gemini 3.8 Flash retains its output allowance above the thinking budget", async () => { + const executor = new AntigravityExecutor(); + for (const model of [ + "gemini-3.8-flash-high", + "gemini-3.8-flash-medium", + "gemini-3.8-flash-low", + "gemini-3.8-flash-tiered", + ]) { + const result = await executor.transformRequest( + `antigravity/${model}`, + { + request: { + contents: [{ role: "user", parts: [{ text: "Hello" }] }], + generationConfig: { + maxOutputTokens: 65536, + thinkingConfig: { thinkingBudget: 24576, includeThoughts: true }, + }, + }, + }, + true, + { projectId: "project-1" } + ); + if (result instanceof Response) throw new Error("Unexpected Response from transformRequest"); + const config = generationConfigOf(result.request); + assert.equal(config.maxOutputTokens, 65536, model); + assert.equal((config.thinkingConfig as Record).thinkingBudget, 24576, model); + } +}); + +test("Gemini 3.8 Flash static spec keeps thinking and context, not only the output cap", () => { + const expectedBudget: Record = { + "gemini-3.8-flash-high": 24576, + "gemini-3.8-flash-medium": 8192, + "gemini-3.8-flash-low": 1024, + "gemini-3.8-flash-tiered": 8192, + }; + for (const model of Object.keys(expectedBudget)) { + const caps = getResolvedModelCapabilities({ + provider: "antigravity", + model, + }); + assert.equal(caps.maxOutputTokens, 65536, model); + assert.equal(caps.supportsThinking, true, model); + assert.equal(caps.supportsTools, true, model); + assert.equal(caps.supportsVision, true, model); + assert.equal(caps.contextWindow, 1048576, model); + assert.equal(caps.defaultThinkingBudget, expectedBudget[model], model); + assert.equal(caps.thinkingBudgetCap, 24576, model); + } +}); + test("the executor's cap differs per model on the same code path", async () => { const executor = new AntigravityExecutor(); @@ -227,6 +279,7 @@ test("a provider-prefixed model id resolves to the model's ceiling, not the fall ["agy/gemini-3.1-pro-high", 65535], ["antigravity/gemini-3.1-pro-high", 65535], ["agy/gemini-3.7-flash-high", 65536], + ["agy/gemini-3.8-flash-high", 65536], ["agy/gpt-oss-120b-medium", 32768], ];