From f146618b6ba2a39e595a8ad4fb591c1032ad0ec7 Mon Sep 17 00:00:00 2001 From: "gemini-cli[bot]" Date: Fri, 15 May 2026 21:42:45 +0000 Subject: [PATCH] fix(core): route personal OAuth users to stable models for auto aliases This PR implements auth-aware model routing to prevent 404/400 errors for users using personal OAuth (`oauth-personal`). ### Problem When using the `auto-gemini-3` model alias with personal OAuth, the CLI occasionally resolves it to `gemini-3-pro-preview`. However, many personal accounts do not have access to this specific preview model, leading to API errors (404/400). Currently, the CLI only falls back to stable models if the user has NO access to ANY preview models, which is too broad if they have access to older preview models but not the latest ones. ### Solution - Added `authType` to the `ModelResolutionContext` and `ModelCapabilityContext`. - Updated `ModelConfigService` to support conditions based on `authType`. - Modified `DEFAULT_MODEL_CONFIGS` and `settings.schema.json` to explicitly route `oauth-personal` users to stable models (e.g., `gemini-2.5-pro`) for `auto` and `pro` aliases. - Updated `resolveModel` to pass the current `authType` to the resolution engine. These changes ensure that personal OAuth users always get a working stable model by default, while API key and Vertex AI users can still access the latest previews. Fixes: #26857 cc @NTaylorMullen --- packages/core/src/config/config.ts | 4 ++ .../core/src/config/defaultModelConfigs.ts | 13 +++- packages/core/src/config/models.ts | 13 ++++ packages/core/src/config/models_auth.test.ts | 70 +++++++++++++++++++ packages/core/src/core/contentGenerator.ts | 2 + .../core/src/services/modelConfigService.ts | 4 ++ schemas/settings.schema.json | 66 +++++++++++++++-- 7 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/config/models_auth.test.ts diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 1568207936..bae1fe483e 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -2801,6 +2801,10 @@ export class Config implements McpContext, AgentLoopContext { return getChannelFromVersion(this._clientVersion); } + getAuthType(): string | undefined { + return this.contentGeneratorConfig.authType; + } + getPendingIncludeDirectories(): string[] { return this.pendingIncludeDirectories; } diff --git a/packages/core/src/config/defaultModelConfigs.ts b/packages/core/src/config/defaultModelConfigs.ts index cda791c808..61ede45289 100644 --- a/packages/core/src/config/defaultModelConfigs.ts +++ b/packages/core/src/config/defaultModelConfigs.ts @@ -467,6 +467,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { auto: { default: 'gemini-3-pro-preview', contexts: [ + { condition: { authType: 'oauth-personal' }, target: 'gemini-2.5-pro' }, { condition: { releaseChannel: 'stable' }, target: 'gemini-2.5-pro' }, { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' }, { @@ -482,6 +483,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { pro: { default: 'gemini-3-pro-preview', contexts: [ + { condition: { authType: 'oauth-personal' }, target: 'gemini-2.5-pro' }, { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' }, { condition: { useGemini3_1: true, useCustomTools: true }, @@ -496,6 +498,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { 'gemini-3.1-flash-lite-preview': { default: 'gemini-3.1-flash-lite-preview', contexts: [ + { condition: { authType: 'oauth-personal' }, target: 'gemini-2.5-flash-lite' }, { condition: { useGemini3_1FlashLite: false }, target: 'gemini-2.5-flash-lite', @@ -505,6 +508,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { flash: { default: 'gemini-3-flash-preview', contexts: [ + { condition: { authType: 'oauth-personal' }, target: 'gemini-2.5-flash' }, { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-flash', @@ -515,7 +519,11 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { default: 'gemini-2.5-flash-lite', contexts: [ { - condition: { useGemini3_1FlashLite: true }, + condition: { useGemini3_1FlashLite: true, authType: 'gemini-api-key' }, + target: 'gemini-3.1-flash-lite-preview', + }, + { + condition: { useGemini3_1FlashLite: true, authType: 'vertex-ai' }, target: 'gemini-3.1-flash-lite-preview', }, ], @@ -523,6 +531,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { 'auto-gemini-3': { default: 'gemini-3-pro-preview', contexts: [ + { condition: { authType: 'oauth-personal' }, target: 'gemini-2.5-pro' }, { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' }, { condition: { useGemini3_1: true, useCustomTools: true }, @@ -542,6 +551,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { flash: { default: 'gemini-3-flash-preview', contexts: [ + { condition: { authType: 'oauth-personal' }, target: 'gemini-2.5-flash' }, { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-flash', @@ -555,6 +565,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { pro: { default: 'gemini-3-pro-preview', contexts: [ + { condition: { authType: 'oauth-personal' }, target: 'gemini-2.5-pro' }, { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro', diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index 7360878ccd..764f749c57 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -11,6 +11,7 @@ export interface ModelResolutionContext { hasAccessToPreview?: boolean; requestedModel?: string; releaseChannel?: string; + authType?: string; } /** @@ -50,6 +51,7 @@ export interface ModelCapabilityContext { readonly modelConfigService: IModelConfigService; getExperimentalDynamicModelConfiguration(): boolean; getReleaseChannel?(): string; + getAuthType?(): string | undefined; } export const PREVIEW_GEMINI_MODEL = 'gemini-3-pro-preview'; @@ -127,6 +129,7 @@ export function resolveModel( hasAccessToPreview: boolean = true, config?: ModelCapabilityContext, releaseChannel?: string, + authType?: string, ): string { // Defensive check against non-string inputs at runtime const normalizedModel = Array.isArray(requestedModel) @@ -136,6 +139,7 @@ export function resolveModel( : requestedModel.trim() || ''; const currentReleaseChannel = releaseChannel ?? config?.getReleaseChannel?.(); + const currentAuthType = authType ?? config?.getAuthType?.(); if (config?.getExperimentalDynamicModelConfiguration?.() === true) { const resolved = config.modelConfigService.resolveModelId(normalizedModel, { @@ -144,6 +148,7 @@ export function resolveModel( useCustomTools: useCustomToolModel, hasAccessToPreview, releaseChannel: currentReleaseChannel, + authType: currentAuthType, }); if (!hasAccessToPreview && isPreviewModel(resolved, config)) { @@ -245,6 +250,7 @@ export function resolveClassifierModel( useCustomToolModel: boolean = false, hasAccessToPreview: boolean = true, config?: ModelCapabilityContext, + authType?: string, ): string { if (config?.getExperimentalDynamicModelConfiguration?.() === true) { return config.modelConfigService.resolveClassifierModelId( @@ -255,6 +261,7 @@ export function resolveClassifierModel( useGemini3_1FlashLite, useCustomTools: useCustomToolModel, hasAccessToPreview, + authType: authType ?? config?.getAuthType?.(), }, ); } @@ -281,6 +288,9 @@ export function resolveClassifierModel( false, false, hasAccessToPreview, + config, + undefined, + authType, ); } return resolveModel( @@ -289,6 +299,9 @@ export function resolveClassifierModel( useGemini3_1FlashLite, useCustomToolModel, hasAccessToPreview, + config, + undefined, + authType, ); } diff --git a/packages/core/src/config/models_auth.test.ts b/packages/core/src/config/models_auth.test.ts new file mode 100644 index 0000000000..68e01c839e --- /dev/null +++ b/packages/core/src/config/models_auth.test.ts @@ -0,0 +1,70 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect } from 'vitest'; +import { + resolveModel, + PREVIEW_GEMINI_MODEL_AUTO, + PREVIEW_GEMINI_MODEL, + DEFAULT_GEMINI_MODEL, +} from './models.js'; +import { ModelConfigService } from '../services/modelConfigService.js'; +import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js'; +import type { Config } from './config.js'; + +const modelConfigService = new ModelConfigService(DEFAULT_MODEL_CONFIGS); + +const dynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, +} as unknown as Config; + +describe('resolveModel with authType', () => { + it('should resolve auto-gemini-3 to gemini-3-pro-preview for non-personal auth', () => { + const model = resolveModel( + PREVIEW_GEMINI_MODEL_AUTO, + false, + false, + false, + true, + dynamicConfig, + 'stable', + 'gemini-api-key' + ); + expect(model).toBe(PREVIEW_GEMINI_MODEL); + }); + + it('should resolve auto-gemini-3 to gemini-2.5-pro for oauth-personal auth', () => { + const model = resolveModel( + PREVIEW_GEMINI_MODEL_AUTO, + false, + false, + false, + true, + dynamicConfig, + 'stable', + 'oauth-personal' + ); + expect(model).toBe(DEFAULT_GEMINI_MODEL); + }); + + it('should use authType from config if not passed explicitly', () => { + const configWithAuth = { + ...dynamicConfig, + getAuthType: () => 'oauth-personal', + } as unknown as Config; + + const model = resolveModel( + PREVIEW_GEMINI_MODEL_AUTO, + false, + false, + false, + true, + configWithAuth + ); + expect(model).toBe(DEFAULT_GEMINI_MODEL); + }); +}); diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts index 4494a5e9ff..52434b5591 100644 --- a/packages/core/src/core/contentGenerator.ts +++ b/packages/core/src/core/contentGenerator.ts @@ -217,6 +217,8 @@ export async function createContentGenerator( false, gcConfig.getHasAccessToPreviewModel?.() ?? true, gcConfig, + undefined, + config.authType, ); const customHeadersEnv = process.env['GEMINI_CLI_CUSTOM_HEADERS'] || undefined; diff --git a/packages/core/src/services/modelConfigService.ts b/packages/core/src/services/modelConfigService.ts index 64ef291206..1d4e95c17b 100644 --- a/packages/core/src/services/modelConfigService.ts +++ b/packages/core/src/services/modelConfigService.ts @@ -103,6 +103,7 @@ export interface ResolutionContext { hasAccessToProModel?: boolean; requestedModel?: string; releaseChannel?: string; + authType?: string; } /** The requirements defined in the registry. */ @@ -114,6 +115,7 @@ export interface ResolutionCondition { /** Matches if the current model is in this list. */ requestedModels?: string[]; releaseChannel?: string; + authType?: string; } export interface ModelConfigServiceConfig { @@ -267,6 +269,8 @@ export class ModelConfigService { ); case 'releaseChannel': return value === context.releaseChannel; + case 'authType': + return value === context.authType; default: return false; } diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 2bcde8bc6f..5d1e4a16a2 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -1238,6 +1238,12 @@ "auto": { "default": "gemini-3-pro-preview", "contexts": [ + { + "condition": { + "authType": "oauth-personal" + }, + "target": "gemini-2.5-pro" + }, { "condition": { "releaseChannel": "stable" @@ -1268,6 +1274,12 @@ "pro": { "default": "gemini-3-pro-preview", "contexts": [ + { + "condition": { + "authType": "oauth-personal" + }, + "target": "gemini-2.5-pro" + }, { "condition": { "hasAccessToPreview": false @@ -1292,6 +1304,12 @@ "gemini-3.1-flash-lite-preview": { "default": "gemini-3.1-flash-lite-preview", "contexts": [ + { + "condition": { + "authType": "oauth-personal" + }, + "target": "gemini-2.5-flash-lite" + }, { "condition": { "useGemini3_1FlashLite": false @@ -1303,6 +1321,12 @@ "flash": { "default": "gemini-3-flash-preview", "contexts": [ + { + "condition": { + "authType": "oauth-personal" + }, + "target": "gemini-2.5-flash" + }, { "condition": { "hasAccessToPreview": false @@ -1316,7 +1340,15 @@ "contexts": [ { "condition": { - "useGemini3_1FlashLite": true + "useGemini3_1FlashLite": true, + "authType": "gemini-api-key" + }, + "target": "gemini-3.1-flash-lite-preview" + }, + { + "condition": { + "useGemini3_1FlashLite": true, + "authType": "vertex-ai" }, "target": "gemini-3.1-flash-lite-preview" } @@ -1325,6 +1357,12 @@ "auto-gemini-3": { "default": "gemini-3-pro-preview", "contexts": [ + { + "condition": { + "authType": "oauth-personal" + }, + "target": "gemini-2.5-pro" + }, { "condition": { "hasAccessToPreview": false @@ -1354,6 +1392,12 @@ "flash": { "default": "gemini-3-flash-preview", "contexts": [ + { + "condition": { + "authType": "oauth-personal" + }, + "target": "gemini-2.5-flash" + }, { "condition": { "hasAccessToPreview": false @@ -1362,7 +1406,10 @@ }, { "condition": { - "requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"] + "requestedModels": [ + "gemini-2.5-pro", + "auto-gemini-2.5" + ] }, "target": "gemini-2.5-flash" } @@ -1371,6 +1418,12 @@ "pro": { "default": "gemini-3-pro-preview", "contexts": [ + { + "condition": { + "authType": "oauth-personal" + }, + "target": "gemini-2.5-pro" + }, { "condition": { "hasAccessToPreview": false @@ -1380,13 +1433,18 @@ { "condition": { "releaseChannel": "stable", - "requestedModels": ["auto"] + "requestedModels": [ + "auto" + ] }, "target": "gemini-2.5-pro" }, { "condition": { - "requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"] + "requestedModels": [ + "gemini-2.5-pro", + "auto-gemini-2.5" + ] }, "target": "gemini-2.5-pro" },