SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
2.3 KB · 48 lines typescript
Raw Blame History
1import { withUser, parseBody } from "@/lib/api";2import { LIMITS } from "@/lib/rate-limit";3import { chatRequestSchema } from "@/lib/chat/schemas";4import { prepareTurn, runTurn, toPublicMessage } from "@/lib/chat/service";5import { sseResponse } from "@/lib/chat/sse";67export const dynamic = "force-dynamic";8export const maxDuration = 900;910/**11 * POST /api/chat — streams a model turn as Server-Sent Events.12 * Events: meta → (text-delta | reasoning-delta | tool-* | citation | server-tool)* → done | error13 *14 * `meta` carries `ephemeral` (temporary chat: conversationId is the "ephemeral" sentinel and nothing15 * is persisted except the usage record) and `requestId` (shown in the error details sheet).16 */17export const POST = withUser(18  async (ctx) => {19    const input = await parseBody(ctx.req, chatRequestSchema, 2_500_000);20    const turn = await prepareTurn({ userId: ctx.user.id, requestId: ctx.requestId, ip: ctx.ip }, input);21    return sseResponse(22      async (emit, signal) => {23        // Abort provider work when the client disconnects.24        const onAbort = () => controller.abort();25        const controller = new AbortController();26        signal.addEventListener("abort", onAbort, { once: true });27        ctx.req.signal?.addEventListener("abort", onAbort, { once: true });28        emit({29          type: "meta",30          conversationId: turn.conversation.id,31          isNewConversation: turn.isNewConversation,32          userMessage: turn.userMessage ? toPublicMessage(turn.userMessage) : null,33          assistantMessageId: turn.assistantMessage.id,34          modelKey: turn.model.key,35          clientId: input.clientId,36          ephemeral: turn.ephemeral,37          requestId: ctx.requestId,38        });39        const outcome = await runTurn({ userId: ctx.user.id, requestId: ctx.requestId, ip: ctx.ip }, turn, emit, controller.signal);40        if (outcome.error && !outcome.message.content) emit({ type: "error", error: outcome.error });41        emit({ type: "done", message: toPublicMessage(outcome.message), usage: outcome.usage, costUsd: outcome.costUsd, latencyMs: outcome.latencyMs, ttftMs: outcome.ttftMs, status: outcome.status, title: outcome.title, error: outcome.error });42      },43      { requestId: ctx.requestId },44    );45  },46  { limit: { ...LIMITS.chat, key: "chat" } },47);48