Skip to content

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.

Pass the runtime to AssistedProvider. Set sendAutomaticallyWhen so a finished tool call sends its result back and the model continues:

app/assistant.tsx
"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>;
}

Read system and tools from the body. Pass system to the model and frontendTools(tools) as its tools:

app/api/chat/route.ts
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.