diff --git a/cypress/composables/ndv.ts b/cypress/composables/ndv.ts index c3fab73f8c8b6..5b3690e6a6946 100644 --- a/cypress/composables/ndv.ts +++ b/cypress/composables/ndv.ts @@ -59,7 +59,7 @@ export function setCredentialByName(name: string) { export function clickCreateNewCredential() { openCredentialSelect(); - getCreateNewCredentialOption().click(); + getCreateNewCredentialOption().click({ force: true }); } export function clickGetBackToCanvas() { diff --git a/cypress/e2e/45-ai-assistant.cy.ts b/cypress/e2e/45-ai-assistant.cy.ts index 440300a6d5b9d..4fc1407bb9616 100644 --- a/cypress/e2e/45-ai-assistant.cy.ts +++ b/cypress/e2e/45-ai-assistant.cy.ts @@ -80,9 +80,9 @@ describe('AI Assistant::enabled', () => { it('should start chat session from node error view', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/simple_message_response.json', + fixture: 'aiAssistant/responses/simple_message_response.json', }).as('chatRequest'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); wf.actions.openNode('Stop and Error'); ndv.getters.nodeExecuteButton().click(); aiAssistant.getters.nodeErrorViewAssistantButton().click(); @@ -98,9 +98,9 @@ describe('AI Assistant::enabled', () => { it('should render chat input correctly', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/simple_message_response.json', + fixture: 'aiAssistant/responses/simple_message_response.json', }).as('chatRequest'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); wf.actions.openNode('Stop and Error'); ndv.getters.nodeExecuteButton().click(); aiAssistant.getters.nodeErrorViewAssistantButton().click(); @@ -131,9 +131,9 @@ describe('AI Assistant::enabled', () => { it('should render and handle quick replies', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/quick_reply_message_response.json', + fixture: 'aiAssistant/responses/quick_reply_message_response.json', }).as('chatRequest'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); wf.actions.openNode('Stop and Error'); ndv.getters.nodeExecuteButton().click(); aiAssistant.getters.nodeErrorViewAssistantButton().click(); @@ -149,15 +149,21 @@ describe('AI Assistant::enabled', () => { cy.intercept('POST', '/rest/ai/chat', (req) => { req.reply((res) => { if (['init-error-helper', 'message'].includes(req.body.payload.type)) { - res.send({ statusCode: 200, fixture: 'aiAssistant/simple_message_response.json' }); + res.send({ + statusCode: 200, + fixture: 'aiAssistant/responses/simple_message_response.json', + }); } else if (req.body.payload.type === 'event') { - res.send({ statusCode: 200, fixture: 'aiAssistant/node_execution_error_response.json' }); + res.send({ + statusCode: 200, + fixture: 'aiAssistant/responses/node_execution_error_response.json', + }); } else { res.send({ statusCode: 500 }); } }); }).as('chatRequest'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); wf.actions.openNode('Edit Fields'); ndv.getters.nodeExecuteButton().click(); aiAssistant.getters.nodeErrorViewAssistantButton().click(); @@ -172,16 +178,15 @@ describe('AI Assistant::enabled', () => { aiAssistant.getters.quickReplies().should('not.exist'); ndv.getters.nodeExecuteButton().click(); // But after executing the node again, quick replies should be shown - aiAssistant.getters.chatMessagesAssistant().should('have.length', 4); aiAssistant.getters.quickReplies().should('have.length', 2); }); it('should warn before starting a new session', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/simple_message_response.json', + fixture: 'aiAssistant/responses/simple_message_response.json', }).as('chatRequest'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); wf.actions.openNode('Edit Fields'); ndv.getters.nodeExecuteButton().click(); aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true }); @@ -206,13 +211,13 @@ describe('AI Assistant::enabled', () => { it('should apply code diff to code node', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/code_diff_suggestion_response.json', + fixture: 'aiAssistant/responses/code_diff_suggestion_response.json', }).as('chatRequest'); cy.intercept('POST', '/rest/ai/chat/apply-suggestion', { statusCode: 200, - fixture: 'aiAssistant/apply_code_diff_response.json', + fixture: 'aiAssistant/responses/apply_code_diff_response.json', }).as('applySuggestion'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); wf.actions.openNode('Code'); ndv.getters.nodeExecuteButton().click(); aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true }); @@ -256,9 +261,9 @@ describe('AI Assistant::enabled', () => { it('should end chat session when `end_session` event is received', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/end_session_response.json', + fixture: 'aiAssistant/responses/end_session_response.json', }).as('chatRequest'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); wf.actions.openNode('Stop and Error'); ndv.getters.nodeExecuteButton().click(); aiAssistant.getters.nodeErrorViewAssistantButton().click(); @@ -271,9 +276,12 @@ describe('AI Assistant::enabled', () => { cy.intercept('POST', '/rest/ai/chat', (req) => { req.reply((res) => { if (['init-support-chat'].includes(req.body.payload.type)) { - res.send({ statusCode: 200, fixture: 'aiAssistant/simple_message_response.json' }); + res.send({ + statusCode: 200, + fixture: 'aiAssistant/responses/simple_message_response.json', + }); } else { - res.send({ statusCode: 200, fixture: 'aiAssistant/end_session_response.json' }); + res.send({ statusCode: 200, fixture: 'aiAssistant/responses/end_session_response.json' }); } }); }).as('chatRequest'); @@ -298,7 +306,7 @@ describe('AI Assistant::enabled', () => { it('Should not reset assistant session when workflow is saved', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/simple_message_response.json', + fixture: 'aiAssistant/responses/simple_message_response.json', }).as('chatRequest'); wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); aiAssistant.actions.openChat(); @@ -323,7 +331,7 @@ describe('AI Assistant Credential Help', () => { it('should start credential help from node credential', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/simple_message_response.json', + fixture: 'aiAssistant/responses/simple_message_response.json', }).as('chatRequest'); wf.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); wf.actions.addNodeToCanvas(GMAIL_NODE_NAME); @@ -349,7 +357,7 @@ describe('AI Assistant Credential Help', () => { it('should start credential help from credential list', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/simple_message_response.json', + fixture: 'aiAssistant/responses/simple_message_response.json', }).as('chatRequest'); cy.visit(credentialsPage.url); @@ -448,7 +456,7 @@ describe('General help', () => { it('assistant returns code snippet', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, - fixture: 'aiAssistant/code_snippet_response.json', + fixture: 'aiAssistant/responses/code_snippet_response.json', }).as('chatRequest'); aiAssistant.getters.askAssistantFloatingButton().should('be.visible'); @@ -492,4 +500,65 @@ describe('General help', () => { ); aiAssistant.getters.codeSnippet().should('have.text', '{{$json.body.city}}'); }); + + it('should send current context to support chat', () => { + cy.createFixtureWorkflow('aiAssistant/workflows/simple_http_request_workflow.json'); + cy.intercept('POST', '/rest/ai/chat', { + statusCode: 200, + fixture: 'aiAssistant/responses/simple_message_response.json', + }).as('chatRequest'); + + aiAssistant.getters.askAssistantFloatingButton().click(); + aiAssistant.actions.sendMessage('What is wrong with this workflow?'); + + cy.wait('@chatRequest').then((interception) => { + const { body } = interception.request; + // Body should contain the current workflow context + expect(body.payload).to.have.property('context'); + expect(body.payload.context).to.have.property('currentView'); + expect(body.payload.context.currentView.name).to.equal('NodeViewExisting'); + expect(body.payload.context).to.have.property('currentWorkflow'); + }); + }); + + it('should not send workflow context if nothing changed', () => { + cy.createFixtureWorkflow('aiAssistant/workflows/simple_http_request_workflow.json'); + cy.intercept('POST', '/rest/ai/chat', { + statusCode: 200, + fixture: 'aiAssistant/responses/simple_message_response.json', + }).as('chatRequest'); + + aiAssistant.getters.askAssistantFloatingButton().click(); + aiAssistant.actions.sendMessage('What is wrong with this workflow?'); + cy.wait('@chatRequest'); + + // Send another message without changing workflow or executing any node + aiAssistant.actions.sendMessage('And now?'); + + cy.wait('@chatRequest').then((interception) => { + const { body } = interception.request; + // Workflow context should be empty + expect(body.payload).to.have.property('context'); + expect(body.payload.context).not.to.have.property('currentWorkflow'); + }); + + // Update http request node url + wf.actions.openNode('HTTP Request'); + ndv.actions.typeIntoParameterInput('url', 'https://example.com'); + ndv.actions.close(); + // Also execute the workflow + wf.actions.executeWorkflow(); + + // Send another message + aiAssistant.actions.sendMessage('What about now?'); + cy.wait('@chatRequest').then((interception) => { + const { body } = interception.request; + // Both workflow and execution context should be sent + expect(body.payload).to.have.property('context'); + expect(body.payload.context).to.have.property('currentWorkflow'); + expect(body.payload.context.currentWorkflow).not.to.be.empty; + expect(body.payload.context).to.have.property('executionData'); + expect(body.payload.context.executionData).not.to.be.empty; + }); + }); }); diff --git a/cypress/fixtures/aiAssistant/apply_code_diff_response.json b/cypress/fixtures/aiAssistant/responses/apply_code_diff_response.json similarity index 100% rename from cypress/fixtures/aiAssistant/apply_code_diff_response.json rename to cypress/fixtures/aiAssistant/responses/apply_code_diff_response.json diff --git a/cypress/fixtures/aiAssistant/code_diff_suggestion_response.json b/cypress/fixtures/aiAssistant/responses/code_diff_suggestion_response.json similarity index 100% rename from cypress/fixtures/aiAssistant/code_diff_suggestion_response.json rename to cypress/fixtures/aiAssistant/responses/code_diff_suggestion_response.json diff --git a/cypress/fixtures/aiAssistant/code_snippet_response.json b/cypress/fixtures/aiAssistant/responses/code_snippet_response.json similarity index 100% rename from cypress/fixtures/aiAssistant/code_snippet_response.json rename to cypress/fixtures/aiAssistant/responses/code_snippet_response.json diff --git a/cypress/fixtures/aiAssistant/end_session_response.json b/cypress/fixtures/aiAssistant/responses/end_session_response.json similarity index 100% rename from cypress/fixtures/aiAssistant/end_session_response.json rename to cypress/fixtures/aiAssistant/responses/end_session_response.json diff --git a/cypress/fixtures/aiAssistant/node_execution_error_response.json b/cypress/fixtures/aiAssistant/responses/node_execution_error_response.json similarity index 100% rename from cypress/fixtures/aiAssistant/node_execution_error_response.json rename to cypress/fixtures/aiAssistant/responses/node_execution_error_response.json diff --git a/cypress/fixtures/aiAssistant/quick_reply_message_response.json b/cypress/fixtures/aiAssistant/responses/quick_reply_message_response.json similarity index 100% rename from cypress/fixtures/aiAssistant/quick_reply_message_response.json rename to cypress/fixtures/aiAssistant/responses/quick_reply_message_response.json diff --git a/cypress/fixtures/aiAssistant/simple_message_response.json b/cypress/fixtures/aiAssistant/responses/simple_message_response.json similarity index 100% rename from cypress/fixtures/aiAssistant/simple_message_response.json rename to cypress/fixtures/aiAssistant/responses/simple_message_response.json diff --git a/cypress/fixtures/aiAssistant/workflows/simple_http_request_workflow.json b/cypress/fixtures/aiAssistant/workflows/simple_http_request_workflow.json new file mode 100644 index 0000000000000..28a0ee5359bd8 --- /dev/null +++ b/cypress/fixtures/aiAssistant/workflows/simple_http_request_workflow.json @@ -0,0 +1,35 @@ +{ + "nodes": [ + { + "parameters": {}, + "id": "298d3dc9-5e99-4b3f-919e-05fdcdfbe2d0", + "name": "When clicking ‘Test workflow’", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [360, 220] + }, + { + "parameters": { + "options": {} + }, + "id": "65c32346-e939-4ec7-88a9-1f9184e2258d", + "name": "HTTP Request", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [580, 220] + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "HTTP Request", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/cypress/fixtures/aiAssistant/test_workflow.json b/cypress/fixtures/aiAssistant/workflows/test_workflow.json similarity index 100% rename from cypress/fixtures/aiAssistant/test_workflow.json rename to cypress/fixtures/aiAssistant/workflows/test_workflow.json diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index b775deec6d233..cae1fb47b033c 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -156,7 +156,7 @@ export class NDV extends BasePage { this.getters.nodeExecuteButton().first().click(); }, close: () => { - this.getters.backToCanvas().click(); + this.getters.backToCanvas().click({ force: true }); }, openInlineExpressionEditor: () => { cy.contains('Expression').invoke('show').click(); diff --git a/n8n.code-workspace b/n8n.code-workspace deleted file mode 100644 index 8f4183e8f0bd3..0000000000000 --- a/n8n.code-workspace +++ /dev/null @@ -1,7 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ] -} diff --git a/packages/editor-ui/src/composables/useAIAssistantHelpers.ts b/packages/editor-ui/src/composables/useAIAssistantHelpers.ts index a1c222ae4893a..5adc3952febaa 100644 --- a/packages/editor-ui/src/composables/useAIAssistantHelpers.ts +++ b/packages/editor-ui/src/composables/useAIAssistantHelpers.ts @@ -1,4 +1,10 @@ -import type { IDataObject, NodeApiError, NodeError, NodeOperationError } from 'n8n-workflow'; +import type { + IDataObject, + IRunExecutionData, + NodeApiError, + NodeError, + NodeOperationError, +} from 'n8n-workflow'; import { deepCopy, type INode } from 'n8n-workflow'; import { useWorkflowHelpers } from './useWorkflowHelpers'; import { useRouter } from 'vue-router'; @@ -203,6 +209,39 @@ export const useAIAssistantHelpers = () => { return undefined; } } + /** + * Prepare workflow execution result data for the AI assistant + * by removing data from nodes + **/ + function simplifyResultData( + data: IRunExecutionData['resultData'], + ): ChatRequest.ExecutionResultData { + const simplifiedResultData: ChatRequest.ExecutionResultData = { + runData: {}, + }; + + // Handle optional error + if (data.error) { + simplifiedResultData.error = data.error; + } + // Map runData, excluding the `data` field from ITaskData + Object.keys(data.runData).forEach((key) => { + const taskDataArray = data.runData[key]; + simplifiedResultData.runData[key] = taskDataArray.map((taskData) => { + const { data: taskDataContent, ...taskDataWithoutData } = taskData; + return taskDataWithoutData; + }); + }); + // Handle lastNodeExecuted if it exists + if (data.lastNodeExecuted) { + simplifiedResultData.lastNodeExecuted = data.lastNodeExecuted; + } + // Handle metadata if it exists + if (data.metadata) { + simplifiedResultData.metadata = data.metadata; + } + return simplifiedResultData; + } return { processNodeForAssistant, @@ -212,5 +251,6 @@ export const useAIAssistantHelpers = () => { getNodesSchemas, getCurrentViewDescription, getReferencedNodes, + simplifyResultData, }; }; diff --git a/packages/editor-ui/src/stores/assistant.store.ts b/packages/editor-ui/src/stores/assistant.store.ts index 69bc734f255fb..c3f8c77e0061c 100644 --- a/packages/editor-ui/src/stores/assistant.store.ts +++ b/packages/editor-ui/src/stores/assistant.store.ts @@ -83,6 +83,9 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { // We use streaming for assistants that support it, and this for agents const assistantThinkingMessage = ref(); const chatSessionTask = ref<'error' | 'support' | 'credentials' | undefined>(); + // Indicate if last sent workflow and execution data is stale + const workflowDataStale = ref(true); + const workflowExecutionDataStale = ref(true); const isExperimentEnabled = computed( () => getVariant(AI_ASSISTANT_EXPERIMENT.name) === AI_ASSISTANT_EXPERIMENT.variant, @@ -124,18 +127,6 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { ).length, ); - watch(route, () => { - const activeWorkflowId = workflowsStore.workflowId; - if ( - !currentSessionId.value || - currentSessionWorkflowId.value === PLACEHOLDER_EMPTY_WORKFLOW_ID || - currentSessionWorkflowId.value === activeWorkflowId - ) { - return; - } - resetAssistantChat(); - }); - function resetAssistantChat() { clearMessages(); currentSessionId.value = undefined; @@ -305,11 +296,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { { withPostHog: true }, ); // Track first user message in support chat now that we have a session id - if ( - usersMessages.value.length === 1 && - !currentSessionId.value && - chatSessionTask.value === 'support' - ) { + if (usersMessages.value.length === 1 && chatSessionTask.value === 'support') { const firstUserMessage = usersMessages.value[0] as ChatUI.TextMessage; trackUserMessage(firstUserMessage.content, false); } @@ -325,6 +312,8 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { function onDoneStreaming(id: string) { stopStreaming(); + workflowDataStale.value = false; + workflowExecutionDataStale.value = false; lastUnread.value = chatMessages.value.find( (msg) => msg.id === id && !msg.read && msg.role === 'assistant' && READABLE_TYPES.includes(msg.type), @@ -353,14 +342,16 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { /** * Gets information about the current view and active node to provide context to the assistant */ - function getVisualContext(nodeInfo?: ChatRequest.NodeInfo): ChatRequest.UserContext | undefined { + function getVisualContext( + nodeInfo?: ChatRequest.NodeInfo, + ): ChatRequest.AssistantContext | undefined { if (chatSessionTask.value === 'error') { return undefined; } const currentView = route.name as VIEWS; const activeNode = workflowsStore.activeNode(); const activeNodeForLLM = activeNode - ? assistantHelpers.processNodeForAssistant(activeNode, ['position']) + ? assistantHelpers.processNodeForAssistant(activeNode, ['position', 'parameters.notice']) : null; const activeModals = uiStore.activeModals; const isCredentialModalActive = activeModals.includes(CREDENTIAL_EDIT_MODAL_KEY); @@ -401,6 +392,11 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { authType: nodeInfo?.authType?.name, } : undefined, + currentWorkflow: workflowDataStale.value ? workflowsStore.workflow : undefined, + executionData: + workflowExecutionDataStale.value && executionResult + ? assistantHelpers.simplifyResultData(executionResult) + : undefined, }; } @@ -491,7 +487,10 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { firstName: usersStore.currentUser?.firstName ?? '', }, error: context.error, - node: assistantHelpers.processNodeForAssistant(context.node, ['position']), + node: assistantHelpers.processNodeForAssistant(context.node, [ + 'position', + 'parameters.notice', + ]), nodeInputData, executionSchema: schemas, authType, @@ -577,7 +576,10 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { ) { nodeExecutionStatus.value = 'not_executed'; } - const userContext = getVisualContext(); + const activeNode = workflowsStore.activeNode() as INode; + const nodeInfo = assistantHelpers.getNodeInfoForAssistant(activeNode); + const userContext = getVisualContext(nodeInfo); + chatWithAssistant( rootStore.restApiContext, { @@ -764,6 +766,33 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { } } + watch(route, () => { + const activeWorkflowId = workflowsStore.workflowId; + if ( + !currentSessionId.value || + currentSessionWorkflowId.value === PLACEHOLDER_EMPTY_WORKFLOW_ID || + currentSessionWorkflowId.value === activeWorkflowId + ) { + return; + } + resetAssistantChat(); + }); + + watch( + () => uiStore.stateIsDirty, + () => { + workflowDataStale.value = true; + }, + ); + + watch( + () => workflowsStore.workflowExecutionData?.data?.resultData ?? {}, + () => { + workflowExecutionDataStale.value = true; + }, + { deep: true, immediate: true }, + ); + return { isAssistantEnabled, canShowAssistantButtonsOnCanvas, diff --git a/packages/editor-ui/src/types/assistant.types.ts b/packages/editor-ui/src/types/assistant.types.ts index feb6150bec8b3..c911f5087b5dc 100644 --- a/packages/editor-ui/src/types/assistant.types.ts +++ b/packages/editor-ui/src/types/assistant.types.ts @@ -1,11 +1,14 @@ import type { VIEWS } from '@/constants'; -import type { NodeAuthenticationOption, Schema } from '@/Interface'; +import type { IWorkflowDb, NodeAuthenticationOption, Schema } from '@/Interface'; import type { + ExecutionError, ICredentialType, IDataObject, INode, INodeIssues, INodeParameters, + IRunExecutionData, + ITaskData, } from 'n8n-workflow'; export namespace ChatRequest { @@ -16,6 +19,15 @@ export namespace ChatRequest { export interface WorkflowContext { executionSchema?: NodeExecutionSchema[]; + currentWorkflow?: IWorkflowDb; + executionData?: IRunExecutionData['resultData']; + } + + export interface ExecutionResultData { + error?: ExecutionError; + runData: Record>>; + lastNodeExecuted?: string; + metadata?: Record; } export interface ErrorContext { @@ -47,6 +59,7 @@ export namespace ChatRequest { firstName: string; }; context?: UserContext; + workflowContext?: WorkflowContext; question: string; } @@ -78,6 +91,7 @@ export namespace ChatRequest { text: string; quickReplyType?: string; context?: UserContext; + workflowContext?: WorkflowContext; } export interface UserContext { @@ -98,6 +112,8 @@ export namespace ChatRequest { }; } + export type AssistantContext = UserContext & WorkflowContext; + export type RequestPayload = | { payload: InitErrorHelper | InitSupportChat | InitCredHelp;