@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.js CHANGED
@@ -210,7 +210,10 @@ var IMAGE_MODELS = [
210
210
  constraints: {
211
211
  aspectRatios: ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "21:9", "4:5", "5:4"],
212
212
  sizes: ["1K", "2K"],
213
- references: { images: 10 }
213
+ references: { images: 10 },
214
+ // Ark's Images API has no output-format, quality or count field; only
215
+ // the seed is forwarded, so anything else must warn, not vanish.
216
+ supports: ["seed"]
214
217
  },
215
218
  defaults: { size: "1K", aspectRatio: "1:1" },
216
219
  usage: { ...IMAGE_USAGE },
@@ -225,21 +228,12 @@ var IMAGE_MODELS = [
225
228
  providerModelId: "seedream-4-5-251128",
226
229
  lifecycle: "active",
227
230
  constraints: {
228
- aspectRatios: [
229
- "1:1",
230
- "2:3",
231
- "3:2",
232
- "4:3",
233
- "3:4",
234
- "4:5",
235
- "5:4",
236
- "9:16",
237
- "16:9",
238
- "21:9",
239
- "97:30"
240
- ],
231
+ // 97:30 has no Ark size spelling; the A+ flow pads inputs to 97:30 and
232
+ // omits the ratio instead. Advertise only sizes the adapter can send.
233
+ aspectRatios: ["1:1", "2:3", "3:2", "4:3", "3:4", "4:5", "5:4", "9:16", "16:9", "21:9"],
241
234
  sizes: ["1K", "2K"],
242
- references: { images: 10 }
235
+ references: { images: 10 },
236
+ supports: ["seed"]
243
237
  },
244
238
  defaults: { size: "1K", aspectRatio: "1:1" },
245
239
  usage: { ...IMAGE_USAGE },
@@ -263,6 +257,9 @@ var IMAGE_MODELS = [
263
257
  pricingKind: "image_size"
264
258
  },
265
259
  {
260
+ // Prodia's gemini-3-pro jobs take aspect_ratio + resolution (1K/2K/4K), not
261
+ // width/height; 97:30 is outside its aspect_ratio enum.
262
+ // https://docs.prodia.com/models/gemini-3/
266
263
  id: "prodia-nano-banana-pro",
267
264
  aliases: [],
268
265
  label: "Nano Banana Pro via Prodia",
@@ -271,19 +268,7 @@ var IMAGE_MODELS = [
271
268
  providerModelId: "inference.gemini-3-pro.img2img.v1",
272
269
  lifecycle: "active",
273
270
  constraints: {
274
- aspectRatios: [
275
- "1:1",
276
- "2:3",
277
- "3:2",
278
- "4:3",
279
- "3:4",
280
- "4:5",
281
- "5:4",
282
- "9:16",
283
- "16:9",
284
- "21:9",
285
- "97:30"
286
- ],
271
+ aspectRatios: ["1:1", "2:3", "3:2", "4:3", "3:4", "4:5", "5:4", "9:16", "16:9", "21:9"],
287
272
  sizes: ["1K", "2K", "4K"],
288
273
  requires: ["references"]
289
274
  },
@@ -292,6 +277,8 @@ var IMAGE_MODELS = [
292
277
  pricingKind: "image_size"
293
278
  },
294
279
  {
280
+ // Nano Banana (img2img.v2) is flat-rate with aspect_ratio only; 3:2, 4:5,
281
+ // 5:4 and 97:30 are not in its enum. https://docs.prodia.com/models/nano-banana/
295
282
  id: "prodia-nano-banana",
296
283
  aliases: [],
297
284
  label: "Nano Banana via Prodia",
@@ -300,19 +287,7 @@ var IMAGE_MODELS = [
300
287
  providerModelId: "inference.nano-banana.img2img.v2",
301
288
  lifecycle: "active",
302
289
  constraints: {
303
- aspectRatios: [
304
- "1:1",
305
- "2:3",
306
- "3:2",
307
- "4:3",
308
- "3:4",
309
- "4:5",
310
- "5:4",
311
- "9:16",
312
- "16:9",
313
- "21:9",
314
- "97:30"
315
- ],
290
+ aspectRatios: ["1:1", "2:3", "3:4", "4:3", "9:16", "16:9", "21:9"],
316
291
  sizes: ["1K"],
317
292
  requires: ["references"]
318
293
  },
@@ -321,6 +296,9 @@ var IMAGE_MODELS = [
321
296
  pricingKind: "image_size"
322
297
  },
323
298
  {
299
+ // WAN 2.7 takes `images` (up to 9) and a `size` enum whose exact values
300
+ // cover 1:1/16:9/9:16/4:3/3:4 only; other ratios have no spelling.
301
+ // https://replicate.com/wan-video/wan-2.7-image-pro (input schema)
324
302
  id: "wan-2.7-image-pro",
325
303
  aliases: [],
326
304
  label: "WAN 2.7 Image Pro",
@@ -329,19 +307,7 @@ var IMAGE_MODELS = [
329
307
  providerModelId: "wan-video/wan-2.7-image-pro",
330
308
  lifecycle: "active",
331
309
  constraints: {
332
- aspectRatios: [
333
- "1:1",
334
- "2:3",
335
- "3:2",
336
- "4:3",
337
- "3:4",
338
- "4:5",
339
- "5:4",
340
- "9:16",
341
- "16:9",
342
- "21:9",
343
- "97:30"
344
- ],
310
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4"],
345
311
  sizes: ["1K", "2K", "4K"],
346
312
  references: { images: 9 }
347
313
  },
@@ -350,6 +316,9 @@ var IMAGE_MODELS = [
350
316
  pricingKind: "image_size"
351
317
  },
352
318
  {
319
+ // Qwen Image 2 Pro has aspect_ratio but no size input; resolution follows
320
+ // the reference image (match_input_image) or the provider default.
321
+ // https://replicate.com/qwen/qwen-image-2-pro (input schema)
353
322
  id: "qwen-image-2-pro",
354
323
  aliases: [],
355
324
  label: "Qwen Image 2 Pro",
@@ -358,28 +327,19 @@ var IMAGE_MODELS = [
358
327
  providerModelId: "qwen/qwen-image-2-pro",
359
328
  lifecycle: "active",
360
329
  constraints: {
361
- aspectRatios: [
362
- "1:1",
363
- "2:3",
364
- "3:2",
365
- "4:3",
366
- "3:4",
367
- "4:5",
368
- "5:4",
369
- "9:16",
370
- "16:9",
371
- "21:9",
372
- "97:30"
373
- ],
374
- sizes: ["1K", "2K", "4K"],
330
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"],
331
+ sizes: [],
375
332
  references: { images: 1 },
376
333
  requires: ["references"]
377
334
  },
378
- defaults: { size: "1K", aspectRatio: "1:1" },
335
+ defaults: { aspectRatio: "1:1" },
379
336
  usage: { unit: "compute_seconds", modality: "IMAGE" },
380
337
  pricingKind: "image_size"
381
338
  },
382
339
  {
340
+ // Qwen Image Edit Plus takes aspect_ratio (or match_input_image) and
341
+ // output_format, but no size; no default ratio so edits keep following
342
+ // the input image. https://replicate.com/qwen-edit-apps/qwen-image-edit-plus-lora-skin
383
343
  id: "qwen-image-edit-plus-lora-skin",
384
344
  aliases: [],
385
345
  label: "Qwen Image Edit Plus",
@@ -388,25 +348,13 @@ var IMAGE_MODELS = [
388
348
  providerModelId: "qwen-edit-apps/qwen-image-edit-plus-lora-skin",
389
349
  lifecycle: "active",
390
350
  constraints: {
391
- aspectRatios: [
392
- "1:1",
393
- "2:3",
394
- "3:2",
395
- "4:3",
396
- "3:4",
397
- "4:5",
398
- "5:4",
399
- "9:16",
400
- "16:9",
401
- "21:9",
402
- "97:30"
403
- ],
404
- sizes: ["1K", "2K", "4K"],
351
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4"],
352
+ sizes: [],
405
353
  references: { images: 1 },
406
354
  requires: ["references"],
407
355
  supports: ["outputFormat"]
408
356
  },
409
- defaults: { size: "1K", aspectRatio: "1:1", outputFormat: "png" },
357
+ defaults: { outputFormat: "png" },
410
358
  usage: { unit: "compute_seconds", modality: "IMAGE" },
411
359
  pricingKind: "image_size"
412
360
  }
@@ -1823,7 +1771,11 @@ var aiSdkTextAdapter = {
1823
1771
  function requiredKeys(model, config) {
1824
1772
  if (model.adapter === "fal" && config.routing?.fal === "cloudflare-passthrough")
1825
1773
  return ["cloudflare"];
1826
- if (model.adapter === "ai-sdk-text") return [textKeyName(model, config.routing ?? {})];
1774
+ if (model.adapter === "ai-sdk-text") {
1775
+ const names = [textKeyName(model, config.routing ?? {})];
1776
+ if (config.routing?.text === "cloudflare") names.push("cloudflare");
1777
+ return names;
1778
+ }
1827
1779
  return ADAPTER_KEYS[model.adapter];
1828
1780
  }
1829
1781
  var IMAGE_OUTPUT_TYPES = /* @__PURE__ */ new Set(["image", "upscale", "bg-remove"]);
@@ -1881,6 +1833,20 @@ function sdkAspectRatio(ratio) {
1881
1833
  const parsed = sdkRatio.safeParse(ratio);
1882
1834
  return parsed.success ? parsed.data : void 0;
1883
1835
  }
1836
+ function sdkDataContent(ref) {
1837
+ return "url" in ref ? ref.url : ref.bytes;
1838
+ }
1839
+ function sdkReference(ref) {
1840
+ return "url" in ref ? ref.url : { data: ref.bytes, mediaType: ref.mime };
1841
+ }
1842
+ function pushIgnored(warnings, field, from, modelId) {
1843
+ warnings.push({
1844
+ code: "option_ignored",
1845
+ field,
1846
+ from,
1847
+ message: `${modelId} provider instance does not support ${field}; ignored`
1848
+ });
1849
+ }
1884
1850
  function createModelKit(config, adapters) {
1885
1851
  const catalog = new Catalog(MODELS).withOverrides(config.catalogOverride ?? []);
1886
1852
  const logger = config.logger ?? console;
@@ -1903,6 +1869,7 @@ function createModelKit(config, adapters) {
1903
1869
  call,
1904
1870
  warnings
1905
1871
  };
1872
+ if (config.endpoints) ctx.endpoints = config.endpoints;
1906
1873
  if (call.signal) ctx.signal = call.signal;
1907
1874
  return { adapter, ctx };
1908
1875
  }
@@ -1942,16 +1909,42 @@ function createModelKit(config, adapters) {
1942
1909
  };
1943
1910
  }
1944
1911
  async function passthroughImage(req, call) {
1945
- const { model, prompt, n, count, size, aspectRatio, seed, providerOptions } = req;
1912
+ const {
1913
+ model,
1914
+ prompt,
1915
+ n,
1916
+ count,
1917
+ size,
1918
+ aspectRatio,
1919
+ seed,
1920
+ providerOptions,
1921
+ references,
1922
+ exactSize: exactSizeField,
1923
+ quality: _quality,
1924
+ outputFormat: _outputFormat
1925
+ } = req;
1946
1926
  const options = { model, prompt };
1927
+ const warnings = [];
1947
1928
  const quantity = n ?? count;
1948
1929
  if (quantity !== void 0) options.n = quantity;
1949
- const exact = exactSize.safeParse(size);
1950
- if (exact.success) options.size = exact.data;
1930
+ if (references !== void 0 && references.length > 0)
1931
+ options.prompt = { images: references.map(sdkDataContent), text: prompt };
1932
+ const exact = exactSizeField === void 0 ? void 0 : exactSize.safeParse(exactSizeField);
1933
+ if (exact?.success) options.size = exact.data;
1934
+ else if (exactSizeField !== void 0)
1935
+ pushIgnored(warnings, "exactSize", exactSizeField, model.modelId);
1936
+ const requested = size === void 0 ? void 0 : exactSize.safeParse(size);
1937
+ if (requested?.success) options.size ??= requested.data;
1938
+ else if (size !== void 0) pushIgnored(warnings, "size", size, model.modelId);
1951
1939
  const ratio = sdkAspectRatio(aspectRatio);
1952
1940
  if (ratio) options.aspectRatio = ratio;
1941
+ else if (aspectRatio !== void 0)
1942
+ pushIgnored(warnings, "aspectRatio", aspectRatio, model.modelId);
1953
1943
  if (seed !== void 0) options.seed = seed;
1954
1944
  if (providerOptions) options.providerOptions = providerOptions;
1945
+ if (_quality !== void 0) pushIgnored(warnings, "quality", _quality, model.modelId);
1946
+ if (_outputFormat !== void 0)
1947
+ pushIgnored(warnings, "outputFormat", _outputFormat, model.modelId);
1955
1948
  const abortSignal = combineSignals(req.abortSignal, call.signal);
1956
1949
  if (abortSignal) options.abortSignal = abortSignal;
1957
1950
  const result = await aiGenerateImage(options);
@@ -1966,17 +1959,73 @@ function createModelKit(config, adapters) {
1966
1959
  quantity: images.length
1967
1960
  };
1968
1961
  await emit(usage2);
1969
- return { image: images[0], images, usage: usage2, raw: result, warnings: [] };
1962
+ return { image: images[0], images, usage: usage2, raw: result, warnings };
1970
1963
  }
1971
1964
  function passthroughVideo(req, call) {
1972
- const { model, prompt, aspectRatio, duration, durationSeconds, seed, providerOptions } = req;
1965
+ const {
1966
+ model,
1967
+ prompt,
1968
+ aspectRatio,
1969
+ duration,
1970
+ durationSeconds,
1971
+ seed,
1972
+ providerOptions,
1973
+ firstFrame,
1974
+ lastFrame,
1975
+ sourceVideo,
1976
+ referenceImages,
1977
+ referenceVideos,
1978
+ referenceAudios,
1979
+ resolution,
1980
+ generateAudio: generateAudio2,
1981
+ negativePrompt: _negativePrompt,
1982
+ cfgScale: _cfgScale,
1983
+ promptExpansion: _promptExpansion,
1984
+ mode: _mode
1985
+ } = req;
1973
1986
  const seconds = duration ?? durationSeconds;
1974
1987
  const abortSignal = combineSignals(req.abortSignal, call.signal);
1975
1988
  const options = { model, prompt };
1976
- const ratio = sdkAspectRatio(aspectRatio);
1989
+ const warnings = [];
1990
+ const ratio = sdkAspectRatio(aspectRatio) ?? (aspectRatio === "adaptive" ? "adaptive" : void 0);
1977
1991
  if (ratio) options.aspectRatio = ratio;
1992
+ else if (aspectRatio !== void 0)
1993
+ pushIgnored(warnings, "aspectRatio", aspectRatio, model.modelId);
1978
1994
  if (seconds !== void 0) options.duration = seconds;
1979
1995
  if (seed !== void 0) options.seed = seed;
1996
+ if (generateAudio2 !== void 0) options.generateAudio = generateAudio2;
1997
+ const frames = [
1998
+ ...firstFrame ? [{ image: sdkDataContent(firstFrame), frameType: "first_frame" }] : [],
1999
+ ...lastFrame ? [{ image: sdkDataContent(lastFrame), frameType: "last_frame" }] : []
2000
+ ];
2001
+ if (frames.length > 0) options.frameImages = frames;
2002
+ const references = [
2003
+ ...(referenceImages ?? []).map(sdkDataContent),
2004
+ ...(referenceVideos ?? []).map(sdkReference)
2005
+ ];
2006
+ if (references.length > 0) {
2007
+ if (frames.length > 0) {
2008
+ warnings.push({
2009
+ code: "option_ignored",
2010
+ field: "inputReferences",
2011
+ from: references.length,
2012
+ message: `${model.modelId} provider instance ignores references when frames are set`
2013
+ });
2014
+ } else {
2015
+ options.inputReferences = references;
2016
+ }
2017
+ }
2018
+ if (sourceVideo) pushIgnored(warnings, "sourceVideo", void 0, model.modelId);
2019
+ if (referenceAudios?.length)
2020
+ pushIgnored(warnings, "referenceAudios", referenceAudios.length, model.modelId);
2021
+ if (resolution !== void 0)
2022
+ pushIgnored(warnings, "resolution", resolution, model.modelId);
2023
+ if (_negativePrompt !== void 0)
2024
+ pushIgnored(warnings, "negativePrompt", void 0, model.modelId);
2025
+ if (_cfgScale !== void 0) pushIgnored(warnings, "cfgScale", _cfgScale, model.modelId);
2026
+ if (_promptExpansion !== void 0)
2027
+ pushIgnored(warnings, "promptExpansion", _promptExpansion, model.modelId);
2028
+ if (_mode !== void 0) pushIgnored(warnings, "mode", _mode, model.modelId);
1980
2029
  if (providerOptions) options.providerOptions = providerOptions;
1981
2030
  if (abortSignal) options.abortSignal = abortSignal;
1982
2031
  const pending = aiGenerateVideo(options);
@@ -2004,7 +2053,7 @@ function createModelKit(config, adapters) {
2004
2053
  });
2005
2054
  return {
2006
2055
  jobId: `ai-sdk:${model.provider}:${model.modelId}`,
2007
- warnings: [],
2056
+ warnings,
2008
2057
  async poll() {
2009
2058
  if (settled) return { status: "succeeded", raw: settled.raw };
2010
2059
  if (failed) return { status: "failed" };
@@ -2228,6 +2277,9 @@ var SUBMIT_PATH = "/contents/generations/tasks";
2228
2277
  var SEEDANCE_POLL_INTERVAL_MS = 15e3;
2229
2278
  var RESULT_TIMEOUT_MS = 15 * 60 * 1e3;
2230
2279
  var VIDEO_URL_TTL_MS = 24 * 60 * 60 * 1e3;
2280
+ function arkBaseUrl(ctx) {
2281
+ return ctx.endpoints?.ark ?? ARK_BASE_URL;
2282
+ }
2231
2283
  var SEEDREAM_SIZES = {
2232
2284
  "1K": {
2233
2285
  "1:1": "1024x1024",
@@ -2238,7 +2290,8 @@ var SEEDREAM_SIZES = {
2238
2290
  "3:2": "1248x832",
2239
2291
  "2:3": "832x1248",
2240
2292
  "21:9": "1568x672",
2241
- "4:5": "864x1080"
2293
+ "4:5": "864x1080",
2294
+ "5:4": "1080x864"
2242
2295
  },
2243
2296
  "2K": {
2244
2297
  "1:1": "2048x2048",
@@ -2249,7 +2302,8 @@ var SEEDREAM_SIZES = {
2249
2302
  "3:2": "2496x1664",
2250
2303
  "2:3": "1664x2496",
2251
2304
  "21:9": "3136x1344",
2252
- "4:5": "1728x2160"
2305
+ "4:5": "1728x2160",
2306
+ "5:4": "2160x1728"
2253
2307
  }
2254
2308
  };
2255
2309
  var arkAdapter = {
@@ -2296,9 +2350,11 @@ function seedreamSize(aspect, size) {
2296
2350
  const table = SEEDREAM_SIZES[tier];
2297
2351
  return table[aspect ?? ""] ?? table["1:1"] ?? "2048x2048";
2298
2352
  }
2299
- function outputMime(format) {
2300
- if (format === "png") return "image/png";
2301
- if (format === "webp") return "image/webp";
2353
+ function sniffedImageMime(bytes) {
2354
+ if (bytes.length >= 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71)
2355
+ return "image/png";
2356
+ 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)
2357
+ return "image/webp";
2302
2358
  return "image/jpeg";
2303
2359
  }
2304
2360
  function arkUrl(ref) {
@@ -2340,7 +2396,7 @@ async function generateSeedreamImage(req, ctx) {
2340
2396
  else if (refs.length > 1) body.image = refs;
2341
2397
  const data = await withRetry(async () => {
2342
2398
  const res = await ctx.fetch(
2343
- `${ARK_BASE_URL}/images/generations`,
2399
+ `${arkBaseUrl(ctx)}/images/generations`,
2344
2400
  fetchInitFor(ctx, {
2345
2401
  method: "POST",
2346
2402
  headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
@@ -2379,7 +2435,7 @@ async function generateSeedreamImage(req, ctx) {
2379
2435
  };
2380
2436
  if (req.size !== void 0) usage2.imageSize = req.size;
2381
2437
  if (req.quality !== void 0) usage2.imageQuality = req.quality;
2382
- return { images: [{ bytes, mime: outputMime(req.outputFormat) }], usage: usage2, raw: data };
2438
+ return { images: [{ bytes, mime: sniffedImageMime(bytes) }], usage: usage2, raw: data };
2383
2439
  }
2384
2440
  var SeedanceResponseSchema = z4.object({
2385
2441
  id: z4.string().optional(),
@@ -2467,7 +2523,7 @@ async function generateSeedanceVideo(req, ctx) {
2467
2523
  if (req.seed !== void 0) body.seed = req.seed;
2468
2524
  const submit = await withRetry(async () => {
2469
2525
  const res = await ctx.fetch(
2470
- `${ARK_BASE_URL}${SUBMIT_PATH}`,
2526
+ `${arkBaseUrl(ctx)}${SUBMIT_PATH}`,
2471
2527
  fetchInitFor(ctx, {
2472
2528
  method: "POST",
2473
2529
  headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
@@ -2487,7 +2543,7 @@ async function generateSeedanceVideo(req, ctx) {
2487
2543
  const usage2 = seedanceUsage(ctx, req, duration);
2488
2544
  const fetchStatus = async () => {
2489
2545
  const res = await ctx.fetch(
2490
- `${ARK_BASE_URL}${SUBMIT_PATH}/${jobId}`,
2546
+ `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`,
2491
2547
  fetchInitFor(ctx, {
2492
2548
  headers: { Authorization: `Bearer ${key}` }
2493
2549
  })
@@ -3548,60 +3604,6 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
3548
3604
  uploadedNames.push(file.fileName);
3549
3605
  return file.fileUri;
3550
3606
  };
3551
- const parts = [];
3552
- if (req.sourceVideo) {
3553
- const media = await resolveMedia(
3554
- req.sourceVideo,
3555
- ctx.fetch,
3556
- ctx.signal,
3557
- "video/mp4",
3558
- modelId
3559
- );
3560
- parts.push({
3561
- type: "video",
3562
- uri: await uploadOne("video", media.bytes, media.mime),
3563
- mime_type: media.mime
3564
- });
3565
- }
3566
- for (const ref of videos) {
3567
- const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "video/mp4", modelId);
3568
- parts.push({
3569
- type: "video",
3570
- uri: await uploadOne("video", media.bytes, media.mime),
3571
- mime_type: media.mime
3572
- });
3573
- }
3574
- if (req.firstFrame) {
3575
- const media = await resolveMedia(
3576
- req.firstFrame,
3577
- ctx.fetch,
3578
- ctx.signal,
3579
- "image/png",
3580
- modelId
3581
- );
3582
- if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3583
- parts.push({
3584
- type: "image",
3585
- uri: await uploadOne("image", media.bytes, media.mime),
3586
- mime_type: media.mime
3587
- });
3588
- } else {
3589
- parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3590
- }
3591
- }
3592
- for (const ref of images) {
3593
- const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "image/png", modelId);
3594
- if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3595
- parts.push({
3596
- type: "image",
3597
- uri: await uploadOne("image", media.bytes, media.mime),
3598
- mime_type: media.mime
3599
- });
3600
- } else {
3601
- parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3602
- }
3603
- }
3604
- parts.push({ type: "text", text: req.prompt });
3605
3607
  const cleanupUploads = async () => {
3606
3608
  for (const fileName of uploadedNames) await deleteGoogleFile(fileName, apiKey, ctx.fetch);
3607
3609
  };
@@ -3615,6 +3617,60 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
3615
3617
  if (ctx.signal !== void 0) createRetryOptions.signal = ctx.signal;
3616
3618
  let interaction;
3617
3619
  try {
3620
+ const parts = [];
3621
+ if (req.sourceVideo) {
3622
+ const media = await resolveMedia(
3623
+ req.sourceVideo,
3624
+ ctx.fetch,
3625
+ ctx.signal,
3626
+ "video/mp4",
3627
+ modelId
3628
+ );
3629
+ parts.push({
3630
+ type: "video",
3631
+ uri: await uploadOne("video", media.bytes, media.mime),
3632
+ mime_type: media.mime
3633
+ });
3634
+ }
3635
+ for (const ref of videos) {
3636
+ const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "video/mp4", modelId);
3637
+ parts.push({
3638
+ type: "video",
3639
+ uri: await uploadOne("video", media.bytes, media.mime),
3640
+ mime_type: media.mime
3641
+ });
3642
+ }
3643
+ if (req.firstFrame) {
3644
+ const media = await resolveMedia(
3645
+ req.firstFrame,
3646
+ ctx.fetch,
3647
+ ctx.signal,
3648
+ "image/png",
3649
+ modelId
3650
+ );
3651
+ if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3652
+ parts.push({
3653
+ type: "image",
3654
+ uri: await uploadOne("image", media.bytes, media.mime),
3655
+ mime_type: media.mime
3656
+ });
3657
+ } else {
3658
+ parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3659
+ }
3660
+ }
3661
+ for (const ref of images) {
3662
+ const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "image/png", modelId);
3663
+ if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3664
+ parts.push({
3665
+ type: "image",
3666
+ uri: await uploadOne("image", media.bytes, media.mime),
3667
+ mime_type: media.mime
3668
+ });
3669
+ } else {
3670
+ parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3671
+ }
3672
+ }
3673
+ parts.push({ type: "text", text: req.prompt });
3618
3674
  try {
3619
3675
  interaction = await withRetry(async () => {
3620
3676
  try {
@@ -4680,7 +4736,8 @@ var RATIO_TO_DIMS = {
4680
4736
  "9:16": { width: 768, height: 1360 },
4681
4737
  "3:4": { width: 768, height: 1024 },
4682
4738
  "2:3": { width: 768, height: 1152 },
4683
- "4:5": { width: 896, height: 1120 }
4739
+ "4:5": { width: 896, height: 1120 },
4740
+ "5:4": { width: 1120, height: 896 }
4684
4741
  };
4685
4742
  function dimsFor(aspectRatio) {
4686
4743
  if (aspectRatio !== void 0) {
@@ -4689,6 +4746,27 @@ function dimsFor(aspectRatio) {
4689
4746
  }
4690
4747
  return { width: 1024, height: 1024 };
4691
4748
  }
4749
+ var GEMINI_JOB_RATIOS = /* @__PURE__ */ new Set([
4750
+ "1:1",
4751
+ "2:3",
4752
+ "3:2",
4753
+ "3:4",
4754
+ "4:3",
4755
+ "4:5",
4756
+ "5:4",
4757
+ "9:16",
4758
+ "16:9",
4759
+ "21:9"
4760
+ ]);
4761
+ var NANO_BANANA_RATIOS = /* @__PURE__ */ new Set([
4762
+ "1:1",
4763
+ "2:3",
4764
+ "3:4",
4765
+ "4:3",
4766
+ "9:16",
4767
+ "16:9",
4768
+ "21:9"
4769
+ ]);
4692
4770
  async function refToBytes(ctx, ref) {
4693
4771
  if ("bytes" in ref) return { bytes: ref.bytes, mime: ref.mime };
4694
4772
  const res = await ctx.fetch(ref.url);
@@ -4788,7 +4866,7 @@ async function generateProdiaImage(req, ctx) {
4788
4866
  inputs = refs.length > 0 && firstRef !== void 0 ? [firstRef] : [];
4789
4867
  accept = req.outputFormat === "png" ? "image/png" : "image/jpeg";
4790
4868
  mime = accept;
4791
- } else if (providerModelId === "flux-2-pro" || providerModelId === FLUX_2_PRO_JOB || providerModelId === "inference.gemini-3-pro.img2img.v1" || providerModelId === "inference.nano-banana.img2img.v2") {
4869
+ } else if (providerModelId === "flux-2-pro" || providerModelId === FLUX_2_PRO_JOB) {
4792
4870
  const dims = dimsFor(req.aspectRatio);
4793
4871
  const config = {
4794
4872
  prompt: req.prompt,
@@ -4797,8 +4875,30 @@ async function generateProdiaImage(req, ctx) {
4797
4875
  safety_tolerance: 5
4798
4876
  };
4799
4877
  if (req.seed !== void 0) config.seed = req.seed;
4800
- const jobType = providerModelId === "flux-2-pro" ? FLUX_2_PRO_JOB : providerModelId;
4801
- job = { type: jobType, config };
4878
+ job = { type: FLUX_2_PRO_JOB, config };
4879
+ inputs = refs;
4880
+ accept = "image/png";
4881
+ mime = "image/png";
4882
+ } else if (providerModelId === "inference.gemini-3-pro.img2img.v1" || providerModelId === "inference.nano-banana.img2img.v2") {
4883
+ const allowed = providerModelId === "inference.nano-banana.img2img.v2" ? NANO_BANANA_RATIOS : GEMINI_JOB_RATIOS;
4884
+ const config = {
4885
+ prompt: req.prompt,
4886
+ safety_tolerance: 5
4887
+ };
4888
+ if (req.aspectRatio !== void 0 && req.aspectRatio !== "adaptive") {
4889
+ if (allowed.has(req.aspectRatio)) config.aspect_ratio = req.aspectRatio;
4890
+ else
4891
+ ctx.warnings.push({
4892
+ code: "option_ignored",
4893
+ field: "aspectRatio",
4894
+ from: req.aspectRatio,
4895
+ message: `${providerModelId} has no ${req.aspectRatio} aspect_ratio; using the provider default`
4896
+ });
4897
+ }
4898
+ if (providerModelId === "inference.gemini-3-pro.img2img.v1")
4899
+ config.resolution = req.size ?? "1K";
4900
+ if (req.seed !== void 0) config.seed = req.seed;
4901
+ job = { type: providerModelId, config };
4802
4902
  inputs = refs;
4803
4903
  accept = "image/png";
4804
4904
  mime = "image/png";
@@ -5072,6 +5172,50 @@ function isUpscaler(providerModelId) {
5072
5172
  function isQwen(providerModelId) {
5073
5173
  return isLower(providerModelId).includes("qwen");
5074
5174
  }
5175
+ function isWanImage(providerModelId) {
5176
+ return isLower(providerModelId).includes("wan-2.7");
5177
+ }
5178
+ function isQwenEditPlus(providerModelId) {
5179
+ return isLower(providerModelId).includes("edit");
5180
+ }
5181
+ var QWEN_IMAGE_RATIOS = /* @__PURE__ */ new Set([
5182
+ "1:1",
5183
+ "16:9",
5184
+ "9:16",
5185
+ "4:3",
5186
+ "3:4",
5187
+ "3:2",
5188
+ "2:3"
5189
+ ]);
5190
+ var QWEN_EDIT_RATIOS = /* @__PURE__ */ new Set(["1:1", "16:9", "9:16", "4:3", "3:4"]);
5191
+ var WAN_IMAGE_SIZES = {
5192
+ "1K": {
5193
+ "1:1": "1024*1024",
5194
+ "16:9": "1280*720",
5195
+ "9:16": "720*1280",
5196
+ "4:3": "1024*768",
5197
+ "3:4": "768*1024"
5198
+ },
5199
+ "2K": {
5200
+ "1:1": "2048*2048",
5201
+ "16:9": "2048*1152",
5202
+ "9:16": "1152*2048",
5203
+ "4:3": "2048*1536",
5204
+ "3:4": "1536*2048"
5205
+ },
5206
+ "4K": {
5207
+ "1:1": "4096*4096",
5208
+ "16:9": "4096*2304",
5209
+ "9:16": "2304*4096",
5210
+ "4:3": "4096*3072",
5211
+ "3:4": "3072*4096"
5212
+ }
5213
+ };
5214
+ function wanImageSize(aspectRatio, size) {
5215
+ const tier = size === "2K" || size === "4K" ? size : "1K";
5216
+ const table = WAN_IMAGE_SIZES[tier];
5217
+ return table[aspectRatio ?? ""] ?? table["1:1"] ?? "1024*1024";
5218
+ }
5075
5219
  async function generateReplicateImage(req, ctx, deps) {
5076
5220
  const id = ctx.model.providerModelId;
5077
5221
  const input = { prompt: req.prompt };
@@ -5091,14 +5235,31 @@ async function generateReplicateImage(req, ctx, deps) {
5091
5235
  input.creativity = 0;
5092
5236
  input.scale_factor = 2;
5093
5237
  input.output_format = "png";
5238
+ } else if (isWanImage(id)) {
5239
+ const refs = (req.references ?? []).map(refToUrl3);
5240
+ if (refs.length > 0) input.images = refs;
5241
+ input.size = wanImageSize(req.aspectRatio, req.size);
5094
5242
  } else if (isQwen(id)) {
5095
5243
  const refs = (req.references ?? []).map(refToUrl3);
5096
5244
  const firstUrl = refs[0];
5097
5245
  if (firstUrl !== void 0) {
5098
5246
  input.image = firstUrl;
5099
- input.match_input_image = true;
5247
+ if (!isQwenEditPlus(id)) input.match_input_image = true;
5248
+ }
5249
+ const ratios = isQwenEditPlus(id) ? QWEN_EDIT_RATIOS : QWEN_IMAGE_RATIOS;
5250
+ if (req.aspectRatio !== void 0) {
5251
+ if (ratios.has(req.aspectRatio)) input.aspect_ratio = req.aspectRatio;
5252
+ else
5253
+ ctx.warnings.push({
5254
+ code: "option_ignored",
5255
+ field: "aspectRatio",
5256
+ from: req.aspectRatio,
5257
+ message: `${id} has no ${req.aspectRatio} aspect_ratio; using the provider default`
5258
+ });
5259
+ }
5260
+ if (isQwenEditPlus(id) && req.outputFormat !== void 0) {
5261
+ input.output_format = req.outputFormat === "jpeg" ? "jpg" : req.outputFormat;
5100
5262
  }
5101
- if (req.outputFormat !== void 0) input.output_format = req.outputFormat;
5102
5263
  } else {
5103
5264
  const refs = req.references ?? [];
5104
5265
  const first = refs[0];