@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.
- package/README.md +1 -1
- package/dist/{index-CXwsu679.d.ts → index-CJtQiGj7.d.ts} +1 -1
- package/dist/{index-DJirK4fh.d.ts → index-ooikuED7.d.ts} +46 -3
- package/dist/index.d.ts +7 -6
- package/dist/index.js +447 -150
- package/dist/index.js.map +1 -1
- package/dist/primitives/index.d.ts +2 -2
- package/dist/primitives/index.js +26 -6
- package/dist/primitives/index.js.map +1 -1
- package/dist/{shots-BKd7TMFN.d.ts → shots-Bh0GrvLx.d.ts} +2 -0
- package/dist/workflows/index.d.ts +2 -2
- package/dist/workflows/index.js +555 -256
- package/dist/workflows/index.js.map +1 -1
- package/package.json +1 -1
package/dist/workflows/index.js
CHANGED
|
@@ -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
|
|
4
|
+
import { z as z3 } from "zod";
|
|
5
5
|
|
|
6
|
-
// src/lib/
|
|
7
|
-
import
|
|
8
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
1467
|
+
lastError = error;
|
|
1468
|
+
const retryError = error instanceof Error ? error : new Error(String(error));
|
|
1287
1469
|
const isLastAttempt = attempt === maxRetries;
|
|
1288
|
-
if (isLastAttempt || !shouldRetry(
|
|
1289
|
-
throw
|
|
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: ${
|
|
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
|
|
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
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
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
|
|
2087
|
-
answers:
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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 >
|
|
2716
|
+
if (q.question.length > ASK_QUESTIONS_MAX_QUESTION_LENGTH) {
|
|
2531
2717
|
throw new MuxAiError(
|
|
2532
|
-
`Question at index ${idx} exceeds the ${
|
|
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
|
|
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
|
|
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
|
|
2761
|
-
var burnedInCaptionsSchema =
|
|
2762
|
-
hasBurnedInCaptions:
|
|
2763
|
-
confidence:
|
|
2764
|
-
detectedLanguage:
|
|
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
|
-
...
|
|
2899
|
-
confidence: Math.min(1, Math.max(0,
|
|
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
|
|
3005
|
-
var chapterSchema =
|
|
3006
|
-
startTime:
|
|
3007
|
-
title:
|
|
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 =
|
|
3010
|
-
chapters:
|
|
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
|
-
() =>
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
3598
|
-
profanity:
|
|
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:
|
|
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
|
|
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 =
|
|
4593
|
-
hotspotIndex:
|
|
4594
|
-
startMs:
|
|
4595
|
-
endMs:
|
|
4596
|
-
timestamp:
|
|
4597
|
-
engagementScore:
|
|
4598
|
-
insight:
|
|
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 =
|
|
4601
|
-
summary:
|
|
4602
|
-
trends:
|
|
4792
|
+
var aiOverallInsightSchema = z7.object({
|
|
4793
|
+
summary: z7.string().max(2e3),
|
|
4794
|
+
trends: z7.array(z7.string().max(500))
|
|
4603
4795
|
});
|
|
4604
|
-
var engagementInsightsSchema =
|
|
4605
|
-
momentInsights:
|
|
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
|
-
() =>
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
5883
|
-
keywords:
|
|
5884
|
-
title:
|
|
5885
|
-
description:
|
|
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
|
|
5890
|
-
keywords:
|
|
5891
|
-
title:
|
|
5892
|
-
description:
|
|
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
|
-
|
|
6384
|
+
}));
|
|
6385
|
+
const output = getGeneratedOutputWithContentPolicyHandling(response);
|
|
6386
|
+
if (!output) {
|
|
6190
6387
|
throw new Error("Summarization output missing");
|
|
6191
6388
|
}
|
|
6192
|
-
const parsed = schema.parse(
|
|
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
|
-
|
|
6428
|
+
}));
|
|
6429
|
+
const output = getGeneratedOutputWithContentPolicyHandling(response);
|
|
6430
|
+
if (!output) {
|
|
6232
6431
|
throw new Error("Summarization output missing");
|
|
6233
6432
|
}
|
|
6234
|
-
const parsed = schema.parse(
|
|
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
|
|
6825
|
-
var translationSchema =
|
|
6826
|
-
translation:
|
|
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 (
|
|
7185
|
+
if (RetryError2.isInstance(error)) {
|
|
6894
7186
|
return isProviderServiceError(error.lastError);
|
|
6895
7187
|
}
|
|
6896
|
-
if (
|
|
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
|
|
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
|
|
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 =
|
|
7125
|
-
translations:
|
|
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:
|
|
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,
|