/ src / server / settings / catalog.ts
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  }