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
This commit is contained in:
gemini-cli[bot]
2026-05-15 21:42:45 +00:00
parent 77e65c0db5
commit f146618b6b
7 changed files with 167 additions and 5 deletions
+4
View File
@@ -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;
}
@@ -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',
+13
View File
@@ -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,
);
}
@@ -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);
});
});
@@ -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;
@@ -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;
}
+62 -4
View File
@@ -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"
},