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