@howells/motif-sdk 0.6.0 → 0.7.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/image.js ADDED
@@ -0,0 +1,1525 @@
1
+ // src/image/index.ts
2
+ import { generateImage } from "ai";
3
+ import { err as err2, ok as ok2 } from "neverthrow";
4
+
5
+ // src/server.ts
6
+ import { err, ok } from "neverthrow";
7
+
8
+ // src/models.ts
9
+ var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-05-12";
10
+ var AA_IMAGE_SOURCES = [
11
+ "https://artificialanalysis.ai/image/leaderboard/text-to-image",
12
+ "https://artificialanalysis.ai/image/leaderboard/editing"
13
+ ];
14
+ var FAL_PRICING_CHECKED_AT = "2026-05-12";
15
+ var FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
16
+ var MODELS = {
17
+ // ─── Generation Models ────────────────────────────────────────
18
+ gpt2: {
19
+ benchmark: {
20
+ artificialAnalysis: {
21
+ editing: { elo: 1249, pricePer1k: 211, rank: 3, winRate: 0.66 },
22
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
23
+ sourceUrls: AA_IMAGE_SOURCES,
24
+ textToImage: { elo: 1337, pricePer1k: 211, rank: 1, winRate: 0.8 }
25
+ },
26
+ speed: {
27
+ medianSeconds: 200.7,
28
+ p95Seconds: 232.6,
29
+ source: "artificial-analysis-models"
30
+ },
31
+ tiers: { price: "ultra", quality: "frontier", speed: "very_slow" },
32
+ useCase: "Highest-ranked text-to-image quality and transparent PNGs"
33
+ },
34
+ editEndpoint: "openai/gpt-image-2/image-to-image",
35
+ endpoint: "openai/gpt-image-2",
36
+ falPricing: {
37
+ checkedAt: FAL_PRICING_CHECKED_AT,
38
+ currency: "USD",
39
+ endpointId: "openai/gpt-image-2",
40
+ estimatedCostPerImageUsd: 0.211,
41
+ source: "fal-pricing-api",
42
+ unit: "units",
43
+ unitPrice: 1
44
+ },
45
+ maxReferenceImages: 4,
46
+ name: "GPT Image 2",
47
+ pricePerImageUsd: 0.211,
48
+ pricing: "$0.211",
49
+ sizeMode: "image_size_enum",
50
+ supportsAspect: true,
51
+ supportsEdit: true,
52
+ supportsMaskImage: true,
53
+ supportsNumImages: true,
54
+ supportsOutputFormat: true,
55
+ supportsQuality: true,
56
+ supportsResolution: false,
57
+ supportsSyncMode: true,
58
+ type: "generation",
59
+ useQueue: true
60
+ },
61
+ gpt: {
62
+ benchmark: {
63
+ artificialAnalysis: {
64
+ editing: { elo: 1262, pricePer1k: 133, rank: 2, winRate: 0.69 },
65
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
66
+ sourceUrls: AA_IMAGE_SOURCES,
67
+ textToImage: { elo: 1268, pricePer1k: 133, rank: 2, winRate: 0.73 }
68
+ },
69
+ speed: {
70
+ medianSeconds: 34.6,
71
+ p95Seconds: 38.3,
72
+ source: "artificial-analysis-models"
73
+ },
74
+ tiers: { price: "ultra", quality: "frontier", speed: "slow" },
75
+ useCase: "Best OpenAI edit quality with transparent PNG support"
76
+ },
77
+ editEndpoint: "fal-ai/gpt-image-1.5/edit",
78
+ endpoint: "fal-ai/gpt-image-1.5",
79
+ falPricing: {
80
+ checkedAt: FAL_PRICING_CHECKED_AT,
81
+ currency: "USD",
82
+ endpointId: "fal-ai/gpt-image-1.5",
83
+ estimatedCostPerImageUsd: 0.133,
84
+ source: "fal-pricing-api",
85
+ unit: "units",
86
+ unitPrice: 1
87
+ },
88
+ maxReferenceImages: 4,
89
+ name: "GPT Image 1.5",
90
+ pricePerImageUsd: 0.133,
91
+ pricing: "$0.133",
92
+ sizeMode: "gpt_size",
93
+ supportsAspect: false,
94
+ supportsBackground: true,
95
+ supportsEdit: true,
96
+ supportsMaskImage: true,
97
+ supportsNumImages: true,
98
+ supportsOutputFormat: true,
99
+ supportsQuality: true,
100
+ supportsResolution: false,
101
+ supportsSyncMode: true,
102
+ type: "generation"
103
+ },
104
+ banana2: {
105
+ benchmark: {
106
+ artificialAnalysis: {
107
+ editing: { elo: 1231, pricePer1k: 67, rank: 5, winRate: 0.63 },
108
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
109
+ sourceUrls: AA_IMAGE_SOURCES,
110
+ textToImage: { elo: 1263, pricePer1k: 67, rank: 3, winRate: 0.71 }
111
+ },
112
+ tiers: { price: "premium", quality: "frontier", speed: "unknown" },
113
+ useCase: "Best balance of quality, edit support, web search, and cost"
114
+ },
115
+ editEndpoint: "fal-ai/nano-banana-2/edit",
116
+ endpoint: "fal-ai/nano-banana-2",
117
+ falPricing: {
118
+ checkedAt: FAL_PRICING_CHECKED_AT,
119
+ currency: "USD",
120
+ endpointId: "fal-ai/nano-banana-2",
121
+ estimatedCostPerImageUsd: 0.08,
122
+ source: "fal-pricing-api",
123
+ unit: "images",
124
+ unitPrice: 0.08
125
+ },
126
+ maxReferenceImages: 4,
127
+ name: "Nano Banana 2",
128
+ pricePerImageUsd: 0.08,
129
+ pricing: "$0.08",
130
+ sizeMode: "aspect_ratio",
131
+ supportsAspect: true,
132
+ supportsEdit: true,
133
+ supportsGoogleSearch: true,
134
+ supportsLimitGenerations: true,
135
+ supportsNumImages: true,
136
+ supportsOutputFormat: true,
137
+ supportsResolution: true,
138
+ supportsSafetyTolerance: true,
139
+ supportsSeed: true,
140
+ supportsSyncMode: true,
141
+ supportsThinkingLevel: true,
142
+ supportsWebSearch: true,
143
+ type: "generation"
144
+ },
145
+ banana: {
146
+ benchmark: {
147
+ artificialAnalysis: {
148
+ editing: { elo: 1241, pricePer1k: 134, rank: 4, winRate: 0.65 },
149
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
150
+ sourceUrls: AA_IMAGE_SOURCES,
151
+ textToImage: { elo: 1220, pricePer1k: 134, rank: 5, winRate: 0.66 }
152
+ },
153
+ speed: {
154
+ medianSeconds: 19.2,
155
+ p95Seconds: 30.3,
156
+ source: "artificial-analysis-models"
157
+ },
158
+ tiers: { price: "ultra", quality: "frontier", speed: "balanced" },
159
+ useCase: "Premium Gemini image generation and multi-reference edits"
160
+ },
161
+ editEndpoint: "fal-ai/nano-banana-pro/edit",
162
+ endpoint: "fal-ai/nano-banana-pro",
163
+ falPricing: {
164
+ checkedAt: FAL_PRICING_CHECKED_AT,
165
+ currency: "USD",
166
+ endpointId: "fal-ai/nano-banana-pro",
167
+ estimatedCostPerImageUsd: 0.15,
168
+ source: "fal-pricing-api",
169
+ unit: "images",
170
+ unitPrice: 0.15
171
+ },
172
+ maxReferenceImages: 14,
173
+ name: "Nano Banana Pro",
174
+ pricePerImageUsd: 0.15,
175
+ pricing: "$0.15",
176
+ sizeMode: "aspect_ratio",
177
+ supportsAspect: true,
178
+ supportsEdit: true,
179
+ supportsGoogleSearch: true,
180
+ supportsLimitGenerations: true,
181
+ supportsNumImages: true,
182
+ supportsOutputFormat: true,
183
+ supportsResolution: true,
184
+ supportsSafetyTolerance: true,
185
+ supportsSeed: true,
186
+ supportsSyncMode: true,
187
+ supportsWebSearch: true,
188
+ type: "generation"
189
+ },
190
+ gemini: {
191
+ editEndpoint: "fal-ai/gemini-25-flash-image/edit",
192
+ endpoint: "fal-ai/gemini-25-flash-image",
193
+ falPricing: {
194
+ checkedAt: FAL_PRICING_CHECKED_AT,
195
+ currency: "USD",
196
+ endpointId: "fal-ai/gemini-25-flash-image",
197
+ estimatedCostPerImageUsd: 0.0398,
198
+ source: "fal-pricing-api",
199
+ unit: "images",
200
+ unitPrice: 0.0398
201
+ },
202
+ maxReferenceImages: 4,
203
+ name: "Gemini 2.5 Flash",
204
+ pricePerImageUsd: 0.0398,
205
+ pricing: "$0.04",
206
+ sizeMode: "aspect_ratio",
207
+ supportsAspect: true,
208
+ supportsEdit: true,
209
+ supportsNumImages: true,
210
+ supportsOutputFormat: true,
211
+ supportsResolution: false,
212
+ supportsSafetyTolerance: true,
213
+ supportsSeed: true,
214
+ supportsSyncMode: true,
215
+ type: "generation"
216
+ },
217
+ gemini3: {
218
+ editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
219
+ endpoint: "fal-ai/gemini-3-pro-image-preview",
220
+ falPricing: {
221
+ checkedAt: FAL_PRICING_CHECKED_AT,
222
+ currency: "USD",
223
+ endpointId: "fal-ai/gemini-3-pro-image-preview",
224
+ estimatedCostPerImageUsd: 0.15,
225
+ source: "fal-pricing-api",
226
+ unit: "images",
227
+ unitPrice: 0.15
228
+ },
229
+ maxReferenceImages: 4,
230
+ name: "Gemini 3 Pro",
231
+ pricePerImageUsd: 0.15,
232
+ pricing: "$0.15",
233
+ sizeMode: "aspect_ratio",
234
+ supportsAspect: true,
235
+ supportsEdit: true,
236
+ supportsNumImages: true,
237
+ supportsOutputFormat: true,
238
+ supportsResolution: true,
239
+ supportsSafetyTolerance: true,
240
+ supportsSeed: true,
241
+ supportsSyncMode: true,
242
+ supportsWebSearch: true,
243
+ type: "generation"
244
+ },
245
+ seedream4: {
246
+ benchmark: {
247
+ artificialAnalysis: {
248
+ editing: { elo: 1184, pricePer1k: 30, rank: 16, winRate: 0.61 },
249
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
250
+ sourceUrls: AA_IMAGE_SOURCES,
251
+ textToImage: { elo: 1198, pricePer1k: 30, rank: 6, winRate: 0.59 }
252
+ },
253
+ speed: {
254
+ medianSeconds: 14.3,
255
+ p95Seconds: 21,
256
+ source: "artificial-analysis-models"
257
+ },
258
+ tiers: { price: "budget", quality: "best", speed: "balanced" },
259
+ useCase: "High-ranked budget generation and large multi-reference edits"
260
+ },
261
+ editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
262
+ endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
263
+ falPricing: {
264
+ checkedAt: FAL_PRICING_CHECKED_AT,
265
+ currency: "USD",
266
+ endpointId: "fal-ai/bytedance/seedream/v4/text-to-image",
267
+ estimatedCostPerImageUsd: 0.03,
268
+ source: "fal-pricing-api",
269
+ unit: "images",
270
+ unitPrice: 0.03
271
+ },
272
+ maxReferenceImages: 10,
273
+ name: "Seedream 4.0",
274
+ pricePerImageUsd: 0.03,
275
+ pricing: "$0.03",
276
+ sizeMode: "image_size_enum",
277
+ supportsAspect: true,
278
+ supportsEdit: true,
279
+ supportsNumImages: true,
280
+ supportsResolution: false,
281
+ supportsSeed: true,
282
+ supportsSyncMode: true,
283
+ type: "generation"
284
+ },
285
+ seedream45: {
286
+ benchmark: {
287
+ artificialAnalysis: {
288
+ editing: { elo: 1184, pricePer1k: 40, rank: 17, winRate: 0.58 },
289
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
290
+ sourceUrls: AA_IMAGE_SOURCES,
291
+ textToImage: { elo: 1167, pricePer1k: 40, rank: 17, winRate: 0.63 }
292
+ },
293
+ speed: {
294
+ medianSeconds: 18.2,
295
+ p95Seconds: 43.3,
296
+ source: "artificial-analysis-models"
297
+ },
298
+ tiers: { price: "standard", quality: "better", speed: "balanced" },
299
+ useCase: "Cheap current Seedream model with good animation/art scores"
300
+ },
301
+ editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
302
+ endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
303
+ falPricing: {
304
+ checkedAt: FAL_PRICING_CHECKED_AT,
305
+ currency: "USD",
306
+ endpointId: "fal-ai/bytedance/seedream/v4.5/text-to-image",
307
+ estimatedCostPerImageUsd: 0.04,
308
+ source: "fal-pricing-api",
309
+ unit: "images",
310
+ unitPrice: 0.04
311
+ },
312
+ maxReferenceImages: 10,
313
+ name: "Seedream 4.5",
314
+ pricePerImageUsd: 0.04,
315
+ pricing: "$0.04",
316
+ sizeMode: "image_size_enum",
317
+ supportsAspect: true,
318
+ supportsEdit: true,
319
+ supportsNumImages: true,
320
+ supportsResolution: false,
321
+ supportsSeed: true,
322
+ supportsSyncMode: true,
323
+ type: "generation"
324
+ },
325
+ seedream5: {
326
+ name: "Seedream 5.0 Pro",
327
+ endpoint: "bytedance/seedream/v5/pro/text-to-image",
328
+ editEndpoint: "bytedance/seedream/v5/pro/edit",
329
+ type: "generation",
330
+ // fal tiers Seedream v5 Pro by output size: $0.0675 up to 1536², $0.135 up to 2048².
331
+ pricing: "$0.0675 (\u22641536\xB2) / $0.135 (\u22642048\xB2)",
332
+ pricePerImageUsd: 0.0675,
333
+ falPricing: {
334
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
335
+ currency: "USD",
336
+ endpointId: "bytedance/seedream/v5/pro/text-to-image",
337
+ estimatedCostPerImageUsd: 0.0675,
338
+ source: "fal-pricing-api",
339
+ unit: "images",
340
+ unitPrice: 0.0675
341
+ },
342
+ sizeMode: "image_size_enum",
343
+ supportsAspect: true,
344
+ supportsResolution: false,
345
+ supportsEdit: true,
346
+ supportsNumImages: true,
347
+ supportsSeed: true,
348
+ supportsSyncMode: true,
349
+ maxReferenceImages: 10
350
+ },
351
+ "seedream5-lite": {
352
+ name: "Seedream 5.0 Lite",
353
+ endpoint: "fal-ai/bytedance/seedream/v5/lite/text-to-image",
354
+ editEndpoint: "fal-ai/bytedance/seedream/v5/lite/edit",
355
+ type: "generation",
356
+ // Flat price regardless of resolution; native up to Auto 3K (~9.4MP).
357
+ pricing: "$0.035",
358
+ pricePerImageUsd: 0.035,
359
+ falPricing: {
360
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
361
+ currency: "USD",
362
+ endpointId: "fal-ai/bytedance/seedream/v5/lite/text-to-image",
363
+ estimatedCostPerImageUsd: 0.035,
364
+ source: "fal-pricing-api",
365
+ unit: "images",
366
+ unitPrice: 0.035
367
+ },
368
+ sizeMode: "image_size_enum",
369
+ supportsAspect: true,
370
+ supportsResolution: false,
371
+ supportsEdit: true,
372
+ supportsNumImages: true,
373
+ supportsSeed: true,
374
+ supportsSyncMode: true,
375
+ maxReferenceImages: 10
376
+ },
377
+ "flux2-max": {
378
+ benchmark: {
379
+ artificialAnalysis: {
380
+ editing: { elo: 1206, pricePer1k: 140, rank: 10, winRate: 0.6 },
381
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
382
+ sourceUrls: AA_IMAGE_SOURCES,
383
+ textToImage: { elo: 1197, pricePer1k: 70, rank: 8, winRate: 0.67 }
384
+ },
385
+ speed: {
386
+ medianSeconds: 31.6,
387
+ p95Seconds: 41,
388
+ source: "artificial-analysis-models"
389
+ },
390
+ tiers: { price: "premium", quality: "best", speed: "slow" },
391
+ useCase: "Best FLUX quality and high-end editing"
392
+ },
393
+ editEndpoint: "fal-ai/flux-2-max/edit",
394
+ endpoint: "fal-ai/flux-2-max",
395
+ falPricing: {
396
+ checkedAt: FAL_PRICING_CHECKED_AT,
397
+ currency: "USD",
398
+ endpointId: "fal-ai/flux-2-max",
399
+ estimatedCostPerImageUsd: 0.07,
400
+ source: "fal-pricing-api",
401
+ unit: "megapixels",
402
+ unitPrice: 0.07
403
+ },
404
+ maxReferenceImages: 10,
405
+ name: "FLUX.2 Max",
406
+ pricePerImageUsd: 0.07,
407
+ pricing: "$0.07/MP",
408
+ sizeMode: "image_size_enum",
409
+ supportsAspect: true,
410
+ supportsEdit: true,
411
+ supportsNumImages: false,
412
+ supportsOutputFormat: true,
413
+ supportsResolution: false,
414
+ supportsSafetyChecker: true,
415
+ supportsSafetyTolerance: true,
416
+ supportsSeed: true,
417
+ supportsSyncMode: true,
418
+ type: "generation"
419
+ },
420
+ "flux2-pro": {
421
+ benchmark: {
422
+ artificialAnalysis: {
423
+ editing: { elo: 1170, pricePer1k: 30, rank: 21 },
424
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
425
+ sourceUrls: AA_IMAGE_SOURCES,
426
+ textToImage: { elo: 1186, pricePer1k: 30, rank: 10, winRate: 0.62 }
427
+ },
428
+ speed: {
429
+ medianSeconds: 16.1,
430
+ p95Seconds: 23.9,
431
+ source: "artificial-analysis-models"
432
+ },
433
+ tiers: { price: "budget", quality: "best", speed: "balanced" },
434
+ useCase: "Production FLUX quality at a low per-megapixel price"
435
+ },
436
+ editEndpoint: "fal-ai/flux-2-pro/edit",
437
+ endpoint: "fal-ai/flux-2-pro",
438
+ falPricing: {
439
+ checkedAt: FAL_PRICING_CHECKED_AT,
440
+ currency: "USD",
441
+ endpointId: "fal-ai/flux-2-pro",
442
+ estimatedCostPerImageUsd: 0.03,
443
+ source: "fal-pricing-api",
444
+ unit: "processed megapixels",
445
+ unitPrice: 0.03
446
+ },
447
+ maxReferenceImages: 10,
448
+ name: "FLUX.2 Pro",
449
+ pricePerImageUsd: 0.03,
450
+ pricing: "$0.03/MP",
451
+ sizeMode: "image_size_enum",
452
+ supportsAspect: true,
453
+ supportsEdit: true,
454
+ supportsNumImages: false,
455
+ supportsOutputFormat: true,
456
+ supportsResolution: false,
457
+ supportsSafetyChecker: true,
458
+ supportsSafetyTolerance: true,
459
+ supportsSeed: true,
460
+ supportsSyncMode: true,
461
+ type: "generation"
462
+ },
463
+ "flux2-flex": {
464
+ benchmark: {
465
+ artificialAnalysis: {
466
+ editing: { elo: 1161, pricePer1k: 60, rank: 22 },
467
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
468
+ sourceUrls: AA_IMAGE_SOURCES,
469
+ textToImage: { elo: 1182, pricePer1k: 60, rank: 13, winRate: 0.61 }
470
+ },
471
+ speed: {
472
+ medianSeconds: 15.3,
473
+ p95Seconds: 22.2,
474
+ source: "artificial-analysis-models"
475
+ },
476
+ tiers: { price: "standard", quality: "best", speed: "balanced" },
477
+ useCase: "FLUX quality with guidance and step controls"
478
+ },
479
+ editEndpoint: "fal-ai/flux-2-flex/edit",
480
+ endpoint: "fal-ai/flux-2-flex",
481
+ falPricing: {
482
+ checkedAt: FAL_PRICING_CHECKED_AT,
483
+ currency: "USD",
484
+ endpointId: "fal-ai/flux-2-flex",
485
+ estimatedCostPerImageUsd: 0.05,
486
+ source: "fal-pricing-api",
487
+ unit: "processed megapixels",
488
+ unitPrice: 0.05
489
+ },
490
+ maxReferenceImages: 10,
491
+ name: "FLUX.2 Flex",
492
+ pricePerImageUsd: 0.05,
493
+ pricing: "$0.05/MP",
494
+ sizeMode: "image_size_enum",
495
+ supportedOutputFormats: ["jpeg", "png"],
496
+ supportsAspect: true,
497
+ supportsEdit: true,
498
+ supportsGuidanceScale: true,
499
+ supportsInferenceSteps: true,
500
+ supportsNumImages: false,
501
+ supportsOutputFormat: true,
502
+ supportsResolution: false,
503
+ supportsSafetyChecker: true,
504
+ supportsSafetyTolerance: true,
505
+ supportsSeed: true,
506
+ supportsSyncMode: true,
507
+ type: "generation"
508
+ },
509
+ "flux2-dev": {
510
+ benchmark: {
511
+ artificialAnalysis: {
512
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
513
+ sourceUrls: AA_IMAGE_SOURCES,
514
+ textToImage: { elo: 1160, pricePer1k: 12, rank: 19, winRate: 0.57 }
515
+ },
516
+ speed: {
517
+ medianSeconds: 4.6,
518
+ p95Seconds: 69.6,
519
+ source: "artificial-analysis-models"
520
+ },
521
+ tiers: { price: "variable", quality: "better", speed: "fast" },
522
+ useCase: "Open FLUX.2 quality with low average cost"
523
+ },
524
+ editEndpoint: "fal-ai/flux-2/edit",
525
+ endpoint: "fal-ai/flux-2",
526
+ falPricing: {
527
+ checkedAt: FAL_PRICING_CHECKED_AT,
528
+ currency: "USD",
529
+ endpointId: "fal-ai/flux-2",
530
+ estimatedCostPerImageUsd: 0.012,
531
+ source: "fal-pricing-api",
532
+ unit: "compute seconds",
533
+ unitPrice: 167e-5
534
+ },
535
+ maxReferenceImages: 10,
536
+ name: "FLUX.2 [dev]",
537
+ pricePerImageUsd: 0.012,
538
+ pricing: "$0.00167/sec",
539
+ sizeMode: "image_size_enum",
540
+ supportsAspect: true,
541
+ supportsEdit: true,
542
+ supportsGuidanceScale: true,
543
+ supportsInferenceSteps: true,
544
+ supportsNumImages: true,
545
+ supportsOutputFormat: true,
546
+ supportsResolution: false,
547
+ supportsSafetyChecker: true,
548
+ supportsSafetyTolerance: true,
549
+ supportsSeed: true,
550
+ supportsSyncMode: true,
551
+ type: "generation"
552
+ },
553
+ "flux2-turbo": {
554
+ endpoint: "fal-ai/flux-2/turbo",
555
+ falPricing: {
556
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
557
+ currency: "USD",
558
+ endpointId: "fal-ai/flux-2/turbo",
559
+ estimatedCostPerImageUsd: 8e-3,
560
+ source: "fal-pricing-api",
561
+ unit: "megapixels",
562
+ unitPrice: 8e-3
563
+ },
564
+ name: "FLUX.2 Turbo",
565
+ pricePerImageUsd: 8e-3,
566
+ pricing: "$0.008/MP",
567
+ sizeMode: "image_size_enum",
568
+ supportsAspect: true,
569
+ supportsEdit: false,
570
+ supportsGuidanceScale: true,
571
+ supportsNumImages: true,
572
+ supportsOutputFormat: true,
573
+ supportsResolution: false,
574
+ supportsSafetyChecker: true,
575
+ supportsSeed: true,
576
+ supportsSyncMode: true,
577
+ type: "generation"
578
+ },
579
+ flux: {
580
+ endpoint: "fal-ai/flux-pro/v1.1-ultra",
581
+ falPricing: {
582
+ checkedAt: FAL_PRICING_CHECKED_AT,
583
+ currency: "USD",
584
+ endpointId: "fal-ai/flux-pro/v1.1-ultra",
585
+ estimatedCostPerImageUsd: 0.06,
586
+ source: "fal-pricing-api",
587
+ unit: "images",
588
+ unitPrice: 0.06
589
+ },
590
+ maxReferenceImages: 1,
591
+ name: "FLUX Pro Ultra",
592
+ pricePerImageUsd: 0.06,
593
+ pricing: "$0.06",
594
+ sizeMode: "aspect_ratio",
595
+ supportsAspect: true,
596
+ supportsEdit: true,
597
+ supportsEnhancePrompt: true,
598
+ supportsImagePromptStrength: true,
599
+ supportsNumImages: true,
600
+ supportsOutputFormat: true,
601
+ supportsRaw: true,
602
+ supportsResolution: false,
603
+ supportsSafetyTolerance: true,
604
+ supportsSeed: true,
605
+ supportsSyncMode: true,
606
+ type: "generation"
607
+ },
608
+ "flux-fast": {
609
+ endpoint: "fal-ai/flux/schnell",
610
+ falPricing: {
611
+ checkedAt: FAL_PRICING_CHECKED_AT,
612
+ currency: "USD",
613
+ endpointId: "fal-ai/flux/schnell",
614
+ estimatedCostPerImageUsd: 3e-3,
615
+ source: "fal-pricing-api",
616
+ unit: "megapixels",
617
+ unitPrice: 3e-3
618
+ },
619
+ name: "FLUX Schnell",
620
+ pricePerImageUsd: 3e-3,
621
+ pricing: "$0.003",
622
+ sizeMode: "image_size_enum",
623
+ supportsAspect: true,
624
+ supportsEdit: false,
625
+ supportsGuidanceScale: true,
626
+ supportsInferenceSteps: true,
627
+ supportsNumImages: true,
628
+ supportsOutputFormat: true,
629
+ supportsResolution: false,
630
+ supportsSeed: true,
631
+ supportsSyncMode: true,
632
+ type: "generation"
633
+ },
634
+ recraft: {
635
+ endpoint: "fal-ai/recraft-v3",
636
+ falPricing: {
637
+ checkedAt: FAL_PRICING_CHECKED_AT,
638
+ currency: "USD",
639
+ endpointId: "fal-ai/recraft-v3",
640
+ estimatedCostPerImageUsd: 0.04,
641
+ source: "fal-pricing-api",
642
+ unit: "images",
643
+ unitPrice: 0.04
644
+ },
645
+ name: "Recraft V3",
646
+ pricePerImageUsd: 0.04,
647
+ pricing: "$0.04",
648
+ sizeMode: "image_size_enum",
649
+ supportsAspect: true,
650
+ supportsEdit: false,
651
+ supportsNumImages: false,
652
+ supportsResolution: false,
653
+ supportsStyle: true,
654
+ supportsSyncMode: true,
655
+ type: "generation"
656
+ },
657
+ recraft4: {
658
+ endpoint: "fal-ai/recraft/v4/text-to-image",
659
+ falPricing: {
660
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
661
+ currency: "USD",
662
+ endpointId: "fal-ai/recraft/v4/text-to-image",
663
+ estimatedCostPerImageUsd: 0.04,
664
+ source: "fal-pricing-api",
665
+ unit: "images",
666
+ unitPrice: 0.04
667
+ },
668
+ name: "Recraft V4",
669
+ pricePerImageUsd: 0.04,
670
+ pricing: "$0.04",
671
+ sizeMode: "image_size_enum",
672
+ supportsAspect: true,
673
+ supportsEdit: false,
674
+ supportsNumImages: false,
675
+ supportsResolution: false,
676
+ supportsStyle: true,
677
+ supportsSyncMode: true,
678
+ type: "generation"
679
+ },
680
+ ideogram: {
681
+ endpoint: "fal-ai/ideogram/v3",
682
+ falPricing: {
683
+ checkedAt: FAL_PRICING_CHECKED_AT,
684
+ currency: "USD",
685
+ endpointId: "fal-ai/ideogram/v3",
686
+ estimatedCostPerImageUsd: 0.03,
687
+ source: "fal-pricing-api",
688
+ unit: "images",
689
+ unitPrice: 0.03
690
+ },
691
+ name: "Ideogram V3",
692
+ pricePerImageUsd: 0.03,
693
+ pricing: "$0.03",
694
+ sizeMode: "image_size_enum",
695
+ supportsAspect: true,
696
+ supportsEdit: false,
697
+ supportsExpandPrompt: true,
698
+ supportsNegativePrompt: true,
699
+ supportsNumImages: true,
700
+ supportsRenderingSpeed: true,
701
+ supportsResolution: false,
702
+ supportsSeed: true,
703
+ supportsStyle: true,
704
+ supportsSyncMode: true,
705
+ type: "generation"
706
+ },
707
+ ideogram4: {
708
+ name: "Ideogram V4",
709
+ endpoint: "ideogram/v4",
710
+ type: "generation",
711
+ // fal bills v4 per output megapixel by rendering speed: TURBO $0.03, BALANCED
712
+ // $0.06, QUALITY $0.10 (plus a flat $0.03 when prompt expansion is used).
713
+ // Headline uses the TURBO 1MP rate, matching how ideogram v3 is modeled.
714
+ pricing: "$0.03",
715
+ pricePerImageUsd: 0.03,
716
+ falPricing: {
717
+ checkedAt: FAL_PRICING_CHECKED_JUL_2026,
718
+ currency: "USD",
719
+ endpointId: "ideogram/v4",
720
+ estimatedCostPerImageUsd: 0.03,
721
+ source: "fal-pricing-api",
722
+ unit: "megapixels",
723
+ unitPrice: 0.03
724
+ },
725
+ sizeMode: "image_size_enum",
726
+ supportsAspect: true,
727
+ supportsResolution: false,
728
+ supportsEdit: false,
729
+ supportsNumImages: true,
730
+ supportsRenderingSpeed: true,
731
+ supportsSeed: true,
732
+ supportsOutputFormat: true,
733
+ supportedOutputFormats: ["jpeg", "png"],
734
+ supportsSafetyChecker: true,
735
+ supportsSyncMode: true
736
+ },
737
+ "grok-image": {
738
+ benchmark: {
739
+ artificialAnalysis: {
740
+ editing: { elo: 1213, pricePer1k: 20, rank: 7, winRate: 0.6 },
741
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
742
+ sourceUrls: AA_IMAGE_SOURCES,
743
+ textToImage: { elo: 1182, pricePer1k: 20, rank: 12, winRate: 0.6 }
744
+ },
745
+ speed: {
746
+ medianSeconds: 5.1,
747
+ p95Seconds: 6.5,
748
+ source: "artificial-analysis-models"
749
+ },
750
+ tiers: { price: "budget", quality: "best", speed: "fast" },
751
+ useCase: "Very fast, cheap generation and edits"
752
+ },
753
+ editEndpoint: "xai/grok-imagine-image/edit",
754
+ endpoint: "xai/grok-imagine-image",
755
+ falPricing: {
756
+ checkedAt: FAL_PRICING_CHECKED_AT,
757
+ currency: "USD",
758
+ endpointId: "xai/grok-imagine-image",
759
+ estimatedCostPerImageUsd: 0.02,
760
+ source: "fal-pricing-api",
761
+ unit: "images",
762
+ unitPrice: 0.02
763
+ },
764
+ maxReferenceImages: 4,
765
+ name: "Grok Imagine Image",
766
+ pricePerImageUsd: 0.02,
767
+ pricing: "$0.02",
768
+ sizeMode: "aspect_ratio",
769
+ supportsAspect: true,
770
+ supportsEdit: true,
771
+ supportsNumImages: true,
772
+ supportsOutputFormat: true,
773
+ supportsResolution: true,
774
+ supportsSyncMode: true,
775
+ type: "generation"
776
+ },
777
+ qwen: {
778
+ benchmark: {
779
+ artificialAnalysis: {
780
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
781
+ sourceUrls: AA_IMAGE_SOURCES,
782
+ textToImage: { elo: 1158, pricePer1k: 20, rank: 20, winRate: 0.6 }
783
+ },
784
+ speed: {
785
+ medianSeconds: 23.3,
786
+ p95Seconds: 491,
787
+ source: "artificial-analysis-models"
788
+ },
789
+ tiers: { price: "budget", quality: "better", speed: "slow" },
790
+ useCase: "Low-cost open-weight text and design generation"
791
+ },
792
+ endpoint: "fal-ai/qwen-image",
793
+ falPricing: {
794
+ checkedAt: FAL_PRICING_CHECKED_AT,
795
+ currency: "USD",
796
+ endpointId: "fal-ai/qwen-image",
797
+ estimatedCostPerImageUsd: 0.02,
798
+ source: "fal-pricing-api",
799
+ unit: "megapixels",
800
+ unitPrice: 0.02
801
+ },
802
+ name: "Qwen Image",
803
+ pricePerImageUsd: 0.02,
804
+ pricing: "$0.02/MP",
805
+ sizeMode: "image_size_enum",
806
+ supportsAspect: true,
807
+ supportsEdit: false,
808
+ supportsNumImages: true,
809
+ supportsOutputFormat: true,
810
+ supportsResolution: false,
811
+ supportsSeed: true,
812
+ supportsSyncMode: true,
813
+ type: "generation"
814
+ },
815
+ // ─── Video Models ─────────────────────────────────────────────
816
+ kling: {
817
+ endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
818
+ name: "Kling v3 Pro",
819
+ pricing: "$0.11/sec",
820
+ sizeMode: "none",
821
+ supportsAspect: false,
822
+ supportsEdit: false,
823
+ supportsNumImages: false,
824
+ supportsResolution: false,
825
+ type: "video"
826
+ },
827
+ // ─── Utility Models ───────────────────────────────────────────
828
+ clarity: {
829
+ endpoint: "fal-ai/clarity-upscaler",
830
+ name: "Clarity Upscaler",
831
+ pricing: "$0.03/MP",
832
+ supportsAspect: false,
833
+ supportsEdit: false,
834
+ supportsNumImages: false,
835
+ supportsResolution: false,
836
+ type: "utility"
837
+ },
838
+ crystal: {
839
+ endpoint: "clarityai/crystal-upscaler",
840
+ name: "Crystal Upscaler",
841
+ pricing: "$0.02",
842
+ supportsAspect: false,
843
+ supportsEdit: false,
844
+ supportsNumImages: false,
845
+ supportsResolution: false,
846
+ type: "utility"
847
+ },
848
+ rmbg: {
849
+ endpoint: "fal-ai/birefnet",
850
+ name: "BiRefNet (Background Removal)",
851
+ pricing: "$0.02",
852
+ supportsAspect: false,
853
+ supportsEdit: false,
854
+ supportsNumImages: false,
855
+ supportsResolution: false,
856
+ type: "utility"
857
+ },
858
+ bria: {
859
+ endpoint: "fal-ai/bria/background/remove",
860
+ name: "Bria RMBG 2.0",
861
+ pricing: "$0.02",
862
+ supportsAspect: false,
863
+ supportsEdit: false,
864
+ supportsNumImages: false,
865
+ supportsResolution: false,
866
+ type: "utility"
867
+ }
868
+ };
869
+ var GENERATION_MODELS = [
870
+ "gpt2",
871
+ "gpt",
872
+ "banana2",
873
+ "banana",
874
+ "gemini",
875
+ "gemini3",
876
+ "seedream4",
877
+ "seedream45",
878
+ "seedream5",
879
+ "seedream5-lite",
880
+ "flux2-max",
881
+ "flux2-pro",
882
+ "flux2-flex",
883
+ "flux2-dev",
884
+ "flux2-turbo",
885
+ "flux",
886
+ "flux-fast",
887
+ "recraft",
888
+ "recraft4",
889
+ "ideogram",
890
+ "ideogram4",
891
+ "grok-image",
892
+ "qwen"
893
+ ];
894
+ var EDIT_CAPABLE_MODELS = GENERATION_MODELS.filter(
895
+ (id) => MODELS[id]?.supportsEdit === true
896
+ );
897
+
898
+ // src/tools.ts
899
+ var FAL_TOOLS = {
900
+ birefnet: {
901
+ category: "background",
902
+ defaultOptions: {
903
+ model: "General Use (Light)",
904
+ operating_resolution: "1024x1024",
905
+ output_format: "png",
906
+ refine_foreground: true
907
+ },
908
+ description: "High-resolution dichotomous image segmentation and background removal.",
909
+ endpoint: "fal-ai/birefnet/v2",
910
+ inputField: "image_url",
911
+ inputKind: "image",
912
+ name: "BirefNet Background Removal",
913
+ outputKeys: ["image", "mask_image"],
914
+ pricing: "$0/compute-second listed by fal",
915
+ sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
916
+ task: "image background removal"
917
+ },
918
+ "bria-rmbg": {
919
+ category: "background",
920
+ description: "Commercial-safe background removal for images.",
921
+ endpoint: "fal-ai/bria/background/remove",
922
+ inputField: "image_url",
923
+ inputKind: "image",
924
+ name: "Bria RMBG 2.0",
925
+ outputKeys: ["image"],
926
+ pricing: "$0.02/image",
927
+ sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove",
928
+ task: "image background removal"
929
+ },
930
+ "bria-video-rmbg": {
931
+ category: "background",
932
+ defaultOptions: {
933
+ background_color: "Black",
934
+ output_container_and_codec: "webm_vp9",
935
+ preserve_audio: true
936
+ },
937
+ description: "Remove video backgrounds with configurable output container.",
938
+ endpoint: "bria/video/background-removal",
939
+ inputField: "video_url",
940
+ inputKind: "video",
941
+ name: "Bria Video Background Removal",
942
+ outputKeys: ["video"],
943
+ pricing: "$0.14/sec",
944
+ sourceUrl: "https://fal.ai/models/bria/video/background-removal",
945
+ task: "video background removal"
946
+ },
947
+ "depth-anything": {
948
+ category: "preprocess",
949
+ description: "Generate Depth Anything v2 depth maps from input images.",
950
+ endpoint: "fal-ai/image-preprocessors/depth-anything/v2",
951
+ inputField: "image_url",
952
+ inputKind: "image",
953
+ name: "Depth Anything v2 Preprocessor",
954
+ outputKeys: ["image"],
955
+ pricing: "$0/compute-second listed by fal",
956
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
957
+ task: "depth preprocessing"
958
+ },
959
+ lineart: {
960
+ category: "preprocess",
961
+ defaultOptions: {
962
+ coarse: false
963
+ },
964
+ description: "Generate line art/control-style edges from an input image.",
965
+ endpoint: "fal-ai/image-preprocessors/lineart",
966
+ inputField: "image_url",
967
+ inputKind: "image",
968
+ name: "Line Art Preprocessor",
969
+ outputKeys: ["image"],
970
+ pricing: "$0/compute-second listed by fal",
971
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
972
+ task: "image preprocessing"
973
+ },
974
+ "marigold-depth": {
975
+ category: "depth",
976
+ defaultOptions: {
977
+ ensemble_size: 10,
978
+ num_inference_steps: 10
979
+ },
980
+ description: "Create depth maps using Marigold depth estimation.",
981
+ endpoint: "fal-ai/imageutils/marigold-depth",
982
+ inputField: "image_url",
983
+ inputKind: "image",
984
+ name: "Marigold Depth Estimation",
985
+ outputKeys: ["image"],
986
+ pricing: "$0/compute-second listed by fal",
987
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
988
+ task: "depth map"
989
+ },
990
+ "midas-depth": {
991
+ category: "depth",
992
+ defaultOptions: {
993
+ a: Math.PI * 2,
994
+ bg_th: 0.1
995
+ },
996
+ description: "Create MiDaS depth maps from input images.",
997
+ endpoint: "fal-ai/imageutils/depth",
998
+ inputField: "image_url",
999
+ inputKind: "image",
1000
+ name: "MiDaS Depth Estimation",
1001
+ outputKeys: ["image"],
1002
+ pricing: "$0/compute-second listed by fal",
1003
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
1004
+ task: "depth map"
1005
+ },
1006
+ "midas-preprocessor": {
1007
+ category: "preprocess",
1008
+ description: "Generate MiDaS depth and normal maps for image workflows.",
1009
+ endpoint: "fal-ai/image-preprocessors/midas",
1010
+ inputField: "image_url",
1011
+ inputKind: "image",
1012
+ name: "MiDaS Preprocessor",
1013
+ outputKeys: ["depth_map", "normal_map"],
1014
+ pricing: "$0/compute-second listed by fal",
1015
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
1016
+ task: "depth and normal preprocessing"
1017
+ },
1018
+ nsfw: {
1019
+ category: "moderation",
1020
+ description: "Predict whether one or more images contain NSFW concepts.",
1021
+ endpoint: "fal-ai/x-ailab/nsfw",
1022
+ inputField: "image_urls",
1023
+ inputKind: "images",
1024
+ name: "NSFW Checker",
1025
+ outputKeys: ["has_nsfw_concepts"],
1026
+ pricing: "$0.001/image",
1027
+ sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
1028
+ task: "vision moderation"
1029
+ },
1030
+ rembg: {
1031
+ category: "background",
1032
+ defaultOptions: {
1033
+ crop_to_bbox: false
1034
+ },
1035
+ description: "Generic image background removal utility.",
1036
+ endpoint: "fal-ai/imageutils/rembg",
1037
+ inputField: "image_url",
1038
+ inputKind: "image",
1039
+ name: "Remove Background",
1040
+ outputKeys: ["image"],
1041
+ pricing: "$0/compute-second listed by fal",
1042
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
1043
+ task: "image background removal"
1044
+ },
1045
+ "sam-preprocessor": {
1046
+ category: "preprocess",
1047
+ description: "Generate a SAM segmentation map for ControlNet-style workflows.",
1048
+ endpoint: "fal-ai/image-preprocessors/sam",
1049
+ inputField: "image_url",
1050
+ inputKind: "image",
1051
+ name: "SAM Preprocessor",
1052
+ outputKeys: ["image"],
1053
+ pricing: "$0/compute-second listed by fal",
1054
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
1055
+ task: "segmentation preprocessing"
1056
+ },
1057
+ "sam2-auto": {
1058
+ category: "segmentation",
1059
+ defaultOptions: {
1060
+ min_mask_region_area: 100,
1061
+ output_format: "png",
1062
+ points_per_side: 32,
1063
+ pred_iou_thresh: 0.88,
1064
+ stability_score_thresh: 0.95
1065
+ },
1066
+ description: "Automatically segment an image into combined and individual masks.",
1067
+ endpoint: "fal-ai/sam2/auto-segment",
1068
+ inputField: "image_url",
1069
+ inputKind: "image",
1070
+ name: "SAM 2 Auto Segment",
1071
+ outputKeys: ["combined_mask", "individual_masks"],
1072
+ pricing: "$0/compute-second listed by fal",
1073
+ sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
1074
+ task: "automatic image segmentation"
1075
+ },
1076
+ "sam3-1-image": {
1077
+ category: "segmentation",
1078
+ defaultOptions: {
1079
+ apply_mask: true,
1080
+ max_masks: 3,
1081
+ output_format: "png"
1082
+ },
1083
+ description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
1084
+ endpoint: "fal-ai/sam-3-1/image",
1085
+ inputField: "image_url",
1086
+ inputKind: "image",
1087
+ name: "SAM 3.1 Image",
1088
+ outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
1089
+ pricing: "$0.005/request",
1090
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
1091
+ task: "promptable image segmentation"
1092
+ },
1093
+ "sam3-1-video": {
1094
+ category: "segmentation",
1095
+ defaultOptions: {
1096
+ apply_mask: true,
1097
+ prompt: "person"
1098
+ },
1099
+ description: "SAM 3.1 video segmentation with Object Multiplex tracking for multiple objects.",
1100
+ endpoint: "fal-ai/sam-3-1/video",
1101
+ inputField: "video_url",
1102
+ inputKind: "video",
1103
+ name: "SAM 3.1 Video",
1104
+ outputKeys: ["video", "boundingbox_frames_zip"],
1105
+ pricing: "fal pricing varies by frame count",
1106
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
1107
+ task: "multi-object video segmentation"
1108
+ },
1109
+ "sam3-3d-align": {
1110
+ category: "3d",
1111
+ description: "Align SAM 3D objects and bodies into a shared scene.",
1112
+ endpoint: "fal-ai/sam-3/3d-align",
1113
+ inputField: "image_url",
1114
+ inputKind: "image",
1115
+ name: "SAM 3D Align",
1116
+ outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
1117
+ pricing: "fal pricing varies by scene",
1118
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
1119
+ task: "3D scene alignment"
1120
+ },
1121
+ "sam3-3d-body": {
1122
+ category: "3d",
1123
+ defaultOptions: {
1124
+ export_meshes: true,
1125
+ include_3d_keypoints: true,
1126
+ include_mhr_params: true
1127
+ },
1128
+ description: "Reconstruct human body meshes and keypoints from a single image.",
1129
+ endpoint: "fal-ai/sam-3/3d-body",
1130
+ inputField: "image_url",
1131
+ inputKind: "image",
1132
+ name: "SAM 3D Body",
1133
+ outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
1134
+ pricing: "$0.015/inference",
1135
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
1136
+ task: "single-image 3D body reconstruction"
1137
+ },
1138
+ "sam3-3d-objects": {
1139
+ category: "3d",
1140
+ defaultOptions: {
1141
+ prompt: "car"
1142
+ },
1143
+ description: "Reconstruct one or more 3D objects from an image and prompts.",
1144
+ endpoint: "fal-ai/sam-3/3d-objects",
1145
+ inputField: "image_url",
1146
+ inputKind: "image",
1147
+ name: "SAM 3D Objects",
1148
+ outputKeys: [
1149
+ "gaussian_splat",
1150
+ "model_glb",
1151
+ "metadata",
1152
+ "individual_splats",
1153
+ "individual_glbs",
1154
+ "artifacts_zip"
1155
+ ],
1156
+ pricing: "$0.02/generation",
1157
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
1158
+ task: "single-image 3D object reconstruction"
1159
+ },
1160
+ "sam3-image": {
1161
+ category: "segmentation",
1162
+ defaultOptions: {
1163
+ apply_mask: true,
1164
+ max_masks: 3,
1165
+ output_format: "png"
1166
+ },
1167
+ description: "Segment image objects with text, point, or box prompts.",
1168
+ endpoint: "fal-ai/sam-3/image",
1169
+ inputField: "image_url",
1170
+ inputKind: "image",
1171
+ name: "SAM 3 Image",
1172
+ outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
1173
+ pricing: "$0.005/request",
1174
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
1175
+ task: "promptable image segmentation"
1176
+ },
1177
+ "sam3-image-rle": {
1178
+ category: "segmentation",
1179
+ defaultOptions: {
1180
+ apply_mask: true,
1181
+ max_masks: 3
1182
+ },
1183
+ description: "Segment image objects and return run-length encoded masks.",
1184
+ endpoint: "fal-ai/sam-3/image-rle",
1185
+ inputField: "image_url",
1186
+ inputKind: "image",
1187
+ name: "SAM 3 Image RLE",
1188
+ outputKeys: ["rle_masks", "metadata", "scores", "boxes"],
1189
+ pricing: "$0.005/request",
1190
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
1191
+ task: "promptable image segmentation to RLE"
1192
+ },
1193
+ "sam3-video": {
1194
+ category: "segmentation",
1195
+ defaultOptions: {
1196
+ apply_mask: true,
1197
+ detection_threshold: 0.5,
1198
+ prompt: "person",
1199
+ video_output_type: "X264 (.mp4)"
1200
+ },
1201
+ description: "Segment and track prompted objects across video frames.",
1202
+ endpoint: "fal-ai/sam-3/video",
1203
+ inputField: "video_url",
1204
+ inputKind: "video",
1205
+ name: "SAM 3 Video",
1206
+ outputKeys: ["video", "boundingbox_frames_zip"],
1207
+ pricing: "$0.005/16 frames",
1208
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
1209
+ task: "promptable video segmentation"
1210
+ },
1211
+ "sam3-video-rle": {
1212
+ category: "segmentation",
1213
+ defaultOptions: {
1214
+ apply_mask: true,
1215
+ detection_threshold: 0.5,
1216
+ prompt: "person"
1217
+ },
1218
+ description: "Track prompted video objects and return RLE mask data.",
1219
+ endpoint: "fal-ai/sam-3/video-rle",
1220
+ inputField: "video_url",
1221
+ inputKind: "video",
1222
+ name: "SAM 3 Video RLE",
1223
+ outputKeys: ["rle_masks", "metadata"],
1224
+ pricing: "$0.005/16 frames",
1225
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
1226
+ task: "promptable video segmentation to RLE"
1227
+ },
1228
+ "topaz-image": {
1229
+ category: "upscale",
1230
+ defaultOptions: {
1231
+ model: "Standard V2",
1232
+ output_format: "jpeg",
1233
+ upscale_factor: 2
1234
+ },
1235
+ description: "Professional Topaz image enhancement and upscaling.",
1236
+ endpoint: "fal-ai/topaz/upscale/image",
1237
+ inputField: "image_url",
1238
+ inputKind: "image",
1239
+ name: "Topaz Image Upscale",
1240
+ outputKeys: ["image"],
1241
+ pricing: "$0.08+ by output megapixels",
1242
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
1243
+ task: "image enhancement"
1244
+ },
1245
+ "topaz-video": {
1246
+ category: "upscale",
1247
+ defaultOptions: {
1248
+ model: "Proteus",
1249
+ upscale_factor: 2
1250
+ },
1251
+ description: "Professional Topaz video enhancement and upscaling.",
1252
+ endpoint: "fal-ai/topaz/upscale/video",
1253
+ inputField: "video_url",
1254
+ inputKind: "video",
1255
+ name: "Topaz Video Upscale",
1256
+ outputKeys: ["video"],
1257
+ pricing: "$0.01-$0.08/sec by output resolution",
1258
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
1259
+ task: "video enhancement"
1260
+ }
1261
+ };
1262
+ var FAL_TOOL_IDS = Object.keys(
1263
+ FAL_TOOLS
1264
+ );
1265
+
1266
+ // src/server.ts
1267
+ var MotifError = class extends Error {
1268
+ status;
1269
+ code;
1270
+ /** fal's request-correlation id (from the `x-fal-request-id` header or the
1271
+ * error body). Ties a failure back to fal's dashboard/support. */
1272
+ requestId;
1273
+ constructor(message, status, code, requestId) {
1274
+ super(message);
1275
+ this.name = "MotifError";
1276
+ this.status = status;
1277
+ this.code = code;
1278
+ this.requestId = requestId;
1279
+ }
1280
+ };
1281
+
1282
+ // src/image/cost.ts
1283
+ var GOOGLE_IMAGE_PRICE_USD = {
1284
+ "gemini-2.5-flash-image": 0.039,
1285
+ "gemini-3.1-flash-image-preview": 0.039,
1286
+ "gemini-3-pro-image-preview": 0.134
1287
+ };
1288
+ function isRecord2(value) {
1289
+ return typeof value === "object" && value !== null;
1290
+ }
1291
+ function costFromProviderMetadata(providerMetadata) {
1292
+ if (!isRecord2(providerMetadata)) {
1293
+ return void 0;
1294
+ }
1295
+ for (const value of Object.values(providerMetadata)) {
1296
+ if (isRecord2(value)) {
1297
+ const cost = value.cost;
1298
+ if (typeof cost === "number" && Number.isFinite(cost)) {
1299
+ return cost;
1300
+ }
1301
+ }
1302
+ }
1303
+ return void 0;
1304
+ }
1305
+ function tablePricePerImage(provider, modelId) {
1306
+ if (provider === "google") {
1307
+ return GOOGLE_IMAGE_PRICE_USD[modelId];
1308
+ }
1309
+ return void 0;
1310
+ }
1311
+ function roundUsd(value) {
1312
+ return Number(value.toFixed(6));
1313
+ }
1314
+ function costForImages(provider, modelId, providerMetadata, imageCount) {
1315
+ const metaCost = costFromProviderMetadata(providerMetadata);
1316
+ if (metaCost !== void 0) {
1317
+ return { usd: roundUsd(metaCost), source: "provider-metadata" };
1318
+ }
1319
+ const perImage = tablePricePerImage(provider, modelId);
1320
+ if (perImage !== void 0) {
1321
+ return {
1322
+ usd: roundUsd(perImage * Math.max(imageCount, 1)),
1323
+ source: "table"
1324
+ };
1325
+ }
1326
+ return { usd: 0, source: "unknown" };
1327
+ }
1328
+
1329
+ // src/image/google.ts
1330
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
1331
+ var GOOGLE_TIER_MODELS = {
1332
+ fast: "gemini-2.5-flash-image",
1333
+ balanced: "gemini-3.1-flash-image-preview",
1334
+ quality: "gemini-3-pro-image-preview",
1335
+ hero: "gemini-3-pro-image-preview"
1336
+ };
1337
+ var GOOGLE_API_KEY_ENV = "GOOGLE_GENERATIVE_AI_API_KEY";
1338
+ function googleModelForTier(tier) {
1339
+ return GOOGLE_TIER_MODELS[tier];
1340
+ }
1341
+ function resolveModel(modelId, apiKey) {
1342
+ const key = apiKey ?? process.env[GOOGLE_API_KEY_ENV];
1343
+ if (key === void 0 || key === "") {
1344
+ throw new MotifError(
1345
+ `Google image generation requires an API key (config.google.apiKey or ${GOOGLE_API_KEY_ENV})`,
1346
+ 0
1347
+ );
1348
+ }
1349
+ return createGoogleGenerativeAI({ apiKey: key }).image(modelId);
1350
+ }
1351
+
1352
+ // src/image/index.ts
1353
+ var DEFAULT_TIER = "balanced";
1354
+ var DEFAULT_PROVIDER = "google";
1355
+ function createMotifImage(config = {}, deps = {}) {
1356
+ const generateImageFn = deps.generateImage ?? generateImage;
1357
+ const resolveModelFn = deps.resolveModel ?? defaultResolveModel;
1358
+ function resolveProvider(provider) {
1359
+ return provider ?? config.defaultProvider ?? DEFAULT_PROVIDER;
1360
+ }
1361
+ function apiKeyFor(provider) {
1362
+ if (provider === "google") {
1363
+ return config.google?.apiKey;
1364
+ }
1365
+ return void 0;
1366
+ }
1367
+ async function generate(opts) {
1368
+ const provider = resolveProvider(opts.provider);
1369
+ try {
1370
+ const modelId = resolveModelId(provider, opts.model, opts.tier);
1371
+ const model = resolveModelFn(provider, modelId, apiKeyFor(provider));
1372
+ const result = await generateImageFn({
1373
+ model,
1374
+ prompt: opts.prompt,
1375
+ ...opts.n === void 0 ? {} : { n: opts.n },
1376
+ ...opts.size === void 0 ? {} : { size: opts.size },
1377
+ ...opts.aspectRatio === void 0 ? {} : { aspectRatio: opts.aspectRatio },
1378
+ ...opts.providerOptions === void 0 ? {} : { providerOptions: toProviderOptions(opts.providerOptions) }
1379
+ });
1380
+ return ok2(toMotifImageResult(result, provider, modelId));
1381
+ } catch (error) {
1382
+ return err2(toMotifError(error));
1383
+ }
1384
+ }
1385
+ async function edit(opts) {
1386
+ const provider = resolveProvider(opts.provider);
1387
+ try {
1388
+ const modelId = resolveModelId(provider, opts.model, opts.tier);
1389
+ const model = resolveModelFn(provider, modelId, apiKeyFor(provider));
1390
+ const result = await generateImageFn({
1391
+ model,
1392
+ prompt: {
1393
+ images: opts.images,
1394
+ text: opts.instruction,
1395
+ ...opts.mask === void 0 ? {} : { mask: opts.mask }
1396
+ },
1397
+ ...opts.n === void 0 ? {} : { n: opts.n },
1398
+ ...opts.providerOptions === void 0 ? {} : { providerOptions: toProviderOptions(opts.providerOptions) }
1399
+ });
1400
+ return ok2(toMotifImageResult(result, provider, modelId));
1401
+ } catch (error) {
1402
+ return err2(toMotifError(error));
1403
+ }
1404
+ }
1405
+ return { generate, edit };
1406
+ }
1407
+ function defaultResolveModel(provider, modelId, apiKey) {
1408
+ if (provider === "google") {
1409
+ return resolveModel(modelId, apiKey);
1410
+ }
1411
+ throw new MotifError(`Unsupported image provider: ${provider}`, 0);
1412
+ }
1413
+ function resolveModelId(provider, model, tier) {
1414
+ if (model !== void 0 && model !== "") {
1415
+ return model;
1416
+ }
1417
+ const resolvedTier = tier ?? DEFAULT_TIER;
1418
+ if (provider === "google") {
1419
+ return googleModelForTier(resolvedTier);
1420
+ }
1421
+ throw new MotifError(`Unsupported image provider: ${provider}`, 0);
1422
+ }
1423
+ function toMotifImageResult(result, provider, model) {
1424
+ const images = result.images.map((file) => ({
1425
+ uint8Array: file.uint8Array,
1426
+ base64: file.base64,
1427
+ mediaType: file.mediaType
1428
+ }));
1429
+ const cost = costForImages(
1430
+ provider,
1431
+ model,
1432
+ result.providerMetadata,
1433
+ images.length
1434
+ );
1435
+ const requestId = extractRequestId(result);
1436
+ return {
1437
+ images,
1438
+ cost,
1439
+ provider,
1440
+ model,
1441
+ ...requestId === void 0 ? {} : { requestId }
1442
+ };
1443
+ }
1444
+ function isRecord3(value) {
1445
+ return typeof value === "object" && value !== null;
1446
+ }
1447
+ function extractRequestId(result) {
1448
+ const fromMetadata = requestIdFromMetadata(result.providerMetadata);
1449
+ if (fromMetadata !== void 0) {
1450
+ return fromMetadata;
1451
+ }
1452
+ for (const response of result.responses) {
1453
+ const { headers } = response;
1454
+ if (headers) {
1455
+ const id = headers["x-request-id"] ?? headers["x-goog-request-id"] ?? headers["x-fal-request-id"];
1456
+ if (typeof id === "string" && id !== "") {
1457
+ return id;
1458
+ }
1459
+ }
1460
+ }
1461
+ return void 0;
1462
+ }
1463
+ function requestIdFromMetadata(providerMetadata) {
1464
+ if (!isRecord3(providerMetadata)) {
1465
+ return void 0;
1466
+ }
1467
+ for (const value of Object.values(providerMetadata)) {
1468
+ if (isRecord3(value)) {
1469
+ const id = value.requestId ?? value.request_id;
1470
+ if (typeof id === "string" && id !== "") {
1471
+ return id;
1472
+ }
1473
+ }
1474
+ }
1475
+ return void 0;
1476
+ }
1477
+ function toProviderOptions(input) {
1478
+ const out = {};
1479
+ for (const [namespace, options] of Object.entries(input)) {
1480
+ const inner = {};
1481
+ for (const [key, value] of Object.entries(options)) {
1482
+ inner[key] = toJsonValue(value);
1483
+ }
1484
+ out[namespace] = inner;
1485
+ }
1486
+ return out;
1487
+ }
1488
+ function toJsonValue(value) {
1489
+ if (value === null) {
1490
+ return null;
1491
+ }
1492
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1493
+ return value;
1494
+ }
1495
+ if (typeof value === "object") {
1496
+ if (Array.isArray(value)) {
1497
+ const arr = [];
1498
+ for (const item of value) {
1499
+ arr.push(toJsonValue(item));
1500
+ }
1501
+ return arr;
1502
+ }
1503
+ const obj = {};
1504
+ for (const [key, entry] of Object.entries(value)) {
1505
+ obj[key] = toJsonValue(entry);
1506
+ }
1507
+ return obj;
1508
+ }
1509
+ return null;
1510
+ }
1511
+ function toMotifError(error) {
1512
+ if (error instanceof MotifError) {
1513
+ return error;
1514
+ }
1515
+ const message = error instanceof Error ? error.message : String(error);
1516
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
1517
+ return new MotifError(message, 0, code);
1518
+ }
1519
+ export {
1520
+ GOOGLE_API_KEY_ENV,
1521
+ GOOGLE_TIER_MODELS,
1522
+ costForImages,
1523
+ costFromProviderMetadata,
1524
+ createMotifImage
1525
+ };