Back to Copilotkit

defineBotTool

showcase/shell-docs/src/content/reference/bot/functions/defineBotTool.mdx

1.60.14.8 KB
Original Source

Overview

defineBotTool defines a BotTool with full type inference: the handler's args are inferred from the parameters schema. A BotTool is forwarded to the agent as a frontend tool; when the agent calls it, the handler runs in the bot, with the conversation's Thread in scope — so a tool can read the thread, post JSX cards, or block on a human choice.

Signature

ts
import { defineBotTool } from "@copilotkit/bot";

function defineBotTool<Schema extends ObjectSchema>(
  tool: BotTool<Schema>,
): BotTool<Schema>;

Parameters

<PropertyReference name="tool" type="BotTool<Schema>" required> The tool definition. <PropertyReference name="name" type="string" required> Tool name the agent calls, e.g. `"read_thread"`. </PropertyReference> <PropertyReference name="description" type="string" required> What the tool does — written for the model. This is the main lever for when the agent reaches for the tool. </PropertyReference> <PropertyReference name="parameters" type="ObjectSchema" required> Any Standard Schema object schema (Zod, Valibot, ArkType, …). Converted to JSON Schema for the LLM, and used to validate the args on the way back in. </PropertyReference> <PropertyReference name="handler" type="(args, ctx: BotToolContext) => Promise<unknown> | unknown" required> Runs in the bot when the agent calls the tool. `args` is inferred from `parameters`. The return value is what the **agent (LLM) reads back** as the tool result: a `string` is sent as-is, `null`/`undefined` becomes an empty string, anything else is JSON-stringified automatically. Return something meaningful — the data itself for a data tool, a short confirmation (e.g. `"Displayed the issue card."`) for a tool that posts UI, or the actual error text on failure so the model can repair and retry. </PropertyReference> </PropertyReference>

BotToolContext

The single shared context every handler receives — there are no per-adapter generics:

<PropertyReference name="thread" type="Thread" required> The conversation the tool call belongs to. Platform power is reached only through capability-gated [`Thread`](/reference/bot/classes/Thread) methods (`getMessages`, `lookupUser`, `postFile`, `post`, …), which keeps tools portable across surfaces. </PropertyReference> <PropertyReference name="message" type="IncomingMessage"> The triggering message, when the adapter supplies it. </PropertyReference> <PropertyReference name="user" type="PlatformUser"> The requesting user, when the adapter supplies it. </PropertyReference> <PropertyReference name="signal" type="AbortSignal"> Abort signal for long-running handlers, when the adapter supplies one. </PropertyReference> <PropertyReference name="platform" type="string" required> The active surface, e.g. `"slack"`. </PropertyReference>

Usage

A data tool

tsx
import { defineBotTool } from "@copilotkit/bot";
import { z } from "zod";

const readThread = defineBotTool({
  name: "read_thread",
  description: "Read the messages in the current conversation.",
  parameters: z.object({}),
  async handler(_args, { thread }) {
    return await thread.getMessages();
  },
});

A render tool (posts JSX)

A common pattern: the component's prop schema doubles as the tool's input schema, and the agent "renders" by calling the tool.

tsx
const issueCard = defineBotTool({
  name: "issue_card",
  description: "Render one issue as a rich card.",
  parameters: issueCardSchema,
  async handler(props, { thread }) {
    await thread.post(<IssueCard {...props} />);
    return "Displayed the issue card to the user.";
  },
});

A blocking human-in-the-loop tool

tsx
const confirmWrite = defineBotTool({
  name: "confirm_write",
  description: "Ask the user to approve a write before performing it.",
  parameters: z.object({ action: z.string() }),
  async handler({ action }, { thread }) {
    const choice = await thread.awaitChoice<{ confirmed: boolean }>(
      <ConfirmWrite action={action} />,
    );
    return choice ?? { confirmed: false }; // serialized for the agent automatically
  },
});

Behavior

  • Validation — args coming back from the model are validated against parameters; invalid args don't reach your handler.
  • Registration — pass tools via createBot({ tools }) or bot.tool(t); per-run extras go through thread.runAgent({ tools }). Tools must be registered before start().
  • Portability — handlers receive the same BotToolContext on every platform; a tool written against thread methods runs unchanged on any adapter that supports them.