@howells/motif-sdk 6.0.0 → 7.1.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
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let neverthrow = require("neverthrow");
3
3
  let _howells_envy = require("@howells/envy");
4
4
  let zod = require("zod");
5
- //#region src/aspects.ts
5
+ // src/aspects.ts
6
6
  /** Ordered by popularity: square first, then common ratios */
7
7
  const ASPECT_RATIOS = [
8
8
  "auto",
@@ -237,8 +237,7 @@ const FORMAT_PRESETS = {
237
237
  label: "Wide"
238
238
  }
239
239
  };
240
- //#endregion
241
- //#region src/creative.ts
240
+ // src/creative.ts
242
241
  const CONTROL_CHAR_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
243
242
  const TRAILING_PERIODS_REGEX = /\.+$/;
244
243
  /**
@@ -314,7 +313,7 @@ const CREATIVE_TAXONOMY = {
314
313
  description: "Natural, unposed documentary portraits of people. Pair with a mood for the light.",
315
314
  id: "portrait",
316
315
  label: "Documentary portrait",
317
- model: "seedream45"
316
+ model: "seedream5"
318
317
  },
319
318
  {
320
319
  acceptsMood: false,
@@ -480,8 +479,7 @@ function enrichPrompt(options) {
480
479
  prompt: clauses.length ? joinSentences([basePrompt, ...clauses].filter((part) => part !== "")) : basePrompt
481
480
  };
482
481
  }
483
- //#endregion
484
- //#region src/errors.ts
482
+ // src/errors.ts
485
483
  /**
486
484
  * `MotifError` and its coercion helper.
487
485
  *
@@ -538,8 +536,7 @@ function toMotifError(error) {
538
536
  if (error instanceof MotifError) return error;
539
537
  return new MotifError(error instanceof Error ? error.message : String(error), 0, error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0);
540
538
  }
541
- //#endregion
542
- //#region src/task-plan-shared.ts
539
+ // src/task-plan-shared.ts
543
540
  /**
544
541
  * Helpers every request builder in the Task planner shares: refusing fields a
545
542
  * Model can't carry, and merging `params` without letting them replace what
@@ -619,8 +616,7 @@ function mergeParams(body, params, owned) {
619
616
  ...params
620
617
  });
621
618
  }
622
- //#endregion
623
- //#region src/prompt-warnings.ts
619
+ // src/prompt-warnings.ts
624
620
  const EDIT_VERB_REGEX = /^\s*(remove|erase|extend|outpaint)\b/i;
625
621
  /** The CLI verb that does each edit directly, keyed by the prompt's first word. */
626
622
  const EDIT_VERBS = {
@@ -686,8 +682,7 @@ function promptWarnings(prompt, context = {}) {
686
682
  });
687
683
  return warnings;
688
684
  }
689
- //#endregion
690
- //#region src/env.ts
685
+ // src/env.ts
691
686
  const motifEnvSchema = (0, _howells_envy.defineEnv)({ optional: { FAL_KEY: zod.z.string().min(1) } });
