fix(providers): keep setup guidance cold

This commit is contained in:
Vincent Koc
2026-04-25 23:01:53 -07:00
parent b979f2964c
commit 585784643e
3 changed files with 66 additions and 22 deletions

View File

@@ -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(),

View File

@@ -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<Record<string, string>>,
): string {
const normalized = normalizeProviderId(providerId);
return normalized ? (aliases[normalized] ?? normalized) : normalized;
}
function matchesProviderAuthChoice(
choice: { providerId: string },
providerId: string,
aliases: Readonly<Record<string, string>>,
): 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: {

View File

@@ -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,
);
}
}
});
});