@howells/motif-sdk 0.1.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 ADDED
@@ -0,0 +1,2747 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ASPECT_RATIOS: () => ASPECT_RATIOS,
24
+ FAL_TOOLS: () => FAL_TOOLS,
25
+ FAL_TOOLS_CHECKED_AT: () => FAL_TOOLS_CHECKED_AT,
26
+ FAL_TOOL_IDS: () => FAL_TOOL_IDS,
27
+ FORMAT_PRESETS: () => FORMAT_PRESETS,
28
+ GENERATION_MODELS: () => GENERATION_MODELS,
29
+ IDEOGRAM_STYLES: () => IDEOGRAM_STYLES,
30
+ IMAGE_EDITING_TOP_20: () => IMAGE_EDITING_TOP_20,
31
+ IMAGE_TEXT_TO_IMAGE_TOP_20: () => IMAGE_TEXT_TO_IMAGE_TOP_20,
32
+ MODELS: () => MODELS,
33
+ MotifError: () => MotifError,
34
+ MotifServer: () => MotifServer,
35
+ RECRAFT_STYLES: () => RECRAFT_STYLES,
36
+ RESOLUTIONS: () => RESOLUTIONS,
37
+ UTILITY_MODELS: () => UTILITY_MODELS,
38
+ VIDEO_IMAGE_TO_VIDEO_TOP_15: () => VIDEO_IMAGE_TO_VIDEO_TOP_15,
39
+ VIDEO_MODELS: () => VIDEO_MODELS,
40
+ VIDEO_TEXT_TO_VIDEO_TOP_15: () => VIDEO_TEXT_TO_VIDEO_TOP_15,
41
+ aspectToFalImageSize: () => aspectToFalImageSize,
42
+ aspectToGptSize: () => aspectToGptSize,
43
+ buildFalToolRequest: () => buildFalToolRequest,
44
+ buildGenerateBody: () => buildGenerateBody,
45
+ err: () => import_neverthrow2.err,
46
+ estimateCost: () => estimateCost,
47
+ estimateVideoCost: () => estimateVideoCost,
48
+ getFalKeyFromEnv: () => getFalKeyFromEnv,
49
+ isFalToolId: () => isFalToolId,
50
+ motifEnvSchema: () => motifEnvSchema,
51
+ ok: () => import_neverthrow2.ok,
52
+ parseMotifEnv: () => parseMotifEnv
53
+ });
54
+ module.exports = __toCommonJS(index_exports);
55
+ var import_neverthrow2 = require("neverthrow");
56
+
57
+ // src/aspects.ts
58
+ var ASPECT_RATIOS = [
59
+ "auto",
60
+ "1:1",
61
+ "4:3",
62
+ "3:4",
63
+ "16:9",
64
+ "9:16",
65
+ "3:2",
66
+ "2:3",
67
+ "4:5",
68
+ "5:4",
69
+ "21:9",
70
+ "4:1",
71
+ "1:4",
72
+ "8:1",
73
+ "1:8"
74
+ ];
75
+ var RESOLUTIONS = ["0.5K", "1K", "2K", "4K"];
76
+ function aspectToGptSize(aspect) {
77
+ switch (aspect) {
78
+ case "auto":
79
+ return "auto";
80
+ case "9:16":
81
+ case "1:8":
82
+ case "1:4":
83
+ case "2:3":
84
+ case "4:5":
85
+ case "3:4":
86
+ return "1024x1536";
87
+ case "16:9":
88
+ case "8:1":
89
+ case "4:1":
90
+ case "3:2":
91
+ case "5:4":
92
+ case "4:3":
93
+ case "21:9":
94
+ return "1536x1024";
95
+ default:
96
+ return "1024x1024";
97
+ }
98
+ }
99
+ function aspectToFalImageSize(aspect) {
100
+ switch (aspect) {
101
+ case "auto":
102
+ return "auto";
103
+ case "16:9":
104
+ case "21:9":
105
+ case "4:1":
106
+ case "8:1":
107
+ return "landscape_16_9";
108
+ case "3:2":
109
+ case "5:4":
110
+ return "landscape_4_3";
111
+ case "4:3":
112
+ return "landscape_4_3";
113
+ case "9:16":
114
+ case "1:4":
115
+ case "1:8":
116
+ return "portrait_16_9";
117
+ case "2:3":
118
+ case "4:5":
119
+ return "portrait_4_3";
120
+ case "3:4":
121
+ return "portrait_4_3";
122
+ default:
123
+ return "square_hd";
124
+ }
125
+ }
126
+ var FORMAT_PRESETS = {
127
+ cover: { label: "Cover", aspect: "2:3", resolution: "2K" },
128
+ square: { label: "Square", aspect: "1:1" },
129
+ landscape: { label: "Landscape", aspect: "16:9" },
130
+ portrait: { label: "Portrait", aspect: "2:3" },
131
+ story: { label: "Story", aspect: "9:16" },
132
+ og: { label: "OG Image", aspect: "16:9" },
133
+ wide: { label: "Wide", aspect: "21:9" }
134
+ };
135
+
136
+ // src/models.ts
137
+ var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-05-12";
138
+ var AA_IMAGE_SOURCES = [
139
+ "https://artificialanalysis.ai/image/leaderboard/text-to-image",
140
+ "https://artificialanalysis.ai/image/leaderboard/editing"
141
+ ];
142
+ var FAL_PRICING_CHECKED_AT = "2026-05-12";
143
+ var MODELS = {
144
+ // ─── Generation Models ────────────────────────────────────────
145
+ gpt2: {
146
+ name: "GPT Image 2",
147
+ endpoint: "openai/gpt-image-2",
148
+ editEndpoint: "openai/gpt-image-2/image-to-image",
149
+ type: "generation",
150
+ pricing: "$0.211",
151
+ pricePerImageUsd: 0.211,
152
+ falPricing: {
153
+ endpointId: "openai/gpt-image-2",
154
+ unitPrice: 1,
155
+ unit: "units",
156
+ currency: "USD",
157
+ estimatedCostPerImageUsd: 0.211,
158
+ source: "fal-pricing-api",
159
+ checkedAt: FAL_PRICING_CHECKED_AT
160
+ },
161
+ benchmark: {
162
+ useCase: "Highest-ranked text-to-image quality and transparent PNGs",
163
+ tiers: { quality: "frontier", speed: "very_slow", price: "ultra" },
164
+ speed: {
165
+ medianSeconds: 200.7,
166
+ p95Seconds: 232.6,
167
+ source: "artificial-analysis-models"
168
+ },
169
+ artificialAnalysis: {
170
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
171
+ sourceUrls: AA_IMAGE_SOURCES,
172
+ textToImage: { rank: 1, elo: 1337, winRate: 0.8, pricePer1k: 211 },
173
+ editing: { rank: 3, elo: 1249, winRate: 0.66, pricePer1k: 211 }
174
+ }
175
+ },
176
+ sizeMode: "image_size_enum",
177
+ supportsAspect: true,
178
+ supportsResolution: false,
179
+ supportsEdit: true,
180
+ supportsNumImages: true,
181
+ supportsOutputFormat: true,
182
+ supportsQuality: true,
183
+ supportsMaskImage: true,
184
+ supportsSyncMode: true,
185
+ maxReferenceImages: 4,
186
+ useQueue: true
187
+ },
188
+ gpt: {
189
+ name: "GPT Image 1.5",
190
+ endpoint: "fal-ai/gpt-image-1.5",
191
+ editEndpoint: "fal-ai/gpt-image-1.5/edit",
192
+ type: "generation",
193
+ pricing: "$0.133",
194
+ pricePerImageUsd: 0.133,
195
+ falPricing: {
196
+ endpointId: "fal-ai/gpt-image-1.5",
197
+ unitPrice: 1,
198
+ unit: "units",
199
+ currency: "USD",
200
+ estimatedCostPerImageUsd: 0.133,
201
+ source: "fal-pricing-api",
202
+ checkedAt: FAL_PRICING_CHECKED_AT
203
+ },
204
+ benchmark: {
205
+ useCase: "Best OpenAI edit quality with transparent PNG support",
206
+ tiers: { quality: "frontier", speed: "slow", price: "ultra" },
207
+ speed: {
208
+ medianSeconds: 34.6,
209
+ p95Seconds: 38.3,
210
+ source: "artificial-analysis-models"
211
+ },
212
+ artificialAnalysis: {
213
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
214
+ sourceUrls: AA_IMAGE_SOURCES,
215
+ textToImage: { rank: 2, elo: 1268, winRate: 0.73, pricePer1k: 133 },
216
+ editing: { rank: 2, elo: 1262, winRate: 0.69, pricePer1k: 133 }
217
+ }
218
+ },
219
+ sizeMode: "gpt_size",
220
+ supportsAspect: false,
221
+ supportsBackground: true,
222
+ supportsResolution: false,
223
+ supportsEdit: true,
224
+ supportsNumImages: true,
225
+ supportsOutputFormat: true,
226
+ supportsQuality: true,
227
+ supportsMaskImage: true,
228
+ supportsSyncMode: true,
229
+ maxReferenceImages: 4
230
+ },
231
+ banana2: {
232
+ name: "Nano Banana 2",
233
+ endpoint: "fal-ai/nano-banana-2",
234
+ editEndpoint: "fal-ai/nano-banana-2/edit",
235
+ type: "generation",
236
+ pricing: "$0.08",
237
+ pricePerImageUsd: 0.08,
238
+ falPricing: {
239
+ endpointId: "fal-ai/nano-banana-2",
240
+ unitPrice: 0.08,
241
+ unit: "images",
242
+ currency: "USD",
243
+ estimatedCostPerImageUsd: 0.08,
244
+ source: "fal-pricing-api",
245
+ checkedAt: FAL_PRICING_CHECKED_AT
246
+ },
247
+ benchmark: {
248
+ useCase: "Best balance of quality, edit support, web search, and cost",
249
+ tiers: { quality: "frontier", speed: "unknown", price: "premium" },
250
+ artificialAnalysis: {
251
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
252
+ sourceUrls: AA_IMAGE_SOURCES,
253
+ textToImage: { rank: 3, elo: 1263, winRate: 0.71, pricePer1k: 67 },
254
+ editing: { rank: 5, elo: 1231, winRate: 0.63, pricePer1k: 67 }
255
+ }
256
+ },
257
+ sizeMode: "aspect_ratio",
258
+ supportsAspect: true,
259
+ supportsResolution: true,
260
+ supportsEdit: true,
261
+ supportsNumImages: true,
262
+ supportsOutputFormat: true,
263
+ supportsSafetyTolerance: true,
264
+ supportsSeed: true,
265
+ supportsWebSearch: true,
266
+ supportsGoogleSearch: true,
267
+ supportsLimitGenerations: true,
268
+ supportsSyncMode: true,
269
+ supportsThinkingLevel: true,
270
+ maxReferenceImages: 4
271
+ },
272
+ banana: {
273
+ name: "Nano Banana Pro",
274
+ endpoint: "fal-ai/nano-banana-pro",
275
+ editEndpoint: "fal-ai/nano-banana-pro/edit",
276
+ type: "generation",
277
+ pricing: "$0.15",
278
+ pricePerImageUsd: 0.15,
279
+ falPricing: {
280
+ endpointId: "fal-ai/nano-banana-pro",
281
+ unitPrice: 0.15,
282
+ unit: "images",
283
+ currency: "USD",
284
+ estimatedCostPerImageUsd: 0.15,
285
+ source: "fal-pricing-api",
286
+ checkedAt: FAL_PRICING_CHECKED_AT
287
+ },
288
+ benchmark: {
289
+ useCase: "Premium Gemini image generation and multi-reference edits",
290
+ tiers: { quality: "frontier", speed: "balanced", price: "ultra" },
291
+ speed: {
292
+ medianSeconds: 19.2,
293
+ p95Seconds: 30.3,
294
+ source: "artificial-analysis-models"
295
+ },
296
+ artificialAnalysis: {
297
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
298
+ sourceUrls: AA_IMAGE_SOURCES,
299
+ textToImage: { rank: 5, elo: 1220, winRate: 0.66, pricePer1k: 134 },
300
+ editing: { rank: 4, elo: 1241, winRate: 0.65, pricePer1k: 134 }
301
+ }
302
+ },
303
+ sizeMode: "aspect_ratio",
304
+ supportsAspect: true,
305
+ supportsResolution: true,
306
+ supportsEdit: true,
307
+ supportsNumImages: true,
308
+ supportsOutputFormat: true,
309
+ supportsSafetyTolerance: true,
310
+ supportsSeed: true,
311
+ supportsWebSearch: true,
312
+ supportsGoogleSearch: true,
313
+ supportsLimitGenerations: true,
314
+ supportsSyncMode: true,
315
+ maxReferenceImages: 14
316
+ },
317
+ gemini: {
318
+ name: "Gemini 2.5 Flash",
319
+ endpoint: "fal-ai/gemini-25-flash-image",
320
+ editEndpoint: "fal-ai/gemini-25-flash-image/edit",
321
+ type: "generation",
322
+ pricing: "$0.04",
323
+ pricePerImageUsd: 0.0398,
324
+ falPricing: {
325
+ endpointId: "fal-ai/gemini-25-flash-image",
326
+ unitPrice: 0.0398,
327
+ unit: "images",
328
+ currency: "USD",
329
+ estimatedCostPerImageUsd: 0.0398,
330
+ source: "fal-pricing-api",
331
+ checkedAt: FAL_PRICING_CHECKED_AT
332
+ },
333
+ sizeMode: "aspect_ratio",
334
+ supportsAspect: true,
335
+ supportsResolution: false,
336
+ supportsEdit: true,
337
+ supportsNumImages: true,
338
+ supportsOutputFormat: true,
339
+ supportsSafetyTolerance: true,
340
+ supportsSeed: true,
341
+ supportsSyncMode: true,
342
+ maxReferenceImages: 4
343
+ },
344
+ gemini3: {
345
+ name: "Gemini 3 Pro",
346
+ endpoint: "fal-ai/gemini-3-pro-image-preview",
347
+ editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
348
+ type: "generation",
349
+ pricing: "$0.15",
350
+ pricePerImageUsd: 0.15,
351
+ falPricing: {
352
+ endpointId: "fal-ai/gemini-3-pro-image-preview",
353
+ unitPrice: 0.15,
354
+ unit: "images",
355
+ currency: "USD",
356
+ estimatedCostPerImageUsd: 0.15,
357
+ source: "fal-pricing-api",
358
+ checkedAt: FAL_PRICING_CHECKED_AT
359
+ },
360
+ sizeMode: "aspect_ratio",
361
+ supportsAspect: true,
362
+ supportsResolution: true,
363
+ supportsEdit: true,
364
+ supportsNumImages: true,
365
+ supportsOutputFormat: true,
366
+ supportsSafetyTolerance: true,
367
+ supportsSeed: true,
368
+ supportsWebSearch: true,
369
+ supportsSyncMode: true,
370
+ maxReferenceImages: 4
371
+ },
372
+ seedream4: {
373
+ name: "Seedream 4.0",
374
+ endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
375
+ editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
376
+ type: "generation",
377
+ pricing: "$0.03",
378
+ pricePerImageUsd: 0.03,
379
+ falPricing: {
380
+ endpointId: "fal-ai/bytedance/seedream/v4/text-to-image",
381
+ unitPrice: 0.03,
382
+ unit: "images",
383
+ currency: "USD",
384
+ estimatedCostPerImageUsd: 0.03,
385
+ source: "fal-pricing-api",
386
+ checkedAt: FAL_PRICING_CHECKED_AT
387
+ },
388
+ benchmark: {
389
+ useCase: "High-ranked budget generation and large multi-reference edits",
390
+ tiers: { quality: "best", speed: "balanced", price: "budget" },
391
+ speed: {
392
+ medianSeconds: 14.3,
393
+ p95Seconds: 21,
394
+ source: "artificial-analysis-models"
395
+ },
396
+ artificialAnalysis: {
397
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
398
+ sourceUrls: AA_IMAGE_SOURCES,
399
+ textToImage: { rank: 6, elo: 1198, winRate: 0.59, pricePer1k: 30 },
400
+ editing: { rank: 16, elo: 1184, winRate: 0.61, pricePer1k: 30 }
401
+ }
402
+ },
403
+ sizeMode: "image_size_enum",
404
+ supportsAspect: true,
405
+ supportsResolution: false,
406
+ supportsEdit: true,
407
+ supportsNumImages: true,
408
+ supportsSeed: true,
409
+ supportsSyncMode: true,
410
+ maxReferenceImages: 10
411
+ },
412
+ seedream45: {
413
+ name: "Seedream 4.5",
414
+ endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
415
+ editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
416
+ type: "generation",
417
+ pricing: "$0.04",
418
+ pricePerImageUsd: 0.04,
419
+ falPricing: {
420
+ endpointId: "fal-ai/bytedance/seedream/v4.5/text-to-image",
421
+ unitPrice: 0.04,
422
+ unit: "images",
423
+ currency: "USD",
424
+ estimatedCostPerImageUsd: 0.04,
425
+ source: "fal-pricing-api",
426
+ checkedAt: FAL_PRICING_CHECKED_AT
427
+ },
428
+ benchmark: {
429
+ useCase: "Cheap current Seedream model with good animation/art scores",
430
+ tiers: { quality: "better", speed: "balanced", price: "standard" },
431
+ speed: {
432
+ medianSeconds: 18.2,
433
+ p95Seconds: 43.3,
434
+ source: "artificial-analysis-models"
435
+ },
436
+ artificialAnalysis: {
437
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
438
+ sourceUrls: AA_IMAGE_SOURCES,
439
+ textToImage: { rank: 17, elo: 1167, winRate: 0.63, pricePer1k: 40 },
440
+ editing: { rank: 17, elo: 1184, winRate: 0.58, pricePer1k: 40 }
441
+ }
442
+ },
443
+ sizeMode: "image_size_enum",
444
+ supportsAspect: true,
445
+ supportsResolution: false,
446
+ supportsEdit: true,
447
+ supportsNumImages: true,
448
+ supportsSeed: true,
449
+ supportsSyncMode: true,
450
+ maxReferenceImages: 10
451
+ },
452
+ "flux2-max": {
453
+ name: "FLUX.2 Max",
454
+ endpoint: "fal-ai/flux-2-max",
455
+ editEndpoint: "fal-ai/flux-2-max/edit",
456
+ type: "generation",
457
+ pricing: "$0.07/MP",
458
+ pricePerImageUsd: 0.07,
459
+ falPricing: {
460
+ endpointId: "fal-ai/flux-2-max",
461
+ unitPrice: 0.07,
462
+ unit: "megapixels",
463
+ currency: "USD",
464
+ estimatedCostPerImageUsd: 0.07,
465
+ source: "fal-pricing-api",
466
+ checkedAt: FAL_PRICING_CHECKED_AT
467
+ },
468
+ benchmark: {
469
+ useCase: "Best FLUX quality and high-end editing",
470
+ tiers: { quality: "best", speed: "slow", price: "premium" },
471
+ speed: {
472
+ medianSeconds: 31.6,
473
+ p95Seconds: 41,
474
+ source: "artificial-analysis-models"
475
+ },
476
+ artificialAnalysis: {
477
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
478
+ sourceUrls: AA_IMAGE_SOURCES,
479
+ textToImage: { rank: 8, elo: 1197, winRate: 0.67, pricePer1k: 70 },
480
+ editing: { rank: 10, elo: 1206, winRate: 0.6, pricePer1k: 140 }
481
+ }
482
+ },
483
+ sizeMode: "image_size_enum",
484
+ supportsAspect: true,
485
+ supportsResolution: false,
486
+ supportsEdit: true,
487
+ supportsNumImages: false,
488
+ supportsOutputFormat: true,
489
+ supportsSafetyTolerance: true,
490
+ supportsSafetyChecker: true,
491
+ supportsSeed: true,
492
+ supportsSyncMode: true,
493
+ maxReferenceImages: 10
494
+ },
495
+ "flux2-pro": {
496
+ name: "FLUX.2 Pro",
497
+ endpoint: "fal-ai/flux-2-pro",
498
+ editEndpoint: "fal-ai/flux-2-pro/edit",
499
+ type: "generation",
500
+ pricing: "$0.03/MP",
501
+ pricePerImageUsd: 0.03,
502
+ falPricing: {
503
+ endpointId: "fal-ai/flux-2-pro",
504
+ unitPrice: 0.03,
505
+ unit: "processed megapixels",
506
+ currency: "USD",
507
+ estimatedCostPerImageUsd: 0.03,
508
+ source: "fal-pricing-api",
509
+ checkedAt: FAL_PRICING_CHECKED_AT
510
+ },
511
+ benchmark: {
512
+ useCase: "Production FLUX quality at a low per-megapixel price",
513
+ tiers: { quality: "best", speed: "balanced", price: "budget" },
514
+ speed: {
515
+ medianSeconds: 16.1,
516
+ p95Seconds: 23.9,
517
+ source: "artificial-analysis-models"
518
+ },
519
+ artificialAnalysis: {
520
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
521
+ sourceUrls: AA_IMAGE_SOURCES,
522
+ textToImage: { rank: 10, elo: 1186, winRate: 0.62, pricePer1k: 30 },
523
+ editing: { rank: 21, elo: 1170, pricePer1k: 30 }
524
+ }
525
+ },
526
+ sizeMode: "image_size_enum",
527
+ supportsAspect: true,
528
+ supportsResolution: false,
529
+ supportsEdit: true,
530
+ supportsNumImages: false,
531
+ supportsOutputFormat: true,
532
+ supportsSafetyTolerance: true,
533
+ supportsSafetyChecker: true,
534
+ supportsSeed: true,
535
+ supportsSyncMode: true,
536
+ maxReferenceImages: 10
537
+ },
538
+ "flux2-flex": {
539
+ name: "FLUX.2 Flex",
540
+ endpoint: "fal-ai/flux-2-flex",
541
+ editEndpoint: "fal-ai/flux-2-flex/edit",
542
+ type: "generation",
543
+ pricing: "$0.05/MP",
544
+ pricePerImageUsd: 0.05,
545
+ falPricing: {
546
+ endpointId: "fal-ai/flux-2-flex",
547
+ unitPrice: 0.05,
548
+ unit: "processed megapixels",
549
+ currency: "USD",
550
+ estimatedCostPerImageUsd: 0.05,
551
+ source: "fal-pricing-api",
552
+ checkedAt: FAL_PRICING_CHECKED_AT
553
+ },
554
+ benchmark: {
555
+ useCase: "FLUX quality with guidance and step controls",
556
+ tiers: { quality: "best", speed: "balanced", price: "standard" },
557
+ speed: {
558
+ medianSeconds: 15.3,
559
+ p95Seconds: 22.2,
560
+ source: "artificial-analysis-models"
561
+ },
562
+ artificialAnalysis: {
563
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
564
+ sourceUrls: AA_IMAGE_SOURCES,
565
+ textToImage: { rank: 13, elo: 1182, winRate: 0.61, pricePer1k: 60 },
566
+ editing: { rank: 22, elo: 1161, pricePer1k: 60 }
567
+ }
568
+ },
569
+ sizeMode: "image_size_enum",
570
+ supportsAspect: true,
571
+ supportsResolution: false,
572
+ supportsEdit: true,
573
+ supportsNumImages: false,
574
+ supportsOutputFormat: true,
575
+ supportedOutputFormats: ["jpeg", "png"],
576
+ supportsSafetyTolerance: true,
577
+ supportsSafetyChecker: true,
578
+ supportsSeed: true,
579
+ supportsGuidanceScale: true,
580
+ supportsInferenceSteps: true,
581
+ supportsSyncMode: true,
582
+ maxReferenceImages: 10
583
+ },
584
+ "flux2-dev": {
585
+ name: "FLUX.2 Dev",
586
+ endpoint: "fal-ai/flux-2",
587
+ editEndpoint: "fal-ai/flux-2/edit",
588
+ type: "generation",
589
+ pricing: "$0.00167/sec",
590
+ pricePerImageUsd: 0.012,
591
+ falPricing: {
592
+ endpointId: "fal-ai/flux-2",
593
+ unitPrice: 167e-5,
594
+ unit: "compute seconds",
595
+ currency: "USD",
596
+ estimatedCostPerImageUsd: 0.012,
597
+ source: "fal-pricing-api",
598
+ checkedAt: FAL_PRICING_CHECKED_AT
599
+ },
600
+ benchmark: {
601
+ useCase: "Open FLUX.2 quality with low average cost",
602
+ tiers: { quality: "better", speed: "fast", price: "variable" },
603
+ speed: {
604
+ medianSeconds: 4.6,
605
+ p95Seconds: 69.6,
606
+ source: "artificial-analysis-models"
607
+ },
608
+ artificialAnalysis: {
609
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
610
+ sourceUrls: AA_IMAGE_SOURCES,
611
+ textToImage: { rank: 19, elo: 1160, winRate: 0.57, pricePer1k: 12 }
612
+ }
613
+ },
614
+ sizeMode: "image_size_enum",
615
+ supportsAspect: true,
616
+ supportsResolution: false,
617
+ supportsEdit: true,
618
+ supportsNumImages: true,
619
+ supportsOutputFormat: true,
620
+ supportsSafetyTolerance: true,
621
+ supportsSafetyChecker: true,
622
+ supportsSeed: true,
623
+ supportsGuidanceScale: true,
624
+ supportsInferenceSteps: true,
625
+ supportsSyncMode: true,
626
+ maxReferenceImages: 10
627
+ },
628
+ flux: {
629
+ name: "FLUX Pro Ultra",
630
+ endpoint: "fal-ai/flux-pro/v1.1-ultra",
631
+ type: "generation",
632
+ pricing: "$0.06",
633
+ pricePerImageUsd: 0.06,
634
+ falPricing: {
635
+ endpointId: "fal-ai/flux-pro/v1.1-ultra",
636
+ unitPrice: 0.06,
637
+ unit: "images",
638
+ currency: "USD",
639
+ estimatedCostPerImageUsd: 0.06,
640
+ source: "fal-pricing-api",
641
+ checkedAt: FAL_PRICING_CHECKED_AT
642
+ },
643
+ sizeMode: "aspect_ratio",
644
+ supportsAspect: true,
645
+ supportsResolution: false,
646
+ supportsEdit: true,
647
+ supportsNumImages: true,
648
+ supportsOutputFormat: true,
649
+ supportsRaw: true,
650
+ supportsEnhancePrompt: true,
651
+ supportsSafetyTolerance: true,
652
+ supportsSeed: true,
653
+ supportsImagePromptStrength: true,
654
+ supportsSyncMode: true,
655
+ maxReferenceImages: 1
656
+ },
657
+ "flux-fast": {
658
+ name: "FLUX Schnell",
659
+ endpoint: "fal-ai/flux/schnell",
660
+ type: "generation",
661
+ pricing: "$0.003",
662
+ pricePerImageUsd: 3e-3,
663
+ falPricing: {
664
+ endpointId: "fal-ai/flux/schnell",
665
+ unitPrice: 3e-3,
666
+ unit: "megapixels",
667
+ currency: "USD",
668
+ estimatedCostPerImageUsd: 3e-3,
669
+ source: "fal-pricing-api",
670
+ checkedAt: FAL_PRICING_CHECKED_AT
671
+ },
672
+ sizeMode: "image_size_enum",
673
+ supportsAspect: true,
674
+ supportsResolution: false,
675
+ supportsEdit: false,
676
+ supportsNumImages: true,
677
+ supportsOutputFormat: true,
678
+ supportsGuidanceScale: true,
679
+ supportsInferenceSteps: true,
680
+ supportsSeed: true,
681
+ supportsSyncMode: true
682
+ },
683
+ recraft: {
684
+ name: "Recraft V3",
685
+ endpoint: "fal-ai/recraft-v3",
686
+ type: "generation",
687
+ pricing: "$0.04",
688
+ pricePerImageUsd: 0.04,
689
+ falPricing: {
690
+ endpointId: "fal-ai/recraft-v3",
691
+ unitPrice: 0.04,
692
+ unit: "images",
693
+ currency: "USD",
694
+ estimatedCostPerImageUsd: 0.04,
695
+ source: "fal-pricing-api",
696
+ checkedAt: FAL_PRICING_CHECKED_AT
697
+ },
698
+ sizeMode: "image_size_enum",
699
+ supportsAspect: true,
700
+ supportsResolution: false,
701
+ supportsEdit: false,
702
+ supportsNumImages: false,
703
+ supportsStyle: true,
704
+ supportsSyncMode: true
705
+ },
706
+ ideogram: {
707
+ name: "Ideogram V3",
708
+ endpoint: "fal-ai/ideogram/v3",
709
+ type: "generation",
710
+ pricing: "$0.03",
711
+ pricePerImageUsd: 0.03,
712
+ falPricing: {
713
+ endpointId: "fal-ai/ideogram/v3",
714
+ unitPrice: 0.03,
715
+ unit: "images",
716
+ currency: "USD",
717
+ estimatedCostPerImageUsd: 0.03,
718
+ source: "fal-pricing-api",
719
+ checkedAt: FAL_PRICING_CHECKED_AT
720
+ },
721
+ sizeMode: "image_size_enum",
722
+ supportsAspect: true,
723
+ supportsResolution: false,
724
+ supportsEdit: false,
725
+ supportsNumImages: true,
726
+ supportsExpandPrompt: true,
727
+ supportsNegativePrompt: true,
728
+ supportsRenderingSpeed: true,
729
+ supportsSeed: true,
730
+ supportsStyle: true,
731
+ supportsSyncMode: true
732
+ },
733
+ "grok-image": {
734
+ name: "Grok Imagine Image",
735
+ endpoint: "xai/grok-imagine-image",
736
+ editEndpoint: "xai/grok-imagine-image/edit",
737
+ type: "generation",
738
+ pricing: "$0.02",
739
+ pricePerImageUsd: 0.02,
740
+ falPricing: {
741
+ endpointId: "xai/grok-imagine-image",
742
+ unitPrice: 0.02,
743
+ unit: "images",
744
+ currency: "USD",
745
+ estimatedCostPerImageUsd: 0.02,
746
+ source: "fal-pricing-api",
747
+ checkedAt: FAL_PRICING_CHECKED_AT
748
+ },
749
+ benchmark: {
750
+ useCase: "Very fast, cheap generation and edits",
751
+ tiers: { quality: "best", speed: "fast", price: "budget" },
752
+ speed: {
753
+ medianSeconds: 5.1,
754
+ p95Seconds: 6.5,
755
+ source: "artificial-analysis-models"
756
+ },
757
+ artificialAnalysis: {
758
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
759
+ sourceUrls: AA_IMAGE_SOURCES,
760
+ textToImage: { rank: 12, elo: 1182, winRate: 0.6, pricePer1k: 20 },
761
+ editing: { rank: 7, elo: 1213, winRate: 0.6, pricePer1k: 20 }
762
+ }
763
+ },
764
+ sizeMode: "aspect_ratio",
765
+ supportsAspect: true,
766
+ supportsResolution: true,
767
+ supportsEdit: true,
768
+ supportsNumImages: true,
769
+ supportsOutputFormat: true,
770
+ supportsSyncMode: true,
771
+ maxReferenceImages: 4
772
+ },
773
+ qwen: {
774
+ name: "Qwen Image",
775
+ endpoint: "fal-ai/qwen-image",
776
+ type: "generation",
777
+ pricing: "$0.02/MP",
778
+ pricePerImageUsd: 0.02,
779
+ falPricing: {
780
+ endpointId: "fal-ai/qwen-image",
781
+ unitPrice: 0.02,
782
+ unit: "megapixels",
783
+ currency: "USD",
784
+ estimatedCostPerImageUsd: 0.02,
785
+ source: "fal-pricing-api",
786
+ checkedAt: FAL_PRICING_CHECKED_AT
787
+ },
788
+ benchmark: {
789
+ useCase: "Low-cost open-weight text and design generation",
790
+ tiers: { quality: "better", speed: "slow", price: "budget" },
791
+ speed: {
792
+ medianSeconds: 23.3,
793
+ p95Seconds: 491,
794
+ source: "artificial-analysis-models"
795
+ },
796
+ artificialAnalysis: {
797
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
798
+ sourceUrls: AA_IMAGE_SOURCES,
799
+ textToImage: { rank: 20, elo: 1158, winRate: 0.6, pricePer1k: 20 }
800
+ }
801
+ },
802
+ sizeMode: "image_size_enum",
803
+ supportsAspect: true,
804
+ supportsResolution: false,
805
+ supportsEdit: false,
806
+ supportsNumImages: true,
807
+ supportsOutputFormat: true,
808
+ supportsSeed: true,
809
+ supportsSyncMode: true
810
+ },
811
+ // ─── Video Models ─────────────────────────────────────────────
812
+ kling: {
813
+ name: "Kling v3 Pro",
814
+ endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
815
+ type: "video",
816
+ pricing: "$0.11/sec",
817
+ sizeMode: "none",
818
+ supportsAspect: false,
819
+ supportsResolution: false,
820
+ supportsEdit: false,
821
+ supportsNumImages: false
822
+ },
823
+ // ─── Utility Models ───────────────────────────────────────────
824
+ clarity: {
825
+ name: "Clarity Upscaler",
826
+ endpoint: "fal-ai/clarity-upscaler",
827
+ type: "utility",
828
+ pricing: "$0.02",
829
+ supportsAspect: false,
830
+ supportsResolution: false,
831
+ supportsEdit: false,
832
+ supportsNumImages: false
833
+ },
834
+ crystal: {
835
+ name: "Crystal Upscaler",
836
+ endpoint: "clarityai/crystal-upscaler",
837
+ type: "utility",
838
+ pricing: "$0.02",
839
+ supportsAspect: false,
840
+ supportsResolution: false,
841
+ supportsEdit: false,
842
+ supportsNumImages: false
843
+ },
844
+ rmbg: {
845
+ name: "BiRefNet (Background Removal)",
846
+ endpoint: "fal-ai/birefnet",
847
+ type: "utility",
848
+ pricing: "$0.02",
849
+ supportsAspect: false,
850
+ supportsResolution: false,
851
+ supportsEdit: false,
852
+ supportsNumImages: false
853
+ },
854
+ bria: {
855
+ name: "Bria RMBG 2.0",
856
+ endpoint: "fal-ai/bria/background/remove",
857
+ type: "utility",
858
+ pricing: "$0.02",
859
+ supportsAspect: false,
860
+ supportsResolution: false,
861
+ supportsEdit: false,
862
+ supportsNumImages: false
863
+ }
864
+ };
865
+ var GENERATION_MODELS = [
866
+ "gpt2",
867
+ "gpt",
868
+ "banana2",
869
+ "banana",
870
+ "gemini",
871
+ "gemini3",
872
+ "seedream4",
873
+ "seedream45",
874
+ "flux2-max",
875
+ "flux2-pro",
876
+ "flux2-flex",
877
+ "flux2-dev",
878
+ "flux",
879
+ "flux-fast",
880
+ "recraft",
881
+ "ideogram",
882
+ "grok-image",
883
+ "qwen"
884
+ ];
885
+ var UTILITY_MODELS = ["clarity", "crystal", "rmbg", "bria"];
886
+ var VIDEO_MODELS = ["kling"];
887
+ var RECRAFT_STYLES = [
888
+ // Realistic
889
+ "realistic_image",
890
+ "realistic_image/b_and_w",
891
+ "realistic_image/enterprise",
892
+ "realistic_image/hard_flash",
893
+ "realistic_image/hdr",
894
+ "realistic_image/motion_blur",
895
+ "realistic_image/natural_light",
896
+ "realistic_image/studio_portrait",
897
+ "realistic_image/long_exposure",
898
+ // Digital illustration
899
+ "digital_illustration",
900
+ "digital_illustration/2d_art_poster",
901
+ "digital_illustration/2d_art_poster_2",
902
+ "digital_illustration/engraving_color",
903
+ "digital_illustration/grain",
904
+ "digital_illustration/hand_drawn",
905
+ "digital_illustration/hand_drawn_outline",
906
+ "digital_illustration/handmade_3d",
907
+ "digital_illustration/infantile_sketch",
908
+ "digital_illustration/pixel_art",
909
+ "digital_illustration/psychedelic",
910
+ "digital_illustration/seamless",
911
+ "digital_illustration/voxel",
912
+ "digital_illustration/watercolor",
913
+ // Vector illustration
914
+ "vector_illustration",
915
+ "vector_illustration/cartoon",
916
+ "vector_illustration/doodle_line_art",
917
+ "vector_illustration/engraving",
918
+ "vector_illustration/flat_2",
919
+ "vector_illustration/kawaii",
920
+ "vector_illustration/line_art",
921
+ "vector_illustration/line_circuit",
922
+ "vector_illustration/linocut",
923
+ "vector_illustration/zentangle"
924
+ ];
925
+ var IDEOGRAM_STYLES = [
926
+ "AUTO",
927
+ "GENERAL",
928
+ "REALISTIC",
929
+ "DESIGN"
930
+ ];
931
+
932
+ // src/cost.ts
933
+ function estimateCost(model, resolution, numImages = 1) {
934
+ const configuredPrice = MODELS[model]?.pricePerImageUsd;
935
+ if (configuredPrice !== void 0) {
936
+ if ((model === "banana" || model === "gemini3") && resolution === "4K") {
937
+ return configuredPrice * 2 * numImages;
938
+ }
939
+ return configuredPrice * numImages;
940
+ }
941
+ switch (model) {
942
+ case "gpt2":
943
+ return 0.211 * numImages;
944
+ case "gpt":
945
+ return 0.133 * numImages;
946
+ case "banana":
947
+ case "gemini3":
948
+ return (resolution === "4K" ? 0.3 : 0.15) * numImages;
949
+ case "gemini":
950
+ return 0.039 * numImages;
951
+ case "flux":
952
+ case "ideogram":
953
+ return 0.06 * numImages;
954
+ case "recraft":
955
+ return 0.04 * numImages;
956
+ case "flux-fast":
957
+ return 3e-3 * numImages;
958
+ case "clarity":
959
+ case "crystal":
960
+ case "rmbg":
961
+ case "bria":
962
+ return 0.02 * numImages;
963
+ default:
964
+ return 0.1 * numImages;
965
+ }
966
+ }
967
+ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
968
+ const perSecond = generateAudio ? 0.168 : 0.112;
969
+ return perSecond * durationSeconds;
970
+ }
971
+
972
+ // src/env.ts
973
+ var import_envy = require("@howells/envy");
974
+ var import_zod = require("zod");
975
+ var motifEnvSchema = (0, import_envy.defineEnv)({
976
+ optional: {
977
+ FAL_KEY: import_zod.z.string().min(1)
978
+ }
979
+ });
980
+ function parseMotifEnv(input = process.env) {
981
+ return motifEnvSchema.parse(input);
982
+ }
983
+ function getFalKeyFromEnv(input = process.env) {
984
+ return parseMotifEnv(input).FAL_KEY;
985
+ }
986
+
987
+ // src/generate.ts
988
+ var GPT_IMAGE_SIZES = ["auto", "1024x1024", "1536x1024", "1024x1536"];
989
+ var FAL_IMAGE_SIZE_PRESETS = [
990
+ "auto",
991
+ "square_hd",
992
+ "square",
993
+ "portrait_4_3",
994
+ "portrait_16_9",
995
+ "landscape_4_3",
996
+ "landscape_16_9"
997
+ ];
998
+ function normalizeImageSize(imageSize, sizeMode) {
999
+ if (imageSize === void 0) {
1000
+ return void 0;
1001
+ }
1002
+ if (typeof imageSize !== "string") {
1003
+ if (sizeMode === "gpt_size") {
1004
+ throw new Error("GPT Image 1.5 image_size must be auto or a fixed size");
1005
+ }
1006
+ return imageSize;
1007
+ }
1008
+ if (sizeMode === "gpt_size") {
1009
+ if (!GPT_IMAGE_SIZES.includes(imageSize)) {
1010
+ throw new Error(
1011
+ `GPT Image 1.5 image_size must be one of ${GPT_IMAGE_SIZES.join(", ")}`
1012
+ );
1013
+ }
1014
+ return imageSize;
1015
+ }
1016
+ if (FAL_IMAGE_SIZE_PRESETS.includes(imageSize)) {
1017
+ return imageSize;
1018
+ }
1019
+ const match = imageSize.match(/^(\d+)x(\d+)$/);
1020
+ if (match) {
1021
+ const width = Number(match[1]);
1022
+ const height = Number(match[2]);
1023
+ return { width, height };
1024
+ }
1025
+ throw new Error(
1026
+ `fal image_size must be one of ${FAL_IMAGE_SIZE_PRESETS.join(", ")} or WIDTHxHEIGHT`
1027
+ );
1028
+ }
1029
+ function unsupported(config, option) {
1030
+ throw new Error(`${config.name} does not support ${option}`);
1031
+ }
1032
+ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
1033
+ if (options.editImageUrls?.length && config.maxReferenceImages !== void 0 && options.editImageUrls.length > config.maxReferenceImages) {
1034
+ throw new Error(
1035
+ `${config.name} supports at most ${config.maxReferenceImages} reference images`
1036
+ );
1037
+ }
1038
+ if (options.editImageUrls?.length && !config.supportsEdit) {
1039
+ unsupported(config, "image editing");
1040
+ }
1041
+ if (options.aspect !== void 0 && sizeMode === "none") {
1042
+ unsupported(config, "aspect");
1043
+ }
1044
+ if (options.resolution !== void 0 && options.resolution !== "2K" && !config.supportsResolution) {
1045
+ unsupported(config, "resolution");
1046
+ }
1047
+ if (options.numImages !== void 0 && options.numImages !== 1 && !config.supportsNumImages) {
1048
+ unsupported(config, "numImages");
1049
+ }
1050
+ if (options.background !== void 0 && !config.supportsBackground) {
1051
+ unsupported(config, "background");
1052
+ }
1053
+ if (options.transparent && !(config.supportsBackground || config.supportsOutputFormat)) {
1054
+ unsupported(config, "transparent output");
1055
+ }
1056
+ if (options.inputFidelity !== void 0) {
1057
+ if (!hasEditImages) {
1058
+ throw new Error("inputFidelity requires editImageUrls");
1059
+ }
1060
+ if (config.sizeMode !== "gpt_size" && config.name !== "GPT Image 2") {
1061
+ unsupported(config, "inputFidelity");
1062
+ }
1063
+ }
1064
+ if (options.imageSize !== void 0 && sizeMode !== "gpt_size" && sizeMode !== "image_size_enum") {
1065
+ unsupported(config, "imageSize");
1066
+ }
1067
+ if (options.imagePromptStrength !== void 0 && !config.supportsImagePromptStrength) {
1068
+ unsupported(config, "imagePromptStrength");
1069
+ }
1070
+ if (options.imagePromptStrength !== void 0 && !hasEditImages) {
1071
+ throw new Error("imagePromptStrength requires editImageUrls");
1072
+ }
1073
+ if (options.maskImageUrl !== void 0 && !config.supportsMaskImage) {
1074
+ unsupported(config, "maskImageUrl");
1075
+ }
1076
+ if (options.maskImageUrl !== void 0 && !hasEditImages) {
1077
+ throw new Error("maskImageUrl requires editImageUrls");
1078
+ }
1079
+ if (options.seed !== void 0 && !config.supportsSeed) {
1080
+ unsupported(config, "seed");
1081
+ }
1082
+ if (options.outputFormat !== void 0 && !config.supportsOutputFormat) {
1083
+ unsupported(config, "outputFormat");
1084
+ }
1085
+ if (options.quality !== void 0 && !config.supportsQuality) {
1086
+ unsupported(config, "quality");
1087
+ }
1088
+ if (options.negativePrompt !== void 0 && !config.supportsNegativePrompt) {
1089
+ unsupported(config, "negativePrompt");
1090
+ }
1091
+ if (options.style !== void 0 && !config.supportsStyle) {
1092
+ unsupported(config, "style");
1093
+ }
1094
+ if (options.syncMode !== void 0 && !config.supportsSyncMode) {
1095
+ unsupported(config, "syncMode");
1096
+ }
1097
+ if (options.renderingSpeed !== void 0 && !config.supportsRenderingSpeed) {
1098
+ unsupported(config, "renderingSpeed");
1099
+ }
1100
+ if (options.guidanceScale !== void 0 && !config.supportsGuidanceScale) {
1101
+ unsupported(config, "guidanceScale");
1102
+ }
1103
+ if (options.numInferenceSteps !== void 0 && !config.supportsInferenceSteps) {
1104
+ unsupported(config, "numInferenceSteps");
1105
+ }
1106
+ if (options.raw !== void 0 && !config.supportsRaw) {
1107
+ unsupported(config, "raw");
1108
+ }
1109
+ if (options.enhancePrompt !== void 0 && !config.supportsEnhancePrompt) {
1110
+ unsupported(config, "enhancePrompt");
1111
+ }
1112
+ if (options.safetyTolerance !== void 0 && !config.supportsSafetyTolerance) {
1113
+ unsupported(config, "safetyTolerance");
1114
+ }
1115
+ if (options.enableWebSearch !== void 0 && !config.supportsWebSearch) {
1116
+ unsupported(config, "enableWebSearch");
1117
+ }
1118
+ if (options.enableGoogleSearch !== void 0 && !config.supportsGoogleSearch) {
1119
+ unsupported(config, "enableGoogleSearch");
1120
+ }
1121
+ if (options.enableSafetyChecker !== void 0 && !config.supportsSafetyChecker) {
1122
+ unsupported(config, "enableSafetyChecker");
1123
+ }
1124
+ if (options.limitGenerations !== void 0 && !config.supportsLimitGenerations) {
1125
+ unsupported(config, "limitGenerations");
1126
+ }
1127
+ if (options.thinkingLevel !== void 0 && !config.supportsThinkingLevel) {
1128
+ unsupported(config, "thinkingLevel");
1129
+ }
1130
+ if (options.expandPrompt !== void 0 && !config.supportsExpandPrompt) {
1131
+ unsupported(config, "expandPrompt");
1132
+ }
1133
+ }
1134
+ function buildGenerateBody(options) {
1135
+ const {
1136
+ prompt,
1137
+ model,
1138
+ aspect = "1:1",
1139
+ resolution = "2K",
1140
+ numImages = 1,
1141
+ background,
1142
+ editImageUrls,
1143
+ enableGoogleSearch,
1144
+ enableSafetyChecker,
1145
+ transparent,
1146
+ inputFidelity,
1147
+ imageSize,
1148
+ imagePromptStrength,
1149
+ limitGenerations,
1150
+ maskImageUrl,
1151
+ seed,
1152
+ outputFormat,
1153
+ quality,
1154
+ negativePrompt,
1155
+ style,
1156
+ syncMode,
1157
+ thinkingLevel,
1158
+ renderingSpeed,
1159
+ guidanceScale,
1160
+ numInferenceSteps,
1161
+ raw,
1162
+ enhancePrompt,
1163
+ safetyTolerance,
1164
+ enableWebSearch,
1165
+ expandPrompt
1166
+ } = options;
1167
+ const config = MODELS[model];
1168
+ if (!config) {
1169
+ throw new Error(`Unknown model: ${model}`);
1170
+ }
1171
+ let endpoint = config.endpoint;
1172
+ const body = { prompt };
1173
+ const hasEditImages = Boolean(editImageUrls?.length);
1174
+ const editImages = editImageUrls ?? [];
1175
+ const explicitAspect = options.aspect !== void 0;
1176
+ const sizeMode = config.sizeMode ?? "aspect_ratio";
1177
+ validateGenerateOptions(options, config, sizeMode, hasEditImages);
1178
+ const normalizedImageSize = normalizeImageSize(imageSize, sizeMode);
1179
+ switch (sizeMode) {
1180
+ case "gpt_size":
1181
+ body.image_size = normalizedImageSize ?? (hasEditImages && !explicitAspect ? "auto" : aspectToGptSize(aspect));
1182
+ break;
1183
+ case "image_size_enum":
1184
+ body.image_size = normalizedImageSize ?? aspectToFalImageSize(aspect);
1185
+ break;
1186
+ case "aspect_ratio":
1187
+ if (config.supportsAspect) {
1188
+ body.aspect_ratio = aspect;
1189
+ }
1190
+ if (config.supportsResolution) {
1191
+ body.resolution = model === "grok-image" ? resolution.toLowerCase() : resolution;
1192
+ }
1193
+ break;
1194
+ }
1195
+ if (config.supportsQuality) {
1196
+ body.quality = quality ?? "high";
1197
+ }
1198
+ if (config.supportsBackground && background) {
1199
+ body.background = background;
1200
+ }
1201
+ if (config.supportsBackground && transparent) {
1202
+ body.background = "transparent";
1203
+ }
1204
+ if (transparent && config.supportsOutputFormat) {
1205
+ body.output_format = "png";
1206
+ }
1207
+ if (syncMode !== void 0 && config.supportsSyncMode) {
1208
+ body.sync_mode = syncMode;
1209
+ }
1210
+ if (seed !== void 0 && config.supportsSeed) {
1211
+ body.seed = seed;
1212
+ }
1213
+ if (outputFormat && config.supportsOutputFormat && !transparent) {
1214
+ if (config.supportedOutputFormats && !config.supportedOutputFormats.includes(outputFormat)) {
1215
+ throw new Error(
1216
+ `${config.name} supports output formats: ${config.supportedOutputFormats.join(", ")}`
1217
+ );
1218
+ }
1219
+ body.output_format = outputFormat;
1220
+ }
1221
+ if (config.supportsRaw && raw !== void 0) {
1222
+ body.raw = raw;
1223
+ }
1224
+ if (config.supportsEnhancePrompt && enhancePrompt !== void 0) {
1225
+ body.enhance_prompt = enhancePrompt;
1226
+ }
1227
+ if (config.supportsImagePromptStrength && imagePromptStrength !== void 0) {
1228
+ body.image_prompt_strength = imagePromptStrength;
1229
+ }
1230
+ if (model === "flux") {
1231
+ if (safetyTolerance) body.safety_tolerance = safetyTolerance;
1232
+ }
1233
+ if (config.supportsGuidanceScale && guidanceScale !== void 0) {
1234
+ body.guidance_scale = guidanceScale;
1235
+ }
1236
+ if (config.supportsInferenceSteps && numInferenceSteps !== void 0) {
1237
+ body.num_inference_steps = numInferenceSteps;
1238
+ }
1239
+ if (config.supportsSafetyTolerance) {
1240
+ if (safetyTolerance) body.safety_tolerance = safetyTolerance;
1241
+ }
1242
+ if (config.supportsWebSearch) {
1243
+ if (enableWebSearch !== void 0) body.enable_web_search = enableWebSearch;
1244
+ }
1245
+ if (config.supportsGoogleSearch) {
1246
+ if (enableGoogleSearch !== void 0) {
1247
+ body.enable_google_search = enableGoogleSearch;
1248
+ }
1249
+ }
1250
+ if (config.supportsSafetyChecker) {
1251
+ if (enableSafetyChecker !== void 0) {
1252
+ body.enable_safety_checker = enableSafetyChecker;
1253
+ }
1254
+ }
1255
+ if (config.supportsLimitGenerations) {
1256
+ if (limitGenerations !== void 0) {
1257
+ body.limit_generations = limitGenerations;
1258
+ }
1259
+ }
1260
+ if (config.supportsThinkingLevel) {
1261
+ if (thinkingLevel) body.thinking_level = thinkingLevel;
1262
+ }
1263
+ if (config.supportsStyle && style) {
1264
+ body.style = style;
1265
+ }
1266
+ if (config.supportsNegativePrompt) {
1267
+ if (negativePrompt) body.negative_prompt = negativePrompt;
1268
+ }
1269
+ if (model === "ideogram") {
1270
+ if (style) body.style = style;
1271
+ }
1272
+ if (config.supportsRenderingSpeed) {
1273
+ if (renderingSpeed) body.rendering_speed = renderingSpeed;
1274
+ }
1275
+ if (config.supportsExpandPrompt) {
1276
+ if (expandPrompt !== void 0) body.expand_prompt = expandPrompt;
1277
+ }
1278
+ if (model === "flux" && hasEditImages) {
1279
+ body.image_url = editImages[0];
1280
+ body.image_prompt_strength = imagePromptStrength ?? 0.5;
1281
+ }
1282
+ if (config.supportsNumImages) {
1283
+ body.num_images = numImages;
1284
+ }
1285
+ if (hasEditImages && sizeMode !== "gpt_size" && model !== "flux") {
1286
+ if (!config.supportsEdit) {
1287
+ throw new Error(`Model ${model} does not support image editing`);
1288
+ }
1289
+ endpoint = config.editEndpoint ?? `${endpoint}/edit`;
1290
+ const editImagesField = config.editImagesField ?? "image_urls";
1291
+ body[editImagesField] = editImagesField === "image_url" ? editImages[0] : editImages;
1292
+ if (model === "gpt2" && inputFidelity) {
1293
+ body.input_fidelity = inputFidelity;
1294
+ }
1295
+ }
1296
+ if (hasEditImages && sizeMode === "gpt_size") {
1297
+ endpoint = config.editEndpoint ?? `${endpoint}/edit`;
1298
+ body.image_urls = editImages;
1299
+ if (inputFidelity) {
1300
+ body.input_fidelity = inputFidelity;
1301
+ }
1302
+ }
1303
+ if (maskImageUrl && config.supportsMaskImage) {
1304
+ body.mask_image_url = maskImageUrl;
1305
+ }
1306
+ return { endpoint, body };
1307
+ }
1308
+
1309
+ // src/leaderboards.ts
1310
+ var IMAGE_TEXT_TO_IMAGE_TOP_20 = {
1311
+ capturedAt: "2026-05-12",
1312
+ source: "https://artificialanalysis.ai/image/leaderboard/text-to-image",
1313
+ entries: [
1314
+ {
1315
+ rank: 1,
1316
+ creator: "OpenAI",
1317
+ model: "GPT Image 2 (high)",
1318
+ elo: 1337,
1319
+ apiPricing: "$211/1k images",
1320
+ isCurrent: true,
1321
+ routableModelId: "gpt2"
1322
+ },
1323
+ {
1324
+ rank: 2,
1325
+ creator: "OpenAI",
1326
+ model: "GPT Image 1.5 (high)",
1327
+ elo: 1268,
1328
+ apiPricing: "$133/1k images",
1329
+ isCurrent: true,
1330
+ routableModelId: "gpt"
1331
+ },
1332
+ {
1333
+ rank: 3,
1334
+ creator: "Google",
1335
+ model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1336
+ elo: 1263,
1337
+ apiPricing: "$67/1k images",
1338
+ isCurrent: true,
1339
+ routableModelId: "banana2"
1340
+ },
1341
+ {
1342
+ rank: 4,
1343
+ creator: "Sourceful",
1344
+ model: "Riverflow 2.0",
1345
+ elo: 1256,
1346
+ apiPricing: "$150/1k images",
1347
+ isCurrent: true
1348
+ },
1349
+ {
1350
+ rank: 5,
1351
+ creator: "Google",
1352
+ model: "Nano Banana Pro (Gemini 3 Pro Image)",
1353
+ elo: 1220,
1354
+ apiPricing: "$134/1k images",
1355
+ isCurrent: true,
1356
+ routableModelId: "banana"
1357
+ },
1358
+ {
1359
+ rank: 6,
1360
+ creator: "ByteDance Seed",
1361
+ model: "Seedream 4.0",
1362
+ elo: 1198,
1363
+ apiPricing: "$30/1k images",
1364
+ isCurrent: true,
1365
+ routableModelId: "seedream4"
1366
+ },
1367
+ {
1368
+ rank: 7,
1369
+ creator: "Microsoft",
1370
+ model: "MAI-Image-2",
1371
+ elo: 1198,
1372
+ apiPricing: "$50/1k images",
1373
+ isCurrent: true
1374
+ },
1375
+ {
1376
+ rank: 8,
1377
+ creator: "Black Forest Labs",
1378
+ model: "FLUX.2 [max]",
1379
+ elo: 1197,
1380
+ apiPricing: "$70/1k images",
1381
+ isCurrent: true,
1382
+ routableModelId: "flux2-max"
1383
+ },
1384
+ {
1385
+ rank: 9,
1386
+ creator: "Peanut",
1387
+ model: "Peanut (Open Weights Coming Soon)",
1388
+ elo: 1187,
1389
+ apiPricing: "Unavailable",
1390
+ isCurrent: true
1391
+ },
1392
+ {
1393
+ rank: 10,
1394
+ creator: "Black Forest Labs",
1395
+ model: "FLUX.2 [pro]",
1396
+ elo: 1186,
1397
+ apiPricing: "$30/1k images",
1398
+ isCurrent: true,
1399
+ routableModelId: "flux2-pro"
1400
+ },
1401
+ {
1402
+ rank: 11,
1403
+ creator: "Google",
1404
+ model: "Imagen 4 Ultra Preview 0606",
1405
+ elo: 1184,
1406
+ apiPricing: "$60/1k images",
1407
+ isCurrent: false
1408
+ },
1409
+ {
1410
+ rank: 12,
1411
+ creator: "xAI",
1412
+ model: "grok-imagine-image",
1413
+ elo: 1182,
1414
+ apiPricing: "$20/1k images",
1415
+ isCurrent: true,
1416
+ routableModelId: "grok-image"
1417
+ },
1418
+ {
1419
+ rank: 13,
1420
+ creator: "Black Forest Labs",
1421
+ model: "FLUX.2 [flex]",
1422
+ elo: 1182,
1423
+ apiPricing: "$60/1k images",
1424
+ isCurrent: true,
1425
+ routableModelId: "flux2-flex"
1426
+ },
1427
+ {
1428
+ rank: 14,
1429
+ creator: "ImagineArt",
1430
+ model: "ImagineArt 2.0",
1431
+ elo: 1181,
1432
+ apiPricing: "$30/1k images",
1433
+ isCurrent: true
1434
+ },
1435
+ {
1436
+ rank: 15,
1437
+ creator: "Google",
1438
+ model: "Imagen 4 Ultra",
1439
+ elo: 1171,
1440
+ apiPricing: "$60/1k images",
1441
+ isCurrent: true
1442
+ },
1443
+ {
1444
+ rank: 16,
1445
+ creator: "Google",
1446
+ model: "Imagen 4 Preview 0606",
1447
+ elo: 1169,
1448
+ apiPricing: "$40/1k images",
1449
+ isCurrent: false
1450
+ },
1451
+ {
1452
+ rank: 17,
1453
+ creator: "ByteDance Seed",
1454
+ model: "Seedream 4.5",
1455
+ elo: 1167,
1456
+ apiPricing: "$40/1k images",
1457
+ isCurrent: true,
1458
+ routableModelId: "seedream45"
1459
+ },
1460
+ {
1461
+ rank: 18,
1462
+ creator: "Fal",
1463
+ model: "FLUX.2 [dev] Turbo",
1464
+ elo: 1161,
1465
+ apiPricing: "$8/1k images",
1466
+ isCurrent: true
1467
+ },
1468
+ {
1469
+ rank: 19,
1470
+ creator: "Black Forest Labs",
1471
+ model: "FLUX.2 [dev]",
1472
+ elo: 1160,
1473
+ apiPricing: "$12/1k images",
1474
+ isCurrent: true,
1475
+ routableModelId: "flux2-dev"
1476
+ },
1477
+ {
1478
+ rank: 20,
1479
+ creator: "Alibaba",
1480
+ model: "Qwen Image Max 2512",
1481
+ elo: 1158,
1482
+ apiPricing: "$20/1k images",
1483
+ isCurrent: true,
1484
+ routableModelId: "qwen"
1485
+ }
1486
+ ]
1487
+ };
1488
+ var IMAGE_EDITING_TOP_20 = {
1489
+ capturedAt: "2026-05-12",
1490
+ source: "https://artificialanalysis.ai/image/leaderboard/editing",
1491
+ entries: [
1492
+ {
1493
+ rank: 1,
1494
+ creator: "Sourceful",
1495
+ model: "Riverflow 2.0",
1496
+ elo: 1286,
1497
+ apiPricing: "$150/1k images",
1498
+ isCurrent: true
1499
+ },
1500
+ {
1501
+ rank: 2,
1502
+ creator: "OpenAI",
1503
+ model: "GPT Image 1.5 (high)",
1504
+ elo: 1262,
1505
+ apiPricing: "$133/1k images",
1506
+ isCurrent: true,
1507
+ routableModelId: "gpt"
1508
+ },
1509
+ {
1510
+ rank: 3,
1511
+ creator: "OpenAI",
1512
+ model: "GPT Image 2 (high)",
1513
+ elo: 1249,
1514
+ apiPricing: "$211/1k images",
1515
+ isCurrent: true,
1516
+ routableModelId: "gpt2"
1517
+ },
1518
+ {
1519
+ rank: 4,
1520
+ creator: "Google",
1521
+ model: "Nano Banana Pro (Gemini 3 Pro Image)",
1522
+ elo: 1241,
1523
+ apiPricing: "$134/1k images",
1524
+ isCurrent: true,
1525
+ routableModelId: "banana"
1526
+ },
1527
+ {
1528
+ rank: 5,
1529
+ creator: "Google",
1530
+ model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1531
+ elo: 1231,
1532
+ apiPricing: "$67/1k images",
1533
+ isCurrent: true,
1534
+ routableModelId: "banana2"
1535
+ },
1536
+ {
1537
+ rank: 6,
1538
+ creator: "Tencent",
1539
+ model: "HunyuanImage 3.0 Instruct (Fal)",
1540
+ elo: 1222,
1541
+ apiPricing: "$90/1k images",
1542
+ isCurrent: true
1543
+ },
1544
+ {
1545
+ rank: 7,
1546
+ creator: "xAI",
1547
+ model: "grok-imagine-image",
1548
+ elo: 1213,
1549
+ apiPricing: "$20/1k images",
1550
+ isCurrent: true,
1551
+ routableModelId: "grok-image"
1552
+ },
1553
+ {
1554
+ rank: 8,
1555
+ creator: "xAI",
1556
+ model: "grok-imagine-image-pro",
1557
+ elo: 1212,
1558
+ apiPricing: "$70/1k images",
1559
+ isCurrent: true
1560
+ },
1561
+ {
1562
+ rank: 9,
1563
+ creator: "KlingAI",
1564
+ model: "Kling Image 3.0 Omni",
1565
+ elo: 1207,
1566
+ apiPricing: "Unavailable",
1567
+ isCurrent: true
1568
+ },
1569
+ {
1570
+ rank: 10,
1571
+ creator: "Black Forest Labs",
1572
+ model: "FLUX.2 [max]",
1573
+ elo: 1206,
1574
+ apiPricing: "$140/1k images",
1575
+ isCurrent: true,
1576
+ routableModelId: "flux2-max"
1577
+ },
1578
+ {
1579
+ rank: 11,
1580
+ creator: "Alibaba",
1581
+ model: "Wan 2.7 Pro",
1582
+ elo: 1200,
1583
+ apiPricing: "Unavailable",
1584
+ isCurrent: true
1585
+ },
1586
+ {
1587
+ rank: 12,
1588
+ creator: "KlingAI",
1589
+ model: "Kling Image 3.0",
1590
+ elo: 1196,
1591
+ apiPricing: "$28/1k images",
1592
+ isCurrent: true
1593
+ },
1594
+ {
1595
+ rank: 13,
1596
+ creator: "KlingAI",
1597
+ model: "Kling Image O1",
1598
+ elo: 1193,
1599
+ apiPricing: "$28/1k images",
1600
+ isCurrent: true
1601
+ },
1602
+ {
1603
+ rank: 14,
1604
+ creator: "Alibaba",
1605
+ model: "Wan 2.6 Image",
1606
+ elo: 1188,
1607
+ apiPricing: "$30/1k images",
1608
+ isCurrent: true
1609
+ },
1610
+ {
1611
+ rank: 15,
1612
+ creator: "Sourceful",
1613
+ model: "Riverflow 1",
1614
+ elo: 1184,
1615
+ apiPricing: "$66/1k images",
1616
+ isCurrent: true
1617
+ },
1618
+ {
1619
+ rank: 16,
1620
+ creator: "ByteDance Seed",
1621
+ model: "Seedream 4.0",
1622
+ elo: 1184,
1623
+ apiPricing: "$30/1k images",
1624
+ isCurrent: true,
1625
+ routableModelId: "seedream4"
1626
+ },
1627
+ {
1628
+ rank: 17,
1629
+ creator: "ByteDance Seed",
1630
+ model: "Seedream 4.5",
1631
+ elo: 1184,
1632
+ apiPricing: "$40/1k images",
1633
+ isCurrent: true,
1634
+ routableModelId: "seedream45"
1635
+ },
1636
+ {
1637
+ rank: 18,
1638
+ creator: "Alibaba",
1639
+ model: "Wan 2.7",
1640
+ elo: 1181,
1641
+ apiPricing: "$26/1k images",
1642
+ isCurrent: true
1643
+ },
1644
+ {
1645
+ rank: 19,
1646
+ creator: "Google",
1647
+ model: "Nano Banana (Gemini 2.5 Flash Image)",
1648
+ elo: 1173,
1649
+ apiPricing: "$39/1k images",
1650
+ isCurrent: true,
1651
+ routableModelId: "gemini"
1652
+ },
1653
+ {
1654
+ rank: 20,
1655
+ creator: "Reve",
1656
+ model: "Reve V1 (December)",
1657
+ elo: 1172,
1658
+ apiPricing: "$40/1k images",
1659
+ isCurrent: true
1660
+ }
1661
+ ]
1662
+ };
1663
+ var VIDEO_TEXT_TO_VIDEO_TOP_15 = {
1664
+ capturedAt: "2026-05-12",
1665
+ source: "https://artificialanalysis.ai/video/leaderboard/text-to-video",
1666
+ entries: [
1667
+ {
1668
+ rank: 1,
1669
+ creator: "Alibaba-ATH",
1670
+ model: "HappyHorse-1.0",
1671
+ elo: 1354,
1672
+ apiPricing: "$14.40/min"
1673
+ },
1674
+ {
1675
+ rank: 2,
1676
+ creator: "ByteDance Seed",
1677
+ model: "Dreamina Seedance 2.0 720p",
1678
+ elo: 1273,
1679
+ apiPricing: "No API available"
1680
+ },
1681
+ {
1682
+ rank: 3,
1683
+ creator: "KlingAI",
1684
+ model: "Kling 3.0 1080p (Pro)",
1685
+ elo: 1249,
1686
+ apiPricing: "$13.44/min"
1687
+ },
1688
+ {
1689
+ rank: 4,
1690
+ creator: "KlingAI",
1691
+ model: "Kling 3.0 Omni 1080p (Pro)",
1692
+ elo: 1233,
1693
+ apiPricing: "$13.44/min"
1694
+ },
1695
+ {
1696
+ rank: 5,
1697
+ creator: "xAI",
1698
+ model: "grok-imagine-video",
1699
+ elo: 1233,
1700
+ apiPricing: "$4.20/min"
1701
+ },
1702
+ {
1703
+ rank: 6,
1704
+ creator: "Vidu",
1705
+ model: "Vidu Q3 Pro",
1706
+ elo: 1225,
1707
+ apiPricing: "$9.60/min"
1708
+ },
1709
+ {
1710
+ rank: 7,
1711
+ creator: "Video Rebirth",
1712
+ model: "Bach-1.0 Preview",
1713
+ elo: 1224,
1714
+ apiPricing: "$3.00/min"
1715
+ },
1716
+ {
1717
+ rank: 8,
1718
+ creator: "KlingAI",
1719
+ model: "Kling 3.0 Omni 720p (Standard)",
1720
+ elo: 1224,
1721
+ apiPricing: "$10.08/min"
1722
+ },
1723
+ {
1724
+ rank: 9,
1725
+ creator: "PixVerse",
1726
+ model: "PixVerse V6",
1727
+ elo: 1222,
1728
+ apiPricing: "$5.40/min"
1729
+ },
1730
+ {
1731
+ rank: 10,
1732
+ creator: "PixVerse",
1733
+ model: "PixVerse V5.6",
1734
+ elo: 1221,
1735
+ apiPricing: "$9.00/min"
1736
+ },
1737
+ {
1738
+ rank: 11,
1739
+ creator: "Runway",
1740
+ model: "Runway Gen-4.5",
1741
+ elo: 1220,
1742
+ apiPricing: "No API available"
1743
+ },
1744
+ {
1745
+ rank: 12,
1746
+ creator: "Google",
1747
+ model: "Veo 3",
1748
+ elo: 1218,
1749
+ apiPricing: "$12.00/min"
1750
+ },
1751
+ {
1752
+ rank: 13,
1753
+ creator: "KlingAI",
1754
+ model: "Kling 3.0 720p (Standard)",
1755
+ elo: 1215,
1756
+ apiPricing: "$10.08/min"
1757
+ },
1758
+ {
1759
+ rank: 14,
1760
+ creator: "Google",
1761
+ model: "Veo 3.1 Lite",
1762
+ elo: 1214,
1763
+ apiPricing: "$3.00/min"
1764
+ },
1765
+ {
1766
+ rank: 15,
1767
+ creator: "KlingAI",
1768
+ model: "Kling O1 Pro (January)",
1769
+ elo: 1209,
1770
+ apiPricing: "$10.08/min"
1771
+ }
1772
+ ]
1773
+ };
1774
+ var VIDEO_IMAGE_TO_VIDEO_TOP_15 = {
1775
+ capturedAt: "2026-05-12",
1776
+ source: "https://artificialanalysis.ai/video/leaderboard/image-to-video",
1777
+ entries: [
1778
+ {
1779
+ rank: 1,
1780
+ creator: "Alibaba-ATH",
1781
+ model: "HappyHorse-1.0",
1782
+ elo: 1395,
1783
+ apiPricing: "Coming soon"
1784
+ },
1785
+ {
1786
+ rank: 2,
1787
+ creator: "ByteDance Seed",
1788
+ model: "Dreamina Seedance 2.0 720p",
1789
+ elo: 1348,
1790
+ apiPricing: "No API available"
1791
+ },
1792
+ {
1793
+ rank: 3,
1794
+ creator: "xAI",
1795
+ model: "grok-imagine-video",
1796
+ elo: 1326,
1797
+ apiPricing: "$4.20/min"
1798
+ },
1799
+ {
1800
+ rank: 4,
1801
+ creator: "PixVerse",
1802
+ model: "PixVerse V6",
1803
+ elo: 1322,
1804
+ apiPricing: "$5.40/min"
1805
+ },
1806
+ {
1807
+ rank: 5,
1808
+ creator: "Vidu",
1809
+ model: "Vidu Q3 Pro",
1810
+ elo: 1287,
1811
+ apiPricing: "$9.60/min"
1812
+ },
1813
+ {
1814
+ rank: 6,
1815
+ creator: "KlingAI",
1816
+ model: "Kling 2.5 Turbo 1080p",
1817
+ elo: 1283,
1818
+ apiPricing: "$4.20/min"
1819
+ },
1820
+ {
1821
+ rank: 7,
1822
+ creator: "KlingAI",
1823
+ model: "Kling 3.0 1080p (Pro)",
1824
+ elo: 1280,
1825
+ apiPricing: "$13.44/min"
1826
+ },
1827
+ {
1828
+ rank: 8,
1829
+ creator: "PixVerse",
1830
+ model: "PixVerse V5.6",
1831
+ elo: 1279,
1832
+ apiPricing: "$9.00/min"
1833
+ },
1834
+ {
1835
+ rank: 9,
1836
+ creator: "KlingAI",
1837
+ model: "Kling 3.0 Omni 1080p (Pro)",
1838
+ elo: 1277,
1839
+ apiPricing: "$13.44/min"
1840
+ },
1841
+ {
1842
+ rank: 10,
1843
+ creator: "KlingAI",
1844
+ model: "Kling 2.6 Standard (January)",
1845
+ elo: 1271,
1846
+ apiPricing: "Coming soon"
1847
+ },
1848
+ {
1849
+ rank: 11,
1850
+ creator: "PixVerse",
1851
+ model: "PixVerse V5.5",
1852
+ elo: 1271,
1853
+ apiPricing: "$6.40/min"
1854
+ },
1855
+ {
1856
+ rank: 12,
1857
+ creator: "Google",
1858
+ model: "Veo 3.1 Fast",
1859
+ elo: 1268,
1860
+ apiPricing: "$6.00/min"
1861
+ },
1862
+ {
1863
+ rank: 13,
1864
+ creator: "Runway",
1865
+ model: "Runway Gen-4.5",
1866
+ elo: 1263,
1867
+ apiPricing: "No API available"
1868
+ },
1869
+ {
1870
+ rank: 14,
1871
+ creator: "KlingAI",
1872
+ model: "Kling 3.0 Omni 720p (Standard)",
1873
+ elo: 1263,
1874
+ apiPricing: "$10.08/min"
1875
+ },
1876
+ {
1877
+ rank: 15,
1878
+ creator: "KlingAI",
1879
+ model: "Kling 3.0 720p (Standard)",
1880
+ elo: 1263,
1881
+ apiPricing: "$10.08/min"
1882
+ }
1883
+ ]
1884
+ };
1885
+
1886
+ // src/server.ts
1887
+ var import_neverthrow = require("neverthrow");
1888
+
1889
+ // src/tools.ts
1890
+ var FAL_EXPLORE_CHECKED_AT = "2026-05-12";
1891
+ var FAL_TOOLS = {
1892
+ nsfw: {
1893
+ name: "NSFW Checker",
1894
+ endpoint: "fal-ai/x-ailab/nsfw",
1895
+ task: "vision moderation",
1896
+ category: "moderation",
1897
+ description: "Predict whether one or more images contain NSFW concepts.",
1898
+ inputKind: "images",
1899
+ inputField: "image_urls",
1900
+ outputKeys: ["has_nsfw_concepts"],
1901
+ pricing: "$0.001/image",
1902
+ sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw"
1903
+ },
1904
+ "topaz-image": {
1905
+ name: "Topaz Image Upscale",
1906
+ endpoint: "fal-ai/topaz/upscale/image",
1907
+ task: "image enhancement",
1908
+ category: "upscale",
1909
+ description: "Professional Topaz image enhancement and upscaling.",
1910
+ inputKind: "image",
1911
+ inputField: "image_url",
1912
+ outputKeys: ["image"],
1913
+ pricing: "$0.08+ by output megapixels",
1914
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
1915
+ defaultOptions: {
1916
+ model: "Standard V2",
1917
+ output_format: "jpeg",
1918
+ upscale_factor: 2
1919
+ }
1920
+ },
1921
+ "topaz-video": {
1922
+ name: "Topaz Video Upscale",
1923
+ endpoint: "fal-ai/topaz/upscale/video",
1924
+ task: "video enhancement",
1925
+ category: "upscale",
1926
+ description: "Professional Topaz video enhancement and upscaling.",
1927
+ inputKind: "video",
1928
+ inputField: "video_url",
1929
+ outputKeys: ["video"],
1930
+ pricing: "$0.01-$0.08/sec by output resolution",
1931
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
1932
+ defaultOptions: {
1933
+ model: "Proteus",
1934
+ upscale_factor: 2
1935
+ }
1936
+ },
1937
+ "bria-rmbg": {
1938
+ name: "Bria RMBG 2.0",
1939
+ endpoint: "fal-ai/bria/background/remove",
1940
+ task: "image background removal",
1941
+ category: "background",
1942
+ description: "Commercial-safe background removal for images.",
1943
+ inputKind: "image",
1944
+ inputField: "image_url",
1945
+ outputKeys: ["image"],
1946
+ pricing: "$0.02/image",
1947
+ sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove"
1948
+ },
1949
+ birefnet: {
1950
+ name: "BirefNet Background Removal",
1951
+ endpoint: "fal-ai/birefnet/v2",
1952
+ task: "image background removal",
1953
+ category: "background",
1954
+ description: "High-resolution dichotomous image segmentation and background removal.",
1955
+ inputKind: "image",
1956
+ inputField: "image_url",
1957
+ outputKeys: ["image", "mask_image"],
1958
+ pricing: "$0/compute-second listed by fal",
1959
+ sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
1960
+ defaultOptions: {
1961
+ model: "General Use (Light)",
1962
+ operating_resolution: "1024x1024",
1963
+ output_format: "png",
1964
+ refine_foreground: true
1965
+ }
1966
+ },
1967
+ rembg: {
1968
+ name: "Remove Background",
1969
+ endpoint: "fal-ai/imageutils/rembg",
1970
+ task: "image background removal",
1971
+ category: "background",
1972
+ description: "Generic image background removal utility.",
1973
+ inputKind: "image",
1974
+ inputField: "image_url",
1975
+ outputKeys: ["image"],
1976
+ pricing: "$0/compute-second listed by fal",
1977
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
1978
+ defaultOptions: {
1979
+ crop_to_bbox: false
1980
+ }
1981
+ },
1982
+ "bria-video-rmbg": {
1983
+ name: "Bria Video Background Removal",
1984
+ endpoint: "bria/video/background-removal",
1985
+ task: "video background removal",
1986
+ category: "background",
1987
+ description: "Remove video backgrounds with configurable output container.",
1988
+ inputKind: "video",
1989
+ inputField: "video_url",
1990
+ outputKeys: ["video"],
1991
+ pricing: "$0.14/sec",
1992
+ sourceUrl: "https://fal.ai/models/bria/video/background-removal",
1993
+ defaultOptions: {
1994
+ background_color: "Black",
1995
+ output_container_and_codec: "webm_vp9",
1996
+ preserve_audio: true
1997
+ }
1998
+ },
1999
+ lineart: {
2000
+ name: "Line Art Preprocessor",
2001
+ endpoint: "fal-ai/image-preprocessors/lineart",
2002
+ task: "image preprocessing",
2003
+ category: "preprocess",
2004
+ description: "Generate line art/control-style edges from an input image.",
2005
+ inputKind: "image",
2006
+ inputField: "image_url",
2007
+ outputKeys: ["image"],
2008
+ pricing: "$0/compute-second listed by fal",
2009
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
2010
+ defaultOptions: {
2011
+ coarse: false
2012
+ }
2013
+ },
2014
+ "sam-preprocessor": {
2015
+ name: "SAM Preprocessor",
2016
+ endpoint: "fal-ai/image-preprocessors/sam",
2017
+ task: "segmentation preprocessing",
2018
+ category: "preprocess",
2019
+ description: "Generate a SAM segmentation map for ControlNet-style workflows.",
2020
+ inputKind: "image",
2021
+ inputField: "image_url",
2022
+ outputKeys: ["image"],
2023
+ pricing: "$0/compute-second listed by fal",
2024
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam"
2025
+ },
2026
+ "midas-depth": {
2027
+ name: "MiDaS Depth Estimation",
2028
+ endpoint: "fal-ai/imageutils/depth",
2029
+ task: "depth map",
2030
+ category: "depth",
2031
+ description: "Create MiDaS depth maps from input images.",
2032
+ inputKind: "image",
2033
+ inputField: "image_url",
2034
+ outputKeys: ["image"],
2035
+ pricing: "$0/compute-second listed by fal",
2036
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
2037
+ defaultOptions: {
2038
+ a: Math.PI * 2,
2039
+ bg_th: 0.1
2040
+ }
2041
+ },
2042
+ "midas-preprocessor": {
2043
+ name: "MiDaS Preprocessor",
2044
+ endpoint: "fal-ai/image-preprocessors/midas",
2045
+ task: "depth and normal preprocessing",
2046
+ category: "preprocess",
2047
+ description: "Generate MiDaS depth and normal maps for image workflows.",
2048
+ inputKind: "image",
2049
+ inputField: "image_url",
2050
+ outputKeys: ["depth_map", "normal_map"],
2051
+ pricing: "$0/compute-second listed by fal",
2052
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas"
2053
+ },
2054
+ "marigold-depth": {
2055
+ name: "Marigold Depth Estimation",
2056
+ endpoint: "fal-ai/imageutils/marigold-depth",
2057
+ task: "depth map",
2058
+ category: "depth",
2059
+ description: "Create depth maps using Marigold depth estimation.",
2060
+ inputKind: "image",
2061
+ inputField: "image_url",
2062
+ outputKeys: ["image"],
2063
+ pricing: "$0/compute-second listed by fal",
2064
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
2065
+ defaultOptions: {
2066
+ ensemble_size: 10,
2067
+ num_inference_steps: 10
2068
+ }
2069
+ },
2070
+ "depth-anything": {
2071
+ name: "Depth Anything v2 Preprocessor",
2072
+ endpoint: "fal-ai/image-preprocessors/depth-anything/v2",
2073
+ task: "depth preprocessing",
2074
+ category: "preprocess",
2075
+ description: "Generate Depth Anything v2 depth maps from input images.",
2076
+ inputKind: "image",
2077
+ inputField: "image_url",
2078
+ outputKeys: ["image"],
2079
+ pricing: "$0/compute-second listed by fal",
2080
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2"
2081
+ },
2082
+ "sam2-auto": {
2083
+ name: "SAM 2 Auto Segment",
2084
+ endpoint: "fal-ai/sam2/auto-segment",
2085
+ task: "automatic image segmentation",
2086
+ category: "segmentation",
2087
+ description: "Automatically segment an image into combined and individual masks.",
2088
+ inputKind: "image",
2089
+ inputField: "image_url",
2090
+ outputKeys: ["combined_mask", "individual_masks"],
2091
+ pricing: "$0/compute-second listed by fal",
2092
+ sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
2093
+ defaultOptions: {
2094
+ min_mask_region_area: 100,
2095
+ output_format: "png",
2096
+ points_per_side: 32,
2097
+ pred_iou_thresh: 0.88,
2098
+ stability_score_thresh: 0.95
2099
+ }
2100
+ },
2101
+ "sam3-image": {
2102
+ name: "SAM 3 Image",
2103
+ endpoint: "fal-ai/sam-3/image",
2104
+ task: "promptable image segmentation",
2105
+ category: "segmentation",
2106
+ description: "Segment image objects with text, point, or box prompts.",
2107
+ inputKind: "image",
2108
+ inputField: "image_url",
2109
+ outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
2110
+ pricing: "$0.005/request",
2111
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
2112
+ defaultOptions: {
2113
+ apply_mask: true,
2114
+ max_masks: 3,
2115
+ output_format: "png"
2116
+ }
2117
+ },
2118
+ "sam3-image-rle": {
2119
+ name: "SAM 3 Image RLE",
2120
+ endpoint: "fal-ai/sam-3/image-rle",
2121
+ task: "promptable image segmentation to RLE",
2122
+ category: "segmentation",
2123
+ description: "Segment image objects and return run-length encoded masks.",
2124
+ inputKind: "image",
2125
+ inputField: "image_url",
2126
+ outputKeys: ["rle_masks", "metadata", "scores", "boxes"],
2127
+ pricing: "$0.005/request",
2128
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
2129
+ defaultOptions: {
2130
+ apply_mask: true,
2131
+ max_masks: 3
2132
+ }
2133
+ },
2134
+ "sam3-video": {
2135
+ name: "SAM 3 Video",
2136
+ endpoint: "fal-ai/sam-3/video",
2137
+ task: "promptable video segmentation",
2138
+ category: "segmentation",
2139
+ description: "Segment and track prompted objects across video frames.",
2140
+ inputKind: "video",
2141
+ inputField: "video_url",
2142
+ outputKeys: ["video", "boundingbox_frames_zip"],
2143
+ pricing: "$0.005/16 frames",
2144
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
2145
+ defaultOptions: {
2146
+ apply_mask: true,
2147
+ detection_threshold: 0.5,
2148
+ prompt: "person",
2149
+ video_output_type: "X264 (.mp4)"
2150
+ }
2151
+ },
2152
+ "sam3-video-rle": {
2153
+ name: "SAM 3 Video RLE",
2154
+ endpoint: "fal-ai/sam-3/video-rle",
2155
+ task: "promptable video segmentation to RLE",
2156
+ category: "segmentation",
2157
+ description: "Track prompted video objects and return RLE mask data.",
2158
+ inputKind: "video",
2159
+ inputField: "video_url",
2160
+ outputKeys: ["rle_masks", "metadata"],
2161
+ pricing: "$0.005/16 frames",
2162
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
2163
+ defaultOptions: {
2164
+ apply_mask: true,
2165
+ detection_threshold: 0.5,
2166
+ prompt: "person"
2167
+ }
2168
+ },
2169
+ "sam3-1-video": {
2170
+ name: "SAM 3.1 Video",
2171
+ endpoint: "fal-ai/sam-3-1/video",
2172
+ task: "multi-object video segmentation",
2173
+ category: "segmentation",
2174
+ description: "SAM 3.1 video segmentation with Object Multiplex tracking for multiple objects.",
2175
+ inputKind: "video",
2176
+ inputField: "video_url",
2177
+ outputKeys: ["video", "boundingbox_frames_zip"],
2178
+ pricing: "fal pricing varies by frame count",
2179
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
2180
+ defaultOptions: {
2181
+ apply_mask: true,
2182
+ prompt: "person"
2183
+ }
2184
+ },
2185
+ "sam3-3d-objects": {
2186
+ name: "SAM 3D Objects",
2187
+ endpoint: "fal-ai/sam-3/3d-objects",
2188
+ task: "single-image 3D object reconstruction",
2189
+ category: "3d",
2190
+ description: "Reconstruct one or more 3D objects from an image and prompts.",
2191
+ inputKind: "image",
2192
+ inputField: "image_url",
2193
+ outputKeys: [
2194
+ "gaussian_splat",
2195
+ "model_glb",
2196
+ "metadata",
2197
+ "individual_splats",
2198
+ "individual_glbs",
2199
+ "artifacts_zip"
2200
+ ],
2201
+ pricing: "$0.02/generation",
2202
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
2203
+ defaultOptions: {
2204
+ prompt: "car"
2205
+ }
2206
+ },
2207
+ "sam3-3d-body": {
2208
+ name: "SAM 3D Body",
2209
+ endpoint: "fal-ai/sam-3/3d-body",
2210
+ task: "single-image 3D body reconstruction",
2211
+ category: "3d",
2212
+ description: "Reconstruct human body meshes and keypoints from a single image.",
2213
+ inputKind: "image",
2214
+ inputField: "image_url",
2215
+ outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
2216
+ pricing: "$0.015/inference",
2217
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
2218
+ defaultOptions: {
2219
+ export_meshes: true,
2220
+ include_3d_keypoints: true,
2221
+ include_mhr_params: true
2222
+ }
2223
+ },
2224
+ "sam3-3d-align": {
2225
+ name: "SAM 3D Align",
2226
+ endpoint: "fal-ai/sam-3/3d-align",
2227
+ task: "3D scene alignment",
2228
+ category: "3d",
2229
+ description: "Align SAM 3D objects and bodies into a shared scene.",
2230
+ inputKind: "image",
2231
+ inputField: "image_url",
2232
+ outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
2233
+ pricing: "fal pricing varies by scene",
2234
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align"
2235
+ }
2236
+ };
2237
+ var FAL_TOOLS_CHECKED_AT = FAL_EXPLORE_CHECKED_AT;
2238
+ var FAL_TOOL_IDS = Object.keys(FAL_TOOLS);
2239
+ function isFalToolId(tool) {
2240
+ return Object.hasOwn(FAL_TOOLS, tool);
2241
+ }
2242
+ function buildFalToolRequest(options) {
2243
+ if (!isFalToolId(options.tool)) {
2244
+ throw new Error(`Unknown fal tool: ${options.tool}`);
2245
+ }
2246
+ const tool = FAL_TOOLS[options.tool];
2247
+ const values = options.inputs ?? (options.input ? [options.input] : []);
2248
+ if (values.length === 0) {
2249
+ throw new Error(`${options.tool} requires input media`);
2250
+ }
2251
+ const mediaValue = tool.inputKind === "images" ? values : values[0];
2252
+ const defaultOptions = "defaultOptions" in tool ? tool.defaultOptions : void 0;
2253
+ const body = {
2254
+ ...defaultOptions,
2255
+ ...options.options,
2256
+ [tool.inputField]: mediaValue
2257
+ };
2258
+ return { endpoint: tool.endpoint, body, tool };
2259
+ }
2260
+
2261
+ // src/server.ts
2262
+ var FAL_BASE_URL = "https://fal.run";
2263
+ var FAL_QUEUE_URL = "https://queue.fal.run";
2264
+ var FAL_REST_URL = "https://rest.alpha.fal.ai";
2265
+ function endpointFromQueueUrl(url, fallback) {
2266
+ if (!url) return fallback;
2267
+ try {
2268
+ const parsed = new URL(url);
2269
+ const match = parsed.pathname.match(/^\/(.+)\/requests\//);
2270
+ return match?.[1] ?? fallback;
2271
+ } catch {
2272
+ return fallback;
2273
+ }
2274
+ }
2275
+ var MotifServer = class {
2276
+ apiKey;
2277
+ timeout;
2278
+ retries;
2279
+ constructor(config) {
2280
+ if (typeof config === "string") {
2281
+ this.apiKey = config;
2282
+ this.timeout = 12e4;
2283
+ this.retries = 3;
2284
+ } else {
2285
+ this.apiKey = config.apiKey;
2286
+ this.timeout = config.timeout ?? 12e4;
2287
+ this.retries = config.retries ?? 3;
2288
+ }
2289
+ if (!this.apiKey) {
2290
+ throw new MotifError("API key is required", 0);
2291
+ }
2292
+ }
2293
+ /** ─── Synchronous Generation ──────────────────────────────── */
2294
+ /** Generate images synchronously (blocks until fal.ai returns). */
2295
+ async generate(options) {
2296
+ const config = MODELS[options.model];
2297
+ if (config?.useQueue) {
2298
+ return this.generateQueued(options);
2299
+ }
2300
+ const { endpoint, body } = buildGenerateBody(options);
2301
+ const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
2302
+ method: "POST",
2303
+ body: JSON.stringify(body)
2304
+ });
2305
+ if (response.isErr()) {
2306
+ return (0, import_neverthrow.err)(response.error);
2307
+ }
2308
+ const data = await response.value.json();
2309
+ return this.normalizeResponse(data);
2310
+ }
2311
+ async generateQueued(options) {
2312
+ const job = await this.submitGeneration(options);
2313
+ if (job.isErr()) {
2314
+ return (0, import_neverthrow.err)(job.error);
2315
+ }
2316
+ const pollIntervalMs = 3e3;
2317
+ const maxAttempts = 160;
2318
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
2319
+ const status = await this.getJobStatus(
2320
+ job.value.endpoint,
2321
+ job.value.requestId
2322
+ );
2323
+ if (status.isErr()) {
2324
+ return (0, import_neverthrow.err)(status.error);
2325
+ }
2326
+ if (status.value.status === "completed") {
2327
+ return this.getJobResult(job.value.endpoint, job.value.requestId);
2328
+ }
2329
+ if (status.value.status === "failed") {
2330
+ return (0, import_neverthrow.err)(
2331
+ new MotifError(status.value.error ?? "Queued generation failed", 0)
2332
+ );
2333
+ }
2334
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2335
+ }
2336
+ return (0, import_neverthrow.err)(new MotifError("Queued generation timed out", 0));
2337
+ }
2338
+ /** ─── Queue-Based Generation ──────────────────────────────── */
2339
+ /** Submit a generation to the fal.ai queue (returns immediately). */
2340
+ async submitGeneration(options) {
2341
+ const { endpoint, body } = buildGenerateBody(options);
2342
+ const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
2343
+ method: "POST",
2344
+ body: JSON.stringify(body)
2345
+ });
2346
+ if (response.isErr()) {
2347
+ return (0, import_neverthrow.err)(response.error);
2348
+ }
2349
+ const data = await response.value.json();
2350
+ return (0, import_neverthrow.ok)({
2351
+ requestId: data.request_id,
2352
+ endpoint: endpointFromQueueUrl(data.response_url, endpoint),
2353
+ estimatedCost: estimateCost(
2354
+ options.model,
2355
+ options.resolution,
2356
+ options.numImages
2357
+ )
2358
+ });
2359
+ }
2360
+ /** Check the status of a queued generation. */
2361
+ async getJobStatus(endpoint, requestId) {
2362
+ const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}/status?logs=1`;
2363
+ const response = await this.request(url);
2364
+ if (response.isErr()) {
2365
+ return (0, import_neverthrow.err)(response.error);
2366
+ }
2367
+ const data = await response.value.json();
2368
+ let status;
2369
+ if (data.status === "IN_QUEUE") {
2370
+ status = "queued";
2371
+ } else if (data.status === "IN_PROGRESS") {
2372
+ status = "processing";
2373
+ } else if (data.status === "COMPLETED") {
2374
+ status = "completed";
2375
+ } else if (data.status === "FAILED" || data.status === "ERROR" || data.status === "CANCELED") {
2376
+ status = "failed";
2377
+ } else {
2378
+ return (0, import_neverthrow.err)(new MotifError(`Unknown job status: ${data.status}`, 0));
2379
+ }
2380
+ return (0, import_neverthrow.ok)({
2381
+ status,
2382
+ error: data.error ?? data.detail,
2383
+ queuePosition: data.queue_position,
2384
+ logs: data.logs
2385
+ });
2386
+ }
2387
+ /** Fetch the completed result from the queue. */
2388
+ async getJobResult(endpoint, requestId) {
2389
+ const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}`;
2390
+ const response = await this.request(url);
2391
+ if (response.isErr()) {
2392
+ return (0, import_neverthrow.err)(response.error);
2393
+ }
2394
+ const data = await response.value.json();
2395
+ return this.normalizeResponse(data);
2396
+ }
2397
+ /** ─── Processing ──────────────────────────────────────────── */
2398
+ /** Upscale an image using clarity or crystal upscaler. */
2399
+ async upscale(options) {
2400
+ const {
2401
+ imageUrl,
2402
+ model = "clarity",
2403
+ scaleFactor,
2404
+ creativity,
2405
+ resemblance,
2406
+ prompt: upscalePrompt,
2407
+ negativePrompt,
2408
+ numInferenceSteps,
2409
+ guidanceScale
2410
+ } = options;
2411
+ const config = MODELS[model];
2412
+ if (!config || config.type !== "utility") {
2413
+ return (0, import_neverthrow.err)(new MotifError(`Invalid upscale model: ${model}`, 0));
2414
+ }
2415
+ const body = { image_url: imageUrl };
2416
+ if (model === "crystal") {
2417
+ if (scaleFactor !== void 0) body.scale_factor = scaleFactor;
2418
+ if (creativity !== void 0) body.creativity = creativity;
2419
+ } else {
2420
+ if (scaleFactor !== void 0) body.upscale_factor = scaleFactor;
2421
+ if (creativity !== void 0) body.creativity = creativity;
2422
+ if (resemblance !== void 0) body.resemblance = resemblance;
2423
+ if (upscalePrompt) body.prompt = upscalePrompt;
2424
+ if (negativePrompt) body.negative_prompt = negativePrompt;
2425
+ if (numInferenceSteps !== void 0)
2426
+ body.num_inference_steps = numInferenceSteps;
2427
+ if (guidanceScale !== void 0) body.guidance_scale = guidanceScale;
2428
+ }
2429
+ const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
2430
+ method: "POST",
2431
+ body: JSON.stringify(body)
2432
+ });
2433
+ if (response.isErr()) {
2434
+ return (0, import_neverthrow.err)(response.error);
2435
+ }
2436
+ const data = await response.value.json();
2437
+ return this.normalizeResponse(data);
2438
+ }
2439
+ /** Remove the background from an image. */
2440
+ async removeBackground(options) {
2441
+ const {
2442
+ imageUrl,
2443
+ model = "rmbg",
2444
+ variant,
2445
+ operatingResolution,
2446
+ outputFormat,
2447
+ refineForeground,
2448
+ outputMask
2449
+ } = options;
2450
+ const config = MODELS[model];
2451
+ if (!config) {
2452
+ return (0, import_neverthrow.err)(
2453
+ new MotifError(`Invalid background removal model: ${model}`, 0)
2454
+ );
2455
+ }
2456
+ const rbBody = { image_url: imageUrl };
2457
+ if (model === "rmbg") {
2458
+ if (variant) rbBody.model = variant;
2459
+ if (operatingResolution)
2460
+ rbBody.operating_resolution = operatingResolution;
2461
+ if (outputFormat) rbBody.output_format = outputFormat;
2462
+ if (refineForeground !== void 0)
2463
+ rbBody.refine_foreground = refineForeground;
2464
+ if (outputMask !== void 0) rbBody.output_mask = outputMask;
2465
+ }
2466
+ const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
2467
+ method: "POST",
2468
+ body: JSON.stringify(rbBody)
2469
+ });
2470
+ if (response.isErr()) {
2471
+ return (0, import_neverthrow.err)(response.error);
2472
+ }
2473
+ const data = await response.value.json();
2474
+ return this.normalizeResponse(data);
2475
+ }
2476
+ /** ─── Video Generation ───────────────────────────────────── */
2477
+ /**
2478
+ * Generate a video from an image using Kling v3 Pro.
2479
+ * This uses the queue API since video generation takes 30-120s.
2480
+ * Returns immediately with a job — poll with getJobStatus/getVideoResult.
2481
+ */
2482
+ async submitVideo(options) {
2483
+ const {
2484
+ imageUrl,
2485
+ prompt,
2486
+ duration = 5,
2487
+ generateAudio = true,
2488
+ endImageUrl,
2489
+ negativePrompt,
2490
+ cfgScale
2491
+ } = options;
2492
+ const config = MODELS.kling;
2493
+ if (!config) {
2494
+ return (0, import_neverthrow.err)(new MotifError("Kling video model not found", 0));
2495
+ }
2496
+ const body = {
2497
+ start_image_url: imageUrl,
2498
+ prompt,
2499
+ duration: String(duration),
2500
+ generate_audio: generateAudio
2501
+ };
2502
+ if (endImageUrl) {
2503
+ body.end_image_url = endImageUrl;
2504
+ }
2505
+ if (negativePrompt) body.negative_prompt = negativePrompt;
2506
+ if (cfgScale !== void 0) body.cfg_scale = cfgScale;
2507
+ const response = await this.request(`${FAL_QUEUE_URL}/${config.endpoint}`, {
2508
+ method: "POST",
2509
+ body: JSON.stringify(body)
2510
+ });
2511
+ if (response.isErr()) {
2512
+ return (0, import_neverthrow.err)(response.error);
2513
+ }
2514
+ const data = await response.value.json();
2515
+ return (0, import_neverthrow.ok)({
2516
+ requestId: data.request_id,
2517
+ endpoint: endpointFromQueueUrl(data.response_url, config.endpoint),
2518
+ estimatedCost: estimateVideoCost(duration, generateAudio)
2519
+ });
2520
+ }
2521
+ /** Fetch the completed video result from the queue. */
2522
+ async getVideoResult(endpoint, requestId) {
2523
+ const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}`;
2524
+ const response = await this.request(url);
2525
+ if (response.isErr()) {
2526
+ return (0, import_neverthrow.err)(response.error);
2527
+ }
2528
+ const data = await response.value.json();
2529
+ if (!data.video) {
2530
+ return (0, import_neverthrow.err)(new MotifError("No video in response", 0));
2531
+ }
2532
+ return (0, import_neverthrow.ok)({
2533
+ url: data.video.url,
2534
+ contentType: data.video.content_type,
2535
+ fileName: data.video.file_name,
2536
+ fileSize: data.video.file_size
2537
+ });
2538
+ }
2539
+ /** ─── File Upload ─────────────────────────────────────────── */
2540
+ /**
2541
+ * Upload a file to fal.ai CDN storage and return the public URL.
2542
+ * Uses the two-step initiate + PUT flow.
2543
+ */
2544
+ async uploadToFalCdn(file, options) {
2545
+ const initiateResponse = await this.request(
2546
+ `${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`,
2547
+ {
2548
+ method: "POST",
2549
+ body: JSON.stringify({
2550
+ content_type: options.contentType,
2551
+ file_name: options.fileName
2552
+ })
2553
+ }
2554
+ );
2555
+ if (initiateResponse.isErr()) {
2556
+ return (0, import_neverthrow.err)(initiateResponse.error);
2557
+ }
2558
+ const { file_url, upload_url } = await initiateResponse.value.json();
2559
+ let putResponse;
2560
+ try {
2561
+ const body = Buffer.from(
2562
+ file instanceof Uint8Array ? file : new Uint8Array(file)
2563
+ );
2564
+ putResponse = await fetch(upload_url, {
2565
+ method: "PUT",
2566
+ headers: { "Content-Type": options.contentType },
2567
+ body
2568
+ });
2569
+ } catch (error) {
2570
+ return (0, import_neverthrow.err)(
2571
+ new MotifError(
2572
+ `Upload PUT failed: ${error instanceof Error ? error.message : String(error)}`,
2573
+ 0
2574
+ )
2575
+ );
2576
+ }
2577
+ if (!putResponse.ok) {
2578
+ return (0, import_neverthrow.err)(
2579
+ new MotifError(
2580
+ `Upload PUT failed: ${putResponse.status}`,
2581
+ putResponse.status
2582
+ )
2583
+ );
2584
+ }
2585
+ return (0, import_neverthrow.ok)(file_url);
2586
+ }
2587
+ /** ─── Utilities ───────────────────────────────────────────── */
2588
+ /** Run a registered fal utility/tool endpoint. */
2589
+ async runTool(options) {
2590
+ let request;
2591
+ try {
2592
+ request = buildFalToolRequest(options);
2593
+ } catch (error) {
2594
+ return (0, import_neverthrow.err)(
2595
+ new MotifError(
2596
+ error instanceof Error ? error.message : String(error),
2597
+ 0
2598
+ )
2599
+ );
2600
+ }
2601
+ const response = await this.request(`${FAL_BASE_URL}/${request.endpoint}`, {
2602
+ method: "POST",
2603
+ body: JSON.stringify(request.body)
2604
+ });
2605
+ if (response.isErr()) {
2606
+ return (0, import_neverthrow.err)(response.error);
2607
+ }
2608
+ return (0, import_neverthrow.ok)(await response.value.json());
2609
+ }
2610
+ /** Estimate cost for a generation (no API call). */
2611
+ estimateCost(model, resolution, numImages) {
2612
+ return estimateCost(model, resolution, numImages);
2613
+ }
2614
+ /** Build the fal.ai request body without sending it. */
2615
+ buildRequestBody(options) {
2616
+ return buildGenerateBody(options);
2617
+ }
2618
+ /** Model registry. */
2619
+ get models() {
2620
+ return MODELS;
2621
+ }
2622
+ /** Generation model keys. */
2623
+ get generationModels() {
2624
+ return GENERATION_MODELS;
2625
+ }
2626
+ /** Utility model keys. */
2627
+ get utilityModels() {
2628
+ return UTILITY_MODELS;
2629
+ }
2630
+ /** Registered fal utility/tool endpoints. */
2631
+ get tools() {
2632
+ return FAL_TOOLS;
2633
+ }
2634
+ /** ─── Private ─────────────────────────────────────────────── */
2635
+ /** Authenticated fetch to fal.ai APIs with retry logic. */
2636
+ async request(url, options = {}) {
2637
+ let lastError = null;
2638
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
2639
+ const controller = new AbortController();
2640
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2641
+ try {
2642
+ const response = await fetch(url, {
2643
+ ...options,
2644
+ signal: controller.signal,
2645
+ headers: {
2646
+ Authorization: `Key ${this.apiKey}`,
2647
+ "Content-Type": "application/json",
2648
+ ...options.headers
2649
+ }
2650
+ });
2651
+ clearTimeout(timeoutId);
2652
+ if ((response.status === 429 || response.status >= 500) && attempt < this.retries) {
2653
+ const delay = 1e3 * 2 ** attempt;
2654
+ await new Promise((r) => setTimeout(r, delay));
2655
+ continue;
2656
+ }
2657
+ if (!response.ok) {
2658
+ const text = await response.text();
2659
+ return (0, import_neverthrow.err)(
2660
+ new MotifError(
2661
+ `Request failed: ${response.status} ${text}`,
2662
+ response.status
2663
+ )
2664
+ );
2665
+ }
2666
+ return (0, import_neverthrow.ok)(response);
2667
+ } catch (error) {
2668
+ clearTimeout(timeoutId);
2669
+ if (error instanceof MotifError) {
2670
+ return (0, import_neverthrow.err)(error);
2671
+ }
2672
+ lastError = new MotifError(
2673
+ error instanceof Error ? error.message : String(error),
2674
+ 0
2675
+ );
2676
+ if (attempt < this.retries) {
2677
+ const delay = 1e3 * 2 ** attempt;
2678
+ await new Promise((r) => setTimeout(r, delay));
2679
+ }
2680
+ }
2681
+ }
2682
+ return (0, import_neverthrow.err)(lastError ?? new MotifError("Request failed after retries", 0));
2683
+ }
2684
+ /**
2685
+ * Normalize fal.ai responses.
2686
+ * Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
2687
+ */
2688
+ normalizeResponse(data) {
2689
+ const obj = data;
2690
+ if ("detail" in obj) {
2691
+ return (0, import_neverthrow.err)(
2692
+ new MotifError(obj.detail, 0, "FAL_ERROR")
2693
+ );
2694
+ }
2695
+ if ("image" in obj && !("images" in obj)) {
2696
+ return (0, import_neverthrow.ok)({
2697
+ images: [obj.image],
2698
+ seed: obj.seed,
2699
+ prompt: obj.prompt
2700
+ });
2701
+ }
2702
+ return (0, import_neverthrow.ok)(obj);
2703
+ }
2704
+ };
2705
+ var MotifError = class extends Error {
2706
+ status;
2707
+ code;
2708
+ constructor(message, status, code) {
2709
+ super(message);
2710
+ this.name = "MotifError";
2711
+ this.status = status;
2712
+ this.code = code;
2713
+ }
2714
+ };
2715
+ // Annotate the CommonJS export names for ESM import in node:
2716
+ 0 && (module.exports = {
2717
+ ASPECT_RATIOS,
2718
+ FAL_TOOLS,
2719
+ FAL_TOOLS_CHECKED_AT,
2720
+ FAL_TOOL_IDS,
2721
+ FORMAT_PRESETS,
2722
+ GENERATION_MODELS,
2723
+ IDEOGRAM_STYLES,
2724
+ IMAGE_EDITING_TOP_20,
2725
+ IMAGE_TEXT_TO_IMAGE_TOP_20,
2726
+ MODELS,
2727
+ MotifError,
2728
+ MotifServer,
2729
+ RECRAFT_STYLES,
2730
+ RESOLUTIONS,
2731
+ UTILITY_MODELS,
2732
+ VIDEO_IMAGE_TO_VIDEO_TOP_15,
2733
+ VIDEO_MODELS,
2734
+ VIDEO_TEXT_TO_VIDEO_TOP_15,
2735
+ aspectToFalImageSize,
2736
+ aspectToGptSize,
2737
+ buildFalToolRequest,
2738
+ buildGenerateBody,
2739
+ err,
2740
+ estimateCost,
2741
+ estimateVideoCost,
2742
+ getFalKeyFromEnv,
2743
+ isFalToolId,
2744
+ motifEnvSchema,
2745
+ ok,
2746
+ parseMotifEnv
2747
+ });