@mastra/connect 0.3.1-alpha.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -1,11 +1,18 @@
1
+ export { tools } from './tools.js';
2
+ export type { ToolsOptions, ToolsIntegrationOptions, ToolsResolver } from './tools.js';
1
3
  export { connect } from './connect.js';
2
4
  export type { ConnectOptions, ConnectIntegrationOptions, ConnectTools } from './connect.js';
5
+ export { channels } from './channels.js';
6
+ export type { ChannelsOptions, ChannelsIntegrationOptions, ChannelsResolver, ChannelsResolverContext, ResolvedChannels, } from './channels.js';
7
+ export type { ChannelProviderRegistration, ChannelBuildContext } from './providers/channel-provider.js';
3
8
  export { credential } from './credential.js';
9
+ export { environment } from './environment.js';
10
+ export type { ConnectEnvironment, EnvironmentIntegrationOptions, EnvironmentOptions } from './environment.js';
4
11
  export { MastraConnectError } from './errors.js';
5
12
  export type { MastraConnectErrorCode } from './errors.js';
6
13
  export type { ConnectClientOptions, ConnectionCredential, ProjectConnection } from './client.js';
7
14
  export type { ProviderToolsOptions, ProxyToolConfig, ProxyToolContext } from './toolset.js';
8
15
  export { defineProxyTool, resolveConnectionId, applyAllowTools } from './toolset.js';
9
- export { PROVIDERS, findRegistration } from './registry.js';
16
+ export { TOOLS, PROVIDERS, CHANNELS, findRegistration, findChannelRegistration } from './registry.js';
10
17
  export type { McpProviderRegistration, ProviderRegistration, ProxyProviderRegistration } from './registry.js';
11
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,cAAc,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjG,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC5D,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,cAAc,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,eAAe,EACf,0BAA0B,EAC1B,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACxG,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjG,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACtG,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -342,6 +342,106 @@ async function proxyRequestWithResponse(client, connectionId, options) {
342
342
  }
343
343
  }
344
344
  //#endregion
345
+ //#region src/providers/channels.ts
346
+ function credentialToken$1(credential) {
347
+ return credential.type === "oauth2" ? credential.accessToken : credential.apiKey;
348
+ }
349
+ function missingPeerError(integrationId, packageName, error) {
350
+ return new MastraConnectError("invalid_options", `channels() cannot build '${integrationId}' provider: install '${packageName}' as a dependency of your app (${error instanceof Error ? error.message : String(error)}).`);
351
+ }
352
+ const RESERVED_OPTION_KEYS = {
353
+ slack: [
354
+ "baseUrl",
355
+ "refreshToken",
356
+ "token",
357
+ "encryptionKey"
358
+ ],
359
+ telegram: [
360
+ "baseUrl",
361
+ "apiBaseUrl",
362
+ "botToken",
363
+ "encryptionKey"
364
+ ],
365
+ discord: ["baseUrl", "encryptionKey"]
366
+ };
367
+ function stripReservedOptions(integrationId, options) {
368
+ if (!options) return options;
369
+ const reserved = RESERVED_OPTION_KEYS[integrationId];
370
+ if (!reserved) return options;
371
+ const stripped = [];
372
+ const filtered = {};
373
+ for (const [key, value] of Object.entries(options)) {
374
+ if (reserved.includes(key)) {
375
+ stripped.push(key);
376
+ continue;
377
+ }
378
+ filtered[key] = value;
379
+ }
380
+ if (stripped.length > 0) console.warn(`[@mastra/connect] ${integrationId} channel: ignoring reserved providerOptions field(s): ${stripped.map((k) => `'${k}'`).join(", ")}. These are managed by the connection credential or the Mastra server config.`);
381
+ return filtered;
382
+ }
383
+ const CHANNELS$1 = [
384
+ {
385
+ integrationId: "slack",
386
+ async build(credential, options) {
387
+ const refreshToken = credentialToken$1(credential);
388
+ let mod;
389
+ try {
390
+ mod = await import("@mastra/slack");
391
+ } catch (error) {
392
+ throw missingPeerError("slack", "@mastra/slack", error);
393
+ }
394
+ const safeOptions = stripReservedOptions("slack", options);
395
+ return new mod.SlackProvider({
396
+ refreshToken,
397
+ ...safeOptions ?? {}
398
+ });
399
+ }
400
+ },
401
+ {
402
+ integrationId: "telegram",
403
+ async build(credential, options) {
404
+ const botToken = credentialToken$1(credential);
405
+ let mod;
406
+ try {
407
+ mod = await import("@mastra/telegram");
408
+ } catch (error) {
409
+ throw missingPeerError("telegram", "@mastra/telegram", error);
410
+ }
411
+ const safeOptions = stripReservedOptions("telegram", options);
412
+ return new mod.TelegramProvider({
413
+ botToken,
414
+ ...safeOptions ?? {}
415
+ });
416
+ }
417
+ },
418
+ {
419
+ integrationId: "discord",
420
+ async build(credential, options, context) {
421
+ const botToken = credentialToken$1(credential);
422
+ const metadata = context?.context?.metadata ?? {};
423
+ const applicationId = options?.applicationId ?? (typeof metadata.applicationId === "string" ? metadata.applicationId : void 0) ?? (typeof metadata.application_id === "string" ? metadata.application_id : void 0);
424
+ const publicKey = options?.publicKey ?? (typeof metadata.publicKey === "string" ? metadata.publicKey : void 0) ?? (typeof metadata.public_key === "string" ? metadata.public_key : void 0);
425
+ let mod;
426
+ try {
427
+ mod = await import("@mastra/discord");
428
+ } catch (error) {
429
+ throw missingPeerError("discord", "@mastra/discord", error);
430
+ }
431
+ if (!applicationId || !publicKey) console.warn(`[@mastra/connect] discord channel: missing ${[!applicationId && "applicationId", !publicKey && "publicKey"].filter(Boolean).join(" + ")} on the connection. Store them on the connection's metadata or pass them via integrations.discord.providerOptions.`);
432
+ const { applicationId: _optAppId, publicKey: _optPubKey, ...rest } = options ?? {};
433
+ return new mod.DiscordProvider({
434
+ app: {
435
+ botToken,
436
+ applicationId,
437
+ publicKey
438
+ },
439
+ ...rest
440
+ });
441
+ }
442
+ }
443
+ ];
444
+ //#endregion
345
445
  //#region src/runtime/platform-proxy.ts
