openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
250 lines (249 loc) • 10.3 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { s as resolveChunkMode } from "./chunk-Cpb8JO1x.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { t as convertMarkdownTables } from "./tables-BgtXxld3.js";
import "./text-chunking-D45TeeEs.js";
import { t as resolveMarkdownTableMode } from "./markdown-tables-LfSv7QSx.js";
import { n as recordChannelActivity } from "./channel-activity-4piA219h.js";
import { t as requireRuntimeConfig } from "./plugin-config-runtime-CpE6DYgJ.js";
import "./reply-chunking-C9pgDFVG.js";
import "./markdown-table-runtime-j_C4iTF_.js";
import { s as resolveDiscordAccount } from "./accounts-BI_6uJlY.js";
import { $ as createChannelMessage, It as ChannelType, et as createThread } from "./discord-DdqXEGEm.js";
import { F as createDiscordClient, P as parseAndResolveChannelRecipient, _ as resolveDiscordMessageFlags, a as normalizeDiscordPollInput, c as normalizeStickerIds, f as sendDiscordMedia, g as buildDiscordMessageRequest, l as resolveChannelId, n as buildDiscordTextChunks, p as sendDiscordText, t as buildDiscordSendError, u as resolveDiscordChannelType, v as resolveDiscordSendComponents, y as resolveDiscordSendEmbeds } from "./send.shared-SdWkx4sF.js";
import { i as rewriteDiscordKnownMentions } from "./mentions-Da7srbtk.js";
import { n as createDiscordSendResult } from "./send.receipt-CUG417Vx.js";
//#region extensions/discord/src/send.outbound.ts
const DEFAULT_DISCORD_MEDIA_MAX_MB = 100;
async function sendDiscordThreadTextChunks(params) {
for (const chunk of params.chunks) await sendDiscordText(params.rest, params.threadId, chunk, void 0, params.request, params.maxLinesPerMessage, void 0, void 0, params.chunkMode, params.silent, params.suppressEmbeds, params.maxChars);
}
function resolveDiscordSuppressEmbeds(params) {
return params.override ?? params.configured ?? true;
}
/** Discord thread names are capped at 100 characters. */
const DISCORD_THREAD_NAME_LIMIT = 100;
/** Derive a thread title from the first non-empty line of the message text. */
function deriveForumThreadName(text) {
return (normalizeOptionalString(text.split("\n").find((line) => normalizeOptionalString(line))) ?? "").slice(0, DISCORD_THREAD_NAME_LIMIT) || (/* @__PURE__ */ new Date()).toISOString().slice(0, 16);
}
/** Forum/Media channels cannot receive regular messages; detect them here. */
function isForumLikeType(channelType) {
return channelType === ChannelType.GuildForum || channelType === ChannelType.GuildMedia;
}
function toDiscordSendResult(result, fallbackChannelId, params = {}) {
const resultParams = {
result,
fallbackChannelId,
kind: params.kind ?? "text"
};
if (params.threadId != null) resultParams.threadId = params.threadId;
if (params.replyToId) resultParams.replyToId = params.replyToId;
return createDiscordSendResult(resultParams);
}
async function resolveDiscordSendTarget(to, opts) {
const cfg = requireRuntimeConfig(opts.cfg, "Discord send target resolution");
const { rest, request } = createDiscordClient({
...opts,
cfg
});
const { channelId } = await resolveChannelId(rest, await parseAndResolveChannelRecipient(to, cfg, opts.accountId), request);
return {
rest,
request,
channelId
};
}
async function sendMessageDiscord(to, text, opts) {
const cfg = requireRuntimeConfig(opts.cfg, "Discord send");
const accountInfo = resolveDiscordAccount({
cfg,
accountId: opts.accountId
});
const tableMode = resolveMarkdownTableMode({
cfg,
channel: "discord",
accountId: accountInfo.accountId
});
const effectiveTableMode = opts.tableMode ?? tableMode;
const chunkMode = opts.chunkMode ?? resolveChunkMode(cfg, "discord", accountInfo.accountId);
const maxLinesPerMessage = opts.maxLinesPerMessage ?? accountInfo.config.maxLinesPerMessage;
const suppressEmbeds = resolveDiscordSuppressEmbeds({
configured: accountInfo.config.suppressEmbeds,
override: opts.suppressEmbeds
});
const textLimit = typeof opts.textLimit === "number" && Number.isFinite(opts.textLimit) ? Math.max(1, Math.min(Math.floor(opts.textLimit), 2e3)) : void 0;
const mediaMaxBytes = typeof accountInfo.config.mediaMaxMb === "number" ? accountInfo.config.mediaMaxMb * 1024 * 1024 : DEFAULT_DISCORD_MEDIA_MAX_MB * 1024 * 1024;
const textWithTables = convertMarkdownTables(text ?? "", effectiveTableMode);
const textWithMentions = rewriteDiscordKnownMentions(textWithTables, {
accountId: accountInfo.accountId,
mentionAliases: accountInfo.config.mentionAliases
});
const { token, rest, request } = createDiscordClient({
...opts,
cfg
});
const { channelId } = await resolveChannelId(rest, await parseAndResolveChannelRecipient(to, cfg, opts.accountId), request);
if (isForumLikeType(await resolveDiscordChannelType(rest, channelId))) {
const threadName = deriveForumThreadName(textWithTables);
const chunks = buildDiscordTextChunks(textWithMentions, {
maxLinesPerMessage,
chunkMode,
maxChars: textLimit
});
const starterContent = chunks[0]?.trim() ? chunks[0] : threadName;
const starterComponents = resolveDiscordSendComponents({
components: opts.components,
text: starterContent,
isFirst: true
});
const starterEmbeds = resolveDiscordSendEmbeds({
embeds: opts.embeds,
isFirst: true
});
const starterBody = buildDiscordMessageRequest({
text: starterContent,
components: starterComponents,
embeds: starterEmbeds,
flags: resolveDiscordMessageFlags({
silent: opts.silent,
suppressEmbeds: suppressEmbeds && !starterEmbeds?.length
})
});
let threadRes;
try {
threadRes = await request(() => createThread(rest, channelId, { body: {
name: threadName,
message: starterBody
} }), "forum-thread");
} catch (err) {
throw await buildDiscordSendError(err, {
channelId,
cfg,
rest,
token,
hasMedia: Boolean(opts.mediaUrl)
});
}
const threadId = threadRes.id;
const messageId = threadRes.message?.id ?? threadId;
const resultChannelId = threadRes.message?.channel_id ?? threadId;
const remainingChunks = chunks.slice(1);
try {
if (opts.mediaUrl) {
const [mediaCaption, ...afterMediaChunks] = remainingChunks;
await sendDiscordMedia(rest, threadId, mediaCaption ?? "", opts.mediaUrl, opts.filename, opts.mediaAccess, opts.mediaLocalRoots, opts.mediaReadFile, mediaMaxBytes, void 0, request, maxLinesPerMessage, void 0, void 0, chunkMode, opts.silent, suppressEmbeds, textLimit);
await sendDiscordThreadTextChunks({
rest,
threadId,
chunks: afterMediaChunks,
request,
maxLinesPerMessage,
chunkMode,
maxChars: textLimit,
silent: opts.silent,
suppressEmbeds
});
} else await sendDiscordThreadTextChunks({
rest,
threadId,
chunks: remainingChunks,
request,
maxLinesPerMessage,
chunkMode,
maxChars: textLimit,
silent: opts.silent,
suppressEmbeds
});
} catch (err) {
throw await buildDiscordSendError(err, {
channelId: threadId,
cfg,
rest,
token,
hasMedia: Boolean(opts.mediaUrl)
});
}
recordChannelActivity({
channel: "discord",
accountId: accountInfo.accountId,
direction: "outbound"
});
return toDiscordSendResult({
id: messageId,
channel_id: resultChannelId
}, channelId, {
kind: opts.mediaUrl ? "media" : "text",
threadId
});
}
let result;
try {
if (opts.mediaUrl) result = await sendDiscordMedia(rest, channelId, textWithMentions, opts.mediaUrl, opts.filename, opts.mediaAccess, opts.mediaLocalRoots, opts.mediaReadFile, mediaMaxBytes, opts.replyTo, request, maxLinesPerMessage, opts.components, opts.embeds, chunkMode, opts.silent, suppressEmbeds, textLimit);
else result = await sendDiscordText(rest, channelId, textWithMentions, opts.replyTo, request, maxLinesPerMessage, opts.components, opts.embeds, chunkMode, opts.silent, suppressEmbeds, textLimit);
} catch (err) {
throw await buildDiscordSendError(err, {
channelId,
cfg,
rest,
token,
hasMedia: Boolean(opts.mediaUrl)
});
}
recordChannelActivity({
channel: "discord",
accountId: accountInfo.accountId,
direction: "outbound"
});
return toDiscordSendResult(result, channelId, {
kind: opts.mediaUrl ? "media" : opts.components || opts.embeds ? "card" : "text",
replyToId: opts.replyTo
});
}
async function sendStickerDiscord(to, stickerIds, opts) {
const { rest, request, channelId, rewrittenContent, suppressEmbeds } = await resolveDiscordStructuredSendContext(to, opts);
const stickers = normalizeStickerIds(stickerIds);
const flags = resolveDiscordMessageFlags({ suppressEmbeds });
return toDiscordSendResult(await request(() => createChannelMessage(rest, channelId, { body: {
content: rewrittenContent || void 0,
sticker_ids: stickers,
...flags ? { flags } : {}
} }), "sticker"), channelId, { kind: "card" });
}
async function sendPollDiscord(to, poll, opts) {
const { rest, request, channelId, rewrittenContent, suppressEmbeds } = await resolveDiscordStructuredSendContext(to, opts);
if (poll.durationSeconds !== void 0) throw new Error("Discord polls do not support durationSeconds; use durationHours");
const payload = normalizeDiscordPollInput(poll);
const flags = resolveDiscordMessageFlags({
silent: opts.silent,
suppressEmbeds
});
return toDiscordSendResult(await request(() => createChannelMessage(rest, channelId, { body: {
content: rewrittenContent || void 0,
poll: payload,
...flags ? { flags } : {}
} }), "poll"), channelId, { kind: "card" });
}
async function resolveDiscordStructuredSendContext(to, opts) {
const accountInfo = resolveDiscordAccount({
cfg: requireRuntimeConfig(opts.cfg, "Discord structured send"),
accountId: opts.accountId
});
const { rest, request, channelId } = await resolveDiscordSendTarget(to, opts);
const content = opts.content?.trim();
return {
rest,
request,
channelId,
rewrittenContent: content ? rewriteDiscordKnownMentions(content, {
accountId: accountInfo.accountId,
mentionAliases: accountInfo.config.mentionAliases
}) : void 0,
suppressEmbeds: resolveDiscordSuppressEmbeds({
configured: accountInfo.config.suppressEmbeds,
override: opts.suppressEmbeds
})
};
}
//#endregion
export { sendPollDiscord as n, sendStickerDiscord as r, sendMessageDiscord as t };