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 @@
+
\ 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 ;
@@ -278,6 +287,8 @@ export function ProviderBrandMark({ type }: { type: ProviderType }): ReactElemen
case 'stepfun-ai':
case 'stepfun':
return ;
+ case 'volcengine-ark':
+ return ;
default:
return ;
}
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/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/__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',
diff --git a/packages/core/src/model-metadata.ts b/packages/core/src/model-metadata.ts
index 751cff3869..6b6080c0db 100644
--- a/packages/core/src/model-metadata.ts
+++ b/packages/core/src/model-metadata.ts
@@ -118,6 +118,19 @@ const STATIC_MODEL_METADATA: Partial {
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/runtime/src/__tests__/model-factory-thinking.test.ts b/packages/runtime/src/__tests__/model-factory-thinking.test.ts
index 22a5c174dc..617e72ec41 100644
--- a/packages/runtime/src/__tests__/model-factory-thinking.test.ts
+++ b/packages/runtime/src/__tests__/model-factory-thinking.test.ts
@@ -71,6 +71,20 @@ describe('buildProviderOptions: thinking level', () => {
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 075ab63112..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 }> = [];
@@ -1232,6 +1232,111 @@ describe('models.dev provider conformance', () => {
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.',
+ providerOptions: buildProviderOptions(connection, modelId),
+ 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.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(
+ (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(
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: {
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({