n8n 1.117.2 → 1.118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/auth/auth.service.d.ts +2 -1
  2. package/dist/auth/auth.service.js +5 -2
  3. package/dist/auth/auth.service.js.map +1 -1
  4. package/dist/build.tsbuildinfo +1 -1
  5. package/dist/controllers/project.controller.d.ts +2 -0
  6. package/dist/controllers/users.controller.d.ts +1 -1
  7. package/dist/environments.ee/source-control/source-control-export.service.ee.d.ts +1 -1
  8. package/dist/environments.ee/source-control/source-control-export.service.ee.js +16 -4
  9. package/dist/environments.ee/source-control/source-control-export.service.ee.js.map +1 -1
  10. package/dist/environments.ee/source-control/source-control-import.service.ee.d.ts +10 -3
  11. package/dist/environments.ee/source-control/source-control-import.service.ee.js +49 -20
  12. package/dist/environments.ee/source-control/source-control-import.service.ee.js.map +1 -1
  13. package/dist/environments.ee/source-control/source-control-status.service.ee.d.ts +8 -6
  14. package/dist/environments.ee/source-control/source-control-status.service.ee.js +25 -3
  15. package/dist/environments.ee/source-control/source-control-status.service.ee.js.map +1 -1
  16. package/dist/environments.ee/source-control/source-control.controller.ee.d.ts +8 -8
  17. package/dist/environments.ee/source-control/source-control.service.ee.d.ts +5 -6
  18. package/dist/environments.ee/source-control/source-control.service.ee.js +11 -8
  19. package/dist/environments.ee/source-control/source-control.service.ee.js.map +1 -1
  20. package/dist/environments.ee/source-control/types/exportable-project.d.ts +2 -0
  21. package/dist/environments.ee/source-control/types/exportable-variable.d.ts +7 -0
  22. package/dist/environments.ee/source-control/types/exportable-variable.js +3 -0
  23. package/dist/environments.ee/source-control/types/exportable-variable.js.map +1 -0
  24. package/dist/environments.ee/variables/variables.service.ee.d.ts +4 -1
  25. package/dist/environments.ee/variables/variables.service.ee.js +11 -4
  26. package/dist/environments.ee/variables/variables.service.ee.js.map +1 -1
  27. package/dist/executions/execution.service.d.ts +1 -0
  28. package/dist/executions/execution.service.js +15 -0
  29. package/dist/executions/execution.service.js.map +1 -1
  30. package/dist/modules/chat-hub/chat-hub.constants.d.ts +1 -0
  31. package/dist/modules/chat-hub/chat-hub.constants.js +13 -0
  32. package/dist/modules/chat-hub/chat-hub.constants.js.map +1 -0
  33. package/dist/modules/chat-hub/chat-hub.controller.js +26 -30
  34. package/dist/modules/chat-hub/chat-hub.controller.js.map +1 -1
  35. package/dist/modules/chat-hub/chat-hub.service.d.ts +17 -9
  36. package/dist/modules/chat-hub/chat-hub.service.js +308 -110
  37. package/dist/modules/chat-hub/chat-hub.service.js.map +1 -1
  38. package/dist/modules/chat-hub/chat-hub.types.d.ts +5 -2
  39. package/dist/modules/chat-hub/chat-message.repository.d.ts +3 -3
  40. package/dist/modules/chat-hub/chat-message.repository.js +18 -12
  41. package/dist/modules/chat-hub/chat-message.repository.js.map +1 -1
  42. package/dist/modules/chat-hub/chat-session.repository.d.ts +1 -1
  43. package/dist/modules/chat-hub/chat-session.repository.js +9 -8
  44. package/dist/modules/chat-hub/chat-session.repository.js.map +1 -1
  45. package/dist/modules/chat-hub/context-limits.d.ts +3 -3
  46. package/dist/modules/chat-hub/context-limits.js.map +1 -1
  47. package/dist/modules/chat-hub/dto/chat-models-request.dto.d.ts +1 -1
  48. package/dist/modules/chat-hub/stream-capturer.d.ts +3 -0
  49. package/dist/modules/chat-hub/stream-capturer.js +31 -0
  50. package/dist/modules/chat-hub/stream-capturer.js.map +1 -0
  51. package/dist/modules/mcp/mcp.controller.d.ts +1 -1
  52. package/dist/modules/mcp/mcp.controller.js +5 -20
  53. package/dist/modules/mcp/mcp.controller.js.map +1 -1
  54. package/dist/modules/mcp/mcp.service.d.ts +3 -1
  55. package/dist/modules/mcp/mcp.service.js +7 -4
  56. package/dist/modules/mcp/mcp.service.js.map +1 -1
  57. package/dist/modules/mcp/mcp.types.d.ts +5 -0
  58. package/dist/modules/mcp/tools/get-workflow-details.tool.d.ts +2 -1
  59. package/dist/modules/mcp/tools/get-workflow-details.tool.js +32 -5
  60. package/dist/modules/mcp/tools/get-workflow-details.tool.js.map +1 -1
  61. package/dist/modules/mcp/tools/search-workflows.tool.d.ts +2 -1
  62. package/dist/modules/mcp/tools/search-workflows.tool.js +39 -15
  63. package/dist/modules/mcp/tools/search-workflows.tool.js.map +1 -1
  64. package/dist/modules/provisioning.ee/provisioning.controller.ee.d.ts +1 -0
  65. package/dist/modules/provisioning.ee/provisioning.controller.ee.js +13 -0
  66. package/dist/modules/provisioning.ee/provisioning.controller.ee.js.map +1 -1
  67. package/dist/modules/provisioning.ee/provisioning.service.ee.d.ts +18 -2
  68. package/dist/modules/provisioning.ee/provisioning.service.ee.js +196 -2
  69. package/dist/modules/provisioning.ee/provisioning.service.ee.js.map +1 -1
  70. package/dist/public-api/v1/openapi.yml +41 -41
  71. package/dist/scaling/constants.d.ts +2 -2
  72. package/dist/scaling/pubsub/pubsub.event-map.d.ts +1 -0
  73. package/dist/scaling/pubsub/pubsub.types.d.ts +2 -1
  74. package/dist/server.d.ts +1 -0
  75. package/dist/server.js +10 -3
  76. package/dist/server.js.map +1 -1
  77. package/dist/services/frontend.service.d.ts +6 -1
  78. package/dist/services/frontend.service.js +21 -0
  79. package/dist/services/frontend.service.js.map +1 -1
  80. package/dist/sso.ee/oidc/oidc.service.ee.d.ts +4 -1
  81. package/dist/sso.ee/oidc/oidc.service.ee.js +21 -5
  82. package/dist/sso.ee/oidc/oidc.service.ee.js.map +1 -1
  83. package/dist/sso.ee/saml/routes/saml.controller.ee.d.ts +4 -1
  84. package/dist/sso.ee/saml/routes/saml.controller.ee.js +9 -2
  85. package/dist/sso.ee/saml/routes/saml.controller.ee.js.map +1 -1
  86. package/dist/sso.ee/saml/saml-helpers.js +2 -0
  87. package/dist/sso.ee/saml/saml-helpers.js.map +1 -1
  88. package/dist/sso.ee/saml/saml.service.ee.js +9 -0
  89. package/dist/sso.ee/saml/saml.service.ee.js.map +1 -1
  90. package/dist/telemetry/index.d.ts +3 -2
  91. package/dist/telemetry/index.js +7 -2
  92. package/dist/telemetry/index.js.map +1 -1
  93. package/dist/workflows/workflow-finder.service.d.ts +1 -0
  94. package/dist/workflows/workflows.controller.d.ts +8 -1
  95. package/dist/workflows/workflows.controller.js +20 -2
  96. package/dist/workflows/workflows.controller.js.map +1 -1
  97. package/package.json +15 -15
