@peopl-health/nexus 1.5.10 → 1.6.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +0 -0
  2. package/LICENSE +0 -0
  3. package/MIGRATION_GUIDE.md +0 -0
  4. package/README.md +0 -0
  5. package/examples/.env.example +0 -0
  6. package/examples/assistants/BaseAssistant.js +0 -0
  7. package/examples/assistants/DoctorScheduleAssistant.js +0 -0
  8. package/examples/assistants/ExampleAssistant.js +0 -0
  9. package/examples/assistants/index.js +0 -0
  10. package/examples/basic-usage.js +7 -8
  11. package/examples/consumer-server.js +0 -0
  12. package/lib/adapters/BaileysProvider.js +0 -0
  13. package/lib/adapters/TwilioProvider.js +5 -177
  14. package/lib/adapters/index.js +0 -0
  15. package/lib/adapters/registry.js +0 -0
  16. package/lib/assistants/BaseAssistant.js +2 -3
  17. package/lib/assistants/index.js +0 -0
  18. package/lib/config/airtableConfig.js +0 -0
  19. package/lib/config/awsConfig.js +0 -0
  20. package/lib/config/configLoader.js +0 -0
  21. package/lib/config/llmConfig.js +0 -0
  22. package/lib/config/mongoAuthConfig.js +0 -0
  23. package/lib/config/runtimeConfig.js +0 -0
  24. package/lib/controllers/assistantController.js +0 -0
  25. package/lib/controllers/conversationController.js +0 -0
  26. package/lib/controllers/mediaController.js +0 -0
  27. package/lib/controllers/messageController.js +0 -0
  28. package/lib/controllers/templateController.js +0 -0
  29. package/lib/controllers/templateFlowController.js +0 -0
  30. package/lib/controllers/uploadController.js +0 -0
  31. package/lib/core/MessageProvider.js +0 -0
  32. package/lib/core/NexusMessaging.js +0 -0
  33. package/lib/core/index.js +0 -0
  34. package/lib/helpers/assistantHelper.js +0 -0
  35. package/lib/helpers/baileysHelper.js +0 -0
  36. package/lib/helpers/filesHelper.js +0 -0
  37. package/lib/helpers/llmsHelper.js +0 -0
  38. package/lib/helpers/mediaHelper.js +0 -0
  39. package/lib/helpers/mongoHelper.js +0 -0
  40. package/lib/helpers/qrHelper.js +0 -0
  41. package/lib/helpers/twilioHelper.js +0 -0
  42. package/lib/helpers/twilioMediaProcessor.js +0 -0
  43. package/lib/helpers/whatsappHelper.js +0 -0
  44. package/lib/index.d.ts +0 -0
  45. package/lib/index.js +0 -0
  46. package/lib/interactive/index.js +0 -0
  47. package/lib/interactive/registry.js +0 -0
  48. package/lib/interactive/twilioMapper.js +0 -0
  49. package/lib/models/agendaMessageModel.js +0 -0
  50. package/lib/models/index.js +0 -0
  51. package/lib/models/messageModel.js +1 -2
  52. package/lib/models/templateModel.js +0 -0
  53. package/lib/models/threadModel.js +0 -0
  54. package/lib/routes/index.js +0 -0
  55. package/lib/services/airtableService.js +0 -0
  56. package/lib/services/assistantService.js +0 -0
  57. package/lib/services/conversationService.js +0 -0
  58. package/lib/services/twilioService.js +0 -0
  59. package/lib/storage/MongoStorage.js +4 -15
  60. package/lib/storage/NoopStorage.js +0 -0
  61. package/lib/storage/index.js +0 -0
  62. package/lib/storage/registry.js +0 -0
  63. package/lib/templates/predefinedTemplates.js +0 -0
  64. package/lib/templates/templateStructure.js +0 -0
  65. package/lib/utils/dateUtils.js +0 -0
  66. package/lib/utils/defaultLLMProvider.js +0 -0
  67. package/lib/utils/errorHandler.js +0 -0
  68. package/lib/utils/index.js +0 -0
  69. package/lib/utils/logger.js +0 -0
  70. package/lib/utils/mediaValidator.js +0 -0
  71. package/lib/utils/messageParser.js +0 -0
  72. package/package.json +3 -3
package/CHANGELOG.md CHANGED
File without changes
package/LICENSE CHANGED
File without changes
File without changes
package/README.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,13 +1,12 @@
1
1
  const express = require('express');
2
- require('dotenv').config();
3
- const { Nexus, setupDefaultRoutes } = require('@peopl-health/nexus');
2
+ const { NexusMessaging, setupDefaultRoutes } = require('@peopl-health/nexus');
4
3
 
