catalog.ts
1 import type { 2 GlobalDefaults, 3 ProviderCatalogItem, 4 } from '@/lib/shared/chat' 5 import type { ProviderWithSettings } from '@/lib/shared/providers' 6 import { resolveProviderScopedDefaultChatModel } from '@/server/storage/app-settings' 7 import { listProviderProfiles } from '@/server/storage/providers' 8 9 export function buildProviderCatalog( 10 defaults: GlobalDefaults, 11 providers: ProviderWithSettings[] = listProviderProfiles(), 12 ): ProviderCatalogItem[] { 13 return providers.map((provider) => { 14 const models = uniqueNonEmptyModelIds( 15 provider.settings.models ?? provider.models ?? [], 16 ) 17 const modelCapabilities = normalizeProviderModelCapabilities( 18 provider.settings.models ?? provider.models ?? [], 19 ) 20 const fallbackDefaultModel = models[0] ?? null 21 const defaultChatModel = 22 resolveProviderScopedDefaultChatModel(defaults, provider.id) ?? 23 fallbackDefaultModel 24 const baseUrl = provider.settings.apiHost?.trim() ?? null 25 26 return { 27 id: provider.id, 28 label: provider.name, 29 available: Boolean(baseUrl), 30 baseUrl, 31 description: provider.isCustom 32 ? 'Custom provider saved in app settings.' 33 : 'Built-in provider profile saved in app settings.', 34 defaultChatModel, 35 models, 36 modelCapabilities, 37 } 38 }) 39 } 40 41 function uniqueNonEmptyModelIds( 42 models: NonNullable<ProviderWithSettings['settings']['models']>, 43 ): string[] { 44 return Array.from( 45 new Set( 46 models 47 .map((model) => model.modelId.trim()) 48 .filter((modelId) => modelId.length > 0), 49 ), 50 ) 51 } 52 53 function normalizeProviderModelCapabilities( 54 models: NonNullable<ProviderWithSettings['settings']['models']>, 55 ): Record<string, string[]> { 56 return Object.fromEntries( 57 models 58 .map((model) => { 59 const modelId = model.modelId.trim() 60 if (!modelId) return null 61 const capabilities = Array.from( 62 new Set( 63 (model.capabilities ?? []) 64 .map((capability) => capability.trim()) 65 .filter((capability) => capability.length > 0), 66 ), 67 ) 68 if (capabilities.length === 0) return null 69 return [modelId, capabilities] as const 70 }) 71 .filter((entry): entry is readonly [string, string[]] => Boolean(entry)), 72 ) 73 }