From 8c841e5b29219af3de5e65755925c0240a60b325 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Tue, 14 Jul 2026 02:14:00 +0800 Subject: [PATCH 1/5] feat(providers): add Volcengine Ark runtime contract --- .../src/__tests__/llm-connections.test.ts | 20 ++++ .../core/src/__tests__/model-catalog.test.ts | 19 ++++ packages/core/src/model-metadata.ts | 8 ++ packages/core/src/provider-registry.ts | 18 +++ .../__tests__/provider-conformance.test.ts | 103 ++++++++++++++++++ 5 files changed, 168 insertions(+) diff --git a/packages/core/src/__tests__/llm-connections.test.ts b/packages/core/src/__tests__/llm-connections.test.ts index 751cc6dd41..d510ab8ad6 100644 --- a/packages/core/src/__tests__/llm-connections.test.ts +++ b/packages/core/src/__tests__/llm-connections.test.ts @@ -48,6 +48,7 @@ describe('provider compatibility contract', () => { 'tencent-tokenhub', 'stepfun', 'stepfun-ai', + 'volcengine-ark', 'ollama', 'lm-studio', 'openai-compatible', @@ -80,6 +81,7 @@ describe('provider compatibility contract', () => { 'stepfun', 'tencent-coding-plan', 'stepfun-ai', + 'volcengine-ark', ]); assert.deepEqual(CATALOG_PROVIDER_TYPES, [ 'kimi-coding-plan', @@ -106,6 +108,7 @@ describe('provider compatibility contract', () => { 'stepfun', 'tencent-coding-plan', 'stepfun-ai', + 'volcengine-ark', ]); for (const orderField of ['readyOrder', 'catalogOrder', 'recommendedOrder'] as const) { @@ -371,6 +374,23 @@ describe('provider compatibility contract', () => { 'step-3.5-flash', ]); }); + + it('owns Volcengine Ark China direct API behavior under the stable volcengine-ark id', () => { + const ark = (PROVIDER_REGISTRY as Partial>)['volcengine-ark']; + + assert.ok(ark, 'Volcengine Ark China direct API must have its own persisted provider id'); + assert.equal(ark.label, 'Volcengine Ark (China)'); + assert.equal(ark.baseUrl, 'https://ark.cn-beijing.volces.com/api/v3'); + assert.equal(ark.authKind, 'api_key'); + assert.equal(ark.protocol, 'openai'); + assert.deepEqual(ark.runtimeAdapter, { kind: 'openai-compatible', name: 'provider' }); + assert.deepEqual(ark.modelDiscovery, { kind: 'fallback' }); + assert.equal(ark.category, 'domestic'); + assert.equal(ark.catalogGroup, 'api'); + assert.equal(ark.signupUrl, 'https://console.volcengine.com/ark/region:ark+cn-beijing/model'); + assert.equal(ark.modelsDevId, undefined); + assert.deepEqual(ark.fallbackModels, ['doubao-seed-2-0-pro-260215']); + }); }); describe('validateConnectionBaseUrl (PR-UI-IPC-1, @kenji msg 35260e29)', () => { diff --git a/packages/core/src/__tests__/model-catalog.test.ts b/packages/core/src/__tests__/model-catalog.test.ts index 0635776657..eb0995f080 100644 --- a/packages/core/src/__tests__/model-catalog.test.ts +++ b/packages/core/src/__tests__/model-catalog.test.ts @@ -34,6 +34,25 @@ describe('ModelCatalogEntry', () => { }); }); + it('uses the official Volcengine Ark snapshot without inventing live model discovery', () => { + const entries = buildConnectionModelCatalogEntries({ + connection: { + slug: 'volcengine-ark', + providerType: 'volcengine-ark', + defaultModel: 'doubao-seed-2-0-pro-260215', + }, + }); + + assert.deepEqual(entries.map((entry) => entry.id), ['doubao-seed-2-0-pro-260215']); + assert.equal(entries[0]?.displayName, 'Doubao Seed 2.0 Pro'); + assert.equal(entries[0]?.source, 'static_catalog'); + assert.equal(entries[0]?.provenance.modelSource, 'fallback'); + assert.deepEqual(entries[0]?.capabilities, { + reasoning: true, + functionCalling: true, + }); + }); + it('uses the checked-in StepFun China snapshot until account discovery succeeds', () => { const entries = buildConnectionModelCatalogEntries({ connection: { diff --git a/packages/core/src/model-metadata.ts b/packages/core/src/model-metadata.ts index 751cff3869..018a28f093 100644 --- a/packages/core/src/model-metadata.ts +++ b/packages/core/src/model-metadata.ts @@ -118,6 +118,14 @@ const STATIC_MODEL_METADATA: Partial { assert.deepEqual(result.steps[0]?.toolResults[0]?.output, { echoed: 'hello' }); assert.equal(result.text, 'Echoed hello.'); }); + + test('Volcengine Ark preserves its snapshot model id through the documented two-stage tool-call loop', async () => { + const modelId = 'doubao-seed-2-0-pro-260215'; + const requestBodies: Array> = []; + const server = await startJsonServer(async (request, response) => { + assert.equal(request.headers.authorization, 'Bearer ark-test-key'); + assert.equal(request.method, 'POST'); + assert.equal(request.url, '/api/v3/chat/completions'); + const body = JSON.parse(await readBody(request)) as Record; + requestBodies.push(body); + if (requestBodies.length === 1) { + respondJson(response, 200, { + id: 'chatcmpl-ark-tool', + object: 'chat.completion', + created: 1, + model: modelId, + choices: [{ + index: 0, + message: { + role: 'assistant', + content: null, + reasoning_content: 'I should call echo with the requested text.', + tool_calls: [{ + id: 'call_ark_echo', + type: 'function', + function: { name: 'echo', arguments: '{"text":"hello"}' }, + }], + }, + finish_reason: 'tool_calls', + }], + usage: { prompt_tokens: 8, completion_tokens: 4, total_tokens: 12 }, + }); + return; + } + + respondJson(response, 200, { + id: 'chatcmpl-ark-final', + object: 'chat.completion', + created: 2, + model: modelId, + choices: [{ + index: 0, + message: { role: 'assistant', content: 'Echoed hello.' }, + finish_reason: 'stop', + }], + usage: { prompt_tokens: 14, completion_tokens: 3, total_tokens: 17 }, + }); + }); + const connection: LlmConnection = { + slug: 'volcengine-ark', + name: 'Volcengine Ark (China)', + providerType: 'volcengine-ark', + baseUrl: `${server.url}/api/v3`, + defaultModel: modelId, + enabled: true, + createdAt: 1, + updatedAt: 1, + }; + + const models = await fetchProviderModels(connection, 'ark-test-key'); + assert.deepEqual(models, [{ id: modelId }]); + + const result = await generateText({ + model: getAIModel({ connection, apiKey: 'ark-test-key', modelId: models[0]!.id }), + prompt: 'Call echo with hello.', + stopWhen: stepCountIs(2), + tools: { + echo: tool({ + description: 'Echo text', + inputSchema: z.object({ text: z.string() }), + execute: async ({ text }) => ({ echoed: text }), + }), + }, + }); + + assert.equal(requestBodies.length, 2); + assert.equal(result.steps[0]?.reasoningText, 'I should call echo with the requested text.'); + assert.deepEqual(requestBodies.map((body) => body.model), [modelId, modelId]); + assert.deepEqual( + (requestBodies[0]?.tools as Array<{ function: { name: string } }>).map((entry) => entry.function.name), + ['echo'], + ); + assert.deepEqual( + (requestBodies[1]?.messages as Array<{ role: string; content: string }>).find(({ role }) => role === 'tool'), + { role: 'tool', content: '{"echoed":"hello"}', tool_call_id: 'call_ark_echo' }, + ); + assert.deepEqual( + (requestBodies[1]?.messages as Array>).find(({ role }) => role === 'assistant'), + { + role: 'assistant', + content: null, + reasoning_content: 'I should call echo with the requested text.', + tool_calls: [{ + id: 'call_ark_echo', + type: 'function', + function: { name: 'echo', arguments: '{"text":"hello"}' }, + }], + }, + ); + assert.equal(result.steps[0]?.toolCalls[0]?.toolName, 'echo'); + assert.deepEqual(result.steps[0]?.toolResults[0]?.output, { echoed: 'hello' }); + assert.equal(result.text, 'Echoed hello.'); + }); }); async function assertOllamaModelContract( From cb68ce41465e73d78415fd3279b9ced96f6a6e2a Mon Sep 17 00:00:00 2001 From: AstroHan Date: Tue, 14 Jul 2026 02:20:02 +0800 Subject: [PATCH 2/5] feat(runtime): wire Volcengine Ark thinking controls --- packages/core/src/model-metadata.ts | 5 +++++ packages/core/src/model-thinking.ts | 3 ++- .../src/__tests__/model-factory-thinking.test.ts | 15 +++++++++++++++ .../src/__tests__/provider-conformance.test.ts | 4 +++- packages/runtime/src/model-factory.ts | 11 +++++++++-- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/core/src/model-metadata.ts b/packages/core/src/model-metadata.ts index 018a28f093..6b6080c0db 100644 --- a/packages/core/src/model-metadata.ts +++ b/packages/core/src/model-metadata.ts @@ -124,6 +124,11 @@ const STATIC_MODEL_METADATA: Partial { assert.deepEqual(buildProviderOptions(conn('deepseek'), 'deepseek-chat', 'high'), {}); }); + test('Volcengine Ark sends its official thinking object and optional reasoning effort', () => { + const modelId = 'doubao-seed-2-0-pro-260215'; + assert.deepEqual([...thinkingVariantsForModel('volcengine-ark', modelId)], ['off', 'minimal', 'low', 'medium', 'high']); + assert.deepEqual(buildProviderOptions(conn('volcengine-ark'), modelId), { + 'volcengine-ark': { thinking: { type: 'enabled' } }, + }); + assert.deepEqual(buildProviderOptions(conn('volcengine-ark'), modelId, 'high'), { + 'volcengine-ark': { thinking: { type: 'enabled' }, reasoningEffort: 'high' }, + }); + assert.deepEqual(buildProviderOptions(conn('volcengine-ark'), modelId, 'off'), { + 'volcengine-ark': { thinking: { type: 'disabled' } }, + }); + }); + test('a level the model does not support is dropped (defensive)', () => { assert.deepEqual(buildProviderOptions(conn('openai'), 'gpt-4o', 'high'), { openai: {} }); assert.deepEqual(buildProviderOptions(conn('anthropic'), 'claude-haiku-4-5', 'max'), { anthropic: {} }); @@ -115,6 +129,7 @@ describe('buildProviderOptions: resolver/options drift guard', () => { { providerType: 'google', model: 'gemini-3.5-flash' }, { providerType: 'deepseek', model: 'deepseek-v4-flash' }, { providerType: 'zai-coding-plan', model: 'glm-5.2', slug: 'zai-coding-plan' }, + { providerType: 'volcengine-ark', model: 'doubao-seed-2-0-pro-260215' }, ]; for (const { providerType, model, slug } of cases) { test(`every effort level for ${providerType}/${model} maps to a non-empty fragment`, () => { diff --git a/packages/runtime/src/__tests__/provider-conformance.test.ts b/packages/runtime/src/__tests__/provider-conformance.test.ts index 8d7becde93..5bfc81c3e5 100644 --- a/packages/runtime/src/__tests__/provider-conformance.test.ts +++ b/packages/runtime/src/__tests__/provider-conformance.test.ts @@ -5,7 +5,7 @@ import type { LlmConnection } from '@maka/core'; import { generateText, stepCountIs, tool } from 'ai'; import { z } from 'zod'; import { fetchProviderModels } from '../model-fetcher.js'; -import { getAIModel } from '../model-factory.js'; +import { buildProviderOptions, getAIModel } from '../model-factory.js'; const servers: Array<{ close(): Promise }> = []; @@ -1297,6 +1297,7 @@ describe('models.dev provider conformance', () => { const result = await generateText({ model: getAIModel({ connection, apiKey: 'ark-test-key', modelId: models[0]!.id }), prompt: 'Call echo with hello.', + providerOptions: buildProviderOptions(connection, modelId), stopWhen: stepCountIs(2), tools: { echo: tool({ @@ -1308,6 +1309,7 @@ describe('models.dev provider conformance', () => { }); assert.equal(requestBodies.length, 2); + assert.deepEqual(requestBodies[0]?.thinking, { type: 'enabled' }); assert.equal(result.steps[0]?.reasoningText, 'I should call echo with the requested text.'); assert.deepEqual(requestBodies.map((body) => body.model), [modelId, modelId]); assert.deepEqual( diff --git a/packages/runtime/src/model-factory.ts b/packages/runtime/src/model-factory.ts index 485258d0f0..2a92ce4f3d 100644 --- a/packages/runtime/src/model-factory.ts +++ b/packages/runtime/src/model-factory.ts @@ -2,7 +2,7 @@ import { createAnthropic } from '@ai-sdk/anthropic'; import { createGoogleGenerativeAI } from '@ai-sdk/google'; import { createOpenAI } from '@ai-sdk/openai'; import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; -import type { LanguageModelV3 } from '@ai-sdk/provider'; +import type { LanguageModelV3, SharedV3ProviderOptions } from '@ai-sdk/provider'; import { PROVIDER_DEFAULTS, effectiveBaseUrl, type LlmConnection, type ProviderType } from '@maka/core/llm-connections'; import type { ThinkingLevel } from '@maka/core/model-thinking'; import { thinkingOptionsForModel, thinkingVariantsForModel } from '@maka/core/model-thinking'; @@ -85,7 +85,7 @@ export function buildProviderOptions( connection: LlmConnection, modelId: string, thinkingLevel?: ThinkingLevel, -): Record { +): SharedV3ProviderOptions { const thinkingOptions = thinkingOptionsForModel(connection.providerType, modelId); const variants = thinkingVariantsForModel(connection.providerType, modelId); const level = thinkingLevel && variants.includes(thinkingLevel) ? thinkingLevel : undefined; @@ -117,6 +117,13 @@ export function buildProviderOptions( }; case 'openai': return { openai: level ? { reasoningEffort: level === 'off' ? 'none' : level } : {} }; + case 'volcengine-ark': + return { + [openaiCompatibleNamespace(connection.providerType)]: { + thinking: { type: level === 'off' ? 'disabled' : 'enabled' }, + ...(level && level !== 'off' ? { reasoningEffort: level } : {}), + }, + }; case 'google': return { google: { From 8c9fd24d8d19cbf156f3d19116c817faab5cd82f Mon Sep 17 00:00:00 2001 From: AstroHan Date: Tue, 14 Jul 2026 02:20:22 +0800 Subject: [PATCH 3/5] feat(headless): add Volcengine Ark credentials --- .../src/__tests__/connection-target.test.ts | 24 +++++++++++++++++ .../src/__tests__/harbor-cell.test.ts | 27 +++++++++++++++++++ .../src/__tests__/provider-env.test.ts | 8 ++++++ packages/headless/src/provider-env.ts | 1 + .../src/__tests__/connection-store.test.ts | 18 +++++++++++++ 5 files changed, 78 insertions(+) diff --git a/packages/cli/src/__tests__/connection-target.test.ts b/packages/cli/src/__tests__/connection-target.test.ts index 9ceda5c0e9..e07d42063c 100644 --- a/packages/cli/src/__tests__/connection-target.test.ts +++ b/packages/cli/src/__tests__/connection-target.test.ts @@ -4,6 +4,30 @@ import type { LlmConnection } from '@maka/core/llm-connections'; import { listReadyModelChoices, resolveDefaultSessionTarget } from '../connection-target.js'; describe('default session target resolver', () => { + test('resolves Volcengine Ark credentials without rewriting the snapshot model id', async () => { + const modelId = 'doubao-seed-2-0-pro-260215'; + const connection = makeConnection({ + slug: 'volcengine-ark', + name: 'Volcengine Ark (China)', + providerType: 'volcengine-ark', + defaultModel: modelId, + }); + + const target = await resolveDefaultSessionTarget({ + connectionStore: { + getDefault: async () => 'volcengine-ark', + get: async (slug) => slug === 'volcengine-ark' ? connection : null, + }, + credentialStore: { + getSecret: async (_slug, kind) => kind === 'api_key' ? 'ark-test-key' : null, + }, + }); + + assert.equal(target.connection.providerType, 'volcengine-ark'); + assert.equal(target.apiKey, 'ark-test-key'); + assert.equal(target.model, modelId); + }); + test('resolves Fireworks credentials without rewriting the exact model path', async () => { const connection = makeConnection({ slug: 'fireworks-ai', diff --git a/packages/headless/src/__tests__/harbor-cell.test.ts b/packages/headless/src/__tests__/harbor-cell.test.ts index e1db6b60d6..9f115d306d 100644 --- a/packages/headless/src/__tests__/harbor-cell.test.ts +++ b/packages/headless/src/__tests__/harbor-cell.test.ts @@ -2362,6 +2362,33 @@ setTimeout(() => { assert.equal(missing.apiKey, ''); }); + test('resolves Volcengine Ark only from its official direct API credential env', () => { + const modelId = 'doubao-seed-2-0-pro-260215'; + const resolved = resolveHarborCellAiSdkEnv({ + provider: 'volcengine-ark', + model: modelId, + env: { + ARK_API_KEY: 'ark-key', + ARK_BASE_URL: 'https://ark.cn-shanghai.volces.com/api/v3', + OPENAI_API_KEY: 'openai-key', + }, + ts: 1, + }); + + assert.equal(resolved.apiKey, 'ark-key'); + assert.equal(resolved.connection.providerType, 'volcengine-ark'); + assert.equal(resolved.connection.defaultModel, modelId); + assert.equal(resolved.connection.baseUrl, 'https://ark.cn-shanghai.volces.com/api/v3'); + + const missing = resolveHarborCellAiSdkEnv({ + provider: 'volcengine-ark', + model: modelId, + env: { OPENAI_API_KEY: 'must-not-cross-provider-boundary' }, + ts: 1, + }); + assert.equal(missing.apiKey, ''); + }); + test('resolves Cerebras only from Cerebras credential env without rewriting the model id', () => { const resolved = resolveHarborCellAiSdkEnv({ provider: 'cerebras', diff --git a/packages/headless/src/__tests__/provider-env.test.ts b/packages/headless/src/__tests__/provider-env.test.ts index 14c25ea9cb..2d4730db2c 100644 --- a/packages/headless/src/__tests__/provider-env.test.ts +++ b/packages/headless/src/__tests__/provider-env.test.ts @@ -94,3 +94,11 @@ test('StepFun Global keeps direct API credentials separate from China and plan i baseUrls: ['STEPFUN_AI_BASE_URL'], }); }); + +test('Volcengine Ark direct API uses its official credential namespace', () => { + assert.deepEqual(providerCredentialEnv('volcengine-ark'), { + apiKeys: ['ARK_API_KEY'], + apiKeyFile: 'ARK_API_KEY_FILE', + baseUrls: ['ARK_BASE_URL'], + }); +}); diff --git a/packages/headless/src/provider-env.ts b/packages/headless/src/provider-env.ts index a3f9b95475..6e767b2ca6 100644 --- a/packages/headless/src/provider-env.ts +++ b/packages/headless/src/provider-env.ts @@ -27,6 +27,7 @@ const PROVIDER_CREDENTIAL_ENV = { 'tencent-tokenhub': env('TENCENT_TOKENHUB', ['TENCENT_TOKENHUB_BASE_URL']), stepfun: env('STEPFUN', ['STEPFUN_BASE_URL']), 'stepfun-ai': env('STEPFUN_AI', ['STEPFUN_AI_BASE_URL']), + 'volcengine-ark': env('ARK', ['ARK_BASE_URL']), 'openai-compatible': env('OPENAI', ['OPENAI_BASE_URL']), 'claude-subscription': env('ANTHROPIC'), } satisfies Partial>; diff --git a/packages/storage/src/__tests__/connection-store.test.ts b/packages/storage/src/__tests__/connection-store.test.ts index 7bffb7dd6d..09ea051105 100644 --- a/packages/storage/src/__tests__/connection-store.test.ts +++ b/packages/storage/src/__tests__/connection-store.test.ts @@ -42,6 +42,24 @@ describe('FileConnectionStore', () => { }); }); + test('persists the Volcengine Ark provider id and exact snapshot model', async () => { + await withConnectionStore(async (store, dir) => { + const modelId = 'doubao-seed-2-0-pro-260215'; + await store.create({ + slug: 'volcengine-ark', + name: 'Volcengine Ark (China)', + providerType: 'volcengine-ark', + defaultModel: modelId, + }); + + const persisted = JSON.parse(await readFile(join(dir, 'llm-connections.json'), 'utf8')) as { + connections: Array<{ providerType: string; defaultModel: string }>; + }; + assert.equal(persisted.connections[0]?.providerType, 'volcengine-ark'); + assert.equal(persisted.connections[0]?.defaultModel, modelId); + }); + }); + test('persists the Fireworks provider id and exact model path', async () => { await withConnectionStore(async (store, dir) => { await store.create({ From 037dc20da10365620320491b137923cf39cae787 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Tue, 14 Jul 2026 02:24:28 +0800 Subject: [PATCH 4/5] test(core): lock Volcengine Ark auth fallback --- packages/core/src/__tests__/provider-auth.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/core/src/__tests__/provider-auth.test.ts b/packages/core/src/__tests__/provider-auth.test.ts index 3560fae172..889df0e6a7 100644 --- a/packages/core/src/__tests__/provider-auth.test.ts +++ b/packages/core/src/__tests__/provider-auth.test.ts @@ -48,6 +48,19 @@ describe('ProviderAuth contract', () => { expect(contract.actionAvailability.fetch_models).toBe('available'); }); + test('Volcengine Ark China uses the shared API-key credential and snapshot-model flow', () => { + const contract = deriveProviderAuthContract({ + providerType: 'volcengine-ark', + hasSecret: true, + }); + + expect(contract.providerType).toBe('volcengine-ark'); + expect(contract.setupMode).toBe('api_key'); + expect(contract.requiresSecret).toBe(true); + expect(contract.actionAvailability.test_credentials).toBe('available'); + expect(contract.actionAvailability.fetch_models).toBe('hidden'); + }); + test('Together AI uses the shared API-key credential flow under its stable provider id', () => { const contract = deriveProviderAuthContract({ providerType: 'togetherai', From 0eb4e8129a7ea824d3a6ff3125fa83de87ee4765 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Tue, 14 Jul 2026 02:24:37 +0800 Subject: [PATCH 5/5] feat(desktop): add Volcengine Ark branding --- apps/desktop/e2e/providers.spec.ts | 30 +++++++++++++++++++ .../icon-governance-contract.test.ts | 26 ++++++++++++++++ .../assets/provider-brands/volcengine.svg | 1 + .../renderer/public/THIRD_PARTY_LICENSES.txt | 4 +++ .../settings/provider-brand-marks.tsx | 11 +++++++ 5 files changed, 72 insertions(+) create mode 100644 apps/desktop/src/renderer/assets/provider-brands/volcengine.svg diff --git a/apps/desktop/e2e/providers.spec.ts b/apps/desktop/e2e/providers.spec.ts index 1cb1e0ccd3..a4820b5282 100644 --- a/apps/desktop/e2e/providers.spec.ts +++ b/apps/desktop/e2e/providers.spec.ts @@ -330,6 +330,36 @@ for (const stepfun of [ await expect(page.getByRole('textbox', { name: '模型密钥' })).toBeVisible(); }); +test('adds Volcengine Ark China with its exact snapshot model, API-key field, and shared official mark', async ({ window: page }) => { + await page.getByRole('button', { name: '展开侧边栏' }).click(); + await page.getByRole('button', { name: '设置' }).click(); + await page.locator('[aria-label="设置分组"]').getByText('模型', { exact: true }).click(); + await page.getByRole('button', { name: '添加服务商' }).click(); + + await page.getByRole('tab', { name: 'API', exact: true }).click(); + await page.getByPlaceholder('搜索服务商').fill('Volcengine'); + const catalogMark = page.locator( + '.providerCatalogRow[data-provider="volcengine-ark"] .providerLogo .providerAssetMask', + ); + await expect(catalogMark).toBeVisible(); + expect(await catalogMark.evaluate(maskRenderContract)).toEqual({ usesAssetMask: true, followsForeground: true }); + await page.getByRole('button', { name: /添加模型供应商:Volcengine Ark \(China\)/ }).click(); + + await expect(page.getByLabel('模型供应商连接标识')).toHaveValue('volcengine-ark'); + await expect(page.getByLabel('模型供应商服务地址')).toHaveValue('https://ark.cn-beijing.volces.com/api/v3'); + await expect(page.getByLabel('模型供应商默认模型')).toHaveValue('doubao-seed-2-0-pro-260215'); + await page.getByRole('button', { name: '保存供应商' }).click(); + + await expect(page.getByRole('heading', { name: 'Volcengine Ark (China)', exact: true }).first()).toBeVisible(); + const detailMark = page.locator( + '.providerSubpageHeader .providerLogo[data-provider="volcengine-ark"] .providerAssetMask', + ); + await expect(detailMark).toBeVisible(); + expect(await detailMark.evaluate(maskRenderContract)).toEqual({ usesAssetMask: true, followsForeground: true }); + await expect(page.getByText('doubao-seed-2-0-pro-260215', { exact: true }).first()).toBeVisible(); + await expect(page.getByRole('textbox', { name: '模型密钥' })).toBeVisible(); +}); + test('restores keyboard focus across provider child pages', async ({ window: page }) => { await page.getByRole('button', { name: '展开侧边栏' }).click(); await page.getByRole('button', { name: '设置' }).click(); diff --git a/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts b/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts index 74dab236f8..5b7746859c 100644 --- a/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts +++ b/apps/desktop/src/main/__tests__/icon-governance-contract.test.ts @@ -46,6 +46,7 @@ const NVIDIA_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/ass const TENCENT_HUNYUAN_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/hunyuan.svg'); const TENCENT_CLOUD_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/tencentcloud.svg'); const STEPFUN_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/stepfun.svg'); +const VOLCENGINE_BRAND_MARK_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/assets/provider-brands/volcengine.svg'); const DESKTOP_PACKAGE_FILE = resolve(REPO_ROOT, 'apps/desktop/package.json'); const THIRD_PARTY_NOTICES_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt'); const ONBOARDING_HERO_FILE = resolve(REPO_ROOT, 'apps/desktop/src/renderer/OnboardingHero.tsx'); @@ -430,6 +431,31 @@ describe('icon + typography governance contract', () => { assert.match(componentSrc, /case 'stepfun':\s*return /); }); + it('vendors and routes the byte-exact upstream Volcengine mark through the shared asset-mask seam', async () => { + const [volcengineMark, componentSrc, notices] = await Promise.all([ + readFile(VOLCENGINE_BRAND_MARK_FILE), + readFile(PROVIDER_BRAND_MARKS_FILE, 'utf8'), + readFile(THIRD_PARTY_NOTICES_FILE, 'utf8'), + ]); + + assert.equal( + createHash('sha256').update(volcengineMark).digest('hex'), + 'f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33', + 'the vendored Volcengine mark must remain byte-identical to @lobehub/icons-static-svg@1.91.0 volcengine.svg', + ); + assert.match( + notices, + /apps\/desktop\/src\/renderer\/assets\/provider-brands\/volcengine\.svg[\s\S]*e4302041fbb3039608d25f9f618bd462783b875e[\s\S]*packages\/static-svg\/icons\/volcengine\.svg[\s\S]*f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33/, + 'Volcengine must append byte-exact provenance to the existing Lobe Icons notice entry', + ); + assert.match(componentSrc, /import volcengineBrandMark from '\.\.\/assets\/provider-brands\/volcengine\.svg';/); + assert.match( + componentSrc, + /Volcengine mark vendored byte-for-byte from Lobe Icons:[\s\S]*@lobehub\/icons-static-svg@1\.91\.0[\s\S]*e4302041fbb3039608d25f9f618bd462783b875e[\s\S]*packages\/static-svg\/icons\/volcengine\.svg[\s\S]*MIT[\s\S]*f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33/, + ); + assert.match(componentSrc, /case 'volcengine-ark':\s*return /); + }); + it('vendors and routes the byte-exact Tencent Cloud mark for Tencent Coding Plan', async () => { const [tencentCloudMark, componentSrc, notices] = await Promise.all([ readFile(TENCENT_CLOUD_BRAND_MARK_FILE), diff --git a/apps/desktop/src/renderer/assets/provider-brands/volcengine.svg b/apps/desktop/src/renderer/assets/provider-brands/volcengine.svg new file mode 100644 index 0000000000..28556c3b79 --- /dev/null +++ b/apps/desktop/src/renderer/assets/provider-brands/volcengine.svg @@ -0,0 +1 @@ +Volcengine \ No newline at end of file diff --git a/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt b/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt index bfd2ca8935..5500ade055 100644 --- a/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt +++ b/apps/desktop/src/renderer/public/THIRD_PARTY_LICENSES.txt @@ -53,6 +53,10 @@ This file records notices for third-party source assets vendored into the Maka d - Upstream commit: `e4302041fbb3039608d25f9f618bd462783b875e` - Upstream path: `packages/static-svg/icons/stepfun.svg` - SHA-256: `f46fbd1eee00a3dc7874395484bcc3e25a803e9eb4b79f07b7eec377a1e2f25c` + - `apps/desktop/src/renderer/assets/provider-brands/volcengine.svg` + - Upstream commit: `e4302041fbb3039608d25f9f618bd462783b875e` + - Upstream path: `packages/static-svg/icons/volcengine.svg` + - SHA-256: `f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33` MIT License diff --git a/apps/desktop/src/renderer/settings/provider-brand-marks.tsx b/apps/desktop/src/renderer/settings/provider-brand-marks.tsx index 56f29d07b6..4a463f3ef6 100644 --- a/apps/desktop/src/renderer/settings/provider-brand-marks.tsx +++ b/apps/desktop/src/renderer/settings/provider-brand-marks.tsx @@ -25,6 +25,7 @@ import mistralBrandMark from '../assets/provider-brands/mistral.svg'; import togetherBrandMark from '../assets/provider-brands/together.svg'; import nvidiaMarkUrl from '../assets/provider-brands/nvidia.svg'; import stepfunBrandMark from '../assets/provider-brands/stepfun.svg'; +import volcengineBrandMark from '../assets/provider-brands/volcengine.svg'; import xaiMarkUrl from '../assets/provider-brands/xai.svg'; // Real xAI/Grok mark vendored byte-for-byte from Lobe Icons: @@ -92,6 +93,14 @@ function XAI(): ReactElement { // - license: MIT (repository LICENSE) // - SHA-256: f46fbd1eee00a3dc7874395484bcc3e25a803e9eb4b79f07b7eec377a1e2f25c +// Volcengine mark vendored byte-for-byte from Lobe Icons: +// - repository: https://github.com/lobehub/lobe-icons +// - package/version: @lobehub/icons-static-svg@1.91.0 +// - commit: e4302041fbb3039608d25f9f618bd462783b875e +// - path: packages/static-svg/icons/volcengine.svg +// - license: MIT (repository LICENSE) +// - SHA-256: f29d0bdc284b33d8664ef221add7fbf06a5b370ef92767fa33f6020c914d3d33 + function ProviderAssetMask({ src }: { src: string }): ReactElement { const mask = `url("${src}")`; return