@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.
@@ -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 { getLastNMessages } = require('../helpers/assistantHelper');
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
- if (!this.lastMessages) return [];
194
- return [{
195
- role: 'assistant',
196
- content: `Últimos mensajes para ${code}: \n${this.lastMessages}`
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({ timestamp: 1 });
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: { timestamp: -1 } },
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.timestamp': -1 } }
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({ timestamp: -1 });
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({ timestamp: -1 })
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 isNotAssistant = !reply.from_me;
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 (isNotAssistant) {
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 {isNotAssistant, url};
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
- timestamp: { $gte: fiveMinutesAgo.toISOString() }
114
- }).sort({ timestamp: -1 }).limit(1);
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({ timestamp: -1 })
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
- timestamp: { $gte: fiveMinutesAgo.toISOString() }
74
- }).sort({ timestamp: -1 }).limit(1);
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({ timestamp: -1 })
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({ numero: contactNumber })
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: 'assistant',
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: 'user', content: instruction }
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, active: true });
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 || !thread.active || thread.stopped) return null;
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 { isNotAssistant, url } = await processIndividualMessage(code, reply, provider, thread);
379
- console.log(`isNotAssistant ${isNotAssistant} ${url}`);
380
- patientMsg = patientMsg || isNotAssistant;
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "2.1.6",
3
+ "version": "2.2.1",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",