BrandApp Playground

Text Completion

Generate text with streaming (SSE) and non-streaming modes.

app/api/ai/chat/route.ts
import { getAi } from "@/lib/ai";
import { serializeSSE } from "@/lib/sse";
import { NextRequest } from "next/server";

export async function POST(request: NextRequest) {
  const { prompt, stream } = await request.json();
  const ai = await getAi();

  if (stream) {
    // Streaming with SSE — streamWithMetadata() returns synchronously with a
    // string-chunk async iterable plus a metadata promise (usage + credits).
    const { textStream, metadata } = ai.streamWithMetadata({
      // Omit `model` to use the BrandApp's configured default,
      // or pass a real id, e.g. "anthropic/claude-haiku-4.5".
      messages: [{ role: "user", content: prompt }],
    });

    const encoder = new TextEncoder();
    const readable = new ReadableStream({
      async start(controller) {
        // Each chunk is a plain string text delta.
        for await (const chunk of textStream) {
          controller.enqueue(encoder.encode(serializeSSE("text-delta", chunk)));
        }
        // metadata = { usage: { promptTokens, completionTokens, creditCost },
        //              creditsRemaining, finishReason? } | null
        const meta = await metadata;
        if (meta) {
          controller.enqueue(encoder.encode(serializeSSE("done", meta)));
        }
        controller.close();
      },
    });

    return new Response(readable, {
      headers: { "Content-Type": "text/event-stream" },
    });
  }

  // Non-streaming — complete() returns the full CompletionResult.
  const result = await ai.complete({
    messages: [{ role: "user", content: prompt }],
  });

  return Response.json({
    content: result.message.content,
    usage: result.usage,
    creditsRemaining: result.creditsRemaining,
  });
}