@mux/ai 0.7.6 → 0.8.1

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.
@@ -523,6 +523,12 @@ interface SummarizationOptions extends MuxAIOptions {
523
523
  * Useful for customizing the AI's output for specific use cases (SEO, social media, etc.)
524
524
  */
525
525
  promptOverrides?: SummarizationPromptOverrides;
526
+ /** Desired title length in characters. */
527
+ titleLength?: number;
528
+ /** Desired description length in characters. */
529
+ descriptionLength?: number;
530
+ /** Desired number of tags. */
531
+ tagCount?: number;
526
532
  }
527
533
  declare function getSummaryAndTags(assetId: string, options?: SummarizationOptions): Promise<SummaryAndTagsResult>;
528
534
 
package/dist/index.d.ts CHANGED
@@ -2,14 +2,14 @@ import { W as WorkflowCredentials, S as StoragePutObjectInput, a as StoragePresi
2
2
  export { A as AssetTextTrack, C as ChunkEmbedding, b as ChunkingStrategy, E as Encrypted, c as EncryptedPayload, I as ImageSubmissionMode, M as MuxAIOptions, d as MuxAsset, P as PlaybackAsset, e as PlaybackPolicy, f as StorageAdapter, T as TextChunk, g as TokenChunkingConfig, h as TokenUsage, i as ToneType, U as UsageMetadata, V as VTTChunkingConfig, j as VideoEmbeddingsResult, k as WorkflowCredentialsInput, l as WorkflowMuxClient, m as decryptFromWorkflow, n as encryptForWorkflow } from './types-BRbaGW3t.js';
3
3
  import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@workflow/serde';
4
4
  export { i as primitives } from './index-Nxf6BaBO.js';
5
- export { i as workflows } from './index-B0U9upb4.js';
5
+ export { i as workflows } from './index-DP02N3iR.js';
6
6
  import '@mux/mux-node';
7
7
  import 'zod';
8
8
  import '@ai-sdk/anthropic';
9
9
  import '@ai-sdk/google';
10
10
  import '@ai-sdk/openai';
11
11
 
12
- var version = "0.7.6";
12
+ var version = "0.8.1";
13
13
 
14
14
  /**
15
15
  * A function that returns workflow credentials, either synchronously or asynchronously.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // package.json
8
- var version = "0.7.6";
8
+ var version = "0.8.1";
9
9
 
10
10
  // src/env.ts
11
11
  import { z } from "zod";
@@ -821,8 +821,44 @@ var DEFAULT_EMBEDDING_MODELS = {
821
821
  var LANGUAGE_MODELS = {
822
822
  openai: ["gpt-5.1", "gpt-5-mini"],
823
823
  anthropic: ["claude-sonnet-4-5"],
824
- google: ["gemini-3-flash-preview", "gemini-2.5-flash"]
824
+ google: ["gemini-3-flash-preview", "gemini-3.1-flash-lite-preview", "gemini-2.5-flash"]
825
825
  };
826
+ var LANGUAGE_MODEL_DEPRECATIONS = [
827
+ {
828
+ provider: "google",
829
+ modelId: "gemini-2.5-flash",
830
+ replacementModelId: "gemini-3.1-flash-lite-preview",
831
+ phase: "warn",
832
+ deprecatedOn: "2026-03-03",
833
+ sunsetOn: "2026-06-30",
834
+ reason: "Gemini 3.1 Flash-Lite Preview offers better quality/latency/cost balance in current evals."
835
+ }
836
+ ];
837
+ var warnedDeprecatedLanguageModels = /* @__PURE__ */ new Set();
838
+ function getLanguageModelDeprecation(provider, modelId) {
839
+ return LANGUAGE_MODEL_DEPRECATIONS.find(
840
+ (deprecation) => deprecation.provider === provider && deprecation.modelId === modelId
841
+ );
842
+ }
843
+ function maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId) {
844
+ const deprecation = getLanguageModelDeprecation(provider, modelId);
845
+ if (!deprecation) {
846
+ return;
847
+ }
848
+ const replacementText = deprecation.replacementModelId ? ` Use replacement provider="${provider}" model="${deprecation.replacementModelId}" instead.` : "";
849
+ const sunsetText = deprecation.sunsetOn ? ` Planned removal date: ${deprecation.sunsetOn}.` : "";
850
+ const reasonText = deprecation.reason ? ` Reason: ${deprecation.reason}` : "";
851
+ const message = deprecation.phase === "blocked" ? `Language model is no longer supported for provider="${provider}" model="${modelId}".${replacementText}${reasonText}` : `Language model is deprecated and in a grace period for provider="${provider}" model="${modelId}".${replacementText}${sunsetText}${reasonText}`;
852
+ if (deprecation.phase === "blocked") {
853
+ throw new Error(message);
854
+ }
855
+ const warningKey = `${provider}:${modelId}`;
856
+ if (warnedDeprecatedLanguageModels.has(warningKey)) {
857
+ return;
858
+ }
859
+ warnedDeprecatedLanguageModels.add(warningKey);
860
+ console.warn(message);
861
+ }
826
862
  function getDefaultEvalModelConfigs() {
827
863
  return Object.entries(DEFAULT_LANGUAGE_MODELS).map(([provider, modelId]) => ({ provider, modelId }));
828
864
  }
