Skip to main content

Documentation Index

Fetch the complete documentation index at: https://s2.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

S2 integrates with the AI SDK through the @s2-dev/resumable-stream/aisdk entrypoint. The package stores AI SDK UIMessageChunk events in S2 and replays them with the same SSE format that the AI SDK transport expects.

Install

npm install @s2-dev/resumable-stream ai @ai-sdk/react @ai-sdk/openai
This integration requires ai >= 5.0; older AI SDK releases do not export UIMessageChunk. Create a basin with Create Stream on Append enabled. If your app reads history streams before writing to them, also enable Create Stream on Read.
export S2_ACCESS_TOKEN="..."
export S2_BASIN="my-basin"
export OPENAI_API_KEY="..."

Server Setup

Create the S2 chat helper once and reuse it in your routes.
lib/s2.ts
import { createResumableChat } from '@s2-dev/resumable-stream/aisdk';

export const chat = createResumableChat({
  accessToken: process.env.S2_ACCESS_TOKEN!,
  basin: process.env.S2_BASIN!,
});

Start A Turn

The POST route starts the model call, writes chunks to S2, and streams the same chunks back to the AI SDK client.
app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { convertToModelMessages, streamText, type UIMessage } from 'ai';
import { after } from 'next/server';
import { chat } from '@/lib/s2';

export async function POST(req: Request) {
  const { id, messages } = (await req.json()) as {
    id: string;
    messages: UIMessage[];
  };

  const result = streamText({
    model: openai('gpt-4o-mini'),
    messages: await convertToModelMessages(messages),
  });

  return chat.makeResumable(`chat-${id}`, result.toUIMessageStream(), {
    waitUntil: (p) => after(async () => { await p; }),
  });
}

Reconnect Route

resume: true in useChat calls the reconnect route on mount. With the default AI SDK transport, that route is ${api}/${chatId}/stream.
app/api/chat/[id]/stream/route.ts
import { chat } from '@/lib/s2';

export async function GET(
  _req: Request,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;
  return chat.replay(`chat-${id}`);
}
If the turn is still active, S2 replays the remaining chunks. If there is no active turn, the route returns 204.

Client

Use the AI SDK useChat React hook to create a resumable chat.
app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const transport = new DefaultChatTransport({ api: '/api/chat' });

export default function Chat() {
  const { messages, sendMessage, status } = useChat({
    id: 'my-chat-id',
    transport,
    resume: true,
  });

  // Render messages and call sendMessage(...)
  return null;
}

Completed History

@s2-dev/resumable-stream/aisdk stores the streaming chunks for resumability. It does not decide where your completed transcript lives. For a chat app, keep a transcript stream or database table in app code:
  1. Load completed messages before rendering the page.
  2. Pass them to useChat as initial state if your UI needs refresh-after-done history.
  3. Use chat.makeResumable(...) for the active assistant turn.
The runnable example stores completed messages in a separate S2 history stream and uses a live stream for the active generation.

Options

createResumableChat accepts the shared resumable-stream options:
optiondefaultdescription
mode"single-use""single-use" uses one stream per generation. "shared" reuses one active-generation stream. "session" appends generations to one durable stream.
endpointsS2 defaultsOptional endpoint overrides, commonly used with S2 Lite.
batchSize10Maximum number of chunks per append batch.
lingerDuration50Maximum batching delay in milliseconds.
leaseDurationMs5000shared mode takeover window for stale active generations.
onErrorgeneric messageMaps upstream errors to the stored AI SDK error chunk.
makeResumable accepts:
optiondefaultdescription
delivery"response""response" streams chunks on the POST response. "replay" returns 202 and expects the client to read from replay.
waitUntilnoneKeeps persistence alive after the HTTP response returns. Use after, Cloudflare waitUntil, or your server’s background-task hook.

Example

A complete Bun server and browser client is available here: examples/ai-sdk-resumable-chat. The TypeScript SDK repo also includes direct S2 examples for: