Skip to content

fix: normalize video duration per model rules via Zod schemas#194

Merged
SecurityQQ merged 1 commit intomainfrom
fix/normalize-provider-duration
Apr 4, 2026
Merged

fix: normalize video duration per model rules via Zod schemas#194
SecurityQQ merged 1 commit intomainfrom
fix/normalize-provider-duration

Conversation

@SecurityQQ
Copy link
Copy Markdown
Contributor

Summary

  • Adds model-rules.ts with per-model Zod schemas that validate and transform duration values via .transform() — rounding floats, clamping to valid ranges, and converting types
  • Replaces the 30-line duration if/else chain in fal.ts with a single normalizeProviderInput() call
  • Non-duration model defaults (Grok resolution, Sora delete_video) preserved alongside the new normalization

Problem

All 6 clips failed with: Input should be '3', '4', '5', ... '15' on fal-ai/kling-video/o3/pro/image-to-video.

The SDK was doing String(duration ?? 5) but never clamping range or rounding — floats like 2.34 from speech segments became "2.34" (rejected), and values outside [3,15] passed through.

Model rules defined

Model Duration rule Output type
kling-v3, kling-v3-standard, kling-v2.6 Clamp [3, 15], round string
kling-v2.5, kling-v2.1, kling-v2 Snap to nearest [5, 10] number
wan-2.5, wan-2.5-preview Snap to nearest [5, 10] number
minimax Round to integer number
grok-imagine Clamp [1, 15], round number
sora-2, sora-2-pro Snap to nearest [4, 8, 12, 16, 20] number
seedance-2-* Snap to nearest [5, 10, 15] number
Unknown models Passthrough original

Companion gateway PR: https://github.com/vargHQ/gateway/pull/new/fix/normalize-provider-duration

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1849c6c4-9339-4ccf-8307-45ee722139ea

📥 Commits

Reviewing files that changed from the base of the PR and between 94fc438 and 0bea8a1.

📒 Files selected for processing (2)
  • src/ai-sdk/providers/fal.ts
  • src/ai-sdk/providers/model-rules.ts

📝 Walkthrough

walkthrough

refactored fal provider by extracting duration normalization into a new model-rules module; fal now calls normalizeProviderInput(modelId, { duration }) for standard video models and preserves non-duration defaults (grok resolution, sora delete_video).

changes

Cohort / File(s) Summary
model rules module
src/ai-sdk/providers/model-rules.ts
new zod-based module defining per-model duration schemas and helpers (rounding, clamping, snapping, kling string conversion). exports normalizeProviderInput(model, input) and ModelDurationRules.
provider integration (fal)
src/ai-sdk/providers/fal.ts
removed inline duration branches for kling/grok/sora and unified duration handling via normalizeProviderInput. retained non-duration defaults: grok resolution -> "720p", sora delete_video -> false.

sequence diagram(s)

