perf(onboard): reuse provider install catalog during setup (#147712)

Reuse one install-choice catalog for deprecated and selected-choice lookup,
and retain only the final stable catalog sort. Preserve trust filters,
blank/rejected paths, deprecated precedence, and choice ordering.

Validate 45 owner tests, the commands/plugins-platform type graphs, and
actual-owner catalog/result/trace parity across the bounded synthetic corpus.
This commit is contained in:
Peter Steinberger 2026-09-13 22:47:09 -07:00 • committed by GitHub
parent 39f2a58a4a
commit dcf4d3c991
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 156 additions and 58 deletions

View file

@ -30,11 +30,13 @@ const resolveManifestProviderAuthChoice = vi.hoisted(() => vi.fn(() => undefined
vi.mock("../../../plugins/provider-auth-choices.js", () => ({
resolveManifestProviderAuthChoice,
}));
const resolveProviderInstallCatalogEntry = vi.hoisted(() => vi.fn(() => undefined));
const resolveDeprecatedProviderInstallCatalogEntry = vi.hoisted(() => vi.fn(() => undefined));
const resolveProviderInstallCatalogEntries = vi.hoisted(() =>
vi.fn<
typeof import("../../../plugins/provider-install-catalog.js").resolveProviderInstallCatalogEntries
>(() => []),
);
vi.mock("../../../plugins/provider-install-catalog.js", () => ({
resolveDeprecatedProviderInstallCatalogEntry,
resolveProviderInstallCatalogEntry,
resolveProviderInstallCatalogEntries,
}));
const ensureOnboardingPluginInstalled = vi.hoisted(() => vi.fn());
vi.mock("../../onboarding-plugin-install.js", () => ({
@ -56,8 +58,7 @@ beforeEach(() => {
vi.clearAllMocks();
resolvePreferredProviderForAuthChoice.mockResolvedValue(undefined);
resolveManifestProviderAuthChoice.mockReturnValue(undefined);
resolveDeprecatedProviderInstallCatalogEntry.mockReturnValue(undefined);
resolveProviderInstallCatalogEntry.mockReturnValue(undefined);
resolveProviderInstallCatalogEntries.mockReturnValue([]);
ensureOnboardingPluginInstalled.mockResolvedValue(undefined);
resolveOwningPluginIdsForProvider.mockReturnValue(undefined as never);
resolveProviderPluginChoice.mockReturnValue(undefined);
@ -321,6 +322,7 @@ describe("applyNonInteractivePluginProviderChoice", () => {
}),
);
expect(result).toEqual({ plugins: { allow: ["vllm"] } });
expect(resolveProviderInstallCatalogEntries).not.toHaveBeenCalled();
});
it.each([false, true])(
@ -380,7 +382,7 @@ describe("applyNonInteractivePluginProviderChoice", () => {
},
);
it("installs an official catalog provider before applying a cold auth choice", async () => {
it("installs an official catalog provider for a cold auth choice with whitespace", async () => {
const runtime = createRuntime();
const runNonInteractive = vi.fn(async ({ config }: { config: OpenClawConfig }) => ({
...config,
@ -391,16 +393,21 @@ describe("applyNonInteractivePluginProviderChoice", () => {
},
}));
const provider = { id: "groq", pluginId: "groq", label: "Groq" };
resolveProviderInstallCatalogEntry.mockReturnValue({
pluginId: "groq",
providerId: "groq",
label: "Groq",
origin: "bundled",
install: {
npmSpec: "@openclaw/groq-provider",
defaultChoice: "npm",
resolveProviderInstallCatalogEntries.mockReturnValue([
{
pluginId: "groq",
providerId: "groq",
methodId: "api-key",
choiceId: "groq-api-key",
choiceLabel: "Groq API key",
label: "Groq",
origin: "bundled",
install: {
npmSpec: "@openclaw/groq-provider",
defaultChoice: "npm",
},
},
} as never);
]);
ensureOnboardingPluginInstalled.mockResolvedValue({
cfg: {
plugins: {
@ -421,7 +428,7 @@ describe("applyNonInteractivePluginProviderChoice", () => {
const result = await applyNonInteractivePluginProviderChoice({
nextConfig: { agents: { defaults: {} } } as OpenClawConfig,
authChoice: "groq-api-key",
authChoice: " groq-api-key ",
opts: { groqApiKey: "groq-key" } as never,
runtime: runtime as never,
baseConfig: { agents: { defaults: {} } } as OpenClawConfig,
@ -430,12 +437,11 @@ describe("applyNonInteractivePluginProviderChoice", () => {
toApiKeyCredential: vi.fn(),
});
expect(resolveProviderInstallCatalogEntry).toHaveBeenCalledWith(
"groq-api-key",
expect.objectContaining({
includeUntrustedWorkspacePlugins: false,
}),
);
expect(resolveProviderInstallCatalogEntries).toHaveBeenCalledExactlyOnceWith({
config: { agents: { defaults: {} } },
workspaceDir: target.workspaceDir,
includeUntrustedWorkspacePlugins: false,
});
expect(ensureOnboardingPluginInstalled).toHaveBeenCalledWith(
expect.objectContaining({
cfg: { agents: { defaults: {} } },
@ -468,11 +474,22 @@ describe("applyNonInteractivePluginProviderChoice", () => {
});
});
it("guides deprecated official auth choices before their plugin is installed", async () => {
it("guides deprecated official auth choices before a current catalog match can install", async () => {
const runtime = createRuntime();
resolveDeprecatedProviderInstallCatalogEntry.mockReturnValue({
const choice = {
pluginId: "qwen",
providerId: "qwen",
methodId: "api-key",
choiceId: "qwen-api-key",
} as never);
choiceLabel: "Qwen API key",
label: "Qwen",
origin: "bundled" as const,
install: { npmSpec: "@openclaw/qwen-provider" },
};
resolveProviderInstallCatalogEntries.mockReturnValue([
{ ...choice, choiceId: "modelstudio-api-key" },
{ ...choice, deprecatedChoiceIds: ["modelstudio-api-key"] },
]);
const result = await applyNonInteractivePluginProviderChoice({
nextConfig: { agents: { defaults: {} } } as OpenClawConfig,
@ -492,9 +509,39 @@ describe("applyNonInteractivePluginProviderChoice", () => {
);
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(ensureOnboardingPluginInstalled).not.toHaveBeenCalled();
expect(resolveProviderInstallCatalogEntry).not.toHaveBeenCalled();
expect(resolveProviderInstallCatalogEntries).toHaveBeenCalledOnce();
});
it.each([
{ authChoice: "", catalogReads: 0 },
{ authChoice: " ", catalogReads: 0 },
{ authChoice: "unknown-choice", catalogReads: 1 },
])(
"leaves unmatched auth choice %j without setup effects",
async ({ authChoice, catalogReads }) => {
const runtime = createRuntime();
const config: OpenClawConfig = { agents: { defaults: {} } };
const resolveApiKey = vi.fn();
const result = await applyNonInteractivePluginProviderChoice({
nextConfig: config,
authChoice,
opts: {},
runtime,
baseConfig: config,
target,
resolveApiKey,
toApiKeyCredential: vi.fn(),
});
expect(result).toBeUndefined();
expect(resolveProviderInstallCatalogEntries).toHaveBeenCalledTimes(catalogReads);
expect(ensureOnboardingPluginInstalled).not.toHaveBeenCalled();
expect(resolveApiKey).not.toHaveBeenCalled();
expect(runtime.error).not.toHaveBeenCalled();
expect(runtime.exit).not.toHaveBeenCalled();
},
);
it.each([false, true])(
"rejects an unmatched provider-plugin auth choice while honoring json=%s",
async (json) => {
@ -513,6 +560,7 @@ describe("applyNonInteractivePluginProviderChoice", () => {
expect(result).toBeNull();
expect(resolvePreferredProviderForAuthChoice).not.toHaveBeenCalled();
expect(resolveProviderInstallCatalogEntries).not.toHaveBeenCalled();
expectRuntimeErrorIncludes(
runtime,
'Auth choice "provider-plugin:workspace-provider:api-key" was not matched to a trusted provider plugin.',
@ -559,6 +607,7 @@ describe("applyNonInteractivePluginProviderChoice", () => {
expectRuntimeErrorIncludes(runtime, '"provider-plugin:<provider-id>"');
expect(resolvePluginProvidersCore).not.toHaveBeenCalled();
expect(resolvePreferredProviderForAuthChoice).not.toHaveBeenCalled();
expect(resolveProviderInstallCatalogEntries).not.toHaveBeenCalled();
if (json) {
expect(runtime.log).toHaveBeenCalledOnce();
expect(JSON.parse(String(runtime.log.mock.calls[0]?.[0]))).toEqual({
@ -601,6 +650,7 @@ describe("applyNonInteractivePluginProviderChoice", () => {
expect(resolveProviderPluginChoice).toHaveBeenCalledTimes(1);
expect(resolvePluginProvidersCore).toHaveBeenCalledTimes(1);
expect(mockCall(resolveManifestProviderAuthChoice, 0)[0]).toBe("workspace-provider-api-key");
expect(resolveProviderInstallCatalogEntries).not.toHaveBeenCalled();
const trustedManifestInput = mockArg(resolveManifestProviderAuthChoice, 0, 1);
expect(trustedManifestInput.includeUntrustedWorkspacePlugins).toBe(false);
expect(mockCall(resolveManifestProviderAuthChoice, 1)[0]).toBe("workspace-provider-api-key");

View file

@ -16,10 +16,7 @@ import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { enablePluginWithCapabilityConsent } from "../../../plugins/enable.js";
import { resolvePreferredProviderForAuthChoice } from "../../../plugins/provider-auth-choice-preference.js";
import { resolveManifestProviderAuthChoice } from "../../../plugins/provider-auth-choices.js";
import {
resolveDeprecatedProviderInstallCatalogEntry,
resolveProviderInstallCatalogEntry,
} from "../../../plugins/provider-install-catalog.js";
import { resolveProviderInstallCatalogEntries } from "../../../plugins/provider-install-catalog.js";
import type {
ProviderAuthOptionBag,
ProviderNonInteractiveApiKeyCredentialParams,
@ -164,23 +161,25 @@ export async function applyNonInteractivePluginProviderChoice(params: {
].join("\n"),
);
}
const installCatalogParams = {
const normalizedChoiceId = params.authChoice.trim();
if (!normalizedChoiceId) {
return undefined;
}
const installCatalog = resolveProviderInstallCatalogEntries({
config: nextConfig,
workspaceDir,
includeUntrustedWorkspacePlugins: false,
};
const deprecatedInstallCatalogEntry = resolveDeprecatedProviderInstallCatalogEntry(
params.authChoice,
installCatalogParams,
});
const deprecatedInstallCatalogEntry = installCatalog.find((entry) =>
entry.deprecatedChoiceIds?.includes(normalizedChoiceId),
);
if (deprecatedInstallCatalogEntry) {
return reject(
`${JSON.stringify(params.authChoice)} is no longer supported. Use --auth-choice ${JSON.stringify(deprecatedInstallCatalogEntry.choiceId)} instead.`,
);
}
const installCatalogEntry = resolveProviderInstallCatalogEntry(
params.authChoice,
installCatalogParams,
const installCatalogEntry = installCatalog.find(
(entry) => entry.choiceId === normalizedChoiceId,
);
if (!installCatalogEntry) {
return undefined;

View file

@ -227,6 +227,57 @@ describe("provider install catalog", () => {
]);
});
it("keeps stable label order and installed-choice priority when merging official entries", () => {
loadPluginRegistrySnapshot.mockReturnValue(
registrySnapshot({ plugins: [{ ...vllmPluginWithPackageInstall(), origin: "bundled" }] }),
);
const choice = (choiceId: string, choiceLabel: string) => ({
pluginId: "vllm",
providerId: "vllm",
methodId: "api-key",
choiceId,
choiceLabel,
});
resolveManifestProviderAuthChoices.mockReturnValue([
choice("last", "Zulu"),
choice("same-first", "Same"),
choice("same-second", "Same"),
choice("first", "Alpha"),
]);
listOfficialExternalProviderCatalogEntries.mockReturnValue([
{
name: "@openclaw/qwen-provider",
openclaw: {
plugin: { id: "qwen", label: "Qwen" },
install: { npmSpec: "@openclaw/qwen-provider" },
providers: [
{
id: "qwen",
name: "Qwen",
authChoices: [
{ method: "api-key", choiceId: "same-official", choiceLabel: "Same" },
{ method: "api-key", choiceId: "same-first", choiceLabel: "A shadow" },
],
},
],
},
},
]);
expect(
resolveProviderInstallCatalogEntries().map(({ choiceId, pluginId }) => ({
choiceId,
pluginId,
})),
).toEqual([
{ choiceId: "first", pluginId: "vllm" },
{ choiceId: "same-first", pluginId: "vllm" },
{ choiceId: "same-second", pluginId: "vllm" },
{ choiceId: "same-official", pluginId: "qwen" },
{ choiceId: "last", pluginId: "vllm" },
]);
});
it("prefers durable install records over package-authored install intent", () => {
loadPluginRegistrySnapshot.mockReturnValue(
registrySnapshot({

View file

@ -311,25 +311,23 @@ export function resolveProviderInstallCatalogEntries(
const installParams = params ?? {};
const { installedPluginIds, installsByPluginId } =
resolvePreferredInstallsByPluginId(installParams);
const manifestEntries = resolveManifestProviderAuthChoices(params)
.flatMap((choice) => {
const install = installsByPluginId.get(choice.pluginId);
if (!install) {
return [];
}
return [
{
...choice,
label: choice.groupLabel ?? choice.choiceLabel,
origin: install.origin,
install: install.install,
installSource: describePluginInstallSource(install.install, {
expectedPackageName: install.packageName,
}),
} satisfies ProviderInstallCatalogEntry,
];
})
.toSorted((left, right) => left.choiceLabel.localeCompare(right.choiceLabel));
const manifestEntries = resolveManifestProviderAuthChoices(params).flatMap((choice) => {
const install = installsByPluginId.get(choice.pluginId);
if (!install) {
return [];
}
return [
{
...choice,
label: choice.groupLabel ?? choice.choiceLabel,
origin: install.origin,
install: install.install,
installSource: describePluginInstallSource(install.install, {
expectedPackageName: install.packageName,
}),
} satisfies ProviderInstallCatalogEntry,
];
});
const seenChoiceIds = new Set(manifestEntries.map((entry) => entry.choiceId));
const officialEntries = resolveOfficialExternalProviderInstallCatalogEntries({
installedPluginIds,