From af14d988d1edff1eee96aca9535109df3a0bf796 Mon Sep 17 00:00:00 2001 From: gemini-cli-robot Date: Mon, 23 Feb 2026 14:48:37 -0500 Subject: [PATCH] fix(patch): cherry-pick aa9163d to release/v0.29.5-pr-19991 [CONFLICTS] (#20039) Co-authored-by: Sehoon Shon --- .../availability/fallbackIntegration.test.ts | 6 +-- .../src/availability/policyCatalog.test.ts | 23 ++++++++++ .../core/src/availability/policyCatalog.ts | 18 +++++--- .../src/availability/policyHelpers.test.ts | 39 ++++++++++++++++ .../core/src/availability/policyHelpers.ts | 45 +++++++++++++++---- 5 files changed, 113 insertions(+), 18 deletions(-) diff --git a/packages/core/src/availability/fallbackIntegration.test.ts b/packages/core/src/availability/fallbackIntegration.test.ts index 55f9ac800f..f9de1f3b2b 100644 --- a/packages/core/src/availability/fallbackIntegration.test.ts +++ b/packages/core/src/availability/fallbackIntegration.test.ts @@ -58,7 +58,7 @@ describe('Fallback Integration', () => { ); }); - it('should NOT fallback if config is NOT in AUTO mode', () => { + it('should fallback for Gemini 3 models even if config is NOT in AUTO mode', () => { // 1. Config is explicitly set to Pro, not Auto vi.spyOn(config, 'getModel').mockReturnValue(PREVIEW_GEMINI_MODEL); @@ -71,7 +71,7 @@ describe('Fallback Integration', () => { // 4. Apply model selection const result = applyModelSelection(config, { model: requestedModel }); - // 5. Expect it to stay on Pro (because single model chain) - expect(result.model).toBe(PREVIEW_GEMINI_MODEL); + // 5. Expect it to fallback to Flash (because Gemini 3 uses PREVIEW_CHAIN) + expect(result.model).toBe(PREVIEW_GEMINI_FLASH_MODEL); }); }); diff --git a/packages/core/src/availability/policyCatalog.test.ts b/packages/core/src/availability/policyCatalog.test.ts index 8edb2c6ad8..0133308688 100644 --- a/packages/core/src/availability/policyCatalog.test.ts +++ b/packages/core/src/availability/policyCatalog.test.ts @@ -12,6 +12,8 @@ import { } from './policyCatalog.js'; import { DEFAULT_GEMINI_MODEL, + PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, + PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_MODEL, } from '../config/models.js'; @@ -22,6 +24,27 @@ describe('policyCatalog', () => { expect(chain).toHaveLength(2); }); + it('returns Gemini 3.1 chain when useGemini31 is true', () => { + const chain = getModelPolicyChain({ + previewEnabled: true, + useGemini31: true, + }); + expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_MODEL); + expect(chain).toHaveLength(2); + expect(chain[1]?.model).toBe('gemini-3-flash-preview'); + }); + + it('returns Gemini 3.1 Custom Tools chain when useGemini31 and useCustomToolModel are true', () => { + const chain = getModelPolicyChain({ + previewEnabled: true, + useGemini31: true, + useCustomToolModel: true, + }); + expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL); + expect(chain).toHaveLength(2); + expect(chain[1]?.model).toBe('gemini-3-flash-preview'); + }); + it('returns default chain when preview disabled', () => { const chain = getModelPolicyChain({ previewEnabled: false }); expect(chain[0]?.model).toBe(DEFAULT_GEMINI_MODEL); diff --git a/packages/core/src/availability/policyCatalog.ts b/packages/core/src/availability/policyCatalog.ts index 48713621cf..39dea34a2f 100644 --- a/packages/core/src/availability/policyCatalog.ts +++ b/packages/core/src/availability/policyCatalog.ts @@ -16,6 +16,7 @@ import { DEFAULT_GEMINI_MODEL, PREVIEW_GEMINI_FLASH_MODEL, PREVIEW_GEMINI_MODEL, + resolveModel, } from '../config/models.js'; import type { UserTierId } from '../code_assist/types.js'; @@ -28,6 +29,8 @@ type PolicyConfig = Omit & { export interface ModelPolicyOptions { previewEnabled: boolean; userTier?: UserTierId; + useGemini31?: boolean; + useCustomToolModel?: boolean; } const DEFAULT_ACTIONS: ModelPolicyActionMap = { @@ -56,11 +59,6 @@ const DEFAULT_CHAIN: ModelPolicyChain = [ definePolicy({ model: DEFAULT_GEMINI_FLASH_MODEL, isLastResort: true }), ]; -const PREVIEW_CHAIN: ModelPolicyChain = [ - definePolicy({ model: PREVIEW_GEMINI_MODEL }), - definePolicy({ model: PREVIEW_GEMINI_FLASH_MODEL, isLastResort: true }), -]; - const FLASH_LITE_CHAIN: ModelPolicyChain = [ definePolicy({ model: DEFAULT_GEMINI_FLASH_LITE_MODEL, @@ -84,7 +82,15 @@ export function getModelPolicyChain( options: ModelPolicyOptions, ): ModelPolicyChain { if (options.previewEnabled) { - return cloneChain(PREVIEW_CHAIN); + const previewModel = resolveModel( + PREVIEW_GEMINI_MODEL, + options.useGemini31, + options.useCustomToolModel, + ); + return [ + definePolicy({ model: previewModel }), + definePolicy({ model: PREVIEW_GEMINI_FLASH_MODEL, isLastResort: true }), + ]; } return cloneChain(DEFAULT_CHAIN); diff --git a/packages/core/src/availability/policyHelpers.test.ts b/packages/core/src/availability/policyHelpers.test.ts index 4e923f638e..22b8a62700 100644 --- a/packages/core/src/availability/policyHelpers.test.ts +++ b/packages/core/src/availability/policyHelpers.test.ts @@ -15,12 +15,17 @@ import type { Config } from '../config/config.js'; import { DEFAULT_GEMINI_FLASH_LITE_MODEL, DEFAULT_GEMINI_MODEL_AUTO, + PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, + PREVIEW_GEMINI_3_1_MODEL, } from '../config/models.js'; +import { AuthType } from '../core/contentGenerator.js'; const createMockConfig = (overrides: Partial = {}): Config => ({ getUserTier: () => undefined, getModel: () => 'gemini-2.5-pro', + getGemini31LaunchedSync: () => false, + getContentGeneratorConfig: () => ({ authType: undefined }), ...overrides, }) as unknown as Config; @@ -115,6 +120,40 @@ describe('policyHelpers', () => { expect(chain[0]?.model).toBe('gemini-2.5-flash'); expect(chain[1]?.model).toBe('gemini-2.5-pro'); }); + + it('proactively returns Gemini 2.5 chain if Gemini 3 requested but user lacks access', () => { + const config = createMockConfig({ + getModel: () => 'auto-gemini-3', + getHasAccessToPreviewModel: () => false, + }); + const chain = resolvePolicyChain(config); + + // Should downgrade to [Pro 2.5, Flash 2.5] + expect(chain).toHaveLength(2); + expect(chain[0]?.model).toBe('gemini-2.5-pro'); + expect(chain[1]?.model).toBe('gemini-2.5-flash'); + }); + + it('returns Gemini 3.1 Pro chain when launched and auto-gemini-3 requested', () => { + const config = createMockConfig({ + getModel: () => 'auto-gemini-3', + getGemini31LaunchedSync: () => true, + }); + const chain = resolvePolicyChain(config); + expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_MODEL); + expect(chain[1]?.model).toBe('gemini-3-flash-preview'); + }); + + it('returns Gemini 3.1 Pro Custom Tools chain when launched, auth is Gemini, and auto-gemini-3 requested', () => { + const config = createMockConfig({ + getModel: () => 'auto-gemini-3', + getGemini31LaunchedSync: () => true, + getContentGeneratorConfig: () => ({ authType: AuthType.USE_GEMINI }), + }); + const chain = resolvePolicyChain(config); + expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL); + expect(chain[1]?.model).toBe('gemini-3-flash-preview'); + }); }); describe('buildFallbackPolicyContext', () => { diff --git a/packages/core/src/availability/policyHelpers.ts b/packages/core/src/availability/policyHelpers.ts index 671f1f8be5..456c8a855f 100644 --- a/packages/core/src/availability/policyHelpers.ts +++ b/packages/core/src/availability/policyHelpers.ts @@ -6,6 +6,7 @@ import type { GenerateContentConfig } from '@google/genai'; import type { Config } from '../config/config.js'; +import { AuthType } from '../core/contentGenerator.js'; import type { FailureKind, FallbackAction, @@ -24,6 +25,7 @@ import { DEFAULT_GEMINI_MODEL, PREVIEW_GEMINI_MODEL_AUTO, isAutoModel, + isGemini3Model, resolveModel, } from '../config/models.js'; import type { ModelSelectionResult } from './modelAvailabilityService.js'; @@ -43,23 +45,48 @@ export function resolvePolicyChain( const configuredModel = config.getModel(); let chain; + const useGemini31 = config.getGemini31LaunchedSync?.() ?? false; + const useCustomToolModel = + useGemini31 && + config.getContentGeneratorConfig?.()?.authType === AuthType.USE_GEMINI; + const resolvedModel = resolveModel( modelFromConfig, - config.getGemini31LaunchedSync?.() ?? false, + useGemini31, + useCustomToolModel, ); const isAutoPreferred = preferredModel ? isAutoModel(preferredModel) : false; const isAutoConfigured = isAutoModel(configuredModel); + const hasAccessToPreview = config.getHasAccessToPreviewModel?.() ?? true; if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) { chain = getFlashLitePolicyChain(); - } else if (isAutoPreferred || isAutoConfigured) { - const previewEnabled = - preferredModel === PREVIEW_GEMINI_MODEL_AUTO || - configuredModel === PREVIEW_GEMINI_MODEL_AUTO; - chain = getModelPolicyChain({ - previewEnabled, - userTier: config.getUserTier(), - }); + } else if ( + isGemini3Model(resolvedModel) || + isAutoPreferred || + isAutoConfigured + ) { + if (hasAccessToPreview) { + const previewEnabled = + isGemini3Model(resolvedModel) || + preferredModel === PREVIEW_GEMINI_MODEL_AUTO || + configuredModel === PREVIEW_GEMINI_MODEL_AUTO; + chain = getModelPolicyChain({ + previewEnabled, + userTier: config.getUserTier(), + useGemini31, + useCustomToolModel, + }); + } else { + // User requested Gemini 3 but has no access. Proactively downgrade + // to the stable Gemini 2.5 chain. + return getModelPolicyChain({ + previewEnabled: false, + userTier: config.getUserTier(), + useGemini31, + useCustomToolModel, + }); + } } else { chain = createSingleModelChain(modelFromConfig); }