@mux/ai 0.25.0 → 0.27.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.
@@ -1,4 +1,4 @@
1
- export { A as AskQuestionsOptions, j as AskQuestionsResult, k as AskQuestionsType, l as AudioTranslationOptions, m as AudioTranslationResult, n as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, o as BurnedInCaptionsOptions, p as BurnedInCaptionsPromptOverrides, q as BurnedInCaptionsPromptSections, r as BurnedInCaptionsResult, C as CaptionReplacement, s as CensorMode, u as Chapter, v as ChapterSystemPromptSections, w as ChaptersOptions, x as ChaptersPromptOverrides, y as ChaptersPromptSections, z as ChaptersResult, D as ChaptersType, E as DEFAULT_DESCRIPTION_LENGTH, F as DEFAULT_SUMMARY_KEYWORD_LIMIT, G as DEFAULT_TITLE_LENGTH, H as EditCaptionsOptions, J as EditCaptionsResult, K as EmbeddingsOptions, M as EmbeddingsResult, N as EngagementInsightsOptions, O as EngagementInsightsResult, P as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, Q as HIVE_SEXUAL_CATEGORIES, R as HIVE_VIOLENCE_CATEGORIES, T as HiveModerationOutput, U as HiveModerationSource, V as ModerationOptions, W as ModerationProvider, X as ModerationResult, Y as MomentInsight, Z as OverallInsight, _ as ProfanityDetectionPayload, $ as Question, a0 as QuestionAnswer, a1 as QuestionAnswerType, a2 as ReplacementRecord, a3 as SummarizationOptions, a4 as SummarizationPromptOverrides, a5 as SummarizationPromptSections, a6 as SummaryAndTagsResult, a7 as SummaryType, a8 as ThumbnailModerationScore, a9 as TranslationChunkingOptions, aa as TranslationOptions, ab as TranslationPayload, ac as TranslationResult, ad as aggregateTokenUsage, ae as applyOverrideLists, af as applyReplacements, ag as askQuestions, ah as buildReplacementRegex, ai as burnedInCaptionsSchema, aj as censorVttContent, ak as chapterSchema, al as chaptersSchema, am as createReplacer, an as editCaptions, ao as extractTranscriptSegmentsForHotspots, ap as generateChapters, aq as generateEmbeddings, ar as generateEngagementInsights, as as generateVideoEmbeddings, at as getModerationScores, au as getSummaryAndTags, av as hasBurnedInCaptions, aw as mapShotsToHotspots, ax as normalizeTranslatedVtt, ay as profanityDetectionSchema, az as questionAnswerSchema, aA as shouldSplitChunkTranslationError, aB as summarySchema, aC as transformCueText, aD as translateAudio, aE as translateCaptions, aF as translationSchema } from '../index-C5zhbQHK.js';
1
+ export { A as AskQuestionsOptions, j as AskQuestionsResult, k as AskQuestionsType, l as AudioTranslationOptions, m as AudioTranslationResult, n as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, o as BurnedInCaptionsOptions, p as BurnedInCaptionsPromptOverrides, q as BurnedInCaptionsPromptSections, r as BurnedInCaptionsResult, C as CaptionReplacement, s as CensorMode, u as Chapter, v as ChapterSystemPromptSections, w as ChaptersOptions, x as ChaptersPromptOverrides, y as ChaptersPromptSections, z as ChaptersResult, D as ChaptersType, E as DEFAULT_DESCRIPTION_LENGTH, F as DEFAULT_SUMMARY_KEYWORD_LIMIT, G as DEFAULT_TITLE_LENGTH, H as EditCaptionsOptions, J as EditCaptionsResult, K as EmbeddingsOptions, M as EmbeddingsResult, N as EngagementInsightsOptions, O as EngagementInsightsResult, P as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, Q as HIVE_SEXUAL_CATEGORIES, R as HIVE_VIOLENCE_CATEGORIES, T as HiveModerationOutput, U as HiveModerationSource, V as ModerationOptions, W as ModerationProvider, X as ModerationResult, Y as MomentInsight, Z as OverallInsight, _ as ProfanityDetectionPayload, $ as Question, a0 as QuestionAnswer, a1 as QuestionAnswerType, a2 as ReplacementRecord, a3 as SummarizationOptions, a4 as SummarizationPromptOverrides, a5 as SummarizationPromptSections, a6 as SummaryAndTagsResult, a7 as SummaryType, a8 as ThumbnailModerationScore, a9 as TranslationChunkingOptions, aa as TranslationOptions, ab as TranslationPayload, ac as TranslationResult, ad as aggregateTokenUsage, ae as applyOverrideLists, af as applyReplacements, ag as askQuestions, ah as buildReplacementRegex, ai as burnedInCaptionsSchema, aj as censorVttContent, ak as chapterSchema, al as chaptersSchema, am as createReplacer, an as editCaptions, ao as extractTranscriptSegmentsForHotspots, ap as generateChapters, aq as generateEmbeddings, ar as generateEngagementInsights, as as generateVideoEmbeddings, at as getModerationScores, au as getSummaryAndTags, av as hasBurnedInCaptions, aw as mapShotsToHotspots, ax as normalizeTranslatedVtt, ay as profanityDetectionSchema, az as questionAnswerSchema, aA as shouldSplitChunkTranslationError, aB as summarySchema, aC as transformCueText, aD as translateAudio, aE as translateCaptions, aF as translationSchema } from '../index-Zwh0Bwq3.js';
2
2
  import 'zod';
