@mastra/discord 1.0.0 → 1.1.0

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.
@@ -0,0 +1,554 @@
1
+ import { ChannelAdapterConfig, ChannelConfig, ChannelConnectResult, ChannelHandlers, ChannelInstallationInfo, ChannelPlatformInfo, ChannelProvider, StreamingConfig, WaitUntilFn } from "@mastra/core/channels";
2
+ import { ChannelsStorage } from "@mastra/core/storage";
3
+ import { DiscordAdapter, DiscordAdapter as DiscordAdapter$1, DiscordAdapterConfig, DiscordAdapterConfig as DiscordAdapterConfig$1, createDiscordAdapter } from "@chat-adapter/discord";
4
+ import { Mastra } from "@mastra/core/mastra";
5
+ import { ApiRoute } from "@mastra/core/server";
6
+ //#region src/types.d.ts
7
+ /**
8
+ * Default Discord REST base — **including** the `/api/v10` version segment. The
9
+ * client concatenates this with the request path verbatim, so an override must
10
+ * carry its own version segment (`https://example.test/api/v10`); passing a bare
11
+ * origin produces unversioned request URLs.
12
+ */
13
+ declare const DISCORD_API_BASE_URL = "https://discord.com/api/v10";
14
+ /** Base for the OAuth2 authorize (bot-invite) URL. */
15
+ declare const DISCORD_OAUTH_AUTHORIZE_URL = "https://discord.com/oauth2/authorize";
16
+ /**
17
+ * Named Discord permission bits (a subset). Discord permissions are a 53+ bit
18
+ * field, so they are represented as `bigint` and serialized to a decimal string
19
+ * in the invite URL.
20
+ *
21
+ * @see https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
22
+ */
23
+ declare const DISCORD_PERMISSIONS: {
24
+ readonly ADD_REACTIONS: bigint;
25
+ readonly VIEW_CHANNEL: bigint;
26
+ readonly SEND_MESSAGES: bigint;
27
+ readonly EMBED_LINKS: bigint;
28
+ readonly ATTACH_FILES: bigint;
29
+ readonly READ_MESSAGE_HISTORY: bigint;
30
+ readonly USE_APPLICATION_COMMANDS: bigint;
31
+ readonly SEND_MESSAGES_IN_THREADS: bigint;
32
+ };
33
+ /**
34
+ * Default permissions requested in the bot-invite URL: enough for a chat agent
35
+ * to read and reply (in channels and threads), post embeds/files, add reactions,
36
+ * and see recent history. Kept intentionally minimal — no moderation or manage
37
+ * permissions. Callers can override via {@link DiscordConnectOptions.permissions}
38
+ * or {@link DiscordProviderConfig.permissions}.
39
+ */
40
+ declare const DEFAULT_INVITE_PERMISSIONS: bigint;
41
+ /** OAuth2 scopes requested by the bot-invite URL. */
42
+ declare const DEFAULT_INVITE_SCOPES: readonly ['bot', 'applications.commands'];
43
+ /**
44
+ * A Discord application command as it goes over the wire (bulk-overwrite
45
+ * registration). Only the `CHAT_INPUT` (slash) shape is modeled here.
46
+ * @see https://discord.com/developers/docs/interactions/application-commands
47
+ */
48
+ interface DiscordCommand {
49
+ /** 1-32 chars, lowercase `[a-z0-9_-]` (Discord also allows a leading `-`/`_`). */
50
+ name: string;
51
+ /** 1-100 chars. */
52
+ description: string;
53
+ }
54
+ /** Command input accepted by the provider — a bare name or `{ name, description }`. */
55
+ type DiscordCommandInput = string | {
56
+ name: string;
57
+ description?: string;
58
+ };
59
+ /**
60
+ * App-level credentials for a single Discord application. Discord has **no
61
+ * programmatic app creation** — these come from the Developer Portal and are
62
+ * stored **once** (one app, many guilds). `botToken` is the only secret;
63
+ * `publicKey` and `applicationId` are public.
64
+ */
65
+ interface DiscordAppConfig {
66
+ /** Bot token (`Authorization: Bot <token>`) — the control-plane credential. Secret. */
67
+ botToken: string;
68
+ /** Ed25519 public key used by the adapter to verify interaction signatures. */
69
+ publicKey: string;
70
+ /** The application (client) id — used as `client_id` in the invite URL. */
71
+ applicationId: string;
72
+ }
73
+ /**
74
+ * Configuration for {@link DiscordProvider}.
75
+ *
76
+ * Discord is **one app, many guilds**: a single application's credentials are
77
+ * reused across every guild the bot is invited to. There is no per-install
78
+ * token — "installing" is a bot invite (an OAuth2 authorize URL), and tenancy
79
+ * is keyed by `guildId` per interaction.
80
+ */
81
+ interface DiscordProviderConfig {
82
+ /**
83
+ * App credentials (bot token / public key / application id). May be omitted
84
+ * here and provided via the `DISCORD_BOT_TOKEN` / `DISCORD_PUBLIC_KEY` /
85
+ * `DISCORD_APPLICATION_ID` env vars, or later via {@link DiscordProvider.configure}.
86
+ * Whichever source resolves first is persisted **once** to channels storage.
87
+ */
88
+ app?: Partial<DiscordAppConfig>;
89
+ /**
90
+ * Public HTTPS base URL for the interactions endpoint (the URL set once in the
91
+ * Developer Portal). May be omitted and auto-detected from the Mastra server.
92
+ */
93
+ baseUrl?: string;
94
+ /**
95
+ * Persistence for the app config + per-agent installs. Defaults to Mastra's
96
+ * channels storage when attached to a Mastra instance with storage, and falls
97
+ * back to an in-memory store otherwise (dev/test — not persisted across restarts).
98
+ */
99
+ storage?: ChannelsStorage;
100
+ /**
101
+ * Override the Discord REST origin (e.g. a test mock). Defaults to
102
+ * {@link DISCORD_API_BASE_URL}.
103
+ */
104
+ apiBaseUrl?: string;
105
+ /**
106
+ * Passphrase for encrypting the stored `botToken` at rest (AES-256-GCM).
107
+ * Defaults to the `MASTRA_ENCRYPTION_KEY` env var. When unset, the token is
108
+ * stored in plaintext (fine for the in-memory dev store; set a key for any
109
+ * persistent backend).
110
+ */
111
+ encryptionKey?: string;
112
+ /**
113
+ * Permissions bitfield requested in the invite URL. Overrides
114
+ * {@link DEFAULT_INVITE_PERMISSIONS}. Accepts a `bigint` or a decimal string.
115
+ */
116
+ permissions?: bigint | string;
117
+ /**
118
+ * Default slash commands registered for every connected agent (a per-agent
119
+ * list can override via {@link DiscordConnectOptions.commands}). Defaults to
120
+ * the conventional `/help` seed.
121
+ */
122
+ commands?: DiscordCommandInput[];
123
+ /**
124
+ * Where slash commands are registered:
125
+ * - `'guild'` (default) — per-guild (`PUT …/guilds/{id}/commands`), updates
126
+ * instantly; registered on first-seen guild, keyed by a per-guild hash.
127
+ * - `'global'` — app-wide (`PUT …/commands`), eventually consistent; registered
128
+ * once regardless of guild.
129
+ *
130
+ * @default 'guild'
131
+ */
132
+ commandScope?: 'guild' | 'global';
133
+ /**
134
+ * Keep the serverless invocation alive while the agent stream runs after the
135
+ * interaction is acked (Vercel/AWS Lambda). See `ChannelConfig.waitUntil`.
136
+ */
137
+ waitUntil?: WaitUntilFn;
138
+ /**
139
+ * Start the Gateway WebSocket (core-owned) so the bot receives DMs, @mentions,
140
+ * and reactions in addition to slash commands. Set `false` for interactions-
141
+ * only serverless deployments. Forwarded to the adapter entry as `gateway`.
142
+ *
143
+ * @default true
144
+ */
145
+ gateway?: boolean;
146
+ /**
147
+ * Role IDs (in addition to direct user mentions) that trigger mention
148
+ * handlers. Forwarded to the adapter (`DISCORD_MENTION_ROLE_IDS` env fallback).
149
+ */
150
+ mentionRoleIds?: string[];
151
+ /**
152
+ * Return interaction response flags (e.g. ephemeral, flag 64) for the initial
153
+ * deferred slash-command response. Flags are locked at defer time, so this
154
+ * fires on the deferred ACK, not the followup. Forwarded to the adapter.
155
+ */
156
+ interactionFlags?: DiscordAdapterConfig$1['interactionFlags'];
157
+ /** Logger forwarded to the underlying `DiscordAdapter` for internal error reporting. */
158
+ logger?: DiscordAdapterConfig$1['logger'];
159
+ /**
160
+ * Stream agent text to Discord as it generates, via the adapter's post-and-edit
161
+ * (`editMessage`) loop. Discord has no native token streaming, so this
162
+ * chunk-edits the interaction followup (throttle via `updateIntervalMs`).
163
+ *
164
+ * @default true
165
+ */
166
+ streaming?: StreamingConfig;
167
+ /**
168
+ * Keep a typing indicator alive during generation. Set `false` to disable.
169
+ *
170
+ * @default true
171
+ */
172
+ typingStatus?: boolean;
173
+ /** Override built-in event handlers. Forwarded to `AgentChannels`. */
174
+ handlers?: ChannelHandlers;
175
+ /** Which media types to send inline to the model. See `ChannelConfig.inlineMedia`. */
176
+ inlineMedia?: ChannelConfig['inlineMedia'];
177
+ /** Promote URLs in message text to file parts. See `ChannelConfig.inlineLinks`. */
178
+ inlineLinks?: ChannelConfig['inlineLinks'];
179
+ /** State adapter for deduplication, locking, and subscriptions. See `ChannelConfig.state`. */
180
+ state?: ChannelConfig['state'];
181
+ /** Fetch recent thread messages when the agent joins mid-conversation. See `ChannelConfig.threadContext`. */
182
+ threadContext?: ChannelConfig['threadContext'];
183
+ /** Additional options passed directly to the Chat SDK. See `ChannelConfig.chatOptions`. */
184
+ chatOptions?: ChannelConfig['chatOptions'];
185
+ /** Resolve the memory `resourceId` before a channel thread is created. See `ChannelConfig.resolveResourceId`. */
186
+ resolveResourceId?: ChannelConfig['resolveResourceId'];
187
+ /** Resolve `waitUntil` from the request's Hono `Context`. See `ChannelConfig.resolveWaitUntil`. */
188
+ resolveWaitUntil?: ChannelConfig['resolveWaitUntil'];
189
+ /** CORS configuration for the generated interactions route. */
190
+ cors?: ChannelAdapterConfig['cors'];
191
+ /** Override how errors are rendered in Discord messages. See `ChannelAdapterConfig.formatError`. */
192
+ formatError?: ChannelAdapterConfig['formatError'];
193
+ /**
194
+ * How tool calls are rendered in the reply. Discord has native embeds +
195
+ * action-row buttons, so this defaults to `'cards'` (unlike Telegram's
196
+ * `'text'`). See `ChannelAdapterConfig.toolDisplay`.
197
+ *
198
+ * @default 'cards'
199
+ */
200
+ toolDisplay?: ChannelAdapterConfig['toolDisplay'];
201
+ /**
202
+ * Whether to expose channel reaction tools (`add_reaction`/`remove_reaction`)
203
+ * to the agent. Set `false` for models without function calling. See `ChannelConfig.tools`.
204
+ *
205
+ * @default true
206
+ */
207
+ tools?: ChannelConfig['tools'];
208
+ /** Called after an agent successfully connects and the installation is persisted. */
209
+ onInstall?: (installation: DiscordInstallation) => void | Promise<void>;
210
+ }
211
+ /** Options accepted by {@link DiscordProvider.connect}. */
212
+ interface DiscordConnectOptions {
213
+ /**
214
+ * The guild to install into. When the bot is **already** a member of this
215
+ * guild, connect binds immediately (`{ type: 'immediate' }`) and registers its
216
+ * commands; otherwise the operator is sent the invite URL and the guild is
217
+ * confirmed lazily off the first inbound interaction's authoritative `guild_id`.
218
+ */
219
+ guildId?: string;
220
+ /** Display name for this installation. Defaults to the application's name. */
221
+ name?: string;
222
+ /**
223
+ * Slash commands to register for this agent. Overrides
224
+ * {@link DiscordProviderConfig.commands}. Defaults to the `/help` seed.
225
+ */
226
+ commands?: DiscordCommandInput[];
227
+ /** Permissions bitfield override for this invite (bigint or decimal string). */
228
+ permissions?: bigint | string;
229
+ }
230
+ /**
231
+ * A per-agent Discord installation. Unlike Telegram (one token = one row),
232
+ * Discord installs are keyed by agent and track the set of `guildIds` the bot is
233
+ * live in. **No secrets live here** — the bot token lives once in the app config
234
+ * ({@link DiscordAppConfig}); this row only holds routing + per-guild command
235
+ * versions. Persisted through {@link DiscordInstallStore}.
236
+ */
237
+ interface DiscordInstallation {
238
+ /** Stable installation id. */
239
+ id: string;
240
+ /** The agent this installation is bound to. */
241
+ agentId: string;
242
+ /**
243
+ * Opaque id embedded in the interactions route path
244
+ * (`/discord/events/:webhookId`). Used to resolve the install on inbound POSTs.
245
+ */
246
+ webhookId: string;
247
+ /**
248
+ * `pending` — invite issued, no guild confirmed yet.
249
+ * `active` — the bot is live in at least one guild (or bound to a known guild).
250
+ */
251
+ status: 'active' | 'pending';
252
+ /** Guilds this agent's bot is confirmed live in (tenancy keys). */
253
+ guildIds: string[];
254
+ /** Display name (application name or an operator-supplied override). */
255
+ displayName?: string;
256
+ /** Normalized slash commands to register for this agent (resolved at connect). */
257
+ commands?: DiscordCommand[];
258
+ /**
259
+ * Registered-command version hash, keyed by `guildId` (guild scope) or the
260
+ * literal `'global'` (global scope). Used to skip re-`PUT`ting commands when
261
+ * unchanged (200/day/guild rate limit).
262
+ */
263
+ commandVersions?: Record<string, string>;
264
+ /** When the installation was created. */
265
+ installedAt: Date;
266
+ }
267
+ //#endregion
268
+ //#region src/discord-provider.d.ts
269
+ /**
270
+ * Resolve the per-adapter config the provider applies to the Discord entry in
271
+ * `AgentChannels.adapters`. Discord has native embeds + action-row buttons, so
272
+ * `toolDisplay` defaults to `'cards'` (unlike Telegram's `'text'`); `streaming`
273
+ * post-and-edits the interaction followup; `gateway` (default `true`) makes core
274
+ * own the DM/mention Gateway reconnection loop.
275
+ */
276
+ declare function resolveDiscordAdapterConfig(config: Pick<DiscordProviderConfig, 'streaming' | 'typingStatus' | 'toolDisplay' | 'gateway'>): {
277
+ streaming: StreamingConfig;
278
+ typingStatus: boolean;
279
+ toolDisplay: ChannelAdapterConfig['toolDisplay'];
280
+ gateway: boolean;
281
+ };
282
+ /**
283
+ * Discord channel provider for Mastra — a {@link ChannelProvider} over
284
+ * `@chat-adapter/discord`. The adapter handles the protocol (Ed25519 request
285
+ * verification, PING/PONG, deferrals, embeds/buttons, the Gateway bridge,
286
+ * post-and-edit streaming); this provider adds the lifecycle layer.
287
+ *
288
+ * **One app, many guilds.** Discord has no programmatic app creation, so a
289
+ * single application's credentials (bot token / public key / application id) are
290
+ * stored **once** and reused across every guild. "Installing" is a bot invite —
291
+ * an OAuth2 authorize URL — not a token exchange, so `connect()` returns the
292
+ * `oauth` variant. A guild is confirmed either eagerly (the bot is already a
293
+ * member) or lazily off the first inbound interaction's authoritative `guild_id`
294
+ * (see {@link activateGuild}).
295
+ *
296
+ * **Gateway is core's job.** The mounted interactions route only ever receives
297
+ * HTTP Interactions (PING, slash commands, buttons). DMs / @mentions / reactions
298
+ * arrive over the Gateway WebSocket, whose reconnection loop core owns: setting
299
+ * `gateway: true` (default) on the adapter entry is enough — the wrapper never
300
+ * calls `startGatewayListener` or runs a reconnect loop.
301
+ *
302
+ * Implemented (issues `mastra-discord-13x.2` + `.3`): the app-config +
303
+ * guild-keyed install store, `connect()`/`disconnect()`, OAuth2 invite-URL
304
+ * generation, the interactions route (raw-body delegation), and adapter/Gateway
305
+ * wiring. Command registration lands in `mastra-discord-13x.4`.
306
+ */
307
+ declare class DiscordProvider implements ChannelProvider {
308
+ #private;
309
+ readonly id = "discord";
310
+ constructor(config?: DiscordProviderConfig);
311
+ /**
312
+ * Called by Mastra when this channel is registered.
313
+ * @internal
314
+ */
315
+ __attach(mastra: Mastra): void;
316
+ /**
317
+ * The interactions route: a single POST endpoint keyed by an opaque
318
+ * `webhookId`. The handler passes the **RAW** request bytes straight to
319
+ * `adapter.handleWebhook` — Ed25519 verification + PING/PONG live in the
320
+ * adapter, and any middleware that parsed/re-serialized the body would break
321
+ * every signature. `requiresAuth: false` (Discord authenticates via Ed25519,
322
+ * not a bearer token). Auto-initializes on first hit (mirrors `@mastra/slack`).
323
+ */
324
+ getRoutes(): ApiRoute[];
325
+ /** Discovery metadata for the editor UI. */
326
+ getInfo(): ChannelPlatformInfo;
327
+ /**
328
+ * Restore state from storage: mark the provider configured when an app config
329
+ * or any active installation exists, and rebuild an adapter + `AgentChannels`
330
+ * per active install so its Gateway loop (core-owned) starts. Idempotent.
331
+ */
332
+ initialize(): Promise<void>;
333
+ /**
334
+ * Provide or clear the app credentials at runtime. An object merges/overrides
335
+ * `botToken` / `publicKey` / `applicationId` (persisted on the next `connect`);
336
+ * `null` clears the stored app config.
337
+ */
338
+ configure(credentials: Partial<DiscordAppConfig> | null): Promise<void>;
339
+ /**
340
+ * Connect an agent to Discord.
341
+ *
342
+ * - Ensures the app config exists (stored, or supplied via provider config /
343
+ * `DISCORD_*` env and persisted once, after validation). Throws if none —
344
+ * Discord apps are created in the Developer Portal, not programmatically.
345
+ * - Validates the bot token via `GET /applications/@me`.
346
+ * - If `options.guildId` is given **and the bot is already in that guild**,
347
+ * binds immediately (`{ type: 'immediate' }`).
348
+ * - Otherwise persists a pending install and returns the OAuth2 bot-invite URL
349
+ * (`{ type: 'oauth', authorizationUrl }`); the guild activates lazily on the
350
+ * first interaction.
351
+ */
352
+ connect(agentId: string, options?: DiscordConnectOptions): Promise<ChannelConnectResult>;
353
+ /**
354
+ * Confirm a guild off an inbound interaction's authoritative `guild_id` and
355
+ * mark the installation active (lazy activation). Called by the interactions
356
+ * route on first use. Returns the updated installation, or `null` if the
357
+ * `webhookId` is unknown.
358
+ */
359
+ activateGuild(webhookId: string, guildId: string): Promise<DiscordInstallation | null>;
360
+ /**
361
+ * Disconnect an agent from Discord: remove its installation row and drop the
362
+ * adapter entry.
363
+ *
364
+ * **Limitation:** the Gateway loop is owned by core (it calls
365
+ * `startGatewayListener` itself; there is no `stopGatewayListener`), so
366
+ * disconnect cannot kill an in-flight gateway window — it lapses at the next
367
+ * duration boundary. Contrast Telegram's clean `stopPolling()`.
368
+ */
369
+ disconnect(agentId: string): Promise<void>;
370
+ /** List installations (public info only — no secrets). */
371
+ listInstallations(): Promise<ChannelInstallationInfo[]>;
372
+ /** The full installation for an agent (no secrets live on it), or `null`. */
373
+ getInstallation(agentId: string): Promise<DiscordInstallation | null>;
374
+ /** Whether the Discord app is configured (credentials resolvable). */
375
+ isConfigured(): boolean;
376
+ /** The live `DiscordAdapter` for an installation id, if one is active. */
377
+ getAdapter(installationId: string): DiscordAdapter$1 | undefined;
378
+ }
379
+ //#endregion
380
+ //#region src/install-store.d.ts
381
+ /** Platform identifier used for every stored record, config, and route. */
382
+ declare const PLATFORM = "discord";
383
+ /**
384
+ * Two-tier persistence for Discord, layered over the platform-agnostic
385
+ * `ChannelsStorage` (the same store `@mastra/slack` / `@mastra/telegram` use).
386
+ *
387
+ * - **App tier** (`saveConfig`/`getConfig`) — the one application's
388
+ * `botToken` + `publicKey` + `applicationId`, stored **once** (one app, many
389
+ * guilds). `botToken` is AES-256-GCM encrypted at rest when an `encryptionKey`
390
+ * is supplied.
391
+ * - **Install tier** (`getInstallationByAgent`/`…ByWebhookId`/`saveInstallation`)
392
+ * — one row per agent, keyed by agent, tracking the `guildIds` the bot is live
393
+ * in. Secrets are **not** duplicated per row.
394
+ */
395
+ declare class DiscordInstallStore {
396
+ #private;
397
+ private readonly storage;
398
+ private readonly encryptionKey?;
399
+ constructor(storage: ChannelsStorage, encryptionKey?: string | undefined);
400
+ /** The stored app config, if any (bot token decrypted). */
401
+ getAppConfig(): Promise<DiscordAppConfig | null>;
402
+ /** Persist the app config once (bot token encrypted at rest when keyed). */
403
+ saveAppConfig(app: DiscordAppConfig): Promise<void>;
404
+ /** Remove the stored app config. */
405
+ deleteAppConfig(): Promise<void>;
406
+ /** The installation for an agent, if any. */
407
+ getByAgent(agentId: string): Promise<DiscordInstallation | null>;
408
+ /** Look up an installation by the routing id in its interactions-route path. */
409
+ getByWebhookId(webhookId: string): Promise<DiscordInstallation | null>;
410
+ /**
411
+ * Find the installation that owns a guild (tenancy lookup by `guildId`). Used
412
+ * when the routing key is a guild rather than a webhook id.
413
+ *
414
+ * `guildIds` is per-agent and nothing enforces exclusivity, so two agents can
415
+ * legitimately be installed into the same guild. Returning the first match
416
+ * would make routing depend on storage row order — the same interaction could
417
+ * reach a different agent on the next lookup. Instead the **oldest** install
418
+ * wins (ties broken by id), which is stable across restarts and storage
419
+ * backends, and the ambiguity is logged once so an operator can see it.
420
+ *
421
+ * `ChannelsStorage` has no query-by-data-field, so this necessarily lists the
422
+ * platform's installations; it is not on the interactions hot path, which
423
+ * routes by `webhookId`.
424
+ */
425
+ getByGuildId(guildId: string): Promise<DiscordInstallation | null>;
426
+ /** Insert or replace an installation. */
427
+ save(installation: DiscordInstallation): Promise<void>;
428
+ /** All Discord installations (active and pending). */
429
+ list(): Promise<DiscordInstallation[]>;
430
+ /** Remove an agent's installation, if present. */
431
+ deleteByAgent(agentId: string): Promise<void>;
432
+ }
433
+ /** Project an installation to its public, secret-free info for the editor UI. */
434
+ declare function toInstallationInfo(install: DiscordInstallation): ChannelInstallationInfo;
435
+ //#endregion
436
+ //#region src/discord-client.d.ts
437
+ /**
438
+ * The subset of Discord's application object the control plane reads back from
439
+ * `GET /applications/@me`.
440
+ * @see https://discord.com/developers/docs/resources/application#application-object
441
+ */
442
+ interface DiscordApplication {
443
+ /** The application (client) id. */
444
+ id: string;
445
+ /** The application's name. */
446
+ name: string;
447
+ }
448
+ /**
449
+ * Call the Discord REST API with Bot-token auth. `method` is required; supplying
450
+ * `body` adds the JSON `content-type` header and serializes the payload. Returns
451
+ * the raw {@link Response} so callers can classify the status themselves rather
452
+ * than having every non-2xx collapse into a thrown error.
453
+ *
454
+ * This is the **control plane only** — validating the app and reading guild
455
+ * membership. Sending replies uses the interaction token inside the adapter,
456
+ * never this bot-token path.
457
+ */
458
+ declare function discordRequest(botToken: string, method: string, path: string, apiBaseUrl?: string, body?: unknown): Promise<Response>;
459
+ /**
460
+ * Validate the app's bot token and resolve the application identity via
461
+ * `GET /applications/@me` (Bot auth). Throws if the token is rejected.
462
+ *
463
+ * @see https://discord.com/developers/docs/resources/application#get-current-application
464
+ */
465
+ declare function validateApp(botToken: string, apiBaseUrl?: string): Promise<DiscordApplication>;
466
+ /**
467
+ * Whether the bot is already a member of a guild. `GET /guilds/{id}` (Bot auth)
468
+ * returns `200` only when the bot is in the guild. Absence is `404` (`10004
469
+ * Unknown Guild`) — Discord standardized on 404 rather than 403 so the response
470
+ * can't confirm a guild exists to a caller that lacks access — with legacy
471
+ * deployments still answering `403` (`50001 Missing Access`).
472
+ *
473
+ * Any **other** failure (`401`, `429`, `5xx`) is a transient or auth problem,
474
+ * not evidence of absence, and throws. Collapsing those into `false` would send
475
+ * a caller whose bot *is* in the guild down the invite path on a rate limit.
476
+ *
477
+ * @see https://discord.com/developers/docs/resources/guild#get-guild
478
+ */
479
+ declare function guildHealthCheck(botToken: string, guildId: string, apiBaseUrl?: string): Promise<boolean>;
480
+ /**
481
+ * Bulk-overwrite a **guild's** slash commands (`PUT …/guilds/{guildId}/commands`).
482
+ * Guild-scoped commands update **instantly**. Register on first-seen guild;
483
+ * callers skip the call when the command hash is unchanged (200 creates/day/guild).
484
+ *
485
+ * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands
486
+ */
487
+ declare function registerGuildCommands(botToken: string, applicationId: string, guildId: string, commands: readonly DiscordCommand[], apiBaseUrl?: string): Promise<void>;
488
+ /**
489
+ * Bulk-overwrite the app's **global** slash commands (`PUT …/commands`).
490
+ * Eventually consistent (propagation is not instant — don't quote a fixed
491
+ * number). Opt-in via `commandScope: 'global'`.
492
+ *
493
+ * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands
494
+ */
495
+ declare function registerGlobalCommands(botToken: string, applicationId: string, commands: readonly DiscordCommand[], apiBaseUrl?: string): Promise<void>;
496
+ /** Inputs for {@link buildInviteUrl}. */
497
+ interface BuildInviteUrlOptions {
498
+ /** The application (client) id — becomes `client_id`. */
499
+ applicationId: string;
500
+ /** Permissions bitfield (bigint or decimal string). Defaults to {@link DEFAULT_INVITE_PERMISSIONS}. */
501
+ permissions?: bigint | string;
502
+ /** OAuth2 scopes. Defaults to {@link DEFAULT_INVITE_SCOPES} (`bot applications.commands`). */
503
+ scopes?: readonly string[];
504
+ /**
505
+ * Preselect this guild in the authorize screen and lock the picker, so the
506
+ * operator can't authorize a different guild than the one `connect()` recorded.
507
+ */
508
+ guildId?: string;
509
+ }
510
+ /**
511
+ * Build the OAuth2 bot-invite URL. This *is* the Discord "install" flow: the
512
+ * operator opens it, picks a guild, and authorizes — there is no token exchange.
513
+ * `scope=bot applications.commands` + a `permissions` bitfield.
514
+ *
515
+ * @see https://discord.com/developers/docs/topics/oauth2#bot-authorization-flow
516
+ */
517
+ declare function buildInviteUrl(options: BuildInviteUrlOptions): string;
518
+ //#endregion
519
+ //#region src/commands.d.ts
520
+ /**
521
+ * Conventional command seed registered when a connect provides none. Discord
522
+ * surfaces `/help` in the command picker; the built-in DM/mention handling
523
+ * covers everything else, so the seed is intentionally tiny.
524
+ * @see https://discord.com/developers/docs/interactions/application-commands
525
+ */
526
+ declare const DEFAULT_COMMANDS: readonly DiscordCommandInput[];
527
+ /**
528
+ * Map user-supplied commands to Discord `CHAT_INPUT` command shapes, enforcing
529
+ * the API constraints: `name` is lowercased, stripped of a leading slash,
530
+ * reduced to `[a-z0-9_-]`, and clamped to 1-32 chars; `description` defaults to
531
+ * `Run /<name>` and is clamped to 1-100 chars. Empty or duplicate names are
532
+ * dropped.
533
+ *
534
+ * @see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-naming
535
+ */
536
+ declare function normalizeCommands(raw: readonly DiscordCommandInput[] | undefined): DiscordCommand[];
537
+ /**
538
+ * Stable content hash of a normalized command list, used to skip re-`PUT`ting a
539
+ * guild/global command set that hasn't changed (Discord allows only 200
540
+ * application-command creates per day, per guild). Order-independent: commands
541
+ * are sorted by name before hashing.
542
+ */
543
+ declare function hashCommands(commands: readonly DiscordCommand[]): string;
544
+ //#endregion
545
+ //#region src/crypto.d.ts
546
+ /** Whether a stored value was produced by {@link encrypt}. */
547
+ declare function isEncrypted(value: string): boolean;
548
+ /** Encrypt a UTF-8 string with a per-value random salt + IV. */
549
+ declare function encrypt(plaintext: string, passphrase: string): string;
550
+ /** Decrypt a value from {@link encrypt}. Plaintext (unprefixed) is returned unchanged. */
551
+ declare function decrypt(value: string, passphrase: string): string;
552
+ //#endregion
553
+ export { type BuildInviteUrlOptions, DEFAULT_COMMANDS, DEFAULT_INVITE_PERMISSIONS, DEFAULT_INVITE_SCOPES, DISCORD_API_BASE_URL, DISCORD_OAUTH_AUTHORIZE_URL, DISCORD_PERMISSIONS, DiscordAdapter, type DiscordAdapterConfig, type DiscordAppConfig, type DiscordApplication, type DiscordCommand, type DiscordCommandInput, type DiscordConnectOptions, DiscordInstallStore, type DiscordInstallation, DiscordProvider, type DiscordProviderConfig, PLATFORM, buildInviteUrl, createDiscordAdapter, decrypt, discordRequest, encrypt, guildHealthCheck, hashCommands, isEncrypted, normalizeCommands, registerGlobalCommands, registerGuildCommands, resolveDiscordAdapterConfig, toInstallationInfo, validateApp };
554
+ //# sourceMappingURL=index.d.ts.map