TanStack Start
Use Better Agent in TanStack Start with a server route and the React useAgent
hook.
Server
// src/lib/better-agent/server.ts
import { betterAgent, defineAgent } from "@better-agent/core";
import { openai } from "@better-agent/openai";
const supportAgent = defineAgent({
name: "support",
model: openai("gpt-5.5"),
instruction: "You help customers.",
});
const app = betterAgent({
agents: [supportAgent],
basePath: "/api/agents",
});
export default app;Route
// src/routes/api/agents/$.ts
import { createFileRoute } from "@tanstack/react-router";
import app from "@/lib/better-agent/server";
export const Route = createFileRoute("/api/agents/$")({
server: {
handlers: {
GET: ({ request }) => app.handler(request),
POST: ({ request }) => app.handler(request),
PUT: ({ request }) => app.handler(request),
PATCH: ({ request }) => app.handler(request),
DELETE: ({ request }) => app.handler(request),
OPTIONS: ({ request }) => app.handler(request),
HEAD: ({ request }) => app.handler(request),
},
},
});Client
// src/lib/better-agent/client.ts
import { createClient } from "@better-agent/client";
import type app from "./server";
export const client = createClient<typeof app>({
baseURL: "/api/agents",
});Basic chat
import { createFileRoute } from "@tanstack/react-router";
import { useAgent } from "@better-agent/client/react";
import { useState } from "react";
import { client } from "../lib/better-agent/client";
export const Route = createFileRoute("/")({
component: ChatPage,
});
function ChatPage() {
const [input, setInput] = useState("");
const agent = useAgent(client.agent("support"), {
threadId: "main",
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
const message = input.trim();
if (!message) return;
setInput("");
agent.sendMessage(message);
}}
>
{agent.messages.map((message) => (
<p key={message.id}>{message.role}</p>
))}
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button disabled={agent.isRunning}>Send</button>
<button type="button" onClick={() => agent.stop()} disabled={!agent.isRunning}>
Stop
</button>
</form>
);
}Threads
const agent = useAgent(client.agent("support"), {
threadId: "customer-123",
});
async function switchThread() {
await agent.loadMessages();
await agent.selectThread("customer-456");
}Context
const agent = useAgent(client.agent("support"), {
context: {
workspaceId: "workspace_123",
},
});Client tools
const agent = useAgent(client.agent("support"), {
toolHandlers: {
confirm_address: async (input) => {
const confirmed = window.confirm(input.address);
return { confirmed };
},
},
});Approvals
async function approveAll() {
for (const approval of agent.pendingToolApprovals) {
await agent.approveToolCall(approval.interruptId);
}
}Events
import { EventType } from "@better-agent/core";
const agent = useAgent(client.agent("support"), {
onEvent(event) {
if (event.type === EventType.TEXT_MESSAGE_CONTENT) {
console.log(event.delta);
}
},
});Finish and errors
const agent = useAgent(client.agent("support"), {
onFinish(finish) {
console.log(finish.runId, finish.isInterrupted);
},
onError(error) {
console.error(error.code, error.message);
},
});Next
See Client, Tools, Human in the Loop, Memory, Events, and Storage.