diff --git a/packages/core/src/availability/policyHelpers.ts b/packages/core/src/availability/policyHelpers.ts index 05c1dd19f9..47c465585c 100644 --- a/packages/core/src/availability/policyHelpers.ts +++ b/packages/core/src/availability/policyHelpers.ts @@ -49,15 +49,16 @@ export function resolvePolicyChain( const useCustomToolModel = useGemini31 && config.getContentGeneratorConfig?.()?.authType === AuthType.USE_GEMINI; + const hasAccessToPreview = config.getHasAccessToPreviewModel?.() ?? true; const resolvedModel = resolveModel( modelFromConfig, useGemini31, useCustomToolModel, + hasAccessToPreview, ); const isAutoPreferred = preferredModel ? isAutoModel(preferredModel) : false; const isAutoConfigured = isAutoModel(configuredModel); - const hasAccessToPreview = config.getHasAccessToPreviewModel?.() ?? true; if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) { chain = getFlashLitePolicyChain(); @@ -80,7 +81,7 @@ export function resolvePolicyChain( } else { // User requested Gemini 3 but has no access. Proactively downgrade // to the stable Gemini 2.5 chain. - return getModelPolicyChain({ + chain = getModelPolicyChain({ previewEnabled: false, userTier: config.getUserTier(), useGemini31, diff --git a/packages/core/src/config/models.test.ts b/packages/core/src/config/models.test.ts index 3337151151..d62827ed91 100644 --- a/packages/core/src/config/models.test.ts +++ b/packages/core/src/config/models.test.ts @@ -217,6 +217,38 @@ describe('resolveModel', () => { expect(model).toBe(customModel); }); }); + + describe('hasAccessToPreview logic', () => { + it('should return default model when access to preview is false and preview model is requested', () => { + expect(resolveModel(PREVIEW_GEMINI_MODEL, false, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); + }); + + it('should return default flash model when access to preview is false and preview flash model is requested', () => { + expect( + resolveModel(PREVIEW_GEMINI_FLASH_MODEL, false, false, false), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); + + it('should return default model when access to preview is false and auto-gemini-3 is requested', () => { + expect(resolveModel(PREVIEW_GEMINI_MODEL_AUTO, false, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); + }); + + it('should return default model when access to preview is false and Gemini 3.1 is requested', () => { + expect(resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); + }); + + it('should still return default model when access to preview is false and auto-gemini-2.5 is requested', () => { + expect(resolveModel(DEFAULT_GEMINI_MODEL_AUTO, false, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); + }); + }); }); describe('isGemini2Model', () => { diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index 54ea063569..32014d5fbd 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -43,38 +43,70 @@ export const DEFAULT_THINKING_MODE = 8192; * * @param requestedModel The model alias or concrete model name requested by the user. * @param useGemini3_1 Whether to use Gemini 3.1 Pro Preview for auto/pro aliases. + * @param hasAccessToPreview Whether the user has access to preview models. * @returns The resolved concrete model name. */ export function resolveModel( requestedModel: string, useGemini3_1: boolean = false, useCustomToolModel: boolean = false, + hasAccessToPreview: boolean = true, ): string { + let resolved: string; switch (requestedModel) { case PREVIEW_GEMINI_MODEL: case PREVIEW_GEMINI_MODEL_AUTO: case GEMINI_MODEL_ALIAS_AUTO: case GEMINI_MODEL_ALIAS_PRO: { if (useGemini3_1) { - return useCustomToolModel + resolved = useCustomToolModel ? PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL : PREVIEW_GEMINI_3_1_MODEL; + } else { + resolved = PREVIEW_GEMINI_MODEL; } - return PREVIEW_GEMINI_MODEL; + break; } case DEFAULT_GEMINI_MODEL_AUTO: { - return DEFAULT_GEMINI_MODEL; + resolved = DEFAULT_GEMINI_MODEL; + break; } case GEMINI_MODEL_ALIAS_FLASH: { - return PREVIEW_GEMINI_FLASH_MODEL; + resolved = PREVIEW_GEMINI_FLASH_MODEL; + break; } case GEMINI_MODEL_ALIAS_FLASH_LITE: { - return DEFAULT_GEMINI_FLASH_LITE_MODEL; + resolved = DEFAULT_GEMINI_FLASH_LITE_MODEL; + break; } default: { - return requestedModel; + resolved = requestedModel; + break; } } + + if (!hasAccessToPreview && isPreviewModel(resolved)) { + // Downgrade to stable models if user lacks preview access. + switch (resolved) { + case PREVIEW_GEMINI_FLASH_MODEL: + return DEFAULT_GEMINI_FLASH_MODEL; + case PREVIEW_GEMINI_MODEL: + case PREVIEW_GEMINI_3_1_MODEL: + case PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL: + return DEFAULT_GEMINI_MODEL; + default: + // Fallback for unknown preview models, preserving original logic. + if (resolved.includes('flash-lite')) { + return DEFAULT_GEMINI_FLASH_LITE_MODEL; + } + if (resolved.includes('flash')) { + return DEFAULT_GEMINI_FLASH_MODEL; + } + return DEFAULT_GEMINI_MODEL; + } + } + + return resolved; } /**