@peopl-health/nexus 3.1.2 → 3.1.4
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/lib/assistants/BaseAssistant.js +6 -3
- package/lib/core/NexusMessaging.js +37 -9
- package/lib/helpers/filesHelper.js +1 -1
- package/lib/helpers/messageHelper.js +43 -19
- package/lib/helpers/processHelper.js +3 -3
- package/lib/memory/DefaultMemoryManager.js +8 -8
- package/lib/memory/MemoryManager.js +1 -1
- package/lib/providers/OpenAIResponsesProvider.js +0 -1
- package/lib/services/assistantServiceCore.js +5 -4
- package/package.json +1 -1
|
@@ -198,9 +198,12 @@ class BaseAssistant {
|
|
|
198
198
|
// Messages with from_me: true are assistant messages, from_me: false are user messages
|
|
199
199
|
const formattedMessages = messagesInOrder
|
|
200
200
|
.filter(message => message && message.timestamp && message.body && message.body.trim() !== '')
|
|
201
|
-
.
|
|
202
|
-
const
|
|
203
|
-
return
|
|
201
|
+
.flatMap(message => {
|
|
202
|
+
const formattedTexts = formatMessage(message);
|
|
203
|
+
return formattedTexts.map(text => ({
|
|
204
|
+
role: message.from_me ? 'assistant' : 'user',
|
|
205
|
+
content: text
|
|
206
|
+
}));
|
|
204
207
|
})
|
|
205
208
|
.filter(message => message !== null);
|
|
206
209
|
|
|
@@ -480,7 +480,7 @@ class NexusMessaging {
|
|
|
480
480
|
return;
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
const result = await replyAssistant(from, body);
|
|
483
|
+
const result = await this._processMessages(from, () => replyAssistant(from, body));
|
|
484
484
|
const response = typeof result === 'string' ? result : result?.output;
|
|
485
485
|
const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
|
|
486
486
|
|
|
@@ -547,7 +547,7 @@ class NexusMessaging {
|
|
|
547
547
|
? body
|
|
548
548
|
: `Media received (${mediaDescriptor || 'attachment'})`;
|
|
549
549
|
|
|
550
|
-
const result = await replyAssistant(from, fallbackMessage);
|
|
550
|
+
const result = await this._processMessages(from, () => replyAssistant(from, fallbackMessage));
|
|
551
551
|
const response = typeof result === 'string' ? result : result?.output;
|
|
552
552
|
const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
|
|
553
553
|
|
|
@@ -718,7 +718,7 @@ class NexusMessaging {
|
|
|
718
718
|
runId = `run_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
719
719
|
this.activeRequests.set(chatId, runId);
|
|
720
720
|
|
|
721
|
-
const result = await replyAssistant(chatId, null, null, { runId });
|
|
721
|
+
const result = await this._processMessages(chatId, () => replyAssistant(chatId, null, null, { runId }));
|
|
722
722
|
|
|
723
723
|
if (this.abandonedRuns.has(runId)) {
|
|
724
724
|
logger.info(`[CheckAfter] Discarding abandoned run ${runId} for ${chatId}`);
|
|
@@ -732,6 +732,7 @@ class NexusMessaging {
|
|
|
732
732
|
if (typingInterval) {
|
|
733
733
|
clearInterval(typingInterval);
|
|
734
734
|
typingInterval = null;
|
|
735
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
735
736
|
}
|
|
736
737
|
|
|
737
738
|
if (botResponse) {
|
|
@@ -743,7 +744,7 @@ class NexusMessaging {
|
|
|
743
744
|
tools_executed
|
|
744
745
|
});
|
|
745
746
|
}
|
|
746
|
-
|
|
747
|
+
|
|
747
748
|
this.events.emit('messages:processed', { chatId, response: botResponse });
|
|
748
749
|
|
|
749
750
|
} catch (error) {
|
|
@@ -762,7 +763,6 @@ class NexusMessaging {
|
|
|
762
763
|
* Get count of unprocessed messages for a chat
|
|
763
764
|
*/
|
|
764
765
|
async _getUnprocessedMessageCount(chatId) {
|
|
765
|
-
const { Message } = require('../models/messageModel');
|
|
766
766
|
return await Message.countDocuments({
|
|
767
767
|
numero: chatId,
|
|
768
768
|
processed: false,
|
|
@@ -770,6 +770,34 @@ class NexusMessaging {
|
|
|
770
770
|
});
|
|
771
771
|
}
|
|
772
772
|
|
|
773
|
+
/**
|
|
774
|
+
* Central processing pipeline - handles assistant processing, preprocessing, and status updates
|
|
775
|
+
*/
|
|
776
|
+
async _processMessages(chatId, processingFn) {
|
|
777
|
+
const unprocessedMessages = await Message.find({
|
|
778
|
+
numero: chatId,
|
|
779
|
+
from_me: false,
|
|
780
|
+
processed: false
|
|
781
|
+
}).select('_id');
|
|
782
|
+
|
|
783
|
+
try {
|
|
784
|
+
const result = await processingFn();
|
|
785
|
+
|
|
786
|
+
if (unprocessedMessages.length > 0) {
|
|
787
|
+
await Message.updateMany(
|
|
788
|
+
{ _id: { $in: unprocessedMessages.map(m => m._id) } },
|
|
789
|
+
{ $set: { processed: true } }
|
|
790
|
+
);
|
|
791
|
+
logger.info(`[_processMessages] Marked ${unprocessedMessages.length} specific messages as processed for ${chatId}`);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return result;
|
|
795
|
+
} catch (error) {
|
|
796
|
+
logger.debug(`[_processMessages] Processing failed, messages remain unprocessed for ${chatId}`);
|
|
797
|
+
throw error;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
773
801
|
/**
|
|
774
802
|
* Start typing indicator refresh interval
|
|
775
803
|
*/
|
|
@@ -781,16 +809,16 @@ class NexusMessaging {
|
|
|
781
809
|
const lastMessage = await Message.findOne({
|
|
782
810
|
numero: chatId,
|
|
783
811
|
from_me: false,
|
|
784
|
-
|
|
785
|
-
|
|
812
|
+
processed: false,
|
|
813
|
+
message_id: { $exists: true, $ne: null, $not: /^pending-/ }
|
|
786
814
|
}).sort({ createdAt: -1 });
|
|
787
815
|
|
|
788
816
|
if (!lastMessage?.message_id) {
|
|
789
|
-
logger.debug(`[_startTypingRefresh] No
|
|
817
|
+
logger.debug(`[_startTypingRefresh] No unprocessed message for typing indicator: ${chatId}`);
|
|
790
818
|
return null;
|
|
791
819
|
}
|
|
792
820
|
|
|
793
|
-
logger.debug(`[_startTypingRefresh] Starting typing indicator for message: ${lastMessage.message_id}`);
|
|
821
|
+
logger.debug(`[_startTypingRefresh] Starting typing indicator for unprocessed message: ${lastMessage.message_id}`);
|
|
794
822
|
|
|
795
823
|
return setInterval(() =>
|
|
796
824
|
this.provider.sendTypingIndicator(lastMessage.message_id).catch(err =>
|
|
@@ -171,7 +171,7 @@ async function downloadMediaAndCreateFile(code, reply) {
|
|
|
171
171
|
if (!resultMedia) return [];
|
|
172
172
|
|
|
173
173
|
if (!resultMedia.media || !resultMedia.media.key) {
|
|
174
|
-
logger.info('[downloadMediaAndCreateFile] No valid media found for message:', reply.message_id);
|
|
174
|
+
logger.info('[downloadMediaAndCreateFile] No valid media found for message:', { messageId: reply.message_id });
|
|
175
175
|
return [];
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -2,31 +2,34 @@ const moment = require('moment-timezone');
|
|
|
2
2
|
const { Message } = require('../models/messageModel.js');
|
|
3
3
|
const { logger } = require('../utils/logger');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Store processed media content (transcriptions, image analysis) before assistant runs
|
|
7
|
+
*/
|
|
8
|
+
const storeProcessedContent = async (reply, thread, processedContent) => {
|
|
9
|
+
if (!processedContent || !reply.media) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
const threadId = thread.getConversationId();
|
|
7
14
|
|
|
8
|
-
const updateData = {
|
|
15
|
+
const updateData = {
|
|
9
16
|
assistant_id: thread.getAssistantId(),
|
|
10
17
|
thread_id: threadId,
|
|
11
|
-
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
if (processedContent && reply.media) {
|
|
15
|
-
updateData.media = {
|
|
18
|
+
media: {
|
|
16
19
|
...reply.media,
|
|
17
20
|
metadata: {
|
|
18
21
|
...(reply.media.metadata || {}),
|
|
19
|
-
|
|
22
|
+
processedContent
|
|
20
23
|
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
23
26
|
|
|
24
27
|
await Message.updateOne(
|
|
25
28
|
{ message_id: reply.message_id, timestamp: reply.timestamp },
|
|
26
29
|
{ $set: updateData }
|
|
27
30
|
);
|
|
28
31
|
|
|
29
|
-
logger.info(`[
|
|
32
|
+
logger.info(`[storeProcessedContent] Media content stored - ID: ${reply.message_id}`);
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
|
|
@@ -79,13 +82,13 @@ async function getLastNMessages(code, n) {
|
|
|
79
82
|
function formatMessage(reply) {
|
|
80
83
|
try {
|
|
81
84
|
if (!reply.createdAt) {
|
|
82
|
-
return
|
|
85
|
+
return [];
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
const msgDate = new Date(reply.createdAt);
|
|
86
89
|
if (isNaN(msgDate.getTime())) {
|
|
87
90
|
logger.warn(`[formatMessage] Invalid timestamp for message ID: ${reply.message_id}`);
|
|
88
|
-
return reply.body;
|
|
91
|
+
return reply.body ? [`[Invalid timestamp] ${reply.body}`] : [];
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
const mexicoCityTime = moment(msgDate)
|
|
@@ -93,15 +96,36 @@ function formatMessage(reply) {
|
|
|
93
96
|
.locale('es')
|
|
94
97
|
.format('dddd, D [de] MMMM [de] YYYY [a las] h:mm A');
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
const messages = [];
|
|
100
|
+
|
|
101
|
+
const generateMediaCode = (messageId, mediaType) => {
|
|
102
|
+
if (!messageId || !mediaType) return null;
|
|
103
|
+
const shortId = messageId.slice(-6);
|
|
104
|
+
return `${mediaType.toUpperCase()}-${shortId}`;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (reply.body && reply.body.trim()) {
|
|
108
|
+
const mediaCode = reply.media?.mediaType ?
|
|
109
|
+
generateMediaCode(reply.message_id, reply.media.mediaType) : null;
|
|
110
|
+
const mediaIndicator = mediaCode ? `[${mediaCode}] ` : '';
|
|
111
|
+
messages.push(`[${mexicoCityTime}] ${mediaIndicator}${reply.body}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (reply.media?.metadata?.processedContent &&
|
|
115
|
+
reply.media.metadata.processedContent !== reply.body) {
|
|
116
|
+
const mediaCode = generateMediaCode(reply.message_id, reply.media.mediaType);
|
|
117
|
+
const processingType = reply.media.mediaType === 'audio' ? 'TRANSCRIPTION' :
|
|
118
|
+
reply.media.mediaType === 'image' ? 'ANALYSIS' :
|
|
119
|
+
reply.media.mediaType === 'document' ? 'DOCUMENT_ANALYSIS' :
|
|
120
|
+
'PROCESSED';
|
|
121
|
+
const indicator = mediaCode ? `[${mediaCode} ${processingType}]` : `[${processingType}]`;
|
|
122
|
+
messages.push(`[${mexicoCityTime}] ${indicator} ${reply.media.metadata.processedContent}`);
|
|
99
123
|
}
|
|
100
124
|
|
|
101
|
-
return
|
|
125
|
+
return messages.length > 0 ? messages : [];
|
|
102
126
|
} catch (error) {
|
|
103
127
|
logger.error(`[formatMessage] Error for message ID: ${reply.message_id}:`, error?.message || String(error));
|
|
104
|
-
return
|
|
128
|
+
return [];
|
|
105
129
|
}
|
|
106
130
|
}
|
|
107
131
|
|
|
@@ -117,7 +141,7 @@ async function isRecentMessage(chatId) {
|
|
|
117
141
|
}
|
|
118
142
|
|
|
119
143
|
module.exports = {
|
|
120
|
-
|
|
144
|
+
storeProcessedContent,
|
|
121
145
|
getLastMessages,
|
|
122
146
|
getLastNMessages,
|
|
123
147
|
formatMessage,
|
|
@@ -299,8 +299,8 @@ const processThreadMessageCore = async (code, replies, provider) => {
|
|
|
299
299
|
const textMessages = processTextMessage(reply);
|
|
300
300
|
const mediaResult = await processMediaFiles(code, reply, provider);
|
|
301
301
|
|
|
302
|
-
const { messagesChat: mediaMessages, url, tempFiles
|
|
303
|
-
tempFiles
|
|
302
|
+
const { messagesChat: mediaMessages, url, tempFiles, timings: mediaTimings } = mediaResult;
|
|
303
|
+
console.log('[processThreadMessageCore] tempFiles', tempFiles);
|
|
304
304
|
|
|
305
305
|
if (mediaTimings) {
|
|
306
306
|
timings.download_ms += mediaTimings.download_ms || 0;
|
|
@@ -321,7 +321,7 @@ const processThreadMessageCore = async (code, replies, provider) => {
|
|
|
321
321
|
hasMedia: !!reply.media,
|
|
322
322
|
hasUrl: !!url
|
|
323
323
|
});
|
|
324
|
-
|
|
324
|
+
await cleanupFiles(tempFiles);
|
|
325
325
|
return { isPatient, url, messages, reply, tempFiles };
|
|
326
326
|
} catch (error) {
|
|
327
327
|
logger.error('processThreadMessage', error, {
|
|
@@ -11,7 +11,7 @@ class DefaultMemoryManager extends MemoryManager {
|
|
|
11
11
|
this.maxHistoricalMessages = parseInt(process.env.MAX_HISTORICAL_MESSAGES || '50', 10);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
async buildContext({ thread,
|
|
14
|
+
async buildContext({ thread, config = {} }) {
|
|
15
15
|
this._logActivity('Building context', { threadCode: thread.code });
|
|
16
16
|
|
|
17
17
|
try {
|
|
@@ -22,14 +22,14 @@ class DefaultMemoryManager extends MemoryManager {
|
|
|
22
22
|
return additionalMessages;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const messageContext = allMessages.reverse().
|
|
26
|
-
const
|
|
27
|
-
return {
|
|
25
|
+
const messageContext = allMessages.reverse().flatMap(msg => {
|
|
26
|
+
const formattedContents = formatMessage(msg);
|
|
27
|
+
return formattedContents.map(content => ({
|
|
28
28
|
role: msg.origin === 'patient' ? 'user' : 'assistant',
|
|
29
|
-
content:
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
|
|
29
|
+
content: content || msg.body || msg.content || ''
|
|
30
|
+
}));
|
|
31
|
+
}).filter(msg => msg.content);
|
|
32
|
+
|
|
33
33
|
return [...additionalMessages, ...messageContext];
|
|
34
34
|
} catch (error) {
|
|
35
35
|
logger.error('[DefaultMemoryManager] Context building failed', {
|
|
@@ -10,7 +10,7 @@ const { Historial_Clinico_ID } = require('../config/airtableConfig');
|
|
|
10
10
|
const { getCurRow, runAssistantWithRetries } = require('../helpers/assistantHelper.js');
|
|
11
11
|
const { getThread } = require('../helpers/threadHelper.js');
|
|
12
12
|
const { processThreadMessage } = require('../helpers/processHelper.js');
|
|
13
|
-
const { getLastNMessages,
|
|
13
|
+
const { getLastNMessages, storeProcessedContent } = require('../helpers/messageHelper.js');
|
|
14
14
|
const { combineImagesToPDF, cleanupFiles } = require('../helpers/filesHelper.js');
|
|
15
15
|
const { getAssistantById } = require('./assistantResolver');
|
|
16
16
|
|
|
@@ -197,8 +197,9 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
197
197
|
.map(msg => msg.content.text)
|
|
198
198
|
.join(' ')
|
|
199
199
|
: null;
|
|
200
|
-
return
|
|
201
|
-
}));
|
|
200
|
+
return r.reply ? storeProcessedContent(r.reply, finalThread, processedContent) : null;
|
|
201
|
+
}).filter(Boolean));
|
|
202
|
+
|
|
202
203
|
await cleanupFiles(allTempFiles);
|
|
203
204
|
|
|
204
205
|
if (urls.length > 0) {
|
|
@@ -218,7 +219,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
if (processedFiles && processedFiles.length) {
|
|
221
|
-
cleanupFiles(processedFiles);
|
|
222
|
+
await cleanupFiles(processedFiles);
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
|