refactor: move Feishu model override parsing to plugin

This commit is contained in:
Peter Steinberger
2026-04-22 05:35:54 +01:00
parent 7189b49f81
commit bdcbb6b49d
4 changed files with 63 additions and 111 deletions

View 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",
]);
});
});

View File

@@ -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", () => {

View File

@@ -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;

View File

@@ -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: [