openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
432 lines (431 loc) • 19.8 kB
JavaScript
import { w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import "./channel-outbound-Dw9bfhFK.js";
import { a as sanitizeAssistantVisibleText } from "./assistant-visible-text-BSYbiXdn.js";
import { g as sendPayloadMediaSequenceOrFallback } from "./reply-payload-Bfsi665c.js";
import { a as resolveSendableOutboundReplyParts } from "./reply-payload-parts-BsFA4sDv.js";
import { n as isSingleUseReplyToMode } from "./reply-reference-cLEWJ7Kr.js";
import { i as chunkMarkdownTextWithMode } from "./chunk-DqjJztIp.js";
import "./number-runtime-Cy4drVnh.js";
import { n as questionGatewayRuntime } from "./question-gateway-runtime-BPxXCqYp.js";
import "./reply-reference-CdV5SXbF.js";
import "./reply-chunking-gFG31C0u.js";
import { n as resolveOutboundSendDep } from "./send-deps-DjbvQHZ4.js";
import { t as sanitizeForPlainText } from "./sanitize-text-BNoGtAII.js";
import "./text-chunking-BCaDmWPm.js";
import { i as createAttachedChannelResultAdapter, t as attachChannelToResult } from "./channel-send-result-BFAnsv6z.js";
import "./text-utility-runtime-BjzvUG99.js";
import { t as mergeTelegramAccountConfig } from "./account-config-B05_z8M7.js";
import { a as resolveDefaultTelegramAccountId } from "./accounts-CgIQ5mFD.js";
import { o as normalizeTelegramOutboundTarget, s as parseTelegramTarget } from "./topic-conversation-Cl4csGES.js";
import { i as parseTelegramThreadId, r as parseTelegramReplyToMessageId } from "./outbound-params-BUIfhgvx.js";
import { E as resolveTelegramPromptContextSource, M as telegramCaptionDeliveryMetadata, R as splitTelegramHtmlChunks, k as TELEGRAM_MAX_CAPTION_LENGTH, n as resolveTelegramTextChunkLimit, t as TELEGRAM_TEXT_CHUNK_LIMIT, x as createTelegramPromptContextProjectionCursor } from "./text-chunk-limit-C3EsGj6Q.js";
import { r as resolveTelegramInlineButtons } from "./button-types-BNe7Hg2C.js";
import { n as resolveTelegramInteractiveTextFallback, r as resolveTelegramPresentationCapabilities, t as canonicalizeTelegramPresentationPayload } from "./interactive-fallback-DOi9HmOl.js";
import { t as loadTelegramSendModule } from "./send-runtime-DkRdeScS.js";
//#region extensions/telegram/src/question-finalization.ts
function registerTelegramQuestionDelivery(params) {
const questionId = questionGatewayRuntime.readAskUserQuestionId(params.payload);
const text = params.text.trim();
if (!questionId || !text) return;
const { accountId, chatId, messageId, textLimit, clearButtons, annotate } = params;
const deliveryId = `telegram:${accountId ?? "default"}:${chatId}:${messageId}`;
questionGatewayRuntime.registerChannelDelivery({
questionId,
deliveryId,
finalize: async (statusLine) => {
await clearButtons();
const limit = Math.max(0, Math.floor(textLimit));
const suffix = truncateUtf16Safe(statusLine.trim(), Math.min(512, limit));
const separator = suffix && limit - suffix.length >= 2 ? "\n\n" : "";
const prefix = truncateUtf16Safe(text, limit - separator.length - suffix.length);
await annotate(`${prefix}${separator}${suffix}`);
}
});
}
//#endregion
//#region extensions/telegram/src/outbound-adapter.ts
const TELEGRAM_POLL_OPTION_LIMIT = 12;
function toTelegramOutboundResult(result) {
const { chatId, ...delivery } = result;
return chatId === void 0 ? delivery : {
...delivery,
target: {
kind: "chat",
id: chatId
}
};
}
async function resolveDefaultTelegramSend(deps) {
return resolveOutboundSendDep(deps, "telegram") ?? (await loadTelegramSendModule()).sendMessageTelegram;
}
function chunkTelegramOutboundText(text, limit, ctx) {
return ctx?.formatting?.parseMode === "HTML" ? splitTelegramHtmlChunks(text, limit) : chunkMarkdownTextWithMode(text, limit, ctx?.formatting?.chunkMode ?? "length");
}
async function resolveTelegramSendContext(params) {
return {
send: await params.resolveSend(params.deps),
baseOpts: {
verbose: false,
cfg: params.cfg,
messageThreadId: parseTelegramThreadId(params.threadId),
replyToMessageId: parseTelegramReplyToMessageId(params.replyToId),
...params.replyToIdSource !== void 0 ? { replyToIdSource: params.replyToIdSource } : {},
...params.replyToMode !== void 0 ? { replyToMode: params.replyToMode } : {},
accountId: params.accountId ?? void 0,
silent: params.silent,
gatewayClientScopes: params.gatewayClientScopes,
onDeliveryResult: params.onDeliveryResult ? async (result) => {
await params.onDeliveryResult?.(attachChannelToResult("telegram", toTelegramOutboundResult(result)));
} : void 0,
onPlatformSendDispatch: params.onPlatformSendDispatch,
assertPlatformSendAuthorized: params.assertDirectAdapterHandoff,
...params.formatting?.parseMode === "HTML" ? { textMode: "html" } : {},
tableMode: params.formatting?.tableMode
}
};
}
async function resolveTelegramOutboundSendContext(params) {
const outboundTo = normalizeTelegramOutboundTarget(params.to);
const { send, baseOpts } = await resolveTelegramSendContext(params);
return {
outboundTo,
send,
baseOpts
};
}
function telegramRichTablesEnabled(params) {
if (params.htmlTextMode) return false;
return mergeTelegramAccountConfig(params.cfg, params.accountId ?? resolveDefaultTelegramAccountId(params.cfg)).richMessages === true;
}
function normalizeTelegramMetadataOnlyPayload(payload) {
const telegramData = payload.channelData?.telegram;
if (resolveTelegramInteractiveTextFallback({
text: payload.text,
interactive: payload.interactive,
presentation: payload.presentation
})?.trim() || resolveSendableOutboundReplyParts(payload).mediaUrls.length > 0 || payload.location || payload.audioAsVoice === true || payload.videoAsNote === true || payload.presentation || payload.interactive) return payload;
const buttons = resolveTelegramInlineButtons({
buttons: telegramData?.buttons,
presentation: payload.presentation,
interactive: payload.interactive
});
const hasQuoteText = typeof telegramData?.quoteText === "string" && Boolean(telegramData.quoteText.trim());
if (typeof telegramData?.reaction?.emoji === "string" && Boolean(telegramData.reaction.emoji.trim()) && !buttons?.length && !hasQuoteText) return payload;
const fallbackText = payload.fallbackText?.text.trim();
if (!buttons?.length && !hasQuoteText) return null;
return fallbackText ? {
...payload,
text: fallbackText
} : null;
}
function mergeTelegramFallbackPayloads(source, adopter) {
const sourceTelegram = source.channelData?.telegram;
const adopterTelegram = adopter.channelData?.telegram;
const buttons = [...sourceTelegram?.buttons ?? [], ...adopterTelegram?.buttons ?? []];
const quoteText = sourceTelegram?.quoteText?.trim() ? sourceTelegram.quoteText : adopterTelegram?.quoteText;
const telegram = sourceTelegram || adopterTelegram ? {
...adopterTelegram,
...sourceTelegram,
...buttons.length > 0 ? { buttons } : {},
...quoteText ? { quoteText } : {}
} : void 0;
return {
...adopter,
...source,
fallbackText: adopter.fallbackText,
channelData: {
...adopter.channelData,
...source.channelData,
...telegram ? { telegram } : {}
}
};
}
function normalizeTelegramFallbackPayloadBatch(entries) {
const normalized = entries.map((entry) => entry.payload);
const positions = new Map(entries.map((entry, position) => [entry.index, position]));
for (const [position, entry] of entries.entries()) {
const fallback = entry.payload.fallbackText;
if (fallback?.replacesPayloadIndex === void 0 || entry.payload.text?.trim() !== fallback.text.trim() || entry.payload.interactive || entry.payload.presentation || resolveSendableOutboundReplyParts(entry.payload).mediaUrls.length > 0 || entry.payload.location || entry.payload.audioAsVoice === true || entry.payload.videoAsNote === true) continue;
const channelData = entry.payload.channelData;
const channelDataKeys = channelData ? Object.keys(channelData) : [];
const telegramData = channelData?.telegram;
if (channelDataKeys.length !== 1 || channelDataKeys[0] !== "telegram" || !telegramData?.buttons?.length || telegramData.quoteText?.trim() || telegramData.reaction) continue;
const sourcePosition = positions.get(fallback.replacesPayloadIndex);
if (sourcePosition === void 0) continue;
const source = normalized[sourcePosition];
if (!source || source.text?.trim() !== fallback.text.trim()) continue;
normalized[sourcePosition] = mergeTelegramFallbackPayloads(source, entry.payload);
normalized[position] = null;
}
return normalized;
}
async function sendTelegramPayloadMessages(params) {
const payload = canonicalizeTelegramPresentationPayload(params.payload, {
allowWebAppButtons: parseTelegramTarget(params.to).chatType === "direct",
richTables: telegramRichTablesEnabled({
cfg: params.baseOpts.cfg,
accountId: params.baseOpts.accountId,
htmlTextMode: params.baseOpts.textMode === "html"
})
});
const telegramData = payload.channelData?.telegram;
const quoteText = typeof telegramData?.quoteText === "string" ? telegramData.quoteText : void 0;
const reactionEmoji = typeof telegramData?.reaction?.emoji === "string" ? telegramData.reaction.emoji : void 0;
const text = resolveTelegramInteractiveTextFallback({
text: payload.text,
interactive: payload.interactive,
presentation: payload.presentation
}) ?? "";
const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
const buttons = resolveTelegramInlineButtons({
buttons: telegramData?.buttons,
presentation: payload.presentation,
interactive: payload.interactive
});
const replyToMessageId = parseStrictPositiveInteger(telegramData?.reaction?.replyToId ?? params.baseOpts.replyToMessageId);
const promptContextSource = resolveTelegramPromptContextSource(params.payload);
const projectionCursor = promptContextSource ? createTelegramPromptContextProjectionCursor(promptContextSource) : void 0;
const projectionOptions = (finalPart) => projectionCursor ? { promptContextProjectionPlan: {
cursor: projectionCursor,
finalPart
} } : {};
const payloadOpts = {
...params.baseOpts,
quoteText,
...payload.audioAsVoice === true ? { asVoice: true } : {},
...payload.videoAsNote === true ? { asVideoNote: true } : {}
};
if (payload.location) {
if (mediaUrls.length > 0 || reactionEmoji || payload.audioAsVoice === true || payload.videoAsNote === true) throw new Error("Telegram location sends cannot be combined with media or reactions.");
if (text.trim()) await params.send(params.to, text, {
...params.baseOpts,
replyToMessageId: void 0,
replyToIdSource: void 0,
replyToMode: void 0
});
return await params.sendLocation(params.to, payload.location, {
...params.baseOpts,
...projectionOptions(true),
buttons,
quoteText
});
}
if (payload.videoAsNote === true && mediaUrls.length !== 1) throw new Error("Telegram video notes require exactly one media attachment.");
const shouldConsumeImplicitReplyTarget = payloadOpts.replyToIdSource === "implicit" && payloadOpts.replyToMode !== void 0 && isSingleUseReplyToMode(payloadOpts.replyToMode);
const consumedImplicitReplyPayloadOpts = shouldConsumeImplicitReplyTarget ? {
...payloadOpts,
replyToMessageId: void 0,
replyToIdSource: void 0,
replyToMode: void 0
} : payloadOpts;
let implicitReplyTargetAvailable = true;
if (reactionEmoji) {
if (typeof replyToMessageId !== "number") throw new Error("Telegram reaction requires a reply target");
await params.baseOpts.onPlatformSendDispatch?.();
params.baseOpts.assertPlatformSendAuthorized?.();
const reactionResult = await params.react(params.to, replyToMessageId, reactionEmoji, {
cfg: params.baseOpts.cfg,
accountId: params.baseOpts.accountId,
gatewayClientScopes: params.baseOpts.gatewayClientScopes,
verbose: false
});
if (!reactionResult.ok) throw new Error(reactionResult.warning);
}
if (reactionEmoji && !text && mediaUrls.length === 0 && !buttons?.length) return {
messageId: String(replyToMessageId),
chatId: params.to
};
return await sendPayloadMediaSequenceOrFallback({
text,
mediaUrls,
fallbackResult: {
messageId: "unknown",
chatId: params.to
},
sendNoMedia: async () => await params.send(params.to, text, {
...payloadOpts,
...projectionOptions(true),
buttons
}),
send: async ({ text: textLocal, mediaUrl, index, isFirst }) => {
const mediaPayloadOpts = shouldConsumeImplicitReplyTarget && !implicitReplyTargetAvailable ? consumedImplicitReplyPayloadOpts : payloadOpts;
implicitReplyTargetAvailable = false;
return await params.send(params.to, textLocal, {
...mediaPayloadOpts,
...projectionOptions(index === mediaUrls.length - 1),
mediaUrl,
...isFirst ? { buttons } : {}
});
}
});
}
function createTelegramOutboundAdapter(options = {}) {
const resolveSend = options.resolveSend ?? resolveDefaultTelegramSend;
const loadSendModule = options.loadSendModule ?? loadTelegramSendModule;
return {
deliveryMode: "direct",
chunker: chunkTelegramOutboundText,
chunkerMode: "markdown",
extractMarkdownImages: true,
textChunkLimit: TELEGRAM_TEXT_CHUNK_LIMIT,
preserveMarkdownDetails: ({ cfg, accountId }) => mergeTelegramAccountConfig(cfg, accountId ?? resolveDefaultTelegramAccountId(cfg)).richMessages === true,
sanitizeText: ({ text, cfg, accountId }) => cfg && mergeTelegramAccountConfig(cfg, accountId ?? resolveDefaultTelegramAccountId(cfg)).richMessages === true ? sanitizeAssistantVisibleText(text) : sanitizeForPlainText(sanitizeAssistantVisibleText(text), { style: "markdown" }),
shouldSuppressLocalPayloadPrompt: options.shouldSuppressLocalPayloadPrompt,
beforeDeliverPayload: options.beforeDeliverPayload,
shouldTreatDeliveredTextAsVisible: options.shouldTreatDeliveredTextAsVisible,
targetsMatchForReplySuppression: options.targetsMatchForReplySuppression,
preferFinalAssistantVisibleText: options.preferFinalAssistantVisibleText,
normalizePayload: ({ payload }) => normalizeTelegramMetadataOnlyPayload(payload),
normalizePayloadBatch: ({ payloads }) => normalizeTelegramFallbackPayloadBatch(payloads),
presentationCapabilities: resolveTelegramPresentationCapabilities({ richMessages: false }),
resolvePresentationCapabilities: ({ cfg, accountId, formatting }) => resolveTelegramPresentationCapabilities({ richMessages: telegramRichTablesEnabled({
cfg,
accountId,
htmlTextMode: formatting?.parseMode === "HTML"
}) }),
deliveryCapabilities: {
pin: true,
durableFinal: {
text: true,
media: true,
payload: true,
silent: true,
replyTo: true,
thread: true,
nativeQuote: false,
messageSendingHooks: true,
batch: true
}
},
renderPresentation: ({ payload, presentation, ctx }) => canonicalizeTelegramPresentationPayload({
...payload,
presentation
}, {
allowWebAppButtons: parseTelegramTarget(ctx.to ?? "").chatType === "direct",
richTables: telegramRichTablesEnabled({
cfg: ctx.cfg,
accountId: ctx.accountId,
htmlTextMode: ctx.formatting?.parseMode === "HTML"
})
}),
afterDeliverPayload: ({ cfg, target, payload, results }) => {
const telegramResults = results.filter((candidate) => candidate.channel === "telegram" && candidate.messageId);
const result = telegramResults.find((candidate) => candidate.meta?.telegramHasInlineKeyboard === true) ?? telegramResults.at(-1);
const text = (typeof result?.meta?.telegramDeliveredText === "string" ? result.meta.telegramDeliveredText : payload.text)?.trim();
if (!result || !text) return;
const chatId = result.target?.kind === "chat" ? result.target.id : normalizeTelegramOutboundTarget(target.to);
const messageId = result.messageId;
const accountId = target.accountId ?? void 0;
const deliveredPart = result.receipt?.parts.find((part) => part.platformMessageId === messageId);
const isCaptionDelivery = deliveredPart?.kind === "media" || deliveredPart?.kind !== "text" && result.meta !== void 0 && telegramCaptionDeliveryMetadata.has(result.meta);
registerTelegramQuestionDelivery({
accountId,
chatId,
messageId,
payload,
text,
textLimit: isCaptionDelivery ? TELEGRAM_MAX_CAPTION_LENGTH : TELEGRAM_TEXT_CHUNK_LIMIT,
clearButtons: async () => {
const { editMessageReplyMarkupTelegram } = await loadSendModule();
await editMessageReplyMarkupTelegram(chatId, messageId, [], {
cfg,
accountId,
verbose: false
});
},
annotate: async (finalText) => {
const { editMessageTelegram } = await loadSendModule();
await editMessageTelegram(chatId, messageId, finalText, {
cfg,
accountId,
verbose: false,
...isCaptionDelivery ? { editMode: "caption" } : {}
});
}
});
},
pinDeliveredMessage: async ({ cfg, target, messageId, pin, gatewayClientScopes }) => {
const { pinMessageTelegram } = await loadSendModule();
const outboundTo = normalizeTelegramOutboundTarget(target.to);
await pinMessageTelegram(parseTelegramTarget(outboundTo).chatId, messageId, {
cfg,
accountId: target.accountId ?? void 0,
notify: pin.notify,
verbose: false,
gatewayClientScopes
});
},
resolveEffectiveTextChunkLimit: ({ cfg, accountId, formatting }) => resolveTelegramTextChunkLimit({
cfg,
accountId,
formatting
}),
pollMaxOptions: TELEGRAM_POLL_OPTION_LIMIT,
supportsPollDurationSeconds: true,
supportsAnonymousPolls: true,
...createAttachedChannelResultAdapter({
channel: "telegram",
sendText: async (params) => {
const { outboundTo, send, baseOpts } = await resolveTelegramOutboundSendContext({
...params,
resolveSend
});
return toTelegramOutboundResult(await send(outboundTo, params.text, { ...baseOpts }));
},
sendMedia: async (params) => {
const { outboundTo, send, baseOpts } = await resolveTelegramOutboundSendContext({
...params,
resolveSend
});
return toTelegramOutboundResult(await send(outboundTo, params.text, {
...baseOpts,
mediaUrl: params.mediaUrl,
...params.mediaAccess !== void 0 ? { mediaAccess: params.mediaAccess } : {},
mediaLocalRoots: params.mediaLocalRoots,
mediaReadFile: params.mediaReadFile,
forceDocument: params.forceDocument ?? false
}));
}
}),
sendPayload: async (params) => {
const { outboundTo, send, baseOpts } = await resolveTelegramOutboundSendContext({
...params,
resolveSend
});
const { reactMessageTelegram, sendLocationTelegram } = await loadSendModule();
const result = await sendTelegramPayloadMessages({
send,
sendLocation: sendLocationTelegram,
react: reactMessageTelegram,
to: outboundTo,
payload: params.payload,
baseOpts: {
...baseOpts,
...params.mediaAccess !== void 0 ? { mediaAccess: params.mediaAccess } : {},
mediaLocalRoots: params.mediaLocalRoots,
mediaReadFile: params.mediaReadFile,
forceDocument: params.forceDocument ?? false
}
});
return attachChannelToResult("telegram", toTelegramOutboundResult(result));
},
sendPoll: async ({ cfg, to, poll, accountId, threadId, silent, isAnonymous, gatewayClientScopes, onPlatformSendDispatch, assertDirectAdapterHandoff }) => {
const outboundTo = normalizeTelegramOutboundTarget(to);
const { sendPollTelegram } = await loadSendModule();
return await sendPollTelegram(outboundTo, poll, {
cfg,
accountId: accountId ?? void 0,
messageThreadId: parseTelegramThreadId(threadId),
silent: silent ?? void 0,
isAnonymous: isAnonymous ?? void 0,
gatewayClientScopes,
onPlatformSendDispatch,
assertPlatformSendAuthorized: assertDirectAdapterHandoff
});
}
};
}
const telegramOutbound = createTelegramOutboundAdapter();
//#endregion
export { registerTelegramQuestionDelivery as i, sendTelegramPayloadMessages as n, telegramOutbound as r, createTelegramOutboundAdapter as t };