openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
795 lines (794 loc) • 38.8 kB
JavaScript
import { w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js";
import { n as normalizeAccountId, r as normalizeOptionalAccountId } from "./account-id-CETVCrTz.js";
import { r as sendDurableMessageBatch } from "./channel-outbound-Dw9bfhFK.js";
import { t as jsonResult } from "./tool-results-BCM3fdVS.js";
import { d as readPositiveIntegerParam, f as readReactionParams, h as readToolStringParam, m as readStringOrNumberParam, p as readStringArrayParam } from "./common-Bm6UTDDA.js";
import { f as normalizeMessagePresentation, v as renderMessagePresentationFallbackText } from "./payload-DYE7bQ2k.js";
import { E as normalizeOutboundLocation } from "./reply-payload-Bfsi665c.js";
import { t as buildOutboundSessionContext } from "./session-context-CpdGMrv6.js";
import { t as readBooleanParam } from "./boolean-param-Djx2w6Yv.js";
import { r as resolvePollMaxSelections } from "./polls-C-v11_tu.js";
import "./number-runtime-Cy4drVnh.js";
import "./account-core-DA6N6mo1.js";
import { t as resolveTelegramToken } from "./token-B1OJKBW2.js";
import { h as resolveStorePath } from "./session-store-runtime-Btld46Zi.js";
import "./channel-inbound-DreWTphY.js";
import { t as resolveReactionMessageId } from "./channel-actions-CrPVkBaw.js";
import { a as resolveDefaultTelegramAccountId, c as resolveTelegramPollActionGateState, t as createTelegramActionGate } from "./accounts-CgIQ5mFD.js";
import { c as resolveTelegramTargetChatType, o as normalizeTelegramOutboundTarget, s as parseTelegramTarget } from "./topic-conversation-Cl4csGES.js";
import { r as resolveTelegramInlineButtonsScope } from "./inline-buttons-BTfJdkW_.js";
import { t as rejectTelegramNativeButtonParams } from "./native-button-params-CIR-Zlcc.js";
import { t as resolveTelegramAccountOwnerAgentId } from "./account-owner-7zwE7Wu9.js";
import { r as resolveTelegramInlineButtons, t as appendTelegramDroppedControlFallback } from "./button-types-BNe7Hg2C.js";
import { n as resolveTelegramInteractiveTextFallback } from "./interactive-fallback-DOi9HmOl.js";
import { t as resolveTelegramReactionLevel } from "./reaction-level-DY32Tfrq.js";
import { i as getCacheStats, o as searchStickers } from "./sticker-cache-B79pDLqm.js";
import { p as resolveTelegramMessageCacheScope } from "./sent-message-cache.legacy-state-RwABm5_I.js";
import { o as resolveTopicNameCacheScope, s as updateTopicName } from "./topic-name-cache-0bursfP6.js";
import { C as editForumTopicTelegram, D as pinMessageTelegram, E as getTelegramAllowedReactions, G as hasProviderObservedTelegramThreadBinding, O as reactMessageTelegram, S as createForumTopicTelegram, T as deleteMessageTelegram, W as createTelegramMessageCache, b as editMessageTelegram, j as TELEGRAM_SUPPORTED_REACTION_EMOJI_LIST, n as sendStickerTelegram, t as sendPollTelegram, u as sendMessageTelegram, y as editMessageReplyMarkupTelegram } from "./send-BXHKghr-.js";
import { t as resolveTelegramPollVisibility } from "./poll-visibility-Ds4ydsWS.js";
import { t as telegramInboundEventDelivery } from "./inbound-event-delivery-BCLyd7Z6.js";
//#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 && requesterAccountId && normalizeAccountId(accountId) === normalizeAccountId(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 telegramActionRuntime = {
createForumTopicTelegram,
deleteMessageTelegram,
editForumTopicTelegram,
editMessageReplyMarkupTelegram,
editMessageTelegram,
getTelegramAllowedReactions,
getCacheStats,
pinMessageTelegram,
reactMessageTelegram,
searchStickers,
sendDurableMessageBatch,
sendMessageTelegram,
sendPollTelegram,
sendStickerTelegram
};
const TELEGRAM_FORUM_TOPIC_ICON_COLORS = [
7322096,
16766590,
13338331,
9367192,
16749490,
16478047
];
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",
searchSticker: "searchSticker",
send: "sendMessage",
sendMessage: "sendMessage",
sendSticker: "sendSticker",
sticker: "sendSticker",
stickerCacheStats: "stickerCacheStats",
"sticker-search": "searchSticker",
"topic-create": "createForumTopic",
"topic-edit": "editForumTopic"
};
function readTelegramForumTopicIconColor(params) {
const iconColor = readPositiveIntegerParam(params, "iconColor", { message: "iconColor must be one of Telegram's supported forum topic colors." });
if (iconColor == null) return;
if (!TELEGRAM_FORUM_TOPIC_ICON_COLORS.includes(iconColor)) throw new Error("iconColor must be one of Telegram's supported forum topic colors.");
return iconColor;
}
function normalizeTelegramActionName(action) {
const normalized = TELEGRAM_ACTION_ALIASES[action];
if (!normalized) throw new Error(`Unsupported Telegram action: ${action}`);
return normalized;
}
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 resolveActionTopicNameCacheScope(cfg, accountId) {
const resolvedAccountId = accountId ?? resolveDefaultTelegramAccountId(cfg);
const storePath = resolveStorePath(cfg.session?.store, { agentId: resolveTelegramAccountOwnerAgentId({
cfg,
accountId: resolvedAccountId
}) });
return resolveTopicNameCacheScope(storePath);
}
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 readTelegramReplyToMessageId(params) {
return readPositiveIntegerParam(params, "replyToMessageId", { message: "replyToMessageId must be a positive integer." }) ?? readPositiveIntegerParam(params, "replyTo", { message: "replyTo must be a positive integer." });
}
function pushTelegramMediaUrl(mediaUrls, seen, value) {
if (typeof value !== "string") return;
const normalized = value.trim();
if (!normalized || seen.has(normalized)) return;
seen.add(normalized);
mediaUrls.push(normalized);
}
function readTelegramSendMediaUrls(params) {
const mediaUrls = [];
const seen = /* @__PURE__ */ new Set();
pushTelegramMediaUrl(mediaUrls, seen, params.mediaUrl);
pushTelegramMediaUrl(mediaUrls, seen, params.media);
pushTelegramMediaUrl(mediaUrls, seen, params.path);
pushTelegramMediaUrl(mediaUrls, seen, params.filePath);
pushTelegramMediaUrl(mediaUrls, seen, params.fileUrl);
if (Array.isArray(params.mediaUrls)) for (const mediaUrl of params.mediaUrls) pushTelegramMediaUrl(mediaUrls, seen, mediaUrl);
if (Array.isArray(params.attachments)) for (const attachment of params.attachments) {
if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) continue;
const record = attachment;
pushTelegramMediaUrl(mediaUrls, seen, record.media);
pushTelegramMediaUrl(mediaUrls, seen, record.mediaUrl);
pushTelegramMediaUrl(mediaUrls, seen, record.path);
pushTelegramMediaUrl(mediaUrls, seen, record.filePath);
pushTelegramMediaUrl(mediaUrls, seen, record.fileUrl);
pushTelegramMediaUrl(mediaUrls, seen, record.url);
}
return mediaUrls;
}
function resolveTelegramButtonsFromParams(params, presentation = normalizeMessagePresentation(params.presentation), options) {
return resolveTelegramInlineButtons({
presentation,
interactive: params.interactive
}, options);
}
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;
let content = (presentationText?.trim() ? presentationText : void 0) ?? explicitContent ?? (interactiveText?.trim() ? interactiveText : void 0);
if ((content == null || content.trim().length === 0) && !params.mediaUrl && params.hasButtons) {
const fallback = presentationText?.trim() ? presentationText : interactiveText;
if (fallback?.trim()) content = fallback;
}
if (content == null && !params.mediaUrl && !params.hasButtons && !params.hasLocation) throw new Error("content required.");
return {
content: content ?? "",
hasExplicitContent: explicitContent != null
};
}
function buildTelegramControlDegradation(controls, fallbackDelivered) {
if (controls.length === 0) return;
const reasons = [...new Set(controls.map((control) => control.reason))];
const hasOverflow = reasons.includes("callback_data_too_long");
return {
warning: fallbackDelivered ? `Telegram delivered ${controls.length} unencodable control${controls.length === 1 ? "" : "s"} as readable text.` : `Telegram could not deliver ${controls.length} control${controls.length === 1 ? "" : "s"}.`,
degradedDelivery: {
droppedControls: controls.length,
fallback: fallbackDelivered ? "text" : "not_delivered",
reasons,
...hasOverflow ? { callbackDataLimitBytes: 64 } : {},
guidance: hasOverflow ? `Shorten callback data to at most 64 UTF-8 bytes and retry if clickable controls are required.` : "Retry with a supported control action if clickable controls are required."
}
};
}
function normalizeTelegramDeliveryPin(params) {
const delivery = params.delivery;
const pin = delivery && typeof delivery === "object" && !Array.isArray(delivery) ? delivery.pin : params.pin === true ? true : void 0;
if (pin === true) return { enabled: true };
if (!pin || typeof pin !== "object" || Array.isArray(pin)) return;
const raw = pin;
if (raw.enabled !== true) return;
return {
enabled: true,
...raw.notify === true ? { notify: true } : {},
...raw.required === true ? { required: true } : {}
};
}
function buildTelegramActionSendPayload(params) {
const telegramData = params.buttons || params.quoteText ? {
...params.buttons ? { buttons: params.buttons } : {},
...params.quoteText ? { quoteText: params.quoteText } : {}
} : void 0;
return {
text: params.content,
...params.mediaUrls.length > 0 ? { mediaUrls: params.mediaUrls } : {},
...params.asVoice === true ? { audioAsVoice: true } : {},
...params.asVideoNote === true ? { videoAsNote: true } : {},
...params.location ? { location: params.location } : {},
...params.pin ? { delivery: { pin: params.pin } } : {},
...telegramData ? { channelData: { telegram: telegramData } } : {}
};
}
function getLastDurableTelegramActionResult(result) {
const lastResult = result.results.at(-1);
const receipt = result.receipt;
return {
messageId: lastResult?.messageId ?? receipt.primaryPlatformMessageId ?? receipt.platformMessageIds.at(-1),
chatId: lastResult?.target?.kind === "chat" ? lastResult.target.id : void 0,
receipt: {
threadId: receipt.threadId,
replyToId: receipt.replyToId
}
};
}
async function describeTelegramAllowedReactionSample(params) {
const reactions = await telegramActionRuntime.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 notifyVisibleOutboundSuccess = (to, messageThreadId) => {
telegramInboundEventDelivery.notify({
sessionKey: options?.sessionKey ?? void 0,
to: formatTelegramDeliveryTarget(to, messageThreadId),
accountId,
inboundEventKind: options?.inboundEventKind
});
};
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 = resolveTelegramToken(cfg, { accountId }).token;
if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
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 telegramActionRuntime.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 telegramActionRuntime.reactMessageTelegram(authorizedChatId, messageId ?? 0, emoji ?? "", {
cfg,
token,
remove,
accountId: accountId ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes
});
} 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 });
if (!resolveTelegramToken(cfg, { accountId }).token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
const sendOptions = {
cfg,
accountId: accountId ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes,
replyToMessageId: replyToMessageId ?? void 0,
messageThreadId: messageThreadId ?? void 0,
quoteText: quoteText ?? void 0,
asVoice: readBooleanParam(params, "asVoice"),
asVideoNote,
silent: readBooleanParam(params, "silent"),
forceDocument: readBooleanParam(params, "forceDocument") ?? readBooleanParam(params, "asDocument") ?? false
};
const payload = buildTelegramActionSendPayload({
content,
mediaUrls,
asVoice: sendOptions.asVoice,
asVideoNote: sendOptions.asVideoNote,
location,
pin: normalizeTelegramDeliveryPin(params),
buttons,
quoteText
});
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 telegramActionRuntime.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: sendOptions.forceDocument,
silent: sendOptions.silent,
durability: "required",
gatewayClientScopes: options?.gatewayClientScopes,
deliveryRetryOwner: options?.deliveryRetryOwner,
...mediaAccess ? { mediaAccess } : {},
...outboundSession ? { session: outboundSession } : {}
});
if (durableResult.status === "failed" || durableResult.status === "partial_failed") throw durableResult.error;
if (durableResult.status === "suppressed") throw new Error("Telegram sendMessage was suppressed before delivery.");
const result = getLastDurableTelegramActionResult(durableResult);
notifyVisibleOutboundSuccess(to, messageThreadId);
return jsonResult({
ok: true,
messageId: result.messageId,
chatId: result.chatId,
receipt: result.receipt,
...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 = resolveTelegramToken(cfg, { accountId }).token;
if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
const result = await telegramActionRuntime.sendPollTelegram(to, {
question,
options: answers,
maxSelections: resolvePollMaxSelections(answers.length, allowMultiselect ?? false),
durationSeconds: durationSeconds ?? void 0,
durationHours: durationHours ?? void 0
}, {
cfg,
token,
accountId: accountId ?? void 0,
replyToMessageId: replyToMessageId ?? void 0,
messageThreadId: messageThreadId ?? void 0,
isAnonymous: isAnonymous ?? void 0,
silent: silent ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes
});
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") {
if (!isActionEnabled("deleteMessage")) throw new Error("Telegram deleteMessage 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
});
const token = resolveTelegramToken(cfg, { accountId }).token;
if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
const result = await telegramActionRuntime.deleteMessageTelegram(authorizedChatId, messageId ?? 0, {
cfg,
token,
accountId: accountId ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes
});
if (!result.ok) return jsonResult({
ok: false,
deleted: false,
warning: result.warning
});
return jsonResult({
ok: true,
deleted: true
});
}
if (action === "editMessage") {
if (!isActionEnabled("editMessage")) throw new Error("Telegram editMessage 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
});
let content = readToolStringParam(params, "content", { allowEmpty: false }) ?? readToolStringParam(params, "message", { allowEmpty: false });
let caption = readToolStringParam(params, "caption", { allowEmpty: true });
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 = resolveTelegramToken(cfg, { accountId }).token;
if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
if (content == null && caption == null && buttons !== void 0) {
const result = await telegramActionRuntime.editMessageReplyMarkupTelegram(authorizedChatId, messageId ?? 0, buttons, {
cfg,
token,
accountId: accountId ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes
});
return jsonResult({
ok: true,
messageId: result.messageId,
chatId: result.chatId,
...buildTelegramControlDegradation(droppedControls, false)
});
}
const result = await telegramActionRuntime.editMessageTelegram(authorizedChatId, messageId ?? 0, caption ?? content ?? "", {
cfg,
token,
accountId: accountId ?? void 0,
buttons,
editMode: caption != null ? "caption" : "auto",
gatewayClientScopes: options?.gatewayClientScopes
});
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 = resolveTelegramToken(cfg, { accountId }).token;
if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
const result = await telegramActionRuntime.sendStickerTelegram(to, fileId, {
cfg,
token,
accountId: accountId ?? void 0,
replyToMessageId: replyToMessageId ?? void 0,
messageThreadId: messageThreadId ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes
});
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 = telegramActionRuntime.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 = telegramActionRuntime.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 = resolveTelegramToken(cfg, { accountId }).token;
if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
const result = await telegramActionRuntime.createForumTopicTelegram(chatId ?? "", name, {
cfg,
token,
accountId: accountId ?? void 0,
iconColor,
iconCustomEmojiId: iconCustomEmojiId ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes
});
if (result.topicId != null && result.chatId) await updateTopicName(result.chatId, result.topicId, {
name,
...iconColor != null ? { iconColor } : {},
...iconCustomEmojiId ? { iconCustomEmojiId } : {}
}, resolveActionTopicNameCacheScope(cfg, accountId)).catch(() => {});
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 = resolveTelegramToken(cfg, { accountId }).token;
if (!token) throw new Error("Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.");
const result = await telegramActionRuntime.editForumTopicTelegram(chatId ?? "", messageThreadId, {
cfg,
token,
accountId: accountId ?? void 0,
name: name ?? void 0,
iconCustomEmojiId: iconCustomEmojiId ?? void 0,
gatewayClientScopes: options?.gatewayClientScopes
});
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, telegramActionRuntime };