@peopl-health/nexus 3.0.3 → 3.0.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.
|
@@ -43,9 +43,14 @@ class NexusMessaging {
|
|
|
43
43
|
};
|
|
44
44
|
// Message processing with check-after strategy
|
|
45
45
|
this.processingLocks = new Map(); // Per-chat locks to prevent parallel processing
|
|
46
|
+
this.activeRequests = new Map(); // Track active AI requests per chat
|
|
47
|
+
this.abandonedRuns = new Set(); // Track runs that should be ignored
|
|
46
48
|
this.batchingConfig = {
|
|
47
49
|
enabled: config.messageBatching?.enabled ?? true, // Enabled by default with check-after
|
|
48
|
-
|
|
50
|
+
abortOnNewMessage: config.messageBatching?.abortOnNewMessage ?? true, // Abort ongoing AI calls when new messages arrive
|
|
51
|
+
immediateRestart: config.messageBatching?.immediateRestart ?? true, // Start new processing immediately without waiting
|
|
52
|
+
batchWindowMs: config.messageBatching?.batchWindowMs ?? 2000, // Wait up to 2s for message bursts
|
|
53
|
+
maxBatchWait: config.messageBatching?.maxBatchWait ?? 5000 // Maximum time to wait for batching
|
|
49
54
|
};
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -656,6 +661,20 @@ class NexusMessaging {
|
|
|
656
661
|
|
|
657
662
|
if (this.processingLocks.has(chatId)) {
|
|
658
663
|
logger.info(`[CheckAfter] Already processing ${chatId}, new message will be included`);
|
|
664
|
+
|
|
665
|
+
if (this.batchingConfig.abortOnNewMessage && this.activeRequests.has(chatId)) {
|
|
666
|
+
const runId = this.activeRequests.get(chatId);
|
|
667
|
+
this.abandonedRuns.add(runId);
|
|
668
|
+
logger.info(`[CheckAfter] Marked run ${runId} as abandoned for ${chatId}`);
|
|
669
|
+
|
|
670
|
+
if (this.batchingConfig.immediateRestart) {
|
|
671
|
+
this.processingLocks.delete(chatId);
|
|
672
|
+
this.activeRequests.delete(chatId);
|
|
673
|
+
|
|
674
|
+
logger.info(`[CheckAfter] Starting immediate reprocessing for ${chatId}`);
|
|
675
|
+
await this._processWithLock(chatId, null);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
659
678
|
return;
|
|
660
679
|
}
|
|
661
680
|
|
|
@@ -668,33 +687,48 @@ class NexusMessaging {
|
|
|
668
687
|
async _processWithLock(chatId, existingTypingInterval = null) {
|
|
669
688
|
this.processingLocks.set(chatId, true);
|
|
670
689
|
let typingInterval = existingTypingInterval;
|
|
690
|
+
let runId = null;
|
|
671
691
|
|
|
672
692
|
try {
|
|
673
693
|
if (!typingInterval) {
|
|
674
694
|
typingInterval = await this._startTypingRefresh(chatId);
|
|
675
695
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
696
|
+
|
|
697
|
+
const startTime = Date.now();
|
|
698
|
+
let messageCount = await this._getUnprocessedMessageCount(chatId);
|
|
699
|
+
let lastCount = messageCount;
|
|
700
|
+
|
|
701
|
+
while (Date.now() - startTime < this.batchingConfig.batchWindowMs) {
|
|
702
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
703
|
+
const newCount = await this._getUnprocessedMessageCount(chatId);
|
|
704
|
+
|
|
705
|
+
if (newCount > lastCount) {
|
|
706
|
+
lastCount = newCount;
|
|
707
|
+
logger.info(`[Batching] New message detected for ${chatId}, extending wait`);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (Date.now() - startTime >= this.batchingConfig.maxBatchWait) {
|
|
711
|
+
logger.info(`[Batching] Max wait reached for ${chatId}`);
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
logger.info(`[CheckAfter] Processing ${lastCount} messages for ${chatId} after batching`);
|
|
684
717
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
numero: chatId,
|
|
688
|
-
processed: false,
|
|
689
|
-
from_me: false
|
|
690
|
-
});
|
|
718
|
+
runId = `run_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
719
|
+
this.activeRequests.set(chatId, runId);
|
|
691
720
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
721
|
+
const result = await replyAssistant(chatId, null, null, { runId });
|
|
722
|
+
|
|
723
|
+
if (this.abandonedRuns.has(runId)) {
|
|
724
|
+
logger.info(`[CheckAfter] Discarding abandoned run ${runId} for ${chatId}`);
|
|
725
|
+
this.abandonedRuns.delete(runId);
|
|
726
|
+
return;
|
|
695
727
|
}
|
|
728
|
+
|
|
729
|
+
const botResponse = typeof result === 'string' ? result : result?.output;
|
|
730
|
+
const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
|
|
696
731
|
|
|
697
|
-
// No new messages - send response and mark processed
|
|
698
732
|
if (botResponse) {
|
|
699
733
|
await this.sendMessage({
|
|
700
734
|
code: chatId,
|
|
@@ -712,9 +746,25 @@ class NexusMessaging {
|
|
|
712
746
|
} finally {
|
|
713
747
|
if (typingInterval) clearInterval(typingInterval);
|
|
714
748
|
this.processingLocks.delete(chatId);
|
|
749
|
+
this.activeRequests.delete(chatId);
|
|
750
|
+
if (this.abandonedRuns.size > 100) {
|
|
751
|
+
this.abandonedRuns.clear();
|
|
752
|
+
}
|
|
715
753
|
}
|
|
716
754
|
}
|
|
717
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Get count of unprocessed messages for a chat
|
|
758
|
+
*/
|
|
759
|
+
async _getUnprocessedMessageCount(chatId) {
|
|
760
|
+
const { Message } = require('../models/messageModel');
|
|
761
|
+
return await Message.countDocuments({
|
|
762
|
+
numero: chatId,
|
|
763
|
+
processed: false,
|
|
764
|
+
from_me: false
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
718
768
|
/**
|
|
719
769
|
* Start typing indicator refresh interval
|
|
720
770
|
*/
|
|
@@ -726,10 +776,16 @@ class NexusMessaging {
|
|
|
726
776
|
const lastMessage = await Message.findOne({
|
|
727
777
|
numero: chatId,
|
|
728
778
|
from_me: false,
|
|
729
|
-
|
|
779
|
+
processed: false,
|
|
780
|
+
message_id: { $exists: true, $ne: null, $not: /^pending-/ }
|
|
730
781
|
}).sort({ createdAt: -1 });
|
|
731
782
|
|
|
732
|
-
if (!lastMessage?.message_id)
|
|
783
|
+
if (!lastMessage?.message_id) {
|
|
784
|
+
logger.debug(`[_startTypingRefresh] No valid message for typing indicator: ${chatId}`);
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
logger.debug(`[_startTypingRefresh] Starting typing indicator for message: ${lastMessage.message_id}`);
|
|
733
789
|
|
|
734
790
|
return setInterval(() =>
|
|
735
791
|
this.provider.sendTypingIndicator(lastMessage.message_id).catch(err =>
|
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
} = require('./OpenAIResponsesProviderTools');
|
|
7
7
|
const { DefaultConversationManager } = require('../services/DefaultConversationManager');
|
|
8
8
|
const { logger } = require('../utils/logger');
|
|
9
|
+
const { getCurrentMexicoDateTime } = require('../utils/dateUtils');
|
|
9
10
|
|
|
10
11
|
const CONVERSATION_PREFIX = 'conv_';
|
|
11
12
|
const RESPONSE_PREFIX = 'resp_';
|
|
@@ -202,7 +203,8 @@ class OpenAIResponsesProvider {
|
|
|
202
203
|
const clinicalData = await this.conversationManager.getClinicalData(thread.code);
|
|
203
204
|
const promptVariables = clinicalData ? {
|
|
204
205
|
clinical_context: clinicalData.clinicalContext || '',
|
|
205
|
-
last_symptoms: clinicalData.lastSymptoms || ''
|
|
206
|
+
last_symptoms: clinicalData.lastSymptoms || '',
|
|
207
|
+
current_date: getCurrentMexicoDateTime(),
|
|
206
208
|
} : null;
|
|
207
209
|
|
|
208
210
|
// Execute with built context
|
package/lib/utils/dateUtils.js
CHANGED
|
@@ -18,9 +18,17 @@ const dateAndTimeFromStart = (startTime) => {
|
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
const getCurrentMexicoDateTime = () => {
|
|
22
|
+
return moment()
|
|
23
|
+
.tz('America/Mexico_City')
|
|
24
|
+
.locale('es')
|
|
25
|
+
.format('dddd, D [de] MMMM [de] YYYY [a las] h:mm A');
|
|
26
|
+
};
|
|
27
|
+
|
|
21
28
|
module.exports = {
|
|
22
29
|
ISO_DATE,
|
|
23
30
|
parseStartTime,
|
|
24
31
|
addDays,
|
|
25
|
-
dateAndTimeFromStart
|
|
32
|
+
dateAndTimeFromStart,
|
|
33
|
+
getCurrentMexicoDateTime
|
|
26
34
|
};
|