Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
93 changes: 93 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,93 @@
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 { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
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 permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
if (permission) {
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',
},
cache: 'no-store',
})

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