mulmocast 2.15.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,9 +16,10 @@ export const provider2TTSAgent = {
16
16
  gemini: {
17
17
  agentName: "ttsGeminiAgent",
18
18
  hasLimitedConcurrency: false,
19
- defaultModel: "gemini-2.5-flash-preview-tts",
19
+ // The 2.5 preview TTS models shut down on 2026-11-17; Google names gemini-3.8-flash-tts as their successor.
20
+ defaultModel: "gemini-3.8-flash-tts",
20
21
  defaultVoice: "Kore",
21
- models: ["gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts"],
22
+ models: ["gemini-3.8-flash-tts", "gemini-3.8-flash-lite-tts", "gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts"],
22
23
  keyName: "GEMINI_API_KEY",
23
24
  },
24
25
  elevenlabs: {
@@ -44,13 +45,18 @@ export const provider2TTSAgent = {
44
45
  models: ["mock-model"],
45
46
  },
46
47
  };
47
- export const gptImages = ["gpt-image-2", "gpt-image-1.5", "gpt-image-1", "gpt-image-1-mini"];
48
- const supportedOpenAIImageReplacementHint = "Use 'gpt-image-1' or another supported model.";
48
+ export const gptImages = ["gpt-image-2.5-sunburst", "gpt-image-2.5-flare", "gpt-image-2", "gpt-image-1.5", "gpt-image-1", "gpt-image-1-mini"];
49
+ // The 2.5 models render "auto" (their default) at "low", where gpt-image-1 rendered it at "high".
50
+ export const isGptImage25Model = (model) => model.startsWith("gpt-image-2.5");
51
+ // An unset quality on 2.5 is sent as "high" to keep the look scripts had before the default model changed;
52
+ // an explicit "auto" is the script's choice and is sent as is.
53
+ export const gptImageQuality = (model, quality) => quality ?? (isGptImage25Model(model) ? "high" : undefined);
54
+ const supportedOpenAIImageReplacementHint = "Use 'gpt-image-2.5-sunburst' or another supported model.";
49
55
  export const deprecatedOpenAIImageModelHints = {
50
56
  "dall-e-2": supportedOpenAIImageReplacementHint,
51
57
  "dall-e-3": supportedOpenAIImageReplacementHint,
52
58
  };
53
- const supportedGoogleImageReplacementHint = "Use 'gemini-2.5-flash-image' or 'gemini-3-pro-image-preview' instead.";
59
+ const supportedGoogleImageReplacementHint = "Use 'gemini-3.1-flash-lite-image' or 'gemini-3-pro-image' instead.";
54
60
  export const deprecatedGoogleImageModelHints = {
55
61
  "imagen-3.0-generate-002": supportedGoogleImageReplacementHint,
56
62
  "imagen-4.0-generate-001": supportedGoogleImageReplacementHint,
@@ -59,11 +65,21 @@ export const deprecatedGoogleImageModelHints = {
59
65
  "imagen-4.0-generate-preview-06-06": supportedGoogleImageReplacementHint,
60
66
  "imagen-4.0-ultra-generate-preview-06-06": supportedGoogleImageReplacementHint,
61
67
  };
68
+ // Gemini image models are called through generateContent; anything else is treated as an Imagen model (generateImages).
69
+ export const isGeminiImageModel = (model) => model.startsWith("gemini-");
62
70
  // Google image models that on Vertex AI are only published under location "global"
63
71
  // (regional endpoints like us-central1 return 404 NOT_FOUND).
64
72
  // See https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-pro-image
65
73
  // and https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-1-flash-image
66
- export const vertexAIGlobalOnlyImageModels = new Set(["gemini-3-pro-image-preview", "gemini-3.1-flash-image-preview"]);
74
+ export const vertexAIGlobalOnlyImageModels = new Set([
75
+ "gemini-3.1-flash-lite-image",
76
+ "gemini-3-pro-image",
77
+ "gemini-3-pro-image-preview",
78
+ "gemini-3.1-flash-image-preview",
79
+ ]);
80
+ const VERTEX_DEFAULT_IMAGE_LOCATION = "us-central1";
81
+ // A global-only model has no regional endpoint, so an omitted location means "global" for it.
82
+ export const vertexImageLocation = (model, location) => location ?? (vertexAIGlobalOnlyImageModels.has(model) ? "global" : VERTEX_DEFAULT_IMAGE_LOCATION);
67
83
  // Per-model reference image limits (image_input array). Only verified entries; unlisted models are not truncated.
68
84
  const replicateImageModelParams = {
69
85
  "bytedance/seedream-4": { maxReferenceImages: 10 },
@@ -71,7 +87,9 @@ const replicateImageModelParams = {
71
87
  export const provider2ImageAgent = {
72
88
  openai: {
73
89
  agentName: "imageOpenaiAgent",
74
- defaultModel: "gpt-image-1",
90
+ // gpt-image-1 shuts down on 2026-10-23; OpenAI names gpt-image-2.5-sunburst or -flare as its successor.
91
+ // https://developers.openai.com/api/docs/deprecations
92
+ defaultModel: "gpt-image-2.5-sunburst",
75
93
  models: [...gptImages],
76
94
  keyName: "OPENAI_API_KEY",
77
95
  baseURLKeyName: "OPENAI_BASE_URL",
@@ -79,8 +97,16 @@ export const provider2ImageAgent = {
79
97
  },
80
98
  google: {
81
99
  agentName: "imageGenAIAgent",
82
- defaultModel: "gemini-2.5-flash-image",
83
- models: ["gemini-2.5-flash-image", "gemini-3.1-flash-image-preview", "gemini-3-pro-image-preview"],
100
+ // gemini-2.5-flash-image is deprecated; Google names gemini-3.1-flash-lite-image as its successor.
101
+ defaultModel: "gemini-3.1-flash-lite-image",
102
+ models: [
103
+ "gemini-3.1-flash-lite-image",
104
+ "gemini-3.1-flash-image",
105
+ "gemini-3-pro-image",
106
+ "gemini-2.5-flash-image",
107
+ "gemini-3.1-flash-image-preview",
108
+ "gemini-3-pro-image-preview",
109
+ ],
84
110
  keyName: "GEMINI_API_KEY",
85
111
  },
86
112
  replicate: {
@@ -126,7 +152,6 @@ export const provider2MovieAgent = {
126
152
  "bytedance/seedance-1-pro",
127
153
  "bytedance/seedance-2.0",
128
154
  "bytedance/seedance-2.0-fast",
129
- "kwaivgi/kling-v1.6-pro",
130
155
  "kwaivgi/kling-v2.1",
131
156
  "kwaivgi/kling-v2.1-master",
132
157
  "google/veo-2",
@@ -173,20 +198,14 @@ export const provider2MovieAgent = {
173
198
  start_image: "image",
174
199
  last_image: "last_frame_image",
175
200
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
176
- price_per_sec: 0.29,
201
+ price_per_sec: 0.18,
177
202
  },
178
203
  "bytedance/seedance-2.0-fast": {
179
204
  durations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
180
205
  start_image: "image",
181
206
  last_image: "last_frame_image",
182
207
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
183
- price_per_sec: 0.22,
184
- },
185
- "kwaivgi/kling-v1.6-pro": {
186
- durations: [5, 10],
187
- start_image: "start_image",
188
- audio: { mode: AUDIO_MODE_NEVER },
189
- price_per_sec: 0.095,
208
+ price_per_sec: 0.15,
190
209
  },
191
210
  "kwaivgi/kling-v2.1": {
192
211
  durations: [5, 10],
@@ -210,7 +229,7 @@ export const provider2MovieAgent = {
210
229
  durations: [8],
211
230
  start_image: "image",
212
231
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
213
- price_per_sec: 0.75,
232
+ price_per_sec: 0.2,
214
233
  },
215
234
  "google/veo-3.1": {
216
235
  durations: [4, 6, 8],
@@ -218,14 +237,14 @@ export const provider2MovieAgent = {
218
237
  last_image: "last_frame_image",
219
238
  reference_images_param: "reference_images",
220
239
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
221
- price_per_sec: 0.75,
240
+ price_per_sec: 0.2,
222
241
  },
223
242
  "google/veo-3.1-fast": {
224
243
  durations: [4, 6, 8],
225
244
  start_image: "image",
226
245
  last_image: "last_frame_image",
227
246
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
228
- price_per_sec: 0.4,
247
+ price_per_sec: 0.1,
229
248
  },
230
249
  "google/veo-3.1-lite": {
231
250
  durations: [4, 6, 8],
@@ -238,13 +257,13 @@ export const provider2MovieAgent = {
238
257
  durations: [8],
239
258
  start_image: "image",
240
259
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
241
- price_per_sec: 0.4,
260
+ price_per_sec: 0.1,
242
261
  },
243
262
  "minimax/video-01": {
244
263
  durations: [6],
245
264
  start_image: "first_frame_image",
246
265
  audio: { mode: AUDIO_MODE_NEVER },
247
- price_per_sec: 0.5,
266
+ price_per_sec: 0.083,
248
267
  },
249
268
  "minimax/hailuo-02": {
250
269
  durations: [6], // NOTE: 10 for only 720p
@@ -264,7 +283,7 @@ export const provider2MovieAgent = {
264
283
  start_image: "image",
265
284
  last_image: "last_frame_image",
266
285
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "sound_effect_switch" },
267
- price_per_sec: 0.12,
286
+ price_per_sec: 0.06,
268
287
  },
269
288
  "wan-video/wan-2.2-i2v-fast": {
270
289
  // No duration input: length is num_frames (default 81) / frames_per_second (default 16) ≈ 5s.
@@ -279,26 +298,26 @@ export const provider2MovieAgent = {
279
298
  durations: [5],
280
299
  start_image: undefined,
281
300
  audio: { mode: AUDIO_MODE_NEVER },
282
- price_per_sec: 0.012,
301
+ price_per_sec: 0.01,
283
302
  },
284
303
  "xai/grok-imagine-video": {
285
304
  durations: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
286
305
  start_image: "image",
287
306
  audio: { mode: AUDIO_MODE_NEVER },
288
- price_per_sec: 0.08,
307
+ price_per_sec: 0.05,
289
308
  },
290
309
  "xai/grok-imagine-r2v": {
291
310
  durations: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
292
311
  start_image: undefined,
293
312
  reference_images_param: "reference_images",
294
313
  audio: { mode: AUDIO_MODE_NEVER },
295
- price_per_sec: 0.08,
314
+ price_per_sec: 0.05,
296
315
  },
297
316
  "runwayml/gen-4.5": {
298
317
  durations: [5, 10],
299
318
  start_image: "image",
300
319
  audio: { mode: AUDIO_MODE_NEVER },
301
- price_per_sec: 0.25,
320
+ price_per_sec: 0.12,
302
321
  },
303
322
  "kwaivgi/kling-v3-omni-video": {
304
323
  durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
@@ -306,7 +325,7 @@ export const provider2MovieAgent = {
306
325
  last_image: "end_image",
307
326
  reference_images_param: "reference_images",
308
327
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
309
- price_per_sec: 0.28, // 'pro' (1080p, default); 'standard' $0.168, '4k' $0.42
328
+ price_per_sec: 0.224,
310
329
  },
311
330
  "kwaivgi/kling-v3-video": {
312
331
  durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
@@ -314,7 +333,7 @@ export const provider2MovieAgent = {
314
333
  last_image: "end_image",
315
334
  reference_images_param: "reference_images",
316
335
  audio: { mode: AUDIO_MODE_OPTIONAL, param: "generate_audio" },
317
- price_per_sec: 0.3,
336
+ price_per_sec: 0.224,
318
337
  },
319
338
  // TODO: price_per_sec for the models below is a coarse approximation.
320
339
  // Actual Replicate pricing varies by resolution / duration / quality and
@@ -324,27 +343,27 @@ export const provider2MovieAgent = {
324
343
  durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
325
344
  start_image: "image",
326
345
  audio: { mode: AUDIO_MODE_NEVER },
327
- price_per_sec: 0.05,
346
+ price_per_sec: 0.14,
328
347
  },
329
348
  "minimax/hailuo-2.3": {
330
349
  durations: [6, 10],
331
350
  start_image: "first_frame_image",
332
351
  audio: { mode: AUDIO_MODE_NEVER },
333
- price_per_sec: 0.1,
352
+ price_per_sec: 0.047,
334
353
  },
335
354
  "minimax/hailuo-2.3-fast": {
336
355
  durations: [6, 10],
337
356
  start_image: "first_frame_image",
338
357
  start_image_required: true,
339
358
  audio: { mode: AUDIO_MODE_NEVER },
340
- price_per_sec: 0.06,
359
+ price_per_sec: 0.032,
341
360
  },
342
361
  "pixverse/pixverse-v5": {
343
362
  durations: [5, 8],
344
363
  start_image: "image",
345
364
  last_image: "last_frame_image",
346
365
  audio: { mode: AUDIO_MODE_NEVER },
347
- price_per_sec: 0.12,
366
+ price_per_sec: 0.06,
348
367
  },
349
368
  "prunaai/p-video": {
350
369
  durations: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
@@ -364,8 +383,9 @@ export const provider2MovieAgent = {
364
383
  },
365
384
  google: {
366
385
  agentName: "movieGenAIAgent",
367
- defaultModel: "veo-2.0-generate-001",
368
- models: ["veo-2.0-generate-001", "veo-3.0-generate-001", "veo-3.1-generate-preview", "veo-3.1-lite-generate-preview"],
386
+ // veo-2.0 and veo-3.0 were shut down on 2026-06-30; the Gemini API now lists only the Veo 3.1 previews.
387
+ defaultModel: "veo-3.1-generate-preview",
388
+ models: ["veo-3.1-generate-preview", "veo-3.1-lite-generate-preview", "veo-3.1-generate-001"],
369
389
  keyName: "GEMINI_API_KEY",
370
390
  modelParams: {
371
391
  "veo-3.1-lite-generate-preview": {
@@ -376,7 +396,8 @@ export const provider2MovieAgent = {
376
396
  supportsPersonGeneration: false,
377
397
  audio: { mode: AUDIO_MODE_ALWAYS },
378
398
  },
379
- "veo-3.1-generate-preview": {
399
+ // Vertex AI only: the GA name of Veo 3.1 there, where the preview IDs are retired.
400
+ "veo-3.1-generate-001": {
380
401
  durations: [4, 6, 8],
381
402
  supportsDuration: true,
382
403
  supportsLastFrame: true,
@@ -384,22 +405,14 @@ export const provider2MovieAgent = {
384
405
  supportsPersonGeneration: false,
385
406
  audio: { mode: AUDIO_MODE_ALWAYS },
386
407
  },
387
- "veo-3.0-generate-001": {
388
- durations: [8],
389
- supportsDuration: false, // Veo 3.0 always generates 8s
390
- supportsLastFrame: false,
391
- supportsReferenceImages: false,
408
+ "veo-3.1-generate-preview": {
409
+ durations: [4, 6, 8],
410
+ supportsDuration: true,
411
+ supportsLastFrame: true,
412
+ supportsReferenceImages: true,
392
413
  supportsPersonGeneration: false,
393
414
  audio: { mode: AUDIO_MODE_ALWAYS },
394
415
  },
395
- "veo-2.0-generate-001": {
396
- durations: [5, 6, 8],
397
- supportsDuration: true,
398
- supportsLastFrame: false, // Vertex AI only
399
- supportsReferenceImages: false,
400
- supportsPersonGeneration: true,
401
- audio: { mode: AUDIO_MODE_NEVER },
402
- },
403
416
  },
404
417
  },
405
418
  mock: {
@@ -471,12 +484,16 @@ export const provider2LipSyncAgent = {
471
484
  export const provider2LLMAgent = {
472
485
  openai: {
473
486
  agentName: "openAIAgent",
474
- defaultModel: "gpt-5",
487
+ // gpt-5 (gpt-5-2025-08-07) shuts down on 2026-12-11; OpenAI names gpt-5.6-sol as its successor.
488
+ defaultModel: "gpt-5.6-sol",
475
489
  keyName: "OPENAI_API_KEY",
476
490
  baseURLKeyName: "OPENAI_BASE_URL",
477
491
  apiVersionKeyName: "OPENAI_API_VERSION",
478
492
  max_tokens: 8192,
479
493
  models: [
494
+ "gpt-5.6-sol",
495
+ "gpt-5.6-terra",
496
+ "gpt-5.6-luna",
480
497
  "gpt-5",
481
498
  "gpt-5-nano",
482
499
  "gpt-5-mini",
@@ -494,26 +511,31 @@ export const provider2LLMAgent = {
494
511
  },
495
512
  anthropic: {
496
513
  agentName: "anthropicAgent",
497
- defaultModel: "claude-sonnet-4-5-20250929",
514
+ // claude-sonnet-4-5 retires on 2026-11-30. Its named successor, claude-sonnet-5-5 (like claude-opus-4-8),
515
+ // rejects the temperature @graphai/anthropic_agent always sends, so the newest model that answers is used.
516
+ defaultModel: "claude-sonnet-4-6",
498
517
  max_tokens: 8192,
499
- models: ["claude-opus-4-1-20250805", "claude-opus-4-20250514", "claude-sonnet-4-20250514", "claude-sonnet-4-5-20250929", "claude-haiku-4-5-20251001"],
518
+ models: ["claude-sonnet-4-6", "claude-sonnet-4-5-20250929", "claude-haiku-4-5-20251001"],
500
519
  keyName: "ANTHROPIC_API_KEY",
501
520
  apiKeyNameOverride: "ANTHROPIC_API_TOKEN",
502
521
  // GraphAI is currently using ANTHROPIC_API_KEY, but the official name is ANTHROPIC_API_TOKEN.
503
522
  },
504
523
  gemini: {
505
524
  agentName: "geminiAgent",
506
- defaultModel: "gemini-2.5-flash",
525
+ // Google limits the 2.5 models to users who have used them before and recommends 3.8 Flash.
526
+ defaultModel: "gemini-3.8-flash",
507
527
  max_tokens: 8192,
508
- models: ["gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"],
528
+ models: ["gemini-3.8-flash", "gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"],
509
529
  keyName: "GEMINI_API_KEY",
510
530
  },
511
531
  groq: {
512
532
  agentName: "groqAgent",
513
- defaultModel: "llama-3.1-8b-instant",
533
+ // The Llama and DeepSeek models were shut down; Groq names openai/gpt-oss-20b as the 8B model's successor.
534
+ // https://console.groq.com/docs/deprecations
535
+ defaultModel: "openai/gpt-oss-20b",
514
536
  keyName: "GROQ_API_KEY",
515
537
  max_tokens: 4096,
516
- models: ["llama-3.1-8b-instant", "llama-3.3-70b-versatile", "deepseek-r1-distill-llama-70b", "openai/gpt-oss-120b", "openai/gpt-oss-20b"],
538
+ models: ["openai/gpt-oss-20b", "openai/gpt-oss-120b"],
517
539
  },
518
540
  mock: {
519
541
  agentName: "mediaMockAgent",
@@ -562,6 +584,14 @@ export const getModelAudio = (provider, model) => {
562
584
  const modelParams = provider2MovieAgent[provider]?.modelParams;
563
585
  return modelParams?.[model]?.audio;
564
586
  };
587
+ const GOOGLE_VERTEX_DEFAULT_MOVIE_MODEL = "veo-3.1-generate-001";
588
+ // The default model is shared by the Gemini API and Vertex AI, but Vertex retired the Veo 3.1 preview IDs.
589
+ export const defaultMovieModel = (provider, movieParams) => provider === "google" && movieParams?.vertexai_project ? GOOGLE_VERTEX_DEFAULT_MOVIE_MODEL : provider2MovieAgent[provider].defaultModel;
590
+ // Veo 3.1 (preview) builds clips longer than one segment by extending an 8-second first segment 8 seconds at a time.
591
+ export const VEO_EXTENSION_MODEL = "veo-3.1-generate-preview";
592
+ export const VEO_SEGMENT_SEC = 8;
593
+ export const veoExtensionCount = (requestedSec) => Math.ceil((requestedSec - VEO_SEGMENT_SEC) / VEO_SEGMENT_SEC);
594
+ export const veoExtendedSeconds = (requestedSec) => VEO_SEGMENT_SEC * (1 + veoExtensionCount(requestedSec));
565
595
  export const getModelDuration = (provider, model, movieDuration) => {
566
596
  const modelParams = provider2MovieAgent[provider]?.modelParams;
567
597
  const { durations } = modelParams[model];
@@ -582,7 +612,8 @@ export const gptImageOutputTokens = {
582
612
  // Spot-checked against replicate.com model pages on this date (seedance-1-lite, kling-v2.1, omni-human).
583
613
  // Some replicate prices vary by resolution/variant; price_per_sec holds the rate for the typical configuration
584
614
  // (e.g. seedance-1-lite at 720p, kling-v2.1 standard).
585
- const REPLICATE_PRICING_AS_OF = "2026-07-03";
615
+ // Optional-audio models are priced without audio, since mulmocast generates them silent by default.
616
+ const REPLICATE_PRICING_AS_OF = "2026-10-07";
586
617
  const replicateMoviePricing = Object.fromEntries(Object.entries(provider2MovieAgent.replicate.modelParams).map(([model, params]) => [
587
618
  model,
588
619
  { unit: "seconds", perSecUSD: params.price_per_sec, asOf: REPLICATE_PRICING_AS_OF },
@@ -601,22 +632,35 @@ export const modelPricing = {
601
632
  "gpt-image-1.5": { unit: "tokens", inputPerMTokensUSD: 5, outputPerMTokensUSD: 32, asOf: "2026-07-03" },
602
633
  "gpt-image-2": { unit: "tokens", inputPerMTokensUSD: 5, outputPerMTokensUSD: 30, asOf: "2026-07-03" },
603
634
  "gpt-image-1-mini": { unit: "tokens", inputPerMTokensUSD: 2, outputPerMTokensUSD: 8, asOf: "2026-07-03" },
635
+ // https://developers.openai.com/api/docs/pricing (text input $5/1M, image output $30/1M; image input $8/1M is not modeled)
636
+ "gpt-image-2.5-sunburst": { unit: "tokens", inputPerMTokensUSD: 5, outputPerMTokensUSD: 30, asOf: "2026-10-07" },
637
+ "gpt-image-2.5-flare": { unit: "tokens", inputPerMTokensUSD: 5, outputPerMTokensUSD: 30, asOf: "2026-10-07" },
604
638
  // https://developers.openai.com/api/docs/models/gpt-5 etc.
605
639
  "gpt-5": { unit: "tokens", inputPerMTokensUSD: 1.25, outputPerMTokensUSD: 10, asOf: "2026-07-03" },
606
640
  "gpt-5-mini": { unit: "tokens", inputPerMTokensUSD: 0.25, outputPerMTokensUSD: 2, asOf: "2026-07-03" },
641
+ // https://developers.openai.com/api/docs/models/gpt-5.6-sol (promotional price, "at least through November 21, 2026")
642
+ "gpt-5.6-sol": { unit: "tokens", inputPerMTokensUSD: 4, outputPerMTokensUSD: 20, asOf: "2026-10-07" },
643
+ "gpt-5.6-terra": { unit: "tokens", inputPerMTokensUSD: 2, outputPerMTokensUSD: 12, asOf: "2026-10-07" },
607
644
  "gpt-4o": { unit: "tokens", inputPerMTokensUSD: 2.5, outputPerMTokensUSD: 10, asOf: "2026-07-03" },
608
645
  },
609
646
  gemini: {
610
647
  // https://ai.google.dev/gemini-api/docs/pricing
611
648
  "gemini-2.5-flash-preview-tts": { unit: "tokens", inputPerMTokensUSD: 0.5, outputPerMTokensUSD: 10, asOf: "2026-07-03" },
612
649
  "gemini-2.5-pro-preview-tts": { unit: "tokens", inputPerMTokensUSD: 1, outputPerMTokensUSD: 20, asOf: "2026-07-03" },
650
+ // through 2026-12-31; $1 / $18 (flash) and $1 / $12 (lite) from 2027-01-01
651
+ "gemini-3.8-flash-tts": { unit: "tokens", inputPerMTokensUSD: 0.5, outputPerMTokensUSD: 9, asOf: "2026-10-07" },
652
+ "gemini-3.8-flash-lite-tts": { unit: "tokens", inputPerMTokensUSD: 0.5, outputPerMTokensUSD: 6, asOf: "2026-10-07" },
613
653
  },
614
654
  google: {
615
655
  // https://ai.google.dev/gemini-api/docs/pricing ($0.039/image ≒ 1290 output tokens at $30/1M)
616
656
  "gemini-2.5-flash-image": { unit: "images", inputPerMTokensUSD: 0.3, perImageUSD: 0.039, asOf: "2026-07-03" },
657
+ // $0.0336 per 1K-resolution image
658
+ "gemini-3.1-flash-lite-image": { unit: "images", inputPerMTokensUSD: 0.25, perImageUSD: 0.0336, asOf: "2026-10-07" },
617
659
  // Veo per second of generated video (720p). veo-2.0-generate-001 and veo-3.0-generate-001
618
660
  // were shut down on 2026-06-30, so they intentionally have no price.
619
661
  "veo-3.1-generate-preview": { unit: "seconds", perSecUSD: 0.4, asOf: "2026-07-03" },
662
+ // Vertex AI: https://cloud.google.com/vertex-ai/generative-ai/pricing (video + audio, 720p/1080p)
663
+ "veo-3.1-generate-001": { unit: "seconds", perSecUSD: 0.4, asOf: "2026-10-07" },
620
664
  "veo-3.1-lite-generate-preview": { unit: "seconds", perSecUSD: 0.05, asOf: "2026-07-03" },
621
665
  // Google Cloud Text-to-Speech per 1M characters, keyed by voice tier.
622
666
  // https://cloud.google.com/text-to-speech/pricing
@@ -629,15 +673,16 @@ export const modelPricing = {
629
673
  anthropic: {
630
674
  // https://platform.claude.com/docs/en/docs/about-claude/pricing
631
675
  "claude-sonnet-4-5-20250929": { unit: "tokens", inputPerMTokensUSD: 3, outputPerMTokensUSD: 15, asOf: "2026-07-03" },
676
+ "claude-sonnet-4-6": { unit: "tokens", inputPerMTokensUSD: 3, outputPerMTokensUSD: 15, asOf: "2026-10-07" },
632
677
  },
633
678
  elevenlabs: {
634
- // https://elevenlabs.io/pricing/api ($0.10 per 1k chars for multilingual/v3, $0.05 for flash/turbo)
635
- eleven_v3: { unit: "chars", perMCharsUSD: 100, asOf: "2026-07-03" },
636
- eleven_multilingual_v2: { unit: "chars", perMCharsUSD: 100, asOf: "2026-07-03" },
637
- eleven_turbo_v2_5: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
638
- eleven_turbo_v2: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
639
- eleven_flash_v2_5: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
640
- eleven_flash_v2: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
679
+ // https://elevenlabs.io/pricing/api ($0.08 per 1k chars for multilingual/v3, $0.04 for flash/turbo)
680
+ eleven_v3: { unit: "chars", perMCharsUSD: 80, asOf: "2026-10-07" },
681
+ eleven_multilingual_v2: { unit: "chars", perMCharsUSD: 80, asOf: "2026-10-07" },
682
+ eleven_turbo_v2_5: { unit: "chars", perMCharsUSD: 40, asOf: "2026-10-07" },
683
+ eleven_turbo_v2: { unit: "chars", perMCharsUSD: 40, asOf: "2026-10-07" },
684
+ eleven_flash_v2_5: { unit: "chars", perMCharsUSD: 40, asOf: "2026-10-07" },
685
+ eleven_flash_v2: { unit: "chars", perMCharsUSD: 40, asOf: "2026-10-07" },
641
686
  },
642
687
  replicate: {
643
688
  ...replicateMoviePricing,
@@ -4466,8 +4466,8 @@ export declare const mulmoOpenAIImageModelSchema: z.ZodObject<{
4466
4466
  [x: string]: string;
4467
4467
  }>>;
4468
4468
  quality: z.ZodOptional<z.ZodEnum<{
4469
- low: "low";
4470
4469
  high: "high";
4470
+ low: "low";
4471
4471
  auto: "auto";
4472
4472
  medium: "medium";
4473
4473
  }>>;
@@ -4,10 +4,11 @@
4
4
  // assume every asset is generated fresh.
5
5
  import { Tiktoken } from "js-tiktoken/lite";
6
6
  import o200k_base from "js-tiktoken/ranks/o200k_base";
7
- import { provider2TTSAgent, provider2MovieAgent, gptImageOutputTokens, getModelDuration, getModelPricing, defaultProviders, } from "../types/provider2agent.js";
7
+ import { provider2TTSAgent, provider2MovieAgent, gptImageOutputTokens, isGptImage25Model, getModelDuration, defaultMovieModel, veoExtendedSeconds, VEO_EXTENSION_MODEL, VEO_SEGMENT_SEC, getModelPricing, defaultProviders, } from "../types/provider2agent.js";
8
8
  import { text2SpeechProviderSchema, text2MovieProviderSchema } from "../types/schema.js";
9
9
  import { MulmoPresentationStyleMethods } from "../methods/mulmo_presentation_style.js";
10
10
  import { MulmoBeatMethods } from "../methods/mulmo_beat.js";
11
+ import { geminiTtsInputText } from "./gemini_tts.js";
11
12
  import { imagePrompt, htmlImageSystemPrompt, translateSystemPrompt, translatePrompts } from "./prompt.js";
12
13
  const MILLION = 1_000_000;
13
14
  const LATIN_CHARS_PER_TOKEN = 4;
@@ -92,15 +93,14 @@ const buildOpenAITts = ({ speaker, text, textIsFinal, beatIndex, lang }) => {
92
93
  return { process: "tts", beatIndex, lang, provider: "openai", model, inputChars: metric(text.length, textIsFinal), ...tokenMetrics };
93
94
  };
94
95
  const buildGeminiTts = ({ speaker, speechOptions, text, beatIndex, lang }) => {
95
- // The gemini TTS agent wraps the transcript in a "Director's Notes" prompt when an instruction is set.
96
- const prompt = speechOptions?.instruction ? `### DIRECTOR'S NOTES\n${speechOptions.instruction}\n\n#### TRANSCRIPT\n${text}` : text;
96
+ const model = speaker.model ?? provider2TTSAgent.gemini.defaultModel;
97
97
  return {
98
98
  process: "tts",
99
99
  beatIndex,
100
100
  lang,
101
101
  provider: "gemini",
102
- model: speaker.model ?? provider2TTSAgent.gemini.defaultModel,
103
- inputTokens: estimated(countHeuristicTokens(prompt)),
102
+ model,
103
+ inputTokens: estimated(countHeuristicTokens(geminiTtsInputText(model, text, speechOptions?.instruction))),
104
104
  outputTokens: estimated(estimateSpeechSec(text) * GEMINI_TTS_AUDIO_TOKENS_PER_SEC),
105
105
  };
106
106
  };
@@ -162,8 +162,8 @@ const gptImageSize = (canvasSize) => {
162
162
  const gptImageOutputMetric = (model, canvasSize, quality) => {
163
163
  const table = gptImageOutputTokens[gptImageSize(canvasSize)];
164
164
  const knownQuality = quality === "low" || quality === "medium" || quality === "high" ? quality : undefined;
165
- // Unspecified quality means API "auto"; assume "high" as the conservative upper bound.
166
- const tokens = table[knownQuality ?? "high"];
165
+ // Anything else renders at "high" (the upper bound), except an explicit "auto" on 2.5, which renders at "low".
166
+ const tokens = table[knownQuality ?? (quality === "auto" && isGptImage25Model(model) ? "low" : "high")];
167
167
  return metric(tokens, GPT_IMAGE_FIXED_TOKEN_MODELS.includes(model) && knownQuality !== undefined);
168
168
  };
169
169
  const buildImageRecord = ({ process, provider, model, prompt, canvasSize, quality, beatIndex, refKey }) => {
@@ -217,6 +217,8 @@ const beatDurationMetric = (beat) => {
217
217
  }
218
218
  return estimated(beat.text ? estimateSpeechSec(beat.text) : DEFAULT_MOVIE_DURATION_SEC);
219
219
  };
220
+ // The movie agent extends these clips past one segment instead of capping them, and Google bills every generated second.
221
+ const isVeoExtension = (provider, model, duration) => provider === "google" && model === VEO_EXTENSION_MODEL && duration.value > VEO_SEGMENT_SEC;
220
222
  const snapMovieDuration = (provider, model, duration) => {
221
223
  if (!isKeyOf(provider2MovieAgent[provider].modelParams, model)) {
222
224
  return duration;
@@ -230,9 +232,11 @@ const estimateMovieGeneration = (style, beat, beatIndex, refKey) => {
230
232
  if (!isKeyOf(provider2MovieAgent, provider) || provider === "mock") {
231
233
  return undefined;
232
234
  }
233
- const model = info.movieParams?.model ?? provider2MovieAgent[provider].defaultModel;
235
+ const model = info.movieParams?.model ?? defaultMovieModel(provider, info.movieParams);
234
236
  const duration = beat ? beatDurationMetric(beat) : estimated(DEFAULT_MOVIE_DURATION_SEC);
235
- const predictSec = snapMovieDuration(provider, model, duration);
237
+ const predictSec = isVeoExtension(provider, model, duration)
238
+ ? metric(veoExtendedSeconds(duration.value), duration.precision === "exact")
239
+ : snapMovieDuration(provider, model, duration);
236
240
  return attachCost({ process: refKey === undefined ? "movie" : "movieReference", beatIndex, refKey, provider, model, predictSec });
237
241
  };
238
242
  const estimateSoundEffect = (style, beat, beatIndex) => {
@@ -0,0 +1,45 @@
1
+ export declare const GEMINI_INTERACTIONS_TTS_MODELS: ReadonlySet<string>;
2
+ export declare const GEMINI_TTS_SAMPLE_RATE = 24000;
3
+ export declare const geminiDirectorsNotesPrompt: (text: string, instructions?: string) => string;
4
+ export declare const geminiTtsInputText: (model: string, text: string, instructions?: string) => string;
5
+ export declare const usesGeminiInteractionsTts: (model: string) => boolean;
6
+ export type GeminiInteractionsTtsInput = {
7
+ model: string;
8
+ text: string;
9
+ voice: string;
10
+ instructions?: string;
11
+ };
12
+ export declare const buildGeminiInteractionsTtsRequest: ({ model, text, voice, instructions }: GeminiInteractionsTtsInput) => {
13
+ model: string;
14
+ input: {
15
+ type: "user_input";
16
+ content: {
17
+ annotations?: {
18
+ type: "speech_metadata";
19
+ style: string;
20
+ }[] | undefined;
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ }[];
25
+ response_format: {
26
+ type: "audio";
27
+ mime_type: string;
28
+ sample_rate: number;
29
+ };
30
+ generation_config: {
31
+ speech_config: {
32
+ voice: string;
33
+ }[];
34
+ };
35
+ };
36
+ type InteractionAudio = {
37
+ data?: string;
38
+ mime_type?: string;
39
+ sample_rate?: number;
40
+ };
41
+ export declare const pcmFromInteractionAudio: (audio: InteractionAudio | undefined) => {
42
+ rawPcm: Buffer;
43
+ sampleRate: number;
44
+ };
45
+ export {};
@@ -0,0 +1,35 @@
1
+ // Gemini 3.8 TTS takes the Interactions API: the text is read verbatim, so speaker direction goes in a
2
+ // speech_metadata annotation instead of the prompt, and raw PCM must be asked for (the unary default is WAV).
3
+ export const GEMINI_INTERACTIONS_TTS_MODELS = new Set(["gemini-3.8-flash-tts", "gemini-3.8-flash-lite-tts"]);
4
+ export const GEMINI_TTS_SAMPLE_RATE = 24000;
5
+ // 2.5 TTS has no field for direction, so it goes in the prompt: https://ai.google.dev/gemini-api/docs/speech-generation#controllable
6
+ export const geminiDirectorsNotesPrompt = (text, instructions) => instructions ? ["### DIRECTOR'S NOTES", instructions, "", "#### TRANSCRIPT", text].join("\n") : text;
7
+ // The text the model is given for one line: 3.8 gets the transcript plus the style annotation, 2.5 the wrapped prompt.
8
+ export const geminiTtsInputText = (model, text, instructions) => usesGeminiInteractionsTts(model) ? [text, instructions].filter(Boolean).join("\n") : geminiDirectorsNotesPrompt(text, instructions);
9
+ export const usesGeminiInteractionsTts = (model) => GEMINI_INTERACTIONS_TTS_MODELS.has(model);
10
+ export const buildGeminiInteractionsTtsRequest = ({ model, text, voice, instructions }) => ({
11
+ model,
12
+ input: [
13
+ {
14
+ type: "user_input",
15
+ content: [{ type: "text", text, ...(instructions ? { annotations: [{ type: "speech_metadata", style: instructions }] } : {}) }],
16
+ },
17
+ ],
18
+ response_format: { type: "audio", mime_type: "audio/l16", sample_rate: GEMINI_TTS_SAMPLE_RATE },
19
+ generation_config: { speech_config: [{ voice }] },
20
+ });
21
+ const sampleRateOf = (audio) => {
22
+ if (typeof audio.sample_rate === "number" && audio.sample_rate > 0)
23
+ return audio.sample_rate;
24
+ const match = audio.mime_type?.match(/rate=(\d+)/);
25
+ return match ? Number(match[1]) : GEMINI_TTS_SAMPLE_RATE;
26
+ };
27
+ // Anything but headerless 16-bit PCM would be fed to the PCM encoder as noise, so it is an error, not a guess.
28
+ export const pcmFromInteractionAudio = (audio) => {
29
+ if (!audio?.data)
30
+ throw new Error("No audio data returned");
31
+ if (!audio.mime_type?.toLowerCase().startsWith("audio/l16")) {
32
+ throw new Error(`Gemini TTS returned ${audio.mime_type ?? "audio of an unknown type"}, expected audio/l16`);
33
+ }
34
+ return { rawPcm: Buffer.from(audio.data, "base64"), sampleRate: sampleRateOf(audio) };
35
+ };
@@ -0,0 +1,5 @@
1
+ import type { HeadlessBrowser } from "@remotion/renderer";
2
+ export type NetworkGuard = {
3
+ failures: string[];
4
+ };
5
+ export declare const guardSceneNetwork: (browser: HeadlessBrowser, serverPort: number, onBlocked: (url: string) => void) => Promise<NetworkGuard>;