import type { ModelListRow } from '@freellmapi/shared/types.js'; import { getDb } from '../db/index.js'; import { isUnifyEnabled, getModelGroups } from './model-groups.js'; // Shared catalog-listing logic behind both the OpenAI `GET /v1/models` and the // Anthropic `GET /v1/models` endpoints, so the two wire formats list the exact // same models (only the envelope differs). Extracted verbatim from the OpenAI // proxy route to keep a single source of truth. export interface NormalizedModel { id: string; name: string; ownedBy: string; available: number; enabled: number; contextWindow: number | null; intel: number; } export interface ModelListing { // Full catalog, sorted usable-first; callers apply their own `available` filter. models: NormalizedModel[]; // Honest ceiling for the virtual "auto" model: the largest context window // among models that can serve a request right now (null when nothing is // connected). Computed over available models regardless of any caller filter. autoContextWindow: number | null; } export function buildModelListing(): ModelListing { const availableExpr = ` (CASE WHEN m.enabled = 1 AND EXISTS ( SELECT 1 FROM api_keys k WHERE k.platform = m.platform AND k.enabled = 1 AND k.status IN ('healthy', 'unknown', 'error') AND (m.key_id IS NULL OR k.id = m.key_id) ) THEN 1 ELSE 0 END)`; const db = getDb(); let allListed: NormalizedModel[]; if (isUnifyEnabled()) { // Unify ON: one entry per logical model group. Pull per-row availability + // context keyed by db id, then aggregate over each group's members. type AvailRow = { id: number; platform: string; intelligence_rank: number; context_window: number | null; enabled: number; available: number }; const rows = db.prepare(` SELECT m.id, m.platform, m.intelligence_rank, m.context_window, m.enabled AS enabled, ${availableExpr} AS available FROM models m `).all() as AvailRow[]; const byId = new Map(rows.map(r => [r.id, r])); allListed = getModelGroups().map(g => { const infos = g.members.map(m => byId.get(m.model_db_id)).filter(Boolean) as AvailRow[]; const ctxs = infos.map(i => i.context_window).filter((c): c is number => c != null); return { id: g.canonicalId, name: g.groupLabel, ownedBy: 'freellmapi', available: infos.some(i => i.available === 1) ? 1 : 0, enabled: infos.some(i => i.enabled === 1) ? 1 : 0, contextWindow: ctxs.length ? Math.max(...ctxs) : null, intel: infos.length ? Math.min(...infos.map(i => i.intelligence_rank)) : Number.MAX_SAFE_INTEGER, }; }); } else { // Unify OFF: one entry per model_id (dedup picks the available, smartest // representative row). const models = db.prepare(` SELECT platform, model_id, display_name, context_window, enabled, available, intelligence_rank, id FROM ( SELECT m.platform, m.model_id, m.display_name, m.context_window, m.intelligence_rank, m.id, m.enabled AS enabled, ${availableExpr} AS available, ROW_NUMBER() OVER ( PARTITION BY m.model_id ORDER BY ${availableExpr} DESC, m.intelligence_rank ASC, m.id ASC ) AS rn FROM models m ) WHERE rn = 1 `).all() as (ModelListRow & { intelligence_rank: number; id: number })[]; allListed = models.map(m => ({ id: m.model_id, name: m.display_name, ownedBy: m.platform, available: m.available, enabled: m.enabled, contextWindow: m.context_window, intel: m.intelligence_rank, })); } // Stable order: usable first, then enabled, then smartest, then name. allListed.sort((a, b) => (b.available - a.available) || (b.enabled - a.enabled) || (a.intel - b.intel) || a.name.localeCompare(b.name)); const availableContextWindows = allListed .filter(m => m.available === 1 && m.contextWindow != null) .map(m => m.contextWindow as number); const autoContextWindow = availableContextWindows.length > 0 ? Math.max(...availableContextWindows) : null; return { models: allListed, autoContextWindow }; }