n8n
Version:
n8n Workflow Automation Tool
480 lines • 22.5 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChatHubExecutionService = void 0;
const api_types_1 = require("@n8n/api-types");
const backend_common_1 = require("@n8n/backend-common");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const uuid_1 = require("uuid");
const active_executions_1 = require("../../active-executions");
const chat_execution_manager_1 = require("../../chat/chat-execution-manager");
const execution_not_found_error_1 = require("../../errors/execution-not-found-error");
const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
const internal_server_error_1 = require("../../errors/response-errors/internal-server.error");
const execution_service_1 = require("../../executions/execution.service");
const workflow_execution_service_1 = require("../../workflows/workflow-execution.service");
const chat_hub_execution_store_service_1 = require("./chat-hub-execution-store.service");
const chat_hub_workflow_service_1 = require("./chat-hub-workflow.service");
const chat_hub_constants_1 = require("./chat-hub.constants");
const chat_message_repository_1 = require("./chat-message.repository");
const chat_stream_service_1 = require("./chat-stream.service");
const stream_capturer_1 = require("./stream-capturer");
let ChatHubExecutionService = class ChatHubExecutionService {
constructor(logger, executionService, workflowExecutionService, executionRepository, executionManager, activeExecutions, instanceSettings, chatStreamService, chatHubWorkflowService, chatHubExecutionStore, messageRepository) {
this.logger = logger;
this.executionService = executionService;
this.workflowExecutionService = workflowExecutionService;
this.executionRepository = executionRepository;
this.executionManager = executionManager;
this.activeExecutions = activeExecutions;
this.instanceSettings = instanceSettings;
this.chatStreamService = chatStreamService;
this.chatHubWorkflowService = chatHubWorkflowService;
this.chatHubExecutionStore = chatHubExecutionStore;
this.messageRepository = messageRepository;
this.logger = this.logger.scoped('chat-hub');
}
async execute(user, workflowData, executionData) {
return await this.workflowExecutionService.executeChatWorkflow(user, workflowData, executionData, undefined, false, 'chat');
}
async stop(executionId, workflowId) {
return await this.executionService.stop(executionId, [workflowId]);
}
async executeChatWorkflowWithCleanup(user, model, workflowData, executionData, sessionId, previousMessageId, retryOfMessageId, responseMode, pushRef) {
const executionMode = pushRef && model.provider === 'n8n'
? 'manual'
: model.provider === 'n8n'
? 'webhook'
: 'chat';
const { id: workflowId } = workflowData;
try {
await this.executeChatWorkflow(user, model, workflowData, executionData, sessionId, previousMessageId, retryOfMessageId, executionMode, responseMode, pushRef);
}
catch (error) {
this.logger.error(`Error in chat execution: ${error}`);
const errorMessageId = (0, uuid_1.v4)();
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
await this.messageRepository.createAIMessage({
id: errorMessageId,
sessionId,
previousMessageId,
content: errorMessage,
model,
retryOfMessageId,
status: 'error',
});
await this.chatStreamService.sendErrorDirect(user.id, sessionId, errorMessageId, errorMessage);
await this.chatStreamService.endExecution(user.id, sessionId, 'error');
}
finally {
if (model.provider !== 'n8n') {
await this.chatHubWorkflowService.deleteChatWorkflow(workflowId);
}
}
}
async executeChatWorkflow(user, model, workflowData, executionData, sessionId, previousMessageId, retryOfMessageId, executionMode, responseMode, pushRef) {
this.logger.debug(`Starting execution of workflow "${workflowData.name}" with ID ${workflowData.id}`);
if (!chat_hub_constants_1.SUPPORTED_RESPONSE_MODES.includes(responseMode)) {
throw new bad_request_error_1.BadRequestError(`Response mode "${responseMode}" is not supported yet.`);
}
if (responseMode === 'lastNode' || responseMode === 'responseNodes') {
return await this.executeNonStreaming(user, model, workflowData, executionData, sessionId, previousMessageId, retryOfMessageId, executionMode, responseMode, pushRef);
}
else if (responseMode === 'streaming') {
return await this.executeWithStreaming(user, model, workflowData, executionData, sessionId, previousMessageId, retryOfMessageId, executionMode, pushRef);
}
}
async executeWithStreaming(user, model, workflowData, executionData, sessionId, previousMessageId, retryOfMessageId, executionMode, pushRef) {
let executionId;
let executionStatus = 'success';
const workflowId = workflowData.id;
await this.chatStreamService.startExecution(user.id, sessionId);
const aggregator = (0, stream_capturer_1.createStructuredChunkAggregator)(previousMessageId, retryOfMessageId, {
onBegin: async (message) => {
await this.messageRepository.createAIMessage({
id: message.id,
sessionId,
previousMessageId: message.previousMessageId ?? previousMessageId,
content: '',
model,
executionId,
retryOfMessageId: message.retryOfMessageId,
status: 'running',
});
await this.chatStreamService.startStream({
userId: user.id,
sessionId,
messageId: message.id,
previousMessageId: message.previousMessageId,
retryOfMessageId: message.retryOfMessageId,
executionId: executionId ? parseInt(executionId, 10) : null,
});
},
onItem: async (message, chunk) => {
await this.chatStreamService.sendChunk(sessionId, message.id, chunk);
},
onEnd: async (message) => {
await this.messageRepository.updateChatMessage(message.id, {
content: message.content,
status: message.status,
});
await this.chatStreamService.endStream(sessionId, message.id, message.status);
},
onError: async (message, errorText) => {
let contentToSave = message.content;
if (!contentToSave && errorText) {
contentToSave = errorText;
}
else if (!contentToSave) {
contentToSave = executionId
? ((await this.waitForErrorDetails(executionId, workflowId)) ?? 'Unknown error')
: 'Request was not processed';
}
await this.messageRepository.manager.transaction(async (trx) => {
await this.messageRepository.updateChatMessage(message.id, { content: contentToSave }, trx);
const savedMessage = await this.messageRepository.getOneById(message.id, sessionId, [], trx);
if (savedMessage?.status === 'cancelled') {
executionStatus = 'cancelled';
await this.chatStreamService.endStream(sessionId, message.id, 'cancelled');
return;
}
executionStatus = 'error';
await this.messageRepository.updateChatMessage(message.id, { status: 'error' }, trx);
await this.chatStreamService.sendError(sessionId, message.id, contentToSave);
await this.chatStreamService.endStream(sessionId, message.id, 'error');
});
},
onCancel: async (message) => {
await this.messageRepository.updateChatMessage(message.id, {
content: message.content,
status: 'cancelled',
});
await this.chatStreamService.endStream(sessionId, message.id, 'cancelled');
},
});
const { adapter: streamAdapter, waitForPendingOperations } = this.createStreamAdapter(aggregator);
try {
const execution = await this.workflowExecutionService.executeChatWorkflow(user, workflowData, executionData, streamAdapter, true, executionMode, pushRef);
executionId = execution.executionId;
if (!executionId) {
throw new n8n_workflow_1.OperationalError('There was a problem starting the chat execution.');
}
await this.waitForExecutionCompletion(executionId);
await waitForPendingOperations();
}
catch (error) {
if (error instanceof n8n_workflow_1.ManualExecutionCancelledError) {
executionStatus = 'cancelled';
if (this.instanceSettings.isMultiMain) {
await aggregator.cancelAll();
}
}
else {
executionStatus = 'error';
throw error;
}
}
finally {
await waitForPendingOperations();
await this.chatStreamService.endExecution(user.id, sessionId, executionStatus);
}
}
async executeNonStreaming(user, model, workflowData, executionData, sessionId, previousMessageId, retryOfMessageId, executionMode, responseMode, pushRef) {
const running = await this.workflowExecutionService.executeChatWorkflow(user, workflowData, executionData, undefined, false, executionMode, pushRef);
const executionId = running.executionId;
if (!executionId) {
throw new n8n_workflow_1.OperationalError('There was a problem starting the chat execution.');
}
const messageId = (0, uuid_1.v4)();
await this.chatHubExecutionStore.register({
executionId,
sessionId,
userId: user.id,
messageId,
previousMessageId,
model,
responseMode,
awaitingResume: false,
createMessageOnResume: false,
workflowId: workflowData.id,
});
await this.chatStreamService.startExecution(user.id, sessionId);
await this.messageRepository.createAIMessage({
id: messageId,
content: '',
sessionId,
executionId,
model,
previousMessageId,
retryOfMessageId,
status: 'running',
});
await this.chatStreamService.startStream({
userId: user.id,
sessionId,
messageId,
previousMessageId,
retryOfMessageId,
executionId: parseInt(executionId, 10),
});
}
async resumeChatExecution(execution, message, sessionId, _user, previousMessageId, model, _responseMode) {
await this.messageRepository.updateChatMessage(previousMessageId, {
status: 'success',
});
const messageId = (0, uuid_1.v4)();
await this.messageRepository.createAIMessage({
id: messageId,
content: '',
sessionId,
executionId: execution.id,
model,
previousMessageId,
retryOfMessageId: null,
status: 'running',
});
await this.chatHubExecutionStore.update(execution.id, {
previousMessageId,
messageId,
awaitingResume: true,
createMessageOnResume: false,
});
await this.executionManager.runWorkflow(execution, {
action: 'sendMessage',
chatInput: message,
sessionId,
});
}
async waitForExecutionCompletion(executionId) {
if (this.instanceSettings.isMultiMain) {
return await this.waitForExecutionPoller(executionId);
}
else {
return await this.waitForExecutionPromise(executionId);
}
}
async waitForExecutionPoller(executionId) {
return await new Promise((resolve, reject) => {
const poller = setInterval(async () => {
try {
const execution = await this.executionRepository.findSingleExecution(executionId, {
includeData: false,
unflattenData: false,
});
if (!execution || chat_hub_constants_1.EXECUTION_FINISHED_STATUSES.includes(execution.status)) {
this.logger.debug(`Execution ${executionId} finished with status ${execution?.status ?? 'missing'}`);
clearInterval(poller);
if (execution?.status === 'canceled') {
reject(new n8n_workflow_1.ManualExecutionCancelledError(executionId));
}
else {
resolve();
}
}
}
catch (error) {
this.logger.error(`Stopping polling for execution ${executionId} due to error.`);
clearInterval(poller);
if (error instanceof Error) {
this.logger.error(`Error while polling execution ${executionId}: ${error.message}`, {
error,
});
}
else {
this.logger.error(`Unknown error while polling execution ${executionId}`, { error });
}
if (error instanceof Error) {
reject(error);
}
else {
reject(new Error('Unknown error while polling execution status'));
}
}
}, chat_hub_constants_1.EXECUTION_POLL_INTERVAL);
});
}
async waitForExecutionPromise(executionId) {
try {
const result = await this.activeExecutions.getPostExecutePromise(executionId);
if (!result) {
throw new n8n_workflow_1.OperationalError('There was a problem executing the chat workflow.');
}
}
catch (error) {
if (error instanceof execution_not_found_error_1.ExecutionNotFoundError) {
return;
}
if (error instanceof n8n_workflow_1.ManualExecutionCancelledError) {
throw error;
}
if (error instanceof Error) {
this.logger.error(`Error during chat workflow execution: ${error}`);
}
throw error;
}
}
async waitForErrorDetails(executionId, workflowId) {
const maxRetries = 5;
let retries = 0;
let errorText;
while (!errorText) {
try {
const execution = await this.executionRepository.findWithUnflattenedData(executionId, [
workflowId,
]);
if (execution && chat_hub_constants_1.EXECUTION_FINISHED_STATUSES.includes(execution.status)) {
errorText = this.extractErrorMessage(execution.data);
break;
}
}
catch (error) {
this.logger.debug(`Failed to fetch execution ${executionId} for error extraction: ${String(error)}`);
}
retries++;
if (maxRetries <= retries) {
break;
}
await (0, n8n_workflow_1.sleep)(Math.min(500 * Math.pow(2, retries), 2000));
}
return errorText;
}
createStreamAdapter(aggregator) {
let processingChain = Promise.resolve();
const adapter = {
headersSent: false,
writableEnded: false,
writeHead: (_statusCode, _headers) => {
adapter.headersSent = true;
return adapter;
},
flushHeaders: () => { },
write: (chunk, _encoding, doneCb) => {
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk;
const lines = text.split('\n').filter((line) => line.trim());
for (const line of lines) {
try {
const parsed = (0, n8n_workflow_1.jsonParse)(line.trim());
processingChain = processingChain.then(async () => {
await aggregator.ingest(parsed);
});
}
catch {
}
}
if (doneCb)
doneCb();
return true;
},
end: (_chunk, _encoding, doneCb) => {
adapter.writableEnded = true;
if (doneCb)
doneCb();
return adapter;
},
flush: () => { },
on: (_event, _handler) => adapter,
once: (_event, _handler) => adapter,
emit: (_event, ..._args) => true,
};
const waitForPendingOperations = async () => {
await processingChain;
};
return { adapter: adapter, waitForPendingOperations };
}
async ensureWasSuccessfulOrThrow(executionId, errorMessage) {
const executionEntity = await this.executionRepository.findSingleExecution(executionId, {
includeData: true,
unflattenData: true,
});
if (!executionEntity) {
throw new internal_server_error_1.InternalServerError(`${errorMessage}: Execution not found`);
}
if (executionEntity.status !== 'success') {
const cause = this.extractErrorMessage(executionEntity.data) ?? 'Unknown error';
throw new internal_server_error_1.InternalServerError(`${errorMessage}: ${cause}`);
}
}
extractErrorMessage(runData) {
const { error, runData: nodeRunData } = runData.resultData;
if (error) {
return error.description ?? error.message;
}
for (const nodeRuns of Object.values(nodeRunData ?? {})) {
for (const nodeRun of nodeRuns) {
if (nodeRun.error) {
return nodeRun.error.description ?? nodeRun.error.message;
}
}
}
return undefined;
}
extractMessage(runData, responseMode) {
const lastNodeExecuted = runData.data.resultData.lastNodeExecuted;
if (typeof lastNodeExecuted !== 'string')
return undefined;
const nodeRunData = runData.data.resultData.runData[lastNodeExecuted];
if (!nodeRunData || nodeRunData.length === 0)
return undefined;
const runIndex = nodeRunData.length - 1;
const data = nodeRunData[runIndex]?.data;
const outputs = data?.main ?? data?.[n8n_workflow_1.NodeConnectionTypes.AiTool] ?? [];
const entry = this.getFirstOutputEntry(outputs);
if (!entry)
return undefined;
return this.extractMessageFromEntry(entry, responseMode);
}
getFirstOutputEntry(outputs) {
for (const branch of outputs) {
if (!Array.isArray(branch) || branch.length === 0)
continue;
return branch[0];
}
return undefined;
}
extractMessageFromEntry(entry, responseMode) {
if (responseMode === 'responseNodes') {
const sendMessage = entry.sendMessage;
if (typeof sendMessage === 'string') {
return sendMessage;
}
const result = api_types_1.chatHubMessageWithButtonsSchema.safeParse(sendMessage);
if (result.success) {
return (0, n8n_workflow_1.jsonStringify)(result.data);
}
return '';
}
if (responseMode === 'lastNode') {
const response = entry.json ?? {};
const message = response.output ?? response.text ?? response.message ?? '';
return typeof message === 'string' ? message : (0, n8n_workflow_1.jsonStringify)(message);
}
return undefined;
}
};
exports.ChatHubExecutionService = ChatHubExecutionService;
exports.ChatHubExecutionService = ChatHubExecutionService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger,
execution_service_1.ExecutionService,
workflow_execution_service_1.WorkflowExecutionService,
db_1.ExecutionRepository,
chat_execution_manager_1.ChatExecutionManager,
active_executions_1.ActiveExecutions,
n8n_core_1.InstanceSettings,
chat_stream_service_1.ChatStreamService,
chat_hub_workflow_service_1.ChatHubWorkflowService,
chat_hub_execution_store_service_1.ChatHubExecutionStore,
chat_message_repository_1.ChatHubMessageRepository])
], ChatHubExecutionService);
//# sourceMappingURL=chat-hub-execution.service.js.map