@mux/ai 0.28.1 → 0.30.0

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.
@@ -1,3 +1,3 @@
1
- export { D as DEFAULT_STORYBOARD_WIDTH, H as HeatmapOptions, a as HeatmapResponse, L as LOW_CONFIDENCE_THRESHOLD, M as MAX_CUE_TEXT_CHARS, T as ThumbnailOptions, b as TranscriptFetchOptions, c as TranscriptResult, V as VTTCue, d as VTTCueBudgetChunkingOptions, e as VTTDurationChunk, f as VTTDurationChunkingOptions, g as buildTranscriptUrl, h as buildVttFromCueBlocks, j as buildVttFromTranslatedCueBlocks, k as chunkByTokens, l as chunkText, m as chunkVTTCues, n as chunkVTTCuesByBudget, o as chunkVTTCuesByDuration, p as concatenateVttSegments, q as estimateTokenCount, r as extractTextFromVTT, s as extractTimestampedTranscript, t as fetchTranscriptForAsset, u as findCaptionTrack, v as getHeatmapForAsset, w as getHeatmapForPlaybackId, x as getHeatmapForVideo, y as getReadyTextTracks, z as getReliableLanguageCode, A as getStoryboardUrl, B as getThumbnailUrls, C as parseVTTCues, E as replaceCueText, F as sanitizeUntrustedText, G as secondsToTimestamp, I as splitVttPreambleAndCueBlocks, J as stripVttMetadataBlocks, K as vttTimestampToSeconds } from '../index-bjQi1ZCj.js';
2
- export { o as CompletedShotsResult, p as ErroredShotsResult, H as Hotspot, q as HotspotOptions, r as HotspotResponse, s as PendingShotsResult, t as Shot, u as ShotRequestOptions, v as ShotsResult, w as WaitForShotsOptions, x as getHotspotsForAsset, y as getHotspotsForPlaybackId, z as getHotspotsForVideo, B as getShotsForAsset, D as requestShotsForAsset, F as waitForShotsForAsset } from '../shots-mGJfYGiO.js';
1
+ export { D as DEFAULT_STORYBOARD_WIDTH, H as HeatmapOptions, a as HeatmapResponse, L as LOW_CONFIDENCE_THRESHOLD, M as MAX_CUE_TEXT_CHARS, T as ThumbnailOptions, b as TranscriptFetchOptions, c as TranscriptResult, V as VTTCue, d as VTTCueBudgetChunkingOptions, e as VTTDurationChunk, f as VTTDurationChunkingOptions, g as buildTranscriptUrl, h as buildVttFromCueBlocks, j as buildVttFromTranslatedCueBlocks, k as chunkByTokens, l as chunkText, m as chunkVTTCues, n as chunkVTTCuesByBudget, o as chunkVTTCuesByDuration, p as concatenateVttSegments, q as estimateTokenCount, r as extractTextFromVTT, s as extractTimestampedTranscript, t as fetchTranscriptForAsset, u as filterVttByScope, v as findCaptionTrack, w as getHeatmapForAsset, x as getHeatmapForPlaybackId, y as getHeatmapForVideo, z as getReadyTextTracks, A as getReliableLanguageCode, B as getStoryboardUrl, C as getThumbnailUrls, E as parseVTTCues, F as replaceCueText, G as sanitizeUntrustedText, I as secondsToTimestamp, J as splitVttPreambleAndCueBlocks, K as stripVttMetadataBlocks, N as vttTimestampToSeconds } from '../index-CJtQiGj7.js';
2
+ export { q as CompletedShotsResult, r as ErroredShotsResult, H as Hotspot, s as HotspotOptions, t as HotspotResponse, u as PendingShotsResult, v as Shot, w as ShotRequestOptions, x as ShotsResult, y as WaitForShotsOptions, z as getHotspotsForAsset, B as getHotspotsForPlaybackId, D as getHotspotsForVideo, F as getShotsForAsset, G as requestShotsForAsset, J as waitForShotsForAsset } from '../shots-Bh0GrvLx.js';
3
3
  import '@mux/mux-node';
