diff --git a/src/agents/embedded-agent-runner/model.static-catalog.test.ts b/src/agents/embedded-agent-runner/model.static-catalog.test.ts index 9670c75ee77..54d946df8d1 100644 --- a/src/agents/embedded-agent-runner/model.static-catalog.test.ts +++ b/src/agents/embedded-agent-runner/model.static-catalog.test.ts @@ -5,6 +5,13 @@ const manifestMocks = vi.hoisted(() => ({ listOpenClawPluginManifestMetadata: vi.fn(), loadPluginManifest: vi.fn(), })); +const providerMocks = vi.hoisted(() => ({ + normalizePluginDiscoveryResult: vi.fn(), + resolveBundledProviderCompatPluginIds: vi.fn(), + resolveOwningPluginIdsForProviderRef: vi.fn(), + resolveRuntimePluginDiscoveryProviders: vi.fn(), + runProviderStaticCatalog: vi.fn(), +})); vi.mock("../../plugins/manifest-metadata-scan.js", () => ({ listOpenClawPluginManifestMetadata: manifestMocks.listOpenClawPluginManifestMetadata, @@ -15,7 +22,24 @@ vi.mock("../../plugins/manifest.js", async (importOriginal) => ({ loadPluginManifest: manifestMocks.loadPluginManifest, })); -import { resolveBundledStaticCatalogModel } from "./model.static-catalog.js"; +vi.mock("../../plugins/providers.js", async (importOriginal) => ({ + ...(await importOriginal()), + resolveBundledProviderCompatPluginIds: providerMocks.resolveBundledProviderCompatPluginIds, + resolveOwningPluginIdsForProviderRef: providerMocks.resolveOwningPluginIdsForProviderRef, +})); + +vi.mock("../../plugins/provider-discovery.js", async (importOriginal) => ({ + ...(await importOriginal()), + normalizePluginDiscoveryResult: providerMocks.normalizePluginDiscoveryResult, + resolveRuntimePluginDiscoveryProviders: providerMocks.resolveRuntimePluginDiscoveryProviders, + runProviderStaticCatalog: providerMocks.runProviderStaticCatalog, +})); + +import { getModelProviderRequestTransport } from "../provider-request-config.js"; +import { + resolveBundledProviderStaticCatalogModel, + resolveBundledStaticCatalogModel, +} from "./model.static-catalog.js"; function setManifestPlugins(plugins: unknown[]) { // Static catalog resolution reads scan metadata first, then loads the manifest @@ -81,7 +105,17 @@ function createMistralManifestPlugin(overrides?: { beforeEach(() => { manifestMocks.listOpenClawPluginManifestMetadata.mockReset(); manifestMocks.loadPluginManifest.mockReset(); + providerMocks.normalizePluginDiscoveryResult.mockReset(); + providerMocks.resolveBundledProviderCompatPluginIds.mockReset(); + providerMocks.resolveOwningPluginIdsForProviderRef.mockReset(); + providerMocks.resolveRuntimePluginDiscoveryProviders.mockReset(); + providerMocks.runProviderStaticCatalog.mockReset(); setManifestPlugins([]); + providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue([]); + providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(undefined); + providerMocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([]); + providerMocks.runProviderStaticCatalog.mockResolvedValue(undefined); + providerMocks.normalizePluginDiscoveryResult.mockReturnValue({}); }); describe("resolveBundledStaticCatalogModel", () => { @@ -166,3 +200,132 @@ describe("resolveBundledStaticCatalogModel", () => { ).toBeUndefined(); }); }); + +describe("resolveBundledProviderStaticCatalogModel", () => { + it("resolves exact rows from bundled provider static catalogs", async () => { + const cfg = { plugins: { entries: { google: { enabled: true } } } }; + const provider = { + id: "google", + pluginId: "google", + label: "Google", + auth: [], + staticCatalog: { run: vi.fn() }, + }; + providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(["google"]); + providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue(["google"]); + providerMocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([provider]); + providerMocks.runProviderStaticCatalog.mockResolvedValue({ marker: "static-result" }); + providerMocks.normalizePluginDiscoveryResult.mockReturnValue({ + google: { + api: "google-generative-ai", + authHeader: true, + baseUrl: "https://generativelanguage.googleapis.com/v1beta", + request: { headers: { "X-Static-Catalog": "yes" } }, + models: [ + { + id: "gemini-3.1-pro-preview", + name: "Gemini 3.1 Pro Preview", + reasoning: true, + input: ["text", "image"], + cost: { input: 2, output: 12, cacheRead: 0.5, cacheWrite: 0 }, + contextWindow: 1_048_576, + maxTokens: 65_536, + mediaInput: { image: { maxSidePx: 3072, tokenMode: "provider" } }, + }, + ], + }, + }); + + const model = await resolveBundledProviderStaticCatalogModel({ + provider: "google", + modelId: "gemini-3.1-pro-preview", + cfg, + }); + + expect(model).toMatchObject({ + api: "google-generative-ai", + authHeader: true, + baseUrl: "https://generativelanguage.googleapis.com/v1beta", + contextTokens: undefined, + contextWindow: 1_048_576, + cost: { input: 2, output: 12, cacheRead: 0.5, cacheWrite: 0 }, + headers: { "X-Static-Catalog": "yes" }, + id: "gemini-3.1-pro-preview", + input: ["text", "image"], + maxTokens: 65_536, + mediaInput: { image: { maxSidePx: 3072, tokenMode: "provider" } }, + name: "Gemini 3.1 Pro Preview", + provider: "google", + reasoning: true, + }); + expect(getModelProviderRequestTransport(model!)).toEqual({ + headers: { "X-Static-Catalog": "yes" }, + }); + expect(providerMocks.resolveRuntimePluginDiscoveryProviders).toHaveBeenCalledWith({ + config: cfg, + workspaceDir: undefined, + env: process.env, + onlyPluginIds: ["google"], + includeUntrustedWorkspacePlugins: false, + requireCompleteDiscoveryEntryCoverage: true, + discoveryEntriesOnly: true, + includeManifestModelCatalogProviders: false, + }); + expect(providerMocks.runProviderStaticCatalog).toHaveBeenCalledWith({ + provider, + config: cfg, + workspaceDir: undefined, + env: process.env, + }); + }); + + it("does not load provider catalogs when the provider owner is not bundled and enabled", async () => { + providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(["google"]); + providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue([]); + + await expect( + resolveBundledProviderStaticCatalogModel({ + provider: "google", + modelId: "gemini-3.1-pro-preview", + cfg: {}, + }), + ).resolves.toBeUndefined(); + + expect(providerMocks.resolveRuntimePluginDiscoveryProviders).not.toHaveBeenCalled(); + expect(providerMocks.runProviderStaticCatalog).not.toHaveBeenCalled(); + }); + + it("requires an exact provider and model match", async () => { + const provider = { id: "google", pluginId: "google", label: "Google", auth: [] }; + providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(["google"]); + providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue(["google"]); + providerMocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([provider]); + providerMocks.normalizePluginDiscoveryResult.mockReturnValue({ + google: { + api: "google-generative-ai", + baseUrl: "https://generativelanguage.googleapis.com/v1beta", + models: [{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" }], + }, + "google-vertex": { + api: "google-vertex", + baseUrl: "https://aiplatform.googleapis.com/v1", + models: [{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" }], + }, + }); + + await expect( + resolveBundledProviderStaticCatalogModel({ + provider: "google", + modelId: "gemini-2.5-pro", + cfg: {}, + }), + ).resolves.toBeUndefined(); + await expect( + resolveBundledProviderStaticCatalogModel({ + provider: "openrouter", + modelId: "gemini-3.1-pro-preview", + cfg: {}, + }), + ).resolves.toBeUndefined(); + }); +}); diff --git a/src/agents/embedded-agent-runner/model.static-catalog.ts b/src/agents/embedded-agent-runner/model.static-catalog.ts index e9ec8e22de5..03ebb294c9a 100644 --- a/src/agents/embedded-agent-runner/model.static-catalog.ts +++ b/src/agents/embedded-agent-runner/model.static-catalog.ts @@ -3,15 +3,26 @@ */ import type { NormalizedModelCatalogRow } from "@openclaw/model-catalog-core/model-catalog-types"; import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; +import type { ModelProviderConfig } from "../../config/types.models.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { planManifestModelCatalogRows } from "../../model-catalog/manifest-planner.js"; import { listOpenClawPluginManifestMetadata } from "../../plugins/manifest-metadata-scan.js"; import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js"; import type { PluginManifestRecord } from "../../plugins/manifest-registry.js"; import { loadPluginManifest } from "../../plugins/manifest.js"; +import { + normalizePluginDiscoveryResult, + resolveRuntimePluginDiscoveryProviders, + runProviderStaticCatalog, +} from "../../plugins/provider-discovery.js"; import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js"; +import { + resolveBundledProviderCompatPluginIds, + resolveOwningPluginIdsForProviderRef, +} from "../../plugins/providers.js"; import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; import { normalizeStaticProviderModelId } from "../model-ref-shared.js"; +import { buildInlineProviderModels } from "./model.inline-provider.js"; /** * Resolves bundled plugin static model-catalog rows into runtime model records. @@ -20,21 +31,35 @@ function rowMatchesModel(params: { row: NormalizedModelCatalogRow; provider: string; modelId: string; +}): boolean { + return staticModelIdMatches({ + candidateId: params.row.id, + provider: params.provider, + modelId: params.modelId, + rowProvider: params.row.provider, + }); +} + +function staticModelIdMatches(params: { + candidateId: string; + provider: string; + modelId: string; + rowProvider?: string; }): boolean { const normalizedProvider = normalizeProviderId(params.provider); - if (normalizeProviderId(params.row.provider) !== normalizedProvider) { + if (params.rowProvider && normalizeProviderId(params.rowProvider) !== normalizedProvider) { return false; } return ( - normalizeStaticProviderModelId(normalizedProvider, params.row.id).trim().toLowerCase() === + normalizeStaticProviderModelId(normalizedProvider, params.candidateId).trim().toLowerCase() === normalizeStaticProviderModelId(normalizedProvider, params.modelId).trim().toLowerCase() ); } function normalizeStaticCatalogInput( - input: NormalizedModelCatalogRow["input"], + input: readonly unknown[] | undefined, ): ProviderRuntimeModel["input"] { - const normalizedInput = input.filter( + const normalizedInput = (input ?? []).filter( (item): item is "text" | "image" => item === "text" || item === "image", ); return normalizedInput.length > 0 ? normalizedInput : ["text"]; @@ -71,6 +96,42 @@ function modelFromStaticCatalogRow(row: NormalizedModelCatalogRow): ProviderRunt }; } +function modelFromProviderStaticCatalog(params: { + provider: string; + providerConfig: ModelProviderConfig; + model: ModelProviderConfig["models"][number]; +}): ProviderRuntimeModel { + const [model] = buildInlineProviderModels({ + [params.provider]: { ...params.providerConfig, models: [params.model] }, + }); + return { + ...model, + id: model?.id ?? params.model.id, + name: model?.name || params.model.name || params.model.id, + provider: params.provider, + api: model?.api ?? params.model.api ?? params.providerConfig.api ?? "openai-responses", + baseUrl: model?.baseUrl ?? params.model.baseUrl ?? params.providerConfig.baseUrl ?? "", + reasoning: model?.reasoning ?? params.model.reasoning ?? false, + input: normalizeStaticCatalogInput(model?.input ?? params.model.input), + cost: model?.cost ?? normalizeStaticCatalogCost(params.model.cost), + contextWindow: + model?.contextWindow ?? + params.model.contextWindow ?? + params.providerConfig.contextWindow ?? + DEFAULT_CONTEXT_TOKENS, + contextTokens: + model?.contextTokens ?? params.model.contextTokens ?? params.providerConfig.contextTokens, + maxTokens: + model?.maxTokens ?? + params.model.maxTokens ?? + params.providerConfig.maxTokens ?? + DEFAULT_CONTEXT_TOKENS, + ...(params.providerConfig.authHeader !== undefined + ? { authHeader: params.providerConfig.authHeader } + : {}), + }; +} + type StaticCatalogPlugin = Parameters< typeof planManifestModelCatalogRows >[0]["registry"]["plugins"][number]; @@ -210,3 +271,86 @@ export function resolveBundledStaticCatalogModel(params: { } return undefined; } + +/** + * Resolves one bundled provider static-catalog model row for provider/model lookup. + * + * Some bundled providers expose their canonical offline rows through + * `providerCatalogEntry` instead of manifest `modelCatalog`. This keeps the + * skip-discovery fallback aligned with model list/inspect without running live + * discovery or untrusted workspace plugins. + */ +export async function resolveBundledProviderStaticCatalogModel(params: { + provider: string; + modelId: string; + cfg?: OpenClawConfig; + workspaceDir?: string; + env?: NodeJS.ProcessEnv; +}): Promise { + const env = params.env ?? process.env; + const provider = normalizeProviderId(params.provider); + if (!provider || !params.modelId.trim()) { + return undefined; + } + const pluginIds = resolveOwningPluginIdsForProviderRef({ + provider, + config: params.cfg, + workspaceDir: params.workspaceDir, + env, + }); + if (!pluginIds || pluginIds.length === 0) { + return undefined; + } + const bundledPluginIds = new Set( + resolveBundledProviderCompatPluginIds({ + config: params.cfg, + workspaceDir: params.workspaceDir, + env, + }), + ); + const scopedPluginIds = pluginIds.filter((pluginId) => bundledPluginIds.has(pluginId)); + if (scopedPluginIds.length === 0) { + return undefined; + } + + const providers = await resolveRuntimePluginDiscoveryProviders({ + config: params.cfg, + workspaceDir: params.workspaceDir, + env, + onlyPluginIds: scopedPluginIds, + includeUntrustedWorkspacePlugins: false, + requireCompleteDiscoveryEntryCoverage: true, + discoveryEntriesOnly: true, + includeManifestModelCatalogProviders: false, + }); + + for (const catalogProvider of providers) { + const result = await runProviderStaticCatalog({ + provider: catalogProvider, + config: params.cfg ?? {}, + workspaceDir: params.workspaceDir, + env, + }); + const normalized = normalizePluginDiscoveryResult({ + provider: catalogProvider, + result, + }); + for (const [providerIdRaw, providerConfig] of Object.entries(normalized)) { + const providerId = normalizeProviderId(providerIdRaw); + if (providerId !== provider || !Array.isArray(providerConfig.models)) { + continue; + } + const model = providerConfig.models.find((candidate) => + staticModelIdMatches({ + candidateId: candidate.id, + provider, + modelId: params.modelId, + }), + ); + if (model) { + return modelFromProviderStaticCatalog({ provider, providerConfig, model }); + } + } + } + return undefined; +} diff --git a/src/agents/embedded-agent-runner/model.test.ts b/src/agents/embedded-agent-runner/model.test.ts index 0df49231d76..ec950acea26 100644 --- a/src/agents/embedded-agent-runner/model.test.ts +++ b/src/agents/embedded-agent-runner/model.test.ts @@ -19,6 +19,7 @@ import { resetModelDiscoveryCacheForTest } from "./model-discovery-cache.js"; import { createProviderRuntimeTestMock } from "./model.provider-runtime.test-support.js"; const resolveBundledStaticCatalogModelMock = vi.hoisted(() => vi.fn()); +const resolveBundledProviderStaticCatalogModelMock = vi.hoisted(() => vi.fn()); const resolveRuntimeSyntheticAuthProviderRefsMock = vi.hoisted(() => vi.fn((): string[] => [])); const resolveRuntimeExternalAuthProviderRefsMock = vi.hoisted(() => vi.fn((): string[] => [])); @@ -134,6 +135,7 @@ vi.mock("./model.static-catalog.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, + resolveBundledProviderStaticCatalogModel: resolveBundledProviderStaticCatalogModelMock, resolveBundledStaticCatalogModel: resolveBundledStaticCatalogModelMock, }; }); @@ -187,6 +189,7 @@ beforeEach(() => { mockLoadOpenRouterModelCapabilities.mockReset(); mockLoadOpenRouterModelCapabilities.mockResolvedValue(); resolveBundledStaticCatalogModelMock.mockReset(); + resolveBundledProviderStaticCatalogModelMock.mockReset(); }); function createRuntimeHooks() { @@ -568,6 +571,58 @@ describe("resolveModel", () => { cfg: undefined, workspaceDir: undefined, }); + expect(resolveBundledProviderStaticCatalogModelMock).not.toHaveBeenCalled(); + expect(discoverAuthStorage).not.toHaveBeenCalled(); + expect(discoverModels).not.toHaveBeenCalled(); + }); + + it("resolves opt-in provider static catalog rows while skipping agent discovery", async () => { + resolveBundledProviderStaticCatalogModelMock.mockResolvedValueOnce({ + provider: "google", + id: "gemini-3.1-pro-preview", + name: "Gemini 3.1 Pro Preview", + api: "google-generative-ai", + baseUrl: "https://generativelanguage.googleapis.com/v1beta", + reasoning: true, + input: ["text", "image"], + cost: { input: 2, output: 12, cacheRead: 0.5, cacheWrite: 0 }, + contextWindow: 1_048_576, + maxTokens: 65_536, + }); + + const result = await resolveModelAsync( + "google", + "gemini-3.1-pro-preview", + "/tmp/agent", + undefined, + { + allowBundledStaticCatalogFallback: true, + runtimeHooks: createRuntimeHooks(), + skipAgentDiscovery: true, + }, + ); + + expectRecordFields(expectResolvedModel(result), { + provider: "google", + id: "gemini-3.1-pro-preview", + api: "google-generative-ai", + baseUrl: "https://generativelanguage.googleapis.com/v1beta", + reasoning: true, + contextWindow: 1_048_576, + maxTokens: 65_536, + }); + expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith({ + provider: "google", + modelId: "gemini-3.1-pro-preview", + cfg: undefined, + workspaceDir: undefined, + }); + expect(resolveBundledProviderStaticCatalogModelMock).toHaveBeenCalledWith({ + provider: "google", + modelId: "gemini-3.1-pro-preview", + cfg: undefined, + workspaceDir: undefined, + }); expect(discoverAuthStorage).not.toHaveBeenCalled(); expect(discoverModels).not.toHaveBeenCalled(); }); diff --git a/src/agents/embedded-agent-runner/model.ts b/src/agents/embedded-agent-runner/model.ts index 361949a348e..aff5f0f1917 100644 --- a/src/agents/embedded-agent-runner/model.ts +++ b/src/agents/embedded-agent-runner/model.ts @@ -54,6 +54,7 @@ import { import { normalizeResolvedProviderModel } from "./model.provider-normalization.js"; import { canonicalizeManifestModelCatalogProviderAlias, + resolveBundledProviderStaticCatalogModel, resolveBundledStaticCatalogModel, } from "./model.static-catalog.js"; @@ -1566,25 +1567,32 @@ export async function resolveModelAsync( authProfileId: options?.authProfileId, preferredProfile: options?.preferredProfile, }); - let staticCatalogLookupComplete = false; - let staticCatalogModel: ReturnType | undefined; - const resolveStaticCatalogModel = () => { + let staticCatalogLookup: Promise | undefined; + const resolveStaticCatalogModel = async () => { if (!options?.allowBundledStaticCatalogFallback) { return undefined; } - if (!staticCatalogLookupComplete) { - staticCatalogLookupComplete = true; - staticCatalogModel = resolveBundledStaticCatalogModel({ + staticCatalogLookup ??= (async () => { + const manifestModel = resolveBundledStaticCatalogModel({ provider: normalizedRef.provider, modelId: normalizedRef.model, cfg, workspaceDir, }); - } - return staticCatalogModel; + if (manifestModel) { + return manifestModel; + } + return await resolveBundledProviderStaticCatalogModel({ + provider: normalizedRef.provider, + modelId: normalizedRef.model, + cfg, + workspaceDir, + }); + })(); + return await staticCatalogLookup; }; - const resolveStaticCatalogFallbackModel = () => { - const catalogModel = resolveStaticCatalogModel(); + const resolveStaticCatalogFallbackModel = async () => { + const catalogModel = await resolveStaticCatalogModel(); if (!catalogModel) { return undefined; } @@ -1657,7 +1665,7 @@ export async function resolveModelAsync( model = await resolveDynamicAttempt(); } if (!model && !explicitModel && options?.allowBundledStaticCatalogFallback) { - model = resolveStaticCatalogFallbackModel(); + model = await resolveStaticCatalogFallbackModel(); } if (!model && !explicitModel && options?.allowBundledStaticCatalogFallback) { model = resolveConfiguredFallbackModel({ @@ -1670,7 +1678,7 @@ export async function resolveModelAsync( }); } if (model && options?.allowBundledStaticCatalogFallback) { - const staticMediaInput = resolveStaticCatalogModel()?.mediaInput; + const staticMediaInput = (await resolveStaticCatalogModel())?.mediaInput; const resolvedMediaInput = (model as ProviderRuntimeModel).mediaInput; const mediaInput = mergeModelMediaInput(staticMediaInput, resolvedMediaInput); if (mediaInput) { diff --git a/src/plugins/provider-discovery.runtime.test.ts b/src/plugins/provider-discovery.runtime.test.ts index bcec8496aba..0c3339713c9 100644 --- a/src/plugins/provider-discovery.runtime.test.ts +++ b/src/plugins/provider-discovery.runtime.test.ts @@ -691,6 +691,25 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => { expect(mocks.resolvePluginProviders).not.toHaveBeenCalled(); }); + it("can omit manifest model catalogs from static discovery entries", () => { + mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["openai"]); + mocks.loadPluginMetadataSnapshot.mockReturnValue({ + index: { plugins: [] }, + manifestRegistry: { + plugins: [createManifestPluginWithModelCatalog("openai")], + diagnostics: [], + }, + }); + + const providers = resolvePluginDiscoveryProvidersRuntime({ + discoveryEntriesOnly: true, + includeManifestModelCatalogProviders: false, + }); + + expect(providers).toStrictEqual([]); + expect(mocks.resolvePluginProviders).not.toHaveBeenCalled(); + }); + it("defaults missing manifest model costs for static discovery entries", async () => { mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["anthropic"]); mocks.loadPluginMetadataSnapshot.mockReturnValue({ diff --git a/src/plugins/provider-discovery.runtime.ts b/src/plugins/provider-discovery.runtime.ts index 7aebe37d950..a1c97bd1669 100644 --- a/src/plugins/provider-discovery.runtime.ts +++ b/src/plugins/provider-discovery.runtime.ts @@ -274,6 +274,7 @@ function resolveProviderDiscoveryEntryPlugins(params: { includeUntrustedWorkspacePlugins?: boolean; requireCompleteDiscoveryEntryCoverage?: boolean; discoveryEntriesOnly?: boolean; + includeManifestModelCatalogProviders?: boolean; pluginMetadataSnapshot?: PluginMetadataRegistryView; }): ProviderDiscoveryEntryResult { const metadataSnapshot = @@ -295,7 +296,10 @@ function resolveProviderDiscoveryEntryPlugins(params: { const runtimeManifestCatalogPluginIds = resolveRuntimeManifestCatalogPluginIds(pluginRecords); const entryRecords = pluginRecords.filter((plugin) => plugin.providerDiscoverySource); const entryPluginIds = new Set(entryRecords.map((plugin) => plugin.id)); - const manifestProviders = resolveManifestModelCatalogProviders(pluginRecords); + const manifestProviders = + params.includeManifestModelCatalogProviders === false + ? [] + : resolveManifestModelCatalogProviders(pluginRecords); const manifestEntryPluginIds = new Set(); for (const pluginId of manifestProviders.map((provider) => provider.pluginId)) { if (pluginId) { @@ -427,6 +431,7 @@ export function resolvePluginDiscoveryProvidersRuntime(params: { includeUntrustedWorkspacePlugins?: boolean; requireCompleteDiscoveryEntryCoverage?: boolean; discoveryEntriesOnly?: boolean; + includeManifestModelCatalogProviders?: boolean; pluginMetadataSnapshot?: PluginMetadataRegistryView; }): ProviderPlugin[] { const env = params.env ?? process.env; diff --git a/src/plugins/provider-discovery.ts b/src/plugins/provider-discovery.ts index 55762ad29f1..96d9ee268aa 100644 --- a/src/plugins/provider-discovery.ts +++ b/src/plugins/provider-discovery.ts @@ -46,6 +46,7 @@ export type ResolveRuntimePluginDiscoveryProvidersParams = { includeUntrustedWorkspacePlugins?: boolean; requireCompleteDiscoveryEntryCoverage?: boolean; discoveryEntriesOnly?: boolean; + includeManifestModelCatalogProviders?: boolean; pluginMetadataSnapshot?: PluginMetadataRegistryView; };