chat 4.13.0 → 4.13.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.
Files changed (53) hide show
  1. package/README.md +19 -31
  2. package/dist/{chunk-WKJEG4FE.js → chunk-THM4ACIE.js} +12 -4
  3. package/dist/chunk-THM4ACIE.js.map +1 -0
  4. package/dist/index.d.ts +409 -415
  5. package/dist/index.js +63 -29
  6. package/dist/index.js.map +1 -1
  7. package/dist/{jsx-runtime-COVsDskT.d.ts → jsx-runtime-Bdt1Dwzf.d.ts} +71 -71
  8. package/dist/jsx-runtime.d.ts +1 -1
  9. package/dist/jsx-runtime.js +1 -1
  10. package/docs/actions.mdx +98 -0
  11. package/docs/adapters/discord.mdx +217 -0
  12. package/docs/adapters/gchat.mdx +232 -0
  13. package/docs/adapters/github.mdx +225 -0
  14. package/docs/adapters/index.mdx +110 -0
  15. package/docs/adapters/linear.mdx +207 -0
  16. package/docs/adapters/meta.json +12 -0
  17. package/docs/adapters/slack.mdx +293 -0
  18. package/docs/adapters/teams.mdx +225 -0
  19. package/docs/api/cards.mdx +217 -0
  20. package/docs/api/channel.mdx +176 -0
  21. package/docs/api/chat.mdx +469 -0
  22. package/docs/api/index.mdx +29 -0
  23. package/docs/api/markdown.mdx +235 -0
  24. package/docs/api/message.mdx +163 -0
  25. package/docs/api/meta.json +14 -0
  26. package/docs/api/modals.mdx +222 -0
  27. package/docs/api/postable-message.mdx +166 -0
  28. package/docs/api/thread.mdx +186 -0
  29. package/docs/cards.mdx +213 -0
  30. package/docs/direct-messages.mdx +56 -0
  31. package/docs/emoji.mdx +77 -0
  32. package/docs/ephemeral-messages.mdx +77 -0
  33. package/docs/error-handling.mdx +147 -0
  34. package/docs/files.mdx +77 -0
  35. package/docs/getting-started.mdx +12 -0
  36. package/docs/guides/code-review-hono.mdx +248 -0
  37. package/docs/guides/discord-nuxt.mdx +237 -0
  38. package/docs/guides/meta.json +4 -0
  39. package/docs/guides/slack-nextjs.mdx +245 -0
  40. package/docs/index.mdx +92 -0
  41. package/docs/meta.json +20 -0
  42. package/docs/modals.mdx +208 -0
  43. package/docs/posting-messages.mdx +177 -0
  44. package/docs/slash-commands.mdx +110 -0
  45. package/docs/state/index.mdx +31 -0
  46. package/docs/state/ioredis.mdx +81 -0
  47. package/docs/state/memory.mdx +52 -0
  48. package/docs/state/meta.json +9 -0
  49. package/docs/state/redis.mdx +93 -0
  50. package/docs/streaming.mdx +99 -0
  51. package/docs/usage.mdx +338 -0
  52. package/package.json +10 -10
  53. package/dist/chunk-WKJEG4FE.js.map +0 -1
@@ -4,77 +4,77 @@
4
4
 
5
5
  type ModalChild = TextInputElement | SelectElement | RadioSelectElement | TextElement | FieldsElement;
6
6
  interface ModalElement {
7
- type: "modal";
8
7
  callbackId: string;
9
- title: string;
10
- submitLabel?: string;
8
+ children: ModalChild[];
11
9
  closeLabel?: string;
12
10
  notifyOnClose?: boolean;
13
11
  /** Arbitrary string passed through the modal lifecycle (e.g., JSON context). */
14
12
  privateMetadata?: string;
15
- children: ModalChild[];
13
+ submitLabel?: string;
14
+ title: string;
15
+ type: "modal";
16
16
  }
17
17
  interface TextInputElement {
18
- type: "text_input";
19
18
  id: string;
20
- label: string;
21
- placeholder?: string;
22
19
  initialValue?: string;
20
+ label: string;
21
+ maxLength?: number;
23
22
  multiline?: boolean;
24
23
  optional?: boolean;
25
- maxLength?: number;
24
+ placeholder?: string;
25
+ type: "text_input";
26
26
  }
