@mastra/teams 0.0.0 → 0.1.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/LICENSE.md +32 -0
- package/dist/index.cjs +113790 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +428 -0
- package/dist/index.d.ts +428 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +817 -0
- package/dist/index.js.map +1 -0
- package/package.json +12 -12
package/dist/index.js
ADDED
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, hkdfSync, randomBytes, randomUUID } from "crypto";
|
|
2
|
+
import { AgentChannels, resolveWaitUntil } from "@mastra/core/channels";
|
|
3
|
+
import { InMemoryChannelsStorage } from "@mastra/core/storage";
|
|
4
|
+
import { TeamsAdapter, createTeamsAdapter, createTeamsAdapter as createTeamsAdapter$1 } from "@chat-adapter/teams";
|
|
5
|
+
//#region src/types.ts
|
|
6
|
+
/** Microsoft Graph origin used for Entra application provisioning. */
|
|
7
|
+
const GRAPH_API_BASE_URL = "https://graph.microsoft.com";
|
|
8
|
+
/** Teams Developer Portal origin used for Bot Framework registrations. */
|
|
9
|
+
const DEV_PORTAL_BASE_URL = "https://dev.teams.microsoft.com";
|
|
10
|
+
/** Microsoft login origin used to validate bot credentials (client-credentials mint). */
|
|
11
|
+
const LOGIN_BASE_URL = "https://login.microsoftonline.com";
|
|
12
|
+
/**
|
|
13
|
+
* OAuth scope for Microsoft Graph control-plane calls (create Entra apps,
|
|
14
|
+
* mint client secrets). Passed to the delegated {@link TeamsTokenResolver}.
|
|
15
|
+
*/
|
|
16
|
+
const TEAMS_GRAPH_SCOPE = "https://graph.microsoft.com/.default";
|
|
17
|
+
/**
|
|
18
|
+
* OAuth scope for the Teams Developer Portal API (bot registrations).
|
|
19
|
+
* Tokens for this audience are distinct from Graph tokens — a single access
|
|
20
|
+
* token cannot serve both APIs. Passed to the delegated
|
|
21
|
+
* {@link TeamsTokenResolver}.
|
|
22
|
+
*/
|
|
23
|
+
const TEAMS_DEV_PORTAL_SCOPE = "https://dev.teams.microsoft.com/AppDefinitions.ReadWrite";
|
|
24
|
+
/**
|
|
25
|
+
* Teams Developer Portal page where minted bot registrations are managed and
|
|
26
|
+
* packaged into a Teams app for distribution.
|
|
27
|
+
*/
|
|
28
|
+
const DEV_PORTAL_BOTS_URL = "https://dev.teams.microsoft.com/bots";
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/microsoft-clients.ts
|
|
31
|
+
/**
|
|
32
|
+
* Thin fetch wrappers for the two Microsoft control planes the provider
|
|
33
|
+
* provisions through — Microsoft Graph (Entra applications) and the Teams
|
|
34
|
+
* Developer Portal (Bot Framework registrations) — plus a credential
|
|
35
|
+
* validation helper against the Microsoft login endpoint.
|
|
36
|
+
*
|
|
37
|
+
* All calls are plain `fetch` with a 15s timeout so tests can intercept them
|
|
38
|
+
* with undici's `MockAgent` (same pattern as `@mastra/telegram`'s client).
|
|
39
|
+
*/
|
|
40
|
+
const REQUEST_TIMEOUT_MS = 15e3;
|
|
41
|
+
async function request(url, init, label) {
|
|
42
|
+
let response;
|
|
43
|
+
try {
|
|
44
|
+
response = await fetch(url, {
|
|
45
|
+
...init,
|
|
46
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
47
|
+
});
|
|
48
|
+
} catch (cause) {
|
|
49
|
+
throw Object.assign(new Error(`${label} request failed`, { cause }), { isTransportError: true });
|
|
50
|
+
}
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const body = await response.text().catch(() => "");
|
|
53
|
+
throw new Error(`${label} failed: HTTP ${response.status}${body ? ` — ${truncate(body)}` : ""}`);
|
|
54
|
+
}
|
|
55
|
+
return response;
|
|
56
|
+
}
|
|
57
|
+
function truncate(text, max = 300) {
|
|
58
|
+
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
59
|
+
}
|
|
60
|
+
function authJson(token) {
|
|
61
|
+
return {
|
|
62
|
+
authorization: `Bearer ${token}`,
|
|
63
|
+
"content-type": "application/json"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create an Entra application for a per-agent bot.
|
|
68
|
+
* @see https://learn.microsoft.com/graph/api/application-post-applications
|
|
69
|
+
*/
|
|
70
|
+
async function createApplication(token, options, graphBaseUrl = GRAPH_API_BASE_URL) {
|
|
71
|
+
const body = await (await request(`${graphBaseUrl}/v1.0/applications`, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: authJson(token),
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
displayName: options.displayName,
|
|
76
|
+
signInAudience: options.signInAudience
|
|
77
|
+
})
|
|
78
|
+
}, "Graph application create")).json();
|
|
79
|
+
if (!body.id || !body.appId) throw new Error("Graph application create returned an unexpected payload (missing id/appId)");
|
|
80
|
+
return {
|
|
81
|
+
id: body.id,
|
|
82
|
+
appId: body.appId
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mint a client secret on an Entra application. The secret text is only
|
|
87
|
+
* returned by this call — it cannot be read back later.
|
|
88
|
+
* @see https://learn.microsoft.com/graph/api/application-addpassword
|
|
89
|
+
*/
|
|
90
|
+
async function addApplicationPassword(token, applicationObjectId, displayName, graphBaseUrl = GRAPH_API_BASE_URL) {
|
|
91
|
+
const body = await (await request(`${graphBaseUrl}/v1.0/applications/${encodeURIComponent(applicationObjectId)}/addPassword`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: authJson(token),
|
|
94
|
+
body: JSON.stringify({ passwordCredential: { displayName } })
|
|
95
|
+
}, "Graph addPassword")).json();
|
|
96
|
+
if (!body.secretText) throw new Error("Graph addPassword returned an unexpected payload (missing secretText)");
|
|
97
|
+
return body.secretText;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Delete an Entra application. Used to clean up provisioned bots on
|
|
101
|
+
* disconnect and to roll back a partially provisioned connect.
|
|
102
|
+
* @see https://learn.microsoft.com/graph/api/application-delete
|
|
103
|
+
*/
|
|
104
|
+
async function deleteApplication(token, applicationObjectId, graphBaseUrl = GRAPH_API_BASE_URL) {
|
|
105
|
+
await request(`${graphBaseUrl}/v1.0/applications/${encodeURIComponent(applicationObjectId)}`, {
|
|
106
|
+
method: "DELETE",
|
|
107
|
+
headers: { authorization: `Bearer ${token}` }
|
|
108
|
+
}, "Graph application delete");
|
|
109
|
+
}
|
|
110
|
+
/** Register a bot with the Teams Developer Portal. */
|
|
111
|
+
async function createBotRegistration(token, registration, devPortalBaseUrl = DEV_PORTAL_BASE_URL) {
|
|
112
|
+
await request(`${devPortalBaseUrl}/api/botframework`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: authJson(token),
|
|
115
|
+
body: JSON.stringify(registration)
|
|
116
|
+
}, "Dev Portal bot registration");
|
|
117
|
+
}
|
|
118
|
+
/** Delete a Teams Developer Portal bot registration. */
|
|
119
|
+
async function deleteBotRegistration(token, botId, devPortalBaseUrl = DEV_PORTAL_BASE_URL) {
|
|
120
|
+
await request(`${devPortalBaseUrl}/api/botframework/${encodeURIComponent(botId)}`, {
|
|
121
|
+
method: "DELETE",
|
|
122
|
+
headers: { authorization: `Bearer ${token}` }
|
|
123
|
+
}, "Dev Portal bot deletion");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Validate a bot's `appId`/`appPassword` by minting a Bot Framework token via
|
|
127
|
+
* the OAuth2 client-credentials grant — the same exchange the adapter performs
|
|
128
|
+
* at runtime, so a success here means outbound sends will authenticate.
|
|
129
|
+
* Multi-tenant bots exchange against the `botframework.com` tenant.
|
|
130
|
+
*
|
|
131
|
+
* Throws when Microsoft rejects the credentials; transport failures are
|
|
132
|
+
* surfaced as-is (tagged `isTransportError`) so callers can tell connectivity
|
|
133
|
+
* problems apart from bad credentials.
|
|
134
|
+
*/
|
|
135
|
+
async function validateBotCredentials(options, loginBaseUrl = LOGIN_BASE_URL) {
|
|
136
|
+
const tenant = encodeURIComponent(options.appTenantId ?? "botframework.com");
|
|
137
|
+
const body = new URLSearchParams({
|
|
138
|
+
grant_type: "client_credentials",
|
|
139
|
+
client_id: options.appId,
|
|
140
|
+
client_secret: options.appPassword,
|
|
141
|
+
scope: "https://api.botframework.com/.default"
|
|
142
|
+
});
|
|
143
|
+
let response;
|
|
144
|
+
try {
|
|
145
|
+
response = await fetch(`${loginBaseUrl}/${tenant}/oauth2/v2.0/token`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
148
|
+
body: body.toString(),
|
|
149
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
150
|
+
});
|
|
151
|
+
} catch (cause) {
|
|
152
|
+
throw Object.assign(new Error("Bot credential validation request failed", { cause }), { isTransportError: true });
|
|
153
|
+
}
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const detail = await response.json().catch(() => null);
|
|
156
|
+
throw new Error(`Microsoft rejected the bot credentials: ${detail?.error_description ?? detail?.error ?? `HTTP ${response.status}`}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/crypto.ts
|
|
161
|
+
/**
|
|
162
|
+
* Opt-in AES-256-GCM encryption for installation secrets at rest, with
|
|
163
|
+
* HKDF-SHA256 key derivation (mirrors `@mastra/slack`'s `crypto.ts`). Each value
|
|
164
|
+
* gets a fresh random 16-byte salt + 12-byte IV; the salt travels in the
|
|
165
|
+
* ciphertext, so the same passphrase never derives the same key twice. The
|
|
166
|
+
* algorithm prefix lets plaintext and encrypted values coexist during migration,
|
|
167
|
+
* so {@link decrypt} can no-op on plaintext.
|
|
168
|
+
*
|
|
169
|
+
* Format: `aes-256-gcm-hkdf:base64(salt):base64(iv):base64(authTag):base64(ciphertext)`
|
|
170
|
+
*/
|
|
171
|
+
const ALGO_PREFIX = "aes-256-gcm-hkdf";
|
|
172
|
+
const HKDF_INFO = "mastra-teams-encryption";
|
|
173
|
+
function deriveKey(passphrase, salt) {
|
|
174
|
+
return Buffer.from(hkdfSync("sha256", passphrase, salt, HKDF_INFO, 32));
|
|
175
|
+
}
|
|
176
|
+
/** Whether a stored value was produced by {@link encrypt}. */
|
|
177
|
+
function isEncrypted(value) {
|
|
178
|
+
return value.startsWith(`${ALGO_PREFIX}:`);
|
|
179
|
+
}
|
|
180
|
+
/** Encrypt a UTF-8 string with a per-value random salt + IV. */
|
|
181
|
+
function encrypt(plaintext, passphrase) {
|
|
182
|
+
const salt = randomBytes(16);
|
|
183
|
+
const iv = randomBytes(12);
|
|
184
|
+
const cipher = createCipheriv("aes-256-gcm", deriveKey(passphrase, salt), iv);
|
|
185
|
+
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
186
|
+
const tag = cipher.getAuthTag();
|
|
187
|
+
return `${ALGO_PREFIX}:${salt.toString("base64")}:${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
|
|
188
|
+
}
|
|
189
|
+
/** Decrypt a value from {@link encrypt}. Plaintext (unprefixed) is returned unchanged. */
|
|
190
|
+
function decrypt(value, passphrase) {
|
|
191
|
+
if (!isEncrypted(value)) return value;
|
|
192
|
+
const [, saltB64, ivB64, tagB64, ctB64] = value.split(":");
|
|
193
|
+
if (!saltB64 || !ivB64 || !tagB64 || ctB64 === void 0) throw new Error("Invalid ciphertext payload");
|
|
194
|
+
const decipher = createDecipheriv("aes-256-gcm", deriveKey(passphrase, Buffer.from(saltB64, "base64")), Buffer.from(ivB64, "base64"));
|
|
195
|
+
decipher.setAuthTag(Buffer.from(tagB64, "base64"));
|
|
196
|
+
return Buffer.concat([decipher.update(Buffer.from(ctB64, "base64")), decipher.final()]).toString("utf8");
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/install-store.ts
|
|
200
|
+
/** Platform identifier used for every stored record and route. */
|
|
201
|
+
const PLATFORM = "teams";
|
|
202
|
+
/**
|
|
203
|
+
* Persistence for Teams bot installations, layered over the platform-agnostic
|
|
204
|
+
* `ChannelsStorage` (the same store `@mastra/slack` / `@mastra/telegram` use).
|
|
205
|
+
* Installations are keyed by agent — one bot = one agent — and the per-bot
|
|
206
|
+
* fields live in the record's `data` blob. When an `encryptionKey` is
|
|
207
|
+
* supplied, `appPassword` (the bot's client secret) is AES-256-GCM encrypted
|
|
208
|
+
* at rest.
|
|
209
|
+
*/
|
|
210
|
+
var TeamsInstallStore = class {
|
|
211
|
+
storage;
|
|
212
|
+
encryptionKey;
|
|
213
|
+
#warnedPlaintext = false;
|
|
214
|
+
constructor(storage, encryptionKey) {
|
|
215
|
+
this.storage = storage;
|
|
216
|
+
this.encryptionKey = encryptionKey;
|
|
217
|
+
}
|
|
218
|
+
/** Whether secrets written through this store are encrypted at rest. */
|
|
219
|
+
get canEncrypt() {
|
|
220
|
+
return Boolean(this.encryptionKey);
|
|
221
|
+
}
|
|
222
|
+
/** The active or pending installation for an agent, if any. */
|
|
223
|
+
async getByAgent(agentId) {
|
|
224
|
+
const record = await this.storage.getInstallationByAgent(PLATFORM, agentId);
|
|
225
|
+
return record ? this.#fromRecord(record) : null;
|
|
226
|
+
}
|
|
227
|
+
/** Look up an installation by the routing id in its messaging endpoint path. */
|
|
228
|
+
async getByWebhookId(webhookId) {
|
|
229
|
+
const record = await this.storage.getInstallationByWebhookId(webhookId);
|
|
230
|
+
return record && record.platform === "teams" ? this.#fromRecord(record) : null;
|
|
231
|
+
}
|
|
232
|
+
/** Insert or replace an installation. */
|
|
233
|
+
async save(installation) {
|
|
234
|
+
await this.storage.saveInstallation(this.#toRecord(installation));
|
|
235
|
+
}
|
|
236
|
+
/** All Teams installations (active and pending). */
|
|
237
|
+
async list() {
|
|
238
|
+
return (await this.storage.listInstallations(PLATFORM)).map((r) => this.#fromRecord(r));
|
|
239
|
+
}
|
|
240
|
+
/** Remove an agent's installation, if present. */
|
|
241
|
+
async deleteByAgent(agentId) {
|
|
242
|
+
const record = await this.storage.getInstallationByAgent(PLATFORM, agentId);
|
|
243
|
+
if (record) await this.storage.deleteInstallation(record.id);
|
|
244
|
+
}
|
|
245
|
+
#enc(value) {
|
|
246
|
+
if (value && !this.encryptionKey && !this.#warnedPlaintext) {
|
|
247
|
+
this.#warnedPlaintext = true;
|
|
248
|
+
console.warn("[Teams] Storing the bot client secret WITHOUT encryption — no encryption key is configured. Set `encryptionKey` on TeamsProvider or the MASTRA_ENCRYPTION_KEY environment variable to encrypt installation secrets at rest.");
|
|
249
|
+
}
|
|
250
|
+
return value && this.encryptionKey ? encrypt(value, this.encryptionKey) : value;
|
|
251
|
+
}
|
|
252
|
+
#dec(value) {
|
|
253
|
+
if (!value) return value;
|
|
254
|
+
if (!this.encryptionKey) {
|
|
255
|
+
if (isEncrypted(value)) throw new Error("Teams installation secrets are encrypted at rest, but no encryption key is configured. Set `encryptionKey` on TeamsProvider or MASTRA_ENCRYPTION_KEY.");
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
return decrypt(value, this.encryptionKey);
|
|
259
|
+
}
|
|
260
|
+
#toRecord(install) {
|
|
261
|
+
const data = {
|
|
262
|
+
appId: install.appId,
|
|
263
|
+
entraObjectId: install.entraObjectId,
|
|
264
|
+
appPassword: this.#enc(install.appPassword),
|
|
265
|
+
appTenantId: install.appTenantId,
|
|
266
|
+
appType: install.appType,
|
|
267
|
+
botName: install.botName,
|
|
268
|
+
messagingEndpoint: install.messagingEndpoint
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
id: install.id,
|
|
272
|
+
platform: PLATFORM,
|
|
273
|
+
agentId: install.agentId,
|
|
274
|
+
status: install.status,
|
|
275
|
+
webhookId: install.webhookId,
|
|
276
|
+
data,
|
|
277
|
+
createdAt: install.installedAt,
|
|
278
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
#fromRecord(record) {
|
|
282
|
+
const data = record.data ?? {};
|
|
283
|
+
return {
|
|
284
|
+
id: record.id,
|
|
285
|
+
agentId: record.agentId,
|
|
286
|
+
webhookId: record.webhookId ?? "",
|
|
287
|
+
status: record.status === "active" ? "active" : "pending",
|
|
288
|
+
appId: data.appId,
|
|
289
|
+
entraObjectId: data.entraObjectId,
|
|
290
|
+
appPassword: this.#dec(data.appPassword),
|
|
291
|
+
appTenantId: data.appTenantId,
|
|
292
|
+
appType: data.appType,
|
|
293
|
+
botName: data.botName,
|
|
294
|
+
messagingEndpoint: data.messagingEndpoint,
|
|
295
|
+
installedAt: record.createdAt
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
/** Project an installation to its public, secret-free info for the editor UI. */
|
|
300
|
+
function toInstallationInfo(install) {
|
|
301
|
+
return {
|
|
302
|
+
id: install.id,
|
|
303
|
+
platform: PLATFORM,
|
|
304
|
+
agentId: install.agentId,
|
|
305
|
+
status: install.status,
|
|
306
|
+
displayName: install.botName,
|
|
307
|
+
installedAt: install.installedAt
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/teams-provider.ts
|
|
312
|
+
/**
|
|
313
|
+
* Resolve the per-adapter streaming/typing config the provider applies to the
|
|
314
|
+
* Teams entry in `AgentChannels.adapters` — both default on.
|
|
315
|
+
*/
|
|
316
|
+
function resolveTeamsAdapterConfig(config) {
|
|
317
|
+
return {
|
|
318
|
+
streaming: config.streaming ?? true,
|
|
319
|
+
typingStatus: config.typingStatus ?? true
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Microsoft Teams channel provider for Mastra — a {@link ChannelProvider} over
|
|
324
|
+
* `@chat-adapter/teams`. The adapter handles the Bot Framework transport
|
|
325
|
+
* (activity parse, JWT webhook verification, send/stream, typing, adaptive
|
|
326
|
+
* cards); this provider adds the install/lifecycle layer.
|
|
327
|
+
*
|
|
328
|
+
* Credential modes (mutually exclusive, enforced at the type level):
|
|
329
|
+
*
|
|
330
|
+
* - **Self-managed** — bring your own Azure Bot registration: pass
|
|
331
|
+
* `appId`/`appPassword` (provider-level or per-`connect()`). Point the bot's
|
|
332
|
+
* messaging endpoint at `{baseUrl}/teams/events/{webhookId}`.
|
|
333
|
+
* - **Delegated** — pass a scope-aware `tokenResolver` for a *manager*
|
|
334
|
+
* credential; `connect(agentId)` then provisions a bot per agent: an Entra
|
|
335
|
+
* application (Microsoft Graph), a fresh client secret, and a Teams
|
|
336
|
+
* Developer Portal bot registration pointing at this server. The per-agent
|
|
337
|
+
* secret is stored (encrypted) and used by the runtime — the manager
|
|
338
|
+
* credential is never needed on the message path.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* const teams = new TeamsProvider({ baseUrl: 'https://my-app.example.com', tokenResolver });
|
|
343
|
+
* const mastra = new Mastra({ agents: { myAgent }, channels: { teams } });
|
|
344
|
+
* await teams.connect('my-agent'); // → { type: 'deep_link', url: 'https://dev.teams.microsoft.com/bots' }
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
var TeamsProvider = class {
|
|
348
|
+
id = PLATFORM;
|
|
349
|
+
#config;
|
|
350
|
+
#mastra;
|
|
351
|
+
#store;
|
|
352
|
+
/** Live adapters, keyed by installation id. */
|
|
353
|
+
#adapters = /* @__PURE__ */ new Map();
|
|
354
|
+
/** Cached sync view of whether any active bot is registered (for {@link getInfo}). */
|
|
355
|
+
#configured = false;
|
|
356
|
+
#initPromise = null;
|
|
357
|
+
constructor(config = {}) {
|
|
358
|
+
this.#config = config;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Called by Mastra when this channel is registered.
|
|
362
|
+
* @internal
|
|
363
|
+
*/
|
|
364
|
+
__attach(mastra) {
|
|
365
|
+
if (this.#mastra && this.#mastra !== mastra) {
|
|
366
|
+
this.#initPromise = null;
|
|
367
|
+
this.#store = void 0;
|
|
368
|
+
this.#adapters.clear();
|
|
369
|
+
this.#configured = false;
|
|
370
|
+
}
|
|
371
|
+
this.#mastra = mastra;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Per-bot messaging endpoint. A single POST route keyed by an opaque
|
|
375
|
+
* `webhookId`; inbound requests are authenticated by Microsoft's JWT
|
|
376
|
+
* signature (verified by the adapter against the bot's `appId`), not by the
|
|
377
|
+
* path. Auto-initializes on first hit (mirrors `@mastra/slack`).
|
|
378
|
+
*/
|
|
379
|
+
getRoutes() {
|
|
380
|
+
const self = this;
|
|
381
|
+
const withInit = (handler) => {
|
|
382
|
+
return async ({ mastra }) => {
|
|
383
|
+
self.#mastra = mastra;
|
|
384
|
+
await self.#autoInitialize();
|
|
385
|
+
return handler.bind(self);
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
return [{
|
|
389
|
+
path: `/${PLATFORM}/events/:webhookId`,
|
|
390
|
+
method: "POST",
|
|
391
|
+
requiresAuth: false,
|
|
392
|
+
createHandler: withInit(this.#handleWebhook)
|
|
393
|
+
}];
|
|
394
|
+
}
|
|
395
|
+
/** Discovery metadata for the editor UI. */
|
|
396
|
+
getInfo() {
|
|
397
|
+
const hasCredentials = Boolean(this.#config.tokenResolver || this.#config.appId && this.#config.appPassword);
|
|
398
|
+
return {
|
|
399
|
+
id: this.id,
|
|
400
|
+
name: "Microsoft Teams",
|
|
401
|
+
isConfigured: this.#configured || hasCredentials,
|
|
402
|
+
connectOptionsSchema: {
|
|
403
|
+
type: "object",
|
|
404
|
+
properties: {
|
|
405
|
+
name: {
|
|
406
|
+
type: "string",
|
|
407
|
+
description: "Display name for the bot (defaults to the agent id)."
|
|
408
|
+
},
|
|
409
|
+
appId: {
|
|
410
|
+
type: "string",
|
|
411
|
+
description: "Microsoft App (client) ID of an existing bot registration (self-managed mode). Omit in delegated mode — a bot is provisioned per agent."
|
|
412
|
+
},
|
|
413
|
+
appPassword: {
|
|
414
|
+
type: "string",
|
|
415
|
+
description: "Client secret for the bot (self-managed mode)."
|
|
416
|
+
},
|
|
417
|
+
appTenantId: {
|
|
418
|
+
type: "string",
|
|
419
|
+
description: "Entra tenant ID for single-tenant bots."
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Restore installations from storage: rebuild an adapter per active bot and
|
|
427
|
+
* inject `AgentChannels` so the agent can receive events immediately.
|
|
428
|
+
* Idempotent. Does not re-register messaging endpoints (they persist in the
|
|
429
|
+
* bot registration across restarts); reconnect an agent if its `baseUrl` changed.
|
|
430
|
+
*/
|
|
431
|
+
async initialize() {
|
|
432
|
+
if (this.#initPromise) return this.#initPromise;
|
|
433
|
+
this.#initPromise = this.#doInitialize();
|
|
434
|
+
try {
|
|
435
|
+
await this.#initPromise;
|
|
436
|
+
} catch (err) {
|
|
437
|
+
this.#initPromise = null;
|
|
438
|
+
throw err;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async #doInitialize() {
|
|
442
|
+
const active = (await (await this.#getStore()).list()).filter((i) => i.status === "active");
|
|
443
|
+
this.#configured = active.length > 0;
|
|
444
|
+
for (const installation of active) try {
|
|
445
|
+
await this.#activateInstallation(installation);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
console.error(`[Teams] Failed to restore installation "${installation.id}":`, err);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Update runtime provider settings. Pass `appId`/`appPassword` to change the
|
|
452
|
+
* default bot credentials new {@link connect} calls fall back to; pass
|
|
453
|
+
* `baseUrl`/`apiUrl` to point the provider/adapter at a different host.
|
|
454
|
+
* `null` clears the default credentials only — per-bot installs are managed
|
|
455
|
+
* via {@link connect}/{@link disconnect}.
|
|
456
|
+
*/
|
|
457
|
+
async configure(credentials) {
|
|
458
|
+
if (this.#config.tokenResolver && (credentials?.appId !== void 0 || credentials?.appPassword !== void 0)) throw new Error("TeamsProvider was constructed with a tokenResolver — bot credentials are provisioned per agent. Remove the tokenResolver to manage credentials manually via configure().");
|
|
459
|
+
if (credentials === null) {
|
|
460
|
+
this.#config = {
|
|
461
|
+
...this.#config,
|
|
462
|
+
appId: void 0,
|
|
463
|
+
appPassword: void 0
|
|
464
|
+
};
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const apiUrlChanged = credentials.apiUrl !== void 0 && credentials.apiUrl !== this.#config.apiUrl;
|
|
468
|
+
this.#config = {
|
|
469
|
+
...this.#config,
|
|
470
|
+
...credentials
|
|
471
|
+
};
|
|
472
|
+
if (!apiUrlChanged) return;
|
|
473
|
+
const wasInitialized = this.#initPromise !== null;
|
|
474
|
+
this.#adapters.clear();
|
|
475
|
+
this.#initPromise = null;
|
|
476
|
+
if (wasInitialized) await this.initialize();
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Connect an agent to Microsoft Teams.
|
|
480
|
+
*
|
|
481
|
+
* - **Delegated** (`tokenResolver`): provisions a per-agent bot — Entra
|
|
482
|
+
* application + client secret via Microsoft Graph, then a Teams Developer
|
|
483
|
+
* Portal bot registration whose messaging endpoint points at this server —
|
|
484
|
+
* and returns `{ type: 'deep_link' }` to the Developer Portal where the
|
|
485
|
+
* bot is packaged into a Teams app for install.
|
|
486
|
+
* - **Self-managed** with `appId`/`appPassword` (per-call or provider
|
|
487
|
+
* default): validates the credentials via a Bot Framework token mint,
|
|
488
|
+
* persists the installation, and returns `{ type: 'immediate' }`. Point
|
|
489
|
+
* the bot registration's messaging endpoint at the installation's
|
|
490
|
+
* `messagingEndpoint`.
|
|
491
|
+
* - Self-managed without credentials: persists a pending installation and
|
|
492
|
+
* returns `{ type: 'deep_link' }` pointing at the Teams Developer Portal.
|
|
493
|
+
*/
|
|
494
|
+
async connect(agentId, options = {}) {
|
|
495
|
+
const existing = await (await this.#getStore()).getByAgent(agentId);
|
|
496
|
+
if (existing?.status === "active") throw new Error(`Agent "${agentId}" is already connected to Microsoft Teams. Disconnect first to reconnect.`);
|
|
497
|
+
if (this.#config.tokenResolver) {
|
|
498
|
+
if (options.appId !== void 0 || options.appPassword !== void 0) throw new Error("TeamsProvider was constructed with a tokenResolver — bots are provisioned per agent and credentials cannot be supplied per connect() call.");
|
|
499
|
+
return this.#connectDelegated(agentId, options, existing);
|
|
500
|
+
}
|
|
501
|
+
return this.#connectSelfManaged(agentId, options, existing);
|
|
502
|
+
}
|
|
503
|
+
async #connectSelfManaged(agentId, options, existing) {
|
|
504
|
+
const store = await this.#getStore();
|
|
505
|
+
const appId = options.appId ?? this.#config.appId;
|
|
506
|
+
const appPassword = options.appPassword ?? this.#config.appPassword;
|
|
507
|
+
if (!appId || !appPassword) {
|
|
508
|
+
const installationId = existing?.id ?? randomUUID();
|
|
509
|
+
await store.save({
|
|
510
|
+
id: installationId,
|
|
511
|
+
agentId,
|
|
512
|
+
webhookId: existing?.webhookId ?? randomUUID(),
|
|
513
|
+
status: "pending",
|
|
514
|
+
installedAt: existing?.installedAt ?? /* @__PURE__ */ new Date()
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
type: "deep_link",
|
|
518
|
+
url: DEV_PORTAL_BOTS_URL,
|
|
519
|
+
installationId
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const duplicate = (await store.list()).find((i) => i.status === "active" && i.agentId !== agentId && i.appId === appId);
|
|
523
|
+
if (duplicate) throw new Error(`This Teams bot is already connected to agent "${duplicate.agentId}". Disconnect it before connecting another agent.`);
|
|
524
|
+
const appTenantId = options.appTenantId ?? this.#config.appTenantId;
|
|
525
|
+
await validateBotCredentials({
|
|
526
|
+
appId,
|
|
527
|
+
appPassword,
|
|
528
|
+
appTenantId
|
|
529
|
+
}, this.#config.loginBaseUrl);
|
|
530
|
+
const installationId = existing?.id ?? randomUUID();
|
|
531
|
+
const webhookId = existing?.webhookId ?? randomUUID();
|
|
532
|
+
const baseUrl = this.#getBaseUrl();
|
|
533
|
+
const installation = {
|
|
534
|
+
id: installationId,
|
|
535
|
+
agentId,
|
|
536
|
+
webhookId,
|
|
537
|
+
status: "active",
|
|
538
|
+
appId,
|
|
539
|
+
appPassword,
|
|
540
|
+
appTenantId,
|
|
541
|
+
appType: this.#config.appType ?? (appTenantId ? "SingleTenant" : "MultiTenant"),
|
|
542
|
+
botName: options.name ?? agentId,
|
|
543
|
+
messagingEndpoint: baseUrl ? `${baseUrl}/${PLATFORM}/events/${webhookId}` : void 0,
|
|
544
|
+
installedAt: existing?.installedAt ?? /* @__PURE__ */ new Date()
|
|
545
|
+
};
|
|
546
|
+
await store.save(installation);
|
|
547
|
+
try {
|
|
548
|
+
await this.#activateInstallation(installation);
|
|
549
|
+
} catch (err) {
|
|
550
|
+
console.warn(`[Teams] Failed to activate installation for agent "${agentId}":`, err);
|
|
551
|
+
}
|
|
552
|
+
this.#configured = true;
|
|
553
|
+
try {
|
|
554
|
+
await this.#config.onInstall?.(installation);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
console.warn(`[Teams] onInstall hook failed for agent "${agentId}":`, err);
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
type: "immediate",
|
|
560
|
+
installationId
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
async #connectDelegated(agentId, options, existing) {
|
|
564
|
+
const tokenResolver = this.#config.tokenResolver;
|
|
565
|
+
const store = await this.#getStore();
|
|
566
|
+
if (!store.canEncrypt) throw new Error("Delegated Teams provisioning stores a Mastra-provisioned bot client secret and requires an encryption key. Set `encryptionKey` on TeamsProvider or the MASTRA_ENCRYPTION_KEY environment variable.");
|
|
567
|
+
const baseUrl = this.#getBaseUrl();
|
|
568
|
+
if (!baseUrl) throw new Error("TeamsProvider needs a baseUrl to register a bot messaging endpoint. Set `baseUrl` or configure the Mastra server.");
|
|
569
|
+
const botName = options.name ?? agentId;
|
|
570
|
+
const appType = this.#config.appType ?? "MultiTenant";
|
|
571
|
+
const appTenantId = options.appTenantId ?? this.#config.appTenantId;
|
|
572
|
+
if (appType === "SingleTenant" && !appTenantId) throw new Error("TeamsProvider is configured for SingleTenant bots but no appTenantId is available. Set `appTenantId` on the provider or pass it to connect().");
|
|
573
|
+
const webhookId = existing?.webhookId ?? randomUUID();
|
|
574
|
+
const messagingEndpoint = `${baseUrl}/${PLATFORM}/events/${webhookId}`;
|
|
575
|
+
const graphToken = await tokenResolver(TEAMS_GRAPH_SCOPE, appTenantId);
|
|
576
|
+
const application = await createApplication(graphToken, {
|
|
577
|
+
displayName: botName,
|
|
578
|
+
signInAudience: appType === "SingleTenant" ? "AzureADMyOrg" : "AzureADMultipleOrgs"
|
|
579
|
+
}, this.#config.graphBaseUrl);
|
|
580
|
+
let appPassword;
|
|
581
|
+
let devPortalToken;
|
|
582
|
+
let botRegistered = false;
|
|
583
|
+
let installation;
|
|
584
|
+
try {
|
|
585
|
+
appPassword = await addApplicationPassword(graphToken, application.id, "mastra-teams", this.#config.graphBaseUrl);
|
|
586
|
+
devPortalToken = await tokenResolver(TEAMS_DEV_PORTAL_SCOPE, appTenantId);
|
|
587
|
+
await createBotRegistration(devPortalToken, {
|
|
588
|
+
botId: application.appId,
|
|
589
|
+
name: botName,
|
|
590
|
+
description: `Mastra agent "${agentId}"`,
|
|
591
|
+
iconUrl: this.#config.botIconUrl ?? "",
|
|
592
|
+
messagingEndpoint,
|
|
593
|
+
callingEndpoint: ""
|
|
594
|
+
}, this.#config.devPortalBaseUrl);
|
|
595
|
+
botRegistered = true;
|
|
596
|
+
installation = {
|
|
597
|
+
id: existing?.id ?? randomUUID(),
|
|
598
|
+
agentId,
|
|
599
|
+
webhookId,
|
|
600
|
+
status: "active",
|
|
601
|
+
appId: application.appId,
|
|
602
|
+
entraObjectId: application.id,
|
|
603
|
+
appPassword,
|
|
604
|
+
appTenantId,
|
|
605
|
+
appType,
|
|
606
|
+
botName,
|
|
607
|
+
messagingEndpoint,
|
|
608
|
+
installedAt: existing?.installedAt ?? /* @__PURE__ */ new Date()
|
|
609
|
+
};
|
|
610
|
+
await store.save(installation);
|
|
611
|
+
} catch (err) {
|
|
612
|
+
if (botRegistered && devPortalToken) try {
|
|
613
|
+
await deleteBotRegistration(devPortalToken, application.appId, this.#config.devPortalBaseUrl);
|
|
614
|
+
} catch (cleanupErr) {
|
|
615
|
+
console.warn(`[Teams] Failed to roll back bot registration "${application.appId}":`, cleanupErr);
|
|
616
|
+
}
|
|
617
|
+
try {
|
|
618
|
+
await deleteApplication(graphToken, application.id, this.#config.graphBaseUrl);
|
|
619
|
+
} catch (cleanupErr) {
|
|
620
|
+
console.warn(`[Teams] Failed to roll back Entra application "${application.appId}":`, cleanupErr);
|
|
621
|
+
}
|
|
622
|
+
throw err;
|
|
623
|
+
}
|
|
624
|
+
try {
|
|
625
|
+
await this.#activateInstallation(installation);
|
|
626
|
+
} catch (err) {
|
|
627
|
+
console.warn(`[Teams] Failed to activate installation for agent "${agentId}":`, err);
|
|
628
|
+
}
|
|
629
|
+
this.#configured = true;
|
|
630
|
+
try {
|
|
631
|
+
await this.#config.onInstall?.(installation);
|
|
632
|
+
} catch (err) {
|
|
633
|
+
console.warn(`[Teams] onInstall hook failed for agent "${agentId}":`, err);
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
type: "deep_link",
|
|
637
|
+
url: DEV_PORTAL_BOTS_URL,
|
|
638
|
+
installationId: installation.id
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Disconnect an agent from Microsoft Teams. For bots this provider
|
|
643
|
+
* provisioned (delegated mode), the Developer Portal registration and the
|
|
644
|
+
* Entra application are deleted best-effort — a control-plane failure never
|
|
645
|
+
* blocks removal of the local installation.
|
|
646
|
+
*/
|
|
647
|
+
async disconnect(agentId) {
|
|
648
|
+
const store = await this.#getStore();
|
|
649
|
+
const existing = await store.getByAgent(agentId);
|
|
650
|
+
if (!existing) throw new Error(`No Teams installation found for agent "${agentId}"`);
|
|
651
|
+
if (existing.entraObjectId && this.#config.tokenResolver) {
|
|
652
|
+
if (existing.appId) try {
|
|
653
|
+
await deleteBotRegistration(await this.#config.tokenResolver(TEAMS_DEV_PORTAL_SCOPE, existing.appTenantId), existing.appId, this.#config.devPortalBaseUrl);
|
|
654
|
+
} catch (err) {
|
|
655
|
+
console.warn(`[Teams] Failed to delete bot registration for agent "${agentId}":`, err);
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
await deleteApplication(await this.#config.tokenResolver(TEAMS_GRAPH_SCOPE, existing.appTenantId), existing.entraObjectId, this.#config.graphBaseUrl);
|
|
659
|
+
} catch (err) {
|
|
660
|
+
console.warn(`[Teams] Failed to delete Entra application for agent "${agentId}":`, err);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
this.#adapters.delete(existing.id);
|
|
664
|
+
await store.deleteByAgent(agentId);
|
|
665
|
+
this.#configured = (await store.list()).some((i) => i.status === "active");
|
|
666
|
+
}
|
|
667
|
+
/** List installations (public info only — no secrets). */
|
|
668
|
+
async listInstallations() {
|
|
669
|
+
return (await (await this.#getStore()).list()).map(toInstallationInfo);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Get the full installation for an agent (includes the client secret).
|
|
673
|
+
* Returns `null` if the agent has no Teams installation. Mirrors
|
|
674
|
+
* `SlackProvider.getInstallation`.
|
|
675
|
+
*/
|
|
676
|
+
async getInstallation(agentId) {
|
|
677
|
+
return await (await this.#getStore()).getByAgent(agentId) ?? null;
|
|
678
|
+
}
|
|
679
|
+
/** Whether at least one bot is actively registered. */
|
|
680
|
+
isConfigured() {
|
|
681
|
+
return this.#configured;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Get the live `TeamsAdapter` for an installation id, if one is active.
|
|
685
|
+
* Used for message formatting/posting. Mirrors `SlackProvider.getAdapter`.
|
|
686
|
+
*/
|
|
687
|
+
getAdapter(installationId) {
|
|
688
|
+
return this.#adapters.get(installationId);
|
|
689
|
+
}
|
|
690
|
+
async #handleWebhook(c) {
|
|
691
|
+
const webhookId = c.req.param("webhookId");
|
|
692
|
+
if (!webhookId) return c.json({
|
|
693
|
+
ok: false,
|
|
694
|
+
error: "Missing webhookId"
|
|
695
|
+
}, 400);
|
|
696
|
+
const installation = await (await this.#getStore()).getByWebhookId(webhookId);
|
|
697
|
+
if (!installation || installation.status !== "active") return c.json({
|
|
698
|
+
ok: false,
|
|
699
|
+
error: "Unknown webhook"
|
|
700
|
+
}, 404);
|
|
701
|
+
const agent = this.#resolveAgent(installation.agentId);
|
|
702
|
+
if (!agent || !this.#mastra) return c.json({ ok: true });
|
|
703
|
+
const adapter = this.#getOrCreateAdapter(installation);
|
|
704
|
+
let channels = agent.getChannels();
|
|
705
|
+
if (!channels || channels.adapters["teams"] !== adapter) {
|
|
706
|
+
channels = this.#createAgentChannels(agent, adapter);
|
|
707
|
+
await channels.initialize(this.#mastra);
|
|
708
|
+
}
|
|
709
|
+
const waitUntil = this.#config.waitUntil ?? resolveWaitUntil(c);
|
|
710
|
+
try {
|
|
711
|
+
return await channels.handleWebhookEvent(PLATFORM, c.req.raw, waitUntil ? { waitUntil } : void 0);
|
|
712
|
+
} catch (err) {
|
|
713
|
+
console.error("[Teams] Error delegating to AgentChannels:", err);
|
|
714
|
+
return c.json({ ok: true });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
#getOrCreateAdapter(installation) {
|
|
718
|
+
const existing = this.#adapters.get(installation.id);
|
|
719
|
+
if (existing) return existing;
|
|
720
|
+
const adapter = createTeamsAdapter$1({
|
|
721
|
+
appId: installation.appId,
|
|
722
|
+
appPassword: installation.appPassword,
|
|
723
|
+
...installation.appTenantId !== void 0 ? { appTenantId: installation.appTenantId } : {},
|
|
724
|
+
...installation.appType !== void 0 ? { appType: installation.appType } : {},
|
|
725
|
+
...installation.botName !== void 0 ? { userName: installation.botName } : {},
|
|
726
|
+
...this.#config.apiUrl !== void 0 ? { apiUrl: this.#config.apiUrl } : {},
|
|
727
|
+
...this.#config.logger !== void 0 ? { logger: this.#config.logger } : {}
|
|
728
|
+
});
|
|
729
|
+
this.#adapters.set(installation.id, adapter);
|
|
730
|
+
return adapter;
|
|
731
|
+
}
|
|
732
|
+
/** Rebuild the adapter and inject AgentChannels for an active installation. */
|
|
733
|
+
async #activateInstallation(installation) {
|
|
734
|
+
const agent = this.#resolveAgent(installation.agentId);
|
|
735
|
+
const adapter = this.#getOrCreateAdapter(installation);
|
|
736
|
+
if (agent && this.#mastra) await this.#createAgentChannels(agent, adapter).initialize(this.#mastra);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Create AgentChannels for an agent with the Teams adapter, preserving any
|
|
740
|
+
* adapters/config the agent author already configured (mirrors `@mastra/slack`).
|
|
741
|
+
*/
|
|
742
|
+
#createAgentChannels(agent, adapter) {
|
|
743
|
+
const existingConfig = agent.getChannels()?.channelConfig;
|
|
744
|
+
const cfg = this.#config;
|
|
745
|
+
const entry = {
|
|
746
|
+
adapter,
|
|
747
|
+
...resolveTeamsAdapterConfig(cfg),
|
|
748
|
+
...cfg.toolDisplay !== void 0 ? { toolDisplay: cfg.toolDisplay } : {},
|
|
749
|
+
...cfg.cors !== void 0 ? { cors: cfg.cors } : {},
|
|
750
|
+
...cfg.formatError !== void 0 ? { formatError: cfg.formatError } : {}
|
|
751
|
+
};
|
|
752
|
+
const channels = new AgentChannels({
|
|
753
|
+
...existingConfig,
|
|
754
|
+
adapters: {
|
|
755
|
+
...existingConfig?.adapters,
|
|
756
|
+
[PLATFORM]: entry
|
|
757
|
+
},
|
|
758
|
+
userName: agent.name,
|
|
759
|
+
handlers: cfg.handlers ?? existingConfig?.handlers,
|
|
760
|
+
inlineMedia: cfg.inlineMedia ?? existingConfig?.inlineMedia,
|
|
761
|
+
inlineLinks: cfg.inlineLinks ?? existingConfig?.inlineLinks,
|
|
762
|
+
state: cfg.state ?? existingConfig?.state,
|
|
763
|
+
threadContext: cfg.threadContext ?? existingConfig?.threadContext,
|
|
764
|
+
chatOptions: cfg.chatOptions ?? existingConfig?.chatOptions,
|
|
765
|
+
tools: cfg.tools ?? existingConfig?.tools,
|
|
766
|
+
resolveResourceId: cfg.resolveResourceId ?? existingConfig?.resolveResourceId,
|
|
767
|
+
waitUntil: cfg.waitUntil ?? existingConfig?.waitUntil,
|
|
768
|
+
resolveWaitUntil: cfg.resolveWaitUntil ?? existingConfig?.resolveWaitUntil
|
|
769
|
+
});
|
|
770
|
+
agent.setChannels(channels);
|
|
771
|
+
return channels;
|
|
772
|
+
}
|
|
773
|
+
async #autoInitialize() {
|
|
774
|
+
if (!this.#mastra) return;
|
|
775
|
+
await this.initialize();
|
|
776
|
+
}
|
|
777
|
+
#resolveAgent(agentId) {
|
|
778
|
+
try {
|
|
779
|
+
return this.#mastra?.getAgentById(agentId);
|
|
780
|
+
} catch {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async #getStore() {
|
|
785
|
+
if (this.#store) return this.#store;
|
|
786
|
+
const encryptionKey = this.#config.encryptionKey ?? process.env.MASTRA_ENCRYPTION_KEY;
|
|
787
|
+
this.#store = new TeamsInstallStore(await this.#resolveStorage(), encryptionKey);
|
|
788
|
+
return this.#store;
|
|
789
|
+
}
|
|
790
|
+
async #resolveStorage() {
|
|
791
|
+
if (this.#config.storage) return this.#config.storage;
|
|
792
|
+
const mastraStore = this.#mastra?.getStorage();
|
|
793
|
+
if (mastraStore) {
|
|
794
|
+
await mastraStore.init();
|
|
795
|
+
const channels = await mastraStore.getStore("channels");
|
|
796
|
+
if (channels) return channels;
|
|
797
|
+
}
|
|
798
|
+
console.warn("[Teams] No persistent channels storage configured — installations are stored in memory and will be lost on restart.");
|
|
799
|
+
return new InMemoryChannelsStorage();
|
|
800
|
+
}
|
|
801
|
+
#getBaseUrl() {
|
|
802
|
+
if (this.#config.baseUrl) return stripTrailingSlash(this.#config.baseUrl);
|
|
803
|
+
const server = this.#mastra?.getServer();
|
|
804
|
+
if (!server) return void 0;
|
|
805
|
+
const protocol = server.studioProtocol ?? "http";
|
|
806
|
+
const host = server.studioHost ?? server.host ?? "localhost";
|
|
807
|
+
const port = server.studioPort ?? server.port ?? (Number(process.env.PORT) || 4111);
|
|
808
|
+
return !(protocol === "https" && port === 443 || protocol === "http" && port === 80) ? `${protocol}://${host}:${port}` : `${protocol}://${host}`;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
function stripTrailingSlash(url) {
|
|
812
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
813
|
+
}
|
|
814
|
+
//#endregion
|
|
815
|
+
export { DEV_PORTAL_BASE_URL, DEV_PORTAL_BOTS_URL, GRAPH_API_BASE_URL, LOGIN_BASE_URL, PLATFORM, TEAMS_DEV_PORTAL_SCOPE, TEAMS_GRAPH_SCOPE, TeamsAdapter, TeamsInstallStore, TeamsProvider, addApplicationPassword, createApplication, createBotRegistration, createTeamsAdapter, deleteApplication, deleteBotRegistration, resolveTeamsAdapterConfig, toInstallationInfo, validateBotCredentials };
|
|
816
|
+
|
|
817
|
+
//# sourceMappingURL=index.js.map
|