diff --git a/src/commands/model-picker.runtime.ts b/src/commands/model-picker.runtime.ts new file mode 100644 index 00000000000..74c4f68c605 --- /dev/null +++ b/src/commands/model-picker.runtime.ts @@ -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"; diff --git a/src/commands/model-picker.test.ts b/src/commands/model-picker.test.ts index ef8b6a3887b..ce8c4bfb9f6 100644 --- a/src/commands/model-picker.test.ts +++ b/src/commands/model-picker.test.ts @@ -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(); }); }); diff --git a/src/commands/model-picker.ts b/src/commands/model-picker.ts index 2e97a01a977..64d9e533e1f 100644 --- a/src/commands/model-picker.ts +++ b/src/commands/model-picker.ts @@ -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,