@mux/ai 0.31.0 → 0.33.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.
@@ -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 (!APICallError.isInstance(error) || !error.responseBody) {
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 (!APICallError.isInstance(error) || !error.responseBody) {
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 isSupportedProvider(value) {
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 (!isSupportedProvider(provider)) {
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 || DEFAULT_LANGUAGE_MODELS[provider];
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 || DEFAULT_EMBEDDING_MODELS[provider];
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 = await resolveProviderApiKey("openai", credentials);
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 = await resolveProviderApiKey("anthropic", credentials);
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 = await resolveProviderApiKey("google", credentials);
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 = await resolveProviderApiKey("openai", credentials);
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 = await resolveProviderApiKey("google", credentials);
1570
+ const apiKey = resolveProviderApiKeyFromCredentials("google", resolvedCredentials);
1283
1571
  const google = createGoogleGenerativeAI({ apiKey });
1284
1572
  return google.textEmbeddingModel(modelId);
1285
1573
  }
@@ -1429,57 +1717,6 @@ function createLanguageSection(languageName) {
1429
1717
  };
1430
1718
  }
1431
1719
 
1432
- // src/lib/retry.ts
1433
- import { DownloadError } from "ai";
1434
- var DEFAULT_RETRY_OPTIONS = {
1435
- maxRetries: 3,
1436
- baseDelay: 2e3,
1437
- maxDelay: 1e4
1438
- };
1439
- function defaultShouldRetry(error, _attempt) {
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;
1450
- }
1451
- function calculateDelay(attempt, baseDelay, maxDelay) {
1452
- const exponentialDelay = baseDelay * 2 ** (attempt - 1);
1453
- const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);
1454
- return Math.min(delayWithJitter, maxDelay);
1455
- }
1456
- async function withRetry(fn, {
1457
- maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,
1458
- baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,
1459
- maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,
1460
- shouldRetry = defaultShouldRetry
1461
- } = {}) {
1462
- let lastError;
1463
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1464
- try {
1465
- return await fn();
1466
- } catch (error) {
1467
- lastError = error;
1468
- const retryError = error instanceof Error ? error : new Error(String(error));
1469
- const isLastAttempt = attempt === maxRetries;
1470
- if (isLastAttempt || !shouldRetry(retryError, attempt + 1)) {
1471
- throw error;
1472
- }
1473
- const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);
1474
- console.warn(
1475
- `Attempt ${attempt + 1} failed: ${retryError.message}. Retrying in ${Math.round(delay)}ms...`
1476
- );
1477
- await new Promise((resolve) => setTimeout(resolve, delay));
1478
- }
1479
- }
1480
- throw lastError ?? new Error("Retry failed with unknown error");
1481
- }
1482
-
1483
1720
  // src/lib/workflow-scope.ts
