@shop-os/modelkit 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -101,6 +101,11 @@ fetch, logging, and the `onUsage` sink.
101
101
  pass-through instances; catalog rows carry their own and the caller's are dropped
102
102
  with an `option_ignored` warning.
103
103
 
104
+ Text catalog rows carry no reasoning defaults. A catalog id behaves like the
105
+ provider's own default, so `temperature` and friends work as they do in `ai`.
106
+ Opt into thinking per call, for example
107
+ `providerOptions: { anthropic: { thinking: { type: "enabled", budgetTokens: 4000 } } }`.
108
+
104
109
  The kit keeps its `Warning` type for coercions; the SDK's warning type is `AiWarning`.
105
110
  `languageModel()` remains the low-level handle for Mastra and custom middleware.
106
111
 
package/dist/index.cjs CHANGED
@@ -212,6 +212,44 @@ var IMAGE_MODELS = [
212
212
  usage: { ...IMAGE_USAGE },
213
213
  pricingKind: "image_size"
214
214
  },
215
+ {
216
+ // Same Images API surface as gpt-image-2 plus the "xhigh" and "max" quality tiers.
217
+ id: "gpt-image-2.5-sunburst",
218
+ aliases: [],
219
+ label: "GPT Image 2.5 Sunburst",
220
+ type: "image",
221
+ adapter: "openai-image",
222
+ providerModelId: "gpt-image-2.5-sunburst",
223
+ lifecycle: "active",
224
+ constraints: {
225
+ aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:5", "5:4", "adaptive"],
226
+ sizes: ["1K", "2K", "4K"],
227
+ references: { images: 16 },
228
+ supports: ["quality", "count"]
229
+ },
230
+ defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
231
+ usage: { ...IMAGE_USAGE },
232
+ pricingKind: "image_size"
233
+ },
234
+ {
235
+ // Same Images API surface as gpt-image-2 plus the "xhigh" and "max" quality tiers.
236
+ id: "gpt-image-2.5-flare",
237
+ aliases: [],
238
+ label: "GPT Image 2.5 Flare",
239
+ type: "image",
240
+ adapter: "openai-image",
241
+ providerModelId: "gpt-image-2.5-flare",
242
+ lifecycle: "active",
243
+ constraints: {
244
+ aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:5", "5:4", "adaptive"],
245
+ sizes: ["1K", "2K", "4K"],
246
+ references: { images: 16 },
247
+ supports: ["quality", "count"]
248
+ },
249
+ defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
250
+ usage: { ...IMAGE_USAGE },
251
+ pricingKind: "image_size"
252
+ },
215
253
  {
216
254
  // Cowork allows 7 ratios; trigger-workflows allows 9 (adds 3:2, 9:21, drops 4:5/5:4).
217
255
  id: "muse-image-1.0",
@@ -588,77 +626,42 @@ function text(id, label, providerModelId, extra) {
588
626
  ...extra
589
627
  };
590
628
  }
