ChatTransportProvider
ChatTransportProvider is the entry point for the Vercel React integration. It wraps children in a ClientSessionProvider bound to UIMessageCodec, constructs a ChatTransport over that session, and exposes both through React context. Descendants call useChatTransport to get the transport (and the session) for Vercel useChat.
The Realtime client is read from the surrounding <AblyProvider>. The session is created once on first render. The chat transport is recreated only when chatOptions changes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { ChatTransportProvider } from '@ably/ai-transport/vercel/react';
const ably = new Ably.Realtime({ authUrl: '/api/auth/token' });
function App() {
return (
<AblyProvider client={ably}>
<ChatTransportProvider channelName="conversation-42">
<Chat />
</ChatTransportProvider>
</AblyProvider>
);
}ChatTransportProvider also re-exports the Vercel-baked core hooks (useClientSession, useView, useTree, useCreateView, useAblyMessages, and the underlying ClientSessionProvider) from the same entry point, all pre-bound to the Vercel TInput / TOutput / TProjection / TMessage types.
Props
ChatTransportProviderProps extends ClientSessionProviderProps with the codec field omitted (always UIMessageCodec) and adds four transport-owned options for the agent-invocation POST.
channelNameStringapiString/api/chat.credentialsRequestCredentials'include' for cookie-based cross-origin auth.fetchtypeof globalThis.fetchglobalThis.fetch.chatOptionsChatTransportOptionsprepareSendMessagesRequest). Must be stable across renders; wrap in useMemo or define outside the component. A new object reference recreates the ChatTransport.clientIdStringclientId on everything the session publishes.messagesUIMessage[]loggerLoggerchildrenReactNodeuseChatTransport.Behaviour
useChatTransport and the re-exported useClientSession / useView / useTree / useCreateView / useAblyMessages find the nearest provider in the tree. Pass channelName to look up a specific provider by name when nesting multiple.
The underlying ClientSession is created once on first render via useRef, and connect() runs from a useEffect. The session is closed when the provider truly unmounts.
The ChatTransport itself is not closed on unmount: its close() delegates to ClientSession.close(), which the inner ClientSessionProvider already calls. Auto-closing here would double-close in React Strict Mode.
If createClientSession throws, the error surfaces via useClientSession.sessionError and useChatTransport.chatTransportError. The component tree does not crash.
Nest providers for multiple sessions
Nest providers with distinct channelName values to manage more than one chat in the same tree. Each provider merges its slot into the parent record so descendants can address any registered session by name.
1
2
3
4
5
<ChatTransportProvider channelName="ai:main">
<ChatTransportProvider channelName="ai:aux">
<App />
</ChatTransportProvider>
</ChatTransportProvider>Inside App:
1
2
3
4
5
6
7
import { useChatTransport } from '@ably/ai-transport/vercel/react';
function App() {
const main = useChatTransport({ channelName: 'ai:main' });
const aux = useChatTransport({ channelName: 'ai:aux' });
// ...
}Example
Full Vercel chat wiring using ChatTransportProvider, useChatTransport, and useChat from @ai-sdk/react.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
'use client';
import { useEffect, useState } from 'react';
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { ChatTransportProvider, useChatTransport } from '@ably/ai-transport/vercel/react';
import { useChat } from '@ai-sdk/react';
function Providers({ children }) {
const [client, setClient] = useState(null);
useEffect(() => {
const ably = new Ably.Realtime({ authUrl: '/api/auth/token', clientId: 'user' });
setClient(ably);
return () => ably.close();
}, []);
if (!client) return null;
return <AblyProvider client={client}>{children}</AblyProvider>;
}
function Chat() {
const { chatTransport } = useChatTransport();
const { messages, sendMessage, status, stop } = useChat({ transport: chatTransport });
return (
<div>
{messages.map((m) => (
<div key={m.id}>{m.role}: {m.parts.map((p) => p.type === 'text' ? p.text : '').join('')}</div>
))}
{status === 'streaming'
? <button type="button" onClick={stop}>Stop</button>
: <button type="button" onClick={() => sendMessage({ text: 'Hello' })}>Send</button>}
</div>
);
}
export default function Page() {
return (
<Providers>
<ChatTransportProvider channelName="my-chat">
<Chat />
</ChatTransportProvider>
</Providers>
);
}