346
446
  /** Maps the platform credential to the field names templates are written against. */
347
447
  function toTemplateCredentials(credential) {
@@ -65717,7 +65817,7 @@ function createWorkosTools(options) {
65717
65817
  * Providers with checked-in HTTP toolsets. MCP-backed providers are discovered
65718
65818
  * from the Platform integration catalog at runtime.
65719
65819
  */
65720
- const PROVIDERS = [
65820
+ const TOOLS = [
65721
65821
  anthropicProvider,
65722
65822
  clerkProvider,
65723
65823
  discordProvider,
@@ -65744,18 +65844,84 @@ const PROVIDERS = [
65744
65844
  createTools: createWorkosTools
65745
65845
  }
65746
65846
  ];
65847
+ /**
65848
+ * @deprecated Use `TOOLS`. This alias is retained for one release cycle so
65849
+ * downstream consumers keep working; it will be removed in the next minor.
65850
+ */
65851
+ const PROVIDERS = TOOLS;
65747
65852
  function findRegistration(integrationId) {
65748
- return PROVIDERS.find((p) => p.integrationId === integrationId);
65853
+ return TOOLS.find((p) => p.integrationId === integrationId);
65854
+ }
65855
+ /**
65856
+ * Channel-capable providers with a hand-maintained `channels()` registration.
65857
+ * The launch ships three (Slack, Telegram, Discord); the provider generator
65858
+ * grows to cover new entries when a fourth lands.
65859
+ */
65860
+ const CHANNELS = CHANNELS$1;
65861
+ function findChannelRegistration(integrationId) {
65862
+ return CHANNELS.find((c) => c.integrationId === integrationId);
65749
65863
  }
65750
65864
  //#endregion
65751
- //#region src/connect.ts
65752
- const DEFAULT_TTL_MS = 3e4;
65865
+ //#region src/resolution.ts
65866
+ const INTEGRATION_ID_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/;
65867
+ function validateIntegrationOverrides(integrations) {
65868
+ for (const integrationId of Object.keys(integrations ?? {})) if (!INTEGRATION_ID_PATTERN.test(integrationId)) throw new MastraConnectError("invalid_options", `Invalid provider '${integrationId}' in integrations option: expected 1-128 letters, numbers, underscores, or hyphens.`);
65869
+ }
65870
+ function connectionIdEnvVar(integrationId) {
65871
+ return `MASTRA_${integrationId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_CONNECTION_ID`;
65872
+ }
65873
+ function groupByIntegrationId(connections) {
65874
+ const byIntegrationId = /* @__PURE__ */ new Map();
65875
+ for (const connection of connections) {
65876
+ const list = byIntegrationId.get(connection.integrationId) ?? [];
65877
+ list.push(connection);
65878
+ byIntegrationId.set(connection.integrationId, list);
65879
+ }
65880
+ return byIntegrationId;
65881
+ }
65882
+ /**
65883
+ * Resolves the connection to use for one provider, per the contract:
65884
+ * option/env var wins; else a single active connection; anything else
65885
+ * (ambiguity, needs_reauth, no usable candidate) warns and skips so one bad
65886
+ * integration never takes down the whole resolution. A needs_reauth
65887
+ * connection is never silently mapped.
65888
+ */
65889
+ function resolveConnection(request, candidates) {
65890
+ const integrationId = request.integrationId;
65891
+ const directed = request.connectionId?.trim() || process.env[request.envVar]?.trim() || void 0;
65892
+ if (directed) {
65893
+ const match = candidates.find((connection) => connection.id === directed);
65894
+ if (!match) {
65895
+ console.warn(`[@mastra/connect] Skipping ${integrationId}: pinned connection ${directed} is not attached to this project.`);
65896
+ return;
65897
+ }
65898
+ if (match.status === "needs_reauth") {
65899
+ console.warn(`[@mastra/connect] Skipping ${integrationId}: connection ${directed} needs re-auth.`);
65900
+ return;
65901
+ }
65902
+ if (match.status !== "active") {
65903
+ console.warn(`[@mastra/connect] Skipping ${integrationId}: connection ${directed} is not active (status '${match.status}').`);
65904
+ return;
65905
+ }
65906
+ return directed;
65907
+ }
65908
+ const active = candidates.filter((connection) => connection.status === "active");
65909
+ if (active.length === 1) return active[0].id;
65910
+ if (active.length === 0) {
65911
+ console.warn(`[@mastra/connect] Skipping ${integrationId}: no active connections (found ${candidates.length} in other states).`);
65912
+ return;
65913
+ }
65914
+ console.warn(`[@mastra/connect] Skipping ${integrationId}: ${active.length} active connections; pin one with connectionId or ${request.envVar}.`);
65915
+ }
65916
+ //#endregion
65917
+ //#region src/tools.ts
65918
+ const DEFAULT_TTL_MS$1 = 3e4;
65753
65919
  /** Minimum wait after a failed platform fetch before another background revalidation. */
65754
- const FAILURE_COOLDOWN_MS = 3e4;
65920
+ const FAILURE_COOLDOWN_MS$1 = 3e4;
65755
65921
  let nextResolverId = 0;
65756
65922
  /**
65757
65923
  * Returns a live toolset resolver over the project's Platform connections.
65758
- * HTTP providers are loaded from the shipped `PROVIDERS` registry. MCP providers
65924
+ * HTTP providers are loaded from the shipped `TOOLS` registry. MCP providers
65759
65925
  * are discovered from the Platform integration catalog. Tools from every supported
65760
65926
  * provider with a matching project connection are merged into one flat record
65761
65927
  * (matched by `integrationId`). The resolver serves a cached snapshot,
@@ -65769,11 +65935,11 @@ let nextResolverId = 0;
65769
65935
  * problems during resolution (needs re-auth or ambiguity) are downgraded to
65770
65936
  * warn-and-skip so one bad integration never takes down the whole toolset.
65771
65937
  */
65772
- function connect(options = {}) {
65938
+ function tools(options = {}) {
65773
65939
  const projectId = options.projectId?.trim() || process.env.MASTRA_PROJECT_ID?.trim();
65774
65940
  if (!projectId) throw new MastraConnectError("missing_project_id", "Missing project id: set MASTRA_PROJECT_ID or pass projectId.");
65775
65941
  if (options.ttlMs !== void 0 && (!Number.isFinite(options.ttlMs) || options.ttlMs < 0)) throw new MastraConnectError("invalid_options", `Invalid ttlMs (${options.ttlMs}): expected a finite number of milliseconds >= 0.`);
65776
- const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
65942
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS$1;
65777
65943
  const client = resolveClient(options.client);
65778
65944
  const resolverId = ++nextResolverId;
65779
65945
  const mcpClients = /* @__PURE__ */ new Map();
@@ -65795,7 +65961,7 @@ function connect(options = {}) {
65795
65961
  connections,
65796
65962
  catalog: catalogResult.value
65797
65963
  };
65798
- const checkedIn = new Set(PROVIDERS.map((registration) => registration.integrationId));
65964
+ const checkedIn = new Set(TOOLS.map((registration) => registration.integrationId));
65799
65965
  if (connections.some((connection) => connection.status === "active" && !checkedIn.has(connection.integrationId) && !options.integrations?.[connection.integrationId]?.disabled)) throw catalogResult.reason;
65800
65966
  const reason = catalogResult.reason;
65801
65967
  console.warn(`[@mastra/connect] Platform catalog unavailable (${reason instanceof Error ? reason.message : String(reason)}); resolving checked-in providers only.`);
@@ -65833,7 +65999,7 @@ function connect(options = {}) {
65833
65999
  if (cache && Date.now() - cache.fetchedAt < ttlMs) return cache.snapshot;
65834
66000
  if (cache) {
65835
66001
  const staleSnapshot = cache.snapshot;
65836
- if (!(lastFailureAt !== void 0 && Date.now() - lastFailureAt < FAILURE_COOLDOWN_MS) && !inflight && !closing) {
66002
+ if (!(lastFailureAt !== void 0 && Date.now() - lastFailureAt < FAILURE_COOLDOWN_MS$1) && !inflight && !closing) {
65837
66003
  const staleFetchedAt = cache.fetchedAt;
65838
66004
  refresh().catch((error) => {
65839
66005
  console.warn(`[@mastra/connect] Keeping cached tools (fetched ${Date.now() - staleFetchedAt}ms ago); platform refresh failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -65864,16 +66030,9 @@ function connect(options = {}) {
65864
66030
  }
65865
66031
  });
65866
66032
  }
65867
- function validateIntegrationOverrides(integrations) {
65868
- const integrationIdPattern = /^[a-zA-Z0-9_-]{1,128}$/;
65869
- for (const integrationId of Object.keys(integrations ?? {})) if (!integrationIdPattern.test(integrationId)) throw new MastraConnectError("invalid_options", `Invalid provider '${integrationId}' in integrations option: expected 1-128 letters, numbers, underscores, or hyphens.`);
65870
- }
65871
- function connectionIdEnvVar(integrationId) {
65872
- return `MASTRA_${integrationId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_CONNECTION_ID`;
65873
- }
65874
66033
  function buildRequests(integrations, catalog) {
65875
66034
  const overrides = integrations ?? {};
65876
- const registrations = new Map(PROVIDERS.map((registration) => [registration.integrationId, registration]));
66035
+ const registrations = new Map(TOOLS.map((registration) => [registration.integrationId, registration]));
65877
66036
  const catalogIds = new Set(catalog.map((integration) => integration.id));
65878
66037
  for (const integration of catalog) {
65879
66038
  if (!integration.capabilities.mcp) continue;
@@ -65907,7 +66066,11 @@ async function mapTools(connections, requests, options, client, mcpClients, reso
65907
66066
  try {
65908
66067
  const candidates = byIntegrationId.get(integrationId) ?? [];
65909
66068
  if (candidates.length === 0) continue;
65910
- const connectionId = resolveProviderConnection(request, candidates);
66069
+ const connectionId = resolveConnection({
66070
+ integrationId,
66071
+ envVar: request.registration.envVar,
66072
+ connectionId: request.options.connectionId
66073
+ }, candidates);
65911
66074
  if (!connectionId) continue;
65912
66075
  if (request.registration.transport === "mcp") {
65913
66076
  activeMcpIntegrations.add(integrationId);
@@ -65967,51 +66130,192 @@ async function discoverMcpTools(input) {
65967
66130
  if (unknown.length > 0) throw new MastraConnectError("invalid_options", `Unknown tool name(s) in autoApproveTools for '${registration.integrationId}': ${unknown.join(", ")}. Known tools: ${Object.keys(discovery.tools).join(", ")}.`);
65968
66131
  return applyAllowTools(discovery.tools, allowTools);
65969
66132
  }
65970
- function groupByIntegrationId(connections) {
65971
- const byIntegrationId = /* @__PURE__ */ new Map();
65972
- for (const connection of connections) {
65973
- const list = byIntegrationId.get(connection.integrationId) ?? [];
65974
- list.push(connection);
65975
- byIntegrationId.set(connection.integrationId, list);
65976
- }
65977
- return byIntegrationId;
66133
+ //#endregion
66134
+ //#region src/connect.ts
66135
+ /**
66136
+ * @deprecated Use `tools` from `@mastra/connect` instead. This module is a
66137
+ * thin re-export retained for one release cycle so downstream consumers keep
66138
+ * working; it will be removed in the next minor.
66139
+ */
66140
+ /**
66141
+ * @deprecated Renamed to {@link tools}.
66142
+ *
66143
+ * `connect(options)` is a drop-in alias for `tools(options)`. Prefer `tools`
66144
+ * from `@mastra/connect` in new code; this export will be removed in the next
66145
+ * minor version.
66146
+ */
66147
+ function connect(options = {}) {
66148
+ return tools(options);
65978
66149
  }
65979
- function readEnvConnectionId(registration) {
65980
- return process.env[registration.envVar]?.trim() || void 0;
66150
+ //#endregion
66151
+ //#region src/channels.ts
66152
+ const DEFAULT_TTL_MS = 3e4;
66153
+ /** Minimum wait after a failed platform fetch before another background revalidation. */
66154
+ const FAILURE_COOLDOWN_MS = 3e4;
66155
+ /**
66156
+ * Returns a live channels resolver over the project's Platform connections.
66157
+ * Channel-capable integrations (Slack, Telegram, Discord) with an active
66158
+ * project connection are materialized into `ChannelProvider` instances ready
66159
+ * to hand to `new Mastra({ channels: await channels({...}) })`.
66160
+ *
66161
+ * Configuration errors (missing project id, bad ttlMs, malformed integration
66162
+ * id) throw at call time so they surface at startup. Actionable per-integration
66163
+ * problems during resolution (needs re-auth, ambiguity, missing peer package)
66164
+ * are downgraded to warn-and-skip so one bad integration never takes down the
66165
+ * whole map.
66166
+ *
66167
+ * @example
66168
+ * ```ts
66169
+ * import { Mastra } from '@mastra/core/mastra';
66170
+ * import { channels } from '@mastra/connect';
66171
+ *
66172
+ * export const mastra = new Mastra({
66173
+ * agents: { pat },
66174
+ * channels: await channels({ projectId }),
66175
+ * });
66176
+ * ```
66177
+ */
66178
+ function channels(options = {}) {
66179
+ const projectId = options.projectId?.trim() || process.env.MASTRA_PROJECT_ID?.trim();
66180
+ if (!projectId) throw new MastraConnectError("missing_project_id", "Missing project id: set MASTRA_PROJECT_ID or pass projectId.");
66181
+ if (options.ttlMs !== void 0 && (!Number.isFinite(options.ttlMs) || options.ttlMs < 0)) throw new MastraConnectError("invalid_options", `Invalid ttlMs (${options.ttlMs}): expected a finite number of milliseconds >= 0.`);
66182
+ validateIntegrationOverrides(options.integrations);
66183
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
66184
+ const client = resolveClient(options.client);
66185
+ let cache;
66186
+ let inflight;
66187
+ let lastFailureAt;
66188
+ const buildSnapshot = async () => {
66189
+ let connections;
66190
+ try {
66191
+ connections = await listProjectConnections(client, projectId);
66192
+ } catch (error) {
66193
+ lastFailureAt = Date.now();
66194
+ throw error;
66195
+ }
66196
+ const byIntegrationId = groupByIntegrationId(connections);
66197
+ const providers = {};
66198
+ for (const registration of CHANNELS) {
66199
+ const integrationId = registration.integrationId;
66200
+ const overrides = options.integrations?.[integrationId] ?? {};
66201
+ if (overrides.disabled) continue;
66202
+ const candidates = byIntegrationId.get(integrationId) ?? [];
66203
+ if (candidates.length === 0) continue;
66204
+ const connectionId = selectChannelConnection(integrationId, overrides.connectionId, candidates);
66205
+ if (!connectionId) continue;
66206
+ const provider = await buildProvider(registration, connectionId, overrides, client);
66207
+ if (!provider) continue;
66208
+ providers[integrationId] = provider;
66209
+ }
66210
+ lastFailureAt = void 0;
66211
+ return providers;
66212
+ };
66213
+ const refresh = () => {
66214
+ if (!inflight) inflight = (async () => {
66215
+ try {
66216
+ const providers = await buildSnapshot();
66217
+ cache = {
66218
+ providers,
66219
+ fetchedAt: Date.now()
66220
+ };
66221
+ return providers;
66222
+ } finally {
66223
+ inflight = void 0;
66224
+ }
66225
+ })();
66226
+ return inflight;
66227
+ };
66228
+ const resolve = async () => {
66229
+ if (cache && Date.now() - cache.fetchedAt < ttlMs) return cache.providers;
66230
+ if (cache) {
66231
+ const stale = cache.providers;
66232
+ if (!(lastFailureAt !== void 0 && Date.now() - lastFailureAt < FAILURE_COOLDOWN_MS) && !inflight) {
66233
+ const staleFetchedAt = cache.fetchedAt;
66234
+ refresh().catch((error) => {
66235
+ console.warn(`[@mastra/connect] Keeping cached channels (fetched ${Date.now() - staleFetchedAt}ms ago); platform refresh failed: ${error instanceof Error ? error.message : String(error)}`);
66236
+ });
66237
+ }
66238
+ return stale;
66239
+ }
66240
+ return refresh();
66241
+ };
66242
+ const invocable = ((_context) => resolve());
66243
+ invocable.invalidate = () => {
66244
+ cache = void 0;
66245
+ };
66246
+ invocable.refresh = refresh;
66247
+ invocable.disconnect = async () => {
66248
+ cache = void 0;
66249
+ };
66250
+ invocable.then = ((onfulfilled, onrejected) => resolve().then(onfulfilled, onrejected));
66251
+ return invocable;
66252
+ }
66253
+ async function buildProvider(registration, connectionId, overrides, client) {
66254
+ let credential;
66255
+ try {
66256
+ credential = await getCredential(client, connectionId);
66257
+ } catch (error) {
66258
+ console.warn(`[@mastra/connect] Skipping ${registration.integrationId} channel: ${error instanceof Error ? error.message : String(error)}`);
66259
+ return;
66260
+ }
66261
+ let context = {
66262
+ connectionId,
66263
+ client
66264
+ };
66265
+ try {
66266
+ context = {
66267
+ connectionId,
66268
+ client,
66269
+ context: await getConnectionContext(client, connectionId)
66270
+ };
66271
+ } catch {}
66272
+ try {
66273
+ return await registration.build(credential, overrides.providerOptions ?? {}, context);
66274
+ } catch (error) {
66275
+ console.warn(`[@mastra/connect] Skipping ${registration.integrationId} channel: ${error instanceof Error ? error.message : String(error)}`);
66276
+ return;
66277
+ }
65981
66278
  }
65982
66279
  /**
65983
- * Resolves the connection to use for one provider, per the contract:
65984
- * option/env var wins; else a single active connection; anything else
65985
- * (ambiguity, needs_reauth, no usable candidate) warns and skips so one bad
65986
- * integration never takes down the whole toolset resolution. A needs_reauth
65987
- * connection is never silently mapped.
66280
+ * Selects the connection `channels()` should use for one provider.
66281
+ *
66282
+ * - Pinned `connectionId` (from `integrations.<id>.connectionId`) wins, but is
66283
+ * skipped if the pinned id isn't attached to the project or the connection
66284
+ * isn't `active`.
66285
+ * - Otherwise, the single active connection is used.
66286
+ * - When more than one active connection exists, this warns naming the chosen
66287
+ * id and picks the first active connection. There is no env-var fallback;
66288
+ * pin explicitly with `integrations.<id>.connectionId` when the deterministic
66289
+ * choice matters.
65988
66290
  */
65989
- function resolveProviderConnection(request, candidates) {
65990
- const integrationId = request.registration.integrationId;
65991
- const directed = request.options.connectionId?.trim() || readEnvConnectionId(request.registration);
66291
+ function selectChannelConnection(integrationId, pinnedId, candidates) {
66292
+ const directed = pinnedId?.trim() || void 0;
65992
66293
  if (directed) {
65993
66294
  const match = candidates.find((connection) => connection.id === directed);
65994
66295
  if (!match) {
65995
- console.warn(`[@mastra/connect] Skipping ${integrationId}: pinned connection ${directed} is not attached to this project.`);
66296
+ console.warn(`[@mastra/connect] Skipping ${integrationId} channel: pinned connection ${directed} is not attached to this project.`);
65996
66297
  return;
65997
66298
  }
65998
66299
  if (match.status === "needs_reauth") {
65999
- console.warn(`[@mastra/connect] Skipping ${integrationId}: connection ${directed} needs re-auth.`);
66300
+ console.warn(`[@mastra/connect] Skipping ${integrationId} channel: connection ${directed} needs re-auth.`);
66000
66301
  return;
66001
66302
  }
66002
66303
  if (match.status !== "active") {
66003
- console.warn(`[@mastra/connect] Skipping ${integrationId}: connection ${directed} is not active (status '${match.status}').`);
66304
+ console.warn(`[@mastra/connect] Skipping ${integrationId} channel: connection ${directed} is not active (status '${match.status}').`);
66004
66305
  return;
66005
66306
  }
66006
66307
  return directed;
66007
66308
  }
66008
66309
  const active = candidates.filter((connection) => connection.status === "active");
66009
- if (active.length === 1) return active[0].id;
66010
66310
  if (active.length === 0) {
66011
- console.warn(`[@mastra/connect] Skipping ${integrationId}: no active connections (found ${candidates.length} in other states).`);
66311
+ console.warn(`[@mastra/connect] Skipping ${integrationId} channel: no active connections (found ${candidates.length} in other states).`);
66012
66312
  return;
66013
66313
  }
66014
- console.warn(`[@mastra/connect] Skipping ${integrationId}: ${active.length} active connections; pin one with connectionId or ${request.registration.envVar}.`);
66314
+ if (active.length === 1) return active[0].id;
66315
+ const chosen = active[0].id;
66316
+ const others = active.slice(1).map((connection) => connection.id).join(", ");
66317
+ console.warn(`[@mastra/connect] ${integrationId} channel: found ${active.length} active connections; using ${chosen}. Ignoring ${others}. Pin one with integrations.${integrationId}.connectionId to silence this warning.`);
66318
+ return chosen;
66015
66319
  }
66016
66320
  //#endregion
66017
66321
  //#region src/credential.ts
@@ -66023,6 +66327,108 @@ async function credential(connectionId, options) {
66023
66327
  return getCredential(resolveClient(options?.client), connectionId);
66024
66328
  }
66025
66329
  //#endregion
66026
- export { MastraConnectError, PROVIDERS, applyAllowTools, connect, credential, defineProxyTool, findRegistration, resolveConnectionId };
66330
+ //#region src/environment.ts
66331
+ function credentialToken(credential) {
66332
+ return credential.type === "oauth2" ? credential.accessToken : credential.apiKey;
66333
+ }
66334
+ /**
66335
+ * Wires git's credential helper to read the token from the environment at use
66336
+ * time, so the secret lives in the process env rather than in git config or
66337
+ * remote URLs. `gh` needs no setup: it honors GH_TOKEN directly.
66338
+ */
66339
+ const GITHUB_GIT_CREDENTIAL_HELPER = "!f() { echo username=x-access-token; echo \"password=$GH_TOKEN\"; }; f";
66340
+ /** Providers that know how to materialize sandbox credentials. */
66341
+ const ENVIRONMENT_CONTRIBUTORS = [{
66342
+ integrationId: "github",
66343
+ envVar: "MASTRA_GITHUB_CONNECTION_ID",
66344
+ build(credential) {
66345
+ const token = credentialToken(credential);
66346
+ return {
66347
+ env: {
66348
+ GH_TOKEN: token,
66349
+ GITHUB_TOKEN: token
66350
+ },
66351
+ onStart: async ({ sandbox }) => {
66352
+ if (!sandbox.executeCommand) {
66353
+ console.warn("[@mastra/connect] github environment: sandbox does not support executeCommand; skipping git credential setup (gh still works via GH_TOKEN).");
66354
+ return;
66355
+ }
66356
+ await sandbox.executeCommand("git", [
66357
+ "config",
66358
+ "--global",
66359
+ "credential.https://github.com.helper",
66360
+ GITHUB_GIT_CREDENTIAL_HELPER
66361
+ ]);
66362
+ }
66363
+ };
66364
+ }
66365
+ }];
66366
+ /**
66367
+ * Resolves the project's Platform connections into sandbox credentials:
66368
+ * environment variables plus a start hook that finishes in-sandbox setup.
66369
+ * Currently GitHub-aware (`GH_TOKEN`/`GITHUB_TOKEN` + git credential helper);
66370
+ * more providers plug in over time.
66371
+ *
66372
+ * Connection resolution matches `connect()`: a per-integration `connectionId`
66373
+ * or the provider's `MASTRA_*_CONNECTION_ID` env var wins, else a single
66374
+ * active connection; ambiguity, needs_reauth, and credential-fetch failures
66375
+ * warn and skip that provider. Configuration errors (missing project id,
66376
+ * malformed integration id) throw here so they surface at startup.
66377
+ *
66378
+ * The returned `env` is a snapshot: call `environment()` again for a fresh
66379
+ * token when starting new sandboxes.
66380
+ *
66381
+ * @example
66382
+ * const { env, onStart } = await environment({ projectId });
66383
+ * const sandbox = new E2BSandbox({ env, onStart });
66384
+ * // in the sandbox: `gh pr list`, `git clone https://github.com/org/repo` just work
66385
+ */
66386
+ async function environment(options = {}) {
66387
+ const projectId = options.projectId?.trim() || process.env.MASTRA_PROJECT_ID?.trim();
66388
+ if (!projectId) throw new MastraConnectError("missing_project_id", "Missing project id: set MASTRA_PROJECT_ID or pass projectId.");
66389
+ validateIntegrationOverrides(options.integrations);
66390
+ const client = resolveClient(options.client);
66391
+ const byIntegrationId = groupByIntegrationId(await listProjectConnections(client, projectId));
66392
+ const env = {};
66393
+ const hooks = [];
66394
+ for (const contributor of ENVIRONMENT_CONTRIBUTORS) {
66395
+ const integrationId = contributor.integrationId;
66396
+ const overrides = options.integrations?.[integrationId] ?? {};
66397
+ if (overrides.disabled) continue;
66398
+ const candidates = byIntegrationId.get(integrationId) ?? [];
66399
+ if (candidates.length === 0) continue;
66400
+ const connectionId = resolveConnection({
66401
+ integrationId,
66402
+ envVar: contributor.envVar,
66403
+ connectionId: overrides.connectionId
66404
+ }, candidates);
66405
+ if (!connectionId) continue;
66406
+ let credential;
66407
+ try {
66408
+ credential = await getCredential(client, connectionId);
66409
+ } catch (error) {
66410
+ console.warn(`[@mastra/connect] Skipping ${integrationId} environment: ${error instanceof Error ? error.message : String(error)}`);
66411
+ continue;
66412
+ }
66413
+ const contribution = contributor.build(credential);
66414
+ for (const [key, value] of Object.entries(contribution.env)) {
66415
+ if (key in env) console.warn(`[@mastra/connect] ${integrationId} environment overwrites env var '${key}' set by an earlier provider.`);
66416
+ env[key] = value;
66417
+ }
66418
+ if (contribution.onStart) hooks.push({
66419
+ integrationId,
66420
+ hook: contribution.onStart
66421
+ });
66422
+ }
66423
+ const onStart = async (args) => {
66424
+ for (const { hook } of hooks) await hook(args);
66425
+ };
66426
+ return {
66427
+ env,
66428
+ onStart
66429
+ };
66430
+ }
66431
+ //#endregion
66432
+ export { CHANNELS, MastraConnectError, PROVIDERS, TOOLS, applyAllowTools, channels, connect, credential, defineProxyTool, environment, findChannelRegistration, findRegistration, resolveConnectionId, tools };
66027
66433
 
66028
66434
  //# sourceMappingURL=index.js.map