@multiplayer-app/ai-agent-node 0.1.0-beta.7 → 0.1.0-beta.70

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 (210) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/config.cjs +87 -45
  3. package/dist/cjs/config.cjs.map +1 -1
  4. package/dist/cjs/config.d.ts +60 -29
  5. package/dist/cjs/config.d.ts.map +1 -1
  6. package/dist/cjs/helpers/AIHelper.cjs +127 -65
  7. package/dist/cjs/helpers/AIHelper.cjs.map +1 -1
  8. package/dist/cjs/helpers/AIHelper.d.ts +22 -16
  9. package/dist/cjs/helpers/AIHelper.d.ts.map +1 -1
  10. package/dist/cjs/helpers/AIHelper.test.cjs +22 -15
  11. package/dist/cjs/helpers/AIHelper.test.cjs.map +1 -1
  12. package/dist/cjs/helpers/ConfigHelper.cjs +15 -6
  13. package/dist/cjs/helpers/ConfigHelper.cjs.map +1 -1
  14. package/dist/cjs/helpers/ConfigHelper.d.ts.map +1 -1
  15. package/dist/cjs/helpers/FileHelper.cjs +131 -151
  16. package/dist/cjs/helpers/FileHelper.cjs.map +1 -1
  17. package/dist/cjs/helpers/FileHelper.d.ts +19 -25
  18. package/dist/cjs/helpers/FileHelper.d.ts.map +1 -1
  19. package/dist/cjs/helpers/index.cjs +0 -1
  20. package/dist/cjs/helpers/index.cjs.map +1 -1
  21. package/dist/cjs/helpers/index.d.ts +0 -1
  22. package/dist/cjs/helpers/index.d.ts.map +1 -1
  23. package/dist/cjs/index.cjs +120 -26
  24. package/dist/cjs/index.cjs.map +1 -1
  25. package/dist/cjs/index.d.ts +43 -10
  26. package/dist/cjs/index.d.ts.map +1 -1
  27. package/dist/cjs/libs/s3/index.cjs +3 -39
  28. package/dist/cjs/libs/s3/index.cjs.map +1 -1
  29. package/dist/cjs/libs/s3/index.d.ts +1 -2
  30. package/dist/cjs/libs/s3/index.d.ts.map +1 -1
  31. package/dist/cjs/libs/s3/s3.lib.cjs +173 -186
  32. package/dist/cjs/libs/s3/s3.lib.cjs.map +1 -1
  33. package/dist/cjs/libs/s3/s3.lib.d.ts +29 -22
  34. package/dist/cjs/libs/s3/s3.lib.d.ts.map +1 -1
  35. package/dist/cjs/processors/ActivityProcessor.cjs +39 -0
  36. package/dist/cjs/processors/ActivityProcessor.cjs.map +1 -0
  37. package/dist/cjs/processors/ActivityProcessor.d.ts +32 -0
  38. package/dist/cjs/processors/ActivityProcessor.d.ts.map +1 -0
  39. package/dist/cjs/processors/ActivityProcessor.test.cjs +84 -0
  40. package/dist/cjs/processors/ActivityProcessor.test.cjs.map +1 -0
  41. package/dist/cjs/processors/ActivityProcessor.test.d.ts +2 -0
  42. package/dist/cjs/processors/ActivityProcessor.test.d.ts.map +1 -0
  43. package/dist/cjs/processors/AgentProcessor.cjs +46 -0
  44. package/dist/cjs/processors/AgentProcessor.cjs.map +1 -0
  45. package/dist/cjs/processors/AgentProcessor.d.ts +25 -0
  46. package/dist/cjs/processors/AgentProcessor.d.ts.map +1 -0
  47. package/dist/cjs/processors/AgentProcessor.test.cjs +103 -0
  48. package/dist/cjs/processors/AgentProcessor.test.cjs.map +1 -0
  49. package/dist/cjs/processors/AgentProcessor.test.d.ts +2 -0
  50. package/dist/cjs/processors/AgentProcessor.test.d.ts.map +1 -0
  51. package/dist/cjs/processors/ChatProcessor.cjs +348 -126
  52. package/dist/cjs/processors/ChatProcessor.cjs.map +1 -1
  53. package/dist/cjs/processors/ChatProcessor.d.ts +70 -11
  54. package/dist/cjs/processors/ChatProcessor.d.ts.map +1 -1
  55. package/dist/cjs/processors/ChatProcessor.test.cjs +762 -0
  56. package/dist/cjs/processors/ChatProcessor.test.cjs.map +1 -0
  57. package/dist/cjs/processors/ChatProcessor.test.d.ts +2 -0
  58. package/dist/cjs/processors/ChatProcessor.test.d.ts.map +1 -0
  59. package/dist/cjs/processors/index.cjs +2 -0
  60. package/dist/cjs/processors/index.cjs.map +1 -1
  61. package/dist/cjs/processors/index.d.ts +2 -0
  62. package/dist/cjs/processors/index.d.ts.map +1 -1
  63. package/dist/cjs/services/AIService.cjs +87 -21
  64. package/dist/cjs/services/AIService.cjs.map +1 -1
  65. package/dist/cjs/services/AIService.d.ts +19 -7
  66. package/dist/cjs/services/AIService.d.ts.map +1 -1
  67. package/dist/cjs/services/InternalEventsHandler.cjs +3 -3
  68. package/dist/cjs/services/InternalEventsHandler.cjs.map +1 -1
  69. package/dist/cjs/services/InternalEventsHandler.d.ts +3 -1
  70. package/dist/cjs/services/InternalEventsHandler.d.ts.map +1 -1
  71. package/dist/cjs/services/ModelFetcher.cjs +2 -8
  72. package/dist/cjs/services/ModelFetcher.cjs.map +1 -1
  73. package/dist/cjs/services/ModelFetcher.d.ts +2 -7
  74. package/dist/cjs/services/ModelFetcher.d.ts.map +1 -1
  75. package/dist/cjs/services/RedisService.cjs +20 -16
  76. package/dist/cjs/services/RedisService.cjs.map +1 -1
  77. package/dist/cjs/services/RedisService.d.ts +5 -2
  78. package/dist/cjs/services/RedisService.d.ts.map +1 -1
  79. package/dist/cjs/services/SocketService.cjs +7 -7
  80. package/dist/cjs/services/SocketService.cjs.map +1 -1
  81. package/dist/cjs/services/SocketService.d.ts +9 -6
  82. package/dist/cjs/services/SocketService.d.ts.map +1 -1
  83. package/dist/cjs/store/AgentStore.cjs +3 -4
  84. package/dist/cjs/store/AgentStore.cjs.map +1 -1
  85. package/dist/cjs/store/AgentStore.d.ts +2 -1
  86. package/dist/cjs/store/AgentStore.d.ts.map +1 -1
  87. package/dist/cjs/store/ConfigStore.cjs +7 -3
  88. package/dist/cjs/store/ConfigStore.cjs.map +1 -1
  89. package/dist/cjs/store/ConfigStore.d.ts +1 -0
  90. package/dist/cjs/store/ConfigStore.d.ts.map +1 -1
  91. package/dist/cjs/tools/generateChartTool.d.ts +2 -2
  92. package/dist/cjs/tools/proposeFormValuesTool.d.ts +2 -2
  93. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  94. package/dist/esm/config.d.ts +60 -29
  95. package/dist/esm/config.d.ts.map +1 -1
  96. package/dist/esm/config.js +87 -43
  97. package/dist/esm/config.js.map +1 -1
  98. package/dist/esm/helpers/AIHelper.d.ts +22 -16
  99. package/dist/esm/helpers/AIHelper.d.ts.map +1 -1
  100. package/dist/esm/helpers/AIHelper.js +134 -70
  101. package/dist/esm/helpers/AIHelper.js.map +1 -1
  102. package/dist/esm/helpers/AIHelper.test.js +22 -15
  103. package/dist/esm/helpers/AIHelper.test.js.map +1 -1
  104. package/dist/esm/helpers/ConfigHelper.d.ts.map +1 -1
  105. package/dist/esm/helpers/ConfigHelper.js +15 -6
  106. package/dist/esm/helpers/ConfigHelper.js.map +1 -1
  107. package/dist/esm/helpers/FileHelper.d.ts +19 -25
  108. package/dist/esm/helpers/FileHelper.d.ts.map +1 -1
  109. package/dist/esm/helpers/FileHelper.js +131 -146
  110. package/dist/esm/helpers/FileHelper.js.map +1 -1
  111. package/dist/esm/helpers/index.d.ts +0 -1
  112. package/dist/esm/helpers/index.d.ts.map +1 -1
  113. package/dist/esm/helpers/index.js +0 -1
  114. package/dist/esm/helpers/index.js.map +1 -1
  115. package/dist/esm/index.d.ts +43 -10
  116. package/dist/esm/index.d.ts.map +1 -1
  117. package/dist/esm/index.js +92 -10
  118. package/dist/esm/index.js.map +1 -1
  119. package/dist/esm/libs/s3/index.d.ts +1 -2
  120. package/dist/esm/libs/s3/index.d.ts.map +1 -1
  121. package/dist/esm/libs/s3/index.js +1 -2
  122. package/dist/esm/libs/s3/index.js.map +1 -1
  123. package/dist/esm/libs/s3/s3.lib.d.ts +29 -22
  124. package/dist/esm/libs/s3/s3.lib.d.ts.map +1 -1
  125. package/dist/esm/libs/s3/s3.lib.js +177 -172
  126. package/dist/esm/libs/s3/s3.lib.js.map +1 -1
  127. package/dist/esm/processors/ActivityProcessor.d.ts +32 -0
  128. package/dist/esm/processors/ActivityProcessor.d.ts.map +1 -0
  129. package/dist/esm/processors/ActivityProcessor.js +36 -0
  130. package/dist/esm/processors/ActivityProcessor.js.map +1 -0
  131. package/dist/esm/processors/ActivityProcessor.test.d.ts +2 -0
  132. package/dist/esm/processors/ActivityProcessor.test.d.ts.map +1 -0
  133. package/dist/esm/processors/ActivityProcessor.test.js +82 -0
  134. package/dist/esm/processors/ActivityProcessor.test.js.map +1 -0
  135. package/dist/esm/processors/AgentProcessor.d.ts +25 -0
  136. package/dist/esm/processors/AgentProcessor.d.ts.map +1 -0
  137. package/dist/esm/processors/AgentProcessor.js +43 -0
  138. package/dist/esm/processors/AgentProcessor.js.map +1 -0
  139. package/dist/esm/processors/AgentProcessor.test.d.ts +2 -0
  140. package/dist/esm/processors/AgentProcessor.test.d.ts.map +1 -0
  141. package/dist/esm/processors/AgentProcessor.test.js +101 -0
  142. package/dist/esm/processors/AgentProcessor.test.js.map +1 -0
  143. package/dist/esm/processors/ChatProcessor.d.ts +70 -11
  144. package/dist/esm/processors/ChatProcessor.d.ts.map +1 -1
  145. package/dist/esm/processors/ChatProcessor.js +359 -130
  146. package/dist/esm/processors/ChatProcessor.js.map +1 -1
  147. package/dist/esm/processors/ChatProcessor.test.d.ts +2 -0
  148. package/dist/esm/processors/ChatProcessor.test.d.ts.map +1 -0
  149. package/dist/esm/processors/ChatProcessor.test.js +760 -0
  150. package/dist/esm/processors/ChatProcessor.test.js.map +1 -0
  151. package/dist/esm/processors/index.d.ts +2 -0
  152. package/dist/esm/processors/index.d.ts.map +1 -1
  153. package/dist/esm/processors/index.js +2 -0
  154. package/dist/esm/processors/index.js.map +1 -1
  155. package/dist/esm/services/AIService.d.ts +19 -7
  156. package/dist/esm/services/AIService.d.ts.map +1 -1
  157. package/dist/esm/services/AIService.js +91 -24
  158. package/dist/esm/services/AIService.js.map +1 -1
  159. package/dist/esm/services/InternalEventsHandler.d.ts +3 -1
  160. package/dist/esm/services/InternalEventsHandler.d.ts.map +1 -1
  161. package/dist/esm/services/InternalEventsHandler.js +4 -3
  162. package/dist/esm/services/InternalEventsHandler.js.map +1 -1
  163. package/dist/esm/services/ModelFetcher.d.ts +2 -7
  164. package/dist/esm/services/ModelFetcher.d.ts.map +1 -1
  165. package/dist/esm/services/ModelFetcher.js +2 -8
  166. package/dist/esm/services/ModelFetcher.js.map +1 -1
  167. package/dist/esm/services/RedisService.d.ts +5 -2
  168. package/dist/esm/services/RedisService.d.ts.map +1 -1
  169. package/dist/esm/services/RedisService.js +21 -14
  170. package/dist/esm/services/RedisService.js.map +1 -1
  171. package/dist/esm/services/SocketService.d.ts +9 -6
  172. package/dist/esm/services/SocketService.d.ts.map +1 -1
  173. package/dist/esm/services/SocketService.js +9 -5
  174. package/dist/esm/services/SocketService.js.map +1 -1
  175. package/dist/esm/store/AgentStore.d.ts +2 -1
  176. package/dist/esm/store/AgentStore.d.ts.map +1 -1
  177. package/dist/esm/store/AgentStore.js +4 -2
  178. package/dist/esm/store/AgentStore.js.map +1 -1
  179. package/dist/esm/store/ConfigStore.d.ts +1 -0
  180. package/dist/esm/store/ConfigStore.d.ts.map +1 -1
  181. package/dist/esm/store/ConfigStore.js +7 -3
  182. package/dist/esm/store/ConfigStore.js.map +1 -1
  183. package/dist/esm/tools/generateChartTool.d.ts +2 -2
  184. package/dist/esm/tools/proposeFormValuesTool.d.ts +2 -2
  185. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  186. package/package.json +12 -11
  187. package/dist/cjs/helpers/SetupHelper.cjs +0 -34
  188. package/dist/cjs/helpers/SetupHelper.cjs.map +0 -1
  189. package/dist/cjs/helpers/SetupHelper.d.ts +0 -4
  190. package/dist/cjs/helpers/SetupHelper.d.ts.map +0 -1
  191. package/dist/cjs/libs/logger/config.cjs +0 -9
  192. package/dist/cjs/libs/logger/config.cjs.map +0 -1
  193. package/dist/cjs/libs/logger/config.d.ts +0 -5
  194. package/dist/cjs/libs/logger/config.d.ts.map +0 -1
  195. package/dist/cjs/libs/s3/config.cjs +0 -8
  196. package/dist/cjs/libs/s3/config.cjs.map +0 -1
  197. package/dist/cjs/libs/s3/config.d.ts +0 -4
  198. package/dist/cjs/libs/s3/config.d.ts.map +0 -1
  199. package/dist/esm/helpers/SetupHelper.d.ts +0 -4
  200. package/dist/esm/helpers/SetupHelper.d.ts.map +0 -1
  201. package/dist/esm/helpers/SetupHelper.js +0 -29
  202. package/dist/esm/helpers/SetupHelper.js.map +0 -1
  203. package/dist/esm/libs/logger/config.d.ts +0 -5
  204. package/dist/esm/libs/logger/config.d.ts.map +0 -1
  205. package/dist/esm/libs/logger/config.js +0 -6
  206. package/dist/esm/libs/logger/config.js.map +0 -1
  207. package/dist/esm/libs/s3/config.d.ts +0 -4
  208. package/dist/esm/libs/s3/config.d.ts.map +0 -1
  209. package/dist/esm/libs/s3/config.js +0 -5
  210. package/dist/esm/libs/s3/config.js.map +0 -1
