Joshua Mo Cursor Agent Joshua Mo commited on
Commit
ccf9166
·
unverified ·
1 Parent(s): 058b0d8

Add Venice AI integration and model support (#301)

Browse files

Co-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'