Skip to content

AI SDK UI (useChat)

Runcell speaks the AI SDK UI Message Stream protocol, so useChat consumes a runcell agent exactly as it consumes a streamText backend. No adapter is needed.

Backend

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

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

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

Frontend

tsx
import { useChat } from '@ai-sdk/react';

export function Chat() {
  const { messages, sendMessage } = useChat(); // posts to /api/chat by default
  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.parts.map((part, i) =>
            part.type === 'text' ? <span key={i}>{part.text}</span> : null,
          )}
        </div>
      ))}
    </>
  );
}

Messages carry the full part structure: text, reasoning, and tool-* parts with their input, output, and state. message.metadata carries the run's usage (token counts, costUsd, costMeasured) and sessionId from the finish chunk.

Controlling what crosses the wire

toUIMessageStreamResponse accepts the same option names as the AI SDK's, plus tool redaction:

ts
return agent.stream({ messages }).toUIMessageStreamResponse({
  sendReasoning: false, // keep thinking server-side
  sendTools: 'names-only', // tool names without payloads
  onError: error => 'Something went wrong.', // masked by default
});

See wire-level controls for the full option reference, and threads for durable server-side conversation state as an alternative to replaying messages.