Docs

React UI

Tools end to end

Define an Anvia tool, stream tool-call parts, and render default or custom tool cards.

Tool calls are streamed as assistant message parts. Start with the default renderer, then customize only the tool cards that need product-specific UI.

Define the tool on the server

import { AgentBuilder, createTool, type UIStreamRequest } from "@anvia/core";
import { OpenAIClient } from "@anvia/openai";
import { createEventStream } from "@anvia/server";
import { z } from "zod";

const lookupOrder = createTool({
  name: "lookup_order",
  description: "Look up a customer order by id.",
  input: z.object({
    orderId: z.string(),
  }),
  output: z.object({
    orderId: z.string(),
    status: z.string(),
    summary: z.string(),
  }),
  execute: ({ orderId }) => ({
    orderId,
    status: "blocked",
    summary: "The order is waiting for warehouse allocation.",
  }),
});

const client = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY });
const model = client.completionModel("gpt-5");
const agent = new AgentBuilder("orders", model)
  .instructions("Use lookup_order when the user asks about an order.")
  .tool(lookupOrder)
  .build();

export async function POST(request: Request) {
  const body = (await request.json()) as UIStreamRequest;

  return createEventStream(agent.prompt(body.messages).stream(), {
    format: "jsonl",
  });
}

Render default tool parts

If you use Thread.Messages or Message.Parts without branching, tool calls already render.

<Thread.Messages className="messages" />

Or inside an app-owned row:

<Message.Content>
  <Message.Parts />
</Message.Content>

Render a custom tool card

Branch only for part.type === "tool" and keep <Message.Part /> as the fallback.

<Message.Parts>
  {(part) =>
    part.type === "tool" ? (
      <Message.Part>
        <Message.Tool className="tool-card" renderWhen="always">
          <header className="tool-card-header">
            <Message.ToolName />
            <Message.ToolStatus />
          </header>
          <section>
            <h3>Input</h3>
            <Message.ToolInput />
          </section>
          <section>
            <h3>Result</h3>
            <Message.ToolOutput />
            <Message.ToolError />
          </section>
        </Message.Tool>
      </Message.Part>
    ) : (
      <Message.Part />
    )
  }
</Message.Parts>

Show only pending tools

Use this for an activity strip while the main transcript hides noisy work.

<Message.Parts filter={(part) => part.type === "tool" && part.state !== "output-available"}>
  <Message.Part>
    <Message.Tool renderWhen="pending" className="tool-activity" />
  </Message.Part>
</Message.Parts>

Show only completed tools

Use this when tool input should stay hidden until there is a result.

<Message.Parts
  filter={(part) => part.type !== "tool" || part.state === "output-available"}
/>

Tool part states

State Meaning Typical UI
input-streaming Tool input is still arriving. Skeleton or “preparing”.
input-available Tool input is complete. Pending card.
output-available Tool result is available. Completed card.
error Tool execution failed. Error panel.

The tool UI is display only. Authorization, validation, retries, and idempotency belong inside the server-side tool implementation.