import { NextRequest, NextResponse } from 'next/server'; import { getStalwartCredentials } from '@/lib/stalwart/credentials'; export const runtime = 'nodejs'; /** * GET /api/ai/server/models — list models on the centrally-hosted `server` * class runtime (docs/AI-ASSISTANT-CONCEPT.md §2.1: "the same self-hosted * open-weight model stack as `local`... running on VNC's own infrastructure * instead of the user's laptop"). Tonight, `AI_SERVER_BASE_URL` stands in for * that infra with the Ollama already running on this developer's Mac — see * the module comment in lib/ai/entitlement.ts. Swapping to the real * EU/CH-hosted instance tomorrow is a config change, not a rewrite. * * Listing models is not a billable action (doc §10 point 1 — cosmetic), so * this only requires a valid session, not a seat. */ export async function GET(request: NextRequest) { const auth = await getStalwartCredentials(request); if (!auth) { return NextResponse.json({ error: 'not authenticated' }, { status: 401 }); } const baseUrl = process.env.AI_SERVER_BASE_URL; if (!baseUrl) { return NextResponse.json({ error: 'AI server class is not configured' }, { status: 503 }); } try { const res = await fetch(`${baseUrl.replace(/\/+$/, '')}/api/tags`); if (!res.ok) { return NextResponse.json({ error: `upstream returned ${res.status}` }, { status: 502 }); } const body = (await res.json()) as { models?: Array<{ name: string }> }; return NextResponse.json({ models: (body.models ?? []).map((m) => m.name).filter(Boolean) }); } catch (cause) { return NextResponse.json( { error: cause instanceof Error ? cause.message : 'AI server unreachable' }, { status: 502 }, ); } }