workflow
Version:
Workflow DevKit - Build durable, resilient, and observable workflows
261 lines (222 loc) • 7.9 kB
text/mdx
---
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
---
<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)
## 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`