@@ -1,21 +1,34 @@
1
- import { AgentStatus, AgentToolCallStatus, ChatType, MessageRole, SortOrder, StreamChunkType } from '@multiplayer-app/ai-agent-types';
2
- import { socketService } from '../services';
3
- import { AIHelper } from '../helpers';
4
- import { AgentProcessEventType, agentStore } from '../store';
1
+ import { AgentStatus, AgentToolCallStatus, ChatType, MessageRole, SortOrder, StreamChunkType, ActivityOperationName, AgentToolType } from '@multiplayer-app/ai-agent-types';
2
+ import { z } from 'zod';
3
+ import { AIHelper, FileHelper } from '../helpers';
4
+ import { AgentProcessEventType } from '../store';
5
5
  import { ContextLimiter } from '../helpers';
6
- import { config } from '../config';
7
- import { PassThrough, pipeline } from 'stream';
8
- import { promisify } from 'util';
6
+ import { PassThrough } from 'stream';
9
7
  import { logger } from '../libs/logger';
10
- const pipelineAsync = promisify(pipeline);
8
+ import { ConfigStore } from '../store';
11
9
  export class ChatProcessor {
10
+ aiHelper;
12
11
  chatRepository;
13
12
  messageRepository;
14
13
  artifactStore;
15
- constructor(chatRepository, messageRepository, artifactStore) {
16
- this.chatRepository = chatRepository;
17
- this.messageRepository = messageRepository;
18
- this.artifactStore = artifactStore;
14
+ activityRepository;
15
+ agentConfigRepository;
16
+ config;
17
+ socketService;
18
+ agentStore;
19
+ debug;
20
+ constructor(params) {
21
+ this.chatRepository = params.chatRepository;
22
+ this.messageRepository = params.messageRepository;
23
+ this.artifactStore = params.artifactStore;
24
+ this.config = params.config;
25
+ this.socketService = params.socketService;
26
+ this.agentStore = params.agentStore;
27
+ this.activityRepository = params.activityRepository;
28
+ this.agentConfigRepository = params.agentConfigRepository;
29
+ const fileHelper = new FileHelper(params.s3Lib, this.config);
30
+ this.aiHelper = new AIHelper(fileHelper, this.config);
31
+ this.debug = this.config.debug;
19
32
  }
20
33
  getTemporaryTitle(contextKey) {
21
34
  return `${contextKey} session ${new Date().toISOString()}`;
@@ -27,6 +40,79 @@ export class ChatProcessor {
27
40
  const pattern = /^.+ session \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
28
41
  return pattern.test(title);
29
42
  }
43
+ isPlainObject(value) {
44
+ return !!value && typeof value === 'object' && !Array.isArray(value);
45
+ }
46
+ mergeMissingUsageFields(target, source) {
47
+ for (const [key, sourceValue] of Object.entries(source)) {
48
+ const targetValue = target[key];
49
+ if (targetValue === undefined) {
50
+ target[key] = sourceValue;
51
+ continue;
52
+ }
53
+ if (this.isPlainObject(targetValue) && this.isPlainObject(sourceValue)) {
54
+ this.mergeMissingUsageFields(targetValue, sourceValue);
55
+ }
56
+ }
57
+ }
58
+ extractCostUsageFields(source) {
59
+ const extracted = {};
60
+ for (const [key, value] of Object.entries(source)) {
61
+ const normalizedKey = key.toLowerCase();
62
+ if (normalizedKey.includes('cost')) {
63
+ extracted[key] = value;
64
+ continue;
65
+ }
66
+ if (this.isPlainObject(value)) {
67
+ const nestedCostFields = this.extractCostUsageFields(value);
68
+ if (Object.keys(nestedCostFields).length > 0) {
69
+ extracted[key] = nestedCostFields;
70
+ }
71
+ }
72
+ }
73
+ return extracted;
74
+ }
75
+ mergeUsageWithProviderMetadata(stepUsage, providerMetadata) {
76
+ const mergedUsage = this.isPlainObject(stepUsage) ? { ...stepUsage } : {};
77
+ let hasProviderUsage = false;
78
+ if (this.isPlainObject(providerMetadata)) {
79
+ for (const providerEntry of Object.values(providerMetadata)) {
80
+ if (!this.isPlainObject(providerEntry) || !this.isPlainObject(providerEntry.usage)) {
81
+ continue;
82
+ }
83
+ const providerCostUsage = this.extractCostUsageFields(providerEntry.usage);
84
+ if (Object.keys(providerCostUsage).length === 0) {
85
+ continue;
86
+ }
87
+ hasProviderUsage = true;
88
+ this.mergeMissingUsageFields(mergedUsage, providerCostUsage);
89
+ }
90
+ }
91
+ if (this.isPlainObject(stepUsage)) {
92
+ return mergedUsage;
93
+ }
94
+ if (hasProviderUsage) {
95
+ return mergedUsage;
96
+ }
97
+ return stepUsage;
98
+ }
99
+ async validateToolCallInput(params) {
100
+ const agentOptions = await this.aiHelper.getAgentOptions({
101
+ agentName: params.agentName,
102
+ });
103
+ const tool = agentOptions.tools?.[params.toolName] ?? undefined;
104
+ const schema = tool?.inputSchema;
105
+ if (!schema) {
106
+ throw new Error(`Tool "${params.toolName}" schema not found`);
107
+ }
108
+ if (!(schema instanceof z.ZodType)) {
109
+ throw new Error(`Tool "${params.toolName}" inputSchema must be a Zod schema`);
110
+ }
111
+ const result = schema.safeParse(params.input);
112
+ if (!result.success) {
113
+ throw new Error(`Invalid tool input for "${params.toolName}": ${result.error.message}`);
114
+ }
115
+ }
30
116
  async listChats(params) {
31
117
  // Build filter object from params
32
118
  const filter = {};
@@ -36,33 +122,56 @@ export class ChatProcessor {
36
122
  if (params?.contextKey) {
37
123
  filter.contextKey = params.contextKey;
38
124
  }
39
- // Build query options for sort and limit with defaults
125
+ // Build query options for sort and limit with defaults.
40
126
  const options = {
41
127
  sort: {
42
128
  field: params?.sortField ?? 'updatedAt',
43
129
  order: params?.sortOrder ?? SortOrder.Desc
44
130
  },
131
+ skip: params?.skip,
45
132
  limit: params?.limit
46
133
  };
47
- // Use aggregation to fetch chats with related messages
48
- return this.chatRepository.findWithMessages(filter, options);
134
+ const [total, data] = await Promise.all([
135
+ this.chatRepository.count(filter),
136
+ this.chatRepository.find(filter, options)
137
+ ]);
138
+ return {
139
+ cursor: {
140
+ ...(params?.skip != null ? { skip: params.skip } : {}),
141
+ ...(params?.limit != null ? { limit: params.limit } : {}),
142
+ total
143
+ },
144
+ data
145
+ };
49
146
  }
50
147
  async getChat(chatId) {
51
148
  const chat = await this.chatRepository.findById(chatId);
52
149
  if (!chat) {
53
150
  throw new Error('Chat not found');
54
151
  }
55
- const messages = await this.messageRepository.findByChatId(chatId);
56
- return {
57
- ...chat,
58
- messages
59
- };
152
+ return chat;
153
+ }
154
+ /**
155
+ * Get messages for a chat with optional cursor pagination.
156
+ * When limit is omitted, returns all messages (backward compatible).
157
+ * Messages are returned in chronological order (oldest → newest).
158
+ */
159
+ async getMessages(chatId, options) {
160
+ const chat = await this.chatRepository.findById(chatId);
161
+ if (!chat) {
162
+ throw new Error('Chat not found');
163
+ }
164
+ return this.messageRepository.findByChatIdPaginated(chatId, options);
60
165
  }
61
166
  async deleteChat(chatId) {
62
167
  const deleted = await this.chatRepository.delete(chatId);
63
168
  if (!deleted) {
64
169
  throw new Error('Chat not found');
65
170
  }
171
+ await Promise.all([
172
+ this.messageRepository.deleteByChatId(chatId),
173
+ this.activityRepository.deleteByGroupId(chatId), // todo: discuss if this is needed
174
+ ]);
66
175
  this.artifactStore.deleteArtifacts(chatId);
67
176
  }
68
177
  async upsertAndGetChat(payload, excludeSocketId) {
@@ -98,7 +207,7 @@ export class ChatProcessor {
98
207
  if (!content) {
99
208
  return this.getTemporaryTitle(payload.contextKey);
100
209
  }
101
- return AIHelper.generateTitleForMessage(payload.contextKey, [{
210
+ return this.aiHelper.generateTitleForMessage(payload.contextKey, [{
102
211
  role: MessageRole.User,
103
212
  content: content
104
213
  }]);
@@ -114,7 +223,7 @@ export class ChatProcessor {
114
223
  toolCalls: []
115
224
  });
116
225
  if (chat.userId) {
117
- socketService.emitMessageUpdate(chat.userId, message, excludeSocketId);
226
+ this.socketService.emitMessageUpdate(chat.userId, message, excludeSocketId);
118
227
  }
119
228
  return message;
120
229
  }
@@ -131,12 +240,7 @@ export class ChatProcessor {
131
240
  metadata,
132
241
  ...(payload.model ? { model: payload.model } : {})
133
242
  });
134
- //await kafkaService.sendChatTitleGenerationEvent(chat.id);
135
- const initialChatResponse = {
136
- ...chat,
137
- messages: []
138
- };
139
- socketService.emitChatUpdate(targetUserId, initialChatResponse, excludeSocketId);
243
+ this.socketService.emitChatUpdate(targetUserId, chat, excludeSocketId);
140
244
  return chat;
141
245
  }
142
246
  /**
@@ -191,33 +295,75 @@ export class ChatProcessor {
191
295
  await this.messageRepository.update(message.id, { toolCalls });
192
296
  // Bump chat updatedAt so listChats ordering reflects the action.
193
297
  await this.chatRepository.update(chat.id, { updatedAt: now });
194
- socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
298
+ this.socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
195
299
  if (params.systemMessage) {
196
300
  //todo discuss support for system messages
197
301
  //await this.createMessage(chat, MessageRole.System, params.systemMessage, undefined, params.excludeSocketId);
198
302
  }
199
303
  return { ok: true };
200
304
  }
305
+ /**
306
+ * Update selected fields (input/output/status) on a specific tool call.
307
+ */
308
+ async updateToolCall(params) {
309
+ if (params.input === undefined && params.output === undefined && params.status === undefined) {
310
+ throw new Error('At least one of input, output, or status must be provided');
311
+ }
312
+ const chat = await this.chatRepository.findById(params.chatId);
313
+ if (!chat) {
314
+ throw new Error('Chat not found');
315
+ }
316
+ if (params.userId && chat.userId !== params.userId) {
317
+ throw new Error('Chat does not belong to this user');
318
+ }
319
+ const message = await this.messageRepository.findById(params.messageId);
320
+ if (!message || message.chat !== params.chatId) {
321
+ throw new Error('Message not found or does not belong to this chat');
322
+ }
323
+ const targetToolCall = (message.toolCalls ?? []).find((tc) => tc.id === params.toolCallId);
324
+ if (!targetToolCall) {
325
+ throw new Error('Tool call not found in message');
326
+ }
327
+ if (params.input !== undefined) {
328
+ await this.validateToolCallInput({
329
+ agentName: message.agentName,
330
+ toolName: targetToolCall.name,
331
+ input: params.input,
332
+ });
333
+ }
334
+ const updatedMessage = await this.messageRepository.updateToolCall(params.messageId, params.toolCallId, {
335
+ ...(params.input !== undefined ? { input: params.input } : {}),
336
+ ...(params.output !== undefined ? { output: params.output } : {}),
337
+ ...(params.status !== undefined ? { status: params.status } : {}),
338
+ });
339
+ if (!updatedMessage) {
340
+ throw new Error('Tool call not found in message');
341
+ }
342
+ const now = new Date().toISOString();
343
+ await this.chatRepository.update(chat.id, { updatedAt: now });
344
+ this.socketService.emitMessageUpdate(chat.userId, updatedMessage, params.excludeSocketId);
345
+ return updatedMessage;
346
+ }
201
347
  async streamMessageStep(params) {
202
348
  const { chat, assistantMessage, existingMessages, signal, excludeSocketId, agentOptions } = params;
203
349
  if (signal.aborted) {
204
350
  return;
205
351
  }
206
- const result = await AIHelper.streamAssistantResponse(existingMessages, signal, agentOptions);
352
+ const result = await this.aiHelper.streamAssistantResponse(existingMessages, signal, agentOptions);
207
353
  try {
208
354
  for await (const chunk of result.fullStream) {
209
355
  if (chunk.type === 'error') {
210
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Error, data: chunk.error });
356
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Error, data: chunk.error });
211
357
  assistantMessage.content = chunk.error.message || 'Sorry, I cannot process you request due to the error';
212
358
  await this.messageRepository.update(assistantMessage.id, assistantMessage);
213
359
  await this.chatRepository.update(chat.id, { status: AgentStatus.Error });
214
360
  continue;
215
361
  }
216
362
  if (chunk.type === 'abort') {
217
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Aborted, data: undefined });
363
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Aborted, data: undefined });
218
364
  await this.chatRepository.update(chat.id, { status: AgentStatus.Aborted });
219
365
  await this.messageRepository.update(assistantMessage.id, assistantMessage);
220
- socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
366
+ this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
221
367
  continue;
222
368
  }
