Compare commits

..

1 Commits

Author SHA1 Message Date
adevwithpurpose
ad672ef822 fix(sse): mark gemini-3.5-flash as thinking-capable
The base gemini-3.5-flash entry spread the shared GEMINI_35_FLASH_MODEL_SPEC
constant, which has supportsThinking:false because it is also spread into
several Antigravity flash-tier aliases that reject client-supplied thinking
params. That made the reasoning-routing policy resolve reasoning_effort as
"unsupported" for the base Google AI Studio model, producing a spurious
pre-provider HTTP 400 even though the model supports reasoning (it has an
effort-tier alias gemini-3.5-flash-high).

Set supportsThinking:true as an explicit override on the base
gemini-3.5-flash entry only, leaving the shared spec and the Antigravity
tier aliases unchanged.

Closes #10286
2026-08-15 03:02:39 -03:00
6 changed files with 86 additions and 55 deletions

View File

@@ -1 +0,0 @@
- fix(dashboard): remap unified Kimi Code card API-key save to the admitted `kimi-coding-apikey` connection id, fixing 400 "Invalid provider" on Save (#10096)

View File

@@ -0,0 +1 @@
- fix(sse): mark gemini-3.5-flash as thinking-capable so reasoning_effort is no longer rejected with a spurious 400 (#10286)

View File

@@ -32,19 +32,6 @@ type UseApiKeySaveParams = {
t: ProviderMessageTranslator;
};
// Issue #10096: the unified Kimi Code dashboard card shares one page/providerId
// ("kimi-coding") between OAuth and API-key auth. "kimi-coding" is an
// OAuth-primary managed id and is NOT an admitted API-key/dual-auth connection
// id (see isManagedProviderConnectionId in src/lib/providers/catalog.ts), so
// posting it here 400s with "Invalid provider". The dedicated managed
// API-key id "kimi-coding-apikey" IS admitted — remap only the POST payload
// so the saved connection lands under the correct managed id. The OAuth flow
// (handleOAuthSuccess in ProviderDetailPageClient.tsx) does not go through
// this hook, so it keeps posting "kimi-coding" unchanged.
export function resolveApiKeySaveProviderId(providerId: string): string {
return providerId === "kimi-coding" ? "kimi-coding-apikey" : providerId;
}
export function useApiKeySave({
providerId,
fetchConnections,
@@ -61,10 +48,7 @@ export function useApiKeySave({
const res = await fetch("/api/providers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
provider: resolveApiKeySaveProviderId(providerId),
...formData,
}),
body: JSON.stringify({ provider: providerId, ...formData }),
});
if (res.ok) {
const connectionData = await res.json();

View File

@@ -226,8 +226,16 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
},
// ── Gemini 3.5 Flash ─────────────────────────────────────────────
// #10286: the base Google AI Studio model DOES support reasoning (it has
// an effort-tier alias gemini-3.5-flash-high) — override the shared spec's
// supportsThinking:false here only. Do NOT flip GEMINI_35_FLASH_MODEL_SPEC
// itself: it is also spread into the Antigravity flash-tier aliases
// (gemini-3.5-flash-low/-extra-low, gemini-3-flash-agent, gemini-3.6-flash-*)
// which reject client-supplied thinking params because the model id itself
// selects the reasoning tier upstream.
"gemini-3.5-flash": {
...GEMINI_35_FLASH_MODEL_SPEC,
supportsThinking: true,
aliases: ["gemini-3.5-flash-high"],
},

View File

@@ -1,37 +0,0 @@
import test from "node:test";
import assert from "node:assert/strict";
// Issue #10096: Kimi Code API key validates OK but Save returns 400 "Invalid provider".
//
// Root cause: the unified Kimi Code dashboard card's API-key branch posted
// provider: "kimi-coding" (an OAuth-primary managed id, NOT an admitted
// API-key connection id) to POST /api/providers, which the backend rejects.
// The dedicated managed API-key id "kimi-coding-apikey" IS admitted.
//
// Fix: resolveApiKeySaveProviderId() in useApiKeySave.ts remaps the posted
// provider id to "kimi-coding-apikey" for the API-key save flow only, while
// the OAuth flow (which never calls this hook) keeps posting "kimi-coding".
const { isManagedProviderConnectionId } = await import("../../src/lib/providers/catalog.ts");
const { resolveApiKeySaveProviderId } = await import(
"../../src/app/(dashboard)/dashboard/providers/[id]/hooks/useApiKeySave.ts"
);
test("Kimi Code API-key save flow remaps to the admitted managed API-key id", () => {
assert.equal(
resolveApiKeySaveProviderId("kimi-coding"),
"kimi-coding-apikey",
"the unified Kimi Code card's API-key save flow must post kimi-coding-apikey, not kimi-coding"
);
assert.equal(
isManagedProviderConnectionId(resolveApiKeySaveProviderId("kimi-coding")),
true,
"the remapped id must be an admitted managed provider connection id (POST /api/providers accepts it)"
);
});
test("resolveApiKeySaveProviderId leaves every other provider id untouched", () => {
assert.equal(resolveApiKeySaveProviderId("openai"), "openai");
assert.equal(resolveApiKeySaveProviderId("kimi-coding-apikey"), "kimi-coding-apikey");
assert.equal(resolveApiKeySaveProviderId("qoder"), "qoder");
});

View File

@@ -0,0 +1,76 @@
// Regression test for #10286: gemini-3.5-flash was incorrectly marked
// supportsThinking:false, causing a spurious pre-provider HTTP 400 for any
// request with reasoning_effort set, even though the base Google AI Studio
// model supports reasoning (it has an effort-tier alias gemini-3.5-flash-high).
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-repro-10286-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-repro-10286-secret";
const caps = await import("../../src/lib/modelCapabilities.ts");
const core = await import("../../src/lib/db/core.ts");
const rulesDb = await import("../../src/lib/db/reasoningRoutingRules.ts");
const policy = await import("../../src/lib/reasoningRouting/policy.ts");
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
rulesDb.invalidateReasoningRoutingRuleCache();
}
function ruleInput(patch: Record<string, unknown> = {}) {
return {
name: "Enable thinking on gemini-3.5-flash",
description: "",
scope: "global",
apiKeyId: null,
comboId: null,
connectionId: null,
modelPattern: "gemini-3.5-flash",
sourceEffort: "any",
requestTags: [],
tagMatchMode: "any",
effortMode: "inherit",
targetEffort: null,
targetKind: "keep",
targetModel: null,
targetComboId: null,
budgetAction: "preserve",
budgetTokens: null,
priority: 0,
enabled: true,
...patch,
};
}
test.beforeEach(resetStorage);
test.after(async () => {
await resetStorage();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("gemini-3.5-flash (AI Studio provider) resolves as thinking-capable", () => {
const resolved = caps.getResolvedModelCapabilities({
provider: "gemini",
model: "gemini-3.5-flash",
});
assert.equal(resolved.supportsThinking, true);
});
test("reasoning_effort 'high' on gemini-3.5-flash is NOT rejected by routing policy", async () => {
await rulesDb.createReasoningRoutingRule(ruleInput());
const decision = await policy.resolveReasoningRoutingRule({
sourceModel: "gemini/gemini-3.5-flash",
sourceModelAliases: ["gemini-3.5-flash"],
sourceEffort: "high",
hasReasoningSignal: true,
});
assert.ok(decision, "a matching rule must produce a decision");
assert.equal(decision.capability, "supported");
});