type OpenAICompatibleValidationOptions = { provider: string; apiKey: string; baseUrl: string; model: string; signal?: AbortSignal; }; type ModelListValidationOptions = { provider: string; apiKey: string; modelsUrl: string; signal?: AbortSignal; }; const VALIDATION_TIMEOUT_MS = 15_000; /** * Validate an API key against an OpenAI-compatible chat completions endpoint. * * Performs a minimal request to verify credentials and endpoint access. */ export async function validateOpenAICompatibleApiKey(options: OpenAICompatibleValidationOptions): Promise { const timeoutSignal = AbortSignal.timeout(VALIDATION_TIMEOUT_MS); const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal; const response = await fetch(`${options.baseUrl}/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.apiKey}`, }, body: JSON.stringify({ model: options.model, messages: [{ role: "user", content: "ping" }], max_tokens: 1, temperature: 0, }), signal, }); if (response.ok) { return; } let details = ""; try { details = (await response.text()).trim(); } catch { // ignore body parse errors, status is enough } const message = details ? `${options.provider} API key validation failed (${response.status}): ${details}` : `${options.provider} API key validation failed (${response.status})`; throw new Error(message); } /** * Validate an API key against a provider models endpoint. * * Useful for providers where access to specific models may vary by plan and * should not block key validation. */ export async function validateApiKeyAgainstModelsEndpoint(options: ModelListValidationOptions): Promise { const timeoutSignal = AbortSignal.timeout(VALIDATION_TIMEOUT_MS); const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal; const response = await fetch(options.modelsUrl, { method: "GET", headers: { Authorization: `Bearer ${options.apiKey}`, }, signal, }); if (response.ok) { return; } let details = ""; try { details = (await response.text()).trim(); } catch { // ignore body parse errors, status is enough } const message = details ? `${options.provider} API key validation failed (${response.status}): ${details}` : `${options.provider} API key validation failed (${response.status})`; throw new Error(message); }