223
369
  if (chunk.type === 'finish') {
@@ -235,42 +381,39 @@ export class ChatProcessor {
235
381
  }
236
382
  await this.messageRepository.update(assistantMessage.id, { ...assistantMessage });
237
383
  if (chunk.finishReason === 'stop') {
238
- if (assistantMessage.toolCalls?.some(toolCall => toolCall.requiresConfirmation && toolCall.approvalId && !toolCall.output)) {
239
- await this.chatRepository.update(chat.id, { status: AgentStatus.WaitingForUserAction });
240
- }
241
- else if (!assistantMessage.content && assistantMessage.toolCalls?.length && assistantMessage.toolCalls[assistantMessage.toolCalls?.length - 1].output) {
242
- // handle tool-calls stop, openrouter provider does not return valid type
243
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
244
- await this.streamMessageStep(params);
245
- continue;
246
- }
247
- else {
248
- await this.chatRepository.update(chat.id, { status: AgentStatus.Finished });
249
- if (chat.title && this.isTemporaryTitle(chat.title)) {
250
- const title = await AIHelper.generateTitleForMessage(chat.contextKey, existingMessages);
251
- await this.chatRepository.update(chat.id, { title });
252
- socketService.emitChatUpdate(chat.userId, { ...chat, title, messages: [] });
253
- }
384
+ await this.chatRepository.update(chat.id, { status: AgentStatus.Finished });
385
+ if (chat.title && this.isTemporaryTitle(chat.title)) {
386
+ const title = await this.aiHelper.generateTitleForMessage(chat.contextKey, existingMessages.filter(m => m.role === MessageRole.User), (stepResult) => {
387
+ return this.storeStepActivity({
388
+ chat,
389
+ stepResult,
390
+ name: ActivityOperationName.TITLE_GENERATION,
391
+ sourceId: chat.id,
392
+ sourceType: 'Chat',
393
+ tenants: params.tenants || {},
394
+ });
395
+ });
396
+ await this.chatRepository.update(chat.id, { title });
397
+ this.socketService.emitChatUpdate(chat.userId, { ...chat, title });
254
398
  }
255
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
256
- continue;
257
399
  }
258
400
  if (chunk.finishReason === 'tool-calls') {
259
401
  if (assistantMessage.toolCalls?.some(toolCall => toolCall.requiresConfirmation && toolCall.approvalId && !toolCall.output)) {
260
402
  await this.chatRepository.update(chat.id, { status: AgentStatus.WaitingForUserAction });
261
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
262
- }
263
- else {
264
- await agentStore.shareAgentProcessEvent(chat.id, {
265
- type: AgentProcessEventType.Update,
266
- data: assistantMessage
267
- });
268
- await this.streamMessageStep(params);
403
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
404
+ continue;
269
405
  }
270
- continue;
271
406
  }
272
407
  //todo: Handle other finish reasons // length, content, error, other, undefined
273
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
408
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Finished, data: assistantMessage });
409
+ if (assistantMessage.activity) {
410
+ const groupedMeta = await this.activityRepository.getGroupedMetadataByParentId(assistantMessage.activity);
411
+ await this.activityRepository.updateMetadata(assistantMessage.activity, {
412
+ usage: groupedMeta,
413
+ finishReason: chunk.finishReason,
414
+ responseTimestamp: new Date().toISOString(),
415
+ });
416
+ }
274
417
  continue;
