import { NextResponse } from "next/server"; import { getProviderDefaultBaseUrl } from "@/lib/ai/provider-defaults"; import { resolveModelRuntimeId } from "@/lib/ai/model-runtime"; import { getAgentAiModelWithProvider } from "@/lib/db/queries"; import { createLogger } from "@/lib/observability/logger"; import { isPascalInternalRequest } from "@/lib/pascal/internal-auth"; export const dynamic = "force-dynamic"; export const maxDuration = 600; export const runtime = "nodejs"; const logger = createLogger("pascal-model-gateway"); const OPENAI_COMPATIBLE_PROVIDER_TYPES = new Set([ "alibaba", "custom", "openai", "zhipu", ]); function upstreamChatUrl(baseUrl: string) { const normalized = baseUrl.replace(/\/+$/, ""); return normalized.endsWith("/chat/completions") ? normalized : `${normalized}/chat/completions`; } function openAiError(message: string, status: number) { return NextResponse.json( { error: { message, type: "pascal_gateway_error" } }, { status } ); } export async function GET(request: Request) { if (!isPascalInternalRequest(request)) { return NextResponse.json({ error: "not_found" }, { status: 404 }); } const configured = await getAgentAiModelWithProvider(); if (!configured) { return NextResponse.json({ configured: false }); } return NextResponse.json({ configured: true, compatible: OPENAI_COMPATIBLE_PROVIDER_TYPES.has(configured.provider.type), model: configured.model.displayName || configured.model.modelId, provider: configured.provider.name, providerType: configured.provider.type, }); } export async function POST(request: Request) { if (!isPascalInternalRequest(request)) { return NextResponse.json({ error: "not_found" }, { status: 404 }); } let body: Record; try { const parsed = await request.json(); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return openAiError("Invalid request body.", 400); } body = parsed as Record; } catch { return openAiError("Invalid JSON body.", 400); } const configured = await getAgentAiModelWithProvider(); if (!configured) { return openAiError("No enabled Agent text model is configured.", 503); } const { model, provider } = configured; if (!OPENAI_COMPATIBLE_PROVIDER_TYPES.has(provider.type)) { return openAiError( `The configured Agent provider type '${provider.type}' is not OpenAI-compatible.`, 503 ); } const baseUrl = provider.baseUrl?.trim() || getProviderDefaultBaseUrl(provider.type) || ""; const runtimeModelId = resolveModelRuntimeId(model, { providerType: provider.type, }); if (!baseUrl || !runtimeModelId || !provider.apiKey) { return openAiError("The configured Agent model is incomplete.", 503); } const startedAt = Date.now(); try { const upstream = await fetch(upstreamChatUrl(baseUrl), { method: "POST", headers: { Authorization: `Bearer ${provider.apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ ...body, model: runtimeModelId }), cache: "no-store", signal: request.signal, }); logger.info("upstream completed", { elapsedMs: Date.now() - startedAt, modelId: model.id, providerId: provider.id, status: upstream.status, }); const headers = new Headers({ "Cache-Control": "no-cache, no-transform", "Content-Type": upstream.headers.get("content-type") || "application/json; charset=utf-8", "X-Accel-Buffering": "no", }); return new Response(upstream.body, { status: upstream.status, headers }); } catch (error) { logger.error("upstream failed", { elapsedMs: Date.now() - startedAt, error: error instanceof Error ? error.message : String(error), modelId: model.id, providerId: provider.id, }); return openAiError("The configured Agent model is temporarily unavailable.", 502); } }