S2 integrates with the Anthropic Messages API through theDocumentation 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-dev/resumable-stream package. The integration persists Anthropic stream
events to S2 and replays them as Anthropic-style SSE.
Prerequisites
- Sign up here, generate an access token, and set
it as
S2_ACCESS_TOKEN. - Create a basin with Create Stream on Append and Create Stream on Read
enabled, and set it as
S2_BASIN. - Set
ANTHROPIC_API_KEYfor the server route that calls Anthropic.
Import paths
The integration has separate server and client entrypoints.Setup
Create one chat helper and share it across routes:lib/s2.ts
chat is this S2 helper. anthropic is the
Anthropic SDK client created in the POST route.
Use one stable S2 stream name per chat:
lib/stream-name.ts
Server: POST route
makeResumable persists each yielded event. In session mode, yield a
user_message event before the Anthropic stream when S2 should store both
sides of the chat.
app/api/chat/route.ts
delivery: 'replay' makes the POST return 202. Clients read chunks from the
replay route, so every tab can consume the same S2 records.
If your runtime can terminate work after a response returns, pass its
background-task hook as waitUntil.
Server: replay
The replay route reads from S2 and returns Anthropic-style SSE frames.app/api/chat/stream/route.ts
live: true in session mode when a tab should stay subscribed after the
current assistant turn ends.
Server: history
The history route returns a JSON snapshot derived from S2.app/api/chat/history/route.ts
turns for ordered chat history, then replay from nextSeqNum to read
anything still in progress.
Client
Pass the send, history, and replay endpoints to the Anthropic client helper.app/chat-client.ts
stopOnTerminal: false so message_stop
ends the current assistant turn without closing the subscription.
client.send(...) can start a
turn and return a per-send subscription.
User messages
The Anthropic helper does not infer user messages. Store them by yielding auser_message event before the Anthropic stream if S2 should be the chat log.
user_message only when another store owns user messages, or when you do
not want user text stored in S2. In that setup, the app must read user messages
from the other store when rendering history and building the next Anthropic
messages array.
Flow
- The browser loads
chat.history(...)and sets its cursor tonextSeqNum. - The browser opens
chat.replay(..., { live: true }). - The browser sends
POST /api/chat. - The server yields
user_message, then Anthropic stream events. makeResumableappends those records to S2.- Replay delivers the same records to subscribed tabs.
- Reconnects continue from the last SSE
id.
Modes
mode controls how generations map to S2 streams.
| mode | use when | refresh behavior |
|---|---|---|
single-use | Each assistant turn has its own stream name. | Replays only the active turn. |
shared | One stream name should hold the active generation only. | Replays the active generation after lease/fence coordination. |
session | You are building a full chat session. | Replays history and tails later turns from one durable log. |
delivery controls how the request that starts generation returns chunks.
| delivery | use when | response behavior |
|---|---|---|
response | You want the simplest one-tab flow. | The POST response streams the assistant output directly. |
replay | You want refresh recovery, session history, or multi-tab sync. | The POST returns 202; clients read from replay. |
mode: 'session',
delivery: 'replay', and replay(..., { live: true }).
Configuration
Relevant options oncreateResumableChat:
| option | default | what it controls |
|---|---|---|
mode | "single-use" | "single-use" uses one stream per generation. "shared" reuses one active-generation stream. "session" stores a durable chat session log. |
endpoints | S2 defaults | Optional endpoint overrides, for example when using S2 Lite. |
leaseDurationMs | 5000 | Only for shared mode. Max pause within an active generation before a new claim can take it over. |
onError | generic message | Maps upstream errors to the stored error event shown to clients. |
batchSize / lingerDuration | 10 / 50ms | S2 append batching knobs. |
makeResumable:
| option | default | what it controls |
|---|---|---|
delivery | "response" | "response" streams on the POST response. "replay" returns 202 and expects clients to read from replay. |
waitUntil | none | Keeps persistence running after the response returns in runtimes that support background tasks. |
replay:
| option | what it controls |
|---|---|
fromSeqNum | S2 cursor to resume from. Use history.nextSeqNum after loading history. |
live | Session mode only. Keeps the SSE open at the tail for future turns. |
createChatClient:
| option | what it controls |
|---|---|
sendUrl | POST endpoint that starts generation. |
subscribeUrl | GET endpoint that returns replay SSE. String URLs get ?from= added automatically; function URLs receive the cursor. |
historyUrl | Optional URL returning { turns, messages, nextSeqNum }. |
stopUrl | Optional URL called by stop() with DELETE. |
headers / credentials | Auth and fetch options for send, stop, history, and subscribe requests. |
reconnectBackoffMs | Millisecond backoff schedule for reconnects. Pass [] to disable reconnect. |
fetch | Custom fetch implementation for tests or framework integrations. |
send and subscribe:
| option | default | what it controls |
|---|---|---|
signal | none | Abort signal for the request or subscription. |
stopOnTerminal | true | Set false for live session streams so message_stop does not close the listener. |
Example
A runnable Bun server + vanilla-JS client with session replay and live sync is available in the SDK repo:examples/anthropic-resumable-chat.
