Feat/Add ByteDance Ark provider (#226)
This commit is contained in:
committed by
GitHub
Unverified
parent
1b45d891c3
commit
8cda9235b3
@@ -10,6 +10,7 @@ export const BUILTIN_PROVIDER_TYPES = [
|
|||||||
'openai',
|
'openai',
|
||||||
'google',
|
'google',
|
||||||
'openrouter',
|
'openrouter',
|
||||||
|
'ark',
|
||||||
'moonshot',
|
'moonshot',
|
||||||
'siliconflow',
|
'siliconflow',
|
||||||
'minimax-portal',
|
'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: {
|
moonshot: {
|
||||||
envVar: 'MOONSHOT_API_KEY',
|
envVar: 'MOONSHOT_API_KEY',
|
||||||
defaultModel: 'moonshot/kimi-k2.5',
|
defaultModel: 'moonshot/kimi-k2.5',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Keys are stored in plain text alongside provider configs in a single electron-store.
|
* 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';
|
import { getActiveOpenClawProviders } from './openclaw-auth';
|
||||||
|
|
||||||
// Lazy-load electron-store (ESM module)
|
// 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 results: Array<ProviderConfig & { hasKey: boolean; keyMasked: string | null }> = [];
|
||||||
const activeOpenClawProviders = await getActiveOpenClawProviders();
|
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) {
|
for (const provider of providers) {
|
||||||
// Sync check: If it's a custom/OAuth provider and it no longer exists in OpenClaw config
|
// 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)
|
// (e.g. wiped by Gateway due to missing plugin, or manually deleted by user)
|
||||||
// we should remove it from ClawX UI to stay consistent.
|
// 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
|
// For custom/ollama providers, the OpenClaw config key is derived as
|
||||||
// "<type>-<suffix>" where suffix = first 8 chars of providerId with hyphens stripped.
|
// "<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"
|
// e.g. provider.id "custom-a1b2c3d4-..." → strip hyphens → "customa1b2c3d4..." → slice(0,8) → "customa1"
|
||||||
@@ -261,4 +255,3 @@ export async function getAllProvidersWithKeyInfo(): Promise<
|
|||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
src/assets/providers/ark.svg
Normal file
5
src/assets/providers/ark.svg
Normal 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 |
@@ -2,6 +2,7 @@ import anthropic from './anthropic.svg';
|
|||||||
import openai from './openai.svg';
|
import openai from './openai.svg';
|
||||||
import google from './google.svg';
|
import google from './google.svg';
|
||||||
import openrouter from './openrouter.svg';
|
import openrouter from './openrouter.svg';
|
||||||
|
import ark from './ark.svg';
|
||||||
import moonshot from './moonshot.svg';
|
import moonshot from './moonshot.svg';
|
||||||
import siliconflow from './siliconflow.svg';
|
import siliconflow from './siliconflow.svg';
|
||||||
import minimaxPortal from './minimax.svg';
|
import minimaxPortal from './minimax.svg';
|
||||||
@@ -14,6 +15,7 @@ export const providerIcons: Record<string, string> = {
|
|||||||
openai,
|
openai,
|
||||||
google,
|
google,
|
||||||
openrouter,
|
openrouter,
|
||||||
|
ark,
|
||||||
moonshot,
|
moonshot,
|
||||||
siliconflow,
|
siliconflow,
|
||||||
'minimax-portal': minimaxPortal,
|
'minimax-portal': minimaxPortal,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const PROVIDER_TYPES = [
|
|||||||
'openai',
|
'openai',
|
||||||
'google',
|
'google',
|
||||||
'openrouter',
|
'openrouter',
|
||||||
|
'ark',
|
||||||
'moonshot',
|
'moonshot',
|
||||||
'siliconflow',
|
'siliconflow',
|
||||||
'minimax-portal',
|
'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: 'openai', name: 'OpenAI', icon: '💚', placeholder: 'sk-proj-...', model: 'GPT', requiresApiKey: true },
|
||||||
{ id: 'google', name: 'Google', icon: '🔷', placeholder: 'AIza...', model: 'Gemini', 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: '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: '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: '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' },
|
{ id: 'minimax-portal', name: 'MiniMax (Global)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultModelId: 'MiniMax-M2.5' },
|
||||||
|
|||||||
42
tests/unit/providers.test.ts
Normal file
42
tests/unit/providers.test.ts
Normal 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'])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user