fix: sanitize cliproxy provider model pickers

This commit is contained in:
Tam Nhu Tran committed 2026-04-10 18:07:22 -04:00
1 parent cfdae7be9f
commit 0e4677f558
6 files changed
+327 -45

No files matched your search

+37 -4
View File
@@ -14,6 +14,21 @@ import {
import { stripModelConfigurationSuffixes } from '../shared/extended-context-utils'; import { stripModelConfigurationSuffixes } from '../shared/extended-context-utils';
import { GEMINI_MINOR_VERSION_COMPATIBILITY_IDS } from '../shared/gemini-minor-version-compatibility'; 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<string, string>);
/** /**
* Thinking support configuration for a model. * Thinking support configuration for a model.
* Defines how thinking/reasoning budget can be controlled. * Defines how thinking/reasoning budget can be controlled.
@@ -114,11 +129,19 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
}, },
}, },
{ {
id: 'gemini-3.1-pro-preview', id: AGY_GEMINI_PRO_HIGH_ID,
name: 'Gemini 3.1 Pro', name: 'Gemini 3.1 Pro High',
description: 'Google latest Gemini Pro model via Antigravity', description: 'Current Antigravity Gemini Pro route with higher reasoning budget',
nativeImageInput: true, 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, extendedContext: true,
}, },
{ {
@@ -465,6 +488,16 @@ export function findModel(provider: CLIProxyProvider, modelId: string): ModelEnt
if (compatibilityId) { if (compatibilityId) {
lookupCandidates.add(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())); return catalog.models.find((m) => lookupCandidates.has(m.id.toLowerCase()));
+20 -9
View File
@@ -91,11 +91,20 @@ describe('Model Catalog', () => {
assert.strictEqual(ids.includes('claude-sonnet-4-5'), false); 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 { MODEL_CATALOG } = modelCatalog;
const gem3 = MODEL_CATALOG.agy.models.find((m) => m.id === 'gemini-3.1-pro-preview'); const gem3 = MODEL_CATALOG.agy.models.find((m) => m.id === 'gemini-3.1-pro-high');
assert(gem3, 'Should include Gemini 3.1 Pro'); assert(gem3, 'Should include Gemini 3.1 Pro High');
assert.strictEqual(gem3.name, 'Gemini 3.1 Pro'); 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 // AGY models are all free - no paid tier
assert.strictEqual(gem3.tier, undefined, 'AGY models should not have 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'); 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; 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'); 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 { findModel, getSuggestedReplacementModel } = modelCatalog;
const legacyAgyGemini = findModel('agy', 'gemini-3-pro-preview'); 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 legacyGemini = findModel('gemini', 'gemini-3-pro-preview');
const currentGemini = findModel('gemini', 'gemini-3.1-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(legacyGemini?.id, 'gemini-3.1-pro-preview');
assert.strictEqual(currentGemini?.id, 'gemini-3.1-pro-preview'); assert.strictEqual(currentGemini?.id, 'gemini-3.1-pro-preview');
assert.strictEqual( assert.strictEqual(
@@ -371,7 +382,7 @@ describe('Model Catalog', () => {
const sonnetThinkingIdx = models.findIndex((m) => m.id === 'claude-sonnet-4-6'); const sonnetThinkingIdx = models.findIndex((m) => m.id === 'claude-sonnet-4-6');
// Find indices of the remaining non-Claude model // 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. // Primary Claude choices should appear ahead of Gemini fallback.
assert(opusIdx < geminiIdx, 'Opus should be above Gemini'); assert(opusIdx < geminiIdx, 'Opus should be above Gemini');
@@ -13,7 +13,7 @@ import { SearchableSelect } from '@/components/ui/searchable-select';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import type { CliproxyProviderRoutingHints } from '@/lib/api-client'; import type { CliproxyProviderRoutingHints } from '@/lib/api-client';
import { getCodexEffortDisplay } from '@/lib/codex-effort'; import { getCodexEffortDisplay } from '@/lib/codex-effort';
import { getResolvedCatalogModels } from '@/lib/model-catalogs'; import { getResolvedCatalogModels, getSupplementalCatalogModels } from '@/lib/model-catalogs';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
/** Model entry from catalog */ /** Model entry from catalog */
@@ -329,6 +329,10 @@ export function FlexibleModelSelector({
() => getResolvedCatalogModels(catalog, allModels), () => getResolvedCatalogModels(catalog, allModels),
[allModels, catalog] [allModels, catalog]
); );
const supplementalModels = useMemo(
() => getSupplementalCatalogModels(catalog?.provider ?? '', catalog, allModels),
[allModels, catalog]
);
const catalogModelIds = new Set(resolvedCatalogModels.map((model) => model.id)); const catalogModelIds = new Set(resolvedCatalogModels.map((model) => model.id));
const routingHints = useMemo( const routingHints = useMemo(
() => () =>
@@ -388,7 +392,7 @@ export function FlexibleModelSelector({
), ),
})); }));
const allModelOptions = allModels const allModelOptions = supplementalModels
.filter((model) => !catalogModelIds.has(model.id)) .filter((model) => !catalogModelIds.has(model.id))
.filter( .filter(
(model) => (model) =>
@@ -495,14 +499,20 @@ export function FlexibleModelSelector({
<span className="text-xs text-primary">{t('providerModelSelector.recommended')}</span> <span className="text-xs text-primary">{t('providerModelSelector.recommended')}</span>
), ),
}, },
{ ...(allModelOptions.length > 0
key: 'all', ? [
label: ( {
<span className="text-xs text-muted-foreground"> key: 'all',
{t('providerModelSelector.allModelsCount', { count: allModels.length })} label: (
</span> <span className="text-xs text-muted-foreground">
), {t('providerModelSelector.allModelsCount', {
}, count: allModelOptions.length,
})}
</span>
),
},
]
: []),
]} ]}
options={[ options={[
...(selectedValueMissing && legacySelectedOption ? [legacySelectedOption] : []), ...(selectedValueMissing && legacySelectedOption ? [legacySelectedOption] : []),
+124 -7
View File
@@ -9,6 +9,21 @@ import { GEMINI_MINOR_VERSION_COMPATIBILITY_IDS } from '@shared/gemini-minor-ver
const GEMINI_PREVIEW_MODEL_ID_PATTERN = const GEMINI_PREVIEW_MODEL_ID_PATTERN =
/^gemini-(\d+(?:[.-]\d+)*)-(pro|flash)-preview(-customtools)?$/i; /^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<string, string>([
['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 = { export type CatalogAvailableModel = {
id: string; id: string;
@@ -29,6 +44,27 @@ function normalizeModelId(modelId: string): string {
return stripModelConfigurationSuffixes(modelId).toLowerCase(); 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 { function parseGeminiPreviewModelId(modelId: string): GeminiPreviewModelInfo | null {
const normalizedId = normalizeModelId(modelId); const normalizedId = normalizeModelId(modelId);
const match = normalizedId.match(GEMINI_PREVIEW_MODEL_ID_PATTERN); const match = normalizedId.match(GEMINI_PREVIEW_MODEL_ID_PATTERN);
@@ -142,14 +178,26 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
}, },
}, },
{ {
id: 'gemini-3.1-pro-preview', id: AGY_GEMINI_PRO_HIGH_ID,
name: 'Gemini Pro', name: 'Gemini Pro High',
description: 'Resolves to the best advertised Gemini Pro preview via Antigravity', description: 'Current Antigravity Gemini Pro route with higher reasoning budget',
extendedContext: true, extendedContext: true,
presetMapping: { presetMapping: {
default: 'gemini-3.1-pro-preview', default: AGY_GEMINI_PRO_HIGH_ID,
opus: 'gemini-3.1-pro-preview', opus: AGY_GEMINI_PRO_HIGH_ID,
sonnet: 'gemini-3.1-pro-preview', 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', haiku: 'gemini-3-1-flash-preview',
}, },
}, },
@@ -684,6 +732,13 @@ function findCatalogModelInCatalog(catalog: ProviderCatalog | undefined, modelId
if (!catalog) return undefined; if (!catalog) return undefined;
const normalizedModelId = normalizeModelId(modelId); 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 = const compatibilityModelId =
GEMINI_MINOR_VERSION_COMPATIBILITY_IDS[ GEMINI_MINOR_VERSION_COMPATIBILITY_IDS[
normalizedModelId.toLowerCase() as keyof typeof 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 []; if (!catalog) return [];
const recommendedCatalog = MODEL_CATALOGS[catalog.provider.toLowerCase()] ?? catalog;
const seenModelIds = new Set<string>(); const seenModelIds = new Set<string>();
return catalog.models return recommendedCatalog.models
.map((model) => { .map((model) => {
const resolvedModelId = resolveCatalogModelId(model.id, availableModels); const resolvedModelId = resolveCatalogModelId(model.id, availableModels);
const resolvedPresetModelMapping = model.presetMapping const resolvedPresetModelMapping = model.presetMapping
? resolvePresetMapping(model.presetMapping, availableModels) ? resolvePresetMapping(model.presetMapping, availableModels)
: undefined; : undefined;
const liveModelMatch = catalog.models.find(
(catalogModel) => normalizeModelId(catalogModel.id) === normalizeModelId(resolvedModelId)
);
return { return {
...model, ...model,
id: resolvedModelId, id: resolvedModelId,
name: liveModelMatch?.name || model.name,
description: liveModelMatch?.description ?? model.description,
presetMapping: resolvedPresetModelMapping, 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<string>();
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( export function supportsExtendedContext(
provider: string, provider: string,
modelId: string, modelId: string,
@@ -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(
<FlexibleModelSelector
label="Primary model"
value={undefined}
onChange={vi.fn()}
catalog={catalog}
allModels={noisyAgyModels}
/>
);
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(
<FlexibleModelSelector
label="Primary model"
value="gemini-3.1-pro-preview"
onChange={vi.fn()}
catalog={catalog}
allModels={noisyAgyModels}
/>
);
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();
});
});
+36 -15
View File
@@ -5,6 +5,7 @@ import {
MODEL_CATALOGS, MODEL_CATALOGS,
findCatalogModel, findCatalogModel,
getResolvedCatalogModels, getResolvedCatalogModels,
getSupplementalCatalogModels,
resolveCatalogModelId, resolveCatalogModelId,
} from '@/lib/model-catalogs'; } from '@/lib/model-catalogs';
import { applyDefaultPreset } from '@/lib/preset-utils'; 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 = [ const availableModels = [
{ id: 'gemini-3.9-pro-preview-customtools', owned_by: 'antigravity' }, { id: 'agy/gemini-3.1-pro-high', owned_by: 'antigravity' },
{ id: 'gemini-3.9-pro-preview', owned_by: 'antigravity' }, { id: 'gemini-3.1-pro-high', owned_by: 'antigravity' },
{ id: 'gemini-3-9-flash-preview-customtools', owned_by: 'antigravity' }, { id: 'gemini-3.1-pro-low', owned_by: 'antigravity' },
{ id: 'gemini-3-9-flash-preview', owned_by: 'antigravity' },
{ id: 'gemini-3.1-pro-preview', 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( expect(findCatalogModel('agy', 'gemini-3.1-pro-preview')?.id).toBe('gemini-3.1-pro-high');
'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');
const resolvedAgyModels = getResolvedCatalogModels(MODEL_CATALOGS.agy, availableModels); const resolvedAgyModels = getResolvedCatalogModels(MODEL_CATALOGS.agy, availableModels);
expect(resolvedAgyModels.find((model) => model.name === 'Gemini Pro')?.id).toBe( expect(resolvedAgyModels.find((model) => model.name === 'Gemini Pro High')?.id).toBe(
'gemini-3.9-pro-preview' '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( 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'
); );
}); });