@howells/motif-sdk 2.0.0 → 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 +37 -97
- package/dist/image.d.ts +25 -42
- package/dist/image.js +952 -183
- package/dist/index.cjs +5022 -1282
- package/dist/index.d.cts +1122 -3181
- package/dist/index.d.ts +1122 -3181
- package/dist/index.js +5004 -1253
- package/package.json +7 -4
package/dist/image.js
CHANGED
|
@@ -4,13 +4,69 @@ 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 = {
|
|
13
62
|
flare: {
|
|
63
|
+
customImageSize: {
|
|
64
|
+
maxEdge: 3840,
|
|
65
|
+
maxPixels: 8294400,
|
|
66
|
+
maxRatio: 3,
|
|
67
|
+
minPixels: 655360,
|
|
68
|
+
multipleOf: 16
|
|
69
|
+
},
|
|
14
70
|
editEndpoint: "openai/gpt-image-2.5/flare/edit",
|
|
15
71
|
endpoint: "openai/gpt-image-2.5/flare/text-to-image",
|
|
16
72
|
maxReferenceImages: 16,
|
|
@@ -40,6 +96,13 @@ const MODELS = {
|
|
|
40
96
|
useQueue: true
|
|
41
97
|
},
|
|
42
98
|
sunburst: {
|
|
99
|
+
customImageSize: {
|
|
100
|
+
maxEdge: 3840,
|
|
101
|
+
maxPixels: 8294400,
|
|
102
|
+
maxRatio: 3,
|
|
103
|
+
minPixels: 655360,
|
|
104
|
+
multipleOf: 16
|
|
105
|
+
},
|
|
43
106
|
editEndpoint: "openai/gpt-image-2.5/sunburst/edit",
|
|
44
107
|
endpoint: "openai/gpt-image-2.5/sunburst/text-to-image",
|
|
45
108
|
maxReferenceImages: 16,
|
|
@@ -94,7 +157,14 @@ const MODELS = {
|
|
|
94
157
|
},
|
|
95
158
|
useCase: "Highest-ranked text-to-image quality and transparent PNGs"
|
|
96
159
|
},
|
|
97
|
-
|
|
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",
|
|
98
168
|
endpoint: "openai/gpt-image-2",
|
|
99
169
|
falPricing: {
|
|
100
170
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -113,11 +183,18 @@ const MODELS = {
|
|
|
113
183
|
supportsAspect: true,
|
|
114
184
|
supportsEdit: true,
|
|
115
185
|
supportsMaskImage: true,
|
|
186
|
+
maskImageField: "mask_url",
|
|
116
187
|
supportsNumImages: true,
|
|
117
188
|
supportsOutputFormat: true,
|
|
118
189
|
supportsQuality: true,
|
|
119
190
|
supportsResolution: false,
|
|
120
191
|
supportsSyncMode: true,
|
|
192
|
+
transparencyRoute: {
|
|
193
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
194
|
+
model: "gpt-image-2",
|
|
195
|
+
provider: "openai",
|
|
196
|
+
supportsEdit: true
|
|
197
|
+
},
|
|
121
198
|
type: "generation",
|
|
122
199
|
useQueue: true
|
|
123
200
|
},
|
|
@@ -267,6 +344,24 @@ const MODELS = {
|
|
|
267
344
|
pricePerImageUsd: .15,
|
|
268
345
|
pricing: "$0.15",
|
|
269
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
|
+
],
|
|
270
365
|
supportsAspect: true,
|
|
271
366
|
supportsEdit: true,
|
|
272
367
|
supportsGoogleSearch: true,
|
|
@@ -305,6 +400,18 @@ const MODELS = {
|
|
|
305
400
|
pricePerImageUsd: .0398,
|
|
306
401
|
pricing: "$0.04",
|
|
307
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
|
+
],
|
|
308
415
|
supportsAspect: true,
|
|
309
416
|
supportsEdit: true,
|
|
310
417
|
supportsNumImages: true,
|
|
@@ -344,6 +451,24 @@ const MODELS = {
|
|
|
344
451
|
pricePerImageUsd: .15,
|
|
345
452
|
pricing: "$0.15",
|
|
346
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
|
+
],
|
|
347
472
|
supportsAspect: true,
|
|
348
473
|
supportsEdit: true,
|
|
349
474
|
supportsNumImages: true,
|
|
@@ -381,6 +506,10 @@ const MODELS = {
|
|
|
381
506
|
},
|
|
382
507
|
useCase: "High-ranked budget generation and large multi-reference edits"
|
|
383
508
|
},
|
|
509
|
+
customImageSize: {
|
|
510
|
+
maxPixels: 16777216,
|
|
511
|
+
minPixels: 921600
|
|
512
|
+
},
|
|
384
513
|
editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
|
|
385
514
|
endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
|
|
386
515
|
falPricing: {
|
|
@@ -431,6 +560,10 @@ const MODELS = {
|
|
|
431
560
|
},
|
|
432
561
|
useCase: "Cheap current Seedream model with good animation/art scores"
|
|
433
562
|
},
|
|
563
|
+
customImageSize: {
|
|
564
|
+
maxPixels: 16777216,
|
|
565
|
+
minPixels: 3686400
|
|
566
|
+
},
|
|
434
567
|
editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
|
|
435
568
|
endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
|
|
436
569
|
falPricing: {
|
|
@@ -468,6 +601,11 @@ const MODELS = {
|
|
|
468
601
|
rank: 10
|
|
469
602
|
}
|
|
470
603
|
} },
|
|
604
|
+
customImageSize: {
|
|
605
|
+
maxPixels: 4194304,
|
|
606
|
+
maxRatio: 16,
|
|
607
|
+
minPixels: 1048576
|
|
608
|
+
},
|
|
471
609
|
name: "Seedream 5.0 Pro",
|
|
472
610
|
endpoint: "bytedance/seedream/v5/pro/text-to-image",
|
|
473
611
|
editEndpoint: "bytedance/seedream/v5/pro/edit",
|
|
@@ -501,16 +639,20 @@ const MODELS = {
|
|
|
501
639
|
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
502
640
|
sourceUrls: AA_IMAGE_SOURCES
|
|
503
641
|
} },
|
|
642
|
+
customImageSize: {
|
|
643
|
+
maxPixels: 16777216,
|
|
644
|
+
minPixels: 3686400
|
|
645
|
+
},
|
|
504
646
|
name: "Seedream 5.0 Lite",
|
|
505
|
-
endpoint: "
|
|
506
|
-
editEndpoint: "
|
|
647
|
+
endpoint: "bytedance/seedream/v5/lite/text-to-image",
|
|
648
|
+
editEndpoint: "bytedance/seedream/v5/lite/edit",
|
|
507
649
|
type: "generation",
|
|
508
650
|
pricing: "$0.035",
|
|
509
651
|
pricePerImageUsd: .035,
|
|
510
652
|
falPricing: {
|
|
511
653
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
512
654
|
currency: "USD",
|
|
513
|
-
endpointId: "
|
|
655
|
+
endpointId: "bytedance/seedream/v5/lite/text-to-image",
|
|
514
656
|
estimatedCostPerImageUsd: .035,
|
|
515
657
|
source: "fal-pricing-api",
|
|
516
658
|
unit: "images",
|
|
@@ -551,6 +693,12 @@ const MODELS = {
|
|
|
551
693
|
},
|
|
552
694
|
useCase: "Best FLUX quality and high-end editing"
|
|
553
695
|
},
|
|
696
|
+
customImageSize: {
|
|
697
|
+
maxEdge: 2560,
|
|
698
|
+
maxPixels: 4194304,
|
|
699
|
+
minEdge: 256,
|
|
700
|
+
multipleOf: 16
|
|
701
|
+
},
|
|
554
702
|
editEndpoint: "fal-ai/flux-2-max/edit",
|
|
555
703
|
endpoint: "fal-ai/flux-2-max",
|
|
556
704
|
falPricing: {
|
|
@@ -604,6 +752,12 @@ const MODELS = {
|
|
|
604
752
|
},
|
|
605
753
|
useCase: "Production FLUX quality at a low per-megapixel price"
|
|
606
754
|
},
|
|
755
|
+
customImageSize: {
|
|
756
|
+
maxEdge: 2560,
|
|
757
|
+
maxPixels: 4194304,
|
|
758
|
+
minEdge: 256,
|
|
759
|
+
multipleOf: 16
|
|
760
|
+
},
|
|
607
761
|
editEndpoint: "fal-ai/flux-2-pro/edit",
|
|
608
762
|
endpoint: "fal-ai/flux-2-pro",
|
|
609
763
|
falPricing: {
|
|
@@ -653,6 +807,12 @@ const MODELS = {
|
|
|
653
807
|
},
|
|
654
808
|
useCase: "FLUX quality with guidance and step controls"
|
|
655
809
|
},
|
|
810
|
+
customImageSize: {
|
|
811
|
+
maxEdge: 2560,
|
|
812
|
+
maxPixels: 4194304,
|
|
813
|
+
minEdge: 256,
|
|
814
|
+
multipleOf: 16
|
|
815
|
+
},
|
|
656
816
|
editEndpoint: "fal-ai/flux-2-flex/edit",
|
|
657
817
|
endpoint: "fal-ai/flux-2-flex",
|
|
658
818
|
falPricing: {
|
|
@@ -701,6 +861,10 @@ const MODELS = {
|
|
|
701
861
|
},
|
|
702
862
|
useCase: "Open FLUX.2 quality with low average cost"
|
|
703
863
|
},
|
|
864
|
+
customImageSize: {
|
|
865
|
+
maxEdge: 2048,
|
|
866
|
+
minEdge: 512
|
|
867
|
+
},
|
|
704
868
|
editEndpoint: "fal-ai/flux-2/edit",
|
|
705
869
|
endpoint: "fal-ai/flux-2",
|
|
706
870
|
falPricing: {
|
|
@@ -731,6 +895,10 @@ const MODELS = {
|
|
|
731
895
|
type: "generation"
|
|
732
896
|
},
|
|
733
897
|
"flux2-turbo": {
|
|
898
|
+
customImageSize: {
|
|
899
|
+
maxEdge: 2048,
|
|
900
|
+
minEdge: 512
|
|
901
|
+
},
|
|
734
902
|
endpoint: "fal-ai/flux-2/turbo",
|
|
735
903
|
falPricing: {
|
|
736
904
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
@@ -786,6 +954,7 @@ const MODELS = {
|
|
|
786
954
|
type: "generation"
|
|
787
955
|
},
|
|
788
956
|
"flux-fast": {
|
|
957
|
+
customImageSize: {},
|
|
789
958
|
endpoint: "fal-ai/flux/schnell",
|
|
790
959
|
falPricing: {
|
|
791
960
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -812,11 +981,12 @@ const MODELS = {
|
|
|
812
981
|
type: "generation"
|
|
813
982
|
},
|
|
814
983
|
recraft: {
|
|
815
|
-
|
|
984
|
+
customImageSize: {},
|
|
985
|
+
endpoint: "fal-ai/recraft/v3/text-to-image",
|
|
816
986
|
falPricing: {
|
|
817
987
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
818
988
|
currency: "USD",
|
|
819
|
-
endpointId: "fal-ai/recraft-
|
|
989
|
+
endpointId: "fal-ai/recraft/v3/text-to-image",
|
|
820
990
|
estimatedCostPerImageUsd: .04,
|
|
821
991
|
source: "fal-pricing-api",
|
|
822
992
|
unit: "images",
|
|
@@ -858,7 +1028,7 @@ const MODELS = {
|
|
|
858
1028
|
editEndpoint: "fal-ai/reve/edit",
|
|
859
1029
|
endpoint: "fal-ai/reve/text-to-image",
|
|
860
1030
|
falPricing: {
|
|
861
|
-
checkedAt:
|
|
1031
|
+
checkedAt: FAL_PRICING_CHECKED_AUG_2026,
|
|
862
1032
|
currency: "USD",
|
|
863
1033
|
endpointId: "fal-ai/reve/text-to-image",
|
|
864
1034
|
estimatedCostPerImageUsd: .04,
|
|
@@ -871,6 +1041,15 @@ const MODELS = {
|
|
|
871
1041
|
pricePerImageUsd: .04,
|
|
872
1042
|
pricing: "$0.04",
|
|
873
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
|
+
],
|
|
874
1053
|
supportsAspect: true,
|
|
875
1054
|
supportsEdit: true,
|
|
876
1055
|
supportsNumImages: true,
|
|
@@ -889,6 +1068,7 @@ const MODELS = {
|
|
|
889
1068
|
rank: 25
|
|
890
1069
|
}
|
|
891
1070
|
} },
|
|
1071
|
+
customImageSize: {},
|
|
892
1072
|
endpoint: "fal-ai/recraft/v4/text-to-image",
|
|
893
1073
|
falPricing: {
|
|
894
1074
|
checkedAt: FAL_PRICING_CHECKED_JUL_2026,
|
|
@@ -907,11 +1087,11 @@ const MODELS = {
|
|
|
907
1087
|
supportsEdit: false,
|
|
908
1088
|
supportsNumImages: false,
|
|
909
1089
|
supportsResolution: false,
|
|
910
|
-
supportsStyle: true,
|
|
911
1090
|
supportsSyncMode: true,
|
|
912
1091
|
type: "generation"
|
|
913
1092
|
},
|
|
914
1093
|
ideogram: {
|
|
1094
|
+
customImageSize: {},
|
|
915
1095
|
endpoint: "fal-ai/ideogram/v3",
|
|
916
1096
|
falPricing: {
|
|
917
1097
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -947,6 +1127,11 @@ const MODELS = {
|
|
|
947
1127
|
rank: 19
|
|
948
1128
|
}
|
|
949
1129
|
} },
|
|
1130
|
+
customImageSize: {
|
|
1131
|
+
maxEdge: 3840,
|
|
1132
|
+
minEdge: 512,
|
|
1133
|
+
multipleOf: 16
|
|
1134
|
+
},
|
|
950
1135
|
name: "Ideogram V4",
|
|
951
1136
|
endpoint: "ideogram/v4",
|
|
952
1137
|
type: "generation",
|
|
@@ -1010,11 +1195,21 @@ const MODELS = {
|
|
|
1010
1195
|
unit: "images",
|
|
1011
1196
|
unitPrice: .02
|
|
1012
1197
|
},
|
|
1013
|
-
maxReferenceImages:
|
|
1198
|
+
maxReferenceImages: 3,
|
|
1014
1199
|
name: "Grok Imagine Image",
|
|
1015
1200
|
pricePerImageUsd: .02,
|
|
1016
1201
|
pricing: "$0.02",
|
|
1017
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"],
|
|
1018
1213
|
supportsAspect: true,
|
|
1019
1214
|
supportsEdit: true,
|
|
1020
1215
|
supportsNumImages: true,
|
|
@@ -1041,6 +1236,7 @@ const MODELS = {
|
|
|
1041
1236
|
},
|
|
1042
1237
|
useCase: "Low-cost open-weight text and design generation"
|
|
1043
1238
|
},
|
|
1239
|
+
customImageSize: {},
|
|
1044
1240
|
endpoint: "fal-ai/qwen-image",
|
|
1045
1241
|
falPricing: {
|
|
1046
1242
|
checkedAt: FAL_PRICING_CHECKED_AT,
|
|
@@ -1085,19 +1281,23 @@ const MODELS = {
|
|
|
1085
1281
|
},
|
|
1086
1282
|
useCase: "Strong quality per dollar - 11th on text-to-image at $30/1k"
|
|
1087
1283
|
},
|
|
1088
|
-
|
|
1284
|
+
customImageSize: {
|
|
1285
|
+
maxPixels: 4194304,
|
|
1286
|
+
minPixels: 262144
|
|
1287
|
+
},
|
|
1288
|
+
endpoint: "alibaba/qwen-image-3/text-to-image",
|
|
1089
1289
|
falPricing: {
|
|
1090
|
-
checkedAt:
|
|
1290
|
+
checkedAt: FAL_PRICING_CHECKED_SEP_2026,
|
|
1091
1291
|
currency: "USD",
|
|
1092
|
-
endpointId: "
|
|
1093
|
-
estimatedCostPerImageUsd: .
|
|
1292
|
+
endpointId: "alibaba/qwen-image-3/text-to-image",
|
|
1293
|
+
estimatedCostPerImageUsd: .04,
|
|
1094
1294
|
source: "fal-pricing-api",
|
|
1095
|
-
unit: "
|
|
1096
|
-
unitPrice: .
|
|
1295
|
+
unit: "images",
|
|
1296
|
+
unitPrice: .04
|
|
1097
1297
|
},
|
|
1098
1298
|
name: "Qwen Image 3",
|
|
1099
|
-
pricePerImageUsd: .
|
|
1100
|
-
pricing: "$0.
|
|
1299
|
+
pricePerImageUsd: .04,
|
|
1300
|
+
pricing: "$0.04 (1K) / $0.075 (2K)",
|
|
1101
1301
|
sizeMode: "image_size_enum",
|
|
1102
1302
|
supportsAspect: true,
|
|
1103
1303
|
supportsEdit: false,
|
|
@@ -1109,6 +1309,179 @@ const MODELS = {
|
|
|
1109
1309
|
supportsSeed: true,
|
|
1110
1310
|
type: "generation"
|
|
1111
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
|
+
},
|
|
1112
1485
|
kling: {
|
|
1113
1486
|
endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
|
|
1114
1487
|
name: "Kling v3 Pro",
|
|
@@ -1120,6 +1493,17 @@ const MODELS = {
|
|
|
1120
1493
|
supportsResolution: false,
|
|
1121
1494
|
type: "video"
|
|
1122
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
|
+
},
|
|
1123
1507
|
clarity: {
|
|
1124
1508
|
endpoint: "fal-ai/clarity-upscaler",
|
|
1125
1509
|
name: "Clarity Upscaler",
|
|
@@ -1186,33 +1570,202 @@ const MODELS = {
|
|
|
1186
1570
|
"ideogram",
|
|
1187
1571
|
"ideogram4",
|
|
1188
1572
|
"grok-image",
|
|
1573
|
+
"grok-image-2",
|
|
1189
1574
|
"qwen",
|
|
1190
|
-
"qwen3"
|
|
1575
|
+
"qwen3",
|
|
1576
|
+
"mai-image-2.5-pro",
|
|
1577
|
+
"banana2-lite",
|
|
1578
|
+
"ideogram3-transparent",
|
|
1579
|
+
"recraft41"
|
|
1191
1580
|
].filter((id) => MODELS[id]?.supportsEdit === true);
|
|
1192
1581
|
//#endregion
|
|
1193
|
-
//#region src/
|
|
1194
|
-
/**
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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
|
|
1215
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;
|
|
1216
1769
|
//#endregion
|
|
1217
1770
|
//#region src/tool-registry/analysis.ts
|
|
1218
1771
|
const ANALYSIS_TOOLS = {
|
|
@@ -1224,7 +1777,11 @@ const ANALYSIS_TOOLS = {
|
|
|
1224
1777
|
inputKind: "images",
|
|
1225
1778
|
name: "GOT-OCR 2.0",
|
|
1226
1779
|
outputKeys: ["outputs"],
|
|
1227
|
-
price: {
|
|
1780
|
+
price: {
|
|
1781
|
+
kind: "call",
|
|
1782
|
+
per: ["input_image_urls"],
|
|
1783
|
+
usd: .05
|
|
1784
|
+
},
|
|
1228
1785
|
pricing: "$0.05/image",
|
|
1229
1786
|
queued: true,
|
|
1230
1787
|
sourceUrl: "https://fal.ai/models/fal-ai/got-ocr/v2",
|
|
@@ -1238,7 +1795,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1238
1795
|
inputKind: "image",
|
|
1239
1796
|
name: "Moondream 3 Caption",
|
|
1240
1797
|
outputKeys: ["output"],
|
|
1241
|
-
price: {
|
|
1798
|
+
price: {
|
|
1799
|
+
inputPerMillion: .4,
|
|
1800
|
+
inputTokens: 737,
|
|
1801
|
+
kind: "token",
|
|
1802
|
+
outputPerMillion: 3.5,
|
|
1803
|
+
outputTokens: 200
|
|
1804
|
+
},
|
|
1242
1805
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1243
1806
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/caption",
|
|
1244
1807
|
task: "image captioning"
|
|
@@ -1251,7 +1814,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1251
1814
|
inputKind: "image",
|
|
1252
1815
|
name: "Moondream 3 Detect",
|
|
1253
1816
|
outputKeys: ["objects", "image"],
|
|
1254
|
-
price: {
|
|
1817
|
+
price: {
|
|
1818
|
+
inputPerMillion: .4,
|
|
1819
|
+
inputTokens: 737,
|
|
1820
|
+
kind: "token",
|
|
1821
|
+
outputPerMillion: 3.5,
|
|
1822
|
+
outputTokens: 100
|
|
1823
|
+
},
|
|
1255
1824
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1256
1825
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/detect",
|
|
1257
1826
|
task: "object detection"
|
|
@@ -1264,7 +1833,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1264
1833
|
inputKind: "image",
|
|
1265
1834
|
name: "Moondream 3 Point",
|
|
1266
1835
|
outputKeys: ["points", "image"],
|
|
1267
|
-
price: {
|
|
1836
|
+
price: {
|
|
1837
|
+
inputPerMillion: .4,
|
|
1838
|
+
inputTokens: 737,
|
|
1839
|
+
kind: "token",
|
|
1840
|
+
outputPerMillion: 3.5,
|
|
1841
|
+
outputTokens: 100
|
|
1842
|
+
},
|
|
1268
1843
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1269
1844
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/point",
|
|
1270
1845
|
task: "object pointing"
|
|
@@ -1277,7 +1852,13 @@ const ANALYSIS_TOOLS = {
|
|
|
1277
1852
|
inputKind: "image",
|
|
1278
1853
|
name: "Moondream 3 Query",
|
|
1279
1854
|
outputKeys: ["output", "reasoning"],
|
|
1280
|
-
price: {
|
|
1855
|
+
price: {
|
|
1856
|
+
inputPerMillion: .4,
|
|
1857
|
+
inputTokens: 737,
|
|
1858
|
+
kind: "token",
|
|
1859
|
+
outputPerMillion: 3.5,
|
|
1860
|
+
outputTokens: 500
|
|
1861
|
+
},
|
|
1281
1862
|
pricing: "$0.40/M input tokens, $3.50/M output tokens",
|
|
1282
1863
|
sourceUrl: "https://fal.ai/models/fal-ai/moondream3-preview/query",
|
|
1283
1864
|
task: "visual question answering"
|
|
@@ -1290,7 +1871,11 @@ const ANALYSIS_TOOLS = {
|
|
|
1290
1871
|
inputKind: "images",
|
|
1291
1872
|
name: "NSFW Checker",
|
|
1292
1873
|
outputKeys: ["has_nsfw_concepts"],
|
|
1293
|
-
price: {
|
|
1874
|
+
price: {
|
|
1875
|
+
kind: "call",
|
|
1876
|
+
per: ["image_urls"],
|
|
1877
|
+
usd: .001
|
|
1878
|
+
},
|
|
1294
1879
|
pricing: "$0.001/image",
|
|
1295
1880
|
sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw",
|
|
1296
1881
|
task: "vision moderation"
|
|
@@ -1374,6 +1959,34 @@ const ASSET_TOOLS = {
|
|
|
1374
1959
|
sourceUrl: "https://fal.ai/models/fal-ai/image2svg",
|
|
1375
1960
|
task: "raster to vector tracing"
|
|
1376
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
|
+
},
|
|
1377
1990
|
patina: {
|
|
1378
1991
|
category: "material",
|
|
1379
1992
|
description: "Decompose a surface photograph into PBR maps: basecolor, normal, roughness, metalness, height.",
|
|
@@ -1392,7 +2005,12 @@ const ASSET_TOOLS = {
|
|
|
1392
2005
|
],
|
|
1393
2006
|
fromOption: "maps"
|
|
1394
2007
|
} },
|
|
1395
|
-
price: {
|
|
2008
|
+
price: {
|
|
2009
|
+
base: .01,
|
|
2010
|
+
kind: "maps",
|
|
2011
|
+
perMapMegapixel: .01,
|
|
2012
|
+
perMegapixel: 0
|
|
2013
|
+
},
|
|
1396
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",
|
|
1397
2015
|
queued: true,
|
|
1398
2016
|
sourceUrl: "https://fal.ai/models/fal-ai/patina",
|
|
@@ -1416,8 +2034,17 @@ const ASSET_TOOLS = {
|
|
|
1416
2034
|
],
|
|
1417
2035
|
fromOption: "maps"
|
|
1418
2036
|
} },
|
|
1419
|
-
price: {
|
|
1420
|
-
|
|
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",
|
|
1421
2048
|
queued: true,
|
|
1422
2049
|
sourceUrl: "https://fal.ai/models/fal-ai/patina/material/extract",
|
|
1423
2050
|
task: "tiling material extraction"
|
|
@@ -1430,8 +2057,11 @@ const ASSET_TOOLS = {
|
|
|
1430
2057
|
inputKind: "image",
|
|
1431
2058
|
name: "Qwen Image Layered",
|
|
1432
2059
|
outputKeys: ["images"],
|
|
1433
|
-
price: {
|
|
1434
|
-
|
|
2060
|
+
price: {
|
|
2061
|
+
kind: "call",
|
|
2062
|
+
usd: .05
|
|
2063
|
+
},
|
|
2064
|
+
pricing: "$0.05 per image, one input image a call",
|
|
1435
2065
|
queued: true,
|
|
1436
2066
|
sourceUrl: "https://fal.ai/models/fal-ai/qwen-image-layered",
|
|
1437
2067
|
task: "image layer decomposition"
|
|
@@ -1498,7 +2128,6 @@ const ASSET_TOOLS = {
|
|
|
1498
2128
|
},
|
|
1499
2129
|
"sam3-3d-objects": {
|
|
1500
2130
|
category: "3d",
|
|
1501
|
-
defaultOptions: { prompt: "car" },
|
|
1502
2131
|
description: "Reconstruct one or more 3D objects from an image and prompts.",
|
|
1503
2132
|
endpoint: "fal-ai/sam-3/3d-objects",
|
|
1504
2133
|
inputField: "image_url",
|
|
@@ -1604,7 +2233,10 @@ const BACKGROUND_TOOLS = {
|
|
|
1604
2233
|
inputKind: "image",
|
|
1605
2234
|
name: "BirefNet Background Removal",
|
|
1606
2235
|
outputKeys: ["image", "mask_image"],
|
|
1607
|
-
price: {
|
|
2236
|
+
price: {
|
|
2237
|
+
kind: "call",
|
|
2238
|
+
usd: 0
|
|
2239
|
+
},
|
|
1608
2240
|
pricing: "$0/compute-second listed by fal",
|
|
1609
2241
|
sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
|
|
1610
2242
|
task: "image background removal"
|
|
@@ -1640,13 +2272,51 @@ const BACKGROUND_TOOLS = {
|
|
|
1640
2272
|
outputKeys: ["video"],
|
|
1641
2273
|
price: {
|
|
1642
2274
|
kind: "second",
|
|
1643
|
-
usd: .
|
|
2275
|
+
usd: .14
|
|
1644
2276
|
},
|
|
1645
|
-
pricing: "$0.
|
|
2277
|
+
pricing: "$0.14/sec",
|
|
1646
2278
|
queued: true,
|
|
1647
2279
|
sourceUrl: "https://fal.ai/models/bria/video/background-removal",
|
|
1648
2280
|
task: "video background removal"
|
|
1649
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
|
+
},
|
|
1650
2320
|
ddcolor: {
|
|
1651
2321
|
category: "restoration",
|
|
1652
2322
|
description: "Colourise black-and-white photographs.",
|
|
@@ -1672,7 +2342,10 @@ const BACKGROUND_TOOLS = {
|
|
|
1672
2342
|
inputKind: "image",
|
|
1673
2343
|
name: "Remove Background",
|
|
1674
2344
|
outputKeys: ["image"],
|
|
1675
|
-
price: {
|
|
2345
|
+
price: {
|
|
2346
|
+
kind: "call",
|
|
2347
|
+
usd: 0
|
|
2348
|
+
},
|
|
1676
2349
|
pricing: "$0/compute-second listed by fal",
|
|
1677
2350
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
|
|
1678
2351
|
task: "image background removal"
|
|
@@ -1705,8 +2378,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1705
2378
|
name: "Topaz Adjust",
|
|
1706
2379
|
outputKeys: ["image"],
|
|
1707
2380
|
price: {
|
|
1708
|
-
kind: "megapixel",
|
|
1709
|
-
|
|
2381
|
+
kind: "megapixel-step",
|
|
2382
|
+
megapixels: 24,
|
|
2383
|
+
usd: .08
|
|
1710
2384
|
},
|
|
1711
2385
|
pricing: "$0.08 per 24 output megapixels",
|
|
1712
2386
|
queued: true,
|
|
@@ -1723,10 +2397,11 @@ const BACKGROUND_TOOLS = {
|
|
|
1723
2397
|
name: "Topaz Creative Upscale",
|
|
1724
2398
|
outputKeys: ["image"],
|
|
1725
2399
|
price: {
|
|
1726
|
-
kind: "megapixel",
|
|
1727
|
-
|
|
2400
|
+
kind: "megapixel-step",
|
|
2401
|
+
megapixels: 2,
|
|
2402
|
+
usd: .08
|
|
1728
2403
|
},
|
|
1729
|
-
pricing: "$0.
|
|
2404
|
+
pricing: "$0.08 per started 2 output megapixels, any Bloom model",
|
|
1730
2405
|
queued: true,
|
|
1731
2406
|
sourceUrl: "https://fal.ai/models/topaz/upscale/image/creative",
|
|
1732
2407
|
task: "creative image upscaling"
|
|
@@ -1741,8 +2416,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1741
2416
|
name: "Topaz Denoise",
|
|
1742
2417
|
outputKeys: ["image"],
|
|
1743
2418
|
price: {
|
|
1744
|
-
kind: "megapixel",
|
|
1745
|
-
|
|
2419
|
+
kind: "megapixel-step",
|
|
2420
|
+
megapixels: 24,
|
|
2421
|
+
usd: .08
|
|
1746
2422
|
},
|
|
1747
2423
|
pricing: "$0.08 per 24 output megapixels at the default Normal model; $0.16 with Denoise Max",
|
|
1748
2424
|
queued: true,
|
|
@@ -1759,10 +2435,11 @@ const BACKGROUND_TOOLS = {
|
|
|
1759
2435
|
name: "Topaz Generative Upscale",
|
|
1760
2436
|
outputKeys: ["image"],
|
|
1761
2437
|
price: {
|
|
1762
|
-
kind: "megapixel",
|
|
1763
|
-
|
|
2438
|
+
kind: "megapixel-step",
|
|
2439
|
+
megapixels: 8,
|
|
2440
|
+
usd: .08
|
|
1764
2441
|
},
|
|
1765
|
-
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",
|
|
1766
2443
|
queued: true,
|
|
1767
2444
|
sourceUrl: "https://fal.ai/models/topaz/upscale/image/generative",
|
|
1768
2445
|
task: "generative image upscaling"
|
|
@@ -1781,8 +2458,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1781
2458
|
name: "Topaz Image Upscale",
|
|
1782
2459
|
outputKeys: ["image"],
|
|
1783
2460
|
price: {
|
|
1784
|
-
kind: "megapixel",
|
|
1785
|
-
|
|
2461
|
+
kind: "megapixel-step",
|
|
2462
|
+
megapixels: 24,
|
|
2463
|
+
usd: .08
|
|
1786
2464
|
},
|
|
1787
2465
|
pricing: "$0.08 for output up to 24MP; $0.16 to 48MP, $0.32 to 96MP, up to $1.36 at 512MP",
|
|
1788
2466
|
queued: true,
|
|
@@ -1799,8 +2477,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1799
2477
|
name: "Topaz Precision Upscale",
|
|
1800
2478
|
outputKeys: ["image"],
|
|
1801
2479
|
price: {
|
|
1802
|
-
kind: "megapixel",
|
|
1803
|
-
|
|
2480
|
+
kind: "megapixel-step",
|
|
2481
|
+
megapixels: 24,
|
|
2482
|
+
usd: .08
|
|
1804
2483
|
},
|
|
1805
2484
|
pricing: "$0.08 per 24 output megapixels, any precision model",
|
|
1806
2485
|
queued: true,
|
|
@@ -1817,8 +2496,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1817
2496
|
name: "Topaz Restore",
|
|
1818
2497
|
outputKeys: ["image"],
|
|
1819
2498
|
price: {
|
|
1820
|
-
kind: "megapixel",
|
|
1821
|
-
|
|
2499
|
+
kind: "megapixel-step",
|
|
2500
|
+
megapixels: 24,
|
|
2501
|
+
usd: .48
|
|
1822
2502
|
},
|
|
1823
2503
|
pricing: "$0.48 per 24 output megapixels at the default Recover 3; $0.08 with Dust-Scratch V2",
|
|
1824
2504
|
queued: true,
|
|
@@ -1835,8 +2515,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1835
2515
|
name: "Topaz Sharpen",
|
|
1836
2516
|
outputKeys: ["image"],
|
|
1837
2517
|
price: {
|
|
1838
|
-
kind: "megapixel",
|
|
1839
|
-
|
|
2518
|
+
kind: "megapixel-step",
|
|
2519
|
+
megapixels: 24,
|
|
2520
|
+
usd: .08
|
|
1840
2521
|
},
|
|
1841
2522
|
pricing: "$0.08 per 24 output megapixels at the default Standard model; $0.16 with Super Focus",
|
|
1842
2523
|
queued: true,
|
|
@@ -1852,8 +2533,9 @@ const BACKGROUND_TOOLS = {
|
|
|
1852
2533
|
name: "Topaz Transparent Upscale",
|
|
1853
2534
|
outputKeys: ["image"],
|
|
1854
2535
|
price: {
|
|
1855
|
-
kind: "megapixel",
|
|
1856
|
-
|
|
2536
|
+
kind: "megapixel-step",
|
|
2537
|
+
megapixels: 24,
|
|
2538
|
+
usd: .08
|
|
1857
2539
|
},
|
|
1858
2540
|
pricing: "$0.08 per 24 output megapixels",
|
|
1859
2541
|
queued: true,
|
|
@@ -1873,10 +2555,22 @@ const BACKGROUND_TOOLS = {
|
|
|
1873
2555
|
name: "Topaz Video Upscale",
|
|
1874
2556
|
outputKeys: ["video"],
|
|
1875
2557
|
price: {
|
|
1876
|
-
|
|
1877
|
-
|
|
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
|
+
]
|
|
1878
2572
|
},
|
|
1879
|
-
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",
|
|
1880
2574
|
queued: true,
|
|
1881
2575
|
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
|
|
1882
2576
|
task: "video enhancement"
|
|
@@ -1927,6 +2621,7 @@ const EDITING_TOOLS = {
|
|
|
1927
2621
|
outputKeys: ["images"],
|
|
1928
2622
|
price: {
|
|
1929
2623
|
kind: "call",
|
|
2624
|
+
perImage: true,
|
|
1930
2625
|
usd: .04
|
|
1931
2626
|
},
|
|
1932
2627
|
pricing: "$0.04/generation",
|
|
@@ -1957,7 +2652,11 @@ const EDITING_TOOLS = {
|
|
|
1957
2652
|
inputKind: "image",
|
|
1958
2653
|
name: "FLUX.2 Pro Outpaint",
|
|
1959
2654
|
outputKeys: ["images"],
|
|
1960
|
-
price: {
|
|
2655
|
+
price: {
|
|
2656
|
+
extra: .015,
|
|
2657
|
+
first: .03,
|
|
2658
|
+
kind: "megapixel-first"
|
|
2659
|
+
},
|
|
1961
2660
|
pricing: "$0.03 for the first output megapixel, then $0.015 per extra megapixel of input and output, rounded up",
|
|
1962
2661
|
sourceUrl: "https://fal.ai/models/fal-ai/flux-2-pro/outpaint",
|
|
1963
2662
|
task: "image outpainting"
|
|
@@ -1988,6 +2687,7 @@ const EDITING_TOOLS = {
|
|
|
1988
2687
|
outputKeys: ["images"],
|
|
1989
2688
|
price: {
|
|
1990
2689
|
kind: "call",
|
|
2690
|
+
perImage: true,
|
|
1991
2691
|
usd: .06
|
|
1992
2692
|
},
|
|
1993
2693
|
pricing: "$0.06/image at the default BALANCED speed; $0.03 turbo, $0.09 quality",
|
|
@@ -2082,12 +2782,35 @@ const EDITING_TOOLS = {
|
|
|
2082
2782
|
inputKind: "image",
|
|
2083
2783
|
name: "Smart Resize",
|
|
2084
2784
|
outputKeys: ["images", "results"],
|
|
2085
|
-
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
|
+
},
|
|
2086
2792
|
pricing: "$0.15 per output image, doubled at 4K, plus a $0.05 vision analysis fee per request",
|
|
2087
2793
|
queued: true,
|
|
2088
2794
|
sourceUrl: "https://fal.ai/models/fal-ai/smart-resize",
|
|
2089
2795
|
task: "multi-size recomposition"
|
|
2090
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
|
+
},
|
|
2091
2814
|
"text-removal": {
|
|
2092
2815
|
category: "erase",
|
|
2093
2816
|
defaultOptions: { output_format: "png" },
|
|
@@ -2104,6 +2827,25 @@ const EDITING_TOOLS = {
|
|
|
2104
2827
|
pricing: "$0.04/image",
|
|
2105
2828
|
sourceUrl: "https://fal.ai/models/fal-ai/image-editing/text-removal",
|
|
2106
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"
|
|
2107
2849
|
}
|
|
2108
2850
|
};
|
|
2109
2851
|
//#endregion
|
|
@@ -2117,7 +2859,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2117
2859
|
inputKind: "image",
|
|
2118
2860
|
name: "Depth Anything v2 Preprocessor",
|
|
2119
2861
|
outputKeys: ["image"],
|
|
2120
|
-
price: {
|
|
2862
|
+
price: {
|
|
2863
|
+
kind: "call",
|
|
2864
|
+
usd: 0
|
|
2865
|
+
},
|
|
2121
2866
|
pricing: "$0/compute-second listed by fal",
|
|
2122
2867
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2",
|
|
2123
2868
|
task: "depth preprocessing"
|
|
@@ -2146,7 +2891,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2146
2891
|
inputKind: "image",
|
|
2147
2892
|
name: "HED Edge Preprocessor",
|
|
2148
2893
|
outputKeys: ["image"],
|
|
2149
|
-
price: {
|
|
2894
|
+
price: {
|
|
2895
|
+
kind: "call",
|
|
2896
|
+
usd: 0
|
|
2897
|
+
},
|
|
2150
2898
|
pricing: "$0/compute-second listed by fal",
|
|
2151
2899
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/hed",
|
|
2152
2900
|
task: "edge preprocessing"
|
|
@@ -2160,7 +2908,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2160
2908
|
inputKind: "image",
|
|
2161
2909
|
name: "Line Art Preprocessor",
|
|
2162
2910
|
outputKeys: ["image"],
|
|
2163
|
-
price: {
|
|
2911
|
+
price: {
|
|
2912
|
+
kind: "call",
|
|
2913
|
+
usd: 0
|
|
2914
|
+
},
|
|
2164
2915
|
pricing: "$0/compute-second listed by fal",
|
|
2165
2916
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
|
|
2166
2917
|
task: "image preprocessing"
|
|
@@ -2177,7 +2928,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2177
2928
|
inputKind: "image",
|
|
2178
2929
|
name: "Marigold Depth Estimation",
|
|
2179
2930
|
outputKeys: ["image"],
|
|
2180
|
-
price: {
|
|
2931
|
+
price: {
|
|
2932
|
+
kind: "call",
|
|
2933
|
+
usd: 0
|
|
2934
|
+
},
|
|
2181
2935
|
pricing: "$0/compute-second listed by fal",
|
|
2182
2936
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
|
|
2183
2937
|
task: "depth map"
|
|
@@ -2194,7 +2948,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2194
2948
|
inputKind: "image",
|
|
2195
2949
|
name: "MiDaS Depth Estimation",
|
|
2196
2950
|
outputKeys: ["image"],
|
|
2197
|
-
price: {
|
|
2951
|
+
price: {
|
|
2952
|
+
kind: "call",
|
|
2953
|
+
usd: 0
|
|
2954
|
+
},
|
|
2198
2955
|
pricing: "$0/compute-second listed by fal",
|
|
2199
2956
|
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
|
|
2200
2957
|
task: "depth map"
|
|
@@ -2207,7 +2964,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2207
2964
|
inputKind: "image",
|
|
2208
2965
|
name: "MiDaS Preprocessor",
|
|
2209
2966
|
outputKeys: ["depth_map", "normal_map"],
|
|
2210
|
-
price: {
|
|
2967
|
+
price: {
|
|
2968
|
+
kind: "call",
|
|
2969
|
+
usd: 0
|
|
2970
|
+
},
|
|
2211
2971
|
pricing: "$0/compute-second listed by fal",
|
|
2212
2972
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas",
|
|
2213
2973
|
task: "depth and normal preprocessing"
|
|
@@ -2220,7 +2980,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2220
2980
|
inputKind: "image",
|
|
2221
2981
|
name: "M-LSD Line Preprocessor",
|
|
2222
2982
|
outputKeys: ["image"],
|
|
2223
|
-
price: {
|
|
2983
|
+
price: {
|
|
2984
|
+
kind: "call",
|
|
2985
|
+
usd: 0
|
|
2986
|
+
},
|
|
2224
2987
|
pricing: "$0/compute-second listed by fal",
|
|
2225
2988
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/mlsd",
|
|
2226
2989
|
task: "line segment preprocessing"
|
|
@@ -2233,7 +2996,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2233
2996
|
inputKind: "image",
|
|
2234
2997
|
name: "PiDiNet Edge Preprocessor",
|
|
2235
2998
|
outputKeys: ["image"],
|
|
2236
|
-
price: {
|
|
2999
|
+
price: {
|
|
3000
|
+
kind: "call",
|
|
3001
|
+
usd: 0
|
|
3002
|
+
},
|
|
2237
3003
|
pricing: "$0/compute-second listed by fal",
|
|
2238
3004
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/pidi",
|
|
2239
3005
|
task: "edge preprocessing"
|
|
@@ -2246,7 +3012,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2246
3012
|
inputKind: "image",
|
|
2247
3013
|
name: "SAM Preprocessor",
|
|
2248
3014
|
outputKeys: ["image"],
|
|
2249
|
-
price: {
|
|
3015
|
+
price: {
|
|
3016
|
+
kind: "call",
|
|
3017
|
+
usd: 0
|
|
3018
|
+
},
|
|
2250
3019
|
pricing: "$0/compute-second listed by fal",
|
|
2251
3020
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam",
|
|
2252
3021
|
task: "segmentation preprocessing"
|
|
@@ -2266,7 +3035,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2266
3035
|
inputKind: "image",
|
|
2267
3036
|
name: "SAM 2 Auto Segment",
|
|
2268
3037
|
outputKeys: ["combined_mask", "individual_masks"],
|
|
2269
|
-
price: {
|
|
3038
|
+
price: {
|
|
3039
|
+
kind: "call",
|
|
3040
|
+
usd: 0
|
|
3041
|
+
},
|
|
2270
3042
|
pricing: "$0/compute-second listed by fal",
|
|
2271
3043
|
queued: true,
|
|
2272
3044
|
sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
|
|
@@ -2311,7 +3083,11 @@ const STRUCTURE_TOOLS = {
|
|
|
2311
3083
|
inputKind: "video",
|
|
2312
3084
|
name: "SAM 3.1 Video",
|
|
2313
3085
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2314
|
-
price: {
|
|
3086
|
+
price: {
|
|
3087
|
+
frames: 16,
|
|
3088
|
+
kind: "frames",
|
|
3089
|
+
usd: .01
|
|
3090
|
+
},
|
|
2315
3091
|
pricing: "$0.01/16 frames of video input",
|
|
2316
3092
|
queued: true,
|
|
2317
3093
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
|
|
@@ -2383,7 +3159,11 @@ const STRUCTURE_TOOLS = {
|
|
|
2383
3159
|
inputKind: "video",
|
|
2384
3160
|
name: "SAM 3 Video",
|
|
2385
3161
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2386
|
-
price: {
|
|
3162
|
+
price: {
|
|
3163
|
+
frames: 16,
|
|
3164
|
+
kind: "frames",
|
|
3165
|
+
usd: .005
|
|
3166
|
+
},
|
|
2387
3167
|
pricing: "$0.005/16 frames of video input",
|
|
2388
3168
|
queued: true,
|
|
2389
3169
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
|
|
@@ -2402,7 +3182,11 @@ const STRUCTURE_TOOLS = {
|
|
|
2402
3182
|
inputKind: "video",
|
|
2403
3183
|
name: "SAM 3 Video RLE",
|
|
2404
3184
|
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2405
|
-
price: {
|
|
3185
|
+
price: {
|
|
3186
|
+
frames: 16,
|
|
3187
|
+
kind: "frames",
|
|
3188
|
+
usd: .005
|
|
3189
|
+
},
|
|
2406
3190
|
pricing: "$0.005/16 frames of video",
|
|
2407
3191
|
queued: true,
|
|
2408
3192
|
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
|
|
@@ -2416,7 +3200,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2416
3200
|
inputKind: "image",
|
|
2417
3201
|
name: "Scribble Preprocessor",
|
|
2418
3202
|
outputKeys: ["image"],
|
|
2419
|
-
price: {
|
|
3203
|
+
price: {
|
|
3204
|
+
kind: "call",
|
|
3205
|
+
usd: 0
|
|
3206
|
+
},
|
|
2420
3207
|
pricing: "$0/compute-second listed by fal",
|
|
2421
3208
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/scribble",
|
|
2422
3209
|
task: "scribble preprocessing"
|
|
@@ -2429,7 +3216,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2429
3216
|
inputKind: "image",
|
|
2430
3217
|
name: "TEED Edge Preprocessor",
|
|
2431
3218
|
outputKeys: ["image"],
|
|
2432
|
-
price: {
|
|
3219
|
+
price: {
|
|
3220
|
+
kind: "call",
|
|
3221
|
+
usd: 0
|
|
3222
|
+
},
|
|
2433
3223
|
pricing: "$0/compute-second listed by fal",
|
|
2434
3224
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/teed",
|
|
2435
3225
|
task: "edge preprocessing"
|
|
@@ -2442,7 +3232,10 @@ const STRUCTURE_TOOLS = {
|
|
|
2442
3232
|
inputKind: "image",
|
|
2443
3233
|
name: "ZoeDepth Preprocessor",
|
|
2444
3234
|
outputKeys: ["image"],
|
|
2445
|
-
price: {
|
|
3235
|
+
price: {
|
|
3236
|
+
kind: "call",
|
|
3237
|
+
usd: 0
|
|
3238
|
+
},
|
|
2446
3239
|
pricing: "$0/compute-second listed by fal",
|
|
2447
3240
|
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/zoe",
|
|
2448
3241
|
task: "depth preprocessing"
|
|
@@ -2459,6 +3252,17 @@ const FAL_TOOLS = {
|
|
|
2459
3252
|
};
|
|
2460
3253
|
Object.keys(FAL_TOOLS);
|
|
2461
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
|
|
2462
3266
|
//#region src/image/fal.ts
|
|
2463
3267
|
/**
|
|
2464
3268
|
* fal provider adapter.
|
|
@@ -2476,35 +3280,22 @@ Object.keys(FAL_TOOLS);
|
|
|
2476
3280
|
* need a specific size. Example:
|
|
2477
3281
|
* img.generate({
|
|
2478
3282
|
* provider: "fal",
|
|
2479
|
-
*
|
|
3283
|
+
* model: "fal-ai/gpt-image-1.5",
|
|
2480
3284
|
* prompt: "...",
|
|
2481
3285
|
* providerOptions: { fal: { image_size: "1024x1024" } },
|
|
2482
3286
|
* });
|
|
2483
3287
|
*/
|
|
2484
3288
|
/**
|
|
2485
|
-
* fal
|
|
2486
|
-
*
|
|
2487
|
-
* 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).
|
|
2488
3291
|
*/
|
|
2489
3292
|
const FAL_FLUX_MODEL = "fal-ai/flux-pro/v1.1-ultra";
|
|
2490
3293
|
const FAL_GPT_IMAGE_MODEL = "fal-ai/gpt-image-1.5";
|
|
2491
|
-
/**
|
|
2492
|
-
* Tier → fal model id. For fal, explicit `model:` endpoint ids are the primary
|
|
2493
|
-
* path (any fal endpoint resolves via passthrough); this tier map is a
|
|
2494
|
-
* convenience covering the two most common (FLUX Pro Ultra + gpt-image).
|
|
2495
|
-
*/
|
|
2496
|
-
const FAL_TIER_MODELS = {
|
|
2497
|
-
fast: FAL_FLUX_MODEL,
|
|
2498
|
-
balanced: FAL_GPT_IMAGE_MODEL,
|
|
2499
|
-
quality: FAL_GPT_IMAGE_MODEL,
|
|
2500
|
-
hero: FAL_GPT_IMAGE_MODEL
|
|
2501
|
-
};
|
|
2502
3294
|
/** Env var read for the fal key when `apiKey` is not supplied in config. */
|
|
2503
3295
|
const FAL_API_KEY_ENV = "FAL_KEY";
|
|
2504
3296
|
/**
|
|
2505
|
-
* Static fal USD/image, keyed by the fal ENDPOINT id.
|
|
2506
|
-
*
|
|
2507
|
-
* 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
|
|
2508
3299
|
* `fal-ai/flux/schnell`, `fal-ai/flux-2-pro`, `fal-ai/flux-2-max`. Every price is
|
|
2509
3300
|
* sourced from the fal registry snapshot in `../models`
|
|
2510
3301
|
* (`MODELS[...].pricePerImageUsd`) so this table stays in sync rather than
|
|
@@ -2531,12 +3322,17 @@ const FAL_IMAGE_PRICE_USD = {
|
|
|
2531
3322
|
"fal-ai/bytedance/seedream/v4/text-to-image": MODELS.seedream4?.pricePerImageUsd ?? .03,
|
|
2532
3323
|
"fal-ai/bytedance/seedream/v4.5/text-to-image": MODELS.seedream45?.pricePerImageUsd ?? .04,
|
|
2533
3324
|
"bytedance/seedream/v5/pro/text-to-image": MODELS.seedream5?.pricePerImageUsd ?? .0675,
|
|
2534
|
-
"
|
|
2535
|
-
"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,
|
|
2536
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,
|
|
2537
3329
|
"fal-ai/ideogram/v3": MODELS.ideogram?.pricePerImageUsd ?? .03,
|
|
2538
3330
|
"ideogram/v4": MODELS.ideogram4?.pricePerImageUsd ?? .03,
|
|
2539
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,
|
|
2540
3336
|
"fal-ai/qwen-image": MODELS.qwen?.pricePerImageUsd ?? .02
|
|
2541
3337
|
};
|
|
2542
3338
|
/**
|
|
@@ -2544,15 +3340,17 @@ const FAL_IMAGE_PRICE_USD = {
|
|
|
2544
3340
|
* var. Throws `MotifError` when neither is present (callers translate this into
|
|
2545
3341
|
* a `Result.err`).
|
|
2546
3342
|
*/
|
|
2547
|
-
function resolveModel$3(modelId, apiKey) {
|
|
3343
|
+
function resolveModel$3(modelId, apiKey, fetch) {
|
|
2548
3344
|
const key = apiKey ?? process.env["FAL_KEY"];
|
|
2549
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);
|
|
2550
|
-
return createFal({
|
|
3346
|
+
return createFal({
|
|
3347
|
+
apiKey: key,
|
|
3348
|
+
...toProviderFetch(fetch)
|
|
3349
|
+
}).image(modelId);
|
|
2551
3350
|
}
|
|
2552
3351
|
/** The fal provider adapter registered in the provider registry. */
|
|
2553
3352
|
const falAdapter = {
|
|
2554
3353
|
id: "fal",
|
|
2555
|
-
tierModels: FAL_TIER_MODELS,
|
|
2556
3354
|
apiKeyEnv: FAL_API_KEY_ENV,
|
|
2557
3355
|
resolveModel: resolveModel$3,
|
|
2558
3356
|
priceUsdByModel: FAL_IMAGE_PRICE_USD
|
|
@@ -2568,19 +3366,6 @@ const falAdapter = {
|
|
|
2568
3366
|
* multi-image-in → image-out editing (with an optional mask), which is the core
|
|
2569
3367
|
* operation this layer normalizes.
|
|
2570
3368
|
*/
|
|
2571
|
-
/**
|
|
2572
|
-
* Tier → Gemini image model id.
|
|
2573
|
-
*
|
|
2574
|
-
* Seeded from Material Desk's `RENDER_IMAGE_MODEL_BY_QUALITY` (the driving
|
|
2575
|
-
* consumer, see the design doc). `gemini-2.5-flash-image` is the proven-reachable
|
|
2576
|
-
* floor; the preview ids may require allowlist/tier access.
|
|
2577
|
-
*/
|
|
2578
|
-
const GOOGLE_TIER_MODELS = {
|
|
2579
|
-
fast: "gemini-2.5-flash-image",
|
|
2580
|
-
balanced: "gemini-3.1-flash-image-preview",
|
|
2581
|
-
quality: "gemini-3-pro-image-preview",
|
|
2582
|
-
hero: "gemini-3-pro-image-preview"
|
|
2583
|
-
};
|
|
2584
3369
|
/** Env var read for the Google API key when `apiKey` is not supplied in config. */
|
|
2585
3370
|
const GOOGLE_API_KEY_ENV = "GOOGLE_GENERATIVE_AI_API_KEY";
|
|
2586
3371
|
/**
|
|
@@ -2610,15 +3395,17 @@ const GOOGLE_IMAGE_PRICE_USD = {
|
|
|
2610
3395
|
* `GOOGLE_GENERATIVE_AI_API_KEY` env var. Throws `MotifError` when neither is
|
|
2611
3396
|
* present (callers translate this into a `Result.err`).
|
|
2612
3397
|
*/
|
|
2613
|
-
function resolveModel$2(modelId, apiKey) {
|
|
3398
|
+
function resolveModel$2(modelId, apiKey, fetch) {
|
|
2614
3399
|
const key = apiKey ?? process.env["GOOGLE_GENERATIVE_AI_API_KEY"];
|
|
2615
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);
|
|
2616
|
-
return createGoogleGenerativeAI({
|
|
3401
|
+
return createGoogleGenerativeAI({
|
|
3402
|
+
apiKey: key,
|
|
3403
|
+
...toProviderFetch(fetch)
|
|
3404
|
+
}).image(modelId);
|
|
2617
3405
|
}
|
|
2618
3406
|
/** The Google (Gemini) provider adapter registered in the provider registry. */
|
|
2619
3407
|
const googleAdapter = {
|
|
2620
3408
|
id: "google",
|
|
2621
|
-
tierModels: GOOGLE_TIER_MODELS,
|
|
2622
3409
|
apiKeyEnv: GOOGLE_API_KEY_ENV,
|
|
2623
3410
|
resolveModel: resolveModel$2,
|
|
2624
3411
|
priceUsdByModel: GOOGLE_IMAGE_PRICE_USD
|
|
@@ -2632,16 +3419,6 @@ const googleAdapter = {
|
|
|
2632
3419
|
* performs no network I/O — the request only happens when `generateImage`
|
|
2633
3420
|
* invokes `model.doGenerate`.
|
|
2634
3421
|
*/
|
|
2635
|
-
/**
|
|
2636
|
-
* Flare favors speed for everyday generation; Sunburst favors editing precision.
|
|
2637
|
-
* Explicit model ids still override tiers, including older GPT Image models.
|
|
2638
|
-
*/
|
|
2639
|
-
const OPENAI_TIER_MODELS = {
|
|
2640
|
-
fast: "gpt-image-2.5-flare",
|
|
2641
|
-
balanced: "gpt-image-2.5-flare",
|
|
2642
|
-
quality: "gpt-image-2.5-sunburst",
|
|
2643
|
-
hero: "gpt-image-2.5-sunburst"
|
|
2644
|
-
};
|
|
2645
3422
|
/** Env var read for the OpenAI API key when `apiKey` is not supplied in config. */
|
|
2646
3423
|
const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
|
|
2647
3424
|
/**
|
|
@@ -2665,15 +3442,17 @@ const OPENAI_IMAGE_PRICE_USD = { "gpt-image-1": .042 };
|
|
|
2665
3442
|
* `OPENAI_API_KEY` env var. Throws `MotifError` when neither is present
|
|
2666
3443
|
* (callers translate this into a `Result.err`).
|
|
2667
3444
|
*/
|
|
2668
|
-
function resolveModel$1(modelId, apiKey) {
|
|
3445
|
+
function resolveModel$1(modelId, apiKey, fetch) {
|
|
2669
3446
|
const key = apiKey ?? process.env["OPENAI_API_KEY"];
|
|
2670
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);
|
|
2671
|
-
return createOpenAI({
|
|
3448
|
+
return createOpenAI({
|
|
3449
|
+
apiKey: key,
|
|
3450
|
+
...toProviderFetch(fetch)
|
|
3451
|
+
}).image(modelId);
|
|
2672
3452
|
}
|
|
2673
3453
|
/** The OpenAI provider adapter registered in the provider registry. */
|
|
2674
3454
|
const openaiAdapter = {
|
|
2675
3455
|
id: "openai",
|
|
2676
|
-
tierModels: OPENAI_TIER_MODELS,
|
|
2677
3456
|
apiKeyEnv: OPENAI_API_KEY_ENV,
|
|
2678
3457
|
resolveModel: resolveModel$1,
|
|
2679
3458
|
priceUsdByModel: OPENAI_IMAGE_PRICE_USD
|
|
@@ -2690,19 +3469,6 @@ const openaiAdapter = {
|
|
|
2690
3469
|
* NOTE: Replicate's SDK names the credential option `apiToken` (not `apiKey`),
|
|
2691
3470
|
* and reads `REPLICATE_API_TOKEN` from the environment.
|
|
2692
3471
|
*/
|
|
2693
|
-
/**
|
|
2694
|
-
* Replicate is wired to a single high-quality model for now, so every tier maps
|
|
2695
|
-
* to FLUX 1.1 Pro Ultra. (The benchmark found Replicate ~1.45× faster than fal
|
|
2696
|
-
* for this model at the same price — see the design doc §10.)
|
|
2697
|
-
*/
|
|
2698
|
-
const REPLICATE_MODEL = "black-forest-labs/flux-1.1-pro-ultra";
|
|
2699
|
-
/** Tier → Replicate model id (all tiers → FLUX 1.1 Pro Ultra for now). */
|
|
2700
|
-
const REPLICATE_TIER_MODELS = {
|
|
2701
|
-
fast: REPLICATE_MODEL,
|
|
2702
|
-
balanced: REPLICATE_MODEL,
|
|
2703
|
-
quality: REPLICATE_MODEL,
|
|
2704
|
-
hero: REPLICATE_MODEL
|
|
2705
|
-
};
|
|
2706
3472
|
/** Env var read for the Replicate API token when `apiToken` is not in config. */
|
|
2707
3473
|
const REPLICATE_API_KEY_ENV = "REPLICATE_API_TOKEN";
|
|
2708
3474
|
/**
|
|
@@ -2719,10 +3485,13 @@ const REPLICATE_IMAGE_PRICE_USD = { "black-forest-labs/flux-1.1-pro-ultra": .06
|
|
|
2719
3485
|
* token), else the `REPLICATE_API_TOKEN` env var. Throws `MotifError` when
|
|
2720
3486
|
* neither is present (callers translate this into a `Result.err`).
|
|
2721
3487
|
*/
|
|
2722
|
-
function resolveModel(modelId, apiKey) {
|
|
3488
|
+
function resolveModel(modelId, apiKey, fetch) {
|
|
2723
3489
|
const token = apiKey ?? process.env["REPLICATE_API_TOKEN"];
|
|
2724
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);
|
|
2725
|
-
return createReplicate({
|
|
3491
|
+
return createReplicate({
|
|
3492
|
+
apiToken: token,
|
|
3493
|
+
...toProviderFetch(fetch)
|
|
3494
|
+
}).image(modelId);
|
|
2726
3495
|
}
|
|
2727
3496
|
//#endregion
|
|
2728
3497
|
//#region src/image/provider.ts
|
|
@@ -2736,7 +3505,6 @@ const PROVIDERS = {
|
|
|
2736
3505
|
openai: openaiAdapter,
|
|
2737
3506
|
replicate: {
|
|
2738
3507
|
id: "replicate",
|
|
2739
|
-
tierModels: REPLICATE_TIER_MODELS,
|
|
2740
3508
|
apiKeyEnv: REPLICATE_API_KEY_ENV,
|
|
2741
3509
|
resolveModel,
|
|
2742
3510
|
priceUsdByModel: REPLICATE_IMAGE_PRICE_USD
|
|
@@ -2780,6 +3548,9 @@ function costFromProviderMetadata(providerMetadata) {
|
|
|
2780
3548
|
}
|
|
2781
3549
|
}
|
|
2782
3550
|
/** Static per-image USD for a (provider, model), or undefined if unknown. */
|
|
3551
|
+
function providerPricePerImageUsd(provider, modelId) {
|
|
3552
|
+
return tablePricePerImage(provider, modelId);
|
|
3553
|
+
}
|
|
2783
3554
|
function tablePricePerImage(provider, modelId) {
|
|
2784
3555
|
const adapter = PROVIDERS[provider];
|
|
2785
3556
|
if (adapter === void 0) return;
|
|
@@ -2814,8 +3585,8 @@ function costForImages(provider, modelId, providerMetadata, imageCount) {
|
|
|
2814
3585
|
* `@howells/motif-sdk/image` — provider-agnostic image generation + editing.
|
|
2815
3586
|
*
|
|
2816
3587
|
* ESM-only subpath export, built on the Vercel AI SDK image interface
|
|
2817
|
-
* (`generateImage`, `@ai-sdk/*`).
|
|
2818
|
-
*
|
|
3588
|
+
* (`generateImage`, `@ai-sdk/*`). The caller names the provider and model;
|
|
3589
|
+
* reuses the SDK's Result convention (`Result<T, MotifError>` — no
|
|
2819
3590
|
* thrown exceptions). Google (Gemini) is the only provider in Phase 1a.
|
|
2820
3591
|
*
|
|
2821
3592
|
* @example
|
|
@@ -2823,11 +3594,10 @@ function costForImages(provider, modelId, providerMetadata, imageCount) {
|
|
|
2823
3594
|
* import { createMotifImage } from "@howells/motif-sdk/image";
|
|
2824
3595
|
*
|
|
2825
3596
|
* const img = createMotifImage({ defaultProvider: "google" });
|
|
2826
|
-
* const r = await img.generate({
|
|
3597
|
+
* const r = await img.generate({ model: "gemini-2.5-flash-image", prompt: "a bare concrete wall" });
|
|
2827
3598
|
* if (r.isOk()) console.log(r.value.images[0].mediaType, r.value.cost);
|
|
2828
3599
|
* ```
|
|
2829
3600
|
*/
|
|
2830
|
-
const DEFAULT_TIER = "balanced";
|
|
2831
3601
|
const DEFAULT_PROVIDER = "google";
|
|
2832
3602
|
/**
|
|
2833
3603
|
* Create a provider-agnostic image client.
|
|
@@ -2854,8 +3624,8 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2854
3624
|
async function generate(opts) {
|
|
2855
3625
|
const provider = resolveProvider(opts.provider);
|
|
2856
3626
|
try {
|
|
2857
|
-
const modelId =
|
|
2858
|
-
const model = resolveModelFn(provider, modelId, apiKeyFor(provider));
|
|
3627
|
+
const modelId = opts.model;
|
|
3628
|
+
const model = resolveModelFn(provider, modelId, apiKeyFor(provider), config.fetch);
|
|
2859
3629
|
const result = await generateImageFn({
|
|
2860
3630
|
model,
|
|
2861
3631
|
prompt: opts.prompt,
|
|
@@ -2863,6 +3633,7 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2863
3633
|
...opts.size === void 0 ? {} : { size: opts.size },
|
|
2864
3634
|
...opts.aspectRatio === void 0 ? {} : { aspectRatio: opts.aspectRatio },
|
|
2865
3635
|
...opts.seed === void 0 ? {} : { seed: opts.seed },
|
|
3636
|
+
...config.maxRetries === void 0 ? {} : { maxRetries: config.maxRetries },
|
|
2866
3637
|
...opts.signal === void 0 ? {} : { abortSignal: opts.signal },
|
|
2867
3638
|
...opts.headers === void 0 ? {} : { headers: opts.headers },
|
|
2868
3639
|
...opts.providerOptions === void 0 ? {} : { providerOptions: toProviderOptions(opts.providerOptions) }
|
|
@@ -2875,8 +3646,8 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2875
3646
|
async function edit(opts) {
|
|
2876
3647
|
const provider = resolveProvider(opts.provider);
|
|
2877
3648
|
try {
|
|
2878
|
-
const modelId =
|
|
2879
|
-
const model = resolveModelFn(provider, modelId, apiKeyFor(provider));
|
|
3649
|
+
const modelId = opts.model;
|
|
3650
|
+
const model = resolveModelFn(provider, modelId, apiKeyFor(provider), config.fetch);
|
|
2880
3651
|
const result = await generateImageFn({
|
|
2881
3652
|
model,
|
|
2882
3653
|
prompt: {
|
|
@@ -2886,6 +3657,7 @@ function createMotifImage(config = {}, deps = {}) {
|
|
|
2886
3657
|
},
|
|
2887
3658
|
...opts.n === void 0 ? {} : { n: opts.n },
|
|
2888
3659
|
...opts.seed === void 0 ? {} : { seed: opts.seed },
|
|
3660
|
+
...config.maxRetries === void 0 ? {} : { maxRetries: config.maxRetries },
|
|
2889
3661
|
...opts.signal === void 0 ? {} : { abortSignal: opts.signal },
|
|
2890
3662
|
...opts.headers === void 0 ? {} : { headers: opts.headers },
|
|
2891
3663
|
...opts.providerOptions === void 0 ? {} : { providerOptions: toProviderOptions(opts.providerOptions) }
|
|
@@ -2971,14 +3743,8 @@ async function runBestOfN(opts, generate, edit) {
|
|
|
2971
3743
|
}
|
|
2972
3744
|
}
|
|
2973
3745
|
/** Default provider dispatch: resolve the model through the provider registry. */
|
|
2974
|
-
function defaultResolveModel(provider, modelId, apiKey) {
|
|
2975
|
-
return getProviderAdapter(provider).resolveModel(modelId, apiKey);
|
|
2976
|
-
}
|
|
2977
|
-
/** Resolve the model id: explicit `model` wins, else the provider's tier map. */
|
|
2978
|
-
function resolveModelId(provider, model, tier) {
|
|
2979
|
-
if (model !== void 0 && model !== "") return model;
|
|
2980
|
-
const resolvedTier = tier ?? DEFAULT_TIER;
|
|
2981
|
-
return getProviderAdapter(provider).tierModels[resolvedTier];
|
|
3746
|
+
function defaultResolveModel(provider, modelId, apiKey, fetch) {
|
|
3747
|
+
return getProviderAdapter(provider).resolveModel(modelId, apiKey, fetch);
|
|
2982
3748
|
}
|
|
2983
3749
|
/** Map the AI SDK result → normalized MotifImageResult (with cost + requestId). */
|
|
2984
3750
|
function toMotifImageResult(result, provider, model) {
|
|
@@ -3070,7 +3836,10 @@ function toMotifError(error) {
|
|
|
3070
3836
|
if (error instanceof MotifError) return error;
|
|
3071
3837
|
const message = error instanceof Error ? error.message : String(error);
|
|
3072
3838
|
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
3073
|
-
|
|
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);
|
|
3074
3843
|
}
|
|
3075
3844
|
//#endregion
|
|
3076
|
-
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 };
|