Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/core/src/availability/fallbackIntegration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
});
});
23 changes: 23 additions & 0 deletions packages/core/src/availability/policyCatalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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);
Expand Down
18 changes: 12 additions & 6 deletions packages/core/src/availability/policyCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -28,6 +29,8 @@ type PolicyConfig = Omit<ModelPolicy, 'actions' | 'stateTransitions'> & {
export interface ModelPolicyOptions {
previewEnabled: boolean;
userTier?: UserTierId;
useGemini31?: boolean;
useCustomToolModel?: boolean;
}

const DEFAULT_ACTIONS: ModelPolicyActionMap = {
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/availability/policyHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = {}): Config =>
({
getUserTier: () => undefined,
getModel: () => 'gemini-2.5-pro',
getGemini31LaunchedSync: () => false,
getContentGeneratorConfig: () => ({ authType: undefined }),
...overrides,
}) as unknown as Config;

Expand Down Expand Up @@ -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', () => {
Expand Down
45 changes: 36 additions & 9 deletions packages/core/src/availability/policyHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -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);
}
Expand Down
Loading