diff --git a/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts b/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts index 2bc3e0cf0af..cad9512f1f9 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts @@ -64,6 +64,7 @@ vi.mock('@ai-sdk/google', () => ({ provider: 'google', modelId: model, apiKey: opts?.apiKey, + baseURL: opts?.baseURL, fetch: opts?.fetch, specificationVersion: 'v3', }), @@ -520,6 +521,41 @@ describe('createModel', () => { }); }); + describe('google baseURL normalization', () => { + const baseURLFor = (creds: Record) => + ( + createModel({ + id: 'google/gemini-3.7-flash', + apiKey: 'g-test', + ...creds, + }) as unknown as Record + ).baseURL; + + // `googlePalmApi.host` defaults to the bare host, which drops the API + // version from every request path — Google then 404s for any model. + it('appends the API version to the credential host', () => { + expect(baseURLFor({ url: 'https://generativelanguage.googleapis.com' })).toBe( + 'https://generativelanguage.googleapis.com/v1beta', + ); + }); + + it('leaves a baseURL that already targets the API version unchanged', () => { + expect(baseURLFor({ url: 'https://generativelanguage.googleapis.com/v1beta' })).toBe( + 'https://generativelanguage.googleapis.com/v1beta', + ); + }); + + it('appends to a proxy host', () => { + expect(baseURLFor({ url: 'https://proxy.example/gemini' })).toBe( + 'https://proxy.example/gemini/v1beta', + ); + }); + + it('leaves the SDK default in place when no host is configured', () => { + expect(baseURLFor({})).toBeUndefined(); + }); + }); + describe('alibaba baseURL normalization', () => { const baseURLFor = (creds: Record) => ( diff --git a/packages/@n8n/agents/src/runtime/model/model-factory.ts b/packages/@n8n/agents/src/runtime/model/model-factory.ts index ab6acf3bd30..23172e7abb3 100644 --- a/packages/@n8n/agents/src/runtime/model/model-factory.ts +++ b/packages/@n8n/agents/src/runtime/model/model-factory.ts @@ -214,7 +214,15 @@ const LANGUAGE_PROVIDERS: ProviderRegistry = { google: { build: (creds, model, fetch) => { const { createGoogle } = require('@ai-sdk/google') as typeof import('@ai-sdk/google'); - return createGoogle({ ...creds, fetch })(model); + // The SDK expects a version-qualified base (its own default ends in + // `/v1beta`), but `googlePalmApi.host` stores the bare host — the Gemini + // node's SDK appends the API version itself. Passing the host through + // unqualified drops the version from every request path, and Google + // answers 404 for any model. + const normalizedBaseURL = creds.baseURL + ? ensureUrlPathSuffix(creds.baseURL, '/v1beta') + : creds.baseURL; + return createGoogle({ ...creds, baseURL: normalizedBaseURL, fetch })(model); }, }, xai: {