Providers: scope compat resolution to owning plugins

This commit is contained in:
Gustavo Madeira Santana
2026-03-16 12:45:56 +00:00
parent c186176ca3
commit 77566a1448
5 changed files with 65 additions and 3 deletions

View File

@@ -1,2 +1,5 @@
export { resolveProviderPluginChoice } from "../../../plugins/provider-wizard.js";
export { resolvePluginProviders } from "../../../plugins/providers.js";
export {
resolveOwningPluginIdsForProvider,
resolvePluginProviders,
} from "../../../plugins/providers.js";

View File

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

View File

@@ -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,
}),

View File

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

View File

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