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 <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-04-19 20:33:43 +02:00
parent 822791fad3
commit 38d3bd55da
2 changed files with 51 additions and 0 deletions

View file

@ -479,6 +479,9 @@ export function resolveModelId<T extends { id: string; provider: string }>(
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<T extends { id: string; provider: string }>(
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.

View file

@ -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", () => {