diff --git a/src/ai-sdk/providers/varg.ts b/src/ai-sdk/providers/varg.ts index 6070c67b..d43d97b5 100644 --- a/src/ai-sdk/providers/varg.ts +++ b/src/ai-sdk/providers/varg.ts @@ -382,9 +382,23 @@ class VargVideoModel implements VideoModelV3 { } if (options.providerOptions?.varg) { - params.provider_options = checkVargProviderOptions( - options.providerOptions.varg as Record, - ); + const vargOpts = { + ...(options.providerOptions.varg as Record), + }; + // Unified varg-API fields riding the `varg` namespace are lifted to the + // request body top level — they are NOT provider_options. `audio` is the + // unified native-audio request (audio: "native" sugar): the API maps it + // per model via mapping_rules (rename audio → generate_audio) and models + // without the rule silently ignore it. Sending it inside + // provider_options.fal instead used to rely on the API silently + // stripping unknown keys, which now 422s. + if (vargOpts.audio !== undefined) { + params.audio = vargOpts.audio; + delete vargOpts.audio; + } + if (Object.keys(vargOpts).length > 0) { + params.provider_options = checkVargProviderOptions(vargOpts); + } } const result = await executeJob(this.baseUrl, this.apiKey, "video", params); diff --git a/src/react/elements.ts b/src/react/elements.ts index c5bbce77..ec3bde52 100644 --- a/src/react/elements.ts +++ b/src/react/elements.ts @@ -132,10 +132,15 @@ function attachAudioGetter< * IGNORED_PROPS_BY_TYPE) — the expanded providerOptions carry the * semantic difference, so pre-existing configurations keep their keys. * - * Provider mapping (verified against the varg API deep-merge behavior): + * Provider mapping: * - direct fal models → `providerOptions.fal.generate_audio` - * - varg gateway models → `providerOptions.varg.fal.generate_audio` - * (the API forwards `provider_options.fal.*` to the fal payload) + * - varg gateway models → `providerOptions.varg.audio` (the UNIFIED field — + * lifted to the request body top level by VargVideoModel and mapped per + * model by the API's mapping_rules; models without native-audio support + * silently ignore it). Do NOT inject provider_options.fal.generate_audio + * here: the API rejects unknown provider_options keys with a 422, so the + * old blind injection broke every fal video model whose schema lacks + * generate_audio. */ function expandNativeAudio(props: VideoProps): VideoProps { if (props.audio !== "native") return props; @@ -155,12 +160,10 @@ function expandNativeAudio(props: VideoProps): VideoProps { string, unknown >; - const falOpts = { ...((vargOpts.fal as object) ?? {}) } as Record< - string, - unknown - >; - if (falOpts.generate_audio === undefined) falOpts.generate_audio = true; - vargOpts.fal = falOpts; + // Unified native-audio request — best-effort by design: the varg API + // maps it to the provider's own flag on models that support it and + // silently drops it elsewhere (mapping_rules, migration 0147). + if (vargOpts.audio === undefined) vargOpts.audio = true; providerOptions.varg = vargOpts as Record; } else { // Default to the fal namespace (fal is the only provider with a diff --git a/src/react/tests/audio-element.test.ts b/src/react/tests/audio-element.test.ts index ea8bd888..f401819b 100644 --- a/src/react/tests/audio-element.test.ts +++ b/src/react/tests/audio-element.test.ts @@ -200,7 +200,12 @@ describe("audio: native sugar", () => { expect(po.fal?.generate_audio).toBe(true); }); - test("expands into varg.fal namespace for varg gateway models", () => { + test("expands to the unified varg.audio field for varg gateway models", () => { + // NOT provider_options.fal.generate_audio: the varg API rejects unknown + // provider_options keys with a 422 on models whose fal schema lacks the + // flag. The unified `audio` field is mapped per model by the API's + // mapping_rules (rename audio → generate_audio) and silently ignored by + // models without native-audio support. const vid = Video({ prompt: "sunset", audio: "native", @@ -213,9 +218,8 @@ describe("audio: native sugar", () => { string, Record >; - expect((po.varg?.fal as Record)?.generate_audio).toBe( - true, - ); + expect(po.varg?.audio).toBe(true); + expect(po.varg?.fal).toBeUndefined(); expect(vid.props.keepAudio).toBe(true); });