




























@@ -1,5 +1,6 @@
11import fs from "node:fs";
22import path from "node:path";
3+import { splitTrailingAuthProfile } from "../agents/model-ref-profile.js";
34import { normalizeProviderId } from "../agents/provider-id.js";
45import type { OpenClawConfig } from "../config/types.openclaw.js";
56import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
@@ -29,11 +30,17 @@ export type BundledPluginRuntimeDepsManifest = {
2930enabledByDefault: boolean;
3031id?: string;
3132legacyPluginIds: string[];
33+modelSupport?: BundledPluginRuntimeDepsModelSupport;
3234providers: string[];
3335};
34363537export type BundledPluginRuntimeDepsManifestCache = Map<string, BundledPluginRuntimeDepsManifest>;
363839+type BundledPluginRuntimeDepsModelSupport = {
40+modelPatterns: string[];
41+modelPrefixes: string[];
42+};
43+3744function collectDeclaredMirroredRootRuntimeDepNames(packageJson: JsonObject): string[] {
3845const openclaw = packageJson.openclaw;
3946const bundle =
@@ -103,6 +110,7 @@ function readBundledPluginRuntimeDepsManifest(
103110const manifest = readRuntimeDepsJsonObject(path.join(pluginDir, "openclaw.plugin.json"));
104111const channels = manifest?.channels;
105112const legacyPluginIds = manifest?.legacyPluginIds;
113+const modelSupport = readBundledPluginRuntimeDepsModelSupport(manifest?.modelSupport);
106114const providers = manifest?.providers;
107115const runtimeDepsManifest = {
108116channels: Array.isArray(channels)
@@ -115,6 +123,7 @@ function readBundledPluginRuntimeDepsManifest(
115123(entry): entry is string => typeof entry === "string" && entry !== "",
116124)
117125 : [],
126+ ...(modelSupport ? { modelSupport } : {}),
118127providers: Array.isArray(providers)
119128 ? providers.filter((entry): entry is string => typeof entry === "string" && entry !== "")
120129 : [],
@@ -123,6 +132,27 @@ function readBundledPluginRuntimeDepsManifest(
123132return runtimeDepsManifest;
124133}
125134135+function readBundledPluginRuntimeDepsModelSupport(
136+value: unknown,
137+): BundledPluginRuntimeDepsModelSupport | undefined {
138+if (!isRecord(value)) {
139+return undefined;
140+}
141+const modelPatterns = readRuntimeDepsManifestStringList(value.modelPatterns);
142+const modelPrefixes = readRuntimeDepsManifestStringList(value.modelPrefixes);
143+if (modelPatterns.length === 0 && modelPrefixes.length === 0) {
144+return undefined;
145+}
146+return { modelPatterns, modelPrefixes };
147+}
148+149+function readRuntimeDepsManifestStringList(value: unknown): string[] {
150+if (!Array.isArray(value)) {
151+return [];
152+}
153+return value.filter((entry): entry is string => typeof entry === "string" && entry !== "");
154+}
155+126156const BUILT_IN_RUNTIME_DEPS_PLUGIN_ALIAS_FALLBACKS: ReadonlyArray<
127157readonly [alias: string, pluginId: string]
128158> = [
@@ -212,69 +242,176 @@ function isRecord(value: unknown): value is Record<string, unknown> {
212242return Boolean(value && typeof value === "object" && !Array.isArray(value));
213243}
214244215-function addConfiguredProviderId(providerIds: Set<string>, value: unknown): void {
245+type ConfiguredRuntimeDepsTargets = {
246+modelRefs: Set<string>;
247+providerIds: Set<string>;
248+};
249+250+function createConfiguredRuntimeDepsTargets(): ConfiguredRuntimeDepsTargets {
251+return {
252+modelRefs: new Set(),
253+providerIds: new Set(),
254+};
255+}
256+257+function addConfiguredProviderId(targets: ConfiguredRuntimeDepsTargets, value: unknown): void {
216258if (typeof value !== "string") {
217259return;
218260}
219261const normalized = normalizeProviderId(value);
220262if (normalized) {
221-providerIds.add(normalized);
263+targets.providerIds.add(normalized);
222264}
223265}
224266225-function addConfiguredProviderFromModelRef(providerIds: Set<string>, value: unknown): void {
267+function addConfiguredModelRef(targets: ConfiguredRuntimeDepsTargets, value: unknown): void {
226268if (typeof value !== "string") {
227269return;
228270}
229-const providerId = value.split("/", 1)[0]?.trim();
230-addConfiguredProviderId(providerIds, providerId);
271+const parsed = parseConfiguredModelRef(value);
272+if (!parsed) {
273+return;
274+}
275+if (parsed.providerId) {
276+targets.providerIds.add(parsed.providerId);
277+} else {
278+targets.modelRefs.add(parsed.modelId);
279+}
231280}
232281233-function addConfiguredProvidersFromModelConfig(providerIds: Set<string>, value: unknown): void {
282+function parseConfiguredModelRef(
283+value: string,
284+): { modelId: string; providerId?: string } | undefined {
285+const trimmed = value.trim();
286+if (!trimmed) {
287+return undefined;
288+}
289+const slash = trimmed.indexOf("/");
290+if (slash < 0) {
291+const modelId = splitTrailingAuthProfile(trimmed).model.trim();
292+return modelId ? { modelId } : undefined;
293+}
294+const providerId = normalizeProviderId(trimmed.slice(0, slash));
295+const modelId = splitTrailingAuthProfile(trimmed.slice(slash + 1)).model.trim();
296+if (!providerId || !modelId) {
297+return undefined;
298+}
299+return { providerId, modelId };
300+}
301+302+function addConfiguredModelsFromModelConfig(
303+targets: ConfiguredRuntimeDepsTargets,
304+value: unknown,
305+): void {
234306if (typeof value === "string") {
235-addConfiguredProviderFromModelRef(providerIds, value);
307+addConfiguredModelRef(targets, value);
236308return;
237309}
238310if (!isRecord(value)) {
239311return;
240312}
241-addConfiguredProviderFromModelRef(providerIds, value.primary);
313+addConfiguredModelRef(targets, value.primary);
242314if (Array.isArray(value.fallbacks)) {
243315for (const fallback of value.fallbacks) {
244-addConfiguredProviderFromModelRef(providerIds, fallback);
316+addConfiguredModelRef(targets, fallback);
245317}
246318}
247319}
248320249-function collectConfiguredProviderIds(config: OpenClawConfig): Set<string> {
250-const providerIds = new Set<string>();
321+function collectConfiguredRuntimeDepsTargets(config: OpenClawConfig): ConfiguredRuntimeDepsTargets {
322+const targets = createConfiguredRuntimeDepsTargets();
251323for (const providerId of Object.keys(config.models?.providers ?? {})) {
252-addConfiguredProviderId(providerIds, providerId);
324+addConfiguredProviderId(targets, providerId);
253325}
254326for (const profile of Object.values(config.auth?.profiles ?? {})) {
255-addConfiguredProviderId(providerIds, profile.provider);
327+addConfiguredProviderId(targets, profile.provider);
256328}
257329for (const providerId of Object.keys(config.auth?.order ?? {})) {
258-addConfiguredProviderId(providerIds, providerId);
330+addConfiguredProviderId(targets, providerId);
259331}
260332261333const defaults = config.agents?.defaults;
262-addConfiguredProvidersFromModelConfig(providerIds, defaults?.model);
263-addConfiguredProvidersFromModelConfig(providerIds, defaults?.imageModel);
264-addConfiguredProvidersFromModelConfig(providerIds, defaults?.imageGenerationModel);
265-addConfiguredProvidersFromModelConfig(providerIds, defaults?.videoGenerationModel);
266-addConfiguredProvidersFromModelConfig(providerIds, defaults?.musicGenerationModel);
267-addConfiguredProvidersFromModelConfig(providerIds, defaults?.pdfModel);
268-addConfiguredProvidersFromModelConfig(providerIds, defaults?.subagents?.model);
334+addConfiguredModelsFromModelConfig(targets, defaults?.model);
335+addConfiguredModelsFromModelConfig(targets, defaults?.imageModel);
336+addConfiguredModelsFromModelConfig(targets, defaults?.imageGenerationModel);
337+addConfiguredModelsFromModelConfig(targets, defaults?.videoGenerationModel);
338+addConfiguredModelsFromModelConfig(targets, defaults?.musicGenerationModel);
339+addConfiguredModelsFromModelConfig(targets, defaults?.pdfModel);
340+addConfiguredModelsFromModelConfig(targets, defaults?.subagents?.model);
269341for (const providerId of Object.keys(defaults?.models ?? {})) {
270-addConfiguredProviderFromModelRef(providerIds, providerId);
342+addConfiguredModelRef(targets, providerId);
271343}
272344273345for (const agent of config.agents?.list ?? []) {
274-addConfiguredProvidersFromModelConfig(providerIds, agent.model);
275-addConfiguredProvidersFromModelConfig(providerIds, agent.subagents?.model);
346+addConfiguredModelsFromModelConfig(targets, agent.model);
347+addConfiguredModelsFromModelConfig(targets, agent.subagents?.model);
276348}
277-return providerIds;
349+return targets;
350+}
351+352+function collectConfiguredProviderIds(config: OpenClawConfig): Set<string> {
353+return collectConfiguredRuntimeDepsTargets(config).providerIds;
354+}
355+356+function matchesBundledRuntimeDepsModelSupport(
357+manifest: BundledPluginRuntimeDepsManifest,
358+modelId: string,
359+kind: "pattern" | "prefix",
360+): boolean {
361+if (kind === "pattern") {
362+for (const patternSource of manifest.modelSupport?.modelPatterns ?? []) {
363+try {
364+if (new RegExp(patternSource, "u").test(modelId)) {
365+return true;
366+}
367+} catch {
368+continue;
369+}
370+}
371+return false;
372+}
373+return (manifest.modelSupport?.modelPrefixes ?? []).some((prefix) => modelId.startsWith(prefix));
374+}
375+376+export function resolveBundledRuntimeDepsConfiguredModelOwnerPluginIds(params: {
377+config: OpenClawConfig;
378+extensionsDir: string;
379+manifestCache?: BundledPluginRuntimeDepsManifestCache;
380+}): ReadonlySet<string> {
381+const targets = collectConfiguredRuntimeDepsTargets(params.config);
382+if (targets.modelRefs.size === 0 || !fs.existsSync(params.extensionsDir)) {
383+return new Set();
384+}
385+const plugins = fs
386+.readdirSync(params.extensionsDir, { withFileTypes: true })
387+.filter((entry) => entry.isDirectory())
388+.map((entry) => {
389+const pluginDir = path.join(params.extensionsDir, entry.name);
390+return {
391+pluginId: entry.name,
392+manifest: readBundledPluginRuntimeDepsManifest(pluginDir, params.manifestCache),
393+};
394+});
395+const pluginIds = new Set<string>();
396+for (const modelId of targets.modelRefs) {
397+const patternMatches = plugins.filter(({ manifest }) =>
398+matchesBundledRuntimeDepsModelSupport(manifest, modelId, "pattern"),
399+);
400+if (patternMatches.length === 1) {
401+pluginIds.add(patternMatches[0].pluginId);
402+continue;
403+}
404+if (patternMatches.length > 1) {
405+continue;
406+}
407+const prefixMatches = plugins.filter(({ manifest }) =>
408+matchesBundledRuntimeDepsModelSupport(manifest, modelId, "prefix"),
409+);
410+if (prefixMatches.length === 1) {
411+pluginIds.add(prefixMatches[0].pluginId);
412+}
413+}
414+return pluginIds;
278415}
279416280417function isBundledProviderConfiguredForRuntimeDeps(params: {
@@ -295,6 +432,7 @@ export function isBundledPluginConfiguredForRuntimeDeps(params: {
295432plugins: NormalizedPluginsConfig;
296433pluginId: string;
297434pluginDir: string;
435+configuredModelOwnerPluginIds?: ReadonlySet<string>;
298436includeConfiguredChannels?: boolean;
299437manifestCache?: BundledPluginRuntimeDepsManifestCache;
300438}): boolean {
@@ -363,6 +501,9 @@ export function isBundledPluginConfiguredForRuntimeDeps(params: {
363501if (hasConfiguredChannel) {
364502return true;
365503}
504+if (params.configuredModelOwnerPluginIds?.has(params.pluginId)) {
505+return true;
506+}
366507if (
367508isBundledProviderConfiguredForRuntimeDeps({
368509config: params.config,
@@ -409,6 +550,7 @@ function shouldIncludeBundledPluginRuntimeDeps(params: {
409550selectedPluginIds?: ReadonlySet<string>;
410551pluginId: string;
411552pluginDir: string;
553+configuredModelOwnerPluginIds?: ReadonlySet<string>;
412554includeConfiguredChannels?: boolean;
413555manifestCache?: BundledPluginRuntimeDepsManifestCache;
414556}): boolean {
@@ -458,6 +600,7 @@ function shouldIncludeBundledPluginRuntimeDeps(params: {
458600plugins: params.plugins,
459601pluginId: params.pluginId,
460602pluginDir: params.pluginDir,
603+configuredModelOwnerPluginIds: params.configuredModelOwnerPluginIds,
461604includeConfiguredChannels: params.includeConfiguredChannels,
462605manifestCache: params.manifestCache,
463606});
@@ -490,6 +633,14 @@ export function collectBundledPluginRuntimeDeps(params: {
490633const plugins = params.config
491634 ? normalizePluginsConfigWithResolver(params.config.plugins, normalizePluginId)
492635 : undefined;
636+const configuredModelOwnerPluginIds =
637+params.config && plugins
638+ ? resolveBundledRuntimeDepsConfiguredModelOwnerPluginIds({
639+config: params.config,
640+extensionsDir: params.extensionsDir,
641+ manifestCache,
642+})
643+ : undefined;
493644const includedPluginIds = new Set<string>();
494645495646for (const entry of fs.readdirSync(params.extensionsDir, { withFileTypes: true })) {
@@ -506,6 +657,7 @@ export function collectBundledPluginRuntimeDeps(params: {
506657selectedPluginIds: params.selectedPluginIds,
507658 pluginId,
508659 pluginDir,
660+ configuredModelOwnerPluginIds,
509661includeConfiguredChannels: params.includeConfiguredChannels,
510662 manifestCache,
511663})
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。