3
3
  import '@ai-sdk/anthropic';
4
4
  import '@ai-sdk/google';
@@ -492,6 +492,68 @@ function createSafetyReporter() {
492
492
  };
493
493
  }
494
494
 
495
+ // src/lib/token-usage.ts
496
+ var TOKEN_USAGE_FIELDS = [
497
+ "inputTokens",
498
+ "outputTokens",
499
+ "totalTokens",
500
+ "reasoningTokens",
501
+ "cachedInputTokens"
502
+ ];
503
+ function isDefinedTokenUsageValue(value) {
504
+ return typeof value === "number";
505
+ }
506
+ function aggregateTokenUsage(usages) {
507
+ return TOKEN_USAGE_FIELDS.reduce((aggregate, field) => {
508
+ const values = usages.map((usage) => usage[field]).filter(isDefinedTokenUsageValue);
509
+ if (values.length > 0) {
510
+ aggregate[field] = values.reduce((total, value) => total + value, 0);
511
+ }
512
+ return aggregate;
513
+ }, {});
514
+ }
515
+ function getErrorTokenUsage(error) {
516
+ if (typeof error !== "object" || error === null) {
517
+ return void 0;
518
+ }
519
+ const usage = error.usage;
520
+ if (typeof usage !== "object" || usage === null) {
521
+ return void 0;
522
+ }
523
+ const source = usage;
524
+ const extracted = {};
525
+ for (const field of TOKEN_USAGE_FIELDS) {
526
+ const value = source[field];
527
+ if (typeof value === "number") {
528
+ extracted[field] = value;
529
+ }
530
+ }
531
+ if (extracted.reasoningTokens === void 0) {
532
+ const reasoningTokens = source.outputTokenDetails?.reasoningTokens;
533
+ if (typeof reasoningTokens === "number") {
534
+ extracted.reasoningTokens = reasoningTokens;
535
+ }
536
+ }
537
+ if (extracted.cachedInputTokens === void 0) {
538
+ const cacheReadTokens = source.inputTokenDetails?.cacheReadTokens;
539
+ if (typeof cacheReadTokens === "number") {
540
+ extracted.cachedInputTokens = cacheReadTokens;
541
+ }
542
+ }
543
+ return Object.keys(extracted).length > 0 ? extracted : void 0;
544
+ }
545
+ function rethrowWithTokenUsage(error, collectedUsage) {
546
+ const usages = [...collectedUsage];
547
+ const errorUsage = getErrorTokenUsage(error);
548
+ if (errorUsage) {
549
+ usages.push(errorUsage);
550
+ }
551
+ if (usages.length > 0 && typeof error === "object" && error !== null) {
552
+ error.usage = aggregateTokenUsage(usages);
553
+ }
554
+ throw error;
555
+ }
556
+
495
557
  // src/lib/mux-ai-error.ts
