Onboard: move volcengine/byteplus auth from .env to profiles

This commit is contained in:
joshavant
2026-02-21 15:09:42 -08:00
committed by Peter Steinberger
parent 2ef109f00a
commit 59e5f12bf9
6 changed files with 199 additions and 62 deletions

View File

@@ -1,5 +1,4 @@
import { resolveEnvApiKey } from "../agents/model-auth.js";
import { upsertSharedEnvVar } from "../infra/env-file.js";
import {
formatApiKeyPreview,
normalizeApiKeyInput,
@@ -7,6 +6,7 @@ import {
} from "./auth-choice.api-key.js";
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
import { applyPrimaryModel } from "./model-picker.js";
import { applyAuthProfileConfig, setByteplusApiKey } from "./onboard-auth.js";
/** Default model for BytePlus auth onboarding. */
export const BYTEPLUS_DEFAULT_MODEL = "byteplus-plan/ark-code-latest";
@@ -25,18 +25,13 @@ export async function applyAuthChoiceBytePlus(
initialValue: true,
});
if (useExisting) {
const result = upsertSharedEnvVar({
key: "BYTEPLUS_API_KEY",
value: envKey.apiKey,
await setByteplusApiKey(envKey.apiKey, params.agentDir);
const configWithAuth = applyAuthProfileConfig(params.config, {
profileId: "byteplus:default",
provider: "byteplus",
mode: "api_key",
});
if (!process.env.BYTEPLUS_API_KEY) {
process.env.BYTEPLUS_API_KEY = envKey.apiKey;
}
await params.prompter.note(
`Copied BYTEPLUS_API_KEY to ${result.path} for launchd compatibility.`,
"BytePlus API key",
);
const configWithModel = applyPrimaryModel(params.config, BYTEPLUS_DEFAULT_MODEL);
const configWithModel = applyPrimaryModel(configWithAuth, BYTEPLUS_DEFAULT_MODEL);
return {
config: configWithModel,
agentModelOverride: BYTEPLUS_DEFAULT_MODEL,
@@ -55,17 +50,13 @@ export async function applyAuthChoiceBytePlus(
}
const trimmed = normalizeApiKeyInput(String(key));
const result = upsertSharedEnvVar({
key: "BYTEPLUS_API_KEY",
value: trimmed,
await setByteplusApiKey(trimmed, params.agentDir);
const configWithAuth = applyAuthProfileConfig(params.config, {
profileId: "byteplus:default",
provider: "byteplus",
mode: "api_key",
});
process.env.BYTEPLUS_API_KEY = trimmed;
await params.prompter.note(
`Saved BYTEPLUS_API_KEY to ${result.path} for launchd compatibility.`,
"BytePlus API key",
);
const configWithModel = applyPrimaryModel(params.config, BYTEPLUS_DEFAULT_MODEL);
const configWithModel = applyPrimaryModel(configWithAuth, BYTEPLUS_DEFAULT_MODEL);
return {
config: configWithModel,
agentModelOverride: BYTEPLUS_DEFAULT_MODEL,

View File

@@ -0,0 +1,129 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { applyAuthChoiceBytePlus } from "./auth-choice.apply.byteplus.js";
import { applyAuthChoiceVolcengine } from "./auth-choice.apply.volcengine.js";
import {
createAuthTestLifecycle,
createExitThrowingRuntime,
createWizardPrompter,
readAuthProfilesForAgent,
setupAuthTestEnv,
} from "./test-wizard-helpers.js";
describe("volcengine/byteplus auth choice", () => {
const lifecycle = createAuthTestLifecycle([
"OPENCLAW_STATE_DIR",
"OPENCLAW_AGENT_DIR",
"PI_CODING_AGENT_DIR",
"VOLCANO_ENGINE_API_KEY",
"BYTEPLUS_API_KEY",
]);
async function setupTempState() {
const env = await setupAuthTestEnv("openclaw-volc-byte-");
lifecycle.setStateDir(env.stateDir);
return env.agentDir;
}
afterEach(async () => {
await lifecycle.cleanup();
});
it("stores volcengine env key as keyRef and configures auth profile", 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: "" },
);
const runtime = createExitThrowingRuntime();
const result = await applyAuthChoiceVolcengine({
authChoice: "volcengine-api-key",
config: {},
prompter,
runtime,
setDefaultModel: true,
});
expect(result).not.toBeNull();
expect(result?.config.auth?.profiles?.["volcengine:default"]).toMatchObject({
provider: "volcengine",
mode: "api_key",
});
const parsed = await readAuthProfilesForAgent<{
profiles?: Record<string, { key?: string; keyRef?: unknown }>;
}>(agentDir);
expect(parsed.profiles?.["volcengine:default"]).toMatchObject({
keyRef: { source: "env", id: "VOLCANO_ENGINE_API_KEY" },
});
expect(parsed.profiles?.["volcengine:default"]?.key).toBeUndefined();
});
it("stores byteplus env key as keyRef and configures auth profile", 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: "" },
);
const runtime = createExitThrowingRuntime();
const result = await applyAuthChoiceBytePlus({
authChoice: "byteplus-api-key",
config: {},
prompter,
runtime,
setDefaultModel: true,
});
expect(result).not.toBeNull();
expect(result?.config.auth?.profiles?.["byteplus:default"]).toMatchObject({
provider: "byteplus",
mode: "api_key",
});
const parsed = await readAuthProfilesForAgent<{
profiles?: Record<string, { key?: string; keyRef?: unknown }>;
}>(agentDir);
expect(parsed.profiles?.["byteplus:default"]).toMatchObject({
keyRef: { source: "env", id: "BYTEPLUS_API_KEY" },
});
expect(parsed.profiles?.["byteplus:default"]?.key).toBeUndefined();
});
it("stores explicit volcengine key when env is not used", async () => {
const agentDir = await setupTempState();
const prompter = createWizardPrompter(
{
confirm: vi.fn(async () => false),
text: vi.fn(async () => "volc-manual-key"),
},
{ defaultSelect: "" },
);
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<string, { key?: string; keyRef?: unknown }>;
}>(agentDir);
expect(parsed.profiles?.["volcengine:default"]?.key).toBe("volc-manual-key");
expect(parsed.profiles?.["volcengine:default"]?.keyRef).toBeUndefined();
});
});

