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