Commands: lazy-load non-interactive plugin provider runtime (#47593)

* Commands: lazy-load non-interactive plugin provider runtime

* Tests: cover non-interactive plugin provider ordering

* Update src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.runtime.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Vincent Koc 2026-03-15 14:37:41 -07:00 committed by GitHub
parent 50c8934231
commit b810e94a17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 136 additions and 23 deletions

View File

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

View File

@ -0,0 +1,54 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { applyNonInteractivePluginProviderChoice } from "./auth-choice.plugin-providers.js";
const resolvePreferredProviderForAuthChoice = vi.hoisted(() => vi.fn(async () => undefined));
vi.mock("../../auth-choice.preferred-provider.js", () => ({
resolvePreferredProviderForAuthChoice,
}));
const resolveProviderPluginChoice = vi.hoisted(() => vi.fn());
const resolvePluginProviders = vi.hoisted(() => vi.fn(() => []));
vi.mock("./auth-choice.plugin-providers.runtime.js", () => ({
resolveProviderPluginChoice,
resolvePluginProviders,
PROVIDER_PLUGIN_CHOICE_PREFIX: "provider-plugin:",
}));
beforeEach(() => {
vi.clearAllMocks();
});
function createRuntime() {
return {
error: vi.fn(),
exit: vi.fn(),
};
}
describe("applyNonInteractivePluginProviderChoice", () => {
it("loads plugin providers for provider-plugin auth choices", async () => {
const runtime = createRuntime();
const runNonInteractive = vi.fn(async () => ({ plugins: { allow: ["vllm"] } }));
resolvePluginProviders.mockReturnValue([{ id: "vllm", pluginId: "vllm" }] as never);
resolveProviderPluginChoice.mockReturnValue({
provider: { id: "vllm", pluginId: "vllm", label: "vLLM" },
method: { runNonInteractive },
});
const result = await applyNonInteractivePluginProviderChoice({
nextConfig: { agents: { defaults: {} } } as OpenClawConfig,
authChoice: "provider-plugin:vllm:custom",
opts: {} as never,
runtime: runtime as never,
baseConfig: { agents: { defaults: {} } } as OpenClawConfig,
resolveApiKey: vi.fn(),
toApiKeyCredential: vi.fn(),
});
expect(resolvePluginProviders).toHaveBeenCalledOnce();
expect(resolveProviderPluginChoice).toHaveBeenCalledOnce();
expect(runNonInteractive).toHaveBeenCalledOnce();
expect(result).toEqual({ plugins: { allow: ["vllm"] } });
});
});

View File

@ -3,11 +3,6 @@ import type { ApiKeyCredential } from "../../../agents/auth-profiles/types.js";
import { resolveDefaultAgentWorkspaceDir } from "../../../agents/workspace.js";
import type { OpenClawConfig } from "../../../config/config.js";
import { enablePluginInConfig } from "../../../plugins/enable.js";
import {
PROVIDER_PLUGIN_CHOICE_PREFIX,
resolveProviderPluginChoice,
} from "../../../plugins/provider-wizard.js";
import { resolvePluginProviders } from "../../../plugins/providers.js";
import type {
ProviderNonInteractiveApiKeyCredentialParams,
ProviderResolveNonInteractiveApiKeyParams,
@ -16,6 +11,12 @@ import type { RuntimeEnv } from "../../../runtime.js";
import { resolvePreferredProviderForAuthChoice } from "../../auth-choice.preferred-provider.js";
import type { OnboardOptions } from "../../onboard-types.js";
const PROVIDER_PLUGIN_CHOICE_PREFIX = "provider-plugin:";
async function loadPluginProviderRuntime() {
return import("./auth-choice.plugin-providers.runtime.js");
}
function buildIsolatedProviderResolutionConfig(
cfg: OpenClawConfig,
providerId: string | undefined,
@ -73,6 +74,7 @@ export async function applyNonInteractivePluginProviderChoice(params: {
params.nextConfig,
preferredProviderId,
);
const { resolveProviderPluginChoice, resolvePluginProviders } = await loadPluginProviderRuntime();
const providerChoice = resolveProviderPluginChoice({
providers: resolvePluginProviders({
config: resolutionConfig,

View File

@ -0,0 +1,53 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { applyNonInteractiveAuthChoice } from "./auth-choice.js";
const applySimpleNonInteractiveApiKeyChoice = vi.hoisted(() =>
vi.fn<() => Promise<OpenClawConfig | null | undefined>>(async () => undefined),
);
vi.mock("./auth-choice.api-key-providers.js", () => ({
applySimpleNonInteractiveApiKeyChoice,
}));
const applyNonInteractivePluginProviderChoice = vi.hoisted(() => vi.fn(async () => undefined));
vi.mock("./auth-choice.plugin-providers.js", () => ({
applyNonInteractivePluginProviderChoice,
}));
const resolveNonInteractiveApiKey = vi.hoisted(() => vi.fn());
vi.mock("../api-keys.js", () => ({
resolveNonInteractiveApiKey,
}));
beforeEach(() => {
vi.clearAllMocks();
});
function createRuntime() {
return {
error: vi.fn(),
exit: vi.fn(),
log: vi.fn(),
};
}
describe("applyNonInteractiveAuthChoice", () => {
it("resolves builtin API key auth before plugin provider resolution", async () => {
const runtime = createRuntime();
const nextConfig = { agents: { defaults: {} } } as OpenClawConfig;
const resolvedConfig = { auth: { profiles: { "openai:default": { mode: "api_key" } } } };
applySimpleNonInteractiveApiKeyChoice.mockResolvedValueOnce(resolvedConfig as never);
const result = await applyNonInteractiveAuthChoice({
nextConfig,
authChoice: "openai-api-key",
opts: {} as never,
runtime: runtime as never,
baseConfig: nextConfig,
});
expect(result).toBe(resolvedConfig);
expect(applySimpleNonInteractiveApiKeyChoice).toHaveBeenCalledOnce();
expect(applyNonInteractivePluginProviderChoice).not.toHaveBeenCalled();
});
});

View File

@ -161,24 +161,6 @@ export async function applyNonInteractiveAuthChoice(params: {
return null;
}
const pluginProviderChoice = await applyNonInteractivePluginProviderChoice({
nextConfig,
authChoice,
opts,
runtime,
baseConfig,
resolveApiKey: (input) =>
resolveApiKey({
...input,
cfg: baseConfig,
runtime,
}),
toApiKeyCredential,
});
if (pluginProviderChoice !== undefined) {
return pluginProviderChoice;
}
if (authChoice === "token") {
const providerRaw = opts.tokenProvider?.trim();
if (!providerRaw) {
@ -484,6 +466,24 @@ export async function applyNonInteractiveAuthChoice(params: {
}
}
const pluginProviderChoice = await applyNonInteractivePluginProviderChoice({
nextConfig,
authChoice,
opts,
runtime,
baseConfig,
resolveApiKey: (input) =>
resolveApiKey({
...input,
cfg: baseConfig,
runtime,
}),
toApiKeyCredential,
});
if (pluginProviderChoice !== undefined) {
return pluginProviderChoice;
}
if (
authChoice === "oauth" ||
authChoice === "chutes" ||