n8n 1.118.2 → 1.119.1

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 (160) hide show
  1. package/dist/build.tsbuildinfo +1 -1
  2. package/dist/controllers/oauth/oauth2-credential.controller.d.ts +2 -0
  3. package/dist/controllers/oauth/oauth2-credential.controller.js +85 -1
  4. package/dist/controllers/oauth/oauth2-credential.controller.js.map +1 -1
  5. package/dist/controllers/users.controller.d.ts +1 -1
  6. package/dist/credentials-helper.js +8 -0
  7. package/dist/credentials-helper.js.map +1 -1
  8. package/dist/environments.ee/source-control/source-control-status.service.ee.d.ts +2 -2
  9. package/dist/environments.ee/source-control/source-control.controller.ee.d.ts +4 -4
  10. package/dist/environments.ee/source-control/source-control.service.ee.d.ts +2 -2
  11. package/dist/errors/response-errors/abstract/response.error.d.ts +1 -0
  12. package/dist/errors/response-errors/abstract/response.error.js.map +1 -1
  13. package/dist/errors/response-errors/license-eula-required.error.d.ts +9 -0
  14. package/dist/errors/response-errors/license-eula-required.error.js +13 -0
  15. package/dist/errors/response-errors/license-eula-required.error.js.map +1 -0
  16. package/dist/events/relays/telemetry.event-relay.js +3 -0
  17. package/dist/events/relays/telemetry.event-relay.js.map +1 -1
  18. package/dist/license/license.controller.js +2 -2
  19. package/dist/license/license.controller.js.map +1 -1
  20. package/dist/license/license.service.d.ts +3 -1
  21. package/dist/license/license.service.js +30 -5
  22. package/dist/license/license.service.js.map +1 -1
  23. package/dist/license.d.ts +1 -1
  24. package/dist/license.js +2 -2
  25. package/dist/license.js.map +1 -1
  26. package/dist/modules/breaking-changes/breaking-changes.controller.d.ts +12 -0
  27. package/dist/modules/breaking-changes/breaking-changes.controller.js +50 -0
  28. package/dist/modules/breaking-changes/breaking-changes.controller.js.map +1 -0
  29. package/dist/modules/breaking-changes/breaking-changes.module.d.ts +4 -0
  30. package/dist/modules/breaking-changes/breaking-changes.module.js +53 -0
  31. package/dist/modules/breaking-changes/breaking-changes.module.js.map +1 -0
  32. package/dist/modules/breaking-changes/breaking-changes.rule-registry.service.d.ts +11 -0
  33. package/dist/modules/breaking-changes/breaking-changes.rule-registry.service.js +47 -0
  34. package/dist/modules/breaking-changes/breaking-changes.rule-registry.service.js.map +1 -0
  35. package/dist/modules/breaking-changes/breaking-changes.service.d.ts +24 -0
  36. package/dist/modules/breaking-changes/breaking-changes.service.js +189 -0
  37. package/dist/modules/breaking-changes/breaking-changes.service.js.map +1 -0
  38. package/dist/modules/breaking-changes/rules/index.d.ts +4 -0
  39. package/dist/modules/breaking-changes/rules/index.js +7 -0
  40. package/dist/modules/breaking-changes/rules/index.js.map +1 -0
  41. package/dist/modules/breaking-changes/rules/v2/file-access.rule.d.ts +10 -0
  42. package/dist/modules/breaking-changes/rules/v2/file-access.rule.js +52 -0
  43. package/dist/modules/breaking-changes/rules/v2/file-access.rule.js.map +1 -0
  44. package/dist/modules/breaking-changes/rules/v2/index.d.ts +3 -0
  45. package/dist/modules/breaking-changes/rules/v2/index.js +9 -0
  46. package/dist/modules/breaking-changes/rules/v2/index.js.map +1 -0
  47. package/dist/modules/breaking-changes/rules/v2/process-env-access.rule.d.ts +9 -0
  48. package/dist/modules/breaking-changes/rules/v2/process-env-access.rule.js +71 -0
  49. package/dist/modules/breaking-changes/rules/v2/process-env-access.rule.js.map +1 -0
  50. package/dist/modules/breaking-changes/rules/v2/removed-nodes.rule.d.ts +10 -0
  51. package/dist/modules/breaking-changes/rules/v2/removed-nodes.rule.js +56 -0
  52. package/dist/modules/breaking-changes/rules/v2/removed-nodes.rule.js.map +1 -0
  53. package/dist/modules/breaking-changes/types/detection.types.d.ts +11 -0
  54. package/dist/modules/breaking-changes/types/detection.types.js +3 -0
  55. package/dist/modules/breaking-changes/types/detection.types.js.map +1 -0
  56. package/dist/modules/breaking-changes/types/index.d.ts +2 -0
  57. package/dist/modules/breaking-changes/types/index.js +18 -0
  58. package/dist/modules/breaking-changes/types/index.js.map +1 -0
  59. package/dist/modules/breaking-changes/types/rule.types.d.ts +71 -0
  60. package/dist/modules/breaking-changes/types/rule.types.js +25 -0
  61. package/dist/modules/breaking-changes/types/rule.types.js.map +1 -0
  62. package/dist/modules/chat-hub/chat-hub-agent.entity.d.ts +14 -0
  63. package/dist/modules/chat-hub/chat-hub-agent.entity.js +63 -0
  64. package/dist/modules/chat-hub/chat-hub-agent.entity.js.map +1 -0
  65. package/dist/modules/chat-hub/chat-hub-agent.repository.d.ts +10 -0
  66. package/dist/modules/chat-hub/chat-hub-agent.repository.js +61 -0
  67. package/dist/modules/chat-hub/chat-hub-agent.repository.js.map +1 -0
  68. package/dist/modules/chat-hub/chat-hub-agent.service.d.ts +32 -0
  69. package/dist/modules/chat-hub/chat-hub-agent.service.js +107 -0
  70. package/dist/modules/chat-hub/chat-hub-agent.service.js.map +1 -0
  71. package/dist/modules/chat-hub/chat-hub-credentials.service.d.ts +15 -0
  72. package/dist/modules/chat-hub/chat-hub-credentials.service.js +54 -0
  73. package/dist/modules/chat-hub/chat-hub-credentials.service.js.map +1 -0
  74. package/dist/modules/chat-hub/chat-hub-message.entity.d.ts +1 -0
  75. package/dist/modules/chat-hub/chat-hub-message.entity.js +4 -0
  76. package/dist/modules/chat-hub/chat-hub-message.entity.js.map +1 -1
  77. package/dist/modules/chat-hub/chat-hub-session.entity.d.ts +7 -4
  78. package/dist/modules/chat-hub/chat-hub-session.entity.js +9 -1
  79. package/dist/modules/chat-hub/chat-hub-session.entity.js.map +1 -1
  80. package/dist/modules/chat-hub/chat-hub-workflow.service.d.ts +29 -0
  81. package/dist/modules/chat-hub/chat-hub-workflow.service.js +382 -0
  82. package/dist/modules/chat-hub/chat-hub-workflow.service.js.map +1 -0
  83. package/dist/modules/chat-hub/chat-hub.constants.d.ts +18 -0
  84. package/dist/modules/chat-hub/chat-hub.constants.js +30 -1
  85. package/dist/modules/chat-hub/chat-hub.constants.js.map +1 -1
  86. package/dist/modules/chat-hub/chat-hub.controller.d.ts +10 -3
  87. package/dist/modules/chat-hub/chat-hub.controller.js +68 -7
  88. package/dist/modules/chat-hub/chat-hub.controller.js.map +1 -1
  89. package/dist/modules/chat-hub/chat-hub.module.d.ts +1 -1
  90. package/dist/modules/chat-hub/chat-hub.module.js +2 -1
  91. package/dist/modules/chat-hub/chat-hub.module.js.map +1 -1
  92. package/dist/modules/chat-hub/chat-hub.service.d.ts +31 -11
  93. package/dist/modules/chat-hub/chat-hub.service.js +519 -498
  94. package/dist/modules/chat-hub/chat-hub.service.js.map +1 -1
  95. package/dist/modules/chat-hub/chat-hub.types.d.ts +2 -1
  96. package/dist/modules/chat-hub/chat-message.repository.js +3 -3
  97. package/dist/modules/chat-hub/chat-message.repository.js.map +1 -1
  98. package/dist/modules/chat-hub/chat-session.repository.d.ts +1 -0
  99. package/dist/modules/chat-hub/chat-session.repository.js +9 -0
  100. package/dist/modules/chat-hub/chat-session.repository.js.map +1 -1
  101. package/dist/modules/chat-hub/dto/chat-models-request.dto.d.ts +1 -1
  102. package/dist/modules/chat-hub/stream-capturer.d.ts +17 -2
  103. package/dist/modules/chat-hub/stream-capturer.js +90 -19
  104. package/dist/modules/chat-hub/stream-capturer.js.map +1 -1
  105. package/dist/modules/data-table/data-table-aggregate.service.js +4 -0
  106. package/dist/modules/data-table/data-table-aggregate.service.js.map +1 -1
  107. package/dist/modules/data-table/data-table.controller.d.ts +7 -1
  108. package/dist/modules/data-table/data-table.controller.js +35 -17
  109. package/dist/modules/data-table/data-table.controller.js.map +1 -1
  110. package/dist/modules/insights/database/repositories/insights-by-period-query.helper.js +1 -1
  111. package/dist/modules/insights/database/repositories/insights-by-period-query.helper.js.map +1 -1
  112. package/dist/modules/insights/database/repositories/insights-by-period.repository.js +1 -1
  113. package/dist/modules/insights/database/repositories/insights-by-period.repository.js.map +1 -1
  114. package/dist/modules/insights/insights-collection.service.js +1 -0
  115. package/dist/modules/insights/insights-collection.service.js.map +1 -1
  116. package/dist/modules/insights/insights.controller.js +2 -15
  117. package/dist/modules/insights/insights.controller.js.map +1 -1
  118. package/dist/modules/insights/insights.service.d.ts +1 -1
  119. package/dist/modules/insights/insights.service.js.map +1 -1
  120. package/dist/modules/workflow-index/workflow-index.service.d.ts +22 -0
  121. package/dist/modules/workflow-index/workflow-index.service.js +166 -0
  122. package/dist/modules/workflow-index/workflow-index.service.js.map +1 -0
  123. package/dist/public-api/v1/handlers/workflows/workflows.handler.js +1 -1
  124. package/dist/public-api/v1/handlers/workflows/workflows.handler.js.map +1 -1
  125. package/dist/public-api/v1/handlers/workflows/workflows.service.d.ts +1 -1
  126. package/dist/public-api/v1/handlers/workflows/workflows.service.js +8 -2
  127. package/dist/public-api/v1/handlers/workflows/workflows.service.js.map +1 -1
  128. package/dist/public-api/v1/openapi.yml +47 -0
  129. package/dist/requests.d.ts +1 -0
  130. package/dist/response-helper.js +3 -0
  131. package/dist/response-helper.js.map +1 -1
  132. package/dist/server.d.ts +1 -0
  133. package/dist/server.js +11 -0
  134. package/dist/server.js.map +1 -1
  135. package/dist/services/frontend.service.d.ts +1 -1
  136. package/dist/services/frontend.service.js +7 -2
  137. package/dist/services/frontend.service.js.map +1 -1
  138. package/dist/services/import.service.d.ts +5 -1
  139. package/dist/services/import.service.js +15 -2
  140. package/dist/services/import.service.js.map +1 -1
  141. package/dist/services/workflow-statistics.service.js +1 -0
  142. package/dist/services/workflow-statistics.service.js.map +1 -1
  143. package/dist/sso.ee/oidc/oidc.service.ee.d.ts +2 -1
  144. package/dist/sso.ee/oidc/oidc.service.ee.js +28 -6
  145. package/dist/sso.ee/oidc/oidc.service.ee.js.map +1 -1
  146. package/dist/sso.ee/saml/routes/saml.controller.ee.d.ts +1 -0
  147. package/dist/sso.ee/saml/routes/saml.controller.ee.js +19 -4
  148. package/dist/sso.ee/saml/routes/saml.controller.ee.js.map +1 -1
  149. package/dist/sso.ee/saml/saml.service.ee.d.ts +1 -1
  150. package/dist/sso.ee/saml/saml.service.ee.js +9 -6
  151. package/dist/sso.ee/saml/saml.service.ee.js.map +1 -1
  152. package/dist/workflow-runner.js +2 -1
  153. package/dist/workflow-runner.js.map +1 -1
  154. package/dist/workflows/workflow-execution.service.d.ts +4 -1
  155. package/dist/workflows/workflow-execution.service.js +14 -0
  156. package/dist/workflows/workflow-execution.service.js.map +1 -1
  157. package/dist/workflows/workflow.service.d.ts +4 -11
  158. package/dist/workflows/workflow.service.js +10 -8
  159. package/dist/workflows/workflow.service.js.map +1 -1
  160. package/package.json +24 -21
