@mux/ai 0.4.0 → 0.4.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/workflows/burned-in-captions.ts","../../src/env.ts","../../src/lib/providers.ts","../../src/lib/client-factory.ts","../../src/lib/image-download.ts","../../src/lib/mux-assets.ts","../../src/lib/prompt-builder.ts","../../src/lib/url-signing.ts","../../src/primitives/storyboards.ts","../../src/workflows/chapters.ts","../../src/lib/retry.ts","../../src/primitives/transcripts.ts","../../src/workflows/embeddings.ts","../../src/primitives/text-chunking.ts","../../src/primitives/thumbnails.ts","../../src/workflows/moderation.ts","../../src/workflows/summarization.ts","../../src/workflows/translate-audio.ts","../../src/lib/language-codes.ts","../../src/workflows/translate-captions.ts"],"sourcesContent":["import { generateObject } from \"ai\";\nimport dedent from \"dedent\";\nimport { z } from \"zod\";\n\nimport { createWorkflowConfig } from \"../lib/client-factory\";\nimport type { ImageDownloadOptions } from \"../lib/image-download\";\nimport { downloadImageAsBase64 } from \"../lib/image-download\";\nimport { getPlaybackIdForAsset } from \"../lib/mux-assets\";\nimport type { PromptOverrides } from \"../lib/prompt-builder\";\nimport { createPromptBuilder } from \"../lib/prompt-builder\";\nimport { createLanguageModelFromConfig } from \"../lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"../lib/providers\";\nimport { getMuxSigningContextFromEnv } from \"../lib/url-signing\";\nimport { getStoryboardUrl } from \"../primitives/storyboards\";\nimport type { ImageSubmissionMode, MuxAIOptions, TokenUsage } from \"../types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Structured payload returned from `hasBurnedInCaptions`. */\nexport interface BurnedInCaptionsResult {\n assetId: string;\n hasBurnedInCaptions: boolean;\n confidence: number;\n detectedLanguage: string | null;\n storyboardUrl: string;\n /** Token usage from the AI provider (for efficiency/cost analysis). */\n usage?: TokenUsage;\n}\n\n/**\n * Sections of the burned-in captions user prompt that can be overridden.\n * Use these to customize the AI's behavior for your specific use case.\n */\nexport type BurnedInCaptionsPromptSections =\n \"task\" |\n \"analysisSteps\" |\n \"positiveIndicators\" |\n \"negativeIndicators\";\n\n/**\n * Override specific sections of the burned-in captions prompt.\n * Each key corresponds to a section that can be customized.\n *\n * @example\n * ```typescript\n * const result = await hasBurnedInCaptions(assetId, {\n * promptOverrides: {\n * task: 'Detect any text overlays in the video frames.',\n * positiveIndicators: 'Classify as captions if text appears consistently.',\n * },\n * });\n * ```\n */\nexport type BurnedInCaptionsPromptOverrides = PromptOverrides<BurnedInCaptionsPromptSections>;\n\n/** Configuration accepted by `hasBurnedInCaptions`. */\nexport interface BurnedInCaptionsOptions extends MuxAIOptions {\n /** AI provider used for storyboard inspection (defaults to 'openai'). */\n provider?: SupportedProvider;\n /** Provider-specific model identifier. */\n model?: ModelIdByProvider[SupportedProvider];\n /** Transport used for storyboard submission (defaults to 'url'). */\n imageSubmissionMode?: ImageSubmissionMode;\n /** Download tuning used when `imageSubmissionMode` === 'base64'. */\n imageDownloadOptions?: ImageDownloadOptions;\n /**\n * Override specific sections of the user prompt.\n * Useful for customizing the AI's detection criteria for specific use cases.\n */\n promptOverrides?: BurnedInCaptionsPromptOverrides;\n}\n\n/** Schema used to validate burned-in captions analysis responses. */\nexport const burnedInCaptionsSchema = z.object({\n hasBurnedInCaptions: z.boolean(),\n confidence: z.number().min(0).max(1),\n detectedLanguage: z.string().nullable(),\n});\n\n/** Inferred shape returned from the burned-in captions schema. */\nexport type BurnedInCaptionsAnalysis = z.infer<typeof burnedInCaptionsSchema>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Prompts\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = dedent`\n <role>\n You are an expert at analyzing video frames to detect burned-in captions (also called open captions or hardcoded subtitles).\n These are text overlays that are permanently embedded in the video image, common on TikTok, Instagram Reels, and other social media platforms.\n </role>\n\n <critical_note>\n Burned-in captions must appear consistently across MOST frames in the storyboard.\n Text appearing in only 1-2 frames at the end is typically marketing copy, taglines, or end-cards - NOT burned-in captions.\n </critical_note>\n\n <confidence_scoring>\n Use this rubric to determine your confidence score (0.0-1.0):\n\n - Score 1.0: Definitive captions - text overlays visible in most frames, consistent positioning, content changes between frames indicating dialogue/narration, clear caption-style formatting\n - Score 0.7-0.9: Strong evidence - captions visible across multiple frames with consistent placement, but minor ambiguity (e.g., some frames unclear, atypical styling)\n - Score 0.4-0.6: Moderate evidence - text present in several frames but uncertain classification (e.g., could be captions or persistent on-screen graphics, ambiguous formatting)\n - Score 0.1-0.3: Weak evidence - minimal text detected, appears in only a few frames, likely marketing copy or end-cards rather than captions\n - Score 0.0: No captions - no text overlays detected, or text is clearly not captions (logos, watermarks, scene content, single end-card)\n </confidence_scoring>\n\n <context>\n You receive storyboard images containing multiple sequential frames extracted from a video.\n These frames are arranged in a grid and represent the visual progression of the content over time.\n Read frames left-to-right, top-to-bottom to understand the temporal sequence.\n </context>\n\n <capabilities>\n - Detect and analyze text overlays in video frames\n - Distinguish between captions and other text elements (marketing, logos, UI)\n - Identify language of detected caption text\n - Assess confidence in caption detection\n </capabilities>\n\n <constraints>\n - Only classify as burned-in captions when evidence is clear across multiple frames\n - Base decisions on observable visual evidence\n - Return structured data matching the requested schema\n </constraints>`;\n\n/**\n * Prompt builder for the burned-in captions user prompt.\n * Sections can be individually overridden via `promptOverrides` in BurnedInCaptionsOptions.\n */\nconst burnedInCaptionsPromptBuilder = createPromptBuilder<BurnedInCaptionsPromptSections>({\n template: {\n task: {\n tag: \"task\",\n content: dedent`\n Analyze the provided video storyboard to detect burned-in captions (hardcoded subtitles).\n Count frames with text vs no text, note position consistency and whether text changes across frames.\n Decide if captions exist, with confidence (0.0-1.0) and detected language if any.`,\n },\n analysisSteps: {\n tag: \"analysis_steps\",\n content: dedent`\n 1. COUNT how many frames contain text overlays vs. how many don't\n 2. Check if text appears in consistent positions across multiple frames\n 3. Verify text changes content between frames (indicating dialogue/narration)\n 4. Ensure text has caption-style formatting (contrasting colors, readable fonts)\n 5. If captions are detected, identify the language of the text`,\n },\n positiveIndicators: {\n tag: \"classify_as_captions\",\n content: dedent`\n ONLY classify as burned-in captions if:\n - Text appears in multiple frames (not just 1-2 end frames)\n - Text positioning is consistent across those frames\n - Content suggests dialogue, narration, or subtitles (not marketing)\n - Formatting looks like captions (not graphics/logos)`,\n },\n negativeIndicators: {\n tag: \"not_captions\",\n content: dedent`\n DO NOT classify as burned-in captions:\n - Marketing taglines appearing only in final 1-2 frames\n - Single words or phrases that don't change between frames\n - Graphics, logos, watermarks, or UI elements\n - Text that's part of the original scene content\n - End-cards with calls-to-action or brand messaging`,\n },\n },\n sectionOrder: [\"task\", \"analysisSteps\", \"positiveIndicators\", \"negativeIndicators\"],\n});\n\nfunction buildUserPrompt(promptOverrides?: BurnedInCaptionsPromptOverrides): string {\n return burnedInCaptionsPromptBuilder.build(promptOverrides);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\nconst DEFAULT_PROVIDER = \"openai\";\n\ninterface AnalysisResponse {\n result: BurnedInCaptionsAnalysis;\n usage: TokenUsage;\n}\n\nasync function fetchImageAsBase64(\n imageUrl: string,\n imageDownloadOptions?: ImageDownloadOptions,\n): Promise<string> {\n \"use step\";\n\n const downloadResult = await downloadImageAsBase64(imageUrl, imageDownloadOptions);\n return downloadResult.base64Data;\n}\n\nasync function analyzeStoryboard({\n imageDataUrl,\n provider,\n modelId,\n userPrompt,\n systemPrompt,\n}: {\n imageDataUrl: string;\n provider: SupportedProvider;\n modelId: string;\n userPrompt: string;\n systemPrompt: string;\n}): Promise<AnalysisResponse> {\n \"use step\";\n\n const model = createLanguageModelFromConfig(provider, modelId);\n\n const response = await generateObject({\n model,\n schema: burnedInCaptionsSchema,\n experimental_telemetry: { isEnabled: true },\n messages: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n {\n role: \"user\",\n content: [\n { type: \"text\", text: userPrompt },\n { type: \"image\", image: imageDataUrl },\n ],\n },\n ],\n });\n\n return {\n result: response.object,\n usage: {\n inputTokens: response.usage.inputTokens,\n outputTokens: response.usage.outputTokens,\n totalTokens: response.usage.totalTokens,\n reasoningTokens: response.usage.reasoningTokens,\n cachedInputTokens: response.usage.cachedInputTokens,\n },\n };\n}\n\nexport async function hasBurnedInCaptions(\n assetId: string,\n options: BurnedInCaptionsOptions = {},\n): Promise<BurnedInCaptionsResult> {\n \"use workflow\";\n const {\n provider = DEFAULT_PROVIDER,\n model,\n imageSubmissionMode = \"url\",\n imageDownloadOptions,\n promptOverrides,\n ...config\n } = options;\n\n // Build the user prompt with any overrides\n const userPrompt = buildUserPrompt(promptOverrides);\n\n const workflowConfig = await createWorkflowConfig(\n { ...config, model },\n provider as SupportedProvider,\n );\n const { playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n const imageUrl = await getStoryboardUrl(playbackId, 640, policy === \"signed\");\n\n let analysisResponse: AnalysisResponse;\n\n if (imageSubmissionMode === \"base64\") {\n const base64Data = await fetchImageAsBase64(imageUrl, imageDownloadOptions);\n analysisResponse = await analyzeStoryboard({\n imageDataUrl: base64Data,\n provider: workflowConfig.provider,\n modelId: workflowConfig.modelId,\n userPrompt,\n systemPrompt: SYSTEM_PROMPT,\n });\n } else {\n analysisResponse = await analyzeStoryboard({\n imageDataUrl: imageUrl,\n provider: workflowConfig.provider,\n modelId: workflowConfig.modelId,\n userPrompt,\n systemPrompt: SYSTEM_PROMPT,\n });\n }\n\n if (!analysisResponse.result) {\n throw new Error(\"No analysis result received from AI provider\");\n }\n\n return {\n assetId,\n hasBurnedInCaptions: analysisResponse.result.hasBurnedInCaptions ?? false,\n confidence: analysisResponse.result.confidence ?? 0,\n detectedLanguage: analysisResponse.result.detectedLanguage ?? null,\n storyboardUrl: imageUrl,\n usage: analysisResponse.usage,\n };\n}\n","/* eslint-disable node/no-process-env */\n\nimport { z } from \"zod\";\n\nimport \"dotenv/config\";\n\nfunction optionalString(description: string, message?: string) {\n return z.preprocess(\n value => typeof value === \"string\" && value.trim().length === 0 ? undefined : value,\n z.string().trim().min(1, message).optional(),\n ).describe(description);\n}\n\nfunction requiredString(description: string, message?: string) {\n return z.preprocess(\n value => typeof value === \"string\" ? value.trim().length > 0 ? value.trim() : undefined : value,\n z.string().trim().min(1, message),\n ).describe(description);\n}\n\nconst EnvSchema = z.object({\n NODE_ENV: z.string().default(\"development\").describe(\"Runtime environment.\"),\n\n MUX_TOKEN_ID: requiredString(\"Mux access token ID.\", \"Required to access Mux APIs\"),\n MUX_TOKEN_SECRET: requiredString(\"Mux access token secret.\", \"Required to access Mux APIs\"),\n\n MUX_SIGNING_KEY: optionalString(\"Mux signing key ID for signed playback URLs.\", \"Used to sign playback URLs\"),\n MUX_PRIVATE_KEY: optionalString(\"Mux signing private key for signed playback URLs.\", \"Used to sign playback URLs\"),\n\n OPENAI_API_KEY: optionalString(\"OpenAI API key for OpenAI-backed workflows.\", \"OpenAI API key\"),\n ANTHROPIC_API_KEY: optionalString(\"Anthropic API key for Claude-backed workflows.\", \"Anthropic API key\"),\n GOOGLE_GENERATIVE_AI_API_KEY: optionalString(\"Google Generative AI API key for Gemini-backed workflows.\", \"Google Generative AI API key\"),\n\n ELEVENLABS_API_KEY: optionalString(\"ElevenLabs API key for audio translation.\", \"ElevenLabs API key\"),\n HIVE_API_KEY: optionalString(\"Hive Visual Moderation API key.\", \"Hive API key\"),\n\n S3_ENDPOINT: optionalString(\"S3-compatible endpoint for uploads.\", \"S3 endpoint\"),\n S3_REGION: optionalString(\"S3 region (defaults to 'auto' when omitted).\"),\n S3_BUCKET: optionalString(\"Bucket used for caption and audio uploads.\", \"S3 bucket\"),\n S3_ACCESS_KEY_ID: optionalString(\"Access key ID for S3-compatible uploads.\", \"S3 access key id\"),\n S3_SECRET_ACCESS_KEY: optionalString(\"Secret access key for S3-compatible uploads.\", \"S3 secret access key\"),\n});\n\nexport type Env = z.infer<typeof EnvSchema>;\n\nfunction parseEnv(): Env {\n const parsedEnv = EnvSchema.safeParse(process.env);\n\n if (!parsedEnv.success) {\n console.error(\"❌ Invalid env:\");\n console.error(JSON.stringify(parsedEnv.error.flatten().fieldErrors, null, 2));\n process.exit(1);\n }\n\n return parsedEnv.data;\n}\n\nconst env: Env = parseEnv();\n\nexport function reloadEnv(): Env {\n const parsed = parseEnv();\n Object.assign(env, parsed);\n return env;\n}\n\nexport { env };\nexport default env;\n","import { createAnthropic } from \"@ai-sdk/anthropic\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\n\nimport env from \"../env.ts\";\nimport type { MuxAIOptions } from \"../types\";\n\nimport type { EmbeddingModel, LanguageModel } from \"ai\";\n\nexport type SupportedProvider = \"openai\" | \"anthropic\" | \"google\";\nexport type SupportedEmbeddingProvider = \"openai\" | \"google\";\n\n// Model ID unions inferred from ai-sdk provider call signatures\ntype OpenAIModelId = Parameters<ReturnType<typeof createOpenAI>[\"chat\"]>[0];\ntype AnthropicModelId = Parameters<ReturnType<typeof createAnthropic>[\"chat\"]>[0];\ntype GoogleModelId = Parameters<ReturnType<typeof createGoogleGenerativeAI>[\"chat\"]>[0];\n\ntype OpenAIEmbeddingModelId = Parameters<ReturnType<typeof createOpenAI>[\"embedding\"]>[0];\ntype GoogleEmbeddingModelId = Parameters<ReturnType<typeof createGoogleGenerativeAI>[\"textEmbeddingModel\"]>[0];\n\nexport interface ModelIdByProvider {\n openai: OpenAIModelId;\n anthropic: AnthropicModelId;\n google: GoogleModelId;\n}\n\nexport interface EmbeddingModelIdByProvider {\n openai: OpenAIEmbeddingModelId;\n google: GoogleEmbeddingModelId;\n}\n\nexport interface ModelRequestOptions<P extends SupportedProvider = SupportedProvider> extends MuxAIOptions {\n provider?: P;\n model?: ModelIdByProvider[P];\n}\n\nexport interface ResolvedModel<P extends SupportedProvider = SupportedProvider> {\n provider: P;\n modelId: ModelIdByProvider[P];\n model: LanguageModel;\n}\n\nexport const DEFAULT_LANGUAGE_MODELS: { [K in SupportedProvider]: ModelIdByProvider[K] } = {\n openai: \"gpt-5.1\",\n anthropic: \"claude-sonnet-4-5\",\n google: \"gemini-3-flash-preview\",\n};\n\nconst DEFAULT_EMBEDDING_MODELS: { [K in SupportedEmbeddingProvider]: EmbeddingModelIdByProvider[K] } = {\n openai: \"text-embedding-3-small\",\n google: \"gemini-embedding-001\",\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Model Pricing\n// ─────────────────────────────────────────────────────────────────────────────\n//\n// Pricing is in USD per million tokens. These values are used for cost estimation\n// in evaluations and should be periodically verified against official sources.\n//\n// Sources (as of December 2025):\n// - OpenAI: https://openai.com/api/pricing\n// - Anthropic: https://www.anthropic.com/pricing\n// - Google: https://ai.google.dev/pricing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Pricing structure for a language model.\n * All costs are in USD per million tokens.\n */\nexport interface ModelPricing {\n /** Cost per million input tokens (USD). */\n inputPerMillion: number;\n /** Cost per million output tokens (USD). */\n outputPerMillion: number;\n /** Cost per million cached input tokens (USD), if supported. */\n cachedInputPerMillion?: number;\n /** URL to the official pricing page for verification. */\n pricingUrl: string;\n}\n\n/**\n * Pricing data for the default language models.\n * Used for cost estimation in evaluations and expense tracking.\n *\n * @remarks\n * Prices are subject to change. Verify against official sources before production use.\n */\nexport const THIRD_PARTY_MODEL_PRICING: { [K in SupportedProvider]: ModelPricing } = {\n // OpenAI GPT-5.1\n // Reference: https://openai.com/api/pricing\n openai: {\n inputPerMillion: 1.25,\n outputPerMillion: 10.00,\n cachedInputPerMillion: 0.125,\n pricingUrl: \"https://openai.com/api/pricing\",\n },\n\n // Anthropic Claude Sonnet 4.5\n // Reference: https://www.anthropic.com/pricing\n anthropic: {\n inputPerMillion: 3.00,\n outputPerMillion: 15.00,\n cachedInputPerMillion: 0.30, // Prompt caching read cost (≤200K tokens)\n pricingUrl: \"https://www.anthropic.com/pricing\",\n },\n\n // Google Gemini 3 Flash Preview\n // Reference: https://ai.google.dev/pricing\n google: {\n inputPerMillion: 0.50,\n outputPerMillion: 3.00,\n cachedInputPerMillion: 0.05, // Context caching price\n pricingUrl: \"https://ai.google.dev/pricing\",\n },\n};\n\n/**\n * Calculates the estimated cost for a request based on token usage.\n *\n * @param provider - The AI provider used\n * @param inputTokens - Number of input tokens consumed\n * @param outputTokens - Number of output tokens generated\n * @param cachedInputTokens - Number of input tokens served from cache (optional)\n * @returns Estimated cost in USD\n *\n * @example\n * ```typescript\n * const cost = calculateCost('openai', 2000, 500);\n * console.log(`Estimated cost: $${cost.toFixed(6)}`);\n * ```\n */\nexport function calculateCost(\n provider: SupportedProvider,\n inputTokens: number,\n outputTokens: number,\n cachedInputTokens: number = 0,\n): number {\n const pricing = THIRD_PARTY_MODEL_PRICING[provider];\n\n // Adjust input tokens: cached tokens are charged at cached rate, rest at full rate\n const uncachedInputTokens = Math.max(0, inputTokens - cachedInputTokens);\n\n const inputCost = (uncachedInputTokens / 1_000_000) * pricing.inputPerMillion;\n const outputCost = (outputTokens / 1_000_000) * pricing.outputPerMillion;\n let cachedCost = 0;\n if (pricing.cachedInputPerMillion) {\n cachedCost = (cachedInputTokens / 1_000_000) * pricing.cachedInputPerMillion;\n }\n\n return inputCost + outputCost + cachedCost;\n}\n\nfunction requireEnv(value: string | undefined, name: string): string {\n if (!value) {\n throw new Error(`Missing ${name}. Set ${name} in your environment or pass it in options.`);\n }\n return value;\n}\n\n/**\n * Creates a language model instance from serializable config.\n * Use this in steps to instantiate models from config passed through workflow.\n * Fetches credentials internally from environment variables to avoid exposing them in step I/O.\n */\nexport function createLanguageModelFromConfig(\n provider: SupportedProvider,\n modelId: string,\n): LanguageModel {\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({ apiKey });\n return openai(modelId);\n }\n case \"anthropic\": {\n const apiKey = env.ANTHROPIC_API_KEY;\n requireEnv(apiKey, \"ANTHROPIC_API_KEY\");\n const anthropic = createAnthropic({ apiKey });\n return anthropic(modelId);\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({ apiKey });\n return google(modelId);\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported provider: ${exhaustiveCheck}`);\n }\n }\n}\n\n/**\n * Creates an embedding model instance from serializable config.\n * Use this in steps to instantiate embedding models from config passed through workflow.\n * Fetches credentials internally from environment variables to avoid exposing them in step I/O.\n */\nexport function createEmbeddingModelFromConfig(\n provider: SupportedEmbeddingProvider,\n modelId: string,\n): EmbeddingModel<string> {\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({ apiKey });\n return openai.embedding(modelId);\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({ apiKey });\n return google.textEmbeddingModel(modelId);\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported embedding provider: ${exhaustiveCheck}`);\n }\n }\n}\n\n/**\n * Resolves a language model from a suggested provider.\n */\nexport function resolveLanguageModel<P extends SupportedProvider = SupportedProvider>(\n options: ModelRequestOptions<P> = {},\n): ResolvedModel<P> {\n const provider = options.provider || (\"openai\" as P);\n const modelId = (options.model || DEFAULT_LANGUAGE_MODELS[provider]) as ModelIdByProvider[P];\n\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: openai(modelId),\n };\n }\n case \"anthropic\": {\n const apiKey = env.ANTHROPIC_API_KEY;\n requireEnv(apiKey, \"ANTHROPIC_API_KEY\");\n const anthropic = createAnthropic({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: anthropic(modelId),\n };\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: google(modelId),\n };\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported provider: ${exhaustiveCheck}`);\n }\n }\n}\n\n/**\n * Resolves an embedding model from a suggested provider.\n */\nexport function resolveEmbeddingModel<P extends SupportedEmbeddingProvider = \"openai\">(\n options: MuxAIOptions & { provider?: P; model?: EmbeddingModelIdByProvider[P] } = {},\n): { provider: P; modelId: EmbeddingModelIdByProvider[P]; model: EmbeddingModel<string> } {\n const provider = options.provider || (\"openai\" as P);\n const modelId = (options.model || DEFAULT_EMBEDDING_MODELS[provider]) as EmbeddingModelIdByProvider[P];\n\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: openai.embedding(modelId),\n };\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: google.textEmbeddingModel(modelId),\n };\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported embedding provider: ${exhaustiveCheck}`);\n }\n }\n}\n","import env from \"../env.ts\";\n\nimport type {\n ModelRequestOptions,\n SupportedProvider,\n} from \"./providers.ts\";\nimport {\n resolveLanguageModel,\n} from \"./providers.ts\";\n\n/**\n * Gets Mux credentials from environment variables.\n * Used internally by workflow steps to avoid passing credentials through step I/O.\n * Throws if credentials are not available.\n */\nexport function getMuxCredentialsFromEnv(): { muxTokenId: string; muxTokenSecret: string } {\n const muxTokenId = env.MUX_TOKEN_ID;\n const muxTokenSecret = env.MUX_TOKEN_SECRET;\n\n if (!muxTokenId || !muxTokenSecret) {\n throw new Error(\n \"Mux credentials are required. Set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables.\",\n );\n }\n\n return { muxTokenId, muxTokenSecret };\n}\n\n/**\n * Gets an API key from environment variables for the specified provider.\n * Used internally by workflow steps to avoid passing credentials through step I/O.\n * Throws if the API key is not available.\n */\nexport function getApiKeyFromEnv(provider: \"openai\" | \"anthropic\" | \"google\" | \"hive\" | \"elevenlabs\"): string {\n const envVarMap: Record<string, string | undefined> = {\n openai: env.OPENAI_API_KEY,\n anthropic: env.ANTHROPIC_API_KEY,\n google: env.GOOGLE_GENERATIVE_AI_API_KEY,\n hive: env.HIVE_API_KEY,\n elevenlabs: env.ELEVENLABS_API_KEY,\n };\n\n const apiKey = envVarMap[provider];\n if (!apiKey) {\n const envVarNames: Record<string, string> = {\n openai: \"OPENAI_API_KEY\",\n anthropic: \"ANTHROPIC_API_KEY\",\n google: \"GOOGLE_GENERATIVE_AI_API_KEY\",\n hive: \"HIVE_API_KEY\",\n elevenlabs: \"ELEVENLABS_API_KEY\",\n };\n throw new Error(\n `${provider} API key is required. Set ${envVarNames[provider]} environment variable.`,\n );\n }\n\n return apiKey;\n}\n\nexport interface ValidatedCredentials {\n muxTokenId: string;\n muxTokenSecret: string;\n openaiApiKey?: string;\n anthropicApiKey?: string;\n googleApiKey?: string;\n}\n\n/**\n * Validates and retrieves credentials from options or environment variables.\n * This function is NOT a workflow step to avoid exposing credentials in step I/O.\n */\nexport async function validateCredentials(\n requiredProvider?: SupportedProvider,\n): Promise<ValidatedCredentials> {\n const muxTokenId = env.MUX_TOKEN_ID;\n const muxTokenSecret = env.MUX_TOKEN_SECRET;\n const openaiApiKey = env.OPENAI_API_KEY;\n const anthropicApiKey = env.ANTHROPIC_API_KEY;\n const googleApiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n\n if (!muxTokenId || !muxTokenSecret) {\n throw new Error(\n \"Mux credentials are required. Provide muxTokenId and muxTokenSecret in options or set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables.\",\n );\n }\n\n if (requiredProvider === \"openai\" && !openaiApiKey) {\n throw new Error(\n \"OpenAI API key is required. Provide openaiApiKey in options or set OPENAI_API_KEY environment variable.\",\n );\n }\n\n if (requiredProvider === \"anthropic\" && !anthropicApiKey) {\n throw new Error(\n \"Anthropic API key is required. Provide anthropicApiKey in options or set ANTHROPIC_API_KEY environment variable.\",\n );\n }\n\n if (requiredProvider === \"google\" && !googleApiKey) {\n throw new Error(\n \"Google Generative AI API key is required. Provide googleApiKey in options or set GOOGLE_GENERATIVE_AI_API_KEY environment variable.\",\n );\n }\n\n return {\n muxTokenId,\n muxTokenSecret,\n openaiApiKey,\n anthropicApiKey,\n googleApiKey,\n };\n}\n\nexport interface WorkflowConfig {\n credentials: ValidatedCredentials;\n provider: SupportedProvider;\n modelId: string;\n}\n\n/**\n * Validates credentials and resolves model configuration for a workflow.\n * This function is NOT a workflow step to avoid exposing credentials in step I/O.\n */\nexport async function createWorkflowConfig(\n options: ModelRequestOptions,\n provider?: SupportedProvider,\n): Promise<WorkflowConfig> {\n const providerToUse = provider || options.provider || \"openai\";\n const credentials = await validateCredentials(providerToUse);\n const resolved = resolveLanguageModel({\n ...options,\n provider: providerToUse,\n });\n\n return {\n credentials,\n provider: resolved.provider,\n modelId: resolved.modelId as string,\n };\n}\n","import { Buffer } from \"node:buffer\";\n\nimport pRetry, { AbortError } from \"p-retry\";\n\nexport interface ImageDownloadOptions {\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Maximum number of retry attempts (default: 3) */\n retries?: number;\n /** Base delay between retries in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Maximum delay between retries in milliseconds (default: 10000) */\n maxRetryDelay?: number;\n /** Whether to use exponential backoff (default: true) */\n exponentialBackoff?: boolean;\n}\n\nexport interface ImageDownloadResult {\n /** Base64 encoded image data with data URI prefix (e.g., \"data:image/png;base64,iVBORw0K...\") */\n base64Data: string;\n /** Raw image buffer for multipart/form-data uploads */\n buffer: Buffer;\n /** Original image URL */\n url: string;\n /** Content type of the downloaded image */\n contentType: string;\n /** Size of the downloaded image in bytes */\n sizeBytes: number;\n /** Number of retry attempts made (0 if successful on first try) */\n attempts: number;\n}\n\nexport interface AnthropicFileUploadResult {\n /** Anthropic Files API file ID */\n fileId: string;\n /** Original image URL */\n url: string;\n /** Content type of the uploaded image */\n contentType: string;\n /** Size of the uploaded image in bytes */\n sizeBytes: number;\n}\n\nconst DEFAULT_OPTIONS: Required<ImageDownloadOptions> = {\n timeout: 10000,\n retries: 3,\n retryDelay: 1000,\n maxRetryDelay: 10000,\n exponentialBackoff: true,\n};\n\n/**\n * Downloads an image from a URL and converts it to base64 with robust retry logic\n *\n * @param url - The image URL to download\n * @param options - Download configuration options\n * @returns Promise resolving to ImageDownloadResult with base64 data and metadata\n * @throws Error if download fails after all retries\n */\nexport async function downloadImageAsBase64(\n url: string,\n options: ImageDownloadOptions = {},\n): Promise<ImageDownloadResult> {\n \"use step\";\n const opts = { ...DEFAULT_OPTIONS, ...options };\n let attemptCount = 0;\n\n return pRetry(\n async () => {\n attemptCount++;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), opts.timeout);\n\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n \"User-Agent\": \"@mux/ai image downloader\",\n },\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n // Don't retry 4xx errors (except 429 rate limiting)\n if (response.status >= 400 && response.status < 500 && response.status !== 429) {\n throw new AbortError(`HTTP ${response.status}: ${response.statusText}`);\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const contentType = response.headers.get(\"content-type\");\n if (!contentType?.startsWith(\"image/\")) {\n throw new AbortError(`Invalid content type: ${contentType}. Expected image/*`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n if (buffer.length === 0) {\n throw new AbortError(\"Downloaded image is empty\");\n }\n\n // Convert to base64 with data URI prefix\n const base64Data = `data:${contentType};base64,${buffer.toString(\"base64\")}`;\n\n return {\n base64Data,\n buffer,\n url,\n contentType,\n sizeBytes: buffer.length,\n attempts: attemptCount,\n };\n } catch (error) {\n clearTimeout(timeoutId);\n\n // If it's an AbortError (non-retryable), re-throw it\n if (error instanceof AbortError) {\n throw error;\n }\n\n // For network errors, timeout errors, etc., wrap in retryable error\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n throw new Error(`Request timeout after ${opts.timeout}ms`);\n }\n throw new Error(`Download failed: ${error.message}`);\n }\n\n throw new Error(\"Unknown download error\");\n }\n },\n {\n retries: opts.retries,\n minTimeout: opts.retryDelay,\n maxTimeout: opts.maxRetryDelay,\n factor: opts.exponentialBackoff ? 2 : 1,\n randomize: true, // Add jitter to prevent thundering herd\n onFailedAttempt: (error) => {\n console.warn(`Image download attempt ${error.attemptNumber} failed for ${url}`);\n if (error.retriesLeft > 0) {\n console.warn(`Retrying... (${error.retriesLeft} attempts left)`);\n }\n },\n },\n );\n}\n\n/**\n * Downloads multiple images concurrently with controlled concurrency\n *\n * @param urls - Array of image URLs to download\n * @param options - Download configuration options\n * @param maxConcurrent - Maximum concurrent downloads (default: 5)\n * @returns Promise resolving to array of ImageDownloadResult (in same order as input URLs)\n */\nexport async function downloadImagesAsBase64(\n urls: string[],\n options: ImageDownloadOptions = {},\n maxConcurrent: number = 5,\n): Promise<ImageDownloadResult[]> {\n \"use step\";\n const results: ImageDownloadResult[] = [];\n\n for (let i = 0; i < urls.length; i += maxConcurrent) {\n const batch = urls.slice(i, i + maxConcurrent);\n const batchPromises = batch.map(url => downloadImageAsBase64(url, options));\n const batchResults = await Promise.all(batchPromises);\n results.push(...batchResults);\n }\n\n return results;\n}\n\n/**\n * Uploads an image to Anthropic Files API for use in messages\n *\n * @param url - The image URL to download and upload\n * @param anthropicApiKey - Anthropic API key\n * @param options - Download configuration options\n * @returns Promise resolving to AnthropicFileUploadResult with file ID and metadata\n * @throws Error if download or upload fails\n */\nexport async function uploadImageToAnthropicFiles(\n url: string,\n anthropicApiKey: string,\n options: ImageDownloadOptions = {},\n): Promise<AnthropicFileUploadResult> {\n \"use step\";\n // First download the image\n const downloadResult = await downloadImageAsBase64(url, options);\n\n // Create form data for Files API upload\n const formData = new FormData();\n\n // Create a Blob from the buffer for form data\n const imageBlob = new Blob([downloadResult.buffer], {\n type: downloadResult.contentType,\n });\n\n // Get file extension from content type\n const extension = downloadResult.contentType.split(\"/\")[1] || \"png\";\n formData.append(\"file\", imageBlob, `image.${extension}`);\n\n // Upload to Anthropic Files API\n const response = await fetch(\"https://api.anthropic.com/v1/files\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": anthropicApiKey,\n \"anthropic-version\": \"2023-06-01\",\n \"anthropic-beta\": \"files-api-2025-04-14\",\n // Don't set Content-Type header - let fetch set it with boundary for multipart\n },\n body: formData,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Anthropic Files API error: ${response.status} ${response.statusText} - ${errorText}`);\n }\n\n const fileResult = await response.json() as { id: string };\n\n return {\n fileId: fileResult.id,\n url: downloadResult.url,\n contentType: downloadResult.contentType,\n sizeBytes: downloadResult.sizeBytes,\n };\n}\n","import Mux from \"@mux/mux-node\";\n\nimport type { MuxAsset, PlaybackAsset, PlaybackPolicy } from \"../types\";\n\nimport { getMuxCredentialsFromEnv } from \"./client-factory\";\n\n/**\n * Finds a usable playback ID for the given asset.\n * Prefers public playback IDs, falls back to signed if no public is available.\n * Throws an error if no public or signed playback ID is found.\n */\nfunction getPlaybackId(asset: MuxAsset): { id: string; policy: PlaybackPolicy } {\n const playbackIds = asset.playback_ids || [];\n\n // First, try to find a public playback ID\n const publicPlaybackId = playbackIds.find(pid => pid.policy === \"public\");\n if (publicPlaybackId?.id) {\n return { id: publicPlaybackId.id, policy: \"public\" };\n }\n\n // Fall back to signed playback ID\n const signedPlaybackId = playbackIds.find(pid => pid.policy === \"signed\");\n if (signedPlaybackId?.id) {\n return { id: signedPlaybackId.id, policy: \"signed\" };\n }\n\n throw new Error(\n \"No public or signed playback ID found for this asset. \" +\n \"A public or signed playback ID is required. DRM playback IDs are not currently supported.\",\n );\n}\n\nexport async function getPlaybackIdForAsset(\n assetId: string,\n): Promise<PlaybackAsset> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n\n const asset = await mux.video.assets.retrieve(assetId);\n const { id: playbackId, policy } = getPlaybackId(asset);\n\n return { asset, playbackId, policy };\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A single section of a prompt, rendered as an XML-like tag.\n */\nexport interface PromptSection {\n /** The XML tag name for this section */\n tag: string;\n /** The content inside the tag */\n content: string;\n /** Optional attributes to add to the tag (e.g., { format: \"plain text\" }) */\n attributes?: Record<string, string>;\n}\n\n/**\n * Configuration for building a prompt section.\n * Can be a full PromptSection object, just a string (content only), or undefined to use default.\n */\nexport type SectionOverride = string | PromptSection | undefined;\n\n/**\n * A template defining the default sections for a prompt.\n * Keys are section identifiers, values are the default PromptSection definitions.\n */\nexport type PromptTemplate<TSections extends string> = Record<TSections, PromptSection>;\n\n/**\n * User-provided overrides for prompt sections.\n * Each key can override the corresponding section's content or full definition.\n */\nexport type PromptOverrides<TSections extends string> = Partial<Record<TSections, SectionOverride>>;\n\n/**\n * Configuration for the prompt builder.\n */\nexport interface PromptBuilderConfig<TSections extends string> {\n /** Default sections that make up the prompt template */\n template: PromptTemplate<TSections>;\n /** Order in which sections should appear in the final prompt */\n sectionOrder: TSections[];\n}\n\n/**\n * A configured prompt builder instance with methods to build prompts.\n */\nexport interface PromptBuilder<TSections extends string> {\n /** The default template sections */\n template: PromptTemplate<TSections>;\n /** Build a prompt string, optionally overriding specific sections */\n build: (overrides?: PromptOverrides<TSections>) => string;\n /** Build a prompt with additional dynamic sections appended */\n buildWithContext: (\n overrides?: PromptOverrides<TSections>,\n additionalSections?: PromptSection[],\n ) => string;\n /** Get a single section's content (useful for partial rendering) */\n getSection: (section: TSections, override?: SectionOverride) => string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Renders a PromptSection as an XML-like string.\n */\nexport function renderSection(section: PromptSection): string {\n const { tag, content, attributes } = section;\n\n const XML_NAME_PATTERN = /^[A-Z_][\\w.:-]*$/i;\n\n const assertValidXmlName = (name: string, context: \"tag\" | \"attribute\"): void => {\n if (!XML_NAME_PATTERN.test(name)) {\n throw new Error(`Invalid XML ${context} name: \"${name}\"`);\n }\n };\n\n const escapeXmlText = (value: string): string =>\n value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;/\");\n\n const escapeXmlAttribute = (value: string): string =>\n escapeXmlText(value).replace(/\"/g, \"&quot;\");\n\n if (!content.trim()) {\n return \"\";\n }\n\n assertValidXmlName(tag, \"tag\");\n\n const attrString = attributes ?\n ` ${\n Object.entries(attributes)\n .map(([key, value]) => {\n assertValidXmlName(key, \"attribute\");\n return `${key}=\"${escapeXmlAttribute(value)}\"`;\n })\n .join(\" \")}` :\n \"\";\n\n const safeContent = escapeXmlText(content.trim());\n\n return `<${tag}${attrString}>\\n${safeContent}\\n</${tag}>`;\n}\n\n/**\n * Resolves a section override to a full PromptSection.\n */\nfunction resolveSection(\n defaultSection: PromptSection,\n override?: SectionOverride,\n): PromptSection {\n if (override === undefined) {\n return defaultSection;\n }\n\n if (typeof override === \"string\") {\n return { ...defaultSection, content: override };\n }\n\n return override;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Factory\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Creates a type-safe prompt builder from a template configuration.\n *\n * @example\n * ```typescript\n * const builder = createPromptBuilder({\n * template: {\n * task: { tag: 'task', content: 'Analyze the image...' },\n * tone: { tag: 'tone', content: 'Be professional.' },\n * },\n * sectionOrder: ['task', 'tone'],\n * });\n *\n * // Use defaults\n * const prompt1 = builder.build();\n *\n * // Override specific sections\n * const prompt2 = builder.build({\n * tone: 'Be casual and friendly.',\n * });\n * ```\n */\nexport function createPromptBuilder<TSections extends string>(\n config: PromptBuilderConfig<TSections>,\n): PromptBuilder<TSections> {\n const { template, sectionOrder } = config;\n\n const getSection = (section: TSections, override?: SectionOverride): string => {\n const resolved = resolveSection(template[section], override);\n return renderSection(resolved);\n };\n\n const build = (overrides?: PromptOverrides<TSections>): string => {\n const sections = sectionOrder\n .map(sectionKey => getSection(sectionKey, overrides?.[sectionKey]))\n .filter(Boolean);\n\n return sections.join(\"\\n\\n\");\n };\n\n const buildWithContext = (\n overrides?: PromptOverrides<TSections>,\n additionalSections?: PromptSection[],\n ): string => {\n const basePrompt = build(overrides);\n\n if (!additionalSections?.length) {\n return basePrompt;\n }\n\n const additional = additionalSections\n .map(renderSection)\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n return additional ? `${basePrompt}\\n\\n${additional}` : basePrompt;\n };\n\n return {\n template,\n build,\n buildWithContext,\n getSection,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Common Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Creates a transcript section for inclusion in prompts.\n */\nexport function createTranscriptSection(\n transcriptText: string,\n format: \"plain text\" | \"WebVTT\" = \"plain text\",\n): PromptSection {\n return {\n tag: \"transcript\",\n content: transcriptText,\n attributes: { format },\n };\n}\n\n/**\n * Creates a tone section for inclusion in prompts.\n */\nexport function createToneSection(instruction: string): PromptSection {\n return {\n tag: \"tone\",\n content: instruction,\n };\n}\n","import Mux from \"@mux/mux-node\";\n\nimport env from \"../env\";\n\n/**\n * Context required to sign URLs for signed playback IDs.\n */\nexport interface SigningContext {\n /** The signing key ID from Mux dashboard. */\n keyId: string;\n /** The base64-encoded private key from Mux dashboard. */\n keySecret: string;\n /** Token expiration time (e.g. '1h', '1d'). Defaults to '1h'. */\n expiration?: string;\n}\n\n/**\n * Token type determines which Mux service the token is valid for.\n */\nexport type TokenType = \"video\" | \"thumbnail\" | \"storyboard\" | \"gif\";\n\n/**\n * Resolves signing context from config or environment variables.\n * Returns undefined if signing keys are not configured.\n */\nexport function getMuxSigningContextFromEnv(): SigningContext | undefined {\n const keyId = env.MUX_SIGNING_KEY;\n const keySecret = env.MUX_PRIVATE_KEY;\n\n if (!keyId || !keySecret) {\n return undefined;\n }\n\n return { keyId, keySecret };\n}\n\n/**\n * Creates a Mux client configured for JWT signing.\n * This client is used internally for signing operations.\n */\nfunction createSigningClient(context: SigningContext): Mux {\n return new Mux({\n // These are not needed for signing, but the SDK requires them\n // Using empty strings as we only need the jwt functionality\n tokenId: env.MUX_TOKEN_ID || \"\",\n tokenSecret: env.MUX_TOKEN_SECRET || \"\",\n jwtSigningKey: context.keyId,\n jwtPrivateKey: context.keySecret,\n });\n}\n\n/**\n * Generates a signed token for a playback ID using the Mux SDK.\n *\n * @param playbackId - The Mux playback ID to sign\n * @param context - Signing context with key credentials\n * @param type - Token type (video, thumbnail, storyboard, gif)\n * @param params - Additional parameters for thumbnail/storyboard tokens (values will be stringified)\n * @returns Signed JWT token\n */\nexport async function signPlaybackId(\n playbackId: string,\n context: SigningContext,\n type: TokenType = \"video\",\n params?: Record<string, string | number>,\n): Promise<string> {\n \"use step\";\n const client = createSigningClient(context);\n\n // Convert params to Record<string, string> as required by the SDK\n const stringParams = params ?\n Object.fromEntries(\n Object.entries(params).map(([key, value]) => [key, String(value)]),\n ) :\n undefined;\n\n return client.jwt.signPlaybackId(playbackId, {\n type,\n expiration: context.expiration || \"1h\",\n params: stringParams,\n });\n}\n\n/**\n * Appends a signed token to a Mux URL.\n *\n * @param url - The base Mux URL (e.g. https://image.mux.com/{playbackId}/thumbnail.png)\n * @param playbackId - The Mux playback ID\n * @param context - Signing context with key credentials\n * @param type - Token type for the URL\n * @param params - Additional parameters for the token\n * @returns URL with token query parameter appended\n */\nexport async function signUrl(\n url: string,\n playbackId: string,\n context: SigningContext,\n type: TokenType = \"video\",\n params?: Record<string, string | number>,\n): Promise<string> {\n \"use step\";\n const token = await signPlaybackId(playbackId, context, type, params);\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}token=${token}`;\n}\n","import { getMuxSigningContextFromEnv, signUrl } from \"../lib/url-signing\";\n\nexport const DEFAULT_STORYBOARD_WIDTH = 640;\n\n/**\n * Generates a storyboard URL for the given playback ID.\n * If shouldSign is true, the URL will be signed with a token using credentials from environment variables.\n *\n * @param playbackId - The Mux playback ID\n * @param width - Width of the storyboard in pixels (default: 640)\n * @param shouldSign - Flag for whether or not to use signed playback IDs (default: false)\n * @returns Storyboard URL (signed if shouldSign is true)\n */\nexport async function getStoryboardUrl(\n playbackId: string,\n width: number = DEFAULT_STORYBOARD_WIDTH,\n shouldSign: boolean = false,\n): Promise<string> {\n \"use step\";\n const baseUrl = `https://image.mux.com/${playbackId}/storyboard.png`;\n\n if (shouldSign) {\n // NOTE: this assumes you have already validated the signing context elsewhere\n const signingContext = getMuxSigningContextFromEnv();\n return signUrl(baseUrl, playbackId, signingContext!, \"storyboard\", { width });\n }\n\n return `${baseUrl}?width=${width}`;\n}\n","import { generateObject } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createWorkflowConfig } from \"../lib/client-factory\";\nimport { getPlaybackIdForAsset } from \"../lib/mux-assets\";\nimport { createLanguageModelFromConfig } from \"../lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"../lib/providers\";\nimport { withRetry } from \"../lib/retry\";\nimport { getMuxSigningContextFromEnv } from \"../lib/url-signing\";\nimport {\n extractTimestampedTranscript,\n fetchTranscriptForAsset,\n getReadyTextTracks,\n} from \"../primitives/transcripts\";\nimport type { MuxAIOptions } from \"../types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const chapterSchema = z.object({\n startTime: z.number(),\n title: z.string(),\n});\n\nexport type Chapter = z.infer<typeof chapterSchema>;\n\nexport const chaptersSchema = z.object({\n chapters: z.array(chapterSchema),\n});\n\nexport type ChaptersType = z.infer<typeof chaptersSchema>;\n\n/** Structured return payload from `generateChapters`. */\nexport interface ChaptersResult {\n assetId: string;\n languageCode: string;\n chapters: Chapter[];\n}\n\n/** Configuration accepted by `generateChapters`. */\nexport interface ChaptersOptions extends MuxAIOptions {\n /** AI provider used to interpret the transcript (defaults to 'openai'). */\n provider?: SupportedProvider;\n /** Provider-specific model identifier. */\n model?: ModelIdByProvider[SupportedProvider];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nasync function generateChaptersWithAI({\n provider,\n modelId,\n timestampedTranscript,\n systemPrompt,\n}: {\n provider: SupportedProvider;\n modelId: string;\n timestampedTranscript: string;\n systemPrompt: string;\n}): Promise<ChaptersType> {\n \"use step\";\n\n const model = createLanguageModelFromConfig(provider, modelId);\n\n const response = await withRetry(() =>\n generateObject({\n model,\n schema: chaptersSchema,\n messages: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n {\n role: \"user\",\n content: timestampedTranscript,\n },\n ],\n }),\n );\n\n return response.object;\n}\n\nconst SYSTEM_PROMPT = `Your role is to segment the following captions into chunked chapters, summarising each chapter with a title.\n\nAnalyze the transcript and create logical chapter breaks based on topic changes, major transitions, or distinct sections of content. Each chapter should represent a meaningful segment of the video.\n\nYou must respond with valid JSON in exactly this format:\n{\n \"chapters\": [\n {\"startTime\": 0, \"title\": \"Introduction\"},\n {\"startTime\": 45.5, \"title\": \"Main Topic Discussion\"},\n {\"startTime\": 120.0, \"title\": \"Conclusion\"}\n ]\n}\n\nImportant rules:\n- startTime must be in seconds (not HH:MM:SS format)\n- Always start with startTime: 0 for the first chapter\n- Create 3-8 chapters depending on content length and natural breaks\n- Chapter titles should be concise and descriptive\n- Do not include any text before or after the JSON\n- The JSON must be valid and parseable`;\n\nexport async function generateChapters(\n assetId: string,\n languageCode: string,\n options: ChaptersOptions = {},\n): Promise<ChaptersResult> {\n \"use workflow\";\n const { provider = \"openai\", model } = options;\n\n // Validate credentials and resolve language model\n const config = await createWorkflowConfig({ ...options, model }, provider as SupportedProvider);\n\n // Fetch asset and caption track/transcript\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n const transcriptResult = await fetchTranscriptForAsset(assetData, playbackId, {\n languageCode,\n cleanTranscript: false, // keep timestamps for chapter segmentation\n shouldSign: policy === \"signed\",\n });\n\n if (!transcriptResult.track || !transcriptResult.transcriptText) {\n const availableLanguages = getReadyTextTracks(assetData)\n .map(t => t.language_code)\n .filter(Boolean)\n .join(\", \");\n throw new Error(\n `No caption track found for language '${languageCode}'. Available languages: ${availableLanguages || \"none\"}`,\n );\n }\n\n const timestampedTranscript = extractTimestampedTranscript(transcriptResult.transcriptText);\n if (!timestampedTranscript) {\n throw new Error(\"No usable content found in caption track\");\n }\n\n // Generate chapters using AI SDK\n let chaptersData: ChaptersType | null = null;\n\n try {\n chaptersData = await generateChaptersWithAI({\n provider: config.provider,\n modelId: config.modelId,\n timestampedTranscript,\n systemPrompt: SYSTEM_PROMPT,\n });\n } catch (error) {\n throw new Error(\n `Failed to generate chapters with ${provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n if (!chaptersData || !chaptersData.chapters) {\n throw new Error(\"No chapters generated from AI response\");\n }\n\n // Validate and sort chapters\n const validChapters = chaptersData.chapters\n .filter(chapter => typeof chapter.startTime === \"number\" && typeof chapter.title === \"string\")\n .sort((a, b) => a.startTime - b.startTime);\n\n if (validChapters.length === 0) {\n throw new Error(\"No valid chapters found in AI response\");\n }\n\n // Ensure first chapter starts at 0\n if (validChapters[0].startTime !== 0) {\n validChapters[0].startTime = 0;\n }\n\n return {\n assetId,\n languageCode,\n chapters: validChapters,\n };\n}\n","/**\n * Retry configuration options\n */\nexport interface RetryOptions {\n maxRetries?: number;\n baseDelay?: number;\n maxDelay?: number;\n shouldRetry?: (error: Error, attempt: number) => boolean;\n}\n\nconst DEFAULT_RETRY_OPTIONS: Required<Omit<RetryOptions, \"shouldRetry\">> = {\n maxRetries: 3,\n baseDelay: 2000,\n maxDelay: 10000,\n};\n\n/**\n * Default retry condition - retries on timeout errors\n */\nfunction defaultShouldRetry(error: Error, _attempt: number): boolean {\n return Boolean(error.message && error.message.includes(\"Timeout while downloading\"));\n}\n\n/**\n * Calculates exponential backoff delay with jitter\n */\nfunction calculateDelay(attempt: number, baseDelay: number, maxDelay: number): number {\n const exponentialDelay = baseDelay * 2 ** (attempt - 1);\n const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);\n return Math.min(delayWithJitter, maxDelay);\n}\n\n/**\n * Executes an async function with retry logic\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n {\n maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,\n baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,\n maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,\n shouldRetry = defaultShouldRetry,\n }: RetryOptions = {},\n): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n const isLastAttempt = attempt === maxRetries;\n if (isLastAttempt || !shouldRetry(lastError, attempt + 1)) {\n throw lastError;\n }\n\n const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);\n console.warn(\n `Attempt ${attempt + 1} failed: ${lastError.message}. Retrying in ${Math.round(delay)}ms...`,\n );\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n throw lastError || new Error(\"Retry failed with unknown error\");\n}\n","import { getMuxSigningContextFromEnv, signUrl } from \"../lib/url-signing\";\nimport type { AssetTextTrack, MuxAsset } from \"../types\";\n\n/** A single cue from a VTT file with timing info. */\nexport interface VTTCue {\n startTime: number;\n endTime: number;\n text: string;\n}\n\nexport interface TranscriptFetchOptions {\n languageCode?: string;\n cleanTranscript?: boolean;\n /** Optional signing context for signed playback IDs */\n shouldSign?: boolean;\n}\n\nexport interface TranscriptResult {\n transcriptText: string;\n transcriptUrl?: string;\n track?: AssetTextTrack;\n}\n\nexport function getReadyTextTracks(asset: MuxAsset): AssetTextTrack[] {\n return (asset.tracks || []).filter(\n track => track.type === \"text\" && track.status === \"ready\",\n );\n}\n\nexport function findCaptionTrack(asset: MuxAsset, languageCode?: string): AssetTextTrack | undefined {\n const tracks = getReadyTextTracks(asset);\n if (!tracks.length)\n return undefined;\n\n if (!languageCode) {\n return tracks[0];\n }\n\n return tracks.find(\n track =>\n track.text_type === \"subtitles\" &&\n track.language_code === languageCode,\n );\n}\n\nexport function extractTextFromVTT(vttContent: string): string {\n if (!vttContent.trim()) {\n return \"\";\n }\n\n const lines = vttContent.split(\"\\n\");\n const textLines: string[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n if (!line)\n continue;\n if (line === \"WEBVTT\")\n continue;\n if (line.startsWith(\"NOTE \"))\n continue;\n if (line.includes(\"-->\"))\n continue;\n if (/^[\\w-]+$/.test(line) && !line.includes(\" \"))\n continue;\n if (line.startsWith(\"STYLE\") || line.startsWith(\"REGION\"))\n continue;\n\n const cleanLine = line.replace(/<[^>]*>/g, \"\").trim();\n\n if (cleanLine) {\n textLines.push(cleanLine);\n }\n }\n\n return textLines.join(\" \").replace(/\\s+/g, \" \").trim();\n}\n\nexport function vttTimestampToSeconds(timestamp: string): number {\n const parts = timestamp.split(\":\");\n if (parts.length !== 3)\n return 0;\n\n const hours = Number.parseInt(parts[0], 10) || 0;\n const minutes = Number.parseInt(parts[1], 10) || 0;\n const seconds = Number.parseFloat(parts[2]) || 0;\n\n return hours * 3600 + minutes * 60 + seconds;\n}\n\nexport function extractTimestampedTranscript(vttContent: string): string {\n if (!vttContent.trim()) {\n return \"\";\n }\n\n const lines = vttContent.split(\"\\n\");\n const segments: Array<{ time: number; text: string }> = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n if (line.includes(\"-->\")) {\n const startTime = line.split(\" --> \")[0].trim();\n const timeInSeconds = vttTimestampToSeconds(startTime);\n\n let j = i + 1;\n while (j < lines.length && !lines[j].trim()) {\n j++;\n }\n\n if (j < lines.length) {\n const text = lines[j].trim().replace(/<[^>]*>/g, \"\");\n if (text) {\n segments.push({ time: timeInSeconds, text });\n }\n }\n }\n }\n\n return segments\n .map(segment => `[${Math.floor(segment.time)}s] ${segment.text}`)\n .join(\"\\n\");\n}\n\n/**\n * Parses VTT content into structured cues with timing.\n *\n * @param vttContent - Raw VTT file content\n * @returns Array of VTT cues with start/end times and text\n */\nexport function parseVTTCues(vttContent: string): VTTCue[] {\n if (!vttContent.trim())\n return [];\n\n const lines = vttContent.split(\"\\n\");\n const cues: VTTCue[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n if (line.includes(\"-->\")) {\n const [startStr, endStr] = line.split(\" --> \").map(s => s.trim());\n const startTime = vttTimestampToSeconds(startStr);\n const endTime = vttTimestampToSeconds(endStr.split(\" \")[0]); // Handle cue settings\n\n // Collect text lines until empty line or next timestamp\n const textLines: string[] = [];\n let j = i + 1;\n while (j < lines.length && lines[j].trim() && !lines[j].includes(\"-->\")) {\n const cleanLine = lines[j].trim().replace(/<[^>]*>/g, \"\");\n if (cleanLine)\n textLines.push(cleanLine);\n j++;\n }\n\n if (textLines.length > 0) {\n cues.push({\n startTime,\n endTime,\n text: textLines.join(\" \"),\n });\n }\n }\n }\n\n return cues;\n}\n\n/**\n * Builds a transcript URL for the given playback ID and track ID.\n * If a signing context is provided, the URL will be signed with a token.\n *\n * @param playbackId - The Mux playback ID\n * @param trackId - The text track ID\n * @param shouldSign - Flag for whether or not to use signed playback IDs\n * @returns Transcript URL (signed if context provided)\n */\nexport async function buildTranscriptUrl(\n playbackId: string,\n trackId: string,\n shouldSign: boolean = false,\n): Promise<string> {\n \"use step\";\n const baseUrl = `https://stream.mux.com/${playbackId}/text/${trackId}.vtt`;\n\n if (shouldSign) {\n // NOTE: this assumes you have already validated the signing context elsewhere\n const signingContext = getMuxSigningContextFromEnv();\n return signUrl(baseUrl, playbackId, signingContext!, \"video\");\n }\n\n return baseUrl;\n}\n\nexport async function fetchTranscriptForAsset(\n asset: MuxAsset,\n playbackId: string,\n options: TranscriptFetchOptions = {},\n): Promise<TranscriptResult> {\n \"use step\";\n const { languageCode, cleanTranscript = true, shouldSign } = options;\n const track = findCaptionTrack(asset, languageCode);\n\n if (!track) {\n return { transcriptText: \"\" };\n }\n\n if (!track.id) {\n return { transcriptText: \"\", track };\n }\n\n const transcriptUrl = await buildTranscriptUrl(playbackId, track.id, shouldSign);\n\n try {\n const response = await fetch(transcriptUrl);\n if (!response.ok) {\n return { transcriptText: \"\", transcriptUrl, track };\n }\n\n const rawVtt = await response.text();\n const transcriptText = cleanTranscript ? extractTextFromVTT(rawVtt) : rawVtt;\n\n return { transcriptText, transcriptUrl, track };\n } catch (error) {\n console.warn(\"Failed to fetch transcript:\", error);\n return { transcriptText: \"\", transcriptUrl, track };\n }\n}\n","import { embed } from \"ai\";\n\nimport { getPlaybackIdForAsset } from \"../lib/mux-assets\";\nimport type { EmbeddingModelIdByProvider, SupportedEmbeddingProvider } from \"../lib/providers\";\nimport { createEmbeddingModelFromConfig, resolveEmbeddingModel } from \"../lib/providers\";\nimport { withRetry } from \"../lib/retry\";\nimport { getMuxSigningContextFromEnv } from \"../lib/url-signing\";\nimport { chunkText, chunkVTTCues } from \"../primitives/text-chunking\";\nimport { fetchTranscriptForAsset, getReadyTextTracks, parseVTTCues } from \"../primitives/transcripts\";\nimport type {\n ChunkEmbedding,\n ChunkingStrategy,\n MuxAIOptions,\n TextChunk,\n VideoEmbeddingsResult,\n} from \"../types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Configuration accepted by `generateVideoEmbeddings`. */\nexport interface EmbeddingsOptions extends MuxAIOptions {\n /** AI provider used to generate embeddings (defaults to 'openai'). */\n provider?: SupportedEmbeddingProvider;\n /** Provider-specific model identifier (defaults to text-embedding-3-small for OpenAI). */\n model?: EmbeddingModelIdByProvider[SupportedEmbeddingProvider];\n /** Language code for transcript selection (defaults to first available). */\n languageCode?: string;\n /** Chunking strategy configuration (defaults to token-based with 500 tokens, 100 overlap). */\n chunkingStrategy?: ChunkingStrategy;\n /** Maximum number of chunks to process concurrently (defaults to 5). */\n batchSize?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Averages multiple embedding vectors into a single vector.\n *\n * @param embeddings - Array of embedding vectors to average\n * @returns Single averaged embedding vector\n */\nfunction averageEmbeddings(embeddings: number[][]): number[] {\n if (embeddings.length === 0) {\n return [];\n }\n\n const dimensions = embeddings[0].length;\n const averaged = Array.from({ length: dimensions }, () => 0);\n\n for (const embedding of embeddings) {\n for (let i = 0; i < dimensions; i++) {\n averaged[i] += embedding[i];\n }\n }\n\n for (let i = 0; i < dimensions; i++) {\n averaged[i] /= embeddings.length;\n }\n\n return averaged;\n}\n\n/**\n * Generates embedding for a single text chunk using the specified AI provider.\n *\n * @param options - Configuration object\n * @param options.chunk - Text chunk to embed\n * @param options.provider - AI provider for embedding generation\n * @param options.modelId - Provider-specific model identifier\n * @returns Chunk embedding with metadata\n */\nasync function generateSingleChunkEmbedding({\n chunk,\n provider,\n modelId,\n}: {\n chunk: TextChunk;\n provider: SupportedEmbeddingProvider;\n modelId: string;\n}): Promise<ChunkEmbedding> {\n \"use step\";\n\n const model = createEmbeddingModelFromConfig(provider, modelId);\n const response = await withRetry(() =>\n embed({\n model,\n value: chunk.text,\n }),\n );\n\n return {\n chunkId: chunk.id,\n embedding: response.embedding,\n metadata: {\n startTime: chunk.startTime,\n endTime: chunk.endTime,\n tokenCount: chunk.tokenCount,\n },\n };\n}\n\n/**\n * Generates vector embeddings for a video asset's transcript.\n *\n * This function:\n * 1. Fetches the video transcript from Mux\n * 2. Chunks the transcript according to the specified strategy\n * 3. Generates embeddings for each chunk using the specified AI provider\n * 4. Returns both individual chunk embeddings and an averaged embedding\n *\n * @param assetId - Mux asset ID\n * @param options - Configuration options\n * @returns Video embeddings result with chunks and averaged embedding\n *\n * @example\n * ```typescript\n * const embeddings = await generateVideoEmbeddings(\"asset-id\", {\n * provider: \"openai\",\n * chunkingStrategy: { type: \"token\", maxTokens: 500, overlap: 100 },\n * });\n *\n * // Store in vector database\n * for (const chunk of embeddings.chunks) {\n * await db.insert({\n * assetId: embeddings.assetId,\n * chunkId: chunk.chunkId,\n * embedding: chunk.embedding,\n * metadata: chunk.metadata,\n * });\n * }\n * ```\n */\nexport async function generateVideoEmbeddings(\n assetId: string,\n options: EmbeddingsOptions = {},\n): Promise<VideoEmbeddingsResult> {\n \"use workflow\";\n const {\n provider = \"openai\",\n model,\n languageCode,\n chunkingStrategy = { type: \"token\", maxTokens: 500, overlap: 100 } as ChunkingStrategy,\n batchSize = 5,\n } = options;\n\n const embeddingModel = resolveEmbeddingModel({ ...options, provider, model });\n\n // Fetch asset and playback ID\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Fetch transcript (raw VTT for VTT strategy, cleaned text otherwise)\n const useVttChunking = chunkingStrategy.type === \"vtt\";\n const transcriptResult = await fetchTranscriptForAsset(assetData, playbackId, {\n languageCode,\n cleanTranscript: !useVttChunking,\n shouldSign: policy === \"signed\",\n });\n\n if (!transcriptResult.track || !transcriptResult.transcriptText) {\n const availableLanguages = getReadyTextTracks(assetData)\n .map(t => t.language_code)\n .filter(Boolean)\n .join(\", \");\n throw new Error(\n `No caption track found${languageCode ? ` for language '${languageCode}'` : \"\"}. Available languages: ${availableLanguages || \"none\"}`,\n );\n }\n\n const transcriptText = transcriptResult.transcriptText;\n if (!transcriptText.trim()) {\n throw new Error(\"Transcript is empty\");\n }\n\n // Chunk the transcript\n const chunks = useVttChunking ?\n chunkVTTCues(\n parseVTTCues(transcriptText),\n chunkingStrategy.maxTokens,\n chunkingStrategy.overlapCues,\n ) :\n chunkText(transcriptText, chunkingStrategy);\n if (chunks.length === 0) {\n throw new Error(\"No chunks generated from transcript\");\n }\n\n // Generate embeddings for all chunks (process in batches)\n const chunkEmbeddings: ChunkEmbedding[] = [];\n try {\n for (let i = 0; i < chunks.length; i += batchSize) {\n const batch = chunks.slice(i, i + batchSize);\n\n const batchResults = await Promise.all(\n batch.map(chunk =>\n generateSingleChunkEmbedding({\n chunk,\n provider: embeddingModel.provider,\n modelId: embeddingModel.modelId as string,\n }),\n ),\n );\n\n chunkEmbeddings.push(...batchResults);\n }\n } catch (error) {\n throw new Error(\n `Failed to generate embeddings with ${provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n if (chunkEmbeddings.length === 0) {\n throw new Error(\"No embeddings generated\");\n }\n\n // Calculate averaged embedding\n const averagedEmbedding = averageEmbeddings(chunkEmbeddings.map(ce => ce.embedding));\n\n // Calculate total tokens\n const totalTokens = chunks.reduce((sum, chunk) => sum + chunk.tokenCount, 0);\n\n return {\n assetId,\n chunks: chunkEmbeddings,\n averagedEmbedding,\n provider,\n model: embeddingModel.modelId,\n metadata: {\n totalChunks: chunks.length,\n totalTokens,\n chunkingStrategy: JSON.stringify(chunkingStrategy),\n embeddingDimensions: chunkEmbeddings[0].embedding.length,\n generatedAt: new Date().toISOString(),\n },\n };\n}\n","import type { ChunkingStrategy, TextChunk } from \"../types\";\n\nimport type { VTTCue } from \"./transcripts\";\n\n/**\n * Simple token counter that approximates tokens by word count.\n * For production use with OpenAI, consider using a proper tokenizer like tiktoken.\n * This approximation is generally close enough for chunking purposes (1 token ≈ 0.75 words).\n */\nexport function estimateTokenCount(text: string): number {\n const words = text.trim().split(/\\s+/).length;\n return Math.ceil(words / 0.75);\n}\n\n/**\n * Chunks text into overlapping segments based on token count.\n *\n * @param text - The text to chunk\n * @param maxTokens - Maximum tokens per chunk\n * @param overlapTokens - Number of tokens to overlap between chunks\n * @returns Array of text chunks with metadata\n */\nexport function chunkByTokens(\n text: string,\n maxTokens: number,\n overlapTokens: number = 0,\n): TextChunk[] {\n if (!text.trim()) {\n return [];\n }\n\n const chunks: TextChunk[] = [];\n const words = text.trim().split(/\\s+/);\n\n // Convert tokens to approximate word count\n const wordsPerChunk = Math.floor(maxTokens * 0.75);\n const overlapWords = Math.floor(overlapTokens * 0.75);\n\n let chunkIndex = 0;\n let currentPosition = 0;\n\n while (currentPosition < words.length) {\n const chunkWords = words.slice(\n currentPosition,\n currentPosition + wordsPerChunk,\n );\n const chunkText = chunkWords.join(\" \");\n const tokenCount = estimateTokenCount(chunkText);\n\n chunks.push({\n id: `chunk-${chunkIndex}`,\n text: chunkText,\n tokenCount,\n });\n\n // Move forward by chunk size minus overlap\n currentPosition += wordsPerChunk - overlapWords;\n chunkIndex++;\n\n // Prevent infinite loop if overlap is too large\n if (currentPosition <= (chunkIndex - 1) * (wordsPerChunk - overlapWords)) {\n break;\n }\n }\n\n return chunks;\n}\n\n/**\n * Creates a TextChunk from a group of VTT cues.\n */\nfunction createChunkFromCues(cues: VTTCue[], index: number): TextChunk {\n const text = cues.map(c => c.text).join(\" \");\n return {\n id: `chunk-${index}`,\n text,\n tokenCount: estimateTokenCount(text),\n startTime: cues[0].startTime,\n endTime: cues[cues.length - 1].endTime,\n };\n}\n\n/**\n * Chunks VTT cues into groups that respect natural cue boundaries.\n * Splits at cue boundaries rather than mid-sentence, preserving accurate timestamps.\n *\n * @param cues - Array of VTT cues to chunk\n * @param maxTokens - Maximum tokens per chunk\n * @param overlapCues - Number of cues to overlap between chunks (default: 2)\n * @returns Array of text chunks with accurate start/end times\n */\nexport function chunkVTTCues(\n cues: VTTCue[],\n maxTokens: number,\n overlapCues: number = 2,\n): TextChunk[] {\n if (cues.length === 0)\n return [];\n\n const chunks: TextChunk[] = [];\n let currentCues: VTTCue[] = [];\n let currentTokens = 0;\n let chunkIndex = 0;\n\n for (let i = 0; i < cues.length; i++) {\n const cue = cues[i];\n const cueTokens = estimateTokenCount(cue.text);\n\n // If adding this cue would exceed limit, finalize current chunk\n if (currentTokens + cueTokens > maxTokens && currentCues.length > 0) {\n chunks.push(createChunkFromCues(currentCues, chunkIndex));\n chunkIndex++;\n\n // Start new chunk with overlap from end of previous\n const overlapStart = Math.max(0, currentCues.length - overlapCues);\n currentCues = currentCues.slice(overlapStart);\n currentTokens = currentCues.reduce(\n (sum, c) => sum + estimateTokenCount(c.text),\n 0,\n );\n }\n\n currentCues.push(cue);\n currentTokens += cueTokens;\n }\n\n // Don't forget the last chunk\n if (currentCues.length > 0) {\n chunks.push(createChunkFromCues(currentCues, chunkIndex));\n }\n\n return chunks;\n}\n\n/**\n * Chunks text according to the specified strategy.\n *\n * @param text - The text to chunk\n * @param strategy - The chunking strategy to use\n * @returns Array of text chunks\n */\nexport function chunkText(text: string, strategy: ChunkingStrategy): TextChunk[] {\n switch (strategy.type) {\n case \"token\": {\n return chunkByTokens(text, strategy.maxTokens, strategy.overlap ?? 0);\n }\n default: {\n const exhaustiveCheck: never = strategy as never;\n throw new Error(`Unsupported chunking strategy: ${exhaustiveCheck}`);\n }\n }\n}\n","import { getMuxSigningContextFromEnv, signUrl } from \"../lib/url-signing\";\n\nexport interface ThumbnailOptions {\n /** Interval between thumbnails in seconds (default: 10) */\n interval?: number;\n /** Width of the thumbnail in pixels (default: 640) */\n width?: number;\n /** Flag for whether or not to use signed playback IDs (default: false) */\n shouldSign?: boolean;\n}\n\n/**\n * Generates thumbnail URLs at regular intervals based on video duration.\n * If shouldSign is true, the URLs will be signed with tokens using credentials from environment variables.\n *\n * @param playbackId - The Mux playback ID\n * @param duration - Video duration in seconds\n * @param options - Thumbnail generation options\n * @returns Array of thumbnail URLs (signed if shouldSign is true)\n */\nexport async function getThumbnailUrls(\n playbackId: string,\n duration: number,\n options: ThumbnailOptions = {},\n): Promise<string[]> {\n \"use step\";\n const { interval = 10, width = 640, shouldSign = false } = options;\n const timestamps: number[] = [];\n\n if (duration <= 50) {\n const spacing = duration / 6;\n for (let i = 1; i <= 5; i++) {\n timestamps.push(Math.round(i * spacing));\n }\n } else {\n for (let time = 0; time < duration; time += interval) {\n timestamps.push(time);\n }\n }\n\n const baseUrl = `https://image.mux.com/${playbackId}/thumbnail.png`;\n\n const urlPromises = timestamps.map(async (time) => {\n if (shouldSign) {\n // NOTE: this assumes you have already validated the signing context elsewhere\n const signingContext = getMuxSigningContextFromEnv();\n return signUrl(baseUrl, playbackId, signingContext!, \"thumbnail\", { time, width });\n }\n\n return `${baseUrl}?time=${time}&width=${width}`;\n });\n\n return Promise.all(urlPromises);\n}\n","import { getApiKeyFromEnv } from \"../lib/client-factory\";\nimport type { ImageDownloadOptions } from \"../lib/image-download\";\nimport { downloadImagesAsBase64 } from \"../lib/image-download\";\nimport { getPlaybackIdForAsset } from \"../lib/mux-assets\";\nimport { getMuxSigningContextFromEnv } from \"../lib/url-signing\";\nimport { getThumbnailUrls } from \"../primitives/thumbnails\";\nimport type { ImageSubmissionMode, MuxAIOptions } from \"../types\";\n\nimport type { Buffer } from \"node:buffer\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Per-thumbnail moderation result returned from `getModerationScores`. */\nexport interface ThumbnailModerationScore {\n url: string;\n sexual: number;\n violence: number;\n error: boolean;\n}\n\n/** Aggregated moderation payload returned from `getModerationScores`. */\nexport interface ModerationResult {\n assetId: string;\n thumbnailScores: ThumbnailModerationScore[];\n maxScores: {\n sexual: number;\n violence: number;\n };\n exceedsThreshold: boolean;\n thresholds: {\n sexual: number;\n violence: number;\n };\n}\n\n/** Provider list accepted by `getModerationScores`. */\nexport type ModerationProvider = \"openai\" | \"hive\";\n\nexport type HiveModerationSource =\n | { kind: \"url\"; value: string } |\n { kind: \"file\"; buffer: Buffer; contentType: string };\n\nexport interface HiveModerationOutput {\n classes?: Array<{\n class: string;\n score: number;\n }>;\n}\n\n/** Configuration accepted by `getModerationScores`. */\nexport interface ModerationOptions extends MuxAIOptions {\n /** Provider used for moderation (defaults to 'openai'). */\n provider?: ModerationProvider;\n /** OpenAI moderation model identifier (defaults to 'omni-moderation-latest'). */\n model?: string;\n /** Override the default sexual/violence thresholds (0-1). */\n thresholds?: {\n sexual?: number;\n violence?: number;\n };\n /** Interval between storyboard thumbnails in seconds (defaults to 10). */\n thumbnailInterval?: number;\n /** Width of storyboard thumbnails in pixels (defaults to 640). */\n thumbnailWidth?: number;\n /** Max concurrent moderation requests (defaults to 5). */\n maxConcurrent?: number;\n /** Transport used for thumbnails (defaults to 'url'). */\n imageSubmissionMode?: ImageSubmissionMode;\n /** Download tuning used when `imageSubmissionMode` === 'base64'. */\n imageDownloadOptions?: ImageDownloadOptions;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst DEFAULT_THRESHOLDS = {\n sexual: 0.7,\n violence: 0.8,\n};\n\nconst DEFAULT_PROVIDER = \"openai\";\n\nconst HIVE_ENDPOINT = \"https://api.thehive.ai/api/v2/task/sync\";\nconst HIVE_SEXUAL_CATEGORIES = [\n \"general_nsfw\",\n \"general_suggestive\",\n \"yes_sexual_activity\",\n \"female_underwear\",\n \"male_underwear\",\n \"bra\",\n \"panties\",\n \"sex_toys\",\n \"nudity_female\",\n \"nudity_male\",\n \"cleavage\",\n \"swimwear\",\n];\n\nconst HIVE_VIOLENCE_CATEGORIES = [\n \"gun_in_hand\",\n \"gun_not_in_hand\",\n \"animated_gun\",\n \"knife_in_hand\",\n \"knife_not_in_hand\",\n \"culinary_knife_not_in_hand\",\n \"culinary_knife_in_hand\",\n \"very_bloody\",\n \"a_little_bloody\",\n \"other_blood\",\n \"hanging\",\n \"noose\",\n \"human_corpse\",\n \"animated_corpse\",\n \"emaciated_body\",\n \"self_harm\",\n \"animal_abuse\",\n \"fights\",\n \"garm_death_injury_or_military_conflict\",\n];\n\nasync function processConcurrently<T>(\n items: any[],\n processor: (item: any) => Promise<T>,\n maxConcurrent: number = 5,\n): Promise<T[]> {\n \"use step\";\n const results: T[] = [];\n\n for (let i = 0; i < items.length; i += maxConcurrent) {\n const batch = items.slice(i, i + maxConcurrent);\n const batchPromises = batch.map(processor);\n const batchResults = await Promise.all(batchPromises);\n results.push(...batchResults);\n }\n\n return results;\n}\n\nasync function requestOpenAIModeration(\n imageUrls: string[],\n model: string,\n maxConcurrent: number = 5,\n submissionMode: \"url\" | \"base64\" = \"url\",\n downloadOptions?: ImageDownloadOptions,\n): Promise<ThumbnailModerationScore[]> {\n \"use step\";\n const targetUrls =\n submissionMode === \"base64\" ?\n (await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent)).map(\n img => ({ url: img.url, image: img.base64Data, model }),\n ) :\n imageUrls.map(url => ({ url, image: url, model }));\n\n const moderate = async (entry: { url: string; image: string; model: string }): Promise<ThumbnailModerationScore> => {\n \"use step\";\n const apiKey = getApiKeyFromEnv(\"openai\");\n try {\n const res = await fetch(\"https://api.openai.com/v1/moderations\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: entry.model,\n input: [\n {\n type: \"image_url\",\n image_url: {\n url: entry.image,\n },\n },\n ],\n }),\n });\n\n const json: any = await res.json();\n if (!res.ok) {\n throw new Error(\n `OpenAI moderation error: ${res.status} ${res.statusText} - ${JSON.stringify(json)}`,\n );\n }\n\n const categoryScores = json.results?.[0]?.category_scores || {};\n\n return {\n url: entry.url,\n sexual: categoryScores.sexual || 0,\n violence: categoryScores.violence || 0,\n error: false,\n };\n } catch (error) {\n console.error(\"OpenAI moderation failed:\", error);\n return {\n url: entry.url,\n sexual: 0,\n violence: 0,\n error: true,\n };\n }\n };\n\n return processConcurrently(targetUrls, moderate, maxConcurrent);\n}\n\nfunction getHiveCategoryScores(\n classes: NonNullable<HiveModerationOutput[\"classes\"]>,\n categoryNames: string[],\n): number {\n const scoreMap = Object.fromEntries(\n classes.map(c => [c.class, c.score]),\n );\n const scores = categoryNames.map(category => scoreMap[category] || 0);\n return Math.max(...scores, 0);\n}\n\nasync function requestHiveModeration(\n imageUrls: string[],\n maxConcurrent: number = 5,\n submissionMode: \"url\" | \"base64\" = \"url\",\n downloadOptions?: ImageDownloadOptions,\n): Promise<ThumbnailModerationScore[]> {\n \"use step\";\n const targets: Array<{ url: string; source: HiveModerationSource }> =\n submissionMode === \"base64\" ?\n (await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent)).map(img => ({\n url: img.url,\n source: {\n kind: \"file\",\n buffer: img.buffer,\n contentType: img.contentType,\n },\n })) :\n imageUrls.map(url => ({\n url,\n source: { kind: \"url\", value: url },\n }));\n\n const moderate = async (entry: { url: string; source: HiveModerationSource }): Promise<ThumbnailModerationScore> => {\n \"use step\";\n const apiKey = getApiKeyFromEnv(\"hive\");\n try {\n const formData = new FormData();\n\n if (entry.source.kind === \"url\") {\n formData.append(\"url\", entry.source.value);\n } else {\n const extension = entry.source.contentType.split(\"/\")[1] || \"jpg\";\n const blob = new Blob([entry.source.buffer], {\n type: entry.source.contentType,\n });\n formData.append(\"media\", blob, `thumbnail.${extension}`);\n }\n\n const res = await fetch(HIVE_ENDPOINT, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n Authorization: `Token ${apiKey}`,\n },\n body: formData,\n });\n\n const json: any = await res.json().catch(() => undefined);\n if (!res.ok) {\n throw new Error(\n `Hive moderation error: ${res.status} ${res.statusText} - ${JSON.stringify(json)}`,\n );\n }\n\n // Extract scores from Hive response\n // Hive returns scores in status[0].response.output[0].classes as array of {class, score}\n const classes = json?.status?.[0]?.response?.output?.[0]?.classes || [];\n\n return {\n url: entry.url,\n sexual: getHiveCategoryScores(classes, HIVE_SEXUAL_CATEGORIES),\n violence: getHiveCategoryScores(classes, HIVE_VIOLENCE_CATEGORIES),\n error: false,\n };\n } catch (error) {\n console.error(\"Hive moderation failed:\", error);\n return {\n url: entry.url,\n sexual: 0,\n violence: 0,\n error: true,\n };\n }\n };\n\n return processConcurrently(targets, moderate, maxConcurrent);\n}\n\n/**\n * Moderate a Mux asset's thumbnails.\n * - provider 'openai' uses OpenAI's hosted moderation endpoint (requires OPENAI_API_KEY)\n */\nexport async function getModerationScores(\n assetId: string,\n options: ModerationOptions = {},\n): Promise<ModerationResult> {\n \"use workflow\";\n const {\n provider = DEFAULT_PROVIDER,\n model = provider === \"openai\" ? \"omni-moderation-latest\" : undefined,\n thresholds = DEFAULT_THRESHOLDS,\n thumbnailInterval = 10,\n thumbnailWidth = 640,\n maxConcurrent = 5,\n imageSubmissionMode = \"url\",\n imageDownloadOptions,\n } = options;\n\n // Fetch asset data and playback ID from Mux via helper\n const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n const duration = asset.duration || 0;\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Generate thumbnail URLs (signed if needed)\n const thumbnailUrls = await getThumbnailUrls(playbackId, duration, {\n interval: thumbnailInterval,\n width: thumbnailWidth,\n shouldSign: policy === \"signed\",\n });\n\n let thumbnailScores: ThumbnailModerationScore[];\n\n if (provider === \"openai\") {\n thumbnailScores = await requestOpenAIModeration(\n thumbnailUrls,\n model || \"omni-moderation-latest\",\n maxConcurrent,\n imageSubmissionMode,\n imageDownloadOptions,\n );\n } else if (provider === \"hive\") {\n thumbnailScores = await requestHiveModeration(\n thumbnailUrls,\n maxConcurrent,\n imageSubmissionMode,\n imageDownloadOptions,\n );\n } else {\n throw new Error(`Unsupported moderation provider: ${provider}`);\n }\n\n // Find highest scores across all thumbnails\n const maxSexual = Math.max(...thumbnailScores.map(s => s.sexual));\n const maxViolence = Math.max(...thumbnailScores.map(s => s.violence));\n\n const finalThresholds = { ...DEFAULT_THRESHOLDS, ...thresholds };\n\n return {\n assetId,\n thumbnailScores,\n maxScores: {\n sexual: maxSexual,\n violence: maxViolence,\n },\n exceedsThreshold: maxSexual > finalThresholds.sexual || maxViolence > finalThresholds.violence,\n thresholds: finalThresholds,\n };\n}\n","import { generateObject } from \"ai\";\nimport dedent from \"dedent\";\nimport { z } from \"zod\";\n\nimport { createWorkflowConfig } from \"../lib/client-factory\";\nimport type { ImageDownloadOptions } from \"../lib/image-download\";\nimport { downloadImageAsBase64 } from \"../lib/image-download\";\nimport { getPlaybackIdForAsset } from \"../lib/mux-assets\";\nimport type {\n PromptOverrides,\n} from \"../lib/prompt-builder\";\nimport {\n createPromptBuilder,\n createToneSection,\n createTranscriptSection,\n} from \"../lib/prompt-builder\";\nimport { createLanguageModelFromConfig } from \"../lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"../lib/providers\";\nimport { withRetry } from \"../lib/retry\";\nimport { getMuxSigningContextFromEnv } from \"../lib/url-signing\";\nimport { getStoryboardUrl } from \"../primitives/storyboards\";\nimport { fetchTranscriptForAsset } from \"../primitives/transcripts\";\nimport type { ImageSubmissionMode, MuxAIOptions, TokenUsage, ToneType } from \"../types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const SUMMARY_KEYWORD_LIMIT = 10;\n\nexport const summarySchema = z.object({\n keywords: z.array(z.string()),\n title: z.string(),\n description: z.string(),\n});\n\nexport type SummaryType = z.infer<typeof summarySchema>;\n\n/** Structured return payload for `getSummaryAndTags`. */\nexport interface SummaryAndTagsResult {\n /** Asset ID passed into the workflow. */\n assetId: string;\n /** Short headline generated from the storyboard. */\n title: string;\n /** Longer description of the detected content. */\n description: string;\n /** Up to 10 keywords extracted by the model. */\n tags: string[];\n /** Storyboard image URL that was analyzed. */\n storyboardUrl: string;\n /** Token usage from the AI provider (for efficiency/cost analysis). */\n usage?: TokenUsage;\n /** Raw transcript text used for analysis (when includeTranscript is true). */\n transcriptText?: string;\n}\n\n/**\n * Sections of the summarization user prompt that can be overridden.\n * Use these to customize the AI's behavior for your specific use case.\n */\nexport type SummarizationPromptSections =\n | \"task\" |\n \"title\" |\n \"description\" |\n \"keywords\" |\n \"qualityGuidelines\";\n\n/**\n * Override specific sections of the summarization prompt.\n * Each key corresponds to a section that can be customized.\n *\n * @example\n * ```typescript\n * const result = await getSummaryAndTags(assetId, {\n * promptOverrides: {\n * task: 'Generate SEO-optimized metadata for this product video.',\n * title: 'Create a click-worthy title under 60 characters for YouTube.',\n * },\n * });\n * ```\n */\nexport type SummarizationPromptOverrides = PromptOverrides<SummarizationPromptSections>;\n\n/** Configuration accepted by `getSummaryAndTags`. */\nexport interface SummarizationOptions extends MuxAIOptions {\n /** AI provider to run (defaults to 'openai'). */\n provider?: SupportedProvider;\n /** Provider-specific chat model identifier. */\n model?: ModelIdByProvider[SupportedProvider];\n /** Prompt tone shim applied to the system instruction (defaults to 'neutral'). */\n tone?: ToneType;\n /** Fetch the transcript and send it alongside the storyboard (defaults to true). */\n includeTranscript?: boolean;\n /** Strip timestamps/markup from transcripts before including them (defaults to true). */\n cleanTranscript?: boolean;\n /** How storyboard frames should be delivered to the provider (defaults to 'url'). */\n imageSubmissionMode?: ImageSubmissionMode;\n /** Fine-tune storyboard downloads when `imageSubmissionMode` === 'base64'. */\n imageDownloadOptions?: ImageDownloadOptions;\n /**\n * Override specific sections of the user prompt.\n * Useful for customizing the AI's output for specific use cases (SEO, social media, etc.)\n */\n promptOverrides?: SummarizationPromptOverrides;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Prompts\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst VALID_TONES = [\"neutral\", \"playful\", \"professional\"] as const;\n\nconst TONE_INSTRUCTIONS: Record<ToneType, string> = {\n neutral: \"Provide a clear, straightforward analysis.\",\n playful: \"Channel your inner diva! Answer with maximum sass, wit, and playful attitude. Don't hold back - be cheeky, clever, and delightfully snarky. Make it pop!\",\n professional: \"Provide a professional, executive-level analysis suitable for business reporting.\",\n};\n\n/**\n * Prompt builder for the summarization user prompt.\n * Sections can be individually overridden via `promptOverrides` in SummarizationOptions.\n */\nconst summarizationPromptBuilder = createPromptBuilder<SummarizationPromptSections>({\n template: {\n task: {\n tag: \"task\",\n content: \"Analyze the storyboard frames and generate metadata that captures the essence of the video content.\",\n },\n title: {\n tag: \"title_requirements\",\n content: dedent`\n A short, compelling headline that immediately communicates the subject or action.\n Aim for brevity - typically under 10 words. Think of how a news headline or video card title would read.\n Start with the primary subject, action, or topic - never begin with \"A video of\" or similar phrasing.\n Use active, specific language.`,\n },\n description: {\n tag: \"description_requirements\",\n content: dedent`\n A concise summary (2-4 sentences) that describes what happens across the video.\n Cover the main subjects, actions, setting, and any notable progression visible across frames.\n Write in present tense. Be specific about observable details rather than making assumptions.\n If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`,\n },\n keywords: {\n tag: \"keywords_requirements\",\n content: dedent`\n Specific, searchable terms (up to 10) that capture:\n - Primary subjects (people, animals, objects)\n - Actions and activities being performed\n - Setting and environment\n - Notable objects or tools\n - Style or genre (if applicable)\n Prefer concrete nouns and action verbs over abstract concepts.\n Use lowercase. Avoid redundant or overly generic terms like \"video\" or \"content\".`,\n },\n qualityGuidelines: {\n tag: \"quality_guidelines\",\n content: dedent`\n - Examine all frames to understand the full context and progression\n - Be precise: \"golden retriever\" is better than \"dog\" when identifiable\n - Capture the narrative: what begins, develops, and concludes\n - Balance brevity with informativeness`,\n },\n },\n sectionOrder: [\"task\", \"title\", \"description\", \"keywords\", \"qualityGuidelines\"],\n});\n\nconst SYSTEM_PROMPT = dedent`\n <role>\n You are a video content analyst specializing in storyboard interpretation and multimodal analysis.\n </role>\n\n <context>\n You receive storyboard images containing multiple sequential frames extracted from a video.\n These frames are arranged in a grid and represent the visual progression of the content over time.\n Read frames left-to-right, top-to-bottom to understand the temporal sequence.\n </context>\n\n <transcript_guidance>\n When a transcript is provided alongside the storyboard:\n - Use it to understand spoken content, dialogue, narration, and audio context\n - Correlate transcript content with visual frames to build a complete picture\n - Extract key terminology, names, and specific language used by speakers\n - Let the transcript inform keyword selection, especially for topics not visually obvious\n - Prioritize visual content for the description, but enrich it with transcript insights\n - If transcript and visuals conflict, trust the visual evidence\n </transcript_guidance>\n\n <capabilities>\n - Extract meaning from visual sequences\n - Identify subjects, actions, settings, and narrative arcs\n - Generate accurate, searchable metadata\n - Synthesize visual and transcript information when provided\n </capabilities>\n\n <constraints>\n - Only describe what is clearly observable in the frames or explicitly stated in the transcript\n - Do not fabricate details or make unsupported assumptions\n - Return structured data matching the requested schema\n </constraints>\n\n <tone_guidance>\n Pay special attention to the <tone> section and lean heavily into those instructions.\n Adapt your entire analysis and writing style to match the specified tone - this should influence\n your word choice, personality, formality level, and overall presentation of the content.\n The tone instructions are not suggestions but core requirements for how you should express yourself.\n </tone_guidance>\n\n <language_guidelines>\n AVOID these meta-descriptive phrases that reference the medium rather than the content:\n - \"The image shows...\" / \"The storyboard shows...\"\n - \"In this video...\" / \"This video features...\"\n - \"The frames depict...\" / \"The footage shows...\"\n - \"We can see...\" / \"You can see...\"\n - \"The clip shows...\" / \"The scene shows...\"\n\n INSTEAD, describe the content directly:\n - BAD: \"The video shows a chef preparing a meal\"\n - GOOD: \"A chef prepares a meal in a professional kitchen\"\n\n Write as if describing reality, not describing a recording of reality.\n </language_guidelines>`;\n\ninterface UserPromptContext {\n tone: ToneType;\n transcriptText?: string;\n isCleanTranscript?: boolean;\n promptOverrides?: SummarizationPromptOverrides;\n}\n\nfunction buildUserPrompt({\n tone,\n transcriptText,\n isCleanTranscript = true,\n promptOverrides,\n}: UserPromptContext): string {\n // Build dynamic context sections\n const contextSections = [createToneSection(TONE_INSTRUCTIONS[tone])];\n\n if (transcriptText) {\n const format = isCleanTranscript ? \"plain text\" : \"WebVTT\";\n contextSections.push(createTranscriptSection(transcriptText, format));\n }\n\n return summarizationPromptBuilder.buildWithContext(promptOverrides, contextSections);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface AnalysisResponse {\n result: SummaryType;\n usage: TokenUsage;\n}\n\nasync function analyzeStoryboard(\n imageDataUrl: string,\n provider: SupportedProvider,\n modelId: string,\n userPrompt: string,\n systemPrompt: string,\n): Promise<AnalysisResponse> {\n \"use step\";\n const model = createLanguageModelFromConfig(provider, modelId);\n\n const response = await generateObject({\n model,\n schema: summarySchema,\n messages: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n {\n role: \"user\",\n content: [\n { type: \"text\", text: userPrompt },\n { type: \"image\", image: imageDataUrl },\n ],\n },\n ],\n });\n\n return {\n result: response.object,\n usage: {\n inputTokens: response.usage.inputTokens,\n outputTokens: response.usage.outputTokens,\n totalTokens: response.usage.totalTokens,\n reasoningTokens: response.usage.reasoningTokens,\n cachedInputTokens: response.usage.cachedInputTokens,\n },\n };\n}\n\nfunction normalizeKeywords(keywords?: string[]): string[] {\n if (!Array.isArray(keywords) || keywords.length === 0) {\n return [];\n }\n\n const uniqueLowercase = new Set<string>();\n const normalized: string[] = [];\n\n for (const keyword of keywords) {\n const trimmed = keyword?.trim();\n if (!trimmed) {\n continue;\n }\n\n const lower = trimmed.toLowerCase();\n if (uniqueLowercase.has(lower)) {\n continue;\n }\n\n uniqueLowercase.add(lower);\n normalized.push(trimmed);\n\n if (normalized.length === SUMMARY_KEYWORD_LIMIT) {\n break;\n }\n }\n\n return normalized;\n}\n\nexport async function getSummaryAndTags(\n assetId: string,\n options?: SummarizationOptions,\n): Promise<SummaryAndTagsResult> {\n \"use workflow\";\n const {\n provider = \"openai\",\n model,\n tone = \"neutral\",\n includeTranscript = true,\n cleanTranscript = true,\n imageSubmissionMode = \"url\",\n imageDownloadOptions,\n abortSignal: _abortSignal,\n promptOverrides,\n } = options ?? {};\n\n // Validate tone parameter\n if (!VALID_TONES.includes(tone)) {\n throw new Error(\n `Invalid tone \"${tone}\". Valid tones are: ${VALID_TONES.join(\", \")}`,\n );\n }\n\n // Validate credentials and resolve language model\n const config = await createWorkflowConfig(\n { ...options, model },\n provider as SupportedProvider,\n );\n\n // Fetch asset data from Mux and grab playback/transcript details\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n const transcriptText =\n includeTranscript ?\n (await fetchTranscriptForAsset(assetData, playbackId, {\n cleanTranscript,\n shouldSign: policy === \"signed\",\n })).transcriptText :\n \"\";\n\n // Build the user prompt with all context and any overrides\n const userPrompt = buildUserPrompt({\n tone,\n transcriptText,\n isCleanTranscript: cleanTranscript,\n promptOverrides,\n });\n\n // Analyze storyboard with AI provider (signed if needed)\n const imageUrl = await getStoryboardUrl(playbackId, 640, policy === \"signed\");\n\n let analysisResponse: AnalysisResponse;\n\n try {\n if (imageSubmissionMode === \"base64\") {\n const downloadResult = await downloadImageAsBase64(imageUrl, imageDownloadOptions);\n analysisResponse = await analyzeStoryboard(\n downloadResult.base64Data,\n config.provider,\n config.modelId,\n userPrompt,\n SYSTEM_PROMPT,\n );\n } else {\n // URL-based submission with retry logic\n analysisResponse = await withRetry(() => analyzeStoryboard(imageUrl, config.provider, config.modelId, userPrompt, SYSTEM_PROMPT));\n }\n } catch (error: unknown) {\n throw new Error(\n `Failed to analyze video content with ${provider}: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n\n if (!analysisResponse.result) {\n throw new Error(`Failed to analyze video content for asset ${assetId}`);\n }\n\n if (!analysisResponse.result.title) {\n throw new Error(`Failed to generate title for asset ${assetId}`);\n }\n\n if (!analysisResponse.result.description) {\n throw new Error(`Failed to generate description for asset ${assetId}`);\n }\n\n return {\n assetId,\n title: analysisResponse.result.title,\n description: analysisResponse.result.description,\n tags: normalizeKeywords(analysisResponse.result.keywords),\n storyboardUrl: imageUrl,\n usage: analysisResponse.usage,\n transcriptText: transcriptText || undefined,\n };\n}\n","import Mux from \"@mux/mux-node\";\n\nimport env from \"../env\";\nimport { getApiKeyFromEnv, getMuxCredentialsFromEnv } from \"../lib/client-factory\";\nimport { getLanguageCodePair, toISO639_1, toISO639_3 } from \"../lib/language-codes\";\nimport type { LanguageCodePair, SupportedISO639_1 } from \"../lib/language-codes\";\nimport { getPlaybackIdForAsset } from \"../lib/mux-assets\";\nimport { getMuxSigningContextFromEnv, signUrl } from \"../lib/url-signing\";\nimport type { MuxAIOptions } from \"../types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Output returned from `translateAudio`. */\nexport interface AudioTranslationResult {\n assetId: string;\n /** Target language code (ISO 639-1 two-letter format). */\n targetLanguageCode: SupportedISO639_1;\n /**\n * Target language codes in both ISO 639-1 (2-letter) and ISO 639-3 (3-letter) formats.\n * Use `iso639_1` for browser players (BCP-47 compliant) and `iso639_3` for ElevenLabs API.\n */\n targetLanguage: LanguageCodePair;\n dubbingId: string;\n uploadedTrackId?: string;\n presignedUrl?: string;\n}\n\n/** Configuration accepted by `translateAudio`. */\nexport interface AudioTranslationOptions extends MuxAIOptions {\n /** Audio dubbing provider (currently ElevenLabs only). */\n provider?: \"elevenlabs\";\n /** Number of speakers supplied to ElevenLabs (0 = auto-detect, default). */\n numSpeakers?: number;\n /** Optional override for the S3-compatible endpoint used for uploads. */\n s3Endpoint?: string;\n /** S3 region (defaults to env.S3_REGION or 'auto'). */\n s3Region?: string;\n /** Bucket that will store dubbed audio files. */\n s3Bucket?: string;\n /**\n * When true (default) the dubbed audio file is uploaded to the configured\n * bucket and attached to the Mux asset.\n */\n uploadToMux?: boolean;\n /** Override for env.ELEVENLABS_API_KEY. */\n elevenLabsApiKey?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst STATIC_RENDITION_POLL_INTERVAL_MS = 5000;\nconst STATIC_RENDITION_MAX_ATTEMPTS = 36; // ~3 minutes\n\nasync function sleep(ms: number): Promise<void> {\n \"use step\";\n await new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction getReadyAudioStaticRendition(asset: any) {\n const files = asset.static_renditions?.files as any[] | undefined;\n if (!files || files.length === 0) {\n return undefined;\n }\n\n return files.find(\n rendition => rendition.name === \"audio.m4a\" && rendition.status === \"ready\",\n );\n}\n\nconst hasReadyAudioStaticRendition = (asset: any) => Boolean(getReadyAudioStaticRendition(asset));\n\nasync function requestStaticRenditionCreation(assetId: string) {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n try {\n await mux.video.assets.createStaticRendition(assetId, {\n resolution: \"audio-only\",\n });\n } catch (error: any) {\n const statusCode = error?.status ?? error?.statusCode;\n const messages: string[] | undefined = error?.error?.messages;\n const alreadyDefined =\n messages?.some(message => message.toLowerCase().includes(\"already defined\")) ??\n error?.message?.toLowerCase().includes(\"already defined\");\n\n if (statusCode === 409 || alreadyDefined) {\n return;\n }\n\n const message = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Failed to request static rendition from Mux: ${message}`);\n }\n}\n\nasync function waitForAudioStaticRendition({\n assetId,\n initialAsset,\n}: {\n assetId: string;\n initialAsset: any;\n}): Promise<any> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n let currentAsset = initialAsset;\n\n if (hasReadyAudioStaticRendition(currentAsset)) {\n return currentAsset;\n }\n\n const status = currentAsset.static_renditions?.status ?? \"not_requested\";\n\n if (status === \"not_requested\" || status === undefined) {\n await requestStaticRenditionCreation(assetId);\n } else if (status === \"errored\") {\n await requestStaticRenditionCreation(assetId);\n } else {\n console.warn(`ℹ️ Static rendition already ${status}. Waiting for it to finish...`);\n }\n\n for (let attempt = 1; attempt <= STATIC_RENDITION_MAX_ATTEMPTS; attempt++) {\n await sleep(STATIC_RENDITION_POLL_INTERVAL_MS);\n currentAsset = await mux.video.assets.retrieve(assetId);\n\n if (hasReadyAudioStaticRendition(currentAsset)) {\n return currentAsset;\n }\n\n const currentStatus = currentAsset.static_renditions?.status || \"unknown\";\n console.warn(\n `⌛ Waiting for static rendition (attempt ${attempt}/${STATIC_RENDITION_MAX_ATTEMPTS}) → ${currentStatus}`,\n );\n\n if (currentStatus === \"errored\") {\n throw new Error(\n \"Mux failed to create the static rendition for this asset. Please check the asset in the Mux dashboard.\",\n );\n }\n }\n\n throw new Error(\n \"Timed out waiting for the static rendition to become ready. Please try again in a moment.\",\n );\n}\n\nasync function fetchAudioFromMux(audioUrl: string): Promise<ArrayBuffer> {\n \"use step\";\n\n const audioResponse = await fetch(audioUrl);\n if (!audioResponse.ok) {\n throw new Error(`Failed to fetch audio file: ${audioResponse.statusText}`);\n }\n\n return audioResponse.arrayBuffer();\n}\n\nasync function createElevenLabsDubbingJob({\n audioBuffer,\n assetId,\n elevenLabsLangCode,\n numSpeakers,\n}: {\n audioBuffer: ArrayBuffer;\n assetId: string;\n elevenLabsLangCode: string;\n numSpeakers: number;\n}): Promise<string> {\n \"use step\";\n const elevenLabsApiKey = getApiKeyFromEnv(\"elevenlabs\");\n\n const audioBlob = new Blob([audioBuffer], { type: \"audio/mp4\" });\n\n const formData = new FormData();\n formData.append(\"file\", audioBlob);\n formData.append(\"target_lang\", elevenLabsLangCode);\n formData.append(\"num_speakers\", numSpeakers.toString());\n formData.append(\"name\", `Mux Asset ${assetId} - auto to ${elevenLabsLangCode}`);\n\n const dubbingResponse = await fetch(\"https://api.elevenlabs.io/v1/dubbing\", {\n method: \"POST\",\n headers: {\n \"xi-api-key\": elevenLabsApiKey,\n },\n body: formData,\n });\n\n if (!dubbingResponse.ok) {\n throw new Error(`ElevenLabs API error: ${dubbingResponse.statusText}`);\n }\n\n const dubbingData = await dubbingResponse.json() as any;\n return dubbingData.dubbing_id;\n}\n\nasync function checkElevenLabsDubbingStatus({\n dubbingId,\n}: {\n dubbingId: string;\n}): Promise<{ status: string; targetLanguages: string[] }> {\n \"use step\";\n const elevenLabsApiKey = getApiKeyFromEnv(\"elevenlabs\");\n\n const statusResponse = await fetch(`https://api.elevenlabs.io/v1/dubbing/${dubbingId}`, {\n headers: {\n \"xi-api-key\": elevenLabsApiKey,\n },\n });\n\n if (!statusResponse.ok) {\n throw new Error(`Status check failed: ${statusResponse.statusText}`);\n }\n\n const statusData = await statusResponse.json() as any;\n return {\n status: statusData.status,\n targetLanguages: statusData.target_languages ?? [],\n };\n}\n\nasync function downloadDubbedAudioFromElevenLabs({\n dubbingId,\n languageCode,\n}: {\n dubbingId: string;\n languageCode: string;\n}): Promise<ArrayBuffer> {\n \"use step\";\n const elevenLabsApiKey = getApiKeyFromEnv(\"elevenlabs\");\n\n const audioUrl = `https://api.elevenlabs.io/v1/dubbing/${dubbingId}/audio/${languageCode}`;\n const audioResponse = await fetch(audioUrl, {\n headers: {\n \"xi-api-key\": elevenLabsApiKey,\n },\n });\n\n if (!audioResponse.ok) {\n throw new Error(`Failed to fetch dubbed audio: ${audioResponse.statusText}`);\n }\n\n return audioResponse.arrayBuffer();\n}\n\nasync function uploadDubbedAudioToS3({\n dubbedAudioBuffer,\n assetId,\n toLanguageCode,\n s3Endpoint,\n s3Region,\n s3Bucket,\n}: {\n dubbedAudioBuffer: ArrayBuffer;\n assetId: string;\n toLanguageCode: string;\n s3Endpoint: string;\n s3Region: string;\n s3Bucket: string;\n}): Promise<string> {\n \"use step\";\n\n const { S3Client, GetObjectCommand } = await import(\"@aws-sdk/client-s3\");\n const { Upload } = await import(\"@aws-sdk/lib-storage\");\n const { getSignedUrl } = await import(\"@aws-sdk/s3-request-presigner\");\n\n // asserting exists. already validated (See: translateAudio())\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID!;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY!;\n\n const s3Client = new S3Client({\n region: s3Region,\n endpoint: s3Endpoint,\n credentials: {\n accessKeyId: s3AccessKeyId,\n secretAccessKey: s3SecretAccessKey,\n },\n forcePathStyle: true,\n });\n\n // Create unique key for the audio file\n const audioKey = `audio-translations/${assetId}/auto-to-${toLanguageCode}-${Date.now()}.m4a`;\n\n // Upload audio to S3\n const upload = new Upload({\n client: s3Client,\n params: {\n Bucket: s3Bucket,\n Key: audioKey,\n Body: new Uint8Array(dubbedAudioBuffer),\n ContentType: \"audio/mp4\",\n },\n });\n\n await upload.done();\n\n // Generate presigned URL (valid for 1 hour)\n const getObjectCommand = new GetObjectCommand({\n Bucket: s3Bucket,\n Key: audioKey,\n });\n\n const presignedUrl = await getSignedUrl(s3Client, getObjectCommand, {\n expiresIn: 3600, // 1 hour\n });\n\n console.warn(`✅ Audio uploaded successfully to: ${audioKey}`);\n console.warn(`🔗 Generated presigned URL (expires in 1 hour)`);\n\n return presignedUrl;\n}\n\nasync function createAudioTrackOnMux(\n assetId: string,\n languageCode: string,\n presignedUrl: string,\n): Promise<string> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n const languageName = new Intl.DisplayNames([\"en\"], { type: \"language\" }).of(languageCode) || languageCode.toUpperCase();\n const trackName = `${languageName} (auto-dubbed)`;\n\n const trackResponse = await mux.video.assets.createTrack(assetId, {\n type: \"audio\",\n language_code: languageCode,\n name: trackName,\n url: presignedUrl,\n });\n\n if (!trackResponse.id) {\n throw new Error(\"Failed to create audio track: no track ID returned from Mux\");\n }\n\n return trackResponse.id;\n}\n\nexport async function translateAudio(\n assetId: string,\n toLanguageCode: string,\n options: AudioTranslationOptions = {},\n): Promise<AudioTranslationResult> {\n \"use workflow\";\n // Uses the default audio track on your asset, language is auto-detected by ElevenLabs\n const {\n provider = \"elevenlabs\",\n numSpeakers = 0, // 0 = auto-detect\n elevenLabsApiKey,\n uploadToMux = true,\n } = options;\n\n if (provider !== \"elevenlabs\") {\n throw new Error(\"Only ElevenLabs provider is currently supported for audio translation\");\n }\n\n const elevenLabsKey = elevenLabsApiKey ?? env.ELEVENLABS_API_KEY;\n\n // S3 configuration\n const s3Endpoint = options.s3Endpoint ?? env.S3_ENDPOINT;\n const s3Region = options.s3Region ?? env.S3_REGION ?? \"auto\";\n const s3Bucket = options.s3Bucket ?? env.S3_BUCKET;\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY;\n\n if (!elevenLabsKey) {\n throw new Error(\"ElevenLabs API key is required. Provide elevenLabsApiKey in options or set ELEVENLABS_API_KEY environment variable.\");\n }\n\n if (uploadToMux && (!s3Endpoint || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey)) {\n throw new Error(\"S3 configuration is required for uploading to Mux. Provide s3Endpoint, s3Bucket, s3AccessKeyId, and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.\");\n }\n\n // Fetch asset data and playback ID from Mux\n const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Check for audio-only static rendition\n\n let currentAsset = initialAsset;\n if (!hasReadyAudioStaticRendition(currentAsset)) {\n console.warn(\"❌ No ready audio static rendition found. Requesting one now...\");\n currentAsset = await waitForAudioStaticRendition({\n assetId,\n initialAsset: currentAsset,\n });\n }\n\n const audioRendition = getReadyAudioStaticRendition(currentAsset);\n\n if (!audioRendition) {\n throw new Error(\n \"Unable to obtain an audio-only static rendition for this asset. Please verify static renditions are enabled in Mux.\",\n );\n }\n\n // Build audio URL (signed if needed)\n let audioUrl = `https://stream.mux.com/${playbackId}/audio.m4a`;\n if (policy === \"signed\" && signingContext) {\n audioUrl = await signUrl(audioUrl, playbackId, signingContext, \"video\");\n }\n\n // Fetch audio from Mux\n console.warn(\"🎙️ Fetching audio from Mux...\");\n\n let audioBuffer: ArrayBuffer;\n try {\n audioBuffer = await fetchAudioFromMux(audioUrl);\n } catch (error) {\n throw new Error(`Failed to fetch audio from Mux: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Create dubbing job in ElevenLabs\n console.warn(\"🎙️ Creating dubbing job in ElevenLabs...\");\n\n // ElevenLabs uses ISO 639-3 (3-letter) codes, so normalize the input\n const elevenLabsLangCode = toISO639_3(toLanguageCode);\n console.warn(`🔍 Creating dubbing job for asset ${assetId} with language code: ${elevenLabsLangCode}`);\n\n let dubbingId: string;\n try {\n dubbingId = await createElevenLabsDubbingJob({\n audioBuffer,\n assetId,\n elevenLabsLangCode,\n numSpeakers,\n });\n console.warn(`✅ Dubbing job created with ID: ${dubbingId}`);\n } catch (error) {\n throw new Error(`Failed to create ElevenLabs dubbing job: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Poll for completion\n console.warn(\"⏳ Waiting for dubbing to complete...\");\n\n let dubbingStatus: string = \"dubbing\";\n let pollAttempts = 0;\n const maxPollAttempts = 180; // 30 minutes at 10s intervals\n let targetLanguages: string[] = [];\n\n while (dubbingStatus === \"dubbing\" && pollAttempts < maxPollAttempts) {\n await sleep(10000); // Wait 10 seconds\n pollAttempts++;\n\n try {\n const statusResult = await checkElevenLabsDubbingStatus({\n dubbingId,\n });\n dubbingStatus = statusResult.status;\n targetLanguages = statusResult.targetLanguages;\n\n if (dubbingStatus === \"failed\") {\n throw new Error(\"ElevenLabs dubbing job failed\");\n }\n } catch (error) {\n throw new Error(`Failed to check dubbing status: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n }\n\n if (dubbingStatus !== \"dubbed\") {\n throw new Error(`Dubbing job timed out or failed. Final status: ${dubbingStatus}`);\n }\n\n console.warn(\"✅ Dubbing completed successfully!\");\n\n // If uploadToMux is false, just return the dubbing info\n // Return ISO 639-1 (2-letter) code for consistency with Mux/player expectations\n if (!uploadToMux) {\n const targetLanguage = getLanguageCodePair(toLanguageCode);\n return {\n assetId,\n targetLanguageCode: targetLanguage.iso639_1 as SupportedISO639_1,\n targetLanguage,\n dubbingId,\n };\n }\n\n // Download dubbed audio from ElevenLabs\n console.warn(\"📥 Downloading dubbed audio from ElevenLabs...\");\n\n let dubbedAudioBuffer: ArrayBuffer;\n\n try {\n // Use the language code from the ElevenLabs status response\n // ElevenLabs returns target_languages array with the exact codes available for download\n const requestedLangCode = toISO639_3(toLanguageCode);\n\n // Find the matching language code from ElevenLabs response\n // First try exact match, then try case-insensitive match\n let downloadLangCode = targetLanguages.find(\n lang => lang === requestedLangCode,\n ) ?? targetLanguages.find(\n lang => lang.toLowerCase() === requestedLangCode.toLowerCase(),\n );\n\n // Fallback to first available target language if no match found\n if (!downloadLangCode && targetLanguages.length > 0) {\n downloadLangCode = targetLanguages[0];\n console.warn(`⚠️ Requested language \"${requestedLangCode}\" not found in target_languages. Using \"${downloadLangCode}\" instead.`);\n }\n\n // If still no language code, fall back to the original behavior\n if (!downloadLangCode) {\n downloadLangCode = requestedLangCode;\n console.warn(`⚠️ No target_languages available from ElevenLabs status. Using requested language code: ${requestedLangCode}`);\n }\n\n dubbedAudioBuffer = await downloadDubbedAudioFromElevenLabs({\n dubbingId,\n languageCode: downloadLangCode,\n });\n console.warn(\"✅ Dubbed audio downloaded successfully!\");\n } catch (error) {\n throw new Error(`Failed to download dubbed audio: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Upload to S3-compatible storage\n console.warn(\"📤 Uploading dubbed audio to S3-compatible storage...\");\n\n let presignedUrl: string;\n\n try {\n presignedUrl = await uploadDubbedAudioToS3({\n dubbedAudioBuffer,\n assetId,\n toLanguageCode,\n s3Endpoint: s3Endpoint!,\n s3Region,\n s3Bucket: s3Bucket!,\n });\n } catch (error) {\n throw new Error(`Failed to upload audio to S3: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Add translated audio track to Mux asset\n console.warn(\"📹 Adding dubbed audio track to Mux asset...\");\n\n let uploadedTrackId: string | undefined;\n // Mux uses ISO 639-1 (2-letter) codes for track language_code\n const muxLangCode = toISO639_1(toLanguageCode);\n\n try {\n uploadedTrackId = await createAudioTrackOnMux(assetId, muxLangCode, presignedUrl);\n const languageName = new Intl.DisplayNames([\"en\"], { type: \"language\" }).of(muxLangCode) || muxLangCode.toUpperCase();\n const trackName = `${languageName} (auto-dubbed)`;\n console.warn(`✅ Track added to Mux asset with ID: ${uploadedTrackId}`);\n console.warn(`📋 Track name: \"${trackName}\"`);\n } catch (error) {\n console.warn(`⚠️ Failed to add audio track to Mux asset: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n console.warn(\"🔗 You can manually add the track using this presigned URL:\");\n console.warn(presignedUrl);\n }\n\n const targetLanguage = getLanguageCodePair(toLanguageCode);\n return {\n assetId,\n targetLanguageCode: targetLanguage.iso639_1 as SupportedISO639_1,\n targetLanguage,\n dubbingId,\n uploadedTrackId,\n presignedUrl,\n };\n}\n","/**\n * Language Code Conversion Utilities\n *\n * Provides bidirectional mapping between:\n * - ISO 639-1 (2-letter codes) - Used by browsers, BCP-47, most video players\n * - ISO 639-3 (3-letter codes) - Used by various APIs and language processing systems\n *\n * This is essential for interoperability between different systems:\n * - Mux uses ISO 639-1 for track language codes\n * - Browser players expect BCP-47 compliant codes (based on ISO 639-1)\n * - Some APIs require ISO 639-3 (3-letter) codes\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Language Code Mapping\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Mapping from ISO 639-1 (2-letter) to ISO 639-3 (3-letter) codes.\n * Covers the most common languages used in video translation.\n *\n * Reference: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes\n */\nconst ISO639_1_TO_3 = {\n // Major world languages\n en: \"eng\", // English\n es: \"spa\", // Spanish\n fr: \"fra\", // French\n de: \"deu\", // German\n it: \"ita\", // Italian\n pt: \"por\", // Portuguese\n ru: \"rus\", // Russian\n zh: \"zho\", // Chinese\n ja: \"jpn\", // Japanese\n ko: \"kor\", // Korean\n ar: \"ara\", // Arabic\n hi: \"hin\", // Hindi\n\n // European languages\n nl: \"nld\", // Dutch\n pl: \"pol\", // Polish\n sv: \"swe\", // Swedish\n da: \"dan\", // Danish\n no: \"nor\", // Norwegian\n fi: \"fin\", // Finnish\n el: \"ell\", // Greek\n cs: \"ces\", // Czech\n hu: \"hun\", // Hungarian\n ro: \"ron\", // Romanian\n bg: \"bul\", // Bulgarian\n hr: \"hrv\", // Croatian\n sk: \"slk\", // Slovak\n sl: \"slv\", // Slovenian\n uk: \"ukr\", // Ukrainian\n tr: \"tur\", // Turkish\n\n // Asian languages\n th: \"tha\", // Thai\n vi: \"vie\", // Vietnamese\n id: \"ind\", // Indonesian\n ms: \"msa\", // Malay\n tl: \"tgl\", // Tagalog/Filipino\n\n // Other languages\n he: \"heb\", // Hebrew\n fa: \"fas\", // Persian/Farsi\n bn: \"ben\", // Bengali\n ta: \"tam\", // Tamil\n te: \"tel\", // Telugu\n mr: \"mar\", // Marathi\n gu: \"guj\", // Gujarati\n kn: \"kan\", // Kannada\n ml: \"mal\", // Malayalam\n pa: \"pan\", // Punjabi\n ur: \"urd\", // Urdu\n sw: \"swa\", // Swahili\n af: \"afr\", // Afrikaans\n ca: \"cat\", // Catalan\n eu: \"eus\", // Basque\n gl: \"glg\", // Galician\n is: \"isl\", // Icelandic\n et: \"est\", // Estonian\n lv: \"lav\", // Latvian\n lt: \"lit\", // Lithuanian\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Supported ISO 639-1 two-letter language codes.\n * These are the language codes supported for translation workflows.\n */\nexport type SupportedISO639_1 = keyof typeof ISO639_1_TO_3;\n\n/**\n * Supported ISO 639-3 three-letter language codes.\n * These are the language codes supported for translation workflows.\n */\nexport type SupportedISO639_3 = (typeof ISO639_1_TO_3)[SupportedISO639_1];\n\n/** ISO 639-1 two-letter language code (e.g., \"en\", \"fr\", \"es\") */\nexport type ISO639_1 = SupportedISO639_1 | (string & {});\n\n/** ISO 639-3 three-letter language code (e.g., \"eng\", \"fra\", \"spa\") */\nexport type ISO639_3 = SupportedISO639_3 | (string & {});\n\n/** Structured language code result containing both formats */\nexport interface LanguageCodePair {\n /** ISO 639-1 two-letter code (BCP-47 compatible) */\n iso639_1: ISO639_1;\n /** ISO 639-3 three-letter code */\n iso639_3: ISO639_3;\n}\n\n/**\n * Reverse mapping from ISO 639-3 (3-letter) to ISO 639-1 (2-letter) codes.\n * Generated from ISO639_1_TO_3 for consistency.\n */\nconst ISO639_3_TO_1 = Object.fromEntries(\n Object.entries(ISO639_1_TO_3).map(([iso1, iso3]) => [iso3, iso1]),\n) as Record<SupportedISO639_3, SupportedISO639_1>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Conversion Functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Converts an ISO 639-1 (2-letter) code to ISO 639-3 (3-letter) code.\n *\n * @param code - ISO 639-1 two-letter language code (e.g., \"en\", \"fr\")\n * @returns ISO 639-3 three-letter code, or the original if not found\n *\n * @example\n * ```typescript\n * toISO639_3(\"en\") // \"eng\"\n * toISO639_3(\"fr\") // \"fra\"\n * toISO639_3(\"ja\") // \"jpn\"\n * ```\n */\nexport function toISO639_3(code: string): ISO639_3 {\n const normalized = code.toLowerCase().trim();\n\n // If it's already a 3-letter code, return as-is\n if (normalized.length === 3) {\n return normalized;\n }\n\n return (ISO639_1_TO_3 as Record<string, string>)[normalized] ?? normalized;\n}\n\n/**\n * Converts an ISO 639-3 (3-letter) code to ISO 639-1 (2-letter) code.\n *\n * @param code - ISO 639-3 three-letter language code (e.g., \"eng\", \"fra\")\n * @returns ISO 639-1 two-letter code, or the original if not found\n *\n * @example\n * ```typescript\n * toISO639_1(\"eng\") // \"en\"\n * toISO639_1(\"fra\") // \"fr\"\n * toISO639_1(\"jpn\") // \"ja\"\n * ```\n */\nexport function toISO639_1(code: string): ISO639_1 {\n const normalized = code.toLowerCase().trim();\n\n // If it's already a 2-letter code, return as-is\n if (normalized.length === 2) {\n return normalized;\n }\n\n return (ISO639_3_TO_1 as Record<string, string>)[normalized] ?? normalized;\n}\n\n/**\n * Returns both ISO 639-1 and ISO 639-3 codes for a given language code.\n * Accepts either format as input and normalizes to both.\n *\n * @param code - Language code in either ISO 639-1 or ISO 639-3 format\n * @returns Object containing both code formats\n *\n * @example\n * ```typescript\n * getLanguageCodePair(\"en\") // { iso639_1: \"en\", iso639_3: \"eng\" }\n * getLanguageCodePair(\"fra\") // { iso639_1: \"fr\", iso639_3: \"fra\" }\n * ```\n */\nexport function getLanguageCodePair(code: string): LanguageCodePair {\n const normalized = code.toLowerCase().trim();\n\n if (normalized.length === 2) {\n // Input is ISO 639-1\n return {\n iso639_1: normalized,\n iso639_3: toISO639_3(normalized),\n };\n } else if (normalized.length === 3) {\n // Input is ISO 639-3\n return {\n iso639_1: toISO639_1(normalized),\n iso639_3: normalized,\n };\n }\n\n // Unknown format, return as-is for both\n return {\n iso639_1: normalized,\n iso639_3: normalized,\n };\n}\n\n/**\n * Validates if a code is a known ISO 639-1 code.\n *\n * @param code - Code to validate\n * @returns true if the code is a known ISO 639-1 code\n */\nexport function isValidISO639_1(code: string): boolean {\n return code.length === 2 && code.toLowerCase() in ISO639_1_TO_3;\n}\n\n/**\n * Validates if a code is a known ISO 639-3 code.\n *\n * @param code - Code to validate\n * @returns true if the code is a known ISO 639-3 code\n */\nexport function isValidISO639_3(code: string): boolean {\n return code.length === 3 && code.toLowerCase() in ISO639_3_TO_1;\n}\n\n/**\n * Gets the human-readable language name for a given code.\n *\n * @param code - Language code in either ISO 639-1 or ISO 639-3 format\n * @returns Human-readable language name (e.g., \"English\", \"French\")\n */\nexport function getLanguageName(code: string): string {\n const iso639_1 = toISO639_1(code);\n try {\n const displayNames = new Intl.DisplayNames([\"en\"], { type: \"language\" });\n return displayNames.of(iso639_1) ?? code.toUpperCase();\n } catch {\n return code.toUpperCase();\n }\n}\n","import Mux from \"@mux/mux-node\";\nimport { generateObject } from \"ai\";\nimport { z } from \"zod\";\n\nimport env from \"../env\";\nimport { createWorkflowConfig, getMuxCredentialsFromEnv } from \"../lib/client-factory\";\nimport { getLanguageCodePair, getLanguageName } from \"../lib/language-codes\";\nimport type { LanguageCodePair, SupportedISO639_1 } from \"../lib/language-codes\";\nimport { getPlaybackIdForAsset } from \"../lib/mux-assets\";\nimport { createLanguageModelFromConfig } from \"../lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"../lib/providers\";\nimport { getMuxSigningContextFromEnv, signUrl } from \"../lib/url-signing\";\nimport type { MuxAIOptions, TokenUsage } from \"../types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Output returned from `translateCaptions`. */\nexport interface TranslationResult {\n assetId: string;\n /** Source language code (ISO 639-1 two-letter format). */\n sourceLanguageCode: SupportedISO639_1;\n /** Target language code (ISO 639-1 two-letter format). */\n targetLanguageCode: SupportedISO639_1;\n /**\n * Source language codes in both ISO 639-1 (2-letter) and ISO 639-3 (3-letter) formats.\n * Use `iso639_1` for browser players (BCP-47 compliant) and `iso639_3` for APIs that require it.\n */\n sourceLanguage: LanguageCodePair;\n /**\n * Target language codes in both ISO 639-1 (2-letter) and ISO 639-3 (3-letter) formats.\n * Use `iso639_1` for browser players (BCP-47 compliant) and `iso639_3` for APIs that require it.\n */\n targetLanguage: LanguageCodePair;\n originalVtt: string;\n translatedVtt: string;\n uploadedTrackId?: string;\n presignedUrl?: string;\n /** Token usage from the AI provider (for efficiency/cost analysis). */\n usage?: TokenUsage;\n}\n\n/** Configuration accepted by `translateCaptions`. */\nexport interface TranslationOptions<P extends SupportedProvider = SupportedProvider> extends MuxAIOptions {\n /** Provider responsible for the translation. */\n provider: P;\n /** Provider-specific chat model identifier. */\n model?: ModelIdByProvider[P];\n /** Optional override for the S3-compatible endpoint used for uploads. */\n s3Endpoint?: string;\n /** S3 region (defaults to env.S3_REGION or 'auto'). */\n s3Region?: string;\n /** Bucket that will store translated VTT files. */\n s3Bucket?: string;\n /**\n * When true (default) the translated VTT is uploaded to the configured\n * bucket and attached to the Mux asset.\n */\n uploadToMux?: boolean;\n}\n\n/** Schema used when requesting caption translation from a language model. */\nexport const translationSchema = z.object({\n translation: z.string(),\n});\n\n/** Inferred shape returned by `translationSchema`. */\nexport type TranslationPayload = z.infer<typeof translationSchema>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nasync function fetchVttFromMux(vttUrl: string): Promise<string> {\n \"use step\";\n\n const vttResponse = await fetch(vttUrl);\n if (!vttResponse.ok) {\n throw new Error(`Failed to fetch VTT file: ${vttResponse.statusText}`);\n }\n\n return vttResponse.text();\n}\n\nasync function translateVttWithAI({\n vttContent,\n fromLanguageCode,\n toLanguageCode,\n provider,\n modelId,\n abortSignal,\n}: {\n vttContent: string;\n fromLanguageCode: string;\n toLanguageCode: string;\n provider: SupportedProvider;\n modelId: string;\n abortSignal?: AbortSignal;\n}): Promise<{ translatedVtt: string; usage: TokenUsage }> {\n \"use step\";\n\n const languageModel = createLanguageModelFromConfig(provider, modelId);\n\n const response = await generateObject({\n model: languageModel,\n schema: translationSchema,\n abortSignal,\n messages: [\n {\n role: \"user\",\n content: `Translate the following VTT subtitle file from ${fromLanguageCode} to ${toLanguageCode}. Preserve all timestamps and VTT formatting exactly as they appear. Return JSON with a single key \"translation\" containing the translated VTT.\\n\\n${vttContent}`,\n },\n ],\n });\n\n return {\n translatedVtt: response.object.translation,\n usage: {\n inputTokens: response.usage.inputTokens,\n outputTokens: response.usage.outputTokens,\n totalTokens: response.usage.totalTokens,\n reasoningTokens: response.usage.reasoningTokens,\n cachedInputTokens: response.usage.cachedInputTokens,\n },\n };\n}\n\nasync function uploadVttToS3({\n translatedVtt,\n assetId,\n fromLanguageCode,\n toLanguageCode,\n s3Endpoint,\n s3Region,\n s3Bucket,\n}: {\n translatedVtt: string;\n assetId: string;\n fromLanguageCode: string;\n toLanguageCode: string;\n s3Endpoint: string;\n s3Region: string;\n s3Bucket: string;\n}): Promise<string> {\n \"use step\";\n\n const { S3Client, GetObjectCommand } = await import(\"@aws-sdk/client-s3\");\n const { Upload } = await import(\"@aws-sdk/lib-storage\");\n const { getSignedUrl } = await import(\"@aws-sdk/s3-request-presigner\");\n\n // asserting exists. already validated (See: translateAudio())\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID!;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY!;\n\n const s3Client = new S3Client({\n region: s3Region,\n endpoint: s3Endpoint,\n credentials: {\n accessKeyId: s3AccessKeyId,\n secretAccessKey: s3SecretAccessKey,\n },\n forcePathStyle: true,\n });\n\n // Create unique key for the VTT file\n const vttKey = `translations/${assetId}/${fromLanguageCode}-to-${toLanguageCode}-${Date.now()}.vtt`;\n\n // Upload VTT to S3\n const upload = new Upload({\n client: s3Client,\n params: {\n Bucket: s3Bucket,\n Key: vttKey,\n Body: translatedVtt,\n ContentType: \"text/vtt\",\n },\n });\n\n await upload.done();\n\n // Generate presigned URL (valid for 1 hour)\n const getObjectCommand = new GetObjectCommand({\n Bucket: s3Bucket,\n Key: vttKey,\n });\n\n const presignedUrl = await getSignedUrl(s3Client, getObjectCommand, {\n expiresIn: 3600, // 1 hour\n });\n\n return presignedUrl;\n}\n\nasync function createTextTrackOnMux(\n assetId: string,\n languageCode: string,\n trackName: string,\n presignedUrl: string,\n): Promise<string> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n const trackResponse = await mux.video.assets.createTrack(assetId, {\n type: \"text\",\n text_type: \"subtitles\",\n language_code: languageCode,\n name: trackName,\n url: presignedUrl,\n });\n\n if (!trackResponse.id) {\n throw new Error(\"Failed to create text track: no track ID returned from Mux\");\n }\n\n return trackResponse.id;\n}\n\nexport async function translateCaptions<P extends SupportedProvider = SupportedProvider>(\n assetId: string,\n fromLanguageCode: string,\n toLanguageCode: string,\n options: TranslationOptions<P>,\n): Promise<TranslationResult> {\n \"use workflow\";\n const {\n provider = \"openai\",\n model,\n s3Endpoint: providedS3Endpoint,\n s3Region: providedS3Region,\n s3Bucket: providedS3Bucket,\n uploadToMux: uploadToMuxOption,\n } = options;\n\n // S3 configuration\n const s3Endpoint = providedS3Endpoint ?? env.S3_ENDPOINT;\n const s3Region = providedS3Region ?? env.S3_REGION ?? \"auto\";\n const s3Bucket = providedS3Bucket ?? env.S3_BUCKET;\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY;\n const uploadToMux = uploadToMuxOption !== false; // Default to true\n\n // Validate credentials and resolve language model\n const config = await createWorkflowConfig(\n { ...options, model },\n provider as SupportedProvider,\n );\n\n if (uploadToMux && (!s3Endpoint || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey)) {\n throw new Error(\"S3 configuration is required for uploading to Mux. Provide s3Endpoint, s3Bucket, s3AccessKeyId, and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.\");\n }\n\n // Fetch asset data and playback ID from Mux\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Find text track with the source language\n if (!assetData.tracks) {\n throw new Error(\"No tracks found for this asset\");\n }\n\n const sourceTextTrack = assetData.tracks.find(track =>\n track.type === \"text\" &&\n track.status === \"ready\" &&\n track.language_code === fromLanguageCode,\n );\n\n if (!sourceTextTrack) {\n throw new Error(`No ready text track found with language code '${fromLanguageCode}' for this asset`);\n }\n\n // Fetch the VTT file content (signed if needed)\n let vttUrl = `https://stream.mux.com/${playbackId}/text/${sourceTextTrack.id}.vtt`;\n if (policy === \"signed\" && signingContext) {\n vttUrl = await signUrl(vttUrl, playbackId, signingContext, \"video\");\n }\n\n let vttContent: string;\n try {\n vttContent = await fetchVttFromMux(vttUrl);\n } catch (error) {\n throw new Error(`Failed to fetch VTT content: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Translate VTT content using configured provider via ai-sdk\n let translatedVtt: string;\n let usage: TokenUsage | undefined;\n\n try {\n const result = await translateVttWithAI({\n vttContent,\n fromLanguageCode,\n toLanguageCode,\n provider: config.provider,\n modelId: config.modelId,\n abortSignal: options.abortSignal,\n });\n translatedVtt = result.translatedVtt;\n usage = result.usage;\n } catch (error) {\n throw new Error(`Failed to translate VTT with ${config.provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Resolve language code pairs for both source and target\n const sourceLanguage = getLanguageCodePair(fromLanguageCode);\n const targetLanguage = getLanguageCodePair(toLanguageCode);\n\n // If uploadToMux is false, just return the translation\n if (!uploadToMux) {\n return {\n assetId,\n sourceLanguageCode: fromLanguageCode as SupportedISO639_1,\n targetLanguageCode: toLanguageCode as SupportedISO639_1,\n sourceLanguage,\n targetLanguage,\n originalVtt: vttContent,\n translatedVtt,\n usage,\n };\n }\n\n // Upload translated VTT to S3-compatible storage\n let presignedUrl: string;\n\n try {\n presignedUrl = await uploadVttToS3({\n translatedVtt,\n assetId,\n fromLanguageCode,\n toLanguageCode,\n s3Endpoint: s3Endpoint!,\n s3Region,\n s3Bucket: s3Bucket!,\n });\n } catch (error) {\n throw new Error(`Failed to upload VTT to S3: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Add translated track to Mux asset\n let uploadedTrackId: string | undefined;\n\n try {\n const languageName = getLanguageName(toLanguageCode);\n const trackName = `${languageName} (auto-translated)`;\n\n uploadedTrackId = await createTextTrackOnMux(assetId, toLanguageCode, trackName, presignedUrl);\n } catch (error) {\n console.warn(`Failed to add track to Mux asset: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n return {\n assetId,\n sourceLanguageCode: fromLanguageCode as SupportedISO639_1,\n targetLanguageCode: toLanguageCode as SupportedISO639_1,\n sourceLanguage,\n targetLanguage,\n originalVtt: vttContent,\n translatedVtt,\n uploadedTrackId,\n presignedUrl,\n usage,\n };\n}\n"],"mappings":"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","names":["z","Mux","Mux","z","generateObject","z","z","generateObject","SYSTEM_PROMPT","chunkText","DEFAULT_PROVIDER","generateObject","dedent","z","z","dedent","SYSTEM_PROMPT","buildUserPrompt","analyzeStoryboard","generateObject","Mux","Mux","message","targetLanguage","Mux","generateObject","z","z","generateObject","Mux"]}
1
+ {"version":3,"sources":["../../src/workflows/burned-in-captions.ts","../../src/env.ts","../../src/lib/providers.ts","../../src/lib/client-factory.ts","../../src/lib/image-download.ts","../../src/lib/mux-assets.ts","../../src/lib/prompt-builder.ts","../../src/lib/url-signing.ts","../../src/primitives/storyboards.ts","../../src/workflows/chapters.ts","../../src/lib/retry.ts","../../src/primitives/transcripts.ts","../../src/workflows/embeddings.ts","../../src/primitives/text-chunking.ts","../../src/primitives/thumbnails.ts","../../src/workflows/moderation.ts","../../src/workflows/summarization.ts","../../src/workflows/translate-audio.ts","../../src/lib/language-codes.ts","../../src/workflows/translate-captions.ts"],"sourcesContent":["import { generateObject } from \"ai\";\nimport dedent from \"dedent\";\nimport { z } from \"zod\";\n\nimport { createWorkflowConfig } from \"@mux/ai/lib/client-factory\";\nimport type { ImageDownloadOptions } from \"@mux/ai/lib/image-download\";\nimport { downloadImageAsBase64 } from \"@mux/ai/lib/image-download\";\nimport { getPlaybackIdForAsset } from \"@mux/ai/lib/mux-assets\";\nimport type { PromptOverrides } from \"@mux/ai/lib/prompt-builder\";\nimport { createPromptBuilder } from \"@mux/ai/lib/prompt-builder\";\nimport { createLanguageModelFromConfig } from \"@mux/ai/lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"@mux/ai/lib/providers\";\nimport { getMuxSigningContextFromEnv } from \"@mux/ai/lib/url-signing\";\nimport { getStoryboardUrl } from \"@mux/ai/primitives/storyboards\";\nimport type { ImageSubmissionMode, MuxAIOptions, TokenUsage } from \"@mux/ai/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Structured payload returned from `hasBurnedInCaptions`. */\nexport interface BurnedInCaptionsResult {\n assetId: string;\n hasBurnedInCaptions: boolean;\n confidence: number;\n detectedLanguage: string | null;\n storyboardUrl: string;\n /** Token usage from the AI provider (for efficiency/cost analysis). */\n usage?: TokenUsage;\n}\n\n/**\n * Sections of the burned-in captions user prompt that can be overridden.\n * Use these to customize the AI's behavior for your specific use case.\n */\nexport type BurnedInCaptionsPromptSections =\n \"task\" |\n \"analysisSteps\" |\n \"positiveIndicators\" |\n \"negativeIndicators\";\n\n/**\n * Override specific sections of the burned-in captions prompt.\n * Each key corresponds to a section that can be customized.\n *\n * @example\n * ```typescript\n * const result = await hasBurnedInCaptions(assetId, {\n * promptOverrides: {\n * task: 'Detect any text overlays in the video frames.',\n * positiveIndicators: 'Classify as captions if text appears consistently.',\n * },\n * });\n * ```\n */\nexport type BurnedInCaptionsPromptOverrides = PromptOverrides<BurnedInCaptionsPromptSections>;\n\n/** Configuration accepted by `hasBurnedInCaptions`. */\nexport interface BurnedInCaptionsOptions extends MuxAIOptions {\n /** AI provider used for storyboard inspection (defaults to 'openai'). */\n provider?: SupportedProvider;\n /** Provider-specific model identifier. */\n model?: ModelIdByProvider[SupportedProvider];\n /** Transport used for storyboard submission (defaults to 'url'). */\n imageSubmissionMode?: ImageSubmissionMode;\n /** Download tuning used when `imageSubmissionMode` === 'base64'. */\n imageDownloadOptions?: ImageDownloadOptions;\n /**\n * Override specific sections of the user prompt.\n * Useful for customizing the AI's detection criteria for specific use cases.\n */\n promptOverrides?: BurnedInCaptionsPromptOverrides;\n}\n\n/** Schema used to validate burned-in captions analysis responses. */\nexport const burnedInCaptionsSchema = z.object({\n hasBurnedInCaptions: z.boolean(),\n confidence: z.number().min(0).max(1),\n detectedLanguage: z.string().nullable(),\n});\n\n/** Inferred shape returned from the burned-in captions schema. */\nexport type BurnedInCaptionsAnalysis = z.infer<typeof burnedInCaptionsSchema>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Prompts\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = dedent`\n <role>\n You are an expert at analyzing video frames to detect burned-in captions (also called open captions or hardcoded subtitles).\n These are text overlays that are permanently embedded in the video image, common on TikTok, Instagram Reels, and other social media platforms.\n </role>\n\n <critical_note>\n Burned-in captions must appear consistently across MOST frames in the storyboard.\n Text appearing in only 1-2 frames at the end is typically marketing copy, taglines, or end-cards - NOT burned-in captions.\n </critical_note>\n\n <confidence_scoring>\n Use this rubric to determine your confidence score (0.0-1.0):\n\n - Score 1.0: Definitive captions - text overlays visible in most frames, consistent positioning, content changes between frames indicating dialogue/narration, clear caption-style formatting\n - Score 0.7-0.9: Strong evidence - captions visible across multiple frames with consistent placement, but minor ambiguity (e.g., some frames unclear, atypical styling)\n - Score 0.4-0.6: Moderate evidence - text present in several frames but uncertain classification (e.g., could be captions or persistent on-screen graphics, ambiguous formatting)\n - Score 0.1-0.3: Weak evidence - minimal text detected, appears in only a few frames, likely marketing copy or end-cards rather than captions\n - Score 0.0: No captions - no text overlays detected, or text is clearly not captions (logos, watermarks, scene content, single end-card)\n </confidence_scoring>\n\n <context>\n You receive storyboard images containing multiple sequential frames extracted from a video.\n These frames are arranged in a grid and represent the visual progression of the content over time.\n Read frames left-to-right, top-to-bottom to understand the temporal sequence.\n </context>\n\n <capabilities>\n - Detect and analyze text overlays in video frames\n - Distinguish between captions and other text elements (marketing, logos, UI)\n - Identify language of detected caption text\n - Assess confidence in caption detection\n </capabilities>\n\n <constraints>\n - Only classify as burned-in captions when evidence is clear across multiple frames\n - Base decisions on observable visual evidence\n - Return structured data matching the requested schema\n </constraints>`;\n\n/**\n * Prompt builder for the burned-in captions user prompt.\n * Sections can be individually overridden via `promptOverrides` in BurnedInCaptionsOptions.\n */\nconst burnedInCaptionsPromptBuilder = createPromptBuilder<BurnedInCaptionsPromptSections>({\n template: {\n task: {\n tag: \"task\",\n content: dedent`\n Analyze the provided video storyboard to detect burned-in captions (hardcoded subtitles).\n Count frames with text vs no text, note position consistency and whether text changes across frames.\n Decide if captions exist, with confidence (0.0-1.0) and detected language if any.`,\n },\n analysisSteps: {\n tag: \"analysis_steps\",\n content: dedent`\n 1. COUNT how many frames contain text overlays vs. how many don't\n 2. Check if text appears in consistent positions across multiple frames\n 3. Verify text changes content between frames (indicating dialogue/narration)\n 4. Ensure text has caption-style formatting (contrasting colors, readable fonts)\n 5. If captions are detected, identify the language of the text`,\n },\n positiveIndicators: {\n tag: \"classify_as_captions\",\n content: dedent`\n ONLY classify as burned-in captions if:\n - Text appears in multiple frames (not just 1-2 end frames)\n - Text positioning is consistent across those frames\n - Content suggests dialogue, narration, or subtitles (not marketing)\n - Formatting looks like captions (not graphics/logos)`,\n },\n negativeIndicators: {\n tag: \"not_captions\",\n content: dedent`\n DO NOT classify as burned-in captions:\n - Marketing taglines appearing only in final 1-2 frames\n - Single words or phrases that don't change between frames\n - Graphics, logos, watermarks, or UI elements\n - Text that's part of the original scene content\n - End-cards with calls-to-action or brand messaging`,\n },\n },\n sectionOrder: [\"task\", \"analysisSteps\", \"positiveIndicators\", \"negativeIndicators\"],\n});\n\nfunction buildUserPrompt(promptOverrides?: BurnedInCaptionsPromptOverrides): string {\n return burnedInCaptionsPromptBuilder.build(promptOverrides);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\nconst DEFAULT_PROVIDER = \"openai\";\n\ninterface AnalysisResponse {\n result: BurnedInCaptionsAnalysis;\n usage: TokenUsage;\n}\n\nasync function fetchImageAsBase64(\n imageUrl: string,\n imageDownloadOptions?: ImageDownloadOptions,\n): Promise<string> {\n \"use step\";\n\n const downloadResult = await downloadImageAsBase64(imageUrl, imageDownloadOptions);\n return downloadResult.base64Data;\n}\n\nasync function analyzeStoryboard({\n imageDataUrl,\n provider,\n modelId,\n userPrompt,\n systemPrompt,\n}: {\n imageDataUrl: string;\n provider: SupportedProvider;\n modelId: string;\n userPrompt: string;\n systemPrompt: string;\n}): Promise<AnalysisResponse> {\n \"use step\";\n\n const model = createLanguageModelFromConfig(provider, modelId);\n\n const response = await generateObject({\n model,\n schema: burnedInCaptionsSchema,\n experimental_telemetry: { isEnabled: true },\n messages: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n {\n role: \"user\",\n content: [\n { type: \"text\", text: userPrompt },\n { type: \"image\", image: imageDataUrl },\n ],\n },\n ],\n });\n\n return {\n result: response.object,\n usage: {\n inputTokens: response.usage.inputTokens,\n outputTokens: response.usage.outputTokens,\n totalTokens: response.usage.totalTokens,\n reasoningTokens: response.usage.reasoningTokens,\n cachedInputTokens: response.usage.cachedInputTokens,\n },\n };\n}\n\nexport async function hasBurnedInCaptions(\n assetId: string,\n options: BurnedInCaptionsOptions = {},\n): Promise<BurnedInCaptionsResult> {\n \"use workflow\";\n const {\n provider = DEFAULT_PROVIDER,\n model,\n imageSubmissionMode = \"url\",\n imageDownloadOptions,\n promptOverrides,\n ...config\n } = options;\n\n // Build the user prompt with any overrides\n const userPrompt = buildUserPrompt(promptOverrides);\n\n const workflowConfig = await createWorkflowConfig(\n { ...config, model },\n provider as SupportedProvider,\n );\n const { playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n const imageUrl = await getStoryboardUrl(playbackId, 640, policy === \"signed\");\n\n let analysisResponse: AnalysisResponse;\n\n if (imageSubmissionMode === \"base64\") {\n const base64Data = await fetchImageAsBase64(imageUrl, imageDownloadOptions);\n analysisResponse = await analyzeStoryboard({\n imageDataUrl: base64Data,\n provider: workflowConfig.provider,\n modelId: workflowConfig.modelId,\n userPrompt,\n systemPrompt: SYSTEM_PROMPT,\n });\n } else {\n analysisResponse = await analyzeStoryboard({\n imageDataUrl: imageUrl,\n provider: workflowConfig.provider,\n modelId: workflowConfig.modelId,\n userPrompt,\n systemPrompt: SYSTEM_PROMPT,\n });\n }\n\n if (!analysisResponse.result) {\n throw new Error(\"No analysis result received from AI provider\");\n }\n\n return {\n assetId,\n hasBurnedInCaptions: analysisResponse.result.hasBurnedInCaptions ?? false,\n confidence: analysisResponse.result.confidence ?? 0,\n detectedLanguage: analysisResponse.result.detectedLanguage ?? null,\n storyboardUrl: imageUrl,\n usage: analysisResponse.usage,\n };\n}\n","/* eslint-disable node/no-process-env */\n\nimport { z } from \"zod\";\n\nimport \"dotenv/config\";\n\nfunction optionalString(description: string, message?: string) {\n return z.preprocess(\n value => typeof value === \"string\" && value.trim().length === 0 ? undefined : value,\n z.string().trim().min(1, message).optional(),\n ).describe(description);\n}\n\nfunction requiredString(description: string, message?: string) {\n return z.preprocess(\n value => typeof value === \"string\" ? value.trim().length > 0 ? value.trim() : undefined : value,\n z.string().trim().min(1, message),\n ).describe(description);\n}\n\nconst EnvSchema = z.object({\n NODE_ENV: z.string().default(\"development\").describe(\"Runtime environment.\"),\n\n MUX_TOKEN_ID: requiredString(\"Mux access token ID.\", \"Required to access Mux APIs\"),\n MUX_TOKEN_SECRET: requiredString(\"Mux access token secret.\", \"Required to access Mux APIs\"),\n\n MUX_SIGNING_KEY: optionalString(\"Mux signing key ID for signed playback URLs.\", \"Used to sign playback URLs\"),\n MUX_PRIVATE_KEY: optionalString(\"Mux signing private key for signed playback URLs.\", \"Used to sign playback URLs\"),\n\n OPENAI_API_KEY: optionalString(\"OpenAI API key for OpenAI-backed workflows.\", \"OpenAI API key\"),\n ANTHROPIC_API_KEY: optionalString(\"Anthropic API key for Claude-backed workflows.\", \"Anthropic API key\"),\n GOOGLE_GENERATIVE_AI_API_KEY: optionalString(\"Google Generative AI API key for Gemini-backed workflows.\", \"Google Generative AI API key\"),\n\n ELEVENLABS_API_KEY: optionalString(\"ElevenLabs API key for audio translation.\", \"ElevenLabs API key\"),\n HIVE_API_KEY: optionalString(\"Hive Visual Moderation API key.\", \"Hive API key\"),\n\n S3_ENDPOINT: optionalString(\"S3-compatible endpoint for uploads.\", \"S3 endpoint\"),\n S3_REGION: optionalString(\"S3 region (defaults to 'auto' when omitted).\"),\n S3_BUCKET: optionalString(\"Bucket used for caption and audio uploads.\", \"S3 bucket\"),\n S3_ACCESS_KEY_ID: optionalString(\"Access key ID for S3-compatible uploads.\", \"S3 access key id\"),\n S3_SECRET_ACCESS_KEY: optionalString(\"Secret access key for S3-compatible uploads.\", \"S3 secret access key\"),\n});\n\nexport type Env = z.infer<typeof EnvSchema>;\n\nfunction parseEnv(): Env {\n const parsedEnv = EnvSchema.safeParse(process.env);\n\n if (!parsedEnv.success) {\n console.error(\"❌ Invalid env:\");\n console.error(JSON.stringify(parsedEnv.error.flatten().fieldErrors, null, 2));\n process.exit(1);\n }\n\n return parsedEnv.data;\n}\n\nconst env: Env = parseEnv();\n\nexport function reloadEnv(): Env {\n const parsed = parseEnv();\n Object.assign(env, parsed);\n return env;\n}\n\nexport { env };\nexport default env;\n","import { createAnthropic } from \"@ai-sdk/anthropic\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\n\nimport env from \"@mux/ai/env\";\nimport type { MuxAIOptions } from \"@mux/ai/types\";\n\nimport type { EmbeddingModel, LanguageModel } from \"ai\";\n\nexport type SupportedProvider = \"openai\" | \"anthropic\" | \"google\";\nexport type SupportedEmbeddingProvider = \"openai\" | \"google\";\n\n// Model ID unions inferred from ai-sdk provider call signatures\ntype OpenAIModelId = Parameters<ReturnType<typeof createOpenAI>[\"chat\"]>[0];\ntype AnthropicModelId = Parameters<ReturnType<typeof createAnthropic>[\"chat\"]>[0];\ntype GoogleModelId = Parameters<ReturnType<typeof createGoogleGenerativeAI>[\"chat\"]>[0];\n\ntype OpenAIEmbeddingModelId = Parameters<ReturnType<typeof createOpenAI>[\"embedding\"]>[0];\ntype GoogleEmbeddingModelId = Parameters<ReturnType<typeof createGoogleGenerativeAI>[\"textEmbeddingModel\"]>[0];\n\nexport interface ModelIdByProvider {\n openai: OpenAIModelId;\n anthropic: AnthropicModelId;\n google: GoogleModelId;\n}\n\nexport interface EmbeddingModelIdByProvider {\n openai: OpenAIEmbeddingModelId;\n google: GoogleEmbeddingModelId;\n}\n\nexport interface ModelRequestOptions<P extends SupportedProvider = SupportedProvider> extends MuxAIOptions {\n provider?: P;\n model?: ModelIdByProvider[P];\n}\n\nexport interface ResolvedModel<P extends SupportedProvider = SupportedProvider> {\n provider: P;\n modelId: ModelIdByProvider[P];\n model: LanguageModel;\n}\n\nexport const DEFAULT_LANGUAGE_MODELS: { [K in SupportedProvider]: ModelIdByProvider[K] } = {\n openai: \"gpt-5.1\",\n anthropic: \"claude-sonnet-4-5\",\n google: \"gemini-3-flash-preview\",\n};\n\nconst DEFAULT_EMBEDDING_MODELS: { [K in SupportedEmbeddingProvider]: EmbeddingModelIdByProvider[K] } = {\n openai: \"text-embedding-3-small\",\n google: \"gemini-embedding-001\",\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Model Pricing\n// ─────────────────────────────────────────────────────────────────────────────\n//\n// Pricing is in USD per million tokens. These values are used for cost estimation\n// in evaluations and should be periodically verified against official sources.\n//\n// Sources (as of December 2025):\n// - OpenAI: https://openai.com/api/pricing\n// - Anthropic: https://www.anthropic.com/pricing\n// - Google: https://ai.google.dev/pricing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Pricing structure for a language model.\n * All costs are in USD per million tokens.\n */\nexport interface ModelPricing {\n /** Cost per million input tokens (USD). */\n inputPerMillion: number;\n /** Cost per million output tokens (USD). */\n outputPerMillion: number;\n /** Cost per million cached input tokens (USD), if supported. */\n cachedInputPerMillion?: number;\n /** URL to the official pricing page for verification. */\n pricingUrl: string;\n}\n\n/**\n * Pricing data for the default language models.\n * Used for cost estimation in evaluations and expense tracking.\n *\n * @remarks\n * Prices are subject to change. Verify against official sources before production use.\n */\nexport const THIRD_PARTY_MODEL_PRICING: { [K in SupportedProvider]: ModelPricing } = {\n // OpenAI GPT-5.1\n // Reference: https://openai.com/api/pricing\n openai: {\n inputPerMillion: 1.25,\n outputPerMillion: 10.00,\n cachedInputPerMillion: 0.125,\n pricingUrl: \"https://openai.com/api/pricing\",\n },\n\n // Anthropic Claude Sonnet 4.5\n // Reference: https://www.anthropic.com/pricing\n anthropic: {\n inputPerMillion: 3.00,\n outputPerMillion: 15.00,\n cachedInputPerMillion: 0.30, // Prompt caching read cost (≤200K tokens)\n pricingUrl: \"https://www.anthropic.com/pricing\",\n },\n\n // Google Gemini 3 Flash Preview\n // Reference: https://ai.google.dev/pricing\n google: {\n inputPerMillion: 0.50,\n outputPerMillion: 3.00,\n cachedInputPerMillion: 0.05, // Context caching price\n pricingUrl: \"https://ai.google.dev/pricing\",\n },\n};\n\n/**\n * Calculates the estimated cost for a request based on token usage.\n *\n * @param provider - The AI provider used\n * @param inputTokens - Number of input tokens consumed\n * @param outputTokens - Number of output tokens generated\n * @param cachedInputTokens - Number of input tokens served from cache (optional)\n * @returns Estimated cost in USD\n *\n * @example\n * ```typescript\n * const cost = calculateCost('openai', 2000, 500);\n * console.log(`Estimated cost: $${cost.toFixed(6)}`);\n * ```\n */\nexport function calculateCost(\n provider: SupportedProvider,\n inputTokens: number,\n outputTokens: number,\n cachedInputTokens: number = 0,\n): number {\n const pricing = THIRD_PARTY_MODEL_PRICING[provider];\n\n // Adjust input tokens: cached tokens are charged at cached rate, rest at full rate\n const uncachedInputTokens = Math.max(0, inputTokens - cachedInputTokens);\n\n const inputCost = (uncachedInputTokens / 1_000_000) * pricing.inputPerMillion;\n const outputCost = (outputTokens / 1_000_000) * pricing.outputPerMillion;\n let cachedCost = 0;\n if (pricing.cachedInputPerMillion) {\n cachedCost = (cachedInputTokens / 1_000_000) * pricing.cachedInputPerMillion;\n }\n\n return inputCost + outputCost + cachedCost;\n}\n\nfunction requireEnv(value: string | undefined, name: string): string {\n if (!value) {\n throw new Error(`Missing ${name}. Set ${name} in your environment or pass it in options.`);\n }\n return value;\n}\n\n/**\n * Creates a language model instance from serializable config.\n * Use this in steps to instantiate models from config passed through workflow.\n * Fetches credentials internally from environment variables to avoid exposing them in step I/O.\n */\nexport function createLanguageModelFromConfig(\n provider: SupportedProvider,\n modelId: string,\n): LanguageModel {\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({ apiKey });\n return openai(modelId);\n }\n case \"anthropic\": {\n const apiKey = env.ANTHROPIC_API_KEY;\n requireEnv(apiKey, \"ANTHROPIC_API_KEY\");\n const anthropic = createAnthropic({ apiKey });\n return anthropic(modelId);\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({ apiKey });\n return google(modelId);\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported provider: ${exhaustiveCheck}`);\n }\n }\n}\n\n/**\n * Creates an embedding model instance from serializable config.\n * Use this in steps to instantiate embedding models from config passed through workflow.\n * Fetches credentials internally from environment variables to avoid exposing them in step I/O.\n */\nexport function createEmbeddingModelFromConfig(\n provider: SupportedEmbeddingProvider,\n modelId: string,\n): EmbeddingModel<string> {\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({ apiKey });\n return openai.embedding(modelId);\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({ apiKey });\n return google.textEmbeddingModel(modelId);\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported embedding provider: ${exhaustiveCheck}`);\n }\n }\n}\n\n/**\n * Resolves a language model from a suggested provider.\n */\nexport function resolveLanguageModel<P extends SupportedProvider = SupportedProvider>(\n options: ModelRequestOptions<P> = {},\n): ResolvedModel<P> {\n const provider = options.provider || (\"openai\" as P);\n const modelId = (options.model || DEFAULT_LANGUAGE_MODELS[provider]) as ModelIdByProvider[P];\n\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: openai(modelId),\n };\n }\n case \"anthropic\": {\n const apiKey = env.ANTHROPIC_API_KEY;\n requireEnv(apiKey, \"ANTHROPIC_API_KEY\");\n const anthropic = createAnthropic({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: anthropic(modelId),\n };\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: google(modelId),\n };\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported provider: ${exhaustiveCheck}`);\n }\n }\n}\n\n/**\n * Resolves an embedding model from a suggested provider.\n */\nexport function resolveEmbeddingModel<P extends SupportedEmbeddingProvider = \"openai\">(\n options: MuxAIOptions & { provider?: P; model?: EmbeddingModelIdByProvider[P] } = {},\n): { provider: P; modelId: EmbeddingModelIdByProvider[P]; model: EmbeddingModel<string> } {\n const provider = options.provider || (\"openai\" as P);\n const modelId = (options.model || DEFAULT_EMBEDDING_MODELS[provider]) as EmbeddingModelIdByProvider[P];\n\n switch (provider) {\n case \"openai\": {\n const apiKey = env.OPENAI_API_KEY;\n requireEnv(apiKey, \"OPENAI_API_KEY\");\n const openai = createOpenAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: openai.embedding(modelId),\n };\n }\n case \"google\": {\n const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n requireEnv(apiKey, \"GOOGLE_GENERATIVE_AI_API_KEY\");\n const google = createGoogleGenerativeAI({\n apiKey,\n });\n\n return {\n provider,\n modelId,\n model: google.textEmbeddingModel(modelId),\n };\n }\n default: {\n const exhaustiveCheck: never = provider;\n throw new Error(`Unsupported embedding provider: ${exhaustiveCheck}`);\n }\n }\n}\n","import env from \"@mux/ai/env\";\nimport type {\n ModelRequestOptions,\n SupportedProvider,\n} from \"@mux/ai/lib/providers\";\nimport {\n resolveLanguageModel,\n} from \"@mux/ai/lib/providers\";\n\n/**\n * Gets Mux credentials from environment variables.\n * Used internally by workflow steps to avoid passing credentials through step I/O.\n * Throws if credentials are not available.\n */\nexport function getMuxCredentialsFromEnv(): { muxTokenId: string; muxTokenSecret: string } {\n const muxTokenId = env.MUX_TOKEN_ID;\n const muxTokenSecret = env.MUX_TOKEN_SECRET;\n\n if (!muxTokenId || !muxTokenSecret) {\n throw new Error(\n \"Mux credentials are required. Set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables.\",\n );\n }\n\n return { muxTokenId, muxTokenSecret };\n}\n\n/**\n * Gets an API key from environment variables for the specified provider.\n * Used internally by workflow steps to avoid passing credentials through step I/O.\n * Throws if the API key is not available.\n */\nexport function getApiKeyFromEnv(provider: \"openai\" | \"anthropic\" | \"google\" | \"hive\" | \"elevenlabs\"): string {\n const envVarMap: Record<string, string | undefined> = {\n openai: env.OPENAI_API_KEY,\n anthropic: env.ANTHROPIC_API_KEY,\n google: env.GOOGLE_GENERATIVE_AI_API_KEY,\n hive: env.HIVE_API_KEY,\n elevenlabs: env.ELEVENLABS_API_KEY,\n };\n\n const apiKey = envVarMap[provider];\n if (!apiKey) {\n const envVarNames: Record<string, string> = {\n openai: \"OPENAI_API_KEY\",\n anthropic: \"ANTHROPIC_API_KEY\",\n google: \"GOOGLE_GENERATIVE_AI_API_KEY\",\n hive: \"HIVE_API_KEY\",\n elevenlabs: \"ELEVENLABS_API_KEY\",\n };\n throw new Error(\n `${provider} API key is required. Set ${envVarNames[provider]} environment variable.`,\n );\n }\n\n return apiKey;\n}\n\nexport interface ValidatedCredentials {\n muxTokenId: string;\n muxTokenSecret: string;\n openaiApiKey?: string;\n anthropicApiKey?: string;\n googleApiKey?: string;\n}\n\n/**\n * Validates and retrieves credentials from options or environment variables.\n * This function is NOT a workflow step to avoid exposing credentials in step I/O.\n */\nexport async function validateCredentials(\n requiredProvider?: SupportedProvider,\n): Promise<ValidatedCredentials> {\n const muxTokenId = env.MUX_TOKEN_ID;\n const muxTokenSecret = env.MUX_TOKEN_SECRET;\n const openaiApiKey = env.OPENAI_API_KEY;\n const anthropicApiKey = env.ANTHROPIC_API_KEY;\n const googleApiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;\n\n if (!muxTokenId || !muxTokenSecret) {\n throw new Error(\n \"Mux credentials are required. Provide muxTokenId and muxTokenSecret in options or set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables.\",\n );\n }\n\n if (requiredProvider === \"openai\" && !openaiApiKey) {\n throw new Error(\n \"OpenAI API key is required. Provide openaiApiKey in options or set OPENAI_API_KEY environment variable.\",\n );\n }\n\n if (requiredProvider === \"anthropic\" && !anthropicApiKey) {\n throw new Error(\n \"Anthropic API key is required. Provide anthropicApiKey in options or set ANTHROPIC_API_KEY environment variable.\",\n );\n }\n\n if (requiredProvider === \"google\" && !googleApiKey) {\n throw new Error(\n \"Google Generative AI API key is required. Provide googleApiKey in options or set GOOGLE_GENERATIVE_AI_API_KEY environment variable.\",\n );\n }\n\n return {\n muxTokenId,\n muxTokenSecret,\n openaiApiKey,\n anthropicApiKey,\n googleApiKey,\n };\n}\n\nexport interface WorkflowConfig {\n credentials: ValidatedCredentials;\n provider: SupportedProvider;\n modelId: string;\n}\n\n/**\n * Validates credentials and resolves model configuration for a workflow.\n * This function is NOT a workflow step to avoid exposing credentials in step I/O.\n */\nexport async function createWorkflowConfig(\n options: ModelRequestOptions,\n provider?: SupportedProvider,\n): Promise<WorkflowConfig> {\n const providerToUse = provider || options.provider || \"openai\";\n const credentials = await validateCredentials(providerToUse);\n const resolved = resolveLanguageModel({\n ...options,\n provider: providerToUse,\n });\n\n return {\n credentials,\n provider: resolved.provider,\n modelId: resolved.modelId as string,\n };\n}\n","import { Buffer } from \"node:buffer\";\n\nimport pRetry, { AbortError } from \"p-retry\";\n\nexport interface ImageDownloadOptions {\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Maximum number of retry attempts (default: 3) */\n retries?: number;\n /** Base delay between retries in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Maximum delay between retries in milliseconds (default: 10000) */\n maxRetryDelay?: number;\n /** Whether to use exponential backoff (default: true) */\n exponentialBackoff?: boolean;\n}\n\nexport interface ImageDownloadResult {\n /** Base64 encoded image data with data URI prefix (e.g., \"data:image/png;base64,iVBORw0K...\") */\n base64Data: string;\n /** Raw image buffer for multipart/form-data uploads */\n buffer: Buffer;\n /** Original image URL */\n url: string;\n /** Content type of the downloaded image */\n contentType: string;\n /** Size of the downloaded image in bytes */\n sizeBytes: number;\n /** Number of retry attempts made (0 if successful on first try) */\n attempts: number;\n}\n\nexport interface AnthropicFileUploadResult {\n /** Anthropic Files API file ID */\n fileId: string;\n /** Original image URL */\n url: string;\n /** Content type of the uploaded image */\n contentType: string;\n /** Size of the uploaded image in bytes */\n sizeBytes: number;\n}\n\nconst DEFAULT_OPTIONS: Required<ImageDownloadOptions> = {\n timeout: 10000,\n retries: 3,\n retryDelay: 1000,\n maxRetryDelay: 10000,\n exponentialBackoff: true,\n};\n\n/**\n * Downloads an image from a URL and converts it to base64 with robust retry logic\n *\n * @param url - The image URL to download\n * @param options - Download configuration options\n * @returns Promise resolving to ImageDownloadResult with base64 data and metadata\n * @throws Error if download fails after all retries\n */\nexport async function downloadImageAsBase64(\n url: string,\n options: ImageDownloadOptions = {},\n): Promise<ImageDownloadResult> {\n \"use step\";\n const opts = { ...DEFAULT_OPTIONS, ...options };\n let attemptCount = 0;\n\n return pRetry(\n async () => {\n attemptCount++;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), opts.timeout);\n\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n \"User-Agent\": \"@mux/ai image downloader\",\n },\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n // Don't retry 4xx errors (except 429 rate limiting)\n if (response.status >= 400 && response.status < 500 && response.status !== 429) {\n throw new AbortError(`HTTP ${response.status}: ${response.statusText}`);\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const contentType = response.headers.get(\"content-type\");\n if (!contentType?.startsWith(\"image/\")) {\n throw new AbortError(`Invalid content type: ${contentType}. Expected image/*`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n if (buffer.length === 0) {\n throw new AbortError(\"Downloaded image is empty\");\n }\n\n // Convert to base64 with data URI prefix\n const base64Data = `data:${contentType};base64,${buffer.toString(\"base64\")}`;\n\n return {\n base64Data,\n buffer,\n url,\n contentType,\n sizeBytes: buffer.length,\n attempts: attemptCount,\n };\n } catch (error) {\n clearTimeout(timeoutId);\n\n // If it's an AbortError (non-retryable), re-throw it\n if (error instanceof AbortError) {\n throw error;\n }\n\n // For network errors, timeout errors, etc., wrap in retryable error\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n throw new Error(`Request timeout after ${opts.timeout}ms`);\n }\n throw new Error(`Download failed: ${error.message}`);\n }\n\n throw new Error(\"Unknown download error\");\n }\n },\n {\n retries: opts.retries,\n minTimeout: opts.retryDelay,\n maxTimeout: opts.maxRetryDelay,\n factor: opts.exponentialBackoff ? 2 : 1,\n randomize: true, // Add jitter to prevent thundering herd\n onFailedAttempt: (error) => {\n console.warn(`Image download attempt ${error.attemptNumber} failed for ${url}`);\n if (error.retriesLeft > 0) {\n console.warn(`Retrying... (${error.retriesLeft} attempts left)`);\n }\n },\n },\n );\n}\n\n/**\n * Downloads multiple images concurrently with controlled concurrency\n *\n * @param urls - Array of image URLs to download\n * @param options - Download configuration options\n * @param maxConcurrent - Maximum concurrent downloads (default: 5)\n * @returns Promise resolving to array of ImageDownloadResult (in same order as input URLs)\n */\nexport async function downloadImagesAsBase64(\n urls: string[],\n options: ImageDownloadOptions = {},\n maxConcurrent: number = 5,\n): Promise<ImageDownloadResult[]> {\n \"use step\";\n const results: ImageDownloadResult[] = [];\n\n for (let i = 0; i < urls.length; i += maxConcurrent) {\n const batch = urls.slice(i, i + maxConcurrent);\n const batchPromises = batch.map(url => downloadImageAsBase64(url, options));\n const batchResults = await Promise.all(batchPromises);\n results.push(...batchResults);\n }\n\n return results;\n}\n\n/**\n * Uploads an image to Anthropic Files API for use in messages\n *\n * @param url - The image URL to download and upload\n * @param anthropicApiKey - Anthropic API key\n * @param options - Download configuration options\n * @returns Promise resolving to AnthropicFileUploadResult with file ID and metadata\n * @throws Error if download or upload fails\n */\nexport async function uploadImageToAnthropicFiles(\n url: string,\n anthropicApiKey: string,\n options: ImageDownloadOptions = {},\n): Promise<AnthropicFileUploadResult> {\n \"use step\";\n // First download the image\n const downloadResult = await downloadImageAsBase64(url, options);\n\n // Create form data for Files API upload\n const formData = new FormData();\n\n // Create a Blob from the buffer for form data\n const imageBlob = new Blob([downloadResult.buffer], {\n type: downloadResult.contentType,\n });\n\n // Get file extension from content type\n const extension = downloadResult.contentType.split(\"/\")[1] || \"png\";\n formData.append(\"file\", imageBlob, `image.${extension}`);\n\n // Upload to Anthropic Files API\n const response = await fetch(\"https://api.anthropic.com/v1/files\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": anthropicApiKey,\n \"anthropic-version\": \"2023-06-01\",\n \"anthropic-beta\": \"files-api-2025-04-14\",\n // Don't set Content-Type header - let fetch set it with boundary for multipart\n },\n body: formData,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Anthropic Files API error: ${response.status} ${response.statusText} - ${errorText}`);\n }\n\n const fileResult = await response.json() as { id: string };\n\n return {\n fileId: fileResult.id,\n url: downloadResult.url,\n contentType: downloadResult.contentType,\n sizeBytes: downloadResult.sizeBytes,\n };\n}\n","import Mux from \"@mux/mux-node\";\n\nimport { getMuxCredentialsFromEnv } from \"@mux/ai/lib/client-factory\";\nimport type { MuxAsset, PlaybackAsset, PlaybackPolicy } from \"@mux/ai/types\";\n\n/**\n * Finds a usable playback ID for the given asset.\n * Prefers public playback IDs, falls back to signed if no public is available.\n * Throws an error if no public or signed playback ID is found.\n */\nfunction getPlaybackId(asset: MuxAsset): { id: string; policy: PlaybackPolicy } {\n const playbackIds = asset.playback_ids || [];\n\n // First, try to find a public playback ID\n const publicPlaybackId = playbackIds.find(pid => pid.policy === \"public\");\n if (publicPlaybackId?.id) {\n return { id: publicPlaybackId.id, policy: \"public\" };\n }\n\n // Fall back to signed playback ID\n const signedPlaybackId = playbackIds.find(pid => pid.policy === \"signed\");\n if (signedPlaybackId?.id) {\n return { id: signedPlaybackId.id, policy: \"signed\" };\n }\n\n throw new Error(\n \"No public or signed playback ID found for this asset. \" +\n \"A public or signed playback ID is required. DRM playback IDs are not currently supported.\",\n );\n}\n\nexport async function getPlaybackIdForAsset(\n assetId: string,\n): Promise<PlaybackAsset> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n\n const asset = await mux.video.assets.retrieve(assetId);\n const { id: playbackId, policy } = getPlaybackId(asset);\n\n return { asset, playbackId, policy };\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A single section of a prompt, rendered as an XML-like tag.\n */\nexport interface PromptSection {\n /** The XML tag name for this section */\n tag: string;\n /** The content inside the tag */\n content: string;\n /** Optional attributes to add to the tag (e.g., { format: \"plain text\" }) */\n attributes?: Record<string, string>;\n}\n\n/**\n * Configuration for building a prompt section.\n * Can be a full PromptSection object, just a string (content only), or undefined to use default.\n */\nexport type SectionOverride = string | PromptSection | undefined;\n\n/**\n * A template defining the default sections for a prompt.\n * Keys are section identifiers, values are the default PromptSection definitions.\n */\nexport type PromptTemplate<TSections extends string> = Record<TSections, PromptSection>;\n\n/**\n * User-provided overrides for prompt sections.\n * Each key can override the corresponding section's content or full definition.\n */\nexport type PromptOverrides<TSections extends string> = Partial<Record<TSections, SectionOverride>>;\n\n/**\n * Configuration for the prompt builder.\n */\nexport interface PromptBuilderConfig<TSections extends string> {\n /** Default sections that make up the prompt template */\n template: PromptTemplate<TSections>;\n /** Order in which sections should appear in the final prompt */\n sectionOrder: TSections[];\n}\n\n/**\n * A configured prompt builder instance with methods to build prompts.\n */\nexport interface PromptBuilder<TSections extends string> {\n /** The default template sections */\n template: PromptTemplate<TSections>;\n /** Build a prompt string, optionally overriding specific sections */\n build: (overrides?: PromptOverrides<TSections>) => string;\n /** Build a prompt with additional dynamic sections appended */\n buildWithContext: (\n overrides?: PromptOverrides<TSections>,\n additionalSections?: PromptSection[],\n ) => string;\n /** Get a single section's content (useful for partial rendering) */\n getSection: (section: TSections, override?: SectionOverride) => string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Renders a PromptSection as an XML-like string.\n */\nexport function renderSection(section: PromptSection): string {\n const { tag, content, attributes } = section;\n\n const XML_NAME_PATTERN = /^[A-Z_][\\w.:-]*$/i;\n\n const assertValidXmlName = (name: string, context: \"tag\" | \"attribute\"): void => {\n if (!XML_NAME_PATTERN.test(name)) {\n throw new Error(`Invalid XML ${context} name: \"${name}\"`);\n }\n };\n\n const escapeXmlText = (value: string): string =>\n value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;/\");\n\n const escapeXmlAttribute = (value: string): string =>\n escapeXmlText(value).replace(/\"/g, \"&quot;\");\n\n if (!content.trim()) {\n return \"\";\n }\n\n assertValidXmlName(tag, \"tag\");\n\n const attrString = attributes ?\n ` ${\n Object.entries(attributes)\n .map(([key, value]) => {\n assertValidXmlName(key, \"attribute\");\n return `${key}=\"${escapeXmlAttribute(value)}\"`;\n })\n .join(\" \")}` :\n \"\";\n\n const safeContent = escapeXmlText(content.trim());\n\n return `<${tag}${attrString}>\\n${safeContent}\\n</${tag}>`;\n}\n\n/**\n * Resolves a section override to a full PromptSection.\n */\nfunction resolveSection(\n defaultSection: PromptSection,\n override?: SectionOverride,\n): PromptSection {\n if (override === undefined) {\n return defaultSection;\n }\n\n if (typeof override === \"string\") {\n return { ...defaultSection, content: override };\n }\n\n return override;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Factory\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Creates a type-safe prompt builder from a template configuration.\n *\n * @example\n * ```typescript\n * const builder = createPromptBuilder({\n * template: {\n * task: { tag: 'task', content: 'Analyze the image...' },\n * tone: { tag: 'tone', content: 'Be professional.' },\n * },\n * sectionOrder: ['task', 'tone'],\n * });\n *\n * // Use defaults\n * const prompt1 = builder.build();\n *\n * // Override specific sections\n * const prompt2 = builder.build({\n * tone: 'Be casual and friendly.',\n * });\n * ```\n */\nexport function createPromptBuilder<TSections extends string>(\n config: PromptBuilderConfig<TSections>,\n): PromptBuilder<TSections> {\n const { template, sectionOrder } = config;\n\n const getSection = (section: TSections, override?: SectionOverride): string => {\n const resolved = resolveSection(template[section], override);\n return renderSection(resolved);\n };\n\n const build = (overrides?: PromptOverrides<TSections>): string => {\n const sections = sectionOrder\n .map(sectionKey => getSection(sectionKey, overrides?.[sectionKey]))\n .filter(Boolean);\n\n return sections.join(\"\\n\\n\");\n };\n\n const buildWithContext = (\n overrides?: PromptOverrides<TSections>,\n additionalSections?: PromptSection[],\n ): string => {\n const basePrompt = build(overrides);\n\n if (!additionalSections?.length) {\n return basePrompt;\n }\n\n const additional = additionalSections\n .map(renderSection)\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n return additional ? `${basePrompt}\\n\\n${additional}` : basePrompt;\n };\n\n return {\n template,\n build,\n buildWithContext,\n getSection,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Common Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Creates a transcript section for inclusion in prompts.\n */\nexport function createTranscriptSection(\n transcriptText: string,\n format: \"plain text\" | \"WebVTT\" = \"plain text\",\n): PromptSection {\n return {\n tag: \"transcript\",\n content: transcriptText,\n attributes: { format },\n };\n}\n\n/**\n * Creates a tone section for inclusion in prompts.\n */\nexport function createToneSection(instruction: string): PromptSection {\n return {\n tag: \"tone\",\n content: instruction,\n };\n}\n","import Mux from \"@mux/mux-node\";\n\nimport env from \"@mux/ai/env\";\n\n/**\n * Context required to sign URLs for signed playback IDs.\n */\nexport interface SigningContext {\n /** The signing key ID from Mux dashboard. */\n keyId: string;\n /** The base64-encoded private key from Mux dashboard. */\n keySecret: string;\n /** Token expiration time (e.g. '1h', '1d'). Defaults to '1h'. */\n expiration?: string;\n}\n\n/**\n * Token type determines which Mux service the token is valid for.\n */\nexport type TokenType = \"video\" | \"thumbnail\" | \"storyboard\" | \"gif\";\n\n/**\n * Resolves signing context from config or environment variables.\n * Returns undefined if signing keys are not configured.\n */\nexport function getMuxSigningContextFromEnv(): SigningContext | undefined {\n const keyId = env.MUX_SIGNING_KEY;\n const keySecret = env.MUX_PRIVATE_KEY;\n\n if (!keyId || !keySecret) {\n return undefined;\n }\n\n return { keyId, keySecret };\n}\n\n/**\n * Creates a Mux client configured for JWT signing.\n * This client is used internally for signing operations.\n */\nfunction createSigningClient(context: SigningContext): Mux {\n return new Mux({\n // These are not needed for signing, but the SDK requires them\n // Using empty strings as we only need the jwt functionality\n tokenId: env.MUX_TOKEN_ID || \"\",\n tokenSecret: env.MUX_TOKEN_SECRET || \"\",\n jwtSigningKey: context.keyId,\n jwtPrivateKey: context.keySecret,\n });\n}\n\n/**\n * Generates a signed token for a playback ID using the Mux SDK.\n *\n * @param playbackId - The Mux playback ID to sign\n * @param context - Signing context with key credentials\n * @param type - Token type (video, thumbnail, storyboard, gif)\n * @param params - Additional parameters for thumbnail/storyboard tokens (values will be stringified)\n * @returns Signed JWT token\n */\nexport async function signPlaybackId(\n playbackId: string,\n context: SigningContext,\n type: TokenType = \"video\",\n params?: Record<string, string | number>,\n): Promise<string> {\n \"use step\";\n const client = createSigningClient(context);\n\n // Convert params to Record<string, string> as required by the SDK\n const stringParams = params ?\n Object.fromEntries(\n Object.entries(params).map(([key, value]) => [key, String(value)]),\n ) :\n undefined;\n\n return client.jwt.signPlaybackId(playbackId, {\n type,\n expiration: context.expiration || \"1h\",\n params: stringParams,\n });\n}\n\n/**\n * Appends a signed token to a Mux URL.\n *\n * @param url - The base Mux URL (e.g. https://image.mux.com/{playbackId}/thumbnail.png)\n * @param playbackId - The Mux playback ID\n * @param context - Signing context with key credentials\n * @param type - Token type for the URL\n * @param params - Additional parameters for the token\n * @returns URL with token query parameter appended\n */\nexport async function signUrl(\n url: string,\n playbackId: string,\n context: SigningContext,\n type: TokenType = \"video\",\n params?: Record<string, string | number>,\n): Promise<string> {\n \"use step\";\n const token = await signPlaybackId(playbackId, context, type, params);\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}token=${token}`;\n}\n","import { getMuxSigningContextFromEnv, signUrl } from \"@mux/ai/lib/url-signing\";\n\nexport const DEFAULT_STORYBOARD_WIDTH = 640;\n\n/**\n * Generates a storyboard URL for the given playback ID.\n * If shouldSign is true, the URL will be signed with a token using credentials from environment variables.\n *\n * @param playbackId - The Mux playback ID\n * @param width - Width of the storyboard in pixels (default: 640)\n * @param shouldSign - Flag for whether or not to use signed playback IDs (default: false)\n * @returns Storyboard URL (signed if shouldSign is true)\n */\nexport async function getStoryboardUrl(\n playbackId: string,\n width: number = DEFAULT_STORYBOARD_WIDTH,\n shouldSign: boolean = false,\n): Promise<string> {\n \"use step\";\n const baseUrl = `https://image.mux.com/${playbackId}/storyboard.png`;\n\n if (shouldSign) {\n // NOTE: this assumes you have already validated the signing context elsewhere\n const signingContext = getMuxSigningContextFromEnv();\n return signUrl(baseUrl, playbackId, signingContext!, \"storyboard\", { width });\n }\n\n return `${baseUrl}?width=${width}`;\n}\n","import { generateObject } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createWorkflowConfig } from \"@mux/ai/lib/client-factory\";\nimport { getPlaybackIdForAsset } from \"@mux/ai/lib/mux-assets\";\nimport { createLanguageModelFromConfig } from \"@mux/ai/lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"@mux/ai/lib/providers\";\nimport { withRetry } from \"@mux/ai/lib/retry\";\nimport { getMuxSigningContextFromEnv } from \"@mux/ai/lib/url-signing\";\nimport {\n extractTimestampedTranscript,\n fetchTranscriptForAsset,\n getReadyTextTracks,\n} from \"@mux/ai/primitives/transcripts\";\nimport type { MuxAIOptions } from \"@mux/ai/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const chapterSchema = z.object({\n startTime: z.number(),\n title: z.string(),\n});\n\nexport type Chapter = z.infer<typeof chapterSchema>;\n\nexport const chaptersSchema = z.object({\n chapters: z.array(chapterSchema),\n});\n\nexport type ChaptersType = z.infer<typeof chaptersSchema>;\n\n/** Structured return payload from `generateChapters`. */\nexport interface ChaptersResult {\n assetId: string;\n languageCode: string;\n chapters: Chapter[];\n}\n\n/** Configuration accepted by `generateChapters`. */\nexport interface ChaptersOptions extends MuxAIOptions {\n /** AI provider used to interpret the transcript (defaults to 'openai'). */\n provider?: SupportedProvider;\n /** Provider-specific model identifier. */\n model?: ModelIdByProvider[SupportedProvider];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nasync function generateChaptersWithAI({\n provider,\n modelId,\n timestampedTranscript,\n systemPrompt,\n}: {\n provider: SupportedProvider;\n modelId: string;\n timestampedTranscript: string;\n systemPrompt: string;\n}): Promise<ChaptersType> {\n \"use step\";\n\n const model = createLanguageModelFromConfig(provider, modelId);\n\n const response = await withRetry(() =>\n generateObject({\n model,\n schema: chaptersSchema,\n messages: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n {\n role: \"user\",\n content: timestampedTranscript,\n },\n ],\n }),\n );\n\n return response.object;\n}\n\nconst SYSTEM_PROMPT = `Your role is to segment the following captions into chunked chapters, summarising each chapter with a title.\n\nAnalyze the transcript and create logical chapter breaks based on topic changes, major transitions, or distinct sections of content. Each chapter should represent a meaningful segment of the video.\n\nYou must respond with valid JSON in exactly this format:\n{\n \"chapters\": [\n {\"startTime\": 0, \"title\": \"Introduction\"},\n {\"startTime\": 45.5, \"title\": \"Main Topic Discussion\"},\n {\"startTime\": 120.0, \"title\": \"Conclusion\"}\n ]\n}\n\nImportant rules:\n- startTime must be in seconds (not HH:MM:SS format)\n- Always start with startTime: 0 for the first chapter\n- Create 3-8 chapters depending on content length and natural breaks\n- Chapter titles should be concise and descriptive\n- Do not include any text before or after the JSON\n- The JSON must be valid and parseable`;\n\nexport async function generateChapters(\n assetId: string,\n languageCode: string,\n options: ChaptersOptions = {},\n): Promise<ChaptersResult> {\n \"use workflow\";\n const { provider = \"openai\", model } = options;\n\n // Validate credentials and resolve language model\n const config = await createWorkflowConfig({ ...options, model }, provider as SupportedProvider);\n\n // Fetch asset and caption track/transcript\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n const transcriptResult = await fetchTranscriptForAsset(assetData, playbackId, {\n languageCode,\n cleanTranscript: false, // keep timestamps for chapter segmentation\n shouldSign: policy === \"signed\",\n });\n\n if (!transcriptResult.track || !transcriptResult.transcriptText) {\n const availableLanguages = getReadyTextTracks(assetData)\n .map(t => t.language_code)\n .filter(Boolean)\n .join(\", \");\n throw new Error(\n `No caption track found for language '${languageCode}'. Available languages: ${availableLanguages || \"none\"}`,\n );\n }\n\n const timestampedTranscript = extractTimestampedTranscript(transcriptResult.transcriptText);\n if (!timestampedTranscript) {\n throw new Error(\"No usable content found in caption track\");\n }\n\n // Generate chapters using AI SDK\n let chaptersData: ChaptersType | null = null;\n\n try {\n chaptersData = await generateChaptersWithAI({\n provider: config.provider,\n modelId: config.modelId,\n timestampedTranscript,\n systemPrompt: SYSTEM_PROMPT,\n });\n } catch (error) {\n throw new Error(\n `Failed to generate chapters with ${provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n if (!chaptersData || !chaptersData.chapters) {\n throw new Error(\"No chapters generated from AI response\");\n }\n\n // Validate and sort chapters\n const validChapters = chaptersData.chapters\n .filter(chapter => typeof chapter.startTime === \"number\" && typeof chapter.title === \"string\")\n .sort((a, b) => a.startTime - b.startTime);\n\n if (validChapters.length === 0) {\n throw new Error(\"No valid chapters found in AI response\");\n }\n\n // Ensure first chapter starts at 0\n if (validChapters[0].startTime !== 0) {\n validChapters[0].startTime = 0;\n }\n\n return {\n assetId,\n languageCode,\n chapters: validChapters,\n };\n}\n","/**\n * Retry configuration options\n */\nexport interface RetryOptions {\n maxRetries?: number;\n baseDelay?: number;\n maxDelay?: number;\n shouldRetry?: (error: Error, attempt: number) => boolean;\n}\n\nconst DEFAULT_RETRY_OPTIONS: Required<Omit<RetryOptions, \"shouldRetry\">> = {\n maxRetries: 3,\n baseDelay: 2000,\n maxDelay: 10000,\n};\n\n/**\n * Default retry condition - retries on timeout errors\n */\nfunction defaultShouldRetry(error: Error, _attempt: number): boolean {\n return Boolean(error.message && error.message.includes(\"Timeout while downloading\"));\n}\n\n/**\n * Calculates exponential backoff delay with jitter\n */\nfunction calculateDelay(attempt: number, baseDelay: number, maxDelay: number): number {\n const exponentialDelay = baseDelay * 2 ** (attempt - 1);\n const delayWithJitter = exponentialDelay * (0.5 + Math.random() * 0.5);\n return Math.min(delayWithJitter, maxDelay);\n}\n\n/**\n * Executes an async function with retry logic\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n {\n maxRetries = DEFAULT_RETRY_OPTIONS.maxRetries,\n baseDelay = DEFAULT_RETRY_OPTIONS.baseDelay,\n maxDelay = DEFAULT_RETRY_OPTIONS.maxDelay,\n shouldRetry = defaultShouldRetry,\n }: RetryOptions = {},\n): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n const isLastAttempt = attempt === maxRetries;\n if (isLastAttempt || !shouldRetry(lastError, attempt + 1)) {\n throw lastError;\n }\n\n const delay = calculateDelay(attempt + 1, baseDelay, maxDelay);\n console.warn(\n `Attempt ${attempt + 1} failed: ${lastError.message}. Retrying in ${Math.round(delay)}ms...`,\n );\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n throw lastError || new Error(\"Retry failed with unknown error\");\n}\n","import { getMuxSigningContextFromEnv, signUrl } from \"@mux/ai/lib/url-signing\";\nimport type { AssetTextTrack, MuxAsset } from \"@mux/ai/types\";\n\n/** A single cue from a VTT file with timing info. */\nexport interface VTTCue {\n startTime: number;\n endTime: number;\n text: string;\n}\n\nexport interface TranscriptFetchOptions {\n languageCode?: string;\n cleanTranscript?: boolean;\n /** Optional signing context for signed playback IDs */\n shouldSign?: boolean;\n}\n\nexport interface TranscriptResult {\n transcriptText: string;\n transcriptUrl?: string;\n track?: AssetTextTrack;\n}\n\nexport function getReadyTextTracks(asset: MuxAsset): AssetTextTrack[] {\n return (asset.tracks || []).filter(\n track => track.type === \"text\" && track.status === \"ready\",\n );\n}\n\nexport function findCaptionTrack(asset: MuxAsset, languageCode?: string): AssetTextTrack | undefined {\n const tracks = getReadyTextTracks(asset);\n if (!tracks.length)\n return undefined;\n\n if (!languageCode) {\n return tracks[0];\n }\n\n return tracks.find(\n track =>\n track.text_type === \"subtitles\" &&\n track.language_code === languageCode,\n );\n}\n\nexport function extractTextFromVTT(vttContent: string): string {\n if (!vttContent.trim()) {\n return \"\";\n }\n\n const lines = vttContent.split(\"\\n\");\n const textLines: string[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n if (!line)\n continue;\n if (line === \"WEBVTT\")\n continue;\n if (line.startsWith(\"NOTE \"))\n continue;\n if (line.includes(\"-->\"))\n continue;\n if (/^[\\w-]+$/.test(line) && !line.includes(\" \"))\n continue;\n if (line.startsWith(\"STYLE\") || line.startsWith(\"REGION\"))\n continue;\n\n const cleanLine = line.replace(/<[^>]*>/g, \"\").trim();\n\n if (cleanLine) {\n textLines.push(cleanLine);\n }\n }\n\n return textLines.join(\" \").replace(/\\s+/g, \" \").trim();\n}\n\nexport function vttTimestampToSeconds(timestamp: string): number {\n const parts = timestamp.split(\":\");\n if (parts.length !== 3)\n return 0;\n\n const hours = Number.parseInt(parts[0], 10) || 0;\n const minutes = Number.parseInt(parts[1], 10) || 0;\n const seconds = Number.parseFloat(parts[2]) || 0;\n\n return hours * 3600 + minutes * 60 + seconds;\n}\n\nexport function extractTimestampedTranscript(vttContent: string): string {\n if (!vttContent.trim()) {\n return \"\";\n }\n\n const lines = vttContent.split(\"\\n\");\n const segments: Array<{ time: number; text: string }> = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n if (line.includes(\"-->\")) {\n const startTime = line.split(\" --> \")[0].trim();\n const timeInSeconds = vttTimestampToSeconds(startTime);\n\n let j = i + 1;\n while (j < lines.length && !lines[j].trim()) {\n j++;\n }\n\n if (j < lines.length) {\n const text = lines[j].trim().replace(/<[^>]*>/g, \"\");\n if (text) {\n segments.push({ time: timeInSeconds, text });\n }\n }\n }\n }\n\n return segments\n .map(segment => `[${Math.floor(segment.time)}s] ${segment.text}`)\n .join(\"\\n\");\n}\n\n/**\n * Parses VTT content into structured cues with timing.\n *\n * @param vttContent - Raw VTT file content\n * @returns Array of VTT cues with start/end times and text\n */\nexport function parseVTTCues(vttContent: string): VTTCue[] {\n if (!vttContent.trim())\n return [];\n\n const lines = vttContent.split(\"\\n\");\n const cues: VTTCue[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n if (line.includes(\"-->\")) {\n const [startStr, endStr] = line.split(\" --> \").map(s => s.trim());\n const startTime = vttTimestampToSeconds(startStr);\n const endTime = vttTimestampToSeconds(endStr.split(\" \")[0]); // Handle cue settings\n\n // Collect text lines until empty line or next timestamp\n const textLines: string[] = [];\n let j = i + 1;\n while (j < lines.length && lines[j].trim() && !lines[j].includes(\"-->\")) {\n const cleanLine = lines[j].trim().replace(/<[^>]*>/g, \"\");\n if (cleanLine)\n textLines.push(cleanLine);\n j++;\n }\n\n if (textLines.length > 0) {\n cues.push({\n startTime,\n endTime,\n text: textLines.join(\" \"),\n });\n }\n }\n }\n\n return cues;\n}\n\n/**\n * Builds a transcript URL for the given playback ID and track ID.\n * If a signing context is provided, the URL will be signed with a token.\n *\n * @param playbackId - The Mux playback ID\n * @param trackId - The text track ID\n * @param shouldSign - Flag for whether or not to use signed playback IDs\n * @returns Transcript URL (signed if context provided)\n */\nexport async function buildTranscriptUrl(\n playbackId: string,\n trackId: string,\n shouldSign: boolean = false,\n): Promise<string> {\n \"use step\";\n const baseUrl = `https://stream.mux.com/${playbackId}/text/${trackId}.vtt`;\n\n if (shouldSign) {\n // NOTE: this assumes you have already validated the signing context elsewhere\n const signingContext = getMuxSigningContextFromEnv();\n return signUrl(baseUrl, playbackId, signingContext!, \"video\");\n }\n\n return baseUrl;\n}\n\nexport async function fetchTranscriptForAsset(\n asset: MuxAsset,\n playbackId: string,\n options: TranscriptFetchOptions = {},\n): Promise<TranscriptResult> {\n \"use step\";\n const { languageCode, cleanTranscript = true, shouldSign } = options;\n const track = findCaptionTrack(asset, languageCode);\n\n if (!track) {\n return { transcriptText: \"\" };\n }\n\n if (!track.id) {\n return { transcriptText: \"\", track };\n }\n\n const transcriptUrl = await buildTranscriptUrl(playbackId, track.id, shouldSign);\n\n try {\n const response = await fetch(transcriptUrl);\n if (!response.ok) {\n return { transcriptText: \"\", transcriptUrl, track };\n }\n\n const rawVtt = await response.text();\n const transcriptText = cleanTranscript ? extractTextFromVTT(rawVtt) : rawVtt;\n\n return { transcriptText, transcriptUrl, track };\n } catch (error) {\n console.warn(\"Failed to fetch transcript:\", error);\n return { transcriptText: \"\", transcriptUrl, track };\n }\n}\n","import { embed } from \"ai\";\n\nimport { getPlaybackIdForAsset } from \"@mux/ai/lib/mux-assets\";\nimport type { EmbeddingModelIdByProvider, SupportedEmbeddingProvider } from \"@mux/ai/lib/providers\";\nimport { createEmbeddingModelFromConfig, resolveEmbeddingModel } from \"@mux/ai/lib/providers\";\nimport { withRetry } from \"@mux/ai/lib/retry\";\nimport { getMuxSigningContextFromEnv } from \"@mux/ai/lib/url-signing\";\nimport { chunkText, chunkVTTCues } from \"@mux/ai/primitives/text-chunking\";\nimport { fetchTranscriptForAsset, getReadyTextTracks, parseVTTCues } from \"@mux/ai/primitives/transcripts\";\nimport type {\n ChunkEmbedding,\n ChunkingStrategy,\n MuxAIOptions,\n TextChunk,\n VideoEmbeddingsResult,\n} from \"@mux/ai/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Configuration accepted by `generateVideoEmbeddings`. */\nexport interface EmbeddingsOptions extends MuxAIOptions {\n /** AI provider used to generate embeddings (defaults to 'openai'). */\n provider?: SupportedEmbeddingProvider;\n /** Provider-specific model identifier (defaults to text-embedding-3-small for OpenAI). */\n model?: EmbeddingModelIdByProvider[SupportedEmbeddingProvider];\n /** Language code for transcript selection (defaults to first available). */\n languageCode?: string;\n /** Chunking strategy configuration (defaults to token-based with 500 tokens, 100 overlap). */\n chunkingStrategy?: ChunkingStrategy;\n /** Maximum number of chunks to process concurrently (defaults to 5). */\n batchSize?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Averages multiple embedding vectors into a single vector.\n *\n * @param embeddings - Array of embedding vectors to average\n * @returns Single averaged embedding vector\n */\nfunction averageEmbeddings(embeddings: number[][]): number[] {\n if (embeddings.length === 0) {\n return [];\n }\n\n const dimensions = embeddings[0].length;\n const averaged = Array.from({ length: dimensions }, () => 0);\n\n for (const embedding of embeddings) {\n for (let i = 0; i < dimensions; i++) {\n averaged[i] += embedding[i];\n }\n }\n\n for (let i = 0; i < dimensions; i++) {\n averaged[i] /= embeddings.length;\n }\n\n return averaged;\n}\n\n/**\n * Generates embedding for a single text chunk using the specified AI provider.\n *\n * @param options - Configuration object\n * @param options.chunk - Text chunk to embed\n * @param options.provider - AI provider for embedding generation\n * @param options.modelId - Provider-specific model identifier\n * @returns Chunk embedding with metadata\n */\nasync function generateSingleChunkEmbedding({\n chunk,\n provider,\n modelId,\n}: {\n chunk: TextChunk;\n provider: SupportedEmbeddingProvider;\n modelId: string;\n}): Promise<ChunkEmbedding> {\n \"use step\";\n\n const model = createEmbeddingModelFromConfig(provider, modelId);\n const response = await withRetry(() =>\n embed({\n model,\n value: chunk.text,\n }),\n );\n\n return {\n chunkId: chunk.id,\n embedding: response.embedding,\n metadata: {\n startTime: chunk.startTime,\n endTime: chunk.endTime,\n tokenCount: chunk.tokenCount,\n },\n };\n}\n\n/**\n * Generates vector embeddings for a video asset's transcript.\n *\n * This function:\n * 1. Fetches the video transcript from Mux\n * 2. Chunks the transcript according to the specified strategy\n * 3. Generates embeddings for each chunk using the specified AI provider\n * 4. Returns both individual chunk embeddings and an averaged embedding\n *\n * @param assetId - Mux asset ID\n * @param options - Configuration options\n * @returns Video embeddings result with chunks and averaged embedding\n *\n * @example\n * ```typescript\n * const embeddings = await generateVideoEmbeddings(\"asset-id\", {\n * provider: \"openai\",\n * chunkingStrategy: { type: \"token\", maxTokens: 500, overlap: 100 },\n * });\n *\n * // Store in vector database\n * for (const chunk of embeddings.chunks) {\n * await db.insert({\n * assetId: embeddings.assetId,\n * chunkId: chunk.chunkId,\n * embedding: chunk.embedding,\n * metadata: chunk.metadata,\n * });\n * }\n * ```\n */\nexport async function generateVideoEmbeddings(\n assetId: string,\n options: EmbeddingsOptions = {},\n): Promise<VideoEmbeddingsResult> {\n \"use workflow\";\n const {\n provider = \"openai\",\n model,\n languageCode,\n chunkingStrategy = { type: \"token\", maxTokens: 500, overlap: 100 } as ChunkingStrategy,\n batchSize = 5,\n } = options;\n\n const embeddingModel = resolveEmbeddingModel({ ...options, provider, model });\n\n // Fetch asset and playback ID\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Fetch transcript (raw VTT for VTT strategy, cleaned text otherwise)\n const useVttChunking = chunkingStrategy.type === \"vtt\";\n const transcriptResult = await fetchTranscriptForAsset(assetData, playbackId, {\n languageCode,\n cleanTranscript: !useVttChunking,\n shouldSign: policy === \"signed\",\n });\n\n if (!transcriptResult.track || !transcriptResult.transcriptText) {\n const availableLanguages = getReadyTextTracks(assetData)\n .map(t => t.language_code)\n .filter(Boolean)\n .join(\", \");\n throw new Error(\n `No caption track found${languageCode ? ` for language '${languageCode}'` : \"\"}. Available languages: ${availableLanguages || \"none\"}`,\n );\n }\n\n const transcriptText = transcriptResult.transcriptText;\n if (!transcriptText.trim()) {\n throw new Error(\"Transcript is empty\");\n }\n\n // Chunk the transcript\n const chunks = useVttChunking ?\n chunkVTTCues(\n parseVTTCues(transcriptText),\n chunkingStrategy.maxTokens,\n chunkingStrategy.overlapCues,\n ) :\n chunkText(transcriptText, chunkingStrategy);\n if (chunks.length === 0) {\n throw new Error(\"No chunks generated from transcript\");\n }\n\n // Generate embeddings for all chunks (process in batches)\n const chunkEmbeddings: ChunkEmbedding[] = [];\n try {\n for (let i = 0; i < chunks.length; i += batchSize) {\n const batch = chunks.slice(i, i + batchSize);\n\n const batchResults = await Promise.all(\n batch.map(chunk =>\n generateSingleChunkEmbedding({\n chunk,\n provider: embeddingModel.provider,\n modelId: embeddingModel.modelId as string,\n }),\n ),\n );\n\n chunkEmbeddings.push(...batchResults);\n }\n } catch (error) {\n throw new Error(\n `Failed to generate embeddings with ${provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n if (chunkEmbeddings.length === 0) {\n throw new Error(\"No embeddings generated\");\n }\n\n // Calculate averaged embedding\n const averagedEmbedding = averageEmbeddings(chunkEmbeddings.map(ce => ce.embedding));\n\n // Calculate total tokens\n const totalTokens = chunks.reduce((sum, chunk) => sum + chunk.tokenCount, 0);\n\n return {\n assetId,\n chunks: chunkEmbeddings,\n averagedEmbedding,\n provider,\n model: embeddingModel.modelId,\n metadata: {\n totalChunks: chunks.length,\n totalTokens,\n chunkingStrategy: JSON.stringify(chunkingStrategy),\n embeddingDimensions: chunkEmbeddings[0].embedding.length,\n generatedAt: new Date().toISOString(),\n },\n };\n}\n","import type { VTTCue } from \"@mux/ai/primitives/transcripts\";\nimport type { ChunkingStrategy, TextChunk } from \"@mux/ai/types\";\n\n/**\n * Simple token counter that approximates tokens by word count.\n * For production use with OpenAI, consider using a proper tokenizer like tiktoken.\n * This approximation is generally close enough for chunking purposes (1 token ≈ 0.75 words).\n */\nexport function estimateTokenCount(text: string): number {\n const words = text.trim().split(/\\s+/).length;\n return Math.ceil(words / 0.75);\n}\n\n/**\n * Chunks text into overlapping segments based on token count.\n *\n * @param text - The text to chunk\n * @param maxTokens - Maximum tokens per chunk\n * @param overlapTokens - Number of tokens to overlap between chunks\n * @returns Array of text chunks with metadata\n */\nexport function chunkByTokens(\n text: string,\n maxTokens: number,\n overlapTokens: number = 0,\n): TextChunk[] {\n if (!text.trim()) {\n return [];\n }\n\n const chunks: TextChunk[] = [];\n const words = text.trim().split(/\\s+/);\n\n // Convert tokens to approximate word count\n const wordsPerChunk = Math.floor(maxTokens * 0.75);\n const overlapWords = Math.floor(overlapTokens * 0.75);\n\n let chunkIndex = 0;\n let currentPosition = 0;\n\n while (currentPosition < words.length) {\n const chunkWords = words.slice(\n currentPosition,\n currentPosition + wordsPerChunk,\n );\n const chunkText = chunkWords.join(\" \");\n const tokenCount = estimateTokenCount(chunkText);\n\n chunks.push({\n id: `chunk-${chunkIndex}`,\n text: chunkText,\n tokenCount,\n });\n\n // Move forward by chunk size minus overlap\n currentPosition += wordsPerChunk - overlapWords;\n chunkIndex++;\n\n // Prevent infinite loop if overlap is too large\n if (currentPosition <= (chunkIndex - 1) * (wordsPerChunk - overlapWords)) {\n break;\n }\n }\n\n return chunks;\n}\n\n/**\n * Creates a TextChunk from a group of VTT cues.\n */\nfunction createChunkFromCues(cues: VTTCue[], index: number): TextChunk {\n const text = cues.map(c => c.text).join(\" \");\n return {\n id: `chunk-${index}`,\n text,\n tokenCount: estimateTokenCount(text),\n startTime: cues[0].startTime,\n endTime: cues[cues.length - 1].endTime,\n };\n}\n\n/**\n * Chunks VTT cues into groups that respect natural cue boundaries.\n * Splits at cue boundaries rather than mid-sentence, preserving accurate timestamps.\n *\n * @param cues - Array of VTT cues to chunk\n * @param maxTokens - Maximum tokens per chunk\n * @param overlapCues - Number of cues to overlap between chunks (default: 2)\n * @returns Array of text chunks with accurate start/end times\n */\nexport function chunkVTTCues(\n cues: VTTCue[],\n maxTokens: number,\n overlapCues: number = 2,\n): TextChunk[] {\n if (cues.length === 0)\n return [];\n\n const chunks: TextChunk[] = [];\n let currentCues: VTTCue[] = [];\n let currentTokens = 0;\n let chunkIndex = 0;\n\n for (let i = 0; i < cues.length; i++) {\n const cue = cues[i];\n const cueTokens = estimateTokenCount(cue.text);\n\n // If adding this cue would exceed limit, finalize current chunk\n if (currentTokens + cueTokens > maxTokens && currentCues.length > 0) {\n chunks.push(createChunkFromCues(currentCues, chunkIndex));\n chunkIndex++;\n\n // Start new chunk with overlap from end of previous\n const overlapStart = Math.max(0, currentCues.length - overlapCues);\n currentCues = currentCues.slice(overlapStart);\n currentTokens = currentCues.reduce(\n (sum, c) => sum + estimateTokenCount(c.text),\n 0,\n );\n }\n\n currentCues.push(cue);\n currentTokens += cueTokens;\n }\n\n // Don't forget the last chunk\n if (currentCues.length > 0) {\n chunks.push(createChunkFromCues(currentCues, chunkIndex));\n }\n\n return chunks;\n}\n\n/**\n * Chunks text according to the specified strategy.\n *\n * @param text - The text to chunk\n * @param strategy - The chunking strategy to use\n * @returns Array of text chunks\n */\nexport function chunkText(text: string, strategy: ChunkingStrategy): TextChunk[] {\n switch (strategy.type) {\n case \"token\": {\n return chunkByTokens(text, strategy.maxTokens, strategy.overlap ?? 0);\n }\n default: {\n const exhaustiveCheck: never = strategy as never;\n throw new Error(`Unsupported chunking strategy: ${exhaustiveCheck}`);\n }\n }\n}\n","import { getMuxSigningContextFromEnv, signUrl } from \"@mux/ai/lib/url-signing\";\n\nexport interface ThumbnailOptions {\n /** Interval between thumbnails in seconds (default: 10) */\n interval?: number;\n /** Width of the thumbnail in pixels (default: 640) */\n width?: number;\n /** Flag for whether or not to use signed playback IDs (default: false) */\n shouldSign?: boolean;\n}\n\n/**\n * Generates thumbnail URLs at regular intervals based on video duration.\n * If shouldSign is true, the URLs will be signed with tokens using credentials from environment variables.\n *\n * @param playbackId - The Mux playback ID\n * @param duration - Video duration in seconds\n * @param options - Thumbnail generation options\n * @returns Array of thumbnail URLs (signed if shouldSign is true)\n */\nexport async function getThumbnailUrls(\n playbackId: string,\n duration: number,\n options: ThumbnailOptions = {},\n): Promise<string[]> {\n \"use step\";\n const { interval = 10, width = 640, shouldSign = false } = options;\n const timestamps: number[] = [];\n\n if (duration <= 50) {\n const spacing = duration / 6;\n for (let i = 1; i <= 5; i++) {\n timestamps.push(Math.round(i * spacing));\n }\n } else {\n for (let time = 0; time < duration; time += interval) {\n timestamps.push(time);\n }\n }\n\n const baseUrl = `https://image.mux.com/${playbackId}/thumbnail.png`;\n\n const urlPromises = timestamps.map(async (time) => {\n if (shouldSign) {\n // NOTE: this assumes you have already validated the signing context elsewhere\n const signingContext = getMuxSigningContextFromEnv();\n return signUrl(baseUrl, playbackId, signingContext!, \"thumbnail\", { time, width });\n }\n\n return `${baseUrl}?time=${time}&width=${width}`;\n });\n\n return Promise.all(urlPromises);\n}\n","import { getApiKeyFromEnv } from \"@mux/ai/lib/client-factory\";\nimport type { ImageDownloadOptions } from \"@mux/ai/lib/image-download\";\nimport { downloadImagesAsBase64 } from \"@mux/ai/lib/image-download\";\nimport { getPlaybackIdForAsset } from \"@mux/ai/lib/mux-assets\";\nimport { getMuxSigningContextFromEnv } from \"@mux/ai/lib/url-signing\";\nimport { getThumbnailUrls } from \"@mux/ai/primitives/thumbnails\";\nimport type { ImageSubmissionMode, MuxAIOptions } from \"@mux/ai/types\";\n\nimport type { Buffer } from \"node:buffer\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Per-thumbnail moderation result returned from `getModerationScores`. */\nexport interface ThumbnailModerationScore {\n url: string;\n sexual: number;\n violence: number;\n error: boolean;\n}\n\n/** Aggregated moderation payload returned from `getModerationScores`. */\nexport interface ModerationResult {\n assetId: string;\n thumbnailScores: ThumbnailModerationScore[];\n maxScores: {\n sexual: number;\n violence: number;\n };\n exceedsThreshold: boolean;\n thresholds: {\n sexual: number;\n violence: number;\n };\n}\n\n/** Provider list accepted by `getModerationScores`. */\nexport type ModerationProvider = \"openai\" | \"hive\";\n\nexport type HiveModerationSource =\n | { kind: \"url\"; value: string } |\n { kind: \"file\"; buffer: Buffer; contentType: string };\n\nexport interface HiveModerationOutput {\n classes?: Array<{\n class: string;\n score: number;\n }>;\n}\n\n/** Configuration accepted by `getModerationScores`. */\nexport interface ModerationOptions extends MuxAIOptions {\n /** Provider used for moderation (defaults to 'openai'). */\n provider?: ModerationProvider;\n /** OpenAI moderation model identifier (defaults to 'omni-moderation-latest'). */\n model?: string;\n /** Override the default sexual/violence thresholds (0-1). */\n thresholds?: {\n sexual?: number;\n violence?: number;\n };\n /** Interval between storyboard thumbnails in seconds (defaults to 10). */\n thumbnailInterval?: number;\n /** Width of storyboard thumbnails in pixels (defaults to 640). */\n thumbnailWidth?: number;\n /** Max concurrent moderation requests (defaults to 5). */\n maxConcurrent?: number;\n /** Transport used for thumbnails (defaults to 'url'). */\n imageSubmissionMode?: ImageSubmissionMode;\n /** Download tuning used when `imageSubmissionMode` === 'base64'. */\n imageDownloadOptions?: ImageDownloadOptions;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst DEFAULT_THRESHOLDS = {\n sexual: 0.7,\n violence: 0.8,\n};\n\nconst DEFAULT_PROVIDER = \"openai\";\n\nconst HIVE_ENDPOINT = \"https://api.thehive.ai/api/v2/task/sync\";\nconst HIVE_SEXUAL_CATEGORIES = [\n \"general_nsfw\",\n \"general_suggestive\",\n \"yes_sexual_activity\",\n \"female_underwear\",\n \"male_underwear\",\n \"bra\",\n \"panties\",\n \"sex_toys\",\n \"nudity_female\",\n \"nudity_male\",\n \"cleavage\",\n \"swimwear\",\n];\n\nconst HIVE_VIOLENCE_CATEGORIES = [\n \"gun_in_hand\",\n \"gun_not_in_hand\",\n \"animated_gun\",\n \"knife_in_hand\",\n \"knife_not_in_hand\",\n \"culinary_knife_not_in_hand\",\n \"culinary_knife_in_hand\",\n \"very_bloody\",\n \"a_little_bloody\",\n \"other_blood\",\n \"hanging\",\n \"noose\",\n \"human_corpse\",\n \"animated_corpse\",\n \"emaciated_body\",\n \"self_harm\",\n \"animal_abuse\",\n \"fights\",\n \"garm_death_injury_or_military_conflict\",\n];\n\nasync function processConcurrently<T>(\n items: any[],\n processor: (item: any) => Promise<T>,\n maxConcurrent: number = 5,\n): Promise<T[]> {\n \"use step\";\n const results: T[] = [];\n\n for (let i = 0; i < items.length; i += maxConcurrent) {\n const batch = items.slice(i, i + maxConcurrent);\n const batchPromises = batch.map(processor);\n const batchResults = await Promise.all(batchPromises);\n results.push(...batchResults);\n }\n\n return results;\n}\n\nasync function moderateImageWithOpenAI(entry: { url: string; image: string; model: string }): Promise<ThumbnailModerationScore> {\n \"use step\";\n const apiKey = getApiKeyFromEnv(\"openai\");\n try {\n const res = await fetch(\"https://api.openai.com/v1/moderations\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: entry.model,\n input: [\n {\n type: \"image_url\",\n image_url: {\n url: entry.image,\n },\n },\n ],\n }),\n });\n\n const json: any = await res.json();\n if (!res.ok) {\n throw new Error(\n `OpenAI moderation error: ${res.status} ${res.statusText} - ${JSON.stringify(json)}`,\n );\n }\n\n const categoryScores = json.results?.[0]?.category_scores || {};\n\n return {\n url: entry.url,\n sexual: categoryScores.sexual || 0,\n violence: categoryScores.violence || 0,\n error: false,\n };\n } catch (error) {\n console.error(\"OpenAI moderation failed:\", error);\n return {\n url: entry.url,\n sexual: 0,\n violence: 0,\n error: true,\n };\n }\n}\n\nasync function requestOpenAIModeration(\n imageUrls: string[],\n model: string,\n maxConcurrent: number = 5,\n submissionMode: \"url\" | \"base64\" = \"url\",\n downloadOptions?: ImageDownloadOptions,\n): Promise<ThumbnailModerationScore[]> {\n \"use step\";\n const targetUrls =\n submissionMode === \"base64\" ?\n (await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent)).map(\n img => ({ url: img.url, image: img.base64Data, model }),\n ) :\n imageUrls.map(url => ({ url, image: url, model }));\n\n return processConcurrently(targetUrls, moderateImageWithOpenAI, maxConcurrent);\n}\n\nfunction getHiveCategoryScores(\n classes: NonNullable<HiveModerationOutput[\"classes\"]>,\n categoryNames: string[],\n): number {\n const scoreMap = Object.fromEntries(\n classes.map(c => [c.class, c.score]),\n );\n const scores = categoryNames.map(category => scoreMap[category] || 0);\n return Math.max(...scores, 0);\n}\n\nasync function moderateImageWithHive(entry: { url: string; source: HiveModerationSource }): Promise<ThumbnailModerationScore> {\n \"use step\";\n const apiKey = getApiKeyFromEnv(\"hive\");\n try {\n const formData = new FormData();\n\n if (entry.source.kind === \"url\") {\n formData.append(\"url\", entry.source.value);\n } else {\n const extension = entry.source.contentType.split(\"/\")[1] || \"jpg\";\n const blob = new Blob([entry.source.buffer], {\n type: entry.source.contentType,\n });\n formData.append(\"media\", blob, `thumbnail.${extension}`);\n }\n\n const res = await fetch(HIVE_ENDPOINT, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n Authorization: `Token ${apiKey}`,\n },\n body: formData,\n });\n\n const json: any = await res.json().catch(() => undefined);\n if (!res.ok) {\n throw new Error(\n `Hive moderation error: ${res.status} ${res.statusText} - ${JSON.stringify(json)}`,\n );\n }\n\n // Extract scores from Hive response\n // Hive returns scores in status[0].response.output[0].classes as array of {class, score}\n const classes = json?.status?.[0]?.response?.output?.[0]?.classes || [];\n\n return {\n url: entry.url,\n sexual: getHiveCategoryScores(classes, HIVE_SEXUAL_CATEGORIES),\n violence: getHiveCategoryScores(classes, HIVE_VIOLENCE_CATEGORIES),\n error: false,\n };\n } catch (error) {\n console.error(\"Hive moderation failed:\", error);\n return {\n url: entry.url,\n sexual: 0,\n violence: 0,\n error: true,\n };\n }\n}\n\nasync function requestHiveModeration(\n imageUrls: string[],\n maxConcurrent: number = 5,\n submissionMode: \"url\" | \"base64\" = \"url\",\n downloadOptions?: ImageDownloadOptions,\n): Promise<ThumbnailModerationScore[]> {\n \"use step\";\n const targets: Array<{ url: string; source: HiveModerationSource }> =\n submissionMode === \"base64\" ?\n (await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent)).map(img => ({\n url: img.url,\n source: {\n kind: \"file\",\n buffer: img.buffer,\n contentType: img.contentType,\n },\n })) :\n imageUrls.map(url => ({\n url,\n source: { kind: \"url\", value: url },\n }));\n\n return processConcurrently(targets, moderateImageWithHive, maxConcurrent);\n}\n\n/**\n * Moderate a Mux asset's thumbnails.\n * - provider 'openai' uses OpenAI's hosted moderation endpoint (requires OPENAI_API_KEY)\n */\nexport async function getModerationScores(\n assetId: string,\n options: ModerationOptions = {},\n): Promise<ModerationResult> {\n \"use workflow\";\n const {\n provider = DEFAULT_PROVIDER,\n model = provider === \"openai\" ? \"omni-moderation-latest\" : undefined,\n thresholds = DEFAULT_THRESHOLDS,\n thumbnailInterval = 10,\n thumbnailWidth = 640,\n maxConcurrent = 5,\n imageSubmissionMode = \"url\",\n imageDownloadOptions,\n } = options;\n\n // Fetch asset data and playback ID from Mux via helper\n const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n const duration = asset.duration || 0;\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Generate thumbnail URLs (signed if needed)\n const thumbnailUrls = await getThumbnailUrls(playbackId, duration, {\n interval: thumbnailInterval,\n width: thumbnailWidth,\n shouldSign: policy === \"signed\",\n });\n\n let thumbnailScores: ThumbnailModerationScore[];\n\n if (provider === \"openai\") {\n thumbnailScores = await requestOpenAIModeration(\n thumbnailUrls,\n model || \"omni-moderation-latest\",\n maxConcurrent,\n imageSubmissionMode,\n imageDownloadOptions,\n );\n } else if (provider === \"hive\") {\n thumbnailScores = await requestHiveModeration(\n thumbnailUrls,\n maxConcurrent,\n imageSubmissionMode,\n imageDownloadOptions,\n );\n } else {\n throw new Error(`Unsupported moderation provider: ${provider}`);\n }\n\n // Find highest scores across all thumbnails\n const maxSexual = Math.max(...thumbnailScores.map(s => s.sexual));\n const maxViolence = Math.max(...thumbnailScores.map(s => s.violence));\n\n const finalThresholds = { ...DEFAULT_THRESHOLDS, ...thresholds };\n\n return {\n assetId,\n thumbnailScores,\n maxScores: {\n sexual: maxSexual,\n violence: maxViolence,\n },\n exceedsThreshold: maxSexual > finalThresholds.sexual || maxViolence > finalThresholds.violence,\n thresholds: finalThresholds,\n };\n}\n","import { generateObject } from \"ai\";\nimport dedent from \"dedent\";\nimport { z } from \"zod\";\n\nimport { createWorkflowConfig } from \"@mux/ai/lib/client-factory\";\nimport type { ImageDownloadOptions } from \"@mux/ai/lib/image-download\";\nimport { downloadImageAsBase64 } from \"@mux/ai/lib/image-download\";\nimport { getPlaybackIdForAsset } from \"@mux/ai/lib/mux-assets\";\nimport type {\n PromptOverrides,\n} from \"@mux/ai/lib/prompt-builder\";\nimport {\n createPromptBuilder,\n createToneSection,\n createTranscriptSection,\n} from \"@mux/ai/lib/prompt-builder\";\nimport { createLanguageModelFromConfig } from \"@mux/ai/lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"@mux/ai/lib/providers\";\nimport { withRetry } from \"@mux/ai/lib/retry\";\nimport { getMuxSigningContextFromEnv } from \"@mux/ai/lib/url-signing\";\nimport { getStoryboardUrl } from \"@mux/ai/primitives/storyboards\";\nimport { fetchTranscriptForAsset } from \"@mux/ai/primitives/transcripts\";\nimport type { ImageSubmissionMode, MuxAIOptions, TokenUsage, ToneType } from \"@mux/ai/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const SUMMARY_KEYWORD_LIMIT = 10;\n\nexport const summarySchema = z.object({\n keywords: z.array(z.string()),\n title: z.string(),\n description: z.string(),\n});\n\nexport type SummaryType = z.infer<typeof summarySchema>;\n\n/** Structured return payload for `getSummaryAndTags`. */\nexport interface SummaryAndTagsResult {\n /** Asset ID passed into the workflow. */\n assetId: string;\n /** Short headline generated from the storyboard. */\n title: string;\n /** Longer description of the detected content. */\n description: string;\n /** Up to 10 keywords extracted by the model. */\n tags: string[];\n /** Storyboard image URL that was analyzed. */\n storyboardUrl: string;\n /** Token usage from the AI provider (for efficiency/cost analysis). */\n usage?: TokenUsage;\n /** Raw transcript text used for analysis (when includeTranscript is true). */\n transcriptText?: string;\n}\n\n/**\n * Sections of the summarization user prompt that can be overridden.\n * Use these to customize the AI's behavior for your specific use case.\n */\nexport type SummarizationPromptSections =\n | \"task\" |\n \"title\" |\n \"description\" |\n \"keywords\" |\n \"qualityGuidelines\";\n\n/**\n * Override specific sections of the summarization prompt.\n * Each key corresponds to a section that can be customized.\n *\n * @example\n * ```typescript\n * const result = await getSummaryAndTags(assetId, {\n * promptOverrides: {\n * task: 'Generate SEO-optimized metadata for this product video.',\n * title: 'Create a click-worthy title under 60 characters for YouTube.',\n * },\n * });\n * ```\n */\nexport type SummarizationPromptOverrides = PromptOverrides<SummarizationPromptSections>;\n\n/** Configuration accepted by `getSummaryAndTags`. */\nexport interface SummarizationOptions extends MuxAIOptions {\n /** AI provider to run (defaults to 'openai'). */\n provider?: SupportedProvider;\n /** Provider-specific chat model identifier. */\n model?: ModelIdByProvider[SupportedProvider];\n /** Prompt tone shim applied to the system instruction (defaults to 'neutral'). */\n tone?: ToneType;\n /** Fetch the transcript and send it alongside the storyboard (defaults to true). */\n includeTranscript?: boolean;\n /** Strip timestamps/markup from transcripts before including them (defaults to true). */\n cleanTranscript?: boolean;\n /** How storyboard frames should be delivered to the provider (defaults to 'url'). */\n imageSubmissionMode?: ImageSubmissionMode;\n /** Fine-tune storyboard downloads when `imageSubmissionMode` === 'base64'. */\n imageDownloadOptions?: ImageDownloadOptions;\n /**\n * Override specific sections of the user prompt.\n * Useful for customizing the AI's output for specific use cases (SEO, social media, etc.)\n */\n promptOverrides?: SummarizationPromptOverrides;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Prompts\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst VALID_TONES = [\"neutral\", \"playful\", \"professional\"] as const;\n\nconst TONE_INSTRUCTIONS: Record<ToneType, string> = {\n neutral: \"Provide a clear, straightforward analysis.\",\n playful: \"Channel your inner diva! Answer with maximum sass, wit, and playful attitude. Don't hold back - be cheeky, clever, and delightfully snarky. Make it pop!\",\n professional: \"Provide a professional, executive-level analysis suitable for business reporting.\",\n};\n\n/**\n * Prompt builder for the summarization user prompt.\n * Sections can be individually overridden via `promptOverrides` in SummarizationOptions.\n */\nconst summarizationPromptBuilder = createPromptBuilder<SummarizationPromptSections>({\n template: {\n task: {\n tag: \"task\",\n content: \"Analyze the storyboard frames and generate metadata that captures the essence of the video content.\",\n },\n title: {\n tag: \"title_requirements\",\n content: dedent`\n A short, compelling headline that immediately communicates the subject or action.\n Aim for brevity - typically under 10 words. Think of how a news headline or video card title would read.\n Start with the primary subject, action, or topic - never begin with \"A video of\" or similar phrasing.\n Use active, specific language.`,\n },\n description: {\n tag: \"description_requirements\",\n content: dedent`\n A concise summary (2-4 sentences) that describes what happens across the video.\n Cover the main subjects, actions, setting, and any notable progression visible across frames.\n Write in present tense. Be specific about observable details rather than making assumptions.\n If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`,\n },\n keywords: {\n tag: \"keywords_requirements\",\n content: dedent`\n Specific, searchable terms (up to 10) that capture:\n - Primary subjects (people, animals, objects)\n - Actions and activities being performed\n - Setting and environment\n - Notable objects or tools\n - Style or genre (if applicable)\n Prefer concrete nouns and action verbs over abstract concepts.\n Use lowercase. Avoid redundant or overly generic terms like \"video\" or \"content\".`,\n },\n qualityGuidelines: {\n tag: \"quality_guidelines\",\n content: dedent`\n - Examine all frames to understand the full context and progression\n - Be precise: \"golden retriever\" is better than \"dog\" when identifiable\n - Capture the narrative: what begins, develops, and concludes\n - Balance brevity with informativeness`,\n },\n },\n sectionOrder: [\"task\", \"title\", \"description\", \"keywords\", \"qualityGuidelines\"],\n});\n\nconst SYSTEM_PROMPT = dedent`\n <role>\n You are a video content analyst specializing in storyboard interpretation and multimodal analysis.\n </role>\n\n <context>\n You receive storyboard images containing multiple sequential frames extracted from a video.\n These frames are arranged in a grid and represent the visual progression of the content over time.\n Read frames left-to-right, top-to-bottom to understand the temporal sequence.\n </context>\n\n <transcript_guidance>\n When a transcript is provided alongside the storyboard:\n - Use it to understand spoken content, dialogue, narration, and audio context\n - Correlate transcript content with visual frames to build a complete picture\n - Extract key terminology, names, and specific language used by speakers\n - Let the transcript inform keyword selection, especially for topics not visually obvious\n - Prioritize visual content for the description, but enrich it with transcript insights\n - If transcript and visuals conflict, trust the visual evidence\n </transcript_guidance>\n\n <capabilities>\n - Extract meaning from visual sequences\n - Identify subjects, actions, settings, and narrative arcs\n - Generate accurate, searchable metadata\n - Synthesize visual and transcript information when provided\n </capabilities>\n\n <constraints>\n - Only describe what is clearly observable in the frames or explicitly stated in the transcript\n - Do not fabricate details or make unsupported assumptions\n - Return structured data matching the requested schema\n </constraints>\n\n <tone_guidance>\n Pay special attention to the <tone> section and lean heavily into those instructions.\n Adapt your entire analysis and writing style to match the specified tone - this should influence\n your word choice, personality, formality level, and overall presentation of the content.\n The tone instructions are not suggestions but core requirements for how you should express yourself.\n </tone_guidance>\n\n <language_guidelines>\n AVOID these meta-descriptive phrases that reference the medium rather than the content:\n - \"The image shows...\" / \"The storyboard shows...\"\n - \"In this video...\" / \"This video features...\"\n - \"The frames depict...\" / \"The footage shows...\"\n - \"We can see...\" / \"You can see...\"\n - \"The clip shows...\" / \"The scene shows...\"\n\n INSTEAD, describe the content directly:\n - BAD: \"The video shows a chef preparing a meal\"\n - GOOD: \"A chef prepares a meal in a professional kitchen\"\n\n Write as if describing reality, not describing a recording of reality.\n </language_guidelines>`;\n\ninterface UserPromptContext {\n tone: ToneType;\n transcriptText?: string;\n isCleanTranscript?: boolean;\n promptOverrides?: SummarizationPromptOverrides;\n}\n\nfunction buildUserPrompt({\n tone,\n transcriptText,\n isCleanTranscript = true,\n promptOverrides,\n}: UserPromptContext): string {\n // Build dynamic context sections\n const contextSections = [createToneSection(TONE_INSTRUCTIONS[tone])];\n\n if (transcriptText) {\n const format = isCleanTranscript ? \"plain text\" : \"WebVTT\";\n contextSections.push(createTranscriptSection(transcriptText, format));\n }\n\n return summarizationPromptBuilder.buildWithContext(promptOverrides, contextSections);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface AnalysisResponse {\n result: SummaryType;\n usage: TokenUsage;\n}\n\nasync function analyzeStoryboard(\n imageDataUrl: string,\n provider: SupportedProvider,\n modelId: string,\n userPrompt: string,\n systemPrompt: string,\n): Promise<AnalysisResponse> {\n \"use step\";\n const model = createLanguageModelFromConfig(provider, modelId);\n\n const response = await generateObject({\n model,\n schema: summarySchema,\n messages: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n {\n role: \"user\",\n content: [\n { type: \"text\", text: userPrompt },\n { type: \"image\", image: imageDataUrl },\n ],\n },\n ],\n });\n\n return {\n result: response.object,\n usage: {\n inputTokens: response.usage.inputTokens,\n outputTokens: response.usage.outputTokens,\n totalTokens: response.usage.totalTokens,\n reasoningTokens: response.usage.reasoningTokens,\n cachedInputTokens: response.usage.cachedInputTokens,\n },\n };\n}\n\nfunction normalizeKeywords(keywords?: string[]): string[] {\n if (!Array.isArray(keywords) || keywords.length === 0) {\n return [];\n }\n\n const uniqueLowercase = new Set<string>();\n const normalized: string[] = [];\n\n for (const keyword of keywords) {\n const trimmed = keyword?.trim();\n if (!trimmed) {\n continue;\n }\n\n const lower = trimmed.toLowerCase();\n if (uniqueLowercase.has(lower)) {\n continue;\n }\n\n uniqueLowercase.add(lower);\n normalized.push(trimmed);\n\n if (normalized.length === SUMMARY_KEYWORD_LIMIT) {\n break;\n }\n }\n\n return normalized;\n}\n\nexport async function getSummaryAndTags(\n assetId: string,\n options?: SummarizationOptions,\n): Promise<SummaryAndTagsResult> {\n \"use workflow\";\n const {\n provider = \"openai\",\n model,\n tone = \"neutral\",\n includeTranscript = true,\n cleanTranscript = true,\n imageSubmissionMode = \"url\",\n imageDownloadOptions,\n abortSignal: _abortSignal,\n promptOverrides,\n } = options ?? {};\n\n // Validate tone parameter\n if (!VALID_TONES.includes(tone)) {\n throw new Error(\n `Invalid tone \"${tone}\". Valid tones are: ${VALID_TONES.join(\", \")}`,\n );\n }\n\n // Validate credentials and resolve language model\n const config = await createWorkflowConfig(\n { ...options, model },\n provider as SupportedProvider,\n );\n\n // Fetch asset data from Mux and grab playback/transcript details\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n const transcriptText =\n includeTranscript ?\n (await fetchTranscriptForAsset(assetData, playbackId, {\n cleanTranscript,\n shouldSign: policy === \"signed\",\n })).transcriptText :\n \"\";\n\n // Build the user prompt with all context and any overrides\n const userPrompt = buildUserPrompt({\n tone,\n transcriptText,\n isCleanTranscript: cleanTranscript,\n promptOverrides,\n });\n\n // Analyze storyboard with AI provider (signed if needed)\n const imageUrl = await getStoryboardUrl(playbackId, 640, policy === \"signed\");\n\n let analysisResponse: AnalysisResponse;\n\n try {\n if (imageSubmissionMode === \"base64\") {\n const downloadResult = await downloadImageAsBase64(imageUrl, imageDownloadOptions);\n analysisResponse = await analyzeStoryboard(\n downloadResult.base64Data,\n config.provider,\n config.modelId,\n userPrompt,\n SYSTEM_PROMPT,\n );\n } else {\n // URL-based submission with retry logic\n analysisResponse = await withRetry(() => analyzeStoryboard(imageUrl, config.provider, config.modelId, userPrompt, SYSTEM_PROMPT));\n }\n } catch (error: unknown) {\n throw new Error(\n `Failed to analyze video content with ${provider}: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n\n if (!analysisResponse.result) {\n throw new Error(`Failed to analyze video content for asset ${assetId}`);\n }\n\n if (!analysisResponse.result.title) {\n throw new Error(`Failed to generate title for asset ${assetId}`);\n }\n\n if (!analysisResponse.result.description) {\n throw new Error(`Failed to generate description for asset ${assetId}`);\n }\n\n return {\n assetId,\n title: analysisResponse.result.title,\n description: analysisResponse.result.description,\n tags: normalizeKeywords(analysisResponse.result.keywords),\n storyboardUrl: imageUrl,\n usage: analysisResponse.usage,\n transcriptText: transcriptText || undefined,\n };\n}\n","import Mux from \"@mux/mux-node\";\n\nimport env from \"@mux/ai/env\";\nimport { getApiKeyFromEnv, getMuxCredentialsFromEnv } from \"@mux/ai/lib/client-factory\";\nimport { getLanguageCodePair, toISO639_1, toISO639_3 } from \"@mux/ai/lib/language-codes\";\nimport type { LanguageCodePair, SupportedISO639_1 } from \"@mux/ai/lib/language-codes\";\nimport { getPlaybackIdForAsset } from \"@mux/ai/lib/mux-assets\";\nimport { getMuxSigningContextFromEnv, signUrl } from \"@mux/ai/lib/url-signing\";\nimport type { MuxAIOptions } from \"@mux/ai/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Output returned from `translateAudio`. */\nexport interface AudioTranslationResult {\n assetId: string;\n /** Target language code (ISO 639-1 two-letter format). */\n targetLanguageCode: SupportedISO639_1;\n /**\n * Target language codes in both ISO 639-1 (2-letter) and ISO 639-3 (3-letter) formats.\n * Use `iso639_1` for browser players (BCP-47 compliant) and `iso639_3` for ElevenLabs API.\n */\n targetLanguage: LanguageCodePair;\n dubbingId: string;\n uploadedTrackId?: string;\n presignedUrl?: string;\n}\n\n/** Configuration accepted by `translateAudio`. */\nexport interface AudioTranslationOptions extends MuxAIOptions {\n /** Audio dubbing provider (currently ElevenLabs only). */\n provider?: \"elevenlabs\";\n /** Number of speakers supplied to ElevenLabs (0 = auto-detect, default). */\n numSpeakers?: number;\n /** Optional override for the S3-compatible endpoint used for uploads. */\n s3Endpoint?: string;\n /** S3 region (defaults to env.S3_REGION or 'auto'). */\n s3Region?: string;\n /** Bucket that will store dubbed audio files. */\n s3Bucket?: string;\n /**\n * When true (default) the dubbed audio file is uploaded to the configured\n * bucket and attached to the Mux asset.\n */\n uploadToMux?: boolean;\n /** Override for env.ELEVENLABS_API_KEY. */\n elevenLabsApiKey?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst STATIC_RENDITION_POLL_INTERVAL_MS = 5000;\nconst STATIC_RENDITION_MAX_ATTEMPTS = 36; // ~3 minutes\n\nasync function sleep(ms: number): Promise<void> {\n \"use step\";\n await new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction getReadyAudioStaticRendition(asset: any) {\n const files = asset.static_renditions?.files as any[] | undefined;\n if (!files || files.length === 0) {\n return undefined;\n }\n\n return files.find(\n rendition => rendition.name === \"audio.m4a\" && rendition.status === \"ready\",\n );\n}\n\nconst hasReadyAudioStaticRendition = (asset: any) => Boolean(getReadyAudioStaticRendition(asset));\n\nasync function requestStaticRenditionCreation(assetId: string) {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n try {\n await mux.video.assets.createStaticRendition(assetId, {\n resolution: \"audio-only\",\n });\n } catch (error: any) {\n const statusCode = error?.status ?? error?.statusCode;\n const messages: string[] | undefined = error?.error?.messages;\n const alreadyDefined =\n messages?.some(message => message.toLowerCase().includes(\"already defined\")) ??\n error?.message?.toLowerCase().includes(\"already defined\");\n\n if (statusCode === 409 || alreadyDefined) {\n return;\n }\n\n const message = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Failed to request static rendition from Mux: ${message}`);\n }\n}\n\nasync function waitForAudioStaticRendition({\n assetId,\n initialAsset,\n}: {\n assetId: string;\n initialAsset: any;\n}): Promise<any> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n let currentAsset = initialAsset;\n\n if (hasReadyAudioStaticRendition(currentAsset)) {\n return currentAsset;\n }\n\n const status = currentAsset.static_renditions?.status ?? \"not_requested\";\n\n if (status === \"not_requested\" || status === undefined) {\n await requestStaticRenditionCreation(assetId);\n } else if (status === \"errored\") {\n await requestStaticRenditionCreation(assetId);\n } else {\n console.warn(`ℹ️ Static rendition already ${status}. Waiting for it to finish...`);\n }\n\n for (let attempt = 1; attempt <= STATIC_RENDITION_MAX_ATTEMPTS; attempt++) {\n await sleep(STATIC_RENDITION_POLL_INTERVAL_MS);\n currentAsset = await mux.video.assets.retrieve(assetId);\n\n if (hasReadyAudioStaticRendition(currentAsset)) {\n return currentAsset;\n }\n\n const currentStatus = currentAsset.static_renditions?.status || \"unknown\";\n console.warn(\n `⌛ Waiting for static rendition (attempt ${attempt}/${STATIC_RENDITION_MAX_ATTEMPTS}) → ${currentStatus}`,\n );\n\n if (currentStatus === \"errored\") {\n throw new Error(\n \"Mux failed to create the static rendition for this asset. Please check the asset in the Mux dashboard.\",\n );\n }\n }\n\n throw new Error(\n \"Timed out waiting for the static rendition to become ready. Please try again in a moment.\",\n );\n}\n\nasync function fetchAudioFromMux(audioUrl: string): Promise<ArrayBuffer> {\n \"use step\";\n\n const audioResponse = await fetch(audioUrl);\n if (!audioResponse.ok) {\n throw new Error(`Failed to fetch audio file: ${audioResponse.statusText}`);\n }\n\n return audioResponse.arrayBuffer();\n}\n\nasync function createElevenLabsDubbingJob({\n audioBuffer,\n assetId,\n elevenLabsLangCode,\n numSpeakers,\n}: {\n audioBuffer: ArrayBuffer;\n assetId: string;\n elevenLabsLangCode: string;\n numSpeakers: number;\n}): Promise<string> {\n \"use step\";\n const elevenLabsApiKey = getApiKeyFromEnv(\"elevenlabs\");\n\n const audioBlob = new Blob([audioBuffer], { type: \"audio/mp4\" });\n\n const formData = new FormData();\n formData.append(\"file\", audioBlob);\n formData.append(\"target_lang\", elevenLabsLangCode);\n formData.append(\"num_speakers\", numSpeakers.toString());\n formData.append(\"name\", `Mux Asset ${assetId} - auto to ${elevenLabsLangCode}`);\n\n const dubbingResponse = await fetch(\"https://api.elevenlabs.io/v1/dubbing\", {\n method: \"POST\",\n headers: {\n \"xi-api-key\": elevenLabsApiKey,\n },\n body: formData,\n });\n\n if (!dubbingResponse.ok) {\n throw new Error(`ElevenLabs API error: ${dubbingResponse.statusText}`);\n }\n\n const dubbingData = await dubbingResponse.json() as any;\n return dubbingData.dubbing_id;\n}\n\nasync function checkElevenLabsDubbingStatus({\n dubbingId,\n}: {\n dubbingId: string;\n}): Promise<{ status: string; targetLanguages: string[] }> {\n \"use step\";\n const elevenLabsApiKey = getApiKeyFromEnv(\"elevenlabs\");\n\n const statusResponse = await fetch(`https://api.elevenlabs.io/v1/dubbing/${dubbingId}`, {\n headers: {\n \"xi-api-key\": elevenLabsApiKey,\n },\n });\n\n if (!statusResponse.ok) {\n throw new Error(`Status check failed: ${statusResponse.statusText}`);\n }\n\n const statusData = await statusResponse.json() as any;\n return {\n status: statusData.status,\n targetLanguages: statusData.target_languages ?? [],\n };\n}\n\nasync function downloadDubbedAudioFromElevenLabs({\n dubbingId,\n languageCode,\n}: {\n dubbingId: string;\n languageCode: string;\n}): Promise<ArrayBuffer> {\n \"use step\";\n const elevenLabsApiKey = getApiKeyFromEnv(\"elevenlabs\");\n\n const audioUrl = `https://api.elevenlabs.io/v1/dubbing/${dubbingId}/audio/${languageCode}`;\n const audioResponse = await fetch(audioUrl, {\n headers: {\n \"xi-api-key\": elevenLabsApiKey,\n },\n });\n\n if (!audioResponse.ok) {\n throw new Error(`Failed to fetch dubbed audio: ${audioResponse.statusText}`);\n }\n\n return audioResponse.arrayBuffer();\n}\n\nasync function uploadDubbedAudioToS3({\n dubbedAudioBuffer,\n assetId,\n toLanguageCode,\n s3Endpoint,\n s3Region,\n s3Bucket,\n}: {\n dubbedAudioBuffer: ArrayBuffer;\n assetId: string;\n toLanguageCode: string;\n s3Endpoint: string;\n s3Region: string;\n s3Bucket: string;\n}): Promise<string> {\n \"use step\";\n\n const { S3Client, GetObjectCommand } = await import(\"@aws-sdk/client-s3\");\n const { Upload } = await import(\"@aws-sdk/lib-storage\");\n const { getSignedUrl } = await import(\"@aws-sdk/s3-request-presigner\");\n\n // asserting exists. already validated (See: translateAudio())\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID!;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY!;\n\n const s3Client = new S3Client({\n region: s3Region,\n endpoint: s3Endpoint,\n credentials: {\n accessKeyId: s3AccessKeyId,\n secretAccessKey: s3SecretAccessKey,\n },\n forcePathStyle: true,\n });\n\n // Create unique key for the audio file\n const audioKey = `audio-translations/${assetId}/auto-to-${toLanguageCode}-${Date.now()}.m4a`;\n\n // Upload audio to S3\n const upload = new Upload({\n client: s3Client,\n params: {\n Bucket: s3Bucket,\n Key: audioKey,\n Body: new Uint8Array(dubbedAudioBuffer),\n ContentType: \"audio/mp4\",\n },\n });\n\n await upload.done();\n\n // Generate presigned URL (valid for 1 hour)\n const getObjectCommand = new GetObjectCommand({\n Bucket: s3Bucket,\n Key: audioKey,\n });\n\n const presignedUrl = await getSignedUrl(s3Client, getObjectCommand, {\n expiresIn: 3600, // 1 hour\n });\n\n console.warn(`✅ Audio uploaded successfully to: ${audioKey}`);\n console.warn(`🔗 Generated presigned URL (expires in 1 hour)`);\n\n return presignedUrl;\n}\n\nasync function createAudioTrackOnMux(\n assetId: string,\n languageCode: string,\n presignedUrl: string,\n): Promise<string> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n const languageName = new Intl.DisplayNames([\"en\"], { type: \"language\" }).of(languageCode) || languageCode.toUpperCase();\n const trackName = `${languageName} (auto-dubbed)`;\n\n const trackResponse = await mux.video.assets.createTrack(assetId, {\n type: \"audio\",\n language_code: languageCode,\n name: trackName,\n url: presignedUrl,\n });\n\n if (!trackResponse.id) {\n throw new Error(\"Failed to create audio track: no track ID returned from Mux\");\n }\n\n return trackResponse.id;\n}\n\nexport async function translateAudio(\n assetId: string,\n toLanguageCode: string,\n options: AudioTranslationOptions = {},\n): Promise<AudioTranslationResult> {\n \"use workflow\";\n // Uses the default audio track on your asset, language is auto-detected by ElevenLabs\n const {\n provider = \"elevenlabs\",\n numSpeakers = 0, // 0 = auto-detect\n elevenLabsApiKey,\n uploadToMux = true,\n } = options;\n\n if (provider !== \"elevenlabs\") {\n throw new Error(\"Only ElevenLabs provider is currently supported for audio translation\");\n }\n\n const elevenLabsKey = elevenLabsApiKey ?? env.ELEVENLABS_API_KEY;\n\n // S3 configuration\n const s3Endpoint = options.s3Endpoint ?? env.S3_ENDPOINT;\n const s3Region = options.s3Region ?? env.S3_REGION ?? \"auto\";\n const s3Bucket = options.s3Bucket ?? env.S3_BUCKET;\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY;\n\n if (!elevenLabsKey) {\n throw new Error(\"ElevenLabs API key is required. Provide elevenLabsApiKey in options or set ELEVENLABS_API_KEY environment variable.\");\n }\n\n if (uploadToMux && (!s3Endpoint || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey)) {\n throw new Error(\"S3 configuration is required for uploading to Mux. Provide s3Endpoint, s3Bucket, s3AccessKeyId, and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.\");\n }\n\n // Fetch asset data and playback ID from Mux\n const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Check for audio-only static rendition\n\n let currentAsset = initialAsset;\n if (!hasReadyAudioStaticRendition(currentAsset)) {\n console.warn(\"❌ No ready audio static rendition found. Requesting one now...\");\n currentAsset = await waitForAudioStaticRendition({\n assetId,\n initialAsset: currentAsset,\n });\n }\n\n const audioRendition = getReadyAudioStaticRendition(currentAsset);\n\n if (!audioRendition) {\n throw new Error(\n \"Unable to obtain an audio-only static rendition for this asset. Please verify static renditions are enabled in Mux.\",\n );\n }\n\n // Build audio URL (signed if needed)\n let audioUrl = `https://stream.mux.com/${playbackId}/audio.m4a`;\n if (policy === \"signed\" && signingContext) {\n audioUrl = await signUrl(audioUrl, playbackId, signingContext, \"video\");\n }\n\n // Fetch audio from Mux\n console.warn(\"🎙️ Fetching audio from Mux...\");\n\n let audioBuffer: ArrayBuffer;\n try {\n audioBuffer = await fetchAudioFromMux(audioUrl);\n } catch (error) {\n throw new Error(`Failed to fetch audio from Mux: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Create dubbing job in ElevenLabs\n console.warn(\"🎙️ Creating dubbing job in ElevenLabs...\");\n\n // ElevenLabs uses ISO 639-3 (3-letter) codes, so normalize the input\n const elevenLabsLangCode = toISO639_3(toLanguageCode);\n console.warn(`🔍 Creating dubbing job for asset ${assetId} with language code: ${elevenLabsLangCode}`);\n\n let dubbingId: string;\n try {\n dubbingId = await createElevenLabsDubbingJob({\n audioBuffer,\n assetId,\n elevenLabsLangCode,\n numSpeakers,\n });\n console.warn(`✅ Dubbing job created with ID: ${dubbingId}`);\n } catch (error) {\n throw new Error(`Failed to create ElevenLabs dubbing job: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Poll for completion\n console.warn(\"⏳ Waiting for dubbing to complete...\");\n\n let dubbingStatus: string = \"dubbing\";\n let pollAttempts = 0;\n const maxPollAttempts = 180; // 30 minutes at 10s intervals\n let targetLanguages: string[] = [];\n\n while (dubbingStatus === \"dubbing\" && pollAttempts < maxPollAttempts) {\n await sleep(10000); // Wait 10 seconds\n pollAttempts++;\n\n try {\n const statusResult = await checkElevenLabsDubbingStatus({\n dubbingId,\n });\n dubbingStatus = statusResult.status;\n targetLanguages = statusResult.targetLanguages;\n\n if (dubbingStatus === \"failed\") {\n throw new Error(\"ElevenLabs dubbing job failed\");\n }\n } catch (error) {\n throw new Error(`Failed to check dubbing status: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n }\n\n if (dubbingStatus !== \"dubbed\") {\n throw new Error(`Dubbing job timed out or failed. Final status: ${dubbingStatus}`);\n }\n\n console.warn(\"✅ Dubbing completed successfully!\");\n\n // If uploadToMux is false, just return the dubbing info\n // Return ISO 639-1 (2-letter) code for consistency with Mux/player expectations\n if (!uploadToMux) {\n const targetLanguage = getLanguageCodePair(toLanguageCode);\n return {\n assetId,\n targetLanguageCode: targetLanguage.iso639_1 as SupportedISO639_1,\n targetLanguage,\n dubbingId,\n };\n }\n\n // Download dubbed audio from ElevenLabs\n console.warn(\"📥 Downloading dubbed audio from ElevenLabs...\");\n\n let dubbedAudioBuffer: ArrayBuffer;\n\n try {\n // Use the language code from the ElevenLabs status response\n // ElevenLabs returns target_languages array with the exact codes available for download\n const requestedLangCode = toISO639_3(toLanguageCode);\n\n // Find the matching language code from ElevenLabs response\n // First try exact match, then try case-insensitive match\n let downloadLangCode = targetLanguages.find(\n lang => lang === requestedLangCode,\n ) ?? targetLanguages.find(\n lang => lang.toLowerCase() === requestedLangCode.toLowerCase(),\n );\n\n // Fallback to first available target language if no match found\n if (!downloadLangCode && targetLanguages.length > 0) {\n downloadLangCode = targetLanguages[0];\n console.warn(`⚠️ Requested language \"${requestedLangCode}\" not found in target_languages. Using \"${downloadLangCode}\" instead.`);\n }\n\n // If still no language code, fall back to the original behavior\n if (!downloadLangCode) {\n downloadLangCode = requestedLangCode;\n console.warn(`⚠️ No target_languages available from ElevenLabs status. Using requested language code: ${requestedLangCode}`);\n }\n\n dubbedAudioBuffer = await downloadDubbedAudioFromElevenLabs({\n dubbingId,\n languageCode: downloadLangCode,\n });\n console.warn(\"✅ Dubbed audio downloaded successfully!\");\n } catch (error) {\n throw new Error(`Failed to download dubbed audio: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Upload to S3-compatible storage\n console.warn(\"📤 Uploading dubbed audio to S3-compatible storage...\");\n\n let presignedUrl: string;\n\n try {\n presignedUrl = await uploadDubbedAudioToS3({\n dubbedAudioBuffer,\n assetId,\n toLanguageCode,\n s3Endpoint: s3Endpoint!,\n s3Region,\n s3Bucket: s3Bucket!,\n });\n } catch (error) {\n throw new Error(`Failed to upload audio to S3: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Add translated audio track to Mux asset\n console.warn(\"📹 Adding dubbed audio track to Mux asset...\");\n\n let uploadedTrackId: string | undefined;\n // Mux uses ISO 639-1 (2-letter) codes for track language_code\n const muxLangCode = toISO639_1(toLanguageCode);\n\n try {\n uploadedTrackId = await createAudioTrackOnMux(assetId, muxLangCode, presignedUrl);\n const languageName = new Intl.DisplayNames([\"en\"], { type: \"language\" }).of(muxLangCode) || muxLangCode.toUpperCase();\n const trackName = `${languageName} (auto-dubbed)`;\n console.warn(`✅ Track added to Mux asset with ID: ${uploadedTrackId}`);\n console.warn(`📋 Track name: \"${trackName}\"`);\n } catch (error) {\n console.warn(`⚠️ Failed to add audio track to Mux asset: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n console.warn(\"🔗 You can manually add the track using this presigned URL:\");\n console.warn(presignedUrl);\n }\n\n const targetLanguage = getLanguageCodePair(toLanguageCode);\n return {\n assetId,\n targetLanguageCode: targetLanguage.iso639_1 as SupportedISO639_1,\n targetLanguage,\n dubbingId,\n uploadedTrackId,\n presignedUrl,\n };\n}\n","/**\n * Language Code Conversion Utilities\n *\n * Provides bidirectional mapping between:\n * - ISO 639-1 (2-letter codes) - Used by browsers, BCP-47, most video players\n * - ISO 639-3 (3-letter codes) - Used by various APIs and language processing systems\n *\n * This is essential for interoperability between different systems:\n * - Mux uses ISO 639-1 for track language codes\n * - Browser players expect BCP-47 compliant codes (based on ISO 639-1)\n * - Some APIs require ISO 639-3 (3-letter) codes\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Language Code Mapping\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Mapping from ISO 639-1 (2-letter) to ISO 639-3 (3-letter) codes.\n * Covers the most common languages used in video translation.\n *\n * Reference: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes\n */\nconst ISO639_1_TO_3 = {\n // Major world languages\n en: \"eng\", // English\n es: \"spa\", // Spanish\n fr: \"fra\", // French\n de: \"deu\", // German\n it: \"ita\", // Italian\n pt: \"por\", // Portuguese\n ru: \"rus\", // Russian\n zh: \"zho\", // Chinese\n ja: \"jpn\", // Japanese\n ko: \"kor\", // Korean\n ar: \"ara\", // Arabic\n hi: \"hin\", // Hindi\n\n // European languages\n nl: \"nld\", // Dutch\n pl: \"pol\", // Polish\n sv: \"swe\", // Swedish\n da: \"dan\", // Danish\n no: \"nor\", // Norwegian\n fi: \"fin\", // Finnish\n el: \"ell\", // Greek\n cs: \"ces\", // Czech\n hu: \"hun\", // Hungarian\n ro: \"ron\", // Romanian\n bg: \"bul\", // Bulgarian\n hr: \"hrv\", // Croatian\n sk: \"slk\", // Slovak\n sl: \"slv\", // Slovenian\n uk: \"ukr\", // Ukrainian\n tr: \"tur\", // Turkish\n\n // Asian languages\n th: \"tha\", // Thai\n vi: \"vie\", // Vietnamese\n id: \"ind\", // Indonesian\n ms: \"msa\", // Malay\n tl: \"tgl\", // Tagalog/Filipino\n\n // Other languages\n he: \"heb\", // Hebrew\n fa: \"fas\", // Persian/Farsi\n bn: \"ben\", // Bengali\n ta: \"tam\", // Tamil\n te: \"tel\", // Telugu\n mr: \"mar\", // Marathi\n gu: \"guj\", // Gujarati\n kn: \"kan\", // Kannada\n ml: \"mal\", // Malayalam\n pa: \"pan\", // Punjabi\n ur: \"urd\", // Urdu\n sw: \"swa\", // Swahili\n af: \"afr\", // Afrikaans\n ca: \"cat\", // Catalan\n eu: \"eus\", // Basque\n gl: \"glg\", // Galician\n is: \"isl\", // Icelandic\n et: \"est\", // Estonian\n lv: \"lav\", // Latvian\n lt: \"lit\", // Lithuanian\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Supported ISO 639-1 two-letter language codes.\n * These are the language codes supported for translation workflows.\n */\nexport type SupportedISO639_1 = keyof typeof ISO639_1_TO_3;\n\n/**\n * Supported ISO 639-3 three-letter language codes.\n * These are the language codes supported for translation workflows.\n */\nexport type SupportedISO639_3 = (typeof ISO639_1_TO_3)[SupportedISO639_1];\n\n/** ISO 639-1 two-letter language code (e.g., \"en\", \"fr\", \"es\") */\nexport type ISO639_1 = SupportedISO639_1 | (string & {});\n\n/** ISO 639-3 three-letter language code (e.g., \"eng\", \"fra\", \"spa\") */\nexport type ISO639_3 = SupportedISO639_3 | (string & {});\n\n/** Structured language code result containing both formats */\nexport interface LanguageCodePair {\n /** ISO 639-1 two-letter code (BCP-47 compatible) */\n iso639_1: ISO639_1;\n /** ISO 639-3 three-letter code */\n iso639_3: ISO639_3;\n}\n\n/**\n * Reverse mapping from ISO 639-3 (3-letter) to ISO 639-1 (2-letter) codes.\n * Generated from ISO639_1_TO_3 for consistency.\n */\nconst ISO639_3_TO_1 = Object.fromEntries(\n Object.entries(ISO639_1_TO_3).map(([iso1, iso3]) => [iso3, iso1]),\n) as Record<SupportedISO639_3, SupportedISO639_1>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Conversion Functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Converts an ISO 639-1 (2-letter) code to ISO 639-3 (3-letter) code.\n *\n * @param code - ISO 639-1 two-letter language code (e.g., \"en\", \"fr\")\n * @returns ISO 639-3 three-letter code, or the original if not found\n *\n * @example\n * ```typescript\n * toISO639_3(\"en\") // \"eng\"\n * toISO639_3(\"fr\") // \"fra\"\n * toISO639_3(\"ja\") // \"jpn\"\n * ```\n */\nexport function toISO639_3(code: string): ISO639_3 {\n const normalized = code.toLowerCase().trim();\n\n // If it's already a 3-letter code, return as-is\n if (normalized.length === 3) {\n return normalized;\n }\n\n return (ISO639_1_TO_3 as Record<string, string>)[normalized] ?? normalized;\n}\n\n/**\n * Converts an ISO 639-3 (3-letter) code to ISO 639-1 (2-letter) code.\n *\n * @param code - ISO 639-3 three-letter language code (e.g., \"eng\", \"fra\")\n * @returns ISO 639-1 two-letter code, or the original if not found\n *\n * @example\n * ```typescript\n * toISO639_1(\"eng\") // \"en\"\n * toISO639_1(\"fra\") // \"fr\"\n * toISO639_1(\"jpn\") // \"ja\"\n * ```\n */\nexport function toISO639_1(code: string): ISO639_1 {\n const normalized = code.toLowerCase().trim();\n\n // If it's already a 2-letter code, return as-is\n if (normalized.length === 2) {\n return normalized;\n }\n\n return (ISO639_3_TO_1 as Record<string, string>)[normalized] ?? normalized;\n}\n\n/**\n * Returns both ISO 639-1 and ISO 639-3 codes for a given language code.\n * Accepts either format as input and normalizes to both.\n *\n * @param code - Language code in either ISO 639-1 or ISO 639-3 format\n * @returns Object containing both code formats\n *\n * @example\n * ```typescript\n * getLanguageCodePair(\"en\") // { iso639_1: \"en\", iso639_3: \"eng\" }\n * getLanguageCodePair(\"fra\") // { iso639_1: \"fr\", iso639_3: \"fra\" }\n * ```\n */\nexport function getLanguageCodePair(code: string): LanguageCodePair {\n const normalized = code.toLowerCase().trim();\n\n if (normalized.length === 2) {\n // Input is ISO 639-1\n return {\n iso639_1: normalized,\n iso639_3: toISO639_3(normalized),\n };\n } else if (normalized.length === 3) {\n // Input is ISO 639-3\n return {\n iso639_1: toISO639_1(normalized),\n iso639_3: normalized,\n };\n }\n\n // Unknown format, return as-is for both\n return {\n iso639_1: normalized,\n iso639_3: normalized,\n };\n}\n\n/**\n * Validates if a code is a known ISO 639-1 code.\n *\n * @param code - Code to validate\n * @returns true if the code is a known ISO 639-1 code\n */\nexport function isValidISO639_1(code: string): boolean {\n return code.length === 2 && code.toLowerCase() in ISO639_1_TO_3;\n}\n\n/**\n * Validates if a code is a known ISO 639-3 code.\n *\n * @param code - Code to validate\n * @returns true if the code is a known ISO 639-3 code\n */\nexport function isValidISO639_3(code: string): boolean {\n return code.length === 3 && code.toLowerCase() in ISO639_3_TO_1;\n}\n\n/**\n * Gets the human-readable language name for a given code.\n *\n * @param code - Language code in either ISO 639-1 or ISO 639-3 format\n * @returns Human-readable language name (e.g., \"English\", \"French\")\n */\nexport function getLanguageName(code: string): string {\n const iso639_1 = toISO639_1(code);\n try {\n const displayNames = new Intl.DisplayNames([\"en\"], { type: \"language\" });\n return displayNames.of(iso639_1) ?? code.toUpperCase();\n } catch {\n return code.toUpperCase();\n }\n}\n","import Mux from \"@mux/mux-node\";\nimport { generateObject } from \"ai\";\nimport { z } from \"zod\";\n\nimport env from \"@mux/ai/env\";\nimport { createWorkflowConfig, getMuxCredentialsFromEnv } from \"@mux/ai/lib/client-factory\";\nimport { getLanguageCodePair, getLanguageName } from \"@mux/ai/lib/language-codes\";\nimport type { LanguageCodePair, SupportedISO639_1 } from \"@mux/ai/lib/language-codes\";\nimport { getPlaybackIdForAsset } from \"@mux/ai/lib/mux-assets\";\nimport { createLanguageModelFromConfig } from \"@mux/ai/lib/providers\";\nimport type { ModelIdByProvider, SupportedProvider } from \"@mux/ai/lib/providers\";\nimport { getMuxSigningContextFromEnv, signUrl } from \"@mux/ai/lib/url-signing\";\nimport type { MuxAIOptions, TokenUsage } from \"@mux/ai/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Output returned from `translateCaptions`. */\nexport interface TranslationResult {\n assetId: string;\n /** Source language code (ISO 639-1 two-letter format). */\n sourceLanguageCode: SupportedISO639_1;\n /** Target language code (ISO 639-1 two-letter format). */\n targetLanguageCode: SupportedISO639_1;\n /**\n * Source language codes in both ISO 639-1 (2-letter) and ISO 639-3 (3-letter) formats.\n * Use `iso639_1` for browser players (BCP-47 compliant) and `iso639_3` for APIs that require it.\n */\n sourceLanguage: LanguageCodePair;\n /**\n * Target language codes in both ISO 639-1 (2-letter) and ISO 639-3 (3-letter) formats.\n * Use `iso639_1` for browser players (BCP-47 compliant) and `iso639_3` for APIs that require it.\n */\n targetLanguage: LanguageCodePair;\n originalVtt: string;\n translatedVtt: string;\n uploadedTrackId?: string;\n presignedUrl?: string;\n /** Token usage from the AI provider (for efficiency/cost analysis). */\n usage?: TokenUsage;\n}\n\n/** Configuration accepted by `translateCaptions`. */\nexport interface TranslationOptions<P extends SupportedProvider = SupportedProvider> extends MuxAIOptions {\n /** Provider responsible for the translation. */\n provider: P;\n /** Provider-specific chat model identifier. */\n model?: ModelIdByProvider[P];\n /** Optional override for the S3-compatible endpoint used for uploads. */\n s3Endpoint?: string;\n /** S3 region (defaults to env.S3_REGION or 'auto'). */\n s3Region?: string;\n /** Bucket that will store translated VTT files. */\n s3Bucket?: string;\n /**\n * When true (default) the translated VTT is uploaded to the configured\n * bucket and attached to the Mux asset.\n */\n uploadToMux?: boolean;\n}\n\n/** Schema used when requesting caption translation from a language model. */\nexport const translationSchema = z.object({\n translation: z.string(),\n});\n\n/** Inferred shape returned by `translationSchema`. */\nexport type TranslationPayload = z.infer<typeof translationSchema>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nasync function fetchVttFromMux(vttUrl: string): Promise<string> {\n \"use step\";\n\n const vttResponse = await fetch(vttUrl);\n if (!vttResponse.ok) {\n throw new Error(`Failed to fetch VTT file: ${vttResponse.statusText}`);\n }\n\n return vttResponse.text();\n}\n\nasync function translateVttWithAI({\n vttContent,\n fromLanguageCode,\n toLanguageCode,\n provider,\n modelId,\n abortSignal,\n}: {\n vttContent: string;\n fromLanguageCode: string;\n toLanguageCode: string;\n provider: SupportedProvider;\n modelId: string;\n abortSignal?: AbortSignal;\n}): Promise<{ translatedVtt: string; usage: TokenUsage }> {\n \"use step\";\n\n const languageModel = createLanguageModelFromConfig(provider, modelId);\n\n const response = await generateObject({\n model: languageModel,\n schema: translationSchema,\n abortSignal,\n messages: [\n {\n role: \"user\",\n content: `Translate the following VTT subtitle file from ${fromLanguageCode} to ${toLanguageCode}. Preserve all timestamps and VTT formatting exactly as they appear. Return JSON with a single key \"translation\" containing the translated VTT.\\n\\n${vttContent}`,\n },\n ],\n });\n\n return {\n translatedVtt: response.object.translation,\n usage: {\n inputTokens: response.usage.inputTokens,\n outputTokens: response.usage.outputTokens,\n totalTokens: response.usage.totalTokens,\n reasoningTokens: response.usage.reasoningTokens,\n cachedInputTokens: response.usage.cachedInputTokens,\n },\n };\n}\n\nasync function uploadVttToS3({\n translatedVtt,\n assetId,\n fromLanguageCode,\n toLanguageCode,\n s3Endpoint,\n s3Region,\n s3Bucket,\n}: {\n translatedVtt: string;\n assetId: string;\n fromLanguageCode: string;\n toLanguageCode: string;\n s3Endpoint: string;\n s3Region: string;\n s3Bucket: string;\n}): Promise<string> {\n \"use step\";\n\n const { S3Client, GetObjectCommand } = await import(\"@aws-sdk/client-s3\");\n const { Upload } = await import(\"@aws-sdk/lib-storage\");\n const { getSignedUrl } = await import(\"@aws-sdk/s3-request-presigner\");\n\n // asserting exists. already validated (See: translateAudio())\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID!;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY!;\n\n const s3Client = new S3Client({\n region: s3Region,\n endpoint: s3Endpoint,\n credentials: {\n accessKeyId: s3AccessKeyId,\n secretAccessKey: s3SecretAccessKey,\n },\n forcePathStyle: true,\n });\n\n // Create unique key for the VTT file\n const vttKey = `translations/${assetId}/${fromLanguageCode}-to-${toLanguageCode}-${Date.now()}.vtt`;\n\n // Upload VTT to S3\n const upload = new Upload({\n client: s3Client,\n params: {\n Bucket: s3Bucket,\n Key: vttKey,\n Body: translatedVtt,\n ContentType: \"text/vtt\",\n },\n });\n\n await upload.done();\n\n // Generate presigned URL (valid for 1 hour)\n const getObjectCommand = new GetObjectCommand({\n Bucket: s3Bucket,\n Key: vttKey,\n });\n\n const presignedUrl = await getSignedUrl(s3Client, getObjectCommand, {\n expiresIn: 3600, // 1 hour\n });\n\n return presignedUrl;\n}\n\nasync function createTextTrackOnMux(\n assetId: string,\n languageCode: string,\n trackName: string,\n presignedUrl: string,\n): Promise<string> {\n \"use step\";\n const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();\n const mux = new Mux({\n tokenId: muxTokenId,\n tokenSecret: muxTokenSecret,\n });\n const trackResponse = await mux.video.assets.createTrack(assetId, {\n type: \"text\",\n text_type: \"subtitles\",\n language_code: languageCode,\n name: trackName,\n url: presignedUrl,\n });\n\n if (!trackResponse.id) {\n throw new Error(\"Failed to create text track: no track ID returned from Mux\");\n }\n\n return trackResponse.id;\n}\n\nexport async function translateCaptions<P extends SupportedProvider = SupportedProvider>(\n assetId: string,\n fromLanguageCode: string,\n toLanguageCode: string,\n options: TranslationOptions<P>,\n): Promise<TranslationResult> {\n \"use workflow\";\n const {\n provider = \"openai\",\n model,\n s3Endpoint: providedS3Endpoint,\n s3Region: providedS3Region,\n s3Bucket: providedS3Bucket,\n uploadToMux: uploadToMuxOption,\n } = options;\n\n // S3 configuration\n const s3Endpoint = providedS3Endpoint ?? env.S3_ENDPOINT;\n const s3Region = providedS3Region ?? env.S3_REGION ?? \"auto\";\n const s3Bucket = providedS3Bucket ?? env.S3_BUCKET;\n const s3AccessKeyId = env.S3_ACCESS_KEY_ID;\n const s3SecretAccessKey = env.S3_SECRET_ACCESS_KEY;\n const uploadToMux = uploadToMuxOption !== false; // Default to true\n\n // Validate credentials and resolve language model\n const config = await createWorkflowConfig(\n { ...options, model },\n provider as SupportedProvider,\n );\n\n if (uploadToMux && (!s3Endpoint || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey)) {\n throw new Error(\"S3 configuration is required for uploading to Mux. Provide s3Endpoint, s3Bucket, s3AccessKeyId, and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.\");\n }\n\n // Fetch asset data and playback ID from Mux\n const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);\n\n // Resolve signing context for signed playback IDs\n const signingContext = getMuxSigningContextFromEnv();\n if (policy === \"signed\" && !signingContext) {\n throw new Error(\n \"Signed playback ID requires signing credentials. \" +\n \"Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.\",\n );\n }\n\n // Find text track with the source language\n if (!assetData.tracks) {\n throw new Error(\"No tracks found for this asset\");\n }\n\n const sourceTextTrack = assetData.tracks.find(track =>\n track.type === \"text\" &&\n track.status === \"ready\" &&\n track.language_code === fromLanguageCode,\n );\n\n if (!sourceTextTrack) {\n throw new Error(`No ready text track found with language code '${fromLanguageCode}' for this asset`);\n }\n\n // Fetch the VTT file content (signed if needed)\n let vttUrl = `https://stream.mux.com/${playbackId}/text/${sourceTextTrack.id}.vtt`;\n if (policy === \"signed\" && signingContext) {\n vttUrl = await signUrl(vttUrl, playbackId, signingContext, \"video\");\n }\n\n let vttContent: string;\n try {\n vttContent = await fetchVttFromMux(vttUrl);\n } catch (error) {\n throw new Error(`Failed to fetch VTT content: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Translate VTT content using configured provider via ai-sdk\n let translatedVtt: string;\n let usage: TokenUsage | undefined;\n\n try {\n const result = await translateVttWithAI({\n vttContent,\n fromLanguageCode,\n toLanguageCode,\n provider: config.provider,\n modelId: config.modelId,\n abortSignal: options.abortSignal,\n });\n translatedVtt = result.translatedVtt;\n usage = result.usage;\n } catch (error) {\n throw new Error(`Failed to translate VTT with ${config.provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Resolve language code pairs for both source and target\n const sourceLanguage = getLanguageCodePair(fromLanguageCode);\n const targetLanguage = getLanguageCodePair(toLanguageCode);\n\n // If uploadToMux is false, just return the translation\n if (!uploadToMux) {\n return {\n assetId,\n sourceLanguageCode: fromLanguageCode as SupportedISO639_1,\n targetLanguageCode: toLanguageCode as SupportedISO639_1,\n sourceLanguage,\n targetLanguage,\n originalVtt: vttContent,\n translatedVtt,\n usage,\n };\n }\n\n // Upload translated VTT to S3-compatible storage\n let presignedUrl: string;\n\n try {\n presignedUrl = await uploadVttToS3({\n translatedVtt,\n assetId,\n fromLanguageCode,\n toLanguageCode,\n s3Endpoint: s3Endpoint!,\n s3Region,\n s3Bucket: s3Bucket!,\n });\n } catch (error) {\n throw new Error(`Failed to upload VTT to S3: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n // Add translated track to Mux asset\n let uploadedTrackId: string | undefined;\n\n try {\n const languageName = getLanguageName(toLanguageCode);\n const trackName = `${languageName} (auto-translated)`;\n\n uploadedTrackId = await createTextTrackOnMux(assetId, toLanguageCode, trackName, presignedUrl);\n } catch (error) {\n console.warn(`Failed to add track to Mux asset: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n }\n\n return {\n assetId,\n sourceLanguageCode: fromLanguageCode as SupportedISO639_1,\n targetLanguageCode: toLanguageCode as SupportedISO639_1,\n sourceLanguage,\n targetLanguage,\n originalVtt: vttContent,\n translatedVtt,\n uploadedTrackId,\n presignedUrl,\n usage,\n };\n}\n"],"mappings":"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","names":["z","Mux","Mux","z","generateObject","z","z","generateObject","SYSTEM_PROMPT","chunkText","DEFAULT_PROVIDER","generateObject","dedent","z","z","dedent","SYSTEM_PROMPT","buildUserPrompt","analyzeStoryboard","generateObject","Mux","Mux","message","targetLanguage","Mux","generateObject","z","z","generateObject","Mux"]}