Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/sim/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
# OLLAMA_URL=http://localhost:11434 # URL for local Ollama server - uncomment if using local models
# VLLM_BASE_URL=http://localhost:8000 # Base URL for your self-hosted vLLM (OpenAI-compatible)
# VLLM_API_KEY= # Optional bearer token if your vLLM instance requires auth
# FIREWORKS_API_KEY= # Optional Fireworks AI API key for model listing

# Admin API (Optional - for self-hosted GitOps)
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
Expand Down
89 changes: 89 additions & 0 deletions apps/sim/app/api/providers/fireworks/models/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { getBYOKKey } from '@/lib/api-key/byok'
import { getSession } from '@/lib/auth'
import { env } from '@/lib/core/config/env'
import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils'

const logger = createLogger('FireworksModelsAPI')

interface FireworksModel {
id: string
object?: string
created?: number
owned_by?: string
}

interface FireworksModelsResponse {
data: FireworksModel[]
object?: string
}

export async function GET(request: NextRequest) {
if (isProviderBlacklisted('fireworks')) {
logger.info('Fireworks provider is blacklisted, returning empty models')
return NextResponse.json({ models: [] })
}

let apiKey: string | undefined

const workspaceId = request.nextUrl.searchParams.get('workspaceId')
if (workspaceId) {
const session = await getSession()
if (session?.user?.id) {
const byokResult = await getBYOKKey(workspaceId, 'fireworks')
if (byokResult) {
apiKey = byokResult.apiKey
}
}
}

if (!apiKey) {
apiKey = env.FIREWORKS_API_KEY
}

if (!apiKey) {
logger.info('No Fireworks API key available, returning empty models')
return NextResponse.json({ models: [] })
}

try {
const response = await fetch('https://api.fireworks.ai/inference/v1/models', {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
next: { revalidate: 300 },
})
Comment thread
waleedlatif1 marked this conversation as resolved.
Comment thread
waleedlatif1 marked this conversation as resolved.

if (!response.ok) {
logger.warn('Failed to fetch Fireworks models', {
status: response.status,
statusText: response.statusText,
})
return NextResponse.json({ models: [] })
}

const data = (await response.json()) as FireworksModelsResponse

const allModels: string[] = []
for (const model of data.data ?? []) {
allModels.push(`fireworks/${model.id}`)
}

const uniqueModels = Array.from(new Set(allModels))
const models = filterBlacklistedModels(uniqueModels)

logger.info('Successfully fetched Fireworks models', {
count: models.length,
filtered: uniqueModels.length - models.length,
})

return NextResponse.json({ models })
} catch (error) {
logger.error('Error fetching Fireworks models', {
error: error instanceof Error ? error.message : 'Unknown error',
})
return NextResponse.json({ models: [] })
}
}
1 change: 1 addition & 0 deletions apps/sim/app/api/workspaces/[id]/byok-keys/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const VALID_PROVIDERS = [
'anthropic',
'google',
'mistral',
'fireworks',
'firecrawl',
'exa',
'serper',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import { useEffect } from 'react'
import { createLogger } from '@sim/logger'
import { useParams } from 'next/navigation'
import { useProviderModels } from '@/hooks/queries/providers'
import {
updateFireworksProviderModels,
updateOllamaProviderModels,
updateOpenRouterProviderModels,
updateVLLMProviderModels,
Expand All @@ -12,11 +14,11 @@ import { type ProviderName, useProvidersStore } from '@/stores/providers'

const logger = createLogger('ProviderModelsLoader')

function useSyncProvider(provider: ProviderName) {
function useSyncProvider(provider: ProviderName, workspaceId?: string) {
const setProviderModels = useProvidersStore((state) => state.setProviderModels)
const setProviderLoading = useProvidersStore((state) => state.setProviderLoading)
const setOpenRouterModelInfo = useProvidersStore((state) => state.setOpenRouterModelInfo)
const { data, isLoading, isFetching, error } = useProviderModels(provider)
const { data, isLoading, isFetching, error } = useProviderModels(provider, workspaceId)

useEffect(() => {
setProviderLoading(provider, isLoading || isFetching)
Expand All @@ -35,6 +37,8 @@ function useSyncProvider(provider: ProviderName) {
if (data.modelInfo) {
setOpenRouterModelInfo(data.modelInfo)
}
} else if (provider === 'fireworks') {
void updateFireworksProviderModels(data.models)
}
} catch (syncError) {
logger.warn(`Failed to sync provider definitions for ${provider}`, syncError as Error)
Expand All @@ -51,9 +55,13 @@ function useSyncProvider(provider: ProviderName) {
}

export function ProviderModelsLoader() {
const params = useParams()
const workspaceId = params?.workspaceId as string | undefined

useSyncProvider('base')
useSyncProvider('ollama')
useSyncProvider('vllm')
useSyncProvider('openrouter')
useSyncProvider('fireworks', workspaceId)
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
BrandfetchIcon,
ExaAIIcon,
FirecrawlIcon,
FireworksIcon,
GeminiIcon,
GoogleIcon,
JinaAIIcon,
Expand Down Expand Up @@ -75,6 +76,13 @@ const PROVIDERS: {
description: 'LLM calls and Knowledge Base OCR',
placeholder: 'Enter your API key',
},
{
id: 'fireworks',
name: 'Fireworks',
icon: FireworksIcon,
description: 'LLM calls',
placeholder: 'Enter your Fireworks API key',
},
{
id: 'firecrawl',
name: 'Firecrawl',
Expand Down
9 changes: 8 additions & 1 deletion apps/sim/blocks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ export function getModelOptions() {
const ollamaModels = providersState.providers.ollama.models
const vllmModels = providersState.providers.vllm.models
const openrouterModels = providersState.providers.openrouter.models
const fireworksModels = providersState.providers.fireworks.models
const allModels = Array.from(
new Set([...baseModels, ...ollamaModels, ...vllmModels, ...openrouterModels])
new Set([
...baseModels,
...ollamaModels,
...vllmModels,
...openrouterModels,
...fireworksModels,
])
)

return allModels.map((model) => {
Expand Down
19 changes: 19 additions & 0 deletions apps/sim/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3420,6 +3420,25 @@ export function MySQLIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function FireworksIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
viewBox='0 0 512 512'
xmlns='http://www.w3.org/2000/svg'
fillRule='evenodd'
clipRule='evenodd'
strokeLinejoin='round'
strokeMiterlimit={2}
>
<path
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'
fill='currentColor'
/>
</svg>
)
}

export function OpenRouterIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
Expand Down
20 changes: 14 additions & 6 deletions apps/sim/hooks/queries/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const providerEndpoints: Record<ProviderName, string> = {
ollama: '/api/providers/ollama/models',
vllm: '/api/providers/vllm/models',
openrouter: '/api/providers/openrouter/models',
fireworks: '/api/providers/fireworks/models',
}

interface ProviderModelsResponse {
Expand All @@ -18,14 +19,21 @@ interface ProviderModelsResponse {

export const providerKeys = {
all: ['provider-models'] as const,
models: (provider: string) => [...providerKeys.all, provider] as const,
models: (provider: string, workspaceId?: string) =>
[...providerKeys.all, provider, workspaceId ?? ''] as const,
}

async function fetchProviderModels(
provider: ProviderName,
signal?: AbortSignal
signal?: AbortSignal,
workspaceId?: string
): Promise<ProviderModelsResponse> {
const response = await fetch(providerEndpoints[provider], { signal })
let url = providerEndpoints[provider]
if (provider === 'fireworks' && workspaceId) {
url = `${url}?workspaceId=${encodeURIComponent(workspaceId)}`
}

const response = await fetch(url, { signal })

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

export function useProviderModels(provider: ProviderName) {
export function useProviderModels(provider: ProviderName, workspaceId?: string) {
return useQuery({
queryKey: providerKeys.models(provider),
queryFn: ({ signal }) => fetchProviderModels(provider, signal),
queryKey: providerKeys.models(provider, workspaceId),
queryFn: ({ signal }) => fetchProviderModels(provider, signal, workspaceId),
staleTime: 5 * 60 * 1000,
})
}
20 changes: 20 additions & 0 deletions apps/sim/lib/api-key/byok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ export async function getApiKeyWithBYOK(
return { apiKey: userProvidedKey || env.VLLM_API_KEY || 'empty', isBYOK: false }
}

const isFireworksModel =
provider === 'fireworks' ||
useProvidersStore.getState().providers.fireworks.models.includes(model)
if (isFireworksModel) {
if (workspaceId) {
const byokResult = await getBYOKKey(workspaceId, 'fireworks')
if (byokResult) {
logger.info('Using BYOK key for Fireworks', { model, workspaceId })
return byokResult
}
}
if (userProvidedKey) {
return { apiKey: userProvidedKey, isBYOK: false }
}
if (env.FIREWORKS_API_KEY) {
return { apiKey: env.FIREWORKS_API_KEY, isBYOK: false }
}
throw new Error(`API key is required for Fireworks ${model}`)
}

const isBedrockModel = provider === 'bedrock' || model.startsWith('bedrock/')
if (isBedrockModel) {
return { apiKey: 'bedrock-uses-own-credentials', isBYOK: false }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,14 +696,19 @@ function resolveAuthType(
/**
* Gets all available models from PROVIDER_DEFINITIONS as static options.
* This provides fallback data when store state is not available server-side.
* Excludes dynamic providers (ollama, vllm, openrouter) which require runtime fetching.
* Excludes dynamic providers (ollama, vllm, openrouter, fireworks) which require runtime fetching.
*/
function getStaticModelOptions(): { id: string; label?: string }[] {
const models: { id: string; label?: string }[] = []

for (const provider of Object.values(PROVIDER_DEFINITIONS)) {
// Skip providers with dynamic/fetched models
if (provider.id === 'ollama' || provider.id === 'vllm' || provider.id === 'openrouter') {
if (
provider.id === 'ollama' ||
provider.id === 'vllm' ||
provider.id === 'openrouter' ||
provider.id === 'fireworks'
) {
continue
}
if (provider?.models) {
Expand Down Expand Up @@ -737,6 +742,7 @@ function callOptionsWithFallback(
ollama: { models: [] },
vllm: { models: [] },
openrouter: { models: [] },
fireworks: { models: [] },
},
}

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/copilot/vfs/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ function getStaticModelOptionsForVFS(): Array<{
hosted: boolean
}> {
const hostedProviders = new Set(['openai', 'anthropic', 'google'])
const dynamicProviders = new Set(['ollama', 'vllm', 'openrouter'])
const dynamicProviders = new Set(['ollama', 'vllm', 'openrouter', 'fireworks'])

const models: Array<{ id: string; provider: string; hosted: boolean }> = []

Expand Down
1 change: 1 addition & 0 deletions apps/sim/lib/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const env = createEnv({
OLLAMA_URL: z.string().url().optional(), // Ollama local LLM server URL
VLLM_BASE_URL: z.string().url().optional(), // vLLM self-hosted base URL (OpenAI-compatible)
VLLM_API_KEY: z.string().optional(), // Optional bearer token for vLLM
FIREWORKS_API_KEY: z.string().optional(), // Optional Fireworks AI API key for model listing
ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for text-to-speech in deployed chat
SERPER_API_KEY: z.string().min(1).optional(), // Serper API key for online search
EXA_API_KEY: z.string().min(1).optional(), // Exa AI API key for enhanced online search
Expand Down
Loading
Loading