@@ -15,11 +15,9 @@ const backend_common_1 = require("@n8n/backend-common");
15
15
  const db_1 = require("@n8n/db");
16
16
  const di_1 = require("@n8n/di");
17
17
  const n8n_workflow_1 = require("n8n-workflow");
18
- const uuid_1 = require("uuid");
19
18
  const active_executions_1 = require("../../active-executions");
20
19
  const credentials_finder_service_1 = require("../../credentials/credentials-finder.service");
21
20
  const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
22
- const forbidden_error_1 = require("../../errors/response-errors/forbidden.error");
23
21
  const not_found_error_1 = require("../../errors/response-errors/not-found.error");
24
22
  const execution_service_1 = require("../../executions/execution.service");
25
23
  const dynamic_node_parameters_service_1 = require("../../services/dynamic-node-parameters.service");
@@ -27,42 +25,15 @@ const workflow_execute_additional_data_1 = require("../../workflow-execute-addit
27
25
  const workflow_execution_service_1 = require("../../workflows/workflow-execution.service");
28
26
  const workflow_finder_service_1 = require("../../workflows/workflow-finder.service");
29
27
  const workflow_service_1 = require("../../workflows/workflow.service");
28
+ const chat_hub_agent_service_1 = require("./chat-hub-agent.service");
29
+ const chat_hub_credentials_service_1 = require("./chat-hub-credentials.service");
30
+ const chat_hub_workflow_service_1 = require("./chat-hub-workflow.service");
30
31
  const chat_hub_constants_1 = require("./chat-hub.constants");
31
32
  const chat_message_repository_1 = require("./chat-message.repository");
32
33
  const chat_session_repository_1 = require("./chat-session.repository");
33
- const context_limits_1 = require("./context-limits");
34
34
  const stream_capturer_1 = require("./stream-capturer");
35
- const providerNodeTypeMapping = {
36
- openai: {
37
- name: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
38
- version: 1.2,
39
- },
40
- anthropic: {
41
- name: '@n8n/n8n-nodes-langchain.lmChatAnthropic',
42
- version: 1.3,
43
- },
44
- google: {
45
- name: '@n8n/n8n-nodes-langchain.lmChatGoogleGemini',
46
- version: 1.2,
47
- },
48
- };
49
- const NODE_NAMES = {
50
- CHAT_TRIGGER: 'When chat message received',
51
- REPLY_AGENT: 'AI Agent',
52
- TITLE_GENERATOR_AGENT: 'Title Generator Agent',
53
- CHAT_MODEL: 'Chat Model',
54
- MEMORY: 'Memory',
55
- RESTORE_CHAT_MEMORY: 'Restore Chat Memory',
56
- CLEAR_CHAT_MEMORY: 'Clear Chat Memory',
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
- };
64
35
  let ChatHubService = class ChatHubService {
65
- constructor(logger, executionService, nodeParametersService, executionRepository, workflowExecutionService, workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, activeExecutions, sessionRepository, messageRepository, credentialsFinderService) {
36
+ constructor(logger, executionService, nodeParametersService, executionRepository, workflowExecutionService, workflowService, workflowFinderService, workflowRepository, activeExecutions, sessionRepository, messageRepository, credentialsFinderService, chatHubAgentService, chatHubCredentialsService, chatHubWorkflowService) {
66
37
  this.logger = logger;
67
38
  this.executionService = executionService;
68
39
  this.nodeParametersService = nodeParametersService;
@@ -71,21 +42,23 @@ let ChatHubService = class ChatHubService {
71
42
  this.workflowService = workflowService;
72
43
  this.workflowFinderService = workflowFinderService;
73
44
  this.workflowRepository = workflowRepository;
74
- this.sharedWorkflowRepository = sharedWorkflowRepository;
75
45
  this.activeExecutions = activeExecutions;
76
46
  this.sessionRepository = sessionRepository;
77
47
  this.messageRepository = messageRepository;
78
48
  this.credentialsFinderService = credentialsFinderService;
49
+ this.chatHubAgentService = chatHubAgentService;
50
+ this.chatHubCredentialsService = chatHubCredentialsService;
51
+ this.chatHubWorkflowService = chatHubWorkflowService;
79
52
  }
80
53
  async getModels(user, credentialIds) {
81
54
  const additionalData = await (0, workflow_execute_additional_data_1.getBase)({ userId: user.id });
82
- const providers = ['openai', 'anthropic', 'google'];
55
+ const providers = api_types_1.chatHubProviderSchema.options;
83
56
  const allCredentials = await this.credentialsFinderService.findCredentialsForUser(user, [
84
57
  'credential:read',
85
58
  ]);
86
59
  const responses = await Promise.all(providers.map(async (provider) => {
87
60
  const credentials = {};
88
- if (provider !== 'n8n') {
61
+ if (provider !== 'n8n' && provider !== 'custom-agent') {
89
62
  const credentialId = credentialIds[provider];
90
63
  if (!credentialId) {
91
64
  return [provider, { models: [] }];
@@ -114,12 +87,7 @@ let ChatHubService = class ChatHubService {
114
87
  return responses.reduce((acc, [provider, res]) => {
115
88
  acc[provider] = res;
116
89
  return acc;
117
- }, {
118
- openai: { models: [] },
119
- anthropic: { models: [] },
120
- google: { models: [] },
121
- n8n: { models: [] },
122
- });
90
+ }, { ...api_types_1.emptyChatModelsResponse });
123
91
  }
124
92
  async fetchModelsForProvider(user, provider, credentials, additionalData) {
125
93
  switch (provider) {
@@ -130,26 +98,38 @@ let ChatHubService = class ChatHubService {
130
98
  case 'google':
131
99
  return await this.fetchGoogleModels(credentials, additionalData);
132
100
  case 'n8n':
133
- return await this.fetchCustomAgentWorkflows(user);
101
+ return await this.fetchAgentWorkflowsAsModels(user);
102
+ case 'custom-agent':
103
+ return await this.chatHubAgentService.getAgentsByUserIdAsModels(user.id);
134
104
  }
135
105
  }
136
106
  async fetchOpenAiModels(credentials, additionalData) {
137
- const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.openai, {}, credentials);
107
+ const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, chat_hub_constants_1.PROVIDER_NODE_TYPE_MAP.openai, {}, credentials);
138
108
  return {
139
109
  models: resourceLocatorResults.results.map((result) => ({
140
- provider: 'openai',
141
- name: String(result.value),
142
- model: String(result.value),
110
+ name: result.name,
111
+ description: result.description ?? null,
112
+ model: {
113
+ provider: 'openai',
114
+ model: String(result.value),
115
+ },
116
+ createdAt: null,
117
+ updatedAt: null,
143
118
  })),
144
119
  };
145
120
  }
146
121
  async fetchAnthropicModels(credentials, additionalData) {
147
- const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, providerNodeTypeMapping.anthropic, {}, credentials);
122
+ const resourceLocatorResults = await this.nodeParametersService.getResourceLocatorResults('searchModels', 'parameters.model', additionalData, chat_hub_constants_1.PROVIDER_NODE_TYPE_MAP.anthropic, {}, credentials);
148
123
  return {
149
124
  models: resourceLocatorResults.results.map((result) => ({
150
- provider: 'anthropic',
151
- name: String(result.value),
152
- model: String(result.value),
125
+ name: result.name,
126
+ description: result.description ?? null,
127
+ model: {
128
+ provider: 'anthropic',
129
+ model: String(result.value),
130
+ },
131
+ createdAt: null,
132
+ updatedAt: null,
153
133
  })),
154
134
  };
155
135
  }
@@ -191,73 +171,57 @@ let ChatHubService = class ChatHubService {
191
171
  ],
192
172
  },
193
173
  },
194
- }, additionalData, providerNodeTypeMapping.google, {}, credentials);
174
+ }, additionalData, chat_hub_constants_1.PROVIDER_NODE_TYPE_MAP.google, {}, credentials);
195
175
  return {
196
176
  models: results.map((result) => ({
197
- provider: 'google',
198
177
  name: String(result.value),
199
- model: String(result.value),
178
+ description: result.description ?? null,
179
+ model: {
180
+ provider: 'google',
181
+ model: String(result.value),
182
+ },
183
+ createdAt: null,
184
+ updatedAt: null,
200
185
  })),
201
186
  };
202
187
  }
