shadimage
Assistant UI
pageai-sdk-backend-resumablesource: ok · hit

AI SDK Resumable Backend

AI SDK route handlers with resumable stream context, so a run survives a reload.

npx shadcn add "https://r.assistant-ui.com/ai-sdk-backend-resumable.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
frameworknext

Files3 · source cached 10/8/2026, 7:40:04 PM

app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { frontendTools } from "@assistant-ui/ai-sdk";
import {
  streamText,
  convertToModelMessages,
  type UIMessage,
  type JSONSchema7,
  UI_MESSAGE_STREAM_HEADERS,
} from "ai";
import { RESUMABLE_STREAM_ID_HEADER } from "assistant-stream/resumable";
import { resumableContext } from "@/lib/resumable-context";

export const maxDuration = 60;

export async function POST(req: Request) {
  const {
    messages,
    system,
    tools,
  }: {
    messages: UIMessage[];
    system?: string;
    tools?: Record<string, { description?: string; parameters: JSONSchema7 }>;
  } = await req.json();

  const streamId = crypto.randomUUID();

  const result = streamText({
    model: openai("gpt-6-luna"),
    messages: await convertToModelMessages(messages),
    tools: {
      ...frontendTools(tools ?? {}),
    },
    ...(system === undefined ? {} : { system }),
  });

  const body = result.toUIMessageStreamResponse().body;
  if (!body) {
    throw new Error("UI message stream response has no body");
  }
  const stream = await resumableContext.run(streamId, () => body);

  return new Response(stream, {
    headers: {
      ...UI_MESSAGE_STREAM_HEADERS,
      [RESUMABLE_STREAM_ID_HEADER]: streamId,
    },
  });
}
app/api/chat/resume/[streamId]/route.ts
import { UI_MESSAGE_STREAM_HEADERS } from "ai";
import { RESUMABLE_STREAM_ID_HEADER } from "assistant-stream/resumable";
import { resumableContext } from "@/lib/resumable-context";

export const maxDuration = 60;

// Resume access is authenticated only by stream id; scope it to the signed-in user in production (see /docs/guides/resumable-stream-deployment).
export async function GET(
  _req: Request,
  context: { params: Promise<{ streamId: string }> },
) {
  const { streamId } = await context.params;
  const stream = await resumableContext.resume(streamId);

  if (!stream) {
    return new Response(JSON.stringify({ error: "stream not found" }), {
      status: 404,
      headers: { "Content-Type": "application/json" },
    });
  }

  return new Response(stream, {
    headers: {
      ...UI_MESSAGE_STREAM_HEADERS,
      [RESUMABLE_STREAM_ID_HEADER]: streamId,
    },
  });
}
lib/resumable-context.ts
import {
  createInMemoryResumableStreamStore,
  createResumableStreamContext,
} from "assistant-stream/resumable";
import { after } from "next/server";

const store = createInMemoryResumableStreamStore();
export const resumableContext = createResumableStreamContext({
  store,
  waitUntil: (promise) => after(promise),
});

More from Assistant UI

Browse all components in this library →