/** * Model fetcher — retrieves available models from each provider's API. * * Each function returns a sorted list of model IDs. * Network errors return empty arrays (non-fatal). */ export interface ModelInfo { id: string; name: string; provider: string; } const TIMEOUT = 8000; export async function fetchClaudeModels(): Promise { const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) return []; try { const res = await fetch('https://api.anthropic.com/v1/models', { headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', }, signal: AbortSignal.timeout(TIMEOUT), }); if (!res.ok) return []; const data = await res.json() as { data?: Array<{ id: string; display_name?: string }> }; return (data.data ?? []) .map(m => ({ id: m.id, name: m.display_name ?? m.id, provider: 'claude' })) .sort((a, b) => a.id.localeCompare(b.id)); } catch { return []; } } export async function fetchCodexModels(): Promise { const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) return []; try { const res = await fetch('https://api.openai.com/v1/models', { headers: { 'Authorization': `Bearer ${apiKey}` }, signal: AbortSignal.timeout(TIMEOUT), }); if (!res.ok) return []; const data = await res.json() as { data?: Array<{ id: string }> }; return (data.data ?? []) .map(m => ({ id: m.id, name: m.id, provider: 'codex' })) .sort((a, b) => a.id.localeCompare(b.id)); } catch { return []; } } export async function fetchOllamaModels(): Promise { try { const res = await fetch('http://localhost:11434/api/tags', { signal: AbortSignal.timeout(TIMEOUT), }); if (!res.ok) return []; const data = await res.json() as { models?: Array<{ name: string; model?: string }> }; return (data.models ?? []) .map(m => ({ id: m.name, name: m.name, provider: 'ollama' })) .sort((a, b) => a.id.localeCompare(b.id)); } catch { return []; } } export async function fetchGeminiModels(): Promise { const apiKey = process.env.GEMINI_API_KEY; if (!apiKey) return []; try { const res = await fetch( `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, { signal: AbortSignal.timeout(TIMEOUT) }, ); if (!res.ok) return []; const data = await res.json() as { models?: Array<{ name: string; displayName?: string }>; }; return (data.models ?? []) .map(m => ({ id: m.name.replace('models/', ''), name: m.displayName ?? m.name, provider: 'gemini', })) .sort((a, b) => a.id.localeCompare(b.id)); } catch { return []; } } /** * Fetch models for a specific provider. */ export async function fetchModelsForProvider( provider: string, ): Promise { switch (provider) { case 'claude': return fetchClaudeModels(); case 'codex': return fetchCodexModels(); case 'ollama': return fetchOllamaModels(); case 'gemini': return fetchGeminiModels(); default: return []; } }