@howells/motif-sdk 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(src_exports, {
28
28
  FAL_TOOLS: () => FAL_TOOLS,
29
29
  FAL_TOOLS_CHECKED_AT: () => FAL_TOOLS_CHECKED_AT,
30
30
  FAL_TOOL_IDS: () => FAL_TOOL_IDS,
31
+ FAL_TOOL_PARAMETERS: () => FAL_TOOL_PARAMETERS,
31
32
  FORMAT_PRESETS: () => FORMAT_PRESETS,
32
33
  FalClient: () => FalClient,
33
34
  GENERATION_MODELS: () => GENERATION_MODELS,
@@ -35,6 +36,7 @@ __export(src_exports, {
35
36
  IMAGE_EDITING_TOP_20: () => IMAGE_EDITING_TOP_20,
36
37
  IMAGE_TEXT_TO_IMAGE_TOP_20: () => IMAGE_TEXT_TO_IMAGE_TOP_20,
37
38
  MODELS: () => MODELS,
39
+ MODEL_OUTPUT: () => MODEL_OUTPUT,
38
40
  MotifError: () => MotifError,
39
41
  RECRAFT_STYLES: () => RECRAFT_STYLES,
40
42
  RESOLUTIONS: () => RESOLUTIONS,
@@ -46,19 +48,27 @@ __export(src_exports, {
46
48
  aspectToGptSize: () => aspectToGptSize,
47
49
  buildFalToolRequest: () => buildFalToolRequest,
48
50
  buildGenerateBody: () => buildGenerateBody,
51
+ describeModelOutput: () => describeModelOutput,
49
52
  enrichPrompt: () => enrichPrompt,
50
- err: () => import_neverthrow2.err,
53
+ err: () => import_neverthrow3.err,
51
54
  estimateCost: () => estimateCost,
52
55
  estimateVideoCost: () => estimateVideoCost,
56
+ falToolParameters: () => falToolParameters,
57
+ formatCost: () => formatCost,
53
58
  getFalKeyFromEnv: () => getFalKeyFromEnv,
54
59
  isFalToolId: () => isFalToolId,
60
+ losslessAvailability: () => losslessAvailability,
61
+ measuredToolCost: () => measuredToolCost,
62
+ modelOutput: () => modelOutput,
55
63
  motifEnvSchema: () => motifEnvSchema,
56
- ok: () => import_neverthrow2.ok,
64
+ ok: () => import_neverthrow3.ok,
57
65
  parseMotifEnv: () => parseMotifEnv,
58
- sanitizePrompt: () => sanitizePrompt
66
+ projectedToolCost: () => projectedToolCost,
67
+ sanitizePrompt: () => sanitizePrompt,
68
+ sumCosts: () => sumCosts
59
69
  });
60
70
  module.exports = __toCommonJS(src_exports);
61
- var import_neverthrow2 = require("neverthrow");
71
+ var import_neverthrow3 = require("neverthrow");
62
72
 
63
73
  // src/aspects.ts
64
74
  var ASPECT_RATIOS = [
@@ -158,22 +168,23 @@ var FORMAT_PRESETS = {
158
168
  };
159
169
 
160
170
  // src/models.ts
161
- var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-05-12";
171
+ var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-08-23";
162
172
  var AA_IMAGE_SOURCES = [
163
173
  "https://artificialanalysis.ai/image/leaderboard/text-to-image",
164
174
  "https://artificialanalysis.ai/image/leaderboard/editing"
165
175
  ];
166
176
  var FAL_PRICING_CHECKED_AT = "2026-05-12";
167
177
  var FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
178
+ var FAL_PRICING_CHECKED_AUG_2026 = "2026-08-23";
168
179
  var MODELS = {
169
180
  // ─── Generation Models ────────────────────────────────────────
170
181
  gpt2: {
171
182
  benchmark: {
172
183
  artificialAnalysis: {
173
- editing: { elo: 1249, pricePer1k: 211, rank: 3, winRate: 0.66 },
184
+ editing: { elo: 1256, rank: 3 },
174
185
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
175
186
  sourceUrls: AA_IMAGE_SOURCES,
176
- textToImage: { elo: 1337, pricePer1k: 211, rank: 1, winRate: 0.8 }
187
+ textToImage: { elo: 1369, rank: 1 }
177
188
  },
178
189
  speed: {
179
190
  medianSeconds: 200.7,
@@ -213,10 +224,10 @@ var MODELS = {
213
224
  gpt: {
214
225
  benchmark: {
215
226
  artificialAnalysis: {
216
- editing: { elo: 1262, pricePer1k: 133, rank: 2, winRate: 0.69 },
227
+ editing: { elo: 1250, rank: 5 },
217
228
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
218
229
  sourceUrls: AA_IMAGE_SOURCES,
219
- textToImage: { elo: 1268, pricePer1k: 133, rank: 2, winRate: 0.73 }
230
+ textToImage: { elo: 1310, rank: 4 }
220
231
  },
221
232
  speed: {
222
233
  medianSeconds: 34.6,
@@ -256,10 +267,10 @@ var MODELS = {
256
267
  banana2: {
257
268
  benchmark: {
258
269
  artificialAnalysis: {
259
- editing: { elo: 1231, pricePer1k: 67, rank: 5, winRate: 0.63 },
270
+ editing: { elo: 1249, rank: 7 },
260
271
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
261
272
  sourceUrls: AA_IMAGE_SOURCES,
262
- textToImage: { elo: 1263, pricePer1k: 67, rank: 3, winRate: 0.71 }
273
+ textToImage: { elo: 1320, rank: 3 }
263
274
  },
264
275
  tiers: { price: "premium", quality: "frontier", speed: "unknown" },
265
276
  useCase: "Best balance of quality, edit support, web search, and cost"
@@ -297,10 +308,10 @@ var MODELS = {
297
308
  banana: {
298
309
  benchmark: {
299
310
  artificialAnalysis: {
300
- editing: { elo: 1241, pricePer1k: 134, rank: 4, winRate: 0.65 },
311
+ editing: { elo: 1245, rank: 9 },
301
312
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
302
313
  sourceUrls: AA_IMAGE_SOURCES,
303
- textToImage: { elo: 1220, pricePer1k: 134, rank: 5, winRate: 0.66 }
314
+ textToImage: { elo: 1297, rank: 6 }
304
315
  },
305
316
  speed: {
306
317
  medianSeconds: 19.2,
@@ -340,6 +351,13 @@ var MODELS = {
340
351
  type: "generation"
341
352
  },
342
353
  gemini: {
354
+ benchmark: {
355
+ artificialAnalysis: {
356
+ editing: { elo: 1180, rank: 29 },
357
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
358
+ sourceUrls: AA_IMAGE_SOURCES
359
+ }
360
+ },
343
361
  editEndpoint: "fal-ai/gemini-25-flash-image/edit",
344
362
  endpoint: "fal-ai/gemini-25-flash-image",
345
363
  falPricing: {
@@ -367,6 +385,14 @@ var MODELS = {
367
385
  type: "generation"
368
386
  },
369
387
  gemini3: {
388
+ benchmark: {
389
+ artificialAnalysis: {
390
+ editing: { elo: 1245, rank: 9 },
391
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
392
+ sourceUrls: AA_IMAGE_SOURCES,
393
+ textToImage: { elo: 1297, rank: 6 }
394
+ }
395
+ },
370
396
  editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
371
397
  endpoint: "fal-ai/gemini-3-pro-image-preview",
372
398
  falPricing: {
@@ -397,10 +423,10 @@ var MODELS = {
397
423
  seedream4: {
398
424
  benchmark: {
399
425
  artificialAnalysis: {
400
- editing: { elo: 1184, pricePer1k: 30, rank: 16, winRate: 0.61 },
426
+ editing: { elo: 1185, rank: 28 },
401
427
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
402
428
  sourceUrls: AA_IMAGE_SOURCES,
403
- textToImage: { elo: 1198, pricePer1k: 30, rank: 6, winRate: 0.59 }
429
+ textToImage: { elo: 1226, rank: 16 }
404
430
  },
405
431
  speed: {
406
432
  medianSeconds: 14.3,
@@ -437,10 +463,10 @@ var MODELS = {
437
463
  seedream45: {
438
464
  benchmark: {
439
465
  artificialAnalysis: {
440
- editing: { elo: 1184, pricePer1k: 40, rank: 17, winRate: 0.58 },
466
+ editing: { elo: 1186, rank: 26 },
441
467
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
442
468
  sourceUrls: AA_IMAGE_SOURCES,
443
- textToImage: { elo: 1167, pricePer1k: 40, rank: 17, winRate: 0.63 }
469
+ textToImage: { elo: 1203, rank: 32 }
444
470
  },
445
471
  speed: {
446
472
  medianSeconds: 18.2,
@@ -475,6 +501,14 @@ var MODELS = {
475
501
  type: "generation"
476
502
  },
477
503
  seedream5: {
504
+ benchmark: {
505
+ artificialAnalysis: {
506
+ editing: { elo: 1248, rank: 8 },
507
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
508
+ sourceUrls: AA_IMAGE_SOURCES,
509
+ textToImage: { elo: 1279, rank: 10 }
510
+ }
511
+ },
478
512
  name: "Seedream 5.0 Pro",
479
513
  endpoint: "bytedance/seedream/v5/pro/text-to-image",
480
514
  editEndpoint: "bytedance/seedream/v5/pro/edit",
@@ -501,6 +535,13 @@ var MODELS = {
501
535
  maxReferenceImages: 10
502
536
  },
503
537
  "seedream5-lite": {
538
+ benchmark: {
539
+ artificialAnalysis: {
540
+ editing: { elo: 1169, rank: 32 },
541
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
542
+ sourceUrls: AA_IMAGE_SOURCES
543
+ }
544
+ },
504
545
  name: "Seedream 5.0 Lite",
505
546
  endpoint: "fal-ai/bytedance/seedream/v5/lite/text-to-image",
506
547
  editEndpoint: "fal-ai/bytedance/seedream/v5/lite/edit",
@@ -529,10 +570,10 @@ var MODELS = {
529
570
  "flux2-max": {
530
571
  benchmark: {
531
572
  artificialAnalysis: {
532
- editing: { elo: 1206, pricePer1k: 140, rank: 10, winRate: 0.6 },
573
+ editing: { elo: 1201, rank: 19 },
533
574
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
534
575
  sourceUrls: AA_IMAGE_SOURCES,
535
- textToImage: { elo: 1197, pricePer1k: 70, rank: 8, winRate: 0.67 }
576
+ textToImage: { elo: 1225, rank: 17 }
536
577
  },
537
578
  speed: {
538
579
  medianSeconds: 31.6,
@@ -572,10 +613,10 @@ var MODELS = {
572
613
  "flux2-pro": {
573
614
  benchmark: {
574
615
  artificialAnalysis: {
575
- editing: { elo: 1170, pricePer1k: 30, rank: 21 },
616
+ editing: { elo: 1170, rank: 31 },
576
617
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
577
618
  sourceUrls: AA_IMAGE_SOURCES,
578
- textToImage: { elo: 1186, pricePer1k: 30, rank: 10, winRate: 0.62 }
619
+ textToImage: { elo: 1207, rank: 30 }
579
620
  },
580
621
  speed: {
581
622
  medianSeconds: 16.1,
@@ -615,10 +656,9 @@ var MODELS = {
615
656
  "flux2-flex": {
616
657
  benchmark: {
617
658
  artificialAnalysis: {
618
- editing: { elo: 1161, pricePer1k: 60, rank: 22 },
619
659
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
620
660
  sourceUrls: AA_IMAGE_SOURCES,
621
- textToImage: { elo: 1182, pricePer1k: 60, rank: 13, winRate: 0.61 }
661
+ textToImage: { elo: 1223, rank: 18 }
622
662
  },
623
663
  speed: {
624
664
  medianSeconds: 15.3,
@@ -662,8 +702,7 @@ var MODELS = {
662
702
  benchmark: {
663
703
  artificialAnalysis: {
664
704
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
665
- sourceUrls: AA_IMAGE_SOURCES,
666
- textToImage: { elo: 1160, pricePer1k: 12, rank: 19, winRate: 0.57 }
705
+ sourceUrls: AA_IMAGE_SOURCES
667
706
  },
668
707
  speed: {
669
708
  medianSeconds: 4.6,
@@ -806,7 +845,55 @@ var MODELS = {
806
845
  supportsSyncMode: true,
807
846
  type: "generation"
808
847
  },
848
+ reve: {
849
+ benchmark: {
850
+ artificialAnalysis: {
851
+ // #2 on BOTH boards, which almost nothing else manages - the top text-to-image model
852
+ // (GPT Image 2) drops to 3rd for editing, and the top editor (MAI-Image-2.5-Pro) is not
853
+ // on fal at all. Reve is the best editor motif can actually reach.
854
+ editing: { elo: 1263, rank: 2 },
855
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
856
+ sourceUrls: AA_IMAGE_SOURCES,
857
+ textToImage: { elo: 1322, rank: 2 }
858
+ },
859
+ tiers: { price: "premium", quality: "frontier", speed: "unknown" },
860
+ useCase: "Highest-ranked editor reachable on fal; strong prompt adherence and text rendering"
861
+ },
862
+ editEndpoint: "fal-ai/reve/edit",
863
+ endpoint: "fal-ai/reve/text-to-image",
864
+ falPricing: {
865
+ checkedAt: FAL_PRICING_CHECKED_AUG_2026,
866
+ currency: "USD",
867
+ endpointId: "fal-ai/reve/text-to-image",
868
+ estimatedCostPerImageUsd: 0.04,
869
+ source: "fal-pricing-api",
870
+ unit: "images",
871
+ unitPrice: 0.04
872
+ },
873
+ maxReferenceImages: 1,
874
+ name: "Reve 2.1",
875
+ pricePerImageUsd: 0.04,
876
+ pricing: "$0.04",
877
+ // fal's schema takes aspect_ratio from a fixed set and has no width/height or resolution
878
+ // parameter, so aspect is supported and resolution is not.
879
+ sizeMode: "aspect_ratio",
880
+ supportsAspect: true,
881
+ supportsEdit: true,
882
+ supportsNumImages: true,
883
+ supportsOutputFormat: true,
884
+ supportsResolution: false,
885
+ supportsSeed: false,
886
+ supportsSyncMode: true,
887
+ type: "generation"
888
+ },
809
889
  recraft4: {
890
+ benchmark: {
891
+ artificialAnalysis: {
892
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
893
+ sourceUrls: AA_IMAGE_SOURCES,
894
+ textToImage: { elo: 1215, rank: 25 }
895
+ }
896
+ },
810
897
  endpoint: "fal-ai/recraft/v4/text-to-image",
811
898
  falPricing: {
812
899
  checkedAt: FAL_PRICING_CHECKED_JUL_2026,
@@ -857,6 +944,13 @@ var MODELS = {
857
944
  type: "generation"
858
945
  },
859
946
  ideogram4: {
947
+ benchmark: {
948
+ artificialAnalysis: {
949
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
950
+ sourceUrls: AA_IMAGE_SOURCES,
951
+ textToImage: { elo: 1220, rank: 19 }
952
+ }
953
+ },
860
954
  name: "Ideogram V4",
861
955
  endpoint: "ideogram/v4",
862
956
  type: "generation",
@@ -889,10 +983,10 @@ var MODELS = {
889
983
  "grok-image": {
890
984
  benchmark: {
891
985
  artificialAnalysis: {
892
- editing: { elo: 1213, pricePer1k: 20, rank: 7, winRate: 0.6 },
986
+ editing: { elo: 1214, rank: 16 },
893
987
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
894
988
  sourceUrls: AA_IMAGE_SOURCES,
895
- textToImage: { elo: 1182, pricePer1k: 20, rank: 12, winRate: 0.6 }
989
+ textToImage: { elo: 1216, rank: 23 }
896
990
  },
897
991
  speed: {
898
992
  medianSeconds: 5.1,
@@ -930,8 +1024,7 @@ var MODELS = {
930
1024
  benchmark: {
931
1025
  artificialAnalysis: {
932
1026
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
933
- sourceUrls: AA_IMAGE_SOURCES,
934
- textToImage: { elo: 1158, pricePer1k: 20, rank: 20, winRate: 0.6 }
1027
+ sourceUrls: AA_IMAGE_SOURCES
935
1028
  },
936
1029
  speed: {
937
1030
  medianSeconds: 23.3,
@@ -966,8 +1059,14 @@ var MODELS = {
966
1059
  },
967
1060
  qwen3: {
968
1061
  benchmark: {
1062
+ artificialAnalysis: {
1063
+ editing: { elo: 1218, rank: 15 },
1064
+ snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1065
+ sourceUrls: AA_IMAGE_SOURCES,
1066
+ textToImage: { elo: 1272, rank: 11 }
1067
+ },
969
1068
  tiers: { price: "budget", quality: "better", speed: "unknown" },
970
- useCase: "Qwen Image 3 - successor to qwen; no leaderboard data yet"
1069
+ useCase: "Strong quality per dollar - 11th on text-to-image at $30/1k"
971
1070
  },
972
1071
  endpoint: "fal-ai/qwen-image-3/text-to-image",
973
1072
  falPricing: {
@@ -1392,7 +1491,7 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
1392
1491
  if (options.background !== void 0 && config.supportsBackground !== true) {
1393
1492
  unsupported(config, "background");
1394
1493
  }
1395
- if (options.transparent === true && !(config.supportsBackground === true || config.supportsOutputFormat === true)) {
1494
+ if (options.transparent === true && config.supportsBackground !== true) {
1396
1495
  unsupported(config, "transparent output");
1397
1496
  }
1398
1497
  if (options.inputFidelity !== void 0) {
@@ -1674,353 +1773,351 @@ function buildGenerateBody(options) {
1674
1773
 
1675
1774
  // src/leaderboards.ts
1676
1775
  var IMAGE_TEXT_TO_IMAGE_TOP_20 = {
1677
- capturedAt: "2026-05-12",
1776
+ capturedAt: "2026-08-23",
1678
1777
  entries: [
1679
1778
  {
1680
1779
  apiPricing: "$211/1k images",
1681
1780
  creator: "OpenAI",
1682
- elo: 1337,
1781
+ elo: 1369,
1683
1782
  isCurrent: true,
1684
1783
  model: "GPT Image 2 (high)",
1685
1784
  rank: 1,
1686
1785
  routableModelId: "gpt2"
1687
1786
  },
1688
1787
  {
1689
- apiPricing: "$133/1k images",
1690
- creator: "OpenAI",
1691
- elo: 1268,
1788
+ apiPricing: "$200/1k images",
1789
+ creator: "Reve",
1790
+ elo: 1322,
1692
1791
  isCurrent: true,
1693
- model: "GPT Image 1.5 (high)",
1792
+ model: "Reve 2.1",
1694
1793
  rank: 2,
1695
- routableModelId: "gpt"
1794
+ routableModelId: "reve"
1696
1795
  },
1697
1796
  {
1698
1797
  apiPricing: "$67/1k images",
1699
1798
  creator: "Google",
1700
- elo: 1263,
1799
+ elo: 1320,
1701
1800
  isCurrent: true,
1702
1801
  model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1703
1802
  rank: 3,
1704
1803
  routableModelId: "banana2"
1705
1804
  },
1706
1805
  {
1707
- apiPricing: "$150/1k images",
1708
- creator: "Sourceful",
1709
- elo: 1256,
1806
+ apiPricing: "$133/1k images",
1807
+ creator: "OpenAI",
1808
+ elo: 1310,
1710
1809
  isCurrent: true,
1711
- model: "Riverflow 2.0",
1712
- rank: 4
1810
+ model: "GPT Image 1.5 (high)",
1811
+ rank: 4,
1812
+ routableModelId: "gpt"
1713
1813
  },
1714
1814
  {
1715
- apiPricing: "$134/1k images",
1716
- creator: "Google",
1717
- elo: 1220,
1815
+ apiPricing: "$48/1k images",
1816
+ creator: "Microsoft AI",
1817
+ elo: 1305,
1718
1818
  isCurrent: true,
1719
- model: "Nano Banana Pro (Gemini 3 Pro Image)",
1720
- rank: 5,
1721
- routableModelId: "banana"
1819
+ model: "MAI-Image-2.5",
1820
+ rank: 5
1722
1821
  },
1723
1822
  {
1724
- apiPricing: "$30/1k images",
1725
- creator: "ByteDance Seed",
1726
- elo: 1198,
1823
+ apiPricing: "$134/1k images",
1824
+ creator: "Google",
1825
+ elo: 1297,
1727
1826
  isCurrent: true,
1728
- model: "Seedream 4.0",
1827
+ model: "Nano Banana Pro (Gemini 3 Pro Image)",
1729
1828
  rank: 6,
1730
- routableModelId: "seedream4"
1829
+ routableModelId: "banana"
1731
1830
  },
1732
1831
  {
1733
- apiPricing: "$50/1k images",
1734
- creator: "Microsoft",
1735
- elo: 1198,
1832
+ apiPricing: "$109/1k images",
1833
+ creator: "Microsoft AI",
1834
+ elo: 1293,
1736
1835
  isCurrent: true,
1737
- model: "MAI-Image-2",
1836
+ model: "MAI-Image-2.5-Pro",
1738
1837
  rank: 7
1739
1838
  },
1740
1839
  {
1741
- apiPricing: "$70/1k images",
1742
- creator: "Black Forest Labs",
1743
- elo: 1197,
1840
+ apiPricing: "$34/1k images",
1841
+ creator: "Google",
1842
+ elo: 1289,
1744
1843
  isCurrent: true,
1745
- model: "FLUX.2 [max]",
1746
- rank: 8,
1747
- routableModelId: "flux2-max"
1844
+ model: "Nano Banana 2 Lite (Gemini 3.1 Flash Lite Image)",
1845
+ rank: 8
1748
1846
  },
1749
1847
  {
1750
- apiPricing: "Unavailable",
1751
- creator: "Peanut",
1752
- elo: 1187,
1848
+ apiPricing: "$43/1k images",
1849
+ creator: "Alibaba",
1850
+ elo: 1282,
1753
1851
  isCurrent: true,
1754
- model: "Peanut (Open Weights Coming Soon)",
1852
+ model: "Qwen-Image-3.0-Pro",
1755
1853
  rank: 9
1756
1854
  },
1757
1855
  {
1758
- apiPricing: "$30/1k images",
1759
- creator: "Black Forest Labs",
1760
- elo: 1186,
1856
+ apiPricing: "$90/1k images",
1857
+ creator: "Bytedance",
1858
+ elo: 1279,
1761
1859
  isCurrent: true,
1762
- model: "FLUX.2 [pro]",
1860
+ model: "Seedream 5.0 Pro",
1763
1861
  rank: 10,
1764
- routableModelId: "flux2-pro"
1862
+ routableModelId: "seedream5"
1765
1863
  },
1766
1864
  {
1767
- apiPricing: "$60/1k images",
1768
- creator: "Google",
1769
- elo: 1184,
1770
- isCurrent: false,
1771
- model: "Imagen 4 Ultra Preview 0606",
1772
- rank: 11
1865
+ apiPricing: "$30/1k images",
1866
+ creator: "Alibaba",
1867
+ elo: 1272,
1868
+ isCurrent: true,
1869
+ model: "Qwen-Image-3.0",
1870
+ rank: 11,
1871
+ routableModelId: "qwen3"
1773
1872
  },
1774
1873
  {
1775
- apiPricing: "$20/1k images",
1776
- creator: "xAI",
1777
- elo: 1182,
1874
+ apiPricing: "$75/1k images",
1875
+ creator: "Alibaba",
1876
+ elo: 1237,
1778
1877
  isCurrent: true,
1779
- model: "grok-imagine-image",
1780
- rank: 12,
1781
- routableModelId: "grok-image"
1878
+ model: "Qwen Image 2.0 Pro",
1879
+ rank: 12
1782
1880
  },
1783
1881
  {
1784
- apiPricing: "$60/1k images",
1785
- creator: "Black Forest Labs",
1786
- elo: 1182,
1882
+ apiPricing: "$50/1k images",
1883
+ creator: "SpaceXAI",
1884
+ elo: 1234,
1787
1885
  isCurrent: true,
1788
- model: "FLUX.2 [flex]",
1789
- rank: 13,
1790
- routableModelId: "flux2-flex"
1886
+ model: "grok-imagine-image-quality",
1887
+ rank: 13
1791
1888
  },
1792
1889
  {
1793
- apiPricing: "$30/1k images",
1794
- creator: "ImagineArt",
1795
- elo: 1181,
1890
+ apiPricing: "$20/1k images",
1891
+ creator: "Microsoft AI",
1892
+ elo: 1228,
1796
1893
  isCurrent: true,
1797
- model: "ImagineArt 2.0",
1894
+ model: "MAI-Image-2.5-Flash",
1798
1895
  rank: 14
1799
1896
  },
1800
1897
  {
1801
- apiPricing: "$60/1k images",
1802
- creator: "Google",
1803
- elo: 1171,
1898
+ apiPricing: "$80/1k images",
1899
+ creator: "HiDream",
1900
+ elo: 1228,
1804
1901
  isCurrent: true,
1805
- model: "Imagen 4 Ultra",
1902
+ model: "HiDream-O1-Image-1.5",
1806
1903
  rank: 15
1807
1904
  },
1808
1905
  {
1809
- apiPricing: "$40/1k images",
1810
- creator: "Google",
1811
- elo: 1169,
1812
- isCurrent: false,
1813
- model: "Imagen 4 Preview 0606",
1814
- rank: 16
1906
+ apiPricing: "$30/1k images",
1907
+ creator: "ByteDance Seed",
1908
+ elo: 1226,
1909
+ isCurrent: true,
1910
+ model: "Seedream 4.0",
1911
+ rank: 16,
1912
+ routableModelId: "seedream4"
1815
1913
  },
1816
1914
  {
1817
- apiPricing: "$40/1k images",
1818
- creator: "ByteDance Seed",
1819
- elo: 1167,
1915
+ apiPricing: "$70/1k images",
1916
+ creator: "Black Forest Labs",
1917
+ elo: 1225,
1820
1918
  isCurrent: true,
1821
- model: "Seedream 4.5",
1919
+ model: "FLUX.2 [max]",
1822
1920
  rank: 17,
1823
- routableModelId: "seedream45"
1921
+ routableModelId: "flux2-max"
1824
1922
  },
1825
1923
  {
1826
- apiPricing: "$8/1k images",
1827
- creator: "Fal",
1828
- elo: 1161,
1924
+ apiPricing: "$60/1k images",
1925
+ creator: "Black Forest Labs",
1926
+ elo: 1223,
1829
1927
  isCurrent: true,
1830
- model: "FLUX.2 [dev] Turbo",
1831
- rank: 18
1928
+ model: "FLUX.2 [flex]",
1929
+ rank: 18,
1930
+ routableModelId: "flux2-flex"
1832
1931
  },
1833
1932
  {
1834
- apiPricing: "$12/1k images",
1835
- creator: "Black Forest Labs",
1836
- elo: 1160,
1933
+ apiPricing: "$60/1k images",
1934
+ creator: "Ideogram",
1935
+ elo: 1220,
1837
1936
  isCurrent: true,
1838
- model: "FLUX.2 [dev]",
1937
+ model: "Ideogram 4.0",
1839
1938
  rank: 19,
1840
- routableModelId: "flux2-dev"
1939
+ routableModelId: "ideogram4"
1841
1940
  },
1842
1941
  {
1843
- apiPricing: "$20/1k images",
1844
- creator: "Alibaba",
1845
- elo: 1158,
1942
+ apiPricing: "$60/1k images",
1943
+ creator: "Krea",
1944
+ elo: 1220,
1846
1945
  isCurrent: true,
1847
- model: "Qwen Image Max 2512",
1848
- rank: 20,
1849
- routableModelId: "qwen"
1946
+ model: "Krea 2 Large",
1947
+ rank: 20
1850
1948
  }
1851
1949
  ],
1852
1950
  source: "https://artificialanalysis.ai/image/leaderboard/text-to-image"
1853
1951
  };
1854
1952
  var IMAGE_EDITING_TOP_20 = {
1855
- capturedAt: "2026-05-12",
1953
+ capturedAt: "2026-08-23",
1856
1954
  entries: [
1857
1955
  {
1858
- apiPricing: "$150/1k images",
1859
- creator: "Sourceful",
1860
- elo: 1286,
1956
+ apiPricing: "$109/1k images",
1957
+ creator: "Microsoft AI",
1958
+ elo: 1272,
1861
1959
  isCurrent: true,
1862
- model: "Riverflow 2.0",
1960
+ model: "MAI-Image-2.5-Pro",
1863
1961
  rank: 1
1864
1962
  },
1865
1963
  {
1866
- apiPricing: "$133/1k images",
1867
- creator: "OpenAI",
1868
- elo: 1262,
1964
+ apiPricing: "$200/1k images",
1965
+ creator: "Reve",
1966
+ elo: 1263,
1869
1967
  isCurrent: true,
1870
- model: "GPT Image 1.5 (high)",
1968
+ model: "Reve 2.1",
1871
1969
  rank: 2,
1872
- routableModelId: "gpt"
1970
+ routableModelId: "reve"
1873
1971
  },
1874
1972
  {
1875
1973
  apiPricing: "$211/1k images",
1876
1974
  creator: "OpenAI",
1877
- elo: 1249,
1975
+ elo: 1256,
1878
1976
  isCurrent: true,
1879
1977
  model: "GPT Image 2 (high)",
1880
1978
  rank: 3,
1881
1979
  routableModelId: "gpt2"
1882
1980
  },
1883
1981
  {
1884
- apiPricing: "$134/1k images",
1885
- creator: "Google",
1886
- elo: 1241,
1982
+ apiPricing: "$48/1k images",
1983
+ creator: "Microsoft AI",
1984
+ elo: 1256,
1887
1985
  isCurrent: true,
1888
- model: "Nano Banana Pro (Gemini 3 Pro Image)",
1889
- rank: 4,
1890
- routableModelId: "banana"
1986
+ model: "MAI-Image-2.5",
1987
+ rank: 4
1891
1988
  },
1892
1989
  {
1893
- apiPricing: "$67/1k images",
1894
- creator: "Google",
1895
- elo: 1231,
1990
+ apiPricing: "$133/1k images",
1991
+ creator: "OpenAI",
1992
+ elo: 1250,
1896
1993
  isCurrent: true,
1897
- model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1994
+ model: "GPT Image 1.5 (high)",
1898
1995
  rank: 5,
1899
- routableModelId: "banana2"
1996
+ routableModelId: "gpt"
1900
1997
  },
1901
1998
  {
1902
- apiPricing: "$90/1k images",
1903
- creator: "Tencent",
1904
- elo: 1222,
1999
+ apiPricing: "$43/1k images",
2000
+ creator: "Alibaba",
2001
+ elo: 1250,
1905
2002
  isCurrent: true,
1906
- model: "HunyuanImage 3.0 Instruct (Fal)",
2003
+ model: "Qwen-Image-3.0-Pro",
1907
2004
  rank: 6
1908
2005
  },
1909
2006
  {
1910
- apiPricing: "$20/1k images",
1911
- creator: "xAI",
1912
- elo: 1213,
2007
+ apiPricing: "$67/1k images",
2008
+ creator: "Google",
2009
+ elo: 1249,
1913
2010
  isCurrent: true,
1914
- model: "grok-imagine-image",
2011
+ model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
1915
2012
  rank: 7,
1916
- routableModelId: "grok-image"
2013
+ routableModelId: "banana2"
1917
2014
  },
1918
2015
  {
1919
- apiPricing: "$70/1k images",
1920
- creator: "xAI",
1921
- elo: 1212,
2016
+ apiPricing: "$90/1k images",
2017
+ creator: "Bytedance",
2018
+ elo: 1248,
1922
2019
  isCurrent: true,
1923
- model: "grok-imagine-image-pro",
1924
- rank: 8
2020
+ model: "Seedream 5.0 Pro",
2021
+ rank: 8,
2022
+ routableModelId: "seedream5"
1925
2023
  },
1926
2024
  {
1927
- apiPricing: "Unavailable",
1928
- creator: "KlingAI",
1929
- elo: 1207,
2025
+ apiPricing: "$134/1k images",
2026
+ creator: "Google",
2027
+ elo: 1245,
1930
2028
  isCurrent: true,
1931
- model: "Kling Image 3.0 Omni",
1932
- rank: 9
2029
+ model: "Nano Banana Pro (Gemini 3 Pro Image)",
2030
+ rank: 9,
2031
+ routableModelId: "banana"
1933
2032
  },
1934
2033
  {
1935
- apiPricing: "$140/1k images",
1936
- creator: "Black Forest Labs",
1937
- elo: 1206,
2034
+ apiPricing: "$20/1k images",
2035
+ creator: "Microsoft AI",
2036
+ elo: 1236,
1938
2037
  isCurrent: true,
1939
- model: "FLUX.2 [max]",
1940
- rank: 10,
1941
- routableModelId: "flux2-max"
2038
+ model: "MAI-Image-2.5-Flash",
2039
+ rank: 10
1942
2040
  },
1943
2041
  {
1944
- apiPricing: "Unavailable",
1945
- creator: "Alibaba",
1946
- elo: 1200,
2042
+ apiPricing: "$50/1k images",
2043
+ creator: "SpaceXAI",
2044
+ elo: 1226,
1947
2045
  isCurrent: true,
1948
- model: "Wan 2.7 Pro",
2046
+ model: "grok-imagine-image-quality",
1949
2047
  rank: 11
1950
2048
  },
1951
2049
  {
1952
- apiPricing: "$28/1k images",
1953
- creator: "KlingAI",
1954
- elo: 1196,
2050
+ apiPricing: "$90/1k images",
2051
+ creator: "Tencent",
2052
+ elo: 1222,
1955
2053
  isCurrent: true,
1956
- model: "Kling Image 3.0",
2054
+ model: "HunyuanImage 3.0 Instruct",
1957
2055
  rank: 12
1958
2056
  },
1959
2057
  {
1960
- apiPricing: "$28/1k images",
1961
- creator: "KlingAI",
1962
- elo: 1193,
2058
+ apiPricing: "$80/1k images",
2059
+ creator: "Newport AI",
2060
+ elo: 1220,
1963
2061
  isCurrent: true,
1964
- model: "Kling Image O1",
2062
+ model: "DreamImage 2.0",
1965
2063
  rank: 13
1966
2064
  },
1967
2065
  {
1968
- apiPricing: "$30/1k images",
1969
- creator: "Alibaba",
1970
- elo: 1188,
2066
+ apiPricing: "$100/1k images",
2067
+ creator: "Luma Labs",
2068
+ elo: 1219,
1971
2069
  isCurrent: true,
1972
- model: "Wan 2.6 Image",
2070
+ model: "Luma UNI 1 Max",
1973
2071
  rank: 14
1974
2072
  },
1975
2073
  {
1976
- apiPricing: "$66/1k images",
1977
- creator: "Sourceful",
1978
- elo: 1184,
2074
+ apiPricing: "$33/1k images",
2075
+ creator: "Alibaba",
2076
+ elo: 1218,
1979
2077
  isCurrent: true,
1980
- model: "Riverflow 1",
1981
- rank: 15
2078
+ model: "Qwen-Image-3.0",
2079
+ rank: 15,
2080
+ routableModelId: "qwen3"
1982
2081
  },
1983
2082
  {
1984
- apiPricing: "$30/1k images",
1985
- creator: "ByteDance Seed",
1986
- elo: 1184,
2083
+ apiPricing: "$20/1k images",
2084
+ creator: "SpaceXAI",
2085
+ elo: 1214,
1987
2086
  isCurrent: true,
1988
- model: "Seedream 4.0",
2087
+ model: "grok-imagine-image",
1989
2088
  rank: 16,
1990
- routableModelId: "seedream4"
2089
+ routableModelId: "grok-image"
1991
2090
  },
1992
2091
  {
1993
- apiPricing: "$40/1k images",
1994
- creator: "ByteDance Seed",
1995
- elo: 1184,
2092
+ apiPricing: "$28/1k images",
2093
+ creator: "KlingAI",
2094
+ elo: 1208,
1996
2095
  isCurrent: true,
1997
- model: "Seedream 4.5",
1998
- rank: 17,
1999
- routableModelId: "seedream45"
2096
+ model: "Kling Image 3.0 Omni",
2097
+ rank: 17
2000
2098
  },
2001
2099
  {
2002
- apiPricing: "$26/1k images",
2003
- creator: "Alibaba",
2004
- elo: 1181,
2100
+ apiPricing: "$34/1k images",
2101
+ creator: "Google",
2102
+ elo: 1202,
2005
2103
  isCurrent: true,
2006
- model: "Wan 2.7",
2104
+ model: "Nano Banana 2 Lite",
2007
2105
  rank: 18
2008
2106
  },
2009
2107
  {
2010
- apiPricing: "$39/1k images",
2011
- creator: "Google",
2012
- elo: 1173,
2108
+ apiPricing: "$140/1k images",
2109
+ creator: "Black Forest Labs",
2110
+ elo: 1201,
2013
2111
  isCurrent: true,
2014
- model: "Nano Banana (Gemini 2.5 Flash Image)",
2112
+ model: "FLUX.2 [max]",
2015
2113
  rank: 19,
2016
- routableModelId: "gemini"
2114
+ routableModelId: "flux2-max"
2017
2115
  },
2018
2116
  {
2019
- apiPricing: "$40/1k images",
2020
- creator: "Reve",
2021
- elo: 1172,
2117
+ creator: "Alibaba",
2118
+ elo: 1200,
2022
2119
  isCurrent: true,
2023
- model: "Reve V1 (December)",
2120
+ model: "Wan 2.7 Pro",
2024
2121
  rank: 20
2025
2122
  }
2026
2123
  ],
@@ -2250,7 +2347,31 @@ var VIDEO_IMAGE_TO_VIDEO_TOP_15 = {
2250
2347
  };
2251
2348
 
2252
2349
  // src/server.ts
2253
- var import_neverthrow = require("neverthrow");
2350
+ var import_neverthrow2 = require("neverthrow");
2351
+
2352
+ // src/errors.ts
2353
+ var MotifError = class extends Error {
2354
+ status;
2355
+ code;
2356
+ /** fal's request-correlation id (from the `x-fal-request-id` header or the
2357
+ * error body). Ties a failure back to fal's dashboard/support. */
2358
+ requestId;
2359
+ constructor(message, status, code, requestId) {
2360
+ super(message);
2361
+ this.name = "MotifError";
2362
+ this.status = status;
2363
+ this.code = code;
2364
+ this.requestId = requestId;
2365
+ }
2366
+ };
2367
+ function toMotifError(error) {
2368
+ if (error instanceof MotifError) {
2369
+ return error;
2370
+ }
2371
+ const message = error instanceof Error ? error.message : String(error);
2372
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
2373
+ return new MotifError(message, 0, code);
2374
+ }
2254
2375
 
2255
2376
  // src/fal-parse.ts
2256
2377
  function isRecord(value) {
@@ -2348,9 +2469,369 @@ function requestIdFromBody(text) {
2348
2469
  return asString(parsed.request_id) ?? asString(parsed.requestId) ?? asString(parsed.trace_id);
2349
2470
  }
2350
2471
 
2351
- // src/tools.ts
2352
- var FAL_EXPLORE_CHECKED_AT = "2026-05-12";
2353
- var FAL_TOOLS = {
2472
+ // src/server-tools.ts
2473
+ var import_neverthrow = require("neverthrow");
2474
+
2475
+ // src/tool-registry/analysis.ts
2476
+ var ANALYSIS_TOOLS = {
2477
+ "got-ocr": {
2478
+ category: "analysis",
2479
+ description: "Transcribe text from one or more images, optionally as formatted multi-page output.",
2480
+ endpoint: "fal-ai/got-ocr/v2",
2481
+ inputField: "input_image_urls",
2482
+ inputKind: "images",
2483
+ name: "GOT-OCR 2.0",
2484
+ outputKeys: ["outputs"],
2485
+ price: { kind: "metered" },
2486
+ pricing: "$0.05/image",
2487
+ queued: true,
2488
+ sourceUrl: "https://fal.ai/models/fal-ai/got-ocr/v2",
2489
+ task: "optical character recognition"
2490
+ },
2491
+ "moondream-caption": {
2492
+ category: "analysis",
2493
+ description: "Write a short, normal, or long caption for an image.",
2494
+ endpoint: "fal-ai/moondream3-preview/caption",
2495
+ inputField: "image_url",
2496
+ inputKind: "image",
2497
+ name: "Moondream 3 Caption",
2498
+ outputKeys: ["output"],
2499
+ price: { kind: "metered" },
2500
+ pricing: "$0.40/M input tokens, $3.50/M output tokens",
2501
+ sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/caption",
2502
+ task: "image captioning"
2503
+ },
2504
+ "moondream-detect": {
2505
+ category: "analysis",
2506
+ description: "Detect prompted objects and return their bounding boxes, with an optional preview.",
2507
+ endpoint: "fal-ai/moondream3-preview/detect",
2508
+ inputField: "image_url",
2509
+ inputKind: "image",
2510
+ name: "Moondream 3 Detect",
2511
+ outputKeys: ["objects", "image"],
2512
+ price: { kind: "metered" },
2513
+ pricing: "$0.40/M input tokens, $3.50/M output tokens",
2514
+ sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/detect",
2515
+ task: "object detection"
2516
+ },
2517
+ "moondream-point": {
2518
+ category: "analysis",
2519
+ description: "Return point coordinates for every instance of a prompted subject.",
2520
+ endpoint: "fal-ai/moondream3-preview/point",
2521
+ inputField: "image_url",
2522
+ inputKind: "image",
2523
+ name: "Moondream 3 Point",
2524
+ outputKeys: ["points", "image"],
2525
+ price: { kind: "metered" },
2526
+ pricing: "$0.40/M input tokens, $3.50/M output tokens",
2527
+ sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/point",
2528
+ task: "object pointing"
2529
+ },
2530
+ "moondream-query": {
2531
+ category: "analysis",
2532
+ description: "Answer a free-form question about an image, with reasoning.",
2533
+ endpoint: "fal-ai/moondream3-preview/query",
2534
+ inputField: "image_url",
2535
+ inputKind: "image",
2536
+ name: "Moondream 3 Query",
2537
+ outputKeys: ["output", "reasoning"],
2538
+ price: { kind: "metered" },
2539
+ pricing: "$0.40/M input tokens, $3.50/M output tokens",
2540
+ sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/query",
2541
+ task: "visual question answering"
2542
+ },
2543
+ nsfw: {
2544
+ category: "moderation",
2545
+ description: "Predict whether one or more images contain NSFW concepts.",
2546
+ endpoint: "fal-ai/x-ailab/nsfw",
2547
+ inputField: "image_urls",
2548
+ inputKind: "images",
2549
+ name: "NSFW Checker",
2550
+ outputKeys: ["has_nsfw_concepts"],
2551
+ price: { kind: "metered" },
2552
+ pricing: "$0.001/image",
2553
+ sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
2554
+ task: "vision moderation"
2555
+ }
2556
+ };
2557
+
2558
+ // src/tool-registry/assets.ts
2559
+ var ASSET_TOOLS = {
2560
+ "hunyuan3d-v3": {
2561
+ category: "3d",
2562
+ description: "Reconstruct a textured 3D mesh from a single image, with optional multi-view inputs.",
2563
+ endpoint: "fal-ai/hunyuan3d-v3/image-to-3d",
2564
+ inputField: "input_image_url",
2565
+ inputKind: "image",
2566
+ name: "Hunyuan3D v3 Image to 3D",
2567
+ outputKeys: ["model_glb", "model_urls", "thumbnail"],
2568
+ price: { kind: "call", usd: 0.375 },
2569
+ 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",
2570
+ queued: true,
2571
+ sourceUrl: "https://fal.ai/models/fal-ai/hunyuan3d-v3/image-to-3d",
2572
+ task: "single-image 3D reconstruction"
2573
+ },
2574
+ "ideogram-layerize-text": {
2575
+ category: "layers",
2576
+ description: "Split an image into background image layers plus editable HTML text containers.",
2577
+ endpoint: "fal-ai/ideogram/v3/layerize-text",
2578
+ inputField: "image_url",
2579
+ inputKind: "image",
2580
+ name: "Ideogram v3 Text Layerize",
2581
+ outputKeys: ["image_layers", "image", "text_html", "text_containers"],
2582
+ price: { kind: "call", usd: 0.09 },
2583
+ pricing: "$0.09/image",
2584
+ queued: true,
2585
+ sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/layerize-text",
2586
+ task: "text and image layer extraction"
2587
+ },
2588
+ "ideogram-tiling": {
2589
+ category: "material",
2590
+ description: "Generate a seamlessly tiling texture, optionally conditioned on a source image.",
2591
+ endpoint: "ideogram/v4/tiling",
2592
+ inputField: "image_url",
2593
+ inputKind: "image",
2594
+ name: "Ideogram v4 Tiling",
2595
+ outputKeys: ["images"],
2596
+ price: { kind: "megapixel", usd: 0.06 },
2597
+ pricing: "$0.06/megapixel at the default BALANCED speed; $0.03 turbo, $0.10 quality",
2598
+ sourceUrl: "https://fal.ai/models/ideogram/v4/tiling",
2599
+ task: "seamless texture generation"
2600
+ },
2601
+ image2svg: {
2602
+ category: "vector",
2603
+ description: "Trace a raster image into layered SVG paths.",
2604
+ endpoint: "fal-ai/image2svg",
2605
+ inputField: "image_url",
2606
+ inputKind: "image",
2607
+ name: "Image to SVG",
2608
+ outputKeys: ["images"],
2609
+ price: { kind: "call", usd: 5e-3 },
2610
+ pricing: "$0.005/image",
2611
+ sourceUrl: "https://fal.ai/models/fal-ai/image2svg",
2612
+ task: "raster to vector tracing"
2613
+ },
2614
+ patina: {
2615
+ category: "material",
2616
+ description: "Decompose a surface photograph into PBR maps: basecolor, normal, roughness, metalness, height.",
2617
+ endpoint: "fal-ai/patina",
2618
+ inputField: "image_url",
2619
+ inputKind: "image",
2620
+ name: "Patina PBR Maps",
2621
+ outputKeys: ["images"],
2622
+ // Output order follows the request's `maps` array, so read that first;
2623
+ // the fallback is the endpoint's own schema default.
2624
+ outputLabels: {
2625
+ images: {
2626
+ fallback: ["basecolor", "normal", "roughness", "metalness", "height"],
2627
+ fromOption: "maps"
2628
+ }
2629
+ },
2630
+ price: { kind: "metered" },
2631
+ 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",
2632
+ queued: true,
2633
+ sourceUrl: "https://fal.ai/models/fal-ai/patina",
2634
+ task: "PBR material decomposition"
2635
+ },
2636
+ "patina-extract": {
2637
+ category: "material",
2638
+ description: "Extract a tiling PBR material from a prompted region of a photograph.",
2639
+ endpoint: "fal-ai/patina/material/extract",
2640
+ inputField: "image_url",
2641
+ inputKind: "image",
2642
+ name: "Patina Material Extract",
2643
+ outputKeys: ["images"],
2644
+ // Same `maps` option and schema default as fal-ai/patina, both verified.
2645
+ // `num_images` above 1 multiplies the array and breaks the mapping, which
2646
+ // the count check catches.
2647
+ outputLabels: {
2648
+ images: {
2649
+ fallback: ["basecolor", "normal", "roughness", "metalness", "height"],
2650
+ fromOption: "maps"
2651
+ }
2652
+ },
2653
+ price: { kind: "metered" },
2654
+ pricing: "$0.10 base only; add $0.02/megapixel plus $0.01/megapixel per map, so 1MP with all 5 maps is $0.17",
2655
+ queued: true,
2656
+ sourceUrl: "https://fal.ai/models/fal-ai/patina/material/extract",
2657
+ task: "tiling material extraction"
2658
+ },
2659
+ "qwen-layered": {
2660
+ category: "layers",
2661
+ description: "Split an image into a configurable number of stacked RGBA layers.",
2662
+ endpoint: "fal-ai/qwen-image-layered",
2663
+ inputField: "image_url",
2664
+ inputKind: "image",
2665
+ name: "Qwen Image Layered",
2666
+ outputKeys: ["images"],
2667
+ // fal publishes a figure but not what it counts. Encoding it as `call`
2668
+ // would assert a per-request price we cannot support, and this endpoint
2669
+ // returns several outputs — the same shape that made `seedream-layerize`
2670
+ // under-report by a factor of its layer count. `metered` reports null
2671
+ // rather than a number that is right only on one reading.
2672
+ price: { kind: "metered" },
2673
+ 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",
2674
+ queued: true,
2675
+ sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-layered",
2676
+ task: "image layer decomposition"
2677
+ },
2678
+ "recraft-vectorize": {
2679
+ category: "vector",
2680
+ description: "Convert a raster image into a clean SVG.",
2681
+ endpoint: "fal-ai/recraft/vectorize",
2682
+ inputField: "image_url",
2683
+ inputKind: "image",
2684
+ name: "Recraft Vectorize",
2685
+ outputKeys: ["image"],
2686
+ price: { kind: "call", usd: 0.04 },
2687
+ pricing: "$0.04/image, $0.08 with a vector style",
2688
+ sourceUrl: "https://fal.ai/models/fal-ai/recraft/vectorize",
2689
+ task: "raster to vector conversion"
2690
+ },
2691
+ "sam3-3d-align": {
2692
+ category: "3d",
2693
+ description: "Align SAM 3D objects and bodies into a shared scene.",
2694
+ endpoint: "fal-ai/sam-3/3d-align",
2695
+ inputField: "image_url",
2696
+ inputKind: "image",
2697
+ name: "SAM 3D Align",
2698
+ outputKeys: [
2699
+ "scene_glb",
2700
+ "model_glb",
2701
+ "visualization",
2702
+ "body_mesh_ply",
2703
+ "metadata"
2704
+ ],
2705
+ // fal publishes a figure but not what it counts. Encoding it as `call`
2706
+ // would assert a per-request price we cannot support, and this endpoint
2707
+ // returns several outputs — the same shape that made `seedream-layerize`
2708
+ // under-report by a factor of its layer count. `metered` reports null
2709
+ // rather than a number that is right only on one reading.
2710
+ price: { kind: "metered" },
2711
+ pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
2712
+ queued: true,
2713
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
2714
+ task: "3D scene alignment"
2715
+ },
2716
+ "sam3-3d-body": {
2717
+ category: "3d",
2718
+ defaultOptions: {
2719
+ export_meshes: true,
2720
+ include_3d_keypoints: true,
2721
+ include_mhr_params: true
2722
+ },
2723
+ description: "Reconstruct human body meshes and keypoints from a single image.",
2724
+ endpoint: "fal-ai/sam-3/3d-body",
2725
+ inputField: "image_url",
2726
+ inputKind: "image",
2727
+ name: "SAM 3D Body",
2728
+ outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
2729
+ // fal publishes a figure but not what it counts. Encoding it as `call`
2730
+ // would assert a per-request price we cannot support, and this endpoint
2731
+ // returns several outputs — the same shape that made `seedream-layerize`
2732
+ // under-report by a factor of its layer count. `metered` reports null
2733
+ // rather than a number that is right only on one reading.
2734
+ price: { kind: "metered" },
2735
+ pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
2736
+ queued: true,
2737
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
2738
+ task: "single-image 3D body reconstruction"
2739
+ },
2740
+ "sam3-3d-objects": {
2741
+ category: "3d",
2742
+ defaultOptions: {
2743
+ prompt: "car"
2744
+ },
2745
+ description: "Reconstruct one or more 3D objects from an image and prompts.",
2746
+ endpoint: "fal-ai/sam-3/3d-objects",
2747
+ inputField: "image_url",
2748
+ inputKind: "image",
2749
+ name: "SAM 3D Objects",
2750
+ outputKeys: [
2751
+ "gaussian_splat",
2752
+ "model_glb",
2753
+ "metadata",
2754
+ "individual_splats",
2755
+ "individual_glbs",
2756
+ "artifacts_zip"
2757
+ ],
2758
+ // fal publishes a figure but not what it counts. Encoding it as `call`
2759
+ // would assert a per-request price we cannot support, and this endpoint
2760
+ // returns several outputs — the same shape that made `seedream-layerize`
2761
+ // under-report by a factor of its layer count. `metered` reports null
2762
+ // rather than a number that is right only on one reading.
2763
+ price: { kind: "metered" },
2764
+ 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",
2765
+ queued: true,
2766
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
2767
+ task: "single-image 3D object reconstruction"
2768
+ },
2769
+ "seedream-layerize": {
2770
+ category: "layers",
2771
+ description: "Split an image into editable Seedream layers plus flattened previews.",
2772
+ endpoint: "bytedance/seedream/v5/pro/layerize",
2773
+ inputField: "image_url",
2774
+ inputKind: "image",
2775
+ name: "Seedream v5 Pro Layerize",
2776
+ outputKeys: ["layers", "images"],
2777
+ // Every element of `layers` carries its own `name` ("Left amber glass
2778
+ // bottle") and a `z_index` that orders the stack, verified live. Without
2779
+ // this the stack lands as layers.png … layers-5.png and the only way to
2780
+ // tell the reconstructed background plate from an object is to open them.
2781
+ outputLabels: {
2782
+ layers: { fromItem: { nameField: "name", orderField: "z_index" } }
2783
+ },
2784
+ price: { kind: "metered" },
2785
+ pricing: "$0.03375 per generated layer below 1536x1536 total area, $0.0675 per layer above; the listed rate is per layer, not per call",
2786
+ queued: true,
2787
+ sourceUrl: "https://fal.ai/models/bytedance/seedream/v5/pro/layerize",
2788
+ task: "image layer decomposition"
2789
+ },
2790
+ "seedvr-seamless": {
2791
+ category: "material",
2792
+ description: "Upscale a tiling texture while keeping its edges seamless. ",
2793
+ endpoint: "fal-ai/seedvr/upscale/image/seamless",
2794
+ inputField: "image_url",
2795
+ inputKind: "image",
2796
+ name: "SeedVR Seamless Upscale",
2797
+ outputKeys: ["image"],
2798
+ price: { kind: "megapixel", usd: 25e-4 },
2799
+ pricing: "$0.0025/megapixel",
2800
+ queued: true,
2801
+ sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image/seamless",
2802
+ task: "seamless texture upscaling"
2803
+ },
2804
+ "trellis-2": {
2805
+ category: "3d",
2806
+ description: "Reconstruct a textured 3D mesh from a single image, with UV unwrapping.",
2807
+ endpoint: "fal-ai/trellis-2",
2808
+ inputField: "image_url",
2809
+ inputKind: "image",
2810
+ name: "TRELLIS 2",
2811
+ outputKeys: ["model_glb"],
2812
+ price: { kind: "call", usd: 0.3 },
2813
+ pricing: "$0.30 at the default 1024p; $0.25 at 512p, $0.35 at 1536p",
2814
+ queued: true,
2815
+ sourceUrl: "https://fal.ai/models/fal-ai/trellis-2",
2816
+ task: "single-image 3D reconstruction"
2817
+ }
2818
+ };
2819
+
2820
+ // src/tool-registry/background.ts
2821
+ var BACKGROUND_TOOLS = {
2822
+ "ben-v2": {
2823
+ category: "background",
2824
+ description: "Fast, high-quality background removal for images.",
2825
+ endpoint: "fal-ai/ben/v2/image",
2826
+ inputField: "image_url",
2827
+ inputKind: "image",
2828
+ name: "BEN v2 Background Removal",
2829
+ outputKeys: ["image"],
2830
+ price: { kind: "megapixel", usd: 0.025 },
2831
+ pricing: "$0.025/megapixel",
2832
+ sourceUrl: "https://fal.ai/models/fal-ai/ben/v2/image",
2833
+ task: "image background removal"
2834
+ },
2354
2835
  birefnet: {
2355
2836
  category: "background",
2356
2837
  defaultOptions: {
@@ -2365,6 +2846,7 @@ var FAL_TOOLS = {
2365
2846
  inputKind: "image",
2366
2847
  name: "BirefNet Background Removal",
2367
2848
  outputKeys: ["image", "mask_image"],
2849
+ price: { kind: "metered" },
2368
2850
  pricing: "$0/compute-second listed by fal",
2369
2851
  sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
2370
2852
  task: "image background removal"
@@ -2377,7 +2859,8 @@ var FAL_TOOLS = {
2377
2859
  inputKind: "image",
2378
2860
  name: "Bria RMBG 2.0",
2379
2861
  outputKeys: ["image"],
2380
- pricing: "$0.02/image",
2862
+ price: { kind: "call", usd: 0.018 },
2863
+ pricing: "$0.018/generation",
2381
2864
  sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove",
2382
2865
  task: "image background removal"
2383
2866
  },
@@ -2394,10 +2877,422 @@ var FAL_TOOLS = {
2394
2877
  inputKind: "video",
2395
2878
  name: "Bria Video Background Removal",
2396
2879
  outputKeys: ["video"],
2397
- pricing: "$0.14/sec",
2880
+ price: { kind: "second", usd: 425e-5 },
2881
+ pricing: "$0.00425/sec",
2882
+ queued: true,
2398
2883
  sourceUrl: "https://fal.ai/models/bria/video/background-removal",
2399
2884
  task: "video background removal"
2400
2885
  },
2886
+ ddcolor: {
2887
+ category: "restoration",
2888
+ description: "Colourise black-and-white photographs.",
2889
+ endpoint: "fal-ai/ddcolor",
2890
+ inputField: "image_url",
2891
+ inputKind: "image",
2892
+ name: "DDColor Colourisation",
2893
+ outputKeys: ["image"],
2894
+ price: { kind: "megapixel", usd: 1e-3 },
2895
+ pricing: "$0.001/megapixel",
2896
+ sourceUrl: "https://fal.ai/models/fal-ai/ddcolor",
2897
+ task: "photo colourisation"
2898
+ },
2899
+ rembg: {
2900
+ category: "background",
2901
+ defaultOptions: {
2902
+ crop_to_bbox: false
2903
+ },
2904
+ description: "Generic image background removal utility.",
2905
+ endpoint: "fal-ai/imageutils/rembg",
2906
+ inputField: "image_url",
2907
+ inputKind: "image",
2908
+ name: "Remove Background",
2909
+ outputKeys: ["image"],
2910
+ price: { kind: "metered" },
2911
+ pricing: "$0/compute-second listed by fal",
2912
+ sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
2913
+ task: "image background removal"
2914
+ },
2915
+ "seedvr-upscale": {
2916
+ category: "restoration",
2917
+ defaultOptions: {
2918
+ output_format: "png"
2919
+ },
2920
+ description: "Diffusion image restoration and upscaling with SeedVR.",
2921
+ endpoint: "fal-ai/seedvr/upscale/image",
2922
+ inputField: "image_url",
2923
+ inputKind: "image",
2924
+ name: "SeedVR Image Upscale",
2925
+ outputKeys: ["image"],
2926
+ price: { kind: "megapixel", usd: 1e-3 },
2927
+ pricing: "$0.001/megapixel",
2928
+ queued: true,
2929
+ sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image",
2930
+ task: "image restoration and upscaling"
2931
+ },
2932
+ "topaz-adjust": {
2933
+ category: "restoration",
2934
+ defaultOptions: {
2935
+ output_format: "png"
2936
+ },
2937
+ description: "Topaz exposure, white balance, and colour adjustment.",
2938
+ endpoint: "topaz/adjust/image",
2939
+ inputField: "image_url",
2940
+ inputKind: "image",
2941
+ name: "Topaz Adjust",
2942
+ outputKeys: ["image"],
2943
+ price: { kind: "megapixel", usd: 0.08 / 24 },
2944
+ pricing: "$0.08 per 24 output megapixels",
2945
+ queued: true,
2946
+ sourceUrl: "https://fal.ai/models/topaz/adjust/image",
2947
+ task: "tonal adjustment"
2948
+ },
2949
+ "topaz-creative": {
2950
+ category: "restoration",
2951
+ defaultOptions: {
2952
+ output_format: "png"
2953
+ },
2954
+ description: "Topaz Bloom upscaling, which reimagines detail rather than preserving it.",
2955
+ endpoint: "topaz/upscale/image/creative",
2956
+ inputField: "image_url",
2957
+ inputKind: "image",
2958
+ name: "Topaz Creative Upscale",
2959
+ outputKeys: ["image"],
2960
+ price: { kind: "megapixel", usd: 0.96 / 24 },
2961
+ pricing: "$0.96 per 24 output megapixels",
2962
+ queued: true,
2963
+ sourceUrl: "https://fal.ai/models/topaz/upscale/image/creative",
2964
+ task: "creative image upscaling"
2965
+ },
2966
+ "topaz-denoise": {
2967
+ category: "restoration",
2968
+ defaultOptions: {
2969
+ output_format: "png"
2970
+ },
2971
+ description: "Topaz noise reduction at source resolution.",
2972
+ endpoint: "topaz/denoise/image",
2973
+ inputField: "image_url",
2974
+ inputKind: "image",
2975
+ name: "Topaz Denoise",
2976
+ outputKeys: ["image"],
2977
+ price: { kind: "megapixel", usd: 0.08 / 24 },
2978
+ pricing: "$0.08 per 24 output megapixels at the default Normal model; $0.16 with Denoise Max",
2979
+ queued: true,
2980
+ sourceUrl: "https://fal.ai/models/topaz/denoise/image",
2981
+ task: "image denoising"
2982
+ },
2983
+ "topaz-generative": {
2984
+ category: "restoration",
2985
+ defaultOptions: {
2986
+ output_format: "png"
2987
+ },
2988
+ description: "Topaz Wonder upscaling, which synthesises plausible detail as it enlarges.",
2989
+ endpoint: "topaz/upscale/image/generative",
2990
+ inputField: "image_url",
2991
+ inputKind: "image",
2992
+ name: "Topaz Generative Upscale",
2993
+ outputKeys: ["image"],
2994
+ price: { kind: "megapixel", usd: 0.24 / 24 },
2995
+ pricing: "$0.24 per 24 output megapixels at the default Wonder 3; $0.48 with Wonder, Wonder 2, Standard MAX, Redefine or Recover 3",
2996
+ queued: true,
2997
+ sourceUrl: "https://fal.ai/models/topaz/upscale/image/generative",
2998
+ task: "generative image upscaling"
2999
+ },
3000
+ "topaz-image": {
3001
+ category: "upscale",
3002
+ defaultOptions: {
3003
+ model: "Standard V2",
3004
+ output_format: "jpeg",
3005
+ upscale_factor: 2
3006
+ },
3007
+ description: "Professional Topaz image enhancement and upscaling.",
3008
+ endpoint: "fal-ai/topaz/upscale/image",
3009
+ inputField: "image_url",
3010
+ inputKind: "image",
3011
+ name: "Topaz Image Upscale",
3012
+ outputKeys: ["image"],
3013
+ price: { kind: "megapixel", usd: 0.08 / 24 },
3014
+ pricing: "$0.08 for output up to 24MP; $0.16 to 48MP, $0.32 to 96MP, up to $1.36 at 512MP",
3015
+ queued: true,
3016
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
3017
+ task: "image enhancement"
3018
+ },
3019
+ "topaz-precision": {
3020
+ category: "restoration",
3021
+ defaultOptions: {
3022
+ output_format: "png"
3023
+ },
3024
+ description: "Topaz upscaling that preserves the original detail rather than inventing it.",
3025
+ endpoint: "topaz/upscale/image/precision",
3026
+ inputField: "image_url",
3027
+ inputKind: "image",
3028
+ name: "Topaz Precision Upscale",
3029
+ outputKeys: ["image"],
3030
+ price: { kind: "megapixel", usd: 0.08 / 24 },
3031
+ pricing: "$0.08 per 24 output megapixels, any precision model",
3032
+ queued: true,
3033
+ sourceUrl: "https://fal.ai/models/topaz/upscale/image/precision",
3034
+ task: "faithful image upscaling"
3035
+ },
3036
+ "topaz-restore": {
3037
+ category: "restoration",
3038
+ defaultOptions: {
3039
+ output_format: "png"
3040
+ },
3041
+ description: "Topaz restoration of damaged or degraded photographs at source resolution.",
3042
+ endpoint: "topaz/restore/image",
3043
+ inputField: "image_url",
3044
+ inputKind: "image",
3045
+ name: "Topaz Restore",
3046
+ outputKeys: ["image"],
3047
+ price: { kind: "megapixel", usd: 0.48 / 24 },
3048
+ pricing: "$0.48 per 24 output megapixels at the default Recover 3; $0.08 with Dust-Scratch V2",
3049
+ queued: true,
3050
+ sourceUrl: "https://fal.ai/models/topaz/restore/image",
3051
+ task: "photo restoration"
3052
+ },
3053
+ "topaz-sharpen": {
3054
+ category: "restoration",
3055
+ defaultOptions: {
3056
+ output_format: "png"
3057
+ },
3058
+ description: "Topaz deblurring and sharpening at source resolution.",
3059
+ endpoint: "topaz/sharpen/image",
3060
+ inputField: "image_url",
3061
+ inputKind: "image",
3062
+ name: "Topaz Sharpen",
3063
+ outputKeys: ["image"],
3064
+ price: { kind: "megapixel", usd: 0.08 / 24 },
3065
+ pricing: "$0.08 per 24 output megapixels at the default Standard model; $0.16 with Super Focus",
3066
+ queued: true,
3067
+ sourceUrl: "https://fal.ai/models/topaz/sharpen/image",
3068
+ task: "image sharpening"
3069
+ },
3070
+ "topaz-transparent": {
3071
+ category: "restoration",
3072
+ description: "Topaz upscaling that preserves the alpha channel end to end.",
3073
+ endpoint: "topaz/upscale/image/transparent",
3074
+ inputField: "image_url",
3075
+ inputKind: "image",
3076
+ name: "Topaz Transparent Upscale",
3077
+ outputKeys: ["image"],
3078
+ price: { kind: "megapixel", usd: 0.08 / 24 },
3079
+ pricing: "$0.08 per 24 output megapixels",
3080
+ queued: true,
3081
+ sourceUrl: "https://fal.ai/models/topaz/upscale/image/transparent",
3082
+ task: "transparent image upscaling"
3083
+ },
3084
+ "topaz-video": {
3085
+ category: "upscale",
3086
+ defaultOptions: {
3087
+ model: "Proteus",
3088
+ upscale_factor: 2
3089
+ },
3090
+ description: "Professional Topaz video enhancement and upscaling.",
3091
+ endpoint: "fal-ai/topaz/upscale/video",
3092
+ inputField: "video_url",
3093
+ inputKind: "video",
3094
+ name: "Topaz Video Upscale",
3095
+ outputKeys: ["video"],
3096
+ price: { kind: "second", usd: 0.01 },
3097
+ pricing: "$0.01/sec up to 720p; $0.02 to 1080p, $0.08 above, doubled at 60fps, halved with Gaia 2",
3098
+ queued: true,
3099
+ sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
3100
+ task: "video enhancement"
3101
+ }
3102
+ };
3103
+
3104
+ // src/tool-registry/editing.ts
3105
+ var EDITING_TOOLS = {
3106
+ "bria-eraser": {
3107
+ category: "erase",
3108
+ description: "Commercial-safe removal of a masked region, inpainted from surrounding context.",
3109
+ endpoint: "fal-ai/bria/eraser",
3110
+ inputField: "image_url",
3111
+ inputKind: "image",
3112
+ name: "Bria Eraser",
3113
+ outputKeys: ["image"],
3114
+ price: { kind: "call", usd: 0.04 },
3115
+ pricing: "$0.04/generation",
3116
+ sourceUrl: "https://fal.ai/models/fal-ai/bria/eraser",
3117
+ task: "masked object erasure"
3118
+ },
3119
+ "bria-expand": {
3120
+ category: "reframe",
3121
+ description: "Expand an image onto a larger canvas, generating the new border area.",
3122
+ endpoint: "fal-ai/bria/expand",
3123
+ inputField: "image_url",
3124
+ inputKind: "image",
3125
+ name: "Bria Image Expansion",
3126
+ outputKeys: ["image"],
3127
+ price: { kind: "call", usd: 0.04 },
3128
+ pricing: "$0.04/generation",
3129
+ sourceUrl: "https://fal.ai/models/fal-ai/bria/expand",
3130
+ task: "image outpainting"
3131
+ },
3132
+ "bria-genfill": {
3133
+ category: "erase",
3134
+ description: "Generative fill of a masked region from a prompt, commercial-safe.",
3135
+ endpoint: "fal-ai/bria/genfill",
3136
+ inputField: "image_url",
3137
+ inputKind: "image",
3138
+ name: "Bria GenFill",
3139
+ outputKeys: ["images"],
3140
+ price: { kind: "call", usd: 0.04 },
3141
+ pricing: "$0.04/generation",
3142
+ sourceUrl: "https://fal.ai/models/fal-ai/bria/genfill",
3143
+ task: "masked generative fill"
3144
+ },
3145
+ "finegrain-eraser": {
3146
+ category: "erase",
3147
+ description: "Remove a prompted object and its shadows and reflections, filling the gap.",
3148
+ endpoint: "fal-ai/finegrain-eraser",
3149
+ inputField: "image_url",
3150
+ inputKind: "image",
3151
+ name: "Finegrain Object Eraser",
3152
+ outputKeys: ["image"],
3153
+ price: { kind: "call", usd: 0.27 },
3154
+ pricing: "$0.27/image at the default standard mode; $0.18 express, $0.36 premium",
3155
+ sourceUrl: "https://fal.ai/models/fal-ai/finegrain-eraser",
3156
+ task: "prompted object erasure"
3157
+ },
3158
+ "flux-outpaint": {
3159
+ category: "reframe",
3160
+ description: "Outpaint an image by a per-edge pixel expansion with FLUX.2 pro.",
3161
+ endpoint: "fal-ai/flux-2-pro/outpaint",
3162
+ inputField: "image_url",
3163
+ inputKind: "image",
3164
+ name: "FLUX.2 Pro Outpaint",
3165
+ outputKeys: ["images"],
3166
+ price: { kind: "metered" },
3167
+ pricing: "$0.03 for the first output megapixel, then $0.015 per extra megapixel of input and output, rounded up",
3168
+ sourceUrl: "https://fal.ai/models/fal-ai/flux-2-pro/outpaint",
3169
+ task: "image outpainting"
3170
+ },
3171
+ "iclight-v2": {
3172
+ category: "relight",
3173
+ description: "Relight a subject from a prompt, harmonising it with a new light direction.",
3174
+ endpoint: "fal-ai/iclight-v2",
3175
+ inputField: "image_url",
3176
+ inputKind: "image",
3177
+ name: "IC-Light v2",
3178
+ outputKeys: ["images"],
3179
+ price: { kind: "megapixel", usd: 0.1 },
3180
+ pricing: "$0.10/megapixel",
3181
+ sourceUrl: "https://fal.ai/models/fal-ai/iclight-v2",
3182
+ task: "prompted relighting"
3183
+ },
3184
+ "ideogram-reframe": {
3185
+ category: "reframe",
3186
+ description: "Reframe an image to a new aspect ratio, generating the fill.",
3187
+ endpoint: "fal-ai/ideogram/v3/reframe",
3188
+ inputField: "image_url",
3189
+ inputKind: "image",
3190
+ name: "Ideogram v3 Reframe",
3191
+ outputKeys: ["images"],
3192
+ price: { kind: "call", usd: 0.06 },
3193
+ pricing: "$0.06/image at the default BALANCED speed; $0.03 turbo, $0.09 quality",
3194
+ sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/reframe",
3195
+ task: "aspect ratio reframing"
3196
+ },
3197
+ "lighting-restoration": {
3198
+ category: "relight",
3199
+ description: "Restore natural, even lighting across one or more input images.",
3200
+ endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
3201
+ inputField: "image_urls",
3202
+ inputKind: "images",
3203
+ name: "Qwen Lighting Restoration",
3204
+ outputKeys: ["images"],
3205
+ price: { kind: "megapixel", usd: 0.035 },
3206
+ pricing: "$0.035/megapixel",
3207
+ sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
3208
+ task: "lighting restoration"
3209
+ },
3210
+ "object-removal": {
3211
+ category: "erase",
3212
+ description: "Remove a prompted object from an image and fill the gap.",
3213
+ endpoint: "fal-ai/object-removal",
3214
+ inputField: "image_url",
3215
+ inputKind: "image",
3216
+ name: "Object Removal",
3217
+ outputKeys: ["images"],
3218
+ price: { kind: "call", usd: 0.024 },
3219
+ pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
3220
+ sourceUrl: "https://fal.ai/models/fal-ai/object-removal",
3221
+ task: "prompted object removal"
3222
+ },
3223
+ "object-removal-bbox": {
3224
+ category: "erase",
3225
+ description: "Remove whatever falls inside supplied bounding boxes.",
3226
+ endpoint: "fal-ai/object-removal/bbox",
3227
+ inputField: "image_url",
3228
+ inputKind: "image",
3229
+ name: "Object Removal by Box",
3230
+ outputKeys: ["images"],
3231
+ price: { kind: "call", usd: 0.024 },
3232
+ pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
3233
+ sourceUrl: "https://fal.ai/models/fal-ai/object-removal/bbox",
3234
+ task: "bounding box object removal"
3235
+ },
3236
+ "object-removal-mask": {
3237
+ category: "erase",
3238
+ description: "Remove the masked region of an image and fill the gap.",
3239
+ endpoint: "fal-ai/object-removal/mask",
3240
+ inputField: "image_url",
3241
+ inputKind: "image",
3242
+ name: "Object Removal by Mask",
3243
+ outputKeys: ["images"],
3244
+ price: { kind: "call", usd: 0.024 },
3245
+ pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
3246
+ sourceUrl: "https://fal.ai/models/fal-ai/object-removal/mask",
3247
+ task: "masked object removal"
3248
+ },
3249
+ "remove-lighting": {
3250
+ category: "relight",
3251
+ description: "Strip baked-in lighting and shadows to leave a flat, neutral surface.",
3252
+ endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
3253
+ inputField: "image_urls",
3254
+ inputKind: "images",
3255
+ name: "Qwen Remove Lighting",
3256
+ outputKeys: ["images"],
3257
+ price: { kind: "megapixel", usd: 0.035 },
3258
+ pricing: "$0.035/megapixel",
3259
+ sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
3260
+ task: "lighting removal"
3261
+ },
3262
+ "smart-resize": {
3263
+ category: "reframe",
3264
+ description: "Resize one image to several target sizes, recomposing rather than cropping.",
3265
+ endpoint: "fal-ai/smart-resize",
3266
+ inputField: "image_url",
3267
+ inputKind: "image",
3268
+ name: "Smart Resize",
3269
+ outputKeys: ["images", "results"],
3270
+ price: { kind: "metered" },
3271
+ pricing: "$0.15 per output image, doubled at 4K, plus a $0.05 vision analysis fee per request",
3272
+ queued: true,
3273
+ sourceUrl: "https://fal.ai/models/fal-ai/smart-resize",
3274
+ task: "multi-size recomposition"
3275
+ },
3276
+ "text-removal": {
3277
+ category: "erase",
3278
+ defaultOptions: {
3279
+ output_format: "png"
3280
+ },
3281
+ description: "Remove all rendered text from an image, rebuilding what sat behind it.",
3282
+ endpoint: "fal-ai/image-editing/text-removal",
3283
+ inputField: "image_url",
3284
+ inputKind: "image",
3285
+ name: "Text Removal",
3286
+ outputKeys: ["images"],
3287
+ price: { kind: "call", usd: 0.04 },
3288
+ pricing: "$0.04/image",
3289
+ sourceUrl: "https://fal.ai/models/fal-ai/image-editing/text-removal",
3290
+ task: "text removal"
3291
+ }
3292
+ };
3293
+
3294
+ // src/tool-registry/structure.ts
3295
+ var STRUCTURE_TOOLS = {
2401
3296
  "depth-anything": {
2402
3297
  category: "preprocess",
2403
3298
  description: "Generate Depth Anything v2 depth maps from input images.",
@@ -2406,10 +3301,37 @@ var FAL_TOOLS = {
2406
3301
  inputKind: "image",
2407
3302
  name: "Depth Anything v2 Preprocessor",
2408
3303
  outputKeys: ["image"],
3304
+ price: { kind: "metered" },
2409
3305
  pricing: "$0/compute-second listed by fal",
2410
3306
  sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
2411
3307
  task: "depth preprocessing"
2412
3308
  },
3309
+ dwpose: {
3310
+ category: "preprocess",
3311
+ description: "Extract body, hand, and face pose skeletons from an image.",
3312
+ endpoint: "fal-ai/dwpose",
3313
+ inputField: "image_url",
3314
+ inputKind: "image",
3315
+ name: "DWPose Estimation",
3316
+ outputKeys: ["image"],
3317
+ price: { kind: "second", usd: 6e-4 },
3318
+ pricing: "$0.0006/compute-second",
3319
+ sourceUrl: "https://fal.ai/models/fal-ai/dwpose",
3320
+ task: "pose preprocessing"
3321
+ },
3322
+ hed: {
3323
+ category: "preprocess",
3324
+ description: "Generate HED soft edge maps for ControlNet-style workflows.",
3325
+ endpoint: "fal-ai/image-preprocessors/hed",
3326
+ inputField: "image_url",
3327
+ inputKind: "image",
3328
+ name: "HED Edge Preprocessor",
3329
+ outputKeys: ["image"],
3330
+ price: { kind: "metered" },
3331
+ pricing: "$0/compute-second listed by fal",
3332
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/hed",
3333
+ task: "edge preprocessing"
3334
+ },
2413
3335
  lineart: {
2414
3336
  category: "preprocess",
2415
3337
  defaultOptions: {
@@ -2421,6 +3343,7 @@ var FAL_TOOLS = {
2421
3343
  inputKind: "image",
2422
3344
  name: "Line Art Preprocessor",
2423
3345
  outputKeys: ["image"],
3346
+ price: { kind: "metered" },
2424
3347
  pricing: "$0/compute-second listed by fal",
2425
3348
  sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
2426
3349
  task: "image preprocessing"
@@ -2437,6 +3360,7 @@ var FAL_TOOLS = {
2437
3360
  inputKind: "image",
2438
3361
  name: "Marigold Depth Estimation",
2439
3362
  outputKeys: ["image"],
3363
+ price: { kind: "metered" },
2440
3364
  pricing: "$0/compute-second listed by fal",
2441
3365
  sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
2442
3366
  task: "depth map"
@@ -2453,6 +3377,7 @@ var FAL_TOOLS = {
2453
3377
  inputKind: "image",
2454
3378
  name: "MiDaS Depth Estimation",
2455
3379
  outputKeys: ["image"],
3380
+ price: { kind: "metered" },
2456
3381
  pricing: "$0/compute-second listed by fal",
2457
3382
  sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
2458
3383
  task: "depth map"
@@ -2465,36 +3390,36 @@ var FAL_TOOLS = {
2465
3390
  inputKind: "image",
2466
3391
  name: "MiDaS Preprocessor",
2467
3392
  outputKeys: ["depth_map", "normal_map"],
3393
+ price: { kind: "metered" },
2468
3394
  pricing: "$0/compute-second listed by fal",
2469
3395
  sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
2470
3396
  task: "depth and normal preprocessing"
2471
3397
  },
2472
- nsfw: {
2473
- category: "moderation",
2474
- description: "Predict whether one or more images contain NSFW concepts.",
2475
- endpoint: "fal-ai/x-ailab/nsfw",
2476
- inputField: "image_urls",
2477
- inputKind: "images",
2478
- name: "NSFW Checker",
2479
- outputKeys: ["has_nsfw_concepts"],
2480
- pricing: "$0.001/image",
2481
- sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
2482
- task: "vision moderation"
3398
+ mlsd: {
3399
+ category: "preprocess",
3400
+ description: "Detect straight line segments for architectural and interior control maps.",
3401
+ endpoint: "fal-ai/image-preprocessors/mlsd",
3402
+ inputField: "image_url",
3403
+ inputKind: "image",
3404
+ name: "M-LSD Line Preprocessor",
3405
+ outputKeys: ["image"],
3406
+ price: { kind: "metered" },
3407
+ pricing: "$0/compute-second listed by fal",
3408
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/mlsd",
3409
+ task: "line segment preprocessing"
2483
3410
  },
2484
- rembg: {
2485
- category: "background",
2486
- defaultOptions: {
2487
- crop_to_bbox: false
2488
- },
2489
- description: "Generic image background removal utility.",
2490
- endpoint: "fal-ai/imageutils/rembg",
3411
+ pidi: {
3412
+ category: "preprocess",
3413
+ description: "Generate PiDiNet soft edge maps from an input image.",
3414
+ endpoint: "fal-ai/image-preprocessors/pidi",
2491
3415
  inputField: "image_url",
2492
3416
  inputKind: "image",
2493
- name: "Remove Background",
3417
+ name: "PiDiNet Edge Preprocessor",
2494
3418
  outputKeys: ["image"],
3419
+ price: { kind: "metered" },
2495
3420
  pricing: "$0/compute-second listed by fal",
2496
- sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
2497
- task: "image background removal"
3421
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/pidi",
3422
+ task: "edge preprocessing"
2498
3423
  },
2499
3424
  "sam-preprocessor": {
2500
3425
  category: "preprocess",
@@ -2504,6 +3429,7 @@ var FAL_TOOLS = {
2504
3429
  inputKind: "image",
2505
3430
  name: "SAM Preprocessor",
2506
3431
  outputKeys: ["image"],
3432
+ price: { kind: "metered" },
2507
3433
  pricing: "$0/compute-second listed by fal",
2508
3434
  sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
2509
3435
  task: "segmentation preprocessing"
@@ -2523,7 +3449,9 @@ var FAL_TOOLS = {
2523
3449
  inputKind: "image",
2524
3450
  name: "SAM 2 Auto Segment",
2525
3451
  outputKeys: ["combined_mask", "individual_masks"],
3452
+ price: { kind: "metered" },
2526
3453
  pricing: "$0/compute-second listed by fal",
3454
+ queued: true,
2527
3455
  sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
2528
3456
  task: "automatic image segmentation"
2529
3457
  },
@@ -2540,7 +3468,8 @@ var FAL_TOOLS = {
2540
3468
  inputKind: "image",
2541
3469
  name: "SAM 3.1 Image",
2542
3470
  outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
2543
- pricing: "$0.005/request",
3471
+ price: { kind: "call", usd: 0.01 },
3472
+ pricing: "$0.01/request",
2544
3473
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
2545
3474
  task: "promptable image segmentation"
2546
3475
  },
@@ -2556,61 +3485,12 @@ var FAL_TOOLS = {
2556
3485
  inputKind: "video",
2557
3486
  name: "SAM 3.1 Video",
2558
3487
  outputKeys: ["video", "boundingbox_frames_zip"],
2559
- pricing: "fal pricing varies by frame count",
3488
+ price: { kind: "metered" },
3489
+ pricing: "$0.01/16 frames of video input",
3490
+ queued: true,
2560
3491
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
2561
3492
  task: "multi-object video segmentation"
2562
3493
  },
2563
- "sam3-3d-align": {
2564
- category: "3d",
2565
- description: "Align SAM 3D objects and bodies into a shared scene.",
2566
- endpoint: "fal-ai/sam-3/3d-align",
2567
- inputField: "image_url",
2568
- inputKind: "image",
2569
- name: "SAM 3D Align",
2570
- outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
2571
- pricing: "fal pricing varies by scene",
2572
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
2573
- task: "3D scene alignment"
2574
- },
2575
- "sam3-3d-body": {
2576
- category: "3d",
2577
- defaultOptions: {
2578
- export_meshes: true,
2579
- include_3d_keypoints: true,
2580
- include_mhr_params: true
2581
- },
2582
- description: "Reconstruct human body meshes and keypoints from a single image.",
2583
- endpoint: "fal-ai/sam-3/3d-body",
2584
- inputField: "image_url",
2585
- inputKind: "image",
2586
- name: "SAM 3D Body",
2587
- outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
2588
- pricing: "$0.015/inference",
2589
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
2590
- task: "single-image 3D body reconstruction"
2591
- },
2592
- "sam3-3d-objects": {
2593
- category: "3d",
2594
- defaultOptions: {
2595
- prompt: "car"
2596
- },
2597
- description: "Reconstruct one or more 3D objects from an image and prompts.",
2598
- endpoint: "fal-ai/sam-3/3d-objects",
2599
- inputField: "image_url",
2600
- inputKind: "image",
2601
- name: "SAM 3D Objects",
2602
- outputKeys: [
2603
- "gaussian_splat",
2604
- "model_glb",
2605
- "metadata",
2606
- "individual_splats",
2607
- "individual_glbs",
2608
- "artifacts_zip"
2609
- ],
2610
- pricing: "$0.02/generation",
2611
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
2612
- task: "single-image 3D object reconstruction"
2613
- },
2614
3494
  "sam3-image": {
2615
3495
  category: "segmentation",
2616
3496
  defaultOptions: {
@@ -2624,6 +3504,7 @@ var FAL_TOOLS = {
2624
3504
  inputKind: "image",
2625
3505
  name: "SAM 3 Image",
2626
3506
  outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
3507
+ price: { kind: "call", usd: 5e-3 },
2627
3508
  pricing: "$0.005/request",
2628
3509
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
2629
3510
  task: "promptable image segmentation"
@@ -2639,7 +3520,8 @@ var FAL_TOOLS = {
2639
3520
  inputField: "image_url",
2640
3521
  inputKind: "image",
2641
3522
  name: "SAM 3 Image RLE",
2642
- outputKeys: ["rle_masks", "metadata", "scores", "boxes"],
3523
+ outputKeys: ["rle", "scores", "boxes", "metadata"],
3524
+ price: { kind: "call", usd: 5e-3 },
2643
3525
  pricing: "$0.005/request",
2644
3526
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
2645
3527
  task: "promptable image segmentation to RLE"
@@ -2658,7 +3540,9 @@ var FAL_TOOLS = {
2658
3540
  inputKind: "video",
2659
3541
  name: "SAM 3 Video",
2660
3542
  outputKeys: ["video", "boundingbox_frames_zip"],
2661
- pricing: "$0.005/16 frames",
3543
+ price: { kind: "metered" },
3544
+ pricing: "$0.005/16 frames of video input",
3545
+ queued: true,
2662
3546
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
2663
3547
  task: "promptable video segmentation"
2664
3548
  },
@@ -2674,45 +3558,65 @@ var FAL_TOOLS = {
2674
3558
  inputField: "video_url",
2675
3559
  inputKind: "video",
2676
3560
  name: "SAM 3 Video RLE",
2677
- outputKeys: ["rle_masks", "metadata"],
2678
- pricing: "$0.005/16 frames",
3561
+ // fal's video-rle endpoint no longer returns RLE data: its Output schema is
3562
+ // now identical to sam3-video's, so this entry differs only by endpoint.
3563
+ outputKeys: ["video", "boundingbox_frames_zip"],
3564
+ price: { kind: "metered" },
3565
+ pricing: "$0.005/16 frames of video",
3566
+ queued: true,
2679
3567
  sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
2680
3568
  task: "promptable video segmentation to RLE"
2681
3569
  },
2682
- "topaz-image": {
2683
- category: "upscale",
2684
- defaultOptions: {
2685
- model: "Standard V2",
2686
- output_format: "jpeg",
2687
- upscale_factor: 2
2688
- },
2689
- description: "Professional Topaz image enhancement and upscaling.",
2690
- endpoint: "fal-ai/topaz/upscale/image",
3570
+ scribble: {
3571
+ category: "preprocess",
3572
+ description: "Turn an image into a scribble-style control map.",
3573
+ endpoint: "fal-ai/image-preprocessors/scribble",
2691
3574
  inputField: "image_url",
2692
3575
  inputKind: "image",
2693
- name: "Topaz Image Upscale",
3576
+ name: "Scribble Preprocessor",
2694
3577
  outputKeys: ["image"],
2695
- pricing: "$0.08+ by output megapixels",
2696
- sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
2697
- task: "image enhancement"
3578
+ price: { kind: "metered" },
3579
+ pricing: "$0/compute-second listed by fal",
3580
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/scribble",
3581
+ task: "scribble preprocessing"
2698
3582
  },
2699
- "topaz-video": {
2700
- category: "upscale",
2701
- defaultOptions: {
2702
- model: "Proteus",
2703
- upscale_factor: 2
2704
- },
2705
- description: "Professional Topaz video enhancement and upscaling.",
2706
- endpoint: "fal-ai/topaz/upscale/video",
2707
- inputField: "video_url",
2708
- inputKind: "video",
2709
- name: "Topaz Video Upscale",
2710
- outputKeys: ["video"],
2711
- pricing: "$0.01-$0.08/sec by output resolution",
2712
- sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
2713
- task: "video enhancement"
3583
+ teed: {
3584
+ category: "preprocess",
3585
+ description: "Generate TEED edge maps from an input image.",
3586
+ endpoint: "fal-ai/image-preprocessors/teed",
3587
+ inputField: "image_url",
3588
+ inputKind: "image",
3589
+ name: "TEED Edge Preprocessor",
3590
+ outputKeys: ["image"],
3591
+ price: { kind: "metered" },
3592
+ pricing: "$0/compute-second listed by fal",
3593
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/teed",
3594
+ task: "edge preprocessing"
3595
+ },
3596
+ "zoe-depth": {
3597
+ category: "preprocess",
3598
+ description: "Generate ZoeDepth metric depth maps from an input image.",
3599
+ endpoint: "fal-ai/image-preprocessors/zoe",
3600
+ inputField: "image_url",
3601
+ inputKind: "image",
3602
+ name: "ZoeDepth Preprocessor",
3603
+ outputKeys: ["image"],
3604
+ price: { kind: "metered" },
3605
+ pricing: "$0/compute-second listed by fal",
3606
+ sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/zoe",
3607
+ task: "depth preprocessing"
2714
3608
  }
2715
3609
  };
3610
+
3611
+ // src/tools.ts
3612
+ var FAL_EXPLORE_CHECKED_AT = "2026-08-23";
3613
+ var FAL_TOOLS = {
3614
+ ...ANALYSIS_TOOLS,
3615
+ ...ASSET_TOOLS,
3616
+ ...BACKGROUND_TOOLS,
3617
+ ...EDITING_TOOLS,
3618
+ ...STRUCTURE_TOOLS
3619
+ };
2716
3620
  var FAL_TOOLS_CHECKED_AT = FAL_EXPLORE_CHECKED_AT;
2717
3621
  var FAL_TOOL_IDS = Object.keys(
2718
3622
  FAL_TOOLS
@@ -2739,9 +3643,110 @@ function buildFalToolRequest(options) {
2739
3643
  return { body, endpoint: tool.endpoint, tool };
2740
3644
  }
2741
3645
 
2742
- // src/server.ts
3646
+ // src/server-tools.ts
2743
3647
  var FAL_BASE_URL = "https://fal.run";
2744
3648
  var FAL_QUEUE_URL = "https://queue.fal.run";
3649
+ var POLL_INTERVAL_MS = 3e3;
3650
+ var MAX_POLL_ATTEMPTS = 160;
3651
+ function buildToolRequest(options) {
3652
+ try {
3653
+ return (0, import_neverthrow.ok)(buildFalToolRequest(options));
3654
+ } catch (error) {
3655
+ return (0, import_neverthrow.err)(
3656
+ new MotifError(error instanceof Error ? error.message : String(error), 0)
3657
+ );
3658
+ }
3659
+ }
3660
+ async function runTool(exec, options) {
3661
+ const request = buildToolRequest(options);
3662
+ if (request.isErr()) {
3663
+ return (0, import_neverthrow.err)(request.error);
3664
+ }
3665
+ const response = await exec.request(
3666
+ `${FAL_BASE_URL}/${request.value.endpoint}`,
3667
+ {
3668
+ body: JSON.stringify(request.value.body),
3669
+ method: "POST"
3670
+ }
3671
+ );
3672
+ if (response.isErr()) {
3673
+ return (0, import_neverthrow.err)(response.error);
3674
+ }
3675
+ const data = await response.value.json();
3676
+ return (0, import_neverthrow.ok)(isRecord(data) ? data : {});
3677
+ }
3678
+ async function submitTool(exec, options) {
3679
+ const request = buildToolRequest(options);
3680
+ if (request.isErr()) {
3681
+ return (0, import_neverthrow.err)(request.error);
3682
+ }
3683
+ const response = await exec.request(
3684
+ `${FAL_QUEUE_URL}/${request.value.endpoint}`,
3685
+ {
3686
+ body: JSON.stringify(request.value.body),
3687
+ method: "POST"
3688
+ }
3689
+ );
3690
+ if (response.isErr()) {
3691
+ return (0, import_neverthrow.err)(response.error);
3692
+ }
3693
+ const data = await response.value.json();
3694
+ const submission = parseQueueSubmission(data);
3695
+ return (0, import_neverthrow.ok)({
3696
+ endpoint: endpointFromQueueUrl(
3697
+ submission.responseUrl,
3698
+ request.value.endpoint
3699
+ ),
3700
+ requestId: submission.requestId
3701
+ });
3702
+ }
3703
+ async function checkToolStatus(exec, job) {
3704
+ return await exec.getJobStatus(job.endpoint, job.requestId);
3705
+ }
3706
+ async function getToolResult(exec, job) {
3707
+ const url = `${FAL_QUEUE_URL}/${job.endpoint}/requests/${job.requestId}`;
3708
+ const response = await exec.request(url);
3709
+ if (response.isErr()) {
3710
+ return (0, import_neverthrow.err)(response.error);
3711
+ }
3712
+ const data = await response.value.json();
3713
+ return (0, import_neverthrow.ok)(isRecord(data) ? data : {});
3714
+ }
3715
+ async function runToolQueued(exec, options, onProgress) {
3716
+ const job = await submitTool(exec, options);
3717
+ if (job.isErr()) {
3718
+ return (0, import_neverthrow.err)(job.error);
3719
+ }
3720
+ for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
3721
+ const status = await checkToolStatus(exec, job.value);
3722
+ if (status.isErr()) {
3723
+ return (0, import_neverthrow.err)(status.error);
3724
+ }
3725
+ onProgress?.(status.value.status, status.value.queuePosition);
3726
+ if (status.value.status === "completed") {
3727
+ return await getToolResult(exec, job.value);
3728
+ }
3729
+ if (status.value.status === "failed") {
3730
+ const message = status.value.error ?? "Queued tool run failed";
3731
+ return (0, import_neverthrow.err)(new MotifError(message, 0, void 0, job.value.requestId));
3732
+ }
3733
+ await new Promise((resolve) => {
3734
+ setTimeout(resolve, POLL_INTERVAL_MS);
3735
+ });
3736
+ }
3737
+ return (0, import_neverthrow.err)(
3738
+ new MotifError(
3739
+ "Queued tool run timed out",
3740
+ 0,
3741
+ void 0,
3742
+ job.value.requestId
3743
+ )
3744
+ );
3745
+ }
3746
+
3747
+ // src/server.ts
3748
+ var FAL_BASE_URL2 = "https://fal.run";
3749
+ var FAL_QUEUE_URL2 = "https://queue.fal.run";
2745
3750
  var FAL_API_URL = "https://api.fal.ai";
2746
3751
  var FAL_REST_URL = "https://rest.alpha.fal.ai";
2747
3752
  var FalClient = class {
@@ -2773,16 +3778,16 @@ var FalClient = class {
2773
3778
  try {
2774
3779
  built = buildGenerateBody(options);
2775
3780
  } catch (error) {
2776
- return (0, import_neverthrow.err)(toMotifError(error));
3781
+ return (0, import_neverthrow2.err)(toMotifError(error));
2777
3782
  }
2778
3783
  const { endpoint, body } = built;
2779
- const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
3784
+ const response = await this.request(`${FAL_BASE_URL2}/${endpoint}`, {
2780
3785
  body: JSON.stringify(body),
2781
3786
  headers: this.ephemeralHeaders(options),
2782
3787
  method: "POST"
2783
3788
  });
2784
3789
  if (response.isErr()) {
2785
- return (0, import_neverthrow.err)(response.error);
3790
+ return (0, import_neverthrow2.err)(response.error);
2786
3791
  }
2787
3792
  const data = await response.value.json();
2788
3793
  return this.normalizeResponse(data);
@@ -2790,7 +3795,7 @@ var FalClient = class {
2790
3795
  async generateQueued(options) {
2791
3796
  const job = await this.submitGeneration(options);
2792
3797
  if (job.isErr()) {
2793
- return (0, import_neverthrow.err)(job.error);
3798
+ return (0, import_neverthrow2.err)(job.error);
2794
3799
  }
2795
3800
  const pollIntervalMs = 3e3;
2796
3801
  const maxAttempts = 160;
@@ -2800,20 +3805,20 @@ var FalClient = class {
2800
3805
  job.value.requestId
2801
3806
  );
2802
3807
  if (status.isErr()) {
2803
- return (0, import_neverthrow.err)(status.error);
3808
+ return (0, import_neverthrow2.err)(status.error);
2804
3809
  }
2805
3810
  if (status.value.status === "completed") {
2806
3811
  return await this.getJobResult(job.value.endpoint, job.value.requestId);
2807
3812
  }
2808
3813
  if (status.value.status === "failed") {
2809
3814
  const message = status.value.error ?? "Queued generation failed";
2810
- return (0, import_neverthrow.err)(new MotifError(message, 0, void 0, job.value.requestId));
3815
+ return (0, import_neverthrow2.err)(new MotifError(message, 0, void 0, job.value.requestId));
2811
3816
  }
2812
3817
  await new Promise((resolve) => {
2813
3818
  setTimeout(resolve, pollIntervalMs);
2814
3819
  });
2815
3820
  }
2816
- return (0, import_neverthrow.err)(new MotifError("Queued generation timed out", 0));
3821
+ return (0, import_neverthrow2.err)(new MotifError("Queued generation timed out", 0));
2817
3822
  }
2818
3823
  /** ─── Queue-Based Generation ──────────────────────────────── */
2819
3824
  /** Submit a generation to the fal.ai queue (returns immediately). */
@@ -2822,20 +3827,20 @@ var FalClient = class {
2822
3827
  try {
2823
3828
  built = buildGenerateBody(options);
2824
3829
  } catch (error) {
2825
- return (0, import_neverthrow.err)(toMotifError(error));
3830
+ return (0, import_neverthrow2.err)(toMotifError(error));
2826
3831
  }
2827
3832
  const { endpoint, body } = built;
2828
- const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
3833
+ const response = await this.request(`${FAL_QUEUE_URL2}/${endpoint}`, {
2829
3834
  body: JSON.stringify(body),
2830
3835
  headers: this.ephemeralHeaders(options),
2831
3836
  method: "POST"
2832
3837
  });
2833
3838
  if (response.isErr()) {
2834
- return (0, import_neverthrow.err)(response.error);
3839
+ return (0, import_neverthrow2.err)(response.error);
2835
3840
  }
2836
3841
  const data = await response.value.json();
2837
3842
  const submission = parseQueueSubmission(data);
2838
- return (0, import_neverthrow.ok)({
3843
+ return (0, import_neverthrow2.ok)({
2839
3844
  endpoint: endpointFromQueueUrl(submission.responseUrl, endpoint),
2840
3845
  estimatedCost: estimateCost(
2841
3846
  options.model,
@@ -2847,10 +3852,10 @@ var FalClient = class {
2847
3852
  }
2848
3853
  /** Check the status of a queued generation. */
2849
3854
  async getJobStatus(endpoint, requestId) {
2850
- const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}/status?logs=1`;
3855
+ const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}/status?logs=1`;
2851
3856
  const response = await this.request(url);
2852
3857
  if (response.isErr()) {
2853
- return (0, import_neverthrow.err)(response.error);
3858
+ return (0, import_neverthrow2.err)(response.error);
2854
3859
  }
2855
3860
  const data = await response.value.json();
2856
3861
  const record = isRecord(data) ? data : {};
@@ -2865,9 +3870,9 @@ var FalClient = class {
2865
3870
  } else if (rawStatus === "FAILED" || rawStatus === "ERROR" || rawStatus === "CANCELED") {
2866
3871
  status = "failed";
2867
3872
  } else {
2868
- return (0, import_neverthrow.err)(new MotifError(`Unknown job status: ${rawStatus}`, 0));
3873
+ return (0, import_neverthrow2.err)(new MotifError(`Unknown job status: ${rawStatus}`, 0));
2869
3874
  }
2870
- return (0, import_neverthrow.ok)({
3875
+ return (0, import_neverthrow2.ok)({
2871
3876
  error: asString(record.error) ?? asString(record.detail),
2872
3877
  logs: parseLogs(record.logs),
2873
3878
  queuePosition: asNumber(record.queue_position),
@@ -2876,10 +3881,10 @@ var FalClient = class {
2876
3881
  }
2877
3882
  /** Fetch the completed result from the queue. */
2878
3883
  async getJobResult(endpoint, requestId) {
2879
- const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}`;
3884
+ const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}`;
2880
3885
  const response = await this.request(url);
2881
3886
  if (response.isErr()) {
2882
- return (0, import_neverthrow.err)(response.error);
3887
+ return (0, import_neverthrow2.err)(response.error);
2883
3888
  }
2884
3889
  const data = await response.value.json();
2885
3890
  return this.normalizeResponse(data, requestId);
@@ -2900,7 +3905,7 @@ var FalClient = class {
2900
3905
  } = options;
2901
3906
  const config = MODELS[model];
2902
3907
  if (!config || config.type !== "utility") {
2903
- return (0, import_neverthrow.err)(new MotifError(`Invalid upscale model: ${model}`, 0));
3908
+ return (0, import_neverthrow2.err)(new MotifError(`Invalid upscale model: ${model}`, 0));
2904
3909
  }
2905
3910
  const body = { image_url: imageUrl };
2906
3911
  if (model === "crystal") {
@@ -2933,12 +3938,12 @@ var FalClient = class {
2933
3938
  body.guidance_scale = guidanceScale;
2934
3939
  }
2935
3940
  }
2936
- const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
3941
+ const response = await this.request(`${FAL_BASE_URL2}/${config.endpoint}`, {
2937
3942
  body: JSON.stringify(body),
2938
3943
  method: "POST"
2939
3944
  });
2940
3945
  if (response.isErr()) {
2941
- return (0, import_neverthrow.err)(response.error);
3946
+ return (0, import_neverthrow2.err)(response.error);
2942
3947
  }
2943
3948
  const data = await response.value.json();
2944
3949
  return this.normalizeResponse(data);
@@ -2956,7 +3961,7 @@ var FalClient = class {
2956
3961
  } = options;
2957
3962
  const config = MODELS[model];
2958
3963
  if (!config) {
2959
- return (0, import_neverthrow.err)(
3964
+ return (0, import_neverthrow2.err)(
2960
3965
  new MotifError(`Invalid background removal model: ${model}`, 0)
2961
3966
  );
2962
3967
  }
@@ -2978,12 +3983,12 @@ var FalClient = class {
2978
3983
  rbBody.output_mask = outputMask;
2979
3984
  }
2980
3985
  }
2981
- const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
3986
+ const response = await this.request(`${FAL_BASE_URL2}/${config.endpoint}`, {
2982
3987
  body: JSON.stringify(rbBody),
2983
3988
  method: "POST"
2984
3989
  });
2985
3990
  if (response.isErr()) {
2986
- return (0, import_neverthrow.err)(response.error);
3991
+ return (0, import_neverthrow2.err)(response.error);
2987
3992
  }
2988
3993
  const data = await response.value.json();
2989
3994
  return this.normalizeResponse(data);
@@ -3006,7 +4011,7 @@ var FalClient = class {
3006
4011
  } = options;
3007
4012
  const config = MODELS.kling;
3008
4013
  if (!config) {
3009
- return (0, import_neverthrow.err)(new MotifError("Kling video model not found", 0));
4014
+ return (0, import_neverthrow2.err)(new MotifError("Kling video model not found", 0));
3010
4015
  }
3011
4016
  const body = {
3012
4017
  duration: String(duration),
@@ -3023,16 +4028,16 @@ var FalClient = class {
3023
4028
  if (cfgScale !== void 0) {
3024
4029
  body.cfg_scale = cfgScale;
3025
4030
  }
3026
- const response = await this.request(`${FAL_QUEUE_URL}/${config.endpoint}`, {
4031
+ const response = await this.request(`${FAL_QUEUE_URL2}/${config.endpoint}`, {
3027
4032
  body: JSON.stringify(body),
3028
4033
  method: "POST"
3029
4034
  });
3030
4035
  if (response.isErr()) {
3031
- return (0, import_neverthrow.err)(response.error);
4036
+ return (0, import_neverthrow2.err)(response.error);
3032
4037
  }
3033
4038
  const data = await response.value.json();
3034
4039
  const submission = parseQueueSubmission(data);
3035
- return (0, import_neverthrow.ok)({
4040
+ return (0, import_neverthrow2.ok)({
3036
4041
  endpoint: endpointFromQueueUrl(submission.responseUrl, config.endpoint),
3037
4042
  estimatedCost: estimateVideoCost(duration, generateAudio),
3038
4043
  requestId: submission.requestId
@@ -3040,17 +4045,17 @@ var FalClient = class {
3040
4045
  }
3041
4046
  /** Fetch the completed video result from the queue. */
3042
4047
  async getVideoResult(endpoint, requestId) {
3043
- const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}`;
4048
+ const url = `${FAL_QUEUE_URL2}/${endpoint}/requests/${requestId}`;
3044
4049
  const response = await this.request(url);
3045
4050
  if (response.isErr()) {
3046
- return (0, import_neverthrow.err)(response.error);
4051
+ return (0, import_neverthrow2.err)(response.error);
3047
4052
  }
3048
4053
  const data = await response.value.json();
3049
4054
  const video = isRecord(data) && isRecord(data.video) ? data.video : void 0;
3050
4055
  if (video === void 0) {
3051
- return (0, import_neverthrow.err)(new MotifError("No video in response", 0));
4056
+ return (0, import_neverthrow2.err)(new MotifError("No video in response", 0));
3052
4057
  }
3053
- return (0, import_neverthrow.ok)({
4058
+ return (0, import_neverthrow2.ok)({
3054
4059
  contentType: asString(video.content_type) ?? "",
3055
4060
  fileName: asString(video.file_name) ?? "",
3056
4061
  fileSize: asNumber(video.file_size) ?? 0,
@@ -3074,13 +4079,13 @@ var FalClient = class {
3074
4079
  }
3075
4080
  );
3076
4081
  if (initiateResponse.isErr()) {
3077
- return (0, import_neverthrow.err)(initiateResponse.error);
4082
+ return (0, import_neverthrow2.err)(initiateResponse.error);
3078
4083
  }
3079
4084
  const initiateData = await initiateResponse.value.json();
3080
4085
  const fileUrl = isRecord(initiateData) ? asString(initiateData.file_url) ?? "" : "";
3081
4086
  const uploadUrl = isRecord(initiateData) ? asString(initiateData.upload_url) : void 0;
3082
4087
  if (uploadUrl === void 0 || uploadUrl === "") {
3083
- return (0, import_neverthrow.err)(
4088
+ return (0, import_neverthrow2.err)(
3084
4089
  new MotifError("Upload initiate response missing upload_url", 0)
3085
4090
  );
3086
4091
  }
@@ -3095,7 +4100,7 @@ var FalClient = class {
3095
4100
  method: "PUT"
3096
4101
  });
3097
4102
  } catch (error) {
3098
- return (0, import_neverthrow.err)(
4103
+ return (0, import_neverthrow2.err)(
3099
4104
  new MotifError(
3100
4105
  `Upload PUT failed: ${error instanceof Error ? error.message : String(error)}`,
3101
4106
  0
@@ -3103,38 +4108,45 @@ var FalClient = class {
3103
4108
  );
3104
4109
  }
3105
4110
  if (!putResponse.ok) {
3106
- return (0, import_neverthrow.err)(
4111
+ return (0, import_neverthrow2.err)(
3107
4112
  new MotifError(
3108
4113
  `Upload PUT failed: ${putResponse.status}`,
3109
4114
  putResponse.status
3110
4115
  )
3111
4116
  );
3112
4117
  }
3113
- return (0, import_neverthrow.ok)(fileUrl);
4118
+ return (0, import_neverthrow2.ok)(fileUrl);
3114
4119
  }
3115
4120
  /** ─── Utilities ───────────────────────────────────────────── */
3116
4121
  /** Run a registered fal utility/tool endpoint. */
3117
4122
  async runTool(options) {
3118
- let request;
3119
- try {
3120
- request = buildFalToolRequest(options);
3121
- } catch (error) {
3122
- return (0, import_neverthrow.err)(
3123
- new MotifError(
3124
- error instanceof Error ? error.message : String(error),
3125
- 0
3126
- )
3127
- );
3128
- }
3129
- const response = await this.request(`${FAL_BASE_URL}/${request.endpoint}`, {
3130
- body: JSON.stringify(request.body),
3131
- method: "POST"
3132
- });
3133
- if (response.isErr()) {
3134
- return (0, import_neverthrow.err)(response.error);
3135
- }
3136
- const data = await response.value.json();
3137
- return (0, import_neverthrow.ok)(isRecord(data) ? data : {});
4123
+ return await runTool(this.toolExecutor, options);
4124
+ }
4125
+ /** ─── Queue-Based Tools ───────────────────────────────────── */
4126
+ /**
4127
+ * Submit a tool run to the fal queue. Returns as soon as fal accepts it.
4128
+ *
4129
+ * The queued counterpart to `runTool`, for endpoints that outrun the
4130
+ * synchronous request timeout. Callers choose the path — neither one falls
4131
+ * back to the other.
4132
+ */
4133
+ async submitTool(options) {
4134
+ return await submitTool(this.toolExecutor, options);
4135
+ }
4136
+ /** Poll one queued tool run. */
4137
+ async checkToolStatus(job) {
4138
+ return await checkToolStatus(this.toolExecutor, job);
4139
+ }
4140
+ /** Fetch the finished payload for a queued tool run. */
4141
+ async getToolResult(job) {
4142
+ return await getToolResult(this.toolExecutor, job);
4143
+ }
4144
+ /**
4145
+ * Submit, poll to completion, and return the result. The convenience path
4146
+ * for tools whose registry entry sets `queued`.
4147
+ */
4148
+ async runToolQueued(options, onProgress) {
4149
+ return await runToolQueued(this.toolExecutor, options, onProgress);
3138
4150
  }
3139
4151
  /**
3140
4152
  * Delete fal's stored IO payloads for a completed request.
@@ -3149,9 +4161,9 @@ var FalClient = class {
3149
4161
  { method: "DELETE" }
3150
4162
  );
3151
4163
  if (response.isErr()) {
3152
- return (0, import_neverthrow.err)(response.error);
4164
+ return (0, import_neverthrow2.err)(response.error);
3153
4165
  }
3154
- return (0, import_neverthrow.ok)();
4166
+ return (0, import_neverthrow2.ok)();
3155
4167
  }
3156
4168
  /** Estimate cost for a generation (no API call). */
3157
4169
  estimateCost(model, resolution, numImages) {
@@ -3177,6 +4189,16 @@ var FalClient = class {
3177
4189
  get tools() {
3178
4190
  return FAL_TOOLS;
3179
4191
  }
4192
+ /**
4193
+ * The seam `server-tools.ts` runs against — the client's authenticated fetch
4194
+ * and its queue status parser, exposed without widening the public surface.
4195
+ */
4196
+ get toolExecutor() {
4197
+ return {
4198
+ getJobStatus: async (endpoint, requestId) => await this.getJobStatus(endpoint, requestId),
4199
+ request: async (url, init) => await this.request(url, init)
4200
+ };
4201
+ }
3180
4202
  /** ─── Private ─────────────────────────────────────────────── */
3181
4203
  /** Authenticated fetch to fal.ai APIs with retry logic. */
3182
4204
  async request(url, options = {}) {
@@ -3208,15 +4230,15 @@ var FalClient = class {
3208
4230
  const text = await response.text();
3209
4231
  const message = `Request failed: ${response.status} ${text}`;
3210
4232
  const requestId = response.headers.get("x-fal-request-id") ?? requestIdFromBody(text);
3211
- return (0, import_neverthrow.err)(
4233
+ return (0, import_neverthrow2.err)(
3212
4234
  new MotifError(message, response.status, void 0, requestId)
3213
4235
  );
3214
4236
  }
3215
- return (0, import_neverthrow.ok)(response);
4237
+ return (0, import_neverthrow2.ok)(response);
3216
4238
  } catch (error) {
3217
4239
  clearTimeout(timeoutId);
3218
4240
  if (error instanceof MotifError) {
3219
- return (0, import_neverthrow.err)(error);
4241
+ return (0, import_neverthrow2.err)(error);
3220
4242
  }
3221
4243
  lastError = new MotifError(
3222
4244
  error instanceof Error ? error.message : String(error),
@@ -3230,7 +4252,7 @@ var FalClient = class {
3230
4252
  }
3231
4253
  }
3232
4254
  }
3233
- return (0, import_neverthrow.err)(lastError ?? new MotifError("Request failed after retries", 0));
4255
+ return (0, import_neverthrow2.err)(lastError ?? new MotifError("Request failed after retries", 0));
3234
4256
  }
3235
4257
  ephemeralHeaders(options) {
3236
4258
  return options.ephemeral === true ? { "X-Fal-Store-IO": "0" } : {};
@@ -3241,24 +4263,24 @@ var FalClient = class {
3241
4263
  */
3242
4264
  normalizeResponse(data, fallbackRequestId) {
3243
4265
  if (!isRecord(data)) {
3244
- return (0, import_neverthrow.err)(
4266
+ return (0, import_neverthrow2.err)(
3245
4267
  new MotifError("Unexpected fal response shape", 0, "FAL_ERROR")
3246
4268
  );
3247
4269
  }
3248
4270
  const obj = data;
3249
4271
  const requestId = asString(obj.request_id) ?? asString(obj.requestId) ?? fallbackRequestId;
3250
4272
  if ("detail" in obj) {
3251
- return (0, import_neverthrow.err)(new MotifError(asString(obj.detail) ?? "", 0, "FAL_ERROR"));
4273
+ return (0, import_neverthrow2.err)(new MotifError(asString(obj.detail) ?? "", 0, "FAL_ERROR"));
3252
4274
  }
3253
4275
  if ("image" in obj && !("images" in obj)) {
3254
- return (0, import_neverthrow.ok)({
4276
+ return (0, import_neverthrow2.ok)({
3255
4277
  images: [toMotifImage(obj.image)],
3256
4278
  prompt: asString(obj.prompt),
3257
4279
  requestId,
3258
4280
  seed: asNumber(obj.seed)
3259
4281
  });
3260
4282
  }
3261
- return (0, import_neverthrow.ok)({
4283
+ return (0, import_neverthrow2.ok)({
3262
4284
  images: parseImages(obj.images),
3263
4285
  prompt: asString(obj.prompt),
3264
4286
  requestId,
@@ -3266,27 +4288,632 @@ var FalClient = class {
3266
4288
  });
3267
4289
  }
3268
4290
  };
3269
- var MotifError = class extends Error {
3270
- status;
3271
- code;
3272
- /** fal's request-correlation id (from the `x-fal-request-id` header or the
3273
- * error body). Ties a failure back to fal's dashboard/support. */
3274
- requestId;
3275
- constructor(message, status, code, requestId) {
3276
- super(message);
3277
- this.name = "MotifError";
3278
- this.status = status;
3279
- this.code = code;
3280
- this.requestId = requestId;
4291
+
4292
+ // src/tool-cost.ts
4293
+ var UNKNOWN = { basis: "unknown", usd: null };
4294
+ function totalMegapixels(outputs) {
4295
+ let pixels = 0;
4296
+ let measured = 0;
4297
+ for (const output of outputs) {
4298
+ if (output.width !== void 0 && output.height !== void 0) {
4299
+ pixels += output.width * output.height;
4300
+ measured += 1;
4301
+ }
4302
+ }
4303
+ return measured === 0 ? null : pixels / 1e6;
4304
+ }
4305
+ function projectedToolCost(price) {
4306
+ return price.kind === "call" ? { basis: "projected", usd: price.usd } : UNKNOWN;
4307
+ }
4308
+ function measuredToolCost(price, outputs) {
4309
+ if (price.kind === "call") {
4310
+ return { basis: "measured", usd: price.usd };
4311
+ }
4312
+ if (price.kind === "megapixel") {
4313
+ const megapixels = totalMegapixels(outputs);
4314
+ return megapixels === null ? UNKNOWN : { basis: "measured", usd: price.usd * megapixels };
4315
+ }
4316
+ return UNKNOWN;
4317
+ }
4318
+ function formatCost(usd) {
4319
+ return usd === null ? "metered" : `$${usd.toFixed(3)}`;
4320
+ }
4321
+ function sumCosts(costs) {
4322
+ let known = 0;
4323
+ let unknown = 0;
4324
+ for (const cost of costs) {
4325
+ if (cost === null) {
4326
+ unknown += 1;
4327
+ } else {
4328
+ known += cost;
4329
+ }
3281
4330
  }
4331
+ return { known, unknown };
4332
+ }
4333
+
4334
+ // src/tool-parameters.generated.ts
4335
+ var FAL_TOOL_PARAMETERS = {
4336
+ "ben-v2": [
4337
+ { key: "seed", type: "integer" }
4338
+ ],
4339
+ "birefnet": [
4340
+ { key: "mask_only", type: "boolean", fallback: false },
4341
+ { key: "model", type: "enum(General Use (Light)|General Use (Light 2K)|General Use (Heavy)|Matting|Portrait|General Use (Dynamic))", fallback: "General Use (Light)" },
4342
+ { key: "operating_resolution", type: "enum(1024x1024|2048x2048|2304x2304)", fallback: "1024x1024" },
4343
+ { key: "output_format", type: "enum(webp|png|gif)", fallback: "png" },
4344
+ { key: "output_mask", type: "boolean", fallback: false },
4345
+ { key: "refine_foreground", type: "boolean", fallback: true }
4346
+ ],
4347
+ "bria-eraser": [
4348
+ { key: "mask_type", type: "enum(manual|automatic)", fallback: "manual" },
4349
+ { key: "preserve_alpha", type: "boolean", fallback: false }
4350
+ ],
4351
+ "bria-expand": [
4352
+ { key: "aspect_ratio", type: "enum(1:1|2:3|3:2|3:4|4:3|4:5|5:4|9:16|16:9)" },
4353
+ { key: "canvas_size", type: "list[integer]", required: true },
4354
+ { key: "negative_prompt", type: "string", fallback: "" },
4355
+ { key: "original_image_location", type: "list[integer]" },
4356
+ { key: "original_image_size", type: "list[integer]" },
4357
+ { key: "prompt", type: "string", fallback: "" },
4358
+ { key: "seed", type: "integer" }
4359
+ ],
4360
+ "bria-genfill": [
4361
+ { key: "negative_prompt", type: "string", fallback: "" },
4362
+ { key: "num_images", type: "integer", fallback: 1 },
4363
+ { key: "prompt", type: "string", required: true },
4364
+ { key: "seed", type: "integer" }
4365
+ ],
4366
+ "bria-rmbg": [],
4367
+ "bria-video-rmbg": [
4368
+ { key: "background_color", type: "enum(Transparent|Black|White|Gray|Red|Green|Blue|Yellow|Cyan|Magenta|Orange)", fallback: "Black" },
4369
+ { key: "output_container_and_codec", type: "enum(mp4_h265|mp4_h264|webm_vp9|mov_h265|mov_proresks|mkv_h265|mkv_h264|mkv_vp9|gif)", fallback: "webm_vp9" },
4370
+ { key: "preserve_audio", type: "boolean", fallback: true }
4371
+ ],
4372
+ "ddcolor": [
4373
+ { key: "seed", type: "integer" }
4374
+ ],
4375
+ "depth-anything": [],
4376
+ "dwpose": [
4377
+ { key: "draw_mode", type: "enum(full-pose|body-pose|face-pose|hand-pose|face-hand-mask|face-mask|hand-mask)", fallback: "body-pose" }
4378
+ ],
4379
+ "finegrain-eraser": [
4380
+ { key: "mode", type: "enum(express|standard|premium)", fallback: "standard" },
4381
+ { key: "prompt", type: "string", required: true },
4382
+ { key: "seed", type: "integer" }
4383
+ ],
4384
+ "flux-outpaint": [
4385
+ { key: "auto_crop", type: "boolean", fallback: false },
4386
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4387
+ { key: "expand_bottom", type: "integer", fallback: 0 },
4388
+ { key: "expand_left", type: "integer", fallback: 0 },
4389
+ { key: "expand_right", type: "integer", fallback: 0 },
4390
+ { key: "expand_top", type: "integer", fallback: 0 },
4391
+ { key: "mode", type: "enum(high|fast)", fallback: "high" },
4392
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" }
4393
+ ],
4394
+ "got-ocr": [
4395
+ { key: "do_format", type: "boolean", fallback: false },
4396
+ { key: "multi_page", type: "boolean", fallback: false }
4397
+ ],
4398
+ "hed": [
4399
+ { key: "safe", type: "boolean", fallback: false },
4400
+ { key: "scribble", type: "boolean", fallback: false }
4401
+ ],
4402
+ "hunyuan3d-v3": [
4403
+ { key: "back_image_url", type: "string" },
4404
+ { key: "enable_pbr", type: "boolean", fallback: false },
4405
+ { key: "face_count", type: "integer", fallback: 5e5 },
4406
+ { key: "generate_type", type: "enum(Normal|LowPoly|Geometry)", fallback: "Normal" },
4407
+ { key: "left_image_url", type: "string" },
4408
+ { key: "polygon_type", type: "enum(triangle|quadrilateral)", fallback: "triangle" },
4409
+ { key: "right_image_url", type: "string" }
4410
+ ],
4411
+ "iclight-v2": [
4412
+ { key: "background_threshold", type: "number", fallback: 0.67 },
4413
+ { key: "cfg", type: "number", fallback: 1 },
4414
+ { key: "enable_hr_fix", type: "boolean", fallback: false },
4415
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4416
+ { key: "guidance_scale", type: "number", fallback: 5 },
4417
+ { key: "highres_denoise", type: "number", fallback: 0.95 },
4418
+ { key: "hr_downscale", type: "number", fallback: 0.5 },
4419
+ { key: "image_size", type: "object|enum(square_hd|square|portrait_4_3|portrait_16_9|landscape_4_3|landscape_16_9)" },
4420
+ { key: "initial_latent", type: "enum(None|Left|Right|Top|Bottom)", fallback: "None" },
4421
+ { key: "lowres_denoise", type: "number", fallback: 0.98 },
4422
+ { key: "mask_image_url", type: "string" },
4423
+ { key: "negative_prompt", type: "string", fallback: "" },
4424
+ { key: "num_images", type: "integer", fallback: 1 },
4425
+ { key: "num_inference_steps", type: "integer", fallback: 28 },
4426
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" },
4427
+ { key: "prompt", type: "string", required: true },
4428
+ { key: "seed", type: "integer" }
4429
+ ],
4430
+ "ideogram-layerize-text": [
4431
+ { key: "font_file_body_url", type: "string" },
4432
+ { key: "font_file_h1_url", type: "string" },
4433
+ { key: "font_file_h2_url", type: "string" },
4434
+ { key: "font_file_small_url", type: "string" },
4435
+ { key: "font_name_body", type: "string" },
4436
+ { key: "font_name_h1", type: "string" },
4437
+ { key: "font_name_h2", type: "string" },
4438
+ { key: "font_name_small", type: "string" },
4439
+ { key: "prompt", type: "string" },
4440
+ { key: "seed", type: "integer" }
4441
+ ],
4442
+ "ideogram-reframe": [
4443
+ { key: "color_palette", type: "object" },
4444
+ { key: "image_size", type: "object|enum(square_hd|square|portrait_4_3|portrait_16_9|landscape_4_3|landscape_16_9)", required: true },
4445
+ { key: "num_images", type: "integer", fallback: 1 },
4446
+ { key: "rendering_speed", type: "enum(TURBO|BALANCED|QUALITY)", fallback: "BALANCED" },
4447
+ { key: "seed", type: "integer" },
4448
+ { key: "style", type: "enum(AUTO|GENERAL|REALISTIC|DESIGN)" },
4449
+ { key: "style_codes", type: "list[string]" },
4450
+ { key: "style_preset", type: "enum(80S_ILLUSTRATION|90S_NOSTALGIA|ABSTRACT_ORGANIC|ANALOG_NOSTALGIA|ART_BRUT|ART_DECO|ART_POSTER|AURA|AVANT_GARDE|BAUHAUS|BLUEPRINT|BLURRY_MOTION|BRIGHT_ART|C4D_CARTOON|CHILDRENS_BOOK|COLLAGE|COLORING_BOOK_I|COLORING_BOOK_II|CUBISM|DARK_AURA|DOODLE|DOUBLE_EXPOSURE|DRAMATIC_CINEMA|EDITORIAL|EMOTIONAL_MINIMAL|ETHEREAL_PARTY|EXPIRED_FILM|FLAT_ART|FLAT_VECTOR|FOREST_REVERIE|GEO_MINIMALIST|GLASS_PRISM|GOLDEN_HOUR|GRAFFITI_I|GRAFFITI_II|HALFTONE_PRINT|HIGH_CONTRAST|HIPPIE_ERA|ICONIC|JAPANDI_FUSION|JAZZY|LONG_EXPOSURE|MAGAZINE_EDITORIAL|MINIMAL_ILLUSTRATION|MIXED_MEDIA|MONOCHROME|NIGHTLIFE|OIL_PAINTING|OLD_CARTOONS|PAINT_GESTURE|POP_ART|RETRO_ETCHING|RIVIERA_POP|SPOTLIGHT_80S|STYLIZED_RED|SURREAL_COLLAGE|TRAVEL_POSTER|VINTAGE_GEO|VINTAGE_POSTER|WATERCOLOR|WEIRD|WOODBLOCK_PRINT)" }
4451
+ ],
4452
+ "ideogram-tiling": [
4453
+ { key: "acceleration", type: "enum(none|low|regular|high)", fallback: "none" },
4454
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4455
+ { key: "expansion_model", type: "enum(None|Medium|Large)", fallback: "Medium" },
4456
+ { key: "image_size", type: "object|enum(square_hd|square|portrait_4_3|portrait_16_9|landscape_4_3|landscape_16_9)", fallback: "square_hd" },
4457
+ { key: "num_images", type: "integer", fallback: 1 },
4458
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" },
4459
+ { key: "prompt", type: "string", required: true },
4460
+ { key: "rendering_speed", type: "enum(TURBO|BALANCED|QUALITY)", fallback: "BALANCED" },
4461
+ { key: "seed", type: "integer" },
4462
+ { key: "strength", type: "number", fallback: 0.8 },
4463
+ { key: "tiling_mode", type: "enum(both|horizontal|vertical)", fallback: "both" }
4464
+ ],
4465
+ "image2svg": [
4466
+ { key: "color_precision", type: "integer", fallback: 6 },
4467
+ { key: "colormode", type: "enum(color|binary)", fallback: "color" },
4468
+ { key: "corner_threshold", type: "integer", fallback: 60 },
4469
+ { key: "filter_speckle", type: "integer", fallback: 4 },
4470
+ { key: "hierarchical", type: "enum(stacked|cutout)", fallback: "stacked" },
4471
+ { key: "layer_difference", type: "integer", fallback: 16 },
4472
+ { key: "length_threshold", type: "number", fallback: 4 },
4473
+ { key: "max_iterations", type: "integer", fallback: 10 },
4474
+ { key: "mode", type: "enum(spline|polygon)", fallback: "spline" },
4475
+ { key: "path_precision", type: "integer", fallback: 3 },
4476
+ { key: "splice_threshold", type: "integer", fallback: 45 }
4477
+ ],
4478
+ "lighting-restoration": [
4479
+ { key: "acceleration", type: "enum(none|regular)", fallback: "regular" },
4480
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4481
+ { key: "guidance_scale", type: "number", fallback: 1 },
4482
+ { key: "image_size", type: "object|enum(square_hd|square|portrait_4_3|portrait_16_9|landscape_4_3|landscape_16_9)" },
4483
+ { key: "negative_prompt", type: "string", fallback: " " },
4484
+ { key: "num_images", type: "integer", fallback: 1 },
4485
+ { key: "num_inference_steps", type: "integer", fallback: 6 },
4486
+ { key: "output_format", type: "enum(png|jpeg|webp)", fallback: "png" },
4487
+ { key: "seed", type: "integer" }
4488
+ ],
4489
+ "lineart": [
4490
+ { key: "coarse", type: "boolean", fallback: false }
4491
+ ],
4492
+ "marigold-depth": [
4493
+ { key: "ensemble_size", type: "integer", fallback: 10 },
4494
+ { key: "num_inference_steps", type: "integer", fallback: 10 },
4495
+ { key: "processing_res", type: "integer", fallback: 0 }
4496
+ ],
4497
+ "midas-depth": [
4498
+ { key: "a", type: "number", fallback: 6.283185307179586 },
4499
+ { key: "bg_th", type: "number", fallback: 0.1 },
4500
+ { key: "depth_and_normal", type: "boolean", fallback: false }
4501
+ ],
4502
+ "midas-preprocessor": [
4503
+ { key: "a", type: "number", fallback: 6.283185307179586 },
4504
+ { key: "background_threshold", type: "number", fallback: 0.1 }
4505
+ ],
4506
+ "mlsd": [
4507
+ { key: "distance_threshold", type: "number", fallback: 0.1 },
4508
+ { key: "score_threshold", type: "number", fallback: 0.1 }
4509
+ ],
4510
+ "moondream-caption": [
4511
+ { key: "length", type: "enum(short|normal|long)", fallback: "normal" },
4512
+ { key: "temperature", type: "number" },
4513
+ { key: "top_p", type: "number" }
4514
+ ],
4515
+ "moondream-detect": [
4516
+ { key: "preview", type: "boolean", fallback: false },
4517
+ { key: "prompt", type: "string", required: true }
4518
+ ],
4519
+ "moondream-point": [
4520
+ { key: "preview", type: "boolean", fallback: false },
4521
+ { key: "prompt", type: "string", required: true }
4522
+ ],
4523
+ "moondream-query": [
4524
+ { key: "prompt", type: "string", required: true },
4525
+ { key: "reasoning", type: "boolean", fallback: true },
4526
+ { key: "temperature", type: "number" },
4527
+ { key: "top_p", type: "number" }
4528
+ ],
4529
+ "nsfw": [],
4530
+ "object-removal": [
4531
+ { key: "mask_expansion", type: "integer", fallback: 15 },
4532
+ { key: "model", type: "enum(low_quality|medium_quality|high_quality|best_quality)", fallback: "best_quality" },
4533
+ { key: "prompt", type: "string", required: true }
4534
+ ],
4535
+ "object-removal-bbox": [
4536
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4537
+ { key: "mask_expansion", type: "integer", fallback: 15 },
4538
+ { key: "model", type: "enum(low_quality|medium_quality|high_quality|best_quality)", fallback: "best_quality" }
4539
+ ],
4540
+ "object-removal-mask": [
4541
+ { key: "mask_expansion", type: "integer", fallback: 15 },
4542
+ { key: "model", type: "enum(low_quality|medium_quality|high_quality|best_quality)", fallback: "best_quality" }
4543
+ ],
4544
+ "patina": [
4545
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4546
+ { key: "maps", type: "list[enum(basecolor|normal|roughness|metalness|height)]", fallback: ["basecolor", "normal", "roughness", "metalness", "height"] },
4547
+ { key: "output_format", type: "enum(jpeg|png|webp)", fallback: "png" },
4548
+ { key: "seed", type: "integer" }
4549
+ ],
4550
+ "patina-extract": [
4551
+ { key: "enable_prompt_expansion", type: "boolean", fallback: true },
4552
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4553
+ { key: "image_size", type: "object|enum(square_hd|square|portrait_4_3|portrait_16_9|landscape_4_3|landscape_16_9)", fallback: "square_hd" },
4554
+ { key: "maps", type: "list[enum(basecolor|normal|roughness|metalness|height)]", fallback: ["basecolor", "normal", "roughness", "metalness", "height"] },
4555
+ { key: "num_images", type: "integer", fallback: 1 },
4556
+ { key: "num_inference_steps", type: "integer", fallback: 8 },
4557
+ { key: "output_format", type: "enum(jpeg|png|webp)", fallback: "png" },
4558
+ { key: "prompt", type: "string", required: true },
4559
+ { key: "seed", type: "integer" },
4560
+ { key: "strength", type: "number", fallback: 0.6 },
4561
+ { key: "tile_size", type: "integer", fallback: 128 },
4562
+ { key: "tile_stride", type: "integer", fallback: 64 },
4563
+ { key: "tiling_mode", type: "enum(both|horizontal|vertical)", fallback: "both" },
4564
+ { key: "upscale_factor", type: "enum(0|2|4)", fallback: 0 }
4565
+ ],
4566
+ "pidi": [
4567
+ { key: "apply_filter", type: "boolean", fallback: false },
4568
+ { key: "safe", type: "boolean", fallback: false },
4569
+ { key: "scribble", type: "boolean", fallback: false }
4570
+ ],
4571
+ "qwen-layered": [
4572
+ { key: "acceleration", type: "enum(none|regular|high)", fallback: "regular" },
4573
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4574
+ { key: "guidance_scale", type: "number", fallback: 5 },
4575
+ { key: "negative_prompt", type: "string", fallback: "" },
4576
+ { key: "num_inference_steps", type: "integer", fallback: 28 },
4577
+ { key: "num_layers", type: "integer", fallback: 4 },
4578
+ { key: "output_format", type: "enum(png|webp)", fallback: "png" },
4579
+ { key: "prompt", type: "string" },
4580
+ { key: "seed", type: "integer" }
4581
+ ],
4582
+ "recraft-vectorize": [],
4583
+ "rembg": [
4584
+ { key: "crop_to_bbox", type: "boolean", fallback: false }
4585
+ ],
4586
+ "remove-lighting": [
4587
+ { key: "acceleration", type: "enum(none|regular)", fallback: "regular" },
4588
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4589
+ { key: "guidance_scale", type: "number", fallback: 1 },
4590
+ { key: "image_size", type: "object|enum(square_hd|square|portrait_4_3|portrait_16_9|landscape_4_3|landscape_16_9)" },
4591
+ { key: "negative_prompt", type: "string", fallback: " " },
4592
+ { key: "num_images", type: "integer", fallback: 1 },
4593
+ { key: "num_inference_steps", type: "integer", fallback: 6 },
4594
+ { key: "output_format", type: "enum(png|jpeg|webp)", fallback: "png" },
4595
+ { key: "seed", type: "integer" }
4596
+ ],
4597
+ "sam-preprocessor": [],
4598
+ "sam2-auto": [
4599
+ { key: "min_mask_region_area", type: "integer", fallback: 100 },
4600
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "png" },
4601
+ { key: "points_per_side", type: "integer", fallback: 32 },
4602
+ { key: "pred_iou_thresh", type: "number", fallback: 0.88 },
4603
+ { key: "stability_score_thresh", type: "number", fallback: 0.95 }
4604
+ ],
4605
+ "sam3-1-image": [
4606
+ { key: "apply_mask", type: "boolean", fallback: true },
4607
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4608
+ { key: "include_boxes", type: "boolean", fallback: false },
4609
+ { key: "include_scores", type: "boolean", fallback: false },
4610
+ { key: "max_masks", type: "integer", fallback: 3 },
4611
+ { key: "output_format", type: "enum(jpeg|png|webp)", fallback: "png" },
4612
+ { key: "point_prompts", type: "list[object]", fallback: [] },
4613
+ { key: "prompt", type: "string", fallback: "wheel" },
4614
+ { key: "return_multiple_masks", type: "boolean", fallback: false }
4615
+ ],
4616
+ "sam3-1-video": [
4617
+ { key: "apply_mask", type: "boolean", fallback: true },
4618
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4619
+ { key: "detection_threshold", type: "number", fallback: 0.5 },
4620
+ { key: "max_num_objects", type: "integer", fallback: 16 },
4621
+ { key: "point_prompts", type: "list[object]", fallback: [] },
4622
+ { key: "prompt", type: "string", fallback: "" },
4623
+ { key: "video_output_type", type: "enum(X264 (.mp4)|VP9 (.webm))", fallback: "X264 (.mp4)" }
4624
+ ],
4625
+ "sam3-3d-align": [
4626
+ { key: "body_mask_url", type: "string" },
4627
+ { key: "body_mesh_url", type: "string", required: true },
4628
+ { key: "focal_length", type: "number" },
4629
+ { key: "object_mesh_url", type: "string" }
4630
+ ],
4631
+ "sam3-3d-body": [
4632
+ { key: "export_meshes", type: "boolean", fallback: true },
4633
+ { key: "include_3d_keypoints", type: "boolean", fallback: true },
4634
+ { key: "include_mhr_params", type: "boolean", fallback: true }
4635
+ ],
4636
+ "sam3-3d-objects": [
4637
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4638
+ { key: "detection_threshold", type: "number" },
4639
+ { key: "export_textured_glb", type: "boolean", fallback: false },
4640
+ { key: "mask_urls", type: "list[string]" },
4641
+ { key: "point_prompts", type: "list[object]", fallback: [] },
4642
+ { key: "pointmap_url", type: "string" },
4643
+ { key: "prompt", type: "string", fallback: "car" },
4644
+ { key: "seed", type: "integer" }
4645
+ ],
4646
+ "sam3-image": [
4647
+ { key: "apply_mask", type: "boolean", fallback: true },
4648
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4649
+ { key: "include_boxes", type: "boolean", fallback: false },
4650
+ { key: "include_scores", type: "boolean", fallback: false },
4651
+ { key: "max_masks", type: "integer", fallback: 3 },
4652
+ { key: "output_format", type: "enum(jpeg|png|webp)", fallback: "png" },
4653
+ { key: "point_prompts", type: "list[object]", fallback: [] },
4654
+ { key: "prompt", type: "string", fallback: "wheel" },
4655
+ { key: "return_multiple_masks", type: "boolean", fallback: false },
4656
+ { key: "text_prompt", type: "string" }
4657
+ ],
4658
+ "sam3-image-rle": [
4659
+ { key: "apply_mask", type: "boolean", fallback: true },
4660
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4661
+ { key: "include_boxes", type: "boolean", fallback: false },
4662
+ { key: "include_scores", type: "boolean", fallback: false },
4663
+ { key: "max_masks", type: "integer", fallback: 3 },
4664
+ { key: "output_format", type: "enum(jpeg|png|webp)", fallback: "png" },
4665
+ { key: "point_prompts", type: "list[object]", fallback: [] },
4666
+ { key: "prompt", type: "string", fallback: "wheel" },
4667
+ { key: "return_multiple_masks", type: "boolean", fallback: false },
4668
+ { key: "text_prompt", type: "string" }
4669
+ ],
4670
+ "sam3-video": [
4671
+ { key: "apply_mask", type: "boolean", fallback: true },
4672
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4673
+ { key: "detection_threshold", type: "number", fallback: 0.5 },
4674
+ { key: "point_prompts", type: "list[object]", fallback: [] },
4675
+ { key: "prompt", type: "string", fallback: "" },
4676
+ { key: "text_prompt", type: "string" },
4677
+ { key: "video_output_type", type: "enum(X264 (.mp4)|VP9 (.webm))", fallback: "X264 (.mp4)" }
4678
+ ],
4679
+ "sam3-video-rle": [
4680
+ { key: "apply_mask", type: "boolean", fallback: false },
4681
+ { key: "boundingbox_zip", type: "boolean", fallback: false },
4682
+ { key: "box_prompts", type: "list[object]", fallback: [] },
4683
+ { key: "detection_threshold", type: "number", fallback: 0.5 },
4684
+ { key: "frame_index", type: "integer", fallback: 0 },
4685
+ { key: "point_prompts", type: "list[object]", fallback: [] },
4686
+ { key: "prompt", type: "string", fallback: "" }
4687
+ ],
4688
+ "scribble": [
4689
+ { key: "model", type: "enum(HED|PiDi)", fallback: "HED" },
4690
+ { key: "safe", type: "boolean", fallback: false }
4691
+ ],
4692
+ "seedream-layerize": [
4693
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4694
+ { key: "enhance_prompt_mode", type: "enum(standard|fast)", fallback: "standard" },
4695
+ { key: "image_size", type: "enum(auto|auto_1K|auto_1.5K|auto_2K)", fallback: "auto" },
4696
+ { key: "prompt", type: "string", fallback: "" }
4697
+ ],
4698
+ "seedvr-seamless": [
4699
+ { key: "enable_safety_checker", type: "boolean", fallback: true },
4700
+ { key: "noise_scale", type: "number", fallback: 0.1 },
4701
+ { key: "output_format", type: "enum(png|jpeg|webp)", fallback: "png" },
4702
+ { key: "seed", type: "integer" },
4703
+ { key: "target_resolution", type: "enum(720p|1080p|1440p|2160p)", fallback: "1080p" },
4704
+ { key: "upscale_factor", type: "number", fallback: 2 },
4705
+ { key: "upscale_mode", type: "enum(target|factor)", fallback: "factor" }
4706
+ ],
4707
+ "seedvr-upscale": [
4708
+ { key: "noise_scale", type: "number", fallback: 0.1 },
4709
+ { key: "output_format", type: "enum(png|jpg|webp)", fallback: "jpg" },
4710
+ { key: "seed", type: "integer" },
4711
+ { key: "target_resolution", type: "enum(720p|1080p|1440p|2160p)", fallback: "1080p" },
4712
+ { key: "upscale_factor", type: "number", fallback: 2 },
4713
+ { key: "upscale_mode", type: "enum(target|factor)", fallback: "factor" }
4714
+ ],
4715
+ "smart-resize": [
4716
+ { key: "num_images_per_size", type: "integer", fallback: 1 },
4717
+ { key: "output_format", type: "enum(jpeg|png|webp)", fallback: "png" },
4718
+ { key: "prompt", type: "string", fallback: "" },
4719
+ { key: "resolution", type: "enum(1K|2K|4K)", fallback: "1K" },
4720
+ { key: "safety_tolerance", type: "enum(1|2|3|4|5|6)", fallback: "4" },
4721
+ { key: "seed", type: "integer" },
4722
+ { key: "target_sizes", type: "list[string]", required: true }
4723
+ ],
4724
+ "teed": [],
4725
+ "text-removal": [
4726
+ { key: "aspect_ratio", type: "enum(21:9|16:9|4:3|3:2|1:1|2:3|3:4|9:16|9:21)" },
4727
+ { key: "guidance_scale", type: "number", fallback: 3.5 },
4728
+ { key: "num_inference_steps", type: "integer", fallback: 30 },
4729
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" },
4730
+ { key: "safety_tolerance", type: "enum(1|2|3|4|5|6)", fallback: "2" },
4731
+ { key: "seed", type: "integer" }
4732
+ ],
4733
+ "topaz-adjust": [
4734
+ { key: "model", type: "enum(Adjust V2|White Balance|Colorize)", fallback: "Adjust V2" },
4735
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" }
4736
+ ],
4737
+ "topaz-creative": [
4738
+ { key: "autoprompt", type: "boolean" },
4739
+ { key: "color_preservation", type: "boolean" },
4740
+ { key: "creativity", type: "integer" },
4741
+ { key: "crop_to_fill", type: "boolean", fallback: false },
4742
+ { key: "model", type: "enum(Bloom 2|Bloom|Bloom Realism)", fallback: "Bloom 2" },
4743
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" },
4744
+ { key: "upscale_factor", type: "number", fallback: 2 }
4745
+ ],
4746
+ "topaz-denoise": [
4747
+ { key: "model", type: "enum(Normal|Strong|Extreme|Denoise Max)", fallback: "Normal" },
4748
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" }
4749
+ ],
4750
+ "topaz-generative": [
4751
+ { key: "autoprompt", type: "boolean" },
4752
+ { key: "creativity", type: "integer" },
4753
+ { key: "crop_to_fill", type: "boolean", fallback: false },
4754
+ { key: "denoise", type: "number" },
4755
+ { key: "detail", type: "number" },
4756
+ { key: "enhancement_strength", type: "enum(low|medium|high)" },
4757
+ { key: "face_enhancement", type: "boolean", fallback: true },
4758
+ { key: "face_enhancement_creativity", type: "number", fallback: 0 },
4759
+ { key: "face_enhancement_strength", type: "number", fallback: 0.8 },
4760
+ { key: "model", type: "enum(Wonder 3.5|Wonder 3|Wonder 2|Wonder|Recover 3|Standard MAX|Redefine|Recovery V2|Recovery)", fallback: "Wonder 3" },
4761
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" },
4762
+ { key: "prompt", type: "string" },
4763
+ { key: "sharpen", type: "number" },
4764
+ { key: "subject_detection", type: "enum(All|Foreground|Background)", fallback: "All" },
4765
+ { key: "texture", type: "integer" },
4766
+ { key: "upscale_factor", type: "number", fallback: 2 }
4767
+ ],
4768
+ "topaz-image": [
4769
+ { key: "autoprompt", type: "boolean" },
4770
+ { key: "creativity", type: "integer" },
4771
+ { key: "crop_to_fill", type: "boolean", fallback: false },
4772
+ { key: "denoise", type: "number" },
4773
+ { key: "detail", type: "number" },
4774
+ { key: "enhancement_strength", type: "enum(low|medium|high)" },
4775
+ { key: "face_enhancement", type: "boolean", fallback: true },
4776
+ { key: "face_enhancement_creativity", type: "number", fallback: 0 },
4777
+ { key: "face_enhancement_strength", type: "number", fallback: 0.8 },
4778
+ { key: "fix_compression", type: "number" },
4779
+ { key: "model", type: "enum(Standard V2|High Fidelity V2|Low Resolution V2|CGI|Text Refine|Wonder 3|Wonder|Standard MAX|Redefine|Recovery V2|Recovery)", fallback: "Standard V2" },
4780
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" },
4781
+ { key: "prompt", type: "string" },
4782
+ { key: "sharpen", type: "number" },
4783
+ { key: "strength", type: "number" },
4784
+ { key: "subject_detection", type: "enum(All|Foreground|Background)", fallback: "All" },
4785
+ { key: "texture", type: "integer" },
4786
+ { key: "upscale_factor", type: "number", fallback: 2 }
4787
+ ],
4788
+ "topaz-precision": [
4789
+ { key: "crop_to_fill", type: "boolean", fallback: false },
4790
+ { key: "denoise", type: "number" },
4791
+ { key: "face_enhancement", type: "boolean", fallback: true },
4792
+ { key: "face_enhancement_creativity", type: "number", fallback: 0 },
4793
+ { key: "face_enhancement_strength", type: "number", fallback: 0.8 },
4794
+ { key: "fix_compression", type: "number" },
4795
+ { key: "model", type: "enum(Standard V2|High Fidelity V3|High Fidelity V2|Low Resolution V2|CGI|Text Refine)", fallback: "Standard V2" },
4796
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" },
4797
+ { key: "sharpen", type: "number" },
4798
+ { key: "strength", type: "number" },
4799
+ { key: "subject_detection", type: "enum(All|Foreground|Background)", fallback: "All" },
4800
+ { key: "upscale_factor", type: "number", fallback: 2 }
4801
+ ],
4802
+ "topaz-restore": [
4803
+ { key: "model", type: "enum(Recover 3|Dust-Scratch V2)", fallback: "Recover 3" },
4804
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" }
4805
+ ],
4806
+ "topaz-sharpen": [
4807
+ { key: "model", type: "enum(Standard|Strong|Lens Blur V2|Motion Blur|Natural|Refocus|Wildlife|Portrait|Auto Sharpen|Super Focus V3|Super Focus V2)", fallback: "Standard" },
4808
+ { key: "output_format", type: "enum(jpeg|png)", fallback: "jpeg" }
4809
+ ],
4810
+ "topaz-transparent": [
4811
+ { key: "output_format", type: "string", fallback: "png" },
4812
+ { key: "upscale_factor", type: "number", fallback: 2 }
4813
+ ],
4814
+ "topaz-video": [
4815
+ { key: "compression", type: "number" },
4816
+ { key: "grain", type: "number" },
4817
+ { key: "H264_output", type: "boolean", fallback: false },
4818
+ { key: "halo", type: "number" },
4819
+ { key: "model", type: "enum(Proteus|Artemis HQ|Artemis MQ|Artemis LQ|Gaia HQ|Gaia CG|Gaia 2|Nyx|Nyx Fast|Nyx XL|Nyx HF|Starlight Precise 2.5|Starlight HQ|Starlight Mini|Starlight Sharp|Starlight Fast 2|Starlight Precise 1|Starlight Precise 2|Starlight Fast 1)", fallback: "Proteus" },
4820
+ { key: "noise", type: "number" },
4821
+ { key: "recover_detail", type: "number" },
4822
+ { key: "target_fps", type: "integer" },
4823
+ { key: "upscale_factor", type: "number", fallback: 2 }
4824
+ ],
4825
+ "trellis-2": [
4826
+ { key: "decimation_target", type: "integer", fallback: 5e5 },
4827
+ { key: "remesh", type: "boolean", fallback: true },
4828
+ { key: "remesh_band", type: "number", fallback: 1 },
4829
+ { key: "remesh_project", type: "number", fallback: 0 },
4830
+ { key: "resolution", type: "enum(512|1024|1536)", fallback: 1024 },
4831
+ { key: "seed", type: "integer" },
4832
+ { key: "shape_slat_guidance_interval_end", type: "number", fallback: 1 },
4833
+ { key: "shape_slat_guidance_interval_start", type: "number", fallback: 0.6 },
4834
+ { key: "shape_slat_guidance_rescale", type: "number", fallback: 0.5 },
4835
+ { key: "shape_slat_guidance_strength", type: "number", fallback: 7.5 },
4836
+ { key: "shape_slat_rescale_t", type: "number", fallback: 3 },
4837
+ { key: "shape_slat_sampling_steps", type: "integer", fallback: 12 },
4838
+ { key: "ss_guidance_interval_end", type: "number", fallback: 1 },
4839
+ { key: "ss_guidance_interval_start", type: "number", fallback: 0.6 },
4840
+ { key: "ss_guidance_rescale", type: "number", fallback: 0.7 },
4841
+ { key: "ss_guidance_strength", type: "number", fallback: 7.5 },
4842
+ { key: "ss_rescale_t", type: "number", fallback: 5 },
4843
+ { key: "ss_sampling_steps", type: "integer", fallback: 12 },
4844
+ { key: "tex_slat_guidance_interval_end", type: "number", fallback: 0.9 },
4845
+ { key: "tex_slat_guidance_interval_start", type: "number", fallback: 0.6 },
4846
+ { key: "tex_slat_guidance_rescale", type: "number", fallback: 0 },
4847
+ { key: "tex_slat_guidance_strength", type: "number", fallback: 1 },
4848
+ { key: "tex_slat_rescale_t", type: "number", fallback: 3 },
4849
+ { key: "tex_slat_sampling_steps", type: "integer", fallback: 12 },
4850
+ { key: "texture_size", type: "enum(1024|2048|4096)", fallback: 2048 },
4851
+ { key: "uv_unwrap_angle_threshold_deg", type: "number", fallback: 90 },
4852
+ { key: "uv_unwrap_global_iterations", type: "integer", fallback: 1 },
4853
+ { key: "uv_unwrap_refine_iterations", type: "integer", fallback: 0 },
4854
+ { key: "uv_unwrap_smooth_strength", type: "number", fallback: 1 }
4855
+ ],
4856
+ "zoe-depth": []
3282
4857
  };
3283
- function toMotifError(error) {
3284
- if (error instanceof MotifError) {
3285
- return error;
4858
+ function falToolParameters(tool) {
4859
+ return FAL_TOOL_PARAMETERS[tool] ?? [];
4860
+ }
4861
+
4862
+ // src/model-output.generated.ts
4863
+ var MODEL_OUTPUT = {
4864
+ "banana": { container: "png", lossless: true, bitDepth: 8 },
4865
+ "banana2": { container: "png", lossless: true, bitDepth: 8 },
4866
+ "flux": { container: "jpeg", subsampling: "4:4:4", lossless: false, bitDepth: 8 },
4867
+ "flux-fast": { container: "jpeg", subsampling: "4:4:4", lossless: false, bitDepth: 8 },
4868
+ "flux2-dev": { container: "png", lossless: true, bitDepth: 8 },
4869
+ "flux2-flex": { container: "jpeg", subsampling: "4:4:4", lossless: false, bitDepth: 8 },
4870
+ "flux2-max": { container: "jpeg", subsampling: "4:4:4", lossless: false, bitDepth: 8 },
4871
+ "flux2-pro": { container: "jpeg", subsampling: "4:4:4", lossless: false, bitDepth: 8 },
4872
+ "flux2-turbo": { container: "png", lossless: true, bitDepth: 8 },
4873
+ "gemini": { container: "png", lossless: true, bitDepth: 8 },
4874
+ "gemini3": { container: "png", lossless: true, bitDepth: 8 },
4875
+ "gpt": { container: "png", lossless: true, bitDepth: 8 },
4876
+ "gpt2": { container: "png", lossless: true, bitDepth: 8 },
4877
+ "grok-image": { container: "png", lossless: true, bitDepth: 8 },
4878
+ "ideogram": { container: "png", lossless: true, bitDepth: 8 },
4879
+ "ideogram4": { container: "jpeg", subsampling: "4:4:4", lossless: false, bitDepth: 8 },
4880
+ "qwen": { container: "png", lossless: true, bitDepth: 8 },
4881
+ "qwen3": { container: "png", lossless: true, bitDepth: 8 },
4882
+ "recraft": { container: "webp (lossless)", lossless: true, hasAlpha: true, bitDepth: 8 },
4883
+ "recraft4": { container: "webp (lossless)", lossless: true, hasAlpha: true, bitDepth: 8 },
4884
+ "seedream4": { container: "jpeg", subsampling: "4:4:4", lossless: false, bitDepth: 8 },
4885
+ "seedream45": { container: "jpeg", subsampling: "4:2:0", lossless: false, bitDepth: 8 },
4886
+ "seedream5": { container: "jpeg", subsampling: "4:2:0", lossless: false, bitDepth: 8 },
4887
+ "seedream5-lite": { container: "jpeg", subsampling: "4:2:0", lossless: false, bitDepth: 8 }
4888
+ };
4889
+
4890
+ // src/model-output.ts
4891
+ function modelOutput(model) {
4892
+ return MODEL_OUTPUT[model];
4893
+ }
4894
+ function losslessAvailability(model) {
4895
+ const shape = MODEL_OUTPUT[model];
4896
+ if (shape === void 0) {
4897
+ return "unknown";
3286
4898
  }
3287
- const message = error instanceof Error ? error.message : String(error);
3288
- const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
3289
- return new MotifError(message, 0, code);
4899
+ if (shape.lossless) {
4900
+ return "default";
4901
+ }
4902
+ return MODELS[model]?.supportsOutputFormat === true ? "on-request" : "unavailable";
4903
+ }
4904
+ function describeModelOutput(model) {
4905
+ const shape = MODEL_OUTPUT[model];
4906
+ if (shape === void 0) {
4907
+ return "not measured";
4908
+ }
4909
+ const base = shape.subsampling === void 0 || shape.subsampling === "" ? shape.container : `${shape.container} ${shape.subsampling}`;
4910
+ const suffix = {
4911
+ default: "lossless",
4912
+ "on-request": "lossless on request",
4913
+ unavailable: "no lossless route",
4914
+ unknown: "lossless route not measured"
4915
+ };
4916
+ return `${base}, ${suffix[losslessAvailability(model)]}`;
3290
4917
  }
3291
4918
  // Annotate the CommonJS export names for ESM import in node:
3292
4919
  0 && (module.exports = {
@@ -3298,6 +4925,7 @@ function toMotifError(error) {
3298
4925
  FAL_TOOLS,
3299
4926
  FAL_TOOLS_CHECKED_AT,
3300
4927
  FAL_TOOL_IDS,
4928
+ FAL_TOOL_PARAMETERS,
3301
4929
  FORMAT_PRESETS,
3302
4930
  FalClient,
3303
4931
  GENERATION_MODELS,
@@ -3305,6 +4933,7 @@ function toMotifError(error) {
3305
4933
  IMAGE_EDITING_TOP_20,
3306
4934
  IMAGE_TEXT_TO_IMAGE_TOP_20,
3307
4935
  MODELS,
4936
+ MODEL_OUTPUT,
3308
4937
  MotifError,
3309
4938
  RECRAFT_STYLES,
3310
4939
  RESOLUTIONS,
@@ -3316,14 +4945,22 @@ function toMotifError(error) {
3316
4945
  aspectToGptSize,
3317
4946
  buildFalToolRequest,
3318
4947
  buildGenerateBody,
4948
+ describeModelOutput,
3319
4949
  enrichPrompt,
3320
4950
  err,
3321
4951
  estimateCost,
3322
4952
  estimateVideoCost,
4953
+ falToolParameters,
4954
+ formatCost,
3323
4955
  getFalKeyFromEnv,
3324
4956
  isFalToolId,
4957
+ losslessAvailability,
4958
+ measuredToolCost,
4959
+ modelOutput,
3325
4960
  motifEnvSchema,
3326
4961
  ok,
3327
4962
  parseMotifEnv,
3328
- sanitizePrompt
4963
+ projectedToolCost,
4964
+ sanitizePrompt,
4965
+ sumCosts
3329
4966
  });