1+ import { createLogger } from '@sim/logger'
2+ import { getErrorMessage } from '@sim/utils/errors'
13import { sleep } from '@sim/utils/helpers'
24
35export 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
512export 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
1624interface 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+
3355function 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
106151export 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}
0 commit comments