openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
248 lines (247 loc) • 8.1 kB
JavaScript
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, t as hasNonEmptyString, u as normalizeOptionalStringifiedId } from "./string-coerce-CIXf7egm.js";
import { t as getBootstrapChannelPlugin } from "./bootstrap-registry-BtX6YWgO.js";
//#region src/infra/outbound/message-action-param-keys.ts
const STANDARD_MESSAGE_ACTION_PARAM_KEYS = /* @__PURE__ */ new Set([
"accountId",
"action",
"asDocument",
"attachments",
"base64",
"bestEffort",
"buffer",
"caption",
"channel",
"channelId",
"contentType",
"delivery",
"dryRun",
"filePath",
"fileUrl",
"filename",
"forceDocument",
"gifPlayback",
"gatewayToken",
"gatewayUrl",
"image",
"idempotencyKey",
"interactive",
"json",
"media",
"mediaUrl",
"mediaUrls",
"media_urls",
"message",
"mimeType",
"path",
"pollAnonymous",
"pollDurationHours",
"pollMulti",
"pollOption",
"pollPublic",
"pollQuestion",
"pin",
"presentation",
"replyTo",
"silent",
"senderIsOwner",
"target",
"targets",
"text",
"threadId",
"timeoutMs",
"topLevel",
"to"
]);
/**
* Detects non-standard message action params that may need plugin-owned handling.
*/
function hasPotentialPluginActionParam(params) {
return Object.entries(params).some(([key, value]) => {
if (STANDARD_MESSAGE_ACTION_PARAM_KEYS.has(key)) return false;
if (typeof value === "string") return Boolean(normalizeOptionalString(value));
if (typeof value === "number") return Number.isFinite(value);
return value !== void 0;
});
}
//#endregion
//#region src/infra/outbound/message-action-spec.ts
/**
* Target-parameter policy for each supported channel message action.
*/
const MESSAGE_ACTION_TARGET_MODE = {
send: "to",
broadcast: "none",
poll: "to",
"poll-vote": "to",
react: "to",
reactions: "to",
read: "to",
edit: "to",
unsend: "to",
reply: "to",
sendWithEffect: "to",
renameGroup: "to",
setGroupIcon: "to",
addParticipant: "to",
removeParticipant: "to",
leaveGroup: "to",
sendAttachment: "to",
delete: "to",
pin: "to",
unpin: "to",
"list-pins": "to",
permissions: "to",
"thread-create": "to",
"thread-list": "none",
"thread-reply": "to",
search: "none",
sticker: "to",
"sticker-search": "none",
"member-info": "none",
"role-info": "none",
"emoji-list": "none",
"emoji-upload": "none",
"sticker-upload": "none",
"role-add": "none",
"role-remove": "none",
"channel-info": "channelId",
"channel-list": "none",
"channel-create": "none",
"conversation-open": "none",
"channel-edit": "channelId",
"channel-delete": "channelId",
"channel-move": "channelId",
"category-create": "none",
"category-edit": "none",
"category-delete": "none",
"topic-create": "to",
"topic-edit": "to",
"voice-status": "none",
"event-list": "none",
"event-create": "none",
timeout: "none",
kick: "none",
ban: "none",
"set-profile": "none",
"set-presence": "none",
"download-file": "none",
"upload-file": "to"
};
const ACTION_TARGET_ALIASES = {
unsend: { aliases: ["messageId"] },
edit: { aliases: ["messageId"] },
react: { aliases: [
"chatGuid",
"chatIdentifier",
"chatId"
] },
renameGroup: { aliases: [
"chatGuid",
"chatIdentifier",
"chatId"
] },
setGroupIcon: { aliases: [
"chatGuid",
"chatIdentifier",
"chatId"
] },
addParticipant: { aliases: [
"chatGuid",
"chatIdentifier",
"chatId"
] },
removeParticipant: { aliases: [
"chatGuid",
"chatIdentifier",
"chatId"
] },
leaveGroup: { aliases: [
"chatGuid",
"chatIdentifier",
"chatId"
] }
};
function listActionTargetAliasSpecs(action, params, channel) {
const specs = [];
const coreSpec = ACTION_TARGET_ALIASES[action];
if (coreSpec) specs.push(coreSpec);
const normalizedChannel = normalizeOptionalLowercaseString(channel);
if (!normalizedChannel || !hasPotentialPluginActionParam(params)) return specs;
const channelSpec = getBootstrapChannelPlugin(normalizedChannel)?.actions?.messageActionTargetAliases?.[action];
if (channelSpec) specs.push(channelSpec);
return specs;
}
/** Resolves a plugin-declared delivery alias into the shared target contract. */
function resolveActionDeliveryTargetAlias(action, params, options) {
const channel = normalizeOptionalLowercaseString(options?.channel);
if (!channel || !hasPotentialPluginActionParam(params)) return;
const aliases = options?.aliasSpec ?? getBootstrapChannelPlugin(channel)?.actions?.messageActionTargetAliases?.[action];
const resolved = aliases?.resolveDeliveryTarget?.({ args: params });
if (resolved !== void 0) return normalizeOptionalString(resolved);
const targets = (aliases?.deliveryTargetAliases ?? []).map((alias) => normalizeOptionalStringifiedId(params[alias])).filter((value) => Boolean(value));
if (new Set(targets).size > 1) throw new Error(`Action ${action} received conflicting delivery target aliases.`);
return targets[0];
}
/** Reports whether a plugin alias identifies an existing resource rather than a conversation. */
function actionHasResourceReference(action, params, options) {
const channel = normalizeOptionalLowercaseString(options?.channel);
if (!channel || !hasPotentialPluginActionParam(params)) return false;
const aliases = options?.aliasSpec ?? getBootstrapChannelPlugin(channel)?.actions?.messageActionTargetAliases?.[action];
if (!aliases?.deliveryTargetAliases) return false;
const deliveryAliases = new Set(aliases.deliveryTargetAliases);
return aliases.aliases.some((alias) => {
if (deliveryAliases.has(alias)) return false;
const value = params[alias];
if (typeof value === "string") return Boolean(normalizeOptionalString(value));
return typeof value === "number" && Number.isFinite(value);
});
}
/**
* Reports whether an action normally needs a destination target.
*/
function actionRequiresTarget(action) {
return MESSAGE_ACTION_TARGET_MODE[action] !== "none";
}
/**
* Detects whether an action invocation already carries a usable target.
*/
function actionHasTarget(action, params, options) {
if (normalizeOptionalString(params.to) ?? "") return true;
if (normalizeOptionalString(params.channelId) ?? "") return true;
const specs = listActionTargetAliasSpecs(action, params, options?.channel);
if (specs.length === 0) return false;
return specs.some((spec) => spec.aliases.some((alias) => {
const value = params[alias];
if (typeof value === "string") return Boolean(normalizeOptionalString(value));
if (typeof value === "number") return Number.isFinite(value);
return false;
}));
}
//#endregion
//#region src/infra/outbound/channel-target.ts
/** Human-readable description for a single message-action destination. */
const CHANNEL_TARGET_DESCRIPTION = "Recipient/channel: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord/Slack/Mattermost <channelId|user:ID|channel:ID>, or iMessage handle/chat_id";
/** Human-readable description for repeated message-action destinations. */
const CHANNEL_TARGETS_DESCRIPTION = "Recipient/channel targets (same format as --target); accepts ids or names when the directory is available.";
/** Maps canonical `target` into the legacy field required by the action implementation. */
function applyTargetToParams(params) {
const target = normalizeOptionalString(params.args.target) ?? "";
const hasLegacyTo = hasNonEmptyString(params.args.to);
const hasLegacyChannelId = hasNonEmptyString(params.args.channelId);
const mode = MESSAGE_ACTION_TARGET_MODE[params.action] ?? "none";
if (mode !== "none") {
if (hasLegacyTo || hasLegacyChannelId) throw new Error("Use `target` instead of `to`/`channelId`.");
} else if (hasLegacyTo) throw new Error("Use `target` for actions that accept a destination.");
if (!target) return;
if (mode === "channelId") {
params.args.channelId = target;
return;
}
if (mode === "to") {
params.args.to = target;
return;
}
throw new Error(`Action ${params.action} does not accept a target.`);
}
//#endregion
export { actionHasTarget as a, hasPotentialPluginActionParam as c, actionHasResourceReference as i, CHANNEL_TARGET_DESCRIPTION as n, actionRequiresTarget as o, applyTargetToParams as r, resolveActionDeliveryTargetAlias as s, CHANNEL_TARGETS_DESCRIPTION as t };