@@ -6,6 +6,20 @@ import { getErrorMessage } from "@/lib/utils";
66import type { FalCredentials } from "../credentials" ;
77
88const FAL_API_URL = "https://queue.fal.run" ;
9+ const POLL_INTERVAL_MS = 2000 ;
10+ const MAX_POLL_ATTEMPTS = 300 ; // 10 minutes max for video
11+
12+ type FalQueueResponse = {
13+ status : "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED" ;
14+ request_id : string ;
15+ response_url : string ;
16+ status_url : string ;
17+ } ;
18+
19+ type FalStatusResponse = {
20+ status : "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED" ;
21+ response_url ?: string ;
22+ } ;
923
1024type FalVideoResponse = {
1125 video ?: {
@@ -29,6 +43,49 @@ export type FalGenerateVideoInput = StepInput &
2943 integrationId ?: string ;
3044 } ;
3145
46+ /**
47+ * Poll fal.ai queue until the request is completed
48+ */
49+ async function pollForResult (
50+ statusUrl : string ,
51+ responseUrl : string ,
52+ apiKey : string
53+ ) : Promise < FalVideoResponse > {
54+ for ( let attempt = 0 ; attempt < MAX_POLL_ATTEMPTS ; attempt ++ ) {
55+ const statusResponse = await fetch ( statusUrl , {
56+ method : "GET" ,
57+ headers : {
58+ Authorization : `Key ${ apiKey } ` ,
59+ } ,
60+ } ) ;
61+
62+ if ( ! statusResponse . ok ) {
63+ throw new Error ( `Failed to check status: HTTP ${ statusResponse . status } ` ) ;
64+ }
65+
66+ const status = ( await statusResponse . json ( ) ) as FalStatusResponse ;
67+
68+ if ( status . status === "COMPLETED" ) {
69+ const resultResponse = await fetch ( responseUrl , {
70+ method : "GET" ,
71+ headers : {
72+ Authorization : `Key ${ apiKey } ` ,
73+ } ,
74+ } ) ;
75+
76+ if ( ! resultResponse . ok ) {
77+ throw new Error ( `Failed to fetch result: HTTP ${ resultResponse . status } ` ) ;
78+ }
79+
80+ return ( await resultResponse . json ( ) ) as FalVideoResponse ;
81+ }
82+
83+ await new Promise ( ( resolve ) => setTimeout ( resolve , POLL_INTERVAL_MS ) ) ;
84+ }
85+
86+ throw new Error ( "Request timed out waiting for fal.ai to complete" ) ;
87+ }
88+
3289/**
3390 * Core logic - portable between app and export
3491 */
@@ -45,7 +102,6 @@ async function stepHandler(
45102 try {
46103 const model = input . model || "fal-ai/minimax-video" ;
47104
48- // Build request body based on whether it's text-to-video or image-to-video
49105 const requestBody : Record < string , unknown > = {
50106 prompt : input . prompt ,
51107 } ;
@@ -68,7 +124,18 @@ async function stepHandler(
68124 throw new Error ( `HTTP ${ response . status } : ${ errorText } ` ) ;
69125 }
70126
71- const result = ( await response . json ( ) ) as FalVideoResponse ;
127+ const queueResponse = ( await response . json ( ) ) as FalQueueResponse ;
128+
129+ let result : FalVideoResponse ;
130+ if ( queueResponse . status === "IN_QUEUE" || queueResponse . status === "IN_PROGRESS" ) {
131+ result = await pollForResult (
132+ queueResponse . status_url ,
133+ queueResponse . response_url ,
134+ apiKey
135+ ) ;
136+ } else {
137+ result = queueResponse as unknown as FalVideoResponse ;
138+ }
72139
73140 if ( result . error ) {
74141 throw new Error ( result . error ) ;
@@ -100,5 +167,6 @@ export async function falGenerateVideoStep(
100167
101168 return withStepLogging ( input , ( ) => stepHandler ( input , credentials ) ) ;
102169}
170+ falGenerateVideoStep . maxRetries = 0 ;
103171
104172export const _integrationType = "fal" ;
0 commit comments