n8n
Version:
n8n Workflow Automation Tool
699 lines • 37 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.ChatHubService = void 0;
const api_types_1 = require("@n8n/api-types");
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
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 bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
const not_found_error_1 = require("../../errors/response-errors/not-found.error");
const workflow_finder_service_1 = require("../../workflows/workflow-finder.service");
const chat_hub_agent_service_1 = require("./chat-hub-agent.service");
const chat_hub_execution_service_1 = require("./chat-hub-execution.service");
const chat_hub_title_service_1 = require("./chat-hub-title.service");
const chat_hub_tool_service_1 = require("./chat-hub-tool.service");
const chat_hub_workflow_service_1 = require("./chat-hub-workflow.service");
const chat_hub_attachment_service_1 = require("./chat-hub.attachment.service");
const chat_hub_models_service_1 = require("./chat-hub.models.service");
const chat_message_repository_1 = require("./chat-message.repository");
const chat_session_repository_1 = require("./chat-session.repository");
const chat_stream_service_1 = require("./chat-stream.service");
const chat_hub_1 = require("@n8n/chat-hub");
let ChatHubService = class ChatHubService {
constructor(logger, errorReporter, executionRepository, workflowFinderService, sessionRepository, messageRepository, chatHubAgentService, chatHubModelsService, chatHubAttachmentService, chatStreamService, chatHubExecutionService, chatHubTitleService, chatHubToolService, chatHubWorkflowService, globalConfig) {
this.logger = logger;
this.errorReporter = errorReporter;
this.executionRepository = executionRepository;
this.workflowFinderService = workflowFinderService;
this.sessionRepository = sessionRepository;
this.messageRepository = messageRepository;
this.chatHubAgentService = chatHubAgentService;
this.chatHubModelsService = chatHubModelsService;
this.chatHubAttachmentService = chatHubAttachmentService;
this.chatStreamService = chatStreamService;
this.chatHubExecutionService = chatHubExecutionService;
this.chatHubTitleService = chatHubTitleService;
this.chatHubToolService = chatHubToolService;
this.chatHubWorkflowService = chatHubWorkflowService;
this.globalConfig = globalConfig;
this.logger = this.logger.scoped('chat-hub');
}
pickCredentialId(provider, credentials) {
if (provider === 'n8n' || provider === 'custom-agent') {
return null;
}
return credentials[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]?.id ?? null;
}
async ensurePreviousMessage(previousMessageId, sessionId, trx) {
if (!previousMessageId) {
return;
}
const previousMessage = await this.messageRepository.getOneById(previousMessageId, sessionId, [], trx);
if (!previousMessage) {
throw new bad_request_error_1.BadRequestError('The previous message does not exist in the session');
}
return previousMessage;
}
async tryResumeWaitingExecution(opts) {
const { workflow, previousMessage, message, sessionId, user, messageId, model } = opts;
if (model.provider !== 'n8n' ||
workflow.responseMode !== 'responseNodes' ||
previousMessage?.status !== 'waiting' ||
!previousMessage?.executionId) {
return false;
}
const execution = await this.executionRepository.findSingleExecution(previousMessage.executionId.toString(), {
includeData: true,
unflattenData: true,
});
if (!execution) {
throw new n8n_workflow_1.OperationalError('Chat session has expired.');
}
this.logger.debug(`Resuming execution ${execution.id} from waiting state for session ${sessionId}`);
await this.messageRepository.updateChatMessage(previousMessage.id, {
status: 'success',
});
void this.chatHubExecutionService.resumeChatExecution(execution, message, sessionId, user, messageId, model, workflow.responseMode);
return true;
}
async stopGeneration(user, sessionId, messageId) {
await this.ensureConversation(user.id, sessionId);
const message = await this.getChatMessage(sessionId, messageId, [
'execution',
'execution.workflow',
]);
if (message.type !== 'ai') {
throw new bad_request_error_1.BadRequestError('Can only stop AI messages');
}
if (!message.executionId || !message.execution) {
throw new bad_request_error_1.BadRequestError('Message is not associated with a workflow execution');
}
if (message.status !== 'running') {
throw new bad_request_error_1.BadRequestError('Can only stop messages that are currently running');
}
await this.chatHubExecutionService.stop(message.execution.id, message.execution.workflowId);
await this.messageRepository.updateChatMessage(messageId, { status: 'cancelled' });
}
getModelCredential(model, credentials) {
const credentialId = model.provider !== 'n8n' ? this.pickCredentialId(model.provider, credentials) : null;
return credentialId;
}
async getChatSession(user, sessionId, trx) {
return await this.sessionRepository.getOneById(sessionId, user.id, trx);
}
async createChatSession(user, sessionId, model, credentialId, manual, agentName, trx) {
await this.ensureValidModel(user, model, manual, trx);
const session = await this.sessionRepository.createChatSession({
id: sessionId,
ownerId: user.id,
title: 'New Chat',
lastMessageAt: new Date(),
agentName,
credentialId,
type: manual ? 'manual' : 'production',
...model,
}, trx);
const enabledTools = await this.chatHubToolService.getEnabledTools(user.id, trx);
const toolIds = enabledTools.map((t) => t.id);
if (toolIds.length > 0) {
await this.chatHubToolService.setSessionTools(sessionId, toolIds, trx);
}
return session;
}
async getChatMessage(sessionId, messageId, relations = [], trx) {
const message = await this.messageRepository.getOneById(messageId, sessionId, relations, trx);
if (!message) {
throw new not_found_error_1.NotFoundError('Chat message not found');
}
return message;
}
async getConversations(userId, limit, cursor, type) {
const sessions = await this.sessionRepository.getManyByUserId(userId, limit + 1, cursor, type);
const hasMore = sessions.length > limit;
const data = hasMore ? sessions.slice(0, limit) : sessions;
const nextCursor = hasMore ? data[data.length - 1].id : null;
return {
data: data.map((session) => this.convertSessionEntityToDto(session)),
nextCursor,
hasMore,
};
}
async ensureConversation(userId, sessionId, trx) {
const sessionExists = await this.sessionRepository.existsById(sessionId, userId, trx);
if (!sessionExists) {
throw new not_found_error_1.NotFoundError('Chat session not found');
}
}
async getConversation(userId, sessionId) {
const session = await this.sessionRepository.getOneById(sessionId, userId);
if (!session) {
throw new not_found_error_1.NotFoundError('Chat session not found');
}
const messages = session.messages ?? [];
const toolIds = await this.chatHubToolService.getToolIdsForSession(sessionId);
return {
session: this.convertSessionEntityToDto(session, toolIds),
conversation: {
messages: Object.fromEntries(messages.map((m) => [m.id, this.convertMessageToDto(m)])),
},
};
}
buildMessageHistory(messages, lastMessageId) {
if (!lastMessageId)
return [];
const visited = new Set();
const historyIds = [];
let current = lastMessageId;
while (current && !visited.has(current)) {
historyIds.unshift(current);
visited.add(current);
current = messages[current]?.previousMessageId ?? null;
}
const history = historyIds.flatMap((id) => messages[id] ?? []);
return history;
}
async deleteAllSessions() {
await this.chatHubAttachmentService.deleteAll();
const result = await this.sessionRepository.deleteAll();
return result;
}
async updateSession(user, sessionId, updates) {
await this.ensureConversation(user.id, sessionId);
const sessionUpdates = {};
if (updates.agent) {
const model = updates.agent.model;
await this.ensureValidModel(user, model, false);
sessionUpdates.agentName = updates.agent.name;
sessionUpdates.provider = model.provider;
sessionUpdates.model = null;
sessionUpdates.credentialId = null;
sessionUpdates.agentId = null;
sessionUpdates.workflowId = null;
if (updates.agent.model.provider === 'n8n') {
sessionUpdates.workflowId = updates.agent.model.workflowId;
}
else if (updates.agent.model.provider === 'custom-agent') {
sessionUpdates.agentId = updates.agent.model.agentId;
}
else {
sessionUpdates.model = updates.agent.model.model;
}
}
if (updates.title !== undefined)
sessionUpdates.title = updates.title;
if (updates.credentialId !== undefined)
sessionUpdates.credentialId = updates.credentialId;
await this.sessionRepository.updateChatSession(sessionId, sessionUpdates);
if (updates.toolIds !== undefined) {
await this.chatHubToolService.setSessionTools(sessionId, updates.toolIds);
}
}
async deleteSession(userId, sessionId) {
await this.messageRepository.manager.transaction(async (trx) => {
await this.ensureConversation(userId, sessionId, trx);
await this.chatHubAttachmentService.deleteAllBySessionId(sessionId, trx);
await this.sessionRepository.deleteChatHubSession(sessionId, trx);
});
}
async ensureValidModel(user, model, manual, trx) {
if (model.provider === 'custom-agent') {
const agent = await this.chatHubAgentService.getAgentById(model.agentId, user.id, trx);
if (!agent) {
throw new bad_request_error_1.BadRequestError('Agent not found for chat session initialization');
}
}
if (model.provider === 'n8n') {
const workflowEntity = await this.workflowFinderService.findWorkflowForUser(model.workflowId, user, ['workflow:execute-chat'], {
includeTags: false,
includeParentFolder: false,
includeActiveVersion: manual ? false : true,
em: trx,
});
if (!workflowEntity) {
throw new bad_request_error_1.BadRequestError('Workflow not found for chat session initialization');
}
const nodes = manual ? workflowEntity.nodes : workflowEntity.activeVersion?.nodes;
if (!nodes) {
throw new bad_request_error_1.BadRequestError('Workflow not found for chat session initialization');
}
const chatTrigger = nodes.find((node) => node.type === n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE);
if (!chatTrigger) {
throw new bad_request_error_1.BadRequestError('Chat trigger not found in workflow for chat session initialization');
}
}
}
async sendHumanMessage(user, payload, executionMetadata) {
const { sessionId, messageId, message, model, credentials, previousMessageId, attachments, timeZone, } = payload;
const tz = timeZone ?? this.globalConfig.generic.timezone;
const credentialId = this.getModelCredential(model, credentials);
let processedAttachments = [];
let workflow;
let previousMessage;
try {
const result = await this.messageRepository.manager.transaction(async (trx) => {
let session = await this.getChatSession(user, sessionId, trx);
const isNewSession = !session;
session ??= await this.createChatSession(user, sessionId, model, credentialId, false, payload.agentName, trx);
const previousMessage = await this.ensurePreviousMessage(previousMessageId, sessionId, trx);
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
const history = this.buildMessageHistory(messages, previousMessageId);
if (attachments.length > 0) {
const policy = await this.chatHubWorkflowService.getAttachmentPolicy(model, user, trx);
this.chatHubAttachmentService.validateAttachments(attachments, policy.allowFileUploads, policy.allowedFilesMimeTypes);
}
processedAttachments = await this.chatHubAttachmentService.store(sessionId, messageId, attachments);
await this.messageRepository.createHumanMessage(payload, processedAttachments, user, previousMessageId, model, undefined, trx);
const tools = isNewSession
? (await this.chatHubToolService.getEnabledTools(user.id, trx)).map((t) => t.definition)
: await this.chatHubToolService.getToolDefinitionsForSession(sessionId, trx);
const replyWorkflow = await this.chatHubWorkflowService.prepareReplyWorkflow(user, sessionId, credentials, model, history, message, tools, processedAttachments, tz, trx, executionMetadata);
return { workflow: replyWorkflow, previousMessage };
});
workflow = result.workflow;
previousMessage = result.previousMessage;
}
catch (error) {
if (processedAttachments.length > 0) {
try {
await this.chatHubAttachmentService.deleteAttachments(processedAttachments);
}
catch {
this.errorReporter.warn(`Could not clean up ${processedAttachments.length} files`);
}
}
throw error;
}
if (!workflow) {
throw new n8n_workflow_1.UnexpectedError('Failed to prepare chat workflow.');
}
await this.chatStreamService.sendHumanMessage({
userId: user.id,
sessionId,
messageId,
previousMessageId,
content: message,
attachments: processedAttachments.map((a) => ({
id: a.id,
fileName: a.fileName ?? 'file',
mimeType: a.mimeType,
})),
});
const resumed = await this.tryResumeWaitingExecution({
workflow,
previousMessage,
message,
sessionId,
user,
messageId,
model,
});
if (resumed)
return;
void this.executeChatWorkflowWithCleanup(user, model, workflow, sessionId, messageId, null, previousMessageId, credentials, message, processedAttachments);
}
async sendHumanMessageManual(user, payload, executionMetadata, pushRef) {
const { sessionId, messageId, message, model, credentials, previousMessageId, attachments, timeZone, } = payload;
const tz = timeZone ?? this.globalConfig.generic.timezone;
if (model.provider !== 'n8n') {
throw new bad_request_error_1.BadRequestError('Manual execution is only supported for n8n workflow agents');
}
const credentialId = this.getModelCredential(model, credentials);
let processedAttachments = [];
let workflow;
let previousMessage;
try {
const result = await this.messageRepository.manager.transaction(async (trx) => {
let session = await this.getChatSession(user, sessionId, trx);
session ??= await this.createChatSession(user, sessionId, model, credentialId, true, payload.agentName, trx);
const previousMessage = await this.ensurePreviousMessage(previousMessageId, sessionId, trx);
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
const history = this.buildMessageHistory(messages, previousMessageId);
if (attachments.length > 0) {
const policy = await this.chatHubWorkflowService.getAttachmentPolicy(model, user, trx, true);
this.chatHubAttachmentService.validateAttachments(attachments, policy.allowFileUploads, policy.allowedFilesMimeTypes);
}
processedAttachments = await this.chatHubAttachmentService.store(sessionId, messageId, attachments);
await this.messageRepository.createHumanMessage(payload, processedAttachments, user, previousMessageId, model, undefined, trx);
const tools = [];
const replyWorkflow = await this.chatHubWorkflowService.prepareReplyWorkflow(user, sessionId, credentials, model, history, message, tools, processedAttachments, tz, trx, executionMetadata, true);
return { workflow: replyWorkflow, previousMessage };
});
workflow = result.workflow;
previousMessage = result.previousMessage;
}
catch (error) {
if (processedAttachments.length > 0) {
try {
await this.chatHubAttachmentService.deleteAttachments(processedAttachments);
}
catch {
this.errorReporter.warn(`Could not clean up ${processedAttachments.length} files`);
}
}
throw error;
}
if (!workflow) {
throw new n8n_workflow_1.UnexpectedError('Failed to prepare chat workflow.');
}
await this.chatStreamService.sendHumanMessage({
userId: user.id,
sessionId,
messageId,
previousMessageId,
content: message,
attachments: processedAttachments.map((a) => ({
id: a.id,
fileName: a.fileName ?? 'file',
mimeType: a.mimeType,
})),
});
const resumed = await this.tryResumeWaitingExecution({
workflow,
previousMessage,
message,
sessionId,
user,
messageId,
model,
});
if (resumed)
return;
void this.executeChatWorkflowWithCleanup(user, model, workflow, sessionId, messageId, null, previousMessageId, credentials, message, processedAttachments, pushRef);
}
async editMessage(user, payload, executionMetadata) {
const { sessionId, editId, messageId, message, model, credentials, timeZone } = payload;
const tz = timeZone ?? this.globalConfig.generic.timezone;
let result = null;
let newStoredAttachments = [];
try {
result = await this.messageRepository.manager.transaction(async (trx) => {
const session = await this.getChatSession(user, sessionId, trx);
if (!session) {
throw new not_found_error_1.NotFoundError('Chat session not found');
}
const messageToEdit = await this.getChatMessage(session.id, editId, [], trx);
if (messageToEdit.type === 'ai') {
throw new bad_request_error_1.BadRequestError('Editing AI messages is not supported');
}
if (messageToEdit.type === 'human') {
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId);
const revisionOfMessageId = messageToEdit.revisionOfMessageId ?? messageToEdit.id;
const originalAttachments = messageToEdit.attachments ?? [];
const keptAttachments = payload.keepAttachmentIndices.flatMap((index) => {
const attachment = originalAttachments[index];
return attachment ? [attachment] : [];
});
newStoredAttachments =
payload.newAttachments.length > 0
? await this.chatHubAttachmentService.store(sessionId, messageId, payload.newAttachments)
: [];
const attachments = [...keptAttachments, ...newStoredAttachments];
await this.messageRepository.createHumanMessage(payload, attachments, user, messageToEdit.previousMessageId, model, revisionOfMessageId, trx);
const tools = await this.chatHubToolService.getToolDefinitionsForSession(sessionId, trx);
const workflow = await this.chatHubWorkflowService.prepareReplyWorkflow(user, sessionId, credentials, model, history, message, tools, attachments, tz, trx, executionMetadata);
return { workflow, combinedAttachments: attachments };
}
throw new bad_request_error_1.BadRequestError('Only human and AI messages can be edited');
});
}
catch (error) {
if (newStoredAttachments.length > 0) {
try {
await this.chatHubAttachmentService.deleteAttachments(newStoredAttachments);
}
catch {
this.errorReporter.warn(`Could not clean up ${newStoredAttachments.length} files`);
}
}
throw error;
}
if (!result?.workflow) {
return;
}
const { workflow } = result;
await this.chatStreamService.sendMessageEdit({
userId: user.id,
sessionId,
revisionOfMessageId: editId,
messageId,
content: message,
attachments: result.combinedAttachments.map((a) => ({
id: a.id,
fileName: a.fileName ?? 'file',
mimeType: a.mimeType,
})),
});
void this.executeChatWorkflowWithCleanup(user, model, workflow, sessionId, messageId, null, null, {}, '', []);
}
async editMessageManual(user, payload, executionMetadata, pushRef) {
const { sessionId, editId, messageId, message, model, credentials, timeZone } = payload;
const tz = timeZone ?? this.globalConfig.generic.timezone;
if (model.provider !== 'n8n') {
throw new bad_request_error_1.BadRequestError('Manual execution is only supported for n8n workflow agents');
}
let result = null;
let newStoredAttachments = [];
try {
result = await this.messageRepository.manager.transaction(async (trx) => {
const session = await this.getChatSession(user, sessionId, trx);
if (!session) {
throw new not_found_error_1.NotFoundError('Chat session not found');
}
const messageToEdit = await this.getChatMessage(session.id, editId, [], trx);
if (messageToEdit.type === 'ai') {
throw new bad_request_error_1.BadRequestError('Editing AI messages is not supported');
}
if (messageToEdit.type === 'human') {
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId);
const revisionOfMessageId = messageToEdit.revisionOfMessageId ?? messageToEdit.id;
const originalAttachments = messageToEdit.attachments ?? [];
const keptAttachments = payload.keepAttachmentIndices.flatMap((index) => {
const attachment = originalAttachments[index];
return attachment ? [attachment] : [];
});
if (payload.newAttachments.length > 0) {
const policy = await this.chatHubWorkflowService.getAttachmentPolicy(model, user, trx, true);
this.chatHubAttachmentService.validateAttachments(payload.newAttachments, policy.allowFileUploads, policy.allowedFilesMimeTypes);
}
newStoredAttachments =
payload.newAttachments.length > 0
? await this.chatHubAttachmentService.store(sessionId, messageId, payload.newAttachments)
: [];
const attachments = [...keptAttachments, ...newStoredAttachments];
await this.messageRepository.createHumanMessage(payload, attachments, user, messageToEdit.previousMessageId, model, revisionOfMessageId, trx);
const tools = [];
const workflow = await this.chatHubWorkflowService.prepareReplyWorkflow(user, sessionId, credentials, model, history, message, tools, attachments, tz, trx, executionMetadata, true);
return { workflow, combinedAttachments: attachments };
}
throw new bad_request_error_1.BadRequestError('Only human and AI messages can be edited');
});
}
catch (error) {
if (newStoredAttachments.length > 0) {
try {
await this.chatHubAttachmentService.deleteAttachments(newStoredAttachments);
}
catch {
this.errorReporter.warn(`Could not clean up ${newStoredAttachments.length} files`);
}
}
throw error;
}
if (!result?.workflow) {
return;
}
const { workflow } = result;
await this.chatStreamService.sendMessageEdit({
userId: user.id,
sessionId,
revisionOfMessageId: editId,
messageId,
content: message,
attachments: result.combinedAttachments.map((a) => ({
id: a.id,
fileName: a.fileName ?? 'file',
mimeType: a.mimeType,
})),
});
void this.executeChatWorkflowWithCleanup(user, model, workflow, sessionId, messageId, null, null, {}, '', [], pushRef);
}
async regenerateAIMessage(user, payload, executionMetadata) {
const { sessionId, retryId, model, credentials, timeZone } = payload;
const tz = timeZone ?? this.globalConfig.generic.timezone;
const { retryOfMessageId, previousMessageId, workflow } = await this.messageRepository.manager.transaction(async (trx) => {
const session = await this.getChatSession(user, sessionId, trx);
if (!session) {
throw new not_found_error_1.NotFoundError('Chat session not found');
}
const messageToRetry = await this.getChatMessage(session.id, retryId, [], trx);
if (messageToRetry.type !== 'ai') {
throw new bad_request_error_1.BadRequestError('Can only retry AI messages');
}
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
const history = this.buildMessageHistory(messages, messageToRetry.previousMessageId);
const lastHumanMessage = history.filter((m) => m.type === 'human').pop();
if (!lastHumanMessage) {
throw new bad_request_error_1.BadRequestError('No human message found to base the retry on');
}
const lastHumanMessageIndex = history.indexOf(lastHumanMessage);
if (lastHumanMessageIndex !== -1) {
history.splice(lastHumanMessageIndex);
}
const retryOfMessageId = messageToRetry.retryOfMessageId ?? messageToRetry.id;
const message = lastHumanMessage ? lastHumanMessage.content : '';
const attachments = lastHumanMessage.attachments ?? [];
const tools = await this.chatHubToolService.getToolDefinitionsForSession(sessionId, trx);
const workflow = await this.chatHubWorkflowService.prepareReplyWorkflow(user, sessionId, credentials, model, history, message, tools, attachments, tz, trx, executionMetadata);
return {
previousMessageId: lastHumanMessage.id,
retryOfMessageId,
workflow,
};
});
void this.executeChatWorkflowWithCleanup(user, model, workflow, sessionId, previousMessageId, retryOfMessageId, null, {}, '', []);
}
async regenerateAIMessageManual(user, payload, executionMetadata, pushRef) {
const { sessionId, retryId, model, credentials, timeZone } = payload;
const tz = timeZone ?? this.globalConfig.generic.timezone;
if (model.provider !== 'n8n') {
throw new bad_request_error_1.BadRequestError('Manual execution is only supported for n8n workflow agents');
}
const { retryOfMessageId, previousMessageId, workflow } = await this.messageRepository.manager.transaction(async (trx) => {
const session = await this.getChatSession(user, sessionId, trx);
if (!session) {
throw new not_found_error_1.NotFoundError('Chat session not found');
}
const messageToRetry = await this.getChatMessage(session.id, retryId, [], trx);
if (messageToRetry.type !== 'ai') {
throw new bad_request_error_1.BadRequestError('Can only retry AI messages');
}
const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
const history = this.buildMessageHistory(messages, messageToRetry.previousMessageId);
const lastHumanMessage = history.filter((m) => m.type === 'human').pop();
if (!lastHumanMessage) {
throw new bad_request_error_1.BadRequestError('No human message found to base the retry on');
}
const lastHumanMessageIndex = history.indexOf(lastHumanMessage);
if (lastHumanMessageIndex !== -1) {
history.splice(lastHumanMessageIndex);
}
const retryOfMessageId = messageToRetry.retryOfMessageId ?? messageToRetry.id;
const message = lastHumanMessage ? lastHumanMessage.content : '';
const attachments = lastHumanMessage.attachments ?? [];
const tools = [];
const workflow = await this.chatHubWorkflowService.prepareReplyWorkflow(user, sessionId, credentials, model, history, message, tools, attachments, tz, trx, executionMetadata, true);
return {
previousMessageId: lastHumanMessage.id,
retryOfMessageId,
workflow,
};
});
void this.executeChatWorkflowWithCleanup(user, model, workflow, sessionId, previousMessageId, retryOfMessageId, null, {}, '', [], pushRef);
}
async reconnectToStream(sessionId, lastReceivedSequence) {
const hasActiveStream = await this.chatStreamService.hasActiveStream(sessionId);
const currentMessageId = await this.chatStreamService.getCurrentMessageId(sessionId);
const pendingChunks = await this.chatStreamService.getPendingChunks(sessionId, lastReceivedSequence);
return {
hasActiveStream,
currentMessageId,
pendingChunks,
lastSequenceNumber: pendingChunks.length > 0
? pendingChunks[pendingChunks.length - 1].sequenceNumber
: lastReceivedSequence,
};
}
async executeChatWorkflowWithCleanup(user, model, workflow, sessionId, previousMessageId, retryOfMessageId, originalPreviousMessageId, credentials, humanMessage, processedAttachments, pushRef) {
await this.chatHubExecutionService.executeChatWorkflowWithCleanup(user, model, workflow.workflowData, workflow.executionData, sessionId, previousMessageId, retryOfMessageId, workflow.responseMode, pushRef);
if (originalPreviousMessageId === null && humanMessage && !pushRef) {
try {
await this.chatHubTitleService.generateSessionTitle(user, sessionId, humanMessage, processedAttachments, credentials, model);
}
catch (error) {
this.logger.warn(`Title generation failed: ${error}`);
}
}
}
convertMessageToDto(message) {
return {
id: message.id,
sessionId: message.sessionId,
type: message.type,
name: message.name,
content: (0, chat_hub_1.parseMessage)(message),
provider: message.provider,
model: message.model,
workflowId: message.workflowId,
agentId: message.agentId,
executionId: message.executionId,
status: message.status,
createdAt: message.createdAt.toISOString(),
updatedAt: message.updatedAt.toISOString(),
previousMessageId: message.previousMessageId,
retryOfMessageId: message.retryOfMessageId,
revisionOfMessageId: message.revisionOfMessageId,
attachments: (message.attachments ?? []).map(({ fileName, mimeType }) => ({
fileName,
mimeType,
})),
};
}
convertSessionEntityToDto(session, toolIds = []) {
const agent = session.workflow
? this.chatHubModelsService.extractModelFromWorkflow(session.workflow, [])
: session.agent
? this.chatHubAgentService.convertAgentEntityToModel(session.agent)
: undefined;
return {
id: session.id,
title: session.title,
ownerId: session.ownerId,
lastMessageAt: session.lastMessageAt?.toISOString() ?? null,
credentialId: session.credentialId,
provider: session.provider,
model: session.model,
workflowId: session.workflowId,
agentId: session.agentId,
agentName: agent?.name ?? session.agentName ?? session.model ?? '',
agentIcon: agent?.icon ?? null,
type: session.type ?? 'production',
createdAt: session.createdAt.toISOString(),
updatedAt: session.updatedAt.toISOString(),
toolIds,
};
}
};
exports.ChatHubService = ChatHubService;
exports.ChatHubService = ChatHubService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger,
n8n_core_1.ErrorReporter,
db_1.ExecutionRepository,
workflow_finder_service_1.WorkflowFinderService,
chat_session_repository_1.ChatHubSessionRepository,
chat_message_repository_1.ChatHubMessageRepository,
chat_hub_agent_service_1.ChatHubAgentService,
chat_hub_models_service_1.ChatHubModelsService,
chat_hub_attachment_service_1.ChatHubAttachmentService,
chat_stream_service_1.ChatStreamService,
chat_hub_execution_service_1.ChatHubExecutionService,
chat_hub_title_service_1.ChatHubTitleService,
chat_hub_tool_service_1.ChatHubToolService,
chat_hub_workflow_service_1.ChatHubWorkflowService,
config_1.GlobalConfig])
], ChatHubService);
//# sourceMappingURL=chat-hub.service.js.map