@shop-os/modelkit 0.1.0 → 0.1.1

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.d.cts CHANGED
@@ -14,7 +14,8 @@ type VideoModel = Parameters<typeof experimental_generateVideo>[0]["model"];
14
14
  type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "5:4" | "21:9" | "9:21" | "97:30" | "adaptive";
15
15
  type ImageSize = "1K" | "2K" | "4K";
16
16
  type VideoResolution = "360p" | "480p" | "720p" | "768p" | "1080p" | "2k" | "4k";
17
- type ImageQuality = "auto" | "low" | "medium" | "high";
17
+ /** "xhigh" and "max" exist only on gpt-image-2.5; gpt-image-2 rejects them at the provider. */
18
+ type ImageQuality = "auto" | "low" | "medium" | "high" | "xhigh" | "max";
18
19
  type OutputFormat = "png" | "jpeg" | "webp";
19
20
  type MediaRef = {
20
21
  url: string;
package/dist/index.d.ts CHANGED
@@ -14,7 +14,8 @@ type VideoModel = Parameters<typeof experimental_generateVideo>[0]["model"];
14
14
  type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "5:4" | "21:9" | "9:21" | "97:30" | "adaptive";
15
15
  type ImageSize = "1K" | "2K" | "4K";
16
16
  type VideoResolution = "360p" | "480p" | "720p" | "768p" | "1080p" | "2k" | "4k";
17
- type ImageQuality = "auto" | "low" | "medium" | "high";
17
+ /** "xhigh" and "max" exist only on gpt-image-2.5; gpt-image-2 rejects them at the provider. */
18
+ type ImageQuality = "auto" | "low" | "medium" | "high" | "xhigh" | "max";
18
19
  type OutputFormat = "png" | "jpeg" | "webp";
19
20
  type MediaRef = {
20
21
  url: string;
package/dist/index.js CHANGED
@@ -130,6 +130,44 @@ var IMAGE_MODELS = [
130
130
  usage: { ...IMAGE_USAGE },
131
131
  pricingKind: "image_size"
132
132
  },
133
+ {
134
+ // Same Images API surface as gpt-image-2 plus the "xhigh" and "max" quality tiers.
135
+ id: "gpt-image-2.5-sunburst",
136
+ aliases: [],
137
+ label: "GPT Image 2.5 Sunburst",
138
+ type: "image",
139
+ adapter: "openai-image",
140
+ providerModelId: "gpt-image-2.5-sunburst",
141
+ lifecycle: "active",
142
+ constraints: {
143
+ aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:5", "5:4", "adaptive"],
144
+ sizes: ["1K", "2K", "4K"],
145
+ references: { images: 16 },
146
+ supports: ["quality", "count"]
147
+ },
148
+ defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
149
+ usage: { ...IMAGE_USAGE },
150
+ pricingKind: "image_size"
151
+ },
152
+ {
153
+ // Same Images API surface as gpt-image-2 plus the "xhigh" and "max" quality tiers.
154
+ id: "gpt-image-2.5-flare",
155
+ aliases: [],
156
+ label: "GPT Image 2.5 Flare",
157
+ type: "image",
158
+ adapter: "openai-image",
159
+ providerModelId: "gpt-image-2.5-flare",
160
+ lifecycle: "active",
161
+ constraints: {
162
+ aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:5", "5:4", "adaptive"],
163
+ sizes: ["1K", "2K", "4K"],
164
+ references: { images: 16 },
165
+ supports: ["quality", "count"]
166
+ },
167
+ defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
168
+ usage: { ...IMAGE_USAGE },
169
+ pricingKind: "image_size"
170
+ },
133
171
  {
134
172
  // Cowork allows 7 ratios; trigger-workflows allows 9 (adds 3:2, 9:21, drops 4:5/5:4).
135
173
  id: "muse-image-1.0",
@@ -506,77 +544,42 @@ function text(id, label, providerModelId, extra) {
506
544
  ...extra
507
545
  };
508
546
  }
509
- var ANTHROPIC_THINKING = {
510
- anthropic: {
511
- thinking: { type: "enabled", budgetTokens: 4e3 },
512
- sendReasoning: true
513
- }
514
- };
515
- var ANTHROPIC_ADAPTIVE = {
516
- anthropic: {
517
- thinking: { type: "adaptive", display: "summarized" },
518
- sendReasoning: true
519
- }
520
- };
521
- var OPENAI_REASONING = {
522
- openai: {
523
- reasoningEffort: "medium",
524
- reasoningSummary: "auto"
525
- }
526
- };
527
- var DEEPSEEK_THINKING = {
528
- deepseek: {
529
- thinking: { type: "enabled" }
530
- }
531
- };
532
547
  var TEXT_MODELS = [
533
548
  text("gpt-4o", "GPT-4o", "gpt-4o"),
534
549
  text("gpt-4o-mini", "GPT-4o mini", "gpt-4o-mini"),
535
550
  text("gpt-5.1", "GPT-5.1", "gpt-5.1"),
536
- text("gpt-5.1-mini", "GPT-5.1 mini", "gpt-5.1-mini"),
537
551
  text("gpt-5.2", "GPT-5.2", "gpt-5.2"),
538
- text("gpt-5.2-mini", "GPT-5.2 mini", "gpt-5.2-mini"),
539
552
  text("gpt-5.4", "GPT-5.4", "gpt-5.4"),
540
- text("gpt-5.4-mini", "GPT-5.4 mini", "gpt-5.4-mini"),
541
- text("gpt-5.5", "GPT-5.5", "gpt-5.5"),
542
- text("gpt-5.6-luna", "GPT-5.6 Luna", "gpt-5.6-luna", {
543
- providerOptions: { ...OPENAI_REASONING }
544
- }),
545
- text("gpt-5.6-terra", "GPT-5.6 Terra", "gpt-5.6-terra", {
546
- providerOptions: { ...OPENAI_REASONING }
547
- }),
548
- text("gpt-5.6-sol", "GPT-5.6 Sol", "gpt-5.6-sol", {
549
- providerOptions: { ...OPENAI_REASONING }
550
- }),
551
- // Dated 4.0 ids are what vibe-api, pro-admin and the TW geo agents still send.
552
- text("claude-sonnet-4", "Claude Sonnet 4", "claude-sonnet-4-20250514", {
553
- aliases: ["claude-sonnet-4.0", "claude-sonnet-4-20250514"]
554
- }),
555
- text("claude-opus-4", "Claude Opus 4", "claude-opus-4-20250514", {
556
- aliases: ["claude-opus-4.0", "claude-opus-4-20250514"]
553
+ // OpenAI never shipped gpt-5.1-mini or gpt-5.2-mini; callers that still send them get 5.4 mini.
554
+ text("gpt-5.4-mini", "GPT-5.4 mini", "gpt-5.4-mini", {
555
+ aliases: ["gpt-5.1-mini", "gpt-5.2-mini"]
557
556
  }),
557
+ text("gpt-5.5", "GPT-5.5", "gpt-5.5"),
558
+ text("gpt-5.6-luna", "GPT-5.6 Luna", "gpt-5.6-luna"),
559
+ text("gpt-5.6-terra", "GPT-5.6 Terra", "gpt-5.6-terra"),
560
+ text("gpt-5.6-sol", "GPT-5.6 Sol", "gpt-5.6-sol"),
558
561
  // Library key "claude-sonnet-4.5" maps to the SDK id "claude-sonnet-4-5".
559
562
  text("claude-sonnet-4-5", "Claude Sonnet 4.5", "claude-sonnet-4-5", {
560
563
  aliases: ["claude-sonnet-4.5"]
561
564
  }),
565
+ // Anthropic retired the dated 4.0 ids; callers that still send them get 4.6.
562
566
  text("claude-sonnet-4-6", "Claude Sonnet 4.6", "claude-sonnet-4-6", {
563
- aliases: ["claude-sonnet-4.6"],
564
- providerOptions: { ...ANTHROPIC_THINKING }
565
- }),
566
- text("claude-sonnet-5", "Claude Sonnet 5", "claude-sonnet-5", {
567
- providerOptions: { ...ANTHROPIC_ADAPTIVE }
567
+ aliases: [
568
+ "claude-sonnet-4.6",
569
+ "claude-sonnet-4",
570
+ "claude-sonnet-4.0",
571
+ "claude-sonnet-4-20250514"
572
+ ]
568
573
  }),
574
+ text("claude-sonnet-5", "Claude Sonnet 5", "claude-sonnet-5"),
569
575
  text("claude-opus-4-8", "Claude Opus 4.8", "claude-opus-4-8", {
570
- aliases: ["claude-opus-4.8"],
571
- providerOptions: { ...ANTHROPIC_ADAPTIVE }
576
+ aliases: ["claude-opus-4.8", "claude-opus-4", "claude-opus-4.0", "claude-opus-4-20250514"]
572
577
  }),
573
578
  text("claude-haiku-4-5", "Claude Haiku 4.5", "claude-haiku-4-5", {
574
- aliases: ["claude-haiku-4.5", "claude-haiku-4-5-20251001"],
575
- providerOptions: { ...ANTHROPIC_THINKING }
579
+ aliases: ["claude-haiku-4.5", "claude-haiku-4-5-20251001"]
576
580
  }),
577
581
  text("claude-opus-5", "Claude Opus 5", "claude-opus-5", {
578
- aliases: ["claude-opus-5.0"],
579
- providerOptions: { ...ANTHROPIC_ADAPTIVE }
582
+ aliases: ["claude-opus-5.0"]
580
583
  }),
581
584
  text("claude-fable-5", "Claude Fable 5", "claude-fable-5"),
582
585
  text("gemini-2.5-flash", "Gemini 2.5 Flash", "gemini-2.5-flash"),
@@ -595,12 +598,8 @@ var TEXT_MODELS = [
595
598
  text("sonar", "Sonar", "sonar"),
596
599
  text("sonar-pro", "Sonar Pro", "sonar-pro"),
597
600
  text("sonar-reasoning-pro", "Sonar Reasoning Pro", "sonar-reasoning-pro"),
598
- text("deepseek-v4-flash", "DeepSeek V4 Flash", "deepseek-v4-flash", {
599
- providerOptions: { ...DEEPSEEK_THINKING }
600
- }),
601
- text("deepseek-v4-pro", "DeepSeek V4 Pro", "deepseek-v4-pro", {
602
- providerOptions: { ...DEEPSEEK_THINKING }
603
- }),
601
+ text("deepseek-v4-flash", "DeepSeek V4 Flash", "deepseek-v4-flash"),
602
+ text("deepseek-v4-pro", "DeepSeek V4 Pro", "deepseek-v4-pro"),
604
603
  text("grok-4.3", "Grok 4.3", "grok-4.3"),
605
604
  text("kimi-k2.5", "Kimi K2.5", "kimi-k2.5"),
606
605
  text("kimi-k3", "Kimi K3", "kimi-k3"),
@@ -1431,23 +1430,46 @@ async function withRetry(fn, opts = {}) {
1431
1430
  }
1432
1431
  function sleep(ms, signal) {
1433
1432
  return new Promise((resolve, reject) => {
1434
- const t = setTimeout(resolve, ms);
1435
- signal?.addEventListener(
1436
- "abort",
1437
- () => {
1438
- clearTimeout(t);
1439
- reject(
1440
- new ModelKitError({ code: "timeout", message: "aborted", retryable: false })
1441
- );
1442
- },
1443
- { once: true }
1444
- );
1433
+ const rejectAborted = () => {
1434
+ reject(new ModelKitError({ code: "timeout", message: "aborted", retryable: false }));
1435
+ };
1436
+ if (signal?.aborted) {
1437
+ rejectAborted();
1438
+ return;
1439
+ }
1440
+ let timer;
1441
+ const onAbort = () => {
1442
+ if (timer !== void 0) clearTimeout(timer);
1443
+ rejectAborted();
1444
+ };
1445
+ timer = setTimeout(() => {
1446
+ signal?.removeEventListener("abort", onAbort);
1447
+ resolve();
1448
+ }, ms);
1449
+ signal?.addEventListener("abort", onAbort, { once: true });
1445
1450
  });
1446
1451
  }
1452
+ function throwAborted() {
1453
+ return new ModelKitError({ code: "timeout", message: "aborted", retryable: false });
1454
+ }
1455
+ function transportError(provider, cause, model) {
1456
+ const message = cause instanceof Error ? cause.message : String(cause);
1457
+ const aborted = cause instanceof Error && cause.name === "AbortError";
1458
+ const init = {
1459
+ code: aborted ? "timeout" : "network",
1460
+ message: `${provider} request failed before a response: ${message}`,
1461
+ provider,
1462
+ retryable: !aborted,
1463
+ cause
1464
+ };
1465
+ if (model !== void 0) init.model = model;
1466
+ return new ModelKitError(init);
1467
+ }
1447
1468
  async function pollUntil(check, opts) {
1448
1469
  const start = Date.now();
1449
1470
  let interval = opts.intervalMs;
1450
1471
  for (; ; ) {
1472
+ if (opts.signal?.aborted) throw throwAborted();
1451
1473
  const value = await check();
1452
1474
  if (value !== void 0) return value;
1453
1475
  if (Date.now() - start > opts.timeoutMs)
@@ -1456,6 +1478,7 @@ async function pollUntil(check, opts) {
1456
1478
  message: `poll timed out after ${opts.timeoutMs}ms`,
1457
1479
  retryable: false
1458
1480
  });
1481
+ if (opts.signal?.aborted) throw throwAborted();
1459
1482
  await sleep(interval, opts.signal);
1460
1483
  if (opts.backoff)
1461
1484
  interval = Math.min(opts.maxIntervalMs ?? interval * 4, interval * opts.backoff);
@@ -1640,7 +1663,7 @@ var OPENAI_COMPATIBLE_BASE_URLS = {
1640
1663
  var GATEWAY_SLUGS = {
1641
1664
  openai: "openai",
1642
1665
  anthropic: "anthropic",
1643
- google: "google-ai-studio",
1666
+ google: "google-ai-studio/v1beta",
1644
1667
  xai: "grok",
1645
1668
  perplexity: "perplexity-ai",
1646
1669
  deepseek: "deepseek"
@@ -2066,13 +2089,21 @@ function createModelKit(config, adapters) {
2066
2089
  });
2067
2090
  const job = await adapter.generateVideo(request, ctx);
2068
2091
  const result = job.result.bind(job);
2092
+ let settled;
2069
2093
  return {
2070
2094
  ...job,
2071
2095
  warnings,
2072
- async result(opts) {
2073
- const out = await result(opts);
2074
- await emit({ ...out.usage, model: model.id });
2075
- return out;
2096
+ result(opts) {
2097
+ if (settled) return settled;
2098
+ const pending = result(opts).then(
2099
+ (out) => emit({ ...out.usage, model: model.id }).then(() => out),
2100
+ (cause) => {
2101
+ settled = void 0;
2102
+ throw cause;
2103
+ }
2104
+ );
2105
+ settled = pending;
2106
+ return pending;
2076
2107
  }
2077
2108
  };
2078
2109
  },
@@ -2886,7 +2917,7 @@ async function generateFalVideo(req, ctx, deps) {
2886
2917
  if (isKling(providerModelId)) {
2887
2918
  const input = klingO1Input(req);
2888
2919
  if (ctx.routing.fal === "cloudflare-passthrough") {
2889
- return passthroughVideoJob(ctx, req, KLING_O1_ENDPOINT, input);
2920
+ return await passthroughVideoJob(ctx, req, KLING_O1_ENDPOINT, input);
2890
2921
  }
2891
2922
  const result2 = await runJob(ctx, deps, KLING_O1_ENDPOINT, input);
2892
2923
  return completedVideoJob(
@@ -2921,7 +2952,7 @@ async function generateFalVideo(req, ctx, deps) {
2921
2952
  base.aspect_ratio = req.aspectRatio ?? "adaptive";
2922
2953
  }
2923
2954
  if (ctx.routing.fal === "cloudflare-passthrough") {
2924
- return passthroughVideoJob(ctx, req, endpoint, base);
2955
+ return await passthroughVideoJob(ctx, req, endpoint, base);
2925
2956
  }
2926
2957
  const result = await runJob(ctx, deps, endpoint, base);
2927
2958
  return completedVideoJob(ctx, req, extractVideoUrl(result, endpoint, ctx.model.id), result);
@@ -2939,31 +2970,26 @@ function completedVideoJob(ctx, req, url, raw) {
2939
2970
  }
2940
2971
  };
2941
2972
  }
2942
- function passthroughVideoJob(ctx, req, endpoint, input) {
2973
+ async function passthroughVideoJob(ctx, req, endpoint, input) {
2943
2974
  const cfg = gatewayConfig(ctx);
2944
- let statusUrl;
2945
- const submit = (async () => {
2946
- const res = await gatewayFetch(cfg, ctx.fetch, `${FAL_QUEUE_HOST}/${endpoint}`, {
2947
- method: "POST",
2948
- body: JSON.stringify({ ...input, sync_mode: true })
2975
+ const res = await gatewayFetch(cfg, ctx.fetch, `${FAL_QUEUE_HOST}/${endpoint}`, {
2976
+ method: "POST",
2977
+ body: JSON.stringify({ ...input, sync_mode: true })
2978
+ });
2979
+ if (!res.ok) throw falError(res.status, await readBody(res), ctx.model.id);
2980
+ const parsed = falQueueSubmitSchema.safeParse(await res.json());
2981
+ if (!parsed.success) {
2982
+ throw new ModelKitError({
2983
+ code: "provider_rejected",
2984
+ message: `fal ${endpoint} submit returned no status URL`,
2985
+ provider: "fal",
2986
+ model: ctx.model.id,
2987
+ retryable: false
2949
2988
  });
2950
- if (!res.ok) throw falError(res.status, await readBody(res), ctx.model.id);
2951
- const parsed = falQueueSubmitSchema.safeParse(await res.json());
2952
- if (!parsed.success) {
2953
- throw new ModelKitError({
2954
- code: "provider_rejected",
2955
- message: `fal ${endpoint} submit returned no status URL`,
2956
- provider: "fal",
2957
- model: ctx.model.id,
2958
- retryable: false
2959
- });
2960
- }
2961
- statusUrl = parsed.data.status_url;
2962
- return parsed.data;
2963
- })();
2989
+ }
2990
+ const { request_id: jobId, status_url, response_url } = parsed.data;
2964
2991
  const checkStatus = async () => {
2965
- const { response_url } = await submit;
2966
- const statusRes = await gatewayFetch(cfg, ctx.fetch, statusUrl ?? "", { method: "GET" });
2992
+ const statusRes = await gatewayFetch(cfg, ctx.fetch, status_url, { method: "GET" });
2967
2993
  if (!statusRes.ok)
2968
2994
  throw falError(statusRes.status, await readBody(statusRes), ctx.model.id);
2969
2995
  const statusParsed = falQueueStatusSchema.safeParse(await statusRes.json());
@@ -2977,7 +3003,7 @@ function passthroughVideoJob(ctx, req, endpoint, input) {
2977
3003
  };
2978
3004
  const duration = req.durationSeconds ?? 5;
2979
3005
  return {
2980
- jobId: endpoint,
3006
+ jobId,
2981
3007
  async poll() {
2982
3008
  try {
2983
3009
  const url = await checkStatus();
@@ -4545,6 +4571,7 @@ var openaiImageAdapter = {
4545
4571
  const refs = req.references ?? [];
4546
4572
  const isEdit = refs.length > 0;
4547
4573
  const quality = req.quality ?? (isEdit ? "medium" : "auto");
4574
+ const sdkQuality = quality;
4548
4575
  const count = req.count !== void 0 && req.count > 1 ? { n: req.count } : {};
4549
4576
  const retryOptions = {};
4550
4577
  if (ctx.signal !== void 0) retryOptions.signal = ctx.signal;
@@ -4556,14 +4583,14 @@ var openaiImageAdapter = {
4556
4583
  model: ctx.model.providerModelId,
4557
4584
  prompt: req.prompt,
4558
4585
  size,
4559
- quality,
4586
+ quality: sdkQuality,
4560
4587
  ...count
4561
4588
  });
4562
4589
  }
4563
4590
  return await client2.images.generate({
4564
4591
  model: ctx.model.providerModelId,
4565
4592
  prompt: req.prompt,
4566
- quality,
4593
+ quality: sdkQuality,
4567
4594
  ...count
4568
4595
  });
4569
4596
  }
@@ -4582,7 +4609,7 @@ var openaiImageAdapter = {
4582
4609
  image: imageInput,
4583
4610
  prompt: req.prompt,
4584
4611
  size,
4585
- quality,
4612
+ quality: sdkQuality,
4586
4613
  ...count
4587
4614
  });
4588
4615
  }
@@ -4590,7 +4617,7 @@ var openaiImageAdapter = {
4590
4617
  model: ctx.model.providerModelId,
4591
4618
  image: imageInput,
4592
4619
  prompt: req.prompt,
4593
- quality,
4620
+ quality: sdkQuality,
4594
4621
  ...count
4595
4622
  });
4596
4623
  } catch (cause) {
@@ -4811,7 +4838,7 @@ async function generateProdiaImage(req, ctx) {
4811
4838
  });
4812
4839
  } catch (err) {
4813
4840
  if (isModelKitError(err)) throw err;
4814
- throw prodiaError(void 0, String(err), ctx.model.id);
4841
+ throw transportError("prodia", err, ctx.model.id);
4815
4842
  }
4816
4843
  if (!attemptRes.ok) {
4817
4844
  const text2 = await attemptRes.text().catch(() => "");
@@ -4821,7 +4848,7 @@ async function generateProdiaImage(req, ctx) {
4821
4848
  }, retryOpts);
4822
4849
  } catch (err) {
4823
4850
  if (isModelKitError(err)) throw err;
4824
- throw prodiaError(void 0, String(err), ctx.model.id);
4851
+ throw transportError("prodia", err, ctx.model.id);
4825
4852
  }
4826
4853
  const bytes = parseImageResponse(
4827
4854
  new Uint8Array(await res.arrayBuffer()),
@@ -5109,6 +5136,27 @@ function klingInput(req) {
5109
5136
  if (req.negativePrompt !== void 0) input.negative_prompt = req.negativePrompt;
5110
5137
  return input;
5111
5138
  }
5139
+ function klingOmniInput(req) {
5140
+ const images = (req.firstFrame ? [req.firstFrame] : []).map(refToUrl3);
5141
+ if (req.lastFrame) images.push(refToUrl3(req.lastFrame));
5142
+ const input = {
5143
+ prompt: req.prompt,
5144
+ duration: req.durationSeconds ?? 10,
5145
+ mode: req.mode ?? "standard",
5146
+ generate_audio: req.generateAudio ?? false
5147
+ };
5148
+ const startImage = images[0];
5149
+ if (startImage !== void 0) {
5150
+ input.start_image = startImage;
5151
+ const endImage = images[1];
5152
+ if (endImage !== void 0) input.end_image = endImage;
5153
+ } else {
5154
+ input.aspect_ratio = req.aspectRatio ?? "16:9";
5155
+ }
5156
+ const refs = (req.referenceImages ?? []).map(refToUrl3);
5157
+ if (refs.length > 0) input.reference_images = refs;
5158
+ return input;
5159
+ }
5112
5160
  function hailuoInput(req) {
5113
5161
  const input = {
5114
5162
  prompt: req.prompt,
@@ -5131,7 +5179,7 @@ function wanInput(req) {
5131
5179
  }
5132
5180
  async function generateReplicateVideo(req, ctx, deps) {
5133
5181
  const id = isLower(ctx.model.providerModelId);
5134
- const input = id.includes("kling") ? klingInput(req) : id.includes("hailuo") ? hailuoInput(req) : wanInput(req);
5182
+ const input = id.includes("kling") ? id.includes("omni") ? klingOmniInput(req) : klingInput(req) : id.includes("hailuo") ? hailuoInput(req) : wanInput(req);
5135
5183
  const api = client(ctx, deps);
5136
5184
  const createOptions = { ...route(ctx.model.providerModelId), input };
5137
5185
  let prediction;
@@ -5221,6 +5269,9 @@ function lyriaAudioInput(req, ctx) {
5221
5269
  if (negative) input.negative_prompt = negative;
5222
5270
  return input;
5223
5271
  }
5272
+ function elevenLabsMusicDuration(seconds) {
5273
+ return Math.min(300, Math.max(5, seconds ?? 15));
5274
+ }
5224
5275
  function elevenLabsMusicInput(req, ctx) {
5225
5276
  const prompt = req.prompt.trim();
5226
5277
  if (!prompt) {
@@ -5232,10 +5283,9 @@ function elevenLabsMusicInput(req, ctx) {
5232
5283
  retryable: false
5233
5284
  });
5234
5285
  }
5235
- const seconds = Math.min(300, Math.max(5, req.durationSeconds ?? 15));
5236
5286
  return {
5237
5287
  prompt,
5238
- music_length_ms: seconds * 1e3,
5288
+ music_length_ms: elevenLabsMusicDuration(req.durationSeconds) * 1e3,
5239
5289
  force_instrumental: req.instrumental ?? true,
5240
5290
  output_format: "wav_cd_quality"
5241
5291
  };
@@ -5275,7 +5325,7 @@ async function generateReplicateAudio(req, ctx, deps) {
5275
5325
  const input = id.includes("mmaudio") ? mmaudioInput(req, ctx) : id.includes("elevenlabs") ? elevenLabsMusicInput(req, ctx) : lyriaAudioInput(req, ctx);
5276
5326
  const { url, predictTime, raw } = await runPrediction(ctx, input, deps);
5277
5327
  const usage2 = audioUsage(ctx, req);
5278
- usage2.quantity = ctx.model.usage.unit === "seconds" ? req.durationSeconds ?? 0 : predictTime;
5328
+ usage2.quantity = ctx.model.usage.unit === "seconds" ? elevenLabsMusicDuration(req.durationSeconds) : predictTime;
5279
5329
  return { audio: { url }, usage: usage2, raw: predictionRaw(raw) };
5280
5330
  }
5281
5331