@howells/motif-sdk 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -81,62 +81,80 @@ var ASPECT_RATIOS = [
81
81
  var RESOLUTIONS = ["0.5K", "1K", "2K", "4K"];
82
82
  function aspectToGptSize(aspect) {
83
83
  switch (aspect) {
84
- case "auto":
84
+ case "auto": {
85
85
  return "auto";
86
+ }
86
87
  case "9:16":
87
88
  case "1:8":
88
89
  case "1:4":
89
90
  case "2:3":
90
91
  case "4:5":
91
- case "3:4":
92
+ case "3:4": {
92
93
  return "1024x1536";
94
+ }
93
95
  case "16:9":
94
96
  case "8:1":
95
97
  case "4:1":
96
98
  case "3:2":
97
99
  case "5:4":
98
100
  case "4:3":
99
- case "21:9":
101
+ case "21:9": {
100
102
  return "1536x1024";
101
- default:
103
+ }
104
+ case "1:1": {
102
105
  return "1024x1024";
106
+ }
107
+ default: {
108
+ return "1024x1024";
109
+ }
103
110
  }
104
111
  }
105
112
  function aspectToFalImageSize(aspect) {
106
113
  switch (aspect) {
107
- case "auto":
114
+ case "auto": {
108
115
  return "auto";
116
+ }
109
117
  case "16:9":
110
118
  case "21:9":
111
119
  case "4:1":
112
- case "8:1":
120
+ case "8:1": {
113
121
  return "landscape_16_9";
122
+ }
114
123
  case "3:2":
115
- case "5:4":
124
+ case "5:4": {
116
125
  return "landscape_4_3";
117
- case "4:3":
126
+ }
127
+ case "4:3": {
118
128
  return "landscape_4_3";
129
+ }
119
130
  case "9:16":
120
131
  case "1:4":
121
- case "1:8":
132
+ case "1:8": {
122
133
  return "portrait_16_9";
134
+ }
123
135
  case "2:3":
124
- case "4:5":
136
+ case "4:5": {
125
137
  return "portrait_4_3";
126
- case "3:4":
138
+ }
139
+ case "3:4": {
127
140
  return "portrait_4_3";
128
- default:
141
+ }
142
+ case "1:1": {
143
+ return "square_hd";
144
+ }
145
+ default: {
129
146
  return "square_hd";
147
+ }
130
148
  }
131
149
  }
132
150
  var FORMAT_PRESETS = {
133
- cover: { label: "Cover", aspect: "2:3", resolution: "2K" },
134
- square: { label: "Square", aspect: "1:1" },
135
- landscape: { label: "Landscape", aspect: "16:9" },
136
- portrait: { label: "Portrait", aspect: "2:3" },
137
- story: { label: "Story", aspect: "9:16" },
138
- og: { label: "OG Image", aspect: "16:9" },
139
- wide: { label: "Wide", aspect: "21:9" }
151
+ cover: { aspect: "2:3", label: "Cover", resolution: "2K" },
152
+ landscape: { aspect: "16:9", label: "Landscape" },
153
+ og: { aspect: "16:9", label: "OG Image" },
154
+ portrait: { aspect: "2:3", label: "Portrait" },
155
+ square: { aspect: "1:1", label: "Square" },
156
+ story: { aspect: "9:16", label: "Story" },
157
+ wide: { aspect: "21:9", label: "Wide" }
140
158
  };
141
159
 
142
160
  // src/models.ts
@@ -150,311 +168,311 @@ var FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
150
168
  var MODELS = {
151
169
  // ─── Generation Models ────────────────────────────────────────
152
170
  gpt2: {
153
- name: "GPT Image 2",
154
- endpoint: "openai/gpt-image-2",
155
- editEndpoint: "openai/gpt-image-2/image-to-image",
156
- type: "generation",
157
- pricing: "$0.211",
158
- pricePerImageUsd: 0.211,
159
- falPricing: {
160
- endpointId: "openai/gpt-image-2",
161
- unitPrice: 1,
162
- unit: "units",
163
- currency: "USD",
164
- estimatedCostPerImageUsd: 0.211,
165
- source: "fal-pricing-api",
166
- checkedAt: FAL_PRICING_CHECKED_AT
167
- },
168
171
  benchmark: {
169
- useCase: "Highest-ranked text-to-image quality and transparent PNGs",
170
- tiers: { quality: "frontier", speed: "very_slow", price: "ultra" },
172
+ artificialAnalysis: {
173
+ editing: { elo: 1249, pricePer1k: 211, rank: 3, winRate: 0.66 },
174
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
175
+ sourceUrls: AA_IMAGE_SOURCES,
176
+ textToImage: { elo: 1337, pricePer1k: 211, rank: 1, winRate: 0.8 }
177
+ },
171
178
  speed: {
172
179
  medianSeconds: 200.7,
173
180
  p95Seconds: 232.6,
174
181
  source: "artificial-analysis-models"
175
182
  },
176
- artificialAnalysis: {
177
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
178
- sourceUrls: AA_IMAGE_SOURCES,
179
- textToImage: { rank: 1, elo: 1337, winRate: 0.8, pricePer1k: 211 },
180
- editing: { rank: 3, elo: 1249, winRate: 0.66, pricePer1k: 211 }
181
- }
183
+ tiers: { price: "ultra", quality: "frontier", speed: "very_slow" },
184
+ useCase: "Highest-ranked text-to-image quality and transparent PNGs"
185
+ },
186
+ editEndpoint: "openai/gpt-image-2/image-to-image",
187
+ endpoint: "openai/gpt-image-2",
188
+ falPricing: {
189
+ checkedAt: FAL_PRICING_CHECKED_AT,
190
+ currency: "USD",
191
+ endpointId: "openai/gpt-image-2",
192
+ estimatedCostPerImageUsd: 0.211,
193
+ source: "fal-pricing-api",
194
+ unit: "units",
195
+ unitPrice: 1
182
196
  },
197
+ maxReferenceImages: 4,
198
+ name: "GPT Image 2",
199
+ pricePerImageUsd: 0.211,
200
+ pricing: "$0.211",
183
201
  sizeMode: "image_size_enum",
184
202
  supportsAspect: true,
185
- supportsResolution: false,
186
203
  supportsEdit: true,
204
+ supportsMaskImage: true,
187
205
  supportsNumImages: true,
188
206
  supportsOutputFormat: true,
189
207
  supportsQuality: true,
190
- supportsMaskImage: true,
208
+ supportsResolution: false,
191
209
  supportsSyncMode: true,
192
- maxReferenceImages: 4,
210
+ type: "generation",
193
211
  useQueue: true
194
212
  },
195
213
  gpt: {
196
- name: "GPT Image 1.5",
197
- endpoint: "fal-ai/gpt-image-1.5",
198
- editEndpoint: "fal-ai/gpt-image-1.5/edit",
199
- type: "generation",
200
- pricing: "$0.133",
201
- pricePerImageUsd: 0.133,
202
- falPricing: {
203
- endpointId: "fal-ai/gpt-image-1.5",
204
- unitPrice: 1,
205
- unit: "units",
206
- currency: "USD",
207
- estimatedCostPerImageUsd: 0.133,
208
- source: "fal-pricing-api",
209
- checkedAt: FAL_PRICING_CHECKED_AT
210
- },
211
214
  benchmark: {
212
- useCase: "Best OpenAI edit quality with transparent PNG support",
213
- tiers: { quality: "frontier", speed: "slow", price: "ultra" },
215
+ artificialAnalysis: {
216
+ editing: { elo: 1262, pricePer1k: 133, rank: 2, winRate: 0.69 },
217
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
218
+ sourceUrls: AA_IMAGE_SOURCES,
219
+ textToImage: { elo: 1268, pricePer1k: 133, rank: 2, winRate: 0.73 }
220
+ },
214
221
  speed: {
215
222
  medianSeconds: 34.6,
216
223
  p95Seconds: 38.3,
217
224
  source: "artificial-analysis-models"
218
225
  },
219
- artificialAnalysis: {
220
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
221
- sourceUrls: AA_IMAGE_SOURCES,
222
- textToImage: { rank: 2, elo: 1268, winRate: 0.73, pricePer1k: 133 },
223
- editing: { rank: 2, elo: 1262, winRate: 0.69, pricePer1k: 133 }
224
- }
226
+ tiers: { price: "ultra", quality: "frontier", speed: "slow" },
227
+ useCase: "Best OpenAI edit quality with transparent PNG support"
228
+ },
229
+ editEndpoint: "fal-ai/gpt-image-1.5/edit",
230
+ endpoint: "fal-ai/gpt-image-1.5",
231
+ falPricing: {
232
+ checkedAt: FAL_PRICING_CHECKED_AT,
233
+ currency: "USD",
234
+ endpointId: "fal-ai/gpt-image-1.5",
235
+ estimatedCostPerImageUsd: 0.133,
236
+ source: "fal-pricing-api",
237
+ unit: "units",
238
+ unitPrice: 1
225
239
  },
240
+ maxReferenceImages: 4,
241
+ name: "GPT Image 1.5",
242
+ pricePerImageUsd: 0.133,
243
+ pricing: "$0.133",
226
244
  sizeMode: "gpt_size",
227
245
  supportsAspect: false,
228
246
  supportsBackground: true,
229
- supportsResolution: false,
230
247
  supportsEdit: true,
248
+ supportsMaskImage: true,
231
249
  supportsNumImages: true,
232
250
  supportsOutputFormat: true,
233
251
  supportsQuality: true,
234
- supportsMaskImage: true,
252
+ supportsResolution: false,
235
253
  supportsSyncMode: true,
236
- maxReferenceImages: 4
254
+ type: "generation"
237
255
  },
238
256
  banana2: {
239
- name: "Nano Banana 2",
240
- endpoint: "fal-ai/nano-banana-2",
257
+ benchmark: {
258
+ artificialAnalysis: {
259
+ editing: { elo: 1231, pricePer1k: 67, rank: 5, winRate: 0.63 },
260
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
261
+ sourceUrls: AA_IMAGE_SOURCES,
262
+ textToImage: { elo: 1263, pricePer1k: 67, rank: 3, winRate: 0.71 }
263
+ },
264
+ tiers: { price: "premium", quality: "frontier", speed: "unknown" },
265
+ useCase: "Best balance of quality, edit support, web search, and cost"
266
+ },
241
267
  editEndpoint: "fal-ai/nano-banana-2/edit",
242
- type: "generation",
243
- pricing: "$0.08",
244
- pricePerImageUsd: 0.08,
268
+ endpoint: "fal-ai/nano-banana-2",
245
269
  falPricing: {
246
- endpointId: "fal-ai/nano-banana-2",
247
- unitPrice: 0.08,
248
- unit: "images",
270
+ checkedAt: FAL_PRICING_CHECKED_AT,
249
271
  currency: "USD",
272
+ endpointId: "fal-ai/nano-banana-2",
250
273
  estimatedCostPerImageUsd: 0.08,
251
274
  source: "fal-pricing-api",
252
- checkedAt: FAL_PRICING_CHECKED_AT
253
- },
254
- benchmark: {
255
- useCase: "Best balance of quality, edit support, web search, and cost",
256
- tiers: { quality: "frontier", speed: "unknown", price: "premium" },
257
- artificialAnalysis: {
258
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
259
- sourceUrls: AA_IMAGE_SOURCES,
260
- textToImage: { rank: 3, elo: 1263, winRate: 0.71, pricePer1k: 67 },
261
- editing: { rank: 5, elo: 1231, winRate: 0.63, pricePer1k: 67 }
262
- }
275
+ unit: "images",
276
+ unitPrice: 0.08
263
277
  },
278
+ maxReferenceImages: 4,
279
+ name: "Nano Banana 2",
280
+ pricePerImageUsd: 0.08,
281
+ pricing: "$0.08",
264
282
  sizeMode: "aspect_ratio",
265
283
  supportsAspect: true,
266
- supportsResolution: true,
267
284
  supportsEdit: true,
285
+ supportsGoogleSearch: true,
286
+ supportsLimitGenerations: true,
268
287
  supportsNumImages: true,
269
288
  supportsOutputFormat: true,
289
+ supportsResolution: true,
270
290
  supportsSafetyTolerance: true,
271
291
  supportsSeed: true,
272
- supportsWebSearch: true,
273
- supportsGoogleSearch: true,
274
- supportsLimitGenerations: true,
275
292
  supportsSyncMode: true,
276
293
  supportsThinkingLevel: true,
277
- maxReferenceImages: 4
294
+ supportsWebSearch: true,
295
+ type: "generation"
278
296
  },
279
297
  banana: {
280
- name: "Nano Banana Pro",
281
- endpoint: "fal-ai/nano-banana-pro",
282
- editEndpoint: "fal-ai/nano-banana-pro/edit",
283
- type: "generation",
284
- pricing: "$0.15",
285
- pricePerImageUsd: 0.15,
286
- falPricing: {
287
- endpointId: "fal-ai/nano-banana-pro",
288
- unitPrice: 0.15,
289
- unit: "images",
290
- currency: "USD",
291
- estimatedCostPerImageUsd: 0.15,
292
- source: "fal-pricing-api",
293
- checkedAt: FAL_PRICING_CHECKED_AT
294
- },
295
298
  benchmark: {
296
- useCase: "Premium Gemini image generation and multi-reference edits",
297
- tiers: { quality: "frontier", speed: "balanced", price: "ultra" },
299
+ artificialAnalysis: {
300
+ editing: { elo: 1241, pricePer1k: 134, rank: 4, winRate: 0.65 },
301
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
302
+ sourceUrls: AA_IMAGE_SOURCES,
303
+ textToImage: { elo: 1220, pricePer1k: 134, rank: 5, winRate: 0.66 }
304
+ },
298
305
  speed: {
299
306
  medianSeconds: 19.2,
300
307
  p95Seconds: 30.3,
301
308
  source: "artificial-analysis-models"
302
309
  },
303
- artificialAnalysis: {
304
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
305
- sourceUrls: AA_IMAGE_SOURCES,
306
- textToImage: { rank: 5, elo: 1220, winRate: 0.66, pricePer1k: 134 },
307
- editing: { rank: 4, elo: 1241, winRate: 0.65, pricePer1k: 134 }
308
- }
310
+ tiers: { price: "ultra", quality: "frontier", speed: "balanced" },
311
+ useCase: "Premium Gemini image generation and multi-reference edits"
312
+ },
313
+ editEndpoint: "fal-ai/nano-banana-pro/edit",
314
+ endpoint: "fal-ai/nano-banana-pro",
315
+ falPricing: {
316
+ checkedAt: FAL_PRICING_CHECKED_AT,
317
+ currency: "USD",
318
+ endpointId: "fal-ai/nano-banana-pro",
319
+ estimatedCostPerImageUsd: 0.15,
320
+ source: "fal-pricing-api",
321
+ unit: "images",
322
+ unitPrice: 0.15
309
323
  },
324
+ maxReferenceImages: 14,
325
+ name: "Nano Banana Pro",
326
+ pricePerImageUsd: 0.15,
327
+ pricing: "$0.15",
310
328
  sizeMode: "aspect_ratio",
311
329
  supportsAspect: true,
312
- supportsResolution: true,
313
330
  supportsEdit: true,
331
+ supportsGoogleSearch: true,
332
+ supportsLimitGenerations: true,
314
333
  supportsNumImages: true,
315
334
  supportsOutputFormat: true,
335
+ supportsResolution: true,
316
336
  supportsSafetyTolerance: true,
317
337
  supportsSeed: true,
318
- supportsWebSearch: true,
319
- supportsGoogleSearch: true,
320
- supportsLimitGenerations: true,
321
338
  supportsSyncMode: true,
322
- maxReferenceImages: 14
339
+ supportsWebSearch: true,
340
+ type: "generation"
323
341
  },
324
342
  gemini: {
325
- name: "Gemini 2.5 Flash",
326
- endpoint: "fal-ai/gemini-25-flash-image",
327
343
  editEndpoint: "fal-ai/gemini-25-flash-image/edit",
328
- type: "generation",
329
- pricing: "$0.04",
330
- pricePerImageUsd: 0.0398,
344
+ endpoint: "fal-ai/gemini-25-flash-image",
331
345
  falPricing: {
332
- endpointId: "fal-ai/gemini-25-flash-image",
333
- unitPrice: 0.0398,
334
- unit: "images",
346
+ checkedAt: FAL_PRICING_CHECKED_AT,
335
347
  currency: "USD",
348
+ endpointId: "fal-ai/gemini-25-flash-image",
336
349
  estimatedCostPerImageUsd: 0.0398,
337
350
  source: "fal-pricing-api",
338
- checkedAt: FAL_PRICING_CHECKED_AT
351
+ unit: "images",
352
+ unitPrice: 0.0398
339
353
  },
354
+ maxReferenceImages: 4,
355
+ name: "Gemini 2.5 Flash",
356
+ pricePerImageUsd: 0.0398,
357
+ pricing: "$0.04",
340
358
  sizeMode: "aspect_ratio",
341
359
  supportsAspect: true,
342
- supportsResolution: false,
343
360
  supportsEdit: true,
344
361
  supportsNumImages: true,
345
362
  supportsOutputFormat: true,
363
+ supportsResolution: false,
346
364
  supportsSafetyTolerance: true,
347
365
  supportsSeed: true,
348
366
  supportsSyncMode: true,
349
- maxReferenceImages: 4
367
+ type: "generation"
350
368
  },
351
369
  gemini3: {
352
- name: "Gemini 3 Pro",
353
- endpoint: "fal-ai/gemini-3-pro-image-preview",
354
370
  editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
355
- type: "generation",
356
- pricing: "$0.15",
357
- pricePerImageUsd: 0.15,
371
+ endpoint: "fal-ai/gemini-3-pro-image-preview",
358
372
  falPricing: {
359
- endpointId: "fal-ai/gemini-3-pro-image-preview",
360
- unitPrice: 0.15,
361
- unit: "images",
373
+ checkedAt: FAL_PRICING_CHECKED_AT,
362
374
  currency: "USD",
375
+ endpointId: "fal-ai/gemini-3-pro-image-preview",
363
376
  estimatedCostPerImageUsd: 0.15,
364
377
  source: "fal-pricing-api",
365
- checkedAt: FAL_PRICING_CHECKED_AT
378
+ unit: "images",
379
+ unitPrice: 0.15
366
380
  },
381
+ maxReferenceImages: 4,
382
+ name: "Gemini 3 Pro",
383
+ pricePerImageUsd: 0.15,
384
+ pricing: "$0.15",
367
385
  sizeMode: "aspect_ratio",
368
386
  supportsAspect: true,
369
- supportsResolution: true,
370
387
  supportsEdit: true,
371
388
  supportsNumImages: true,
372
389
  supportsOutputFormat: true,
390
+ supportsResolution: true,
373
391
  supportsSafetyTolerance: true,
374
392
  supportsSeed: true,
375
- supportsWebSearch: true,
376
393
  supportsSyncMode: true,
377
- maxReferenceImages: 4
394
+ supportsWebSearch: true,
395
+ type: "generation"
378
396
  },
379
397
  seedream4: {
380
- name: "Seedream 4.0",
381
- endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
382
- editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
383
- type: "generation",
384
- pricing: "$0.03",
385
- pricePerImageUsd: 0.03,
386
- falPricing: {
387
- endpointId: "fal-ai/bytedance/seedream/v4/text-to-image",
388
- unitPrice: 0.03,
389
- unit: "images",
390
- currency: "USD",
391
- estimatedCostPerImageUsd: 0.03,
392
- source: "fal-pricing-api",
393
- checkedAt: FAL_PRICING_CHECKED_AT
394
- },
395
398
  benchmark: {
396
- useCase: "High-ranked budget generation and large multi-reference edits",
397
- tiers: { quality: "best", speed: "balanced", price: "budget" },
399
+ artificialAnalysis: {
400
+ editing: { elo: 1184, pricePer1k: 30, rank: 16, winRate: 0.61 },
401
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
402
+ sourceUrls: AA_IMAGE_SOURCES,
403
+ textToImage: { elo: 1198, pricePer1k: 30, rank: 6, winRate: 0.59 }
404
+ },
398
405
  speed: {
399
406
  medianSeconds: 14.3,
400
407
  p95Seconds: 21,
401
408
  source: "artificial-analysis-models"
402
409
  },
403
- artificialAnalysis: {
404
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
405
- sourceUrls: AA_IMAGE_SOURCES,
406
- textToImage: { rank: 6, elo: 1198, winRate: 0.59, pricePer1k: 30 },
407
- editing: { rank: 16, elo: 1184, winRate: 0.61, pricePer1k: 30 }
408
- }
410
+ tiers: { price: "budget", quality: "best", speed: "balanced" },
411
+ useCase: "High-ranked budget generation and large multi-reference edits"
412
+ },
413
+ editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
414
+ endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
415
+ falPricing: {
416
+ checkedAt: FAL_PRICING_CHECKED_AT,
417
+ currency: "USD",
418
+ endpointId: "fal-ai/bytedance/seedream/v4/text-to-image",
419
+ estimatedCostPerImageUsd: 0.03,
420
+ source: "fal-pricing-api",
421
+ unit: "images",
422
+ unitPrice: 0.03
409
423
  },
424
+ maxReferenceImages: 10,
425
+ name: "Seedream 4.0",
426
+ pricePerImageUsd: 0.03,
427
+ pricing: "$0.03",
410
428
  sizeMode: "image_size_enum",
411
429
  supportsAspect: true,
412
- supportsResolution: false,
413
430
  supportsEdit: true,
414
431
  supportsNumImages: true,
432
+ supportsResolution: false,
415
433
  supportsSeed: true,
416
434
  supportsSyncMode: true,
417
- maxReferenceImages: 10
435
+ type: "generation"
418
436
  },
419
437
  seedream45: {
420
- name: "Seedream 4.5",
421
- endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
422
- editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
423
- type: "generation",
424
- pricing: "$0.04",
425
- pricePerImageUsd: 0.04,
426
- falPricing: {
427
- endpointId: "fal-ai/bytedance/seedream/v4.5/text-to-image",
428
- unitPrice: 0.04,
429
- unit: "images",
430
- currency: "USD",
431
- estimatedCostPerImageUsd: 0.04,
432
- source: "fal-pricing-api",
433
- checkedAt: FAL_PRICING_CHECKED_AT
434
- },
435
438
  benchmark: {
436
- useCase: "Cheap current Seedream model with good animation/art scores",
437
- tiers: { quality: "better", speed: "balanced", price: "standard" },
439
+ artificialAnalysis: {
440
+ editing: { elo: 1184, pricePer1k: 40, rank: 17, winRate: 0.58 },
441
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
442
+ sourceUrls: AA_IMAGE_SOURCES,
443
+ textToImage: { elo: 1167, pricePer1k: 40, rank: 17, winRate: 0.63 }
444
+ },
438
445
  speed: {
439
446
  medianSeconds: 18.2,
440
447
  p95Seconds: 43.3,
441
448
  source: "artificial-analysis-models"
442
449
  },
443
- artificialAnalysis: {
444
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
445
- sourceUrls: AA_IMAGE_SOURCES,
446
- textToImage: { rank: 17, elo: 1167, winRate: 0.63, pricePer1k: 40 },
447
- editing: { rank: 17, elo: 1184, winRate: 0.58, pricePer1k: 40 }
448
- }
450
+ tiers: { price: "standard", quality: "better", speed: "balanced" },
451
+ useCase: "Cheap current Seedream model with good animation/art scores"
452
+ },
453
+ editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
454
+ endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
455
+ falPricing: {
456
+ checkedAt: FAL_PRICING_CHECKED_AT,
457
+ currency: "USD",
458
+ endpointId: "fal-ai/bytedance/seedream/v4.5/text-to-image",
459
+ estimatedCostPerImageUsd: 0.04,
460
+ source: "fal-pricing-api",
461
+ unit: "images",
462
+ unitPrice: 0.04
449
463
  },
464
+ maxReferenceImages: 10,
465
+ name: "Seedream 4.5",
466
+ pricePerImageUsd: 0.04,
467
+ pricing: "$0.04",
450
468
  sizeMode: "image_size_enum",
451
469
  supportsAspect: true,
452
- supportsResolution: false,
453
470
  supportsEdit: true,
454
471
  supportsNumImages: true,
472
+ supportsResolution: false,
455
473
  supportsSeed: true,
456
474
  supportsSyncMode: true,
457
- maxReferenceImages: 10
475
+ type: "generation"
458
476
  },
459
477
  seedream5: {
460
478
  name: "Seedream 5.0 Pro",
@@ -465,13 +483,13 @@ var MODELS = {
465
483
  pricing: "$0.0675 (\u22641536\xB2) / $0.135 (\u22642048\xB2)",
466
484
  pricePerImageUsd: 0.0675,
467
485
  falPricing: {
468
- endpointId: "bytedance/seedream/v5/pro/text-to-image",
469
- unitPrice: 0.0675,
470
- unit: "images",
486
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
471
487
  currency: "USD",
488
+ endpointId: "bytedance/seedream/v5/pro/text-to-image",
472
489
  estimatedCostPerImageUsd: 0.0675,
473
490
  source: "fal-pricing-api",
474
- checkedAt: FAL_PRICING_CHECKED_JUL_2026
491
+ unit: "images",
492
+ unitPrice: 0.0675
475
493
  },
476
494
  sizeMode: "image_size_enum",
477
495
  supportsAspect: true,
@@ -491,13 +509,13 @@ var MODELS = {
491
509
  pricing: "$0.035",
492
510
  pricePerImageUsd: 0.035,
493
511
  falPricing: {
494
- endpointId: "fal-ai/bytedance/seedream/v5/lite/text-to-image",
495
- unitPrice: 0.035,
496
- unit: "images",
512
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
497
513
  currency: "USD",
514
+ endpointId: "fal-ai/bytedance/seedream/v5/lite/text-to-image",
498
515
  estimatedCostPerImageUsd: 0.035,
499
516
  source: "fal-pricing-api",
500
- checkedAt: FAL_PRICING_CHECKED_JUL_2026
517
+ unit: "images",
518
+ unitPrice: 0.035
501
519
  },
502
520
  sizeMode: "image_size_enum",
503
521
  supportsAspect: true,
@@ -509,334 +527,334 @@ var MODELS = {
509
527
  maxReferenceImages: 10
510
528
  },
511
529
  "flux2-max": {
512
- name: "FLUX.2 Max",
513
- endpoint: "fal-ai/flux-2-max",
514
- editEndpoint: "fal-ai/flux-2-max/edit",
515
- type: "generation",
516
- pricing: "$0.07/MP",
517
- pricePerImageUsd: 0.07,
518
- falPricing: {
519
- endpointId: "fal-ai/flux-2-max",
520
- unitPrice: 0.07,
521
- unit: "megapixels",
522
- currency: "USD",
523
- estimatedCostPerImageUsd: 0.07,
524
- source: "fal-pricing-api",
525
- checkedAt: FAL_PRICING_CHECKED_AT
526
- },
527
530
  benchmark: {
528
- useCase: "Best FLUX quality and high-end editing",
529
- tiers: { quality: "best", speed: "slow", price: "premium" },
531
+ artificialAnalysis: {
532
+ editing: { elo: 1206, pricePer1k: 140, rank: 10, winRate: 0.6 },
533
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
534
+ sourceUrls: AA_IMAGE_SOURCES,
535
+ textToImage: { elo: 1197, pricePer1k: 70, rank: 8, winRate: 0.67 }
536
+ },
530
537
  speed: {
531
538
  medianSeconds: 31.6,
532
539
  p95Seconds: 41,
533
540
  source: "artificial-analysis-models"
534
541
  },
535
- artificialAnalysis: {
536
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
537
- sourceUrls: AA_IMAGE_SOURCES,
538
- textToImage: { rank: 8, elo: 1197, winRate: 0.67, pricePer1k: 70 },
539
- editing: { rank: 10, elo: 1206, winRate: 0.6, pricePer1k: 140 }
540
- }
542
+ tiers: { price: "premium", quality: "best", speed: "slow" },
543
+ useCase: "Best FLUX quality and high-end editing"
544
+ },
545
+ editEndpoint: "fal-ai/flux-2-max/edit",
546
+ endpoint: "fal-ai/flux-2-max",
547
+ falPricing: {
548
+ checkedAt: FAL_PRICING_CHECKED_AT,
549
+ currency: "USD",
550
+ endpointId: "fal-ai/flux-2-max",
551
+ estimatedCostPerImageUsd: 0.07,
552
+ source: "fal-pricing-api",
553
+ unit: "megapixels",
554
+ unitPrice: 0.07
541
555
  },
556
+ maxReferenceImages: 10,
557
+ name: "FLUX.2 Max",
558
+ pricePerImageUsd: 0.07,
559
+ pricing: "$0.07/MP",
542
560
  sizeMode: "image_size_enum",
543
561
  supportsAspect: true,
544
- supportsResolution: false,
545
562
  supportsEdit: true,
546
563
  supportsNumImages: false,
547
564
  supportsOutputFormat: true,
548
- supportsSafetyTolerance: true,
565
+ supportsResolution: false,
549
566
  supportsSafetyChecker: true,
567
+ supportsSafetyTolerance: true,
550
568
  supportsSeed: true,
551
569
  supportsSyncMode: true,
552
- maxReferenceImages: 10
570
+ type: "generation"
553
571
  },
554
572
  "flux2-pro": {
555
- name: "FLUX.2 Pro",
556
- endpoint: "fal-ai/flux-2-pro",
557
- editEndpoint: "fal-ai/flux-2-pro/edit",
558
- type: "generation",
559
- pricing: "$0.03/MP",
560
- pricePerImageUsd: 0.03,
561
- falPricing: {
562
- endpointId: "fal-ai/flux-2-pro",
563
- unitPrice: 0.03,
564
- unit: "processed megapixels",
565
- currency: "USD",
566
- estimatedCostPerImageUsd: 0.03,
567
- source: "fal-pricing-api",
568
- checkedAt: FAL_PRICING_CHECKED_AT
569
- },
570
573
  benchmark: {
571
- useCase: "Production FLUX quality at a low per-megapixel price",
572
- tiers: { quality: "best", speed: "balanced", price: "budget" },
574
+ artificialAnalysis: {
575
+ editing: { elo: 1170, pricePer1k: 30, rank: 21 },
576
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
577
+ sourceUrls: AA_IMAGE_SOURCES,
578
+ textToImage: { elo: 1186, pricePer1k: 30, rank: 10, winRate: 0.62 }
579
+ },
573
580
  speed: {
574
581
  medianSeconds: 16.1,
575
582
  p95Seconds: 23.9,
576
583
  source: "artificial-analysis-models"
577
584
  },
578
- artificialAnalysis: {
579
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
580
- sourceUrls: AA_IMAGE_SOURCES,
581
- textToImage: { rank: 10, elo: 1186, winRate: 0.62, pricePer1k: 30 },
582
- editing: { rank: 21, elo: 1170, pricePer1k: 30 }
583
- }
585
+ tiers: { price: "budget", quality: "best", speed: "balanced" },
586
+ useCase: "Production FLUX quality at a low per-megapixel price"
587
+ },
588
+ editEndpoint: "fal-ai/flux-2-pro/edit",
589
+ endpoint: "fal-ai/flux-2-pro",
590
+ falPricing: {
591
+ checkedAt: FAL_PRICING_CHECKED_AT,
592
+ currency: "USD",
593
+ endpointId: "fal-ai/flux-2-pro",
594
+ estimatedCostPerImageUsd: 0.03,
595
+ source: "fal-pricing-api",
596
+ unit: "processed megapixels",
597
+ unitPrice: 0.03
584
598
  },
599
+ maxReferenceImages: 10,
600
+ name: "FLUX.2 Pro",
601
+ pricePerImageUsd: 0.03,
602
+ pricing: "$0.03/MP",
585
603
  sizeMode: "image_size_enum",
586
604
  supportsAspect: true,
587
- supportsResolution: false,
588
605
  supportsEdit: true,
589
606
  supportsNumImages: false,
590
607
  supportsOutputFormat: true,
591
- supportsSafetyTolerance: true,
608
+ supportsResolution: false,
592
609
  supportsSafetyChecker: true,
610
+ supportsSafetyTolerance: true,
593
611
  supportsSeed: true,
594
612
  supportsSyncMode: true,
595
- maxReferenceImages: 10
613
+ type: "generation"
596
614
  },
597
615
  "flux2-flex": {
598
- name: "FLUX.2 Flex",
599
- endpoint: "fal-ai/flux-2-flex",
600
- editEndpoint: "fal-ai/flux-2-flex/edit",
601
- type: "generation",
602
- pricing: "$0.05/MP",
603
- pricePerImageUsd: 0.05,
604
- falPricing: {
605
- endpointId: "fal-ai/flux-2-flex",
606
- unitPrice: 0.05,
607
- unit: "processed megapixels",
608
- currency: "USD",
609
- estimatedCostPerImageUsd: 0.05,
610
- source: "fal-pricing-api",
611
- checkedAt: FAL_PRICING_CHECKED_AT
612
- },
613
616
  benchmark: {
614
- useCase: "FLUX quality with guidance and step controls",
615
- tiers: { quality: "best", speed: "balanced", price: "standard" },
617
+ artificialAnalysis: {
618
+ editing: { elo: 1161, pricePer1k: 60, rank: 22 },
619
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
620
+ sourceUrls: AA_IMAGE_SOURCES,
621
+ textToImage: { elo: 1182, pricePer1k: 60, rank: 13, winRate: 0.61 }
622
+ },
616
623
  speed: {
617
624
  medianSeconds: 15.3,
618
625
  p95Seconds: 22.2,
619
626
  source: "artificial-analysis-models"
620
627
  },
621
- artificialAnalysis: {
622
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
623
- sourceUrls: AA_IMAGE_SOURCES,
624
- textToImage: { rank: 13, elo: 1182, winRate: 0.61, pricePer1k: 60 },
625
- editing: { rank: 22, elo: 1161, pricePer1k: 60 }
626
- }
628
+ tiers: { price: "standard", quality: "best", speed: "balanced" },
629
+ useCase: "FLUX quality with guidance and step controls"
630
+ },
631
+ editEndpoint: "fal-ai/flux-2-flex/edit",
632
+ endpoint: "fal-ai/flux-2-flex",
633
+ falPricing: {
634
+ checkedAt: FAL_PRICING_CHECKED_AT,
635
+ currency: "USD",
636
+ endpointId: "fal-ai/flux-2-flex",
637
+ estimatedCostPerImageUsd: 0.05,
638
+ source: "fal-pricing-api",
639
+ unit: "processed megapixels",
640
+ unitPrice: 0.05
627
641
  },
642
+ maxReferenceImages: 10,
643
+ name: "FLUX.2 Flex",
644
+ pricePerImageUsd: 0.05,
645
+ pricing: "$0.05/MP",
628
646
  sizeMode: "image_size_enum",
647
+ supportedOutputFormats: ["jpeg", "png"],
629
648
  supportsAspect: true,
630
- supportsResolution: false,
631
649
  supportsEdit: true,
650
+ supportsGuidanceScale: true,
651
+ supportsInferenceSteps: true,
632
652
  supportsNumImages: false,
633
653
  supportsOutputFormat: true,
634
- supportedOutputFormats: ["jpeg", "png"],
635
- supportsSafetyTolerance: true,
654
+ supportsResolution: false,
636
655
  supportsSafetyChecker: true,
656
+ supportsSafetyTolerance: true,
637
657
  supportsSeed: true,
638
- supportsGuidanceScale: true,
639
- supportsInferenceSteps: true,
640
658
  supportsSyncMode: true,
641
- maxReferenceImages: 10
659
+ type: "generation"
642
660
  },
643
661
  "flux2-dev": {
644
- name: "FLUX.2 [dev]",
645
- endpoint: "fal-ai/flux-2",
646
- editEndpoint: "fal-ai/flux-2/edit",
647
- type: "generation",
648
- pricing: "$0.00167/sec",
649
- pricePerImageUsd: 0.012,
650
- falPricing: {
651
- endpointId: "fal-ai/flux-2",
652
- unitPrice: 167e-5,
653
- unit: "compute seconds",
654
- currency: "USD",
655
- estimatedCostPerImageUsd: 0.012,
656
- source: "fal-pricing-api",
657
- checkedAt: FAL_PRICING_CHECKED_AT
658
- },
659
662
  benchmark: {
660
- useCase: "Open FLUX.2 quality with low average cost",
661
- tiers: { quality: "better", speed: "fast", price: "variable" },
663
+ artificialAnalysis: {
664
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
665
+ sourceUrls: AA_IMAGE_SOURCES,
666
+ textToImage: { elo: 1160, pricePer1k: 12, rank: 19, winRate: 0.57 }
667
+ },
662
668
  speed: {
663
669
  medianSeconds: 4.6,
664
670
  p95Seconds: 69.6,
665
671
  source: "artificial-analysis-models"
666
672
  },
667
- artificialAnalysis: {
668
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
669
- sourceUrls: AA_IMAGE_SOURCES,
670
- textToImage: { rank: 19, elo: 1160, winRate: 0.57, pricePer1k: 12 }
671
- }
673
+ tiers: { price: "variable", quality: "better", speed: "fast" },
674
+ useCase: "Open FLUX.2 quality with low average cost"
675
+ },
676
+ editEndpoint: "fal-ai/flux-2/edit",
677
+ endpoint: "fal-ai/flux-2",
678
+ falPricing: {
679
+ checkedAt: FAL_PRICING_CHECKED_AT,
680
+ currency: "USD",
681
+ endpointId: "fal-ai/flux-2",
682
+ estimatedCostPerImageUsd: 0.012,
683
+ source: "fal-pricing-api",
684
+ unit: "compute seconds",
685
+ unitPrice: 167e-5
672
686
  },
687
+ maxReferenceImages: 10,
688
+ name: "FLUX.2 [dev]",
689
+ pricePerImageUsd: 0.012,
690
+ pricing: "$0.00167/sec",
673
691
  sizeMode: "image_size_enum",
674
692
  supportsAspect: true,
675
- supportsResolution: false,
676
693
  supportsEdit: true,
694
+ supportsGuidanceScale: true,
695
+ supportsInferenceSteps: true,
677
696
  supportsNumImages: true,
678
697
  supportsOutputFormat: true,
679
- supportsSafetyTolerance: true,
698
+ supportsResolution: false,
680
699
  supportsSafetyChecker: true,
700
+ supportsSafetyTolerance: true,
681
701
  supportsSeed: true,
682
- supportsGuidanceScale: true,
683
- supportsInferenceSteps: true,
684
702
  supportsSyncMode: true,
685
- maxReferenceImages: 10
703
+ type: "generation"
686
704
  },
687
705
  "flux2-turbo": {
688
- name: "FLUX.2 Turbo",
689
706
  endpoint: "fal-ai/flux-2/turbo",
690
- type: "generation",
691
- pricing: "$0.008/MP",
692
- pricePerImageUsd: 8e-3,
693
707
  falPricing: {
694
- endpointId: "fal-ai/flux-2/turbo",
695
- unitPrice: 8e-3,
696
- unit: "megapixels",
708
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
697
709
  currency: "USD",
710
+ endpointId: "fal-ai/flux-2/turbo",
698
711
  estimatedCostPerImageUsd: 8e-3,
699
712
  source: "fal-pricing-api",
700
- checkedAt: FAL_PRICING_CHECKED_JUL_2026
713
+ unit: "megapixels",
714
+ unitPrice: 8e-3
701
715
  },
716
+ name: "FLUX.2 Turbo",
717
+ pricePerImageUsd: 8e-3,
718
+ pricing: "$0.008/MP",
702
719
  sizeMode: "image_size_enum",
703
720
  supportsAspect: true,
704
- supportsResolution: false,
705
721
  supportsEdit: false,
722
+ supportsGuidanceScale: true,
706
723
  supportsNumImages: true,
707
724
  supportsOutputFormat: true,
725
+ supportsResolution: false,
708
726
  supportsSafetyChecker: true,
709
727
  supportsSeed: true,
710
- supportsGuidanceScale: true,
711
- supportsSyncMode: true
728
+ supportsSyncMode: true,
729
+ type: "generation"
712
730
  },
713
731
  flux: {
714
- name: "FLUX Pro Ultra",
715
732
  endpoint: "fal-ai/flux-pro/v1.1-ultra",
716
- type: "generation",
717
- pricing: "$0.06",
718
- pricePerImageUsd: 0.06,
719
733
  falPricing: {
720
- endpointId: "fal-ai/flux-pro/v1.1-ultra",
721
- unitPrice: 0.06,
722
- unit: "images",
734
+ checkedAt: FAL_PRICING_CHECKED_AT,
723
735
  currency: "USD",
736
+ endpointId: "fal-ai/flux-pro/v1.1-ultra",
724
737
  estimatedCostPerImageUsd: 0.06,
725
738
  source: "fal-pricing-api",
726
- checkedAt: FAL_PRICING_CHECKED_AT
739
+ unit: "images",
740
+ unitPrice: 0.06
727
741
  },
742
+ maxReferenceImages: 1,
743
+ name: "FLUX Pro Ultra",
744
+ pricePerImageUsd: 0.06,
745
+ pricing: "$0.06",
728
746
  sizeMode: "aspect_ratio",
729
747
  supportsAspect: true,
730
- supportsResolution: false,
731
748
  supportsEdit: true,
749
+ supportsEnhancePrompt: true,
750
+ supportsImagePromptStrength: true,
732
751
  supportsNumImages: true,
733
752
  supportsOutputFormat: true,
734
753
  supportsRaw: true,
735
- supportsEnhancePrompt: true,
754
+ supportsResolution: false,
736
755
  supportsSafetyTolerance: true,
737
756
  supportsSeed: true,
738
- supportsImagePromptStrength: true,
739
757
  supportsSyncMode: true,
740
- maxReferenceImages: 1
758
+ type: "generation"
741
759
  },
742
760
  "flux-fast": {
743
- name: "FLUX Schnell",
744
761
  endpoint: "fal-ai/flux/schnell",
745
- type: "generation",
746
- pricing: "$0.003",
747
- pricePerImageUsd: 3e-3,
748
762
  falPricing: {
749
- endpointId: "fal-ai/flux/schnell",
750
- unitPrice: 3e-3,
751
- unit: "megapixels",
763
+ checkedAt: FAL_PRICING_CHECKED_AT,
752
764
  currency: "USD",
765
+ endpointId: "fal-ai/flux/schnell",
753
766
  estimatedCostPerImageUsd: 3e-3,
754
767
  source: "fal-pricing-api",
755
- checkedAt: FAL_PRICING_CHECKED_AT
768
+ unit: "megapixels",
769
+ unitPrice: 3e-3
756
770
  },
771
+ name: "FLUX Schnell",
772
+ pricePerImageUsd: 3e-3,
773
+ pricing: "$0.003",
757
774
  sizeMode: "image_size_enum",
758
775
  supportsAspect: true,
759
- supportsResolution: false,
760
776
  supportsEdit: false,
761
- supportsNumImages: true,
762
- supportsOutputFormat: true,
763
777
  supportsGuidanceScale: true,
764
778
  supportsInferenceSteps: true,
779
+ supportsNumImages: true,
780
+ supportsOutputFormat: true,
781
+ supportsResolution: false,
765
782
  supportsSeed: true,
766
- supportsSyncMode: true
783
+ supportsSyncMode: true,
784
+ type: "generation"
767
785
  },
768
786
  recraft: {
769
- name: "Recraft V3",
770
787
  endpoint: "fal-ai/recraft-v3",
771
- type: "generation",
772
- pricing: "$0.04",
773
- pricePerImageUsd: 0.04,
774
788
  falPricing: {
775
- endpointId: "fal-ai/recraft-v3",
776
- unitPrice: 0.04,
777
- unit: "images",
789
+ checkedAt: FAL_PRICING_CHECKED_AT,
778
790
  currency: "USD",
791
+ endpointId: "fal-ai/recraft-v3",
779
792
  estimatedCostPerImageUsd: 0.04,
780
793
  source: "fal-pricing-api",
781
- checkedAt: FAL_PRICING_CHECKED_AT
794
+ unit: "images",
795
+ unitPrice: 0.04
782
796
  },
797
+ name: "Recraft V3",
798
+ pricePerImageUsd: 0.04,
799
+ pricing: "$0.04",
783
800
  sizeMode: "image_size_enum",
784
801
  supportsAspect: true,
785
- supportsResolution: false,
786
802
  supportsEdit: false,
787
803
  supportsNumImages: false,
804
+ supportsResolution: false,
788
805
  supportsStyle: true,
789
- supportsSyncMode: true
806
+ supportsSyncMode: true,
807
+ type: "generation"
790
808
  },
791
809
  recraft4: {
792
- name: "Recraft V4",
793
810
  endpoint: "fal-ai/recraft/v4/text-to-image",
794
- type: "generation",
795
- pricing: "$0.04",
796
- pricePerImageUsd: 0.04,
797
811
  falPricing: {
798
- endpointId: "fal-ai/recraft/v4/text-to-image",
799
- unitPrice: 0.04,
800
- unit: "images",
812
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
801
813
  currency: "USD",
814
+ endpointId: "fal-ai/recraft/v4/text-to-image",
802
815
  estimatedCostPerImageUsd: 0.04,
803
816
  source: "fal-pricing-api",
804
- checkedAt: FAL_PRICING_CHECKED_JUL_2026
817
+ unit: "images",
818
+ unitPrice: 0.04
805
819
  },
820
+ name: "Recraft V4",
821
+ pricePerImageUsd: 0.04,
822
+ pricing: "$0.04",
806
823
  sizeMode: "image_size_enum",
807
824
  supportsAspect: true,
808
- supportsResolution: false,
809
825
  supportsEdit: false,
810
826
  supportsNumImages: false,
827
+ supportsResolution: false,
811
828
  supportsStyle: true,
812
- supportsSyncMode: true
829
+ supportsSyncMode: true,
830
+ type: "generation"
813
831
  },
814
832
  ideogram: {
815
- name: "Ideogram V3",
816
833
  endpoint: "fal-ai/ideogram/v3",
817
- type: "generation",
818
- pricing: "$0.03",
819
- pricePerImageUsd: 0.03,
820
834
  falPricing: {
821
- endpointId: "fal-ai/ideogram/v3",
822
- unitPrice: 0.03,
823
- unit: "images",
835
+ checkedAt: FAL_PRICING_CHECKED_AT,
824
836
  currency: "USD",
837
+ endpointId: "fal-ai/ideogram/v3",
825
838
  estimatedCostPerImageUsd: 0.03,
826
839
  source: "fal-pricing-api",
827
- checkedAt: FAL_PRICING_CHECKED_AT
840
+ unit: "images",
841
+ unitPrice: 0.03
828
842
  },
843
+ name: "Ideogram V3",
844
+ pricePerImageUsd: 0.03,
845
+ pricing: "$0.03",
829
846
  sizeMode: "image_size_enum",
830
847
  supportsAspect: true,
831
- supportsResolution: false,
832
848
  supportsEdit: false,
833
- supportsNumImages: true,
834
849
  supportsExpandPrompt: true,
835
850
  supportsNegativePrompt: true,
851
+ supportsNumImages: true,
836
852
  supportsRenderingSpeed: true,
853
+ supportsResolution: false,
837
854
  supportsSeed: true,
838
855
  supportsStyle: true,
839
- supportsSyncMode: true
856
+ supportsSyncMode: true,
857
+ type: "generation"
840
858
  },
841
859
  ideogram4: {
842
860
  name: "Ideogram V4",
@@ -848,13 +866,13 @@ var MODELS = {
848
866
  pricing: "$0.03",
849
867
  pricePerImageUsd: 0.03,
850
868
  falPricing: {
851
- endpointId: "ideogram/v4",
852
- unitPrice: 0.03,
853
- unit: "megapixels",
869
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
854
870
  currency: "USD",
871
+ endpointId: "ideogram/v4",
855
872
  estimatedCostPerImageUsd: 0.03,
856
873
  source: "fal-pricing-api",
857
- checkedAt: FAL_PRICING_CHECKED_JUL_2026
874
+ unit: "megapixels",
875
+ unitPrice: 0.03
858
876
  },
859
877
  sizeMode: "image_size_enum",
860
878
  supportsAspect: true,
@@ -869,135 +887,135 @@ var MODELS = {
869
887
  supportsSyncMode: true
870
888
  },
871
889
  "grok-image": {
872
- name: "Grok Imagine Image",
873
- endpoint: "xai/grok-imagine-image",
874
- editEndpoint: "xai/grok-imagine-image/edit",
875
- type: "generation",
876
- pricing: "$0.02",
877
- pricePerImageUsd: 0.02,
878
- falPricing: {
879
- endpointId: "xai/grok-imagine-image",
880
- unitPrice: 0.02,
881
- unit: "images",
882
- currency: "USD",
883
- estimatedCostPerImageUsd: 0.02,
884
- source: "fal-pricing-api",
885
- checkedAt: FAL_PRICING_CHECKED_AT
886
- },
887
890
  benchmark: {
888
- useCase: "Very fast, cheap generation and edits",
889
- tiers: { quality: "best", speed: "fast", price: "budget" },
891
+ artificialAnalysis: {
892
+ editing: { elo: 1213, pricePer1k: 20, rank: 7, winRate: 0.6 },
893
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
894
+ sourceUrls: AA_IMAGE_SOURCES,
895
+ textToImage: { elo: 1182, pricePer1k: 20, rank: 12, winRate: 0.6 }
896
+ },
890
897
  speed: {
891
898
  medianSeconds: 5.1,
892
899
  p95Seconds: 6.5,
893
900
  source: "artificial-analysis-models"
894
901
  },
895
- artificialAnalysis: {
896
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
897
- sourceUrls: AA_IMAGE_SOURCES,
898
- textToImage: { rank: 12, elo: 1182, winRate: 0.6, pricePer1k: 20 },
899
- editing: { rank: 7, elo: 1213, winRate: 0.6, pricePer1k: 20 }
900
- }
902
+ tiers: { price: "budget", quality: "best", speed: "fast" },
903
+ useCase: "Very fast, cheap generation and edits"
901
904
  },
905
+ editEndpoint: "xai/grok-imagine-image/edit",
906
+ endpoint: "xai/grok-imagine-image",
907
+ falPricing: {
908
+ checkedAt: FAL_PRICING_CHECKED_AT,
909
+ currency: "USD",
910
+ endpointId: "xai/grok-imagine-image",
911
+ estimatedCostPerImageUsd: 0.02,
912
+ source: "fal-pricing-api",
913
+ unit: "images",
914
+ unitPrice: 0.02
915
+ },
916
+ maxReferenceImages: 4,
917
+ name: "Grok Imagine Image",
918
+ pricePerImageUsd: 0.02,
919
+ pricing: "$0.02",
902
920
  sizeMode: "aspect_ratio",
903
921
  supportsAspect: true,
904
- supportsResolution: true,
905
922
  supportsEdit: true,
906
923
  supportsNumImages: true,
907
924
  supportsOutputFormat: true,
925
+ supportsResolution: true,
908
926
  supportsSyncMode: true,
909
- maxReferenceImages: 4
927
+ type: "generation"
910
928
  },
911
929
  qwen: {
912
- name: "Qwen Image",
913
- endpoint: "fal-ai/qwen-image",
914
- type: "generation",
915
- pricing: "$0.02/MP",
916
- pricePerImageUsd: 0.02,
917
- falPricing: {
918
- endpointId: "fal-ai/qwen-image",
919
- unitPrice: 0.02,
920
- unit: "megapixels",
921
- currency: "USD",
922
- estimatedCostPerImageUsd: 0.02,
923
- source: "fal-pricing-api",
924
- checkedAt: FAL_PRICING_CHECKED_AT
925
- },
926
930
  benchmark: {
927
- useCase: "Low-cost open-weight text and design generation",
928
- tiers: { quality: "better", speed: "slow", price: "budget" },
931
+ artificialAnalysis: {
932
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
933
+ sourceUrls: AA_IMAGE_SOURCES,
934
+ textToImage: { elo: 1158, pricePer1k: 20, rank: 20, winRate: 0.6 }
935
+ },
929
936
  speed: {
930
937
  medianSeconds: 23.3,
931
938
  p95Seconds: 491,
932
939
  source: "artificial-analysis-models"
933
940
  },
934
- artificialAnalysis: {
935
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
936
- sourceUrls: AA_IMAGE_SOURCES,
937
- textToImage: { rank: 20, elo: 1158, winRate: 0.6, pricePer1k: 20 }
938
- }
941
+ tiers: { price: "budget", quality: "better", speed: "slow" },
942
+ useCase: "Low-cost open-weight text and design generation"
943
+ },
944
+ endpoint: "fal-ai/qwen-image",
945
+ falPricing: {
946
+ checkedAt: FAL_PRICING_CHECKED_AT,
947
+ currency: "USD",
948
+ endpointId: "fal-ai/qwen-image",
949
+ estimatedCostPerImageUsd: 0.02,
950
+ source: "fal-pricing-api",
951
+ unit: "megapixels",
952
+ unitPrice: 0.02
939
953
  },
954
+ name: "Qwen Image",
955
+ pricePerImageUsd: 0.02,
956
+ pricing: "$0.02/MP",
940
957
  sizeMode: "image_size_enum",
941
958
  supportsAspect: true,
942
- supportsResolution: false,
943
959
  supportsEdit: false,
944
960
  supportsNumImages: true,
945
961
  supportsOutputFormat: true,
962
+ supportsResolution: false,
946
963
  supportsSeed: true,
947
- supportsSyncMode: true
964
+ supportsSyncMode: true,
965
+ type: "generation"
948
966
  },
949
967
  // ─── Video Models ─────────────────────────────────────────────
950
968
  kling: {
951
- name: "Kling v3 Pro",
952
969
  endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
953
- type: "video",
970
+ name: "Kling v3 Pro",
954
971
  pricing: "$0.11/sec",
955
972
  sizeMode: "none",
956
973
  supportsAspect: false,
957
- supportsResolution: false,
958
974
  supportsEdit: false,
959
- supportsNumImages: false
975
+ supportsNumImages: false,
976
+ supportsResolution: false,
977
+ type: "video"
960
978
  },
961
979
  // ─── Utility Models ───────────────────────────────────────────
962
980
  clarity: {
963
- name: "Clarity Upscaler",
964
981
  endpoint: "fal-ai/clarity-upscaler",
965
- type: "utility",
982
+ name: "Clarity Upscaler",
966
983
  pricing: "$0.03/MP",
967
984
  supportsAspect: false,
968
- supportsResolution: false,
969
985
  supportsEdit: false,
970
- supportsNumImages: false
986
+ supportsNumImages: false,
987
+ supportsResolution: false,
988
+ type: "utility"
971
989
  },
972
990
  crystal: {
973
- name: "Crystal Upscaler",
974
991
  endpoint: "clarityai/crystal-upscaler",
975
- type: "utility",
992
+ name: "Crystal Upscaler",
976
993
  pricing: "$0.02",
977
994
  supportsAspect: false,
978
- supportsResolution: false,
979
995
  supportsEdit: false,
980
- supportsNumImages: false
996
+ supportsNumImages: false,
997
+ supportsResolution: false,
998
+ type: "utility"
981
999
  },
982
1000
  rmbg: {
983
- name: "BiRefNet (Background Removal)",
984
1001
  endpoint: "fal-ai/birefnet",
985
- type: "utility",
1002
+ name: "BiRefNet (Background Removal)",
986
1003
  pricing: "$0.02",
987
1004
  supportsAspect: false,
988
- supportsResolution: false,
989
1005
  supportsEdit: false,
990
- supportsNumImages: false
1006
+ supportsNumImages: false,
1007
+ supportsResolution: false,
1008
+ type: "utility"
991
1009
  },
992
1010
  bria: {
993
- name: "Bria RMBG 2.0",
994
1011
  endpoint: "fal-ai/bria/background/remove",
995
- type: "utility",
1012
+ name: "Bria RMBG 2.0",
996
1013
  pricing: "$0.02",
997
1014
  supportsAspect: false,
998
- supportsResolution: false,
999
1015
  supportsEdit: false,
1000
- supportsNumImages: false
1016
+ supportsNumImages: false,
1017
+ supportsResolution: false,
1018
+ type: "utility"
1001
1019
  }
1002
1020
  };
1003
1021
  var GENERATION_MODELS = [
@@ -1026,7 +1044,7 @@ var GENERATION_MODELS = [
1026
1044
  "qwen"
1027
1045
  ];
1028
1046
  var EDIT_CAPABLE_MODELS = GENERATION_MODELS.filter(
1029
- (id) => MODELS[id]?.supportsEdit
1047
+ (id) => MODELS[id]?.supportsEdit === true
1030
1048
  );
1031
1049
  var UTILITY_MODELS = ["clarity", "crystal", "rmbg", "bria"];
1032
1050
  var VIDEO_MODELS = ["kling"];
@@ -1092,30 +1110,40 @@ function estimateCost(model, resolution, numImages = 1) {
1092
1110
  return configuredPrice * numImages;
1093
1111
  }
1094
1112
  switch (model) {
1095
- case "gpt2":
1113
+ case "gpt2": {
1096
1114
  return 0.211 * numImages;
1097
- case "gpt":
1115
+ }
1116
+ case "gpt": {
1098
1117
  return 0.133 * numImages;
1118
+ }
1099
1119
  case "banana":
1100
- case "gemini3":
1120
+ case "gemini3": {
1101
1121
  return (resolution === "4K" ? 0.3 : 0.15) * numImages;
1102
- case "gemini":
1122
+ }
1123
+ case "gemini": {
1103
1124
  return 0.039 * numImages;
1125
+ }
1104
1126
  case "flux":
1105
- case "ideogram":
1127
+ case "ideogram": {
1106
1128
  return 0.06 * numImages;
1107
- case "recraft":
1129
+ }
1130
+ case "recraft": {
1108
1131
  return 0.04 * numImages;
1109
- case "flux-fast":
1132
+ }
1133
+ case "flux-fast": {
1110
1134
  return 3e-3 * numImages;
1111
- case "clarity":
1135
+ }
1136
+ case "clarity": {
1112
1137
  return 0.03 * numImages;
1138
+ }
1113
1139
  case "crystal":
1114
1140
  case "rmbg":
1115
- case "bria":
1141
+ case "bria": {
1116
1142
  return 0.02 * numImages;
1117
- default:
1143
+ }
1144
+ default: {
1118
1145
  return 0.1 * numImages;
1146
+ }
1119
1147
  }
1120
1148
  }
1121
1149
  function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
@@ -1124,7 +1152,7 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
1124
1152
  }
1125
1153
 
1126
1154
  // src/creative.ts
1127
- var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
1155
+ var CONTROL_CHAR_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
1128
1156
  var CreativeOptionError = class extends Error {
1129
1157
  availableIds;
1130
1158
  code = "INVALID_OPTION";
@@ -1151,28 +1179,20 @@ var CREATIVE_FIELDS = [
1151
1179
  "motion"
1152
1180
  ];
1153
1181
  var CREATIVE_TAXONOMY = {
1154
- recipe: [
1155
- {
1156
- clause: "cinematic scene",
1157
- description: "Frames the prompt as a cinematic still or scene.",
1158
- id: "cinematic",
1159
- label: "Cinematic"
1160
- }
1161
- ],
1162
- shot: [
1182
+ camera: [
1163
1183
  {
1164
- clause: "close-up composition with controlled depth of field",
1165
- description: "Tight framing that emphasizes subject detail.",
1166
- id: "close-up",
1167
- label: "Close-up"
1184
+ clause: "macro product photography with crisp surface detail",
1185
+ description: "Product-oriented macro camera language for close detail.",
1186
+ id: "macro-product",
1187
+ label: "Macro product"
1168
1188
  }
1169
1189
  ],
1170
- lighting: [
1190
+ color: [
1171
1191
  {
1172
- clause: "rim lighting with defined edge highlights",
1173
- description: "Back or side light that separates the subject edge.",
1174
- id: "rim",
1175
- label: "Rim"
1192
+ clause: "monochrome palette with tonal contrast",
1193
+ description: "Black-and-white or single-channel tonal treatment.",
1194
+ id: "monochrome",
1195
+ label: "Monochrome"
1176
1196
  }
1177
1197
  ],
1178
1198
  genre: [
@@ -1183,20 +1203,12 @@ var CREATIVE_TAXONOMY = {
1183
1203
  label: "Film noir"
1184
1204
  }
1185
1205
  ],
1186
- camera: [
1187
- {
1188
- clause: "macro product photography with crisp surface detail",
1189
- description: "Product-oriented macro camera language for close detail.",
1190
- id: "macro-product",
1191
- label: "Macro product"
1192
- }
1193
- ],
1194
- color: [
1206
+ lighting: [
1195
1207
  {
1196
- clause: "monochrome palette with tonal contrast",
1197
- description: "Black-and-white or single-channel tonal treatment.",
1198
- id: "monochrome",
1199
- label: "Monochrome"
1208
+ clause: "rim lighting with defined edge highlights",
1209
+ description: "Back or side light that separates the subject edge.",
1210
+ id: "rim",
1211
+ label: "Rim"
1200
1212
  }
1201
1213
  ],
1202
1214
  material: [
@@ -1214,10 +1226,26 @@ var CREATIVE_TAXONOMY = {
1214
1226
  id: "still",
1215
1227
  label: "Still"
1216
1228
  }
1229
+ ],
1230
+ recipe: [
1231
+ {
1232
+ clause: "cinematic scene",
1233
+ description: "Frames the prompt as a cinematic still or scene.",
1234
+ id: "cinematic",
1235
+ label: "Cinematic"
1236
+ }
1237
+ ],
1238
+ shot: [
1239
+ {
1240
+ clause: "close-up composition with controlled depth of field",
1241
+ description: "Tight framing that emphasizes subject detail.",
1242
+ id: "close-up",
1243
+ label: "Close-up"
1244
+ }
1217
1245
  ]
1218
1246
  };
1219
1247
  function sanitizePrompt(prompt) {
1220
- return prompt.replace(CONTROL_CHAR_REGEX, "").replace(/\r\n/g, "\n").trim();
1248
+ return prompt.replace(CONTROL_CHAR_REGEX, "").replaceAll("\r\n", "\n").trim();
1221
1249
  }
1222
1250
  function enrichPrompt(options) {
1223
1251
  const basePrompt = sanitizePrompt(options.prompt);
@@ -1225,7 +1253,7 @@ function enrichPrompt(options) {
1225
1253
  const selected = {};
1226
1254
  for (const field of CREATIVE_FIELDS) {
1227
1255
  const optionId = options.creative?.[field];
1228
- if (!optionId) {
1256
+ if (optionId === void 0 || optionId === "") {
1229
1257
  continue;
1230
1258
  }
1231
1259
  const option = CREATIVE_TAXONOMY[field].find(
@@ -1300,11 +1328,11 @@ function normalizeImageSize(imageSize, sizeMode) {
1300
1328
  if (FAL_IMAGE_SIZE_PRESETS.includes(imageSize)) {
1301
1329
  return imageSize;
1302
1330
  }
1303
- const match = imageSize.match(/^(\d+)x(\d+)$/);
1331
+ const match = /^(\d+)x(\d+)$/.exec(imageSize);
1304
1332
  if (match) {
1305
1333
  const width = Number(match[1]);
1306
1334
  const height = Number(match[2]);
1307
- return { width, height };
1335
+ return { height, width };
1308
1336
  }
1309
1337
  throw new Error(
1310
1338
  `fal image_size must be one of ${FAL_IMAGE_SIZE_PRESETS.join(", ")} or WIDTHxHEIGHT`
@@ -1314,12 +1342,12 @@ function unsupported(config, option) {
1314
1342
  throw new Error(`${config.name} does not support ${option}`);
1315
1343
  }
1316
1344
  function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
1317
- if (options.editImageUrls?.length && config.maxReferenceImages !== void 0 && options.editImageUrls.length > config.maxReferenceImages) {
1345
+ if (options.editImageUrls !== void 0 && options.editImageUrls.length > 0 && config.maxReferenceImages !== void 0 && options.editImageUrls.length > config.maxReferenceImages) {
1318
1346
  throw new Error(
1319
1347
  `${config.name} supports at most ${config.maxReferenceImages} reference images`
1320
1348
  );
1321
1349
  }
1322
- if (options.editImageUrls?.length && !config.supportsEdit) {
1350
+ if (options.editImageUrls !== void 0 && options.editImageUrls.length > 0 && !config.supportsEdit) {
1323
1351
  unsupported(config, "image editing");
1324
1352
  }
1325
1353
  if (options.aspect !== void 0 && sizeMode === "none") {
@@ -1331,10 +1359,10 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
1331
1359
  if (options.numImages !== void 0 && options.numImages !== 1 && !config.supportsNumImages) {
1332
1360
  unsupported(config, "numImages");
1333
1361
  }
1334
- if (options.background !== void 0 && !config.supportsBackground) {
1362
+ if (options.background !== void 0 && config.supportsBackground !== true) {
1335
1363
  unsupported(config, "background");
1336
1364
  }
1337
- if (options.transparent && !(config.supportsBackground || config.supportsOutputFormat)) {
1365
+ if (options.transparent === true && !(config.supportsBackground === true || config.supportsOutputFormat === true)) {
1338
1366
  unsupported(config, "transparent output");
1339
1367
  }
1340
1368
  if (options.inputFidelity !== void 0) {
@@ -1348,70 +1376,70 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
1348
1376
  if (options.imageSize !== void 0 && sizeMode !== "gpt_size" && sizeMode !== "image_size_enum") {
1349
1377
  unsupported(config, "imageSize");
1350
1378
  }
1351
- if (options.imagePromptStrength !== void 0 && !config.supportsImagePromptStrength) {
1379
+ if (options.imagePromptStrength !== void 0 && config.supportsImagePromptStrength !== true) {
1352
1380
  unsupported(config, "imagePromptStrength");
1353
1381
  }
1354
1382
  if (options.imagePromptStrength !== void 0 && !hasEditImages) {
1355
1383
  throw new Error("imagePromptStrength requires editImageUrls");
1356
1384
  }
1357
- if (options.maskImageUrl !== void 0 && !config.supportsMaskImage) {
1385
+ if (options.maskImageUrl !== void 0 && config.supportsMaskImage !== true) {
1358
1386
  unsupported(config, "maskImageUrl");
1359
1387
  }
1360
1388
  if (options.maskImageUrl !== void 0 && !hasEditImages) {
1361
1389
  throw new Error("maskImageUrl requires editImageUrls");
1362
1390
  }
1363
- if (options.seed !== void 0 && !config.supportsSeed) {
1391
+ if (options.seed !== void 0 && config.supportsSeed !== true) {
1364
1392
  unsupported(config, "seed");
1365
1393
  }
1366
- if (options.outputFormat !== void 0 && !config.supportsOutputFormat) {
1394
+ if (options.outputFormat !== void 0 && config.supportsOutputFormat !== true) {
1367
1395
  unsupported(config, "outputFormat");
1368
1396
  }
1369
- if (options.quality !== void 0 && !config.supportsQuality) {
1397
+ if (options.quality !== void 0 && config.supportsQuality !== true) {
1370
1398
  unsupported(config, "quality");
1371
1399
  }
1372
- if (options.negativePrompt !== void 0 && !config.supportsNegativePrompt) {
1400
+ if (options.negativePrompt !== void 0 && config.supportsNegativePrompt !== true) {
1373
1401
  unsupported(config, "negativePrompt");
1374
1402
  }
1375
- if (options.style !== void 0 && !config.supportsStyle) {
1403
+ if (options.style !== void 0 && config.supportsStyle !== true) {
1376
1404
  unsupported(config, "style");
1377
1405
  }
1378
- if (options.syncMode !== void 0 && !config.supportsSyncMode) {
1406
+ if (options.syncMode !== void 0 && config.supportsSyncMode !== true) {
1379
1407
  unsupported(config, "syncMode");
1380
1408
  }
1381
- if (options.renderingSpeed !== void 0 && !config.supportsRenderingSpeed) {
1409
+ if (options.renderingSpeed !== void 0 && config.supportsRenderingSpeed !== true) {
1382
1410
  unsupported(config, "renderingSpeed");
1383
1411
  }
1384
- if (options.guidanceScale !== void 0 && !config.supportsGuidanceScale) {
1412
+ if (options.guidanceScale !== void 0 && config.supportsGuidanceScale !== true) {
1385
1413
  unsupported(config, "guidanceScale");
1386
1414
  }
1387
- if (options.numInferenceSteps !== void 0 && !config.supportsInferenceSteps) {
1415
+ if (options.numInferenceSteps !== void 0 && config.supportsInferenceSteps !== true) {
1388
1416
  unsupported(config, "numInferenceSteps");
1389
1417
  }
1390
- if (options.raw !== void 0 && !config.supportsRaw) {
1418
+ if (options.raw !== void 0 && config.supportsRaw !== true) {
1391
1419
  unsupported(config, "raw");
1392
1420
  }
1393
- if (options.enhancePrompt !== void 0 && !config.supportsEnhancePrompt) {
1421
+ if (options.enhancePrompt !== void 0 && config.supportsEnhancePrompt !== true) {
1394
1422
  unsupported(config, "enhancePrompt");
1395
1423
  }
1396
- if (options.safetyTolerance !== void 0 && !config.supportsSafetyTolerance) {
1424
+ if (options.safetyTolerance !== void 0 && config.supportsSafetyTolerance !== true) {
1397
1425
  unsupported(config, "safetyTolerance");
1398
1426
  }
1399
- if (options.enableWebSearch !== void 0 && !config.supportsWebSearch) {
1427
+ if (options.enableWebSearch !== void 0 && config.supportsWebSearch !== true) {
1400
1428
  unsupported(config, "enableWebSearch");
1401
1429
  }
1402
- if (options.enableGoogleSearch !== void 0 && !config.supportsGoogleSearch) {
1430
+ if (options.enableGoogleSearch !== void 0 && config.supportsGoogleSearch !== true) {
1403
1431
  unsupported(config, "enableGoogleSearch");
1404
1432
  }
1405
- if (options.enableSafetyChecker !== void 0 && !config.supportsSafetyChecker) {
1433
+ if (options.enableSafetyChecker !== void 0 && config.supportsSafetyChecker !== true) {
1406
1434
  unsupported(config, "enableSafetyChecker");
1407
1435
  }
1408
- if (options.limitGenerations !== void 0 && !config.supportsLimitGenerations) {
1436
+ if (options.limitGenerations !== void 0 && config.supportsLimitGenerations !== true) {
1409
1437
  unsupported(config, "limitGenerations");
1410
1438
  }
1411
- if (options.thinkingLevel !== void 0 && !config.supportsThinkingLevel) {
1439
+ if (options.thinkingLevel !== void 0 && config.supportsThinkingLevel !== true) {
1412
1440
  unsupported(config, "thinkingLevel");
1413
1441
  }
1414
- if (options.expandPrompt !== void 0 && !config.supportsExpandPrompt) {
1442
+ if (options.expandPrompt !== void 0 && config.supportsExpandPrompt !== true) {
1415
1443
  unsupported(config, "expandPrompt");
1416
1444
  }
1417
1445
  }
@@ -1453,8 +1481,8 @@ function buildGenerateBody(options) {
1453
1481
  if (!config) {
1454
1482
  throw new Error(`Unknown model: ${model}`);
1455
1483
  }
1456
- let endpoint = config.endpoint;
1457
- const enrichedPrompt = creative ? enrichPrompt({ prompt, creative }).prompt : prompt;
1484
+ let { endpoint } = config;
1485
+ const enrichedPrompt = creative ? enrichPrompt({ creative, prompt }).prompt : prompt;
1458
1486
  const body = { prompt: enrichedPrompt };
1459
1487
  const hasEditImages = Boolean(editImageUrls?.length);
1460
1488
  const editImages = editImageUrls ?? [];
@@ -1463,13 +1491,15 @@ function buildGenerateBody(options) {
1463
1491
  validateGenerateOptions(options, config, sizeMode, hasEditImages);
1464
1492
  const normalizedImageSize = normalizeImageSize(imageSize, sizeMode);
1465
1493
  switch (sizeMode) {
1466
- case "gpt_size":
1494
+ case "gpt_size": {
1467
1495
  body.image_size = normalizedImageSize ?? (hasEditImages && !explicitAspect ? "auto" : aspectToGptSize(aspect));
1468
1496
  break;
1469
- case "image_size_enum":
1497
+ }
1498
+ case "image_size_enum": {
1470
1499
  body.image_size = normalizedImageSize ?? aspectToFalImageSize(aspect);
1471
1500
  break;
1472
- case "aspect_ratio":
1501
+ }
1502
+ case "aspect_ratio": {
1473
1503
  if (config.supportsAspect) {
1474
1504
  body.aspect_ratio = aspect;
1475
1505
  }
@@ -1477,26 +1507,30 @@ function buildGenerateBody(options) {
1477
1507
  body.resolution = model === "grok-image" ? resolution.toLowerCase() : resolution;
1478
1508
  }
1479
1509
  break;
1510
+ }
1511
+ case "none": {
1512
+ break;
1513
+ }
1480
1514
  }
1481
- if (config.supportsQuality) {
1515
+ if (config.supportsQuality === true) {
1482
1516
  body.quality = quality ?? "high";
1483
1517
  }
1484
- if (config.supportsBackground && background) {
1518
+ if (config.supportsBackground === true && background) {
1485
1519
  body.background = background;
1486
1520
  }
1487
- if (config.supportsBackground && transparent) {
1521
+ if (config.supportsBackground === true && transparent === true) {
1488
1522
  body.background = "transparent";
1489
1523
  }
1490
- if (transparent && config.supportsOutputFormat) {
1524
+ if (transparent === true && config.supportsOutputFormat === true) {
1491
1525
  body.output_format = "png";
1492
1526
  }
1493
- if (syncMode !== void 0 && config.supportsSyncMode) {
1527
+ if (syncMode !== void 0 && config.supportsSyncMode === true) {
1494
1528
  body.sync_mode = syncMode;
1495
1529
  }
1496
- if (seed !== void 0 && config.supportsSeed) {
1530
+ if (seed !== void 0 && config.supportsSeed === true) {
1497
1531
  body.seed = seed;
1498
1532
  }
1499
- if (outputFormat && config.supportsOutputFormat && !transparent) {
1533
+ if (outputFormat && config.supportsOutputFormat === true && transparent !== true) {
1500
1534
  if (config.supportedOutputFormats && !config.supportedOutputFormats.includes(outputFormat)) {
1501
1535
  throw new Error(
1502
1536
  `${config.name} supports output formats: ${config.supportedOutputFormats.join(", ")}`
@@ -1504,62 +1538,78 @@ function buildGenerateBody(options) {
1504
1538
  }
1505
1539
  body.output_format = outputFormat;
1506
1540
  }
1507
- if (config.supportsRaw && raw !== void 0) {
1541
+ if (config.supportsRaw === true && raw !== void 0) {
1508
1542
  body.raw = raw;
1509
1543
  }
1510
- if (config.supportsEnhancePrompt && enhancePrompt !== void 0) {
1544
+ if (config.supportsEnhancePrompt === true && enhancePrompt !== void 0) {
1511
1545
  body.enhance_prompt = enhancePrompt;
1512
1546
  }
1513
- if (config.supportsImagePromptStrength && imagePromptStrength !== void 0) {
1547
+ if (config.supportsImagePromptStrength === true && imagePromptStrength !== void 0) {
1514
1548
  body.image_prompt_strength = imagePromptStrength;
1515
1549
  }
1516
1550
  if (model === "flux") {
1517
- if (safetyTolerance) body.safety_tolerance = safetyTolerance;
1551
+ if (safetyTolerance !== void 0 && safetyTolerance !== "") {
1552
+ body.safety_tolerance = safetyTolerance;
1553
+ }
1518
1554
  }
1519
- if (config.supportsGuidanceScale && guidanceScale !== void 0) {
1555
+ if (config.supportsGuidanceScale === true && guidanceScale !== void 0) {
1520
1556
  body.guidance_scale = guidanceScale;
1521
1557
  }
1522
- if (config.supportsInferenceSteps && numInferenceSteps !== void 0) {
1558
+ if (config.supportsInferenceSteps === true && numInferenceSteps !== void 0) {
1523
1559
  body.num_inference_steps = numInferenceSteps;
1524
1560
  }
1525
- if (config.supportsSafetyTolerance) {
1526
- if (safetyTolerance) body.safety_tolerance = safetyTolerance;
1561
+ if (config.supportsSafetyTolerance === true) {
1562
+ if (safetyTolerance !== void 0 && safetyTolerance !== "") {
1563
+ body.safety_tolerance = safetyTolerance;
1564
+ }
1527
1565
  }
1528
- if (config.supportsWebSearch) {
1529
- if (enableWebSearch !== void 0) body.enable_web_search = enableWebSearch;
1566
+ if (config.supportsWebSearch === true) {
1567
+ if (enableWebSearch !== void 0) {
1568
+ body.enable_web_search = enableWebSearch;
1569
+ }
1530
1570
  }
1531
- if (config.supportsGoogleSearch) {
1571
+ if (config.supportsGoogleSearch === true) {
1532
1572
  if (enableGoogleSearch !== void 0) {
1533
1573
  body.enable_google_search = enableGoogleSearch;
1534
1574
  }
1535
1575
  }
1536
- if (config.supportsSafetyChecker) {
1576
+ if (config.supportsSafetyChecker === true) {
1537
1577
  if (enableSafetyChecker !== void 0) {
1538
1578
  body.enable_safety_checker = enableSafetyChecker;
1539
1579
  }
1540
1580
  }
1541
- if (config.supportsLimitGenerations) {
1581
+ if (config.supportsLimitGenerations === true) {
1542
1582
  if (limitGenerations !== void 0) {
1543
1583
  body.limit_generations = limitGenerations;
1544
1584
  }
1545
1585
  }
1546
- if (config.supportsThinkingLevel) {
1547
- if (thinkingLevel) body.thinking_level = thinkingLevel;
1586
+ if (config.supportsThinkingLevel === true) {
1587
+ if (thinkingLevel) {
1588
+ body.thinking_level = thinkingLevel;
1589
+ }
1548
1590
  }
1549
- if (config.supportsStyle && style) {
1591
+ if (config.supportsStyle === true && style !== void 0 && style !== "") {
1550
1592
  body.style = style;
1551
1593
  }
1552
- if (config.supportsNegativePrompt) {
1553
- if (negativePrompt) body.negative_prompt = negativePrompt;
1594
+ if (config.supportsNegativePrompt === true) {
1595
+ if (negativePrompt !== void 0 && negativePrompt !== "") {
1596
+ body.negative_prompt = negativePrompt;
1597
+ }
1554
1598
  }
1555
1599
  if (model === "ideogram") {
1556
- if (style) body.style = style;
1600
+ if (style !== void 0 && style !== "") {
1601
+ body.style = style;
1602
+ }
1557
1603
  }
1558
- if (config.supportsRenderingSpeed) {
1559
- if (renderingSpeed) body.rendering_speed = renderingSpeed;
1604
+ if (config.supportsRenderingSpeed === true) {
1605
+ if (renderingSpeed) {
1606
+ body.rendering_speed = renderingSpeed;
1607
+ }
1560
1608
  }
1561
- if (config.supportsExpandPrompt) {
1562
- if (expandPrompt !== void 0) body.expand_prompt = expandPrompt;
1609
+ if (config.supportsExpandPrompt === true) {
1610
+ if (expandPrompt !== void 0) {
1611
+ body.expand_prompt = expandPrompt;
1612
+ }
1563
1613
  }
1564
1614
  if (model === "flux" && hasEditImages) {
1565
1615
  body.image_url = editImages[0];
@@ -1586,959 +1636,1057 @@ function buildGenerateBody(options) {
1586
1636
  body.input_fidelity = inputFidelity;
1587
1637
  }
1588
1638
  }
1589
- if (maskImageUrl && config.supportsMaskImage) {
1639
+ if (maskImageUrl !== void 0 && maskImageUrl !== "" && config.supportsMaskImage === true) {
1590
1640
  body.mask_image_url = maskImageUrl;
1591
1641
  }
1592
- return { endpoint, body };
1642
+ return { body, endpoint };
1593
1643
  }
1594
1644
 
1595
1645
  // src/leaderboards.ts
1596
1646
  var IMAGE_TEXT_TO_IMAGE_TOP_20 = {
1597
1647
  capturedAt: "2026-05-12",
1598
- source: "https://artificialanalysis.ai/image/leaderboard/text-to-image",
1599
1648
  entries: [
1600
1649
  {
1601
- rank: 1,
1650
+ apiPricing: "$211/1k images",
1602
1651
  creator: "OpenAI",
1603
- model: "GPT Image 2 (high)",
1604
1652
  elo: 1337,
1605
- apiPricing: "$211/1k images",
1606
1653
  isCurrent: true,
1654
+ model: "GPT Image 2 (high)",
1655
+ rank: 1,
1607
1656
  routableModelId: "gpt2"
1608
1657
  },
1609
1658
  {
1610
- rank: 2,
1659
+ apiPricing: "$133/1k images",
1611
1660
  creator: "OpenAI",
1612
- model: "GPT Image 1.5 (high)",
1613
1661
  elo: 1268,
1614
- apiPricing: "$133/1k images",
1615
1662
  isCurrent: true,
1663
+ model: "GPT Image 1.5 (high)",
1664
+ rank: 2,
1616
1665
  routableModelId: "gpt"
1617
1666
  },
1618
1667
  {
1619
- rank: 3,
1668
+ apiPricing: "$67/1k images",
1620
1669
  creator: "Google",
1621
- model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1622
1670
  elo: 1263,
1623
- apiPricing: "$67/1k images",
1624
1671
  isCurrent: true,
1672
+ model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1673
+ rank: 3,
1625
1674
  routableModelId: "banana2"
1626
1675
  },
1627
1676
  {
1628
- rank: 4,
1677
+ apiPricing: "$150/1k images",
1629
1678
  creator: "Sourceful",
1630
- model: "Riverflow 2.0",
1631
1679
  elo: 1256,
1632
- apiPricing: "$150/1k images",
1633
- isCurrent: true
1680
+ isCurrent: true,
1681
+ model: "Riverflow 2.0",
1682
+ rank: 4
1634
1683
  },
1635
1684
  {
1636
- rank: 5,
1685
+ apiPricing: "$134/1k images",
1637
1686
  creator: "Google",
1638
- model: "Nano Banana Pro (Gemini 3 Pro Image)",
1639
1687
  elo: 1220,
1640
- apiPricing: "$134/1k images",
1641
1688
  isCurrent: true,
1689
+ model: "Nano Banana Pro (Gemini 3 Pro Image)",
1690
+ rank: 5,
1642
1691
  routableModelId: "banana"
1643
1692
  },
1644
1693
  {
1645
- rank: 6,
1694
+ apiPricing: "$30/1k images",
1646
1695
  creator: "ByteDance Seed",
1647
- model: "Seedream 4.0",
1648
1696
  elo: 1198,
1649
- apiPricing: "$30/1k images",
1650
1697
  isCurrent: true,
1698
+ model: "Seedream 4.0",
1699
+ rank: 6,
1651
1700
  routableModelId: "seedream4"
1652
1701
  },
1653
1702
  {
1654
- rank: 7,
1703
+ apiPricing: "$50/1k images",
1655
1704
  creator: "Microsoft",
1656
- model: "MAI-Image-2",
1657
1705
  elo: 1198,
1658
- apiPricing: "$50/1k images",
1659
- isCurrent: true
1706
+ isCurrent: true,
1707
+ model: "MAI-Image-2",
1708
+ rank: 7
1660
1709
  },
1661
1710
  {
1662
- rank: 8,
1711
+ apiPricing: "$70/1k images",
1663
1712
  creator: "Black Forest Labs",
1664
- model: "FLUX.2 [max]",
1665
1713
  elo: 1197,
1666
- apiPricing: "$70/1k images",
1667
1714
  isCurrent: true,
1715
+ model: "FLUX.2 [max]",
1716
+ rank: 8,
1668
1717
  routableModelId: "flux2-max"
1669
1718
  },
1670
1719
  {
1671
- rank: 9,
1720
+ apiPricing: "Unavailable",
1672
1721
  creator: "Peanut",
1673
- model: "Peanut (Open Weights Coming Soon)",
1674
1722
  elo: 1187,
1675
- apiPricing: "Unavailable",
1676
- isCurrent: true
1723
+ isCurrent: true,
1724
+ model: "Peanut (Open Weights Coming Soon)",
1725
+ rank: 9
1677
1726
  },
1678
1727
  {
1679
- rank: 10,
1728
+ apiPricing: "$30/1k images",
1680
1729
  creator: "Black Forest Labs",
1681
- model: "FLUX.2 [pro]",
1682
1730
  elo: 1186,
1683
- apiPricing: "$30/1k images",
1684
1731
  isCurrent: true,
1732
+ model: "FLUX.2 [pro]",
1733
+ rank: 10,
1685
1734
  routableModelId: "flux2-pro"
1686
1735
  },
1687
1736
  {
1688
- rank: 11,
1737
+ apiPricing: "$60/1k images",
1689
1738
  creator: "Google",
1690
- model: "Imagen 4 Ultra Preview 0606",
1691
1739
  elo: 1184,
1692
- apiPricing: "$60/1k images",
1693
- isCurrent: false
1740
+ isCurrent: false,
1741
+ model: "Imagen 4 Ultra Preview 0606",
1742
+ rank: 11
1694
1743
  },
1695
1744
  {
1696
- rank: 12,
1745
+ apiPricing: "$20/1k images",
1697
1746
  creator: "xAI",
1698
- model: "grok-imagine-image",
1699
1747
  elo: 1182,
1700
- apiPricing: "$20/1k images",
1701
1748
  isCurrent: true,
1749
+ model: "grok-imagine-image",
1750
+ rank: 12,
1702
1751
  routableModelId: "grok-image"
1703
1752
  },
1704
1753
  {
1705
- rank: 13,
1754
+ apiPricing: "$60/1k images",
1706
1755
  creator: "Black Forest Labs",
1707
- model: "FLUX.2 [flex]",
1708
1756
  elo: 1182,
1709
- apiPricing: "$60/1k images",
1710
1757
  isCurrent: true,
1758
+ model: "FLUX.2 [flex]",
1759
+ rank: 13,
1711
1760
  routableModelId: "flux2-flex"
1712
1761
  },
1713
1762
  {
1714
- rank: 14,
1763
+ apiPricing: "$30/1k images",
1715
1764
  creator: "ImagineArt",
1716
- model: "ImagineArt 2.0",
1717
1765
  elo: 1181,
1718
- apiPricing: "$30/1k images",
1719
- isCurrent: true
1766
+ isCurrent: true,
1767
+ model: "ImagineArt 2.0",
1768
+ rank: 14
1720
1769
  },
1721
1770
  {
1722
- rank: 15,
1771
+ apiPricing: "$60/1k images",
1723
1772
  creator: "Google",
1724
- model: "Imagen 4 Ultra",
1725
1773
  elo: 1171,
1726
- apiPricing: "$60/1k images",
1727
- isCurrent: true
1774
+ isCurrent: true,
1775
+ model: "Imagen 4 Ultra",
1776
+ rank: 15
1728
1777
  },
1729
1778
  {
1730
- rank: 16,
1779
+ apiPricing: "$40/1k images",
1731
1780
  creator: "Google",
1732
- model: "Imagen 4 Preview 0606",
1733
1781
  elo: 1169,
1734
- apiPricing: "$40/1k images",
1735
- isCurrent: false
1782
+ isCurrent: false,
1783
+ model: "Imagen 4 Preview 0606",
1784
+ rank: 16
1736
1785
  },
1737
1786
  {
1738
- rank: 17,
1787
+ apiPricing: "$40/1k images",
1739
1788
  creator: "ByteDance Seed",
1740
- model: "Seedream 4.5",
1741
1789
  elo: 1167,
1742
- apiPricing: "$40/1k images",
1743
1790
  isCurrent: true,
1791
+ model: "Seedream 4.5",
1792
+ rank: 17,
1744
1793
  routableModelId: "seedream45"
1745
1794
  },
1746
1795
  {
1747
- rank: 18,
1796
+ apiPricing: "$8/1k images",
1748
1797
  creator: "Fal",
1749
- model: "FLUX.2 [dev] Turbo",
1750
1798
  elo: 1161,
1751
- apiPricing: "$8/1k images",
1752
- isCurrent: true
1799
+ isCurrent: true,
1800
+ model: "FLUX.2 [dev] Turbo",
1801
+ rank: 18
1753
1802
  },
1754
1803
  {
1755
- rank: 19,
1804
+ apiPricing: "$12/1k images",
1756
1805
  creator: "Black Forest Labs",
1757
- model: "FLUX.2 [dev]",
1758
1806
  elo: 1160,
1759
- apiPricing: "$12/1k images",
1760
1807
  isCurrent: true,
1808
+ model: "FLUX.2 [dev]",
1809
+ rank: 19,
1761
1810
  routableModelId: "flux2-dev"
1762
1811
  },
1763
1812
  {
1764
- rank: 20,
1813
+ apiPricing: "$20/1k images",
1765
1814
  creator: "Alibaba",
1766
- model: "Qwen Image Max 2512",
1767
1815
  elo: 1158,
1768
- apiPricing: "$20/1k images",
1769
1816
  isCurrent: true,
1817
+ model: "Qwen Image Max 2512",
1818
+ rank: 20,
1770
1819
  routableModelId: "qwen"
1771
1820
  }
1772
- ]
1821
+ ],
1822
+ source: "https://artificialanalysis.ai/image/leaderboard/text-to-image"
1773
1823
  };
1774
1824
  var IMAGE_EDITING_TOP_20 = {
1775
1825
  capturedAt: "2026-05-12",
1776
- source: "https://artificialanalysis.ai/image/leaderboard/editing",
1777
1826
  entries: [
1778
1827
  {
1779
- rank: 1,
1828
+ apiPricing: "$150/1k images",
1780
1829
  creator: "Sourceful",
1781
- model: "Riverflow 2.0",
1782
1830
  elo: 1286,
1783
- apiPricing: "$150/1k images",
1784
- isCurrent: true
1831
+ isCurrent: true,
1832
+ model: "Riverflow 2.0",
1833
+ rank: 1
1785
1834
  },
1786
1835
  {
1787
- rank: 2,
1836
+ apiPricing: "$133/1k images",
1788
1837
  creator: "OpenAI",
1789
- model: "GPT Image 1.5 (high)",
1790
1838
  elo: 1262,
1791
- apiPricing: "$133/1k images",
1792
1839
  isCurrent: true,
1840
+ model: "GPT Image 1.5 (high)",
1841
+ rank: 2,
1793
1842
  routableModelId: "gpt"
1794
1843
  },
1795
1844
  {
1796
- rank: 3,
1845
+ apiPricing: "$211/1k images",
1797
1846
  creator: "OpenAI",
1798
- model: "GPT Image 2 (high)",
1799
1847
  elo: 1249,
1800
- apiPricing: "$211/1k images",
1801
1848
  isCurrent: true,
1849
+ model: "GPT Image 2 (high)",
1850
+ rank: 3,
1802
1851
  routableModelId: "gpt2"
1803
1852
  },
1804
1853
  {
1805
- rank: 4,
1854
+ apiPricing: "$134/1k images",
1806
1855
  creator: "Google",
1807
- model: "Nano Banana Pro (Gemini 3 Pro Image)",
1808
1856
  elo: 1241,
1809
- apiPricing: "$134/1k images",
1810
1857
  isCurrent: true,
1858
+ model: "Nano Banana Pro (Gemini 3 Pro Image)",
1859
+ rank: 4,
1811
1860
  routableModelId: "banana"
1812
1861
  },
1813
1862
  {
1814
- rank: 5,
1863
+ apiPricing: "$67/1k images",
1815
1864
  creator: "Google",
1816
- model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1817
1865
  elo: 1231,
1818
- apiPricing: "$67/1k images",
1819
1866
  isCurrent: true,
1867
+ model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1868
+ rank: 5,
1820
1869
  routableModelId: "banana2"
1821
1870
  },
1822
1871
  {
1823
- rank: 6,
1872
+ apiPricing: "$90/1k images",
1824
1873
  creator: "Tencent",
1825
- model: "HunyuanImage 3.0 Instruct (Fal)",
1826
1874
  elo: 1222,
1827
- apiPricing: "$90/1k images",
1828
- isCurrent: true
1875
+ isCurrent: true,
1876
+ model: "HunyuanImage 3.0 Instruct (Fal)",
1877
+ rank: 6
1829
1878
  },
1830
1879
  {
1831
- rank: 7,
1880
+ apiPricing: "$20/1k images",
1832
1881
  creator: "xAI",
1833
- model: "grok-imagine-image",
1834
1882
  elo: 1213,
1835
- apiPricing: "$20/1k images",
1836
1883
  isCurrent: true,
1884
+ model: "grok-imagine-image",
1885
+ rank: 7,
1837
1886
  routableModelId: "grok-image"
1838
1887
  },
1839
1888
  {
1840
- rank: 8,
1889
+ apiPricing: "$70/1k images",
1841
1890
  creator: "xAI",
1842
- model: "grok-imagine-image-pro",
1843
1891
  elo: 1212,
1844
- apiPricing: "$70/1k images",
1845
- isCurrent: true
1892
+ isCurrent: true,
1893
+ model: "grok-imagine-image-pro",
1894
+ rank: 8
1846
1895
  },
1847
1896
  {
1848
- rank: 9,
1897
+ apiPricing: "Unavailable",
1849
1898
  creator: "KlingAI",
1850
- model: "Kling Image 3.0 Omni",
1851
1899
  elo: 1207,
1852
- apiPricing: "Unavailable",
1853
- isCurrent: true
1900
+ isCurrent: true,
1901
+ model: "Kling Image 3.0 Omni",
1902
+ rank: 9
1854
1903
  },
1855
1904
  {
1856
- rank: 10,
1905
+ apiPricing: "$140/1k images",
1857
1906
  creator: "Black Forest Labs",
1858
- model: "FLUX.2 [max]",
1859
1907
  elo: 1206,
1860
- apiPricing: "$140/1k images",
1861
1908
  isCurrent: true,
1909
+ model: "FLUX.2 [max]",
1910
+ rank: 10,
1862
1911
  routableModelId: "flux2-max"
1863
1912
  },
1864
1913
  {
1865
- rank: 11,
1914
+ apiPricing: "Unavailable",
1866
1915
  creator: "Alibaba",
1867
- model: "Wan 2.7 Pro",
1868
1916
  elo: 1200,
1869
- apiPricing: "Unavailable",
1870
- isCurrent: true
1917
+ isCurrent: true,
1918
+ model: "Wan 2.7 Pro",
1919
+ rank: 11
1871
1920
  },
1872
1921
  {
1873
- rank: 12,
1922
+ apiPricing: "$28/1k images",
1874
1923
  creator: "KlingAI",
1875
- model: "Kling Image 3.0",
1876
1924
  elo: 1196,
1877
- apiPricing: "$28/1k images",
1878
- isCurrent: true
1925
+ isCurrent: true,
1926
+ model: "Kling Image 3.0",
1927
+ rank: 12
1879
1928
  },
1880
1929
  {
1881
- rank: 13,
1930
+ apiPricing: "$28/1k images",
1882
1931
  creator: "KlingAI",
1883
- model: "Kling Image O1",
1884
1932
  elo: 1193,
1885
- apiPricing: "$28/1k images",
1886
- isCurrent: true
1933
+ isCurrent: true,
1934
+ model: "Kling Image O1",
1935
+ rank: 13
1887
1936
  },
1888
1937
  {
1889
- rank: 14,
1938
+ apiPricing: "$30/1k images",
1890
1939
  creator: "Alibaba",
1891
- model: "Wan 2.6 Image",
1892
1940
  elo: 1188,
1893
- apiPricing: "$30/1k images",
1894
- isCurrent: true
1941
+ isCurrent: true,
1942
+ model: "Wan 2.6 Image",
1943
+ rank: 14
1895
1944
  },
1896
1945
  {
1897
- rank: 15,
1946
+ apiPricing: "$66/1k images",
1898
1947
  creator: "Sourceful",
1899
- model: "Riverflow 1",
1900
1948
  elo: 1184,
1901
- apiPricing: "$66/1k images",
1902
- isCurrent: true
1949
+ isCurrent: true,
1950
+ model: "Riverflow 1",
1951
+ rank: 15
1903
1952
  },
1904
1953
  {
1905
- rank: 16,
1954
+ apiPricing: "$30/1k images",
1906
1955
  creator: "ByteDance Seed",
1907
- model: "Seedream 4.0",
1908
1956
  elo: 1184,
1909
- apiPricing: "$30/1k images",
1910
1957
  isCurrent: true,
1958
+ model: "Seedream 4.0",
1959
+ rank: 16,
1911
1960
  routableModelId: "seedream4"
1912
1961
  },
1913
1962
  {
1914
- rank: 17,
1963
+ apiPricing: "$40/1k images",
1915
1964
  creator: "ByteDance Seed",
1916
- model: "Seedream 4.5",
1917
1965
  elo: 1184,
1918
- apiPricing: "$40/1k images",
1919
1966
  isCurrent: true,
1967
+ model: "Seedream 4.5",
1968
+ rank: 17,
1920
1969
  routableModelId: "seedream45"
1921
1970
  },
1922
1971
  {
1923
- rank: 18,
1972
+ apiPricing: "$26/1k images",
1924
1973
  creator: "Alibaba",
1925
- model: "Wan 2.7",
1926
1974
  elo: 1181,
1927
- apiPricing: "$26/1k images",
1928
- isCurrent: true
1975
+ isCurrent: true,
1976
+ model: "Wan 2.7",
1977
+ rank: 18
1929
1978
  },
1930
1979
  {
1931
- rank: 19,
1980
+ apiPricing: "$39/1k images",
1932
1981
  creator: "Google",
1933
- model: "Nano Banana (Gemini 2.5 Flash Image)",
1934
1982
  elo: 1173,
1935
- apiPricing: "$39/1k images",
1936
1983
  isCurrent: true,
1984
+ model: "Nano Banana (Gemini 2.5 Flash Image)",
1985
+ rank: 19,
1937
1986
  routableModelId: "gemini"
1938
1987
  },
1939
1988
  {
1940
- rank: 20,
1989
+ apiPricing: "$40/1k images",
1941
1990
  creator: "Reve",
1942
- model: "Reve V1 (December)",
1943
1991
  elo: 1172,
1944
- apiPricing: "$40/1k images",
1945
- isCurrent: true
1992
+ isCurrent: true,
1993
+ model: "Reve V1 (December)",
1994
+ rank: 20
1946
1995
  }
1947
- ]
1996
+ ],
1997
+ source: "https://artificialanalysis.ai/image/leaderboard/editing"
1948
1998
  };
1949
1999
  var VIDEO_TEXT_TO_VIDEO_TOP_15 = {
1950
2000
  capturedAt: "2026-05-12",
1951
- source: "https://artificialanalysis.ai/video/leaderboard/text-to-video",
1952
2001
  entries: [
1953
2002
  {
1954
- rank: 1,
2003
+ apiPricing: "$14.40/min",
1955
2004
  creator: "Alibaba-ATH",
1956
- model: "HappyHorse-1.0",
1957
2005
  elo: 1354,
1958
- apiPricing: "$14.40/min"
2006
+ model: "HappyHorse-1.0",
2007
+ rank: 1
1959
2008
  },
1960
2009
  {
1961
- rank: 2,
2010
+ apiPricing: "No API available",
1962
2011
  creator: "ByteDance Seed",
1963
- model: "Dreamina Seedance 2.0 720p",
1964
2012
  elo: 1273,
1965
- apiPricing: "No API available"
2013
+ model: "Dreamina Seedance 2.0 720p",
2014
+ rank: 2
1966
2015
  },
1967
2016
  {
1968
- rank: 3,
2017
+ apiPricing: "$13.44/min",
1969
2018
  creator: "KlingAI",
1970
- model: "Kling 3.0 1080p (Pro)",
1971
2019
  elo: 1249,
1972
- apiPricing: "$13.44/min"
2020
+ model: "Kling 3.0 1080p (Pro)",
2021
+ rank: 3
1973
2022
  },
1974
2023
  {
1975
- rank: 4,
2024
+ apiPricing: "$13.44/min",
1976
2025
  creator: "KlingAI",
1977
- model: "Kling 3.0 Omni 1080p (Pro)",
1978
2026
  elo: 1233,
1979
- apiPricing: "$13.44/min"
2027
+ model: "Kling 3.0 Omni 1080p (Pro)",
2028
+ rank: 4
1980
2029
  },
1981
2030
  {
1982
- rank: 5,
2031
+ apiPricing: "$4.20/min",
1983
2032
  creator: "xAI",
1984
- model: "grok-imagine-video",
1985
2033
  elo: 1233,
1986
- apiPricing: "$4.20/min"
2034
+ model: "grok-imagine-video",
2035
+ rank: 5
1987
2036
  },
1988
2037
  {
1989
- rank: 6,
2038
+ apiPricing: "$9.60/min",
1990
2039
  creator: "Vidu",
1991
- model: "Vidu Q3 Pro",
1992
2040
  elo: 1225,
1993
- apiPricing: "$9.60/min"
2041
+ model: "Vidu Q3 Pro",
2042
+ rank: 6
1994
2043
  },
1995
2044
  {
1996
- rank: 7,
2045
+ apiPricing: "$3.00/min",
1997
2046
  creator: "Video Rebirth",
1998
- model: "Bach-1.0 Preview",
1999
2047
  elo: 1224,
2000
- apiPricing: "$3.00/min"
2048
+ model: "Bach-1.0 Preview",
2049
+ rank: 7
2001
2050
  },
2002
2051
  {
2003
- rank: 8,
2052
+ apiPricing: "$10.08/min",
2004
2053
  creator: "KlingAI",
2005
- model: "Kling 3.0 Omni 720p (Standard)",
2006
2054
  elo: 1224,
2007
- apiPricing: "$10.08/min"
2055
+ model: "Kling 3.0 Omni 720p (Standard)",
2056
+ rank: 8
2008
2057
  },
2009
2058
  {
2010
- rank: 9,
2059
+ apiPricing: "$5.40/min",
2011
2060
  creator: "PixVerse",
2012
- model: "PixVerse V6",
2013
2061
  elo: 1222,
2014
- apiPricing: "$5.40/min"
2062
+ model: "PixVerse V6",
2063
+ rank: 9
2015
2064
  },
2016
2065
  {
2017
- rank: 10,
2066
+ apiPricing: "$9.00/min",
2018
2067
  creator: "PixVerse",
2019
- model: "PixVerse V5.6",
2020
2068
  elo: 1221,
2021
- apiPricing: "$9.00/min"
2069
+ model: "PixVerse V5.6",
2070
+ rank: 10
2022
2071
  },
2023
2072
  {
2024
- rank: 11,
2073
+ apiPricing: "No API available",
2025
2074
  creator: "Runway",
2026
- model: "Runway Gen-4.5",
2027
2075
  elo: 1220,
2028
- apiPricing: "No API available"
2076
+ model: "Runway Gen-4.5",
2077
+ rank: 11
2029
2078
  },
2030
2079
  {
2031
- rank: 12,
2080
+ apiPricing: "$12.00/min",
2032
2081
  creator: "Google",
2033
- model: "Veo 3",
2034
2082
  elo: 1218,
2035
- apiPricing: "$12.00/min"
2083
+ model: "Veo 3",
2084
+ rank: 12
2036
2085
  },
2037
2086
  {
2038
- rank: 13,
2087
+ apiPricing: "$10.08/min",
2039
2088
  creator: "KlingAI",
2040
- model: "Kling 3.0 720p (Standard)",
2041
2089
  elo: 1215,
2042
- apiPricing: "$10.08/min"
2090
+ model: "Kling 3.0 720p (Standard)",
2091
+ rank: 13
2043
2092
  },
2044
2093
  {
2045
- rank: 14,
2094
+ apiPricing: "$3.00/min",
2046
2095
  creator: "Google",
2047
- model: "Veo 3.1 Lite",
2048
2096
  elo: 1214,
2049
- apiPricing: "$3.00/min"
2097
+ model: "Veo 3.1 Lite",
2098
+ rank: 14
2050
2099
  },
2051
2100
  {
2052
- rank: 15,
2101
+ apiPricing: "$10.08/min",
2053
2102
  creator: "KlingAI",
2054
- model: "Kling O1 Pro (January)",
2055
2103
  elo: 1209,
2056
- apiPricing: "$10.08/min"
2104
+ model: "Kling O1 Pro (January)",
2105
+ rank: 15
2057
2106
  }
2058
- ]
2107
+ ],
2108
+ source: "https://artificialanalysis.ai/video/leaderboard/text-to-video"
2059
2109
  };
2060
2110
  var VIDEO_IMAGE_TO_VIDEO_TOP_15 = {
2061
2111
  capturedAt: "2026-05-12",
2062
- source: "https://artificialanalysis.ai/video/leaderboard/image-to-video",
2063
2112
  entries: [
2064
2113
  {
2065
- rank: 1,
2114
+ apiPricing: "Coming soon",
2066
2115
  creator: "Alibaba-ATH",
2067
- model: "HappyHorse-1.0",
2068
2116
  elo: 1395,
2069
- apiPricing: "Coming soon"
2117
+ model: "HappyHorse-1.0",
2118
+ rank: 1
2070
2119
  },
2071
2120
  {
2072
- rank: 2,
2121
+ apiPricing: "No API available",
2073
2122
  creator: "ByteDance Seed",
2074
- model: "Dreamina Seedance 2.0 720p",
2075
2123
  elo: 1348,
2076
- apiPricing: "No API available"
2124
+ model: "Dreamina Seedance 2.0 720p",
2125
+ rank: 2
2077
2126
  },
2078
2127
  {
2079
- rank: 3,
2128
+ apiPricing: "$4.20/min",
2080
2129
  creator: "xAI",
2081
- model: "grok-imagine-video",
2082
2130
  elo: 1326,
2083
- apiPricing: "$4.20/min"
2131
+ model: "grok-imagine-video",
2132
+ rank: 3
2084
2133
  },
2085
2134
  {
2086
- rank: 4,
2135
+ apiPricing: "$5.40/min",
2087
2136
  creator: "PixVerse",
2088
- model: "PixVerse V6",
2089
2137
  elo: 1322,
2090
- apiPricing: "$5.40/min"
2138
+ model: "PixVerse V6",
2139
+ rank: 4
2091
2140
  },
2092
2141
  {
2093
- rank: 5,
2142
+ apiPricing: "$9.60/min",
2094
2143
  creator: "Vidu",
2095
- model: "Vidu Q3 Pro",
2096
2144
  elo: 1287,
2097
- apiPricing: "$9.60/min"
2145
+ model: "Vidu Q3 Pro",
2146
+ rank: 5
2098
2147
  },
2099
2148
  {
2100
- rank: 6,
2149
+ apiPricing: "$4.20/min",
2101
2150
  creator: "KlingAI",
2102
- model: "Kling 2.5 Turbo 1080p",
2103
2151
  elo: 1283,
2104
- apiPricing: "$4.20/min"
2152
+ model: "Kling 2.5 Turbo 1080p",
2153
+ rank: 6
2105
2154
  },
2106
2155
  {
2107
- rank: 7,
2156
+ apiPricing: "$13.44/min",
2108
2157
  creator: "KlingAI",
2109
- model: "Kling 3.0 1080p (Pro)",
2110
2158
  elo: 1280,
2111
- apiPricing: "$13.44/min"
2159
+ model: "Kling 3.0 1080p (Pro)",
2160
+ rank: 7
2112
2161
  },
2113
2162
  {
2114
- rank: 8,
2163
+ apiPricing: "$9.00/min",
2115
2164
  creator: "PixVerse",
2116
- model: "PixVerse V5.6",
2117
2165
  elo: 1279,
2118
- apiPricing: "$9.00/min"
2166
+ model: "PixVerse V5.6",
2167
+ rank: 8
2119
2168
  },
2120
2169
  {
2121
- rank: 9,
2170
+ apiPricing: "$13.44/min",
2122
2171
  creator: "KlingAI",
2123
- model: "Kling 3.0 Omni 1080p (Pro)",
2124
2172
  elo: 1277,
2125
- apiPricing: "$13.44/min"
2173
+ model: "Kling 3.0 Omni 1080p (Pro)",
2174
+ rank: 9
2126
2175
  },
2127
2176
  {
2128
- rank: 10,
2177
+ apiPricing: "Coming soon",
2129
2178
  creator: "KlingAI",
2130
- model: "Kling 2.6 Standard (January)",
2131
2179
  elo: 1271,
2132
- apiPricing: "Coming soon"
2180
+ model: "Kling 2.6 Standard (January)",
2181
+ rank: 10
2133
2182
  },
2134
2183
  {
2135
- rank: 11,
2184
+ apiPricing: "$6.40/min",
2136
2185
  creator: "PixVerse",
2137
- model: "PixVerse V5.5",
2138
2186
  elo: 1271,
2139
- apiPricing: "$6.40/min"
2187
+ model: "PixVerse V5.5",
2188
+ rank: 11
2140
2189
  },
2141
2190
  {
2142
- rank: 12,
2191
+ apiPricing: "$6.00/min",
2143
2192
  creator: "Google",
2144
- model: "Veo 3.1 Fast",
2145
2193
  elo: 1268,
2146
- apiPricing: "$6.00/min"
2194
+ model: "Veo 3.1 Fast",
2195
+ rank: 12
2147
2196
  },
2148
2197
  {
2149
- rank: 13,
2198
+ apiPricing: "No API available",
2150
2199
  creator: "Runway",
2151
- model: "Runway Gen-4.5",
2152
2200
  elo: 1263,
2153
- apiPricing: "No API available"
2201
+ model: "Runway Gen-4.5",
2202
+ rank: 13
2154
2203
  },
2155
2204
  {
2156
- rank: 14,
2205
+ apiPricing: "$10.08/min",
2157
2206
  creator: "KlingAI",
2158
- model: "Kling 3.0 Omni 720p (Standard)",
2159
2207
  elo: 1263,
2160
- apiPricing: "$10.08/min"
2208
+ model: "Kling 3.0 Omni 720p (Standard)",
2209
+ rank: 14
2161
2210
  },
2162
2211
  {
2163
- rank: 15,
2212
+ apiPricing: "$10.08/min",
2164
2213
  creator: "KlingAI",
2165
- model: "Kling 3.0 720p (Standard)",
2166
2214
  elo: 1263,
2167
- apiPricing: "$10.08/min"
2215
+ model: "Kling 3.0 720p (Standard)",
2216
+ rank: 15
2168
2217
  }
2169
- ]
2218
+ ],
2219
+ source: "https://artificialanalysis.ai/video/leaderboard/image-to-video"
2170
2220
  };
2171
2221
 
2172
2222
  // src/server.ts
2173
2223
  var import_neverthrow = require("neverthrow");
2174
2224
 
2225
+ // src/fal-parse.ts
2226
+ function isRecord(value) {
2227
+ return typeof value === "object" && value !== null;
2228
+ }
2229
+ function asString(value) {
2230
+ return typeof value === "string" ? value : void 0;
2231
+ }
2232
+ function asNumber(value) {
2233
+ return typeof value === "number" ? value : void 0;
2234
+ }
2235
+ function toMotifImage(value) {
2236
+ if (isRecord(value)) {
2237
+ return {
2238
+ content_type: asString(value.content_type),
2239
+ height: asNumber(value.height),
2240
+ url: asString(value.url) ?? "",
2241
+ width: asNumber(value.width)
2242
+ };
2243
+ }
2244
+ return { url: "" };
2245
+ }
2246
+ function parseImages(value) {
2247
+ if (!Array.isArray(value)) {
2248
+ return [];
2249
+ }
2250
+ return value.map((entry) => toMotifImage(entry));
2251
+ }
2252
+ function parseLogs(value) {
2253
+ if (!Array.isArray(value)) {
2254
+ return void 0;
2255
+ }
2256
+ const entries = [];
2257
+ for (const entry of value) {
2258
+ if (isRecord(entry)) {
2259
+ entries.push({
2260
+ message: asString(entry.message) ?? "",
2261
+ timestamp: asString(entry.timestamp) ?? ""
2262
+ });
2263
+ }
2264
+ }
2265
+ return entries;
2266
+ }
2267
+ function endpointFromQueueUrl(url, fallback) {
2268
+ if (url === void 0 || url === "") {
2269
+ return fallback;
2270
+ }
2271
+ try {
2272
+ const parsed = new URL(url);
2273
+ const match = /^\/(.+)\/requests\//.exec(parsed.pathname);
2274
+ return match?.[1] ?? fallback;
2275
+ } catch {
2276
+ return fallback;
2277
+ }
2278
+ }
2279
+ function parseQueueSubmission(data) {
2280
+ if (!isRecord(data)) {
2281
+ return { requestId: "" };
2282
+ }
2283
+ return {
2284
+ requestId: asString(data.request_id) ?? "",
2285
+ responseUrl: asString(data.response_url)
2286
+ };
2287
+ }
2288
+ function toHeaderRecord(headers) {
2289
+ if (headers === void 0) {
2290
+ return {};
2291
+ }
2292
+ if (headers instanceof Headers) {
2293
+ const record = {};
2294
+ for (const [key, value] of headers.entries()) {
2295
+ record[key] = value;
2296
+ }
2297
+ return record;
2298
+ }
2299
+ if (Array.isArray(headers)) {
2300
+ const record = {};
2301
+ for (const [key, value] of headers) {
2302
+ record[key] = value;
2303
+ }
2304
+ return record;
2305
+ }
2306
+ return headers;
2307
+ }
2308
+ function requestIdFromBody(text) {
2309
+ let parsed;
2310
+ try {
2311
+ parsed = JSON.parse(text);
2312
+ } catch {
2313
+ return void 0;
2314
+ }
2315
+ if (!isRecord(parsed)) {
2316
+ return void 0;
2317
+ }
2318
+ return asString(parsed.request_id) ?? asString(parsed.requestId) ?? asString(parsed.trace_id);
2319
+ }
2320
+
2175
2321
  // src/tools.ts
2176
2322
  var FAL_EXPLORE_CHECKED_AT = "2026-05-12";
2177
2323
  var FAL_TOOLS = {
2178
- nsfw: {
2179
- name: "NSFW Checker",
2180
- endpoint: "fal-ai/x-ailab/nsfw",
2181
- task: "vision moderation",
2182
- category: "moderation",
2183
- description: "Predict whether one or more images contain NSFW concepts.",
2184
- inputKind: "images",
2185
- inputField: "image_urls",
2186
- outputKeys: ["has_nsfw_concepts"],
2187
- pricing: "$0.001/image",
2188
- sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw"
2189
- },
2190
- "topaz-image": {
2191
- name: "Topaz Image Upscale",
2192
- endpoint: "fal-ai/topaz/upscale/image",
2193
- task: "image enhancement",
2194
- category: "upscale",
2195
- description: "Professional Topaz image enhancement and upscaling.",
2196
- inputKind: "image",
2197
- inputField: "image_url",
2198
- outputKeys: ["image"],
2199
- pricing: "$0.08+ by output megapixels",
2200
- sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
2201
- defaultOptions: {
2202
- model: "Standard V2",
2203
- output_format: "jpeg",
2204
- upscale_factor: 2
2205
- }
2206
- },
2207
- "topaz-video": {
2208
- name: "Topaz Video Upscale",
2209
- endpoint: "fal-ai/topaz/upscale/video",
2210
- task: "video enhancement",
2211
- category: "upscale",
2212
- description: "Professional Topaz video enhancement and upscaling.",
2213
- inputKind: "video",
2214
- inputField: "video_url",
2215
- outputKeys: ["video"],
2216
- pricing: "$0.01-$0.08/sec by output resolution",
2217
- sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
2218
- defaultOptions: {
2219
- model: "Proteus",
2220
- upscale_factor: 2
2221
- }
2222
- },
2223
- "bria-rmbg": {
2224
- name: "Bria RMBG 2.0",
2225
- endpoint: "fal-ai/bria/background/remove",
2226
- task: "image background removal",
2227
- category: "background",
2228
- description: "Commercial-safe background removal for images.",
2229
- inputKind: "image",
2230
- inputField: "image_url",
2231
- outputKeys: ["image"],
2232
- pricing: "$0.02/image",
2233
- sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove"
2234
- },
2235
2324
  birefnet: {
2236
- name: "BirefNet Background Removal",
2237
- endpoint: "fal-ai/birefnet/v2",
2238
- task: "image background removal",
2239
2325
  category: "background",
2240
- description: "High-resolution dichotomous image segmentation and background removal.",
2241
- inputKind: "image",
2242
- inputField: "image_url",
2243
- outputKeys: ["image", "mask_image"],
2244
- pricing: "$0/compute-second listed by fal",
2245
- sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
2246
2326
  defaultOptions: {
2247
2327
  model: "General Use (Light)",
2248
2328
  operating_resolution: "1024x1024",
2249
2329
  output_format: "png",
2250
2330
  refine_foreground: true
2251
- }
2331
+ },
2332
+ description: "High-resolution dichotomous image segmentation and background removal.",
2333
+ endpoint: "fal-ai/birefnet/v2",
2334
+ inputField: "image_url",
2335
+ inputKind: "image",
2336
+ name: "BirefNet Background Removal",
2337
+ outputKeys: ["image", "mask_image"],
2338
+ pricing: "$0/compute-second listed by fal",
2339
+ sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
2340
+ task: "image background removal"
2252
2341
  },
2253
- rembg: {
2254
- name: "Remove Background",
2255
- endpoint: "fal-ai/imageutils/rembg",
2256
- task: "image background removal",
2342
+ "bria-rmbg": {
2257
2343
  category: "background",
2258
- description: "Generic image background removal utility.",
2259
- inputKind: "image",
2344
+ description: "Commercial-safe background removal for images.",
2345
+ endpoint: "fal-ai/bria/background/remove",
2260
2346
  inputField: "image_url",
2347
+ inputKind: "image",
2348
+ name: "Bria RMBG 2.0",
2261
2349
  outputKeys: ["image"],
2262
- pricing: "$0/compute-second listed by fal",
2263
- sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
2264
- defaultOptions: {
2265
- crop_to_bbox: false
2266
- }
2350
+ pricing: "$0.02/image",
2351
+ sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove",
2352
+ task: "image background removal"
2267
2353
  },
2268
2354
  "bria-video-rmbg": {
2269
- name: "Bria Video Background Removal",
2270
- endpoint: "bria/video/background-removal",
2271
- task: "video background removal",
2272
2355
  category: "background",
2356
+ defaultOptions: {
2357
+ background_color: "Black",
2358
+ output_container_and_codec: "webm_vp9",
2359
+ preserve_audio: true
2360
+ },
2273
2361
  description: "Remove video backgrounds with configurable output container.",
2274
- inputKind: "video",
2362
+ endpoint: "bria/video/background-removal",
2275
2363
  inputField: "video_url",
2364
+ inputKind: "video",
2365
+ name: "Bria Video Background Removal",
2276
2366
  outputKeys: ["video"],
2277
2367
  pricing: "$0.14/sec",
2278
2368
  sourceUrl: "https://fal.ai/models/bria/video/background-removal",
2279
- defaultOptions: {
2280
- background_color: "Black",
2281
- output_container_and_codec: "webm_vp9",
2282
- preserve_audio: true
2283
- }
2369
+ task: "video background removal"
2370
+ },
2371
+ "depth-anything": {
2372
+ category: "preprocess",
2373
+ description: "Generate Depth Anything v2 depth maps from input images.",
2374
+ endpoint: "fal-ai/image-preprocessors/depth-anything/v2",
2375
+ inputField: "image_url",
2376
+ inputKind: "image",
2377
+ name: "Depth Anything v2 Preprocessor",
2378
+ outputKeys: ["image"],
2379
+ pricing: "$0/compute-second listed by fal",
2380
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
2381
+ task: "depth preprocessing"
2284
2382
  },
2285
2383
  lineart: {
2286
- name: "Line Art Preprocessor",
2287
- endpoint: "fal-ai/image-preprocessors/lineart",
2288
- task: "image preprocessing",
2289
2384
  category: "preprocess",
2385
+ defaultOptions: {
2386
+ coarse: false
2387
+ },
2290
2388
  description: "Generate line art/control-style edges from an input image.",
2291
- inputKind: "image",
2389
+ endpoint: "fal-ai/image-preprocessors/lineart",
2292
2390
  inputField: "image_url",
2391
+ inputKind: "image",
2392
+ name: "Line Art Preprocessor",
2293
2393
  outputKeys: ["image"],
2294
2394
  pricing: "$0/compute-second listed by fal",
2295
2395
  sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
2296
- defaultOptions: {
2297
- coarse: false
2298
- }
2396
+ task: "image preprocessing"
2299
2397
  },
2300
- "sam-preprocessor": {
2301
- name: "SAM Preprocessor",
2302
- endpoint: "fal-ai/image-preprocessors/sam",
2303
- task: "segmentation preprocessing",
2304
- category: "preprocess",
2305
- description: "Generate a SAM segmentation map for ControlNet-style workflows.",
2306
- inputKind: "image",
2398
+ "marigold-depth": {
2399
+ category: "depth",
2400
+ defaultOptions: {
2401
+ ensemble_size: 10,
2402
+ num_inference_steps: 10
2403
+ },
2404
+ description: "Create depth maps using Marigold depth estimation.",
2405
+ endpoint: "fal-ai/imageutils/marigold-depth",
2307
2406
  inputField: "image_url",
2407
+ inputKind: "image",
2408
+ name: "Marigold Depth Estimation",
2308
2409
  outputKeys: ["image"],
2309
2410
  pricing: "$0/compute-second listed by fal",
2310
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam"
2411
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
2412
+ task: "depth map"
2311
2413
  },
2312
2414
  "midas-depth": {
2313
- name: "MiDaS Depth Estimation",
2314
- endpoint: "fal-ai/imageutils/depth",
2315
- task: "depth map",
2316
2415
  category: "depth",
2416
+ defaultOptions: {
2417
+ a: Math.PI * 2,
2418
+ bg_th: 0.1
2419
+ },
2317
2420
  description: "Create MiDaS depth maps from input images.",
2318
- inputKind: "image",
2421
+ endpoint: "fal-ai/imageutils/depth",
2319
2422
  inputField: "image_url",
2423
+ inputKind: "image",
2424
+ name: "MiDaS Depth Estimation",
2320
2425
  outputKeys: ["image"],
2321
2426
  pricing: "$0/compute-second listed by fal",
2322
2427
  sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
2323
- defaultOptions: {
2324
- a: Math.PI * 2,
2325
- bg_th: 0.1
2326
- }
2428
+ task: "depth map"
2327
2429
  },
2328
2430
  "midas-preprocessor": {
2329
- name: "MiDaS Preprocessor",
2330
- endpoint: "fal-ai/image-preprocessors/midas",
2331
- task: "depth and normal preprocessing",
2332
2431
  category: "preprocess",
2333
2432
  description: "Generate MiDaS depth and normal maps for image workflows.",
2334
- inputKind: "image",
2433
+ endpoint: "fal-ai/image-preprocessors/midas",
2335
2434
  inputField: "image_url",
2435
+ inputKind: "image",
2436
+ name: "MiDaS Preprocessor",
2336
2437
  outputKeys: ["depth_map", "normal_map"],
2337
2438
  pricing: "$0/compute-second listed by fal",
2338
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas"
2439
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
2440
+ task: "depth and normal preprocessing"
2339
2441
  },
2340
- "marigold-depth": {
2341
- name: "Marigold Depth Estimation",
2342
- endpoint: "fal-ai/imageutils/marigold-depth",
2343
- task: "depth map",
2344
- category: "depth",
2345
- description: "Create depth maps using Marigold depth estimation.",
2346
- inputKind: "image",
2442
+ nsfw: {
2443
+ category: "moderation",
2444
+ description: "Predict whether one or more images contain NSFW concepts.",
2445
+ endpoint: "fal-ai/x-ailab/nsfw",
2446
+ inputField: "image_urls",
2447
+ inputKind: "images",
2448
+ name: "NSFW Checker",
2449
+ outputKeys: ["has_nsfw_concepts"],
2450
+ pricing: "$0.001/image",
2451
+ sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
2452
+ task: "vision moderation"
2453
+ },
2454
+ rembg: {
2455
+ category: "background",
2456
+ defaultOptions: {
2457
+ crop_to_bbox: false
2458
+ },
2459
+ description: "Generic image background removal utility.",
2460
+ endpoint: "fal-ai/imageutils/rembg",
2347
2461
  inputField: "image_url",
2462
+ inputKind: "image",
2463
+ name: "Remove Background",
2348
2464
  outputKeys: ["image"],
2349
2465
  pricing: "$0/compute-second listed by fal",
2350
- sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
2351
- defaultOptions: {
2352
- ensemble_size: 10,
2353
- num_inference_steps: 10
2354
- }
2466
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
2467
+ task: "image background removal"
2355
2468
  },
2356
- "depth-anything": {
2357
- name: "Depth Anything v2 Preprocessor",
2358
- endpoint: "fal-ai/image-preprocessors/depth-anything/v2",
2359
- task: "depth preprocessing",
2469
+ "sam-preprocessor": {
2360
2470
  category: "preprocess",
2361
- description: "Generate Depth Anything v2 depth maps from input images.",
2362
- inputKind: "image",
2471
+ description: "Generate a SAM segmentation map for ControlNet-style workflows.",
2472
+ endpoint: "fal-ai/image-preprocessors/sam",
2363
2473
  inputField: "image_url",
2474
+ inputKind: "image",
2475
+ name: "SAM Preprocessor",
2364
2476
  outputKeys: ["image"],
2365
2477
  pricing: "$0/compute-second listed by fal",
2366
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2"
2478
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
2479
+ task: "segmentation preprocessing"
2480
+ },
2481
+ "sam2-auto": {
2482
+ category: "segmentation",
2483
+ defaultOptions: {
2484
+ min_mask_region_area: 100,
2485
+ output_format: "png",
2486
+ points_per_side: 32,
2487
+ pred_iou_thresh: 0.88,
2488
+ stability_score_thresh: 0.95
2489
+ },
2490
+ description: "Automatically segment an image into combined and individual masks.",
2491
+ endpoint: "fal-ai/sam2/auto-segment",
2492
+ inputField: "image_url",
2493
+ inputKind: "image",
2494
+ name: "SAM 2 Auto Segment",
2495
+ outputKeys: ["combined_mask", "individual_masks"],
2496
+ pricing: "$0/compute-second listed by fal",
2497
+ sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
2498
+ task: "automatic image segmentation"
2499
+ },
2500
+ "sam3-1-image": {
2501
+ category: "segmentation",
2502
+ defaultOptions: {
2503
+ apply_mask: true,
2504
+ max_masks: 3,
2505
+ output_format: "png"
2506
+ },
2507
+ description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
2508
+ endpoint: "fal-ai/sam-3-1/image",
2509
+ inputField: "image_url",
2510
+ inputKind: "image",
2511
+ name: "SAM 3.1 Image",
2512
+ outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
2513
+ pricing: "$0.005/request",
2514
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
2515
+ task: "promptable image segmentation"
2516
+ },
2517
+ "sam3-1-video": {
2518
+ category: "segmentation",
2519
+ defaultOptions: {
2520
+ apply_mask: true,
2521
+ prompt: "person"
2522
+ },
2523
+ description: "SAM 3.1 video segmentation with Object Multiplex tracking for multiple objects.",
2524
+ endpoint: "fal-ai/sam-3-1/video",
2525
+ inputField: "video_url",
2526
+ inputKind: "video",
2527
+ name: "SAM 3.1 Video",
2528
+ outputKeys: ["video", "boundingbox_frames_zip"],
2529
+ pricing: "fal pricing varies by frame count",
2530
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
2531
+ task: "multi-object video segmentation"
2367
2532
  },
2368
- "sam2-auto": {
2369
- name: "SAM 2 Auto Segment",
2370
- endpoint: "fal-ai/sam2/auto-segment",
2371
- task: "automatic image segmentation",
2372
- category: "segmentation",
2373
- description: "Automatically segment an image into combined and individual masks.",
2533
+ "sam3-3d-align": {
2534
+ category: "3d",
2535
+ description: "Align SAM 3D objects and bodies into a shared scene.",
2536
+ endpoint: "fal-ai/sam-3/3d-align",
2537
+ inputField: "image_url",
2374
2538
  inputKind: "image",
2539
+ name: "SAM 3D Align",
2540
+ outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
2541
+ pricing: "fal pricing varies by scene",
2542
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
2543
+ task: "3D scene alignment"
2544
+ },
2545
+ "sam3-3d-body": {
2546
+ category: "3d",
2547
+ defaultOptions: {
2548
+ export_meshes: true,
2549
+ include_3d_keypoints: true,
2550
+ include_mhr_params: true
2551
+ },
2552
+ description: "Reconstruct human body meshes and keypoints from a single image.",
2553
+ endpoint: "fal-ai/sam-3/3d-body",
2375
2554
  inputField: "image_url",
2376
- outputKeys: ["combined_mask", "individual_masks"],
2377
- pricing: "$0/compute-second listed by fal",
2378
- sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
2555
+ inputKind: "image",
2556
+ name: "SAM 3D Body",
2557
+ outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
2558
+ pricing: "$0.015/inference",
2559
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
2560
+ task: "single-image 3D body reconstruction"
2561
+ },
2562
+ "sam3-3d-objects": {
2563
+ category: "3d",
2379
2564
  defaultOptions: {
2380
- min_mask_region_area: 100,
2381
- output_format: "png",
2382
- points_per_side: 32,
2383
- pred_iou_thresh: 0.88,
2384
- stability_score_thresh: 0.95
2385
- }
2565
+ prompt: "car"
2566
+ },
2567
+ description: "Reconstruct one or more 3D objects from an image and prompts.",
2568
+ endpoint: "fal-ai/sam-3/3d-objects",
2569
+ inputField: "image_url",
2570
+ inputKind: "image",
2571
+ name: "SAM 3D Objects",
2572
+ outputKeys: [
2573
+ "gaussian_splat",
2574
+ "model_glb",
2575
+ "metadata",
2576
+ "individual_splats",
2577
+ "individual_glbs",
2578
+ "artifacts_zip"
2579
+ ],
2580
+ pricing: "$0.02/generation",
2581
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
2582
+ task: "single-image 3D object reconstruction"
2386
2583
  },
2387
2584
  "sam3-image": {
2388
- name: "SAM 3 Image",
2389
- endpoint: "fal-ai/sam-3/image",
2390
- task: "promptable image segmentation",
2391
2585
  category: "segmentation",
2392
- description: "Segment image objects with text, point, or box prompts.",
2393
- inputKind: "image",
2394
- inputField: "image_url",
2395
- outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
2396
- pricing: "$0.005/request",
2397
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
2398
2586
  defaultOptions: {
2399
2587
  apply_mask: true,
2400
2588
  max_masks: 3,
2401
2589
  output_format: "png"
2402
- }
2403
- },
2404
- "sam3-1-image": {
2405
- name: "SAM 3.1 Image",
2406
- endpoint: "fal-ai/sam-3-1/image",
2407
- task: "promptable image segmentation",
2408
- category: "segmentation",
2409
- description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
2410
- inputKind: "image",
2590
+ },
2591
+ description: "Segment image objects with text, point, or box prompts.",
2592
+ endpoint: "fal-ai/sam-3/image",
2411
2593
  inputField: "image_url",
2594
+ inputKind: "image",
2595
+ name: "SAM 3 Image",
2412
2596
  outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
2413
2597
  pricing: "$0.005/request",
2414
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
2415
- defaultOptions: {
2416
- apply_mask: true,
2417
- max_masks: 3,
2418
- output_format: "png"
2419
- }
2598
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
2599
+ task: "promptable image segmentation"
2420
2600
  },
2421
2601
  "sam3-image-rle": {
2422
- name: "SAM 3 Image RLE",
2423
- endpoint: "fal-ai/sam-3/image-rle",
2424
- task: "promptable image segmentation to RLE",
2425
2602
  category: "segmentation",
2603
+ defaultOptions: {
2604
+ apply_mask: true,
2605
+ max_masks: 3
2606
+ },
2426
2607
  description: "Segment image objects and return run-length encoded masks.",
2427
- inputKind: "image",
2608
+ endpoint: "fal-ai/sam-3/image-rle",
2428
2609
  inputField: "image_url",
2610
+ inputKind: "image",
2611
+ name: "SAM 3 Image RLE",
2429
2612
  outputKeys: ["rle_masks", "metadata", "scores", "boxes"],
2430
2613
  pricing: "$0.005/request",
2431
2614
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
2432
- defaultOptions: {
2433
- apply_mask: true,
2434
- max_masks: 3
2435
- }
2615
+ task: "promptable image segmentation to RLE"
2436
2616
  },
2437
2617
  "sam3-video": {
2438
- name: "SAM 3 Video",
2439
- endpoint: "fal-ai/sam-3/video",
2440
- task: "promptable video segmentation",
2441
2618
  category: "segmentation",
2442
- description: "Segment and track prompted objects across video frames.",
2443
- inputKind: "video",
2444
- inputField: "video_url",
2445
- outputKeys: ["video", "boundingbox_frames_zip"],
2446
- pricing: "$0.005/16 frames",
2447
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
2448
2619
  defaultOptions: {
2449
2620
  apply_mask: true,
2450
2621
  detection_threshold: 0.5,
2451
2622
  prompt: "person",
2452
2623
  video_output_type: "X264 (.mp4)"
2453
- }
2624
+ },
2625
+ description: "Segment and track prompted objects across video frames.",
2626
+ endpoint: "fal-ai/sam-3/video",
2627
+ inputField: "video_url",
2628
+ inputKind: "video",
2629
+ name: "SAM 3 Video",
2630
+ outputKeys: ["video", "boundingbox_frames_zip"],
2631
+ pricing: "$0.005/16 frames",
2632
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
2633
+ task: "promptable video segmentation"
2454
2634
  },
2455
2635
  "sam3-video-rle": {
2456
- name: "SAM 3 Video RLE",
2457
- endpoint: "fal-ai/sam-3/video-rle",
2458
- task: "promptable video segmentation to RLE",
2459
2636
  category: "segmentation",
2637
+ defaultOptions: {
2638
+ apply_mask: true,
2639
+ detection_threshold: 0.5,
2640
+ prompt: "person"
2641
+ },
2460
2642
  description: "Track prompted video objects and return RLE mask data.",
2461
- inputKind: "video",
2643
+ endpoint: "fal-ai/sam-3/video-rle",
2462
2644
  inputField: "video_url",
2645
+ inputKind: "video",
2646
+ name: "SAM 3 Video RLE",
2463
2647
  outputKeys: ["rle_masks", "metadata"],
2464
2648
  pricing: "$0.005/16 frames",
2465
2649
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
2466
- defaultOptions: {
2467
- apply_mask: true,
2468
- detection_threshold: 0.5,
2469
- prompt: "person"
2470
- }
2650
+ task: "promptable video segmentation to RLE"
2471
2651
  },
2472
- "sam3-1-video": {
2473
- name: "SAM 3.1 Video",
2474
- endpoint: "fal-ai/sam-3-1/video",
2475
- task: "multi-object video segmentation",
2476
- category: "segmentation",
2477
- description: "SAM 3.1 video segmentation with Object Multiplex tracking for multiple objects.",
2478
- inputKind: "video",
2479
- inputField: "video_url",
2480
- outputKeys: ["video", "boundingbox_frames_zip"],
2481
- pricing: "fal pricing varies by frame count",
2482
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
2652
+ "topaz-image": {
2653
+ category: "upscale",
2483
2654
  defaultOptions: {
2484
- apply_mask: true,
2485
- prompt: "person"
2486
- }
2487
- },
2488
- "sam3-3d-objects": {
2489
- name: "SAM 3D Objects",
2490
- endpoint: "fal-ai/sam-3/3d-objects",
2491
- task: "single-image 3D object reconstruction",
2492
- category: "3d",
2493
- description: "Reconstruct one or more 3D objects from an image and prompts.",
2494
- inputKind: "image",
2655
+ model: "Standard V2",
2656
+ output_format: "jpeg",
2657
+ upscale_factor: 2
2658
+ },
2659
+ description: "Professional Topaz image enhancement and upscaling.",
2660
+ endpoint: "fal-ai/topaz/upscale/image",
2495
2661
  inputField: "image_url",
2496
- outputKeys: [
2497
- "gaussian_splat",
2498
- "model_glb",
2499
- "metadata",
2500
- "individual_splats",
2501
- "individual_glbs",
2502
- "artifacts_zip"
2503
- ],
2504
- pricing: "$0.02/generation",
2505
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
2506
- defaultOptions: {
2507
- prompt: "car"
2508
- }
2509
- },
2510
- "sam3-3d-body": {
2511
- name: "SAM 3D Body",
2512
- endpoint: "fal-ai/sam-3/3d-body",
2513
- task: "single-image 3D body reconstruction",
2514
- category: "3d",
2515
- description: "Reconstruct human body meshes and keypoints from a single image.",
2516
2662
  inputKind: "image",
2517
- inputField: "image_url",
2518
- outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
2519
- pricing: "$0.015/inference",
2520
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
2521
- defaultOptions: {
2522
- export_meshes: true,
2523
- include_3d_keypoints: true,
2524
- include_mhr_params: true
2525
- }
2663
+ name: "Topaz Image Upscale",
2664
+ outputKeys: ["image"],
2665
+ pricing: "$0.08+ by output megapixels",
2666
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
2667
+ task: "image enhancement"
2526
2668
  },
2527
- "sam3-3d-align": {
2528
- name: "SAM 3D Align",
2529
- endpoint: "fal-ai/sam-3/3d-align",
2530
- task: "3D scene alignment",
2531
- category: "3d",
2532
- description: "Align SAM 3D objects and bodies into a shared scene.",
2533
- inputKind: "image",
2534
- inputField: "image_url",
2535
- outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
2536
- pricing: "fal pricing varies by scene",
2537
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align"
2669
+ "topaz-video": {
2670
+ category: "upscale",
2671
+ defaultOptions: {
2672
+ model: "Proteus",
2673
+ upscale_factor: 2
2674
+ },
2675
+ description: "Professional Topaz video enhancement and upscaling.",
2676
+ endpoint: "fal-ai/topaz/upscale/video",
2677
+ inputField: "video_url",
2678
+ inputKind: "video",
2679
+ name: "Topaz Video Upscale",
2680
+ outputKeys: ["video"],
2681
+ pricing: "$0.01-$0.08/sec by output resolution",
2682
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
2683
+ task: "video enhancement"
2538
2684
  }
2539
2685
  };
2540
2686
  var FAL_TOOLS_CHECKED_AT = FAL_EXPLORE_CHECKED_AT;
2541
- var FAL_TOOL_IDS = Object.keys(FAL_TOOLS);
2687
+ var FAL_TOOL_IDS = Object.keys(
2688
+ FAL_TOOLS
2689
+ );
2542
2690
  function isFalToolId(tool) {
2543
2691
  return Object.hasOwn(FAL_TOOLS, tool);
2544
2692
  }
@@ -2547,7 +2695,7 @@ function buildFalToolRequest(options) {
2547
2695
  throw new Error(`Unknown fal tool: ${options.tool}`);
2548
2696
  }
2549
2697
  const tool = FAL_TOOLS[options.tool];
2550
- const values = options.inputs ?? (options.input ? [options.input] : []);
2698
+ const values = options.inputs ?? (options.input !== void 0 && options.input !== "" ? [options.input] : []);
2551
2699
  if (values.length === 0) {
2552
2700
  throw new Error(`${options.tool} requires input media`);
2553
2701
  }
@@ -2558,7 +2706,7 @@ function buildFalToolRequest(options) {
2558
2706
  ...options.options,
2559
2707
  [tool.inputField]: mediaValue
2560
2708
  };
2561
- return { endpoint: tool.endpoint, body, tool };
2709
+ return { body, endpoint: tool.endpoint, tool };
2562
2710
  }
2563
2711
 
2564
2712
  // src/server.ts
@@ -2566,16 +2714,6 @@ var FAL_BASE_URL = "https://fal.run";
2566
2714
  var FAL_QUEUE_URL = "https://queue.fal.run";
2567
2715
  var FAL_API_URL = "https://api.fal.ai";
2568
2716
  var FAL_REST_URL = "https://rest.alpha.fal.ai";
2569
- function endpointFromQueueUrl(url, fallback) {
2570
- if (!url) return fallback;
2571
- try {
2572
- const parsed = new URL(url);
2573
- const match = parsed.pathname.match(/^\/(.+)\/requests\//);
2574
- return match?.[1] ?? fallback;
2575
- } catch {
2576
- return fallback;
2577
- }
2578
- }
2579
2717
  var MotifServer = class {
2580
2718
  apiKey;
2581
2719
  timeout;
@@ -2598,8 +2736,8 @@ var MotifServer = class {
2598
2736
  /** Generate images synchronously (blocks until fal.ai returns). */
2599
2737
  async generate(options) {
2600
2738
  const config = MODELS[options.model];
2601
- if (config?.useQueue) {
2602
- return this.generateQueued(options);
2739
+ if (config?.useQueue === true) {
2740
+ return await this.generateQueued(options);
2603
2741
  }
2604
2742
  let built;
2605
2743
  try {
@@ -2609,9 +2747,9 @@ var MotifServer = class {
2609
2747
  }
2610
2748
  const { endpoint, body } = built;
2611
2749
  const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
2612
- method: "POST",
2613
2750
  body: JSON.stringify(body),
2614
- headers: this.ephemeralHeaders(options)
2751
+ headers: this.ephemeralHeaders(options),
2752
+ method: "POST"
2615
2753
  });
2616
2754
  if (response.isErr()) {
2617
2755
  return (0, import_neverthrow.err)(response.error);
@@ -2635,14 +2773,15 @@ var MotifServer = class {
2635
2773
  return (0, import_neverthrow.err)(status.error);
2636
2774
  }
2637
2775
  if (status.value.status === "completed") {
2638
- return this.getJobResult(job.value.endpoint, job.value.requestId);
2776
+ return await this.getJobResult(job.value.endpoint, job.value.requestId);
2639
2777
  }
2640
2778
  if (status.value.status === "failed") {
2641
- return (0, import_neverthrow.err)(
2642
- new MotifError(status.value.error ?? "Queued generation failed", 0)
2643
- );
2779
+ const message = status.value.error ?? "Queued generation failed";
2780
+ return (0, import_neverthrow.err)(new MotifError(message, 0, void 0, job.value.requestId));
2644
2781
  }
2645
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2782
+ await new Promise((resolve) => {
2783
+ setTimeout(resolve, pollIntervalMs);
2784
+ });
2646
2785
  }
2647
2786
  return (0, import_neverthrow.err)(new MotifError("Queued generation timed out", 0));
2648
2787
  }
@@ -2657,22 +2796,23 @@ var MotifServer = class {
2657
2796
  }
2658
2797
  const { endpoint, body } = built;
2659
2798
  const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
2660
- method: "POST",
2661
2799
  body: JSON.stringify(body),
2662
- headers: this.ephemeralHeaders(options)
2800
+ headers: this.ephemeralHeaders(options),
2801
+ method: "POST"
2663
2802
  });
2664
2803
  if (response.isErr()) {
2665
2804
  return (0, import_neverthrow.err)(response.error);
2666
2805
  }
2667
2806
  const data = await response.value.json();
2807
+ const submission = parseQueueSubmission(data);
2668
2808
  return (0, import_neverthrow.ok)({
2669
- requestId: data.request_id,
2670
- endpoint: endpointFromQueueUrl(data.response_url, endpoint),
2809
+ endpoint: endpointFromQueueUrl(submission.responseUrl, endpoint),
2671
2810
  estimatedCost: estimateCost(
2672
2811
  options.model,
2673
2812
  options.resolution,
2674
2813
  options.numImages
2675
- )
2814
+ ),
2815
+ requestId: submission.requestId
2676
2816
  });
2677
2817
  }
2678
2818
  /** Check the status of a queued generation. */
@@ -2683,23 +2823,25 @@ var MotifServer = class {
2683
2823
  return (0, import_neverthrow.err)(response.error);
2684
2824
  }
2685
2825
  const data = await response.value.json();
2826
+ const record = isRecord(data) ? data : {};
2827
+ const rawStatus = asString(record.status);
2686
2828
  let status;
2687
- if (data.status === "IN_QUEUE") {
2829
+ if (rawStatus === "IN_QUEUE") {
2688
2830
  status = "queued";
2689
- } else if (data.status === "IN_PROGRESS") {
2831
+ } else if (rawStatus === "IN_PROGRESS") {
2690
2832
  status = "processing";
2691
- } else if (data.status === "COMPLETED") {
2833
+ } else if (rawStatus === "COMPLETED") {
2692
2834
  status = "completed";
2693
- } else if (data.status === "FAILED" || data.status === "ERROR" || data.status === "CANCELED") {
2835
+ } else if (rawStatus === "FAILED" || rawStatus === "ERROR" || rawStatus === "CANCELED") {
2694
2836
  status = "failed";
2695
2837
  } else {
2696
- return (0, import_neverthrow.err)(new MotifError(`Unknown job status: ${data.status}`, 0));
2838
+ return (0, import_neverthrow.err)(new MotifError(`Unknown job status: ${rawStatus}`, 0));
2697
2839
  }
2698
2840
  return (0, import_neverthrow.ok)({
2699
- status,
2700
- error: data.error ?? data.detail,
2701
- queuePosition: data.queue_position,
2702
- logs: data.logs
2841
+ error: asString(record.error) ?? asString(record.detail),
2842
+ logs: parseLogs(record.logs),
2843
+ queuePosition: asNumber(record.queue_position),
2844
+ status
2703
2845
  });
2704
2846
  }
2705
2847
  /** Fetch the completed result from the queue. */
@@ -2732,21 +2874,38 @@ var MotifServer = class {
2732
2874
  }
2733
2875
  const body = { image_url: imageUrl };
2734
2876
  if (model === "crystal") {
2735
- if (scaleFactor !== void 0) body.scale_factor = scaleFactor;
2736
- if (creativity !== void 0) body.creativity = creativity;
2877
+ if (scaleFactor !== void 0) {
2878
+ body.scale_factor = scaleFactor;
2879
+ }
2880
+ if (creativity !== void 0) {
2881
+ body.creativity = creativity;
2882
+ }
2737
2883
  } else {
2738
- if (scaleFactor !== void 0) body.upscale_factor = scaleFactor;
2739
- if (creativity !== void 0) body.creativity = creativity;
2740
- if (resemblance !== void 0) body.resemblance = resemblance;
2741
- if (upscalePrompt) body.prompt = upscalePrompt;
2742
- if (negativePrompt) body.negative_prompt = negativePrompt;
2743
- if (numInferenceSteps !== void 0)
2884
+ if (scaleFactor !== void 0) {
2885
+ body.upscale_factor = scaleFactor;
2886
+ }
2887
+ if (creativity !== void 0) {
2888
+ body.creativity = creativity;
2889
+ }
2890
+ if (resemblance !== void 0) {
2891
+ body.resemblance = resemblance;
2892
+ }
2893
+ if (upscalePrompt !== void 0 && upscalePrompt !== "") {
2894
+ body.prompt = upscalePrompt;
2895
+ }
2896
+ if (negativePrompt !== void 0 && negativePrompt !== "") {
2897
+ body.negative_prompt = negativePrompt;
2898
+ }
2899
+ if (numInferenceSteps !== void 0) {
2744
2900
  body.num_inference_steps = numInferenceSteps;
2745
- if (guidanceScale !== void 0) body.guidance_scale = guidanceScale;
2901
+ }
2902
+ if (guidanceScale !== void 0) {
2903
+ body.guidance_scale = guidanceScale;
2904
+ }
2746
2905
  }
2747
2906
  const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
2748
- method: "POST",
2749
- body: JSON.stringify(body)
2907
+ body: JSON.stringify(body),
2908
+ method: "POST"
2750
2909
  });
2751
2910
  if (response.isErr()) {
2752
2911
  return (0, import_neverthrow.err)(response.error);
@@ -2773,17 +2932,25 @@ var MotifServer = class {
2773
2932
  }
2774
2933
  const rbBody = { image_url: imageUrl };
2775
2934
  if (model === "rmbg") {
2776
- if (variant) rbBody.model = variant;
2777
- if (operatingResolution)
2935
+ if (variant) {
2936
+ rbBody.model = variant;
2937
+ }
2938
+ if (operatingResolution) {
2778
2939
  rbBody.operating_resolution = operatingResolution;
2779
- if (outputFormat) rbBody.output_format = outputFormat;
2780
- if (refineForeground !== void 0)
2940
+ }
2941
+ if (outputFormat) {
2942
+ rbBody.output_format = outputFormat;
2943
+ }
2944
+ if (refineForeground !== void 0) {
2781
2945
  rbBody.refine_foreground = refineForeground;
2782
- if (outputMask !== void 0) rbBody.output_mask = outputMask;
2946
+ }
2947
+ if (outputMask !== void 0) {
2948
+ rbBody.output_mask = outputMask;
2949
+ }
2783
2950
  }
2784
2951
  const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
2785
- method: "POST",
2786
- body: JSON.stringify(rbBody)
2952
+ body: JSON.stringify(rbBody),
2953
+ method: "POST"
2787
2954
  });
2788
2955
  if (response.isErr()) {
2789
2956
  return (0, import_neverthrow.err)(response.error);
@@ -2812,28 +2979,33 @@ var MotifServer = class {
2812
2979
  return (0, import_neverthrow.err)(new MotifError("Kling video model not found", 0));
2813
2980
  }
2814
2981
  const body = {
2815
- start_image_url: imageUrl,
2816
- prompt,
2817
2982
  duration: String(duration),
2818
- generate_audio: generateAudio
2983
+ generate_audio: generateAudio,
2984
+ prompt,
2985
+ start_image_url: imageUrl
2819
2986
  };
2820
- if (endImageUrl) {
2987
+ if (endImageUrl !== void 0 && endImageUrl !== "") {
2821
2988
  body.end_image_url = endImageUrl;
2822
2989
  }
2823
- if (negativePrompt) body.negative_prompt = negativePrompt;
2824
- if (cfgScale !== void 0) body.cfg_scale = cfgScale;
2990
+ if (negativePrompt !== void 0 && negativePrompt !== "") {
2991
+ body.negative_prompt = negativePrompt;
2992
+ }
2993
+ if (cfgScale !== void 0) {
2994
+ body.cfg_scale = cfgScale;
2995
+ }
2825
2996
  const response = await this.request(`${FAL_QUEUE_URL}/${config.endpoint}`, {
2826
- method: "POST",
2827
- body: JSON.stringify(body)
2997
+ body: JSON.stringify(body),
2998
+ method: "POST"
2828
2999
  });
2829
3000
  if (response.isErr()) {
2830
3001
  return (0, import_neverthrow.err)(response.error);
2831
3002
  }
2832
3003
  const data = await response.value.json();
3004
+ const submission = parseQueueSubmission(data);
2833
3005
  return (0, import_neverthrow.ok)({
2834
- requestId: data.request_id,
2835
- endpoint: endpointFromQueueUrl(data.response_url, config.endpoint),
2836
- estimatedCost: estimateVideoCost(duration, generateAudio)
3006
+ endpoint: endpointFromQueueUrl(submission.responseUrl, config.endpoint),
3007
+ estimatedCost: estimateVideoCost(duration, generateAudio),
3008
+ requestId: submission.requestId
2837
3009
  });
2838
3010
  }
2839
3011
  /** Fetch the completed video result from the queue. */
@@ -2844,14 +3016,15 @@ var MotifServer = class {
2844
3016
  return (0, import_neverthrow.err)(response.error);
2845
3017
  }
2846
3018
  const data = await response.value.json();
2847
- if (!data.video) {
3019
+ const video = isRecord(data) && isRecord(data.video) ? data.video : void 0;
3020
+ if (video === void 0) {
2848
3021
  return (0, import_neverthrow.err)(new MotifError("No video in response", 0));
2849
3022
  }
2850
3023
  return (0, import_neverthrow.ok)({
2851
- url: data.video.url,
2852
- contentType: data.video.content_type,
2853
- fileName: data.video.file_name,
2854
- fileSize: data.video.file_size
3024
+ contentType: asString(video.content_type) ?? "",
3025
+ fileName: asString(video.file_name) ?? "",
3026
+ fileSize: asNumber(video.file_size) ?? 0,
3027
+ url: asString(video.url) ?? ""
2855
3028
  });
2856
3029
  }
2857
3030
  /** ─── File Upload ─────────────────────────────────────────── */
@@ -2863,26 +3036,33 @@ var MotifServer = class {
2863
3036
  const initiateResponse = await this.request(
2864
3037
  `${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`,
2865
3038
  {
2866
- method: "POST",
2867
3039
  body: JSON.stringify({
2868
3040
  content_type: options.contentType,
2869
3041
  file_name: options.fileName
2870
- })
3042
+ }),
3043
+ method: "POST"
2871
3044
  }
2872
3045
  );
2873
3046
  if (initiateResponse.isErr()) {
2874
3047
  return (0, import_neverthrow.err)(initiateResponse.error);
2875
3048
  }
2876
- const { file_url, upload_url } = await initiateResponse.value.json();
3049
+ const initiateData = await initiateResponse.value.json();
3050
+ const fileUrl = isRecord(initiateData) ? asString(initiateData.file_url) ?? "" : "";
3051
+ const uploadUrl = isRecord(initiateData) ? asString(initiateData.upload_url) : void 0;
3052
+ if (uploadUrl === void 0 || uploadUrl === "") {
3053
+ return (0, import_neverthrow.err)(
3054
+ new MotifError("Upload initiate response missing upload_url", 0)
3055
+ );
3056
+ }
2877
3057
  let putResponse;
2878
3058
  try {
2879
3059
  const body = Buffer.from(
2880
3060
  file instanceof Uint8Array ? file : new Uint8Array(file)
2881
3061
  );
2882
- putResponse = await fetch(upload_url, {
2883
- method: "PUT",
3062
+ putResponse = await fetch(uploadUrl, {
3063
+ body,
2884
3064
  headers: { "Content-Type": options.contentType },
2885
- body
3065
+ method: "PUT"
2886
3066
  });
2887
3067
  } catch (error) {
2888
3068
  return (0, import_neverthrow.err)(
@@ -2900,7 +3080,7 @@ var MotifServer = class {
2900
3080
  )
2901
3081
  );
2902
3082
  }
2903
- return (0, import_neverthrow.ok)(file_url);
3083
+ return (0, import_neverthrow.ok)(fileUrl);
2904
3084
  }
2905
3085
  /** ─── Utilities ───────────────────────────────────────────── */
2906
3086
  /** Run a registered fal utility/tool endpoint. */
@@ -2917,13 +3097,14 @@ var MotifServer = class {
2917
3097
  );
2918
3098
  }
2919
3099
  const response = await this.request(`${FAL_BASE_URL}/${request.endpoint}`, {
2920
- method: "POST",
2921
- body: JSON.stringify(request.body)
3100
+ body: JSON.stringify(request.body),
3101
+ method: "POST"
2922
3102
  });
2923
3103
  if (response.isErr()) {
2924
3104
  return (0, import_neverthrow.err)(response.error);
2925
3105
  }
2926
- return (0, import_neverthrow.ok)(await response.value.json());
3106
+ const data = await response.value.json();
3107
+ return (0, import_neverthrow.ok)(isRecord(data) ? data : {});
2927
3108
  }
2928
3109
  /**
2929
3110
  * Delete fal's stored IO payloads for a completed request.
@@ -2940,7 +3121,7 @@ var MotifServer = class {
2940
3121
  if (response.isErr()) {
2941
3122
  return (0, import_neverthrow.err)(response.error);
2942
3123
  }
2943
- return (0, import_neverthrow.ok)(void 0);
3124
+ return (0, import_neverthrow.ok)();
2944
3125
  }
2945
3126
  /** Estimate cost for a generation (no API call). */
2946
3127
  estimateCost(model, resolution, numImages) {
@@ -2972,30 +3153,33 @@ var MotifServer = class {
2972
3153
  let lastError = null;
2973
3154
  for (let attempt = 0; attempt <= this.retries; attempt++) {
2974
3155
  const controller = new AbortController();
2975
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3156
+ const timeoutId = setTimeout(() => {
3157
+ controller.abort();
3158
+ }, this.timeout);
2976
3159
  try {
2977
3160
  const response = await fetch(url, {
2978
3161
  ...options,
2979
- signal: controller.signal,
2980
3162
  headers: {
2981
3163
  Authorization: `Key ${this.apiKey}`,
2982
3164
  "Content-Type": "application/json",
2983
- ...options.headers
2984
- }
3165
+ ...toHeaderRecord(options.headers)
3166
+ },
3167
+ signal: controller.signal
2985
3168
  });
2986
3169
  clearTimeout(timeoutId);
2987
3170
  if ((response.status === 429 || response.status >= 500) && attempt < this.retries) {
2988
3171
  const delay = 1e3 * 2 ** attempt;
2989
- await new Promise((r) => setTimeout(r, delay));
3172
+ await new Promise((r) => {
3173
+ setTimeout(r, delay);
3174
+ });
2990
3175
  continue;
2991
3176
  }
2992
3177
  if (!response.ok) {
2993
3178
  const text = await response.text();
3179
+ const message = `Request failed: ${response.status} ${text}`;
3180
+ const requestId = response.headers.get("x-fal-request-id") ?? requestIdFromBody(text);
2994
3181
  return (0, import_neverthrow.err)(
2995
- new MotifError(
2996
- `Request failed: ${response.status} ${text}`,
2997
- response.status
2998
- )
3182
+ new MotifError(message, response.status, void 0, requestId)
2999
3183
  );
3000
3184
  }
3001
3185
  return (0, import_neverthrow.ok)(response);
@@ -3010,49 +3194,60 @@ var MotifServer = class {
3010
3194
  );
3011
3195
  if (attempt < this.retries) {
3012
3196
  const delay = 1e3 * 2 ** attempt;
3013
- await new Promise((r) => setTimeout(r, delay));
3197
+ await new Promise((r) => {
3198
+ setTimeout(r, delay);
3199
+ });
3014
3200
  }
3015
3201
  }
3016
3202
  }
3017
3203
  return (0, import_neverthrow.err)(lastError ?? new MotifError("Request failed after retries", 0));
3018
3204
  }
3019
3205
  ephemeralHeaders(options) {
3020
- return options.ephemeral ? { "X-Fal-Store-IO": "0" } : {};
3206
+ return options.ephemeral === true ? { "X-Fal-Store-IO": "0" } : {};
3021
3207
  }
3022
3208
  /**
3023
3209
  * Normalize fal.ai responses.
3024
3210
  * Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
3025
3211
  */
3026
3212
  normalizeResponse(data, fallbackRequestId) {
3027
- const obj = data;
3028
- const requestId = obj.request_id ?? obj.requestId ?? fallbackRequestId;
3029
- if ("detail" in obj) {
3213
+ if (!isRecord(data)) {
3030
3214
  return (0, import_neverthrow.err)(
3031
- new MotifError(obj.detail, 0, "FAL_ERROR")
3215
+ new MotifError("Unexpected fal response shape", 0, "FAL_ERROR")
3032
3216
  );
3033
3217
  }
3218
+ const obj = data;
3219
+ const requestId = asString(obj.request_id) ?? asString(obj.requestId) ?? fallbackRequestId;
3220
+ if ("detail" in obj) {
3221
+ return (0, import_neverthrow.err)(new MotifError(asString(obj.detail) ?? "", 0, "FAL_ERROR"));
3222
+ }
3034
3223
  if ("image" in obj && !("images" in obj)) {
3035
3224
  return (0, import_neverthrow.ok)({
3036
- images: [obj.image],
3037
- seed: obj.seed,
3038
- prompt: obj.prompt,
3039
- requestId
3225
+ images: [toMotifImage(obj.image)],
3226
+ prompt: asString(obj.prompt),
3227
+ requestId,
3228
+ seed: asNumber(obj.seed)
3040
3229
  });
3041
3230
  }
3042
3231
  return (0, import_neverthrow.ok)({
3043
- ...obj,
3044
- requestId
3232
+ images: parseImages(obj.images),
3233
+ prompt: asString(obj.prompt),
3234
+ requestId,
3235
+ seed: asNumber(obj.seed)
3045
3236
  });
3046
3237
  }
3047
3238
  };
3048
3239
  var MotifError = class extends Error {
3049
3240
  status;
3050
3241
  code;
3051
- constructor(message, status, code) {
3242
+ /** fal's request-correlation id (from the `x-fal-request-id` header or the
3243
+ * error body). Ties a failure back to fal's dashboard/support. */
3244
+ requestId;
3245
+ constructor(message, status, code, requestId) {
3052
3246
  super(message);
3053
3247
  this.name = "MotifError";
3054
3248
  this.status = status;
3055
3249
  this.code = code;
3250
+ this.requestId = requestId;
3056
3251
  }
3057
3252
  };
3058
3253
  function toMotifError(error) {