Skip to main content
Client Side Tools can enhance your agent by allowing agent to operate on the client-side.
Framework Compatibility
  • ✅ AG-Kit (TypeScript)
  • ✅ LangGraph (TypeScript)
  • ✅ LangGraph (Python)
This page assumes you already have useChat wired up (message loop, rendering). If not, start with Building Copilot Chat UI.

Defining Client Side Tools

AG-Kit SDK provides 2 ways to define client-side tools of different behaviors:
  • auto-run: define a client-side tool with handler to run and regenerate response automatically.
  • let frontend control: define a client-side tool with renderAndWaitForResponse to render a component and define when to run and regenerate response.

1) Auto-Run

When the agent calls a tool that has a handler, it automatically runs on the client once input is available. Return a string as the tool’s result, which will be sent to the agent as the handler finishes. To add an auto run client-side tool, first define a tool with handler:
import { clientTool } from '@ag-kit/ui-react';
import { z } from 'zod';

const alertUser = clientTool({
  name: "alert",
  description: "Alert the user",
  parameters: z.object({ message: z.string() }),
  handler: async ({ message }) => {
    alert(message);
    return "done"; // sent to the agent as tool result
  },
});
Then build the UI with useChat, passing the tool to the tools option:
import { useChat } from '@ag-kit/ui-react';
import { useState } from 'react';

function Chat() {
  const { uiMessages, sendMessage, streaming } = useChat({
    url,
    clientTools: [alertUser],
  });
  const [input, setInput] = useState("");

  return (
    <div>
      <div>
        {uiMessages.map((m, mi) => (
          <div key={mi}>
            <p>Role: {m.role}</p>
            {m.parts.map((p, pi) => {
              switch (p.type) {
                case "text":
                  return (
                    <p key={pi}>
                      {m.role}: {p.text}
                    </p>
                  );
                case "tool-call":
                  return (
                    <div key={pi}>
                      <p>Tool Name: {p.name}</p>
                      <p>Tool Call Arguments: {p.arguments}</p>
                      {p.result && <p>Tool Call Result: {p.result}</p>}
                    </div>
                  );
                default:
                  return null;
              }
            })}
          </div>
        ))}
      </div>

      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={() => sendMessage(input)} disabled={streaming}>
        Send
      </button>
    </div>
  );
}
Interact with the UI by sending:
Alert user about typhoon.
Then the agent will make a tool call to actually pop up an alert.

2) Let Frontend Control

This approach allows the frontend to control how the tool call is executed, including handling user interactions, rendering custom UI, and determining when and how to submit results back to the agent. This provides greater flexibility for complex interactions that require frontend logic. To add a fully controlled client-side tool, first define a tool with renderAndWaitForResponse:
import { clientTool } from '@ag-kit/ui-react';
import { z } from 'zod';

const alertUser = clientTool({
  name: "alert",
  description: "Alert the user",
  parameters: z.object({ message: z.string() }),
  renderAndWaitForResponse: ({ input, submitToolResult }) => (
    <div className="space-x-2">
      <span>{input.message}</span>
      <button
        onClick={() => {
          alert(input.message);
          submitToolResult?.("done");
        }}
      >
        Allow
      </button>
      <button onClick={() => submitToolResult?.("User refused the alert")}>
        Cancel
      </button>
    </div>
  ),
});
Then build the UI with useChat, passing the tool to the tools option. Since we just defined a custom UI for the tool call, now the tool call message part will have a render function that we can call.
import { useChat } from '@ag-kit/ui-react';
import { useState } from 'react';

function Chat() {
  const { uiMessages, sendMessage, streaming } = useChat({
    url,
    clientTools: [alertUser],
  });
  const [input, setInput] = useState("");

  return (
    <div>
      <div>
        {uiMessages.map((m, mi) => (
          <div key={mi}>
            <p>Role: {m.role}</p>
            {m.parts.map((p, pi) => {
              switch (p.type) {
                case "text":
                  return (
                    <p key={pi}>
                      {m.role}: {p.text}
                    </p>
                  );
                case "tool-call":
                  return p.render?.();
                default:
                  return null;
              }
            })}
          </div>
        ))}
      </div>

      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={() => sendMessage(input)} disabled={streaming}>
        Send
      </button>
    </div>
  );
}
Interact with the UI by sending:
Alert user about typhoon.
Then the agent will make a tool call. What’s different from the auto-run approach is that now we render a custom UI for the tool call, and after the user interacts with the UI to control how the tool call is executed.

Choosing an approach

  • Start simple: Use a handler when no UI is needed.
  • Need user input: Use renderAndWaitForResponse to render UI and submit a result.