(skip — changes are modular and don't introduce a new multi-component flow)

estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

poem

schemas click, durations align — meow
kling counts, grok settles at seven-twenty
sora nods and keeps the delete flag low
one normalize to rule them all ✨
refactor purrs, code sleeps soundly

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed title clearly summarizes the main change: normalizing video duration using zod schemas, with references to model rules and the primary fix.
Description check ✅ Passed description is well-related to the changeset, explaining the problem, solution, model rules table, and specific duration handling for each model variant.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/normalize-provider-duration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ai-sdk/providers/model-rules.ts`:
- Around line 68-100: ModelInputSchemas is missing entries for several Falcon
video model variants so those IDs will bypass validation; add explicit schema
rules for "grok-imagine-edit", "grok-imagine-image", "grok-imagine-image/edit",
"kling-v2.6-motion", "kling-v2.6-motion-standard", and "sora-2-remix" into the
ModelInputSchemas map (or implement a clear fallback rule at the top of the map)
using the same helper validators used elsewhere (stringIntDuration,
enumDuration, rangeDuration, intDuration) to mirror the appropriate existing
variants (e.g., match "grok-imagine" rules for grok-imagine-* variants, match
"kling-v2.6" for kling-v2.6-motion*, and match "sora-2" for sora-2-remix) so
these IDs are validated consistently by the lookup.
- Around line 116-127: normalizeProviderInput currently silently applies schema
coercions via ModelInputSchemas and schema.safeParse, losing the previous
warning behavior; update normalizeProviderInput to detect when the
parsed/normalized value differs from the original (e.g., compare input.duration
to result.data.duration) and return the normalized input together with a
warnings array (or attach a warnings property) describing adjustments (for
example: "Duration Xs not supported. Using Ys"); ensure the function signature
and callers that consume normalizeProviderInput are updated to accept { input:
Record<string, unknown>, warnings?: string[] } (or similar) and emit a warning
only when the schema clamps or coerces values so callers regain observability of
changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38e54a08-c86b-4122-8723-1b53b644d8f6

📥 Commits

Reviewing files that changed from the base of the PR and between e236651 and 94fc438.

📒 Files selected for processing (2)
  • src/ai-sdk/providers/fal.ts
  • src/ai-sdk/providers/model-rules.ts

Comment on lines +68 to +100
const ModelInputSchemas: Record<string, z.ZodType> = {
// Kling O3 (v3): fal expects string integer "3"–"15"
"kling-v3": z.object({ duration: stringIntDuration(3, 15, 5) }),
"kling-v3-standard": z.object({ duration: stringIntDuration(3, 15, 5) }),

// Kling v2.6: same rules as v3
"kling-v2.6": z.object({ duration: stringIntDuration(3, 15, 5) }),

// Kling legacy: exactly 5 or 10
"kling-v2.5": z.object({ duration: enumDuration([5, 10], 5) }),
"kling-v2.1": z.object({ duration: enumDuration([5, 10], 5) }),
"kling-v2": z.object({ duration: enumDuration([5, 10], 5) }),

// Wan: 5 or 10
"wan-2.5": z.object({ duration: enumDuration([5, 10], 5) }),
"wan-2.5-preview": z.object({ duration: enumDuration([5, 10], 5) }),

// Minimax: round to integer
minimax: z.object({ duration: intDuration(5) }),

// Grok Imagine: integer 1–15
"grok-imagine": z.object({ duration: rangeDuration(1, 15, 6) }),

// Sora 2: only 4, 8, 12, 16, 20
"sora-2": z.object({ duration: enumDuration([4, 8, 12, 16, 20], 4) }),
"sora-2-pro": z.object({ duration: enumDuration([4, 8, 12, 16, 20], 4) }),

// Seedance (piapi): 5, 10, or 15
"seedance-2-preview": z.object({ duration: enumDuration([5, 10, 15], 5) }),
"seedance-2-fast-preview": z.object({
duration: enumDuration([5, 10, 15], 5),
}),
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# check if VIDEO_MODELS keys in fal.ts are all covered in ModelInputSchemas
# extract model ids from both files and compare

echo "=== VIDEO_MODELS in fal.ts ==="
rg -o '"[^"]+":' src/ai-sdk/providers/fal.ts | head -50 | grep -E 'kling|wan|minimax|grok|sora|seedance' | sort -u

echo ""
echo "=== ModelInputSchemas keys ==="
rg -o '"[^"]+":|^  [a-z].*:' src/ai-sdk/providers/model-rules.ts | grep -v '//' | sort -u

Repository: vargHQ/sdk

Length of output: 712


add schema definitions for missing model variants - the lookup doesn't cover all video models from fal.ts. these ids are missing from ModelInputSchemas and will skip validation:

  • grok-imagine-edit
  • grok-imagine-image
  • grok-imagine-image/edit
  • kling-v2.6-motion
  • kling-v2.6-motion-standard
  • sora-2-remix

add explicit schema rules for each or define a fallback strategy meow

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai-sdk/providers/model-rules.ts` around lines 68 - 100, ModelInputSchemas
is missing entries for several Falcon video model variants so those IDs will
bypass validation; add explicit schema rules for "grok-imagine-edit",
"grok-imagine-image", "grok-imagine-image/edit", "kling-v2.6-motion",
"kling-v2.6-motion-standard", and "sora-2-remix" into the ModelInputSchemas map
(or implement a clear fallback rule at the top of the map) using the same helper
validators used elsewhere (stringIntDuration, enumDuration, rangeDuration,
intDuration) to mirror the appropriate existing variants (e.g., match
"grok-imagine" rules for grok-imagine-* variants, match "kling-v2.6" for
kling-v2.6-motion*, and match "sora-2" for sora-2-remix) so these IDs are
validated consistently by the lookup.

Comment on lines +116 to +127
export function normalizeProviderInput(
model: string,
input: Record<string, unknown>,
): Record<string, unknown> {
const schema = ModelInputSchemas[model];
if (!schema) return input;

const result = schema.safeParse({ duration: input.duration });
if (!result.success) return input;

return { ...input, ...(result.data as Record<string, unknown>) };
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

silent normalization loses observability from previous implementation

the old openai.ts and fal.ts code pushed warnings when duration was invalid or clamped (e.g., "Duration ${duration}s not supported. Using ${validSeconds}s"). this new approach silently transforms values without any indication to the caller.

if someone passes duration: 2.5 to kling-v3, it silently becomes "3" with no warning. might want to consider returning warnings alongside the normalized input for cases where values were adjusted.

💡 possible approach to add observability
+interface NormalizeResult {
+  input: Record<string, unknown>;
+  warnings: string[];
+}
+
 export function normalizeProviderInput(
   model: string,
   input: Record<string, unknown>,
-): Record<string, unknown> {
+): NormalizeResult {
   const schema = ModelInputSchemas[model];
-  if (!schema) return input;
+  if (!schema) return { input, warnings: [] };

   const result = schema.safeParse({ duration: input.duration });
-  if (!result.success) return input;
+  if (!result.success) return { input, warnings: [] };

-  return { ...input, ...(result.data as Record<string, unknown>) };
+  const normalized = { ...input, ...(result.data as Record<string, unknown>) };
+  const warnings: string[] = [];
+  if (input.duration !== undefined && normalized.duration !== input.duration) {
+    warnings.push(`Duration ${input.duration} normalized to ${normalized.duration} for ${model}`);
+  }
+  return { input: normalized, warnings };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai-sdk/providers/model-rules.ts` around lines 116 - 127,
normalizeProviderInput currently silently applies schema coercions via
ModelInputSchemas and schema.safeParse, losing the previous warning behavior;
update normalizeProviderInput to detect when the parsed/normalized value differs
from the original (e.g., compare input.duration to result.data.duration) and
return the normalized input together with a warnings array (or attach a warnings
property) describing adjustments (for example: "Duration Xs not supported. Using
Ys"); ensure the function signature and callers that consume
normalizeProviderInput are updated to accept { input: Record<string, unknown>,
warnings?: string[] } (or similar) and emit a warning only when the schema
clamps or coerces values so callers regain observability of changes.

Replace the scattered duration if/else chain in fal.ts with a
centralised normalizeProviderInput() backed by per-model Zod schemas.
Each model gets a .transform() schema that clamps, rounds, and
type-converts duration values automatically.

Fixes kling-v3/v2.6 failures when duration is a float (from speech
segments) or outside the [3,15] range.
@SecurityQQ SecurityQQ force-pushed the fix/normalize-provider-duration branch from 94fc438 to 0bea8a1 Compare April 4, 2026 00:07
@SecurityQQ SecurityQQ merged commit 2652614 into main Apr 4, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant