Providers

ClientSessionProvider creates a ClientSession once on mount, connects it, and exposes it to descendants through React context. The hooks in @ably/ai-transport/react read the session via this provider, so wrap every subtree that uses them in one.

Nest multiple providers with distinct channelName values to manage more than one session in the same tree. Each provider merges its slot into the parent registry so descendants can address any registered session by channelName.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { ClientSessionProvider } from '@ably/ai-transport/react';
import { createUIMessageSessionCodec } from '@ably/ai-transport/vercel';

const ably = new Ably.Realtime({ authUrl: '/api/auth/token' });

function App() {
  return (
    <AblyProvider client={ably}>
      <ClientSessionProvider channelName="conversation-42" codec={createUIMessageSessionCodec()}>
        <Chat />
      </ClientSessionProvider>
    </AblyProvider>
  );
}

ClientSessionProvider

The provider reads the Ably Realtime client from the surrounding <AblyProvider> and forwards it, along with the supplied props, to createClientSession. The session is created once on first render via useRef. The provider calls connect() from a useEffect on mount without awaiting it, so the channel finishes subscribing and attaching after the first render, and each write through the session waits on that same connect promise internally.

If createClientSession throws, the error is stored on the slot alongside an undefined session. useClientSession surfaces it as sessionError so your UI can render an error state.

The provider also renders an ably-js <ChannelProvider> for the session's channel, so ably-js's channel hooks (usePresence, usePresenceListener, useChannel) work for any descendant, which is what agent presence in React relies on. Pass the same channelName you gave the provider.

Props

ClientSessionProviderProps extends all ClientSessionOptions except client (which is read from <AblyProvider>). The session takes its identity from that client's auth.clientId (set via the Ably token or ClientOptions.clientId), reads it at publish time rather than at construction, and sets it as the run client id on the inputs it publishes. The agent then writes that same identity as the input client id on its own run events, reading it from the input message's Ably clientId. A connection with no concrete clientId, whether anonymous or holding a wildcard * token, publishes without one.

channelNameString
The channel the session subscribes to. Used as the registry key for nested providers.
codecCodec<TInput, TOutput, TProjection, TMessage>
The codec used to encode and decode events and messages.
channelModesAbly.ChannelMode[]
Extra channel modes to request on top of the modes AI Transport always needs. Pass OBJECT_MODES to use Ably LiveObjects. Keep the value constant for the provider's lifetime.
historyPageSizeNumber
Wire-message limit fetched per channel-history round trip when paging older history, shared by every view on the session. Defaults to 100.
reorderWindowMsNumber
Advanced. How long, in milliseconds on the Ably message-timestamp timeline, a structurally complete run's event log is kept after its last activity before the tree may drop it. The tree uses that log to merge a late, out-of-order wire message into canonical position, and to drop an earlier attempt's output when a step retry supersedes it. Once the log is gone, a late wire message merges in arrival order instead. Raise it for a durable agent whose step retries back off longer than the default. Defaults to 120000.
loggerLogger
Logger instance for diagnostic output.
childrenReactNode
Descendant components that consume the session via hooks.

channelModes must stay constant while the provider is mounted. The provider recreates its session only when channelName changes, so changing the modes after mount silently reverts the channel's mode set without reattaching the channel. The provider seeds its internal <ChannelProvider> with the same resolved modes, which keeps the ably-js channel hooks and the session in agreement.

On unmount the provider closes the session as a microtask rather than synchronously. React Strict Mode's development remount happens synchronously in between, and it cancels the pending close, so a session survives the double mount instead of being torn down and rebuilt.

ClientSessionSlot

A single entry in the registry holding the session and any error from construction. useClientSession reads the slot and returns a ClientSessionHandle with the same two fields, except that its session is never undefined: a failed slot is surfaced as a stub session that throws on every access.

sessionClientSession<TInput, TOutput, TProjection, TMessage> or Undefined
The constructed session, or undefined if construction failed.
sessionErrorAbly.ErrorInfo or Undefined
Construction error from createClientSession, or undefined on success.

Capture the codec types with createSessionHooks

function createSessionHooks<TInput, TOutput, TProjection, TMessage>(): SessionHooks<TInput, TOutput, TProjection, TMessage>

A factory that captures the codec's four type parameters once and returns a bundle of type-safe hooks plus a ClientSessionProvider narrowed to the captured types. Hook call sites need no type parameters once you have called the factory.

Use this when you have one codec for the whole app and want call sites that look like const view = useView({ limit: 30 }) without generics. The Vercel React entry point already exports a bundle pre-typed to the Vercel types.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// session.ts: shared module
import { createSessionHooks } from '@ably/ai-transport/react';
import type {
  VercelSessionInput,
  VercelOutput,
  VercelProjection,
} from '@ably/ai-transport/vercel';
import type { UIMessage } from 'ai';

export const {
  ClientSessionProvider,
  useClientSession,
  useView,
  useTree,
  useCreateView,
  useAblyMessages,
} = createSessionHooks<VercelSessionInput, VercelOutput, VercelProjection, UIMessage>();

The factory takes four type parameters. With no type arguments TInput and TOutput fall back to their constraints, CodecInputEvent and CodecOutputEvent, and TProjection and TMessage become unknown; pass all four so call sites stay type-safe.

Returns

ClientSessionProviderComponentType<ClientSessionProviderProps>
ClientSessionProvider narrowed to the captured types. No JSX type params needed.
useClientSessionFunction
Type-narrowed useClientSession.
useViewFunction
Type-narrowed useView.
useTreeFunction
Type-narrowed useTree.
useAblyMessagesFunction
Type-narrowed useAblyMessages.
useCreateViewFunction
Type-narrowed useCreateView.

Example

Nested providers with distinct channel names and a child component that addresses each session by name.

JavaScript

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

import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { ClientSessionProvider, useClientSession } from '@ably/ai-transport/react';
import { createUIMessageSessionCodec } from '@ably/ai-transport/vercel';

const ably = new Ably.Realtime({ authUrl: '/api/auth/token' });

function App() {
  return (
    <AblyProvider client={ably}>
      <ClientSessionProvider channelName="ai:main" codec={createUIMessageSessionCodec()}>
        <ClientSessionProvider channelName="ai:aux" codec={createUIMessageSessionCodec()}>
          <Chat />
        </ClientSessionProvider>
      </ClientSessionProvider>
    </AblyProvider>
  );
}

function Chat() {
  const { session: main } = useClientSession({ channelName: 'ai:main' });
  const { session: aux, sessionError } = useClientSession({ channelName: 'ai:aux' });

  if (sessionError) return <ErrorBanner error={sessionError} />;
  return <SplitPane mainSession={main} auxSession={aux} />;
}