Feat/Add ByteDance Ark provider (#226)

This commit is contained in:
Lingxuan Zuo
2026-02-28 16:44:58 +08:00
committed by GitHub
Unverified
parent 1b45d891c3
commit 8cda9235b3
6 changed files with 62 additions and 9 deletions

View File

@@ -10,6 +10,7 @@ export const BUILTIN_PROVIDER_TYPES = [
'openai',
'google',
'openrouter',
'ark',
'moonshot',
'siliconflow',
'minimax-portal',
@@ -73,6 +74,14 @@ const REGISTRY: Record<string, ProviderBackendMeta> = {
},
},
},
ark: {
envVar: 'ARK_API_KEY',
providerConfig: {
baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
api: 'openai-completions',
apiKeyEnv: 'ARK_API_KEY',
},
},
moonshot: {
envVar: 'MOONSHOT_API_KEY',
defaultModel: 'moonshot/kimi-k2.5',

View File

@@ -4,7 +4,7 @@
* Keys are stored in plain text alongside provider configs in a single electron-store.
*/
import type { ProviderType } from './provider-registry';
import { BUILTIN_PROVIDER_TYPES, type ProviderType } from './provider-registry';
import { getActiveOpenClawProviders } from './openclaw-auth';
// Lazy-load electron-store (ESM module)
@@ -216,17 +216,11 @@ export async function getAllProvidersWithKeyInfo(): Promise<
const results: Array<ProviderConfig & { hasKey: boolean; keyMasked: string | null }> = [];
const activeOpenClawProviders = await getActiveOpenClawProviders();
// We need to avoid deleting native ones like 'anthropic' or 'google'
// that don't need to exist in openclaw.json models.providers
const OpenClawBuiltinList = [
'anthropic', 'openai', 'google', 'moonshot', 'siliconflow', 'ollama'
];
for (const provider of providers) {
// Sync check: If it's a custom/OAuth provider and it no longer exists in OpenClaw config
// (e.g. wiped by Gateway due to missing plugin, or manually deleted by user)
// we should remove it from ClawX UI to stay consistent.
const isBuiltin = OpenClawBuiltinList.includes(provider.type);
const isBuiltin = BUILTIN_PROVIDER_TYPES.includes(provider.type);
// For custom/ollama providers, the OpenClaw config key is derived as
// "<type>-<suffix>" where suffix = first 8 chars of providerId with hyphens stripped.
// e.g. provider.id "custom-a1b2c3d4-..." → strip hyphens → "customa1b2c3d4..." → slice(0,8) → "customa1"
@@ -261,4 +255,3 @@ export async function getAllProvidersWithKeyInfo(): Promise<
return results;
}

View File

@@ -0,0 +1,5 @@
<svg fill="#191919" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>ByteDance Ark</title>
<path d="M12 1.5 2.75 22.5h3.57l1.87-4.36h7.62l-1.45-3.25h-4.76L12 9.47l5.72 13.03h3.53Z"/>
<path d="M14.96 9.1h3.48L21.25 2.8h-3.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 265 B

View File

@@ -2,6 +2,7 @@ import anthropic from './anthropic.svg';
import openai from './openai.svg';
import google from './google.svg';
import openrouter from './openrouter.svg';
import ark from './ark.svg';
import moonshot from './moonshot.svg';
import siliconflow from './siliconflow.svg';
import minimaxPortal from './minimax.svg';
@@ -14,6 +15,7 @@ export const providerIcons: Record<string, string> = {
openai,
google,
openrouter,
ark,
moonshot,
siliconflow,
'minimax-portal': minimaxPortal,

View File

@@ -10,6 +10,7 @@ export const PROVIDER_TYPES = [
'openai',
'google',
'openrouter',
'ark',
'moonshot',
'siliconflow',
'minimax-portal',
@@ -68,6 +69,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [
{ id: 'openai', name: 'OpenAI', icon: '💚', placeholder: 'sk-proj-...', model: 'GPT', requiresApiKey: true },
{ id: 'google', name: 'Google', icon: '🔷', placeholder: 'AIza...', model: 'Gemini', requiresApiKey: true },
{ id: 'openrouter', name: 'OpenRouter', icon: '🌐', placeholder: 'sk-or-v1-...', model: 'Multi-Model', requiresApiKey: true },
{ id: 'ark', name: 'ByteDance Ark', icon: 'A', placeholder: 'your-ark-api-key', model: 'Doubao', requiresApiKey: true, defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'ep-20260228000000-xxxxx' },
{ id: 'moonshot', name: 'Moonshot (CN)', icon: '🌙', placeholder: 'sk-...', model: 'Kimi', requiresApiKey: true, defaultBaseUrl: 'https://api.moonshot.cn/v1', defaultModelId: 'kimi-k2.5' },
{ id: 'siliconflow', name: 'SiliconFlow (CN)', icon: '🌊', placeholder: 'sk-...', model: 'Multi-Model', requiresApiKey: true, defaultBaseUrl: 'https://api.siliconflow.cn/v1', defaultModelId: 'Pro/moonshotai/Kimi-K2.5' },
{ id: 'minimax-portal', name: 'MiniMax (Global)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultModelId: 'MiniMax-M2.5' },

View File

@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest';
import { PROVIDER_TYPES, PROVIDER_TYPE_INFO } from '@/lib/providers';
import {
BUILTIN_PROVIDER_TYPES,
getProviderConfig,
getProviderEnvVar,
} from '@electron/utils/provider-registry';
describe('provider metadata', () => {
it('includes ark in the frontend provider registry', () => {
expect(PROVIDER_TYPES).toContain('ark');
expect(PROVIDER_TYPE_INFO).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'ark',
name: 'ByteDance Ark',
requiresApiKey: true,
defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
showBaseUrl: true,
showModelId: true,
}),
])
);
});
it('includes ark in the backend provider registry', () => {
expect(BUILTIN_PROVIDER_TYPES).toContain('ark');
expect(getProviderEnvVar('ark')).toBe('ARK_API_KEY');
expect(getProviderConfig('ark')).toEqual({
baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
api: 'openai-completions',
apiKeyEnv: 'ARK_API_KEY',
});
});
it('keeps builtin provider sources in sync', () => {
expect(BUILTIN_PROVIDER_TYPES).toEqual(
expect.arrayContaining(['anthropic', 'openai', 'google', 'openrouter', 'ark', 'moonshot', 'siliconflow', 'minimax-portal', 'minimax-portal-cn', 'qwen-portal', 'ollama'])
);
});
});