feat(secrets): expand onboarding secret-ref flows and custom-provider parity

This commit is contained in:
joshavant
2026-02-24 22:26:33 -06:00
committed by Peter Steinberger
parent e8637c79b3
commit 5e3a86fd2f
23 changed files with 857 additions and 417 deletions

View File

@@ -7,6 +7,7 @@ import { resolveEnvApiKey } from "../../agents/model-auth.js";
import type { OpenClawConfig } from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js";
import { normalizeOptionalSecretInput } from "../../utils/normalize-secret-input.js";
import type { SecretInputMode } from "../onboard-types.js";
export type NonInteractiveApiKeySource = "flag" | "env" | "profile";
@@ -50,23 +51,38 @@ export async function resolveNonInteractiveApiKey(params: {
agentDir?: string;
allowProfile?: boolean;
required?: boolean;
secretInputMode?: SecretInputMode;
}): Promise<{ key: string; source: NonInteractiveApiKeySource } | null> {
const flagKey = normalizeOptionalSecretInput(params.flagValue);
const envResolved = resolveEnvApiKey(params.provider);
const explicitEnvVar = params.envVarName?.trim();
const explicitEnvKey = explicitEnvVar
? normalizeOptionalSecretInput(process.env[explicitEnvVar])
: undefined;
const resolvedEnvKey = envResolved?.apiKey ?? explicitEnvKey;
if (params.secretInputMode === "ref") {
if (!resolvedEnvKey && flagKey) {
params.runtime.error(
[
`${params.flagName} cannot be used with --secret-input-mode ref unless ${params.envVar} is set in env.`,
`Set ${params.envVar} in env and omit ${params.flagName}, or use --secret-input-mode plaintext.`,
].join("\n"),
);
params.runtime.exit(1);
return null;
}
if (resolvedEnvKey) {
return { key: resolvedEnvKey, source: "env" };
}
}
if (flagKey) {
return { key: flagKey, source: "flag" };
}
const envResolved = resolveEnvApiKey(params.provider);
if (envResolved?.apiKey) {
return { key: envResolved.apiKey, source: "env" };
}
const explicitEnvVar = params.envVarName?.trim();
if (explicitEnvVar) {
const explicitEnvKey = normalizeOptionalSecretInput(process.env[explicitEnvVar]);
if (explicitEnvKey) {
return { key: explicitEnvKey, source: "env" };
}
if (resolvedEnvKey) {
return { key: resolvedEnvKey, source: "env" };
}
if (params.allowProfile ?? true) {

View File

@@ -2,6 +2,7 @@ import { upsertAuthProfile } from "../../../agents/auth-profiles.js";
import { normalizeProviderId } from "../../../agents/model-selection.js";
import { parseDurationMs } from "../../../cli/parse-duration.js";
import type { OpenClawConfig } from "../../../config/config.js";
import type { SecretInput } from "../../../config/types.secrets.js";
import type { RuntimeEnv } from "../../../runtime.js";
import { normalizeSecretInput } from "../../../utils/normalize-secret-input.js";
import { normalizeSecretInputModeInput } from "../../auth-choice.apply-helpers.js";
@@ -84,6 +85,13 @@ export async function applyNonInteractiveAuthChoice(params: {
const apiKeyStorageOptions = requestedSecretInputMode
? { secretInputMode: requestedSecretInputMode }
: undefined;
const resolveApiKey = (
input: Parameters<typeof resolveNonInteractiveApiKey>[0],
): ReturnType<typeof resolveNonInteractiveApiKey> =>
resolveNonInteractiveApiKey({
...input,
secretInputMode: requestedSecretInputMode,
});
if (authChoice === "claude-cli" || authChoice === "codex-cli") {
runtime.error(
@@ -119,7 +127,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "apiKey") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "anthropic",
cfg: baseConfig,
flagValue: opts.anthropicApiKey,
@@ -196,7 +204,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "gemini-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "google",
cfg: baseConfig,
flagValue: opts.geminiApiKey,
@@ -225,7 +233,7 @@ export async function applyNonInteractiveAuthChoice(params: {
authChoice === "zai-global" ||
authChoice === "zai-cn"
) {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "zai",
cfg: baseConfig,
flagValue: opts.zaiApiKey,
@@ -274,7 +282,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "xiaomi-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "xiaomi",
cfg: baseConfig,
flagValue: opts.xiaomiApiKey,
@@ -297,7 +305,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "xai-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "xai",
cfg: baseConfig,
flagValue: opts.xaiApiKey,
@@ -320,7 +328,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "mistral-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "mistral",
cfg: baseConfig,
flagValue: opts.mistralApiKey,
@@ -343,7 +351,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "volcengine-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "volcengine",
cfg: baseConfig,
flagValue: opts.volcengineApiKey,
@@ -366,7 +374,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "byteplus-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "byteplus",
cfg: baseConfig,
flagValue: opts.byteplusApiKey,
@@ -389,7 +397,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "qianfan-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "qianfan",
cfg: baseConfig,
flagValue: opts.qianfanApiKey,
@@ -412,7 +420,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "openai-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "openai",
cfg: baseConfig,
flagValue: opts.openaiApiKey,
@@ -435,7 +443,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "openrouter-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "openrouter",
cfg: baseConfig,
flagValue: opts.openrouterApiKey,
@@ -458,7 +466,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "kilocode-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "kilocode",
cfg: baseConfig,
flagValue: opts.kilocodeApiKey,
@@ -481,7 +489,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "litellm-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "litellm",
cfg: baseConfig,
flagValue: opts.litellmApiKey,
@@ -504,7 +512,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "ai-gateway-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "vercel-ai-gateway",
cfg: baseConfig,
flagValue: opts.aiGatewayApiKey,
@@ -539,7 +547,7 @@ export async function applyNonInteractiveAuthChoice(params: {
runtime.exit(1);
return null;
}
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "cloudflare-ai-gateway",
cfg: baseConfig,
flagValue: opts.cloudflareAiGatewayApiKey,
@@ -573,7 +581,7 @@ export async function applyNonInteractiveAuthChoice(params: {
const applyMoonshotApiKeyChoice = async (
applyConfig: (cfg: OpenClawConfig) => OpenClawConfig,
): Promise<OpenClawConfig | null> => {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "moonshot",
cfg: baseConfig,
flagValue: opts.moonshotApiKey,
@@ -604,7 +612,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "kimi-code-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "kimi-coding",
cfg: baseConfig,
flagValue: opts.kimiCodeApiKey,
@@ -627,7 +635,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "synthetic-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "synthetic",
cfg: baseConfig,
flagValue: opts.syntheticApiKey,
@@ -650,7 +658,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "venice-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "venice",
cfg: baseConfig,
flagValue: opts.veniceApiKey,
@@ -681,7 +689,7 @@ export async function applyNonInteractiveAuthChoice(params: {
const isCn = authChoice === "minimax-api-key-cn";
const providerId = isCn ? "minimax-cn" : "minimax";
const profileId = `${providerId}:default`;
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: providerId,
cfg: baseConfig,
flagValue: opts.minimaxApiKey,
@@ -712,7 +720,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "opencode-zen") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "opencode",
cfg: baseConfig,
flagValue: opts.opencodeZenApiKey,
@@ -735,7 +743,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "together-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "together",
cfg: baseConfig,
flagValue: opts.togetherApiKey,
@@ -758,7 +766,7 @@ export async function applyNonInteractiveAuthChoice(params: {
}
if (authChoice === "huggingface-api-key") {
const resolved = await resolveNonInteractiveApiKey({
const resolved = await resolveApiKey({
provider: "huggingface",
cfg: baseConfig,
flagValue: opts.huggingfaceApiKey,
@@ -794,7 +802,7 @@ export async function applyNonInteractiveAuthChoice(params: {
baseUrl: customAuth.baseUrl,
providerId: customAuth.providerId,
});
const resolvedCustomApiKey = await resolveNonInteractiveApiKey({
const resolvedCustomApiKey = await resolveApiKey({
provider: resolvedProviderId.providerId,
cfg: baseConfig,
flagValue: customAuth.apiKey,
@@ -804,12 +812,16 @@ export async function applyNonInteractiveAuthChoice(params: {
runtime,
required: false,
});
const customApiKeyInput: SecretInput | undefined =
requestedSecretInputMode === "ref" && resolvedCustomApiKey?.source === "env"
? { source: "env", id: "CUSTOM_API_KEY" }
: resolvedCustomApiKey?.key;
const result = applyCustomApiConfig({
config: nextConfig,
baseUrl: customAuth.baseUrl,
modelId: customAuth.modelId,
compatibility: customAuth.compatibility,
apiKey: resolvedCustomApiKey?.key,
apiKey: customApiKeyInput,
providerId: customAuth.providerId,
});
if (result.providerIdRenamedFrom && result.providerId) {