@mastra/discord 1.1.0-alpha.0 → 1.2.0-alpha.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @mastra/discord
2
2
 
3
+ ## 1.2.0-alpha.0
4
+
5
+ ### Minor Changes
6
+
7
+ - A bot token is now enough to configure `DiscordProvider` — `applicationId` and `publicKey` are resolved automatically from Discord's `GET /applications/@me` when omitted, then persisted alongside the token. ([#25125](https://github.com/mastra-ai/mastra/pull/25125))
8
+
9
+ ```typescript
10
+ // Before: all three credentials were required
11
+ new DiscordProvider({ app: { botToken, publicKey, applicationId } });
12
+
13
+ // After: the bot token alone works
14
+ new DiscordProvider({ app: { botToken } });
15
+ ```
16
+
17
+ Explicitly supplied values (config, `DISCORD_PUBLIC_KEY` / `DISCORD_APPLICATION_ID` env vars, or `configure()`) still take precedence over the resolved ones.
18
+
19
+ ### Patch Changes
20
+
21
+ - Switching the Discord bot token via `configure()` now replaces the app config instead of merging into it. Previously the prior application's `publicKey` and `applicationId` survived the switch — including in persisted config — so the old application's Ed25519 key kept verifying inbound webhooks while the new bot token was active. The provider now drops everything derived from the old token and re-resolves the new application's identity from `GET /applications/@me`. ([#25149](https://github.com/mastra-ai/mastra/pull/25149))
22
+
23
+ For the same reason, `publicKey` and `applicationId` no longer fall back to `DISCORD_PUBLIC_KEY` / `DISCORD_APPLICATION_ID` when the bot token is supplied via config or `configure()` — stale environment values from a different application would otherwise attach to the new token. Environment fallback for those fields applies only when the bot token itself comes from `DISCORD_BOT_TOKEN`.
24
+
25
+ - Updated dependencies [[`68cc668`](https://github.com/mastra-ai/mastra/commit/68cc66800e5ce6f5d62189fc7b5ef9d71cf80971), [`781762b`](https://github.com/mastra-ai/mastra/commit/781762b2dcd0c8cc7f9b8ab73824ec45a5225db7), [`cc0da13`](https://github.com/mastra-ai/mastra/commit/cc0da13b826d5f74213c4d8c470acf8698542249), [`f2c3f8c`](https://github.com/mastra-ai/mastra/commit/f2c3f8c74e1d7bc7baca5303b36320b0b361775c), [`1fe1c2b`](https://github.com/mastra-ai/mastra/commit/1fe1c2b6f0b29481dca62a9199af751d594e3ea6), [`279a736`](https://github.com/mastra-ai/mastra/commit/279a736c62495cac0f247ab1402a8c80bccc892a), [`4edc93d`](https://github.com/mastra-ai/mastra/commit/4edc93dedadb89686aad75a4853cb0aa807d256e)]:
26
+ - @mastra/core@1.72.0-alpha.2
27
+
28
+ ## 1.1.0
29
+
30
+ ### Minor Changes
31
+
32
+ - Added `@mastra/discord` for connecting Mastra agents to Discord. One Discord app serves many servers, and agents respond to slash commands, DMs, and @mentions. Set `encryptionKey` or `MASTRA_ENCRYPTION_KEY` to encrypt the stored bot token at rest. ([#25002](https://github.com/mastra-ai/mastra/pull/25002))
33
+
34
+ ```ts
35
+ import { Mastra } from '@mastra/core';
36
+ import { DiscordProvider } from '@mastra/discord';
37
+
38
+ // App credentials from the Discord Developer Portal (or the DISCORD_BOT_TOKEN /
39
+ // DISCORD_PUBLIC_KEY / DISCORD_APPLICATION_ID env vars):
40
+ const discord = new DiscordProvider({
41
+ app: {
42
+ botToken: process.env.DISCORD_BOT_TOKEN!,
43
+ publicKey: process.env.DISCORD_PUBLIC_KEY!,
44
+ applicationId: process.env.DISCORD_APPLICATION_ID!,
45
+ },
46
+ });
47
+
48
+ export const mastra = new Mastra({
49
+ agents: { support },
50
+ channels: { discord },
51
+ });
52
+
53
+ // Bind an agent. If the bot is already in DISCORD_GUILD_ID, this binds the
54
+ // agent to that guild and registers its slash commands immediately. Otherwise
55
+ // it returns an OAuth2 bot-invite URL and the install stays pending until the
56
+ // bot joins a guild — the first interaction from that guild activates it.
57
+ const result = await discord.connect('support', { guildId: process.env.DISCORD_GUILD_ID });
58
+ // → { type: 'immediate' } OR { type: 'oauth', authorizationUrl, installationId }
59
+ ```
60
+
61
+ ### Patch Changes
62
+
63
+ - Updated dependencies [[`fc7d2c1`](https://github.com/mastra-ai/mastra/commit/fc7d2c102e911f43f70f425e67c970231ea19363), [`4607046`](https://github.com/mastra-ai/mastra/commit/460704663e2869183e7dfff7efec49a4f2f47503), [`1e435dc`](https://github.com/mastra-ai/mastra/commit/1e435dc84a9c1b35aa58d0ab9b14ff39fe13aab0), [`9ba23a2`](https://github.com/mastra-ai/mastra/commit/9ba23a23893622b72c76189199d02432590606c1), [`b757896`](https://github.com/mastra-ai/mastra/commit/b757896872edd74f71ec104be92273c5406265da), [`7f64865`](https://github.com/mastra-ai/mastra/commit/7f648656d2b24b214a899e8835b8286333c80a19), [`f751e65`](https://github.com/mastra-ai/mastra/commit/f751e659f496e5e53ed38632c59c296fec2ccbe5)]:
64
+ - @mastra/core@1.71.0
65
+
3
66
  ## 1.1.0-alpha.0
4
67
 
5
68
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -115427,7 +115427,7 @@ var DiscordProvider = class {
115427
115427
  #storeGeneration = 0;
115428
115428
  constructor(config = {}) {
115429
115429
  this.#config = config;
115430
- this.#configured = this.#suppliedAppConfig() != null;
115430
+ this.#configured = this.#hasSuppliedCredentials();
115431
115431
  }
115432
115432
  /**
115433
115433
  * Called by Mastra when this channel is registered.
@@ -115444,7 +115444,7 @@ var DiscordProvider = class {
115444
115444
  }
115445
115445
  if (isNewInstance) {
115446
115446
  this.#adapters.clear();
115447
- this.#configured = this.#suppliedAppConfig() != null;
115447
+ this.#configured = this.#hasSuppliedCredentials();
115448
115448
  }
115449
115449
  this.#mastra = mastra;
115450
115450
  }
@@ -115516,7 +115516,7 @@ var DiscordProvider = class {
115516
115516
  async #doInitialize() {
115517
115517
  const store = await this.#getStore();
115518
115518
  const active = (await store.list()).filter((i) => i.status === "active");
115519
- const hasApp = await store.getAppConfig() != null || this.#suppliedAppConfig() != null;
115519
+ const hasApp = await store.getAppConfig() != null || this.#hasSuppliedCredentials();
115520
115520
  this.#configured = hasApp || active.length > 0;
115521
115521
  for (const installation of active) try {
115522
115522
  await this.#activateInstallation(installation);
@@ -115527,7 +115527,10 @@ var DiscordProvider = class {
115527
115527
  /**
115528
115528
  * Provide or clear the app credentials at runtime. An object merges/overrides
115529
115529
  * `botToken` / `publicKey` / `applicationId` (persisted on the next `connect`);
115530
- * `null` clears the stored app config.
115530
+ * `null` clears the stored app config. Supplying a **different bot token**
115531
+ * replaces the app config instead of merging: the token identifies the
115532
+ * Discord application, so any `publicKey`/`applicationId` from the previous
115533
+ * application must not survive the switch.
115531
115534
  */
115532
115535
  async configure(credentials) {
115533
115536
  if (credentials === null) {
@@ -115549,7 +115552,7 @@ var DiscordProvider = class {
115549
115552
  ...credentials
115550
115553
  }
115551
115554
  };
115552
- this.#configured = this.#suppliedAppConfig() != null || this.#configured;
115555
+ this.#configured = this.#hasSuppliedCredentials() || this.#configured;
115553
115556
  if (![
115554
115557
  "botToken",
115555
115558
  "publicKey",
@@ -115557,7 +115560,14 @@ var DiscordProvider = class {
115557
115560
  ].some((k) => credentials[k] !== void 0 && credentials[k] !== previous?.[k])) return;
115558
115561
  const store = await this.#getStore();
115559
115562
  const stored = await store.getAppConfig();
115560
- if (stored) {
115563
+ const tokenSupersedes = (base) => credentials.botToken !== void 0 && base?.botToken !== void 0 && base.botToken !== credentials.botToken;
115564
+ if (tokenSupersedes(previous) || tokenSupersedes(stored)) {
115565
+ this.#config = {
115566
+ ...this.#config,
115567
+ app: { ...credentials }
115568
+ };
115569
+ await store.deleteAppConfig();
115570
+ } else if (stored) {
115561
115571
  const merged = {
115562
115572
  ...stored,
115563
115573
  ...credentials
@@ -115586,8 +115596,8 @@ var DiscordProvider = class {
115586
115596
  const store = await this.#getStore();
115587
115597
  const existing = await store.getByAgent(agentId);
115588
115598
  if (existing?.status === "active") throw new Error(`Agent "${agentId}" is already connected to Discord. Disconnect first to reconnect. To add another server, invite the bot with the existing URL — new guilds activate on first use.`);
115589
- const { app, persisted } = await this.#resolveAppConfig(store);
115590
- const identity = await validateApp(app.botToken, this.#apiBaseUrl());
115599
+ const { app, persisted, identity: resolvedIdentity } = await this.#resolveAppConfig(store);
115600
+ const identity = resolvedIdentity ?? await validateApp(app.botToken, this.#apiBaseUrl());
115591
115601
  if (!persisted) await store.saveAppConfig(app);
115592
115602
  const installationId = existing?.id ?? (0, crypto$1.randomUUID)();
115593
115603
  const webhookId = existing?.webhookId ?? (0, crypto$1.randomUUID)();
@@ -115854,12 +115864,33 @@ var DiscordProvider = class {
115854
115864
  return;
115855
115865
  }
115856
115866
  }
115867
+ /**
115868
+ * App credentials from provider config or `DISCORD_*` env.
115869
+ *
115870
+ * A config-supplied bot token defines the application identity, so
115871
+ * `publicKey` / `applicationId` never fall back to the environment in that
115872
+ * case — stale `DISCORD_PUBLIC_KEY` / `DISCORD_APPLICATION_ID` from a
115873
+ * *different* application would otherwise complete the config and keep that
115874
+ * application's Ed25519 key trusted for webhook verification (bypassing the
115875
+ * `GET /applications/@me` backfill). Env fallback for those fields applies
115876
+ * only when the bot token itself comes from the environment.
115877
+ */
115878
+ #suppliedPartialAppConfig() {
115879
+ const a = this.#config.app ?? {};
115880
+ if (a.botToken != null) return {
115881
+ botToken: a.botToken,
115882
+ publicKey: a.publicKey,
115883
+ applicationId: a.applicationId
115884
+ };
115885
+ return {
115886
+ botToken: process.env.DISCORD_BOT_TOKEN,
115887
+ publicKey: a.publicKey ?? process.env.DISCORD_PUBLIC_KEY,
115888
+ applicationId: a.applicationId ?? process.env.DISCORD_APPLICATION_ID
115889
+ };
115890
+ }
115857
115891
  /** App credentials from provider config or `DISCORD_*` env, or `null` if incomplete. */
115858
115892
  #suppliedAppConfig() {
115859
- const a = this.#config.app ?? {};
115860
- const botToken = a.botToken ?? process.env.DISCORD_BOT_TOKEN;
115861
- const publicKey = a.publicKey ?? process.env.DISCORD_PUBLIC_KEY;
115862
- const applicationId = a.applicationId ?? process.env.DISCORD_APPLICATION_ID;
115893
+ const { botToken, publicKey, applicationId } = this.#suppliedPartialAppConfig();
115863
115894
  if (botToken && publicKey && applicationId) return {
115864
115895
  botToken,
115865
115896
  publicKey,
@@ -115868,10 +115899,22 @@ var DiscordProvider = class {
115868
115899
  return null;
115869
115900
  }
115870
115901
  /**
115902
+ * Whether credentials are resolvable without storage. A bot token alone is
115903
+ * enough: `applicationId` and `publicKey` are backfilled from
115904
+ * `GET /applications/@me` by {@link #resolveAppConfig}.
115905
+ */
115906
+ #hasSuppliedCredentials() {
115907
+ return this.#suppliedPartialAppConfig().botToken != null;
115908
+ }
115909
+ /**
115871
115910
  * Resolve the app config: prefer the stored one, else the supplied credentials
115872
- * (config / `DISCORD_*` env). `persisted` says whether it's already in storage,
115873
- * so the caller can persist supplied credentials **once**, after validation.
115874
- * Throws if neither is available.
115911
+ * (config / `DISCORD_*` env). A supplied `botToken` without `publicKey` /
115912
+ * `applicationId` is completed from `GET /applications/@me` — the application
115913
+ * object carries both the id and the Ed25519 `verify_key`. `persisted` says
115914
+ * whether it's already in storage, so the caller can persist supplied
115915
+ * credentials **once**, after validation. `identity` is set when the token was
115916
+ * already validated against `/applications/@me` here, so callers can skip a
115917
+ * second validation call. Throws if no credentials are available.
115875
115918
  */
115876
115919
  async #resolveAppConfig(store) {
115877
115920
  const stored = await store.getAppConfig();
@@ -115884,7 +115927,22 @@ var DiscordProvider = class {
115884
115927
  app: supplied,
115885
115928
  persisted: false
115886
115929
  };
115887
- throw new Error("Discord app is not configured. Provide botToken, publicKey, and applicationId via the provider `app` option, the DISCORD_BOT_TOKEN / DISCORD_PUBLIC_KEY / DISCORD_APPLICATION_ID env vars, or configure() — Discord applications are created in the Developer Portal, not programmatically.");
115930
+ const partial = this.#suppliedPartialAppConfig();
115931
+ if (partial.botToken) {
115932
+ const identity = await validateApp(partial.botToken, this.#apiBaseUrl());
115933
+ const publicKey = partial.publicKey ?? identity.verify_key;
115934
+ if (!publicKey) throw new Error("Discord /applications/@me returned no verify_key; provide publicKey explicitly.");
115935
+ return {
115936
+ app: {
115937
+ botToken: partial.botToken,
115938
+ publicKey,
115939
+ applicationId: partial.applicationId ?? identity.id
115940
+ },
115941
+ persisted: false,
115942
+ identity
115943
+ };
115944
+ }
115945
+ throw new Error("Discord app is not configured. Provide at least a botToken via the provider `app` option, the DISCORD_BOT_TOKEN env var, or configure() — publicKey and applicationId are resolved from Discord automatically when omitted. Discord applications are created in the Developer Portal, not programmatically.");
115888
115946
  }
115889
115947
  /**
115890
115948
  * The single entry point to the store. Every caller either gets the cached