275
418
  }
276
419
  if (chunk.type === 'text-delta') {
@@ -279,8 +422,8 @@ export class ChatProcessor {
279
422
  ...assistantMessage,
280
423
  content: assistantMessage.content
281
424
  };
282
- socketService.emitMessageUpdate(chat.userId, updatedMessage, excludeSocketId);
283
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: updatedMessage });
425
+ this.socketService.emitMessageUpdate(chat.userId, updatedMessage, excludeSocketId);
426
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: updatedMessage });
284
427
  await this.messageRepository.update(assistantMessage.id, { content: assistantMessage.content });
285
428
  continue;
286
429
  }
@@ -295,7 +438,7 @@ export class ChatProcessor {
295
438
  });
296
439
  continue;
297
440
  }
298
- if (chunk.type === 'tool-call') { //Todo possible place to handle user-approval
441
+ if (chunk.type === 'tool-call') {
299
442
  const toolCall = assistantMessage.toolCalls?.find(toolCall => toolCall.id === chunk.toolCallId);
300
443
  if (toolCall) {
301
444
  toolCall.status = AgentToolCallStatus.Running;
@@ -307,7 +450,7 @@ export class ChatProcessor {
307
450
  const toolCall = assistantMessage.toolCalls?.find(toolCall => toolCall.id === chunk.toolCallId);
308
451
  if (toolCall) {
309
452
  toolCall.status = AgentToolCallStatus.Failed;
310
- toolCall.error = JSON.stringify(chunk.error);
453
+ toolCall.error = chunk.error.message || JSON.stringify(chunk.error);
311
454
  }
312
455
  continue;
313
456
  }
@@ -316,13 +459,16 @@ export class ChatProcessor {
316
459
  if (toolCall) {
317
460
  toolCall.requiresConfirmation = true;
318
461
  toolCall.approvalId = chunk.approvalId;
462
+ if (toolCall.name === AgentToolType.REQUEST_CLARIFICATION) {
463
+ toolCall.requiresUserAction = true;
464
+ }
319
465
  }
320
466
  continue;
321
467
  }
322
468
  if (chunk.type === 'tool-result') {
323
469
  const toolCall = assistantMessage.toolCalls?.find(toolCall => toolCall.id === chunk.toolCallId);
324
470
  if (toolCall) {
325
- if (chunk.output.requiresApproval) {
471
+ if (chunk.output?.requiresApproval) {
326
472
  toolCall.requiresConfirmation = true;
327
473
  continue;
328
474
  }
@@ -335,9 +481,9 @@ export class ChatProcessor {
335
481
  if (!assistantMessage.reasoning)
336
482
  assistantMessage.reasoning = '';
337
483
  assistantMessage.reasoning += chunk.text;
338
- socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
484
+ this.socketService.emitMessageUpdate(chat.userId, assistantMessage, excludeSocketId);
339
485
  await this.messageRepository.update(assistantMessage.id, { reasoning: assistantMessage.reasoning });
340
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
486
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
341
487
  continue;
342
488
  }
343
489
  }
@@ -346,7 +492,7 @@ export class ChatProcessor {
346
492
  if (streamError instanceof Error && streamError.name === 'AbortError') {
347
493
  return;
348
494
  }
349
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Error, data: streamError });
495
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Error, data: streamError });
350
496
  throw streamError;
351
497
  }
352
498
  }