5
4
  const app = express();
6
5
  app.use(express.json());
7
6
 
8
7
  async function startServer() {
9
8
  // Initialize Nexus with all services
10
- const nexus = new Nexus();
9
+ const nexus = new NexusMessaging();
11
10
 
12
11
  await nexus.initialize({
13
12
  // MongoDB connection
@@ -18,7 +17,7 @@ async function startServer() {
18
17
  providerConfig: {
19
18
  accountSid: process.env.TWILIO_ACCOUNT_SID,
20
19
  authToken: process.env.TWILIO_AUTH_TOKEN,
21
- whatsappNumber: process.env.TWILIO_WHATSAPP_NUMBER
20
+ phoneNumber: process.env.TWILIO_PHONE_NUMBER
22
21
  }
23
22
  });
24
23
 
@@ -28,7 +27,7 @@ async function startServer() {
28
27
  // Add webhook endpoint for incoming messages
29
28
  app.post('/webhook', async (req, res) => {
30
29
  try {
31
- await nexus.messaging.processIncomingMessage(req.body);
30
+ await nexus.processIncomingMessage(req.body);
32
31
  res.status(200).send('OK');
33
32
  } catch (error) {
34
33
  console.error('Webhook error:', error);
@@ -39,10 +38,10 @@ async function startServer() {
39
38
  // Custom endpoint example
40
39
  app.get('/status', (req, res) => {
41
40
  res.json({
42
- connected: nexus.messaging.isConnected(),
41
+ connected: nexus.isConnected(),
43
42
  provider: 'twilio',
44
- mongodb: nexus.messaging.mongodb?.readyState === 1,
45
- airtable: !!nexus.messaging.airtable
43
+ mongodb: nexus.mongodb?.readyState === 1,
44
+ airtable: !!nexus.airtable
46
45
  });
47
46
  });
48
47
 
File without changes
File without changes
@@ -44,11 +44,10 @@ class TwilioProvider extends MessageProvider {
44
44
  throw new Error('Twilio provider not initialized');
45
45
  }
46
46
 
47
- const { code, message, fileUrl, fileType, variables, contentSid } = messageData;
47
+ const { to, message, fileUrl, fileType, variables, contentSid } = messageData;
48
48
 
49
49
  const formattedFrom = this.ensureWhatsAppFormat(this.whatsappNumber);
50
- const formattedTo = this.ensureWhatsAppFormat(code);
51
-
50
+ const formattedTo = this.ensureWhatsAppFormat(to);
52
51
 
53
52
  if (!formattedFrom || !formattedTo) {
54
53
  throw new Error('Invalid sender or recipient number');
@@ -61,12 +60,6 @@ class TwilioProvider extends MessageProvider {
61
60
 
62
61
  // Handle template messages
63
62
  if (contentSid) {
64
- // Render template and add to messageData for storage
65
- const renderedMessage = await this.renderTemplate(contentSid, variables);
66
- if (renderedMessage) {
67
- messageData.body = renderedMessage; // Add rendered content for storage
68
- }
69
-
70
63
  messageParams.contentSid = contentSid;
71
64
  if (variables && Object.keys(variables).length > 0) {
72
65
  const formattedVariables = {};
@@ -147,18 +140,17 @@ class TwilioProvider extends MessageProvider {
147
140
  : async (payload) => await this.sendMessage(payload);
148
141
 
149
142
  console.log('[TwilioProvider] Scheduled message created', {
150
- to: scheduledMessage.code,
143
+ to: scheduledMessage.to || scheduledMessage.code,
151
144
  delay,
152
145
  hasContentSid: Boolean(scheduledMessage.contentSid)
153
146
  });
154
147
 
155
148
  setTimeout(async () => {
156
149
  try {
157
- // Convert Mongoose document to plain object if needed
158
- const payload = scheduledMessage.toObject ? scheduledMessage.toObject() : { ...scheduledMessage };
150
+ const payload = { ...scheduledMessage };
159
151
  delete payload.__nexusSend;
160
152
  console.log('[TwilioProvider] Timer fired', {
161
- to: payload.code,
153
+ to: payload.to || payload.code,
162
154
  hasMessage: Boolean(payload.message || payload.body),
163
155
  hasMedia: Boolean(payload.fileUrl)
164
156
  });
@@ -319,170 +311,6 @@ class TwilioProvider extends MessageProvider {
319
311
  }
320
312
  }
321
313
 
322
- /**
323
- * Render template content with variables
324
- * @param {string} contentSid - The Twilio content SID
325
- * @param {Object} variables - The variables object with keys like "1", "2"
326
- * @returns {Promise<string|null>} The rendered message content or null if not found
327
- */
328
- async renderTemplate(contentSid, variables) {
329
- try {
330
- if (!contentSid) return null;
331
-
332
- const template = await this.getTemplate(contentSid);
333
-
334
- if (!template || !template.types) {
335
- console.warn('[TwilioProvider] Template not found or has no types:', contentSid);
336
- return null;
337
- }
338
-
339
- // Extract text content from different template types
340
- let textContent = this.extractTextFromTemplate(template);
341
-
342
- if (!textContent) {
343
- console.warn('[TwilioProvider] No text content found in template:', contentSid);
344
- return null;
345
- }
346
-
347
- // Render variables if provided
348
- if (variables && typeof variables === 'object' && Object.keys(variables).length > 0) {
349
- return this.renderTemplateWithVariables(textContent, variables);
350
- }
351
-
352
- return textContent.trim();
353
- } catch (error) {
354
- console.error('[TwilioProvider] Error rendering template:', error.message);
355
- return null;
356
- }
357
- }
358
-
359
- /**
360
- * Extract text content from different template types
361
- * @param {Object} template - The Twilio template object
362
- * @returns {string} The extracted text content
363
- */
364
- extractTextFromTemplate(template) {
365
- const types = template.types || {};
366
-
367
- // Handle plain text templates
368
- if (types['twilio/text']) {
369
- return types['twilio/text'].body || '';
370
- }
371
-
372
- // Handle quick reply templates
373
- if (types['twilio/quick-reply']) {
374
- const quickReply = types['twilio/quick-reply'];
375
- let text = quickReply.body || '';
376
-
377
- // Add quick reply options
378
- if (quickReply.actions && Array.isArray(quickReply.actions)) {
379
- const options = quickReply.actions
380
- .filter(action => action.title)
381
- .map(action => `• ${action.title}`)
382
- .join('\n');
383
- if (options) {
384
- text += (text ? '\n\n' : '') + options;
385
- }
386
- }
387
-
388
- return text;
389
- }
390
-
391
- // Handle list templates
392
- if (types['twilio/list']) {
393
- const list = types['twilio/list'];
394
- let text = list.body || '';
395
-
396
- // Add list items
397
- if (list.items && Array.isArray(list.items)) {
398
- const items = list.items
399
- .filter(item => item.title)
400
- .map((item, index) => `${index + 1}. ${item.title}`)
401
- .join('\n');
402
- if (items) {
403
- text += (text ? '\n\n' : '') + items;
404
- }
405
- }
406
-
407
- return text;
408
- }
409
-
410
- // Handle button templates
411
- if (types['twilio/button']) {
412
- const button = types['twilio/button'];
413
- let text = button.body || '';
414
-
415
- // Add button options
416
- if (button.actions && Array.isArray(button.actions)) {
417
- const buttons = button.actions
418
- .filter(action => action.title)
419
- .map(action => `[${action.title}]`)
420
- .join(' ');
421
- if (buttons) {
422
- text += (text ? '\n\n' : '') + buttons;
423
- }
424
- }
425
-
426
- return text;
427
- }
428
-
429
- // Handle flow templates (fallback to body)
430
- if (types['twilio/flows']) {
431
- return types['twilio/flows'].body || '';
432
- }
433
-
434
- // Handle media templates (extract caption)
435
- if (types['twilio/media']) {
436
- return types['twilio/media'].caption || '';
437
- }
438
-
439
- // Fallback: try to find any body content
440
- for (const typeKey of Object.keys(types)) {
441
- const type = types[typeKey];
442
- if (type && typeof type === 'object' && type.body) {
443
- return type.body;
444
- }
445
- }
446
-
447
- return '';
448
- }
449
-
450
- /**
451
- * Render template content with variables
452
- * @param {string} templateBody - The template body with placeholders like {{1}}, {{2}}
453
- * @param {Object} variables - The variables object with keys like "1", "2"
454
- * @returns {string} The rendered message content
455
- */
456
- renderTemplateWithVariables(templateBody, variables) {
457
- if (!templateBody || typeof templateBody !== 'string') {
458
- return '';
459
- }
460
-
461
- if (!variables || typeof variables !== 'object') {
462
- return templateBody;
463
- }
464
-
465
- try {
466
- let rendered = templateBody;
467
-
468
- // Replace placeholders like {{1}}, {{2}}, etc. with variable values
469
- Object.keys(variables).forEach(key => {
470
- const placeholder = `{{${key}}}`;
471
- const value = variables[key] || '';
472
-
473
- // Simple string replacement - more reliable than regex for this use case
474
- if (rendered.includes(placeholder)) {
475
- rendered = rendered.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), value);
476
- }
477
- });
478
-
479
- return rendered.trim();
480
- } catch (error) {
481
- console.warn('[TwilioProvider] Error rendering template variables:', error.message);
482
- return templateBody; // Return original template if rendering fails
483
- }
484
- }
485
-
486
314
  /**
487
315
  * Check template approval status using Twilio Content API helpers
488
316
  */
File without changes
File without changes
@@ -67,7 +67,7 @@ class BaseAssistant {
67
67
  this.thread = thread;
68
68
  }
69
69
 
70
- setReplies(replies) {
70
+ set_replies(replies) {
71
71
  this.replies = replies;
72
72
  }
73
73
 
@@ -230,8 +230,7 @@ class BaseAssistant {
230
230
  if (!this.lastMessages) return [];
231
231
  return [{
232
232
  role: 'assistant',
233
- content: `Últimos mensajes para ${code}:
234
- ${this.lastMessages}`
233
+ content: `Últimos mensajes para ${code}: \n${this.lastMessages}`
235
234
  }];
236
235
  }
237
236
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/lib/core/index.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/lib/index.d.ts CHANGED
File without changes
package/lib/index.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -68,8 +68,7 @@ async function insertMessage(values) {
68
68
  reply_id: values.reply_id,
69
69
  from_me: values.from_me,
70
70
  processed: skipNumbers.includes(values.numero),
71
- media: values.media ? values.media : null,
72
- content_sid: values.content_sid || null
71
+ media: values.media ? values.media : null
73
72
  };
74
73
 
75
74
  await Message.findOneAndUpdate(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -66,11 +66,10 @@ class MongoStorage {
66
66
  from: messageData?.from,
67
67
  provider: messageData?.provider || 'unknown',
68
68
  hasRaw: Boolean(messageData?.raw),
69
- hasMedia: Boolean(messageData?.media || messageData?.fileUrl),
70
- hasContentSid: Boolean(messageData?.contentSid)
69
+ hasMedia: Boolean(messageData?.media || messageData?.fileUrl)
71
70
  });
72
71
  const enrichedMessage = await this._enrichTwilioMedia(messageData);
73
- const values = this.buildMessageValues(enrichedMessage);
72
+ const values = this.buildLegacyMessageValues(enrichedMessage);
74
73
  const { insertMessage } = require('../models/messageModel');
75
74
  await insertMessage(values);
76
75
  console.log('[MongoStorage] Message stored', {
@@ -167,8 +166,7 @@ class MongoStorage {
167
166
  return trimmed;
168
167
  }
169
168
 
170
-
171
- buildMessageValues(messageData = {}) {
169
+ buildLegacyMessageValues(messageData = {}) {
172
170
  const numero = messageData.to || messageData.code || messageData.numero || messageData.from;
173
171
  const rawNumero = typeof numero === 'string' ? numero : '';
174
172
  const normalizedNumero = this.normalizeNumero(rawNumero);
@@ -177,16 +175,7 @@ class MongoStorage {
177
175
  const now = new Date();
178
176
  const timestamp = now.toISOString();
179
177
  const nombre = messageData.nombre_whatsapp || messageData.author || messageData.fromName || runtimeConfig.get('USER_DB_MONGO') || process.env.USER_DB_MONGO || 'Nexus';
180
-
181
- // Use message body directly (template rendering is now handled by the provider)
182
- let textBody = messageData.message || messageData.body;
183
-
184
- if (!textBody && isMedia) {
185
- textBody = `[Media:${messageData.fileType || 'attachment'}]`;
186
- } else if (!textBody) {
187
- textBody = '';
188
- }
189
-
178
+ const textBody = messageData.message || messageData.body || (messageData.contentSid ? `[Template:${messageData.contentSid}]` : isMedia ? `[Media:${messageData.fileType || 'attachment'}]` : '');
190
179
  const providerId = messageData.messageId || messageData.sid || messageData.id || messageData._id || `pending-${now.getTime()}-${Math.floor(Math.random()*1000)}`;
191
180
 
192
181
  const media = messageData.media || (messageData.fileUrl ? {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "1.5.10",
3
+ "version": "1.6.0",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",
@@ -73,7 +73,7 @@
73
73
  "airtable": "^0.12.2",
74
74
  "aws-sdk": "2.1674.0",
75
75
  "axios": "^1.5.0",
76
- "dotenv": "^16.6.1",
76
+ "dotenv": "^16.4.7",
77
77
  "moment-timezone": "^0.5.43",
78
78
  "mongoose": "^7.5.0",
79
79
  "multer": "1.4.5-lts.1",
@@ -102,4 +102,4 @@
102
102
  "publishConfig": {
103
103
  "access": "public"
104
104
  }
105
- }
105
+ }