@@ -69,7 +69,7 @@ var EnvSchema = z.object({
69
69
  MUX_TEST_ASSET_ID_VIOLENT_AUDIO_ONLY: optionalString("Mux test asset ID for audio-only assets with violent content.", "Mux test asset id for audio-only assets with violent content for testing"),
70
70
  // Eval config
71
71
  MUX_AI_EVAL_MODEL_SET: optionalString("Eval model selection mode.", "Choose between 'default' (provider defaults only) or 'all' (all configured models)"),
72
- MUX_AI_EVAL_MODELS: optionalString("Comma-separated eval model pairs.", "Comma-separated provider:model pairs (e.g. 'openai:gpt-5.1,anthropic:claude-sonnet-4-5,google:gemini-3-flash-preview')"),
72
+ MUX_AI_EVAL_MODELS: optionalString("Comma-separated eval model pairs.", "Comma-separated provider:model pairs (e.g. 'openai:gpt-5.6-luna,anthropic:claude-sonnet-4-5,google:gemini-3-flash-preview')"),
73
73
  // AI Providers
74
74
  OPENAI_API_KEY: optionalString("OpenAI API key for OpenAI-backed workflows.", "OpenAI API key"),
75
75
  ANTHROPIC_API_KEY: optionalString("Anthropic API key for Claude-backed workflows.", "Anthropic API key"),
@@ -407,16 +407,26 @@ async function resolveMuxSigningContext(credentials) {
407
407
 
408
408
  // src/lib/providers.ts
409
409
  var DEFAULT_LANGUAGE_MODELS = {
410
- openai: "gpt-5.1",
410
+ // GPT-5.6 models default to medium reasoning when no effort is specified.
411
+ openai: "gpt-5.6-luna",
411
412
  anthropic: "claude-sonnet-4-5",
412
413
  google: "gemini-3-flash-preview"
413
414
  };
414
415
  var LANGUAGE_MODELS = {
415
- openai: ["gpt-5.1", "gpt-5-mini"],
416
+ openai: ["gpt-5.6-luna", "gpt-5.1", "gpt-5-mini"],
416
417
  anthropic: ["claude-sonnet-4-5"],
417
418
  google: ["gemini-3-flash-preview", "gemini-3.1-flash-lite", "gemini-2.5-flash"]
418
419
  };
419
420
  var LANGUAGE_MODEL_DEPRECATIONS = [
421
+ {
422
+ provider: "openai",
423
+ modelId: "gpt-5.1",
424
+ replacementModelId: "gpt-5.6-luna",
425
+ phase: "warn",
426
+ deprecatedOn: "2026-08-01",
427
+ sunsetOn: "2026-10-01",
428
+ reason: "GPT-5.6 Luna with its default medium reasoning effort is the new OpenAI default."
429
+ },
420
430
  {
421
431
  provider: "google",
422
432
  modelId: "gemini-2.5-flash",
@@ -477,7 +487,7 @@ function parseEvalModelPair(value) {
477
487
  const modelId = modelIdRaw?.trim();
478
488
  if (!provider || !modelId) {
479
489
  throw new Error(
480
- `Invalid eval model pair "${value}". Use "provider:model" (example: "openai:gpt-5.1").`
490
+ `Invalid eval model pair "${value}". Use "provider:model" (example: "openai:gpt-5.6-luna").`
481
491
  );
482
492
  }
483
493
  if (!isSupportedProvider(provider)) {
@@ -798,7 +808,8 @@ var TOKEN_USAGE_FIELDS = [
798
808
  "outputTokens",
799
809
  "totalTokens",
800
810
  "reasoningTokens",
801
- "cachedInputTokens"
811
+ "cachedInputTokens",
812
+ "cacheWriteTokens"
802
813
  ];
803
814
  function getErrorTokenUsage(error) {
804
815
  if (typeof error !== "object" || error === null) {
@@ -828,6 +839,12 @@ function getErrorTokenUsage(error) {
828
839
  extracted.cachedInputTokens = cacheReadTokens;
829
840
  }
830
841
  }
842
+ if (extracted.cacheWriteTokens === void 0) {
843
+ const cacheWriteTokens = source.inputTokenDetails?.cacheWriteTokens;
844
+ if (typeof cacheWriteTokens === "number") {
845
+ extracted.cacheWriteTokens = cacheWriteTokens;
846
+ }
847
+ }
831
848
  return Object.keys(extracted).length > 0 ? extracted : void 0;
832
849
  }
833
850
 
@@ -842,6 +859,9 @@ var MuxAiError = class extends Error {
842
859
  this.publicMessage = message;
843
860
  this.retryable = opts?.retryable ?? false;
844
861
  }
862
+ static is(value) {
863
+ return typeof value === "object" && value !== null && "__robots_error" in value && value.__robots_error === true;
864
+ }
845
865
  };
846
866
  function extractErrorDetail(error) {
847
867
  if (error instanceof Error && error.message) {
@@ -859,7 +879,7 @@ function extractErrorDetail(error) {
859
879
  return "Unknown error";
860
880
  }
861
881
  function wrapError(error, message) {
862
- if (error instanceof MuxAiError) {
882
+ if (MuxAiError.is(error)) {
863
883
  throw error;
864
884
  }
865
885
  const rawDetail = extractErrorDetail(error);
@@ -1111,13 +1131,22 @@ async function signUrl(url, playbackId, type = "video", params, credentials) {
1111
1131
 
1112
1132
  // src/primitives/storyboards.ts
1113
1133
  var DEFAULT_STORYBOARD_WIDTH = 640;
1114
- async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials) {
1134
+ async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials, scope) {
1115
1135
  "use step";
1116
1136
  const baseUrl = getMuxStoryboardBaseUrl(playbackId);
1137
+ const params = { width };
1138
+ if (scope?.startTime !== void 0) {
1139
+ params.asset_start_time = scope.startTime;
1140
+ }
1141
+ if (scope?.endTime !== void 0) {
1142
+ params.asset_end_time = scope.endTime;
1143
+ }
1117
1144
  if (shouldSign) {
1118
- return signUrl(baseUrl, playbackId, "storyboard", { width }, credentials);
1145
+ return signUrl(baseUrl, playbackId, "storyboard", params, credentials);
1119
1146
  }
1120
- return `${baseUrl}?width=${width}`;
1147
+ return `${baseUrl}?${new URLSearchParams(
1148
+ Object.entries(params).map(([key, value]) => [key, String(value)])
1149
+ ).toString()}`;
1121
1150
  }
1122
1151
 
1123
1152
  // src/primitives/text-chunking.ts
@@ -1358,30 +1387,77 @@ function chunkText(text, strategy) {
1358
1387
  }
1359
1388
  }
1360
1389
 
1390
+ // src/lib/workflow-scope.ts
1391
+ function hasWorkflowScopeBoundaries(scope) {
1392
+ return scope?.startTime !== void 0 || scope?.endTime !== void 0;
1393
+ }
1394
+ function resolveWorkflowScope(scope, assetDurationSeconds) {
1395
+ if (assetDurationSeconds === void 0 || !Number.isFinite(assetDurationSeconds) || assetDurationSeconds <= 0) {
1396
+ throw new MuxAiError("Asset has no valid duration.", { type: "validation_error" });
1397
+ }
1398
+ const startTime = scope?.startTime ?? 0;
1399
+ const endTime = scope?.endTime ?? assetDurationSeconds;
1400
+ if (!Number.isFinite(startTime) || startTime < 0) {
1401
+ throw new MuxAiError(
1402
+ `scope.startTime must be a finite, non-negative number (received ${startTime}).`,
1403
+ { type: "validation_error" }
1404
+ );
1405
+ }
1406
+ if (!Number.isFinite(endTime) || endTime < 0) {
1407
+ throw new MuxAiError(
1408
+ `scope.endTime must be a finite, non-negative number (received ${endTime}).`,
1409
+ { type: "validation_error" }
1410
+ );
1411
+ }
1412
+ if (endTime > assetDurationSeconds) {
1413
+ throw new MuxAiError(
1414
+ `scope.endTime (${endTime}) cannot exceed the asset duration (${assetDurationSeconds}).`,
1415
+ { type: "validation_error" }
1416
+ );
1417
+ }
1418
+ if (startTime >= endTime) {
1419
+ throw new MuxAiError(
1420
+ `scope.startTime (${startTime}) must be less than scope.endTime (${endTime}).`,
1421
+ { type: "validation_error" }
1422
+ );
1423
+ }
1424
+ return { startTime, endTime };
1425
+ }
1426
+ function timeRangesOverlap(firstStart, firstEnd, second) {
1427
+ const secondStart = second.startTime ?? 0;
1428
+ const secondEnd = second.endTime ?? Number.POSITIVE_INFINITY;
1429
+ return firstStart < secondEnd && firstEnd > secondStart;
1430
+ }
1431
+
1361
1432
  // src/primitives/thumbnails.ts
1362
1433
  async function getThumbnailUrls(playbackId, duration, options = {}) {
1363
1434
  "use step";
1364
- const { interval = 10, width = 640, shouldSign = false, maxSamples, credentials } = options;
1435
+ const { interval = 10, width = 640, shouldSign = false, maxSamples, credentials, scope } = options;
1436
+ const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
1437
+ const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, duration) : { startTime: 0, endTime: duration };
1438
+ const rangeDuration = resolvedScope.endTime - resolvedScope.startTime;
1365
1439
  let timestamps = [];
1366
- if (duration <= 50) {
1367
- const spacing = duration / 6;
1440
+ if (rangeDuration <= 50) {
1441
+ const spacing = rangeDuration / 6;
1368
1442
  for (let i = 1; i <= 5; i++) {
1369
- timestamps.push(Math.round(i * spacing));
1443
+ const time = resolvedScope.startTime + i * spacing;
1444
+ timestamps.push(effectiveScope ? Number(time.toFixed(3)) : Math.round(time));
1370
1445
  }
1371
1446
  } else {
1372
- for (let time = 0; time < duration; time += interval) {
1447
+ for (let time = resolvedScope.startTime; time < resolvedScope.endTime; time += interval) {
1373
1448
  timestamps.push(time);
1374
1449
  }
1375
1450
  }
1376
1451
  if (maxSamples !== void 0 && timestamps.length > maxSamples) {
1377
1452
  const newTimestamps = [];
1378
- newTimestamps.push(0);
1453
+ newTimestamps.push(resolvedScope.startTime);
1379
1454
  if (maxSamples >= 2) {
1380
- const spacing = duration / (maxSamples - 1);
1455
+ const lastTime = effectiveScope ? Math.max(resolvedScope.startTime, resolvedScope.endTime - 1e-3) : resolvedScope.endTime;
1456
+ const spacing = (lastTime - resolvedScope.startTime) / (maxSamples - 1);
1381
1457
  for (let i = 1; i < maxSamples - 1; i++) {
1382
- newTimestamps.push(spacing * i);
1458
+ newTimestamps.push(resolvedScope.startTime + spacing * i);
1383
1459
  }
1384
- newTimestamps.push(duration);
1460
+ newTimestamps.push(lastTime);
1385
1461
  }
1386
1462
  timestamps = newTimestamps;
1387
1463
  }
@@ -1792,6 +1868,23 @@ function parseVTTCues(vttContent) {
1792
1868
  }
1793
1869
  return cues;
1794
1870
  }
1871
+ function filterVttByScope(vttContent, scope) {
1872
+ if (!scope || scope.startTime === void 0 && scope.endTime === void 0) {
1873
+ return vttContent;
1874
+ }
1875
+ const { preamble, cueBlocks } = splitVttPreambleAndCueBlocks(vttContent);
1876
+ const scopedCueBlocks = cueBlocks.filter((cueBlock) => {
1877
+ const cue = parseVTTCues(cueBlock)[0];
1878
+ return cue ? timeRangesOverlap(cue.startTime, cue.endTime, scope) : false;
1879
+ }).map((cueBlock, index) => {
1880
+ const lines = cueBlock.split("\n");
1881
+ if (/^\d+$/.test(lines[0]?.trim()) && lines[1]?.includes("-->")) {
1882
+ lines[0] = String(index + 1);
1883
+ }
1884
+ return lines.join("\n");
1885
+ });
1886
+ return buildVttFromCueBlocks(scopedCueBlocks, preamble);
1887
+ }
1795
1888
  function splitVttPreambleAndCueBlocks(vttContent) {
1796
1889
  const normalizedContent = normalizeLineEndings(vttContent).trim();
1797
1890
  if (!normalizedContent) {
@@ -1896,6 +1989,7 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
1896
1989
  const {
1897
1990
  languageCode,
1898
1991
  cleanTranscript = true,
1992
+ scope,
1899
1993
  shouldSign,
1900
1994
  credentials,
1901
1995
  required = false
@@ -1927,9 +2021,15 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
1927
2021
  return { transcriptText: "", transcriptUrl, track };
1928
2022
  }
1929
2023
  const rawVtt = await response.text();
1930
- const transcriptText = cleanTranscript ? extractTextFromVTT(rawVtt) : rawVtt;
1931
- if (required && !transcriptText.trim()) {
1932
- throw new MuxAiError("Transcript is empty.", { type: "validation_error" });
2024
+ const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
2025
+ const scopedVtt = filterVttByScope(rawVtt, effectiveScope);
2026
+ const transcriptText = cleanTranscript ? extractTextFromVTT(scopedVtt) : scopedVtt;
2027
+ const hasScopedCues = !effectiveScope || parseVTTCues(scopedVtt).length > 0;
2028
+ if (required && (!transcriptText.trim() || !hasScopedCues)) {
2029
+ throw new MuxAiError(
2030
+ effectiveScope ? "Transcript has no cues in the requested scope." : "Transcript is empty.",
2031
+ { type: "validation_error" }
2032
+ );
1933
2033
  }
1934
2034
  return { transcriptText, transcriptUrl, track };
1935
2035
  } catch (error) {
@@ -1957,6 +2057,7 @@ export {
1957
2057
  extractTextFromVTT,
1958
2058
  extractTimestampedTranscript,
1959
2059
  fetchTranscriptForAsset,
2060
+ filterVttByScope,
1960
2061
  findCaptionTrack,
1961
2062
  getHeatmapForAsset,
1962
2063
  getHeatmapForPlaybackId,