UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

857 lines (856 loc) • 41.4 kB
import { w as parseStrictPositiveInteger } from "./number-coercion-CuVhdCBs.mjs"; import { a as asOptionalRecord, c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { _ as normalizeUniqueTrimmedStringList } from "./string-normalization-CECOqf64.mjs"; import { r as normalizeOptionalAccountId } from "./account-id-DbRsdayc.mjs"; import { r as createRuntimeConfigReader } from "./runtime-snapshot-DYptB0Cf.mjs"; import { f as normalizeMessagePresentation, v as renderMessagePresentationFallbackText } from "./payload-DfZ7JVGs.mjs"; import { a as sendDurableMessageBatch } from "./channel-outbound-BaV16y8h.mjs"; import "./string-coerce-runtime-50qKeWeL.mjs"; import { f as resolveStorePath } from "./session-store-runtime-DSst5RB7.mjs"; import { t as jsonResult } from "./tool-results-BCM3fdVS.mjs"; import { d as readReactionParams, f as readStringArrayParam, m as readToolStringParam, p as readStringOrNumberParam, u as readPositiveIntegerParam } from "./common-Dyxcwsy8.mjs"; import { t as captureChannelReadAuthority } from "./channel-read-authority-CyorchNP.mjs"; import { n as normalizeOutboundLocation } from "./location-Dba72-LP.mjs"; import { t as buildOutboundSessionContext } from "./session-context-Kov7ff_A.mjs"; import "./fetch-runtime-DT4gd-qk.mjs"; import "./number-runtime-CgyXj1A5.mjs"; import "./account-core-BE1fOhGS.mjs"; import { t as resolveTelegramToken } from "./token-CVFbm7zQ.mjs"; import { r as resolvePollMaxSelections } from "./polls-C-v11_tu.mjs"; import { t as readBooleanParam } from "./boolean-param-Cb1071iT.mjs"; import { t as resolveReactionMessageId } from "./reaction-message-id-BgZjxV6k.mjs"; import "./channel-inbound-CFVoOzkD.mjs"; import "./runtime-config-snapshot-A-BTduWa.mjs"; import "./channel-actions-DF1kQl0D.mjs"; import { i as resolveDefaultTelegramAccountId, s as resolveTelegramPollActionGateState, t as createTelegramActionGate } from "./accounts-CRT4NMMs.mjs"; import { r as inspectTelegramAccount } from "./account-inspect-BoNOSe1D.mjs"; import { c as normalizeTelegramOutboundTarget, l as parseTelegramTarget, u as resolveTelegramTargetChatType } from "./topic-conversation-C-lzScEI.mjs"; import { r as resolveTelegramInlineButtonsScope } from "./inline-buttons-BbTysNhP.mjs"; import { t as rejectTelegramNativeButtonParams } from "./native-button-params-CIR-Zlcc.mjs"; import { t as resolveTelegramReactionLevel } from "./reaction-level-CiNaXKoO.mjs"; import { R as resolveTelegramAccountOwnerAgentId } from "./text-chunk-limit-DDOUtk0-.mjs"; import { i as resolveTelegramButtonsFromParams, n as buildTelegramControlDegradation, t as appendTelegramDroppedControlFallback } from "./button-types-B016oYPy.mjs"; import { o as resolveTelegramInteractiveTextFallback } from "./interactive-fallback-CG_59cc4.mjs"; import { i as getCacheStats, o as searchStickers } from "./sticker-cache-CMT589Zc.mjs"; import { $ as hasProviderObservedTelegramThreadBinding, C as editMessageReplyMarkupTelegram, D as editForumTopicTelegram, E as createForumTopicTelegram, I as reactMessageTelegram, M as updateTopicName, N as deleteMessageTelegram, P as getTelegramAllowedReactions, Q as createTelegramMessageCache, n as sendStickerTelegram, nt as resolveTelegramMessageCacheScope, t as sendPollTelegram, w as editMessageTelegram, z as TELEGRAM_SUPPORTED_REACTION_EMOJI_LIST } from "./send-wlidaQLJ.mjs"; import { t as resolveTelegramPollVisibility } from "./poll-visibility-Ds4ydsWS.mjs"; import { a as isTelegramHistorySenderAllowed, i as isTelegramHistoryNodeAllowed, o as readTelegramHistory, r as telegramInboundEventDelivery, t as renderTelegramAccountProgressDraftPreview } from "./progress-draft-preview-CArCYSRh.mjs"; //#region extensions/telegram/src/action-params.ts const TELEGRAM_FORUM_TOPIC_ICON_COLORS = [ 7322096, 16766590, 13338331, 9367192, 16749490, 16478047 ]; function readTelegramForumTopicIconColor(params) { const iconColor = readPositiveIntegerParam(params, "iconColor", { message: "iconColor must be one of Telegram's supported forum topic colors." }); if (iconColor == null) return; const supportedColor = TELEGRAM_FORUM_TOPIC_ICON_COLORS.find((color) => color === iconColor); if (supportedColor === void 0) throw new Error("iconColor must be one of Telegram's supported forum topic colors."); return supportedColor; } function readTelegramChatId(params) { return readStringOrNumberParam(params, "chatId") ?? readStringOrNumberParam(params, "channelId") ?? readStringOrNumberParam(params, "to", { required: true }); } function readTelegramThreadId(params) { return readPositiveIntegerParam(params, "messageThreadId", { message: "messageThreadId must be a positive integer." }) ?? readPositiveIntegerParam(params, "threadId", { message: "threadId must be a positive integer." }); } function readTelegramReplyToMessageId(params) { return readPositiveIntegerParam(params, "replyToMessageId", { message: "replyToMessageId must be a positive integer." }) ?? readPositiveIntegerParam(params, "replyTo", { message: "replyTo must be a positive integer." }); } function readTelegramSendMediaUrls(params) { const attachments = Array.isArray(params.attachments) ? params.attachments.filter(isRecord) : []; return normalizeUniqueTrimmedStringList([ params.mediaUrl, params.media, params.path, params.filePath, params.fileUrl, ...Array.isArray(params.mediaUrls) ? params.mediaUrls : [], ...attachments.flatMap((attachment) => [ attachment.media, attachment.mediaUrl, attachment.path, attachment.filePath, attachment.fileUrl, attachment.url ]) ]); } //#endregion //#region extensions/telegram/src/history-read.ts const HISTORY_SCOPE_ERROR = "Telegram history read requires the authenticated current group session, account, chat, and exact topic."; function resolveReadScope(params, context) { const toolContext = context?.toolContext; const accountId = normalizeOptionalAccountId(context?.requesterAccountId); const requestedAccount = normalizeOptionalAccountId(readStringOrNumberParam(params, "accountId")); const senderId = context?.requesterSenderId?.trim(); if (!context?.sessionKey?.trim() || !accountId || requestedAccount !== void 0 && requestedAccount !== accountId || !senderId || !/^\d+$/.test(senderId) || toolContext?.currentChannelProvider?.trim().toLowerCase() !== "telegram" || toolContext.currentChatType !== void 0 && toolContext.currentChatType !== "group") throw new Error(HISTORY_SCOPE_ERROR); const currentTargets = [toolContext.currentChannelId, toolContext.currentMessagingTarget].filter((value) => typeof value === "string" && Boolean(value.trim())).map(parseTelegramTarget); const chatId = currentTargets[0]?.chatId; const contextThread = parseStrictPositiveInteger(toolContext.currentThreadTs); if (toolContext.currentThreadTs != null && contextThread === void 0) throw new Error(HISTORY_SCOPE_ERROR); const threadIds = [...currentTargets.map((target) => target.messageThreadId), contextThread].filter((value) => value !== void 0); const threadId = threadIds[0]; if (!chatId || !/^-\d+$/.test(chatId) || currentTargets.some((target) => target.chatId !== chatId || target.directMessagesTopicId !== void 0) || threadIds.some((id) => id !== threadId || id <= 0)) throw new Error(HISTORY_SCOPE_ERROR); for (const key of [ "target", "to", "chatId", "channelId" ]) { const requested = readStringOrNumberParam(params, key); if (requested === void 0) continue; const target = parseTelegramTarget(requested); if (target.chatId !== chatId || target.directMessagesTopicId !== void 0 || target.messageThreadId !== void 0 && target.messageThreadId !== threadId) throw new Error(HISTORY_SCOPE_ERROR); } for (const key of ["threadId", "messageThreadId"]) { const requested = readPositiveIntegerParam(params, key); if (requested !== void 0 && requested !== threadId) throw new Error(HISTORY_SCOPE_ERROR); } return { accountId, chatId, threadId, senderId, sessionKey: context.sessionKey, currentMessageId: parseStrictPositiveInteger(toolContext.currentMessageId) }; } async function readTelegramHistoryAction(params, cfg, context) { const assertReadAuthority = captureChannelReadAuthority(); assertReadAuthority?.(); context?.assertDirectAdapterHandoff?.(); const scope = resolveReadScope(params, context); const getConfig = createRuntimeConfigReader(cfg); const currentConfig = getConfig(); const currentToolContext = { ...context?.toolContext }; const requesterAccountId = context?.requesterAccountId; const requesterSenderId = context?.requesterSenderId; const assertCurrent = () => { assertReadAuthority?.(); context?.assertDirectAdapterHandoff?.(); const current = context?.toolContext; if (getConfig() !== currentConfig || context?.requesterAccountId !== requesterAccountId || context?.requesterSenderId !== requesterSenderId || context?.sessionKey !== scope.sessionKey || current?.currentChannelProvider !== currentToolContext.currentChannelProvider || current?.currentChatType !== currentToolContext.currentChatType || current?.currentChannelId !== currentToolContext.currentChannelId || current?.currentMessagingTarget !== currentToolContext.currentMessagingTarget || current?.currentThreadTs !== currentToolContext.currentThreadTs || current?.currentMessageId !== currentToolContext.currentMessageId) throw new Error(HISTORY_SCOPE_ERROR); }; const account = inspectTelegramAccount({ cfg: currentConfig, accountId: scope.accountId }); if (!account.enabled || !account.configured || account.accountId !== scope.accountId) throw new Error("Telegram history is unavailable for the current account."); const policy = { cfg: currentConfig, accountId: scope.accountId, chatId: scope.chatId, threadId: scope.threadId, botUserId: parseStrictPositiveInteger(account.token.split(":", 1)[0]), assertCurrent }; if (!await isTelegramHistorySenderAllowed({ ...policy, senderId: scope.senderId })) throw new Error("Telegram history read is not permitted by the current group policy."); assertCurrent(); const cache = createTelegramMessageCache({ scope: resolveTelegramMessageCacheScope(resolveStorePath(currentConfig.session?.store, { agentId: resolveTelegramAccountOwnerAgentId({ cfg: currentConfig, accountId: scope.accountId }) })) }); const messageId = readPositiveIntegerParam(params, "messageId"); const before = readPositiveIntegerParam(params, "before"); const after = readPositiveIntegerParam(params, "after"); if (params.around != null) throw new Error("Telegram history supports messageId for an exact message, or before/after for paging."); if (messageId !== void 0 && (before !== void 0 || after !== void 0)) throw new Error("Use messageId alone for an exact Telegram history read."); const limit = Math.min(readPositiveIntegerParam(params, "limit") ?? 50, 100); assertCurrent(); let result; if (messageId !== void 0) { const node = await cache.get({ ...scope, messageId: String(messageId) }); assertCurrent(); const allowed = node && await isTelegramHistoryNodeAllowed({ ...policy, node }); assertCurrent(); result = { messages: node && allowed ? [node] : [], hasMore: false }; } else { const anchor = before ?? (after === void 0 ? scope.currentMessageId : void 0); result = await readTelegramHistory({ ...policy, cache, before: anchor === void 0 ? void 0 : String(anchor), after: after === void 0 ? void 0 : String(after), limit }); assertCurrent(); } if (!await isTelegramHistorySenderAllowed({ ...policy, senderId: scope.senderId })) throw new Error("Telegram history read is not permitted by the current group policy."); assertCurrent(); return jsonResult({ ok: true, chatId: scope.chatId, ...scope.threadId !== void 0 ? { threadId: scope.threadId } : {}, messages: result.messages.map((node) => ({ messageId: node.messageId, sender: node.sender, senderId: node.senderId, senderUsername: node.senderUsername, timestamp: node.timestamp, body: node.body, mediaType: node.mediaType, mediaRef: node.mediaRef, replyToId: node.replyToId })), hasMore: result.hasMore, oldestMessageId: result.messages[0]?.messageId, newestMessageId: result.messages.at(-1)?.messageId }); } //#endregion //#region extensions/telegram/src/message-topic-binding.ts const TOPIC_BINDING_ERROR = "Delegated Telegram message mutation requires a provider-observed binding to the exact current topic and account."; const CONVERSATION_BINDING_ERROR = "Delegated Telegram conversation read requires the exact current chat and account."; function rejectUnboundTopicMutation() { throw new Error(TOPIC_BINDING_ERROR); } function resolveCurrentTelegramConversation(toolContext, chatId) { if (toolContext?.currentChannelProvider?.trim().toLowerCase() !== "telegram") return { hasThreadContext: false, matchesChat: false }; const targets = [toolContext.currentChannelId, toolContext.currentMessagingTarget].filter((value) => typeof value === "string" && Boolean(value.trim())); const parsedTargets = targets.map((value) => parseTelegramTarget(value)); const threadIds = [...parsedTargets.map((target) => target.messageThreadId), parseStrictPositiveInteger(toolContext.currentThreadTs)].filter((value) => value !== void 0); const threadId = threadIds[0]; const matchesChat = targets.length > 0 && parsedTargets.every((target) => target.chatId === chatId) && (threadId === void 0 || threadIds.every((value) => value === threadId)); return { hasThreadContext: threadIds.length > 0, matchesChat, ...threadId !== void 0 ? { threadId } : {} }; } function resolveMatchingTelegramRequesterAccount(params) { const accountId = normalizeOptionalAccountId(params.accountId ?? resolveDefaultTelegramAccountId(params.cfg)); const requesterAccountId = normalizeOptionalAccountId(params.context?.requesterAccountId); return accountId && accountId === requesterAccountId ? accountId : void 0; } function resolveTelegramConversationReadChatId(params) { const currentTarget = params.context?.toolContext?.currentChannelId ?? params.context?.toolContext?.currentMessagingTarget; const requestedTarget = params.chatId ?? currentTarget; if (requestedTarget == null || !String(requestedTarget).trim()) throw new Error("Telegram emoji-list requires a chatId or current Telegram conversation."); const target = parseTelegramTarget(String(requestedTarget)); if (params.context?.conversationReadOrigin === "direct-operator") return target.chatId; const currentConversation = resolveCurrentTelegramConversation(params.context?.toolContext, target.chatId); if (!resolveMatchingTelegramRequesterAccount(params) || !currentConversation.matchesChat || target.messageThreadId !== void 0 && target.messageThreadId !== currentConversation.threadId) throw new Error(CONVERSATION_BINDING_ERROR); return target.chatId; } async function resolveTelegramMessageMutationChatId(params) { const target = parseTelegramTarget(String(params.chatId)); if (params.context?.conversationReadOrigin === "direct-operator") return target.messageThreadId === void 0 ? params.chatId : target.chatId; const currentConversation = resolveCurrentTelegramConversation(params.context?.toolContext, target.chatId); const selectedAccountId = resolveMatchingTelegramRequesterAccount(params); if (!selectedAccountId || !currentConversation.matchesChat) return rejectUnboundTopicMutation(); const threadId = target.messageThreadId ?? currentConversation.threadId; if (threadId === void 0 && !currentConversation.hasThreadContext) return target.chatId; if (threadId === void 0 || currentConversation.threadId !== threadId) return rejectUnboundTopicMutation(); if (parseStrictPositiveInteger(params.context?.toolContext?.currentMessageId) === params.messageId) return target.chatId; const cached = await createTelegramMessageCache({ scope: resolveTelegramMessageCacheScope(resolveStorePath(params.cfg.session?.store, { agentId: resolveTelegramAccountOwnerAgentId({ cfg: params.cfg, accountId: selectedAccountId }) })) }).get({ accountId: selectedAccountId, chatId: target.chatId, messageId: String(params.messageId) }); if (!hasProviderObservedTelegramThreadBinding(cached, threadId)) return rejectUnboundTopicMutation(); return target.chatId; } //#endregion //#region extensions/telegram/src/action-runtime.ts const TELEGRAM_EMOJI_LIST_LIMIT = 100; const TELEGRAM_REACTION_HINT_LIMIT = 20; const TELEGRAM_ACTION_ALIASES = { createForumTopic: "createForumTopic", delete: "deleteMessage", deleteMessage: "deleteMessage", edit: "editMessage", editForumTopic: "editForumTopic", editMessage: "editMessage", "emoji-list": "emoji-list", poll: "poll", react: "react", read: "read", searchSticker: "searchSticker", send: "sendMessage", sendMessage: "sendMessage", sendSticker: "sendSticker", sticker: "sendSticker", stickerCacheStats: "stickerCacheStats", "sticker-search": "searchSticker", "topic-create": "createForumTopic", "topic-edit": "editForumTopic" }; function normalizeTelegramActionName(action) { const normalized = TELEGRAM_ACTION_ALIASES[action]; if (!normalized) throw new Error(`Unsupported Telegram action: ${action}`); return normalized; } function resolveActionTopicNameCacheScope(cfg, accountId) { const resolvedAccountId = accountId ?? resolveDefaultTelegramAccountId(cfg); return resolveStorePath(cfg.session?.store, { agentId: resolveTelegramAccountOwnerAgentId({ cfg, accountId: resolvedAccountId }) }); } function formatTelegramDeliveryTarget(to, messageThreadId) { const parsed = parseTelegramTarget(to); const directTopicId = parsed.directMessagesTopicId; if (directTopicId != null) return `${parsed.chatId}:direct-topic:${directTopicId}`; const topicId = messageThreadId ?? parsed.messageThreadId; if (topicId == null) return to; return `${parsed.chatId}:topic:${topicId}`; } function readTelegramSendContent(params) { const explicitContent = readToolStringParam(params.args, "content", { allowEmpty: true }) ?? readToolStringParam(params.args, "message", { allowEmpty: true }) ?? readToolStringParam(params.args, "caption", { allowEmpty: true }); const unsupportedBlocks = params.presentation?.blocks.filter((block) => block.type === "chart" || block.type === "table") ?? []; const presentationText = explicitContent == null && params.presentation ? renderMessagePresentationFallbackText({ presentation: params.presentation }) : explicitContent != null && unsupportedBlocks.length > 0 ? renderMessagePresentationFallbackText({ text: explicitContent, presentation: { ...params.presentation, blocks: unsupportedBlocks } }) : void 0; const interactiveText = explicitContent == null && !params.presentation ? resolveTelegramInteractiveTextFallback({ interactive: params.interactive }) : void 0; const content = (presentationText?.trim() ? presentationText : void 0) ?? explicitContent ?? (interactiveText?.trim() ? interactiveText : void 0); if (content == null && !params.mediaUrl && !params.hasButtons && !params.hasLocation) throw new Error("content required."); return { content: content ?? "", hasExplicitContent: explicitContent != null }; } function normalizeTelegramDeliveryPin(params) { const delivery = asOptionalRecord(params.delivery); const pin = delivery ? delivery.pin : params.pin === true ? true : void 0; if (pin === true) return { enabled: true }; const raw = asOptionalRecord(pin); if (raw?.enabled !== true) return; return { enabled: true, ...raw.notify === true ? { notify: true } : {}, ...raw.required === true ? { required: true } : {} }; } async function describeTelegramAllowedReactionSample(params) { const reactions = await getTelegramAllowedReactions(params.chatId, { cfg: params.cfg, token: params.token, accountId: params.accountId }).catch(() => void 0); if (reactions === void 0) return ""; const allowed = reactions ?? TELEGRAM_SUPPORTED_REACTION_EMOJI_LIST.map((emoji) => ({ type: "emoji", emoji })); const emojis = allowed.filter((reaction) => reaction.type === "emoji").slice(0, TELEGRAM_REACTION_HINT_LIMIT).map((reaction) => reaction.emoji); const customIds = allowed.filter((reaction) => reaction.type === "custom_emoji").slice(0, TELEGRAM_REACTION_HINT_LIMIT - emojis.length).map((reaction) => reaction.custom_emoji_id); const customSample = customIds.length ? `numeric custom IDs ${customIds.join(", ")}` : ""; const sample = [emojis.join(" "), customSample].filter(Boolean).join("; "); return sample ? ` This chat allows: ${sample}.` : ""; } async function handleTelegramAction(params, cfg, options) { rejectTelegramNativeButtonParams(params); const { action, accountId } = { action: normalizeTelegramActionName(readToolStringParam(params, "action", { required: true })), accountId: readToolStringParam(params, "accountId") }; const isActionEnabled = createTelegramActionGate({ cfg, accountId }); const apiOptions = { cfg, accountId, gatewayClientScopes: options?.gatewayClientScopes }; const requireToken = () => { const token = resolveTelegramToken(cfg, { accountId }).token; if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken."); return token; }; const notifyVisibleOutboundSuccess = (to, messageThreadId) => { telegramInboundEventDelivery.notify({ sessionKey: options?.sessionKey ?? void 0, to: formatTelegramDeliveryTarget(to, messageThreadId), accountId, inboundEventKind: options?.inboundEventKind }); }; if (action === "read") return await readTelegramHistoryAction(params, cfg, options); if (action === "emoji-list") { if (!isActionEnabled("reactions")) throw new Error("Telegram reactions are disabled via actions.reactions."); const chatId = resolveTelegramConversationReadChatId({ chatId: readStringOrNumberParam(params, "chatId") ?? readStringOrNumberParam(params, "channelId") ?? readStringOrNumberParam(params, "to"), cfg, accountId, context: options }); const token = requireToken(); const limit = Math.min(readPositiveIntegerParam(params, "limit", { message: "limit must be a positive integer." }) ?? TELEGRAM_EMOJI_LIST_LIMIT, TELEGRAM_EMOJI_LIST_LIMIT); const allowed = await getTelegramAllowedReactions(chatId, { cfg, token, accountId: accountId ?? void 0 }); const reactions = allowed ?? TELEGRAM_SUPPORTED_REACTION_EMOJI_LIST.map((emoji) => ({ type: "emoji", emoji })); return jsonResult({ ok: true, emojis: reactions.slice(0, limit).map((reaction) => reaction.type === "emoji" ? { name: reaction.emoji, identifier: reaction.emoji } : { identifier: reaction.custom_emoji_id, type: "custom_emoji" }), ...allowed === null ? { note: "All standard Telegram reactions are allowed." } : {} }); } if (action === "react") { const reactionLevelInfo = resolveTelegramReactionLevel({ cfg, accountId: accountId ?? void 0 }); if (!reactionLevelInfo.agentReactionsEnabled) return jsonResult({ ok: false, reason: "disabled", hint: `Telegram agent reactions disabled (reactionLevel="${reactionLevelInfo.level}"). Do not retry.` }); if (!isActionEnabled("reactions")) return jsonResult({ ok: false, reason: "disabled", hint: "Telegram reactions are disabled via actions.reactions. Do not retry." }); const chatId = readTelegramChatId(params); let explicitMessageId; try { explicitMessageId = readPositiveIntegerParam(params, "messageId", { message: "messageId must be a positive integer." }); } catch { return jsonResult({ ok: false, reason: "missing_message_id", hint: "Telegram reaction requires a valid messageId (or inbound context fallback). Do not retry." }); } const messageId = explicitMessageId ?? resolveReactionMessageId({ args: params }); if (typeof messageId !== "number" || !Number.isFinite(messageId) || messageId <= 0) return jsonResult({ ok: false, reason: "missing_message_id", hint: "Telegram reaction requires a valid messageId (or inbound context fallback). Do not retry." }); const { emoji, remove, isEmpty } = readReactionParams(params, { removeErrorMessage: "Emoji is required to remove a Telegram reaction." }); const token = resolveTelegramToken(cfg, { accountId }).token; if (!token) return jsonResult({ ok: false, reason: "missing_token", hint: "Telegram bot token missing. Do not retry." }); let reactionResult; let authorizedChatId = chatId ?? ""; try { authorizedChatId = await resolveTelegramMessageMutationChatId({ chatId: chatId ?? "", messageId, cfg, accountId, context: options }); reactionResult = await reactMessageTelegram(authorizedChatId, messageId, emoji ?? "", { ...apiOptions, token, remove }); } catch (err) { const isInvalid = String(err).includes("REACTION_INVALID"); return jsonResult({ ok: false, reason: isInvalid ? "REACTION_INVALID" : "error", emoji, hint: isInvalid ? `This reaction is unavailable.${await describeTelegramAllowedReactionSample({ chatId: authorizedChatId, cfg, token, accountId: accountId ?? void 0 })}` : "Reaction failed. Do not retry." }); } if (!reactionResult.ok) { const allowedHint = await describeTelegramAllowedReactionSample({ chatId: authorizedChatId, cfg, token, accountId: accountId ?? void 0 }); return jsonResult({ ok: false, warning: `${reactionResult.warning}${allowedHint}`, ...remove || isEmpty ? { removed: true } : { added: emoji } }); } if (!remove && !isEmpty) return jsonResult({ ok: true, added: emoji }); return jsonResult({ ok: true, removed: true }); } if (action === "sendMessage") { if (!isActionEnabled("sendMessage")) throw new Error("Telegram sendMessage is disabled."); const to = normalizeTelegramOutboundTarget(readToolStringParam(params, "to", { required: true })); const mediaUrls = readTelegramSendMediaUrls(params); const firstMediaUrl = mediaUrls[0]; const location = normalizeOutboundLocation(params.location); const presentation = normalizeMessagePresentation(params.presentation); const droppedControls = []; const buttons = resolveTelegramButtonsFromParams(params, presentation, { allowWebAppButtons: resolveTelegramTargetChatType(to) === "direct", onDroppedControl: (control) => droppedControls.push(control) }); const resolvedContent = readTelegramSendContent({ args: params, mediaUrl: firstMediaUrl, hasButtons: Array.isArray(buttons) && buttons.length > 0, hasLocation: Boolean(location), interactive: params.interactive, presentation }); const content = droppedControls.length > 0 && resolvedContent.hasExplicitContent ? appendTelegramDroppedControlFallback(resolvedContent.content, droppedControls) : resolvedContent.content; const droppedControlFallback = appendTelegramDroppedControlFallback("", droppedControls); const hasOnlyDroppedControlFallback = !resolvedContent.hasExplicitContent && droppedControlFallback.length > 0 && content.trim() === droppedControlFallback.trim(); const asVideoNote = readBooleanParam(params, "asVideoNote") ?? false; if (location && (content.trim() && !hasOnlyDroppedControlFallback || mediaUrls.length > 0 || asVideoNote)) throw new Error("Telegram location sends cannot be combined with message text or media."); if (asVideoNote && mediaUrls.length !== 1) throw new Error("Telegram video notes require exactly one media attachment."); if (buttons) { const inlineButtonsScope = resolveTelegramInlineButtonsScope({ cfg, accountId: accountId ?? void 0 }); if (inlineButtonsScope === "off") throw new Error("Telegram inline buttons are disabled. Set channels.telegram.capabilities.inlineButtons to \"dm\", \"group\", \"all\", or \"allowlist\"."); if (inlineButtonsScope === "dm" || inlineButtonsScope === "group") { const targetType = resolveTelegramTargetChatType(to); if (targetType === "unknown") throw new Error(`Telegram inline buttons require a numeric chat id when inlineButtons="${inlineButtonsScope}".`); if (inlineButtonsScope === "dm" && targetType !== "direct") throw new Error("Telegram inline buttons are limited to DMs when inlineButtons=\"dm\"."); if (inlineButtonsScope === "group" && targetType !== "group") throw new Error("Telegram inline buttons are limited to groups when inlineButtons=\"group\"."); } } const replyToMessageId = readTelegramReplyToMessageId(params); const messageThreadId = readTelegramThreadId(params); const quoteText = readToolStringParam(params, "quoteText", { trim: false }); requireToken(); const asVoice = readBooleanParam(params, "asVoice"); const silent = readBooleanParam(params, "silent"); const forceDocument = readBooleanParam(params, "forceDocument") ?? readBooleanParam(params, "asDocument") ?? false; const pin = normalizeTelegramDeliveryPin(params); const telegramData = buttons || quoteText ? { ...buttons ? { buttons } : {}, ...quoteText ? { quoteText } : {} } : void 0; const payload = { text: content, ...mediaUrls.length > 0 ? { mediaUrls } : {}, ...asVoice === true ? { audioAsVoice: true } : {}, ...asVideoNote ? { videoAsNote: true } : {}, ...location ? { location } : {}, ...pin ? { delivery: { pin } } : {}, ...telegramData ? { channelData: { telegram: telegramData } } : {} }; const mediaAccess = options?.mediaAccess ?? (options?.mediaLocalRoots || options?.mediaReadFile ? { ...options.mediaLocalRoots ? { localRoots: options.mediaLocalRoots } : {}, ...options.mediaReadFile ? { readFile: options.mediaReadFile } : {} } : void 0); const outboundSession = buildOutboundSessionContext({ cfg, sessionKey: options?.sessionKey, requesterAccountId: accountId }); const durableResult = await sendDurableMessageBatch({ cfg, channel: "telegram", to, accountId: accountId ?? void 0, payloads: [payload], ...options?.reply ? { reply: options.reply } : { replyToId: replyToMessageId == null ? void 0 : String(replyToMessageId) }, threadId: messageThreadId, forceDocument, silent, durability: "required", gatewayClientScopes: options?.gatewayClientScopes, deliveryRetryOwner: options?.deliveryRetryOwner, onPlatformSendDispatch: options?.onPlatformSendDispatch, assertDirectAdapterHandoff: options?.assertDirectAdapterHandoff, skipQueue: options?.skipQueue, ...mediaAccess ? { mediaAccess } : {}, ...outboundSession ? { session: outboundSession } : {} }); if (durableResult.status === "failed" || durableResult.status === "partial_failed") throw durableResult.error; if (durableResult.status === "suppressed") { if (durableResult.reason === "adapter_returned_no_identity" || durableResult.payloadOutcomes?.some((outcome) => outcome.status === "failed" ? outcome.sentBeforeError : outcome.status === "sent" || outcome.reason === "adapter_returned_no_identity")) throw new Error("Telegram sendMessage was suppressed before delivery."); return jsonResult({ status: "suppressed", reason: durableResult.reason }); } const result = durableResult.results.at(-1); const receipt = durableResult.receipt; notifyVisibleOutboundSuccess(to, messageThreadId); return jsonResult({ ok: true, messageId: result?.messageId ?? receipt.primaryPlatformMessageId ?? receipt.platformMessageIds.at(-1), chatId: result?.target?.kind === "chat" ? result.target.id : void 0, receipt: { threadId: receipt.threadId, replyToId: receipt.replyToId }, ...buildTelegramControlDegradation(droppedControls, Boolean(content.trim())) }); } if (action === "poll") { const pollActionState = resolveTelegramPollActionGateState(isActionEnabled); if (!pollActionState.sendMessageEnabled) throw new Error("Telegram sendMessage is disabled."); if (!pollActionState.pollEnabled) throw new Error("Telegram polls are disabled."); const to = readToolStringParam(params, "to", { required: true }); const question = readToolStringParam(params, "question") ?? readToolStringParam(params, "pollQuestion", { required: true }); const answers = readStringArrayParam(params, "answers") ?? readStringArrayParam(params, "pollOption", { required: true }); const allowMultiselect = readBooleanParam(params, "allowMultiselect") ?? readBooleanParam(params, "pollMulti"); const durationSeconds = readPositiveIntegerParam(params, "durationSeconds", { message: "durationSeconds must be a positive integer." }) ?? readPositiveIntegerParam(params, "pollDurationSeconds", { message: "pollDurationSeconds must be a positive integer." }); const durationHours = readPositiveIntegerParam(params, "durationHours", { message: "durationHours must be a positive integer." }) ?? readPositiveIntegerParam(params, "pollDurationHours", { message: "pollDurationHours must be a positive integer." }); const replyToMessageId = readTelegramReplyToMessageId(params); const messageThreadId = readTelegramThreadId(params); const isAnonymous = readBooleanParam(params, "isAnonymous") ?? resolveTelegramPollVisibility({ pollAnonymous: readBooleanParam(params, "pollAnonymous"), pollPublic: readBooleanParam(params, "pollPublic") }); const silent = readBooleanParam(params, "silent"); const token = requireToken(); const result = await sendPollTelegram(to, { question, options: answers, maxSelections: resolvePollMaxSelections(answers.length, allowMultiselect ?? false), durationSeconds: durationSeconds ?? void 0, durationHours: durationHours ?? void 0 }, { ...apiOptions, token, replyToMessageId: replyToMessageId ?? void 0, messageThreadId: messageThreadId ?? void 0, isAnonymous: isAnonymous ?? void 0, silent: silent ?? void 0 }); notifyVisibleOutboundSuccess(to, messageThreadId); return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId, pollId: result.pollId, ...result.pollAnswerRouting ? { pollAnswerRouting: result.pollAnswerRouting } : {}, ...result.warning ? { warning: result.warning } : {} }); } if (action === "deleteMessage" || action === "editMessage") { if (!isActionEnabled(action)) throw new Error(`Telegram ${action} is disabled.`); const chatId = readTelegramChatId(params); const messageId = readPositiveIntegerParam(params, "messageId", { message: "messageId must be a positive integer." }); if (messageId === void 0) throw new Error("messageId required"); const authorizedChatId = await resolveTelegramMessageMutationChatId({ chatId: chatId ?? "", messageId, cfg, accountId, context: options }); if (action === "deleteMessage") { const result = await deleteMessageTelegram(authorizedChatId, messageId, { ...apiOptions, assertPlatformSendAuthorized: options?.assertDirectAdapterHandoff }); if (!result.ok) return jsonResult({ ok: false, deleted: false, warning: result.warning }); return jsonResult({ ok: true, deleted: true }); } let content = readToolStringParam(params, "content", { allowEmpty: false }) ?? readToolStringParam(params, "message", { allowEmpty: false }); let caption = readToolStringParam(params, "caption", { allowEmpty: true }); let progressPreview; if (options?.progressSnapshot) { progressPreview = renderTelegramAccountProgressDraftPreview(options.progressSnapshot, { cfg, accountId }); content = progressPreview.text; } const droppedControls = []; const buttons = resolveTelegramButtonsFromParams(params, void 0, { allowWebAppButtons: resolveTelegramTargetChatType(chatId ?? "") === "direct", onDroppedControl: (control) => droppedControls.push(control) }); if (droppedControls.length > 0) { if (caption != null) caption = appendTelegramDroppedControlFallback(caption, droppedControls); else if (content != null) content = appendTelegramDroppedControlFallback(content, droppedControls); } if (content == null && caption == null && buttons === void 0) { const degradation = buildTelegramControlDegradation(droppedControls, false); if (degradation) return jsonResult({ ok: false, ...degradation }); throw new Error("content required."); } if (buttons !== void 0) { if (resolveTelegramInlineButtonsScope({ cfg, accountId: accountId ?? void 0 }) === "off") throw new Error("Telegram inline buttons are disabled. Set channels.telegram.capabilities.inlineButtons to \"dm\", \"group\", \"all\", or \"allowlist\"."); } const token = requireToken(); if (content == null && caption == null && buttons !== void 0) { const result = await editMessageReplyMarkupTelegram(authorizedChatId, messageId, buttons, { ...apiOptions, token, assertPlatformSendAuthorized: options?.assertDirectAdapterHandoff }); return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId, ...buildTelegramControlDegradation(droppedControls, false) }); } const result = await editMessageTelegram(authorizedChatId, messageId, progressPreview?.parseMode === "HTML" ? progressPreview.text.replaceAll("<br>", "\n") : progressPreview?.text ?? caption ?? content ?? "", { ...apiOptions, token, buttons, editMode: progressPreview ? "text" : caption != null ? "caption" : "auto", ...progressPreview ? { textMode: progressPreview.parseMode === "HTML" ? "html" : "markdown", richMessage: progressPreview.richMessage } : {}, assertPlatformSendAuthorized: options?.assertDirectAdapterHandoff }); return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId, ...buildTelegramControlDegradation(droppedControls, true) }); } if (action === "sendSticker") { if (!isActionEnabled("sticker", false)) throw new Error("Telegram sticker actions are disabled. Set channels.telegram.actions.sticker to true."); const to = readToolStringParam(params, "to") ?? readToolStringParam(params, "target", { required: true }); const fileId = readToolStringParam(params, "fileId") ?? readStringArrayParam(params, "stickerId")?.[0]; if (!fileId) throw new Error("fileId is required."); const replyToMessageId = readTelegramReplyToMessageId(params); const messageThreadId = readTelegramThreadId(params); const token = requireToken(); const result = await sendStickerTelegram(to, fileId, { ...apiOptions, token, replyToMessageId: replyToMessageId ?? void 0, messageThreadId: messageThreadId ?? void 0 }); notifyVisibleOutboundSuccess(to, messageThreadId); return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId }); } if (action === "searchSticker") { if (!isActionEnabled("sticker", false)) throw new Error("Telegram sticker actions are disabled. Set channels.telegram.actions.sticker to true."); const query = readToolStringParam(params, "query", { required: true }); const limit = readPositiveIntegerParam(params, "limit", { message: "limit must be a positive integer." }) ?? 5; const results = await searchStickers(query, limit); return jsonResult({ ok: true, count: results.length, stickers: results.map((s) => ({ fileId: s.fileId, emoji: s.emoji, description: s.description, setName: s.setName })) }); } if (action === "stickerCacheStats") { const stats = await getCacheStats(); return jsonResult({ ok: true, ...stats }); } if (action === "createForumTopic") { if (!isActionEnabled("createForumTopic")) throw new Error("Telegram createForumTopic is disabled."); const chatId = readTelegramChatId(params); const name = readToolStringParam(params, "name") ?? readToolStringParam(params, "threadName", { required: true, label: "name" }); const iconColor = readTelegramForumTopicIconColor(params); const iconCustomEmojiId = readToolStringParam(params, "iconCustomEmojiId"); const token = requireToken(); const result = await createForumTopicTelegram(chatId ?? "", name, { ...apiOptions, token, iconColor, iconCustomEmojiId: iconCustomEmojiId ?? void 0 }); return jsonResult({ ok: true, topicId: result.topicId, name: result.name, chatId: result.chatId }); } if (action === "editForumTopic") { if (!isActionEnabled("editForumTopic")) throw new Error("Telegram editForumTopic is disabled."); const chatId = readTelegramChatId(params); const messageThreadId = readTelegramThreadId(params); if (typeof messageThreadId !== "number") throw new Error("messageThreadId or threadId is required."); const name = readToolStringParam(params, "name") ?? readToolStringParam(params, "threadName"); const iconCustomEmojiId = readToolStringParam(params, "iconCustomEmojiId"); const token = requireToken(); const result = await editForumTopicTelegram(chatId ?? "", messageThreadId, { ...apiOptions, token, name: name ?? void 0, iconCustomEmojiId: iconCustomEmojiId ?? void 0 }); if (result.chatId) { const patch = {}; if (name) patch.name = name; if (iconCustomEmojiId) patch.iconCustomEmojiId = iconCustomEmojiId; if (Object.keys(patch).length > 0) await updateTopicName(result.chatId, result.messageThreadId, patch, resolveActionTopicNameCacheScope(cfg, accountId)).catch(() => {}); } return jsonResult(result); } throw new Error(`Unsupported Telegram action: ${String(action)}`); } //#endregion export { handleTelegramAction };