@mux/ai 0.30.0 → 0.32.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 +37 -12
- package/dist/{index-D3cB2ltG.d.ts → index-D_nj_Nr4.d.ts} +29 -14
- package/dist/{index-CJtQiGj7.d.ts → index-SjpXUdXl.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +464 -122
- package/dist/index.js.map +1 -1
- package/dist/primitives/index.d.ts +2 -2
- package/dist/primitives/index.js +45 -3
- package/dist/primitives/index.js.map +1 -1
- package/dist/{shots-Bh0GrvLx.d.ts → shots-BMbViFbZ.d.ts} +12 -0
- package/dist/workflows/index.d.ts +2 -2
- package/dist/workflows/index.js +461 -121
- package/dist/workflows/index.js.map +1 -1
- package/package.json +2 -1
package/dist/workflows/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import dedent2 from "dedent";
|
|
|
4
4
|
import { z as z3 } from "zod";
|
|
5
5
|
|
|
6
6
|
// src/lib/content-policy-error.ts
|
|
7
|
-
import { APICallError, NoObjectGeneratedError, RetryError } from "ai";
|
|
7
|
+
import { APICallError as APICallError2, NoObjectGeneratedError, RetryError } from "ai";
|
|
8
8
|
import { z as z2 } from "zod";
|
|
9
9
|
|
|
10
10
|
// src/lib/prompt-fragments.ts
|
|
@@ -79,6 +79,43 @@ var EnvSchema = z.object({
|
|
|
79
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')"),
|
|
80
80
|
// AI Providers
|
|
81
81
|
OPENAI_API_KEY: optionalString("OpenAI API key for OpenAI-backed workflows.", "OpenAI API key"),
|
|
82
|
+
BASETEN_API_KEY: optionalString("Baseten API key for Baseten-backed workflows.", "Baseten API key"),
|
|
83
|
+
BASETEN_URL: optionalString(
|
|
84
|
+
"Optional Baseten language endpoint URL: a dedicated deployment's /sync/v1 URL (such as https://model-{MODEL_ID}.api.baseten.co/environments/production/sync/v1) or a shared OpenAI-compatible base URL. Omit to use Baseten Model APIs.",
|
|
85
|
+
"Baseten URL"
|
|
86
|
+
),
|
|
87
|
+
BASETEN_EMBEDDING_URL: optionalString(
|
|
88
|
+
"Baseten dedicated embedding deployment URL, such as https://model-{MODEL_ID}.api.baseten.co/environments/production/sync. Required for Baseten embeddings.",
|
|
89
|
+
"Baseten embedding URL"
|
|
90
|
+
),
|
|
91
|
+
BASETEN_MODEL: optionalString(
|
|
92
|
+
"Default Baseten language model identifier used when provider='baseten' and no explicit model is passed.",
|
|
93
|
+
"Baseten model"
|
|
94
|
+
),
|
|
95
|
+
BASETEN_EMBEDDING_MODEL: optionalString(
|
|
96
|
+
"Default Baseten embedding model identifier used when provider='baseten' and no explicit model is passed.",
|
|
97
|
+
"Baseten embedding model"
|
|
98
|
+
),
|
|
99
|
+
OPENAI_COMPATIBLE_API_KEY: optionalString(
|
|
100
|
+
"Optional API key for OpenAI-compatible endpoints. Some local or self-hosted endpoints do not require one.",
|
|
101
|
+
"OpenAI-compatible API key"
|
|
102
|
+
),
|
|
103
|
+
OPENAI_COMPATIBLE_BASE_URL: optionalString(
|
|
104
|
+
"Base URL of an OpenAI-compatible API, such as https://my-endpoint.example.com/v1.",
|
|
105
|
+
"OpenAI-compatible base URL"
|
|
106
|
+
),
|
|
107
|
+
OPENAI_COMPATIBLE_EMBEDDING_BASE_URL: optionalString(
|
|
108
|
+
"Optional embedding-specific OpenAI-compatible base URL. Falls back to OPENAI_COMPATIBLE_BASE_URL.",
|
|
109
|
+
"OpenAI-compatible embedding base URL"
|
|
110
|
+
),
|
|
111
|
+
OPENAI_COMPATIBLE_MODEL: optionalString(
|
|
112
|
+
"Default language model identifier used when provider='openai-compatible' and no explicit model is passed.",
|
|
113
|
+
"OpenAI-compatible model"
|
|
114
|
+
),
|
|
115
|
+
OPENAI_COMPATIBLE_EMBEDDING_MODEL: optionalString(
|
|
116
|
+
"Default embedding model identifier used when provider='openai-compatible' and no explicit model is passed.",
|
|
117
|
+
"OpenAI-compatible embedding model"
|
|
118
|
+
),
|
|
82
119
|
ANTHROPIC_API_KEY: optionalString("Anthropic API key for Claude-backed workflows.", "Anthropic API key"),
|
|
83
120
|
GOOGLE_GENERATIVE_AI_API_KEY: optionalString("Google Generative AI API key for Gemini-backed workflows.", "Google Generative AI API key"),
|
|
84
121
|
ELEVENLABS_API_KEY: optionalString("ElevenLabs API key for audio translation.", "ElevenLabs API key"),
|
|
@@ -507,6 +544,67 @@ function wrapError(error, message) {
|
|
|
507
544
|
throw wrapped;
|
|
508
545
|
}
|
|
509
546
|
|
|
547
|
+
// src/lib/retry.ts
|
|
548
|
+
import { APICallError, DownloadError, NoOutputGeneratedError } from "ai";
|
|
549
|
+
var DEFAULT_RETRY_OPTIONS = {
|
|
550
|
+
maxRetries: 3,
|
|
551
|
+
baseDelay: 2e3,
|
|
552
|
+
maxDelay: 1e4
|
|
553
|
+
};
|
|
554
|
+
function defaultShouldRetry(error, _attempt) {
|
|
555
|
+
if (error.message.includes("Timeout while downloading")) {
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
if (NoOutputGeneratedError.isInstance(error) || error.name === "AI_NoOutputGeneratedError" || error.name === "AI_NoObjectGeneratedError") {
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
if (APICallError.isInstance(error) || error.name === "AI_APICallError") {
|
|
562
|
+
const apiError = error;
|
|
563
|
+
if (typeof apiError.isRetryable === "boolean") {
|
|
564
|
+
return apiError.isRetryable;
|
|
565
|
+
}
|
|
566
|
+
return apiError.statusCode === 408 || apiError.statusCode === 409 || apiError.statusCode === 429 || apiError.statusCode !== void 0 && apiError.statusCode >= 500;
|
|
567
|
+
}
|
|
568
|
+
const isDownloadError = DownloadError.isInstance(error);
|
|
569
|
+
const isSerializedDownloadError = error.name === "AI_DownloadError";
|
|
570
|
+
if (!isDownloadError && !isSerializedDownloadError) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
const statusCode = error.statusCode;
|
|
574
|
+
return statusCode === void 0 || statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
|
|
575
|
+
}
|
|
576
|
+
function calculateDelay(attempt, baseDelay, maxDelay) {
|
|
577
|
+
const exponentialDelay = baseDelay * 2 ** (attempt - 1);
|
|
578
|
+
const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);
|
|
579
|
+
return Math.min(delayWithJitter, maxDelay);
|
|
580
|
+
}
|
|
581
|
+
async function withRetry(fn, {
|
|
582
|
+
maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,
|
|
583
|
+
baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,
|
|
584
|
+
maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,
|
|
585
|
+
shouldRetry = defaultShouldRetry
|
|
586
|
+
} = {}) {
|
|
587
|
+
let lastError;
|
|
588
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
589
|
+
try {
|
|
590
|
+
return await fn();
|
|
591
|
+
} catch (error) {
|
|
592
|
+
lastError = error;
|
|
593
|
+
const retryError = error instanceof Error ? error : new Error(String(error));
|
|
594
|
+
const isLastAttempt = attempt === maxRetries;
|
|
595
|
+
if (isLastAttempt || !shouldRetry(retryError, attempt + 1)) {
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);
|
|
599
|
+
console.warn(
|
|
600
|
+
`Attempt ${attempt + 1} failed: ${retryError.message}. Retrying in ${Math.round(delay)}ms...`
|
|
601
|
+
);
|
|
602
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
throw lastError ?? new Error("Retry failed with unknown error");
|
|
606
|
+
}
|
|
607
|
+
|
|
510
608
|
// src/lib/content-policy-error.ts
|
|
511
609
|
var PolicyTokenSchema = z2.string().trim().min(1).max(64).regex(/^[A-Z0-9_]+$/);
|
|
512
610
|
var SafetyRatingSchema = z2.object({
|
|
@@ -552,6 +650,12 @@ async function withContentPolicyErrorHandling(operation) {
|
|
|
552
650
|
rethrowContentPolicyError(error);
|
|
553
651
|
}
|
|
554
652
|
}
|
|
653
|
+
async function withContentPolicyAwareRetry(operation, retryOptions) {
|
|
654
|
+
return withRetry(
|
|
655
|
+
() => withContentPolicyErrorHandling(operation),
|
|
656
|
+
retryOptions
|
|
657
|
+
);
|
|
658
|
+
}
|
|
555
659
|
function rethrowContentPolicyError(error) {
|
|
556
660
|
const block = extractContentPolicyBlock(error);
|
|
557
661
|
if (!block) {
|
|
@@ -588,7 +692,7 @@ function extractContentPolicyBlock(error) {
|
|
|
588
692
|
if (NoObjectGeneratedError.isInstance(error) && error.finishReason === "content-filter") {
|
|
589
693
|
return { reason: "CONTENT_FILTER" };
|
|
590
694
|
}
|
|
591
|
-
if (!
|
|
695
|
+
if (!APICallError2.isInstance(error) || !error.responseBody) {
|
|
592
696
|
return void 0;
|
|
593
697
|
}
|
|
594
698
|
try {
|
|
@@ -627,7 +731,7 @@ function getContentPolicyTokenUsage(error) {
|
|
|
627
731
|
if (RetryError.isInstance(error)) {
|
|
628
732
|
return getContentPolicyTokenUsage(error.lastError);
|
|
629
733
|
}
|
|
630
|
-
if (!
|
|
734
|
+
if (!APICallError2.isInstance(error) || !error.responseBody) {
|
|
631
735
|
return void 0;
|
|
632
736
|
}
|
|
633
737
|
try {
|
|
@@ -765,6 +869,7 @@ async function downloadImagesAsBase64(urls, options = {}, maxConcurrent = 5) {
|
|
|
765
869
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
766
870
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
767
871
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
872
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
768
873
|
|
|
769
874
|
// src/lib/workflow-crypto.ts
|
|
770
875
|
import { gcm } from "@noble/ciphers/aes.js";
|
|
@@ -1051,6 +1156,7 @@ async function resolveMuxClient(credentials) {
|
|
|
1051
1156
|
function resolveProviderApiKeyFromCredentials(provider, resolved) {
|
|
1052
1157
|
const record = resolved;
|
|
1053
1158
|
const openaiApiKey = readString(record, "openaiApiKey");
|
|
1159
|
+
const basetenApiKey = readString(record, "basetenApiKey");
|
|
1054
1160
|
const anthropicApiKey = readString(record, "anthropicApiKey");
|
|
1055
1161
|
const googleApiKey = readString(record, "googleApiKey");
|
|
1056
1162
|
const googleVisionApiKey = readString(record, "googleVisionApiKey");
|
|
@@ -1058,6 +1164,7 @@ function resolveProviderApiKeyFromCredentials(provider, resolved) {
|
|
|
1058
1164
|
const elevenLabsApiKey = readString(record, "elevenLabsApiKey");
|
|
1059
1165
|
const apiKeyMap = {
|
|
1060
1166
|
"openai": openaiApiKey ?? env_default.OPENAI_API_KEY,
|
|
1167
|
+
"baseten": basetenApiKey ?? env_default.BASETEN_API_KEY,
|
|
1061
1168
|
"anthropic": anthropicApiKey ?? env_default.ANTHROPIC_API_KEY,
|
|
1062
1169
|
"google": googleApiKey ?? env_default.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
1063
1170
|
"google-vision-api": googleVisionApiKey ?? env_default.GOOGLE_VISION_API_KEY,
|
|
@@ -1068,6 +1175,7 @@ function resolveProviderApiKeyFromCredentials(provider, resolved) {
|
|
|
1068
1175
|
if (!apiKey) {
|
|
1069
1176
|
const envVarNames = {
|
|
1070
1177
|
"openai": "OPENAI_API_KEY",
|
|
1178
|
+
"baseten": "BASETEN_API_KEY",
|
|
1071
1179
|
"anthropic": "ANTHROPIC_API_KEY",
|
|
1072
1180
|
"google": "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
1073
1181
|
"google-vision-api": "GOOGLE_VISION_API_KEY",
|
|
@@ -1106,6 +1214,60 @@ var DEFAULT_EMBEDDING_MODELS = {
|
|
|
1106
1214
|
openai: "text-embedding-3-small",
|
|
1107
1215
|
google: "gemini-embedding-001"
|
|
1108
1216
|
};
|
|
1217
|
+
function resolveBasetenLanguageModelId(model) {
|
|
1218
|
+
const resolved = model ?? env_default.BASETEN_MODEL;
|
|
1219
|
+
if (!resolved) {
|
|
1220
|
+
throw new Error(
|
|
1221
|
+
'Baseten model is required. Pass `model` when provider is "baseten" or set BASETEN_MODEL.'
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
return resolved;
|
|
1225
|
+
}
|
|
1226
|
+
function resolveBasetenEmbeddingModelId(model) {
|
|
1227
|
+
const resolved = model ?? env_default.BASETEN_EMBEDDING_MODEL;
|
|
1228
|
+
if (!resolved) {
|
|
1229
|
+
throw new Error(
|
|
1230
|
+
'Baseten embedding model is required. Pass `model` when provider is "baseten" or set BASETEN_EMBEDDING_MODEL.'
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
return resolved;
|
|
1234
|
+
}
|
|
1235
|
+
function resolveOpenAICompatibleLanguageModelId(model) {
|
|
1236
|
+
const resolved = model ?? env_default.OPENAI_COMPATIBLE_MODEL;
|
|
1237
|
+
if (!resolved) {
|
|
1238
|
+
throw new Error(
|
|
1239
|
+
'OpenAI-compatible model is required. Pass `model` when provider is "openai-compatible" or set OPENAI_COMPATIBLE_MODEL.'
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
return resolved;
|
|
1243
|
+
}
|
|
1244
|
+
function resolveOpenAICompatibleEmbeddingModelId(model) {
|
|
1245
|
+
const resolved = model ?? env_default.OPENAI_COMPATIBLE_EMBEDDING_MODEL;
|
|
1246
|
+
if (!resolved) {
|
|
1247
|
+
throw new Error(
|
|
1248
|
+
'OpenAI-compatible embedding model is required. Pass `model` when provider is "openai-compatible" or set OPENAI_COMPATIBLE_EMBEDDING_MODEL.'
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
return resolved;
|
|
1252
|
+
}
|
|
1253
|
+
function getDefaultLanguageModel(provider) {
|
|
1254
|
+
if (provider === "baseten") {
|
|
1255
|
+
return resolveBasetenLanguageModelId();
|
|
1256
|
+
}
|
|
1257
|
+
if (provider === "openai-compatible") {
|
|
1258
|
+
return resolveOpenAICompatibleLanguageModelId();
|
|
1259
|
+
}
|
|
1260
|
+
return DEFAULT_LANGUAGE_MODELS[provider];
|
|
1261
|
+
}
|
|
1262
|
+
function getDefaultEmbeddingModel(provider) {
|
|
1263
|
+
if (provider === "baseten") {
|
|
1264
|
+
return resolveBasetenEmbeddingModelId();
|
|
1265
|
+
}
|
|
1266
|
+
if (provider === "openai-compatible") {
|
|
1267
|
+
return resolveOpenAICompatibleEmbeddingModelId();
|
|
1268
|
+
}
|
|
1269
|
+
return DEFAULT_EMBEDDING_MODELS[provider];
|
|
1270
|
+
}
|
|
1109
1271
|
var LANGUAGE_MODELS = {
|
|
1110
1272
|
openai: ["gpt-5.6-luna", "gpt-5.1", "gpt-5-mini"],
|
|
1111
1273
|
anthropic: ["claude-sonnet-4-5"],
|
|
@@ -1171,7 +1333,7 @@ function getDefaultEvalModelConfigs() {
|
|
|
1171
1333
|
function getAllEvalModelConfigs() {
|
|
1172
1334
|
return Object.entries(LANGUAGE_MODELS).flatMap(([provider, models]) => models.map((modelId) => ({ provider, modelId })));
|
|
1173
1335
|
}
|
|
1174
|
-
function
|
|
1336
|
+
function isSupportedEvalProvider(value) {
|
|
1175
1337
|
return value === "openai" || value === "anthropic" || value === "google";
|
|
1176
1338
|
}
|
|
1177
1339
|
function parseEvalModelPair(value) {
|
|
@@ -1184,7 +1346,10 @@ function parseEvalModelPair(value) {
|
|
|
1184
1346
|
`Invalid eval model pair "${value}". Use "provider:model" (example: "openai:gpt-5.6-luna").`
|
|
1185
1347
|
);
|
|
1186
1348
|
}
|
|
1187
|
-
if (
|
|
1349
|
+
if (provider === "baseten" || provider === "openai-compatible") {
|
|
1350
|
+
return void 0;
|
|
1351
|
+
}
|
|
1352
|
+
if (!isSupportedEvalProvider(provider)) {
|
|
1188
1353
|
throw new Error(
|
|
1189
1354
|
`Unsupported eval provider "${provider}" in "${value}". Supported providers: ${Object.keys(LANGUAGE_MODELS).join(", ")}.`
|
|
1190
1355
|
);
|
|
@@ -1205,7 +1370,7 @@ function resolveEvalModelConfigs(options = {}) {
|
|
|
1205
1370
|
const explicitPairs = options.modelPairs?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
1206
1371
|
if (explicitPairs.length > 0) {
|
|
1207
1372
|
const dedupedPairs = Array.from(new Set(explicitPairs));
|
|
1208
|
-
return dedupedPairs.map(parseEvalModelPair);
|
|
1373
|
+
return dedupedPairs.map(parseEvalModelPair).filter((config) => Boolean(config));
|
|
1209
1374
|
}
|
|
1210
1375
|
const selection = options.selection ?? "default";
|
|
1211
1376
|
if (selection === "all") {
|
|
@@ -1238,30 +1403,140 @@ function resolveEvalModelConfigsFromEnv(environment = env_default) {
|
|
|
1238
1403
|
var EVAL_MODEL_CONFIGS = resolveEvalModelConfigsFromEnv();
|
|
1239
1404
|
function resolveLanguageModelConfig(options = {}) {
|
|
1240
1405
|
const provider = options.provider || "openai";
|
|
1241
|
-
const modelId = options.model ||
|
|
1406
|
+
const modelId = options.model || getDefaultLanguageModel(provider);
|
|
1242
1407
|
maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId);
|
|
1243
1408
|
return { provider, modelId };
|
|
1244
1409
|
}
|
|
1245
1410
|
function resolveEmbeddingModelConfig(options = {}) {
|
|
1246
1411
|
const provider = options.provider || "openai";
|
|
1247
|
-
const modelId = options.model ||
|
|
1412
|
+
const modelId = options.model || getDefaultEmbeddingModel(provider);
|
|
1248
1413
|
return { provider, modelId };
|
|
1249
1414
|
}
|
|
1415
|
+
var BASETEN_MODEL_APIS_BASE_URL = "https://inference.baseten.co/v1";
|
|
1416
|
+
function readCredentialString(record, key) {
|
|
1417
|
+
const value = record?.[key];
|
|
1418
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
1419
|
+
}
|
|
1420
|
+
function normalizeBaseUrl(value) {
|
|
1421
|
+
const trimmed = value?.trim();
|
|
1422
|
+
if (!trimmed) {
|
|
1423
|
+
return void 0;
|
|
1424
|
+
}
|
|
1425
|
+
return trimmed.replace(/\/(chat\/completions|responses|embeddings)\/?$/, "").replace(/\/$/, "");
|
|
1426
|
+
}
|
|
1427
|
+
function isDedicatedBasetenModelUrl(value) {
|
|
1428
|
+
return Boolean(value && (value.includes("/sync") || value.includes("/predict")));
|
|
1429
|
+
}
|
|
1430
|
+
function validateDedicatedBasetenUrl(url, kind) {
|
|
1431
|
+
if (kind === "language") {
|
|
1432
|
+
if (!url.includes("/sync/v1")) {
|
|
1433
|
+
throw new Error(
|
|
1434
|
+
`Baseten dedicated deployment URLs must be /sync/v1 endpoints for language models ("/predict" URLs are not supported). Update BASETEN_URL (or basetenUrl in credentials) to the deployment's /sync/v1 URL.`
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
return url;
|
|
1438
|
+
}
|
|
1439
|
+
if (!url.includes("/sync")) {
|
|
1440
|
+
throw new Error(
|
|
1441
|
+
"Baseten embeddings require a dedicated /sync or /sync/v1 deployment URL. Set BASETEN_EMBEDDING_URL or provide basetenEmbeddingUrl in credentials."
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
return url;
|
|
1445
|
+
}
|
|
1446
|
+
function basetenEndpointBaseUrl(url, kind) {
|
|
1447
|
+
if (kind === "embedding" && !url.includes("/sync/v1")) {
|
|
1448
|
+
return `${url}/v1`;
|
|
1449
|
+
}
|
|
1450
|
+
return url;
|
|
1451
|
+
}
|
|
1452
|
+
function basetenBaseUrlFromUrl(url, kind) {
|
|
1453
|
+
const normalized = normalizeBaseUrl(url);
|
|
1454
|
+
if (!normalized) {
|
|
1455
|
+
if (kind === "embedding") {
|
|
1456
|
+
throw new Error(
|
|
1457
|
+
"Baseten embedding URL is required. Set BASETEN_EMBEDDING_URL or provide basetenEmbeddingUrl in credentials."
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
return BASETEN_MODEL_APIS_BASE_URL;
|
|
1461
|
+
}
|
|
1462
|
+
if (isDedicatedBasetenModelUrl(normalized)) {
|
|
1463
|
+
return basetenEndpointBaseUrl(validateDedicatedBasetenUrl(normalized, kind), kind);
|
|
1464
|
+
}
|
|
1465
|
+
if (kind === "embedding") {
|
|
1466
|
+
throw new Error(
|
|
1467
|
+
"Baseten embeddings require a dedicated /sync or /sync/v1 deployment URL. Set BASETEN_EMBEDDING_URL or provide basetenEmbeddingUrl in credentials."
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
return normalized;
|
|
1471
|
+
}
|
|
1472
|
+
function resolveBasetenBaseUrlFromRecord(record, kind) {
|
|
1473
|
+
if (kind === "embedding") {
|
|
1474
|
+
return basetenBaseUrlFromUrl(
|
|
1475
|
+
readCredentialString(record, "basetenEmbeddingUrl") ?? env_default.BASETEN_EMBEDDING_URL,
|
|
1476
|
+
kind
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
return basetenBaseUrlFromUrl(
|
|
1480
|
+
readCredentialString(record, "basetenUrl") ?? env_default.BASETEN_URL,
|
|
1481
|
+
kind
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
function resolveOpenAICompatibleSettingsFromRecord(record, kind) {
|
|
1485
|
+
const apiKey = readCredentialString(record, "openaiCompatibleApiKey") ?? env_default.OPENAI_COMPATIBLE_API_KEY;
|
|
1486
|
+
const sharedBaseUrl = readCredentialString(record, "openaiCompatibleBaseUrl") ?? env_default.OPENAI_COMPATIBLE_BASE_URL;
|
|
1487
|
+
const baseUrl = normalizeBaseUrl(
|
|
1488
|
+
kind === "embedding" ? readCredentialString(record, "openaiCompatibleEmbeddingBaseUrl") ?? env_default.OPENAI_COMPATIBLE_EMBEDDING_BASE_URL ?? sharedBaseUrl : sharedBaseUrl
|
|
1489
|
+
);
|
|
1490
|
+
if (!baseUrl) {
|
|
1491
|
+
throw new Error(
|
|
1492
|
+
"OpenAI-compatible base URL is required. Set OPENAI_COMPATIBLE_BASE_URL or provide openaiCompatibleBaseUrl in credentials."
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
return { apiKey, baseURL: baseUrl };
|
|
1496
|
+
}
|
|
1497
|
+
function createBasetenProvider(apiKey, baseURL) {
|
|
1498
|
+
return createOpenAICompatible({
|
|
1499
|
+
name: "baseten",
|
|
1500
|
+
apiKey,
|
|
1501
|
+
baseURL,
|
|
1502
|
+
supportsStructuredOutputs: true
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
function createGenericOpenAICompatibleProvider(settings) {
|
|
1506
|
+
return createOpenAICompatible({
|
|
1507
|
+
name: "openai-compatible",
|
|
1508
|
+
...settings,
|
|
1509
|
+
supportsStructuredOutputs: true
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1250
1512
|
async function createLanguageModelFromConfig(provider, modelId, credentials) {
|
|
1251
1513
|
maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId);
|
|
1514
|
+
const resolvedCredentials = await resolveWorkflowCredentials(credentials);
|
|
1515
|
+
const credentialsRecord = resolvedCredentials;
|
|
1252
1516
|
switch (provider) {
|
|
1253
1517
|
case "openai": {
|
|
1254
|
-
const apiKey =
|
|
1518
|
+
const apiKey = resolveProviderApiKeyFromCredentials("openai", resolvedCredentials);
|
|
1255
1519
|
const openai = createOpenAI({ apiKey });
|
|
1256
1520
|
return openai(modelId);
|
|
1257
1521
|
}
|
|
1522
|
+
case "baseten": {
|
|
1523
|
+
const apiKey = resolveProviderApiKeyFromCredentials("baseten", resolvedCredentials);
|
|
1524
|
+
const baseten = createBasetenProvider(apiKey, resolveBasetenBaseUrlFromRecord(credentialsRecord, "language"));
|
|
1525
|
+
return baseten.chatModel(modelId);
|
|
1526
|
+
}
|
|
1527
|
+
case "openai-compatible": {
|
|
1528
|
+
const compatible = createGenericOpenAICompatibleProvider(
|
|
1529
|
+
resolveOpenAICompatibleSettingsFromRecord(credentialsRecord, "language")
|
|
1530
|
+
);
|
|
1531
|
+
return compatible.chatModel(modelId);
|
|
1532
|
+
}
|
|
1258
1533
|
case "anthropic": {
|
|
1259
|
-
const apiKey =
|
|
1534
|
+
const apiKey = resolveProviderApiKeyFromCredentials("anthropic", resolvedCredentials);
|
|
1260
1535
|
const anthropic = createAnthropic({ apiKey });
|
|
1261
1536
|
return anthropic(modelId);
|
|
1262
1537
|
}
|
|
1263
1538
|
case "google": {
|
|
1264
|
-
const apiKey =
|
|
1539
|
+
const apiKey = resolveProviderApiKeyFromCredentials("google", resolvedCredentials);
|
|
1265
1540
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
1266
1541
|
return google(modelId);
|
|
1267
1542
|
}
|
|
@@ -1272,14 +1547,27 @@ async function createLanguageModelFromConfig(provider, modelId, credentials) {
|
|
|
1272
1547
|
}
|
|
1273
1548
|
}
|
|
1274
1549
|
async function createEmbeddingModelFromConfig(provider, modelId, credentials) {
|
|
1550
|
+
const resolvedCredentials = await resolveWorkflowCredentials(credentials);
|
|
1551
|
+
const credentialsRecord = resolvedCredentials;
|
|
1275
1552
|
switch (provider) {
|
|
1276
1553
|
case "openai": {
|
|
1277
|
-
const apiKey =
|
|
1554
|
+
const apiKey = resolveProviderApiKeyFromCredentials("openai", resolvedCredentials);
|
|
1278
1555
|
const openai = createOpenAI({ apiKey });
|
|
1279
1556
|
return openai.embedding(modelId);
|
|
1280
1557
|
}
|
|
1558
|
+
case "baseten": {
|
|
1559
|
+
const apiKey = resolveProviderApiKeyFromCredentials("baseten", resolvedCredentials);
|
|
1560
|
+
const baseten = createBasetenProvider(apiKey, resolveBasetenBaseUrlFromRecord(credentialsRecord, "embedding"));
|
|
1561
|
+
return baseten.textEmbeddingModel(modelId);
|
|
1562
|
+
}
|
|
1563
|
+
case "openai-compatible": {
|
|
1564
|
+
const compatible = createGenericOpenAICompatibleProvider(
|
|
1565
|
+
resolveOpenAICompatibleSettingsFromRecord(credentialsRecord, "embedding")
|
|
1566
|
+
);
|
|
1567
|
+
return compatible.textEmbeddingModel(modelId);
|
|
1568
|
+
}
|
|
1281
1569
|
case "google": {
|
|
1282
|
-
const apiKey =
|
|
1570
|
+
const apiKey = resolveProviderApiKeyFromCredentials("google", resolvedCredentials);
|
|
1283
1571
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
1284
1572
|
return google.textEmbeddingModel(modelId);
|
|
1285
1573
|
}
|
|
@@ -1429,47 +1717,6 @@ function createLanguageSection(languageName) {
|
|
|
1429
1717
|
};
|
|
1430
1718
|
}
|
|
1431
1719
|
|
|
1432
|
-
// src/lib/retry.ts
|
|
1433
|
-
var DEFAULT_RETRY_OPTIONS = {
|
|
1434
|
-
maxRetries: 3,
|
|
1435
|
-
baseDelay: 2e3,
|
|
1436
|
-
maxDelay: 1e4
|
|
1437
|
-
};
|
|
1438
|
-
function defaultShouldRetry(error, _attempt) {
|
|
1439
|
-
return Boolean(error.message && error.message.includes("Timeout while downloading"));
|
|
1440
|
-
}
|
|
1441
|
-
function calculateDelay(attempt, baseDelay, maxDelay) {
|
|
1442
|
-
const exponentialDelay = baseDelay * 2 ** (attempt - 1);
|
|
1443
|
-
const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);
|
|
1444
|
-
return Math.min(delayWithJitter, maxDelay);
|
|
1445
|
-
}
|
|
1446
|
-
async function withRetry(fn, {
|
|
1447
|
-
maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,
|
|
1448
|
-
baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,
|
|
1449
|
-
maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,
|
|
1450
|
-
shouldRetry = defaultShouldRetry
|
|
1451
|
-
} = {}) {
|
|
1452
|
-
let lastError;
|
|
1453
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1454
|
-
try {
|
|
1455
|
-
return await fn();
|
|
1456
|
-
} catch (error) {
|
|
1457
|
-
lastError = error;
|
|
1458
|
-
const retryError = error instanceof Error ? error : new Error(String(error));
|
|
1459
|
-
const isLastAttempt = attempt === maxRetries;
|
|
1460
|
-
if (isLastAttempt || !shouldRetry(retryError, attempt + 1)) {
|
|
1461
|
-
throw error;
|
|
1462
|
-
}
|
|
1463
|
-
const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);
|
|
1464
|
-
console.warn(
|
|
1465
|
-
`Attempt ${attempt + 1} failed: ${retryError.message}. Retrying in ${Math.round(delay)}ms...`
|
|
1466
|
-
);
|
|
1467
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
throw lastError ?? new Error("Retry failed with unknown error");
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
1720
|
// src/lib/workflow-scope.ts
|
|
1474
1721
|
function hasWorkflowScopeBoundaries(scope) {
|
|
1475
1722
|
return scope?.startTime !== void 0 || scope?.endTime !== void 0;
|
|
@@ -2632,8 +2879,9 @@ async function analyzeQuestions({
|
|
|
2632
2879
|
normalizedQuestions,
|
|
2633
2880
|
maxFreeFormAnswerLength
|
|
2634
2881
|
);
|
|
2635
|
-
const response = await
|
|
2882
|
+
const response = await withContentPolicyAwareRetry(() => generateText({
|
|
2636
2883
|
model,
|
|
2884
|
+
maxRetries: 0,
|
|
2637
2885
|
output: Output.object({ schema: responseSchema }),
|
|
2638
2886
|
experimental_telemetry: { isEnabled: true },
|
|
2639
2887
|
messages: [
|
|
@@ -2832,18 +3080,16 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
2832
3080
|
credentials
|
|
2833
3081
|
});
|
|
2834
3082
|
} else {
|
|
2835
|
-
analysisResponse = await
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
})
|
|
2846
|
-
);
|
|
3083
|
+
analysisResponse = await analyzeQuestions({
|
|
3084
|
+
provider: modelConfig.provider,
|
|
3085
|
+
modelId: modelConfig.modelId,
|
|
3086
|
+
userPrompt,
|
|
3087
|
+
systemPrompt,
|
|
3088
|
+
normalizedQuestions,
|
|
3089
|
+
maxFreeFormAnswerLength,
|
|
3090
|
+
imageDataUrl: storyboardUrl,
|
|
3091
|
+
credentials
|
|
3092
|
+
});
|
|
2847
3093
|
}
|
|
2848
3094
|
}
|
|
2849
3095
|
} catch (error) {
|
|
@@ -3045,8 +3291,9 @@ async function analyzeStoryboard({
|
|
|
3045
3291
|
}) {
|
|
3046
3292
|
"use step";
|
|
3047
3293
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
3048
|
-
const response = await
|
|
3294
|
+
const response = await withContentPolicyAwareRetry(() => generateText2({
|
|
3049
3295
|
model,
|
|
3296
|
+
maxRetries: 0,
|
|
3050
3297
|
output: Output2.object({ schema: burnedInCaptionsSchema }),
|
|
3051
3298
|
experimental_telemetry: { isEnabled: true },
|
|
3052
3299
|
messages: [
|
|
@@ -3194,24 +3441,21 @@ async function generateChaptersWithAI({
|
|
|
3194
3441
|
}) {
|
|
3195
3442
|
"use step";
|
|
3196
3443
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
3197
|
-
const response = await
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
})
|
|
3213
|
-
)
|
|
3214
|
-
);
|
|
3444
|
+
const response = await withContentPolicyAwareRetry(() => generateText3({
|
|
3445
|
+
model,
|
|
3446
|
+
maxRetries: 0,
|
|
3447
|
+
output: Output3.object({ schema: chaptersSchema }),
|
|
3448
|
+
messages: [
|
|
3449
|
+
{
|
|
3450
|
+
role: "system",
|
|
3451
|
+
content: systemPrompt
|
|
3452
|
+
},
|
|
3453
|
+
{
|
|
3454
|
+
role: "user",
|
|
3455
|
+
content: userPrompt
|
|
3456
|
+
}
|
|
3457
|
+
]
|
|
3458
|
+
}));
|
|
3215
3459
|
const output = getGeneratedOutputWithContentPolicyHandling(response);
|
|
3216
3460
|
const unexpectedRootKeys = detectUnexpectedKeysFromRawText(
|
|
3217
3461
|
response.text,
|
|
@@ -3952,8 +4196,9 @@ async function identifyProfanityWithAI({
|
|
|
3952
4196
|
tag: "transcript",
|
|
3953
4197
|
content: plainText
|
|
3954
4198
|
});
|
|
3955
|
-
const response = await
|
|
4199
|
+
const response = await withContentPolicyAwareRetry(() => generateText4({
|
|
3956
4200
|
model,
|
|
4201
|
+
maxRetries: 0,
|
|
3957
4202
|
output: Output4.object({ schema: profanityDetectionSchema }),
|
|
3958
4203
|
messages: [
|
|
3959
4204
|
{
|
|
@@ -4478,9 +4723,10 @@ async function generateSingleChunkEmbedding({
|
|
|
4478
4723
|
}) {
|
|
4479
4724
|
"use step";
|
|
4480
4725
|
const model = await createEmbeddingModelFromConfig(provider, modelId, credentials);
|
|
4481
|
-
const response = await
|
|
4726
|
+
const response = await withContentPolicyAwareRetry(
|
|
4482
4727
|
() => embed({
|
|
4483
4728
|
model,
|
|
4729
|
+
maxRetries: 0,
|
|
4484
4730
|
value: chunk.text
|
|
4485
4731
|
})
|
|
4486
4732
|
);
|
|
@@ -5045,28 +5291,25 @@ async function fetchEngagementData(assetId, options) {
|
|
|
5045
5291
|
async function generateInsightsWithAI(provider, modelId, systemPrompt, userPrompt, imageUrls, credentials) {
|
|
5046
5292
|
"use step";
|
|
5047
5293
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
5048
|
-
const response = await
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
...imageUrls.map((url) => ({ type: "image", image: url }))
|
|
5064
|
-
]
|
|
5065
|
-
}
|
|
5294
|
+
const response = await withContentPolicyAwareRetry(() => generateText5({
|
|
5295
|
+
model,
|
|
5296
|
+
maxRetries: 0,
|
|
5297
|
+
output: Output5.object({ schema: engagementInsightsSchema }),
|
|
5298
|
+
experimental_telemetry: { isEnabled: true },
|
|
5299
|
+
messages: [
|
|
5300
|
+
{
|
|
5301
|
+
role: "system",
|
|
5302
|
+
content: systemPrompt
|
|
5303
|
+
},
|
|
5304
|
+
{
|
|
5305
|
+
role: "user",
|
|
5306
|
+
content: [
|
|
5307
|
+
{ type: "text", text: userPrompt },
|
|
5308
|
+
...imageUrls.map((url) => ({ type: "image", image: url }))
|
|
5066
5309
|
]
|
|
5067
|
-
}
|
|
5068
|
-
|
|
5069
|
-
);
|
|
5310
|
+
}
|
|
5311
|
+
]
|
|
5312
|
+
}));
|
|
5070
5313
|
const output = getGeneratedOutputWithContentPolicyHandling(response);
|
|
5071
5314
|
if (!output) {
|
|
5072
5315
|
throw new Error("AI returned empty or unparseable response");
|
|
@@ -5386,6 +5629,7 @@ async function getThumbnailUrls(playbackId, duration, options = {}) {
|
|
|
5386
5629
|
timestamps.push(time);
|
|
5387
5630
|
}
|
|
5388
5631
|
}
|
|
5632
|
+
timestamps = [...new Set(timestamps)];
|
|
5389
5633
|
if (maxSamples !== void 0 && timestamps.length > maxSamples) {
|
|
5390
5634
|
const newTimestamps = [];
|
|
5391
5635
|
newTimestamps.push(resolvedScope.startTime);
|
|
@@ -5864,8 +6108,8 @@ async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options)
|
|
|
5864
6108
|
"use step";
|
|
5865
6109
|
const { width, shouldSign, credentials } = options;
|
|
5866
6110
|
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
5867
|
-
const
|
|
5868
|
-
|
|
6111
|
+
const times = [...new Set(timestampsMs.map(toThumbnailTimeSeconds))];
|
|
6112
|
+
const urlPromises = times.map(async (time) => {
|
|
5869
6113
|
const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
|
|
5870
6114
|
return { url, time };
|
|
5871
6115
|
});
|
|
@@ -6351,8 +6595,9 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
|
|
|
6351
6595
|
"use step";
|
|
6352
6596
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
6353
6597
|
const schema = buildSummarySchema(descriptionLength);
|
|
6354
|
-
const response = await
|
|
6598
|
+
const response = await withContentPolicyAwareRetry(() => generateText6({
|
|
6355
6599
|
model,
|
|
6600
|
+
maxRetries: 0,
|
|
6356
6601
|
output: Output6.object({
|
|
6357
6602
|
name: "summary_metadata",
|
|
6358
6603
|
description: "Structured summary with title, description, and keywords.",
|
|
@@ -6398,8 +6643,9 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
|
|
|
6398
6643
|
"use step";
|
|
6399
6644
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
6400
6645
|
const schema = buildSummarySchema(descriptionLength);
|
|
6401
|
-
const response = await
|
|
6646
|
+
const response = await withContentPolicyAwareRetry(() => generateText6({
|
|
6402
6647
|
model,
|
|
6648
|
+
maxRetries: 0,
|
|
6403
6649
|
output: Output6.object({
|
|
6404
6650
|
name: "summary_metadata",
|
|
6405
6651
|
description: "Structured summary with title, description, and keywords.",
|
|
@@ -6582,7 +6828,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6582
6828
|
workflowCredentials
|
|
6583
6829
|
);
|
|
6584
6830
|
} else {
|
|
6585
|
-
analysisResponse = await
|
|
6831
|
+
analysisResponse = await analyzeStoryboard2(
|
|
6586
6832
|
storyboardUrl,
|
|
6587
6833
|
modelConfig.provider,
|
|
6588
6834
|
modelConfig.modelId,
|
|
@@ -6590,7 +6836,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6590
6836
|
systemPrompt,
|
|
6591
6837
|
effectiveDescriptionLength,
|
|
6592
6838
|
workflowCredentials
|
|
6593
|
-
)
|
|
6839
|
+
);
|
|
6594
6840
|
}
|
|
6595
6841
|
}
|
|
6596
6842
|
} catch (error) {
|
|
@@ -6834,6 +7080,59 @@ async function downloadAndUploadDubbedAudio({
|
|
|
6834
7080
|
console.warn(`\u{1F517} Generated presigned URL (expires in ${expiryHours} hour${expiryHours === 1 ? "" : "s"})`);
|
|
6835
7081
|
return presignedUrl;
|
|
6836
7082
|
}
|
|
7083
|
+
async function downloadAndUploadDubTranscript({
|
|
7084
|
+
dubbingId,
|
|
7085
|
+
languageCode,
|
|
7086
|
+
assetId,
|
|
7087
|
+
toLanguageCode,
|
|
7088
|
+
s3Endpoint,
|
|
7089
|
+
s3Region,
|
|
7090
|
+
s3Bucket,
|
|
7091
|
+
storageAdapter,
|
|
7092
|
+
s3SignedUrlExpirySeconds,
|
|
7093
|
+
credentials
|
|
7094
|
+
}) {
|
|
7095
|
+
"use step";
|
|
7096
|
+
const elevenLabsApiKey = await getApiKeyFromEnv("elevenlabs", credentials);
|
|
7097
|
+
const transcriptUrl = `https://api.elevenlabs.io/v1/dubbing/${dubbingId}/transcripts/${languageCode}/format/webvtt`;
|
|
7098
|
+
const transcriptResponse = await fetch(transcriptUrl, {
|
|
7099
|
+
headers: {
|
|
7100
|
+
"xi-api-key": elevenLabsApiKey
|
|
7101
|
+
}
|
|
7102
|
+
});
|
|
7103
|
+
if (!transcriptResponse.ok) {
|
|
7104
|
+
throw new Error(`Failed to fetch dub transcript: ${transcriptResponse.statusText}`);
|
|
7105
|
+
}
|
|
7106
|
+
const transcriptBody = await transcriptResponse.json();
|
|
7107
|
+
const transcriptVtt = transcriptBody.webvtt;
|
|
7108
|
+
if (!transcriptVtt) {
|
|
7109
|
+
throw new Error("Dub transcript response did not include webvtt content");
|
|
7110
|
+
}
|
|
7111
|
+
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
7112
|
+
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
7113
|
+
const vttKey = `audio-translations/${assetId}/auto-to-${toLanguageCode}-${Date.now()}.vtt`;
|
|
7114
|
+
await putObjectWithStorageAdapter({
|
|
7115
|
+
accessKeyId: s3AccessKeyId,
|
|
7116
|
+
secretAccessKey: s3SecretAccessKey,
|
|
7117
|
+
endpoint: s3Endpoint,
|
|
7118
|
+
region: s3Region,
|
|
7119
|
+
bucket: s3Bucket,
|
|
7120
|
+
key: vttKey,
|
|
7121
|
+
body: transcriptVtt,
|
|
7122
|
+
contentType: "text/vtt"
|
|
7123
|
+
}, storageAdapter);
|
|
7124
|
+
const presignedUrl = await createPresignedGetUrlWithStorageAdapter({
|
|
7125
|
+
accessKeyId: s3AccessKeyId,
|
|
7126
|
+
secretAccessKey: s3SecretAccessKey,
|
|
7127
|
+
endpoint: s3Endpoint,
|
|
7128
|
+
region: s3Region,
|
|
7129
|
+
bucket: s3Bucket,
|
|
7130
|
+
key: vttKey,
|
|
7131
|
+
expiresInSeconds: s3SignedUrlExpirySeconds ?? 86400
|
|
7132
|
+
}, storageAdapter);
|
|
7133
|
+
console.warn(`\u2705 Dub transcript uploaded successfully to: ${vttKey}`);
|
|
7134
|
+
return presignedUrl;
|
|
7135
|
+
}
|
|
6837
7136
|
async function createAudioTrackOnMux(assetId, languageCode, presignedUrl, credentials) {
|
|
6838
7137
|
"use step";
|
|
6839
7138
|
const muxClient = await resolveMuxClient(credentials);
|
|
@@ -6860,6 +7159,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
6860
7159
|
// 0 = auto-detect
|
|
6861
7160
|
uploadToS3: uploadToS3Option,
|
|
6862
7161
|
uploadToMux: uploadToMuxOption,
|
|
7162
|
+
uploadCaptionsToMux = false,
|
|
6863
7163
|
storageAdapter,
|
|
6864
7164
|
credentials: providedCredentials
|
|
6865
7165
|
} = options;
|
|
@@ -6869,7 +7169,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
6869
7169
|
const credentials = providedCredentials;
|
|
6870
7170
|
const effectiveStorageAdapter = storageAdapter;
|
|
6871
7171
|
const uploadToMux = uploadToMuxOption !== false;
|
|
6872
|
-
const uploadToS3 = uploadToS3Option || uploadToMux;
|
|
7172
|
+
const uploadToS3 = uploadToS3Option || uploadToMux || uploadCaptionsToMux;
|
|
6873
7173
|
const s3Endpoint = options.s3Endpoint ?? env_default.S3_ENDPOINT;
|
|
6874
7174
|
const s3Region = options.s3Region ?? env_default.S3_REGION ?? "auto";
|
|
6875
7175
|
const s3Bucket = options.s3Bucket ?? env_default.S3_BUCKET;
|
|
@@ -6950,10 +7250,12 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
6950
7250
|
console.warn("\u2705 Dubbing completed successfully!");
|
|
6951
7251
|
let presignedUrl;
|
|
6952
7252
|
let uploadedTrackId;
|
|
7253
|
+
let captionsPresignedUrl;
|
|
7254
|
+
let captionsTrackId;
|
|
6953
7255
|
if (uploadToS3) {
|
|
6954
7256
|
console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs and staging to S3...");
|
|
7257
|
+
const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
|
|
6955
7258
|
try {
|
|
6956
|
-
const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
|
|
6957
7259
|
presignedUrl = await downloadAndUploadDubbedAudio({
|
|
6958
7260
|
dubbingId,
|
|
6959
7261
|
languageCode: downloadLangCode,
|
|
@@ -6985,6 +7287,40 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
6985
7287
|
console.warn(presignedUrl);
|
|
6986
7288
|
}
|
|
6987
7289
|
}
|
|
7290
|
+
try {
|
|
7291
|
+
console.warn("\u{1F4E5} Downloading dub transcript from ElevenLabs and staging to S3...");
|
|
7292
|
+
captionsPresignedUrl = await downloadAndUploadDubTranscript({
|
|
7293
|
+
dubbingId,
|
|
7294
|
+
languageCode: downloadLangCode,
|
|
7295
|
+
assetId,
|
|
7296
|
+
toLanguageCode,
|
|
7297
|
+
s3Endpoint,
|
|
7298
|
+
s3Region,
|
|
7299
|
+
s3Bucket,
|
|
7300
|
+
storageAdapter: effectiveStorageAdapter,
|
|
7301
|
+
s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
|
|
7302
|
+
credentials
|
|
7303
|
+
});
|
|
7304
|
+
if (uploadCaptionsToMux) {
|
|
7305
|
+
console.warn("\u{1F4F9} Adding dubbed captions track to Mux asset...");
|
|
7306
|
+
const muxLangCode = toISO639_1(toLanguageCode);
|
|
7307
|
+
const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
|
|
7308
|
+
captionsTrackId = await createTextTrackOnMux(
|
|
7309
|
+
assetId,
|
|
7310
|
+
muxLangCode,
|
|
7311
|
+
`${languageName} (auto-dubbed)`,
|
|
7312
|
+
captionsPresignedUrl,
|
|
7313
|
+
credentials
|
|
7314
|
+
);
|
|
7315
|
+
console.warn(`\u2705 Captions track added to Mux asset with ID: ${captionsTrackId}`);
|
|
7316
|
+
}
|
|
7317
|
+
} catch (error) {
|
|
7318
|
+
console.warn(`\u26A0\uFE0F Failed to attach dubbed captions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
7319
|
+
if (captionsPresignedUrl) {
|
|
7320
|
+
console.warn("\u{1F517} You can manually add the captions track using this presigned URL:");
|
|
7321
|
+
console.warn(captionsPresignedUrl);
|
|
7322
|
+
}
|
|
7323
|
+
}
|
|
6988
7324
|
}
|
|
6989
7325
|
const targetLanguage = getLanguageCodePair(toLanguageCode);
|
|
6990
7326
|
return {
|
|
@@ -6994,6 +7330,8 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
6994
7330
|
dubbingId,
|
|
6995
7331
|
uploadedTrackId,
|
|
6996
7332
|
presignedUrl,
|
|
7333
|
+
captionsTrackId,
|
|
7334
|
+
captionsPresignedUrl,
|
|
6997
7335
|
usage: {
|
|
6998
7336
|
metadata: {
|
|
6999
7337
|
assetDurationSeconds
|
|
@@ -7004,7 +7342,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7004
7342
|
|
|
7005
7343
|
// src/workflows/translate-captions.ts
|
|
7006
7344
|
import {
|
|
7007
|
-
APICallError as
|
|
7345
|
+
APICallError as APICallError3,
|
|
7008
7346
|
generateText as generateText7,
|
|
7009
7347
|
NoObjectGeneratedError as NoObjectGeneratedError2,
|
|
7010
7348
|
Output as Output7,
|
|
@@ -7083,7 +7421,7 @@ function isProviderServiceError(error) {
|
|
|
7083
7421
|
if (RetryError2.isInstance(error)) {
|
|
7084
7422
|
return isProviderServiceError(error.lastError);
|
|
7085
7423
|
}
|
|
7086
|
-
if (
|
|
7424
|
+
if (APICallError3.isInstance(error)) {
|
|
7087
7425
|
return true;
|
|
7088
7426
|
}
|
|
7089
7427
|
if (error instanceof Error && "cause" in error) {
|
|
@@ -7256,8 +7594,9 @@ async function translateVttWithAI({
|
|
|
7256
7594
|
"use step";
|
|
7257
7595
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
7258
7596
|
const sanitisedVttContent = stripVttMetadataBlocks(vttContent);
|
|
7259
|
-
const response = await
|
|
7597
|
+
const response = await withContentPolicyAwareRetry(() => generateText7({
|
|
7260
7598
|
model,
|
|
7599
|
+
maxRetries: 0,
|
|
7261
7600
|
output: Output7.object({ schema: translationSchema }),
|
|
7262
7601
|
messages: [
|
|
7263
7602
|
{
|
|
@@ -7322,8 +7661,9 @@ async function translateCueChunkWithAI({
|
|
|
7322
7661
|
endTime: cue.endTime,
|
|
7323
7662
|
text: cue.text
|
|
7324
7663
|
}));
|
|
7325
|
-
const response = await
|
|
7664
|
+
const response = await withContentPolicyAwareRetry(() => generateText7({
|
|
7326
7665
|
model,
|
|
7666
|
+
maxRetries: 0,
|
|
7327
7667
|
output: Output7.object({ schema }),
|
|
7328
7668
|
messages: [
|
|
7329
7669
|
{
|