@@ -17,16 +17,21 @@ const di_1 = require("@n8n/di");
17
17
  const n8n_workflow_1 = require("n8n-workflow");
18
18
  const uuid_1 = require("uuid");
19
19
  const active_executions_1 = require("../../active-executions");
20
- const credentials_service_1 = require("../../credentials/credentials.service");
20
+ const credentials_finder_service_1 = require("../../credentials/credentials-finder.service");
21
21
  const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
22
+ const forbidden_error_1 = require("../../errors/response-errors/forbidden.error");
22
23
  const not_found_error_1 = require("../../errors/response-errors/not-found.error");
23
24
  const execution_service_1 = require("../../executions/execution.service");
24
25
  const dynamic_node_parameters_service_1 = require("../../services/dynamic-node-parameters.service");
25
26
  const workflow_execute_additional_data_1 = require("../../workflow-execute-additional-data");
26
27
  const workflow_execution_service_1 = require("../../workflows/workflow-execution.service");
28
+ const workflow_finder_service_1 = require("../../workflows/workflow-finder.service");
29
+ const workflow_service_1 = require("../../workflows/workflow.service");
30
+ const chat_hub_constants_1 = require("./chat-hub.constants");
27
31
  const chat_message_repository_1 = require("./chat-message.repository");
28
32
  const chat_session_repository_1 = require("./chat-session.repository");
29
33
  const context_limits_1 = require("./context-limits");
