Skip to content

assistant-ui ​

assistant-ui renders runcell agents with zero glue: runcell speaks the AI SDK UI Message Stream protocol that assistant-ui's useChatRuntime consumes.

Backend ​

One route handler:

ts
// app/api/chat/route.ts
import { createAgent } from 'runcell';

const agent = createAgent({
  model: 'anthropic/claude-sonnet-4-5',
  tools: {/* host tools */},
});

export async function POST(req: Request): Promise<Response> {
  const { messages } = await req.json();
  return agent
    .stream({ messages, signal: req.signal })
    .toUIMessageStreamResponse();
}

Frontend ​

Follow assistant-ui's getting started for components and styling. The runtime side is the default AI SDK setup:

tsx
import { AssistantRuntimeProvider } from '@assistant-ui/react';
import { useChatRuntime } from '@assistant-ui/react-ai-sdk';

export function Chat() {
  const runtime = useChatRuntime(); // posts to /api/chat by default
  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* assistant-ui components, e.g. <Thread /> */}
    </AssistantRuntimeProvider>
  );
}

What renders ​

  • Text and reasoning stream live. assistant-ui shows reasoning as a collapsible block.
  • Tool calls appear as tool parts and change to output-available when the tool returns. Register per-tool UI components in assistant-ui to customize rendering. Runcell-internal tools (submitResult, fileChange) never reach the wire.
  • Repair turns arrive as additional steps of the same assistant message.
  • Usage (token counts, costUsd, costMeasured) rides the finish chunk's messageMetadata, so the UI can show per-message cost.
  • Failures end the stream with an in-band error chunk. The message is masked by default (see below).
  • Attachments on the sent message (file parts with data URLs) are seeded into the run workspace under attachments/, and the agent reads them with its file tools. With the optional peers pdfjs-dist and @napi-rs/canvas installed, PDF attachments get a built-in readPdfPages tool that renders pages as images the model can view. See messages input for limits.

Controlling what crosses the wire ​

Anything sent to the browser is visible in its network inspector, regardless of what the UI renders. Redaction therefore happens server-side, on the response:

ts
return agent.stream({ messages }).toUIMessageStreamResponse({
  sendReasoning: false,
  sendTools: { queryBilling: false, weather: 'names-only' },
  onError: () => 'Something went wrong.',
});

See wire-level controls for the full option reference.

Server-side conversation state ​

messages replays the client-held history on every request, which suits assistant-ui's default model. To keep durable server-side state instead, use threads with prompt and persist thread.toJSON(). The chat agent guide shows the full pattern.