@@ -853,6 +889,7 @@ function parseEvalModelPair(value) {
853
889
  `Unsupported eval model "${modelId}" for provider "${provider}". Supported models: ${supportedModels.join(", ")}.`
854
890
  );
855
891
  }
892
+ maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId);
856
893
  return {
857
894
  provider,
858
895
  modelId
@@ -896,6 +933,7 @@ var EVAL_MODEL_CONFIGS = resolveEvalModelConfigsFromEnv();
896
933
  function resolveLanguageModelConfig(options = {}) {
897
934
  const provider = options.provider || "openai";
898
935
  const modelId = options.model || DEFAULT_LANGUAGE_MODELS[provider];
936
+ maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId);
899
937
  return { provider, modelId };
900
938
  }
901
939
  function resolveEmbeddingModelConfig(options = {}) {
@@ -904,6 +942,7 @@ function resolveEmbeddingModelConfig(options = {}) {
904
942
  return { provider, modelId };
905
943
  }
906
944
  async function createLanguageModelFromConfig(provider, modelId, credentials) {
945
+ maybeWarnOrThrowForDeprecatedLanguageModel(provider, modelId);
907
946
  switch (provider) {
908
947
  case "openai": {
909
948
  const apiKey = await resolveProviderApiKey("openai", credentials);
@@ -1798,16 +1837,6 @@ var SYSTEM_PROMPT = dedent`
1798
1837
  - GOOD: "A person runs through a park"
1799
1838
  - Be specific and evidence-based
1800
1839
  </language_guidelines>`;
1801
- function buildSystemPrompt(allowedAnswers) {
1802
- const answerList = allowedAnswers.map((answer) => `"${answer}"`).join(", ");
1803
- return `${SYSTEM_PROMPT}
1804
-
1805
- ${dedent`
1806
- <response_options>
1807
- Allowed answers: ${answerList}
1808
- </response_options>
1809
- `}`;
1810
- }
1811
1840
  var askQuestionsPromptBuilder = createPromptBuilder({
1812
1841
  template: {
1813
1842
  questions: {
@@ -1817,21 +1846,30 @@ var askQuestionsPromptBuilder = createPromptBuilder({
1817
1846
  },
1818
1847
  sectionOrder: ["questions"]
1819
1848
  });
1820
- function buildUserPrompt(questions, transcriptText, isCleanTranscript = true) {
1849
+ function buildUserPrompt(questions, allowedAnswers, transcriptText, isCleanTranscript = true) {
1821
1850
  const questionsList = questions.map((q, idx) => `${idx + 1}. ${q.question}`).join("\n");
1822
1851
  const questionsContent = dedent`
1823
1852
  Please answer the following yes/no questions about this video:
1824
1853
 
1825
1854
  ${questionsList}`;
1855
+ const answerList = allowedAnswers.map((answer) => `"${answer}"`).join(", ");
1856
+ const responseOptions = dedent`
1857
+ <response_options>
1858
+ Allowed answers: ${answerList}
1859
+ </response_options>`;
1860
+ const questionsSection = askQuestionsPromptBuilder.build({ questions: questionsContent });
1826
1861
  if (!transcriptText) {
1827
- return askQuestionsPromptBuilder.build({ questions: questionsContent });
1862
+ return `${questionsSection}
1863
+
1864
+ ${responseOptions}`;
1828
1865
  }
1829
1866
  const format = isCleanTranscript ? "plain text" : "WebVTT";
1830
- const transcriptSection = createTranscriptSection(transcriptText, format);
1831
- return askQuestionsPromptBuilder.buildWithContext(
1832
- { questions: questionsContent },
1833
- [transcriptSection]
1834
- );
1867
+ const transcriptSection = renderSection(createTranscriptSection(transcriptText, format));
1868
+ return `${transcriptSection}
1869
+
1870
+ ${questionsSection}
1871
+
1872
+ ${responseOptions}`;
1835
1873
  }
1836
1874
  async function fetchImageAsBase64(imageUrl, imageDownloadOptions) {
1837
1875
  "use step";
@@ -1927,8 +1965,8 @@ async function askQuestions(assetId, questions, options) {
1927
1965
  cleanTranscript,
1928
1966
  shouldSign: policy === "signed"
1929
1967
  })).transcriptText : "";
1930
- const userPrompt = buildUserPrompt(questions, transcriptText, cleanTranscript);
1931
- const systemPrompt = buildSystemPrompt(normalizedAnswerOptions);
1968
+ const userPrompt = buildUserPrompt(questions, allowedAnswers, transcriptText, cleanTranscript);
1969
+ const systemPrompt = SYSTEM_PROMPT;
1932
1970
  const imageUrl = await getStoryboardUrl(
1933
1971
  playbackId,
1934
1972
  storyboardWidth,
@@ -2643,7 +2681,7 @@ function planSamplingTimestamps(options) {
2643
2681
 
2644
2682
  // src/workflows/moderation.ts
2645
2683
  var DEFAULT_THRESHOLDS = {
2646
- sexual: 0.7,
2684
+ sexual: 0.8,
2647
2685
  violence: 0.8
2648
2686
  };
2649
2687
  var DEFAULT_PROVIDER2 = "openai";
@@ -3085,96 +3123,106 @@ var TONE_INSTRUCTIONS = {
3085
3123
  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!",
3086
3124
  professional: "Provide a professional, executive-level analysis suitable for business reporting."
3087
3125
  };
3088
- var summarizationPromptBuilder = createPromptBuilder({
3089
- template: {
3090
- task: {
3091
- tag: "task",
3092
- content: "Analyze the storyboard frames and generate metadata that captures the essence of the video content."
3093
- },
3094
- title: {
3095
- tag: "title_requirements",
3096
- content: dedent4`
3097
- A short, compelling headline that immediately communicates the subject or action.
3098
- Aim for brevity - typically under 10 words. Think of how a news headline or video card title would read.
3099
- Start with the primary subject, action, or topic - never begin with "A video of" or similar phrasing.
3100
- Use active, specific language.`
3101
- },
3102
- description: {
3103
- tag: "description_requirements",
3104
- content: dedent4`
3105
- A concise summary (2-4 sentences) that describes what happens across the video.
3106
- Cover the main subjects, actions, setting, and any notable progression visible across frames.
3107
- Write in present tense. Be specific about observable details rather than making assumptions.
3108
- If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`
3109
- },
3110
- keywords: {
3111
- tag: "keywords_requirements",
3112
- content: dedent4`
3113
- Specific, searchable terms (up to ${SUMMARY_KEYWORD_LIMIT}) that capture:
3114
- - Primary subjects (people, animals, objects)
3115
- - Actions and activities being performed
3116
- - Setting and environment
3117
- - Notable objects or tools
3118
- - Style or genre (if applicable)
3119
- Prefer concrete nouns and action verbs over abstract concepts.
3120
- Use lowercase. Avoid redundant or overly generic terms like "video" or "content".`
3121
- },
3122
- qualityGuidelines: {
3123
- tag: "quality_guidelines",
3124
- content: dedent4`
3125
- - Examine all frames to understand the full context and progression
3126
- - Be precise: "golden retriever" is better than "dog" when identifiable
3127
- - Capture the narrative: what begins, develops, and concludes
3128
- - Balance brevity with informativeness`
3129
- }
3130
- },
3131
- sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
3132
- });
3133
- var audioOnlyPromptBuilder = createPromptBuilder({
3134
- template: {
3135
- task: {
3136
- tag: "task",
3137
- content: "Analyze the transcript and generate metadata that captures the essence of the audio content."
3138
- },
3139
- title: {
3140
- tag: "title_requirements",
3141
- content: dedent4`
3142
- A short, compelling headline that immediately communicates the subject or topic.
3143
- Aim for brevity - typically under 10 words. Think of how a podcast title or audio description would read.
3144
- Start with the primary subject, action, or topic - never begin with "An audio of" or similar phrasing.
3145
- Use active, specific language.`
3146
- },
3147
- description: {
3148
- tag: "description_requirements",
3149
- content: dedent4`
3150
- A concise summary (2-4 sentences) that describes the audio content.
3151
- Cover the main topics, speakers, themes, and any notable progression in the discussion or narration.
3152
- Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
3153
- Focus on the spoken content and any key insights, dialogue, or narrative elements.`
3126
+ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount } = {}) {
3127
+ const titleBrevity = titleLength != null ? `Aim for approximately ${titleLength} characters.` : "Aim for brevity - typically under 10 words.";
3128
+ const descConstraint = descriptionLength != null ? `approximately ${descriptionLength} characters` : "2-4 sentences";
3129
+ const keywordLimit = tagCount ?? SUMMARY_KEYWORD_LIMIT;
3130
+ return createPromptBuilder({
3131
+ template: {
3132
+ task: {
3133
+ tag: "task",
3134
+ content: "Analyze the storyboard frames and generate metadata that captures the essence of the video content."
3135
+ },
3136
+ title: {
3137
+ tag: "title_requirements",
3138
+ content: dedent4`
3139
+ A short, compelling headline that immediately communicates the subject or action.
3140
+ ${titleBrevity} Think of how a news headline or video card title would read.
3141
+ Start with the primary subject, action, or topic - never begin with "A video of" or similar phrasing.
3142
+ Use active, specific language.`
3143
+ },
3144
+ description: {
3145
+ tag: "description_requirements",
3146
+ content: dedent4`
3147
+ A concise summary (${descConstraint}) that describes what happens across the video.
3148
+ Cover the main subjects, actions, setting, and any notable progression visible across frames.
3149
+ Write in present tense. Be specific about observable details rather than making assumptions.
3150
+ If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`
3151
+ },
3152
+ keywords: {
3153
+ tag: "keywords_requirements",
3154
+ content: dedent4`
3155
+ Specific, searchable terms (up to ${keywordLimit}) that capture:
3156
+ - Primary subjects (people, animals, objects)
3157
+ - Actions and activities being performed
3158
+ - Setting and environment
3159
+ - Notable objects or tools
3160
+ - Style or genre (if applicable)
3161
+ Prefer concrete nouns and action verbs over abstract concepts.
3162
+ Use lowercase. Avoid redundant or overly generic terms like "video" or "content".`
3163
+ },
3164
+ qualityGuidelines: {
3165
+ tag: "quality_guidelines",
3166
+ content: dedent4`
3167
+ - Examine all frames to understand the full context and progression
3168
+ - Be precise: "golden retriever" is better than "dog" when identifiable
3169
+ - Capture the narrative: what begins, develops, and concludes
3170
+ - Balance brevity with informativeness`
3171
+ }
3154
3172
  },
3155
- keywords: {
3156
- tag: "keywords_requirements",
3157
- content: dedent4`
3158
- Specific, searchable terms (up to ${SUMMARY_KEYWORD_LIMIT}) that capture:
3159
- - Primary topics and themes
3160
- - Speakers or presenters (if named)
3161
- - Key concepts and terminology
3162
- - Content type (interview, lecture, music, etc.)
3163
- - Genre or style (if applicable)
3164
- Prefer concrete nouns and relevant terms over abstract concepts.
3165
- Use lowercase. Avoid redundant or overly generic terms like "audio" or "content".`
3173
+ sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
3174
+ });
3175
+ }
3176
+ function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {}) {
3177
+ const titleBrevity = titleLength != null ? `Aim for approximately ${titleLength} characters.` : "Aim for brevity - typically under 10 words.";
3178
+ const descConstraint = descriptionLength != null ? `approximately ${descriptionLength} characters` : "2-4 sentences";
3179
+ const keywordLimit = tagCount ?? SUMMARY_KEYWORD_LIMIT;
3180
+ return createPromptBuilder({
3181
+ template: {
3182
+ task: {
3183
+ tag: "task",
3184
+ content: "Analyze the transcript and generate metadata that captures the essence of the audio content."
3185
+ },
3186
+ title: {
3187
+ tag: "title_requirements",
3188
+ content: dedent4`
3189
+ A short, compelling headline that immediately communicates the subject or topic.
3190
+ ${titleBrevity} Think of how a podcast title or audio description would read.
3191
+ Start with the primary subject, action, or topic - never begin with "An audio of" or similar phrasing.
3192
+ Use active, specific language.`
3193
+ },
3194
+ description: {
3195
+ tag: "description_requirements",
3196
+ content: dedent4`
3197
+ A concise summary (${descConstraint}) that describes the audio content.
3198
+ Cover the main topics, speakers, themes, and any notable progression in the discussion or narration.
3199
+ Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
3200
+ Focus on the spoken content and any key insights, dialogue, or narrative elements.`
3201
+ },
3202
+ keywords: {
3203
+ tag: "keywords_requirements",
3204
+ content: dedent4`
3205
+ Specific, searchable terms (up to ${keywordLimit}) that capture:
3206
+ - Primary topics and themes
3207
+ - Speakers or presenters (if named)
3208
+ - Key concepts and terminology
3209
+ - Content type (interview, lecture, music, etc.)
3210
+ - Genre or style (if applicable)
3211
+ Prefer concrete nouns and relevant terms over abstract concepts.
3212
+ Use lowercase. Avoid redundant or overly generic terms like "audio" or "content".`
3213
+ },
3214
+ qualityGuidelines: {
3215
+ tag: "quality_guidelines",
3216
+ content: dedent4`
3217
+ - Analyze the full transcript to understand context and themes
3218
+ - Be precise: use specific terminology when mentioned
3219
+ - Capture the narrative: what is introduced, discussed, and concluded
3220
+ - Balance brevity with informativeness`
3221
+ }
3166
3222
  },
3167
- qualityGuidelines: {
3168
- tag: "quality_guidelines",
3169
- content: dedent4`
3170
- - Analyze the full transcript to understand context and themes
3171
- - Be precise: use specific terminology when mentioned
3172
- - Capture the narrative: what is introduced, discussed, and concluded
3173
- - Balance brevity with informativeness`
3174
- }
3175
- },
3176
- sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
3177
- });
3223
+ sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
3224
+ });
3225
+ }
3178
3226
  var SYSTEM_PROMPT3 = dedent4`
3179
3227
  <role>
3180
3228
  You are a video content analyst specializing in storyboard interpretation and multimodal analysis.
@@ -3289,14 +3337,18 @@ function buildUserPrompt4({
3289
3337
  transcriptText,
3290
3338
  isCleanTranscript = true,
3291
3339
  promptOverrides,
3292
- isAudioOnly = false
3340
+ isAudioOnly = false,
3341
+ titleLength,
3342
+ descriptionLength,
3343
+ tagCount
3293
3344
  }) {
3294
3345
  const contextSections = [createToneSection(TONE_INSTRUCTIONS[tone])];
3295
3346
  if (transcriptText) {
3296
3347
  const format = isCleanTranscript ? "plain text" : "WebVTT";
3297
3348
  contextSections.push(createTranscriptSection(transcriptText, format));
3298
3349
  }
3299
- const promptBuilder = isAudioOnly ? audioOnlyPromptBuilder : summarizationPromptBuilder;
3350
+ const constraints = { titleLength, descriptionLength, tagCount };
3351
+ const promptBuilder = isAudioOnly ? createAudioOnlyBuilder(constraints) : createSummarizationBuilder(constraints);
3300
3352
  return promptBuilder.buildWithContext(promptOverrides, contextSections);
3301
3353
  }
3302
3354
  async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, systemPrompt, credentials) {
@@ -3366,7 +3418,7 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, cre
3366
3418
  }
3367
3419
  };
3368
3420
  }
3369
- function normalizeKeywords(keywords) {
3421
+ function normalizeKeywords(keywords, limit = SUMMARY_KEYWORD_LIMIT) {
3370
3422
  if (!Array.isArray(keywords) || keywords.length === 0) {
3371
3423
  return [];
3372
3424
  }
@@ -3383,7 +3435,7 @@ function normalizeKeywords(keywords) {
3383
3435
  }
3384
3436
  uniqueLowercase.add(lower);
3385
3437
  normalized.push(trimmed);
3386
- if (normalized.length === SUMMARY_KEYWORD_LIMIT) {
3438
+ if (normalized.length === limit) {
3387
3439
  break;
3388
3440
  }
3389
3441
  }
@@ -3400,7 +3452,10 @@ async function getSummaryAndTags(assetId, options) {
3400
3452
  imageSubmissionMode = "url",
3401
3453
  imageDownloadOptions,
3402
3454
  promptOverrides,
3403
- credentials
3455
+ credentials,
3456
+ titleLength,
3457
+ descriptionLength,
3458
+ tagCount
3404
3459
  } = options ?? {};
3405
3460
  if (!VALID_TONES.includes(tone)) {
3406
3461
  throw new Error(
@@ -3438,7 +3493,10 @@ async function getSummaryAndTags(assetId, options) {
3438
3493
  transcriptText,
3439
3494
  isCleanTranscript: cleanTranscript,
3440
3495
  promptOverrides,
3441
- isAudioOnly
3496
+ isAudioOnly,
3497
+ titleLength,
3498
+ descriptionLength,
3499
+ tagCount
3442
3500
  });
3443
3501
  let analysisResponse;
3444
3502
  let imageUrl;
@@ -3495,7 +3553,7 @@ async function getSummaryAndTags(assetId, options) {
3495
3553
  assetId,
3496
3554
  title: analysisResponse.result.title,
3497
3555
  description: analysisResponse.result.description,
3498
- tags: normalizeKeywords(analysisResponse.result.keywords),
3556
+ tags: normalizeKeywords(analysisResponse.result.keywords, tagCount ?? SUMMARY_KEYWORD_LIMIT),
3499
3557
  storyboardUrl: imageUrl,
3500
3558
  // undefined for audio-only assets
3501
3559
  usage: {
@@ -4115,6 +4173,7 @@ import { z as z6 } from "zod";
4115
4173
  var translationSchema = z6.object({
4116
4174
  translation: z6.string()
4117
4175
  });
4176
+ var SYSTEM_PROMPT4 = 'You are a subtitle translation expert. Translate VTT subtitle files to the target language specified by the user. Preserve all timestamps and VTT formatting exactly as they appear. Return JSON with a single key "translation" containing the translated VTT content.';
4118
4177
  async function fetchVttFromMux(vttUrl) {
4119
4178
  "use step";
4120
4179
  const vttResponse = await fetch(vttUrl);
@@ -4137,9 +4196,13 @@ async function translateVttWithAI({
4137
4196
  model,
4138
4197
  output: Output5.object({ schema: translationSchema }),
4139
4198
  messages: [
4199
+ {
4200
+ role: "system",
4201
+ content: SYSTEM_PROMPT4
4202
+ },
4140
4203
  {
4141
4204
  role: "user",
4142
- 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.
4205
+ content: `Translate from ${fromLanguageCode} to ${toLanguageCode}:
4143
4206
 
4144
4207
  ${vttContent}`
4145
4208
  }