@mastra/telegram 0.0.0-aggregate-trace-20261015182851
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/README.md +49 -0
- package/dist/index.cjs +24938 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +482 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +834 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, hkdfSync, timingSafeEqual } from "crypto";
|
|
2
|
+
import { AgentChannels, resolveWaitUntil } from "@mastra/core/channels";
|
|
3
|
+
import { InMemoryChannelsStorage } from "@mastra/core/storage";
|
|
4
|
+
import { TelegramAdapter, createTelegramAdapter, createTelegramAdapter as createTelegramAdapter$1 } from "@chat-adapter/telegram";
|
|
5
|
+
//#region src/types.ts
|
|
6
|
+
/** Default Telegram Bot API origin. */
|
|
7
|
+
const TELEGRAM_API_BASE_URL = "https://api.telegram.org";
|
|
8
|
+
/**
|
|
9
|
+
* Default update types requested from Telegram. `message_reaction` must be
|
|
10
|
+
* listed explicitly (Telegram omits it otherwise).
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_ALLOWED_UPDATES = [
|
|
13
|
+
"message",
|
|
14
|
+
"edited_message",
|
|
15
|
+
"channel_post",
|
|
16
|
+
"edited_channel_post",
|
|
17
|
+
"callback_query",
|
|
18
|
+
"message_reaction"
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Deep link that opens BotFather so an operator can create a new bot with
|
|
22
|
+
* `/newbot`. Telegram has no OAuth: the resulting BotFather token is pasted
|
|
23
|
+
* back into {@link TelegramProvider.connect} to finish the installation.
|
|
24
|
+
*/
|
|
25
|
+
const BOTFATHER_DEEP_LINK = "https://t.me/botfather";
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/telegram-client.ts
|
|
28
|
+
/**
|
|
29
|
+
* Call a Bot API method. Sends a `GET` when `payload` is omitted and a JSON
|
|
30
|
+
* `POST` otherwise. Throws when the transport fails or the API returns
|
|
31
|
+
* `ok: false`.
|
|
32
|
+
*/
|
|
33
|
+
async function botApiRequest(botToken, method, apiBaseUrl, payload) {
|
|
34
|
+
const init = payload === void 0 ? void 0 : {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "content-type": "application/json" },
|
|
37
|
+
body: JSON.stringify(payload)
|
|
38
|
+
};
|
|
39
|
+
let response;
|
|
40
|
+
try {
|
|
41
|
+
response = await fetch(`${apiBaseUrl}/bot${botToken}/${method}`, {
|
|
42
|
+
...init,
|
|
43
|
+
signal: AbortSignal.timeout(1e4)
|
|
44
|
+
});
|
|
45
|
+
} catch (cause) {
|
|
46
|
+
throw Object.assign(new Error(`Telegram ${method} request failed`, { cause }), { isTransportError: true });
|
|
47
|
+
}
|
|
48
|
+
const body = await response.json().catch(() => null);
|
|
49
|
+
if (!response.ok || !body?.ok) {
|
|
50
|
+
const detail = body?.description ?? `HTTP ${response.status}`;
|
|
51
|
+
throw new Error(`Telegram ${method} failed: ${detail}`);
|
|
52
|
+
}
|
|
53
|
+
return body.result;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate a bot token via `getMe` and resolve the bot's identity. Throws if
|
|
57
|
+
* the token is rejected or the returned user is not a bot.
|
|
58
|
+
*
|
|
59
|
+
* @see https://core.telegram.org/bots/api#getme
|
|
60
|
+
*/
|
|
61
|
+
async function getMe(botToken, apiBaseUrl = TELEGRAM_API_BASE_URL) {
|
|
62
|
+
let result;
|
|
63
|
+
try {
|
|
64
|
+
result = await botApiRequest(botToken, "getMe", apiBaseUrl);
|
|
65
|
+
} catch (cause) {
|
|
66
|
+
if (cause instanceof Error && cause.isTransportError) throw cause;
|
|
67
|
+
throw new Error(`Telegram rejected the bot token: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
68
|
+
}
|
|
69
|
+
if (!result?.is_bot) throw new Error("Telegram getMe returned a non-bot user; expected a BotFather token");
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Register a per-bot webhook. Setting a webhook disables `getUpdates`
|
|
74
|
+
* (long-polling) for that bot — the two transports are mutually exclusive.
|
|
75
|
+
*
|
|
76
|
+
* @see https://core.telegram.org/bots/api#setwebhook
|
|
77
|
+
*/
|
|
78
|
+
async function setWebhook(botToken, options, apiBaseUrl = TELEGRAM_API_BASE_URL) {
|
|
79
|
+
await botApiRequest(botToken, "setWebhook", apiBaseUrl, {
|
|
80
|
+
url: options.url,
|
|
81
|
+
secret_token: options.secretToken,
|
|
82
|
+
allowed_updates: options.allowedUpdates,
|
|
83
|
+
drop_pending_updates: options.dropPendingUpdates
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Remove a bot's webhook. Required before switching a bot to long-polling
|
|
88
|
+
* (`getUpdates` fails while a webhook is set).
|
|
89
|
+
*
|
|
90
|
+
* @see https://core.telegram.org/bots/api#deletewebhook
|
|
91
|
+
*/
|
|
92
|
+
async function deleteWebhook(botToken, dropPendingUpdates = false, apiBaseUrl = TELEGRAM_API_BASE_URL) {
|
|
93
|
+
await botApiRequest(botToken, "deleteWebhook", apiBaseUrl, { drop_pending_updates: dropPendingUpdates });
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Publish the bot's command list for a scope.
|
|
97
|
+
*
|
|
98
|
+
* @see https://core.telegram.org/bots/api#setmycommands
|
|
99
|
+
*/
|
|
100
|
+
async function setMyCommands(botToken, options, apiBaseUrl = TELEGRAM_API_BASE_URL) {
|
|
101
|
+
await botApiRequest(botToken, "setMyCommands", apiBaseUrl, {
|
|
102
|
+
commands: options.commands,
|
|
103
|
+
scope: options.scope,
|
|
104
|
+
language_code: options.languageCode
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Generate a webhook secret token within Telegram's `setWebhook` constraint:
|
|
109
|
+
* 1-256 chars from `[A-Za-z0-9_-]`. base64url of 32 random bytes yields 43
|
|
110
|
+
* such chars.
|
|
111
|
+
*
|
|
112
|
+
* @see https://core.telegram.org/bots/api#setwebhook
|
|
113
|
+
*/
|
|
114
|
+
function generateSecretToken() {
|
|
115
|
+
return Buffer.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32))).toString("base64url");
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/commands.ts
|
|
119
|
+
/**
|
|
120
|
+
* Conventional command seed registered when a connect provides none.
|
|
121
|
+
* @see https://core.telegram.org/bots/features#commands
|
|
122
|
+
*/
|
|
123
|
+
const DEFAULT_COMMANDS = [
|
|
124
|
+
{
|
|
125
|
+
command: "start",
|
|
126
|
+
description: "Start a conversation"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
command: "help",
|
|
130
|
+
description: "Show what this bot can do"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
command: "settings",
|
|
134
|
+
description: "Manage your preferences"
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
/**
|
|
138
|
+
* Map user-supplied commands (agent capabilities) to Telegram `BotCommand[]`,
|
|
139
|
+
* enforcing the Bot API constraints: `command` is lowercased, stripped of a
|
|
140
|
+
* leading slash, reduced to `[a-z0-9_]`, and clamped to 1-32 chars;
|
|
141
|
+
* `description` defaults to `Run /<command>` and is clamped to 256 chars.
|
|
142
|
+
* Empty or duplicate command names are dropped.
|
|
143
|
+
*/
|
|
144
|
+
function normalizeCommands(raw) {
|
|
145
|
+
if (!raw) return [];
|
|
146
|
+
const seen = /* @__PURE__ */ new Set();
|
|
147
|
+
const commands = [];
|
|
148
|
+
for (const item of raw) {
|
|
149
|
+
const input = typeof item === "string" ? { command: item } : item;
|
|
150
|
+
const command = input.command.replace(/^\//, "").toLowerCase().replace(/[^a-z0-9_]/g, "").slice(0, 32);
|
|
151
|
+
if (!command || seen.has(command)) continue;
|
|
152
|
+
seen.add(command);
|
|
153
|
+
const description = (input.description?.trim() || `Run /${command}`).slice(0, 256);
|
|
154
|
+
commands.push({
|
|
155
|
+
command,
|
|
156
|
+
description
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return commands;
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/crypto.ts
|
|
163
|
+
/**
|
|
164
|
+
* Opt-in AES-256-GCM encryption for installation secrets at rest, with
|
|
165
|
+
* HKDF-SHA256 key derivation (mirrors `@mastra/slack`'s `crypto.ts`). Each value
|
|
166
|
+
* gets a fresh random 16-byte salt + 12-byte IV; the salt travels in the
|
|
167
|
+
* ciphertext, so the same passphrase never derives the same key twice. The
|
|
168
|
+
* algorithm prefix lets plaintext and encrypted values coexist during migration,
|
|
169
|
+
* so {@link decrypt} can no-op on plaintext.
|
|
170
|
+
*
|
|
171
|
+
* Format: `aes-256-gcm-hkdf:base64(salt):base64(iv):base64(authTag):base64(ciphertext)`
|
|
172
|
+
*/
|
|
173
|
+
const ALGO_PREFIX = "aes-256-gcm-hkdf";
|
|
174
|
+
const HKDF_INFO = "mastra-telegram-encryption";
|
|
175
|
+
function deriveKey(passphrase, salt) {
|
|
176
|
+
return Buffer.from(hkdfSync("sha256", passphrase, salt, HKDF_INFO, 32));
|
|
177
|
+
}
|
|
178
|
+
/** Whether a stored value was produced by {@link encrypt}. */
|
|
179
|
+
function isEncrypted(value) {
|
|
180
|
+
return value.startsWith(`${ALGO_PREFIX}:`);
|
|
181
|
+
}
|
|
182
|
+
/** Encrypt a UTF-8 string with a per-value random salt + IV. */
|
|
183
|
+
function encrypt(plaintext, passphrase) {
|
|
184
|
+
const salt = Buffer.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16)));
|
|
185
|
+
const iv = Buffer.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(12)));
|
|
186
|
+
const cipher = createCipheriv("aes-256-gcm", deriveKey(passphrase, salt), iv);
|
|
187
|
+
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
188
|
+
const tag = cipher.getAuthTag();
|
|
189
|
+
return `${ALGO_PREFIX}:${salt.toString("base64")}:${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
|
|
190
|
+
}
|
|
191
|
+
/** Decrypt a value from {@link encrypt}. Plaintext (unprefixed) is returned unchanged. */
|
|
192
|
+
function decrypt(value, passphrase) {
|
|
193
|
+
if (!isEncrypted(value)) return value;
|
|
194
|
+
const [, saltB64, ivB64, tagB64, ctB64] = value.split(":");
|
|
195
|
+
if (!saltB64 || !ivB64 || !tagB64 || ctB64 === void 0) throw new Error("Invalid ciphertext payload");
|
|
196
|
+
const decipher = createDecipheriv("aes-256-gcm", deriveKey(passphrase, Buffer.from(saltB64, "base64")), Buffer.from(ivB64, "base64"));
|
|
197
|
+
decipher.setAuthTag(Buffer.from(tagB64, "base64"));
|
|
198
|
+
return Buffer.concat([decipher.update(Buffer.from(ctB64, "base64")), decipher.final()]).toString("utf8");
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/install-store.ts
|
|
202
|
+
/** Platform identifier used for every stored record and route. */
|
|
203
|
+
const PLATFORM = "telegram";
|
|
204
|
+
/**
|
|
205
|
+
* Persistence for Telegram bot installations, layered over the platform-agnostic
|
|
206
|
+
* `ChannelsStorage` (the same store `@mastra/slack` uses). Installations are
|
|
207
|
+
* keyed by agent — one bot = one agent — and the per-bot secret fields live in
|
|
208
|
+
* the record's `data` blob. When an `encryptionKey` is supplied, `botToken` and
|
|
209
|
+
* `secretToken` are AES-256-GCM encrypted at rest.
|
|
210
|
+
*/
|
|
211
|
+
var TelegramInstallStore = class {
|
|
212
|
+
storage;
|
|
213
|
+
encryptionKey;
|
|
214
|
+
constructor(storage, encryptionKey) {
|
|
215
|
+
this.storage = storage;
|
|
216
|
+
this.encryptionKey = encryptionKey;
|
|
217
|
+
}
|
|
218
|
+
/** The active or pending installation for an agent, if any. */
|
|
219
|
+
async getByAgent(agentId) {
|
|
220
|
+
const record = await this.storage.getInstallationByAgent(PLATFORM, agentId);
|
|
221
|
+
return record ? this.#fromRecord(record) : null;
|
|
222
|
+
}
|
|
223
|
+
/** Look up an installation by the routing id in its webhook path (M1 dispatch). */
|
|
224
|
+
async getByWebhookId(webhookId) {
|
|
225
|
+
const record = await this.storage.getInstallationByWebhookId(webhookId);
|
|
226
|
+
return record && record.platform === "telegram" ? this.#fromRecord(record) : null;
|
|
227
|
+
}
|
|
228
|
+
/** Insert or replace an installation. */
|
|
229
|
+
async save(installation) {
|
|
230
|
+
await this.storage.saveInstallation(this.#toRecord(installation));
|
|
231
|
+
}
|
|
232
|
+
/** All Telegram installations (active and pending). */
|
|
233
|
+
async list() {
|
|
234
|
+
return (await this.storage.listInstallations(PLATFORM)).map((r) => this.#fromRecord(r));
|
|
235
|
+
}
|
|
236
|
+
/** Remove an agent's installation, if present. */
|
|
237
|
+
async deleteByAgent(agentId) {
|
|
238
|
+
const record = await this.storage.getInstallationByAgent(PLATFORM, agentId);
|
|
239
|
+
if (record) await this.storage.deleteInstallation(record.id);
|
|
240
|
+
}
|
|
241
|
+
#enc(value) {
|
|
242
|
+
return value && this.encryptionKey ? encrypt(value, this.encryptionKey) : value;
|
|
243
|
+
}
|
|
244
|
+
#dec(value) {
|
|
245
|
+
if (!value) return value;
|
|
246
|
+
if (!this.encryptionKey) {
|
|
247
|
+
if (isEncrypted(value)) throw new Error("Telegram installation secrets are encrypted at rest, but no encryption key is configured. Set `encryptionKey` on TelegramProvider or MASTRA_ENCRYPTION_KEY.");
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
return decrypt(value, this.encryptionKey);
|
|
251
|
+
}
|
|
252
|
+
#toRecord(install) {
|
|
253
|
+
const data = {
|
|
254
|
+
botToken: this.#enc(install.botToken),
|
|
255
|
+
botUserId: install.botUserId,
|
|
256
|
+
secretToken: this.#enc(install.secretToken),
|
|
257
|
+
username: install.username,
|
|
258
|
+
webhookUrl: install.webhookUrl,
|
|
259
|
+
commands: install.commands
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
id: install.id,
|
|
263
|
+
platform: PLATFORM,
|
|
264
|
+
agentId: install.agentId,
|
|
265
|
+
status: install.status,
|
|
266
|
+
webhookId: install.webhookId,
|
|
267
|
+
data,
|
|
268
|
+
createdAt: install.installedAt,
|
|
269
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
#fromRecord(record) {
|
|
273
|
+
const data = record.data ?? {};
|
|
274
|
+
return {
|
|
275
|
+
id: record.id,
|
|
276
|
+
agentId: record.agentId,
|
|
277
|
+
webhookId: record.webhookId ?? "",
|
|
278
|
+
status: record.status === "active" ? "active" : "pending",
|
|
279
|
+
botToken: this.#dec(data.botToken),
|
|
280
|
+
botUserId: data.botUserId,
|
|
281
|
+
secretToken: this.#dec(data.secretToken),
|
|
282
|
+
username: data.username,
|
|
283
|
+
webhookUrl: data.webhookUrl,
|
|
284
|
+
commands: data.commands,
|
|
285
|
+
installedAt: record.createdAt
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
/** Project an installation to its public, secret-free info for the editor UI. */
|
|
290
|
+
function toInstallationInfo(install) {
|
|
291
|
+
return {
|
|
292
|
+
id: install.id,
|
|
293
|
+
platform: PLATFORM,
|
|
294
|
+
agentId: install.agentId,
|
|
295
|
+
status: install.status,
|
|
296
|
+
displayName: install.username,
|
|
297
|
+
installedAt: install.installedAt
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/telegram-provider.ts
|
|
302
|
+
/**
|
|
303
|
+
* Resolve the per-adapter streaming/typing config the provider applies to the
|
|
304
|
+
* Telegram entry in `AgentChannels.adapters`. This is the wrapper's stream
|
|
305
|
+
* binding: enabling `streaming` runs the adapter's post-and-edit
|
|
306
|
+
* (`editMessageText`) chunking loop, and `typingStatus` keeps a `sendChatAction`
|
|
307
|
+
* indicator alive — both default on.
|
|
308
|
+
*/
|
|
309
|
+
function resolveTelegramAdapterConfig(config) {
|
|
310
|
+
return {
|
|
311
|
+
streaming: config.streaming ?? true,
|
|
312
|
+
typingStatus: config.typingStatus ?? true
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/** Header Telegram echoes the per-bot secret on for every webhook POST. */
|
|
316
|
+
const SECRET_HEADER = "x-telegram-bot-api-secret-token";
|
|
317
|
+
/**
|
|
318
|
+
* Telegram channel provider for Mastra — a {@link ChannelProvider} over
|
|
319
|
+
* `@chat-adapter/telegram`. The adapter handles the Bot API transport (webhook
|
|
320
|
+
* parse, send/edit, typing, rich messages); this provider adds the
|
|
321
|
+
* install/lifecycle layer.
|
|
322
|
+
*
|
|
323
|
+
* Implemented:
|
|
324
|
+
* - **`mastra-telegram-i2g.2`** — multi-token install store (one bot = one
|
|
325
|
+
* agent), `connect()`/`disconnect()`, `getMe` token ingestion.
|
|
326
|
+
* - **`mastra-telegram-i2g.3`** — per-bot `setWebhook` lifecycle,
|
|
327
|
+
* `X-Telegram-Bot-Api-Secret-Token` verification, webhook⇄polling exclusion,
|
|
328
|
+
* and a mounted POST route that delegates to `AgentChannels.handleWebhookEvent`.
|
|
329
|
+
*
|
|
330
|
+
* Later: `setMyCommands` + streaming (`mastra-telegram-i2g.4`).
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```ts
|
|
334
|
+
* const telegram = new TelegramProvider({ baseUrl: 'https://my-app.example.com' })
|
|
335
|
+
* const mastra = new Mastra({ agents: { myAgent }, channels: { telegram } })
|
|
336
|
+
* await telegram.connect('my-agent', { botToken: '123456:ABC-...' }) // → { type: 'immediate' }
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
var TelegramProvider = class {
|
|
340
|
+
id = PLATFORM;
|
|
341
|
+
#config;
|
|
342
|
+
#mastra;
|
|
343
|
+
#store;
|
|
344
|
+
/** Live adapters, keyed by installation id. */
|
|
345
|
+
#adapters = /* @__PURE__ */ new Map();
|
|
346
|
+
/** Cached sync view of whether any active bot is registered (for {@link getInfo}). */
|
|
347
|
+
#configured = false;
|
|
348
|
+
#initPromise = null;
|
|
349
|
+
#connectingBotTokens = /* @__PURE__ */ new Set();
|
|
350
|
+
constructor(config = {}) {
|
|
351
|
+
if ("tokenResolver" in config && config.tokenResolver && "botToken" in config && config.botToken) throw new Error("TelegramProvider cannot be constructed with both `botToken` and `tokenResolver` — pick one credential mode (self-managed or delegated).");
|
|
352
|
+
this.#config = config;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Called by Mastra when this channel is registered.
|
|
356
|
+
* @internal
|
|
357
|
+
*/
|
|
358
|
+
__attach(mastra) {
|
|
359
|
+
if (this.#mastra && this.#mastra !== mastra) {
|
|
360
|
+
this.#initPromise = null;
|
|
361
|
+
this.#store = void 0;
|
|
362
|
+
this.#adapters.clear();
|
|
363
|
+
this.#configured = false;
|
|
364
|
+
}
|
|
365
|
+
this.#mastra = mastra;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Per-bot webhook route. A single POST endpoint keyed by an opaque
|
|
369
|
+
* `webhookId`; the per-bot secret is verified from the request header, never
|
|
370
|
+
* carried in the URL. Auto-initializes on first hit (mirrors `@mastra/slack`).
|
|
371
|
+
*/
|
|
372
|
+
getRoutes() {
|
|
373
|
+
const self = this;
|
|
374
|
+
const withInit = (handler) => {
|
|
375
|
+
return async ({ mastra }) => {
|
|
376
|
+
self.#mastra = mastra;
|
|
377
|
+
await self.#autoInitialize();
|
|
378
|
+
return handler.bind(self);
|
|
379
|
+
};
|
|
380
|
+
};
|
|
381
|
+
return [{
|
|
382
|
+
path: `/${PLATFORM}/events/:webhookId`,
|
|
383
|
+
method: "POST",
|
|
384
|
+
requiresAuth: false,
|
|
385
|
+
createHandler: withInit(this.#handleWebhook)
|
|
386
|
+
}];
|
|
387
|
+
}
|
|
388
|
+
/** Discovery metadata for the editor UI. */
|
|
389
|
+
getInfo() {
|
|
390
|
+
return {
|
|
391
|
+
id: this.id,
|
|
392
|
+
name: "Telegram",
|
|
393
|
+
isConfigured: this.isConfigured(),
|
|
394
|
+
connectOptionsSchema: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: {
|
|
397
|
+
botToken: {
|
|
398
|
+
type: "string",
|
|
399
|
+
description: "BotFather bot token. Omit to use the provider default token. If neither is set, returns a BotFather deep link."
|
|
400
|
+
},
|
|
401
|
+
name: {
|
|
402
|
+
type: "string",
|
|
403
|
+
description: "Display name for the bot (defaults to the bot's @username)."
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Restore installations from storage: rebuild an adapter per active bot and
|
|
411
|
+
* inject `AgentChannels` so the agent can receive events immediately.
|
|
412
|
+
* Idempotent. Does not re-register webhooks (they persist server-side across
|
|
413
|
+
* restarts); reconnect an agent if its `baseUrl` changed.
|
|
414
|
+
*/
|
|
415
|
+
async initialize() {
|
|
416
|
+
if (this.#initPromise) return this.#initPromise;
|
|
417
|
+
this.#initPromise = this.#doInitialize();
|
|
418
|
+
try {
|
|
419
|
+
await this.#initPromise;
|
|
420
|
+
} catch (err) {
|
|
421
|
+
this.#initPromise = null;
|
|
422
|
+
throw err;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async #doInitialize() {
|
|
426
|
+
const store = await this.#getStore();
|
|
427
|
+
const active = (await store.list()).filter((i) => i.status === "active");
|
|
428
|
+
this.#configured = active.length > 0;
|
|
429
|
+
for (const installation of active) try {
|
|
430
|
+
const prepared = await this.#migrateInstallation(installation, store);
|
|
431
|
+
if (!prepared) continue;
|
|
432
|
+
await this.#activateInstallation(prepared);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.error(`[Telegram] Failed to restore installation "${installation.id}":`, err);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Bring a stored installation up to the provider's current credential mode.
|
|
439
|
+
*
|
|
440
|
+
* When {@link TelegramProviderConfig.tokenResolver} is configured but the
|
|
441
|
+
* record was written under self-managed mode, the record still carries a
|
|
442
|
+
* `botToken` and may be missing `botUserId`. Left as-is, the adapter would
|
|
443
|
+
* keep using that stale token (bypassing rotation) and duplicate detection
|
|
444
|
+
* would fall back to token comparison. Resolve the current token, verify it
|
|
445
|
+
* points at the same bot, populate `botUserId` if missing, and drop the
|
|
446
|
+
* persisted token so future rotations flow through the resolver.
|
|
447
|
+
*
|
|
448
|
+
* For a legacy record with no `botUserId`, the "same bot" check needs an
|
|
449
|
+
* expected identity — so we `getMe` the stored `botToken` first and compare
|
|
450
|
+
* that identity to the resolver's. A stored-token lookup failure leaves the
|
|
451
|
+
* record untouched (we cannot prove it's still the same bot, and silently
|
|
452
|
+
* assigning `me.id` would let a repointed resolver retarget the install and
|
|
453
|
+
* orphan the old bot's webhook).
|
|
454
|
+
*
|
|
455
|
+
* A resolver failure — or an identity mismatch — is not fatal here: we log
|
|
456
|
+
* and leave the record untouched, so the operator can investigate before
|
|
457
|
+
* connect/disconnect on that installation.
|
|
458
|
+
*/
|
|
459
|
+
async #migrateInstallation(installation, store) {
|
|
460
|
+
if (!this.#config.tokenResolver) return installation;
|
|
461
|
+
const needsMigration = installation.botToken !== void 0 || installation.botUserId === void 0;
|
|
462
|
+
const needsIdentityCheck = installation.botUserId !== void 0;
|
|
463
|
+
if (!needsMigration && !needsIdentityCheck) return installation;
|
|
464
|
+
try {
|
|
465
|
+
const resolved = await this.#config.tokenResolver();
|
|
466
|
+
if (!resolved) return installation;
|
|
467
|
+
const me = await getMe(resolved, this.#apiBaseUrl());
|
|
468
|
+
let expectedBotUserId = installation.botUserId;
|
|
469
|
+
if (expectedBotUserId === void 0 && installation.botToken) try {
|
|
470
|
+
expectedBotUserId = (await getMe(installation.botToken, this.#apiBaseUrl())).id;
|
|
471
|
+
} catch (err) {
|
|
472
|
+
console.warn(`[Telegram] Could not verify stored bot identity for installation "${installation.id}" during delegated-mode migration; leaving the record untouched.`, err);
|
|
473
|
+
return installation;
|
|
474
|
+
}
|
|
475
|
+
if (expectedBotUserId !== void 0 && expectedBotUserId !== me.id) {
|
|
476
|
+
console.warn(`[Telegram] Delegated credential for installation "${installation.id}" now resolves to bot ${me.id} but was bot ${expectedBotUserId}. Disconnect and reconnect the agent to adopt the new bot.`);
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
if (!needsMigration) return installation;
|
|
480
|
+
const { botToken: _dropped, ...rest } = installation;
|
|
481
|
+
const migrated = {
|
|
482
|
+
...rest,
|
|
483
|
+
botUserId: me.id
|
|
484
|
+
};
|
|
485
|
+
await store.save(migrated);
|
|
486
|
+
return migrated;
|
|
487
|
+
} catch (err) {
|
|
488
|
+
console.warn(`[Telegram] Delegated-mode migration failed for installation "${installation.id}":`, err);
|
|
489
|
+
return installation;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Update runtime provider settings. Pass `botToken` to change the default
|
|
494
|
+
* bot token new {@link connect} calls fall back to when none is supplied
|
|
495
|
+
* per-agent (matching {@link SlackProvider.configure}'s rotation pattern);
|
|
496
|
+
* pass `apiBaseUrl`/`baseUrl` to point the adapter at a different host.
|
|
497
|
+
* `null` clears the default `botToken` only — per-bot installs are managed
|
|
498
|
+
* via {@link connect}/{@link disconnect}.
|
|
499
|
+
*/
|
|
500
|
+
async configure(credentials) {
|
|
501
|
+
if (this.#config.tokenResolver && credentials?.botToken !== void 0) throw new Error("TelegramProvider was constructed with a tokenResolver — bot tokens are managed externally. Remove the tokenResolver to manage credentials manually via configure().");
|
|
502
|
+
if (credentials === null) {
|
|
503
|
+
this.#config = {
|
|
504
|
+
...this.#config,
|
|
505
|
+
botToken: void 0
|
|
506
|
+
};
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const apiBaseUrlChanged = credentials.apiBaseUrl !== void 0 && credentials.apiBaseUrl !== this.#config.apiBaseUrl;
|
|
510
|
+
this.#config = {
|
|
511
|
+
...this.#config,
|
|
512
|
+
...credentials
|
|
513
|
+
};
|
|
514
|
+
if (!apiBaseUrlChanged) return;
|
|
515
|
+
const wasInitialized = this.#initPromise !== null;
|
|
516
|
+
for (const adapter of this.#adapters.values()) try {
|
|
517
|
+
await adapter.stopPolling();
|
|
518
|
+
} catch (err) {
|
|
519
|
+
console.warn("[Telegram] Failed to stop polling while reconfiguring:", err);
|
|
520
|
+
}
|
|
521
|
+
this.#adapters.clear();
|
|
522
|
+
this.#initPromise = null;
|
|
523
|
+
if (wasInitialized) await this.initialize();
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Connect an agent to a Telegram bot.
|
|
527
|
+
*
|
|
528
|
+
* - With `options.botToken` or a provider default token: validate via `getMe`,
|
|
529
|
+
* mint a per-bot webhook secret, persist the installation, register the
|
|
530
|
+
* transport (webhook or polling), and return `{ type: 'immediate' }`.
|
|
531
|
+
* - Without either token: persist a pending installation and return
|
|
532
|
+
* `{ type: 'deep_link' }` pointing at BotFather.
|
|
533
|
+
*/
|
|
534
|
+
async connect(agentId, options = {}) {
|
|
535
|
+
const store = await this.#getStore();
|
|
536
|
+
const existing = await store.getByAgent(agentId);
|
|
537
|
+
if (existing?.status === "active") throw new Error(`Agent "${agentId}" is already connected to Telegram. Disconnect first to reconnect.`);
|
|
538
|
+
if (this.#config.tokenResolver && options.botToken !== void 0) throw new Error("TelegramProvider was constructed with a tokenResolver — bot tokens are managed externally and cannot be supplied per connect() call.");
|
|
539
|
+
let botToken;
|
|
540
|
+
if (this.#config.tokenResolver) {
|
|
541
|
+
const resolved = await this.#config.tokenResolver();
|
|
542
|
+
if (!resolved) throw new Error("TelegramProvider tokenResolver returned an empty bot token. Attach a Telegram bot credential upstream before connecting an agent.");
|
|
543
|
+
botToken = resolved;
|
|
544
|
+
} else botToken = options.botToken ?? this.#config.botToken;
|
|
545
|
+
if (!botToken) {
|
|
546
|
+
const installationId = existing?.id ?? globalThis.crypto.randomUUID();
|
|
547
|
+
await store.save({
|
|
548
|
+
id: installationId,
|
|
549
|
+
agentId,
|
|
550
|
+
webhookId: existing?.webhookId ?? globalThis.crypto.randomUUID(),
|
|
551
|
+
status: "pending",
|
|
552
|
+
installedAt: existing?.installedAt ?? /* @__PURE__ */ new Date()
|
|
553
|
+
});
|
|
554
|
+
return {
|
|
555
|
+
type: "deep_link",
|
|
556
|
+
url: BOTFATHER_DEEP_LINK,
|
|
557
|
+
installationId
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const baseUrl = this.#getBaseUrl();
|
|
561
|
+
const mode = this.#resolveMode(baseUrl);
|
|
562
|
+
if (mode === "webhook" && !baseUrl) throw new Error("TelegramProvider needs a baseUrl to register a webhook. Set `baseUrl`, configure the Mastra server, or use `mode: \"polling\"`.");
|
|
563
|
+
if (this.#connectingBotTokens.has(botToken)) throw new Error("This Telegram bot is already being connected. Wait for that connection to finish.");
|
|
564
|
+
this.#connectingBotTokens.add(botToken);
|
|
565
|
+
try {
|
|
566
|
+
const me = await getMe(botToken, this.#apiBaseUrl());
|
|
567
|
+
const duplicate = (await store.list()).find((i) => i.status === "active" && i.agentId !== agentId && (i.botUserId !== void 0 ? i.botUserId === me.id : i.botToken === botToken));
|
|
568
|
+
if (duplicate) throw new Error(`This Telegram bot is already connected to agent "${duplicate.agentId}". Disconnect it before connecting another agent.`);
|
|
569
|
+
const installationId = existing?.id ?? globalThis.crypto.randomUUID();
|
|
570
|
+
const webhookId = existing?.webhookId ?? globalThis.crypto.randomUUID();
|
|
571
|
+
const webhookUrl = mode === "webhook" ? `${baseUrl}/${PLATFORM}/events/${webhookId}` : void 0;
|
|
572
|
+
const commands = normalizeCommands(options.commands ?? this.#config.commands ?? DEFAULT_COMMANDS);
|
|
573
|
+
const installation = {
|
|
574
|
+
id: installationId,
|
|
575
|
+
agentId,
|
|
576
|
+
webhookId,
|
|
577
|
+
status: "active",
|
|
578
|
+
...this.#config.tokenResolver ? {} : { botToken },
|
|
579
|
+
botUserId: me.id,
|
|
580
|
+
secretToken: generateSecretToken(),
|
|
581
|
+
username: options.name ?? me.username ?? me.first_name,
|
|
582
|
+
webhookUrl,
|
|
583
|
+
commands: commands.length ? commands : void 0,
|
|
584
|
+
installedAt: existing?.installedAt ?? /* @__PURE__ */ new Date()
|
|
585
|
+
};
|
|
586
|
+
await this.#registerTransport(installation, mode, botToken);
|
|
587
|
+
await this.#registerCommands(installation, botToken);
|
|
588
|
+
await store.save(installation);
|
|
589
|
+
await this.#activateInstallation(installation);
|
|
590
|
+
this.#configured = true;
|
|
591
|
+
await this.#config.onInstall?.(installation);
|
|
592
|
+
return {
|
|
593
|
+
type: "immediate",
|
|
594
|
+
installationId
|
|
595
|
+
};
|
|
596
|
+
} finally {
|
|
597
|
+
this.#connectingBotTokens.delete(botToken);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/** Disconnect an agent from Telegram, removing its webhook and installation. */
|
|
601
|
+
async disconnect(agentId) {
|
|
602
|
+
const store = await this.#getStore();
|
|
603
|
+
const existing = await store.getByAgent(agentId);
|
|
604
|
+
if (!existing) throw new Error(`No Telegram installation found for agent "${agentId}"`);
|
|
605
|
+
const adapter = this.#adapters.get(existing.id);
|
|
606
|
+
if (adapter) try {
|
|
607
|
+
await adapter.stopPolling();
|
|
608
|
+
} catch (err) {
|
|
609
|
+
console.warn(`[Telegram] Failed to stop polling for agent "${agentId}":`, err);
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
let botToken;
|
|
613
|
+
if (existing.botToken) botToken = existing.botToken;
|
|
614
|
+
else if (this.#config.tokenResolver) {
|
|
615
|
+
const resolved = await this.#config.tokenResolver();
|
|
616
|
+
if (resolved && existing.botUserId !== void 0) {
|
|
617
|
+
const me = await getMe(resolved, this.#apiBaseUrl());
|
|
618
|
+
if (me.id !== existing.botUserId) console.warn(`[Telegram] Skipping deleteWebhook for agent "${agentId}": resolver now points at bot ${me.id} but this installation was bot ${existing.botUserId}.`);
|
|
619
|
+
else botToken = resolved;
|
|
620
|
+
} else if (resolved) botToken = resolved;
|
|
621
|
+
}
|
|
622
|
+
if (botToken) await deleteWebhook(botToken, true, this.#apiBaseUrl());
|
|
623
|
+
} catch (err) {
|
|
624
|
+
console.warn(`[Telegram] Failed to delete webhook for agent "${agentId}":`, err);
|
|
625
|
+
}
|
|
626
|
+
this.#adapters.delete(existing.id);
|
|
627
|
+
await store.deleteByAgent(agentId);
|
|
628
|
+
this.#configured = (await store.list()).some((i) => i.status === "active");
|
|
629
|
+
}
|
|
630
|
+
/** List installations (public info only — no tokens or secrets). */
|
|
631
|
+
async listInstallations() {
|
|
632
|
+
return (await (await this.#getStore()).list()).map(toInstallationInfo);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Get the full installation for an agent (includes the bot token / secret).
|
|
636
|
+
* Returns `null` if the agent has no Telegram installation. Mirrors
|
|
637
|
+
* `SlackProvider.getInstallation`.
|
|
638
|
+
*/
|
|
639
|
+
async getInstallation(agentId) {
|
|
640
|
+
return await (await this.#getStore()).getByAgent(agentId) ?? null;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Whether the provider is ready to connect agents: either a bot is already
|
|
644
|
+
* registered (#configured) or a credential source exists — a default
|
|
645
|
+
* `botToken`, or a `tokenResolver` in delegated mode (@mastra/connect).
|
|
646
|
+
* Without the credential-source half, a freshly attached platform credential
|
|
647
|
+
* reports unconfigured and UIs hide the connect action entirely. Mirrors
|
|
648
|
+
* `DiscordProvider.isConfigured` / `SlackProvider.isConfigured`.
|
|
649
|
+
*/
|
|
650
|
+
isConfigured() {
|
|
651
|
+
return this.#configured || Boolean(this.#config.tokenResolver ?? this.#config.botToken);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Get the live `TelegramAdapter` for an installation id, if one is active.
|
|
655
|
+
* Used for message formatting/posting. Mirrors `SlackProvider.getAdapter`.
|
|
656
|
+
*/
|
|
657
|
+
getAdapter(installationId) {
|
|
658
|
+
return this.#adapters.get(installationId);
|
|
659
|
+
}
|
|
660
|
+
async #handleWebhook(c) {
|
|
661
|
+
const webhookId = c.req.param("webhookId");
|
|
662
|
+
if (!webhookId) return c.json({
|
|
663
|
+
ok: false,
|
|
664
|
+
error: "Missing webhookId"
|
|
665
|
+
}, 400);
|
|
666
|
+
const installation = await (await this.#getStore()).getByWebhookId(webhookId);
|
|
667
|
+
if (!installation || installation.status !== "active") return c.json({
|
|
668
|
+
ok: false,
|
|
669
|
+
error: "Unknown webhook"
|
|
670
|
+
}, 404);
|
|
671
|
+
if (!secretMatches(c.req.header(SECRET_HEADER), installation.secretToken)) return c.json({
|
|
672
|
+
ok: false,
|
|
673
|
+
error: "Invalid secret token"
|
|
674
|
+
}, 401);
|
|
675
|
+
const agent = this.#resolveAgent(installation.agentId);
|
|
676
|
+
if (!agent || !this.#mastra) return c.json({ ok: true });
|
|
677
|
+
const adapter = this.#getOrCreateAdapter(installation);
|
|
678
|
+
let channels = agent.getChannels();
|
|
679
|
+
if (!channels || channels.adapters["telegram"] !== adapter) {
|
|
680
|
+
channels = this.#createAgentChannels(agent, adapter);
|
|
681
|
+
await channels.initialize(this.#mastra);
|
|
682
|
+
}
|
|
683
|
+
const waitUntil = this.#config.waitUntil ?? resolveWaitUntil(c);
|
|
684
|
+
try {
|
|
685
|
+
return await channels.handleWebhookEvent(PLATFORM, c.req.raw, waitUntil ? { waitUntil } : void 0);
|
|
686
|
+
} catch (err) {
|
|
687
|
+
console.error("[Telegram] Error delegating to AgentChannels:", err);
|
|
688
|
+
return c.json({ ok: true });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
#apiBaseUrl() {
|
|
692
|
+
return this.#config.apiBaseUrl ?? "https://api.telegram.org";
|
|
693
|
+
}
|
|
694
|
+
#resolveMode(baseUrl) {
|
|
695
|
+
const mode = this.#config.mode ?? "auto";
|
|
696
|
+
if (mode === "auto") return baseUrl ? "webhook" : "polling";
|
|
697
|
+
return mode;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Register (or clear) the receive transport for a bot, enforcing the
|
|
701
|
+
* exclusion. The token is passed explicitly — delegated-mode installations
|
|
702
|
+
* do not persist one.
|
|
703
|
+
*/
|
|
704
|
+
async #registerTransport(installation, mode, botToken) {
|
|
705
|
+
if (mode === "webhook" && installation.webhookUrl && installation.secretToken) await setWebhook(botToken, {
|
|
706
|
+
url: installation.webhookUrl,
|
|
707
|
+
secretToken: installation.secretToken,
|
|
708
|
+
allowedUpdates: this.#config.allowedUpdates ?? [...DEFAULT_ALLOWED_UPDATES],
|
|
709
|
+
dropPendingUpdates: true
|
|
710
|
+
}, this.#apiBaseUrl());
|
|
711
|
+
else await deleteWebhook(botToken, true, this.#apiBaseUrl());
|
|
712
|
+
}
|
|
713
|
+
/** Publish the bot's command list (best-effort — a failure won't block connect). */
|
|
714
|
+
async #registerCommands(installation, botToken) {
|
|
715
|
+
if (!installation.commands?.length) return;
|
|
716
|
+
try {
|
|
717
|
+
await setMyCommands(botToken, {
|
|
718
|
+
commands: installation.commands,
|
|
719
|
+
scope: this.#config.commandScope
|
|
720
|
+
}, this.#apiBaseUrl());
|
|
721
|
+
} catch (err) {
|
|
722
|
+
console.warn(`[Telegram] Failed to register commands for agent "${installation.agentId}":`, err);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
#getOrCreateAdapter(installation) {
|
|
726
|
+
const existing = this.#adapters.get(installation.id);
|
|
727
|
+
if (existing) return existing;
|
|
728
|
+
const adapter = createTelegramAdapter$1({
|
|
729
|
+
botToken: installation.botToken ?? this.#config.tokenResolver,
|
|
730
|
+
secretToken: installation.secretToken,
|
|
731
|
+
userName: installation.username,
|
|
732
|
+
apiBaseUrl: this.#apiBaseUrl(),
|
|
733
|
+
mode: installation.webhookUrl ? "webhook" : this.#config.mode ?? "auto",
|
|
734
|
+
...this.#config.logger !== void 0 ? { logger: this.#config.logger } : {},
|
|
735
|
+
...this.#config.longPolling !== void 0 ? { longPolling: this.#config.longPolling } : {}
|
|
736
|
+
});
|
|
737
|
+
this.#adapters.set(installation.id, adapter);
|
|
738
|
+
return adapter;
|
|
739
|
+
}
|
|
740
|
+
/** Rebuild the adapter and inject AgentChannels for an active installation. */
|
|
741
|
+
async #activateInstallation(installation) {
|
|
742
|
+
const agent = this.#resolveAgent(installation.agentId);
|
|
743
|
+
const adapter = this.#getOrCreateAdapter(installation);
|
|
744
|
+
if (agent && this.#mastra) await this.#createAgentChannels(agent, adapter).initialize(this.#mastra);
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Create AgentChannels for an agent with the Telegram adapter, preserving any
|
|
748
|
+
* adapters/config the agent author already configured (mirrors `@mastra/slack`).
|
|
749
|
+
*/
|
|
750
|
+
#createAgentChannels(agent, adapter) {
|
|
751
|
+
const existingConfig = agent.getChannels()?.channelConfig;
|
|
752
|
+
const cfg = this.#config;
|
|
753
|
+
const entry = {
|
|
754
|
+
adapter,
|
|
755
|
+
...resolveTelegramAdapterConfig(cfg),
|
|
756
|
+
toolDisplay: cfg.toolDisplay ?? "text",
|
|
757
|
+
...cfg.cors !== void 0 ? { cors: cfg.cors } : {},
|
|
758
|
+
...cfg.formatError !== void 0 ? { formatError: cfg.formatError } : {}
|
|
759
|
+
};
|
|
760
|
+
const channels = new AgentChannels({
|
|
761
|
+
...existingConfig,
|
|
762
|
+
adapters: {
|
|
763
|
+
...existingConfig?.adapters,
|
|
764
|
+
[PLATFORM]: entry
|
|
765
|
+
},
|
|
766
|
+
userName: agent.name,
|
|
767
|
+
handlers: cfg.handlers ?? existingConfig?.handlers,
|
|
768
|
+
inlineMedia: cfg.inlineMedia ?? existingConfig?.inlineMedia,
|
|
769
|
+
inlineLinks: cfg.inlineLinks ?? existingConfig?.inlineLinks,
|
|
770
|
+
state: cfg.state ?? existingConfig?.state,
|
|
771
|
+
threadContext: cfg.threadContext ?? existingConfig?.threadContext,
|
|
772
|
+
chatOptions: cfg.chatOptions ?? existingConfig?.chatOptions,
|
|
773
|
+
tools: cfg.tools ?? existingConfig?.tools,
|
|
774
|
+
resolveResourceId: cfg.resolveResourceId ?? existingConfig?.resolveResourceId,
|
|
775
|
+
waitUntil: cfg.waitUntil ?? existingConfig?.waitUntil,
|
|
776
|
+
resolveWaitUntil: cfg.resolveWaitUntil ?? existingConfig?.resolveWaitUntil
|
|
777
|
+
});
|
|
778
|
+
agent.setChannels(channels);
|
|
779
|
+
return channels;
|
|
780
|
+
}
|
|
781
|
+
async #autoInitialize() {
|
|
782
|
+
if (!this.#mastra) return;
|
|
783
|
+
await this.initialize();
|
|
784
|
+
}
|
|
785
|
+
#resolveAgent(agentId) {
|
|
786
|
+
try {
|
|
787
|
+
return this.#mastra?.getAgentById(agentId);
|
|
788
|
+
} catch {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async #getStore() {
|
|
793
|
+
if (this.#store) return this.#store;
|
|
794
|
+
const encryptionKey = this.#config.encryptionKey ?? process.env.MASTRA_ENCRYPTION_KEY;
|
|
795
|
+
this.#store = new TelegramInstallStore(await this.#resolveStorage(), encryptionKey);
|
|
796
|
+
return this.#store;
|
|
797
|
+
}
|
|
798
|
+
async #resolveStorage() {
|
|
799
|
+
if (this.#config.storage) return this.#config.storage;
|
|
800
|
+
const mastraStore = this.#mastra?.getStorage();
|
|
801
|
+
if (mastraStore) try {
|
|
802
|
+
await mastraStore.init();
|
|
803
|
+
const channels = await mastraStore.getStore("channels");
|
|
804
|
+
if (channels) return channels;
|
|
805
|
+
} catch {}
|
|
806
|
+
return new InMemoryChannelsStorage();
|
|
807
|
+
}
|
|
808
|
+
#getBaseUrl() {
|
|
809
|
+
if (this.#config.baseUrl) return stripTrailingSlash(this.#config.baseUrl);
|
|
810
|
+
const server = this.#mastra?.getServer();
|
|
811
|
+
const hasPublicOverride = server?.studioHost != null || server?.studioPort != null || server?.studioProtocol != null;
|
|
812
|
+
const envUrl = process.env.MASTRA_SERVER_URL?.trim();
|
|
813
|
+
if (!hasPublicOverride && envUrl) return stripTrailingSlash(envUrl);
|
|
814
|
+
if (!server) return void 0;
|
|
815
|
+
const protocol = server.studioProtocol ?? "http";
|
|
816
|
+
const host = server.studioHost ?? server.host ?? "localhost";
|
|
817
|
+
const port = server.studioPort ?? server.port ?? (Number(process.env.PORT) || 4111);
|
|
818
|
+
return !(protocol === "https" && port === 443 || protocol === "http" && port === 80) ? `${protocol}://${host}:${port}` : `${protocol}://${host}`;
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
/** Constant-time comparison of the webhook secret header. */
|
|
822
|
+
function secretMatches(provided, expected) {
|
|
823
|
+
if (!provided || !expected) return false;
|
|
824
|
+
const a = Buffer.from(provided);
|
|
825
|
+
const b = Buffer.from(expected);
|
|
826
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
827
|
+
}
|
|
828
|
+
function stripTrailingSlash(url) {
|
|
829
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
830
|
+
}
|
|
831
|
+
//#endregion
|
|
832
|
+
export { BOTFATHER_DEEP_LINK, DEFAULT_ALLOWED_UPDATES, DEFAULT_COMMANDS, PLATFORM, TELEGRAM_API_BASE_URL, TelegramAdapter, TelegramInstallStore, TelegramProvider, createTelegramAdapter, deleteWebhook, generateSecretToken, getMe, normalizeCommands, resolveTelegramAdapterConfig, setMyCommands, setWebhook, toInstallationInfo };
|
|
833
|
+
|
|
834
|
+
//# sourceMappingURL=index.js.map
|