From 9fc867ce47ba5e10e653c615a44ed13b24d808df Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:17:10 +0200 Subject: [PATCH] chore(ai-builder): Rollout builder experiment, removing PostHog flags (#21079) Co-authored-by: Claude --- .../src/ai-workflow-builder-agent.service.ts | 37 +--- .../ai-workflow-builder-agent.service.test.ts | 33 --- .../src/test/workflow-builder-agent.test.ts | 3 - .../src/workflow-builder-agent.ts | 6 - .../ai/__tests__/ai-build-request.dto.test.ts | 17 -- .../src/dto/ai/ai-build-request.dto.ts | 1 - .../__tests__/ai.controller.test.ts | 2 - packages/cli/src/controllers/ai.controller.ts | 3 +- .../frontend/editor-ui/src/api/ai.test.ts | 96 +-------- packages/frontend/editor-ui/src/api/ai.ts | 2 - .../editor-ui/src/constants/experiments.ts | 14 -- .../ai/assistant/builder.store.test.ts | 194 ++---------------- .../features/ai/assistant/builder.store.ts | 40 +--- 13 files changed, 39 insertions(+), 409 deletions(-) diff --git a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts index fcc765137da..664c69b97f5 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts @@ -49,28 +49,18 @@ export class AiWorkflowBuilderService { }); } - private async getApiProxyAuthHeaders(user: IUser, useDeprecatedCredentials = false) { + private async getApiProxyAuthHeaders(user: IUser) { assert(this.client); - let authHeaders: { Authorization: string }; - - if (useDeprecatedCredentials) { - const authResponse = await this.client.generateApiProxyCredentials(user); - authHeaders = { Authorization: authResponse.apiKey }; - } else { - const authResponse = await this.client.getBuilderApiProxyToken(user); - authHeaders = { - Authorization: `${authResponse.tokenType} ${authResponse.accessToken}`, - }; - } + const authResponse = await this.client.getBuilderApiProxyToken(user); + const authHeaders = { + Authorization: `${authResponse.tokenType} ${authResponse.accessToken}`, + }; return authHeaders; } - private async setupModels( - user: IUser, - useDeprecatedCredentials = false, - ): Promise<{ + private async setupModels(user: IUser): Promise<{ anthropicClaude: ChatAnthropic; tracingClient?: TracingClient; authHeaders?: { Authorization: string }; @@ -78,7 +68,7 @@ export class AiWorkflowBuilderService { try { // If client is provided, use it for API proxy if (this.client) { - const authHeaders = await this.getApiProxyAuthHeaders(user, useDeprecatedCredentials); + const authHeaders = await this.getApiProxyAuthHeaders(user); // Extract baseUrl from client configuration const baseUrl = this.client.getApiProxyBaseUrl(); @@ -154,11 +144,8 @@ export class AiWorkflowBuilderService { }); } - private async getAgent(user: IUser, useDeprecatedCredentials = false) { - const { anthropicClaude, tracingClient, authHeaders } = await this.setupModels( - user, - useDeprecatedCredentials, - ); + private async getAgent(user: IUser) { + const { anthropicClaude, tracingClient, authHeaders } = await this.setupModels(user); const agent = new WorkflowBuilderAgent({ parsedNodeTypes: this.parsedNodeTypes, @@ -172,9 +159,7 @@ export class AiWorkflowBuilderService { : undefined, instanceUrl: this.instanceUrl, onGenerationSuccess: async () => { - if (!useDeprecatedCredentials) { - await this.onGenerationSuccess(user, authHeaders); - } + await this.onGenerationSuccess(user, authHeaders); }, }); @@ -204,7 +189,7 @@ export class AiWorkflowBuilderService { } async *chat(payload: ChatPayload, user: IUser, abortSignal?: AbortSignal) { - const agent = await this.getAgent(user, payload.useDeprecatedCredentials); + const agent = await this.getAgent(user); for await (const output of agent.chat(payload, user?.id?.toString(), abortSignal)) { yield output; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts index 7240b6754d8..533168ff660 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/test/ai-workflow-builder-agent.service.test.ts @@ -278,7 +278,6 @@ describe('AiWorkflowBuilderService', () => { workflowContext: { currentWorkflow: { id: 'test-workflow' }, }, - useDeprecatedCredentials: false, }; }); @@ -308,19 +307,6 @@ describe('AiWorkflowBuilderService', () => { ); }); - it('should handle deprecated credentials', async () => { - const payloadWithDeprecatedCredentials = { - ...mockPayload, - useDeprecatedCredentials: true, - }; - - const generator = service.chat(payloadWithDeprecatedCredentials, mockUser); - await generator.next(); - - // Verify that the deprecated credentials flow was used - expect(mockClient.generateApiProxyCredentials).toHaveBeenCalledWith(mockUser); - }); - it('should create WorkflowBuilderAgent with correct configuration when using client', async () => { const generator = service.chat(mockPayload, mockUser); await generator.next(); @@ -439,24 +425,6 @@ describe('AiWorkflowBuilderService', () => { // Verify callback was called with correct parameters expect(mockOnCreditsUpdated).toHaveBeenCalledWith('test-user-id', 10, 1); }); - - it('should not call markBuilderSuccess when using deprecated credentials', async () => { - const payloadWithDeprecatedCredentials = { - ...mockPayload, - useDeprecatedCredentials: true, - }; - - const generator = service.chat(payloadWithDeprecatedCredentials, mockUser); - await generator.next(); - - const config = MockedWorkflowBuilderAgent.mock.calls[0][0]; - - // Call the onGenerationSuccess callback - await config.onGenerationSuccess!(); - - // Should not call markBuilderSuccess for deprecated credentials - expect(mockClient.markBuilderSuccess).not.toHaveBeenCalled(); - }); }); describe('getSessions', () => { @@ -580,7 +548,6 @@ describe('AiWorkflowBuilderService', () => { workflowContext: { currentWorkflow: { id: workflowId }, }, - useDeprecatedCredentials: false, }; // First, simulate a chat session diff --git a/packages/@n8n/ai-workflow-builder.ee/src/test/workflow-builder-agent.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/test/workflow-builder-agent.test.ts index 696d2f50991..99dd5ae3dc7 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/test/workflow-builder-agent.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/test/workflow-builder-agent.test.ts @@ -143,7 +143,6 @@ describe('WorkflowBuilderAgent', () => { workflowContext: { currentWorkflow: { id: 'workflow-123' }, }, - useDeprecatedCredentials: false, }; }); @@ -151,7 +150,6 @@ describe('WorkflowBuilderAgent', () => { const longMessage = 'x'.repeat(MAX_AI_BUILDER_PROMPT_LENGTH + 1); const payload: ChatPayload = { message: longMessage, - useDeprecatedCredentials: false, }; await expect(async () => { @@ -169,7 +167,6 @@ describe('WorkflowBuilderAgent', () => { const validMessage = 'Create a simple workflow'; const payload: ChatPayload = { message: validMessage, - useDeprecatedCredentials: false, }; // Mock the stream processing to return a proper StreamOutput diff --git a/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts b/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts index c616578ba58..3b7b59eb62d 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts @@ -66,12 +66,6 @@ export interface ChatPayload { executionData?: IRunExecutionData['resultData']; expressionValues?: Record; }; - /** - * Calls AI Assistant Service using deprecated credentials and endpoints - * These credentials/endpoints will soon be removed - * As new implementation is rolled out and builder experiment is released - */ - useDeprecatedCredentials?: boolean; } export class WorkflowBuilderAgent { diff --git a/packages/@n8n/api-types/src/dto/ai/__tests__/ai-build-request.dto.test.ts b/packages/@n8n/api-types/src/dto/ai/__tests__/ai-build-request.dto.test.ts index 087e8034d29..e31edc8ff7b 100644 --- a/packages/@n8n/api-types/src/dto/ai/__tests__/ai-build-request.dto.test.ts +++ b/packages/@n8n/api-types/src/dto/ai/__tests__/ai-build-request.dto.test.ts @@ -12,7 +12,6 @@ describe('AiBuilderChatRequestDto', () => { connections: {}, }, }, - useDeprecatedCredentials: false, }, }; @@ -316,21 +315,5 @@ describe('AiBuilderChatRequestDto', () => { expect(result.success).toBe(false); }); - - it('should validate when useDeprecatedCredentials is explicitly set', () => { - const requestWithFlag = { - payload: { - ...validBasePayload.payload, - useDeprecatedCredentials: true, - }, - }; - - const result = AiBuilderChatRequestDto.safeParse(requestWithFlag); - - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.payload.useDeprecatedCredentials).toBe(true); - } - }); }); }); diff --git a/packages/@n8n/api-types/src/dto/ai/ai-build-request.dto.ts b/packages/@n8n/api-types/src/dto/ai/ai-build-request.dto.ts index 1e740fe8654..996ecc28d6f 100644 --- a/packages/@n8n/api-types/src/dto/ai/ai-build-request.dto.ts +++ b/packages/@n8n/api-types/src/dto/ai/ai-build-request.dto.ts @@ -57,6 +57,5 @@ export class AiBuilderChatRequestDto extends Z.class({ }) .optional(), }), - useDeprecatedCredentials: z.boolean().default(false), }), }) {} diff --git a/packages/cli/src/controllers/__tests__/ai.controller.test.ts b/packages/cli/src/controllers/__tests__/ai.controller.test.ts index 552d262a19f..7dd17f2a83c 100644 --- a/packages/cli/src/controllers/__tests__/ai.controller.test.ts +++ b/packages/cli/src/controllers/__tests__/ai.controller.test.ts @@ -124,7 +124,6 @@ describe('AiController', () => { workflowContext: { currentWorkflow: { id: 'workflow123' }, }, - useDeprecatedCredentials: false, }, }; @@ -153,7 +152,6 @@ describe('AiController', () => { executionData: undefined, executionSchema: undefined, }, - useDeprecatedCredentials: false, }, request.user, expect.any(AbortSignal), diff --git a/packages/cli/src/controllers/ai.controller.ts b/packages/cli/src/controllers/ai.controller.ts index 371169e0684..18ad54a22eb 100644 --- a/packages/cli/src/controllers/ai.controller.ts +++ b/packages/cli/src/controllers/ai.controller.ts @@ -55,7 +55,7 @@ export class AiController { res.on('close', handleClose); - const { text, workflowContext, useDeprecatedCredentials } = payload.payload; + const { text, workflowContext } = payload.payload; const aiResponse = this.workflowBuilderService.chat( { message: text, @@ -65,7 +65,6 @@ export class AiController { executionSchema: workflowContext.executionSchema, expressionValues: workflowContext.expressionValues, }, - useDeprecatedCredentials, }, req.user, signal, diff --git a/packages/frontend/editor-ui/src/api/ai.test.ts b/packages/frontend/editor-ui/src/api/ai.test.ts index 9eabd757b8d..b990ccc75ac 100644 --- a/packages/frontend/editor-ui/src/api/ai.test.ts +++ b/packages/frontend/editor-ui/src/api/ai.test.ts @@ -50,13 +50,7 @@ describe('API: ai', () => { expect(streamRequestSpy).toHaveBeenCalledWith( mockContext, '/ai/build', - { - ...payload, - payload: { - ...payload.payload, - useDeprecatedCredentials: false, - }, - }, + payload, mockOnMessageUpdated, mockOnDone, mockOnError, @@ -89,56 +83,12 @@ describe('API: ai', () => { expect(streamRequestSpy).toHaveBeenCalledWith( mockContext, '/ai/build', - { - ...payload, - payload: { - ...payload.payload, - useDeprecatedCredentials: false, - }, - }, - mockOnMessageUpdated, - mockOnDone, - mockOnError, - undefined, - abortSignal, - ); - }); - - it('should use deprecated credentials when flag is true', () => { - const payload: ChatRequest.RequestPayload = { - payload: { - role: 'user', - type: 'message', - text: 'Build me a workflow', - }, - sessionId: 'session-456', - }; - - chatWithBuilder( - mockContext, payload, mockOnMessageUpdated, mockOnDone, mockOnError, undefined, - true, - ); - - expect(streamRequestSpy).toHaveBeenCalledWith( - mockContext, - '/ai/build', - { - ...payload, - payload: { - ...payload.payload, - useDeprecatedCredentials: true, - }, - }, - mockOnMessageUpdated, - mockOnDone, - mockOnError, - undefined, - undefined, + abortSignal, ); }); @@ -165,13 +115,7 @@ describe('API: ai', () => { expect(streamRequestSpy).toHaveBeenCalledWith( mockContext, '/ai/build', - { - ...payload, - payload: { - ...payload.payload, - useDeprecatedCredentials: false, - }, - }, + payload, mockOnMessageUpdated, mockOnDone, mockOnError, @@ -195,13 +139,7 @@ describe('API: ai', () => { expect(streamRequestSpy).toHaveBeenCalledWith( mockContext, '/ai/build', - { - ...payload, - payload: { - ...payload.payload, - useDeprecatedCredentials: false, - }, - }, + payload, mockOnMessageUpdated, mockOnDone, mockOnError, @@ -316,26 +254,12 @@ describe('API: ai', () => { sessionId: 'session-complex', }; - chatWithBuilder( - mockContext, - payload, - mockOnMessageUpdated, - mockOnDone, - mockOnError, - undefined, - false, - ); + chatWithBuilder(mockContext, payload, mockOnMessageUpdated, mockOnDone, mockOnError); expect(streamRequestSpy).toHaveBeenCalledWith( mockContext, '/ai/build', - { - ...payload, - payload: { - ...payload.payload, - useDeprecatedCredentials: false, - }, - }, + payload, mockOnMessageUpdated, mockOnDone, mockOnError, @@ -358,13 +282,7 @@ describe('API: ai', () => { expect(streamRequestSpy).toHaveBeenCalledWith( mockContext, '/ai/build', - { - ...payload, - payload: { - ...payload.payload, - useDeprecatedCredentials: false, - }, - }, + payload, mockOnMessageUpdated, mockOnDone, mockOnError, diff --git a/packages/frontend/editor-ui/src/api/ai.ts b/packages/frontend/editor-ui/src/api/ai.ts index cb93d4540cd..b8244ded881 100644 --- a/packages/frontend/editor-ui/src/api/ai.ts +++ b/packages/frontend/editor-ui/src/api/ai.ts @@ -18,7 +18,6 @@ export function chatWithBuilder( onDone: () => void, onError: (e: Error) => void, abortSignal?: AbortSignal, - useDeprecatedCredentials = false, ): void { void streamRequest( ctx, @@ -27,7 +26,6 @@ export function chatWithBuilder( ...payload, payload: { ...payload.payload, - useDeprecatedCredentials, }, }, onMessageUpdated, diff --git a/packages/frontend/editor-ui/src/constants/experiments.ts b/packages/frontend/editor-ui/src/constants/experiments.ts index de57c5776bc..9022f70efad 100644 --- a/packages/frontend/editor-ui/src/constants/experiments.ts +++ b/packages/frontend/editor-ui/src/constants/experiments.ts @@ -22,18 +22,6 @@ export const NDV_UI_OVERHAUL_EXPERIMENT = { variant: 'variant', }; -export const WORKFLOW_BUILDER_RELEASE_EXPERIMENT = { - name: '043_workflow_builder_release', - control: 'control', - variant: 'variant', -}; - -export const WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT = { - name: '036_workflow_builder_agent', - control: 'control', - variant: 'variant', -}; - export const EXTRA_TEMPLATE_LINKS_EXPERIMENT = { name: '034_extra_template_links', control: 'control', @@ -88,8 +76,6 @@ export const PERSONALIZED_TEMPLATES_V3 = { }; export const EXPERIMENTS_TO_TRACK = [ - WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.name, - WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name, EXTRA_TEMPLATE_LINKS_EXPERIMENT.name, TEMPLATE_ONBOARDING_EXPERIMENT.name, NDV_UI_OVERHAUL_EXPERIMENT.name, diff --git a/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.test.ts b/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.test.ts index 03d085af179..ae46bb80276 100644 --- a/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.test.ts +++ b/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.test.ts @@ -19,11 +19,7 @@ import { useSettingsStore } from '@/stores/settings.store'; import { defaultSettings } from '@/__tests__/defaults'; import merge from 'lodash/merge'; import { DEFAULT_POSTHOG_SETTINGS } from '@/stores/posthog.store.test'; -import { - WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT, - WORKFLOW_BUILDER_RELEASE_EXPERIMENT, - DEFAULT_NEW_WORKFLOW_NAME, -} from '@/constants'; +import { DEFAULT_NEW_WORKFLOW_NAME } from '@/constants'; import { reactive } from 'vue'; import * as chatAPI from '@/api/ai'; import * as telemetryModule from '@/composables/useTelemetry'; @@ -442,70 +438,16 @@ describe('AI Builder store', () => { const settingsStore = useSettingsStore(); vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(false); - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; - }); expect(builderStore.isAIBuilderEnabled).toBe(false); }); - it('should return true when license has aiBuilder and release experiment is set to variant', () => { + it('should return true when license has aiBuilder feature', () => { const builderStore = useBuilderStore(); const settingsStore = useSettingsStore(); vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true); - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; // deprecated should be control - }); - expect(builderStore.isAIBuilderEnabled).toBe(true); - }); - it('should return true when license has aiBuilder and release experiment is control but deprecated experiment is variant', () => { - const builderStore = useBuilderStore(); - const settingsStore = useSettingsStore(); - - vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true); - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant; - }); - expect(builderStore.isAIBuilderEnabled).toBe(true); - }); - - it('should return false when license has aiBuilder but both experiments are set to control', () => { - const builderStore = useBuilderStore(); - const settingsStore = useSettingsStore(); - - vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true); - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; - }); - expect(builderStore.isAIBuilderEnabled).toBe(false); - }); - - it('should prioritize release experiment over deprecated experiment when license has aiBuilder', () => { - const builderStore = useBuilderStore(); - const settingsStore = useSettingsStore(); - - vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true); - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant; - } - // Even if deprecated is control, release variant should win - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; - }); expect(builderStore.isAIBuilderEnabled).toBe(true); }); }); @@ -787,8 +729,6 @@ describe('AI Builder store', () => { // Verify the API was called with correct parameters expect(apiSpy).toHaveBeenCalled(); const callArgs = apiSpy.mock.calls[0]; - expect(callArgs).toHaveLength(7); // Should have 7 arguments - const signal = callArgs[5]; // The 6th argument is the abort signal expect(signal).toBeDefined(); expect(signal).toBeInstanceOf(AbortSignal); @@ -1229,92 +1169,6 @@ describe('AI Builder store', () => { }); }); - describe('useDeprecatedCredentials logic in sendChatMessage', () => { - it('should set useDeprecatedCredentials to true when release experiment is control and deprecated experiment is variant', () => { - const builderStore = useBuilderStore(); - - // Mock posthog to return control for release and variant for deprecated - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant; - }); - - // Mock the API to capture the arguments - apiSpy.mockImplementationOnce(() => {}); - - builderStore.sendChatMessage({ text: 'test message' }); - - // Verify chatWithBuilder was called with useDeprecatedCredentials = true - expect(apiSpy).toHaveBeenCalledWith( - expect.anything(), // rootStore.restApiContext - expect.anything(), // payload - expect.anything(), // onMessage callback - expect.anything(), // onDone callback - expect.anything(), // onError callback - expect.anything(), // abort signal - true, // useDeprecatedCredentials - ); - }); - - it('should set useDeprecatedCredentials to false when release experiment is variant', () => { - const builderStore = useBuilderStore(); - - // Mock posthog to return variant for release (regardless of deprecated) - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant; - }); - - // Mock the API to capture the arguments - apiSpy.mockImplementationOnce(() => {}); - - builderStore.sendChatMessage({ text: 'test message' }); - - // Verify chatWithBuilder was called with useDeprecatedCredentials = false - expect(apiSpy).toHaveBeenCalledWith( - expect.anything(), // rootStore.restApiContext - expect.anything(), // payload - expect.anything(), // onMessage callback - expect.anything(), // onDone callback - expect.anything(), // onError callback - expect.anything(), // abort signal - false, // useDeprecatedCredentials - ); - }); - - it('should set useDeprecatedCredentials to false when both experiments are control', () => { - const builderStore = useBuilderStore(); - - // Mock posthog to return control for both experiments - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; - }); - - // Mock the API to capture the arguments - apiSpy.mockImplementationOnce(() => {}); - - builderStore.sendChatMessage({ text: 'test message' }); - - // Verify chatWithBuilder was called with useDeprecatedCredentials = false - expect(apiSpy).toHaveBeenCalledWith( - expect.anything(), // rootStore.restApiContext - expect.anything(), // payload - expect.anything(), // onMessage callback - expect.anything(), // onDone callback - expect.anything(), // onError callback - expect.anything(), // abort signal - false, // useDeprecatedCredentials - ); - }); - }); - describe('Credits management', () => { it('should update builder credits correctly', () => { const builderStore = useBuilderStore(); @@ -1462,16 +1316,12 @@ describe('AI Builder store', () => { mockGetBuilderCredits.mockClear(); }); - it('should fetch and update credits when release experiment is variant', async () => { + it('should fetch and update credits when AI builder is enabled', async () => { const builderStore = useBuilderStore(); + const settingsStore = useSettingsStore(); - // Mock posthog to return variant for release experiment - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; - }); + // Mock AI builder as enabled + vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true); // Mock API response mockGetBuilderCredits.mockResolvedValueOnce({ @@ -1486,16 +1336,12 @@ describe('AI Builder store', () => { expect(builderStore.creditsRemaining).toBe(150); }); - it('should not fetch credits when release experiment is not variant', async () => { + it('should not fetch credits when AI builder is not enabled', async () => { const builderStore = useBuilderStore(); + const settingsStore = useSettingsStore(); - // Mock posthog to return control for release experiment - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.control; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant; - }); + // Mock AI builder as disabled + vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(false); await builderStore.fetchBuilderCredits(); @@ -1506,14 +1352,10 @@ describe('AI Builder store', () => { it('should handle API errors gracefully', async () => { const builderStore = useBuilderStore(); + const settingsStore = useSettingsStore(); - // Mock posthog to return variant for release experiment - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; - }); + // Mock AI builder as enabled + vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true); // Mock API to throw error mockGetBuilderCredits.mockRejectedValueOnce(new Error('API error')); @@ -1529,14 +1371,10 @@ describe('AI Builder store', () => { it('should call fetchBuilderCredits when opening chat', async () => { const builderStore = useBuilderStore(); const chatPanelStore = useChatPanelStore(); + const settingsStore = useSettingsStore(); - // Mock posthog to return variant for release experiment - vi.spyOn(posthogStore, 'getVariant').mockImplementation((experimentName) => { - if (experimentName === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) { - return WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant; - } - return WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.control; - }); + // Mock AI builder as enabled + vi.spyOn(settingsStore, 'isAiBuilderEnabled', 'get').mockReturnValue(true); // Mock API response mockGetBuilderCredits.mockResolvedValueOnce({ diff --git a/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.ts b/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.ts index 1484351d0e5..001eecf8eca 100644 --- a/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.ts +++ b/packages/frontend/editor-ui/src/features/ai/assistant/builder.store.ts @@ -1,10 +1,5 @@ import type { VIEWS } from '@/constants'; -import { - DEFAULT_NEW_WORKFLOW_NAME, - WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT, - WORKFLOW_BUILDER_RELEASE_EXPERIMENT, - PLACEHOLDER_EMPTY_WORKFLOW_ID, -} from '@/constants'; +import { DEFAULT_NEW_WORKFLOW_NAME, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants'; import { BUILDER_ENABLED_VIEWS } from './constants'; import { STORES } from '@n8n/stores'; import type { ChatUI } from '@n8n/design-system/types/assistant'; @@ -16,7 +11,6 @@ import { useSettingsStore } from '@/stores/settings.store'; import { assert } from '@n8n/utils/assert'; import { useI18n } from '@n8n/i18n'; import { useTelemetry } from '@/composables/useTelemetry'; -import { usePostHog } from '@/stores/posthog.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useBuilderMessages } from './composables/useBuilderMessages'; import { chatWithBuilder, getAiSessions, getBuilderCredits, getSessionsMetadata } from '@/api/ai'; @@ -57,7 +51,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { const route = useRoute(); const locale = useI18n(); const telemetry = useTelemetry(); - const posthogStore = usePostHog(); // Composables const { @@ -81,23 +74,8 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { return firstUserMessage?.content; }); - const isAIBuilderEnabled = computed(() => { - // Check license first - if (!settings.isAiBuilderEnabled) { - return false; - } - - const releaseExperimentVariant = posthogStore.getVariant( - WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name, - ); - if (releaseExperimentVariant === WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant) { - return true; - } - - return ( - posthogStore.getVariant(WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.name) === - WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant - ); + const isAIBuilderEnabled = computed((): boolean => { + return settings.isAiBuilderEnabled; }); const toolMessages = computed(() => chatMessages.value.filter(isToolMessage)); @@ -305,12 +283,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { streamingAbortController.value.abort(); } - const useDeprecatedCredentials = - posthogStore.getVariant(WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name) !== - WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant && - posthogStore.getVariant(WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.name) === - WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT.variant; - streamingAbortController.value = new AbortController(); try { chatWithBuilder( @@ -335,7 +307,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { () => stopStreaming(), (e) => handleServiceError(e, messageId, retry), streamingAbortController.value?.signal, - useDeprecatedCredentials, ); } catch (e: unknown) { handleServiceError(e, messageId, retry); @@ -508,10 +479,7 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { } async function fetchBuilderCredits() { - const releaseExperimentVariant = posthogStore.getVariant( - WORKFLOW_BUILDER_RELEASE_EXPERIMENT.name, - ); - if (releaseExperimentVariant !== WORKFLOW_BUILDER_RELEASE_EXPERIMENT.variant) { + if (!isAIBuilderEnabled.value) { return; }