692
687
  function parseMotifEnv(input = process.env) {
693
688
  return motifEnvSchema.parse(input);
@@ -695,13 +690,11 @@ function parseMotifEnv(input = process.env) {
695
690
  function getFalKeyFromEnv(input = process.env) {
696
691
  return parseMotifEnv(input).FAL_KEY;
697
692
  }
698
- //#endregion
699
- //#region src/models.ts
693
+ // src/models.ts
700
694
  const AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-10-07";
701
695
  const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-to-image", "https://artificialanalysis.ai/image/leaderboard/editing"];
702
696
  const FAL_PRICING_CHECKED_AT = "2026-05-12";
703
697
  const FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
704
- const FAL_PRICING_CHECKED_AUG_2026 = "2026-08-23";
705
698
  const FAL_PRICING_CHECKED_SEP_2026 = "2026-09-16";
706
699
  const FAL_PRICING_CHECKED_OCT_2026 = "2026-10-29";
707
700
  const MODELS = {
@@ -1000,7 +993,7 @@ const MODELS = {
1000
993
  quality: "frontier",
1001
994
  speed: "unknown"
1002
995
  },
1003
- useCase: "Previous-generation Gemini workhorse; retains 0.5K output"
996
+ useCase: "Gemini workhorse with 0.5K output"
1004
997
  },
1005
998
  editEndpoint: "fal-ai/nano-banana-2/edit",
1006
999
  endpoint: "fal-ai/nano-banana-2",
@@ -1105,211 +1098,6 @@ const MODELS = {
1105
1098
  supportsWebSearch: true,
1106
1099
  type: "generation"
1107
1100
  },
1108
- gemini: {
1109
- benchmark: { artificialAnalysis: {
1110
- editing: {
1111
- elo: 987,
1112
- rank: 50
1113
- },
1114
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1115
- sourceUrls: AA_IMAGE_SOURCES,
1116
- textToImage: {
1117
- elo: 985,
1118
- rank: 60
1119
- }
1120
- } },
1121
- editEndpoint: "fal-ai/gemini-25-flash-image/edit",
1122
- endpoint: "fal-ai/gemini-25-flash-image",
1123
- falPricing: {
1124
- checkedAt: FAL_PRICING_CHECKED_AT,
1125
- currency: "USD",
1126
- endpointId: "fal-ai/gemini-25-flash-image",
1127
- estimatedCostPerImageUsd: .0398,
1128
- source: "fal-pricing-api",
1129
- unit: "images",
1130
- unitPrice: .0398
1131
- },
1132
- maxReferenceImages: 4,
1133
- name: "Gemini 2.5 Flash",
1134
- pricePerImageUsd: .0398,
1135
- pricing: "$0.04",
1136
- sizeMode: "aspect_ratio",
1137
- supportedAspects: [
1138
- "21:9",
1139
- "16:9",
1140
- "3:2",
1141
- "4:3",
1142
- "5:4",
1143
- "1:1",
1144
- "4:5",
1145
- "3:4",
1146
- "2:3",
1147
- "9:16"
1148
- ],
1149
- supportsAspect: true,
1150
- supportsEdit: true,
1151
- supportsNumImages: true,
1152
- supportsOutputFormat: true,
1153
- supportsResolution: false,
1154
- supportsSafetyTolerance: true,
1155
- supportsSeed: true,
1156
- supportsSyncMode: true,
1157
- type: "generation"
1158
- },
1159
- gemini3: {
1160
- editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
1161
- endpoint: "fal-ai/gemini-3-pro-image-preview",
1162
- falPricing: {
1163
- checkedAt: FAL_PRICING_CHECKED_AT,
1164
- currency: "USD",
1165
- endpointId: "fal-ai/gemini-3-pro-image-preview",
1166
- estimatedCostPerImageUsd: .15,
1167
- source: "fal-pricing-api",
1168
- unit: "images",
1169
- unitPrice: .15
1170
- },
1171
- maxReferenceImages: 4,
1172
- name: "Gemini 3 Pro",
1173
- pricePerImageUsd: .15,
1174
- pricing: "$0.15",
1175
- sizeMode: "aspect_ratio",
1176
- supportedAspects: [
1177
- "auto",
1178
- "21:9",
1179
- "16:9",
1180
- "3:2",
1181
- "4:3",
1182
- "5:4",
1183
- "1:1",
1184
- "4:5",
1185
- "3:4",
1186
- "2:3",
1187
- "9:16"
1188
- ],
1189
- supportedResolutions: [
1190
- "1K",
1191
- "2K",
1192
- "4K"
1193
- ],
1194
- supportsAspect: true,
1195
- supportsEdit: true,
1196
- supportsNumImages: true,
1197
- supportsOutputFormat: true,
1198
- supportsResolution: true,
1199
- supportsSafetyTolerance: true,
1200
- supportsSeed: true,
1201
- supportsSyncMode: true,
1202
- supportsWebSearch: true,
1203
- type: "generation"
1204
- },
1205
- seedream4: {
1206
- benchmark: {
1207
- artificialAnalysis: {
1208
- editing: {
1209
- elo: 1015,
1210
- rank: 36
1211
- },
1212
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1213
- sourceUrls: AA_IMAGE_SOURCES,
1214
- textToImage: {
1215
- elo: 1030,
1216
- rank: 21
1217
- }
1218
- },
1219
- speed: {
1220
- medianSeconds: 14.3,
1221
- p95Seconds: 21,
1222
- source: "artificial-analysis-models"
1223
- },
1224
- tiers: {
1225
- price: "budget",
1226
- quality: "best",
1227
- speed: "balanced"
1228
- },
1229
- useCase: "Legacy budget generation and large multi-reference edits"
1230
- },
1231
- customImageSize: {
1232
- maxPixels: 16777216,
1233
- minPixels: 921600
1234
- },
1235
- editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
1236
- endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
1237
- falPricing: {
1238
- checkedAt: FAL_PRICING_CHECKED_AT,
1239
- currency: "USD",
1240
- endpointId: "fal-ai/bytedance/seedream/v4/text-to-image",
1241
- estimatedCostPerImageUsd: .03,
1242
- source: "fal-pricing-api",
1243
- unit: "images",
1244
- unitPrice: .03
1245
- },
1246
- maxReferenceImages: 10,
1247
- name: "Seedream 4.0",
1248
- pricePerImageUsd: .03,
1249
- pricing: "$0.03",
1250
- sizeMode: "image_size_enum",
1251
- supportsAspect: true,
1252
- supportsEdit: true,
1253
- supportsNumImages: true,
1254
- supportsResolution: false,
1255
- supportsSeed: true,
1256
- supportsSyncMode: true,
1257
- type: "generation"
1258
- },
1259
- seedream45: {
1260
- benchmark: {
1261
- artificialAnalysis: {
1262
- editing: {
1263
- elo: 1037,
1264
- rank: 32
1265
- },
1266
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1267
- sourceUrls: AA_IMAGE_SOURCES,
1268
- textToImage: {
1269
- elo: 1022,
1270
- rank: 26
1271
- }
1272
- },
1273
- speed: {
1274
- medianSeconds: 18.2,
1275
- p95Seconds: 43.3,
1276
- source: "artificial-analysis-models"
1277
- },
1278
- tiers: {
1279
- price: "standard",
1280
- quality: "better",
1281
- speed: "balanced"
1282
- },
1283
- useCase: "Cheap current Seedream model with good animation/art scores"
1284
- },
1285
- customImageSize: {
1286
- maxPixels: 16777216,
1287
- minPixels: 3686400
1288
- },
1289
- editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
1290
- endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
1291
- falPricing: {
1292
- checkedAt: FAL_PRICING_CHECKED_AT,
1293
- currency: "USD",
1294
- endpointId: "fal-ai/bytedance/seedream/v4.5/text-to-image",
1295
- estimatedCostPerImageUsd: .04,
1296
- source: "fal-pricing-api",
1297
- unit: "images",
1298
- unitPrice: .04
1299
- },
1300
- maxReferenceImages: 10,
1301
- name: "Seedream 4.5",
1302
- pricePerImageUsd: .04,
1303
- pricing: "$0.04",
1304
- sizeMode: "image_size_enum",
1305
- supportsAspect: true,
1306
- supportsEdit: true,
1307
- supportsNumImages: true,
1308
- supportsResolution: false,
1309
- supportsSeed: true,
1310
- supportsSyncMode: true,
1311
- type: "generation"
1312
- },
1313
1101
  seedream5: {
1314
1102
  benchmark: { artificialAnalysis: {
1315
1103
  editing: {
@@ -1393,65 +1181,6 @@ const MODELS = {
1393
1181
  supportsSyncMode: true,
1394
1182
  maxReferenceImages: 10
1395
1183
  },
1396
- "flux2-max": {
1397
- benchmark: {
1398
- artificialAnalysis: {
1399
- editing: {
1400
- elo: 997,
1401
- rank: 49
1402
- },
1403
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1404
- sourceUrls: AA_IMAGE_SOURCES,
1405
- textToImage: {
1406
- elo: 1020,
1407
- rank: 28
1408
- }
1409
- },
1410
- speed: {
1411
- medianSeconds: 31.6,
1412
- p95Seconds: 41,
1413
- source: "artificial-analysis-models"
1414
- },
1415
- tiers: {
1416
- price: "premium",
1417
- quality: "best",
1418
- speed: "slow"
1419
- },
1420
- useCase: "Best FLUX quality and high-end editing"
1421
- },
1422
- customImageSize: {
1423
- maxEdge: 2560,
1424
- maxPixels: 4194304,
1425
- minEdge: 256,
1426
- multipleOf: 16
1427
- },
1428
- editEndpoint: "fal-ai/flux-2-max/edit",
1429
- endpoint: "fal-ai/flux-2-max",
1430
- falPricing: {
1431
- checkedAt: FAL_PRICING_CHECKED_AT,
1432
- currency: "USD",
1433
- endpointId: "fal-ai/flux-2-max",
1434
- estimatedCostPerImageUsd: .07,
1435
- source: "fal-pricing-api",
1436
- unit: "megapixels",
1437
- unitPrice: .07
1438
- },
1439
- maxReferenceImages: 10,
1440
- name: "FLUX.2 Max",
1441
- pricePerImageUsd: .07,
1442
- pricing: "$0.07/MP",
1443
- sizeMode: "image_size_enum",
1444
- supportsAspect: true,
1445
- supportsEdit: true,
1446
- supportsNumImages: false,
1447
- supportsOutputFormat: true,
1448
- supportsResolution: false,
1449
- supportsSafetyChecker: true,
1450
- supportsSafetyTolerance: true,
1451
- supportsSeed: true,
1452
- supportsSyncMode: true,
1453
- type: "generation"
1454
- },
1455
1184
  "flux2-pro": {
1456
1185
  benchmark: {
1457
1186
  artificialAnalysis: {
@@ -1573,69 +1302,6 @@ const MODELS = {
1573
1302
  supportsSyncMode: true,
1574
1303
  type: "generation"
1575
1304
  },
1576
- "flux2-dev": {
1577
- benchmark: {
1578
- artificialAnalysis: {
1579
- editing: {
1580
- elo: 1e3,
1581
- rank: 45
1582
- },
1583
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1584
- sourceUrls: AA_IMAGE_SOURCES,
1585
- textToImage: {
1586
- elo: 1e3,
1587
- rank: 42
1588
- }
1589
- },
1590
- speed: {
1591
- medianSeconds: 4.6,
1592
- p95Seconds: 69.6,
1593
- source: "artificial-analysis-models"
1594
- },
1595
- tiers: {
1596
- price: "variable",
1597
- quality: "better",
1598
- speed: "fast"
1599
- },
1600
- useCase: "Open FLUX.2 quality with low average cost"
1601
- },
1602
- customImageSize: {
1603
- maxEdge: 2048,
1604
- minEdge: 512
1605
- },
1606
- editEndpoint: "fal-ai/flux-2/edit",
1607
- endpoint: "fal-ai/flux-2",
1608
- falPricing: {
1609
- checkedAt: FAL_PRICING_CHECKED_AT,
1610
- currency: "USD",
1611
- endpointId: "fal-ai/flux-2",
1612
- estimatedCostPerImageUsd: .012,
1613
- source: "fal-pricing-api",
1614
- unit: "compute seconds",
1615
- unitPrice: .00167
1616
- },
1617
- maxReferenceImages: 4,
1618
- name: "FLUX.2 [dev]",
1619
- pricePerImageUsd: .012,
1620
- pricing: "$0.00167/sec",
1621
- sizeMode: "image_size_enum",
1622
- supportsAspect: true,
1623
- streaming: {
1624
- generation: true,
1625
- edit: true
1626
- },
1627
- supportsEdit: true,
1628
- supportsGuidanceScale: true,
1629
- supportsInferenceSteps: true,
1630
- supportsNumImages: true,
1631
- supportsOutputFormat: true,
1632
- supportsResolution: false,
1633
- supportsSafetyChecker: true,
1634
- supportsSafetyTolerance: true,
1635
- supportsSeed: true,
1636
- supportsSyncMode: true,
1637
- type: "generation"
1638
- },
1639
1305
  "flux2-turbo": {
1640
1306
  customImageSize: {
1641
1307
  maxEdge: 2048,
@@ -1666,188 +1332,6 @@ const MODELS = {
1666
1332
  supportsSyncMode: true,
1667
1333
  type: "generation"
1668
1334
  },
1669
- flux: {
1670
- endpoint: "fal-ai/flux-pro/v1.1-ultra",
1671
- falPricing: {
1672
- checkedAt: FAL_PRICING_CHECKED_AT,
1673
- currency: "USD",
1674
- endpointId: "fal-ai/flux-pro/v1.1-ultra",
1675
- estimatedCostPerImageUsd: .06,
1676
- source: "fal-pricing-api",
1677
- unit: "images",
1678
- unitPrice: .06
1679
- },
1680
- maxReferenceImages: 1,
1681
- name: "FLUX Pro Ultra",
1682
- pricePerImageUsd: .06,
1683
- pricing: "$0.06",
1684
- sizeMode: "aspect_ratio",
1685
- supportsAspect: true,
1686
- supportsEdit: true,
1687
- supportsEnhancePrompt: true,
1688
- supportsImagePromptStrength: true,
1689
- supportsNumImages: true,
1690
- supportsOutputFormat: true,
1691
- supportsRaw: true,
1692
- supportsResolution: false,
1693
- supportsSafetyTolerance: true,
1694
- supportsSeed: true,
1695
- supportsSyncMode: true,
1696
- type: "generation"
1697
- },
1698
- "flux-fast": {
1699
- customImageSize: {},
1700
- endpoint: "fal-ai/flux/schnell",
1701
- falPricing: {
1702
- checkedAt: FAL_PRICING_CHECKED_AT,
1703
- currency: "USD",
1704
- endpointId: "fal-ai/flux/schnell",
1705
- estimatedCostPerImageUsd: .003,
1706
- source: "fal-pricing-api",
1707
- unit: "megapixels",
1708
- unitPrice: .003
1709
- },
1710
- name: "FLUX Schnell",
1711
- pricePerImageUsd: .003,
1712
- pricing: "$0.003",
1713
- sizeMode: "image_size_enum",
1714
- supportsAspect: true,
1715
- supportsEdit: false,
1716
- supportsGuidanceScale: true,
1717
- supportsInferenceSteps: true,
1718
- supportsNumImages: true,
1719
- supportsOutputFormat: true,
1720
- supportsResolution: false,
1721
- supportsSeed: true,
1722
- supportsSyncMode: true,
1723
- type: "generation"
1724
- },
1725
- recraft: {
1726
- customImageSize: {},
1727
- endpoint: "fal-ai/recraft/v3/text-to-image",
1728
- falPricing: {
1729
- checkedAt: FAL_PRICING_CHECKED_AT,
1730
- currency: "USD",
1731
- endpointId: "fal-ai/recraft/v3/text-to-image",
1732
- estimatedCostPerImageUsd: .04,
1733
- source: "fal-pricing-api",
1734
- unit: "images",
1735
- unitPrice: .04
1736
- },
1737
- name: "Recraft V3",
1738
- pricePerImageUsd: .04,
1739
- pricing: "$0.04",
1740
- sizeMode: "image_size_enum",
1741
- supportsAspect: true,
1742
- supportsEdit: false,
1743
- supportsNumImages: false,
1744
- supportsResolution: false,
1745
- supportsStyle: true,
1746
- supportsSyncMode: true,
1747
- type: "generation"
1748
- },
1749
- reve: {
1750
- benchmark: {
1751
- tiers: {
1752
- price: "premium",
1753
- quality: "frontier",
1754
- speed: "unknown"
1755
- },
1756
- useCase: "Historical Reve route; not currently verified on fal"
1757
- },
1758
- editEndpoint: "fal-ai/reve/edit",
1759
- endpoint: "fal-ai/reve/text-to-image",
1760
- falPricing: {
1761
- checkedAt: FAL_PRICING_CHECKED_AUG_2026,
1762
- currency: "USD",
1763
- endpointId: "fal-ai/reve/text-to-image",
1764
- estimatedCostPerImageUsd: .04,
1765
- source: "fal-pricing-api",
1766
- unit: "images",
1767
- unitPrice: .04
1768
- },
1769
- maxReferenceImages: 1,
1770
- name: "Reve 2.1",
1771
- pricePerImageUsd: .04,
1772
- pricing: "$0.04",
1773
- sizeMode: "aspect_ratio",
1774
- supportedAspects: [
1775
- "16:9",
1776
- "9:16",
1777
- "3:2",
1778
- "2:3",
1779
- "4:3",
1780
- "3:4",
1781
- "1:1"
1782
- ],
1783
- supportsAspect: true,
1784
- supportsEdit: true,
1785
- supportsNumImages: true,
1786
- supportsOutputFormat: true,
1787
- supportsResolution: false,
1788
- supportsSeed: false,
1789
- supportsSyncMode: true,
1790
- type: "generation"
1791
- },
1792
- recraft4: {
1793
- benchmark: { artificialAnalysis: {
1794
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1795
- sourceUrls: AA_IMAGE_SOURCES,
1796
- textToImage: {
1797
- elo: 984,
1798
- rank: 62
1799
- }
1800
- } },
1801
- customImageSize: {},
1802
- endpoint: "fal-ai/recraft/v4/text-to-image",
1803
- falPricing: {
1804
- checkedAt: FAL_PRICING_CHECKED_JUL_2026,
1805
- currency: "USD",
1806
- endpointId: "fal-ai/recraft/v4/text-to-image",
1807
- estimatedCostPerImageUsd: .04,
1808
- source: "fal-pricing-api",
1809
- unit: "images",
1810
- unitPrice: .04
1811
- },
1812
- name: "Recraft V4",
1813
- pricePerImageUsd: .04,
1814
- pricing: "$0.04",
1815
- sizeMode: "image_size_enum",
1816
- supportsAspect: true,
1817
- supportsEdit: false,
1818
- supportsNumImages: false,
1819
- supportsResolution: false,
1820
- supportsSyncMode: true,
1821
- type: "generation"
1822
- },
1823
- ideogram: {
1824
- customImageSize: {},
1825
- endpoint: "fal-ai/ideogram/v3",
1826
- falPricing: {
1827
- checkedAt: FAL_PRICING_CHECKED_AT,
1828
- currency: "USD",
1829
- endpointId: "fal-ai/ideogram/v3",
1830
- estimatedCostPerImageUsd: .03,
1831
- source: "fal-pricing-api",
1832
- unit: "images",
1833
- unitPrice: .03
1834
- },
1835
- name: "Ideogram V3",
1836
- pricePerImageUsd: .03,
1837
- pricing: "$0.03",
1838
- sizeMode: "image_size_enum",
1839
- supportsAspect: true,
1840
- supportsEdit: false,
1841
- supportsExpandPrompt: true,
1842
- supportsNegativePrompt: true,
1843
- supportsNumImages: true,
1844
- supportsRenderingSpeed: true,
1845
- supportsResolution: false,
1846
- supportsSeed: true,
1847
- supportsStyle: true,
1848
- supportsSyncMode: true,
1849
- type: "generation"
1850
- },
1851
1335
  ideogram4: {
1852
1336
  benchmark: { artificialAnalysis: {
1853
1337
  snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
@@ -1948,52 +1432,6 @@ const MODELS = {
1948
1432
  supportsSyncMode: true,
1949
1433
  type: "generation"
1950
1434
  },
1951
- qwen: {
1952
- benchmark: {
1953
- artificialAnalysis: {
1954
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
1955
- sourceUrls: AA_IMAGE_SOURCES,
1956
- textToImage: {
1957
- elo: 890,
1958
- rank: 106
1959
- }
1960
- },
1961
- speed: {
1962
- medianSeconds: 23.3,
1963
- p95Seconds: 491,
1964
- source: "artificial-analysis-models"
1965
- },
1966
- tiers: {
1967
- price: "budget",
1968
- quality: "better",
1969
- speed: "slow"
1970
- },
1971
- useCase: "Low-cost open-weight text and design generation"
1972
- },
1973
- customImageSize: {},
1974
- endpoint: "fal-ai/qwen-image",
1975
- falPricing: {
1976
- checkedAt: FAL_PRICING_CHECKED_AT,
1977
- currency: "USD",
1978
- endpointId: "fal-ai/qwen-image",
1979
- estimatedCostPerImageUsd: .02,
1980
- source: "fal-pricing-api",
1981
- unit: "megapixels",
1982
- unitPrice: .02
1983
- },
1984
- name: "Qwen Image",
1985
- pricePerImageUsd: .02,
1986
- pricing: "$0.02/MP",
1987
- sizeMode: "image_size_enum",
1988
- supportsAspect: true,
1989
- supportsEdit: false,
1990
- supportsNumImages: true,
1991
- supportsOutputFormat: true,
1992
- supportsResolution: false,
1993
- supportsSeed: true,
1994
- supportsSyncMode: true,
1995
- type: "generation"
1996
- },
1997
1435
  qwen3: {
1998
1436
  benchmark: {
1999
1437
  artificialAnalysis: {
@@ -2394,29 +1832,9 @@ const MODELS = {
2394
1832
  supportsNumImages: false,
2395
1833
  supportsResolution: false,
2396
1834
  type: "utility"
2397
- },
2398
- rmbg: {
2399
- endpoint: "fal-ai/birefnet",
2400
- name: "BiRefNet (Background Removal)",
2401
- pricing: "$0.02",
2402
- supportsAspect: false,
2403
- supportsEdit: false,
2404
- supportsNumImages: false,
2405
- supportsResolution: false,
2406
- type: "utility"
2407
- },
2408
- bria: {
2409
- endpoint: "fal-ai/bria/background/remove",
2410
- name: "Bria RMBG 2.0",
2411
- pricing: "$0.02",
2412
- supportsAspect: false,
2413
- supportsEdit: false,
2414
- supportsNumImages: false,
2415
- supportsResolution: false,
2416
- type: "utility"
2417
1835
  }
2418
1836
  };
2419
- /** Curated generation choices; predecessor routes are explicit-only. */
1837
+ /** Curated generation Models, the only ones Motif recommends. */
2420
1838
  const GENERATION_MODELS = [
2421
1839
  "sunburst",
2422
1840
  "flare",
@@ -2442,30 +1860,8 @@ const GENERATION_MODELS = [
2442
1860
  "ideogram3-transparent",
2443
1861
  "recraft41"
2444
1862
  ];
2445
- /** Retained for old pins, explicit overrides and tuned Looks, never auto-selected. */
2446
- const LEGACY_GENERATION_MODELS = [
2447
- "gemini",
2448
- "gemini3",
2449
- "seedream4",
2450
- "seedream45",
2451
- "flux2-max",
2452
- "flux2-dev",
2453
- "flux",
2454
- "flux-fast",
2455
- "recraft",
2456
- "recraft4",
2457
- "ideogram",
2458
- "qwen"
2459
- ];
2460
1863
  GENERATION_MODELS.filter((id) => MODELS[id]?.supportsEdit === true);
2461
- const UTILITY_MODELS = [
2462
- "clarity",
2463
- "crystal",
2464
- "rmbg",
2465
- "bria"
2466
- ];
2467
- //#endregion
2468
- //#region src/cost.ts
1864
+ // src/cost.ts
2469
1865
  /**
2470
1866
  * Estimate cost based on model and settings. `editImages` is the number of
2471
1867
  * input images on an edit, for Models whose edits cost more than generation.
@@ -2477,7 +1873,7 @@ function estimateCost(model, resolution, numImages = 1, editImages = 0) {
2477
1873
  if (model === "grok-image-2" && editImages > 0) return (resolution === "2K" || resolution === "4K" ? .08 : .06) * numImages + .01 * editImages;
2478
1874
  if (model === "grok-image-quality") return (resolution === "2K" || resolution === "4K" ? .07 : .05) * numImages + .01 * editImages;
2479
1875
  if (configuredPrice !== void 0) {
2480
- if ((model === "banana" || model === "gemini3") && resolution === "4K") return configuredPrice * 2 * numImages;
1876
+ if (model === "banana" && resolution === "4K") return configuredPrice * 2 * numImages;
2481
1877
  if (model === "seedream5" && (resolution === "2K" || resolution === "4K")) return configuredPrice * 2 * numImages;
2482
1878
  if (model === "grok-image-2" && (resolution === "2K" || resolution === "4K")) return .08 * numImages;
2483
1879
  if (model === "banana2") return configuredPrice * (resolution === "4K" ? 2 : resolution === "2K" ? 1.5 : resolution === "0.5K" ? .75 : 1) * numImages;
@@ -2485,17 +1881,8 @@ function estimateCost(model, resolution, numImages = 1, editImages = 0) {
2485
1881
  }
2486
1882
  switch (model) {
2487
1883
  case "gpt": return .133 * numImages;
2488
- case "banana":
2489
- case "gemini3": return (resolution === "4K" ? .3 : .15) * numImages;
2490
- case "gemini": return .039 * numImages;
2491
- case "flux":
2492
- case "ideogram": return .06 * numImages;
2493
- case "recraft": return .04 * numImages;
2494
- case "flux-fast": return .003 * numImages;
2495
1884
  case "clarity": return .03 * numImages;
2496
- case "crystal":
2497
- case "rmbg":
2498
- case "bria": return .02 * numImages;
1885
+ case "crystal": return .02 * numImages;
2499
1886
  default: return .1 * numImages;
2500
1887
  }
2501
1888
  }
@@ -2504,8 +1891,7 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true, model = "k
2504
1891
  if (model === "kling-turbo") return .14 * durationSeconds;
2505
1892
  return (generateAudio ? .168 : .112) * durationSeconds;
2506
1893
  }
2507
- //#endregion
2508
- //#region src/fal-parse.ts
1894
+ // src/fal-parse.ts
2509
1895
  /**
2510
1896
  * Boundary parsing for fal.ai HTTP responses.
2511
1897
  *
@@ -2610,8 +1996,7 @@ function requestIdFromBody(text) {
2610
1996
  if (!isRecord$1(parsed)) return;
2611
1997
  return asString(parsed.request_id) ?? asString(parsed.requestId) ?? asString(parsed.trace_id);
2612
1998
  }
2613
- //#endregion
2614
- //#region src/capabilities.ts
1999
+ // src/capabilities.ts
2615
2000
  /**
2616
2001
  * Which generation options each model accepts, derived from the registry.
2617
2002
  *
@@ -2639,11 +2024,9 @@ const OPTION_CAPABILITIES = {
2639
2024
  enableGoogleSearch: (config) => config.supportsGoogleSearch === true,
2640
2025
  enableSafetyChecker: (config) => config.supportsSafetyChecker === true,
2641
2026
  enableWebSearch: (config) => config.supportsWebSearch === true,
2642
- enhancePrompt: (config) => config.supportsEnhancePrompt === true,
2643
2027
  expandPrompt: (config) => config.supportsExpandPrompt === true,
2644
2028
  guidanceScale: (config) => config.supportsGuidanceScale === true,
2645
2029
  "image editing": (config) => config.supportsEdit,
2646
- imagePromptStrength: (config) => config.supportsImagePromptStrength === true,
2647
2030
  imageSize: (config) => config.sizeMode === "gpt_size" || config.sizeMode === "image_size_enum",
2648
2031
  inputFidelity: (config) => config.sizeMode === "gpt_size",
2649
2032
  limitGenerations: (config) => config.supportsLimitGenerations === true,
@@ -2654,12 +2037,10 @@ const OPTION_CAPABILITIES = {
2654
2037
  numInferenceSteps: (config) => config.supportsInferenceSteps === true,
2655
2038
  outputFormat: (config) => config.supportsOutputFormat === true,
2656
2039
  quality: (config) => config.supportsQuality === true,
2657
- raw: (config) => config.supportsRaw === true,
2658
2040
  renderingSpeed: (config) => config.supportsRenderingSpeed === true,
2659
2041
  resolution: (config) => config.supportsResolution,
2660
2042
  safetyTolerance: (config) => config.supportsSafetyTolerance === true,
2661
2043
  seed: (config) => config.supportsSeed === true,
2662
- style: (config) => config.supportsStyle === true,
2663
2044
  syncMode: (config) => config.supportsSyncMode === true,
2664
2045
  thinkingLevel: (config) => config.supportsThinkingLevel === true,
2665
2046
  "transparent output": (config) => config.supportsBackground === true || config.transparentOutput === true
@@ -2687,7 +2068,7 @@ function modelsSupporting(option) {
2687
2068
  */
2688
2069
  var UnsupportedOptionError = class extends Error {
2689
2070
  code = "INVALID_OPTION";
2690
- /** Display name of the model that refused, e.g. `"Seedream 4.5"`. */
2071
+ /** Display name of the model that refused, e.g. `"Seedream 5 Pro"`. */
2691
2072
  model;
2692
2073
  /** Generation model ids that accept `option`. */
2693
2074
  modelsSupporting;
@@ -2705,8 +2086,7 @@ var UnsupportedOptionError = class extends Error {
2705
2086
  this.supportedOptions = supported;
2706
2087
  }
2707
2088
  };
2708
- //#endregion
2709
- //#region src/generate.ts
2089
+ // src/generate.ts
2710
2090
  const GPT_IMAGE_SIZES = [
2711
2091
  "auto",
2712
2092
  "1024x1024",
@@ -2776,8 +2156,6 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
2776
2156
  if (config.sizeMode !== "gpt_size") unsupported(config, "inputFidelity");
2777
2157
  }
2778
2158
  if (options.imageSize !== void 0 && sizeMode !== "gpt_size" && sizeMode !== "image_size_enum") unsupported(config, "imageSize");
2779
- if (options.imagePromptStrength !== void 0 && config.supportsImagePromptStrength !== true) unsupported(config, "imagePromptStrength");
2780
- if (options.imagePromptStrength !== void 0 && !hasEditImages) throw new Error("imagePromptStrength requires editImageUrls");
2781
2159
  if (options.maskImageUrl !== void 0 && config.supportsMaskImage !== true) unsupported(config, "maskImageUrl");
2782
2160
  if (options.maskImageUrl !== void 0 && !hasEditImages) throw new Error("maskImageUrl requires editImageUrls");
2783
2161
  if (options.markupImageUrl !== void 0 && config.supportsMarkupImage !== true) unsupported(config, "markupImageUrl");
@@ -2796,13 +2174,10 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
2796
2174
  if (!qualities.includes(options.quality)) throw new Error(`${config.name} quality must be one of ${qualities.join(", ")}`);
2797
2175
  }
2798
2176
  if (options.negativePrompt !== void 0 && config.supportsNegativePrompt !== true) unsupported(config, "negativePrompt");
2799
- if (options.style !== void 0 && config.supportsStyle !== true) unsupported(config, "style");
2800
2177
  if (options.syncMode !== void 0 && config.supportsSyncMode !== true) unsupported(config, "syncMode");
2801
2178
  if (options.renderingSpeed !== void 0 && config.supportsRenderingSpeed !== true) unsupported(config, "renderingSpeed");
2802
2179
  if (options.guidanceScale !== void 0 && config.supportsGuidanceScale !== true) unsupported(config, "guidanceScale");
2803
2180
  if (options.numInferenceSteps !== void 0 && config.supportsInferenceSteps !== true) unsupported(config, "numInferenceSteps");
2804
- if (options.raw !== void 0 && config.supportsRaw !== true) unsupported(config, "raw");
2805
- if (options.enhancePrompt !== void 0 && config.supportsEnhancePrompt !== true) unsupported(config, "enhancePrompt");
2806
2181
  if (options.safetyTolerance !== void 0 && config.supportsSafetyTolerance !== true) unsupported(config, "safetyTolerance");
2807
2182
  if (options.enableWebSearch !== void 0 && config.supportsWebSearch !== true) unsupported(config, "enableWebSearch");
2808
2183
  if (options.enableGoogleSearch !== void 0 && config.supportsGoogleSearch !== true) unsupported(config, "enableGoogleSearch");
@@ -2822,7 +2197,7 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
2822
2197
  * - `none`: no size control (video models)
2823
2198
  */
2824
2199
  function buildGenerateBody(options) {
2825
- const { prompt, model, creative, aspect = "1:1", resolution = "2K", numImages = 1, background, editImageUrls, enableGoogleSearch, enableSafetyChecker, transparent, inputFidelity, imageSize, imagePromptStrength, limitGenerations, maskImageUrl, markupImageUrl, seed, outputFormat, quality, negativePrompt, style, syncMode, thinkingLevel, renderingSpeed, guidanceScale, numInferenceSteps, raw, enhancePrompt, safetyTolerance, enableWebSearch, expandPrompt } = options;
2200
+ const { prompt, model, creative, aspect = "1:1", resolution = "2K", numImages = 1, background, editImageUrls, enableGoogleSearch, enableSafetyChecker, transparent, inputFidelity, imageSize, limitGenerations, maskImageUrl, markupImageUrl, seed, outputFormat, quality, negativePrompt, syncMode, thinkingLevel, renderingSpeed, guidanceScale, numInferenceSteps, safetyTolerance, enableWebSearch, expandPrompt } = options;
2826
2201
  const config = MODELS[model];
2827
2202
  if (!config) throw new Error(`Unknown model: ${model}`);
2828
2203
  let { endpoint } = config;
@@ -2858,12 +2233,6 @@ function buildGenerateBody(options) {
2858
2233
  if (!formats.includes(outputFormat)) throw new Error(`${config.name} ${hasEditImages ? "edits support" : "supports"} output formats: ${formats.join(", ")}`);
2859
2234
  body.output_format = outputFormat;
2860
2235
  }
2861
- if (config.supportsRaw === true && raw !== void 0) body.raw = raw;
2862
- if (config.supportsEnhancePrompt === true && enhancePrompt !== void 0) body.enhance_prompt = enhancePrompt;
2863
- if (config.supportsImagePromptStrength === true && imagePromptStrength !== void 0) body.image_prompt_strength = imagePromptStrength;
2864
- if (model === "flux") {
2865
- if (safetyTolerance !== void 0 && safetyTolerance !== "") body.safety_tolerance = safetyTolerance;
2866
- }
2867
2236
  if (config.supportsGuidanceScale === true && guidanceScale !== void 0) body.guidance_scale = guidanceScale;
2868
2237
  if (config.supportsInferenceSteps === true && numInferenceSteps !== void 0) body.num_inference_steps = numInferenceSteps;
2869
2238
  if (config.supportsSafetyTolerance === true) {
@@ -2884,25 +2253,17 @@ function buildGenerateBody(options) {
2884
2253
  if (config.supportsThinkingLevel === true) {
2885
2254
  if (thinkingLevel) body.thinking_level = thinkingLevel;
2886
2255
  }
2887
- if (config.supportsStyle === true && style !== void 0 && style !== "") body.style = style;
2888
2256
  if (config.supportsNegativePrompt === true) {
2889
2257
  if (negativePrompt !== void 0 && negativePrompt !== "") body.negative_prompt = negativePrompt;
2890
2258
  }
2891
- if (model === "ideogram") {
2892
- if (style !== void 0 && style !== "") body.style = style;
2893
- }
2894
2259
  if (config.supportsRenderingSpeed === true) {
2895
2260
  if (renderingSpeed) body.rendering_speed = renderingSpeed;
2896
2261
  }
2897
2262
  if (config.supportsExpandPrompt === true) {
2898
2263
  if (expandPrompt !== void 0) body.expand_prompt = expandPrompt;
2899
2264
  }
2900
- if (model === "flux" && hasEditImages) {
2901
- body.image_url = editImages[0];
2902
- body.image_prompt_strength = imagePromptStrength ?? .5;
2903
- }
2904
2265
  if (config.supportsNumImages) body.num_images = numImages;
2905
- if (hasEditImages && sizeMode !== "gpt_size" && model !== "flux") {
2266
+ if (hasEditImages && sizeMode !== "gpt_size") {
2906
2267
  if (!config.supportsEdit) throw new Error(`Model ${model} does not support image editing`);
2907
2268
  endpoint = config.editEndpoint ?? `${endpoint}/edit`;
2908
2269
  const editImagesField = config.editImagesField ?? "image_urls";
@@ -2920,8 +2281,7 @@ function buildGenerateBody(options) {
2920
2281
  endpoint
2921
2282
  };
2922
2283
  }
2923
- //#endregion
2924
- //#region src/tool-registry/analysis.ts
2284
+ // src/tool-registry/analysis.ts
2925
2285
  const ANALYSIS_TOOLS = {
2926
2286
  "got-ocr": {
2927
2287
  category: "analysis",
@@ -3035,8 +2395,7 @@ const ANALYSIS_TOOLS = {
3035
2395
  task: "vision moderation"
3036
2396
  }
3037
2397
  };
3038
- //#endregion
3039
- //#region src/tool-registry/assets.ts
2398
+ // src/tool-registry/assets.ts
3040
2399
  const ASSET_TOOLS = {
3041
2400
  "hunyuan3d-v3": {
3042
2401
  category: "3d",
@@ -3354,8 +2713,7 @@ const ASSET_TOOLS = {
3354
2713
  task: "single-image 3D reconstruction"
3355
2714
  }
3356
2715
  };
3357
- //#endregion
3358
- //#region src/tool-registry/background.ts
2716
+ // src/tool-registry/background.ts
3359
2717
  const BACKGROUND_TOOLS = {
3360
2718
  "ben-v2": {
3361
2719
  category: "background",
@@ -3730,8 +3088,7 @@ const BACKGROUND_TOOLS = {
3730
3088
  task: "video enhancement"
3731
3089
  }
3732
3090
  };
3733
- //#endregion
3734
- //#region src/tool-registry/editing.ts
3091
+ // src/tool-registry/editing.ts
3735
3092
  const EDITING_TOOLS = {
3736
3093
  "bria-eraser": {
3737
3094
  category: "erase",
@@ -4002,8 +3359,7 @@ const EDITING_TOOLS = {
4002
3359
  task: "virtual garment try-on"
4003
3360
  }
4004
3361
  };
4005
- //#endregion
4006
- //#region src/tool-registry/structure.ts
3362
+ // src/tool-registry/structure.ts
4007
3363
  const STRUCTURE_TOOLS = {
4008
3364
  "depth-anything": {
4009
3365
  category: "preprocess",
@@ -4395,8 +3751,7 @@ const STRUCTURE_TOOLS = {
4395
3751
  task: "depth preprocessing"
4396
3752
  }
4397
3753
  };
4398
- //#endregion
4399
- //#region src/tools.ts
3754
+ // src/tools.ts
4400
3755
  const FAL_TOOLS = {
4401
3756
  ...ANALYSIS_TOOLS,
4402
3757
  ...ASSET_TOOLS,
@@ -4424,8 +3779,7 @@ function buildFalToolRequest(options) {
4424
3779
  tool
4425
3780
  };
4426
3781
  }
4427
- //#endregion
4428
- //#region src/server-tools.ts
3782
+ // src/server-tools.ts
4429
3783
  /**
4430
3784
  * Tool execution bodies for `FalClient`, both synchronous and queued.
4431
3785
  *
@@ -4555,8 +3909,7 @@ async function runToolQueued(exec, options, onProgress) {
4555
3909
  if (request.isErr()) return (0, neverthrow.err)(request.error);
4556
3910
  return (await runRequestQueued(exec, request.value, onProgress)).map(({ data }) => data);
4557
3911
  }
4558
- //#endregion
4559
- //#region src/server.ts
3912
+ // src/server.ts
4560
3913
  const FAL_BASE_URL = "https://fal.run";
4561
3914
  const FAL_QUEUE_URL = "https://queue.fal.run";
4562
3915
  const FAL_API_URL = "https://api.fal.ai";
@@ -4691,100 +4044,6 @@ var FalClient = class {
4691
4044
  const data = await response.value.json();
4692
4045
  return this.normalizeResponse(data, requestId);
4693
4046
  }
4694
- /** ─── Processing ──────────────────────────────────────────── */
4695
- /** Upscale an image using clarity or crystal upscaler. */
4696
- async upscale(options) {
4697
- const { imageUrl, model = "clarity", scaleFactor, creativity, resemblance, prompt: upscalePrompt, negativePrompt, numInferenceSteps, guidanceScale } = options;
4698
- const config = MODELS[model];
4699
- if (!config || config.type !== "utility") return (0, neverthrow.err)(new MotifError(`Invalid upscale model: ${model}`, 0));
4700
- const body = { image_url: imageUrl };
4701
- if (model === "crystal") {
4702
- if (scaleFactor !== void 0) body.scale_factor = scaleFactor;
4703
- if (creativity !== void 0) body.creativity = creativity;
4704
- } else {
4705
- if (scaleFactor !== void 0) body.upscale_factor = scaleFactor;
4706
- if (creativity !== void 0) body.creativity = creativity;
4707
- if (resemblance !== void 0) body.resemblance = resemblance;
4708
- if (upscalePrompt !== void 0 && upscalePrompt !== "") body.prompt = upscalePrompt;
4709
- if (negativePrompt !== void 0 && negativePrompt !== "") body.negative_prompt = negativePrompt;
4710
- if (numInferenceSteps !== void 0) body.num_inference_steps = numInferenceSteps;
4711
- if (guidanceScale !== void 0) body.guidance_scale = guidanceScale;
4712
- }
4713
- const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
4714
- body: JSON.stringify(body),
4715
- method: "POST"
4716
- });
4717
- if (response.isErr()) return (0, neverthrow.err)(response.error);
4718
- const data = await response.value.json();
4719
- return this.normalizeResponse(data);
4720
- }
4721
- /** Remove the background from an image. */
4722
- async removeBackground(options) {
4723
- const { imageUrl, model = "rmbg", variant, operatingResolution, outputFormat, refineForeground, outputMask } = options;
4724
- const config = MODELS[model];
4725
- if (!config) return (0, neverthrow.err)(new MotifError(`Invalid background removal model: ${model}`, 0));
4726
- const rbBody = { image_url: imageUrl };
4727
- if (model === "rmbg") {
4728
- if (variant) rbBody.model = variant;
4729
- if (operatingResolution) rbBody.operating_resolution = operatingResolution;
4730
- if (outputFormat) rbBody.output_format = outputFormat;
4731
- if (refineForeground !== void 0) rbBody.refine_foreground = refineForeground;
4732
- if (outputMask !== void 0) rbBody.output_mask = outputMask;
4733
- }
4734
- const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
4735
- body: JSON.stringify(rbBody),
4736
- method: "POST"
4737
- });
4738
- if (response.isErr()) return (0, neverthrow.err)(response.error);
4739
- const data = await response.value.json();
4740
- return this.normalizeResponse(data);
4741
- }
4742
- /** ─── Video Generation ───────────────────────────────────── */
4743
- /**
4744
- * Generate a video from an image using Kling v3 Pro.
4745
- * This uses the queue API since video generation takes 30-120s.
4746
- * Returns immediately with a job — poll with getJobStatus/getVideoResult.
4747
- */
4748
- async submitVideo(options) {
4749
- const { imageUrl, prompt, duration = 5, generateAudio = true, endImageUrl, negativePrompt, cfgScale } = options;
4750
- const config = MODELS.kling;
4751
- if (!config) return (0, neverthrow.err)(new MotifError("Kling video model not found", 0));
4752
- const body = {
4753
- duration: String(duration),
4754
- generate_audio: generateAudio,
4755
- prompt,
4756
- start_image_url: imageUrl
4757
- };
4758
- if (endImageUrl !== void 0 && endImageUrl !== "") body.end_image_url = endImageUrl;
4759
- if (negativePrompt !== void 0 && negativePrompt !== "") body.negative_prompt = negativePrompt;
4760
- if (cfgScale !== void 0) body.cfg_scale = cfgScale;
4761
- const response = await this.request(`${FAL_QUEUE_URL}/${config.endpoint}`, {
4762
- body: JSON.stringify(body),
4763
- method: "POST"
4764
- });
4765
- if (response.isErr()) return (0, neverthrow.err)(response.error);
4766
- const submission = parseQueueSubmission(await response.value.json());
4767
- return (0, neverthrow.ok)({
4768
- endpoint: endpointFromQueueUrl(submission.responseUrl, config.endpoint),
4769
- estimatedCost: estimateVideoCost(duration, generateAudio),
4770
- requestId: submission.requestId
4771
- });
4772
- }
4773
- /** Fetch the completed video result from the queue. */
4774
- async getVideoResult(endpoint, requestId) {
4775
- const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}`;
4776
- const response = await this.request(url);
4777
- if (response.isErr()) return (0, neverthrow.err)(response.error);
4778
- const data = await response.value.json();
4779
- const video = isRecord$1(data) && isRecord$1(data.video) ? data.video : void 0;
4780
- if (video === void 0) return (0, neverthrow.err)(new MotifError("No video in response", 0));
4781
- return (0, neverthrow.ok)({
4782
- contentType: asString(video.content_type) ?? "",
4783
- fileName: asString(video.file_name) ?? "",
4784
- fileSize: asNumber(video.file_size) ?? 0,
4785
- url: asString(video.url) ?? ""
4786
- });
4787
- }
4788
4047
  /** ─── File Upload ─────────────────────────────────────────── */
4789
4048
  /**
4790
4049
  * Upload a file to fal.ai CDN storage and return the public URL.
@@ -4880,10 +4139,6 @@ var FalClient = class {
4880
4139
  get generationModels() {
4881
4140
  return GENERATION_MODELS;
4882
4141
  }
4883
- /** Utility model keys. */
4884
- get utilityModels() {
4885
- return UTILITY_MODELS;
4886
- }
4887
4142
  /** Registered fal utility/tool endpoints. */
4888
4143
  get tools() {
4889
4144
  return FAL_TOOLS;
@@ -4971,8 +4226,7 @@ var FalClient = class {
4971
4226
  });
4972
4227
  }
4973
4228
  };
4974
- //#endregion
4975
- //#region src/source-size.ts
4229
+ // src/source-size.ts
4976
4230
  const DATA_URL_REGEX = /^data:[^;,]*;base64,(.*)$/s;
4977
4231
  function decodeBase64(data) {
4978
4232
  try {
@@ -5070,8 +4324,7 @@ function dataUrlImageSize(url) {
5070
4324
  const size = pngSize(bytes) ?? jpegSize(bytes) ?? webpSize(bytes);
5071
4325
  return size !== void 0 && size.width > 0 && size.height > 0 ? size : void 0;
5072
4326
  }
5073
- //#endregion
5074
- //#region src/tool-cost.ts
4327
+ // src/tool-cost.ts
5075
4328
  /**
5076
4329
  * What a tool run costs, before it happens and after.
5077
4330
  *
@@ -5255,8 +4508,8 @@ function sumCosts(costs) {
5255
4508
  unknown
5256
4509
  };
5257
4510
  }
5258
- //#endregion
5259
- //#region src/image-benchmarks.ts
4511
+ // src/image-benchmarks.ts
4512
+ const MEASURED_ON_2_5 = "Measured on GPT Image 2.5. Motif runs GPT Image 2.6, which Arena has not scored yet.";
5260
4513
  const BOARDS = {
5261
4514
  "text-to-image": {
5262
4515
  sunburst: {
@@ -5264,6 +4517,7 @@ const BOARDS = {
5264
4517
  rank: 1,
5265
4518
  uncertainty: 5,
5266
4519
  evaluatedModel: "GPT-Image-2.5 Sunburst",
4520
+ note: MEASURED_ON_2_5,
5267
4521
  preliminary: true,
5268
4522
  votes: 40323
5269
4523
  },
@@ -5272,6 +4526,7 @@ const BOARDS = {
5272
4526
  rank: 2,
5273
4527
  uncertainty: 5,
5274
4528
  evaluatedModel: "GPT-Image-2.5 Flare",
4529
+ note: MEASURED_ON_2_5,
5275
4530
  preliminary: true,
5276
4531
  votes: 39218
5277
4532
  },
@@ -5361,6 +4616,7 @@ const BOARDS = {
5361
4616
  rank: 1,
5362
4617
  uncertainty: 5,
5363
4618
  evaluatedModel: "GPT-Image-2.5 Sunburst",
4619
+ note: MEASURED_ON_2_5,
5364
4620
  preliminary: true
5365
4621
  },
5366
4622
  flare: {
@@ -5368,6 +4624,7 @@ const BOARDS = {
5368
4624
  rank: 2,
5369
4625
  uncertainty: 5,
5370
4626
  evaluatedModel: "GPT-Image-2.5 Flare",
4627
+ note: MEASURED_ON_2_5,
5371
4628
  preliminary: true
5372
4629
  },
5373
4630
  "grok-image-2": {
@@ -5437,8 +4694,7 @@ function imageModelBenchmark(model, task) {
5437
4694
  task
5438
4695
  };
5439
4696
  }
5440
- //#endregion
5441
- //#region src/tasks.ts
4697
+ // src/tasks.ts
5442
4698
  /**
5443
4699
  * The Task registry: every job Motif does, with the Models that can do it
5444
4700
  * ranked best-first and tagged with the Tier each belongs to.
@@ -5815,7 +5071,7 @@ const TASKS = {
5815
5071
  summary: "Remove an object, person, text or clutter from an image and fill the gap."
5816
5072
  },
5817
5073
  generate: {
5818
- basis: "Order follows Arena text-to-image (https://arena.ai/leaderboard/image/text-to-image), read 2026-10-29 (MOT-72). Sunburst and Flare lead quality at the benchmarked max setting. Balanced starts with Nano Banana 2.1 on preliminary Arena evidence, then Grok Image 2 and Muse Image. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Fast keeps Nano Banana 2 Lite, budget Grok and Seedream 5 Lite, and FLUX.2 Turbo. Design, transparency and tuned Look routes are capability exceptions. Generation with References uses the separate editing ranking. Older routes are explicit/pin/Look-only.",
5074
+ basis: "Order follows Arena text-to-image (https://arena.ai/leaderboard/image/text-to-image), read 2026-10-29. Sunburst and Flare lead quality at the benchmarked max setting. Balanced starts with Nano Banana 2.1 on preliminary Arena evidence, then Grok Image 2 and Muse Image. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Fast keeps Nano Banana 2 Lite, budget Grok and Seedream 5 Lite, and FLUX.2 Turbo. Design, transparency and tuned Look routes are capability exceptions. Generation with References uses the separate editing ranking. Older routes are explicit/pin/Look-only.",
5819
5075
  models: GENERATION_MODELS_RANKED,
5820
5076
  notFor: "Variations of an image you already have (vary), or a consistent set of images (series run).",
5821
5077
  rankedAt: IMAGE_RANKED_AT,
@@ -6303,7 +5559,7 @@ const TASKS = {
6303
5559
  summary: "Trace a raster image to a clean SVG."
6304
5560
  },
6305
5561
  vary: {
6306
- basis: "Order follows Arena image edit, single image (https://arena.ai/leaderboard/image/image-edit/single-image), read 2026-10-29 (MOT-72). Editing order differs from generation: Grok Image 2 leads balanced, ahead of Nano Banana 2.1, and Seedream 5 Pro outranks GPT Image 1.5. Arena scores Krea 3 only at its Ultra setting, which costs $0.27 to $0.35 an edit and ignores the requested size, so krea3, which runs fal's default setting, is ranked as unscored. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Sunburst and Flare use max quality. vary reuses a previously chosen Model when explicitly supplied, including retained legacy routes.",
5562
+ basis: "Order follows Arena image edit, single image (https://arena.ai/leaderboard/image/image-edit/single-image), read 2026-10-29. Editing order differs from generation: Grok Image 2 leads balanced, ahead of Nano Banana 2.1, and Seedream 5 Pro outranks GPT Image 1.5. Arena scores Krea 3 only at its Ultra setting, which costs $0.27 to $0.35 an edit and ignores the requested size, so krea3, which runs fal's default setting, is ranked as unscored. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Sunburst and Flare use max quality. vary reuses a previously chosen Model when explicitly supplied, including retained legacy routes.",
6307
5563
  models: EDITING_MODELS_RANKED,
6308
5564
  notFor: "A specific change to an image described in words (generate with a reference), or a set of different scenes in one style (series run).",
6309
5565
  rankedAt: IMAGE_RANKED_AT,
@@ -6315,8 +5571,7 @@ const TASK_IDS = Object.keys(TASKS).filter(isTaskId);
6315
5571
  function isTaskId(value) {
6316
5572
  return Object.hasOwn(TASKS, value);
6317
5573
  }
6318
- //#endregion
6319
- //#region src/tool-parameters.generated.ts
5574
+ // src/tool-parameters.generated.ts
6320
5575
  const FAL_TOOL_PARAMETERS = {
6321
5576
  "ben-v2": [{
6322
5577
  key: "seed",
@@ -8473,8 +7728,7 @@ const FAL_TOOL_PARAMETERS = {
8473
7728
  function falToolParameters(tool) {
8474
7729
  return FAL_TOOL_PARAMETERS[tool] ?? [];
8475
7730
  }
8476
- //#endregion
8477
- //#region src/resolve.ts
7731
+ // src/resolve.ts
8478
7732
  /**
8479
7733
  * Model choice: the one place a Model is chosen, for the CLI and the SDK.
8480
7734
  *
@@ -8509,6 +7763,7 @@ function generationProfile(config) {
8509
7763
  if (config.supportsNegativePrompt === true) capabilities.add("negativePrompt");
8510
7764
  if (config.supportsOutputFormat === true) capabilities.add("outputFormat");
8511
7765
  return {
7766
+ name: config.name,
8512
7767
  aspects: config.supportedAspects,
8513
7768
  capabilities,
8514
7769
  keys: [FAL_KEY],
@@ -8542,6 +7797,7 @@ function toolProfile(tool) {
8542
7797
  const takesReference = "referenceField" in config;
8543
7798
  if (takesReference) capabilities.add("references");
8544
7799
  return {
7800
+ name: config.name,
8545
7801
  capabilities,
8546
7802
  keys: [FAL_KEY],
8547
7803
  maxReferences: takesReference ? 1 : 0
@@ -8683,7 +7939,7 @@ function resolveFixed(task, fixed, request, needed, keys) {
8683
7939
  const { chosenBy, model } = fixed;
8684
7940
  const tier = request.tier ?? "balanced";
8685
7941
  const entries = TASKS[task].models;
8686
- const entry = entries.find((candidate) => candidate.model === model && candidate.mode === request.mode) ?? legacyEntry(task, model, request.mode, tier);
7942
+ const entry = entries.find((candidate) => candidate.model === model && candidate.mode === request.mode);
8687
7943
  const profile = modelProfile(model);
8688
7944
  if (entry === void 0 || profile === void 0) {
8689
7945
  const servesTask = entries.some((candidate) => candidate.model === model);
@@ -8696,14 +7952,6 @@ function resolveFixed(task, fixed, request, needed, keys) {
8696
7952
  unblockers.push(unblockerFor(result));
8697
7953
  return unresolved(task, result.blockedBy, unblockers, `${model} (${chosenBy}) ${describeBlock(result.blockedBy, request, result.missingKey, result.missingInput)}.`, result.missingKey);
8698
7954
  }
8699
- /** Old pins and tuned Looks remain usable without recommending predecessors. */
8700
- function legacyEntry(task, model, mode, tier) {
8701
- if (mode !== void 0 || task !== "generate" && task !== "vary" || !LEGACY_GENERATION_MODELS.some((id) => id === model) || task === "vary" && MODELS[model]?.supportsEdit !== true) return;
8702
- return {
8703
- model,
8704
- tier
8705
- };
8706
- }
8707
7955
  function unknownForMode(task, model, mode, entries) {
8708
7956
  const otherModes = entries.filter((candidate) => candidate.model === model).map((candidate) => candidate.mode ?? "no mode");
8709
7957
  const inMode = [...new Set(entries.filter((candidate) => candidate.mode === mode).map((candidate) => candidate.model))];
@@ -8790,8 +8038,7 @@ function tierChangesChoice(task, request, environment) {
8790
8038
  }
8791
8039
  return chosen.size > 1;
8792
8040
  }
8793
- //#endregion
8794
- //#region src/task-output.ts
8041
+ // src/task-output.ts
8795
8042
  function isPlainRecord(value) {
8796
8043
  return typeof value === "object" && value !== null && !Array.isArray(value);
8797
8044
  }
@@ -8916,8 +8163,7 @@ function labelUrls(urls, labels) {
8916
8163
  return names !== void 0 && names.length === totals.get(key) ? names[occurrence] : void 0;
8917
8164
  });
8918
8165
  }
8919
- //#endregion
8920
- //#region src/task-plan-pricing.ts
8166
+ // src/task-plan-pricing.ts
8921
8167
  /**
8922
8168
  * What a generation request bills and which body keys the caller owns. Both
8923
8169
  * read the body as sent, `params` included, because fal bills that body.
@@ -8996,8 +8242,7 @@ function checkParamsImageSize(config, model, task, params) {
8996
8242
  task
8997
8243
  });
8998
8244
  }
8999
- //#endregion
9000
- //#region src/task-plan-tools.ts
8245
+ // src/task-plan-tools.ts
9001
8246
  /**
9002
8247
  * Request bodies for fal tool Models: map each TaskInput field onto the key
9003
8248
  * the tool's generated parameter list names, refuse what it doesn't list, and
@@ -9346,8 +8591,7 @@ function toolPlan(task, model, input) {
9346
8591
  queued: "queued" in tool
9347
8592
  });
9348
8593
  }
9349
- //#endregion
9350
- //#region src/task-plan-video.ts
8594
+ // src/task-plan-video.ts
9351
8595
  /**
9352
8596
  * Request bodies for video Models: Kling v3 Pro and Kling v3 Turbo Pro, which
9353
8597
  * take the start frame under different keys and bill by the second.
@@ -9428,8 +8672,7 @@ function turboVideoPlan(task, endpoint, input) {
9428
8672
  queued: true
9429
8673
  });
9430
8674
  }
9431
- //#endregion
9432
- //#region src/task-plan.ts
8675
+ // src/task-plan.ts
9433
8676
  /**
9434
8677
  * `plan()` for the Task client: resolve the Model, build the request body the
9435
8678
  * Model's kind needs, and price it. Pure: no I/O.
@@ -9730,8 +8973,7 @@ function upscalerPlan(task, model, input) {
9730
8973
  queued: false
9731
8974
  });
9732
8975
  }
9733
- //#endregion
9734
- //#region src/task-stream.ts
8976
+ // src/task-stream.ts
9735
8977
  const MAX_EVENT_CHARACTERS = 16777216;
9736
8978
  const JSON_START = /^[[{]/;
9737
8979
  function parsePayload(text) {
@@ -10010,8 +9252,7 @@ function createFrames(requestId) {
10010
9252
  function enforceBufferLimit(length, requestId) {
10011
9253
  if (length > MAX_EVENT_CHARACTERS) throw new MotifError("Stream event exceeds the buffer limit.", 0, "STREAM_EVENT_TOO_LARGE", requestId);
10012
9254
  }
10013
- //#endregion
10014
- //#region src/task-client.ts
9255
+ // src/task-client.ts
10015
9256
  /**
10016
9257
  * The Task client: one function per Task. `createMotif()` returns a client
10017
9258
  * whose `generate`, `erase`, `upscale` and the rest resolve the Model through
@@ -10187,7 +9428,6 @@ function createMotif(config = {}) {
10187
9428
  }
10188
9429
  };
10189
9430
  }
10190
- //#endregion
10191
9431
  exports.ACCOUNT_LOCKED = ACCOUNT_LOCKED;
10192
9432
  exports.ASPECT_RATIOS = ASPECT_RATIOS;
10193
9433
  exports.CREATIVE_FIELDS = CREATIVE_FIELDS;