npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ai-sdk-reconnect

v0.3.0

Published

Cursor-aware reconnecting chat transport for Vercel AI SDK

Downloads

318

Readme

ai-sdk-reconnect

Automatic cursor-based reconnects for Vercel AI SDK chat streams.

ReconnectingChatTransport is a drop-in replacement for DefaultChatTransport. It keeps the same logical UIMessageChunk stream alive across temporary HTTP or network disconnects, without replaying chunks the client has already processed.

Requirements

  • AI SDK 7
  • Node.js 22 or newer
  • ESM imports; CommonJS require() is not supported
  • A backend that keeps generation running after a client disconnects and can replay the active stream by cursor

Install

npm install ai-sdk-reconnect ai

Client setup

import { useChat } from "@ai-sdk/react";
import { ReconnectingChatTransport } from "ai-sdk-reconnect";

const transport = new ReconnectingChatTransport({
  api: "/api/chat",
  credentials: "include",
});

function Chat({ chatId }: { chatId: string }) {
  const chat = useChat({
    id: chatId,
    resume: true,
    transport,
  });

  return <div>{chat.messages.length} messages</div>;
}

The transport accepts the same HTTP options as DefaultChatTransport, including api, body, credentials, fetch, headers, and prepareSendMessagesRequest.

Message submissions use POST /api/chat. Reconnects use GET /api/chat/:chatId/stream by default. To use another endpoint:

const transport = new ReconnectingChatTransport({
  api: "/api/chat",
  prepareReconnectToStreamRequest({ id }) {
    return {
      api: `/api/chat/${encodeURIComponent(id)}/stream`,
    };
  },
});

The callback also receives cursor, source, request metadata, headers, credentials, and body. source is "send" for a stream created by a message submission and "resume" for a stream opened by resumeStream(). The transport manages Last-Event-ID; callers do not need to set it.

Backend contract

The reconnect endpoint must provide a durable logical stream:

  1. Generation continues when an HTTP client disconnects.
  2. Every SSE event containing an AI SDK JSON chunk has an opaque id. The ID remains stable when the event is replayed. The standard id-less data: [DONE] marker is allowed.
  3. A request without Last-Event-ID replays the stream from its initial start event.
  4. A request with Last-Event-ID returns only the events after that cursor, in their original order.
  5. The endpoint returns 204 when no active stream exists.
  6. Terminal finish, abort, or error events are retained long enough for disconnected clients to read them.
  7. Response caching and proxy buffering are disabled.

Example:

id: cursor-1
data: {"type":"start","messageId":"assistant-1"}

id: cursor-2
data: {"type":"text-start","id":"text-1"}

id: cursor-3
data: {"type":"text-delta","id":"text-1","delta":"Hello"}

Page reloads

When using resume: true, do not return an unfinished assistant message in the initial chat history. Keep its chunks in the durable stream and replay that stream from start; persist the completed assistant message after finish.

The history response and stream lookup should refer to the same backend run or snapshot. If a run finishes between those requests, replay it when the loaded history did not include it, and return 204 when that completed message was already included.

Reconnect status parts

prepareReconnectDataPart can expose reconnect state as a typed AI SDK data part. When the callback is absent or returns undefined, no part is added.

import type { UIMessage } from "ai";
import {
  ReconnectingChatTransport,
  type ReconnectEvent,
} from "ai-sdk-reconnect";

type ChatMessage = UIMessage<
  unknown,
  {
    reconnect: Pick<
      ReconnectEvent,
      "attempt" | "attemptId" | "maxAttempts" | "state"
    >;
  }
>;

const transport = new ReconnectingChatTransport<ChatMessage>({
  prepareReconnectDataPart(event) {
    return {
      type: "data-reconnect",
      id: "reconnect-status",
      data: {
        attempt: event.attempt,
        attemptId: event.attemptId,
        maxAttempts: event.maxAttempts,
        state: event.state,
      },
    };
  },
});

ReconnectEvent contains:

  • state: "reconnecting", "reconnected", or "failed"
  • attempt: the one-based physical attempt number
  • attemptId: a unique ID shared by an attempt's reconnecting and terminal event
  • maxAttempts: the current maximum, or null for unlimited retries
  • source: "send" or "resume"

Using a constant part id makes AI SDK update one status part. Using attemptId as the part ID keeps one part per attempt. Reconnect parts are regular, non-transient message parts and may be rendered, persisted, and sent to the backend like other data parts.

An attempt emits "failed" before the next attempt starts or before its error is surfaced. Cancellation stops the reconnect lifecycle without emitting a terminal reconnect event, so also use the chat status when rendering state.

Retry options

const transport = new ReconnectingChatTransport({
  reconnect: {
    initialDelayMs: 500,
    jitter: 0.2,
    maxDelayMs: 10_000,
    maxRetries: 8,
    multiplier: 2,
    visibilityReconnectAfterMs: 30_000,
  },
});

| Option | Default | Description | | --- | ---: | --- | | initialDelayMs | 1000 | Delay before the first retry | | maxDelayMs | 30000 | Maximum retry delay | | maxRetries | Infinity | Retry limit for consecutive failures | | multiplier | 2 | Exponential backoff multiplier | | jitter | 0.2 | Random delay variation from 0 to 1 | | visibilityReconnectAfterMs | 30000 | Replace a stale connection after returning to the page; use false to disable |

The transport waits while the browser is offline without consuming retry budget. It never resubmits the initial POST: when the outcome of that request is unknown, recovery uses the reconnect GET endpoint.

An initial resumeStream() call made while offline waits until the browser is online. chat.stop() cannot cancel this initial wait.

Cancellation

For streams started by sendMessage(), chat.stop() immediately stops the local stream and reconnect loop. It does not stop the durable backend job.

Use a separate backend cancel endpoint to:

  1. Stop the targeted generation.
  2. Store an SSE abort event with an event ID.
  3. Close the active stream.

For a stream opened through resumeStream(), chat.stop() does not stop the local resumed stream. The server-side cancel and durable abort event are required to return the resumed client to ready.

Send the current backend run or stream ID to the cancel endpoint. The server should ignore the request if the chat already points to a newer run.

Errors

The package exports:

  • ChatStreamHttpError: a non-successful HTTP response
  • MissingEventIdError: an AI SDK data event did not have a cursor
  • ReconnectRetriesExhaustedError: the configured retry limit was reached; its attempts property contains the number of reconnect requests
  • ResumableStreamUnavailableError: the active stream disappeared before a terminal event

An explicit error response to the initial message POST is surfaced without resubmitting the message.

License

Apache License 2.0