@howells/motif-sdk 1.3.1 → 4.0.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/README.md +52 -95
- package/dist/image.d.ts +25 -39
- package/dist/image.js +1017 -187
- package/dist/index.cjs +5079 -1269
- package/dist/index.d.cts +1122 -3178
- package/dist/index.d.ts +1122 -3178
- package/dist/index.js +5061 -1240
- package/package.json +7 -4
package/dist/image.js
CHANGED
|
@@ -4,12 +4,133 @@ import { createFal } from "@ai-sdk/fal";
|
|
|
4
4
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
5
5
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
6
6
|
import { createReplicate } from "@ai-sdk/replicate";
|
|
7
|
+
//#region src/errors.ts
|
|
8
|
+
/**
|
|
9
|
+
* `MotifError` and its coercion helper.
|
|
10
|
+
*
|
|
11
|
+
* Extracted from `server.ts` so `server-tools.ts` can construct errors without
|
|
12
|
+
* importing back into `server.ts` — that import was a dependency cycle. Both
|
|
13
|
+
* `server.ts` and `server-tools.ts` import from here, and `server.ts`
|
|
14
|
+
* re-exports `MotifError` so the package's public surface is unchanged.
|
|
15
|
+
*/
|
|
16
|
+
var MotifError = class extends Error {
|
|
17
|
+
status;
|
|
18
|
+
code;
|
|
19
|
+
/** fal's request-correlation id (from the `x-fal-request-id` header or the
|
|
20
|
+
* error body). Ties a failure back to fal's dashboard/support. */
|
|
21
|
+
requestId;
|
|
22
|
+
/** Structured context for the failure, e.g. the refused field. */
|
|
23
|
+
details;
|
|
24
|
+
constructor(message, status, code, requestId, details) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "MotifError";
|
|
27
|
+
this.status = status;
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.requestId = requestId;
|
|
30
|
+
this.details = details;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/** Error code for a fal account that is locked, usually for lack of credit. */
|
|
34
|
+
const ACCOUNT_LOCKED = "ACCOUNT_LOCKED";
|
|
35
|
+
const ACCOUNT_LOCKED_DETAIL = "User is locked";
|
|
36
|
+
/**
|
|
37
|
+
* Whether a fal HTTP failure means the account is locked.
|
|
38
|
+
*
|
|
39
|
+
* fal answers `403 {"detail":"User is locked. Reason: TOP_UP."}` when the
|
|
40
|
+
* account has run out of credit. Retrying cannot help, and the key is valid.
|
|
41
|
+
*/
|
|
42
|
+
function isFalAccountLocked(status, body) {
|
|
43
|
+
return status === 403 && body.includes(ACCOUNT_LOCKED_DETAIL);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the `MotifError` for a non-OK fal response, recognising a locked
|
|
47
|
+
* account as `ACCOUNT_LOCKED`.
|
|
48
|
+
*/
|
|
49
|
+
function falHttpError(status, body, requestId) {
|
|
50
|
+
if (isFalAccountLocked(status, body)) return new MotifError(`fal account is locked because it is out of credit (fal said: ${body})`, status, ACCOUNT_LOCKED, requestId);
|
|
51
|
+
return new MotifError(`Request failed: ${status} ${body}`, status, void 0, requestId);
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
7
54
|
//#region src/models.ts
|
|
8
55
|
const AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-08-23";
|
|
9
56
|
const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-to-image", "https://artificialanalysis.ai/image/leaderboard/editing"];
|
|
10
57
|
const FAL_PRICING_CHECKED_AT = "2026-05-12";
|
|
11
58
|
const FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
|
|
59
|
+
const FAL_PRICING_CHECKED_AUG_2026 = "2026-08-23";
|
|
60
|
+
const FAL_PRICING_CHECKED_SEP_2026 = "2026-09-16";
|
|
12
61
|
const MODELS = {
|
|
62
|
+
flare: {
|
|
63
|
+
customImageSize: {
|
|
64
|
+
maxEdge: 3840,
|
|
65
|
+
maxPixels: 8294400,
|
|
66
|
+
maxRatio: 3,
|
|
67
|
+
minPixels: 655360,
|
|
68
|
+
multipleOf: 16
|
|
69
|
+
},
|
|
70
|
+
editEndpoint: "openai/gpt-image-2.5/flare/edit",
|
|
71
|
+
endpoint: "openai/gpt-image-2.5/flare/text-to-image",
|
|
72
|
+
maxReferenceImages: 16,
|
|
73
|
+
name: "GPT Image 2.5 Flare",
|
|
74
|
+
pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
|
|
75
|
+
pricePerImageUsd: null,
|
|
76
|
+
sizeMode: "image_size_enum",
|
|
77
|
+
supportsAspect: true,
|
|
78
|
+
supportsBackground: true,
|
|
79
|
+
supportsEdit: true,
|
|
80
|
+
supportsMaskImage: true,
|
|
81
|
+
maskImageField: "mask_url",
|
|
82
|
+
supportsNumImages: true,
|
|
83
|
+
supportsOutputFormat: true,
|
|
84
|
+
supportsQuality: true,
|
|
85
|
+
supportedQualities: [
|
|
86
|
+
"auto",
|
|
87
|
+
"low",
|
|
88
|
+
"medium",
|
|
89
|
+
"high",
|
|
90
|
+
"xhigh",
|
|
91
|
+
"max"
|
|
92
|
+
],
|
|
93
|
+
supportsResolution: false,
|
|
94
|
+
supportsSyncMode: true,
|
|
95
|
+
type: "generation",
|
|
96
|
+
useQueue: true
|
|
97
|
+
},
|
|
98
|
+
sunburst: {
|
|
99
|
+
customImageSize: {
|
|
100
|
+
maxEdge: 3840,
|
|
101
|
+
maxPixels: 8294400,
|
|
102
|
+
maxRatio: 3,
|
|
103
|
+
minPixels: 655360,
|
|
104
|
+
multipleOf: 16
|
|
105
|
+
},
|
|
106
|
+
editEndpoint: "openai/gpt-image-2.5/sunburst/edit",
|
|
107
|
+
endpoint: "openai/gpt-image-2.5/sunburst/text-to-image",
|
|
108
|
+
maxReferenceImages: 16,
|
|
109
|
+
name: "GPT Image 2.5 Sunburst",
|
|
110
|
+
pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
|
|
111
|
+
pricePerImageUsd: null,
|
|
112
|
+
sizeMode: "image_size_enum",
|
|
113
|
+
supportsAspect: true,
|
|
114
|
+
supportsBackground: true,
|
|
115
|
+
supportsEdit: true,
|
|
116
|
+
supportsMaskImage: true,
|
|
117
|
+
maskImageField: "mask_url",
|
|
118
|
+
supportsNumImages: true,
|
|
119
|
+
supportsOutputFormat: true,
|
|
120
|
+
supportsQuality: true,
|
|
121
|
+
supportedQualities: [
|
|
122
|
+
"auto",
|
|
123
|
+
"low",
|
|
124
|
+
"medium",
|
|
125
|
+
"high",
|
|
126
|
+
"xhigh",
|
|
127
|
+
"max"
|
|
128
|
+
],
|
|
129
|
+
supportsResolution: false,
|
|
130
|
+
supportsSyncMode: true,
|
|
131
|
+
type: "generation",
|
|
132
|
+
useQueue: true
|
|
133
|
+
},
|
|
13
134
|
gpt2: {
|
|
14
135
|
benchmark: {
|
|
15
136
|
artificialAnalysis: {
|
|
@@ -36,7 +157,14 @@ const MODELS = {
|
|
|
36
157
|
},
|
|
37
158
|
useCase: "Highest-ranked text-to-image quality and transparent PNGs"
|
|
38
159
|
},
|
|
39
|
-
|
|
160
|
+
customImageSize: {
|
|
161
|
+
maxEdge: 3840,
|
|
162
|
+
maxPixels: 8294400,
|
|
163
|
+
maxRatio: 3,
|
|
164
|
+
minPixels: 655360,
|
|
165
|
+
multipleOf: 16
|
|
166
|
+
},
|
|
167
|
+
editEndpoint: "openai/gpt-image-2/edit",
|
|
40
168
|
endpoint: "openai/gpt-image-2",
|
|
41
169
|
falPricing: {
|
|
42
170
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -55,11 +183,18 @@ const MODELS = {
|
|
|
55
183
|
supportsAspect: true,
|
|
56
184
|
supportsEdit: true,
|
|
57
185
|
supportsMaskImage: true,
|
|
186
|
+
maskImageField: "mask_url",
|
|
58
187
|
supportsNumImages: true,
|
|
59
188
|
supportsOutputFormat: true,
|
|
60
189
|
supportsQuality: true,
|
|
61
190
|
supportsResolution: false,
|
|
62
191
|
supportsSyncMode: true,
|
|
192
|
+
transparencyRoute: {
|
|
193
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
194
|
+
model: "gpt-image-2",
|
|
195
|
+
provider: "openai",
|
|
196
|
+
supportsEdit: true
|
|
197
|
+
},
|
|
63
198
|
type: "generation",
|
|
64
199
|
useQueue: true
|
|
65
200
|
},
|
|
@@ -209,6 +344,24 @@ const MODELS = {
|
|
|
209
344
|
pricePerImageUsd: .15,
|
|
210
345
|
pricing: "$0.15",
|
|
211
346
|
sizeMode: "aspect_ratio",
|
|
347
|
+
supportedAspects: [
|
|
348
|
+
"auto",
|
|
349
|
+
"21:9",
|
|
350
|
+
"16:9",
|
|
351
|
+
"3:2",
|
|
352
|
+
"4:3",
|
|
353
|
+
"5:4",
|
|
354
|
+
"1:1",
|
|
355
|
+
"4:5",
|
|
356
|
+
"3:4",
|
|
357
|
+
"2:3",
|
|
358
|
+
"9:16"
|
|
359
|
+
],
|
|
360
|
+
supportedResolutions: [
|
|
361
|
+
"1K",
|
|
362
|
+
"2K",
|
|
363
|
+
"4K"
|
|
364
|
+
],
|
|
212
365
|
supportsAspect: true,
|
|
213
366
|
supportsEdit: true,
|
|
214
367
|
supportsGoogleSearch: true,
|
|
@@ -247,6 +400,18 @@ const MODELS = {
|
|
|
247
400
|
pricePerImageUsd: .0398,
|
|
248
401
|
pricing: "$0.04",
|
|
249
402
|
sizeMode: "aspect_ratio",
|
|
403
|
+
supportedAspects: [
|
|
404
|
+
"21:9",
|
|
405
|
+
"16:9",
|
|
406
|
+
"3:2",
|
|
407
|
+
"4:3",
|
|
408
|
+
"5:4",
|
|
409
|
+
"1:1",
|
|
410
|
+
"4:5",
|
|
411
|
+
"3:4",
|
|
412
|
+
"2:3",
|
|
413
|
+
"9:16"
|
|
414
|
+
],
|
|
250
415
|
supportsAspect: true,
|
|
251
416
|
supportsEdit: true,
|
|
252
417
|
supportsNumImages: true,
|
|
@@ -286,6 +451,24 @@ const MODELS = {
|
|
|
286
451
|
pricePerImageUsd: .15,
|
|
287
452
|
pricing: "$0.15",
|
|
288
453
|
sizeMode: "aspect_ratio",
|
|
454
|
+
supportedAspects: [
|
|
455
|
+
"auto",
|
|
456
|
+
"21:9",
|
|
457
|
+
"16:9",
|
|
458
|
+
"3:2",
|
|
459
|
+
"4:3",
|
|
460
|
+
"5:4",
|
|
461
|
+
"1:1",
|
|
462
|
+
"4:5",
|
|
463
|
+
"3:4",
|
|
464
|
+
"2:3",
|
|
465
|
+
"9:16"
|
|
466
|
+
],
|
|
467
|
+
supportedResolutions: [
|
|
468
|
+
"1K",
|
|
469
|
+
"2K",
|
|
470
|
+
"4K"
|
|
471
|
+
],
|
|
289
472
|
supportsAspect: true,
|
|
290
473
|
supportsEdit: true,
|
|
291
474
|
supportsNumImages: true,
|
|
@@ -323,6 +506,10 @@ const MODELS = {
|
|
|
323
506
|
},
|
|
324
507
|
useCase: "High-ranked budget generation and large multi-reference edits"
|
|
325
508
|
},
|
|
509
|
+
customImageSize: {
|
|
510
|
+
maxPixels: 16777216,
|
|
511
|
+
minPixels: 921600
|
|
512
|
+
},
|
|
326
513
|
editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
|
|
327
514
|
endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
|
|
328
515
|
falPricing: {
|
|
@@ -373,6 +560,10 @@ const MODELS = {
|
|
|
373
560
|
},
|
|
374
561
|
useCase: "Cheap current Seedream model with good animation/art scores"
|
|
375
562
|
},
|
|
563
|
+
customImageSize: {
|
|
564
|
+
maxPixels: 16777216,
|
|
565
|
+
minPixels: 3686400
|
|
566
|
+
},
|
|
376
567
|
editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
|
|
377
568
|
endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
|
|
378
569
|
falPricing: {
|
|
@@ -410,6 +601,11 @@ const MODELS = {
|
|
|
410
601
|
rank: 10
|
|
411
602
|
}
|
|
412
603
|
} },
|
|
604
|
+
customImageSize: {
|
|
605
|
+
maxPixels: 4194304,
|
|
606
|
+
maxRatio: 16,
|
|
607
|
+
minPixels: 1048576
|
|
608
|
+
},
|
|
413
609
|
name: "Seedream 5.0 Pro",
|
|
414
610
|
endpoint: "bytedance/seedream/v5/pro/text-to-image",
|
|
415
611
|
editEndpoint: "bytedance/seedream/v5/pro/edit",
|
|
@@ -443,16 +639,20 @@ const MODELS = {
|
|
|
443
639
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
444
640
|
sourceUrls: AA_IMAGE_SOURCES
|
|
445
641
|
} },
|
|
642
|
+
customImageSize: {
|
|
643
|
+
maxPixels: 16777216,
|
|
644
|
+
minPixels: 3686400
|
|
645
|
+
},
|
|
446
646
|
name: "Seedream 5.0 Lite",
|
|
447
|
-
endpoint: "
|
|
448
|
-
editEndpoint: "
|
|
647
|
+
endpoint: "bytedance/seedream/v5/lite/text-to-image",
|
|
648
|
+
editEndpoint: "bytedance/seedream/v5/lite/edit",
|
|
449
649
|
type: "generation",
|
|
450
650
|
pricing: "$0.035",
|
|
451
651
|
pricePerImageUsd: .035,
|
|
452
652
|
falPricing: {
|
|
453
653
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
454
654
|
currency: "USD",
|
|
455
|
-
endpointId: "
|
|
655
|
+
endpointId: "bytedance/seedream/v5/lite/text-to-image",
|
|
456
656
|
estimatedCostPerImageUsd: .035,
|
|
457
657
|
source: "fal-pricing-api",
|
|
458
658
|
unit: "images",
|
|
@@ -493,6 +693,12 @@ const MODELS = {
|
|
|
493
693
|
},
|
|
494
694
|
useCase: "Best FLUX quality and high-end editing"
|
|
495
695
|
},
|
|
696
|
+
customImageSize: {
|
|
697
|
+
maxEdge: 2560,
|
|
698
|
+
maxPixels: 4194304,
|
|
699
|
+
minEdge: 256,
|
|
700
|
+
multipleOf: 16
|
|
701
|
+
},
|
|
496
702
|
editEndpoint: "fal-ai/flux-2-max/edit",
|
|
497
703
|
endpoint: "fal-ai/flux-2-max",
|
|
498
704
|
falPricing: {
|
|
@@ -546,6 +752,12 @@ const MODELS = {
|
|
|
546
752
|
},
|
|
547
753
|
useCase: "Production FLUX quality at a low per-megapixel price"
|
|
548
754
|
},
|
|
755
|
+
customImageSize: {
|
|
756
|
+
maxEdge: 2560,
|
|
757
|
+
maxPixels: 4194304,
|
|
758
|
+
minEdge: 256,
|
|
759
|
+
multipleOf: 16
|
|
760
|
+
},
|
|
549
761
|
editEndpoint: "fal-ai/flux-2-pro/edit",
|
|
550
762
|
endpoint: "fal-ai/flux-2-pro",
|
|
551
763
|
falPricing: {
|
|
@@ -595,6 +807,12 @@ const MODELS = {
|
|
|
595
807
|
},
|
|
596
808
|
useCase: "FLUX quality with guidance and step controls"
|
|
597
809
|
},
|
|
810
|
+
customImageSize: {
|
|
811
|
+
maxEdge: 2560,
|
|
812
|
+
maxPixels: 4194304,
|
|
813
|
+
minEdge: 256,
|
|
814
|
+
multipleOf: 16
|
|
815
|
+
},
|
|
598
816
|
editEndpoint: "fal-ai/flux-2-flex/edit",
|
|
599
817
|
endpoint: "fal-ai/flux-2-flex",
|
|
600
818
|
falPricing: {
|
|
@@ -643,6 +861,10 @@ const MODELS = {
|
|
|
643
861
|
},
|
|
644
862
|
useCase: "Open FLUX.2 quality with low average cost"
|
|
645
863
|
},
|
|
864
|
+
customImageSize: {
|
|
865
|
+
maxEdge: 2048,
|
|
866
|
+
minEdge: 512
|
|
867
|
+
},
|
|
646
868
|
editEndpoint: "fal-ai/flux-2/edit",
|
|
647
869
|
endpoint: "fal-ai/flux-2",
|
|
648
870
|
falPricing: {
|
|
@@ -673,6 +895,10 @@ const MODELS = {
|
|
|
673
895
|
type: "generation"
|
|
674
896
|
},
|
|
675
897
|
"flux2-turbo": {
|
|
898
|
+
customImageSize: {
|
|
899
|
+
maxEdge: 2048,
|
|
900
|
+
minEdge: 512
|
|
901
|
+
},
|
|
676
902
|
endpoint: "fal-ai/flux-2/turbo",
|
|
677
903
|
falPricing: {
|
|
678
904
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
@@ -728,6 +954,7 @@ const MODELS = {
|
|
|
728
954
|
type: "generation"
|
|
729
955
|
},
|
|
730
956
|
"flux-fast": {
|
|
957
|
+
customImageSize: {},
|
|
731
958
|
endpoint: "fal-ai/flux/schnell",
|
|
732
959
|
falPricing: {
|
|
733
960
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -754,11 +981,12 @@ const MODELS = {
|
|
|
754
981
|
type: "generation"
|
|
755
982
|
},
|
|
756
983
|
recraft: {
|
|
757
|
-
|
|
984
|
+
customImageSize: {},
|
|
985
|
+
endpoint: "fal-ai/recraft/v3/text-to-image",
|
|
758
986
|
falPricing: {
|
|
759
987
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
760
988
|
currency: "USD",
|
|
761
|
-
endpointId: "fal-ai/recraft-
|
|
989
|
+
endpointId: "fal-ai/recraft/v3/text-to-image",
|
|
762
990
|
estimatedCostPerImageUsd: .04,
|
|
763
991
|
source: "fal-pricing-api",
|
|
764
992
|
unit: "images",
|
|
@@ -800,7 +1028,7 @@ const MODELS = {
|
|
|
800
1028
|
editEndpoint: "fal-ai/reve/edit",
|
|
801
1029
|
endpoint: "fal-ai/reve/text-to-image",
|
|
802
1030
|
falPricing: {
|
|
803
|
-
checkedAt:
|
|
1031
|
+
checkedAt: FAL_PRICING_CHECKED_AUG_2026,
|
|
804
1032
|
currency: "USD",
|
|
805
1033
|
endpointId: "fal-ai/reve/text-to-image",
|
|
806
1034
|
estimatedCostPerImageUsd: .04,
|
|
@@ -813,6 +1041,15 @@ const MODELS = {
|
|
|
813
1041
|
pricePerImageUsd: .04,
|
|
814
1042
|
pricing: "$0.04",
|
|
815
1043
|
sizeMode: "aspect_ratio",
|
|
1044
|
+
supportedAspects: [
|
|
1045
|
+
"16:9",
|
|
1046
|
+
"9:16",
|
|
1047
|
+
"3:2",
|
|
1048
|
+
"2:3",
|
|
1049
|
+
"4:3",
|
|
1050
|
+
"3:4",
|
|
1051
|
+
"1:1"
|
|
1052
|
+
],
|
|
816
1053
|
supportsAspect: true,
|
|
817
1054
|
supportsEdit: true,
|
|
818
1055
|
supportsNumImages: true,
|
|
@@ -831,6 +1068,7 @@ const MODELS = {
|
|
|
831
1068
|
rank: 25
|
|
832
1069
|
}
|
|
833
1070
|
} },
|
|
1071
|
+
customImageSize: {},
|
|
834
1072
|
endpoint: "fal-ai/recraft/v4/text-to-image",
|
|
835
1073
|
falPricing: {
|
|
836
1074
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
@@ -849,11 +1087,11 @@ const MODELS = {
|
|
|
849
1087
|
supportsEdit: false,
|
|
850
1088
|
supportsNumImages: false,
|
|
851
1089
|
supportsResolution: false,
|
|
852
|
-
supportsStyle: true,
|
|
853
1090
|
supportsSyncMode: true,
|
|
854
1091
|
type: "generation"
|
|
855
1092
|
},
|
|
856
1093
|
ideogram: {
|
|
1094
|
+
customImageSize: {},
|
|
857
1095
|
endpoint: "fal-ai/ideogram/v3",
|
|
858
1096
|
falPricing: {
|
|
859
1097
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -889,6 +1127,11 @@ const MODELS = {
|
|
|
889
1127
|
rank: 19
|
|
890
1128
|
}
|
|
891
1129
|
} },
|
|
1130
|
+
customImageSize: {
|
|
1131
|
+
maxEdge: 3840,
|
|
1132
|
+
minEdge: 512,
|
|
1133
|
+
multipleOf: 16
|
|
1134
|
+
},
|
|
892
1135
|
name: "Ideogram V4",
|
|
893
1136
|
endpoint: "ideogram/v4",
|
|
894
1137
|
type: "generation",
|
|
@@ -952,11 +1195,21 @@ const MODELS = {
|
|
|
952
1195
|
unit: "images",
|
|
953
1196
|
unitPrice: .02
|
|
954
1197
|
},
|
|
955
|
-
maxReferenceImages:
|
|
1198
|
+
maxReferenceImages: 3,
|
|
956
1199
|
name: "Grok Imagine Image",
|
|
957
1200
|
pricePerImageUsd: .02,
|
|
958
1201
|
pricing: "$0.02",
|
|
959
1202
|
sizeMode: "aspect_ratio",
|
|
1203
|
+
supportedAspects: [
|
|
1204
|
+
"16:9",
|
|
1205
|
+
"4:3",
|
|
1206
|
+
"3:2",
|
|
1207
|
+
"1:1",
|
|
1208
|
+
"2:3",
|
|
1209
|
+
"3:4",
|
|
1210
|
+
"9:16"
|
|
1211
|
+
],
|
|
1212
|
+
supportedResolutions: ["1K", "2K"],
|
|
960
1213
|
supportsAspect: true,
|
|
961
1214
|
supportsEdit: true,
|
|
962
1215
|
supportsNumImages: true,
|
|
@@ -983,6 +1236,7 @@ const MODELS = {
|
|
|
983
1236
|
},
|
|
984
1237
|
useCase: "Low-cost open-weight text and design generation"
|
|
985
1238
|
},
|
|
1239
|
+
customImageSize: {},
|
|
986
1240
|
endpoint: "fal-ai/qwen-image",
|
|
987
1241
|
falPricing: {
|
|
988
1242
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -1027,19 +1281,23 @@ const MODELS = {
|
|
|
1027
1281
|
},
|
|
1028
1282
|
useCase: "Strong quality per dollar - 11th on text-to-image at $30/1k"
|
|
1029
1283
|
},
|
|
1030
|
-
|
|
1284
|
+
customImageSize: {
|
|
1285
|
+
maxPixels: 4194304,
|
|
1286
|
+
minPixels: 262144
|
|
1287
|
+
},
|
|
1288
|
+
endpoint: "alibaba/qwen-image-3/text-to-image",
|
|
1031
1289
|
falPricing: {
|
|
1032
|
-
checkedAt:
|
|
1290
|
+
checkedAt: FAL_PRICING_CHECKED_SEP_2026,
|
|
1033
1291
|
currency: "USD",
|
|
1034
|
-
endpointId: "
|
|
1035
|
-
estimatedCostPerImageUsd: .
|
|
1292
|
+
endpointId: "alibaba/qwen-image-3/text-to-image",
|
|
1293
|
+
estimatedCostPerImageUsd: .04,
|
|
1036
1294
|
source: "fal-pricing-api",
|
|
1037
|
-
unit: "
|
|
1038
|
-
unitPrice: .
|
|
1295
|
+
unit: "images",
|
|
1296
|
+
unitPrice: .04
|
|
1039
1297
|
},
|
|
1040
1298
|
name: "Qwen Image 3",
|
|
1041
|
-
pricePerImageUsd: .
|
|
1042
|
-
pricing: "$0.
|
|
1299
|
+
pricePerImageUsd: .04,
|
|
1300
|
+
pricing: "$0.04 (1K) / $0.075 (2K)",
|
|
1043
1301
|
sizeMode: "image_size_enum",
|
|
1044
1302
|
supportsAspect: true,
|
|
1045
1303
|
supportsEdit: false,
|
|
@@ -1051,6 +1309,179 @@ const MODELS = {
|
|
|
1051
1309
|
supportsSeed: true,
|
|
1052
1310
|
type: "generation"
|
|
1053
1311
|
},
|
|
1312
|
+
"mai-image-2.5-pro": {
|
|
1313
|
+
benchmark: {
|
|
1314
|
+
artificialAnalysis: {
|
|
1315
|
+
editing: {
|
|
1316
|
+
elo: 1272,
|
|
1317
|
+
rank: 1
|
|
1318
|
+
},
|
|
1319
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
1320
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
1321
|
+
textToImage: {
|
|
1322
|
+
elo: 1293,
|
|
1323
|
+
rank: 7
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
useCase: "Highest-ranked image editor, single reference image"
|
|
1327
|
+
},
|
|
1328
|
+
editEndpoint: "microsoft/mai-image-2.5-pro/edit",
|
|
1329
|
+
editImagesField: "image_url",
|
|
1330
|
+
endpoint: "microsoft/mai-image-2.5-pro",
|
|
1331
|
+
falPricing: {
|
|
1332
|
+
checkedAt: FAL_PRICING_CHECKED_SEP_2026,
|
|
1333
|
+
currency: "USD",
|
|
1334
|
+
endpointId: "microsoft/mai-image-2.5-pro",
|
|
1335
|
+
estimatedCostPerImageUsd: .17,
|
|
1336
|
+
source: "fal-pricing-api",
|
|
1337
|
+
unit: "images",
|
|
1338
|
+
unitPrice: .17
|
|
1339
|
+
},
|
|
1340
|
+
maxReferenceImages: 1,
|
|
1341
|
+
name: "MAI Image 2.5 Pro",
|
|
1342
|
+
pricePerImageUsd: .17,
|
|
1343
|
+
pricing: "~$0.17 (edit ~$0.18-$0.27)",
|
|
1344
|
+
sizeMode: "aspect_ratio",
|
|
1345
|
+
supportedAspects: [
|
|
1346
|
+
"auto",
|
|
1347
|
+
"1:1",
|
|
1348
|
+
"4:3",
|
|
1349
|
+
"3:4",
|
|
1350
|
+
"16:9",
|
|
1351
|
+
"9:16",
|
|
1352
|
+
"3:2",
|
|
1353
|
+
"2:3"
|
|
1354
|
+
],
|
|
1355
|
+
supportsAspect: true,
|
|
1356
|
+
supportsEdit: true,
|
|
1357
|
+
supportsNumImages: true,
|
|
1358
|
+
supportsOutputFormat: true,
|
|
1359
|
+
supportsResolution: false,
|
|
1360
|
+
supportsSyncMode: true,
|
|
1361
|
+
type: "generation"
|
|
1362
|
+
},
|
|
1363
|
+
"banana2-lite": {
|
|
1364
|
+
benchmark: { artificialAnalysis: {
|
|
1365
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
1366
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
1367
|
+
textToImage: {
|
|
1368
|
+
elo: 1289,
|
|
1369
|
+
rank: 8
|
|
1370
|
+
}
|
|
1371
|
+
} },
|
|
1372
|
+
endpoint: "google/nano-banana-2-lite",
|
|
1373
|
+
name: "Nano Banana 2 Lite",
|
|
1374
|
+
pricing: "Token-based: text $0.3125/M input, $1.875/M output; image $0.3125/M input, $37.50/M output; fixed 1K output (~$0.048)",
|
|
1375
|
+
pricePerImageUsd: .048,
|
|
1376
|
+
sizeMode: "aspect_ratio",
|
|
1377
|
+
supportsAspect: true,
|
|
1378
|
+
supportsEdit: false,
|
|
1379
|
+
supportsLimitGenerations: true,
|
|
1380
|
+
supportsNumImages: true,
|
|
1381
|
+
supportsOutputFormat: true,
|
|
1382
|
+
supportsResolution: false,
|
|
1383
|
+
supportsSafetyTolerance: true,
|
|
1384
|
+
supportsSeed: true,
|
|
1385
|
+
supportsSyncMode: true,
|
|
1386
|
+
supportsThinkingLevel: true,
|
|
1387
|
+
type: "generation"
|
|
1388
|
+
},
|
|
1389
|
+
"ideogram3-transparent": {
|
|
1390
|
+
endpoint: "fal-ai/ideogram/v3/generate-transparent",
|
|
1391
|
+
falPricing: {
|
|
1392
|
+
checkedAt: FAL_PRICING_CHECKED_SEP_2026,
|
|
1393
|
+
currency: "USD",
|
|
1394
|
+
endpointId: "fal-ai/ideogram/v3/generate-transparent",
|
|
1395
|
+
estimatedCostPerImageUsd: .06,
|
|
1396
|
+
source: "fal-pricing-api",
|
|
1397
|
+
unit: "images",
|
|
1398
|
+
unitPrice: .06
|
|
1399
|
+
},
|
|
1400
|
+
name: "Ideogram V3 Transparent",
|
|
1401
|
+
pricePerImageUsd: .06,
|
|
1402
|
+
pricing: "$0.03 TURBO / $0.06 BALANCED / $0.09 QUALITY",
|
|
1403
|
+
sizeMode: "aspect_ratio",
|
|
1404
|
+
supportedAspects: [
|
|
1405
|
+
"16:9",
|
|
1406
|
+
"9:16",
|
|
1407
|
+
"3:2",
|
|
1408
|
+
"2:3",
|
|
1409
|
+
"4:3",
|
|
1410
|
+
"3:4",
|
|
1411
|
+
"5:4",
|
|
1412
|
+
"4:5",
|
|
1413
|
+
"1:1"
|
|
1414
|
+
],
|
|
1415
|
+
supportsAspect: true,
|
|
1416
|
+
supportsEdit: false,
|
|
1417
|
+
supportsExpandPrompt: true,
|
|
1418
|
+
supportsNegativePrompt: true,
|
|
1419
|
+
supportsNumImages: true,
|
|
1420
|
+
supportsRenderingSpeed: true,
|
|
1421
|
+
supportsResolution: false,
|
|
1422
|
+
supportsSeed: true,
|
|
1423
|
+
supportsSyncMode: true,
|
|
1424
|
+
transparentOutput: true,
|
|
1425
|
+
type: "generation"
|
|
1426
|
+
},
|
|
1427
|
+
recraft41: {
|
|
1428
|
+
customImageSize: {},
|
|
1429
|
+
endpoint: "fal-ai/recraft/v4.1/text-to-image",
|
|
1430
|
+
falPricing: {
|
|
1431
|
+
checkedAt: FAL_PRICING_CHECKED_SEP_2026,
|
|
1432
|
+
currency: "USD",
|
|
1433
|
+
endpointId: "fal-ai/recraft/v4.1/text-to-image",
|
|
1434
|
+
estimatedCostPerImageUsd: .035,
|
|
1435
|
+
source: "fal-pricing-api",
|
|
1436
|
+
unit: "images",
|
|
1437
|
+
unitPrice: .035
|
|
1438
|
+
},
|
|
1439
|
+
name: "Recraft V4.1",
|
|
1440
|
+
pricePerImageUsd: .035,
|
|
1441
|
+
pricing: "$0.035",
|
|
1442
|
+
sizeMode: "image_size_enum",
|
|
1443
|
+
supportsAspect: true,
|
|
1444
|
+
supportsEdit: false,
|
|
1445
|
+
supportsNumImages: false,
|
|
1446
|
+
supportsResolution: false,
|
|
1447
|
+
supportsSafetyChecker: true,
|
|
1448
|
+
type: "generation"
|
|
1449
|
+
},
|
|
1450
|
+
"grok-image-2": {
|
|
1451
|
+
editEndpoint: "xai/grok-imagine-image/v2.0/edit",
|
|
1452
|
+
endpoint: "xai/grok-imagine-image/v2.0/text-to-image",
|
|
1453
|
+
falPricing: {
|
|
1454
|
+
checkedAt: FAL_PRICING_CHECKED_SEP_2026,
|
|
1455
|
+
currency: "USD",
|
|
1456
|
+
endpointId: "xai/grok-imagine-image/v2.0/text-to-image",
|
|
1457
|
+
estimatedCostPerImageUsd: .06,
|
|
1458
|
+
source: "fal-pricing-api",
|
|
1459
|
+
unit: "images",
|
|
1460
|
+
unitPrice: .06
|
|
1461
|
+
},
|
|
1462
|
+
maxReferenceImages: 3,
|
|
1463
|
+
name: "Grok Imagine Image 2.0",
|
|
1464
|
+
pricePerImageUsd: .06,
|
|
1465
|
+
pricing: "$0.06 (1K) / $0.08 (2K) at medium quality",
|
|
1466
|
+
sizeMode: "aspect_ratio",
|
|
1467
|
+
supportedAspects: [
|
|
1468
|
+
"16:9",
|
|
1469
|
+
"4:3",
|
|
1470
|
+
"3:2",
|
|
1471
|
+
"1:1",
|
|
1472
|
+
"2:3",
|
|
1473
|
+
"3:4",
|
|
1474
|
+
"9:16"
|
|
1475
|
+
],
|
|
1476
|
+
supportedResolutions: ["1K", "2K"],
|
|
1477
|
+
supportsAspect: true,
|
|
1478
|
+
supportsEdit: true,
|
|
1479
|
+
supportsNumImages: true,
|
|
1480
|
+
supportsOutputFormat: true,
|
|
1481
|
+
supportsResolution: true,
|
|
1482
|
+
supportsSyncMode: true,
|
|
1483
|
+
type: "generation"
|
|
1484
|
+
},
|
|
1054
1485
|
kling: {
|
|
1055
1486
|
endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
|
|
1056
1487
|
name: "Kling v3 Pro",
|
|
@@ -1062,6 +1493,17 @@ const MODELS = {
|
|
|
1062
1493
|
supportsResolution: false,
|
|
1063
1494
|
type: "video"
|
|
1064
1495
|
},
|
|
1496
|
+
"kling-turbo": {
|
|
1497
|
+
endpoint: "fal-ai/kling-video/v3/turbo/pro/image-to-video",
|
|
1498
|
+
name: "Kling v3 Turbo Pro",
|
|
1499
|
+
pricing: "$0.14/sec",
|
|
1500
|
+
sizeMode: "none",
|
|
1501
|
+
supportsAspect: false,
|
|
1502
|
+
supportsEdit: false,
|
|
1503
|
+
supportsNumImages: false,
|
|
1504
|
+
supportsResolution: false,
|
|
1505
|
+
type: "video"
|
|
1506
|
+
},
|
|
1065
1507
|
clarity: {
|
|
1066
1508
|
endpoint: "fal-ai/clarity-upscaler",
|
|
1067
1509
|
name: "Clarity Upscaler",
|
|
@@ -1104,6 +1546,8 @@ const MODELS = {
|
|
|
1104
1546
|
}
|
|
1105
1547
|
};
|
|
1106
1548
|
[
|
|
1549
|
+
"flare",
|
|
1550
|
+
"sunburst",
|
|
1107
1551
|
"gpt2",
|
|
1108
1552
|
"gpt",
|
|
1109
1553
|
"banana2",
|
|
@@ -1126,33 +1570,202 @@ const MODELS = {
|
|
|
1126
1570
|
"ideogram",
|
|
1127
1571
|
"ideogram4",
|
|
1128
1572
|
"grok-image",
|
|
1573
|
+
"grok-image-2",
|
|
1129
1574
|
"qwen",
|
|
1130
|
-
"qwen3"
|
|
1575
|
+
"qwen3",
|
|
1576
|
+
"mai-image-2.5-pro",
|
|
1577
|
+
"banana2-lite",
|
|
1578
|
+
"ideogram3-transparent",
|
|
1579
|
+
"recraft41"
|
|
1131
1580
|
].filter((id) => MODELS[id]?.supportsEdit === true);
|
|
1132
1581
|
//#endregion
|
|
1133
|
-
//#region src/
|
|
1134
|
-
/**
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1582
|
+
//#region src/capabilities.ts
|
|
1583
|
+
/** Generation options a model can refuse, with the registry test for each. */
|
|
1584
|
+
const OPTION_CAPABILITIES = {
|
|
1585
|
+
aspect: (config) => (config.sizeMode ?? "aspect_ratio") !== "none",
|
|
1586
|
+
background: (config) => config.supportsBackground === true,
|
|
1587
|
+
enableGoogleSearch: (config) => config.supportsGoogleSearch === true,
|
|
1588
|
+
enableSafetyChecker: (config) => config.supportsSafetyChecker === true,
|
|
1589
|
+
enableWebSearch: (config) => config.supportsWebSearch === true,
|
|
1590
|
+
enhancePrompt: (config) => config.supportsEnhancePrompt === true,
|
|
1591
|
+
expandPrompt: (config) => config.supportsExpandPrompt === true,
|
|
1592
|
+
guidanceScale: (config) => config.supportsGuidanceScale === true,
|
|
1593
|
+
"image editing": (config) => config.supportsEdit,
|
|
1594
|
+
imagePromptStrength: (config) => config.supportsImagePromptStrength === true,
|
|
1595
|
+
imageSize: (config) => config.sizeMode === "gpt_size" || config.sizeMode === "image_size_enum",
|
|
1596
|
+
inputFidelity: (config) => config.sizeMode === "gpt_size",
|
|
1597
|
+
limitGenerations: (config) => config.supportsLimitGenerations === true,
|
|
1598
|
+
maskImageUrl: (config) => config.supportsMaskImage === true,
|
|
1599
|
+
negativePrompt: (config) => config.supportsNegativePrompt === true,
|
|
1600
|
+
numImages: (config) => config.supportsNumImages,
|
|
1601
|
+
numInferenceSteps: (config) => config.supportsInferenceSteps === true,
|
|
1602
|
+
outputFormat: (config) => config.supportsOutputFormat === true,
|
|
1603
|
+
quality: (config) => config.supportsQuality === true,
|
|
1604
|
+
raw: (config) => config.supportsRaw === true,
|
|
1605
|
+
renderingSpeed: (config) => config.supportsRenderingSpeed === true,
|
|
1606
|
+
resolution: (config) => config.supportsResolution,
|
|
1607
|
+
safetyTolerance: (config) => config.supportsSafetyTolerance === true,
|
|
1608
|
+
seed: (config) => config.supportsSeed === true,
|
|
1609
|
+
style: (config) => config.supportsStyle === true,
|
|
1610
|
+
syncMode: (config) => config.supportsSyncMode === true,
|
|
1611
|
+
thinkingLevel: (config) => config.supportsThinkingLevel === true,
|
|
1612
|
+
"transparent output": (config) => config.supportsBackground === true || config.transparentOutput === true
|
|
1155
1613
|
};
|
|
1614
|
+
function isModelOption(key) {
|
|
1615
|
+
return Object.hasOwn(OPTION_CAPABILITIES, key);
|
|
1616
|
+
}
|
|
1617
|
+
Object.keys(OPTION_CAPABILITIES).filter(isModelOption);
|
|
1618
|
+
({
|
|
1619
|
+
look: [
|
|
1620
|
+
{
|
|
1621
|
+
acceptsMood: true,
|
|
1622
|
+
aspect: "1:1",
|
|
1623
|
+
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",
|
|
1624
|
+
description: "Quiet, materially rich editorial photography for brand and mood imagery.",
|
|
1625
|
+
id: "editorial",
|
|
1626
|
+
label: "Quiet editorial",
|
|
1627
|
+
model: "flux2-pro"
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
acceptsMood: true,
|
|
1631
|
+
aspect: "1:1",
|
|
1632
|
+
clause: "Editorial still life on a warm bone plaster ground, chalky unglazed surfaces, a long soft shadow, generous empty space, shot on film with fine grain. No text, no logos, no people",
|
|
1633
|
+
description: "Objects and material samples on a plaster ground, for product and swatch shots.",
|
|
1634
|
+
id: "still-life",
|
|
1635
|
+
label: "Material still life",
|
|
1636
|
+
model: "flux2-pro"
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
acceptsMood: true,
|
|
1640
|
+
aspect: "3:2",
|
|
1641
|
+
clause: "Interior photograph 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, slightly imperfect and lived-in rather than showroom-perfect, photographic realism. No text, no logos, no people",
|
|
1642
|
+
description: "Bright, collected rooms that feel lived in, for interior scenes.",
|
|
1643
|
+
id: "lived-in",
|
|
1644
|
+
label: "Lived-in interior",
|
|
1645
|
+
model: "flux2-pro"
|
|
1646
|
+
},
|
|
1647
|
+
{
|
|
1648
|
+
acceptsMood: true,
|
|
1649
|
+
aspect: "4:5",
|
|
1650
|
+
clause: "Architectural editorial photograph at full room scale, honest materials meeting precise detailing, one hero element genuinely installed, plausible light and shadow, generous negative space, empty of people. No text, no logos",
|
|
1651
|
+
description: "Whole rooms with one product installed, for showing a material at scale.",
|
|
1652
|
+
id: "architectural",
|
|
1653
|
+
label: "Architectural scale",
|
|
1654
|
+
model: "banana"
|
|
1655
|
+
},
|
|
1656
|
+
{
|
|
1657
|
+
acceptsMood: true,
|
|
1658
|
+
aspect: "4:3",
|
|
1659
|
+
clause: "Amateur phone photo of a real home taken by the homeowner, slightly wonky framing, unstyled domestic photography, ordinary exposure. No text, no people",
|
|
1660
|
+
description: "Unstyled phone snapshots of real homes, for believable before and after shots.",
|
|
1661
|
+
id: "homeowner",
|
|
1662
|
+
label: "Homeowner snapshot",
|
|
1663
|
+
model: "seedream45"
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
acceptsMood: true,
|
|
1667
|
+
aspect: "1:1",
|
|
1668
|
+
clause: "Stylised architectural illustration of the room, colour laid as flat planes on walls, joinery and trim, fine hand-drawn line with a gentle gouache wash, clearly a drawing of a design decision rather than a photograph. No text, no people",
|
|
1669
|
+
description: "Line and gouache room drawings, for showing a colour scheme as a design idea.",
|
|
1670
|
+
experimental: true,
|
|
1671
|
+
id: "drawing",
|
|
1672
|
+
label: "Palette drawing",
|
|
1673
|
+
model: "gpt2"
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
acceptsMood: false,
|
|
1677
|
+
aspect: "1:1",
|
|
1678
|
+
clause: "Straight-on orthographic photograph of the surface filling the entire frame edge to edge, even shadowless studio light, crisp macro texture, colour-accurate. No text, no logos",
|
|
1679
|
+
description: "Flat, edge-to-edge surface photographs, for textures and material swatches.",
|
|
1680
|
+
id: "plate",
|
|
1681
|
+
label: "Flat plate",
|
|
1682
|
+
model: "flux2-pro"
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
acceptsMood: false,
|
|
1686
|
+
aspect: "1:1",
|
|
1687
|
+
clause: "Fine hand-engraved botanical plate with delicate hatching and dry brush, grey ink only, reaching near-black at its densest, on matte uncoated stock under flat even light, cropped mid-motif and running past all four edges, never simplified or cartoonish. No text",
|
|
1688
|
+
description: "Grey-ink botanical engravings that run off the edges, for patterns and backgrounds.",
|
|
1689
|
+
id: "engraved",
|
|
1690
|
+
label: "Engraved grey ink",
|
|
1691
|
+
model: "gpt2"
|
|
1692
|
+
},
|
|
1693
|
+
{
|
|
1694
|
+
acceptsMood: false,
|
|
1695
|
+
aspect: "2:3",
|
|
1696
|
+
clause: "Tightly cropped photograph of a single piece of late-1940s American printed matter, flat and square-on in even light, every pixel paper, letterpress and wood type, sun-faded ink, foxing, soft creases and thumbtack holes, era-correct typography, nothing that looks like a digital photo run through a filter",
|
|
1697
|
+
description: "Aged mid-century printed matter such as posters and cards, where the lettering matters.",
|
|
1698
|
+
id: "ephemera",
|
|
1699
|
+
label: "Period ephemera",
|
|
1700
|
+
model: "ideogram4"
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
acceptsMood: false,
|
|
1704
|
+
aspect: "3:4",
|
|
1705
|
+
clause: "Physical mineral pigment and chalk gesso on coarse natural linen, two or three confident gestures, warm ivory, oatmeal, putty and soft charcoal, flat diffuse museum reproduction lighting, shown unframed. No text",
|
|
1706
|
+
description: "Loose abstract paintings on linen, for wall art and calm backgrounds.",
|
|
1707
|
+
id: "canvas",
|
|
1708
|
+
label: "Linen abstract",
|
|
1709
|
+
model: "banana"
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
acceptsMood: true,
|
|
1713
|
+
aspect: "1:1",
|
|
1714
|
+
clause: "Editorial documentary portrait, muted warm palette, waist-up, unposed, plain clothing with no logos. No text",
|
|
1715
|
+
description: "Natural, unposed documentary portraits of people. Pair with a mood for the light.",
|
|
1716
|
+
id: "portrait",
|
|
1717
|
+
label: "Documentary portrait",
|
|
1718
|
+
model: "seedream45"
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
acceptsMood: false,
|
|
1722
|
+
aspect: "1:1",
|
|
1723
|
+
clause: "A single matte object centred with generous empty space, soft diffused studio light, minimal and quiet, one committed colour. No text, no logos, no people",
|
|
1724
|
+
description: "One object in one colour on a clean ground, for icons and simple product shots.",
|
|
1725
|
+
id: "object",
|
|
1726
|
+
label: "Studio object",
|
|
1727
|
+
model: "flux2-pro"
|
|
1728
|
+
}
|
|
1729
|
+
],
|
|
1730
|
+
mood: [
|
|
1731
|
+
{
|
|
1732
|
+
clause: "soft natural daylight from a window out of frame, gentle falloff into the corners, low contrast, even diffused light with soft, held highlights",
|
|
1733
|
+
description: "Soft, even daylight from a window. The safe default.",
|
|
1734
|
+
id: "window",
|
|
1735
|
+
label: "Window light"
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
clause: "early morning light through tall glazing, cool and clear",
|
|
1739
|
+
description: "Cool, clear early morning light.",
|
|
1740
|
+
id: "dawn",
|
|
1741
|
+
label: "Dawn"
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
clause: "low raking daylight from the left, long soft shadows that reveal texture",
|
|
1745
|
+
description: "Low side light that brings out surface texture.",
|
|
1746
|
+
id: "raking",
|
|
1747
|
+
label: "Raking light"
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
clause: "overcast afternoon with rain on a tall window, soft even grey light",
|
|
1751
|
+
description: "Soft grey light on a rainy afternoon.",
|
|
1752
|
+
id: "overcast",
|
|
1753
|
+
label: "Overcast"
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
clause: "evening, warm practical lamps around 2400K, candles and a lit fire, cosy and warm, never gloomy",
|
|
1757
|
+
description: "Warm evening light from lamps, candles and a fire.",
|
|
1758
|
+
id: "lamplit",
|
|
1759
|
+
label: "Lamplit evening"
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
clause: "night, one warm low practical light, deep shadow, candlelit",
|
|
1763
|
+
description: "Dark night scene lit by one warm low light.",
|
|
1764
|
+
id: "nocturne",
|
|
1765
|
+
label: "Nocturne"
|
|
1766
|
+
}
|
|
1767
|
+
]
|
|
1768
|
+
}).look;
|
|
1156
1769
|
//#endregion
|
|
1157
1770
|
//#region src/tool-registry/analysis.ts
|
|
1158
1771
|
const ANALYSIS_TOOLS = {
|
|
@@ -1164,7 +1777,11 @@ const ANALYSIS_TOOLS = {
|
|
|
1164
1777
|
inputKind: "images",
|
|
1165
1778
|
name: "GOT-OCR 2.0",
|
|
1166
1779
|
outputKeys: ["outputs"],
|
|
1167
|
-
price: {
|
|
1780
|
+
price: {
|
|
1781
|
+
kind: "call",
|
|
1782
|
+
per: ["input_image_urls"],
|
|
1783
|
+
usd: .05
|
|
1784
|
+
},
|
|
1168
1785
|
pricing: "$0.05/image",
|
|
1169
1786
|
queued: true,
|
|
1170
1787
|
sourceUrl: "https://fal.ai/models/fal-ai/got-ocr/v2",
|
|
@@ -1178,7 +1795,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1178
1795
|
inputKind: "image",
|
|
1179
1796
|
name: "Moondream 3 Caption",
|
|
1180
1797
|
outputKeys: ["output"],
|
|
1181
|
-
price: {
|
|
1798
|
+
price: {
|
|
1799
|
+
inputPerMillion: .4,
|
|
1800
|
+
inputTokens: 737,
|
|
1801
|
+
kind: "token",
|
|
1802
|
+
outputPerMillion: 3.5,
|
|
1803
|
+
outputTokens: 200
|
|
1804
|
+
},
|
|
1182
1805
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1183
1806
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/caption",
|
|
1184
1807
|
task: "image captioning"
|
|
@@ -1191,7 +1814,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1191
1814
|
inputKind: "image",
|
|
1192
1815
|
name: "Moondream 3 Detect",
|
|
1193
1816
|
outputKeys: ["objects", "image"],
|
|
1194
|
-
price: {
|
|
1817
|
+
price: {
|
|
1818
|
+
inputPerMillion: .4,
|
|
1819
|
+
inputTokens: 737,
|
|
1820
|
+
kind: "token",
|
|
1821
|
+
outputPerMillion: 3.5,
|
|
1822
|
+
outputTokens: 100
|
|
1823
|
+
},
|
|
1195
1824
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1196
1825
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/detect",
|
|
1197
1826
|
task: "object detection"
|
|
@@ -1204,7 +1833,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1204
1833
|
inputKind: "image",
|
|
1205
1834
|
name: "Moondream 3 Point",
|
|
1206
1835
|
outputKeys: ["points", "image"],
|
|
1207
|
-
price: {
|
|
1836
|
+
price: {
|
|
1837
|
+
inputPerMillion: .4,
|
|
1838
|
+
inputTokens: 737,
|
|
1839
|
+
kind: "token",
|
|
1840
|
+
outputPerMillion: 3.5,
|
|
1841
|
+
outputTokens: 100
|
|
1842
|
+
},
|
|
1208
1843
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1209
1844
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/point",
|
|
1210
1845
|
task: "object pointing"
|
|
@@ -1217,7 +1852,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1217
1852
|
inputKind: "image",
|
|
1218
1853
|
name: "Moondream 3 Query",
|
|
1219
1854
|
outputKeys: ["output", "reasoning"],
|
|
1220
|
-
price: {
|
|
1855
|
+
price: {
|
|
1856
|
+
inputPerMillion: .4,
|
|
1857
|
+
inputTokens: 737,
|
|
1858
|
+
kind: "token",
|
|
1859
|
+
outputPerMillion: 3.5,
|
|
1860
|
+
outputTokens: 500
|
|
1861
|
+
},
|
|
1221
1862
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1222
1863
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/query",
|
|
1223
1864
|
task: "visual question answering"
|
|
@@ -1230,7 +1871,11 @@ const ANALYSIS_TOOLS = {
|
|
|
1230
1871
|
inputKind: "images",
|
|
1231
1872
|
name: "NSFW Checker",
|
|
1232
1873
|
outputKeys: ["has_nsfw_concepts"],
|
|
1233
|
-
price: {
|
|
1874
|
+
price: {
|
|
1875
|
+
kind: "call",
|
|
1876
|
+
per: ["image_urls"],
|
|
1877
|
+
usd: .001
|
|
1878
|
+
},
|
|
1234
1879
|
pricing: "$0.001/image",
|
|
1235
1880
|
sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
|
|
1236
1881
|
task: "vision moderation"
|
|
@@ -1314,6 +1959,34 @@ const ASSET_TOOLS = {
|
|
|
1314
1959
|
sourceUrl: "https://fal.ai/models/fal-ai/image2svg",
|
|
1315
1960
|
task: "raster to vector tracing"
|
|
1316
1961
|
},
|
|
1962
|
+
"meshy-v7": {
|
|
1963
|
+
category: "3d",
|
|
1964
|
+
description: "Make a textured 3D mesh from one image, optionally rigged for animation.",
|
|
1965
|
+
endpoint: "meshy/v7/image-to-3d",
|
|
1966
|
+
inputField: "image_url",
|
|
1967
|
+
inputKind: "image",
|
|
1968
|
+
name: "Meshy v7 Image to 3D",
|
|
1969
|
+
outputKeys: [
|
|
1970
|
+
"model_glb",
|
|
1971
|
+
"model_urls",
|
|
1972
|
+
"thumbnail",
|
|
1973
|
+
"rigged_character_glb",
|
|
1974
|
+
"rigged_character_fbx"
|
|
1975
|
+
],
|
|
1976
|
+
price: {
|
|
1977
|
+
extras: {
|
|
1978
|
+
enable_animation: .12,
|
|
1979
|
+
enable_rigging: .2,
|
|
1980
|
+
ultra_mode: .2
|
|
1981
|
+
},
|
|
1982
|
+
kind: "call",
|
|
1983
|
+
usd: 1.2
|
|
1984
|
+
},
|
|
1985
|
+
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",
|
|
1986
|
+
queued: true,
|
|
1987
|
+
sourceUrl: "https://fal.ai/models/meshy/v7/image-to-3d",
|
|
1988
|
+
task: "single-image 3D reconstruction"
|
|
1989
|
+
},
|
|
1317
1990
|
patina: {
|
|
1318
1991
|
category: "material",
|
|
1319
1992
|
description: "Decompose a surface photograph into PBR maps: basecolor, normal, roughness, metalness, height.",
|
|
@@ -1332,7 +2005,12 @@ const ASSET_TOOLS = {
|
|
|
1332
2005
|
],
|
|
1333
2006
|
fromOption: "maps"
|
|
1334
2007
|
} },
|
|
1335
|
-
price: {
|
|
2008
|
+
price: {
|
|
2009
|
+
base: .01,
|
|
2010
|
+
kind: "maps",
|
|
2011
|
+
perMapMegapixel: .01,
|
|
2012
|
+
perMegapixel: 0
|
|
2013
|
+
},
|
|
1336
2014
|
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",
|
|
1337
2015
|
queued: true,
|
|
1338
2016
|
sourceUrl: "https://fal.ai/models/fal-ai/patina",
|
|
@@ -1356,8 +2034,17 @@ const ASSET_TOOLS = {
|
|
|
1356
2034
|
],
|
|
1357
2035
|
fromOption: "maps"
|
|
1358
2036
|
} },
|
|
1359
|
-
price: {
|
|
1360
|
-
|
|
2037
|
+
price: {
|
|
2038
|
+
base: .1,
|
|
2039
|
+
kind: "maps",
|
|
2040
|
+
perMapMegapixel: .01,
|
|
2041
|
+
perMegapixel: .02,
|
|
2042
|
+
upscale: {
|
|
2043
|
+
"2": .004,
|
|
2044
|
+
"4": .016
|
|
2045
|
+
}
|
|
2046
|
+
},
|
|
2047
|
+
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",
|
|
1361
2048
|
queued: true,
|
|
1362
2049
|
sourceUrl: "https://fal.ai/models/fal-ai/patina/material/extract",
|
|
1363
2050
|
task: "tiling material extraction"
|
|
@@ -1370,8 +2057,11 @@ const ASSET_TOOLS = {
|
|
|
1370
2057
|
inputKind: "image",
|
|
1371
2058
|
name: "Qwen Image Layered",
|
|
1372
2059
|
outputKeys: ["images"],
|
|
1373
|
-
price: {
|
|
1374
|
-
|
|
2060
|
+
price: {
|
|
2061
|
+
kind: "call",
|
|
2062
|
+
usd: .05
|
|
2063
|
+
},
|
|
2064
|
+
pricing: "$0.05 per image, one input image a call",
|
|
1375
2065
|
queued: true,
|
|
1376
2066
|
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-layered",
|
|
1377
2067
|
task: "image layer decomposition"
|
|
@@ -1438,7 +2128,6 @@ const ASSET_TOOLS = {
|
|
|
1438
2128
|
},
|
|
1439
2129
|
"sam3-3d-objects": {
|
|
1440
2130
|
category: "3d",
|
|
1441
|
-
defaultOptions: { prompt: "car" },
|
|
1442
2131
|
description: "Reconstruct one or more 3D objects from an image and prompts.",
|
|
1443
2132
|
endpoint: "fal-ai/sam-3/3d-objects",
|
|
1444
2133
|
inputField: "image_url",
|
|
@@ -1544,7 +2233,10 @@ const BACKGROUND_TOOLS = {
|
|
|
1544
2233
|
inputKind: "image",
|
|
1545
2234
|
name: "BirefNet Background Removal",
|
|
1546
2235
|
outputKeys: ["image", "mask_image"],
|
|
1547
|
-
price: {
|
|
2236
|
+
price: {
|
|
2237
|
+
kind: "call",
|
|
2238
|
+
usd: 0
|
|
2239
|
+
},
|
|
1548
2240
|
pricing: "$0/compute-second listed by fal",
|
|
1549
2241
|
sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
|
|
1550
2242
|
task: "image background removal"
|
|
@@ -1580,13 +2272,51 @@ const BACKGROUND_TOOLS = {
|
|
|
1580
2272
|
outputKeys: ["video"],
|
|
1581
2273
|
price: {
|
|
1582
2274
|
kind: "second",
|
|
1583
|
-
usd: .
|
|
2275
|
+
usd: .14
|
|
1584
2276
|
},
|
|
1585
|
-
pricing: "$0.
|
|
2277
|
+
pricing: "$0.14/sec",
|
|
1586
2278
|
queued: true,
|
|
1587
2279
|
sourceUrl: "https://fal.ai/models/bria/video/background-removal",
|
|
1588
2280
|
task: "video background removal"
|
|
1589
2281
|
},
|
|
2282
|
+
"bria-video-rmbg-v3": {
|
|
2283
|
+
category: "background",
|
|
2284
|
+
defaultOptions: {
|
|
2285
|
+
background_color: "Black",
|
|
2286
|
+
output_container_and_codec: "webm_vp9",
|
|
2287
|
+
preserve_audio: true
|
|
2288
|
+
},
|
|
2289
|
+
description: "Remove video backgrounds with Bria's VRMBG 3.0, with configurable output container.",
|
|
2290
|
+
endpoint: "bria/video/background-removal/v3",
|
|
2291
|
+
inputField: "video_url",
|
|
2292
|
+
inputKind: "video",
|
|
2293
|
+
name: "Bria Video Background Removal 3.0",
|
|
2294
|
+
outputKeys: ["video"],
|
|
2295
|
+
price: {
|
|
2296
|
+
kind: "second",
|
|
2297
|
+
usd: .05
|
|
2298
|
+
},
|
|
2299
|
+
pricing: "$0.05/sec",
|
|
2300
|
+
queued: true,
|
|
2301
|
+
sourceUrl: "https://fal.ai/models/bria/video/background-removal/v3",
|
|
2302
|
+
task: "video background removal"
|
|
2303
|
+
},
|
|
2304
|
+
"control-light": {
|
|
2305
|
+
category: "restoration",
|
|
2306
|
+
description: "Brighten a dark or underexposed photo, recovering detail while keeping its identity, geometry and colour.",
|
|
2307
|
+
endpoint: "fal-ai/control-light",
|
|
2308
|
+
inputField: "image_url",
|
|
2309
|
+
inputKind: "image",
|
|
2310
|
+
name: "Control Light",
|
|
2311
|
+
outputKeys: ["images"],
|
|
2312
|
+
price: {
|
|
2313
|
+
kind: "megapixel",
|
|
2314
|
+
usd: .03
|
|
2315
|
+
},
|
|
2316
|
+
pricing: "$0.03/megapixel",
|
|
2317
|
+
sourceUrl: "https://fal.ai/models/fal-ai/control-light",
|
|
2318
|
+
task: "low-light enhancement"
|
|
2319
|
+
},
|
|
1590
2320
|
ddcolor: {
|
|
1591
2321
|
category: "restoration",
|
|
1592
2322
|
description: "Colourise black-and-white photographs.",
|
|
@@ -1612,7 +2342,10 @@ const BACKGROUND_TOOLS = {
|
|
|
1612
2342
|
inputKind: "image",
|
|
1613
2343
|
name: "Remove Background",
|
|
1614
2344
|
outputKeys: ["image"],
|
|
1615
|
-
price: {
|
|
2345
|
+
price: {
|
|
2346
|
+
kind: "call",
|
|
2347
|
+
usd: 0
|
|
2348
|
+
},
|
|
1616
2349
|
pricing: "$0/compute-second listed by fal",
|
|
1617
2350
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
|
|
1618
2351
|
task: "image background removal"
|
|
@@ -1645,8 +2378,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1645
2378
|
name: "Topaz Adjust",
|
|
1646
2379
|
outputKeys: ["image"],
|
|
1647
2380
|
price: {
|
|
1648
|
-
kind: "megapixel",
|
|
1649
|
-
|
|
2381
|
+
kind: "megapixel-step",
|
|
2382
|
+
megapixels: 24,
|
|
2383
|
+
usd: .08
|
|
1650
2384
|
},
|
|
1651
2385
|
pricing: "$0.08 per 24 output megapixels",
|
|
1652
2386
|
queued: true,
|
|
@@ -1663,10 +2397,11 @@ const BACKGROUND_TOOLS = {
|
|
|
1663
2397
|
name: "Topaz Creative Upscale",
|
|
1664
2398
|
outputKeys: ["image"],
|
|
1665
2399
|
price: {
|
|
1666
|
-
kind: "megapixel",
|
|
1667
|
-
|
|
2400
|
+
kind: "megapixel-step",
|
|
2401
|
+
megapixels: 2,
|
|
2402
|
+
usd: .08
|
|
1668
2403
|
},
|
|
1669
|
-
pricing: "$0.
|
|
2404
|
+
pricing: "$0.08 per started 2 output megapixels, any Bloom model",
|
|
1670
2405
|
queued: true,
|
|
1671
2406
|
sourceUrl: "https://fal.ai/models/topaz/upscale/image/creative",
|
|
1672
2407
|
task: "creative image upscaling"
|
|
@@ -1681,8 +2416,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1681
2416
|
name: "Topaz Denoise",
|
|
1682
2417
|
outputKeys: ["image"],
|
|
1683
2418
|
price: {
|
|
1684
|
-
kind: "megapixel",
|
|
1685
|
-
|
|
2419
|
+
kind: "megapixel-step",
|
|
2420
|
+
megapixels: 24,
|
|
2421
|
+
usd: .08
|
|
1686
2422
|
},
|
|
1687
2423
|
pricing: "$0.08 per 24 output megapixels at the default Normal model; $0.16 with Denoise Max",
|
|
1688
2424
|
queued: true,
|
|
@@ -1699,10 +2435,11 @@ const BACKGROUND_TOOLS = {
|
|
|
1699
2435
|
name: "Topaz Generative Upscale",
|
|
1700
2436
|
outputKeys: ["image"],
|
|
1701
2437
|
price: {
|
|
1702
|
-
kind: "megapixel",
|
|
1703
|
-
|
|
2438
|
+
kind: "megapixel-step",
|
|
2439
|
+
megapixels: 8,
|
|
2440
|
+
usd: .08
|
|
1704
2441
|
},
|
|
1705
|
-
pricing: "$0.
|
|
2442
|
+
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",
|
|
1706
2443
|
queued: true,
|
|
1707
2444
|
sourceUrl: "https://fal.ai/models/topaz/upscale/image/generative",
|
|
1708
2445
|
task: "generative image upscaling"
|
|
@@ -1721,8 +2458,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1721
2458
|
name: "Topaz Image Upscale",
|
|
1722
2459
|
outputKeys: ["image"],
|
|
1723
2460
|
price: {
|
|
1724
|
-
kind: "megapixel",
|
|
1725
|
-
|
|
2461
|
+
kind: "megapixel-step",
|
|
2462
|
+
megapixels: 24,
|
|
2463
|
+
usd: .08
|
|
1726
2464
|
},
|
|
1727
2465
|
pricing: "$0.08 for output up to 24MP; $0.16 to 48MP, $0.32 to 96MP, up to $1.36 at 512MP",
|
|
1728
2466
|
queued: true,
|
|
@@ -1739,8 +2477,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1739
2477
|
name: "Topaz Precision Upscale",
|
|
1740
2478
|
outputKeys: ["image"],
|
|
1741
2479
|
price: {
|
|
1742
|
-
kind: "megapixel",
|
|
1743
|
-
|
|
2480
|
+
kind: "megapixel-step",
|
|
2481
|
+
megapixels: 24,
|
|
2482
|
+
usd: .08
|
|
1744
2483
|
},
|
|
1745
2484
|
pricing: "$0.08 per 24 output megapixels, any precision model",
|
|
1746
2485
|
queued: true,
|
|
@@ -1757,8 +2496,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1757
2496
|
name: "Topaz Restore",
|
|
1758
2497
|
outputKeys: ["image"],
|
|
1759
2498
|
price: {
|
|
1760
|
-
kind: "megapixel",
|
|
1761
|
-
|
|
2499
|
+
kind: "megapixel-step",
|
|
2500
|
+
megapixels: 24,
|
|
2501
|
+
usd: .48
|
|
1762
2502
|
},
|
|
1763
2503
|
pricing: "$0.48 per 24 output megapixels at the default Recover 3; $0.08 with Dust-Scratch V2",
|
|
1764
2504
|
queued: true,
|
|
@@ -1775,8 +2515,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1775
2515
|
name: "Topaz Sharpen",
|
|
1776
2516
|
outputKeys: ["image"],
|
|
1777
2517
|
price: {
|
|
1778
|
-
kind: "megapixel",
|
|
1779
|
-
|
|
2518
|
+
kind: "megapixel-step",
|
|
2519
|
+
megapixels: 24,
|
|
2520
|
+
usd: .08
|
|
1780
2521
|
},
|
|
1781
2522
|
pricing: "$0.08 per 24 output megapixels at the default Standard model; $0.16 with Super Focus",
|
|
1782
2523
|
queued: true,
|
|
@@ -1792,8 +2533,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1792
2533
|
name: "Topaz Transparent Upscale",
|
|
1793
2534
|
outputKeys: ["image"],
|
|
1794
2535
|
price: {
|
|
1795
|
-
kind: "megapixel",
|
|
1796
|
-
|
|
2536
|
+
kind: "megapixel-step",
|
|
2537
|
+
megapixels: 24,
|
|
2538
|
+
usd: .08
|
|
1797
2539
|
},
|
|
1798
2540
|
pricing: "$0.08 per 24 output megapixels",
|
|
1799
2541
|
queued: true,
|
|
@@ -1813,10 +2555,22 @@ const BACKGROUND_TOOLS = {
|
|
|
1813
2555
|
name: "Topaz Video Upscale",
|
|
1814
2556
|
outputKeys: ["video"],
|
|
1815
2557
|
price: {
|
|
1816
|
-
|
|
1817
|
-
|
|
2558
|
+
doubleAtFps: 60,
|
|
2559
|
+
halfWithModel: "Gaia 2",
|
|
2560
|
+
kind: "video-second",
|
|
2561
|
+
tiers: [
|
|
2562
|
+
{
|
|
2563
|
+
upTo: 720,
|
|
2564
|
+
usd: .01
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
upTo: 1080,
|
|
2568
|
+
usd: .02
|
|
2569
|
+
},
|
|
2570
|
+
{ usd: .08 }
|
|
2571
|
+
]
|
|
1818
2572
|
},
|
|
1819
|
-
pricing: "$0.01/sec up to 720p; $0.02 to 1080p, $0.08 above, doubled at 60fps, halved with Gaia 2",
|
|
2573
|
+
pricing: "$0.01/sec up to 720p output; $0.02 to 1080p, $0.08 above, doubled at 60fps, halved with Gaia 2",
|
|
1820
2574
|
queued: true,
|
|
1821
2575
|
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
|
|
1822
2576
|
task: "video enhancement"
|
|
@@ -1867,6 +2621,7 @@ const EDITING_TOOLS = {
|
|
|
1867
2621
|
outputKeys: ["images"],
|
|
1868
2622
|
price: {
|
|
1869
2623
|
kind: "call",
|
|
2624
|
+
perImage: true,
|
|
1870
2625
|
usd: .04
|
|
1871
2626
|
},
|
|
1872
2627
|
pricing: "$0.04/generation",
|
|
@@ -1897,7 +2652,11 @@ const EDITING_TOOLS = {
|
|
|
1897
2652
|
inputKind: "image",
|
|
1898
2653
|
name: "FLUX.2 Pro Outpaint",
|
|
1899
2654
|
outputKeys: ["images"],
|
|
1900
|
-
price: {
|
|
2655
|
+
price: {
|
|
2656
|
+
extra: .015,
|
|
2657
|
+
first: .03,
|
|
2658
|
+
kind: "megapixel-first"
|
|
2659
|
+
},
|
|
1901
2660
|
pricing: "$0.03 for the first output megapixel, then $0.015 per extra megapixel of input and output, rounded up",
|
|
1902
2661
|
sourceUrl: "https://fal.ai/models/fal-ai/flux-2-pro/outpaint",
|
|
1903
2662
|
task: "image outpainting"
|
|
@@ -1928,6 +2687,7 @@ const EDITING_TOOLS = {
|
|
|
1928
2687
|
outputKeys: ["images"],
|
|
1929
2688
|
price: {
|
|
1930
2689
|
kind: "call",
|
|
2690
|
+
perImage: true,
|
|
1931
2691
|
usd: .06
|
|
1932
2692
|
},
|
|
1933
2693
|
pricing: "$0.06/image at the default BALANCED speed; $0.03 turbo, $0.09 quality",
|
|
@@ -2022,12 +2782,35 @@ const EDITING_TOOLS = {
|
|
|
2022
2782
|
inputKind: "image",
|
|
2023
2783
|
name: "Smart Resize",
|
|
2024
2784
|
outputKeys: ["images", "results"],
|
|
2025
|
-
price: {
|
|
2785
|
+
price: {
|
|
2786
|
+
fee: .05,
|
|
2787
|
+
kind: "call",
|
|
2788
|
+
multipliers: { resolution: { "4K": 2 } },
|
|
2789
|
+
per: ["target_sizes", "num_images_per_size"],
|
|
2790
|
+
usd: .15
|
|
2791
|
+
},
|
|
2026
2792
|
pricing: "$0.15 per output image, doubled at 4K, plus a $0.05 vision analysis fee per request",
|
|
2027
2793
|
queued: true,
|
|
2028
2794
|
sourceUrl: "https://fal.ai/models/fal-ai/smart-resize",
|
|
2029
2795
|
task: "multi-size recomposition"
|
|
2030
2796
|
},
|
|
2797
|
+
"telestyle-v2": {
|
|
2798
|
+
category: "restyle",
|
|
2799
|
+
description: "Redraw a content image in the style of a second, style image.",
|
|
2800
|
+
endpoint: "fal-ai/telestyle-v2",
|
|
2801
|
+
inputField: "content_image_url",
|
|
2802
|
+
inputKind: "image",
|
|
2803
|
+
name: "TeleStyle v2",
|
|
2804
|
+
outputKeys: ["images"],
|
|
2805
|
+
price: {
|
|
2806
|
+
kind: "megapixel",
|
|
2807
|
+
usd: .035
|
|
2808
|
+
},
|
|
2809
|
+
pricing: "$0.035/megapixel",
|
|
2810
|
+
referenceField: "style_image_url",
|
|
2811
|
+
sourceUrl: "https://fal.ai/models/fal-ai/telestyle-v2",
|
|
2812
|
+
task: "style transfer from a reference image"
|
|
2813
|
+
},
|
|
2031
2814
|
"text-removal": {
|
|
2032
2815
|
category: "erase",
|
|
2033
2816
|
defaultOptions: { output_format: "png" },
|
|
@@ -2044,6 +2827,25 @@ const EDITING_TOOLS = {
|
|
|
2044
2827
|
pricing: "$0.04/image",
|
|
2045
2828
|
sourceUrl: "https://fal.ai/models/fal-ai/image-editing/text-removal",
|
|
2046
2829
|
task: "text removal"
|
|
2830
|
+
},
|
|
2831
|
+
"virtual-try-on": {
|
|
2832
|
+
category: "try-on",
|
|
2833
|
+
description: "Dress the person in one image in the garment shown in another.",
|
|
2834
|
+
endpoint: "google/virtual-try-on",
|
|
2835
|
+
inputField: "person_image_url",
|
|
2836
|
+
inputKind: "image",
|
|
2837
|
+
name: "Google Virtual Try-On",
|
|
2838
|
+
outputKeys: ["images"],
|
|
2839
|
+
price: {
|
|
2840
|
+
kind: "call",
|
|
2841
|
+
perImage: true,
|
|
2842
|
+
usd: .075
|
|
2843
|
+
},
|
|
2844
|
+
pricing: "$0.075/image",
|
|
2845
|
+
queued: true,
|
|
2846
|
+
referenceField: "product_image_url",
|
|
2847
|
+
sourceUrl: "https://fal.ai/models/google/virtual-try-on",
|
|
2848
|
+
task: "virtual garment try-on"
|
|
2047
2849
|
}
|
|
2048
2850
|
};
|
|
2049
2851
|
//#endregion
|
|
@@ -2057,7 +2859,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2057
2859
|
inputKind: "image",
|
|
2058
2860
|
name: "Depth Anything v2 Preprocessor",
|
|
2059
2861
|
outputKeys: ["image"],
|
|
2060
|
-
price: {
|
|
2862
|
+
price: {
|
|
2863
|
+
kind: "call",
|
|
2864
|
+
usd: 0
|
|
2865
|
+
},
|
|
2061
2866
|
pricing: "$0/compute-second listed by fal",
|
|
2062
2867
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
|
|
2063
2868
|
task: "depth preprocessing"
|
|
@@ -2086,7 +2891,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2086
2891
|
inputKind: "image",
|
|
2087
2892
|
name: "HED Edge Preprocessor",
|
|
2088
2893
|
outputKeys: ["image"],
|
|
2089
|
-
price: {
|
|
2894
|
+
price: {
|
|
2895
|
+
kind: "call",
|
|
2896
|
+
usd: 0
|
|
2897
|
+
},
|
|
2090
2898
|
pricing: "$0/compute-second listed by fal",
|
|
2091
2899
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/hed",
|
|
2092
2900
|
task: "edge preprocessing"
|
|
@@ -2100,7 +2908,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2100
2908
|
inputKind: "image",
|
|
2101
2909
|
name: "Line Art Preprocessor",
|
|
2102
2910
|
outputKeys: ["image"],
|
|
2103
|
-
price: {
|
|
2911
|
+
price: {
|
|
2912
|
+
kind: "call",
|
|
2913
|
+
usd: 0
|
|
2914
|
+
},
|
|
2104
2915
|
pricing: "$0/compute-second listed by fal",
|
|
2105
2916
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
|
|
2106
2917
|
task: "image preprocessing"
|
|
@@ -2117,7 +2928,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2117
2928
|
inputKind: "image",
|
|
2118
2929
|
name: "Marigold Depth Estimation",
|
|
2119
2930
|
outputKeys: ["image"],
|
|
2120
|
-
price: {
|
|
2931
|
+
price: {
|
|
2932
|
+
kind: "call",
|
|
2933
|
+
usd: 0
|
|
2934
|
+
},
|
|
2121
2935
|
pricing: "$0/compute-second listed by fal",
|
|
2122
2936
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
|
|
2123
2937
|
task: "depth map"
|
|
@@ -2134,7 +2948,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2134
2948
|
inputKind: "image",
|
|
2135
2949
|
name: "MiDaS Depth Estimation",
|
|
2136
2950
|
outputKeys: ["image"],
|
|
2137
|
-
price: {
|
|
2951
|
+
price: {
|
|
2952
|
+
kind: "call",
|
|
2953
|
+
usd: 0
|
|
2954
|
+
},
|
|
2138
2955
|
pricing: "$0/compute-second listed by fal",
|
|
2139
2956
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
|
|
2140
2957
|
task: "depth map"
|
|
@@ -2147,7 +2964,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2147
2964
|
inputKind: "image",
|
|
2148
2965
|
name: "MiDaS Preprocessor",
|
|
2149
2966
|
outputKeys: ["depth_map", "normal_map"],
|
|
2150
|
-
price: {
|
|
2967
|
+
price: {
|
|
2968
|
+
kind: "call",
|
|
2969
|
+
usd: 0
|
|
2970
|
+
},
|
|
2151
2971
|
pricing: "$0/compute-second listed by fal",
|
|
2152
2972
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
|
|
2153
2973
|
task: "depth and normal preprocessing"
|
|
@@ -2160,7 +2980,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2160
2980
|
inputKind: "image",
|
|
2161
2981
|
name: "M-LSD Line Preprocessor",
|
|
2162
2982
|
outputKeys: ["image"],
|
|
2163
|
-
price: {
|
|
2983
|
+
price: {
|
|
2984
|
+
kind: "call",
|
|
2985
|
+
usd: 0
|
|
2986
|
+
},
|
|
2164
2987
|
pricing: "$0/compute-second listed by fal",
|
|
2165
2988
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/mlsd",
|
|
2166
2989
|
task: "line segment preprocessing"
|
|
@@ -2173,7 +2996,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2173
2996
|
inputKind: "image",
|
|
2174
2997
|
name: "PiDiNet Edge Preprocessor",
|
|
2175
2998
|
outputKeys: ["image"],
|
|
2176
|
-
price: {
|
|
2999
|
+
price: {
|
|
3000
|
+
kind: "call",
|
|
3001
|
+
usd: 0
|
|
3002
|
+
},
|
|
2177
3003
|
pricing: "$0/compute-second listed by fal",
|
|
2178
3004
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/pidi",
|
|
2179
3005
|
task: "edge preprocessing"
|
|
@@ -2186,7 +3012,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2186
3012
|
inputKind: "image",
|
|
2187
3013
|
name: "SAM Preprocessor",
|
|
2188
3014
|
outputKeys: ["image"],
|
|
2189
|
-
price: {
|
|
3015
|
+
price: {
|
|
3016
|
+
kind: "call",
|
|
3017
|
+
usd: 0
|
|
3018
|
+
},
|
|
2190
3019
|
pricing: "$0/compute-second listed by fal",
|
|
2191
3020
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
|
|
2192
3021
|
task: "segmentation preprocessing"
|
|
@@ -2206,7 +3035,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2206
3035
|
inputKind: "image",
|
|
2207
3036
|
name: "SAM 2 Auto Segment",
|
|
2208
3037
|
outputKeys: ["combined_mask", "individual_masks"],
|
|
2209
|
-
price: {
|
|
3038
|
+
price: {
|
|
3039
|
+
kind: "call",
|
|
3040
|
+
usd: 0
|
|
3041
|
+
},
|
|
2210
3042
|
pricing: "$0/compute-second listed by fal",
|
|
2211
3043
|
queued: true,
|
|
2212
3044
|
sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
|
|
@@ -2251,7 +3083,11 @@ const STRUCTURE_TOOLS = {
|
|
|
2251
3083
|
inputKind: "video",
|
|
2252
3084
|
name: "SAM 3.1 Video",
|
|
2253
3085
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2254
|
-
price: {
|
|
3086
|
+
price: {
|
|
3087
|
+
frames: 16,
|
|
3088
|
+
kind: "frames",
|
|
3089
|
+
usd: .01
|
|
3090
|
+
},
|
|
2255
3091
|
pricing: "$0.01/16 frames of video input",
|
|
2256
3092
|
queued: true,
|
|
2257
3093
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
|
|
@@ -2323,7 +3159,11 @@ const STRUCTURE_TOOLS = {
|
|
|
2323
3159
|
inputKind: "video",
|
|
2324
3160
|
name: "SAM 3 Video",
|
|
2325
3161
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2326
|
-
price: {
|
|
3162
|
+
price: {
|
|
3163
|
+
frames: 16,
|
|
3164
|
+
kind: "frames",
|
|
3165
|
+
usd: .005
|
|
3166
|
+
},
|
|
2327
3167
|
pricing: "$0.005/16 frames of video input",
|
|
2328
3168
|
queued: true,
|
|
2329
3169
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
|
|
@@ -2342,7 +3182,11 @@ const STRUCTURE_TOOLS = {
|
|
|
2342
3182
|
inputKind: "video",
|
|
2343
3183
|
name: "SAM 3 Video RLE",
|
|
2344
3184
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2345
|
-
price: {
|
|
3185
|
+
price: {
|
|
3186
|
+
frames: 16,
|
|
3187
|
+
kind: "frames",
|
|
3188
|
+
usd: .005
|
|
3189
|
+
},
|
|
2346
3190
|
pricing: "$0.005/16 frames of video",
|
|
2347
3191
|
queued: true,
|
|
2348
3192
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
|
|
@@ -2356,7 +3200,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2356
3200
|
inputKind: "image",
|
|
2357
3201
|
name: "Scribble Preprocessor",
|
|
2358
3202
|
outputKeys: ["image"],
|
|
2359
|
-
price: {
|
|
3203
|
+
price: {
|
|
3204
|
+
kind: "call",
|
|
3205
|
+
usd: 0
|
|
3206
|
+
},
|
|
2360
3207
|
pricing: "$0/compute-second listed by fal",
|
|
2361
3208
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/scribble",
|
|
2362
3209
|
task: "scribble preprocessing"
|
|
@@ -2369,7 +3216,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2369
3216
|
inputKind: "image",
|
|
2370
3217
|
name: "TEED Edge Preprocessor",
|
|
2371
3218
|
outputKeys: ["image"],
|
|
2372
|
-
price: {
|
|
3219
|
+
price: {
|
|
3220
|
+
kind: "call",
|
|
3221
|
+
usd: 0
|
|
3222
|
+
},
|
|
2373
3223
|
pricing: "$0/compute-second listed by fal",
|
|
2374
3224
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/teed",
|
|
2375
3225
|
task: "edge preprocessing"
|
|
@@ -2382,7 +3232,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2382
3232
|
inputKind: "image",
|
|
2383
3233
|
name: "ZoeDepth Preprocessor",
|
|
2384
3234
|
outputKeys: ["image"],
|
|
2385
|
-
price: {
|
|
3235
|
+
price: {
|
|
3236
|
+
kind: "call",
|
|
3237
|
+
usd: 0
|
|
3238
|
+
},
|
|
2386
3239
|
pricing: "$0/compute-second listed by fal",
|
|
2387
3240
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/zoe",
|
|
2388
3241
|
task: "depth preprocessing"
|
|
@@ -2399,6 +3252,17 @@ const FAL_TOOLS = {
|
|
|
2399
3252
|
};
|
|
2400
3253
|
Object.keys(FAL_TOOLS);
|
|
2401
3254
|
//#endregion
|
|
3255
|
+
//#region src/image/fetch.ts
|
|
3256
|
+
function urlOf(input) {
|
|
3257
|
+
if (typeof input === "string") return input;
|
|
3258
|
+
return input instanceof URL ? input.href : input.url;
|
|
3259
|
+
}
|
|
3260
|
+
/** Adapt a configured fetch for a provider factory; undefined keeps global fetch. */
|
|
3261
|
+
function toProviderFetch(fetch) {
|
|
3262
|
+
if (fetch === void 0) return {};
|
|
3263
|
+
return { fetch: async (input, init) => await fetch(urlOf(input), init ?? {}) };
|
|
3264
|
+
}
|
|
3265
|
+
//#endregion
|
|
2402
3266
|
//#region src/image/fal.ts
|
|
2403
3267
|
/**
|
|
2404
3268
|
* fal provider adapter.
|
|
@@ -2416,35 +3280,22 @@ Object.keys(FAL_TOOLS);
|
|
|
2416
3280
|
* need a specific size. Example:
|
|
2417
3281
|
* img.generate({
|
|
2418
3282
|
* provider: "fal",
|
|
2419
|
-
*
|
|
3283
|
+
* model: "fal-ai/gpt-image-1.5",
|
|
2420
3284
|
* prompt: "...",
|
|
2421
3285
|
* providerOptions: { fal: { image_size: "1024x1024" } },
|
|
2422
3286
|
* });
|
|
2423
3287
|
*/
|
|
2424
3288
|
/**
|
|
2425
|
-
* fal
|
|
2426
|
-
*
|
|
2427
|
-
* endpoints that also exist in the `../models` registry snapshot.
|
|
3289
|
+
* fal endpoint ids proven to work with this adapter and priced in
|
|
3290
|
+
* {@link FAL_IMAGE_PRICE_USD} below (FLUX Pro Ultra + gpt-image).
|
|
2428
3291
|
*/
|
|
2429
3292
|
const FAL_FLUX_MODEL = "fal-ai/flux-pro/v1.1-ultra";
|
|
2430
3293
|
const FAL_GPT_IMAGE_MODEL = "fal-ai/gpt-image-1.5";
|
|
2431
|
-
/**
|
|
2432
|
-
* Tier → fal model id. For fal, explicit `model:` endpoint ids are the primary
|
|
2433
|
-
* path (any fal endpoint resolves via passthrough); this tier map is a
|
|
2434
|
-
* convenience covering the two most common (FLUX Pro Ultra + gpt-image).
|
|
2435
|
-
*/
|
|
2436
|
-
const FAL_TIER_MODELS = {
|
|
2437
|
-
fast: FAL_FLUX_MODEL,
|
|
2438
|
-
balanced: FAL_GPT_IMAGE_MODEL,
|
|
2439
|
-
quality: FAL_GPT_IMAGE_MODEL,
|
|
2440
|
-
hero: FAL_GPT_IMAGE_MODEL
|
|
2441
|
-
};
|
|
2442
3294
|
/** Env var read for the fal key when `apiKey` is not supplied in config. */
|
|
2443
3295
|
const FAL_API_KEY_ENV = "FAL_KEY";
|
|
2444
3296
|
/**
|
|
2445
|
-
* Static fal USD/image, keyed by the fal ENDPOINT id.
|
|
2446
|
-
*
|
|
2447
|
-
* must price the endpoints consumers actually pass — kiln uses
|
|
3297
|
+
* Static fal USD/image, keyed by the fal ENDPOINT id. Cost tracking must price
|
|
3298
|
+
* the endpoints consumers actually pass — kiln uses
|
|
2448
3299
|
* `fal-ai/flux/schnell`, `fal-ai/flux-2-pro`, `fal-ai/flux-2-max`. Every price is
|
|
2449
3300
|
* sourced from the fal registry snapshot in `../models`
|
|
2450
3301
|
* (`MODELS[...].pricePerImageUsd`) so this table stays in sync rather than
|
|
@@ -2471,12 +3322,17 @@ const FAL_IMAGE_PRICE_USD = {
|
|
|
2471
3322
|
"fal-ai/bytedance/seedream/v4/text-to-image": MODELS.seedream4?.pricePerImageUsd ?? .03,
|
|
2472
3323
|
"fal-ai/bytedance/seedream/v4.5/text-to-image": MODELS.seedream45?.pricePerImageUsd ?? .04,
|
|
2473
3324
|
"bytedance/seedream/v5/pro/text-to-image": MODELS.seedream5?.pricePerImageUsd ?? .0675,
|
|
2474
|
-
"
|
|
2475
|
-
"fal-ai/recraft-
|
|
3325
|
+
"bytedance/seedream/v5/lite/text-to-image": MODELS["seedream5-lite"]?.pricePerImageUsd ?? .035,
|
|
3326
|
+
"fal-ai/recraft/v3/text-to-image": MODELS.recraft?.pricePerImageUsd ?? .04,
|
|
2476
3327
|
"fal-ai/recraft/v4/text-to-image": MODELS.recraft4?.pricePerImageUsd ?? .04,
|
|
3328
|
+
"fal-ai/recraft/v4.1/text-to-image": MODELS.recraft41?.pricePerImageUsd ?? .035,
|
|
2477
3329
|
"fal-ai/ideogram/v3": MODELS.ideogram?.pricePerImageUsd ?? .03,
|
|
2478
3330
|
"ideogram/v4": MODELS.ideogram4?.pricePerImageUsd ?? .03,
|
|
2479
3331
|
"xai/grok-imagine-image": MODELS["grok-image"]?.pricePerImageUsd ?? .02,
|
|
3332
|
+
"xai/grok-imagine-image/v2.0/text-to-image": MODELS["grok-image-2"]?.pricePerImageUsd ?? .06,
|
|
3333
|
+
"microsoft/mai-image-2.5-pro": MODELS["mai-image-2.5-pro"]?.pricePerImageUsd ?? .17,
|
|
3334
|
+
"fal-ai/ideogram/v3/generate-transparent": MODELS["ideogram3-transparent"]?.pricePerImageUsd ?? .06,
|
|
3335
|
+
"alibaba/qwen-image-3/text-to-image": MODELS.qwen3?.pricePerImageUsd ?? .04,
|
|
2480
3336
|
"fal-ai/qwen-image": MODELS.qwen?.pricePerImageUsd ?? .02
|
|
2481
3337
|
};
|
|
2482
3338
|
/**
|
|
@@ -2484,15 +3340,17 @@ const FAL_IMAGE_PRICE_USD = {
|
|
|
2484
3340
|
* var. Throws `MotifError` when neither is present (callers translate this into
|
|
2485
3341
|
* a `Result.err`).
|
|
2486
3342
|
*/
|
|
2487
|
-
function resolveModel$3(modelId, apiKey) {
|
|
3343
|
+
function resolveModel$3(modelId, apiKey, fetch) {
|
|
2488
3344
|
const key = apiKey ?? process.env["FAL_KEY"];
|
|
2489
3345
|
if (key === void 0 || key === "") throw new MotifError(`fal image generation requires an API key (config.fal.apiKey or ${FAL_API_KEY_ENV})`, 0);
|
|
2490
|
-
return createFal({
|
|
3346
|
+
return createFal({
|
|
3347
|
+
apiKey: key,
|
|
3348
|
+
...toProviderFetch(fetch)
|
|
3349
|
+
}).image(modelId);
|
|
2491
3350
|
}
|
|
2492
3351
|
/** The fal provider adapter registered in the provider registry. */
|
|
2493
3352
|
const falAdapter = {
|
|
2494
3353
|
id: "fal",
|
|
2495
|
-
tierModels: FAL_TIER_MODELS,
|
|
2496
3354
|
apiKeyEnv: FAL_API_KEY_ENV,
|
|
2497
3355
|
resolveModel: resolveModel$3,
|
|
2498
3356
|
priceUsdByModel: FAL_IMAGE_PRICE_USD
|
|
@@ -2508,19 +3366,6 @@ const falAdapter = {
|
|
|
2508
3366
|
* multi-image-in → image-out editing (with an optional mask), which is the core
|
|
2509
3367
|
* operation this layer normalizes.
|
|
2510
3368
|
*/
|
|
2511
|
-
/**
|
|
2512
|
-
* Tier → Gemini image model id.
|
|
2513
|
-
*
|
|
2514
|
-
* Seeded from Material Desk's `RENDER_IMAGE_MODEL_BY_QUALITY` (the driving
|
|
2515
|
-
* consumer, see the design doc). `gemini-2.5-flash-image` is the proven-reachable
|
|
2516
|
-
* floor; the preview ids may require allowlist/tier access.
|
|
2517
|
-
*/
|
|
2518
|
-
const GOOGLE_TIER_MODELS = {
|
|
2519
|
-
fast: "gemini-2.5-flash-image",
|
|
2520
|
-
balanced: "gemini-3.1-flash-image-preview",
|
|
2521
|
-
quality: "gemini-3-pro-image-preview",
|
|
2522
|
-
hero: "gemini-3-pro-image-preview"
|
|
2523
|
-
};
|
|
2524
3369
|
/** Env var read for the Google API key when `apiKey` is not supplied in config. */
|
|
2525
3370
|
const GOOGLE_API_KEY_ENV = "GOOGLE_GENERATIVE_AI_API_KEY";
|
|
2526
3371
|
/**
|
|
@@ -2550,15 +3395,17 @@ const GOOGLE_IMAGE_PRICE_USD = {
|
|
|
2550
3395
|
* `GOOGLE_GENERATIVE_AI_API_KEY` env var. Throws `MotifError` when neither is
|
|
2551
3396
|
* present (callers translate this into a `Result.err`).
|
|
2552
3397
|
*/
|
|
2553
|
-
function resolveModel$2(modelId, apiKey) {
|
|
3398
|
+
function resolveModel$2(modelId, apiKey, fetch) {
|
|
2554
3399
|
const key = apiKey ?? process.env["GOOGLE_GENERATIVE_AI_API_KEY"];
|
|
2555
3400
|
if (key === void 0 || key === "") throw new MotifError(`Google image generation requires an API key (config.google.apiKey or ${GOOGLE_API_KEY_ENV})`, 0);
|
|
2556
|
-
return createGoogleGenerativeAI({
|
|
3401
|
+
return createGoogleGenerativeAI({
|
|
3402
|
+
apiKey: key,
|
|
3403
|
+
...toProviderFetch(fetch)
|
|
3404
|
+
}).image(modelId);
|
|
2557
3405
|
}
|
|
2558
3406
|
/** The Google (Gemini) provider adapter registered in the provider registry. */
|
|
2559
3407
|
const googleAdapter = {
|
|
2560
3408
|
id: "google",
|
|
2561
|
-
tierModels: GOOGLE_TIER_MODELS,
|
|
2562
3409
|
apiKeyEnv: GOOGLE_API_KEY_ENV,
|
|
2563
3410
|
resolveModel: resolveModel$2,
|
|
2564
3411
|
priceUsdByModel: GOOGLE_IMAGE_PRICE_USD
|
|
@@ -2572,20 +3419,6 @@ const googleAdapter = {
|
|
|
2572
3419
|
* performs no network I/O — the request only happens when `generateImage`
|
|
2573
3420
|
* invokes `model.doGenerate`.
|
|
2574
3421
|
*/
|
|
2575
|
-
/**
|
|
2576
|
-
* OpenAI's image API currently exposes a single gpt-image model, so every tier
|
|
2577
|
-
* maps to `gpt-image-1`. Tiers will later differ by `quality`
|
|
2578
|
-
* (low/medium/high/auto) passed via `providerOptions.openai` (Phase 1c); until
|
|
2579
|
-
* then the tier only selects this one model.
|
|
2580
|
-
*/
|
|
2581
|
-
const OPENAI_MODEL = "gpt-image-1";
|
|
2582
|
-
/** Tier → OpenAI image model id (all tiers → the single gpt-image model). */
|
|
2583
|
-
const OPENAI_TIER_MODELS = {
|
|
2584
|
-
fast: OPENAI_MODEL,
|
|
2585
|
-
balanced: OPENAI_MODEL,
|
|
2586
|
-
quality: OPENAI_MODEL,
|
|
2587
|
-
hero: OPENAI_MODEL
|
|
2588
|
-
};
|
|
2589
3422
|
/** Env var read for the OpenAI API key when `apiKey` is not supplied in config. */
|
|
2590
3423
|
const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
|
|
2591
3424
|
/**
|
|
@@ -2597,6 +3430,11 @@ const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
|
|
|
2597
3430
|
* Source: https://platform.openai.com/docs/pricing (image generation)
|
|
2598
3431
|
* Sanity anchor: the Phase 0 benchmark measured gpt-image direct at $0.042
|
|
2599
3432
|
* (vs $0.133 via fal — see docs/design/provider-agnostic-image-layer.md §10).
|
|
3433
|
+
*
|
|
3434
|
+
* GPT Image 2.5 is token-priced, with no published per-image estimate. Leave
|
|
3435
|
+
* these models absent so cost remains unknown unless supplied by the provider.
|
|
3436
|
+
* https://developers.openai.com/api/docs/models/gpt-image-2.5-flare
|
|
3437
|
+
* https://developers.openai.com/api/docs/models/gpt-image-2.5-sunburst
|
|
2600
3438
|
*/
|
|
2601
3439
|
const OPENAI_IMAGE_PRICE_USD = { "gpt-image-1": .042 };
|
|
2602
3440
|
/**
|
|
@@ -2604,15 +3442,17 @@ const OPENAI_IMAGE_PRICE_USD = { "gpt-image-1": .042 };
|
|
|
2604
3442
|
* `OPENAI_API_KEY` env var. Throws `MotifError` when neither is present
|
|
2605
3443
|
* (callers translate this into a `Result.err`).
|
|
2606
3444
|
*/
|
|
2607
|
-
function resolveModel$1(modelId, apiKey) {
|
|
3445
|
+
function resolveModel$1(modelId, apiKey, fetch) {
|
|
2608
3446
|
const key = apiKey ?? process.env["OPENAI_API_KEY"];
|
|
2609
3447
|
if (key === void 0 || key === "") throw new MotifError(`OpenAI image generation requires an API key (config.openai.apiKey or ${OPENAI_API_KEY_ENV})`, 0);
|
|
2610
|
-
return createOpenAI({
|
|
3448
|
+
return createOpenAI({
|
|
3449
|
+
apiKey: key,
|
|
3450
|
+
...toProviderFetch(fetch)
|
|
3451
|
+
}).image(modelId);
|
|
2611
3452
|
}
|
|
2612
3453
|
/** The OpenAI provider adapter registered in the provider registry. */
|
|
2613
3454
|
const openaiAdapter = {
|
|
2614
3455
|
id: "openai",
|
|
2615
|
-
tierModels: OPENAI_TIER_MODELS,
|
|
2616
3456
|
apiKeyEnv: OPENAI_API_KEY_ENV,
|
|
2617
3457
|
resolveModel: resolveModel$1,
|
|
2618
3458
|
priceUsdByModel: OPENAI_IMAGE_PRICE_USD
|
|
@@ -2629,19 +3469,6 @@ const openaiAdapter = {
|
|
|
2629
3469
|
* NOTE: Replicate's SDK names the credential option `apiToken` (not `apiKey`),
|
|
2630
3470
|
* and reads `REPLICATE_API_TOKEN` from the environment.
|
|
2631
3471
|
*/
|
|
2632
|
-
/**
|
|
2633
|
-
* Replicate is wired to a single high-quality model for now, so every tier maps
|
|
2634
|
-
* to FLUX 1.1 Pro Ultra. (The benchmark found Replicate ~1.45× faster than fal
|
|
2635
|
-
* for this model at the same price — see the design doc §10.)
|
|
2636
|
-
*/
|
|
2637
|
-
const REPLICATE_MODEL = "black-forest-labs/flux-1.1-pro-ultra";
|
|
2638
|
-
/** Tier → Replicate model id (all tiers → FLUX 1.1 Pro Ultra for now). */
|
|
2639
|
-
const REPLICATE_TIER_MODELS = {
|
|
2640
|
-
fast: REPLICATE_MODEL,
|
|
2641
|
-
balanced: REPLICATE_MODEL,
|
|
2642
|
-
quality: REPLICATE_MODEL,
|
|
2643
|
-
hero: REPLICATE_MODEL
|
|
2644
|
-
};
|
|
2645
3472
|
/** Env var read for the Replicate API token when `apiToken` is not in config. */
|
|
2646
3473
|
const REPLICATE_API_KEY_ENV = "REPLICATE_API_TOKEN";
|
|
2647
3474
|
/**
|
|
@@ -2658,10 +3485,13 @@ const REPLICATE_IMAGE_PRICE_USD = { "black-forest-labs/flux-1.1-pro-ultra": .06
|
|
|
2658
3485
|
* token), else the `REPLICATE_API_TOKEN` env var. Throws `MotifError` when
|
|
2659
3486
|
* neither is present (callers translate this into a `Result.err`).
|
|
2660
3487
|
*/
|
|
2661
|
-
function resolveModel(modelId, apiKey) {
|
|
3488
|
+
function resolveModel(modelId, apiKey, fetch) {
|
|
2662
3489
|
const token = apiKey ?? process.env["REPLICATE_API_TOKEN"];
|
|
2663
3490
|
if (token === void 0 || token === "") throw new MotifError(`Replicate image generation requires an API token (config.replicate.apiToken or ${REPLICATE_API_KEY_ENV})`, 0);
|
|
2664
|
-
return createReplicate({
|
|
3491
|
+
return createReplicate({
|
|
3492
|
+
apiToken: token,
|
|
3493
|
+
...toProviderFetch(fetch)
|
|
3494
|
+
}).image(modelId);
|
|
2665
3495
|
}
|
|
2666
3496
|
//#endregion
|
|
2667
3497
|
//#region src/image/provider.ts
|
|
@@ -2675,7 +3505,6 @@ const PROVIDERS = {
|
|
|
2675
3505
|
openai: openaiAdapter,
|
|
2676
3506
|
replicate: {
|
|
2677
3507
|
id: "replicate",
|
|
2678
|
-
tierModels: REPLICATE_TIER_MODELS,
|
|
2679
3508
|
apiKeyEnv: REPLICATE_API_KEY_ENV,
|
|
2680
3509
|
resolveModel,
|
|
2681
3510
|
priceUsdByModel: REPLICATE_IMAGE_PRICE_USD
|
|
@@ -2719,6 +3548,9 @@ function costFromProviderMetadata(providerMetadata) {
|
|
|
2719
3548
|
}
|
|
2720
3549
|
}
|
|
2721
3550
|
/** Static per-image USD for a (provider, model), or undefined if unknown. */
|
|
3551
|
+
function providerPricePerImageUsd(provider, modelId) {
|
|
3552
|
+
return tablePricePerImage(provider, modelId);
|
|
3553
|
+
}
|
|
2722
3554
|
function tablePricePerImage(provider, modelId) {
|
|
2723
3555
|
const adapter = PROVIDERS[provider];
|
|
2724
3556
|
if (adapter === void 0) return;
|
|
@@ -2753,8 +3585,8 @@ function costForImages(provider, modelId, providerMetadata, imageCount) {
|
|
|
2753
3585
|
* `@howells/motif-sdk/image` — provider-agnostic image generation + editing.
|
|
2754
3586
|
*
|
|
2755
3587
|
* ESM-only subpath export, built on the Vercel AI SDK image interface
|
|
2756
|
-
* (`generateImage`, `@ai-sdk/*`).
|
|
2757
|
-
*
|
|
3588
|
+
* (`generateImage`, `@ai-sdk/*`). The caller names the provider and model;
|
|
3589
|
+
* reuses the SDK's Result convention (`Result<T, MotifError>` — no
|
|
2758
3590
|
* thrown exceptions). Google (Gemini) is the only provider in Phase 1a.
|
|
2759
3591
|
*
|
|
2760
3592
|
* @example
|
|
@@ -2762,11 +3594,10 @@ function costForImages(provider, modelId, providerMetadata, imageCount) {
|
|
|
2762
3594
|
* import { createMotifImage } from "@howells/motif-sdk/image";
|
|
2763
3595
|
*
|
|
2764
3596
|
* const img = createMotifImage({ defaultProvider: "google" });
|
|
2765
|
-
* const r = await img.generate({
|
|
3597
|
+
* const r = await img.generate({ model: "gemini-2.5-flash-image", prompt: "a bare concrete wall" });
|
|
2766
3598
|
* if (r.isOk()) console.log(r.value.images[0].mediaType, r.value.cost);
|
|
2767
3599
|
* ```
|
|
2768
3600
|
*/
|
|
2769
|
-
const DEFAULT_TIER = "balanced";
|
|
2770
3601
|
const DEFAULT_PROVIDER = "google";
|
|
2771
3602
|
/**
|
|
2772
3603
|
* Create a provider-agnostic image client.
|
|
@@ -2793,8 +3624,8 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2793
3624
|
async function generate(opts) {
|
|
2794
3625
|
const provider = resolveProvider(opts.provider);
|
|
2795
3626
|
try {
|
|
2796
|
-
const modelId =
|
|
2797
|
-
const model = resolveModelFn(provider, modelId, apiKeyFor(provider));
|
|
3627
|
+
const modelId = opts.model;
|
|
3628
|
+
const model = resolveModelFn(provider, modelId, apiKeyFor(provider), config.fetch);
|
|
2798
3629
|
const result = await generateImageFn({
|
|
2799
3630
|
model,
|
|
2800
3631
|
prompt: opts.prompt,
|
|
@@ -2802,6 +3633,7 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2802
3633
|
...opts.size === void 0 ? {} : { size: opts.size },
|
|
2803
3634
|
...opts.aspectRatio === void 0 ? {} : { aspectRatio: opts.aspectRatio },
|
|
2804
3635
|
...opts.seed === void 0 ? {} : { seed: opts.seed },
|
|
3636
|
+
...config.maxRetries === void 0 ? {} : { maxRetries: config.maxRetries },
|
|
2805
3637
|
...opts.signal === void 0 ? {} : { abortSignal: opts.signal },
|
|
2806
3638
|
...opts.headers === void 0 ? {} : { headers: opts.headers },
|
|
2807
3639
|
...opts.providerOptions === void 0 ? {} : { providerOptions: toProviderOptions(opts.providerOptions) }
|
|
@@ -2814,8 +3646,8 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2814
3646
|
async function edit(opts) {
|
|
2815
3647
|
const provider = resolveProvider(opts.provider);
|
|
2816
3648
|
try {
|
|
2817
|
-
const modelId =
|
|
2818
|
-
const model = resolveModelFn(provider, modelId, apiKeyFor(provider));
|
|
3649
|
+
const modelId = opts.model;
|
|
3650
|
+
const model = resolveModelFn(provider, modelId, apiKeyFor(provider), config.fetch);
|
|
2819
3651
|
const result = await generateImageFn({
|
|
2820
3652
|
model,
|
|
2821
3653
|
prompt: {
|
|
@@ -2825,6 +3657,7 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2825
3657
|
},
|
|
2826
3658
|
...opts.n === void 0 ? {} : { n: opts.n },
|
|
2827
3659
|
...opts.seed === void 0 ? {} : { seed: opts.seed },
|
|
3660
|
+
...config.maxRetries === void 0 ? {} : { maxRetries: config.maxRetries },
|
|
2828
3661
|
...opts.signal === void 0 ? {} : { abortSignal: opts.signal },
|
|
2829
3662
|
...opts.headers === void 0 ? {} : { headers: opts.headers },
|
|
2830
3663
|
...opts.providerOptions === void 0 ? {} : { providerOptions: toProviderOptions(opts.providerOptions) }
|
|
@@ -2910,14 +3743,8 @@ async function runBestOfN(opts, generate, edit) {
|
|
|
2910
3743
|
}
|
|
2911
3744
|
}
|
|
2912
3745
|
/** Default provider dispatch: resolve the model through the provider registry. */
|
|
2913
|
-
function defaultResolveModel(provider, modelId, apiKey) {
|
|
2914
|
-
return getProviderAdapter(provider).resolveModel(modelId, apiKey);
|
|
2915
|
-
}
|
|
2916
|
-
/** Resolve the model id: explicit `model` wins, else the provider's tier map. */
|
|
2917
|
-
function resolveModelId(provider, model, tier) {
|
|
2918
|
-
if (model !== void 0 && model !== "") return model;
|
|
2919
|
-
const resolvedTier = tier ?? DEFAULT_TIER;
|
|
2920
|
-
return getProviderAdapter(provider).tierModels[resolvedTier];
|
|
3746
|
+
function defaultResolveModel(provider, modelId, apiKey, fetch) {
|
|
3747
|
+
return getProviderAdapter(provider).resolveModel(modelId, apiKey, fetch);
|
|
2921
3748
|
}
|
|
2922
3749
|
/** Map the AI SDK result → normalized MotifImageResult (with cost + requestId). */
|
|
2923
3750
|
function toMotifImageResult(result, provider, model) {
|
|
@@ -3009,7 +3836,10 @@ function toMotifError(error) {
|
|
|
3009
3836
|
if (error instanceof MotifError) return error;
|
|
3010
3837
|
const message = error instanceof Error ? error.message : String(error);
|
|
3011
3838
|
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
3012
|
-
|
|
3839
|
+
const status = error instanceof Error && "statusCode" in error && typeof error.statusCode === "number" ? error.statusCode : 0;
|
|
3840
|
+
const body = error instanceof Error && "responseBody" in error && typeof error.responseBody === "string" ? error.responseBody : message;
|
|
3841
|
+
if (isFalAccountLocked(status, body)) return falHttpError(status, body);
|
|
3842
|
+
return new MotifError(message, status, code);
|
|
3013
3843
|
}
|
|
3014
3844
|
//#endregion
|
|
3015
|
-
export { FAL_API_KEY_ENV,
|
|
3845
|
+
export { FAL_API_KEY_ENV, GOOGLE_API_KEY_ENV, OPENAI_API_KEY_ENV, PROVIDERS, REPLICATE_API_KEY_ENV, costForImages, costFromProviderMetadata, createMotifImage, getProviderAdapter, providerPricePerImageUsd };
|