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