Skip to content

Commit 8000394

Browse files
authored
feat(models): host google gemini models (#2122)
* feat(models): host google gemini models * remove unused primary key
1 parent 0830490 commit 8000394

File tree

7 files changed

+97
-22
lines changed

7 files changed

+97
-22
lines changed

apps/docs/content/docs/en/execution/costs.mdx

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,53 @@ The model breakdown shows:
4949
<Tabs items={['Hosted Models', 'Bring Your Own API Key']}>
5050
<Tab>
5151
**Hosted Models** - Sim provides API keys with a 2.5x pricing multiplier:
52-
52+
53+
**OpenAI**
5354
| Model | Base Price (Input/Output) | Hosted Price (Input/Output) |
5455
|-------|---------------------------|----------------------------|
56+
| GPT-5.1 | $1.25 / $10.00 | $3.13 / $25.00 |
57+
| GPT-5 | $1.25 / $10.00 | $3.13 / $25.00 |
58+
| GPT-5 Mini | $0.25 / $2.00 | $0.63 / $5.00 |
59+
| GPT-5 Nano | $0.05 / $0.40 | $0.13 / $1.00 |
5560
| GPT-4o | $2.50 / $10.00 | $6.25 / $25.00 |
5661
| GPT-4.1 | $2.00 / $8.00 | $5.00 / $20.00 |
62+
| GPT-4.1 Mini | $0.40 / $1.60 | $1.00 / $4.00 |
63+
| GPT-4.1 Nano | $0.10 / $0.40 | $0.25 / $1.00 |
5764
| o1 | $15.00 / $60.00 | $37.50 / $150.00 |
5865
| o3 | $2.00 / $8.00 | $5.00 / $20.00 |
59-
| Claude 3.5 Sonnet | $3.00 / $15.00 | $7.50 / $37.50 |
60-
| Claude Opus 4.0 | $15.00 / $75.00 | $37.50 / $187.50 |
61-
66+
| o4 Mini | $1.10 / $4.40 | $2.75 / $11.00 |
67+
68+
**Anthropic**
69+
| Model | Base Price (Input/Output) | Hosted Price (Input/Output) |
70+
|-------|---------------------------|----------------------------|
71+
| Claude Opus 4.5 | $5.00 / $25.00 | $12.50 / $62.50 |
72+
| Claude Opus 4.1 | $15.00 / $75.00 | $37.50 / $187.50 |
73+
| Claude Sonnet 4.5 | $3.00 / $15.00 | $7.50 / $37.50 |
74+
| Claude Sonnet 4.0 | $3.00 / $15.00 | $7.50 / $37.50 |
75+
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.50 / $12.50 |
76+
77+
**Google**
78+
| Model | Base Price (Input/Output) | Hosted Price (Input/Output) |
79+
|-------|---------------------------|----------------------------|
80+
| Gemini 3 Pro Preview | $2.00 / $12.00 | $5.00 / $30.00 |
81+
| Gemini 2.5 Pro | $0.15 / $0.60 | $0.38 / $1.50 |
82+
| Gemini 2.5 Flash | $0.15 / $0.60 | $0.38 / $1.50 |
83+
6284
*The 2.5x multiplier covers infrastructure and API management costs.*
6385
</Tab>
64-
86+
6587
<Tab>
6688
**Your Own API Keys** - Use any model at base pricing:
67-
68-
| Provider | Models | Input / Output |
69-
|----------|---------|----------------|
70-
| Google | Gemini 2.5 | $0.15 / $0.60 |
89+
90+
| Provider | Example Models | Input / Output |
91+
|----------|----------------|----------------|
7192
| Deepseek | V3, R1 | $0.75 / $1.00 |
72-
| xAI | Grok 4, Grok 3 | $5.00 / $25.00 |
73-
| Groq | Llama 4 Scout | $0.40 / $0.60 |
74-
| Cerebras | Llama 3.3 70B | $0.94 / $0.94 |
93+
| xAI | Grok 4 Latest, Grok 3 | $3.00 / $15.00 |
94+
| Groq | Llama 4 Scout, Llama 3.3 70B | $0.11 / $0.34 |
95+
| Cerebras | Llama 4 Scout, Llama 3.3 70B | $0.11 / $0.34 |
7596
| Ollama | Local models | Free |
7697
| VLLM | Local models | Free |
77-
98+
7899
*Pay providers directly with no markup*
79100
</Tab>
80101
</Tabs>

apps/sim/lib/env.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ export const env = createEnv({
7676
ANTHROPIC_API_KEY_1: z.string().min(1).optional(), // Primary Anthropic Claude API key
7777
ANTHROPIC_API_KEY_2: z.string().min(1).optional(), // Additional Anthropic API key for load balancing
7878
ANTHROPIC_API_KEY_3: z.string().min(1).optional(), // Additional Anthropic API key for load balancing
79+
GEMINI_API_KEY_1: z.string().min(1).optional(), // Primary Gemini API key
80+
GEMINI_API_KEY_2: z.string().min(1).optional(), // Additional Gemini API key for load balancing
81+
GEMINI_API_KEY_3: z.string().min(1).optional(), // Additional Gemini API key for load balancing
7982
OLLAMA_URL: z.string().url().optional(), // Ollama local LLM server URL
8083
VLLM_BASE_URL: z.string().url().optional(), // vLLM self-hosted base URL (OpenAI-compatible)
8184
VLLM_API_KEY: z.string().optional(), // Optional bearer token for vLLM

apps/sim/lib/utils.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
formatDuration,
1010
formatTime,
1111
getInvalidCharacters,
12+
getRotatingApiKey,
1213
getTimezoneAbbreviation,
1314
isValidName,
1415
redactApiKeys,
@@ -36,6 +37,15 @@ vi.mock('crypto', () => ({
3637
vi.mock('@/lib/env', () => ({
3738
env: {
3839
ENCRYPTION_KEY: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
40+
OPENAI_API_KEY_1: 'test-openai-key-1',
41+
OPENAI_API_KEY_2: 'test-openai-key-2',
42+
OPENAI_API_KEY_3: 'test-openai-key-3',
43+
ANTHROPIC_API_KEY_1: 'test-anthropic-key-1',
44+
ANTHROPIC_API_KEY_2: 'test-anthropic-key-2',
45+
ANTHROPIC_API_KEY_3: 'test-anthropic-key-3',
46+
GEMINI_API_KEY_1: 'test-gemini-key-1',
47+
GEMINI_API_KEY_2: 'test-gemini-key-2',
48+
GEMINI_API_KEY_3: 'test-gemini-key-3',
3949
},
4050
}))
4151

@@ -383,3 +393,29 @@ describe('getInvalidCharacters', () => {
383393
expect(result).toEqual(['@', '#', '$', '%'])
384394
})
385395
})
396+
397+
describe('getRotatingApiKey', () => {
398+
it.concurrent('should return OpenAI API key based on current minute', () => {
399+
const result = getRotatingApiKey('openai')
400+
expect(result).toMatch(/^test-openai-key-[1-3]$/)
401+
})
402+
403+
it.concurrent('should return Anthropic API key based on current minute', () => {
404+
const result = getRotatingApiKey('anthropic')
405+
expect(result).toMatch(/^test-anthropic-key-[1-3]$/)
406+
})
407+
408+
it.concurrent('should return Gemini API key based on current minute', () => {
409+
const result = getRotatingApiKey('gemini')
410+
expect(result).toMatch(/^test-gemini-key-[1-3]$/)
411+
})
412+
413+
it.concurrent('should throw error for unsupported provider', () => {
414+
expect(() => getRotatingApiKey('unsupported')).toThrow('No rotation implemented for provider')
415+
})
416+
417+
it.concurrent('should rotate keys based on minute modulo', () => {
418+
const result = getRotatingApiKey('openai')
419+
expect(['test-openai-key-1', 'test-openai-key-2', 'test-openai-key-3']).toContain(result)
420+
})
421+
})

apps/sim/lib/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ export function generatePassword(length = 24): string {
287287
* @throws Error if no API keys are configured for rotation
288288
*/
289289
export function getRotatingApiKey(provider: string): string {
290-
if (provider !== 'openai' && provider !== 'anthropic') {
290+
if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'gemini') {
291291
throw new Error(`No rotation implemented for provider: ${provider}`)
292292
}
293293

@@ -301,6 +301,10 @@ export function getRotatingApiKey(provider: string): string {
301301
if (env.ANTHROPIC_API_KEY_1) keys.push(env.ANTHROPIC_API_KEY_1)
302302
if (env.ANTHROPIC_API_KEY_2) keys.push(env.ANTHROPIC_API_KEY_2)
303303
if (env.ANTHROPIC_API_KEY_3) keys.push(env.ANTHROPIC_API_KEY_3)
304+
} else if (provider === 'gemini') {
305+
if (env.GEMINI_API_KEY_1) keys.push(env.GEMINI_API_KEY_1)
306+
if (env.GEMINI_API_KEY_2) keys.push(env.GEMINI_API_KEY_2)
307+
if (env.GEMINI_API_KEY_3) keys.push(env.GEMINI_API_KEY_3)
304308
}
305309

306310
if (keys.length === 0) {

apps/sim/providers/models.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,8 +1336,11 @@ export function getProvidersWithToolUsageControl(): string[] {
13361336
* Get all models that are hosted (don't require user API keys)
13371337
*/
13381338
export function getHostedModels(): string[] {
1339-
// Currently, OpenAI and Anthropic models are hosted
1340-
return [...getProviderModels('openai'), ...getProviderModels('anthropic')]
1339+
return [
1340+
...getProviderModels('openai'),
1341+
...getProviderModels('anthropic'),
1342+
...getProviderModels('google'),
1343+
]
13411344
}
13421345

13431346
/**

apps/sim/providers/utils.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,17 +445,24 @@ describe('Cost Calculation', () => {
445445
})
446446

447447
describe('getHostedModels', () => {
448-
it.concurrent('should return OpenAI and Anthropic models as hosted', () => {
448+
it.concurrent('should return OpenAI, Anthropic, and Google models as hosted', () => {
449449
const hostedModels = getHostedModels()
450450

451+
// OpenAI models
451452
expect(hostedModels).toContain('gpt-4o')
452-
expect(hostedModels).toContain('claude-sonnet-4-0')
453453
expect(hostedModels).toContain('o1')
454+
455+
// Anthropic models
456+
expect(hostedModels).toContain('claude-sonnet-4-0')
454457
expect(hostedModels).toContain('claude-opus-4-0')
455458

459+
// Google models
460+
expect(hostedModels).toContain('gemini-2.5-pro')
461+
expect(hostedModels).toContain('gemini-2.5-flash')
462+
456463
// Should not contain models from other providers
457-
expect(hostedModels).not.toContain('gemini-2.5-pro')
458464
expect(hostedModels).not.toContain('deepseek-v3')
465+
expect(hostedModels).not.toContain('grok-4-latest')
459466
})
460467

461468
it.concurrent('should return an array of strings', () => {

apps/sim/providers/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -637,15 +637,16 @@ export function getApiKey(provider: string, model: string, userProvidedKey?: str
637637
return 'empty' // Ollama uses 'empty' as a placeholder API key
638638
}
639639

640-
// Use server key rotation for all OpenAI models and Anthropic's Claude models on the hosted platform
640+
// Use server key rotation for all OpenAI models, Anthropic's Claude models, and Google's Gemini models on the hosted platform
641641
const isOpenAIModel = provider === 'openai'
642642
const isClaudeModel = provider === 'anthropic'
643+
const isGeminiModel = provider === 'google'
643644

644-
if (isHosted && (isOpenAIModel || isClaudeModel)) {
645+
if (isHosted && (isOpenAIModel || isClaudeModel || isGeminiModel)) {
645646
try {
646647
// Import the key rotation function
647648
const { getRotatingApiKey } = require('@/lib/utils')
648-
const serverKey = getRotatingApiKey(provider)
649+
const serverKey = getRotatingApiKey(isGeminiModel ? 'gemini' : provider)
649650
return serverKey
650651
} catch (_error) {
651652
// If server key fails and we have a user key, fallback to that

0 commit comments

Comments
 (0)