mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 12:11:20 +00:00
185 lines
5.7 KiB
TypeScript
185 lines
5.7 KiB
TypeScript
import type { OpenClawConfig } from "../config/config.js";
|
|
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
|
import {
|
|
buildChannelKeyCandidates,
|
|
normalizeChannelSlug,
|
|
resolveChannelEntryMatchWithFallback,
|
|
type ChannelMatchSource,
|
|
} from "./channel-config.js";
|
|
import { normalizeChatType } from "./chat-type.js";
|
|
import { getChannelPlugin } from "./plugins/registry.js";
|
|
import {
|
|
resolveSessionConversation,
|
|
resolveSessionConversationRef,
|
|
} from "./plugins/session-conversation.js";
|
|
|
|
export type ChannelModelOverride = {
|
|
channel: string;
|
|
model: string;
|
|
matchKey?: string;
|
|
matchSource?: ChannelMatchSource;
|
|
};
|
|
|
|
type ChannelModelByChannelConfig = Record<string, Record<string, string>>;
|
|
|
|
type ChannelModelOverrideParams = {
|
|
cfg: OpenClawConfig;
|
|
channel?: string | null;
|
|
groupId?: string | null;
|
|
groupChatType?: string | null;
|
|
groupChannel?: string | null;
|
|
groupSubject?: string | null;
|
|
parentSessionKey?: string | null;
|
|
};
|
|
|
|
function resolveProviderEntry(
|
|
modelByChannel: ChannelModelByChannelConfig | undefined,
|
|
channel: string,
|
|
): Record<string, string> | undefined {
|
|
const normalized = normalizeMessageChannel(channel) ?? channel.trim().toLowerCase();
|
|
return (
|
|
modelByChannel?.[normalized] ??
|
|
modelByChannel?.[
|
|
Object.keys(modelByChannel ?? {}).find((key) => {
|
|
const normalizedKey = normalizeMessageChannel(key) ?? key.trim().toLowerCase();
|
|
return normalizedKey === normalized;
|
|
}) ?? ""
|
|
]
|
|
);
|
|
}
|
|
|
|
function buildChannelCandidates(
|
|
params: Pick<
|
|
ChannelModelOverrideParams,
|
|
"channel" | "groupId" | "groupChatType" | "groupChannel" | "groupSubject" | "parentSessionKey"
|
|
>,
|
|
): { keys: string[]; parentKeys: string[] } {
|
|
const normalizedChannel =
|
|
normalizeMessageChannel(params.channel ?? "") ?? params.channel?.trim().toLowerCase();
|
|
const groupId = params.groupId?.trim();
|
|
const sessionConversation = resolveSessionConversationRef(params.parentSessionKey);
|
|
const feishuParentOverrideFallbacks =
|
|
normalizedChannel === "feishu"
|
|
? buildFeishuParentOverrideCandidates(sessionConversation?.rawId)
|
|
: [];
|
|
const parentOverrideFallbacks =
|
|
(normalizedChannel
|
|
? getChannelPlugin(
|
|
normalizedChannel,
|
|
)?.conversationBindings?.buildModelOverrideParentCandidates?.({
|
|
parentConversationId: sessionConversation?.rawId,
|
|
})
|
|
: null) ?? [];
|
|
const groupConversationKind =
|
|
normalizeChatType(params.groupChatType ?? undefined) === "channel"
|
|
? "channel"
|
|
: sessionConversation?.kind === "channel"
|
|
? "channel"
|
|
: "group";
|
|
const groupConversation = resolveSessionConversation({
|
|
channel: normalizedChannel ?? "",
|
|
kind: groupConversationKind,
|
|
rawId: groupId ?? "",
|
|
});
|
|
const groupChannel = params.groupChannel?.trim();
|
|
const groupSubject = params.groupSubject?.trim();
|
|
const channelBare = groupChannel ? groupChannel.replace(/^#/, "") : undefined;
|
|
const subjectBare = groupSubject ? groupSubject.replace(/^#/, "") : undefined;
|
|
const channelSlug = channelBare ? normalizeChannelSlug(channelBare) : undefined;
|
|
const subjectSlug = subjectBare ? normalizeChannelSlug(subjectBare) : undefined;
|
|
|
|
return {
|
|
keys: buildChannelKeyCandidates(
|
|
groupId,
|
|
sessionConversation?.rawId,
|
|
...(groupConversation?.parentConversationCandidates ?? []),
|
|
...(sessionConversation?.parentConversationCandidates ?? []),
|
|
...feishuParentOverrideFallbacks,
|
|
...parentOverrideFallbacks,
|
|
),
|
|
parentKeys: buildChannelKeyCandidates(
|
|
groupChannel,
|
|
channelBare,
|
|
channelSlug,
|
|
groupSubject,
|
|
subjectBare,
|
|
subjectSlug,
|
|
),
|
|
};
|
|
}
|
|
|
|
function buildFeishuParentOverrideCandidates(rawId: string | undefined): string[] {
|
|
const value = rawId?.trim();
|
|
if (!value) {
|
|
return [];
|
|
}
|
|
const topicSenderMatch = value.match(/^(.+):topic:([^:]+):sender:([^:]+)$/i);
|
|
if (topicSenderMatch) {
|
|
const chatId = topicSenderMatch[1]?.trim().toLowerCase();
|
|
const topicId = topicSenderMatch[2]?.trim().toLowerCase();
|
|
return [`${chatId}:topic:${topicId}`, chatId].filter((entry): entry is string =>
|
|
Boolean(entry),
|
|
);
|
|
}
|
|
const topicMatch = value.match(/^(.+):topic:([^:]+)$/i);
|
|
if (topicMatch) {
|
|
const chatId = topicMatch[1]?.trim().toLowerCase();
|
|
const topicId = topicMatch[2]?.trim().toLowerCase();
|
|
return [`${chatId}:topic:${topicId}`, chatId].filter((entry): entry is string =>
|
|
Boolean(entry),
|
|
);
|
|
}
|
|
const senderMatch = value.match(/^(.+):sender:([^:]+)$/i);
|
|
if (senderMatch) {
|
|
const chatId = senderMatch[1]?.trim().toLowerCase();
|
|
return chatId ? [chatId] : [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
export function resolveChannelModelOverride(
|
|
params: ChannelModelOverrideParams,
|
|
): ChannelModelOverride | null {
|
|
const channel = params.channel?.trim();
|
|
if (!channel) {
|
|
return null;
|
|
}
|
|
const modelByChannel = params.cfg.channels?.modelByChannel as
|
|
| ChannelModelByChannelConfig
|
|
| undefined;
|
|
if (!modelByChannel) {
|
|
return null;
|
|
}
|
|
const providerEntries = resolveProviderEntry(modelByChannel, channel);
|
|
if (!providerEntries) {
|
|
return null;
|
|
}
|
|
|
|
const { keys, parentKeys } = buildChannelCandidates(params);
|
|
if (keys.length === 0 && parentKeys.length === 0) {
|
|
return null;
|
|
}
|
|
const match = resolveChannelEntryMatchWithFallback({
|
|
entries: providerEntries,
|
|
keys,
|
|
parentKeys,
|
|
wildcardKey: "*",
|
|
normalizeKey: (value) => value.trim().toLowerCase(),
|
|
});
|
|
const raw = match.entry ?? match.wildcardEntry;
|
|
if (typeof raw !== "string") {
|
|
return null;
|
|
}
|
|
const model = raw.trim();
|
|
if (!model) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
channel: normalizeMessageChannel(channel) ?? channel.trim().toLowerCase(),
|
|
model,
|
|
matchKey: match.matchKey,
|
|
matchSource: match.matchSource,
|
|
};
|
|
}
|