fix: keep live catalog defaults visible

This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-30 14:57:02 -04:00
parent c21f589247
commit f9dec01cfd
7 changed files with 87 additions and 18 deletions
@@ -55,6 +55,18 @@ describe('model-catalog compatibility lookups', () => {
expect(catalog?.models.map((model) => model.id)).toEqual(['gemini-2.5-pro']); expect(catalog?.models.map((model) => model.id)).toEqual(['gemini-2.5-pro']);
}); });
it('falls back to a visible live model when the static default is absent', () => {
const catalog = mergeCatalog('gemini', [
{
id: 'gemini-live-only',
display_name: 'Gemini Live Only',
},
]);
expect(catalog?.defaultModel).toBe('gemini-live-only');
expect(catalog?.models.map((model) => model.id)).toContain(catalog?.defaultModel);
});
it('preserves static maxLevel when live thinking metadata omits it', () => { it('preserves static maxLevel when live thinking metadata omits it', () => {
const catalog = mergeCatalog('claude', [ const catalog = mergeCatalog('claude', [
{ {
+4 -6
View File
@@ -483,9 +483,8 @@ export async function reconcileExhaustedRotationAccounts(
return []; return [];
} }
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = await import( const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } =
'../accounts/account-safety' await import('../accounts/account-safety');
);
restoreExpiredQuotaPauses(); restoreExpiredQuotaPauses();
const config = loadOrCreateUnifiedConfig(); const config = loadOrCreateUnifiedConfig();
@@ -593,9 +592,8 @@ export async function preflightCheck(provider: CLIProxyProvider): Promise<Prefli
return { proceed: true, accountId: defaultAccount?.id || '' }; return { proceed: true, accountId: defaultAccount?.id || '' };
} }
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = await import( const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } =
'../accounts/account-safety' await import('../accounts/account-safety');
);
restoreExpiredQuotaPauses(); restoreExpiredQuotaPauses();
const config = loadOrCreateUnifiedConfig(); const config = loadOrCreateUnifiedConfig();
+6 -4
View File
@@ -249,7 +249,6 @@ export function mergeCatalog(
if (!staticCatalog && filteredRemoteModels.length === 0) return undefined; if (!staticCatalog && filteredRemoteModels.length === 0) return undefined;
const displayName = staticCatalog?.displayName || provider; const displayName = staticCatalog?.displayName || provider;
const defaultModel = staticCatalog?.defaultModel || (filteredRemoteModels[0]?.id ?? '');
// Build map of static models by lowercase ID for fast lookup // Build map of static models by lowercase ID for fast lookup
const staticMap = new Map<string, ModelEntry>(); const staticMap = new Map<string, ModelEntry>();
@@ -260,14 +259,11 @@ export function mergeCatalog(
} }
// Process remote models: merge with static entries // Process remote models: merge with static entries
const mergedIds = new Set<string>();
const mergedModels: ModelEntry[] = []; const mergedModels: ModelEntry[] = [];
for (const remote of filteredRemoteModels) { for (const remote of filteredRemoteModels) {
const remoteEntry = mapRemoteToModelEntry(remote); const remoteEntry = mapRemoteToModelEntry(remote);
const staticEntry = staticMap.get(remote.id.toLowerCase()); const staticEntry = staticMap.get(remote.id.toLowerCase());
mergedIds.add(remote.id.toLowerCase());
if (staticEntry) { if (staticEntry) {
const mergedThinking = remoteEntry.thinking const mergedThinking = remoteEntry.thinking
? { ? {
@@ -292,6 +288,12 @@ export function mergeCatalog(
} }
} }
const staticDefaultModel = staticCatalog?.defaultModel;
const hasStaticDefaultModel =
typeof staticDefaultModel === 'string' &&
mergedModels.some((model) => model.id.toLowerCase() === staticDefaultModel.toLowerCase());
const defaultModel = hasStaticDefaultModel ? staticDefaultModel : (mergedModels[0]?.id ?? '');
return { return {
provider, provider,
displayName, displayName,
@@ -112,9 +112,8 @@ export async function runImageAnalysisCheck(results: HealthCheck): Promise<void>
* Fix image analysis configuration issues * Fix image analysis configuration issues
*/ */
export async function fixImageAnalysisConfig(): Promise<boolean> { export async function fixImageAnalysisConfig(): Promise<boolean> {
const { updateConfig, loadOrCreateUnifiedConfig } = await import( const { updateConfig, loadOrCreateUnifiedConfig } =
'../../config/config-loader-facade' await import('../../config/config-loader-facade');
);
const config = loadOrCreateUnifiedConfig(); const config = loadOrCreateUnifiedConfig();
let fixed = false; let fixed = false;
+9 -1
View File
@@ -792,11 +792,19 @@ export function buildUiCatalog(
availableModels.some( availableModels.some(
(model) => normalizeModelId(model.id) === normalizeModelId(fallbackDefaultModel) (model) => normalizeModelId(model.id) === normalizeModelId(fallbackDefaultModel)
); );
const hasLiveDefaultModel = availableModels.some(
(model) => normalizeModelId(model.id) === normalizeModelId(liveCatalog.defaultModel)
);
const defaultModel = hasFallbackDefaultModel
? fallbackDefaultModel
: hasLiveDefaultModel
? liveCatalog.defaultModel
: (models[0]?.id ?? '');
return { return {
provider: liveCatalog.provider, provider: liveCatalog.provider,
displayName: liveCatalog.displayName || staticCatalog?.displayName || provider, displayName: liveCatalog.displayName || staticCatalog?.displayName || provider,
defaultModel: hasFallbackDefaultModel ? fallbackDefaultModel : liveCatalog.defaultModel, defaultModel,
models, models,
}; };
} }
+5 -4
View File
@@ -66,11 +66,12 @@ export async function applyDefaultPreset(
const defaultModelEntry = const defaultModelEntry =
resolvedCatalog.models.find((model) => model.id === resolvedCatalog.defaultModel) || resolvedCatalog.models.find((model) => model.id === resolvedCatalog.defaultModel) ||
resolvedCatalog.models[0]; resolvedCatalog.models[0];
const selectedModelId = defaultModelEntry?.id ?? resolvedCatalog.defaultModel;
const mapping = defaultModelEntry?.presetMapping || { const mapping = defaultModelEntry?.presetMapping || {
default: resolvedCatalog.defaultModel, default: selectedModelId,
opus: resolvedCatalog.defaultModel, opus: selectedModelId,
sonnet: resolvedCatalog.defaultModel, sonnet: selectedModelId,
haiku: resolvedCatalog.defaultModel, haiku: selectedModelId,
}; };
// Fetch effective API key (respects user customization) // Fetch effective API key (respects user customization)
+49
View File
@@ -116,6 +116,55 @@ describe('claude preset utils', () => {
); );
}); });
it('builds UI catalogs with a visible default when the live default is stale', () => {
const liveCatalogs = {
gemini: {
provider: 'gemini',
displayName: 'Gemini',
defaultModel: 'gemini-2.5-pro',
models: [{ id: 'gemini-live-only', name: 'Gemini Live Only' }],
},
};
const catalogs = buildUiCatalogs(liveCatalogs);
expect(catalogs.gemini?.defaultModel).toBe('gemini-live-only');
expect(catalogs.gemini?.models.map((model) => model.id)).toContain(
catalogs.gemini?.defaultModel
);
});
it('uses the selected visible model for quick setup fallback mappings', async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce({
ok: true,
json: async () => ({ apiKey: { value: 'managed-key' } }),
})
.mockResolvedValueOnce({ ok: true });
vi.stubGlobal('fetch', fetchMock);
const result = await applyDefaultPreset('gemini', undefined, {
provider: 'gemini',
displayName: 'Gemini',
defaultModel: 'gemini-2.5-pro',
models: [{ id: 'gemini-live-only', name: 'Gemini Live Only' }],
});
expect(result).toEqual({ success: true, presetName: 'Gemini Live Only' });
const [, requestInit] = fetchMock.mock.calls[1] ?? [];
const body = JSON.parse(String(requestInit?.body));
expect(body.settings.env).toMatchObject({
ANTHROPIC_MODEL: 'gemini-live-only',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-live-only',
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-live-only',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-live-only',
});
});
it('keeps Gemini presets on 3.1 Pro while resolving 3/3.1 alias variants', () => { it('keeps Gemini presets on 3.1 Pro while resolving 3/3.1 alias variants', () => {
const geminiCatalog = MODEL_CATALOGS.gemini; const geminiCatalog = MODEL_CATALOGS.gemini;
const latestPro = geminiCatalog.models.find((model) => model.id === 'gemini-3.1-pro-preview'); const latestPro = geminiCatalog.models.find((model) => model.id === 'gemini-3.1-pro-preview');