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,
});
}