showcase/shell-docs/src/content/reference/bot/functions/defineBotTool.mdx
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.
import { defineBotTool } from "@copilotkit/bot";
function defineBotTool<Schema extends ObjectSchema>(
tool: BotTool<Schema>,
): BotTool<Schema>;
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>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 common pattern: the component's prop schema doubles as the tool's input schema, and the agent "renders" by calling the tool.
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.";
},
});
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
},
});
parameters; invalid args don't reach your handler.createBot({ tools }) or bot.tool(t); per-run extras go through thread.runAgent({ tools }). Tools must be registered before start().BotToolContext on every platform; a tool written against thread methods runs unchanged on any adapter that supports them.ctx.thread