showcase/shell-docs/src/content/docs/channels/commands-and-reactions.mdx
Two channel-level shortcuts that are not tied to a message you rendered: slash commands the user types, and reactions on any message in the channel.
A command runs when the user types /name. Define it with
defineChannelCommand and wire it into the channel.
import { defineChannelCommand } from "@copilotkit/channels";
export const commands = [
defineChannelCommand({
name: "ask", // no leading slash, matched case-insensitively
description: "Ask the agent anything, no mention needed.",
async handler({ thread, text, user }) {
if (!text) {
await thread.post("Usage: `/ask <your question>`");
return;
}
await thread.runAgent({ prompt: text });
},
}),
];
import { createChannel } from "@copilotkit/channels";
import { agent } from "./agent";
import { commands } from "./commands";
const channel = createChannel({
name: "support-bot",
agent,
commands,
});
The user's words after the command arrive as text:
/ask how do I reset my password
-> text = "how do I reset my password"
Platforms with native structured arguments (Discord) can parse args for you.
Add an options schema, and read options in the handler:
import { z } from "zod";
defineChannelCommand({
name: "weather",
description: "Get the weather for a city.",
options: z.object({ city: z.string() }),
async handler({ thread, options }) {
await thread.runAgent({ prompt: `What's the weather in ${options.city}?` });
},
});
On text-only surfaces options is empty and the raw string is in text, so
handle both if a command targets several platforms.
The command handler also has thread.postEphemeral(...) for a reply only the
caller sees, and openModal(...) on platforms that support dialogs.
channel.onReaction fires when a user reacts to any message in the channel,
not just a card you rendered. Use it for channel-wide shortcuts, like a
refresh emoji that re-runs the last request.
channel.onReaction(async ({ added, emoji, thread, user }) => {
if (!added) return; // ignore un-reactions
if (emoji !== "refresh") return; // 🔄
await thread.post(`On it, ${user?.name ?? "there"}.`);
});
Scope it to one emoji so you skip the check:
channel.onReaction("fire", async ({ thread }) => {
await thread.post("🔥");
});
The event carries emoji (the canonical name), rawEmoji (the platform
token), added, user, thread, and messageRef for
thread.update(messageRef, ...).