27
27
  interface SelectElement {
28
- type: "select";
29
28
  id: string;
30
- label: string;
31
- placeholder?: string;
32
- options: SelectOptionElement[];
33
29
  initialOption?: string;
30
+ label: string;
34
31
  optional?: boolean;
32
+ options: SelectOptionElement[];
33
+ placeholder?: string;
34
+ type: "select";
35
35
  }
36
36
  interface SelectOptionElement {
37
+ description?: string;
37
38
  label: string;
38
39
  value: string;
39
- description?: string;
40
40
  }
41
41
  interface RadioSelectElement {
42
- type: "radio_select";
43
42
  id: string;
44
- label: string;
45
- options: SelectOptionElement[];
46
43
  initialOption?: string;
44
+ label: string;
47
45
  optional?: boolean;
46
+ options: SelectOptionElement[];
47
+ type: "radio_select";
48
48
  }
49
49
  declare function isModalElement(value: unknown): value is ModalElement;
50
50
  interface ModalOptions {
51
51
  callbackId: string;
52
- title: string;
53
- submitLabel?: string;
52
+ children?: ModalChild[];
54
53
  closeLabel?: string;
55
54
  notifyOnClose?: boolean;
56
55
  /** Arbitrary string passed through the modal lifecycle (e.g., JSON context). */
57
56
  privateMetadata?: string;
58
- children?: ModalChild[];
57
+ submitLabel?: string;
58
+ title: string;
59
59
  }
60
60
  declare function Modal(options: ModalOptions): ModalElement;
61
61
  interface TextInputOptions {
62
62
  id: string;
63
- label: string;
64
- placeholder?: string;
65
63
  initialValue?: string;
64
+ label: string;
65
+ maxLength?: number;
66
66
  multiline?: boolean;
67
67
  optional?: boolean;
68
- maxLength?: number;
68
+ placeholder?: string;
69
69
  }
70
70
  declare function TextInput(options: TextInputOptions): TextInputElement;
71
71
  interface SelectOptions {
72
72
  id: string;
73
- label: string;
74
- placeholder?: string;
75
- options: SelectOptionElement[];
76
73
  initialOption?: string;
74
+ label: string;
77
75
  optional?: boolean;
76
+ options: SelectOptionElement[];
77
+ placeholder?: string;
78
78
  }
79
79
  declare function Select(options: SelectOptions): SelectElement;