496
558
  var MuxAiError = class extends Error {
497
559
  constructor(message, opts) {
@@ -504,18 +566,38 @@ var MuxAiError = class extends Error {
504
566
  this.retryable = opts?.retryable ?? false;
505
567
  }
506
568
  };
569
+ function extractErrorDetail(error) {
570
+ if (error instanceof Error && error.message) {
571
+ return error.message;
572
+ }
573
+ if (typeof error === "string" && error.length > 0) {
574
+ return error;
575
+ }
576
+ if (typeof error === "object" && error !== null) {
577
+ const message = error.message;
578
+ if (typeof message === "string" && message.length > 0) {
579
+ return message;
580
+ }
581
+ }
582
+ return "Unknown error";
583
+ }
507
584
  function wrapError(error, message) {
508
585
  if (error instanceof MuxAiError) {
509
586
  throw error;
510
587
  }
511
- const rawDetail = error instanceof Error ? error.message : "Unknown error";
588
+ const rawDetail = extractErrorDetail(error);
512
589
  const leakReason = detectLeakReason(rawDetail);
513
590
  const shouldSuppress = leakReason === "canary" || leakReason === "prompt_tag";
514
591
  const detail = shouldSuppress ? "Upstream error details suppressed by safety filter" : rawDetail;
515
592
  if (shouldSuppress) {
516
593
  console.warn(`[@mux/ai] Suppressed suspected prompt leak in wrapped error (context: ${message}, reason: ${leakReason}).`);
517
594
  }
518
- throw new Error(`${message}: ${detail}`);
595
+ const wrapped = new Error(`${message}: ${detail}`);
596
+ const usage = getErrorTokenUsage(error);
597
+ if (usage) {
598
+ wrapped.usage = usage;
599
+ }
600
+ throw wrapped;
519
601
  }
520
602
 
521
603
  // src/lib/providers.ts
@@ -865,7 +947,7 @@ var DEFAULT_EMBEDDING_MODELS = {
865
947
  var LANGUAGE_MODELS = {
866
948
  openai: ["gpt-5.1", "gpt-5-mini"],
867
949
  anthropic: ["claude-sonnet-4-5"],
868
- google: ["gemini-3-flash-preview", "gemini-3.1-flash-lite", "gemini-3.1-flash-lite-preview", "gemini-2.5-flash"]
950
+ google: ["gemini-3-flash-preview", "gemini-3.1-flash-lite", "gemini-2.5-flash"]
869
951
  };
870
952
  var LANGUAGE_MODEL_DEPRECATIONS = [
871
953
  {
@@ -881,10 +963,10 @@ var LANGUAGE_MODEL_DEPRECATIONS = [
881
963
  provider: "google",
882
964
  modelId: "gemini-3.1-flash-lite-preview",
883
965
  replacementModelId: "gemini-3.1-flash-lite",
884
- phase: "warn",
966
+ phase: "blocked",
885
967
  deprecatedOn: "2026-05-12",
886
968
  sunsetOn: "2026-05-25",
887
- reason: "Preview model graduated to the stable Gemini 3.1 Flash-Lite release."
969
+ reason: "Google shut down Gemini 3.1 Flash-Lite Preview on 2026-05-25."
888
970
  }
889
971
  ];
890
972
  var warnedDeprecatedLanguageModels = /* @__PURE__ */ new Set();
@@ -2331,6 +2413,14 @@ async function analyzeQuestions({
2331
2413
  }
2332
2414
  async function askQuestions(assetId, questions, options) {
2333
2415
  "use workflow";
2416
+ const collectedUsage = [];
2417
+ try {
2418
+ return await askQuestionsInternal(assetId, questions, options, collectedUsage);
2419
+ } catch (error) {
2420
+ rethrowWithTokenUsage(error, collectedUsage);
2421
+ }
2422
+ }
2423
+ async function askQuestionsInternal(assetId, questions, options, collectedUsage) {
2334
2424
  if (!questions || questions.length === 0) {
2335
2425
  throw new MuxAiError("At least one question must be provided.", { type: "validation_error" });
2336
2426
  }
@@ -2479,6 +2569,7 @@ async function askQuestions(assetId, questions, options) {
2479
2569
  const contentType = isAudioOnly ? "audio" : "video";
2480
2570
  wrapError(error, `Failed to analyze ${contentType} questions with ${provider}`);
2481
2571
  }
2572
+ collectedUsage.push(analysisResponse.usage);
2482
2573
  if (!analysisResponse.result?.answers) {
2483
2574
  throw new MuxAiError(`Failed to generate answers for asset ${assetId}.`);
2484
2575
  }
@@ -2712,6 +2803,14 @@ async function analyzeStoryboard({
2712
2803
  }
2713
2804
  async function hasBurnedInCaptions(assetId, options = {}) {
2714
2805
  "use workflow";
2806
+ const collectedUsage = [];
2807
+ try {
2808
+ return await hasBurnedInCaptionsInternal(assetId, options, collectedUsage);
2809
+ } catch (error) {
2810
+ rethrowWithTokenUsage(error, collectedUsage);
2811
+ }
2812
+ }
2813
+ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
2715
2814
  const {
2716
2815
  provider = DEFAULT_PROVIDER,
2717
2816
  model,
@@ -2751,6 +2850,7 @@ async function hasBurnedInCaptions(assetId, options = {}) {
2751
2850
  credentials
2752
2851
  });
2753
2852
  }
2853
+ collectedUsage.push(analysisResponse.usage);
2754
2854
  if (!analysisResponse.result) {
2755
2855
  throw new Error("No analysis result received from AI provider");
2756
2856
  }
@@ -2952,6 +3052,14 @@ function buildUserPrompt3({
2952
3052
  }
2953
3053
  async function generateChapters(assetId, options = {}) {
2954
3054
  "use workflow";
3055
+ const collectedUsage = [];
3056
+ try {
3057
+ return await generateChaptersInternal(assetId, options, collectedUsage);
3058
+ } catch (error) {
3059
+ rethrowWithTokenUsage(error, collectedUsage);
3060
+ }
3061
+ }
3062
+ async function generateChaptersInternal(assetId, options, collectedUsage) {
2955
3063
  const {
2956
3064
  languageCode,
2957
3065
  provider = "openai",
@@ -3019,6 +3127,9 @@ async function generateChapters(assetId, options = {}) {
3019
3127
  } catch (error) {
3020
3128
  wrapError(error, `Failed to generate chapters with ${provider}`);
3021
3129
  }
3130
+ if (chaptersData) {
3131
+ collectedUsage.push(chaptersData.usage);
3132
+ }
3022
3133
  if (!chaptersData || !chaptersData.chapters) {
3023
3134
  throw new MuxAiError(`Failed to generate chapters for asset ${assetId}.`);
3024
3135
  }
@@ -3605,6 +3716,14 @@ async function deleteTrackOnMux(assetId, trackId, credentials) {
3605
3716
  }
3606
3717
  async function editCaptions(assetId, trackId, options) {
3607
3718
  "use workflow";
3719
+ const collectedUsage = [];
3720
+ try {
3721
+ return await editCaptionsInternal(assetId, trackId, options, collectedUsage);
3722
+ } catch (error) {
3723
+ rethrowWithTokenUsage(error, collectedUsage);
3724
+ }
3725
+ }
3726
+ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
3608
3727
  const {
3609
3728
  provider,
3610
3729
  model,
@@ -3693,6 +3812,7 @@ async function editCaptions(assetId, trackId, options) {
3693
3812
  });
3694
3813
  detectedProfanity = result.profanity;
3695
3814
  usage = result.usage;
3815
+ collectedUsage.push(result.usage);
3696
3816
  if (result.unexpectedKeys.length > 0) {
3697
3817
  console.warn(
3698
3818
  `[@mux/ai] Model emitted unexpected keys in profanity detection (stripped): ${result.unexpectedKeys.join(", ")}.`
@@ -4053,16 +4173,19 @@ async function generateSingleChunkEmbedding({
4053
4173
  })
4054
4174
  );
4055
4175
  return {
4056
- chunkId: chunk.id,
4057
- embedding: response.embedding,
4058
- metadata: {
4059
- startTime: chunk.startTime,
4060
- endTime: chunk.endTime,
4061
- tokenCount: chunk.tokenCount
4062
- }
4176
+ chunk: {
4177
+ chunkId: chunk.id,
4178
+ embedding: response.embedding,
4179
+ metadata: {
4180
+ startTime: chunk.startTime,
4181
+ endTime: chunk.endTime,
4182
+ tokenCount: chunk.tokenCount
4183
+ }
4184
+ },
4185
+ usage: typeof response.usage?.tokens === "number" ? { inputTokens: response.usage.tokens, totalTokens: response.usage.tokens } : void 0
4063
4186
  };
4064
4187
  }
4065
- async function generateEmbeddingsInternal(assetId, options = {}) {
4188
+ async function generateEmbeddingsInternal(assetId, options, collectedUsage) {
4066
4189
  const {
4067
4190
  provider = "openai",
4068
4191
  model,
@@ -4101,7 +4224,7 @@ async function generateEmbeddingsInternal(assetId, options = {}) {
4101
4224
  try {
4102
4225
  for (let i = 0; i < chunks.length; i += batchSize) {
4103
4226
  const batch = chunks.slice(i, i + batchSize);
4104
- const batchResults = await Promise.all(
4227
+ const batchOutcomes = await Promise.allSettled(
4105
4228
  batch.map(
4106
4229
  (chunk) => generateSingleChunkEmbedding({
4107
4230
  chunk,
@@ -4111,7 +4234,24 @@ async function generateEmbeddingsInternal(assetId, options = {}) {
4111
4234
  })
4112
4235
  )
4113
4236
  );
4114
- chunkEmbeddings.push(...batchResults);
4237
+ const failures = [];
4238
+ for (const outcome of batchOutcomes) {
4239
+ if (outcome.status === "fulfilled") {
4240
+ if (outcome.value.usage) {
4241
+ collectedUsage.push(outcome.value.usage);
4242
+ }
4243
+ chunkEmbeddings.push(outcome.value.chunk);
4244
+ } else {
4245
+ const failureUsage = getErrorTokenUsage(outcome.reason);
4246
+ if (failureUsage) {
4247
+ collectedUsage.push(failureUsage);
4248
+ }
4249
+ failures.push(outcome.reason);
4250
+ }
4251
+ }
4252
+ if (failures.length > 0) {
4253
+ throw failures[0];
4254
+ }
4115
4255
  }
4116
4256
  } catch (error) {
4117
4257
  throw new Error(
@@ -4145,12 +4285,22 @@ async function generateEmbeddingsInternal(assetId, options = {}) {
4145
4285
  }
4146
4286
  async function generateEmbeddings(assetId, options = {}) {
4147
4287
  "use workflow";
4148
- return generateEmbeddingsInternal(assetId, options);
4288
+ const collectedUsage = [];
4289
+ try {
4290
+ return await generateEmbeddingsInternal(assetId, options, collectedUsage);
4291
+ } catch (error) {
4292
+ rethrowWithTokenUsage(error, collectedUsage);
4293
+ }
4149
4294
  }
4150
4295
  async function generateVideoEmbeddings(assetId, options = {}) {
4151
4296
  "use workflow";
4152
4297
  console.warn("generateVideoEmbeddings is deprecated. Use generateEmbeddings instead.");
4153
- return generateEmbeddingsInternal(assetId, options);
4298
+ const collectedUsage = [];
4299
+ try {
4300
+ return await generateEmbeddingsInternal(assetId, options, collectedUsage);
4301
+ } catch (error) {
4302
+ rethrowWithTokenUsage(error, collectedUsage);
4303
+ }
4154
4304
  }
4155
4305
 
4156
4306
  // src/workflows/engagement-insights.ts
@@ -4635,6 +4785,14 @@ async function generateInsightsWithAI(provider, modelId, systemPrompt, userPromp
4635
4785
  }
4636
4786
  async function generateEngagementInsights(assetId, options = {}) {
4637
4787
  "use workflow";
4788
+ const collectedUsage = [];
4789
+ try {
4790
+ return await generateEngagementInsightsInternal(assetId, options, collectedUsage);
4791
+ } catch (error) {
4792
+ rethrowWithTokenUsage(error, collectedUsage);
4793
+ }
4794
+ }
4795
+ async function generateEngagementInsightsInternal(assetId, options, collectedUsage) {
4638
4796
  const {
4639
4797
  provider = "openai",
4640
4798
  model,
@@ -4761,6 +4919,7 @@ async function generateEngagementInsights(assetId, options = {}) {
4761
4919
  credentials
4762
4920
  );
4763
4921
  const { result: aiInsights, usage } = aiStepResult;
4922
+ collectedUsage.push(usage);
4764
4923
  if (!aiInsights.momentInsights || aiInsights.momentInsights.length === 0) {
4765
4924
  throw new MuxAiError(`Failed to generate insights for asset ${assetId}.`);
4766
4925
  }
@@ -5539,6 +5698,15 @@ async function getModerationScores(assetId, options = {}) {
5539
5698
  import { generateText as generateText6, Output as Output6 } from "ai";
5540
5699
  import dedent6 from "dedent";
5541
5700
  import { z as z7 } from "zod";
5701
+
5702
+ // src/lib/markdown-normalization.ts
5703
+ var ESCAPED_LINE_BREAK_REGEX = /\\r\\n|\\n|\\r/g;
5704
+ var MULTIPLE_BLANK_LINES_REGEX = /\n{3,}/g;
5705
+ function normalizeMarkdownDescription(description) {
5706
+ return description.replace(ESCAPED_LINE_BREAK_REGEX, "\n").replace(/([^\n]) {2,}([*-]) (?=\S)/g, "$1\n$2 ").replace(/([.!?:;]) ([*-]) (?=\S)/g, "$1\n$2 ").replace(MULTIPLE_BLANK_LINES_REGEX, "\n\n");
5707
+ }
5708
+
5709
+ // src/workflows/summarization.ts
5542
5710
  var DEFAULT_SUMMARY_KEYWORD_LIMIT = 10;
5543
5711
  var DEFAULT_TITLE_LENGTH = 10;
5544
5712
  var DEFAULT_DESCRIPTION_LENGTH = 50;
@@ -5936,6 +6104,14 @@ function normalizeKeywords(keywords, limit = DEFAULT_SUMMARY_KEYWORD_LIMIT) {
5936
6104
  }
5937
6105
  async function getSummaryAndTags(assetId, options) {
5938
6106
  "use workflow";
6107
+ const collectedUsage = [];
6108
+ try {
6109
+ return await getSummaryAndTagsInternal(assetId, options, collectedUsage);
6110
+ } catch (error) {
6111
+ rethrowWithTokenUsage(error, collectedUsage);
6112
+ }
6113
+ }
6114
+ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
5939
6115
  const {
5940
6116
  provider = "openai",
5941
6117
  model,
@@ -6045,6 +6221,7 @@ async function getSummaryAndTags(assetId, options) {
6045
6221
  const contentType = isAudioOnly ? "audio" : "video";
6046
6222
  wrapError(error, `Failed to analyze ${contentType} content with ${provider}`);
6047
6223
  }
6224
+ collectedUsage.push(analysisResponse.usage);
6048
6225
  if (!analysisResponse.result) {
6049
6226
  const contentType = isAudioOnly ? "audio" : "video";
6050
6227
  throw new MuxAiError(`Failed to analyze ${contentType} content for asset ${assetId}.`);
@@ -6065,7 +6242,8 @@ async function getSummaryAndTags(assetId, options) {
6065
6242
  }
6066
6243
  }
6067
6244
  const scrubbedTitle = safety.scrub(analysisResponse.result.title, "title");
6068
- const scrubbedDescription = safety.scrub(analysisResponse.result.description, "description");
6245
+ const normalizedDescription = normalizeMarkdownDescription(analysisResponse.result.description);
6246
+ const scrubbedDescription = safety.scrub(normalizedDescription, "description");
6069
6247
  const scrubbedKeywords = (analysisResponse.result.keywords ?? []).map((kw, i) => safety.scrubDetailed(kw, `keywords[${i}]`)).filter((result) => !result.leaked).map((result) => result.text);
6070
6248
  return {
6071
6249
  assetId,
@@ -6086,12 +6264,9 @@ async function getSummaryAndTags(assetId, options) {
6086
6264
  }
6087
6265
 
6088
6266
  // src/workflows/translate-audio.ts
6267
+ import { sleep } from "workflow";
6089
6268
  var STATIC_RENDITION_POLL_INTERVAL_MS = 5e3;
6090
6269
  var STATIC_RENDITION_MAX_ATTEMPTS = 36;
6091
- async function sleep(ms) {
6092
- "use step";
6093
- await new Promise((resolve) => setTimeout(resolve, ms));
6094
- }
6095
6270
  function getReadyAudioStaticRendition(asset) {
6096
6271
  const files = asset.static_renditions?.files;
6097
6272
  if (!files || files.length === 0) {
@@ -6132,14 +6307,17 @@ async function requestStaticRenditionCreation(assetId, credentials) {
6132
6307
  wrapError(error, "Failed to request static rendition from Mux");
6133
6308
  }
6134
6309
  }
6310
+ async function retrieveAsset(assetId, credentials) {
6311
+ "use step";
6312
+ const muxClient = await resolveMuxClient(credentials);
6313
+ const mux = await muxClient.createClient();
6314
+ return mux.video.assets.retrieve(assetId);
6315
+ }
6135
6316
  async function waitForAudioStaticRendition({
6136
6317
  assetId,
6137
6318
  initialAsset,
6138
6319
  credentials
6139
6320
  }) {
6140
- "use step";
6141
- const muxClient = await resolveMuxClient(credentials);
6142
- const mux = await muxClient.createClient();
6143
6321
  let currentAsset = initialAsset;
6144
6322
  if (hasReadyAudioStaticRendition(currentAsset)) {
6145
6323
  return currentAsset;
@@ -6154,7 +6332,7 @@ async function waitForAudioStaticRendition({
6154
6332
  }
6155
6333
  for (let attempt = 1; attempt <= STATIC_RENDITION_MAX_ATTEMPTS; attempt++) {
6156
6334
  await sleep(STATIC_RENDITION_POLL_INTERVAL_MS);
6157
- currentAsset = await mux.video.assets.retrieve(assetId);
6335
+ currentAsset = await retrieveAsset(assetId, credentials);
6158
6336
  if (hasReadyAudioStaticRendition(currentAsset)) {
6159
6337
  return currentAsset;
6160
6338
  }
@@ -6173,16 +6351,8 @@ async function waitForAudioStaticRendition({
6173
6351
  { type: "timeout_error", retryable: true }
6174
6352
  );
6175
6353
  }
6176
- async function fetchAudioFromMux(audioUrl) {
6177
- "use step";
6178
- const audioResponse = await fetch(audioUrl);
6179
- if (!audioResponse.ok) {
6180
- throw new Error(`Failed to fetch audio file: ${audioResponse.statusText}`);
6181
- }
6182
- return audioResponse.arrayBuffer();
6183
- }
6184
6354
  async function createElevenLabsDubbingJob({
6185
- audioBuffer,
6355
+ sourceUrl,
6186
6356
  assetId,
6187
6357
  elevenLabsLangCode,
6188
6358
  elevenLabsSourceLangCode,
@@ -6191,9 +6361,8 @@ async function createElevenLabsDubbingJob({
6191
6361
  }) {
6192
6362
  "use step";
6193
6363
  const elevenLabsApiKey = await getApiKeyFromEnv("elevenlabs", credentials);
6194
- const audioBlob = new Blob([audioBuffer], { type: "audio/mp4" });
6195
6364
  const formData = new FormData();
6196
- formData.append("file", audioBlob);
6365
+ formData.append("source_url", sourceUrl);
6197
6366
  formData.append("target_lang", elevenLabsLangCode);
6198
6367
  if (elevenLabsSourceLangCode) {
6199
6368
  formData.append("source_lang", elevenLabsSourceLangCode);
@@ -6358,13 +6527,6 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6358
6527
  if (policy === "signed") {
6359
6528
  audioUrl = await signUrl(audioUrl, playbackId, "video", void 0, credentials);
6360
6529
  }
6361
- console.warn("\u{1F399}\uFE0F Fetching audio from Mux...");
6362
- let audioBuffer;
6363
- try {
6364
- audioBuffer = await fetchAudioFromMux(audioUrl);
6365
- } catch (error) {
6366
- wrapError(error, "Failed to fetch audio from Mux");
6367
- }
6368
6530
  console.warn("\u{1F399}\uFE0F Creating dubbing job in ElevenLabs...");
6369
6531
  const elevenLabsLangCode = toISO639_3(toLanguageCode);
6370
6532
  const normalizedFromLanguageCode = fromLanguageCode?.trim();
@@ -6375,7 +6537,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6375
6537
  let dubbingId;
6376
6538
  try {
6377
6539
  dubbingId = await createElevenLabsDubbingJob({
6378
- audioBuffer,
6540
+ sourceUrl: audioUrl,
6379
6541
  assetId,
6380
6542
  elevenLabsLangCode,
6381
6543
  elevenLabsSourceLangCode,
@@ -6391,7 +6553,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6391
6553
  let pollAttempts = 0;
6392
6554
  const maxPollAttempts = 180;
6393
6555
  let targetLanguages = [];
6394
- while (dubbingStatus === "dubbing" && pollAttempts < maxPollAttempts) {
6556
+ while (dubbingStatus !== "dubbed" && pollAttempts < maxPollAttempts) {
6395
6557
  await sleep(1e4);
6396
6558
  pollAttempts++;
6397
6559
  try {
@@ -6401,12 +6563,12 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6401
6563
  });
6402
6564
  dubbingStatus = statusResult.status;
6403
6565
  targetLanguages = statusResult.targetLanguages;
6404
- if (dubbingStatus === "failed") {
6405
- throw new Error("ElevenLabs dubbing job failed");
6406
- }
6407
6566
  } catch (error) {
6408
6567
  wrapError(error, "Failed to check dubbing status");
6409
6568
  }
6569
+ if (dubbingStatus === "failed") {
6570
+ throw new MuxAiError("ElevenLabs reported that the dubbing job failed.", { type: "processing_error" });
6571
+ }
6410
6572
  }
6411
6573
  if (dubbingStatus !== "dubbed") {
6412
6574
  throw new MuxAiError("Audio translation timed out or failed. Please try again.", { type: "timeout_error", retryable: true });
@@ -6553,13 +6715,6 @@ var DEFAULT_TRANSLATION_CHUNKING = {
6553
6715
  maxCuesPerChunk: 80,
6554
6716
  maxCueTextTokensPerChunk: 2e3
6555
6717
  };
6556
- var TOKEN_USAGE_FIELDS = [
6557
- "inputTokens",
6558
- "outputTokens",
6559
- "totalTokens",
6560
- "reasoningTokens",
6561
- "cachedInputTokens"
6562
- ];
6563
6718
  var TranslationChunkValidationError = class extends Error {
6564
6719
  constructor(message) {
6565
6720
  super(message);
@@ -6590,9 +6745,6 @@ function shouldSplitChunkTranslationError(error) {
6590
6745
  }
6591
6746
  return NoObjectGeneratedError.isInstance(error) || TypeValidationError.isInstance(error) || isTranslationChunkValidationError(error);
6592
6747
  }
6593
- function isDefinedTokenUsageValue(value) {
6594
- return typeof value === "number";
6595
- }
6596
6748
  function resolveTranslationChunkingOptions(options) {
6597
6749
  const targetChunkDurationSeconds = Math.max(
6598
6750
  1,
@@ -6619,14 +6771,27 @@ function resolveTranslationChunkingOptions(options) {
6619
6771
  )
6620
6772
  };
6621
6773
  }
6622
- function aggregateTokenUsage(usages) {
6623
- return TOKEN_USAGE_FIELDS.reduce((aggregate, field) => {
6624
- const values = usages.map((usage) => usage[field]).filter(isDefinedTokenUsageValue);
6625
- if (values.length > 0) {
6626
- aggregate[field] = values.reduce((total, value) => total + value, 0);
6774
+ function collectSettledTranslationsOrRethrow(outcomes, priorUsage) {
6775
+ const results = [];
6776
+ const failures = [];
6777
+ for (const outcome of outcomes) {
6778
+ if (outcome.status === "fulfilled") {
6779
+ results.push(outcome.value);
6780
+ } else {
6781
+ failures.push(outcome.reason);
6627
6782
  }
6628
- return aggregate;
6629
- }, {});
6783
+ }
6784
+ if (failures.length > 0) {
6785
+ const partialUsage = [...priorUsage, ...results.map((result) => result.usage)];
6786
+ for (const failure of failures.slice(1)) {
6787
+ const usage = getErrorTokenUsage(failure);
6788
+ if (usage) {
6789
+ partialUsage.push(usage);
6790
+ }
6791
+ }
6792
+ rethrowWithTokenUsage(failures[0], partialUsage);
6793
+ }
6794
+ return results;
6630
6795
  }
6631
6796
  function createTranslationChunkRequest(id, cues, cueBlocks) {
6632
6797
  return {
@@ -6902,24 +7067,28 @@ async function translateChunkWithFallback({
6902
7067
  wrapError(error, `Chunk ${chunk.id} failed for ${Math.round(chunk.startTime)}s-${Math.round(chunk.endTime)}s`);
6903
7068
  }
6904
7069
  const [leftChunk, rightChunk] = splitTranslationChunkAtMidpoint(chunk);
6905
- const [leftResult, rightResult] = await Promise.all([
6906
- translateChunkWithFallback({
6907
- chunk: leftChunk,
6908
- fromLanguageCode,
6909
- toLanguageCode,
6910
- provider,
6911
- modelId,
6912
- credentials
6913
- }),
6914
- translateChunkWithFallback({
6915
- chunk: rightChunk,
6916
- fromLanguageCode,
6917
- toLanguageCode,
6918
- provider,
6919
- modelId,
6920
- credentials
6921
- })
6922
- ]);
7070
+ const failedAttemptUsage = getErrorTokenUsage(error);
7071
+ const [leftResult, rightResult] = collectSettledTranslationsOrRethrow(
7072
+ await Promise.allSettled([
7073
+ translateChunkWithFallback({
7074
+ chunk: leftChunk,
7075
+ fromLanguageCode,
7076
+ toLanguageCode,
7077
+ provider,
7078
+ modelId,
7079
+ credentials
7080
+ }),
7081
+ translateChunkWithFallback({
7082
+ chunk: rightChunk,
7083
+ fromLanguageCode,
7084
+ toLanguageCode,
7085
+ provider,
7086
+ modelId,
7087
+ credentials
7088
+ })
7089
+ ]),
7090
+ failedAttemptUsage ? [failedAttemptUsage] : []
7091
+ );
6923
7092
  return {
6924
7093
  translatedVtt: concatenateVttSegments([leftResult.translatedVtt, rightResult.translatedVtt]),
6925
7094
  usage: aggregateTokenUsage([leftResult.usage, rightResult.usage]),
@@ -6960,17 +7129,20 @@ async function translateCaptionTrack({
6960
7129
  let totalUnexpectedKeyCount = 0;
6961
7130
  for (let index = 0; index < chunkPlan.chunks.length; index += resolvedChunking.maxConcurrentTranslations) {
6962
7131
  const batch = chunkPlan.chunks.slice(index, index + resolvedChunking.maxConcurrentTranslations);
6963
- const batchResults = await Promise.all(
6964
- batch.map(
6965
- (chunk) => translateChunkWithFallback({
6966
- chunk,
6967
- fromLanguageCode,
6968
- toLanguageCode,
6969
- provider,
6970
- modelId,
6971
- credentials
6972
- })
6973
- )
7132
+ const batchResults = collectSettledTranslationsOrRethrow(
7133
+ await Promise.allSettled(
7134
+ batch.map(
7135
+ (chunk) => translateChunkWithFallback({
7136
+ chunk,
7137
+ fromLanguageCode,
7138
+ toLanguageCode,
7139
+ provider,
7140
+ modelId,
7141
+ credentials
7142
+ })
7143
+ )
7144
+ ),
7145
+ usageByChunk
6974
7146
  );
6975
7147
  translatedSegments.push(...batchResults.map((result) => result.translatedVtt));
6976
7148
  usageByChunk.push(...batchResults.map((result) => result.usage));
@@ -7023,6 +7195,14 @@ async function uploadVttToS3({
7023
7195
  }
7024
7196
  async function translateCaptions(assetId, trackId, toLanguageCode, options) {
7025
7197
  "use workflow";
7198
+ const collectedUsage = [];
7199
+ try {
7200
+ return await translateCaptionsInternal(assetId, trackId, toLanguageCode, options, collectedUsage);
7201
+ } catch (error) {
7202
+ rethrowWithTokenUsage(error, collectedUsage);
7203
+ }
7204
+ }
7205
+ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, options, collectedUsage) {
7026
7206
  const {
7027
7207
  provider = "openai",
7028
7208
  model,
@@ -7101,6 +7281,7 @@ async function translateCaptions(assetId, trackId, toLanguageCode, options) {
7101
7281
  });
7102
7282
  translatedVtt = result.translatedVtt;
7103
7283
  usage = result.usage;
7284
+ collectedUsage.push(result.usage);
7104
7285
  scrubbedCueCounts = result.scrubbedCueCounts;
7105
7286
  unexpectedKeyCount = result.unexpectedKeyCount;
7106
7287
  } catch (error) {