203
- async fetchCustomAgentWorkflows(user) {
188
+ async fetchAgentWorkflowsAsModels(user) {
204
189
  const nodeTypes = [n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE];
205
- const workflows = await this.workflowService.getWorkflowsWithNodesIncluded(user, nodeTypes);
190
+ const workflows = await this.workflowService.getWorkflowsWithNodesIncluded(user, nodeTypes, true);
206
191
  return {
207
192
  models: workflows
193
+ .filter((workflow) => workflow.scopes.includes('workflow:read'))
208
194
  .filter((workflow) => workflow.active)
209
- .map((workflow) => ({
210
- provider: 'n8n',
211
- name: workflow.name ?? 'Unnamed workflow',
212
- workflowId: workflow.id,
213
- })),
195
+ .flatMap((workflow) => {
196
+ const chatTrigger = workflow.nodes?.find((node) => node.type === n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE);
197
+ if (!chatTrigger) {
198
+ return [];
199
+ }
200
+ if (chatTrigger.parameters.availableInChat !== true) {
201
+ return [];
202
+ }
203
+ const name = typeof chatTrigger.parameters.agentName === 'string' &&
204
+ chatTrigger.parameters.agentName.length > 0
205
+ ? chatTrigger.parameters.agentName
206
+ : workflow.name;
207
+ return [
208
+ {
209
+ name: name ?? 'Unknown Agent',
210
+ description: typeof chatTrigger.parameters.agentDescription === 'string' &&
211
+ chatTrigger.parameters.agentDescription.length > 0
212
+ ? chatTrigger.parameters.agentDescription
213
+ : null,
214
+ model: {
215
+ provider: 'n8n',
216
+ workflowId: workflow.id,
217
+ },
218
+ createdAt: workflow.createdAt ? workflow.createdAt.toISOString() : null,
219
+ updatedAt: workflow.updatedAt ? workflow.updatedAt.toISOString() : null,
220
+ },
221
+ ];
222
+ }),
214
223
  };
215
224
  }
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
- });
226
- const newWorkflow = new db_1.WorkflowEntity();
227
- newWorkflow.versionId = (0, uuid_1.v4)();
228
- newWorkflow.name = `Chat ${sessionId}`;
229
- newWorkflow.active = false;
230
- newWorkflow.nodes = nodes;
231
- newWorkflow.connections = connections;
232
- const workflow = await em.save(newWorkflow);
233
- await em.save(this.sharedWorkflowRepository.create({
234
- role: 'workflow:owner',
235
- projectId,
236
- workflow,
237
- }));
238
- return {
239
- workflowData: {
240
- ...workflow,
241
- nodes,
242
- connections,
243
- versionId: (0, uuid_1.v4)(),
244
- },
245
- triggerToStartFrom,
246
- };
247
- });
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
- }
261
225
  async deleteChatWorkflow(workflowId) {
262
226
  await this.workflowRepository.delete(workflowId);
263
227
  }
