@howells/motif-sdk 7.2.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/image.js CHANGED
@@ -4,14 +4,7 @@ import { createFal } from "@ai-sdk/fal";
4
4
  import { createOpenAI } from "@ai-sdk/openai";
5
5
  import { createReplicate } from "@ai-sdk/replicate";
6
6
  // src/errors.ts
7
- /**
8
- * `MotifError` and its coercion helper.
9
- *
10
- * Extracted from `server.ts` so `server-tools.ts` can construct errors without
11
- * importing back into `server.ts` — that import was a dependency cycle. Both
12
- * `server.ts` and `server-tools.ts` import from here, and `server.ts`
13
- * re-exports `MotifError` so the package's public surface is unchanged.
14
- */
7
+ /** `MotifError` and the fal HTTP error mapping. */
15
8
  var MotifError = class extends Error {
16
9
  status;
17
10
  code;
@@ -49,6 +42,16 @@ function falHttpError(status, body, requestId) {
49
42
  if (isFalAccountLocked(status, body)) return new MotifError(`fal account is locked because it is out of credit (fal said: ${body})`, status, ACCOUNT_LOCKED, requestId);
50
43
  return new MotifError(`Request failed: ${status} ${body}`, status, void 0, requestId);
51
44
  }
45
+ // src/fal-parse.ts
46
+ /**
47
+ * Boundary parsing for fal.ai HTTP responses.
48
+ *
49
+ * `Response.json()` yields `any`; these helpers narrow that untyped payload into
50
+ * the SDK's response types with runtime guards instead of unchecked assertions.
51
+ */
52
+ function isRecord(value) {
53
+ return typeof value === "object" && value !== null;
54
+ }
52
55
  // src/models.ts
53
56
  const AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-10-07";
54
57
  const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-to-image", "https://artificialanalysis.ai/image/leaderboard/editing"];
@@ -95,6 +98,8 @@ const MODELS = {
95
98
  maskImageField: "mask_url",
96
99
  supportsMarkupImage: true,
97
100
  autoEditSize: true,
101
+ maxCount: 10,
102
+ singleImageFormats: ["svg"],
98
103
  supportsNumImages: true,
99
104
  supportsOutputFormat: true,
100
105
  supportedOutputFormats: [
@@ -162,6 +167,8 @@ const MODELS = {
162
167
  maskImageField: "mask_url",
163
168
  supportsMarkupImage: true,
164
169
  autoEditSize: true,
170
+ maxCount: 10,
171
+ singleImageFormats: ["svg"],
165
172
  supportsNumImages: true,
166
173
  supportsOutputFormat: true,
167
174
  supportedOutputFormats: [
@@ -233,6 +240,12 @@ const MODELS = {
233
240
  pricePerImageUsd: .133,
234
241
  pricing: "$0.133",
235
242
  sizeMode: "gpt_size",
243
+ supportedAspects: [
244
+ "auto",
245
+ "1:1",
246
+ "3:2",
247
+ "2:3"
248
+ ],
236
249
  supportsAspect: false,
237
250
  supportsBackground: true,
238
251
  streaming: {
@@ -241,6 +254,7 @@ const MODELS = {
241
254
  },
242
255
  supportsEdit: true,
243
256
  supportsMaskImage: true,
257
+ maxCount: 4,
244
258
  supportsNumImages: true,
245
259
  supportsOutputFormat: true,
246
260
  supportsQuality: true,
@@ -282,6 +296,7 @@ const MODELS = {
282
296
  supportsAspect: true,
283
297
  supportsEdit: true,
284
298
  supportsLimitGenerations: true,
299
+ maxCount: 4,
285
300
  supportsNumImages: true,
286
301
  supportsOutputFormat: true,
287
302
  supportsResolution: true,
@@ -327,6 +342,7 @@ const MODELS = {
327
342
  ],
328
343
  supportsAspect: true,
329
344
  supportsEdit: true,
345
+ maxCount: 4,
330
346
  supportsNumImages: true,
331
347
  supportsOutputFormat: true,
332
348
  supportsResolution: false,
@@ -374,6 +390,7 @@ const MODELS = {
374
390
  supportsEdit: true,
375
391
  supportsGoogleSearch: true,
376
392
  supportsLimitGenerations: true,
393
+ maxCount: 4,
377
394
  supportsNumImages: true,
378
395
  supportsOutputFormat: true,
379
396
  supportsResolution: true,
@@ -448,6 +465,7 @@ const MODELS = {
448
465
  supportsEdit: true,
449
466
  supportsGoogleSearch: true,
450
467
  supportsLimitGenerations: true,
468
+ maxCount: 4,
451
469
  supportsNumImages: true,
452
470
  supportsOutputFormat: true,
453
471
  supportsResolution: true,
@@ -494,6 +512,7 @@ const MODELS = {
494
512
  supportsAspect: true,
495
513
  supportsResolution: false,
496
514
  supportsEdit: true,
515
+ maxCount: 6,
497
516
  supportsNumImages: true,
498
517
  supportsSeed: true,
499
518
  supportsSyncMode: true,
@@ -535,6 +554,7 @@ const MODELS = {
535
554
  supportsAspect: true,
536
555
  supportsResolution: false,
537
556
  supportsEdit: true,
557
+ maxCount: 6,
538
558
  supportsNumImages: true,
539
559
  supportsSeed: true,
540
560
  supportsSyncMode: true,
@@ -683,6 +703,7 @@ const MODELS = {
683
703
  supportsAspect: true,
684
704
  supportsEdit: false,
685
705
  supportsGuidanceScale: true,
706
+ maxCount: 4,
686
707
  supportsNumImages: true,
687
708
  supportsOutputFormat: true,
688
709
  supportsResolution: false,
@@ -723,6 +744,7 @@ const MODELS = {
723
744
  supportsAspect: true,
724
745
  supportsResolution: false,
725
746
  supportsEdit: false,
747
+ maxCount: 4,
726
748
  supportsNumImages: true,
727
749
  supportsRenderingSpeed: true,
728
750
  supportsSeed: true,
@@ -768,6 +790,7 @@ const MODELS = {
768
790
  unit: "images",
769
791
  unitPrice: .02
770
792
  },
793
+ lowercaseResolution: true,
771
794
  maxReferenceImages: 3,
772
795
  name: "Grok Imagine Image",
773
796
  pricePerImageUsd: .02,
@@ -785,6 +808,7 @@ const MODELS = {
785
808
  supportedResolutions: ["1K", "2K"],
786
809
  supportsAspect: true,
787
810
  supportsEdit: true,
811
+ maxCount: 4,
788
812
  supportsNumImages: true,
789
813
  supportsOutputFormat: true,
790
814
  supportsResolution: true,
@@ -835,6 +859,7 @@ const MODELS = {
835
859
  supportsAspect: true,
836
860
  supportsEdit: true,
837
861
  supportsNegativePrompt: true,
862
+ maxCount: 6,
838
863
  supportsNumImages: true,
839
864
  supportsOutputFormat: true,
840
865
  supportsResolution: false,
@@ -887,6 +912,7 @@ const MODELS = {
887
912
  ],
888
913
  supportsAspect: true,
889
914
  supportsEdit: true,
915
+ maxCount: 4,
890
916
  supportsNumImages: true,
891
917
  supportsOutputFormat: true,
892
918
  supportsResolution: false,
@@ -916,6 +942,7 @@ const MODELS = {
916
942
  supportsAspect: true,
917
943
  supportsEdit: true,
918
944
  supportsLimitGenerations: true,
945
+ maxCount: 4,
919
946
  supportsNumImages: true,
920
947
  supportsOutputFormat: true,
921
948
  supportsResolution: false,
@@ -940,6 +967,7 @@ const MODELS = {
940
967
  pricePerImageUsd: .06,
941
968
  pricing: "$0.03 TURBO / $0.06 BALANCED / $0.09 QUALITY",
942
969
  sizeMode: "aspect_ratio",
970
+ fixedOutputFormat: "png",
943
971
  supportedAspects: [
944
972
  "16:9",
945
973
  "9:16",
@@ -955,6 +983,7 @@ const MODELS = {
955
983
  supportsEdit: false,
956
984
  supportsExpandPrompt: true,
957
985
  supportsNegativePrompt: true,
986
+ maxCount: 8,
958
987
  supportsNumImages: true,
959
988
  supportsRenderingSpeed: true,
960
989
  supportsResolution: false,
@@ -1013,6 +1042,7 @@ const MODELS = {
1013
1042
  unit: "images",
1014
1043
  unitPrice: .06
1015
1044
  },
1045
+ lowercaseResolution: true,
1016
1046
  maxReferenceImages: 3,
1017
1047
  name: "Grok Imagine Image 2.0",
1018
1048
  pricePerImageUsd: .06,
@@ -1030,6 +1060,7 @@ const MODELS = {
1030
1060
  supportedResolutions: ["1K", "2K"],
1031
1061
  supportsAspect: true,
1032
1062
  supportsEdit: true,
1063
+ maxCount: 10,
1033
1064
  supportsNumImages: true,
1034
1065
  supportsOutputFormat: true,
1035
1066
  supportsResolution: true,
@@ -1072,6 +1103,7 @@ const MODELS = {
1072
1103
  ],
1073
1104
  supportsAspect: true,
1074
1105
  supportsEdit: true,
1106
+ maxCount: 10,
1075
1107
  supportsNumImages: true,
1076
1108
  supportsOutputFormat: true,
1077
1109
  supportsResolution: false,
@@ -1127,6 +1159,7 @@ const MODELS = {
1127
1159
  unit: "images",
1128
1160
  unitPrice: .05
1129
1161
  },
1162
+ lowercaseResolution: true,
1130
1163
  maxReferenceImages: 3,
1131
1164
  name: "Grok Imagine Image Quality",
1132
1165
  pricePerImageUsd: .05,
@@ -1144,6 +1177,7 @@ const MODELS = {
1144
1177
  supportedResolutions: ["1K", "2K"],
1145
1178
  supportsAspect: true,
1146
1179
  supportsEdit: true,
1180
+ maxCount: 4,
1147
1181
  supportsNumImages: true,
1148
1182
  supportsOutputFormat: true,
1149
1183
  supportsResolution: true,
@@ -1152,1708 +1186,87 @@ const MODELS = {
1152
1186
  },
1153
1187
  kling: {
1154
1188
  endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
1189
+ generatesAudio: true,
1155
1190
  name: "Kling v3 Pro",
1191
+ outputKeys: ["video"],
1192
+ price: {
1193
+ audioUsd: .168,
1194
+ kind: "output-second",
1195
+ usd: .112
1196
+ },
1156
1197
  pricing: "$0.11/sec",
1157
1198
  sizeMode: "none",
1199
+ startFrameField: "start_image_url",
1158
1200
  supportsAspect: false,
1159
1201
  supportsEdit: false,
1160
1202
  supportsNumImages: false,
1161
1203
  supportsResolution: false,
1162
- type: "video"
1204
+ type: "video",
1205
+ useQueue: true
1163
1206
  },
1164
1207
  "kling-turbo": {
1165
1208
  endpoint: "fal-ai/kling-video/v3/turbo/pro/image-to-video",
1166
1209
  name: "Kling v3 Turbo Pro",
1210
+ outputKeys: ["video"],
1211
+ price: {
1212
+ kind: "output-second",
1213
+ usd: .14
1214
+ },
1167
1215
  pricing: "$0.14/sec",
1168
1216
  sizeMode: "none",
1217
+ startFrameField: "image_url",
1169
1218
  supportsAspect: false,
1170
1219
  supportsEdit: false,
1171
1220
  supportsNumImages: false,
1172
1221
  supportsResolution: false,
1173
- type: "video"
1222
+ type: "video",
1223
+ useQueue: true
1174
1224
  },
1175
1225
  clarity: {
1226
+ defaultScale: 2,
1176
1227
  endpoint: "fal-ai/clarity-upscaler",
1177
1228
  name: "Clarity Upscaler",
1229
+ outputKeys: ["image"],
1230
+ price: {
1231
+ kind: "megapixel",
1232
+ usd: .03
1233
+ },
1178
1234
  pricing: "$0.03/MP",
1235
+ scaleField: "upscale_factor",
1236
+ scaleRange: {
1237
+ max: 4,
1238
+ min: 1
1239
+ },
1179
1240
  supportsAspect: false,
1180
1241
  supportsEdit: false,
1181
1242
  supportsNumImages: false,
1243
+ supportsPrompt: true,
1182
1244
  supportsResolution: false,
1183
1245
  type: "utility"
1184
1246
  },
1185
1247
  crystal: {
1248
+ defaultScale: 2,
1186
1249
  endpoint: "clarityai/crystal-upscaler",
1187
1250
  name: "Crystal Upscaler",
1188
- pricing: "$0.02",
1189
- supportsAspect: false,
1190
- supportsEdit: false,
1191
- supportsNumImages: false,
1192
- supportsResolution: false,
1193
- type: "utility"
1194
- }
1195
- };
1196
- [
1197
- "sunburst",
1198
- "flare",
1199
- "gpt",
1200
- "banana21",
1201
- "banana2",
1202
- "banana",
1203
- "mai-image-2.5",
1204
- "mai-image-2.5-pro",
1205
- "seedream5",
1206
- "qwen3",
1207
- "banana2-lite",
1208
- "grok-image-2",
1209
- "grok-image-quality",
1210
- "grok-image",
1211
- "muse-image",
1212
- "krea3",
1213
- "flux2-flex",
1214
- "flux2-pro",
1215
- "flux2-turbo",
1216
- "seedream5-lite",
1217
- "ideogram4",
1218
- "ideogram3-transparent",
1219
- "recraft41"
1220
- ].filter((id) => MODELS[id]?.supportsEdit === true);
1221
- // src/capabilities.ts
1222
- /** Generation options a model can refuse, with the registry test for each. */
1223
- const OPTION_CAPABILITIES = {
1224
- aspect: (config) => (config.sizeMode ?? "aspect_ratio") !== "none",
1225
- background: (config) => config.supportsBackground === true,
1226
- enableGoogleSearch: (config) => config.supportsGoogleSearch === true,
1227
- enableSafetyChecker: (config) => config.supportsSafetyChecker === true,
1228
- enableWebSearch: (config) => config.supportsWebSearch === true,
1229
- expandPrompt: (config) => config.supportsExpandPrompt === true,
1230
- guidanceScale: (config) => config.supportsGuidanceScale === true,
1231
- "image editing": (config) => config.supportsEdit,
1232
- imageSize: (config) => config.sizeMode === "gpt_size" || config.sizeMode === "image_size_enum",
1233
- inputFidelity: (config) => config.sizeMode === "gpt_size",
1234
- limitGenerations: (config) => config.supportsLimitGenerations === true,
1235
- markupImageUrl: (config) => config.supportsMarkupImage === true,
1236
- maskImageUrl: (config) => config.supportsMaskImage === true,
1237
- negativePrompt: (config) => config.supportsNegativePrompt === true,
1238
- numImages: (config) => config.supportsNumImages,
1239
- numInferenceSteps: (config) => config.supportsInferenceSteps === true,
1240
- outputFormat: (config) => config.supportsOutputFormat === true,
1241
- quality: (config) => config.supportsQuality === true,
1242
- renderingSpeed: (config) => config.supportsRenderingSpeed === true,
1243
- resolution: (config) => config.supportsResolution,
1244
- safetyTolerance: (config) => config.supportsSafetyTolerance === true,
1245
- seed: (config) => config.supportsSeed === true,
1246
- syncMode: (config) => config.supportsSyncMode === true,
1247
- thinkingLevel: (config) => config.supportsThinkingLevel === true,
1248
- "transparent output": (config) => config.supportsBackground === true || config.transparentOutput === true
1249
- };
1250
- function isModelOption(key) {
1251
- return Object.hasOwn(OPTION_CAPABILITIES, key);
1252
- }
1253
- Object.keys(OPTION_CAPABILITIES).filter(isModelOption);
1254
- ({
1255
- look: [
1256
- {
1257
- acceptsMood: true,
1258
- aspect: "1:1",
1259
- clause: "Editorial photograph in the register of Atelier Ellis, Aman hotels, Kinfolk magazine and Aesop, pigment-rich mineral colour on named matte surfaces, quiet composition with generous negative space, shallow depth of field, shot on film with fine grain, restrained and materially rich. No text, no logos, no people",
1260
- description: "Quiet, materially rich editorial photography for brand and mood imagery.",
1261
- id: "editorial",
1262
- label: "Quiet editorial",
1263
- model: "flux2-pro"
1264
- },
1265
- {
1266
- acceptsMood: true,
1267
- aspect: "1:1",
1268
- clause: "Editorial still life in the register of Aesop and Kinfolk, on a warm bone plaster ground, chalky unglazed surfaces in muted mineral colour, a long soft shadow, generous empty space, shot on film with fine grain, restrained and materially rich. No text, no logos, no people",
1269
- description: "Objects and products on a plaster ground, for product and editorial still life.",
1270
- id: "still-life",
1271
- label: "Editorial still life",
1272
- model: "flux2-pro"
1273
- },
1274
- {
1275
- acceptsMood: true,
1276
- aspect: "3:2",
1277
- clause: "Interior photograph in the register of House & Garden and Kinfolk, shot square-on at eye level on a 35mm lens, warm off-white plaster, wide oak floorboards, linen, brass and a little pattern, light, bright and layered, collected rather than styled, lived-in rather than showroom-perfect, soft natural daylight, shot on film with fine grain. No text, no logos, no people",
1278
- description: "Bright, collected rooms that feel lived in, for interior scenes.",
1279
- id: "interior",
1280
- label: "Interior",
1281
- model: "flux2-pro"
1282
- },
1283
- {
1284
- acceptsMood: true,
1285
- aspect: "4:5",
1286
- clause: "Architectural photograph in the register of House & Garden and Kinfolk, a considered house seen from outside at editorial distance with its garden and setting, pale render, stone or timber meeting precise detailing, clipped planting, soft warm daylight and long shadow, generous negative space, immaculate and calm, shot on film with fine grain. No text, no logos, no people",
1287
- description: "Buildings and their settings from outside, for architecture, property and place.",
1288
- id: "architectural",
1289
- label: "Architectural exterior",
1290
- model: "banana"
1291
- },
1292
- {
1293
- acceptsMood: true,
1294
- aspect: "1:1",
1295
- clause: "Editorial documentary portrait in the register of Kinfolk, muted warm palette, waist-up and unposed against a plain plaster or linen ground, plain clothing with no logos, soft natural light, shot on film with fine grain. No text",
1296
- description: "Natural, unposed documentary portraits of people. Pair with a mood for the light.",
1297
- id: "portrait",
1298
- label: "Documentary portrait",
1299
- model: "seedream5"
1300
- },
1301
- {
1302
- acceptsMood: false,
1303
- aspect: "1:1",
1304
- clause: "A single matte object centred with generous empty space, soft diffused studio light, minimal and quiet in the register of Aesop, one committed muted mineral colour on a plain ground. No text, no logos, no people",
1305
- description: "One object in one colour on a clean ground, for icons and simple product shots.",
1306
- id: "object",
1307
- label: "Studio object",
1308
- model: "flux2-pro"
1309
- },
1310
- {
1311
- acceptsMood: false,
1312
- aspect: "1:1",
1313
- clause: "Straight-on orthographic photograph of the surface filling the entire frame edge to edge, even shadowless studio light, crisp macro texture, colour-accurate and quietly material. No text, no logos",
1314
- description: "Flat, edge-to-edge surface photographs, for textures, backgrounds and material swatches.",
1315
- id: "surface",
1316
- label: "Flat surface",
1317
- model: "flux2-pro"
1318
- },
1319
- {
1320
- acceptsMood: false,
1321
- aspect: "3:2",
1322
- clause: "Painted abstraction filling the frame edge to edge, mineral pigment and chalk gesso on coarse natural linen, two or three confident gestures, warm ivory, oatmeal, putty and soft charcoal, flat diffuse reproduction light. No text",
1323
- description: "Painted abstraction edge to edge, for wall art, heroes and calm backgrounds.",
1324
- id: "abstract",
1325
- label: "Painted abstract",
1326
- model: "banana"
1327
- },
1328
- {
1329
- acceptsMood: false,
1330
- aspect: "1:1",
1331
- clause: "Stylised editorial illustration in the register of Kinfolk, colour laid as flat planes in a warm muted palette of ivory, putty, sage and charcoal, fine hand-drawn line with a gentle gouache wash, generous empty space, clearly a drawing rather than a photograph. No text, no logos",
1332
- description: "Line and gouache illustration of any subject, for drawn editorial imagery.",
1333
- experimental: true,
1334
- id: "illustration",
1335
- label: "Editorial illustration",
1336
- model: "flare"
1337
- }
1338
- ],
1339
- mood: [
1340
- {
1341
- clause: "soft natural daylight from a window out of frame, gentle falloff into the corners, low contrast, even diffused light with soft, held highlights",
1342
- description: "Soft, even daylight from a window. The safe default.",
1343
- id: "window",
1344
- label: "Window light"
1345
- },
1346
- {
1347
- clause: "early morning light through tall glazing, cool and clear",
1348
- description: "Cool, clear early morning light.",
1349
- id: "dawn",
1350
- label: "Dawn"
1351
- },
1352
- {
1353
- clause: "low raking daylight from the left, long soft shadows that reveal texture",
1354
- description: "Low side light that brings out surface texture.",
1355
- id: "raking",
1356
- label: "Raking light"
1357
- },
1358
- {
1359
- clause: "overcast afternoon with rain on a tall window, soft even grey light",
1360
- description: "Soft grey light on a rainy afternoon.",
1361
- id: "overcast",
1362
- label: "Overcast"
1363
- },
1364
- {
1365
- clause: "evening, warm practical lamps around 2400K, candles and a lit fire, cosy and warm, never gloomy",
1366
- description: "Warm evening light from lamps, candles and a fire.",
1367
- id: "lamplit",
1368
- label: "Lamplit evening"
1369
- },
1370
- {
1371
- clause: "night, one warm low practical light, deep shadow, candlelit",
1372
- description: "Dark night scene lit by one warm low light.",
1373
- id: "nocturne",
1374
- label: "Nocturne"
1375
- }
1376
- ]
1377
- }).look;
1378
- // src/tool-registry/analysis.ts
1379
- const ANALYSIS_TOOLS = {
1380
- "got-ocr": {
1381
- category: "analysis",
1382
- description: "Transcribe text from one or more images, optionally as formatted multi-page output.",
1383
- endpoint: "fal-ai/got-ocr/v2",
1384
- inputField: "input_image_urls",
1385
- inputKind: "images",
1386
- name: "GOT-OCR 2.0",
1387
- outputKeys: ["outputs"],
1388
- price: {
1389
- kind: "call",
1390
- per: ["input_image_urls"],
1391
- usd: .05
1392
- },
1393
- pricing: "$0.05/image",
1394
- queued: true,
1395
- sourceUrl: "https://fal.ai/models/fal-ai/got-ocr/v2",
1396
- task: "optical character recognition"
1397
- },
1398
- "moondream-caption": {
1399
- category: "analysis",
1400
- description: "Write a short, normal, or long caption for an image.",
1401
- endpoint: "fal-ai/moondream3-preview/caption",
1402
- inputField: "image_url",
1403
- inputKind: "image",
1404
- name: "Moondream 3 Caption",
1405
- outputKeys: ["output"],
1406
- price: {
1407
- inputPerMillion: .4,
1408
- inputTokens: 737,
1409
- kind: "token",
1410
- outputPerMillion: 3.5,
1411
- outputTokens: 200
1412
- },
1413
- pricing: "$0.40/M input tokens, $3.50/M output tokens",
1414
- sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/caption",
1415
- task: "image captioning"
1416
- },
1417
- "moondream-detect": {
1418
- category: "analysis",
1419
- description: "Detect prompted objects and return their bounding boxes, with an optional preview.",
1420
- endpoint: "fal-ai/moondream3-preview/detect",
1421
- inputField: "image_url",
1422
- inputKind: "image",
1423
- name: "Moondream 3 Detect",
1424
- outputKeys: ["objects", "image"],
1425
- price: {
1426
- inputPerMillion: .4,
1427
- inputTokens: 737,
1428
- kind: "token",
1429
- outputPerMillion: 3.5,
1430
- outputTokens: 100
1431
- },
1432
- pricing: "$0.40/M input tokens, $3.50/M output tokens",
1433
- sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/detect",
1434
- task: "object detection"
1435
- },
1436
- "moondream-point": {
1437
- category: "analysis",
1438
- description: "Return point coordinates for every instance of a prompted subject.",
1439
- endpoint: "fal-ai/moondream3-preview/point",
1440
- inputField: "image_url",
1441
- inputKind: "image",
1442
- name: "Moondream 3 Point",
1443
- outputKeys: ["points", "image"],
1444
- price: {
1445
- inputPerMillion: .4,
1446
- inputTokens: 737,
1447
- kind: "token",
1448
- outputPerMillion: 3.5,
1449
- outputTokens: 100
1450
- },
1451
- pricing: "$0.40/M input tokens, $3.50/M output tokens",
1452
- sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/point",
1453
- task: "object pointing"
1454
- },
1455
- "moondream-query": {
1456
- category: "analysis",
1457
- description: "Answer a free-form question about an image, with reasoning.",
1458
- endpoint: "fal-ai/moondream3-preview/query",
1459
- inputField: "image_url",
1460
- inputKind: "image",
1461
- name: "Moondream 3 Query",
1462
- outputKeys: ["output", "reasoning"],
1463
- price: {
1464
- inputPerMillion: .4,
1465
- inputTokens: 737,
1466
- kind: "token",
1467
- outputPerMillion: 3.5,
1468
- outputTokens: 500
1469
- },
1470
- pricing: "$0.40/M input tokens, $3.50/M output tokens",
1471
- sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/query",
1472
- task: "visual question answering"
1473
- },
1474
- nsfw: {
1475
- category: "moderation",
1476
- description: "Predict whether one or more images contain NSFW concepts.",
1477
- endpoint: "fal-ai/x-ailab/nsfw",
1478
- inputField: "image_urls",
1479
- inputKind: "images",
1480
- name: "NSFW Checker",
1481
- outputKeys: ["has_nsfw_concepts"],
1482
- price: {
1483
- kind: "call",
1484
- per: ["image_urls"],
1485
- usd: .001
1486
- },
1487
- pricing: "$0.001/image",
1488
- sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
1489
- task: "vision moderation"
1490
- }
1491
- };
1492
- // src/tool-registry/assets.ts
1493
- const ASSET_TOOLS = {
1494
- "hunyuan3d-v3": {
1495
- category: "3d",
1496
- description: "Reconstruct a textured 3D mesh from a single image, with optional multi-view inputs.",
1497
- endpoint: "fal-ai/hunyuan3d-v3/image-to-3d",
1498
- inputField: "input_image_url",
1499
- inputKind: "image",
1500
- name: "Hunyuan3D v3 Image to 3D",
1501
- outputKeys: [
1502
- "model_glb",
1503
- "model_urls",
1504
- "thumbnail"
1505
- ],
1506
- price: {
1507
- kind: "call",
1508
- usd: .375
1509
- },
1510
- pricing: "$0.375/generation at the default Normal type; $0.45 low poly, $0.225 geometry only, and $0.15 each for PBR, multi-view, or a custom face count",
1511
- queued: true,
1512
- sourceUrl: "https://fal.ai/models/fal-ai/hunyuan3d-v3/image-to-3d",
1513
- task: "single-image 3D reconstruction"
1514
- },
1515
- "ideogram-layerize-text": {
1516
- category: "layers",
1517
- description: "Split an image into background image layers plus editable HTML text containers.",
1518
- endpoint: "fal-ai/ideogram/v3/layerize-text",
1519
- inputField: "image_url",
1520
- inputKind: "image",
1521
- name: "Ideogram v3 Text Layerize",
1522
- outputKeys: [
1523
- "image_layers",
1524
- "image",
1525
- "text_html",
1526
- "text_containers"
1527
- ],
1528
- price: {
1529
- kind: "call",
1530
- usd: .09
1531
- },
1532
- pricing: "$0.09/image",
1533
- queued: true,
1534
- sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/layerize-text",
1535
- task: "text and image layer extraction"
1536
- },
1537
- "ideogram-tiling": {
1538
- category: "material",
1539
- description: "Generate a seamlessly tiling texture, optionally conditioned on a source image.",
1540
- endpoint: "ideogram/v4/tiling",
1541
- inputField: "image_url",
1542
- inputKind: "image",
1543
- name: "Ideogram v4 Tiling",
1544
- outputKeys: ["images"],
1545
- price: {
1546
- kind: "megapixel",
1547
- usd: .06
1548
- },
1549
- pricing: "$0.06/megapixel at the default BALANCED speed; $0.03 turbo, $0.10 quality",
1550
- sourceUrl: "https://fal.ai/models/ideogram/v4/tiling",
1551
- task: "seamless texture generation"
1552
- },
1553
- image2svg: {
1554
- category: "vector",
1555
- description: "Trace a raster image into layered SVG paths.",
1556
- endpoint: "fal-ai/image2svg",
1557
- inputField: "image_url",
1558
- inputKind: "image",
1559
- name: "Image to SVG",
1560
- outputKeys: ["images"],
1561
- price: {
1562
- kind: "call",
1563
- usd: .005
1564
- },
1565
- pricing: "$0.005/image",
1566
- sourceUrl: "https://fal.ai/models/fal-ai/image2svg",
1567
- task: "raster to vector tracing"
1568
- },
1569
- "meshy-v7": {
1570
- category: "3d",
1571
- description: "Make a textured 3D mesh from one image, optionally rigged for animation.",
1572
- endpoint: "meshy/v7/image-to-3d",
1573
- inputField: "image_url",
1574
- inputKind: "image",
1575
- name: "Meshy v7 Image to 3D",
1576
- outputKeys: [
1577
- "model_glb",
1578
- "model_urls",
1579
- "thumbnail",
1580
- "rigged_character_glb",
1581
- "rigged_character_fbx"
1582
- ],
1583
- price: {
1584
- extras: {
1585
- enable_animation: .12,
1586
- enable_rigging: .2,
1587
- ultra_mode: .2
1588
- },
1589
- kind: "call",
1590
- usd: 1.2
1591
- },
1592
- pricing: "$1.20/generation at the default textured model; $0.80 untextured, $1.40 ultra, plus $0.20 for rigging and $0.12 for animation",
1593
- queued: true,
1594
- sourceUrl: "https://fal.ai/models/meshy/v7/image-to-3d",
1595
- task: "single-image 3D reconstruction"
1596
- },
1597
- patina: {
1598
- category: "material",
1599
- description: "Decompose a surface photograph into PBR maps: basecolor, normal, roughness, metalness, height.",
1600
- endpoint: "fal-ai/patina",
1601
- inputField: "image_url",
1602
- inputKind: "image",
1603
- name: "Patina PBR Maps",
1604
- outputKeys: ["images"],
1605
- outputLabels: { images: {
1606
- fallback: [
1607
- "basecolor",
1608
- "normal",
1609
- "roughness",
1610
- "metalness",
1611
- "height"
1612
- ],
1613
- fromOption: "maps"
1614
- } },
1615
- price: {
1616
- base: .01,
1617
- kind: "maps",
1618
- perMapMegapixel: .01,
1619
- perMegapixel: 0
1620
- },
1621
- pricing: "$0.01 base plus $0.01/megapixel per output map, so all 5 maps on a 1MP image cost $0.06; the listed rate is per map",
1622
- queued: true,
1623
- sourceUrl: "https://fal.ai/models/fal-ai/patina",
1624
- task: "PBR material decomposition"
1625
- },
1626
- "patina-extract": {
1627
- category: "material",
1628
- description: "Extract a tiling PBR material from a prompted region of a photograph.",
1629
- endpoint: "fal-ai/patina/material/extract",
1630
- inputField: "image_url",
1631
- inputKind: "image",
1632
- name: "Patina Material Extract",
1633
- outputKeys: ["images"],
1634
- outputLabels: { images: {
1635
- fallback: [
1636
- "basecolor",
1637
- "normal",
1638
- "roughness",
1639
- "metalness",
1640
- "height"
1641
- ],
1642
- fromOption: "maps"
1643
- } },
1644
- price: {
1645
- base: .1,
1646
- kind: "maps",
1647
- perMapMegapixel: .01,
1648
- perMegapixel: .02,
1649
- upscale: {
1650
- "2": .004,
1651
- "4": .016
1652
- }
1653
- },
1654
- pricing: "$0.10 plus $0.02/megapixel plus $0.01/megapixel per map, so 1MP with all 5 maps is $0.17; upscaling adds $0.004 (2x) or $0.016 (4x) per pre-upscale megapixel per map",
1655
- queued: true,
1656
- sourceUrl: "https://fal.ai/models/fal-ai/patina/material/extract",
1657
- task: "tiling material extraction"
1658
- },
1659
- "qwen-layered": {
1660
- category: "layers",
1661
- description: "Split an image into a configurable number of stacked RGBA layers.",
1662
- endpoint: "fal-ai/qwen-image-layered",
1663
- inputField: "image_url",
1664
- inputKind: "image",
1665
- name: "Qwen Image Layered",
1666
- outputKeys: ["images"],
1667
- price: {
1668
- kind: "call",
1669
- usd: .05
1670
- },
1671
- pricing: "$0.05 per image, one input image a call",
1672
- queued: true,
1673
- sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-layered",
1674
- task: "image layer decomposition"
1675
- },
1676
- "recraft-vectorize": {
1677
- category: "vector",
1678
- description: "Convert a raster image into a clean SVG.",
1679
- endpoint: "fal-ai/recraft/vectorize",
1680
- inputField: "image_url",
1681
- inputKind: "image",
1682
- name: "Recraft Vectorize",
1683
- outputKeys: ["image"],
1684
- price: {
1685
- kind: "call",
1686
- usd: .04
1687
- },
1688
- pricing: "$0.04/image, $0.08 with a vector style",
1689
- sourceUrl: "https://fal.ai/models/fal-ai/recraft/vectorize",
1690
- task: "raster to vector conversion"
1691
- },
1692
- "sam3-3d-align": {
1693
- category: "3d",
1694
- description: "Align SAM 3D objects and bodies into a shared scene.",
1695
- endpoint: "fal-ai/sam-3/3d-align",
1696
- inputField: "image_url",
1697
- inputKind: "image",
1698
- name: "SAM 3D Align",
1699
- outputKeys: [
1700
- "scene_glb",
1701
- "model_glb",
1702
- "visualization",
1703
- "body_mesh_ply",
1704
- "metadata"
1705
- ],
1706
- price: { kind: "metered" },
1707
- pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
1708
- queued: true,
1709
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align",
1710
- task: "3D scene alignment"
1711
- },
1712
- "sam3-3d-body": {
1713
- category: "3d",
1714
- defaultOptions: {
1715
- export_meshes: true,
1716
- include_3d_keypoints: true,
1717
- include_mhr_params: true
1718
- },
1719
- description: "Reconstruct human body meshes and keypoints from a single image.",
1720
- endpoint: "fal-ai/sam-3/3d-body",
1721
- inputField: "image_url",
1722
- inputKind: "image",
1723
- name: "SAM 3D Body",
1724
- outputKeys: [
1725
- "model_glb",
1726
- "visualization",
1727
- "meshes",
1728
- "metadata"
1729
- ],
1730
- price: { kind: "metered" },
1731
- pricing: "$0.02 per unit; fal does not define a unit, so no estimate is reported",
1732
- queued: true,
1733
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
1734
- task: "single-image 3D body reconstruction"
1735
- },
1736
- "sam3-3d-objects": {
1737
- category: "3d",
1738
- description: "Reconstruct one or more 3D objects from an image and prompts.",
1739
- endpoint: "fal-ai/sam-3/3d-objects",
1740
- inputField: "image_url",
1741
- inputKind: "image",
1742
- name: "SAM 3D Objects",
1743
- outputKeys: [
1744
- "gaussian_splat",
1745
- "model_glb",
1746
- "metadata",
1747
- "individual_splats",
1748
- "individual_glbs",
1749
- "artifacts_zip"
1750
- ],
1751
- price: { kind: "metered" },
1752
- pricing: "$0.02 per unit; fal does not define a unit, and this endpoint returns one mesh per detected object, so no estimate is reported",
1753
- queued: true,
1754
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
1755
- task: "single-image 3D object reconstruction"
1756
- },
1757
- "seedream-layerize": {
1758
- category: "layers",
1759
- description: "Split an image into editable Seedream layers plus flattened previews.",
1760
- endpoint: "bytedance/seedream/v5/pro/layerize",
1761
- inputField: "image_url",
1762
- inputKind: "image",
1763
- name: "Seedream v5 Pro Layerize",
1764
- outputKeys: ["layers", "images"],
1765
- outputLabels: { layers: { fromItem: {
1766
- nameField: "name",
1767
- orderField: "z_index"
1768
- } } },
1769
- price: { kind: "metered" },
1770
- pricing: "$0.03375 per generated layer below 1536x1536 total area, $0.0675 per layer above; the listed rate is per layer, not per call",
1771
- queued: true,
1772
- sourceUrl: "https://fal.ai/models/bytedance/seedream/v5/pro/layerize",
1773
- task: "image layer decomposition"
1774
- },
1775
- "seedvr-seamless": {
1776
- category: "material",
1777
- description: "Upscale a tiling texture while keeping its edges seamless. ",
1778
- endpoint: "fal-ai/seedvr/upscale/image/seamless",
1779
- inputField: "image_url",
1780
- inputKind: "image",
1781
- name: "SeedVR Seamless Upscale",
1782
- outputKeys: ["image"],
1783
- price: {
1784
- kind: "megapixel",
1785
- usd: .0025
1786
- },
1787
- pricing: "$0.0025/megapixel",
1788
- queued: true,
1789
- sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image/seamless",
1790
- task: "seamless texture upscaling"
1791
- },
1792
- "trellis-2": {
1793
- category: "3d",
1794
- description: "Reconstruct a textured 3D mesh from a single image, with UV unwrapping.",
1795
- endpoint: "fal-ai/trellis-2",
1796
- inputField: "image_url",
1797
- inputKind: "image",
1798
- name: "TRELLIS 2",
1799
- outputKeys: ["model_glb"],
1800
- price: {
1801
- kind: "call",
1802
- usd: .3
1803
- },
1804
- pricing: "$0.30 at the default 1024p; $0.25 at 512p, $0.35 at 1536p",
1805
- queued: true,
1806
- sourceUrl: "https://fal.ai/models/fal-ai/trellis-2",
1807
- task: "single-image 3D reconstruction"
1808
- }
1809
- };
1810
- // src/tool-registry/background.ts
1811
- const BACKGROUND_TOOLS = {
1812
- "ben-v2": {
1813
- category: "background",
1814
- description: "Fast, high-quality background removal for images.",
1815
- endpoint: "fal-ai/ben/v2/image",
1816
- inputField: "image_url",
1817
- inputKind: "image",
1818
- name: "BEN v2 Background Removal",
1819
- outputKeys: ["image"],
1820
- price: {
1821
- kind: "megapixel",
1822
- usd: .025
1823
- },
1824
- pricing: "$0.025/megapixel",
1825
- sourceUrl: "https://fal.ai/models/fal-ai/ben/v2/image",
1826
- task: "image background removal"
1827
- },
1828
- birefnet: {
1829
- category: "background",
1830
- defaultOptions: {
1831
- model: "General Use (Light)",
1832
- operating_resolution: "1024x1024",
1833
- output_format: "png",
1834
- refine_foreground: true
1835
- },
1836
- description: "High-resolution dichotomous image segmentation and background removal.",
1837
- endpoint: "fal-ai/birefnet/v2",
1838
- inputField: "image_url",
1839
- inputKind: "image",
1840
- name: "BirefNet Background Removal",
1841
- outputKeys: ["image", "mask_image"],
1842
- price: {
1843
- kind: "call",
1844
- usd: 0
1845
- },
1846
- pricing: "$0/compute-second listed by fal",
1847
- sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
1848
- task: "image background removal"
1849
- },
1850
- "bria-rmbg": {
1851
- category: "background",
1852
- description: "Commercial-safe background removal for images.",
1853
- endpoint: "fal-ai/bria/background/remove",
1854
- inputField: "image_url",
1855
- inputKind: "image",
1856
- name: "Bria RMBG 2.0",
1857
- outputKeys: ["image"],
1858
- price: {
1859
- kind: "call",
1860
- usd: .018
1861
- },
1862
- pricing: "$0.018/generation",
1863
- sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove",
1864
- task: "image background removal"
1865
- },
1866
- "bria-video-rmbg": {
1867
- category: "background",
1868
- defaultOptions: {
1869
- background_color: "Black",
1870
- output_container_and_codec: "webm_vp9",
1871
- preserve_audio: true
1872
- },
1873
- description: "Remove video backgrounds with configurable output container.",
1874
- endpoint: "bria/video/background-removal",
1875
- inputField: "video_url",
1876
- inputKind: "video",
1877
- name: "Bria Video Background Removal",
1878
- outputKeys: ["video"],
1879
- price: {
1880
- kind: "second",
1881
- usd: .14
1882
- },
1883
- pricing: "$0.14/sec",
1884
- queued: true,
1885
- sourceUrl: "https://fal.ai/models/bria/video/background-removal",
1886
- task: "video background removal"
1887
- },
1888
- "bria-video-rmbg-v3": {
1889
- category: "background",
1890
- defaultOptions: {
1891
- background_color: "Black",
1892
- output_container_and_codec: "webm_vp9",
1893
- preserve_audio: true
1894
- },
1895
- description: "Remove video backgrounds with Bria's VRMBG 3.0, with configurable output container.",
1896
- endpoint: "bria/video/background-removal/v3",
1897
- inputField: "video_url",
1898
- inputKind: "video",
1899
- name: "Bria Video Background Removal 3.0",
1900
- outputKeys: ["video"],
1901
- price: {
1902
- kind: "second",
1903
- usd: .05
1904
- },
1905
- pricing: "$0.05/sec",
1906
- queued: true,
1907
- sourceUrl: "https://fal.ai/models/bria/video/background-removal/v3",
1908
- task: "video background removal"
1909
- },
1910
- "control-light": {
1911
- category: "restoration",
1912
- description: "Brighten a dark or underexposed photo, recovering detail while keeping its identity, geometry and colour.",
1913
- endpoint: "fal-ai/control-light",
1914
- inputField: "image_url",
1915
- inputKind: "image",
1916
- name: "Control Light",
1917
- outputKeys: ["images"],
1918
- price: {
1919
- kind: "megapixel",
1920
- usd: .03
1921
- },
1922
- pricing: "$0.03/megapixel",
1923
- sourceUrl: "https://fal.ai/models/fal-ai/control-light",
1924
- task: "low-light enhancement"
1925
- },
1926
- ddcolor: {
1927
- category: "restoration",
1928
- description: "Colourise black-and-white photographs.",
1929
- endpoint: "fal-ai/ddcolor",
1930
- inputField: "image_url",
1931
- inputKind: "image",
1932
- name: "DDColor Colourisation",
1933
- outputKeys: ["image"],
1934
- price: {
1935
- kind: "megapixel",
1936
- usd: .001
1937
- },
1938
- pricing: "$0.001/megapixel",
1939
- sourceUrl: "https://fal.ai/models/fal-ai/ddcolor",
1940
- task: "photo colourisation"
1941
- },
1942
- rembg: {
1943
- category: "background",
1944
- defaultOptions: { crop_to_bbox: false },
1945
- description: "Generic image background removal utility.",
1946
- endpoint: "fal-ai/imageutils/rembg",
1947
- inputField: "image_url",
1948
- inputKind: "image",
1949
- name: "Remove Background",
1950
- outputKeys: ["image"],
1951
- price: {
1952
- kind: "call",
1953
- usd: 0
1954
- },
1955
- pricing: "$0/compute-second listed by fal",
1956
- sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
1957
- task: "image background removal"
1958
- },
1959
- "seedvr-upscale": {
1960
- category: "restoration",
1961
- defaultOptions: { output_format: "png" },
1962
- description: "Diffusion image restoration and upscaling with SeedVR.",
1963
- endpoint: "fal-ai/seedvr/upscale/image",
1964
- inputField: "image_url",
1965
- inputKind: "image",
1966
- name: "SeedVR Image Upscale",
1967
- outputKeys: ["image"],
1968
- price: {
1969
- kind: "megapixel",
1970
- usd: .001
1971
- },
1972
- pricing: "$0.001/megapixel",
1973
- queued: true,
1974
- sourceUrl: "https://fal.ai/models/fal-ai/seedvr/upscale/image",
1975
- task: "image restoration and upscaling"
1976
- },
1977
- "topaz-adjust": {
1978
- category: "restoration",
1979
- defaultOptions: { output_format: "png" },
1980
- description: "Topaz exposure, white balance, and colour adjustment.",
1981
- endpoint: "topaz/adjust/image",
1982
- inputField: "image_url",
1983
- inputKind: "image",
1984
- name: "Topaz Adjust",
1985
- outputKeys: ["image"],
1986
- price: {
1987
- kind: "megapixel-step",
1988
- megapixels: 24,
1989
- usd: .08
1990
- },
1991
- pricing: "$0.08 per 24 output megapixels",
1992
- queued: true,
1993
- sourceUrl: "https://fal.ai/models/topaz/adjust/image",
1994
- task: "tonal adjustment"
1995
- },
1996
- "topaz-creative": {
1997
- category: "restoration",
1998
- defaultOptions: { output_format: "png" },
1999
- description: "Topaz Bloom upscaling, which reimagines detail rather than preserving it.",
2000
- endpoint: "topaz/upscale/image/creative",
2001
- inputField: "image_url",
2002
- inputKind: "image",
2003
- name: "Topaz Creative Upscale",
2004
- outputKeys: ["image"],
2005
- price: {
2006
- kind: "megapixel-step",
2007
- megapixels: 2,
2008
- usd: .08
2009
- },
2010
- pricing: "$0.08 per started 2 output megapixels, any Bloom model",
2011
- queued: true,
2012
- sourceUrl: "https://fal.ai/models/topaz/upscale/image/creative",
2013
- task: "creative image upscaling"
2014
- },
2015
- "topaz-denoise": {
2016
- category: "restoration",
2017
- defaultOptions: { output_format: "png" },
2018
- description: "Topaz noise reduction at source resolution.",
2019
- endpoint: "topaz/denoise/image",
2020
- inputField: "image_url",
2021
- inputKind: "image",
2022
- name: "Topaz Denoise",
2023
- outputKeys: ["image"],
2024
- price: {
2025
- kind: "megapixel-step",
2026
- megapixels: 24,
2027
- usd: .08
2028
- },
2029
- pricing: "$0.08 per 24 output megapixels at the default Normal model; $0.16 with Denoise Max",
2030
- queued: true,
2031
- sourceUrl: "https://fal.ai/models/topaz/denoise/image",
2032
- task: "image denoising"
2033
- },
2034
- "topaz-generative": {
2035
- category: "restoration",
2036
- defaultOptions: { output_format: "png" },
2037
- description: "Topaz Wonder upscaling, which synthesises plausible detail as it enlarges.",
2038
- endpoint: "topaz/upscale/image/generative",
2039
- inputField: "image_url",
2040
- inputKind: "image",
2041
- name: "Topaz Generative Upscale",
2042
1251
  outputKeys: ["image"],
2043
- price: {
2044
- kind: "megapixel-step",
2045
- megapixels: 8,
2046
- usd: .08
2047
- },
2048
- pricing: "$0.08 per started 8 output megapixels at the default Wonder 3; per started 4 with Wonder, Wonder 2, Standard MAX, Redefine or Recover 3",
2049
- queued: true,
2050
- sourceUrl: "https://fal.ai/models/topaz/upscale/image/generative",
2051
- task: "generative image upscaling"
2052
- },
2053
- "topaz-image": {
2054
- category: "upscale",
2055
- defaultOptions: {
2056
- model: "Standard V2",
2057
- output_format: "jpeg",
2058
- upscale_factor: 2
2059
- },
2060
- description: "Professional Topaz image enhancement and upscaling.",
2061
- endpoint: "fal-ai/topaz/upscale/image",
2062
- inputField: "image_url",
2063
- inputKind: "image",
2064
- name: "Topaz Image Upscale",
2065
- outputKeys: ["image"],
2066
- price: {
2067
- kind: "megapixel-step",
2068
- megapixels: 24,
2069
- usd: .08
2070
- },
2071
- pricing: "$0.08 for output up to 24MP; $0.16 to 48MP, $0.32 to 96MP, up to $1.36 at 512MP",
2072
- queued: true,
2073
- sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
2074
- task: "image enhancement"
2075
- },
2076
- "topaz-precision": {
2077
- category: "restoration",
2078
- defaultOptions: { output_format: "png" },
2079
- description: "Topaz upscaling that preserves the original detail rather than inventing it.",
2080
- endpoint: "topaz/upscale/image/precision",
2081
- inputField: "image_url",
2082
- inputKind: "image",
2083
- name: "Topaz Precision Upscale",
2084
- outputKeys: ["image"],
2085
- price: {
2086
- kind: "megapixel-step",
2087
- megapixels: 24,
2088
- usd: .08
2089
- },
2090
- pricing: "$0.08 per 24 output megapixels, any precision model",
2091
- queued: true,
2092
- sourceUrl: "https://fal.ai/models/topaz/upscale/image/precision",
2093
- task: "faithful image upscaling"
2094
- },
2095
- "topaz-restore": {
2096
- category: "restoration",
2097
- defaultOptions: { output_format: "png" },
2098
- description: "Topaz restoration of damaged or degraded photographs at source resolution.",
2099
- endpoint: "topaz/restore/image",
2100
- inputField: "image_url",
2101
- inputKind: "image",
2102
- name: "Topaz Restore",
2103
- outputKeys: ["image"],
2104
- price: {
2105
- kind: "megapixel-step",
2106
- megapixels: 24,
2107
- usd: .48
2108
- },
2109
- pricing: "$0.48 per 24 output megapixels at the default Recover 3; $0.08 with Dust-Scratch V2",
2110
- queued: true,
2111
- sourceUrl: "https://fal.ai/models/topaz/restore/image",
2112
- task: "photo restoration"
2113
- },
2114
- "topaz-sharpen": {
2115
- category: "restoration",
2116
- defaultOptions: { output_format: "png" },
2117
- description: "Topaz deblurring and sharpening at source resolution.",
2118
- endpoint: "topaz/sharpen/image",
2119
- inputField: "image_url",
2120
- inputKind: "image",
2121
- name: "Topaz Sharpen",
2122
- outputKeys: ["image"],
2123
- price: {
2124
- kind: "megapixel-step",
2125
- megapixels: 24,
2126
- usd: .08
2127
- },
2128
- pricing: "$0.08 per 24 output megapixels at the default Standard model; $0.16 with Super Focus",
2129
- queued: true,
2130
- sourceUrl: "https://fal.ai/models/topaz/sharpen/image",
2131
- task: "image sharpening"
2132
- },
2133
- "topaz-transparent": {
2134
- category: "restoration",
2135
- description: "Topaz upscaling that preserves the alpha channel end to end.",
2136
- endpoint: "topaz/upscale/image/transparent",
2137
- inputField: "image_url",
2138
- inputKind: "image",
2139
- name: "Topaz Transparent Upscale",
2140
- outputKeys: ["image"],
2141
- price: {
2142
- kind: "megapixel-step",
2143
- megapixels: 24,
2144
- usd: .08
2145
- },
2146
- pricing: "$0.08 per 24 output megapixels",
2147
- queued: true,
2148
- sourceUrl: "https://fal.ai/models/topaz/upscale/image/transparent",
2149
- task: "transparent image upscaling"
2150
- },
2151
- "topaz-video": {
2152
- category: "upscale",
2153
- defaultOptions: {
2154
- model: "Proteus",
2155
- upscale_factor: 2
2156
- },
2157
- description: "Professional Topaz video enhancement and upscaling.",
2158
- endpoint: "fal-ai/topaz/upscale/video",
2159
- inputField: "video_url",
2160
- inputKind: "video",
2161
- name: "Topaz Video Upscale",
2162
- outputKeys: ["video"],
2163
- price: {
2164
- doubleAtFps: 60,
2165
- halfWithModel: "Gaia 2",
2166
- kind: "video-second",
2167
- tiers: [
2168
- {
2169
- upTo: 720,
2170
- usd: .01
2171
- },
2172
- {
2173
- upTo: 1080,
2174
- usd: .02
2175
- },
2176
- { usd: .08 }
2177
- ]
2178
- },
2179
- pricing: "$0.01/sec up to 720p output; $0.02 to 1080p, $0.08 above, doubled at 60fps, halved with Gaia 2",
2180
- queued: true,
2181
- sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
2182
- task: "video enhancement"
2183
- }
2184
- };
2185
- // src/tool-registry/editing.ts
2186
- const EDITING_TOOLS = {
2187
- "bria-eraser": {
2188
- category: "erase",
2189
- description: "Commercial-safe removal of a masked region, inpainted from surrounding context.",
2190
- endpoint: "fal-ai/bria/eraser",
2191
- inputField: "image_url",
2192
- inputKind: "image",
2193
- name: "Bria Eraser",
2194
- outputKeys: ["image"],
2195
- price: {
2196
- kind: "call",
2197
- usd: .04
2198
- },
2199
- pricing: "$0.04/generation",
2200
- sourceUrl: "https://fal.ai/models/fal-ai/bria/eraser",
2201
- task: "masked object erasure"
2202
- },
2203
- "bria-expand": {
2204
- category: "reframe",
2205
- description: "Expand an image onto a larger canvas, generating the new border area.",
2206
- endpoint: "fal-ai/bria/expand",
2207
- inputField: "image_url",
2208
- inputKind: "image",
2209
- name: "Bria Image Expansion",
2210
- outputKeys: ["image"],
2211
- price: {
2212
- kind: "call",
2213
- usd: .04
2214
- },
2215
- pricing: "$0.04/generation",
2216
- sourceUrl: "https://fal.ai/models/fal-ai/bria/expand",
2217
- task: "image outpainting"
2218
- },
2219
- "bria-genfill": {
2220
- category: "erase",
2221
- description: "Generative fill of a masked region from a prompt, commercial-safe.",
2222
- endpoint: "fal-ai/bria/genfill",
2223
- inputField: "image_url",
2224
- inputKind: "image",
2225
- name: "Bria GenFill",
2226
- outputKeys: ["images"],
2227
- price: {
2228
- kind: "call",
2229
- perImage: true,
2230
- usd: .04
2231
- },
2232
- pricing: "$0.04/generation",
2233
- sourceUrl: "https://fal.ai/models/fal-ai/bria/genfill",
2234
- task: "masked generative fill"
2235
- },
2236
- "finegrain-eraser": {
2237
- category: "erase",
2238
- description: "Remove a prompted object and its shadows and reflections, filling the gap.",
2239
- endpoint: "fal-ai/finegrain-eraser",
2240
- inputField: "image_url",
2241
- inputKind: "image",
2242
- name: "Finegrain Object Eraser",
2243
- outputKeys: ["image"],
2244
- price: {
2245
- kind: "call",
2246
- usd: .27
2247
- },
2248
- pricing: "$0.27/image at the default standard mode; $0.18 express, $0.36 premium",
2249
- sourceUrl: "https://fal.ai/models/fal-ai/finegrain-eraser",
2250
- task: "prompted object erasure"
2251
- },
2252
- "flux-outpaint": {
2253
- category: "reframe",
2254
- description: "Outpaint an image by a per-edge pixel expansion with FLUX.2 pro.",
2255
- endpoint: "fal-ai/flux-2-pro/outpaint",
2256
- inputField: "image_url",
2257
- inputKind: "image",
2258
- name: "FLUX.2 Pro Outpaint",
2259
- outputKeys: ["images"],
2260
- price: {
2261
- extra: .015,
2262
- first: .03,
2263
- kind: "megapixel-first"
2264
- },
2265
- pricing: "$0.03 for the first output megapixel, then $0.015 per extra megapixel of input and output, rounded up",
2266
- sourceUrl: "https://fal.ai/models/fal-ai/flux-2-pro/outpaint",
2267
- task: "image outpainting"
2268
- },
2269
- "iclight-v2": {
2270
- category: "relight",
2271
- description: "Relight a subject from a prompt, harmonising it with a new light direction.",
2272
- endpoint: "fal-ai/iclight-v2",
2273
- inputField: "image_url",
2274
- inputKind: "image",
2275
- name: "IC-Light v2",
2276
- outputKeys: ["images"],
2277
- price: {
2278
- kind: "megapixel",
2279
- usd: .1
2280
- },
2281
- pricing: "$0.10/megapixel",
2282
- sourceUrl: "https://fal.ai/models/fal-ai/iclight-v2",
2283
- task: "prompted relighting"
2284
- },
2285
- "ideogram-reframe": {
2286
- category: "reframe",
2287
- description: "Reframe an image to a new aspect ratio, generating the fill.",
2288
- endpoint: "fal-ai/ideogram/v3/reframe",
2289
- inputField: "image_url",
2290
- inputKind: "image",
2291
- name: "Ideogram v3 Reframe",
2292
- outputKeys: ["images"],
2293
- price: {
2294
- kind: "call",
2295
- perImage: true,
2296
- usd: .06
2297
- },
2298
- pricing: "$0.06/image at the default BALANCED speed; $0.03 turbo, $0.09 quality",
2299
- sourceUrl: "https://fal.ai/models/fal-ai/ideogram/v3/reframe",
2300
- task: "aspect ratio reframing"
2301
- },
2302
- "lighting-restoration": {
2303
- category: "relight",
2304
- description: "Restore natural, even lighting across one or more input images.",
2305
- endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
2306
- inputField: "image_urls",
2307
- inputKind: "images",
2308
- name: "Qwen Lighting Restoration",
2309
- outputKeys: ["images"],
2310
- price: {
2311
- kind: "megapixel",
2312
- usd: .035
2313
- },
2314
- pricing: "$0.035/megapixel",
2315
- sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/lighting-restoration",
2316
- task: "lighting restoration"
2317
- },
2318
- "object-removal": {
2319
- category: "erase",
2320
- description: "Remove a prompted object from an image and fill the gap.",
2321
- endpoint: "fal-ai/object-removal",
2322
- inputField: "image_url",
2323
- inputKind: "image",
2324
- name: "Object Removal",
2325
- outputKeys: ["images"],
2326
- price: {
2327
- kind: "call",
2328
- usd: .024
2329
- },
2330
- pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
2331
- sourceUrl: "https://fal.ai/models/fal-ai/object-removal",
2332
- task: "prompted object removal"
2333
- },
2334
- "object-removal-bbox": {
2335
- category: "erase",
2336
- description: "Remove whatever falls inside supplied bounding boxes.",
2337
- endpoint: "fal-ai/object-removal/bbox",
2338
- inputField: "image_url",
2339
- inputKind: "image",
2340
- name: "Object Removal by Box",
2341
- outputKeys: ["images"],
2342
- price: {
2343
- kind: "call",
2344
- usd: .024
2345
- },
2346
- pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
2347
- sourceUrl: "https://fal.ai/models/fal-ai/object-removal/bbox",
2348
- task: "bounding box object removal"
2349
- },
2350
- "object-removal-mask": {
2351
- category: "erase",
2352
- description: "Remove the masked region of an image and fill the gap.",
2353
- endpoint: "fal-ai/object-removal/mask",
2354
- inputField: "image_url",
2355
- inputKind: "image",
2356
- name: "Object Removal by Mask",
2357
- outputKeys: ["images"],
2358
- price: {
2359
- kind: "call",
2360
- usd: .024
2361
- },
2362
- pricing: "$0.024/image at the default best quality; $0.006 low, $0.012 medium, $0.018 high",
2363
- sourceUrl: "https://fal.ai/models/fal-ai/object-removal/mask",
2364
- task: "masked object removal"
2365
- },
2366
- "remove-lighting": {
2367
- category: "relight",
2368
- description: "Strip baked-in lighting and shadows to leave a flat, neutral surface.",
2369
- endpoint: "fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
2370
- inputField: "image_urls",
2371
- inputKind: "images",
2372
- name: "Qwen Remove Lighting",
2373
- outputKeys: ["images"],
2374
- price: {
2375
- kind: "megapixel",
2376
- usd: .035
2377
- },
2378
- pricing: "$0.035/megapixel",
2379
- sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-edit-plus-lora-gallery/remove-lighting",
2380
- task: "lighting removal"
2381
- },
2382
- "smart-resize": {
2383
- category: "reframe",
2384
- description: "Resize one image to several target sizes, recomposing rather than cropping.",
2385
- endpoint: "fal-ai/smart-resize",
2386
- inputField: "image_url",
2387
- inputKind: "image",
2388
- name: "Smart Resize",
2389
- outputKeys: ["images", "results"],
2390
- price: {
2391
- fee: .05,
2392
- kind: "call",
2393
- multipliers: { resolution: { "4K": 2 } },
2394
- per: ["target_sizes", "num_images_per_size"],
2395
- usd: .15
2396
- },
2397
- pricing: "$0.15 per output image, doubled at 4K, plus a $0.05 vision analysis fee per request",
2398
- queued: true,
2399
- sourceUrl: "https://fal.ai/models/fal-ai/smart-resize",
2400
- task: "multi-size recomposition"
2401
- },
2402
- "telestyle-v2": {
2403
- category: "restyle",
2404
- description: "Redraw a content image in the style of a second, style image.",
2405
- endpoint: "fal-ai/telestyle-v2",
2406
- inputField: "content_image_url",
2407
- inputKind: "image",
2408
- name: "TeleStyle v2",
2409
- outputKeys: ["images"],
2410
1252
  price: {
2411
1253
  kind: "megapixel",
2412
- usd: .035
1254
+ usd: .016
2413
1255
  },
2414
- pricing: "$0.035/megapixel",
2415
- referenceField: "style_image_url",
2416
- sourceUrl: "https://fal.ai/models/fal-ai/telestyle-v2",
2417
- task: "style transfer from a reference image"
2418
- },
2419
- "text-removal": {
2420
- category: "erase",
2421
- defaultOptions: { output_format: "png" },
2422
- description: "Remove all rendered text from an image, rebuilding what sat behind it.",
2423
- endpoint: "fal-ai/image-editing/text-removal",
2424
- inputField: "image_url",
2425
- inputKind: "image",
2426
- name: "Text Removal",
2427
- outputKeys: ["images"],
2428
- price: {
2429
- kind: "call",
2430
- usd: .04
1256
+ pricing: "$0.016/MP of output",
1257
+ scaleField: "scale_factor",
1258
+ scaleRange: {
1259
+ max: 200,
1260
+ min: 1
2431
1261
  },
2432
- pricing: "$0.04/image",
2433
- sourceUrl: "https://fal.ai/models/fal-ai/image-editing/text-removal",
2434
- task: "text removal"
2435
- },
2436
- "virtual-try-on": {
2437
- category: "try-on",
2438
- description: "Dress the person in one image in the garment shown in another.",
2439
- endpoint: "google/virtual-try-on",
2440
- inputField: "person_image_url",
2441
- inputKind: "image",
2442
- name: "Google Virtual Try-On",
2443
- outputKeys: ["images"],
2444
- price: {
2445
- kind: "call",
2446
- perImage: true,
2447
- usd: .075
2448
- },
2449
- pricing: "$0.075/image",
2450
- queued: true,
2451
- referenceField: "product_image_url",
2452
- sourceUrl: "https://fal.ai/models/google/virtual-try-on",
2453
- task: "virtual garment try-on"
2454
- }
2455
- };
2456
- // src/tool-registry/structure.ts
2457
- const STRUCTURE_TOOLS = {
2458
- "depth-anything": {
2459
- category: "preprocess",
2460
- description: "Generate Depth Anything v2 depth maps from input images.",
2461
- endpoint: "fal-ai/image-preprocessors/depth-anything/v2",
2462
- inputField: "image_url",
2463
- inputKind: "image",
2464
- name: "Depth Anything v2 Preprocessor",
2465
- outputKeys: ["image"],
2466
- price: {
2467
- kind: "call",
2468
- usd: 0
2469
- },
2470
- pricing: "$0/compute-second listed by fal",
2471
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
2472
- task: "depth preprocessing"
2473
- },
2474
- dwpose: {
2475
- category: "preprocess",
2476
- description: "Extract body, hand, and face pose skeletons from an image.",
2477
- endpoint: "fal-ai/dwpose",
2478
- inputField: "image_url",
2479
- inputKind: "image",
2480
- name: "DWPose Estimation",
2481
- outputKeys: ["image"],
2482
- price: {
2483
- kind: "second",
2484
- usd: 6e-4
2485
- },
2486
- pricing: "$0.0006/compute-second",
2487
- sourceUrl: "https://fal.ai/models/fal-ai/dwpose",
2488
- task: "pose preprocessing"
2489
- },
2490
- hed: {
2491
- category: "preprocess",
2492
- description: "Generate HED soft edge maps for ControlNet-style workflows.",
2493
- endpoint: "fal-ai/image-preprocessors/hed",
2494
- inputField: "image_url",
2495
- inputKind: "image",
2496
- name: "HED Edge Preprocessor",
2497
- outputKeys: ["image"],
2498
- price: {
2499
- kind: "call",
2500
- usd: 0
2501
- },
2502
- pricing: "$0/compute-second listed by fal",
2503
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/hed",
2504
- task: "edge preprocessing"
2505
- },
2506
- lineart: {
2507
- category: "preprocess",
2508
- defaultOptions: { coarse: false },
2509
- description: "Generate line art/control-style edges from an input image.",
2510
- endpoint: "fal-ai/image-preprocessors/lineart",
2511
- inputField: "image_url",
2512
- inputKind: "image",
2513
- name: "Line Art Preprocessor",
2514
- outputKeys: ["image"],
2515
- price: {
2516
- kind: "call",
2517
- usd: 0
2518
- },
2519
- pricing: "$0/compute-second listed by fal",
2520
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
2521
- task: "image preprocessing"
2522
- },
2523
- "marigold-depth": {
2524
- category: "depth",
2525
- defaultOptions: {
2526
- ensemble_size: 10,
2527
- num_inference_steps: 10
2528
- },
2529
- description: "Create depth maps using Marigold depth estimation.",
2530
- endpoint: "fal-ai/imageutils/marigold-depth",
2531
- inputField: "image_url",
2532
- inputKind: "image",
2533
- name: "Marigold Depth Estimation",
2534
- outputKeys: ["image"],
2535
- price: {
2536
- kind: "call",
2537
- usd: 0
2538
- },
2539
- pricing: "$0/compute-second listed by fal",
2540
- sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
2541
- task: "depth map"
2542
- },
2543
- "midas-depth": {
2544
- category: "depth",
2545
- defaultOptions: {
2546
- a: Math.PI * 2,
2547
- bg_th: .1
2548
- },
2549
- description: "Create MiDaS depth maps from input images.",
2550
- endpoint: "fal-ai/imageutils/depth",
2551
- inputField: "image_url",
2552
- inputKind: "image",
2553
- name: "MiDaS Depth Estimation",
2554
- outputKeys: ["image"],
2555
- price: {
2556
- kind: "call",
2557
- usd: 0
2558
- },
2559
- pricing: "$0/compute-second listed by fal",
2560
- sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
2561
- task: "depth map"
2562
- },
2563
- "midas-preprocessor": {
2564
- category: "preprocess",
2565
- description: "Generate MiDaS depth and normal maps for image workflows.",
2566
- endpoint: "fal-ai/image-preprocessors/midas",
2567
- inputField: "image_url",
2568
- inputKind: "image",
2569
- name: "MiDaS Preprocessor",
2570
- outputKeys: ["depth_map", "normal_map"],
2571
- price: {
2572
- kind: "call",
2573
- usd: 0
2574
- },
2575
- pricing: "$0/compute-second listed by fal",
2576
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
2577
- task: "depth and normal preprocessing"
2578
- },
2579
- mlsd: {
2580
- category: "preprocess",
2581
- description: "Detect straight line segments for architectural and interior control maps.",
2582
- endpoint: "fal-ai/image-preprocessors/mlsd",
2583
- inputField: "image_url",
2584
- inputKind: "image",
2585
- name: "M-LSD Line Preprocessor",
2586
- outputKeys: ["image"],
2587
- price: {
2588
- kind: "call",
2589
- usd: 0
2590
- },
2591
- pricing: "$0/compute-second listed by fal",
2592
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/mlsd",
2593
- task: "line segment preprocessing"
2594
- },
2595
- pidi: {
2596
- category: "preprocess",
2597
- description: "Generate PiDiNet soft edge maps from an input image.",
2598
- endpoint: "fal-ai/image-preprocessors/pidi",
2599
- inputField: "image_url",
2600
- inputKind: "image",
2601
- name: "PiDiNet Edge Preprocessor",
2602
- outputKeys: ["image"],
2603
- price: {
2604
- kind: "call",
2605
- usd: 0
2606
- },
2607
- pricing: "$0/compute-second listed by fal",
2608
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/pidi",
2609
- task: "edge preprocessing"
2610
- },
2611
- "sam-preprocessor": {
2612
- category: "preprocess",
2613
- description: "Generate a SAM segmentation map for ControlNet-style workflows.",
2614
- endpoint: "fal-ai/image-preprocessors/sam",
2615
- inputField: "image_url",
2616
- inputKind: "image",
2617
- name: "SAM Preprocessor",
2618
- outputKeys: ["image"],
2619
- price: {
2620
- kind: "call",
2621
- usd: 0
2622
- },
2623
- pricing: "$0/compute-second listed by fal",
2624
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
2625
- task: "segmentation preprocessing"
2626
- },
2627
- "sam2-auto": {
2628
- category: "segmentation",
2629
- defaultOptions: {
2630
- min_mask_region_area: 100,
2631
- output_format: "png",
2632
- points_per_side: 32,
2633
- pred_iou_thresh: .88,
2634
- stability_score_thresh: .95
2635
- },
2636
- description: "Automatically segment an image into combined and individual masks.",
2637
- endpoint: "fal-ai/sam2/auto-segment",
2638
- inputField: "image_url",
2639
- inputKind: "image",
2640
- name: "SAM 2 Auto Segment",
2641
- outputKeys: ["combined_mask", "individual_masks"],
2642
- price: {
2643
- kind: "call",
2644
- usd: 0
2645
- },
2646
- pricing: "$0/compute-second listed by fal",
2647
- queued: true,
2648
- sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
2649
- task: "automatic image segmentation"
2650
- },
2651
- "sam3-1-image": {
2652
- category: "segmentation",
2653
- defaultOptions: {
2654
- apply_mask: true,
2655
- max_masks: 3,
2656
- output_format: "png"
2657
- },
2658
- description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
2659
- endpoint: "fal-ai/sam-3-1/image",
2660
- inputField: "image_url",
2661
- inputKind: "image",
2662
- name: "SAM 3.1 Image",
2663
- outputKeys: [
2664
- "image",
2665
- "masks",
2666
- "metadata",
2667
- "scores",
2668
- "boxes"
2669
- ],
2670
- price: {
2671
- kind: "call",
2672
- usd: .01
2673
- },
2674
- pricing: "$0.01/request",
2675
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
2676
- task: "promptable image segmentation"
2677
- },
2678
- "sam3-1-video": {
2679
- category: "segmentation",
2680
- defaultOptions: {
2681
- apply_mask: true,
2682
- prompt: "person"
2683
- },
2684
- description: "SAM 3.1 video segmentation with Object Multiplex tracking for multiple objects.",
2685
- endpoint: "fal-ai/sam-3-1/video",
2686
- inputField: "video_url",
2687
- inputKind: "video",
2688
- name: "SAM 3.1 Video",
2689
- outputKeys: ["video", "boundingbox_frames_zip"],
2690
- price: {
2691
- frames: 16,
2692
- kind: "frames",
2693
- usd: .01
2694
- },
2695
- pricing: "$0.01/16 frames of video input",
2696
- queued: true,
2697
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
2698
- task: "multi-object video segmentation"
2699
- },
2700
- "sam3-image": {
2701
- category: "segmentation",
2702
- defaultOptions: {
2703
- apply_mask: true,
2704
- max_masks: 3,
2705
- output_format: "png"
2706
- },
2707
- description: "Segment image objects with text, point, or box prompts.",
2708
- endpoint: "fal-ai/sam-3/image",
2709
- inputField: "image_url",
2710
- inputKind: "image",
2711
- name: "SAM 3 Image",
2712
- outputKeys: [
2713
- "image",
2714
- "masks",
2715
- "metadata",
2716
- "scores",
2717
- "boxes"
2718
- ],
2719
- price: {
2720
- kind: "call",
2721
- usd: .005
2722
- },
2723
- pricing: "$0.005/request",
2724
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
2725
- task: "promptable image segmentation"
2726
- },
2727
- "sam3-image-rle": {
2728
- category: "segmentation",
2729
- defaultOptions: {
2730
- apply_mask: true,
2731
- max_masks: 3
2732
- },
2733
- description: "Segment image objects and return run-length encoded masks.",
2734
- endpoint: "fal-ai/sam-3/image-rle",
2735
- inputField: "image_url",
2736
- inputKind: "image",
2737
- name: "SAM 3 Image RLE",
2738
- outputKeys: [
2739
- "rle",
2740
- "scores",
2741
- "boxes",
2742
- "metadata"
2743
- ],
2744
- price: {
2745
- kind: "call",
2746
- usd: .005
2747
- },
2748
- pricing: "$0.005/request",
2749
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
2750
- task: "promptable image segmentation to RLE"
2751
- },
2752
- "sam3-video": {
2753
- category: "segmentation",
2754
- defaultOptions: {
2755
- apply_mask: true,
2756
- detection_threshold: .5,
2757
- prompt: "person",
2758
- video_output_type: "X264 (.mp4)"
2759
- },
2760
- description: "Segment and track prompted objects across video frames.",
2761
- endpoint: "fal-ai/sam-3/video",
2762
- inputField: "video_url",
2763
- inputKind: "video",
2764
- name: "SAM 3 Video",
2765
- outputKeys: ["video", "boundingbox_frames_zip"],
2766
- price: {
2767
- frames: 16,
2768
- kind: "frames",
2769
- usd: .005
2770
- },
2771
- pricing: "$0.005/16 frames of video input",
2772
- queued: true,
2773
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
2774
- task: "promptable video segmentation"
2775
- },
2776
- "sam3-video-rle": {
2777
- category: "segmentation",
2778
- defaultOptions: {
2779
- apply_mask: true,
2780
- detection_threshold: .5,
2781
- prompt: "person"
2782
- },
2783
- description: "Track prompted video objects and return RLE mask data.",
2784
- endpoint: "fal-ai/sam-3/video-rle",
2785
- inputField: "video_url",
2786
- inputKind: "video",
2787
- name: "SAM 3 Video RLE",
2788
- outputKeys: ["video", "boundingbox_frames_zip"],
2789
- price: {
2790
- frames: 16,
2791
- kind: "frames",
2792
- usd: .005
2793
- },
2794
- pricing: "$0.005/16 frames of video",
2795
- queued: true,
2796
- sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
2797
- task: "promptable video segmentation to RLE"
2798
- },
2799
- scribble: {
2800
- category: "preprocess",
2801
- description: "Turn an image into a scribble-style control map.",
2802
- endpoint: "fal-ai/image-preprocessors/scribble",
2803
- inputField: "image_url",
2804
- inputKind: "image",
2805
- name: "Scribble Preprocessor",
2806
- outputKeys: ["image"],
2807
- price: {
2808
- kind: "call",
2809
- usd: 0
2810
- },
2811
- pricing: "$0/compute-second listed by fal",
2812
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/scribble",
2813
- task: "scribble preprocessing"
2814
- },
2815
- teed: {
2816
- category: "preprocess",
2817
- description: "Generate TEED edge maps from an input image.",
2818
- endpoint: "fal-ai/image-preprocessors/teed",
2819
- inputField: "image_url",
2820
- inputKind: "image",
2821
- name: "TEED Edge Preprocessor",
2822
- outputKeys: ["image"],
2823
- price: {
2824
- kind: "call",
2825
- usd: 0
2826
- },
2827
- pricing: "$0/compute-second listed by fal",
2828
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/teed",
2829
- task: "edge preprocessing"
2830
- },
2831
- "zoe-depth": {
2832
- category: "preprocess",
2833
- description: "Generate ZoeDepth metric depth maps from an input image.",
2834
- endpoint: "fal-ai/image-preprocessors/zoe",
2835
- inputField: "image_url",
2836
- inputKind: "image",
2837
- name: "ZoeDepth Preprocessor",
2838
- outputKeys: ["image"],
2839
- price: {
2840
- kind: "call",
2841
- usd: 0
2842
- },
2843
- pricing: "$0/compute-second listed by fal",
2844
- sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/zoe",
2845
- task: "depth preprocessing"
1262
+ supportsAspect: false,
1263
+ supportsEdit: false,
1264
+ supportsNumImages: false,
1265
+ supportsResolution: false,
1266
+ type: "utility"
2846
1267
  }
2847
1268
  };
2848
- // src/tools.ts
2849
- const FAL_TOOLS = {
2850
- ...ANALYSIS_TOOLS,
2851
- ...ASSET_TOOLS,
2852
- ...BACKGROUND_TOOLS,
2853
- ...EDITING_TOOLS,
2854
- ...STRUCTURE_TOOLS
2855
- };
2856
- Object.keys(FAL_TOOLS);
1269
+ Object.keys(MODELS).filter((id) => MODELS[id]?.type === "generation");
2857
1270
  // src/image/fetch.ts
2858
1271
  function urlOf(input) {
2859
1272
  if (typeof input === "string") return input;
@@ -2871,7 +1284,7 @@ function toProviderFetch(fetch) {
2871
1284
  * Builds a Vercel AI SDK `ImageModel` from `@ai-sdk/fal`. Building a model
2872
1285
  * performs no network I/O — the request only happens when `generateImage`
2873
1286
  * invokes `model.doGenerate`. This is the lightweight fal *image* adapter; the
2874
- * richer `FalClient` fal client (queue, upload, tools) is separate.
1287
+ * Task client (`createMotif`) runs fal's queue, upload and tools.
2875
1288
  *
2876
1289
  * ENDPOINT QUIRK (Phase 0): fal's gpt-image endpoint wants `image_size` as a
2877
1290
  * STRING enum (e.g. "1024x1024"), passed at generate time via
@@ -2889,35 +1302,38 @@ function toProviderFetch(fetch) {
2889
1302
  const FAL_GPT_IMAGE_MODEL = "fal-ai/gpt-image-1.5";
2890
1303
  /** Env var read for the fal key when `apiKey` is not supplied in config. */
2891
1304
  const FAL_API_KEY_ENV = "FAL_KEY";
1305
+ function registryPrice(modelId) {
1306
+ const price = MODELS[modelId]?.pricePerImageUsd;
1307
+ if (typeof price !== "number") throw new MotifError(`Registry Model ${modelId} has no per-image price`, 0);
1308
+ return price;
1309
+ }
2892
1310
  /**
2893
1311
  * Static fal USD/image, keyed by the fal ENDPOINT id. Cost tracking must price
2894
1312
  * the endpoints consumers actually pass. Every price is
2895
1313
  * sourced from the fal registry snapshot in `../models`
2896
1314
  * (`MODELS[...].pricePerImageUsd`) so this table stays in sync rather than
2897
- * drifting to a second hand-maintained copy; the `??` fallbacks are the registry
2898
- * values at time of writing, used only if a registry entry loses its optional
2899
- * price. Each key is `MODELS[x].endpoint`; the trailing comment cites the
2900
- * `MODELS` key it comes from. Covers the flux2 family, banana, seedream, and
1315
+ * drifting to a second hand-maintained copy; a registry entry without a price throws at
1316
+ * load. Each key is `MODELS[x].endpoint`. Covers the flux2 family, banana, seedream, and
2901
1317
  * the other priced fal generation endpoints.
2902
1318
  */
2903
1319
  const FAL_IMAGE_PRICE_USD = {
2904
- "fal-ai/flux-2-pro": MODELS["flux2-pro"]?.pricePerImageUsd ?? .03,
2905
- "fal-ai/flux-2-flex": MODELS["flux2-flex"]?.pricePerImageUsd ?? .05,
2906
- "fal-ai/flux-2/turbo": MODELS["flux2-turbo"]?.pricePerImageUsd ?? .008,
2907
- [FAL_GPT_IMAGE_MODEL]: MODELS.gpt?.pricePerImageUsd ?? .133,
2908
- "fal-ai/nano-banana-2": MODELS.banana2?.pricePerImageUsd ?? .08,
2909
- "fal-ai/nano-banana-pro": MODELS.banana?.pricePerImageUsd ?? .15,
2910
- "bytedance/seedream/v5/pro/text-to-image": MODELS.seedream5?.pricePerImageUsd ?? .0675,
2911
- "bytedance/seedream/v5/lite/text-to-image": MODELS["seedream5-lite"]?.pricePerImageUsd ?? .035,
2912
- "fal-ai/recraft/v4.1/text-to-image": MODELS.recraft41?.pricePerImageUsd ?? .035,
2913
- "ideogram/v4": MODELS.ideogram4?.pricePerImageUsd ?? .03,
2914
- "xai/grok-imagine-image": MODELS["grok-image"]?.pricePerImageUsd ?? .02,
2915
- "xai/grok-imagine-image/v2.0/text-to-image": MODELS["grok-image-2"]?.pricePerImageUsd ?? .06,
2916
- "xai/grok-imagine-image/quality/text-to-image": MODELS["grok-image-quality"]?.pricePerImageUsd ?? .05,
2917
- "meta/muse-image/text-to-image": MODELS["muse-image"]?.pricePerImageUsd ?? .01,
2918
- "microsoft/mai-image-2.5-pro": MODELS["mai-image-2.5-pro"]?.pricePerImageUsd ?? .17,
2919
- "fal-ai/ideogram/v3/generate-transparent": MODELS["ideogram3-transparent"]?.pricePerImageUsd ?? .06,
2920
- "alibaba/qwen-image-3/text-to-image": MODELS.qwen3?.pricePerImageUsd ?? .04
1320
+ "fal-ai/flux-2-pro": registryPrice("flux2-pro"),
1321
+ "fal-ai/flux-2-flex": registryPrice("flux2-flex"),
1322
+ "fal-ai/flux-2/turbo": registryPrice("flux2-turbo"),
1323
+ [FAL_GPT_IMAGE_MODEL]: registryPrice("gpt"),
1324
+ "fal-ai/nano-banana-2": registryPrice("banana2"),
1325
+ "fal-ai/nano-banana-pro": registryPrice("banana"),
1326
+ "bytedance/seedream/v5/pro/text-to-image": registryPrice("seedream5"),
1327
+ "bytedance/seedream/v5/lite/text-to-image": registryPrice("seedream5-lite"),
1328
+ "fal-ai/recraft/v4.1/text-to-image": registryPrice("recraft41"),
1329
+ "ideogram/v4": registryPrice("ideogram4"),
1330
+ "xai/grok-imagine-image": registryPrice("grok-image"),
1331
+ "xai/grok-imagine-image/v2.0/text-to-image": registryPrice("grok-image-2"),
1332
+ "xai/grok-imagine-image/quality/text-to-image": registryPrice("grok-image-quality"),
1333
+ "meta/muse-image/text-to-image": registryPrice("muse-image"),
1334
+ "microsoft/mai-image-2.5-pro": registryPrice("mai-image-2.5-pro"),
1335
+ "fal-ai/ideogram/v3/generate-transparent": registryPrice("ideogram3-transparent"),
1336
+ "alibaba/qwen-image-3/text-to-image": registryPrice("qwen3")
2921
1337
  };
2922
1338
  /**
2923
1339
  * Build a fal `ImageModel`. Prefers the passed `apiKey`, else the `FAL_KEY` env
@@ -3046,9 +1462,6 @@ function openRouterModelSlug(modelId) {
3046
1462
  if (slug === void 0) throw new MotifError(`No OpenRouter image model for "${modelId}". Known: ${Object.keys(OPENROUTER_GEMINI_IMAGE_MODELS).join(", ")}; or pass an OpenRouter slug such as google/gemini-3.1-flash-image.`, 0);
3047
1463
  return slug;
3048
1464
  }
3049
- function isRecord$2(value) {
3050
- return typeof value === "object" && value !== null;
3051
- }
3052
1465
  function toDataUrl(mediaType, data) {
3053
1466
  return `data:${mediaType};base64,${typeof data === "string" ? data : Buffer.from(data).toString("base64")}`;
3054
1467
  }
@@ -3071,20 +1484,20 @@ function buildBody(slug, options) {
3071
1484
  };
3072
1485
  }
3073
1486
  function errorMessage(body, fallback) {
3074
- if (isRecord$2(body) && isRecord$2(body.error)) {
1487
+ if (isRecord(body) && isRecord(body.error)) {
3075
1488
  const { message } = body.error;
3076
1489
  if (typeof message === "string" && message !== "") return message;
3077
1490
  }
3078
1491
  return fallback;
3079
1492
  }
3080
1493
  function parseResponse(body) {
3081
- if (!isRecord$2(body) || !Array.isArray(body.data)) throw new MotifError("OpenRouter image response had no data array", 502);
1494
+ if (!isRecord(body) || !Array.isArray(body.data)) throw new MotifError("OpenRouter image response had no data array", 502);
3082
1495
  const images = [];
3083
- for (const entry of body.data) if (isRecord$2(entry) && typeof entry.b64_json === "string") images.push(entry.b64_json);
1496
+ for (const entry of body.data) if (isRecord(entry) && typeof entry.b64_json === "string") images.push(entry.b64_json);
3084
1497
  if (images.length === 0) throw new MotifError("OpenRouter returned no images", 502);
3085
1498
  return {
3086
1499
  images,
3087
- cost: isRecord$2(body.usage) && typeof body.usage.cost === "number" ? body.usage.cost : void 0
1500
+ cost: isRecord(body.usage) && typeof body.usage.cost === "number" ? body.usage.cost : void 0
3088
1501
  };
3089
1502
  }
3090
1503
  function buildModel(modelId, apiKey, doFetch) {
@@ -3231,17 +1644,14 @@ function getProviderAdapter(provider) {
3231
1644
  * from the provider registry (see sources in each adapter).
3232
1645
  * 3. Unknown → `{ usd: 0, source: "unknown" }`.
3233
1646
  */
3234
- function isRecord$1(value) {
3235
- return typeof value === "object" && value !== null;
3236
- }
3237
1647
  /**
3238
1648
  * Extract a provider-reported total cost from a `generateImage` result's
3239
1649
  * `providerMetadata`, if the provider surfaces a numeric `cost`. The shape is
3240
1650
  * `{ [provider]: { ...; cost?: number } }`. Returns undefined otherwise.
3241
1651
  */
3242
1652
  function costFromProviderMetadata(providerMetadata) {
3243
- if (!isRecord$1(providerMetadata)) return;
3244
- for (const value of Object.values(providerMetadata)) if (isRecord$1(value)) {
1653
+ if (!isRecord(providerMetadata)) return;
1654
+ for (const value of Object.values(providerMetadata)) if (isRecord(value)) {
3245
1655
  const cost = value.cost;
3246
1656
  if (typeof cost === "number" && Number.isFinite(cost)) return cost;
3247
1657
  }
@@ -3493,9 +1903,6 @@ function renderWarning(warning) {
3493
1903
  if (warning.type === "deprecated") return `deprecated setting "${warning.setting}": ${warning.message}`;
3494
1904
  return warning.message;
3495
1905
  }
3496
- function isRecord(value) {
3497
- return typeof value === "object" && value !== null;
3498
- }
3499
1906
  /** Look for a provider correlation id in providerMetadata, then response headers. */
3500
1907
  function extractRequestId(result) {
3501
1908
  for (const call of result.calls) {