From 0e4677f558a2231b7eef7eb3de5c7f0336bbb041 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Fri, 10 Apr 2026 17:24:46 -0400 Subject: [PATCH] fix: sanitize cliproxy provider model pickers --- src/cliproxy/model-catalog.ts | 41 +++++- tests/unit/cliproxy/model-catalog.test.js | 29 ++-- .../cliproxy/provider-model-selector.tsx | 30 ++-- ui/src/lib/model-catalogs.ts | 131 +++++++++++++++++- .../cliproxy/provider-model-selector.test.tsx | 90 ++++++++++++ ui/tests/unit/ui/lib/preset-utils.test.ts | 51 +++++-- 6 files changed, 327 insertions(+), 45 deletions(-) create mode 100644 ui/tests/unit/components/cliproxy/provider-model-selector.test.tsx diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index 241c3b4e..3671eb6c 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -14,6 +14,21 @@ import { import { stripModelConfigurationSuffixes } from '../shared/extended-context-utils'; import { GEMINI_MINOR_VERSION_COMPATIBILITY_IDS } from '../shared/gemini-minor-version-compatibility'; +const AGY_GEMINI_PRO_HIGH_ID = 'gemini-3.1-pro-high'; +const AGY_GEMINI_PRO_LOW_ID = 'gemini-3.1-pro-low'; +const AGY_GEMINI_PRO_COMPATIBILITY_IDS = Object.freeze({ + 'gemini-3-pro-high': AGY_GEMINI_PRO_HIGH_ID, + 'gemini-3.1-pro-high': AGY_GEMINI_PRO_HIGH_ID, + 'gemini-3-pro-low': AGY_GEMINI_PRO_LOW_ID, + 'gemini-3.1-pro-low': AGY_GEMINI_PRO_LOW_ID, + 'gemini-3-pro-preview': AGY_GEMINI_PRO_HIGH_ID, + 'gemini-3-pro-preview-customtools': AGY_GEMINI_PRO_HIGH_ID, + 'gemini-3.1-pro-preview': AGY_GEMINI_PRO_HIGH_ID, + 'gemini-3.1-pro-preview-customtools': AGY_GEMINI_PRO_HIGH_ID, + 'gemini-3-1-pro-preview': AGY_GEMINI_PRO_HIGH_ID, + 'gemini-3-1-pro-preview-customtools': AGY_GEMINI_PRO_HIGH_ID, +} satisfies Record); + /** * Thinking support configuration for a model. * Defines how thinking/reasoning budget can be controlled. @@ -114,11 +129,19 @@ export const MODEL_CATALOG: Partial> = }, }, { - id: 'gemini-3.1-pro-preview', - name: 'Gemini 3.1 Pro', - description: 'Google latest Gemini Pro model via Antigravity', + id: AGY_GEMINI_PRO_HIGH_ID, + name: 'Gemini 3.1 Pro High', + description: 'Current Antigravity Gemini Pro route with higher reasoning budget', nativeImageInput: true, - thinking: { type: 'levels', levels: ['low', 'high'], dynamicAllowed: true }, + thinking: { type: 'none' }, + extendedContext: true, + }, + { + id: AGY_GEMINI_PRO_LOW_ID, + name: 'Gemini 3.1 Pro Low', + description: 'Current Antigravity Gemini Pro route with the lighter quota tier', + nativeImageInput: true, + thinking: { type: 'none' }, extendedContext: true, }, { @@ -465,6 +488,16 @@ export function findModel(provider: CLIProxyProvider, modelId: string): ModelEnt if (compatibilityId) { lookupCandidates.add(compatibilityId); } + + if (isAntigravityProvider(provider)) { + const agyCompatibilityId = + AGY_GEMINI_PRO_COMPATIBILITY_IDS[ + candidate as keyof typeof AGY_GEMINI_PRO_COMPATIBILITY_IDS + ]; + if (agyCompatibilityId) { + lookupCandidates.add(agyCompatibilityId); + } + } } return catalog.models.find((m) => lookupCandidates.has(m.id.toLowerCase())); diff --git a/tests/unit/cliproxy/model-catalog.test.js b/tests/unit/cliproxy/model-catalog.test.js index b379f5ab..17068475 100644 --- a/tests/unit/cliproxy/model-catalog.test.js +++ b/tests/unit/cliproxy/model-catalog.test.js @@ -91,11 +91,20 @@ describe('Model Catalog', () => { assert.strictEqual(ids.includes('claude-sonnet-4-5'), false); }); - it('includes Gemini 3.1 Pro (free via Antigravity)', () => { + it('includes Gemini 3.1 Pro High via Antigravity', () => { const { MODEL_CATALOG } = modelCatalog; - const gem3 = MODEL_CATALOG.agy.models.find((m) => m.id === 'gemini-3.1-pro-preview'); - assert(gem3, 'Should include Gemini 3.1 Pro'); - assert.strictEqual(gem3.name, 'Gemini 3.1 Pro'); + const gem3 = MODEL_CATALOG.agy.models.find((m) => m.id === 'gemini-3.1-pro-high'); + assert(gem3, 'Should include Gemini 3.1 Pro High'); + assert.strictEqual(gem3.name, 'Gemini 3.1 Pro High'); + // AGY models are all free - no paid tier + assert.strictEqual(gem3.tier, undefined, 'AGY models should not have paid tier'); + }); + + it('includes Gemini 3.1 Pro Low via Antigravity', () => { + const { MODEL_CATALOG } = modelCatalog; + const gem3 = MODEL_CATALOG.agy.models.find((m) => m.id === 'gemini-3.1-pro-low'); + assert(gem3, 'Should include Gemini 3.1 Pro Low'); + assert.strictEqual(gem3.name, 'Gemini 3.1 Pro Low'); // AGY models are all free - no paid tier assert.strictEqual(gem3.tier, undefined, 'AGY models should not have paid tier'); }); @@ -108,9 +117,9 @@ describe('Model Catalog', () => { assert.strictEqual(flash.tier, undefined, 'AGY models should not have paid tier'); }); - it('has 4 models total', () => { + it('has 5 models total', () => { const { MODEL_CATALOG } = modelCatalog; - assert.strictEqual(MODEL_CATALOG.agy.models.length, 4); + assert.strictEqual(MODEL_CATALOG.agy.models.length, 5); }); }); @@ -262,13 +271,15 @@ describe('Model Catalog', () => { assert.strictEqual(legacySonnet?.id, 'claude-sonnet-4-6'); }); - it('treats Gemini 3 and 3.1 preview IDs as the same catalog family', () => { + it('maps legacy Antigravity Gemini Pro aliases onto the current 3.1 high/low models', () => { const { findModel, getSuggestedReplacementModel } = modelCatalog; const legacyAgyGemini = findModel('agy', 'gemini-3-pro-preview'); + const legacyAgyGeminiLow = findModel('agy', 'gemini-3-pro-low'); const legacyGemini = findModel('gemini', 'gemini-3-pro-preview'); const currentGemini = findModel('gemini', 'gemini-3.1-pro-preview'); - assert.strictEqual(legacyAgyGemini?.id, 'gemini-3.1-pro-preview'); + assert.strictEqual(legacyAgyGemini?.id, 'gemini-3.1-pro-high'); + assert.strictEqual(legacyAgyGeminiLow?.id, 'gemini-3.1-pro-low'); assert.strictEqual(legacyGemini?.id, 'gemini-3.1-pro-preview'); assert.strictEqual(currentGemini?.id, 'gemini-3.1-pro-preview'); assert.strictEqual( @@ -371,7 +382,7 @@ describe('Model Catalog', () => { const sonnetThinkingIdx = models.findIndex((m) => m.id === 'claude-sonnet-4-6'); // Find indices of the remaining non-Claude model - const geminiIdx = models.findIndex((m) => m.id === 'gemini-3.1-pro-preview'); + const geminiIdx = models.findIndex((m) => m.id === 'gemini-3.1-pro-high'); // Primary Claude choices should appear ahead of Gemini fallback. assert(opusIdx < geminiIdx, 'Opus should be above Gemini'); diff --git a/ui/src/components/cliproxy/provider-model-selector.tsx b/ui/src/components/cliproxy/provider-model-selector.tsx index be7502bc..737915db 100644 --- a/ui/src/components/cliproxy/provider-model-selector.tsx +++ b/ui/src/components/cliproxy/provider-model-selector.tsx @@ -13,7 +13,7 @@ import { SearchableSelect } from '@/components/ui/searchable-select'; import { Skeleton } from '@/components/ui/skeleton'; import type { CliproxyProviderRoutingHints } from '@/lib/api-client'; import { getCodexEffortDisplay } from '@/lib/codex-effort'; -import { getResolvedCatalogModels } from '@/lib/model-catalogs'; +import { getResolvedCatalogModels, getSupplementalCatalogModels } from '@/lib/model-catalogs'; import { cn } from '@/lib/utils'; /** Model entry from catalog */ @@ -329,6 +329,10 @@ export function FlexibleModelSelector({ () => getResolvedCatalogModels(catalog, allModels), [allModels, catalog] ); + const supplementalModels = useMemo( + () => getSupplementalCatalogModels(catalog?.provider ?? '', catalog, allModels), + [allModels, catalog] + ); const catalogModelIds = new Set(resolvedCatalogModels.map((model) => model.id)); const routingHints = useMemo( () => @@ -388,7 +392,7 @@ export function FlexibleModelSelector({ ), })); - const allModelOptions = allModels + const allModelOptions = supplementalModels .filter((model) => !catalogModelIds.has(model.id)) .filter( (model) => @@ -495,14 +499,20 @@ export function FlexibleModelSelector({ {t('providerModelSelector.recommended')} ), }, - { - key: 'all', - label: ( - - {t('providerModelSelector.allModelsCount', { count: allModels.length })} - - ), - }, + ...(allModelOptions.length > 0 + ? [ + { + key: 'all', + label: ( + + {t('providerModelSelector.allModelsCount', { + count: allModelOptions.length, + })} + + ), + }, + ] + : []), ]} options={[ ...(selectedValueMissing && legacySelectedOption ? [legacySelectedOption] : []), diff --git a/ui/src/lib/model-catalogs.ts b/ui/src/lib/model-catalogs.ts index 0401b405..2ccccf28 100644 --- a/ui/src/lib/model-catalogs.ts +++ b/ui/src/lib/model-catalogs.ts @@ -9,6 +9,21 @@ import { GEMINI_MINOR_VERSION_COMPATIBILITY_IDS } from '@shared/gemini-minor-ver const GEMINI_PREVIEW_MODEL_ID_PATTERN = /^gemini-(\d+(?:[.-]\d+)*)-(pro|flash)-preview(-customtools)?$/i; +const AGY_GEMINI_PRO_HIGH_ID = 'gemini-3.1-pro-high'; +const AGY_GEMINI_PRO_LOW_ID = 'gemini-3.1-pro-low'; +const AGY_GEMINI_PRO_COMPATIBILITY_IDS = new Map([ + ['gemini-3-pro-high', AGY_GEMINI_PRO_HIGH_ID], + ['gemini-3.1-pro-high', AGY_GEMINI_PRO_HIGH_ID], + ['gemini-3-pro-low', AGY_GEMINI_PRO_LOW_ID], + ['gemini-3.1-pro-low', AGY_GEMINI_PRO_LOW_ID], + ['gemini-3-pro-preview', AGY_GEMINI_PRO_HIGH_ID], + ['gemini-3-pro-preview-customtools', AGY_GEMINI_PRO_HIGH_ID], + ['gemini-3.1-pro-preview', AGY_GEMINI_PRO_HIGH_ID], + ['gemini-3.1-pro-preview-customtools', AGY_GEMINI_PRO_HIGH_ID], + ['gemini-3-1-pro-preview', AGY_GEMINI_PRO_HIGH_ID], + ['gemini-3-1-pro-preview-customtools', AGY_GEMINI_PRO_HIGH_ID], +]); +const MANAGED_MODEL_PREFIXES = ['agy/', 'gcli/'] as const; export type CatalogAvailableModel = { id: string; @@ -29,6 +44,27 @@ function normalizeModelId(modelId: string): string { return stripModelConfigurationSuffixes(modelId).toLowerCase(); } +function stripManagedModelPrefix(modelId: string): string { + const trimmedModelId = modelId.trim(); + const normalizedModelId = trimmedModelId.toLowerCase(); + + for (const prefix of MANAGED_MODEL_PREFIXES) { + if (normalizedModelId.startsWith(prefix)) { + return trimmedModelId.slice(prefix.length); + } + } + + return trimmedModelId; +} + +function stripCustomtoolsSuffix(modelId: string): string { + return modelId.replace(/-customtools$/i, ''); +} + +function getAgyGeminiProCompatibilityId(modelId: string): string | undefined { + return AGY_GEMINI_PRO_COMPATIBILITY_IDS.get(normalizeModelId(modelId)); +} + function parseGeminiPreviewModelId(modelId: string): GeminiPreviewModelInfo | null { const normalizedId = normalizeModelId(modelId); const match = normalizedId.match(GEMINI_PREVIEW_MODEL_ID_PATTERN); @@ -142,14 +178,26 @@ export const MODEL_CATALOGS: Record = { }, }, { - id: 'gemini-3.1-pro-preview', - name: 'Gemini Pro', - description: 'Resolves to the best advertised Gemini Pro preview via Antigravity', + id: AGY_GEMINI_PRO_HIGH_ID, + name: 'Gemini Pro High', + description: 'Current Antigravity Gemini Pro route with higher reasoning budget', extendedContext: true, presetMapping: { - default: 'gemini-3.1-pro-preview', - opus: 'gemini-3.1-pro-preview', - sonnet: 'gemini-3.1-pro-preview', + default: AGY_GEMINI_PRO_HIGH_ID, + opus: AGY_GEMINI_PRO_HIGH_ID, + sonnet: AGY_GEMINI_PRO_HIGH_ID, + haiku: 'gemini-3-1-flash-preview', + }, + }, + { + id: AGY_GEMINI_PRO_LOW_ID, + name: 'Gemini Pro Low', + description: 'Current Antigravity Gemini Pro route with the lighter quota tier', + extendedContext: true, + presetMapping: { + default: AGY_GEMINI_PRO_LOW_ID, + opus: AGY_GEMINI_PRO_LOW_ID, + sonnet: AGY_GEMINI_PRO_LOW_ID, haiku: 'gemini-3-1-flash-preview', }, }, @@ -684,6 +732,13 @@ function findCatalogModelInCatalog(catalog: ProviderCatalog | undefined, modelId if (!catalog) return undefined; const normalizedModelId = normalizeModelId(modelId); + if (catalog.provider === 'agy') { + const agyCompatibilityId = getAgyGeminiProCompatibilityId(normalizedModelId); + if (agyCompatibilityId) { + const compatibilityMatch = catalog.models.find((model) => model.id === agyCompatibilityId); + if (compatibilityMatch) return compatibilityMatch; + } + } const compatibilityModelId = GEMINI_MINOR_VERSION_COMPATIBILITY_IDS[ normalizedModelId.toLowerCase() as keyof typeof GEMINI_MINOR_VERSION_COMPATIBILITY_IDS @@ -835,18 +890,24 @@ export function getResolvedCatalogModels( ) { if (!catalog) return []; + const recommendedCatalog = MODEL_CATALOGS[catalog.provider.toLowerCase()] ?? catalog; const seenModelIds = new Set(); - return catalog.models + return recommendedCatalog.models .map((model) => { const resolvedModelId = resolveCatalogModelId(model.id, availableModels); const resolvedPresetModelMapping = model.presetMapping ? resolvePresetMapping(model.presetMapping, availableModels) : undefined; + const liveModelMatch = catalog.models.find( + (catalogModel) => normalizeModelId(catalogModel.id) === normalizeModelId(resolvedModelId) + ); return { ...model, id: resolvedModelId, + name: liveModelMatch?.name || model.name, + description: liveModelMatch?.description ?? model.description, presetMapping: resolvedPresetModelMapping, }; }) @@ -857,6 +918,62 @@ export function getResolvedCatalogModels( }); } +export function getSupplementalCatalogModels( + provider: string, + catalog: ProviderCatalog | undefined, + availableModels: CatalogAvailableModel[] = [] +) { + const normalizedProvider = provider.trim().toLowerCase(); + if (!catalog || !normalizedProvider) return []; + + const staticCatalog = MODEL_CATALOGS[normalizedProvider] ?? catalog; + const recommendedModels = getResolvedCatalogModels(catalog, availableModels); + const recommendedIds = new Set(recommendedModels.map((model) => normalizeModelId(model.id))); + const recommendedCanonicalIds = new Set( + recommendedModels + .map((model) => findCatalogModelInCatalog(staticCatalog, model.id)?.id) + .filter((modelId): modelId is string => Boolean(modelId)) + .map((modelId) => normalizeModelId(modelId)) + ); + const seenCanonicalIds = new Set(); + const normalizedAvailableIds = new Set( + availableModels.map((model) => normalizeModelId(stripManagedModelPrefix(model.id))) + ); + + return availableModels.filter((availableModel) => { + const strippedModelId = stripManagedModelPrefix(availableModel.id); + const baseModelId = stripCustomtoolsSuffix(strippedModelId); + const matchedModel = + findCatalogModelInCatalog(staticCatalog, strippedModelId) ?? + findCatalogModelInCatalog(staticCatalog, baseModelId); + + if (!matchedModel) return false; + + if (recommendedIds.has(normalizeModelId(strippedModelId))) { + return false; + } + + const canonicalId = normalizeModelId(matchedModel.id); + if (recommendedCanonicalIds.has(canonicalId)) { + return false; + } + if (seenCanonicalIds.has(canonicalId)) { + return false; + } + + const normalizedBaseModelId = normalizeModelId(baseModelId); + if ( + normalizedBaseModelId !== normalizeModelId(strippedModelId) && + normalizedAvailableIds.has(normalizedBaseModelId) + ) { + return false; + } + + seenCanonicalIds.add(canonicalId); + return true; + }); +} + export function supportsExtendedContext( provider: string, modelId: string, diff --git a/ui/tests/unit/components/cliproxy/provider-model-selector.test.tsx b/ui/tests/unit/components/cliproxy/provider-model-selector.test.tsx new file mode 100644 index 00000000..3b8b94b1 --- /dev/null +++ b/ui/tests/unit/components/cliproxy/provider-model-selector.test.tsx @@ -0,0 +1,90 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { FlexibleModelSelector } from '@/components/cliproxy/provider-model-selector'; +import { buildUiCatalogs } from '@/lib/model-catalogs'; +import { render, screen, userEvent } from '@tests/setup/test-utils'; + +const noisyAgyModels = [ + { id: 'agy/gemini-3.1-pro-high', owned_by: 'antigravity' }, + { id: 'gemini-3.1-pro-high', owned_by: 'antigravity' }, + { id: 'gemini-3.1-pro-low', owned_by: 'antigravity' }, + { id: 'gemini-3.1-pro-preview', owned_by: 'antigravity' }, + { id: 'gemini-3.1-pro-preview-customtools', owned_by: 'antigravity' }, + { id: 'gemini-3-pro-preview', owned_by: 'antigravity' }, + { id: 'gemini-3-1-flash-preview', owned_by: 'antigravity' }, + { id: 'gemini-3-1-flash-preview-customtools', owned_by: 'antigravity' }, + { id: 'gpt-oss-120b-medium', owned_by: 'antigravity' }, +]; + +const catalog = buildUiCatalogs({ + agy: { + provider: 'agy', + displayName: 'Antigravity', + defaultModel: 'claude-opus-4-6-thinking', + models: noisyAgyModels.map(({ id }) => ({ id, name: id })), + }, +}).agy; + +describe('FlexibleModelSelector', () => { + beforeEach(() => { + Object.defineProperty(HTMLElement.prototype, 'hasPointerCapture', { + configurable: true, + value: vi.fn(() => false), + }); + Object.defineProperty(HTMLElement.prototype, 'setPointerCapture', { + configurable: true, + value: vi.fn(), + }); + Object.defineProperty(HTMLElement.prototype, 'releasePointerCapture', { + configurable: true, + value: vi.fn(), + }); + Object.defineProperty(Element.prototype, 'scrollIntoView', { + configurable: true, + value: vi.fn(), + }); + }); + + it('keeps the picker focused on curated Antigravity live routes', async () => { + render( + + ); + + await userEvent.click(screen.getByRole('button', { name: /select model/i })); + + expect(screen.getByText('gemini-3.1-pro-high')).toBeInTheDocument(); + expect(screen.getByText('gemini-3.1-pro-low')).toBeInTheDocument(); + expect(screen.getByText('gemini-3-1-flash-preview')).toBeInTheDocument(); + + expect(screen.queryByText('gemini-3.1-pro-preview')).not.toBeInTheDocument(); + expect(screen.queryByText('gemini-3-pro-preview')).not.toBeInTheDocument(); + expect(screen.queryByText('agy/gemini-3.1-pro-high')).not.toBeInTheDocument(); + expect(screen.queryByText('gemini-3-1-flash-preview-customtools')).not.toBeInTheDocument(); + expect(screen.queryByText('gpt-oss-120b-medium')).not.toBeInTheDocument(); + expect(screen.queryByText(/All Models \(/i)).not.toBeInTheDocument(); + }); + + it('preserves a filtered legacy value under the current-value fallback group', async () => { + render( + + ); + + await userEvent.click(screen.getByRole('button', { name: /gemini-3\.1-pro-preview/i })); + + expect(screen.getByText('Current value')).toBeInTheDocument(); + expect(screen.getAllByText('gemini-3.1-pro-preview').length).toBeGreaterThan(0); + expect(screen.getByText('gemini-3.1-pro-high')).toBeInTheDocument(); + }); +}); diff --git a/ui/tests/unit/ui/lib/preset-utils.test.ts b/ui/tests/unit/ui/lib/preset-utils.test.ts index dbc852a2..a258523b 100644 --- a/ui/tests/unit/ui/lib/preset-utils.test.ts +++ b/ui/tests/unit/ui/lib/preset-utils.test.ts @@ -5,6 +5,7 @@ import { MODEL_CATALOGS, findCatalogModel, getResolvedCatalogModels, + getSupplementalCatalogModels, resolveCatalogModelId, } from '@/lib/model-catalogs'; import { applyDefaultPreset } from '@/lib/preset-utils'; @@ -126,29 +127,49 @@ describe('claude preset utils', () => { ); }); - it('resolves Gemini preview presets to the best live family match', () => { + it('maps legacy Antigravity Gemini Pro aliases to the current catalog entries', () => { + expect(findCatalogModel('agy', 'gemini-3.1-pro-preview')?.id).toBe('gemini-3.1-pro-high'); + expect(findCatalogModel('agy', 'gemini-3-pro-preview')?.id).toBe('gemini-3.1-pro-high'); + expect(findCatalogModel('agy', 'gemini-3-pro-low')?.id).toBe('gemini-3.1-pro-low'); + }); + + it('resolves Antigravity Gemini presets to the current live-safe model ids', () => { const availableModels = [ - { id: 'gemini-3.9-pro-preview-customtools', owned_by: 'antigravity' }, - { id: 'gemini-3.9-pro-preview', owned_by: 'antigravity' }, - { id: 'gemini-3-9-flash-preview-customtools', owned_by: 'antigravity' }, - { id: 'gemini-3-9-flash-preview', owned_by: 'antigravity' }, + { id: 'agy/gemini-3.1-pro-high', owned_by: 'antigravity' }, + { id: 'gemini-3.1-pro-high', owned_by: 'antigravity' }, + { id: 'gemini-3.1-pro-low', owned_by: 'antigravity' }, { id: 'gemini-3.1-pro-preview', owned_by: 'antigravity' }, + { id: 'gemini-3.1-pro-preview-customtools', owned_by: 'antigravity' }, + { id: 'gemini-3-pro-preview', owned_by: 'antigravity' }, + { id: 'gemini-3-1-flash-preview-customtools', owned_by: 'antigravity' }, + { id: 'gemini-3-1-flash-preview', owned_by: 'antigravity' }, + { id: 'gpt-oss-120b-medium', owned_by: 'antigravity' }, ]; - expect(resolveCatalogModelId('gemini-3.1-pro-preview', availableModels)).toBe( - 'gemini-3.9-pro-preview' - ); - expect(resolveCatalogModelId('gemini-3-flash-preview', availableModels)).toBe( - 'gemini-3-9-flash-preview' - ); - expect(findCatalogModel('agy', 'gemini-3.9-pro-preview')?.id).toBe('gemini-3.1-pro-preview'); + expect(findCatalogModel('agy', 'gemini-3.1-pro-preview')?.id).toBe('gemini-3.1-pro-high'); const resolvedAgyModels = getResolvedCatalogModels(MODEL_CATALOGS.agy, availableModels); - expect(resolvedAgyModels.find((model) => model.name === 'Gemini Pro')?.id).toBe( - 'gemini-3.9-pro-preview' + expect(resolvedAgyModels.find((model) => model.name === 'Gemini Pro High')?.id).toBe( + 'gemini-3.1-pro-high' + ); + expect(resolvedAgyModels.find((model) => model.name === 'Gemini Pro Low')?.id).toBe( + 'gemini-3.1-pro-low' ); expect(resolvedAgyModels.find((model) => model.name === 'Gemini Flash')?.id).toBe( - 'gemini-3-9-flash-preview' + 'gemini-3-1-flash-preview' + ); + + const supplementalAgyModels = getSupplementalCatalogModels( + 'agy', + MODEL_CATALOGS.agy, + availableModels + ); + expect(supplementalAgyModels.map((model) => model.id)).not.toContain('gpt-oss-120b-medium'); + expect(supplementalAgyModels.map((model) => model.id)).not.toContain('gemini-3-pro-preview'); + expect(supplementalAgyModels.map((model) => model.id)).not.toContain('gemini-3.1-pro-preview'); + expect(supplementalAgyModels.map((model) => model.id)).not.toContain('agy/gemini-3.1-pro-high'); + expect(supplementalAgyModels.map((model) => model.id)).not.toContain( + 'gemini-3-1-flash-preview-customtools' ); });