@howells/motif-sdk 1.0.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 +1078 -183
- package/dist/index.cjs +1460 -447
- package/dist/index.d.cts +2288 -410
- package/dist/index.d.ts +2288 -410
- package/dist/index.js +1460 -447
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { err as
|
|
2
|
+
import { err as err3, ok as ok3 } from "neverthrow";
|
|
3
3
|
|
|
4
4
|
// src/aspects.ts
|
|
5
5
|
var ASPECT_RATIOS = [
|
|
@@ -99,22 +99,23 @@ var FORMAT_PRESETS = {
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
// src/models.ts
|
|
102
|
-
var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-
|
|
102
|
+
var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-08-23";
|
|
103
103
|
var AA_IMAGE_SOURCES = [
|
|
104
104
|
"https://artificialanalysis.ai/image/leaderboard/text-to-image",
|
|
105
105
|
"https://artificialanalysis.ai/image/leaderboard/editing"
|
|
106
106
|
];
|
|
107
107
|
var FAL_PRICING_CHECKED_AT = "2026-05-12";
|
|
108
108
|
var FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
|
|
109
|
+
var FAL_PRICING_CHECKED_AUG_2026 = "2026-08-23";
|
|
109
110
|
var MODELS = {
|
|
110
111
|
// ─── Generation Models ────────────────────────────────────────
|
|
111
112
|
gpt2: {
|
|
112
113
|
benchmark: {
|
|
113
114
|
artificialAnalysis: {
|
|
114
|
-
editing: { elo:
|
|
115
|
+
editing: { elo: 1256, pricePer1k: 211, rank: 3 },
|
|
115
116
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
116
117
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
117
|
-
textToImage: { elo:
|
|
118
|
+
textToImage: { elo: 1369, pricePer1k: 211, rank: 1 }
|
|
118
119
|
},
|
|
119
120
|
speed: {
|
|
120
121
|
medianSeconds: 200.7,
|
|
@@ -154,10 +155,10 @@ var MODELS = {
|
|
|
154
155
|
gpt: {
|
|
155
156
|
benchmark: {
|
|
156
157
|
artificialAnalysis: {
|
|
157
|
-
editing: { elo:
|
|
158
|
+
editing: { elo: 1250, pricePer1k: 133, rank: 5 },
|
|
158
159
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
159
160
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
160
|
-
textToImage: { elo:
|
|
161
|
+
textToImage: { elo: 1310, pricePer1k: 133, rank: 4 }
|
|
161
162
|
},
|
|
162
163
|
speed: {
|
|
163
164
|
medianSeconds: 34.6,
|
|
@@ -197,10 +198,10 @@ var MODELS = {
|
|
|
197
198
|
banana2: {
|
|
198
199
|
benchmark: {
|
|
199
200
|
artificialAnalysis: {
|
|
200
|
-
editing: { elo:
|
|
201
|
+
editing: { elo: 1249, pricePer1k: 67, rank: 7 },
|
|
201
202
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
202
203
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
203
|
-
textToImage: { elo:
|
|
204
|
+
textToImage: { elo: 1320, pricePer1k: 67, rank: 3 }
|
|
204
205
|
},
|
|
205
206
|
tiers: { price: "premium", quality: "frontier", speed: "unknown" },
|
|
206
207
|
useCase: "Best balance of quality, edit support, web search, and cost"
|
|
@@ -238,10 +239,10 @@ var MODELS = {
|
|
|
238
239
|
banana: {
|
|
239
240
|
benchmark: {
|
|
240
241
|
artificialAnalysis: {
|
|
241
|
-
editing: { elo:
|
|
242
|
+
editing: { elo: 1245, pricePer1k: 134, rank: 9 },
|
|
242
243
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
243
244
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
244
|
-
textToImage: { elo:
|
|
245
|
+
textToImage: { elo: 1297, pricePer1k: 134, rank: 6 }
|
|
245
246
|
},
|
|
246
247
|
speed: {
|
|
247
248
|
medianSeconds: 19.2,
|
|
@@ -281,6 +282,13 @@ var MODELS = {
|
|
|
281
282
|
type: "generation"
|
|
282
283
|
},
|
|
283
284
|
gemini: {
|
|
285
|
+
benchmark: {
|
|
286
|
+
artificialAnalysis: {
|
|
287
|
+
editing: { elo: 1180, pricePer1k: 39, rank: 29 },
|
|
288
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
289
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
290
|
+
}
|
|
291
|
+
},
|
|
284
292
|
editEndpoint: "fal-ai/gemini-25-flash-image/edit",
|
|
285
293
|
endpoint: "fal-ai/gemini-25-flash-image",
|
|
286
294
|
falPricing: {
|
|
@@ -308,6 +316,14 @@ var MODELS = {
|
|
|
308
316
|
type: "generation"
|
|
309
317
|
},
|
|
310
318
|
gemini3: {
|
|
319
|
+
benchmark: {
|
|
320
|
+
artificialAnalysis: {
|
|
321
|
+
editing: { elo: 1245, pricePer1k: 134, rank: 9 },
|
|
322
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
323
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
324
|
+
textToImage: { elo: 1297, pricePer1k: 134, rank: 6 }
|
|
325
|
+
}
|
|
326
|
+
},
|
|
311
327
|
editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
|
|
312
328
|
endpoint: "fal-ai/gemini-3-pro-image-preview",
|
|
313
329
|
falPricing: {
|
|
@@ -338,10 +354,10 @@ var MODELS = {
|
|
|
338
354
|
seedream4: {
|
|
339
355
|
benchmark: {
|
|
340
356
|
artificialAnalysis: {
|
|
341
|
-
editing: { elo:
|
|
357
|
+
editing: { elo: 1185, pricePer1k: 30, rank: 28 },
|
|
342
358
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
343
359
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
344
|
-
textToImage: { elo:
|
|
360
|
+
textToImage: { elo: 1226, pricePer1k: 30, rank: 16 }
|
|
345
361
|
},
|
|
346
362
|
speed: {
|
|
347
363
|
medianSeconds: 14.3,
|
|
@@ -378,10 +394,10 @@ var MODELS = {
|
|
|
378
394
|
seedream45: {
|
|
379
395
|
benchmark: {
|
|
380
396
|
artificialAnalysis: {
|
|
381
|
-
editing: { elo:
|
|
397
|
+
editing: { elo: 1186, pricePer1k: 40, rank: 26 },
|
|
382
398
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
383
399
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
384
|
-
textToImage: { elo:
|
|
400
|
+
textToImage: { elo: 1203, pricePer1k: 40, rank: 32 }
|
|
385
401
|
},
|
|
386
402
|
speed: {
|
|
387
403
|
medianSeconds: 18.2,
|
|
@@ -416,6 +432,14 @@ var MODELS = {
|
|
|
416
432
|
type: "generation"
|
|
417
433
|
},
|
|
418
434
|
seedream5: {
|
|
435
|
+
benchmark: {
|
|
436
|
+
artificialAnalysis: {
|
|
437
|
+
editing: { elo: 1248, pricePer1k: 90, rank: 8 },
|
|
438
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
439
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
440
|
+
textToImage: { elo: 1279, pricePer1k: 90, rank: 10 }
|
|
441
|
+
}
|
|
442
|
+
},
|
|
419
443
|
name: "Seedream 5.0 Pro",
|
|
420
444
|
endpoint: "bytedance/seedream/v5/pro/text-to-image",
|
|
421
445
|
editEndpoint: "bytedance/seedream/v5/pro/edit",
|
|
@@ -442,6 +466,13 @@ var MODELS = {
|
|
|
442
466
|
maxReferenceImages: 10
|
|
443
467
|
},
|
|
444
468
|
"seedream5-lite": {
|
|
469
|
+
benchmark: {
|
|
470
|
+
artificialAnalysis: {
|
|
471
|
+
editing: { elo: 1169, pricePer1k: 35, rank: 32 },
|
|
472
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
473
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
474
|
+
}
|
|
475
|
+
},
|
|
445
476
|
name: "Seedream 5.0 Lite",
|
|
446
477
|
endpoint: "fal-ai/bytedance/seedream/v5/lite/text-to-image",
|
|
447
478
|
editEndpoint: "fal-ai/bytedance/seedream/v5/lite/edit",
|
|
@@ -470,10 +501,10 @@ var MODELS = {
|
|
|
470
501
|
"flux2-max": {
|
|
471
502
|
benchmark: {
|
|
472
503
|
artificialAnalysis: {
|
|
473
|
-
editing: { elo:
|
|
504
|
+
editing: { elo: 1201, pricePer1k: 140, rank: 19 },
|
|
474
505
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
475
506
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
476
|
-
textToImage: { elo:
|
|
507
|
+
textToImage: { elo: 1225, pricePer1k: 70, rank: 17 }
|
|
477
508
|
},
|
|
478
509
|
speed: {
|
|
479
510
|
medianSeconds: 31.6,
|
|
@@ -513,10 +544,10 @@ var MODELS = {
|
|
|
513
544
|
"flux2-pro": {
|
|
514
545
|
benchmark: {
|
|
515
546
|
artificialAnalysis: {
|
|
516
|
-
editing: { elo: 1170, pricePer1k:
|
|
547
|
+
editing: { elo: 1170, pricePer1k: 45, rank: 31 },
|
|
517
548
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
518
549
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
519
|
-
textToImage: { elo:
|
|
550
|
+
textToImage: { elo: 1207, pricePer1k: 30, rank: 30 }
|
|
520
551
|
},
|
|
521
552
|
speed: {
|
|
522
553
|
medianSeconds: 16.1,
|
|
@@ -556,10 +587,9 @@ var MODELS = {
|
|
|
556
587
|
"flux2-flex": {
|
|
557
588
|
benchmark: {
|
|
558
589
|
artificialAnalysis: {
|
|
559
|
-
editing: { elo: 1161, pricePer1k: 60, rank: 22 },
|
|
560
590
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
561
591
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
562
|
-
textToImage: { elo:
|
|
592
|
+
textToImage: { elo: 1223, pricePer1k: 60, rank: 18 }
|
|
563
593
|
},
|
|
564
594
|
speed: {
|
|
565
595
|
medianSeconds: 15.3,
|
|
@@ -603,8 +633,7 @@ var MODELS = {
|
|
|
603
633
|
benchmark: {
|
|
604
634
|
artificialAnalysis: {
|
|
605
635
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
606
|
-
sourceUrls: AA_IMAGE_SOURCES
|
|
607
|
-
textToImage: { elo: 1160, pricePer1k: 12, rank: 19, winRate: 0.57 }
|
|
636
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
608
637
|
},
|
|
609
638
|
speed: {
|
|
610
639
|
medianSeconds: 4.6,
|
|
@@ -747,7 +776,55 @@ var MODELS = {
|
|
|
747
776
|
supportsSyncMode: true,
|
|
748
777
|
type: "generation"
|
|
749
778
|
},
|
|
779
|
+
reve: {
|
|
780
|
+
benchmark: {
|
|
781
|
+
artificialAnalysis: {
|
|
782
|
+
// #2 on BOTH boards, which almost nothing else manages - the top text-to-image model
|
|
783
|
+
// (GPT Image 2) drops to 3rd for editing, and the top editor (MAI-Image-2.5-Pro) is not
|
|
784
|
+
// on fal at all. Reve is the best editor motif can actually reach.
|
|
785
|
+
editing: { elo: 1263, pricePer1k: 200, rank: 2 },
|
|
786
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
787
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
788
|
+
textToImage: { elo: 1322, pricePer1k: 200, rank: 2 }
|
|
789
|
+
},
|
|
790
|
+
tiers: { price: "premium", quality: "frontier", speed: "unknown" },
|
|
791
|
+
useCase: "Highest-ranked editor reachable on fal; strong prompt adherence and text rendering"
|
|
792
|
+
},
|
|
793
|
+
editEndpoint: "fal-ai/reve/edit",
|
|
794
|
+
endpoint: "fal-ai/reve/text-to-image",
|
|
795
|
+
falPricing: {
|
|
796
|
+
checkedAt: FAL_PRICING_CHECKED_AUG_2026,
|
|
797
|
+
currency: "USD",
|
|
798
|
+
endpointId: "fal-ai/reve/text-to-image",
|
|
799
|
+
estimatedCostPerImageUsd: 0.04,
|
|
800
|
+
source: "fal-pricing-api",
|
|
801
|
+
unit: "images",
|
|
802
|
+
unitPrice: 0.04
|
|
803
|
+
},
|
|
804
|
+
maxReferenceImages: 1,
|
|
805
|
+
name: "Reve 2.1",
|
|
806
|
+
pricePerImageUsd: 0.04,
|
|
807
|
+
pricing: "$0.04",
|
|
808
|
+
// fal's schema takes aspect_ratio from a fixed set and has no width/height or resolution
|
|
809
|
+
// parameter, so aspect is supported and resolution is not.
|
|
810
|
+
sizeMode: "aspect_ratio",
|
|
811
|
+
supportsAspect: true,
|
|
812
|
+
supportsEdit: true,
|
|
813
|
+
supportsNumImages: true,
|
|
814
|
+
supportsOutputFormat: true,
|
|
815
|
+
supportsResolution: false,
|
|
816
|
+
supportsSeed: false,
|
|
817
|
+
supportsSyncMode: true,
|
|
818
|
+
type: "generation"
|
|
819
|
+
},
|
|
750
820
|
recraft4: {
|
|
821
|
+
benchmark: {
|
|
822
|
+
artificialAnalysis: {
|
|
823
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
824
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
825
|
+
textToImage: { elo: 1215, pricePer1k: 35, rank: 25 }
|
|
826
|
+
}
|
|
827
|
+
},
|
|
751
828
|
endpoint: "fal-ai/recraft/v4/text-to-image",
|
|
752
829
|
falPricing: {
|
|
753
830
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
@@ -798,6 +875,13 @@ var MODELS = {
|
|
|
798
875
|
type: "generation"
|
|
799
876
|
},
|
|
800
877
|
ideogram4: {
|
|
878
|
+
benchmark: {
|
|
879
|
+
artificialAnalysis: {
|
|
880
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
881
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
882
|
+
textToImage: { elo: 1220, pricePer1k: 60, rank: 19 }
|
|
883
|
+
}
|
|
884
|
+
},
|
|
801
885
|
name: "Ideogram V4",
|
|
802
886
|
endpoint: "ideogram/v4",
|
|
803
887
|
type: "generation",
|
|
@@ -830,10 +914,10 @@ var MODELS = {
|
|
|
830
914
|
"grok-image": {
|
|
831
915
|
benchmark: {
|
|
832
916
|
artificialAnalysis: {
|
|
833
|
-
editing: { elo:
|
|
917
|
+
editing: { elo: 1214, pricePer1k: 20, rank: 16 },
|
|
834
918
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
835
919
|
sourceUrls: AA_IMAGE_SOURCES,
|
|
836
|
-
textToImage: { elo:
|
|
920
|
+
textToImage: { elo: 1216, pricePer1k: 20, rank: 23 }
|
|
837
921
|
},
|
|
838
922
|
speed: {
|
|
839
923
|
medianSeconds: 5.1,
|
|
@@ -871,8 +955,7 @@ var MODELS = {
|
|
|
871
955
|
benchmark: {
|
|
872
956
|
artificialAnalysis: {
|
|
873
957
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
874
|
-
sourceUrls: AA_IMAGE_SOURCES
|
|
875
|
-
textToImage: { elo: 1158, pricePer1k: 20, rank: 20, winRate: 0.6 }
|
|
958
|
+
sourceUrls: AA_IMAGE_SOURCES
|
|
876
959
|
},
|
|
877
960
|
speed: {
|
|
878
961
|
medianSeconds: 23.3,
|
|
@@ -905,6 +988,41 @@ var MODELS = {
|
|
|
905
988
|
supportsSyncMode: true,
|
|
906
989
|
type: "generation"
|
|
907
990
|
},
|
|
991
|
+
qwen3: {
|
|
992
|
+
benchmark: {
|
|
993
|
+
artificialAnalysis: {
|
|
994
|
+
editing: { elo: 1218, pricePer1k: 33, rank: 15 },
|
|
995
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
996
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
997
|
+
textToImage: { elo: 1272, pricePer1k: 30, rank: 11 }
|
|
998
|
+
},
|
|
999
|
+
tiers: { price: "budget", quality: "better", speed: "unknown" },
|
|
1000
|
+
useCase: "Strong quality per dollar - 11th on text-to-image at $30/1k"
|
|
1001
|
+
},
|
|
1002
|
+
endpoint: "fal-ai/qwen-image-3/text-to-image",
|
|
1003
|
+
falPricing: {
|
|
1004
|
+
checkedAt: "2026-08-05",
|
|
1005
|
+
currency: "USD",
|
|
1006
|
+
endpointId: "fal-ai/qwen-image-3/text-to-image",
|
|
1007
|
+
estimatedCostPerImageUsd: 0.02,
|
|
1008
|
+
source: "fal-pricing-api",
|
|
1009
|
+
unit: "megapixels",
|
|
1010
|
+
unitPrice: 0.02
|
|
1011
|
+
},
|
|
1012
|
+
name: "Qwen Image 3",
|
|
1013
|
+
pricePerImageUsd: 0.02,
|
|
1014
|
+
pricing: "$0.02/MP (assumed from qwen v1; fal pricing page bot-gated at check time)",
|
|
1015
|
+
sizeMode: "image_size_enum",
|
|
1016
|
+
supportsAspect: true,
|
|
1017
|
+
supportsEdit: false,
|
|
1018
|
+
supportsNegativePrompt: true,
|
|
1019
|
+
supportsNumImages: true,
|
|
1020
|
+
supportsOutputFormat: true,
|
|
1021
|
+
supportsResolution: false,
|
|
1022
|
+
supportsSafetyChecker: true,
|
|
1023
|
+
supportsSeed: true,
|
|
1024
|
+
type: "generation"
|
|
1025
|
+
},
|
|
908
1026
|
// ─── Video Models ─────────────────────────────────────────────
|
|
909
1027
|
kling: {
|
|
910
1028
|
endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
|
|
@@ -982,7 +1100,8 @@ var GENERATION_MODELS = [
|
|
|
982
1100
|
"ideogram",
|
|
983
1101
|
"ideogram4",
|
|
984
1102
|
"grok-image",
|
|
985
|
-
"qwen"
|
|
1103
|
+
"qwen",
|
|
1104
|
+
"qwen3"
|
|
986
1105
|
];
|
|
987
1106
|
var EDIT_CAPABLE_MODELS = GENERATION_MODELS.filter(
|
|
988
1107
|
(id) => MODELS[id]?.supportsEdit === true
|
|
@@ -1303,7 +1422,7 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
|
|
|
1303
1422
|
if (options.background !== void 0 && config.supportsBackground !== true) {
|
|
1304
1423
|
unsupported(config, "background");
|
|
1305
1424
|
}
|
|
1306
|
-
if (options.transparent === true &&
|
|
1425
|
+
if (options.transparent === true && config.supportsBackground !== true) {
|
|
1307
1426
|
unsupported(config, "transparent output");
|
|
1308
1427
|
}
|
|
1309
1428
|
if (options.inputFidelity !== void 0) {
|
|
@@ -1585,353 +1704,351 @@ function buildGenerateBody(options) {
|
|
|
1585
1704
|
|
|
1586
1705
|
// src/leaderboards.ts
|
|
1587
1706
|
var IMAGE_TEXT_TO_IMAGE_TOP_20 = {
|
|
1588
|
-
capturedAt: "2026-
|
|
1707
|
+
capturedAt: "2026-08-23",
|
|
1589
1708
|
entries: [
|
|
1590
1709
|
{
|
|
1591
1710
|
apiPricing: "$211/1k images",
|
|
1592
1711
|
creator: "OpenAI",
|
|
1593
|
-
elo:
|
|
1712
|
+
elo: 1369,
|
|
1594
1713
|
isCurrent: true,
|
|
1595
1714
|
model: "GPT Image 2 (high)",
|
|
1596
1715
|
rank: 1,
|
|
1597
1716
|
routableModelId: "gpt2"
|
|
1598
1717
|
},
|
|
1599
1718
|
{
|
|
1600
|
-
apiPricing: "$
|
|
1601
|
-
creator: "
|
|
1602
|
-
elo:
|
|
1719
|
+
apiPricing: "$200/1k images",
|
|
1720
|
+
creator: "Reve",
|
|
1721
|
+
elo: 1322,
|
|
1603
1722
|
isCurrent: true,
|
|
1604
|
-
model: "
|
|
1723
|
+
model: "Reve 2.1",
|
|
1605
1724
|
rank: 2,
|
|
1606
|
-
routableModelId: "
|
|
1725
|
+
routableModelId: "reve"
|
|
1607
1726
|
},
|
|
1608
1727
|
{
|
|
1609
1728
|
apiPricing: "$67/1k images",
|
|
1610
1729
|
creator: "Google",
|
|
1611
|
-
elo:
|
|
1730
|
+
elo: 1320,
|
|
1612
1731
|
isCurrent: true,
|
|
1613
1732
|
model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
|
|
1614
1733
|
rank: 3,
|
|
1615
1734
|
routableModelId: "banana2"
|
|
1616
1735
|
},
|
|
1617
1736
|
{
|
|
1618
|
-
apiPricing: "$
|
|
1619
|
-
creator: "
|
|
1620
|
-
elo:
|
|
1737
|
+
apiPricing: "$133/1k images",
|
|
1738
|
+
creator: "OpenAI",
|
|
1739
|
+
elo: 1310,
|
|
1621
1740
|
isCurrent: true,
|
|
1622
|
-
model: "
|
|
1623
|
-
rank: 4
|
|
1741
|
+
model: "GPT Image 1.5 (high)",
|
|
1742
|
+
rank: 4,
|
|
1743
|
+
routableModelId: "gpt"
|
|
1624
1744
|
},
|
|
1625
1745
|
{
|
|
1626
|
-
apiPricing: "$
|
|
1627
|
-
creator: "
|
|
1628
|
-
elo:
|
|
1746
|
+
apiPricing: "$48/1k images",
|
|
1747
|
+
creator: "Microsoft AI",
|
|
1748
|
+
elo: 1305,
|
|
1629
1749
|
isCurrent: true,
|
|
1630
|
-
model: "
|
|
1631
|
-
rank: 5
|
|
1632
|
-
routableModelId: "banana"
|
|
1750
|
+
model: "MAI-Image-2.5",
|
|
1751
|
+
rank: 5
|
|
1633
1752
|
},
|
|
1634
1753
|
{
|
|
1635
|
-
apiPricing: "$
|
|
1636
|
-
creator: "
|
|
1637
|
-
elo:
|
|
1754
|
+
apiPricing: "$134/1k images",
|
|
1755
|
+
creator: "Google",
|
|
1756
|
+
elo: 1297,
|
|
1638
1757
|
isCurrent: true,
|
|
1639
|
-
model: "
|
|
1758
|
+
model: "Nano Banana Pro (Gemini 3 Pro Image)",
|
|
1640
1759
|
rank: 6,
|
|
1641
|
-
routableModelId: "
|
|
1760
|
+
routableModelId: "banana"
|
|
1642
1761
|
},
|
|
1643
1762
|
{
|
|
1644
|
-
apiPricing: "$
|
|
1645
|
-
creator: "Microsoft",
|
|
1646
|
-
elo:
|
|
1763
|
+
apiPricing: "$109/1k images",
|
|
1764
|
+
creator: "Microsoft AI",
|
|
1765
|
+
elo: 1293,
|
|
1647
1766
|
isCurrent: true,
|
|
1648
|
-
model: "MAI-Image-2",
|
|
1767
|
+
model: "MAI-Image-2.5-Pro",
|
|
1649
1768
|
rank: 7
|
|
1650
1769
|
},
|
|
1651
1770
|
{
|
|
1652
|
-
apiPricing: "$
|
|
1653
|
-
creator: "
|
|
1654
|
-
elo:
|
|
1771
|
+
apiPricing: "$34/1k images",
|
|
1772
|
+
creator: "Google",
|
|
1773
|
+
elo: 1289,
|
|
1655
1774
|
isCurrent: true,
|
|
1656
|
-
model: "
|
|
1657
|
-
rank: 8
|
|
1658
|
-
routableModelId: "flux2-max"
|
|
1775
|
+
model: "Nano Banana 2 Lite (Gemini 3.1 Flash Lite Image)",
|
|
1776
|
+
rank: 8
|
|
1659
1777
|
},
|
|
1660
1778
|
{
|
|
1661
|
-
apiPricing: "
|
|
1662
|
-
creator: "
|
|
1663
|
-
elo:
|
|
1779
|
+
apiPricing: "$43/1k images",
|
|
1780
|
+
creator: "Alibaba",
|
|
1781
|
+
elo: 1282,
|
|
1664
1782
|
isCurrent: true,
|
|
1665
|
-
model: "
|
|
1783
|
+
model: "Qwen-Image-3.0-Pro",
|
|
1666
1784
|
rank: 9
|
|
1667
1785
|
},
|
|
1668
1786
|
{
|
|
1669
|
-
apiPricing: "$
|
|
1670
|
-
creator: "
|
|
1671
|
-
elo:
|
|
1787
|
+
apiPricing: "$90/1k images",
|
|
1788
|
+
creator: "Bytedance",
|
|
1789
|
+
elo: 1279,
|
|
1672
1790
|
isCurrent: true,
|
|
1673
|
-
model: "
|
|
1791
|
+
model: "Seedream 5.0 Pro",
|
|
1674
1792
|
rank: 10,
|
|
1675
|
-
routableModelId: "
|
|
1793
|
+
routableModelId: "seedream5"
|
|
1676
1794
|
},
|
|
1677
1795
|
{
|
|
1678
|
-
apiPricing: "$
|
|
1679
|
-
creator: "
|
|
1680
|
-
elo:
|
|
1681
|
-
isCurrent:
|
|
1682
|
-
model: "
|
|
1683
|
-
rank: 11
|
|
1796
|
+
apiPricing: "$30/1k images",
|
|
1797
|
+
creator: "Alibaba",
|
|
1798
|
+
elo: 1272,
|
|
1799
|
+
isCurrent: true,
|
|
1800
|
+
model: "Qwen-Image-3.0",
|
|
1801
|
+
rank: 11,
|
|
1802
|
+
routableModelId: "qwen3"
|
|
1684
1803
|
},
|
|
1685
1804
|
{
|
|
1686
|
-
apiPricing: "$
|
|
1687
|
-
creator: "
|
|
1688
|
-
elo:
|
|
1805
|
+
apiPricing: "$75/1k images",
|
|
1806
|
+
creator: "Alibaba",
|
|
1807
|
+
elo: 1237,
|
|
1689
1808
|
isCurrent: true,
|
|
1690
|
-
model: "
|
|
1691
|
-
rank: 12
|
|
1692
|
-
routableModelId: "grok-image"
|
|
1809
|
+
model: "Qwen Image 2.0 Pro",
|
|
1810
|
+
rank: 12
|
|
1693
1811
|
},
|
|
1694
1812
|
{
|
|
1695
|
-
apiPricing: "$
|
|
1696
|
-
creator: "
|
|
1697
|
-
elo:
|
|
1813
|
+
apiPricing: "$50/1k images",
|
|
1814
|
+
creator: "SpaceXAI",
|
|
1815
|
+
elo: 1234,
|
|
1698
1816
|
isCurrent: true,
|
|
1699
|
-
model: "
|
|
1700
|
-
rank: 13
|
|
1701
|
-
routableModelId: "flux2-flex"
|
|
1817
|
+
model: "grok-imagine-image-quality",
|
|
1818
|
+
rank: 13
|
|
1702
1819
|
},
|
|
1703
1820
|
{
|
|
1704
|
-
apiPricing: "$
|
|
1705
|
-
creator: "
|
|
1706
|
-
elo:
|
|
1821
|
+
apiPricing: "$20/1k images",
|
|
1822
|
+
creator: "Microsoft AI",
|
|
1823
|
+
elo: 1228,
|
|
1707
1824
|
isCurrent: true,
|
|
1708
|
-
model: "
|
|
1825
|
+
model: "MAI-Image-2.5-Flash",
|
|
1709
1826
|
rank: 14
|
|
1710
1827
|
},
|
|
1711
1828
|
{
|
|
1712
|
-
apiPricing: "$
|
|
1713
|
-
creator: "
|
|
1714
|
-
elo:
|
|
1829
|
+
apiPricing: "$80/1k images",
|
|
1830
|
+
creator: "HiDream",
|
|
1831
|
+
elo: 1228,
|
|
1715
1832
|
isCurrent: true,
|
|
1716
|
-
model: "
|
|
1833
|
+
model: "HiDream-O1-Image-1.5",
|
|
1717
1834
|
rank: 15
|
|
1718
1835
|
},
|
|
1719
1836
|
{
|
|
1720
|
-
apiPricing: "$
|
|
1721
|
-
creator: "
|
|
1722
|
-
elo:
|
|
1723
|
-
isCurrent:
|
|
1724
|
-
model: "
|
|
1725
|
-
rank: 16
|
|
1837
|
+
apiPricing: "$30/1k images",
|
|
1838
|
+
creator: "ByteDance Seed",
|
|
1839
|
+
elo: 1226,
|
|
1840
|
+
isCurrent: true,
|
|
1841
|
+
model: "Seedream 4.0",
|
|
1842
|
+
rank: 16,
|
|
1843
|
+
routableModelId: "seedream4"
|
|
1726
1844
|
},
|
|
1727
1845
|
{
|
|
1728
|
-
apiPricing: "$
|
|
1729
|
-
creator: "
|
|
1730
|
-
elo:
|
|
1846
|
+
apiPricing: "$70/1k images",
|
|
1847
|
+
creator: "Black Forest Labs",
|
|
1848
|
+
elo: 1225,
|
|
1731
1849
|
isCurrent: true,
|
|
1732
|
-
model: "
|
|
1850
|
+
model: "FLUX.2 [max]",
|
|
1733
1851
|
rank: 17,
|
|
1734
|
-
routableModelId: "
|
|
1852
|
+
routableModelId: "flux2-max"
|
|
1735
1853
|
},
|
|
1736
1854
|
{
|
|
1737
|
-
apiPricing: "$
|
|
1738
|
-
creator: "
|
|
1739
|
-
elo:
|
|
1855
|
+
apiPricing: "$60/1k images",
|
|
1856
|
+
creator: "Black Forest Labs",
|
|
1857
|
+
elo: 1223,
|
|
1740
1858
|
isCurrent: true,
|
|
1741
|
-
model: "FLUX.2 [
|
|
1742
|
-
rank: 18
|
|
1859
|
+
model: "FLUX.2 [flex]",
|
|
1860
|
+
rank: 18,
|
|
1861
|
+
routableModelId: "flux2-flex"
|
|
1743
1862
|
},
|
|
1744
1863
|
{
|
|
1745
|
-
apiPricing: "$
|
|
1746
|
-
creator: "
|
|
1747
|
-
elo:
|
|
1864
|
+
apiPricing: "$60/1k images",
|
|
1865
|
+
creator: "Ideogram",
|
|
1866
|
+
elo: 1220,
|
|
1748
1867
|
isCurrent: true,
|
|
1749
|
-
model: "
|
|
1868
|
+
model: "Ideogram 4.0",
|
|
1750
1869
|
rank: 19,
|
|
1751
|
-
routableModelId: "
|
|
1870
|
+
routableModelId: "ideogram4"
|
|
1752
1871
|
},
|
|
1753
1872
|
{
|
|
1754
|
-
apiPricing: "$
|
|
1755
|
-
creator: "
|
|
1756
|
-
elo:
|
|
1873
|
+
apiPricing: "$60/1k images",
|
|
1874
|
+
creator: "Krea",
|
|
1875
|
+
elo: 1220,
|
|
1757
1876
|
isCurrent: true,
|
|
1758
|
-
model: "
|
|
1759
|
-
rank: 20
|
|
1760
|
-
routableModelId: "qwen"
|
|
1877
|
+
model: "Krea 2 Large",
|
|
1878
|
+
rank: 20
|
|
1761
1879
|
}
|
|
1762
1880
|
],
|
|
1763
1881
|
source: "https://artificialanalysis.ai/image/leaderboard/text-to-image"
|
|
1764
1882
|
};
|
|
1765
1883
|
var IMAGE_EDITING_TOP_20 = {
|
|
1766
|
-
capturedAt: "2026-
|
|
1884
|
+
capturedAt: "2026-08-23",
|
|
1767
1885
|
entries: [
|
|
1768
1886
|
{
|
|
1769
|
-
apiPricing: "$
|
|
1770
|
-
creator: "
|
|
1771
|
-
elo:
|
|
1887
|
+
apiPricing: "$109/1k images",
|
|
1888
|
+
creator: "Microsoft AI",
|
|
1889
|
+
elo: 1272,
|
|
1772
1890
|
isCurrent: true,
|
|
1773
|
-
model: "
|
|
1891
|
+
model: "MAI-Image-2.5-Pro",
|
|
1774
1892
|
rank: 1
|
|
1775
1893
|
},
|
|
1776
1894
|
{
|
|
1777
|
-
apiPricing: "$
|
|
1778
|
-
creator: "
|
|
1779
|
-
elo:
|
|
1895
|
+
apiPricing: "$200/1k images",
|
|
1896
|
+
creator: "Reve",
|
|
1897
|
+
elo: 1263,
|
|
1780
1898
|
isCurrent: true,
|
|
1781
|
-
model: "
|
|
1899
|
+
model: "Reve 2.1",
|
|
1782
1900
|
rank: 2,
|
|
1783
|
-
routableModelId: "
|
|
1901
|
+
routableModelId: "reve"
|
|
1784
1902
|
},
|
|
1785
1903
|
{
|
|
1786
1904
|
apiPricing: "$211/1k images",
|
|
1787
1905
|
creator: "OpenAI",
|
|
1788
|
-
elo:
|
|
1906
|
+
elo: 1256,
|
|
1789
1907
|
isCurrent: true,
|
|
1790
1908
|
model: "GPT Image 2 (high)",
|
|
1791
1909
|
rank: 3,
|
|
1792
1910
|
routableModelId: "gpt2"
|
|
1793
1911
|
},
|
|
1794
1912
|
{
|
|
1795
|
-
apiPricing: "$
|
|
1796
|
-
creator: "
|
|
1797
|
-
elo:
|
|
1913
|
+
apiPricing: "$48/1k images",
|
|
1914
|
+
creator: "Microsoft AI",
|
|
1915
|
+
elo: 1256,
|
|
1798
1916
|
isCurrent: true,
|
|
1799
|
-
model: "
|
|
1800
|
-
rank: 4
|
|
1801
|
-
routableModelId: "banana"
|
|
1917
|
+
model: "MAI-Image-2.5",
|
|
1918
|
+
rank: 4
|
|
1802
1919
|
},
|
|
1803
1920
|
{
|
|
1804
|
-
apiPricing: "$
|
|
1805
|
-
creator: "
|
|
1806
|
-
elo:
|
|
1921
|
+
apiPricing: "$133/1k images",
|
|
1922
|
+
creator: "OpenAI",
|
|
1923
|
+
elo: 1250,
|
|
1807
1924
|
isCurrent: true,
|
|
1808
|
-
model: "
|
|
1925
|
+
model: "GPT Image 1.5 (high)",
|
|
1809
1926
|
rank: 5,
|
|
1810
|
-
routableModelId: "
|
|
1927
|
+
routableModelId: "gpt"
|
|
1811
1928
|
},
|
|
1812
1929
|
{
|
|
1813
|
-
apiPricing: "$
|
|
1814
|
-
creator: "
|
|
1815
|
-
elo:
|
|
1930
|
+
apiPricing: "$43/1k images",
|
|
1931
|
+
creator: "Alibaba",
|
|
1932
|
+
elo: 1250,
|
|
1816
1933
|
isCurrent: true,
|
|
1817
|
-
model: "
|
|
1934
|
+
model: "Qwen-Image-3.0-Pro",
|
|
1818
1935
|
rank: 6
|
|
1819
1936
|
},
|
|
1820
1937
|
{
|
|
1821
|
-
apiPricing: "$
|
|
1822
|
-
creator: "
|
|
1823
|
-
elo:
|
|
1938
|
+
apiPricing: "$67/1k images",
|
|
1939
|
+
creator: "Google",
|
|
1940
|
+
elo: 1249,
|
|
1824
1941
|
isCurrent: true,
|
|
1825
|
-
model: "
|
|
1942
|
+
model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
|
|
1826
1943
|
rank: 7,
|
|
1827
|
-
routableModelId: "
|
|
1944
|
+
routableModelId: "banana2"
|
|
1828
1945
|
},
|
|
1829
1946
|
{
|
|
1830
|
-
apiPricing: "$
|
|
1831
|
-
creator: "
|
|
1832
|
-
elo:
|
|
1947
|
+
apiPricing: "$90/1k images",
|
|
1948
|
+
creator: "Bytedance",
|
|
1949
|
+
elo: 1248,
|
|
1833
1950
|
isCurrent: true,
|
|
1834
|
-
model: "
|
|
1835
|
-
rank: 8
|
|
1951
|
+
model: "Seedream 5.0 Pro",
|
|
1952
|
+
rank: 8,
|
|
1953
|
+
routableModelId: "seedream5"
|
|
1836
1954
|
},
|
|
1837
1955
|
{
|
|
1838
|
-
apiPricing: "
|
|
1839
|
-
creator: "
|
|
1840
|
-
elo:
|
|
1956
|
+
apiPricing: "$134/1k images",
|
|
1957
|
+
creator: "Google",
|
|
1958
|
+
elo: 1245,
|
|
1841
1959
|
isCurrent: true,
|
|
1842
|
-
model: "
|
|
1843
|
-
rank: 9
|
|
1960
|
+
model: "Nano Banana Pro (Gemini 3 Pro Image)",
|
|
1961
|
+
rank: 9,
|
|
1962
|
+
routableModelId: "banana"
|
|
1844
1963
|
},
|
|
1845
1964
|
{
|
|
1846
|
-
apiPricing: "$
|
|
1847
|
-
creator: "
|
|
1848
|
-
elo:
|
|
1965
|
+
apiPricing: "$20/1k images",
|
|
1966
|
+
creator: "Microsoft AI",
|
|
1967
|
+
elo: 1236,
|
|
1849
1968
|
isCurrent: true,
|
|
1850
|
-
model: "
|
|
1851
|
-
rank: 10
|
|
1852
|
-
routableModelId: "flux2-max"
|
|
1969
|
+
model: "MAI-Image-2.5-Flash",
|
|
1970
|
+
rank: 10
|
|
1853
1971
|
},
|
|
1854
1972
|
{
|
|
1855
|
-
apiPricing: "
|
|
1856
|
-
creator: "
|
|
1857
|
-
elo:
|
|
1973
|
+
apiPricing: "$50/1k images",
|
|
1974
|
+
creator: "SpaceXAI",
|
|
1975
|
+
elo: 1226,
|
|
1858
1976
|
isCurrent: true,
|
|
1859
|
-
model: "
|
|
1977
|
+
model: "grok-imagine-image-quality",
|
|
1860
1978
|
rank: 11
|
|
1861
1979
|
},
|
|
1862
1980
|
{
|
|
1863
|
-
apiPricing: "$
|
|
1864
|
-
creator: "
|
|
1865
|
-
elo:
|
|
1981
|
+
apiPricing: "$90/1k images",
|
|
1982
|
+
creator: "Tencent",
|
|
1983
|
+
elo: 1222,
|
|
1866
1984
|
isCurrent: true,
|
|
1867
|
-
model: "
|
|
1985
|
+
model: "HunyuanImage 3.0 Instruct",
|
|
1868
1986
|
rank: 12
|
|
1869
1987
|
},
|
|
1870
1988
|
{
|
|
1871
|
-
apiPricing: "$
|
|
1872
|
-
creator: "
|
|
1873
|
-
elo:
|
|
1989
|
+
apiPricing: "$80/1k images",
|
|
1990
|
+
creator: "Newport AI",
|
|
1991
|
+
elo: 1220,
|
|
1874
1992
|
isCurrent: true,
|
|
1875
|
-
model: "
|
|
1993
|
+
model: "DreamImage 2.0",
|
|
1876
1994
|
rank: 13
|
|
1877
1995
|
},
|
|
1878
1996
|
{
|
|
1879
|
-
apiPricing: "$
|
|
1880
|
-
creator: "
|
|
1881
|
-
elo:
|
|
1997
|
+
apiPricing: "$100/1k images",
|
|
1998
|
+
creator: "Luma Labs",
|
|
1999
|
+
elo: 1219,
|
|
1882
2000
|
isCurrent: true,
|
|
1883
|
-
model: "
|
|
2001
|
+
model: "Luma UNI 1 Max",
|
|
1884
2002
|
rank: 14
|
|
1885
2003
|
},
|
|
1886
2004
|
{
|
|
1887
|
-
apiPricing: "$
|
|
1888
|
-
creator: "
|
|
1889
|
-
elo:
|
|
2005
|
+
apiPricing: "$33/1k images",
|
|
2006
|
+
creator: "Alibaba",
|
|
2007
|
+
elo: 1218,
|
|
1890
2008
|
isCurrent: true,
|
|
1891
|
-
model: "
|
|
1892
|
-
rank: 15
|
|
2009
|
+
model: "Qwen-Image-3.0",
|
|
2010
|
+
rank: 15,
|
|
2011
|
+
routableModelId: "qwen3"
|
|
1893
2012
|
},
|
|
1894
2013
|
{
|
|
1895
|
-
apiPricing: "$
|
|
1896
|
-
creator: "
|
|
1897
|
-
elo:
|
|
1898
|
-
isCurrent: true,
|
|
1899
|
-
model: "
|
|
2014
|
+
apiPricing: "$20/1k images",
|
|
2015
|
+
creator: "SpaceXAI",
|
|
2016
|
+
elo: 1214,
|
|
2017
|
+
isCurrent: true,
|
|
2018
|
+
model: "grok-imagine-image",
|
|
1900
2019
|
rank: 16,
|
|
1901
|
-
routableModelId: "
|
|
2020
|
+
routableModelId: "grok-image"
|
|
1902
2021
|
},
|
|
1903
2022
|
{
|
|
1904
|
-
apiPricing: "$
|
|
1905
|
-
creator: "
|
|
1906
|
-
elo:
|
|
2023
|
+
apiPricing: "$28/1k images",
|
|
2024
|
+
creator: "KlingAI",
|
|
2025
|
+
elo: 1208,
|
|
1907
2026
|
isCurrent: true,
|
|
1908
|
-
model: "
|
|
1909
|
-
rank: 17
|
|
1910
|
-
routableModelId: "seedream45"
|
|
2027
|
+
model: "Kling Image 3.0 Omni",
|
|
2028
|
+
rank: 17
|
|
1911
2029
|
},
|
|
1912
2030
|
{
|
|
1913
|
-
apiPricing: "$
|
|
1914
|
-
creator: "
|
|
1915
|
-
elo:
|
|
2031
|
+
apiPricing: "$34/1k images",
|
|
2032
|
+
creator: "Google",
|
|
2033
|
+
elo: 1202,
|
|
1916
2034
|
isCurrent: true,
|
|
1917
|
-
model: "
|
|
2035
|
+
model: "Nano Banana 2 Lite",
|
|
1918
2036
|
rank: 18
|
|
1919
2037
|
},
|
|
1920
2038
|
{
|
|
1921
|
-
apiPricing: "$
|
|
1922
|
-
creator: "
|
|
1923
|
-
elo:
|
|
2039
|
+
apiPricing: "$140/1k images",
|
|
2040
|
+
creator: "Black Forest Labs",
|
|
2041
|
+
elo: 1201,
|
|
1924
2042
|
isCurrent: true,
|
|
1925
|
-
model: "
|
|
2043
|
+
model: "FLUX.2 [max]",
|
|
1926
2044
|
rank: 19,
|
|
1927
|
-
routableModelId: "
|
|
2045
|
+
routableModelId: "flux2-max"
|
|
1928
2046
|
},
|
|
1929
2047
|
{
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
elo: 1172,
|
|
2048
|
+
creator: "Alibaba",
|
|
2049
|
+
elo: 1200,
|
|
1933
2050
|
isCurrent: true,
|
|
1934
|
-
model: "
|
|
2051
|
+
model: "Wan 2.7 Pro",
|
|
1935
2052
|
rank: 20
|
|
1936
2053
|
}
|
|
1937
2054
|
],
|
|
@@ -2161,7 +2278,31 @@ var VIDEO_IMAGE_TO_VIDEO_TOP_15 = {
|
|
|
2161
2278
|
};
|
|
2162
2279
|
|
|
2163
2280
|
// src/server.ts
|
|
2164
|
-
import { err, ok } from "neverthrow";
|
|
2281
|
+
import { err as err2, ok as ok2 } from "neverthrow";
|
|
2282
|
+
|
|
2283
|
+
// src/errors.ts
|
|
2284
|
+
var MotifError = class extends Error {
|
|
2285
|
+
status;
|
|
2286
|
+
code;
|
|
2287
|
+
/** fal's request-correlation id (from the `x-fal-request-id` header or the
|
|
2288
|
+
* error body). Ties a failure back to fal's dashboard/support. */
|
|
2289
|
+
requestId;
|
|
2290
|
+
constructor(message, status, code, requestId) {
|
|
2291
|
+
super(message);
|
|
2292
|
+
this.name = "MotifError";
|
|
2293
|
+
this.status = status;
|
|
2294
|
+
this.code = code;
|
|
2295
|
+
this.requestId = requestId;
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
function toMotifError(error) {
|
|
2299
|
+
if (error instanceof MotifError) {
|
|
2300
|
+
return error;
|
|
2301
|
+
}
|
|
2302
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2303
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2304
|
+
return new MotifError(message, 0, code);
|
|
2305
|
+
}
|
|
2165
2306
|
|
|
2166
2307
|
// src/fal-parse.ts
|
|
2167
2308
|
function isRecord(value) {
|
|
@@ -2259,9 +2400,362 @@ function requestIdFromBody(text) {
|
|
|
2259
2400
|
return asString(parsed.request_id) ?? asString(parsed.requestId) ?? asString(parsed.trace_id);
|
|
2260
2401
|
}
|
|
2261
2402
|
|
|
2262
|
-
// src/tools.ts
|
|
2263
|
-
|
|
2264
|
-
|
|
2403
|
+
// src/server-tools.ts
|
|
2404
|
+
import { err, ok } from "neverthrow";
|
|
2405
|
+
|
|
2406
|
+
// src/tool-registry/analysis.ts
|
|
2407
|
+
var ANALYSIS_TOOLS = {
|
|
2408
|
+
"got-ocr": {
|
|
2409
|
+
category: "analysis",
|
|
2410
|
+
description: "Transcribe text from one or more images, optionally as formatted multi-page output.",
|
|
2411
|
+
endpoint: "fal-ai/got-ocr/v2",
|
|
2412
|
+
inputField: "input_image_urls",
|
|
2413
|
+
inputKind: "images",
|
|
2414
|
+
name: "GOT-OCR 2.0",
|
|
2415
|
+
outputKeys: ["outputs"],
|
|
2416
|
+
price: { kind: "metered" },
|
|
2417
|
+
pricing: "$0.05/image",
|
|
2418
|
+
queued: true,
|
|
2419
|
+
sourceUrl: "https://fal.ai/models/fal-ai/got-ocr/v2",
|
|
2420
|
+
task: "optical character recognition"
|
|
2421
|
+
},
|
|
2422
|
+
"moondream-caption": {
|
|
2423
|
+
category: "analysis",
|
|
2424
|
+
description: "Write a short, normal, or long caption for an image.",
|
|
2425
|
+
endpoint: "fal-ai/moondream3-preview/caption",
|
|
2426
|
+
inputField: "image_url",
|
|
2427
|
+
inputKind: "image",
|
|
2428
|
+
name: "Moondream 3 Caption",
|
|
2429
|
+
outputKeys: ["output"],
|
|
2430
|
+
price: { kind: "metered" },
|
|
2431
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2432
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/caption",
|
|
2433
|
+
task: "image captioning"
|
|
2434
|
+
},
|
|
2435
|
+
"moondream-detect": {
|
|
2436
|
+
category: "analysis",
|
|
2437
|
+
description: "Detect prompted objects and return their bounding boxes, with an optional preview.",
|
|
2438
|
+
endpoint: "fal-ai/moondream3-preview/detect",
|
|
2439
|
+
inputField: "image_url",
|
|
2440
|
+
inputKind: "image",
|
|
2441
|
+
name: "Moondream 3 Detect",
|
|
2442
|
+
outputKeys: ["objects", "image"],
|
|
2443
|
+
price: { kind: "metered" },
|
|
2444
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2445
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/detect",
|
|
2446
|
+
task: "object detection"
|
|
2447
|
+
},
|
|
2448
|
+
"moondream-point": {
|
|
2449
|
+
category: "analysis",
|
|
2450
|
+
description: "Return point coordinates for every instance of a prompted subject.",
|
|
2451
|
+
endpoint: "fal-ai/moondream3-preview/point",
|
|
2452
|
+
inputField: "image_url",
|
|
2453
|
+
inputKind: "image",
|
|
2454
|
+
name: "Moondream 3 Point",
|
|
2455
|
+
outputKeys: ["points", "image"],
|
|
2456
|
+
price: { kind: "metered" },
|
|
2457
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2458
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/point",
|
|
2459
|
+
task: "object pointing"
|
|
2460
|
+
},
|
|
2461
|
+
"moondream-query": {
|
|
2462
|
+
category: "analysis",
|
|
2463
|
+
description: "Answer a free-form question about an image, with reasoning.",
|
|
2464
|
+
endpoint: "fal-ai/moondream3-preview/query",
|
|
2465
|
+
inputField: "image_url",
|
|
2466
|
+
inputKind: "image",
|
|
2467
|
+
name: "Moondream 3 Query",
|
|
2468
|
+
outputKeys: ["output", "reasoning"],
|
|
2469
|
+
price: { kind: "metered" },
|
|
2470
|
+
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
2471
|
+
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/query",
|
|
2472
|
+
task: "visual question answering"
|
|
2473
|
+
},
|
|
2474
|
+
nsfw: {
|
|
2475
|
+
category: "moderation",
|
|
2476
|
+
description: "Predict whether one or more images contain NSFW concepts.",
|
|
2477
|
+
endpoint: "fal-ai/x-ailab/nsfw",
|
|
2478
|
+
inputField: "image_urls",
|
|
2479
|
+
inputKind: "images",
|
|
2480
|
+
name: "NSFW Checker",
|
|
2481
|
+
outputKeys: ["has_nsfw_concepts"],
|
|
2482
|
+
price: { kind: "metered" },
|
|
2483
|
+
pricing: "$0.001/image",
|
|
2484
|
+
sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
|
|
2485
|
+
task: "vision moderation"
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
|
|
2489
|
+
// src/tool-registry/assets.ts
|
|
2490
|
+
var ASSET_TOOLS = {
|
|
2491
|
+
"hunyuan3d-v3": {
|
|
2492
|
+
category: "3d",
|
|
2493
|
+
description: "Reconstruct a textured 3D mesh from a single image, with optional multi-view inputs.",
|
|
2494
|
+
endpoint: "fal-ai/hunyuan3d-v3/image-to-3d",
|
|
2495
|
+
inputField: "input_image_url",
|
|
2496
|
+
inputKind: "image",
|
|
2497
|
+
name: "Hunyuan3D v3 Image to 3D",
|
|
2498
|
+
outputKeys: ["model_glb", "model_urls", "thumbnail"],
|
|
2499
|
+
price: { kind: "call", usd: 0.375 },
|
|
2500
|
+
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",
|
|
2501
|
+
queued: true,
|
|
2502
|
+
sourceUrl: "https://fal.ai/models/fal-ai/hunyuan3d-v3/image-to-3d",
|
|
2503
|
+
task: "single-image 3D reconstruction"
|
|
2504
|
+
},
|
|
2505
|
+
"ideogram-layerize-text": {
|
|
2506
|
+
category: "layers",
|
|
2507
|
+
description: "Split an image into background image layers plus editable HTML text containers.",
|
|
2508
|
+
endpoint: "fal-ai/ideogram/v3/layerize-text",
|
|
2509
|
+
inputField: "image_url",
|
|
2510
|
+
inputKind: "image",
|
|
2511
|
+
name: "Ideogram v3 Text Layerize",
|
|
2512
|
+
outputKeys: ["image_layers", "image", "text_html", "text_containers"],
|
|
2513
|
+
price: { kind: "call", usd: 0.09 },
|
|
2514
|
+
pricing: "$0.09/image",
|
|
2515
|
+
queued: true,
|
|
2516
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/layerize-text",
|
|
2517
|
+
task: "text and image layer extraction"
|
|
2518
|
+
},
|
|
2519
|
+
"ideogram-tiling": {
|
|
2520
|
+
category: "material",
|
|
2521
|
+
description: "Generate a seamlessly tiling texture, optionally conditioned on a source image.",
|
|
2522
|
+
endpoint: "ideogram/v4/tiling",
|
|
2523
|
+
inputField: "image_url",
|
|
2524
|
+
inputKind: "image",
|
|
2525
|
+
name: "Ideogram v4 Tiling",
|
|
2526
|
+
outputKeys: ["images"],
|
|
2527
|
+
price: { kind: "megapixel", usd: 0.06 },
|
|
2528
|
+
pricing: "$0.06/megapixel at the default BALANCED speed; $0.03 turbo, $0.10 quality",
|
|
2529
|
+
sourceUrl: "https://fal.ai/models/ideogram/v4/tiling",
|
|
2530
|
+
task: "seamless texture generation"
|
|
2531
|
+
},
|
|
2532
|
+
image2svg: {
|
|
2533
|
+
category: "vector",
|
|
2534
|
+
description: "Trace a raster image into layered SVG paths.",
|
|
2535
|
+
endpoint: "fal-ai/image2svg",
|
|
2536
|
+
inputField: "image_url",
|
|
2537
|
+
inputKind: "image",
|
|
2538
|
+
name: "Image to SVG",
|
|
2539
|
+
outputKeys: ["images"],
|
|
2540
|
+
price: { kind: "call", usd: 5e-3 },
|
|
2541
|
+
pricing: "$0.005/image",
|
|
2542
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image2svg",
|
|
2543
|
+
task: "raster to vector tracing"
|
|
2544
|
+
},
|
|
2545
|
+
patina: {
|
|
2546
|
+
category: "material",
|
|
2547
|
+
description: "Decompose a surface photograph into PBR maps: basecolor, normal, roughness, metalness, height.",
|
|
2548
|
+
endpoint: "fal-ai/patina",
|
|
2549
|
+
inputField: "image_url",
|
|
2550
|
+
inputKind: "image",
|
|
2551
|
+
name: "Patina PBR Maps",
|
|
2552
|
+
outputKeys: ["images"],
|
|
2553
|
+
// Output order follows the request's `maps` array, so read that first;
|
|
2554
|
+
// the fallback is the endpoint's own schema default.
|
|
2555
|
+
outputLabels: {
|
|
2556
|
+
images: {
|
|
2557
|
+
fallback: ["basecolor", "normal", "roughness", "metalness", "height"],
|
|
2558
|
+
fromOption: "maps"
|
|
2559
|
+
}
|
|
2560
|
+
},
|
|
2561
|
+
price: { kind: "metered" },
|
|
2562
|
+
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",
|
|
2563
|
+
queued: true,
|
|
2564
|
+
sourceUrl: "https://fal.ai/models/fal-ai/patina",
|
|
2565
|
+
task: "PBR material decomposition"
|
|
2566
|
+
},
|
|
2567
|
+
"patina-extract": {
|
|
2568
|
+
category: "material",
|
|
2569
|
+
description: "Extract a tiling PBR material from a prompted region of a photograph.",
|
|
2570
|
+
endpoint: "fal-ai/patina/material/extract",
|
|
2571
|
+
inputField: "image_url",
|
|
2572
|
+
inputKind: "image",
|
|
2573
|
+
name: "Patina Material Extract",
|
|
2574
|
+
outputKeys: ["images"],
|
|
2575
|
+
// Same `maps` option and schema default as fal-ai/patina, both verified.
|
|
2576
|
+
// `num_images` above 1 multiplies the array and breaks the mapping, which
|
|
2577
|
+
// the count check catches.
|
|
2578
|
+
outputLabels: {
|
|
2579
|
+
images: {
|
|
2580
|
+
fallback: ["basecolor", "normal", "roughness", "metalness", "height"],
|
|
2581
|
+
fromOption: "maps"
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2584
|
+
price: { kind: "metered" },
|
|
2585
|
+
pricing: "$0.10 base only; add $0.02/megapixel plus $0.01/megapixel per map, so 1MP with all 5 maps is $0.17",
|
|
2586
|
+
queued: true,
|
|
2587
|
+
sourceUrl: "https://fal.ai/models/fal-ai/patina/material/extract",
|
|
2588
|
+
task: "tiling material extraction"
|
|
2589
|
+
},
|
|
2590
|
+
"qwen-layered": {
|
|
2591
|
+
category: "layers",
|
|
2592
|
+
description: "Split an image into a configurable number of stacked RGBA layers.",
|
|
2593
|
+
endpoint: "fal-ai/qwen-image-layered",
|
|
2594
|
+
inputField: "image_url",
|
|
2595
|
+
inputKind: "image",
|
|
2596
|
+
name: "Qwen Image Layered",
|
|
2597
|
+
outputKeys: ["images"],
|
|
2598
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2599
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2600
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2601
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2602
|
+
// rather than a number that is right only on one reading.
|
|
2603
|
+
price: { kind: "metered" },
|
|
2604
|
+
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",
|
|
2605
|
+
queued: true,
|
|
2606
|
+
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-layered",
|
|
2607
|
+
task: "image layer decomposition"
|
|
2608
|
+
},
|
|
2609
|
+
"recraft-vectorize": {
|
|
2610
|
+
category: "vector",
|
|
2611
|
+
description: "Convert a raster image into a clean SVG.",
|
|
2612
|
+
endpoint: "fal-ai/recraft/vectorize",
|
|
2613
|
+
inputField: "image_url",
|
|
2614
|
+
inputKind: "image",
|
|
2615
|
+
name: "Recraft Vectorize",
|
|
2616
|
+
outputKeys: ["image"],
|
|
2617
|
+
price: { kind: "call", usd: 0.04 },
|
|
2618
|
+
pricing: "$0.04/image, $0.08 with a vector style",
|
|
2619
|
+
sourceUrl: "https://fal.ai/models/fal-ai/recraft/vectorize",
|
|
2620
|
+
task: "raster to vector conversion"
|
|
2621
|
+
},
|
|
2622
|
+
"sam3-3d-align": {
|
|
2623
|
+
category: "3d",
|
|
2624
|
+
description: "Align SAM 3D objects and bodies into a shared scene.",
|
|
2625
|
+
endpoint: "fal-ai/sam-3/3d-align",
|
|
2626
|
+
inputField: "image_url",
|
|
2627
|
+
inputKind: "image",
|
|
2628
|
+
name: "SAM 3D Align",
|
|
2629
|
+
outputKeys: [
|
|
2630
|
+
"scene_glb",
|
|
2631
|
+
"model_glb",
|
|
2632
|
+
"visualization",
|
|
2633
|
+
"body_mesh_ply",
|
|
2634
|
+
"metadata"
|
|
2635
|
+
],
|
|
2636
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2637
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2638
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2639
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2640
|
+
// rather than a number that is right only on one reading.
|
|
2641
|
+
price: { kind: "metered" },
|
|
2642
|
+
pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
|
|
2643
|
+
queued: true,
|
|
2644
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
|
|
2645
|
+
task: "3D scene alignment"
|
|
2646
|
+
},
|
|
2647
|
+
"sam3-3d-body": {
|
|
2648
|
+
category: "3d",
|
|
2649
|
+
defaultOptions: {
|
|
2650
|
+
export_meshes: true,
|
|
2651
|
+
include_3d_keypoints: true,
|
|
2652
|
+
include_mhr_params: true
|
|
2653
|
+
},
|
|
2654
|
+
description: "Reconstruct human body meshes and keypoints from a single image.",
|
|
2655
|
+
endpoint: "fal-ai/sam-3/3d-body",
|
|
2656
|
+
inputField: "image_url",
|
|
2657
|
+
inputKind: "image",
|
|
2658
|
+
name: "SAM 3D Body",
|
|
2659
|
+
outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
|
|
2660
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2661
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2662
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2663
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2664
|
+
// rather than a number that is right only on one reading.
|
|
2665
|
+
price: { kind: "metered" },
|
|
2666
|
+
pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
|
|
2667
|
+
queued: true,
|
|
2668
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
|
|
2669
|
+
task: "single-image 3D body reconstruction"
|
|
2670
|
+
},
|
|
2671
|
+
"sam3-3d-objects": {
|
|
2672
|
+
category: "3d",
|
|
2673
|
+
defaultOptions: {
|
|
2674
|
+
prompt: "car"
|
|
2675
|
+
},
|
|
2676
|
+
description: "Reconstruct one or more 3D objects from an image and prompts.",
|
|
2677
|
+
endpoint: "fal-ai/sam-3/3d-objects",
|
|
2678
|
+
inputField: "image_url",
|
|
2679
|
+
inputKind: "image",
|
|
2680
|
+
name: "SAM 3D Objects",
|
|
2681
|
+
outputKeys: [
|
|
2682
|
+
"gaussian_splat",
|
|
2683
|
+
"model_glb",
|
|
2684
|
+
"metadata",
|
|
2685
|
+
"individual_splats",
|
|
2686
|
+
"individual_glbs",
|
|
2687
|
+
"artifacts_zip"
|
|
2688
|
+
],
|
|
2689
|
+
// fal publishes a figure but not what it counts. Encoding it as `call`
|
|
2690
|
+
// would assert a per-request price we cannot support, and this endpoint
|
|
2691
|
+
// returns several outputs — the same shape that made `seedream-layerize`
|
|
2692
|
+
// under-report by a factor of its layer count. `metered` reports null
|
|
2693
|
+
// rather than a number that is right only on one reading.
|
|
2694
|
+
price: { kind: "metered" },
|
|
2695
|
+
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",
|
|
2696
|
+
queued: true,
|
|
2697
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
|
|
2698
|
+
task: "single-image 3D object reconstruction"
|
|
2699
|
+
},
|
|
2700
|
+
"seedream-layerize": {
|
|
2701
|
+
category: "layers",
|
|
2702
|
+
description: "Split an image into editable Seedream layers plus flattened previews.",
|
|
2703
|
+
endpoint: "bytedance/seedream/v5/pro/layerize",
|
|
2704
|
+
inputField: "image_url",
|
|
2705
|
+
inputKind: "image",
|
|
2706
|
+
name: "Seedream v5 Pro Layerize",
|
|
2707
|
+
outputKeys: ["layers", "images"],
|
|
2708
|
+
price: { kind: "metered" },
|
|
2709
|
+
pricing: "$0.03375 per generated layer below 1536x1536 total area, $0.0675 per layer above; the listed rate is per layer, not per call",
|
|
2710
|
+
queued: true,
|
|
2711
|
+
sourceUrl: "https://fal.ai/models/bytedance/seedream/v5/pro/layerize",
|
|
2712
|
+
task: "image layer decomposition"
|
|
2713
|
+
},
|
|
2714
|
+
"seedvr-seamless": {
|
|
2715
|
+
category: "material",
|
|
2716
|
+
description: "Upscale a tiling texture while keeping its edges seamless. ",
|
|
2717
|
+
endpoint: "fal-ai/seedvr/upscale/image/seamless",
|
|
2718
|
+
inputField: "image_url",
|
|
2719
|
+
inputKind: "image",
|
|
2720
|
+
name: "SeedVR Seamless Upscale",
|
|
2721
|
+
outputKeys: ["image"],
|
|
2722
|
+
price: { kind: "megapixel", usd: 25e-4 },
|
|
2723
|
+
pricing: "$0.0025/megapixel",
|
|
2724
|
+
queued: true,
|
|
2725
|
+
sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image/seamless",
|
|
2726
|
+
task: "seamless texture upscaling"
|
|
2727
|
+
},
|
|
2728
|
+
"trellis-2": {
|
|
2729
|
+
category: "3d",
|
|
2730
|
+
description: "Reconstruct a textured 3D mesh from a single image, with UV unwrapping.",
|
|
2731
|
+
endpoint: "fal-ai/trellis-2",
|
|
2732
|
+
inputField: "image_url",
|
|
2733
|
+
inputKind: "image",
|
|
2734
|
+
name: "TRELLIS 2",
|
|
2735
|
+
outputKeys: ["model_glb"],
|
|
2736
|
+
price: { kind: "call", usd: 0.3 },
|
|
2737
|
+
pricing: "$0.30 at the default 1024p; $0.25 at 512p, $0.35 at 1536p",
|
|
2738
|
+
queued: true,
|
|
2739
|
+
sourceUrl: "https://fal.ai/models/fal-ai/trellis-2",
|
|
2740
|
+
task: "single-image 3D reconstruction"
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
|
|
2744
|
+
// src/tool-registry/background.ts
|
|
2745
|
+
var BACKGROUND_TOOLS = {
|
|
2746
|
+
"ben-v2": {
|
|
2747
|
+
category: "background",
|
|
2748
|
+
description: "Fast, high-quality background removal for images.",
|
|
2749
|
+
endpoint: "fal-ai/ben/v2/image",
|
|
2750
|
+
inputField: "image_url",
|
|
2751
|
+
inputKind: "image",
|
|
2752
|
+
name: "BEN v2 Background Removal",
|
|
2753
|
+
outputKeys: ["image"],
|
|
2754
|
+
price: { kind: "megapixel", usd: 0.025 },
|
|
2755
|
+
pricing: "$0.025/megapixel",
|
|
2756
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ben/v2/image",
|
|
2757
|
+
task: "image background removal"
|
|
2758
|
+
},
|
|
2265
2759
|
birefnet: {
|
|
2266
2760
|
category: "background",
|
|
2267
2761
|
defaultOptions: {
|
|
@@ -2274,41 +2768,455 @@ var FAL_TOOLS = {
|
|
|
2274
2768
|
endpoint: "fal-ai/birefnet/v2",
|
|
2275
2769
|
inputField: "image_url",
|
|
2276
2770
|
inputKind: "image",
|
|
2277
|
-
name: "BirefNet Background Removal",
|
|
2278
|
-
outputKeys: ["image", "mask_image"],
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2771
|
+
name: "BirefNet Background Removal",
|
|
2772
|
+
outputKeys: ["image", "mask_image"],
|
|
2773
|
+
price: { kind: "metered" },
|
|
2774
|
+
pricing: "$0/compute-second listed by fal",
|
|
2775
|
+
sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
|
|
2776
|
+
task: "image background removal"
|
|
2777
|
+
},
|
|
2778
|
+
"bria-rmbg": {
|
|
2779
|
+
category: "background",
|
|
2780
|
+
description: "Commercial-safe background removal for images.",
|
|
2781
|
+
endpoint: "fal-ai/bria/background/remove",
|
|
2782
|
+
inputField: "image_url",
|
|
2783
|
+
inputKind: "image",
|
|
2784
|
+
name: "Bria RMBG 2.0",
|
|
2785
|
+
outputKeys: ["image"],
|
|
2786
|
+
price: { kind: "call", usd: 0.018 },
|
|
2787
|
+
pricing: "$0.018/generation",
|
|
2788
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove",
|
|
2789
|
+
task: "image background removal"
|
|
2790
|
+
},
|
|
2791
|
+
"bria-video-rmbg": {
|
|
2792
|
+
category: "background",
|
|
2793
|
+
defaultOptions: {
|
|
2794
|
+
background_color: "Black",
|
|
2795
|
+
output_container_and_codec: "webm_vp9",
|
|
2796
|
+
preserve_audio: true
|
|
2797
|
+
},
|
|
2798
|
+
description: "Remove video backgrounds with configurable output container.",
|
|
2799
|
+
endpoint: "bria/video/background-removal",
|
|
2800
|
+
inputField: "video_url",
|
|
2801
|
+
inputKind: "video",
|
|
2802
|
+
name: "Bria Video Background Removal",
|
|
2803
|
+
outputKeys: ["video"],
|
|
2804
|
+
price: { kind: "second", usd: 425e-5 },
|
|
2805
|
+
pricing: "$0.00425/sec",
|
|
2806
|
+
queued: true,
|
|
2807
|
+
sourceUrl: "https://fal.ai/models/bria/video/background-removal",
|
|
2808
|
+
task: "video background removal"
|
|
2809
|
+
},
|
|
2810
|
+
ddcolor: {
|
|
2811
|
+
category: "restoration",
|
|
2812
|
+
description: "Colourise black-and-white photographs.",
|
|
2813
|
+
endpoint: "fal-ai/ddcolor",
|
|
2814
|
+
inputField: "image_url",
|
|
2815
|
+
inputKind: "image",
|
|
2816
|
+
name: "DDColor Colourisation",
|
|
2817
|
+
outputKeys: ["image"],
|
|
2818
|
+
price: { kind: "megapixel", usd: 1e-3 },
|
|
2819
|
+
pricing: "$0.001/megapixel",
|
|
2820
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ddcolor",
|
|
2821
|
+
task: "photo colourisation"
|
|
2822
|
+
},
|
|
2823
|
+
rembg: {
|
|
2824
|
+
category: "background",
|
|
2825
|
+
defaultOptions: {
|
|
2826
|
+
crop_to_bbox: false
|
|
2827
|
+
},
|
|
2828
|
+
description: "Generic image background removal utility.",
|
|
2829
|
+
endpoint: "fal-ai/imageutils/rembg",
|
|
2830
|
+
inputField: "image_url",
|
|
2831
|
+
inputKind: "image",
|
|
2832
|
+
name: "Remove Background",
|
|
2833
|
+
outputKeys: ["image"],
|
|
2834
|
+
price: { kind: "metered" },
|
|
2835
|
+
pricing: "$0/compute-second listed by fal",
|
|
2836
|
+
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
|
|
2837
|
+
task: "image background removal"
|
|
2838
|
+
},
|
|
2839
|
+
"seedvr-upscale": {
|
|
2840
|
+
category: "restoration",
|
|
2841
|
+
defaultOptions: {
|
|
2842
|
+
output_format: "png"
|
|
2843
|
+
},
|
|
2844
|
+
description: "Diffusion image restoration and upscaling with SeedVR.",
|
|
2845
|
+
endpoint: "fal-ai/seedvr/upscale/image",
|
|
2846
|
+
inputField: "image_url",
|
|
2847
|
+
inputKind: "image",
|
|
2848
|
+
name: "SeedVR Image Upscale",
|
|
2849
|
+
outputKeys: ["image"],
|
|
2850
|
+
price: { kind: "megapixel", usd: 1e-3 },
|
|
2851
|
+
pricing: "$0.001/megapixel",
|
|
2852
|
+
queued: true,
|
|
2853
|
+
sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image",
|
|
2854
|
+
task: "image restoration and upscaling"
|
|
2855
|
+
},
|
|
2856
|
+
"topaz-adjust": {
|
|
2857
|
+
category: "restoration",
|
|
2858
|
+
defaultOptions: {
|
|
2859
|
+
output_format: "png"
|
|
2860
|
+
},
|
|
2861
|
+
description: "Topaz exposure, white balance, and colour adjustment.",
|
|
2862
|
+
endpoint: "topaz/adjust/image",
|
|
2863
|
+
inputField: "image_url",
|
|
2864
|
+
inputKind: "image",
|
|
2865
|
+
name: "Topaz Adjust",
|
|
2866
|
+
outputKeys: ["image"],
|
|
2867
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2868
|
+
pricing: "$0.08 per 24 output megapixels",
|
|
2869
|
+
queued: true,
|
|
2870
|
+
sourceUrl: "https://fal.ai/models/topaz/adjust/image",
|
|
2871
|
+
task: "tonal adjustment"
|
|
2872
|
+
},
|
|
2873
|
+
"topaz-creative": {
|
|
2874
|
+
category: "restoration",
|
|
2875
|
+
defaultOptions: {
|
|
2876
|
+
output_format: "png"
|
|
2877
|
+
},
|
|
2878
|
+
description: "Topaz Bloom upscaling, which reimagines detail rather than preserving it.",
|
|
2879
|
+
endpoint: "topaz/upscale/image/creative",
|
|
2880
|
+
inputField: "image_url",
|
|
2881
|
+
inputKind: "image",
|
|
2882
|
+
name: "Topaz Creative Upscale",
|
|
2883
|
+
outputKeys: ["image"],
|
|
2884
|
+
price: { kind: "megapixel", usd: 0.96 / 24 },
|
|
2885
|
+
pricing: "$0.96 per 24 output megapixels",
|
|
2886
|
+
queued: true,
|
|
2887
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/creative",
|
|
2888
|
+
task: "creative image upscaling"
|
|
2889
|
+
},
|
|
2890
|
+
"topaz-denoise": {
|
|
2891
|
+
category: "restoration",
|
|
2892
|
+
defaultOptions: {
|
|
2893
|
+
output_format: "png"
|
|
2894
|
+
},
|
|
2895
|
+
description: "Topaz noise reduction at source resolution.",
|
|
2896
|
+
endpoint: "topaz/denoise/image",
|
|
2897
|
+
inputField: "image_url",
|
|
2898
|
+
inputKind: "image",
|
|
2899
|
+
name: "Topaz Denoise",
|
|
2900
|
+
outputKeys: ["image"],
|
|
2901
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2902
|
+
pricing: "$0.08 per 24 output megapixels at the default Normal model; $0.16 with Denoise Max",
|
|
2903
|
+
queued: true,
|
|
2904
|
+
sourceUrl: "https://fal.ai/models/topaz/denoise/image",
|
|
2905
|
+
task: "image denoising"
|
|
2906
|
+
},
|
|
2907
|
+
"topaz-generative": {
|
|
2908
|
+
category: "restoration",
|
|
2909
|
+
defaultOptions: {
|
|
2910
|
+
output_format: "png"
|
|
2911
|
+
},
|
|
2912
|
+
description: "Topaz Wonder upscaling, which synthesises plausible detail as it enlarges.",
|
|
2913
|
+
endpoint: "topaz/upscale/image/generative",
|
|
2914
|
+
inputField: "image_url",
|
|
2915
|
+
inputKind: "image",
|
|
2916
|
+
name: "Topaz Generative Upscale",
|
|
2917
|
+
outputKeys: ["image"],
|
|
2918
|
+
price: { kind: "megapixel", usd: 0.24 / 24 },
|
|
2919
|
+
pricing: "$0.24 per 24 output megapixels at the default Wonder 3; $0.48 with Wonder, Wonder 2, Standard MAX, Redefine or Recover 3",
|
|
2920
|
+
queued: true,
|
|
2921
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/generative",
|
|
2922
|
+
task: "generative image upscaling"
|
|
2923
|
+
},
|
|
2924
|
+
"topaz-image": {
|
|
2925
|
+
category: "upscale",
|
|
2926
|
+
defaultOptions: {
|
|
2927
|
+
model: "Standard V2",
|
|
2928
|
+
output_format: "jpeg",
|
|
2929
|
+
upscale_factor: 2
|
|
2930
|
+
},
|
|
2931
|
+
description: "Professional Topaz image enhancement and upscaling.",
|
|
2932
|
+
endpoint: "fal-ai/topaz/upscale/image",
|
|
2933
|
+
inputField: "image_url",
|
|
2934
|
+
inputKind: "image",
|
|
2935
|
+
name: "Topaz Image Upscale",
|
|
2936
|
+
outputKeys: ["image"],
|
|
2937
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2938
|
+
pricing: "$0.08 for output up to 24MP; $0.16 to 48MP, $0.32 to 96MP, up to $1.36 at 512MP",
|
|
2939
|
+
queued: true,
|
|
2940
|
+
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
|
|
2941
|
+
task: "image enhancement"
|
|
2942
|
+
},
|
|
2943
|
+
"topaz-precision": {
|
|
2944
|
+
category: "restoration",
|
|
2945
|
+
defaultOptions: {
|
|
2946
|
+
output_format: "png"
|
|
2947
|
+
},
|
|
2948
|
+
description: "Topaz upscaling that preserves the original detail rather than inventing it.",
|
|
2949
|
+
endpoint: "topaz/upscale/image/precision",
|
|
2950
|
+
inputField: "image_url",
|
|
2951
|
+
inputKind: "image",
|
|
2952
|
+
name: "Topaz Precision Upscale",
|
|
2953
|
+
outputKeys: ["image"],
|
|
2954
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2955
|
+
pricing: "$0.08 per 24 output megapixels, any precision model",
|
|
2956
|
+
queued: true,
|
|
2957
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/precision",
|
|
2958
|
+
task: "faithful image upscaling"
|
|
2959
|
+
},
|
|
2960
|
+
"topaz-restore": {
|
|
2961
|
+
category: "restoration",
|
|
2962
|
+
defaultOptions: {
|
|
2963
|
+
output_format: "png"
|
|
2964
|
+
},
|
|
2965
|
+
description: "Topaz restoration of damaged or degraded photographs at source resolution.",
|
|
2966
|
+
endpoint: "topaz/restore/image",
|
|
2967
|
+
inputField: "image_url",
|
|
2968
|
+
inputKind: "image",
|
|
2969
|
+
name: "Topaz Restore",
|
|
2970
|
+
outputKeys: ["image"],
|
|
2971
|
+
price: { kind: "megapixel", usd: 0.48 / 24 },
|
|
2972
|
+
pricing: "$0.48 per 24 output megapixels at the default Recover 3; $0.08 with Dust-Scratch V2",
|
|
2973
|
+
queued: true,
|
|
2974
|
+
sourceUrl: "https://fal.ai/models/topaz/restore/image",
|
|
2975
|
+
task: "photo restoration"
|
|
2976
|
+
},
|
|
2977
|
+
"topaz-sharpen": {
|
|
2978
|
+
category: "restoration",
|
|
2979
|
+
defaultOptions: {
|
|
2980
|
+
output_format: "png"
|
|
2981
|
+
},
|
|
2982
|
+
description: "Topaz deblurring and sharpening at source resolution.",
|
|
2983
|
+
endpoint: "topaz/sharpen/image",
|
|
2984
|
+
inputField: "image_url",
|
|
2985
|
+
inputKind: "image",
|
|
2986
|
+
name: "Topaz Sharpen",
|
|
2987
|
+
outputKeys: ["image"],
|
|
2988
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
2989
|
+
pricing: "$0.08 per 24 output megapixels at the default Standard model; $0.16 with Super Focus",
|
|
2990
|
+
queued: true,
|
|
2991
|
+
sourceUrl: "https://fal.ai/models/topaz/sharpen/image",
|
|
2992
|
+
task: "image sharpening"
|
|
2993
|
+
},
|
|
2994
|
+
"topaz-transparent": {
|
|
2995
|
+
category: "restoration",
|
|
2996
|
+
description: "Topaz upscaling that preserves the alpha channel end to end.",
|
|
2997
|
+
endpoint: "topaz/upscale/image/transparent",
|
|
2998
|
+
inputField: "image_url",
|
|
2999
|
+
inputKind: "image",
|
|
3000
|
+
name: "Topaz Transparent Upscale",
|
|
3001
|
+
outputKeys: ["image"],
|
|
3002
|
+
price: { kind: "megapixel", usd: 0.08 / 24 },
|
|
3003
|
+
pricing: "$0.08 per 24 output megapixels",
|
|
3004
|
+
queued: true,
|
|
3005
|
+
sourceUrl: "https://fal.ai/models/topaz/upscale/image/transparent",
|
|
3006
|
+
task: "transparent image upscaling"
|
|
3007
|
+
},
|
|
3008
|
+
"topaz-video": {
|
|
3009
|
+
category: "upscale",
|
|
3010
|
+
defaultOptions: {
|
|
3011
|
+
model: "Proteus",
|
|
3012
|
+
upscale_factor: 2
|
|
3013
|
+
},
|
|
3014
|
+
description: "Professional Topaz video enhancement and upscaling.",
|
|
3015
|
+
endpoint: "fal-ai/topaz/upscale/video",
|
|
3016
|
+
inputField: "video_url",
|
|
3017
|
+
inputKind: "video",
|
|
3018
|
+
name: "Topaz Video Upscale",
|
|
3019
|
+
outputKeys: ["video"],
|
|
3020
|
+
price: { kind: "second", usd: 0.01 },
|
|
3021
|
+
pricing: "$0.01/sec up to 720p; $0.02 to 1080p, $0.08 above, doubled at 60fps, halved with Gaia 2",
|
|
3022
|
+
queued: true,
|
|
3023
|
+
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
|
|
3024
|
+
task: "video enhancement"
|
|
3025
|
+
}
|
|
3026
|
+
};
|
|
3027
|
+
|
|
3028
|
+
// src/tool-registry/editing.ts
|
|
3029
|
+
var EDITING_TOOLS = {
|
|
3030
|
+
"bria-eraser": {
|
|
3031
|
+
category: "erase",
|
|
3032
|
+
description: "Commercial-safe removal of a masked region, inpainted from surrounding context.",
|
|
3033
|
+
endpoint: "fal-ai/bria/eraser",
|
|
3034
|
+
inputField: "image_url",
|
|
3035
|
+
inputKind: "image",
|
|
3036
|
+
name: "Bria Eraser",
|
|
3037
|
+
outputKeys: ["image"],
|
|
3038
|
+
price: { kind: "call", usd: 0.04 },
|
|
3039
|
+
pricing: "$0.04/generation",
|
|
3040
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/eraser",
|
|
3041
|
+
task: "masked object erasure"
|
|
3042
|
+
},
|
|
3043
|
+
"bria-expand": {
|
|
3044
|
+
category: "reframe",
|
|
3045
|
+
description: "Expand an image onto a larger canvas, generating the new border area.",
|
|
3046
|
+
endpoint: "fal-ai/bria/expand",
|
|
3047
|
+
inputField: "image_url",
|
|
3048
|
+
inputKind: "image",
|
|
3049
|
+
name: "Bria Image Expansion",
|
|
3050
|
+
outputKeys: ["image"],
|
|
3051
|
+
price: { kind: "call", usd: 0.04 },
|
|
3052
|
+
pricing: "$0.04/generation",
|
|
3053
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/expand",
|
|
3054
|
+
task: "image outpainting"
|
|
3055
|
+
},
|
|
3056
|
+
"bria-genfill": {
|
|
3057
|
+
category: "erase",
|
|
3058
|
+
description: "Generative fill of a masked region from a prompt, commercial-safe.",
|
|
3059
|
+
endpoint: "fal-ai/bria/genfill",
|
|
3060
|
+
inputField: "image_url",
|
|
3061
|
+
inputKind: "image",
|
|
3062
|
+
name: "Bria GenFill",
|
|
3063
|
+
outputKeys: ["images"],
|
|
3064
|
+
price: { kind: "call", usd: 0.04 },
|
|
3065
|
+
pricing: "$0.04/generation",
|
|
3066
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/genfill",
|
|
3067
|
+
task: "masked generative fill"
|
|
3068
|
+
},
|
|
3069
|
+
"finegrain-eraser": {
|
|
3070
|
+
category: "erase",
|
|
3071
|
+
description: "Remove a prompted object and its shadows and reflections, filling the gap.",
|
|
3072
|
+
endpoint: "fal-ai/finegrain-eraser",
|
|
3073
|
+
inputField: "image_url",
|
|
3074
|
+
inputKind: "image",
|
|
3075
|
+
name: "Finegrain Object Eraser",
|
|
3076
|
+
outputKeys: ["image"],
|
|
3077
|
+
price: { kind: "call", usd: 0.27 },
|
|
3078
|
+
pricing: "$0.27/image at the default standard mode; $0.18 express, $0.36 premium",
|
|
3079
|
+
sourceUrl: "https://fal.ai/models/fal-ai/finegrain-eraser",
|
|
3080
|
+
task: "prompted object erasure"
|
|
3081
|
+
},
|
|
3082
|
+
"flux-outpaint": {
|
|
3083
|
+
category: "reframe",
|
|
3084
|
+
description: "Outpaint an image by a per-edge pixel expansion with FLUX.2 pro.",
|
|
3085
|
+
endpoint: "fal-ai/flux-2-pro/outpaint",
|
|
3086
|
+
inputField: "image_url",
|
|
3087
|
+
inputKind: "image",
|
|
3088
|
+
name: "FLUX.2 Pro Outpaint",
|
|
3089
|
+
outputKeys: ["images"],
|
|
3090
|
+
price: { kind: "metered" },
|
|
3091
|
+
pricing: "$0.03 for the first output megapixel, then $0.015 per extra megapixel of input and output, rounded up",
|
|
3092
|
+
sourceUrl: "https://fal.ai/models/fal-ai/flux-2-pro/outpaint",
|
|
3093
|
+
task: "image outpainting"
|
|
3094
|
+
},
|
|
3095
|
+
"iclight-v2": {
|
|
3096
|
+
category: "relight",
|
|
3097
|
+
description: "Relight a subject from a prompt, harmonising it with a new light direction.",
|
|
3098
|
+
endpoint: "fal-ai/iclight-v2",
|
|
3099
|
+
inputField: "image_url",
|
|
3100
|
+
inputKind: "image",
|
|
3101
|
+
name: "IC-Light v2",
|
|
3102
|
+
outputKeys: ["images"],
|
|
3103
|
+
price: { kind: "megapixel", usd: 0.1 },
|
|
3104
|
+
pricing: "$0.10/megapixel",
|
|
3105
|
+
sourceUrl: "https://fal.ai/models/fal-ai/iclight-v2",
|
|
3106
|
+
task: "prompted relighting"
|
|
3107
|
+
},
|
|
3108
|
+
"ideogram-reframe": {
|
|
3109
|
+
category: "reframe",
|
|
3110
|
+
description: "Reframe an image to a new aspect ratio, generating the fill.",
|
|
3111
|
+
endpoint: "fal-ai/ideogram/v3/reframe",
|
|
3112
|
+
inputField: "image_url",
|
|
3113
|
+
inputKind: "image",
|
|
3114
|
+
name: "Ideogram v3 Reframe",
|
|
3115
|
+
outputKeys: ["images"],
|
|
3116
|
+
price: { kind: "call", usd: 0.06 },
|
|
3117
|
+
pricing: "$0.06/image at the default BALANCED speed; $0.03 turbo, $0.09 quality",
|
|
3118
|
+
sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/reframe",
|
|
3119
|
+
task: "aspect ratio reframing"
|
|
3120
|
+
},
|
|
3121
|
+
"lighting-restoration": {
|
|
3122
|
+
category: "relight",
|
|
3123
|
+
description: "Restore natural, even lighting across one or more input images.",
|
|
3124
|
+
endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
|
|
3125
|
+
inputField: "image_urls",
|
|
3126
|
+
inputKind: "images",
|
|
3127
|
+
name: "Qwen Lighting Restoration",
|
|
3128
|
+
outputKeys: ["images"],
|
|
3129
|
+
price: { kind: "megapixel", usd: 0.035 },
|
|
3130
|
+
pricing: "$0.035/megapixel",
|
|
3131
|
+
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
|
|
3132
|
+
task: "lighting restoration"
|
|
3133
|
+
},
|
|
3134
|
+
"object-removal": {
|
|
3135
|
+
category: "erase",
|
|
3136
|
+
description: "Remove a prompted object from an image and fill the gap.",
|
|
3137
|
+
endpoint: "fal-ai/object-removal",
|
|
3138
|
+
inputField: "image_url",
|
|
3139
|
+
inputKind: "image",
|
|
3140
|
+
name: "Object Removal",
|
|
3141
|
+
outputKeys: ["images"],
|
|
3142
|
+
price: { kind: "call", usd: 0.024 },
|
|
3143
|
+
pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
|
|
3144
|
+
sourceUrl: "https://fal.ai/models/fal-ai/object-removal",
|
|
3145
|
+
task: "prompted object removal"
|
|
2282
3146
|
},
|
|
2283
|
-
"
|
|
2284
|
-
category: "
|
|
2285
|
-
description: "
|
|
2286
|
-
endpoint: "fal-ai/
|
|
3147
|
+
"object-removal-bbox": {
|
|
3148
|
+
category: "erase",
|
|
3149
|
+
description: "Remove whatever falls inside supplied bounding boxes.",
|
|
3150
|
+
endpoint: "fal-ai/object-removal/bbox",
|
|
2287
3151
|
inputField: "image_url",
|
|
2288
3152
|
inputKind: "image",
|
|
2289
|
-
name: "
|
|
2290
|
-
outputKeys: ["
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
3153
|
+
name: "Object Removal by Box",
|
|
3154
|
+
outputKeys: ["images"],
|
|
3155
|
+
price: { kind: "call", usd: 0.024 },
|
|
3156
|
+
pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
|
|
3157
|
+
sourceUrl: "https://fal.ai/models/fal-ai/object-removal/bbox",
|
|
3158
|
+
task: "bounding box object removal"
|
|
2294
3159
|
},
|
|
2295
|
-
"
|
|
2296
|
-
category: "
|
|
3160
|
+
"object-removal-mask": {
|
|
3161
|
+
category: "erase",
|
|
3162
|
+
description: "Remove the masked region of an image and fill the gap.",
|
|
3163
|
+
endpoint: "fal-ai/object-removal/mask",
|
|
3164
|
+
inputField: "image_url",
|
|
3165
|
+
inputKind: "image",
|
|
3166
|
+
name: "Object Removal by Mask",
|
|
3167
|
+
outputKeys: ["images"],
|
|
3168
|
+
price: { kind: "call", usd: 0.024 },
|
|
3169
|
+
pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
|
|
3170
|
+
sourceUrl: "https://fal.ai/models/fal-ai/object-removal/mask",
|
|
3171
|
+
task: "masked object removal"
|
|
3172
|
+
},
|
|
3173
|
+
"remove-lighting": {
|
|
3174
|
+
category: "relight",
|
|
3175
|
+
description: "Strip baked-in lighting and shadows to leave a flat, neutral surface.",
|
|
3176
|
+
endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
|
|
3177
|
+
inputField: "image_urls",
|
|
3178
|
+
inputKind: "images",
|
|
3179
|
+
name: "Qwen Remove Lighting",
|
|
3180
|
+
outputKeys: ["images"],
|
|
3181
|
+
price: { kind: "megapixel", usd: 0.035 },
|
|
3182
|
+
pricing: "$0.035/megapixel",
|
|
3183
|
+
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
|
|
3184
|
+
task: "lighting removal"
|
|
3185
|
+
},
|
|
3186
|
+
"smart-resize": {
|
|
3187
|
+
category: "reframe",
|
|
3188
|
+
description: "Resize one image to several target sizes, recomposing rather than cropping.",
|
|
3189
|
+
endpoint: "fal-ai/smart-resize",
|
|
3190
|
+
inputField: "image_url",
|
|
3191
|
+
inputKind: "image",
|
|
3192
|
+
name: "Smart Resize",
|
|
3193
|
+
outputKeys: ["images", "results"],
|
|
3194
|
+
price: { kind: "metered" },
|
|
3195
|
+
pricing: "$0.15 per output image, doubled at 4K, plus a $0.05 vision analysis fee per request",
|
|
3196
|
+
queued: true,
|
|
3197
|
+
sourceUrl: "https://fal.ai/models/fal-ai/smart-resize",
|
|
3198
|
+
task: "multi-size recomposition"
|
|
3199
|
+
},
|
|
3200
|
+
"text-removal": {
|
|
3201
|
+
category: "erase",
|
|
2297
3202
|
defaultOptions: {
|
|
2298
|
-
|
|
2299
|
-
output_container_and_codec: "webm_vp9",
|
|
2300
|
-
preserve_audio: true
|
|
3203
|
+
output_format: "png"
|
|
2301
3204
|
},
|
|
2302
|
-
description: "Remove
|
|
2303
|
-
endpoint: "
|
|
2304
|
-
inputField: "
|
|
2305
|
-
inputKind: "
|
|
2306
|
-
name: "
|
|
2307
|
-
outputKeys: ["
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
3205
|
+
description: "Remove all rendered text from an image, rebuilding what sat behind it.",
|
|
3206
|
+
endpoint: "fal-ai/image-editing/text-removal",
|
|
3207
|
+
inputField: "image_url",
|
|
3208
|
+
inputKind: "image",
|
|
3209
|
+
name: "Text Removal",
|
|
3210
|
+
outputKeys: ["images"],
|
|
3211
|
+
price: { kind: "call", usd: 0.04 },
|
|
3212
|
+
pricing: "$0.04/image",
|
|
3213
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-editing/text-removal",
|
|
3214
|
+
task: "text removal"
|
|
3215
|
+
}
|
|
3216
|
+
};
|
|
3217
|
+
|
|
3218
|
+
// src/tool-registry/structure.ts
|
|
3219
|
+
var STRUCTURE_TOOLS = {
|
|
2312
3220
|
"depth-anything": {
|
|
2313
3221
|
category: "preprocess",
|
|
2314
3222
|
description: "Generate Depth Anything v2 depth maps from input images.",
|
|
@@ -2317,10 +3225,37 @@ var FAL_TOOLS = {
|
|
|
2317
3225
|
inputKind: "image",
|
|
2318
3226
|
name: "Depth Anything v2 Preprocessor",
|
|
2319
3227
|
outputKeys: ["image"],
|
|
3228
|
+
price: { kind: "metered" },
|
|
2320
3229
|
pricing: "$0/compute-second listed by fal",
|
|
2321
3230
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
|
|
2322
3231
|
task: "depth preprocessing"
|
|
2323
3232
|
},
|
|
3233
|
+
dwpose: {
|
|
3234
|
+
category: "preprocess",
|
|
3235
|
+
description: "Extract body, hand, and face pose skeletons from an image.",
|
|
3236
|
+
endpoint: "fal-ai/dwpose",
|
|
3237
|
+
inputField: "image_url",
|
|
3238
|
+
inputKind: "image",
|
|
3239
|
+
name: "DWPose Estimation",
|
|
3240
|
+
outputKeys: ["image"],
|
|
3241
|
+
price: { kind: "second", usd: 6e-4 },
|
|
3242
|
+
pricing: "$0.0006/compute-second",
|
|
3243
|
+
sourceUrl: "https://fal.ai/models/fal-ai/dwpose",
|
|
3244
|
+
task: "pose preprocessing"
|
|
3245
|
+
},
|
|
3246
|
+
hed: {
|
|
3247
|
+
category: "preprocess",
|
|
3248
|
+
description: "Generate HED soft edge maps for ControlNet-style workflows.",
|
|
3249
|
+
endpoint: "fal-ai/image-preprocessors/hed",
|
|
3250
|
+
inputField: "image_url",
|
|
3251
|
+
inputKind: "image",
|
|
3252
|
+
name: "HED Edge Preprocessor",
|
|
3253
|
+
outputKeys: ["image"],
|
|
3254
|
+
price: { kind: "metered" },
|
|
3255
|
+
pricing: "$0/compute-second listed by fal",
|
|
3256
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/hed",
|
|
3257
|
+
task: "edge preprocessing"
|
|
3258
|
+
},
|
|
2324
3259
|
lineart: {
|
|
2325
3260
|
category: "preprocess",
|
|
2326
3261
|
defaultOptions: {
|
|
@@ -2332,6 +3267,7 @@ var FAL_TOOLS = {
|
|
|
2332
3267
|
inputKind: "image",
|
|
2333
3268
|
name: "Line Art Preprocessor",
|
|
2334
3269
|
outputKeys: ["image"],
|
|
3270
|
+
price: { kind: "metered" },
|
|
2335
3271
|
pricing: "$0/compute-second listed by fal",
|
|
2336
3272
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
|
|
2337
3273
|
task: "image preprocessing"
|
|
@@ -2348,6 +3284,7 @@ var FAL_TOOLS = {
|
|
|
2348
3284
|
inputKind: "image",
|
|
2349
3285
|
name: "Marigold Depth Estimation",
|
|
2350
3286
|
outputKeys: ["image"],
|
|
3287
|
+
price: { kind: "metered" },
|
|
2351
3288
|
pricing: "$0/compute-second listed by fal",
|
|
2352
3289
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
|
|
2353
3290
|
task: "depth map"
|
|
@@ -2364,6 +3301,7 @@ var FAL_TOOLS = {
|
|
|
2364
3301
|
inputKind: "image",
|
|
2365
3302
|
name: "MiDaS Depth Estimation",
|
|
2366
3303
|
outputKeys: ["image"],
|
|
3304
|
+
price: { kind: "metered" },
|
|
2367
3305
|
pricing: "$0/compute-second listed by fal",
|
|
2368
3306
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
|
|
2369
3307
|
task: "depth map"
|
|
@@ -2376,36 +3314,36 @@ var FAL_TOOLS = {
|
|
|
2376
3314
|
inputKind: "image",
|
|
2377
3315
|
name: "MiDaS Preprocessor",
|
|
2378
3316
|
outputKeys: ["depth_map", "normal_map"],
|
|
3317
|
+
price: { kind: "metered" },
|
|
2379
3318
|
pricing: "$0/compute-second listed by fal",
|
|
2380
3319
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
|
|
2381
3320
|
task: "depth and normal preprocessing"
|
|
2382
3321
|
},
|
|
2383
|
-
|
|
2384
|
-
category: "
|
|
2385
|
-
description: "
|
|
2386
|
-
endpoint: "fal-ai/
|
|
2387
|
-
inputField: "
|
|
2388
|
-
inputKind: "
|
|
2389
|
-
name: "
|
|
2390
|
-
outputKeys: ["
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
3322
|
+
mlsd: {
|
|
3323
|
+
category: "preprocess",
|
|
3324
|
+
description: "Detect straight line segments for architectural and interior control maps.",
|
|
3325
|
+
endpoint: "fal-ai/image-preprocessors/mlsd",
|
|
3326
|
+
inputField: "image_url",
|
|
3327
|
+
inputKind: "image",
|
|
3328
|
+
name: "M-LSD Line Preprocessor",
|
|
3329
|
+
outputKeys: ["image"],
|
|
3330
|
+
price: { kind: "metered" },
|
|
3331
|
+
pricing: "$0/compute-second listed by fal",
|
|
3332
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/mlsd",
|
|
3333
|
+
task: "line segment preprocessing"
|
|
2394
3334
|
},
|
|
2395
|
-
|
|
2396
|
-
category: "
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
},
|
|
2400
|
-
description: "Generic image background removal utility.",
|
|
2401
|
-
endpoint: "fal-ai/imageutils/rembg",
|
|
3335
|
+
pidi: {
|
|
3336
|
+
category: "preprocess",
|
|
3337
|
+
description: "Generate PiDiNet soft edge maps from an input image.",
|
|
3338
|
+
endpoint: "fal-ai/image-preprocessors/pidi",
|
|
2402
3339
|
inputField: "image_url",
|
|
2403
3340
|
inputKind: "image",
|
|
2404
|
-
name: "
|
|
3341
|
+
name: "PiDiNet Edge Preprocessor",
|
|
2405
3342
|
outputKeys: ["image"],
|
|
3343
|
+
price: { kind: "metered" },
|
|
2406
3344
|
pricing: "$0/compute-second listed by fal",
|
|
2407
|
-
sourceUrl: "https://fal.ai/models/fal-ai/
|
|
2408
|
-
task: "
|
|
3345
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/pidi",
|
|
3346
|
+
task: "edge preprocessing"
|
|
2409
3347
|
},
|
|
2410
3348
|
"sam-preprocessor": {
|
|
2411
3349
|
category: "preprocess",
|
|
@@ -2415,6 +3353,7 @@ var FAL_TOOLS = {
|
|
|
2415
3353
|
inputKind: "image",
|
|
2416
3354
|
name: "SAM Preprocessor",
|
|
2417
3355
|
outputKeys: ["image"],
|
|
3356
|
+
price: { kind: "metered" },
|
|
2418
3357
|
pricing: "$0/compute-second listed by fal",
|
|
2419
3358
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
|
|
2420
3359
|
task: "segmentation preprocessing"
|
|
@@ -2434,7 +3373,9 @@ var FAL_TOOLS = {
|
|
|
2434
3373
|
inputKind: "image",
|
|
2435
3374
|
name: "SAM 2 Auto Segment",
|
|
2436
3375
|
outputKeys: ["combined_mask", "individual_masks"],
|
|
3376
|
+
price: { kind: "metered" },
|
|
2437
3377
|
pricing: "$0/compute-second listed by fal",
|
|
3378
|
+
queued: true,
|
|
2438
3379
|
sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
|
|
2439
3380
|
task: "automatic image segmentation"
|
|
2440
3381
|
},
|
|
@@ -2451,7 +3392,8 @@ var FAL_TOOLS = {
|
|
|
2451
3392
|
inputKind: "image",
|
|
2452
3393
|
name: "SAM 3.1 Image",
|
|
2453
3394
|
outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
|
|
2454
|
-
|
|
3395
|
+
price: { kind: "call", usd: 0.01 },
|
|
3396
|
+
pricing: "$0.01/request",
|
|
2455
3397
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
|
|
2456
3398
|
task: "promptable image segmentation"
|
|
2457
3399
|
},
|
|
@@ -2467,61 +3409,12 @@ var FAL_TOOLS = {
|
|
|
2467
3409
|
inputKind: "video",
|
|
2468
3410
|
name: "SAM 3.1 Video",
|
|
2469
3411
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2470
|
-
|
|
3412
|
+
price: { kind: "metered" },
|
|
3413
|
+
pricing: "$0.01/16 frames of video input",
|
|
3414
|
+
queued: true,
|
|
2471
3415
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
|
|
2472
3416
|
task: "multi-object video segmentation"
|
|
2473
3417
|
},
|
|
2474
|
-
"sam3-3d-align": {
|
|
2475
|
-
category: "3d",
|
|
2476
|
-
description: "Align SAM 3D objects and bodies into a shared scene.",
|
|
2477
|
-
endpoint: "fal-ai/sam-3/3d-align",
|
|
2478
|
-
inputField: "image_url",
|
|
2479
|
-
inputKind: "image",
|
|
2480
|
-
name: "SAM 3D Align",
|
|
2481
|
-
outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
|
|
2482
|
-
pricing: "fal pricing varies by scene",
|
|
2483
|
-
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
|
|
2484
|
-
task: "3D scene alignment"
|
|
2485
|
-
},
|
|
2486
|
-
"sam3-3d-body": {
|
|
2487
|
-
category: "3d",
|
|
2488
|
-
defaultOptions: {
|
|
2489
|
-
export_meshes: true,
|
|
2490
|
-
include_3d_keypoints: true,
|
|
2491
|
-
include_mhr_params: true
|
|
2492
|
-
},
|
|
2493
|
-
description: "Reconstruct human body meshes and keypoints from a single image.",
|
|
2494
|
-
endpoint: "fal-ai/sam-3/3d-body",
|
|
2495
|
-
inputField: "image_url",
|
|
2496
|
-
inputKind: "image",
|
|
2497
|
-
name: "SAM 3D Body",
|
|
2498
|
-
outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
|
|
2499
|
-
pricing: "$0.015/inference",
|
|
2500
|
-
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
|
|
2501
|
-
task: "single-image 3D body reconstruction"
|
|
2502
|
-
},
|
|
2503
|
-
"sam3-3d-objects": {
|
|
2504
|
-
category: "3d",
|
|
2505
|
-
defaultOptions: {
|
|
2506
|
-
prompt: "car"
|
|
2507
|
-
},
|
|
2508
|
-
description: "Reconstruct one or more 3D objects from an image and prompts.",
|
|
2509
|
-
endpoint: "fal-ai/sam-3/3d-objects",
|
|
2510
|
-
inputField: "image_url",
|
|
2511
|
-
inputKind: "image",
|
|
2512
|
-
name: "SAM 3D Objects",
|
|
2513
|
-
outputKeys: [
|
|
2514
|
-
"gaussian_splat",
|
|
2515
|
-
"model_glb",
|
|
2516
|
-
"metadata",
|
|
2517
|
-
"individual_splats",
|
|
2518
|
-
"individual_glbs",
|
|
2519
|
-
"artifacts_zip"
|
|
2520
|
-
],
|
|
2521
|
-
pricing: "$0.02/generation",
|
|
2522
|
-
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
|
|
2523
|
-
task: "single-image 3D object reconstruction"
|
|
2524
|
-
},
|
|
2525
3418
|
"sam3-image": {
|
|
2526
3419
|
category: "segmentation",
|
|
2527
3420
|
defaultOptions: {
|
|
@@ -2535,6 +3428,7 @@ var FAL_TOOLS = {
|
|
|
2535
3428
|
inputKind: "image",
|
|
2536
3429
|
name: "SAM 3 Image",
|
|
2537
3430
|
outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
|
|
3431
|
+
price: { kind: "call", usd: 5e-3 },
|
|
2538
3432
|
pricing: "$0.005/request",
|
|
2539
3433
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
|
|
2540
3434
|
task: "promptable image segmentation"
|
|
@@ -2550,7 +3444,8 @@ var FAL_TOOLS = {
|
|
|
2550
3444
|
inputField: "image_url",
|
|
2551
3445
|
inputKind: "image",
|
|
2552
3446
|
name: "SAM 3 Image RLE",
|
|
2553
|
-
outputKeys: ["
|
|
3447
|
+
outputKeys: ["rle", "scores", "boxes", "metadata"],
|
|
3448
|
+
price: { kind: "call", usd: 5e-3 },
|
|
2554
3449
|
pricing: "$0.005/request",
|
|
2555
3450
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
|
|
2556
3451
|
task: "promptable image segmentation to RLE"
|
|
@@ -2569,7 +3464,9 @@ var FAL_TOOLS = {
|
|
|
2569
3464
|
inputKind: "video",
|
|
2570
3465
|
name: "SAM 3 Video",
|
|
2571
3466
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2572
|
-
|
|
3467
|
+
price: { kind: "metered" },
|
|
3468
|
+
pricing: "$0.005/16 frames of video input",
|
|
3469
|
+
queued: true,
|
|
2573
3470
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
|
|
2574
3471
|
task: "promptable video segmentation"
|
|
2575
3472
|
},
|
|
@@ -2585,45 +3482,65 @@ var FAL_TOOLS = {
|
|
|
2585
3482
|
inputField: "video_url",
|
|
2586
3483
|
inputKind: "video",
|
|
2587
3484
|
name: "SAM 3 Video RLE",
|
|
2588
|
-
|
|
2589
|
-
|
|
3485
|
+
// fal's video-rle endpoint no longer returns RLE data: its Output schema is
|
|
3486
|
+
// now identical to sam3-video's, so this entry differs only by endpoint.
|
|
3487
|
+
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
3488
|
+
price: { kind: "metered" },
|
|
3489
|
+
pricing: "$0.005/16 frames of video",
|
|
3490
|
+
queued: true,
|
|
2590
3491
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
|
|
2591
3492
|
task: "promptable video segmentation to RLE"
|
|
2592
3493
|
},
|
|
2593
|
-
|
|
2594
|
-
category: "
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
output_format: "jpeg",
|
|
2598
|
-
upscale_factor: 2
|
|
2599
|
-
},
|
|
2600
|
-
description: "Professional Topaz image enhancement and upscaling.",
|
|
2601
|
-
endpoint: "fal-ai/topaz/upscale/image",
|
|
3494
|
+
scribble: {
|
|
3495
|
+
category: "preprocess",
|
|
3496
|
+
description: "Turn an image into a scribble-style control map.",
|
|
3497
|
+
endpoint: "fal-ai/image-preprocessors/scribble",
|
|
2602
3498
|
inputField: "image_url",
|
|
2603
3499
|
inputKind: "image",
|
|
2604
|
-
name: "
|
|
3500
|
+
name: "Scribble Preprocessor",
|
|
2605
3501
|
outputKeys: ["image"],
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
3502
|
+
price: { kind: "metered" },
|
|
3503
|
+
pricing: "$0/compute-second listed by fal",
|
|
3504
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/scribble",
|
|
3505
|
+
task: "scribble preprocessing"
|
|
2609
3506
|
},
|
|
2610
|
-
|
|
2611
|
-
category: "
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
3507
|
+
teed: {
|
|
3508
|
+
category: "preprocess",
|
|
3509
|
+
description: "Generate TEED edge maps from an input image.",
|
|
3510
|
+
endpoint: "fal-ai/image-preprocessors/teed",
|
|
3511
|
+
inputField: "image_url",
|
|
3512
|
+
inputKind: "image",
|
|
3513
|
+
name: "TEED Edge Preprocessor",
|
|
3514
|
+
outputKeys: ["image"],
|
|
3515
|
+
price: { kind: "metered" },
|
|
3516
|
+
pricing: "$0/compute-second listed by fal",
|
|
3517
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/teed",
|
|
3518
|
+
task: "edge preprocessing"
|
|
3519
|
+
},
|
|
3520
|
+
"zoe-depth": {
|
|
3521
|
+
category: "preprocess",
|
|
3522
|
+
description: "Generate ZoeDepth metric depth maps from an input image.",
|
|
3523
|
+
endpoint: "fal-ai/image-preprocessors/zoe",
|
|
3524
|
+
inputField: "image_url",
|
|
3525
|
+
inputKind: "image",
|
|
3526
|
+
name: "ZoeDepth Preprocessor",
|
|
3527
|
+
outputKeys: ["image"],
|
|
3528
|
+
price: { kind: "metered" },
|
|
3529
|
+
pricing: "$0/compute-second listed by fal",
|
|
3530
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/zoe",
|
|
3531
|
+
task: "depth preprocessing"
|
|
2625
3532
|
}
|
|
2626
3533
|
};
|
|
3534
|
+
|
|
3535
|
+
// src/tools.ts
|
|
3536
|
+
var FAL_EXPLORE_CHECKED_AT = "2026-08-23";
|
|
3537
|
+
var FAL_TOOLS = {
|
|
3538
|
+
...ANALYSIS_TOOLS,
|
|
3539
|
+
...ASSET_TOOLS,
|
|
3540
|
+
...BACKGROUND_TOOLS,
|
|
3541
|
+
...EDITING_TOOLS,
|
|
3542
|
+
...STRUCTURE_TOOLS
|
|
3543
|
+
};
|
|
2627
3544
|
var FAL_TOOLS_CHECKED_AT = FAL_EXPLORE_CHECKED_AT;
|
|
2628
3545
|
var FAL_TOOL_IDS = Object.keys(
|
|
2629
3546
|
FAL_TOOLS
|
|
@@ -2650,9 +3567,110 @@ function buildFalToolRequest(options) {
|
|
|
2650
3567
|
return { body, endpoint: tool.endpoint, tool };
|
|
2651
3568
|
}
|
|
2652
3569
|
|
|
2653
|
-
// src/server.ts
|
|
3570
|
+
// src/server-tools.ts
|
|
2654
3571
|
var FAL_BASE_URL = "https://fal.run";
|
|
2655
3572
|
var FAL_QUEUE_URL = "https://queue.fal.run";
|
|
3573
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
3574
|
+
var MAX_POLL_ATTEMPTS = 160;
|
|
3575
|
+
function buildToolRequest(options) {
|
|
3576
|
+
try {
|
|
3577
|
+
return ok(buildFalToolRequest(options));
|
|
3578
|
+
} catch (error) {
|
|
3579
|
+
return err(
|
|
3580
|
+
new MotifError(error instanceof Error ? error.message : String(error), 0)
|
|
3581
|
+
);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
async function runTool(exec, options) {
|
|
3585
|
+
const request = buildToolRequest(options);
|
|
3586
|
+
if (request.isErr()) {
|
|
3587
|
+
return err(request.error);
|
|
3588
|
+
}
|
|
3589
|
+
const response = await exec.request(
|
|
3590
|
+
`${FAL_BASE_URL}/${request.value.endpoint}`,
|
|
3591
|
+
{
|
|
3592
|
+
body: JSON.stringify(request.value.body),
|
|
3593
|
+
method: "POST"
|
|
3594
|
+
}
|
|
3595
|
+
);
|
|
3596
|
+
if (response.isErr()) {
|
|
3597
|
+
return err(response.error);
|
|
3598
|
+
}
|
|
3599
|
+
const data = await response.value.json();
|
|
3600
|
+
return ok(isRecord(data) ? data : {});
|
|
3601
|
+
}
|
|
3602
|
+
async function submitTool(exec, options) {
|
|
3603
|
+
const request = buildToolRequest(options);
|
|
3604
|
+
if (request.isErr()) {
|
|
3605
|
+
return err(request.error);
|
|
3606
|
+
}
|
|
3607
|
+
const response = await exec.request(
|
|
3608
|
+
`${FAL_QUEUE_URL}/${request.value.endpoint}`,
|
|
3609
|
+
{
|
|
3610
|
+
body: JSON.stringify(request.value.body),
|
|
3611
|
+
method: "POST"
|
|
3612
|
+
}
|
|
3613
|
+
);
|
|
3614
|
+
if (response.isErr()) {
|
|
3615
|
+
return err(response.error);
|
|
3616
|
+
}
|
|
3617
|
+
const data = await response.value.json();
|
|
3618
|
+
const submission = parseQueueSubmission(data);
|
|
3619
|
+
return ok({
|
|
3620
|
+
endpoint: endpointFromQueueUrl(
|
|
3621
|
+
submission.responseUrl,
|
|
3622
|
+
request.value.endpoint
|
|
3623
|
+
),
|
|
3624
|
+
requestId: submission.requestId
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
async function checkToolStatus(exec, job) {
|
|
3628
|
+
return await exec.getJobStatus(job.endpoint, job.requestId);
|
|
3629
|
+
}
|
|
3630
|
+
async function getToolResult(exec, job) {
|
|
3631
|
+
const url = `${FAL_QUEUE_URL}/${job.endpoint}/requests/${job.requestId}`;
|
|
3632
|
+
const response = await exec.request(url);
|
|
3633
|
+
if (response.isErr()) {
|
|
3634
|
+
return err(response.error);
|
|
3635
|
+
}
|
|
3636
|
+
const data = await response.value.json();
|
|
3637
|
+
return ok(isRecord(data) ? data : {});
|
|
3638
|
+
}
|
|
3639
|
+
async function runToolQueued(exec, options, onProgress) {
|
|
3640
|
+
const job = await submitTool(exec, options);
|
|
3641
|
+
if (job.isErr()) {
|
|
3642
|
+
return err(job.error);
|
|
3643
|
+
}
|
|
3644
|
+
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
|
|
3645
|
+
const status = await checkToolStatus(exec, job.value);
|
|
3646
|
+
if (status.isErr()) {
|
|
3647
|
+
return err(status.error);
|
|
3648
|
+
}
|
|
3649
|
+
onProgress?.(status.value.status, status.value.queuePosition);
|
|
3650
|
+
if (status.value.status === "completed") {
|
|
3651
|
+
return await getToolResult(exec, job.value);
|
|
3652
|
+
}
|
|
3653
|
+
if (status.value.status === "failed") {
|
|
3654
|
+
const message = status.value.error ?? "Queued tool run failed";
|
|
3655
|
+
return err(new MotifError(message, 0, void 0, job.value.requestId));
|
|
3656
|
+
}
|
|
3657
|
+
await new Promise((resolve) => {
|
|
3658
|
+
setTimeout(resolve, POLL_INTERVAL_MS);
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
return err(
|
|
3662
|
+
new MotifError(
|
|
3663
|
+
"Queued tool run timed out",
|
|
3664
|
+
0,
|
|
3665
|
+
void 0,
|
|
3666
|
+
job.value.requestId
|
|
3667
|
+
)
|
|
3668
|
+
);
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
// src/server.ts
|
|
3672
|
+
var FAL_BASE_URL2 = "https://fal.run";
|
|
3673
|
+
var FAL_QUEUE_URL2 = "https://queue.fal.run";
|
|
2656
3674
|
var FAL_API_URL = "https://api.fal.ai";
|
|
2657
3675
|
var FAL_REST_URL = "https://rest.alpha.fal.ai";
|
|
2658
3676
|
var FalClient = class {
|
|
@@ -2684,16 +3702,16 @@ var FalClient = class {
|
|
|
2684
3702
|
try {
|
|
2685
3703
|
built = buildGenerateBody(options);
|
|
2686
3704
|
} catch (error) {
|
|
2687
|
-
return
|
|
3705
|
+
return err2(toMotifError(error));
|
|
2688
3706
|
}
|
|
2689
3707
|
const { endpoint, body } = built;
|
|
2690
|
-
const response = await this.request(`${
|
|
3708
|
+
const response = await this.request(`${FAL_BASE_URL2}/${endpoint}`, {
|
|
2691
3709
|
body: JSON.stringify(body),
|
|
2692
3710
|
headers: this.ephemeralHeaders(options),
|
|
2693
3711
|
method: "POST"
|
|
2694
3712
|
});
|
|
2695
3713
|
if (response.isErr()) {
|
|
2696
|
-
return
|
|
3714
|
+
return err2(response.error);
|
|
2697
3715
|
}
|
|
2698
3716
|
const data = await response.value.json();
|
|
2699
3717
|
return this.normalizeResponse(data);
|
|
@@ -2701,7 +3719,7 @@ var FalClient = class {
|
|
|
2701
3719
|
async generateQueued(options) {
|
|
2702
3720
|
const job = await this.submitGeneration(options);
|
|
2703
3721
|
if (job.isErr()) {
|
|
2704
|
-
return
|
|
3722
|
+
return err2(job.error);
|
|
2705
3723
|
}
|
|
2706
3724
|
const pollIntervalMs = 3e3;
|
|
2707
3725
|
const maxAttempts = 160;
|
|
@@ -2711,20 +3729,20 @@ var FalClient = class {
|
|
|
2711
3729
|
job.value.requestId
|
|
2712
3730
|
);
|
|
2713
3731
|
if (status.isErr()) {
|
|
2714
|
-
return
|
|
3732
|
+
return err2(status.error);
|
|
2715
3733
|
}
|
|
2716
3734
|
if (status.value.status === "completed") {
|
|
2717
3735
|
return await this.getJobResult(job.value.endpoint, job.value.requestId);
|
|
2718
3736
|
}
|
|
2719
3737
|
if (status.value.status === "failed") {
|
|
2720
3738
|
const message = status.value.error ?? "Queued generation failed";
|
|
2721
|
-
return
|
|
3739
|
+
return err2(new MotifError(message, 0, void 0, job.value.requestId));
|
|
2722
3740
|
}
|
|
2723
3741
|
await new Promise((resolve) => {
|
|
2724
3742
|
setTimeout(resolve, pollIntervalMs);
|
|
2725
3743
|
});
|
|
2726
3744
|
}
|
|
2727
|
-
return
|
|
3745
|
+
return err2(new MotifError("Queued generation timed out", 0));
|
|
2728
3746
|
}
|
|
2729
3747
|
/** ─── Queue-Based Generation ──────────────────────────────── */
|
|
2730
3748
|
/** Submit a generation to the fal.ai queue (returns immediately). */
|
|
@@ -2733,20 +3751,20 @@ var FalClient = class {
|
|
|
2733
3751
|
try {
|
|
2734
3752
|
built = buildGenerateBody(options);
|
|
2735
3753
|
} catch (error) {
|
|
2736
|
-
return
|
|
3754
|
+
return err2(toMotifError(error));
|
|
2737
3755
|
}
|
|
2738
3756
|
const { endpoint, body } = built;
|
|
2739
|
-
const response = await this.request(`${
|
|
3757
|
+
const response = await this.request(`${FAL_QUEUE_URL2}/${endpoint}`, {
|
|
2740
3758
|
body: JSON.stringify(body),
|
|
2741
3759
|
headers: this.ephemeralHeaders(options),
|
|
2742
3760
|
method: "POST"
|
|
2743
3761
|
});
|
|
2744
3762
|
if (response.isErr()) {
|
|
2745
|
-
return
|
|
3763
|
+
return err2(response.error);
|
|
2746
3764
|
}
|
|
2747
3765
|
const data = await response.value.json();
|
|
2748
3766
|
const submission = parseQueueSubmission(data);
|
|
2749
|
-
return
|
|
3767
|
+
return ok2({
|
|
2750
3768
|
endpoint: endpointFromQueueUrl(submission.responseUrl, endpoint),
|
|
2751
3769
|
estimatedCost: estimateCost(
|
|
2752
3770
|
options.model,
|
|
@@ -2758,10 +3776,10 @@ var FalClient = class {
|
|
|
2758
3776
|
}
|
|
2759
3777
|
/** Check the status of a queued generation. */
|
|
2760
3778
|
async getJobStatus(endpoint, requestId) {
|
|
2761
|
-
const url = `${
|
|
3779
|
+
const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}/status?logs=1`;
|
|
2762
3780
|
const response = await this.request(url);
|
|
2763
3781
|
if (response.isErr()) {
|
|
2764
|
-
return
|
|
3782
|
+
return err2(response.error);
|
|
2765
3783
|
}
|
|
2766
3784
|
const data = await response.value.json();
|
|
2767
3785
|
const record = isRecord(data) ? data : {};
|
|
@@ -2776,9 +3794,9 @@ var FalClient = class {
|
|
|
2776
3794
|
} else if (rawStatus === "FAILED" || rawStatus === "ERROR" || rawStatus === "CANCELED") {
|
|
2777
3795
|
status = "failed";
|
|
2778
3796
|
} else {
|
|
2779
|
-
return
|
|
3797
|
+
return err2(new MotifError(`Unknown job status: ${rawStatus}`, 0));
|
|
2780
3798
|
}
|
|
2781
|
-
return
|
|
3799
|
+
return ok2({
|
|
2782
3800
|
error: asString(record.error) ?? asString(record.detail),
|
|
2783
3801
|
logs: parseLogs(record.logs),
|
|
2784
3802
|
queuePosition: asNumber(record.queue_position),
|
|
@@ -2787,10 +3805,10 @@ var FalClient = class {
|
|
|
2787
3805
|
}
|
|
2788
3806
|
/** Fetch the completed result from the queue. */
|
|
2789
3807
|
async getJobResult(endpoint, requestId) {
|
|
2790
|
-
const url = `${
|
|
3808
|
+
const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}`;
|
|
2791
3809
|
const response = await this.request(url);
|
|
2792
3810
|
if (response.isErr()) {
|
|
2793
|
-
return
|
|
3811
|
+
return err2(response.error);
|
|
2794
3812
|
}
|
|
2795
3813
|
const data = await response.value.json();
|
|
2796
3814
|
return this.normalizeResponse(data, requestId);
|
|
@@ -2811,7 +3829,7 @@ var FalClient = class {
|
|
|
2811
3829
|
} = options;
|
|
2812
3830
|
const config = MODELS[model];
|
|
2813
3831
|
if (!config || config.type !== "utility") {
|
|
2814
|
-
return
|
|
3832
|
+
return err2(new MotifError(`Invalid upscale model: ${model}`, 0));
|
|
2815
3833
|
}
|
|
2816
3834
|
const body = { image_url: imageUrl };
|
|
2817
3835
|
if (model === "crystal") {
|
|
@@ -2844,12 +3862,12 @@ var FalClient = class {
|
|
|
2844
3862
|
body.guidance_scale = guidanceScale;
|
|
2845
3863
|
}
|
|
2846
3864
|
}
|
|
2847
|
-
const response = await this.request(`${
|
|
3865
|
+
const response = await this.request(`${FAL_BASE_URL2}/${config.endpoint}`, {
|
|
2848
3866
|
body: JSON.stringify(body),
|
|
2849
3867
|
method: "POST"
|
|
2850
3868
|
});
|
|
2851
3869
|
if (response.isErr()) {
|
|
2852
|
-
return
|
|
3870
|
+
return err2(response.error);
|
|
2853
3871
|
}
|
|
2854
3872
|
const data = await response.value.json();
|
|
2855
3873
|
return this.normalizeResponse(data);
|
|
@@ -2867,7 +3885,7 @@ var FalClient = class {
|
|
|
2867
3885
|
} = options;
|
|
2868
3886
|
const config = MODELS[model];
|
|
2869
3887
|
if (!config) {
|
|
2870
|
-
return
|
|
3888
|
+
return err2(
|
|
2871
3889
|
new MotifError(`Invalid background removal model: ${model}`, 0)
|
|
2872
3890
|
);
|
|
2873
3891
|
}
|
|
@@ -2889,12 +3907,12 @@ var FalClient = class {
|
|
|
2889
3907
|
rbBody.output_mask = outputMask;
|
|
2890
3908
|
}
|
|
2891
3909
|
}
|
|
2892
|
-
const response = await this.request(`${
|
|
3910
|
+
const response = await this.request(`${FAL_BASE_URL2}/${config.endpoint}`, {
|
|
2893
3911
|
body: JSON.stringify(rbBody),
|
|
2894
3912
|
method: "POST"
|
|
2895
3913
|
});
|
|
2896
3914
|
if (response.isErr()) {
|
|
2897
|
-
return
|
|
3915
|
+
return err2(response.error);
|
|
2898
3916
|
}
|
|
2899
3917
|
const data = await response.value.json();
|
|
2900
3918
|
return this.normalizeResponse(data);
|
|
@@ -2917,7 +3935,7 @@ var FalClient = class {
|
|
|
2917
3935
|
} = options;
|
|
2918
3936
|
const config = MODELS.kling;
|
|
2919
3937
|
if (!config) {
|
|
2920
|
-
return
|
|
3938
|
+
return err2(new MotifError("Kling video model not found", 0));
|
|
2921
3939
|
}
|
|
2922
3940
|
const body = {
|
|
2923
3941
|
duration: String(duration),
|
|
@@ -2934,16 +3952,16 @@ var FalClient = class {
|
|
|
2934
3952
|
if (cfgScale !== void 0) {
|
|
2935
3953
|
body.cfg_scale = cfgScale;
|
|
2936
3954
|
}
|
|
2937
|
-
const response = await this.request(`${
|
|
3955
|
+
const response = await this.request(`${FAL_QUEUE_URL2}/${config.endpoint}`, {
|
|
2938
3956
|
body: JSON.stringify(body),
|
|
2939
3957
|
method: "POST"
|
|
2940
3958
|
});
|
|
2941
3959
|
if (response.isErr()) {
|
|
2942
|
-
return
|
|
3960
|
+
return err2(response.error);
|
|
2943
3961
|
}
|
|
2944
3962
|
const data = await response.value.json();
|
|
2945
3963
|
const submission = parseQueueSubmission(data);
|
|
2946
|
-
return
|
|
3964
|
+
return ok2({
|
|
2947
3965
|
endpoint: endpointFromQueueUrl(submission.responseUrl, config.endpoint),
|
|
2948
3966
|
estimatedCost: estimateVideoCost(duration, generateAudio),
|
|
2949
3967
|
requestId: submission.requestId
|
|
@@ -2951,17 +3969,17 @@ var FalClient = class {
|
|
|
2951
3969
|
}
|
|
2952
3970
|
/** Fetch the completed video result from the queue. */
|
|
2953
3971
|
async getVideoResult(endpoint, requestId) {
|
|
2954
|
-
const url = `${
|
|
3972
|
+
const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}`;
|
|
2955
3973
|
const response = await this.request(url);
|
|
2956
3974
|
if (response.isErr()) {
|
|
2957
|
-
return
|
|
3975
|
+
return err2(response.error);
|
|
2958
3976
|
}
|
|
2959
3977
|
const data = await response.value.json();
|
|
2960
3978
|
const video = isRecord(data) && isRecord(data.video) ? data.video : void 0;
|
|
2961
3979
|
if (video === void 0) {
|
|
2962
|
-
return
|
|
3980
|
+
return err2(new MotifError("No video in response", 0));
|
|
2963
3981
|
}
|
|
2964
|
-
return
|
|
3982
|
+
return ok2({
|
|
2965
3983
|
contentType: asString(video.content_type) ?? "",
|
|
2966
3984
|
fileName: asString(video.file_name) ?? "",
|
|
2967
3985
|
fileSize: asNumber(video.file_size) ?? 0,
|
|
@@ -2985,13 +4003,13 @@ var FalClient = class {
|
|
|
2985
4003
|
}
|
|
2986
4004
|
);
|
|
2987
4005
|
if (initiateResponse.isErr()) {
|
|
2988
|
-
return
|
|
4006
|
+
return err2(initiateResponse.error);
|
|
2989
4007
|
}
|
|
2990
4008
|
const initiateData = await initiateResponse.value.json();
|
|
2991
4009
|
const fileUrl = isRecord(initiateData) ? asString(initiateData.file_url) ?? "" : "";
|
|
2992
4010
|
const uploadUrl = isRecord(initiateData) ? asString(initiateData.upload_url) : void 0;
|
|
2993
4011
|
if (uploadUrl === void 0 || uploadUrl === "") {
|
|
2994
|
-
return
|
|
4012
|
+
return err2(
|
|
2995
4013
|
new MotifError("Upload initiate response missing upload_url", 0)
|
|
2996
4014
|
);
|
|
2997
4015
|
}
|
|
@@ -3006,7 +4024,7 @@ var FalClient = class {
|
|
|
3006
4024
|
method: "PUT"
|
|
3007
4025
|
});
|
|
3008
4026
|
} catch (error) {
|
|
3009
|
-
return
|
|
4027
|
+
return err2(
|
|
3010
4028
|
new MotifError(
|
|
3011
4029
|
`Upload PUT failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3012
4030
|
0
|
|
@@ -3014,38 +4032,45 @@ var FalClient = class {
|
|
|
3014
4032
|
);
|
|
3015
4033
|
}
|
|
3016
4034
|
if (!putResponse.ok) {
|
|
3017
|
-
return
|
|
4035
|
+
return err2(
|
|
3018
4036
|
new MotifError(
|
|
3019
4037
|
`Upload PUT failed: ${putResponse.status}`,
|
|
3020
4038
|
putResponse.status
|
|
3021
4039
|
)
|
|
3022
4040
|
);
|
|
3023
4041
|
}
|
|
3024
|
-
return
|
|
4042
|
+
return ok2(fileUrl);
|
|
3025
4043
|
}
|
|
3026
4044
|
/** ─── Utilities ───────────────────────────────────────────── */
|
|
3027
4045
|
/** Run a registered fal utility/tool endpoint. */
|
|
3028
4046
|
async runTool(options) {
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
return
|
|
4047
|
+
return await runTool(this.toolExecutor, options);
|
|
4048
|
+
}
|
|
4049
|
+
/** ─── Queue-Based Tools ───────────────────────────────────── */
|
|
4050
|
+
/**
|
|
4051
|
+
* Submit a tool run to the fal queue. Returns as soon as fal accepts it.
|
|
4052
|
+
*
|
|
4053
|
+
* The queued counterpart to `runTool`, for endpoints that outrun the
|
|
4054
|
+
* synchronous request timeout. Callers choose the path — neither one falls
|
|
4055
|
+
* back to the other.
|
|
4056
|
+
*/
|
|
4057
|
+
async submitTool(options) {
|
|
4058
|
+
return await submitTool(this.toolExecutor, options);
|
|
4059
|
+
}
|
|
4060
|
+
/** Poll one queued tool run. */
|
|
4061
|
+
async checkToolStatus(job) {
|
|
4062
|
+
return await checkToolStatus(this.toolExecutor, job);
|
|
4063
|
+
}
|
|
4064
|
+
/** Fetch the finished payload for a queued tool run. */
|
|
4065
|
+
async getToolResult(job) {
|
|
4066
|
+
return await getToolResult(this.toolExecutor, job);
|
|
4067
|
+
}
|
|
4068
|
+
/**
|
|
4069
|
+
* Submit, poll to completion, and return the result. The convenience path
|
|
4070
|
+
* for tools whose registry entry sets `queued`.
|
|
4071
|
+
*/
|
|
4072
|
+
async runToolQueued(options, onProgress) {
|
|
4073
|
+
return await runToolQueued(this.toolExecutor, options, onProgress);
|
|
3049
4074
|
}
|
|
3050
4075
|
/**
|
|
3051
4076
|
* Delete fal's stored IO payloads for a completed request.
|
|
@@ -3060,9 +4085,9 @@ var FalClient = class {
|
|
|
3060
4085
|
{ method: "DELETE" }
|
|
3061
4086
|
);
|
|
3062
4087
|
if (response.isErr()) {
|
|
3063
|
-
return
|
|
4088
|
+
return err2(response.error);
|
|
3064
4089
|
}
|
|
3065
|
-
return
|
|
4090
|
+
return ok2();
|
|
3066
4091
|
}
|
|
3067
4092
|
/** Estimate cost for a generation (no API call). */
|
|
3068
4093
|
estimateCost(model, resolution, numImages) {
|
|
@@ -3088,6 +4113,16 @@ var FalClient = class {
|
|
|
3088
4113
|
get tools() {
|
|
3089
4114
|
return FAL_TOOLS;
|
|
3090
4115
|
}
|
|
4116
|
+
/**
|
|
4117
|
+
* The seam `server-tools.ts` runs against — the client's authenticated fetch
|
|
4118
|
+
* and its queue status parser, exposed without widening the public surface.
|
|
4119
|
+
*/
|
|
4120
|
+
get toolExecutor() {
|
|
4121
|
+
return {
|
|
4122
|
+
getJobStatus: async (endpoint, requestId) => await this.getJobStatus(endpoint, requestId),
|
|
4123
|
+
request: async (url, init) => await this.request(url, init)
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
3091
4126
|
/** ─── Private ─────────────────────────────────────────────── */
|
|
3092
4127
|
/** Authenticated fetch to fal.ai APIs with retry logic. */
|
|
3093
4128
|
async request(url, options = {}) {
|
|
@@ -3119,15 +4154,15 @@ var FalClient = class {
|
|
|
3119
4154
|
const text = await response.text();
|
|
3120
4155
|
const message = `Request failed: ${response.status} ${text}`;
|
|
3121
4156
|
const requestId = response.headers.get("x-fal-request-id") ?? requestIdFromBody(text);
|
|
3122
|
-
return
|
|
4157
|
+
return err2(
|
|
3123
4158
|
new MotifError(message, response.status, void 0, requestId)
|
|
3124
4159
|
);
|
|
3125
4160
|
}
|
|
3126
|
-
return
|
|
4161
|
+
return ok2(response);
|
|
3127
4162
|
} catch (error) {
|
|
3128
4163
|
clearTimeout(timeoutId);
|
|
3129
4164
|
if (error instanceof MotifError) {
|
|
3130
|
-
return
|
|
4165
|
+
return err2(error);
|
|
3131
4166
|
}
|
|
3132
4167
|
lastError = new MotifError(
|
|
3133
4168
|
error instanceof Error ? error.message : String(error),
|
|
@@ -3141,7 +4176,7 @@ var FalClient = class {
|
|
|
3141
4176
|
}
|
|
3142
4177
|
}
|
|
3143
4178
|
}
|
|
3144
|
-
return
|
|
4179
|
+
return err2(lastError ?? new MotifError("Request failed after retries", 0));
|
|
3145
4180
|
}
|
|
3146
4181
|
ephemeralHeaders(options) {
|
|
3147
4182
|
return options.ephemeral === true ? { "X-Fal-Store-IO": "0" } : {};
|
|
@@ -3152,24 +4187,24 @@ var FalClient = class {
|
|
|
3152
4187
|
*/
|
|
3153
4188
|
normalizeResponse(data, fallbackRequestId) {
|
|
3154
4189
|
if (!isRecord(data)) {
|
|
3155
|
-
return
|
|
4190
|
+
return err2(
|
|
3156
4191
|
new MotifError("Unexpected fal response shape", 0, "FAL_ERROR")
|
|
3157
4192
|
);
|
|
3158
4193
|
}
|
|
3159
4194
|
const obj = data;
|
|
3160
4195
|
const requestId = asString(obj.request_id) ?? asString(obj.requestId) ?? fallbackRequestId;
|
|
3161
4196
|
if ("detail" in obj) {
|
|
3162
|
-
return
|
|
4197
|
+
return err2(new MotifError(asString(obj.detail) ?? "", 0, "FAL_ERROR"));
|
|
3163
4198
|
}
|
|
3164
4199
|
if ("image" in obj && !("images" in obj)) {
|
|
3165
|
-
return
|
|
4200
|
+
return ok2({
|
|
3166
4201
|
images: [toMotifImage(obj.image)],
|
|
3167
4202
|
prompt: asString(obj.prompt),
|
|
3168
4203
|
requestId,
|
|
3169
4204
|
seed: asNumber(obj.seed)
|
|
3170
4205
|
});
|
|
3171
4206
|
}
|
|
3172
|
-
return
|
|
4207
|
+
return ok2({
|
|
3173
4208
|
images: parseImages(obj.images),
|
|
3174
4209
|
prompt: asString(obj.prompt),
|
|
3175
4210
|
requestId,
|
|
@@ -3177,28 +4212,6 @@ var FalClient = class {
|
|
|
3177
4212
|
});
|
|
3178
4213
|
}
|
|
3179
4214
|
};
|
|
3180
|
-
var MotifError = class extends Error {
|
|
3181
|
-
status;
|
|
3182
|
-
code;
|
|
3183
|
-
/** fal's request-correlation id (from the `x-fal-request-id` header or the
|
|
3184
|
-
* error body). Ties a failure back to fal's dashboard/support. */
|
|
3185
|
-
requestId;
|
|
3186
|
-
constructor(message, status, code, requestId) {
|
|
3187
|
-
super(message);
|
|
3188
|
-
this.name = "MotifError";
|
|
3189
|
-
this.status = status;
|
|
3190
|
-
this.code = code;
|
|
3191
|
-
this.requestId = requestId;
|
|
3192
|
-
}
|
|
3193
|
-
};
|
|
3194
|
-
function toMotifError(error) {
|
|
3195
|
-
if (error instanceof MotifError) {
|
|
3196
|
-
return error;
|
|
3197
|
-
}
|
|
3198
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3199
|
-
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
3200
|
-
return new MotifError(message, 0, code);
|
|
3201
|
-
}
|
|
3202
4215
|
export {
|
|
3203
4216
|
ASPECT_RATIOS,
|
|
3204
4217
|
CREATIVE_FIELDS,
|
|
@@ -3227,13 +4240,13 @@ export {
|
|
|
3227
4240
|
buildFalToolRequest,
|
|
3228
4241
|
buildGenerateBody,
|
|
3229
4242
|
enrichPrompt,
|
|
3230
|
-
|
|
4243
|
+
err3 as err,
|
|
3231
4244
|
estimateCost,
|
|
3232
4245
|
estimateVideoCost,
|
|
3233
4246
|
getFalKeyFromEnv,
|
|
3234
4247
|
isFalToolId,
|
|
3235
4248
|
motifEnvSchema,
|
|
3236
|
-
|
|
4249
|
+
ok3 as ok,
|
|
3237
4250
|
parseMotifEnv,
|
|
3238
4251
|
sanitizePrompt
|
|
3239
4252
|
};
|