From 2ef109f00a75c793b4a69269108930df159ca025 Mon Sep 17 00:00:00 2001 From: joshavant <830519+joshavant@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:42:45 -0600 Subject: [PATCH] Onboard OpenAI: explicit secret-input-mode behavior --- src/commands/auth-choice.apply.openai.test.ts | 29 +++++++++++++++++-- src/commands/auth-choice.apply.openai.ts | 17 +++++++++-- src/commands/onboard-auth.credentials.test.ts | 18 +++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/commands/auth-choice.apply.openai.test.ts b/src/commands/auth-choice.apply.openai.test.ts index cae67bbabfb..c74081f6d12 100644 --- a/src/commands/auth-choice.apply.openai.test.ts +++ b/src/commands/auth-choice.apply.openai.test.ts @@ -26,13 +26,13 @@ describe("applyAuthChoiceOpenAI", () => { await lifecycle.cleanup(); }); - it("writes env-backed OpenAI key as keyRef in auth profiles", async () => { + it("writes env-backed OpenAI key as plaintext by default", async () => { const agentDir = await setupTempState(); process.env.OPENAI_API_KEY = "sk-openai-env"; const confirm = vi.fn(async () => true); const text = vi.fn(async () => "unused"); - const prompter = createWizardPrompter({ confirm, text }, { defaultSelect: "" }); + const prompter = createWizardPrompter({ confirm, text }, { defaultSelect: "plaintext" }); const runtime = createExitThrowingRuntime(); const result = await applyAuthChoiceOpenAI({ @@ -53,6 +53,31 @@ describe("applyAuthChoiceOpenAI", () => { expect(primaryModel).toBe("openai/gpt-5.1-codex"); expect(text).not.toHaveBeenCalled(); + const parsed = await readAuthProfilesForAgent<{ + profiles?: Record; + }>(agentDir); + expect(parsed.profiles?.["openai:default"]?.key).toBe("sk-openai-env"); + expect(parsed.profiles?.["openai:default"]?.keyRef).toBeUndefined(); + }); + + it("writes env-backed OpenAI key as keyRef when secret-input-mode=ref", async () => { + const agentDir = await setupTempState(); + process.env.OPENAI_API_KEY = "sk-openai-env"; + + const confirm = vi.fn(async () => true); + const text = vi.fn(async () => "unused"); + const prompter = createWizardPrompter({ confirm, text }, { defaultSelect: "ref" }); + const runtime = createExitThrowingRuntime(); + + const result = await applyAuthChoiceOpenAI({ + authChoice: "openai-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.openai.ts b/src/commands/auth-choice.apply.openai.ts index cf616b3126e..d0418381afe 100644 --- a/src/commands/auth-choice.apply.openai.ts +++ b/src/commands/auth-choice.apply.openai.ts @@ -4,7 +4,11 @@ import { normalizeApiKeyInput, validateApiKeyInput, } from "./auth-choice.api-key.js"; -import { createAuthChoiceAgentModelNoter } from "./auth-choice.apply-helpers.js"; +import { + createAuthChoiceAgentModelNoter, + normalizeSecretInputModeInput, + resolveSecretInputModeForEnvSelection, +} from "./auth-choice.apply-helpers.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { applyDefaultModelChoice } from "./auth-choice.default-model.js"; import { isRemoteEnvironment } from "./oauth-env.js"; @@ -24,6 +28,7 @@ import { export async function applyAuthChoiceOpenAI( params: ApplyAuthChoiceParams, ): Promise { + const requestedSecretInputMode = normalizeSecretInputModeInput(params.opts?.secretInputMode); const noteAgentModel = createAuthChoiceAgentModelNoter(params); let authChoice = params.authChoice; if (authChoice === "apiKey" && params.opts?.tokenProvider === "openai") { @@ -57,7 +62,11 @@ export async function applyAuthChoiceOpenAI( initialValue: true, }); if (useExisting) { - await setOpenaiApiKey(envKey.apiKey, params.agentDir); + const mode = await resolveSecretInputModeForEnvSelection({ + prompter: params.prompter, + explicitMode: requestedSecretInputMode, + }); + await setOpenaiApiKey(envKey.apiKey, params.agentDir, { secretInputMode: mode }); nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "openai:default", provider: "openai", @@ -78,7 +87,9 @@ export async function applyAuthChoiceOpenAI( } const trimmed = normalizeApiKeyInput(String(key)); - await setOpenaiApiKey(trimmed, params.agentDir); + await setOpenaiApiKey(trimmed, params.agentDir, { + secretInputMode: requestedSecretInputMode, + }); nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "openai:default", provider: "openai", diff --git a/src/commands/onboard-auth.credentials.test.ts b/src/commands/onboard-auth.credentials.test.ts index 5825409f878..340b33a0379 100644 --- a/src/commands/onboard-auth.credentials.test.ts +++ b/src/commands/onboard-auth.credentials.test.ts @@ -106,13 +106,29 @@ describe("onboard auth credentials secret refs", () => { expect(parsed.profiles?.["cloudflare-ai-gateway:default"]?.key).toBeUndefined(); }); - it("stores env-backed openai key as keyRef", async () => { + it("keeps env-backed openai key as plaintext by default", async () => { const env = await setupAuthTestEnv("openclaw-onboard-auth-credentials-openai-"); lifecycle.setStateDir(env.stateDir); process.env.OPENAI_API_KEY = "sk-openai-env"; await setOpenaiApiKey("sk-openai-env"); + const parsed = await readAuthProfilesForAgent<{ + profiles?: Record; + }>(env.agentDir); + expect(parsed.profiles?.["openai:default"]).toMatchObject({ + key: "sk-openai-env", + }); + expect(parsed.profiles?.["openai:default"]?.keyRef).toBeUndefined(); + }); + + it("stores env-backed openai key as keyRef in ref mode", async () => { + const env = await setupAuthTestEnv("openclaw-onboard-auth-credentials-openai-ref-"); + lifecycle.setStateDir(env.stateDir); + process.env.OPENAI_API_KEY = "sk-openai-env"; + + await setOpenaiApiKey("sk-openai-env", env.agentDir, { secretInputMode: "ref" }); + const parsed = await readAuthProfilesForAgent<{ profiles?: Record; }>(env.agentDir);