mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 07:21:23 +00:00
Onboard: require explicit mode for env secret refs
This commit is contained in:
committed by
Peter Steinberger
parent
103d02f98c
commit
04aa856fc0
@@ -8,6 +8,7 @@ import { isSecretRef, type SecretInput, type SecretRef } from "../config/types.s
|
||||
import { KILOCODE_DEFAULT_MODEL_REF } from "../providers/kilocode-shared.js";
|
||||
import { PROVIDER_ENV_VARS } from "../secrets/provider-env-vars.js";
|
||||
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
|
||||
import type { SecretInputMode } from "./onboard-types.js";
|
||||
export { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "../agents/cloudflare-ai-gateway.js";
|
||||
export { MISTRAL_DEFAULT_MODEL_REF, XAI_DEFAULT_MODEL_REF } from "./onboard-auth.models.js";
|
||||
export { KILOCODE_DEFAULT_MODEL_REF };
|
||||
@@ -15,6 +16,11 @@ export { KILOCODE_DEFAULT_MODEL_REF };
|
||||
const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir();
|
||||
|
||||
const ENV_REF_PATTERN = /^\$\{([A-Z][A-Z0-9_]*)\}$/;
|
||||
|
||||
export type ApiKeyStorageOptions = {
|
||||
secretInputMode?: SecretInputMode;
|
||||
};
|
||||
|
||||
function buildEnvSecretRef(id: string): SecretRef {
|
||||
return { source: "env", id };
|
||||
}
|
||||
@@ -27,21 +33,22 @@ function parseEnvSecretRef(value: string): SecretRef | null {
|
||||
return buildEnvSecretRef(match[1]);
|
||||
}
|
||||
|
||||
function inferProviderEnvSecretRef(provider: string, value: string): SecretRef | null {
|
||||
function resolveProviderDefaultEnvSecretRef(provider: string): SecretRef {
|
||||
const envVars = PROVIDER_ENV_VARS[provider];
|
||||
if (!envVars || value.length === 0) {
|
||||
return null;
|
||||
const envVar = envVars?.find((candidate) => candidate.trim().length > 0);
|
||||
if (!envVar) {
|
||||
throw new Error(
|
||||
`Provider "${provider}" does not have a default env var mapping for secret-input-mode=ref.`,
|
||||
);
|
||||
}
|
||||
for (const envVar of envVars) {
|
||||
const envValue = normalizeSecretInput(process.env[envVar] ?? "");
|
||||
if (envValue && envValue === value) {
|
||||
return buildEnvSecretRef(envVar);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return buildEnvSecretRef(envVar);
|
||||
}
|
||||
|
||||
function resolveApiKeySecretInput(provider: string, input: SecretInput): SecretInput {
|
||||
function resolveApiKeySecretInput(
|
||||
provider: string,
|
||||
input: SecretInput,
|
||||
options?: ApiKeyStorageOptions,
|
||||
): SecretInput {
|
||||
if (isSecretRef(input)) {
|
||||
return input;
|
||||
}
|
||||
@@ -50,9 +57,8 @@ function resolveApiKeySecretInput(provider: string, input: SecretInput): SecretI
|
||||
if (inlineEnvRef) {
|
||||
return inlineEnvRef;
|
||||
}
|
||||
const inferredEnvRef = inferProviderEnvSecretRef(provider, normalized);
|
||||
if (inferredEnvRef) {
|
||||
return inferredEnvRef;
|
||||
if (options?.secretInputMode === "ref") {
|
||||
return resolveProviderDefaultEnvSecretRef(provider);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
@@ -61,6 +67,7 @@ function buildApiKeyCredential(
|
||||
provider: string,
|
||||
input: SecretInput,
|
||||
metadata?: Record<string, string>,
|
||||
options?: ApiKeyStorageOptions,
|
||||
): {
|
||||
type: "api_key";
|
||||
provider: string;
|
||||
@@ -68,7 +75,7 @@ function buildApiKeyCredential(
|
||||
keyRef?: SecretRef;
|
||||
metadata?: Record<string, string>;
|
||||
} {
|
||||
const secretInput = resolveApiKeySecretInput(provider, input);
|
||||
const secretInput = resolveApiKeySecretInput(provider, input, options);
|
||||
if (typeof secretInput === "string") {
|
||||
return {
|
||||
type: "api_key",
|
||||
@@ -186,20 +193,40 @@ export async function writeOAuthCredentials(
|
||||
return profileId;
|
||||
}
|
||||
|
||||
export async function setAnthropicApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setAnthropicApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "anthropic:default",
|
||||
credential: buildApiKeyCredential("anthropic", key),
|
||||
credential: buildApiKeyCredential("anthropic", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setGeminiApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setOpenaiApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "openai:default",
|
||||
credential: buildApiKeyCredential("openai", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setGeminiApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "google:default",
|
||||
credential: buildApiKeyCredential("google", key),
|
||||
credential: buildApiKeyCredential("google", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
@@ -208,48 +235,89 @@ export async function setMinimaxApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
profileId: string = "minimax:default",
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
const provider = profileId.split(":")[0] ?? "minimax";
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId,
|
||||
credential: buildApiKeyCredential(provider, key),
|
||||
credential: buildApiKeyCredential(provider, key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setMoonshotApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setMoonshotApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "moonshot:default",
|
||||
credential: buildApiKeyCredential("moonshot", key),
|
||||
credential: buildApiKeyCredential("moonshot", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setKimiCodingApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setKimiCodingApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "kimi-coding:default",
|
||||
credential: buildApiKeyCredential("kimi-coding", key),
|
||||
credential: buildApiKeyCredential("kimi-coding", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setSyntheticApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setVolcengineApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "volcengine:default",
|
||||
credential: buildApiKeyCredential("volcengine", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setByteplusApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "byteplus:default",
|
||||
credential: buildApiKeyCredential("byteplus", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setSyntheticApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "synthetic:default",
|
||||
credential: buildApiKeyCredential("synthetic", key),
|
||||
credential: buildApiKeyCredential("synthetic", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setVeniceApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setVeniceApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "venice:default",
|
||||
credential: buildApiKeyCredential("venice", key),
|
||||
credential: buildApiKeyCredential("venice", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
@@ -262,29 +330,41 @@ export const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5";
|
||||
export const LITELLM_DEFAULT_MODEL_REF = "litellm/claude-opus-4-6";
|
||||
export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.6";
|
||||
|
||||
export async function setZaiApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setZaiApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "zai:default",
|
||||
credential: buildApiKeyCredential("zai", key),
|
||||
credential: buildApiKeyCredential("zai", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setXiaomiApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setXiaomiApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "xiaomi:default",
|
||||
credential: buildApiKeyCredential("xiaomi", key),
|
||||
credential: buildApiKeyCredential("xiaomi", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setOpenrouterApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setOpenrouterApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
// Never persist the literal "undefined" (e.g. when prompt returns undefined and caller used String(key)).
|
||||
const safeKey = typeof key === "string" && key === "undefined" ? "" : key;
|
||||
upsertAuthProfile({
|
||||
profileId: "openrouter:default",
|
||||
credential: buildApiKeyCredential("openrouter", safeKey),
|
||||
credential: buildApiKeyCredential("openrouter", safeKey, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
@@ -294,95 +374,125 @@ export async function setCloudflareAiGatewayConfig(
|
||||
gatewayId: string,
|
||||
apiKey: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
const normalizedAccountId = accountId.trim();
|
||||
const normalizedGatewayId = gatewayId.trim();
|
||||
upsertAuthProfile({
|
||||
profileId: "cloudflare-ai-gateway:default",
|
||||
credential: buildApiKeyCredential("cloudflare-ai-gateway", apiKey, {
|
||||
accountId: normalizedAccountId,
|
||||
gatewayId: normalizedGatewayId,
|
||||
}),
|
||||
credential: buildApiKeyCredential(
|
||||
"cloudflare-ai-gateway",
|
||||
apiKey,
|
||||
{
|
||||
accountId: normalizedAccountId,
|
||||
gatewayId: normalizedGatewayId,
|
||||
},
|
||||
options,
|
||||
),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setLitellmApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setLitellmApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "litellm:default",
|
||||
credential: buildApiKeyCredential("litellm", key),
|
||||
credential: buildApiKeyCredential("litellm", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setVercelAiGatewayApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setVercelAiGatewayApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "vercel-ai-gateway:default",
|
||||
credential: buildApiKeyCredential("vercel-ai-gateway", key),
|
||||
credential: buildApiKeyCredential("vercel-ai-gateway", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setOpencodeZenApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setOpencodeZenApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "opencode:default",
|
||||
credential: buildApiKeyCredential("opencode", key),
|
||||
credential: buildApiKeyCredential("opencode", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setTogetherApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setTogetherApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "together:default",
|
||||
credential: buildApiKeyCredential("together", key),
|
||||
credential: buildApiKeyCredential("together", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setHuggingfaceApiKey(key: SecretInput, agentDir?: string) {
|
||||
export async function setHuggingfaceApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "huggingface:default",
|
||||
credential: buildApiKeyCredential("huggingface", key),
|
||||
credential: buildApiKeyCredential("huggingface", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export function setQianfanApiKey(key: SecretInput, agentDir?: string) {
|
||||
export function setQianfanApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "qianfan:default",
|
||||
credential: buildApiKeyCredential("qianfan", key),
|
||||
credential: buildApiKeyCredential("qianfan", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export function setXaiApiKey(key: SecretInput, agentDir?: string) {
|
||||
export function setXaiApiKey(key: SecretInput, agentDir?: string, options?: ApiKeyStorageOptions) {
|
||||
upsertAuthProfile({
|
||||
profileId: "xai:default",
|
||||
credential: buildApiKeyCredential("xai", key),
|
||||
credential: buildApiKeyCredential("xai", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setMistralApiKey(key: string, agentDir?: string) {
|
||||
export async function setMistralApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "mistral:default",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "mistral",
|
||||
key,
|
||||
},
|
||||
credential: buildApiKeyCredential("mistral", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setKilocodeApiKey(key: string, agentDir?: string) {
|
||||
export async function setKilocodeApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) {
|
||||
upsertAuthProfile({
|
||||
profileId: "kilocode:default",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "kilocode",
|
||||
key,
|
||||
},
|
||||
credential: buildApiKeyCredential("kilocode", key, undefined, options),
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user