80
80
  declare function SelectOption(options: {
@@ -84,10 +84,10 @@ declare function SelectOption(options: {
84
84
  }): SelectOptionElement;
85
85
  interface RadioSelectOptions {
86
86
  id: string;
87
- label: string;
88
- options: SelectOptionElement[];
89
87
  initialOption?: string;
88
+ label: string;
90
89
  optional?: boolean;
90
+ options: SelectOptionElement[];
91
91
  }
92
92
  declare function RadioSelect(options: RadioSelectOptions): RadioSelectElement;
93
93
  type AnyModalElement = ModalElement | ModalChild | SelectOptionElement;
@@ -145,41 +145,41 @@ type ButtonStyle = "primary" | "danger" | "default";
145
145
  type TextStyle = "plain" | "bold" | "muted";
146
146
  /** Button element for interactive actions */
147
147
  interface ButtonElement {
148
- type: "button";
149
148
  /** Unique action ID for callback routing */
150
149
  id: string;
151
150
  /** Button label text */
152
151
  label: string;
153
152
  /** Visual style */
154
153
  style?: ButtonStyle;
154
+ type: "button";
155
155
  /** Optional payload value sent with action callback */
156
156
  value?: string;
157
157
  }
158
158
  /** Link button element that opens a URL */
159
159
  interface LinkButtonElement {
160
- type: "link-button";
161
- /** URL to open when clicked */
162
- url: string;
163
160
  /** Button label text */
164
161
  label: string;
165
162
  /** Visual style */
166
163
  style?: ButtonStyle;
164
+ type: "link-button";
165
+ /** URL to open when clicked */
166
+ url: string;
167
167
  }
168
168
  /** Text content element */
169
169
  interface TextElement {
170
- type: "text";
171
170
  /** Text content (supports markdown in some platforms) */
172
171
  content: string;
173
172
  /** Text style */
174
173
  style?: TextStyle;
174
+ type: "text";
175
175
  }
176
176
  /** Image element */
177
177
  interface ImageElement {
178
+ /** Alt text for accessibility */
179
+ alt?: string;
178
180
  type: "image";
179
181
  /** Image URL */
180
182
  url: string;
181
- /** Alt text for accessibility */
182
- alt?: string;
183
183
  }
184
184
  /** Visual divider/separator */
185
185
  interface DividerElement {
@@ -187,29 +187,29 @@ interface DividerElement {
187
187
  }
188
188
  /** Container for action buttons and selects */
189
189
  interface ActionsElement {
190
- type: "actions";
191
190
  /** Button, link button, select, and radio select elements */
192
191
  children: (ButtonElement | LinkButtonElement | SelectElement | RadioSelectElement)[];
192
+ type: "actions";
193
193
  }
194
194
  /** Section container for grouping elements */
195
195
  interface SectionElement {
196
- type: "section";
197
196
  /** Section children */
198
197
  children: CardChild[];
198
+ type: "section";
199
199
  }
200
200
  /** Field for key-value display */
201
201
  interface FieldElement {
202
- type: "field";
203
202
  /** Field label */
204
203
  label: string;
204
+ type: "field";
205
205
  /** Field value */
206
206
  value: string;
207
207
  }
208
208
  /** Fields container for multi-column layout */
209
209
  interface FieldsElement {
210
- type: "fields";
211
210
  /** Field elements */
212
211
  children: FieldElement[];
212
+ type: "fields";
213
213
  }
214
214
  /** Union of all card child element types */
215
215
  type CardChild = TextElement | ImageElement | DividerElement | ActionsElement | SectionElement | FieldsElement;
@@ -217,24 +217,24 @@ type CardChild = TextElement | ImageElement | DividerElement | ActionsElement |
217
217
  type AnyCardElement = CardChild | CardElement | ButtonElement | LinkButtonElement | FieldElement | SelectElement | RadioSelectElement;
218
218
  /** Root card element */
219
219
  interface CardElement {
220
- type: "card";
221
- /** Card title */
222
- title?: string;
223
- /** Card subtitle */
224
- subtitle?: string;
225
- /** Header image URL */
226
- imageUrl?: string;
227
220
  /** Card content */
228
221
  children: CardChild[];
222
+ /** Header image URL */
223
+ imageUrl?: string;
224
+ /** Card subtitle */
225
+ subtitle?: string;
226
+ /** Card title */
227
+ title?: string;
228
+ type: "card";
229
229
  }
230
230
  /** Type guard for CardElement */
231
231
  declare function isCardElement(value: unknown): value is CardElement;
232
232
  /** Options for Card */
233
233
  interface CardOptions {
234
- title?: string;
235
- subtitle?: string;
236
- imageUrl?: string;
237
234
  children?: CardChild[];
235
+ imageUrl?: string;
236
+ subtitle?: string;
237
+ title?: string;
238
238
  }
239
239
  /**
240
240
  * Create a Card element.
@@ -331,12 +331,12 @@ interface ButtonOptions {
331
331
  declare function Button(options: ButtonOptions): ButtonElement;
332
332
  /** Options for LinkButton */
333
333
  interface LinkButtonOptions {
334
- /** URL to open when clicked */
335
- url: string;
336
334
  /** Button label text */
337
335
  label: string;
338
336
  /** Visual style */
339
337
  style?: ButtonStyle;
338
+ /** URL to open when clicked */
339
+ url: string;
340
340
  }
341
341
  /**
342
342
  * Create a LinkButton element that opens a URL when clicked.
@@ -427,35 +427,35 @@ declare function fromReactElement(element: unknown): AnyCardElement | null;
427
427
  declare const JSX_ELEMENT: unique symbol;
428
428
  /** Props for Card component in JSX */
429
429
  interface CardProps {
430
- title?: string;
431
- subtitle?: string;
432
- imageUrl?: string;
433
430
  children?: unknown;
431
+ imageUrl?: string;
432
+ subtitle?: string;
433
+ title?: string;
434
434
  }
435
435
  /** Props for Text component in JSX */
436
436
  interface TextProps {
437
- style?: TextStyle;
438
437
  children?: string | number;
438
+ style?: TextStyle;
439
439
  }
440
440
  /** Props for Button component in JSX */
441
441
  interface ButtonProps {
442
+ children?: string | number;
442
443
  id: string;
443
444
  label?: string;
444
445
  style?: ButtonStyle;
445
446
  value?: string;
446
- children?: string | number;
447
447
  }
448
448
  /** Props for LinkButton component in JSX */
449
449
  interface LinkButtonProps {
450
- url: string;
450
+ children?: string | number;
451
451
  label?: string;
452
452
  style?: ButtonStyle;
453
- children?: string | number;
453
+ url: string;
454
454
  }
455
455
  /** Props for Image component in JSX */
456
456
  interface ImageProps {
457
- url: string;
458
457
  alt?: string;
458
+ url: string;
459
459
  }
460
460
  /** Props for Field component in JSX */
461
461
  interface FieldProps {
@@ -471,37 +471,37 @@ type DividerProps = Record<string, never>;
471
471
  /** Props for Modal component in JSX */
472
472
  interface ModalProps {
473
473
  callbackId: string;
474
- title: string;
475
- submitLabel?: string;
474
+ children?: unknown;
476
475
  closeLabel?: string;
477
476
  notifyOnClose?: boolean;
478
477
  privateMetadata?: string;
479
- children?: unknown;
478
+ submitLabel?: string;
479
+ title: string;
480
480
  }
481
481
  /** Props for TextInput component in JSX */
482
482
  interface TextInputProps {
483
483
  id: string;
484
- label: string;
485
- placeholder?: string;
486
484
  initialValue?: string;
485
+ label: string;
486
+ maxLength?: number;
487
487
  multiline?: boolean;
488
488
  optional?: boolean;
489
- maxLength?: number;
489
+ placeholder?: string;
490
490
  }
491
491
  /** Props for Select component in JSX */
492
492
  interface SelectProps {
493
+ children?: unknown;
493
494
  id: string;
494
- label: string;
495
- placeholder?: string;
496
495
  initialOption?: string;
496
+ label: string;
497
497
  optional?: boolean;
498
- children?: unknown;
498
+ placeholder?: string;
499
499
  }
500
500
  /** Props for SelectOption component in JSX */
501
501
  interface SelectOptionProps {
502
+ description?: string;
502
503
  label: string;
503
504
  value: string;
504
- description?: string;
505
505
  }
506
506
  /** Union of all valid JSX props */
507
507
  type CardJSXProps = CardProps | TextProps | ButtonProps | LinkButtonProps | ImageProps | FieldProps | ContainerProps | DividerProps | ModalProps | TextInputProps | SelectProps | SelectOptionProps;
@@ -513,9 +513,9 @@ type CardComponentFunction = typeof Card | typeof Text | typeof Button | typeof
513
513
  */
514
514
  interface CardJSXElement<P extends CardJSXProps = CardJSXProps> {
515
515
  $$typeof: typeof JSX_ELEMENT;
516
- type: CardComponentFunction;
517
- props: P;
518
516
  children: unknown[];
517
+ props: P;
518
+ type: CardComponentFunction;
519
519
  }
520
520
  type JSXElement = CardJSXElement;
521
521
  /**
@@ -560,4 +560,4 @@ declare namespace JSX {
560
560
  }
561
561
  }
562
562
 
563
- export { type SelectElement as $, Actions as A, Button as B, type CardElement as C, Divider as D, type ButtonOptions as E, Field as F, type ButtonStyle as G, type CardOptions as H, Image as I, type DividerElement as J, type FieldElement as K, LinkButton as L, type ModalElement as M, type FieldsElement as N, type ImageElement as O, type LinkButtonElement as P, type LinkButtonOptions as Q, RadioSelect as R, Section as S, Text as T, type SectionElement as U, type TextElement as V, type TextStyle as W, type ModalChild as X, type ModalOptions as Y, type RadioSelectElement as Z, type RadioSelectOptions as _, type CardJSXElement as a, type SelectOptionElement as a0, type SelectOptions as a1, type TextInputElement as a2, type TextInputOptions as a3, type ModalProps as a4, type TextInputProps as a5, type SelectProps as a6, type SelectOptionProps as a7, jsx as a8, jsxs as a9, jsxDEV as aa, Fragment as ab, JSX as ac, type CardChild as b, Card as c, Fields as d, isJSX as e, fromReactElement as f, toModalElement as g, fromReactModalElement as h, isCardElement as i, isModalElement as j, Modal as k, Select as l, SelectOption as m, TextInput as n, type ButtonProps as o, type CardJSXProps as p, type CardProps as q, type ContainerProps as r, type DividerProps as s, toCardElement as t, type FieldProps as u, type ImageProps as v, type LinkButtonProps as w, type TextProps as x, type ActionsElement as y, type ButtonElement as z };
563
+ export { type SelectElement as $, Actions as A, Button as B, type CardElement as C, Divider as D, type SectionElement as E, Field as F, type TextElement as G, type TextStyle as H, Image as I, type ButtonProps as J, type CardJSXProps as K, LinkButton as L, type ModalElement as M, type CardProps as N, type ContainerProps as O, type DividerProps as P, type FieldProps as Q, RadioSelect as R, Section as S, Text as T, type ImageProps as U, type LinkButtonProps as V, type TextProps as W, type ModalChild as X, type ModalOptions as Y, type RadioSelectElement as Z, type RadioSelectOptions as _, type CardJSXElement as a, type SelectOptionElement as a0, type SelectOptions as a1, type TextInputElement as a2, type TextInputOptions as a3, type ModalProps as a4, type TextInputProps as a5, type SelectProps as a6, type SelectOptionProps as a7, jsx as a8, jsxs as a9, jsxDEV as aa, Fragment as ab, JSX as ac, type CardChild as b, Card as c, Fields as d, isJSX as e, fromReactElement as f, toModalElement as g, fromReactModalElement as h, isCardElement as i, isModalElement as j, Modal as k, Select as l, SelectOption as m, TextInput as n, type ActionsElement as o, type ButtonElement as p, type ButtonOptions as q, type ButtonStyle as r, type CardOptions as s, toCardElement as t, type DividerElement as u, type FieldElement as v, type FieldsElement as w, type ImageElement as x, type LinkButtonElement as y, type LinkButtonOptions as z };
@@ -1 +1 @@
1
- export { o as ButtonProps, a as CardJSXElement, p as CardJSXProps, q as CardProps, r as ContainerProps, s as DividerProps, u as FieldProps, ab as Fragment, v as ImageProps, ac as JSX, w as LinkButtonProps, a4 as ModalProps, a7 as SelectOptionProps, a6 as SelectProps, a5 as TextInputProps, x as TextProps, e as isJSX, a8 as jsx, aa as jsxDEV, a9 as jsxs, t as toCardElement, g as toModalElement } from './jsx-runtime-COVsDskT.js';
1
+ export { J as ButtonProps, a as CardJSXElement, K as CardJSXProps, N as CardProps, O as ContainerProps, P as DividerProps, Q as FieldProps, ab as Fragment, U as ImageProps, ac as JSX, V as LinkButtonProps, a4 as ModalProps, a7 as SelectOptionProps, a6 as SelectProps, a5 as TextInputProps, W as TextProps, e as isJSX, a8 as jsx, aa as jsxDEV, a9 as jsxs, t as toCardElement, g as toModalElement } from './jsx-runtime-Bdt1Dwzf.js';
@@ -6,7 +6,7 @@ import {
6
6
  jsxs,
7
7
  toCardElement,
8
8
  toModalElement
9
- } from "./chunk-WKJEG4FE.js";
9
+ } from "./chunk-THM4ACIE.js";
10
10
  export {
11
11
  Fragment,
12
12
  isJSX,
@@ -0,0 +1,98 @@
1
+ ---
2
+ title: Actions
3
+ description: Handle button clicks and interactive card events across platforms.
4
+ type: guide
5
+ prerequisites:
6
+ - /docs/cards
7
+ related:
8
+ - /docs/modals
9
+ ---
10
+
11
+ Actions let you handle button clicks, dropdown selections, and other interactive events from [cards](/docs/cards). Register handlers with `onAction` to respond when users interact with your cards.
12
+
13
+ ## Handle a specific action
14
+
15
+ ```typescript title="lib/bot.ts" lineNumbers
16
+ bot.onAction("approve", async (event) => {
17
+ await event.thread.post(`Order approved by ${event.user.fullName}!`);
18
+ });
19
+ ```
20
+
21
+ ## Handle multiple actions
22
+
23
+ ```typescript title="lib/bot.ts" lineNumbers
24
+ bot.onAction(["approve", "reject"], async (event) => {
25
+ const action = event.actionId === "approve" ? "approved" : "rejected";
26
+ await event.thread.post(`Order ${action} by ${event.user.fullName}`);
27
+ });
28
+ ```
29
+
30
+ ## Catch-all handler
31
+
32
+ Register a handler without an action ID to catch all actions:
33
+
34
+ ```typescript title="lib/bot.ts" lineNumbers
35
+ bot.onAction(async (event) => {
36
+ console.log(`Action: ${event.actionId}, Value: ${event.value}`);
37
+ });
38
+ ```
39
+
40
+ ## ActionEvent
41
+
42
+ The `event` object passed to action handlers:
43
+
44
+ | Property | Type | Description |
45
+ |----------|------|-------------|
46
+ | `actionId` | `string` | The `id` from the Button or Select component |
47
+ | `value` | `string` (optional) | The `value` from the Button or selected option |
48
+ | `user` | `Author` | The user who clicked |
49
+ | `thread` | `Thread` | The thread containing the card |
50
+ | `messageId` | `string` | The message containing the card |
51
+ | `threadId` | `string` | Thread ID |
52
+ | `adapter` | `Adapter` | The platform adapter |
53
+ | `triggerId` | `string` (optional) | Platform trigger ID (used for opening modals) |
54
+ | `openModal` | `(modal) => Promise<void>` | Open a modal dialog |
55
+ | `raw` | `unknown` | Platform-specific event payload |
56
+
57
+ ## Pass data with buttons
58
+
59
+ Use the `value` prop on buttons to pass extra context to your handler:
60
+
61
+ ```tsx title="lib/bot.tsx"
62
+ <Button id="report" value="bug">Report Bug</Button>
63
+ <Button id="report" value="feature">Request Feature</Button>
64
+ ```
65
+
66
+ ```typescript title="lib/bot.ts" lineNumbers
67
+ bot.onAction("report", async (event) => {
68
+ if (event.value === "bug") {
69
+ // Open bug report flow
70
+ } else if (event.value === "feature") {
71
+ // Open feature request flow
72
+ }
73
+ });
74
+ ```
75
+
76
+ ## Open a modal from an action
77
+
78
+ Use `event.openModal()` to open a [modal](/docs/modals) in response to a button click:
79
+
80
+ ```tsx title="lib/bot.tsx" lineNumbers
81
+ import { Modal, TextInput, Select, SelectOption } from "chat";
82
+
83
+ bot.onAction("feedback", async (event) => {
84
+ await event.openModal(
85
+ <Modal callbackId="feedback_form" title="Send Feedback" submitLabel="Send">
86
+ <TextInput id="message" label="Your Feedback" multiline />
87
+ <Select id="category" label="Category">
88
+ <SelectOption label="Bug" value="bug" />
89
+ <SelectOption label="Feature" value="feature" />
90
+ </Select>
91
+ </Modal>
92
+ );
93
+ });
94
+ ```
95
+
96
+ <Callout type="info">
97
+ Modals are currently supported on Slack. Other platforms will receive a no-op or fallback behavior.
98
+ </Callout>
@@ -0,0 +1,217 @@
1
+ ---
2
+ title: Discord
3
+ description: Configure the Discord adapter with HTTP Interactions and Gateway WebSocket support.
4
+ type: integration
5
+ prerequisites:
6
+ - /docs/getting-started
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```sh title="Terminal"
12
+ pnpm add @chat-adapter/discord
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript title="lib/bot.ts" lineNumbers
18
+ import { Chat } from "chat";
19
+ import { createDiscordAdapter } from "@chat-adapter/discord";
20
+
21
+ const bot = new Chat({
22
+ userName: "mybot",
23
+ adapters: {
24
+ discord: createDiscordAdapter({
25
+ botToken: process.env.DISCORD_BOT_TOKEN!,
26
+ publicKey: process.env.DISCORD_PUBLIC_KEY!,
27
+ applicationId: process.env.DISCORD_APPLICATION_ID!,
28
+ mentionRoleIds: process.env.DISCORD_MENTION_ROLE_IDS?.split(","),
29
+ }),
30
+ },
31
+ });
32
+
33
+ bot.onNewMention(async (thread, message) => {
34
+ await thread.post("Hello from Discord!");
35
+ });
36
+ ```
37
+
38
+ ## Discord application setup
39
+
40
+ ### 1. Create application
41
+
42
+ 1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
43
+ 2. Click **New Application** and give it a name
44
+ 3. Note the **Application ID** from the General Information page
45
+ 4. Copy the **Public Key** from the General Information page
46
+
47
+ ### 2. Create bot
48
+
49
+ 1. Go to the **Bot** section in the left sidebar
50
+ 2. Click **Reset Token** to generate a new bot token
51
+ 3. Copy and save the token (you won't see it again)
52
+ 4. Enable these **Privileged Gateway Intents**:
53
+ - Message Content Intent
54
+ - Server Members Intent (if needed)
55
+
56
+ ### 3. Configure interactions endpoint
57
+
58
+ 1. Go to **General Information**
59
+ 2. Set **Interactions Endpoint URL** to `https://your-domain.com/api/webhooks/discord`
60
+ 3. Discord sends a PING to verify the endpoint
61
+
62
+ ### 4. Add bot to server
63
+
64
+ 1. Go to **OAuth2** then **URL Generator**
65
+ 2. Select scopes: `bot`, `applications.commands`
66
+ 3. Select bot permissions: Send Messages, Send Messages in Threads, Create Public Threads, Read Message History, Add Reactions, Attach Files
67
+ 4. Copy the generated URL and open it to invite the bot to your server
68
+
69
+ ## Architecture: HTTP Interactions vs Gateway
70
+
71
+ Discord has two ways to receive events:
72
+
73
+ **HTTP Interactions (default):**
74
+ - Receives button clicks, slash commands, and verification pings
75
+ - Works out of the box with serverless
76
+ - Does **not** receive regular messages
77
+
78
+ **Gateway WebSocket (required for messages):**
79
+ - Receives regular messages and reactions
80
+ - Requires a persistent connection
81
+ - In serverless environments, use a cron job to maintain the connection
82
+
83
+ ## Gateway setup for serverless
84
+
85
+ ### 1. Create Gateway route
86
+
87
+ ```typescript title="app/api/discord/gateway/route.ts"
88
+ import { after } from "next/server";
89
+ import { bot } from "@/lib/bot";
90
+
91
+ export const maxDuration = 800;
92
+
93
+ export async function GET(request: Request): Promise<Response> {
94
+ const cronSecret = process.env.CRON_SECRET;
95
+ if (!cronSecret) {
96
+ return new Response("CRON_SECRET not configured", { status: 500 });
97
+ }
98
+
99
+ const authHeader = request.headers.get("authorization");
100
+ if (authHeader !== `Bearer ${cronSecret}`) {
101
+ return new Response("Unauthorized", { status: 401 });
102
+ }
103
+
104
+ const durationMs = 600 * 1000;
105
+ const webhookUrl = `https://${process.env.VERCEL_URL}/api/webhooks/discord`;
106
+
107
+ return bot.adapters.discord.startGatewayListener(
108
+ { waitUntil: (task) => after(() => task) },
109
+ durationMs,
110
+ undefined,
111
+ webhookUrl
112
+ );
113
+ }
114
+ ```
115
+
116
+ ### 2. Configure Vercel Cron
117
+
118
+ ```json title="vercel.json"
119
+ {
120
+ "crons": [
121
+ {
122
+ "path": "/api/discord/gateway",
123
+ "schedule": "*/9 * * * *"
124
+ }
125
+ ]
126
+ }
127
+ ```
128
+
129
+ This runs every 9 minutes, ensuring overlap with the 10-minute listener duration.
130
+
131
+ ### 3. Add environment variables
132
+
133
+ Add `CRON_SECRET` to your Vercel project settings.
134
+
135
+ ## Role mentions
136
+
137
+ By default, only direct user mentions (`@BotName`) trigger `onNewMention` handlers. To also trigger on role mentions (e.g., `@AI`):
138
+
139
+ 1. Create a role in your Discord server (e.g., "AI")
140
+ 2. Assign the role to your bot
141
+ 3. Copy the role ID (right-click role in server settings with Developer Mode enabled)
142
+ 4. Add to `mentionRoleIds`:
143
+
144
+ ```typescript title="lib/bot.ts" lineNumbers
145
+ createDiscordAdapter({
146
+ botToken: process.env.DISCORD_BOT_TOKEN!,
147
+ publicKey: process.env.DISCORD_PUBLIC_KEY!,
148
+ applicationId: process.env.DISCORD_APPLICATION_ID!,
149
+ mentionRoleIds: ["1457473602180878604"],
150
+ });
151
+ ```
152
+
153
+ Or set `DISCORD_MENTION_ROLE_IDS` as a comma-separated string.
154
+
155
+ ## Configuration
156
+
157
+ | Option | Required | Description |
158
+ |--------|----------|-------------|
159
+ | `botToken` | Yes | Discord bot token |
160
+ | `publicKey` | Yes | Application public key (for webhook signature verification) |
161
+ | `applicationId` | Yes | Discord application ID |
162
+ | `mentionRoleIds` | No | Array of role IDs that trigger mention handlers |
163
+
164
+ ## Environment variables
165
+
166
+ ```bash title=".env.local"
167
+ DISCORD_BOT_TOKEN=your-bot-token
168
+ DISCORD_PUBLIC_KEY=your-application-public-key
169
+ DISCORD_APPLICATION_ID=your-application-id
170
+ DISCORD_MENTION_ROLE_IDS=1234567890,0987654321 # Optional
171
+ CRON_SECRET=your-random-secret # For Gateway cron
172
+ ```
173
+
174
+ ## Features
175
+
176
+ | Feature | Supported |
177
+ |---------|-----------|
178
+ | Mentions | Yes |
179
+ | Reactions (add/remove) | Yes |
180
+ | Cards (Embeds) | Yes |
181
+ | Modals | No |
182
+ | Streaming | Post+Edit fallback |
183
+ | DMs | Yes |
184
+ | Ephemeral messages | No (DM fallback) |
185
+ | File uploads | Yes |
186
+ | Typing indicator | Yes |
187
+ | Message history | Yes |
188
+
189
+ ## Testing
190
+
191
+ Run a local tunnel (e.g., ngrok) to test webhooks locally:
192
+
193
+ ```sh title="Terminal"
194
+ ngrok http 3000
195
+ ```
196
+
197
+ Update the Interactions Endpoint URL in the Discord Developer Portal to your ngrok URL.
198
+
199
+ ## Troubleshooting
200
+
201
+ ### Bot not responding to messages
202
+
203
+ 1. **Check Gateway connection**: Messages require the Gateway WebSocket, not just HTTP interactions
204
+ 2. **Verify Message Content Intent**: Enable this in the Bot settings
205
+ 3. **Check bot permissions**: Ensure the bot can read messages in the channel
206
+
207
+ ### Role mentions not triggering
208
+
209
+ 1. **Verify role ID**: Enable Developer Mode in Discord settings, then right-click the role
210
+ 2. **Check `mentionRoleIds` config**: Ensure the role ID is in the array
211
+ 3. **Confirm bot has the role**: The bot must have the role assigned
212
+
213
+ ### Signature verification failing
214
+
215
+ 1. **Check public key format**: Should be a 64-character hex string (lowercase)
216
+ 2. **Verify endpoint URL**: Must exactly match what's configured in Discord Developer Portal
217
+ 3. **Check for body parsing**: Don't parse the request body before verification