Spaces:
Sleeping
Sleeping
Joshua Mo Cursor Agent Joshua Mo commited on
Add Venice AI integration and model support (#301)
Browse filesCo-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Joshua Mo <joshua-mo-143@users.noreply.github.com>
src/app/api/integrations/route.ts
CHANGED
|
@@ -45,6 +45,7 @@ const INTEGRATIONS: IntegrationDef[] = [
|
|
| 45 |
{ id: 'anthropic', name: 'Anthropic', category: 'ai', envVars: ['ANTHROPIC_API_KEY'], vaultItem: 'openclaw-anthropic-api-key', testable: true },
|
| 46 |
{ id: 'openai', name: 'OpenAI', category: 'ai', envVars: ['OPENAI_API_KEY'], vaultItem: 'openclaw-openai-api-key', testable: true },
|
| 47 |
{ id: 'openrouter', name: 'OpenRouter', category: 'ai', envVars: ['OPENROUTER_API_KEY'], vaultItem: 'openclaw-openrouter-api-key', testable: true },
|
|
|
|
| 48 |
{ id: 'nvidia', name: 'NVIDIA', category: 'ai', envVars: ['NVIDIA_API_KEY'], vaultItem: 'openclaw-nvidia-api-key' },
|
| 49 |
{ id: 'moonshot', name: 'Moonshot / Kimi', category: 'ai', envVars: ['MOONSHOT_API_KEY'], vaultItem: 'openclaw-moonshot-api-key' },
|
| 50 |
{ id: 'ollama', name: 'Ollama (Local)', category: 'ai', envVars: ['OLLAMA_API_KEY'], vaultItem: 'openclaw-ollama-api-key' },
|
|
@@ -735,6 +736,19 @@ async function handleTest(
|
|
| 735 |
break
|
| 736 |
}
|
| 737 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
case 'hyperbrowser': {
|
| 739 |
const key = getEffectiveEnvValue(envMap, 'HYPERBROWSER_API_KEY')
|
| 740 |
if (!key) return NextResponse.json({ ok: false, detail: 'API key not set' })
|
|
|
|
| 45 |
{ id: 'anthropic', name: 'Anthropic', category: 'ai', envVars: ['ANTHROPIC_API_KEY'], vaultItem: 'openclaw-anthropic-api-key', testable: true },
|
| 46 |
{ id: 'openai', name: 'OpenAI', category: 'ai', envVars: ['OPENAI_API_KEY'], vaultItem: 'openclaw-openai-api-key', testable: true },
|
| 47 |
{ id: 'openrouter', name: 'OpenRouter', category: 'ai', envVars: ['OPENROUTER_API_KEY'], vaultItem: 'openclaw-openrouter-api-key', testable: true },
|
| 48 |
+
{ id: 'venice', name: 'Venice AI', category: 'ai', envVars: ['VENICE_API_KEY'], vaultItem: 'openclaw-venice-api-key', testable: true },
|
| 49 |
{ id: 'nvidia', name: 'NVIDIA', category: 'ai', envVars: ['NVIDIA_API_KEY'], vaultItem: 'openclaw-nvidia-api-key' },
|
| 50 |
{ id: 'moonshot', name: 'Moonshot / Kimi', category: 'ai', envVars: ['MOONSHOT_API_KEY'], vaultItem: 'openclaw-moonshot-api-key' },
|
| 51 |
{ id: 'ollama', name: 'Ollama (Local)', category: 'ai', envVars: ['OLLAMA_API_KEY'], vaultItem: 'openclaw-ollama-api-key' },
|
|
|
|
| 736 |
break
|
| 737 |
}
|
| 738 |
|
| 739 |
+
case 'venice': {
|
| 740 |
+
const key = getEffectiveEnvValue(envMap, 'VENICE_API_KEY')
|
| 741 |
+
if (!key) return NextResponse.json({ ok: false, detail: 'API key not set' })
|
| 742 |
+
const res = await fetch('https://api.venice.ai/api/v1/models', {
|
| 743 |
+
headers: { Authorization: `Bearer ${key}` },
|
| 744 |
+
signal: AbortSignal.timeout(5000),
|
| 745 |
+
})
|
| 746 |
+
result = res.ok
|
| 747 |
+
? { ok: true, detail: 'API key valid' }
|
| 748 |
+
: { ok: false, detail: `HTTP ${res.status}` }
|
| 749 |
+
break
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
case 'hyperbrowser': {
|
| 753 |
const key = getEffectiveEnvValue(envMap, 'HYPERBROWSER_API_KEY')
|
| 754 |
if (!key) return NextResponse.json({ ok: false, detail: 'API key not set' })
|
src/lib/__tests__/token-pricing.test.ts
CHANGED
|
@@ -34,6 +34,7 @@ describe('token pricing', () => {
|
|
| 34 |
it('maps providers from model prefixes and names', () => {
|
| 35 |
expect(getProviderFromModel('openai/gpt-4.1')).toBe('openai')
|
| 36 |
expect(getProviderFromModel('anthropic/claude-sonnet-4-5')).toBe('anthropic')
|
|
|
|
| 37 |
expect(getProviderFromModel('gateway::codex-mini')).toBe('openai')
|
| 38 |
})
|
| 39 |
})
|
|
|
|
| 34 |
it('maps providers from model prefixes and names', () => {
|
| 35 |
expect(getProviderFromModel('openai/gpt-4.1')).toBe('openai')
|
| 36 |
expect(getProviderFromModel('anthropic/claude-sonnet-4-5')).toBe('anthropic')
|
| 37 |
+
expect(getProviderFromModel('venice/llama-3.3-70b')).toBe('venice')
|
| 38 |
expect(getProviderFromModel('gateway::codex-mini')).toBe('openai')
|
| 39 |
})
|
| 40 |
})
|
src/lib/__tests__/token-utils.test.ts
CHANGED
|
@@ -11,6 +11,7 @@ describe('detectProvider', () => {
|
|
| 11 |
['mistral-large', 'Mistral'],
|
| 12 |
['llama-3', 'Meta'],
|
| 13 |
['deepseek-coder', 'DeepSeek'],
|
|
|
|
| 14 |
['unknown-model', 'Other'],
|
| 15 |
])('%s -> %s', (model, expected) => {
|
| 16 |
expect(detectProvider(model)).toBe(expected)
|
|
|
|
| 11 |
['mistral-large', 'Mistral'],
|
| 12 |
['llama-3', 'Meta'],
|
| 13 |
['deepseek-coder', 'DeepSeek'],
|
| 14 |
+
['venice/llama-3.3-70b', 'Venice AI'],
|
| 15 |
['unknown-model', 'Other'],
|
| 16 |
])('%s -> %s', (model, expected) => {
|
| 17 |
expect(detectProvider(model)).toBe(expected)
|
src/lib/models.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const MODEL_CATALOG: ModelConfig[] = [
|
|
| 14 |
{ alias: 'groq-fast', name: 'groq/llama-3.1-8b-instant', provider: 'groq', description: '840 tok/s, ultra fast', costPer1k: 0.05 },
|
| 15 |
{ alias: 'groq', name: 'groq/llama-3.3-70b-versatile', provider: 'groq', description: 'Fast + quality balance', costPer1k: 0.59 },
|
| 16 |
{ alias: 'kimi', name: 'moonshot/kimi-k2.5', provider: 'moonshot', description: 'Alternative provider', costPer1k: 1.0 },
|
|
|
|
| 17 |
{ alias: 'minimax', name: 'minimax/minimax-m2.1', provider: 'minimax', description: 'Cost-effective (1/10th price), strong coding', costPer1k: 0.3 },
|
| 18 |
]
|
| 19 |
|
|
|
|
| 14 |
{ alias: 'groq-fast', name: 'groq/llama-3.1-8b-instant', provider: 'groq', description: '840 tok/s, ultra fast', costPer1k: 0.05 },
|
| 15 |
{ alias: 'groq', name: 'groq/llama-3.3-70b-versatile', provider: 'groq', description: 'Fast + quality balance', costPer1k: 0.59 },
|
| 16 |
{ alias: 'kimi', name: 'moonshot/kimi-k2.5', provider: 'moonshot', description: 'Alternative provider', costPer1k: 1.0 },
|
| 17 |
+
{ alias: 'venice-llama-3.3-70b', name: 'venice/llama-3.3-70b', provider: 'venice', description: 'Venice AI Llama 3.3 70B', costPer1k: 0.7 },
|
| 18 |
{ alias: 'minimax', name: 'minimax/minimax-m2.1', provider: 'minimax', description: 'Cost-effective (1/10th price), strong coding', costPer1k: 0.3 },
|
| 19 |
]
|
| 20 |
|
src/lib/token-pricing.ts
CHANGED
|
@@ -33,6 +33,7 @@ const MODEL_PRICING: Record<string, ModelPricing> = {
|
|
| 33 |
'groq/llama-3.1-8b-instant': { inputPerMTok: 0.05, outputPerMTok: 0.05 },
|
| 34 |
'groq/llama-3.3-70b-versatile': { inputPerMTok: 0.59, outputPerMTok: 0.59 },
|
| 35 |
'moonshot/kimi-k2.5': { inputPerMTok: 1.0, outputPerMTok: 1.0 },
|
|
|
|
| 36 |
'minimax/minimax-m2.1': { inputPerMTok: 0.3, outputPerMTok: 0.3 },
|
| 37 |
'ollama/deepseek-r1:14b': { inputPerMTok: 0.0, outputPerMTok: 0.0 },
|
| 38 |
'ollama/qwen2.5-coder:7b': { inputPerMTok: 0.0, outputPerMTok: 0.0 },
|
|
|
|
| 33 |
'groq/llama-3.1-8b-instant': { inputPerMTok: 0.05, outputPerMTok: 0.05 },
|
| 34 |
'groq/llama-3.3-70b-versatile': { inputPerMTok: 0.59, outputPerMTok: 0.59 },
|
| 35 |
'moonshot/kimi-k2.5': { inputPerMTok: 1.0, outputPerMTok: 1.0 },
|
| 36 |
+
'venice/llama-3.3-70b': { inputPerMTok: 0.7, outputPerMTok: 2.8 },
|
| 37 |
'minimax/minimax-m2.1': { inputPerMTok: 0.3, outputPerMTok: 0.3 },
|
| 38 |
'ollama/deepseek-r1:14b': { inputPerMTok: 0.0, outputPerMTok: 0.0 },
|
| 39 |
'ollama/qwen2.5-coder:7b': { inputPerMTok: 0.0, outputPerMTok: 0.0 },
|
src/lib/token-utils.ts
CHANGED
|
@@ -4,6 +4,7 @@ export function detectProvider(model: string): string {
|
|
| 4 |
if (lower.includes('gpt') || lower.includes('o1') || lower.includes('o3') || lower.includes('o4') || lower.includes('openai')) return 'OpenAI'
|
| 5 |
if (lower.includes('gemini') || lower.includes('google')) return 'Google'
|
| 6 |
if (lower.includes('mistral') || lower.includes('mixtral')) return 'Mistral'
|
|
|
|
| 7 |
if (lower.includes('llama') || lower.includes('meta')) return 'Meta'
|
| 8 |
if (lower.includes('deepseek')) return 'DeepSeek'
|
| 9 |
if (lower.includes('command') || lower.includes('cohere')) return 'Cohere'
|
|
|
|
| 4 |
if (lower.includes('gpt') || lower.includes('o1') || lower.includes('o3') || lower.includes('o4') || lower.includes('openai')) return 'OpenAI'
|
| 5 |
if (lower.includes('gemini') || lower.includes('google')) return 'Google'
|
| 6 |
if (lower.includes('mistral') || lower.includes('mixtral')) return 'Mistral'
|
| 7 |
+
if (lower.includes('venice')) return 'Venice AI'
|
| 8 |
if (lower.includes('llama') || lower.includes('meta')) return 'Meta'
|
| 9 |
if (lower.includes('deepseek')) return 'DeepSeek'
|
| 10 |
if (lower.includes('command') || lower.includes('cohere')) return 'Cohere'
|