@mux/ai 0.31.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/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // package.json
8
- var version = "0.31.0";
8
+ var version = "0.32.0";
9
9
 
10
10
  // src/lib/language-codes.ts
11
11
  var ISO639_1_TO_3 = {
@@ -250,6 +250,43 @@ var EnvSchema = z.object({
250
250
  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')"),
251
251
  // AI Providers
252
252
  OPENAI_API_KEY: optionalString("OpenAI API key for OpenAI-backed workflows.", "OpenAI API key"),
253
+ BASETEN_API_KEY: optionalString("Baseten API key for Baseten-backed workflows.", "Baseten API key"),
254
+ BASETEN_URL: optionalString(
255
+ "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.",
256
+ "Baseten URL"
257
+ ),
258
+ BASETEN_EMBEDDING_URL: optionalString(
259
+ "Baseten dedicated embedding deployment URL, such as https://model-{MODEL_ID}.api.baseten.co/environments/production/sync. Required for Baseten embeddings.",
260
+ "Baseten embedding URL"
261
+ ),
262
+ BASETEN_MODEL: optionalString(
263
+ "Default Baseten language model identifier used when provider='baseten' and no explicit model is passed.",
264
+ "Baseten model"
265
+ ),
266
+ BASETEN_EMBEDDING_MODEL: optionalString(
267
+ "Default Baseten embedding model identifier used when provider='baseten' and no explicit model is passed.",
268
+ "Baseten embedding model"
269
+ ),
270
+ OPENAI_COMPATIBLE_API_KEY: optionalString(
271
+ "Optional API key for OpenAI-compatible endpoints. Some local or self-hosted endpoints do not require one.",
272
+ "OpenAI-compatible API key"
273
+ ),
274
+ OPENAI_COMPATIBLE_BASE_URL: optionalString(
275
+ "Base URL of an OpenAI-compatible API, such as https://my-endpoint.example.com/v1.",
276
+ "OpenAI-compatible base URL"
277
+ ),
278
+ OPENAI_COMPATIBLE_EMBEDDING_BASE_URL: optionalString(
279
+ "Optional embedding-specific OpenAI-compatible base URL. Falls back to OPENAI_COMPATIBLE_BASE_URL.",
280
+ "OpenAI-compatible embedding base URL"
281
+ ),
282
+ OPENAI_COMPATIBLE_MODEL: optionalString(
283
+ "Default language model identifier used when provider='openai-compatible' and no explicit model is passed.",
284
+ "OpenAI-compatible model"
285
+ ),
286
+ OPENAI_COMPATIBLE_EMBEDDING_MODEL: optionalString(
287
+ "Default embedding model identifier used when provider='openai-compatible' and no explicit model is passed.",
288
+ "OpenAI-compatible embedding model"
289
+ ),
253
290
  ANTHROPIC_API_KEY: optionalString("Anthropic API key for Claude-backed workflows.", "Anthropic API key"),
254
291
  GOOGLE_GENERATIVE_AI_API_KEY: optionalString("Google Generative AI API key for Gemini-backed workflows.", "Google Generative AI API key"),
255
292
  ELEVENLABS_API_KEY: optionalString("ElevenLabs API key for audio translation.", "ElevenLabs API key"),
@@ -1025,6 +1062,7 @@ async function resolveMuxClient(credentials) {
1025
1062
  function resolveProviderApiKeyFromCredentials(provider, resolved) {
1026
1063
  const record = resolved;
1027
1064
  const openaiApiKey = readString(record, "openaiApiKey");
1065
+ const basetenApiKey = readString(record, "basetenApiKey");
1028
1066
  const anthropicApiKey = readString(record, "anthropicApiKey");
1029
1067
  const googleApiKey = readString(record, "googleApiKey");
1030
1068
  const googleVisionApiKey = readString(record, "googleVisionApiKey");
@@ -1032,6 +1070,7 @@ function resolveProviderApiKeyFromCredentials(provider, resolved) {
1032
1070
  const elevenLabsApiKey = readString(record, "elevenLabsApiKey");
1033
1071
  const apiKeyMap = {
1034
1072
  "openai": openaiApiKey ?? env_default.OPENAI_API_KEY,
1073
+ "baseten": basetenApiKey ?? env_default.BASETEN_API_KEY,
1035
1074
  "anthropic": anthropicApiKey ?? env_default.ANTHROPIC_API_KEY,
1036
1075
  "google": googleApiKey ?? env_default.GOOGLE_GENERATIVE_AI_API_KEY,
1037
1076
  "google-vision-api": googleVisionApiKey ?? env_default.GOOGLE_VISION_API_KEY,
@@ -1042,6 +1081,7 @@ function resolveProviderApiKeyFromCredentials(provider, resolved) {
1042
1081
  if (!apiKey) {
1043
1082
  const envVarNames = {
1044
1083
  "openai": "OPENAI_API_KEY",
1084
+ "baseten": "BASETEN_API_KEY",
1045
1085
  "anthropic": "ANTHROPIC_API_KEY",
1046
1086
  "google": "GOOGLE_GENERATIVE_AI_API_KEY",
1047
1087
  "google-vision-api": "GOOGLE_VISION_API_KEY",
@@ -1410,6 +1450,7 @@ __export(primitives_exports, {
1410
1450
  import { createAnthropic } from "@ai-sdk/anthropic";
1411
1451
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
1412
1452
  import { createOpenAI } from "@ai-sdk/openai";
1453
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
1413
1454
  var DEFAULT_LANGUAGE_MODELS = {
1414
1455
  // GPT-5.6 models default to medium reasoning when no effort is specified.
1415
1456
  openai: "gpt-5.6-luna",
@@ -1420,6 +1461,60 @@ var DEFAULT_EMBEDDING_MODELS = {
1420
1461
  openai: "text-embedding-3-small",
1421
1462
  google: "gemini-embedding-001"
1422
1463
  };
1464
+ function resolveBasetenLanguageModelId(model) {
1465
+ const resolved = model ?? env_default.BASETEN_MODEL;
1466
+ if (!resolved) {
1467
+ throw new Error(
1468
+ 'Baseten model is required. Pass `model` when provider is "baseten" or set BASETEN_MODEL.'
1469
+ );
1470
+ }
1471
+ return resolved;
1472
+ }
1473
+ function resolveBasetenEmbeddingModelId(model) {
1474
+ const resolved = model ?? env_default.BASETEN_EMBEDDING_MODEL;
1475
+ if (!resolved) {
1476
+ throw new Error(
1477
+ 'Baseten embedding model is required. Pass `model` when provider is "baseten" or set BASETEN_EMBEDDING_MODEL.'
1478
+ );
1479
+ }
1480
+ return resolved;
1481
+ }
1482
+ function resolveOpenAICompatibleLanguageModelId(model) {
1483
+ const resolved = model ?? env_default.OPENAI_COMPATIBLE_MODEL;
1484
+ if (!resolved) {
1485
+ throw new Error(
1486
+ 'OpenAI-compatible model is required. Pass `model` when provider is "openai-compatible" or set OPENAI_COMPATIBLE_MODEL.'
1487
+ );
1488
+ }
1489
+ return resolved;
1490
+ }
1491
+ function resolveOpenAICompatibleEmbeddingModelId(model) {
1492
+ const resolved = model ?? env_default.OPENAI_COMPATIBLE_EMBEDDING_MODEL;
1493
+ if (!resolved) {
1494
+ throw new Error(
1495
+ 'OpenAI-compatible embedding model is required. Pass `model` when provider is "openai-compatible" or set OPENAI_COMPATIBLE_EMBEDDING_MODEL.'
1496
+ );
1497
+ }
1498
+ return resolved;
1499
+ }
1500
+ function getDefaultLanguageModel(provider) {
1501
+ if (provider === "baseten") {
1502
+ return resolveBasetenLanguageModelId();
1503
+ }
1504
+ if (provider === "openai-compatible") {
1505
+ return resolveOpenAICompatibleLanguageModelId();
1506
+ }
1507
+ return DEFAULT_LANGUAGE_MODELS[provider];
1508
+ }
1509
+ function getDefaultEmbeddingModel(provider) {
1510
+ if (provider === "baseten") {
1511
+ return resolveBasetenEmbeddingModelId();
1512
+ }
1513
+ if (provider === "openai-compatible") {
1514
+ return resolveOpenAICompatibleEmbeddingModelId();
1515
+ }
1516
+ return DEFAULT_EMBEDDING_MODELS[provider];
1517
+ }
1423
1518
  var LANGUAGE_MODELS = {
1424
1519
  openai: ["gpt-5.6-luna", "gpt-5.1", "gpt-5-mini"],
1425
1520
  anthropic: ["claude-sonnet-4-5"],
@@ -1485,7 +1580,7 @@ function getDefaultEvalModelConfigs() {
1485
1580
  function getAllEvalModelConfigs() {
1486
1581
  return Object.entries(LANGUAGE_MODELS).flatMap(([provider, models]) => models.map((modelId) => ({ provider, modelId })));
1487
1582
  }
1488
- function isSupportedProvider(value) {
1583
+ function isSupportedEvalProvider(value) {
1489
1584
  return value === "openai" || value === "anthropic" || value === "google";
1490
1585
  }
1491
1586
  function parseEvalModelPair(value) {
@@ -1498,7 +1593,10 @@ function parseEvalModelPair(value) {
1498
1593
  `Invalid eval model pair "${value}". Use "provider:model" (example: "openai:gpt-5.6-luna").`
1499
1594
  );
1500
1595
  }
1501
- if (!isSupportedProvider(provider)) {
1596
+ if (provider === "baseten" || provider === "openai-compatible") {
1597
+ return void 0;
1598
+ }
1599
+ if (!isSupportedEvalProvider(provider)) {
1502
1600
  throw new Error(
1503
1601
  `Unsupported eval provider "${provider}" in "${value}". Supported providers: ${Object.keys(LANGUAGE_MODELS).join(", ")}.`
1504
1602
  );
@@ -1519,7 +1617,7 @@ function resolveEvalModelConfigs(options = {}) {
1519
1617
  const explicitPairs = options.modelPairs?.map((value) => value.trim()).filter(Boolean) ?? [];
1520
1618
  if (explicitPairs.length > 0) {
1521
1619
  const dedupedPairs = Array.from(new Set(explicitPairs));
1522
- return dedupedPairs.map(parseEvalModelPair);
1620
+ return dedupedPairs.map(parseEvalModelPair).filter((config) => Boolean(config));
1523
1621
  }
1524
1622
  const selection = options.selection ?? "default";
1525
1623
  if (selection === "all") {
@@ -1552,30 +1650,140 @@ function resolveEvalModelConfigsFromEnv(environment = env_default) {
1552
1650
  var EVAL_MODEL_CONFIGS = resolveEvalModelConfigsFromEnv();
1553
1651
  function resolveLanguageModelConfig(options = {}) {
1554
1652
  const provider = options.provider || "openai";
1555
- const modelId = options.model || DEFAULT_LANGUAGE_MODELS[provider];
1653
+ const modelId = options.model || getDefaultLanguageModel(provider);
1556
1654
  maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId);
1557
1655
  return { provider, modelId };
1558
1656
  }
1559
1657
  function resolveEmbeddingModelConfig(options = {}) {
1560
1658
  const provider = options.provider || "openai";
1561
- const modelId = options.model || DEFAULT_EMBEDDING_MODELS[provider];
1659
+ const modelId = options.model || getDefaultEmbeddingModel(provider);
1562
1660
  return { provider, modelId };
1563
1661
  }
1662
+ var BASETEN_MODEL_APIS_BASE_URL = "https://inference.baseten.co/v1";
1663
+ function readCredentialString(record, key) {
1664
+ const value = record?.[key];
1665
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1666
+ }
1667
+ function normalizeBaseUrl(value) {
1668
+ const trimmed = value?.trim();
1669
+ if (!trimmed) {
1670
+ return void 0;
1671
+ }
1672
+ return trimmed.replace(/\/(chat\/completions|responses|embeddings)\/?$/, "").replace(/\/$/, "");
1673
+ }
1674
+ function isDedicatedBasetenModelUrl(value) {
1675
+ return Boolean(value && (value.includes("/sync") || value.includes("/predict")));
1676
+ }
1677
+ function validateDedicatedBasetenUrl(url, kind) {
1678
+ if (kind === "language") {
1679
+ if (!url.includes("/sync/v1")) {
1680
+ throw new Error(
1681
+ `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.`
1682
+ );
1683
+ }
1684
+ return url;
1685
+ }
1686
+ if (!url.includes("/sync")) {
1687
+ throw new Error(
1688
+ "Baseten embeddings require a dedicated /sync or /sync/v1 deployment URL. Set BASETEN_EMBEDDING_URL or provide basetenEmbeddingUrl in credentials."
1689
+ );
1690
+ }
1691
+ return url;
1692
+ }
1693
+ function basetenEndpointBaseUrl(url, kind) {
1694
+ if (kind === "embedding" && !url.includes("/sync/v1")) {
1695
+ return `${url}/v1`;
1696
+ }
1697
+ return url;
1698
+ }
1699
+ function basetenBaseUrlFromUrl(url, kind) {
1700
+ const normalized = normalizeBaseUrl(url);
1701
+ if (!normalized) {
1702
+ if (kind === "embedding") {
1703
+ throw new Error(
1704
+ "Baseten embedding URL is required. Set BASETEN_EMBEDDING_URL or provide basetenEmbeddingUrl in credentials."
1705
+ );
1706
+ }
1707
+ return BASETEN_MODEL_APIS_BASE_URL;
1708
+ }
1709
+ if (isDedicatedBasetenModelUrl(normalized)) {
1710
+ return basetenEndpointBaseUrl(validateDedicatedBasetenUrl(normalized, kind), kind);
1711
+ }
1712
+ if (kind === "embedding") {
1713
+ throw new Error(
1714
+ "Baseten embeddings require a dedicated /sync or /sync/v1 deployment URL. Set BASETEN_EMBEDDING_URL or provide basetenEmbeddingUrl in credentials."
1715
+ );
1716
+ }
1717
+ return normalized;
1718
+ }
1719
+ function resolveBasetenBaseUrlFromRecord(record, kind) {
1720
+ if (kind === "embedding") {
1721
+ return basetenBaseUrlFromUrl(
1722
+ readCredentialString(record, "basetenEmbeddingUrl") ?? env_default.BASETEN_EMBEDDING_URL,
1723
+ kind
1724
+ );
1725
+ }
1726
+ return basetenBaseUrlFromUrl(
1727
+ readCredentialString(record, "basetenUrl") ?? env_default.BASETEN_URL,
1728
+ kind
1729
+ );
1730
+ }
1731
+ function resolveOpenAICompatibleSettingsFromRecord(record, kind) {
1732
+ const apiKey = readCredentialString(record, "openaiCompatibleApiKey") ?? env_default.OPENAI_COMPATIBLE_API_KEY;
1733
+ const sharedBaseUrl = readCredentialString(record, "openaiCompatibleBaseUrl") ?? env_default.OPENAI_COMPATIBLE_BASE_URL;
1734
+ const baseUrl = normalizeBaseUrl(
1735
+ kind === "embedding" ? readCredentialString(record, "openaiCompatibleEmbeddingBaseUrl") ?? env_default.OPENAI_COMPATIBLE_EMBEDDING_BASE_URL ?? sharedBaseUrl : sharedBaseUrl
1736
+ );
1737
+ if (!baseUrl) {
1738
+ throw new Error(
1739
+ "OpenAI-compatible base URL is required. Set OPENAI_COMPATIBLE_BASE_URL or provide openaiCompatibleBaseUrl in credentials."
1740
+ );
1741
+ }
1742
+ return { apiKey, baseURL: baseUrl };
1743
+ }
1744
+ function createBasetenProvider(apiKey, baseURL) {
1745
+ return createOpenAICompatible({
1746
+ name: "baseten",
1747
+ apiKey,
1748
+ baseURL,
1749
+ supportsStructuredOutputs: true
1750
+ });
1751
+ }
1752
+ function createGenericOpenAICompatibleProvider(settings) {
1753
+ return createOpenAICompatible({
1754
+ name: "openai-compatible",
1755
+ ...settings,
1756
+ supportsStructuredOutputs: true
1757
+ });
1758
+ }
1564
1759
  async function createLanguageModelFromConfig(provider, modelId, credentials) {
1565
1760
  maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId);
1761
+ const resolvedCredentials = await resolveWorkflowCredentials(credentials);
1762
+ const credentialsRecord = resolvedCredentials;
1566
1763
  switch (provider) {
1567
1764
  case "openai": {
1568
- const apiKey = await resolveProviderApiKey("openai", credentials);
1765
+ const apiKey = resolveProviderApiKeyFromCredentials("openai", resolvedCredentials);
1569
1766
  const openai = createOpenAI({ apiKey });
1570
1767
  return openai(modelId);
1571
1768
  }
1769
+ case "baseten": {
1770
+ const apiKey = resolveProviderApiKeyFromCredentials("baseten", resolvedCredentials);
1771
+ const baseten = createBasetenProvider(apiKey, resolveBasetenBaseUrlFromRecord(credentialsRecord, "language"));
1772
+ return baseten.chatModel(modelId);
1773
+ }
1774
+ case "openai-compatible": {
1775
+ const compatible = createGenericOpenAICompatibleProvider(
1776
+ resolveOpenAICompatibleSettingsFromRecord(credentialsRecord, "language")
1777
+ );
1778
+ return compatible.chatModel(modelId);
1779
+ }
1572
1780
  case "anthropic": {
1573
- const apiKey = await resolveProviderApiKey("anthropic", credentials);
1781
+ const apiKey = resolveProviderApiKeyFromCredentials("anthropic", resolvedCredentials);
1574
1782
  const anthropic = createAnthropic({ apiKey });
1575
1783
  return anthropic(modelId);
1576
1784
  }
1577
1785
  case "google": {
1578
- const apiKey = await resolveProviderApiKey("google", credentials);
1786
+ const apiKey = resolveProviderApiKeyFromCredentials("google", resolvedCredentials);
1579
1787
  const google = createGoogleGenerativeAI({ apiKey });
1580
1788
  return google(modelId);
1581
1789
  }
@@ -1586,14 +1794,27 @@ async function createLanguageModelFromConfig(provider, modelId, credentials) {
1586
1794
  }
1587
1795
  }
1588
1796
  async function createEmbeddingModelFromConfig(provider, modelId, credentials) {
1797
+ const resolvedCredentials = await resolveWorkflowCredentials(credentials);
1798
+ const credentialsRecord = resolvedCredentials;
1589
1799
  switch (provider) {
1590
1800
  case "openai": {
1591
- const apiKey = await resolveProviderApiKey("openai", credentials);
1801
+ const apiKey = resolveProviderApiKeyFromCredentials("openai", resolvedCredentials);
1592
1802
  const openai = createOpenAI({ apiKey });
1593
1803
  return openai.embedding(modelId);
1594
1804
  }
1805
+ case "baseten": {
1806
+ const apiKey = resolveProviderApiKeyFromCredentials("baseten", resolvedCredentials);
1807
+ const baseten = createBasetenProvider(apiKey, resolveBasetenBaseUrlFromRecord(credentialsRecord, "embedding"));
1808
+ return baseten.textEmbeddingModel(modelId);
1809
+ }
1810
+ case "openai-compatible": {
1811
+ const compatible = createGenericOpenAICompatibleProvider(
1812
+ resolveOpenAICompatibleSettingsFromRecord(credentialsRecord, "embedding")
1813
+ );
1814
+ return compatible.textEmbeddingModel(modelId);
1815
+ }
1595
1816
  case "google": {
1596
- const apiKey = await resolveProviderApiKey("google", credentials);
1817
+ const apiKey = resolveProviderApiKeyFromCredentials("google", resolvedCredentials);
1597
1818
  const google = createGoogleGenerativeAI({ apiKey });
1598
1819
  return google.textEmbeddingModel(modelId);
1599
1820
  }
@@ -2266,6 +2487,7 @@ async function getThumbnailUrls(playbackId, duration, options = {}) {
2266
2487
  timestamps.push(time);
2267
2488
  }
2268
2489
  }
2490
+ timestamps = [...new Set(timestamps)];
2269
2491
  if (maxSamples !== void 0 && timestamps.length > maxSamples) {
2270
2492
  const newTimestamps = [];
2271
2493
  newTimestamps.push(resolvedScope.startTime);
@@ -2831,8 +3053,71 @@ import dedent2 from "dedent";
2831
3053
  import { z as z3 } from "zod";
2832
3054
 
2833
3055
  // src/lib/content-policy-error.ts
2834
- import { APICallError, NoObjectGeneratedError, RetryError } from "ai";
3056
+ import { APICallError as APICallError2, NoObjectGeneratedError, RetryError } from "ai";
2835
3057
  import { z as z2 } from "zod";
3058
+
3059
+ // src/lib/retry.ts
3060
+ import { APICallError, DownloadError, NoOutputGeneratedError } from "ai";
3061
+ var DEFAULT_RETRY_OPTIONS = {
3062
+ maxRetries: 3,
3063
+ baseDelay: 2e3,
3064
+ maxDelay: 1e4
3065
+ };
3066
+ function defaultShouldRetry(error, _attempt) {
3067
+ if (error.message.includes("Timeout while downloading")) {
3068
+ return true;
3069
+ }
3070
+ if (NoOutputGeneratedError.isInstance(error) || error.name === "AI_NoOutputGeneratedError" || error.name === "AI_NoObjectGeneratedError") {
3071
+ return true;
3072
+ }
3073
+ if (APICallError.isInstance(error) || error.name === "AI_APICallError") {
3074
+ const apiError = error;
3075
+ if (typeof apiError.isRetryable === "boolean") {
3076
+ return apiError.isRetryable;
3077
+ }
3078
+ return apiError.statusCode === 408 || apiError.statusCode === 409 || apiError.statusCode === 429 || apiError.statusCode !== void 0 && apiError.statusCode >= 500;
3079
+ }
3080
+ const isDownloadError = DownloadError.isInstance(error);
3081
+ const isSerializedDownloadError = error.name === "AI_DownloadError";
3082
+ if (!isDownloadError && !isSerializedDownloadError) {
3083
+ return false;
3084
+ }
3085
+ const statusCode = error.statusCode;
3086
+ return statusCode === void 0 || statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
3087
+ }
3088
+ function calculateDelay(attempt, baseDelay, maxDelay) {
3089
+ const exponentialDelay = baseDelay * 2 ** (attempt - 1);
3090
+ const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);
3091
+ return Math.min(delayWithJitter, maxDelay);
3092
+ }
3093
+ async function withRetry(fn, {
3094
+ maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,
3095
+ baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,
3096
+ maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,
3097
+ shouldRetry = defaultShouldRetry
3098
+ } = {}) {
3099
+ let lastError;
3100
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
3101
+ try {
3102
+ return await fn();
3103
+ } catch (error) {
3104
+ lastError = error;
3105
+ const retryError = error instanceof Error ? error : new Error(String(error));
3106
+ const isLastAttempt = attempt === maxRetries;
3107
+ if (isLastAttempt || !shouldRetry(retryError, attempt + 1)) {
3108
+ throw error;
3109
+ }
3110
+ const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);
3111
+ console.warn(
3112
+ `Attempt ${attempt + 1} failed: ${retryError.message}. Retrying in ${Math.round(delay)}ms...`
3113
+ );
3114
+ await new Promise((resolve) => setTimeout(resolve, delay));
3115
+ }
3116
+ }
3117
+ throw lastError ?? new Error("Retry failed with unknown error");
3118
+ }
3119
+
3120
+ // src/lib/content-policy-error.ts
2836
3121
  var PolicyTokenSchema = z2.string().trim().min(1).max(64).regex(/^[A-Z0-9_]+$/);
2837
3122
  var SafetyRatingSchema = z2.object({
2838
3123
  category: PolicyTokenSchema.nullish(),
@@ -2877,6 +3162,12 @@ async function withContentPolicyErrorHandling(operation) {
2877
3162
  rethrowContentPolicyError(error);
2878
3163
  }
2879
3164
  }
3165
+ async function withContentPolicyAwareRetry(operation, retryOptions) {
3166
+ return withRetry(
3167
+ () => withContentPolicyErrorHandling(operation),
3168
+ retryOptions
3169
+ );
3170
+ }
2880
3171
  function rethrowContentPolicyError(error) {
2881
3172
  const block = extractContentPolicyBlock(error);
2882
3173
  if (!block) {
@@ -2913,7 +3204,7 @@ function extractContentPolicyBlock(error) {
2913
3204
  if (NoObjectGeneratedError.isInstance(error) && error.finishReason === "content-filter") {
2914
3205
  return { reason: "CONTENT_FILTER" };
2915
3206
  }
2916
- if (!APICallError.isInstance(error) || !error.responseBody) {
3207
+ if (!APICallError2.isInstance(error) || !error.responseBody) {
2917
3208
  return void 0;
2918
3209
  }
2919
3210
  try {
@@ -2952,7 +3243,7 @@ function getContentPolicyTokenUsage(error) {
2952
3243
  if (RetryError.isInstance(error)) {
2953
3244
  return getContentPolicyTokenUsage(error.lastError);
2954
3245
  }
2955
- if (!APICallError.isInstance(error) || !error.responseBody) {
3246
+ if (!APICallError2.isInstance(error) || !error.responseBody) {
2956
3247
  return void 0;
2957
3248
  }
2958
3249
  try {
@@ -3166,57 +3457,6 @@ function createLanguageSection(languageName) {
3166
3457
  };
3167
3458
  }
3168
3459
 
3169
- // src/lib/retry.ts
3170
- import { DownloadError } from "ai";
3171
- var DEFAULT_RETRY_OPTIONS = {
3172
- maxRetries: 3,
3173
- baseDelay: 2e3,
3174
- maxDelay: 1e4
3175
- };
3176
- function defaultShouldRetry(error, _attempt) {
3177
- if (error.message.includes("Timeout while downloading")) {
3178
- return true;
3179
- }
3180
- const isDownloadError = DownloadError.isInstance(error);
3181
- const isSerializedDownloadError = error.name === "AI_DownloadError";
3182
- if (!isDownloadError && !isSerializedDownloadError) {
3183
- return false;
3184
- }
3185
- const statusCode = error.statusCode;
3186
- return statusCode === void 0 || statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
3187
- }
3188
- function calculateDelay(attempt, baseDelay, maxDelay) {
3189
- const exponentialDelay = baseDelay * 2 ** (attempt - 1);
3190
- const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);
3191
- return Math.min(delayWithJitter, maxDelay);
3192
- }
3193
- async function withRetry(fn, {
3194
- maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,
3195
- baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,
3196
- maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,
3197
- shouldRetry = defaultShouldRetry
3198
- } = {}) {
3199
- let lastError;
3200
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
3201
- try {
3202
- return await fn();
3203
- } catch (error) {
3204
- lastError = error;
3205
- const retryError = error instanceof Error ? error : new Error(String(error));
3206
- const isLastAttempt = attempt === maxRetries;
3207
- if (isLastAttempt || !shouldRetry(retryError, attempt + 1)) {
3208
- throw error;
3209
- }
3210
- const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);
3211
- console.warn(
3212
- `Attempt ${attempt + 1} failed: ${retryError.message}. Retrying in ${Math.round(delay)}ms...`
3213
- );
3214
- await new Promise((resolve) => setTimeout(resolve, delay));
3215
- }
3216
- }
3217
- throw lastError ?? new Error("Retry failed with unknown error");
3218
- }
3219
-
3220
3460
  // src/workflows/ask-questions.ts
3221
3461
  var ASK_QUESTIONS_MAX_QUESTION_LENGTH = 600;
3222
3462
  var ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH = 150;
@@ -3604,8 +3844,9 @@ async function analyzeQuestions({
3604
3844
  normalizedQuestions,
3605
3845
  maxFreeFormAnswerLength
3606
3846
  );
3607
- const response = await withContentPolicyErrorHandling(() => generateText({
3847
+ const response = await withContentPolicyAwareRetry(() => generateText({
3608
3848
  model,
3849
+ maxRetries: 0,
3609
3850
  output: Output.object({ schema: responseSchema }),
3610
3851
  experimental_telemetry: { isEnabled: true },
3611
3852
  messages: [
@@ -3804,18 +4045,16 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
3804
4045
  credentials
3805
4046
  });
3806
4047
  } else {
3807
- analysisResponse = await withRetry(
3808
- () => analyzeQuestions({
3809
- provider: modelConfig.provider,
3810
- modelId: modelConfig.modelId,
3811
- userPrompt,
3812
- systemPrompt,
3813
- normalizedQuestions,
3814
- maxFreeFormAnswerLength,
3815
- imageDataUrl: storyboardUrl,
3816
- credentials
3817
- })
3818
- );
4048
+ analysisResponse = await analyzeQuestions({
4049
+ provider: modelConfig.provider,
4050
+ modelId: modelConfig.modelId,
4051
+ userPrompt,
4052
+ systemPrompt,
4053
+ normalizedQuestions,
4054
+ maxFreeFormAnswerLength,
4055
+ imageDataUrl: storyboardUrl,
4056
+ credentials
4057
+ });
3819
4058
  }
3820
4059
  }
3821
4060
  } catch (error) {
@@ -4017,8 +4256,9 @@ async function analyzeStoryboard({
4017
4256
  }) {
4018
4257
  "use step";
4019
4258
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
4020
- const response = await withContentPolicyErrorHandling(() => generateText2({
4259
+ const response = await withContentPolicyAwareRetry(() => generateText2({
4021
4260
  model,
4261
+ maxRetries: 0,
4022
4262
  output: Output2.object({ schema: burnedInCaptionsSchema }),
4023
4263
  experimental_telemetry: { isEnabled: true },
4024
4264
  messages: [
@@ -4108,14 +4348,14 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
4108
4348
  credentials
4109
4349
  });
4110
4350
  } else {
4111
- analysisResponse = await withRetry(() => analyzeStoryboard({
4351
+ analysisResponse = await analyzeStoryboard({
4112
4352
  imageDataUrl: imageUrl,
4113
4353
  provider: modelConfig.provider,
4114
4354
  modelId: modelConfig.modelId,
4115
4355
  userPrompt,
4116
4356
  systemPrompt: SYSTEM_PROMPT2,
4117
4357
  credentials
4118
- }));
4358
+ });
4119
4359
  }
4120
4360
  collectedUsage.push(analysisResponse.usage);
4121
4361
  if (!analysisResponse.result) {
@@ -4166,24 +4406,21 @@ async function generateChaptersWithAI({
4166
4406
  }) {
4167
4407
  "use step";
4168
4408
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
4169
- const response = await withRetry(
4170
- () => withContentPolicyErrorHandling(
4171
- () => generateText3({
4172
- model,
4173
- output: Output3.object({ schema: chaptersSchema }),
4174
- messages: [
4175
- {
4176
- role: "system",
4177
- content: systemPrompt
4178
- },
4179
- {
4180
- role: "user",
4181
- content: userPrompt
4182
- }
4183
- ]
4184
- })
4185
- )
4186
- );
4409
+ const response = await withContentPolicyAwareRetry(() => generateText3({
4410
+ model,
4411
+ maxRetries: 0,
4412
+ output: Output3.object({ schema: chaptersSchema }),
4413
+ messages: [
4414
+ {
4415
+ role: "system",
4416
+ content: systemPrompt
4417
+ },
4418
+ {
4419
+ role: "user",
4420
+ content: userPrompt
4421
+ }
4422
+ ]
4423
+ }));
4187
4424
  const output = getGeneratedOutputWithContentPolicyHandling(response);
4188
4425
  const unexpectedRootKeys = detectUnexpectedKeysFromRawText(
4189
4426
  response.text,
@@ -4712,8 +4949,9 @@ async function identifyProfanityWithAI({
4712
4949
  tag: "transcript",
4713
4950
  content: plainText
4714
4951
  });
4715
- const response = await withContentPolicyErrorHandling(() => generateText4({
4952
+ const response = await withContentPolicyAwareRetry(() => generateText4({
4716
4953
  model,
4954
+ maxRetries: 0,
4717
4955
  output: Output4.object({ schema: profanityDetectionSchema }),
4718
4956
  messages: [
4719
4957
  {
@@ -4998,9 +5236,10 @@ async function generateSingleChunkEmbedding({
4998
5236
  }) {
4999
5237
  "use step";
5000
5238
  const model = await createEmbeddingModelFromConfig(provider, modelId, credentials);
5001
- const response = await withRetry(
5239
+ const response = await withContentPolicyAwareRetry(
5002
5240
  () => embed({
5003
5241
  model,
5242
+ maxRetries: 0,
5004
5243
  value: chunk.text
5005
5244
  })
5006
5245
  );
@@ -5419,28 +5658,25 @@ async function fetchEngagementData(assetId, options) {
5419
5658
  async function generateInsightsWithAI(provider, modelId, systemPrompt, userPrompt, imageUrls, credentials) {
5420
5659
  "use step";
5421
5660
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
5422
- const response = await withRetry(
5423
- () => withContentPolicyErrorHandling(
5424
- () => generateText5({
5425
- model,
5426
- output: Output5.object({ schema: engagementInsightsSchema }),
5427
- experimental_telemetry: { isEnabled: true },
5428
- messages: [
5429
- {
5430
- role: "system",
5431
- content: systemPrompt
5432
- },
5433
- {
5434
- role: "user",
5435
- content: [
5436
- { type: "text", text: userPrompt },
5437
- ...imageUrls.map((url) => ({ type: "image", image: url }))
5438
- ]
5439
- }
5661
+ const response = await withContentPolicyAwareRetry(() => generateText5({
5662
+ model,
5663
+ maxRetries: 0,
5664
+ output: Output5.object({ schema: engagementInsightsSchema }),
5665
+ experimental_telemetry: { isEnabled: true },
5666
+ messages: [
5667
+ {
5668
+ role: "system",
5669
+ content: systemPrompt
5670
+ },
5671
+ {
5672
+ role: "user",
5673
+ content: [
5674
+ { type: "text", text: userPrompt },
5675
+ ...imageUrls.map((url) => ({ type: "image", image: url }))
5440
5676
  ]
5441
- })
5442
- )
5443
- );
5677
+ }
5678
+ ]
5679
+ }));
5444
5680
  const output = getGeneratedOutputWithContentPolicyHandling(response);
5445
5681
  if (!output) {
5446
5682
  throw new Error("AI returned empty or unparseable response");
@@ -6198,8 +6434,8 @@ async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options)
6198
6434
  "use step";
6199
6435
  const { width, shouldSign, credentials } = options;
6200
6436
  const baseUrl = getMuxThumbnailBaseUrl(playbackId);
6201
- const urlPromises = timestampsMs.map(async (tsMs) => {
6202
- const time = toThumbnailTimeSeconds(tsMs);
6437
+ const times = [...new Set(timestampsMs.map(toThumbnailTimeSeconds))];
6438
+ const urlPromises = times.map(async (time) => {
6203
6439
  const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
6204
6440
  return { url, time };
6205
6441
  });
@@ -6685,8 +6921,9 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
6685
6921
  "use step";
6686
6922
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
6687
6923
  const schema = buildSummarySchema(descriptionLength);
6688
- const response = await withContentPolicyErrorHandling(() => generateText6({
6924
+ const response = await withContentPolicyAwareRetry(() => generateText6({
6689
6925
  model,
6926
+ maxRetries: 0,
6690
6927
  output: Output6.object({
6691
6928
  name: "summary_metadata",
6692
6929
  description: "Structured summary with title, description, and keywords.",
@@ -6732,8 +6969,9 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
6732
6969
  "use step";
6733
6970
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
6734
6971
  const schema = buildSummarySchema(descriptionLength);
6735
- const response = await withContentPolicyErrorHandling(() => generateText6({
6972
+ const response = await withContentPolicyAwareRetry(() => generateText6({
6736
6973
  model,
6974
+ maxRetries: 0,
6737
6975
  output: Output6.object({
6738
6976
  name: "summary_metadata",
6739
6977
  description: "Structured summary with title, description, and keywords.",
@@ -6916,7 +7154,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
6916
7154
  workflowCredentials
6917
7155
  );
6918
7156
  } else {
6919
- analysisResponse = await withRetry(() => analyzeStoryboard2(
7157
+ analysisResponse = await analyzeStoryboard2(
6920
7158
  storyboardUrl,
6921
7159
  modelConfig.provider,
6922
7160
  modelConfig.modelId,
@@ -6924,7 +7162,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
6924
7162
  systemPrompt,
6925
7163
  effectiveDescriptionLength,
6926
7164
  workflowCredentials
6927
- ));
7165
+ );
6928
7166
  }
6929
7167
  }
6930
7168
  } catch (error) {
@@ -7430,7 +7668,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7430
7668
 
7431
7669
  // src/workflows/translate-captions.ts
7432
7670
  import {
7433
- APICallError as APICallError2,
7671
+ APICallError as APICallError3,
7434
7672
  generateText as generateText7,
7435
7673
  NoObjectGeneratedError as NoObjectGeneratedError2,
7436
7674
  Output as Output7,
@@ -7509,7 +7747,7 @@ function isProviderServiceError(error) {
7509
7747
  if (RetryError2.isInstance(error)) {
7510
7748
  return isProviderServiceError(error.lastError);
7511
7749
  }
7512
- if (APICallError2.isInstance(error)) {
7750
+ if (APICallError3.isInstance(error)) {
7513
7751
  return true;
7514
7752
  }
7515
7753
  if (error instanceof Error && "cause" in error) {
@@ -7682,8 +7920,9 @@ async function translateVttWithAI({
7682
7920
  "use step";
7683
7921
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
7684
7922
  const sanitisedVttContent = stripVttMetadataBlocks(vttContent);
7685
- const response = await withContentPolicyErrorHandling(() => generateText7({
7923
+ const response = await withContentPolicyAwareRetry(() => generateText7({
7686
7924
  model,
7925
+ maxRetries: 0,
7687
7926
  output: Output7.object({ schema: translationSchema }),
7688
7927
  messages: [
7689
7928
  {
@@ -7748,8 +7987,9 @@ async function translateCueChunkWithAI({
7748
7987
  endTime: cue.endTime,
7749
7988
  text: cue.text
7750
7989
  }));
7751
- const response = await withContentPolicyErrorHandling(() => generateText7({
7990
+ const response = await withContentPolicyAwareRetry(() => generateText7({
7752
7991
  model,
7992
+ maxRetries: 0,
7753
7993
  output: Output7.object({ schema }),
7754
7994
  messages: [
7755
7995
  {