From 77a35025e86d0d07304a2e2e74e378b31c1f1e27 Mon Sep 17 00:00:00 2001 From: pomelo-nwu Date: Mon, 9 Mar 2026 13:26:17 +0800 Subject: [PATCH] feat: integrate Alibaba Bailian Coding Plan into onboarding wizard --- src/cli/program/register.onboard.ts | 2 + src/commands/auth-choice-options.ts | 17 +++ .../auth-choice.apply.api-providers.ts | 46 ++++++++ src/commands/onboard-auth.config-core.ts | 98 +++++++++++++++++ src/commands/onboard-auth.credentials.ts | 18 ++- src/commands/onboard-auth.models.ts | 103 ++++++++++++++++++ src/commands/onboard-auth.ts | 6 + .../local/auth-choice-inference.ts | 2 + .../local/auth-choice.ts | 57 ++++++++++ src/commands/onboard-provider-auth-flags.ts | 16 +++ src/commands/onboard-types.ts | 5 + 11 files changed, 369 insertions(+), 1 deletion(-) diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 03fb832a041..7fd5283dcd4 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -160,6 +160,8 @@ export function registerOnboardCommand(program: Command) { zaiApiKey: opts.zaiApiKey as string | undefined, xiaomiApiKey: opts.xiaomiApiKey as string | undefined, qianfanApiKey: opts.qianfanApiKey as string | undefined, + bailianApiKeyCn: opts.bailianApiKeyCn as string | undefined, + bailianApiKey: opts.bailianApiKey as string | undefined, minimaxApiKey: opts.minimaxApiKey as string | undefined, syntheticApiKey: opts.syntheticApiKey as string | undefined, veniceApiKey: opts.veniceApiKey as string | undefined, diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 27fee5dc01f..3c7a609161e 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -119,6 +119,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "API key", choices: ["qianfan-api-key"], }, + { + value: "bailian", + label: "Alibaba Bailian", + hint: "Coding Plan API key (CN / Global)", + choices: ["bailian-api-key-cn", "bailian-api-key"], + }, { value: "copilot", label: "Copilot", @@ -297,6 +303,17 @@ const BASE_AUTH_CHOICE_OPTIONS: ReadonlyArray = [ label: "MiniMax M2.5 Highspeed", hint: "Official fast tier", }, + { value: "qianfan-api-key", label: "Qianfan API key" }, + { + value: "bailian-api-key-cn", + label: "Coding Plan API Key for China (subscription)", + hint: "Endpoint: coding.dashscope.aliyuncs.com", + }, + { + value: "bailian-api-key", + label: "Coding Plan API Key for Global/Intl (subscription)", + hint: "Endpoint: coding-intl.dashscope.aliyuncs.com", + }, { value: "custom-api-key", label: "Custom Provider" }, ]; diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 370951e9f0d..9d7b717385b 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -76,6 +76,12 @@ import { setXiaomiApiKey, setZaiApiKey, ZAI_DEFAULT_MODEL_REF, + BAILIAN_DEFAULT_MODEL_REF, + applyBailianConfig, + applyBailianConfigCn, + applyBailianProviderConfig, + applyBailianProviderConfigCn, + setBailianApiKey, } from "./onboard-auth.js"; import type { AuthChoice, SecretInputMode } from "./onboard-types.js"; import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js"; @@ -295,6 +301,46 @@ const SIMPLE_API_KEY_PROVIDER_FLOWS: Partial String(value ?? "").trim(), + validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), + }, + "bailian-api-key": { + provider: "bailian", + profileId: "bailian:default", + expectedProviders: ["bailian"], + envLabel: "BAILIAN_API_KEY", + promptMessage: "Enter Alibaba Bailian Coding Plan API key (Global/Intl)", + setCredential: setBailianApiKey, + defaultModel: BAILIAN_DEFAULT_MODEL_REF, + applyDefaultConfig: applyBailianConfig, + applyProviderConfig: applyBailianProviderConfig, + noteDefault: BAILIAN_DEFAULT_MODEL_REF, + noteMessage: [ + "Get your API key at: https://bailian.console.aliyun.com/", + "Endpoint: coding-intl.dashscope.aliyuncs.com", + "Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.", + ].join("\n"), + noteTitle: "Alibaba Bailian Coding Plan (Global/Intl)", + normalize: (value) => String(value ?? "").trim(), + validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), + }, "synthetic-api-key": { provider: "synthetic", profileId: "synthetic:default", diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 103343d5914..5b6e0d5a590 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -573,3 +573,101 @@ export function applyQianfanConfig(cfg: OpenClawConfig): OpenClawConfig { const next = applyQianfanProviderConfig(cfg); return applyAgentDefaultModelPrimary(next, QIANFAN_DEFAULT_MODEL_REF); } + +// Alibaba Cloud Model Studio (Bailian) Coding Plan +import { + BAILIAN_CN_BASE_URL, + BAILIAN_GLOBAL_BASE_URL, + BAILIAN_DEFAULT_MODEL_REF, + buildBailianModelDefinition, +} from "./onboard-auth.models.js"; + +function applyBailianProviderConfigWithBaseUrl( + cfg: OpenClawConfig, + baseUrl: string, +): OpenClawConfig { + const models = { ...cfg.agents?.defaults?.models }; + + const bailianModelIds = [ + "qwen3.5-plus", + "qwen3-max-2026-01-23", + "qwen3-coder-next", + "qwen3-coder-plus", + "MiniMax-M2.5", + "glm-5", + "glm-4.7", + "kimi-k2.5", + ]; + for (const modelId of bailianModelIds) { + const modelRef = `bailian/${modelId}`; + if (!models[modelRef]) { + models[modelRef] = {}; + } + } + models[BAILIAN_DEFAULT_MODEL_REF] = { + ...models[BAILIAN_DEFAULT_MODEL_REF], + alias: models[BAILIAN_DEFAULT_MODEL_REF]?.alias ?? "Qwen", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.bailian; + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + + const defaultModels = [ + buildBailianModelDefinition({ id: "qwen3.5-plus" }), + buildBailianModelDefinition({ id: "qwen3-max-2026-01-23" }), + buildBailianModelDefinition({ id: "qwen3-coder-next" }), + buildBailianModelDefinition({ id: "qwen3-coder-plus" }), + buildBailianModelDefinition({ id: "MiniMax-M2.5" }), + buildBailianModelDefinition({ id: "glm-5" }), + buildBailianModelDefinition({ id: "glm-4.7" }), + buildBailianModelDefinition({ id: "kimi-k2.5" }), + ]; + + const mergedModels = [...existingModels]; + const seen = new Set(existingModels.map((m) => m.id)); + for (const model of defaultModels) { + if (!seen.has(model.id)) { + mergedModels.push(model); + seen.add(model.id); + } + } + + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + + providers.bailian = { + ...existingProviderRest, + baseUrl, + api: "openai-completions", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : defaultModels, + }; + + return applyOnboardAuthAgentModelsAndProviders(cfg, { agentModels: models, providers }); +} + +export function applyBailianProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + const existingBaseUrl = cfg.models?.providers?.bailian?.baseUrl; + const resolvedBaseUrl = + typeof existingBaseUrl === "string" ? existingBaseUrl : BAILIAN_GLOBAL_BASE_URL; + return applyBailianProviderConfigWithBaseUrl(cfg, resolvedBaseUrl); +} + +export function applyBailianProviderConfigCn(cfg: OpenClawConfig): OpenClawConfig { + return applyBailianProviderConfigWithBaseUrl(cfg, BAILIAN_CN_BASE_URL); +} + +export function applyBailianConfig(cfg: OpenClawConfig): OpenClawConfig { + const next = applyBailianProviderConfig(cfg); + return applyAgentDefaultModelPrimary(next, BAILIAN_DEFAULT_MODEL_REF); +} + +export function applyBailianConfigCn(cfg: OpenClawConfig): OpenClawConfig { + const next = applyBailianProviderConfigCn(cfg); + return applyAgentDefaultModelPrimary(next, BAILIAN_DEFAULT_MODEL_REF); +} diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index c32a3ea9ae6..41b95f0d537 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -15,7 +15,11 @@ import { PROVIDER_ENV_VARS } from "../secrets/provider-env-vars.js"; import { normalizeSecretInput } from "../utils/normalize-secret-input.js"; import type { SecretInputMode } from "./onboard-types.js"; export { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "../agents/cloudflare-ai-gateway.js"; -export { MISTRAL_DEFAULT_MODEL_REF, XAI_DEFAULT_MODEL_REF } from "./onboard-auth.models.js"; +export { + MISTRAL_DEFAULT_MODEL_REF, + XAI_DEFAULT_MODEL_REF, + BAILIAN_DEFAULT_MODEL_REF, +} from "./onboard-auth.models.js"; export { KILOCODE_DEFAULT_MODEL_REF }; const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir(); @@ -472,6 +476,18 @@ export function setQianfanApiKey( }); } +export function setBailianApiKey( + key: SecretInput, + agentDir?: string, + options?: ApiKeyStorageOptions, +) { + upsertAuthProfile({ + profileId: "bailian:default", + credential: buildApiKeyCredential("bailian", key, undefined, options), + agentDir: resolveAuthAgentDir(agentDir), + }); +} + export function setXaiApiKey(key: SecretInput, agentDir?: string, options?: ApiKeyStorageOptions) { upsertAuthProfile({ profileId: "xai:default", diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index 36ae85dadac..d3615bdd33e 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -224,3 +224,106 @@ export function buildKilocodeModelDefinition(): ModelDefinitionConfig { maxTokens: KILOCODE_DEFAULT_MAX_TOKENS, }; } + +// Alibaba Cloud Model Studio (Bailian) Coding Plan +export const BAILIAN_CN_BASE_URL = "https://coding.dashscope.aliyuncs.com/v1"; +export const BAILIAN_GLOBAL_BASE_URL = "https://coding-intl.dashscope.aliyuncs.com/v1"; +export const BAILIAN_BASE_URL = BAILIAN_CN_BASE_URL; +export const BAILIAN_DEFAULT_MODEL_ID = "qwen3.5-plus"; +export const BAILIAN_DEFAULT_MODEL_REF = `bailian/${BAILIAN_DEFAULT_MODEL_ID}`; +export const BAILIAN_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const BAILIAN_MODEL_CATALOG = { + "qwen3.5-plus": { + name: "qwen3.5-plus", + reasoning: false, + input: ["text", "image"], + contextWindow: 1000000, + maxTokens: 65536, + }, + "qwen3-max-2026-01-23": { + name: "qwen3-max-2026-01-23", + reasoning: false, + input: ["text"], + contextWindow: 262144, + maxTokens: 65536, + }, + "qwen3-coder-next": { + name: "qwen3-coder-next", + reasoning: false, + input: ["text"], + contextWindow: 262144, + maxTokens: 65536, + }, + "qwen3-coder-plus": { + name: "qwen3-coder-plus", + reasoning: false, + input: ["text"], + contextWindow: 1000000, + maxTokens: 65536, + }, + "MiniMax-M2.5": { + name: "MiniMax-M2.5", + reasoning: false, + input: ["text"], + contextWindow: 1000000, + maxTokens: 65536, + }, + "glm-5": { + name: "glm-5", + reasoning: false, + input: ["text"], + contextWindow: 202752, + maxTokens: 16384, + }, + "glm-4.7": { + name: "glm-4.7", + reasoning: false, + input: ["text"], + contextWindow: 202752, + maxTokens: 16384, + }, + "kimi-k2.5": { + name: "kimi-k2.5", + reasoning: false, + input: ["text", "image"], + contextWindow: 262144, + maxTokens: 32768, + }, +} as const; + +type BailianCatalogId = keyof typeof BAILIAN_MODEL_CATALOG; + +export function buildBailianModelDefinition(params: { + id: string; + name?: string; + reasoning?: boolean; + input?: string[]; + cost?: ModelDefinitionConfig["cost"]; + contextWindow?: number; + maxTokens?: number; +}): ModelDefinitionConfig { + const catalog = BAILIAN_MODEL_CATALOG[params.id as BailianCatalogId]; + return { + id: params.id, + name: params.name ?? catalog?.name ?? params.id, + reasoning: params.reasoning ?? catalog?.reasoning ?? false, + input: + (params.input as ("text" | "image")[]) ?? + ([...(catalog?.input ?? ["text"])] as ("text" | "image")[]), + cost: params.cost ?? BAILIAN_DEFAULT_COST, + contextWindow: params.contextWindow ?? catalog?.contextWindow ?? 262144, + maxTokens: params.maxTokens ?? catalog?.maxTokens ?? 65536, + }; +} + +export function buildBailianDefaultModelDefinition(): ModelDefinitionConfig { + return buildBailianModelDefinition({ + id: BAILIAN_DEFAULT_MODEL_ID, + }); +} diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 13d2cf75bf0..8c81a0fa962 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -39,6 +39,10 @@ export { applyXiaomiProviderConfig, applyZaiConfig, applyZaiProviderConfig, + applyBailianConfig, + applyBailianConfigCn, + applyBailianProviderConfig, + applyBailianProviderConfigCn, KILOCODE_BASE_URL, } from "./onboard-auth.config-core.js"; export { @@ -84,6 +88,7 @@ export { setVolcengineApiKey, setZaiApiKey, setXaiApiKey, + setBailianApiKey, writeOAuthCredentials, HUGGINGFACE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, @@ -92,6 +97,7 @@ export { TOGETHER_DEFAULT_MODEL_REF, MISTRAL_DEFAULT_MODEL_REF, XAI_DEFAULT_MODEL_REF, + BAILIAN_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; export { buildKilocodeModelDefinition, diff --git a/src/commands/onboard-non-interactive/local/auth-choice-inference.ts b/src/commands/onboard-non-interactive/local/auth-choice-inference.ts index aecab3ba489..29aeaa1d4e9 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice-inference.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice-inference.ts @@ -30,6 +30,8 @@ type AuthChoiceFlagOptions = Pick< | "xaiApiKey" | "litellmApiKey" | "qianfanApiKey" + | "bailianApiKeyCn" + | "bailianApiKey" | "volcengineApiKey" | "byteplusApiKey" | "customBaseUrl" diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 98eef51dd20..30ecc9c20ac 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -15,6 +15,8 @@ import { applyCloudflareAiGatewayConfig, applyKilocodeConfig, applyQianfanConfig, + applyBailianConfig, + applyBailianConfigCn, applyKimiCodeConfig, applyMinimaxApiConfig, applyMinimaxApiConfigCn, @@ -37,6 +39,7 @@ import { setCloudflareAiGatewayConfig, setByteplusApiKey, setQianfanApiKey, + setBailianApiKey, setGeminiApiKey, setKilocodeApiKey, setKimiCodingApiKey, @@ -498,6 +501,60 @@ export async function applyNonInteractiveAuthChoice(params: { return applyQianfanConfig(nextConfig); } + if (authChoice === "bailian-api-key-cn") { + const resolved = await resolveApiKey({ + provider: "bailian", + cfg: baseConfig, + flagValue: opts.bailianApiKeyCn, + flagName: "--bailian-api-key-cn", + envVar: "BAILIAN_API_KEY", + runtime, + }); + if (!resolved) { + return null; + } + if ( + !(await maybeSetResolvedApiKey(resolved, (value) => + setBailianApiKey(value, undefined, apiKeyStorageOptions), + )) + ) { + return null; + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "bailian:default", + provider: "bailian", + mode: "api_key", + }); + return applyBailianConfigCn(nextConfig); + } + + if (authChoice === "bailian-api-key") { + const resolved = await resolveApiKey({ + provider: "bailian", + cfg: baseConfig, + flagValue: opts.bailianApiKey, + flagName: "--bailian-api-key", + envVar: "BAILIAN_API_KEY", + runtime, + }); + if (!resolved) { + return null; + } + if ( + !(await maybeSetResolvedApiKey(resolved, (value) => + setBailianApiKey(value, undefined, apiKeyStorageOptions), + )) + ) { + return null; + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "bailian:default", + provider: "bailian", + mode: "api_key", + }); + return applyBailianConfig(nextConfig); + } + if (authChoice === "openai-api-key") { const resolved = await resolveApiKey({ provider: "openai", diff --git a/src/commands/onboard-provider-auth-flags.ts b/src/commands/onboard-provider-auth-flags.ts index a1038625a78..6555caf1f91 100644 --- a/src/commands/onboard-provider-auth-flags.ts +++ b/src/commands/onboard-provider-auth-flags.ts @@ -23,6 +23,8 @@ type OnboardProviderAuthOptionKey = keyof Pick< | "xaiApiKey" | "litellmApiKey" | "qianfanApiKey" + | "bailianApiKeyCn" + | "bailianApiKey" | "volcengineApiKey" | "byteplusApiKey" >; @@ -184,6 +186,20 @@ export const ONBOARD_PROVIDER_AUTH_FLAGS: ReadonlyArray cliOption: "--qianfan-api-key ", description: "QIANFAN API key", }, + { + optionKey: "bailianApiKeyCn", + authChoice: "bailian-api-key-cn", + cliFlag: "--bailian-api-key-cn", + cliOption: "--bailian-api-key-cn ", + description: "Alibaba Bailian Coding Plan API key (China)", + }, + { + optionKey: "bailianApiKey", + authChoice: "bailian-api-key", + cliFlag: "--bailian-api-key", + cliOption: "--bailian-api-key ", + description: "Alibaba Bailian Coding Plan API key (Global/Intl)", + }, { optionKey: "volcengineApiKey", authChoice: "volcengine-api-key", diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 7e938430517..e6d1bbcf806 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -49,6 +49,8 @@ export type AuthChoice = | "volcengine-api-key" | "byteplus-api-key" | "qianfan-api-key" + | "bailian-api-key-cn" + | "bailian-api-key" | "custom-api-key" | "skip"; export type AuthChoiceGroupId = @@ -75,6 +77,7 @@ export type AuthChoiceGroupId = | "together" | "huggingface" | "qianfan" + | "bailian" | "xai" | "volcengine" | "byteplus" @@ -135,6 +138,8 @@ export type OnboardOptions = { volcengineApiKey?: string; byteplusApiKey?: string; qianfanApiKey?: string; + bailianApiKeyCn?: string; + bailianApiKey?: string; customBaseUrl?: string; customApiKey?: string; customModelId?: string;