mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 02:01:25 +00:00
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 { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
|
||||||
import {
|
import {
|
||||||
formatApiKeyPreview,
|
formatApiKeyPreview,
|
||||||
normalizeApiKeyInput,
|
normalizeApiKeyInput,
|
||||||
@@ -9,7 +8,7 @@ import { createAuthChoiceAgentModelNoter } from "./auth-choice.apply-helpers.js"
|
|||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||||
import { isRemoteEnvironment } from "./oauth-env.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 { openUrl } from "./onboard-helpers.js";
|
||||||
import {
|
import {
|
||||||
applyOpenAICodexModelDefault,
|
applyOpenAICodexModelDefault,
|
||||||
@@ -58,17 +57,12 @@ export async function applyAuthChoiceOpenAI(
|
|||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (useExisting) {
|
if (useExisting) {
|
||||||
const result = upsertSharedEnvVar({
|
await setOpenaiApiKey(envKey.apiKey, params.agentDir);
|
||||||
key: "OPENAI_API_KEY",
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
value: envKey.apiKey,
|
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();
|
return await applyOpenAiDefaultModelChoice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,15 +78,12 @@ export async function applyAuthChoiceOpenAI(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = normalizeApiKeyInput(String(key));
|
const trimmed = normalizeApiKeyInput(String(key));
|
||||||
const result = upsertSharedEnvVar({
|
await setOpenaiApiKey(trimmed, params.agentDir);
|
||||||
key: "OPENAI_API_KEY",
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
value: trimmed,
|
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();
|
return await applyOpenAiDefaultModelChoice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { setCloudflareAiGatewayConfig, setMoonshotApiKey } from "./onboard-auth.js";
|
import {
|
||||||
|
setCloudflareAiGatewayConfig,
|
||||||
|
setMoonshotApiKey,
|
||||||
|
setOpenaiApiKey,
|
||||||
|
} from "./onboard-auth.js";
|
||||||
import {
|
import {
|
||||||
createAuthTestLifecycle,
|
createAuthTestLifecycle,
|
||||||
readAuthProfilesForAgent,
|
readAuthProfilesForAgent,
|
||||||
@@ -12,6 +16,7 @@ describe("onboard auth credentials secret refs", () => {
|
|||||||
"OPENCLAW_AGENT_DIR",
|
"OPENCLAW_AGENT_DIR",
|
||||||
"PI_CODING_AGENT_DIR",
|
"PI_CODING_AGENT_DIR",
|
||||||
"MOONSHOT_API_KEY",
|
"MOONSHOT_API_KEY",
|
||||||
|
"OPENAI_API_KEY",
|
||||||
"CLOUDFLARE_AI_GATEWAY_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();
|
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(
|
export async function setOpenaiApiKey(
|
||||||
key: SecretInput,
|
key: SecretInput,
|
||||||
agentDir?: string,
|
agentDir?: string,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export {
|
|||||||
KILOCODE_DEFAULT_MODEL_REF,
|
KILOCODE_DEFAULT_MODEL_REF,
|
||||||
LITELLM_DEFAULT_MODEL_REF,
|
LITELLM_DEFAULT_MODEL_REF,
|
||||||
OPENROUTER_DEFAULT_MODEL_REF,
|
OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
|
setOpenaiApiKey,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setCloudflareAiGatewayConfig,
|
setCloudflareAiGatewayConfig,
|
||||||
setQianfanApiKey,
|
setQianfanApiKey,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
setMistralApiKey,
|
setMistralApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
setMoonshotApiKey,
|
setMoonshotApiKey,
|
||||||
|
setOpenaiApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
setOpenrouterApiKey,
|
setOpenrouterApiKey,
|
||||||
setSyntheticApiKey,
|
setSyntheticApiKey,
|
||||||
@@ -408,15 +409,16 @@ export async function applyNonInteractiveAuthChoice(params: {
|
|||||||
flagName: "--openai-api-key",
|
flagName: "--openai-api-key",
|
||||||
envVar: "OPENAI_API_KEY",
|
envVar: "OPENAI_API_KEY",
|
||||||
runtime,
|
runtime,
|
||||||
allowProfile: false,
|
|
||||||
});
|
});
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const key = resolved.key;
|
await setOpenaiApiKey(resolved.key);
|
||||||
const result = upsertSharedEnvVar({ key: "OPENAI_API_KEY", value: key });
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
process.env.OPENAI_API_KEY = key;
|
profileId: "openai:default",
|
||||||
runtime.log(`Saved OPENAI_API_KEY to ${shortenHomePath(result.path)}`);
|
provider: "openai",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
return applyOpenAIConfig(nextConfig);
|
return applyOpenAIConfig(nextConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user