mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:11:24 +00:00
feat(volcengine): integrate Volcengine & Byteplus Provider
This commit is contained in:
committed by
Peter Steinberger
parent
95c14d9b5f
commit
559736a5a0
@@ -43,6 +43,8 @@ describe("buildAuthChoiceOptions", () => {
|
||||
["Chutes OAuth auth choice", ["chutes"]],
|
||||
["Qwen auth choice", ["qwen-portal"]],
|
||||
["xAI auth choice", ["xai-api-key"]],
|
||||
["Volcano Engine auth choice", ["volcengine-api-key"]],
|
||||
["BytePlus auth choice", ["byteplus-api-key"]],
|
||||
["vLLM auth choice", ["vllm"]],
|
||||
])("includes %s", (_label, expectedValues) => {
|
||||
const options = getOptions();
|
||||
|
||||
@@ -70,6 +70,18 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
||||
hint: "API key",
|
||||
choices: ["xai-api-key"],
|
||||
},
|
||||
{
|
||||
value: "volcengine",
|
||||
label: "Volcano Engine",
|
||||
hint: "API key",
|
||||
choices: ["volcengine-api-key"],
|
||||
},
|
||||
{
|
||||
value: "byteplus",
|
||||
label: "BytePlus",
|
||||
hint: "API key",
|
||||
choices: ["byteplus-api-key"],
|
||||
},
|
||||
{
|
||||
value: "openrouter",
|
||||
label: "OpenRouter",
|
||||
@@ -180,6 +192,8 @@ const BASE_AUTH_CHOICE_OPTIONS: ReadonlyArray<AuthChoiceOption> = [
|
||||
},
|
||||
{ value: "openai-api-key", label: "OpenAI API key" },
|
||||
{ value: "xai-api-key", label: "xAI (Grok) API key" },
|
||||
{ value: "volcengine-api-key", label: "Volcano Engine API key" },
|
||||
{ value: "byteplus-api-key", label: "BytePlus API key" },
|
||||
{
|
||||
value: "qianfan-api-key",
|
||||
label: "Qianfan API key",
|
||||
|
||||
73
src/commands/auth-choice.apply.byteplus.ts
Normal file
73
src/commands/auth-choice.apply.byteplus.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
validateApiKeyInput,
|
||||
} from "./auth-choice.api-key.js";
|
||||
import { applyPrimaryModel } from "./model-picker.js";
|
||||
|
||||
/** Default model for BytePlus auth onboarding. */
|
||||
export const BYTEPLUS_DEFAULT_MODEL = "byteplus-plan/ark-code-latest";
|
||||
|
||||
export async function applyAuthChoiceBytePlus(
|
||||
params: ApplyAuthChoiceParams,
|
||||
): Promise<ApplyAuthChoiceResult | null> {
|
||||
if (params.authChoice !== "byteplus-api-key") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("byteplus");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing BYTEPLUS_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "BYTEPLUS_API_KEY",
|
||||
value: envKey.apiKey,
|
||||
});
|
||||
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);
|
||||
return {
|
||||
config: configWithModel,
|
||||
agentModelOverride: BYTEPLUS_DEFAULT_MODEL,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let key: string | undefined;
|
||||
if (params.opts?.byteplusApiKey) {
|
||||
key = params.opts.byteplusApiKey;
|
||||
} else {
|
||||
key = await params.prompter.text({
|
||||
message: "Enter BytePlus API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
}
|
||||
|
||||
const trimmed = normalizeApiKeyInput(String(key));
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "BYTEPLUS_API_KEY",
|
||||
value: trimmed,
|
||||
});
|
||||
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);
|
||||
return {
|
||||
config: configWithModel,
|
||||
agentModelOverride: BYTEPLUS_DEFAULT_MODEL,
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import type { AuthChoice, OnboardOptions } from "./onboard-types.js";
|
||||
import { applyAuthChoiceAnthropic } from "./auth-choice.apply.anthropic.js";
|
||||
import { applyAuthChoiceApiProviders } from "./auth-choice.apply.api-providers.js";
|
||||
import { applyAuthChoiceBytePlus } from "./auth-choice.apply.byteplus.js";
|
||||
import { applyAuthChoiceCopilotProxy } from "./auth-choice.apply.copilot-proxy.js";
|
||||
import { applyAuthChoiceGitHubCopilot } from "./auth-choice.apply.github-copilot.js";
|
||||
import { applyAuthChoiceGoogleAntigravity } from "./auth-choice.apply.google-antigravity.js";
|
||||
@@ -12,8 +14,8 @@ import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js";
|
||||
import { applyAuthChoiceOpenAI } from "./auth-choice.apply.openai.js";
|
||||
import { applyAuthChoiceQwenPortal } from "./auth-choice.apply.qwen-portal.js";
|
||||
import { applyAuthChoiceVllm } from "./auth-choice.apply.vllm.js";
|
||||
import { applyAuthChoiceVolcengine } from "./auth-choice.apply.volcengine.js";
|
||||
import { applyAuthChoiceXAI } from "./auth-choice.apply.xai.js";
|
||||
import type { AuthChoice } from "./onboard-types.js";
|
||||
|
||||
export type ApplyAuthChoiceParams = {
|
||||
authChoice: AuthChoice;
|
||||
@@ -23,14 +25,7 @@ export type ApplyAuthChoiceParams = {
|
||||
agentDir?: string;
|
||||
setDefaultModel: boolean;
|
||||
agentId?: string;
|
||||
opts?: {
|
||||
tokenProvider?: string;
|
||||
token?: string;
|
||||
cloudflareAiGatewayAccountId?: string;
|
||||
cloudflareAiGatewayGatewayId?: string;
|
||||
cloudflareAiGatewayApiKey?: string;
|
||||
xaiApiKey?: string;
|
||||
};
|
||||
opts?: Partial<OnboardOptions>;
|
||||
};
|
||||
|
||||
export type ApplyAuthChoiceResult = {
|
||||
@@ -54,6 +49,8 @@ export async function applyAuthChoice(
|
||||
applyAuthChoiceCopilotProxy,
|
||||
applyAuthChoiceQwenPortal,
|
||||
applyAuthChoiceXAI,
|
||||
applyAuthChoiceVolcengine,
|
||||
applyAuthChoiceBytePlus,
|
||||
];
|
||||
|
||||
for (const handler of handlers) {
|
||||
|
||||
73
src/commands/auth-choice.apply.volcengine.ts
Normal file
73
src/commands/auth-choice.apply.volcengine.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
validateApiKeyInput,
|
||||
} from "./auth-choice.api-key.js";
|
||||
import { applyPrimaryModel } from "./model-picker.js";
|
||||
|
||||
/** Default model for Volcano Engine auth onboarding. */
|
||||
export const VOLCENGINE_DEFAULT_MODEL = "volcengine-plan/ark-code-latest";
|
||||
|
||||
export async function applyAuthChoiceVolcengine(
|
||||
params: ApplyAuthChoiceParams,
|
||||
): Promise<ApplyAuthChoiceResult | null> {
|
||||
if (params.authChoice !== "volcengine-api-key") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("volcengine");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing VOLCANO_ENGINE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "VOLCANO_ENGINE_API_KEY",
|
||||
value: envKey.apiKey,
|
||||
});
|
||||
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);
|
||||
return {
|
||||
config: configWithModel,
|
||||
agentModelOverride: VOLCENGINE_DEFAULT_MODEL,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let key: string | undefined;
|
||||
if (params.opts?.volcengineApiKey) {
|
||||
key = params.opts.volcengineApiKey;
|
||||
} else {
|
||||
key = await params.prompter.text({
|
||||
message: "Enter Volcano Engine API Key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
}
|
||||
|
||||
const trimmed = normalizeApiKeyInput(String(key));
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "VOLCANO_ENGINE_API_KEY",
|
||||
value: trimmed,
|
||||
});
|
||||
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);
|
||||
return {
|
||||
config: configWithModel,
|
||||
agentModelOverride: VOLCENGINE_DEFAULT_MODEL,
|
||||
};
|
||||
}
|
||||
@@ -41,6 +41,8 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
|
||||
"xai-api-key": "xai",
|
||||
"litellm-api-key": "litellm",
|
||||
"qwen-portal": "qwen-portal",
|
||||
"volcengine-api-key": "volcengine",
|
||||
"byteplus-api-key": "byteplus",
|
||||
"minimax-portal": "minimax-portal",
|
||||
"qianfan-api-key": "qianfan",
|
||||
"custom-api-key": "custom",
|
||||
|
||||
@@ -258,7 +258,15 @@ export async function promptDefaultModel(
|
||||
}
|
||||
|
||||
if (hasPreferredProvider && preferredProvider) {
|
||||
models = models.filter((entry) => entry.provider === preferredProvider);
|
||||
models = models.filter((entry) => {
|
||||
if (preferredProvider === "volcengine") {
|
||||
return entry.provider === "volcengine" || entry.provider === "volcengine-plan";
|
||||
}
|
||||
if (preferredProvider === "byteplus") {
|
||||
return entry.provider === "byteplus" || entry.provider === "byteplus-plan";
|
||||
}
|
||||
return entry.provider === preferredProvider;
|
||||
});
|
||||
if (preferredProvider === "anthropic") {
|
||||
models = models.filter((entry) => !isAnthropicLegacyModel(entry));
|
||||
}
|
||||
|
||||
@@ -227,6 +227,27 @@ describe("onboard (non-interactive): provider auth", () => {
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("stores Volcano Engine API key and sets default model", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-volcengine-", async (env) => {
|
||||
const cfg = await runOnboardingAndReadConfig(env, {
|
||||
authChoice: "volcengine-api-key",
|
||||
volcengineApiKey: "volcengine-test-key",
|
||||
});
|
||||
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("volcengine-plan/ark-code-latest");
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("infers BytePlus auth choice from --byteplus-api-key and sets default model", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-byteplus-infer-", async (env) => {
|
||||
const cfg = await runOnboardingAndReadConfig(env, {
|
||||
byteplusApiKey: "byteplus-test-key",
|
||||
});
|
||||
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("byteplus-plan/ark-code-latest");
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("stores Vercel AI Gateway API key and sets default model", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-ai-gateway-", async (env) => {
|
||||
const cfg = await runOnboardingAndReadConfig(env, {
|
||||
|
||||
@@ -28,6 +28,8 @@ type AuthChoiceFlagOptions = Pick<
|
||||
| "xaiApiKey"
|
||||
| "litellmApiKey"
|
||||
| "qianfanApiKey"
|
||||
| "volcengineApiKey"
|
||||
| "byteplusApiKey"
|
||||
| "customBaseUrl"
|
||||
| "customModelId"
|
||||
| "customApiKey"
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
resolveCustomProviderId,
|
||||
} from "../../onboard-custom.js";
|
||||
import type { AuthChoice, OnboardOptions } from "../../onboard-types.js";
|
||||
import { applyPrimaryModel } from "../../model-picker.js";
|
||||
import { applyOpenAIConfig } from "../../openai-model-default.js";
|
||||
import { detectZaiEndpoint } from "../../zai-endpoint-detect.js";
|
||||
import { resolveNonInteractiveApiKey } from "../api-keys.js";
|
||||
@@ -303,6 +304,52 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
return applyXaiConfig(nextConfig);
|
||||
}
|
||||
|
||||
if (authChoice === "volcengine-api-key") {
|
||||
const resolved = await resolveNonInteractiveApiKey({
|
||||
provider: "volcengine",
|
||||
cfg: baseConfig,
|
||||
flagValue: opts.volcengineApiKey,
|
||||
flagName: "--volcengine-api-key",
|
||||
envVar: "VOLCANO_ENGINE_API_KEY",
|
||||
runtime,
|
||||
});
|
||||
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)}`);
|
||||
}
|
||||
return applyPrimaryModel(nextConfig, "volcengine-plan/ark-code-latest");
|
||||
}
|
||||
|
||||
if (authChoice === "byteplus-api-key") {
|
||||
const resolved = await resolveNonInteractiveApiKey({
|
||||
provider: "byteplus",
|
||||
cfg: baseConfig,
|
||||
flagValue: opts.byteplusApiKey,
|
||||
flagName: "--byteplus-api-key",
|
||||
envVar: "BYTEPLUS_API_KEY",
|
||||
runtime,
|
||||
});
|
||||
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)}`);
|
||||
}
|
||||
return applyPrimaryModel(nextConfig, "byteplus-plan/ark-code-latest");
|
||||
}
|
||||
|
||||
if (authChoice === "qianfan-api-key") {
|
||||
const resolved = await resolveNonInteractiveApiKey({
|
||||
provider: "qianfan",
|
||||
|
||||
@@ -21,6 +21,8 @@ type OnboardProviderAuthOptionKey = keyof Pick<
|
||||
| "xaiApiKey"
|
||||
| "litellmApiKey"
|
||||
| "qianfanApiKey"
|
||||
| "volcengineApiKey"
|
||||
| "byteplusApiKey"
|
||||
>;
|
||||
|
||||
export type OnboardProviderAuthFlag = {
|
||||
@@ -166,4 +168,18 @@ export const ONBOARD_PROVIDER_AUTH_FLAGS: ReadonlyArray<OnboardProviderAuthFlag>
|
||||
cliOption: "--qianfan-api-key <key>",
|
||||
description: "QIANFAN API key",
|
||||
},
|
||||
{
|
||||
optionKey: "volcengineApiKey",
|
||||
authChoice: "volcengine-api-key",
|
||||
cliFlag: "--volcengine-api-key",
|
||||
cliOption: "--volcengine-api-key <key>",
|
||||
description: "Volcano Engine API key",
|
||||
},
|
||||
{
|
||||
optionKey: "byteplusApiKey",
|
||||
authChoice: "byteplus-api-key",
|
||||
cliFlag: "--byteplus-api-key",
|
||||
cliOption: "--byteplus-api-key <key>",
|
||||
description: "BytePlus API key",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -45,6 +45,8 @@ export type AuthChoice =
|
||||
| "copilot-proxy"
|
||||
| "qwen-portal"
|
||||
| "xai-api-key"
|
||||
| "volcengine-api-key"
|
||||
| "byteplus-api-key"
|
||||
| "qianfan-api-key"
|
||||
| "custom-api-key"
|
||||
| "skip";
|
||||
@@ -71,6 +73,8 @@ export type AuthChoiceGroupId =
|
||||
| "huggingface"
|
||||
| "qianfan"
|
||||
| "xai"
|
||||
| "volcengine"
|
||||
| "byteplus"
|
||||
| "custom";
|
||||
export type GatewayAuthChoice = "token" | "password";
|
||||
export type ResetScope = "config" | "config+creds+sessions" | "full";
|
||||
@@ -119,6 +123,8 @@ export type OnboardOptions = {
|
||||
huggingfaceApiKey?: string;
|
||||
opencodeZenApiKey?: string;
|
||||
xaiApiKey?: string;
|
||||
volcengineApiKey?: string;
|
||||
byteplusApiKey?: string;
|
||||
qianfanApiKey?: string;
|
||||
customBaseUrl?: string;
|
||||
customApiKey?: string;
|
||||
|
||||
Reference in New Issue
Block a user