UNPKG

ai

Version:

AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.

376 lines (292 loc) • 13.4 kB
--- title: Transport description: Learn how to use custom transports with useChat. --- # Transport The `useChat` transport system provides fine-grained control over how messages are sent to your API endpoints and how responses are processed. This is particularly useful for alternative communication protocols like WebSockets, custom authentication patterns, or specialized backend integrations. ## Default Transport By default, `useChat` uses HTTP POST requests to send messages to `/api/chat`: ```tsx import { useChat } from '@ai-sdk/react'; // Uses default HTTP transport const { messages, sendMessage } = useChat(); ``` This is equivalent to: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); ``` ## Custom Transport Configuration Configure the default transport with custom options: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/custom-chat', headers: { Authorization: 'Bearer your-token', 'X-API-Version': '2024-01', }, credentials: 'include', }), }); ``` ### Dynamic Configuration You can also provide functions that return configuration values. This is useful for authentication tokens that need to be refreshed, or for configuration that depends on runtime conditions: ```tsx const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', headers: () => ({ Authorization: `Bearer ${getAuthToken()}`, 'X-User-ID': getCurrentUserId(), }), body: () => ({ sessionId: getCurrentSessionId(), preferences: getUserPreferences(), }), credentials: () => 'include', }), }); ``` ### Request Transformation Transform requests before sending to your API: ```tsx const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => { return { headers: { 'X-Session-ID': id, }, body: { messages: messages.slice(-10), // Only send last 10 messages trigger, messageId, }, }; }, }), }); ``` ## WebSocket Transport Use `WebSocketChatTransport` when you want chat turns to share a persistent WebSocket connection instead of opening an HTTP request for every turn. This is useful for chats with frequent client-side tool results or other multi-step interactions. ```tsx import { useChat } from '@ai-sdk/react'; import { WebSocketChatTransport } from 'ai'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WebSocketChatTransport({ url: '/api/chat', params: () => ({ token: getShortLivedToken() }), }), [], ); const { messages, sendMessage } = useChat({ transport }); // ... render the chat } ``` The transport multiplexes requests by `requestId`, validates incoming `UIMessageChunk` values, forwards aborts to the server, and reuses the open socket for later turns. HTTP and HTTPS URLs are automatically converted to WS and WSS URLs. The code that creates a transport owns its lifecycle. React and Vue chat hooks do not close caller-supplied transports when options change or components are disposed, because the same transport can be shared by multiple chats. Call `transport.close()` after its last consumer is gone. If a directly managed `Chat` instance exclusively owns the transport, call `chat.dispose()`; for a shared transport, stop individual chats and close the transport only after all consumers are finished. Your WebSocket server must implement the [`WebSocketChatTransport` protocol](/docs/reference/ai-sdk-ui/websocket-chat-transport#wire-protocol). The exported `WebSocketChatTransportRequest` and `WebSocketChatTransportResponse` types can be used to type server frames. Use `safeValidateWebSocketChatTransportRequest` before handling an untrusted client frame. <Note> Browser WebSocket APIs cannot set arbitrary handshake headers. The `headers` option is included inside each application-level request frame. Authenticate the handshake with secure cookies, a short-lived query parameter, or a subprotocol, and validate the connection origin and every frame on the server. </Note> `sendAutomaticallyWhen` still starts a new chat turn after the preceding UI message stream finishes. `WebSocketChatTransport` avoids opening another HTTP connection for that turn, but it does not send tool results into an active model generation. If the socket closes during a response, the active stream errors. A later send opens a new connection. The transport does not automatically replay requests, because replaying a generation can duplicate model output or tool effects. `reconnectToStream` requests a complete replay of the retained response from sequence zero. Chat rebuilds the message with a fresh parser, so the replay must include all earlier chunks and the original `start` chunk's `messageId`. That stable message ID lets Chat replace the partial assistant message. Duplicate sequence numbers are ignored within each response stream. For complete options, protocol details, cancellation behavior, and a server example, see the [`WebSocketChatTransport` reference](/docs/reference/ai-sdk-ui/websocket-chat-transport). ## Direct Agent Transport For scenarios where you want to communicate directly with an [Agent](/docs/reference/ai-sdk-core/agent) without going through HTTP, you can use `DirectChatTransport`. This transport invokes the agent's `stream()` method directly in-process. This is useful for: - **Server-side rendering**: Run the agent on the server without an API endpoint - **Testing**: Test chat functionality without network requests - **Single-process applications**: Desktop or CLI apps where client and agent run together ```tsx import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent } from 'ai'; __PROVIDER_IMPORT__; const agent = new ToolLoopAgent({ model: __MODEL__, instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, }, }); const { messages, sendMessage } = useChat({ transport: new DirectChatTransport({ agent }), }); ``` ### How It Works Unlike `DefaultChatTransport` which sends HTTP requests: 1. `DirectChatTransport` validates incoming UI messages 2. Converts them to model messages using `convertToModelMessages` 3. Calls the agent's `stream()` method directly 4. Returns the result as a UI message stream via `toUIMessageStream()` ### Configuration Options You can pass additional options to customize the stream output: ```tsx const transport = new DirectChatTransport({ agent, // Pass options to the agent options: { customOption: 'value' }, // Configure what's sent to the client sendReasoning: true, sendSources: true, }); ``` <Note> `DirectChatTransport` does not support stream reconnection since there is no persistent server-side stream. The `reconnectToStream()` method always returns `null`. </Note> For complete API details, see the [DirectChatTransport reference](/docs/reference/ai-sdk-ui/direct-chat-transport). ## Workflow Transport For chat apps built on the [Workflow SDK](/docs/agents/workflow-agent), `WorkflowChatTransport` from `@ai-sdk/workflow` provides automatic stream reconnection. It handles the common scenario where a workflow function times out mid-stream — the transport detects the missing `finish` event and reconnects to resume from where it left off. ```tsx import { useChat } from '@ai-sdk/react'; import { WorkflowChatTransport } from '@ai-sdk/workflow/client'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WorkflowChatTransport({ api: '/api/chat', maxConsecutiveErrors: 5, onChatEnd: ({ chatId, chunkIndex }) => { console.log(`Chat complete: ${chunkIndex} chunks`); }, }), [], ); const { messages, sendMessage } = useChat({ transport }); // ... render chat UI } ``` Key features: - **Automatic reconnection**: Detects interrupted streams (no `finish` event) and reconnects via GET to `{api}/{runId}/stream` - **Page refresh recovery**: `initialStartIndex` controls where the initial reconnection begins - **Configurable retries**: `maxConsecutiveErrors` controls how many consecutive reconnection failures to tolerate - **Lifecycle callbacks**: `onChatSendMessage` and `onChatEnd` for tracking chat state Negative `initialStartIndex` values can fetch only the tail when the durable server stream already stores `UIMessageChunk` objects. For raw `WorkflowAgent` streams, use a non-negative cursor and follow the server-side conversion in the WorkflowAgent guide. For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport). For server-side endpoint setup, see the [WorkflowAgent guide](/docs/agents/workflow-agent#resumable-streaming-with-workflowchattransport). ## Chat SDK Web Adapter The [Chat SDK](https://chat-sdk.dev) Web adapter (`@chat-adapter/web`) serves a `useChat` endpoint from a Chat SDK bot. It speaks the [UI message stream protocol](/docs/ai-sdk-ui/stream-protocol), so the default transport works without changes. Use it when the same bot also runs on Slack, Microsoft Teams, Discord, or other [chat platforms](/docs/agents/chat-platforms), and you want one set of handlers for all of them. <InstallPackages packages="ai @ai-sdk/react chat @chat-adapter/web @chat-adapter/state-redis" /> On the server, register the Web adapter and handle messages with `onDirectMessage`. Each browser conversation is a direct message thread: ```ts filename="lib/bot.ts" import { Chat } from 'chat'; import { toAiMessages } from 'chat/ai'; import { createWebAdapter } from '@chat-adapter/web'; import { createRedisState } from '@chat-adapter/state-redis'; import { ToolLoopAgent } from 'ai'; import { getSession } from './session'; // your app's auth const agent = new ToolLoopAgent({ model: 'anthropic/claude-sonnet-5.5', instructions: 'You are a helpful assistant.', }); export const bot = new Chat({ userName: 'mybot', adapters: { web: createWebAdapter({ userName: 'mybot', // Identify the caller from your app's session. Returning null responds with 401. getUser: async () => { const session = await getSession(); return session ? { id: session.userId } : null; }, }), }, state: createRedisState(), }); bot.onDirectMessage(async thread => { const { messages } = await bot.history.thread.list(thread.id, { limit: 20, }); const result = await agent.stream({ messages: await toAiMessages(messages), abortSignal: thread.signal, }); await thread.post(result.fullStream); }); ``` ```ts filename="app/api/chat/route.ts" import { after } from 'next/server'; import { bot } from '@/lib/bot'; export async function POST(request: Request): Promise<Response> { return bot.webhooks.web(request, { waitUntil: task => after(() => task), }); } ``` On the client, point the default transport at the route: ```tsx filename="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; export default function Chat() { const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }), }); // ... render chat UI } ``` Things to know: - **Authentication**: browser requests have no platform signature, so `getUser` is the security boundary. Resolve the user from your app's session and return `null` to reject the request. - **Conversation history**: the adapter ignores the message history in the request body. It stores each conversation's messages in the state adapter, and `bot.history.thread.list()` reads them back. The chat `id` from `useChat` identifies the conversation. - **Stopping**: the adapter honors the request's abort signal, so calling `stop()` in `useChat` stops the handler's stream. For all options, including the `ag-ui` protocol and `useChat` wrappers for React, Vue, and Svelte, see the [Web adapter docs](https://chat-sdk.dev/adapters/official/web). ## Building Custom Transports To understand how to build your own transport, refer to the source code of the default implementation: - **[DefaultChatTransport](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/default-chat-transport.ts)** - The complete default HTTP transport implementation - **[HttpChatTransport](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/http-chat-transport.ts)** - Base HTTP transport with request handling - **[ChatTransport Interface](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/chat-transport.ts)** - The transport interface you need to implement These implementations show you exactly how to: - Handle the `sendMessages` method - Process UI message streams - Transform requests and responses - Handle errors and connection management The transport system gives you complete control over how your chat application communicates, enabling integration with any backend protocol or service.