@mux/ai 0.45.0 → 0.47.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,4 +1,4 @@
1
- import Mux from '@mux/mux-node';
1
+ import Mux from '@mux/ts';
2
2
 
3
3
  /**
4
4
  * Workflow Crypto
@@ -171,7 +171,7 @@ type WorkflowCredentialsInput = WorkflowCredentials | Encrypted<WorkflowCredenti
171
171
  type ToneType = "neutral" | "playful" | "professional";
172
172
  /** Common transport for image-based workflows. */
173
173
  type ImageSubmissionMode = "url" | "base64";
174
- /** Result of calling mux-node's asset retrieval helper. */
174
+ /** Result of calling the Mux SDK asset retrieval helper. */
175
175
  type MuxAsset = Awaited<ReturnType<Mux["video"]["assets"]["retrieve"]>>;
176
176
  /** Single ready track extracted from a Mux asset. */
177
177
  type AssetTextTrack = NonNullable<MuxAsset["tracks"]>[number];
@@ -294,7 +294,7 @@ interface HotspotOptions {
294
294
  limit?: number;
295
295
  /** Sort order: 'asc' or 'desc' (default: 'desc') */
296
296
  orderDirection?: "asc" | "desc";
297
- /** Order by field (default: 'score') */
297
+ /** @deprecated The API always orders by score; this option is ignored. */
298
298
  orderBy?: "score";
299
299
  /** Time window for results, e.g., '7:days' (default: '7:days') */
300
300
  timeframe?: string;
@@ -1,8 +1,8 @@
1
- export { E as ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH, F as ASK_QUESTIONS_DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH, G as ASK_QUESTIONS_MAX_QUESTIONS_PER_CALL, H as ASK_QUESTIONS_MAX_QUESTION_LENGTH, J as AskQuestionsOptions, K as AskQuestionsResult, N as AskQuestionsType, O as AudioTranslationOptions, P as AudioTranslationResult, Q as AutoCensorProfanityOptions, U as BurnedInCaptionsAnalysis, V as BurnedInCaptionsOptions, W as BurnedInCaptionsPromptOverrides, X as BurnedInCaptionsPromptSections, Y as BurnedInCaptionsResult, Z as CaptionReplacement, _ as CensorMode, $ as Chapter, a0 as ChapterSystemPromptSections, a1 as ChaptersOptions, a2 as ChaptersPromptOverrides, a3 as ChaptersPromptSections, a4 as ChaptersResult, a5 as ChaptersType, a6 as EditCaptionsOptions, a7 as EditCaptionsResult, a8 as EmbeddingsOptions, a9 as EmbeddingsResult, aa as EngagementInsightsOptions, ab as EngagementInsightsResult, ac as GENERATE_TEXT_CALLS_TO_ACTION, ad as GENERATE_TEXT_CHANNELS, ae as GENERATE_TEXT_CHANNEL_CEILINGS, af as GENERATE_TEXT_FORMATS, ag as GENERATE_TEXT_LENGTH_BOUNDS, ah as GENERATE_TEXT_MAX_ARTIFACTS, ai as GENERATE_TEXT_MAX_AUDIENCE_CHARS, aj as GENERATE_TEXT_MAX_BRAND_TERMS, ak as GENERATE_TEXT_MAX_BRAND_TERMS_TOTAL_CHARS, al as GENERATE_TEXT_MAX_BRAND_TERM_CHARS, am as GENERATE_TEXT_MAX_CONCURRENT_GENERATIONS, an as GENERATE_TEXT_MAX_INSTRUCTIONS_CHARS, ao as GENERATE_TEXT_MAX_SHOT_FRAMES, ap as GENERATE_TEXT_MAX_VARIANTS, aq as GENERATE_TEXT_VOICES, ar as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, as as GenerateTextArtifact, at as GenerateTextCallToAction, au as GenerateTextChannel, av as GenerateTextFormat, aw as GenerateTextKeyedItem, ax as GenerateTextLengthLimit, ay as GenerateTextOptions, az as GenerateTextResult, aA as GenerateTextVariant, aB as GenerateTextVoice, aC as GeneratedTextArtifact, aD as GeneratedTextVariant, aE as HIVE_SEXUAL_CATEGORIES, aF as HIVE_VIOLENCE_CATEGORIES, aG as HiveModerationOutput, aH as HiveModerationSource, aI as ModerationOptions, aJ as ModerationProvider, aK as ModerationResult, aL as MomentInsight, aM as OverallInsight, aN as ProfanityDetectionPayload, aO as Question, aP as QuestionAnswer, aQ as QuestionAnswerType, aR as ReplacementRecord, aS as SpeakerReplacement, aT as StaticRenditionCleanupOutcome, aU as SummarizationOptions, aV as SummarizationPromptOverrides, aW as SummarizationPromptSections, aX as SummaryAndTagsResult, aY as SummaryType, aZ as ThumbnailModerationScore, a_ as TranslationChunkingOptions, a$ as TranslationOptions, b0 as TranslationPayload, b1 as TranslationResult, b2 as aggregateTokenUsage, b3 as applyOverrideLists, b4 as applyReplacements, b5 as applySpeakerReplacements, b6 as askQuestions, b7 as buildReplacementRegex, b8 as burnedInCaptionsSchema, b9 as censorVttContent, ba as chapterSchema, bb as chaptersSchema, bc as createReplacer, bd as editCaptions, be as extractTranscriptSegmentsForHotspots, bf as findGenerateTextLengthViolation, bg as generateChapters, bh as generateEmbeddings, bi as generateEngagementInsights, bj as generateText, bk as generateVideoEmbeddings, bl as getModerationScores, bm as getSummaryAndTags, bn as hasBurnedInCaptions, bo as mapShotsToHotspots, bp as measureGenerateTextLength, bq as normalizeTranslatedVtt, br as profanityDetectionSchema, bs as questionAnswerSchema, bt as resolveGenerateTextLengthLimit, bu as resolveGenerateTextLengthLimits, bv as resolveGenerateTextOptions, bw as selectGenerateTextShotFrames, bx as shouldSplitChunkTranslationError, by as summarySchema, bz as transformCueText, bA as translateAudio, bB as translateCaptions, bC as translationSchema, bD as validateNeverTranslateTerms, bE as verifyNeverTranslateTerms } from '../index-BD4acrwt.js';
1
+ export { J as ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH, K as ASK_QUESTIONS_DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH, N as ASK_QUESTIONS_MAX_QUESTIONS_PER_CALL, O as ASK_QUESTIONS_MAX_QUESTION_LENGTH, P as AskQuestionsOptions, Q as AskQuestionsResult, U as AskQuestionsType, V as AudioTranslationOptions, W as AudioTranslationResult, X as AutoCensorProfanityOptions, Y as BurnedInCaptionsAnalysis, Z as BurnedInCaptionsOptions, _ as BurnedInCaptionsPromptOverrides, $ as BurnedInCaptionsPromptSections, a0 as BurnedInCaptionsResult, a1 as CaptionReplacement, a2 as CensorMode, a3 as Chapter, a4 as ChapterSystemPromptSections, a5 as ChaptersOptions, a6 as ChaptersPromptOverrides, a7 as ChaptersPromptSections, a8 as ChaptersResult, a9 as ChaptersType, aa as EditCaptionsOptions, ab as EditCaptionsResult, ac as EmbeddingsOptions, ad as EmbeddingsResult, ae as EngagementInsightsOptions, af as EngagementInsightsResult, ag as GENERATE_TEXT_CALLS_TO_ACTION, ah as GENERATE_TEXT_CHANNELS, ai as GENERATE_TEXT_CHANNEL_CEILINGS, aj as GENERATE_TEXT_FORMATS, ak as GENERATE_TEXT_LENGTH_BOUNDS, al as GENERATE_TEXT_MAX_ARTIFACTS, am as GENERATE_TEXT_MAX_AUDIENCE_CHARS, an as GENERATE_TEXT_MAX_BRAND_TERMS, ao as GENERATE_TEXT_MAX_BRAND_TERMS_TOTAL_CHARS, ap as GENERATE_TEXT_MAX_BRAND_TERM_CHARS, aq as GENERATE_TEXT_MAX_CONCURRENT_GENERATIONS, ar as GENERATE_TEXT_MAX_INSTRUCTIONS_CHARS, as as GENERATE_TEXT_MAX_SHOT_FRAMES, at as GENERATE_TEXT_MAX_VARIANTS, au as GENERATE_TEXT_VOICES, av as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, aw as GenerateTextArtifact, ax as GenerateTextCallToAction, ay as GenerateTextChannel, az as GenerateTextFormat, aA as GenerateTextKeyedItem, aB as GenerateTextLengthLimit, aC as GenerateTextOptions, aD as GenerateTextResult, aE as GenerateTextVariant, aF as GenerateTextVoice, aG as GeneratedTextArtifact, aH as GeneratedTextVariant, aI as HIVE_SEXUAL_CATEGORIES, aJ as HIVE_VIOLENCE_CATEGORIES, aK as HiveModerationOutput, aL as HiveModerationSource, aM as ModerationOptions, aN as ModerationProvider, aO as ModerationResult, aP as MomentInsight, aQ as OverallInsight, aR as ProfanityDetectionPayload, aS as Question, aT as QuestionAnswer, aU as QuestionAnswerType, aV as ReplacementRecord, aW as SpeakerReplacement, aX as StaticRenditionCleanupOutcome, aY as SummarizationOptions, aZ as SummarizationPromptOverrides, a_ as SummarizationPromptSections, a$ as SummaryAndTagsResult, b0 as SummaryType, b1 as ThumbnailModerationScore, b2 as TranslationChunkingOptions, b3 as TranslationOptions, b4 as TranslationPayload, b5 as TranslationResult, b6 as aggregateTokenUsage, b7 as applyOverrideLists, b8 as applyReplacements, b9 as applySpeakerReplacements, ba as askQuestions, bb as buildReplacementRegex, bc as burnedInCaptionsSchema, bd as censorVttContent, be as chapterSchema, bf as chaptersSchema, bg as createReplacer, bh as editCaptions, bi as extractTranscriptSegmentsForHotspots, bj as findGenerateTextLengthViolation, bk as generateChapters, bl as generateEmbeddings, bm as generateEngagementInsights, bn as generateText, bo as generateVideoEmbeddings, bp as getModerationScores, bq as getSummaryAndTags, br as hasBurnedInCaptions, bs as mapShotsToHotspots, bt as measureGenerateTextLength, bu as normalizeTranslatedVtt, bv as profanityDetectionSchema, bw as questionAnswerSchema, bx as resolveGenerateTextLengthLimit, by as resolveGenerateTextLengthLimits, bz as resolveGenerateTextOptions, bA as selectGenerateTextShotFrames, bB as shouldSplitChunkTranslationError, bC as summarySchema, bD as transformCueText, bE as translateAudio, bF as translateCaptions, bG as translationSchema, bH as validateNeverTranslateTerms, bI as verifyNeverTranslateTerms } from '../index-DxAt9Jq8.js';
2
2
  export { D as DEFAULT_DESCRIPTION_LENGTH, a as DEFAULT_SUMMARY_KEYWORD_LIMIT, b as DEFAULT_TITLE_LENGTH } from '../summarization-BFmhXeAu.js';
3
3
  import 'zod';
4
4
  import '@ai-sdk/anthropic';
5
5
  import '@ai-sdk/google';
6
6
  import '@ai-sdk/openai';
7
- import '../shots-B7G0IIF1.js';
8
- import '@mux/mux-node';
7
+ import '../shots-BwpjZDpr.js';
8
+ import '@mux/ts';
@@ -1173,7 +1173,7 @@ function resolveDirectMuxCredentials(record) {
1173
1173
  function createWorkflowMuxClient(options) {
1174
1174
  return {
1175
1175
  async createClient() {
1176
- const { default: MuxClient } = await import("@mux/mux-node");
1176
+ const { default: MuxClient } = await import("@mux/ts");
1177
1177
  return new MuxClient({
1178
1178
  tokenId: options.tokenId,
1179
1179
  tokenSecret: options.tokenSecret,
@@ -1913,12 +1913,8 @@ async function getMuxThumbnailBaseUrl(playbackId, credentials) {
1913
1913
 
1914
1914
  // src/lib/url-signing.ts
1915
1915
  async function createSigningClient(context) {
1916
- const { default: MuxClient } = await import("@mux/mux-node");
1916
+ const { default: MuxClient } = await import("@mux/ts");
1917
1917
  return new MuxClient({
1918
- // These are not needed for signing, but the SDK requires them
1919
- // Using empty strings as we only need the jwt functionality
1920
- tokenId: env_default.MUX_TOKEN_ID || "",
1921
- tokenSecret: env_default.MUX_TOKEN_SECRET || "",
1922
1918
  jwtSigningKey: context.keyId,
1923
1919
  jwtPrivateKey: context.keySecret
1924
1920
  });
@@ -3979,17 +3975,24 @@ function findNameCollisionTextTracks(asset, target) {
3979
3975
  }
3980
3976
  return listTracks(asset, targetType(target)).filter((track) => normalizeTrackName(track.name) === targetName);
3981
3977
  }
3978
+ function countTracks(tracks, label, singularVerb, pluralVerb) {
3979
+ const kind = label.toLowerCase();
3980
+ if (tracks.length === 1) {
3981
+ return `${kind === "audio" ? "An" : "A"} ${kind} track ${singularVerb}`;
3982
+ }
3983
+ return `${tracks.length} ${kind} tracks ${pluralVerb}`;
3984
+ }
3985
+ function itOrThem(tracks) {
3986
+ return tracks.length === 1 ? "it" : "them";
3987
+ }
3982
3988
  function describeTracks(tracks) {
3983
3989
  return tracks.map((track) => `${track.name ?? "(unnamed)"} [${track.language_code ?? "?"}, ${track.type === "audio" ? track.primary ? "primary audio" : "audio" : track.text_source ?? "unknown source"}, ${track.status ?? "ready"}]`).join("; ");
3984
3990
  }
3985
- function planTextTrackReplacement(asset, target, policy, options = {}) {
3991
+ function planTextTrackReplacement(asset, target, policy) {
3986
3992
  const label = targetType(target) === "audio" ? "Audio" : "Text";
3987
- const keep = new Set(options.keepTrackIds ?? []);
3988
3993
  const conflicts = /* @__PURE__ */ new Map();
3989
3994
  for (const track of [...findSameLanguageTextTracks(asset, target), ...findNameCollisionTextTracks(asset, target)]) {
3990
- if (!keep.has(track.id)) {
3991
- conflicts.set(track.id, track);
3992
- }
3995
+ conflicts.set(track.id, track);
3993
3996
  }
3994
3997
  const conflicting = [...conflicts.values()];
3995
3998
  if (conflicting.length === 0) {
@@ -3998,7 +4001,7 @@ function planTextTrackReplacement(asset, target, policy, options = {}) {
3998
4001
  if (policy === "fail") {
3999
4002
  return {
4000
4003
  kind: "blocked",
4001
- reason: `${label} track(s) already exist for language '${target.languageCode}' or name '${target.name}': ${describeTracks(conflicting)}. Set replaceExistingTracks to replace them.`,
4004
+ reason: `${countTracks(conflicting, label, "already exists", "already exist")} for language '${target.languageCode}' or name '${target.name}': ${describeTracks(conflicting)}. Set replaceExistingTracks to replace ${itOrThem(conflicting)}.`,
4002
4005
  tracks: conflicting.map(summarizeTextTrack)
4003
4006
  };
4004
4007
  }
@@ -4015,13 +4018,26 @@ function planTextTrackReplacement(asset, target, policy, options = {}) {
4015
4018
  if (notGenerated.length > 0) {
4016
4019
  return {
4017
4020
  kind: "blocked",
4018
- reason: `${label} track(s) that are not Mux-generated exist for language '${target.languageCode}' or name '${target.name}': ${describeTracks(notGenerated)}. Use replaceExistingTracks: "replace_all" to replace them.`,
4021
+ reason: `${countTracks(notGenerated, label, "that is not Mux-generated exists", "that are not Mux-generated exist")} for language '${target.languageCode}' or name '${target.name}': ${describeTracks(notGenerated)}. Use replaceExistingTracks: "replace_all" to replace ${itOrThem(notGenerated)}.`,
4019
4022
  tracks: notGenerated.map(summarizeTextTrack)
4020
4023
  };
4021
4024
  }
4022
4025
  }
4023
4026
  return { kind: "replace", toDelete: conflicting.map(summarizeTextTrack) };
4024
4027
  }
4028
+ function planSourceTrackReplacement(asset, sourceTrackId, target, policy) {
4029
+ const colliding = findNameCollisionTextTracks(asset, target);
4030
+ const blocking = policy === "fail" ? colliding : colliding.filter((track) => track.id !== sourceTrackId);
4031
+ if (blocking.length > 0) {
4032
+ return {
4033
+ kind: "blocked",
4034
+ reason: `${countTracks(blocking, "Text", "already has", "already have")} the name '${target.name}': ${describeTracks(blocking)}. Choose a different trackName.`,
4035
+ tracks: blocking.map(summarizeTextTrack)
4036
+ };
4037
+ }
4038
+ const source = policy === "replace" ? listTracks(asset, targetType(target)).find((track) => track.id === sourceTrackId) : void 0;
4039
+ return source ? { kind: "replace", toDelete: [summarizeTextTrack(source)] } : { kind: "clear" };
4040
+ }
4025
4041
  function isDuplicateTrackNameError(error) {
4026
4042
  if (typeof error !== "object" || error === null) {
4027
4043
  return false;
@@ -4077,6 +4093,14 @@ async function createTextTrackOnMux(assetId, languageCode, trackName, presignedU
4077
4093
  }
4078
4094
  async function replaceAndCreateTrack(input) {
4079
4095
  "use step";
4096
+ return writeTrack(input, (asset) => planTextTrackReplacement(asset, input.target, input.policy));
4097
+ }
4098
+ async function replaceSourceTrack(input) {
4099
+ "use step";
4100
+ const target = { ...input.target, type: "text" };
4101
+ return writeTrack({ ...input, target }, (asset) => planSourceTrackReplacement(asset, input.sourceTrackId, target, input.policy));
4102
+ }
4103
+ async function writeTrack(input, plan) {
4080
4104
  const muxClient = await resolveMuxClient(input.credentials);
4081
4105
  const mux = await muxClient.createClient();
4082
4106
  const type = targetType(input.target);
@@ -4096,12 +4120,12 @@ async function replaceAndCreateTrack(input) {
4096
4120
  }
4097
4121
  return failed(error);
4098
4122
  }
4099
- const plan = planTextTrackReplacement(asset, input.target, input.policy, { keepTrackIds: input.keepTrackIds });
4100
- if (plan.kind === "blocked") {
4101
- return { ...plan, deleted };
4123
+ const planned = plan(asset);
4124
+ if (planned.kind === "blocked") {
4125
+ return { ...planned, deleted };
4102
4126
  }
4103
- if (plan.kind === "replace") {
4104
- for (const track of plan.toDelete) {
4127
+ if (planned.kind === "replace") {
4128
+ for (const track of planned.toDelete) {
4105
4129
  try {
4106
4130
  await mux.video.assets.deleteTrack(input.assetId, track.id);
4107
4131
  deleted.push(track);
@@ -4725,7 +4749,13 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4725
4749
  { type: "validation_error" }
4726
4750
  );
4727
4751
  }
4728
- const replaceExistingTracks = replaceExistingTracksOption ?? "replace_all";
4752
+ if (replaceExistingTracksOption !== void 0 && replaceExistingTracksOption !== "replace" && replaceExistingTracksOption !== "fail") {
4753
+ throw new MuxAiError(
4754
+ `replaceExistingTracks must be "replace" or "fail" for editCaptions (received "${String(replaceExistingTracksOption)}").`,
4755
+ { type: "validation_error" }
4756
+ );
4757
+ }
4758
+ const replaceExistingTracks = replaceExistingTracksOption ?? "replace";
4729
4759
  if (!legacyTrackNaming && replaceExistingTracks === "fail" && !providedTrackName) {
4730
4760
  throw new MuxAiError(
4731
4761
  `trackName is required when replaceExistingTracks is "fail": the edited track cannot reuse the source track's name while the source is kept.`,
@@ -4781,15 +4811,14 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4781
4811
  languageCode: sourceLanguageCode,
4782
4812
  name: providedTrackName ?? sourceName
4783
4813
  };
4784
- const keepTrackIds = replaceExistingTracks === "fail" ? [trackId] : [];
4785
4814
  if (replaceExistingTracks === "fail" && normalizeTrackName(outputTrack.name) === normalizeTrackName(sourceName)) {
4786
4815
  throw new MuxAiError(
4787
- `trackName "${outputTrack.name}" matches the source track's name. Mux track names must be unique; choose a different trackName or use replaceExistingTracks: "replace_all" to replace the source.`,
4816
+ `trackName "${outputTrack.name}" matches the source track's name. Mux track names must be unique; choose a different trackName or use replaceExistingTracks: "replace" to replace the source.`,
4788
4817
  { type: "validation_error" }
4789
4818
  );
4790
4819
  }
4791
4820
  if (uploadToMux && !legacyTrackNaming) {
4792
- const plan = planTextTrackReplacement(assetData, outputTrack, replaceExistingTracks, { keepTrackIds });
4821
+ const plan = planSourceTrackReplacement(assetData, trackId, outputTrack, replaceExistingTracks);
4793
4822
  if (plan.kind === "blocked") {
4794
4823
  throw new MuxAiError(plan.reason, { type: "validation_error" });
4795
4824
  }
@@ -4911,14 +4940,14 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4911
4940
  } else if (uploadToMux) {
4912
4941
  let outcome;
4913
4942
  try {
4914
- outcome = await replaceAndCreateTextTrack({
4943
+ outcome = await replaceSourceTrack({
4915
4944
  assetId,
4945
+ sourceTrackId: trackId,
4916
4946
  target: outputTrack,
4917
4947
  policy: replaceExistingTracks,
4918
4948
  presignedUrl,
4919
4949
  closedCaptions: sourceTrack.closed_captions,
4920
4950
  passthrough: trackPassthrough,
4921
- keepTrackIds,
4922
4951
  credentials
4923
4952
  });
4924
4953
  } catch (error) {
@@ -5389,25 +5418,22 @@ async function getHeatmapForAsset(assetId, options = {}) {
5389
5418
  "use step";
5390
5419
  return fetchHeatmap("assets", assetId, options);
5391
5420
  }
5392
- function transformHeatmapResponse(response) {
5393
- return {
5394
- assetId: response.data.asset_id,
5395
- videoId: response.data.video_id,
5396
- playbackId: response.data.playback_id,
5397
- heatmap: response.data.heatmap,
5398
- timeframe: response.timeframe
5399
- };
5400
- }
5401
5421
  async function fetchHeatmap(identifierType, id, options) {
5402
5422
  "use step";
5403
5423
  const { timeframe = "7:days", credentials } = options;
5404
5424
  const muxClient = await getMuxClientFromEnv(credentials);
5405
5425
  const mux = await muxClient.createClient();
5406
- const queryParams = new URLSearchParams();
5407
- queryParams.append("timeframe[]", timeframe);
5408
- const path = `/data/v1/engagement/${identifierType}/${id}/heatmap?${queryParams.toString()}`;
5409
- const response = await mux.get(path);
5410
- return transformHeatmapResponse(response);
5426
+ const query = { timeframe: [timeframe] };
5427
+ const resource = identifierType === "playback-ids" ? mux.data.engagement.playbackIds : mux.data.engagement[identifierType];
5428
+ const response = await resource.heatmap(id, query);
5429
+ const { data } = response;
5430
+ return {
5431
+ assetId: data.asset_id ?? (identifierType === "assets" ? id : void 0),
5432
+ videoId: data.video_id ?? (identifierType === "videos" ? id : void 0),
5433
+ playbackId: data.playback_id ?? (identifierType === "playback-ids" ? id : void 0),
5434
+ heatmap: data.heatmap,
5435
+ timeframe: response.timeframe
5436
+ };
5411
5437
  }
5412
5438
 
5413
5439
  // src/primitives/hotspots.ts
@@ -5416,43 +5442,37 @@ async function getHotspotsForAsset(assetId, options = {}) {
5416
5442
  const response = await fetchHotspots("assets", assetId, options);
5417
5443
  return response.hotspots;
5418
5444
  }
5419
- function transformHotspotResponse(response) {
5420
- return {
5421
- assetId: response.data.asset_id,
5422
- videoId: response.data.video_id,
5423
- playbackId: response.data.playback_id,
5424
- hotspots: response.data.hotspots.map((h) => ({
5425
- startMs: h.start_ms,
5426
- endMs: h.end_ms,
5427
- score: h.score
5428
- }))
5429
- };
5430
- }
5431
5445
  async function fetchHotspots(identifierType, id, options) {
5432
5446
  "use step";
5433
5447
  const {
5434
5448
  limit = 5,
5435
5449
  orderDirection = "desc",
5436
- orderBy = "score",
5437
5450
  timeframe = "7:days",
5438
5451
  credentials
5439
5452
  } = options;
5440
5453
  const muxClient = await getMuxClientFromEnv(credentials);
5441
5454
  const mux = await muxClient.createClient();
5442
- const queryParams = new URLSearchParams();
5443
- queryParams.append("limit", String(limit));
5444
- queryParams.append("order_direction", orderDirection);
5445
- queryParams.append("order_by", orderBy);
5446
- queryParams.append("timeframe[]", timeframe);
5447
- const path = `/data/v1/engagement/${identifierType}/${id}/hotspots?${queryParams.toString()}`;
5448
- const response = await mux.get(path);
5449
- return transformHotspotResponse(response);
5455
+ const query = {
5456
+ limit,
5457
+ order_direction: orderDirection,
5458
+ timeframe: [timeframe]
5459
+ };
5460
+ const resource = identifierType === "playback-ids" ? mux.data.engagement.playbackIds : mux.data.engagement[identifierType];
5461
+ const response = await resource.hotspots(id, query);
5462
+ const { data } = response;
5463
+ return {
5464
+ assetId: data.asset_id ?? (identifierType === "assets" ? id : void 0),
5465
+ videoId: data.video_id ?? (identifierType === "videos" ? id : void 0),
5466
+ playbackId: data.playback_id ?? (identifierType === "playback-ids" ? id : void 0),
5467
+ hotspots: data.hotspots.map((h) => ({
5468
+ startMs: h.start_ms,
5469
+ endMs: h.end_ms,
5470
+ score: h.score
5471
+ }))
5472
+ };
5450
5473
  }
5451
5474
 
5452
5475
  // src/primitives/shots.ts
5453
- function getShotsPath(assetId) {
5454
- return `/video/v1/assets/${assetId}/shots`;
5455
- }
5456
5476
  function mapManifestShots(shots) {
5457
5477
  let usedDeprecatedField = false;
5458
5478
  const mapped = shots.map((shot, index) => {
@@ -5492,29 +5512,35 @@ async function fetchShotsFromManifest(shotsManifestUrl) {
5492
5512
  }
5493
5513
  return mapManifestShots(manifest.shots);
5494
5514
  }
5495
- async function transformShotsResponse(response) {
5496
- switch (response.data.status) {
5515
+ async function transformShotsResponse(data) {
5516
+ switch (data.status) {
5497
5517
  case "pending":
5498
5518
  return {
5499
5519
  status: "pending",
5500
- createdAt: response.data.created_at
5520
+ createdAt: data.created_at
5501
5521
  };
5502
- case "errored":
5522
+ case "errored": {
5523
+ const details = data.errors ?? data.error;
5503
5524
  return {
5504
5525
  status: "errored",
5505
- createdAt: response.data.created_at,
5506
- error: response.data.error
5526
+ createdAt: data.created_at,
5527
+ error: {
5528
+ type: details?.type ?? "unknown",
5529
+ messages: details?.messages ?? []
5530
+ }
5507
5531
  };
5532
+ }
5508
5533
  case "completed":
5534
+ if (!data.shots_manifest_url) {
5535
+ throw new Error("Completed shots response is missing shots_manifest_url");
5536
+ }
5509
5537
  return {
5510
5538
  status: "completed",
5511
- createdAt: response.data.created_at,
5512
- shots: await fetchShotsFromManifest(response.data.shots_manifest_url)
5539
+ createdAt: data.created_at,
5540
+ shots: await fetchShotsFromManifest(data.shots_manifest_url)
5513
5541
  };
5514
- default: {
5515
- const exhaustiveCheck = response.data;
5516
- throw new Error(`Unsupported shots response: ${JSON.stringify(exhaustiveCheck)}`);
5517
- }
5542
+ default:
5543
+ throw new Error(`Unsupported shots status '${data.status}'`);
5518
5544
  }
5519
5545
  }
5520
5546
  async function getShotsForAsset(assetId, options = {}) {
@@ -5522,10 +5548,8 @@ async function getShotsForAsset(assetId, options = {}) {
5522
5548
  const { credentials } = options;
5523
5549
  const muxClient = await getMuxClientFromEnv(credentials);
5524
5550
  const mux = await muxClient.createClient();
5525
- const response = await mux.get(
5526
- getShotsPath(assetId)
5527
- );
5528
- return await transformShotsResponse(response);
5551
+ const data = await mux.video.assets.retrieveShots(assetId);
5552
+ return await transformShotsResponse(data);
5529
5553
  }
5530
5554
 
5531
5555
  // src/workflows/engagement-insights.ts