> ## 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.

# Anthropic

> Persist Anthropic Messages API streams on S2 and replay them after refreshes or reconnects.

S2 integrates with the Anthropic Messages API through the
`@s2-dev/resumable-stream/anthropic` entrypoint.

The integration includes two helpers:

* The `createResumableChat` server helper stores raw Anthropic stream events in S2 streams and replays them as
  Anthropic-shaped SSE.
* The `subscribe` browser helper is a small async generator that reads the replay endpoint.

```bash theme={null}
export S2_ACCESS_TOKEN="..."
export S2_BASIN="my-basin"
export ANTHROPIC_API_KEY="..."
```

## Server Setup

```ts lib/s2.ts theme={null}
import { createResumableChat } from '@s2-dev/resumable-stream/anthropic';

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

`mode: 'session'` can be useful when you want your chat app to have one S2
stream to hold a long-lived log for a chat. Use `single-use` if each response
needs its own stream.

## Start A Turn

```ts app/api/chat/route.ts theme={null}
import Anthropic from '@anthropic-ai/sdk';
import type { MessageParam } from '@anthropic-ai/sdk/resources/messages';
import { chat } from '@/lib/s2';

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY!,
});

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

  const source = anthropic.messages.stream({
    model: process.env.ANTHROPIC_MODEL ?? 'claude-haiku-4-5-20251001',
    max_tokens: 1024,
    messages,
  });

  return chat.makeResumable(`chat-${id}`, source, {
    delivery: 'replay',
    waitUntil: (p) => p.catch(console.error),
  });
}
```

`delivery: 'replay'` makes the POST return `202`. The client reads the actual
events from the replay route.

## Replay Route

```ts app/api/chat/stream/route.ts theme={null}
import { chat } from '@/lib/s2';

function parseFromSeqNum(value: string | null): number | undefined {
  if (value === null) return undefined;
  const parsed = Number.parseInt(value, 10);
  return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : undefined;
}

export async function GET(req: Request) {
  const url = new URL(req.url);
  const id = url.searchParams.get('id');
  if (!id) return new Response('Missing id query parameter', { status: 400 });

  return chat.replay(`chat-${id}`, {
    fromSeqNum: parseFromSeqNum(url.searchParams.get('from')),
    live: url.searchParams.get('live') === '1',
  });
}
```

Use `live: true` in session mode when the browser should stay connected at the
tail and receive future turns.

## Browser Subscription

```ts theme={null}
import { subscribe } from '@s2-dev/resumable-stream/anthropic/client';

await fetch('/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: chatId, messages }),
});

for await (const event of subscribe({
  url: (cursor) => {
    const params = new URLSearchParams({ id: chatId, live: '1' });
    if (cursor !== undefined) params.set('from', String(cursor));
    return `/api/chat/stream?${params}`;
  },
})) {
  switch (event.type) {
    case 'message_start':
      break;
    case 'content_block_delta':
      break;
    case 'message_stop':
      break;
  }
}
```

`subscribe` yields Anthropic `RawMessageStreamEvent` values, tracks the replay
cursor from SSE `id:` values, and reconnects from that cursor if the response
drops. It can also yield the adapter's error envelope:

```ts theme={null}
{ type: 'error', error: { type: string, message: string } }
```

## Completed History

The Anthropic helper does not infer user messages and does not build a
transcript for you. You can pair the replay stream with a separate history
store:

1. Append the user message to history before starting the model.
2. Fold Anthropic stream events into a completed assistant message.
3. Append the assistant message when `message_stop` arrives.
4. On page load, render history immediately, then subscribe to replay for any
   active turn.

## Options

`createResumableChat` accepts:

| option            | default         | description                                                                                                                                           |
| ----------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`            | `"single-use"`  | `"single-use"` uses one stream per generation. `"shared"` reuses one active-generation stream. `"session"` appends generations to one durable stream. |
| `endpoints`       | S2 defaults     | Optional endpoint overrides, commonly used with S2 Lite.                                                                                              |
| `batchSize`       | `10`            | Maximum number of events per append batch.                                                                                                            |
| `lingerDuration`  | `50`            | Maximum batching delay in milliseconds.                                                                                                               |
| `leaseDurationMs` | `5000`          | `shared` mode takeover window for stale active generations.                                                                                           |
| `onError`         | generic message | Maps upstream errors to an Anthropic-shaped `error` event.                                                                                            |

`replay` accepts:

| option       | description                                                             |
| ------------ | ----------------------------------------------------------------------- |
| `fromSeqNum` | S2 cursor to resume from. The client tracks this from SSE `id:` values. |
| `live`       | Session mode only. Keeps the SSE open at the tail for future records.   |

`subscribe` accepts:

| option               | description                                                                                           |
| -------------------- | ----------------------------------------------------------------------------------------------------- |
| `url`                | Replay URL. String URLs get `?from=<cursor>` appended on reconnect; function URLs receive the cursor. |
| `signal`             | Optional abort signal.                                                                                |
| `fetch`              | Optional fetch implementation override.                                                               |
| `headers`            | Static or lazy headers sent on every request.                                                         |
| `credentials`        | Fetch credentials mode. Defaults to `same-origin`.                                                    |
| `reconnectBackoffMs` | Millisecond backoff schedule. Pass `[]` to disable reconnect.                                         |

## Example

A complete Bun server and browser client is available here:
[`examples/anthropic-resumable-chat`](https://github.com/s2-streamstore/s2-sdk-typescript/tree/main/examples/anthropic-resumable-chat).
