From a52786df8d35233f5cda8ce47f4298236685cd14 Mon Sep 17 00:00:00 2001 From: kuzen-so Date: Sun, 20 Sep 2026 15:34:32 +0800 Subject: [PATCH] feat(providers): add Volcengine Ark Agent Plan / Coding Plan presets The built-in Volcengine Ark preset only covers the standard pay-as-you-go endpoint (https://ark.cn-beijing.volces.com/api/v3). Ark's subscription plans are served from dedicated hosts with their own protocol variants: - Coding Plan: anthropic_messages at /api/coding, chat_completions at /api/coding/v3 - Agent Plan: anthropic_messages at /api/plan, chat_completions at /api/plan/v3 Calling plan models through /api/v3 does not consume plan quota and bills pay-as-you-go instead, so the plans need first-class presets. Add four named endpoint presets (Coding Plan / Agent Plan, each in an Anthropic and an OpenAI variant). All plan rows keep the shared `volcengine` vendor key so models.dev metadata still resolves; the stored base URL distinguishes the preset, and presetByUrl() now maps the plan paths back to their preset. KNOWN_PROVIDER_BASE_URLS maps the plan hosts to the volcengine catalog provider. Refs: - https://www.volcengine.com/docs/82379/1928261 (Coding Plan quickstart) - https://www.volcengine.com/activity/agentplan (Agent Plan) --- .../electron/main/models-dev-catalog.ts | 11 +++- .../03-runtime/12-provider-config-schema.md | 5 +- .../03-runtime/12-provider-config-schema.md | 5 +- packages/i18n/src/locales/de/index.ts | 4 ++ packages/i18n/src/locales/en/index.ts | 4 ++ packages/i18n/src/locales/es/index.ts | 4 ++ packages/i18n/src/locales/fr/index.ts | 4 ++ packages/i18n/src/locales/ko/index.ts | 4 ++ packages/i18n/src/locales/tr/index.ts | 4 ++ packages/i18n/src/locales/zh-CN/index.ts | 4 ++ packages/i18n/src/locales/zh-TW/index.ts | 4 ++ packages/shared/src/provider-presets.test.ts | 34 ++++++++++ packages/shared/src/provider-presets.ts | 66 +++++++++++++++++++ 13 files changed, 150 insertions(+), 3 deletions(-) diff --git a/apps/desktop/electron/main/models-dev-catalog.ts b/apps/desktop/electron/main/models-dev-catalog.ts index 0fdcaaf0ec..82628b04c1 100644 --- a/apps/desktop/electron/main/models-dev-catalog.ts +++ b/apps/desktop/electron/main/models-dev-catalog.ts @@ -559,7 +559,16 @@ const KNOWN_PROVIDER_BASE_URLS: Record = { "alibaba-cn": ["https://dashscope.aliyuncs.com/compatible-mode/v1"], "moonshotai-cn": ["https://api.moonshot.cn/v1"], "siliconflow-cn": ["https://api.siliconflow.cn/v1"], - volcengine: ["https://ark.cn-beijing.volces.com/api/v3"], + // Ark subscription plans (Agent Plan / Coding Plan) live on dedicated + // paths of the same host. Map them to the same models.dev provider so a + // plan row still inherits Doubao-family metadata. + volcengine: [ + "https://ark.cn-beijing.volces.com/api/v3", + "https://ark.cn-beijing.volces.com/api/coding", + "https://ark.cn-beijing.volces.com/api/coding/v3", + "https://ark.cn-beijing.volces.com/api/plan", + "https://ark.cn-beijing.volces.com/api/plan/v3", + ], // MiniMax exposes both an Anthropic endpoint (the published models.dev // URL) and an OpenAI-compatible `/v1` endpoint. Treat the latter as the // same provider so a custom OpenAI-style row still inherits M3's vision diff --git a/docs/spec/03-runtime/12-provider-config-schema.md b/docs/spec/03-runtime/12-provider-config-schema.md index 5d465e65aa..782058fa89 100644 --- a/docs/spec/03-runtime/12-provider-config-schema.md +++ b/docs/spec/03-runtime/12-provider-config-schema.md @@ -273,7 +273,10 @@ Fireworks, OpenCode Go (`opencode_go`), Z.AI / Z.AI Coding Plan. China: DeepSeek, Qwen DashScope (`alibaba-cn`), Moonshot (`moonshotai-cn`), Zhipu AI / Coding Plan, SiliconFlow (`siliconflow-cn`), Volcengine Ark, -MiniMax (`anthropic_messages` at `https://api.minimaxi.com/anthropic/v1`), +Volcengine Ark Coding Plan / Agent Plan (each in an `anthropic_messages` +variant at `https://ark.cn-beijing.volces.com/api/coding` or `/api/plan` +and an OpenAI `chat_completions` variant at `/api/coding/v3` or +`/api/plan/v3`; all keep the shared `volcengine` vendor key), MiniMax (`anthropic_messages` at `https://api.minimaxi.com/anthropic/v1`), MiniMax (OpenAI) (`chat_completions` at `https://api.minimaxi.com/v1`, aliases `minimax-openai` / `minimax-compatible`), Kimi For Coding (`anthropic_messages`). diff --git a/docs/zh-CN/spec/03-runtime/12-provider-config-schema.md b/docs/zh-CN/spec/03-runtime/12-provider-config-schema.md index 26ca173c8f..377a8211f0 100644 --- a/docs/zh-CN/spec/03-runtime/12-provider-config-schema.md +++ b/docs/zh-CN/spec/03-runtime/12-provider-config-schema.md @@ -176,7 +176,10 @@ API 密钥;自定义端点在常见路径上并排显示 API 密钥与接口 Together、Fireworks、OpenCode Go、Z.AI。 国内:DeepSeek、通义千问、月之暗面、智谱 / Coding Plan、硅基流动、火山方舟、 -MiniMax(`anthropic_messages`,`https://api.minimaxi.com/anthropic/v1`)、 +火山方舟 Coding Plan / Agent Plan(各有 `anthropic_messages` 变体,地址 +`https://ark.cn-beijing.volces.com/api/coding` 或 `/api/plan`,以及 OpenAI +`chat_completions` 变体 `/api/coding/v3` 或 `/api/plan/v3`;均沿用共享的 +`volcengine` vendor key)、MiniMax(`anthropic_messages`,`https://api.minimaxi.com/anthropic/v1`)、 MiniMax (OpenAI)(`chat_completions`,`https://api.minimaxi.com/v1`,别名 `minimax-openai` / `minimax-compatible`)、Kimi 编程。 diff --git a/packages/i18n/src/locales/de/index.ts b/packages/i18n/src/locales/de/index.ts index bf3c3c6684..4869490ee6 100644 --- a/packages/i18n/src/locales/de/index.ts +++ b/packages/i18n/src/locales/de/index.ts @@ -856,6 +856,10 @@ export const de = { "presetZaiCodingPlan": "Z.AI Codierungsplan", "presetSiliconflowCn": "SiliconFlow", "presetVolcengine": "Volcengine Ark", + "presetVolcengineCodingPlan": "Volcengine Ark Coding Plan", + "presetVolcengineCodingPlanOpenai": "Volcengine Ark Coding Plan (OpenAI)", + "presetVolcengineAgentPlan": "Volcengine Ark Agent Plan", + "presetVolcengineAgentPlanOpenai": "Volcengine Ark Agent Plan (OpenAI)", "presetMinimaxCn": "MiniMax", "presetMinimaxCnOpenai": "MiniMax (OpenAI)", "presetKimiCoding": "Kimi für Codierung", diff --git a/packages/i18n/src/locales/en/index.ts b/packages/i18n/src/locales/en/index.ts index 76c458c3e8..b2e1a5ed1e 100644 --- a/packages/i18n/src/locales/en/index.ts +++ b/packages/i18n/src/locales/en/index.ts @@ -869,6 +869,10 @@ export const en = { presetZaiCodingPlan: "Z.AI Coding Plan", presetSiliconflowCn: "SiliconFlow", presetVolcengine: "Volcengine Ark", + presetVolcengineCodingPlan: "Volcengine Ark Coding Plan", + presetVolcengineCodingPlanOpenai: "Volcengine Ark Coding Plan (OpenAI)", + presetVolcengineAgentPlan: "Volcengine Ark Agent Plan", + presetVolcengineAgentPlanOpenai: "Volcengine Ark Agent Plan (OpenAI)", presetMinimaxCn: "MiniMax", presetMinimaxCnOpenai: "MiniMax (OpenAI)", presetKimiCoding: "Kimi For Coding", diff --git a/packages/i18n/src/locales/es/index.ts b/packages/i18n/src/locales/es/index.ts index 9f10bad818..97beb56957 100644 --- a/packages/i18n/src/locales/es/index.ts +++ b/packages/i18n/src/locales/es/index.ts @@ -856,6 +856,10 @@ export const es = { "presetZaiCodingPlan": "Z.AI Plan de codificación", "presetSiliconflowCn": "SiliconFlow", "presetVolcengine": "Volcengine Ark", + "presetVolcengineCodingPlan": "Volcengine Ark Coding Plan", + "presetVolcengineCodingPlanOpenai": "Volcengine Ark Coding Plan (OpenAI)", + "presetVolcengineAgentPlan": "Volcengine Ark Agent Plan", + "presetVolcengineAgentPlanOpenai": "Volcengine Ark Agent Plan (OpenAI)", "presetMinimaxCn": "MiniMax", "presetMinimaxCnOpenai": "MiniMax (OpenAI)", "presetKimiCoding": "Kimi para codificación", diff --git a/packages/i18n/src/locales/fr/index.ts b/packages/i18n/src/locales/fr/index.ts index 46c65be96f..992652f4ff 100644 --- a/packages/i18n/src/locales/fr/index.ts +++ b/packages/i18n/src/locales/fr/index.ts @@ -856,6 +856,10 @@ export const fr = { "presetZaiCodingPlan": "Plan de codage Z.AI", "presetSiliconflowCn": "SiliconFlow", "presetVolcengine": "Volcengine Ark", + "presetVolcengineCodingPlan": "Volcengine Ark Coding Plan", + "presetVolcengineCodingPlanOpenai": "Volcengine Ark Coding Plan (OpenAI)", + "presetVolcengineAgentPlan": "Volcengine Ark Agent Plan", + "presetVolcengineAgentPlanOpenai": "Volcengine Ark Agent Plan (OpenAI)", "presetMinimaxCn": "MiniMax", "presetMinimaxCnOpenai": "MiniMax (OpenAI)", "presetKimiCoding": "Kimi pour le codage", diff --git a/packages/i18n/src/locales/ko/index.ts b/packages/i18n/src/locales/ko/index.ts index 485b8535a4..24d8cfaf0e 100644 --- a/packages/i18n/src/locales/ko/index.ts +++ b/packages/i18n/src/locales/ko/index.ts @@ -871,6 +871,10 @@ export const ko = { presetZaiCodingPlan: "Z.AI Coding Plan", presetSiliconflowCn: "SiliconFlow", presetVolcengine: "Volcengine Ark", + presetVolcengineCodingPlan: "Volcengine Ark Coding Plan", + presetVolcengineCodingPlanOpenai: "Volcengine Ark Coding Plan (OpenAI)", + presetVolcengineAgentPlan: "Volcengine Ark Agent Plan", + presetVolcengineAgentPlanOpenai: "Volcengine Ark Agent Plan (OpenAI)", presetMinimaxCn: "MiniMax", presetMinimaxCnOpenai: "MiniMax (OpenAI)", presetKimiCoding: "Kimi For Coding", diff --git a/packages/i18n/src/locales/tr/index.ts b/packages/i18n/src/locales/tr/index.ts index c5d324ca03..5f50a18a12 100644 --- a/packages/i18n/src/locales/tr/index.ts +++ b/packages/i18n/src/locales/tr/index.ts @@ -871,6 +871,10 @@ export const tr = { presetZaiCodingPlan: "Z.AI Coding Plan", presetSiliconflowCn: "SiliconFlow", presetVolcengine: "Volcengine Ark", + presetVolcengineCodingPlan: "Volcengine Ark Coding Plan", + presetVolcengineCodingPlanOpenai: "Volcengine Ark Coding Plan (OpenAI)", + presetVolcengineAgentPlan: "Volcengine Ark Agent Plan", + presetVolcengineAgentPlanOpenai: "Volcengine Ark Agent Plan (OpenAI)", presetMinimaxCn: "MiniMax", presetMinimaxCnOpenai: "MiniMax (OpenAI)", presetKimiCoding: "Kimi For Coding", diff --git a/packages/i18n/src/locales/zh-CN/index.ts b/packages/i18n/src/locales/zh-CN/index.ts index 670fc7ad89..9ec7618f8d 100644 --- a/packages/i18n/src/locales/zh-CN/index.ts +++ b/packages/i18n/src/locales/zh-CN/index.ts @@ -865,6 +865,10 @@ export const zhCN = { presetZaiCodingPlan: "Z.AI Coding Plan", presetSiliconflowCn: "硅基流动", presetVolcengine: "火山方舟", + presetVolcengineCodingPlan: "火山方舟 Coding Plan", + presetVolcengineCodingPlanOpenai: "火山方舟 Coding Plan(OpenAI)", + presetVolcengineAgentPlan: "火山方舟 Agent Plan", + presetVolcengineAgentPlanOpenai: "火山方舟 Agent Plan(OpenAI)", presetMinimaxCn: "MiniMax", presetMinimaxCnOpenai: "MiniMax(OpenAI)", presetKimiCoding: "Kimi 编程", diff --git a/packages/i18n/src/locales/zh-TW/index.ts b/packages/i18n/src/locales/zh-TW/index.ts index 5ffecd1450..8bfe49dd79 100644 --- a/packages/i18n/src/locales/zh-TW/index.ts +++ b/packages/i18n/src/locales/zh-TW/index.ts @@ -865,6 +865,10 @@ export const zhTW = { presetZaiCodingPlan: "Z.AI Coding Plan", presetSiliconflowCn: "矽基流動", presetVolcengine: "火山方舟", + presetVolcengineCodingPlan: "火山方舟 Coding Plan", + presetVolcengineCodingPlanOpenai: "火山方舟 Coding Plan(OpenAI)", + presetVolcengineAgentPlan: "火山方舟 Agent Plan", + presetVolcengineAgentPlanOpenai: "火山方舟 Agent Plan(OpenAI)", presetMinimaxCn: "MiniMax", presetMinimaxCnOpenai: "MiniMax(OpenAI)", presetKimiCoding: "Kimi 程式設計", diff --git a/packages/shared/src/provider-presets.test.ts b/packages/shared/src/provider-presets.test.ts index 736a4f8cb3..e4bbf2f37f 100644 --- a/packages/shared/src/provider-presets.test.ts +++ b/packages/shared/src/provider-presets.test.ts @@ -132,4 +132,38 @@ describe("named endpoint presets", () => { })?.id, ).toBe("opencode_go"); }); + + it("distinguishes Volcengine Ark plan endpoints from the standard API by path", () => { + expect( + matchNamedPreset({ baseUrl: "https://ark.cn-beijing.volces.com/api/v3/" })?.id, + ).toBe("volcengine"); + expect( + matchNamedPreset({ baseUrl: "https://ark.cn-beijing.volces.com/api/coding" }) + ?.apiStyle, + ).toBe("anthropic_messages"); + expect( + matchNamedPreset({ baseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3" }) + ?.id, + ).toBe("volcengine-coding-plan-openai"); + expect( + matchNamedPreset({ baseUrl: "https://ark.cn-beijing.volces.com/api/plan" })?.id, + ).toBe("volcengine-agent-plan"); + expect( + matchNamedPreset({ baseUrl: "https://ark.cn-beijing.volces.com/api/plan/v3" }) + ?.apiStyle, + ).toBe("chat_completions"); + }); + + it("keeps Ark plan rows on the shared volcengine vendor key for catalog metadata", () => { + for (const id of [ + "volcengine-coding-plan", + "volcengine-coding-plan-openai", + "volcengine-agent-plan", + "volcengine-agent-plan-openai", + ]) { + expect(NAMED_ENDPOINT_PRESETS.find((preset) => preset.id === id)?.vendorKey).toBe( + "volcengine", + ); + } + }); }); diff --git a/packages/shared/src/provider-presets.ts b/packages/shared/src/provider-presets.ts index 4f5aba2db0..96b996b68c 100644 --- a/packages/shared/src/provider-presets.ts +++ b/packages/shared/src/provider-presets.ts @@ -185,6 +185,47 @@ export const NAMED_ENDPOINT_PRESETS: readonly NamedEndpointPreset[] = [ labelKey: "settings.presetVolcengine", aliases: ["doubao", "ark"], }, + // Volcengine Ark subscription plans (Agent Plan / Coding Plan) are served + // from dedicated hosts with their own protocol variants; the standard + // `/api/v3` endpoint above does not cover them. All plan rows keep the + // `volcengine` vendor key so models.dev metadata still resolves; the + // stored base URL is what distinguishes the preset on a saved row. + { + id: "volcengine-coding-plan", + vendorKey: "volcengine", + name: "Volcengine Ark Coding Plan", + baseUrl: "https://ark.cn-beijing.volces.com/api/coding", + apiStyle: "anthropic_messages", + labelKey: "settings.presetVolcengineCodingPlan", + aliases: ["ark-coding-plan", "doubao-coding-plan"], + }, + { + id: "volcengine-coding-plan-openai", + vendorKey: "volcengine", + name: "Volcengine Ark Coding Plan (OpenAI)", + baseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3", + apiStyle: "chat_completions", + labelKey: "settings.presetVolcengineCodingPlanOpenai", + aliases: ["ark-coding-plan-openai"], + }, + { + id: "volcengine-agent-plan", + vendorKey: "volcengine", + name: "Volcengine Ark Agent Plan", + baseUrl: "https://ark.cn-beijing.volces.com/api/plan", + apiStyle: "anthropic_messages", + labelKey: "settings.presetVolcengineAgentPlan", + aliases: ["ark-agent-plan"], + }, + { + id: "volcengine-agent-plan-openai", + vendorKey: "volcengine", + name: "Volcengine Ark Agent Plan (OpenAI)", + baseUrl: "https://ark.cn-beijing.volces.com/api/plan/v3", + apiStyle: "chat_completions", + labelKey: "settings.presetVolcengineAgentPlanOpenai", + aliases: ["ark-agent-plan-openai"], + }, { id: "minimax-cn", vendorKey: "minimax-cn", @@ -255,6 +296,31 @@ function presetByUrl(url: string): NamedEndpointPreset | undefined { preset.id === (url.includes("/coding/") ? "zai-coding-plan" : "zai"), ); } + if (url.includes("volces.com")) { + // Plan endpoints win over the standard `/api/v3` host so a saved row is + // catalog-matched to the preset it was created from. + if (url.includes("/api/coding/v3")) { + return NAMED_ENDPOINT_PRESETS.find( + (preset) => preset.id === "volcengine-coding-plan-openai", + ); + } + if (url.includes("/api/coding")) { + return NAMED_ENDPOINT_PRESETS.find( + (preset) => preset.id === "volcengine-coding-plan", + ); + } + if (url.includes("/api/plan/v3")) { + return NAMED_ENDPOINT_PRESETS.find( + (preset) => preset.id === "volcengine-agent-plan-openai", + ); + } + if (url.includes("/api/plan")) { + return NAMED_ENDPOINT_PRESETS.find( + (preset) => preset.id === "volcengine-agent-plan", + ); + } + return NAMED_ENDPOINT_PRESETS.find((preset) => preset.id === "volcengine"); + } return undefined; }