import { type AiGatewayProvider } from '@/lib/ai/router'; // Base URLs mapping for providers const PROVIDER_URLS: Record = { openai: 'https://api.openai.com/v1', deepseek: 'https://api.deepseek.com/v1', openrouter: 'https://openrouter.ai/api/v1', mistral: 'https://api.mistral.ai/v1', zai: 'https://api.zukijourney.com/v1', minimax: 'https://api.minimax.io/v1', glm: 'https://open.bigmodel.ai/api/paas/v4', }; // Fallback popular models when live fetching fails or for providers without /models endpoint (e.g. Anthropic, Google) export const PROVIDER_MODEL_SUGGESTIONS: Record = { openai: ['gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'], anthropic: ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'], google: ['gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-2.0-flash-exp'], deepseek: ['deepseek-chat', 'deepseek-coder'], minimax: ['MiniMax-M2.7', 'MiniMax-M2.5', 'MiniMax-M2-her'], mistral: ['mistral-small-latest', 'mistral-medium-latest', 'mistral-large-latest'], glm: ['glm-4', 'glm-4-flash'], openrouter: ['openai/gpt-4o-mini', 'anthropic/claude-3.5-sonnet', 'deepseek/deepseek-chat'], custom: [], }; /** * Result of fetching models - includes whether they came from the real API or fallbacks */ export interface FetchModelsResult { models: string[] fromApi: boolean // true = fetched from provider API, false = fallback suggestions } /** * Dynamically queries the provider's /models endpoint using the user's API Key * to fetch their actual available models list instead of relying on hardcoded choices. */ export async function fetchLiveModelsForProvider( provider: AiGatewayProvider, apiKey: string, customBaseUrl?: string ): Promise { try { // Anthropic and Google do not expose a public list via a simple key GET /models (or need specific formats) // We fall back to the popular defaults for those. if ( provider === 'anthropic' || provider === 'anthropic_custom' || provider === 'custom_anthropic' || provider === 'google' || provider === 'minimax' ) { const standardProvider = provider === 'anthropic_custom' || provider === 'custom_anthropic' ? 'anthropic' : provider; const models = PROVIDER_MODEL_SUGGESTIONS[standardProvider] ?? []; return { models, fromApi: false }; } const baseUrl = (provider === 'custom' || provider === 'custom_openai') ? customBaseUrl?.replace(/\/$/, '') : PROVIDER_URLS[provider]; if (!baseUrl) { const models = PROVIDER_MODEL_SUGGESTIONS[provider] ?? []; return { models, fromApi: false }; } const headers: Record = { 'Content-Type': 'application/json' }; headers['Authorization'] = `Bearer ${apiKey}`; if (provider === 'openrouter') { headers['HTTP-Referer'] = 'https://localhost:3000'; headers['X-Title'] = 'Memento AI'; } const response = await fetch(`${baseUrl}/models`, { headers, signal: AbortSignal.timeout(6000), }); if (!response.ok) { console.warn(`[fetchLiveModelsForProvider] API returned ${response.status} for ${provider}`); const models = PROVIDER_MODEL_SUGGESTIONS[provider] ?? []; return { models, fromApi: false }; } const data = await response.json(); const fetched: string[] = (data.data ?? []) .map((m: any) => m.id || m.name) .filter(Boolean) .sort(); if (fetched.length > 0) { console.log(`[fetchLiveModelsForProvider] Got ${fetched.length} models from ${provider} API:`, fetched); return { models: fetched, fromApi: true }; } else { console.warn(`[fetchLiveModelsForProvider] API returned empty data array for ${provider}`); } } catch (err) { console.warn(`[fetchLiveModelsForProvider] Failed to fetch live models for ${provider}:`, err); } const fallbackProvider = provider === 'custom_openai' ? 'openai' : provider === 'custom_anthropic' ? 'anthropic' : provider === 'anthropic_custom' ? 'anthropic' : provider; const models = PROVIDER_MODEL_SUGGESTIONS[fallbackProvider] ?? []; return { models, fromApi: false }; }