From 38d3bd55da7602421943357793ec2673c7352e40 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sun, 19 Apr 2026 20:33:43 +0200 Subject: [PATCH] sf: route Gemini family models to google-gemini-cli by default resolveModelId now prefers google-gemini-cli over google (direct API) for bare Gemini/Gemma IDs, matching the operational default after the CLI-core re-platform. google-vertex is still honoured when it's the current provider. Co-Authored-By: Claude Sonnet 4.6 --- .../extensions/sf/auto-model-selection.ts | 19 +++++++++++ .../sf/tests/auto-model-selection.test.ts | 32 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/resources/extensions/sf/auto-model-selection.ts b/src/resources/extensions/sf/auto-model-selection.ts index 031b6103f..db9cdf6fb 100644 --- a/src/resources/extensions/sf/auto-model-selection.ts +++ b/src/resources/extensions/sf/auto-model-selection.ts @@ -479,6 +479,9 @@ export function resolveModelId( if (candidates.length === 0) return undefined; if (candidates.length === 1) return candidates[0]; + const lowerModelId = modelId.toLowerCase(); + const isGeminiFamily = lowerModelId.startsWith("gemini-") || lowerModelId.startsWith("gemma-"); + // When the user's current provider is claude-code (set by startup migration // or explicit selection), honour it for bare IDs. Routing back to anthropic // would undo the migration and hit the third-party subscription block (#3772). @@ -487,6 +490,22 @@ export function resolveModelId( if (ccMatch) return ccMatch; } + // Google Gemini routing should converge on the operational Google default + // backend for bare IDs. Keep Vertex explicit, but prefer the CLI-core + // backend over the direct API backend when both expose the same Gemini + // family model. The direct API path remains available as an explicit or + // fallback route, but is not the default operational surface. + if (isGeminiFamily && currentProvider === "google-vertex") { + const vertexMatch = candidates.find(m => m.provider === "google-vertex"); + if (vertexMatch) return vertexMatch; + } + if (isGeminiFamily) { + const googleCliMatch = candidates.find(m => m.provider === "google-gemini-cli"); + if (googleCliMatch) return googleCliMatch; + const googleApiMatch = candidates.find(m => m.provider === "google"); + if (googleApiMatch) return googleApiMatch; + } + // Extension / CLI-wrapper providers that should not win bare-ID resolution // when a first-class API provider also offers the same model AND the user // has not explicitly chosen the extension provider. diff --git a/src/resources/extensions/sf/tests/auto-model-selection.test.ts b/src/resources/extensions/sf/tests/auto-model-selection.test.ts index 27e5a7a5f..93bcf8592 100644 --- a/src/resources/extensions/sf/tests/auto-model-selection.test.ts +++ b/src/resources/extensions/sf/tests/auto-model-selection.test.ts @@ -199,6 +199,38 @@ test("resolveModelId: bare ID with claude-code as only provider still resolves", assert.equal(result.provider, "claude-code"); }); +test("resolveModelId: bare Gemini ID prefers cli-core backend over direct google api", () => { + const availableModels = [ + { id: "gemini-2.5-pro", provider: "google-gemini-cli" }, + { id: "gemini-2.5-pro", provider: "google" }, + ]; + + const result = resolveModelId("gemini-2.5-pro", availableModels, "google-gemini-cli"); + assert.ok(result, "should resolve a Gemini model"); + assert.equal(result.provider, "google-gemini-cli", "bare Gemini IDs should use CLI-core when api and cli backends coexist"); +}); + +test("resolveModelId: bare Gemini ID preserves google-vertex when that is the current provider", () => { + const availableModels = [ + { id: "gemini-2.5-pro", provider: "google" }, + { id: "gemini-2.5-pro", provider: "google-vertex" }, + ]; + + const result = resolveModelId("gemini-2.5-pro", availableModels, "google-vertex"); + assert.ok(result, "should resolve a Gemini model"); + assert.equal(result.provider, "google-vertex", "vertex should remain an explicit enterprise route"); +}); + +test("resolveModelId: bare Gemini ID falls back to direct google api when cli-core backend is absent", () => { + const availableModels = [ + { id: "gemini-2.5-pro", provider: "google" }, + ]; + + const result = resolveModelId("gemini-2.5-pro", availableModels, "google-gemini-cli"); + assert.ok(result, "should resolve a Gemini model"); + assert.equal(result.provider, "google"); +}); + // ─── selectAndApplyModel verbose-gating tests ────────────────────────── test("model change notify in selectAndApplyModel is gated behind verbose flag", () => {