@peopl-health/nexus 2.4.12 → 2.5.0

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.
@@ -9,8 +9,59 @@ app.use(express.json());
9
9
  class GeneralAssistant extends BaseAssistant {
10
10
  constructor(options = {}) {
11
11
  super(options);
12
- // You can add custom tools or setup here
13
- // Example: this.registerTool('toolName', schema, handler);
12
+ // Register the reportUnresolvedRequests tool for testing
13
+ this.registerTool({
14
+ name: 'reportUnresolvedRequests',
15
+ definition: {
16
+ description: 'Identifica preguntas o síntomas que no se resolvieron completamente o que fueron escaladas al equipo médico.',
17
+ strict: false,
18
+ parameters: {
19
+ type: 'object',
20
+ required: [
21
+ 'unresolvedQuestion',
22
+ 'escalationDetails'
23
+ ],
24
+ properties: {
25
+ unresolvedQuestion: {
26
+ type: 'string',
27
+ description: 'La pregunta o síntoma que quedó sin resolver.'
28
+ },
29
+ escalationDetails: {
30
+ type: 'string',
31
+ description: 'Detalles sobre por qué se escaló o a quién fue escalada (equipo médico, etc.).'
32
+ },
33
+ resolutionAttempted: {
34
+ type: 'boolean',
35
+ description: 'Indica si se intentó ofrecer una resolución antes de escalar.'
36
+ },
37
+ triggerConditions: {
38
+ type: 'object',
39
+ description: 'Condiciones que activan la función cuando se detecta una pregunta o síntoma no resuelto en su totalidad.',
40
+ properties: {
41
+ responseAnalysis: {
42
+ type: 'string',
43
+ description: 'Análisis de la respuesta para determinar si es suficientemente específica o resuelve la pregunta.',
44
+ default: 'La respuesta contiene palabras como \'depende\', \'consulte a su equipo médico\', \'varía según el caso\', \'haremos las consultas al equipo médico de PEOPL\', \'puedo comunicarme con el equipo médico de PEOPL para ti\', \'Voy a escalar la consulta al equipo médico\' o no proporciona un plan de acción claro.'
45
+ }
46
+ },
47
+ required: [
48
+ 'responseAnalysis'
49
+ ]
50
+ }
51
+ },
52
+ additionalProperties: false
53
+ }
54
+ },
55
+ handler: async (args) => {
56
+ // Handler for testing - logs the unresolved request information
57
+ console.log('🔍 [reportUnresolvedRequests] Tool called with:', JSON.stringify(args, null, 2));
58
+ return {
59
+ success: true,
60
+ message: 'Unresolved request reported successfully',
61
+ data: args
62
+ };
63
+ }
64
+ });
14
65
  }
15
66
  }
16
67
 
@@ -35,7 +86,7 @@ async function startServer() {
35
86
  providerConfig: {
36
87
  accountSid: process.env.TWILIO_ACCOUNT_SID,
37
88
  authToken: process.env.TWILIO_AUTH_TOKEN,
38
- whatsappNumber: process.env.TWILIO_WHATSAPP_NUMBER
89
+ whatsappNumber: process.env.TWILIO_WHATSAPP_NUMBER,
39
90
  },
40
91
 
41
92
  // Media configuration for AWS S3 upload
@@ -56,6 +107,8 @@ async function startServer() {
56
107
  // Register the general assistant with its OpenAI assistant ID
57
108
  'asst_O6mAXAhf0xyVj3t4DHRs26uT': GeneralAssistant,
58
109
  'pmpt_68f844cd975481979c080431bde74f6e0adf01a52110813b': GeneralAssistant,
110
+ 'pmpt_68f8059adc7881938477ddbfb6e0d1970c2ed91d30c64be6': GeneralAssistant,
111
+ 'pmpt_68f649ce39ec81908fade27434c167e6014528b0dc5469c6': GeneralAssistant,
59
112
  }
60
113
  }
61
114
  });
