@howells/motif-sdk 5.2.0 → 5.4.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 +8 -14
- package/dist/image.js +167 -14
- package/dist/index.cjs +628 -183
- package/dist/index.d.cts +56 -27
- package/dist/index.d.ts +56 -27
- package/dist/index.js +628 -183
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -556,6 +556,7 @@ const CARRIED_FIELDS = [
|
|
|
556
556
|
"video",
|
|
557
557
|
"prompt",
|
|
558
558
|
"mask",
|
|
559
|
+
"markup",
|
|
559
560
|
"references",
|
|
560
561
|
"boxes",
|
|
561
562
|
"margin",
|
|
@@ -713,6 +714,7 @@ const FAL_PRICING_CHECKED_AT = "2026-05-12";
|
|
|
713
714
|
const FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
|
|
714
715
|
const FAL_PRICING_CHECKED_AUG_2026 = "2026-08-23";
|
|
715
716
|
const FAL_PRICING_CHECKED_SEP_2026 = "2026-09-16";
|
|
717
|
+
const FAL_PRICING_CHECKED_OCT_2026 = "2026-10-29";
|
|
716
718
|
const MODELS = {
|
|
717
719
|
flare: {
|
|
718
720
|
benchmark: {
|
|
@@ -728,7 +730,7 @@ const MODELS = {
|
|
|
728
730
|
rank: 2
|
|
729
731
|
}
|
|
730
732
|
},
|
|
731
|
-
useCase: "Frontier generation and editing
|
|
733
|
+
useCase: "Frontier generation and editing at max reasoning effort; scored as GPT Image 2.5 until 2.6 is benchmarked"
|
|
732
734
|
},
|
|
733
735
|
defaultQuality: "max",
|
|
734
736
|
customImageSize: {
|
|
@@ -738,11 +740,11 @@ const MODELS = {
|
|
|
738
740
|
minPixels: 655360,
|
|
739
741
|
multipleOf: 16
|
|
740
742
|
},
|
|
741
|
-
editEndpoint: "openai/gpt-image-2.
|
|
742
|
-
endpoint: "openai/gpt-image-2.
|
|
743
|
+
editEndpoint: "openai/gpt-image-2.6/flare/edit",
|
|
744
|
+
endpoint: "openai/gpt-image-2.6/flare/text-to-image",
|
|
743
745
|
maxReferenceImages: 16,
|
|
744
|
-
name: "GPT Image 2.
|
|
745
|
-
pricing: "Token-based:
|
|
746
|
+
name: "GPT Image 2.6 Flare",
|
|
747
|
+
pricing: "Token-based: image $8/M input, $2/M cached, $30/M output; text $5/M input, $1.25/M cached",
|
|
746
748
|
pricePerImageUsd: null,
|
|
747
749
|
sizeMode: "image_size_enum",
|
|
748
750
|
supportsAspect: true,
|
|
@@ -750,8 +752,23 @@ const MODELS = {
|
|
|
750
752
|
supportsEdit: true,
|
|
751
753
|
supportsMaskImage: true,
|
|
752
754
|
maskImageField: "mask_url",
|
|
755
|
+
supportsMarkupImage: true,
|
|
756
|
+
autoEditSize: true,
|
|
753
757
|
supportsNumImages: true,
|
|
754
758
|
supportsOutputFormat: true,
|
|
759
|
+
supportedOutputFormats: [
|
|
760
|
+
"jpeg",
|
|
761
|
+
"png",
|
|
762
|
+
"webp",
|
|
763
|
+
"gif",
|
|
764
|
+
"svg"
|
|
765
|
+
],
|
|
766
|
+
supportedEditOutputFormats: [
|
|
767
|
+
"jpeg",
|
|
768
|
+
"png",
|
|
769
|
+
"webp",
|
|
770
|
+
"gif"
|
|
771
|
+
],
|
|
755
772
|
supportsQuality: true,
|
|
756
773
|
supportedQualities: [
|
|
757
774
|
"auto",
|
|
@@ -780,7 +797,7 @@ const MODELS = {
|
|
|
780
797
|
rank: 1
|
|
781
798
|
}
|
|
782
799
|
},
|
|
783
|
-
useCase: "Frontier generation and editing
|
|
800
|
+
useCase: "Frontier generation and editing at max reasoning effort; scored as GPT Image 2.5 until 2.6 is benchmarked"
|
|
784
801
|
},
|
|
785
802
|
defaultQuality: "max",
|
|
786
803
|
customImageSize: {
|
|
@@ -790,11 +807,11 @@ const MODELS = {
|
|
|
790
807
|
minPixels: 655360,
|
|
791
808
|
multipleOf: 16
|
|
792
809
|
},
|
|
793
|
-
editEndpoint: "openai/gpt-image-2.
|
|
794
|
-
endpoint: "openai/gpt-image-2.
|
|
810
|
+
editEndpoint: "openai/gpt-image-2.6/sunburst/edit",
|
|
811
|
+
endpoint: "openai/gpt-image-2.6/sunburst/text-to-image",
|
|
795
812
|
maxReferenceImages: 16,
|
|
796
|
-
name: "GPT Image 2.
|
|
797
|
-
pricing: "Token-based:
|
|
813
|
+
name: "GPT Image 2.6 Sunburst",
|
|
814
|
+
pricing: "Token-based: image $8/M input, $2/M cached, $30/M output; text $5/M input, $1.25/M cached",
|
|
798
815
|
pricePerImageUsd: null,
|
|
799
816
|
sizeMode: "image_size_enum",
|
|
800
817
|
supportsAspect: true,
|
|
@@ -802,8 +819,23 @@ const MODELS = {
|
|
|
802
819
|
supportsEdit: true,
|
|
803
820
|
supportsMaskImage: true,
|
|
804
821
|
maskImageField: "mask_url",
|
|
822
|
+
supportsMarkupImage: true,
|
|
823
|
+
autoEditSize: true,
|
|
805
824
|
supportsNumImages: true,
|
|
806
825
|
supportsOutputFormat: true,
|
|
826
|
+
supportedOutputFormats: [
|
|
827
|
+
"jpeg",
|
|
828
|
+
"png",
|
|
829
|
+
"webp",
|
|
830
|
+
"gif",
|
|
831
|
+
"svg"
|
|
832
|
+
],
|
|
833
|
+
supportedEditOutputFormats: [
|
|
834
|
+
"jpeg",
|
|
835
|
+
"png",
|
|
836
|
+
"webp",
|
|
837
|
+
"gif"
|
|
838
|
+
],
|
|
807
839
|
supportsQuality: true,
|
|
808
840
|
supportedQualities: [
|
|
809
841
|
"auto",
|
|
@@ -2069,6 +2101,7 @@ const MODELS = {
|
|
|
2069
2101
|
maxPixels: 4194304,
|
|
2070
2102
|
minPixels: 262144
|
|
2071
2103
|
},
|
|
2104
|
+
editEndpoint: "alibaba/qwen-image-3/edit",
|
|
2072
2105
|
endpoint: "alibaba/qwen-image-3/text-to-image",
|
|
2073
2106
|
falPricing: {
|
|
2074
2107
|
checkedAt: FAL_PRICING_CHECKED_SEP_2026,
|
|
@@ -2079,12 +2112,13 @@ const MODELS = {
|
|
|
2079
2112
|
unit: "images",
|
|
2080
2113
|
unitPrice: .04
|
|
2081
2114
|
},
|
|
2115
|
+
maxReferenceImages: 3,
|
|
2082
2116
|
name: "Qwen Image 3",
|
|
2083
2117
|
pricePerImageUsd: .04,
|
|
2084
2118
|
pricing: "$0.04 (1K) / $0.075 (2K)",
|
|
2085
2119
|
sizeMode: "image_size_enum",
|
|
2086
2120
|
supportsAspect: true,
|
|
2087
|
-
supportsEdit:
|
|
2121
|
+
supportsEdit: true,
|
|
2088
2122
|
supportsNegativePrompt: true,
|
|
2089
2123
|
supportsNumImages: true,
|
|
2090
2124
|
supportsOutputFormat: true,
|
|
@@ -2287,6 +2321,120 @@ const MODELS = {
|
|
|
2287
2321
|
supportsSyncMode: true,
|
|
2288
2322
|
type: "generation"
|
|
2289
2323
|
},
|
|
2324
|
+
"muse-image": {
|
|
2325
|
+
benchmark: { useCase: "Low-cost generation and multi-reference editing; Arena #8 generation / #10 editing, 2026-10-29" },
|
|
2326
|
+
editEndpoint: "meta/muse-image/edit",
|
|
2327
|
+
endpoint: "meta/muse-image/text-to-image",
|
|
2328
|
+
falPricing: {
|
|
2329
|
+
checkedAt: FAL_PRICING_CHECKED_OCT_2026,
|
|
2330
|
+
currency: "USD",
|
|
2331
|
+
endpointId: "meta/muse-image/text-to-image",
|
|
2332
|
+
estimatedCostPerImageUsd: .01,
|
|
2333
|
+
source: "fal-pricing-api",
|
|
2334
|
+
unit: "images",
|
|
2335
|
+
unitPrice: .01
|
|
2336
|
+
},
|
|
2337
|
+
maxReferenceImages: 10,
|
|
2338
|
+
name: "Meta Muse Image",
|
|
2339
|
+
pricePerImageUsd: .01,
|
|
2340
|
+
pricing: "$0.01",
|
|
2341
|
+
sizeMode: "aspect_ratio",
|
|
2342
|
+
supportedAspects: [
|
|
2343
|
+
"1:1",
|
|
2344
|
+
"4:3",
|
|
2345
|
+
"3:4",
|
|
2346
|
+
"16:9",
|
|
2347
|
+
"9:16",
|
|
2348
|
+
"3:2",
|
|
2349
|
+
"2:3",
|
|
2350
|
+
"4:5",
|
|
2351
|
+
"5:4",
|
|
2352
|
+
"21:9",
|
|
2353
|
+
"4:1",
|
|
2354
|
+
"1:4",
|
|
2355
|
+
"8:1",
|
|
2356
|
+
"1:8"
|
|
2357
|
+
],
|
|
2358
|
+
supportsAspect: true,
|
|
2359
|
+
supportsEdit: true,
|
|
2360
|
+
supportsNumImages: true,
|
|
2361
|
+
supportsOutputFormat: true,
|
|
2362
|
+
supportsResolution: false,
|
|
2363
|
+
supportsSyncMode: true,
|
|
2364
|
+
type: "generation"
|
|
2365
|
+
},
|
|
2366
|
+
krea3: {
|
|
2367
|
+
benchmark: { useCase: "Reference editing with prompt planning. Motif sends fal's defaults (berry, medium reasoning), which Arena has not scored; Arena's 1411 editing score (2026-10-29) is for Ultra, available with --param reasoning_level=ultra" },
|
|
2368
|
+
customImageSize: {
|
|
2369
|
+
maxEdge: 3200,
|
|
2370
|
+
maxPixels: 4220160,
|
|
2371
|
+
minEdge: 256,
|
|
2372
|
+
multipleOf: 16
|
|
2373
|
+
},
|
|
2374
|
+
editEndpoint: "krea/v3/edit",
|
|
2375
|
+
endpoint: "krea/v3/text-to-image",
|
|
2376
|
+
falPricing: {
|
|
2377
|
+
checkedAt: FAL_PRICING_CHECKED_OCT_2026,
|
|
2378
|
+
currency: "USD",
|
|
2379
|
+
endpointId: "krea/v3/text-to-image",
|
|
2380
|
+
estimatedCostPerImageUsd: .025,
|
|
2381
|
+
source: "fal-pricing-api",
|
|
2382
|
+
unit: "megapixels",
|
|
2383
|
+
unitPrice: .025
|
|
2384
|
+
},
|
|
2385
|
+
maxReferenceImages: 8,
|
|
2386
|
+
name: "Krea 3",
|
|
2387
|
+
pricePerImageUsd: .025,
|
|
2388
|
+
pricing: "$0.025/MP (edits also bill each reference)",
|
|
2389
|
+
sizeMode: "image_size_enum",
|
|
2390
|
+
supportedOutputFormats: ["png", "jpeg"],
|
|
2391
|
+
supportsAspect: true,
|
|
2392
|
+
supportsEdit: true,
|
|
2393
|
+
supportsNegativePrompt: true,
|
|
2394
|
+
supportsNumImages: false,
|
|
2395
|
+
supportsOutputFormat: true,
|
|
2396
|
+
supportsResolution: false,
|
|
2397
|
+
supportsSafetyChecker: true,
|
|
2398
|
+
supportsSeed: true,
|
|
2399
|
+
supportsSyncMode: true,
|
|
2400
|
+
type: "generation"
|
|
2401
|
+
},
|
|
2402
|
+
"grok-image-quality": {
|
|
2403
|
+
benchmark: { useCase: "Detailed generation and editing with stronger text rendering; Arena #11 editing, 2026-10-29" },
|
|
2404
|
+
editEndpoint: "xai/grok-imagine-image/quality/edit",
|
|
2405
|
+
endpoint: "xai/grok-imagine-image/quality/text-to-image",
|
|
2406
|
+
falPricing: {
|
|
2407
|
+
checkedAt: FAL_PRICING_CHECKED_OCT_2026,
|
|
2408
|
+
currency: "USD",
|
|
2409
|
+
endpointId: "xai/grok-imagine-image/quality/text-to-image",
|
|
2410
|
+
estimatedCostPerImageUsd: .05,
|
|
2411
|
+
source: "fal-pricing-api",
|
|
2412
|
+
unit: "images",
|
|
2413
|
+
unitPrice: .05
|
|
2414
|
+
},
|
|
2415
|
+
maxReferenceImages: 3,
|
|
2416
|
+
name: "Grok Imagine Image Quality",
|
|
2417
|
+
pricePerImageUsd: .05,
|
|
2418
|
+
pricing: "$0.05 (1K) / $0.07 (2K)",
|
|
2419
|
+
sizeMode: "aspect_ratio",
|
|
2420
|
+
supportedAspects: [
|
|
2421
|
+
"16:9",
|
|
2422
|
+
"4:3",
|
|
2423
|
+
"3:2",
|
|
2424
|
+
"1:1",
|
|
2425
|
+
"2:3",
|
|
2426
|
+
"3:4",
|
|
2427
|
+
"9:16"
|
|
2428
|
+
],
|
|
2429
|
+
supportedResolutions: ["1K", "2K"],
|
|
2430
|
+
supportsAspect: true,
|
|
2431
|
+
supportsEdit: true,
|
|
2432
|
+
supportsNumImages: true,
|
|
2433
|
+
supportsOutputFormat: true,
|
|
2434
|
+
supportsResolution: true,
|
|
2435
|
+
supportsSyncMode: true,
|
|
2436
|
+
type: "generation"
|
|
2437
|
+
},
|
|
2290
2438
|
kling: {
|
|
2291
2439
|
endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
|
|
2292
2440
|
name: "Kling v3 Pro",
|
|
@@ -2365,7 +2513,10 @@ const GENERATION_MODELS = [
|
|
|
2365
2513
|
"qwen3",
|
|
2366
2514
|
"banana2-lite",
|
|
2367
2515
|
"grok-image-2",
|
|
2516
|
+
"grok-image-quality",
|
|
2368
2517
|
"grok-image",
|
|
2518
|
+
"muse-image",
|
|
2519
|
+
"krea3",
|
|
2369
2520
|
"flux2-flex",
|
|
2370
2521
|
"flux2-pro",
|
|
2371
2522
|
"flux2-turbo",
|
|
@@ -2407,6 +2558,7 @@ function estimateCost(model, resolution, numImages = 1, editImages = 0) {
|
|
|
2407
2558
|
if (configuredPrice === null) return null;
|
|
2408
2559
|
if (model === "mai-image-2.5-pro" && editImages > 0) return .27 * numImages;
|
|
2409
2560
|
if (model === "grok-image-2" && editImages > 0) return (resolution === "2K" || resolution === "4K" ? .08 : .06) * numImages + .01 * editImages;
|
|
2561
|
+
if (model === "grok-image-quality") return (resolution === "2K" || resolution === "4K" ? .07 : .05) * numImages + .01 * editImages;
|
|
2410
2562
|
if (configuredPrice !== void 0) {
|
|
2411
2563
|
if ((model === "banana" || model === "gemini3") && resolution === "4K") return configuredPrice * 2 * numImages;
|
|
2412
2564
|
if (model === "seedream5" && (resolution === "2K" || resolution === "4K")) return configuredPrice * 2 * numImages;
|
|
@@ -2552,6 +2704,18 @@ function requestIdFromBody(text) {
|
|
|
2552
2704
|
* error can name both what a model does support and which models support the
|
|
2553
2705
|
* option that was refused.
|
|
2554
2706
|
*/
|
|
2707
|
+
const DEFAULT_OUTPUT_FORMATS = [
|
|
2708
|
+
"jpeg",
|
|
2709
|
+
"png",
|
|
2710
|
+
"webp"
|
|
2711
|
+
];
|
|
2712
|
+
/** The output formats a Model returns, for an edit when `editing` is set.
|
|
2713
|
+
* A Model that takes no format returns none it can be asked for. */
|
|
2714
|
+
function outputFormatsOf(config, editing) {
|
|
2715
|
+
if (config.supportsOutputFormat !== true) return [];
|
|
2716
|
+
const formats = config.supportedOutputFormats ?? DEFAULT_OUTPUT_FORMATS;
|
|
2717
|
+
return editing ? config.supportedEditOutputFormats ?? formats : formats;
|
|
2718
|
+
}
|
|
2555
2719
|
/** Generation options a model can refuse, with the registry test for each. */
|
|
2556
2720
|
const OPTION_CAPABILITIES = {
|
|
2557
2721
|
aspect: (config) => (config.sizeMode ?? "aspect_ratio") !== "none",
|
|
@@ -2567,6 +2731,7 @@ const OPTION_CAPABILITIES = {
|
|
|
2567
2731
|
imageSize: (config) => config.sizeMode === "gpt_size" || config.sizeMode === "image_size_enum",
|
|
2568
2732
|
inputFidelity: (config) => config.sizeMode === "gpt_size",
|
|
2569
2733
|
limitGenerations: (config) => config.supportsLimitGenerations === true,
|
|
2734
|
+
markupImageUrl: (config) => config.supportsMarkupImage === true,
|
|
2570
2735
|
maskImageUrl: (config) => config.supportsMaskImage === true,
|
|
2571
2736
|
negativePrompt: (config) => config.supportsNegativePrompt === true,
|
|
2572
2737
|
numImages: (config) => config.supportsNumImages,
|
|
@@ -2700,6 +2865,9 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
|
|
|
2700
2865
|
if (options.imagePromptStrength !== void 0 && !hasEditImages) throw new Error("imagePromptStrength requires editImageUrls");
|
|
2701
2866
|
if (options.maskImageUrl !== void 0 && config.supportsMaskImage !== true) unsupported(config, "maskImageUrl");
|
|
2702
2867
|
if (options.maskImageUrl !== void 0 && !hasEditImages) throw new Error("maskImageUrl requires editImageUrls");
|
|
2868
|
+
if (options.markupImageUrl !== void 0 && config.supportsMarkupImage !== true) unsupported(config, "markupImageUrl");
|
|
2869
|
+
if (options.markupImageUrl !== void 0 && !hasEditImages) throw new Error("markupImageUrl requires editImageUrls");
|
|
2870
|
+
if (options.markupImageUrl !== void 0 && options.maskImageUrl !== void 0) throw new Error("A markup image can't be combined with a mask.");
|
|
2703
2871
|
if (options.seed !== void 0 && config.supportsSeed !== true) unsupported(config, "seed");
|
|
2704
2872
|
if (options.outputFormat !== void 0 && config.supportsOutputFormat !== true) unsupported(config, "outputFormat");
|
|
2705
2873
|
if (options.quality !== void 0 && config.supportsQuality !== true) unsupported(config, "quality");
|
|
@@ -2739,7 +2907,7 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
|
|
|
2739
2907
|
* - `none`: no size control (video models)
|
|
2740
2908
|
*/
|
|
2741
2909
|
function buildGenerateBody(options) {
|
|
2742
|
-
const { prompt, model, creative, aspect = "1:1", resolution = "2K", numImages = 1, background, editImageUrls, enableGoogleSearch, enableSafetyChecker, transparent, inputFidelity, imageSize, imagePromptStrength, limitGenerations, maskImageUrl, seed, outputFormat, quality, negativePrompt, style, syncMode, thinkingLevel, renderingSpeed, guidanceScale, numInferenceSteps, raw, enhancePrompt, safetyTolerance, enableWebSearch, expandPrompt } = options;
|
|
2910
|
+
const { prompt, model, creative, aspect = "1:1", resolution = "2K", numImages = 1, background, editImageUrls, enableGoogleSearch, enableSafetyChecker, transparent, inputFidelity, imageSize, imagePromptStrength, limitGenerations, maskImageUrl, markupImageUrl, seed, outputFormat, quality, negativePrompt, style, syncMode, thinkingLevel, renderingSpeed, guidanceScale, numInferenceSteps, raw, enhancePrompt, safetyTolerance, enableWebSearch, expandPrompt } = options;
|
|
2743
2911
|
const config = MODELS[model];
|
|
2744
2912
|
if (!config) throw new Error(`Unknown model: ${model}`);
|
|
2745
2913
|
let { endpoint } = config;
|
|
@@ -2758,11 +2926,11 @@ function buildGenerateBody(options) {
|
|
|
2758
2926
|
body.image_size = normalizedImageSize ?? (hasEditImages && !explicitAspect ? "auto" : aspectToGptSize(aspect));
|
|
2759
2927
|
break;
|
|
2760
2928
|
case "image_size_enum":
|
|
2761
|
-
body.image_size = normalizedImageSize ?? falImageSizeFor(config, aspect, resolution);
|
|
2929
|
+
body.image_size = normalizedImageSize ?? (config.autoEditSize === true && hasEditImages && !explicitAspect ? "auto" : falImageSizeFor(config, aspect, resolution));
|
|
2762
2930
|
break;
|
|
2763
2931
|
case "aspect_ratio":
|
|
2764
2932
|
if (config.supportsAspect) body.aspect_ratio = aspect;
|
|
2765
|
-
if (config.supportsResolution) body.resolution = model === "grok-image" || model === "grok-image-2" ? resolution.toLowerCase() : resolution;
|
|
2933
|
+
if (config.supportsResolution) body.resolution = model === "grok-image" || model === "grok-image-2" || model === "grok-image-quality" ? resolution.toLowerCase() : resolution;
|
|
2766
2934
|
}
|
|
2767
2935
|
if (config.supportsQuality === true) body.quality = quality ?? config.defaultQuality ?? "high";
|
|
2768
2936
|
if (config.supportsBackground === true && background) body.background = background;
|
|
@@ -2771,7 +2939,8 @@ function buildGenerateBody(options) {
|
|
|
2771
2939
|
if (syncMode !== void 0 && config.supportsSyncMode === true) body.sync_mode = syncMode;
|
|
2772
2940
|
if (seed !== void 0 && config.supportsSeed === true) body.seed = seed;
|
|
2773
2941
|
if (outputFormat && config.supportsOutputFormat === true && transparent !== true) {
|
|
2774
|
-
|
|
2942
|
+
const formats = outputFormatsOf(config, hasEditImages);
|
|
2943
|
+
if (!formats.includes(outputFormat)) throw new Error(`${config.name} ${hasEditImages ? "edits support" : "supports"} output formats: ${formats.join(", ")}`);
|
|
2775
2944
|
body.output_format = outputFormat;
|
|
2776
2945
|
}
|
|
2777
2946
|
if (config.supportsRaw === true && raw !== void 0) body.raw = raw;
|
|
@@ -2830,6 +2999,7 @@ function buildGenerateBody(options) {
|
|
|
2830
2999
|
if (inputFidelity) body.input_fidelity = inputFidelity;
|
|
2831
3000
|
}
|
|
2832
3001
|
if (maskImageUrl !== void 0 && maskImageUrl !== "" && config.supportsMaskImage === true) body[config.maskImageField ?? "mask_image_url"] = maskImageUrl;
|
|
3002
|
+
if (markupImageUrl !== void 0 && config.supportsMarkupImage === true) body.markup_image_url = markupImageUrl;
|
|
2833
3003
|
return {
|
|
2834
3004
|
body,
|
|
2835
3005
|
endpoint
|
|
@@ -4418,10 +4588,15 @@ async function getToolResult(exec, job) {
|
|
|
4418
4588
|
}
|
|
4419
4589
|
/** Poll a submitted job to completion and fetch its result. Submits nothing. */
|
|
4420
4590
|
async function awaitQueuedRequest(exec, job, onProgress) {
|
|
4591
|
+
let logsSeen = 0;
|
|
4421
4592
|
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
|
|
4422
4593
|
const status = await checkToolStatus(exec, job);
|
|
4423
4594
|
if (status.isErr()) return (0, neverthrow.err)(status.error);
|
|
4424
|
-
|
|
4595
|
+
const logs = status.value.logs ?? [];
|
|
4596
|
+
const fresh = logs.slice(logs.length < logsSeen ? 0 : logsSeen);
|
|
4597
|
+
logsSeen = logs.length;
|
|
4598
|
+
if (fresh.length > 0) onProgress?.(status.value.status, status.value.queuePosition, fresh);
|
|
4599
|
+
else onProgress?.(status.value.status, status.value.queuePosition);
|
|
4425
4600
|
if (status.value.status === "completed") return (await getToolResult(exec, job)).map((data) => ({
|
|
4426
4601
|
data,
|
|
4427
4602
|
requestId: job.requestId
|
|
@@ -5166,6 +5341,201 @@ function sumCosts(costs) {
|
|
|
5166
5341
|
};
|
|
5167
5342
|
}
|
|
5168
5343
|
//#endregion
|
|
5344
|
+
//#region src/image-benchmarks.ts
|
|
5345
|
+
const BOARDS = {
|
|
5346
|
+
"text-to-image": {
|
|
5347
|
+
sunburst: {
|
|
5348
|
+
elo: 1427,
|
|
5349
|
+
rank: 1,
|
|
5350
|
+
uncertainty: 5,
|
|
5351
|
+
evaluatedModel: "GPT-Image-2.5 Sunburst",
|
|
5352
|
+
preliminary: true,
|
|
5353
|
+
votes: 40323
|
|
5354
|
+
},
|
|
5355
|
+
flare: {
|
|
5356
|
+
elo: 1401,
|
|
5357
|
+
rank: 2,
|
|
5358
|
+
uncertainty: 5,
|
|
5359
|
+
evaluatedModel: "GPT-Image-2.5 Flare",
|
|
5360
|
+
preliminary: true,
|
|
5361
|
+
votes: 39218
|
|
5362
|
+
},
|
|
5363
|
+
gpt2: {
|
|
5364
|
+
elo: 1381,
|
|
5365
|
+
rank: 3,
|
|
5366
|
+
uncertainty: 4,
|
|
5367
|
+
evaluatedModel: "GPT-Image-2 (Medium)",
|
|
5368
|
+
votes: 103385
|
|
5369
|
+
},
|
|
5370
|
+
banana21: {
|
|
5371
|
+
elo: 1325,
|
|
5372
|
+
rank: 5,
|
|
5373
|
+
uncertainty: 9,
|
|
5374
|
+
evaluatedModel: "Nano Banana 2.1",
|
|
5375
|
+
preliminary: true,
|
|
5376
|
+
votes: 5290
|
|
5377
|
+
},
|
|
5378
|
+
"grok-image-2": {
|
|
5379
|
+
elo: 1317,
|
|
5380
|
+
rank: 6,
|
|
5381
|
+
uncertainty: 7,
|
|
5382
|
+
evaluatedModel: "Grok Imagine Image 2.0",
|
|
5383
|
+
preliminary: true,
|
|
5384
|
+
votes: 10902
|
|
5385
|
+
},
|
|
5386
|
+
"muse-image": {
|
|
5387
|
+
elo: 1269,
|
|
5388
|
+
rank: 8,
|
|
5389
|
+
uncertainty: 5,
|
|
5390
|
+
evaluatedModel: "Muse Image (Meta)",
|
|
5391
|
+
votes: 53444
|
|
5392
|
+
},
|
|
5393
|
+
qwen3: {
|
|
5394
|
+
elo: 1254,
|
|
5395
|
+
rank: 9,
|
|
5396
|
+
uncertainty: 6,
|
|
5397
|
+
evaluatedModel: "Qwen-Image-3.0-Pro",
|
|
5398
|
+
votes: 11444
|
|
5399
|
+
},
|
|
5400
|
+
"mai-image-2.5": {
|
|
5401
|
+
elo: 1253,
|
|
5402
|
+
rank: 10,
|
|
5403
|
+
uncertainty: 4,
|
|
5404
|
+
evaluatedModel: "MAI-Image-2.5",
|
|
5405
|
+
votes: 61286
|
|
5406
|
+
},
|
|
5407
|
+
seedream5: {
|
|
5408
|
+
elo: 1250,
|
|
5409
|
+
rank: 11,
|
|
5410
|
+
uncertainty: 4,
|
|
5411
|
+
evaluatedModel: "Seedream 5.0 Pro",
|
|
5412
|
+
votes: 105824
|
|
5413
|
+
},
|
|
5414
|
+
"banana2-lite": {
|
|
5415
|
+
elo: 1249,
|
|
5416
|
+
rank: 12,
|
|
5417
|
+
uncertainty: 6,
|
|
5418
|
+
evaluatedModel: "Nano Banana 2 Lite",
|
|
5419
|
+
votes: 17813
|
|
5420
|
+
},
|
|
5421
|
+
banana: {
|
|
5422
|
+
elo: 1246,
|
|
5423
|
+
rank: 13,
|
|
5424
|
+
uncertainty: 3,
|
|
5425
|
+
evaluatedModel: "Nano Banana Pro (2K)",
|
|
5426
|
+
votes: 188288
|
|
5427
|
+
},
|
|
5428
|
+
gpt: {
|
|
5429
|
+
elo: 1236,
|
|
5430
|
+
rank: 14,
|
|
5431
|
+
uncertainty: 3,
|
|
5432
|
+
evaluatedModel: "GPT-Image-1.5 (High Fidelity)",
|
|
5433
|
+
votes: 174012
|
|
5434
|
+
},
|
|
5435
|
+
ideogram4: {
|
|
5436
|
+
elo: 1200,
|
|
5437
|
+
rank: 15,
|
|
5438
|
+
uncertainty: 4,
|
|
5439
|
+
evaluatedModel: "Ideogram 4.0 (Quality)",
|
|
5440
|
+
votes: 72865
|
|
5441
|
+
},
|
|
5442
|
+
"flux2-flex": {
|
|
5443
|
+
elo: 1156,
|
|
5444
|
+
rank: 17,
|
|
5445
|
+
uncertainty: 3,
|
|
5446
|
+
evaluatedModel: "FLUX.2 [flex]",
|
|
5447
|
+
votes: 156876
|
|
5448
|
+
}
|
|
5449
|
+
},
|
|
5450
|
+
"image-edit-single-image": {
|
|
5451
|
+
sunburst: {
|
|
5452
|
+
elo: 1517,
|
|
5453
|
+
rank: 1,
|
|
5454
|
+
uncertainty: 5,
|
|
5455
|
+
evaluatedModel: "GPT-Image-2.5 Sunburst",
|
|
5456
|
+
preliminary: true
|
|
5457
|
+
},
|
|
5458
|
+
flare: {
|
|
5459
|
+
elo: 1482,
|
|
5460
|
+
rank: 2,
|
|
5461
|
+
uncertainty: 5,
|
|
5462
|
+
evaluatedModel: "GPT-Image-2.5 Flare",
|
|
5463
|
+
preliminary: true
|
|
5464
|
+
},
|
|
5465
|
+
gpt2: {
|
|
5466
|
+
elo: 1460,
|
|
5467
|
+
rank: 4,
|
|
5468
|
+
uncertainty: 3,
|
|
5469
|
+
evaluatedModel: "GPT-Image-2 (Medium)"
|
|
5470
|
+
},
|
|
5471
|
+
"grok-image-2": {
|
|
5472
|
+
elo: 1439,
|
|
5473
|
+
rank: 5,
|
|
5474
|
+
uncertainty: 6,
|
|
5475
|
+
evaluatedModel: "Grok Imagine Image 2.0",
|
|
5476
|
+
preliminary: true
|
|
5477
|
+
},
|
|
5478
|
+
banana21: {
|
|
5479
|
+
elo: 1427,
|
|
5480
|
+
rank: 7,
|
|
5481
|
+
uncertainty: 6,
|
|
5482
|
+
evaluatedModel: "Nano Banana 2.1",
|
|
5483
|
+
preliminary: true
|
|
5484
|
+
},
|
|
5485
|
+
"mai-image-2.5": {
|
|
5486
|
+
elo: 1400,
|
|
5487
|
+
rank: 9,
|
|
5488
|
+
uncertainty: 4,
|
|
5489
|
+
evaluatedModel: "MAI-Image-2.5"
|
|
5490
|
+
},
|
|
5491
|
+
"muse-image": {
|
|
5492
|
+
elo: 1400,
|
|
5493
|
+
rank: 10,
|
|
5494
|
+
uncertainty: 4,
|
|
5495
|
+
evaluatedModel: "Muse Image (Meta)"
|
|
5496
|
+
},
|
|
5497
|
+
"grok-image-quality": {
|
|
5498
|
+
elo: 1391,
|
|
5499
|
+
rank: 11,
|
|
5500
|
+
uncertainty: 6,
|
|
5501
|
+
evaluatedModel: "Grok Imagine Image (Quality, 20260519)"
|
|
5502
|
+
},
|
|
5503
|
+
seedream5: {
|
|
5504
|
+
elo: 1390,
|
|
5505
|
+
rank: 12,
|
|
5506
|
+
uncertainty: 3,
|
|
5507
|
+
evaluatedModel: "Seedream 5.0 Pro"
|
|
5508
|
+
},
|
|
5509
|
+
banana: {
|
|
5510
|
+
elo: 1390,
|
|
5511
|
+
rank: 13,
|
|
5512
|
+
uncertainty: 3,
|
|
5513
|
+
evaluatedModel: "Nano Banana Pro (2K)"
|
|
5514
|
+
},
|
|
5515
|
+
gpt: {
|
|
5516
|
+
elo: 1368,
|
|
5517
|
+
rank: 14,
|
|
5518
|
+
uncertainty: 3,
|
|
5519
|
+
evaluatedModel: "GPT-Image-1.5 (High Fidelity)"
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
};
|
|
5523
|
+
const SOURCES = {
|
|
5524
|
+
"text-to-image": "https://arena.ai/leaderboard/image/text-to-image",
|
|
5525
|
+
"image-edit-single-image": "https://arena.ai/leaderboard/image/image-edit/single-image"
|
|
5526
|
+
};
|
|
5527
|
+
/** A published score for this route, or null when no matching score was saved. */
|
|
5528
|
+
function imageModelBenchmark(model, task) {
|
|
5529
|
+
const score = BOARDS[task][model];
|
|
5530
|
+
if (score === void 0) return null;
|
|
5531
|
+
return {
|
|
5532
|
+
...score,
|
|
5533
|
+
source: SOURCES[task],
|
|
5534
|
+
snapshotDate: "2026-10-29",
|
|
5535
|
+
task
|
|
5536
|
+
};
|
|
5537
|
+
}
|
|
5538
|
+
//#endregion
|
|
5169
5539
|
//#region src/tasks.ts
|
|
5170
5540
|
/**
|
|
5171
5541
|
* The Task registry: every job Motif does, with the Models that can do it
|
|
@@ -5192,7 +5562,195 @@ const TIERS = [
|
|
|
5192
5562
|
];
|
|
5193
5563
|
const DEFAULT_TIER = "balanced";
|
|
5194
5564
|
const HAND_RANKED_AT = "2026-09-16";
|
|
5195
|
-
const IMAGE_RANKED_AT = "2026-10-
|
|
5565
|
+
const IMAGE_RANKED_AT = "2026-10-29";
|
|
5566
|
+
function withImageBenchmarks(models, task) {
|
|
5567
|
+
return models.map((entry) => ({
|
|
5568
|
+
...entry,
|
|
5569
|
+
benchmark: imageModelBenchmark(entry.model, task)
|
|
5570
|
+
}));
|
|
5571
|
+
}
|
|
5572
|
+
const GENERATION_MODELS_RANKED = withImageBenchmarks([
|
|
5573
|
+
{
|
|
5574
|
+
model: "sunburst",
|
|
5575
|
+
tier: "quality"
|
|
5576
|
+
},
|
|
5577
|
+
{
|
|
5578
|
+
model: "flare",
|
|
5579
|
+
tier: "quality"
|
|
5580
|
+
},
|
|
5581
|
+
{
|
|
5582
|
+
model: "gpt2",
|
|
5583
|
+
tier: "quality"
|
|
5584
|
+
},
|
|
5585
|
+
{
|
|
5586
|
+
model: "seedream5",
|
|
5587
|
+
tier: "quality"
|
|
5588
|
+
},
|
|
5589
|
+
{
|
|
5590
|
+
model: "gpt",
|
|
5591
|
+
tier: "quality"
|
|
5592
|
+
},
|
|
5593
|
+
{
|
|
5594
|
+
model: "mai-image-2.5-pro",
|
|
5595
|
+
tier: "quality"
|
|
5596
|
+
},
|
|
5597
|
+
{
|
|
5598
|
+
model: "banana21",
|
|
5599
|
+
tier: "balanced"
|
|
5600
|
+
},
|
|
5601
|
+
{
|
|
5602
|
+
model: "grok-image-2",
|
|
5603
|
+
tier: "balanced"
|
|
5604
|
+
},
|
|
5605
|
+
{
|
|
5606
|
+
model: "muse-image",
|
|
5607
|
+
tier: "balanced"
|
|
5608
|
+
},
|
|
5609
|
+
{
|
|
5610
|
+
model: "banana2",
|
|
5611
|
+
tier: "balanced"
|
|
5612
|
+
},
|
|
5613
|
+
{
|
|
5614
|
+
model: "qwen3",
|
|
5615
|
+
tier: "balanced"
|
|
5616
|
+
},
|
|
5617
|
+
{
|
|
5618
|
+
model: "mai-image-2.5",
|
|
5619
|
+
tier: "balanced"
|
|
5620
|
+
},
|
|
5621
|
+
{
|
|
5622
|
+
model: "banana",
|
|
5623
|
+
tier: "balanced"
|
|
5624
|
+
},
|
|
5625
|
+
{
|
|
5626
|
+
model: "ideogram4",
|
|
5627
|
+
tier: "balanced"
|
|
5628
|
+
},
|
|
5629
|
+
{
|
|
5630
|
+
model: "flux2-flex",
|
|
5631
|
+
tier: "balanced"
|
|
5632
|
+
},
|
|
5633
|
+
{
|
|
5634
|
+
model: "recraft41",
|
|
5635
|
+
tier: "balanced"
|
|
5636
|
+
},
|
|
5637
|
+
{
|
|
5638
|
+
model: "flux2-pro",
|
|
5639
|
+
tier: "balanced"
|
|
5640
|
+
},
|
|
5641
|
+
{
|
|
5642
|
+
model: "krea3",
|
|
5643
|
+
tier: "balanced"
|
|
5644
|
+
},
|
|
5645
|
+
{
|
|
5646
|
+
model: "grok-image-quality",
|
|
5647
|
+
tier: "balanced"
|
|
5648
|
+
},
|
|
5649
|
+
{
|
|
5650
|
+
model: "ideogram3-transparent",
|
|
5651
|
+
requires: ["transparency"],
|
|
5652
|
+
supports: ["transparency"],
|
|
5653
|
+
tier: "balanced"
|
|
5654
|
+
},
|
|
5655
|
+
{
|
|
5656
|
+
model: "banana2-lite",
|
|
5657
|
+
tier: "fast"
|
|
5658
|
+
},
|
|
5659
|
+
{
|
|
5660
|
+
model: "grok-image",
|
|
5661
|
+
tier: "fast"
|
|
5662
|
+
},
|
|
5663
|
+
{
|
|
5664
|
+
model: "seedream5-lite",
|
|
5665
|
+
tier: "fast"
|
|
5666
|
+
},
|
|
5667
|
+
{
|
|
5668
|
+
model: "flux2-turbo",
|
|
5669
|
+
tier: "fast"
|
|
5670
|
+
}
|
|
5671
|
+
], "text-to-image");
|
|
5672
|
+
const EDITING_MODELS_RANKED = withImageBenchmarks([
|
|
5673
|
+
{
|
|
5674
|
+
model: "sunburst",
|
|
5675
|
+
tier: "quality"
|
|
5676
|
+
},
|
|
5677
|
+
{
|
|
5678
|
+
model: "flare",
|
|
5679
|
+
tier: "quality"
|
|
5680
|
+
},
|
|
5681
|
+
{
|
|
5682
|
+
model: "gpt2",
|
|
5683
|
+
tier: "quality"
|
|
5684
|
+
},
|
|
5685
|
+
{
|
|
5686
|
+
model: "seedream5",
|
|
5687
|
+
tier: "quality"
|
|
5688
|
+
},
|
|
5689
|
+
{
|
|
5690
|
+
model: "gpt",
|
|
5691
|
+
tier: "quality"
|
|
5692
|
+
},
|
|
5693
|
+
{
|
|
5694
|
+
model: "mai-image-2.5-pro",
|
|
5695
|
+
tier: "quality"
|
|
5696
|
+
},
|
|
5697
|
+
{
|
|
5698
|
+
model: "grok-image-2",
|
|
5699
|
+
tier: "balanced"
|
|
5700
|
+
},
|
|
5701
|
+
{
|
|
5702
|
+
model: "banana21",
|
|
5703
|
+
tier: "balanced"
|
|
5704
|
+
},
|
|
5705
|
+
{
|
|
5706
|
+
model: "mai-image-2.5",
|
|
5707
|
+
tier: "balanced"
|
|
5708
|
+
},
|
|
5709
|
+
{
|
|
5710
|
+
model: "muse-image",
|
|
5711
|
+
tier: "balanced"
|
|
5712
|
+
},
|
|
5713
|
+
{
|
|
5714
|
+
model: "grok-image-quality",
|
|
5715
|
+
tier: "balanced"
|
|
5716
|
+
},
|
|
5717
|
+
{
|
|
5718
|
+
model: "banana",
|
|
5719
|
+
tier: "balanced"
|
|
5720
|
+
},
|
|
5721
|
+
{
|
|
5722
|
+
model: "banana2",
|
|
5723
|
+
tier: "balanced"
|
|
5724
|
+
},
|
|
5725
|
+
{
|
|
5726
|
+
model: "krea3",
|
|
5727
|
+
tier: "balanced"
|
|
5728
|
+
},
|
|
5729
|
+
{
|
|
5730
|
+
model: "qwen3",
|
|
5731
|
+
tier: "balanced"
|
|
5732
|
+
},
|
|
5733
|
+
{
|
|
5734
|
+
model: "flux2-pro",
|
|
5735
|
+
tier: "balanced"
|
|
5736
|
+
},
|
|
5737
|
+
{
|
|
5738
|
+
model: "flux2-flex",
|
|
5739
|
+
tier: "balanced"
|
|
5740
|
+
},
|
|
5741
|
+
{
|
|
5742
|
+
model: "grok-image",
|
|
5743
|
+
tier: "fast"
|
|
5744
|
+
},
|
|
5745
|
+
{
|
|
5746
|
+
model: "banana2-lite",
|
|
5747
|
+
tier: "fast"
|
|
5748
|
+
},
|
|
5749
|
+
{
|
|
5750
|
+
model: "seedream5-lite",
|
|
5751
|
+
tier: "fast"
|
|
5752
|
+
}
|
|
5753
|
+
], "image-edit-single-image");
|
|
5196
5754
|
/** Every Task with a Model. A Task is absent here until it has at least one Model. */
|
|
5197
5755
|
const TASKS = {
|
|
5198
5756
|
animate: {
|
|
@@ -5363,95 +5921,8 @@ const TASKS = {
|
|
|
5363
5921
|
summary: "Remove an object, person, text or clutter from an image and fill the gap."
|
|
5364
5922
|
},
|
|
5365
5923
|
generate: {
|
|
5366
|
-
basis: "
|
|
5367
|
-
models:
|
|
5368
|
-
{
|
|
5369
|
-
model: "sunburst",
|
|
5370
|
-
tier: "quality"
|
|
5371
|
-
},
|
|
5372
|
-
{
|
|
5373
|
-
model: "flare",
|
|
5374
|
-
tier: "quality"
|
|
5375
|
-
},
|
|
5376
|
-
{
|
|
5377
|
-
model: "gpt2",
|
|
5378
|
-
tier: "quality"
|
|
5379
|
-
},
|
|
5380
|
-
{
|
|
5381
|
-
model: "gpt",
|
|
5382
|
-
tier: "quality"
|
|
5383
|
-
},
|
|
5384
|
-
{
|
|
5385
|
-
model: "mai-image-2.5-pro",
|
|
5386
|
-
tier: "quality"
|
|
5387
|
-
},
|
|
5388
|
-
{
|
|
5389
|
-
model: "seedream5",
|
|
5390
|
-
tier: "quality"
|
|
5391
|
-
},
|
|
5392
|
-
{
|
|
5393
|
-
model: "banana21",
|
|
5394
|
-
tier: "balanced"
|
|
5395
|
-
},
|
|
5396
|
-
{
|
|
5397
|
-
model: "grok-image-2",
|
|
5398
|
-
tier: "balanced"
|
|
5399
|
-
},
|
|
5400
|
-
{
|
|
5401
|
-
model: "banana2",
|
|
5402
|
-
tier: "balanced"
|
|
5403
|
-
},
|
|
5404
|
-
{
|
|
5405
|
-
model: "mai-image-2.5",
|
|
5406
|
-
tier: "balanced"
|
|
5407
|
-
},
|
|
5408
|
-
{
|
|
5409
|
-
model: "banana",
|
|
5410
|
-
tier: "balanced"
|
|
5411
|
-
},
|
|
5412
|
-
{
|
|
5413
|
-
model: "qwen3",
|
|
5414
|
-
tier: "balanced"
|
|
5415
|
-
},
|
|
5416
|
-
{
|
|
5417
|
-
model: "flux2-flex",
|
|
5418
|
-
tier: "balanced"
|
|
5419
|
-
},
|
|
5420
|
-
{
|
|
5421
|
-
model: "flux2-pro",
|
|
5422
|
-
tier: "balanced"
|
|
5423
|
-
},
|
|
5424
|
-
{
|
|
5425
|
-
model: "ideogram4",
|
|
5426
|
-
tier: "balanced"
|
|
5427
|
-
},
|
|
5428
|
-
{
|
|
5429
|
-
model: "recraft41",
|
|
5430
|
-
tier: "balanced"
|
|
5431
|
-
},
|
|
5432
|
-
{
|
|
5433
|
-
model: "ideogram3-transparent",
|
|
5434
|
-
requires: ["transparency"],
|
|
5435
|
-
supports: ["transparency"],
|
|
5436
|
-
tier: "balanced"
|
|
5437
|
-
},
|
|
5438
|
-
{
|
|
5439
|
-
model: "banana2-lite",
|
|
5440
|
-
tier: "fast"
|
|
5441
|
-
},
|
|
5442
|
-
{
|
|
5443
|
-
model: "grok-image",
|
|
5444
|
-
tier: "fast"
|
|
5445
|
-
},
|
|
5446
|
-
{
|
|
5447
|
-
model: "seedream5-lite",
|
|
5448
|
-
tier: "fast"
|
|
5449
|
-
},
|
|
5450
|
-
{
|
|
5451
|
-
model: "flux2-turbo",
|
|
5452
|
-
tier: "fast"
|
|
5453
|
-
}
|
|
5454
|
-
],
|
|
5924
|
+
basis: "Order follows Arena text-to-image (https://arena.ai/leaderboard/image/text-to-image), read 2026-10-29 (MOT-72). Sunburst and Flare lead quality at the benchmarked max setting. Balanced starts with Nano Banana 2.1 on preliminary Arena evidence, then Grok Image 2 and Muse Image. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Fast keeps Nano Banana 2 Lite, budget Grok and Seedream 5 Lite, and FLUX.2 Turbo. Design, transparency and tuned Look routes are capability exceptions. Generation with References uses the separate editing ranking. Older routes are explicit/pin/Look-only.",
|
|
5925
|
+
models: GENERATION_MODELS_RANKED,
|
|
5455
5926
|
notFor: "Variations of an image you already have (vary), or a consistent set of images (series run).",
|
|
5456
5927
|
rankedAt: IMAGE_RANKED_AT,
|
|
5457
5928
|
rankedFrom: "hand",
|
|
@@ -5938,73 +6409,8 @@ const TASKS = {
|
|
|
5938
6409
|
summary: "Trace a raster image to a clean SVG."
|
|
5939
6410
|
},
|
|
5940
6411
|
vary: {
|
|
5941
|
-
basis: "
|
|
5942
|
-
models:
|
|
5943
|
-
{
|
|
5944
|
-
model: "sunburst",
|
|
5945
|
-
tier: "quality"
|
|
5946
|
-
},
|
|
5947
|
-
{
|
|
5948
|
-
model: "flare",
|
|
5949
|
-
tier: "quality"
|
|
5950
|
-
},
|
|
5951
|
-
{
|
|
5952
|
-
model: "gpt2",
|
|
5953
|
-
tier: "quality"
|
|
5954
|
-
},
|
|
5955
|
-
{
|
|
5956
|
-
model: "mai-image-2.5-pro",
|
|
5957
|
-
tier: "quality"
|
|
5958
|
-
},
|
|
5959
|
-
{
|
|
5960
|
-
model: "seedream5",
|
|
5961
|
-
tier: "quality"
|
|
5962
|
-
},
|
|
5963
|
-
{
|
|
5964
|
-
model: "gpt",
|
|
5965
|
-
tier: "quality"
|
|
5966
|
-
},
|
|
5967
|
-
{
|
|
5968
|
-
model: "banana21",
|
|
5969
|
-
tier: "balanced"
|
|
5970
|
-
},
|
|
5971
|
-
{
|
|
5972
|
-
model: "mai-image-2.5",
|
|
5973
|
-
tier: "balanced"
|
|
5974
|
-
},
|
|
5975
|
-
{
|
|
5976
|
-
model: "grok-image-2",
|
|
5977
|
-
tier: "balanced"
|
|
5978
|
-
},
|
|
5979
|
-
{
|
|
5980
|
-
model: "banana2",
|
|
5981
|
-
tier: "balanced"
|
|
5982
|
-
},
|
|
5983
|
-
{
|
|
5984
|
-
model: "banana",
|
|
5985
|
-
tier: "balanced"
|
|
5986
|
-
},
|
|
5987
|
-
{
|
|
5988
|
-
model: "flux2-pro",
|
|
5989
|
-
tier: "balanced"
|
|
5990
|
-
},
|
|
5991
|
-
{
|
|
5992
|
-
model: "flux2-flex",
|
|
5993
|
-
tier: "balanced"
|
|
5994
|
-
},
|
|
5995
|
-
{
|
|
5996
|
-
model: "grok-image",
|
|
5997
|
-
tier: "fast"
|
|
5998
|
-
},
|
|
5999
|
-
{
|
|
6000
|
-
model: "seedream5-lite",
|
|
6001
|
-
tier: "fast"
|
|
6002
|
-
},
|
|
6003
|
-
{
|
|
6004
|
-
model: "banana2-lite",
|
|
6005
|
-
tier: "fast"
|
|
6006
|
-
}
|
|
6007
|
-
],
|
|
6412
|
+
basis: "Order follows Arena image edit, single image (https://arena.ai/leaderboard/image/image-edit/single-image), read 2026-10-29 (MOT-72). Editing order differs from generation: Grok Image 2 leads balanced, ahead of Nano Banana 2.1, and Seedream 5 Pro outranks GPT Image 1.5. Arena scores Krea 3 only at its Ultra setting, which costs $0.27 to $0.35 an edit and ignores the requested size, so krea3, which runs fal's default setting, is ranked as unscored. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Sunburst and Flare use max quality. vary reuses a previously chosen Model when explicitly supplied, including retained legacy routes.",
|
|
6413
|
+
models: EDITING_MODELS_RANKED,
|
|
6008
6414
|
notFor: "A specific change to an image described in words (generate with a reference), or a set of different scenes in one style (series run).",
|
|
6009
6415
|
rankedAt: IMAGE_RANKED_AT,
|
|
6010
6416
|
rankedFrom: "hand",
|
|
@@ -8203,6 +8609,7 @@ function generationProfile(config) {
|
|
|
8203
8609
|
else if (config.transparencyRoute !== void 0) keyed.set("transparency", config.transparencyRoute.apiKeyEnv);
|
|
8204
8610
|
if (config.supportsEdit) capabilities.add("references");
|
|
8205
8611
|
if (config.supportsMaskImage === true) capabilities.add("mask");
|
|
8612
|
+
if (config.supportsMarkupImage === true) capabilities.add("markup");
|
|
8206
8613
|
if ((config.sizeMode ?? "aspect_ratio") !== "none") capabilities.add("aspect");
|
|
8207
8614
|
if (config.supportsResolution) capabilities.add("resolution");
|
|
8208
8615
|
if (config.supportsNumImages) capabilities.add("count");
|
|
@@ -8215,6 +8622,10 @@ function generationProfile(config) {
|
|
|
8215
8622
|
keyedCapabilities: keyed,
|
|
8216
8623
|
keys: [FAL_KEY],
|
|
8217
8624
|
maxReferences: config.supportsEdit ? config.maxReferenceImages ?? 1 : 0,
|
|
8625
|
+
outputFormats: {
|
|
8626
|
+
edit: outputFormatsOf(config, true),
|
|
8627
|
+
text: outputFormatsOf(config, false)
|
|
8628
|
+
},
|
|
8218
8629
|
resolutions: config.supportedResolutions
|
|
8219
8630
|
};
|
|
8220
8631
|
}
|
|
@@ -8265,12 +8676,13 @@ function requirements(request) {
|
|
|
8265
8676
|
if (request.transparent === true) needed.push("transparency");
|
|
8266
8677
|
if ((request.references ?? 0) > 0) needed.push("references");
|
|
8267
8678
|
if (request.mask === true) needed.push("mask");
|
|
8679
|
+
if (request.markup === true) needed.push("markup");
|
|
8268
8680
|
if (request.aspect !== void 0) needed.push("aspect");
|
|
8269
8681
|
if (request.resolution !== void 0) needed.push("resolution");
|
|
8270
8682
|
if ((request.count ?? 1) > 1) needed.push("count");
|
|
8271
8683
|
if (request.seed === true) needed.push("seed");
|
|
8272
8684
|
if (request.negativePrompt === true) needed.push("negativePrompt");
|
|
8273
|
-
if (request.outputFormat
|
|
8685
|
+
if (request.outputFormat !== void 0) needed.push("outputFormat");
|
|
8274
8686
|
if (request.rig === true) needed.push("rig");
|
|
8275
8687
|
return needed;
|
|
8276
8688
|
}
|
|
@@ -8279,6 +8691,7 @@ function withinLimits(profile, capability, request) {
|
|
|
8279
8691
|
if (capability === "references") return (request.references ?? 0) <= profile.maxReferences;
|
|
8280
8692
|
if (capability === "aspect") return accepts(profile.aspects, request.aspect);
|
|
8281
8693
|
if (capability === "resolution") return accepts(profile.resolutions, request.resolution);
|
|
8694
|
+
if (capability === "outputFormat") return accepts((request.references ?? 0) > 0 || request.source !== void 0 ? profile.outputFormats?.edit : profile.outputFormats?.text, request.outputFormat);
|
|
8282
8695
|
return true;
|
|
8283
8696
|
}
|
|
8284
8697
|
function accepts(allowed, value) {
|
|
@@ -8333,7 +8746,11 @@ const TIER_ORDER = {
|
|
|
8333
8746
|
"fast"
|
|
8334
8747
|
]
|
|
8335
8748
|
};
|
|
8336
|
-
function describeBlock(blockedBy, missingKey, missingInput) {
|
|
8749
|
+
function describeBlock(blockedBy, request, missingKey, missingInput) {
|
|
8750
|
+
if (blockedBy === "outputFormat" && request.outputFormat !== void 0) {
|
|
8751
|
+
const editing = (request.references ?? 0) > 0 || request.source !== void 0;
|
|
8752
|
+
return `cannot return ${request.outputFormat}${editing ? " from an edit" : ""}`;
|
|
8753
|
+
}
|
|
8337
8754
|
if (missingInput === true) return `needs a ${blockedBy} input`;
|
|
8338
8755
|
if (blockedBy === "key") return `needs ${missingKey ?? "a provider key"}`;
|
|
8339
8756
|
if (blockedBy === "unknown-model") return "is not a Model for this Task";
|
|
@@ -8402,7 +8819,7 @@ function resolveFixed(task, fixed, request, needed, keys) {
|
|
|
8402
8819
|
if (result.ok) return resolved(task, model, tier, chosenBy);
|
|
8403
8820
|
const unblockers = chosenBy === "model" ? [] : ["model"];
|
|
8404
8821
|
unblockers.push(unblockerFor(result));
|
|
8405
|
-
return unresolved(task, result.blockedBy, unblockers, `${model} (${chosenBy}) ${describeBlock(result.blockedBy, result.missingKey, result.missingInput)}.`, result.missingKey);
|
|
8822
|
+
return unresolved(task, result.blockedBy, unblockers, `${model} (${chosenBy}) ${describeBlock(result.blockedBy, request, result.missingKey, result.missingInput)}.`, result.missingKey);
|
|
8406
8823
|
}
|
|
8407
8824
|
/** Old pins and tuned Looks remain usable without recommending predecessors. */
|
|
8408
8825
|
function legacyEntry(task, model, mode, tier) {
|
|
@@ -8577,6 +8994,8 @@ const FAL_IMAGE_PRICE_USD = {
|
|
|
8577
8994
|
"ideogram/v4": MODELS.ideogram4?.pricePerImageUsd ?? .03,
|
|
8578
8995
|
"xai/grok-imagine-image": MODELS["grok-image"]?.pricePerImageUsd ?? .02,
|
|
8579
8996
|
"xai/grok-imagine-image/v2.0/text-to-image": MODELS["grok-image-2"]?.pricePerImageUsd ?? .06,
|
|
8997
|
+
"xai/grok-imagine-image/quality/text-to-image": MODELS["grok-image-quality"]?.pricePerImageUsd ?? .05,
|
|
8998
|
+
"meta/muse-image/text-to-image": MODELS["muse-image"]?.pricePerImageUsd ?? .01,
|
|
8580
8999
|
"microsoft/mai-image-2.5-pro": MODELS["mai-image-2.5-pro"]?.pricePerImageUsd ?? .17,
|
|
8581
9000
|
"fal-ai/ideogram/v3/generate-transparent": MODELS["ideogram3-transparent"]?.pricePerImageUsd ?? .06,
|
|
8582
9001
|
"alibaba/qwen-image-3/text-to-image": MODELS.qwen3?.pricePerImageUsd ?? .04,
|
|
@@ -8634,10 +9053,10 @@ const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
|
|
|
8634
9053
|
* Sanity anchor: the Phase 0 benchmark measured gpt-image direct at $0.042
|
|
8635
9054
|
* (vs $0.133 via fal — full data and methodology on Linear MOT-23).
|
|
8636
9055
|
*
|
|
8637
|
-
* GPT Image 2.
|
|
9056
|
+
* GPT Image 2.6 is token-priced, with no published per-image estimate. Leave
|
|
8638
9057
|
* these models absent so cost remains unknown unless supplied by the provider.
|
|
8639
|
-
* https://developers.openai.com/api/docs/models/gpt-image-2.
|
|
8640
|
-
* https://developers.openai.com/api/docs/models/gpt-image-2.
|
|
9058
|
+
* https://developers.openai.com/api/docs/models/gpt-image-2.6-flare
|
|
9059
|
+
* https://developers.openai.com/api/docs/models/gpt-image-2.6-sunburst
|
|
8641
9060
|
*/
|
|
8642
9061
|
const OPENAI_IMAGE_PRICE_USD = { "gpt-image-1": .042 };
|
|
8643
9062
|
/**
|
|
@@ -9889,11 +10308,12 @@ function taskRequest(task, input) {
|
|
|
9889
10308
|
aspect: input.aspect,
|
|
9890
10309
|
count: input.count,
|
|
9891
10310
|
look: task === "generate" || task === "vary" ? input.look : void 0,
|
|
10311
|
+
markup: input.markup !== void 0,
|
|
9892
10312
|
mask: input.mask !== void 0,
|
|
9893
10313
|
mode: input.mode,
|
|
9894
10314
|
model: input.model,
|
|
9895
10315
|
negativePrompt: input.negativePrompt !== void 0,
|
|
9896
|
-
outputFormat: input.outputFormat
|
|
10316
|
+
outputFormat: input.outputFormat,
|
|
9897
10317
|
references,
|
|
9898
10318
|
resolution: (task === "generate" || task === "vary") && input.resolution === "2K" ? void 0 : input.resolution,
|
|
9899
10319
|
rig: input.rig,
|
|
@@ -10004,6 +10424,7 @@ const GENERATION_FIELDS = /* @__PURE__ */ new Set([
|
|
|
10004
10424
|
"aspect",
|
|
10005
10425
|
"count",
|
|
10006
10426
|
"look",
|
|
10427
|
+
"markup",
|
|
10007
10428
|
"mask",
|
|
10008
10429
|
"mood",
|
|
10009
10430
|
"negativePrompt",
|
|
@@ -10014,11 +10435,23 @@ const GENERATION_FIELDS = /* @__PURE__ */ new Set([
|
|
|
10014
10435
|
"seed",
|
|
10015
10436
|
"transparent"
|
|
10016
10437
|
]);
|
|
10438
|
+
const IMAGE_QUALITIES = [
|
|
10439
|
+
"auto",
|
|
10440
|
+
"low",
|
|
10441
|
+
"medium",
|
|
10442
|
+
"high",
|
|
10443
|
+
"xhigh",
|
|
10444
|
+
"max"
|
|
10445
|
+
];
|
|
10446
|
+
function isImageQuality(value) {
|
|
10447
|
+
return IMAGE_QUALITIES.some((level) => level === value);
|
|
10448
|
+
}
|
|
10017
10449
|
/** The TaskInput field behind each option `buildGenerateBody` can refuse. */
|
|
10018
10450
|
const FIELD_FOR_OPTION = {
|
|
10019
10451
|
aspect: "aspect",
|
|
10020
10452
|
background: "transparent",
|
|
10021
10453
|
"image editing": "references",
|
|
10454
|
+
markupImageUrl: "markup",
|
|
10022
10455
|
maskImageUrl: "mask",
|
|
10023
10456
|
negativePrompt: "negativePrompt",
|
|
10024
10457
|
numImages: "count",
|
|
@@ -10058,23 +10491,35 @@ function generationPlan(task, model, config, input) {
|
|
|
10058
10491
|
task
|
|
10059
10492
|
}));
|
|
10060
10493
|
const editImageUrls = [...task === "vary" && input.image !== void 0 ? [input.image] : [], ...input.references ?? []];
|
|
10494
|
+
if (input.markup !== void 0 && editImageUrls.length === 0) return (0, neverthrow.err)(invalidOption("Markup applies to the image being edited; pass references.", {
|
|
10495
|
+
field: "markup",
|
|
10496
|
+
requires: "references"
|
|
10497
|
+
}));
|
|
10061
10498
|
if (input.mask !== void 0 && editImageUrls.length === 0) return (0, neverthrow.err)(invalidOption("A mask applies to a reference image; pass references.", {
|
|
10062
10499
|
field: "mask",
|
|
10063
10500
|
requires: "references"
|
|
10064
10501
|
}));
|
|
10065
10502
|
if (input.transparent === true && config.supportsBackground !== true && config.transparencyRoute !== void 0) return openAiPlan(task, model, config.transparencyRoute, input, editImageUrls);
|
|
10066
|
-
if (input.outputFormat !== void 0 && config.
|
|
10503
|
+
if (input.outputFormat !== void 0 && !outputFormatsOf(config, editImageUrls.length > 0).includes(input.outputFormat)) return (0, neverthrow.err)(cannotCarry("outputFormat", model, task));
|
|
10504
|
+
const { quality, ...params } = input.params ?? {};
|
|
10505
|
+
const liftQuality = config.supportsQuality === true && quality !== void 0;
|
|
10506
|
+
if (liftQuality && !isImageQuality(quality)) return (0, neverthrow.err)(invalidOption(`params.quality must be one of ${IMAGE_QUALITIES.join(", ")}.`, {
|
|
10507
|
+
field: "params",
|
|
10508
|
+
key: "quality"
|
|
10509
|
+
}));
|
|
10067
10510
|
const options = {
|
|
10068
10511
|
aspect: input.aspect,
|
|
10069
10512
|
background: input.transparent === true ? "transparent" : void 0,
|
|
10070
10513
|
creative: creativeOf(input),
|
|
10071
10514
|
editImageUrls: editImageUrls.length > 0 ? editImageUrls : void 0,
|
|
10515
|
+
markupImageUrl: input.markup,
|
|
10072
10516
|
maskImageUrl: input.mask,
|
|
10073
10517
|
model,
|
|
10074
10518
|
negativePrompt: input.negativePrompt,
|
|
10075
10519
|
numImages: input.count,
|
|
10076
10520
|
outputFormat: input.outputFormat,
|
|
10077
10521
|
prompt: task === "vary" && (input.prompt ?? "") === "" ? VARY_PROMPT : input.prompt ?? "",
|
|
10522
|
+
quality: liftQuality && isImageQuality(quality) ? quality : void 0,
|
|
10078
10523
|
resolution: input.resolution,
|
|
10079
10524
|
seed: input.seed
|
|
10080
10525
|
};
|
|
@@ -10084,7 +10529,7 @@ function generationPlan(task, model, config, input) {
|
|
|
10084
10529
|
} catch (error) {
|
|
10085
10530
|
return (0, neverthrow.err)(generationError(error, model, task));
|
|
10086
10531
|
}
|
|
10087
|
-
const merged = mergeParams(built.body, input.params, ownedGenerationKeys(input));
|
|
10532
|
+
const merged = mergeParams(built.body, liftQuality ? params : input.params, ownedGenerationKeys(input));
|
|
10088
10533
|
if (merged.isErr()) return (0, neverthrow.err)(merged.error);
|
|
10089
10534
|
const badSize = checkParamsImageSize(config, model, task, input.params);
|
|
10090
10535
|
if (badSize !== void 0) return (0, neverthrow.err)(badSize);
|
|
@@ -10702,7 +11147,7 @@ function createMotif(config = {}) {
|
|
|
10702
11147
|
headers
|
|
10703
11148
|
};
|
|
10704
11149
|
const executor = falRequestExecutor(client.value);
|
|
10705
|
-
return (chosen.queued ? await runRequestQueued(executor, prepared, input.onProgress, input.onSubmitted) : await runRequest(executor, prepared)).map((value) => falOutput(chosen, value));
|
|
11150
|
+
return (chosen.queued || input.onSubmitted !== void 0 ? await runRequestQueued(executor, prepared, input.onProgress, input.onSubmitted) : await runRequest(executor, prepared)).map((value) => falOutput(chosen, value));
|
|
10706
11151
|
}
|
|
10707
11152
|
async function resume(task, input, requestId) {
|
|
10708
11153
|
const planned = plan(task, input);
|