@@ -285,109 +249,42 @@ let ChatHubService = class ChatHubService {
285
249
  return undefined;
286
250
  }
287
251
  pickCredentialId(provider, credentials) {
288
- if (provider === 'n8n') {
252
+ if (provider === 'n8n' || provider === 'custom-agent') {
289
253
  return null;
290
254
  }
291
255
  return credentials[api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[provider]]?.id ?? null;
292
256
  }
293
257
  async sendHumanMessage(res, user, payload) {
294
- const { sessionId, messageId, replyId, message } = payload;
295
- const provider = payload.model.provider;
296
- const selectedModel = {
297
- ...payload.model,
298
- credentialId: provider !== 'n8n' ? this.pickCredentialId(provider, payload.credentials) : null,
299
- };
300
- const workflow = await this.messageRepository.manager.transaction(async (trx) => {
258
+ const { sessionId, messageId, message, model, credentials, previousMessageId } = payload;
259
+ const { provider } = model;
260
+ const selectedModel = this.getModelWithCredentials(model, credentials);
261
+ const { executionData, workflowData } = await this.messageRepository.manager.transaction(async (trx) => {
301
262
  const session = await this.getChatSession(user, sessionId, selectedModel, true, trx);
302
- await this.ensurePreviousMessage(payload.previousMessageId, sessionId, trx);
263
+ await this.ensurePreviousMessage(previousMessageId, sessionId, trx);
303
264
  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);
265
+ const history = this.buildMessageHistory(messages, previousMessageId);
266
+ await this.saveHumanMessage(payload, user, previousMessageId, selectedModel, undefined, trx);
267
+ if (provider === 'n8n') {
268
+ return await this.prepareCustomAgentWorkflow(user, sessionId, model.workflowId, message);
308
269
  }
309
- return await this.prepareCustomAgentWorkflow(user, sessionId, payload.model.workflowId, message);
310
- });
311
- try {
312
- await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
313
- }
314
- finally {
315
- if (provider !== 'n8n') {
316
- await this.deleteChatWorkflow(workflow.workflowData.id);
270
+ if (provider === 'custom-agent') {
271
+ return await this.prepareChatAgentWorkflow(model.agentId, user, sessionId, history, message, trx);
317
272
  }
318
- }
319
- }
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');
273
+ return await this.prepareBaseChatWorkflow(user, sessionId, credentials, model, history, message, undefined, trx);
274
+ });
275
+ await this.executeChatWorkflowWithCleanup(res, user, workflowData, executionData, sessionId, messageId, selectedModel, provider);
276
+ if (previousMessageId === null) {
277
+ await this.generateSessionTitle(user, sessionId, message, credentials, model).catch((error) => {
278
+ this.logger.error(`Title generation failed: ${error}`);
279
+ });
378
280
  }
379
281
  }
380
282
  async editMessage(res, user, payload) {
381
- const { sessionId, editId, messageId, replyId } = payload;
382
- const selectedModel = {
383
- ...payload.model,
384
- credentialId: payload.model.provider !== 'n8n'
385
- ? this.pickCredentialId(payload.model.provider, payload.credentials)
386
- : null,
387
- };
283
+ const { sessionId, editId, messageId, message, model, credentials } = payload;
284
+ const { provider } = model;
285
+ const selectedModel = this.getModelWithCredentials(model, credentials);
388
286
  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);
287
+ const session = await this.getChatSession(user, sessionId, selectedModel, true, trx);
391
288
  const messageToEdit = await this.getChatMessage(session.id, editId, [], trx);
392
289
  if (!['ai', 'human'].includes(messageToEdit.type)) {
393
290
  throw new bad_request_error_1.BadRequestError('Only human and AI messages can be edited');
@@ -397,35 +294,31 @@ let ChatHubService = class ChatHubService {
397
294
  return null;
398
295
  }
399
296
  if (messageToEdit.type === 'human') {
400
- const { message } = payload;
401
297
  const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m]));
402
298
  const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId);
403
299
  const revisionOfMessageId = messageToEdit.revisionOfMessageId ?? messageToEdit.id;
404
300
  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);
301
+ if (provider === 'n8n') {
302
+ return await this.prepareCustomAgentWorkflow(user, sessionId, model.workflowId, message);
303
+ }
304
+ if (provider === 'custom-agent') {
305
+ return await this.prepareChatAgentWorkflow(model.agentId, user, sessionId, history, message, trx);
306
+ }
307
+ return await this.prepareBaseChatWorkflow(user, sessionId, credentials, model, history, message, undefined, trx);
406
308
  }
407
309
  return null;
408
310
  });
409
311
  if (!workflow) {
410
312
  return;
411
313
  }
412
- try {
413
- await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, messageId, selectedModel);
414
- }
415
- finally {
416
- await this.deleteChatWorkflow(workflow.workflowData.id);
417
- }
314
+ const { workflowData, executionData } = workflow;
315
+ await this.executeChatWorkflowWithCleanup(res, user, workflowData, executionData, sessionId, messageId, selectedModel, provider);
418
316
  }
