# Get started with the Core SDK Build a streaming AI chat application using Ably AI Transport's core React hooks. This approach gives you direct access to the conversation tree, branching, and pagination through hooks like `useView`, `useSend`, `useRegenerate`, and `useEdit`. To use directly with Vercel's `useChat` for message management, see [Get started with Vercel AI SDK](https://ably.com/docs/ai-transport/getting-started/vercel-ai-sdk.md). ## Prerequisites - Node.js 20+ - An [Ably account](https://ably.com/sign-up) with an API key - An Anthropic API key (or OpenAI, adapt the model config) ## Install dependencies ### Shell ``` npm install @ably/ai-transport ably ai @ai-sdk/anthropic next react react-dom jsonwebtoken ``` ## Set up environment variables Create `.env.local`: ### Shell ``` ABLY_API_KEY=your-ably-api-key ANTHROPIC_API_KEY=your-anthropic-api-key ``` ## Step 1: Create an Ably token endpoint Create the file `app/api/auth/ably-token/route.ts`. This endpoint issues JWT tokens for client authentication: ### Javascript ``` import jwt from 'jsonwebtoken' import { NextResponse } from 'next/server' export async function GET(req) { const apiKey = process.env.ABLY_API_KEY const [keyName, keySecret] = apiKey.split(':') const url = new URL(req.url) const clientId = url.searchParams.get('clientId') ?? `user-${crypto.randomUUID().slice(0, 8)}` const token = jwt.sign( { 'x-ably-clientId': clientId, 'x-ably-capability': JSON.stringify({ '*': ['publish', 'subscribe', 'history'] }), }, keySecret, { algorithm: 'HS256', keyid: keyName, expiresIn: '1h' }, ) return new NextResponse(token, { headers: { 'Content-Type': 'application/jwt' }, }) } ``` ## Step 2: Create an Ably provider Create the file `app/providers.tsx`. The Ably provider creates an authenticated realtime client using the token endpoint and makes it available to all child components. AI Transport uses this client to connect to channels for durable sessions. ### Javascript ``` 'use client' import { useEffect, useState } from 'react' import * as Ably from 'ably' import { AblyProvider } from 'ably/react' export function Providers({ clientId, children }) { const [client, setClient] = useState(null) useEffect(() => { const ably = new Ably.Realtime({ authCallback: async (_tokenParams, callback) => { try { const response = await fetch(`/api/auth/ably-token?clientId=${encodeURIComponent(clientId ?? '')}`) const jwt = await response.text() callback(null, jwt) } catch (err) { callback(err instanceof Error ? err.message : String(err), null) } }, }) setClient(ably) return () => ably.close() }, [clientId]) if (!client) return null return {children} } ``` ## Step 3: Create the server API route Create the file `app/api/chat/route.ts`. The server creates a turn, publishes the user message, streams the LLM response through the Ably channel, and returns immediately. This example uses Vercel AI SDK's `streamText` for model orchestration and `UIMessageCodec` to encode the response for Ably. You can swap `streamText` for any model inference approach. You need a codec that converts your model's output into Ably messages; `UIMessageCodec` handles this for Vercel AI SDK's message types. ### Javascript ``` import { after } from 'next/server' import { streamText, convertToModelMessages } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Ably from 'ably' import { createServerTransport } from '@ably/ai-transport' import { UIMessageCodec } from '@ably/ai-transport/vercel' const ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY }) export async function POST(req) { const { messages, history, id, turnId, clientId, forkOf, parent } = await req.json() const channel = ably.channels.get(id) const transport = createServerTransport({ channel, codec: UIMessageCodec }) const turn = transport.newTurn({ turnId, clientId, parent, forkOf }) await turn.start() if (messages.length > 0) { await turn.addMessages(messages, { clientId }) } const allMessages = [...(history ?? []).map((h) => h.message), ...messages.map((m) => m.message)] const result = streamText({ model: anthropic('claude-sonnet-4-20250514'), system: 'You are a helpful assistant.', messages: await convertToModelMessages(allMessages), abortSignal: turn.abortSignal, }) after(async () => { const { reason } = await turn.streamResponse(result.toUIMessageStream()) await turn.end(reason) transport.close() }) return new Response(null, { status: 200 }) } ``` ## Step 4: Create the chat component Create the file `app/chat.tsx`. This uses AI Transport's core hooks directly. The `useView` hook provides the visible messages, a `send` function, and pagination: ### Javascript ``` 'use client' import { useClientTransport, useActiveTurns, useView } from '@ably/ai-transport/react' import { useState } from 'react' export function Chat({ chatId }) { const [input, setInput] = useState('') const transport = useClientTransport({ channelName: chatId }) const { messages, send, hasOlder, loadOlder } = useView({ transport, limit: 30 }) const activeTurns = useActiveTurns({ transport }) const isStreaming = activeTurns.size > 0 const handleSubmit = async (e) => { e.preventDefault() if (!input.trim()) return const text = input setInput('') await send({ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text }], }) } return (
{hasOlder && ( )} {messages.map((msg) => (
{msg.role}:{' '} {msg.parts.map((part, i) => part.type === 'text' ? {part.text} : null )}
))}
setInput(e.target.value)} placeholder="Type a message..." /> {isStreaming ? ( ) : ( )}
) } ```
## Step 5: Wire it together Create the file `app/page.tsx`. `UIMessageCodec` is the codec for Vercel AI SDK's message types, matching the server's output format. If you use a different model inference layer, provide a different codec to `TransportProvider`. ### Javascript ``` import { Providers } from './providers' import { TransportProvider } from '@ably/ai-transport/react' import { UIMessageCodec } from '@ably/ai-transport/vercel' import { Chat } from './chat' export default function Page() { const chatId = 'my-chat-session' return ( ) } ``` Run `npm run dev` and open `http://localhost:3000`. Open a second tab to the same URL to see both tabs share the same durable session. ## What's happening 1. The client sends the user message via HTTP POST to your API route. 2. The server publishes the message to the Ably channel, invokes the LLM, and streams tokens to the channel. 3. Every client subscribed to the channel receives tokens in realtime. 4. If a client disconnects, it automatically reconnects and resumes from where it left off. The `useView` hook subscribes to the conversation tree and returns the visible messages along the currently selected branch. You have direct access to the tree structure: branching, pagination, and sibling navigation. ## Explore next - [Conversation branching](https://ably.com/docs/ai-transport/features/branching.md): use `useView`'s `getSiblings` and `select` to navigate branches. - [Edit and regenerate](https://ably.com/docs/ai-transport/features/edit-and-regenerate.md): use `useView`'s `edit` and `regenerate` to fork conversations. - [History and replay](https://ably.com/docs/ai-transport/features/history.md): `useView` loads history on mount with the `limit` option. - [Sessions](https://ably.com/docs/ai-transport/concepts/sessions.md): understand durable sessions and how the conversation persists. - [React hooks API reference](https://ably.com/docs/ai-transport/api-reference/react-hooks.md): the full generic hooks API. ## Related Topics - [Vercel AI SDK](https://ably.com/docs/ai-transport/getting-started/vercel-ai-sdk.md): Build a streaming AI chat app with Vercel AI SDK and Ably AI Transport in 5 minutes. Durable sessions, multi-device sync, and cancellation out of the box. ## 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.