mirror of https://github.com/openclaw/openclaw.git
feat(volcengine): integrate Volcengine & Byteplus Provider
This commit is contained in:
parent
95c14d9b5f
commit
559736a5a0
|
|
@ -216,6 +216,70 @@ Model refs:
|
|||
|
||||
See [/providers/qwen](/providers/qwen) for setup details and notes.
|
||||
|
||||
### Volcano Engine (Doubao)
|
||||
|
||||
Volcano Engine (火山引擎) provides access to Doubao and other models in China.
|
||||
|
||||
- Provider: `volcengine` (coding: `volcengine-plan`)
|
||||
- Auth: `VOLCANO_ENGINE_API_KEY`
|
||||
- Example model: `volcengine/doubao-seed-1-8-251228`
|
||||
- CLI: `openclaw onboard --auth-choice volcengine-api-key`
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "volcengine/doubao-seed-1-8-251228" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Available models:
|
||||
|
||||
- `volcengine/doubao-seed-1-8-251228` (Doubao Seed 1.8)
|
||||
- `volcengine/doubao-seed-code-preview-251028`
|
||||
- `volcengine/kimi-k2-5-260127` (Kimi K2.5)
|
||||
- `volcengine/glm-4-7-251222` (GLM 4.7)
|
||||
- `volcengine/deepseek-v3-2-251201` (DeepSeek V3.2 128K)
|
||||
|
||||
Coding models (`volcengine-plan`):
|
||||
|
||||
- `volcengine-plan/ark-code-latest`
|
||||
- `volcengine-plan/doubao-seed-code`
|
||||
- `volcengine-plan/kimi-k2.5`
|
||||
- `volcengine-plan/kimi-k2-thinking`
|
||||
- `volcengine-plan/glm-4.7`
|
||||
|
||||
### BytePlus (International)
|
||||
|
||||
BytePlus ARK provides access to the same models as Volcano Engine for international users.
|
||||
|
||||
- Provider: `byteplus` (coding: `byteplus-plan`)
|
||||
- Auth: `BYTEPLUS_API_KEY`
|
||||
- Example model: `byteplus/seed-1-8-251228`
|
||||
- CLI: `openclaw onboard --auth-choice byteplus-api-key`
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "byteplus/seed-1-8-251228" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Available models:
|
||||
|
||||
- `byteplus/seed-1-8-251228` (Seed 1.8)
|
||||
- `byteplus/kimi-k2-5-260127` (Kimi K2.5)
|
||||
- `byteplus/glm-4-7-251222` (GLM 4.7)
|
||||
|
||||
Coding models (`byteplus-plan`):
|
||||
|
||||
- `byteplus-plan/ark-code-latest`
|
||||
- `byteplus-plan/doubao-seed-code`
|
||||
- `byteplus-plan/kimi-k2.5`
|
||||
- `byteplus-plan/kimi-k2-thinking`
|
||||
- `byteplus-plan/glm-4.7`
|
||||
|
||||
### Synthetic
|
||||
|
||||
Synthetic provides Anthropic-compatible models behind the `synthetic` provider:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
import type { ModelDefinitionConfig } from "../config/types.js";
|
||||
|
||||
export const BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
|
||||
export const BYTEPLUS_CODING_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/coding/v3";
|
||||
export const BYTEPLUS_DEFAULT_MODEL_ID = "seed-1-8-251228";
|
||||
export const BYTEPLUS_CODING_DEFAULT_MODEL_ID = "ark-code-latest";
|
||||
export const BYTEPLUS_DEFAULT_MODEL_REF = `byteplus/${BYTEPLUS_DEFAULT_MODEL_ID}`;
|
||||
|
||||
// BytePlus pricing (approximate, adjust based on actual pricing)
|
||||
export const BYTEPLUS_DEFAULT_COST = {
|
||||
input: 0.0001, // $0.0001 per 1K tokens
|
||||
output: 0.0002, // $0.0002 per 1K tokens
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Complete catalog of BytePlus ARK models.
|
||||
*
|
||||
* BytePlus ARK provides access to various models
|
||||
* through the ARK API. Authentication requires a BYTEPLUS_API_KEY.
|
||||
*/
|
||||
export const BYTEPLUS_MODEL_CATALOG = [
|
||||
{
|
||||
id: "seed-1-8-251228",
|
||||
name: "Seed 1.8",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "kimi-k2-5-260127",
|
||||
name: "Kimi K2.5",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "glm-4-7-251222",
|
||||
name: "GLM 4.7",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type BytePlusCatalogEntry = (typeof BYTEPLUS_MODEL_CATALOG)[number];
|
||||
export type BytePlusCodingCatalogEntry = (typeof BYTEPLUS_CODING_MODEL_CATALOG)[number];
|
||||
|
||||
export function buildBytePlusModelDefinition(
|
||||
entry: BytePlusCatalogEntry | BytePlusCodingCatalogEntry,
|
||||
): ModelDefinitionConfig {
|
||||
return {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
reasoning: entry.reasoning,
|
||||
input: [...entry.input],
|
||||
cost: BYTEPLUS_DEFAULT_COST,
|
||||
contextWindow: entry.contextWindow,
|
||||
maxTokens: entry.maxTokens,
|
||||
};
|
||||
}
|
||||
|
||||
export const BYTEPLUS_CODING_MODEL_CATALOG = [
|
||||
{
|
||||
id: "ark-code-latest",
|
||||
name: "Ark Coding Plan",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "doubao-seed-code",
|
||||
name: "Doubao Seed Code",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "glm-4.7",
|
||||
name: "GLM 4.7 Coding",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "kimi-k2-thinking",
|
||||
name: "Kimi K2 Thinking",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "kimi-k2.5",
|
||||
name: "Kimi K2.5 Coding",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
] as const;
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import type { ModelDefinitionConfig } from "../config/types.js";
|
||||
|
||||
export const DOUBAO_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
|
||||
export const DOUBAO_CODING_BASE_URL = "https://ark.cn-beijing.volces.com/api/coding/v3";
|
||||
export const DOUBAO_DEFAULT_MODEL_ID = "doubao-seed-1-8-251228";
|
||||
export const DOUBAO_CODING_DEFAULT_MODEL_ID = "ark-code-latest";
|
||||
export const DOUBAO_DEFAULT_MODEL_REF = `volcengine/${DOUBAO_DEFAULT_MODEL_ID}`;
|
||||
|
||||
// Volcano Engine Doubao pricing (approximate, adjust based on actual pricing)
|
||||
export const DOUBAO_DEFAULT_COST = {
|
||||
input: 0.0001, // ¥0.0001 per 1K tokens
|
||||
output: 0.0002, // ¥0.0002 per 1K tokens
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Complete catalog of Volcano Engine models.
|
||||
*
|
||||
* Volcano Engine provides access to models
|
||||
* through the API. Authentication requires a Volcano Engine API Key.
|
||||
*/
|
||||
export const DOUBAO_MODEL_CATALOG = [
|
||||
{
|
||||
id: "doubao-seed-code-preview-251028",
|
||||
name: "doubao-seed-code-preview-251028",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "doubao-seed-1-8-251228",
|
||||
name: "Doubao Seed 1.8",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "kimi-k2-5-260127",
|
||||
name: "Kimi K2.5",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "glm-4-7-251222",
|
||||
name: "GLM 4.7",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "deepseek-v3-2-251201",
|
||||
name: "DeepSeek V3.2",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 128000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type DoubaoCatalogEntry = (typeof DOUBAO_MODEL_CATALOG)[number];
|
||||
export type DoubaoCodingCatalogEntry = (typeof DOUBAO_CODING_MODEL_CATALOG)[number];
|
||||
|
||||
export function buildDoubaoModelDefinition(
|
||||
entry: DoubaoCatalogEntry | DoubaoCodingCatalogEntry,
|
||||
): ModelDefinitionConfig {
|
||||
return {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
reasoning: entry.reasoning,
|
||||
input: [...entry.input],
|
||||
cost: DOUBAO_DEFAULT_COST,
|
||||
contextWindow: entry.contextWindow,
|
||||
maxTokens: entry.maxTokens,
|
||||
};
|
||||
}
|
||||
|
||||
export const DOUBAO_CODING_MODEL_CATALOG = [
|
||||
{
|
||||
id: "ark-code-latest",
|
||||
name: "Ark Coding Plan",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "doubao-seed-code",
|
||||
name: "Doubao Seed Code",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "glm-4.7",
|
||||
name: "GLM 4.7 Coding",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "kimi-k2-thinking",
|
||||
name: "Kimi K2 Thinking",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "kimi-k2.5",
|
||||
name: "Kimi K2.5 Coding",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
{
|
||||
id: "doubao-seed-code-preview-251028",
|
||||
name: "Doubao Seed Code Preview",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
] as const;
|
||||
|
|
@ -279,6 +279,13 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
|||
return pick("QWEN_OAUTH_TOKEN") ?? pick("QWEN_PORTAL_API_KEY");
|
||||
}
|
||||
|
||||
if (normalized === "volcengine" || normalized === "volcengine-plan") {
|
||||
return pick("VOLCANO_ENGINE_API_KEY");
|
||||
}
|
||||
|
||||
if (normalized === "byteplus" || normalized === "byteplus-plan") {
|
||||
return pick("BYTEPLUS_API_KEY");
|
||||
}
|
||||
if (normalized === "minimax-portal") {
|
||||
return pick("MINIMAX_OAUTH_TOKEN") ?? pick("MINIMAX_API_KEY");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ export function normalizeProviderId(provider: string): string {
|
|||
if (normalized === "kimi-code") {
|
||||
return "kimi-coding";
|
||||
}
|
||||
// Backward compatibility for older provider naming.
|
||||
if (normalized === "bytedance" || normalized === "doubao") {
|
||||
return "volcengine";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,20 @@ import {
|
|||
buildCloudflareAiGatewayModelDefinition,
|
||||
resolveCloudflareAiGatewayBaseUrl,
|
||||
} from "./cloudflare-ai-gateway.js";
|
||||
import {
|
||||
buildBytePlusModelDefinition,
|
||||
BYTEPLUS_BASE_URL,
|
||||
BYTEPLUS_MODEL_CATALOG,
|
||||
BYTEPLUS_CODING_BASE_URL,
|
||||
BYTEPLUS_CODING_MODEL_CATALOG,
|
||||
} from "./byteplus-models.js";
|
||||
import {
|
||||
buildDoubaoModelDefinition,
|
||||
DOUBAO_BASE_URL,
|
||||
DOUBAO_MODEL_CATALOG,
|
||||
DOUBAO_CODING_BASE_URL,
|
||||
DOUBAO_CODING_MODEL_CATALOG,
|
||||
} from "./doubao-models.js";
|
||||
import {
|
||||
discoverHuggingfaceModels,
|
||||
HUGGINGFACE_BASE_URL,
|
||||
|
|
@ -547,6 +561,38 @@ function buildSyntheticProvider(): ProviderConfig {
|
|||
};
|
||||
}
|
||||
|
||||
function buildDoubaoProvider(): ProviderConfig {
|
||||
return {
|
||||
baseUrl: DOUBAO_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: DOUBAO_MODEL_CATALOG.map(buildDoubaoModelDefinition),
|
||||
};
|
||||
}
|
||||
|
||||
function buildDoubaoCodingProvider(): ProviderConfig {
|
||||
return {
|
||||
baseUrl: DOUBAO_CODING_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: DOUBAO_CODING_MODEL_CATALOG.map(buildDoubaoModelDefinition),
|
||||
};
|
||||
}
|
||||
|
||||
function buildBytePlusProvider(): ProviderConfig {
|
||||
return {
|
||||
baseUrl: BYTEPLUS_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: BYTEPLUS_MODEL_CATALOG.map(buildBytePlusModelDefinition),
|
||||
};
|
||||
}
|
||||
|
||||
function buildBytePlusCodingProvider(): ProviderConfig {
|
||||
return {
|
||||
baseUrl: BYTEPLUS_CODING_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: BYTEPLUS_CODING_MODEL_CATALOG.map(buildBytePlusModelDefinition),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildXiaomiProvider(): ProviderConfig {
|
||||
return {
|
||||
baseUrl: XIAOMI_BASE_URL,
|
||||
|
|
@ -745,6 +791,28 @@ export async function resolveImplicitProviders(params: {
|
|||
};
|
||||
}
|
||||
|
||||
const volcengineKey =
|
||||
resolveEnvApiKeyVarName("volcengine") ??
|
||||
resolveApiKeyFromProfiles({ provider: "volcengine", store: authStore });
|
||||
if (volcengineKey) {
|
||||
providers.volcengine = { ...buildDoubaoProvider(), apiKey: volcengineKey };
|
||||
providers["volcengine-plan"] = {
|
||||
...buildDoubaoCodingProvider(),
|
||||
apiKey: volcengineKey,
|
||||
};
|
||||
}
|
||||
|
||||
const byteplusKey =
|
||||
resolveEnvApiKeyVarName("byteplus") ??
|
||||
resolveApiKeyFromProfiles({ provider: "byteplus", store: authStore });
|
||||
if (byteplusKey) {
|
||||
providers.byteplus = { ...buildBytePlusProvider(), apiKey: byteplusKey };
|
||||
providers["byteplus-plan"] = {
|
||||
...buildBytePlusCodingProvider(),
|
||||
apiKey: byteplusKey,
|
||||
};
|
||||
}
|
||||
|
||||
const xiaomiKey =
|
||||
resolveEnvApiKeyVarName("xiaomi") ??
|
||||
resolveApiKeyFromProfiles({ provider: "xiaomi", store: authStore });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { mkdtempSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import { resolveImplicitProviders } from "./models-config.providers.js";
|
||||
|
||||
describe("Volcengine and BytePlus providers", () => {
|
||||
it("includes volcengine and volcengine-plan when VOLCANO_ENGINE_API_KEY is configured", async () => {
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
|
||||
const envSnapshot = captureEnv(["VOLCANO_ENGINE_API_KEY"]);
|
||||
process.env.VOLCANO_ENGINE_API_KEY = "test-key";
|
||||
|
||||
try {
|
||||
const providers = await resolveImplicitProviders({ agentDir });
|
||||
expect(providers?.volcengine).toBeDefined();
|
||||
expect(providers?.["volcengine-plan"]).toBeDefined();
|
||||
expect(providers?.volcengine?.apiKey).toBe("VOLCANO_ENGINE_API_KEY");
|
||||
expect(providers?.["volcengine-plan"]?.apiKey).toBe("VOLCANO_ENGINE_API_KEY");
|
||||
} finally {
|
||||
envSnapshot.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("includes byteplus and byteplus-plan when BYTEPLUS_API_KEY is configured", async () => {
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
|
||||
const envSnapshot = captureEnv(["BYTEPLUS_API_KEY"]);
|
||||
process.env.BYTEPLUS_API_KEY = "test-key";
|
||||
|
||||
try {
|
||||
const providers = await resolveImplicitProviders({ agentDir });
|
||||
expect(providers?.byteplus).toBeDefined();
|
||||
expect(providers?.["byteplus-plan"]).toBeDefined();
|
||||
expect(providers?.byteplus?.apiKey).toBe("BYTEPLUS_API_KEY");
|
||||
expect(providers?.["byteplus-plan"]?.apiKey).toBe("BYTEPLUS_API_KEY");
|
||||
} finally {
|
||||
envSnapshot.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -14,7 +14,10 @@ import {
|
|||
type ModelRegistry,
|
||||
} from "../pi-model-discovery.js";
|
||||
|
||||
type InlineModelEntry = ModelDefinitionConfig & { provider: string; baseUrl?: string };
|
||||
type InlineModelEntry = ModelDefinitionConfig & {
|
||||
provider: string;
|
||||
baseUrl?: string;
|
||||
};
|
||||
type InlineProviderConfig = {
|
||||
baseUrl?: string;
|
||||
api?: ModelDefinitionConfig["api"];
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ export function registerOnboardCommand(program: Command) {
|
|||
opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined,
|
||||
xaiApiKey: opts.xaiApiKey as string | undefined,
|
||||
litellmApiKey: opts.litellmApiKey as string | undefined,
|
||||
volcengineApiKey: opts.volcengineApiKey as string | undefined,
|
||||
byteplusApiKey: opts.byteplusApiKey as string | undefined,
|
||||
customBaseUrl: opts.customBaseUrl as string | undefined,
|
||||
customApiKey: opts.customApiKey as string | undefined,
|
||||
customModelId: opts.customModelId as string | undefined,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue