From 77566a14483cf38b359bc24166fd4e9b9131494c Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 16 Mar 2026 12:45:56 +0000 Subject: [PATCH] Providers: scope compat resolution to owning plugins --- .../auth-choice.plugin-providers.runtime.ts | 5 +++- .../auth-choice.plugin-providers.test.ts | 14 ++++++++++ .../local/auth-choice.plugin-providers.ts | 11 +++++++- src/plugins/providers.test.ts | 28 +++++++++++++++++++ src/plugins/providers.ts | 10 ++++++- 5 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.runtime.ts b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.runtime.ts index a19d1861c7e..a02dd2f2ee2 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.runtime.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.runtime.ts @@ -1,2 +1,5 @@ export { resolveProviderPluginChoice } from "../../../plugins/provider-wizard.js"; -export { resolvePluginProviders } from "../../../plugins/providers.js"; +export { + resolveOwningPluginIdsForProvider, + resolvePluginProviders, +} from "../../../plugins/providers.js"; diff --git a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts index 4e0f37e2882..f993091dd49 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts @@ -7,9 +7,11 @@ vi.mock("../../auth-choice.preferred-provider.js", () => ({ resolvePreferredProviderForAuthChoice, })); +const resolveOwningPluginIdsForProvider = vi.hoisted(() => vi.fn(() => undefined)); const resolveProviderPluginChoice = vi.hoisted(() => vi.fn()); const resolvePluginProviders = vi.hoisted(() => vi.fn(() => [])); vi.mock("./auth-choice.plugin-providers.runtime.js", () => ({ + resolveOwningPluginIdsForProvider, resolveProviderPluginChoice, resolvePluginProviders, PROVIDER_PLUGIN_CHOICE_PREFIX: "provider-plugin:", @@ -30,6 +32,7 @@ describe("applyNonInteractivePluginProviderChoice", () => { it("loads plugin providers for provider-plugin auth choices", async () => { const runtime = createRuntime(); const runNonInteractive = vi.fn(async () => ({ plugins: { allow: ["vllm"] } })); + resolveOwningPluginIdsForProvider.mockReturnValue(["vllm"] as never); resolvePluginProviders.mockReturnValue([{ id: "vllm", pluginId: "vllm" }] as never); resolveProviderPluginChoice.mockReturnValue({ provider: { id: "vllm", pluginId: "vllm", label: "vLLM" }, @@ -46,7 +49,18 @@ describe("applyNonInteractivePluginProviderChoice", () => { toApiKeyCredential: vi.fn(), }); + expect(resolveOwningPluginIdsForProvider).toHaveBeenCalledOnce(); + expect(resolveOwningPluginIdsForProvider).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "vllm", + }), + ); expect(resolvePluginProviders).toHaveBeenCalledOnce(); + expect(resolvePluginProviders).toHaveBeenCalledWith( + expect.objectContaining({ + onlyPluginIds: ["vllm"], + }), + ); expect(resolveProviderPluginChoice).toHaveBeenCalledOnce(); expect(runNonInteractive).toHaveBeenCalledOnce(); expect(result).toEqual({ plugins: { allow: ["vllm"] } }); diff --git a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.ts b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.ts index 8d9b820fc52..3f11a7367a9 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.ts @@ -79,11 +79,20 @@ export async function applyNonInteractivePluginProviderChoice(params: { params.nextConfig, preferredProviderId, ); - const { resolveProviderPluginChoice, resolvePluginProviders } = await loadPluginProviderRuntime(); + const { resolveOwningPluginIdsForProvider, resolveProviderPluginChoice, resolvePluginProviders } = + await loadPluginProviderRuntime(); + const owningPluginIds = preferredProviderId + ? resolveOwningPluginIdsForProvider({ + provider: preferredProviderId, + config: resolutionConfig, + workspaceDir, + }) + : undefined; const providerChoice = resolveProviderPluginChoice({ providers: resolvePluginProviders({ config: resolutionConfig, workspaceDir, + onlyPluginIds: owningPluginIds, bundledProviderAllowlistCompat: true, bundledProviderVitestCompat: true, }), diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index 50530a3c051..bfc976a7abf 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -125,6 +125,34 @@ describe("resolvePluginProviders", () => { expect(allow).not.toContain("workspace-provider"); }); + it("scopes bundled provider compat expansion to the requested plugin ids", () => { + resolvePluginProviders({ + config: { + plugins: { + allow: ["openrouter"], + }, + }, + bundledProviderAllowlistCompat: true, + onlyPluginIds: ["moonshot"], + }); + + expect(loadOpenClawPluginsMock).toHaveBeenCalledWith( + expect.objectContaining({ + onlyPluginIds: ["moonshot"], + config: expect.objectContaining({ + plugins: expect.objectContaining({ + allow: expect.arrayContaining(["openrouter", "moonshot"]), + }), + }), + }), + ); + + const call = loadOpenClawPluginsMock.mock.calls.at(-1)?.[0]; + const allow = call?.config?.plugins?.allow; + expect(allow).not.toContain("google"); + expect(allow).not.toContain("kilocode"); + }); + it("maps provider ids to owning plugin ids via manifests", () => { loadPluginManifestRegistryMock.mockReturnValue({ plugins: [ diff --git a/src/plugins/providers.ts b/src/plugins/providers.ts index f2a2b4497c9..90a2acedcad 100644 --- a/src/plugins/providers.ts +++ b/src/plugins/providers.ts @@ -62,14 +62,21 @@ function resolveBundledProviderCompatPluginIds(params: { config?: PluginLoadOptions["config"]; workspaceDir?: string; env?: PluginLoadOptions["env"]; + onlyPluginIds?: string[]; }): string[] { + const onlyPluginIdSet = params.onlyPluginIds ? new Set(params.onlyPluginIds) : null; const registry = loadPluginManifestRegistry({ config: params.config, workspaceDir: params.workspaceDir, env: params.env, }); return registry.plugins - .filter((plugin) => plugin.origin === "bundled" && plugin.providers.length > 0) + .filter( + (plugin) => + plugin.origin === "bundled" && + plugin.providers.length > 0 && + (!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)), + ) .map((plugin) => plugin.id) .toSorted((left, right) => left.localeCompare(right)); } @@ -116,6 +123,7 @@ export function resolvePluginProviders(params: { config: params.config, workspaceDir: params.workspaceDir, env: params.env, + onlyPluginIds: params.onlyPluginIds, }) : []; const maybeAllowlistCompat = params.bundledProviderAllowlistCompat