Wire an AI SDK route
useChatRuntime sends assistant-ui’s model context with every request: its transport puts system and tools in the body. The route hands both to the model.
In the browser
Section titled “In the browser”Pass the runtime to AssistedProvider. Set sendAutomaticallyWhen so a finished tool call sends its result back and the model continues:
"use client";
import type { ReactNode } from "react";import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";import { useChatRuntime } from "@assistant-ui/ai-sdk";import { AssistedProvider } from "@super-assist/react";
export function Assistant({ children }: { children: ReactNode }) { const runtime = useChatRuntime({ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, }); return <AssistedProvider runtime={runtime}>{children}</AssistedProvider>;}In the route
Section titled “In the route”Read system and tools from the body. Pass system to the model and frontendTools(tools) as its tools:
import { createOpenAI } from "@ai-sdk/openai";import { frontendTools, type FrontendTools } from "@assistant-ui/ai-sdk";import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, type UIMessage,} from "ai";
type Body = { messages: UIMessage[]; system?: string; tools?: FrontendTools };
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function POST(request: Request) { const { messages, system, tools }: Body = await request.json(); const result = streamText({ model: openai.responses("gpt-6-luna"), system, messages: await convertToModelMessages(messages), tools: frontendTools(tools ?? {}), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) });}useChatRuntime posts to /api/chat by default. Each request carries the model context as it is when it is sent, so a request after a page change carries the new page’s tools.
The example in the repo runs on Cloudflare Agents. This setup is type-checked against the same packages, not walked in a browser.