Download packages/core/src/availability/policyHelpers.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 17.4 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/availability/policyHelpers.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/core/src/availability/policyHelpers.test.ts
-
curl -L -o policyHelpers.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/availability/policyHelpers.test.ts
17.4 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |
| import { | |
| resolvePolicyChain, | |
| buildFallbackPolicyContext, | |
| applyModelSelection, | |
| applyAvailabilityTransition, | |
| } from './policyHelpers.js'; | |
| import { createDefaultPolicy, SILENT_ACTIONS } from './policyCatalog.js'; | |
| import type { RetryAvailabilityContext } from './modelPolicy.js'; | |
| 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'; | |
| import { ModelConfigService } from '../services/modelConfigService.js'; | |
| import { DEFAULT_MODEL_CONFIGS } from '../config/defaultModelConfigs.js'; | |
| import { ApprovalMode } from '../policy/types.js'; | |
| const createMockConfig = (overrides: Partial<Config> = {}): Config => { | |
| const config = { | |
| getUserTier: () => undefined, | |
| getModel: () => 'gemini-2.5-pro', | |
| getGemini31LaunchedSync: () => false, | |
| getUseCustomToolModelSync: () => { | |
| const useGemini31 = config.getGemini31LaunchedSync(); | |
| const authType = config.getContentGeneratorConfig().authType; | |
| return useGemini31 && authType === AuthType.USE_GEMINI; | |
| }, | |
| getContentGeneratorConfig: () => ({ authType: undefined }), | |
| getHasAccessToPreviewModel: () => true, | |
| getMaxAttemptsPerTurn: () => 3, | |
| getExperimentalDynamicModelConfiguration: () => false, | |
| getReleaseChannel: () => 'preview', | |
| modelConfigService: new ModelConfigService(DEFAULT_MODEL_CONFIGS), | |
| ...overrides, | |
| } as unknown as Config; | |
| return config; | |
| }; | |
| describe('policyHelpers', () => { | |
| describe('resolvePolicyChain', () => { | |
| it('returns a single-model chain for a custom model', () => { | |
| const config = createMockConfig({ | |
| getModel: () => 'custom-model', | |
| }); | |
| const chain = resolvePolicyChain(config); | |
| expect(chain).toHaveLength(1); | |
| expect(chain[0]?.model).toBe('custom-model'); | |
| }); | |
| it('leaves catalog order untouched when active model already present', () => { | |
| const config = createMockConfig({ | |
| getModel: () => 'gemini-2.5-pro', | |
| }); | |
| const chain = resolvePolicyChain(config); | |
| expect(chain[0]?.model).toBe('gemini-2.5-pro'); | |
| }); | |
| it('returns the default chain when active model is "auto"', () => { | |
| const config = createMockConfig({ | |
| getModel: () => DEFAULT_GEMINI_MODEL_AUTO, | |
| }); | |
| const chain = resolvePolicyChain(config); | |
| // Expect default chain [Pro, Flash] | |
| expect(chain).toHaveLength(2); | |
| expect(chain[0]?.model).toBe('gemini-2.5-pro'); | |
| expect(chain[1]?.model).toBe('gemini-2.5-flash'); | |
| }); | |
| it('uses auto chain when preferred model is auto', () => { | |
| const config = createMockConfig({ | |
| getModel: () => 'gemini-2.5-pro', | |
| }); | |
| const chain = resolvePolicyChain(config, DEFAULT_GEMINI_MODEL_AUTO); | |
| expect(chain).toHaveLength(2); | |
| expect(chain[0]?.model).toBe('gemini-2.5-pro'); | |
| expect(chain[1]?.model).toBe('gemini-2.5-flash'); | |
| }); | |
| it('uses auto chain when configured model is auto even if preferred is concrete', () => { | |
| const config = createMockConfig({ | |
| getModel: () => DEFAULT_GEMINI_MODEL_AUTO, | |
| }); | |
| const chain = resolvePolicyChain(config, 'gemini-2.5-pro'); | |
| expect(chain).toHaveLength(2); | |
| expect(chain[0]?.model).toBe('gemini-2.5-pro'); | |
| expect(chain[1]?.model).toBe('gemini-2.5-flash'); | |
| }); | |
| it('starts chain from preferredModel when model is "auto"', () => { | |
| const config = createMockConfig({ | |
| getModel: () => 'auto', | |
| }); | |
| const chain = resolvePolicyChain(config, 'gemini-2.5-flash'); | |
| // Due to Gemini 2.x wrapsAround, the chain will contain both flash and pro | |
| expect(chain.length).toBeGreaterThanOrEqual(1); | |
| expect(chain[0]?.model).toBe('gemini-2.5-flash'); | |
| }); | |
| it('returns flash-lite chain when preferred model is flash-lite', () => { | |
| const config = createMockConfig({ | |
| getModel: () => DEFAULT_GEMINI_MODEL_AUTO, | |
| }); | |
| const chain = resolvePolicyChain(config, DEFAULT_GEMINI_FLASH_LITE_MODEL); | |
| expect(chain).toHaveLength(3); | |
| expect(chain[0]?.model).toBe('gemini-3.1-flash-lite'); | |
| expect(chain[1]?.model).toBe('gemini-2.5-flash'); | |
| expect(chain[2]?.model).toBe('gemini-2.5-pro'); | |
| }); | |
| it('returns flash-lite chain when configured model is flash-lite', () => { | |
| const config = createMockConfig({ | |
| getModel: () => DEFAULT_GEMINI_FLASH_LITE_MODEL, | |
| }); | |
| const chain = resolvePolicyChain(config); | |
| expect(chain).toHaveLength(3); | |
| expect(chain[0]?.model).toBe('gemini-3.1-flash-lite'); | |
| expect(chain[1]?.model).toBe('gemini-2.5-flash'); | |
| expect(chain[2]?.model).toBe('gemini-2.5-pro'); | |
| }); | |
| it('wraps around the chain when wrapsAround is true', () => { | |
| const config = createMockConfig({ | |
| getModel: () => DEFAULT_GEMINI_MODEL_AUTO, | |
| }); | |
| const chain = resolvePolicyChain(config, 'gemini-2.5-flash', true); | |
| expect(chain).toHaveLength(2); | |
| 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'); | |
| }); | |
| it('applies SILENT_ACTIONS when ApprovalMode is PLAN', () => { | |
| const config = createMockConfig({ | |
| getApprovalMode: () => ApprovalMode.PLAN, | |
| getModel: () => DEFAULT_GEMINI_MODEL_AUTO, | |
| }); | |
| const chain = resolvePolicyChain(config); | |
| expect(chain).toHaveLength(2); | |
| expect(chain[0]?.actions).toEqual(SILENT_ACTIONS); | |
| expect(chain[1]?.actions).toEqual(SILENT_ACTIONS); | |
| }); | |
| }); | |
| describe('resolvePolicyChain behavior is identical between dynamic and legacy implementations', () => { | |
| const testCases = [ | |
| { name: 'Default Auto', model: DEFAULT_GEMINI_MODEL_AUTO }, | |
| { name: 'Gemini 3 Auto', model: 'auto-gemini-3' }, | |
| { name: 'Unified Auto', model: 'auto' }, | |
| { name: 'Flash Lite', model: DEFAULT_GEMINI_FLASH_LITE_MODEL }, | |
| { | |
| name: 'Gemini 3 Auto (3.1 Enabled)', | |
| model: 'auto-gemini-3', | |
| useGemini31: true, | |
| }, | |
| { | |
| name: 'Gemini 3 Auto (3.1 + Custom Tools)', | |
| model: 'auto-gemini-3', | |
| useGemini31: true, | |
| authType: AuthType.USE_GEMINI, | |
| }, | |
| { | |
| name: 'Gemini 3 Auto (No Access)', | |
| model: 'auto-gemini-3', | |
| hasAccess: false, | |
| }, | |
| { name: 'Concrete Model (2.5 Pro)', model: 'gemini-2.5-pro' }, | |
| { name: 'Explicit Gemini 3', model: 'gemini-3-pro-preview' }, | |
| { name: 'Custom Model', model: 'my-custom-model' }, | |
| { | |
| name: 'Wrap Around', | |
| model: DEFAULT_GEMINI_MODEL_AUTO, | |
| wrapsAround: true, | |
| }, | |
| ]; | |
| testCases.forEach( | |
| ({ name, model, useGemini31, hasAccess, authType, wrapsAround }) => { | |
| it(`achieves parity for: ${name}`, () => { | |
| const createBaseConfig = (dynamic: boolean) => | |
| createMockConfig({ | |
| getExperimentalDynamicModelConfiguration: () => dynamic, | |
| getModel: () => model, | |
| getGemini31LaunchedSync: () => useGemini31 ?? false, | |
| getHasAccessToPreviewModel: () => hasAccess ?? true, | |
| getContentGeneratorConfig: () => ({ authType }), | |
| getReleaseChannel: () => 'preview', | |
| modelConfigService: new ModelConfigService(DEFAULT_MODEL_CONFIGS), | |
| }); | |
| const legacyChain = resolvePolicyChain( | |
| createBaseConfig(false), | |
| model, | |
| wrapsAround, | |
| ); | |
| const dynamicChain = resolvePolicyChain( | |
| createBaseConfig(true), | |
| model, | |
| wrapsAround, | |
| ); | |
| expect(dynamicChain).toEqual(legacyChain); | |
| }); | |
| }, | |
| ); | |
| }); | |
| describe('buildFallbackPolicyContext', () => { | |
| it('returns remaining candidates after the failed model', () => { | |
| const chain = [ | |
| createDefaultPolicy('a'), | |
| createDefaultPolicy('b'), | |
| createDefaultPolicy('c'), | |
| ]; | |
| const context = buildFallbackPolicyContext(chain, 'b'); | |
| expect(context.failedPolicy?.model).toBe('b'); | |
| expect(context.candidates.map((p) => p.model)).toEqual(['c']); | |
| }); | |
| it('wraps around when building fallback context if wrapsAround is true', () => { | |
| const chain = [ | |
| createDefaultPolicy('a'), | |
| createDefaultPolicy('b'), | |
| createDefaultPolicy('c'), | |
| ]; | |
| const context = buildFallbackPolicyContext(chain, 'b', true); | |
| expect(context.failedPolicy?.model).toBe('b'); | |
| expect(context.candidates.map((p) => p.model)).toEqual(['c', 'a']); | |
| }); | |
| it('returns full chain when model is not in policy list', () => { | |
| const chain = [createDefaultPolicy('a'), createDefaultPolicy('b')]; | |
| const context = buildFallbackPolicyContext(chain, 'x'); | |
| expect(context.failedPolicy).toBeUndefined(); | |
| expect(context.candidates).toEqual(chain); | |
| }); | |
| }); | |
| describe('applyModelSelection', () => { | |
| const mockModelConfigService = { | |
| getResolvedConfig: vi.fn(), | |
| }; | |
| const mockAvailabilityService = { | |
| selectFirstAvailable: vi.fn(), | |
| consumeStickyAttempt: vi.fn(), | |
| }; | |
| const createExtendedMockConfig = ( | |
| overrides: Partial<Config> = {}, | |
| ): Config => { | |
| const defaults = { | |
| getModelAvailabilityService: () => mockAvailabilityService, | |
| setActiveModel: vi.fn(), | |
| modelConfigService: mockModelConfigService, | |
| }; | |
| return createMockConfig({ ...defaults, ...overrides } as Partial<Config>); | |
| }; | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| }); | |
| it('returns requested model if it is available', () => { | |
| const config = createExtendedMockConfig(); | |
| mockModelConfigService.getResolvedConfig.mockReturnValue({ | |
| model: 'gemini-pro', | |
| generateContentConfig: {}, | |
| }); | |
| mockAvailabilityService.selectFirstAvailable.mockReturnValue({ | |
| selectedModel: 'gemini-pro', | |
| }); | |
| const result = applyModelSelection(config, { | |
| model: 'gemini-pro', | |
| isChatModel: true, | |
| }); | |
| expect(result.model).toBe('gemini-pro'); | |
| expect(result.maxAttempts).toBeUndefined(); | |
| expect(config.setActiveModel).toHaveBeenCalledWith('gemini-pro'); | |
| }); | |
| it('switches to backup model and updates config if requested is unavailable', () => { | |
| const config = createExtendedMockConfig(); | |
| mockModelConfigService.getResolvedConfig | |
| .mockReturnValueOnce({ | |
| model: 'gemini-pro', | |
| generateContentConfig: { temperature: 0.9, topP: 1 }, | |
| }) | |
| .mockReturnValueOnce({ | |
| model: 'gemini-flash', | |
| generateContentConfig: { temperature: 0.1, topP: 1 }, | |
| }); | |
| mockAvailabilityService.selectFirstAvailable.mockReturnValue({ | |
| selectedModel: 'gemini-flash', | |
| }); | |
| const result = applyModelSelection(config, { | |
| model: 'gemini-pro', | |
| isChatModel: true, | |
| }); | |
| expect(result.model).toBe('gemini-flash'); | |
| expect(result.config).toEqual({ | |
| temperature: 0.1, | |
| topP: 1, | |
| }); | |
| expect(mockModelConfigService.getResolvedConfig).toHaveBeenCalledWith({ | |
| model: 'gemini-pro', | |
| isChatModel: true, | |
| }); | |
| expect(mockModelConfigService.getResolvedConfig).toHaveBeenCalledWith({ | |
| model: 'gemini-flash', | |
| isChatModel: true, | |
| }); | |
| expect(config.setActiveModel).toHaveBeenCalledWith('gemini-flash'); | |
| }); | |
| it('does not call setActiveModel if isChatModel is false', () => { | |
| const config = createExtendedMockConfig(); | |
| mockModelConfigService.getResolvedConfig.mockReturnValue({ | |
| model: 'gemini-pro', | |
| generateContentConfig: {}, | |
| }); | |
| mockAvailabilityService.selectFirstAvailable.mockReturnValue({ | |
| selectedModel: 'gemini-pro', | |
| }); | |
| applyModelSelection(config, { | |
| model: 'gemini-pro', | |
| isChatModel: false, | |
| }); | |
| expect(config.setActiveModel).not.toHaveBeenCalled(); | |
| }); | |
| it('consumes sticky attempt if indicated and isChatModel is true', () => { | |
| const config = createExtendedMockConfig(); | |
| mockModelConfigService.getResolvedConfig.mockReturnValue({ | |
| model: 'gemini-pro', | |
| generateContentConfig: {}, | |
| }); | |
| mockAvailabilityService.selectFirstAvailable.mockReturnValue({ | |
| selectedModel: 'gemini-pro', | |
| attempts: 1, | |
| }); | |
| const result = applyModelSelection(config, { | |
| model: 'gemini-pro', | |
| isChatModel: true, | |
| }); | |
| expect(mockAvailabilityService.consumeStickyAttempt).toHaveBeenCalledWith( | |
| 'gemini-pro', | |
| ); | |
| expect(config.setActiveModel).toHaveBeenCalledWith('gemini-pro'); | |
| expect(result.maxAttempts).toBe(1); | |
| }); | |
| it('consumes sticky attempt if indicated but does not call setActiveModel if isChatModel is false', () => { | |
| const config = createExtendedMockConfig(); | |
| mockModelConfigService.getResolvedConfig.mockReturnValue({ | |
| model: 'gemini-pro', | |
| generateContentConfig: {}, | |
| }); | |
| mockAvailabilityService.selectFirstAvailable.mockReturnValue({ | |
| selectedModel: 'gemini-pro', | |
| attempts: 1, | |
| }); | |
| const result = applyModelSelection(config, { | |
| model: 'gemini-pro', | |
| isChatModel: false, | |
| }); | |
| expect(mockAvailabilityService.consumeStickyAttempt).toHaveBeenCalledWith( | |
| 'gemini-pro', | |
| ); | |
| expect(config.setActiveModel).not.toHaveBeenCalled(); | |
| expect(result.maxAttempts).toBe(1); | |
| }); | |
| it('does not consume sticky attempt if consumeAttempt is false', () => { | |
| const config = createExtendedMockConfig(); | |
| mockModelConfigService.getResolvedConfig.mockReturnValue({ | |
| model: 'gemini-pro', | |
| generateContentConfig: {}, | |
| }); | |
| mockAvailabilityService.selectFirstAvailable.mockReturnValue({ | |
| selectedModel: 'gemini-pro', | |
| attempts: 1, | |
| }); | |
| const result = applyModelSelection( | |
| config, | |
| { model: 'gemini-pro', isChatModel: true }, | |
| { | |
| consumeAttempt: false, | |
| }, | |
| ); | |
| expect( | |
| mockAvailabilityService.consumeStickyAttempt, | |
| ).not.toHaveBeenCalled(); | |
| expect(config.setActiveModel).toHaveBeenCalledWith('gemini-pro'); | |
| expect(result.maxAttempts).toBe(1); | |
| }); | |
| }); | |
| describe('applyAvailabilityTransition', () => { | |
| it('marks terminal on terminal transition', () => { | |
| const mockService = { markTerminal: vi.fn() }; | |
| const context = { | |
| service: mockService, | |
| policy: { | |
| model: 'test-model', | |
| stateTransitions: { transient: 'terminal' }, | |
| }, | |
| }; | |
| const getContext = () => context as unknown as RetryAvailabilityContext; | |
| applyAvailabilityTransition(getContext, 'transient'); | |
| expect(mockService.markTerminal).toHaveBeenCalledWith( | |
| 'test-model', | |
| 'capacity', | |
| ); | |
| }); | |
| it('marks sticky and consumes on sticky_retry transition', () => { | |
| const mockService = { | |
| markRetryOncePerTurn: vi.fn(), | |
| consumeStickyAttempt: vi.fn(), | |
| }; | |
| const context = { | |
| service: mockService, | |
| policy: { | |
| model: 'test-model', | |
| stateTransitions: { transient: 'sticky_retry' }, | |
| maxAttempts: 3, | |
| }, | |
| }; | |
| const getContext = () => context as unknown as RetryAvailabilityContext; | |
| applyAvailabilityTransition(getContext, 'transient'); | |
| expect(mockService.markRetryOncePerTurn).toHaveBeenCalledWith( | |
| 'test-model', | |
| 3, | |
| ); | |
| expect(mockService.consumeStickyAttempt).toHaveBeenCalledWith( | |
| 'test-model', | |
| ); | |
| }); | |
| }); | |
| }); | |