@@ -75,6 +128,25 @@ async function startServer() {
75
128
  }
76
129
  });
77
130
 
131
+ // Add status callback endpoint for Twilio message status updates
132
+ // Twilio sends form-encoded data, so we need urlencoded middleware
133
+ const { handleStatusCallback } = require('../lib/helpers/messageStatusHelper');
134
+ app.use('/twilio/status_callback', express.urlencoded({ extended: true }));
135
+ app.post('/twilio/status_callback', async (req, res) => {
136
+ try {
137
+ const updated = await handleStatusCallback(req.body);
138
+ if (updated) {
139
+ console.log('✅ Status updated:', req.body.MessageSid, req.body.MessageStatus);
140
+ res.status(200).json({ success: true, message: 'Status updated' });
141
+ } else {
142
+ res.status(200).json({ success: false, message: 'Message not found' });
143
+ }
144
+ } catch (error) {
145
+ console.error('Status callback error:', error);
146
+ res.status(500).json({ success: false, error: error.message });
147
+ }
148
+ });
149
+
78
150
  // Custom endpoint example
79
151
  app.get('/status', (req, res) => {
80
152
  res.json({
@@ -107,6 +179,7 @@ async function startServer() {
107
179
  console.log('Available endpoints:');
108
180
  console.log('- POST /webhook - Webhook for incoming messages');
109
181
  console.log('- POST /twilio/webhook - Twilio webhook for incoming messages');
182
+ console.log('- POST /twilio/status_callback - Twilio status callback for message delivery status');
110
183
  console.log('- GET /status - Connection status');
111
184
  console.log('- GET /airtable-test - Test Airtable connection');
112
185
  console.log('- GET /media-test - Test AWS media upload configuration');
@@ -117,6 +190,11 @@ async function startServer() {
117
190
  console.log(' - Multiple messages from same sender will be batched');
118
191
  console.log(' - Use test-batching.js to test the functionality');
119
192
  console.log('');
193
+ console.log('🧪 Assistant Testing:');
194
+ console.log(' - Use test-assistant.js to test assistant functionality');
195
+ console.log(' - Run: node examples/test-assistant.js');
196
+ console.log(' - Tests basic messages, responses, batching, and retry logic');
197
+ console.log('');
120
198
  console.log('📸 Media Upload Setup:');
121
199
  console.log(' Add these to your .env file for media upload:');
122
200
  console.log(' - AWS_ACCESS_KEY_ID=your_access_key');
@@ -18,6 +18,7 @@ class TwilioProvider extends MessageProvider {
18
18
  this.accountSid = config.accountSid;
19
19
  this.authToken = config.authToken;
20
20
  this.whatsappNumber = config.whatsappNumber;
21
+ this.statusCallbackUrl = config.statusCallbackUrl || null;
21
22
  }
22
23
 
23
24
  async initialize() {
@@ -61,6 +62,17 @@ class TwilioProvider extends MessageProvider {
61
62
  to: formattedCode
62
63
  };
63
64
 
65
+ if (this.statusCallbackUrl) {
66
+ messageParams.statusCallback = this.statusCallbackUrl;
67
+ messageParams.statusCallbackMethod = messageData.statusCallbackMethod || 'POST';
68
+ logger.info('[TwilioProvider] Message will use status callback', {
69
+ callbackUrl: this.statusCallbackUrl,
70
+ method: messageParams.statusCallbackMethod
71
+ });
72
+ } else {
73
+ logger.debug('[TwilioProvider] No status callback URL configured');
74
+ }
75
+
64
76
  // Handle template messages
65
77
  if (contentSid) {
66
78
  const renderedMessage = await this.renderTemplate(contentSid, variables);
@@ -118,17 +130,23 @@ class TwilioProvider extends MessageProvider {
118
130
  provider: 'twilio',
119
131
  timestamp: new Date(),
120
132
  fromMe: true,
121
- processed: messageData.processed !== undefined ? messageData.processed : false
133
+ processed: messageData.processed !== undefined ? messageData.processed : false,
134
+ statusInfo: {
135
+ status: result.status ? result.status.toLowerCase() : null,
136
+ updatedAt: result.dateCreated || new Date()
137
+ }
122
138
  });
123
139
  logger.info('[TwilioProvider] Message persisted successfully', { messageId: result.sid });
124
140
  } catch (storageError) {
125
141
  logger.error('TwilioProvider storage failed:', storageError);
126
142
  }
127
143
  }
144
+
128
145
  return {
129
146
  success: true,
130
147
  messageId: result.sid,
131
148
  provider: 'twilio',
149
+ status: result.status,
132
150
  result
133
151
  };
134
152
  } catch (error) {
@@ -215,7 +233,6 @@ class TwilioProvider extends MessageProvider {
215
233
  const payload = scheduledMessage.toObject ? scheduledMessage.toObject() : { ...scheduledMessage };
216
234
  delete payload.__nexusSend;
217
235
 
218
- // Map message field to body for consistency (scheduled messages use 'message' field)
219
236
  if (payload.message && !payload.body) {
220
237
  payload.body = payload.message;
221
238
  }
@@ -681,6 +698,30 @@ class TwilioProvider extends MessageProvider {
681
698
  }
682
699
  }
683
700
 
701
+ /**
702
+ * Check the status of a sent message using its SID
703
+ */
704
+ async getMessageStatus(messageSid) {
705
+ if (!this.isConnected || !this.twilioClient) {
706
+ throw new Error('Twilio provider not initialized');
707
+ }
708
+ if (!messageSid) {
709
+ throw new Error('Message SID is required');
710
+ }
711
+
712
+ try {
713
+ const message = await this.twilioClient.messages(messageSid).fetch();
714
+ // Returns the complete Twilio message object with all status information
715
+ // Status-related fields: status, errorCode, errorMessage, dateCreated, dateSent, dateUpdated
716
+ return message;
717
+ } catch (error) {
718
+ if (error.status === 404) {
719
+ throw new Error(`Message with SID ${messageSid} not found`);
720
+ }
721
+ throw new Error(`Failed to fetch message status: ${error.message}`);
722
+ }
723
+ }
724
+
684
725
  }
685
726
 
686
727
  module.exports = { TwilioProvider };
@@ -117,10 +117,8 @@ const getConversationMessagesController = async (req, res) => {
117
117
  }
118
118
  }
119
119
 
120
- logger.info('Fetching conversation messages with query:', JSON.stringify(query));
121
- logger.info('Using limit:', limit);
122
-
123
- logger.info('About to execute Message.find with query:', JSON.stringify(query));
120
+ logger.info('Fetching conversation messages', { query, limit });
121
+ logger.info('Executing Message.find', { query });
124
122
  let messages = [];
125
123
 
126
124
  try {
@@ -242,7 +240,7 @@ const getConversationReplyController = async (req, res) => {
242
240
  messageData.contentSid = contentSid;
243
241
 
244
242
  if (variables && Object.keys(variables).length > 0) {
245
- logger.info('Template variables:', JSON.stringify(variables));
243
+ logger.info('Template variables', { variables });
246
244
  messageData.variables = variables;
247
245
  }
248
246
 
@@ -275,7 +273,7 @@ const getConversationReplyController = async (req, res) => {
275
273
  messageData.body = message;
276
274
  }
277
275
 
278
- logger.info('Sending message with data:', JSON.stringify(messageData));
276
+ logger.info('Sending message', { messageData });
279
277
  await sendMessage(messageData);
280
278
  logger.info('Message sent successfully');
281
279
 
@@ -285,7 +283,7 @@ const getConversationReplyController = async (req, res) => {
285
283
  });
286
284
  } catch (error) {
287
285
  logger.error('Error sending reply:', error);
288
- logger.info('Request body:', JSON.stringify(req.body || {}));
286
+ logger.info('Request body', { body: req.body || {} });
289
287
  const errorMsg = error.message || 'Failed to send reply';
290
288
  logger.error('Responding with error:', errorMsg);
291
289
  res.status(500).json({
@@ -615,7 +613,7 @@ const sendTemplateToNewNumberController = async (req, res) => {
615
613
 
616
614
  if (variables && Object.keys(variables).length > 0) {
617
615
  messageData.variables = variables;
618
- logger.info('Template variables:', JSON.stringify(variables));
616
+ logger.info('Template variables', { variables });
619
617
  }
620
618
 
621
619
  const message = await sendMessage(messageData);
@@ -0,0 +1,92 @@
1
+ const { handleStatusCallback, getMessageStatus } = require('../helpers/messageStatusHelper');
2
+ const { logger } = require('../utils/logger');
3
+
4
+ /**
5
+ * Handle Twilio status callback webhook
6
+ * POST /api/message/status-callback
7
+ */
8
+ async function messageStatusCallbackController(req, res) {
9
+ try {
10
+ logger.info('[MessageStatusController] Received status callback', {
11
+ method: req.method,
12
+ path: req.path,
13
+ headers: req.headers['content-type'],
14
+ bodyKeys: Object.keys(req.body || {}),
15
+ body: req.body
16
+ });
17
+
18
+ const updated = await handleStatusCallback(req.body);
19
+
20
+ if (updated) {
21
+ logger.info('[MessageStatusController] Status updated successfully', {
22
+ messageSid: req.body.MessageSid,
23
+ status: req.body.MessageStatus
24
+ });
25
+ res.status(200).json({
26
+ success: true,
27
+ message: 'Status updated',
28
+ messageSid: req.body.MessageSid,
29
+ status: req.body.MessageStatus
30
+ });
31
+ } else {
32
+ res.status(200).json({
33
+ success: false,
34
+ message: 'Message not found or invalid data',
35
+ messageSid: req.body.MessageSid
36
+ });
37
+ }
38
+ } catch (error) {
39
+ logger.error('[MessageStatusController] Error handling status callback', {
40
+ error: error.message,
41
+ body: req.body
42
+ });
43
+ res.status(500).json({
44
+ success: false,
45
+ error: 'Failed to process status callback'
46
+ });
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Get message status from database
52
+ * GET /api/message/status/:messageSid
53
+ */
54
+ async function getMessageStatusController(req, res) {
55
+ try {
56
+ const { messageSid } = req.params;
57
+
58
+ const message = await getMessageStatus(messageSid);
59
+
60
+ if (message) {
61
+ res.status(200).json({
62
+ success: true,
63
+ message: {
64
+ messageSid: message.message_id,
65
+ statusInfo: message.statusInfo || null,
66
+ numero: message.numero,
67
+ body: message.body
68
+ }
69
+ });
70
+ } else {
71
+ res.status(404).json({
72
+ success: false,
73
+ error: 'Message not found'
74
+ });
75
+ }
76
+ } catch (error) {
77
+ logger.error('[MessageStatusController] Error fetching message status', {
78
+ error: error.message,
79
+ messageSid: req.params.messageSid
80
+ });
81
+ res.status(500).json({
82
+ success: false,
83
+ error: 'Failed to fetch message status'
84
+ });
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ messageStatusCallbackController,
90
+ getMessageStatusController
91
+ };
92
+
@@ -71,7 +71,7 @@ async function analyzeImage(imagePath, isSticker = false, contentType = null) {
71
71
 
72
72
  // Description of the image (for both stickers and regular images)
73
73
  const imageDescription = 'Describe the image in detail.';
74
- const messageDescription = await anthropicClient.messages.create({
74
+ const descriptionPromise = anthropicClient.messages.create({
75
75
  model: 'claude-sonnet-4-5',
76
76
  max_tokens: 1024,
77
77
  messages: [
@@ -94,11 +94,13 @@ async function analyzeImage(imagePath, isSticker = false, contentType = null) {
94
94
  },
95
95
  ],
96
96
  });
97
- logger.info('[analyzeImage] Description received');
98
- const description = messageDescription.content[0].text;
99
97
 
100
98
  // For stickers, skip medical analysis and table extraction
101
99
  if (isSticker) {
100
+ const messageDescription = await descriptionPromise;
101
+ const description = messageDescription.content[0].text;
102
+ logger.info('[analyzeImage] Description received (sticker)');
103
+
102
104
  return {
103
105
  description: description,
104
106
  medical_analysis: 'NOT_MEDICAL',
@@ -108,7 +110,9 @@ async function analyzeImage(imagePath, isSticker = false, contentType = null) {
108
110
  };
109
111
  }
110
112
 
111
- // Create a more specific prompt for table detection and extraction
113
+ // Run all analysis calls in parallel
114
+ logger.info('[analyzeImage] Starting parallel analysis calls');
115
+
112
116
  const tablePrompt = `Please analyze this image and respond in the following format:
113
117
  1. First, determine if there is a table in the image.
114
118
  2. If there is NO table, respond with exactly "NONE"
@@ -126,32 +130,6 @@ async function analyzeImage(imagePath, isSticker = false, contentType = null) {
126
130
 
127
131
  Only extract tables - ignore any other content in the image.`;
128
132
 
129
- // Create the message with the image
130
- const messageTable = await anthropicClient.messages.create({
131
- model: 'claude-3-7-sonnet-20250219',
132
- max_tokens: 1024,
133
- messages: [
134
- {
135
- role: 'user',
136
- content: [
137
- {
138
- type: 'image',
139
- source: {
140
- type: 'base64',
141
- media_type: mimeType,
142
- data: base64Image,
143
- },
144
- },
145
- {
146
- type: 'text',
147
- text: tablePrompt,
148
- },
149
- ],
150
- },
151
- ],
152
- });
153
-
154
- // Create a more specific prompt for table detection and extraction
155
133
  const medImagePrompt = `
156
134
  Eres un oncólogo clínico con experiencia. Se te proporcionará una imagen médica o laboratorio. Analízala y responde exactamente en este formato:
157
135
 
@@ -193,59 +171,48 @@ Ejemplo 1:
193
171
  </EJEMPLOS>
194
172
  `;
195
173
 
196
- // Create the message with the image
197
- const messageMedImage = await anthropicClient.messages.create({
198
- model: 'claude-3-7-sonnet-20250219',
199
- max_tokens: 1024,
200
- messages: [
201
- {
202
- role: 'user',
203
- content: [
204
- {
205
- type: 'image',
206
- source: {
207
- type: 'base64',
208
- media_type: mimeType,
209
- data: base64Image,
210
- },
211
- },
212
- {
213
- type: 'text',
214
- text: medImagePrompt,
215
- },
216
- ],
217
- },
218
- ],
219
- });
220
-
221
174
  const relevancePrompt = `Please analyze this image and respond in this format:
222
175
  Medical Relevance: [YES/NO]`;
223
176
 
224
- // Create the message with the image
225
- const messageRelevance = await anthropicClient.messages.create({
226
- model: 'claude-3-7-sonnet-20250219',
227
- max_tokens: 1024,
228
- messages: [
229
- {
177
+ // Execute all 4 API calls in parallel
178
+ const [messageDescription, messageTable, messageMedImage, messageRelevance] = await Promise.all([
179
+ descriptionPromise,
180
+ anthropicClient.messages.create({
181
+ model: 'claude-3-7-sonnet-20250219',
182
+ max_tokens: 1024,
183
+ messages: [{
230
184
  role: 'user',
231
185
  content: [
232
- {
233
- type: 'image',
234
- source: {
235
- type: 'base64',
236
- media_type: mimeType,
237
- data: base64Image,
238
- },
239
- },
240
- {
241
- type: 'text',
242
- text: relevancePrompt,
243
- },
244
- ],
245
- },
246
- ],
247
- });
186
+ { type: 'image', source: { type: 'base64', media_type: mimeType, data: base64Image } },
187
+ { type: 'text', text: tablePrompt }
188
+ ]
189
+ }]
190
+ }),
191
+ anthropicClient.messages.create({
192
+ model: 'claude-3-7-sonnet-20250219',
193
+ max_tokens: 1024,
194
+ messages: [{
195
+ role: 'user',
196
+ content: [
197
+ { type: 'image', source: { type: 'base64', media_type: mimeType, data: base64Image } },
198
+ { type: 'text', text: medImagePrompt }
199
+ ]
200
+ }]
201
+ }),
202
+ anthropicClient.messages.create({
203
+ model: 'claude-3-7-sonnet-20250219',
204
+ max_tokens: 1024,
205
+ messages: [{
206
+ role: 'user',
207
+ content: [
208
+ { type: 'image', source: { type: 'base64', media_type: mimeType, data: base64Image } },
209
+ { type: 'text', text: relevancePrompt }
210
+ ]
211
+ }]
212
+ })
213
+ ]);
248
214
 
215
+ const description = messageDescription.content[0].text;
249
216
  const messageTableStr = messageTable.content[0].text;
250
217
  const messageRelevanceStr = messageRelevance.content[0].text;
251
218
  const messageAnalysisStr = messageMedImage.content[0].text;
@@ -0,0 +1,97 @@
1
+ const { Message } = require('../models/messageModel');
2
+ const { logger } = require('../utils/logger');
3
+
4
+ /**
5
+ * Update message delivery status in the database based on Twilio status callback data
6
+ */
7
+ async function updateMessageStatus(messageSid, status, errorCode = null, errorMessage = null) {
8
+ try {
9
+ const statusInfo = {
10
+ status,
11
+ updatedAt: new Date()
12
+ };
13
+
14
+ if (errorCode) {
15
+ statusInfo.errorCode = errorCode;
16
+ }
17
+
18
+ if (errorMessage) {
19
+ statusInfo.errorMessage = errorMessage;
20
+ }
21
+
22
+ const updated = await Message.findOneAndUpdate(
23
+ { message_id: messageSid },
24
+ { $set: { statusInfo } },
25
+ { new: true }
26
+ );
27
+
28
+ if (updated) {
29
+ logger.info('[MessageStatus] Updated message status', {
30
+ messageSid,
31
+ status,
32
+ errorCode,
33
+ errorMessage
34
+ });
35
+ } else {
36
+ logger.warn('[MessageStatus] Message not found for status update', { messageSid });
37
+ }
38
+
39
+ return updated;
40
+ } catch (error) {
41
+ logger.error('[MessageStatus] Error updating message status', {
42
+ messageSid,
43
+ error: error.message
44
+ });
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Handle Twilio status callback webhook
51
+ */
52
+ async function handleStatusCallback(twilioStatusData) {
53
+ const {
54
+ MessageSid,
55
+ MessageStatus,
56
+ ErrorCode,
57
+ ErrorMessage
58
+ } = twilioStatusData;
59
+
60
+ if (!MessageSid || !MessageStatus) {
61
+ logger.warn('[MessageStatus] Invalid status callback data', twilioStatusData);
62
+ return null;
63
+ }
64
+
65
+ return await updateMessageStatus(
66
+ MessageSid,
67
+ MessageStatus.toLowerCase(),
68
+ ErrorCode || null,
69
+ ErrorMessage || null
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Get message status from database
75
+ */
76
+ async function getMessageStatus(messageSid) {
77
+ try {
78
+ const message = await Message.findOne({ message_id: messageSid })
79
+ .select('statusInfo message_id numero body')
80
+ .lean();
81
+
82
+ return message;
83
+ } catch (error) {
84
+ logger.error('[MessageStatus] Error fetching message status from DB', {
85
+ messageSid,
86
+ error: error.message
87
+ });
88
+ return null;
89
+ }
90
+ }
91
+
92
+ module.exports = {
93
+ updateMessageStatus,
94
+ handleStatusCallback,
95
+ getMessageStatus
96
+ };
97
+
@@ -63,6 +63,25 @@ const messageSchema = new mongoose.Schema({
63
63
  read: {
64
64
  type: Boolean,
65
65
  default: false
66
+ },
67
+ statusInfo: {
68
+ status: {
69
+ type: String,
70
+ enum: ['queued', 'sending', 'sent', 'delivered', 'undelivered', 'failed', 'read', null],
71
+ default: null
72
+ },
73
+ errorCode: {
74
+ type: String,
75
+ default: null
76
+ },
77
+ errorMessage: {
78
+ type: String,
79
+ default: null
80
+ },
81
+ updatedAt: {
82
+ type: Date,
83
+ default: null
84
+ }
66
85
  }
67
86
  }, { timestamps: true });
68
87
 
@@ -118,7 +137,13 @@ async function insertMessage(values) {
118
137
  clinical_context: clinical_context,
119
138
  origin: values.origin,
120
139
  tools_executed: values.tools_executed || [],
121
- raw: values.raw || null
140
+ raw: values.raw || null,
141
+ statusInfo: values.statusInfo || (values.delivery_status ? {
142
+ status: values.delivery_status,
143
+ errorCode: values.delivery_error_code || null,
144
+ errorMessage: values.delivery_error_message || null,
145
+ updatedAt: values.delivery_status_updated_at || null
146
+ } : null)
122
147
  };
123
148
 
124
149
  await Message.findOneAndUpdate(
@@ -1,5 +1,6 @@
1
1
  // Export route definitions for customer servers to import
2
2
  // These are the route patterns without controller dependencies
3
+ const express = require('express');
3
4
 
4
5
  const assistantRouteDefinitions = {
5
6
  'POST /active': 'activeAssistantController',
@@ -65,7 +66,6 @@ const templateRouteDefinitions = {
65
66
 
66
67
  // Helper function to create Express router from route definitions
67
68
  const createRouter = (routeDefinitions, controllers) => {
68
- const express = require('express');
69
69
  const router = express.Router();
70
70
 
71
71
  for (const [route, controllerName] of Object.entries(routeDefinitions)) {
@@ -135,8 +135,8 @@ const fetchConversationData = async (filter, skip, limit) => {
135
135
  }
136
136
  return map;
137
137
  }, {}) || {};
138
- logger.info('unreadMap', JSON.stringify(unreadMap));
139
- logger.info('Number of conversations found:', conversations?.length || 0);
138
+ logger.info('Unread map calculated', { unreadMap });
139
+ logger.info('Conversations found', { count: conversations?.length || 0 });
140
140
 
141
141
  // Calculate total count for pagination
142
142
  let totalFilterConditions = { is_group: false };
@@ -49,6 +49,11 @@ const TwilioService = {
49
49
  return await nexusProvider.createTemplate(templateData);
50
50
  },
51
51
 
52
+ async getMessageStatus(messageSid) {
53
+ checkTwilioSupport();
54
+ return await nexusProvider.getMessageStatus(messageSid);
55
+ },
56
+
52
57
  // Add any other Twilio operations as needed
53
58
  configureNexusProvider
54
59
  };
@@ -187,7 +187,13 @@ class MongoStorage {
187
187
  template_variables: messageData.variables ? JSON.stringify(messageData.variables) : null,
188
188
  raw: messageData.raw || null,
189
189
  origin,
190
- tools_executed: messageData.tools_executed || []
190
+ tools_executed: messageData.tools_executed || [],
191
+ statusInfo: messageData.statusInfo || (messageData.delivery_status ? {
192
+ status: messageData.delivery_status,
193
+ errorCode: messageData.delivery_error_code || null,
194
+ errorMessage: messageData.delivery_error_message || null,
195
+ updatedAt: messageData.delivery_status_updated_at || null
196
+ } : null)
191
197
  };
192
198
  }
193
199
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "2.4.12",
3
+ "version": "2.5.0",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",