591
- var ANTHROPIC_THINKING = {
592
- anthropic: {
593
- thinking: { type: "enabled", budgetTokens: 4e3 },
594
- sendReasoning: true
595
- }
596
- };
597
- var ANTHROPIC_ADAPTIVE = {
598
- anthropic: {
599
- thinking: { type: "adaptive", display: "summarized" },
600
- sendReasoning: true
601
- }
602
- };
603
- var OPENAI_REASONING = {
604
- openai: {
605
- reasoningEffort: "medium",
606
- reasoningSummary: "auto"
607
- }
608
- };
609
- var DEEPSEEK_THINKING = {
610
- deepseek: {
611
- thinking: { type: "enabled" }
612
- }
613
- };
614
629
  var TEXT_MODELS = [
615
630
  text("gpt-4o", "GPT-4o", "gpt-4o"),
616
631
  text("gpt-4o-mini", "GPT-4o mini", "gpt-4o-mini"),
617
632
  text("gpt-5.1", "GPT-5.1", "gpt-5.1"),
618
- text("gpt-5.1-mini", "GPT-5.1 mini", "gpt-5.1-mini"),
619
633
  text("gpt-5.2", "GPT-5.2", "gpt-5.2"),
620
- text("gpt-5.2-mini", "GPT-5.2 mini", "gpt-5.2-mini"),
621
634
  text("gpt-5.4", "GPT-5.4", "gpt-5.4"),
622
- text("gpt-5.4-mini", "GPT-5.4 mini", "gpt-5.4-mini"),
623
- text("gpt-5.5", "GPT-5.5", "gpt-5.5"),
624
- text("gpt-5.6-luna", "GPT-5.6 Luna", "gpt-5.6-luna", {
625
- providerOptions: { ...OPENAI_REASONING }
626
- }),
627
- text("gpt-5.6-terra", "GPT-5.6 Terra", "gpt-5.6-terra", {
628
- providerOptions: { ...OPENAI_REASONING }
629
- }),
630
- text("gpt-5.6-sol", "GPT-5.6 Sol", "gpt-5.6-sol", {
631
- providerOptions: { ...OPENAI_REASONING }
632
- }),
633
- // Dated 4.0 ids are what vibe-api, pro-admin and the TW geo agents still send.
634
- text("claude-sonnet-4", "Claude Sonnet 4", "claude-sonnet-4-20250514", {
635
- aliases: ["claude-sonnet-4.0", "claude-sonnet-4-20250514"]
636
- }),
637
- text("claude-opus-4", "Claude Opus 4", "claude-opus-4-20250514", {
638
- aliases: ["claude-opus-4.0", "claude-opus-4-20250514"]
635
+ // OpenAI never shipped gpt-5.1-mini or gpt-5.2-mini; callers that still send them get 5.4 mini.
636
+ text("gpt-5.4-mini", "GPT-5.4 mini", "gpt-5.4-mini", {
637
+ aliases: ["gpt-5.1-mini", "gpt-5.2-mini"]
639
638
  }),
639
+ text("gpt-5.5", "GPT-5.5", "gpt-5.5"),
640
+ text("gpt-5.6-luna", "GPT-5.6 Luna", "gpt-5.6-luna"),
641
+ text("gpt-5.6-terra", "GPT-5.6 Terra", "gpt-5.6-terra"),
642
+ text("gpt-5.6-sol", "GPT-5.6 Sol", "gpt-5.6-sol"),
640
643
  // Library key "claude-sonnet-4.5" maps to the SDK id "claude-sonnet-4-5".
641
644
  text("claude-sonnet-4-5", "Claude Sonnet 4.5", "claude-sonnet-4-5", {
642
645
  aliases: ["claude-sonnet-4.5"]
643
646
  }),
647
+ // Anthropic retired the dated 4.0 ids; callers that still send them get 4.6.
644
648
  text("claude-sonnet-4-6", "Claude Sonnet 4.6", "claude-sonnet-4-6", {
645
- aliases: ["claude-sonnet-4.6"],
646
- providerOptions: { ...ANTHROPIC_THINKING }
647
- }),
648
- text("claude-sonnet-5", "Claude Sonnet 5", "claude-sonnet-5", {
649
- providerOptions: { ...ANTHROPIC_ADAPTIVE }
649
+ aliases: [
650
+ "claude-sonnet-4.6",
651
+ "claude-sonnet-4",
652
+ "claude-sonnet-4.0",
653
+ "claude-sonnet-4-20250514"
654
+ ]
650
655
  }),
656
+ text("claude-sonnet-5", "Claude Sonnet 5", "claude-sonnet-5"),
651
657
  text("claude-opus-4-8", "Claude Opus 4.8", "claude-opus-4-8", {
652
- aliases: ["claude-opus-4.8"],
653
- providerOptions: { ...ANTHROPIC_ADAPTIVE }
658
+ aliases: ["claude-opus-4.8", "claude-opus-4", "claude-opus-4.0", "claude-opus-4-20250514"]
654
659
  }),
655
660
  text("claude-haiku-4-5", "Claude Haiku 4.5", "claude-haiku-4-5", {
656
- aliases: ["claude-haiku-4.5", "claude-haiku-4-5-20251001"],
657
- providerOptions: { ...ANTHROPIC_THINKING }
661
+ aliases: ["claude-haiku-4.5", "claude-haiku-4-5-20251001"]
658
662
  }),
659
663
  text("claude-opus-5", "Claude Opus 5", "claude-opus-5", {
660
- aliases: ["claude-opus-5.0"],
661
- providerOptions: { ...ANTHROPIC_ADAPTIVE }
664
+ aliases: ["claude-opus-5.0"]
662
665
  }),
663
666
  text("claude-fable-5", "Claude Fable 5", "claude-fable-5"),
664
667
  text("gemini-2.5-flash", "Gemini 2.5 Flash", "gemini-2.5-flash"),
@@ -677,12 +680,8 @@ var TEXT_MODELS = [
677
680
  text("sonar", "Sonar", "sonar"),
678
681
  text("sonar-pro", "Sonar Pro", "sonar-pro"),
679
682
  text("sonar-reasoning-pro", "Sonar Reasoning Pro", "sonar-reasoning-pro"),
680
- text("deepseek-v4-flash", "DeepSeek V4 Flash", "deepseek-v4-flash", {
681
- providerOptions: { ...DEEPSEEK_THINKING }
682
- }),
683
- text("deepseek-v4-pro", "DeepSeek V4 Pro", "deepseek-v4-pro", {
684
- providerOptions: { ...DEEPSEEK_THINKING }
685
- }),
683
+ text("deepseek-v4-flash", "DeepSeek V4 Flash", "deepseek-v4-flash"),
684
+ text("deepseek-v4-pro", "DeepSeek V4 Pro", "deepseek-v4-pro"),
686
685
  text("grok-4.3", "Grok 4.3", "grok-4.3"),
687
686
  text("kimi-k2.5", "Kimi K2.5", "kimi-k2.5"),
688
687
  text("kimi-k3", "Kimi K3", "kimi-k3"),
@@ -827,7 +826,7 @@ var VIDEO_MODELS = [
827
826
  durations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
828
827
  references: { images: 9, videos: 3, audios: 3, audioOnly: false },
829
828
  ratioIgnoredWith: ["firstFrame"],
830
- supports: ["generateAudio", "seed"]
829
+ supports: ["generateAudio", "seed", "lastFrame"]
831
830
  },
832
831
  defaults: {
833
832
  durationSeconds: 5,
@@ -852,7 +851,7 @@ var VIDEO_MODELS = [
852
851
  durations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
853
852
  references: { images: 9, videos: 3, audios: 3, audioOnly: false },
854
853
  ratioIgnoredWith: ["firstFrame"],
855
- supports: ["generateAudio", "seed"]
854
+ supports: ["generateAudio", "seed", "lastFrame"]
856
855
  },
857
856
  defaults: {
858
857
  durationSeconds: 5,
@@ -878,7 +877,7 @@ var VIDEO_MODELS = [
878
877
  durations: { min: 4, max: 30 },
879
878
  references: { images: 30, videos: 10, audios: 10 },
880
879
  ratioIgnoredWith: ["firstFrame"],
881
- supports: ["generateAudio", "seed"]
880
+ supports: ["generateAudio", "seed", "lastFrame"]
882
881
  },
883
882
  defaults: {
884
883
  durationSeconds: 5,
@@ -980,7 +979,7 @@ var VIDEO_MODELS = [
980
979
  resolutions: ["720p", "1080p"],
981
980
  durations: { min: 3, max: 15 },
982
981
  references: { images: 7 },
983
- supports: ["generateAudio"]
982
+ supports: ["generateAudio", "lastFrame"]
984
983
  },
985
984
  defaults: { durationSeconds: 5, aspectRatio: "16:9", generateAudio: false, mode: "pro" },
986
985
  usage: { unit: "compute_seconds", modality: "VIDEO" },
@@ -1513,23 +1512,46 @@ async function withRetry(fn, opts = {}) {
1513
1512
  }
1514
1513
  function sleep(ms, signal) {
1515
1514
  return new Promise((resolve, reject) => {
1516
- const t = setTimeout(resolve, ms);
1517
- signal?.addEventListener(
1518
- "abort",
1519
- () => {
1520
- clearTimeout(t);
1521
- reject(
1522
- new ModelKitError({ code: "timeout", message: "aborted", retryable: false })
1523
- );
1524
- },
1525
- { once: true }
1526
- );
1515
+ const rejectAborted = () => {
1516
+ reject(new ModelKitError({ code: "timeout", message: "aborted", retryable: false }));
1517
+ };
1518
+ if (signal?.aborted) {
1519
+ rejectAborted();
1520
+ return;
1521
+ }
1522
+ let timer;
1523
+ const onAbort = () => {
1524
+ if (timer !== void 0) clearTimeout(timer);
1525
+ rejectAborted();
1526
+ };
1527
+ timer = setTimeout(() => {
1528
+ signal?.removeEventListener("abort", onAbort);
1529
+ resolve();
1530
+ }, ms);
1531
+ signal?.addEventListener("abort", onAbort, { once: true });
1527
1532
  });
1528
1533
  }
1534
+ function throwAborted() {
1535
+ return new ModelKitError({ code: "timeout", message: "aborted", retryable: false });
1536
+ }
1537
+ function transportError(provider, cause, model) {
1538
+ const message = cause instanceof Error ? cause.message : String(cause);
1539
+ const aborted = cause instanceof Error && cause.name === "AbortError";
1540
+ const init = {
1541
+ code: aborted ? "timeout" : "network",
1542
+ message: `${provider} request failed before a response: ${message}`,
1543
+ provider,
1544
+ retryable: !aborted,
1545
+ cause
1546
+ };
1547
+ if (model !== void 0) init.model = model;
1548
+ return new ModelKitError(init);
1549
+ }
1529
1550
  async function pollUntil(check, opts) {
1530
1551
  const start = Date.now();
1531
1552
  let interval = opts.intervalMs;
1532
1553
  for (; ; ) {
1554
+ if (opts.signal?.aborted) throw throwAborted();
1533
1555
  const value = await check();
1534
1556
  if (value !== void 0) return value;
1535
1557
  if (Date.now() - start > opts.timeoutMs)
@@ -1538,6 +1560,7 @@ async function pollUntil(check, opts) {
1538
1560
  message: `poll timed out after ${opts.timeoutMs}ms`,
1539
1561
  retryable: false
1540
1562
  });
1563
+ if (opts.signal?.aborted) throw throwAborted();
1541
1564
  await sleep(interval, opts.signal);
1542
1565
  if (opts.backoff)
1543
1566
  interval = Math.min(opts.maxIntervalMs ?? interval * 4, interval * opts.backoff);
@@ -1715,7 +1738,7 @@ var OPENAI_COMPATIBLE_BASE_URLS = {
1715
1738
  var GATEWAY_SLUGS = {
1716
1739
  openai: "openai",
1717
1740
  anthropic: "anthropic",
1718
- google: "google-ai-studio",
1741
+ google: "google-ai-studio/v1beta",
1719
1742
  xai: "grok",
1720
1743
  perplexity: "perplexity-ai",
1721
1744
  deepseek: "deepseek"
@@ -2141,13 +2164,21 @@ function createModelKit(config, adapters) {
2141
2164
  });
2142
2165
  const job = await adapter.generateVideo(request, ctx);
2143
2166
  const result = job.result.bind(job);
2167
+ let settled;
2144
2168
  return {
2145
2169
  ...job,
2146
2170
  warnings,
2147
- async result(opts) {
2148
- const out = await result(opts);
2149
- await emit({ ...out.usage, model: model.id });
2150
- return out;
2171
+ result(opts) {
2172
+ if (settled) return settled;
2173
+ const pending = result(opts).then(
2174
+ (out) => emit({ ...out.usage, model: model.id }).then(() => out),
2175
+ (cause) => {
2176
+ settled = void 0;
2177
+ throw cause;
2178
+ }
2179
+ );
2180
+ settled = pending;
2181
+ return pending;
2151
2182
  }
2152
2183
  };
2153
2184
  },
@@ -2508,6 +2539,7 @@ async function generateSeedanceVideo(req, ctx) {
2508
2539
  };
2509
2540
  if (!hasFrameAnchor && req.aspectRatio !== void 0) body.ratio = req.aspectRatio;
2510
2541
  if (req.generateAudio !== void 0) body.generate_audio = req.generateAudio;
2542
+ if (req.seed !== void 0) body.seed = req.seed;
2511
2543
  const submit = await withRetry(async () => {
2512
2544
  const res = await ctx.fetch(
2513
2545
  `${ARK_BASE_URL}${SUBMIT_PATH}`,
@@ -2961,7 +2993,7 @@ async function generateFalVideo(req, ctx, deps) {
2961
2993
  if (isKling(providerModelId)) {
2962
2994
  const input = klingO1Input(req);
2963
2995
  if (ctx.routing.fal === "cloudflare-passthrough") {
2964
- return passthroughVideoJob(ctx, req, KLING_O1_ENDPOINT, input);
2996
+ return await passthroughVideoJob(ctx, req, KLING_O1_ENDPOINT, input);
2965
2997
  }
2966
2998
  const result2 = await runJob(ctx, deps, KLING_O1_ENDPOINT, input);
2967
2999
  return completedVideoJob(
@@ -2996,7 +3028,7 @@ async function generateFalVideo(req, ctx, deps) {
2996
3028
  base.aspect_ratio = req.aspectRatio ?? "adaptive";
2997
3029
  }
2998
3030
  if (ctx.routing.fal === "cloudflare-passthrough") {
2999
- return passthroughVideoJob(ctx, req, endpoint, base);
3031
+ return await passthroughVideoJob(ctx, req, endpoint, base);
3000
3032
  }
3001
3033
  const result = await runJob(ctx, deps, endpoint, base);
3002
3034
  return completedVideoJob(ctx, req, extractVideoUrl(result, endpoint, ctx.model.id), result);
@@ -3014,31 +3046,26 @@ function completedVideoJob(ctx, req, url, raw) {
3014
3046
  }
3015
3047
  };
3016
3048
  }
3017
- function passthroughVideoJob(ctx, req, endpoint, input) {
3049
+ async function passthroughVideoJob(ctx, req, endpoint, input) {
3018
3050
  const cfg = gatewayConfig(ctx);
3019
- let statusUrl;
3020
- const submit = (async () => {
3021
- const res = await gatewayFetch(cfg, ctx.fetch, `${FAL_QUEUE_HOST}/${endpoint}`, {
3022
- method: "POST",
3023
- body: JSON.stringify({ ...input, sync_mode: true })
3051
+ const res = await gatewayFetch(cfg, ctx.fetch, `${FAL_QUEUE_HOST}/${endpoint}`, {
3052
+ method: "POST",
3053
+ body: JSON.stringify({ ...input, sync_mode: true })
3054
+ });
3055
+ if (!res.ok) throw falError(res.status, await readBody(res), ctx.model.id);
3056
+ const parsed = falQueueSubmitSchema.safeParse(await res.json());
3057
+ if (!parsed.success) {
3058
+ throw new ModelKitError({
3059
+ code: "provider_rejected",
3060
+ message: `fal ${endpoint} submit returned no status URL`,
3061
+ provider: "fal",
3062
+ model: ctx.model.id,
3063
+ retryable: false
3024
3064
  });
3025
- if (!res.ok) throw falError(res.status, await readBody(res), ctx.model.id);
3026
- const parsed = falQueueSubmitSchema.safeParse(await res.json());
3027
- if (!parsed.success) {
3028
- throw new ModelKitError({
3029
- code: "provider_rejected",
3030
- message: `fal ${endpoint} submit returned no status URL`,
3031
- provider: "fal",
3032
- model: ctx.model.id,
3033
- retryable: false
3034
- });
3035
- }
3036
- statusUrl = parsed.data.status_url;
3037
- return parsed.data;
3038
- })();
3065
+ }
3066
+ const { request_id: jobId, status_url, response_url } = parsed.data;
3039
3067
  const checkStatus = async () => {
3040
- const { response_url } = await submit;
3041
- const statusRes = await gatewayFetch(cfg, ctx.fetch, statusUrl ?? "", { method: "GET" });
3068
+ const statusRes = await gatewayFetch(cfg, ctx.fetch, status_url, { method: "GET" });
3042
3069
  if (!statusRes.ok)
3043
3070
  throw falError(statusRes.status, await readBody(statusRes), ctx.model.id);
3044
3071
  const statusParsed = falQueueStatusSchema.safeParse(await statusRes.json());
@@ -3052,7 +3079,7 @@ function passthroughVideoJob(ctx, req, endpoint, input) {
3052
3079
  };
3053
3080
  const duration = req.durationSeconds ?? 5;
3054
3081
  return {
3055
- jobId: endpoint,
3082
+ jobId,
3056
3083
  async poll() {
3057
3084
  try {
3058
3085
  const url = await checkStatus();
@@ -3911,6 +3938,7 @@ function toProviderError(cause, ctx) {
3911
3938
  });
3912
3939
  }
3913
3940
  const info = describeThrown(cause);
3941
+ if (info.status === void 0) return transportError("google-image", cause, ctx.model.id);
3914
3942
  return classifyHttpError(info.status, info.body, "google-image", ctx.model.id);
3915
3943
  }
3916
3944
  function createGoogleImageAdapter(deps = defaultGoogleImageDeps) {
@@ -4618,6 +4646,7 @@ var openaiImageAdapter = {
4618
4646
  const refs = req.references ?? [];
4619
4647
  const isEdit = refs.length > 0;
4620
4648
  const quality = req.quality ?? (isEdit ? "medium" : "auto");
4649
+ const sdkQuality = quality;
4621
4650
  const count = req.count !== void 0 && req.count > 1 ? { n: req.count } : {};
4622
4651
  const retryOptions = {};
4623
4652
  if (ctx.signal !== void 0) retryOptions.signal = ctx.signal;
@@ -4629,14 +4658,14 @@ var openaiImageAdapter = {
4629
4658
  model: ctx.model.providerModelId,
4630
4659
  prompt: req.prompt,
4631
4660
  size,
4632
- quality,
4661
+ quality: sdkQuality,
4633
4662
  ...count
4634
4663
  });
4635
4664
  }
4636
4665
  return await client2.images.generate({
4637
4666
  model: ctx.model.providerModelId,
4638
4667
  prompt: req.prompt,
4639
- quality,
4668
+ quality: sdkQuality,
4640
4669
  ...count
4641
4670
  });
4642
4671
  }
@@ -4655,7 +4684,7 @@ var openaiImageAdapter = {
4655
4684
  image: imageInput,
4656
4685
  prompt: req.prompt,
4657
4686
  size,
4658
- quality,
4687
+ quality: sdkQuality,
4659
4688
  ...count
4660
4689
  });
4661
4690
  }
@@ -4663,7 +4692,7 @@ var openaiImageAdapter = {
4663
4692
  model: ctx.model.providerModelId,
4664
4693
  image: imageInput,
4665
4694
  prompt: req.prompt,
4666
- quality,
4695
+ quality: sdkQuality,
4667
4696
  ...count
4668
4697
  });
4669
4698
  } catch (cause) {
@@ -4884,7 +4913,7 @@ async function generateProdiaImage(req, ctx) {
4884
4913
  });
4885
4914
  } catch (err) {
4886
4915
  if (isModelKitError(err)) throw err;
4887
- throw prodiaError(void 0, String(err), ctx.model.id);
4916
+ throw transportError("prodia", err, ctx.model.id);
4888
4917
  }
4889
4918
  if (!attemptRes.ok) {
4890
4919
  const text2 = await attemptRes.text().catch(() => "");
@@ -4894,7 +4923,7 @@ async function generateProdiaImage(req, ctx) {
4894
4923
  }, retryOpts);
4895
4924
  } catch (err) {
4896
4925
  if (isModelKitError(err)) throw err;
4897
- throw prodiaError(void 0, String(err), ctx.model.id);
4926
+ throw transportError("prodia", err, ctx.model.id);
4898
4927
  }
4899
4928
  const bytes = parseImageResponse(
4900
4929
  new Uint8Array(await res.arrayBuffer()),
@@ -5182,6 +5211,27 @@ function klingInput(req) {
5182
5211
  if (req.negativePrompt !== void 0) input.negative_prompt = req.negativePrompt;
5183
5212
  return input;
5184
5213
  }
5214
+ function klingOmniInput(req) {
5215
+ const images = (req.firstFrame ? [req.firstFrame] : []).map(refToUrl3);
5216
+ if (req.lastFrame) images.push(refToUrl3(req.lastFrame));
5217
+ const input = {
5218
+ prompt: req.prompt,
5219
+ duration: req.durationSeconds ?? 10,
5220
+ mode: req.mode ?? "standard",
5221
+ generate_audio: req.generateAudio ?? false
5222
+ };
5223
+ const startImage = images[0];
5224
+ if (startImage !== void 0) {
5225
+ input.start_image = startImage;
5226
+ const endImage = images[1];
5227
+ if (endImage !== void 0) input.end_image = endImage;
5228
+ } else {
5229
+ input.aspect_ratio = req.aspectRatio ?? "16:9";
5230
+ }
5231
+ const refs = (req.referenceImages ?? []).map(refToUrl3);
5232
+ if (refs.length > 0) input.reference_images = refs;
5233
+ return input;
5234
+ }
5185
5235
  function hailuoInput(req) {
5186
5236
  const input = {
5187
5237
  prompt: req.prompt,
@@ -5204,7 +5254,7 @@ function wanInput(req) {
5204
5254
  }
5205
5255
  async function generateReplicateVideo(req, ctx, deps) {
5206
5256
  const id = isLower(ctx.model.providerModelId);
5207
- const input = id.includes("kling") ? klingInput(req) : id.includes("hailuo") ? hailuoInput(req) : wanInput(req);
5257
+ const input = id.includes("kling") ? id.includes("omni") ? klingOmniInput(req) : klingInput(req) : id.includes("hailuo") ? hailuoInput(req) : wanInput(req);
5208
5258
  const api = client(ctx, deps);
5209
5259
  const createOptions = { ...route(ctx.model.providerModelId), input };
5210
5260
  let prediction;
@@ -5294,6 +5344,9 @@ function lyriaAudioInput(req, ctx) {
5294
5344
  if (negative) input.negative_prompt = negative;
5295
5345
  return input;
5296
5346
  }
5347
+ function elevenLabsMusicDuration(seconds) {
5348
+ return Math.min(300, Math.max(5, seconds ?? 15));
5349
+ }
5297
5350
  function elevenLabsMusicInput(req, ctx) {
5298
5351
  const prompt = req.prompt.trim();
5299
5352
  if (!prompt) {
@@ -5305,10 +5358,9 @@ function elevenLabsMusicInput(req, ctx) {
5305
5358
  retryable: false
5306
5359
  });
5307
5360
  }
5308
- const seconds = Math.min(300, Math.max(5, req.durationSeconds ?? 15));
5309
5361
  return {
5310
5362
  prompt,
5311
- music_length_ms: seconds * 1e3,
5363
+ music_length_ms: elevenLabsMusicDuration(req.durationSeconds) * 1e3,
5312
5364
  force_instrumental: req.instrumental ?? true,
5313
5365
  output_format: "wav_cd_quality"
5314
5366
  };
@@ -5348,7 +5400,13 @@ async function generateReplicateAudio(req, ctx, deps) {
5348
5400
  const input = id.includes("mmaudio") ? mmaudioInput(req, ctx) : id.includes("elevenlabs") ? elevenLabsMusicInput(req, ctx) : lyriaAudioInput(req, ctx);
5349
5401
  const { url, predictTime, raw } = await runPrediction(ctx, input, deps);
5350
5402
  const usage2 = audioUsage(ctx, req);
5351
- usage2.quantity = ctx.model.usage.unit === "seconds" ? req.durationSeconds ?? 0 : predictTime;
5403
+ if (ctx.model.usage.unit === "seconds") {
5404
+ const seconds = elevenLabsMusicDuration(req.durationSeconds);
5405
+ usage2.quantity = seconds;
5406
+ usage2.durationSeconds = seconds;
5407
+ } else {
5408
+ usage2.quantity = predictTime;
5409
+ }
5352
5410
  return { audio: { url }, usage: usage2, raw: predictionRaw(raw) };
5353
5411
  }
5354
5412