@multiplayer-app/ai-agent-node 0.1.0-beta.75 → 0.1.0-beta.77
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.
- package/dist/cjs/processors/ChatProcessor.cjs +363 -28
- package/dist/cjs/processors/ChatProcessor.cjs.map +1 -1
- package/dist/cjs/processors/ChatProcessor.d.ts +23 -2
- package/dist/cjs/processors/ChatProcessor.d.ts.map +1 -1
- package/dist/cjs/processors/ChatProcessor.test.cjs +10 -10
- package/dist/cjs/processors/ChatProcessor.test.cjs.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/processors/ChatProcessor.d.ts +23 -2
- package/dist/esm/processors/ChatProcessor.d.ts.map +1 -1
- package/dist/esm/processors/ChatProcessor.js +363 -28
- package/dist/esm/processors/ChatProcessor.js.map +1 -1
- package/dist/esm/processors/ChatProcessor.test.js +10 -10
- package/dist/esm/processors/ChatProcessor.test.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -17,6 +17,9 @@ export class ChatProcessor {
|
|
|
17
17
|
socketService;
|
|
18
18
|
agentStore;
|
|
19
19
|
debug;
|
|
20
|
+
getChatUserId(chat) {
|
|
21
|
+
return chat.tenants?.userId;
|
|
22
|
+
}
|
|
20
23
|
constructor(params) {
|
|
21
24
|
this.chatRepository = params.chatRepository;
|
|
22
25
|
this.messageRepository = params.messageRepository;
|
|
@@ -50,14 +53,34 @@ export class ChatProcessor {
|
|
|
50
53
|
}
|
|
51
54
|
return payload.tenants ?? {};
|
|
52
55
|
}
|
|
56
|
+
getParticipantIds(chat) {
|
|
57
|
+
const primary = chat.userId ?? this.getChatUserId(chat);
|
|
58
|
+
return Array.from(new Set([primary, ...(chat.participantIds ?? [])].filter(Boolean)));
|
|
59
|
+
}
|
|
60
|
+
hasChatAccess(chat, userId) {
|
|
61
|
+
if (!userId)
|
|
62
|
+
return true;
|
|
63
|
+
return this.getParticipantIds(chat).includes(userId);
|
|
64
|
+
}
|
|
65
|
+
emitChatToParticipants(chat, chatToEmit, excludeSocketId) {
|
|
66
|
+
for (const userId of this.getParticipantIds(chatToEmit.participantIds ? chatToEmit : chat)) {
|
|
67
|
+
this.socketService.emitChatUpdate(userId, chatToEmit, excludeSocketId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
emitMessageToParticipants(chat, message, excludeSocketId) {
|
|
71
|
+
for (const userId of this.getParticipantIds(chat)) {
|
|
72
|
+
this.socketService.emitMessageUpdate(userId, message, excludeSocketId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
53
75
|
async updateChatAndEmit(chat, update, excludeSocketId) {
|
|
54
76
|
await this.chatRepository.update(chat.id, update);
|
|
55
77
|
const updatedChat = await this.chatRepository.findById(chat.id);
|
|
56
78
|
const chatToEmit = updatedChat ?? { ...chat, ...update };
|
|
57
|
-
this.
|
|
79
|
+
this.emitChatToParticipants(chat, chatToEmit, excludeSocketId);
|
|
58
80
|
return chatToEmit;
|
|
59
81
|
}
|
|
60
82
|
async startSubagentProcess(params) {
|
|
83
|
+
const parentUserId = params.parentChat.userId ?? this.getChatUserId(params.parentChat) ?? 'guest';
|
|
61
84
|
const childChat = await this.chatRepository.create({
|
|
62
85
|
title: `${params.subAgentConfig.name} subagent`,
|
|
63
86
|
type: ChatType.Agent,
|
|
@@ -67,7 +90,8 @@ export class ChatProcessor {
|
|
|
67
90
|
parentMessageId: params.parentMessage.id,
|
|
68
91
|
parentToolCallId: params.parentToolCallId,
|
|
69
92
|
contextKey: params.parentChat.contextKey,
|
|
70
|
-
userId:
|
|
93
|
+
userId: parentUserId,
|
|
94
|
+
tenants: { ...params.parentChat.tenants },
|
|
71
95
|
model: params.subAgentConfig.defaultModel || params.parentChat.model,
|
|
72
96
|
});
|
|
73
97
|
try {
|
|
@@ -82,10 +106,10 @@ export class ChatProcessor {
|
|
|
82
106
|
toolCalls: params.parentMessage.toolCalls,
|
|
83
107
|
});
|
|
84
108
|
if (updatedParentMessage) {
|
|
85
|
-
this.
|
|
109
|
+
this.emitMessageToParticipants(params.parentChat, updatedParentMessage);
|
|
86
110
|
}
|
|
87
111
|
}
|
|
88
|
-
this.
|
|
112
|
+
this.emitChatToParticipants(params.parentChat, childChat);
|
|
89
113
|
const executionTenants = this.getExecutionTenants(params);
|
|
90
114
|
const abortController = this.agentStore.registerSubAgentProcess(childChat.id, params.parentChat.id);
|
|
91
115
|
const userMessage = await this.createMessage(childChat, MessageRole.User, {
|
|
@@ -101,7 +125,7 @@ export class ChatProcessor {
|
|
|
101
125
|
agentName: params.subAgentConfig.name
|
|
102
126
|
});
|
|
103
127
|
const parentActivity = await this.activityRepository.create({
|
|
104
|
-
ownerId:
|
|
128
|
+
ownerId: parentUserId,
|
|
105
129
|
groupId: params.parentChat.id,
|
|
106
130
|
name: ActivityOperationName.SUBAGENT,
|
|
107
131
|
tenants: executionTenants,
|
|
@@ -138,7 +162,7 @@ export class ChatProcessor {
|
|
|
138
162
|
tenants: executionTenants,
|
|
139
163
|
});
|
|
140
164
|
};
|
|
141
|
-
const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(
|
|
165
|
+
const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(parentUserId, agentOptions.name);
|
|
142
166
|
agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
|
|
143
167
|
await this.streamMessageStep({
|
|
144
168
|
chat: childChat,
|
|
@@ -239,11 +263,15 @@ export class ChatProcessor {
|
|
|
239
263
|
sessionKind: AgentSessionKind.ROOT
|
|
240
264
|
};
|
|
241
265
|
if (params?.userId) {
|
|
266
|
+
// Chats visible to this user: owner, tenant owner, or participant (multi-user).
|
|
242
267
|
filter.userId = params.userId;
|
|
243
268
|
}
|
|
244
269
|
if (params?.contextKey) {
|
|
245
270
|
filter.contextKey = params.contextKey;
|
|
246
271
|
}
|
|
272
|
+
if (params?.multiUser !== undefined) {
|
|
273
|
+
filter.multiUser = params.multiUser;
|
|
274
|
+
}
|
|
247
275
|
// Build query options for sort and limit with defaults.
|
|
248
276
|
const options = {
|
|
249
277
|
sort: {
|
|
@@ -330,17 +358,12 @@ export class ChatProcessor {
|
|
|
330
358
|
return descendants;
|
|
331
359
|
}
|
|
332
360
|
async upsertAndGetChat(payload, excludeSocketId) {
|
|
333
|
-
const targetUserId = payload.userId ?? 'guest';
|
|
334
361
|
let chat = null;
|
|
335
362
|
if (payload.chatId) {
|
|
336
363
|
chat = await this.chatRepository.findById(payload.chatId);
|
|
337
364
|
if (!chat) {
|
|
338
365
|
throw new Error('Chat not found');
|
|
339
366
|
}
|
|
340
|
-
// Ensure chat is associated with the caller's userId
|
|
341
|
-
if (chat.userId && chat.userId !== targetUserId) {
|
|
342
|
-
throw new Error('Chat does not belong to this user');
|
|
343
|
-
}
|
|
344
367
|
}
|
|
345
368
|
if (!chat) {
|
|
346
369
|
chat = await this.createChat(payload, excludeSocketId);
|
|
@@ -373,19 +396,22 @@ export class ChatProcessor {
|
|
|
373
396
|
role,
|
|
374
397
|
content: messageData.content,
|
|
375
398
|
agentName: messageData.agentName,
|
|
399
|
+
sender: messageData.sender,
|
|
400
|
+
mentions: messageData.mentions ?? [],
|
|
401
|
+
annotations: messageData.annotations,
|
|
376
402
|
attachments: attachments ?? [],
|
|
377
403
|
reasoning: "",
|
|
378
404
|
toolCalls: []
|
|
379
405
|
});
|
|
380
|
-
|
|
381
|
-
this.socketService.emitMessageUpdate(chat.userId, message, excludeSocketId);
|
|
382
|
-
}
|
|
406
|
+
this.emitMessageToParticipants(chat, message, excludeSocketId);
|
|
383
407
|
return message;
|
|
384
408
|
}
|
|
385
409
|
async createChat(payload, excludeSocketId) {
|
|
386
|
-
const targetUserId = payload.userId ?? 'guest';
|
|
410
|
+
const targetUserId = payload.userId ?? payload.tenants?.userId ?? 'guest';
|
|
387
411
|
const contextKey = 'contextKey' in payload ? payload.contextKey : 'default';
|
|
388
412
|
const metadata = 'metadata' in payload ? payload.metadata : {};
|
|
413
|
+
const requestedParticipantIds = 'participantIds' in payload && Array.isArray(payload.participantIds) ? payload.participantIds : [];
|
|
414
|
+
const participantIds = Array.from(new Set([targetUserId, ...requestedParticipantIds].filter(Boolean)));
|
|
389
415
|
const chat = await this.chatRepository.create({
|
|
390
416
|
title: this.getTemporaryTitle(contextKey),
|
|
391
417
|
type: ChatType.Chat,
|
|
@@ -393,10 +419,15 @@ export class ChatProcessor {
|
|
|
393
419
|
sessionKind: AgentSessionKind.ROOT,
|
|
394
420
|
contextKey,
|
|
395
421
|
userId: targetUserId,
|
|
422
|
+
tenants: {
|
|
423
|
+
...(payload.tenants ?? {}),
|
|
424
|
+
userId: targetUserId,
|
|
425
|
+
},
|
|
426
|
+
participantIds,
|
|
396
427
|
metadata,
|
|
397
428
|
...(payload.model ? { model: payload.model } : {})
|
|
398
429
|
});
|
|
399
|
-
this.
|
|
430
|
+
this.emitChatToParticipants(chat, chat, excludeSocketId);
|
|
400
431
|
return chat;
|
|
401
432
|
}
|
|
402
433
|
/**
|
|
@@ -451,7 +482,7 @@ export class ChatProcessor {
|
|
|
451
482
|
await this.messageRepository.update(message.id, { toolCalls });
|
|
452
483
|
// Bump chat updatedAt so listChats ordering reflects the action.
|
|
453
484
|
await this.chatRepository.update(chat.id, { updatedAt: now });
|
|
454
|
-
this.
|
|
485
|
+
this.emitMessageToParticipants(chat, updatedMessage, params.excludeSocketId);
|
|
455
486
|
if (params.systemMessage) {
|
|
456
487
|
//todo discuss support for system messages
|
|
457
488
|
//await this.createMessage(chat, MessageRole.System, params.systemMessage, undefined, params.excludeSocketId);
|
|
@@ -472,7 +503,7 @@ export class ChatProcessor {
|
|
|
472
503
|
if (!chat) {
|
|
473
504
|
throw new Error('Chat not found');
|
|
474
505
|
}
|
|
475
|
-
if (params.userId && chat
|
|
506
|
+
if (params.userId && !this.hasChatAccess(chat, params.userId)) {
|
|
476
507
|
throw new Error('Chat does not belong to this user');
|
|
477
508
|
}
|
|
478
509
|
const message = await this.messageRepository.findById(params.messageId);
|
|
@@ -501,7 +532,7 @@ export class ChatProcessor {
|
|
|
501
532
|
}
|
|
502
533
|
const now = new Date().toISOString();
|
|
503
534
|
await this.chatRepository.update(chat.id, { updatedAt: now });
|
|
504
|
-
this.
|
|
535
|
+
this.emitMessageToParticipants(chat, updatedMessage, params.excludeSocketId);
|
|
505
536
|
return updatedMessage;
|
|
506
537
|
}
|
|
507
538
|
async storeSubagentResponse(params) {
|
|
@@ -558,7 +589,7 @@ export class ChatProcessor {
|
|
|
558
589
|
tenants: executionTenants,
|
|
559
590
|
});
|
|
560
591
|
};
|
|
561
|
-
const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat.userId, agentOptions.name);
|
|
592
|
+
const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(this.getChatUserId(chat) ?? chat.userId ?? 'guest', agentOptions.name);
|
|
562
593
|
agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
|
|
563
594
|
await this.streamMessageStep({
|
|
564
595
|
chat,
|
|
@@ -575,7 +606,7 @@ export class ChatProcessor {
|
|
|
575
606
|
const { chat, assistantMessage, existingMessages, signal, excludeSocketId, agentOptions } = params;
|
|
576
607
|
const persistAndBroadcastToolCalls = async () => {
|
|
577
608
|
await this.messageRepository.update(assistantMessage.id, { toolCalls: assistantMessage.toolCalls ?? [] });
|
|
578
|
-
this.
|
|
609
|
+
this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
|
|
579
610
|
await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
|
|
580
611
|
};
|
|
581
612
|
if (signal.aborted) {
|
|
@@ -607,7 +638,7 @@ export class ChatProcessor {
|
|
|
607
638
|
await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Aborted, data: undefined });
|
|
608
639
|
await this.updateChatAndEmit(chat, { status: AgentStatus.Aborted }, excludeSocketId);
|
|
609
640
|
await this.messageRepository.update(assistantMessage.id, assistantMessage);
|
|
610
|
-
this.
|
|
641
|
+
this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
|
|
611
642
|
continue;
|
|
612
643
|
}
|
|
613
644
|
if (chunk.type === 'finish') {
|
|
@@ -624,7 +655,7 @@ export class ChatProcessor {
|
|
|
624
655
|
((totalUsage.promptTokens ?? 0) + (totalUsage.completionTokens ?? 0));
|
|
625
656
|
}
|
|
626
657
|
await this.messageRepository.update(assistantMessage.id, { ...assistantMessage });
|
|
627
|
-
this.
|
|
658
|
+
this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
|
|
628
659
|
if (chunk.finishReason === 'stop') {
|
|
629
660
|
let nextChat = await this.updateChatAndEmit(chat, { status: AgentStatus.Finished }, excludeSocketId);
|
|
630
661
|
if (chat.title && this.isTemporaryTitle(chat.title)) {
|
|
@@ -702,7 +733,7 @@ export class ChatProcessor {
|
|
|
702
733
|
...assistantMessage,
|
|
703
734
|
content: assistantMessage.content
|
|
704
735
|
};
|
|
705
|
-
this.
|
|
736
|
+
this.emitMessageToParticipants(chat, updatedMessage, excludeSocketId);
|
|
706
737
|
await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: updatedMessage });
|
|
707
738
|
await this.messageRepository.update(assistantMessage.id, { content: assistantMessage.content });
|
|
708
739
|
continue;
|
|
@@ -788,7 +819,7 @@ export class ChatProcessor {
|
|
|
788
819
|
if (!assistantMessage.reasoning)
|
|
789
820
|
assistantMessage.reasoning = '';
|
|
790
821
|
assistantMessage.reasoning += chunk.text;
|
|
791
|
-
this.
|
|
822
|
+
this.emitMessageToParticipants(chat, assistantMessage, excludeSocketId);
|
|
792
823
|
await this.messageRepository.update(assistantMessage.id, { reasoning: assistantMessage.reasoning });
|
|
793
824
|
await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: assistantMessage });
|
|
794
825
|
continue;
|
|
@@ -871,11 +902,12 @@ export class ChatProcessor {
|
|
|
871
902
|
}
|
|
872
903
|
async processNewUserMessage(chat, payload, prevMessages, excludeSocketId) {
|
|
873
904
|
const executionTenants = this.getExecutionTenants(payload);
|
|
905
|
+
const chatUserId = this.getChatUserId(chat) ?? 'guest';
|
|
874
906
|
const userMessage = await this.createMessage(chat, MessageRole.User, { content: payload.content, agentName: undefined }, payload.attachments);
|
|
875
907
|
await this.agentStore.shareAgentProcessEvent(chat.id, { type: AgentProcessEventType.Update, data: userMessage });
|
|
876
908
|
let assistantMessage = await this.createMessage(chat, MessageRole.Assistant, { content: '', agentName: undefined }, undefined, excludeSocketId);
|
|
877
909
|
const parentActivity = await this.activityRepository.create({
|
|
878
|
-
ownerId:
|
|
910
|
+
ownerId: chatUserId,
|
|
879
911
|
groupId: chat.id,
|
|
880
912
|
name: ActivityOperationName.MESSAGE_PROCESSING,
|
|
881
913
|
tenants: executionTenants,
|
|
@@ -937,12 +969,235 @@ export class ChatProcessor {
|
|
|
937
969
|
agentOptions,
|
|
938
970
|
};
|
|
939
971
|
}
|
|
972
|
+
getAgentNamesForContext(contextKey) {
|
|
973
|
+
try {
|
|
974
|
+
return ConfigStore.getInstance().getAgentsForContext(contextKey).map((config) => config.name);
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
return [];
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
normalizeMentionName(value) {
|
|
981
|
+
return value.trim().replace(/^@/, '').toLowerCase();
|
|
982
|
+
}
|
|
983
|
+
extractMentionNames(content) {
|
|
984
|
+
const mentions = content.matchAll(/(^|[\s([{])@([a-zA-Z0-9_.-]+)/g);
|
|
985
|
+
return Array.from(mentions, (match) => match[2]).filter(Boolean);
|
|
986
|
+
}
|
|
987
|
+
getExplicitAgentMention(params) {
|
|
988
|
+
const agentNames = this.getAgentNamesForContext(params.contextKey);
|
|
989
|
+
const normalizedAgentNames = new Map(agentNames.map((name) => [this.normalizeMentionName(name), name]));
|
|
990
|
+
for (const mention of params.mentions ?? []) {
|
|
991
|
+
if (mention.type !== 'agent')
|
|
992
|
+
continue;
|
|
993
|
+
const normalized = this.normalizeMentionName(mention.name);
|
|
994
|
+
return { mentioned: true, agentName: normalizedAgentNames.get(normalized) };
|
|
995
|
+
}
|
|
996
|
+
for (const name of this.extractMentionNames(params.content)) {
|
|
997
|
+
const normalized = this.normalizeMentionName(name);
|
|
998
|
+
if (normalized === 'agent') {
|
|
999
|
+
return { mentioned: true };
|
|
1000
|
+
}
|
|
1001
|
+
const agentName = normalizedAgentNames.get(normalized);
|
|
1002
|
+
if (agentName) {
|
|
1003
|
+
return { mentioned: true, agentName };
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return { mentioned: false };
|
|
1007
|
+
}
|
|
1008
|
+
hasNonAgentMention(params) {
|
|
1009
|
+
const agentNames = this.getAgentNamesForContext(params.contextKey);
|
|
1010
|
+
const normalizedAgentNames = new Set(['agent', ...agentNames.map((name) => this.normalizeMentionName(name))]);
|
|
1011
|
+
if ((params.mentions ?? []).some((mention) => mention.type === 'user')) {
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
return this.extractMentionNames(params.content).some((name) => {
|
|
1015
|
+
const normalized = this.normalizeMentionName(name);
|
|
1016
|
+
return !normalizedAgentNames.has(normalized);
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
renderMessageForDecision(message) {
|
|
1020
|
+
const sender = message.sender?.displayName || message.sender?.id || message.agentName || message.role;
|
|
1021
|
+
return `${sender}: ${message.content}`;
|
|
1022
|
+
}
|
|
1023
|
+
async analyzeAgentShouldRespond(params) {
|
|
1024
|
+
try {
|
|
1025
|
+
const recentMessages = [...params.previousMessages.slice(-8), params.userMessage]
|
|
1026
|
+
.map((message) => this.renderMessageForDecision(message))
|
|
1027
|
+
.join('\n');
|
|
1028
|
+
const response = await this.aiHelper.getAssistantResponse([
|
|
1029
|
+
{
|
|
1030
|
+
role: MessageRole.User,
|
|
1031
|
+
content: `Conversation:\n${recentMessages}\n\nShould the AI agent respond to the latest message?`
|
|
1032
|
+
}
|
|
1033
|
+
], {
|
|
1034
|
+
system: [
|
|
1035
|
+
'You decide whether an AI agent should reply in a multi-user chat.',
|
|
1036
|
+
'Reply with exactly YES or NO.',
|
|
1037
|
+
'YES only when the latest message asks for help, requests agent action, or is clearly addressed to the AI.',
|
|
1038
|
+
'NO when the latest message is human-to-human chatter, status, acknowledgement, or otherwise does not need the AI.'
|
|
1039
|
+
].join('\n'),
|
|
1040
|
+
temperature: 0,
|
|
1041
|
+
maxOutputTokens: 5,
|
|
1042
|
+
model: params.chat.model,
|
|
1043
|
+
executionContext: params.executionContext
|
|
1044
|
+
});
|
|
1045
|
+
return /^yes\b/i.test(response.trim());
|
|
1046
|
+
}
|
|
1047
|
+
catch (error) {
|
|
1048
|
+
logger.warn({ error, chatId: params.chat.id }, 'Agent response decision failed');
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
async shouldRunAgentForMultiUserMessage(params) {
|
|
1053
|
+
const firstUserMessage = !params.previousMessages.some((message) => message.role === MessageRole.User);
|
|
1054
|
+
if (firstUserMessage) {
|
|
1055
|
+
return { shouldRun: true };
|
|
1056
|
+
}
|
|
1057
|
+
const explicitAgent = this.getExplicitAgentMention({
|
|
1058
|
+
content: params.payload.content,
|
|
1059
|
+
mentions: params.payload.mentions,
|
|
1060
|
+
contextKey: params.chat.contextKey,
|
|
1061
|
+
});
|
|
1062
|
+
if (explicitAgent.mentioned) {
|
|
1063
|
+
return { shouldRun: true, agentName: explicitAgent.agentName };
|
|
1064
|
+
}
|
|
1065
|
+
if (this.hasNonAgentMention({
|
|
1066
|
+
content: params.payload.content,
|
|
1067
|
+
mentions: params.payload.mentions,
|
|
1068
|
+
contextKey: params.chat.contextKey,
|
|
1069
|
+
})) {
|
|
1070
|
+
return { shouldRun: false };
|
|
1071
|
+
}
|
|
1072
|
+
const shouldRun = await this.analyzeAgentShouldRespond({
|
|
1073
|
+
chat: params.chat,
|
|
1074
|
+
previousMessages: params.previousMessages,
|
|
1075
|
+
userMessage: params.userMessage,
|
|
1076
|
+
executionContext: params.payload.executionContext,
|
|
1077
|
+
});
|
|
1078
|
+
return { shouldRun };
|
|
1079
|
+
}
|
|
1080
|
+
async prepareAgentResponseForUserMessage(chat, payload, prevMessages, userMessage, excludeSocketId, agentName) {
|
|
1081
|
+
const executionTenants = this.getExecutionTenants(payload);
|
|
1082
|
+
let assistantMessage = await this.createMessage(chat, MessageRole.Assistant, {
|
|
1083
|
+
content: '',
|
|
1084
|
+
agentName: agentName,
|
|
1085
|
+
sender: {
|
|
1086
|
+
id: agentName ?? 'agent',
|
|
1087
|
+
type: 'agent',
|
|
1088
|
+
displayName: agentName ?? 'Agent',
|
|
1089
|
+
}
|
|
1090
|
+
}, undefined, excludeSocketId);
|
|
1091
|
+
const ownerId = payload.userId ?? chat.userId ?? this.getChatUserId(chat) ?? 'guest';
|
|
1092
|
+
const parentActivity = await this.activityRepository.create({
|
|
1093
|
+
ownerId,
|
|
1094
|
+
groupId: chat.id,
|
|
1095
|
+
name: ActivityOperationName.MESSAGE_PROCESSING,
|
|
1096
|
+
tenants: executionTenants,
|
|
1097
|
+
sourceId: userMessage.id,
|
|
1098
|
+
sourceType: 'AgentMessage',
|
|
1099
|
+
metadata: {
|
|
1100
|
+
contextKey: chat.contextKey,
|
|
1101
|
+
modelId: this.aiHelper.getLanguageModelId(chat.model),
|
|
1102
|
+
},
|
|
1103
|
+
});
|
|
1104
|
+
const agentOptions = await this.aiHelper.getAgentOptions(agentName
|
|
1105
|
+
? {
|
|
1106
|
+
agentName,
|
|
1107
|
+
modelId: chat.model,
|
|
1108
|
+
context: payload.context,
|
|
1109
|
+
executionContext: payload.executionContext,
|
|
1110
|
+
}
|
|
1111
|
+
: {
|
|
1112
|
+
contextKey: chat.contextKey,
|
|
1113
|
+
messages: [...prevMessages, userMessage],
|
|
1114
|
+
context: payload.context,
|
|
1115
|
+
agentName: undefined,
|
|
1116
|
+
modelId: chat.model,
|
|
1117
|
+
executionContext: payload.executionContext,
|
|
1118
|
+
}, {
|
|
1119
|
+
onStepFinish: async (stepResult) => {
|
|
1120
|
+
await this.storeStepActivity({
|
|
1121
|
+
chat,
|
|
1122
|
+
parentId: parentActivity.id,
|
|
1123
|
+
stepResult,
|
|
1124
|
+
name: ActivityOperationName.AGENT_SELECTION,
|
|
1125
|
+
sourceId: userMessage.id,
|
|
1126
|
+
sourceType: 'AgentMessage',
|
|
1127
|
+
tenants: executionTenants,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
const updatedAssistantMessage = await this.messageRepository.update(assistantMessage.id, {
|
|
1132
|
+
agentName: agentOptions.name,
|
|
1133
|
+
activity: parentActivity.id,
|
|
1134
|
+
sender: {
|
|
1135
|
+
id: agentOptions.name,
|
|
1136
|
+
type: 'agent',
|
|
1137
|
+
displayName: agentOptions.name,
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
await this.activityRepository.update(parentActivity.id, {
|
|
1141
|
+
metadata: {
|
|
1142
|
+
...parentActivity.metadata,
|
|
1143
|
+
agentOptions: {
|
|
1144
|
+
name: agentOptions.name,
|
|
1145
|
+
modelId: this.aiHelper.getLanguageModelId(agentOptions.model),
|
|
1146
|
+
temperature: agentOptions.temperature,
|
|
1147
|
+
maxOutputTokens: agentOptions.maxOutputTokens,
|
|
1148
|
+
topP: agentOptions.topP,
|
|
1149
|
+
topK: agentOptions.topK,
|
|
1150
|
+
presencePenalty: agentOptions.presencePenalty,
|
|
1151
|
+
frequencyPenalty: agentOptions.frequencyPenalty,
|
|
1152
|
+
stopSequences: agentOptions.stopSequences,
|
|
1153
|
+
seed: agentOptions.seed,
|
|
1154
|
+
},
|
|
1155
|
+
},
|
|
1156
|
+
});
|
|
1157
|
+
if (!updatedAssistantMessage) {
|
|
1158
|
+
throw new Error(`Assistant message with id ${assistantMessage.id} not found after update`);
|
|
1159
|
+
}
|
|
1160
|
+
assistantMessage = updatedAssistantMessage;
|
|
1161
|
+
return {
|
|
1162
|
+
assistantMessage,
|
|
1163
|
+
agentOptions,
|
|
1164
|
+
executionTenants,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
async streamAgentResponseForUserMessage(params) {
|
|
1168
|
+
const { assistantMessage, agentOptions, executionTenants } = await this.prepareAgentResponseForUserMessage(params.chat, params.payload, params.previousMessages, params.userMessage, params.excludeSocketId, params.agentName);
|
|
1169
|
+
const messagesForAgent = [...params.previousMessages, params.userMessage];
|
|
1170
|
+
agentOptions.onStepFinish = (stepResult) => {
|
|
1171
|
+
return this.storeStepActivity({
|
|
1172
|
+
chat: params.chat,
|
|
1173
|
+
parentId: assistantMessage.activity,
|
|
1174
|
+
stepResult,
|
|
1175
|
+
name: ActivityOperationName.STEP_FINISHED,
|
|
1176
|
+
sourceId: assistantMessage.id,
|
|
1177
|
+
sourceType: 'AgentMessage',
|
|
1178
|
+
tenants: executionTenants,
|
|
1179
|
+
});
|
|
1180
|
+
};
|
|
1181
|
+
const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(params.payload.userId ?? params.chat.userId ?? this.getChatUserId(params.chat) ?? 'guest', agentOptions.name);
|
|
1182
|
+
agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
|
|
1183
|
+
await this.streamMessageStep({
|
|
1184
|
+
chat: params.chat,
|
|
1185
|
+
existingMessages: messagesForAgent,
|
|
1186
|
+
assistantMessage,
|
|
1187
|
+
agentOptions,
|
|
1188
|
+
excludeSocketId: params.excludeSocketId,
|
|
1189
|
+
signal: params.signal,
|
|
1190
|
+
tenants: executionTenants,
|
|
1191
|
+
executionContext: params.payload.executionContext,
|
|
1192
|
+
context: params.payload.context,
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
940
1195
|
async storeStepActivity(params) {
|
|
941
1196
|
try {
|
|
942
1197
|
const stepResult = params.stepResult;
|
|
943
1198
|
const mergedUsage = this.mergeUsageWithProviderMetadata(stepResult.usage, stepResult.providerMetadata);
|
|
944
1199
|
const activity = await this.activityRepository.create({
|
|
945
|
-
ownerId: params.chat
|
|
1200
|
+
ownerId: this.getChatUserId(params.chat) ?? 'guest',
|
|
946
1201
|
groupId: params.chat.id,
|
|
947
1202
|
name: params.name,
|
|
948
1203
|
tenants: params.tenants || {},
|
|
@@ -1005,7 +1260,7 @@ export class ChatProcessor {
|
|
|
1005
1260
|
tenants: executionTenants,
|
|
1006
1261
|
});
|
|
1007
1262
|
};
|
|
1008
|
-
const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(chat
|
|
1263
|
+
const userPreferences = await this.agentConfigRepository.findByUserIdAndAgentName(this.getChatUserId(chat) ?? 'guest', agentOptions.name);
|
|
1009
1264
|
agentOptions.activeTools = this.getAvailableTools(agentOptions, userPreferences);
|
|
1010
1265
|
await this.streamMessageStep({
|
|
1011
1266
|
chat,
|
|
@@ -1135,6 +1390,86 @@ export class ChatProcessor {
|
|
|
1135
1390
|
}
|
|
1136
1391
|
return stream;
|
|
1137
1392
|
}
|
|
1393
|
+
async upsertAndGetMultiUserChat(payload, excludeSocketId) {
|
|
1394
|
+
const targetUserId = payload.userId ?? 'guest';
|
|
1395
|
+
let chat = null;
|
|
1396
|
+
let isNewChat = false;
|
|
1397
|
+
if (payload.chatId) {
|
|
1398
|
+
chat = await this.chatRepository.findById(payload.chatId);
|
|
1399
|
+
if (!chat) {
|
|
1400
|
+
throw new Error('Chat not found');
|
|
1401
|
+
}
|
|
1402
|
+
if (!this.hasChatAccess(chat, targetUserId)) {
|
|
1403
|
+
throw new Error('Chat does not belong to this user');
|
|
1404
|
+
}
|
|
1405
|
+
const participantIds = Array.from(new Set([
|
|
1406
|
+
...this.getParticipantIds(chat),
|
|
1407
|
+
...(payload.participantIds ?? []),
|
|
1408
|
+
targetUserId,
|
|
1409
|
+
].filter(Boolean)));
|
|
1410
|
+
const updates = {};
|
|
1411
|
+
if (participantIds.length !== this.getParticipantIds(chat).length) {
|
|
1412
|
+
updates.participantIds = participantIds;
|
|
1413
|
+
}
|
|
1414
|
+
if ('model' in payload) {
|
|
1415
|
+
updates.model = payload.model || undefined;
|
|
1416
|
+
}
|
|
1417
|
+
if (Object.keys(updates).length > 0) {
|
|
1418
|
+
chat = await this.updateChatAndEmit(chat, updates, excludeSocketId);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
if (!chat) {
|
|
1422
|
+
chat = await this.createChat(payload, excludeSocketId);
|
|
1423
|
+
isNewChat = true;
|
|
1424
|
+
}
|
|
1425
|
+
return { chat, isNewChat };
|
|
1426
|
+
}
|
|
1427
|
+
async postMultiUserMessage(payload, excludeSocketId) {
|
|
1428
|
+
const { chat } = await this.upsertAndGetMultiUserChat(payload, excludeSocketId);
|
|
1429
|
+
const existingMessages = await this.messageRepository.findByChatId(chat.id);
|
|
1430
|
+
const sender = {
|
|
1431
|
+
id: payload.userId ?? 'guest',
|
|
1432
|
+
type: 'user',
|
|
1433
|
+
...(payload.senderDisplayName ? { displayName: payload.senderDisplayName } : {}),
|
|
1434
|
+
...(payload.senderAvatarUrl ? { avatarUrl: payload.senderAvatarUrl } : {}),
|
|
1435
|
+
};
|
|
1436
|
+
const userMessage = await this.createMessage(chat, MessageRole.User, {
|
|
1437
|
+
content: payload.content,
|
|
1438
|
+
agentName: undefined,
|
|
1439
|
+
sender,
|
|
1440
|
+
mentions: payload.mentions ?? [],
|
|
1441
|
+
}, payload.attachments, excludeSocketId);
|
|
1442
|
+
const decision = await this.shouldRunAgentForMultiUserMessage({
|
|
1443
|
+
chat,
|
|
1444
|
+
previousMessages: existingMessages,
|
|
1445
|
+
userMessage,
|
|
1446
|
+
payload,
|
|
1447
|
+
});
|
|
1448
|
+
if (!decision.shouldRun) {
|
|
1449
|
+
const updatedChat = await this.updateChatAndEmit(chat, { status: AgentStatus.Finished }, excludeSocketId);
|
|
1450
|
+
return { chat: updatedChat, message: userMessage, agentTriggered: false };
|
|
1451
|
+
}
|
|
1452
|
+
const updatedChat = await this.updateChatAndEmit(chat, { status: AgentStatus.Streaming }, undefined);
|
|
1453
|
+
const abortController = this.agentStore.registerAgentProcess(chat.id);
|
|
1454
|
+
const limitedMessages = ContextLimiter.limitContext(existingMessages, {
|
|
1455
|
+
maxMessages: this.config.ai.maxContextMessages,
|
|
1456
|
+
keepFirstUserMessage: true,
|
|
1457
|
+
keepSystemMessages: true,
|
|
1458
|
+
});
|
|
1459
|
+
void this.streamAgentResponseForUserMessage({
|
|
1460
|
+
chat,
|
|
1461
|
+
payload,
|
|
1462
|
+
previousMessages: limitedMessages,
|
|
1463
|
+
userMessage,
|
|
1464
|
+
excludeSocketId: undefined,
|
|
1465
|
+
signal: abortController.signal,
|
|
1466
|
+
agentName: decision.agentName,
|
|
1467
|
+
}).catch(async (error) => {
|
|
1468
|
+
logger.error({ error, chatId: chat.id }, 'Multi-user background agent flow failed');
|
|
1469
|
+
await this.updateChatAndEmit(chat, { status: AgentStatus.Error }, undefined);
|
|
1470
|
+
});
|
|
1471
|
+
return { chat: updatedChat, message: userMessage, agentTriggered: true };
|
|
1472
|
+
}
|
|
1138
1473
|
async createMessageStream(payload, excludeSocketId, signal) {
|
|
1139
1474
|
const stream = new PassThrough();
|
|
1140
1475
|
this.runMessageStream(stream, payload, excludeSocketId, signal)
|