discord
The Discord platform adapter factory — Gateway ingress via discord.js, Components V2 egress, streaming replies, and slash-command registration.
Overview
discord(opts) returns a DiscordAdapter — the Discord implementation of the engine's PlatformAdapter boundary. It handles ingress via discord.js (Gateway WebSocket), renders the JSX vocabulary to Discord's Components V2 within per-message budgets, streams agent replies by editing the reply message in place, and registers slash commands on startup. @copilotkit/bot-discord is the only package in the stack that talks to Discord.
For app creation, tokens, and first run, see the Discord quickstart.
Signature
import { discord } from "@copilotkit/bot-discord";
function discord(opts: DiscordAdapterOptions): DiscordAdapter;Parameters
Prop
Type
Return Value
A DiscordAdapter to pass into createBot({ adapters }). It advertises platform: "discord", ackDeadlineMs: 3000, and:
capabilities: {
supportsStreaming: true,
supportsModals: false,
supportsTyping: true,
supportsReactions: true,
maxBlocksPerMessage: 40,
}The capability-gated Thread methods are all backed: getMessages() via channel history fetch, lookupUser(query) via guild member search, and postFile(...) via attachment upload. Inbound file attachments are downloaded and delivered to the agent as multimodal content parts.
Usage
import { createBot } from "@copilotkit/bot";
import { discord, defaultDiscordTools, defaultDiscordContext } from "@copilotkit/bot-discord";
const bot = createBot({
adapters: [
discord({
botToken: process.env.DISCORD_BOT_TOKEN!, // Bot …
appId: process.env.DISCORD_APP_ID!,
guildId: process.env.DISCORD_GUILD_ID, // omit for global commands
}),
],
agent: (threadId) => makeAgent(threadId),
tools: [...defaultDiscordTools, ...appTools],
context: [...defaultDiscordContext, ...appContext],
});Behavior
Ingress
The Discord listener pre-filters Gateway events to the turns the bot should answer — @-mentions in guild channels, replies in threads it owns, and DMs — so a single onMention handler covers the common cases. Conversation history is rebuilt from Discord (channel messages fetch) on every turn: Discord is the source of truth, so bot restarts don't lose conversations.
Slash Commands
On bot.start() the adapter calls the Discord REST API to register (or update) slash commands. Guild-scoped registration (guildId) is instant; global registration propagates within an hour. Commands are defined via defineBotCommand and run through the same agent pipeline as @-mention turns.
Streaming
thread.runAgent() and thread.stream(...) send an initial reply and then edit it in place as text arrives. Edit calls are queued per message with a minimum gap between flushes (default 1100 ms); long replies roll over into follow-up messages at a soft 1900-character limit (under Discord's 2000), breaking at the last newline or space and keeping fenced code blocks whole. Dangling markdown is auto-closed on each flush so the in-flight message always renders.
Components V2
When the reply contains interactive components (<Button>, <Select>, etc.) the adapter renders them using Discord's Components V2 system (available in supported channels). renderComponents maps the JSX component tree to the Discord components payload, and DISCORD_LIMITS documents the per-message budgets. The IS_COMPONENTS_V2_ENABLED flag on the message flags field must be set; the adapter sets it automatically.
Gateway Intents
The intents are fixed (not configurable): the client requests Guilds, GuildMessages, MessageContent (privileged), DirectMessages, and GuildMembers (privileged). Two of these are privileged and must be enabled in the Discord Developer Portal under Bot → Privileged Gateway Intents:
MessageContent— required to read the body of messages the bot receives (otherwise only the author and channel metadata are visible).GuildMembers— required for user lookup; it powersThread'slookupUser(query), which resolves members viaguild.members.search.
If either privileged intent is left disabled in the portal, the bot will fail to receive message content or to look up users, respectively.
Interactions (ack-first)
Every interaction (button click, select menu choice) is acknowledged within Discord's 3-second deadline (ackDeadlineMs: 3000), then handled asynchronously. decodeInteraction extracts the opaque minted id (ck:…), the component's value, and the message ref — those are the only things that ride in the Discord payload; handler code, other props, and bind() args stay server-side.
Initialization
The underlying discord.js Client is constructed lazily — construction is side-effect-free, and bot.start() owns initialization (Gateway login + an awaited ready event to resolve the bot's own user id), so auth and config errors surface to the caller instead of firing in the background.
What's NOT in v1
- True modal / multi-step form flows
- OAuth / multi-server bot installation (single bot token only)
- Durable (Redis/DB)
ActionStore— in-memory only; actions expire on restart - Proactive posting (the bot replies only to turns it's part of)
- Voice channel support
Related
- renderComponents — the JSX → Components V2 mapping and per-element budgets
- defaultDiscordTools / defaultDiscordContext — the shipped built-ins
- DISCORD_LIMITS — per-message component and character budgets
- Discord quickstart — app creation, token setup, first run