@copilotkit/channels-discord 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/__tests__/ephemeral.test.d.ts +2 -0
- package/dist/__tests__/ephemeral.test.d.ts.map +1 -0
- package/dist/__tests__/ephemeral.test.js +26 -0
- package/dist/__tests__/intents.test.d.ts +2 -0
- package/dist/__tests__/intents.test.d.ts.map +1 -0
- package/dist/__tests__/intents.test.js +8 -0
- package/dist/__tests__/modal-submit.test.d.ts +2 -0
- package/dist/__tests__/modal-submit.test.d.ts.map +1 -0
- package/dist/__tests__/modal-submit.test.js +26 -0
- package/dist/__tests__/reactions.test.d.ts +2 -0
- package/dist/__tests__/reactions.test.d.ts.map +1 -0
- package/dist/__tests__/reactions.test.js +87 -0
- package/dist/adapter.d.ts +118 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +567 -0
- package/dist/adapter.test.d.ts +2 -0
- package/dist/adapter.test.d.ts.map +1 -0
- package/dist/adapter.test.js +343 -0
- package/dist/auto-close-streaming.d.ts +61 -0
- package/dist/auto-close-streaming.d.ts.map +1 -0
- package/dist/auto-close-streaming.js +276 -0
- package/dist/auto-close-streaming.test.d.ts +2 -0
- package/dist/auto-close-streaming.test.d.ts.map +1 -0
- package/dist/auto-close-streaming.test.js +149 -0
- package/dist/built-in-context.d.ts +20 -0
- package/dist/built-in-context.d.ts.map +1 -0
- package/dist/built-in-context.js +30 -0
- package/dist/built-in-context.test.d.ts +2 -0
- package/dist/built-in-context.test.d.ts.map +1 -0
- package/dist/built-in-context.test.js +21 -0
- package/dist/built-in-tools.d.ts +4 -0
- package/dist/built-in-tools.d.ts.map +1 -0
- package/dist/built-in-tools.js +27 -0
- package/dist/built-in-tools.test.d.ts +2 -0
- package/dist/built-in-tools.test.d.ts.map +1 -0
- package/dist/built-in-tools.test.js +30 -0
- package/dist/chunked-message-stream.d.ts +94 -0
- package/dist/chunked-message-stream.d.ts.map +1 -0
- package/dist/chunked-message-stream.js +203 -0
- package/dist/chunked-message-stream.test.d.ts +2 -0
- package/dist/chunked-message-stream.test.d.ts.map +1 -0
- package/dist/chunked-message-stream.test.js +384 -0
- package/dist/commands.d.ts +29 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +193 -0
- package/dist/commands.test.d.ts +2 -0
- package/dist/commands.test.d.ts.map +1 -0
- package/dist/commands.test.js +281 -0
- package/dist/conversation-store.d.ts +45 -0
- package/dist/conversation-store.d.ts.map +1 -0
- package/dist/conversation-store.js +78 -0
- package/dist/conversation-store.test.d.ts +2 -0
- package/dist/conversation-store.test.d.ts.map +1 -0
- package/dist/conversation-store.test.js +106 -0
- package/dist/discord-listener.d.ts +88 -0
- package/dist/discord-listener.d.ts.map +1 -0
- package/dist/discord-listener.js +118 -0
- package/dist/discord-listener.test.d.ts +2 -0
- package/dist/discord-listener.test.d.ts.map +1 -0
- package/dist/discord-listener.test.js +361 -0
- package/dist/download-files.d.ts +94 -0
- package/dist/download-files.d.ts.map +1 -0
- package/dist/download-files.js +145 -0
- package/dist/download-files.test.d.ts +2 -0
- package/dist/download-files.test.d.ts.map +1 -0
- package/dist/download-files.test.js +181 -0
- package/dist/event-renderer.d.ts +36 -0
- package/dist/event-renderer.d.ts.map +1 -0
- package/dist/event-renderer.js +270 -0
- package/dist/event-renderer.test.d.ts +2 -0
- package/dist/event-renderer.test.d.ts.map +1 -0
- package/dist/event-renderer.test.js +160 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/interaction.d.ts +11 -0
- package/dist/interaction.d.ts.map +1 -0
- package/dist/interaction.js +138 -0
- package/dist/interaction.test.d.ts +2 -0
- package/dist/interaction.test.d.ts.map +1 -0
- package/dist/interaction.test.js +120 -0
- package/dist/markdown.d.ts +7 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +66 -0
- package/dist/markdown.test.d.ts +2 -0
- package/dist/markdown.test.d.ts.map +1 -0
- package/dist/markdown.test.js +47 -0
- package/dist/message-stream.d.ts +45 -0
- package/dist/message-stream.d.ts.map +1 -0
- package/dist/message-stream.js +76 -0
- package/dist/message-stream.test.d.ts +2 -0
- package/dist/message-stream.test.d.ts.map +1 -0
- package/dist/message-stream.test.js +162 -0
- package/dist/pending-interactions.d.ts +26 -0
- package/dist/pending-interactions.d.ts.map +1 -0
- package/dist/pending-interactions.js +50 -0
- package/dist/pending-interactions.test.d.ts +2 -0
- package/dist/pending-interactions.test.d.ts.map +1 -0
- package/dist/pending-interactions.test.js +73 -0
- package/dist/render/budget.d.ts +28 -0
- package/dist/render/budget.d.ts.map +1 -0
- package/dist/render/budget.js +49 -0
- package/dist/render/budget.test.d.ts +2 -0
- package/dist/render/budget.test.d.ts.map +1 -0
- package/dist/render/budget.test.js +59 -0
- package/dist/render/components-v2.d.ts +16 -0
- package/dist/render/components-v2.d.ts.map +1 -0
- package/dist/render/components-v2.js +396 -0
- package/dist/render/components-v2.test.d.ts +2 -0
- package/dist/render/components-v2.test.d.ts.map +1 -0
- package/dist/render/components-v2.test.js +395 -0
- package/dist/render/modal.d.ts +12 -0
- package/dist/render/modal.d.ts.map +1 -0
- package/dist/render/modal.js +44 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.test.d.ts +2 -0
- package/dist/types.test.d.ts.map +1 -0
- package/dist/types.test.js +12 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Atai Barkai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# @copilotkit/channels-discord
|
|
2
|
+
|
|
3
|
+
The **Discord `PlatformAdapter`** for [`@copilotkit/channels`](../channels). It connects a
|
|
4
|
+
Discord application to any AG-UI agent: ingress via discord.js (Gateway), egress
|
|
5
|
+
as Components V2 rendered from the `@copilotkit/channels-ui` JSX vocabulary, plus text
|
|
6
|
+
streaming, opaque-id interactions, and HITL.
|
|
7
|
+
|
|
8
|
+
You write your UI as JSX once (`@copilotkit/channels-ui`) and drive the bot with
|
|
9
|
+
`@copilotkit/channels`; this package is the only one that talks to Discord.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm add @copilotkit/channels-discord @copilotkit/channels @copilotkit/channels-ui
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createBot } from "@copilotkit/channels";
|
|
21
|
+
import {
|
|
22
|
+
discord,
|
|
23
|
+
defaultDiscordTools,
|
|
24
|
+
defaultDiscordContext,
|
|
25
|
+
} from "@copilotkit/channels-discord";
|
|
26
|
+
|
|
27
|
+
const bot = createBot({
|
|
28
|
+
adapters: [
|
|
29
|
+
discord({
|
|
30
|
+
botToken: process.env.DISCORD_BOT_TOKEN!, // Bot token — Gateway + REST
|
|
31
|
+
appId: process.env.DISCORD_APP_ID!, // Application ID for command registration
|
|
32
|
+
guildId: process.env.DISCORD_GUILD_ID, // Optional: instant guild-scoped dev commands
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
35
|
+
agent: (threadId) => makeAgent(threadId),
|
|
36
|
+
tools: [...defaultDiscordTools, ...appTools], // lookup_discord_user + your tools
|
|
37
|
+
context: [...defaultDiscordContext, ...appContext], // tagging/formatting/thread guidance
|
|
38
|
+
commands: [
|
|
39
|
+
{
|
|
40
|
+
name: "triage",
|
|
41
|
+
description: "Summarize the thread and propose issues.",
|
|
42
|
+
async handler({ thread, text }) {
|
|
43
|
+
await thread.runAgent({ prompt: `Triage: ${text}` });
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
bot.onMention(({ thread }) => thread.runAgent());
|
|
50
|
+
|
|
51
|
+
await bot.start();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`discord(opts)` returns a `DiscordAdapter`. The adapter connects via the
|
|
55
|
+
Discord Gateway (WebSocket) — no public URL required. The listener pre-filters
|
|
56
|
+
ingress to the turns the bot should answer (@-mentions in guild channels and
|
|
57
|
+
DMs), so a single `onMention` handler covers most use cases.
|
|
58
|
+
|
|
59
|
+
### Required env
|
|
60
|
+
|
|
61
|
+
| Var | Purpose |
|
|
62
|
+
| ------------------- | ------------------------------------------------------------------------ |
|
|
63
|
+
| `DISCORD_BOT_TOKEN` | Bot token for Gateway login and REST calls. |
|
|
64
|
+
| `DISCORD_APP_ID` | Application ID used when registering slash commands. |
|
|
65
|
+
| `DISCORD_GUILD_ID` | _(Optional)_ Guild ID for instant per-guild command registration in dev. |
|
|
66
|
+
|
|
67
|
+
> **Global commands** (no `guildId`) propagate across Discord in ~1 hour.
|
|
68
|
+
> **Guild-scoped commands** (with `guildId`) register instantly — use them
|
|
69
|
+
> during development and switch to global for production.
|
|
70
|
+
|
|
71
|
+
### Privileged intents
|
|
72
|
+
|
|
73
|
+
The adapter requests **two** privileged gateway intents — `MessageContent` and
|
|
74
|
+
`GuildMembers`. Both must be enabled in the
|
|
75
|
+
[Discord Developer Portal](https://discord.com/developers/applications)
|
|
76
|
+
(your application → Bot → Privileged Gateway Intents) or Gateway login is
|
|
77
|
+
rejected.
|
|
78
|
+
|
|
79
|
+
> - **Message Content Intent** — without it the Gateway delivers messages with
|
|
80
|
+
> an empty `content` string and the bot cannot read what users write.
|
|
81
|
+
> - **Server Members Intent** (`GuildMembers`) — backs member search, which
|
|
82
|
+
> powers `lookup_discord_user` / `thread.lookupUser`. Without it those
|
|
83
|
+
> lookups fail and login is rejected.
|
|
84
|
+
|
|
85
|
+
### Reaction intent and partials
|
|
86
|
+
|
|
87
|
+
The adapter also requests `GuildMessageReactions` and enables the
|
|
88
|
+
`Partials.Message` and `Partials.Reaction` partials:
|
|
89
|
+
|
|
90
|
+
> - **`GuildMessageReactions`** — a **non-privileged** gateway intent; no
|
|
91
|
+
> toggle is needed in the Discord Developer Portal (unlike `MessageContent`
|
|
92
|
+
> and `GuildMembers`). Required to receive reaction events in guild channels.
|
|
93
|
+
> - **`DirectMessageReactions`** — also non-privileged; required to receive
|
|
94
|
+
> reaction events in DMs. Discord.js v14 treats guild and DM reactions as
|
|
95
|
+
> separate intents — both are needed if the bot operates in DMs.
|
|
96
|
+
> - **`Partials.Message` + `Partials.Reaction`** — Discord only includes full
|
|
97
|
+
> message objects in the reaction event payload when the message is already
|
|
98
|
+
> in the client's in-memory cache. For any message that was sent before the
|
|
99
|
+
> bot started (or was evicted from cache), the payload arrives as a _partial_.
|
|
100
|
+
> Enabling these two partials lets the listener fetch the full object on
|
|
101
|
+
> demand via `reaction.fetch()` / `reaction.message.fetch()`, so reactions
|
|
102
|
+
> on older messages are not silently dropped.
|
|
103
|
+
|
|
104
|
+
## What it provides
|
|
105
|
+
|
|
106
|
+
### JSX → Components V2 rendering
|
|
107
|
+
|
|
108
|
+
`renderDiscordMessage(ir)` translates the `@copilotkit/channels-ui` vocabulary to a
|
|
109
|
+
ready-to-send Discord Components V2 payload (`{ components, flags }`) with the
|
|
110
|
+
`IS_COMPONENTS_V2` flag (`MessageFlags.IsComponentsV2`) set. It builds on
|
|
111
|
+
`renderComponents(ir)`, the lower-level building block, which returns a bare
|
|
112
|
+
`ContainerBuilder` with no flag. The entire message is wrapped in a single
|
|
113
|
+
`Container`; child nodes map as follows:
|
|
114
|
+
|
|
115
|
+
| bot-ui element | Discord output |
|
|
116
|
+
| ---------------------- | ------------------------------------------------------- |
|
|
117
|
+
| `Message` | `Container` (accent color → `setAccentColor`) |
|
|
118
|
+
| `Header` | `TextDisplay` with `# ` prefix |
|
|
119
|
+
| `Section` / `Markdown` | `TextDisplay` |
|
|
120
|
+
| `Fields` | `TextDisplay` — each field as a **bold-label** line |
|
|
121
|
+
| `Context` | `TextDisplay` — each part as a `-# subtext` line |
|
|
122
|
+
| `Actions` | One or more `ActionRow`s |
|
|
123
|
+
| `Button` | `Button` (custom_id = minted opaque `ck:` id) |
|
|
124
|
+
| `Select` | `StringSelect` (custom_id = minted opaque `ck:` id) |
|
|
125
|
+
| `Image` | `MediaGallery` with a single item |
|
|
126
|
+
| `Divider` | `Separator` |
|
|
127
|
+
| `Table` | `TextDisplay` — fenced code block via `discordMarkdown` |
|
|
128
|
+
|
|
129
|
+
### Per-element budget
|
|
130
|
+
|
|
131
|
+
Discord caps every element. The renderer degrades by truncate-with-overflow /
|
|
132
|
+
clamp — it never silently drops content. Limits live in `DISCORD_LIMITS`:
|
|
133
|
+
|
|
134
|
+
| Limit | Value | Element |
|
|
135
|
+
| ---------------------- | ----- | ------------------------------------- |
|
|
136
|
+
| `componentsPerMessage` | 40 | total (nested) components per message |
|
|
137
|
+
| `actionRows` | 5 | action rows per message |
|
|
138
|
+
| `buttonsPerRow` | 5 | buttons per action row |
|
|
139
|
+
| `selectOptions` | 25 | options per string select |
|
|
140
|
+
| `textDisplayChars` | 2000 | chars per TextDisplay |
|
|
141
|
+
| `buttonLabel` | 80 | button label chars |
|
|
142
|
+
| `customId` | 100 | `custom_id` chars |
|
|
143
|
+
| `headerText` | 256 | header line chars (`# ` TextDisplay) |
|
|
144
|
+
|
|
145
|
+
### Streaming
|
|
146
|
+
|
|
147
|
+
`thread.stream(...)` posts a plain-text placeholder and edits it in place via
|
|
148
|
+
`ChunkedMessageStream`: throttled `message.edit` calls at ~1100 ms intervals,
|
|
149
|
+
2000-char chunking, mid-stream bracket auto-close, and Markdown →
|
|
150
|
+
Discord-flavored Markdown translation so the in-flight message always renders.
|
|
151
|
+
|
|
152
|
+
### Interactions (ack-first)
|
|
153
|
+
|
|
154
|
+
Every `interactionCreate` event (button click, string select) is immediately
|
|
155
|
+
acknowledged with `deferUpdate` (within the **≤3s** deadline,
|
|
156
|
+
`ackDeadlineMs = 3000`). `decodeInteraction` then extracts the opaque minted
|
|
157
|
+
id (`ck:…`) from the `custom_id`, and hands an `InteractionEvent` to the
|
|
158
|
+
engine. The token carries only the opaque id — no props or secrets. Unrelated
|
|
159
|
+
clicks decode to events the bot harmlessly ignores.
|
|
160
|
+
|
|
161
|
+
### Human-in-the-loop
|
|
162
|
+
|
|
163
|
+
Use `thread.awaitChoice(<Picker .../>)` to post an interactive message and
|
|
164
|
+
block until a click resolves it; the resolved value is the clicked control's
|
|
165
|
+
value. Agent interrupts (`on_interrupt`) are captured by the run renderer and
|
|
166
|
+
dispatched to your `onInterrupt` handler, which posts a picker; the click
|
|
167
|
+
resumes the agent via `thread.resume(value)`.
|
|
168
|
+
|
|
169
|
+
### Modals
|
|
170
|
+
|
|
171
|
+
`openModal(view)` opens a Discord modal in response to a button click or slash
|
|
172
|
+
command. The call must happen **before any other response** (within Discord's
|
|
173
|
+
3-second acknowledgement window) — open the modal first, then do long-running
|
|
174
|
+
work in a follow-up message.
|
|
175
|
+
|
|
176
|
+
> **Validation re-open is not supported on Discord.** When a user submits a
|
|
177
|
+
> modal, a `bot.onModalSubmit` handler may return `{ errors }`, but Discord has
|
|
178
|
+
> no API to re-open the same modal with per-field validation errors (unlike
|
|
179
|
+
> Slack's `response_action: "errors"` mechanic). The `{ errors }` result is
|
|
180
|
+
> ignored by the adapter — the modal is acknowledged with `deferUpdate`
|
|
181
|
+
> regardless. Validate inputs before calling `openModal`, or post a follow-up
|
|
182
|
+
> message to report any submission errors.
|
|
183
|
+
>
|
|
184
|
+
> Only `TextInput` fields are supported in Discord modals. `ModalSelect` and
|
|
185
|
+
> `RadioButtons` elements are rejected at render time with a `ModalRenderError`
|
|
186
|
+
> (which `openModal` surfaces as `{ ok: false, error }`).
|
|
187
|
+
|
|
188
|
+
### Ephemeral messages
|
|
189
|
+
|
|
190
|
+
Discord ephemeral messages are interaction-scoped — Discord only supports them
|
|
191
|
+
as the initial response to a button click or slash command, and they cannot be
|
|
192
|
+
sent outside that 3-second window. For this reason `supportsEphemeral` is
|
|
193
|
+
advertised as `false`.
|
|
194
|
+
|
|
195
|
+
`thread.postEphemeral(user, ui, { fallbackToDM: true })` works around this by
|
|
196
|
+
sending the message as a **DM** to the target user when native ephemeral is
|
|
197
|
+
unavailable. The result carries `{ ok: true, usedFallback: true }` so callers
|
|
198
|
+
can detect that a DM was used instead of an in-channel ephemeral.
|
|
199
|
+
|
|
200
|
+
With `{ fallbackToDM: false }` and no live interaction, `postEphemeral` returns
|
|
201
|
+
`null` (the documented no-fallback sentinel).
|
|
202
|
+
|
|
203
|
+
> **Future enhancement:** native interaction-ephemeral follow-up (calling
|
|
204
|
+
> `interaction.followUp({ ephemeral: true, … })` within the ack window) is a
|
|
205
|
+
> planned addition. Once plumbed, `supportsEphemeral` will be upgraded to `true`
|
|
206
|
+
> for interaction contexts and `usedFallback` will return `false`.
|
|
207
|
+
|
|
208
|
+
### Typing indicator and reactions
|
|
209
|
+
|
|
210
|
+
The adapter supports both Discord-native capabilities:
|
|
211
|
+
|
|
212
|
+
- **Typing indicator** — `channel.sendTyping()` is called at the start of each
|
|
213
|
+
run, giving users immediate feedback.
|
|
214
|
+
- **Reactions** — `supportsReactions: true` is advertised; the engine can add
|
|
215
|
+
emoji reactions during processing.
|
|
216
|
+
|
|
217
|
+
### Sender-profile resolution
|
|
218
|
+
|
|
219
|
+
The adapter resolves each turn's Discord user id to a `PlatformUser`
|
|
220
|
+
(`{ id, name?, handle? }`), cached per id. Note that Discord bots cannot read
|
|
221
|
+
user email addresses — `PlatformUser.email` is always `undefined` on this
|
|
222
|
+
platform. Inbound file attachments can be downloaded and delivered to the agent
|
|
223
|
+
as multimodal content parts (`buildFileContentParts`); a tool can post a file
|
|
224
|
+
back out via `thread.postFile(...)`.
|
|
225
|
+
|
|
226
|
+
### Built-ins
|
|
227
|
+
|
|
228
|
+
- `defaultDiscordTools` — ships `lookup_discord_user` so the agent can resolve
|
|
229
|
+
a name/handle to a `<@USERID>` mention. Spread into `tools`.
|
|
230
|
+
- `defaultDiscordContext` — tagging procedure, Discord Markdown formatting
|
|
231
|
+
guidance, and the Discord channel/thread conversation model. Spread into
|
|
232
|
+
`context`.
|
|
233
|
+
|
|
234
|
+
## Tool context
|
|
235
|
+
|
|
236
|
+
Tools receive the single shared `BotToolContext` from `@copilotkit/channels`
|
|
237
|
+
(`{ thread, message?, user?, signal?, platform }`) and reach Discord power
|
|
238
|
+
only through capability-gated `thread` methods, which this adapter backs:
|
|
239
|
+
|
|
240
|
+
- `thread.getMessages()` — the current channel's recent messages (via
|
|
241
|
+
`channel.messages.fetch`), each a `ThreadMessage` (`{ user?, text, ts?,
|
|
242
|
+
isBot? }`).
|
|
243
|
+
- `thread.lookupUser(query)` — resolve a name/handle to a `PlatformUser` by
|
|
244
|
+
searching guild members.
|
|
245
|
+
- `thread.postFile({ bytes, filename, title?, altText? })` — upload a file
|
|
246
|
+
into the channel as an attachment.
|
|
247
|
+
|
|
248
|
+
This keeps tools portable: define them with `defineBotTool({...})` and they
|
|
249
|
+
work against any adapter that advertises the same capabilities.
|
|
250
|
+
|
|
251
|
+
## Slash commands
|
|
252
|
+
|
|
253
|
+
Slash commands are registered up front on `bot.start()` via `registerCommands`.
|
|
254
|
+
When `guildId` is set they register to that guild instantly; without it they
|
|
255
|
+
register globally and take ~1 hour to propagate. Register handlers with
|
|
256
|
+
`bot.onCommand`:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
bot.onCommand({
|
|
260
|
+
name: "triage",
|
|
261
|
+
description: "Summarize the thread and propose issues.",
|
|
262
|
+
options: {
|
|
263
|
+
// Optional JSON Schema for native Discord slash-command options.
|
|
264
|
+
// Generates typed Discord option descriptors via jsonSchemaToDiscordOptions.
|
|
265
|
+
},
|
|
266
|
+
async handler({ thread, text, user, rawOptions }) {
|
|
267
|
+
await thread.runAgent({ prompt: `Triage: ${text}` });
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Unlike Slack, commands are registered programmatically — there is no manifest
|
|
273
|
+
file. Discord delivers native structured option values via `rawOptions` when the
|
|
274
|
+
command's `options` schema is provided; args also arrive flattened as free text
|
|
275
|
+
in `ctx.text`.
|
|
276
|
+
|
|
277
|
+
> **App setup** (OAuth scopes, bot permissions, invite URL) is done via the
|
|
278
|
+
> Discord Developer Portal and the OAuth2 invite flow. A complete wiring example
|
|
279
|
+
> lives in [`examples/slack`](../../examples/slack) — one bot app that runs
|
|
280
|
+
> Slack and/or Discord depending on which secrets you set.
|
|
281
|
+
|
|
282
|
+
## What's NOT in v1
|
|
283
|
+
|
|
284
|
+
- **Modal limitations** — text-input modals are supported (up to 5 fields).
|
|
285
|
+
`ModalSelect` and `RadioButtons` elements are rejected at render time.
|
|
286
|
+
Validation re-open (`response_action: "errors"`) is not supported — Discord
|
|
287
|
+
has no API for it; submit errors should be posted as a follow-up message
|
|
288
|
+
instead.
|
|
289
|
+
- OAuth / multi-guild install (single bot token only)
|
|
290
|
+
- Durable (Redis/DB) `ActionStore` — in-memory only; actions expire on restart
|
|
291
|
+
- Proactive posting (bot replies only to turns it's part of)
|
|
292
|
+
- Auto-sharding (single `Client` instance)
|
|
293
|
+
- Native interaction-ephemeral (`supportsEphemeral` is `false`; use
|
|
294
|
+
`thread.postEphemeral(user, ui, { fallbackToDM: true })` for a DM-based
|
|
295
|
+
workaround)
|
|
296
|
+
|
|
297
|
+
## Exports
|
|
298
|
+
|
|
299
|
+
`discord`, `DiscordAdapter`, `DiscordAdapterOptions`; `DiscordConversationStore`;
|
|
300
|
+
`attachDiscordListener`, `ListenerConfig`, `ClientLike`, `IncomingCommandRaw`;
|
|
301
|
+
`createRunRenderer`, `ChannelLike`; `decodeInteraction`; `conversationKeyOf`,
|
|
302
|
+
`ReplyTarget`, `IncomingTurn`; `renderComponents`, `renderDiscordMessage`,
|
|
303
|
+
`DISCORD_LIMITS`; `discordMarkdown`; `MessageStream`, `MessageStreamConfig`;
|
|
304
|
+
`ChunkedMessageStream`, `ChunkedMessageStreamConfig`; `autoCloseOpenMarkdown`;
|
|
305
|
+
`registerCommands`, `jsonSchemaToDiscordOptions`; `buildFileContentParts`,
|
|
306
|
+
`DiscordAttachmentRef`, `AgentContentPart`, `FileDeliveryConfig`;
|
|
307
|
+
`defaultDiscordContext`, `discordTaggingContext`, `discordFormattingContext`,
|
|
308
|
+
`discordConversationModelContext`; `lookupDiscordUserTool`, `defaultDiscordTools`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ephemeral.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ephemeral.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// packages/channels-discord/src/__tests__/ephemeral.test.ts
|
|
2
|
+
import { it, expect, vi } from "vitest";
|
|
3
|
+
import { DiscordAdapter } from "../adapter.js";
|
|
4
|
+
function makeAdapter() {
|
|
5
|
+
const send = vi.fn().mockResolvedValue({ id: "dm1" });
|
|
6
|
+
const client = {
|
|
7
|
+
users: {
|
|
8
|
+
fetch: vi.fn().mockResolvedValue({
|
|
9
|
+
createDM: vi.fn().mockResolvedValue({ id: "dm-channel-1", send }),
|
|
10
|
+
}),
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
const adapter = new DiscordAdapter({ botToken: "t", appId: "app" }, { client: client });
|
|
14
|
+
return { adapter, send, client };
|
|
15
|
+
}
|
|
16
|
+
it("DM-falls-back when fallbackToDM=true (usedFallback=true)", async () => {
|
|
17
|
+
const { adapter, send } = makeAdapter();
|
|
18
|
+
const res = await adapter.postEphemeral({ channelId: "C1" }, { id: "U1" }, [{ type: "text", props: { value: "hi" } }], { fallbackToDM: true });
|
|
19
|
+
expect(res).toMatchObject({ ok: true, usedFallback: true });
|
|
20
|
+
expect(send).toHaveBeenCalled();
|
|
21
|
+
});
|
|
22
|
+
it("returns null when fallbackToDM=false and no live interaction", async () => {
|
|
23
|
+
const { adapter } = makeAdapter();
|
|
24
|
+
const res = await adapter.postEphemeral({ channelId: "C1" }, { id: "U1" }, [{ type: "text", props: { value: "hi" } }], { fallbackToDM: false });
|
|
25
|
+
expect(res).toBeNull();
|
|
26
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intents.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/intents.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { it, expect } from "vitest";
|
|
2
|
+
import { GatewayIntentBits, Partials } from "discord.js";
|
|
3
|
+
import { DISCORD_DEFAULT_INTENTS, DISCORD_DEFAULT_PARTIALS, } from "../adapter.js";
|
|
4
|
+
it("requests the reaction intent and partials", () => {
|
|
5
|
+
expect(DISCORD_DEFAULT_INTENTS).toContain(GatewayIntentBits.GuildMessageReactions);
|
|
6
|
+
expect(DISCORD_DEFAULT_PARTIALS).toContain(Partials.Message);
|
|
7
|
+
expect(DISCORD_DEFAULT_PARTIALS).toContain(Partials.Reaction);
|
|
8
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal-submit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/modal-submit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// packages/channels-discord/src/__tests__/modal-submit.test.ts
|
|
2
|
+
import { it, expect } from "vitest";
|
|
3
|
+
import { decodeModalSubmit } from "../interaction.js";
|
|
4
|
+
it("decodes a modal submission's text fields by custom_id", () => {
|
|
5
|
+
const interaction = {
|
|
6
|
+
customId: "triage",
|
|
7
|
+
channelId: "C1",
|
|
8
|
+
guildId: "G1",
|
|
9
|
+
user: { id: "U1", username: "ada" },
|
|
10
|
+
fields: {
|
|
11
|
+
fields: new Map([
|
|
12
|
+
["summary", { customId: "summary", value: "boom" }],
|
|
13
|
+
["detail", { customId: "detail", value: "ctx" }],
|
|
14
|
+
]),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const evt = decodeModalSubmit(interaction);
|
|
18
|
+
expect(evt).toMatchObject({
|
|
19
|
+
callbackId: "triage",
|
|
20
|
+
values: { summary: "boom", detail: "ctx" },
|
|
21
|
+
user: { id: "U1", name: "ada" },
|
|
22
|
+
conversationKey: "C1",
|
|
23
|
+
replyTarget: { channelId: "C1", guildId: "G1" },
|
|
24
|
+
platform: "discord",
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactions.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/reactions.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { emoji } from "@copilotkit/channels-ui";
|
|
3
|
+
import { decodeReaction } from "../interaction.js";
|
|
4
|
+
import { DiscordAdapter } from "../adapter.js";
|
|
5
|
+
it("decodes a unicode reaction add", () => {
|
|
6
|
+
const evt = decodeReaction({
|
|
7
|
+
emoji: { name: "👍", id: null },
|
|
8
|
+
message: { id: "m1", channelId: "C1", guildId: "G1" },
|
|
9
|
+
}, { id: "U1", username: "ada", bot: false }, true);
|
|
10
|
+
expect(evt).toMatchObject({
|
|
11
|
+
rawEmoji: "👍",
|
|
12
|
+
added: true,
|
|
13
|
+
user: { id: "U1", name: "ada" },
|
|
14
|
+
conversationKey: "C1",
|
|
15
|
+
messageId: "m1",
|
|
16
|
+
replyTarget: { channelId: "C1", guildId: "G1" },
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
it("encodes a custom emoji as name:id passthrough", () => {
|
|
20
|
+
const evt = decodeReaction({
|
|
21
|
+
emoji: { name: "blob", id: "999" },
|
|
22
|
+
message: { id: "m2", channelId: "C2" },
|
|
23
|
+
}, { id: "U2", bot: false }, false);
|
|
24
|
+
expect(evt.rawEmoji).toBe("blob:999");
|
|
25
|
+
expect(evt.added).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
describe("addReaction / removeReaction egress", () => {
|
|
28
|
+
function makeAdapter() {
|
|
29
|
+
const botId = "BOT1";
|
|
30
|
+
const react = vi.fn().mockResolvedValue(undefined);
|
|
31
|
+
const removeUser = vi.fn().mockResolvedValue(undefined);
|
|
32
|
+
const msg = {
|
|
33
|
+
react,
|
|
34
|
+
reactions: {
|
|
35
|
+
cache: new Map([
|
|
36
|
+
["👍", { users: { remove: removeUser } }],
|
|
37
|
+
["custom", { users: { remove: removeUser } }],
|
|
38
|
+
// Discord keys hearts by the BARE codepoint (no trailing U+FE0F).
|
|
39
|
+
["❤", { users: { remove: removeUser } }],
|
|
40
|
+
]),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const client = {
|
|
44
|
+
user: { id: botId },
|
|
45
|
+
channels: {
|
|
46
|
+
fetch: vi.fn().mockResolvedValue({
|
|
47
|
+
id: "C1",
|
|
48
|
+
send: vi.fn(),
|
|
49
|
+
messages: {
|
|
50
|
+
fetch: vi.fn().mockResolvedValue(msg),
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const adapter = new DiscordAdapter({ botToken: "t", appId: "app" }, { client: client });
|
|
56
|
+
return { adapter, react, removeUser, botId, msg };
|
|
57
|
+
}
|
|
58
|
+
const target = { channelId: "C1" };
|
|
59
|
+
const messageRef = { id: "M1", channelId: "C1" };
|
|
60
|
+
it("addReaction resolves thumbs_up to 👍 and calls msg.react", async () => {
|
|
61
|
+
const { adapter, react } = makeAdapter();
|
|
62
|
+
const res = await adapter.addReaction(target, messageRef, "thumbs_up");
|
|
63
|
+
expect(res).toEqual({ ok: true });
|
|
64
|
+
expect(react).toHaveBeenCalledWith("👍");
|
|
65
|
+
});
|
|
66
|
+
it("addReaction passes unknown emoji through raw", async () => {
|
|
67
|
+
const { adapter, react } = makeAdapter();
|
|
68
|
+
const res = await adapter.addReaction(target, messageRef, "some_custom_emoji");
|
|
69
|
+
expect(res).toEqual({ ok: true });
|
|
70
|
+
expect(react).toHaveBeenCalledWith("some_custom_emoji");
|
|
71
|
+
});
|
|
72
|
+
it("removeReaction removes the bot's own id via reactions.cache.get(token)?.users.remove", async () => {
|
|
73
|
+
const { adapter, removeUser, botId } = makeAdapter();
|
|
74
|
+
const res = await adapter.removeReaction(target, messageRef, "thumbs_up");
|
|
75
|
+
expect(res).toEqual({ ok: true });
|
|
76
|
+
expect(removeUser).toHaveBeenCalledWith(botId);
|
|
77
|
+
});
|
|
78
|
+
it("removeReaction finds a reaction cached under the bare (VS16-stripped) codepoint", async () => {
|
|
79
|
+
// `emoji.heart` resolves to the qualified "❤️" (U+2764 U+FE0F), but Discord
|
|
80
|
+
// keys the cache by the bare "❤" (U+2764). A VS16-tolerant lookup must still
|
|
81
|
+
// find the reaction and remove the bot's own id.
|
|
82
|
+
const { adapter, removeUser, botId } = makeAdapter();
|
|
83
|
+
const res = await adapter.removeReaction(target, messageRef, emoji.heart);
|
|
84
|
+
expect(res).toEqual({ ok: true });
|
|
85
|
+
expect(removeUser).toHaveBeenCalledWith(botId);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Client, GatewayIntentBits, Partials } from "discord.js";
|
|
2
|
+
import type { PlatformAdapter, SurfaceCapabilities, IngressSink, InteractionEvent, RunRenderer, ReplyTarget as BotReplyTarget, ConversationStore, MessageRef, PlatformUser, UserQuery, CommandSpec, NativePayload } from "@copilotkit/channels";
|
|
3
|
+
import type { BotNode, ThreadMessage, EmojiValue, EphemeralResult } from "@copilotkit/channels-ui";
|
|
4
|
+
import type { RestLike } from "./commands.js";
|
|
5
|
+
export interface DiscordAdapterOptions {
|
|
6
|
+
botToken: string;
|
|
7
|
+
appId: string;
|
|
8
|
+
/** When set, slash commands register to this guild instantly (dev); else global. */
|
|
9
|
+
guildId?: string;
|
|
10
|
+
interruptEventNames?: ReadonlySet<string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Default Gateway intents for the bot.
|
|
14
|
+
*
|
|
15
|
+
* - `Guilds`, `GuildMessages`, `MessageContent`, `DirectMessages` — message ingress.
|
|
16
|
+
* - `GuildMembers` — privileged; backs `lookup_discord_user` / `thread.lookupUser`.
|
|
17
|
+
* Must be toggled on in the Discord Developer Portal (Bot → Privileged Gateway Intents).
|
|
18
|
+
* - `GuildMessageReactions` — non-privileged; no portal toggle needed.
|
|
19
|
+
* Required to receive reaction add/remove events in guild channels.
|
|
20
|
+
* - `DirectMessageReactions` — non-privileged; required to receive reaction
|
|
21
|
+
* add/remove events in DMs. Distinct from `GuildMessageReactions` in discord.js v14.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DISCORD_DEFAULT_INTENTS: readonly [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.DirectMessages, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.DirectMessageReactions];
|
|
24
|
+
/**
|
|
25
|
+
* Default partials for the bot.
|
|
26
|
+
*
|
|
27
|
+
* - `Channel` — needed to receive DMs.
|
|
28
|
+
* - `Message` + `Reaction` — required to receive reactions on uncached messages.
|
|
29
|
+
* Discord only delivers partial reaction events when the reacted-to message is not
|
|
30
|
+
* in the client's cache; without these partials those events are silently dropped.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DISCORD_DEFAULT_PARTIALS: readonly [Partials.Channel, Partials.Message, Partials.Reaction];
|
|
33
|
+
export declare class DiscordAdapter implements PlatformAdapter {
|
|
34
|
+
private readonly opts;
|
|
35
|
+
readonly platform = "discord";
|
|
36
|
+
readonly capabilities: SurfaceCapabilities;
|
|
37
|
+
readonly ackDeadlineMs = 3000;
|
|
38
|
+
private readonly client;
|
|
39
|
+
private readonly rest;
|
|
40
|
+
private readonly store;
|
|
41
|
+
private botUserId;
|
|
42
|
+
private isReady;
|
|
43
|
+
private pendingCommands;
|
|
44
|
+
private readonly userCache;
|
|
45
|
+
/**
|
|
46
|
+
* Tracks live component interactions so a handler can open a modal (which
|
|
47
|
+
* must be the interaction's INITIAL response, within ~3s) before the adapter
|
|
48
|
+
* auto-defers. Constructed in `start()`. Used by `openModal` (Task D4).
|
|
49
|
+
*/
|
|
50
|
+
private pending;
|
|
51
|
+
/**
|
|
52
|
+
* Tracks live slash-command interactions (a command's initial response is a
|
|
53
|
+
* reply, not a component update, so it needs its own registry). `openModal`
|
|
54
|
+
* consults this too, so a command handler can `showModal` before the adapter
|
|
55
|
+
* auto-defers. Constructed in `start()`.
|
|
56
|
+
*/
|
|
57
|
+
private commandPending;
|
|
58
|
+
constructor(opts: DiscordAdapterOptions, injected?: {
|
|
59
|
+
client?: Client;
|
|
60
|
+
rest?: RestLike;
|
|
61
|
+
});
|
|
62
|
+
registerCommands(commands: readonly CommandSpec[]): void;
|
|
63
|
+
/**
|
|
64
|
+
* Publish the registered commands. Guards against an empty list: an empty
|
|
65
|
+
* `setMyCommands`/`PUT` CLEARS all of the bot's commands, so a race where
|
|
66
|
+
* `ready` fires before commands are stashed must not wipe them.
|
|
67
|
+
*/
|
|
68
|
+
private publishCommands;
|
|
69
|
+
start(sink: IngressSink): Promise<void>;
|
|
70
|
+
stop(): Promise<void>;
|
|
71
|
+
render(ir: BotNode[]): import("discord.js").ContainerBuilder;
|
|
72
|
+
post(target: BotReplyTarget, ir: BotNode[]): Promise<MessageRef>;
|
|
73
|
+
update(ref: MessageRef, ir: BotNode[]): Promise<void>;
|
|
74
|
+
stream(target: BotReplyTarget, chunks: AsyncIterable<string>): Promise<MessageRef>;
|
|
75
|
+
delete(ref: MessageRef): Promise<void>;
|
|
76
|
+
createRunRenderer(target: BotReplyTarget): RunRenderer;
|
|
77
|
+
decodeInteraction(raw: unknown): InteractionEvent | undefined;
|
|
78
|
+
lookupUser(q: UserQuery): Promise<PlatformUser | undefined>;
|
|
79
|
+
get conversationStore(): ConversationStore;
|
|
80
|
+
getMessages(target: BotReplyTarget): Promise<ThreadMessage[]>;
|
|
81
|
+
postFile(target: BotReplyTarget, args: {
|
|
82
|
+
bytes: Uint8Array;
|
|
83
|
+
filename: string;
|
|
84
|
+
title?: string;
|
|
85
|
+
altText?: string;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
ok: boolean;
|
|
88
|
+
fileId?: string;
|
|
89
|
+
error?: string;
|
|
90
|
+
}>;
|
|
91
|
+
addReaction(target: BotReplyTarget, messageRef: MessageRef, emoji: EmojiValue): Promise<{
|
|
92
|
+
ok: boolean;
|
|
93
|
+
error?: string;
|
|
94
|
+
}>;
|
|
95
|
+
removeReaction(target: BotReplyTarget, messageRef: MessageRef, emoji: EmojiValue): Promise<{
|
|
96
|
+
ok: boolean;
|
|
97
|
+
error?: string;
|
|
98
|
+
}>;
|
|
99
|
+
postEphemeral(_target: BotReplyTarget, user: PlatformUser | string, ir: BotNode[], opts: {
|
|
100
|
+
fallbackToDM: boolean;
|
|
101
|
+
}): Promise<EphemeralResult | null>;
|
|
102
|
+
renderModal(ir: BotNode[]): NativePayload;
|
|
103
|
+
openModal(_target: BotReplyTarget, triggerId: string, ir: BotNode[]): Promise<{
|
|
104
|
+
ok: boolean;
|
|
105
|
+
error?: string;
|
|
106
|
+
}>;
|
|
107
|
+
resolveUser(userId: string): Promise<PlatformUser>;
|
|
108
|
+
private channelIdOf;
|
|
109
|
+
private fetchSendable;
|
|
110
|
+
/**
|
|
111
|
+
* Fetch the channel's recent messages OLDEST→NEWEST, normalized for the
|
|
112
|
+
* conversation store's history reconstruction. Best-effort: on any fetch
|
|
113
|
+
* error returns [] (bot-slack's fetchHistory does the same).
|
|
114
|
+
*/
|
|
115
|
+
private fetchHistory;
|
|
116
|
+
}
|
|
117
|
+
export declare function discord(opts: DiscordAdapterOptions): DiscordAdapter;
|
|
118
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|