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