@mux/ai 0.29.0 → 0.31.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,114 +1,11 @@
1
1
  // src/workflows/ask-questions.ts
2
2
  import { generateText, Output } from "ai";
3
3
  import dedent2 from "dedent";
4
- import { z as z2 } from "zod";
4
+ import { z as z3 } from "zod";
5
5
 
6
- // src/lib/image-download.ts
7
- import pRetry, { AbortError } from "p-retry";
8
- var BASE64_CHUNK_SIZE = 32768;
9
- function bytesToBase64(bytes) {
10
- if (bytes.length === 0) {
11
- return "";
12
- }
13
- let binary = "";
14
- for (let i = 0; i < bytes.length; i += BASE64_CHUNK_SIZE) {
15
- const chunk = bytes.subarray(i, i + BASE64_CHUNK_SIZE);
16
- binary += String.fromCharCode(...chunk);
17
- }
18
- if (typeof globalThis.btoa !== "function") {
19
- throw new TypeError("Base64 encoder is not available in this environment.");
20
- }
21
- return globalThis.btoa(binary);
22
- }
23
- var DEFAULT_OPTIONS = {
24
- timeout: 1e4,
25
- retries: 3,
26
- retryDelay: 1e3,
27
- maxRetryDelay: 1e4,
28
- exponentialBackoff: true
29
- };
30
- async function downloadImageAsBase64(url, options = {}) {
31
- "use step";
32
- const opts = { ...DEFAULT_OPTIONS, ...options };
33
- let attemptCount = 0;
34
- return pRetry(
35
- async () => {
36
- attemptCount++;
37
- const controller = new AbortController();
38
- const timeoutId = setTimeout(() => controller.abort(), opts.timeout);
39
- try {
40
- const response = await fetch(url, {
41
- signal: controller.signal,
42
- headers: {
43
- "User-Agent": "@mux/ai image downloader"
44
- }
45
- });
46
- clearTimeout(timeoutId);
47
- if (!response.ok) {
48
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
49
- throw new AbortError(`HTTP ${response.status}: ${response.statusText}`);
50
- }
51
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
52
- }
53
- const contentType = response.headers.get("content-type");
54
- if (!contentType?.startsWith("image/")) {
55
- throw new AbortError(`Invalid content type: ${contentType}. Expected image/*`);
56
- }
57
- const arrayBuffer = await response.arrayBuffer();
58
- const buffer = new Uint8Array(arrayBuffer);
59
- if (buffer.length === 0) {
60
- throw new AbortError("Downloaded image is empty");
61
- }
62
- const base64Data = `data:${contentType};base64,${bytesToBase64(buffer)}`;
63
- return {
64
- base64Data,
65
- buffer,
66
- url,
67
- contentType,
68
- sizeBytes: buffer.length,
69
- attempts: attemptCount
70
- };
71
- } catch (error) {
72
- clearTimeout(timeoutId);
73
- if (error instanceof AbortError) {
74
- throw error;
75
- }
76
- if (error instanceof Error) {
77
- if (error.name === "AbortError") {
78
- throw new Error(`Request timeout after ${opts.timeout}ms`);
79
- }
80
- throw new Error(`Download failed: ${error.message}`);
81
- }
82
- throw new Error("Unknown download error");
83
- }
84
- },
85
- {
86
- retries: opts.retries,
87
- minTimeout: opts.retryDelay,
88
- maxTimeout: opts.maxRetryDelay,
89
- factor: opts.exponentialBackoff ? 2 : 1,
90
- randomize: true,
91
- // Add jitter to prevent thundering herd
92
- onFailedAttempt: (error) => {
93
- console.warn(`Image download attempt ${error.attemptNumber} failed for ${url}`);
94
- if (error.retriesLeft > 0) {
95
- console.warn(`Retrying... (${error.retriesLeft} attempts left)`);
96
- }
97
- }
98
- }
99
- );
100
- }
101
- async function downloadImagesAsBase64(urls, options = {}, maxConcurrent = 5) {
102
- "use step";
103
- const results = [];
104
- for (let i = 0; i < urls.length; i += maxConcurrent) {
105
- const batch = urls.slice(i, i + maxConcurrent);
106
- const batchPromises = batch.map((url) => downloadImageAsBase64(url, options));
107
- const batchResults = await Promise.all(batchPromises);
108
- results.push(...batchResults);
109
- }
110
- return results;
111
- }
6
+ // src/lib/content-policy-error.ts
7
+ import { APICallError, NoObjectGeneratedError, RetryError } from "ai";
8
+ import { z as z2 } from "zod";
112
9
 
113
10
  // src/lib/prompt-fragments.ts
114
11
  import dedent from "dedent";