419
317
  async regenerateAIMessage(res, user, payload) {
420
- const { sessionId, retryId, replyId } = payload;
421
- const selectedModel = {
422
- ...payload.model,
423
- credentialId: payload.model.provider !== 'n8n'
424
- ? this.pickCredentialId(payload.model.provider, payload.credentials)
425
- : null,
426
- };
427
- const { workflow, retryOfMessageId, previousMessageId } = await this.messageRepository.manager.transaction(async (trx) => {
428
- const credential = await this.ensureCredentials(user, payload.model, payload.credentials, trx);
318
+ const { sessionId, retryId, model, credentials } = payload;
319
+ const { provider } = model;
320
+ const selectedModel = this.getModelWithCredentials(model, credentials);
321
+ const { workflow: { workflowData, executionData }, retryOfMessageId, previousMessageId, } = await this.messageRepository.manager.transaction(async (trx) => {
429
322
  const session = await this.getChatSession(user, sessionId, undefined, false, trx);
430
323
  const messageToRetry = await this.getChatMessage(session.id, retryId, [], trx);
431
324
  if (messageToRetry.type !== 'ai') {
@@ -442,18 +335,120 @@ let ChatHubService = class ChatHubService {
442
335
  history.splice(lastHumanMessageIndex + 1);
443
336
  }
444
337
  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);
338
+ const message = lastHumanMessage ? lastHumanMessage.content : '';
339
+ let workflow;
340
+ if (provider === 'n8n') {
341
+ workflow = await this.prepareCustomAgentWorkflow(user, sessionId, model.workflowId, message);
342
+ }
343
+ else if (provider === 'custom-agent') {
344
+ workflow = await this.prepareChatAgentWorkflow(model.agentId, user, sessionId, history, message, trx);
345
+ }
346
+ else {
347
+ workflow = await this.prepareBaseChatWorkflow(user, sessionId, credentials, model, history, message, undefined, trx);
348
+ }
446
349
  return {
447
350
  workflow,
448
351
  previousMessageId: lastHumanMessage.id,
449
352
  retryOfMessageId,
450
353
  };
451
354
  });
452
- try {
453
- await this.executeChatWorkflow(res, user, workflow, replyId, sessionId, previousMessageId, selectedModel, retryOfMessageId);
355
+ await this.executeChatWorkflowWithCleanup(res, user, workflowData, executionData, sessionId, previousMessageId, selectedModel, provider, retryOfMessageId);
356
+ }
357
+ async prepareBaseChatWorkflow(user, sessionId, credentials, model, history, message, systemMessage, trx) {
358
+ const credential = await this.chatHubCredentialsService.ensureCredentials(user, model.provider, credentials, trx);
359
+ return await this.chatHubWorkflowService.createChatWorkflow(user.id, sessionId, credential.projectId, history, message, credentials, model, systemMessage, trx);
360
+ }
361
+ async prepareChatAgentWorkflow(agentId, user, sessionId, history, message, trx) {
362
+ const agent = await this.chatHubAgentService.getAgentById(agentId, user.id);
363
+ if (!agent) {
364
+ throw new bad_request_error_1.BadRequestError('Agent not found');
454
365
  }
455
- finally {
456
- await this.deleteChatWorkflow(workflow.workflowData.id);
366
+ if (!agent.provider || !agent.model) {
367
+ throw new bad_request_error_1.BadRequestError('Provider or model not set for agent');
368
+ }
369
+ if (agent.provider === 'n8n' || agent.provider === 'custom-agent') {
370
+ throw new bad_request_error_1.BadRequestError('Invalid provider');
371
+ }
372
+ const credentialId = agent.credentialId;
373
+ if (!credentialId) {
374
+ throw new bad_request_error_1.BadRequestError('Credentials not set for agent');
375
+ }
376
+ const systemMessage = agent.systemPrompt;
377
+ const model = {
378
+ provider: agent.provider,
379
+ model: agent.model,
380
+ };
381
+ const credentials = {
382
+ [api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[agent.provider]]: {
383
+ id: credentialId,
384
+ name: '',
385
+ },
386
+ };
387
+ return await this.prepareBaseChatWorkflow(user, sessionId, credentials, model, history, message, systemMessage, trx);
388
+ }
389
+ async prepareCustomAgentWorkflow(user, sessionId, workflowId, message) {
390
+ const workflowEntity = await this.workflowFinderService.findWorkflowForUser(workflowId, user, ['workflow:read'], { includeTags: false, includeParentFolder: false });
391
+ if (!workflowEntity) {
392
+ throw new bad_request_error_1.BadRequestError('Workflow not found');
393
+ }
394
+ const chatTriggers = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE);
395
+ if (chatTriggers.length !== 1) {
396
+ throw new bad_request_error_1.BadRequestError('Workflow must have exactly one chat trigger');
397
+ }
398
+ const chatTriggerNode = chatTriggers[0];
399
+ const chatResponseNodes = workflowEntity.nodes.filter((node) => node.type === n8n_workflow_1.RESPOND_TO_CHAT_NODE_TYPE);
400
+ if (chatResponseNodes.length > 0) {
401
+ throw new bad_request_error_1.BadRequestError('Respond to Chat nodes are not supported in custom agent workflows');
402
+ }
403
+ const nodeExecutionStack = [
404
+ {
405
+ node: chatTriggerNode,
406
+ data: {
407
+ main: [
408
+ [
409
+ {
410
+ json: {
411
+ sessionId,
412
+ action: 'sendMessage',
413
+ chatInput: message,
414
+ },
415
+ },
416
+ ],
417
+ ],
418
+ },
419
+ source: null,
420
+ },
421
+ ];
422
+ const executionData = {
423
+ startData: {},
424
+ resultData: {
425
+ runData: {},
426
+ },
427
+ executionData: {
428
+ contextData: {},
429
+ metadata: {},
430
+ nodeExecutionStack,
431
+ waitingExecution: {},
432
+ waitingExecutionSource: {},
433
+ },
434
+ manualData: {
435
+ userId: user.id,
436
+ },
437
+ };
438
+ return {
439
+ workflowData: {
440
+ ...workflowEntity,
441
+ },
442
+ executionData,
443
+ };
444
+ }
445
+ async ensurePreviousMessage(previousMessageId, sessionId, trx) {
446
+ if (!previousMessageId) {
447
+ return;
448
+ }
449
+ const previousMessage = await this.messageRepository.getOneById(previousMessageId, sessionId, [], trx);
450
+ if (!previousMessage) {
451
+ throw new bad_request_error_1.BadRequestError('The previous message does not exist in the session');
457
452
  }
458
453
  }
459
454
  async stopGeneration(user, sessionId, messageId) {
@@ -474,286 +469,281 @@ let ChatHubService = class ChatHubService {
474
469
  await this.executionService.stop(message.execution.id, [message.execution.workflowId]);
475
470
  await this.messageRepository.updateChatMessage(messageId, { status: 'cancelled' });
476
471
  }
477
- async executeChatWorkflow(res, user, workflow, replyId, sessionId, previousMessageId, selectedModel, retryOfMessageId) {
478
- const { workflowData, triggerToStartFrom } = workflow;
472
+ async executeChatWorkflow(res, user, workflowData, executionData, sessionId, previousMessageId, selectedModel, retryOfMessageId = null) {
479
473
  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;
474
+ let executionId = undefined;
475
+ const aggregator = (0, stream_capturer_1.createStructuredChunkAggregator)(previousMessageId, retryOfMessageId, {
476
+ onBegin: async (message) => {
477
+ await this.saveAIMessage({
478
+ ...message,
479
+ sessionId,
480
+ executionId,
481
+ selectedModel,
482
+ retryOfMessageId,
483
+ });
484
+ },
485
+ onItem: (_message, _chunk) => {
486
+ },
487
+ onEnd: async (message) => {
488
+ await this.messageRepository.updateChatMessage(message.id, {
489
+ content: message.content,
490
+ status: message.status,
491
+ });
492
+ },
493
+ onError: async (message, _errorText) => {
494
+ await this.messageRepository.manager.transaction(async (trx) => {
495
+ await this.messageRepository.updateChatMessage(message.id, {
496
+ content: message.content,
497
+ }, trx);
498
+ const savedMessage = await this.messageRepository.getOneById(message.id, sessionId, [], trx);
499
+ if (savedMessage?.status === 'cancelled') {
500
+ return;
501
+ }
502
+ await this.messageRepository.updateChatMessage(message.id, {
503
+ status: 'error',
504
+ }, trx);
505
+ });
506
+ },
507
+ });
508
+ const transform = (text) => {
509
+ const trimmed = text.trim();
510
+ if (!trimmed)
511
+ return text;
512
+ let chunk = null;
513
+ try {
514
+ chunk = (0, n8n_workflow_1.jsonParse)(trimmed);
515
+ }
516
+ catch {
517
+ return text;
485
518
  }
519
+ const message = aggregator.ingest(chunk);
520
+ const enriched = {
521
+ ...chunk,
522
+ metadata: {
523
+ ...chunk.metadata,
524
+ messageId: message.id,
525
+ previousMessageId: message.previousMessageId,
526
+ retryOfMessageId: message.retryOfMessageId,
527
+ executionId: executionId ? +executionId : null,
528
+ },
529
+ };
530
+ return JSON.stringify(enriched) + '\n';
486
531
  };
487
- const stream = (0, stream_capturer_1.captureResponseWrites)(res, onChunk);
488
- stream.writeHead(200, JSONL_STREAM_HEADERS);
532
+ const stream = (0, stream_capturer_1.interceptResponseWrites)(res, transform);
533
+ stream.on('finish', aggregator.finalizeAll);
534
+ stream.on('close', aggregator.finalizeAll);
535
+ stream.writeHead(200, chat_hub_constants_1.JSONL_STREAM_HEADERS);
489
536
  stream.flushHeaders();
490
- const { executionId } = await this.workflowExecutionService.executeManually({
491
- workflowData,
492
- triggerToStartFrom,
493
- }, user, undefined, true, stream);
537
+ const execution = await this.workflowExecutionService.executeChatWorkflow(workflowData, executionData, user, stream, true);
538
+ executionId = execution.executionId;
494
539
  if (!executionId) {
495
540
  throw new n8n_workflow_1.OperationalError('There was a problem starting the chat execution.');
496
541
  }
497
- await this.saveAIMessage({
498
- id: replyId,
499
- sessionId,
500
- executionId,
501
- previousMessageId,
502
- message: partialMessage,
503
- selectedModel,
504
- retryOfMessageId,
505
- status: 'running',
506
- });
507
542
  try {
508
- let result;
509
- try {
510
- result = await this.activeExecutions.getPostExecutePromise(executionId);
511
- if (!result) {
512
- throw new n8n_workflow_1.OperationalError('There was a problem executing the chat workflow.');
513
- }
543
+ const result = await this.activeExecutions.getPostExecutePromise(executionId);
544
+ if (!result) {
545
+ throw new n8n_workflow_1.OperationalError('There was a problem executing the chat workflow.');
514
546
  }
515
- catch (error) {
516
- if (error instanceof n8n_workflow_1.ManualExecutionCancelledError) {
517
- const execution = await this.executionRepository.findWithUnflattenedData(executionId, [
518
- workflowData.id,
519
- ]);
520
- if (!execution) {
521
- throw new n8n_workflow_1.OperationalError(`Could not find execution with ID ${executionId}`);
522
- }
523
- if (execution.status === 'canceled') {
524
- await this.messageRepository.updateChatMessage(replyId, {
525
- content: partialMessage || 'Generation cancelled.',
526
- status: 'cancelled',
527
- });
528
- return;
529
- }
530
- }
531
- throw error;
547
+ }
548
+ catch (error) {
549
+ if (error instanceof n8n_workflow_1.ManualExecutionCancelledError) {
550
+ return;
532
551
  }
533
- const execution = await this.executionRepository.findWithUnflattenedData(executionId, [
534
- workflowData.id,
535
- ]);
536
- if (!execution) {
537
- throw new n8n_workflow_1.OperationalError(`Could not find execution with ID ${executionId}`);
552
+ if (error instanceof Error) {
553
+ this.logger.error(`Error during chat workflow execution: ${error}`);
538
554
  }
539
- if (!execution.status || execution.status !== 'success') {
540
- const message = this.getErrorMessage(execution) ?? 'Failed to generate a response';
541
- throw new n8n_workflow_1.OperationalError(message);
555
+ throw error;
556
+ }
557
+ }
558
+ async executeChatWorkflowWithCleanup(res, user, workflowData, executionData, sessionId, previousMessageId, selectedModel, provider, retryOfMessageId = null) {
559
+ try {
560
+ await this.executeChatWorkflow(res, user, workflowData, executionData, sessionId, previousMessageId, selectedModel, retryOfMessageId);
561
+ }
562
+ finally {
563
+ if (provider !== 'n8n') {
564
+ await this.deleteChatWorkflow(workflowData.id);
542
565
  }
543
- await this.messageRepository.updateChatMessage(replyId, {
544
- content: partialMessage,
545
- status: 'success',
546
- });
547
- const title = this.getAIOutput(execution, NODE_NAMES.TITLE_GENERATOR_AGENT);
566
+ }
567
+ }
568
+ async generateSessionTitle(user, sessionId, humanMessage, credentials, model) {
569
+ const { executionData, workflowData } = await this.prepareTitleGenerationWorkflow(user, sessionId, humanMessage, credentials, model);
570
+ try {
571
+ const title = await this.runTitleWorkflowAndGetTitle(user, workflowData, executionData);
548
572
  if (title) {
549
573
  await this.sessionRepository.updateChatTitle(sessionId, title);
550
574
  }
551
575
  }
552
576
  catch (error) {
553
- const message = error instanceof Error ? error.message : 'Unknown error';
554
- await this.messageRepository.updateChatMessage(replyId, {
555
- content: `Error: ${message}`,
556
- status: 'error',
557
- });
577
+ if (error instanceof Error) {
578
+ this.logger.error(`Error during session title generation workflow execution: ${error}`);
579
+ }
580
+ throw error;
581
+ }
582
+ finally {
583
+ await this.deleteChatWorkflow(workflowData.id);
558
584
  }
559
585
  }
560
- prepareChatWorkflow({ sessionId, history, humanMessage, credentials, model, generateConversationTitle, }) {
561
- const nodes = [
562
- {
563
- parameters: {
564
- public: true,
565
- mode: 'webhook',
566
- options: { responseMode: 'streaming' },
567
- },
568
- type: n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE,
569
- typeVersion: 1.3,
570
- position: [0, 0],
571
- id: (0, uuid_1.v4)(),
572
- name: NODE_NAMES.CHAT_TRIGGER,
573
- webhookId: (0, uuid_1.v4)(),
574
- },
575
- {
576
- parameters: {
577
- promptType: 'define',
578
- text: "={{ $('When chat message received').item.json.chatInput }}",
579
- options: {
580
- enableStreaming: true,
581
- maxTokensFromMemory: model.provider !== 'n8n'
582
- ? (0, context_limits_1.getMaxContextWindowTokens)(model.provider, model.model)
583
- : undefined,
584
- },
585
- },
586
- type: n8n_workflow_1.AGENT_LANGCHAIN_NODE_TYPE,
587
- typeVersion: 3,
588
- position: [600, 0],
589
- id: (0, uuid_1.v4)(),
590
- name: NODE_NAMES.REPLY_AGENT,
591
- },
592
- this.createModelNode(credentials, model),
593
- {
594
- parameters: {
595
- sessionIdType: 'customKey',
596
- sessionKey: `={{ $('${NODE_NAMES.CHAT_TRIGGER}').item.json.sessionId }}`,
597
- contextWindowLength: 20,
598
- },
599
- type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
600
- typeVersion: 1.3,
601
- position: [480, 208],
602
- id: (0, uuid_1.v4)(),
603
- name: NODE_NAMES.MEMORY,
604
- },
605
- {
606
- parameters: {
607
- mode: 'insert',
608
- insertMode: 'override',
609
- messages: {
610
- messageValues: history.map((message) => {
611
- const typeMap = {
612
- human: 'user',
613
- ai: 'ai',
614
- system: 'system',
615
- };
616
- return {
617
- type: typeMap[message.type] || 'system',
618
- message: message.content,
619
- hideFromUI: false,
620
- };
621
- }),
622
- },
623
- },
624
- type: '@n8n/n8n-nodes-langchain.memoryManager',
625
- typeVersion: 1.1,
626
- position: [224, 0],
627
- id: (0, uuid_1.v4)(),
628
- name: NODE_NAMES.RESTORE_CHAT_MEMORY,
629
- },
630
- {
631
- parameters: {
632
- mode: 'delete',
633
- deleteMode: 'all',
634
- },
635
- type: '@n8n/n8n-nodes-langchain.memoryManager',
636
- typeVersion: 1.1,
637
- position: [976, 0],
638
- id: (0, uuid_1.v4)(),
639
- name: NODE_NAMES.CLEAR_CHAT_MEMORY,
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
- },
657
- ];
658
- const connections = {
659
- [NODE_NAMES.CHAT_TRIGGER]: {
660
- main: [
661
- [{ node: NODE_NAMES.RESTORE_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.Main, index: 0 }],
662
- ],
663
- },
664
- [NODE_NAMES.RESTORE_CHAT_MEMORY]: {
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
- ],
671
- },
672
- [NODE_NAMES.CHAT_MODEL]: {
673
- ai_languageModel: [
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
- ],
682
- ],
683
- },
684
- [NODE_NAMES.MEMORY]: {
685
- ai_memory: [
686
- [
687
- { node: NODE_NAMES.REPLY_AGENT, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
688
- { node: NODE_NAMES.RESTORE_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
689
- { node: NODE_NAMES.CLEAR_CHAT_MEMORY, type: n8n_workflow_1.NodeConnectionTypes.AiMemory, index: 0 },
690
- ],
691
- ],
692
- },
693
- [NODE_NAMES.REPLY_AGENT]: {
694
- main: [
695
- [
696
- {
697
- node: NODE_NAMES.CLEAR_CHAT_MEMORY,
698
- type: n8n_workflow_1.NodeConnectionTypes.Main,
699
- index: 0,
700
- },
701
- ],
702
- ],
586
+ async prepareTitleGenerationWorkflow(user, sessionId, humanMessage, incomingCredentials, incomingModel) {
587
+ return await this.messageRepository.manager.transaction(async (trx) => {
588
+ const { resolvedCredentials, resolvedModel, credential } = await this.resolveCredentialsAndModelForTitle(user, incomingModel, incomingCredentials, trx);
589
+ if (!credential) {
590
+ throw new bad_request_error_1.BadRequestError('Could not determine credentials for title generation');
591
+ }
592
+ this.logger.debug(`Using credential ID ${credential.id} for title generation in project ${credential.projectId}, model ${JSON.stringify(resolvedModel)}`);
593
+ return await this.chatHubWorkflowService.createTitleGenerationWorkflow(user.id, sessionId, credential.projectId, humanMessage, resolvedCredentials, resolvedModel, trx);
594
+ });
595
+ }
596
+ async resolveCredentialsAndModelForTitle(user, model, credentials, trx) {
597
+ if (model.provider === 'n8n') {
598
+ return await this.resolveFromN8nWorkflow(user, model, trx);
599
+ }
600
+ if (model.provider === 'custom-agent') {
601
+ return await this.resolveFromCustomAgent(user, model, trx);
602
+ }
603
+ const credential = await this.chatHubCredentialsService.ensureCredentials(user, model.provider, credentials, trx);
604
+ return {
605
+ resolvedCredentials: credentials,
606
+ resolvedModel: model,
607
+ credential,
608
+ };
609
+ }
610
+ async resolveFromN8nWorkflow(user, model, trx) {
611
+ const workflowEntity = await this.workflowFinderService.findWorkflowForUser(model.workflowId, user, ['workflow:read'], { includeTags: false, includeParentFolder: false });
612
+ if (!workflowEntity) {
613
+ throw new bad_request_error_1.BadRequestError('Workflow not found for title generation');
614
+ }
615
+ const modelNodes = this.findSupportedLLMNodes(workflowEntity);
616
+ this.logger.debug(`Found ${modelNodes.length} LLM nodes in workflow ${workflowEntity.id} for title generation`);
617
+ if (modelNodes.length === 0) {
618
+ throw new bad_request_error_1.BadRequestError('No supported Model nodes found in workflow for title generation');
619
+ }
620
+ const modelNode = modelNodes[0];
621
+ const llmModel = modelNode.node.parameters?.model?.value;
622
+ if (!llmModel) {
623
+ throw new bad_request_error_1.BadRequestError(`No model set on Model node "${modelNode.node.name}" for title generation`);
624
+ }
625
+ if (typeof llmModel !== 'string' || llmModel.length === 0 || llmModel.startsWith('=')) {
626
+ throw new bad_request_error_1.BadRequestError(`Invalid model set on Model node "${modelNode.node.name}" for title generation`);
627
+ }
628
+ const llmCredentials = modelNode.node.credentials;
629
+ if (!llmCredentials) {
630
+ throw new bad_request_error_1.BadRequestError(`No credentials found on Model node "${modelNode.node.name}" for title generation`);
631
+ }
632
+ const credential = await this.chatHubCredentialsService.ensureCredentials(user, modelNode.provider, llmCredentials, trx);
633
+ const resolvedModel = {
634
+ provider: modelNode.provider,
635
+ model: llmModel,
636
+ };
637
+ const resolvedCredentials = {
638
+ [api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[modelNode.provider]]: {
639
+ id: credential.id,
640
+ name: '',
703
641
  },
704
642
  };
705
- const triggerToStartFrom = {
706
- name: NODE_NAMES.CHAT_TRIGGER,
707
- data: {
708
- startTime: Date.now(),
709
- executionTime: 0,
710
- executionIndex: 0,
711
- executionStatus: 'success',
712
- data: {
713
- main: [
714
- [
715
- {
716
- json: {
717
- sessionId,
718
- action: 'sendMessage',
719
- chatInput: humanMessage,
720
- },
721
- },
722
- ],
723
- ],
724
- },
725
- source: [null],
643
+ return { resolvedCredentials, resolvedModel, credential };
644
+ }
645
+ findSupportedLLMNodes(workflowEntity) {
646
+ return workflowEntity.nodes.reduce((acc, node) => {
647
+ const supportedProvider = Object.entries(chat_hub_constants_1.PROVIDER_NODE_TYPE_MAP).find(([_provider, { name }]) => node.type === name);
648
+ if (supportedProvider) {
649
+ const [provider] = supportedProvider;
650
+ acc.push({ node, provider: provider });
651
+ }
652
+ return acc;
653
+ }, []);
654
+ }
655
+ async resolveFromCustomAgent(user, model, trx) {
656
+ const agent = await this.chatHubAgentService.getAgentById(model.agentId, user.id);
657
+ if (!agent) {
658
+ throw new bad_request_error_1.BadRequestError('Agent not found for title generation');
659
+ }
660
+ if (agent.provider === 'n8n' || agent.provider === 'custom-agent') {
661
+ throw new bad_request_error_1.BadRequestError('Invalid provider for title generation');
662
+ }
663
+ const credentialId = agent.credentialId;
664
+ if (!credentialId) {
665
+ throw new bad_request_error_1.BadRequestError('Credentials not set for agent');
666
+ }
667
+ const resolvedModel = {
668
+ provider: agent.provider,
669
+ model: agent.model,
670
+ };
671
+ const resolvedCredentials = {
672
+ [api_types_1.PROVIDER_CREDENTIAL_TYPE_MAP[agent.provider]]: {
673
+ id: credentialId,
674
+ name: '',
726
675
  },
727
676
  };
728
- return { nodes, connections, triggerToStartFrom };
677
+ const credential = await this.chatHubCredentialsService.ensureCredentials(user, agent.provider, resolvedCredentials, trx);
678
+ return { resolvedCredentials, resolvedModel, credential };
679
+ }
680
+ async runTitleWorkflowAndGetTitle(user, workflowData, executionData) {
681
+ const started = await this.workflowExecutionService.executeChatWorkflow(workflowData, executionData, user);
682
+ const executionId = started.executionId;
683
+ if (!executionId) {
684
+ throw new n8n_workflow_1.OperationalError('There was a problem starting the chat execution.');
685
+ }
686
+ let run;
687
+ try {
688
+ run = await this.activeExecutions.getPostExecutePromise(executionId);
689
+ if (!run) {
690
+ throw new n8n_workflow_1.OperationalError('There was a problem executing the chat workflow.');
691
+ }
692
+ }
693
+ catch (error) {
694
+ if (error instanceof n8n_workflow_1.ManualExecutionCancelledError) {
695
+ return null;
696
+ }
697
+ throw error;
698
+ }
699
+ const execution = await this.executionRepository.findWithUnflattenedData(executionId, [
700
+ workflowData.id,
701
+ ]);
702
+ if (!execution) {
703
+ throw new n8n_workflow_1.OperationalError(`Could not find execution with ID ${executionId}`);
704
+ }
705
+ if (!execution.status || execution.status !== 'success') {
706
+ const message = this.getErrorMessage(execution) ?? 'Failed to generate a response';
707
+ throw new n8n_workflow_1.OperationalError(message);
708
+ }
709
+ const title = this.getAIOutput(execution, chat_hub_constants_1.NODE_NAMES.TITLE_GENERATOR_AGENT);
710
+ return title ?? null;
729
711
  }
730
712
  async saveHumanMessage(payload, user, previousMessageId, selectedModel, revisionOfMessageId, trx) {
731
713
  await this.messageRepository.createChatMessage({
732
714
  id: payload.messageId,
733
715
  sessionId: payload.sessionId,
734
716
  type: 'human',
735
- name: user.firstName || 'User',
736
717
  status: 'success',
737
718
  content: payload.message,
738
719
  previousMessageId,
739
720
  revisionOfMessageId,
740
721
  ...selectedModel,
722
+ name: user.firstName || 'User',
741
723
  }, trx);
742
724
  }
743
- async saveAIMessage({ id, sessionId, executionId, previousMessageId, message, selectedModel, retryOfMessageId, status, }) {
725
+ async saveAIMessage({ id, sessionId, executionId, previousMessageId, content, selectedModel, retryOfMessageId, status, }) {
744
726
  await this.messageRepository.createChatMessage({
745
727
  id,
746
728
  sessionId,
747
729
  previousMessageId,
748
- executionId: parseInt(executionId, 10),
730
+ executionId: executionId ? parseInt(executionId, 10) : null,
749
731
  type: 'ai',
750
732
  name: 'AI',
751
733
  status,
752
- content: message,
734
+ content,
753
735
  retryOfMessageId,
754
736
  ...selectedModel,
755
737
  });
756
738
  }
739
+ getModelWithCredentials(selectedModel, credentials) {
740
+ const provider = selectedModel.provider;
741
+ const modelWithCredentials = {
742
+ ...selectedModel,
743
+ credentialId: provider !== 'n8n' ? this.pickCredentialId(provider, credentials) : null,
744
+ };
745
+ return modelWithCredentials;
746
+ }
757
747
  async getChatSession(user, sessionId, selectedModel, initialize = false, trx) {
758
748
  const existing = await this.sessionRepository.getOneById(sessionId, user.id, trx);
759
749
  if (existing) {
@@ -762,10 +752,34 @@ let ChatHubService = class ChatHubService {
762
752
  else if (!initialize) {
763
753
  throw new not_found_error_1.NotFoundError('Chat session not found');
764
754
  }
755
+ let agentName = undefined;
756
+ if (selectedModel?.provider === 'custom-agent') {
757
+ const agent = await this.chatHubAgentService.getAgentById(selectedModel.agentId, user.id);
758
+ if (!agent) {
759
+ throw new bad_request_error_1.BadRequestError('Agent not found for chat session initialization');
760
+ }
761
+ agentName = agent.name;
762
+ }
763
+ if (selectedModel?.provider === 'n8n') {
764
+ const workflow = await this.workflowFinderService.findWorkflowForUser(selectedModel.workflowId, user, ['workflow:read'], { includeTags: false, includeParentFolder: false });
765
+ if (!workflow) {
766
+ throw new bad_request_error_1.BadRequestError('Workflow not found for chat session initialization');
767
+ }
768
+ const chatTrigger = workflow.nodes?.find((node) => node.type === n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE);
769
+ if (!chatTrigger) {
770
+ throw new bad_request_error_1.BadRequestError('Chat trigger not found in workflow for chat session initialization');
771
+ }
772
+ agentName =
773
+ typeof chatTrigger.parameters.agentName === 'string' &&
774
+ chatTrigger.parameters.agentName.length > 0
775
+ ? chatTrigger.parameters.agentName
776
+ : workflow.name;
777
+ }
765
778
  return await this.sessionRepository.createChatSession({
766
779
  id: sessionId,
767
780
  ownerId: user.id,
768
781
  title: 'New Chat',
782
+ agentName,
769
783
  ...selectedModel,
770
784
  }, trx);
771
785
  }
@@ -776,51 +790,6 @@ let ChatHubService = class ChatHubService {
776
790
  }
777
791
  return message;
778
792
  }
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;
784
- const common = {
785
- position: [600, 500],
786
- id: (0, uuid_1.v4)(),
787
- name: 'Chat Model',
788
- credentials,
789
- type: providerNodeTypeMapping[provider].name,
790
- typeVersion: providerNodeTypeMapping[provider].version,
791
- };
792
- switch (provider) {
793
- case 'openai':
794
- return {
795
- ...common,
796
- parameters: {
797
- model: { __rl: true, mode: 'list', value: model },
798
- options: {},
799
- },
800
- };
801
- case 'anthropic':
802
- return {
803
- ...common,
804
- parameters: {
805
- model: {
806
- __rl: true,
807
- mode: 'list',
808
- value: model,
809
- cachedResultName: model,
810
- },
811
- options: {},
812
- },
813
- };
814
- case 'google':
815
- return {
816
- ...common,
817
- parameters: {
818
- model: { __rl: true, mode: 'list', value: model },
819
- options: {},
820
- },
821
- };
822
- }
823
- }
824
793
  async getConversations(userId) {
825
794
  const sessions = await this.sessionRepository.getManyByUserId(userId);
826
795
  return sessions.map((session) => ({
@@ -832,6 +801,8 @@ let ChatHubService = class ChatHubService {
832
801
  provider: session.provider,
833
802
  model: session.model,
834
803
  workflowId: session.workflowId,
804
+ agentId: session.agentId,
805
+ agentName: session.agentName,
835
806
  createdAt: session.createdAt.toISOString(),
836
807
  updatedAt: session.updatedAt.toISOString(),
837
808
  }));
@@ -852,6 +823,8 @@ let ChatHubService = class ChatHubService {
852
823
  provider: session.provider,
853
824
  model: session.model,
854
825
  workflowId: session.workflowId,
826
+ agentId: session.agentId,
827
+ agentName: session.agentName,
855
828
  createdAt: session.createdAt.toISOString(),
856
829
  updatedAt: session.updatedAt.toISOString(),
857
830
  },
@@ -870,6 +843,7 @@ let ChatHubService = class ChatHubService {
870
843
  provider: message.provider,
871
844
  model: message.model,
872
845
  workflowId: message.workflowId,
846
+ agentId: message.agentId,
873
847
  executionId: message.executionId,
874
848
  status: message.status,
875
849
  createdAt: message.createdAt.toISOString(),
@@ -904,6 +878,51 @@ let ChatHubService = class ChatHubService {
904
878
  }
905
879
  return await this.sessionRepository.updateChatTitle(sessionId, title);
906
880
  }
881
+ async updateSession(user, sessionId, updates) {
882
+ const session = await this.sessionRepository.getOneById(sessionId, user.id);
883
+ if (!session) {
884
+ throw new not_found_error_1.NotFoundError('Session not found');
885
+ }
886
+ if (updates.workflowId) {
887
+ const workflow = await this.workflowFinderService.findWorkflowForUser(updates.workflowId, user, ['workflow:read'], { includeTags: false, includeParentFolder: false });
888
+ if (!workflow) {
889
+ throw new bad_request_error_1.BadRequestError('Workflow not found');
890
+ }
891
+ const chatTriggers = workflow.nodes.filter((node) => node.type === n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE);
892
+ if (chatTriggers.length !== 1) {
893
+ throw new bad_request_error_1.BadRequestError('Workflow must have exactly one chat trigger');
894
+ }
895
+ const chatTrigger = chatTriggers[0];
896
+ updates.agentName =
897
+ typeof chatTrigger.parameters.agentName === 'string' &&
898
+ chatTrigger.parameters.agentName.length > 0
899
+ ? chatTrigger.parameters.agentName
900
+ : workflow.name;
901
+ }
902
+ if (updates.agentId) {
903
+ const agent = await this.chatHubAgentService.getAgentById(updates.agentId, user.id);
904
+ if (!agent) {
905
+ throw new bad_request_error_1.BadRequestError('Agent not found');
906
+ }
907
+ updates.agentName = agent.name;
908
+ }
909
+ if (updates.provider === 'n8n') {
910
+ updates.model = null;
911
+ updates.credentialId = null;
912
+ updates.agentId = null;
913
+ }
914
+ else if (updates.provider === 'custom-agent') {
915
+ updates.model = null;
916
+ updates.credentialId = null;
917
+ updates.workflowId = null;
918
+ }
919
+ else if (updates.provider) {
920
+ updates.workflowId = null;
921
+ updates.agentId = null;
922
+ updates.agentName = null;
923
+ }
924
+ return await this.sessionRepository.updateChatSession(sessionId, updates);
925
+ }
907
926
  async deleteSession(userId, sessionId) {
908
927
  const session = await this.sessionRepository.getOneById(sessionId, userId);
909
928
  if (!session) {
@@ -923,10 +942,12 @@ exports.ChatHubService = ChatHubService = __decorate([
923
942
  workflow_service_1.WorkflowService,
924
943
  workflow_finder_service_1.WorkflowFinderService,
925
944
  db_1.WorkflowRepository,
926
- db_1.SharedWorkflowRepository,
927
945
  active_executions_1.ActiveExecutions,
928
946
  chat_session_repository_1.ChatHubSessionRepository,
929
947
  chat_message_repository_1.ChatHubMessageRepository,
930
- credentials_finder_service_1.CredentialsFinderService])
948
+ credentials_finder_service_1.CredentialsFinderService,
949
+ chat_hub_agent_service_1.ChatHubAgentService,
950
+ chat_hub_credentials_service_1.ChatHubCredentialsService,
951
+ chat_hub_workflow_service_1.ChatHubWorkflowService])
931
952
  ], ChatHubService);
932
953
  //# sourceMappingURL=chat-hub.service.js.map