---
title: WorkflowChatTransport
description: Chat transport with automatic reconnection and recovery from interrupted streams.
type: reference
summary: Use WorkflowChatTransport as a drop-in AI SDK transport for automatic stream reconnection.
prerequisites:
  - /docs/ai
related:
  - /docs/ai/resumable-streams
---

# WorkflowChatTransport



<Callout type="warn">
  The `@workflow/ai` package is currently in active development and should be considered experimental.
</Callout>

A chat transport implementation for the AI SDK that provides reliable message streaming with automatic reconnection to interrupted streams. This transport is a drop-in replacement for the default AI SDK transport, enabling seamless recovery from network issues, page refreshes, or Vercel Function timeouts.

<Callout>
  `WorkflowChatTransport` implements the [`ChatTransport`](https://ai-sdk.dev/docs/ai-sdk-ui/transport) interface from the AI SDK and is designed to work with workflow-based chat applications. It requires endpoints that return the `x-workflow-run-id` header to enable stream resumption.
</Callout>

```typescript lineNumbers
import { useChat } from "@ai-sdk/react";
import { WorkflowChatTransport } from "@workflow/ai";

export default function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new WorkflowChatTransport(),
  });

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>{m.content}</div>
      ))}
    </div>
  );
}
```

## API Signature

### Class

<TSDoc
  definition={`
import { WorkflowChatTransport } from "@workflow/ai";
export default WorkflowChatTransport;`}
/>

### WorkflowChatTransportOptions

<TSDoc
  definition={`
import type { WorkflowChatTransportOptions } from "@workflow/ai";
export default WorkflowChatTransportOptions;`}
/>

## Key Features

* **Automatic Reconnection**: Automatically recovers from interrupted streams with configurable retry limits
* **Workflow Integration**: Seamlessly works with workflow-based endpoints that provide the `x-workflow-run-id` header
* **Customizable Requests**: Allows intercepting and modifying requests via `prepareSendMessagesRequest` and `prepareReconnectToStreamRequest`
* **Stream Callbacks**: Provides hooks for tracking chat lifecycle via `onChatSendMessage` and `onChatEnd`
* **Custom Fetch**: Supports custom fetch implementations for advanced use cases

## Good to Know

* The transport expects chat endpoints to return the `x-workflow-run-id` header in the response to enable stream resumption
* By default, the transport posts to `/api/chat` and reconnects via `/api/chat/{runId}/stream`
* The `onChatSendMessage` callback receives the full response object, allowing you to extract and store the workflow run ID for session resumption
* Stream interruptions are automatically detected when a "finish" chunk is not received in the initial response
* The `maxConsecutiveErrors` option controls how many reconnection attempts are made before giving up (default: 3)
* `initialStartIndex` (constructor option) sets the default chunk position for the **first** reconnection attempt (e.g. after a page refresh). Subsequent retries within the same reconnection loop always resume from the last received chunk. Negative values (e.g. `-20`) read from the end of the stream, which is useful for showing only recent output without replaying the full conversation. `startIndex` (per-call option on `reconnectToStream`) overrides `initialStartIndex` for a single reconnection
* When using a negative `initialStartIndex`, the reconnection endpoint must return the `x-workflow-stream-tail-index` response header (via `readable.getTailIndex()`). The transport reads this header to compute absolute chunk positions for retries. Without it, startIndex is assumed to be 0, replaying the entire stream

## Examples

### Basic Chat Setup

```typescript
"use client";

import { useChat } from "@ai-sdk/react";
import { WorkflowChatTransport } from "@workflow/ai";
import { useState } from "react";

export default function BasicChat() {
  const [input, setInput] = useState("");
  const { messages, sendMessage } = useChat({
    transport: new WorkflowChatTransport(),
  });

  return (
    <div>
      <div className="space-y-4">
        {messages.map((m) => (
          <div key={m.id}>
            <strong>{m.role}:</strong> {m.content}
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput("");
        }}
      >
        <input
          value={input}
          placeholder="Say something..."
          onChange={(e) => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

### With Session Persistence and Resumption

```typescript
"use client";

import { useChat } from "@ai-sdk/react";
import { WorkflowChatTransport } from "@workflow/ai";
import { useMemo, useState } from "react";

export default function ChatWithResumption() {
  const [input, setInput] = useState("");
  const activeWorkflowRunId = useMemo(() => {
    if (typeof window === "undefined") return;
    return localStorage.getItem("active-workflow-run-id") ?? undefined;
  }, []);

  const { messages, sendMessage } = useChat({
    resume: !!activeWorkflowRunId,
    transport: new WorkflowChatTransport({
      onChatSendMessage: (response, options) => {
        // Save chat history to localStorage
        localStorage.setItem(
          "chat-history",
          JSON.stringify(options.messages)
        );

        // Extract and store the workflow run ID for session resumption
        const workflowRunId = response.headers.get("x-workflow-run-id");
        if (workflowRunId) {
          localStorage.setItem("active-workflow-run-id", workflowRunId);
        }
      },
      onChatEnd: ({ chatId, chunkIndex }) => {
        console.log(`Chat ${chatId} completed with ${chunkIndex} chunks`);
        // Clear the active run ID when chat completes
        localStorage.removeItem("active-workflow-run-id");
      },
    }),
  });

  return (
    <div>
      <div className="space-y-4">
        {messages.map((m) => (
          <div key={m.id}>
            <strong>{m.role}:</strong> {m.content}
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput("");
        }}
      >
        <input
          value={input}
          placeholder="Say something..."
          onChange={(e) => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

### With Custom Request Configuration

```typescript
"use client";

import { useChat } from "@ai-sdk/react";
import { WorkflowChatTransport } from "@workflow/ai";
import { useState } from "react";

export default function ChatWithCustomConfig() {
  const [input, setInput] = useState("");
  const { messages, sendMessage } = useChat({
    transport: new WorkflowChatTransport({
      prepareSendMessagesRequest: async (config) => {
        return {
          ...config,
          api: "/api/chat",
          headers: {
            ...config.headers,
            "Authorization": `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
            "X-Custom-Header": "custom-value",
          },
          credentials: "include",
        };
      },
      prepareReconnectToStreamRequest: async (config) => {
        return {
          ...config,
          headers: {
            ...config.headers,
            "Authorization": `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
          },
          credentials: "include",
        };
      },
      maxConsecutiveErrors: 5,
    }),
  });

  return (
    <div>
      <div className="space-y-4">
        {messages.map((m) => (
          <div key={m.id}>
            <strong>{m.role}:</strong> {m.content}
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput("");
        }}
      >
        <input
          value={input}
          placeholder="Say something..."
          onChange={(e) => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

## See Also

* [DurableAgent](/docs/api-reference/workflow-ai/durable-agent) - Building durable AI agents within workflows
* [AI SDK `useChat` Documentation](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) - Using `useChat` with custom transports
* [Workflows and Steps](/docs/foundations/workflows-and-steps) - Understanding workflow fundamentals
* ["flight-booking-app" Example](https://github.com/vercel/workflow-examples/tree/main/flight-booking-app) - An example application which uses `WorkflowChatTransport`


## Sitemap
[Overview of all docs pages](/sitemap.md)
