From 0517f7f18bfd8f65f0d022a9c64ab944fc566802 Mon Sep 17 00:00:00 2001 From: ALESSIO ATTTILIO Date: Tue, 30 Jun 2026 08:37:59 +0200 Subject: [PATCH 1/2] fix (gateway): forward non-gateway provider options under gateway.providerOptions for fallback model support --- .../src/gateway-language-model.test.ts | 136 ++++++++++++++++++ .../gateway/src/gateway-language-model.ts | 25 ++++ .../gateway/src/gateway-provider-options.ts | 3 + 3 files changed, 164 insertions(+) diff --git a/packages/gateway/src/gateway-language-model.test.ts b/packages/gateway/src/gateway-language-model.test.ts index 679e3ddb8231..413dc5df5cd7 100644 --- a/packages/gateway/src/gateway-language-model.test.ts +++ b/packages/gateway/src/gateway-language-model.test.ts @@ -1652,6 +1652,142 @@ describe('GatewayLanguageModel', () => { }); }); + it('should nest non-gateway provider options under gateway.providerOptions for doGenerate', async () => { + prepareJsonResponse({ + content: { type: 'text', text: 'Test response' }, + }); + + await createTestModel().doGenerate({ + prompt: TEST_PROMPT, + providerOptions: { + google: { + thinkingConfig: { + thinkingBudget: 128, + includeThoughts: false, + }, + }, + gateway: { + models: ['google/gemini-2.5-flash', 'openai/gpt-5-mini'], + }, + }, + }); + + const requestBody = await server.calls[0].requestBodyJson; + expect(requestBody.providerOptions).toEqual({ + google: { + thinkingConfig: { + thinkingBudget: 128, + includeThoughts: false, + }, + }, + gateway: { + models: ['google/gemini-2.5-flash', 'openai/gpt-5-mini'], + providerOptions: { + google: { + thinkingConfig: { + thinkingBudget: 128, + includeThoughts: false, + }, + }, + }, + }, + }); + }); + + it('should nest non-gateway provider options under gateway.providerOptions for doStream', async () => { + prepareStreamResponse({ + content: ['Hello', ' world'], + }); + + const { stream } = await createTestModel().doStream({ + prompt: TEST_PROMPT, + providerOptions: { + anthropic: { + cacheControl: { type: 'ephemeral' }, + }, + gateway: { + order: ['bedrock', 'anthropic'], + }, + }, + }); + + await convertReadableStreamToArray(stream); + + const requestBody = await server.calls[0].requestBodyJson; + expect(requestBody.providerOptions).toEqual({ + anthropic: { + cacheControl: { type: 'ephemeral' }, + }, + gateway: { + order: ['bedrock', 'anthropic'], + providerOptions: { + anthropic: { + cacheControl: { type: 'ephemeral' }, + }, + }, + }, + }); + }); + + it('should not modify providerOptions when no non-gateway options exist', async () => { + prepareJsonResponse({ + content: { type: 'text', text: 'Test response' }, + }); + + await createTestModel().doGenerate({ + prompt: TEST_PROMPT, + providerOptions: { + gateway: { + order: ['openai'], + zeroDataRetention: true, + }, + }, + }); + + const requestBody = await server.calls[0].requestBodyJson; + expect(requestBody.providerOptions).toEqual({ + gateway: { + order: ['openai'], + zeroDataRetention: true, + }, + }); + expect( + (requestBody.providerOptions as Record).gateway, + ).not.toHaveProperty('providerOptions'); + }); + + it('should preserve original top-level non-gateway options for backward compatibility', async () => { + prepareJsonResponse({ + content: { type: 'text', text: 'Test response' }, + }); + + await createTestModel().doGenerate({ + prompt: TEST_PROMPT, + providerOptions: { + google: { + thinkingConfig: { + thinkingBudget: 128, + includeThoughts: false, + }, + }, + gateway: { + models: ['google/gemini-2.5-flash'], + }, + }, + }); + + const requestBody = await server.calls[0].requestBodyJson; + + expect( + (requestBody.providerOptions as Record).google, + ).toEqual({ + thinkingConfig: { + thinkingBudget: 128, + includeThoughts: false, + }, + }); + }); + it('should pass both zeroDataRetention and hipaaCompliant options', async () => { prepareJsonResponse({ content: { type: 'text', text: 'Test response' }, diff --git a/packages/gateway/src/gateway-language-model.ts b/packages/gateway/src/gateway-language-model.ts index 0848a5fa9b16..ca2ced6ab1ae 100644 --- a/packages/gateway/src/gateway-language-model.ts +++ b/packages/gateway/src/gateway-language-model.ts @@ -4,6 +4,7 @@ import type { LanguageModelV4StreamPart, LanguageModelV4GenerateResult, LanguageModelV4StreamResult, + JSONObject, } from '@ai-sdk/provider'; import { combineHeaders, @@ -59,6 +60,30 @@ export class GatewayLanguageModel implements LanguageModelV4 { private async getArgs(options: LanguageModelV4CallOptions) { const { abortSignal: _abortSignal, ...optionsWithoutSignal } = options; + if (optionsWithoutSignal.providerOptions) { + const { gateway: gatewayOpts, ...otherProviderOpts } = + optionsWithoutSignal.providerOptions; + + if ( + gatewayOpts != null && + typeof gatewayOpts === 'object' && + !Array.isArray(gatewayOpts) + ) { + const updatedGateway: Record = { + ...(gatewayOpts as Record), + }; + + if (Object.keys(otherProviderOpts).length > 0) { + updatedGateway['providerOptions'] = otherProviderOpts; + + optionsWithoutSignal.providerOptions = { + ...optionsWithoutSignal.providerOptions, + gateway: updatedGateway as JSONObject, + }; + } + } + } + return { args: this.maybeEncodeFileParts(optionsWithoutSignal), warnings: [], diff --git a/packages/gateway/src/gateway-provider-options.ts b/packages/gateway/src/gateway-provider-options.ts index 72ec2c6615e9..9ba3409e0c80 100644 --- a/packages/gateway/src/gateway-provider-options.ts +++ b/packages/gateway/src/gateway-provider-options.ts @@ -55,4 +55,7 @@ export type GatewayProviderOptions = { /** Filter to providers with zero data retention agreements. */ zeroDataRetention?: boolean; + + /** Provider options keyed by provider name, used for fallback model support. */ + providerOptions?: Record>; }; From e2c9166e275c06a72b35ada5a8830dea1fbb7829 Mon Sep 17 00:00:00 2001 From: ALESSIO ATTTILIO Date: Tue, 30 Jun 2026 08:40:09 +0200 Subject: [PATCH 2/2] add changeset --- .changeset/sixty-suns-mix.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sixty-suns-mix.md diff --git a/.changeset/sixty-suns-mix.md b/.changeset/sixty-suns-mix.md new file mode 100644 index 000000000000..e0d3643f323b --- /dev/null +++ b/.changeset/sixty-suns-mix.md @@ -0,0 +1,5 @@ +--- +"@ai-sdk/gateway": patch +--- + +fix (gateway): forward non-gateway provider options under gateway.providerOptions for fallback support