@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/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
|
-
var version = "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
|
|
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 (
|
|
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 ||
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
3246
|
+
if (!APICallError2.isInstance(error) || !error.responseBody) {
|
|
2956
3247
|
return void 0;
|
|
2957
3248
|
}
|
|
2958
3249
|
try {
|
|
@@ -3166,47 +3457,6 @@ function createLanguageSection(languageName) {
|
|
|
3166
3457
|
};
|
|
3167
3458
|
}
|
|
3168
3459
|
|
|
3169
|
-
// src/lib/retry.ts
|
|
3170
|
-
var DEFAULT_RETRY_OPTIONS = {
|
|
3171
|
-
maxRetries: 3,
|
|
3172
|
-
baseDelay: 2e3,
|
|
3173
|
-
maxDelay: 1e4
|
|
3174
|
-
};
|
|
3175
|
-
function defaultShouldRetry(error, _attempt) {
|
|
3176
|
-
return Boolean(error.message && error.message.includes("Timeout while downloading"));
|
|
3177
|
-
}
|
|
3178
|
-
function calculateDelay(attempt, baseDelay, maxDelay) {
|
|
3179
|
-
const exponentialDelay = baseDelay * 2 ** (attempt - 1);
|
|
3180
|
-
const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);
|
|
3181
|
-
return Math.min(delayWithJitter, maxDelay);
|
|
3182
|
-
}
|
|
3183
|
-
async function withRetry(fn, {
|
|
3184
|
-
maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,
|
|
3185
|
-
baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,
|
|
3186
|
-
maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,
|
|
3187
|
-
shouldRetry = defaultShouldRetry
|
|
3188
|
-
} = {}) {
|
|
3189
|
-
let lastError;
|
|
3190
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3191
|
-
try {
|
|
3192
|
-
return await fn();
|
|
3193
|
-
} catch (error) {
|
|
3194
|
-
lastError = error;
|
|
3195
|
-
const retryError = error instanceof Error ? error : new Error(String(error));
|
|
3196
|
-
const isLastAttempt = attempt === maxRetries;
|
|
3197
|
-
if (isLastAttempt || !shouldRetry(retryError, attempt + 1)) {
|
|
3198
|
-
throw error;
|
|
3199
|
-
}
|
|
3200
|
-
const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);
|
|
3201
|
-
console.warn(
|
|
3202
|
-
`Attempt ${attempt + 1} failed: ${retryError.message}. Retrying in ${Math.round(delay)}ms...`
|
|
3203
|
-
);
|
|
3204
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
throw lastError ?? new Error("Retry failed with unknown error");
|
|
3208
|
-
}
|
|
3209
|
-
|
|
3210
3460
|
// src/workflows/ask-questions.ts
|
|
3211
3461
|
var ASK_QUESTIONS_MAX_QUESTION_LENGTH = 600;
|
|
3212
3462
|
var ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH = 150;
|
|
@@ -3594,8 +3844,9 @@ async function analyzeQuestions({
|
|
|
3594
3844
|
normalizedQuestions,
|
|
3595
3845
|
maxFreeFormAnswerLength
|
|
3596
3846
|
);
|
|
3597
|
-
const response = await
|
|
3847
|
+
const response = await withContentPolicyAwareRetry(() => generateText({
|
|
3598
3848
|
model,
|
|
3849
|
+
maxRetries: 0,
|
|
3599
3850
|
output: Output.object({ schema: responseSchema }),
|
|
3600
3851
|
experimental_telemetry: { isEnabled: true },
|
|
3601
3852
|
messages: [
|
|
@@ -3794,18 +4045,16 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
3794
4045
|
credentials
|
|
3795
4046
|
});
|
|
3796
4047
|
} else {
|
|
3797
|
-
analysisResponse = await
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
})
|
|
3808
|
-
);
|
|
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
|
+
});
|
|
3809
4058
|
}
|
|
3810
4059
|
}
|
|
3811
4060
|
} catch (error) {
|
|
@@ -4007,8 +4256,9 @@ async function analyzeStoryboard({
|
|
|
4007
4256
|
}) {
|
|
4008
4257
|
"use step";
|
|
4009
4258
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
4010
|
-
const response = await
|
|
4259
|
+
const response = await withContentPolicyAwareRetry(() => generateText2({
|
|
4011
4260
|
model,
|
|
4261
|
+
maxRetries: 0,
|
|
4012
4262
|
output: Output2.object({ schema: burnedInCaptionsSchema }),
|
|
4013
4263
|
experimental_telemetry: { isEnabled: true },
|
|
4014
4264
|
messages: [
|
|
@@ -4156,24 +4406,21 @@ async function generateChaptersWithAI({
|
|
|
4156
4406
|
}) {
|
|
4157
4407
|
"use step";
|
|
4158
4408
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
4159
|
-
const response = await
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
})
|
|
4175
|
-
)
|
|
4176
|
-
);
|
|
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
|
+
}));
|
|
4177
4424
|
const output = getGeneratedOutputWithContentPolicyHandling(response);
|
|
4178
4425
|
const unexpectedRootKeys = detectUnexpectedKeysFromRawText(
|
|
4179
4426
|
response.text,
|
|
@@ -4702,8 +4949,9 @@ async function identifyProfanityWithAI({
|
|
|
4702
4949
|
tag: "transcript",
|
|
4703
4950
|
content: plainText
|
|
4704
4951
|
});
|
|
4705
|
-
const response = await
|
|
4952
|
+
const response = await withContentPolicyAwareRetry(() => generateText4({
|
|
4706
4953
|
model,
|
|
4954
|
+
maxRetries: 0,
|
|
4707
4955
|
output: Output4.object({ schema: profanityDetectionSchema }),
|
|
4708
4956
|
messages: [
|
|
4709
4957
|
{
|
|
@@ -4988,9 +5236,10 @@ async function generateSingleChunkEmbedding({
|
|
|
4988
5236
|
}) {
|
|
4989
5237
|
"use step";
|
|
4990
5238
|
const model = await createEmbeddingModelFromConfig(provider, modelId, credentials);
|
|
4991
|
-
const response = await
|
|
5239
|
+
const response = await withContentPolicyAwareRetry(
|
|
4992
5240
|
() => embed({
|
|
4993
5241
|
model,
|
|
5242
|
+
maxRetries: 0,
|
|
4994
5243
|
value: chunk.text
|
|
4995
5244
|
})
|
|
4996
5245
|
);
|
|
@@ -5409,28 +5658,25 @@ async function fetchEngagementData(assetId, options) {
|
|
|
5409
5658
|
async function generateInsightsWithAI(provider, modelId, systemPrompt, userPrompt, imageUrls, credentials) {
|
|
5410
5659
|
"use step";
|
|
5411
5660
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
5412
|
-
const response = await
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
...imageUrls.map((url) => ({ type: "image", image: url }))
|
|
5428
|
-
]
|
|
5429
|
-
}
|
|
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 }))
|
|
5430
5676
|
]
|
|
5431
|
-
}
|
|
5432
|
-
|
|
5433
|
-
);
|
|
5677
|
+
}
|
|
5678
|
+
]
|
|
5679
|
+
}));
|
|
5434
5680
|
const output = getGeneratedOutputWithContentPolicyHandling(response);
|
|
5435
5681
|
if (!output) {
|
|
5436
5682
|
throw new Error("AI returned empty or unparseable response");
|
|
@@ -6188,8 +6434,8 @@ async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options)
|
|
|
6188
6434
|
"use step";
|
|
6189
6435
|
const { width, shouldSign, credentials } = options;
|
|
6190
6436
|
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
6191
|
-
const
|
|
6192
|
-
|
|
6437
|
+
const times = [...new Set(timestampsMs.map(toThumbnailTimeSeconds))];
|
|
6438
|
+
const urlPromises = times.map(async (time) => {
|
|
6193
6439
|
const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
|
|
6194
6440
|
return { url, time };
|
|
6195
6441
|
});
|
|
@@ -6675,8 +6921,9 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
|
|
|
6675
6921
|
"use step";
|
|
6676
6922
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
6677
6923
|
const schema = buildSummarySchema(descriptionLength);
|
|
6678
|
-
const response = await
|
|
6924
|
+
const response = await withContentPolicyAwareRetry(() => generateText6({
|
|
6679
6925
|
model,
|
|
6926
|
+
maxRetries: 0,
|
|
6680
6927
|
output: Output6.object({
|
|
6681
6928
|
name: "summary_metadata",
|
|
6682
6929
|
description: "Structured summary with title, description, and keywords.",
|
|
@@ -6722,8 +6969,9 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
|
|
|
6722
6969
|
"use step";
|
|
6723
6970
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
6724
6971
|
const schema = buildSummarySchema(descriptionLength);
|
|
6725
|
-
const response = await
|
|
6972
|
+
const response = await withContentPolicyAwareRetry(() => generateText6({
|
|
6726
6973
|
model,
|
|
6974
|
+
maxRetries: 0,
|
|
6727
6975
|
output: Output6.object({
|
|
6728
6976
|
name: "summary_metadata",
|
|
6729
6977
|
description: "Structured summary with title, description, and keywords.",
|
|
@@ -6906,7 +7154,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6906
7154
|
workflowCredentials
|
|
6907
7155
|
);
|
|
6908
7156
|
} else {
|
|
6909
|
-
analysisResponse = await
|
|
7157
|
+
analysisResponse = await analyzeStoryboard2(
|
|
6910
7158
|
storyboardUrl,
|
|
6911
7159
|
modelConfig.provider,
|
|
6912
7160
|
modelConfig.modelId,
|
|
@@ -6914,7 +7162,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6914
7162
|
systemPrompt,
|
|
6915
7163
|
effectiveDescriptionLength,
|
|
6916
7164
|
workflowCredentials
|
|
6917
|
-
)
|
|
7165
|
+
);
|
|
6918
7166
|
}
|
|
6919
7167
|
}
|
|
6920
7168
|
} catch (error) {
|
|
@@ -7158,6 +7406,59 @@ async function downloadAndUploadDubbedAudio({
|
|
|
7158
7406
|
console.warn(`\u{1F517} Generated presigned URL (expires in ${expiryHours} hour${expiryHours === 1 ? "" : "s"})`);
|
|
7159
7407
|
return presignedUrl;
|
|
7160
7408
|
}
|
|
7409
|
+
async function downloadAndUploadDubTranscript({
|
|
7410
|
+
dubbingId,
|
|
7411
|
+
languageCode,
|
|
7412
|
+
assetId,
|
|
7413
|
+
toLanguageCode,
|
|
7414
|
+
s3Endpoint,
|
|
7415
|
+
s3Region,
|
|
7416
|
+
s3Bucket,
|
|
7417
|
+
storageAdapter,
|
|
7418
|
+
s3SignedUrlExpirySeconds,
|
|
7419
|
+
credentials
|
|
7420
|
+
}) {
|
|
7421
|
+
"use step";
|
|
7422
|
+
const elevenLabsApiKey = await getApiKeyFromEnv("elevenlabs", credentials);
|
|
7423
|
+
const transcriptUrl = `https://api.elevenlabs.io/v1/dubbing/${dubbingId}/transcripts/${languageCode}/format/webvtt`;
|
|
7424
|
+
const transcriptResponse = await fetch(transcriptUrl, {
|
|
7425
|
+
headers: {
|
|
7426
|
+
"xi-api-key": elevenLabsApiKey
|
|
7427
|
+
}
|
|
7428
|
+
});
|
|
7429
|
+
if (!transcriptResponse.ok) {
|
|
7430
|
+
throw new Error(`Failed to fetch dub transcript: ${transcriptResponse.statusText}`);
|
|
7431
|
+
}
|
|
7432
|
+
const transcriptBody = await transcriptResponse.json();
|
|
7433
|
+
const transcriptVtt = transcriptBody.webvtt;
|
|
7434
|
+
if (!transcriptVtt) {
|
|
7435
|
+
throw new Error("Dub transcript response did not include webvtt content");
|
|
7436
|
+
}
|
|
7437
|
+
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
7438
|
+
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
7439
|
+
const vttKey = `audio-translations/${assetId}/auto-to-${toLanguageCode}-${Date.now()}.vtt`;
|
|
7440
|
+
await putObjectWithStorageAdapter({
|
|
7441
|
+
accessKeyId: s3AccessKeyId,
|
|
7442
|
+
secretAccessKey: s3SecretAccessKey,
|
|
7443
|
+
endpoint: s3Endpoint,
|
|
7444
|
+
region: s3Region,
|
|
7445
|
+
bucket: s3Bucket,
|
|
7446
|
+
key: vttKey,
|
|
7447
|
+
body: transcriptVtt,
|
|
7448
|
+
contentType: "text/vtt"
|
|
7449
|
+
}, storageAdapter);
|
|
7450
|
+
const presignedUrl = await createPresignedGetUrlWithStorageAdapter({
|
|
7451
|
+
accessKeyId: s3AccessKeyId,
|
|
7452
|
+
secretAccessKey: s3SecretAccessKey,
|
|
7453
|
+
endpoint: s3Endpoint,
|
|
7454
|
+
region: s3Region,
|
|
7455
|
+
bucket: s3Bucket,
|
|
7456
|
+
key: vttKey,
|
|
7457
|
+
expiresInSeconds: s3SignedUrlExpirySeconds ?? 86400
|
|
7458
|
+
}, storageAdapter);
|
|
7459
|
+
console.warn(`\u2705 Dub transcript uploaded successfully to: ${vttKey}`);
|
|
7460
|
+
return presignedUrl;
|
|
7461
|
+
}
|
|
7161
7462
|
async function createAudioTrackOnMux(assetId, languageCode, presignedUrl, credentials) {
|
|
7162
7463
|
"use step";
|
|
7163
7464
|
const muxClient = await resolveMuxClient(credentials);
|
|
@@ -7184,6 +7485,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7184
7485
|
// 0 = auto-detect
|
|
7185
7486
|
uploadToS3: uploadToS3Option,
|
|
7186
7487
|
uploadToMux: uploadToMuxOption,
|
|
7488
|
+
uploadCaptionsToMux = false,
|
|
7187
7489
|
storageAdapter,
|
|
7188
7490
|
credentials: providedCredentials
|
|
7189
7491
|
} = options;
|
|
@@ -7193,7 +7495,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7193
7495
|
const credentials = providedCredentials;
|
|
7194
7496
|
const effectiveStorageAdapter = storageAdapter;
|
|
7195
7497
|
const uploadToMux = uploadToMuxOption !== false;
|
|
7196
|
-
const uploadToS3 = uploadToS3Option || uploadToMux;
|
|
7498
|
+
const uploadToS3 = uploadToS3Option || uploadToMux || uploadCaptionsToMux;
|
|
7197
7499
|
const s3Endpoint = options.s3Endpoint ?? env_default.S3_ENDPOINT;
|
|
7198
7500
|
const s3Region = options.s3Region ?? env_default.S3_REGION ?? "auto";
|
|
7199
7501
|
const s3Bucket = options.s3Bucket ?? env_default.S3_BUCKET;
|
|
@@ -7274,10 +7576,12 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7274
7576
|
console.warn("\u2705 Dubbing completed successfully!");
|
|
7275
7577
|
let presignedUrl;
|
|
7276
7578
|
let uploadedTrackId;
|
|
7579
|
+
let captionsPresignedUrl;
|
|
7580
|
+
let captionsTrackId;
|
|
7277
7581
|
if (uploadToS3) {
|
|
7278
7582
|
console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs and staging to S3...");
|
|
7583
|
+
const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
|
|
7279
7584
|
try {
|
|
7280
|
-
const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
|
|
7281
7585
|
presignedUrl = await downloadAndUploadDubbedAudio({
|
|
7282
7586
|
dubbingId,
|
|
7283
7587
|
languageCode: downloadLangCode,
|
|
@@ -7309,6 +7613,40 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7309
7613
|
console.warn(presignedUrl);
|
|
7310
7614
|
}
|
|
7311
7615
|
}
|
|
7616
|
+
try {
|
|
7617
|
+
console.warn("\u{1F4E5} Downloading dub transcript from ElevenLabs and staging to S3...");
|
|
7618
|
+
captionsPresignedUrl = await downloadAndUploadDubTranscript({
|
|
7619
|
+
dubbingId,
|
|
7620
|
+
languageCode: downloadLangCode,
|
|
7621
|
+
assetId,
|
|
7622
|
+
toLanguageCode,
|
|
7623
|
+
s3Endpoint,
|
|
7624
|
+
s3Region,
|
|
7625
|
+
s3Bucket,
|
|
7626
|
+
storageAdapter: effectiveStorageAdapter,
|
|
7627
|
+
s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
|
|
7628
|
+
credentials
|
|
7629
|
+
});
|
|
7630
|
+
if (uploadCaptionsToMux) {
|
|
7631
|
+
console.warn("\u{1F4F9} Adding dubbed captions track to Mux asset...");
|
|
7632
|
+
const muxLangCode = toISO639_1(toLanguageCode);
|
|
7633
|
+
const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
|
|
7634
|
+
captionsTrackId = await createTextTrackOnMux(
|
|
7635
|
+
assetId,
|
|
7636
|
+
muxLangCode,
|
|
7637
|
+
`${languageName} (auto-dubbed)`,
|
|
7638
|
+
captionsPresignedUrl,
|
|
7639
|
+
credentials
|
|
7640
|
+
);
|
|
7641
|
+
console.warn(`\u2705 Captions track added to Mux asset with ID: ${captionsTrackId}`);
|
|
7642
|
+
}
|
|
7643
|
+
} catch (error) {
|
|
7644
|
+
console.warn(`\u26A0\uFE0F Failed to attach dubbed captions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
7645
|
+
if (captionsPresignedUrl) {
|
|
7646
|
+
console.warn("\u{1F517} You can manually add the captions track using this presigned URL:");
|
|
7647
|
+
console.warn(captionsPresignedUrl);
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7312
7650
|
}
|
|
7313
7651
|
const targetLanguage = getLanguageCodePair(toLanguageCode);
|
|
7314
7652
|
return {
|
|
@@ -7318,6 +7656,8 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7318
7656
|
dubbingId,
|
|
7319
7657
|
uploadedTrackId,
|
|
7320
7658
|
presignedUrl,
|
|
7659
|
+
captionsTrackId,
|
|
7660
|
+
captionsPresignedUrl,
|
|
7321
7661
|
usage: {
|
|
7322
7662
|
metadata: {
|
|
7323
7663
|
assetDurationSeconds
|
|
@@ -7328,7 +7668,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7328
7668
|
|
|
7329
7669
|
// src/workflows/translate-captions.ts
|
|
7330
7670
|
import {
|
|
7331
|
-
APICallError as
|
|
7671
|
+
APICallError as APICallError3,
|
|
7332
7672
|
generateText as generateText7,
|
|
7333
7673
|
NoObjectGeneratedError as NoObjectGeneratedError2,
|
|
7334
7674
|
Output as Output7,
|
|
@@ -7407,7 +7747,7 @@ function isProviderServiceError(error) {
|
|
|
7407
7747
|
if (RetryError2.isInstance(error)) {
|
|
7408
7748
|
return isProviderServiceError(error.lastError);
|
|
7409
7749
|
}
|
|
7410
|
-
if (
|
|
7750
|
+
if (APICallError3.isInstance(error)) {
|
|
7411
7751
|
return true;
|
|
7412
7752
|
}
|
|
7413
7753
|
if (error instanceof Error && "cause" in error) {
|
|
@@ -7580,8 +7920,9 @@ async function translateVttWithAI({
|
|
|
7580
7920
|
"use step";
|
|
7581
7921
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
7582
7922
|
const sanitisedVttContent = stripVttMetadataBlocks(vttContent);
|
|
7583
|
-
const response = await
|
|
7923
|
+
const response = await withContentPolicyAwareRetry(() => generateText7({
|
|
7584
7924
|
model,
|
|
7925
|
+
maxRetries: 0,
|
|
7585
7926
|
output: Output7.object({ schema: translationSchema }),
|
|
7586
7927
|
messages: [
|
|
7587
7928
|
{
|
|
@@ -7646,8 +7987,9 @@ async function translateCueChunkWithAI({
|
|
|
7646
7987
|
endTime: cue.endTime,
|
|
7647
7988
|
text: cue.text
|
|
7648
7989
|
}));
|
|
7649
|
-
const response = await
|
|
7990
|
+
const response = await withContentPolicyAwareRetry(() => generateText7({
|
|
7650
7991
|
model,
|
|
7992
|
+
maxRetries: 0,
|
|
7651
7993
|
output: Output7.object({ schema }),
|
|
7652
7994
|
messages: [
|
|
7653
7995
|
{
|