Skip to content

Commit 89be0a6

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 9b44642 commit 89be0a6

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
}
@@ -2007,7 +2014,11 @@ export const VideoGeneratorV3Block: BlockConfig = {
20072014
if (!model) return { ...falParams, visualReference: undefined }
20082015
const withImage = Boolean(model.image && falParams.visualReference)
20092016
return {
2010-
...keepFalSettingsAllowed(falParams, getFalVideoVariant(params.model, withImage)),
2017+
...keepFalSettingsAllowed(
2018+
falParams,
2019+
getFalVideoVariant(params.model, withImage),
2020+
getFalVideoVariant(params.model, !withImage)
2021+
),
20112022
visualReference: withImage ? falParams.visualReference : undefined,
20122023
}
20132024
}
@@ -2016,21 +2027,35 @@ export const VideoGeneratorV3Block: BlockConfig = {
20162027
? normalizeFileInput(params.visualReference, { single: true })
20172028
: undefined
20182029
const model = getDirectVideoModel(params.provider, params.model, Boolean(visualReference))
2030+
const other = getDirectVideoModel(params.provider, params.model, !visualReference)?.settings
20192031
const setting = (key: string, supported: unknown, fallbackKey?: string) => {
20202032
if (!supported) return undefined
20212033
const value = params[key] ?? (fallbackKey ? params[fallbackKey] : undefined)
20222034
return value === FAL_MODEL_DEFAULT.id ? undefined : value
20232035
}
2024-
const duration = setting('duration', model?.settings?.durations)
2036+
const duration = dropStaleSetting(
2037+
setting('duration', model?.settings?.durations),
2038+
model?.settings?.durations,
2039+
other?.durations,
2040+
true
2041+
)
20252042
return {
20262043
provider: params.provider,
20272044
model: params.model,
20282045
apiKey: params.apiKey,
20292046
prompt: params.prompt,
20302047
visualReference,
20312048
duration: duration ? Number(duration) : undefined,
2032-
aspectRatio: setting('aspectRatio', model?.settings?.aspectRatios),
2033-
resolution: setting('resolution', model?.settings?.resolutions),
2049+
aspectRatio: dropStaleSetting(
2050+
setting('aspectRatio', model?.settings?.aspectRatios),
2051+
model?.settings?.aspectRatios,
2052+
other?.aspectRatios
2053+
),
2054+
resolution: dropStaleSetting(
2055+
setting('resolution', model?.settings?.resolutions),
2056+
model?.settings?.resolutions,
2057+
other?.resolutions
2058+
),
20342059
generateAudio: parseOptionalBooleanInput(
20352060
setting('directGenerateAudio', model?.settings?.audio, 'generateAudio')
20362061
),

‎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)