34
+ const stream_capturer_1 = require("./stream-capturer");
30
35
  const providerNodeTypeMapping = {
31
36
  openai: {
32
37
  name: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
@@ -43,42 +48,60 @@ const providerNodeTypeMapping = {
43
48
  };
44
49
  const NODE_NAMES = {
45
50
  CHAT_TRIGGER: 'When chat message received',
46
- AI_AGENT: 'AI Agent',
51
+ REPLY_AGENT: 'AI Agent',
52
+ TITLE_GENERATOR_AGENT: 'Title Generator Agent',
47
53
  CHAT_MODEL: 'Chat Model',
48
54
  MEMORY: 'Memory',
49
55
  RESTORE_CHAT_MEMORY: 'Restore Chat Memory',
50
56
  CLEAR_CHAT_MEMORY: 'Clear Chat Memory',
51
57
  };
58
+ const JSONL_STREAM_HEADERS = {
59
+ 'Content-Type': 'application/json-lines; charset=utf-8',
60
+ 'Transfer-Encoding': 'chunked',
61
+ 'Cache-Control': 'no-cache',
62
+ Connection: 'keep-alive',
63
+ };
52
64
  let ChatHubService = class ChatHubService {
53
- constructor(logger, credentialsService, executionService, nodeParametersService, executionRepository, workflowExecutionService, workflowRepository, projectRepository, sharedWorkflowRepository, activeExecutions, sessionRepository, messageRepository) {
65
+ constructor(logger, executionService, nodeParametersService, executionRepository, workflowExecutionService, workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, activeExecutions, sessionRepository, messageRepository, credentialsFinderService) {
54
66
  this.logger = logger;
55
- this.credentialsService = credentialsService;
56
67
  this.executionService = executionService;
57
68
  this.nodeParametersService = nodeParametersService;
58
69
  this.executionRepository = executionRepository;
59
70
  this.workflowExecutionService = workflowExecutionService;
71
+ this.workflowService = workflowService;
72
+ this.workflowFinderService = workflowFinderService;
60
73
  this.workflowRepository = workflowRepository;
61
- this.projectRepository = projectRepository;
62
74
  this.sharedWorkflowRepository = sharedWorkflowRepository;
63
75
  this.activeExecutions = activeExecutions;
64
76
  this.sessionRepository = sessionRepository;
65
77
  this.messageRepository = messageRepository;
78
+ this.credentialsFinderService = credentialsFinderService;
66
79
  }
67
80
  async getModels(user, credentialIds) {
68
81
  const additionalData = await (0, workflow_execute_additional_data_1.getBase)({ userId: user.id });
69
- const responses = await Promise.all(api_types_1.chatHubProviderSchema.options.map(async (provider) => {
70
- const credentialId = credentialIds[provider];
71
- if (!credentialId) {
72
- return [provider, { models: [] }];
82
+ const providers = ['openai', 'anthropic', 'google'];
83
+ const allCredentials = await this.credentialsFinderService.findCredentialsForUser(user, [
84
+ 'credential:read',
85
+ ]);
86
+ const responses = await Promise.all(providers.map(async (provider) => {
87
+ const credentials = {};
88
+ if (provider !== 'n8n') {
89
+ const credentialId = credentialIds[provider];
90
+ if (!credentialId) {
91
+ return [provider, { models: [] }];
92
+ }
93
+ if (!allCredentials.some((credential) => credential.id === credentialId)) {
94
+ return [
95
+ provider,
96
+ { models: [], error: 'Could not retrieve models. Verify credentials.' },
97
+ ];
98
+ }
99
+ credentials[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]] = { name: '', id: credentialId };
73
100
  }
74
- await this.credentialsService.getOne(user, credentialId, false);
75
101
  try {
76
- const credentials = {
77
- [api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]: { name: '', id: credentialId },
78
- };
79
102
  return [
80
103
  provider,
81
- await this.fetchModelsForProvider(provider, credentials, additionalData),
104
+ await this.fetchModelsForProvider(user, provider, credentials, additionalData),
82
105
  ];
83
106
  }
84
107
  catch {
@@ -95,9 +118,10 @@ let ChatHubService = class ChatHubService {
95
118
  openai: { models: [] },
96
119
  anthropic: { models: [] },
97
120
  google: { models: [] },
121
+ n8n: { models: [] },
98
122
  });
99
123
  }
100
- async fetchModelsForProvider(provider, credentials, additionalData) {
124
+ async fetchModelsForProvider(user, provider, credentials, additionalData) {
101
125
  switch (provider) {
102
126
  case 'openai':
103
127
  return await this.fetchOpenAiModels(credentials, additionalData);
@@ -105,18 +129,28 @@ let ChatHubService = class ChatHubService {
105
129
  return await this.fetchAnthropicModels(credentials, additionalData);
106
130
  case 'google':
107
131
  return await this.fetchGoogleModels(credentials, additionalData);
132
+ case 'n8n':
133
+ return await this.fetchCustomAgentWorkflows(user);
108
134
  }
109
135
  }
110
136
  async fetchOpenAiModels(credentials, additionalData) {
111
137
  const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.openai, {}, credentials);
112
138
  return {
113
- models: resourceLocatorResults.results.map((result) => ({ name: String(result.value) })),
139
+ models: resourceLocatorResults.results.map((result) => ({
140
+ provider: 'openai',
141
+ name: String(result.value),
142
+ model: String(result.value),
143
+ })),
114
144
  };
115
145
  }
116
146
  async fetchAnthropicModels(credentials, additionalData) {
117
147
  const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.anthropic, {}, credentials);
118
148
  return {
119
- models: resourceLocatorResults.results.map((result) => ({ name: String(result.value) })),
149
+ models: resourceLocatorResults.results.map((result) => ({
150
+ provider: 'anthropic',
151
+ name: String(result.value),
152
+ model: String(result.value),
153
+ })),
120
154
  };
121
155
  }
122
156
  async fetchGoogleModels(credentials, additionalData) {
@@ -159,27 +193,46 @@ let ChatHubService = class ChatHubService {
159
193
  },
160
194
  }, additionalData, providerNodeTypeMapping.google, {}, credentials);
161
195
  return {
162
- models: results.map((result) => ({ name: String(result.value) })),
196
+ models: results.map((result) => ({
197
+ provider: 'google',
198
+ name: String(result.value),
199
+ model: String(result.value),
200
+ })),
163
201
  };
164
202
  }
165
- async createChatWorkflow(user, sessionId, history, humanMessage, credentials, model) {
166
- const { nodes, connections, startNodes, triggerToStartFrom } = this.prepareChatWorkflow(sessionId, history, humanMessage, credentials, model);
167
- const { manager } = this.projectRepository;
168
- return await manager.transaction(async (trx) => {
169
- const project = await this.projectRepository.getPersonalProjectForUser(user.id, trx);
170
- if (!project) {
171
- throw new not_found_error_1.NotFoundError('Could not find a personal project for this user');
172
- }
203
+ async fetchCustomAgentWorkflows(user) {
204
+ const nodeTypes = [n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE];
205
+ const workflows = await this.workflowService.getWorkflowsWithNodesIncluded(user, nodeTypes);
206
+ return {
207
+ models: workflows
208
+ .filter((workflow) => workflow.active)
209
+ .map((workflow) => ({
210
+ provider: 'n8n',
211
+ name: workflow.name ?? 'Unnamed workflow',
212
+ workflowId: workflow.id,
213
+ })),
214
+ };
215
+ }
216
+ async createChatWorkflow(sessionId, projectId, history, humanMessage, credentials, model, generateConversationTitle, trx) {
217
+ return await (0, db_1.withTransaction)(this.workflowRepository.manager, trx, async (em) => {
218
+ const { nodes, connections, triggerToStartFrom } = this.prepareChatWorkflow({
219
+ sessionId,
220
+ history,
221
+ humanMessage,
222
+ credentials,
223
+ model,
224
+ generateConversationTitle,
225
+ });
173
226
  const newWorkflow = new db_1.WorkflowEntity();
174
227
  newWorkflow.versionId = (0, uuid_1.v4)();
175
228
  newWorkflow.name = `Chat ${sessionId}`;
176
229
  newWorkflow.active = false;
177
230
  newWorkflow.nodes = nodes;
178
231
  newWorkflow.connections = connections;
179
- const workflow = await trx.save(newWorkflow);
180
- await trx.save(this.sharedWorkflowRepository.create({
232
+ const workflow = await em.save(newWorkflow);
233
+ await em.save(this.sharedWorkflowRepository.create({
181
234
  role: 'workflow:owner',
182
- projectId: project.id,
235
+ projectId,
183
236
  workflow,
184
237
  }));
185
238
  return {
@@ -189,11 +242,22 @@ let ChatHubService = class ChatHubService {
189
242
  connections,
190
243
  versionId: (0, uuid_1.v4)(),
191
244
  },
192
- startNodes,
193
245
  triggerToStartFrom,
194
246
  };
195
247
  });
196
248
  }
249
+ async ensureCredentials(user, model, credentials, trx) {
250
+ const allCredentials = await this.credentialsFinderService.findAllCredentialsForUser(user, ['credential:read'], trx);
251
+ const credentialId = this.pickCredentialId(model.provider, credentials);
252
+ if (!credentialId) {
253
+ throw new bad_request_error_1.BadRequestError('No credentials provided for the selected model provider');
254
+ }
255
+ const credential = allCredentials.find((c) => c.id === credentialId);
256
+ if (!credential) {
257
+ throw new forbidden_error_1.ForbiddenError("You don't have access to the provided credentials");
258
+ }
259
+ return credential;
260
+ }
197
261
  async deleteChatWorkflow(workflowId) {
198
262
  await this.workflowRepository.delete(workflowId);
199
263
  }
@@ -203,8 +267,8 @@ let ChatHubService = class ChatHubService {
203
267
  }
204
268
  return undefined;
205
269
  }
206
- getAIOutput(execution) {
207
- const agent = execution.data.resultData.runData[NODE_NAMES.AI_AGENT];
270
+ getAIOutput(execution, nodeName) {
271
+ const agent = execution.data.resultData.runData[nodeName];
208
272
  if (!agent || !Array.isArray(agent) || agent.length === 0)
209
273
  return undefined;
210
274
  const runIndex = agent.length - 1;
@@ -220,49 +284,131 @@ let ChatHubService = class ChatHubService {
220
284
  }
221
285
  return undefined;
222
286
  }
223
- getCredentialId(provider, credentials) {
287
+ pickCredentialId(provider, credentials) {
288
+ if (provider === 'n8n') {
289
+ return null;
290
+ }
224
291
  return credentials[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]?.id ?? null;
225
292
  }
226
293
  async sendHumanMessage(res, user, payload) {
227
294
  const { sessionId, messageId, replyId, message } = payload;
295
+ const provider = payload.model.provider;
228
296
  const selectedModel = {
229
297
  ...payload.model,
230
- credentialId: this.getCredentialId(payload.model.provider, payload.credentials),
298
+ credentialId: provider !== 'n8n' ? this.pickCredentialId(provider, payload.credentials) : null,
231
299
  };
232
- const session = await this.getChatSession(user, sessionId, selectedModel, true, message);
233
- if (payload.previousMessageId) {
234
- const previousMessage = await this.messageRepository.getOneById(payload.previousMessageId, sessionId);
235
- if (!previousMessage) {
236
- throw new bad_request_error_1.BadRequestError('The previous message does not exist in the session');
300
+ const workflow = await this.messageRepository.manager.transaction(async (trx) => {
301
+ const session = await this.getChatSession(user, sessionId, selectedModel, true, trx);
302
+ await this.ensurePreviousMessage(payload.previousMessageId, sessionId, trx);
303
+ const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
304
+ const history = this.buildMessageHistory(messages, payload.previousMessageId);
305
+ await this.saveHumanMessage(payload, user, payload.previousMessageId, selectedModel, undefined, trx);
306
+ if (provider !== 'n8n') {
307
+ return await this.prepareBaseChatWorkflow(user, payload, sessionId, history, message, trx);
237
308
  }
238
- }
239
- const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
240
- const history = this.buildMessageHistory(messages, payload.previousMessageId);
241
- await this.saveHumanMessage(payload, user, payload.previousMessageId, selectedModel);
242
- const workflow = await this.createChatWorkflow(user, session.id, history, message, payload.credentials, payload.model);
309
+ return await this.prepareCustomAgentWorkflow(user, sessionId, payload.model.workflowId, message);
310
+ });
243
311
  try {
244
312
  await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
245
313
  }
246
314
  finally {
247
- await this.deleteChatWorkflow(workflow.workflowData.id);
315
+ if (provider !== 'n8n') {
316
+ await this.deleteChatWorkflow(workflow.workflowData.id);
317
+ }
248
318
  }
249
319
  }
250
- async editHumanMessage(res, user, payload) {
251
- const { sessionId, editId, messageId, message, replyId } = payload;
320
+ async prepareBaseChatWorkflow(user, payload, sessionId, history, message, trx) {
321
+ const credential = await this.ensureCredentials(user, payload.model, payload.credentials, trx);
322
+ return await this.createChatWorkflow(sessionId, credential.projectId, history, message, payload.credentials, payload.model, payload.previousMessageId === null, trx);
323
+ }
324
+ async prepareCustomAgentWorkflow(user, sessionId, workflowId, message) {
325
+ const workflowEntity = await this.workflowFinderService.findWorkflowForUser(workflowId, user, ['workflow:read'], { includeTags: false, includeParentFolder: false });
326
+ if (!workflowEntity) {
327
+ throw new bad_request_error_1.BadRequestError('Workflow not found');
328
+ }
329
+ const chatTriggers = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE);
330
+ if (chatTriggers.length !== 1) {
331
+ throw new bad_request_error_1.BadRequestError('Workflow must have exactly one chat trigger');
332
+ }
333
+ const chatResponseNodes = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.RESPOND_TO_CHAT_NODE_TYPE);
334
+ if (chatResponseNodes.length > 0) {
335
+ throw new bad_request_error_1.BadRequestError('Respond to Chat nodes are not supported in custom agent workflows');
336
+ }
337
+ const agents = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE);
338
+ if (agents.length !== 1) {
339
+ throw new bad_request_error_1.BadRequestError('Workflow must have exactly one AI Agent node');
340
+ }
341
+ return {
342
+ workflowData: {
343
+ ...workflowEntity,
344
+ pinData: {},
345
+ },
346
+ triggerToStartFrom: {
347
+ name: chatTriggers[0].name,
348
+ data: {
349
+ startTime: Date.now(),
350
+ executionTime: 0,
351
+ executionIndex: 0,
352
+ executionStatus: 'success',
353
+ data: {
354
+ main: [
355
+ [
356
+ {
357
+ json: {
358
+ sessionId,
359
+ action: 'sendMessage',
360
+ chatInput: message,
361
+ },
362
+ },
363
+ ],
364
+ ],
365
+ },
366
+ source: [null],
367
+ },
368
+ },
369
+ };
370
+ }
371
+ async ensurePreviousMessage(previousMessageId, sessionId, trx) {
372
+ if (!previousMessageId) {
373
+ return;
374
+ }
375
+ const previousMessage = await this.messageRepository.getOneById(previousMessageId, sessionId, [], trx);
376
+ if (!previousMessage) {
377
+ throw new bad_request_error_1.BadRequestError('The previous message does not exist in the session');
378
+ }
379
+ }
380
+ async editMessage(res, user, payload) {
381
+ const { sessionId, editId, messageId, replyId } = payload;
252
382
  const selectedModel = {
253
383
  ...payload.model,
254
- credentialId: this.getCredentialId(payload.model.provider, payload.credentials),
384
+ credentialId: payload.model.provider !== 'n8n'
385
+ ? this.pickCredentialId(payload.model.provider, payload.credentials)
386
+ : null,
255
387
  };
256
- const session = await this.getChatSession(user, sessionId);
257
- const messageToEdit = await this.getChatMessage(session.id, editId);
258
- if (messageToEdit.type !== 'human') {
259
- throw new bad_request_error_1.BadRequestError('Can only edit human messages');
260
- }
261
- const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
262
- const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId);
263
- const revisionOfMessageId = messageToEdit.revisionOfMessageId ?? messageToEdit.id;
264
- await this.saveHumanMessage(payload, user, messageToEdit.previousMessageId, selectedModel, revisionOfMessageId);
265
- const workflow = await this.createChatWorkflow(user, session.id, history, message, payload.credentials, payload.model);
388
+ const workflow = await this.messageRepository.manager.transaction(async (trx) => {
389
+ const credential = await this.ensureCredentials(user, payload.model, payload.credentials, trx);
390
+ const session = await this.getChatSession(user, sessionId, undefined, false, trx);
391
+ const messageToEdit = await this.getChatMessage(session.id, editId, [], trx);
392
+ if (!['ai', 'human'].includes(messageToEdit.type)) {
393
+ throw new bad_request_error_1.BadRequestError('Only human and AI messages can be edited');
394
+ }
395
+ if (messageToEdit.type === 'ai') {
396
+ await this.messageRepository.updateChatMessage(editId, { content: payload.message }, trx);
397
+ return null;
398
+ }
399
+ if (messageToEdit.type === 'human') {
400
+ const { message } = payload;
401
+ const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
402
+ const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId);
403
+ const revisionOfMessageId = messageToEdit.revisionOfMessageId ?? messageToEdit.id;
404
+ await this.saveHumanMessage(payload, user, messageToEdit.previousMessageId, selectedModel, revisionOfMessageId, trx);
405
+ return await this.createChatWorkflow(session.id, credential.projectId, history, message, payload.credentials, payload.model, messageToEdit.previousMessageId === null, trx);
406
+ }
407
+ return null;
408
+ });
409
+ if (!workflow) {
410
+ return;
411
+ }
266
412
  try {
267
413
  await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
268
414
  }
@@ -274,27 +420,37 @@ let ChatHubService = class ChatHubService {
274
420
  const { sessionId, retryId, replyId } = payload;
275
421
  const selectedModel = {
276
422
  ...payload.model,
277
- credentialId: this.getCredentialId(payload.model.provider, payload.credentials),
423
+ credentialId: payload.model.provider !== 'n8n'
424
+ ? this.pickCredentialId(payload.model.provider, payload.credentials)
425
+ : null,
278
426
  };
279
- const session = await this.getChatSession(user, sessionId);
280
- const messageToRetry = await this.getChatMessage(session.id, retryId);
281
- if (messageToRetry.type !== 'ai') {
282
- throw new bad_request_error_1.BadRequestError('Can only retry AI messages');
283
- }
284
- const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
285
- const history = this.buildMessageHistory(messages, messageToRetry.previousMessageId);
286
- const lastHumanMessage = history.filter((m) => m.type === 'human').pop();
287
- if (!lastHumanMessage) {
288
- throw new bad_request_error_1.BadRequestError('No human message found to base the retry on');
289
- }
290
- const lastHumanMessageIndex = history.indexOf(lastHumanMessage);
291
- if (lastHumanMessageIndex !== -1) {
292
- history.splice(lastHumanMessageIndex + 1);
293
- }
294
- const retryOfMessageId = messageToRetry.retryOfMessageId ?? messageToRetry.id;
295
- const workflow = await this.createChatWorkflow(user, session.id, history, lastHumanMessage ? lastHumanMessage.content : '', payload.credentials, payload.model);
427
+ const { workflow, retryOfMessageId, previousMessageId } = await this.messageRepository.manager.transaction(async (trx) => {
428
+ const credential = await this.ensureCredentials(user, payload.model, payload.credentials, trx);
429
+ const session = await this.getChatSession(user, sessionId, undefined, false, trx);
430
+ const messageToRetry = await this.getChatMessage(session.id, retryId, [], trx);
431
+ if (messageToRetry.type !== 'ai') {
432
+ throw new bad_request_error_1.BadRequestError('Can only retry AI messages');
433
+ }
434
+ const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
435
+ const history = this.buildMessageHistory(messages, messageToRetry.previousMessageId);
436
+ const lastHumanMessage = history.filter((m) => m.type === 'human').pop();
437
+ if (!lastHumanMessage) {
438
+ throw new bad_request_error_1.BadRequestError('No human message found to base the retry on');
439
+ }
440
+ const lastHumanMessageIndex = history.indexOf(lastHumanMessage);
441
+ if (lastHumanMessageIndex !== -1) {
442
+ history.splice(lastHumanMessageIndex + 1);
443
+ }
444
+ const retryOfMessageId = messageToRetry.retryOfMessageId ?? messageToRetry.id;
445
+ const workflow = await this.createChatWorkflow(session.id, credential.projectId, history, lastHumanMessage ? lastHumanMessage.content : '', payload.credentials, payload.model, false, trx);
446
+ return {
447
+ workflow,
448
+ previousMessageId: lastHumanMessage.id,
449
+ retryOfMessageId,
450
+ };
451
+ });
296
452
  try {
297
- await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, lastHumanMessage.id, selectedModel, retryOfMessageId);
453
+ await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, previousMessageId, selectedModel, retryOfMessageId);
298
454
  }
299
455
  finally {
300
456
  await this.deleteChatWorkflow(workflow.workflowData.id);
@@ -319,13 +475,22 @@ let ChatHubService = class ChatHubService {
319
475
  await this.messageRepository.updateChatMessage(messageId, { status: 'cancelled' });
320
476
  }
321
477
  async executeChatWorkflow(res, user, workflow, replyId, sessionId, previousMessageId, selectedModel, retryOfMessageId) {
322
- const { workflowData, startNodes, triggerToStartFrom } = workflow;
478
+ const { workflowData, triggerToStartFrom } = workflow;
323
479
  this.logger.debug(`Starting execution of workflow "${workflowData.name}" with ID ${workflowData.id}`);
480
+ let partialMessage = '';
481
+ const onChunk = (chunk) => {
482
+ const data = (0, n8n_workflow_1.jsonParse)(chunk);
483
+ if (data && data.type === 'item' && typeof data.content === 'string') {
484
+ partialMessage += data.content;
485
+ }
486
+ };
487
+ const stream = (0, stream_capturer_1.captureResponseWrites)(res, onChunk);
488
+ stream.writeHead(200, JSONL_STREAM_HEADERS);
489
+ stream.flushHeaders();
324
490
  const { executionId } = await this.workflowExecutionService.executeManually({
325
491
  workflowData,
326
- startNodes,
327
492
  triggerToStartFrom,
328
- }, user, undefined, true, res);
493
+ }, user, undefined, true, stream);
329
494
  if (!executionId) {
330
495
  throw new n8n_workflow_1.OperationalError('There was a problem starting the chat execution.');
331
496
  }
@@ -334,7 +499,7 @@ let ChatHubService = class ChatHubService {
334
499
  sessionId,
335
500
  executionId,
336
501
  previousMessageId,
337
- message: '',
502
+ message: partialMessage,
338
503
  selectedModel,
339
504
  retryOfMessageId,
340
505
  status: 'running',
@@ -356,9 +521,8 @@ let ChatHubService = class ChatHubService {
356
521
  throw new n8n_workflow_1.OperationalError(`Could not find execution with ID ${executionId}`);
357
522
  }
358
523
  if (execution.status === 'canceled') {
359
- const message = 'Generation cancelled.';
360
524
  await this.messageRepository.updateChatMessage(replyId, {
361
- content: message,
525
+ content: partialMessage || 'Generation cancelled.',
362
526
  status: 'cancelled',
363
527
  });
364
528
  return;
@@ -376,14 +540,14 @@ let ChatHubService = class ChatHubService {
376
540
  const message = this.getErrorMessage(execution) ?? 'Failed to generate a response';
377
541
  throw new n8n_workflow_1.OperationalError(message);
378
542
  }
379
- const message = this.getAIOutput(execution);
380
- if (!message) {
381
- throw new n8n_workflow_1.OperationalError('No response generated');
382
- }
383
543
  await this.messageRepository.updateChatMessage(replyId, {
384
- content: message,
544
+ content: partialMessage,
385
545
  status: 'success',
386
546
  });
547
+ const title = this.getAIOutput(execution, NODE_NAMES.TITLE_GENERATOR_AGENT);
548
+ if (title) {
549
+ await this.sessionRepository.updateChatTitle(sessionId, title);
550
+ }
387
551
  }
388
552
  catch (error) {
389
553
  const message = error instanceof Error ? error.message : 'Unknown error';
@@ -393,7 +557,7 @@ let ChatHubService = class ChatHubService {
393
557
  });
394
558
  }
395
559
  }
396
- prepareChatWorkflow(sessionId, history, humanMessage, credentials, model) {
560
+ prepareChatWorkflow({ sessionId, history, humanMessage, credentials, model, generateConversationTitle, }) {
397
561
  const nodes = [
398
562
  {
399
563
  parameters: {
@@ -414,14 +578,16 @@ let ChatHubService = class ChatHubService {
414
578
  text: "={{ $('When chat message received').item.json.chatInput }}",
415
579
  options: {
416
580
  enableStreaming: true,
417
- maxTokensFromMemory: (0, context_limits_1.getMaxContextWindowTokens)(model.provider, model.model),
581
+ maxTokensFromMemory: model.provider !== 'n8n'
582
+ ? (0, context_limits_1.getMaxContextWindowTokens)(model.provider, model.model)
583
+ : undefined,
418
584
  },
419
585
  },
420
586
  type: n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE,
421
587
  typeVersion: 3,
422
588
  position: [600, 0],
423
589
  id: (0, uuid_1.v4)(),
424
- name: NODE_NAMES.AI_AGENT,
590
+ name: NODE_NAMES.REPLY_AGENT,
425
591
  },
426
592
  this.createModelNode(credentials, model),
427
593
  {
@@ -472,6 +638,22 @@ let ChatHubService = class ChatHubService {
472
638
  id: (0, uuid_1.v4)(),
473
639
  name: NODE_NAMES.CLEAR_CHAT_MEMORY,
474
640
  },
641
+ {
642
+ disabled: !generateConversationTitle,
643
+ parameters: {
644
+ promptType: 'define',
645
+ text: "={{ $('When chat message received').item.json.chatInput }}",
646
+ options: {
647
+ enableStreaming: false,
648
+ systemMessage: chat_hub_constants_1.CONVERSATION_TITLE_GENERATION_PROMPT,
649
+ },
650
+ },
651
+ type: n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE,
652
+ typeVersion: 3,
653
+ position: [224, 360],
654
+ id: (0, uuid_1.v4)(),
655
+ name: NODE_NAMES.TITLE_GENERATOR_AGENT,
656
+ },
475
657
  ];
476
658
  const connections = {
477
659
  [NODE_NAMES.CHAT_TRIGGER]: {
@@ -480,23 +662,35 @@ let ChatHubService = class ChatHubService {
480
662
  ],
481
663
  },
482
664
  [NODE_NAMES.RESTORE_CHAT_MEMORY]: {
483
- main: [[{ node: NODE_NAMES.AI_AGENT, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 }]],
665
+ main: [
666
+ [
667
+ { node: NODE_NAMES.REPLY_AGENT, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 },
668
+ { node: NODE_NAMES.TITLE_GENERATOR_AGENT, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 },
669
+ ],
670
+ ],
484
671
  },
485
672
  [NODE_NAMES.CHAT_MODEL]: {
486
673
  ai_languageModel: [
487
- [{ node: NODE_NAMES.AI_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiLanguageModel, index: 0 }],
674
+ [
675
+ { node: NODE_NAMES.REPLY_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiLanguageModel, index: 0 },
676
+ {
677
+ node: NODE_NAMES.TITLE_GENERATOR_AGENT,
678
+ type: n8n_workflow_1.NodeConnectionTypes.AiLanguageModel,
679
+ index: 0,
680
+ },
681
+ ],
488
682
  ],
489
683
  },
490
684
  [NODE_NAMES.MEMORY]: {
491
685
  ai_memory: [
492
686
  [
493
- { node: NODE_NAMES.AI_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
687
+ { node: NODE_NAMES.REPLY_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
494
688
  { node: NODE_NAMES.RESTORE_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
495
689
  { node: NODE_NAMES.CLEAR_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
496
690
  ],
497
691
  ],
498
692
  },
499
- [NODE_NAMES.AI_AGENT]: {
693
+ [NODE_NAMES.REPLY_AGENT]: {
500
694
  main: [
501
695
  [
502
696
  {
@@ -508,7 +702,6 @@ let ChatHubService = class ChatHubService {
508
702
  ],
509
703
  },
510
704
  };
511
- const startNodes = [{ name: 'Restore Chat Memory', sourceData: null }];
512
705
  const triggerToStartFrom = {
513
706
  name: NODE_NAMES.CHAT_TRIGGER,
514
707
  data: {
@@ -532,9 +725,9 @@ let ChatHubService = class ChatHubService {
532
725
  source: [null],
533
726
  },
534
727
  };
535
- return { nodes, connections, startNodes, triggerToStartFrom };
728
+ return { nodes, connections, triggerToStartFrom };
536
729
  }
537
- async saveHumanMessage(payload, user, previousMessageId, selectedModel, revisionOfMessageId) {
730
+ async saveHumanMessage(payload, user, previousMessageId, selectedModel, revisionOfMessageId, trx) {
538
731
  await this.messageRepository.createChatMessage({
539
732
  id: payload.messageId,
540
733
  sessionId: payload.sessionId,
@@ -545,7 +738,7 @@ let ChatHubService = class ChatHubService {
545
738
  previousMessageId,
546
739
  revisionOfMessageId,
547
740
  ...selectedModel,
548
- });
741
+ }, trx);
549
742
  }
550
743
  async saveAIMessage({ id, sessionId, executionId, previousMessageId, message, selectedModel, retryOfMessageId, status, }) {
551
744
  await this.messageRepository.createChatMessage({
@@ -561,8 +754,8 @@ let ChatHubService = class ChatHubService {
561
754
  ...selectedModel,
562
755
  });
563
756
  }
564
- async getChatSession(user, sessionId, selectedModel, initialize = false, title = null) {
565
- const existing = await this.sessionRepository.getOneById(sessionId, user.id);
757
+ async getChatSession(user, sessionId, selectedModel, initialize = false, trx) {
758
+ const existing = await this.sessionRepository.getOneById(sessionId, user.id, trx);
566
759
  if (existing) {
567
760
  return existing;
568
761
  }
@@ -572,20 +765,24 @@ let ChatHubService = class ChatHubService {
572
765
  return await this.sessionRepository.createChatSession({
573
766
  id: sessionId,
574
767
  ownerId: user.id,
575
- title: title ?? 'New Chat',
768
+ title: 'New Chat',
576
769
  ...selectedModel,
577
- });
770
+ }, trx);
578
771
  }
579
- async getChatMessage(sessionId, messageId, relations = []) {
580
- const message = await this.messageRepository.getOneById(messageId, sessionId, relations);
772
+ async getChatMessage(sessionId, messageId, relations = [], trx) {
773
+ const message = await this.messageRepository.getOneById(messageId, sessionId, relations, trx);
581
774
  if (!message) {
582
775
  throw new not_found_error_1.NotFoundError('Chat message not found');
583
776
  }
584
777
  return message;
585
778
  }
586
- createModelNode(credentials, { provider, model }) {
779
+ createModelNode(credentials, conversationModel) {
780
+ if (conversationModel.provider === 'n8n') {
781
+ throw new n8n_workflow_1.OperationalError('Custom agent workflows do not require a model node');
782
+ }
783
+ const { provider, model } = conversationModel;
587
784
  const common = {
588
- position: [600, 200],
785
+ position: [600, 500],
589
786
  id: (0, uuid_1.v4)(),
590
787
  name: 'Chat Model',
591
788
  credentials,
@@ -719,16 +916,17 @@ exports.ChatHubService = ChatHubService;
719
916
  exports.ChatHubService = ChatHubService = __decorate([
720
917
  (0, di_1.Service)(),
721
918
  __metadata("design:paramtypes", [backend_common_1.Logger,
722
- credentials_service_1.CredentialsService,
723
919
  execution_service_1.ExecutionService,
724
920
  dynamic_node_parameters_service_1.DynamicNodeParametersService,
725
921
  db_1.ExecutionRepository,
726
922
  workflow_execution_service_1.WorkflowExecutionService,
923
+ workflow_service_1.WorkflowService,
924
+ workflow_finder_service_1.WorkflowFinderService,
727
925
  db_1.WorkflowRepository,
728
- db_1.ProjectRepository,
729
926
  db_1.SharedWorkflowRepository,
730
927
  active_executions_1.ActiveExecutions,
731
928
  chat_session_repository_1.ChatHubSessionRepository,
732
- chat_message_repository_1.ChatHubMessageRepository])
929
+ chat_message_repository_1.ChatHubMessageRepository,
930
+ credentials_finder_service_1.CredentialsFinderService])
733
931
  ], ChatHubService);
734
932
  //# sourceMappingURL=chat-hub.service.js.map