@mastra/telegram 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,680 @@
1
+ // src/telegram-provider.ts
2
+ import { randomUUID, timingSafeEqual } from "crypto";
3
+ import { AgentChannels, resolveWaitUntil } from "@mastra/core/channels";
4
+ import { InMemoryChannelsStorage } from "@mastra/core/storage";
5
+ import { createTelegramAdapter } from "@chat-adapter/telegram";
6
+
7
+ // src/telegram-client.ts
8
+ import { randomBytes } from "crypto";
9
+
10
+ // src/types.ts
11
+ var TELEGRAM_API_BASE_URL = "https://api.telegram.org";
12
+ var DEFAULT_ALLOWED_UPDATES = [
13
+ "message",
14
+ "edited_message",
15
+ "channel_post",
16
+ "edited_channel_post",
17
+ "callback_query",
18
+ "message_reaction"
19
+ ];
20
+ var BOTFATHER_DEEP_LINK = "https://t.me/botfather";
21
+
22
+ // src/telegram-client.ts
23
+ async function botApiRequest(botToken, method, apiBaseUrl, payload) {
24
+ const init = payload === void 0 ? void 0 : { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload) };
25
+ let response;
26
+ try {
27
+ response = await fetch(`${apiBaseUrl}/bot${botToken}/${method}`, {
28
+ ...init,
29
+ signal: AbortSignal.timeout(1e4)
30
+ });
31
+ } catch (cause) {
32
+ throw Object.assign(new Error(`Telegram ${method} request failed`, { cause }), {
33
+ isTransportError: true
34
+ });
35
+ }
36
+ const body = await response.json().catch(() => null);
37
+ if (!response.ok || !body?.ok) {
38
+ const detail = body?.description ?? `HTTP ${response.status}`;
39
+ throw new Error(`Telegram ${method} failed: ${detail}`);
40
+ }
41
+ return body.result;
42
+ }
43
+ async function getMe(botToken, apiBaseUrl = TELEGRAM_API_BASE_URL) {
44
+ let result;
45
+ try {
46
+ result = await botApiRequest(botToken, "getMe", apiBaseUrl);
47
+ } catch (cause) {
48
+ if (cause instanceof Error && cause.isTransportError) {
49
+ throw cause;
50
+ }
51
+ throw new Error(`Telegram rejected the bot token: ${cause instanceof Error ? cause.message : String(cause)}`, {
52
+ cause
53
+ });
54
+ }
55
+ if (!result?.is_bot) {
56
+ throw new Error("Telegram getMe returned a non-bot user; expected a BotFather token");
57
+ }
58
+ return result;
59
+ }
60
+ async function setWebhook(botToken, options, apiBaseUrl = TELEGRAM_API_BASE_URL) {
61
+ await botApiRequest(botToken, "setWebhook", apiBaseUrl, {
62
+ url: options.url,
63
+ secret_token: options.secretToken,
64
+ allowed_updates: options.allowedUpdates,
65
+ drop_pending_updates: options.dropPendingUpdates
66
+ });
67
+ }
68
+ async function deleteWebhook(botToken, dropPendingUpdates = false, apiBaseUrl = TELEGRAM_API_BASE_URL) {
69
+ await botApiRequest(botToken, "deleteWebhook", apiBaseUrl, {
70
+ drop_pending_updates: dropPendingUpdates
71
+ });
72
+ }
73
+ async function setMyCommands(botToken, options, apiBaseUrl = TELEGRAM_API_BASE_URL) {
74
+ await botApiRequest(botToken, "setMyCommands", apiBaseUrl, {
75
+ commands: options.commands,
76
+ scope: options.scope,
77
+ language_code: options.languageCode
78
+ });
79
+ }
80
+ function generateSecretToken() {
81
+ return randomBytes(32).toString("base64url");
82
+ }
83
+
84
+ // src/commands.ts
85
+ var DEFAULT_COMMANDS = [
86
+ { command: "start", description: "Start a conversation" },
87
+ { command: "help", description: "Show what this bot can do" },
88
+ { command: "settings", description: "Manage your preferences" }
89
+ ];
90
+ function normalizeCommands(raw) {
91
+ if (!raw) return [];
92
+ const seen = /* @__PURE__ */ new Set();
93
+ const commands = [];
94
+ for (const item of raw) {
95
+ const input = typeof item === "string" ? { command: item } : item;
96
+ const command = input.command.replace(/^\//, "").toLowerCase().replace(/[^a-z0-9_]/g, "").slice(0, 32);
97
+ if (!command || seen.has(command)) continue;
98
+ seen.add(command);
99
+ const description = (input.description?.trim() || `Run /${command}`).slice(0, 256);
100
+ commands.push({ command, description });
101
+ }
102
+ return commands;
103
+ }
104
+
105
+ // src/crypto.ts
106
+ import { createCipheriv, createDecipheriv, hkdfSync, randomBytes as randomBytes2 } from "crypto";
107
+ var ALGO_PREFIX = "aes-256-gcm-hkdf";
108
+ var HKDF_INFO = "mastra-telegram-encryption";
109
+ function deriveKey(passphrase, salt) {
110
+ return Buffer.from(hkdfSync("sha256", passphrase, salt, HKDF_INFO, 32));
111
+ }
112
+ function isEncrypted(value) {
113
+ return value.startsWith(`${ALGO_PREFIX}:`);
114
+ }
115
+ function encrypt(plaintext, passphrase) {
116
+ const salt = randomBytes2(16);
117
+ const iv = randomBytes2(12);
118
+ const cipher = createCipheriv("aes-256-gcm", deriveKey(passphrase, salt), iv);
119
+ const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
120
+ const tag = cipher.getAuthTag();
121
+ return `${ALGO_PREFIX}:${salt.toString("base64")}:${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
122
+ }
123
+ function decrypt(value, passphrase) {
124
+ if (!isEncrypted(value)) return value;
125
+ const [, saltB64, ivB64, tagB64, ctB64] = value.split(":");
126
+ if (!saltB64 || !ivB64 || !tagB64 || ctB64 === void 0) {
127
+ throw new Error("Invalid ciphertext payload");
128
+ }
129
+ const decipher = createDecipheriv(
130
+ "aes-256-gcm",
131
+ deriveKey(passphrase, Buffer.from(saltB64, "base64")),
132
+ Buffer.from(ivB64, "base64")
133
+ );
134
+ decipher.setAuthTag(Buffer.from(tagB64, "base64"));
135
+ return Buffer.concat([decipher.update(Buffer.from(ctB64, "base64")), decipher.final()]).toString("utf8");
136
+ }
137
+
138
+ // src/install-store.ts
139
+ var PLATFORM = "telegram";
140
+ var TelegramInstallStore = class {
141
+ constructor(storage, encryptionKey) {
142
+ this.storage = storage;
143
+ this.encryptionKey = encryptionKey;
144
+ }
145
+ storage;
146
+ encryptionKey;
147
+ /** The active or pending installation for an agent, if any. */
148
+ async getByAgent(agentId) {
149
+ const record = await this.storage.getInstallationByAgent(PLATFORM, agentId);
150
+ return record ? this.#fromRecord(record) : null;
151
+ }
152
+ /** Look up an installation by the routing id in its webhook path (M1 dispatch). */
153
+ async getByWebhookId(webhookId) {
154
+ const record = await this.storage.getInstallationByWebhookId(webhookId);
155
+ return record && record.platform === PLATFORM ? this.#fromRecord(record) : null;
156
+ }
157
+ /** Insert or replace an installation. */
158
+ async save(installation) {
159
+ await this.storage.saveInstallation(this.#toRecord(installation));
160
+ }
161
+ /** All Telegram installations (active and pending). */
162
+ async list() {
163
+ const records = await this.storage.listInstallations(PLATFORM);
164
+ return records.map((r) => this.#fromRecord(r));
165
+ }
166
+ /** Remove an agent's installation, if present. */
167
+ async deleteByAgent(agentId) {
168
+ const record = await this.storage.getInstallationByAgent(PLATFORM, agentId);
169
+ if (record) await this.storage.deleteInstallation(record.id);
170
+ }
171
+ #enc(value) {
172
+ return value && this.encryptionKey ? encrypt(value, this.encryptionKey) : value;
173
+ }
174
+ #dec(value) {
175
+ if (!value) return value;
176
+ if (!this.encryptionKey) {
177
+ if (isEncrypted(value)) {
178
+ throw new Error(
179
+ "Telegram installation secrets are encrypted at rest, but no encryption key is configured. Set `encryptionKey` on TelegramProvider or MASTRA_ENCRYPTION_KEY."
180
+ );
181
+ }
182
+ return value;
183
+ }
184
+ return decrypt(value, this.encryptionKey);
185
+ }
186
+ #toRecord(install) {
187
+ const data = {
188
+ botToken: this.#enc(install.botToken),
189
+ secretToken: this.#enc(install.secretToken),
190
+ username: install.username,
191
+ webhookUrl: install.webhookUrl,
192
+ commands: install.commands
193
+ };
194
+ return {
195
+ id: install.id,
196
+ platform: PLATFORM,
197
+ agentId: install.agentId,
198
+ status: install.status,
199
+ webhookId: install.webhookId,
200
+ data,
201
+ createdAt: install.installedAt,
202
+ updatedAt: /* @__PURE__ */ new Date()
203
+ };
204
+ }
205
+ #fromRecord(record) {
206
+ const data = record.data ?? {};
207
+ return {
208
+ id: record.id,
209
+ agentId: record.agentId,
210
+ webhookId: record.webhookId ?? "",
211
+ status: record.status === "active" ? "active" : "pending",
212
+ botToken: this.#dec(data.botToken),
213
+ secretToken: this.#dec(data.secretToken),
214
+ username: data.username,
215
+ webhookUrl: data.webhookUrl,
216
+ commands: data.commands,
217
+ installedAt: record.createdAt
218
+ };
219
+ }
220
+ };
221
+ function toInstallationInfo(install) {
222
+ return {
223
+ id: install.id,
224
+ platform: PLATFORM,
225
+ agentId: install.agentId,
226
+ status: install.status,
227
+ displayName: install.username,
228
+ installedAt: install.installedAt
229
+ };
230
+ }
231
+
232
+ // src/telegram-provider.ts
233
+ function resolveTelegramAdapterConfig(config) {
234
+ return {
235
+ streaming: config.streaming ?? true,
236
+ typingStatus: config.typingStatus ?? true
237
+ };
238
+ }
239
+ var SECRET_HEADER = "x-telegram-bot-api-secret-token";
240
+ var TelegramProvider = class {
241
+ id = PLATFORM;
242
+ #config;
243
+ #mastra;
244
+ #store;
245
+ /** Live adapters, keyed by installation id. */
246
+ #adapters = /* @__PURE__ */ new Map();
247
+ /** Cached sync view of whether any active bot is registered (for {@link getInfo}). */
248
+ #configured = false;
249
+ #initPromise = null;
250
+ constructor(config = {}) {
251
+ this.#config = config;
252
+ }
253
+ /**
254
+ * Called by Mastra when this channel is registered.
255
+ * @internal
256
+ */
257
+ __attach(mastra) {
258
+ if (this.#mastra && this.#mastra !== mastra) {
259
+ this.#initPromise = null;
260
+ this.#store = void 0;
261
+ this.#adapters.clear();
262
+ this.#configured = false;
263
+ }
264
+ this.#mastra = mastra;
265
+ }
266
+ /**
267
+ * Per-bot webhook route. A single POST endpoint keyed by an opaque
268
+ * `webhookId`; the per-bot secret is verified from the request header, never
269
+ * carried in the URL. Auto-initializes on first hit (mirrors `@mastra/slack`).
270
+ */
271
+ getRoutes() {
272
+ const self = this;
273
+ const withInit = (handler) => {
274
+ return async ({ mastra }) => {
275
+ self.#mastra = mastra;
276
+ await self.#autoInitialize();
277
+ return handler.bind(self);
278
+ };
279
+ };
280
+ return [
281
+ {
282
+ path: `/${PLATFORM}/events/:webhookId`,
283
+ method: "POST",
284
+ requiresAuth: false,
285
+ createHandler: withInit(this.#handleWebhook)
286
+ }
287
+ ];
288
+ }
289
+ /** Discovery metadata for the editor UI. */
290
+ getInfo() {
291
+ return {
292
+ id: this.id,
293
+ name: "Telegram",
294
+ isConfigured: this.#configured,
295
+ connectOptionsSchema: {
296
+ type: "object",
297
+ properties: {
298
+ botToken: {
299
+ type: "string",
300
+ description: "BotFather bot token. Omit to receive a BotFather deep link instead."
301
+ },
302
+ name: {
303
+ type: "string",
304
+ description: "Display name for the bot (defaults to the bot's @username)."
305
+ }
306
+ }
307
+ }
308
+ };
309
+ }
310
+ /**
311
+ * Restore installations from storage: rebuild an adapter per active bot and
312
+ * inject `AgentChannels` so the agent can receive events immediately.
313
+ * Idempotent. Does not re-register webhooks (they persist server-side across
314
+ * restarts); reconnect an agent if its `baseUrl` changed.
315
+ */
316
+ async initialize() {
317
+ if (this.#initPromise) return this.#initPromise;
318
+ this.#initPromise = this.#doInitialize();
319
+ try {
320
+ await this.#initPromise;
321
+ } catch (err) {
322
+ this.#initPromise = null;
323
+ throw err;
324
+ }
325
+ }
326
+ async #doInitialize() {
327
+ const store = await this.#getStore();
328
+ const active = (await store.list()).filter((i) => i.status === "active");
329
+ this.#configured = active.length > 0;
330
+ for (const installation of active) {
331
+ try {
332
+ await this.#activateInstallation(installation);
333
+ } catch (err) {
334
+ console.error(`[Telegram] Failed to restore installation "${installation.id}":`, err);
335
+ }
336
+ }
337
+ }
338
+ /**
339
+ * Update runtime provider settings. Telegram has no global auth credential to
340
+ * clear (per-bot tokens are managed via {@link connect}/{@link disconnect}),
341
+ * so `null` is a no-op; an object merges `apiBaseUrl`/`baseUrl` overrides.
342
+ */
343
+ async configure(credentials) {
344
+ if (credentials === null) return;
345
+ const apiBaseUrlChanged = credentials.apiBaseUrl !== void 0 && credentials.apiBaseUrl !== this.#config.apiBaseUrl;
346
+ this.#config = { ...this.#config, ...credentials };
347
+ if (!apiBaseUrlChanged) return;
348
+ const wasInitialized = this.#initPromise !== null;
349
+ for (const adapter of this.#adapters.values()) {
350
+ try {
351
+ await adapter.stopPolling();
352
+ } catch (err) {
353
+ console.warn("[Telegram] Failed to stop polling while reconfiguring:", err);
354
+ }
355
+ }
356
+ this.#adapters.clear();
357
+ this.#initPromise = null;
358
+ if (wasInitialized) await this.initialize();
359
+ }
360
+ /**
361
+ * Connect an agent to a Telegram bot.
362
+ *
363
+ * - With `options.botToken`: validate via `getMe`, mint a per-bot webhook
364
+ * secret, persist the installation, register the transport (webhook or
365
+ * polling), and return `{ type: 'immediate' }`.
366
+ * - Without a token: persist a pending installation and return
367
+ * `{ type: 'deep_link' }` pointing at BotFather.
368
+ */
369
+ async connect(agentId, options = {}) {
370
+ const store = await this.#getStore();
371
+ const existing = await store.getByAgent(agentId);
372
+ if (existing?.status === "active") {
373
+ throw new Error(`Agent "${agentId}" is already connected to Telegram. Disconnect first to reconnect.`);
374
+ }
375
+ if (!options.botToken) {
376
+ const installationId2 = existing?.id ?? randomUUID();
377
+ await store.save({
378
+ id: installationId2,
379
+ agentId,
380
+ webhookId: existing?.webhookId ?? randomUUID(),
381
+ status: "pending",
382
+ installedAt: existing?.installedAt ?? /* @__PURE__ */ new Date()
383
+ });
384
+ return { type: "deep_link", url: BOTFATHER_DEEP_LINK, installationId: installationId2 };
385
+ }
386
+ const me = await getMe(options.botToken, this.#apiBaseUrl());
387
+ const installationId = existing?.id ?? randomUUID();
388
+ const webhookId = existing?.webhookId ?? randomUUID();
389
+ const baseUrl = this.#getBaseUrl();
390
+ const mode = this.#resolveMode(baseUrl);
391
+ if (mode === "webhook" && !baseUrl) {
392
+ throw new Error(
393
+ 'TelegramProvider needs a baseUrl to register a webhook. Set `baseUrl`, configure the Mastra server, or use `mode: "polling"`.'
394
+ );
395
+ }
396
+ const webhookUrl = mode === "webhook" ? `${baseUrl}/${PLATFORM}/events/${webhookId}` : void 0;
397
+ const commands = normalizeCommands(options.commands ?? this.#config.commands ?? DEFAULT_COMMANDS);
398
+ const installation = {
399
+ id: installationId,
400
+ agentId,
401
+ webhookId,
402
+ status: "active",
403
+ botToken: options.botToken,
404
+ secretToken: generateSecretToken(),
405
+ username: options.name ?? me.username ?? me.first_name,
406
+ webhookUrl,
407
+ commands: commands.length ? commands : void 0,
408
+ installedAt: existing?.installedAt ?? /* @__PURE__ */ new Date()
409
+ };
410
+ await this.#registerTransport(installation, mode);
411
+ await this.#registerCommands(installation);
412
+ await store.save(installation);
413
+ await this.#activateInstallation(installation);
414
+ this.#configured = true;
415
+ await this.#config.onInstall?.(installation);
416
+ return { type: "immediate", installationId };
417
+ }
418
+ /** Disconnect an agent from Telegram, removing its webhook and installation. */
419
+ async disconnect(agentId) {
420
+ const store = await this.#getStore();
421
+ const existing = await store.getByAgent(agentId);
422
+ if (!existing) {
423
+ throw new Error(`No Telegram installation found for agent "${agentId}"`);
424
+ }
425
+ const adapter = this.#adapters.get(existing.id);
426
+ if (adapter) {
427
+ try {
428
+ await adapter.stopPolling();
429
+ } catch (err) {
430
+ console.warn(`[Telegram] Failed to stop polling for agent "${agentId}":`, err);
431
+ }
432
+ }
433
+ if (existing.botToken) {
434
+ try {
435
+ await deleteWebhook(existing.botToken, true, this.#apiBaseUrl());
436
+ } catch (err) {
437
+ console.warn(`[Telegram] Failed to delete webhook for agent "${agentId}":`, err);
438
+ }
439
+ }
440
+ this.#adapters.delete(existing.id);
441
+ await store.deleteByAgent(agentId);
442
+ this.#configured = (await store.list()).some((i) => i.status === "active");
443
+ }
444
+ /** List installations (public info only — no tokens or secrets). */
445
+ async listInstallations() {
446
+ const store = await this.#getStore();
447
+ const installations = await store.list();
448
+ return installations.map(toInstallationInfo);
449
+ }
450
+ /**
451
+ * Get the full installation for an agent (includes the bot token / secret).
452
+ * Returns `null` if the agent has no Telegram installation. Mirrors
453
+ * `SlackProvider.getInstallation`.
454
+ */
455
+ async getInstallation(agentId) {
456
+ const store = await this.#getStore();
457
+ return await store.getByAgent(agentId) ?? null;
458
+ }
459
+ /**
460
+ * Whether at least one bot is actively registered. Mirrors
461
+ * `SlackProvider.isConfigured` (Telegram has no global credential to check —
462
+ * "configured" means an active installation exists).
463
+ */
464
+ isConfigured() {
465
+ return this.#configured;
466
+ }
467
+ /**
468
+ * Get the live `TelegramAdapter` for an installation id, if one is active.
469
+ * Used for message formatting/posting. Mirrors `SlackProvider.getAdapter`.
470
+ */
471
+ getAdapter(installationId) {
472
+ return this.#adapters.get(installationId);
473
+ }
474
+ // ===========================================================================
475
+ // Webhook handling
476
+ // ===========================================================================
477
+ async #handleWebhook(c) {
478
+ const webhookId = c.req.param("webhookId");
479
+ if (!webhookId) return c.json({ ok: false, error: "Missing webhookId" }, 400);
480
+ const store = await this.#getStore();
481
+ const installation = await store.getByWebhookId(webhookId);
482
+ if (!installation || installation.status !== "active") {
483
+ return c.json({ ok: false, error: "Unknown webhook" }, 404);
484
+ }
485
+ const provided = c.req.header(SECRET_HEADER);
486
+ if (!secretMatches(provided, installation.secretToken)) {
487
+ return c.json({ ok: false, error: "Invalid secret token" }, 401);
488
+ }
489
+ const agent = this.#resolveAgent(installation.agentId);
490
+ if (!agent || !this.#mastra) {
491
+ return c.json({ ok: true });
492
+ }
493
+ const adapter = this.#getOrCreateAdapter(installation);
494
+ let channels = agent.getChannels();
495
+ if (!channels || channels.adapters[PLATFORM] !== adapter) {
496
+ channels = this.#createAgentChannels(agent, adapter);
497
+ await channels.initialize(this.#mastra);
498
+ }
499
+ const waitUntil = this.#config.waitUntil ?? resolveWaitUntil(c);
500
+ try {
501
+ return await channels.handleWebhookEvent(PLATFORM, c.req.raw, waitUntil ? { waitUntil } : void 0);
502
+ } catch (err) {
503
+ console.error("[Telegram] Error delegating to AgentChannels:", err);
504
+ return c.json({ ok: true });
505
+ }
506
+ }
507
+ // ===========================================================================
508
+ // Internals
509
+ // ===========================================================================
510
+ #apiBaseUrl() {
511
+ return this.#config.apiBaseUrl ?? TELEGRAM_API_BASE_URL;
512
+ }
513
+ #resolveMode(baseUrl) {
514
+ const mode = this.#config.mode ?? "auto";
515
+ if (mode === "auto") return baseUrl ? "webhook" : "polling";
516
+ return mode;
517
+ }
518
+ /** Register (or clear) the receive transport for a bot, enforcing the exclusion. */
519
+ async #registerTransport(installation, mode) {
520
+ if (!installation.botToken) return;
521
+ if (mode === "webhook" && installation.webhookUrl && installation.secretToken) {
522
+ await setWebhook(
523
+ installation.botToken,
524
+ {
525
+ url: installation.webhookUrl,
526
+ secretToken: installation.secretToken,
527
+ allowedUpdates: this.#config.allowedUpdates ?? [...DEFAULT_ALLOWED_UPDATES],
528
+ dropPendingUpdates: true
529
+ },
530
+ this.#apiBaseUrl()
531
+ );
532
+ } else {
533
+ await deleteWebhook(installation.botToken, true, this.#apiBaseUrl());
534
+ }
535
+ }
536
+ /** Publish the bot's command list (best-effort — a failure won't block connect). */
537
+ async #registerCommands(installation) {
538
+ if (!installation.botToken || !installation.commands?.length) return;
539
+ try {
540
+ await setMyCommands(
541
+ installation.botToken,
542
+ { commands: installation.commands, scope: this.#config.commandScope },
543
+ this.#apiBaseUrl()
544
+ );
545
+ } catch (err) {
546
+ console.warn(`[Telegram] Failed to register commands for agent "${installation.agentId}":`, err);
547
+ }
548
+ }
549
+ #getOrCreateAdapter(installation) {
550
+ const existing = this.#adapters.get(installation.id);
551
+ if (existing) return existing;
552
+ const adapter = createTelegramAdapter({
553
+ botToken: installation.botToken,
554
+ secretToken: installation.secretToken,
555
+ userName: installation.username,
556
+ apiBaseUrl: this.#apiBaseUrl(),
557
+ mode: installation.webhookUrl ? "webhook" : this.#config.mode ?? "auto",
558
+ ...this.#config.logger !== void 0 ? { logger: this.#config.logger } : {},
559
+ ...this.#config.longPolling !== void 0 ? { longPolling: this.#config.longPolling } : {}
560
+ });
561
+ this.#adapters.set(installation.id, adapter);
562
+ return adapter;
563
+ }
564
+ /** Rebuild the adapter and inject AgentChannels for an active installation. */
565
+ async #activateInstallation(installation) {
566
+ const agent = this.#resolveAgent(installation.agentId);
567
+ const adapter = this.#getOrCreateAdapter(installation);
568
+ if (agent && this.#mastra) {
569
+ const channels = this.#createAgentChannels(agent, adapter);
570
+ await channels.initialize(this.#mastra);
571
+ }
572
+ }
573
+ /**
574
+ * Create AgentChannels for an agent with the Telegram adapter, preserving any
575
+ * adapters/config the agent author already configured (mirrors `@mastra/slack`).
576
+ */
577
+ #createAgentChannels(agent, adapter) {
578
+ const existing = agent.getChannels();
579
+ const existingConfig = existing?.channelConfig;
580
+ const cfg = this.#config;
581
+ const entry = {
582
+ adapter,
583
+ ...resolveTelegramAdapterConfig(cfg),
584
+ // Telegram has no Block Kit; default tool rendering to plain text so
585
+ // 'cards'/'grouped'/'timeline' don't degrade to fallback text unexpectedly.
586
+ toolDisplay: cfg.toolDisplay ?? "text",
587
+ ...cfg.cors !== void 0 ? { cors: cfg.cors } : {},
588
+ ...cfg.formatError !== void 0 ? { formatError: cfg.formatError } : {}
589
+ };
590
+ const channels = new AgentChannels({
591
+ ...existingConfig,
592
+ adapters: { ...existingConfig?.adapters, [PLATFORM]: entry },
593
+ userName: agent.name,
594
+ handlers: cfg.handlers ?? existingConfig?.handlers,
595
+ inlineMedia: cfg.inlineMedia ?? existingConfig?.inlineMedia,
596
+ inlineLinks: cfg.inlineLinks ?? existingConfig?.inlineLinks,
597
+ state: cfg.state ?? existingConfig?.state,
598
+ threadContext: cfg.threadContext ?? existingConfig?.threadContext,
599
+ chatOptions: cfg.chatOptions ?? existingConfig?.chatOptions,
600
+ tools: cfg.tools ?? existingConfig?.tools,
601
+ resolveResourceId: cfg.resolveResourceId ?? existingConfig?.resolveResourceId,
602
+ waitUntil: cfg.waitUntil ?? existingConfig?.waitUntil,
603
+ resolveWaitUntil: cfg.resolveWaitUntil ?? existingConfig?.resolveWaitUntil
604
+ });
605
+ agent.setChannels(channels);
606
+ return channels;
607
+ }
608
+ async #autoInitialize() {
609
+ if (!this.#mastra) return;
610
+ await this.initialize();
611
+ }
612
+ #resolveAgent(agentId) {
613
+ try {
614
+ return this.#mastra?.getAgentById(agentId);
615
+ } catch {
616
+ return void 0;
617
+ }
618
+ }
619
+ async #getStore() {
620
+ if (this.#store) return this.#store;
621
+ const encryptionKey = this.#config.encryptionKey ?? process.env.MASTRA_ENCRYPTION_KEY;
622
+ this.#store = new TelegramInstallStore(await this.#resolveStorage(), encryptionKey);
623
+ return this.#store;
624
+ }
625
+ async #resolveStorage() {
626
+ if (this.#config.storage) return this.#config.storage;
627
+ const mastraStore = this.#mastra?.getStorage();
628
+ if (mastraStore) {
629
+ try {
630
+ await mastraStore.init();
631
+ const channels = await mastraStore.getStore("channels");
632
+ if (channels) return channels;
633
+ } catch {
634
+ }
635
+ }
636
+ return new InMemoryChannelsStorage();
637
+ }
638
+ #getBaseUrl() {
639
+ if (this.#config.baseUrl) return stripTrailingSlash(this.#config.baseUrl);
640
+ const server = this.#mastra?.getServer();
641
+ if (!server) return void 0;
642
+ const protocol = server.studioProtocol ?? "http";
643
+ const host = server.studioHost ?? server.host ?? "localhost";
644
+ const port = server.studioPort ?? server.port ?? (Number(process.env.PORT) || 4111);
645
+ const includePort = !(protocol === "https" && port === 443 || protocol === "http" && port === 80);
646
+ return includePort ? `${protocol}://${host}:${port}` : `${protocol}://${host}`;
647
+ }
648
+ };
649
+ function secretMatches(provided, expected) {
650
+ if (!provided || !expected) return false;
651
+ const a = Buffer.from(provided);
652
+ const b = Buffer.from(expected);
653
+ return a.length === b.length && timingSafeEqual(a, b);
654
+ }
655
+ function stripTrailingSlash(url) {
656
+ return url.endsWith("/") ? url.slice(0, -1) : url;
657
+ }
658
+
659
+ // src/index.ts
660
+ import { createTelegramAdapter as createTelegramAdapter2, TelegramAdapter } from "@chat-adapter/telegram";
661
+ export {
662
+ BOTFATHER_DEEP_LINK,
663
+ DEFAULT_ALLOWED_UPDATES,
664
+ DEFAULT_COMMANDS,
665
+ PLATFORM,
666
+ TELEGRAM_API_BASE_URL,
667
+ TelegramAdapter,
668
+ TelegramInstallStore,
669
+ TelegramProvider,
670
+ createTelegramAdapter2 as createTelegramAdapter,
671
+ deleteWebhook,
672
+ generateSecretToken,
673
+ getMe,
674
+ normalizeCommands,
675
+ resolveTelegramAdapterConfig,
676
+ setMyCommands,
677
+ setWebhook,
678
+ toInstallationInfo
679
+ };
680
+ //# sourceMappingURL=index.js.map