mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 03:11:10 +00:00
217 lines
6.5 KiB
TypeScript
217 lines
6.5 KiB
TypeScript
import { normalizeConversationText } from "../acp/conversation-id.js";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import { resolveConversationIdFromTargets } from "../infra/outbound/conversation-id.js";
|
|
import { getActivePluginChannelRegistry } from "../plugins/runtime.js";
|
|
import { parseExplicitTargetForChannel } from "./plugins/target-parsing.js";
|
|
import type { ChannelPlugin } from "./plugins/types.js";
|
|
import { normalizeAnyChannelId, normalizeChannelId } from "./registry.js";
|
|
|
|
export type ConversationBindingContext = {
|
|
channel: string;
|
|
accountId: string;
|
|
conversationId: string;
|
|
parentConversationId?: string;
|
|
threadId?: string;
|
|
};
|
|
|
|
export type ResolveConversationBindingContextInput = {
|
|
cfg: OpenClawConfig;
|
|
channel?: string | null;
|
|
accountId?: string | null;
|
|
chatType?: string | null;
|
|
threadId?: string | number | null;
|
|
threadParentId?: string | null;
|
|
senderId?: string | null;
|
|
sessionKey?: string | null;
|
|
parentSessionKey?: string | null;
|
|
originatingTo?: string | null;
|
|
commandTo?: string | null;
|
|
fallbackTo?: string | null;
|
|
from?: string | null;
|
|
nativeChannelId?: string | null;
|
|
};
|
|
|
|
const CANONICAL_TARGET_PREFIXES = [
|
|
"user:",
|
|
"channel:",
|
|
"conversation:",
|
|
"group:",
|
|
"room:",
|
|
"dm:",
|
|
"spaces/",
|
|
] as const;
|
|
|
|
function normalizeText(value: unknown): string | undefined {
|
|
const normalized = normalizeConversationText(value);
|
|
return normalized || undefined;
|
|
}
|
|
|
|
function getLoadedChannelPlugin(rawChannel: string): ChannelPlugin | undefined {
|
|
const normalized = normalizeAnyChannelId(rawChannel) ?? normalizeText(rawChannel);
|
|
if (!normalized) {
|
|
return undefined;
|
|
}
|
|
return getActivePluginChannelRegistry()?.channels.find((entry) => entry.plugin.id === normalized)
|
|
?.plugin;
|
|
}
|
|
|
|
function resolveChannelTargetId(params: {
|
|
channel: string;
|
|
target?: string | null;
|
|
}): string | undefined {
|
|
const target = normalizeText(params.target);
|
|
if (!target) {
|
|
return undefined;
|
|
}
|
|
|
|
const lower = target.toLowerCase();
|
|
const channelPrefix = `${params.channel}:`;
|
|
if (lower.startsWith(channelPrefix)) {
|
|
return resolveChannelTargetId({
|
|
channel: params.channel,
|
|
target: target.slice(channelPrefix.length),
|
|
});
|
|
}
|
|
if (CANONICAL_TARGET_PREFIXES.some((prefix) => lower.startsWith(prefix))) {
|
|
return target;
|
|
}
|
|
|
|
const parsed = parseExplicitTargetForChannel(params.channel, target);
|
|
const parsedTarget = normalizeText(parsed?.to);
|
|
if (parsedTarget) {
|
|
return (
|
|
resolveConversationIdFromTargets({
|
|
targets: [parsedTarget],
|
|
}) ?? parsedTarget
|
|
);
|
|
}
|
|
|
|
const explicitConversationId = resolveConversationIdFromTargets({
|
|
targets: [target],
|
|
});
|
|
return explicitConversationId ?? target;
|
|
}
|
|
|
|
function buildThreadingContext(params: {
|
|
fallbackTo?: string;
|
|
originatingTo?: string;
|
|
threadId?: string;
|
|
from?: string;
|
|
chatType?: string;
|
|
nativeChannelId?: string;
|
|
}) {
|
|
const to = normalizeText(params.originatingTo) ?? normalizeText(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 } : {}),
|
|
};
|
|
}
|
|
|
|
export function resolveConversationBindingContext(
|
|
params: ResolveConversationBindingContextInput,
|
|
): ConversationBindingContext | null {
|
|
const channel =
|
|
normalizeAnyChannelId(params.channel) ??
|
|
normalizeChannelId(params.channel) ??
|
|
normalizeText(params.channel)?.toLowerCase();
|
|
if (!channel) {
|
|
return null;
|
|
}
|
|
const accountId = normalizeText(params.accountId) || "default";
|
|
const threadId = normalizeText(params.threadId != null ? String(params.threadId) : undefined);
|
|
const loadedPlugin = getLoadedChannelPlugin(channel);
|
|
|
|
const resolvedByProvider = loadedPlugin?.bindings?.resolveCommandConversation?.({
|
|
accountId,
|
|
threadId,
|
|
threadParentId: normalizeText(params.threadParentId),
|
|
senderId: normalizeText(params.senderId),
|
|
sessionKey: normalizeText(params.sessionKey),
|
|
parentSessionKey: normalizeText(params.parentSessionKey),
|
|
originatingTo: params.originatingTo ?? undefined,
|
|
commandTo: params.commandTo ?? undefined,
|
|
fallbackTo: params.fallbackTo ?? undefined,
|
|
});
|
|
if (resolvedByProvider?.conversationId) {
|
|
const resolvedParentConversationId =
|
|
channel === "telegram" && !threadId && !resolvedByProvider.parentConversationId
|
|
? resolvedByProvider.conversationId
|
|
: resolvedByProvider.parentConversationId;
|
|
return {
|
|
channel,
|
|
accountId,
|
|
conversationId: resolvedByProvider.conversationId,
|
|
...(resolvedParentConversationId
|
|
? { parentConversationId: resolvedParentConversationId }
|
|
: {}),
|
|
...(threadId ? { threadId } : {}),
|
|
};
|
|
}
|
|
|
|
const focusedBinding = loadedPlugin?.threading?.resolveFocusedBinding?.({
|
|
cfg: params.cfg,
|
|
accountId,
|
|
context: buildThreadingContext({
|
|
fallbackTo: params.fallbackTo ?? undefined,
|
|
originatingTo: params.originatingTo ?? undefined,
|
|
threadId,
|
|
from: normalizeText(params.from),
|
|
chatType: normalizeText(params.chatType),
|
|
nativeChannelId: normalizeText(params.nativeChannelId),
|
|
}),
|
|
});
|
|
if (focusedBinding?.conversationId) {
|
|
return {
|
|
channel,
|
|
accountId,
|
|
conversationId: focusedBinding.conversationId,
|
|
...(focusedBinding.parentConversationId
|
|
? { parentConversationId: focusedBinding.parentConversationId }
|
|
: {}),
|
|
...(threadId ? { threadId } : {}),
|
|
};
|
|
}
|
|
|
|
const baseConversationId =
|
|
resolveChannelTargetId({
|
|
channel,
|
|
target: params.originatingTo,
|
|
}) ??
|
|
resolveChannelTargetId({
|
|
channel,
|
|
target: params.commandTo,
|
|
}) ??
|
|
resolveChannelTargetId({
|
|
channel,
|
|
target: params.fallbackTo,
|
|
});
|
|
const parentConversationId =
|
|
resolveChannelTargetId({
|
|
channel,
|
|
target: params.threadParentId,
|
|
}) ??
|
|
(threadId && baseConversationId && baseConversationId !== threadId
|
|
? baseConversationId
|
|
: undefined);
|
|
const conversationId = threadId || baseConversationId;
|
|
if (!conversationId) {
|
|
return null;
|
|
}
|
|
const normalizedParentConversationId =
|
|
channel === "telegram" && !threadId && !parentConversationId
|
|
? conversationId
|
|
: parentConversationId;
|
|
return {
|
|
channel,
|
|
accountId,
|
|
conversationId,
|
|
...(normalizedParentConversationId
|
|
? { parentConversationId: normalizedParentConversationId }
|
|
: {}),
|
|
...(threadId ? { threadId } : {}),
|
|
};
|
|
}
|