mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:30:42 +00:00
refactor: move Feishu model override parsing to plugin
This commit is contained in:
18
extensions/feishu/src/conversation-id.test.ts
Normal file
18
extensions/feishu/src/conversation-id.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildFeishuModelOverrideParentCandidates } from "./conversation-id.js";
|
||||
|
||||
describe("buildFeishuModelOverrideParentCandidates", () => {
|
||||
it("returns topic and chat fallback ids for sender-scoped topics", () => {
|
||||
expect(
|
||||
buildFeishuModelOverrideParentCandidates(
|
||||
"oc_group_chat:Topic:om_topic_root:Sender:ou_topic_user",
|
||||
),
|
||||
).toEqual(["oc_group_chat:topic:om_topic_root", "oc_group_chat"]);
|
||||
});
|
||||
|
||||
it("returns chat fallback ids for sender-scoped chats", () => {
|
||||
expect(buildFeishuModelOverrideParentCandidates("oc_group_chat:sender:ou_topic_user")).toEqual([
|
||||
"oc_group_chat",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { createSessionConversationTestRegistry } from "../test-utils/session-conversation-registry.js";
|
||||
import { resolveChannelModelOverride } from "./model-overrides.js";
|
||||
@@ -64,48 +64,6 @@ describe("resolveChannelModelOverride", () => {
|
||||
},
|
||||
expected: { model: "demo-provider/demo-parent-model", matchKey: "123" },
|
||||
},
|
||||
{
|
||||
name: "preserves feishu topic ids for direct matches",
|
||||
input: {
|
||||
cfg: {
|
||||
channels: {
|
||||
modelByChannel: {
|
||||
feishu: {
|
||||
"oc_group_chat:topic:om_topic_root": "demo-provider/demo-feishu-topic-model",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
channel: "feishu",
|
||||
groupId: "oc_group_chat:topic:om_topic_root",
|
||||
},
|
||||
expected: {
|
||||
model: "demo-provider/demo-feishu-topic-model",
|
||||
matchKey: "oc_group_chat:topic:om_topic_root",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preserves feishu topic ids when falling back from parent session key",
|
||||
input: {
|
||||
cfg: {
|
||||
channels: {
|
||||
modelByChannel: {
|
||||
feishu: {
|
||||
"oc_group_chat:topic:om_topic_root": "demo-provider/demo-feishu-topic-model",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
channel: "feishu",
|
||||
groupId: "unrelated",
|
||||
parentSessionKey:
|
||||
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
},
|
||||
expected: {
|
||||
model: "demo-provider/demo-feishu-topic-model",
|
||||
matchKey: "oc_group_chat:topic:om_topic_root",
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ input, expected }) => {
|
||||
const resolved = resolveChannelModelOverride(input);
|
||||
expect(resolved?.model).toBe(expected.model);
|
||||
@@ -168,48 +126,58 @@ describe("resolveChannelModelOverride", () => {
|
||||
expect(resolved?.matchKey).toBe("thread-parent");
|
||||
});
|
||||
|
||||
it("keeps bundled Feishu parent fallback matching before registry bootstrap", () => {
|
||||
resetPluginRuntimeStateForTest();
|
||||
it("uses plugin-owned parent fallback candidates", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "scoped-chat",
|
||||
source: "test",
|
||||
plugin: {
|
||||
id: "scoped-chat",
|
||||
meta: {
|
||||
id: "scoped-chat",
|
||||
label: "Scoped Chat",
|
||||
selectionLabel: "Scoped Chat",
|
||||
docsPath: "/channels/scoped-chat",
|
||||
blurb: "test stub.",
|
||||
},
|
||||
capabilities: { chatTypes: ["group"] },
|
||||
conversationBindings: {
|
||||
buildModelOverrideParentCandidates: ({
|
||||
parentConversationId,
|
||||
}: {
|
||||
parentConversationId?: string | null;
|
||||
}) =>
|
||||
parentConversationId === "room:topic:thread:sender:user"
|
||||
? ["room:topic:thread", "room"]
|
||||
: [],
|
||||
},
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const resolved = resolveChannelModelOverride({
|
||||
cfg: {
|
||||
channels: {
|
||||
modelByChannel: {
|
||||
feishu: {
|
||||
"oc_group_chat:topic:om_topic_root": "demo-provider/demo-feishu-topic-model",
|
||||
"scoped-chat": {
|
||||
"room:topic:thread": "demo-provider/demo-scoped-model",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
channel: "feishu",
|
||||
channel: "scoped-chat",
|
||||
groupId: "unrelated",
|
||||
parentSessionKey:
|
||||
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
parentSessionKey: "agent:main:scoped-chat:group:room:topic:thread:sender:user",
|
||||
});
|
||||
|
||||
expect(resolved?.model).toBe("demo-provider/demo-feishu-topic-model");
|
||||
expect(resolved?.matchKey).toBe("oc_group_chat:topic:om_topic_root");
|
||||
});
|
||||
|
||||
it("keeps mixed-case Feishu scoped markers when matching parent session fallbacks", () => {
|
||||
const resolved = resolveChannelModelOverride({
|
||||
cfg: {
|
||||
channels: {
|
||||
modelByChannel: {
|
||||
feishu: {
|
||||
"oc_group_chat:topic:om_topic_root": "demo-provider/demo-feishu-topic-model",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
channel: "feishu",
|
||||
groupId: "unrelated",
|
||||
parentSessionKey:
|
||||
"agent:main:feishu:group:oc_group_chat:Topic:om_topic_root:Sender:ou_topic_user",
|
||||
});
|
||||
|
||||
expect(resolved?.model).toBe("demo-provider/demo-feishu-topic-model");
|
||||
expect(resolved?.matchKey).toBe("oc_group_chat:topic:om_topic_root");
|
||||
expect(resolved?.model).toBe("demo-provider/demo-scoped-model");
|
||||
expect(resolved?.matchKey).toBe("room:topic:thread");
|
||||
});
|
||||
|
||||
it("prefers parent conversation ids over channel-name fallbacks", () => {
|
||||
|
||||
@@ -69,10 +69,6 @@ function buildChannelCandidates(
|
||||
normalizeOptionalLowercaseString(params.channel);
|
||||
const groupId = normalizeOptionalString(params.groupId);
|
||||
const sessionConversation = resolveSessionConversationRef(params.parentSessionKey);
|
||||
const feishuParentOverrideFallbacks =
|
||||
normalizedChannel === "feishu"
|
||||
? buildFeishuParentOverrideCandidates(sessionConversation?.rawId)
|
||||
: [];
|
||||
const parentOverrideFallbacks =
|
||||
(normalizedChannel
|
||||
? getChannelPlugin(
|
||||
@@ -105,7 +101,6 @@ function buildChannelCandidates(
|
||||
sessionConversation?.rawId,
|
||||
...(groupConversation?.parentConversationCandidates ?? []),
|
||||
...(sessionConversation?.parentConversationCandidates ?? []),
|
||||
...feishuParentOverrideFallbacks,
|
||||
...parentOverrideFallbacks,
|
||||
),
|
||||
parentKeys: buildChannelKeyCandidates(
|
||||
@@ -128,48 +123,15 @@ function buildGenericParentOverrideCandidates(sessionKey: string | null | undefi
|
||||
return buildChannelKeyCandidates(threadId ? baseSessionKey : raw.rawId);
|
||||
}
|
||||
|
||||
function buildFeishuParentOverrideCandidates(rawId: string | undefined): string[] {
|
||||
const value = normalizeOptionalString(rawId);
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
const topicSenderMatch = value.match(/^(.+):topic:([^:]+):sender:([^:]+)$/i);
|
||||
if (topicSenderMatch) {
|
||||
const chatId = normalizeOptionalLowercaseString(topicSenderMatch[1]);
|
||||
const topicId = normalizeOptionalLowercaseString(topicSenderMatch[2]);
|
||||
return [`${chatId}:topic:${topicId}`, chatId].filter((entry): entry is string =>
|
||||
Boolean(entry),
|
||||
);
|
||||
}
|
||||
const topicMatch = value.match(/^(.+):topic:([^:]+)$/i);
|
||||
if (topicMatch) {
|
||||
const chatId = normalizeOptionalLowercaseString(topicMatch[1]);
|
||||
const topicId = normalizeOptionalLowercaseString(topicMatch[2]);
|
||||
return [`${chatId}:topic:${topicId}`, chatId].filter((entry): entry is string =>
|
||||
Boolean(entry),
|
||||
);
|
||||
}
|
||||
const senderMatch = value.match(/^(.+):sender:([^:]+)$/i);
|
||||
if (senderMatch) {
|
||||
const chatId = normalizeOptionalLowercaseString(senderMatch[1]);
|
||||
return chatId ? [chatId] : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function resolveDirectChannelModelMatch(params: {
|
||||
channel: string;
|
||||
providerEntries: Record<string, string>;
|
||||
groupId?: string | null;
|
||||
parentSessionKey?: string | null;
|
||||
}): { model: string; matchKey?: string; matchSource?: ChannelMatchSource } | null {
|
||||
const rawParent = parseRawSessionConversationRef(params.parentSessionKey);
|
||||
const directKeys = buildChannelKeyCandidates(
|
||||
params.groupId,
|
||||
...buildGenericParentOverrideCandidates(params.parentSessionKey),
|
||||
...(normalizeOptionalLowercaseString(params.channel) === "feishu"
|
||||
? buildFeishuParentOverrideCandidates(rawParent?.rawId)
|
||||
: []),
|
||||
);
|
||||
if (directKeys.length === 0) {
|
||||
return null;
|
||||
|
||||
@@ -54,6 +54,10 @@ const CORE_SECRET_SURFACE_GUARDS = [
|
||||
path: "src/gateway/channel-health-policy.ts",
|
||||
forbiddenPatterns: [/\btelegram\b/],
|
||||
},
|
||||
{
|
||||
path: "src/channels/model-overrides.ts",
|
||||
forbiddenPatterns: [/\bfeishu\b/],
|
||||
},
|
||||
{
|
||||
path: "src/media-understanding/defaults.ts",
|
||||
forbiddenPatterns: [
|
||||
|
||||
Reference in New Issue
Block a user