@@ -179,7 +76,7 @@ var EnvSchema = z.object({
179
76
  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"),
180
77
  // Eval config
181
78
  MUX_AI_EVAL_MODEL_SET: optionalString("Eval model selection mode.", "Choose between 'default' (provider defaults only) or 'all' (all configured models)"),
182
- 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')"),
79
+ 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')"),
183
80
  // AI Providers
184
81
  OPENAI_API_KEY: optionalString("OpenAI API key for OpenAI-backed workflows.", "OpenAI API key"),
185
82
  ANTHROPIC_API_KEY: optionalString("Anthropic API key for Claude-backed workflows.", "Anthropic API key"),
@@ -498,7 +395,8 @@ var TOKEN_USAGE_FIELDS = [
498
395
  "outputTokens",
499
396
  "totalTokens",
500
397
  "reasoningTokens",
501
- "cachedInputTokens"
398
+ "cachedInputTokens",
399
+ "cacheWriteTokens"
502
400
  ];
503
401
  function isDefinedTokenUsageValue(value) {
504
402
  return typeof value === "number";
@@ -540,6 +438,12 @@ function getErrorTokenUsage(error) {
540
438
  extracted.cachedInputTokens = cacheReadTokens;
541
439
  }
542
440
  }
441
+ if (extracted.cacheWriteTokens === void 0) {
442
+ const cacheWriteTokens = source.inputTokenDetails?.cacheWriteTokens;
443
+ if (typeof cacheWriteTokens === "number") {
444
+ extracted.cacheWriteTokens = cacheWriteTokens;
445
+ }
446
+ }
543
447
  return Object.keys(extracted).length > 0 ? extracted : void 0;
544
448
  }
545
449
  function rethrowWithTokenUsage(error, collectedUsage) {
@@ -565,6 +469,9 @@ var MuxAiError = class extends Error {
565
469
  this.publicMessage = message;
566
470
  this.retryable = opts?.retryable ?? false;
567
471
  }
472
+ static is(value) {
473
+ return typeof value === "object" && value !== null && "__robots_error" in value && value.__robots_error === true;
474
+ }
568
475
  };
569
476
  function extractErrorDetail(error) {
570
477
  if (error instanceof Error && error.message) {
@@ -582,7 +489,7 @@ function extractErrorDetail(error) {
582
489
  return "Unknown error";
583
490
  }
584
491
  function wrapError(error, message) {
585
- if (error instanceof MuxAiError) {
492
+ if (MuxAiError.is(error)) {
586
493
  throw error;
587
494
  }
588
495
  const rawDetail = extractErrorDetail(error);
@@ -600,6 +507,260 @@ function wrapError(error, message) {
600
507
  throw wrapped;
601
508
  }
602
509
 
510
+ // src/lib/content-policy-error.ts
511
+ var PolicyTokenSchema = z2.string().trim().min(1).max(64).regex(/^[A-Z0-9_]+$/);
512
+ var SafetyRatingSchema = z2.object({
513
+ category: PolicyTokenSchema.nullish(),
514
+ blocked: z2.boolean().nullish()
515
+ });
516
+ var ContentPolicyResponseSchema = z2.object({
517
+ promptFeedback: z2.object({
518
+ blockReason: PolicyTokenSchema.nullish(),
519
+ safetyRatings: z2.array(SafetyRatingSchema).max(32).nullish()
520
+ }).nullish(),
521
+ candidates: z2.array(z2.object({
522
+ finishReason: PolicyTokenSchema.nullish(),
523
+ safetyRatings: z2.array(SafetyRatingSchema).max(32).nullish()
524
+ })).max(8).nullish()
525
+ });
526
+ var ContentPolicyUsageResponseSchema = z2.object({
527
+ usageMetadata: z2.object({
528
+ promptTokenCount: z2.number().finite().nonnegative().nullish(),
529
+ candidatesTokenCount: z2.number().finite().nonnegative().nullish(),
530
+ totalTokenCount: z2.number().finite().nonnegative().nullish(),
531
+ cachedContentTokenCount: z2.number().finite().nonnegative().nullish(),
532
+ thoughtsTokenCount: z2.number().finite().nonnegative().nullish()
533
+ }).nullish()
534
+ });
535
+ var CONTENT_POLICY_REASONS = /* @__PURE__ */ new Set([
536
+ "BLOCKLIST",
537
+ "ESCALATION",
538
+ "IMAGE_PROHIBITED_CONTENT",
539
+ "IMAGE_RECITATION",
540
+ "IMAGE_SAFETY",
541
+ "JAILBREAK",
542
+ "MODEL_ARMOR",
543
+ "PROHIBITED_CONTENT",
544
+ "RECITATION",
545
+ "SAFETY",
546
+ "SPII"
547
+ ]);
548
+ async function withContentPolicyErrorHandling(operation) {
549
+ try {
550
+ return await operation();
551
+ } catch (error) {
552
+ rethrowContentPolicyError(error);
553
+ }
554
+ }
555
+ function rethrowContentPolicyError(error) {
556
+ const block = extractContentPolicyBlock(error);
557
+ if (!block) {
558
+ throw error;
559
+ }
560
+ throwContentPolicyError(block, getContentPolicyTokenUsage(error));
561
+ }
562
+ function getGeneratedOutputWithContentPolicyHandling(response) {
563
+ if (response.finishReason === "content-filter") {
564
+ const rawReason = PolicyTokenSchema.safeParse(response.rawFinishReason);
565
+ const reason = rawReason.success && isContentPolicyReason(rawReason.data) ? rawReason.data : "CONTENT_FILTER";
566
+ throwContentPolicyError({ reason }, getErrorTokenUsage(response));
567
+ }
568
+ return response.output;
569
+ }
570
+ function throwContentPolicyError(block, usage) {
571
+ const category = block.category ? `; category: ${block.category}` : "";
572
+ const contentPolicyError = new MuxAiError(
573
+ `The supplied content was blocked by a content policy (reason: ${block.reason}${category}).`,
574
+ {
575
+ type: "content_policy_error",
576
+ retryable: false
577
+ }
578
+ );
579
+ if (usage) {
580
+ contentPolicyError.usage = usage;
581
+ }
582
+ throw contentPolicyError;
583
+ }
584
+ function extractContentPolicyBlock(error) {
585
+ if (RetryError.isInstance(error)) {
586
+ return extractContentPolicyBlock(error.lastError);
587
+ }
588
+ if (NoObjectGeneratedError.isInstance(error) && error.finishReason === "content-filter") {
589
+ return { reason: "CONTENT_FILTER" };
590
+ }
591
+ if (!APICallError.isInstance(error) || !error.responseBody) {
592
+ return void 0;
593
+ }
594
+ try {
595
+ const parsed = ContentPolicyResponseSchema.safeParse(JSON.parse(error.responseBody));
596
+ if (!parsed.success) {
597
+ return void 0;
598
+ }
599
+ const promptReason = parsed.data.promptFeedback?.blockReason;
600
+ if (isContentPolicyReason(promptReason)) {
601
+ return {
602
+ reason: promptReason,
603
+ category: findBlockedCategory(parsed.data.promptFeedback?.safetyRatings)
604
+ };
605
+ }
606
+ for (const candidate of parsed.data.candidates ?? []) {
607
+ if (isContentPolicyReason(candidate.finishReason)) {
608
+ return {
609
+ reason: candidate.finishReason,
610
+ category: findBlockedCategory(candidate.safetyRatings)
611
+ };
612
+ }
613
+ }
614
+ } catch {
615
+ return void 0;
616
+ }
617
+ return void 0;
618
+ }
619
+ function isContentPolicyReason(reason) {
620
+ return Boolean(reason && CONTENT_POLICY_REASONS.has(reason));
621
+ }
622
+ function getContentPolicyTokenUsage(error) {
623
+ const errorUsage = getErrorTokenUsage(error);
624
+ if (errorUsage) {
625
+ return errorUsage;
626
+ }
627
+ if (RetryError.isInstance(error)) {
628
+ return getContentPolicyTokenUsage(error.lastError);
629
+ }
630
+ if (!APICallError.isInstance(error) || !error.responseBody) {
631
+ return void 0;
632
+ }
633
+ try {
634
+ const parsed = ContentPolicyUsageResponseSchema.safeParse(JSON.parse(error.responseBody));
635
+ const usage = parsed.success ? parsed.data.usageMetadata : void 0;
636
+ if (!usage) {
637
+ return void 0;
638
+ }
639
+ const inputTokens = usage.promptTokenCount ?? 0;
640
+ const reasoningTokens = usage.thoughtsTokenCount ?? 0;
641
+ const outputTokens = (usage.candidatesTokenCount ?? 0) + reasoningTokens;
642
+ return {
643
+ inputTokens,
644
+ outputTokens,
645
+ totalTokens: usage.totalTokenCount ?? inputTokens + outputTokens,
646
+ reasoningTokens,
647
+ cachedInputTokens: usage.cachedContentTokenCount ?? 0
648
+ };
649
+ } catch {
650
+ return void 0;
651
+ }
652
+ }
653
+ function findBlockedCategory(ratings) {
654
+ return ratings?.find((rating) => rating.blocked === true)?.category ?? void 0;
655
+ }
656
+
657
+ // src/lib/image-download.ts
658
+ import pRetry, { AbortError } from "p-retry";
659
+ var BASE64_CHUNK_SIZE = 32768;
660
+ function bytesToBase64(bytes) {
661
+ if (bytes.length === 0) {
662
+ return "";
663
+ }
664
+ let binary = "";
665
+ for (let i = 0; i < bytes.length; i += BASE64_CHUNK_SIZE) {
666
+ const chunk = bytes.subarray(i, i + BASE64_CHUNK_SIZE);
667
+ binary += String.fromCharCode(...chunk);
668
+ }
669
+ if (typeof globalThis.btoa !== "function") {
670
+ throw new TypeError("Base64 encoder is not available in this environment.");
671
+ }
672
+ return globalThis.btoa(binary);
673
+ }
674
+ var DEFAULT_OPTIONS = {
675
+ timeout: 1e4,
676
+ retries: 3,
677
+ retryDelay: 1e3,
678
+ maxRetryDelay: 1e4,
679
+ exponentialBackoff: true
680
+ };
681
+ async function downloadImageAsBase64(url, options = {}) {
682
+ "use step";
683
+ const opts = { ...DEFAULT_OPTIONS, ...options };
684
+ let attemptCount = 0;
685
+ return pRetry(
686
+ async () => {
687
+ attemptCount++;
688
+ const controller = new AbortController();
689
+ const timeoutId = setTimeout(() => controller.abort(), opts.timeout);
690
+ try {
691
+ const response = await fetch(url, {
692
+ signal: controller.signal,
693
+ headers: {
694
+ "User-Agent": "@mux/ai image downloader"
695
+ }
696
+ });
697
+ clearTimeout(timeoutId);
698
+ if (!response.ok) {
699
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
700
+ throw new AbortError(`HTTP ${response.status}: ${response.statusText}`);
701
+ }
702
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
703
+ }
704
+ const contentType = response.headers.get("content-type");
705
+ if (!contentType?.startsWith("image/")) {
706
+ throw new AbortError(`Invalid content type: ${contentType}. Expected image/*`);
707
+ }
708
+ const arrayBuffer = await response.arrayBuffer();
709
+ const buffer = new Uint8Array(arrayBuffer);
710
+ if (buffer.length === 0) {
711
+ throw new AbortError("Downloaded image is empty");
712
+ }
713
+ const base64Data = `data:${contentType};base64,${bytesToBase64(buffer)}`;
714
+ return {
715
+ base64Data,
716
+ buffer,
717
+ url,
718
+ contentType,
719
+ sizeBytes: buffer.length,
720
+ attempts: attemptCount
721
+ };
722
+ } catch (error) {
723
+ clearTimeout(timeoutId);
724
+ if (error instanceof AbortError) {
725
+ throw error;
726
+ }
727
+ if (error instanceof Error) {
728
+ if (error.name === "AbortError") {
729
+ throw new Error(`Request timeout after ${opts.timeout}ms`);
730
+ }
731
+ throw new Error(`Download failed: ${error.message}`);
732
+ }
733
+ throw new Error("Unknown download error");
734
+ }
735
+ },
736
+ {
737
+ retries: opts.retries,
738
+ minTimeout: opts.retryDelay,
739
+ maxTimeout: opts.maxRetryDelay,
740
+ factor: opts.exponentialBackoff ? 2 : 1,
741
+ randomize: true,
742
+ // Add jitter to prevent thundering herd
743
+ onFailedAttempt: (error) => {
744
+ console.warn(`Image download attempt ${error.attemptNumber} failed for ${url}`);
745
+ if (error.retriesLeft > 0) {
746
+ console.warn(`Retrying... (${error.retriesLeft} attempts left)`);
747
+ }
748
+ }
749
+ }
750
+ );
751
+ }
752
+ async function downloadImagesAsBase64(urls, options = {}, maxConcurrent = 5) {
753
+ "use step";
754
+ const results = [];
755
+ for (let i = 0; i < urls.length; i += maxConcurrent) {
756
+ const batch = urls.slice(i, i + maxConcurrent);
757
+ const batchPromises = batch.map((url) => downloadImageAsBase64(url, options));
758
+ const batchResults = await Promise.all(batchPromises);
759
+ results.push(...batchResults);
760
+ }
761
+ return results;
762
+ }
763
+
603
764
  // src/lib/providers.ts
604
765
  import { createAnthropic } from "@ai-sdk/anthropic";
605
766
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
@@ -936,7 +1097,8 @@ async function resolveMuxSigningContext(credentials) {
936
1097
 
937
1098
  // src/lib/providers.ts
938
1099
  var DEFAULT_LANGUAGE_MODELS = {
939
- openai: "gpt-5.1",
1100
+ // GPT-5.6 models default to medium reasoning when no effort is specified.
1101
+ openai: "gpt-5.6-luna",
940
1102
  anthropic: "claude-sonnet-4-5",
941
1103
  google: "gemini-3-flash-preview"
942
1104
  };
@@ -945,11 +1107,20 @@ var DEFAULT_EMBEDDING_MODELS = {
945
1107
  google: "gemini-embedding-001"
946
1108
  };
947
1109
  var LANGUAGE_MODELS = {
948
- openai: ["gpt-5.1", "gpt-5-mini"],
1110
+ openai: ["gpt-5.6-luna", "gpt-5.1", "gpt-5-mini"],
949
1111
  anthropic: ["claude-sonnet-4-5"],
950
1112
  google: ["gemini-3-flash-preview", "gemini-3.1-flash-lite", "gemini-2.5-flash"]
951
1113
  };
952
1114
  var LANGUAGE_MODEL_DEPRECATIONS = [
1115
+ {
1116
+ provider: "openai",
1117
+ modelId: "gpt-5.1",
1118
+ replacementModelId: "gpt-5.6-luna",
1119
+ phase: "warn",
1120
+ deprecatedOn: "2026-08-01",
1121
+ sunsetOn: "2026-10-01",
1122
+ reason: "GPT-5.6 Luna with its default medium reasoning effort is the new OpenAI default."
1123
+ },
953
1124
  {
954
1125
  provider: "google",
955
1126
  modelId: "gemini-2.5-flash",
@@ -1010,7 +1181,7 @@ function parseEvalModelPair(value) {
1010
1181
  const modelId = modelIdRaw?.trim();
1011
1182
  if (!provider || !modelId) {
1012
1183
  throw new Error(
1013
- `Invalid eval model pair "${value}". Use "provider:model" (example: "openai:gpt-5.1").`
1184
+ `Invalid eval model pair "${value}". Use "provider:model" (example: "openai:gpt-5.6-luna").`
1014
1185
  );
1015
1186
  }
1016
1187
  if (!isSupportedProvider(provider)) {
@@ -1259,13 +1430,23 @@ function createLanguageSection(languageName) {
1259
1430
  }
1260
1431
 
1261
1432
  // src/lib/retry.ts
1433
+ import { DownloadError } from "ai";
1262
1434
  var DEFAULT_RETRY_OPTIONS = {
1263
1435
  maxRetries: 3,
1264
1436
  baseDelay: 2e3,
1265
1437
  maxDelay: 1e4
1266
1438
  };
1267
1439
  function defaultShouldRetry(error, _attempt) {
1268
- return Boolean(error.message && error.message.includes("Timeout while downloading"));
1440
+ if (error.message.includes("Timeout while downloading")) {
1441
+ return true;
1442
+ }
1443
+ const isDownloadError = DownloadError.isInstance(error);
1444
+ const isSerializedDownloadError = error.name === "AI_DownloadError";
1445
+ if (!isDownloadError && !isSerializedDownloadError) {
1446
+ return false;
1447
+ }
1448
+ const statusCode = error.statusCode;
1449
+ return statusCode === void 0 || statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
1269
1450
  }
1270
1451
  function calculateDelay(attempt, baseDelay, maxDelay) {
1271
1452
  const exponentialDelay = baseDelay * 2 ** (attempt - 1);
@@ -1283,19 +1464,20 @@ async function withRetry(fn, {
1283
1464
  try {
1284
1465
  return await fn();
1285
1466
  } catch (error) {
1286
- lastError = error instanceof Error ? error : new Error(String(error));
1467
+ lastError = error;
1468
+ const retryError = error instanceof Error ? error : new Error(String(error));
1287
1469
  const isLastAttempt = attempt === maxRetries;
1288
- if (isLastAttempt || !shouldRetry(lastError, attempt + 1)) {
1289
- throw lastError;
1470
+ if (isLastAttempt || !shouldRetry(retryError, attempt + 1)) {
1471
+ throw error;
1290
1472
  }
1291
1473
  const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);
1292
1474
  console.warn(
1293
- `Attempt ${attempt + 1} failed: ${lastError.message}. Retrying in ${Math.round(delay)}ms...`
1475
+ `Attempt ${attempt + 1} failed: ${retryError.message}. Retrying in ${Math.round(delay)}ms...`
1294
1476
  );
1295
1477
  await new Promise((resolve) => setTimeout(resolve, delay));
1296
1478
  }
1297
1479
  }
1298
- throw lastError || new Error("Retry failed with unknown error");
1480
+ throw lastError ?? new Error("Retry failed with unknown error");
1299
1481
  }
1300
1482
 
1301
1483
  // src/lib/workflow-scope.ts
@@ -2074,17 +2256,20 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
2074
2256
  }
2075
2257
 
2076
2258
  // src/workflows/ask-questions.ts
2077
- var questionAnswerSchema = z2.object({
2078
- question: z2.string().describe("The full text of the original question"),
2079
- answer: z2.string().nullable(),
2080
- confidence: z2.number(),
2081
- reasoning: z2.string().max(1e3),
2082
- skipped: z2.boolean()
2259
+ var ASK_QUESTIONS_MAX_QUESTION_LENGTH = 600;
2260
+ var ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH = 150;
2261
+ var ASK_QUESTIONS_DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH = 500;
2262
+ var questionAnswerSchema = z3.object({
2263
+ question: z3.string().describe("The full text of the original question"),
2264
+ answer: z3.string().nullable(),
2265
+ confidence: z3.number(),
2266
+ reasoning: z3.string().max(1e3),
2267
+ skipped: z3.boolean()
2083
2268
  });
2084
2269
  var SKIP_SENTINEL = "__SKIPPED__";
2085
2270
  function createAskQuestionsSchema(answerSchema) {
2086
- return z2.object({
2087
- answers: z2.array(
2271
+ return z3.object({
2272
+ answers: z3.array(
2088
2273
  questionAnswerSchema.extend({
2089
2274
  answer: answerSchema
2090
2275
  })
@@ -2434,12 +2619,12 @@ function buildResponseSchemaForQuestions(normalizedQuestions, maxFreeFormAnswerL
2434
2619
  SKIP_SENTINEL.length,
2435
2620
  longestConstrained
2436
2621
  );
2437
- return createAskQuestionsSchema(z2.string().min(1).max(schemaMax));
2622
+ return createAskQuestionsSchema(z3.string().min(1).max(schemaMax));
2438
2623
  }
2439
2624
  const allowed = Array.from(
2440
2625
  new Set(normalizedQuestions.flatMap(allowedAnswersForQuestion))
2441
2626
  );
2442
- return createAskQuestionsSchema(z2.enum([...allowed, SKIP_SENTINEL]));
2627
+ return createAskQuestionsSchema(z3.enum([...allowed, SKIP_SENTINEL]));
2443
2628
  }
2444
2629
  async function analyzeQuestions({
2445
2630
  provider,
@@ -2457,7 +2642,7 @@ async function analyzeQuestions({
2457
2642
  normalizedQuestions,
2458
2643
  maxFreeFormAnswerLength
2459
2644
  );
2460
- const response = await generateText({
2645
+ const response = await withContentPolicyErrorHandling(() => generateText({
2461
2646
  model,
2462
2647
  output: Output.object({ schema: responseSchema }),
2463
2648
  experimental_telemetry: { isEnabled: true },
@@ -2474,11 +2659,12 @@ async function analyzeQuestions({
2474
2659
  ] : userPrompt
2475
2660
  }
2476
2661
  ]
2477
- });
2478
- if (!response.output) {
2662
+ }));
2663
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
2664
+ if (!output) {
2479
2665
  throw new Error("Ask-questions output missing");
2480
2666
  }
2481
- const parsed = responseSchema.parse(response.output);
2667
+ const parsed = responseSchema.parse(output);
2482
2668
  const unexpectedRootKeys = detectUnexpectedKeysFromRawText(
2483
2669
  response.text,
2484
2670
  responseSchema.keyof().options
@@ -2500,7 +2686,8 @@ async function analyzeQuestions({
2500
2686
  outputTokens: response.usage.outputTokens,
2501
2687
  totalTokens: response.usage.totalTokens,
2502
2688
  reasoningTokens: response.usage.reasoningTokens,
2503
- cachedInputTokens: response.usage.cachedInputTokens
2689
+ cachedInputTokens: response.usage.cachedInputTokens,
2690
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
2504
2691
  },
2505
2692
  unexpectedRootKeys,
2506
2693
  unexpectedAnswerKeys
@@ -2519,7 +2706,6 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
2519
2706
  if (!questions || questions.length === 0) {
2520
2707
  throw new MuxAiError("At least one question must be provided.", { type: "validation_error" });
2521
2708
  }
2522
- const MAX_QUESTION_LENGTH = 500;
2523
2709
  questions.forEach((q, idx) => {
2524
2710
  if (!q.question || typeof q.question !== "string" || !q.question.trim()) {
2525
2711
  throw new MuxAiError(
@@ -2527,15 +2713,14 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
2527
2713
  { type: "validation_error" }
2528
2714
  );
2529
2715
  }
2530
- if (q.question.length > MAX_QUESTION_LENGTH) {
2716
+ if (q.question.length > ASK_QUESTIONS_MAX_QUESTION_LENGTH) {
2531
2717
  throw new MuxAiError(
2532
- `Question at index ${idx} exceeds the ${MAX_QUESTION_LENGTH}-character limit (received ${q.question.length}).`,
2718
+ `Question at index ${idx} exceeds the ${ASK_QUESTIONS_MAX_QUESTION_LENGTH}-character limit (received ${q.question.length}).`,
2533
2719
  { type: "validation_error" }
2534
2720
  );
2535
2721
  }
2536
2722
  });
2537
- const DEFAULT_MAX_ANSWER_OPTION_LENGTH = 150;
2538
- const maxAnswerOptionLength = options?.maxAnswerOptionLength ?? DEFAULT_MAX_ANSWER_OPTION_LENGTH;
2723
+ const maxAnswerOptionLength = options?.maxAnswerOptionLength ?? ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH;
2539
2724
  if (!Number.isFinite(maxAnswerOptionLength) || maxAnswerOptionLength <= 0) {
2540
2725
  throw new MuxAiError(
2541
2726
  `maxAnswerOptionLength must be a positive number (received ${maxAnswerOptionLength}).`,
@@ -2554,8 +2739,7 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
2554
2739
  }
2555
2740
  }
2556
2741
  });
2557
- const DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH = 500;
2558
- const maxFreeFormAnswerLength = options?.maxFreeFormAnswerLength ?? DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH;
2742
+ const maxFreeFormAnswerLength = options?.maxFreeFormAnswerLength ?? ASK_QUESTIONS_DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH;
2559
2743
  if (!Number.isFinite(maxFreeFormAnswerLength) || maxFreeFormAnswerLength <= 0) {
2560
2744
  throw new MuxAiError(
2561
2745
  `maxFreeFormAnswerLength must be a positive number (received ${maxFreeFormAnswerLength}).`,
@@ -2757,11 +2941,11 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
2757
2941
  // src/workflows/burned-in-captions.ts
2758
2942
  import { generateText as generateText2, Output as Output2 } from "ai";
2759
2943
  import dedent3 from "dedent";
2760
- import { z as z3 } from "zod";
2761
- var burnedInCaptionsSchema = z3.object({
2762
- hasBurnedInCaptions: z3.boolean(),
2763
- confidence: z3.number(),
2764
- detectedLanguage: z3.string().max(100).nullable()
2944
+ import { z as z4 } from "zod";
2945
+ var burnedInCaptionsSchema = z4.object({
2946
+ hasBurnedInCaptions: z4.boolean(),
2947
+ confidence: z4.number(),
2948
+ detectedLanguage: z4.string().max(100).nullable()
2765
2949
  });
2766
2950
  var SYSTEM_PROMPT2 = promptDedent`
2767
2951
  <role>
@@ -2871,7 +3055,7 @@ async function analyzeStoryboard({
2871
3055
  }) {
2872
3056
  "use step";
2873
3057
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
2874
- const response = await generateText2({
3058
+ const response = await withContentPolicyErrorHandling(() => generateText2({
2875
3059
  model,
2876
3060
  output: Output2.object({ schema: burnedInCaptionsSchema }),
2877
3061
  experimental_telemetry: { isEnabled: true },
@@ -2888,22 +3072,24 @@ async function analyzeStoryboard({
2888
3072
  ]
2889
3073
  }
2890
3074
  ]
2891
- });
3075
+ }));
3076
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
2892
3077
  const unexpectedKeys = detectUnexpectedKeysFromRawText(
2893
3078
  response.text,
2894
3079
  burnedInCaptionsSchema.keyof().options
2895
3080
  );
2896
3081
  return {
2897
3082
  result: {
2898
- ...response.output,
2899
- confidence: Math.min(1, Math.max(0, response.output.confidence))
3083
+ ...output,
3084
+ confidence: Math.min(1, Math.max(0, output.confidence))
2900
3085
  },
2901
3086
  usage: {
2902
3087
  inputTokens: response.usage.inputTokens,
2903
3088
  outputTokens: response.usage.outputTokens,
2904
3089
  totalTokens: response.usage.totalTokens,
2905
3090
  reasoningTokens: response.usage.reasoningTokens,
2906
- cachedInputTokens: response.usage.cachedInputTokens
3091
+ cachedInputTokens: response.usage.cachedInputTokens,
3092
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
2907
3093
  },
2908
3094
  unexpectedKeys
2909
3095
  };
@@ -2960,14 +3146,14 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
2960
3146
  credentials
2961
3147
  });
2962
3148
  } else {
2963
- analysisResponse = await analyzeStoryboard({
3149
+ analysisResponse = await withRetry(() => analyzeStoryboard({
2964
3150
  imageDataUrl: imageUrl,
2965
3151
  provider: modelConfig.provider,
2966
3152
  modelId: modelConfig.modelId,
2967
3153
  userPrompt,
2968
3154
  systemPrompt: SYSTEM_PROMPT2,
2969
3155
  credentials
2970
- });
3156
+ }));
2971
3157
  }
2972
3158
  collectedUsage.push(analysisResponse.usage);
2973
3159
  if (!analysisResponse.result) {
@@ -3001,13 +3187,13 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
3001
3187
  // src/workflows/chapters.ts
3002
3188
  import { generateText as generateText3, Output as Output3 } from "ai";
3003
3189
  import dedent4 from "dedent";
3004
- import { z as z4 } from "zod";
3005
- var chapterSchema = z4.object({
3006
- startTime: z4.number(),
3007
- title: z4.string().max(300)
3190
+ import { z as z5 } from "zod";
3191
+ var chapterSchema = z5.object({
3192
+ startTime: z5.number(),
3193
+ title: z5.string().max(300)
3008
3194
  });
3009
- var chaptersSchema = z4.object({
3010
- chapters: z4.array(chapterSchema)
3195
+ var chaptersSchema = z5.object({
3196
+ chapters: z5.array(chapterSchema)
3011
3197
  });
3012
3198
  async function generateChaptersWithAI({
3013
3199
  provider,
@@ -3019,21 +3205,24 @@ async function generateChaptersWithAI({
3019
3205
  "use step";
3020
3206
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
3021
3207
  const response = await withRetry(
3022
- () => generateText3({
3023
- model,
3024
- output: Output3.object({ schema: chaptersSchema }),
3025
- messages: [
3026
- {
3027
- role: "system",
3028
- content: systemPrompt
3029
- },
3030
- {
3031
- role: "user",
3032
- content: userPrompt
3033
- }
3034
- ]
3035
- })
3208
+ () => withContentPolicyErrorHandling(
3209
+ () => generateText3({
3210
+ model,
3211
+ output: Output3.object({ schema: chaptersSchema }),
3212
+ messages: [
3213
+ {
3214
+ role: "system",
3215
+ content: systemPrompt
3216
+ },
3217
+ {
3218
+ role: "user",
3219
+ content: userPrompt
3220
+ }
3221
+ ]
3222
+ })
3223
+ )
3036
3224
  );
3225
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
3037
3226
  const unexpectedRootKeys = detectUnexpectedKeysFromRawText(
3038
3227
  response.text,
3039
3228
  chaptersSchema.keyof().options
@@ -3049,13 +3238,14 @@ async function generateChaptersWithAI({
3049
3238
  } catch {
3050
3239
  }
3051
3240
  return {
3052
- chapters: response.output,
3241
+ chapters: output,
3053
3242
  usage: {
3054
3243
  inputTokens: response.usage.inputTokens,
3055
3244
  outputTokens: response.usage.outputTokens,
3056
3245
  totalTokens: response.usage.totalTokens,
3057
3246
  reasoningTokens: response.usage.reasoningTokens,
3058
- cachedInputTokens: response.usage.cachedInputTokens
3247
+ cachedInputTokens: response.usage.cachedInputTokens,
3248
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
3059
3249
  },
3060
3250
  unexpectedRootKeys,
3061
3251
  unexpectedChapterKeys
@@ -3311,7 +3501,7 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
3311
3501
 
3312
3502
  // src/workflows/edit-captions.ts
3313
3503
  import { generateText as generateText4, Output as Output4 } from "ai";
3314
- import { z as z5 } from "zod";
3504
+ import { z as z6 } from "zod";
3315
3505
 
3316
3506
  // src/lib/mux-tracks.ts
3317
3507
  async function fetchVttFromMux(vttUrl) {
@@ -3594,8 +3784,8 @@ async function createPresignedGetUrlWithStorageAdapter(input, adapter) {
3594
3784
  }
3595
3785
 
3596
3786
  // src/workflows/edit-captions.ts
3597
- var profanityDetectionSchema = z5.object({
3598
- profanity: z5.array(z5.string().max(100)).describe(
3787
+ var profanityDetectionSchema = z6.object({
3788
+ profanity: z6.array(z6.string().max(100)).describe(
3599
3789
  "Unique profane words or short phrases exactly as they appear in the transcript text. Include each distinct form only once (e.g., if 'fuck' and 'fucking' both appear, list both)."
3600
3790
  )
3601
3791
  });
@@ -3772,7 +3962,7 @@ async function identifyProfanityWithAI({
3772
3962
  tag: "transcript",
3773
3963
  content: plainText
3774
3964
  });
3775
- const response = await generateText4({
3965
+ const response = await withContentPolicyErrorHandling(() => generateText4({
3776
3966
  model,
3777
3967
  output: Output4.object({ schema: profanityDetectionSchema }),
3778
3968
  messages: [
@@ -3787,19 +3977,21 @@ async function identifyProfanityWithAI({
3787
3977
  ${transcriptSection}`
3788
3978
  }
3789
3979
  ]
3790
- });
3980
+ }));
3981
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
3791
3982
  const unexpectedKeys = detectUnexpectedKeysFromRawText(
3792
3983
  response.text,
3793
3984
  profanityDetectionSchema.keyof().options
3794
3985
  );
3795
3986
  return {
3796
- profanity: response.output.profanity,
3987
+ profanity: output.profanity,
3797
3988
  usage: {
3798
3989
  inputTokens: response.usage.inputTokens,
3799
3990
  outputTokens: response.usage.outputTokens,
3800
3991
  totalTokens: response.usage.totalTokens,
3801
3992
  reasoningTokens: response.usage.reasoningTokens,
3802
- cachedInputTokens: response.usage.cachedInputTokens
3993
+ cachedInputTokens: response.usage.cachedInputTokens,
3994
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
3803
3995
  },
3804
3996
  unexpectedKeys
3805
3997
  };
@@ -4442,7 +4634,7 @@ async function generateVideoEmbeddings(assetId, options = {}) {
4442
4634
  // src/workflows/engagement-insights.ts
4443
4635
  import { generateText as generateText5, Output as Output5 } from "ai";
4444
4636
  import dedent5 from "dedent";
4445
- import { z as z6 } from "zod";
4637
+ import { z as z7 } from "zod";
4446
4638
 
4447
4639
  // src/primitives/heatmap.ts
4448
4640
  async function getHeatmapForAsset(assetId, options = {}) {
@@ -4589,20 +4781,20 @@ async function getShotsForAsset(assetId, options = {}) {
4589
4781
  }
4590
4782
 
4591
4783
  // src/workflows/engagement-insights.ts
4592
- var aiMomentInsightSchema = z6.object({
4593
- hotspotIndex: z6.number(),
4594
- startMs: z6.number(),
4595
- endMs: z6.number(),
4596
- timestamp: z6.string(),
4597
- engagementScore: z6.number(),
4598
- insight: z6.string().max(1e3)
4784
+ var aiMomentInsightSchema = z7.object({
4785
+ hotspotIndex: z7.number(),
4786
+ startMs: z7.number(),
4787
+ endMs: z7.number(),
4788
+ timestamp: z7.string(),
4789
+ engagementScore: z7.number(),
4790
+ insight: z7.string().max(1e3)
4599
4791
  });
4600
- var aiOverallInsightSchema = z6.object({
4601
- summary: z6.string().max(2e3),
4602
- trends: z6.array(z6.string().max(500))
4792
+ var aiOverallInsightSchema = z7.object({
4793
+ summary: z7.string().max(2e3),
4794
+ trends: z7.array(z7.string().max(500))
4603
4795
  });
4604
- var engagementInsightsSchema = z6.object({
4605
- momentInsights: z6.array(aiMomentInsightSchema),
4796
+ var engagementInsightsSchema = z7.object({
4797
+ momentInsights: z7.array(aiMomentInsightSchema),
4606
4798
  overallInsight: aiOverallInsightSchema
4607
4799
  });
4608
4800
  var SYSTEM_PROMPT4 = promptDedent`
@@ -4864,26 +5056,29 @@ async function generateInsightsWithAI(provider, modelId, systemPrompt, userPromp
4864
5056
  "use step";
4865
5057
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
4866
5058
  const response = await withRetry(
4867
- () => generateText5({
4868
- model,
4869
- output: Output5.object({ schema: engagementInsightsSchema }),
4870
- experimental_telemetry: { isEnabled: true },
4871
- messages: [
4872
- {
4873
- role: "system",
4874
- content: systemPrompt
4875
- },
4876
- {
4877
- role: "user",
4878
- content: [
4879
- { type: "text", text: userPrompt },
4880
- ...imageUrls.map((url) => ({ type: "image", image: url }))
4881
- ]
4882
- }
4883
- ]
4884
- })
5059
+ () => withContentPolicyErrorHandling(
5060
+ () => generateText5({
5061
+ model,
5062
+ output: Output5.object({ schema: engagementInsightsSchema }),
5063
+ experimental_telemetry: { isEnabled: true },
5064
+ messages: [
5065
+ {
5066
+ role: "system",
5067
+ content: systemPrompt
5068
+ },
5069
+ {
5070
+ role: "user",
5071
+ content: [
5072
+ { type: "text", text: userPrompt },
5073
+ ...imageUrls.map((url) => ({ type: "image", image: url }))
5074
+ ]
5075
+ }
5076
+ ]
5077
+ })
5078
+ )
4885
5079
  );
4886
- if (!response.output) {
5080
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
5081
+ if (!output) {
4887
5082
  throw new Error("AI returned empty or unparseable response");
4888
5083
  }
4889
5084
  const unexpectedRootKeys = detectUnexpectedKeysFromRawText(
@@ -4906,13 +5101,14 @@ async function generateInsightsWithAI(provider, modelId, systemPrompt, userPromp
4906
5101
  } catch {
4907
5102
  }
4908
5103
  return {
4909
- result: response.output,
5104
+ result: output,
4910
5105
  usage: {
4911
5106
  inputTokens: response.usage.inputTokens,
4912
5107
  outputTokens: response.usage.outputTokens,
4913
5108
  totalTokens: response.usage.totalTokens,
4914
5109
  reasoningTokens: response.usage.reasoningTokens,
4915
- cachedInputTokens: response.usage.cachedInputTokens
5110
+ cachedInputTokens: response.usage.cachedInputTokens,
5111
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
4916
5112
  },
4917
5113
  unexpectedRootKeys,
4918
5114
  unexpectedMomentKeys,
@@ -5866,7 +6062,7 @@ async function getModerationScores(assetId, options = {}) {
5866
6062
  // src/workflows/summarization.ts
5867
6063
  import { generateText as generateText6, Output as Output6 } from "ai";
5868
6064
  import dedent6 from "dedent";
5869
- import { z as z7 } from "zod";
6065
+ import { z as z8 } from "zod";
5870
6066
 
5871
6067
  // src/lib/markdown-normalization.ts
5872
6068
  var ESCAPED_LINE_BREAK_REGEX = /\\r\\n|\\n|\\r/g;
@@ -5879,17 +6075,17 @@ function normalizeMarkdownDescription(description) {
5879
6075
  var DEFAULT_SUMMARY_KEYWORD_LIMIT = 10;
5880
6076
  var DEFAULT_TITLE_LENGTH = 10;
5881
6077
  var DEFAULT_DESCRIPTION_LENGTH = 50;
5882
- var summarySchema = z7.object({
5883
- keywords: z7.array(z7.string().max(200)),
5884
- title: z7.string().max(500),
5885
- description: z7.string().max(1e4)
6078
+ var summarySchema = z8.object({
6079
+ keywords: z8.array(z8.string().max(200)),
6080
+ title: z8.string().max(500),
6081
+ description: z8.string().max(1e4)
5886
6082
  });
5887
6083
  function buildSummarySchema(descriptionLength) {
5888
6084
  const descriptionMaxChars = Math.max(2e3, descriptionLength * 15);
5889
- return z7.object({
5890
- keywords: z7.array(z7.string().max(200)),
5891
- title: z7.string().max(500),
5892
- description: z7.string().max(descriptionMaxChars)
6085
+ return z8.object({
6086
+ keywords: z8.array(z8.string().max(200)),
6087
+ title: z8.string().max(500),
6088
+ description: z8.string().max(descriptionMaxChars)
5893
6089
  });
5894
6090
  }
5895
6091
  var VALID_TONES = ["neutral", "playful", "professional"];
@@ -6165,7 +6361,7 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
6165
6361
  "use step";
6166
6362
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
6167
6363
  const schema = buildSummarySchema(descriptionLength);
6168
- const response = await generateText6({
6364
+ const response = await withContentPolicyErrorHandling(() => generateText6({
6169
6365
  model,
6170
6366
  output: Output6.object({
6171
6367
  name: "summary_metadata",
@@ -6185,11 +6381,12 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
6185
6381
  ]
6186
6382
  }
6187
6383
  ]
6188
- });
6189
- if (!response.output) {
6384
+ }));
6385
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
6386
+ if (!output) {
6190
6387
  throw new Error("Summarization output missing");
6191
6388
  }
6192
- const parsed = schema.parse(response.output);
6389
+ const parsed = schema.parse(output);
6193
6390
  const unexpectedKeys = detectUnexpectedKeysFromRawText(
6194
6391
  response.text,
6195
6392
  schema.keyof().options
@@ -6201,7 +6398,8 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
6201
6398
  outputTokens: response.usage.outputTokens,
6202
6399
  totalTokens: response.usage.totalTokens,
6203
6400
  reasoningTokens: response.usage.reasoningTokens,
6204
- cachedInputTokens: response.usage.cachedInputTokens
6401
+ cachedInputTokens: response.usage.cachedInputTokens,
6402
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
6205
6403
  },
6206
6404
  unexpectedKeys
6207
6405
  };
@@ -6210,7 +6408,7 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
6210
6408
  "use step";
6211
6409
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
6212
6410
  const schema = buildSummarySchema(descriptionLength);
6213
- const response = await generateText6({
6411
+ const response = await withContentPolicyErrorHandling(() => generateText6({
6214
6412
  model,
6215
6413
  output: Output6.object({
6216
6414
  name: "summary_metadata",
@@ -6227,11 +6425,12 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
6227
6425
  content: userPrompt
6228
6426
  }
6229
6427
  ]
6230
- });
6231
- if (!response.output) {
6428
+ }));
6429
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
6430
+ if (!output) {
6232
6431
  throw new Error("Summarization output missing");
6233
6432
  }
6234
- const parsed = schema.parse(response.output);
6433
+ const parsed = schema.parse(output);
6235
6434
  const unexpectedKeys = detectUnexpectedKeysFromRawText(
6236
6435
  response.text,
6237
6436
  schema.keyof().options
@@ -6243,7 +6442,8 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
6243
6442
  outputTokens: response.usage.outputTokens,
6244
6443
  totalTokens: response.usage.totalTokens,
6245
6444
  reasoningTokens: response.usage.reasoningTokens,
6246
- cachedInputTokens: response.usage.cachedInputTokens
6445
+ cachedInputTokens: response.usage.cachedInputTokens,
6446
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
6247
6447
  },
6248
6448
  unexpectedKeys
6249
6449
  };
@@ -6644,6 +6844,59 @@ async function downloadAndUploadDubbedAudio({
6644
6844
  console.warn(`\u{1F517} Generated presigned URL (expires in ${expiryHours} hour${expiryHours === 1 ? "" : "s"})`);
6645
6845
  return presignedUrl;
6646
6846
  }
6847
+ async function downloadAndUploadDubTranscript({
6848
+ dubbingId,
6849
+ languageCode,
6850
+ assetId,
6851
+ toLanguageCode,
6852
+ s3Endpoint,
6853
+ s3Region,
6854
+ s3Bucket,
6855
+ storageAdapter,
6856
+ s3SignedUrlExpirySeconds,
6857
+ credentials
6858
+ }) {
6859
+ "use step";
6860
+ const elevenLabsApiKey = await getApiKeyFromEnv("elevenlabs", credentials);
6861
+ const transcriptUrl = `https://api.elevenlabs.io/v1/dubbing/${dubbingId}/transcripts/${languageCode}/format/webvtt`;
6862
+ const transcriptResponse = await fetch(transcriptUrl, {
6863
+ headers: {
6864
+ "xi-api-key": elevenLabsApiKey
6865
+ }
6866
+ });
6867
+ if (!transcriptResponse.ok) {
6868
+ throw new Error(`Failed to fetch dub transcript: ${transcriptResponse.statusText}`);
6869
+ }
6870
+ const transcriptBody = await transcriptResponse.json();
6871
+ const transcriptVtt = transcriptBody.webvtt;
6872
+ if (!transcriptVtt) {
6873
+ throw new Error("Dub transcript response did not include webvtt content");
6874
+ }
6875
+ const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
6876
+ const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
6877
+ const vttKey = `audio-translations/${assetId}/auto-to-${toLanguageCode}-${Date.now()}.vtt`;
6878
+ await putObjectWithStorageAdapter({
6879
+ accessKeyId: s3AccessKeyId,
6880
+ secretAccessKey: s3SecretAccessKey,
6881
+ endpoint: s3Endpoint,
6882
+ region: s3Region,
6883
+ bucket: s3Bucket,
6884
+ key: vttKey,
6885
+ body: transcriptVtt,
6886
+ contentType: "text/vtt"
6887
+ }, storageAdapter);
6888
+ const presignedUrl = await createPresignedGetUrlWithStorageAdapter({
6889
+ accessKeyId: s3AccessKeyId,
6890
+ secretAccessKey: s3SecretAccessKey,
6891
+ endpoint: s3Endpoint,
6892
+ region: s3Region,
6893
+ bucket: s3Bucket,
6894
+ key: vttKey,
6895
+ expiresInSeconds: s3SignedUrlExpirySeconds ?? 86400
6896
+ }, storageAdapter);
6897
+ console.warn(`\u2705 Dub transcript uploaded successfully to: ${vttKey}`);
6898
+ return presignedUrl;
6899
+ }
6647
6900
  async function createAudioTrackOnMux(assetId, languageCode, presignedUrl, credentials) {
6648
6901
  "use step";
6649
6902
  const muxClient = await resolveMuxClient(credentials);
@@ -6670,6 +6923,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6670
6923
  // 0 = auto-detect
6671
6924
  uploadToS3: uploadToS3Option,
6672
6925
  uploadToMux: uploadToMuxOption,
6926
+ uploadCaptionsToMux = false,
6673
6927
  storageAdapter,
6674
6928
  credentials: providedCredentials
6675
6929
  } = options;
@@ -6679,7 +6933,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6679
6933
  const credentials = providedCredentials;
6680
6934
  const effectiveStorageAdapter = storageAdapter;
6681
6935
  const uploadToMux = uploadToMuxOption !== false;
6682
- const uploadToS3 = uploadToS3Option || uploadToMux;
6936
+ const uploadToS3 = uploadToS3Option || uploadToMux || uploadCaptionsToMux;
6683
6937
  const s3Endpoint = options.s3Endpoint ?? env_default.S3_ENDPOINT;
6684
6938
  const s3Region = options.s3Region ?? env_default.S3_REGION ?? "auto";
6685
6939
  const s3Bucket = options.s3Bucket ?? env_default.S3_BUCKET;
@@ -6760,10 +7014,12 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6760
7014
  console.warn("\u2705 Dubbing completed successfully!");
6761
7015
  let presignedUrl;
6762
7016
  let uploadedTrackId;
7017
+ let captionsPresignedUrl;
7018
+ let captionsTrackId;
6763
7019
  if (uploadToS3) {
6764
7020
  console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs and staging to S3...");
7021
+ const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
6765
7022
  try {
6766
- const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
6767
7023
  presignedUrl = await downloadAndUploadDubbedAudio({
6768
7024
  dubbingId,
6769
7025
  languageCode: downloadLangCode,
@@ -6795,6 +7051,40 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6795
7051
  console.warn(presignedUrl);
6796
7052
  }
6797
7053
  }
7054
+ try {
7055
+ console.warn("\u{1F4E5} Downloading dub transcript from ElevenLabs and staging to S3...");
7056
+ captionsPresignedUrl = await downloadAndUploadDubTranscript({
7057
+ dubbingId,
7058
+ languageCode: downloadLangCode,
7059
+ assetId,
7060
+ toLanguageCode,
7061
+ s3Endpoint,
7062
+ s3Region,
7063
+ s3Bucket,
7064
+ storageAdapter: effectiveStorageAdapter,
7065
+ s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
7066
+ credentials
7067
+ });
7068
+ if (uploadCaptionsToMux) {
7069
+ console.warn("\u{1F4F9} Adding dubbed captions track to Mux asset...");
7070
+ const muxLangCode = toISO639_1(toLanguageCode);
7071
+ const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
7072
+ captionsTrackId = await createTextTrackOnMux(
7073
+ assetId,
7074
+ muxLangCode,
7075
+ `${languageName} (auto-dubbed)`,
7076
+ captionsPresignedUrl,
7077
+ credentials
7078
+ );
7079
+ console.warn(`\u2705 Captions track added to Mux asset with ID: ${captionsTrackId}`);
7080
+ }
7081
+ } catch (error) {
7082
+ console.warn(`\u26A0\uFE0F Failed to attach dubbed captions: ${error instanceof Error ? error.message : "Unknown error"}`);
7083
+ if (captionsPresignedUrl) {
7084
+ console.warn("\u{1F517} You can manually add the captions track using this presigned URL:");
7085
+ console.warn(captionsPresignedUrl);
7086
+ }
7087
+ }
6798
7088
  }
6799
7089
  const targetLanguage = getLanguageCodePair(toLanguageCode);
6800
7090
  return {
@@ -6804,6 +7094,8 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6804
7094
  dubbingId,
6805
7095
  uploadedTrackId,
6806
7096
  presignedUrl,
7097
+ captionsTrackId,
7098
+ captionsPresignedUrl,
6807
7099
  usage: {
6808
7100
  metadata: {
6809
7101
  assetDurationSeconds
@@ -6814,16 +7106,16 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
6814
7106
 
6815
7107
  // src/workflows/translate-captions.ts
6816
7108
  import {
6817
- APICallError,
7109
+ APICallError as APICallError2,
6818
7110
  generateText as generateText7,
6819
- NoObjectGeneratedError,
7111
+ NoObjectGeneratedError as NoObjectGeneratedError2,
6820
7112
  Output as Output7,
6821
- RetryError,
7113
+ RetryError as RetryError2,
6822
7114
  TypeValidationError
6823
7115
  } from "ai";
6824
- import { z as z8 } from "zod";
6825
- var translationSchema = z8.object({
6826
- translation: z8.string()
7116
+ import { z as z9 } from "zod";
7117
+ var translationSchema = z9.object({
7118
+ translation: z9.string()
6827
7119
  });
6828
7120
  var SYSTEM_PROMPT6 = promptDedent`
6829
7121
  You are a subtitle translation expert. Translate VTT subtitle files to the target language specified by the user.
@@ -6890,10 +7182,10 @@ function isProviderServiceError(error) {
6890
7182
  if (!error) {
6891
7183
  return false;
6892
7184
  }
6893
- if (RetryError.isInstance(error)) {
7185
+ if (RetryError2.isInstance(error)) {
6894
7186
  return isProviderServiceError(error.lastError);
6895
7187
  }
6896
- if (APICallError.isInstance(error)) {
7188
+ if (APICallError2.isInstance(error)) {
6897
7189
  return true;
6898
7190
  }
6899
7191
  if (error instanceof Error && "cause" in error) {
@@ -6905,7 +7197,7 @@ function shouldSplitChunkTranslationError(error) {
6905
7197
  if (isProviderServiceError(error)) {
6906
7198
  return false;
6907
7199
  }
6908
- return NoObjectGeneratedError.isInstance(error) || TypeValidationError.isInstance(error) || isTranslationChunkValidationError(error);
7200
+ return NoObjectGeneratedError2.isInstance(error) || TypeValidationError.isInstance(error) || isTranslationChunkValidationError(error);
6909
7201
  }
6910
7202
  function resolveTranslationChunkingOptions(options) {
6911
7203
  const targetChunkDurationSeconds = Math.max(
@@ -7066,7 +7358,7 @@ async function translateVttWithAI({
7066
7358
  "use step";
7067
7359
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
7068
7360
  const sanitisedVttContent = stripVttMetadataBlocks(vttContent);
7069
- const response = await generateText7({
7361
+ const response = await withContentPolicyErrorHandling(() => generateText7({
7070
7362
  model,
7071
7363
  output: Output7.object({ schema: translationSchema }),
7072
7364
  messages: [
@@ -7081,8 +7373,9 @@ async function translateVttWithAI({
7081
7373
  ${sanitisedVttContent}`
7082
7374
  }
7083
7375
  ]
7084
- });
7085
- const translated = normalizeTranslatedVtt(response.output.translation);
7376
+ }));
7377
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
7378
+ const translated = normalizeTranslatedVtt(output.translation);
7086
7379
  const leakReason = detectLeakReason(translated);
7087
7380
  const safeTranslated = leakReason !== null ? vttContent : translated;
7088
7381
  if (leakReason !== null) {
@@ -7107,7 +7400,8 @@ ${sanitisedVttContent}`
7107
7400
  outputTokens: response.usage.outputTokens,
7108
7401
  totalTokens: response.usage.totalTokens,
7109
7402
  reasoningTokens: response.usage.reasoningTokens,
7110
- cachedInputTokens: response.usage.cachedInputTokens
7403
+ cachedInputTokens: response.usage.cachedInputTokens,
7404
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
7111
7405
  }
7112
7406
  };
7113
7407
  }
@@ -7121,8 +7415,8 @@ async function translateCueChunkWithAI({
7121
7415
  }) {
7122
7416
  "use step";
7123
7417
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
7124
- const schema = z8.object({
7125
- translations: z8.array(z8.string().min(1).max(2e3)).length(cues.length)
7418
+ const schema = z9.object({
7419
+ translations: z9.array(z9.string().min(1).max(2e3)).length(cues.length)
7126
7420
  });
7127
7421
  const cuePayload = cues.map((cue, index) => ({
7128
7422
  index,
@@ -7130,7 +7424,7 @@ async function translateCueChunkWithAI({
7130
7424
  endTime: cue.endTime,
7131
7425
  text: cue.text
7132
7426
  }));
7133
- const response = await generateText7({
7427
+ const response = await withContentPolicyErrorHandling(() => generateText7({
7134
7428
  model,
7135
7429
  output: Output7.object({ schema }),
7136
7430
  messages: [
@@ -7146,7 +7440,8 @@ Return exactly ${cues.length} translated cues in the same order as the input.
7146
7440
  ${JSON.stringify(cuePayload, null, 2)}`
7147
7441
  }
7148
7442
  ]
7149
- });
7443
+ }));
7444
+ const output = getGeneratedOutputWithContentPolicyHandling(response);
7150
7445
  const unexpectedKeys = detectUnexpectedKeysFromRawText(
7151
7446
  response.text,
7152
7447
  schema.keyof().options
@@ -7157,13 +7452,14 @@ ${JSON.stringify(cuePayload, null, 2)}`
7157
7452
  );
7158
7453
  }
7159
7454
  return {
7160
- translations: response.output.translations,
7455
+ translations: output.translations,
7161
7456
  usage: {
7162
7457
  inputTokens: response.usage.inputTokens,
7163
7458
  outputTokens: response.usage.outputTokens,
7164
7459
  totalTokens: response.usage.totalTokens,
7165
7460
  reasoningTokens: response.usage.reasoningTokens,
7166
- cachedInputTokens: response.usage.cachedInputTokens
7461
+ cachedInputTokens: response.usage.cachedInputTokens,
7462
+ cacheWriteTokens: response.usage.inputTokenDetails?.cacheWriteTokens
7167
7463
  },
7168
7464
  unexpectedKeyCount: unexpectedKeys.length
7169
7465
  };
@@ -7526,6 +7822,9 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
7526
7822
  };
7527
7823
  }
7528
7824
  export {
7825
+ ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH,
7826
+ ASK_QUESTIONS_DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH,
7827
+ ASK_QUESTIONS_MAX_QUESTION_LENGTH,
7529
7828
  DEFAULT_DESCRIPTION_LENGTH,
7530
7829
  DEFAULT_SUMMARY_KEYWORD_LIMIT,
7531
7830
  DEFAULT_TITLE_LENGTH,