diff --git a/src/commands/auth-choice.apply.byteplus.ts b/src/commands/auth-choice.apply.byteplus.ts index 816f82edfd0..87ae7a75595 100644 --- a/src/commands/auth-choice.apply.byteplus.ts +++ b/src/commands/auth-choice.apply.byteplus.ts @@ -4,6 +4,10 @@ import { normalizeApiKeyInput, validateApiKeyInput, } from "./auth-choice.api-key.js"; +import { + normalizeSecretInputModeInput, + resolveSecretInputModeForEnvSelection, +} from "./auth-choice.apply-helpers.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { applyPrimaryModel } from "./model-picker.js"; import { applyAuthProfileConfig, setByteplusApiKey } from "./onboard-auth.js"; @@ -18,6 +22,7 @@ export async function applyAuthChoiceBytePlus( return null; } + const requestedSecretInputMode = normalizeSecretInputModeInput(params.opts?.secretInputMode); const envKey = resolveEnvApiKey("byteplus"); if (envKey) { const useExisting = await params.prompter.confirm({ @@ -25,7 +30,11 @@ export async function applyAuthChoiceBytePlus( initialValue: true, }); if (useExisting) { - await setByteplusApiKey(envKey.apiKey, params.agentDir); + const mode = await resolveSecretInputModeForEnvSelection({ + prompter: params.prompter, + explicitMode: requestedSecretInputMode, + }); + await setByteplusApiKey(envKey.apiKey, params.agentDir, { secretInputMode: mode }); const configWithAuth = applyAuthProfileConfig(params.config, { profileId: "byteplus:default", provider: "byteplus", @@ -50,7 +59,9 @@ export async function applyAuthChoiceBytePlus( } const trimmed = normalizeApiKeyInput(String(key)); - await setByteplusApiKey(trimmed, params.agentDir); + await setByteplusApiKey(trimmed, params.agentDir, { + secretInputMode: requestedSecretInputMode, + }); const configWithAuth = applyAuthProfileConfig(params.config, { profileId: "byteplus:default", provider: "byteplus", diff --git a/src/commands/auth-choice.apply.volcengine-byteplus.test.ts b/src/commands/auth-choice.apply.volcengine-byteplus.test.ts index fd61fb74a8a..4f104beebc9 100644 --- a/src/commands/auth-choice.apply.volcengine-byteplus.test.ts +++ b/src/commands/auth-choice.apply.volcengine-byteplus.test.ts @@ -28,7 +28,7 @@ describe("volcengine/byteplus auth choice", () => { await lifecycle.cleanup(); }); - it("stores volcengine env key as keyRef and configures auth profile", async () => { + it("stores volcengine env key as plaintext by default", async () => { const agentDir = await setupTempState(); process.env.VOLCANO_ENGINE_API_KEY = "volc-env-key"; @@ -37,7 +37,7 @@ describe("volcengine/byteplus auth choice", () => { confirm: vi.fn(async () => true), text: vi.fn(async () => "unused"), }, - { defaultSelect: "" }, + { defaultSelect: "plaintext" }, ); const runtime = createExitThrowingRuntime(); @@ -55,6 +55,35 @@ describe("volcengine/byteplus auth choice", () => { mode: "api_key", }); + const parsed = await readAuthProfilesForAgent<{ + profiles?: Record; + }>(agentDir); + expect(parsed.profiles?.["volcengine:default"]?.key).toBe("volc-env-key"); + expect(parsed.profiles?.["volcengine:default"]?.keyRef).toBeUndefined(); + }); + + it("stores volcengine env key as keyRef in ref mode", async () => { + const agentDir = await setupTempState(); + process.env.VOLCANO_ENGINE_API_KEY = "volc-env-key"; + + const prompter = createWizardPrompter( + { + confirm: vi.fn(async () => true), + text: vi.fn(async () => "unused"), + }, + { defaultSelect: "ref" }, + ); + const runtime = createExitThrowingRuntime(); + + const result = await applyAuthChoiceVolcengine({ + authChoice: "volcengine-api-key", + config: {}, + prompter, + runtime, + setDefaultModel: true, + }); + + expect(result).not.toBeNull(); const parsed = await readAuthProfilesForAgent<{ profiles?: Record; }>(agentDir); @@ -64,7 +93,7 @@ describe("volcengine/byteplus auth choice", () => { expect(parsed.profiles?.["volcengine:default"]?.key).toBeUndefined(); }); - it("stores byteplus env key as keyRef and configures auth profile", async () => { + it("stores byteplus env key as plaintext by default", async () => { const agentDir = await setupTempState(); process.env.BYTEPLUS_API_KEY = "byte-env-key"; @@ -73,7 +102,7 @@ describe("volcengine/byteplus auth choice", () => { confirm: vi.fn(async () => true), text: vi.fn(async () => "unused"), }, - { defaultSelect: "" }, + { defaultSelect: "plaintext" }, ); const runtime = createExitThrowingRuntime(); @@ -91,6 +120,35 @@ describe("volcengine/byteplus auth choice", () => { mode: "api_key", }); + const parsed = await readAuthProfilesForAgent<{ + profiles?: Record; + }>(agentDir); + expect(parsed.profiles?.["byteplus:default"]?.key).toBe("byte-env-key"); + expect(parsed.profiles?.["byteplus:default"]?.keyRef).toBeUndefined(); + }); + + it("stores byteplus env key as keyRef in ref mode", async () => { + const agentDir = await setupTempState(); + process.env.BYTEPLUS_API_KEY = "byte-env-key"; + + const prompter = createWizardPrompter( + { + confirm: vi.fn(async () => true), + text: vi.fn(async () => "unused"), + }, + { defaultSelect: "ref" }, + ); + const runtime = createExitThrowingRuntime(); + + const result = await applyAuthChoiceBytePlus({ + authChoice: "byteplus-api-key", + config: {}, + prompter, + runtime, + setDefaultModel: true, + }); + + expect(result).not.toBeNull(); const parsed = await readAuthProfilesForAgent<{ profiles?: Record; }>(agentDir); diff --git a/src/commands/auth-choice.apply.volcengine.ts b/src/commands/auth-choice.apply.volcengine.ts index 599f9efdcaf..3974234755a 100644 --- a/src/commands/auth-choice.apply.volcengine.ts +++ b/src/commands/auth-choice.apply.volcengine.ts @@ -4,6 +4,10 @@ import { normalizeApiKeyInput, validateApiKeyInput, } from "./auth-choice.api-key.js"; +import { + normalizeSecretInputModeInput, + resolveSecretInputModeForEnvSelection, +} from "./auth-choice.apply-helpers.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { applyPrimaryModel } from "./model-picker.js"; import { applyAuthProfileConfig, setVolcengineApiKey } from "./onboard-auth.js"; @@ -18,6 +22,7 @@ export async function applyAuthChoiceVolcengine( return null; } + const requestedSecretInputMode = normalizeSecretInputModeInput(params.opts?.secretInputMode); const envKey = resolveEnvApiKey("volcengine"); if (envKey) { const useExisting = await params.prompter.confirm({ @@ -25,7 +30,11 @@ export async function applyAuthChoiceVolcengine( initialValue: true, }); if (useExisting) { - await setVolcengineApiKey(envKey.apiKey, params.agentDir); + const mode = await resolveSecretInputModeForEnvSelection({ + prompter: params.prompter, + explicitMode: requestedSecretInputMode, + }); + await setVolcengineApiKey(envKey.apiKey, params.agentDir, { secretInputMode: mode }); const configWithAuth = applyAuthProfileConfig(params.config, { profileId: "volcengine:default", provider: "volcengine", @@ -50,7 +59,9 @@ export async function applyAuthChoiceVolcengine( } const trimmed = normalizeApiKeyInput(String(key)); - await setVolcengineApiKey(trimmed, params.agentDir); + await setVolcengineApiKey(trimmed, params.agentDir, { + secretInputMode: requestedSecretInputMode, + }); const configWithAuth = applyAuthProfileConfig(params.config, { profileId: "volcengine:default", provider: "volcengine", diff --git a/src/commands/onboard-auth.credentials.test.ts b/src/commands/onboard-auth.credentials.test.ts index 16cdea4d61a..9dba5766ba4 100644 --- a/src/commands/onboard-auth.credentials.test.ts +++ b/src/commands/onboard-auth.credentials.test.ts @@ -44,7 +44,7 @@ describe("onboard auth credentials secret refs", () => { expect(parsed.profiles?.["moonshot:default"]?.keyRef).toBeUndefined(); }); - it("stores env-backed moonshot key as keyRef in ref mode", async () => { + it("stores env-backed moonshot key as keyRef when secret-input-mode=ref", async () => { const env = await setupAuthTestEnv("openclaw-onboard-auth-credentials-ref-"); lifecycle.setStateDir(env.stateDir); process.env.MOONSHOT_API_KEY = "sk-moonshot-env"; @@ -142,14 +142,14 @@ describe("onboard auth credentials secret refs", () => { expect(parsed.profiles?.["openai:default"]?.key).toBeUndefined(); }); - it("stores env-backed volcengine and byteplus keys as keyRef", async () => { + it("stores env-backed volcengine and byteplus keys as keyRef in ref mode", async () => { const env = await setupAuthTestEnv("openclaw-onboard-auth-credentials-volc-byte-"); lifecycle.setStateDir(env.stateDir); process.env.VOLCANO_ENGINE_API_KEY = "volcengine-secret"; process.env.BYTEPLUS_API_KEY = "byteplus-secret"; - await setVolcengineApiKey("volcengine-secret"); - await setByteplusApiKey("byteplus-secret"); + await setVolcengineApiKey("volcengine-secret", env.agentDir, { secretInputMode: "ref" }); + await setByteplusApiKey("byteplus-secret", env.agentDir, { secretInputMode: "ref" }); const parsed = await readAuthProfilesForAgent<{ profiles?: Record; diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 406394a0b54..04292ea4578 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -4,6 +4,7 @@ import { parseDurationMs } from "../../../cli/parse-duration.js"; import type { OpenClawConfig } from "../../../config/config.js"; import type { RuntimeEnv } from "../../../runtime.js"; import { normalizeSecretInput } from "../../../utils/normalize-secret-input.js"; +import { normalizeSecretInputModeInput } from "../../auth-choice.apply-helpers.js"; import { buildTokenProfileId, validateAnthropicSetupToken } from "../../auth-token.js"; import { applyGoogleGeminiModelDefault } from "../../google-gemini-model-default.js"; import { applyPrimaryModel } from "../../model-picker.js"; @@ -74,6 +75,15 @@ export async function applyNonInteractiveAuthChoice(params: { }): Promise { const { authChoice, opts, runtime, baseConfig } = params; let nextConfig = params.nextConfig; + const requestedSecretInputMode = normalizeSecretInputModeInput(opts.secretInputMode); + if (opts.secretInputMode && !requestedSecretInputMode) { + runtime.error('Invalid --secret-input-mode. Use "plaintext" or "ref".'); + runtime.exit(1); + return null; + } + const apiKeyStorageOptions = requestedSecretInputMode + ? { secretInputMode: requestedSecretInputMode } + : undefined; if (authChoice === "claude-cli" || authChoice === "codex-cli") { runtime.error( @@ -121,7 +131,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setAnthropicApiKey(resolved.key); + await setAnthropicApiKey(resolved.key, undefined, apiKeyStorageOptions); } return applyAuthProfileConfig(nextConfig, { profileId: "anthropic:default", @@ -198,7 +208,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setGeminiApiKey(resolved.key); + await setGeminiApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "google:default", @@ -227,7 +237,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setZaiApiKey(resolved.key); + await setZaiApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "zai:default", @@ -276,7 +286,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setXiaomiApiKey(resolved.key); + await setXiaomiApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "xiaomi:default", @@ -299,7 +309,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - setXaiApiKey(resolved.key); + setXaiApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "xai:default", @@ -322,7 +332,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setMistralApiKey(resolved.key); + await setMistralApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "mistral:default", @@ -345,7 +355,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setVolcengineApiKey(resolved.key); + await setVolcengineApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "volcengine:default", @@ -368,7 +378,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setByteplusApiKey(resolved.key); + await setByteplusApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "byteplus:default", @@ -391,7 +401,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - setQianfanApiKey(resolved.key); + setQianfanApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "qianfan:default", @@ -414,7 +424,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setOpenaiApiKey(resolved.key); + await setOpenaiApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "openai:default", @@ -437,7 +447,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setOpenrouterApiKey(resolved.key); + await setOpenrouterApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "openrouter:default", @@ -460,7 +470,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setKilocodeApiKey(resolved.key); + await setKilocodeApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "kilocode:default", @@ -483,7 +493,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setLitellmApiKey(resolved.key); + await setLitellmApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "litellm:default", @@ -506,7 +516,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setVercelAiGatewayApiKey(resolved.key); + await setVercelAiGatewayApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "vercel-ai-gateway:default", @@ -541,7 +551,13 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setCloudflareAiGatewayConfig(accountId, gatewayId, resolved.key); + await setCloudflareAiGatewayConfig( + accountId, + gatewayId, + resolved.key, + undefined, + apiKeyStorageOptions, + ); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "cloudflare-ai-gateway:default", @@ -569,7 +585,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setMoonshotApiKey(resolved.key); + await setMoonshotApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "moonshot:default", @@ -600,7 +616,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setKimiCodingApiKey(resolved.key); + await setKimiCodingApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "kimi-coding:default", @@ -623,7 +639,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setSyntheticApiKey(resolved.key); + await setSyntheticApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "synthetic:default", @@ -646,7 +662,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setVeniceApiKey(resolved.key); + await setVeniceApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "venice:default", @@ -677,7 +693,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setMinimaxApiKey(resolved.key, undefined, profileId); + await setMinimaxApiKey(resolved.key, undefined, profileId, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId, @@ -708,7 +724,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setOpencodeZenApiKey(resolved.key); + await setOpencodeZenApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "opencode:default", @@ -731,7 +747,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setTogetherApiKey(resolved.key); + await setTogetherApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "together:default", @@ -754,7 +770,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setHuggingfaceApiKey(resolved.key); + await setHuggingfaceApiKey(resolved.key, undefined, apiKeyStorageOptions); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "huggingface:default",