Skip to content

Commit e39c534

Browse files
authored
feat(providers): add Fireworks AI provider integration (#3873)
* feat(providers): add Fireworks AI provider integration * fix(providers): remove unused logger and dead modelInfo from fireworks * lint * feat(providers): add Fireworks BYOK support and official icon * fix(providers): add workspace membership check and remove shared fetch cache for fireworks models
1 parent b95a049 commit e39c534

File tree

21 files changed

+892
-14
lines changed

21 files changed

+892
-14
lines changed

apps/sim/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
2828
# OLLAMA_URL=http://localhost:11434 # URL for local Ollama server - uncomment if using local models
2929
# VLLM_BASE_URL=http://localhost:8000 # Base URL for your self-hosted vLLM (OpenAI-compatible)
3030
# VLLM_API_KEY= # Optional bearer token if your vLLM instance requires auth
31+
# FIREWORKS_API_KEY= # Optional Fireworks AI API key for model listing
3132

3233
# Admin API (Optional - for self-hosted GitOps)
3334
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { createLogger } from '@sim/logger'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { getBYOKKey } from '@/lib/api-key/byok'
4+
import { getSession } from '@/lib/auth'
5+
import { env } from '@/lib/core/config/env'
6+
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
7+
import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils'
8+
9+
const logger = createLogger('FireworksModelsAPI')
10+
11+
interface FireworksModel {
12+
id: string
13+
object?: string
14+
created?: number
15+
owned_by?: string
16+
}
17+
18+
interface FireworksModelsResponse {
19+
data: FireworksModel[]
20+
object?: string
21+
}
22+
23+
export async function GET(request: NextRequest) {
24+
if (isProviderBlacklisted('fireworks')) {
25+
logger.info('Fireworks provider is blacklisted, returning empty models')
26+
return NextResponse.json({ models: [] })
27+
}
28+
29+
let apiKey: string | undefined
30+
31+
const workspaceId = request.nextUrl.searchParams.get('workspaceId')
32+
if (workspaceId) {
33+
const session = await getSession()
34+
if (session?.user?.id) {
35+
const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
36+
if (permission) {
37+
const byokResult = await getBYOKKey(workspaceId, 'fireworks')
38+
if (byokResult) {
39+
apiKey = byokResult.apiKey
40+
}
41+
}
42+
}
43+
}
44+
45+
if (!apiKey) {
46+
apiKey = env.FIREWORKS_API_KEY
47+
}
48+
49+
if (!apiKey) {
50+
logger.info('No Fireworks API key available, returning empty models')
51+
return NextResponse.json({ models: [] })
52+
}
53+
54+
try {
55+
const response = await fetch('https://api.fireworks.ai/inference/v1/models', {
56+
headers: {
57+
Authorization: `Bearer ${apiKey}`,
58+
'Content-Type': 'application/json',
59+
},
60+
cache: 'no-store',
61+
})
62+
63+
if (!response.ok) {
64+
logger.warn('Failed to fetch Fireworks models', {
65+
status: response.status,
66+
statusText: response.statusText,
67+
})
68+
return NextResponse.json({ models: [] })
69+
}
70+
71+
const data = (await response.json()) as FireworksModelsResponse
72+
73+
const allModels: string[] = []
74+
for (const model of data.data ?? []) {
75+
allModels.push(`fireworks/${model.id}`)
76+
}
77+
78+
const uniqueModels = Array.from(new Set(allModels))
79+
const models = filterBlacklistedModels(uniqueModels)
80+
81+
logger.info('Successfully fetched Fireworks models', {
82+
count: models.length,
83+
filtered: uniqueModels.length - models.length,
84+
})
85+
86+
return NextResponse.json({ models })
87+
} catch (error) {
88+
logger.error('Error fetching Fireworks models', {
89+
error: error instanceof Error ? error.message : 'Unknown error',
90+
})
91+
return NextResponse.json({ models: [] })
92+
}
93+
}

apps/sim/app/api/workspaces/[id]/byok-keys/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const VALID_PROVIDERS = [
1818
'anthropic',
1919
'google',
2020
'mistral',
21+
'fireworks',
2122
'firecrawl',
2223
'exa',
2324
'serper',

apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import { useEffect } from 'react'
44
import { createLogger } from '@sim/logger'
5+
import { useParams } from 'next/navigation'
56
import { useProviderModels } from '@/hooks/queries/providers'
67
import {
8+
updateFireworksProviderModels,
79
updateOllamaProviderModels,
810
updateOpenRouterProviderModels,
911
updateVLLMProviderModels,
@@ -12,11 +14,11 @@ import { type ProviderName, useProvidersStore } from '@/stores/providers'
1214

1315
const logger = createLogger('ProviderModelsLoader')
1416

15-
function useSyncProvider(provider: ProviderName) {
17+
function useSyncProvider(provider: ProviderName, workspaceId?: string) {
1618
const setProviderModels = useProvidersStore((state) => state.setProviderModels)
1719
const setProviderLoading = useProvidersStore((state) => state.setProviderLoading)
1820
const setOpenRouterModelInfo = useProvidersStore((state) => state.setOpenRouterModelInfo)
19-
const { data, isLoading, isFetching, error } = useProviderModels(provider)
21+
const { data, isLoading, isFetching, error } = useProviderModels(provider, workspaceId)
2022

2123
useEffect(() => {
2224
setProviderLoading(provider, isLoading || isFetching)
@@ -35,6 +37,8 @@ function useSyncProvider(provider: ProviderName) {
3537
if (data.modelInfo) {
3638
setOpenRouterModelInfo(data.modelInfo)
3739
}
40+
} else if (provider === 'fireworks') {
41+
void updateFireworksProviderModels(data.models)
3842
}
3943
} catch (syncError) {
4044
logger.warn(`Failed to sync provider definitions for ${provider}`, syncError as Error)
@@ -51,9 +55,13 @@ function useSyncProvider(provider: ProviderName) {
5155
}
5256

5357
export function ProviderModelsLoader() {
58+
const params = useParams()
59+
const workspaceId = params?.workspaceId as string | undefined
60+
5461
useSyncProvider('base')
5562
useSyncProvider('ollama')
5663
useSyncProvider('vllm')
5764
useSyncProvider('openrouter')
65+
useSyncProvider('fireworks', workspaceId)
5866
return null
5967
}

apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
BrandfetchIcon,
1919
ExaAIIcon,
2020
FirecrawlIcon,
21+
FireworksIcon,
2122
GeminiIcon,
2223
GoogleIcon,
2324
JinaAIIcon,
@@ -75,6 +76,13 @@ const PROVIDERS: {
7576
description: 'LLM calls and Knowledge Base OCR',
7677
placeholder: 'Enter your API key',
7778
},
79+
{
80+
id: 'fireworks',
81+
name: 'Fireworks',
82+
icon: FireworksIcon,
83+
description: 'LLM calls',
84+
placeholder: 'Enter your Fireworks API key',
85+
},
7886
{
7987
id: 'firecrawl',
8088
name: 'Firecrawl',

apps/sim/blocks/utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ export function getModelOptions() {
2121
const ollamaModels = providersState.providers.ollama.models
2222
const vllmModels = providersState.providers.vllm.models
2323
const openrouterModels = providersState.providers.openrouter.models
24+
const fireworksModels = providersState.providers.fireworks.models
2425
const allModels = Array.from(
25-
new Set([...baseModels, ...ollamaModels, ...vllmModels, ...openrouterModels])
26+
new Set([
27+
...baseModels,
28+
...ollamaModels,
29+
...vllmModels,
30+
...openrouterModels,
31+
...fireworksModels,
32+
])
2633
)
2734

2835
return allModels.map((model) => {

apps/sim/components/icons.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3483,6 +3483,25 @@ export function MySQLIcon(props: SVGProps<SVGSVGElement>) {
34833483
)
34843484
}
34853485

3486+
export function FireworksIcon(props: SVGProps<SVGSVGElement>) {
3487+
return (
3488+
<svg
3489+
{...props}
3490+
viewBox='0 0 512 512'
3491+
xmlns='http://www.w3.org/2000/svg'
3492+
fillRule='evenodd'
3493+
clipRule='evenodd'
3494+
strokeLinejoin='round'
3495+
strokeMiterlimit={2}
3496+
>
3497+
<path
3498+
d='M314.333 110.167L255.98 251.729l-58.416-141.562h-37.459l64 154.75c5.23 12.854 17.771 21.312 31.646 21.312s26.417-8.437 31.646-21.27l64.396-154.792h-37.459zm24.917 215.666L446 216.583l-14.562-34.77-116.584 119.562c-9.708 9.958-12.541 24.833-7.146 37.646 5.292 12.73 17.792 21.083 31.584 21.083l.042.063L506 359.75l-14.562-34.77-152.146.853h-.042zM66 216.5l14.563-34.77 116.583 119.562a34.592 34.592 0 017.146 37.646C199 351.667 186.5 360.02 172.708 360.02l-166.666-.375-.042.042 14.563-34.771 152.145.875L66 216.5z'
3499+
fill='currentColor'
3500+
/>
3501+
</svg>
3502+
)
3503+
}
3504+
34863505
export function OpenRouterIcon(props: SVGProps<SVGSVGElement>) {
34873506
return (
34883507
<svg

apps/sim/hooks/queries/providers.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const providerEndpoints: Record<ProviderName, string> = {
99
ollama: '/api/providers/ollama/models',
1010
vllm: '/api/providers/vllm/models',
1111
openrouter: '/api/providers/openrouter/models',
12+
fireworks: '/api/providers/fireworks/models',
1213
}
1314

1415
interface ProviderModelsResponse {
@@ -18,14 +19,21 @@ interface ProviderModelsResponse {
1819

1920
export const providerKeys = {
2021
all: ['provider-models'] as const,
21-
models: (provider: string) => [...providerKeys.all, provider] as const,
22+
models: (provider: string, workspaceId?: string) =>
23+
[...providerKeys.all, provider, workspaceId ?? ''] as const,
2224
}
2325

2426
async function fetchProviderModels(
2527
provider: ProviderName,
26-
signal?: AbortSignal
28+
signal?: AbortSignal,
29+
workspaceId?: string
2730
): Promise<ProviderModelsResponse> {
28-
const response = await fetch(providerEndpoints[provider], { signal })
31+
let url = providerEndpoints[provider]
32+
if (provider === 'fireworks' && workspaceId) {
33+
url = `${url}?workspaceId=${encodeURIComponent(workspaceId)}`
34+
}
35+
36+
const response = await fetch(url, { signal })
2937

3038
if (!response.ok) {
3139
logger.warn(`Failed to fetch ${provider} models`, {
@@ -45,10 +53,10 @@ async function fetchProviderModels(
4553
}
4654
}
4755

48-
export function useProviderModels(provider: ProviderName) {
56+
export function useProviderModels(provider: ProviderName, workspaceId?: string) {
4957
return useQuery({
50-
queryKey: providerKeys.models(provider),
51-
queryFn: ({ signal }) => fetchProviderModels(provider, signal),
58+
queryKey: providerKeys.models(provider, workspaceId),
59+
queryFn: ({ signal }) => fetchProviderModels(provider, signal, workspaceId),
5260
staleTime: 5 * 60 * 1000,
5361
})
5462
}

apps/sim/lib/api-key/byok.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,26 @@ export async function getApiKeyWithBYOK(
7373
return { apiKey: userProvidedKey || env.VLLM_API_KEY || 'empty', isBYOK: false }
7474
}
7575

76+
const isFireworksModel =
77+
provider === 'fireworks' ||
78+
useProvidersStore.getState().providers.fireworks.models.includes(model)
79+
if (isFireworksModel) {
80+
if (workspaceId) {
81+
const byokResult = await getBYOKKey(workspaceId, 'fireworks')
82+
if (byokResult) {
83+
logger.info('Using BYOK key for Fireworks', { model, workspaceId })
84+
return byokResult
85+
}
86+
}
87+
if (userProvidedKey) {
88+
return { apiKey: userProvidedKey, isBYOK: false }
89+
}
90+
if (env.FIREWORKS_API_KEY) {
91+
return { apiKey: env.FIREWORKS_API_KEY, isBYOK: false }
92+
}
93+
throw new Error(`API key is required for Fireworks ${model}`)
94+
}
95+
7696
const isBedrockModel = provider === 'bedrock' || model.startsWith('bedrock/')
7797
if (isBedrockModel) {
7898
return { apiKey: 'bedrock-uses-own-credentials', isBYOK: false }

apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,14 +696,19 @@ function resolveAuthType(
696696
/**
697697
* Gets all available models from PROVIDER_DEFINITIONS as static options.
698698
* This provides fallback data when store state is not available server-side.
699-
* Excludes dynamic providers (ollama, vllm, openrouter) which require runtime fetching.
699+
* Excludes dynamic providers (ollama, vllm, openrouter, fireworks) which require runtime fetching.
700700
*/
701701
function getStaticModelOptions(): { id: string; label?: string }[] {
702702
const models: { id: string; label?: string }[] = []
703703

704704
for (const provider of Object.values(PROVIDER_DEFINITIONS)) {
705705
// Skip providers with dynamic/fetched models
706-
if (provider.id === 'ollama' || provider.id === 'vllm' || provider.id === 'openrouter') {
706+
if (
707+
provider.id === 'ollama' ||
708+
provider.id === 'vllm' ||
709+
provider.id === 'openrouter' ||
710+
provider.id === 'fireworks'
711+
) {
707712
continue
708713
}
709714
if (provider?.models) {
@@ -737,6 +742,7 @@ function callOptionsWithFallback(
737742
ollama: { models: [] },
738743
vllm: { models: [] },
739744
openrouter: { models: [] },
745+
fireworks: { models: [] },
740746
},
741747
}
742748

0 commit comments

Comments
 (0)