mulmocast 2.8.1 → 2.9.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.
@@ -24,6 +24,9 @@ type ImagePreprocessAgentReturnValue = {
24
24
  };
25
25
  firstFrameImagePath?: string;
26
26
  lastFrameImagePath?: string;
27
+ firstFrameIsBeatImage?: boolean;
28
+ lastFrameIsBeatImage?: boolean;
29
+ frameFillColor?: string;
27
30
  movieReferenceImages?: {
28
31
  imagePath: string;
29
32
  referenceType: "ASSET" | "STYLE";
@@ -62,6 +65,23 @@ export declare const imagePreprocessAgent: (namedInputs: {
62
65
  index: number;
63
66
  imageRefs?: Record<string, string>;
64
67
  }) => Promise<ImagePreprocessAgentResponse>;
68
+ export type BeatFramePreprocessorInput = {
69
+ imagePath?: string;
70
+ lastFrameImagePath?: string;
71
+ firstFrameIsBeatImage?: boolean;
72
+ lastFrameIsBeatImage?: boolean;
73
+ frameFillColor?: string;
74
+ referenceImageForMovie?: string;
75
+ };
76
+ export declare const beatFrameResolverAgent: (namedInputs: {
77
+ context: MulmoStudioContext;
78
+ beat: MulmoBeat;
79
+ index: number;
80
+ preprocessor?: BeatFramePreprocessorInput;
81
+ }) => Promise<{
82
+ lastFrameImagePath: string | undefined;
83
+ referenceImageForMovie: string | undefined;
84
+ }>;
65
85
  export declare const imagePluginAgent: (namedInputs: {
66
86
  context: MulmoStudioContext;
67
87
  beat: MulmoBeat;
@@ -1,9 +1,11 @@
1
1
  import fs from "fs";
2
2
  import { createHash } from "crypto";
3
3
  import { GraphAILogger } from "graphai";
4
+ import { BEAT_IMAGE_SENTINEL, ImageMediaType, beatImageTypesWithoutOwnStill } from "../types/schema.js";
4
5
  import { MulmoPresentationStyleMethods, MulmoStudioContextMethods, MulmoBeatMethods, MulmoMediaSourceMethods } from "../methods/index.js";
5
6
  import { getBeatPngImagePath, getBeatMoviePaths, getBeatAnimatedVideoPath, getAudioFilePath, getGroupedAudioFilePath, getReferenceImagePath, } from "../utils/file.js";
6
7
  import { ffmpegGetImageDimensions, padImageToCanvas } from "../utils/ffmpeg_utils.js";
8
+ import { beatImageSentinelError, bothFramesTarget, missingBeatImageTarget, unsupportedImageTypeTarget, missingFirstFrameTarget } from "../utils/error_cause.js";
7
9
  import { imagePrompt, htmlImageSystemPrompt } from "../utils/prompt.js";
8
10
  import { renderHTMLToImage } from "../utils/html_render.js";
9
11
  import { beatId } from "../utils/utils.js";
@@ -71,6 +73,64 @@ export const conformFrameImageToCanvas = async (context, imageName, imagePath, f
71
73
  conformingInFlight.delete(destPath);
72
74
  }
73
75
  };
76
+ const validateBeatImageSentinel = (movieParams, beat, index) => {
77
+ if (movieParams.firstFrameImageName === BEAT_IMAGE_SENTINEL && movieParams.lastFrameImageName === BEAT_IMAGE_SENTINEL) {
78
+ throw new Error(`${BEAT_IMAGE_SENTINEL} cannot be used for both firstFrameImageName and lastFrameImageName. beat: ${index}`, {
79
+ cause: beatImageSentinelError(index, bothFramesTarget),
80
+ });
81
+ }
82
+ if (!beat.imagePrompt && !beat.image) {
83
+ throw new Error(`${BEAT_IMAGE_SENTINEL} requires the beat to have an imagePrompt or image. beat: ${index}`, {
84
+ cause: beatImageSentinelError(index, missingBeatImageTarget),
85
+ });
86
+ }
87
+ if (beat.image && beatImageTypesWithoutOwnStill.includes(beat.image.type)) {
88
+ throw new Error(`${BEAT_IMAGE_SENTINEL} cannot be used with image type "${beat.image.type}" (the beat image is not its own still). beat: ${index}`, {
89
+ cause: beatImageSentinelError(index, unsupportedImageTypeTarget),
90
+ });
91
+ }
92
+ // The implicit first frame (the raw beat image) is not conformed to the canvas, so pairing
93
+ // it with a conformed $beatImage last frame would feed mismatched frame sizes to strict
94
+ // image-to-video models (e.g. wan-2.2-i2v-fast). Require an explicit, conformed first frame.
95
+ if (movieParams.lastFrameImageName === BEAT_IMAGE_SENTINEL && !movieParams.firstFrameImageName) {
96
+ throw new Error(`${BEAT_IMAGE_SENTINEL} as lastFrameImageName requires firstFrameImageName. beat: ${index}`, {
97
+ cause: beatImageSentinelError(index, missingFirstFrameTarget),
98
+ });
99
+ }
100
+ };
101
+ // Resolve firstFrameImageName/lastFrameImageName. Named refs conform to the canvas here;
102
+ // the $beatImage sentinel only sets flags (the beat image does not exist yet at preprocess
103
+ // time) and is resolved by beatFrameResolverAgent after image generation.
104
+ const applyFrameImageParams = async (returnValue, context, beat, index, movieParams, imageRefs) => {
105
+ const frameFillColor = movieParams.frameFillColor ?? "black";
106
+ // A style-level $beatImage merges into every beat; beats that generate no movie
107
+ // (no moviePrompt) ignore the sentinel instead of failing validation.
108
+ const sentinelActive = Boolean(beat.moviePrompt);
109
+ const firstFrameImageName = !sentinelActive && movieParams.firstFrameImageName === BEAT_IMAGE_SENTINEL ? undefined : movieParams.firstFrameImageName;
110
+ const lastFrameImageName = !sentinelActive && movieParams.lastFrameImageName === BEAT_IMAGE_SENTINEL ? undefined : movieParams.lastFrameImageName;
111
+ if (firstFrameImageName === BEAT_IMAGE_SENTINEL || lastFrameImageName === BEAT_IMAGE_SENTINEL) {
112
+ validateBeatImageSentinel(movieParams, beat, index);
113
+ returnValue.frameFillColor = frameFillColor;
114
+ }
115
+ if (firstFrameImageName === BEAT_IMAGE_SENTINEL) {
116
+ returnValue.firstFrameIsBeatImage = true;
117
+ }
118
+ else if (firstFrameImageName && imageRefs) {
119
+ const firstFramePath = imageRefs[firstFrameImageName];
120
+ if (firstFramePath) {
121
+ returnValue.firstFrameImagePath = await conformFrameImageToCanvas(context, firstFrameImageName, firstFramePath, frameFillColor);
122
+ }
123
+ }
124
+ if (lastFrameImageName === BEAT_IMAGE_SENTINEL) {
125
+ returnValue.lastFrameIsBeatImage = true;
126
+ }
127
+ else if (lastFrameImageName && imageRefs) {
128
+ const lastFramePath = imageRefs[lastFrameImageName];
129
+ if (lastFramePath) {
130
+ returnValue.lastFrameImagePath = await conformFrameImageToCanvas(context, lastFrameImageName, lastFramePath, frameFillColor);
131
+ }
132
+ }
133
+ };
74
134
  export const imagePreprocessAgent = async (namedInputs) => {
75
135
  const { context, beat, index, imageRefs } = namedInputs;
76
136
  const studioBeat = context.studio.beats[index];
@@ -88,7 +148,7 @@ export const imagePreprocessAgent = async (namedInputs) => {
88
148
  movieFile: beat.moviePrompt ? moviePaths.movieFile : undefined,
89
149
  beatDuration: beat.duration ?? studioBeat?.duration,
90
150
  };
91
- const isMovie = Boolean(beat.moviePrompt || beat?.image?.type === "movie");
151
+ const isMovie = Boolean(beat.moviePrompt || beat?.image?.type === ImageMediaType.Movie);
92
152
  if (beat.soundEffectPrompt) {
93
153
  if (isMovie) {
94
154
  returnValue.soundEffectAgentInfo = MulmoPresentationStyleMethods.getSoundEffectAgentInfo(context.presentationStyle, beat);
@@ -130,19 +190,7 @@ export const imagePreprocessAgent = async (namedInputs) => {
130
190
  // Shallow-merge like getMovieAgentInfo: beat-level fields override style-level fields
131
191
  // individually, so a beat setting only lastFrameImageName keeps the global frameFillColor.
132
192
  const movieParams = { ...context.presentationStyle.movieParams, ...beat.movieParams };
133
- const frameFillColor = movieParams.frameFillColor ?? "black";
134
- if (movieParams.firstFrameImageName && imageRefs) {
135
- const firstFramePath = imageRefs[movieParams.firstFrameImageName];
136
- if (firstFramePath) {
137
- returnValue.firstFrameImagePath = await conformFrameImageToCanvas(context, movieParams.firstFrameImageName, firstFramePath, frameFillColor);
138
- }
139
- }
140
- if (movieParams.lastFrameImageName && imageRefs) {
141
- const lastFramePath = imageRefs[movieParams.lastFrameImageName];
142
- if (lastFramePath) {
143
- returnValue.lastFrameImagePath = await conformFrameImageToCanvas(context, movieParams.lastFrameImageName, lastFramePath, frameFillColor);
144
- }
145
- }
193
+ await applyFrameImageParams(returnValue, context, beat, index, movieParams, imageRefs);
146
194
  if (movieParams.referenceImages && imageRefs) {
147
195
  returnValue.movieReferenceImages = movieParams.referenceImages
148
196
  .map((ref) => {
@@ -156,7 +204,7 @@ export const imagePreprocessAgent = async (namedInputs) => {
156
204
  const pluginPath = plugin.path({ beat, context, imagePath, ...htmlStyle(context, beat) });
157
205
  const markdown = plugin.markdown ? plugin.markdown({ beat, context, imagePath, ...htmlStyle(context, beat) }) : undefined;
158
206
  const html = plugin.html ? await plugin.html({ beat, context, imagePath, ...htmlStyle(context, beat) }) : undefined;
159
- const isTypeMovie = beat.image.type === "movie";
207
+ const isTypeMovie = beat.image.type === ImageMediaType.Movie;
160
208
  const isAnimatedHtml = MulmoBeatMethods.isAnimatedHtmlTailwind(beat);
161
209
  // animation and moviePrompt cannot be used together
162
210
  if (isAnimatedHtml && beat.moviePrompt) {
@@ -203,6 +251,26 @@ export const imagePreprocessAgent = async (namedInputs) => {
203
251
  const movieFirstFramePath = returnValue.firstFrameImagePath ?? imagePath;
204
252
  return { ...returnValue, imagePath, referenceImageForMovie: movieFirstFramePath, imageAgentInfo, prompt, referenceImages };
205
253
  };
254
+ // Resolves $beatImage frame references after the beat's image exists. imagePreprocessAgent runs
255
+ // before image generation, so it can only flag the sentinel; this agent runs after
256
+ // imageGenerator/imagePlugin and conforms the now-existing beat image to the canvas.
257
+ // movieGenerator consumes only referenceImageForMovie (the movie's first frame / i2v input)
258
+ // and lastFrameImagePath.
259
+ export const beatFrameResolverAgent = async (namedInputs) => {
260
+ const { context, beat, index, preprocessor } = namedInputs;
261
+ if (preprocessor?.imagePath && (preprocessor.firstFrameIsBeatImage || preprocessor.lastFrameIsBeatImage)) {
262
+ const name = `${beatId(beat.id, index)}_beatImage`;
263
+ const conformed = await conformFrameImageToCanvas(context, name, preprocessor.imagePath, preprocessor.frameFillColor ?? "black");
264
+ return {
265
+ lastFrameImagePath: preprocessor.lastFrameIsBeatImage ? conformed : preprocessor.lastFrameImagePath,
266
+ referenceImageForMovie: preprocessor.firstFrameIsBeatImage ? conformed : preprocessor.referenceImageForMovie,
267
+ };
268
+ }
269
+ return {
270
+ lastFrameImagePath: preprocessor?.lastFrameImagePath,
271
+ referenceImageForMovie: preprocessor?.referenceImageForMovie,
272
+ };
273
+ };
206
274
  export const imagePluginAgent = async (namedInputs) => {
207
275
  const { context, beat, index, imageRefs, movieRefs } = namedInputs;
208
276
  const { imagePath } = getBeatPngImagePath(context, index);
@@ -1,10 +1,13 @@
1
1
  import { MulmoStudioContext, MulmoBeat, MulmoImagePromptMedia } from "../types/index.js";
2
+ import type { ResolvedImageReference } from "../methods/mulmo_image_prompt_media.js";
3
+ export type { ResolvedImageReference } from "../methods/mulmo_image_prompt_media.js";
2
4
  export declare const generateReferenceImage: (inputs: {
3
5
  context: MulmoStudioContext;
4
6
  key: string;
5
7
  index: number;
6
8
  image: MulmoImagePromptMedia;
7
9
  referenceImagePath?: string;
10
+ references?: ResolvedImageReference[];
8
11
  force?: boolean;
9
12
  }) => Promise<string>;
10
13
  export type MediaRefs = {
@@ -1,10 +1,36 @@
1
- import { GraphAI, GraphAILogger } from "graphai";
1
+ import { GraphAI, GraphAILogger, graphDataLatestVersion } from "graphai";
2
2
  import { getReferenceImagePath } from "../utils/file.js";
3
3
  import { imageGraphOption } from "./graph_option.js";
4
4
  import { createUsageCallback } from "../utils/usage_callback.js";
5
- import { MulmoPresentationStyleMethods, MulmoMediaSourceMethods } from "../methods/index.js";
5
+ import { MulmoPresentationStyleMethods, MulmoMediaSourceMethods, MulmoImagePromptMediaMethods } from "../methods/index.js";
6
+ import { ImageMediaType, } from "../types/index.js";
6
7
  import { imageOpenaiAgent, mediaMockAgent, imageGenAIAgent, imageReplicateAgent, movieGenAIAgent, movieReplicateAgent } from "../agents/index.js";
7
8
  import { agentGenerationError, imageReferenceAction, imageFileTarget, movieFileTarget } from "../utils/error_cause.js";
9
+ // Resolve the normalized reference list to local file paths (with labels), in reference order.
10
+ // imageKey is the imageRefs key of the image being generated (used for warnings and url
11
+ // download file naming). A named reference that is missing from imageRefs warns and is
12
+ // skipped (existing behavior).
13
+ const resolveImageReferences = async (context, imageKey, image, imageRefs) => {
14
+ const imageReferences = MulmoImagePromptMediaMethods.getReferences(image);
15
+ const resolved = await Promise.all(imageReferences.map(async (ref, i) => {
16
+ if (ref.name !== undefined) {
17
+ const path = imageRefs?.[ref.name];
18
+ if (!path) {
19
+ GraphAILogger.warn(`imagePrompt "${imageKey}": reference "${ref.name}" not found in imageRefs — generating without this reference`);
20
+ return undefined;
21
+ }
22
+ return { path, label: ref.label };
23
+ }
24
+ if (ref.source !== undefined) {
25
+ // The legacy referenceImage keeps the bare imageKey so url downloads reuse their historical cache file name.
26
+ const sourceKey = ref.legacy ? imageKey : `${imageKey}_ref${i}`;
27
+ return { path: await MulmoMediaSourceMethods.imageReference(ref.source, context, sourceKey), label: ref.label };
28
+ }
29
+ return undefined;
30
+ }));
31
+ const references = resolved.filter((ref) => ref !== undefined);
32
+ return references.length > 0 ? references : undefined;
33
+ };
8
34
  // public api
9
35
  // Application may call this function directly to generate reference image.
10
36
  export const generateReferenceImage = async (inputs) => {
@@ -12,11 +38,13 @@ export const generateReferenceImage = async (inputs) => {
12
38
  const imagePath = getReferenceImagePath(context, key, "png");
13
39
  // generate image
14
40
  const imageAgentInfo = MulmoPresentationStyleMethods.getImageAgentInfo(context.presentationStyle);
15
- const prompt = `${image.prompt}\n${imageAgentInfo.imageParams.style || ""}`;
41
+ const references = MulmoImagePromptMediaMethods.limitReferences(inputs.references ?? (referenceImagePath ? [{ path: referenceImagePath }] : undefined), imageAgentInfo, key);
42
+ const preamble = MulmoImagePromptMediaMethods.buildReferencePreamble((references ?? []).map((ref) => ref.label));
43
+ const prompt = `${preamble}${image.prompt}\n${imageAgentInfo.imageParams.style || ""}`;
16
44
  GraphAILogger.info(`Generating reference image for ${key}: ${prompt}`);
17
- const referenceImages = referenceImagePath ? [referenceImagePath] : undefined;
45
+ const referenceImages = references ? references.map((ref) => ref.path) : undefined;
18
46
  const image_graph_data = {
19
- version: 0.5,
47
+ version: graphDataLatestVersion,
20
48
  nodes: {
21
49
  imageGenerator: {
22
50
  agent: imageAgentInfo.agent,
@@ -62,33 +90,30 @@ export const getMediaRefs = async (context) => {
62
90
  }
63
91
  const imageRefs = {};
64
92
  const movieRefs = {};
65
- // Stage 1: resolve non-referencing entries (image, imagePrompt without referenceImageName, movie)
93
+ // Stage 1: resolve non-referencing entries (image, imagePrompt without named references, movie)
66
94
  await Promise.all(Object.keys(images)
67
95
  .sort()
68
96
  .map(async (key, index) => {
69
97
  const image = images[key];
70
- if (image.type === "imagePrompt" && !image.referenceImageName) {
71
- const refPath = image.referenceImage ? await MulmoMediaSourceMethods.imageReference(image.referenceImage, context, key) : undefined;
72
- imageRefs[key] = await generateReferenceImage({ context, key, index, image, referenceImagePath: refPath, force: false });
98
+ if (image.type === ImageMediaType.ImagePrompt && !MulmoImagePromptMediaMethods.hasNamedReference(image)) {
99
+ const references = await resolveImageReferences(context, key, image);
100
+ imageRefs[key] = await generateReferenceImage({ context, key, index, image, references, force: false });
73
101
  }
74
- else if (image.type === "image") {
102
+ else if (image.type === ImageMediaType.Image) {
75
103
  imageRefs[key] = await MulmoMediaSourceMethods.imageReference(image.source, context, key);
76
104
  }
77
- else if (image.type === "movie") {
105
+ else if (image.type === ImageMediaType.Movie) {
78
106
  movieRefs[key] = await resolveMovieReference(image, context, key);
79
107
  }
80
108
  }));
81
- // Stage 2: resolve imagePrompt with referenceImageName (depends on Stage 1 results)
109
+ // Stage 2: resolve imagePrompt with named references (depends on Stage 1 results)
82
110
  await Promise.all(Object.keys(images)
83
111
  .sort()
84
112
  .map(async (key, index) => {
85
113
  const image = images[key];
86
- if (image.type === "imagePrompt" && image.referenceImageName) {
87
- const refPath = imageRefs[image.referenceImageName];
88
- if (!refPath) {
89
- GraphAILogger.warn(`imagePrompt "${key}": referenceImageName "${image.referenceImageName}" not found in imageRefs — generating without reference`);
90
- }
91
- imageRefs[key] = await generateReferenceImage({ context, key, index, image, referenceImagePath: refPath, force: false });
114
+ if (image.type === ImageMediaType.ImagePrompt && MulmoImagePromptMediaMethods.hasNamedReference(image)) {
115
+ const references = await resolveImageReferences(context, key, image, imageRefs);
116
+ imageRefs[key] = await generateReferenceImage({ context, key, index, image, references, force: false });
92
117
  }
93
118
  }));
94
119
  return { imageRefs, movieRefs };
@@ -102,7 +127,7 @@ const generateReferenceMovie = async (inputs) => {
102
127
  const movieAgentInfo = MulmoPresentationStyleMethods.getMovieAgentInfo(context.presentationStyle);
103
128
  GraphAILogger.info(`Generating reference movie for ${key}: ${moviePrompt.prompt}`);
104
129
  const movie_graph_data = {
105
- version: 0.5,
130
+ version: graphDataLatestVersion,
106
131
  nodes: {
107
132
  movieGenerator: {
108
133
  agent: movieAgentInfo.agent,
@@ -145,45 +170,42 @@ const generateReferenceMovie = async (inputs) => {
145
170
  const resolveLocalRefs = async (context, images, beatIndex, globalImageRefs) => {
146
171
  const localImageRefs = {};
147
172
  const localMovieRefs = {};
148
- // Stage 1: image, imagePrompt (without referenceImageName), movie (parallel)
173
+ // Stage 1: image, imagePrompt (without named references), movie (parallel)
149
174
  await Promise.all(Object.keys(images)
150
175
  .sort()
151
176
  .map(async (key, i) => {
152
177
  const entry = images[key];
153
- if (entry.type === "imagePrompt" && !entry.referenceImageName) {
154
- const refPath = entry.referenceImage ? await MulmoMediaSourceMethods.imageReference(entry.referenceImage, context, key) : undefined;
178
+ if (entry.type === ImageMediaType.ImagePrompt && !MulmoImagePromptMediaMethods.hasNamedReference(entry)) {
179
+ const references = await resolveImageReferences(context, key, entry);
155
180
  localImageRefs[key] = await generateReferenceImage({
156
181
  context,
157
182
  key,
158
183
  index: beatIndex * 100 + i,
159
184
  image: entry,
160
- referenceImagePath: refPath,
185
+ references,
161
186
  });
162
187
  }
163
- else if (entry.type === "image") {
188
+ else if (entry.type === ImageMediaType.Image) {
164
189
  localImageRefs[key] = await MulmoMediaSourceMethods.imageReference(entry.source, context, key);
165
190
  }
166
- else if (entry.type === "movie") {
191
+ else if (entry.type === ImageMediaType.Movie) {
167
192
  localMovieRefs[key] = await resolveMovieReference(entry, context, key);
168
193
  }
169
194
  }));
170
- // Stage 2: imagePrompt with referenceImageName (depends on Stage 1)
195
+ // Stage 2: imagePrompt with named references (depends on Stage 1)
171
196
  const combinedImageRefsForImagePrompt = { ...globalImageRefs, ...localImageRefs };
172
197
  await Promise.all(Object.keys(images)
173
198
  .sort()
174
199
  .map(async (key, i) => {
175
200
  const entry = images[key];
176
- if (entry.type === "imagePrompt" && entry.referenceImageName) {
177
- const refPath = combinedImageRefsForImagePrompt[entry.referenceImageName];
178
- if (!refPath) {
179
- GraphAILogger.warn(`imagePrompt "${key}": referenceImageName "${entry.referenceImageName}" not found — generating without reference`);
180
- }
201
+ if (entry.type === ImageMediaType.ImagePrompt && MulmoImagePromptMediaMethods.hasNamedReference(entry)) {
202
+ const references = await resolveImageReferences(context, key, entry, combinedImageRefsForImagePrompt);
181
203
  localImageRefs[key] = await generateReferenceImage({
182
204
  context,
183
205
  key,
184
206
  index: beatIndex * 100 + i,
185
207
  image: entry,
186
- referenceImagePath: refPath,
208
+ references,
187
209
  });
188
210
  }
189
211
  }));
@@ -193,7 +215,7 @@ const resolveLocalRefs = async (context, images, beatIndex, globalImageRefs) =>
193
215
  .sort()
194
216
  .map(async (key, i) => {
195
217
  const entry = images[key];
196
- if (entry.type === "moviePrompt") {
218
+ if (entry.type === ImageMediaType.MoviePrompt) {
197
219
  const refImagePath = entry.imageName ? combinedImageRefs[entry.imageName] : undefined;
198
220
  localMovieRefs[key] = await generateReferenceMovie({
199
221
  context,
@@ -72,6 +72,9 @@ export declare const beat_graph_data: {
72
72
  };
73
73
  firstFrameImagePath?: string;
74
74
  lastFrameImagePath?: string;
75
+ firstFrameIsBeatImage?: boolean;
76
+ lastFrameIsBeatImage?: boolean;
77
+ frameFillColor?: string;
75
78
  movieReferenceImages?: {
76
79
  imagePath: string;
77
80
  referenceType: "ASSET" | "STYLE";
@@ -103,6 +106,9 @@ export declare const beat_graph_data: {
103
106
  };
104
107
  firstFrameImagePath?: string;
105
108
  lastFrameImagePath?: string;
109
+ firstFrameIsBeatImage?: boolean;
110
+ lastFrameIsBeatImage?: boolean;
111
+ frameFillColor?: string;
106
112
  movieReferenceImages?: {
107
113
  imagePath: string;
108
114
  referenceType: "ASSET" | "STYLE";
@@ -145,6 +151,9 @@ export declare const beat_graph_data: {
145
151
  };
146
152
  firstFrameImagePath?: string;
147
153
  lastFrameImagePath?: string;
154
+ firstFrameIsBeatImage?: boolean;
155
+ lastFrameIsBeatImage?: boolean;
156
+ frameFillColor?: string;
148
157
  movieReferenceImages?: {
149
158
  imagePath: string;
150
159
  referenceType: "ASSET" | "STYLE";
@@ -179,6 +188,9 @@ export declare const beat_graph_data: {
179
188
  };
180
189
  firstFrameImagePath?: string;
181
190
  lastFrameImagePath?: string;
191
+ firstFrameIsBeatImage?: boolean;
192
+ lastFrameIsBeatImage?: boolean;
193
+ frameFillColor?: string;
182
194
  movieReferenceImages?: {
183
195
  imagePath: string;
184
196
  referenceType: "ASSET" | "STYLE";
@@ -298,6 +310,24 @@ export declare const beat_graph_data: {
298
310
  };
299
311
  defaultValue: {};
300
312
  };
313
+ frameResolver: {
314
+ agent: (namedInputs: {
315
+ context: MulmoStudioContext;
316
+ beat: import("../types/type.js").MulmoBeat;
317
+ index: number;
318
+ preprocessor?: import("./image_agents.js").BeatFramePreprocessorInput;
319
+ }) => Promise<{
320
+ lastFrameImagePath: string | undefined;
321
+ referenceImageForMovie: string | undefined;
322
+ }>;
323
+ inputs: {
324
+ onComplete: string[];
325
+ context: string;
326
+ beat: string;
327
+ index: string;
328
+ preprocessor: string;
329
+ };
330
+ };
301
331
  movieGenerator: {
302
332
  if: string;
303
333
  agent: string;
@@ -13,7 +13,7 @@ import { getOutputStudioFilePath, mkdir } from "../utils/file.js";
13
13
  import { audioCheckerError } from "../utils/error_cause.js";
14
14
  import { extractImageFromMovie, ffmpegGetMediaDuration, trimMusic } from "../utils/ffmpeg_utils.js";
15
15
  import { getMediaRefs, resolveBeatLocalRefs } from "./image_references.js";
16
- import { imagePreprocessAgent, imagePluginAgent, htmlImageGeneratorAgent } from "./image_agents.js";
16
+ import { imagePreprocessAgent, imagePluginAgent, htmlImageGeneratorAgent, beatFrameResolverAgent } from "./image_agents.js";
17
17
  import { imageGraphOption } from "./graph_option.js";
18
18
  import { createUsageCallback } from "../utils/usage_callback.js";
19
19
  const vanillaAgents = vanilla.default ?? vanilla;
@@ -169,15 +169,27 @@ export const beat_graph_data = {
169
169
  },
170
170
  defaultValue: {},
171
171
  },
172
+ // Resolves $beatImage frame references once the beat image exists (after imageGenerator/imagePlugin);
173
+ // passes the preprocessor's frame paths through unchanged for non-sentinel beats.
174
+ frameResolver: {
175
+ agent: beatFrameResolverAgent,
176
+ inputs: {
177
+ onComplete: [":imageGenerator", ":imagePlugin"],
178
+ context: ":context",
179
+ beat: ":beat",
180
+ index: ":__mapIndex",
181
+ preprocessor: ":preprocessor",
182
+ },
183
+ },
172
184
  movieGenerator: {
173
185
  if: ":beat.moviePrompt",
174
186
  agent: ":preprocessor.movieAgentInfo.agent",
175
187
  inputs: {
176
188
  media: "movie",
177
- onComplete: [":imageGenerator", ":imagePlugin"], // to wait for imageGenerator to finish
189
+ onComplete: [":frameResolver"], // frameResolver itself waits for imageGenerator/imagePlugin
178
190
  prompt: ":beat.moviePrompt",
179
- imagePath: ":preprocessor.referenceImageForMovie",
180
- lastFrameImagePath: ":preprocessor.lastFrameImagePath",
191
+ imagePath: ":frameResolver.referenceImageForMovie",
192
+ lastFrameImagePath: ":frameResolver.lastFrameImagePath",
181
193
  referenceImages: ":preprocessor.movieReferenceImages",
182
194
  movieFile: ":preprocessor.movieFile", // for google genai agent
183
195
  cache: {
@@ -1,5 +1,6 @@
1
1
  export * from "./mulmo_presentation_style.js";
2
2
  export * from "./mulmo_studio_context.js";
3
3
  export * from "./mulmo_media_source.js";
4
+ export * from "./mulmo_image_prompt_media.js";
4
5
  export * from "./mulmo_beat_node.js";
5
6
  export * from "./mulmo_script.js";
@@ -1,6 +1,7 @@
1
1
  export * from "./mulmo_presentation_style.js";
2
2
  export * from "./mulmo_studio_context.js";
3
3
  export * from "./mulmo_media_source.js";
4
+ export * from "./mulmo_image_prompt_media.js";
4
5
  // Node-only MulmoBeatMethods (includes getPlugin). The browser-safe subset lives in
5
6
  // ./mulmo_beat.js and is re-exported by ../index.common.js for mulmocast/browser.
6
7
  export * from "./mulmo_beat_node.js";
@@ -0,0 +1,14 @@
1
+ import { MulmoImagePromptMedia, MulmoImageReference, Text2ImageAgentInfo } from "../types/index.js";
2
+ export type MulmoNormalizedImageReference = MulmoImageReference & {
3
+ legacy?: boolean;
4
+ };
5
+ export type ResolvedImageReference = {
6
+ path: string;
7
+ label?: string;
8
+ };
9
+ export declare const MulmoImagePromptMediaMethods: {
10
+ getReferences(image: MulmoImagePromptMedia): MulmoNormalizedImageReference[];
11
+ hasNamedReference(image: MulmoImagePromptMedia): boolean;
12
+ buildReferencePreamble(labels: (string | undefined)[]): string;
13
+ limitReferences(references: ResolvedImageReference[] | undefined, imageAgentInfo: Text2ImageAgentInfo, imageKey: string): ResolvedImageReference[] | undefined;
14
+ };
@@ -0,0 +1,45 @@
1
+ import { GraphAILogger } from "graphai";
2
+ import { getMaxImageReferenceImages } from "../types/provider2agent.js";
3
+ export const MulmoImagePromptMediaMethods = {
4
+ // Normalized reference list: legacy single-ref fields first, then the references array.
5
+ // When both legacy fields are set, referenceImageName wins and referenceImage is ignored,
6
+ // matching the pre-references resolution behavior.
7
+ getReferences(image) {
8
+ const references = [];
9
+ if (image.referenceImageName) {
10
+ references.push({ name: image.referenceImageName, legacy: true });
11
+ }
12
+ else if (image.referenceImage) {
13
+ references.push({ source: image.referenceImage, legacy: true });
14
+ }
15
+ references.push(...(image.references ?? []));
16
+ return references;
17
+ },
18
+ // Entries with a named reference must resolve after stage 1 (which generates/loads the named images).
19
+ hasNamedReference(image) {
20
+ return this.getReferences(image).some((ref) => ref.name !== undefined);
21
+ },
22
+ // "Reference image 1 shows <label>. ... Use these exact designs.\n"
23
+ // Empty when no reference has a label, keeping label-free prompts byte-identical to before.
24
+ buildReferencePreamble(labels) {
25
+ const sentences = labels.map((label, index) => (label ? `Reference image ${index + 1} shows ${label}.` : undefined)).filter((s) => s !== undefined);
26
+ if (sentences.length === 0) {
27
+ return "";
28
+ }
29
+ return `${sentences.join(" ")} Use these exact designs.\n`;
30
+ },
31
+ // Truncate to the provider/model limit, keeping the first N deterministically.
32
+ // imageKey is the imageRefs key of the image being generated (used for the warning).
33
+ limitReferences(references, imageAgentInfo, imageKey) {
34
+ if (!references) {
35
+ return undefined;
36
+ }
37
+ const { provider, model } = imageAgentInfo.imageParams;
38
+ const max = getMaxImageReferenceImages(provider, model ?? "");
39
+ if (max !== undefined && references.length > max) {
40
+ GraphAILogger.warn(`imagePrompt "${imageKey}": ${references.length} reference images exceed the ${provider} limit of ${max} — keeping the first ${max}`);
41
+ return references.slice(0, max);
42
+ }
43
+ return references;
44
+ },
45
+ };
@@ -64,6 +64,7 @@ export declare const provider2ImageAgent: {
64
64
  models: string[];
65
65
  keyName: string;
66
66
  baseURLKeyName: string;
67
+ maxReferenceImages: number;
67
68
  };
68
69
  google: {
69
70
  agentName: string;
@@ -74,6 +75,9 @@ export declare const provider2ImageAgent: {
74
75
  replicate: {
75
76
  agentName: string;
76
77
  defaultModel: string;
78
+ imageModelParams: Record<string, {
79
+ maxReferenceImages?: number;
80
+ }>;
77
81
  models: string[];
78
82
  keyName: string;
79
83
  };
@@ -213,6 +217,17 @@ export declare const defaultProviders: {
213
217
  export declare const llm: (keyof typeof provider2LLMAgent)[];
214
218
  export type LLM = keyof typeof provider2LLMAgent;
215
219
  export declare const htmlLLMProvider: string[];
220
+ export declare const ProviderName: {
221
+ readonly OpenAI: "openai";
222
+ readonly Google: "google";
223
+ readonly Gemini: "gemini";
224
+ readonly Anthropic: "anthropic";
225
+ readonly Replicate: "replicate";
226
+ readonly ElevenLabs: "elevenlabs";
227
+ readonly Mock: "mock";
228
+ };
229
+ export declare const isImageProvider: (provider: string | undefined) => provider is keyof typeof provider2ImageAgent;
230
+ export declare const getMaxImageReferenceImages: (provider: string | undefined, model: string) => number | undefined;
216
231
  export declare const getModelAudio: (provider: keyof typeof provider2MovieAgent, model: string) => MovieAudioSpec | undefined;
217
232
  export declare const getModelDuration: (provider: keyof typeof provider2MovieAgent, model: string, movieDuration?: number) => number | undefined;
218
233
  export type ModelPricing = {
@@ -64,6 +64,10 @@ export const deprecatedGoogleImageModelHints = {
64
64
  // See https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-pro-image
65
65
  // and https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-1-flash-image
66
66
  export const vertexAIGlobalOnlyImageModels = new Set(["gemini-3-pro-image-preview", "gemini-3.1-flash-image-preview"]);
67
+ // Per-model reference image limits (image_input array). Only verified entries; unlisted models are not truncated.
68
+ const replicateImageModelParams = {
69
+ "bytedance/seedream-4": { maxReferenceImages: 10 },
70
+ };
67
71
  export const provider2ImageAgent = {
68
72
  openai: {
69
73
  agentName: "imageOpenaiAgent",
@@ -71,6 +75,7 @@ export const provider2ImageAgent = {
71
75
  models: [...gptImages],
72
76
  keyName: "OPENAI_API_KEY",
73
77
  baseURLKeyName: "OPENAI_BASE_URL",
78
+ maxReferenceImages: 16, // images.edit accepts up to 16 input images
74
79
  },
75
80
  google: {
76
81
  agentName: "imageGenAIAgent",
@@ -81,6 +86,7 @@ export const provider2ImageAgent = {
81
86
  replicate: {
82
87
  agentName: "imageReplicateAgent",
83
88
  defaultModel: "bytedance/seedream-4",
89
+ imageModelParams: replicateImageModelParams,
84
90
  models: [
85
91
  "bytedance/seedream-4",
86
92
  "qwen/qwen-image",
@@ -527,6 +533,31 @@ export const defaultProviders = {
527
533
  };
528
534
  export const llm = Object.keys(provider2LLMAgent);
529
535
  export const htmlLLMProvider = ["openai", "anthropic", "mock"];
536
+ // Provider name literals. Compare via these identifiers (e.g. provider === ProviderName.Replicate)
537
+ // instead of raw strings.
538
+ export const ProviderName = {
539
+ OpenAI: "openai",
540
+ Google: "google",
541
+ Gemini: "gemini",
542
+ Anthropic: "anthropic",
543
+ Replicate: "replicate",
544
+ ElevenLabs: "elevenlabs",
545
+ Mock: "mock",
546
+ };
547
+ export const isImageProvider = (provider) => {
548
+ return provider !== undefined && provider in provider2ImageAgent;
549
+ };
550
+ // Max reference images an image model accepts as input; undefined means no known limit (no truncation).
551
+ export const getMaxImageReferenceImages = (provider, model) => {
552
+ if (!isImageProvider(provider)) {
553
+ return undefined;
554
+ }
555
+ if (provider === ProviderName.Replicate) {
556
+ return provider2ImageAgent.replicate.imageModelParams[model]?.maxReferenceImages;
557
+ }
558
+ const agentInfo = provider2ImageAgent[provider];
559
+ return "maxReferenceImages" in agentInfo ? agentInfo.maxReferenceImages : undefined;
560
+ };
530
561
  export const getModelAudio = (provider, model) => {
531
562
  const modelParams = provider2MovieAgent[provider]?.modelParams;
532
563
  return modelParams?.[model]?.audio;
@@ -263,6 +263,15 @@ export declare const mulmoMarkdownMediaSchema: z.ZodObject<{
263
263
  opacity: z.ZodOptional<z.ZodNumber>;
264
264
  }, z.core.$strip>]>>>;
265
265
  }, z.core.$strict>;
266
+ export declare const ImageMediaType: {
267
+ readonly Image: "image";
268
+ readonly ImagePrompt: "imagePrompt";
269
+ readonly Movie: "movie";
270
+ readonly MoviePrompt: "moviePrompt";
271
+ readonly Beat: "beat";
272
+ readonly VoiceOver: "voice_over";
273
+ };
274
+ export declare const beatImageTypesWithoutOwnStill: readonly string[];
266
275
  export declare const mulmoImageMediaSchema: z.ZodObject<{
267
276
  type: z.ZodLiteral<"image">;
268
277
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -4135,6 +4144,21 @@ export declare const mulmoAudioAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
4135
4144
  type: z.ZodLiteral<"midi">;
4136
4145
  source: z.ZodString;
4137
4146
  }, z.core.$strict>]>;
4147
+ export declare const BEAT_IMAGE_SENTINEL = "$beatImage";
4148
+ export declare const mulmoImageReferenceSchema: z.ZodObject<{
4149
+ name: z.ZodOptional<z.ZodString>;
4150
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4151
+ kind: z.ZodLiteral<"url">;
4152
+ url: z.ZodURL;
4153
+ }, z.core.$strict>, z.ZodObject<{
4154
+ kind: z.ZodLiteral<"base64">;
4155
+ data: z.ZodString;
4156
+ }, z.core.$strict>, z.ZodObject<{
4157
+ kind: z.ZodLiteral<"path">;
4158
+ path: z.ZodString;
4159
+ }, z.core.$strict>], "kind">>;
4160
+ label: z.ZodOptional<z.ZodString>;
4161
+ }, z.core.$strict>;
4138
4162
  export declare const mulmoImagePromptMediaSchema: z.ZodObject<{
4139
4163
  type: z.ZodLiteral<"imagePrompt">;
4140
4164
  prompt: z.ZodString;
@@ -4153,6 +4177,20 @@ export declare const mulmoImagePromptMediaSchema: z.ZodObject<{
4153
4177
  kind: z.ZodLiteral<"path">;
4154
4178
  path: z.ZodString;
4155
4179
  }, z.core.$strict>], "kind">>;
4180
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
4181
+ name: z.ZodOptional<z.ZodString>;
4182
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4183
+ kind: z.ZodLiteral<"url">;
4184
+ url: z.ZodURL;
4185
+ }, z.core.$strict>, z.ZodObject<{
4186
+ kind: z.ZodLiteral<"base64">;
4187
+ data: z.ZodString;
4188
+ }, z.core.$strict>, z.ZodObject<{
4189
+ kind: z.ZodLiteral<"path">;
4190
+ path: z.ZodString;
4191
+ }, z.core.$strict>], "kind">>;
4192
+ label: z.ZodOptional<z.ZodString>;
4193
+ }, z.core.$strict>>>;
4156
4194
  }, z.core.$strict>;
4157
4195
  export declare const mulmoImageParamsImagesValueSchema: z.ZodUnion<readonly [z.ZodObject<{
4158
4196
  type: z.ZodLiteral<"image">;
@@ -4184,6 +4222,20 @@ export declare const mulmoImageParamsImagesValueSchema: z.ZodUnion<readonly [z.Z
4184
4222
  kind: z.ZodLiteral<"path">;
4185
4223
  path: z.ZodString;
4186
4224
  }, z.core.$strict>], "kind">>;
4225
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
4226
+ name: z.ZodOptional<z.ZodString>;
4227
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4228
+ kind: z.ZodLiteral<"url">;
4229
+ url: z.ZodURL;
4230
+ }, z.core.$strict>, z.ZodObject<{
4231
+ kind: z.ZodLiteral<"base64">;
4232
+ data: z.ZodString;
4233
+ }, z.core.$strict>, z.ZodObject<{
4234
+ kind: z.ZodLiteral<"path">;
4235
+ path: z.ZodString;
4236
+ }, z.core.$strict>], "kind">>;
4237
+ label: z.ZodOptional<z.ZodString>;
4238
+ }, z.core.$strict>>>;
4187
4239
  }, z.core.$strict>, z.ZodObject<{
4188
4240
  type: z.ZodLiteral<"movie">;
4189
4241
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -4231,6 +4283,20 @@ export declare const mulmoImageParamsImagesSchema: z.ZodRecord<z.ZodString, z.Zo
4231
4283
  kind: z.ZodLiteral<"path">;
4232
4284
  path: z.ZodString;
4233
4285
  }, z.core.$strict>], "kind">>;
4286
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
4287
+ name: z.ZodOptional<z.ZodString>;
4288
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4289
+ kind: z.ZodLiteral<"url">;
4290
+ url: z.ZodURL;
4291
+ }, z.core.$strict>, z.ZodObject<{
4292
+ kind: z.ZodLiteral<"base64">;
4293
+ data: z.ZodString;
4294
+ }, z.core.$strict>, z.ZodObject<{
4295
+ kind: z.ZodLiteral<"path">;
4296
+ path: z.ZodString;
4297
+ }, z.core.$strict>], "kind">>;
4298
+ label: z.ZodOptional<z.ZodString>;
4299
+ }, z.core.$strict>>>;
4234
4300
  }, z.core.$strict>, z.ZodObject<{
4235
4301
  type: z.ZodLiteral<"movie">;
4236
4302
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -4330,6 +4396,20 @@ export declare const mulmoImageParamsSchema: z.ZodObject<{
4330
4396
  kind: z.ZodLiteral<"path">;
4331
4397
  path: z.ZodString;
4332
4398
  }, z.core.$strict>], "kind">>;
4399
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
4400
+ name: z.ZodOptional<z.ZodString>;
4401
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4402
+ kind: z.ZodLiteral<"url">;
4403
+ url: z.ZodURL;
4404
+ }, z.core.$strict>, z.ZodObject<{
4405
+ kind: z.ZodLiteral<"base64">;
4406
+ data: z.ZodString;
4407
+ }, z.core.$strict>, z.ZodObject<{
4408
+ kind: z.ZodLiteral<"path">;
4409
+ path: z.ZodString;
4410
+ }, z.core.$strict>], "kind">>;
4411
+ label: z.ZodOptional<z.ZodString>;
4412
+ }, z.core.$strict>>>;
4333
4413
  }, z.core.$strict>, z.ZodObject<{
4334
4414
  type: z.ZodLiteral<"movie">;
4335
4415
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -8800,6 +8880,20 @@ export declare const mulmoBeatSchema: z.ZodObject<{
8800
8880
  kind: z.ZodLiteral<"path">;
8801
8881
  path: z.ZodString;
8802
8882
  }, z.core.$strict>], "kind">>;
8883
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
8884
+ name: z.ZodOptional<z.ZodString>;
8885
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
8886
+ kind: z.ZodLiteral<"url">;
8887
+ url: z.ZodURL;
8888
+ }, z.core.$strict>, z.ZodObject<{
8889
+ kind: z.ZodLiteral<"base64">;
8890
+ data: z.ZodString;
8891
+ }, z.core.$strict>, z.ZodObject<{
8892
+ kind: z.ZodLiteral<"path">;
8893
+ path: z.ZodString;
8894
+ }, z.core.$strict>], "kind">>;
8895
+ label: z.ZodOptional<z.ZodString>;
8896
+ }, z.core.$strict>>>;
8803
8897
  }, z.core.$strict>, z.ZodObject<{
8804
8898
  type: z.ZodLiteral<"movie">;
8805
8899
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -8942,6 +9036,20 @@ export declare const mulmoPresentationStyleSchema: z.ZodObject<{
8942
9036
  kind: z.ZodLiteral<"path">;
8943
9037
  path: z.ZodString;
8944
9038
  }, z.core.$strict>], "kind">>;
9039
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
9040
+ name: z.ZodOptional<z.ZodString>;
9041
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
9042
+ kind: z.ZodLiteral<"url">;
9043
+ url: z.ZodURL;
9044
+ }, z.core.$strict>, z.ZodObject<{
9045
+ kind: z.ZodLiteral<"base64">;
9046
+ data: z.ZodString;
9047
+ }, z.core.$strict>, z.ZodObject<{
9048
+ kind: z.ZodLiteral<"path">;
9049
+ path: z.ZodString;
9050
+ }, z.core.$strict>], "kind">>;
9051
+ label: z.ZodOptional<z.ZodString>;
9052
+ }, z.core.$strict>>>;
8945
9053
  }, z.core.$strict>, z.ZodObject<{
8946
9054
  type: z.ZodLiteral<"movie">;
8947
9055
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -9455,6 +9563,20 @@ export declare const mulmoScriptSchema: z.ZodObject<{
9455
9563
  kind: z.ZodLiteral<"path">;
9456
9564
  path: z.ZodString;
9457
9565
  }, z.core.$strict>], "kind">>;
9566
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
9567
+ name: z.ZodOptional<z.ZodString>;
9568
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
9569
+ kind: z.ZodLiteral<"url">;
9570
+ url: z.ZodURL;
9571
+ }, z.core.$strict>, z.ZodObject<{
9572
+ kind: z.ZodLiteral<"base64">;
9573
+ data: z.ZodString;
9574
+ }, z.core.$strict>, z.ZodObject<{
9575
+ kind: z.ZodLiteral<"path">;
9576
+ path: z.ZodString;
9577
+ }, z.core.$strict>], "kind">>;
9578
+ label: z.ZodOptional<z.ZodString>;
9579
+ }, z.core.$strict>>>;
9458
9580
  }, z.core.$strict>, z.ZodObject<{
9459
9581
  type: z.ZodLiteral<"movie">;
9460
9582
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -13913,6 +14035,20 @@ export declare const mulmoScriptSchema: z.ZodObject<{
13913
14035
  kind: z.ZodLiteral<"path">;
13914
14036
  path: z.ZodString;
13915
14037
  }, z.core.$strict>], "kind">>;
14038
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
14039
+ name: z.ZodOptional<z.ZodString>;
14040
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
14041
+ kind: z.ZodLiteral<"url">;
14042
+ url: z.ZodURL;
14043
+ }, z.core.$strict>, z.ZodObject<{
14044
+ kind: z.ZodLiteral<"base64">;
14045
+ data: z.ZodString;
14046
+ }, z.core.$strict>, z.ZodObject<{
14047
+ kind: z.ZodLiteral<"path">;
14048
+ path: z.ZodString;
14049
+ }, z.core.$strict>], "kind">>;
14050
+ label: z.ZodOptional<z.ZodString>;
14051
+ }, z.core.$strict>>>;
13916
14052
  }, z.core.$strict>, z.ZodObject<{
13917
14053
  type: z.ZodLiteral<"movie">;
13918
14054
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -14131,6 +14267,20 @@ export declare const mulmoStudioSchema: z.ZodObject<{
14131
14267
  kind: z.ZodLiteral<"path">;
14132
14268
  path: z.ZodString;
14133
14269
  }, z.core.$strict>], "kind">>;
14270
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
14271
+ name: z.ZodOptional<z.ZodString>;
14272
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
14273
+ kind: z.ZodLiteral<"url">;
14274
+ url: z.ZodURL;
14275
+ }, z.core.$strict>, z.ZodObject<{
14276
+ kind: z.ZodLiteral<"base64">;
14277
+ data: z.ZodString;
14278
+ }, z.core.$strict>, z.ZodObject<{
14279
+ kind: z.ZodLiteral<"path">;
14280
+ path: z.ZodString;
14281
+ }, z.core.$strict>], "kind">>;
14282
+ label: z.ZodOptional<z.ZodString>;
14283
+ }, z.core.$strict>>>;
14134
14284
  }, z.core.$strict>, z.ZodObject<{
14135
14285
  type: z.ZodLiteral<"movie">;
14136
14286
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -18589,6 +18739,20 @@ export declare const mulmoStudioSchema: z.ZodObject<{
18589
18739
  kind: z.ZodLiteral<"path">;
18590
18740
  path: z.ZodString;
18591
18741
  }, z.core.$strict>], "kind">>;
18742
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
18743
+ name: z.ZodOptional<z.ZodString>;
18744
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
18745
+ kind: z.ZodLiteral<"url">;
18746
+ url: z.ZodURL;
18747
+ }, z.core.$strict>, z.ZodObject<{
18748
+ kind: z.ZodLiteral<"base64">;
18749
+ data: z.ZodString;
18750
+ }, z.core.$strict>, z.ZodObject<{
18751
+ kind: z.ZodLiteral<"path">;
18752
+ path: z.ZodString;
18753
+ }, z.core.$strict>], "kind">>;
18754
+ label: z.ZodOptional<z.ZodString>;
18755
+ }, z.core.$strict>>>;
18592
18756
  }, z.core.$strict>, z.ZodObject<{
18593
18757
  type: z.ZodLiteral<"movie">;
18594
18758
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -18742,6 +18906,20 @@ export declare const mulmoPromptTemplateSchema: z.ZodObject<{
18742
18906
  kind: z.ZodLiteral<"path">;
18743
18907
  path: z.ZodString;
18744
18908
  }, z.core.$strict>], "kind">>;
18909
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
18910
+ name: z.ZodOptional<z.ZodString>;
18911
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
18912
+ kind: z.ZodLiteral<"url">;
18913
+ url: z.ZodURL;
18914
+ }, z.core.$strict>, z.ZodObject<{
18915
+ kind: z.ZodLiteral<"base64">;
18916
+ data: z.ZodString;
18917
+ }, z.core.$strict>, z.ZodObject<{
18918
+ kind: z.ZodLiteral<"path">;
18919
+ path: z.ZodString;
18920
+ }, z.core.$strict>], "kind">>;
18921
+ label: z.ZodOptional<z.ZodString>;
18922
+ }, z.core.$strict>>>;
18745
18923
  }, z.core.$strict>, z.ZodObject<{
18746
18924
  type: z.ZodLiteral<"movie">;
18747
18925
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -19249,6 +19427,20 @@ export declare const mulmoPromptTemplateFileSchema: z.ZodObject<{
19249
19427
  kind: z.ZodLiteral<"path">;
19250
19428
  path: z.ZodString;
19251
19429
  }, z.core.$strict>], "kind">>;
19430
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
19431
+ name: z.ZodOptional<z.ZodString>;
19432
+ source: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
19433
+ kind: z.ZodLiteral<"url">;
19434
+ url: z.ZodURL;
19435
+ }, z.core.$strict>, z.ZodObject<{
19436
+ kind: z.ZodLiteral<"base64">;
19437
+ data: z.ZodString;
19438
+ }, z.core.$strict>, z.ZodObject<{
19439
+ kind: z.ZodLiteral<"path">;
19440
+ path: z.ZodString;
19441
+ }, z.core.$strict>], "kind">>;
19442
+ label: z.ZodOptional<z.ZodString>;
19443
+ }, z.core.$strict>>>;
19252
19444
  }, z.core.$strict>, z.ZodObject<{
19253
19445
  type: z.ZodLiteral<"movie">;
19254
19446
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -130,9 +130,22 @@ const mulmoPdfMediaSchema = z
130
130
  source: mediaSourceSchema,
131
131
  })
132
132
  .strict();
133
+ // Media type literals shared by imageParams.images entries and beat images. Compare via
134
+ // these identifiers (e.g. entry.type === ImageMediaType.ImagePrompt) instead of raw strings.
135
+ export const ImageMediaType = {
136
+ Image: "image",
137
+ ImagePrompt: "imagePrompt",
138
+ Movie: "movie",
139
+ MoviePrompt: "moviePrompt",
140
+ Beat: "beat",
141
+ VoiceOver: "voice_over",
142
+ };
143
+ // Beat image types whose still derives from the movie or another beat — they cannot supply
144
+ // their own still for the $beatImage frame sentinel.
145
+ export const beatImageTypesWithoutOwnStill = [ImageMediaType.Movie, ImageMediaType.Beat, ImageMediaType.VoiceOver];
133
146
  export const mulmoImageMediaSchema = z
134
147
  .object({
135
- type: z.literal("image"),
148
+ type: z.literal(ImageMediaType.Image),
136
149
  source: mediaSourceSchema,
137
150
  })
138
151
  .strict();
@@ -144,13 +157,13 @@ const mulmoSvgMediaSchema = z
144
157
  .strict();
145
158
  export const mulmoMovieMediaSchema = z
146
159
  .object({
147
- type: z.literal("movie"),
160
+ type: z.literal(ImageMediaType.Movie),
148
161
  source: mediaSourceSchema,
149
162
  })
150
163
  .strict();
151
164
  export const mulmoMoviePromptMediaSchema = z
152
165
  .object({
153
- type: z.literal("moviePrompt"),
166
+ type: z.literal(ImageMediaType.MoviePrompt),
154
167
  prompt: z.string().min(1),
155
168
  imageName: z.string().optional().describe("Reference an imageRefs key to use as image-to-video input"),
156
169
  })
@@ -292,13 +305,13 @@ export const mulmoHtmlTailwindMediaSchema = z
292
305
  .strict();
293
306
  export const mulmoBeatReferenceMediaSchema = z
294
307
  .object({
295
- type: z.literal("beat"),
308
+ type: z.literal(ImageMediaType.Beat),
296
309
  id: z.string().optional().describe("Specifies the beat to reference."),
297
310
  })
298
311
  .strict();
299
312
  export const mulmoVoiceOverMediaSchema = z
300
313
  .object({
301
- type: z.literal("voice_over"),
314
+ type: z.literal(ImageMediaType.VoiceOver),
302
315
  startAt: z.number().optional().describe("The time to start the voice over the video in seconds."),
303
316
  })
304
317
  .strict();
@@ -339,13 +352,26 @@ const mulmoMidiMediaSchema = z
339
352
  .strict();
340
353
  export const mulmoAudioAssetSchema = z.union([mulmoAudioMediaSchema, mulmoMidiMediaSchema]);
341
354
  const imageIdSchema = z.string();
355
+ // Sentinel for movieParams.firstFrameImageName / lastFrameImageName: "this beat's own generated image".
356
+ export const BEAT_IMAGE_SENTINEL = "$beatImage";
357
+ // imageRefs keys may not start with "$": that prefix is reserved for sentinels like $beatImage.
358
+ const imageRefKeySchema = imageIdSchema.refine((key) => !key.startsWith("$"), { message: 'image keys must not start with the reserved prefix "$"' });
359
+ export const mulmoImageReferenceSchema = z
360
+ .object({
361
+ name: imageIdSchema.optional().describe("An imageRefs key to use as a reference image"),
362
+ source: mediaSourceSchema.optional().describe("Direct source (path/url) to use as a reference image"),
363
+ label: z.string().optional().describe("Role of this reference (e.g. 'the firefighter Travis Kane'), injected into the prompt as a preamble"),
364
+ })
365
+ .strict()
366
+ .refine((ref) => (ref.name === undefined) !== (ref.source === undefined), { message: "specify exactly one of name or source" });
342
367
  export const mulmoImagePromptMediaSchema = z
343
368
  .object({
344
- type: z.literal("imagePrompt"),
369
+ type: z.literal(ImageMediaType.ImagePrompt),
345
370
  prompt: z.string().min(1),
346
371
  canvasSize: z.object({ width: z.number(), height: z.number() }).strict().optional(),
347
- referenceImageName: imageIdSchema.optional().describe("Reference another imageRefs key as input for image generation"),
348
- referenceImage: mediaSourceSchema.optional().describe("Direct source (path/url/base64) as reference image for generation"),
372
+ referenceImageName: imageIdSchema.optional().describe("Reference another imageRefs key as input for image generation. Sugar for references[0].name"),
373
+ referenceImage: mediaSourceSchema.optional().describe("Direct source (path/url/base64) as reference image for generation. Sugar for references[0].source"),
374
+ references: z.array(mulmoImageReferenceSchema).optional().describe("Reference images (by imageRefs key or direct source) with optional role labels"),
349
375
  })
350
376
  .strict();
351
377
  export const mulmoImageParamsImagesValueSchema = z.union([
@@ -354,7 +380,7 @@ export const mulmoImageParamsImagesValueSchema = z.union([
354
380
  mulmoMovieMediaSchema,
355
381
  mulmoMoviePromptMediaSchema,
356
382
  ]);
357
- export const mulmoImageParamsImagesSchema = z.record(imageIdSchema, mulmoImageParamsImagesValueSchema);
383
+ export const mulmoImageParamsImagesSchema = z.record(imageRefKeySchema, mulmoImageParamsImagesValueSchema);
358
384
  export const mulmoFillOptionSchema = z
359
385
  .object({
360
386
  style: z.enum(["aspectFit", "aspectFill"]).optional().default("aspectFit"),
@@ -500,8 +526,12 @@ export const mulmoMovieParamsSchema = z.object({
500
526
  filters: z.array(mulmoVideoFilterSchema).optional(), // for movie.ts
501
527
  vertexai_project: z.string().optional(), // Google Cloud Project ID for Vertex AI
502
528
  vertexai_location: z.string().optional(), // Vertex AI location (default: us-central1)
503
- firstFrameImageName: imageIdSchema.optional().describe("Reference an imageRefs key for the first frame (image-to-video input)"),
504
- lastFrameImageName: imageIdSchema.optional().describe("Reference an imageRefs key for the last frame (image-to-video interpolation)"),
529
+ firstFrameImageName: imageIdSchema
530
+ .optional()
531
+ .describe("Reference an imageRefs key for the first frame (image-to-video input), or '$beatImage' for the beat's own generated image"),
532
+ lastFrameImageName: imageIdSchema
533
+ .optional()
534
+ .describe("Reference an imageRefs key for the last frame (image-to-video interpolation), or '$beatImage' for the beat's own generated image"),
505
535
  frameFillColor: z
506
536
  .string()
507
537
  .regex(/^(#|0x)?[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$|^[a-zA-Z]+$/, "must be a hex color ('#RRGGBB[AA]', '0x' or no prefix) or a color name")
@@ -540,7 +570,7 @@ export const mulmoBeatSchema = z
540
570
  images: mulmoImageParamsImagesSchema
541
571
  .optional()
542
572
  .describe("Beat-local media references. Same schema as imageParams.images. Merged with global refs (local takes precedence)."),
543
- imageNames: z.array(imageIdSchema).optional(), // list of image names to use for image generation. The default is all images in the imageParams.images.
573
+ imageNames: z.array(imageRefKeySchema).optional(), // list of image names to use for image generation. The default is all images in the imageParams.images.
544
574
  imagePrompt: z.string().optional(),
545
575
  moviePrompt: z.string().optional(),
546
576
  soundEffectPrompt: z.string().optional(),
@@ -1,6 +1,6 @@
1
1
  import { type CallbackFunction } from "graphai";
2
2
  import type { UsageCollectorAPI } from "./usage.js";
3
- import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema } from "./schema.js";
3
+ import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoImageReferenceSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema } from "./schema.js";
4
4
  import { pdf_modes, pdf_sizes, storyToScriptGenerateMode } from "./const.js";
5
5
  import type { LLM } from "./provider2agent.js";
6
6
  import { z } from "zod";
@@ -49,6 +49,7 @@ export type MulmoGoogleImageModel = z.infer<typeof mulmoGoogleImageModelSchema>;
49
49
  export type MulmoGoogleMovieModel = z.infer<typeof mulmoGoogleMovieModelSchema>;
50
50
  export type MulmoReplicateMovieModel = z.infer<typeof mulmoReplicateMovieModelSchema>;
51
51
  export type MulmoImagePromptMedia = z.infer<typeof mulmoImagePromptMediaSchema>;
52
+ export type MulmoImageReference = z.infer<typeof mulmoImageReferenceSchema>;
52
53
  export type MulmoMoviePromptMedia = z.infer<typeof mulmoMoviePromptMediaSchema>;
53
54
  export type MulmoMarkdownLayout = z.infer<typeof markdownLayoutSchema>;
54
55
  export type MulmoRow2 = z.infer<typeof row2Schema>;
@@ -103,6 +103,20 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
103
103
  kind: "path";
104
104
  path: string;
105
105
  } | undefined;
106
+ references?: {
107
+ name?: string | undefined;
108
+ source?: {
109
+ kind: "url";
110
+ url: string;
111
+ } | {
112
+ kind: "base64";
113
+ data: string;
114
+ } | {
115
+ kind: "path";
116
+ path: string;
117
+ } | undefined;
118
+ label?: string | undefined;
119
+ }[] | undefined;
106
120
  }> | undefined;
107
121
  backgroundImage?: string | {
108
122
  source: {
@@ -2404,6 +2418,20 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
2404
2418
  kind: "path";
2405
2419
  path: string;
2406
2420
  } | undefined;
2421
+ references?: {
2422
+ name?: string | undefined;
2423
+ source?: {
2424
+ kind: "url";
2425
+ url: string;
2426
+ } | {
2427
+ kind: "base64";
2428
+ data: string;
2429
+ } | {
2430
+ kind: "path";
2431
+ path: string;
2432
+ } | undefined;
2433
+ label?: string | undefined;
2434
+ }[] | undefined;
2407
2435
  }> | undefined;
2408
2436
  imageNames?: string[] | undefined;
2409
2437
  imagePrompt?: string | undefined;
@@ -2641,6 +2669,20 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
2641
2669
  kind: "path";
2642
2670
  path: string;
2643
2671
  } | undefined;
2672
+ references?: {
2673
+ name?: string | undefined;
2674
+ source?: {
2675
+ kind: "url";
2676
+ url: string;
2677
+ } | {
2678
+ kind: "base64";
2679
+ data: string;
2680
+ } | {
2681
+ kind: "path";
2682
+ path: string;
2683
+ } | undefined;
2684
+ label?: string | undefined;
2685
+ }[] | undefined;
2644
2686
  }> | undefined;
2645
2687
  backgroundImage?: string | {
2646
2688
  source: {
@@ -4942,6 +4984,20 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
4942
4984
  kind: "path";
4943
4985
  path: string;
4944
4986
  } | undefined;
4987
+ references?: {
4988
+ name?: string | undefined;
4989
+ source?: {
4990
+ kind: "url";
4991
+ url: string;
4992
+ } | {
4993
+ kind: "base64";
4994
+ data: string;
4995
+ } | {
4996
+ kind: "path";
4997
+ path: string;
4998
+ } | undefined;
4999
+ label?: string | undefined;
5000
+ }[] | undefined;
4945
5001
  }> | undefined;
4946
5002
  imageNames?: string[] | undefined;
4947
5003
  imagePrompt?: string | undefined;
@@ -5186,6 +5242,20 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5186
5242
  kind: "path";
5187
5243
  path: string;
5188
5244
  } | undefined;
5245
+ references?: {
5246
+ name?: string | undefined;
5247
+ source?: {
5248
+ kind: "url";
5249
+ url: string;
5250
+ } | {
5251
+ kind: "base64";
5252
+ data: string;
5253
+ } | {
5254
+ kind: "path";
5255
+ path: string;
5256
+ } | undefined;
5257
+ label?: string | undefined;
5258
+ }[] | undefined;
5189
5259
  }> | undefined;
5190
5260
  backgroundImage?: string | {
5191
5261
  source: {
@@ -55,6 +55,7 @@ export declare const apiRateLimitErrorType = "apiRateLimit";
55
55
  export declare const apiKeyMissingType = "apiKeyMissing";
56
56
  export declare const invalidResponseType = "invalidResponse";
57
57
  export declare const voiceLimitReachedType = "voice_limit_reached";
58
+ export declare const invalidFrameSentinelType = "invalidFrameSentinel";
58
59
  export declare const movieAction = "movie";
59
60
  export declare const imageAction = "images";
60
61
  export declare const audioAction = "audio";
@@ -69,6 +70,10 @@ export declare const multiLingualFileTarget = "multiLingualFile";
69
70
  export declare const videoSourceTarget = "videoSource";
70
71
  export declare const audioSourceTarget = "audioSource";
71
72
  export declare const codeTextTarget = "codeText";
73
+ export declare const bothFramesTarget = "bothFrames";
74
+ export declare const missingBeatImageTarget = "missingBeatImage";
75
+ export declare const unsupportedImageTypeTarget = "unsupportedImageType";
76
+ export declare const missingFirstFrameTarget = "missingFirstFrame";
72
77
  export declare const agentFileNotExistError: (agentName: string, action: string, target: string, fileName: string, beatIndex?: number) => {
73
78
  beatIndex?: number | undefined;
74
79
  type: string;
@@ -115,6 +120,13 @@ export declare const invalidAudioSourceError: (beatIndex: number) => {
115
120
  agentName: string;
116
121
  beatIndex: number;
117
122
  };
123
+ export declare const beatImageSentinelError: (beatIndex: number, target: string) => {
124
+ type: string;
125
+ action: string;
126
+ target: string;
127
+ agentName: string;
128
+ beatIndex: number;
129
+ };
118
130
  export declare const downLoadReferenceImageError: (key: string, url: string) => {
119
131
  type: string;
120
132
  action: string;
@@ -56,6 +56,7 @@ export const apiRateLimitErrorType = "apiRateLimit";
56
56
  export const apiKeyMissingType = "apiKeyMissing";
57
57
  export const invalidResponseType = "invalidResponse";
58
58
  export const voiceLimitReachedType = "voice_limit_reached";
59
+ export const invalidFrameSentinelType = "invalidFrameSentinel";
59
60
  // Actions
60
61
  export const movieAction = "movie";
61
62
  export const imageAction = "images";
@@ -72,6 +73,10 @@ export const multiLingualFileTarget = "multiLingualFile";
72
73
  export const videoSourceTarget = "videoSource";
73
74
  export const audioSourceTarget = "audioSource";
74
75
  export const codeTextTarget = "codeText";
76
+ export const bothFramesTarget = "bothFrames";
77
+ export const missingBeatImageTarget = "missingBeatImage";
78
+ export const unsupportedImageTypeTarget = "unsupportedImageType";
79
+ export const missingFirstFrameTarget = "missingFirstFrame";
75
80
  // Agent File Not Exist Errors
76
81
  export const agentFileNotExistError = (agentName, action, target, fileName, beatIndex) => {
77
82
  return {
@@ -111,6 +116,16 @@ export const invalidAudioSourceError = (beatIndex) => {
111
116
  beatIndex,
112
117
  };
113
118
  };
119
+ // $beatImage frame sentinel misuse (invalid combination or beat cannot supply its own image)
120
+ export const beatImageSentinelError = (beatIndex, target) => {
121
+ return {
122
+ type: invalidFrameSentinelType,
123
+ action: imageAction,
124
+ target,
125
+ agentName: "imagePreprocessAgent",
126
+ beatIndex,
127
+ };
128
+ };
114
129
  // 404
115
130
  export const downLoadReferenceImageError = (key, url) => {
116
131
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.8.1",
3
+ "version": "2.9.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",
@@ -89,7 +89,7 @@
89
89
  "homepage": "https://github.com/receptron/mulmocast-cli#readme",
90
90
  "dependencies": {
91
91
  "@google-cloud/text-to-speech": "^6.4.1",
92
- "@google/genai": "^2.10.0",
92
+ "@google/genai": "^2.12.0",
93
93
  "@graphai/anthropic_agent": "^2.0.12",
94
94
  "@graphai/browserless_agent": "^2.0.3",
95
95
  "@graphai/gemini_agent": "^2.0.5",
@@ -102,7 +102,7 @@
102
102
  "@inquirer/input": "^5.1.2",
103
103
  "@inquirer/select": "^5.2.1",
104
104
  "@modelcontextprotocol/sdk": "^1.29.0",
105
- "@modernized/fluent-ffmpeg": "^1.0.1",
105
+ "@modernized/fluent-ffmpeg": "^1.0.2",
106
106
  "@mozilla/readability": "^0.6.0",
107
107
  "@mulmocast/deck": "^1.1.0",
108
108
  "@tavily/core": "^0.7.6",