@shop-os/modelkit 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -292,7 +292,10 @@ var IMAGE_MODELS = [
292
292
  constraints: {
293
293
  aspectRatios: ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "21:9", "4:5", "5:4"],
294
294
  sizes: ["1K", "2K"],
295
- references: { images: 10 }
295
+ references: { images: 10 },
296
+ // Ark's Images API has no output-format, quality or count field; only
297
+ // the seed is forwarded, so anything else must warn, not vanish.
298
+ supports: ["seed"]
296
299
  },
297
300
  defaults: { size: "1K", aspectRatio: "1:1" },
298
301
  usage: { ...IMAGE_USAGE },
@@ -307,21 +310,12 @@ var IMAGE_MODELS = [
307
310
  providerModelId: "seedream-4-5-251128",
308
311
  lifecycle: "active",
309
312
  constraints: {
310
- aspectRatios: [
311
- "1:1",
312
- "2:3",
313
- "3:2",
314
- "4:3",
315
- "3:4",
316
- "4:5",
317
- "5:4",
318
- "9:16",
319
- "16:9",
320
- "21:9",
321
- "97:30"
322
- ],
313
+ // 97:30 has no Ark size spelling; the A+ flow pads inputs to 97:30 and
314
+ // omits the ratio instead. Advertise only sizes the adapter can send.
315
+ aspectRatios: ["1:1", "2:3", "3:2", "4:3", "3:4", "4:5", "5:4", "9:16", "16:9", "21:9"],
323
316
  sizes: ["1K", "2K"],
324
- references: { images: 10 }
317
+ references: { images: 10 },
318
+ supports: ["seed"]
325
319
  },
326
320
  defaults: { size: "1K", aspectRatio: "1:1" },
327
321
  usage: { ...IMAGE_USAGE },
@@ -345,6 +339,9 @@ var IMAGE_MODELS = [
345
339
  pricingKind: "image_size"
346
340
  },
347
341
  {
342
+ // Prodia's gemini-3-pro jobs take aspect_ratio + resolution (1K/2K/4K), not
343
+ // width/height; 97:30 is outside its aspect_ratio enum.
344
+ // https://docs.prodia.com/models/gemini-3/
348
345
  id: "prodia-nano-banana-pro",
349
346
  aliases: [],
350
347
  label: "Nano Banana Pro via Prodia",
@@ -353,19 +350,7 @@ var IMAGE_MODELS = [
353
350
  providerModelId: "inference.gemini-3-pro.img2img.v1",
354
351
  lifecycle: "active",
355
352
  constraints: {
356
- aspectRatios: [
357
- "1:1",
358
- "2:3",
359
- "3:2",
360
- "4:3",
361
- "3:4",
362
- "4:5",
363
- "5:4",
364
- "9:16",
365
- "16:9",
366
- "21:9",
367
- "97:30"
368
- ],
353
+ aspectRatios: ["1:1", "2:3", "3:2", "4:3", "3:4", "4:5", "5:4", "9:16", "16:9", "21:9"],
369
354
  sizes: ["1K", "2K", "4K"],
370
355
  requires: ["references"]
371
356
  },
@@ -374,6 +359,8 @@ var IMAGE_MODELS = [
374
359
  pricingKind: "image_size"
375
360
  },
376
361
  {
362
+ // Nano Banana (img2img.v2) is flat-rate with aspect_ratio only; 3:2, 4:5,
363
+ // 5:4 and 97:30 are not in its enum. https://docs.prodia.com/models/nano-banana/
377
364
  id: "prodia-nano-banana",
378
365
  aliases: [],
379
366
  label: "Nano Banana via Prodia",
@@ -382,19 +369,7 @@ var IMAGE_MODELS = [
382
369
  providerModelId: "inference.nano-banana.img2img.v2",
383
370
  lifecycle: "active",
384
371
  constraints: {
385
- aspectRatios: [
386
- "1:1",
387
- "2:3",
388
- "3:2",
389
- "4:3",
390
- "3:4",
391
- "4:5",
392
- "5:4",
393
- "9:16",
394
- "16:9",
395
- "21:9",
396
- "97:30"
397
- ],
372
+ aspectRatios: ["1:1", "2:3", "3:4", "4:3", "9:16", "16:9", "21:9"],
398
373
  sizes: ["1K"],
399
374
  requires: ["references"]
400
375
  },
@@ -403,6 +378,9 @@ var IMAGE_MODELS = [
403
378
  pricingKind: "image_size"
404
379
  },
405
380
  {
381
+ // WAN 2.7 takes `images` (up to 9) and a `size` enum whose exact values
382
+ // cover 1:1/16:9/9:16/4:3/3:4 only; other ratios have no spelling.
383
+ // https://replicate.com/wan-video/wan-2.7-image-pro (input schema)
406
384
  id: "wan-2.7-image-pro",
407
385
  aliases: [],
408
386
  label: "WAN 2.7 Image Pro",
@@ -411,19 +389,7 @@ var IMAGE_MODELS = [
411
389
  providerModelId: "wan-video/wan-2.7-image-pro",
412
390
  lifecycle: "active",
413
391
  constraints: {
414
- aspectRatios: [
415
- "1:1",
416
- "2:3",
417
- "3:2",
418
- "4:3",
419
- "3:4",
420
- "4:5",
421
- "5:4",
422
- "9:16",
423
- "16:9",
424
- "21:9",
425
- "97:30"
426
- ],
392
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4"],
427
393
  sizes: ["1K", "2K", "4K"],
428
394
  references: { images: 9 }
429
395
  },
@@ -432,6 +398,9 @@ var IMAGE_MODELS = [
432
398
  pricingKind: "image_size"
433
399
  },
434
400
  {
401
+ // Qwen Image 2 Pro has aspect_ratio but no size input; resolution follows
402
+ // the reference image (match_input_image) or the provider default.
403
+ // https://replicate.com/qwen/qwen-image-2-pro (input schema)
435
404
  id: "qwen-image-2-pro",
436
405
  aliases: [],
437
406
  label: "Qwen Image 2 Pro",
@@ -440,28 +409,19 @@ var IMAGE_MODELS = [
440
409
  providerModelId: "qwen/qwen-image-2-pro",
441
410
  lifecycle: "active",
442
411
  constraints: {
443
- aspectRatios: [
444
- "1:1",
445
- "2:3",
446
- "3:2",
447
- "4:3",
448
- "3:4",
449
- "4:5",
450
- "5:4",
451
- "9:16",
452
- "16:9",
453
- "21:9",
454
- "97:30"
455
- ],
456
- sizes: ["1K", "2K", "4K"],
412
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"],
413
+ sizes: [],
457
414
  references: { images: 1 },
458
415
  requires: ["references"]
459
416
  },
460
- defaults: { size: "1K", aspectRatio: "1:1" },
417
+ defaults: { aspectRatio: "1:1" },
461
418
  usage: { unit: "compute_seconds", modality: "IMAGE" },
462
419
  pricingKind: "image_size"
463
420
  },
464
421
  {
422
+ // Qwen Image Edit Plus takes aspect_ratio (or match_input_image) and
423
+ // output_format, but no size; no default ratio so edits keep following
424
+ // the input image. https://replicate.com/qwen-edit-apps/qwen-image-edit-plus-lora-skin
465
425
  id: "qwen-image-edit-plus-lora-skin",
466
426
  aliases: [],
467
427
  label: "Qwen Image Edit Plus",
@@ -470,25 +430,13 @@ var IMAGE_MODELS = [
470
430
  providerModelId: "qwen-edit-apps/qwen-image-edit-plus-lora-skin",
471
431
  lifecycle: "active",
472
432
  constraints: {
473
- aspectRatios: [
474
- "1:1",
475
- "2:3",
476
- "3:2",
477
- "4:3",
478
- "3:4",
479
- "4:5",
480
- "5:4",
481
- "9:16",
482
- "16:9",
483
- "21:9",
484
- "97:30"
485
- ],
486
- sizes: ["1K", "2K", "4K"],
433
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4"],
434
+ sizes: [],
487
435
  references: { images: 1 },
488
436
  requires: ["references"],
489
437
  supports: ["outputFormat"]
490
438
  },
491
- defaults: { size: "1K", aspectRatio: "1:1", outputFormat: "png" },
439
+ defaults: { outputFormat: "png" },
492
440
  usage: { unit: "compute_seconds", modality: "IMAGE" },
493
441
  pricingKind: "image_size"
494
442
  }
@@ -1898,7 +1846,11 @@ var aiSdkTextAdapter = {
1898
1846
  function requiredKeys(model, config) {
1899
1847
  if (model.adapter === "fal" && config.routing?.fal === "cloudflare-passthrough")
1900
1848
  return ["cloudflare"];
1901
- if (model.adapter === "ai-sdk-text") return [textKeyName(model, config.routing ?? {})];
1849
+ if (model.adapter === "ai-sdk-text") {
1850
+ const names = [textKeyName(model, config.routing ?? {})];
1851
+ if (config.routing?.text === "cloudflare") names.push("cloudflare");
1852
+ return names;
1853
+ }
1902
1854
  return ADAPTER_KEYS[model.adapter];
1903
1855
  }
1904
1856
  var IMAGE_OUTPUT_TYPES = /* @__PURE__ */ new Set(["image", "upscale", "bg-remove"]);
@@ -1956,6 +1908,20 @@ function sdkAspectRatio(ratio) {
1956
1908
  const parsed = sdkRatio.safeParse(ratio);
1957
1909
  return parsed.success ? parsed.data : void 0;
1958
1910
  }
1911
+ function sdkDataContent(ref) {
1912
+ return "url" in ref ? ref.url : ref.bytes;
1913
+ }
1914
+ function sdkReference(ref) {
1915
+ return "url" in ref ? ref.url : { data: ref.bytes, mediaType: ref.mime };
1916
+ }
1917
+ function pushIgnored(warnings, field, from, modelId) {
1918
+ warnings.push({
1919
+ code: "option_ignored",
1920
+ field,
1921
+ from,
1922
+ message: `${modelId} provider instance does not support ${field}; ignored`
1923
+ });
1924
+ }
1959
1925
  function createModelKit(config, adapters) {
1960
1926
  const catalog = new Catalog(MODELS).withOverrides(config.catalogOverride ?? []);
1961
1927
  const logger = config.logger ?? console;
@@ -1978,6 +1944,7 @@ function createModelKit(config, adapters) {
1978
1944
  call,
1979
1945
  warnings
1980
1946
  };
1947
+ if (config.endpoints) ctx.endpoints = config.endpoints;
1981
1948
  if (call.signal) ctx.signal = call.signal;
1982
1949
  return { adapter, ctx };
1983
1950
  }
@@ -2017,16 +1984,42 @@ function createModelKit(config, adapters) {
2017
1984
  };
2018
1985
  }
2019
1986
  async function passthroughImage(req, call) {
2020
- const { model, prompt, n, count, size, aspectRatio, seed, providerOptions } = req;
1987
+ const {
1988
+ model,
1989
+ prompt,
1990
+ n,
1991
+ count,
1992
+ size,
1993
+ aspectRatio,
1994
+ seed,
1995
+ providerOptions,
1996
+ references,
1997
+ exactSize: exactSizeField,
1998
+ quality: _quality,
1999
+ outputFormat: _outputFormat
2000
+ } = req;
2021
2001
  const options = { model, prompt };
2002
+ const warnings = [];
2022
2003
  const quantity = n ?? count;
2023
2004
  if (quantity !== void 0) options.n = quantity;
2024
- const exact = exactSize.safeParse(size);
2025
- if (exact.success) options.size = exact.data;
2005
+ if (references !== void 0 && references.length > 0)
2006
+ options.prompt = { images: references.map(sdkDataContent), text: prompt };
2007
+ const exact = exactSizeField === void 0 ? void 0 : exactSize.safeParse(exactSizeField);
2008
+ if (exact?.success) options.size = exact.data;
2009
+ else if (exactSizeField !== void 0)
2010
+ pushIgnored(warnings, "exactSize", exactSizeField, model.modelId);
2011
+ const requested = size === void 0 ? void 0 : exactSize.safeParse(size);
2012
+ if (requested?.success) options.size ??= requested.data;
2013
+ else if (size !== void 0) pushIgnored(warnings, "size", size, model.modelId);
2026
2014
  const ratio = sdkAspectRatio(aspectRatio);
2027
2015
  if (ratio) options.aspectRatio = ratio;
2016
+ else if (aspectRatio !== void 0)
2017
+ pushIgnored(warnings, "aspectRatio", aspectRatio, model.modelId);
2028
2018
  if (seed !== void 0) options.seed = seed;
2029
2019
  if (providerOptions) options.providerOptions = providerOptions;
2020
+ if (_quality !== void 0) pushIgnored(warnings, "quality", _quality, model.modelId);
2021
+ if (_outputFormat !== void 0)
2022
+ pushIgnored(warnings, "outputFormat", _outputFormat, model.modelId);
2030
2023
  const abortSignal = combineSignals(req.abortSignal, call.signal);
2031
2024
  if (abortSignal) options.abortSignal = abortSignal;
2032
2025
  const result = await (0, import_ai2.generateImage)(options);
@@ -2041,17 +2034,73 @@ function createModelKit(config, adapters) {
2041
2034
  quantity: images.length
2042
2035
  };
2043
2036
  await emit(usage2);
2044
- return { image: images[0], images, usage: usage2, raw: result, warnings: [] };
2037
+ return { image: images[0], images, usage: usage2, raw: result, warnings };
2045
2038
  }
2046
2039
  function passthroughVideo(req, call) {
2047
- const { model, prompt, aspectRatio, duration, durationSeconds, seed, providerOptions } = req;
2040
+ const {
2041
+ model,
2042
+ prompt,
2043
+ aspectRatio,
2044
+ duration,
2045
+ durationSeconds,
2046
+ seed,
2047
+ providerOptions,
2048
+ firstFrame,
2049
+ lastFrame,
2050
+ sourceVideo,
2051
+ referenceImages,
2052
+ referenceVideos,
2053
+ referenceAudios,
2054
+ resolution,
2055
+ generateAudio: generateAudio2,
2056
+ negativePrompt: _negativePrompt,
2057
+ cfgScale: _cfgScale,
2058
+ promptExpansion: _promptExpansion,
2059
+ mode: _mode
2060
+ } = req;
2048
2061
  const seconds = duration ?? durationSeconds;
2049
2062
  const abortSignal = combineSignals(req.abortSignal, call.signal);
2050
2063
  const options = { model, prompt };
2051
- const ratio = sdkAspectRatio(aspectRatio);
2064
+ const warnings = [];
2065
+ const ratio = sdkAspectRatio(aspectRatio) ?? (aspectRatio === "adaptive" ? "adaptive" : void 0);
2052
2066
  if (ratio) options.aspectRatio = ratio;
2067
+ else if (aspectRatio !== void 0)
2068
+ pushIgnored(warnings, "aspectRatio", aspectRatio, model.modelId);
2053
2069
  if (seconds !== void 0) options.duration = seconds;
2054
2070
  if (seed !== void 0) options.seed = seed;
2071
+ if (generateAudio2 !== void 0) options.generateAudio = generateAudio2;
2072
+ const frames = [
2073
+ ...firstFrame ? [{ image: sdkDataContent(firstFrame), frameType: "first_frame" }] : [],
2074
+ ...lastFrame ? [{ image: sdkDataContent(lastFrame), frameType: "last_frame" }] : []
2075
+ ];
2076
+ if (frames.length > 0) options.frameImages = frames;
2077
+ const references = [
2078
+ ...(referenceImages ?? []).map(sdkDataContent),
2079
+ ...(referenceVideos ?? []).map(sdkReference)
2080
+ ];
2081
+ if (references.length > 0) {
2082
+ if (frames.length > 0) {
2083
+ warnings.push({
2084
+ code: "option_ignored",
2085
+ field: "inputReferences",
2086
+ from: references.length,
2087
+ message: `${model.modelId} provider instance ignores references when frames are set`
2088
+ });
2089
+ } else {
2090
+ options.inputReferences = references;
2091
+ }
2092
+ }
2093
+ if (sourceVideo) pushIgnored(warnings, "sourceVideo", void 0, model.modelId);
2094
+ if (referenceAudios?.length)
2095
+ pushIgnored(warnings, "referenceAudios", referenceAudios.length, model.modelId);
2096
+ if (resolution !== void 0)
2097
+ pushIgnored(warnings, "resolution", resolution, model.modelId);
2098
+ if (_negativePrompt !== void 0)
2099
+ pushIgnored(warnings, "negativePrompt", void 0, model.modelId);
2100
+ if (_cfgScale !== void 0) pushIgnored(warnings, "cfgScale", _cfgScale, model.modelId);
2101
+ if (_promptExpansion !== void 0)
2102
+ pushIgnored(warnings, "promptExpansion", _promptExpansion, model.modelId);
2103
+ if (_mode !== void 0) pushIgnored(warnings, "mode", _mode, model.modelId);
2055
2104
  if (providerOptions) options.providerOptions = providerOptions;
2056
2105
  if (abortSignal) options.abortSignal = abortSignal;
2057
2106
  const pending = (0, import_ai2.experimental_generateVideo)(options);
@@ -2079,7 +2128,7 @@ function createModelKit(config, adapters) {
2079
2128
  });
2080
2129
  return {
2081
2130
  jobId: `ai-sdk:${model.provider}:${model.modelId}`,
2082
- warnings: [],
2131
+ warnings,
2083
2132
  async poll() {
2084
2133
  if (settled) return { status: "succeeded", raw: settled.raw };
2085
2134
  if (failed) return { status: "failed" };
@@ -2303,6 +2352,9 @@ var SUBMIT_PATH = "/contents/generations/tasks";
2303
2352
  var SEEDANCE_POLL_INTERVAL_MS = 15e3;
2304
2353
  var RESULT_TIMEOUT_MS = 15 * 60 * 1e3;
2305
2354
  var VIDEO_URL_TTL_MS = 24 * 60 * 60 * 1e3;
2355
+ function arkBaseUrl(ctx) {
2356
+ return ctx.endpoints?.ark ?? ARK_BASE_URL;
2357
+ }
2306
2358
  var SEEDREAM_SIZES = {
2307
2359
  "1K": {
2308
2360
  "1:1": "1024x1024",
@@ -2313,7 +2365,8 @@ var SEEDREAM_SIZES = {
2313
2365
  "3:2": "1248x832",
2314
2366
  "2:3": "832x1248",
2315
2367
  "21:9": "1568x672",
2316
- "4:5": "864x1080"
2368
+ "4:5": "864x1080",
2369
+ "5:4": "1080x864"
2317
2370
  },
2318
2371
  "2K": {
2319
2372
  "1:1": "2048x2048",
@@ -2324,7 +2377,8 @@ var SEEDREAM_SIZES = {
2324
2377
  "3:2": "2496x1664",
2325
2378
  "2:3": "1664x2496",
2326
2379
  "21:9": "3136x1344",
2327
- "4:5": "1728x2160"
2380
+ "4:5": "1728x2160",
2381
+ "5:4": "2160x1728"
2328
2382
  }
2329
2383
  };
2330
2384
  var arkAdapter = {
@@ -2371,9 +2425,11 @@ function seedreamSize(aspect, size) {
2371
2425
  const table = SEEDREAM_SIZES[tier];
2372
2426
  return table[aspect ?? ""] ?? table["1:1"] ?? "2048x2048";
2373
2427
  }
2374
- function outputMime(format) {
2375
- if (format === "png") return "image/png";
2376
- if (format === "webp") return "image/webp";
2428
+ function sniffedImageMime(bytes) {
2429
+ if (bytes.length >= 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71)
2430
+ return "image/png";
2431
+ if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80)
2432
+ return "image/webp";
2377
2433
  return "image/jpeg";
2378
2434
  }
2379
2435
  function arkUrl(ref) {
@@ -2415,7 +2471,7 @@ async function generateSeedreamImage(req, ctx) {
2415
2471
  else if (refs.length > 1) body.image = refs;
2416
2472
  const data = await withRetry(async () => {
2417
2473
  const res = await ctx.fetch(
2418
- `${ARK_BASE_URL}/images/generations`,
2474
+ `${arkBaseUrl(ctx)}/images/generations`,
2419
2475
  fetchInitFor(ctx, {
2420
2476
  method: "POST",
2421
2477
  headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
@@ -2454,7 +2510,7 @@ async function generateSeedreamImage(req, ctx) {
2454
2510
  };
2455
2511
  if (req.size !== void 0) usage2.imageSize = req.size;
2456
2512
  if (req.quality !== void 0) usage2.imageQuality = req.quality;
2457
- return { images: [{ bytes, mime: outputMime(req.outputFormat) }], usage: usage2, raw: data };
2513
+ return { images: [{ bytes, mime: sniffedImageMime(bytes) }], usage: usage2, raw: data };
2458
2514
  }
2459
2515
  var SeedanceResponseSchema = import_zod4.z.object({
2460
2516
  id: import_zod4.z.string().optional(),
@@ -2542,7 +2598,7 @@ async function generateSeedanceVideo(req, ctx) {
2542
2598
  if (req.seed !== void 0) body.seed = req.seed;
2543
2599
  const submit = await withRetry(async () => {
2544
2600
  const res = await ctx.fetch(
2545
- `${ARK_BASE_URL}${SUBMIT_PATH}`,
2601
+ `${arkBaseUrl(ctx)}${SUBMIT_PATH}`,
2546
2602
  fetchInitFor(ctx, {
2547
2603
  method: "POST",
2548
2604
  headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
@@ -2562,7 +2618,7 @@ async function generateSeedanceVideo(req, ctx) {
2562
2618
  const usage2 = seedanceUsage(ctx, req, duration);
2563
2619
  const fetchStatus = async () => {
2564
2620
  const res = await ctx.fetch(
2565
- `${ARK_BASE_URL}${SUBMIT_PATH}/${jobId}`,
2621
+ `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`,
2566
2622
  fetchInitFor(ctx, {
2567
2623
  headers: { Authorization: `Bearer ${key}` }
2568
2624
  })
@@ -3621,60 +3677,6 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
3621
3677
  uploadedNames.push(file.fileName);
3622
3678
  return file.fileUri;
3623
3679
  };
3624
- const parts = [];
3625
- if (req.sourceVideo) {
3626
- const media = await resolveMedia(
3627
- req.sourceVideo,
3628
- ctx.fetch,
3629
- ctx.signal,
3630
- "video/mp4",
3631
- modelId
3632
- );
3633
- parts.push({
3634
- type: "video",
3635
- uri: await uploadOne("video", media.bytes, media.mime),
3636
- mime_type: media.mime
3637
- });
3638
- }
3639
- for (const ref of videos) {
3640
- const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "video/mp4", modelId);
3641
- parts.push({
3642
- type: "video",
3643
- uri: await uploadOne("video", media.bytes, media.mime),
3644
- mime_type: media.mime
3645
- });
3646
- }
3647
- if (req.firstFrame) {
3648
- const media = await resolveMedia(
3649
- req.firstFrame,
3650
- ctx.fetch,
3651
- ctx.signal,
3652
- "image/png",
3653
- modelId
3654
- );
3655
- if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3656
- parts.push({
3657
- type: "image",
3658
- uri: await uploadOne("image", media.bytes, media.mime),
3659
- mime_type: media.mime
3660
- });
3661
- } else {
3662
- parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3663
- }
3664
- }
3665
- for (const ref of images) {
3666
- const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "image/png", modelId);
3667
- if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3668
- parts.push({
3669
- type: "image",
3670
- uri: await uploadOne("image", media.bytes, media.mime),
3671
- mime_type: media.mime
3672
- });
3673
- } else {
3674
- parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3675
- }
3676
- }
3677
- parts.push({ type: "text", text: req.prompt });
3678
3680
  const cleanupUploads = async () => {
3679
3681
  for (const fileName of uploadedNames) await deleteGoogleFile(fileName, apiKey, ctx.fetch);
3680
3682
  };
@@ -3688,6 +3690,60 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
3688
3690
  if (ctx.signal !== void 0) createRetryOptions.signal = ctx.signal;
3689
3691
  let interaction;
3690
3692
  try {
3693
+ const parts = [];
3694
+ if (req.sourceVideo) {
3695
+ const media = await resolveMedia(
3696
+ req.sourceVideo,
3697
+ ctx.fetch,
3698
+ ctx.signal,
3699
+ "video/mp4",
3700
+ modelId
3701
+ );
3702
+ parts.push({
3703
+ type: "video",
3704
+ uri: await uploadOne("video", media.bytes, media.mime),
3705
+ mime_type: media.mime
3706
+ });
3707
+ }
3708
+ for (const ref of videos) {
3709
+ const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "video/mp4", modelId);
3710
+ parts.push({
3711
+ type: "video",
3712
+ uri: await uploadOne("video", media.bytes, media.mime),
3713
+ mime_type: media.mime
3714
+ });
3715
+ }
3716
+ if (req.firstFrame) {
3717
+ const media = await resolveMedia(
3718
+ req.firstFrame,
3719
+ ctx.fetch,
3720
+ ctx.signal,
3721
+ "image/png",
3722
+ modelId
3723
+ );
3724
+ if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3725
+ parts.push({
3726
+ type: "image",
3727
+ uri: await uploadOne("image", media.bytes, media.mime),
3728
+ mime_type: media.mime
3729
+ });
3730
+ } else {
3731
+ parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3732
+ }
3733
+ }
3734
+ for (const ref of images) {
3735
+ const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "image/png", modelId);
3736
+ if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3737
+ parts.push({
3738
+ type: "image",
3739
+ uri: await uploadOne("image", media.bytes, media.mime),
3740
+ mime_type: media.mime
3741
+ });
3742
+ } else {
3743
+ parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3744
+ }
3745
+ }
3746
+ parts.push({ type: "text", text: req.prompt });
3691
3747
  try {
3692
3748
  interaction = await withRetry(async () => {
3693
3749
  try {
@@ -4753,7 +4809,8 @@ var RATIO_TO_DIMS = {
4753
4809
  "9:16": { width: 768, height: 1360 },
4754
4810
  "3:4": { width: 768, height: 1024 },
4755
4811
  "2:3": { width: 768, height: 1152 },
4756
- "4:5": { width: 896, height: 1120 }
4812
+ "4:5": { width: 896, height: 1120 },
4813
+ "5:4": { width: 1120, height: 896 }
4757
4814
  };
4758
4815
  function dimsFor(aspectRatio) {
4759
4816
  if (aspectRatio !== void 0) {
@@ -4762,6 +4819,27 @@ function dimsFor(aspectRatio) {
4762
4819
  }
4763
4820
  return { width: 1024, height: 1024 };
4764
4821
  }
4822
+ var GEMINI_JOB_RATIOS = /* @__PURE__ */ new Set([
4823
+ "1:1",
4824
+ "2:3",
4825
+ "3:2",
4826
+ "3:4",
4827
+ "4:3",
4828
+ "4:5",
4829
+ "5:4",
4830
+ "9:16",
4831
+ "16:9",
4832
+ "21:9"
4833
+ ]);
4834
+ var NANO_BANANA_RATIOS = /* @__PURE__ */ new Set([
4835
+ "1:1",
4836
+ "2:3",
4837
+ "3:4",
4838
+ "4:3",
4839
+ "9:16",
4840
+ "16:9",
4841
+ "21:9"
4842
+ ]);
4765
4843
  async function refToBytes(ctx, ref) {
4766
4844
  if ("bytes" in ref) return { bytes: ref.bytes, mime: ref.mime };
4767
4845
  const res = await ctx.fetch(ref.url);
@@ -4861,7 +4939,7 @@ async function generateProdiaImage(req, ctx) {
4861
4939
  inputs = refs.length > 0 && firstRef !== void 0 ? [firstRef] : [];
4862
4940
  accept = req.outputFormat === "png" ? "image/png" : "image/jpeg";
4863
4941
  mime = accept;
4864
- } else if (providerModelId === "flux-2-pro" || providerModelId === FLUX_2_PRO_JOB || providerModelId === "inference.gemini-3-pro.img2img.v1" || providerModelId === "inference.nano-banana.img2img.v2") {
4942
+ } else if (providerModelId === "flux-2-pro" || providerModelId === FLUX_2_PRO_JOB) {
4865
4943
  const dims = dimsFor(req.aspectRatio);
4866
4944
  const config = {
4867
4945
  prompt: req.prompt,
@@ -4870,8 +4948,30 @@ async function generateProdiaImage(req, ctx) {
4870
4948
  safety_tolerance: 5
4871
4949
  };
4872
4950
  if (req.seed !== void 0) config.seed = req.seed;
4873
- const jobType = providerModelId === "flux-2-pro" ? FLUX_2_PRO_JOB : providerModelId;
4874
- job = { type: jobType, config };
4951
+ job = { type: FLUX_2_PRO_JOB, config };
4952
+ inputs = refs;
4953
+ accept = "image/png";
4954
+ mime = "image/png";
4955
+ } else if (providerModelId === "inference.gemini-3-pro.img2img.v1" || providerModelId === "inference.nano-banana.img2img.v2") {
4956
+ const allowed = providerModelId === "inference.nano-banana.img2img.v2" ? NANO_BANANA_RATIOS : GEMINI_JOB_RATIOS;
4957
+ const config = {
4958
+ prompt: req.prompt,
4959
+ safety_tolerance: 5
4960
+ };
4961
+ if (req.aspectRatio !== void 0 && req.aspectRatio !== "adaptive") {
4962
+ if (allowed.has(req.aspectRatio)) config.aspect_ratio = req.aspectRatio;
4963
+ else
4964
+ ctx.warnings.push({
4965
+ code: "option_ignored",
4966
+ field: "aspectRatio",
4967
+ from: req.aspectRatio,
4968
+ message: `${providerModelId} has no ${req.aspectRatio} aspect_ratio; using the provider default`
4969
+ });
4970
+ }
4971
+ if (providerModelId === "inference.gemini-3-pro.img2img.v1")
4972
+ config.resolution = req.size ?? "1K";
4973
+ if (req.seed !== void 0) config.seed = req.seed;
4974
+ job = { type: providerModelId, config };
4875
4975
  inputs = refs;
4876
4976
  accept = "image/png";
4877
4977
  mime = "image/png";
@@ -5145,6 +5245,50 @@ function isUpscaler(providerModelId) {
5145
5245
  function isQwen(providerModelId) {
5146
5246
  return isLower(providerModelId).includes("qwen");
5147
5247
  }
5248
+ function isWanImage(providerModelId) {
5249
+ return isLower(providerModelId).includes("wan-2.7");
5250
+ }
5251
+ function isQwenEditPlus(providerModelId) {
5252
+ return isLower(providerModelId).includes("edit");
5253
+ }
5254
+ var QWEN_IMAGE_RATIOS = /* @__PURE__ */ new Set([
5255
+ "1:1",
5256
+ "16:9",
5257
+ "9:16",
5258
+ "4:3",
5259
+ "3:4",
5260
+ "3:2",
5261
+ "2:3"
5262
+ ]);
5263
+ var QWEN_EDIT_RATIOS = /* @__PURE__ */ new Set(["1:1", "16:9", "9:16", "4:3", "3:4"]);
5264
+ var WAN_IMAGE_SIZES = {
5265
+ "1K": {
5266
+ "1:1": "1024*1024",
5267
+ "16:9": "1280*720",
5268
+ "9:16": "720*1280",
5269
+ "4:3": "1024*768",
5270
+ "3:4": "768*1024"
5271
+ },
5272
+ "2K": {
5273
+ "1:1": "2048*2048",
5274
+ "16:9": "2048*1152",
5275
+ "9:16": "1152*2048",
5276
+ "4:3": "2048*1536",
5277
+ "3:4": "1536*2048"
5278
+ },
5279
+ "4K": {
5280
+ "1:1": "4096*4096",
5281
+ "16:9": "4096*2304",
5282
+ "9:16": "2304*4096",
5283
+ "4:3": "4096*3072",
5284
+ "3:4": "3072*4096"
5285
+ }
5286
+ };
5287
+ function wanImageSize(aspectRatio, size) {
5288
+ const tier = size === "2K" || size === "4K" ? size : "1K";
5289
+ const table = WAN_IMAGE_SIZES[tier];
5290
+ return table[aspectRatio ?? ""] ?? table["1:1"] ?? "1024*1024";
5291
+ }
5148
5292
  async function generateReplicateImage(req, ctx, deps) {
5149
5293
  const id = ctx.model.providerModelId;
5150
5294
  const input = { prompt: req.prompt };
@@ -5164,14 +5308,31 @@ async function generateReplicateImage(req, ctx, deps) {
5164
5308
  input.creativity = 0;
5165
5309
  input.scale_factor = 2;
5166
5310
  input.output_format = "png";
5311
+ } else if (isWanImage(id)) {
5312
+ const refs = (req.references ?? []).map(refToUrl3);
5313
+ if (refs.length > 0) input.images = refs;
5314
+ input.size = wanImageSize(req.aspectRatio, req.size);
5167
5315
  } else if (isQwen(id)) {
5168
5316
  const refs = (req.references ?? []).map(refToUrl3);
5169
5317
  const firstUrl = refs[0];
5170
5318
  if (firstUrl !== void 0) {
5171
5319
  input.image = firstUrl;
5172
- input.match_input_image = true;
5320
+ if (!isQwenEditPlus(id)) input.match_input_image = true;
5321
+ }
5322
+ const ratios = isQwenEditPlus(id) ? QWEN_EDIT_RATIOS : QWEN_IMAGE_RATIOS;
5323
+ if (req.aspectRatio !== void 0) {
5324
+ if (ratios.has(req.aspectRatio)) input.aspect_ratio = req.aspectRatio;
5325
+ else
5326
+ ctx.warnings.push({
5327
+ code: "option_ignored",
5328
+ field: "aspectRatio",
5329
+ from: req.aspectRatio,
5330
+ message: `${id} has no ${req.aspectRatio} aspect_ratio; using the provider default`
5331
+ });
5332
+ }
5333
+ if (isQwenEditPlus(id) && req.outputFormat !== void 0) {
5334
+ input.output_format = req.outputFormat === "jpeg" ? "jpg" : req.outputFormat;
5173
5335
  }
5174
- if (req.outputFormat !== void 0) input.output_format = req.outputFormat;
5175
5336
  } else {
5176
5337
  const refs = req.references ?? [];
5177
5338
  const first = refs[0];