mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 10:10:18 +00:00
refactor(auth-choice): unify api-key resolution flows
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import { formatApiKeyPreview } from "./auth-choice.api-key.js";
|
||||
import type { ApplyAuthChoiceParams } from "./auth-choice.apply.js";
|
||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||
|
||||
export function createAuthChoiceAgentModelNoter(
|
||||
params: ApplyAuthChoiceParams,
|
||||
@@ -13,3 +17,152 @@ export function createAuthChoiceAgentModelNoter(
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApplyAuthChoiceModelState {
|
||||
config: ApplyAuthChoiceParams["config"];
|
||||
agentModelOverride: string | undefined;
|
||||
}
|
||||
|
||||
export function createAuthChoiceModelStateBridge(bindings: {
|
||||
getConfig: () => ApplyAuthChoiceParams["config"];
|
||||
setConfig: (config: ApplyAuthChoiceParams["config"]) => void;
|
||||
getAgentModelOverride: () => string | undefined;
|
||||
setAgentModelOverride: (model: string | undefined) => void;
|
||||
}): ApplyAuthChoiceModelState {
|
||||
return {
|
||||
get config() {
|
||||
return bindings.getConfig();
|
||||
},
|
||||
set config(config) {
|
||||
bindings.setConfig(config);
|
||||
},
|
||||
get agentModelOverride() {
|
||||
return bindings.getAgentModelOverride();
|
||||
},
|
||||
set agentModelOverride(model) {
|
||||
bindings.setAgentModelOverride(model);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createAuthChoiceDefaultModelApplier(
|
||||
params: ApplyAuthChoiceParams,
|
||||
state: ApplyAuthChoiceModelState,
|
||||
): (
|
||||
options: Omit<
|
||||
Parameters<typeof applyDefaultModelChoice>[0],
|
||||
"config" | "setDefaultModel" | "noteAgentModel" | "prompter"
|
||||
>,
|
||||
) => Promise<void> {
|
||||
const noteAgentModel = createAuthChoiceAgentModelNoter(params);
|
||||
|
||||
return async (options) => {
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: state.config,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
...options,
|
||||
});
|
||||
state.config = applied.config;
|
||||
state.agentModelOverride = applied.agentModelOverride ?? state.agentModelOverride;
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeTokenProviderInput(
|
||||
tokenProvider: string | null | undefined,
|
||||
): string | undefined {
|
||||
const normalized = String(tokenProvider ?? "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
export async function maybeApplyApiKeyFromOption(params: {
|
||||
token: string | undefined;
|
||||
tokenProvider: string | undefined;
|
||||
expectedProviders: string[];
|
||||
normalize: (value: string) => string;
|
||||
setCredential: (apiKey: string) => Promise<void>;
|
||||
}): Promise<string | undefined> {
|
||||
const tokenProvider = normalizeTokenProviderInput(params.tokenProvider);
|
||||
const expectedProviders = params.expectedProviders
|
||||
.map((provider) => normalizeTokenProviderInput(provider))
|
||||
.filter((provider): provider is string => Boolean(provider));
|
||||
if (!params.token || !tokenProvider || !expectedProviders.includes(tokenProvider)) {
|
||||
return undefined;
|
||||
}
|
||||
const apiKey = params.normalize(params.token);
|
||||
await params.setCredential(apiKey);
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
export async function ensureApiKeyFromOptionEnvOrPrompt(params: {
|
||||
token: string | undefined;
|
||||
tokenProvider: string | undefined;
|
||||
expectedProviders: string[];
|
||||
provider: string;
|
||||
envLabel: string;
|
||||
promptMessage: string;
|
||||
normalize: (value: string) => string;
|
||||
validate: (value: string) => string | undefined;
|
||||
prompter: WizardPrompter;
|
||||
setCredential: (apiKey: string) => Promise<void>;
|
||||
noteMessage?: string;
|
||||
noteTitle?: string;
|
||||
}): Promise<string> {
|
||||
const optionApiKey = await maybeApplyApiKeyFromOption({
|
||||
token: params.token,
|
||||
tokenProvider: params.tokenProvider,
|
||||
expectedProviders: params.expectedProviders,
|
||||
normalize: params.normalize,
|
||||
setCredential: params.setCredential,
|
||||
});
|
||||
if (optionApiKey) {
|
||||
return optionApiKey;
|
||||
}
|
||||
|
||||
if (params.noteMessage) {
|
||||
await params.prompter.note(params.noteMessage, params.noteTitle);
|
||||
}
|
||||
|
||||
return await ensureApiKeyFromEnvOrPrompt({
|
||||
provider: params.provider,
|
||||
envLabel: params.envLabel,
|
||||
promptMessage: params.promptMessage,
|
||||
normalize: params.normalize,
|
||||
validate: params.validate,
|
||||
prompter: params.prompter,
|
||||
setCredential: params.setCredential,
|
||||
});
|
||||
}
|
||||
|
||||
export async function ensureApiKeyFromEnvOrPrompt(params: {
|
||||
provider: string;
|
||||
envLabel: string;
|
||||
promptMessage: string;
|
||||
normalize: (value: string) => string;
|
||||
validate: (value: string) => string | undefined;
|
||||
prompter: WizardPrompter;
|
||||
setCredential: (apiKey: string) => Promise<void>;
|
||||
}): Promise<string> {
|
||||
const envKey = resolveEnvApiKey(params.provider);
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing ${params.envLabel} (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
await params.setCredential(envKey.apiKey);
|
||||
return envKey.apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
const key = await params.prompter.text({
|
||||
message: params.promptMessage,
|
||||
validate: params.validate,
|
||||
});
|
||||
const apiKey = params.normalize(String(key ?? ""));
|
||||
await params.setCredential(apiKey);
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,11 @@ import {
|
||||
discoverHuggingfaceModels,
|
||||
isHuggingfacePolicyLocked,
|
||||
} from "../agents/huggingface-models.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
validateApiKeyInput,
|
||||
} from "./auth-choice.api-key.js";
|
||||
import { createAuthChoiceAgentModelNoter } from "./auth-choice.apply-helpers.js";
|
||||
createAuthChoiceAgentModelNoter,
|
||||
ensureApiKeyFromOptionEnvOrPrompt,
|
||||
} from "./auth-choice.apply-helpers.js";
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||
import { ensureModelAllowlistEntry } from "./model-allowlist.js";
|
||||
@@ -30,47 +28,23 @@ export async function applyAuthChoiceHuggingface(
|
||||
let agentModelOverride: string | undefined;
|
||||
const noteAgentModel = createAuthChoiceAgentModelNoter(params);
|
||||
|
||||
let hasCredential = false;
|
||||
let hfKey = "";
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts.tokenProvider === "huggingface") {
|
||||
hfKey = normalizeApiKeyInput(params.opts.token);
|
||||
await setHuggingfaceApiKey(hfKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Hugging Face Inference Providers offer OpenAI-compatible chat completions.",
|
||||
"Create a token at: https://huggingface.co/settings/tokens (fine-grained, 'Make calls to Inference Providers').",
|
||||
].join("\n"),
|
||||
"Hugging Face",
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
const envKey = resolveEnvApiKey("huggingface");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing Hugging Face token (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
hfKey = envKey.apiKey;
|
||||
await setHuggingfaceApiKey(hfKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter Hugging Face API key (HF token)",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
hfKey = normalizeApiKeyInput(String(key ?? ""));
|
||||
await setHuggingfaceApiKey(hfKey, params.agentDir);
|
||||
}
|
||||
const hfKey = await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
token: params.opts?.token,
|
||||
tokenProvider: params.opts?.tokenProvider,
|
||||
expectedProviders: ["huggingface"],
|
||||
provider: "huggingface",
|
||||
envLabel: "Hugging Face token",
|
||||
promptMessage: "Enter Hugging Face API key (HF token)",
|
||||
normalize: normalizeApiKeyInput,
|
||||
validate: validateApiKeyInput,
|
||||
prompter: params.prompter,
|
||||
setCredential: async (apiKey) => setHuggingfaceApiKey(apiKey, params.agentDir),
|
||||
noteMessage: [
|
||||
"Hugging Face Inference Providers offer OpenAI-compatible chat completions.",
|
||||
"Create a token at: https://huggingface.co/settings/tokens (fine-grained, 'Make calls to Inference Providers').",
|
||||
].join("\n"),
|
||||
noteTitle: "Hugging Face",
|
||||
});
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "huggingface:default",
|
||||
provider: "huggingface",
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
validateApiKeyInput,
|
||||
} from "./auth-choice.api-key.js";
|
||||
import { createAuthChoiceAgentModelNoter } from "./auth-choice.apply-helpers.js";
|
||||
createAuthChoiceDefaultModelApplier,
|
||||
createAuthChoiceModelStateBridge,
|
||||
ensureApiKeyFromOptionEnvOrPrompt,
|
||||
} from "./auth-choice.apply-helpers.js";
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import { applyAuthChoicePluginProvider } from "./auth-choice.apply.plugin-provider.js";
|
||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
applyMinimaxApiConfig,
|
||||
@@ -24,31 +22,64 @@ export async function applyAuthChoiceMiniMax(
|
||||
): Promise<ApplyAuthChoiceResult | null> {
|
||||
let nextConfig = params.config;
|
||||
let agentModelOverride: string | undefined;
|
||||
const applyProviderDefaultModel = createAuthChoiceDefaultModelApplier(
|
||||
params,
|
||||
createAuthChoiceModelStateBridge({
|
||||
getConfig: () => nextConfig,
|
||||
setConfig: (config) => (nextConfig = config),
|
||||
getAgentModelOverride: () => agentModelOverride,
|
||||
setAgentModelOverride: (model) => (agentModelOverride = model),
|
||||
}),
|
||||
);
|
||||
const ensureMinimaxApiKey = async (opts: {
|
||||
profileId: string;
|
||||
promptMessage: string;
|
||||
}): Promise<void> => {
|
||||
let hasCredential = false;
|
||||
const envKey = resolveEnvApiKey("minimax");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing MINIMAX_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
await setMinimaxApiKey(envKey.apiKey, params.agentDir, opts.profileId);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: opts.promptMessage,
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
await setMinimaxApiKey(normalizeApiKeyInput(String(key)), params.agentDir, opts.profileId);
|
||||
}
|
||||
await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
token: params.opts?.token,
|
||||
tokenProvider: params.opts?.tokenProvider,
|
||||
expectedProviders: ["minimax", "minimax-cn"],
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: opts.promptMessage,
|
||||
normalize: normalizeApiKeyInput,
|
||||
validate: validateApiKeyInput,
|
||||
prompter: params.prompter,
|
||||
setCredential: async (apiKey) => setMinimaxApiKey(apiKey, params.agentDir, opts.profileId),
|
||||
});
|
||||
};
|
||||
const applyMinimaxApiVariant = async (opts: {
|
||||
profileId: string;
|
||||
provider: "minimax" | "minimax-cn";
|
||||
promptMessage: string;
|
||||
modelRefPrefix: "minimax" | "minimax-cn";
|
||||
modelId: string;
|
||||
applyDefaultConfig: (
|
||||
config: ApplyAuthChoiceParams["config"],
|
||||
modelId: string,
|
||||
) => ApplyAuthChoiceParams["config"];
|
||||
applyProviderConfig: (
|
||||
config: ApplyAuthChoiceParams["config"],
|
||||
modelId: string,
|
||||
) => ApplyAuthChoiceParams["config"];
|
||||
}): Promise<ApplyAuthChoiceResult> => {
|
||||
await ensureMinimaxApiKey({
|
||||
profileId: opts.profileId,
|
||||
promptMessage: opts.promptMessage,
|
||||
});
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: opts.profileId,
|
||||
provider: opts.provider,
|
||||
mode: "api_key",
|
||||
});
|
||||
const modelRef = `${opts.modelRefPrefix}/${opts.modelId}`;
|
||||
await applyProviderDefaultModel({
|
||||
defaultModel: modelRef,
|
||||
applyDefaultConfig: (config) => opts.applyDefaultConfig(config, opts.modelId),
|
||||
applyProviderConfig: (config) => opts.applyProviderConfig(config, opts.modelId),
|
||||
});
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
};
|
||||
const noteAgentModel = createAuthChoiceAgentModelNoter(params);
|
||||
if (params.authChoice === "minimax-portal") {
|
||||
// Let user choose between Global/CN endpoints
|
||||
const endpoint = await params.prompter.select({
|
||||
@@ -73,74 +104,36 @@ export async function applyAuthChoiceMiniMax(
|
||||
params.authChoice === "minimax-api" ||
|
||||
params.authChoice === "minimax-api-lightning"
|
||||
) {
|
||||
const modelId =
|
||||
params.authChoice === "minimax-api-lightning" ? "MiniMax-M2.5-Lightning" : "MiniMax-M2.5";
|
||||
await ensureMinimaxApiKey({
|
||||
profileId: "minimax:default",
|
||||
promptMessage: "Enter MiniMax API key",
|
||||
});
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
return await applyMinimaxApiVariant({
|
||||
profileId: "minimax:default",
|
||||
provider: "minimax",
|
||||
mode: "api_key",
|
||||
promptMessage: "Enter MiniMax API key",
|
||||
modelRefPrefix: "minimax",
|
||||
modelId:
|
||||
params.authChoice === "minimax-api-lightning" ? "MiniMax-M2.5-Lightning" : "MiniMax-M2.5",
|
||||
applyDefaultConfig: applyMinimaxApiConfig,
|
||||
applyProviderConfig: applyMinimaxApiProviderConfig,
|
||||
});
|
||||
{
|
||||
const modelRef = `minimax/${modelId}`;
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: modelRef,
|
||||
applyDefaultConfig: (config) => applyMinimaxApiConfig(config, modelId),
|
||||
applyProviderConfig: (config) => applyMinimaxApiProviderConfig(config, modelId),
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
nextConfig = applied.config;
|
||||
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||
}
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (params.authChoice === "minimax-api-key-cn") {
|
||||
const modelId = "MiniMax-M2.5";
|
||||
await ensureMinimaxApiKey({
|
||||
profileId: "minimax-cn:default",
|
||||
promptMessage: "Enter MiniMax China API key",
|
||||
});
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
return await applyMinimaxApiVariant({
|
||||
profileId: "minimax-cn:default",
|
||||
provider: "minimax-cn",
|
||||
mode: "api_key",
|
||||
promptMessage: "Enter MiniMax China API key",
|
||||
modelRefPrefix: "minimax-cn",
|
||||
modelId: "MiniMax-M2.5",
|
||||
applyDefaultConfig: applyMinimaxApiConfigCn,
|
||||
applyProviderConfig: applyMinimaxApiProviderConfigCn,
|
||||
});
|
||||
{
|
||||
const modelRef = `minimax-cn/${modelId}`;
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: modelRef,
|
||||
applyDefaultConfig: (config) => applyMinimaxApiConfigCn(config, modelId),
|
||||
applyProviderConfig: (config) => applyMinimaxApiProviderConfigCn(config, modelId),
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
nextConfig = applied.config;
|
||||
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||
}
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (params.authChoice === "minimax") {
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
await applyProviderDefaultModel({
|
||||
defaultModel: "lmstudio/minimax-m2.1-gs32",
|
||||
applyDefaultConfig: applyMinimaxConfig,
|
||||
applyProviderConfig: applyMinimaxProviderConfig,
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
nextConfig = applied.config;
|
||||
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user