openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
314 lines (313 loc) • 13.3 kB
JavaScript
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { n as loadBundledPluginPublicArtifactModuleSync } from "./public-surface-loader-i-BsDNoA.js";
import { t as normalizeAnyChannelId } from "./registry-normalize-CY8V_nbx.js";
import "./registry-COqNvmCg.js";
import { r as getLoadedChannelPluginForRead } from "./registry-loaded-Bh7xuMJh.js";
import { f as stringifyRouteThreadId } from "./channel-route--2in3Ckg.js";
import { i as stripTargetTopicSuffix, n as stripOutboundTargetKindPrefix, r as stripTargetProviderPrefix, t as resolveTargetPrefixedChannel } from "./channel-target-prefix-D7Ut6oKe.js";
import { r as normalizeConversationTargetRef } from "./session-binding-normalization-DA3Q-Bzn.js";
//#region src/infra/outbound/conversation-id.ts
function resolveExplicitConversationTargetId(target) {
for (const prefix of [
"channel:",
"conversation:",
"group:",
"room:",
"dm:"
]) if (normalizeLowercaseStringOrEmpty(target).startsWith(prefix)) return normalizeOptionalString(target.slice(prefix.length));
}
/**
* Chooses the best conversation id from an explicit thread id or outbound targets.
*/
function resolveConversationIdFromTargets(params) {
const threadId = stringifyRouteThreadId(params.threadId);
if (threadId) return threadId;
for (const rawTarget of params.targets) {
const target = normalizeOptionalString(rawTarget);
if (!target) continue;
const explicitConversationId = resolveExplicitConversationTargetId(target);
if (explicitConversationId) return explicitConversationId;
if (target.includes(":") && explicitConversationId === void 0) continue;
const mentionMatch = target.match(/^<#(\d+)>$/);
if (mentionMatch?.[1]) return mentionMatch[1];
if (/^\d{6,}$/.test(target)) return target;
}
}
//#endregion
//#region src/channels/plugins/thread-binding-api.ts
/**
* Bundled channel thread-binding public artifact loader.
*
* Reads lightweight thread placement and inbound conversation hooks without full plugin loading.
*/
const THREAD_BINDING_API_ARTIFACT_BASENAME = "thread-binding-api.js";
const MISSING_PUBLIC_SURFACE_PREFIX = "Unable to resolve bundled plugin public surface ";
function loadBundledChannelThreadBindingApi(channelId) {
const cacheKey = channelId.trim();
try {
return loadBundledPluginPublicArtifactModuleSync({
dirName: cacheKey,
artifactBasename: THREAD_BINDING_API_ARTIFACT_BASENAME
});
} catch (error) {
if (error instanceof Error && error.message.startsWith(MISSING_PUBLIC_SURFACE_PREFIX)) return;
throw error;
}
}
function normalizeThreadBindingPlacement(value) {
const normalized = normalizeOptionalString(typeof value === "string" ? value : void 0);
return normalized === "current" || normalized === "child" ? normalized : void 0;
}
/**
* Resolves the default top-level thread-binding placement for a bundled channel.
*/
function resolveBundledChannelThreadBindingDefaultPlacement(channelId) {
return normalizeThreadBindingPlacement(loadBundledChannelThreadBindingApi(channelId)?.defaultTopLevelPlacement);
}
/**
* Resolves inbound conversation refs from a bundled channel thread-binding artifact.
*/
function resolveBundledChannelThreadBindingInboundConversation(params) {
const api = loadBundledChannelThreadBindingApi(params.channelId);
if (typeof api?.resolveInboundConversation !== "function") return;
return api.resolveInboundConversation({
from: params.from,
to: params.to,
conversationId: params.conversationId,
threadId: params.threadId,
threadParentId: params.threadParentId,
isGroup: params.isGroup
});
}
//#endregion
//#region src/channels/conversation-resolution.ts
/**
* Canonical conversation resolution for command and inbound channel flows.
* This module turns channel targets, thread ids, aliases, and plugin hooks into stable binding ids.
*/
const CANONICAL_TARGET_PREFIXES = ["user:", "spaces/"];
function resolveChannelId(raw) {
const normalizedRaw = normalizeOptionalString(raw);
if (!normalizedRaw) return null;
return normalizeAnyChannelId(normalizedRaw) ?? normalizeOptionalLowercaseString(normalizedRaw) ?? null;
}
function shouldDefaultParentConversationToSelf(plugin) {
return plugin?.bindings?.selfParentConversationByDefault === true;
}
function normalizeResolutionTarget(params) {
const conversationId = normalizeOptionalString(params.conversation?.conversationId);
if (!conversationId) return null;
const parentConversationId = normalizeOptionalString(params.conversation?.parentConversationId);
const defaultParentToSelf = shouldDefaultParentConversationToSelf(params.plugin) && !params.threadId && !parentConversationId;
const normalized = normalizeConversationTargetRef({
conversationId,
parentConversationId: defaultParentToSelf ? conversationId : parentConversationId
});
const normalizedParentConversationId = defaultParentToSelf ? normalized.conversationId : normalized.parentConversationId;
return {
channel: params.channel,
accountId: params.accountId,
conversationId: normalized.conversationId,
...normalizedParentConversationId ? { parentConversationId: normalizedParentConversationId } : {},
...params.threadId ? { threadId: params.threadId } : {}
};
}
function resolveBindingAccountId(params) {
return normalizeOptionalString(params.rawAccountId) || normalizeOptionalString(params.plugin?.config.defaultAccountId?.(params.cfg)) || "default";
}
function resolveFallbackConversationTargetId(params) {
const { allowNumericTopicShorthand = false } = params;
const target = normalizeOptionalString(params.rawTarget);
if (!target) return;
const withoutKind = stripOutboundTargetKindPrefix(target);
const withoutTopic = params.preserveExplicitTopicSuffix && /:topic:/iu.test(withoutKind) ? withoutKind : stripTargetTopicSuffix(withoutKind, { allowNumericShorthand: allowNumericTopicShorthand });
return resolveConversationIdFromTargets({ targets: [withoutTopic] }) ?? (withoutTopic !== target ? withoutTopic : void 0) ?? resolveConversationIdFromTargets({ targets: [target] });
}
function resolveChannelTargetId(params) {
const target = normalizeOptionalString(params.target);
if (!target) return;
const messaging = params.plugin?.messaging;
const lower = normalizeLowercaseStringOrEmpty(target);
const channelPrefix = `${params.channel}:`;
if (lower.startsWith(channelPrefix)) return resolveChannelTargetId({
...params,
target: target.slice(channelPrefix.length)
});
if (CANONICAL_TARGET_PREFIXES.some((prefix) => lower.startsWith(prefix))) return target;
const prefixedChannel = resolveTargetPrefixedChannel(target);
if (!prefixedChannel || prefixedChannel !== params.channel) {
const explicitConversationId = resolveFallbackConversationTargetId({
rawTarget: target,
allowNumericTopicShorthand: messaging?.numericTopicShorthand === true,
preserveExplicitTopicSuffix: params.preserveExplicitTopicSuffix
});
if (explicitConversationId) return explicitConversationId;
}
const normalizedTarget = normalizeOptionalString(messaging?.normalizeTarget?.(target));
if (normalizedTarget) {
const withoutProvider = stripTargetProviderPrefix(normalizedTarget, params.channel);
return resolveFallbackConversationTargetId({
rawTarget: withoutProvider,
allowNumericTopicShorthand: messaging?.numericTopicShorthand === true,
preserveExplicitTopicSuffix: params.preserveExplicitTopicSuffix
}) || withoutProvider || normalizedTarget;
}
return target;
}
function buildThreadingContext(params) {
const to = normalizeOptionalString(params.originatingTo) ?? normalizeOptionalString(params.fallbackTo);
return {
...to ? { To: to } : {},
...params.from ? { From: params.from } : {},
...params.chatType ? { ChatType: params.chatType } : {},
...params.threadId ? { MessageThreadId: params.threadId } : {},
...params.nativeChannelId ? { NativeChannelId: params.nativeChannelId } : {}
};
}
/**
* Resolves whether top-level bindings default to the current conversation or a child thread.
*/
function resolveChannelDefaultBindingPlacement(rawChannel) {
const channel = resolveChannelId(rawChannel);
if (!channel) return;
return getLoadedChannelPluginForRead(channel)?.conversationBindings?.defaultTopLevelPlacement ?? resolveBundledChannelThreadBindingDefaultPlacement(channel);
}
/**
* Resolves command context into a canonical channel/account/conversation tuple.
*/
function resolveCommandConversationResolution(params) {
const channel = resolveChannelId(params.channel);
if (!channel) return null;
const plugin = params.plugin ?? getLoadedChannelPluginForRead(channel);
const accountId = resolveBindingAccountId({
rawAccountId: params.accountId,
plugin,
cfg: params.cfg
});
const threadId = stringifyRouteThreadId(params.threadId);
const commandParams = {
accountId,
threadId,
threadParentId: normalizeOptionalString(params.threadParentId),
senderId: normalizeOptionalString(params.senderId),
sessionKey: normalizeOptionalString(params.sessionKey),
parentSessionKey: normalizeOptionalString(params.parentSessionKey),
from: normalizeOptionalString(params.from),
chatType: normalizeOptionalString(params.chatType),
originatingTo: params.originatingTo ?? void 0,
commandTo: params.commandTo ?? void 0,
fallbackTo: params.fallbackTo ?? void 0
};
const resolvedByProvider = plugin?.bindings?.resolveCommandConversation?.(commandParams);
const providerResolution = normalizeResolutionTarget({
channel,
accountId,
conversation: resolvedByProvider,
threadId,
plugin
});
if (providerResolution) return providerResolution;
const focusedBinding = plugin?.threading?.resolveFocusedBinding?.({
cfg: params.cfg,
accountId,
context: buildThreadingContext({
fallbackTo: params.fallbackTo ?? void 0,
originatingTo: params.originatingTo ?? void 0,
threadId,
from: normalizeOptionalString(params.from),
chatType: normalizeOptionalString(params.chatType),
nativeChannelId: normalizeOptionalString(params.nativeChannelId)
})
});
const focusedResolution = normalizeResolutionTarget({
channel,
accountId,
conversation: focusedBinding,
threadId,
plugin
});
if (focusedResolution) return focusedResolution;
const resolveTarget = (target) => resolveChannelTargetId({
channel,
plugin,
target
});
const baseConversationId = resolveTarget(params.originatingTo) ?? resolveTarget(params.commandTo) ?? resolveTarget(params.fallbackTo);
const parentConversationId = resolveTarget(params.threadParentId) ?? (threadId && baseConversationId && baseConversationId !== threadId ? baseConversationId : void 0);
const conversationId = threadId || baseConversationId;
if (!conversationId) return null;
return normalizeResolutionTarget({
channel,
accountId,
conversation: {
conversationId,
parentConversationId
},
threadId,
plugin
});
}
/**
* Resolves inbound message context into the canonical binding conversation tuple.
*/
function resolveInboundConversationResolution(params) {
const channel = resolveChannelId(params.channel);
if (!channel) return null;
const plugin = getLoadedChannelPluginForRead(channel);
const accountId = resolveBindingAccountId({
rawAccountId: params.accountId,
plugin,
cfg: params.cfg
});
const threadId = stringifyRouteThreadId(params.threadId);
const resolverParams = {
from: normalizeOptionalString(params.from),
to: normalizeOptionalString(params.to),
conversationId: normalizeOptionalString(params.conversationId) ?? normalizeOptionalString(params.groupId) ?? normalizeOptionalString(params.to),
threadId,
threadParentId: stringifyRouteThreadId(params.threadParentId),
isGroup: params.isGroup ?? true
};
const providerConversation = plugin?.messaging?.resolveInboundConversation?.(resolverParams);
const providerResolution = normalizeResolutionTarget({
channel,
accountId,
conversation: providerConversation,
threadId,
plugin
});
if (providerResolution || providerConversation === null) return providerResolution;
const artifactConversation = resolveBundledChannelThreadBindingInboundConversation({
channelId: channel,
...resolverParams
});
const artifactResolution = normalizeResolutionTarget({
channel,
accountId,
conversation: artifactConversation,
threadId,
plugin
});
if (artifactResolution || artifactConversation === null) return artifactResolution;
const resolveTarget = (target) => resolveChannelTargetId({
channel,
plugin,
target,
preserveExplicitTopicSuffix: threadId == null
});
const parentConversationId = resolveTarget(params.threadParentId == null ? void 0 : String(params.threadParentId)) ?? resolveTarget(params.to) ?? resolveTarget(params.conversationId) ?? resolveTarget(params.groupId);
const genericConversationId = threadId ?? resolveTarget(params.conversationId) ?? resolveTarget(params.groupId) ?? parentConversationId;
if (!genericConversationId) return null;
return normalizeResolutionTarget({
channel,
accountId,
conversation: {
conversationId: genericConversationId,
parentConversationId: threadId != null ? parentConversationId : void 0
},
threadId,
plugin
});
}
//#endregion
export { resolveConversationIdFromTargets as i, resolveCommandConversationResolution as n, resolveInboundConversationResolution as r, resolveChannelDefaultBindingPlacement as t };