SSE Stream

Docs

Server-Sent Events are a structured one-way stream from your server to the browser. This demo receives named events such as chunk and done through EventSource, then uses the done event to finish cleanly. Use SSE for LLM output, progress updates, and live feeds. For simpler use cases where you only need raw text chunks, see Text Stream.

Prompt
What are AI agents and how do they work?
Anthropic/Claude Opus 4.8
Ready
Output will appear here as tokens stream in...
Reference Code
import { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { AIGatewayClient } from "@agentuity/aigateway";
import { getAIGatewayStreamDeltaText } from "@agentuity/aigateway";

const app = new Hono();
const gateway = new AIGatewayClient();
// Default model in the live demo; any gateway model id works here.
const MODEL = "anthropic/claude-opus-4-8";

app.get("/api/sse-stream", (c) => {
  return streamSSE(c, async (stream) => {
    const result = await gateway.streamRequest({
      path: "/",
      body: {
        model: MODEL,
        stream: true,
        messages: [
          {
            role: "user",
            content: "What are AI agents and how do they work?",
          },
        ],
      },
    });

    let id = 0;
    for await (const chunk of readGatewayText(result.stream)) {
      await stream.writeSSE({
        event: "chunk",
        data: chunk,
        id: String(id++),
      });
    }

    const metadata = await result.metadata;
    const totalTokens =
      (metadata.cost?.promptTokens ?? 0) +
      (metadata.cost?.completionTokens ?? 0);

    await stream.writeSSE({
      event: "done",
      data: JSON.stringify({ totalTokens }),
      id: String(id),
    });
  });
});

// streamRequest() returns the provider's raw SSE bytes. Split frames on
// blank lines; the SDK decodes each provider's delta shape to plain text.
async function* readGatewayText(stream: ReadableStream<Uint8Array>) {
  const reader = stream.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  try {
    for (;;) {
      const { value, done } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });
      const frames = buffer.split(/\r?\n\r?\n/);
      buffer = frames.pop() ?? "";

      for (const frame of frames) {
        const text = frameText(frame);
        if (text) yield text;
      }
    }

    buffer += decoder.decode();
    const text = frameText(buffer);
    if (text) yield text;
  } finally {
    reader.releaseLock();
  }
}

function frameText(frame: string): string {
  const data = frame
    .split(/\r?\n/)
    .filter((line) => line.startsWith("data:"))
    .map((line) => line.slice(5).trimStart())
    .join("\n")
    .trim();

  if (!data || data === "[DONE]") return "";

  try {
    return getAIGatewayStreamDeltaText(JSON.parse(data));
  } catch {
    return "";
  }
}

export default app;
Ready
Output will appear here...