A chat API route and useChat
A chat route receives UI messages from the browser, runs the agent, and returns a streamed response that useChat in React turns into live messages.
import { createAgentUIStreamResponse } from "ai";
import { deskAgent } from "./agent.ts";
// In Next.js this is app/api/chat/route.ts.
export async function POST(request: Request) {
const { messages } = await request.json();
return createAgentUIStreamResponse({ agent: deskAgent, uiMessages: messages });
}POST is a standard Web handler: a Request in, a Response out, the shape Next.js route handlers, Hono and other frameworks use. createAgentUIStreamResponse converts the UI messages to model messages, runs the agent with streaming, and returns the UI message stream.
import { POST } from "./route.ts";
const body = { messages: [{ id: "m1", role: "user", parts: [{ type: "text", text: "Where is A-1001?" }] }] };
const response = await POST(new Request("http://localhost/api/chat", { method: "POST", body: JSON.stringify(body) }));
console.log(response.status, response.headers.get("content-type"));
for (const line of (await response.text()).split("\n").filter(Boolean)) {
console.log(line);
}npx tsx call-route.tsCalling the handler directly shows exactly what the browser receives: server-sent events, each a JSON part. The tool call arrives as tool-input-available and tool-output-available, then the text as deltas, so the page can show the lookup before the answer is written.
The page
"use client";
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
export default function Chat() {
const { messages, sendMessage, status } = useChat();
const [input, setInput] = useState("");
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<b>{message.role}:</b>
{message.parts.map((part, i) =>
part.type === "text" ? <span key={i}>{part.text}</span>
: part.type === "tool-lookupOrder" ? <em key={i}> (looking up {part.input?.orderId}) </em>
: null,
)}
</div>
))}
<form onSubmit={(e) => { e.preventDefault(); sendMessage({ text: input }); setInput(""); }}>
<input value={input} onChange={(e) => setInput(e.target.value)} disabled={status !== "ready"} />
</form>
</div>
);
}useChat, from @ai-sdk/react, posts to /api/chat by default and keeps messages updated as parts arrive. Each message has parts: text, and one part per tool call named tool- plus the tool's name, with its input and output. status is ready when no request is running. The page is shown, not run here: it needs React and a browser.
- Send two user messages in
body.messagesand read the stream. - Ask a question without an order id and compare the events.
- In the page, show
part.outputwhenpart.stateisoutput-available.
This is what real progress feels like.