Client

The client connects your frontend to the Better Agent HTTP handler. Pass your server app type to keep agent names, context, memory helpers, and client tool handlers aligned with the backend.

Create a client

import { createClient } from "@better-agent/client";
import type app from "@/better-agent/server";

export const client = createClient<typeof app>({
  baseURL: "/api/agents",
});

baseURL should point to the route where your app handler is mounted.

Run an agent

Use client.agent("name") to call a specific agent.

const result = await client.agent("support").run({
  messages: [{ role: "user", content: "My order is late." }],
});

console.log(result.outcome);

Use stream when you want events as the run progresses.

import { EventType } from "@better-agent/core";

for await (const event of client.agent("support").stream({
  messages: [{ role: "user", content: "Summarize this thread." }],
})) {
  if (event.type === EventType.TEXT_MESSAGE_CONTENT) {
    appendText(event.delta);
  }
}

See Events for AG-UI event details.

React

Use the React hook for chat state, streaming, and interrupts.

"use client";

import { useAgent } from "@better-agent/client/react";
import { client } from "@/better-agent/client";

export function SupportChat() {
  const agent = useAgent(client.agent("support"), {
    threadId: "main",
  });

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const form = new FormData(event.currentTarget);
        agent.sendMessage(String(form.get("message") ?? ""));
        event.currentTarget.reset();
      }}
    >
      {agent.messages.map((message) => (
        <p key={message.id}>{message.role}</p>
      ))}

      <input name="message" disabled={agent.isRunning} />
      <button disabled={agent.isRunning}>Send</button>
    </form>
  );
}

The hook returns messages, state, status, isRunning, sendMessage, stop, resume, and interrupt helpers.

Framework hooks

Framework adapters are available from sub-paths:

FrameworkImport
React@better-agent/client/react
Vue@better-agent/client/vue
Svelte@better-agent/client/svelte
Solid@better-agent/client/solid
Preact@better-agent/client/preact

Each adapter exposes framework-native state for messages, streaming, state, threads, and interrupts.

Auth

Use headers for bearer tokens or API keys.

export const client = createClient<typeof app>({
  baseURL: "/api/agents",
  headers: async () => ({
    Authorization: `Bearer ${await getToken()}`,
  }),
});

Use credentials: "include" for cookie-based auth.

export const client = createClient<typeof app>({
  baseURL: "/api/agents",
  credentials: "include",
});

Use prepareRequest when you need to rewrite the request before fetch.

const client = createClient<typeof app>({
  baseURL: "/api/agents",
  prepareRequest(request) {
    request.headers.set("x-request-id", crypto.randomUUID());
    return request;
  },
});

See Auth for server-side identity resolution.

Client tools

Client tool handlers run in the user's app.

const agent = useAgent(client.agent("support"), {
  toolHandlers: {
    confirm_address: async (input) => {
      const confirmed = await openAddressDialog(input);
      return { confirmed };
    },
  },
});

When a matching handler exists, the hook resolves the client tool and resumes the run automatically. Without a handler, pendingClientTools lets your UI show what input is waiting for a handler.

See Tools for client tools.

Approvals

The hook exposes pending approval interrupts.

async function approveAll() {
  for (const approval of agent.pendingToolApprovals) {
    await agent.approveToolCall(approval.interruptId);
  }
}

Use rejectToolCall to deny a pending approval. See Human in the Loop for approval setup.

Memory

Pass threadId to continue a saved conversation.

await client.agent("support").run({
  threadId: "thread_123",
  messages: [{ role: "user", content: "Continue our last chat." }],
});

When memory is configured, the client exposes thread and message helpers.

const threads = await client.agent("support").memory.threads.list();
const messages = await client
  .agent("support")
  .memory.messages.list("thread_123", { limit: 20 });

See Memory for setup.

Runs

Abort an active run from the client.

await client.agent("support").abort("run_123");

Resume a stored stream by run id.

for await (const event of client.agent("support").resumeStream({
  runId: "run_123",
  afterSequence: 42,
})) {
  console.log(event.type);
}

Stream resume requires storage for run and stream records. See Storage for setup and Drizzle, Kysely, Prisma, or Redis for production adapters.