import { NextRequest } from 'next/server'; import { createLogger } from '@/lib/logger'; import { apiError, apiSuccess } from '@/lib/server/api-response'; import { validateUrlForSSRF } from '@/lib/server/ssrf-guard'; import { fetchModels, ModelFetchError } from '@/lib/server/model-fetch'; const log = createLogger('ProbeModels'); /** Model ids that are not chat models — filtered out of probe results. */ const NON_CHAT_PATTERN = /(tts|asr|whisper|embedding|rerank|mineru|image|video|voxcpm|moderation)/i; /** * POST /api/provider/probe-models * * Discovers the chat models a base URL + key exposes, via the OpenAI-compatible * /models endpoint (with multi-candidate fallback). Returns the lit-up list, or * a typed status so the UI can fall back to manual model entry. */ export async function POST(req: NextRequest) { try { const body = await req.json(); const { baseUrl, apiKey, modelsUrl } = body as { baseUrl?: string; apiKey?: string; modelsUrl?: string; }; if (!baseUrl) { return apiError('MISSING_REQUIRED_FIELD', 400, 'baseUrl is required'); } // SSRF guard on both the base URL and an explicit models URL override. for (const url of [baseUrl, modelsUrl].filter(Boolean) as string[]) { const ssrfError = await validateUrlForSSRF(url); if (ssrfError) return apiError('INVALID_REQUEST', 400, ssrfError); } const models = await fetchModels(baseUrl, apiKey || '', { modelsUrlOverride: modelsUrl }); const chatModels = models.filter((m) => !NON_CHAT_PATTERN.test(m.id)); return apiSuccess({ models: chatModels.map((m) => ({ id: m.id, ownedBy: m.ownedBy })), total: models.length, filtered: models.length - chatModels.length, }); } catch (error) { if (error instanceof ModelFetchError) { if (error.status >= 300 && error.status < 400) { return apiError('REDIRECT_NOT_ALLOWED', 403, 'Redirects are not allowed'); } if (error.status === 401 || error.status === 403) { return apiError('INVALID_REQUEST', 401, 'API key is invalid or expired'); } if (error.status === 404) { // No /models endpoint — signal the UI (via 404) to use manual model entry. return apiError('INVALID_REQUEST', 404, 'This provider does not expose a model list'); } return apiError('INTERNAL_ERROR', 502, error.message); } log.error('Model probe failed:', error); return apiError( 'INTERNAL_ERROR', 500, error instanceof Error ? error.message : 'Failed to probe models', ); } }