Skip to content

Commit 3c7fd57

Browse files
committed
address comments
1 parent dacdbe3 commit 3c7fd57

5 files changed

Lines changed: 154 additions & 37 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,13 @@ export const Sidebar = memo(function Sidebar() {
361361
const { config: permissionConfig, filterBlocks } = usePermissionConfig()
362362
const { navigateToSettings, getSettingsHref } = useSettingsNavigation()
363363
const initializeSearchData = useSearchModalStore((state) => state.initializeData)
364-
const providerModelSignature = useProvidersStore((state) =>
365-
Object.values(state.providers)
366-
.map((provider) => provider.models.join('\x00'))
367-
.join('\x01')
364+
const providers = useProvidersStore((state) => state.providers)
365+
const providerModelSignature = useMemo(
366+
() =>
367+
Object.values(providers)
368+
.map((provider) => provider.models.join('\x00'))
369+
.join('\x01'),
370+
[providers]
368371
)
369372

370373
useEffect(() => {

apps/sim/lib/tools/falai-pricing.ts

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1+
import { createLogger } from '@sim/logger'
2+
import { getErrorMessage } from '@sim/utils/errors'
13
import { sleep } from '@sim/utils/helpers'
24

35
export const FALAI_HOSTED_KEY_MARKUP_MULTIPLIER = 1.5
6+
export const FALAI_IMAGE_FALLBACK_PROVIDER_COST_DOLLARS = 0.05
7+
export const FALAI_VIDEO_FALLBACK_PROVIDER_COST_DOLLARS = 0.25
8+
const FALAI_BILLING_EVENT_ATTEMPTS = 2
9+
const FALAI_BILLING_EVENT_RETRY_MS = 500
10+
const logger = createLogger('FalAIPricing')
411

512
export interface FalAICostMetadata {
613
endpointId: string
714
requestId: string
815
costDollars: number
9-
source: 'billing_events' | 'historical_estimate'
16+
source: 'billing_events' | 'historical_estimate' | 'fallback_floor'
1017
outputUnits?: number | null
1118
unitPrice?: number | null
1219
percentDiscount?: number | null
1320
currency?: string
21+
error?: string
1422
}
1523

1624
interface FalAIBillingEvent {
@@ -30,6 +38,20 @@ function getNumber(value: unknown): number | undefined {
3038
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
3139
}
3240

41+
function getFalAIFallbackProviderCostDollars(endpointId: string): number {
42+
const normalizedEndpointId = endpointId.toLowerCase()
43+
const isImageEndpoint =
44+
normalizedEndpointId.includes('image') ||
45+
normalizedEndpointId.includes('nano-banana') ||
46+
normalizedEndpointId.includes('seedream') ||
47+
normalizedEndpointId.includes('flux') ||
48+
normalizedEndpointId.includes('grok-imagine')
49+
50+
return isImageEndpoint
51+
? FALAI_IMAGE_FALLBACK_PROVIDER_COST_DOLLARS
52+
: FALAI_VIDEO_FALLBACK_PROVIDER_COST_DOLLARS
53+
}
54+
3355
function parseBillingEvent(value: unknown): FalAIBillingEvent | undefined {
3456
if (!isRecord(value)) return undefined
3557

@@ -58,49 +80,72 @@ async function fetchFalAIBillingEvent(
5880
url.searchParams.set('request_id', requestId)
5981
url.searchParams.set('limit', '1')
6082

61-
const response = await fetch(url, {
62-
headers: {
63-
Authorization: `Key ${apiKey}`,
64-
},
65-
})
83+
let response: Response
84+
try {
85+
response = await fetch(url, {
86+
headers: {
87+
Authorization: `Key ${apiKey}`,
88+
},
89+
})
90+
} catch (error) {
91+
logger.warn('Failed to fetch Fal.ai billing event', {
92+
requestId,
93+
error: getErrorMessage(error, 'Unknown error'),
94+
})
95+
return undefined
96+
}
6697

6798
if (!response.ok) return undefined
6899

69-
const data = (await response.json()) as unknown
100+
const data = await response.json().catch((error) => {
101+
logger.warn('Failed to parse Fal.ai billing event response', {
102+
requestId,
103+
error: getErrorMessage(error, 'Unknown error'),
104+
})
105+
return undefined
106+
})
70107
if (!isRecord(data) || !Array.isArray(data.billing_events)) return undefined
71108

72109
return data.billing_events.map(parseBillingEvent).find(Boolean)
73110
}
74111

75-
async function estimateFalAICallCost(apiKey: string, endpointId: string): Promise<number> {
76-
const response = await fetch('https://api.fal.ai/v1/models/pricing/estimate', {
77-
method: 'POST',
78-
headers: {
79-
Authorization: `Key ${apiKey}`,
80-
'Content-Type': 'application/json',
81-
},
82-
body: JSON.stringify({
83-
estimate_type: 'historical_api_price',
84-
endpoints: {
85-
[endpointId]: {
86-
call_quantity: 1,
87-
},
112+
async function estimateFalAICallCost(
113+
apiKey: string,
114+
endpointId: string
115+
): Promise<{ costDollars?: number; error?: string }> {
116+
let response: Response
117+
try {
118+
response = await fetch('https://api.fal.ai/v1/models/pricing/estimate', {
119+
method: 'POST',
120+
headers: {
121+
Authorization: `Key ${apiKey}`,
122+
'Content-Type': 'application/json',
88123
},
89-
}),
90-
})
124+
body: JSON.stringify({
125+
estimate_type: 'historical_api_price',
126+
endpoints: {
127+
[endpointId]: {
128+
call_quantity: 1,
129+
},
130+
},
131+
}),
132+
})
133+
} catch (error) {
134+
return { error: getErrorMessage(error, 'Unknown error') }
135+
}
91136

92137
if (!response.ok) {
93138
const error = await response.text().catch(() => '')
94-
throw new Error(`Fal.ai pricing estimate failed: ${response.status} ${error}`)
139+
return { error: `Fal.ai pricing estimate failed: ${response.status} ${error}` }
95140
}
96141

97142
const data = (await response.json()) as unknown
98143
const totalCost = isRecord(data) ? getNumber(data.total_cost) : undefined
99144
if (totalCost === undefined) {
100-
throw new Error('Fal.ai pricing estimate missing total_cost')
145+
return { error: 'Fal.ai pricing estimate missing total_cost' }
101146
}
102147

103-
return totalCost
148+
return { costDollars: totalCost }
104149
}
105150

106151
export async function getFalAICostMetadata({
@@ -112,7 +157,7 @@ export async function getFalAICostMetadata({
112157
endpointId: string
113158
requestId: string
114159
}): Promise<FalAICostMetadata> {
115-
for (let attempt = 0; attempt < 5; attempt++) {
160+
for (let attempt = 0; attempt < FALAI_BILLING_EVENT_ATTEMPTS; attempt++) {
116161
const event = await fetchFalAIBillingEvent(apiKey, requestId)
117162
if (event) {
118163
return {
@@ -127,14 +172,34 @@ export async function getFalAICostMetadata({
127172
}
128173
}
129174

130-
await sleep(1000)
175+
if (attempt < FALAI_BILLING_EVENT_ATTEMPTS - 1) {
176+
await sleep(FALAI_BILLING_EVENT_RETRY_MS)
177+
}
178+
}
179+
180+
const estimate = await estimateFalAICallCost(apiKey, endpointId)
181+
if (estimate.costDollars !== undefined) {
182+
return {
183+
endpointId,
184+
requestId,
185+
costDollars: estimate.costDollars,
186+
source: 'historical_estimate',
187+
currency: 'USD',
188+
}
131189
}
132190

191+
logger.warn('Fal.ai cost metadata unavailable after generation completed', {
192+
endpointId,
193+
requestId,
194+
error: estimate.error,
195+
})
196+
133197
return {
134198
endpointId,
135199
requestId,
136-
costDollars: await estimateFalAICallCost(apiKey, endpointId),
137-
source: 'historical_estimate',
200+
costDollars: getFalAIFallbackProviderCostDollars(endpointId),
201+
source: 'fallback_floor',
138202
currency: 'USD',
203+
error: estimate.error,
139204
}
140205
}

apps/sim/tools/falai-hosting.test.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
/**
22
* @vitest-environment node
33
*/
4-
import { describe, expect, it } from 'vitest'
5-
import { FALAI_HOSTED_KEY_MARKUP_MULTIPLIER } from '@/lib/tools/falai-pricing'
4+
import { afterEach, describe, expect, it, vi } from 'vitest'
5+
import {
6+
FALAI_HOSTED_KEY_MARKUP_MULTIPLIER,
7+
FALAI_IMAGE_FALLBACK_PROVIDER_COST_DOLLARS,
8+
getFalAICostMetadata,
9+
} from '@/lib/tools/falai-pricing'
610
import { imageGenerateTool } from '@/tools/image/generate'
711
import { falaiVideoTool } from '@/tools/video/falai'
812

13+
afterEach(() => {
14+
vi.useRealTimers()
15+
vi.unstubAllGlobals()
16+
})
17+
918
describe('Fal.ai hosted key pricing', () => {
1019
it('applies hosted markup to image generation provider cost', () => {
1120
const pricing = imageGenerateTool.hosting?.pricing
@@ -58,4 +67,42 @@ describe('Fal.ai hosted key pricing', () => {
5867
source: 'billing_events',
5968
})
6069
})
70+
71+
it('returns fallback floor cost metadata instead of throwing when billing and estimate fail', async () => {
72+
vi.useFakeTimers()
73+
74+
const mockFetch = vi
75+
.fn()
76+
.mockResolvedValueOnce(
77+
new Response(JSON.stringify({ billing_events: [] }), {
78+
status: 200,
79+
headers: { 'Content-Type': 'application/json' },
80+
})
81+
)
82+
.mockResolvedValueOnce(
83+
new Response(JSON.stringify({ billing_events: [] }), {
84+
status: 200,
85+
headers: { 'Content-Type': 'application/json' },
86+
})
87+
)
88+
.mockResolvedValueOnce(new Response('pricing unavailable', { status: 500 }))
89+
vi.stubGlobal('fetch', mockFetch)
90+
91+
const resultPromise = getFalAICostMetadata({
92+
apiKey: 'fal-key',
93+
endpointId: 'fal-ai/nano-banana-2',
94+
requestId: 'request-1',
95+
})
96+
97+
await vi.advanceTimersByTimeAsync(500)
98+
const result = await resultPromise
99+
100+
expect(result).toMatchObject({
101+
endpointId: 'fal-ai/nano-banana-2',
102+
requestId: 'request-1',
103+
costDollars: FALAI_IMAGE_FALLBACK_PROVIDER_COST_DOLLARS,
104+
source: 'fallback_floor',
105+
currency: 'USD',
106+
})
107+
})
61108
})

apps/sim/tools/image/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ export interface ImageGenerationResponse extends ToolResponse {
4040
__falaiBilling?: {
4141
endpointId: string
4242
requestId: string
43-
source: 'billing_events' | 'historical_estimate'
43+
source: 'billing_events' | 'historical_estimate' | 'fallback_floor'
4444
outputUnits?: number | null
4545
unitPrice?: number | null
4646
percentDiscount?: number | null
4747
currency?: string
48+
error?: string
4849
}
4950
}
5051
}

apps/sim/tools/video/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ export interface VideoResponse extends ToolResponse {
3737
__falaiBilling?: {
3838
endpointId: string
3939
requestId: string
40-
source: 'billing_events' | 'historical_estimate'
40+
source: 'billing_events' | 'historical_estimate' | 'fallback_floor'
4141
outputUnits?: number | null
4242
unitPrice?: number | null
4343
percentDiscount?: number | null
4444
currency?: string
45+
error?: string
4546
}
4647
}
4748
}

0 commit comments

Comments
 (0)