From 585784643e0014ca012982bea44808bb35f6c468 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 23:01:53 -0700 Subject: [PATCH] fix(providers): keep setup guidance cold --- src/commands/configure.gateway-auth.ts | 13 ++++--- src/commands/provider-auth-guidance.ts | 37 ++++++++++-------- .../provider-setup-cold-imports.test.ts | 38 +++++++++++++++++++ 3 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 src/commands/provider-setup-cold-imports.test.ts diff --git a/src/commands/configure.gateway-auth.ts b/src/commands/configure.gateway-auth.ts index 7ce8e3f7b76..a507356f80a 100644 --- a/src/commands/configure.gateway-auth.ts +++ b/src/commands/configure.gateway-auth.ts @@ -2,8 +2,6 @@ import { ensureAuthProfileStore } from "../agents/auth-profiles.js"; import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js"; import type { OpenClawConfig, GatewayAuthConfig } from "../config/config.js"; import { isSecretRef, type SecretInput } from "../config/types.secrets.js"; -import { resolveProviderPluginChoice } from "../plugins/provider-wizard.js"; -import { resolvePluginProviders } from "../plugins/providers.runtime.js"; import type { RuntimeEnv } from "../runtime.js"; import type { WizardPrompter } from "../wizard/prompts.js"; import { promptAuthChoiceGrouped } from "./auth-choice-prompt.js"; @@ -32,18 +30,21 @@ function sanitizeTokenValue(value: unknown): string | undefined { return trimmed; } -function resolveProviderChoiceModelAllowlist(params: { +async function resolveProviderChoiceModelAllowlist(params: { authChoice: string; config: OpenClawConfig; workspaceDir?: string; env?: NodeJS.ProcessEnv; -}): +}): Promise< | { allowedKeys?: string[]; initialSelections?: string[]; message?: string; } - | undefined { + | undefined +> { + const { resolvePluginProviders, resolveProviderPluginChoice } = + await import("../plugins/provider-auth-choice.runtime.js"); const providers = resolvePluginProviders({ config: params.config, workspaceDir: params.workspaceDir, @@ -162,7 +163,7 @@ export async function promptAuthConfig( } if (authChoice !== "custom-api-key") { - const modelAllowlist = resolveProviderChoiceModelAllowlist({ + const modelAllowlist = await resolveProviderChoiceModelAllowlist({ authChoice, config: next, workspaceDir: resolveDefaultAgentWorkspaceDir(), diff --git a/src/commands/provider-auth-guidance.ts b/src/commands/provider-auth-guidance.ts index 947dfa5ca51..ea15c1c20b0 100644 --- a/src/commands/provider-auth-guidance.ts +++ b/src/commands/provider-auth-guidance.ts @@ -1,20 +1,27 @@ import { normalizeProviderId } from "../agents/model-selection.js"; +import { resolveProviderAuthAliasMap } from "../agents/provider-auth-aliases.js"; import { formatCliCommand } from "../cli/command-format.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { resolvePluginProviders } from "../plugins/providers.runtime.js"; +import { resolveManifestProviderAuthChoices } from "../plugins/provider-auth-choices.js"; -function matchesProviderId( - candidate: { id: string; aliases?: string[] | readonly string[] }, +function normalizeProviderIdForAuth( providerId: string, -): boolean { + aliases: Readonly>, +): string { const normalized = normalizeProviderId(providerId); + return normalized ? (aliases[normalized] ?? normalized) : normalized; +} + +function matchesProviderAuthChoice( + choice: { providerId: string }, + providerId: string, + aliases: Readonly>, +): boolean { + const normalized = normalizeProviderIdForAuth(providerId, aliases); if (!normalized) { return false; } - if (normalizeProviderId(candidate.id) === normalized) { - return true; - } - return (candidate.aliases ?? []).some((alias) => normalizeProviderId(alias) === normalized); + return normalizeProviderIdForAuth(choice.providerId, aliases) === normalized; } export function resolveProviderAuthLoginCommand(params: { @@ -23,16 +30,14 @@ export function resolveProviderAuthLoginCommand(params: { workspaceDir?: string; env?: NodeJS.ProcessEnv; }): string | undefined { - const provider = resolvePluginProviders({ - config: params.config, - workspaceDir: params.workspaceDir, - env: params.env, - mode: "setup", - }).find((candidate) => matchesProviderId(candidate, params.provider)); - if (!provider || provider.auth.length === 0) { + const aliases = resolveProviderAuthAliasMap(params); + const choice = resolveManifestProviderAuthChoices(params).find((candidate) => + matchesProviderAuthChoice(candidate, params.provider, aliases), + ); + if (!choice) { return undefined; } - return formatCliCommand(`openclaw models auth login --provider ${provider.id}`); + return formatCliCommand(`openclaw models auth login --provider ${choice.providerId}`); } export function buildProviderAuthRecoveryHint(params: { diff --git a/src/commands/provider-setup-cold-imports.test.ts b/src/commands/provider-setup-cold-imports.test.ts new file mode 100644 index 00000000000..2c843544aa8 --- /dev/null +++ b/src/commands/provider-setup-cold-imports.test.ts @@ -0,0 +1,38 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const repoRoot = fileURLToPath(new URL("../..", import.meta.url)); + +const coldProviderSetupFiles = [ + "src/commands/auth-choice-options.ts", + "src/commands/configure.gateway-auth.ts", + "src/commands/provider-auth-guidance.ts", + "src/flows/provider-flow.ts", + "src/plugins/provider-auth-choices.ts", + "src/plugins/provider-install-catalog.ts", +] as const; + +const forbiddenRuntimeImports = [ + "providers.runtime.js", + "provider-wizard.js", + "provider-flow.runtime.js", + "provider-auth-choice.runtime.js", +] as const; + +describe("provider setup cold imports", () => { + it("keeps auth/setup/configure metadata callers off static provider runtime imports", () => { + for (const file of coldProviderSetupFiles) { + const source = fs.readFileSync(path.join(repoRoot, file), "utf8"); + for (const importPath of forbiddenRuntimeImports) { + const staticImportPattern = new RegExp( + `\\bfrom\\s+["'][^"']*${importPath.replaceAll(".", "\\.")}["']`, + ); + expect(source, `${file} must not statically import ${importPath}`).not.toMatch( + staticImportPattern, + ); + } + } + }); +});