import type { AiGatewayProvider } from '@/lib/ai/router'; const OPENAI_COMPAT = new Set([ 'openai', 'deepseek', 'openrouter', 'mistral', 'zai', 'minimax', 'glm', 'custom', 'lmstudio', ]); async function validateOpenAiCompatible( apiKey: string, baseUrl: string, ): Promise { const res = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, { headers: { Authorization: `Bearer ${apiKey}` }, signal: AbortSignal.timeout(10_000), }); if (!res.ok) { throw new Error(`Provider rejected the API key (${res.status})`); } } async function validateAnthropic(apiKey: string): Promise { const res = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', 'content-type': 'application/json', }, body: JSON.stringify({ model: 'claude-3-5-haiku-20241022', max_tokens: 1, messages: [{ role: 'user', content: 'ping' }], }), signal: AbortSignal.timeout(15_000), }); if (res.status === 401 || res.status === 403) { throw new Error('Provider rejected the API key'); } if (res.status >= 500) { throw new Error('Provider temporarily unavailable; try again'); } } async function validateGoogle(apiKey: string): Promise { const res = await fetch( `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`, { signal: AbortSignal.timeout(10_000) }, ); if (!res.ok) { throw new Error(`Provider rejected the API key (${res.status})`); } } const BASE_URLS: Partial> = { 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', openai: 'https://api.openai.com/v1', }; export async function validateProviderApiKey( provider: AiGatewayProvider, apiKey: string, baseUrl?: string, ): Promise { if (!apiKey.trim()) { throw new Error('API key is required'); } if (provider === 'anthropic' || provider === 'anthropic_custom') { await validateAnthropic(apiKey); return; } if (provider === 'google') { await validateGoogle(apiKey); return; } if (provider === 'ollama' || provider === 'lmstudio') { throw new Error('Local providers are not supported for BYOK'); } if (OPENAI_COMPAT.has(provider)) { const url = provider === 'custom' ? baseUrl : BASE_URLS[provider as AiGatewayProvider]; if (!url) { throw new Error('Base URL is required for this provider'); } await validateOpenAiCompatible(apiKey, url); return; } throw new Error(`Unsupported provider: ${provider}`); }