@howells/motif-sdk 1.1.0 → 1.2.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.d.ts +8 -0
- package/dist/image.js +1065 -200
- package/dist/index.cjs +1429 -446
- package/dist/index.d.cts +2285 -407
- package/dist/index.d.ts +2285 -407
- package/dist/index.js +1429 -446
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -47,18 +47,18 @@ __export(src_exports, {
|
|
|
47
47
|
buildFalToolRequest: () => buildFalToolRequest,
|
|
48
48
|
buildGenerateBody: () => buildGenerateBody,
|
|
49
49
|
enrichPrompt: () => enrichPrompt,
|
|
50
|
-
err: () =>
|
|
50
|
+
err: () => import_neverthrow3.err,
|
|
51
51
|
estimateCost: () => estimateCost,
|
|
52
52
|
estimateVideoCost: () => estimateVideoCost,
|
|
53
53
|
getFalKeyFromEnv: () => getFalKeyFromEnv,
|
|
54
54
|
isFalToolId: () => isFalToolId,
|
|
55
55
|
motifEnvSchema: () => motifEnvSchema,
|
|
56
|
-
ok: () =>
|
|
56
|
+
ok: () => import_neverthrow3.ok,
|
|
57
57
|
parseMotifEnv: () => parseMotifEnv,
|
|
58
58
|
sanitizePrompt: () => sanitizePrompt
|
|
59
59
|
});
|
|
60
60
|
module.exports = __toCommonJS(src_exports);
|
|
61
|
-
var
|
|
61
|
+
var import_neverthrow3 = require("neverthrow");
|
|
62
62
|
|
|
63
63
|
// src/aspects.ts
|
|
64
64
|
var ASPECT_RATIOS = [
|
|
@@ -158,22 +158,23 @@ var FORMAT_PRESETS = {
|
|
|
158
158
|
};
|
|
159
159
|
|
|
160
160
|
// src/models.ts
|
|
161
|
-
var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-
|
|
161
|
+
var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-08-23";
|
|
162
162
|
var AA_IMAGE_SOURCES = [
|
|
163
163
|
"https://artificialanalysis.ai/image/leaderboard/text-to-image",
|
|
164
164
|
"https://artificialanalysis.ai/image/leaderboard/editing"
|
|
165
165
|
];
|
|
166
166
|
var FAL_PRICING_CHECKED_AT = "2026-05-12";
|
|
167
167
|
var FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
|
|
168
|
+
var FAL_PRICING_CHECKED_AUG_2026 = "2026-08-23";
|
|
168
169
|
var MODELS = {
|
|
169
170
|
// ─── Generation Models ────────────────────────────────────────
|
|
170
171
|
gpt2: {
|
|
171
172
|
benchmark: {
|
|
172
173
|
artificialAnalysis: {
|
|
173
|
-
editing: { elo:
|
|
174
|
+
editing: { elo: 1256, pricePer1k: 211, rank: 3 },
|
|
174
175
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
175
176
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
176
|
-
textToImage: { elo:
|
|
177
|
+
textToImage: { elo: 1369, pricePer1k: 211, rank: 1 }
|
|
177
178
|
},
|
|
178
179
|
speed: {
|
|
179
180
|
medianSeconds: 200.7,
|
|
@@ -213,10 +214,10 @@ var MODELS = {
|
|
|
213
214
|
gpt: {
|
|
214
215
|
benchmark: {
|
|
215
216
|
artificialAnalysis: {
|
|
216
|
-
editing: { elo:
|
|
217
|
+
editing: { elo: 1250, pricePer1k: 133, rank: 5 },
|
|
217
218
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
218
219
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
219
|
-
textToImage: { elo:
|
|
220
|
+
textToImage: { elo: 1310, pricePer1k: 133, rank: 4 }
|
|
220
221
|
},
|
|
221
222
|
speed: {
|
|
222
223
|
medianSeconds: 34.6,
|
|
@@ -256,10 +257,10 @@ var MODELS = {
|
|
|
256
257
|
banana2: {
|
|
257
258
|
benchmark: {
|
|
258
259
|
artificialAnalysis: {
|
|
259
|
-
editing: { elo:
|
|
260
|
+
editing: { elo: 1249, pricePer1k: 67, rank: 7 },
|
|
260
261
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
261
262
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
262
|
-
textToImage: { elo:
|
|
263
|
+
textToImage: { elo: 1320, pricePer1k: 67, rank: 3 }
|
|
263
264
|
},
|
|
264
265
|
tiers: { price: "premium", quality: "frontier", speed: "unknown" },
|
|
265
266
|
useCase: "Best balance of quality, edit support, web search, and cost"
|
|
@@ -297,10 +298,10 @@ var MODELS = {
|
|
|
297
298
|
banana: {
|
|
298
299
|
benchmark: {
|
|
299
300
|
artificialAnalysis: {
|
|
300
|
-
editing: { elo:
|
|
301
|
+
editing: { elo: 1245, pricePer1k: 134, rank: 9 },
|
|
301
302
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
302
303
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
303
|
-
textToImage: { elo:
|
|
304
|
+
textToImage: { elo: 1297, pricePer1k: 134, rank: 6 }
|
|
304
305
|
},
|
|
305
306
|
speed: {
|
|
306
307
|
medianSeconds: 19.2,
|
|
@@ -340,6 +341,13 @@ var MODELS = {
|
|
|
340
341
|
type: "generation"
|
|
341
342
|
},
|
|
342
343
|
gemini: {
|
|
344
|
+
benchmark: {
|
|
345
|
+
artificialAnalysis: {
|
|
346
|
+
editing: { elo: 1180, pricePer1k: 39, rank: 29 },
|
|
347
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
348
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
349
|
+
}
|
|
350
|
+
},
|
|
343
351
|
editEndpoint: "fal-ai/gemini-25-flash-image/edit",
|
|
344
352
|
endpoint: "fal-ai/gemini-25-flash-image",
|
|
345
353
|
falPricing: {
|
|
@@ -367,6 +375,14 @@ var MODELS = {
|
|
|
367
375
|
type: "generation"
|
|
368
376
|
},
|
|
369
377
|
gemini3: {
|
|
378
|
+
benchmark: {
|
|
379
|
+
artificialAnalysis: {
|
|
380
|
+
editing: { elo: 1245, pricePer1k: 134, rank: 9 },
|
|
381
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
382
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
383
|
+
textToImage: { elo: 1297, pricePer1k: 134, rank: 6 }
|
|
384
|
+
}
|
|
385
|
+
},
|
|
370
386
|
editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
|
|
371
387
|
endpoint: "fal-ai/gemini-3-pro-image-preview",
|
|
372
388
|
falPricing: {
|
|
@@ -397,10 +413,10 @@ var MODELS = {
|
|
|
397
413
|
seedream4: {
|
|
398
414
|
benchmark: {
|
|
399
415
|
artificialAnalysis: {
|
|
400
|
-
editing: { elo:
|
|
416
|
+
editing: { elo: 1185, pricePer1k: 30, rank: 28 },
|
|
401
417
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
402
418
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
403
|
-
textToImage: { elo:
|
|
419
|
+
textToImage: { elo: 1226, pricePer1k: 30, rank: 16 }
|
|
404
420
|
},
|
|
405
421
|
speed: {
|
|
406
422
|
medianSeconds: 14.3,
|
|
@@ -437,10 +453,10 @@ var MODELS = {
|
|
|
437
453
|
seedream45: {
|
|
438
454
|
benchmark: {
|
|
439
455
|
artificialAnalysis: {
|
|
440
|
-
editing: { elo:
|
|
456
|
+
editing: { elo: 1186, pricePer1k: 40, rank: 26 },
|
|
441
457
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
442
458
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
443
|
-
textToImage: { elo:
|
|
459
|
+
textToImage: { elo: 1203, pricePer1k: 40, rank: 32 }
|
|
444
460
|
},
|
|
445
461
|
speed: {
|
|
446
462
|
medianSeconds: 18.2,
|
|
@@ -475,6 +491,14 @@ var MODELS = {
|
|
|
475
491
|
type: "generation"
|
|
476
492
|
},
|
|
477
493
|
seedream5: {
|
|
494
|
+
benchmark: {
|
|
495
|
+
artificialAnalysis: {
|
|
496
|
+
editing: { elo: 1248, pricePer1k: 90, rank: 8 },
|
|
497
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
498
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
499
|
+
textToImage: { elo: 1279, pricePer1k: 90, rank: 10 }
|
|
500
|
+
}
|
|
501
|
+
},
|
|
478
502
|
name: "Seedream 5.0 Pro",
|
|
479
503
|
endpoint: "bytedance/seedream/v5/pro/text-to-image",
|
|
480
504
|
editEndpoint: "bytedance/seedream/v5/pro/edit",
|
|
@@ -501,6 +525,13 @@ var MODELS = {
|
|
|
501
525
|
maxReferenceImages: 10
|
|
502
526
|
},
|
|
503
527
|
"seedream5-lite": {
|
|
528
|
+
benchmark: {
|
|
529
|
+
artificialAnalysis: {
|
|
530
|
+
editing: { elo: 1169, pricePer1k: 35, rank: 32 },
|
|
531
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
532
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
533
|
+
}
|
|
534
|
+
},
|
|
504
535
|
name: "Seedream 5.0 Lite",
|
|
505
536
|
endpoint: "fal-ai/bytedance/seedream/v5/lite/text-to-image",
|
|
506
537
|
editEndpoint: "fal-ai/bytedance/seedream/v5/lite/edit",
|
|
@@ -529,10 +560,10 @@ var MODELS = {
|
|
|
529
560
|
"flux2-max": {
|
|
530
561
|
benchmark: {
|
|
531
562
|
artificialAnalysis: {
|
|
532
|
-
editing: { elo:
|
|
563
|
+
editing: { elo: 1201, pricePer1k: 140, rank: 19 },
|
|
533
564
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
534
565
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
535
|
-
textToImage: { elo:
|
|
566
|
+
textToImage: { elo: 1225, pricePer1k: 70, rank: 17 }
|
|
536
567
|
},
|
|
537
568
|
speed: {
|
|
538
569
|
medianSeconds: 31.6,
|
|
@@ -572,10 +603,10 @@ var MODELS = {
|
|
|
572
603
|
"flux2-pro": {
|
|
573
604
|
benchmark: {
|
|
574
605
|
artificialAnalysis: {
|
|
575
|
-
editing: { elo: 1170, pricePer1k:
|
|
606
|
+
editing: { elo: 1170, pricePer1k: 45, rank: 31 },
|
|
576
607
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
577
608
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
578
|
-
textToImage: { elo:
|
|
609
|
+
textToImage: { elo: 1207, pricePer1k: 30, rank: 30 }
|
|
579
610
|
},
|
|
580
611
|
speed: {
|
|
581
612
|
medianSeconds: 16.1,
|
|
@@ -615,10 +646,9 @@ var MODELS = {
|
|
|
615
646
|
"flux2-flex": {
|
|
616
647
|
benchmark: {
|
|
617
648
|
artificialAnalysis: {
|
|
618
|
-
editing: { elo: 1161, pricePer1k: 60, rank: 22 },
|
|
619
649
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
620
650
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
621
|
-
textToImage: { elo:
|
|
651
|
+
textToImage: { elo: 1223, pricePer1k: 60, rank: 18 }
|
|
622
652
|
},
|
|
623
653
|
speed: {
|
|
624
654
|
medianSeconds: 15.3,
|
|
@@ -662,8 +692,7 @@ var MODELS = {
|
|
|
662
692
|
benchmark: {
|
|
663
693
|
artificialAnalysis: {
|
|
664
694
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
665
|
-
sourceUrls: AA_IMAGE_SOURCES
|
|
666
|
-
textToImage: { elo: 1160, pricePer1k: 12, rank: 19, winRate: 0.57 }
|
|
695
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
667
696
|
},
|
|
668
697
|
speed: {
|
|
669
698
|
medianSeconds: 4.6,
|
|
@@ -806,7 +835,55 @@ var MODELS = {
|
|
|
806
835
|
supportsSyncMode: true,
|
|
807
836
|
type: "generation"
|
|
808
837
|
},
|
|
838
|
+
reve: {
|
|
839
|
+
benchmark: {
|
|
840
|
+
artificialAnalysis: {
|
|
841
|
+
// #2 on BOTH boards, which almost nothing else manages - the top text-to-image model
|
|
842
|
+
// (GPT Image 2) drops to 3rd for editing, and the top editor (MAI-Image-2.5-Pro) is not
|
|
843
|
+
// on fal at all. Reve is the best editor motif can actually reach.
|
|
844
|
+
editing: { elo: 1263, pricePer1k: 200, rank: 2 },
|
|
845
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
846
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
847
|
+
textToImage: { elo: 1322, pricePer1k: 200, rank: 2 }
|
|
848
|
+
},
|
|
849
|
+
tiers: { price: "premium", quality: "frontier", speed: "unknown" },
|
|
850
|
+
useCase: "Highest-ranked editor reachable on fal; strong prompt adherence and text rendering"
|
|
851
|
+
},
|
|
852
|
+
editEndpoint: "fal-ai/reve/edit",
|
|
853
|
+
endpoint: "fal-ai/reve/text-to-image",
|
|
854
|
+
falPricing: {
|
|
855
|
+
checkedAt: FAL_PRICING_CHECKED_AUG_2026,
|
|
856
|
+
currency: "USD",
|
|
857
|
+
endpointId: "fal-ai/reve/text-to-image",
|
|
858
|
+
estimatedCostPerImageUsd: 0.04,
|
|
859
|
+
source: "fal-pricing-api",
|
|
860
|
+
unit: "images",
|
|
861
|
+
unitPrice: 0.04
|
|
862
|
+
},
|
|
863
|
+
maxReferenceImages: 1,
|
|
864
|
+
name: "Reve 2.1",
|
|
865
|
+
pricePerImageUsd: 0.04,
|
|
866
|
+
pricing: "$0.04",
|
|
867
|
+
// fal's schema takes aspect_ratio from a fixed set and has no width/height or resolution
|
|
868
|
+
// parameter, so aspect is supported and resolution is not.
|
|
869
|
+
sizeMode: "aspect_ratio",
|
|
870
|
+
supportsAspect: true,
|
|
871
|
+
supportsEdit: true,
|
|
872
|
+
supportsNumImages: true,
|
|
873
|
+
supportsOutputFormat: true,
|
|
874
|
+
supportsResolution: false,
|
|
875
|
+
supportsSeed: false,
|
|
876
|
+
supportsSyncMode: true,
|
|
877
|
+
type: "generation"
|
|
878
|
+
},
|
|
809
879
|
recraft4: {
|
|
880
|
+
benchmark: {
|
|
881
|
+
artificialAnalysis: {
|
|
882
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
883
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
884
|
+
textToImage: { elo: 1215, pricePer1k: 35, rank: 25 }
|
|
885
|
+
}
|
|
886
|
+
},
|
|
810
887
|
endpoint: "fal-ai/recraft/v4/text-to-image",
|
|
811
888
|
falPricing: {
|
|
812
889
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
@@ -857,6 +934,13 @@ var MODELS = {
|
|
|
857
934
|
type: "generation"
|
|
858
935
|
},
|
|
859
936
|
ideogram4: {
|
|
937
|
+
benchmark: {
|
|
938
|
+
artificialAnalysis: {
|
|
939
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
940
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
941
|
+
textToImage: { elo: 1220, pricePer1k: 60, rank: 19 }
|
|
942
|
+
}
|
|
943
|
+
},
|
|
860
944
|
name: "Ideogram V4",
|
|
861
945
|
endpoint: "ideogram/v4",
|
|
862
946
|
type: "generation",
|
|
@@ -889,10 +973,10 @@ var MODELS = {
|
|
|
889
973
|
"grok-image": {
|
|
890
974
|
benchmark: {
|
|
891
975
|
artificialAnalysis: {
|
|
892
|
-
editing: { elo:
|
|
976
|
+
editing: { elo: 1214, pricePer1k: 20, rank: 16 },
|
|
893
977
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
894
978
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
895
|
-
textToImage: { elo:
|
|
979
|
+
textToImage: { elo: 1216, pricePer1k: 20, rank: 23 }
|
|
896
980
|
},
|
|
897
981
|
speed: {
|
|
898
982
|
medianSeconds: 5.1,
|
|
@@ -930,8 +1014,7 @@ var MODELS = {
|
|
|
930
1014
|
benchmark: {
|
|
931
1015
|
artificialAnalysis: {
|
|
932
1016
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
933
|
-
sourceUrls: AA_IMAGE_SOURCES
|
|
934
|
-
textToImage: { elo: 1158, pricePer1k: 20, rank: 20, winRate: 0.6 }
|
|
1017
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
935
1018
|
},
|
|
936
1019
|
speed: {
|
|
937
1020
|
medianSeconds: 23.3,
|
|
@@ -966,8 +1049,14 @@ var MODELS = {
|
|
|
966
1049
|
},
|
|
967
1050
|
qwen3: {
|
|
968
1051
|
benchmark: {
|
|
1052
|
+
artificialAnalysis: {
|
|
1053
|
+
editing: { elo: 1218, pricePer1k: 33, rank: 15 },
|
|
1054
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
1055
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
1056
|
+
textToImage: { elo: 1272, pricePer1k: 30, rank: 11 }
|
|
1057
|
+
},
|
|
969
1058
|
tiers: { price: "budget", quality: "better", speed: "unknown" },
|
|
970
|
-
useCase: "
|
|
1059
|
+
useCase: "Strong quality per dollar - 11th on text-to-image at $30/1k"
|
|
971
1060
|
},
|
|
972
1061
|
endpoint: "fal-ai/qwen-image-3/text-to-image",
|
|
973
1062
|
falPricing: {
|
|
@@ -1392,7 +1481,7 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
|
|
|
1392
1481
|
if (options.background !== void 0 && config.supportsBackground !== true) {
|
|
1393
1482
|
unsupported(config, "background");
|
|
1394
1483
|
}
|
|
1395
|
-
if (options.transparent === true &&
|
|
1484
|
+
if (options.transparent === true && config.supportsBackground !== true) {
|
|
1396
1485
|
unsupported(config, "transparent output");
|
|
1397
1486
|
}
|
|
1398
1487
|
if (options.inputFidelity !== void 0) {
|
|
@@ -1674,353 +1763,351 @@ function buildGenerateBody(options) {
|
|
|
1674
1763
|
|
|
1675
1764
|
// src/leaderboards.ts
|
|
1676
1765
|
var IMAGE_TEXT_TO_IMAGE_TOP_20 = {
|
|
1677
|
-
capturedAt: "2026-
|
|
1766
|
+
capturedAt: "2026-08-23",
|
|
1678
1767
|
entries: [
|
|
1679
1768
|
{
|
|
1680
1769
|
apiPricing: "$211/1k images",
|
|
1681
1770
|
creator: "OpenAI",
|
|
1682
|
-
elo:
|
|
1771
|
+
elo: 1369,
|
|
1683
1772
|
isCurrent: true,
|
|
1684
1773
|
model: "GPT Image 2 (high)",
|
|
1685
1774
|
rank: 1,
|
|
1686
1775
|
routableModelId: "gpt2"
|
|
1687
1776
|
},
|
|
1688
1777
|
{
|
|
1689
|
-
apiPricing: "$
|
|
1690
|
-
creator: "
|
|
1691
|
-
elo:
|
|
1778
|
+
apiPricing: "$200/1k images",
|
|
1779
|
+
creator: "Reve",
|
|
1780
|
+
elo: 1322,
|
|
1692
1781
|
isCurrent: true,
|
|
1693
|
-
model: "
|
|
1782
|
+
model: "Reve 2.1",
|
|
1694
1783
|
rank: 2,
|
|
1695
|
-
routableModelId: "
|
|
1784
|
+
routableModelId: "reve"
|
|
1696
1785
|
},
|
|
1697
1786
|
{
|
|
1698
1787
|
apiPricing: "$67/1k images",
|
|
1699
1788
|
creator: "Google",
|
|
1700
|
-
elo:
|
|
1789
|
+
elo: 1320,
|
|
1701
1790
|
isCurrent: true,
|
|
1702
1791
|
model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
|
|
1703
1792
|
rank: 3,
|
|
1704
1793
|
routableModelId: "banana2"
|
|
1705
1794
|
},
|
|
1706
1795
|
{
|
|
1707
|
-
apiPricing: "$
|
|
1708
|
-
creator: "
|
|
1709
|
-
elo:
|
|
1796
|
+
apiPricing: "$133/1k images",
|
|
1797
|
+
creator: "OpenAI",
|
|
1798
|
+
elo: 1310,
|
|
1710
1799
|
isCurrent: true,
|
|
1711
|
-
model: "
|
|
1712
|
-
rank: 4
|
|
1800
|
+
model: "GPT Image 1.5 (high)",
|
|
1801
|
+
rank: 4,
|
|
1802
|
+
routableModelId: "gpt"
|
|
1713
1803
|
},
|
|
1714
1804
|
{
|
|
1715
|
-
apiPricing: "$
|
|
1716
|
-
creator: "
|
|
1717
|
-
elo:
|
|
1805
|
+
apiPricing: "$48/1k images",
|
|
1806
|
+
creator: "Microsoft AI",
|
|
1807
|
+
elo: 1305,
|
|
1718
1808
|
isCurrent: true,
|
|
1719
|
-
model: "
|
|
1720
|
-
rank: 5
|
|
1721
|
-
routableModelId: "banana"
|
|
1809
|
+
model: "MAI-Image-2.5",
|
|
1810
|
+
rank: 5
|
|
1722
1811
|
},
|
|
1723
1812
|
{
|
|
1724
|
-
apiPricing: "$
|
|
1725
|
-
creator: "
|
|
1726
|
-
elo:
|
|
1813
|
+
apiPricing: "$134/1k images",
|
|
1814
|
+
creator: "Google",
|
|
1815
|
+
elo: 1297,
|
|
1727
1816
|
isCurrent: true,
|
|
1728
|
-
model: "
|
|
1817
|
+
model: "Nano Banana Pro (Gemini 3 Pro Image)",
|
|
1729
1818
|
rank: 6,
|
|
1730
|
-
routableModelId: "
|
|
1819
|
+
routableModelId: "banana"
|
|
1731
1820
|
},
|
|
1732
1821
|
{
|
|
1733
|
-
apiPricing: "$
|
|
1734
|
-
creator: "Microsoft",
|
|
1735
|
-
elo:
|
|
1822
|
+
apiPricing: "$109/1k images",
|
|
1823
|
+
creator: "Microsoft AI",
|
|
1824
|
+
elo: 1293,
|
|
1736
1825
|
isCurrent: true,
|
|
1737
|
-
model: "MAI-Image-2",
|
|
1826
|
+
model: "MAI-Image-2.5-Pro",
|
|
1738
1827
|
rank: 7
|
|
1739
1828
|
},
|
|
1740
1829
|
{
|
|
1741
|
-
apiPricing: "$
|
|
1742
|
-
creator: "
|
|
1743
|
-
elo:
|
|
1830
|
+
apiPricing: "$34/1k images",
|
|
1831
|
+
creator: "Google",
|
|
1832
|
+
elo: 1289,
|
|
1744
1833
|
isCurrent: true,
|
|
1745
|
-
model: "
|
|
1746
|
-
rank: 8
|
|
1747
|
-
routableModelId: "flux2-max"
|
|
1834
|
+
model: "Nano Banana 2 Lite (Gemini 3.1 Flash Lite Image)",
|
|
1835
|
+
rank: 8
|
|
1748
1836
|
},
|
|
1749
1837
|
{
|
|
1750
|
-
apiPricing: "
|
|
1751
|
-
creator: "
|
|
1752
|
-
elo:
|
|
1838
|
+
apiPricing: "$43/1k images",
|
|
1839
|
+
creator: "Alibaba",
|
|
1840
|
+
elo: 1282,
|
|
1753
1841
|
isCurrent: true,
|
|
1754
|
-
model: "
|
|
1842
|
+
model: "Qwen-Image-3.0-Pro",
|
|
1755
1843
|
rank: 9
|
|
1756
1844
|
},
|
|
1757
1845
|
{
|
|
1758
|
-
apiPricing: "$
|
|
1759
|
-
creator: "
|
|
1760
|
-
elo:
|
|
1846
|
+
apiPricing: "$90/1k images",
|
|
1847
|
+
creator: "Bytedance",
|
|
1848
|
+
elo: 1279,
|
|
1761
1849
|
isCurrent: true,
|
|
1762
|
-
model: "
|
|
1850
|
+
model: "Seedream 5.0 Pro",
|
|
1763
1851
|
rank: 10,
|
|
1764
|
-
routableModelId: "
|
|
1852
|
+
routableModelId: "seedream5"
|
|
1765
1853
|
},
|
|
1766
1854
|
{
|
|
1767
|
-
apiPricing: "$
|
|
1768
|
-
creator: "
|
|
1769
|
-
elo:
|
|
1770
|
-
isCurrent:
|
|
1771
|
-
model: "
|
|
1772
|
-
rank: 11
|
|
1855
|
+
apiPricing: "$30/1k images",
|
|
1856
|
+
creator: "Alibaba",
|
|
1857
|
+
elo: 1272,
|
|
1858
|
+
isCurrent: true,
|
|
1859
|
+
model: "Qwen-Image-3.0",
|
|
1860
|
+
rank: 11,
|
|
1861
|
+
routableModelId: "qwen3"
|
|
1773
1862
|
},
|
|
1774
1863
|
{
|
|
1775
|
-
apiPricing: "$
|
|
1776
|
-
creator: "
|
|
1777
|
-
elo:
|
|
1864
|
+
apiPricing: "$75/1k images",
|
|
1865
|
+
creator: "Alibaba",
|
|
1866
|
+
elo: 1237,
|
|
1778
1867
|
isCurrent: true,
|
|
1779
|
-
model: "
|
|
1780
|
-
rank: 12
|
|
1781
|
-
routableModelId: "grok-image"
|
|
1868
|
+
model: "Qwen Image 2.0 Pro",
|
|
1869
|
+
rank: 12
|
|
1782
1870
|
},
|
|
1783
1871
|
{
|
|
1784
|
-
apiPricing: "$
|
|
1785
|
-
creator: "
|
|
1786
|
-
elo:
|
|
1872
|
+
apiPricing: "$50/1k images",
|
|
1873
|
+
creator: "SpaceXAI",
|
|
1874
|
+
elo: 1234,
|
|
1787
1875
|
isCurrent: true,
|
|
1788
|
-
model: "
|
|
1789
|
-
rank: 13
|
|
1790
|
-
routableModelId: "flux2-flex"
|
|
1876
|
+
model: "grok-imagine-image-quality",
|
|
1877
|
+
rank: 13
|
|
1791
1878
|
},
|
|
1792
1879
|
{
|
|
1793
|
-
apiPricing: "$
|
|
1794
|
-
creator: "
|
|
1795
|
-
elo:
|
|
1880
|
+
apiPricing: "$20/1k images",
|
|
1881
|
+
creator: "Microsoft AI",
|
|
1882
|
+
elo: 1228,
|
|
1796
1883
|
isCurrent: true,
|
|
1797
|
-
model: "
|
|
1884
|
+
model: "MAI-Image-2.5-Flash",
|
|
1798
1885
|
rank: 14
|
|
1799
1886
|
},
|
|
1800
1887
|
{
|
|
1801
|
-
apiPricing: "$
|
|
1802
|
-
creator: "
|
|
1803
|
-
elo:
|
|
1888
|
+
apiPricing: "$80/1k images",
|
|
1889
|
+
creator: "HiDream",
|
|
1890
|
+
elo: 1228,
|
|
1804
1891
|
isCurrent: true,
|
|
1805
|
-
model: "
|
|
1892
|
+
model: "HiDream-O1-Image-1.5",
|
|
1806
1893
|
rank: 15
|
|
1807
1894
|
},
|
|
1808
1895
|
{
|
|
1809
|
-
apiPricing: "$
|
|
1810
|
-
creator: "
|
|
1811
|
-
elo:
|
|
1812
|
-
isCurrent:
|
|
1813
|
-
model: "
|
|
1814
|
-
rank: 16
|
|
1896
|
+
apiPricing: "$30/1k images",
|
|
1897
|
+
creator: "ByteDance Seed",
|
|
1898
|
+
elo: 1226,
|
|
1899
|
+
isCurrent: true,
|
|
1900
|
+
model: "Seedream 4.0",
|
|
1901
|
+
rank: 16,
|
|
1902
|
+
routableModelId: "seedream4"
|
|
1815
1903
|
},
|
|
1816
1904
|
{
|
|
1817
|
-
apiPricing: "$
|
|
1818
|
-
creator: "
|
|
1819
|
-
elo:
|
|
1905
|
+
apiPricing: "$70/1k images",
|
|
1906
|
+
creator: "Black Forest Labs",
|
|
1907
|
+
elo: 1225,
|
|
1820
1908
|
isCurrent: true,
|
|
1821
|
-
model: "
|
|
1909
|
+
model: "FLUX.2 [max]",
|
|
1822
1910
|
rank: 17,
|
|
1823
|
-
routableModelId: "
|
|
1911
|
+
routableModelId: "flux2-max"
|
|
1824
1912
|
},
|
|
1825
1913
|
{
|
|
1826
|
-
apiPricing: "$
|
|
1827
|
-
creator: "
|
|
1828
|
-
elo:
|
|
1914
|
+
apiPricing: "$60/1k images",
|
|
1915
|
+
creator: "Black Forest Labs",
|
|
1916
|
+
elo: 1223,
|
|
1829
1917
|
isCurrent: true,
|
|
1830
|
-
model: "FLUX.2 [
|
|
1831
|
-
rank: 18
|
|
1918
|
+
model: "FLUX.2 [flex]",
|
|
1919
|
+
rank: 18,
|
|
1920
|
+
routableModelId: "flux2-flex"
|
|
1832
1921
|
},
|
|
1833
1922
|
{
|
|
1834
|
-
apiPricing: "$
|
|
1835
|
-
creator: "
|
|
1836
|
-
elo:
|
|
1923
|
+
apiPricing: "$60/1k images",
|
|
1924
|
+
creator: "Ideogram",
|
|
1925
|
+
elo: 1220,
|
|
1837
1926
|
isCurrent: true,
|
|
1838
|
-
model: "
|
|
1927
|
+
model: "Ideogram 4.0",
|
|
1839
1928
|
rank: 19,
|
|
1840
|
-
routableModelId: "
|
|
1929
|
+
routableModelId: "ideogram4"
|
|
1841
1930
|
},
|
|
1842
1931
|
{
|
|
1843
|
-
apiPricing: "$
|
|
1844
|
-
creator: "
|
|
1845
|
-
elo:
|
|
1932
|
+
apiPricing: "$60/1k images",
|
|
1933
|
+
creator: "Krea",
|
|
1934
|
+
elo: 1220,
|
|
1846
1935
|
isCurrent: true,
|
|
1847
|
-
model: "
|
|
1848
|
-
rank: 20
|
|
1849
|
-
routableModelId: "qwen"
|
|
1936
|
+
model: "Krea 2 Large",
|
|
1937
|
+
rank: 20
|
|
1850
1938
|
}
|
|
1851
1939
|
],
|
|
1852
1940
|
source: "https://artificialanalysis.ai/image/leaderboard/text-to-image"
|
|
1853
1941
|
};
|
|
1854
1942
|
var IMAGE_EDITING_TOP_20 = {
|
|
1855
|
-
capturedAt: "2026-
|
|
1943
|
+
capturedAt: "2026-08-23",
|
|
1856
1944
|
entries: [
|
|
1857
1945
|
{
|
|
1858
|
-
apiPricing: "$
|
|
1859
|
-
creator: "
|
|
1860
|
-
elo:
|
|
1946
|
+
apiPricing: "$109/1k images",
|
|
1947
|
+
creator: "Microsoft AI",
|
|
1948
|
+
elo: 1272,
|
|
1861
1949
|
isCurrent: true,
|
|
1862
|
-
model: "
|
|
1950
|
+
model: "MAI-Image-2.5-Pro",
|
|
1863
1951
|
rank: 1
|
|
1864
1952
|
},
|
|
1865
1953
|
{
|
|
1866
|
-
apiPricing: "$
|
|
1867
|
-
creator: "
|
|
1868
|
-
elo:
|
|
1954
|
+
apiPricing: "$200/1k images",
|
|
1955
|
+
creator: "Reve",
|
|
1956
|
+
elo: 1263,
|
|
1869
1957
|
isCurrent: true,
|
|
1870
|
-
model: "
|
|
1958
|
+
model: "Reve 2.1",
|
|
1871
1959
|
rank: 2,
|
|
1872
|
-
routableModelId: "
|
|
1960
|
+
routableModelId: "reve"
|
|
1873
1961
|
},
|
|
1874
1962
|
{
|
|
1875
1963
|
apiPricing: "$211/1k images",
|
|
1876
1964
|
creator: "OpenAI",
|
|
1877
|
-
elo:
|
|
1965
|
+
elo: 1256,
|
|
1878
1966
|
isCurrent: true,
|
|
1879
1967
|
model: "GPT Image 2 (high)",
|
|
1880
1968
|
rank: 3,
|
|
1881
1969
|
routableModelId: "gpt2"
|
|
1882
1970
|
},
|
|
1883
1971
|
{
|
|
1884
|
-
apiPricing: "$
|
|
1885
|
-
creator: "
|
|
1886
|
-
elo:
|
|
1972
|
+
apiPricing: "$48/1k images",
|
|
1973
|
+
creator: "Microsoft AI",
|
|
1974
|
+
elo: 1256,
|
|
1887
1975
|
isCurrent: true,
|
|
1888
|
-
model: "
|
|
1889
|
-
rank: 4
|
|
1890
|
-
routableModelId: "banana"
|
|
1976
|
+
model: "MAI-Image-2.5",
|
|
1977
|
+
rank: 4
|
|
1891
1978
|
},
|
|
1892
1979
|
{
|
|
1893
|
-
apiPricing: "$
|
|
1894
|
-
creator: "
|
|
1895
|
-
elo:
|
|
1980
|
+
apiPricing: "$133/1k images",
|
|
1981
|
+
creator: "OpenAI",
|
|
1982
|
+
elo: 1250,
|
|
1896
1983
|
isCurrent: true,
|
|
1897
|
-
model: "
|
|
1984
|
+
model: "GPT Image 1.5 (high)",
|
|
1898
1985
|
rank: 5,
|
|
1899
|
-
routableModelId: "
|
|
1986
|
+
routableModelId: "gpt"
|
|
1900
1987
|
},
|
|
1901
1988
|
{
|
|
1902
|
-
apiPricing: "$
|
|
1903
|
-
creator: "
|
|
1904
|
-
elo:
|
|
1989
|
+
apiPricing: "$43/1k images",
|
|
1990
|
+
creator: "Alibaba",
|
|
1991
|
+
elo: 1250,
|
|
1905
1992
|
isCurrent: true,
|
|
1906
|
-
model: "
|
|
1993
|
+
model: "Qwen-Image-3.0-Pro",
|
|
1907
1994
|
rank: 6
|
|
1908
1995
|
},
|
|
1909
1996
|
{
|
|
1910
|
-
apiPricing: "$
|
|
1911
|
-
creator: "
|
|
1912
|
-
elo:
|
|
1997
|
+
apiPricing: "$67/1k images",
|
|
1998
|
+
creator: "Google",
|
|
1999
|
+
elo: 1249,
|
|
1913
2000
|
isCurrent: true,
|
|
1914
|
-
model: "
|
|
2001
|
+
model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
|
|
1915
2002
|
rank: 7,
|
|
1916
|
-
routableModelId: "
|
|
2003
|
+
routableModelId: "banana2"
|
|
1917
2004
|
},
|
|
1918
2005
|
{
|
|
1919
|
-
apiPricing: "$
|
|
1920
|
-
creator: "
|
|
1921
|
-
elo:
|
|
2006
|
+
apiPricing: "$90/1k images",
|
|
2007
|
+
creator: "Bytedance",
|
|
2008
|
+
elo: 1248,
|
|
1922
2009
|
isCurrent: true,
|
|
1923
|
-
model: "
|
|
1924
|
-
rank: 8
|
|
2010
|
+
model: "Seedream 5.0 Pro",
|
|
2011
|
+
rank: 8,
|
|
2012
|
+
routableModelId: "seedream5"
|
|
1925
2013
|
},
|
|
1926
2014
|
{
|
|
1927
|
-
apiPricing: "
|
|
1928
|
-
creator: "
|
|
1929
|
-
elo:
|
|
2015
|
+
apiPricing: "$134/1k images",
|
|
2016
|
+
creator: "Google",
|
|
2017
|
+
elo: 1245,
|
|
1930
2018
|
isCurrent: true,
|
|
1931
|
-
model: "
|
|
1932
|
-
rank: 9
|
|
2019
|
+
model: "Nano Banana Pro (Gemini 3 Pro Image)",
|
|
2020
|
+
rank: 9,
|
|
2021
|
+
routableModelId: "banana"
|
|
1933
2022
|
},
|
|
1934
2023
|
{
|
|
1935
|
-
apiPricing: "$
|
|
1936
|
-
creator: "
|
|
1937
|
-
elo:
|
|
2024
|
+
apiPricing: "$20/1k images",
|
|
2025
|
+
creator: "Microsoft AI",
|
|
2026
|
+
elo: 1236,
|
|
1938
2027
|
isCurrent: true,
|
|
1939
|
-
model: "
|
|
1940
|
-
rank: 10
|
|
1941
|
-
routableModelId: "flux2-max"
|
|
2028
|
+
model: "MAI-Image-2.5-Flash",
|
|
2029
|
+
rank: 10
|
|
1942
2030
|
},
|
|
1943
2031
|
{
|
|
1944
|
-
apiPricing: "
|
|
1945
|
-
creator: "
|
|
1946
|
-
elo:
|
|
2032
|
+
apiPricing: "$50/1k images",
|
|
2033
|
+
creator: "SpaceXAI",
|
|
2034
|
+
elo: 1226,
|
|
1947
2035
|
isCurrent: true,
|
|
1948
|
-
model: "
|
|
2036
|
+
model: "grok-imagine-image-quality",
|
|
1949
2037
|
rank: 11
|
|
1950
2038
|
},
|
|
1951
2039
|
{
|
|
1952
|
-
apiPricing: "$
|
|
1953
|
-
creator: "
|
|
1954
|
-
elo:
|
|
2040
|
+
apiPricing: "$90/1k images",
|
|
2041
|
+
creator: "Tencent",
|
|
2042
|
+
elo: 1222,
|
|
1955
2043
|
isCurrent: true,
|
|
1956
|
-
model: "
|
|
2044
|
+
model: "HunyuanImage 3.0 Instruct",
|
|
1957
2045
|
rank: 12
|
|
1958
2046
|
},
|
|
1959
2047
|
{
|
|
1960
|
-
apiPricing: "$
|
|
1961
|
-
creator: "
|
|
1962
|
-
elo:
|
|
2048
|
+
apiPricing: "$80/1k images",
|
|
2049
|
+
creator: "Newport AI",
|
|
2050
|
+
elo: 1220,
|
|
1963
2051
|
isCurrent: true,
|
|
1964
|
-
model: "
|
|
2052
|
+
model: "DreamImage 2.0",
|
|
1965
2053
|
rank: 13
|
|
1966
2054
|
},
|
|
1967
2055
|
{
|
|
1968
|
-
apiPricing: "$
|
|
1969
|
-
creator: "
|
|
1970
|
-
elo:
|
|
2056
|
+
apiPricing: "$100/1k images",
|
|
2057
|
+
creator: "Luma Labs",
|
|
2058
|
+
elo: 1219,
|
|
1971
2059
|
isCurrent: true,
|
|
1972
|
-
model: "
|
|
2060
|
+
model: "Luma UNI 1 Max",
|
|
1973
2061
|
rank: 14
|
|
1974
2062
|
},
|
|
1975
2063
|
{
|
|
1976
|
-
apiPricing: "$
|
|
1977
|
-
creator: "
|
|
1978
|
-
elo:
|
|
2064
|
+
apiPricing: "$33/1k images",
|
|
2065
|
+
creator: "Alibaba",
|
|
2066
|
+
elo: 1218,
|
|
1979
2067
|
isCurrent: true,
|
|
1980
|
-
model: "
|
|
1981
|
-
rank: 15
|
|
2068
|
+
model: "Qwen-Image-3.0",
|
|
2069
|
+
rank: 15,
|
|
2070
|
+
routableModelId: "qwen3"
|
|
1982
2071
|
},
|
|
1983
2072
|
{
|
|
1984
|
-
apiPricing: "$
|
|
1985
|
-
creator: "
|
|
1986
|
-
elo:
|
|
2073
|
+
apiPricing: "$20/1k images",
|
|
2074
|
+
creator: "SpaceXAI",
|
|
2075
|
+
elo: 1214,
|
|
1987
2076
|
isCurrent: true,
|
|
1988
|
-
model: "
|
|
2077
|
+
model: "grok-imagine-image",
|
|
1989
2078
|
rank: 16,
|
|
1990
|
-
routableModelId: "
|
|
2079
|
+
routableModelId: "grok-image"
|
|
1991
2080
|
},
|
|
1992
2081
|
{
|
|
1993
|
-
apiPricing: "$
|
|
1994
|
-
creator: "
|
|
1995
|
-
elo:
|
|
2082
|
+
apiPricing: "$28/1k images",
|
|
2083
|
+
creator: "KlingAI",
|
|
2084
|
+
elo: 1208,
|
|
1996
2085
|
isCurrent: true,
|
|
1997
|
-
model: "
|
|
1998
|
-
rank: 17
|
|
1999
|
-
routableModelId: "seedream45"
|
|
2086
|
+
model: "Kling Image 3.0 Omni",
|
|
2087
|
+
rank: 17
|
|
2000
2088
|
},
|
|
2001
2089
|
{
|
|
2002
|
-
apiPricing: "$
|
|
2003
|
-
creator: "
|
|
2004
|
-
elo:
|
|
2090
|
+
apiPricing: "$34/1k images",
|
|
2091
|
+
creator: "Google",
|
|
2092
|
+
elo: 1202,
|
|
2005
2093
|
isCurrent: true,
|
|
2006
|
-
model: "
|
|
2094
|
+
model: "Nano Banana 2 Lite",
|
|
2007
2095
|
rank: 18
|
|
2008
2096
|
},
|
|
2009
2097
|
{
|
|
2010
|
-
apiPricing: "$
|
|
2011
|
-
creator: "
|
|
2012
|
-
elo:
|
|
2098
|
+
apiPricing: "$140/1k images",
|
|
2099
|
+
creator: "Black Forest Labs",
|
|
2100
|
+
elo: 1201,
|
|
2013
2101
|
isCurrent: true,
|
|
2014
|
-
model: "
|
|
2102
|
+
model: "FLUX.2 [max]",
|
|
2015
2103
|
rank: 19,
|
|
2016
|
-
routableModelId: "
|
|
2104
|
+
routableModelId: "flux2-max"
|
|
2017
2105
|
},
|
|
2018
2106
|
{
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
elo: 1172,
|
|
2107
|
+
creator: "Alibaba",
|
|
2108
|
+
elo: 1200,
|
|
2022
2109
|
isCurrent: true,
|
|
2023
|
-
model: "
|
|
2110
|
+
model: "Wan 2.7 Pro",
|
|
2024
2111
|
rank: 20
|
|
2025
2112
|
}
|
|
2026
2113
|
],
|
|
@@ -2250,7 +2337,31 @@ var VIDEO_IMAGE_TO_VIDEO_TOP_15 = {
|
|
|
2250
2337
|
};
|
|
2251
2338
|
|
|
2252
2339
|
// src/server.ts
|
|
2253
|
-
var
|
|
2340
|
+
var import_neverthrow2 = require("neverthrow");
|
|
2341
|
+
|
|
2342
|
+
// src/errors.ts
|
|
2343
|
+
var MotifError = class extends Error {
|
|
2344
|
+
status;
|
|
2345
|
+
code;
|
|
2346
|
+
/** fal's request-correlation id (from the `x-fal-request-id` header or the
|
|
2347
|
+
* error body). Ties a failure back to fal's dashboard/support. */
|
|
2348
|
+
requestId;
|
|
2349
|
+
constructor(message, status, code, requestId) {
|
|
2350
|
+
super(message);
|
|
2351
|
+
this.name = "MotifError";
|
|
2352
|
+
this.status = status;
|
|
2353
|
+
this.code = code;
|
|
2354
|
+
this.requestId = requestId;
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2357
|
+
function toMotifError(error) {
|
|
2358
|
+
if (error instanceof MotifError) {
|
|
2359
|
+
return error;
|
|
2360
|
+
}
|
|
2361
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2362
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2363
|
+
return new MotifError(message, 0, code);
|
|
2364
|
+
}
|
|
2254
2365
|
|
|
2255
2366
|
// src/fal-parse.ts
|
|
2256
2367
|
function isRecord(value) {
|
|
@@ -2348,9 +2459,362 @@ function requestIdFromBody(text) {
|
|
|
2348
2459
|
return asString(parsed.request_id) ?? asString(parsed.requestId) ?? asString(parsed.trace_id);
|
|
2349
2460
|
}
|
|
2350
2461
|
|
|
2351
|
-
// src/tools.ts
|
|
2352
|
-
var
|
|
2353
|
-
|
|
2462
|
+
// src/server-tools.ts
|
|
2463
|
+
var import_neverthrow = require("neverthrow");
|
|
2464
|
+
|
|
2465
|
+
// src/tool-registry/analysis.ts
|
|
2466
|
+
var ANALYSIS_TOOLS = {
|
|
2467
|
+
"got-ocr": {
|
|
2468
|
+
category: "analysis",
|
|
2469
|
+
description: "Transcribe text from one or more images, optionally as formatted multi-page output.",
|
|
2470
|
+
endpoint: "fal-ai/got-ocr/v2",
|
|
2471
|
+
inputField: "input_image_urls",
|
|
2472
|
+
inputKind: "images",
|
|
2473
|
+
name: "GOT-OCR 2.0",
|
|
2474
|
+
outputKeys: ["outputs"],
|
|
2475
|
+
price: { kind: "metered" },
|
|
2476
|
+
pricing: "$0.05/image",
|
|
2477
|
+
queued: true,
|
|
2478
|
+
sourceUrl: "https://fal.ai/models/fal-ai/got-ocr/v2",
|
|
2479
|
+
task: "optical character recognition"
|
|
2480
|
+
},
|
|
2481
|
+
"moondream-caption": {
|
|
2482
|
+
category: "analysis",
|
|
2483
|
+
description: "Write a short, normal, or long caption for an image.",
|
|
2484
|
+
endpoint: "fal-ai/moondream3-preview/caption",
|
|
2485
|
+
inputField: "image_url",
|
|
2486
|
+
inputKind: "image",
|
|
2487
|
+
name: "Moondream 3 Caption",
|
|
2488
|
+
outputKeys: ["output"],
|
|
2489
|
+
price: { kind: "metered" },
|
|
2490
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2491
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/caption",
|
|
2492
|
+
task: "image captioning"
|
|
2493
|
+
},
|
|
2494
|
+
"moondream-detect": {
|
|
2495
|
+
category: "analysis",
|
|
2496
|
+
description: "Detect prompted objects and return their bounding boxes, with an optional preview.",
|
|
2497
|
+
endpoint: "fal-ai/moondream3-preview/detect",
|
|
2498
|
+
inputField: "image_url",
|
|
2499
|
+
inputKind: "image",
|
|
2500
|
+
name: "Moondream 3 Detect",
|
|
2501
|
+
outputKeys: ["objects", "image"],
|
|
2502
|
+
price: { kind: "metered" },
|
|
2503
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2504
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/detect",
|
|
2505
|
+
task: "object detection"
|
|
2506
|
+
},
|
|
2507
|
+
"moondream-point": {
|
|
2508
|
+
category: "analysis",
|
|
2509
|
+
description: "Return point coordinates for every instance of a prompted subject.",
|
|
2510
|
+
endpoint: "fal-ai/moondream3-preview/point",
|
|
2511
|
+
inputField: "image_url",
|
|
2512
|
+
inputKind: "image",
|
|
2513
|
+
name: "Moondream 3 Point",
|
|
2514
|
+
outputKeys: ["points", "image"],
|
|
2515
|
+
price: { kind: "metered" },
|
|
2516
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2517
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/point",
|
|
2518
|
+
task: "object pointing"
|
|
2519
|
+
},
|
|
2520
|
+
"moondream-query": {
|
|
2521
|
+
category: "analysis",
|
|
2522
|
+
description: "Answer a free-form question about an image, with reasoning.",
|
|
2523
|
+
endpoint: "fal-ai/moondream3-preview/query",
|
|
2524
|
+
inputField: "image_url",
|
|
2525
|
+
inputKind: "image",
|
|
2526
|
+
name: "Moondream 3 Query",
|
|
2527
|
+
outputKeys: ["output", "reasoning"],
|
|
2528
|
+
price: { kind: "metered" },
|
|
2529
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2530
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/query",
|
|
2531
|
+
task: "visual question answering"
|
|
2532
|
+
},
|
|
2533
|
+
nsfw: {
|
|
2534
|
+
category: "moderation",
|
|
2535
|
+
description: "Predict whether one or more images contain NSFW concepts.",
|
|
2536
|
+
endpoint: "fal-ai/x-ailab/nsfw",
|
|
2537
|
+
inputField: "image_urls",
|
|
2538
|
+
inputKind: "images",
|
|
2539
|
+
name: "NSFW Checker",
|
|
2540
|
+
outputKeys: ["has_nsfw_concepts"],
|
|
2541
|
+
price: { kind: "metered" },
|
|
2542
|
+
pricing: "$0.001/image",
|
|
2543
|
+
sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
|
|
2544
|
+
task: "vision moderation"
|
|
2545
|
+
}
|
|
2546
|
+
};
|
|
2547
|
+
|
|
2548
|
+
// src/tool-registry/assets.ts
|
|
2549
|
+
var ASSET_TOOLS = {
|
|
2550
|
+
"hunyuan3d-v3": {
|
|
2551
|
+
category: "3d",
|
|
2552
|
+
description: "Reconstruct a textured 3D mesh from a single image, with optional multi-view inputs.",
|
|
2553
|
+
endpoint: "fal-ai/hunyuan3d-v3/image-to-3d",
|
|
2554
|
+
inputField: "input_image_url",
|
|
2555
|
+
inputKind: "image",
|
|
2556
|
+
name: "Hunyuan3D v3 Image to 3D",
|
|
2557
|
+
outputKeys: ["model_glb", "model_urls", "thumbnail"],
|
|
2558
|
+
price: { kind: "call", usd: 0.375 },
|
|
2559
|
+
pricing: "$0.375/generation at the default Normal type; $0.45 low poly, $0.225 geometry only, and $0.15 each for PBR, multi-view, or a custom face count",
|
|
2560
|
+
queued: true,
|
|
2561
|
+
sourceUrl: "https://fal.ai/models/fal-ai/hunyuan3d-v3/image-to-3d",
|
|
2562
|
+
task: "single-image 3D reconstruction"
|
|
2563
|
+
},
|
|
2564
|
+
"ideogram-layerize-text": {
|
|
2565
|
+
category: "layers",
|
|
2566
|
+
description: "Split an image into background image layers plus editable HTML text containers.",
|
|
2567
|
+
endpoint: "fal-ai/ideogram/v3/layerize-text",
|
|
2568
|
+
inputField: "image_url",
|
|
2569
|
+
inputKind: "image",
|
|
2570
|
+
name: "Ideogram v3 Text Layerize",
|
|
2571
|
+
outputKeys: ["image_layers", "image", "text_html", "text_containers"],
|
|
2572
|
+
price: { kind: "call", usd: 0.09 },
|
|
2573
|
+
pricing: "$0.09/image",
|
|
2574
|
+
queued: true,
|
|
2575
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/layerize-text",
|
|
2576
|
+
task: "text and image layer extraction"
|
|
2577
|
+
},
|
|
2578
|
+
"ideogram-tiling": {
|
|
2579
|
+
category: "material",
|
|
2580
|
+
description: "Generate a seamlessly tiling texture, optionally conditioned on a source image.",
|
|
2581
|
+
endpoint: "ideogram/v4/tiling",
|
|
2582
|
+
inputField: "image_url",
|
|
2583
|
+
inputKind: "image",
|
|
2584
|
+
name: "Ideogram v4 Tiling",
|
|
2585
|
+
outputKeys: ["images"],
|
|
2586
|
+
price: { kind: "megapixel", usd: 0.06 },
|
|
2587
|
+
pricing: "$0.06/megapixel at the default BALANCED speed; $0.03 turbo, $0.10 quality",
|
|
2588
|
+
sourceUrl: "https://fal.ai/models/ideogram/v4/tiling",
|
|
2589
|
+
task: "seamless texture generation"
|
|
2590
|
+
},
|
|
2591
|
+
image2svg: {
|
|
2592
|
+
category: "vector",
|
|
2593
|
+
description: "Trace a raster image into layered SVG paths.",
|
|
2594
|
+
endpoint: "fal-ai/image2svg",
|
|
2595
|
+
inputField: "image_url",
|
|
2596
|
+
inputKind: "image",
|
|
2597
|
+
name: "Image to SVG",
|
|
2598
|
+
outputKeys: ["images"],
|
|
2599
|
+
price: { kind: "call", usd: 5e-3 },
|
|
2600
|
+
pricing: "$0.005/image",
|
|
2601
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image2svg",
|
|
2602
|
+
task: "raster to vector tracing"
|
|
2603
|
+
},
|
|
2604
|
+
patina: {
|
|
2605
|
+
category: "material",
|
|
2606
|
+
description: "Decompose a surface photograph into PBR maps: basecolor, normal, roughness, metalness, height.",
|
|
2607
|
+
endpoint: "fal-ai/patina",
|
|
2608
|
+
inputField: "image_url",
|
|
2609
|
+
inputKind: "image",
|
|
2610
|
+
name: "Patina PBR Maps",
|
|
2611
|
+
outputKeys: ["images"],
|
|
2612
|
+
// Output order follows the request's `maps` array, so read that first;
|
|
2613
|
+
// the fallback is the endpoint's own schema default.
|
|
2614
|
+
outputLabels: {
|
|
2615
|
+
images: {
|
|
2616
|
+
fallback: ["basecolor", "normal", "roughness", "metalness", "height"],
|
|
2617
|
+
fromOption: "maps"
|
|
2618
|
+
}
|
|
2619
|
+
},
|
|
2620
|
+
price: { kind: "metered" },
|
|
2621
|
+
pricing: "$0.01 base plus $0.01/megapixel per output map, so all 5 maps on a 1MP image cost $0.06; the listed rate is per map",
|
|
2622
|
+
queued: true,
|
|
2623
|
+
sourceUrl: "https://fal.ai/models/fal-ai/patina",
|
|
2624
|
+
task: "PBR material decomposition"
|
|
2625
|
+
},
|
|
2626
|
+
"patina-extract": {
|
|
2627
|
+
category: "material",
|
|
2628
|
+
description: "Extract a tiling PBR material from a prompted region of a photograph.",
|
|
2629
|
+
endpoint: "fal-ai/patina/material/extract",
|
|
2630
|
+
inputField: "image_url",
|
|
2631
|
+
inputKind: "image",
|
|
2632
|
+
name: "Patina Material Extract",
|
|
2633
|
+
outputKeys: ["images"],
|
|
2634
|
+
// Same `maps` option and schema default as fal-ai/patina, both verified.
|
|
2635
|
+
// `num_images` above 1 multiplies the array and breaks the mapping, which
|
|
2636
|
+
// the count check catches.
|
|
2637
|
+
outputLabels: {
|
|
2638
|
+
images: {
|
|
2639
|
+
fallback: ["basecolor", "normal", "roughness", "metalness", "height"],
|
|
2640
|
+
fromOption: "maps"
|
|
2641
|
+
}
|
|
2642
|
+
},
|
|
2643
|
+
price: { kind: "metered" },
|
|
2644
|
+
pricing: "$0.10 base only; add $0.02/megapixel plus $0.01/megapixel per map, so 1MP with all 5 maps is $0.17",
|
|
2645
|
+
queued: true,
|
|
2646
|
+
sourceUrl: "https://fal.ai/models/fal-ai/patina/material/extract",
|
|
2647
|
+
task: "tiling material extraction"
|
|
2648
|
+
},
|
|
2649
|
+
"qwen-layered": {
|
|
2650
|
+
category: "layers",
|
|
2651
|
+
description: "Split an image into a configurable number of stacked RGBA layers.",
|
|
2652
|
+
endpoint: "fal-ai/qwen-image-layered",
|
|
2653
|
+
inputField: "image_url",
|
|
2654
|
+
inputKind: "image",
|
|
2655
|
+
name: "Qwen Image Layered",
|
|
2656
|
+
outputKeys: ["images"],
|
|
2657
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2658
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2659
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2660
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2661
|
+
// rather than a number that is right only on one reading.
|
|
2662
|
+
price: { kind: "metered" },
|
|
2663
|
+
pricing: "$0.05 per image; fal does not say whether that counts the input image or each of the generated layers, so no estimate is reported",
|
|
2664
|
+
queued: true,
|
|
2665
|
+
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-layered",
|
|
2666
|
+
task: "image layer decomposition"
|
|
2667
|
+
},
|
|
2668
|
+
"recraft-vectorize": {
|
|
2669
|
+
category: "vector",
|
|
2670
|
+
description: "Convert a raster image into a clean SVG.",
|
|
2671
|
+
endpoint: "fal-ai/recraft/vectorize",
|
|
2672
|
+
inputField: "image_url",
|
|
2673
|
+
inputKind: "image",
|
|
2674
|
+
name: "Recraft Vectorize",
|
|
2675
|
+
outputKeys: ["image"],
|
|
2676
|
+
price: { kind: "call", usd: 0.04 },
|
|
2677
|
+
pricing: "$0.04/image, $0.08 with a vector style",
|
|
2678
|
+
sourceUrl: "https://fal.ai/models/fal-ai/recraft/vectorize",
|
|
2679
|
+
task: "raster to vector conversion"
|
|
2680
|
+
},
|
|
2681
|
+
"sam3-3d-align": {
|
|
2682
|
+
category: "3d",
|
|
2683
|
+
description: "Align SAM 3D objects and bodies into a shared scene.",
|
|
2684
|
+
endpoint: "fal-ai/sam-3/3d-align",
|
|
2685
|
+
inputField: "image_url",
|
|
2686
|
+
inputKind: "image",
|
|
2687
|
+
name: "SAM 3D Align",
|
|
2688
|
+
outputKeys: [
|
|
2689
|
+
"scene_glb",
|
|
2690
|
+
"model_glb",
|
|
2691
|
+
"visualization",
|
|
2692
|
+
"body_mesh_ply",
|
|
2693
|
+
"metadata"
|
|
2694
|
+
],
|
|
2695
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2696
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2697
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2698
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2699
|
+
// rather than a number that is right only on one reading.
|
|
2700
|
+
price: { kind: "metered" },
|
|
2701
|
+
pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
|
|
2702
|
+
queued: true,
|
|
2703
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
|
|
2704
|
+
task: "3D scene alignment"
|
|
2705
|
+
},
|
|
2706
|
+
"sam3-3d-body": {
|
|
2707
|
+
category: "3d",
|
|
2708
|
+
defaultOptions: {
|
|
2709
|
+
export_meshes: true,
|
|
2710
|
+
include_3d_keypoints: true,
|
|
2711
|
+
include_mhr_params: true
|
|
2712
|
+
},
|
|
2713
|
+
description: "Reconstruct human body meshes and keypoints from a single image.",
|
|
2714
|
+
endpoint: "fal-ai/sam-3/3d-body",
|
|
2715
|
+
inputField: "image_url",
|
|
2716
|
+
inputKind: "image",
|
|
2717
|
+
name: "SAM 3D Body",
|
|
2718
|
+
outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
|
|
2719
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2720
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2721
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2722
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2723
|
+
// rather than a number that is right only on one reading.
|
|
2724
|
+
price: { kind: "metered" },
|
|
2725
|
+
pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
|
|
2726
|
+
queued: true,
|
|
2727
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
|
|
2728
|
+
task: "single-image 3D body reconstruction"
|
|
2729
|
+
},
|
|
2730
|
+
"sam3-3d-objects": {
|
|
2731
|
+
category: "3d",
|
|
2732
|
+
defaultOptions: {
|
|
2733
|
+
prompt: "car"
|
|
2734
|
+
},
|
|
2735
|
+
description: "Reconstruct one or more 3D objects from an image and prompts.",
|
|
2736
|
+
endpoint: "fal-ai/sam-3/3d-objects",
|
|
2737
|
+
inputField: "image_url",
|
|
2738
|
+
inputKind: "image",
|
|
2739
|
+
name: "SAM 3D Objects",
|
|
2740
|
+
outputKeys: [
|
|
2741
|
+
"gaussian_splat",
|
|
2742
|
+
"model_glb",
|
|
2743
|
+
"metadata",
|
|
2744
|
+
"individual_splats",
|
|
2745
|
+
"individual_glbs",
|
|
2746
|
+
"artifacts_zip"
|
|
2747
|
+
],
|
|
2748
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2749
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2750
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2751
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2752
|
+
// rather than a number that is right only on one reading.
|
|
2753
|
+
price: { kind: "metered" },
|
|
2754
|
+
pricing: "$0.02 per unit; fal does not define a unit, and this endpoint returns one mesh per detected object, so no estimate is reported",
|
|
2755
|
+
queued: true,
|
|
2756
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
|
|
2757
|
+
task: "single-image 3D object reconstruction"
|
|
2758
|
+
},
|
|
2759
|
+
"seedream-layerize": {
|
|
2760
|
+
category: "layers",
|
|
2761
|
+
description: "Split an image into editable Seedream layers plus flattened previews.",
|
|
2762
|
+
endpoint: "bytedance/seedream/v5/pro/layerize",
|
|
2763
|
+
inputField: "image_url",
|
|
2764
|
+
inputKind: "image",
|
|
2765
|
+
name: "Seedream v5 Pro Layerize",
|
|
2766
|
+
outputKeys: ["layers", "images"],
|
|
2767
|
+
price: { kind: "metered" },
|
|
2768
|
+
pricing: "$0.03375 per generated layer below 1536x1536 total area, $0.0675 per layer above; the listed rate is per layer, not per call",
|
|
2769
|
+
queued: true,
|
|
2770
|
+
sourceUrl: "https://fal.ai/models/bytedance/seedream/v5/pro/layerize",
|
|
2771
|
+
task: "image layer decomposition"
|
|
2772
|
+
},
|
|
2773
|
+
"seedvr-seamless": {
|
|
2774
|
+
category: "material",
|
|
2775
|
+
description: "Upscale a tiling texture while keeping its edges seamless. ",
|
|
2776
|
+
endpoint: "fal-ai/seedvr/upscale/image/seamless",
|
|
2777
|
+
inputField: "image_url",
|
|
2778
|
+
inputKind: "image",
|
|
2779
|
+
name: "SeedVR Seamless Upscale",
|
|
2780
|
+
outputKeys: ["image"],
|
|
2781
|
+
price: { kind: "megapixel", usd: 25e-4 },
|
|
2782
|
+
pricing: "$0.0025/megapixel",
|
|
2783
|
+
queued: true,
|
|
2784
|
+
sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image/seamless",
|
|
2785
|
+
task: "seamless texture upscaling"
|
|
2786
|
+
},
|
|
2787
|
+
"trellis-2": {
|
|
2788
|
+
category: "3d",
|
|
2789
|
+
description: "Reconstruct a textured 3D mesh from a single image, with UV unwrapping.",
|
|
2790
|
+
endpoint: "fal-ai/trellis-2",
|
|
2791
|
+
inputField: "image_url",
|
|
2792
|
+
inputKind: "image",
|
|
2793
|
+
name: "TRELLIS 2",
|
|
2794
|
+
outputKeys: ["model_glb"],
|
|
2795
|
+
price: { kind: "call", usd: 0.3 },
|
|
2796
|
+
pricing: "$0.30 at the default 1024p; $0.25 at 512p, $0.35 at 1536p",
|
|
2797
|
+
queued: true,
|
|
2798
|
+
sourceUrl: "https://fal.ai/models/fal-ai/trellis-2",
|
|
2799
|
+
task: "single-image 3D reconstruction"
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
|
|
2803
|
+
// src/tool-registry/background.ts
|
|
2804
|
+
var BACKGROUND_TOOLS = {
|
|
2805
|
+
"ben-v2": {
|
|
2806
|
+
category: "background",
|
|
2807
|
+
description: "Fast, high-quality background removal for images.",
|
|
2808
|
+
endpoint: "fal-ai/ben/v2/image",
|
|
2809
|
+
inputField: "image_url",
|
|
2810
|
+
inputKind: "image",
|
|
2811
|
+
name: "BEN v2 Background Removal",
|
|
2812
|
+
outputKeys: ["image"],
|
|
2813
|
+
price: { kind: "megapixel", usd: 0.025 },
|
|
2814
|
+
pricing: "$0.025/megapixel",
|
|
2815
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ben/v2/image",
|
|
2816
|
+
task: "image background removal"
|
|
2817
|
+
},
|
|
2354
2818
|
birefnet: {
|
|
2355
2819
|
category: "background",
|
|
2356
2820
|
defaultOptions: {
|
|
@@ -2363,41 +2827,455 @@ var FAL_TOOLS = {
|
|
|
2363
2827
|
endpoint: "fal-ai/birefnet/v2",
|
|
2364
2828
|
inputField: "image_url",
|
|
2365
2829
|
inputKind: "image",
|
|
2366
|
-
name: "BirefNet Background Removal",
|
|
2367
|
-
outputKeys: ["image", "mask_image"],
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2830
|
+
name: "BirefNet Background Removal",
|
|
2831
|
+
outputKeys: ["image", "mask_image"],
|
|
2832
|
+
price: { kind: "metered" },
|
|
2833
|
+
pricing: "$0/compute-second listed by fal",
|
|
2834
|
+
sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
|
|
2835
|
+
task: "image background removal"
|
|
2836
|
+
},
|
|
2837
|
+
"bria-rmbg": {
|
|
2838
|
+
category: "background",
|
|
2839
|
+
description: "Commercial-safe background removal for images.",
|
|
2840
|
+
endpoint: "fal-ai/bria/background/remove",
|
|
2841
|
+
inputField: "image_url",
|
|
2842
|
+
inputKind: "image",
|
|
2843
|
+
name: "Bria RMBG 2.0",
|
|
2844
|
+
outputKeys: ["image"],
|
|
2845
|
+
price: { kind: "call", usd: 0.018 },
|
|
2846
|
+
pricing: "$0.018/generation",
|
|
2847
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove",
|
|
2848
|
+
task: "image background removal"
|
|
2849
|
+
},
|
|
2850
|
+
"bria-video-rmbg": {
|
|
2851
|
+
category: "background",
|
|
2852
|
+
defaultOptions: {
|
|
2853
|
+
background_color: "Black",
|
|
2854
|
+
output_container_and_codec: "webm_vp9",
|
|
2855
|
+
preserve_audio: true
|
|
2856
|
+
},
|
|
2857
|
+
description: "Remove video backgrounds with configurable output container.",
|
|
2858
|
+
endpoint: "bria/video/background-removal",
|
|
2859
|
+
inputField: "video_url",
|
|
2860
|
+
inputKind: "video",
|
|
2861
|
+
name: "Bria Video Background Removal",
|
|
2862
|
+
outputKeys: ["video"],
|
|
2863
|
+
price: { kind: "second", usd: 425e-5 },
|
|
2864
|
+
pricing: "$0.00425/sec",
|
|
2865
|
+
queued: true,
|
|
2866
|
+
sourceUrl: "https://fal.ai/models/bria/video/background-removal",
|
|
2867
|
+
task: "video background removal"
|
|
2868
|
+
},
|
|
2869
|
+
ddcolor: {
|
|
2870
|
+
category: "restoration",
|
|
2871
|
+
description: "Colourise black-and-white photographs.",
|
|
2872
|
+
endpoint: "fal-ai/ddcolor",
|
|
2873
|
+
inputField: "image_url",
|
|
2874
|
+
inputKind: "image",
|
|
2875
|
+
name: "DDColor Colourisation",
|
|
2876
|
+
outputKeys: ["image"],
|
|
2877
|
+
price: { kind: "megapixel", usd: 1e-3 },
|
|
2878
|
+
pricing: "$0.001/megapixel",
|
|
2879
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ddcolor",
|
|
2880
|
+
task: "photo colourisation"
|
|
2881
|
+
},
|
|
2882
|
+
rembg: {
|
|
2883
|
+
category: "background",
|
|
2884
|
+
defaultOptions: {
|
|
2885
|
+
crop_to_bbox: false
|
|
2886
|
+
},
|
|
2887
|
+
description: "Generic image background removal utility.",
|
|
2888
|
+
endpoint: "fal-ai/imageutils/rembg",
|
|
2889
|
+
inputField: "image_url",
|
|
2890
|
+
inputKind: "image",
|
|
2891
|
+
name: "Remove Background",
|
|
2892
|
+
outputKeys: ["image"],
|
|
2893
|
+
price: { kind: "metered" },
|
|
2894
|
+
pricing: "$0/compute-second listed by fal",
|
|
2895
|
+
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
|
|
2896
|
+
task: "image background removal"
|
|
2897
|
+
},
|
|
2898
|
+
"seedvr-upscale": {
|
|
2899
|
+
category: "restoration",
|
|
2900
|
+
defaultOptions: {
|
|
2901
|
+
output_format: "png"
|
|
2902
|
+
},
|
|
2903
|
+
description: "Diffusion image restoration and upscaling with SeedVR.",
|
|
2904
|
+
endpoint: "fal-ai/seedvr/upscale/image",
|
|
2905
|
+
inputField: "image_url",
|
|
2906
|
+
inputKind: "image",
|
|
2907
|
+
name: "SeedVR Image Upscale",
|
|
2908
|
+
outputKeys: ["image"],
|
|
2909
|
+
price: { kind: "megapixel", usd: 1e-3 },
|
|
2910
|
+
pricing: "$0.001/megapixel",
|
|
2911
|
+
queued: true,
|
|
2912
|
+
sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image",
|
|
2913
|
+
task: "image restoration and upscaling"
|
|
2914
|
+
},
|
|
2915
|
+
"topaz-adjust": {
|
|
2916
|
+
category: "restoration",
|
|
2917
|
+
defaultOptions: {
|
|
2918
|
+
output_format: "png"
|
|
2919
|
+
},
|
|
2920
|
+
description: "Topaz exposure, white balance, and colour adjustment.",
|
|
2921
|
+
endpoint: "topaz/adjust/image",
|
|
2922
|
+
inputField: "image_url",
|
|
2923
|
+
inputKind: "image",
|
|
2924
|
+
name: "Topaz Adjust",
|
|
2925
|
+
outputKeys: ["image"],
|
|
2926
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2927
|
+
pricing: "$0.08 per 24 output megapixels",
|
|
2928
|
+
queued: true,
|
|
2929
|
+
sourceUrl: "https://fal.ai/models/topaz/adjust/image",
|
|
2930
|
+
task: "tonal adjustment"
|
|
2931
|
+
},
|
|
2932
|
+
"topaz-creative": {
|
|
2933
|
+
category: "restoration",
|
|
2934
|
+
defaultOptions: {
|
|
2935
|
+
output_format: "png"
|
|
2936
|
+
},
|
|
2937
|
+
description: "Topaz Bloom upscaling, which reimagines detail rather than preserving it.",
|
|
2938
|
+
endpoint: "topaz/upscale/image/creative",
|
|
2939
|
+
inputField: "image_url",
|
|
2940
|
+
inputKind: "image",
|
|
2941
|
+
name: "Topaz Creative Upscale",
|
|
2942
|
+
outputKeys: ["image"],
|
|
2943
|
+
price: { kind: "megapixel", usd: 0.96 / 24 },
|
|
2944
|
+
pricing: "$0.96 per 24 output megapixels",
|
|
2945
|
+
queued: true,
|
|
2946
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/creative",
|
|
2947
|
+
task: "creative image upscaling"
|
|
2948
|
+
},
|
|
2949
|
+
"topaz-denoise": {
|
|
2950
|
+
category: "restoration",
|
|
2951
|
+
defaultOptions: {
|
|
2952
|
+
output_format: "png"
|
|
2953
|
+
},
|
|
2954
|
+
description: "Topaz noise reduction at source resolution.",
|
|
2955
|
+
endpoint: "topaz/denoise/image",
|
|
2956
|
+
inputField: "image_url",
|
|
2957
|
+
inputKind: "image",
|
|
2958
|
+
name: "Topaz Denoise",
|
|
2959
|
+
outputKeys: ["image"],
|
|
2960
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2961
|
+
pricing: "$0.08 per 24 output megapixels at the default Normal model; $0.16 with Denoise Max",
|
|
2962
|
+
queued: true,
|
|
2963
|
+
sourceUrl: "https://fal.ai/models/topaz/denoise/image",
|
|
2964
|
+
task: "image denoising"
|
|
2965
|
+
},
|
|
2966
|
+
"topaz-generative": {
|
|
2967
|
+
category: "restoration",
|
|
2968
|
+
defaultOptions: {
|
|
2969
|
+
output_format: "png"
|
|
2970
|
+
},
|
|
2971
|
+
description: "Topaz Wonder upscaling, which synthesises plausible detail as it enlarges.",
|
|
2972
|
+
endpoint: "topaz/upscale/image/generative",
|
|
2973
|
+
inputField: "image_url",
|
|
2974
|
+
inputKind: "image",
|
|
2975
|
+
name: "Topaz Generative Upscale",
|
|
2976
|
+
outputKeys: ["image"],
|
|
2977
|
+
price: { kind: "megapixel", usd: 0.24 / 24 },
|
|
2978
|
+
pricing: "$0.24 per 24 output megapixels at the default Wonder 3; $0.48 with Wonder, Wonder 2, Standard MAX, Redefine or Recover 3",
|
|
2979
|
+
queued: true,
|
|
2980
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/generative",
|
|
2981
|
+
task: "generative image upscaling"
|
|
2982
|
+
},
|
|
2983
|
+
"topaz-image": {
|
|
2984
|
+
category: "upscale",
|
|
2985
|
+
defaultOptions: {
|
|
2986
|
+
model: "Standard V2",
|
|
2987
|
+
output_format: "jpeg",
|
|
2988
|
+
upscale_factor: 2
|
|
2989
|
+
},
|
|
2990
|
+
description: "Professional Topaz image enhancement and upscaling.",
|
|
2991
|
+
endpoint: "fal-ai/topaz/upscale/image",
|
|
2992
|
+
inputField: "image_url",
|
|
2993
|
+
inputKind: "image",
|
|
2994
|
+
name: "Topaz Image Upscale",
|
|
2995
|
+
outputKeys: ["image"],
|
|
2996
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2997
|
+
pricing: "$0.08 for output up to 24MP; $0.16 to 48MP, $0.32 to 96MP, up to $1.36 at 512MP",
|
|
2998
|
+
queued: true,
|
|
2999
|
+
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
|
|
3000
|
+
task: "image enhancement"
|
|
3001
|
+
},
|
|
3002
|
+
"topaz-precision": {
|
|
3003
|
+
category: "restoration",
|
|
3004
|
+
defaultOptions: {
|
|
3005
|
+
output_format: "png"
|
|
3006
|
+
},
|
|
3007
|
+
description: "Topaz upscaling that preserves the original detail rather than inventing it.",
|
|
3008
|
+
endpoint: "topaz/upscale/image/precision",
|
|
3009
|
+
inputField: "image_url",
|
|
3010
|
+
inputKind: "image",
|
|
3011
|
+
name: "Topaz Precision Upscale",
|
|
3012
|
+
outputKeys: ["image"],
|
|
3013
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
3014
|
+
pricing: "$0.08 per 24 output megapixels, any precision model",
|
|
3015
|
+
queued: true,
|
|
3016
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/precision",
|
|
3017
|
+
task: "faithful image upscaling"
|
|
3018
|
+
},
|
|
3019
|
+
"topaz-restore": {
|
|
3020
|
+
category: "restoration",
|
|
3021
|
+
defaultOptions: {
|
|
3022
|
+
output_format: "png"
|
|
3023
|
+
},
|
|
3024
|
+
description: "Topaz restoration of damaged or degraded photographs at source resolution.",
|
|
3025
|
+
endpoint: "topaz/restore/image",
|
|
3026
|
+
inputField: "image_url",
|
|
3027
|
+
inputKind: "image",
|
|
3028
|
+
name: "Topaz Restore",
|
|
3029
|
+
outputKeys: ["image"],
|
|
3030
|
+
price: { kind: "megapixel", usd: 0.48 / 24 },
|
|
3031
|
+
pricing: "$0.48 per 24 output megapixels at the default Recover 3; $0.08 with Dust-Scratch V2",
|
|
3032
|
+
queued: true,
|
|
3033
|
+
sourceUrl: "https://fal.ai/models/topaz/restore/image",
|
|
3034
|
+
task: "photo restoration"
|
|
3035
|
+
},
|
|
3036
|
+
"topaz-sharpen": {
|
|
3037
|
+
category: "restoration",
|
|
3038
|
+
defaultOptions: {
|
|
3039
|
+
output_format: "png"
|
|
3040
|
+
},
|
|
3041
|
+
description: "Topaz deblurring and sharpening at source resolution.",
|
|
3042
|
+
endpoint: "topaz/sharpen/image",
|
|
3043
|
+
inputField: "image_url",
|
|
3044
|
+
inputKind: "image",
|
|
3045
|
+
name: "Topaz Sharpen",
|
|
3046
|
+
outputKeys: ["image"],
|
|
3047
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
3048
|
+
pricing: "$0.08 per 24 output megapixels at the default Standard model; $0.16 with Super Focus",
|
|
3049
|
+
queued: true,
|
|
3050
|
+
sourceUrl: "https://fal.ai/models/topaz/sharpen/image",
|
|
3051
|
+
task: "image sharpening"
|
|
3052
|
+
},
|
|
3053
|
+
"topaz-transparent": {
|
|
3054
|
+
category: "restoration",
|
|
3055
|
+
description: "Topaz upscaling that preserves the alpha channel end to end.",
|
|
3056
|
+
endpoint: "topaz/upscale/image/transparent",
|
|
3057
|
+
inputField: "image_url",
|
|
3058
|
+
inputKind: "image",
|
|
3059
|
+
name: "Topaz Transparent Upscale",
|
|
3060
|
+
outputKeys: ["image"],
|
|
3061
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
3062
|
+
pricing: "$0.08 per 24 output megapixels",
|
|
3063
|
+
queued: true,
|
|
3064
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/transparent",
|
|
3065
|
+
task: "transparent image upscaling"
|
|
3066
|
+
},
|
|
3067
|
+
"topaz-video": {
|
|
3068
|
+
category: "upscale",
|
|
3069
|
+
defaultOptions: {
|
|
3070
|
+
model: "Proteus",
|
|
3071
|
+
upscale_factor: 2
|
|
3072
|
+
},
|
|
3073
|
+
description: "Professional Topaz video enhancement and upscaling.",
|
|
3074
|
+
endpoint: "fal-ai/topaz/upscale/video",
|
|
3075
|
+
inputField: "video_url",
|
|
3076
|
+
inputKind: "video",
|
|
3077
|
+
name: "Topaz Video Upscale",
|
|
3078
|
+
outputKeys: ["video"],
|
|
3079
|
+
price: { kind: "second", usd: 0.01 },
|
|
3080
|
+
pricing: "$0.01/sec up to 720p; $0.02 to 1080p, $0.08 above, doubled at 60fps, halved with Gaia 2",
|
|
3081
|
+
queued: true,
|
|
3082
|
+
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
|
|
3083
|
+
task: "video enhancement"
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
|
|
3087
|
+
// src/tool-registry/editing.ts
|
|
3088
|
+
var EDITING_TOOLS = {
|
|
3089
|
+
"bria-eraser": {
|
|
3090
|
+
category: "erase",
|
|
3091
|
+
description: "Commercial-safe removal of a masked region, inpainted from surrounding context.",
|
|
3092
|
+
endpoint: "fal-ai/bria/eraser",
|
|
3093
|
+
inputField: "image_url",
|
|
3094
|
+
inputKind: "image",
|
|
3095
|
+
name: "Bria Eraser",
|
|
3096
|
+
outputKeys: ["image"],
|
|
3097
|
+
price: { kind: "call", usd: 0.04 },
|
|
3098
|
+
pricing: "$0.04/generation",
|
|
3099
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/eraser",
|
|
3100
|
+
task: "masked object erasure"
|
|
3101
|
+
},
|
|
3102
|
+
"bria-expand": {
|
|
3103
|
+
category: "reframe",
|
|
3104
|
+
description: "Expand an image onto a larger canvas, generating the new border area.",
|
|
3105
|
+
endpoint: "fal-ai/bria/expand",
|
|
3106
|
+
inputField: "image_url",
|
|
3107
|
+
inputKind: "image",
|
|
3108
|
+
name: "Bria Image Expansion",
|
|
3109
|
+
outputKeys: ["image"],
|
|
3110
|
+
price: { kind: "call", usd: 0.04 },
|
|
3111
|
+
pricing: "$0.04/generation",
|
|
3112
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/expand",
|
|
3113
|
+
task: "image outpainting"
|
|
3114
|
+
},
|
|
3115
|
+
"bria-genfill": {
|
|
3116
|
+
category: "erase",
|
|
3117
|
+
description: "Generative fill of a masked region from a prompt, commercial-safe.",
|
|
3118
|
+
endpoint: "fal-ai/bria/genfill",
|
|
3119
|
+
inputField: "image_url",
|
|
3120
|
+
inputKind: "image",
|
|
3121
|
+
name: "Bria GenFill",
|
|
3122
|
+
outputKeys: ["images"],
|
|
3123
|
+
price: { kind: "call", usd: 0.04 },
|
|
3124
|
+
pricing: "$0.04/generation",
|
|
3125
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/genfill",
|
|
3126
|
+
task: "masked generative fill"
|
|
3127
|
+
},
|
|
3128
|
+
"finegrain-eraser": {
|
|
3129
|
+
category: "erase",
|
|
3130
|
+
description: "Remove a prompted object and its shadows and reflections, filling the gap.",
|
|
3131
|
+
endpoint: "fal-ai/finegrain-eraser",
|
|
3132
|
+
inputField: "image_url",
|
|
3133
|
+
inputKind: "image",
|
|
3134
|
+
name: "Finegrain Object Eraser",
|
|
3135
|
+
outputKeys: ["image"],
|
|
3136
|
+
price: { kind: "call", usd: 0.27 },
|
|
3137
|
+
pricing: "$0.27/image at the default standard mode; $0.18 express, $0.36 premium",
|
|
3138
|
+
sourceUrl: "https://fal.ai/models/fal-ai/finegrain-eraser",
|
|
3139
|
+
task: "prompted object erasure"
|
|
3140
|
+
},
|
|
3141
|
+
"flux-outpaint": {
|
|
3142
|
+
category: "reframe",
|
|
3143
|
+
description: "Outpaint an image by a per-edge pixel expansion with FLUX.2 pro.",
|
|
3144
|
+
endpoint: "fal-ai/flux-2-pro/outpaint",
|
|
3145
|
+
inputField: "image_url",
|
|
3146
|
+
inputKind: "image",
|
|
3147
|
+
name: "FLUX.2 Pro Outpaint",
|
|
3148
|
+
outputKeys: ["images"],
|
|
3149
|
+
price: { kind: "metered" },
|
|
3150
|
+
pricing: "$0.03 for the first output megapixel, then $0.015 per extra megapixel of input and output, rounded up",
|
|
3151
|
+
sourceUrl: "https://fal.ai/models/fal-ai/flux-2-pro/outpaint",
|
|
3152
|
+
task: "image outpainting"
|
|
3153
|
+
},
|
|
3154
|
+
"iclight-v2": {
|
|
3155
|
+
category: "relight",
|
|
3156
|
+
description: "Relight a subject from a prompt, harmonising it with a new light direction.",
|
|
3157
|
+
endpoint: "fal-ai/iclight-v2",
|
|
3158
|
+
inputField: "image_url",
|
|
3159
|
+
inputKind: "image",
|
|
3160
|
+
name: "IC-Light v2",
|
|
3161
|
+
outputKeys: ["images"],
|
|
3162
|
+
price: { kind: "megapixel", usd: 0.1 },
|
|
3163
|
+
pricing: "$0.10/megapixel",
|
|
3164
|
+
sourceUrl: "https://fal.ai/models/fal-ai/iclight-v2",
|
|
3165
|
+
task: "prompted relighting"
|
|
3166
|
+
},
|
|
3167
|
+
"ideogram-reframe": {
|
|
3168
|
+
category: "reframe",
|
|
3169
|
+
description: "Reframe an image to a new aspect ratio, generating the fill.",
|
|
3170
|
+
endpoint: "fal-ai/ideogram/v3/reframe",
|
|
3171
|
+
inputField: "image_url",
|
|
3172
|
+
inputKind: "image",
|
|
3173
|
+
name: "Ideogram v3 Reframe",
|
|
3174
|
+
outputKeys: ["images"],
|
|
3175
|
+
price: { kind: "call", usd: 0.06 },
|
|
3176
|
+
pricing: "$0.06/image at the default BALANCED speed; $0.03 turbo, $0.09 quality",
|
|
3177
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/reframe",
|
|
3178
|
+
task: "aspect ratio reframing"
|
|
3179
|
+
},
|
|
3180
|
+
"lighting-restoration": {
|
|
3181
|
+
category: "relight",
|
|
3182
|
+
description: "Restore natural, even lighting across one or more input images.",
|
|
3183
|
+
endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
|
|
3184
|
+
inputField: "image_urls",
|
|
3185
|
+
inputKind: "images",
|
|
3186
|
+
name: "Qwen Lighting Restoration",
|
|
3187
|
+
outputKeys: ["images"],
|
|
3188
|
+
price: { kind: "megapixel", usd: 0.035 },
|
|
3189
|
+
pricing: "$0.035/megapixel",
|
|
3190
|
+
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
|
|
3191
|
+
task: "lighting restoration"
|
|
3192
|
+
},
|
|
3193
|
+
"object-removal": {
|
|
3194
|
+
category: "erase",
|
|
3195
|
+
description: "Remove a prompted object from an image and fill the gap.",
|
|
3196
|
+
endpoint: "fal-ai/object-removal",
|
|
3197
|
+
inputField: "image_url",
|
|
3198
|
+
inputKind: "image",
|
|
3199
|
+
name: "Object Removal",
|
|
3200
|
+
outputKeys: ["images"],
|
|
3201
|
+
price: { kind: "call", usd: 0.024 },
|
|
3202
|
+
pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
|
|
3203
|
+
sourceUrl: "https://fal.ai/models/fal-ai/object-removal",
|
|
3204
|
+
task: "prompted object removal"
|
|
2371
3205
|
},
|
|
2372
|
-
"
|
|
2373
|
-
category: "
|
|
2374
|
-
description: "
|
|
2375
|
-
endpoint: "fal-ai/
|
|
3206
|
+
"object-removal-bbox": {
|
|
3207
|
+
category: "erase",
|
|
3208
|
+
description: "Remove whatever falls inside supplied bounding boxes.",
|
|
3209
|
+
endpoint: "fal-ai/object-removal/bbox",
|
|
2376
3210
|
inputField: "image_url",
|
|
2377
3211
|
inputKind: "image",
|
|
2378
|
-
name: "
|
|
2379
|
-
outputKeys: ["
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
3212
|
+
name: "Object Removal by Box",
|
|
3213
|
+
outputKeys: ["images"],
|
|
3214
|
+
price: { kind: "call", usd: 0.024 },
|
|
3215
|
+
pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
|
|
3216
|
+
sourceUrl: "https://fal.ai/models/fal-ai/object-removal/bbox",
|
|
3217
|
+
task: "bounding box object removal"
|
|
2383
3218
|
},
|
|
2384
|
-
"
|
|
2385
|
-
category: "
|
|
3219
|
+
"object-removal-mask": {
|
|
3220
|
+
category: "erase",
|
|
3221
|
+
description: "Remove the masked region of an image and fill the gap.",
|
|
3222
|
+
endpoint: "fal-ai/object-removal/mask",
|
|
3223
|
+
inputField: "image_url",
|
|
3224
|
+
inputKind: "image",
|
|
3225
|
+
name: "Object Removal by Mask",
|
|
3226
|
+
outputKeys: ["images"],
|
|
3227
|
+
price: { kind: "call", usd: 0.024 },
|
|
3228
|
+
pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
|
|
3229
|
+
sourceUrl: "https://fal.ai/models/fal-ai/object-removal/mask",
|
|
3230
|
+
task: "masked object removal"
|
|
3231
|
+
},
|
|
3232
|
+
"remove-lighting": {
|
|
3233
|
+
category: "relight",
|
|
3234
|
+
description: "Strip baked-in lighting and shadows to leave a flat, neutral surface.",
|
|
3235
|
+
endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
|
|
3236
|
+
inputField: "image_urls",
|
|
3237
|
+
inputKind: "images",
|
|
3238
|
+
name: "Qwen Remove Lighting",
|
|
3239
|
+
outputKeys: ["images"],
|
|
3240
|
+
price: { kind: "megapixel", usd: 0.035 },
|
|
3241
|
+
pricing: "$0.035/megapixel",
|
|
3242
|
+
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
|
|
3243
|
+
task: "lighting removal"
|
|
3244
|
+
},
|
|
3245
|
+
"smart-resize": {
|
|
3246
|
+
category: "reframe",
|
|
3247
|
+
description: "Resize one image to several target sizes, recomposing rather than cropping.",
|
|
3248
|
+
endpoint: "fal-ai/smart-resize",
|
|
3249
|
+
inputField: "image_url",
|
|
3250
|
+
inputKind: "image",
|
|
3251
|
+
name: "Smart Resize",
|
|
3252
|
+
outputKeys: ["images", "results"],
|
|
3253
|
+
price: { kind: "metered" },
|
|
3254
|
+
pricing: "$0.15 per output image, doubled at 4K, plus a $0.05 vision analysis fee per request",
|
|
3255
|
+
queued: true,
|
|
3256
|
+
sourceUrl: "https://fal.ai/models/fal-ai/smart-resize",
|
|
3257
|
+
task: "multi-size recomposition"
|
|
3258
|
+
},
|
|
3259
|
+
"text-removal": {
|
|
3260
|
+
category: "erase",
|
|
2386
3261
|
defaultOptions: {
|
|
2387
|
-
|
|
2388
|
-
output_container_and_codec: "webm_vp9",
|
|
2389
|
-
preserve_audio: true
|
|
3262
|
+
output_format: "png"
|
|
2390
3263
|
},
|
|
2391
|
-
description: "Remove
|
|
2392
|
-
endpoint: "
|
|
2393
|
-
inputField: "
|
|
2394
|
-
inputKind: "
|
|
2395
|
-
name: "
|
|
2396
|
-
outputKeys: ["
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
3264
|
+
description: "Remove all rendered text from an image, rebuilding what sat behind it.",
|
|
3265
|
+
endpoint: "fal-ai/image-editing/text-removal",
|
|
3266
|
+
inputField: "image_url",
|
|
3267
|
+
inputKind: "image",
|
|
3268
|
+
name: "Text Removal",
|
|
3269
|
+
outputKeys: ["images"],
|
|
3270
|
+
price: { kind: "call", usd: 0.04 },
|
|
3271
|
+
pricing: "$0.04/image",
|
|
3272
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-editing/text-removal",
|
|
3273
|
+
task: "text removal"
|
|
3274
|
+
}
|
|
3275
|
+
};
|
|
3276
|
+
|
|
3277
|
+
// src/tool-registry/structure.ts
|
|
3278
|
+
var STRUCTURE_TOOLS = {
|
|
2401
3279
|
"depth-anything": {
|
|
2402
3280
|
category: "preprocess",
|
|
2403
3281
|
description: "Generate Depth Anything v2 depth maps from input images.",
|
|
@@ -2406,10 +3284,37 @@ var FAL_TOOLS = {
|
|
|
2406
3284
|
inputKind: "image",
|
|
2407
3285
|
name: "Depth Anything v2 Preprocessor",
|
|
2408
3286
|
outputKeys: ["image"],
|
|
3287
|
+
price: { kind: "metered" },
|
|
2409
3288
|
pricing: "$0/compute-second listed by fal",
|
|
2410
3289
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
|
|
2411
3290
|
task: "depth preprocessing"
|
|
2412
3291
|
},
|
|
3292
|
+
dwpose: {
|
|
3293
|
+
category: "preprocess",
|
|
3294
|
+
description: "Extract body, hand, and face pose skeletons from an image.",
|
|
3295
|
+
endpoint: "fal-ai/dwpose",
|
|
3296
|
+
inputField: "image_url",
|
|
3297
|
+
inputKind: "image",
|
|
3298
|
+
name: "DWPose Estimation",
|
|
3299
|
+
outputKeys: ["image"],
|
|
3300
|
+
price: { kind: "second", usd: 6e-4 },
|
|
3301
|
+
pricing: "$0.0006/compute-second",
|
|
3302
|
+
sourceUrl: "https://fal.ai/models/fal-ai/dwpose",
|
|
3303
|
+
task: "pose preprocessing"
|
|
3304
|
+
},
|
|
3305
|
+
hed: {
|
|
3306
|
+
category: "preprocess",
|
|
3307
|
+
description: "Generate HED soft edge maps for ControlNet-style workflows.",
|
|
3308
|
+
endpoint: "fal-ai/image-preprocessors/hed",
|
|
3309
|
+
inputField: "image_url",
|
|
3310
|
+
inputKind: "image",
|
|
3311
|
+
name: "HED Edge Preprocessor",
|
|
3312
|
+
outputKeys: ["image"],
|
|
3313
|
+
price: { kind: "metered" },
|
|
3314
|
+
pricing: "$0/compute-second listed by fal",
|
|
3315
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/hed",
|
|
3316
|
+
task: "edge preprocessing"
|
|
3317
|
+
},
|
|
2413
3318
|
lineart: {
|
|
2414
3319
|
category: "preprocess",
|
|
2415
3320
|
defaultOptions: {
|
|
@@ -2421,6 +3326,7 @@ var FAL_TOOLS = {
|
|
|
2421
3326
|
inputKind: "image",
|
|
2422
3327
|
name: "Line Art Preprocessor",
|
|
2423
3328
|
outputKeys: ["image"],
|
|
3329
|
+
price: { kind: "metered" },
|
|
2424
3330
|
pricing: "$0/compute-second listed by fal",
|
|
2425
3331
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
|
|
2426
3332
|
task: "image preprocessing"
|
|
@@ -2437,6 +3343,7 @@ var FAL_TOOLS = {
|
|
|
2437
3343
|
inputKind: "image",
|
|
2438
3344
|
name: "Marigold Depth Estimation",
|
|
2439
3345
|
outputKeys: ["image"],
|
|
3346
|
+
price: { kind: "metered" },
|
|
2440
3347
|
pricing: "$0/compute-second listed by fal",
|
|
2441
3348
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
|
|
2442
3349
|
task: "depth map"
|
|
@@ -2453,6 +3360,7 @@ var FAL_TOOLS = {
|
|
|
2453
3360
|
inputKind: "image",
|
|
2454
3361
|
name: "MiDaS Depth Estimation",
|
|
2455
3362
|
outputKeys: ["image"],
|
|
3363
|
+
price: { kind: "metered" },
|
|
2456
3364
|
pricing: "$0/compute-second listed by fal",
|
|
2457
3365
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
|
|
2458
3366
|
task: "depth map"
|
|
@@ -2465,36 +3373,36 @@ var FAL_TOOLS = {
|
|
|
2465
3373
|
inputKind: "image",
|
|
2466
3374
|
name: "MiDaS Preprocessor",
|
|
2467
3375
|
outputKeys: ["depth_map", "normal_map"],
|
|
3376
|
+
price: { kind: "metered" },
|
|
2468
3377
|
pricing: "$0/compute-second listed by fal",
|
|
2469
3378
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
|
|
2470
3379
|
task: "depth and normal preprocessing"
|
|
2471
3380
|
},
|
|
2472
|
-
|
|
2473
|
-
category: "
|
|
2474
|
-
description: "
|
|
2475
|
-
endpoint: "fal-ai/
|
|
2476
|
-
inputField: "
|
|
2477
|
-
inputKind: "
|
|
2478
|
-
name: "
|
|
2479
|
-
outputKeys: ["
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
3381
|
+
mlsd: {
|
|
3382
|
+
category: "preprocess",
|
|
3383
|
+
description: "Detect straight line segments for architectural and interior control maps.",
|
|
3384
|
+
endpoint: "fal-ai/image-preprocessors/mlsd",
|
|
3385
|
+
inputField: "image_url",
|
|
3386
|
+
inputKind: "image",
|
|
3387
|
+
name: "M-LSD Line Preprocessor",
|
|
3388
|
+
outputKeys: ["image"],
|
|
3389
|
+
price: { kind: "metered" },
|
|
3390
|
+
pricing: "$0/compute-second listed by fal",
|
|
3391
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/mlsd",
|
|
3392
|
+
task: "line segment preprocessing"
|
|
2483
3393
|
},
|
|
2484
|
-
|
|
2485
|
-
category: "
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
},
|
|
2489
|
-
description: "Generic image background removal utility.",
|
|
2490
|
-
endpoint: "fal-ai/imageutils/rembg",
|
|
3394
|
+
pidi: {
|
|
3395
|
+
category: "preprocess",
|
|
3396
|
+
description: "Generate PiDiNet soft edge maps from an input image.",
|
|
3397
|
+
endpoint: "fal-ai/image-preprocessors/pidi",
|
|
2491
3398
|
inputField: "image_url",
|
|
2492
3399
|
inputKind: "image",
|
|
2493
|
-
name: "
|
|
3400
|
+
name: "PiDiNet Edge Preprocessor",
|
|
2494
3401
|
outputKeys: ["image"],
|
|
3402
|
+
price: { kind: "metered" },
|
|
2495
3403
|
pricing: "$0/compute-second listed by fal",
|
|
2496
|
-
sourceUrl: "https://fal.ai/models/fal-ai/
|
|
2497
|
-
task: "
|
|
3404
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/pidi",
|
|
3405
|
+
task: "edge preprocessing"
|
|
2498
3406
|
},
|
|
2499
3407
|
"sam-preprocessor": {
|
|
2500
3408
|
category: "preprocess",
|
|
@@ -2504,6 +3412,7 @@ var FAL_TOOLS = {
|
|
|
2504
3412
|
inputKind: "image",
|
|
2505
3413
|
name: "SAM Preprocessor",
|
|
2506
3414
|
outputKeys: ["image"],
|
|
3415
|
+
price: { kind: "metered" },
|
|
2507
3416
|
pricing: "$0/compute-second listed by fal",
|
|
2508
3417
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
|
|
2509
3418
|
task: "segmentation preprocessing"
|
|
@@ -2523,7 +3432,9 @@ var FAL_TOOLS = {
|
|
|
2523
3432
|
inputKind: "image",
|
|
2524
3433
|
name: "SAM 2 Auto Segment",
|
|
2525
3434
|
outputKeys: ["combined_mask", "individual_masks"],
|
|
3435
|
+
price: { kind: "metered" },
|
|
2526
3436
|
pricing: "$0/compute-second listed by fal",
|
|
3437
|
+
queued: true,
|
|
2527
3438
|
sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
|
|
2528
3439
|
task: "automatic image segmentation"
|
|
2529
3440
|
},
|
|
@@ -2540,7 +3451,8 @@ var FAL_TOOLS = {
|
|
|
2540
3451
|
inputKind: "image",
|
|
2541
3452
|
name: "SAM 3.1 Image",
|
|
2542
3453
|
outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
|
|
2543
|
-
|
|
3454
|
+
price: { kind: "call", usd: 0.01 },
|
|
3455
|
+
pricing: "$0.01/request",
|
|
2544
3456
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
|
|
2545
3457
|
task: "promptable image segmentation"
|
|
2546
3458
|
},
|
|
@@ -2556,61 +3468,12 @@ var FAL_TOOLS = {
|
|
|
2556
3468
|
inputKind: "video",
|
|
2557
3469
|
name: "SAM 3.1 Video",
|
|
2558
3470
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2559
|
-
|
|
3471
|
+
price: { kind: "metered" },
|
|
3472
|
+
pricing: "$0.01/16 frames of video input",
|
|
3473
|
+
queued: true,
|
|
2560
3474
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
|
|
2561
3475
|
task: "multi-object video segmentation"
|
|
2562
3476
|
},
|
|
2563
|
-
"sam3-3d-align": {
|
|
2564
|
-
category: "3d",
|
|
2565
|
-
description: "Align SAM 3D objects and bodies into a shared scene.",
|
|
2566
|
-
endpoint: "fal-ai/sam-3/3d-align",
|
|
2567
|
-
inputField: "image_url",
|
|
2568
|
-
inputKind: "image",
|
|
2569
|
-
name: "SAM 3D Align",
|
|
2570
|
-
outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
|
|
2571
|
-
pricing: "fal pricing varies by scene",
|
|
2572
|
-
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
|
|
2573
|
-
task: "3D scene alignment"
|
|
2574
|
-
},
|
|
2575
|
-
"sam3-3d-body": {
|
|
2576
|
-
category: "3d",
|
|
2577
|
-
defaultOptions: {
|
|
2578
|
-
export_meshes: true,
|
|
2579
|
-
include_3d_keypoints: true,
|
|
2580
|
-
include_mhr_params: true
|
|
2581
|
-
},
|
|
2582
|
-
description: "Reconstruct human body meshes and keypoints from a single image.",
|
|
2583
|
-
endpoint: "fal-ai/sam-3/3d-body",
|
|
2584
|
-
inputField: "image_url",
|
|
2585
|
-
inputKind: "image",
|
|
2586
|
-
name: "SAM 3D Body",
|
|
2587
|
-
outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
|
|
2588
|
-
pricing: "$0.015/inference",
|
|
2589
|
-
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
|
|
2590
|
-
task: "single-image 3D body reconstruction"
|
|
2591
|
-
},
|
|
2592
|
-
"sam3-3d-objects": {
|
|
2593
|
-
category: "3d",
|
|
2594
|
-
defaultOptions: {
|
|
2595
|
-
prompt: "car"
|
|
2596
|
-
},
|
|
2597
|
-
description: "Reconstruct one or more 3D objects from an image and prompts.",
|
|
2598
|
-
endpoint: "fal-ai/sam-3/3d-objects",
|
|
2599
|
-
inputField: "image_url",
|
|
2600
|
-
inputKind: "image",
|
|
2601
|
-
name: "SAM 3D Objects",
|
|
2602
|
-
outputKeys: [
|
|
2603
|
-
"gaussian_splat",
|
|
2604
|
-
"model_glb",
|
|
2605
|
-
"metadata",
|
|
2606
|
-
"individual_splats",
|
|
2607
|
-
"individual_glbs",
|
|
2608
|
-
"artifacts_zip"
|
|
2609
|
-
],
|
|
2610
|
-
pricing: "$0.02/generation",
|
|
2611
|
-
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
|
|
2612
|
-
task: "single-image 3D object reconstruction"
|
|
2613
|
-
},
|
|
2614
3477
|
"sam3-image": {
|
|
2615
3478
|
category: "segmentation",
|
|
2616
3479
|
defaultOptions: {
|
|
@@ -2624,6 +3487,7 @@ var FAL_TOOLS = {
|
|
|
2624
3487
|
inputKind: "image",
|
|
2625
3488
|
name: "SAM 3 Image",
|
|
2626
3489
|
outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
|
|
3490
|
+
price: { kind: "call", usd: 5e-3 },
|
|
2627
3491
|
pricing: "$0.005/request",
|
|
2628
3492
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
|
|
2629
3493
|
task: "promptable image segmentation"
|
|
@@ -2639,7 +3503,8 @@ var FAL_TOOLS = {
|
|
|
2639
3503
|
inputField: "image_url",
|
|
2640
3504
|
inputKind: "image",
|
|
2641
3505
|
name: "SAM 3 Image RLE",
|
|
2642
|
-
outputKeys: ["
|
|
3506
|
+
outputKeys: ["rle", "scores", "boxes", "metadata"],
|
|
3507
|
+
price: { kind: "call", usd: 5e-3 },
|
|
2643
3508
|
pricing: "$0.005/request",
|
|
2644
3509
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
|
|
2645
3510
|
task: "promptable image segmentation to RLE"
|
|
@@ -2658,7 +3523,9 @@ var FAL_TOOLS = {
|
|
|
2658
3523
|
inputKind: "video",
|
|
2659
3524
|
name: "SAM 3 Video",
|
|
2660
3525
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2661
|
-
|
|
3526
|
+
price: { kind: "metered" },
|
|
3527
|
+
pricing: "$0.005/16 frames of video input",
|
|
3528
|
+
queued: true,
|
|
2662
3529
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
|
|
2663
3530
|
task: "promptable video segmentation"
|
|
2664
3531
|
},
|
|
@@ -2674,45 +3541,65 @@ var FAL_TOOLS = {
|
|
|
2674
3541
|
inputField: "video_url",
|
|
2675
3542
|
inputKind: "video",
|
|
2676
3543
|
name: "SAM 3 Video RLE",
|
|
2677
|
-
|
|
2678
|
-
|
|
3544
|
+
// fal's video-rle endpoint no longer returns RLE data: its Output schema is
|
|
3545
|
+
// now identical to sam3-video's, so this entry differs only by endpoint.
|
|
3546
|
+
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
3547
|
+
price: { kind: "metered" },
|
|
3548
|
+
pricing: "$0.005/16 frames of video",
|
|
3549
|
+
queued: true,
|
|
2679
3550
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
|
|
2680
3551
|
task: "promptable video segmentation to RLE"
|
|
2681
3552
|
},
|
|
2682
|
-
|
|
2683
|
-
category: "
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
output_format: "jpeg",
|
|
2687
|
-
upscale_factor: 2
|
|
2688
|
-
},
|
|
2689
|
-
description: "Professional Topaz image enhancement and upscaling.",
|
|
2690
|
-
endpoint: "fal-ai/topaz/upscale/image",
|
|
3553
|
+
scribble: {
|
|
3554
|
+
category: "preprocess",
|
|
3555
|
+
description: "Turn an image into a scribble-style control map.",
|
|
3556
|
+
endpoint: "fal-ai/image-preprocessors/scribble",
|
|
2691
3557
|
inputField: "image_url",
|
|
2692
3558
|
inputKind: "image",
|
|
2693
|
-
name: "
|
|
3559
|
+
name: "Scribble Preprocessor",
|
|
2694
3560
|
outputKeys: ["image"],
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
3561
|
+
price: { kind: "metered" },
|
|
3562
|
+
pricing: "$0/compute-second listed by fal",
|
|
3563
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/scribble",
|
|
3564
|
+
task: "scribble preprocessing"
|
|
2698
3565
|
},
|
|
2699
|
-
|
|
2700
|
-
category: "
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
3566
|
+
teed: {
|
|
3567
|
+
category: "preprocess",
|
|
3568
|
+
description: "Generate TEED edge maps from an input image.",
|
|
3569
|
+
endpoint: "fal-ai/image-preprocessors/teed",
|
|
3570
|
+
inputField: "image_url",
|
|
3571
|
+
inputKind: "image",
|
|
3572
|
+
name: "TEED Edge Preprocessor",
|
|
3573
|
+
outputKeys: ["image"],
|
|
3574
|
+
price: { kind: "metered" },
|
|
3575
|
+
pricing: "$0/compute-second listed by fal",
|
|
3576
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/teed",
|
|
3577
|
+
task: "edge preprocessing"
|
|
3578
|
+
},
|
|
3579
|
+
"zoe-depth": {
|
|
3580
|
+
category: "preprocess",
|
|
3581
|
+
description: "Generate ZoeDepth metric depth maps from an input image.",
|
|
3582
|
+
endpoint: "fal-ai/image-preprocessors/zoe",
|
|
3583
|
+
inputField: "image_url",
|
|
3584
|
+
inputKind: "image",
|
|
3585
|
+
name: "ZoeDepth Preprocessor",
|
|
3586
|
+
outputKeys: ["image"],
|
|
3587
|
+
price: { kind: "metered" },
|
|
3588
|
+
pricing: "$0/compute-second listed by fal",
|
|
3589
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/zoe",
|
|
3590
|
+
task: "depth preprocessing"
|
|
2714
3591
|
}
|
|
2715
3592
|
};
|
|
3593
|
+
|
|
3594
|
+
// src/tools.ts
|
|
3595
|
+
var FAL_EXPLORE_CHECKED_AT = "2026-08-23";
|
|
3596
|
+
var FAL_TOOLS = {
|
|
3597
|
+
...ANALYSIS_TOOLS,
|
|
3598
|
+
...ASSET_TOOLS,
|
|
3599
|
+
...BACKGROUND_TOOLS,
|
|
3600
|
+
...EDITING_TOOLS,
|
|
3601
|
+
...STRUCTURE_TOOLS
|
|
3602
|
+
};
|
|
2716
3603
|
var FAL_TOOLS_CHECKED_AT = FAL_EXPLORE_CHECKED_AT;
|
|
2717
3604
|
var FAL_TOOL_IDS = Object.keys(
|
|
2718
3605
|
FAL_TOOLS
|
|
@@ -2739,9 +3626,110 @@ function buildFalToolRequest(options) {
|
|
|
2739
3626
|
return { body, endpoint: tool.endpoint, tool };
|
|
2740
3627
|
}
|
|
2741
3628
|
|
|
2742
|
-
// src/server.ts
|
|
3629
|
+
// src/server-tools.ts
|
|
2743
3630
|
var FAL_BASE_URL = "https://fal.run";
|
|
2744
3631
|
var FAL_QUEUE_URL = "https://queue.fal.run";
|
|
3632
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
3633
|
+
var MAX_POLL_ATTEMPTS = 160;
|
|
3634
|
+
function buildToolRequest(options) {
|
|
3635
|
+
try {
|
|
3636
|
+
return (0, import_neverthrow.ok)(buildFalToolRequest(options));
|
|
3637
|
+
} catch (error) {
|
|
3638
|
+
return (0, import_neverthrow.err)(
|
|
3639
|
+
new MotifError(error instanceof Error ? error.message : String(error), 0)
|
|
3640
|
+
);
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
async function runTool(exec, options) {
|
|
3644
|
+
const request = buildToolRequest(options);
|
|
3645
|
+
if (request.isErr()) {
|
|
3646
|
+
return (0, import_neverthrow.err)(request.error);
|
|
3647
|
+
}
|
|
3648
|
+
const response = await exec.request(
|
|
3649
|
+
`${FAL_BASE_URL}/${request.value.endpoint}`,
|
|
3650
|
+
{
|
|
3651
|
+
body: JSON.stringify(request.value.body),
|
|
3652
|
+
method: "POST"
|
|
3653
|
+
}
|
|
3654
|
+
);
|
|
3655
|
+
if (response.isErr()) {
|
|
3656
|
+
return (0, import_neverthrow.err)(response.error);
|
|
3657
|
+
}
|
|
3658
|
+
const data = await response.value.json();
|
|
3659
|
+
return (0, import_neverthrow.ok)(isRecord(data) ? data : {});
|
|
3660
|
+
}
|
|
3661
|
+
async function submitTool(exec, options) {
|
|
3662
|
+
const request = buildToolRequest(options);
|
|
3663
|
+
if (request.isErr()) {
|
|
3664
|
+
return (0, import_neverthrow.err)(request.error);
|
|
3665
|
+
}
|
|
3666
|
+
const response = await exec.request(
|
|
3667
|
+
`${FAL_QUEUE_URL}/${request.value.endpoint}`,
|
|
3668
|
+
{
|
|
3669
|
+
body: JSON.stringify(request.value.body),
|
|
3670
|
+
method: "POST"
|
|
3671
|
+
}
|
|
3672
|
+
);
|
|
3673
|
+
if (response.isErr()) {
|
|
3674
|
+
return (0, import_neverthrow.err)(response.error);
|
|
3675
|
+
}
|
|
3676
|
+
const data = await response.value.json();
|
|
3677
|
+
const submission = parseQueueSubmission(data);
|
|
3678
|
+
return (0, import_neverthrow.ok)({
|
|
3679
|
+
endpoint: endpointFromQueueUrl(
|
|
3680
|
+
submission.responseUrl,
|
|
3681
|
+
request.value.endpoint
|
|
3682
|
+
),
|
|
3683
|
+
requestId: submission.requestId
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
async function checkToolStatus(exec, job) {
|
|
3687
|
+
return await exec.getJobStatus(job.endpoint, job.requestId);
|
|
3688
|
+
}
|
|
3689
|
+
async function getToolResult(exec, job) {
|
|
3690
|
+
const url = `${FAL_QUEUE_URL}/${job.endpoint}/requests/${job.requestId}`;
|
|
3691
|
+
const response = await exec.request(url);
|
|
3692
|
+
if (response.isErr()) {
|
|
3693
|
+
return (0, import_neverthrow.err)(response.error);
|
|
3694
|
+
}
|
|
3695
|
+
const data = await response.value.json();
|
|
3696
|
+
return (0, import_neverthrow.ok)(isRecord(data) ? data : {});
|
|
3697
|
+
}
|
|
3698
|
+
async function runToolQueued(exec, options, onProgress) {
|
|
3699
|
+
const job = await submitTool(exec, options);
|
|
3700
|
+
if (job.isErr()) {
|
|
3701
|
+
return (0, import_neverthrow.err)(job.error);
|
|
3702
|
+
}
|
|
3703
|
+
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
|
|
3704
|
+
const status = await checkToolStatus(exec, job.value);
|
|
3705
|
+
if (status.isErr()) {
|
|
3706
|
+
return (0, import_neverthrow.err)(status.error);
|
|
3707
|
+
}
|
|
3708
|
+
onProgress?.(status.value.status, status.value.queuePosition);
|
|
3709
|
+
if (status.value.status === "completed") {
|
|
3710
|
+
return await getToolResult(exec, job.value);
|
|
3711
|
+
}
|
|
3712
|
+
if (status.value.status === "failed") {
|
|
3713
|
+
const message = status.value.error ?? "Queued tool run failed";
|
|
3714
|
+
return (0, import_neverthrow.err)(new MotifError(message, 0, void 0, job.value.requestId));
|
|
3715
|
+
}
|
|
3716
|
+
await new Promise((resolve) => {
|
|
3717
|
+
setTimeout(resolve, POLL_INTERVAL_MS);
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
return (0, import_neverthrow.err)(
|
|
3721
|
+
new MotifError(
|
|
3722
|
+
"Queued tool run timed out",
|
|
3723
|
+
0,
|
|
3724
|
+
void 0,
|
|
3725
|
+
job.value.requestId
|
|
3726
|
+
)
|
|
3727
|
+
);
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
// src/server.ts
|
|
3731
|
+
var FAL_BASE_URL2 = "https://fal.run";
|
|
3732
|
+
var FAL_QUEUE_URL2 = "https://queue.fal.run";
|
|
2745
3733
|
var FAL_API_URL = "https://api.fal.ai";
|
|
2746
3734
|
var FAL_REST_URL = "https://rest.alpha.fal.ai";
|
|
2747
3735
|
var FalClient = class {
|
|
@@ -2773,16 +3761,16 @@ var FalClient = class {
|
|
|
2773
3761
|
try {
|
|
2774
3762
|
built = buildGenerateBody(options);
|
|
2775
3763
|
} catch (error) {
|
|
2776
|
-
return (0,
|
|
3764
|
+
return (0, import_neverthrow2.err)(toMotifError(error));
|
|
2777
3765
|
}
|
|
2778
3766
|
const { endpoint, body } = built;
|
|
2779
|
-
const response = await this.request(`${
|
|
3767
|
+
const response = await this.request(`${FAL_BASE_URL2}/${endpoint}`, {
|
|
2780
3768
|
body: JSON.stringify(body),
|
|
2781
3769
|
headers: this.ephemeralHeaders(options),
|
|
2782
3770
|
method: "POST"
|
|
2783
3771
|
});
|
|
2784
3772
|
if (response.isErr()) {
|
|
2785
|
-
return (0,
|
|
3773
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
2786
3774
|
}
|
|
2787
3775
|
const data = await response.value.json();
|
|
2788
3776
|
return this.normalizeResponse(data);
|
|
@@ -2790,7 +3778,7 @@ var FalClient = class {
|
|
|
2790
3778
|
async generateQueued(options) {
|
|
2791
3779
|
const job = await this.submitGeneration(options);
|
|
2792
3780
|
if (job.isErr()) {
|
|
2793
|
-
return (0,
|
|
3781
|
+
return (0, import_neverthrow2.err)(job.error);
|
|
2794
3782
|
}
|
|
2795
3783
|
const pollIntervalMs = 3e3;
|
|
2796
3784
|
const maxAttempts = 160;
|
|
@@ -2800,20 +3788,20 @@ var FalClient = class {
|
|
|
2800
3788
|
job.value.requestId
|
|
2801
3789
|
);
|
|
2802
3790
|
if (status.isErr()) {
|
|
2803
|
-
return (0,
|
|
3791
|
+
return (0, import_neverthrow2.err)(status.error);
|
|
2804
3792
|
}
|
|
2805
3793
|
if (status.value.status === "completed") {
|
|
2806
3794
|
return await this.getJobResult(job.value.endpoint, job.value.requestId);
|
|
2807
3795
|
}
|
|
2808
3796
|
if (status.value.status === "failed") {
|
|
2809
3797
|
const message = status.value.error ?? "Queued generation failed";
|
|
2810
|
-
return (0,
|
|
3798
|
+
return (0, import_neverthrow2.err)(new MotifError(message, 0, void 0, job.value.requestId));
|
|
2811
3799
|
}
|
|
2812
3800
|
await new Promise((resolve) => {
|
|
2813
3801
|
setTimeout(resolve, pollIntervalMs);
|
|
2814
3802
|
});
|
|
2815
3803
|
}
|
|
2816
|
-
return (0,
|
|
3804
|
+
return (0, import_neverthrow2.err)(new MotifError("Queued generation timed out", 0));
|
|
2817
3805
|
}
|
|
2818
3806
|
/** ─── Queue-Based Generation ──────────────────────────────── */
|
|
2819
3807
|
/** Submit a generation to the fal.ai queue (returns immediately). */
|
|
@@ -2822,20 +3810,20 @@ var FalClient = class {
|
|
|
2822
3810
|
try {
|
|
2823
3811
|
built = buildGenerateBody(options);
|
|
2824
3812
|
} catch (error) {
|
|
2825
|
-
return (0,
|
|
3813
|
+
return (0, import_neverthrow2.err)(toMotifError(error));
|
|
2826
3814
|
}
|
|
2827
3815
|
const { endpoint, body } = built;
|
|
2828
|
-
const response = await this.request(`${
|
|
3816
|
+
const response = await this.request(`${FAL_QUEUE_URL2}/${endpoint}`, {
|
|
2829
3817
|
body: JSON.stringify(body),
|
|
2830
3818
|
headers: this.ephemeralHeaders(options),
|
|
2831
3819
|
method: "POST"
|
|
2832
3820
|
});
|
|
2833
3821
|
if (response.isErr()) {
|
|
2834
|
-
return (0,
|
|
3822
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
2835
3823
|
}
|
|
2836
3824
|
const data = await response.value.json();
|
|
2837
3825
|
const submission = parseQueueSubmission(data);
|
|
2838
|
-
return (0,
|
|
3826
|
+
return (0, import_neverthrow2.ok)({
|
|
2839
3827
|
endpoint: endpointFromQueueUrl(submission.responseUrl, endpoint),
|
|
2840
3828
|
estimatedCost: estimateCost(
|
|
2841
3829
|
options.model,
|
|
@@ -2847,10 +3835,10 @@ var FalClient = class {
|
|
|
2847
3835
|
}
|
|
2848
3836
|
/** Check the status of a queued generation. */
|
|
2849
3837
|
async getJobStatus(endpoint, requestId) {
|
|
2850
|
-
const url = `${
|
|
3838
|
+
const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}/status?logs=1`;
|
|
2851
3839
|
const response = await this.request(url);
|
|
2852
3840
|
if (response.isErr()) {
|
|
2853
|
-
return (0,
|
|
3841
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
2854
3842
|
}
|
|
2855
3843
|
const data = await response.value.json();
|
|
2856
3844
|
const record = isRecord(data) ? data : {};
|
|
@@ -2865,9 +3853,9 @@ var FalClient = class {
|
|
|
2865
3853
|
} else if (rawStatus === "FAILED" || rawStatus === "ERROR" || rawStatus === "CANCELED") {
|
|
2866
3854
|
status = "failed";
|
|
2867
3855
|
} else {
|
|
2868
|
-
return (0,
|
|
3856
|
+
return (0, import_neverthrow2.err)(new MotifError(`Unknown job status: ${rawStatus}`, 0));
|
|
2869
3857
|
}
|
|
2870
|
-
return (0,
|
|
3858
|
+
return (0, import_neverthrow2.ok)({
|
|
2871
3859
|
error: asString(record.error) ?? asString(record.detail),
|
|
2872
3860
|
logs: parseLogs(record.logs),
|
|
2873
3861
|
queuePosition: asNumber(record.queue_position),
|
|
@@ -2876,10 +3864,10 @@ var FalClient = class {
|
|
|
2876
3864
|
}
|
|
2877
3865
|
/** Fetch the completed result from the queue. */
|
|
2878
3866
|
async getJobResult(endpoint, requestId) {
|
|
2879
|
-
const url = `${
|
|
3867
|
+
const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}`;
|
|
2880
3868
|
const response = await this.request(url);
|
|
2881
3869
|
if (response.isErr()) {
|
|
2882
|
-
return (0,
|
|
3870
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
2883
3871
|
}
|
|
2884
3872
|
const data = await response.value.json();
|
|
2885
3873
|
return this.normalizeResponse(data, requestId);
|
|
@@ -2900,7 +3888,7 @@ var FalClient = class {
|
|
|
2900
3888
|
} = options;
|
|
2901
3889
|
const config = MODELS[model];
|
|
2902
3890
|
if (!config || config.type !== "utility") {
|
|
2903
|
-
return (0,
|
|
3891
|
+
return (0, import_neverthrow2.err)(new MotifError(`Invalid upscale model: ${model}`, 0));
|
|
2904
3892
|
}
|
|
2905
3893
|
const body = { image_url: imageUrl };
|
|
2906
3894
|
if (model === "crystal") {
|
|
@@ -2933,12 +3921,12 @@ var FalClient = class {
|
|
|
2933
3921
|
body.guidance_scale = guidanceScale;
|
|
2934
3922
|
}
|
|
2935
3923
|
}
|
|
2936
|
-
const response = await this.request(`${
|
|
3924
|
+
const response = await this.request(`${FAL_BASE_URL2}/${config.endpoint}`, {
|
|
2937
3925
|
body: JSON.stringify(body),
|
|
2938
3926
|
method: "POST"
|
|
2939
3927
|
});
|
|
2940
3928
|
if (response.isErr()) {
|
|
2941
|
-
return (0,
|
|
3929
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
2942
3930
|
}
|
|
2943
3931
|
const data = await response.value.json();
|
|
2944
3932
|
return this.normalizeResponse(data);
|
|
@@ -2956,7 +3944,7 @@ var FalClient = class {
|
|
|
2956
3944
|
} = options;
|
|
2957
3945
|
const config = MODELS[model];
|
|
2958
3946
|
if (!config) {
|
|
2959
|
-
return (0,
|
|
3947
|
+
return (0, import_neverthrow2.err)(
|
|
2960
3948
|
new MotifError(`Invalid background removal model: ${model}`, 0)
|
|
2961
3949
|
);
|
|
2962
3950
|
}
|
|
@@ -2978,12 +3966,12 @@ var FalClient = class {
|
|
|
2978
3966
|
rbBody.output_mask = outputMask;
|
|
2979
3967
|
}
|
|
2980
3968
|
}
|
|
2981
|
-
const response = await this.request(`${
|
|
3969
|
+
const response = await this.request(`${FAL_BASE_URL2}/${config.endpoint}`, {
|
|
2982
3970
|
body: JSON.stringify(rbBody),
|
|
2983
3971
|
method: "POST"
|
|
2984
3972
|
});
|
|
2985
3973
|
if (response.isErr()) {
|
|
2986
|
-
return (0,
|
|
3974
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
2987
3975
|
}
|
|
2988
3976
|
const data = await response.value.json();
|
|
2989
3977
|
return this.normalizeResponse(data);
|
|
@@ -3006,7 +3994,7 @@ var FalClient = class {
|
|
|
3006
3994
|
} = options;
|
|
3007
3995
|
const config = MODELS.kling;
|
|
3008
3996
|
if (!config) {
|
|
3009
|
-
return (0,
|
|
3997
|
+
return (0, import_neverthrow2.err)(new MotifError("Kling video model not found", 0));
|
|
3010
3998
|
}
|
|
3011
3999
|
const body = {
|
|
3012
4000
|
duration: String(duration),
|
|
@@ -3023,16 +4011,16 @@ var FalClient = class {
|
|
|
3023
4011
|
if (cfgScale !== void 0) {
|
|
3024
4012
|
body.cfg_scale = cfgScale;
|
|
3025
4013
|
}
|
|
3026
|
-
const response = await this.request(`${
|
|
4014
|
+
const response = await this.request(`${FAL_QUEUE_URL2}/${config.endpoint}`, {
|
|
3027
4015
|
body: JSON.stringify(body),
|
|
3028
4016
|
method: "POST"
|
|
3029
4017
|
});
|
|
3030
4018
|
if (response.isErr()) {
|
|
3031
|
-
return (0,
|
|
4019
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
3032
4020
|
}
|
|
3033
4021
|
const data = await response.value.json();
|
|
3034
4022
|
const submission = parseQueueSubmission(data);
|
|
3035
|
-
return (0,
|
|
4023
|
+
return (0, import_neverthrow2.ok)({
|
|
3036
4024
|
endpoint: endpointFromQueueUrl(submission.responseUrl, config.endpoint),
|
|
3037
4025
|
estimatedCost: estimateVideoCost(duration, generateAudio),
|
|
3038
4026
|
requestId: submission.requestId
|
|
@@ -3040,17 +4028,17 @@ var FalClient = class {
|
|
|
3040
4028
|
}
|
|
3041
4029
|
/** Fetch the completed video result from the queue. */
|
|
3042
4030
|
async getVideoResult(endpoint, requestId) {
|
|
3043
|
-
const url = `${
|
|
4031
|
+
const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}`;
|
|
3044
4032
|
const response = await this.request(url);
|
|
3045
4033
|
if (response.isErr()) {
|
|
3046
|
-
return (0,
|
|
4034
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
3047
4035
|
}
|
|
3048
4036
|
const data = await response.value.json();
|
|
3049
4037
|
const video = isRecord(data) && isRecord(data.video) ? data.video : void 0;
|
|
3050
4038
|
if (video === void 0) {
|
|
3051
|
-
return (0,
|
|
4039
|
+
return (0, import_neverthrow2.err)(new MotifError("No video in response", 0));
|
|
3052
4040
|
}
|
|
3053
|
-
return (0,
|
|
4041
|
+
return (0, import_neverthrow2.ok)({
|
|
3054
4042
|
contentType: asString(video.content_type) ?? "",
|
|
3055
4043
|
fileName: asString(video.file_name) ?? "",
|
|
3056
4044
|
fileSize: asNumber(video.file_size) ?? 0,
|
|
@@ -3074,13 +4062,13 @@ var FalClient = class {
|
|
|
3074
4062
|
}
|
|
3075
4063
|
);
|
|
3076
4064
|
if (initiateResponse.isErr()) {
|
|
3077
|
-
return (0,
|
|
4065
|
+
return (0, import_neverthrow2.err)(initiateResponse.error);
|
|
3078
4066
|
}
|
|
3079
4067
|
const initiateData = await initiateResponse.value.json();
|
|
3080
4068
|
const fileUrl = isRecord(initiateData) ? asString(initiateData.file_url) ?? "" : "";
|
|
3081
4069
|
const uploadUrl = isRecord(initiateData) ? asString(initiateData.upload_url) : void 0;
|
|
3082
4070
|
if (uploadUrl === void 0 || uploadUrl === "") {
|
|
3083
|
-
return (0,
|
|
4071
|
+
return (0, import_neverthrow2.err)(
|
|
3084
4072
|
new MotifError("Upload initiate response missing upload_url", 0)
|
|
3085
4073
|
);
|
|
3086
4074
|
}
|
|
@@ -3095,7 +4083,7 @@ var FalClient = class {
|
|
|
3095
4083
|
method: "PUT"
|
|
3096
4084
|
});
|
|
3097
4085
|
} catch (error) {
|
|
3098
|
-
return (0,
|
|
4086
|
+
return (0, import_neverthrow2.err)(
|
|
3099
4087
|
new MotifError(
|
|
3100
4088
|
`Upload PUT failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3101
4089
|
0
|
|
@@ -3103,38 +4091,45 @@ var FalClient = class {
|
|
|
3103
4091
|
);
|
|
3104
4092
|
}
|
|
3105
4093
|
if (!putResponse.ok) {
|
|
3106
|
-
return (0,
|
|
4094
|
+
return (0, import_neverthrow2.err)(
|
|
3107
4095
|
new MotifError(
|
|
3108
4096
|
`Upload PUT failed: ${putResponse.status}`,
|
|
3109
4097
|
putResponse.status
|
|
3110
4098
|
)
|
|
3111
4099
|
);
|
|
3112
4100
|
}
|
|
3113
|
-
return (0,
|
|
4101
|
+
return (0, import_neverthrow2.ok)(fileUrl);
|
|
3114
4102
|
}
|
|
3115
4103
|
/** ─── Utilities ───────────────────────────────────────────── */
|
|
3116
4104
|
/** Run a registered fal utility/tool endpoint. */
|
|
3117
4105
|
async runTool(options) {
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
return (
|
|
4106
|
+
return await runTool(this.toolExecutor, options);
|
|
4107
|
+
}
|
|
4108
|
+
/** ─── Queue-Based Tools ───────────────────────────────────── */
|
|
4109
|
+
/**
|
|
4110
|
+
* Submit a tool run to the fal queue. Returns as soon as fal accepts it.
|
|
4111
|
+
*
|
|
4112
|
+
* The queued counterpart to `runTool`, for endpoints that outrun the
|
|
4113
|
+
* synchronous request timeout. Callers choose the path — neither one falls
|
|
4114
|
+
* back to the other.
|
|
4115
|
+
*/
|
|
4116
|
+
async submitTool(options) {
|
|
4117
|
+
return await submitTool(this.toolExecutor, options);
|
|
4118
|
+
}
|
|
4119
|
+
/** Poll one queued tool run. */
|
|
4120
|
+
async checkToolStatus(job) {
|
|
4121
|
+
return await checkToolStatus(this.toolExecutor, job);
|
|
4122
|
+
}
|
|
4123
|
+
/** Fetch the finished payload for a queued tool run. */
|
|
4124
|
+
async getToolResult(job) {
|
|
4125
|
+
return await getToolResult(this.toolExecutor, job);
|
|
4126
|
+
}
|
|
4127
|
+
/**
|
|
4128
|
+
* Submit, poll to completion, and return the result. The convenience path
|
|
4129
|
+
* for tools whose registry entry sets `queued`.
|
|
4130
|
+
*/
|
|
4131
|
+
async runToolQueued(options, onProgress) {
|
|
4132
|
+
return await runToolQueued(this.toolExecutor, options, onProgress);
|
|
3138
4133
|
}
|
|
3139
4134
|
/**
|
|
3140
4135
|
* Delete fal's stored IO payloads for a completed request.
|
|
@@ -3149,9 +4144,9 @@ var FalClient = class {
|
|
|
3149
4144
|
{ method: "DELETE" }
|
|
3150
4145
|
);
|
|
3151
4146
|
if (response.isErr()) {
|
|
3152
|
-
return (0,
|
|
4147
|
+
return (0, import_neverthrow2.err)(response.error);
|
|
3153
4148
|
}
|
|
3154
|
-
return (0,
|
|
4149
|
+
return (0, import_neverthrow2.ok)();
|
|
3155
4150
|
}
|
|
3156
4151
|
/** Estimate cost for a generation (no API call). */
|
|
3157
4152
|
estimateCost(model, resolution, numImages) {
|
|
@@ -3177,6 +4172,16 @@ var FalClient = class {
|
|
|
3177
4172
|
get tools() {
|
|
3178
4173
|
return FAL_TOOLS;
|
|
3179
4174
|
}
|
|
4175
|
+
/**
|
|
4176
|
+
* The seam `server-tools.ts` runs against — the client's authenticated fetch
|
|
4177
|
+
* and its queue status parser, exposed without widening the public surface.
|
|
4178
|
+
*/
|
|
4179
|
+
get toolExecutor() {
|
|
4180
|
+
return {
|
|
4181
|
+
getJobStatus: async (endpoint, requestId) => await this.getJobStatus(endpoint, requestId),
|
|
4182
|
+
request: async (url, init) => await this.request(url, init)
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
3180
4185
|
/** ─── Private ─────────────────────────────────────────────── */
|
|
3181
4186
|
/** Authenticated fetch to fal.ai APIs with retry logic. */
|
|
3182
4187
|
async request(url, options = {}) {
|
|
@@ -3208,15 +4213,15 @@ var FalClient = class {
|
|
|
3208
4213
|
const text = await response.text();
|
|
3209
4214
|
const message = `Request failed: ${response.status} ${text}`;
|
|
3210
4215
|
const requestId = response.headers.get("x-fal-request-id") ?? requestIdFromBody(text);
|
|
3211
|
-
return (0,
|
|
4216
|
+
return (0, import_neverthrow2.err)(
|
|
3212
4217
|
new MotifError(message, response.status, void 0, requestId)
|
|
3213
4218
|
);
|
|
3214
4219
|
}
|
|
3215
|
-
return (0,
|
|
4220
|
+
return (0, import_neverthrow2.ok)(response);
|
|
3216
4221
|
} catch (error) {
|
|
3217
4222
|
clearTimeout(timeoutId);
|
|
3218
4223
|
if (error instanceof MotifError) {
|
|
3219
|
-
return (0,
|
|
4224
|
+
return (0, import_neverthrow2.err)(error);
|
|
3220
4225
|
}
|
|
3221
4226
|
lastError = new MotifError(
|
|
3222
4227
|
error instanceof Error ? error.message : String(error),
|
|
@@ -3230,7 +4235,7 @@ var FalClient = class {
|
|
|
3230
4235
|
}
|
|
3231
4236
|
}
|
|
3232
4237
|
}
|
|
3233
|
-
return (0,
|
|
4238
|
+
return (0, import_neverthrow2.err)(lastError ?? new MotifError("Request failed after retries", 0));
|
|
3234
4239
|
}
|
|
3235
4240
|
ephemeralHeaders(options) {
|
|
3236
4241
|
return options.ephemeral === true ? { "X-Fal-Store-IO": "0" } : {};
|
|
@@ -3241,24 +4246,24 @@ var FalClient = class {
|
|
|
3241
4246
|
*/
|
|
3242
4247
|
normalizeResponse(data, fallbackRequestId) {
|
|
3243
4248
|
if (!isRecord(data)) {
|
|
3244
|
-
return (0,
|
|
4249
|
+
return (0, import_neverthrow2.err)(
|
|
3245
4250
|
new MotifError("Unexpected fal response shape", 0, "FAL_ERROR")
|
|
3246
4251
|
);
|
|
3247
4252
|
}
|
|
3248
4253
|
const obj = data;
|
|
3249
4254
|
const requestId = asString(obj.request_id) ?? asString(obj.requestId) ?? fallbackRequestId;
|
|
3250
4255
|
if ("detail" in obj) {
|
|
3251
|
-
return (0,
|
|
4256
|
+
return (0, import_neverthrow2.err)(new MotifError(asString(obj.detail) ?? "", 0, "FAL_ERROR"));
|
|
3252
4257
|
}
|
|
3253
4258
|
if ("image" in obj && !("images" in obj)) {
|
|
3254
|
-
return (0,
|
|
4259
|
+
return (0, import_neverthrow2.ok)({
|
|
3255
4260
|
images: [toMotifImage(obj.image)],
|
|
3256
4261
|
prompt: asString(obj.prompt),
|
|
3257
4262
|
requestId,
|
|
3258
4263
|
seed: asNumber(obj.seed)
|
|
3259
4264
|
});
|
|
3260
4265
|
}
|
|
3261
|
-
return (0,
|
|
4266
|
+
return (0, import_neverthrow2.ok)({
|
|
3262
4267
|
images: parseImages(obj.images),
|
|
3263
4268
|
prompt: asString(obj.prompt),
|
|
3264
4269
|
requestId,
|
|
@@ -3266,28 +4271,6 @@ var FalClient = class {
|
|
|
3266
4271
|
});
|
|
3267
4272
|
}
|
|
3268
4273
|
};
|
|
3269
|
-
var MotifError = class extends Error {
|
|
3270
|
-
status;
|
|
3271
|
-
code;
|
|
3272
|
-
/** fal's request-correlation id (from the `x-fal-request-id` header or the
|
|
3273
|
-
* error body). Ties a failure back to fal's dashboard/support. */
|
|
3274
|
-
requestId;
|
|
3275
|
-
constructor(message, status, code, requestId) {
|
|
3276
|
-
super(message);
|
|
3277
|
-
this.name = "MotifError";
|
|
3278
|
-
this.status = status;
|
|
3279
|
-
this.code = code;
|
|
3280
|
-
this.requestId = requestId;
|
|
3281
|
-
}
|
|
3282
|
-
};
|
|
3283
|
-
function toMotifError(error) {
|
|
3284
|
-
if (error instanceof MotifError) {
|
|
3285
|
-
return error;
|
|
3286
|
-
}
|
|
3287
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3288
|
-
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
3289
|
-
return new MotifError(message, 0, code);
|
|
3290
|
-
}
|
|
3291
4274
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3292
4275
|
0 && (module.exports = {
|
|
3293
4276
|
ASPECT_RATIOS,
|