@peopl-health/nexus 2.1.6 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/adapters/TwilioProvider.js +2 -1
- package/lib/assistants/BaseAssistant.js +35 -12
- package/lib/controllers/bugReportController.js +1 -1
- package/lib/controllers/conversationController.js +2 -2
- package/lib/core/NexusMessaging.js +14 -2
- package/lib/helpers/assistantHelper.js +11 -6
- package/lib/helpers/baileysHelper.js +3 -3
- package/lib/helpers/twilioHelper.js +3 -3
- package/lib/models/messageModel.js +5 -1
- package/lib/services/assistantService.js +10 -10
- package/lib/storage/MongoStorage.js +1 -0
- package/package.json +1 -1
|
@@ -121,7 +121,8 @@ class TwilioProvider extends MessageProvider {
|
|
|
121
121
|
messageId: result.sid,
|
|
122
122
|
provider: 'twilio',
|
|
123
123
|
timestamp: new Date(),
|
|
124
|
-
fromMe: true
|
|
124
|
+
fromMe: true,
|
|
125
|
+
processed: true
|
|
125
126
|
});
|
|
126
127
|
console.log('[TwilioProvider] Message persisted successfully', { messageId: result.sid });
|
|
127
128
|
} catch (storageError) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const llmConfig = require('../config/llmConfig');
|
|
2
2
|
const { Thread } = require('../models/threadModel');
|
|
3
|
-
const {
|
|
3
|
+
const { Message } = require('../models/messageModel');
|
|
4
|
+
const { formatMessage } = require('../helpers/assistantHelper');
|
|
4
5
|
const { createProvider } = require('../providers/createProvider');
|
|
5
6
|
|
|
6
7
|
const DEFAULT_MAX_HISTORICAL_MESSAGES = parseInt(process.env.MAX_HISTORICAL_MESSAGES || '50', 10);
|
|
@@ -165,11 +166,6 @@ class BaseAssistant {
|
|
|
165
166
|
this._ensureClient();
|
|
166
167
|
this.status = 'active';
|
|
167
168
|
|
|
168
|
-
const whatsappId = context?.whatsapp_id || code;
|
|
169
|
-
if (whatsappId) {
|
|
170
|
-
this.lastMessages = await getLastNMessages(whatsappId, DEFAULT_MAX_HISTORICAL_MESSAGES);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
169
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
174
170
|
if (!provider || typeof provider.createConversation !== 'function') {
|
|
175
171
|
throw new Error('Provider not configured. Cannot create conversation.');
|
|
@@ -189,12 +185,39 @@ class BaseAssistant {
|
|
|
189
185
|
return this.thread;
|
|
190
186
|
}
|
|
191
187
|
|
|
192
|
-
async buildInitialMessages({ code }) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
async buildInitialMessages({ code, context = {} }) {
|
|
189
|
+
const whatsappId = context?.whatsapp_id || code;
|
|
190
|
+
if (!whatsappId) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const lastMessages = await Message.find({ numero: whatsappId })
|
|
196
|
+
.sort({ createdAt: -1 })
|
|
197
|
+
.limit(DEFAULT_MAX_HISTORICAL_MESSAGES);
|
|
198
|
+
|
|
199
|
+
if (!lastMessages || lastMessages.length === 0) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const messagesInOrder = lastMessages.reverse();
|
|
204
|
+
|
|
205
|
+
// Messages with from_me: true are assistant messages, from_me: false are user messages
|
|
206
|
+
const formattedMessages = messagesInOrder
|
|
207
|
+
.filter(message => message && message.timestamp && message.body && message.body.trim() !== '')
|
|
208
|
+
.map(message => {
|
|
209
|
+
const formattedText = formatMessage(message);
|
|
210
|
+
return formattedText ? { role: message.from_me ? 'assistant' : 'user', content: formattedText } : null;
|
|
211
|
+
})
|
|
212
|
+
.filter(message => message !== null); // Remove any null entries
|
|
213
|
+
|
|
214
|
+
console.log(`[buildInitialMessages] Built ${formattedMessages.length} initial messages for ${code}`);
|
|
215
|
+
|
|
216
|
+
return formattedMessages;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error('[buildInitialMessages] Error fetching messages:', error);
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
198
221
|
}
|
|
199
222
|
|
|
200
223
|
async close() {
|
|
@@ -6,7 +6,7 @@ async function logBugReportToAirtable(reporter, whatsapp_id, description, severi
|
|
|
6
6
|
try {
|
|
7
7
|
let conversation = null;
|
|
8
8
|
if (messageIds && messageIds.length > 0) {
|
|
9
|
-
const messageObjects = await Message.find({ _id: { $in: messageIds } }).sort({
|
|
9
|
+
const messageObjects = await Message.find({ _id: { $in: messageIds } }).sort({ createdAt: 1 });
|
|
10
10
|
conversation = messageObjects.map(msg => {
|
|
11
11
|
const timestamp = new Date(msg.timestamp).toISOString().slice(0, 16).replace('T', ' ');
|
|
12
12
|
const role = msg.from_me ? 'Assistant' : 'Patient';
|
|
@@ -409,14 +409,14 @@ const getConversationsByNameController = async (req, res) => {
|
|
|
409
409
|
try {
|
|
410
410
|
const conversations = await Message.aggregate([
|
|
411
411
|
{ $match: { from_me: false, is_group: false } },
|
|
412
|
-
{ $sort: {
|
|
412
|
+
{ $sort: { createdAt: -1 } },
|
|
413
413
|
{ $group: {
|
|
414
414
|
_id: '$numero',
|
|
415
415
|
name: { $first: '$nombre_whatsapp' },
|
|
416
416
|
latestMessage: { $first: '$$ROOT' },
|
|
417
417
|
messageCount: { $sum: 1 }
|
|
418
418
|
}},
|
|
419
|
-
{ $sort: { 'latestMessage.
|
|
419
|
+
{ $sort: { 'latestMessage.createdAt': -1 } }
|
|
420
420
|
]);
|
|
421
421
|
|
|
422
422
|
res.status(200).json({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { airtable, getBase } = require('../config/airtableConfig');
|
|
2
|
-
const { replyAssistant } = require('../services/assistantService');
|
|
2
|
+
const { addMsgAssistant, replyAssistant } = require('../services/assistantService');
|
|
3
3
|
const { createProvider } = require('../adapters/registry');
|
|
4
4
|
const runtimeConfig = require('../config/runtimeConfig');
|
|
5
5
|
const { hasPreprocessingHandler, invokePreprocessingHandler } = require('../services/preprocessingHooks');
|
|
@@ -291,10 +291,22 @@ class NexusMessaging {
|
|
|
291
291
|
messageId: result.messageId,
|
|
292
292
|
provider: result.provider,
|
|
293
293
|
timestamp: new Date(),
|
|
294
|
-
fromMe: true
|
|
294
|
+
fromMe: true,
|
|
295
|
+
processed: true
|
|
295
296
|
});
|
|
296
297
|
}
|
|
297
298
|
|
|
299
|
+
// Add to thread context for manual sends
|
|
300
|
+
if (messageData.origin !== 'assistant' && messageData.code &&
|
|
301
|
+
(messageData.body || messageData.message)) {
|
|
302
|
+
await addMsgAssistant(
|
|
303
|
+
messageData.code,
|
|
304
|
+
[messageData.body || messageData.message],
|
|
305
|
+
'assistant',
|
|
306
|
+
false
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
298
310
|
return result;
|
|
299
311
|
}
|
|
300
312
|
|
|
@@ -48,7 +48,7 @@ async function getLastMessages(code) {
|
|
|
48
48
|
query.is_group = false;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const lastMessages = await Message.find(query).sort({
|
|
51
|
+
const lastMessages = await Message.find(query).sort({ createdAt: -1 });
|
|
52
52
|
console.log('[getLastMessages] lastMessages', lastMessages.map(msg => msg.body).join('\n\n'));
|
|
53
53
|
|
|
54
54
|
if (lastMessages.length === 0) return [];
|
|
@@ -73,7 +73,7 @@ async function getLastMessages(code) {
|
|
|
73
73
|
async function getLastNMessages(code, n) {
|
|
74
74
|
try {
|
|
75
75
|
const lastMessages = await Message.find({ numero: code })
|
|
76
|
-
.sort({
|
|
76
|
+
.sort({ createdAt: -1 })
|
|
77
77
|
.limit(n);
|
|
78
78
|
|
|
79
79
|
// Format each message and concatenate them with skip lines
|
|
@@ -172,7 +172,7 @@ async function processIndividualMessage(code, reply, provider, thread) {
|
|
|
172
172
|
try {
|
|
173
173
|
const formattedMessage = formatMessage(reply);
|
|
174
174
|
console.log('[processIndividualMessage] formattedMessage:', formattedMessage);
|
|
175
|
-
const
|
|
175
|
+
const isPatient = reply.origin === 'patient';
|
|
176
176
|
let messagesChat = [];
|
|
177
177
|
let url = null;
|
|
178
178
|
|
|
@@ -247,13 +247,18 @@ async function processIndividualMessage(code, reply, provider, thread) {
|
|
|
247
247
|
console.log('[processIndividualMessage] messagesChat', messagesChat);
|
|
248
248
|
|
|
249
249
|
const threadId = thread.getConversationId();
|
|
250
|
-
if (
|
|
250
|
+
if (reply.origin === 'whatsapp_platform') {
|
|
251
|
+
await provider.addMessage({
|
|
252
|
+
threadId,
|
|
253
|
+
role: 'assistant',
|
|
254
|
+
content: messagesChat
|
|
255
|
+
});
|
|
256
|
+
} else if (reply.origin === 'patient') {
|
|
251
257
|
await provider.addMessage({
|
|
252
258
|
threadId,
|
|
253
259
|
role: 'user',
|
|
254
260
|
content: messagesChat
|
|
255
261
|
});
|
|
256
|
-
console.log(`[processIndividualMessage] User message added to thread ${threadId}`);
|
|
257
262
|
}
|
|
258
263
|
|
|
259
264
|
await Message.updateOne(
|
|
@@ -261,7 +266,7 @@ async function processIndividualMessage(code, reply, provider, thread) {
|
|
|
261
266
|
{ $set: { assistant_id: thread.getAssistantId(), thread_id: threadId } }
|
|
262
267
|
);
|
|
263
268
|
|
|
264
|
-
return {
|
|
269
|
+
return {isPatient, url};
|
|
265
270
|
} catch (err) {
|
|
266
271
|
console.log(`Error inside process message ${err}`);
|
|
267
272
|
} finally {
|
|
@@ -110,15 +110,15 @@ async function isRecentMessage(chatId) {
|
|
|
110
110
|
|
|
111
111
|
const recentMessage = await Message.find({
|
|
112
112
|
$or: [{ group_id: chatId }, { numero: chatId }],
|
|
113
|
-
|
|
114
|
-
}).sort({
|
|
113
|
+
createdAt: { $gte: fiveMinutesAgo }
|
|
114
|
+
}).sort({ createdAt: -1 }).limit(1);
|
|
115
115
|
|
|
116
116
|
return !!recentMessage;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
async function getLastMessages(chatId, n) {
|
|
120
120
|
const messages = await Message.find({ group_id: chatId })
|
|
121
|
-
.sort({
|
|
121
|
+
.sort({ createdAt: -1 })
|
|
122
122
|
.limit(n)
|
|
123
123
|
.select('timestamp numero nombre_whatsapp body');
|
|
124
124
|
|
|
@@ -70,8 +70,8 @@ async function isRecentMessage(chatId) {
|
|
|
70
70
|
|
|
71
71
|
const recentMessage = await Message.find({
|
|
72
72
|
$or: [{ group_id: chatId }, { numero: chatId }],
|
|
73
|
-
|
|
74
|
-
}).sort({
|
|
73
|
+
createdAt: { $gte: fiveMinutesAgo }
|
|
74
|
+
}).sort({ createdAt: -1 }).limit(1);
|
|
75
75
|
|
|
76
76
|
return !!recentMessage;
|
|
77
77
|
}
|
|
@@ -79,7 +79,7 @@ async function isRecentMessage(chatId) {
|
|
|
79
79
|
|
|
80
80
|
async function getLastMessages(chatId, n) {
|
|
81
81
|
const messages = await Message.find({ numero: chatId })
|
|
82
|
-
.sort({
|
|
82
|
+
.sort({ createdAt: -1 })
|
|
83
83
|
.limit(n)
|
|
84
84
|
.select('timestamp numero nombre_whatsapp body');
|
|
85
85
|
|
|
@@ -57,6 +57,7 @@ const messageSchema = new mongoose.Schema({
|
|
|
57
57
|
}, { timestamps: true });
|
|
58
58
|
|
|
59
59
|
messageSchema.index({ message_id: 1, timestamp: 1 }, { unique: true });
|
|
60
|
+
messageSchema.index({ numero: 1, createdAt: -1 });
|
|
60
61
|
|
|
61
62
|
messageSchema.pre('save', function (next) {
|
|
62
63
|
if (this.timestamp) {
|
|
@@ -157,7 +158,10 @@ function getMessageValues(message, content, reply, is_media) {
|
|
|
157
158
|
|
|
158
159
|
async function getContactDisplayName(contactNumber) {
|
|
159
160
|
try {
|
|
160
|
-
const latestMessage = await Message.findOne({
|
|
161
|
+
const latestMessage = await Message.findOne({
|
|
162
|
+
numero: contactNumber,
|
|
163
|
+
from_me: false
|
|
164
|
+
})
|
|
161
165
|
.sort({ createdAt: -1 })
|
|
162
166
|
.select('nombre_whatsapp');
|
|
163
167
|
|
|
@@ -230,7 +230,7 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
|
|
|
230
230
|
return thread;
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
-
const addMsgAssistant = async (code, inMessages, reply = false) => {
|
|
233
|
+
const addMsgAssistant = async (code, inMessages, role = 'user', reply = false) => {
|
|
234
234
|
try {
|
|
235
235
|
const thread = await Thread.findOne({ code: code });
|
|
236
236
|
console.log(thread);
|
|
@@ -241,7 +241,7 @@ const addMsgAssistant = async (code, inMessages, reply = false) => {
|
|
|
241
241
|
console.log(message);
|
|
242
242
|
await provider.addMessage({
|
|
243
243
|
threadId: thread.getConversationId(),
|
|
244
|
-
role:
|
|
244
|
+
role: role,
|
|
245
245
|
content: message
|
|
246
246
|
});
|
|
247
247
|
}
|
|
@@ -269,7 +269,7 @@ const addMsgAssistant = async (code, inMessages, reply = false) => {
|
|
|
269
269
|
}
|
|
270
270
|
};
|
|
271
271
|
|
|
272
|
-
const addInsAssistant = async (code, instruction) => {
|
|
272
|
+
const addInsAssistant = async (code, instruction, role = 'user') => {
|
|
273
273
|
try {
|
|
274
274
|
const thread = await Thread.findOne({ code: code });
|
|
275
275
|
console.log(thread);
|
|
@@ -286,7 +286,7 @@ const addInsAssistant = async (code, instruction) => {
|
|
|
286
286
|
runConfig: {
|
|
287
287
|
additionalInstructions: instruction,
|
|
288
288
|
additionalMessages: [
|
|
289
|
-
{ role:
|
|
289
|
+
{ role: role, content: instruction }
|
|
290
290
|
]
|
|
291
291
|
}
|
|
292
292
|
}));
|
|
@@ -328,7 +328,7 @@ const getThread = async (code, message = null) => {
|
|
|
328
328
|
if (run.status === 'cancelled' || run.status === 'expired' || run.status === 'completed') {
|
|
329
329
|
await Thread.updateOne({ code: code }, { $set: { run_id: null } });
|
|
330
330
|
}
|
|
331
|
-
thread = await Thread.findOne({ code: code
|
|
331
|
+
thread = await Thread.findOne({ code: code });
|
|
332
332
|
await delay(5000);
|
|
333
333
|
}
|
|
334
334
|
|
|
@@ -353,7 +353,7 @@ const replyAssistant = async function (code, message_ = null, thread_ = null, ru
|
|
|
353
353
|
try {
|
|
354
354
|
let thread = thread_ || await getThread(code);
|
|
355
355
|
console.log('THREAD STOPPED', code, thread?.active);
|
|
356
|
-
if (!thread
|
|
356
|
+
if (!thread) return null;
|
|
357
357
|
|
|
358
358
|
const patientReply = await getLastMessages(code);
|
|
359
359
|
if (!patientReply) {
|
|
@@ -375,9 +375,9 @@ const replyAssistant = async function (code, message_ = null, thread_ = null, ru
|
|
|
375
375
|
let patientMsg = false;
|
|
376
376
|
let urls = [];
|
|
377
377
|
for (const reply of patientReply) {
|
|
378
|
-
const {
|
|
379
|
-
console.log(`
|
|
380
|
-
patientMsg = patientMsg ||
|
|
378
|
+
const { isPatient, url } = await processIndividualMessage(code, reply, provider, thread);
|
|
379
|
+
console.log(`isPatient ${isPatient} ${url}`);
|
|
380
|
+
patientMsg = patientMsg || isPatient;
|
|
381
381
|
if (url) urls.push({ 'url': url });
|
|
382
382
|
}
|
|
383
383
|
|
|
@@ -395,7 +395,7 @@ const replyAssistant = async function (code, message_ = null, thread_ = null, ru
|
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
if (!patientMsg) return null;
|
|
398
|
+
if (!patientMsg || thread.stopped) return null;
|
|
399
399
|
|
|
400
400
|
const assistant = getAssistantById(thread.getAssistantId(), thread);
|
|
401
401
|
assistant.setReplies(patientReply);
|
|
@@ -45,6 +45,7 @@ class MongoStorage {
|
|
|
45
45
|
const enrichedMessage = await this._enrichTwilioMedia(messageData);
|
|
46
46
|
console.log('[MongoStorage] Enriched message', enrichedMessage);
|
|
47
47
|
const values = this.buildMessageValues(enrichedMessage);
|
|
48
|
+
console.log('[MongoStorage] Message values', values);
|
|
48
49
|
const { insertMessage } = require('../models/messageModel');
|
|
49
50
|
await insertMessage(values);
|
|
50
51
|
console.log('[MongoStorage] Message stored');
|