@shop-os/modelkit 0.1.2 → 0.1.4

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
@@ -83,7 +83,18 @@ var Catalog = class _Catalog {
83
83
 
84
84
  // src/catalog/image.ts
85
85
  var IMAGE_USAGE = { unit: "images", modality: "IMAGE" };
86
- var GEMINI_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "4:5", "5:4"];
86
+ var GEMINI_RATIOS = [
87
+ "1:1",
88
+ "2:3",
89
+ "3:2",
90
+ "16:9",
91
+ "9:16",
92
+ "4:3",
93
+ "3:4",
94
+ "21:9",
95
+ "4:5",
96
+ "5:4"
97
+ ];
87
98
  function geminiImage(id, label, aliases, sizes) {
88
99
  const model = {
89
100
  id,
@@ -106,7 +117,8 @@ var IMAGE_MODELS = [
106
117
  "gemini-3.1-flash-image-preview",
107
118
  "Nano Banana Flash",
108
119
  ["nb-2"],
109
- ["1K", "2K", "4K"]
120
+ // Flash 3.1 is the only Gemini image model with the 512 tier.
121
+ ["512", "1K", "2K", "4K"]
110
122
  ),
111
123
  geminiImage("gemini-3.1-flash-lite-image", "Nano Banana Lite", ["nb-lite"], ["1K"]),
112
124
  // trigger-workflows validates 1K only; pricing seeds 2K/4K rows so allow all three.
@@ -210,7 +222,10 @@ var IMAGE_MODELS = [
210
222
  constraints: {
211
223
  aspectRatios: ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "21:9", "4:5", "5:4"],
212
224
  sizes: ["1K", "2K"],
213
- references: { images: 10 }
225
+ references: { images: 10 },
226
+ // Ark's Images API has no output-format, quality or count field; only
227
+ // the seed is forwarded, so anything else must warn, not vanish.
228
+ supports: ["seed"]
214
229
  },
215
230
  defaults: { size: "1K", aspectRatio: "1:1" },
216
231
  usage: { ...IMAGE_USAGE },
@@ -225,21 +240,12 @@ var IMAGE_MODELS = [
225
240
  providerModelId: "seedream-4-5-251128",
226
241
  lifecycle: "active",
227
242
  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
- ],
243
+ // 97:30 has no Ark size spelling; the A+ flow pads inputs to 97:30 and
244
+ // omits the ratio instead. Advertise only sizes the adapter can send.
245
+ aspectRatios: ["1:1", "2:3", "3:2", "4:3", "3:4", "4:5", "5:4", "9:16", "16:9", "21:9"],
241
246
  sizes: ["1K", "2K"],
242
- references: { images: 10 }
247
+ references: { images: 10 },
248
+ supports: ["seed"]
243
249
  },
244
250
  defaults: { size: "1K", aspectRatio: "1:1" },
245
251
  usage: { ...IMAGE_USAGE },
@@ -263,6 +269,9 @@ var IMAGE_MODELS = [
263
269
  pricingKind: "image_size"
264
270
  },
265
271
  {
272
+ // Prodia's gemini-3-pro jobs take aspect_ratio + resolution (1K/2K/4K), not
273
+ // width/height; 97:30 is outside its aspect_ratio enum.
274
+ // https://docs.prodia.com/models/gemini-3/
266
275
  id: "prodia-nano-banana-pro",
267
276
  aliases: [],
268
277
  label: "Nano Banana Pro via Prodia",
@@ -271,19 +280,7 @@ var IMAGE_MODELS = [
271
280
  providerModelId: "inference.gemini-3-pro.img2img.v1",
272
281
  lifecycle: "active",
273
282
  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
- ],
283
+ aspectRatios: ["1:1", "2:3", "3:2", "4:3", "3:4", "4:5", "5:4", "9:16", "16:9", "21:9"],
287
284
  sizes: ["1K", "2K", "4K"],
288
285
  requires: ["references"]
289
286
  },
@@ -292,6 +289,8 @@ var IMAGE_MODELS = [
292
289
  pricingKind: "image_size"
293
290
  },
294
291
  {
292
+ // Nano Banana (img2img.v2) is flat-rate with aspect_ratio only; 3:2, 4:5,
293
+ // 5:4 and 97:30 are not in its enum. https://docs.prodia.com/models/nano-banana/
295
294
  id: "prodia-nano-banana",
296
295
  aliases: [],
297
296
  label: "Nano Banana via Prodia",
@@ -300,19 +299,7 @@ var IMAGE_MODELS = [
300
299
  providerModelId: "inference.nano-banana.img2img.v2",
301
300
  lifecycle: "active",
302
301
  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
- ],
302
+ aspectRatios: ["1:1", "2:3", "3:4", "4:3", "9:16", "16:9", "21:9"],
316
303
  sizes: ["1K"],
317
304
  requires: ["references"]
318
305
  },
@@ -321,6 +308,9 @@ var IMAGE_MODELS = [
321
308
  pricingKind: "image_size"
322
309
  },
323
310
  {
311
+ // WAN 2.7 takes `images` (up to 9) and a `size` enum whose exact values
312
+ // cover 1:1/16:9/9:16/4:3/3:4 only; other ratios have no spelling.
313
+ // https://replicate.com/wan-video/wan-2.7-image-pro (input schema)
324
314
  id: "wan-2.7-image-pro",
325
315
  aliases: [],
326
316
  label: "WAN 2.7 Image Pro",
@@ -329,19 +319,7 @@ var IMAGE_MODELS = [
329
319
  providerModelId: "wan-video/wan-2.7-image-pro",
330
320
  lifecycle: "active",
331
321
  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
- ],
322
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4"],
345
323
  sizes: ["1K", "2K", "4K"],
346
324
  references: { images: 9 }
347
325
  },
@@ -350,6 +328,9 @@ var IMAGE_MODELS = [
350
328
  pricingKind: "image_size"
351
329
  },
352
330
  {
331
+ // Qwen Image 2 Pro has aspect_ratio but no size input; resolution follows
332
+ // the reference image (match_input_image) or the provider default.
333
+ // https://replicate.com/qwen/qwen-image-2-pro (input schema)
353
334
  id: "qwen-image-2-pro",
354
335
  aliases: [],
355
336
  label: "Qwen Image 2 Pro",
@@ -358,28 +339,19 @@ var IMAGE_MODELS = [
358
339
  providerModelId: "qwen/qwen-image-2-pro",
359
340
  lifecycle: "active",
360
341
  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"],
342
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"],
343
+ sizes: [],
375
344
  references: { images: 1 },
376
345
  requires: ["references"]
377
346
  },
378
- defaults: { size: "1K", aspectRatio: "1:1" },
347
+ defaults: { aspectRatio: "1:1" },
379
348
  usage: { unit: "compute_seconds", modality: "IMAGE" },
380
349
  pricingKind: "image_size"
381
350
  },
382
351
  {
352
+ // Qwen Image Edit Plus takes aspect_ratio (or match_input_image) and
353
+ // output_format, but no size; no default ratio so edits keep following
354
+ // the input image. https://replicate.com/qwen-edit-apps/qwen-image-edit-plus-lora-skin
383
355
  id: "qwen-image-edit-plus-lora-skin",
384
356
  aliases: [],
385
357
  label: "Qwen Image Edit Plus",
@@ -388,25 +360,13 @@ var IMAGE_MODELS = [
388
360
  providerModelId: "qwen-edit-apps/qwen-image-edit-plus-lora-skin",
389
361
  lifecycle: "active",
390
362
  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"],
363
+ aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4"],
364
+ sizes: [],
405
365
  references: { images: 1 },
406
366
  requires: ["references"],
407
367
  supports: ["outputFormat"]
408
368
  },
409
- defaults: { size: "1K", aspectRatio: "1:1", outputFormat: "png" },
369
+ defaults: { outputFormat: "png" },
410
370
  usage: { unit: "compute_seconds", modality: "IMAGE" },
411
371
  pricingKind: "image_size"
412
372
  }
@@ -503,8 +463,12 @@ var OTHER_MODELS = [
503
463
  adapter: "replicate",
504
464
  providerModelId: "topazlabs/image-upscale",
505
465
  lifecycle: "active",
506
- constraints: {},
507
- defaults: {},
466
+ constraints: {
467
+ references: { images: 1 },
468
+ requires: ["references"],
469
+ supports: ["scaleFactor", "outputFormat"]
470
+ },
471
+ defaults: { scaleFactor: 2, outputFormat: "png" },
508
472
  usage: { unit: "compute_seconds", modality: "IMAGE" },
509
473
  pricingKind: "flat"
510
474
  },
@@ -560,7 +524,7 @@ var TEXT_MODELS = [
560
524
  text("gpt-5.6-sol", "GPT-5.6 Sol", "gpt-5.6-sol"),
561
525
  // Library key "claude-sonnet-4.5" maps to the SDK id "claude-sonnet-4-5".
562
526
  text("claude-sonnet-4-5", "Claude Sonnet 4.5", "claude-sonnet-4-5", {
563
- aliases: ["claude-sonnet-4.5"]
527
+ aliases: ["claude-sonnet-4.5", "claude-sonnet-4-5-20250929"]
564
528
  }),
565
529
  // Anthropic retired the dated 4.0 ids; callers that still send them get 4.6.
566
530
  text("claude-sonnet-4-6", "Claude Sonnet 4.6", "claude-sonnet-4-6", {
@@ -576,7 +540,7 @@ var TEXT_MODELS = [
576
540
  aliases: ["claude-opus-4.8", "claude-opus-4", "claude-opus-4.0", "claude-opus-4-20250514"]
577
541
  }),
578
542
  text("claude-haiku-4-5", "Claude Haiku 4.5", "claude-haiku-4-5", {
579
- aliases: ["claude-haiku-4.5", "claude-haiku-4-5-20251001"]
543
+ aliases: ["claude-haiku-4.5", "claude-haiku-4-5-20251001", "claude-haiku-4"]
580
544
  }),
581
545
  text("claude-opus-5", "Claude Opus 5", "claude-opus-5", {
582
546
  aliases: ["claude-opus-5.0"]
@@ -1059,7 +1023,7 @@ var MODELS = [
1059
1023
  ];
1060
1024
 
1061
1025
  // src/resolve-options.ts
1062
- var SIZE_ORDER = ["1K", "2K", "4K"];
1026
+ var SIZE_ORDER = ["512", "1K", "2K", "4K"];
1063
1027
  var RESOLUTION_ORDER = ["360p", "480p", "720p", "768p", "1080p", "2k", "4k"];
1064
1028
  function ratioValue(r) {
1065
1029
  if (r === "adaptive") return 1;
@@ -1191,7 +1155,13 @@ function resolveImageOptions(model, request, onUnsupported = "error") {
1191
1155
  model: model.id
1192
1156
  });
1193
1157
  }
1194
- dropUnsupported(merged, model, warnings, ["seed", "quality", "count", "outputFormat"]);
1158
+ dropUnsupported(merged, model, warnings, [
1159
+ "seed",
1160
+ "quality",
1161
+ "count",
1162
+ "outputFormat",
1163
+ "scaleFactor"
1164
+ ]);
1195
1165
  return { request: merged, warnings };
1196
1166
  }
1197
1167
  function resolveVideoOptions(model, request, onUnsupported = "error") {
@@ -1823,7 +1793,11 @@ var aiSdkTextAdapter = {
1823
1793
  function requiredKeys(model, config) {
1824
1794
  if (model.adapter === "fal" && config.routing?.fal === "cloudflare-passthrough")
1825
1795
  return ["cloudflare"];
1826
- if (model.adapter === "ai-sdk-text") return [textKeyName(model, config.routing ?? {})];
1796
+ if (model.adapter === "ai-sdk-text") {
1797
+ const names = [textKeyName(model, config.routing ?? {})];
1798
+ if (config.routing?.text === "cloudflare") names.push("cloudflare");
1799
+ return names;
1800
+ }
1827
1801
  return ADAPTER_KEYS[model.adapter];
1828
1802
  }
1829
1803
  var IMAGE_OUTPUT_TYPES = /* @__PURE__ */ new Set(["image", "upscale", "bg-remove"]);
@@ -1881,6 +1855,20 @@ function sdkAspectRatio(ratio) {
1881
1855
  const parsed = sdkRatio.safeParse(ratio);
1882
1856
  return parsed.success ? parsed.data : void 0;
1883
1857
  }
1858
+ function sdkDataContent(ref) {
1859
+ return "url" in ref ? ref.url : ref.bytes;
1860
+ }
1861
+ function sdkReference(ref) {
1862
+ return "url" in ref ? ref.url : { data: ref.bytes, mediaType: ref.mime };
1863
+ }
1864
+ function pushIgnored(warnings, field, from, modelId) {
1865
+ warnings.push({
1866
+ code: "option_ignored",
1867
+ field,
1868
+ from,
1869
+ message: `${modelId} provider instance does not support ${field}; ignored`
1870
+ });
1871
+ }
1884
1872
  function createModelKit(config, adapters) {
1885
1873
  const catalog = new Catalog(MODELS).withOverrides(config.catalogOverride ?? []);
1886
1874
  const logger = config.logger ?? console;
@@ -1903,6 +1891,7 @@ function createModelKit(config, adapters) {
1903
1891
  call,
1904
1892
  warnings
1905
1893
  };
1894
+ if (config.endpoints) ctx.endpoints = config.endpoints;
1906
1895
  if (call.signal) ctx.signal = call.signal;
1907
1896
  return { adapter, ctx };
1908
1897
  }
@@ -1942,16 +1931,45 @@ function createModelKit(config, adapters) {
1942
1931
  };
1943
1932
  }
1944
1933
  async function passthroughImage(req, call) {
1945
- const { model, prompt, n, count, size, aspectRatio, seed, providerOptions } = req;
1934
+ const {
1935
+ model,
1936
+ prompt,
1937
+ n,
1938
+ count,
1939
+ size,
1940
+ aspectRatio,
1941
+ seed,
1942
+ providerOptions,
1943
+ references,
1944
+ exactSize: exactSizeField,
1945
+ scaleFactor,
1946
+ quality: _quality,
1947
+ outputFormat: _outputFormat
1948
+ } = req;
1946
1949
  const options = { model, prompt };
1950
+ const warnings = [];
1947
1951
  const quantity = n ?? count;
1948
1952
  if (quantity !== void 0) options.n = quantity;
1949
- const exact = exactSize.safeParse(size);
1950
- if (exact.success) options.size = exact.data;
1953
+ if (references !== void 0 && references.length > 0)
1954
+ options.prompt = { images: references.map(sdkDataContent), text: prompt };
1955
+ const exact = exactSizeField === void 0 ? void 0 : exactSize.safeParse(exactSizeField);
1956
+ if (exact?.success) options.size = exact.data;
1957
+ else if (exactSizeField !== void 0)
1958
+ pushIgnored(warnings, "exactSize", exactSizeField, model.modelId);
1959
+ const requested = size === void 0 ? void 0 : exactSize.safeParse(size);
1960
+ if (requested?.success) options.size ??= requested.data;
1961
+ else if (size !== void 0) pushIgnored(warnings, "size", size, model.modelId);
1951
1962
  const ratio = sdkAspectRatio(aspectRatio);
1952
1963
  if (ratio) options.aspectRatio = ratio;
1964
+ else if (aspectRatio !== void 0)
1965
+ pushIgnored(warnings, "aspectRatio", aspectRatio, model.modelId);
1953
1966
  if (seed !== void 0) options.seed = seed;
1954
1967
  if (providerOptions) options.providerOptions = providerOptions;
1968
+ if (_quality !== void 0) pushIgnored(warnings, "quality", _quality, model.modelId);
1969
+ if (_outputFormat !== void 0)
1970
+ pushIgnored(warnings, "outputFormat", _outputFormat, model.modelId);
1971
+ if (scaleFactor !== void 0)
1972
+ pushIgnored(warnings, "scaleFactor", scaleFactor, model.modelId);
1955
1973
  const abortSignal = combineSignals(req.abortSignal, call.signal);
1956
1974
  if (abortSignal) options.abortSignal = abortSignal;
1957
1975
  const result = await aiGenerateImage(options);
@@ -1966,17 +1984,73 @@ function createModelKit(config, adapters) {
1966
1984
  quantity: images.length
1967
1985
  };
1968
1986
  await emit(usage2);
1969
- return { image: images[0], images, usage: usage2, raw: result, warnings: [] };
1987
+ return { image: images[0], images, usage: usage2, raw: result, warnings };
1970
1988
  }
1971
1989
  function passthroughVideo(req, call) {
1972
- const { model, prompt, aspectRatio, duration, durationSeconds, seed, providerOptions } = req;
1990
+ const {
1991
+ model,
1992
+ prompt,
1993
+ aspectRatio,
1994
+ duration,
1995
+ durationSeconds,
1996
+ seed,
1997
+ providerOptions,
1998
+ firstFrame,
1999
+ lastFrame,
2000
+ sourceVideo,
2001
+ referenceImages,
2002
+ referenceVideos,
2003
+ referenceAudios,
2004
+ resolution,
2005
+ generateAudio: generateAudio2,
2006
+ negativePrompt: _negativePrompt,
2007
+ cfgScale: _cfgScale,
2008
+ promptExpansion: _promptExpansion,
2009
+ mode: _mode
2010
+ } = req;
1973
2011
  const seconds = duration ?? durationSeconds;
1974
2012
  const abortSignal = combineSignals(req.abortSignal, call.signal);
1975
2013
  const options = { model, prompt };
1976
- const ratio = sdkAspectRatio(aspectRatio);
2014
+ const warnings = [];
2015
+ const ratio = sdkAspectRatio(aspectRatio) ?? (aspectRatio === "adaptive" ? "adaptive" : void 0);
1977
2016
  if (ratio) options.aspectRatio = ratio;
2017
+ else if (aspectRatio !== void 0)
2018
+ pushIgnored(warnings, "aspectRatio", aspectRatio, model.modelId);
1978
2019
  if (seconds !== void 0) options.duration = seconds;
1979
2020
  if (seed !== void 0) options.seed = seed;
2021
+ if (generateAudio2 !== void 0) options.generateAudio = generateAudio2;
2022
+ const frames = [
2023
+ ...firstFrame ? [{ image: sdkDataContent(firstFrame), frameType: "first_frame" }] : [],
2024
+ ...lastFrame ? [{ image: sdkDataContent(lastFrame), frameType: "last_frame" }] : []
2025
+ ];
2026
+ if (frames.length > 0) options.frameImages = frames;
2027
+ const references = [
2028
+ ...(referenceImages ?? []).map(sdkDataContent),
2029
+ ...(referenceVideos ?? []).map(sdkReference)
2030
+ ];
2031
+ if (references.length > 0) {
2032
+ if (frames.length > 0) {
2033
+ warnings.push({
2034
+ code: "option_ignored",
2035
+ field: "inputReferences",
2036
+ from: references.length,
2037
+ message: `${model.modelId} provider instance ignores references when frames are set`
2038
+ });
2039
+ } else {
2040
+ options.inputReferences = references;
2041
+ }
2042
+ }
2043
+ if (sourceVideo) pushIgnored(warnings, "sourceVideo", void 0, model.modelId);
2044
+ if (referenceAudios?.length)
2045
+ pushIgnored(warnings, "referenceAudios", referenceAudios.length, model.modelId);
2046
+ if (resolution !== void 0)
2047
+ pushIgnored(warnings, "resolution", resolution, model.modelId);
2048
+ if (_negativePrompt !== void 0)
2049
+ pushIgnored(warnings, "negativePrompt", void 0, model.modelId);
2050
+ if (_cfgScale !== void 0) pushIgnored(warnings, "cfgScale", _cfgScale, model.modelId);
2051
+ if (_promptExpansion !== void 0)
2052
+ pushIgnored(warnings, "promptExpansion", _promptExpansion, model.modelId);
2053
+ if (_mode !== void 0) pushIgnored(warnings, "mode", _mode, model.modelId);
1980
2054
  if (providerOptions) options.providerOptions = providerOptions;
1981
2055
  if (abortSignal) options.abortSignal = abortSignal;
1982
2056
  const pending = aiGenerateVideo(options);
@@ -2004,7 +2078,7 @@ function createModelKit(config, adapters) {
2004
2078
  });
2005
2079
  return {
2006
2080
  jobId: `ai-sdk:${model.provider}:${model.modelId}`,
2007
- warnings: [],
2081
+ warnings,
2008
2082
  async poll() {
2009
2083
  if (settled) return { status: "succeeded", raw: settled.raw };
2010
2084
  if (failed) return { status: "failed" };
@@ -2228,6 +2302,9 @@ var SUBMIT_PATH = "/contents/generations/tasks";
2228
2302
  var SEEDANCE_POLL_INTERVAL_MS = 15e3;
2229
2303
  var RESULT_TIMEOUT_MS = 15 * 60 * 1e3;
2230
2304
  var VIDEO_URL_TTL_MS = 24 * 60 * 60 * 1e3;
2305
+ function arkBaseUrl(ctx) {
2306
+ return ctx.endpoints?.ark ?? ARK_BASE_URL;
2307
+ }
2231
2308
  var SEEDREAM_SIZES = {
2232
2309
  "1K": {
2233
2310
  "1:1": "1024x1024",
@@ -2238,7 +2315,8 @@ var SEEDREAM_SIZES = {
2238
2315
  "3:2": "1248x832",
2239
2316
  "2:3": "832x1248",
2240
2317
  "21:9": "1568x672",
2241
- "4:5": "864x1080"
2318
+ "4:5": "864x1080",
2319
+ "5:4": "1080x864"
2242
2320
  },
2243
2321
  "2K": {
2244
2322
  "1:1": "2048x2048",
@@ -2249,7 +2327,8 @@ var SEEDREAM_SIZES = {
2249
2327
  "3:2": "2496x1664",
2250
2328
  "2:3": "1664x2496",
2251
2329
  "21:9": "3136x1344",
2252
- "4:5": "1728x2160"
2330
+ "4:5": "1728x2160",
2331
+ "5:4": "2160x1728"
2253
2332
  }
2254
2333
  };
2255
2334
  var arkAdapter = {
@@ -2296,9 +2375,11 @@ function seedreamSize(aspect, size) {
2296
2375
  const table = SEEDREAM_SIZES[tier];
2297
2376
  return table[aspect ?? ""] ?? table["1:1"] ?? "2048x2048";
2298
2377
  }
2299
- function outputMime(format) {
2300
- if (format === "png") return "image/png";
2301
- if (format === "webp") return "image/webp";
2378
+ function sniffedImageMime(bytes) {
2379
+ if (bytes.length >= 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71)
2380
+ return "image/png";
2381
+ 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)
2382
+ return "image/webp";
2302
2383
  return "image/jpeg";
2303
2384
  }
2304
2385
  function arkUrl(ref) {
@@ -2340,7 +2421,7 @@ async function generateSeedreamImage(req, ctx) {
2340
2421
  else if (refs.length > 1) body.image = refs;
2341
2422
  const data = await withRetry(async () => {
2342
2423
  const res = await ctx.fetch(
2343
- `${ARK_BASE_URL}/images/generations`,
2424
+ `${arkBaseUrl(ctx)}/images/generations`,
2344
2425
  fetchInitFor(ctx, {
2345
2426
  method: "POST",
2346
2427
  headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
@@ -2379,7 +2460,7 @@ async function generateSeedreamImage(req, ctx) {
2379
2460
  };
2380
2461
  if (req.size !== void 0) usage2.imageSize = req.size;
2381
2462
  if (req.quality !== void 0) usage2.imageQuality = req.quality;
2382
- return { images: [{ bytes, mime: outputMime(req.outputFormat) }], usage: usage2, raw: data };
2463
+ return { images: [{ bytes, mime: sniffedImageMime(bytes) }], usage: usage2, raw: data };
2383
2464
  }
2384
2465
  var SeedanceResponseSchema = z4.object({
2385
2466
  id: z4.string().optional(),
@@ -2467,7 +2548,7 @@ async function generateSeedanceVideo(req, ctx) {
2467
2548
  if (req.seed !== void 0) body.seed = req.seed;
2468
2549
  const submit = await withRetry(async () => {
2469
2550
  const res = await ctx.fetch(
2470
- `${ARK_BASE_URL}${SUBMIT_PATH}`,
2551
+ `${arkBaseUrl(ctx)}${SUBMIT_PATH}`,
2471
2552
  fetchInitFor(ctx, {
2472
2553
  method: "POST",
2473
2554
  headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
@@ -2487,7 +2568,7 @@ async function generateSeedanceVideo(req, ctx) {
2487
2568
  const usage2 = seedanceUsage(ctx, req, duration);
2488
2569
  const fetchStatus = async () => {
2489
2570
  const res = await ctx.fetch(
2490
- `${ARK_BASE_URL}${SUBMIT_PATH}/${jobId}`,
2571
+ `${arkBaseUrl(ctx)}${SUBMIT_PATH}/${jobId}`,
2491
2572
  fetchInitFor(ctx, {
2492
2573
  headers: { Authorization: `Bearer ${key}` }
2493
2574
  })
@@ -3548,60 +3629,6 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
3548
3629
  uploadedNames.push(file.fileName);
3549
3630
  return file.fileUri;
3550
3631
  };
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
3632
  const cleanupUploads = async () => {
3606
3633
  for (const fileName of uploadedNames) await deleteGoogleFile(fileName, apiKey, ctx.fetch);
3607
3634
  };
@@ -3615,6 +3642,60 @@ async function generateOmniVideo(req, ctx, createGenAIClient) {
3615
3642
  if (ctx.signal !== void 0) createRetryOptions.signal = ctx.signal;
3616
3643
  let interaction;
3617
3644
  try {
3645
+ const parts = [];
3646
+ if (req.sourceVideo) {
3647
+ const media = await resolveMedia(
3648
+ req.sourceVideo,
3649
+ ctx.fetch,
3650
+ ctx.signal,
3651
+ "video/mp4",
3652
+ modelId
3653
+ );
3654
+ parts.push({
3655
+ type: "video",
3656
+ uri: await uploadOne("video", media.bytes, media.mime),
3657
+ mime_type: media.mime
3658
+ });
3659
+ }
3660
+ for (const ref of videos) {
3661
+ const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "video/mp4", modelId);
3662
+ parts.push({
3663
+ type: "video",
3664
+ uri: await uploadOne("video", media.bytes, media.mime),
3665
+ mime_type: media.mime
3666
+ });
3667
+ }
3668
+ if (req.firstFrame) {
3669
+ const media = await resolveMedia(
3670
+ req.firstFrame,
3671
+ ctx.fetch,
3672
+ ctx.signal,
3673
+ "image/png",
3674
+ modelId
3675
+ );
3676
+ if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3677
+ parts.push({
3678
+ type: "image",
3679
+ uri: await uploadOne("image", media.bytes, media.mime),
3680
+ mime_type: media.mime
3681
+ });
3682
+ } else {
3683
+ parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3684
+ }
3685
+ }
3686
+ for (const ref of images) {
3687
+ const media = await resolveMedia(ref, ctx.fetch, ctx.signal, "image/png", modelId);
3688
+ if (media.bytes.length > OMNI_INLINE_LIMIT_BYTES) {
3689
+ parts.push({
3690
+ type: "image",
3691
+ uri: await uploadOne("image", media.bytes, media.mime),
3692
+ mime_type: media.mime
3693
+ });
3694
+ } else {
3695
+ parts.push({ type: "image", data: toBase64(media.bytes), mime_type: media.mime });
3696
+ }
3697
+ }
3698
+ parts.push({ type: "text", text: req.prompt });
3618
3699
  try {
3619
3700
  interaction = await withRetry(async () => {
3620
3701
  try {
@@ -3948,6 +4029,10 @@ function createGoogleImageAdapter(deps = defaultGoogleImageDeps) {
3948
4029
  imageQuality: "auto"
3949
4030
  };
3950
4031
  if (req.size !== void 0) usage2.imageSize = req.size;
4032
+ if (result.usage?.inputTokens !== void 0)
4033
+ usage2.inputTokens = result.usage.inputTokens;
4034
+ if (result.usage?.outputTokens !== void 0)
4035
+ usage2.outputTokens = result.usage.outputTokens;
3951
4036
  return { images, usage: usage2, raw: result };
3952
4037
  }
3953
4038
  };
@@ -4479,7 +4564,7 @@ function aspectRatioToSize(ratio, tier) {
4479
4564
  };
4480
4565
  const MIN_PIXELS = 655360;
4481
4566
  const MAX_PIXELS = 8294400;
4482
- const maxLong = TIER_LONG_EDGE[tier];
4567
+ const maxLong = TIER_LONG_EDGE[tier] ?? 1024;
4483
4568
  const RATIO_MAP = /* @__PURE__ */ new Map([
4484
4569
  ["1:1", [1, 1]],
4485
4570
  ["16:9", [16, 9]],
@@ -4680,7 +4765,8 @@ var RATIO_TO_DIMS = {
4680
4765
  "9:16": { width: 768, height: 1360 },
4681
4766
  "3:4": { width: 768, height: 1024 },
4682
4767
  "2:3": { width: 768, height: 1152 },
4683
- "4:5": { width: 896, height: 1120 }
4768
+ "4:5": { width: 896, height: 1120 },
4769
+ "5:4": { width: 1120, height: 896 }
4684
4770
  };
4685
4771
  function dimsFor(aspectRatio) {
4686
4772
  if (aspectRatio !== void 0) {
@@ -4689,6 +4775,27 @@ function dimsFor(aspectRatio) {
4689
4775
  }
4690
4776
  return { width: 1024, height: 1024 };
4691
4777
  }
4778
+ var GEMINI_JOB_RATIOS = /* @__PURE__ */ new Set([
4779
+ "1:1",
4780
+ "2:3",
4781
+ "3:2",
4782
+ "3:4",
4783
+ "4:3",
4784
+ "4:5",
4785
+ "5:4",
4786
+ "9:16",
4787
+ "16:9",
4788
+ "21:9"
4789
+ ]);
4790
+ var NANO_BANANA_RATIOS = /* @__PURE__ */ new Set([
4791
+ "1:1",
4792
+ "2:3",
4793
+ "3:4",
4794
+ "4:3",
4795
+ "9:16",
4796
+ "16:9",
4797
+ "21:9"
4798
+ ]);
4692
4799
  async function refToBytes(ctx, ref) {
4693
4800
  if ("bytes" in ref) return { bytes: ref.bytes, mime: ref.mime };
4694
4801
  const res = await ctx.fetch(ref.url);
@@ -4788,7 +4895,7 @@ async function generateProdiaImage(req, ctx) {
4788
4895
  inputs = refs.length > 0 && firstRef !== void 0 ? [firstRef] : [];
4789
4896
  accept = req.outputFormat === "png" ? "image/png" : "image/jpeg";
4790
4897
  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") {
4898
+ } else if (providerModelId === "flux-2-pro" || providerModelId === FLUX_2_PRO_JOB) {
4792
4899
  const dims = dimsFor(req.aspectRatio);
4793
4900
  const config = {
4794
4901
  prompt: req.prompt,
@@ -4797,8 +4904,30 @@ async function generateProdiaImage(req, ctx) {
4797
4904
  safety_tolerance: 5
4798
4905
  };
4799
4906
  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 };
4907
+ job = { type: FLUX_2_PRO_JOB, config };
4908
+ inputs = refs;
4909
+ accept = "image/png";
4910
+ mime = "image/png";
4911
+ } else if (providerModelId === "inference.gemini-3-pro.img2img.v1" || providerModelId === "inference.nano-banana.img2img.v2") {
4912
+ const allowed = providerModelId === "inference.nano-banana.img2img.v2" ? NANO_BANANA_RATIOS : GEMINI_JOB_RATIOS;
4913
+ const config = {
4914
+ prompt: req.prompt,
4915
+ safety_tolerance: 5
4916
+ };
4917
+ if (req.aspectRatio !== void 0 && req.aspectRatio !== "adaptive") {
4918
+ if (allowed.has(req.aspectRatio)) config.aspect_ratio = req.aspectRatio;
4919
+ else
4920
+ ctx.warnings.push({
4921
+ code: "option_ignored",
4922
+ field: "aspectRatio",
4923
+ from: req.aspectRatio,
4924
+ message: `${providerModelId} has no ${req.aspectRatio} aspect_ratio; using the provider default`
4925
+ });
4926
+ }
4927
+ if (providerModelId === "inference.gemini-3-pro.img2img.v1")
4928
+ config.resolution = req.size ?? "1K";
4929
+ if (req.seed !== void 0) config.seed = req.seed;
4930
+ job = { type: providerModelId, config };
4802
4931
  inputs = refs;
4803
4932
  accept = "image/png";
4804
4933
  mime = "image/png";
@@ -5069,13 +5198,88 @@ function isUpscaler(providerModelId) {
5069
5198
  const id = isLower(providerModelId);
5070
5199
  return id.includes("upscal") || id.includes("crystal") || id.includes("topaz");
5071
5200
  }
5201
+ function isTopazUpscaler(providerModelId) {
5202
+ return isLower(providerModelId).includes("topazlabs/image-upscale");
5203
+ }
5072
5204
  function isQwen(providerModelId) {
5073
5205
  return isLower(providerModelId).includes("qwen");
5074
5206
  }
5207
+ function isWanImage(providerModelId) {
5208
+ return isLower(providerModelId).includes("wan-2.7");
5209
+ }
5210
+ function isQwenEditPlus(providerModelId) {
5211
+ return isLower(providerModelId).includes("edit");
5212
+ }
5213
+ var QWEN_IMAGE_RATIOS = /* @__PURE__ */ new Set([
5214
+ "1:1",
5215
+ "16:9",
5216
+ "9:16",
5217
+ "4:3",
5218
+ "3:4",
5219
+ "3:2",
5220
+ "2:3"
5221
+ ]);
5222
+ var QWEN_EDIT_RATIOS = /* @__PURE__ */ new Set(["1:1", "16:9", "9:16", "4:3", "3:4"]);
5223
+ var WAN_IMAGE_SIZES = {
5224
+ "1K": {
5225
+ "1:1": "1024*1024",
5226
+ "16:9": "1280*720",
5227
+ "9:16": "720*1280",
5228
+ "4:3": "1024*768",
5229
+ "3:4": "768*1024"
5230
+ },
5231
+ "2K": {
5232
+ "1:1": "2048*2048",
5233
+ "16:9": "2048*1152",
5234
+ "9:16": "1152*2048",
5235
+ "4:3": "2048*1536",
5236
+ "3:4": "1536*2048"
5237
+ },
5238
+ "4K": {
5239
+ "1:1": "4096*4096",
5240
+ "16:9": "4096*2304",
5241
+ "9:16": "2304*4096",
5242
+ "4:3": "4096*3072",
5243
+ "3:4": "3072*4096"
5244
+ }
5245
+ };
5246
+ function wanImageSize(aspectRatio, size) {
5247
+ const tier = size === "2K" || size === "4K" ? size : "1K";
5248
+ const table = WAN_IMAGE_SIZES[tier];
5249
+ return table[aspectRatio ?? ""] ?? table["1:1"] ?? "1024*1024";
5250
+ }
5075
5251
  async function generateReplicateImage(req, ctx, deps) {
5076
5252
  const id = ctx.model.providerModelId;
5077
- const input = { prompt: req.prompt };
5078
- if (isUpscaler(id)) {
5253
+ let input;
5254
+ if (isTopazUpscaler(id)) {
5255
+ const first = (req.references ?? [])[0];
5256
+ if (!first) {
5257
+ throw new ModelKitError({
5258
+ code: "missing_input",
5259
+ message: `${ctx.model.id} requires a reference image to upscale`,
5260
+ provider: "replicate",
5261
+ model: ctx.model.id,
5262
+ retryable: false
5263
+ });
5264
+ }
5265
+ let format = req.outputFormat === "jpeg" ? "jpg" : req.outputFormat ?? "png";
5266
+ if (format !== "png" && format !== "jpg") {
5267
+ ctx.warnings.push({
5268
+ code: "option_ignored",
5269
+ field: "outputFormat",
5270
+ from: format,
5271
+ message: "topazlabs/image-upscale supports only png and jpg; using png"
5272
+ });
5273
+ format = "png";
5274
+ }
5275
+ input = {
5276
+ image: refToUrl3(first),
5277
+ enhance_model: "Standard V2",
5278
+ output_format: format,
5279
+ upscale_factor: `${req.scaleFactor ?? 2}x`,
5280
+ face_enhancement: true
5281
+ };
5282
+ } else if (isUpscaler(id)) {
5079
5283
  const refs = req.references ?? [];
5080
5284
  const first = refs[0];
5081
5285
  if (!first) {
@@ -5087,28 +5291,53 @@ async function generateReplicateImage(req, ctx, deps) {
5087
5291
  retryable: false
5088
5292
  });
5089
5293
  }
5090
- input.image = refToUrl3(first);
5091
- input.creativity = 0;
5092
- input.scale_factor = 2;
5093
- input.output_format = "png";
5294
+ const imageInput = { prompt: req.prompt };
5295
+ imageInput.image = refToUrl3(first);
5296
+ imageInput.creativity = 0;
5297
+ imageInput.scale_factor = 2;
5298
+ imageInput.output_format = "png";
5299
+ input = imageInput;
5300
+ } else if (isWanImage(id)) {
5301
+ const refs = (req.references ?? []).map(refToUrl3);
5302
+ const imageInput = { prompt: req.prompt };
5303
+ if (refs.length > 0) imageInput.images = refs;
5304
+ imageInput.size = wanImageSize(req.aspectRatio, req.size);
5305
+ input = imageInput;
5094
5306
  } else if (isQwen(id)) {
5095
5307
  const refs = (req.references ?? []).map(refToUrl3);
5096
5308
  const firstUrl = refs[0];
5309
+ const imageInput = { prompt: req.prompt };
5097
5310
  if (firstUrl !== void 0) {
5098
- input.image = firstUrl;
5099
- input.match_input_image = true;
5311
+ imageInput.image = firstUrl;
5312
+ if (!isQwenEditPlus(id)) imageInput.match_input_image = true;
5313
+ }
5314
+ const ratios = isQwenEditPlus(id) ? QWEN_EDIT_RATIOS : QWEN_IMAGE_RATIOS;
5315
+ if (req.aspectRatio !== void 0) {
5316
+ if (ratios.has(req.aspectRatio)) imageInput.aspect_ratio = req.aspectRatio;
5317
+ else
5318
+ ctx.warnings.push({
5319
+ code: "option_ignored",
5320
+ field: "aspectRatio",
5321
+ from: req.aspectRatio,
5322
+ message: `${id} has no ${req.aspectRatio} aspect_ratio; using the provider default`
5323
+ });
5324
+ }
5325
+ if (isQwenEditPlus(id) && req.outputFormat !== void 0) {
5326
+ imageInput.output_format = req.outputFormat === "jpeg" ? "jpg" : req.outputFormat;
5100
5327
  }
5101
- if (req.outputFormat !== void 0) input.output_format = req.outputFormat;
5328
+ input = imageInput;
5102
5329
  } else {
5103
5330
  const refs = req.references ?? [];
5104
5331
  const first = refs[0];
5105
- if (first) input.image = refToUrl3(first);
5332
+ const imageInput = { prompt: req.prompt };
5333
+ if (first) imageInput.image = refToUrl3(first);
5334
+ input = imageInput;
5106
5335
  }
5107
5336
  const { url, predictTime, raw } = await runPrediction(ctx, input, deps);
5108
5337
  const file = await download(ctx, url);
5109
5338
  const usage2 = imageUsage2(ctx, req);
5110
5339
  usage2.quantity = predictTime;
5111
- return { images: [{ bytes: file.bytes, mime: file.mime }], usage: usage2, raw };
5340
+ return { images: [{ bytes: file.bytes, mime: file.mime }], usage: usage2, raw, outputUrl: url };
5112
5341
  }
5113
5342
  function wanSize(aspectRatio) {
5114
5343
  if (!aspectRatio || aspectRatio === "adaptive" || aspectRatio === "1:1") return "2K";