Integrate cloud persistence using assistant-ui runtime and pre-built components.
Overview
This guide shows how to integrate Assistant Cloud with the AI SDK using assistant-ui's runtime system and pre-built UI components.
What You Get
This integration provides:
<Thread />— A complete chat interface with messages, composer, and status indicators<ThreadList />— A sidebar showing all conversations with auto-generated titles, plus new/delete/manage actions- Automatic Persistence — Messages save as they stream. Threads are created automatically on first message.
- Runtime Integration — The assistant-ui runtime handles all cloud synchronization behind the scenes.
How It Works
The useChatRuntime hook from @assistant-ui/react-ai-sdk wraps AI SDK's useChat and adds cloud persistence via the cloud parameter. The runtime automatically:
- Creates a cloud thread on the first user message
- Persists messages as they complete streaming
- Generates a conversation title after the assistant's first response
- Loads historical messages when switching threads via
<ThreadList />
You provide the cloud configuration—everything else is handled. The default AssistantChatTransport automatically sends requests to /api/chat.
Prerequisites
You need an assistant-cloud account to follow this guide. Sign up here to get started.
Setup Guide
Create a Cloud Project
Create a new project in the assistant-cloud dashboard and from the settings page, copy:
- Frontend API URL:
https://proj-[ID].assistant-api.com - Assistant Cloud API Key:
sk_aui_proj_*
Configure Environment Variables
Add the following environment variables to your project:
# Frontend API URL from your cloud project settings
NEXT_PUBLIC_ASSISTANT_BASE_URL=https://proj-[YOUR-ID].assistant-api.com
# API key for server-side operations
ASSISTANT_API_KEY=your-api-key-hereInstall Dependencies
Install the required packages:
npm install @assistant-ui/react @assistant-ui/react-ai-sdkSet Up the Cloud Runtime
Create a client-side AssistantCloud instance and integrate it with your AI SDK runtime:
"use client";
import { useMemo } from "react";
import { AssistantCloud, AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
import { ThreadList } from "@/components/assistant-ui/thread-list";
import { Thread } from "@/components/assistant-ui/thread";
export default function ChatPage() {
const cloud = useMemo(
() =>
new AssistantCloud({
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
anonymous: true, // Creates browser-session based user ID
}),
[],
);
const runtime = useChatRuntime({
cloud,
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<div className="grid h-dvh grid-cols-[200px_1fr] gap-x-2 px-4 py-4">
<ThreadList />
<Thread />
</div>
</AssistantRuntimeProvider>
);
}useChatRuntime Options
cloud?: AssistantCloudOptional AssistantCloud instance for chat persistence and thread management.
adapters?: RuntimeAdaptersOptional runtime adapters to extend or override built-in functionality.
RuntimeAdaptersattachments?: AttachmentAdapterCustom attachment adapter for file uploads. Defaults to the Vercel AI SDK attachment adapter.
speech?: SpeechSynthesisAdapterAdapter for text-to-speech functionality.
dictation?: DictationAdapterAdapter for speech-to-text dictation input.
feedback?: FeedbackAdapterAdapter for collecting user feedback on messages.
history?: ThreadHistoryAdapterAdapter for loading and saving thread history. Used to restore previous messages when switching threads.
toCreateMessage?: (message: AppendMessage) => CreateUIMessageOptional custom function to convert an assistant-ui AppendMessage into an AI SDK CreateUIMessage before sending. Use this to customize how outgoing messages are formatted, for example to add custom metadata or transform content parts.
transport?: ChatTransportCustom transport implementation. Defaults to AssistantChatTransport which sends requests to '/api/chat'.
Telemetry
The useChatRuntime hook captures full run telemetry including timing data. This integrates with the assistant-ui runtime to provide:
Automatically captured:
status—"completed","incomplete", or"error"duration_ms— Total run duration (measured client-side)steps— Per-step breakdowns with timing, usage, and tool callstool_calls— Tool invocations with name, arguments, results, and sourcetotal_steps— Number of reasoning/tool stepsoutput_text— Full response text (truncated at 50K characters)
Requires route configuration:
model_id— The model usedinput_tokens/output_tokens— Token usage statistics
To capture model and usage data, add the messageMetadata callback to your AI SDK route:
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o-mini"),
messages,
});
return result.toUIMessageStreamResponse({
messageMetadata: ({ part }) => {
if (part.type === "finish") {
return {
usage: part.totalUsage,
};
}
if (part.type === "finish-step") {
return {
modelId: part.response.modelId,
};
}
return undefined;
},
});
}Without this configuration, model and token data will be omitted from telemetry reports.
Customizing Reports
Use the beforeReport hook to add custom metadata or filter reports:
const cloud = new AssistantCloud({
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
anonymous: true,
telemetry: {
beforeReport: (report) => ({
...report,
metadata: { userTier: "pro", region: "us-east" },
}),
},
});Return null from beforeReport to skip reporting a specific run. To disable telemetry entirely, pass telemetry: false.
Sub-Agent Model Tracking
When tool calls delegate to a different model (e.g., the main run uses GPT but a tool invokes Gemini), you can track the delegated model's usage. Pass sampling call data through messageMetadata.samplingCalls in your API route, and the telemetry reporter will automatically include it in the report.
See the AI SDK Telemetry guide for the full setup with createSamplingCollector and wrapSamplingHandler.
Authentication
The example above uses anonymous mode (browser session-based user ID) via the env var. For production apps with user accounts, pass an explicit cloud instance:
import { useMemo } from "react";
import { useAuth } from "@clerk/nextjs";
import { AssistantCloud } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
function Chat() {
const { getToken } = useAuth();
const cloud = useMemo(() => new AssistantCloud({
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
authToken: async () => getToken({ template: "assistant-ui" }),
}), [getToken]);
const runtime = useChatRuntime({ cloud });
// ...
}See the Cloud Authorization guide for other auth providers.
Complete Example
Check out the with-cloud example on GitHub for a fully working implementation.