@@ -19,10 +19,12 @@ import {
1919import { MAX_REMOTE_IMAGE_BYTES } from '@/lib/internal/image/fetch'
2020import { buildImageGenerationRequest } from '@/lib/internal/image/request'
2121import type { ImageGenerationInput , ImageProvider } from '@/lib/internal/image/schema'
22+ import { FalAIResultError , fetchFalAIResult } from '@/lib/media/falai-result'
2223import { recordServiceMeteringFailure } from '@/lib/mothership/billing/service-observer'
23- import { type FalAICostMetadata , getFalAICostMetadata } from '@/lib/tools/falai-pricing'
24+ import type { FalAICostMetadata } from '@/lib/tools/falai-pricing'
2425import { uploadCopilotFile } from '@/lib/uploads/contexts/copilot'
2526import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
27+ import { MAX_BUFFERED_TRANSFER_BYTES } from '@/lib/uploads/shared/types'
2628import { resolveStoredFileMetadata } from '@/lib/uploads/utils/stored-file-metadata'
2729
2830const logger = createLogger ( 'ImageOperations' )
@@ -78,7 +80,7 @@ export async function executeImageGeneration(
7880 context : ImageOperationContext
7981) : Promise < Response > {
8082 const { requestId } = context
81- let completedFalCost : FalAICostMetadata | undefined
83+ let undeliveredFalCost : FalAICostMetadata | undefined
8284 logger . info ( `[${ requestId } ] Image generation request started` )
8385
8486 try {
@@ -108,7 +110,7 @@ export async function executeImageGeneration(
108110 requestId ,
109111 logger ,
110112 ( cost ) => {
111- completedFalCost = cost
113+ undeliveredFalCost = cost
112114 } ,
113115 context . signal
114116 )
@@ -126,16 +128,16 @@ export async function executeImageGeneration(
126128
127129 return Response . json ( storedImage )
128130 } catch ( error ) {
129- if ( completedFalCost ) {
130- logger . warn ( 'Hosted fal image completed but delivery failed ' , {
131+ if ( undeliveredFalCost ) {
132+ logger . warn ( 'Hosted fal image result delivery interrupted ' , {
131133 requestId,
132- falaiBilling : completedFalCost ,
134+ falaiBilling : undeliveredFalCost ,
133135 } )
134- const { requestId : jobId , endpointId, costDollars } = completedFalCost
136+ const { requestId : jobId , endpointId, costDollars } = undeliveredFalCost
135137 await recordServiceMeteringFailure (
136- `Completed fal image ${ jobId } (${ endpointId } ): USD ${ costDollars } ; delivery failed `
138+ `Undelivered fal image ${ jobId } (${ endpointId } ): USD ${ costDollars } ( ${ undeliveredFalCost . source } ); reconciliation required `
137139 ) . catch ( ( meteringError ) => {
138- logger . error ( 'Failed to record completed fal image for reconciliation' , {
140+ logger . error ( 'Failed to record undelivered fal image for reconciliation' , {
139141 requestId,
140142 error : getErrorMessage ( meteringError ) ,
141143 } )
@@ -146,7 +148,14 @@ export async function executeImageGeneration(
146148 const errorMessage = getUserFacingErrorMessage ( error , 'Unknown error' )
147149 return Response . json (
148150 { error : errorMessage } ,
149- { status : isPayloadSizeLimitError ( error ) ? 413 : 500 }
151+ {
152+ status :
153+ error instanceof FalAIResultError
154+ ? error . status
155+ : isPayloadSizeLimitError ( error )
156+ ? 413
157+ : 500 ,
158+ }
150159 )
151160 }
152161}
@@ -207,7 +216,7 @@ async function bufferFromImageUrl(
207216 const imageResponse = await secureFetchWithPinnedIP ( url , urlValidation . resolvedIP , {
208217 profile : 'contentFetch' ,
209218 method : 'GET' ,
210- maxResponseBytes : MAX_IMAGE_BYTES ,
219+ maxResponseBytes : MAX_BUFFERED_TRANSFER_BYTES ,
211220 signal,
212221 } )
213222 if ( ! imageResponse . ok ) {
@@ -221,7 +230,7 @@ async function bufferFromImageUrl(
221230
222231 const contentType = imageResponse . headers . get ( 'content-type' ) || 'image/png'
223232 const buffer = await readResponseToBufferWithLimit ( imageResponse , {
224- maxBytes : MAX_IMAGE_BYTES ,
233+ maxBytes : MAX_BUFFERED_TRANSFER_BYTES ,
225234 label : 'generated image download' ,
226235 signal,
227236 } )
@@ -505,41 +514,30 @@ async function generateWithFalAI(
505514 throw new Error ( `Fal.ai generation failed: ${ getFalAIErrorMessage ( statusError ) } ` )
506515 }
507516
508- const falaiCost = body . useHostedCostTracking
509- ? await getFalAICostMetadata ( { apiKey, endpointId : endpoint , requestId : falRequestId } )
510- : undefined
511- if ( falaiCost ) onFalCost ( falaiCost )
512- signal ?. throwIfAborted ( )
513-
514- const resultUrl = falQueueUrl ( getStringProperty ( statusData , 'response_url' ) || responseUrl )
515- const resultValidation = await validateUrlWithDNS ( resultUrl , 'resultUrl' , 'contentFetch' )
516- if ( ! resultValidation . isValid ) {
517- throw new Error ( resultValidation . error )
518- }
519- const resultResponse = await secureFetchWithPinnedIP ( resultUrl , resultValidation . resolvedIP , {
520- profile : 'contentFetch' ,
521- maxRedirects : 0 ,
522- headers : {
523- Authorization : `Key ${ apiKey } ` ,
524- } ,
517+ const { data : resultData , cost : falaiCost } = await fetchFalAIResult ( {
518+ apiKey,
519+ endpointId : endpoint ,
520+ requestId : falRequestId ,
525521 maxResponseBytes : MAX_IMAGE_JSON_BYTES ,
522+ trackCost : body . useHostedCostTracking ,
523+ onCost : onFalCost ,
526524 signal,
525+ fetchResponse : async ( ) => {
526+ const resultUrl = falQueueUrl (
527+ getStringProperty ( statusData , 'response_url' ) || responseUrl
528+ )
529+ const resultValidation = await validateUrlWithDNS ( resultUrl , 'resultUrl' , 'contentFetch' )
530+ if ( ! resultValidation . isValid ) throw new Error ( resultValidation . error )
531+ return secureFetchWithPinnedIP ( resultUrl , resultValidation . resolvedIP , {
532+ profile : 'contentFetch' ,
533+ maxRedirects : 0 ,
534+ headers : { Authorization : `Key ${ apiKey } ` } ,
535+ maxResponseBytes : MAX_IMAGE_JSON_BYTES ,
536+ signal,
537+ } )
538+ } ,
527539 } )
528540
529- if ( ! resultResponse . ok ) {
530- await readResponseTextWithLimit ( resultResponse , {
531- maxBytes : DEFAULT_MAX_ERROR_BODY_BYTES ,
532- label : 'Fal.ai result error response' ,
533- signal,
534- } ) . catch ( ( ) => '' )
535- throw new Error ( `Failed to fetch Fal.ai result: ${ resultResponse . status } ` )
536- }
537-
538- const resultData = await readResponseJsonWithLimit ( resultResponse , {
539- maxBytes : MAX_IMAGE_JSON_BYTES ,
540- label : 'Fal.ai result response' ,
541- signal,
542- } )
543541 if ( ! isRecordLike ( resultData ) ) {
544542 throw new Error ( 'Invalid Fal.ai result response' )
545543 }
0 commit comments