Onboard: store OpenAI auth in profiles instead of .env
This commit is contained in:
committed by
Peter Steinberger
parent
09c7cb5d34
commit
68b9d89ee7
89
src/commands/auth-choice.apply.openai.test.ts
Normal file
89
src/commands/auth-choice.apply.openai.test.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { applyAuthChoiceOpenAI } from "./auth-choice.apply.openai.js";
|
||||
import {
|
||||
createAuthTestLifecycle,
|
||||
createExitThrowingRuntime,
|
||||
createWizardPrompter,
|
||||
readAuthProfilesForAgent,
|
||||
setupAuthTestEnv,
|
||||
} from "./test-wizard-helpers.js";
|
||||
|
||||
describe("applyAuthChoiceOpenAI", () => {
|
||||
const lifecycle = createAuthTestLifecycle([
|
||||
"OPENCLAW_STATE_DIR",
|
||||
"OPENCLAW_AGENT_DIR",
|
||||
"PI_CODING_AGENT_DIR",
|
||||
"OPENAI_API_KEY",
|
||||
]);
|
||||
|
||||
async function setupTempState() {
|
||||
const env = await setupAuthTestEnv("openclaw-openai-");
|
||||
lifecycle.setStateDir(env.stateDir);
|
||||
return env.agentDir;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await lifecycle.cleanup();
|
||||
});
|
||||
|
||||
it("writes env-backed OpenAI key as keyRef in auth profiles", 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 runtime = createExitThrowingRuntime();
|
||||
|
||||
const result = await applyAuthChoiceOpenAI({
|
||||
authChoice: "openai-api-key",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.config.auth?.profiles?.["openai:default"]).toMatchObject({
|
||||
provider: "openai",
|
||||
mode: "api_key",
|
||||
});
|
||||
expect(result?.config.agents?.defaults?.model?.primary).toBe("openai/gpt-5.1-codex");
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
|
||||
const parsed = await readAuthProfilesForAgent<{
|
||||
profiles?: Record<string, { key?: string; keyRef?: unknown }>;
|
||||
}>(agentDir);
|
||||
expect(parsed.profiles?.["openai:default"]).toMatchObject({
|
||||
keyRef: { source: "env", id: "OPENAI_API_KEY" },
|
||||
});
|
||||
expect(parsed.profiles?.["openai:default"]?.key).toBeUndefined();
|
||||
});
|
||||
|
||||
it("writes explicit token input into openai auth profile", async () => {
|
||||
const agentDir = await setupTempState();
|
||||
|
||||
const prompter = createWizardPrompter({}, { defaultSelect: "" });
|
||||
const runtime = createExitThrowingRuntime();
|
||||
|
||||
const result = await applyAuthChoiceOpenAI({
|
||||
authChoice: "apiKey",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: "openai",
|
||||
token: "sk-openai-token",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
const parsed = await readAuthProfilesForAgent<{
|
||||
profiles?: Record<string, { key?: string; keyRef?: unknown }>;
|
||||
}>(agentDir);
|
||||
expect(parsed.profiles?.["openai:default"]?.key).toBe("sk-openai-token");
|
||||
expect(parsed.profiles?.["openai:default"]?.keyRef).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
@@ -9,7 +8,7 @@ import { createAuthChoiceAgentModelNoter } 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";
|
||||
import { applyAuthProfileConfig, writeOAuthCredentials } from "./onboard-auth.js";
|
||||
import { applyAuthProfileConfig, setOpenaiApiKey, writeOAuthCredentials } from "./onboard-auth.js";
|
||||
import { openUrl } from "./onboard-helpers.js";
|
||||
import {
|
||||
applyOpenAICodexModelDefault,
|
||||
@@ -58,17 +57,12 @@ export async function applyAuthChoiceOpenAI(
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "OPENAI_API_KEY",
|
||||
value: envKey.apiKey,
|
||||
await setOpenaiApiKey(envKey.apiKey, params.agentDir);
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "openai:default",
|
||||
provider: "openai",
|
||||
mode: "api_key",
|
||||
});
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
process.env.OPENAI_API_KEY = envKey.apiKey;
|
||||
}
|
||||
await params.prompter.note(
|
||||
`Copied OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
||||
"OpenAI API key",
|
||||
);
|
||||
return await applyOpenAiDefaultModelChoice();
|
||||
}
|
||||
}
|
||||
@@ -84,15 +78,12 @@ export async function applyAuthChoiceOpenAI(
|
||||
}
|
||||
|
||||
const trimmed = normalizeApiKeyInput(String(key));
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "OPENAI_API_KEY",
|
||||
value: trimmed,
|
||||
await setOpenaiApiKey(trimmed, params.agentDir);
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "openai:default",
|
||||
provider: "openai",
|
||||
mode: "api_key",
|
||||
});
|
||||
process.env.OPENAI_API_KEY = trimmed;
|
||||
await params.prompter.note(
|
||||
`Saved OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
||||
"OpenAI API key",
|
||||
);
|
||||
return await applyOpenAiDefaultModelChoice();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { setCloudflareAiGatewayConfig, setMoonshotApiKey } from "./onboard-auth.js";
|
||||
import {
|
||||
setCloudflareAiGatewayConfig,
|
||||
setMoonshotApiKey,
|
||||
setOpenaiApiKey,
|
||||
} from "./onboard-auth.js";
|
||||
import {
|
||||
createAuthTestLifecycle,
|
||||
readAuthProfilesForAgent,
|
||||
@@ -12,6 +16,7 @@ describe("onboard auth credentials secret refs", () => {
|
||||
"OPENCLAW_AGENT_DIR",
|
||||
"PI_CODING_AGENT_DIR",
|
||||
"MOONSHOT_API_KEY",
|
||||
"OPENAI_API_KEY",
|
||||
"CLOUDFLARE_AI_GATEWAY_API_KEY",
|
||||
]);
|
||||
|
||||
@@ -100,4 +105,20 @@ describe("onboard auth credentials secret refs", () => {
|
||||
});
|
||||
expect(parsed.profiles?.["cloudflare-ai-gateway:default"]?.key).toBeUndefined();
|
||||
});
|
||||
|
||||
it("stores env-backed openai key as keyRef", 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<string, { key?: string; keyRef?: unknown }>;
|
||||
}>(env.agentDir);
|
||||
expect(parsed.profiles?.["openai:default"]).toMatchObject({
|
||||
keyRef: { source: "env", id: "OPENAI_API_KEY" },
|
||||
});
|
||||
expect(parsed.profiles?.["openai:default"]?.key).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,6 +206,7 @@ export async function setAnthropicApiKey(
|
||||
});
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
export async function setOpenaiApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
|
||||
@@ -61,6 +61,7 @@ export {
|
||||
KILOCODE_DEFAULT_MODEL_REF,
|
||||
LITELLM_DEFAULT_MODEL_REF,
|
||||
OPENROUTER_DEFAULT_MODEL_REF,
|
||||
setOpenaiApiKey,
|
||||
setAnthropicApiKey,
|
||||
setCloudflareAiGatewayConfig,
|
||||
setQianfanApiKey,
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
setMistralApiKey,
|
||||
setMinimaxApiKey,
|
||||
setMoonshotApiKey,
|
||||
setOpenaiApiKey,
|
||||
setOpencodeZenApiKey,
|
||||
setOpenrouterApiKey,
|
||||
setSyntheticApiKey,
|
||||
@@ -408,15 +409,16 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
flagName: "--openai-api-key",
|
||||
envVar: "OPENAI_API_KEY",
|
||||
runtime,
|
||||
allowProfile: false,
|
||||
});
|
||||
if (!resolved) {
|
||||
return null;
|
||||
}
|
||||
const key = resolved.key;
|
||||
const result = upsertSharedEnvVar({ key: "OPENAI_API_KEY", value: key });
|
||||
process.env.OPENAI_API_KEY = key;
|
||||
runtime.log(`Saved OPENAI_API_KEY to ${shortenHomePath(result.path)}`);
|
||||
await setOpenaiApiKey(resolved.key);
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "openai:default",
|
||||
provider: "openai",
|
||||
mode: "api_key",
|
||||
});
|
||||
return applyOpenAIConfig(nextConfig);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user