Skip to content

Commit 1d96dc0

Browse files
myxamediyarclaude
andcommitted
fix(video): drop only settings a start frame change made stale
Runway models also switch endpoints when a start frame is attached, and their stored settings can be valid on one and not the other (Kling 4 takes 1920:1080 from a prompt but only auto ratios from a frame). Both fal and direct providers now drop a setting only when the selected endpoint rejects it and the model's other endpoint accepts it. Values neither endpoint lists still reach the request builder, which maps legacy Runway ratios such as 16:9 and rejects anything else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ca301b3 commit 1d96dc0

2 files changed

Lines changed: 71 additions & 19 deletions

File tree

‎apps/sim/blocks/blocks/video_generator.ts‎

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -141,27 +141,34 @@ const LEGACY_FAL_SETTING_KEYS: Record<string, keyof FalVideoSettings> = {
141141
}
142142

143143
/**
144-
* Drops stored settings the endpoint a run calls does not accept, so the model default applies.
145-
* Attaching or removing a start frame switches endpoints without clearing the settings chosen for
146-
* the other one: Seedance 2.5 takes `16:9` from a prompt but only `auto` from a start frame.
144+
* Drops a stored setting the endpoint a run calls rejects but the model's other endpoint accepts,
145+
* so the model default applies. Attaching or removing a start frame switches endpoints without
146+
* clearing settings picked for the other one: Seedance 2.5 takes `16:9` from a prompt but only
147+
* `auto` from a start frame. Any other value passes through to the request's own legacy mapping
148+
* and validation.
147149
*/
150+
function dropStaleSetting(
151+
value: unknown,
152+
allowed: readonly (number | string)[] | undefined,
153+
otherAllowed: readonly (number | string)[] | undefined,
154+
numeric = false
155+
) {
156+
const normalize = (choice: unknown) => (numeric ? Number.parseFloat(String(choice)) : choice)
157+
const accepts = (choices?: readonly (number | string)[]) =>
158+
choices?.some((choice) => normalize(choice) === normalize(value)) ?? false
159+
return value !== undefined && !accepts(allowed) && accepts(otherAllowed) ? undefined : value
160+
}
161+
148162
function keepFalSettingsAllowed(
149163
params: Record<string, unknown>,
150-
settings: FalVideoSettings | undefined
164+
settings: FalVideoSettings | undefined,
165+
other: FalVideoSettings | undefined
151166
) {
152-
const { duration, aspectRatio, resolution } = params
153-
const durations = settings?.durations?.map((value) => Number.parseFloat(String(value)))
154167
return {
155168
...params,
156-
duration: typeof duration === 'number' && durations?.includes(duration) ? duration : undefined,
157-
aspectRatio:
158-
typeof aspectRatio === 'string' && settings?.aspectRatios?.includes(aspectRatio)
159-
? aspectRatio
160-
: undefined,
161-
resolution:
162-
typeof resolution === 'string' && settings?.resolutions?.includes(resolution)
163-
? resolution
164-
: undefined,
169+
duration: dropStaleSetting(params.duration, settings?.durations, other?.durations, true),
170+
aspectRatio: dropStaleSetting(params.aspectRatio, settings?.aspectRatios, other?.aspectRatios),
171+
resolution: dropStaleSetting(params.resolution, settings?.resolutions, other?.resolutions),
165172
generateAudio: settings?.audioField ? params.generateAudio : undefined,
166173
promptOptimizer: settings?.promptOptimizer ? params.promptOptimizer : undefined,
167174
}
@@ -2029,7 +2036,11 @@ export const VideoGeneratorV3Block: BlockConfig = {
20292036
if (!model) return { ...falParams, visualReference: undefined }
20302037
const withImage = Boolean(model.image && falParams.visualReference)
20312038
return {
2032-
...keepFalSettingsAllowed(falParams, getFalVideoVariant(params.model, withImage)),
2039+
...keepFalSettingsAllowed(
2040+
falParams,
2041+
getFalVideoVariant(params.model, withImage),
2042+
getFalVideoVariant(params.model, !withImage)
2043+
),
20332044
visualReference: withImage ? falParams.visualReference : undefined,
20342045
}
20352046
}
@@ -2038,21 +2049,35 @@ export const VideoGeneratorV3Block: BlockConfig = {
20382049
? normalizeFileInput(params.visualReference, { single: true })
20392050
: undefined
20402051
const model = getDirectVideoModel(params.provider, params.model, Boolean(visualReference))
2052+
const other = getDirectVideoModel(params.provider, params.model, !visualReference)?.settings
20412053
const setting = (key: string, supported: unknown, fallbackKey?: string) => {
20422054
if (!supported) return undefined
20432055
const value = params[key] ?? (fallbackKey ? params[fallbackKey] : undefined)
20442056
return value === FAL_MODEL_DEFAULT.id ? undefined : value
20452057
}
2046-
const duration = setting('duration', model?.settings?.durations)
2058+
const duration = dropStaleSetting(
2059+
setting('duration', model?.settings?.durations),
2060+
model?.settings?.durations,
2061+
other?.durations,
2062+
true
2063+
)
20472064
return {
20482065
provider: params.provider,
20492066
model: params.model,
20502067
apiKey: params.apiKey,
20512068
prompt: params.prompt,
20522069
visualReference,
20532070
duration: duration ? Number(duration) : undefined,
2054-
aspectRatio: setting('aspectRatio', model?.settings?.aspectRatios),
2055-
resolution: setting('resolution', model?.settings?.resolutions),
2071+
aspectRatio: dropStaleSetting(
2072+
setting('aspectRatio', model?.settings?.aspectRatios),
2073+
model?.settings?.aspectRatios,
2074+
other?.aspectRatios
2075+
),
2076+
resolution: dropStaleSetting(
2077+
setting('resolution', model?.settings?.resolutions),
2078+
model?.settings?.resolutions,
2079+
other?.resolutions
2080+
),
20562081
generateAudio: parseOptionalBooleanInput(
20572082
setting('directGenerateAudio', model?.settings?.audio, 'generateAudio')
20582083
),

‎apps/sim/serializer/field-analysis.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,33 @@ describe('extractBlockParams', () => {
262262
}
263263
)
264264

265+
it('sends the model default for a Runway setting the start frame endpoint rejects', () => {
266+
svcConfig.value = VideoGeneratorV3Block
267+
const frameFile = { id: 'f', name: 'frame.png', url: '', size: 3, type: 'image/png', key: 'k' }
268+
const values = {
269+
provider: 'runway',
270+
apiKey: 'test-key',
271+
model: 'kling_4',
272+
prompt: 'A cinematic sunrise',
273+
aspectRatio: '1920:1080',
274+
visualReferenceInput: frameFile,
275+
}
276+
const params = extractBlockParams(
277+
block({
278+
type: 'svc',
279+
subBlocks: Object.fromEntries(
280+
Object.entries(values).map(([id, value]) => [id, { id, type: 'short-input', value }])
281+
),
282+
})
283+
)
284+
const mapped = VideoGeneratorV3Block.tools.config?.params?.(params)
285+
const request = buildDirectVideoRequest({
286+
...videoToolBodySchema.parse(mapped),
287+
provider: 'runway',
288+
})
289+
expect(request.body).not.toHaveProperty('ratio')
290+
})
291+
265292
it('resolves a canonical pair to its canonical id (advanced value wins when basic empty)', () => {
266293
svcConfig.value = config([
267294
{

0 commit comments

Comments
 (0)