Commands: lazy-load model picker provider runtime (#47536)

* Commands: lazy-load model picker provider runtime

* Tests: cover model picker runtime boundary
This commit is contained in:
Vincent Koc 2026-03-15 10:54:46 -07:00 committed by GitHub
parent 630958749c
commit 438991b6a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 31 deletions

View File

@ -0,0 +1,7 @@
export {
resolveProviderModelPickerEntries,
resolveProviderPluginChoice,
runProviderModelSelectedHook,
} from "../plugins/provider-wizard.js";
export { resolvePluginProviders } from "../plugins/providers.js";
export { runProviderPluginAuthMethod } from "./auth-choice.apply.plugin-provider.js";

View File

@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
applyModelAllowlist,
@ -37,19 +37,13 @@ vi.mock("../agents/model-auth.js", () => ({
const resolveProviderModelPickerEntries = vi.hoisted(() => vi.fn(() => []));
const resolveProviderPluginChoice = vi.hoisted(() => vi.fn());
const runProviderModelSelectedHook = vi.hoisted(() => vi.fn(async () => {}));
vi.mock("../plugins/provider-wizard.js", () => ({
const resolvePluginProviders = vi.hoisted(() => vi.fn(() => []));
const runProviderPluginAuthMethod = vi.hoisted(() => vi.fn());
vi.mock("./model-picker.runtime.js", () => ({
resolveProviderModelPickerEntries,
resolveProviderPluginChoice,
runProviderModelSelectedHook,
}));
const resolvePluginProviders = vi.hoisted(() => vi.fn(() => []));
vi.mock("../plugins/providers.js", () => ({
resolvePluginProviders,
}));
const runProviderPluginAuthMethod = vi.hoisted(() => vi.fn());
vi.mock("./auth-choice.apply.plugin-provider.js", () => ({
runProviderPluginAuthMethod,
}));
@ -77,6 +71,10 @@ function createSelectAllMultiselect() {
return vi.fn(async (params) => params.options.map((option: { value: string }) => option.value));
}
beforeEach(() => {
vi.clearAllMocks();
});
describe("promptDefaultModel", () => {
it("supports configuring vLLM during onboarding", async () => {
loadModelCatalog.mockResolvedValue([
@ -211,6 +209,7 @@ describe("router model filtering", () => {
const allowlistCall = multiselect.mock.calls[0]?.[0];
expectRouterModelFiltering(allowlistCall?.options as Array<{ value: string }>);
expect(allowlistCall?.searchable).toBe(true);
expect(runProviderPluginAuthMethod).not.toHaveBeenCalled();
});
});

View File

@ -11,14 +11,8 @@ import {
} from "../agents/model-selection.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
import {
resolveProviderPluginChoice,
resolveProviderModelPickerEntries,
runProviderModelSelectedHook,
} from "../plugins/provider-wizard.js";
import { resolvePluginProviders } from "../plugins/providers.js";
import type { ProviderPlugin } from "../plugins/types.js";
import type { WizardPrompter, WizardSelectOption } from "../wizard/prompts.js";
import { runProviderPluginAuthMethod } from "./auth-choice.apply.plugin-provider.js";
import { formatTokenK } from "./models/shared.js";
import { OPENAI_CODEX_DEFAULT_MODEL } from "./openai-codex-model-default.js";
@ -49,6 +43,10 @@ type PromptDefaultModelParams = {
type PromptDefaultModelResult = { model?: string; config?: OpenClawConfig };
type PromptModelAllowlistResult = { models?: string[] };
async function loadModelPickerRuntime() {
return import("./model-picker.runtime.js");
}
function hasAuthForProvider(
provider: string,
cfg: OpenClawConfig,
@ -295,6 +293,7 @@ export async function promptDefaultModel(
options.push({ value: MANUAL_VALUE, label: "Enter model manually" });
}
if (includeProviderPluginSetups && agentDir) {
const { resolveProviderModelPickerEntries } = await loadModelPickerRuntime();
options.push(
...resolveProviderModelPickerEntries({
config: cfg,
@ -347,20 +346,24 @@ export async function promptDefaultModel(
initialValue: configuredRaw || resolvedKey || undefined,
});
}
const pluginProviders = resolvePluginProviders({
config: cfg,
workspaceDir: params.workspaceDir,
env: params.env,
});
const pluginResolution = selection.startsWith("provider-plugin:")
? selection
: selection.includes("/")
? null
: pluginProviders.some(
(provider) => normalizeProviderId(provider.id) === normalizeProviderId(selection),
)
? selection
: null;
let pluginResolution: string | null = null;
let pluginProviders: ProviderPlugin[] = [];
if (selection.startsWith("provider-plugin:")) {
pluginResolution = selection;
} else if (!selection.includes("/")) {
const { resolvePluginProviders } = await loadModelPickerRuntime();
pluginProviders = resolvePluginProviders({
config: cfg,
workspaceDir: params.workspaceDir,
env: params.env,
});
pluginResolution = pluginProviders.some(
(provider) => normalizeProviderId(provider.id) === normalizeProviderId(selection),
)
? selection
: null;
}
if (pluginResolution) {
if (!agentDir || !params.runtime) {
await params.prompter.note(
@ -369,6 +372,19 @@ export async function promptDefaultModel(
);
return {};
}
const {
resolvePluginProviders,
resolveProviderPluginChoice,
runProviderModelSelectedHook,
runProviderPluginAuthMethod,
} = await loadModelPickerRuntime();
if (pluginProviders.length === 0) {
pluginProviders = resolvePluginProviders({
config: cfg,
workspaceDir: params.workspaceDir,
env: params.env,
});
}
const resolved = resolveProviderPluginChoice({
providers: pluginProviders,
choice: pluginResolution,
@ -397,6 +413,7 @@ export async function promptDefaultModel(
return { model: applied.defaultModel, config: applied.config };
}
const model = String(selection);
const { runProviderModelSelectedHook } = await loadModelPickerRuntime();
await runProviderModelSelectedHook({
config: cfg,
model,