@@ -358,7 +504,7 @@ export class ChatProcessor {
358
504
  if (!assistantMessage || assistantMessage.chat !== chat.id) {
359
505
  throw new Error(`Assistant message with id ${payload.messageId} not found`);
360
506
  }
361
- const agentOptions = await AIHelper.getAgentOptions({
507
+ const agentOptions = await this.aiHelper.getAgentOptions({
362
508
  agentName: assistantMessage.agentName,
363
509
  context: payload.context
364
510
  });
@@ -371,14 +517,20 @@ export class ChatProcessor {
371
517
  };
372
518
  }
373
519
  toolCall.approved = payload.approved;
374
- toolCall.reason = payload.reason;
520
+ toolCall.userResponse = payload.userResponse;
375
521
  if (!toolCall.approved) {
376
522
  toolCall.output = {
377
523
  type: 'execution-denied',
378
- reason: toolCall.reason
524
+ reason: toolCall.userResponse
379
525
  };
380
526
  toolCall.status = AgentToolCallStatus.Failed;
381
527
  }
528
+ else if (toolCall.requiresUserAction) {
529
+ toolCall.output = {
530
+ userResponse: toolCall.userResponse
531
+ };
532
+ toolCall.status = AgentToolCallStatus.Succeeded;
533
+ }
382
534
  else {
383
535
  const executeFunction = agentOptions.tools?.[toolCall.name]?.execute;
384
536
  if (executeFunction) {
@@ -411,32 +563,105 @@ export class ChatProcessor {
411
563
  }
412
564
  async processNewUserMessage(chat, payload, prevMessages, excludeSocketId) {
413
565
  const userMessage = await this.createMessage(chat, MessageRole.User, { content: payload.content, agentName: undefined }, payload.attachments);
414
- await agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: userMessage });
415
- const agentOptions = await AIHelper.getAgentOptions({
566
+ await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: userMessage });
567
+ let assistantMessage = await this.createMessage(chat, MessageRole.Assistant, { content: '', agentName: undefined }, undefined, excludeSocketId);
568
+ const parentActivity = await this.activityRepository.create({
569
+ ownerId: chat.userId,
570
+ groupId: chat.id,
571
+ name: ActivityOperationName.MESSAGE_PROCESSING,
572
+ tenants: payload.tenants ?? {},
573
+ sourceId: userMessage.id,
574
+ sourceType: 'AgentMessage',
575
+ metadata: {
576
+ contextKey: chat.contextKey,
577
+ modelId: this.aiHelper.getLanguageModelId(chat.model),
578
+ },
579
+ });
580
+ const agentOptions = await this.aiHelper.getAgentOptions({
416
581
  contextKey: chat.contextKey,
417
- messages: await AIHelper.convertMessages([...prevMessages, userMessage], true),
582
+ messages: [...prevMessages, userMessage],
418
583
  context: payload.context,
419
584
  agentName: undefined,
420
585
  modelId: chat.model,
586
+ }, {
587
+ onStepFinish: async (stepResult) => {
588
+ await this.storeStepActivity({
589
+ chat,
590
+ parentId: parentActivity.id,
591
+ stepResult,
592
+ name: ActivityOperationName.AGENT_SELECTION,
593
+ sourceId: userMessage.id,
594
+ sourceType: 'AgentMessage',
595
+ tenants: payload.tenants || {},
596
+ });
597
+ }
598
+ });
599
+ const updatedAssistantMessage = await this.messageRepository.update(assistantMessage.id, {
600
+ agentName: agentOptions.name,
601
+ activity: parentActivity.id
421
602
  });
422
- const assistantMessage = await this.createMessage(chat, MessageRole.Assistant, { content: ' ', agentName: agentOptions.name }, undefined, excludeSocketId);
423
- assistantMessage.content = '';
603
+ await this.activityRepository.update(parentActivity.id, {
604
+ metadata: {
605
+ ...parentActivity.metadata,
606
+ agentOptions: {
607
+ name: agentOptions.name,
608
+ modelId: this.aiHelper.getLanguageModelId(agentOptions.model),
609
+ temperature: agentOptions.temperature,
610
+ maxOutputTokens: agentOptions.maxOutputTokens,
611
+ topP: agentOptions.topP,
612
+ topK: agentOptions.topK,
613
+ presencePenalty: agentOptions.presencePenalty,
614
+ frequencyPenalty: agentOptions.frequencyPenalty,
615
+ stopSequences: agentOptions.stopSequences,
616
+ seed: agentOptions.seed,
617
+ },
618
+ },
619
+ });
620
+ if (!updatedAssistantMessage) {
621
+ throw new Error(`Assistant message with id ${assistantMessage.id} not found after update`);
622
+ }
623
+ assistantMessage = updatedAssistantMessage;
424
624
  return {
425
625
  userMessage,
426
626
  assistantMessage,
427
627
  agentOptions,
428
628
  };
429
629
  }
630
+ async storeStepActivity(params) {
631
+ try {
632
+ const stepResult = params.stepResult;
633
+ const mergedUsage = this.mergeUsageWithProviderMetadata(stepResult.usage, stepResult.providerMetadata);
634
+ const activity = await this.activityRepository.create({
635
+ ownerId: params.chat.userId,
636
+ groupId: params.chat.id,
637
+ name: params.name,
638
+ tenants: params.tenants || {},
639
+ sourceId: params.sourceId,
640
+ sourceType: params.sourceType,
641
+ metadata: {
642
+ finishReason: stepResult.finishReason,
643
+ usage: mergedUsage,
644
+ responseTimestamp: stepResult.response.timestamp,
645
+ modelId: stepResult.response.modelId,
646
+ messages: this.debug ? stepResult.request?.body?.messages : undefined,
647
+ },
648
+ parentId: params.parentId,
649
+ });
650
+ }
651
+ catch (error) {
652
+ logger.error({ error, chatId: params.chat.id, parentId: params.parentId }, 'Failed to store step activity');
653
+ }
654
+ }
430
655
  async streamMessage(chat, payload, excludeSocketId, onAgentStart) {
431
656
  try {
432
- const abortController = agentStore.registerAgentProcess(chat.id);
657
+ const abortController = this.agentStore.registerAgentProcess(chat.id);
433
658
  onAgentStart?.();
434
659
  let assistantMessage;
435
660
  let agentOptions;
436
661
  const existingMessages = await this.messageRepository.findByChatId(chat.id);
437
662
  // Limit context to prevent exceeding token limits
438
663
  const limitedMessages = ContextLimiter.limitContext(existingMessages, {
439
- maxMessages: config.ai.maxContextMessages,
664
+ maxMessages: this.config.ai.maxContextMessages,
440
665
  keepFirstUserMessage: true,
441
666
  keepSystemMessages: true,
442
667
  });
@@ -458,6 +683,19 @@ export class ChatProcessor {
458
683
  agentOptions = result.agentOptions;
459
684
  limitedMessages.push(result.userMessage);
460
685
  }
686
+ agentOptions.onStepFinish = (stepResult) => {
687
+ return this.storeStepActivity({
688
+ chat,
689
+ parentId: assistantMessage.activity,
690
+ stepResult,
691
+ name: ActivityOperationName.STEP_FINISHED,
692
+ sourceId: assistantMessage.id,
693
+ sourceType: 'AgentMessage',
694
+ tenants: payload.tenants || {},
695
+ });
696
+ };
697
+ const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat.userId, agentOptions.name);
698
+ agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
461
699
  await this.streamMessageStep({
462
700
  chat,
463
701
  existingMessages: limitedMessages,
@@ -465,12 +703,28 @@ export class ChatProcessor {
465
703
  agentOptions,
466
704
  excludeSocketId: excludeSocketId,
467
705
  signal: abortController.signal,
706
+ tenants: payload.tenants || {},
468
707
  });
469
708
  }
470
709
  catch (error) {
471
710
  throw error;
472
711
  }
473
712
  }
713
+ getAvailableTools(agentOptions, userPreferences) {
714
+ const toolNames = Object.keys(agentOptions.tools || {});
715
+ const availableToolsMap = Object.fromEntries(toolNames.map(key => [key, true]));
716
+ const agentsConfiguration = ConfigStore.getInstance().getAgentConfigByName(agentOptions.name);
717
+ agentsConfiguration.tools.forEach((tool) => {
718
+ const toolTitle = tool.data?.title || tool.type;
719
+ if (tool.disabledByDefault) {
720
+ availableToolsMap[toolTitle] = false;
721
+ }
722
+ if (tool.userConfigurable && userPreferences?.tools?.[toolTitle] !== undefined) {
723
+ availableToolsMap[toolTitle] = userPreferences.tools[toolTitle];
724
+ }
725
+ });
726
+ return Object.keys(availableToolsMap).filter(key => availableToolsMap[key]);
727
+ }
474
728
  listArtifacts(chatId) {
475
729
  return this.artifactStore.listArtifacts(chatId);
476
730
  }
@@ -501,7 +755,7 @@ export class ChatProcessor {
501
755
  return null;
502
756
  }
503
757
  }
504
- getMessageStream(chatId) {
758
+ getMessageStream(chatId, signal) {
505
759
  const stream = new PassThrough();
506
760
  let ended = false;
507
761
  const endStream = (error) => {
@@ -522,13 +776,15 @@ export class ChatProcessor {
522
776
  }
523
777
  };
524
778
  const pushChunk = (chunk) => {
525
- if (ended || stream.destroyed || stream.writableEnded)
779
+ if (ended || stream.destroyed || stream.writableEnded || signal?.aborted)
526
780
  return;
527
781
  stream.write(`data: ${JSON.stringify(chunk)}\n\n`);
528
782
  };
529
783
  const listener = (event) => {
530
- if (ended)
784
+ if (signal?.aborted) {
785
+ endStream();
531
786
  return;
787
+ }
532
788
  try {
533
789
  const chunk = this.eventToChunk(event);
534
790
  if (chunk) {
@@ -538,7 +794,6 @@ export class ChatProcessor {
538
794
  event.type === AgentProcessEventType.Error ||
539
795
  event.type === AgentProcessEventType.Aborted) {
540
796
  endStream();
541
- agentStore.removeListener(chatId, listener);
542
797
  }
543
798
  }
544
799
  catch (err) {
@@ -547,21 +802,18 @@ export class ChatProcessor {
547
802
  error: err instanceof Error ? err.message : 'Unknown error occurred',
548
803
  });
549
804
  endStream(err);
550
- agentStore.removeListener(chatId, listener);
551
805
  }
552
806
  };
553
- // Cleanup on consumer side closing
554
807
  const closeHandler = () => {
555
- agentStore.removeListener(chatId, listener);
808
+ this.agentStore.removeListener(chatId, listener);
556
809
  };
557
810
  const errorHandler = (err) => {
558
- logger.error({ err }, 'Readable stream error');
559
- agentStore.removeListener(chatId, listener);
811
+ signal?.removeEventListener('abort', endStream);
560
812
  endStream(err);
561
813
  };
562
814
  stream.on('close', closeHandler);
563
815
  stream.on('error', errorHandler);
564
- const listenerAttached = agentStore.addListener(chatId, listener);
816
+ const listenerAttached = this.agentStore.addListener(chatId, listener);
565
817
  if (!listenerAttached) {
566
818
  // Remove stream listeners if listener attachment failed to prevent memory leak
567
819
  stream.removeListener('close', closeHandler);
@@ -570,50 +822,27 @@ export class ChatProcessor {
570
822
  }
571
823
  return stream;
572
824
  }
573
- async createMessageStream(payload, excludeSocketId) {
825
+ async createMessageStream(payload, excludeSocketId, signal) {
574
826
  const stream = new PassThrough();
575
- (async () => {
576
- try {
577
- const chat = await this.upsertAndGetChat(payload, excludeSocketId);
578
- // Send chat metadata first
579
- if (!('chatId' in payload) && !('messageId' in payload) && chat) {
580
- stream.write(`data: ${JSON.stringify({
581
- type: StreamChunkType.Chat,
582
- chatId: chat.id,
583
- chat,
584
- })}\n\n`);
585
- }
586
- await this.streamMessage(chat, payload, excludeSocketId, async () => {
587
- if (stream.destroyed || stream.writableEnded) {
588
- return;
589
- }
590
- const messageStream = this.getMessageStream(chat.id);
591
- // If client disconnects, destroy inner stream
592
- stream.on('close', () => {
593
- if (!messageStream.destroyed) {
594
- messageStream.destroy();
595
- }
596
- });
597
- await pipelineAsync(messageStream, stream);
598
- });
599
- }
600
- catch (error) {
601
- logger.error({ error }, 'createMessageStream failed');
602
- if (!stream.destroyed && !stream.writableEnded) {
603
- stream.write(`data: ${JSON.stringify({
604
- type: StreamChunkType.Error,
605
- error: error instanceof Error ? error.message : 'Unknown error occurred',
606
- })}\n\n`);
607
- stream.write('data: [DONE]\n\n');
608
- stream.end();
609
- }
610
- }
611
- })().catch((err) => {
612
- logger.error({ err }, 'Unhandled stream task error');
613
- if (!stream.destroyed)
614
- stream.destroy(err);
827
+ this.runMessageStream(stream, payload, excludeSocketId, signal)
828
+ .catch(err => {
829
+ logger.error({ err }, 'Stream task failed');
615
830
  });
616
831
  return stream;
617
832
  }
833
+ async runMessageStream(stream, payload, excludeSocketId, signal) {
834
+ const chat = await this.upsertAndGetChat(payload, excludeSocketId);
835
+ if (!('chatId' in payload) && !('messageId' in payload) && chat) {
836
+ stream.write(`data: ${JSON.stringify({
837
+ type: StreamChunkType.Chat,
838
+ chatId: chat.id,
839
+ chat,
840
+ })}\n\n`);
841
+ }
842
+ await this.streamMessage(chat, payload, excludeSocketId, () => {
843
+ const messageStream = this.getMessageStream(chat.id, signal);
844
+ messageStream.pipe(stream);
845
+ });
846
+ }
618
847
  }
619
848
  //# sourceMappingURL=ChatProcessor.js.map