mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-25 10:47:19 -07:00
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:
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user