# ClientSession The `ClientSession` subscribes to an Ably channel, decodes incoming messages through a codec, and builds a conversation tree. It owns the channel attach and the cancel-publish path, exposes a default branch-aware `View` for rendering, and lets you derive additional views over the same tree. Construct one with `createClientSession` from the core entry point. For Vercel `UIMessage` channels, use the pre-bound factory from [`@ably/ai-transport/vercel`](https://ably.com/docs/ai-transport/api/javascript/vercel/chat-transport.md) instead. #### Javascript ``` import * as Ably from 'ably'; import { createClientSession } from '@ably/ai-transport'; import { UIMessageCodec } from '@ably/ai-transport/vercel'; const ably = new Ably.Realtime({ authUrl: '/api/auth/token' }); const session = createClientSession({ client: ably, channelName: 'conversation-42', codec: UIMessageCodec, }); await session.connect(); ``` ## Properties | Property | Description | Type | | --- | --- | --- | | tree | The complete conversation tree. Holds every known Run node and emits events on any change. Use `view` in most cases; reach for `tree` for low-level inspection. |
| | view | The default paginated, branch-aware view for rendering. Events scope to the visible messages. |
|
| Property | Description | Type | | --- | --- | --- | | getRunNode | `(runId: string) => RunNode \| undefined`. Get a Run by id. | Function | | getNodeByCodecMessageId | `(codecMessageId: string) => ConversationNode \| undefined`. Get the input or run node that owns a given codec-message-id. Narrow on `kind` (`'input'` or `'run'`) before reading kind-specific fields. | Function | | getSiblingNodes | `(key: string) => ConversationNode[]`. The sibling group for a node key: edit versions for an input node, regenerate siblings for a reply run. Ordered oldest-first by serial; single-element when there are no siblings; empty when the key is unknown. | Function | | on | `(event: 'update' \| 'ably-message' \| 'run' \| 'output', handler) => () => void`. Subscribe to tree changes. Returns an unsubscribe function. | Function |
| Property | Description | Type | | --- | --- | --- | | getMessages | `() => CodecMessage[]`. Visible messages along the selected branch, each paired with its `codecMessageId`. Read the domain object from each entry's `message` field; correlate back to the transport (run lookups, branch navigation, continuation routing) via `codecMessageId`. | Function | | runs | `() => RunInfo[]`. Visible Runs along the selected branch. | Function | | runOf | `(codecMessageId: string) => RunInfo \| undefined`. Run that owns the message. | Function | | run | `(runId: string) => RunInfo \| undefined`. Direct Run lookup by id. | Function | | branchSelection | `(codecMessageId: string) => BranchSelection`. Branch siblings at the anchor. | Function | | selectSibling | `(codecMessageId: string, index: number) => void`. Select a sibling at the anchor. | Function | | hasOlder | `() => boolean`. Whether older Runs can be loaded. | Function | | loadOlder | `(limit?: number) => Promise`. Reveal older Runs. | Function | | send | `(events, options?) => Promise`. Send one or more `TInput`s on the channel and fire a POST. Wrap a domain message via `codec.createUserMessage` to send a fresh user message. | Function | | regenerate | `(messageId, options?) => Promise`. Regenerate an assistant message. | Function | | edit | `(messageId, inputs, options?) => Promise`. Edit a user message. | Function | | on | `(event: 'update' \| 'ably-message' \| 'run', handler) => () => void`. Subscribe to view updates, raw Ably messages for visible nodes, or run lifecycle events. Returns an unsubscribe function. | Function | | close | `() => void`. Tear down the view. | Function |
## Create a client session `function createClientSession(options: ClientSessionOptions): ClientSession` Construct a `ClientSession` bound to an Ably channel. The session does not attach to the channel until [`connect()`](#connect) resolves. ### Javascript ``` import * as Ably from 'ably'; import { createClientSession } from '@ably/ai-transport'; import { UIMessageCodec } from '@ably/ai-transport/vercel'; const ably = new Ably.Realtime({ authUrl: '/api/auth/token' }); const session = createClientSession({ client: ably, channelName: 'conversation-42', codec: UIMessageCodec, clientId: 'user-abc', }); ``` ### Parameters | Parameter | Required | Description | Type | | --- | --- | --- | --- | | client | required | The Ably Realtime client. The caller owns its lifecycle; `session.close()` does not close the client. | `Ably.Realtime` | | channelName | required | The channel to subscribe to and publish cancel signals on. The session owns this channel; do not also resolve it elsewhere with conflicting options. | String | | codec | required | The codec used to encode and decode events and messages. | `Codec` | | clientId | optional | The client's identity, used as the Ably publisher `clientId` on everything this session publishes. Surfaces on the wire as the run-owner / input-owner id so other clients can attribute messages. | String | | messages | optional | Initial messages to seed the conversation tree with. Forms a linear chain. | `TMessage[]` | | logger | optional | Logger instance for diagnostic output. | `Logger` |
### Returns `ClientSession`. The session instance. Call [`connect()`](#connect) to attach before sending or cancelling. ## Connect the session `connect(): Promise` Subscribe to the channel and implicitly attach. Idempotent: subsequent calls return the same promise. All write methods on [`view`](https://ably.com/docs/ai-transport/api/javascript/core/client-session.md#view) and [`cancel`](#cancel) throw `InvalidArgument` until `connect()` resolves. ### Javascript ``` await session.connect(); ``` ### Returns `Promise`. Resolves when the channel is attached and the session is ready for writes. ## Create an additional view `createView(): View` Create an additional view over the same conversation tree. Each view has independent branch selections and pagination state. The caller owns the returned view's lifecycle: call its `close()` when it is no longer needed, or `session.close()` closes it. ### Javascript ``` const secondaryView = session.createView(); secondaryView.selectSibling(messageId, 1); // the default view is unaffected session.view.branchSelection(messageId).index; // 0 ``` ### Returns `View`. A new view with its own pagination window and branch selection state. ## Cancel a run `cancel(runId: string): Promise` Publish a cancel signal for the specified Run. The agent receives the cancel through its own channel subscription and ends the Run with reason `'cancelled'`. ### Javascript ``` const activeRun = await session.view.send({ kind: 'user-message', message: { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Tell me a story' }], }, }); // later, cancel from the UI. // activeRun.cancel() works immediately, even before the runId // promise on activeRun has resolved. await activeRun.cancel(); // Or, when you already have a resolved runId (for example from a // RunInfo in view.runs()): // await session.cancel(someRunInfo.runId); ``` ### Parameters | Parameter | Required | Description | Type | | --- | --- | --- | --- | | runId | required | The Run to cancel. Typically obtained from `ActiveRun.runId` returned by a send. | String |
### Returns `Promise`. Resolves once the cancel message has been published. The cancel is best-effort: if the agent has already ended the Run, the cancel is a no-op. ## Subscribe to session errors `on(event: 'error', handler: (error: Ably.ErrorInfo) => void): () => void` Subscribe to non-fatal session errors. These indicate something went wrong but the session is still operational; examples are subscription callback failures and channel continuity loss. ### Javascript ``` const unsubscribe = session.on('error', (error) => { console.error('Session error:', error.code, error.message); }); // later, when the listener is no longer needed unsubscribe(); ``` ### Parameters | Parameter | Required | Description | Type | | --- | --- | --- | --- | | event | required | The event to subscribe to. Currently only `'error'`. | `'error'` | | handler | required | Called with an [`ErrorInfo`](https://ably.com/docs/ai-transport/api/errors.md#errorinfo) for every non-fatal error. | Function |
### Returns `() => void`. An unsubscribe function. Call it to remove the listener. ## Close the session `close(): Promise` Tear down the session. Unsubscribe from the channel, close active streams, clear handlers, and prevent further operations. `close()` is local-state-only. The server keeps streaming until its Runs end on their own. To stop in-progress Runs, call [`cancel`](#cancel) for each before `close()`. ### Javascript ``` const runIds = session.view.runs() .filter((run) => run.status === 'active') .map((run) => run.runId); await Promise.all(runIds.map((runId) => session.cancel(runId))); await session.close(); ``` ### Returns `Promise`. Resolves once the channel has been released. ## Example End-to-end usage covering construction, connect, send, and teardown. ### Javascript ``` import * as Ably from 'ably'; import { createClientSession } from '@ably/ai-transport'; import { UIMessageCodec } from '@ably/ai-transport/vercel'; const ably = new Ably.Realtime({ authUrl: '/api/auth/token' }); const session = createClientSession({ client: ably, channelName: 'conversation-42', codec: UIMessageCodec, clientId: 'user-abc', }); await session.connect(); session.view.on('update', () => { render(session.view.getMessages().map(({ message }) => message)); }); const activeRun = await session.view.send(UIMessageCodec.createUserMessage({ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Plan a 3-day trip to Lisbon.' }], })); // The SDK doesn't POST. The application wakes the agent itself. await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(activeRun.toInvocation().toJSON()), }); // The agent assigns the runId on the server, so activeRun.runId is a // Promise. Await it to read the id. const runId = await activeRun.runId; await session.close(); ably.close(); ``` ## Related Topics - [Agent session](https://ably.com/docs/ai-transport/api/javascript/core/agent-session.md): API reference for the AI Transport AgentSession: factory, lifecycle methods, the Run interface, and the Invocation value object. - [Codec](https://ably.com/docs/ai-transport/api/javascript/core/codec.md): API reference for the AI Transport Codec interface. Build custom codecs to integrate any AI framework with Ably channels. ## Documentation Index To discover additional Ably documentation: 1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages. 2. Identify relevant URLs from that index. 3. Fetch target pages as needed. Avoid using assumed or outdated documentation paths.