View File

@@ -1,5 +1,4 @@
import { resolveEnvApiKey } from "../agents/model-auth.js";
import { upsertSharedEnvVar } from "../infra/env-file.js";
import {
formatApiKeyPreview,
normalizeApiKeyInput,
@@ -7,6 +6,7 @@ import {
} from "./auth-choice.api-key.js";
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
import { applyPrimaryModel } from "./model-picker.js";
import { applyAuthProfileConfig, setVolcengineApiKey } from "./onboard-auth.js";
/** Default model for Volcano Engine auth onboarding. */
export const VOLCENGINE_DEFAULT_MODEL = "volcengine-plan/ark-code-latest";
@@ -25,18 +25,13 @@ export async function applyAuthChoiceVolcengine(
initialValue: true,
});
if (useExisting) {
const result = upsertSharedEnvVar({
key: "VOLCANO_ENGINE_API_KEY",
value: envKey.apiKey,
await setVolcengineApiKey(envKey.apiKey, params.agentDir);
const configWithAuth = applyAuthProfileConfig(params.config, {
profileId: "volcengine:default",
provider: "volcengine",
mode: "api_key",
});
if (!process.env.VOLCANO_ENGINE_API_KEY) {
process.env.VOLCANO_ENGINE_API_KEY = envKey.apiKey;
}
await params.prompter.note(
`Copied VOLCANO_ENGINE_API_KEY to ${result.path} for launchd compatibility.`,
"Volcano Engine API Key",
);
const configWithModel = applyPrimaryModel(params.config, VOLCENGINE_DEFAULT_MODEL);
const configWithModel = applyPrimaryModel(configWithAuth, VOLCENGINE_DEFAULT_MODEL);
return {
config: configWithModel,
agentModelOverride: VOLCENGINE_DEFAULT_MODEL,
@@ -55,17 +50,13 @@ export async function applyAuthChoiceVolcengine(
}
const trimmed = normalizeApiKeyInput(String(key));
const result = upsertSharedEnvVar({
key: "VOLCANO_ENGINE_API_KEY",
value: trimmed,
await setVolcengineApiKey(trimmed, params.agentDir);
const configWithAuth = applyAuthProfileConfig(params.config, {
profileId: "volcengine:default",
provider: "volcengine",
mode: "api_key",
});
process.env.VOLCANO_ENGINE_API_KEY = trimmed;
await params.prompter.note(
`Saved VOLCANO_ENGINE_API_KEY to ${result.path} for launchd compatibility.`,
"Volcano Engine API Key",
);
const configWithModel = applyPrimaryModel(params.config, VOLCENGINE_DEFAULT_MODEL);
const configWithModel = applyPrimaryModel(configWithAuth, VOLCENGINE_DEFAULT_MODEL);
return {
config: configWithModel,
agentModelOverride: VOLCENGINE_DEFAULT_MODEL,

View File

@@ -1,8 +1,10 @@
import { afterEach, describe, expect, it } from "vitest";
import {
setByteplusApiKey,
setCloudflareAiGatewayConfig,
setMoonshotApiKey,
setOpenaiApiKey,
setVolcengineApiKey,
} from "./onboard-auth.js";
import {
createAuthTestLifecycle,
@@ -18,6 +20,8 @@ describe("onboard auth credentials secret refs", () => {
"MOONSHOT_API_KEY",
"OPENAI_API_KEY",
"CLOUDFLARE_AI_GATEWAY_API_KEY",
"VOLCANO_ENGINE_API_KEY",
"BYTEPLUS_API_KEY",
]);
afterEach(async () => {
@@ -137,4 +141,28 @@ describe("onboard auth credentials secret refs", () => {
});
expect(parsed.profiles?.["openai:default"]?.key).toBeUndefined();
});
it("stores env-backed volcengine and byteplus keys as keyRef", 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");
const parsed = await readAuthProfilesForAgent<{
profiles?: Record<string, { key?: string; keyRef?: unknown }>;
}>(env.agentDir);
expect(parsed.profiles?.["volcengine:default"]).toMatchObject({
keyRef: { source: "env", id: "VOLCANO_ENGINE_API_KEY" },
});
expect(parsed.profiles?.["volcengine:default"]?.key).toBeUndefined();
expect(parsed.profiles?.["byteplus:default"]).toMatchObject({
keyRef: { source: "env", id: "BYTEPLUS_API_KEY" },
});
expect(parsed.profiles?.["byteplus:default"]?.key).toBeUndefined();
});
});

View File

@@ -64,6 +64,7 @@ export {
setOpenaiApiKey,
setAnthropicApiKey,
setCloudflareAiGatewayConfig,
setByteplusApiKey,
setQianfanApiKey,
setGeminiApiKey,
setKilocodeApiKey,
@@ -80,6 +81,7 @@ export {
setVeniceApiKey,
setVercelAiGatewayApiKey,
setXiaomiApiKey,
setVolcengineApiKey,
setZaiApiKey,
setXaiApiKey,
writeOAuthCredentials,

View File

@@ -2,9 +2,7 @@ import { upsertAuthProfile } from "../../../agents/auth-profiles.js";
import { normalizeProviderId } from "../../../agents/model-selection.js";
import { parseDurationMs } from "../../../cli/parse-duration.js";
import type { OpenClawConfig } from "../../../config/config.js";
import { upsertSharedEnvVar } from "../../../infra/env-file.js";
import type { RuntimeEnv } from "../../../runtime.js";
import { shortenHomePath } from "../../../utils.js";
import { normalizeSecretInput } from "../../../utils/normalize-secret-input.js";
import { buildTokenProfileId, validateAnthropicSetupToken } from "../../auth-token.js";
import { applyGoogleGeminiModelDefault } from "../../google-gemini-model-default.js";
@@ -34,6 +32,7 @@ import {
applyZaiConfig,
setAnthropicApiKey,
setCloudflareAiGatewayConfig,
setByteplusApiKey,
setQianfanApiKey,
setGeminiApiKey,
setKilocodeApiKey,
@@ -46,6 +45,7 @@ import {
setOpencodeZenApiKey,
setOpenrouterApiKey,
setSyntheticApiKey,
setVolcengineApiKey,
setXaiApiKey,
setVeniceApiKey,
setTogetherApiKey,
@@ -344,14 +344,12 @@ export async function applyNonInteractiveAuthChoice(params: {
if (!resolved) {
return null;
}
if (resolved.source !== "profile") {
const result = upsertSharedEnvVar({
key: "VOLCANO_ENGINE_API_KEY",
value: resolved.key,
});
process.env.VOLCANO_ENGINE_API_KEY = resolved.key;
runtime.log(`Saved VOLCANO_ENGINE_API_KEY to ${shortenHomePath(result.path)}`);
}
await setVolcengineApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "volcengine:default",
provider: "volcengine",
mode: "api_key",
});
return applyPrimaryModel(nextConfig, "volcengine-plan/ark-code-latest");
}
@@ -367,14 +365,12 @@ export async function applyNonInteractiveAuthChoice(params: {
if (!resolved) {
return null;
}
if (resolved.source !== "profile") {
const result = upsertSharedEnvVar({
key: "BYTEPLUS_API_KEY",
value: resolved.key,
});
process.env.BYTEPLUS_API_KEY = resolved.key;
runtime.log(`Saved BYTEPLUS_API_KEY to ${shortenHomePath(result.path)}`);
}
await setByteplusApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "byteplus:default",
provider: "byteplus",
mode: "api_key",
});
return applyPrimaryModel(nextConfig, "byteplus-plan/ark-code-latest");
}