diff --git a/packages/cli/src/services/__tests__/ai-gateway.service.test.ts b/packages/cli/src/services/__tests__/ai-gateway.service.test.ts index b16e98c4faf..a9f411eaa96 100644 --- a/packages/cli/src/services/__tests__/ai-gateway.service.test.ts +++ b/packages/cli/src/services/__tests__/ai-gateway.service.test.ts @@ -10,6 +10,9 @@ import type { License } from '@/license'; import { AiGatewayService } from '@/services/ai-gateway.service'; import type { Project, User, UserRepository } from '@n8n/db'; import type { OwnershipService } from '@/services/ownership.service'; +import type { UrlService } from '@/services/url.service'; + +const INSTANCE_BASE_URL = 'https://my-n8n.example.com'; const BASE_URL = 'http://gateway.test'; const INSTANCE_ID = 'test-instance-id'; @@ -34,6 +37,9 @@ function makeService({ isAiGatewayLicensed = true, ownershipService = mock(), userRepository = mock({ findOneBy: jest.fn().mockResolvedValue(null) }), + urlService = mock({ + getInstanceBaseUrl: jest.fn().mockReturnValue(INSTANCE_BASE_URL), + }), } = {}) { const globalConfig = { aiAssistant: { baseUrl: baseUrl ?? undefined }, @@ -53,6 +59,7 @@ function makeService({ instanceSettings, ownershipService, userRepository, + urlService, ); } @@ -189,11 +196,21 @@ describe('AiGatewayService', () => { 'x-n8n-version': N8N_VERSION, 'x-instance-id': INSTANCE_ID, }, - body: JSON.stringify({ licenseCert: LICENSE_CERT }), + body: JSON.stringify({ licenseCert: LICENSE_CERT, instanceUrl: INSTANCE_BASE_URL }), }), ); }); + it('includes instanceUrl in token body', async () => { + mockConfigThenToken(fetchMock); + const service = makeService(); + + await service.getSyntheticCredential({ credentialType: 'googlePalmApi', userId: USER_ID }); + + const body = JSON.parse(fetchMock.mock.calls[1][1].body as string); + expect(body.instanceUrl).toBe(INSTANCE_BASE_URL); + }); + it('includes userEmail and userName in token body when user exists', async () => { const userRepository = mock({ findOneBy: jest @@ -215,6 +232,7 @@ describe('AiGatewayService', () => { licenseCert: LICENSE_CERT, userEmail: 'alice@example.com', userName: 'Alice Smith', + instanceUrl: INSTANCE_BASE_URL, }), }), ); @@ -245,7 +263,7 @@ describe('AiGatewayService', () => { await service.getSyntheticCredential({ credentialType: 'googlePalmApi', userId: USER_ID }); const body = JSON.parse(fetchMock.mock.calls[1][1].body as string); - expect(body).toEqual({ licenseCert: LICENSE_CERT }); + expect(body).toEqual({ licenseCert: LICENSE_CERT, instanceUrl: INSTANCE_BASE_URL }); }); it('caches config and token — second call makes no additional fetches', async () => { @@ -416,7 +434,7 @@ describe('AiGatewayService', () => { `${BASE_URL}/v1/gateway/credentials`, expect.objectContaining({ method: 'POST', - body: JSON.stringify({ licenseCert: LICENSE_CERT }), + body: JSON.stringify({ licenseCert: LICENSE_CERT, instanceUrl: INSTANCE_BASE_URL }), }), ); expect(fetchMock).toHaveBeenNthCalledWith(2, `${BASE_URL}/v1/gateway/wallet`, { diff --git a/packages/cli/src/services/ai-gateway.service.ts b/packages/cli/src/services/ai-gateway.service.ts index 171d0957ceb..04d442cd7de 100644 --- a/packages/cli/src/services/ai-gateway.service.ts +++ b/packages/cli/src/services/ai-gateway.service.ts @@ -12,6 +12,7 @@ import { N8N_VERSION, AI_ASSISTANT_SDK_VERSION } from '@/constants'; import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error'; import { License } from '@/license'; import { OwnershipService } from '@/services/ownership.service'; +import { UrlService } from '@/services/url.service'; interface GatewayTokenResponse { token: string; @@ -44,6 +45,7 @@ export class AiGatewayService { private readonly instanceSettings: InstanceSettings, private readonly ownershipService: OwnershipService, private readonly userRepository: UserRepository, + private readonly urlService: UrlService, ) {} /** @@ -273,6 +275,7 @@ export class AiGatewayService { ...(user && { userName: [user.firstName, user.lastName].filter(Boolean).join(' ') || undefined, }), + instanceUrl: this.urlService.getInstanceBaseUrl(), }), });