( endpoint: string, input: Record<string, unknown>, apiKey: string )
| 81 | * result JSON. Shared by the video and audio generators. |
| 82 | */ |
| 83 | export async function runFalQueue( |
| 84 | endpoint: string, |
| 85 | input: Record<string, unknown>, |
| 86 | apiKey: string |
| 87 | ): Promise<FalQueueResult> { |
| 88 | const createResponse = await fetch(`https://queue.fal.run/${endpoint}`, { |
| 89 | method: 'POST', |
| 90 | headers: { Authorization: `Key ${apiKey}`, 'Content-Type': 'application/json' }, |
| 91 | body: JSON.stringify(input), |
| 92 | }) |
| 93 | if (!createResponse.ok) { |
| 94 | const err = await readResponseTextWithLimit(createResponse, { |
| 95 | maxBytes: DEFAULT_MAX_ERROR_BODY_BYTES, |
| 96 | label: 'Fal.ai create error response', |
| 97 | }).catch(() => '') |
| 98 | throw new Error(`Fal.ai API error: ${createResponse.status} - ${err}`) |
| 99 | } |
| 100 | |
| 101 | const createData = await readResponseJsonWithLimit(createResponse, { |
| 102 | maxBytes: MAX_MEDIA_JSON_BYTES, |
| 103 | label: 'Fal.ai create response', |
| 104 | }) |
| 105 | if (!isRecordLike(createData)) throw new Error('Invalid Fal.ai queue response') |
| 106 | |
| 107 | const requestId = getStringProp(createData, 'request_id') |
| 108 | if (!requestId) throw new Error('Fal.ai queue response missing request_id') |
| 109 | |
| 110 | const statusUrl = |
| 111 | getStringProp(createData, 'status_url') || falQueueUrl(endpoint, requestId, 'status') |
| 112 | const responseUrl = |
| 113 | getStringProp(createData, 'response_url') || falQueueUrl(endpoint, requestId, 'response') |
| 114 | |
| 115 | const maxAttempts = Math.ceil(getMaxExecutionTimeout() / POLL_INTERVAL_MS) |
| 116 | for (let attempt = 0; attempt < maxAttempts; attempt++) { |
| 117 | await sleep(POLL_INTERVAL_MS) |
| 118 | |
| 119 | const statusResponse = await fetch(statusUrl, { headers: { Authorization: `Key ${apiKey}` } }) |
| 120 | if (!statusResponse.ok) { |
| 121 | const body = await readResponseTextWithLimit(statusResponse, { |
| 122 | maxBytes: DEFAULT_MAX_ERROR_BODY_BYTES, |
| 123 | label: 'Fal.ai status error response', |
| 124 | }).catch(() => '') |
| 125 | throw new Error( |
| 126 | `Fal.ai status check failed: ${statusResponse.status}${body ? ` - ${body}` : ''}` |
| 127 | ) |
| 128 | } |
| 129 | |
| 130 | const statusData = await readResponseJsonWithLimit(statusResponse, { |
| 131 | maxBytes: MAX_MEDIA_JSON_BYTES, |
| 132 | label: 'Fal.ai status response', |
| 133 | }) |
| 134 | if (!isRecordLike(statusData)) throw new Error('Invalid Fal.ai status response') |
| 135 | |
| 136 | const status = getStringProp(statusData, 'status') |
| 137 | if (status === 'COMPLETED') { |
| 138 | if (statusData.error) { |
| 139 | throw new Error(`Fal.ai generation failed: ${falErrorMessage(statusData.error)}`) |
| 140 | } |
no test coverage detected