1484
1721
  function hasWorkflowScopeBoundaries(scope) {
1485
1722
  return scope?.startTime !== void 0 || scope?.endTime !== void 0;
@@ -2376,11 +2613,9 @@ var SYSTEM_PROMPT = promptDedent`
2376
2613
  </constraints>
2377
2614
 
2378
2615
  <language_guidelines>
2379
- When explaining reasoning:
2380
- - Describe content directly, not the medium
2381
- - BAD: "The video shows a person running"
2382
- - GOOD: "A person runs through a park"
2383
- - Be specific and evidence-based
2616
+ When explaining reasoning, be specific and evidence-based.
2617
+
2618
+ ${createLanguageGuidelines("video")}
2384
2619
  </language_guidelines>`;
2385
2620
  var AUDIO_ONLY_SYSTEM_PROMPT = promptDedent`
2386
2621
  <role>
@@ -2473,11 +2708,9 @@ var AUDIO_ONLY_SYSTEM_PROMPT = promptDedent`
2473
2708
  </constraints>
2474
2709
 
2475
2710
  <language_guidelines>
2476
- When explaining reasoning:
2477
- - Describe content directly, not the medium
2478
- - BAD: "The audio says someone is running"
2479
- - GOOD: "The speaker describes running through a park"
2480
- - Be specific and evidence-based
2711
+ When explaining reasoning, be specific and evidence-based.
2712
+
2713
+ ${createLanguageGuidelines("audio")}
2481
2714
  </language_guidelines>`;
2482
2715
  function freeFormAddendum(maxFreeFormAnswerLength) {
2483
2716
  return promptDedent`
@@ -2642,8 +2875,9 @@ async function analyzeQuestions({
2642
2875
  normalizedQuestions,
2643
2876
  maxFreeFormAnswerLength
2644
2877
  );
2645
- const response = await withContentPolicyErrorHandling(() => generateText({
2878
+ const response = await withContentPolicyAwareRetry(() => generateText({
2646
2879
  model,
2880
+ maxRetries: 0,
2647
2881
  output: Output.object({ schema: responseSchema }),
2648
2882
  experimental_telemetry: { isEnabled: true },
2649
2883
  messages: [
@@ -2842,18 +3076,16 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
2842
3076
  credentials
2843
3077
  });
2844
3078
  } else {
2845
- analysisResponse = await withRetry(
2846
- () => analyzeQuestions({
2847
- provider: modelConfig.provider,
2848
- modelId: modelConfig.modelId,
2849
- userPrompt,
2850
- systemPrompt,
2851
- normalizedQuestions,
2852
- maxFreeFormAnswerLength,
2853
- imageDataUrl: storyboardUrl,
2854
- credentials
2855
- })
2856
- );
3079
+ analysisResponse = await analyzeQuestions({
3080
+ provider: modelConfig.provider,
3081
+ modelId: modelConfig.modelId,
3082
+ userPrompt,
3083
+ systemPrompt,
3084
+ normalizedQuestions,
3085
+ maxFreeFormAnswerLength,
3086
+ imageDataUrl: storyboardUrl,
3087
+ credentials
3088
+ });
2857
3089
  }
2858
3090
  }
2859
3091
  } catch (error) {
@@ -3055,8 +3287,9 @@ async function analyzeStoryboard({
3055
3287
  }) {
3056
3288
  "use step";
3057
3289
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
3058
- const response = await withContentPolicyErrorHandling(() => generateText2({
3290
+ const response = await withContentPolicyAwareRetry(() => generateText2({
3059
3291
  model,
3292
+ maxRetries: 0,
3060
3293
  output: Output2.object({ schema: burnedInCaptionsSchema }),
3061
3294
  experimental_telemetry: { isEnabled: true },
3062
3295
  messages: [
@@ -3146,14 +3379,14 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
3146
3379
  credentials
3147
3380
  });
3148
3381
  } else {
3149
- analysisResponse = await withRetry(() => analyzeStoryboard({
3382
+ analysisResponse = await analyzeStoryboard({
3150
3383
  imageDataUrl: imageUrl,
3151
3384
  provider: modelConfig.provider,
3152
3385
  modelId: modelConfig.modelId,
3153
3386
  userPrompt,
3154
3387
  systemPrompt: SYSTEM_PROMPT2,
3155
3388
  credentials
3156
- }));
3389
+ });
3157
3390
  }
3158
3391
  collectedUsage.push(analysisResponse.usage);
3159
3392
  if (!analysisResponse.result) {
@@ -3204,24 +3437,21 @@ async function generateChaptersWithAI({
3204
3437
  }) {
3205
3438
  "use step";
3206
3439
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
3207
- const response = await withRetry(
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
- )
3224
- );
3440
+ const response = await withContentPolicyAwareRetry(() => generateText3({
3441
+ model,
3442
+ maxRetries: 0,
3443
+ output: Output3.object({ schema: chaptersSchema }),
3444
+ messages: [
3445
+ {
3446
+ role: "system",
3447
+ content: systemPrompt
3448
+ },
3449
+ {
3450
+ role: "user",
3451
+ content: userPrompt
3452
+ }
3453
+ ]
3454
+ }));
3225
3455
  const output = getGeneratedOutputWithContentPolicyHandling(response);
3226
3456
  const unexpectedRootKeys = detectUnexpectedKeysFromRawText(
3227
3457
  response.text,
@@ -3962,8 +4192,9 @@ async function identifyProfanityWithAI({
3962
4192
  tag: "transcript",
3963
4193
  content: plainText
3964
4194
  });
3965
- const response = await withContentPolicyErrorHandling(() => generateText4({
4195
+ const response = await withContentPolicyAwareRetry(() => generateText4({
3966
4196
  model,
4197
+ maxRetries: 0,
3967
4198
  output: Output4.object({ schema: profanityDetectionSchema }),
3968
4199
  messages: [
3969
4200
  {
@@ -4488,9 +4719,10 @@ async function generateSingleChunkEmbedding({
4488
4719
  }) {
4489
4720
  "use step";
4490
4721
  const model = await createEmbeddingModelFromConfig(provider, modelId, credentials);
4491
- const response = await withRetry(
4722
+ const response = await withContentPolicyAwareRetry(
4492
4723
  () => embed({
4493
4724
  model,
4725
+ maxRetries: 0,
4494
4726
  value: chunk.text
4495
4727
  })
4496
4728
  );
@@ -5055,28 +5287,25 @@ async function fetchEngagementData(assetId, options) {
5055
5287
  async function generateInsightsWithAI(provider, modelId, systemPrompt, userPrompt, imageUrls, credentials) {
5056
5288
  "use step";
5057
5289
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
5058
- const response = await withRetry(
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
- }
5290
+ const response = await withContentPolicyAwareRetry(() => generateText5({
5291
+ model,
5292
+ maxRetries: 0,
5293
+ output: Output5.object({ schema: engagementInsightsSchema }),
5294
+ experimental_telemetry: { isEnabled: true },
5295
+ messages: [
5296
+ {
5297
+ role: "system",
5298
+ content: systemPrompt
5299
+ },
5300
+ {
5301
+ role: "user",
5302
+ content: [
5303
+ { type: "text", text: userPrompt },
5304
+ ...imageUrls.map((url) => ({ type: "image", image: url }))
5076
5305
  ]
5077
- })
5078
- )
5079
- );
5306
+ }
5307
+ ]
5308
+ }));
5080
5309
  const output = getGeneratedOutputWithContentPolicyHandling(response);
5081
5310
  if (!output) {
5082
5311
  throw new Error("AI returned empty or unparseable response");
@@ -5396,6 +5625,7 @@ async function getThumbnailUrls(playbackId, duration, options = {}) {
5396
5625
  timestamps.push(time);
5397
5626
  }
5398
5627
  }
5628
+ timestamps = [...new Set(timestamps)];
5399
5629
  if (maxSamples !== void 0 && timestamps.length > maxSamples) {
5400
5630
  const newTimestamps = [];
5401
5631
  newTimestamps.push(resolvedScope.startTime);
@@ -5874,8 +6104,8 @@ async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options)
5874
6104
  "use step";
5875
6105
  const { width, shouldSign, credentials } = options;
5876
6106
  const baseUrl = getMuxThumbnailBaseUrl(playbackId);
5877
- const urlPromises = timestampsMs.map(async (tsMs) => {
5878
- const time = toThumbnailTimeSeconds(tsMs);
6107
+ const times = [...new Set(timestampsMs.map(toThumbnailTimeSeconds))];
6108
+ const urlPromises = times.map(async (time) => {
5879
6109
  const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
5880
6110
  return { url, time };
5881
6111
  });
@@ -6361,8 +6591,9 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
6361
6591
  "use step";
6362
6592
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
6363
6593
  const schema = buildSummarySchema(descriptionLength);
6364
- const response = await withContentPolicyErrorHandling(() => generateText6({
6594
+ const response = await withContentPolicyAwareRetry(() => generateText6({
6365
6595
  model,
6596
+ maxRetries: 0,
6366
6597
  output: Output6.object({
6367
6598
  name: "summary_metadata",
6368
6599
  description: "Structured summary with title, description, and keywords.",
@@ -6408,8 +6639,9 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
6408
6639
  "use step";
6409
6640
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
6410
6641
  const schema = buildSummarySchema(descriptionLength);
6411
- const response = await withContentPolicyErrorHandling(() => generateText6({
6642
+ const response = await withContentPolicyAwareRetry(() => generateText6({
6412
6643
  model,
6644
+ maxRetries: 0,
6413
6645
  output: Output6.object({
6414
6646
  name: "summary_metadata",
6415
6647
  description: "Structured summary with title, description, and keywords.",
@@ -6592,7 +6824,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
6592
6824
  workflowCredentials
6593
6825
  );
6594
6826
  } else {
6595
- analysisResponse = await withRetry(() => analyzeStoryboard2(
6827
+ analysisResponse = await analyzeStoryboard2(
6596
6828
  storyboardUrl,
6597
6829
  modelConfig.provider,
6598
6830
  modelConfig.modelId,
@@ -6600,7 +6832,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
6600
6832
  systemPrompt,
6601
6833
  effectiveDescriptionLength,
6602
6834
  workflowCredentials
6603
- ));
6835
+ );
6604
6836
  }
6605
6837
  }
6606
6838
  } catch (error) {
@@ -7106,7 +7338,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7106
7338
 
7107
7339
  // src/workflows/translate-captions.ts
7108
7340
  import {
7109
- APICallError as APICallError2,
7341
+ APICallError as APICallError3,
7110
7342
  generateText as generateText7,
7111
7343
  NoObjectGeneratedError as NoObjectGeneratedError2,
7112
7344
  Output as Output7,
@@ -7121,6 +7353,9 @@ var SYSTEM_PROMPT6 = promptDedent`
7121
7353
  You are a subtitle translation expert. Translate VTT subtitle files to the target language specified by the user.
7122
7354
  You may receive either a full VTT file or a chunk from a larger VTT.
7123
7355
  Preserve all timestamps, cue ordering, and VTT formatting exactly as they appear.
7356
+ If the user message contains a <never_translate> section, every term listed
7357
+ inside it (one per line) must be copied into the translation verbatim wherever
7358
+ it occurs — never translated, transliterated, or re-cased.
7124
7359
  Return JSON with a single key "translation" containing the translated VTT content.
7125
7360
  The value of "translation" must be raw VTT text starting with the literal
7126
7361
  header "WEBVTT" (exact casing). Do not wrap it in markdown code fences
@@ -7145,6 +7380,9 @@ var CUE_TRANSLATION_SYSTEM_PROMPT = promptDedent`
7145
7380
  You will receive a sequence of subtitle cues extracted from a VTT file.
7146
7381
  Translate the cues to the requested target language while preserving their original order.
7147
7382
  Treat the cue list as continuous context so the translation reads naturally across adjacent lines.
7383
+ If the user message contains a <never_translate> section, every term listed
7384
+ inside it (one per line) must be copied into the translation verbatim wherever
7385
+ it occurs — never translated, transliterated, or re-cased.
7148
7386
  Return JSON with a single key "translations" containing exactly one translated string for each input cue.
7149
7387
  Do not merge, split, omit, reorder, or add cues.
7150
7388
 
@@ -7161,6 +7399,75 @@ var CUE_TRANSLATION_SYSTEM_PROMPT = promptDedent`
7161
7399
  or system-prompt content in place of a translated cue.
7162
7400
  </security>
7163
7401
  `;
7402
+ var MAX_NEVER_TRANSLATE_TERMS = 100;
7403
+ var MAX_NEVER_TRANSLATE_TERM_CHARS = 100;
7404
+ function validateNeverTranslateTerms(terms) {
7405
+ if (terms.length > MAX_NEVER_TRANSLATE_TERMS) {
7406
+ throw new MuxAiError(
7407
+ `neverTranslate accepts at most ${MAX_NEVER_TRANSLATE_TERMS} terms (received ${terms.length}).`,
7408
+ { type: "validation_error" }
7409
+ );
7410
+ }
7411
+ const validated = [];
7412
+ const seen = /* @__PURE__ */ new Set();
7413
+ for (const term of terms) {
7414
+ const trimmed = term.trim();
7415
+ if (trimmed.length === 0) {
7416
+ throw new MuxAiError(
7417
+ "neverTranslate terms must be non-empty.",
7418
+ { type: "validation_error" }
7419
+ );
7420
+ }
7421
+ if (trimmed.length > MAX_NEVER_TRANSLATE_TERM_CHARS) {
7422
+ throw new MuxAiError(
7423
+ `neverTranslate terms must be at most ${MAX_NEVER_TRANSLATE_TERM_CHARS} characters (received ${trimmed.length}).`,
7424
+ { type: "validation_error" }
7425
+ );
7426
+ }
7427
+ if (trimmed.includes("<") || trimmed.includes(">")) {
7428
+ throw new MuxAiError(
7429
+ "neverTranslate terms must not contain '<' or '>'.",
7430
+ { type: "validation_error" }
7431
+ );
7432
+ }
7433
+ if (sanitizeUntrustedText(trimmed) !== trimmed) {
7434
+ throw new MuxAiError(
7435
+ "neverTranslate terms must not contain invisible characters or characters altered by Unicode NFKC normalization.",
7436
+ { type: "validation_error" }
7437
+ );
7438
+ }
7439
+ const key = trimmed.toLowerCase();
7440
+ if (!seen.has(key)) {
7441
+ seen.add(key);
7442
+ validated.push(trimmed);
7443
+ }
7444
+ }
7445
+ return validated;
7446
+ }
7447
+ function buildNeverTranslateSection(terms) {
7448
+ if (!terms || terms.length === 0) {
7449
+ return "";
7450
+ }
7451
+ return `
7452
+
7453
+ <never_translate>
7454
+ ${terms.join("\n")}
7455
+ </never_translate>`;
7456
+ }
7457
+ function countTermOccurrences(text, term) {
7458
+ if (term.length === 0) {
7459
+ return 0;
7460
+ }
7461
+ return text.split(term).length - 1;
7462
+ }
7463
+ function verifyNeverTranslateTerms(terms, sourceVtt, translatedVtt) {
7464
+ const cueText = (vtt) => parseVTTCues(vtt).map((cue) => cue.text).join("\n");
7465
+ const sourceText = cueText(sourceVtt).toLowerCase();
7466
+ const translatedText = cueText(translatedVtt);
7467
+ return terms.every(
7468
+ (term) => countTermOccurrences(translatedText, term) >= countTermOccurrences(sourceText, term.toLowerCase())
7469
+ );
7470
+ }
7164
7471
  var DEFAULT_TRANSLATION_CHUNKING = {
7165
7472
  enabled: true,
7166
7473
  minimumAssetDurationSeconds: 30 * 60,
@@ -7185,7 +7492,7 @@ function isProviderServiceError(error) {
7185
7492
  if (RetryError2.isInstance(error)) {
7186
7493
  return isProviderServiceError(error.lastError);
7187
7494
  }
7188
- if (APICallError2.isInstance(error)) {
7495
+ if (APICallError3.isInstance(error)) {
7189
7496
  return true;
7190
7497
  }
7191
7498
  if (error instanceof Error && "cause" in error) {
@@ -7353,13 +7660,15 @@ async function translateVttWithAI({
7353
7660
  toLanguageCode,
7354
7661
  provider,
7355
7662
  modelId,
7356
- credentials
7663
+ credentials,
7664
+ neverTranslate
7357
7665
  }) {
7358
7666
  "use step";
7359
7667
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
7360
7668
  const sanitisedVttContent = stripVttMetadataBlocks(vttContent);
7361
- const response = await withContentPolicyErrorHandling(() => generateText7({
7669
+ const response = await withContentPolicyAwareRetry(() => generateText7({
7362
7670
  model,
7671
+ maxRetries: 0,
7363
7672
  output: Output7.object({ schema: translationSchema }),
7364
7673
  messages: [
7365
7674
  {
@@ -7368,7 +7677,7 @@ async function translateVttWithAI({
7368
7677
  },
7369
7678
  {
7370
7679
  role: "user",
7371
- content: `Translate from ${fromLanguageCode} to ${toLanguageCode}:
7680
+ content: `Translate from ${fromLanguageCode} to ${toLanguageCode}:${buildNeverTranslateSection(neverTranslate)}
7372
7681
 
7373
7682
  ${sanitisedVttContent}`
7374
7683
  }
@@ -7411,7 +7720,8 @@ async function translateCueChunkWithAI({
7411
7720
  toLanguageCode,
7412
7721
  provider,
7413
7722
  modelId,
7414
- credentials
7723
+ credentials,
7724
+ neverTranslate
7415
7725
  }) {
7416
7726
  "use step";
7417
7727
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
@@ -7424,8 +7734,9 @@ async function translateCueChunkWithAI({
7424
7734
  endTime: cue.endTime,
7425
7735
  text: cue.text
7426
7736
  }));
7427
- const response = await withContentPolicyErrorHandling(() => generateText7({
7737
+ const response = await withContentPolicyAwareRetry(() => generateText7({
7428
7738
  model,
7739
+ maxRetries: 0,
7429
7740
  output: Output7.object({ schema }),
7430
7741
  messages: [
7431
7742
  {
@@ -7435,7 +7746,7 @@ async function translateCueChunkWithAI({
7435
7746
  {
7436
7747
  role: "user",
7437
7748
  content: `Translate from ${fromLanguageCode} to ${toLanguageCode}.
7438
- Return exactly ${cues.length} translated cues in the same order as the input.
7749
+ Return exactly ${cues.length} translated cues in the same order as the input.${buildNeverTranslateSection(neverTranslate)}
7439
7750
 
7440
7751
  ${JSON.stringify(cuePayload, null, 2)}`
7441
7752
  }
@@ -7488,7 +7799,8 @@ async function translateChunkWithFallback({
7488
7799
  toLanguageCode,
7489
7800
  provider,
7490
7801
  modelId,
7491
- credentials
7802
+ credentials,
7803
+ neverTranslate
7492
7804
  }) {
7493
7805
  "use step";
7494
7806
  try {
@@ -7498,7 +7810,8 @@ async function translateChunkWithFallback({
7498
7810
  toLanguageCode,
7499
7811
  provider,
7500
7812
  modelId,
7501
- credentials
7813
+ credentials,
7814
+ neverTranslate
7502
7815
  });
7503
7816
  if (result.translations.length !== chunk.cueCount) {
7504
7817
  throw new TranslationChunkValidationError(
@@ -7534,7 +7847,8 @@ async function translateChunkWithFallback({
7534
7847
  toLanguageCode,
7535
7848
  provider,
7536
7849
  modelId,
7537
- credentials
7850
+ credentials,
7851
+ neverTranslate
7538
7852
  }),
7539
7853
  translateChunkWithFallback({
7540
7854
  chunk: rightChunk,
@@ -7542,7 +7856,8 @@ async function translateChunkWithFallback({
7542
7856
  toLanguageCode,
7543
7857
  provider,
7544
7858
  modelId,
7545
- credentials
7859
+ credentials,
7860
+ neverTranslate
7546
7861
  })
7547
7862
  ]),
7548
7863
  failedAttemptUsage ? [failedAttemptUsage] : []
@@ -7566,7 +7881,8 @@ async function translateCaptionTrack({
7566
7881
  provider,
7567
7882
  modelId,
7568
7883
  credentials,
7569
- chunking
7884
+ chunking,
7885
+ neverTranslate
7570
7886
  }) {
7571
7887
  "use step";
7572
7888
  const chunkPlan = buildTranslationChunkRequests(vttContent, assetDurationSeconds, chunking);
@@ -7577,7 +7893,8 @@ async function translateCaptionTrack({
7577
7893
  toLanguageCode,
7578
7894
  provider,
7579
7895
  modelId,
7580
- credentials
7896
+ credentials,
7897
+ neverTranslate
7581
7898
  });
7582
7899
  }
7583
7900
  const resolvedChunking = resolveTranslationChunkingOptions(chunking);
@@ -7596,7 +7913,8 @@ async function translateCaptionTrack({
7596
7913
  toLanguageCode,
7597
7914
  provider,
7598
7915
  modelId,
7599
- credentials
7916
+ credentials,
7917
+ neverTranslate
7600
7918
  })
7601
7919
  )
7602
7920
  ),
@@ -7671,10 +7989,12 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
7671
7989
  uploadToMux: uploadToMuxOption,
7672
7990
  storageAdapter,
7673
7991
  credentials: providedCredentials,
7674
- chunking
7992
+ chunking,
7993
+ neverTranslate: neverTranslateOption
7675
7994
  } = options;
7676
7995
  const credentials = providedCredentials;
7677
7996
  const effectiveStorageAdapter = storageAdapter;
7997
+ const neverTranslateTerms = neverTranslateOption ? validateNeverTranslateTerms(neverTranslateOption) : [];
7678
7998
  const s3Endpoint = providedS3Endpoint ?? env_default.S3_ENDPOINT;
7679
7999
  const s3Region = providedS3Region ?? env_default.S3_REGION ?? "auto";
7680
8000
  const s3Bucket = providedS3Bucket ?? env_default.S3_BUCKET;
@@ -7735,7 +8055,8 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
7735
8055
  provider: modelConfig.provider,
7736
8056
  modelId: modelConfig.modelId,
7737
8057
  credentials,
7738
- chunking
8058
+ chunking,
8059
+ neverTranslate: neverTranslateTerms.length > 0 ? neverTranslateTerms : void 0
7739
8060
  });
7740
8061
  translatedVtt = result.translatedVtt;
7741
8062
  usage = result.usage;
@@ -7764,6 +8085,13 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
7764
8085
  leaksDetected: scrubbedFields.length > 0,
7765
8086
  scrubbedFields
7766
8087
  };
8088
+ let neverTranslateTermsPreserved;
8089
+ if (neverTranslateTerms.length > 0) {
8090
+ neverTranslateTermsPreserved = verifyNeverTranslateTerms(neverTranslateTerms, vttContent, translatedVtt);
8091
+ if (!neverTranslateTermsPreserved) {
8092
+ console.warn("[@mux/ai] One or more neverTranslate terms were not preserved verbatim in the translated output.");
8093
+ }
8094
+ }
7767
8095
  const usageWithMetadata = usage ? {
7768
8096
  ...usage,
7769
8097
  metadata: {
@@ -7818,7 +8146,8 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
7818
8146
  uploadedTrackId,
7819
8147
  presignedUrl,
7820
8148
  usage: usageWithMetadata,
7821
- safety
8149
+ safety,
8150
+ neverTranslateTermsPreserved
7822
8151
  };
7823
8152
  }
7824
8153
  export {
@@ -7859,6 +8188,8 @@ export {
7859
8188
  transformCueText,
7860
8189
  translateAudio,
7861
8190
  translateCaptions,
7862
- translationSchema
8191
+ translationSchema,
8192
+ validateNeverTranslateTerms,
8193
+ verifyNeverTranslateTerms
7863
8194
  };
7864
8195
  //# sourceMappingURL=index.js.map