TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { z } from 'zod';2import { NextResponse } from 'next/server';3import { researchTurn, listSessions } from '@/lib/ai/research';4import { clientIpHash, consumeQuota, currentUserId, getAnonId } from '@/lib/ai/request';56export const runtime = 'nodejs';7export const maxDuration = 300;89const Body = z.object({ sessionId: z.string().regex(/^rs_[a-z0-9]+$/).nullable().optional(), message: z.string().min(1).max(4000) });1011/** POST → Server-Sent Events stream of ResearchEvent JSON lines. */12export async function POST(req: Request) {13 const parsed = Body.safeParse(await req.json().catch(() => null));14 if (!parsed.success) return NextResponse.json({ error: 'Invalid request' }, { status: 400 });15 const [userId, ipHash, anonId] = await Promise.all([currentUserId(), clientIpHash(), getAnonId()]);16 const quota = await consumeQuota('research', { userId, ipHash });17 if (!quota.ok) return NextResponse.json({ error: `Daily research limit reached (${quota.limit}). Sign in for a higher limit.` }, { status: 429 });1819 const encoder = new TextEncoder();20 const stream = new ReadableStream<Uint8Array>({21 async start(controller) {22 const send = (e: unknown) => controller.enqueue(encoder.encode(`data: ${JSON.stringify(e)}\n\n`));23 try {24 for await (const ev of researchTurn({ sessionId: parsed.data.sessionId ?? null, anonId, userId, message: parsed.data.message, signal: req.signal })) send(ev);25 } catch (err) {26 send({ type: 'error', message: err instanceof Error ? err.message : String(err) });27 } finally {28 controller.enqueue(encoder.encode('data: [DONE]\n\n'));29 controller.close();30 }31 },32 });33 return new Response(stream, { headers: { 'content-type': 'text/event-stream; charset=utf-8', 'cache-control': 'no-cache, no-transform', connection: 'keep-alive', 'x-accel-buffering': 'no' } });34}3536export async function GET() {37 const [userId, anonId] = await Promise.all([currentUserId(), getAnonId(false)]);38 const sessions = await listSessions({ anonId, userId });39 return NextResponse.json({ sessions: sessions.map((s) => ({ id: s.id, title: s.title, messageCount: s.messageCount, updatedAt: s.updatedAt })) });40}41