diff --git a/electron/utils/provider-registry.ts b/electron/utils/provider-registry.ts index d908b7a4d..1d4904d28 100644 --- a/electron/utils/provider-registry.ts +++ b/electron/utils/provider-registry.ts @@ -10,6 +10,7 @@ export const BUILTIN_PROVIDER_TYPES = [ 'openai', 'google', 'openrouter', + 'ark', 'moonshot', 'siliconflow', 'minimax-portal', @@ -73,6 +74,14 @@ const REGISTRY: Record = { }, }, }, + 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', diff --git a/electron/utils/secure-storage.ts b/electron/utils/secure-storage.ts index 9f717ddab..495f1df8a 100644 --- a/electron/utils/secure-storage.ts +++ b/electron/utils/secure-storage.ts @@ -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 = []; 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 // "-" 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; } - diff --git a/src/assets/providers/ark.svg b/src/assets/providers/ark.svg new file mode 100644 index 000000000..f835a4922 --- /dev/null +++ b/src/assets/providers/ark.svg @@ -0,0 +1,5 @@ + + ByteDance Ark + + + diff --git a/src/assets/providers/index.ts b/src/assets/providers/index.ts index e13feb8e8..b1bd376cb 100644 --- a/src/assets/providers/index.ts +++ b/src/assets/providers/index.ts @@ -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 = { openai, google, openrouter, + ark, moonshot, siliconflow, 'minimax-portal': minimaxPortal, diff --git a/src/lib/providers.ts b/src/lib/providers.ts index e778274cc..b6698b16e 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -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' }, diff --git a/tests/unit/providers.test.ts b/tests/unit/providers.test.ts new file mode 100644 index 000000000..dc85e3781 --- /dev/null +++ b/tests/unit/providers.test.ts @@ -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']) + ); + }); +});