@peopl-health/nexus 3.3.7 → 3.3.9

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 (86) hide show
  1. package/examples/basic-usage.js +8 -0
  2. package/lib/adapters/BaileysProvider.js +2 -11
  3. package/lib/adapters/TwilioProvider.js +9 -77
  4. package/lib/adapters/registry.js +3 -4
  5. package/lib/assistants/BaseAssistant.js +9 -10
  6. package/lib/config/airtableConfig.js +1 -3
  7. package/lib/config/awsConfig.js +4 -4
  8. package/lib/config/configLoader.js +1 -1
  9. package/lib/config/llmConfig.js +6 -4
  10. package/lib/config/metaConfig.js +31 -0
  11. package/lib/config/mongoAuthConfig.js +3 -1
  12. package/lib/config/mongoConfig.js +1 -0
  13. package/lib/controllers/assistantController.js +5 -4
  14. package/lib/controllers/bugReportController.js +5 -2
  15. package/lib/controllers/caseDocumentationController.js +3 -1
  16. package/lib/controllers/conversationController.js +12 -36
  17. package/lib/controllers/interactionController.js +6 -3
  18. package/lib/controllers/mediaController.js +1 -2
  19. package/lib/controllers/messageController.js +10 -4
  20. package/lib/controllers/messageStatusController.js +2 -9
  21. package/lib/controllers/patientController.js +3 -1
  22. package/lib/controllers/qualityMessageController.js +2 -1
  23. package/lib/controllers/templateController.js +35 -72
  24. package/lib/controllers/templateFlowController.js +159 -87
  25. package/lib/controllers/threadController.js +2 -10
  26. package/lib/controllers/uploadController.js +2 -0
  27. package/lib/core/MessageProvider.js +0 -3
  28. package/lib/core/NexusMessaging.js +18 -150
  29. package/lib/helpers/assistantHelper.js +6 -4
  30. package/lib/helpers/baileysHelper.js +7 -8
  31. package/lib/helpers/filesHelper.js +7 -4
  32. package/lib/helpers/llmsHelper.js +3 -16
  33. package/lib/helpers/mediaHelper.js +2 -0
  34. package/lib/helpers/messageHelper.js +3 -5
  35. package/lib/helpers/messageStatusHelper.js +5 -12
  36. package/lib/helpers/metaFlowHelper.js +13 -0
  37. package/lib/helpers/mongoHelper.js +1 -4
  38. package/lib/helpers/processHelper.js +5 -13
  39. package/lib/helpers/qrHelper.js +2 -2
  40. package/lib/helpers/templateFlowControllerHelper.js +93 -0
  41. package/lib/helpers/templateRecoveryHelper.js +5 -6
  42. package/lib/helpers/threadHelper.js +6 -3
  43. package/lib/helpers/threadRecoveryHelper.js +5 -3
  44. package/lib/helpers/twilioHelper.js +1 -7
  45. package/lib/helpers/twilioMediaProcessor.js +9 -6
  46. package/lib/helpers/whatsappHelper.js +1 -3
  47. package/lib/index.js +11 -96
  48. package/lib/memory/DefaultMemoryManager.js +9 -6
  49. package/lib/memory/MemoryManager.js +3 -3
  50. package/lib/middleware/requestId.js +2 -0
  51. package/lib/models/interactionModel.js +2 -0
  52. package/lib/models/messageModel.js +11 -20
  53. package/lib/models/predictionMetricsModel.js +2 -0
  54. package/lib/models/qualityMessageModel.js +2 -0
  55. package/lib/models/templateModel.js +0 -1
  56. package/lib/observability/index.js +2 -15
  57. package/lib/providers/OpenAIAssistantsProvider.js +4 -17
  58. package/lib/providers/OpenAIResponsesProvider.js +18 -31
  59. package/lib/providers/OpenAIResponsesProviderTools.js +1 -10
  60. package/lib/providers/{createProvider.js → createLLMProvider.js} +6 -8
  61. package/lib/routes/index.js +2 -8
  62. package/lib/services/airtableService.js +1 -5
  63. package/lib/services/assistantResolver.js +1 -0
  64. package/lib/services/assistantService.js +2 -13
  65. package/lib/services/assistantServiceCore.js +16 -17
  66. package/lib/services/conversationService.js +4 -5
  67. package/lib/services/metaFlowService.js +71 -0
  68. package/lib/services/metaService.js +169 -0
  69. package/lib/services/twilioService.js +0 -5
  70. package/lib/storage/MongoStorage.js +2 -4
  71. package/lib/storage/NoopStorage.js +0 -4
  72. package/lib/storage/registry.js +0 -3
  73. package/lib/templates/templateStructure.js +0 -3
  74. package/lib/utils/logger.js +0 -6
  75. package/lib/utils/messageParser.js +2 -33
  76. package/lib/utils/outputSanitizer.js +0 -4
  77. package/lib/utils/retryHelper.js +0 -25
  78. package/lib/utils/scheduleUtils.js +1 -3
  79. package/lib/utils/tracingDecorator.js +2 -7
  80. package/package.json +2 -1
  81. package/lib/assistants/index.js +0 -5
  82. package/lib/interactive/index.js +0 -86
  83. package/lib/interactive/registry.js +0 -31
  84. package/lib/interactive/twilioMapper.js +0 -61
  85. package/lib/models/index.js +0 -15
  86. package/lib/storage/index.js +0 -5
@@ -1,6 +1,7 @@
1
1
  const express = require('express');
2
2
  require('dotenv').config();
3
3
  const { Nexus, setupDefaultRoutes, BaseAssistant } = require('@peopl-health/nexus');
4
+ const { setMetaConfig } = require('../lib/config/metaConfig');
4
5
 
5
6
  const app = express();
6
7
  app.use(express.json());
@@ -66,6 +67,13 @@ class GeneralAssistant extends BaseAssistant {
66
67
  }
67
68
 
68
69
  async function startServer() {
70
+ // Configure Meta Flow API (consumer-owned)
71
+ setMetaConfig({
72
+ accessToken: process.env.META_ACCESS_TOKEN,
73
+ wabaId: process.env.META_WABA_ID,
74
+ apiVersion: process.env.META_API_VERSION || 'v21.0'
75
+ });
76
+
69
77
  // Initialize Nexus with check-after processing (immediate response, checks for new messages after)
70
78
  const nexus = new Nexus({
71
79
  messaging: {
@@ -1,10 +1,8 @@
1
- const { MessageProvider } = require('../core/MessageProvider');
2
1
  const { logger } = require('../utils/logger');
3
2
  const { calculateDelay } = require('../utils/scheduleUtils');
4
3
 
5
- /**
6
- * Baileys WhatsApp messaging provider
7
- */
4
+ const { MessageProvider } = require('../core/MessageProvider');
5
+
8
6
  class BaileysProvider extends MessageProvider {
9
7
  constructor(config) {
10
8
  super(config);
@@ -17,7 +15,6 @@ class BaileysProvider extends MessageProvider {
17
15
  async initialize() {
18
16
  try {
19
17
  const baileys = require('baileys');
20
- // Support both CJS and ESM shapes
21
18
  const makeWASocket = baileys.default || baileys.makeWASocket || baileys;
22
19
  const useMultiFileAuthState = baileys.useMultiFileAuthState || baileys.useMultiFileAuthState;
23
20
  const { useMongoDBAuthState } = require('../config/mongoAuthConfig');
@@ -58,7 +55,6 @@ class BaileysProvider extends MessageProvider {
58
55
  const { connection, lastDisconnect, qr } = update || {};
59
56
 
60
57
  if (qr) {
61
- // Emit QR code event for consumer to handle
62
58
  this.emit?.('qr', qr);
63
59
  }
64
60
 
@@ -100,7 +96,6 @@ class BaileysProvider extends MessageProvider {
100
96
  let sendOptions = {};
101
97
  let formattedMessage = this.formatMessage(body || '');
102
98
 
103
- // Handle mentions
104
99
  const regex = /@\d+/g;
105
100
  const matches = formattedMessage.match(regex);
106
101
  if (matches) {
@@ -110,13 +105,11 @@ class BaileysProvider extends MessageProvider {
110
105
  sendOptions.mentions = processedNumbers;
111
106
  }
112
107
 
113
- // Handle link preview
114
108
  const keywords = ['meet.google.com', 'airtable', 'zoom'];
115
109
  if (keywords.some(keyword => formattedMessage.includes(keyword)) || hidePreview) {
116
110
  sendOptions.linkPreview = false;
117
111
  }
118
112
 
119
- // Set message content based on type
120
113
  if (!fileUrl || fileType === 'text') {
121
114
  sendOptions.text = formattedMessage;
122
115
  } else {
@@ -182,7 +175,6 @@ class BaileysProvider extends MessageProvider {
182
175
  return { scheduled: true, delay };
183
176
  }
184
177
 
185
-
186
178
  getConnectionStatus() {
187
179
  return this.waSocket?.user ? true : false;
188
180
  }
@@ -195,7 +187,6 @@ class BaileysProvider extends MessageProvider {
195
187
  this.isConnected = false;
196
188
  }
197
189
 
198
- // Content/Template operations are not supported for Baileys
199
190
  async listTemplates() {
200
191
  throw new Error('Template operations are only supported with Twilio provider');
201
192
  }
@@ -1,18 +1,19 @@
1
- const { MessageProvider } = require('../core/MessageProvider');
2
1
  const axios = require('axios');
2
+ const { v4: uuidv4 } = require('uuid');
3
+
3
4
  const runtimeConfig = require('../config/runtimeConfig');
4
- const { uploadMediaToS3, getFileExtension } = require('../helpers/mediaHelper');
5
- const { ensureWhatsAppFormat } = require('../helpers/twilioHelper');
6
- const { sanitizeMediaFilename } = require('../utils/inputSanitizer');
7
5
  const { generatePresignedUrl } = require('../config/awsConfig');
6
+
7
+ const { sanitizeMediaFilename } = require('../utils/inputSanitizer');
8
8
  const { validateMedia, getMediaType } = require('../utils/mediaValidator');
9
9
  const { logger } = require('../utils/logger');
10
10
  const { calculateDelay } = require('../utils/scheduleUtils');
11
- const { v4: uuidv4 } = require('uuid');
12
11
 
13
- /**
14
- * Twilio WhatsApp messaging provider
15
- */
12
+ const { ensureWhatsAppFormat } = require('../helpers/twilioHelper');
13
+ const { uploadMediaToS3, getFileExtension } = require('../helpers/mediaHelper');
14
+
15
+ const { MessageProvider } = require('../core/MessageProvider');
16
+
16
17
  class TwilioProvider extends MessageProvider {
17
18
  constructor(config) {
18
19
  super(config);
@@ -47,7 +48,6 @@ class TwilioProvider extends MessageProvider {
47
48
  const formattedFrom = ensureWhatsAppFormat(this.whatsappNumber);
48
49
  const formattedCode = ensureWhatsAppFormat(code);
49
50
 
50
-
51
51
  if (!formattedFrom || !formattedCode) {
52
52
  throw new Error('Invalid sender or recipient number');
53
53
  }
@@ -68,7 +68,6 @@ class TwilioProvider extends MessageProvider {
68
68
  logger.debug('[TwilioProvider] No status callback URL configured');
69
69
  }
70
70
 
71
- // Handle template messages
72
71
  if (contentSid) {
73
72
  const renderedMessage = await this.renderTemplate(contentSid, variables);
74
73
  if (renderedMessage) {
@@ -89,7 +88,6 @@ class TwilioProvider extends MessageProvider {
89
88
  messageParams.body = body;
90
89
  }
91
90
 
92
- // Handle media messages
93
91
  if (fileUrl && fileType !== 'text') {
94
92
  const mediaPrep = await this.prepareOutboundMedia(messageData, formattedCode);
95
93
  const outboundMediaUrl = mediaPrep.mediaUrl || fileUrl;
@@ -294,7 +292,6 @@ class TwilioProvider extends MessageProvider {
294
292
 
295
293
  setTimeout(async () => {
296
294
  try {
297
- // Convert Mongoose document to plain object if needed
298
295
  const payload = scheduledMessage.toObject ? scheduledMessage.toObject() : { ...scheduledMessage };
299
296
  delete payload.__nexusSend;
300
297
 
@@ -335,7 +332,6 @@ class TwilioProvider extends MessageProvider {
335
332
  return { mediaUrl: fileUrl, uploaded: false };
336
333
  }
337
334
 
338
- // Reuse existing uploaded media if present
339
335
  if (messageData.media?.bucketName && messageData.media?.key) {
340
336
  const presigned = await generatePresignedUrl(messageData.media.bucketName, messageData.media.key, 3600);
341
337
  return {
@@ -424,13 +420,6 @@ class TwilioProvider extends MessageProvider {
424
420
  }
425
421
  }
426
422
 
427
-
428
- /**
429
- * Split a message into chunks at sentence boundaries, respecting Twilio's character limit
430
- * @param {string} text - The message text to split
431
- * @param {number} maxLength - Maximum length per chunk (default: 1600)
432
- * @returns {Array<string>} Array of message chunks
433
- */
434
423
  splitMessageAtWordBoundaries(text, maxLength = 1600) {
435
424
  if (!text || text.length <= maxLength) return [text];
436
425
  const chunks = [];
@@ -465,11 +454,6 @@ class TwilioProvider extends MessageProvider {
465
454
  return chunks;
466
455
  }
467
456
 
468
- /**
469
- * List templates from Twilio Content API
470
- * @param {Object} options - Query options
471
- * @returns {Promise<Array>} Array of templates
472
- */
473
457
  async listTemplates(options = {}) {
474
458
  if (!this.isConnected || !this.twilioClient) {
475
459
  throw new Error('Twilio provider not initialized');
@@ -484,9 +468,6 @@ class TwilioProvider extends MessageProvider {
484
468
  }
485
469
  }
486
470
 
487
- /**
488
- * Fetch a specific template/content by SID
489
- */
490
471
  async getTemplate(sid) {
491
472
  if (!this.isConnected || !this.twilioClient) {
492
473
  throw new Error('Twilio provider not initialized');
@@ -500,12 +481,6 @@ class TwilioProvider extends MessageProvider {
500
481
  }
501
482
  }
502
483
 
503
- /**
504
- * Render template content with variables
505
- * @param {string} contentSid - The Twilio content SID
506
- * @param {Object} variables - The variables object with keys like "1", "2"
507
- * @returns {Promise<string|null>} The rendered message content or null if not found
508
- */
509
484
  async renderTemplate(contentSid, variables) {
510
485
  try {
511
486
  if (!contentSid) return null;
@@ -517,7 +492,6 @@ class TwilioProvider extends MessageProvider {
517
492
  return null;
518
493
  }
519
494
 
520
- // Extract text content from different template types
521
495
  let textContent = this.extractTextFromTemplate(template);
522
496
 
523
497
  if (!textContent) {
@@ -525,7 +499,6 @@ class TwilioProvider extends MessageProvider {
525
499
  return null;
526
500
  }
527
501
 
528
- // Render variables if provided
529
502
  if (variables && typeof variables === 'object' && Object.keys(variables).length > 0) {
530
503
  return this.renderTemplateWithVariables(textContent, variables);
531
504
  }
@@ -537,25 +510,17 @@ class TwilioProvider extends MessageProvider {
537
510
  }
538
511
  }
539
512
 
540
- /**
541
- * Extract text content from different template types
542
- * @param {Object} template - The Twilio template object
543
- * @returns {string} The extracted text content
544
- */
545
513
  extractTextFromTemplate(template) {
546
514
  const types = template.types || {};
547
515
 
548
- // Handle plain text templates
549
516
  if (types['twilio/text']) {
550
517
  return types['twilio/text'].body || '';
551
518
  }
552
519
 
553
- // Handle quick reply templates
554
520
  if (types['twilio/quick-reply']) {
555
521
  const quickReply = types['twilio/quick-reply'];
556
522
  let text = quickReply.body || '';
557
523
 
558
- // Add quick reply options
559
524
  if (quickReply.actions && Array.isArray(quickReply.actions)) {
560
525
  const options = quickReply.actions
561
526
  .filter(action => action.title)
@@ -569,12 +534,10 @@ class TwilioProvider extends MessageProvider {
569
534
  return text;
570
535
  }
571
536
 
572
- // Handle list templates
573
537
  if (types['twilio/list']) {
574
538
  const list = types['twilio/list'];
575
539
  let text = list.body || '';
576
540
 
577
- // Add list items
578
541
  if (list.items && Array.isArray(list.items)) {
579
542
  const items = list.items
580
543
  .filter(item => item.title)
@@ -588,12 +551,10 @@ class TwilioProvider extends MessageProvider {
588
551
  return text;
589
552
  }
590
553
 
591
- // Handle button templates
592
554
  if (types['twilio/button']) {
593
555
  const button = types['twilio/button'];
594
556
  let text = button.body || '';
595
557
 
596
- // Add button options
597
558
  if (button.actions && Array.isArray(button.actions)) {
598
559
  const buttons = button.actions
599
560
  .filter(action => action.title)
@@ -607,17 +568,14 @@ class TwilioProvider extends MessageProvider {
607
568
  return text;
608
569
  }
609
570
 
610
- // Handle flow templates (fallback to body)
611
571
  if (types['twilio/flows']) {
612
572
  return types['twilio/flows'].body || '';
613
573
  }
614
574
 
615
- // Handle media templates (extract caption)
616
575
  if (types['twilio/media']) {
617
576
  return types['twilio/media'].caption || '';
618
577
  }
619
578
 
620
- // Fallback: try to find any body content
621
579
  for (const typeKey of Object.keys(types)) {
622
580
  const type = types[typeKey];
623
581
  if (type && typeof type === 'object' && type.body) {
@@ -628,12 +586,6 @@ class TwilioProvider extends MessageProvider {
628
586
  return '';
629
587
  }
630
588
 
631
- /**
632
- * Render template content with variables
633
- * @param {string} templateBody - The template body with placeholders like {{1}}, {{2}}
634
- * @param {Object} variables - The variables object with keys like "1", "2"
635
- * @returns {string} The rendered message content
636
- */
637
589
  renderTemplateWithVariables(templateBody, variables) {
638
590
  if (!templateBody || typeof templateBody !== 'string') {
639
591
  return '';
@@ -646,12 +598,10 @@ class TwilioProvider extends MessageProvider {
646
598
  try {
647
599
  let rendered = templateBody;
648
600
 
649
- // Replace placeholders like {{1}}, {{2}}, etc. with variable values
650
601
  Object.keys(variables).forEach(key => {
651
602
  const placeholder = `{{${key}}}`;
652
603
  const value = variables[key] || '';
653
604
 
654
- // Simple string replacement - more reliable than regex for this use case
655
605
  if (rendered.includes(placeholder)) {
656
606
  rendered = rendered.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), value);
657
607
  }
@@ -664,9 +614,6 @@ class TwilioProvider extends MessageProvider {
664
614
  }
665
615
  }
666
616
 
667
- /**
668
- * Check template approval status using Twilio Content API helpers
669
- */
670
617
  async checkApprovalStatus(sid) {
671
618
  if (!this.isConnected || !this.twilioClient) {
672
619
  throw new Error('Twilio provider not initialized');
@@ -729,9 +676,6 @@ class TwilioProvider extends MessageProvider {
729
676
  }
730
677
  }
731
678
 
732
- /**
733
- * Submit template for approval (best-effort placeholder)
734
- */
735
679
  async submitForApproval(contentSid, name, category) {
736
680
  if (!contentSid) throw new Error('Content SID is required');
737
681
  const content = await this.getTemplate(contentSid);
@@ -763,9 +707,6 @@ class TwilioProvider extends MessageProvider {
763
707
  }
764
708
  }
765
709
 
766
- /**
767
- * Delete a template/content by SID
768
- */
769
710
  async deleteTemplate(sid) {
770
711
  if (!this.isConnected || !this.twilioClient) {
771
712
  throw new Error('Twilio provider not initialized');
@@ -779,10 +720,6 @@ class TwilioProvider extends MessageProvider {
779
720
  }
780
721
  }
781
722
 
782
- /**
783
- * Create a template/content using Twilio Content API
784
- * @param {Object} templateData - Must follow Twilio Content API schema
785
- */
786
723
  async createTemplate(templateData) {
787
724
  if (!this.isConnected || !this.twilioClient) {
788
725
  throw new Error('Twilio provider not initialized');
@@ -798,9 +735,6 @@ class TwilioProvider extends MessageProvider {
798
735
  }
799
736
  }
800
737
 
801
- /**
802
- * Check the status of a sent message using its SID
803
- */
804
738
  async getMessageStatus(messageSid) {
805
739
  if (!this.isConnected || !this.twilioClient) {
806
740
  throw new Error('Twilio provider not initialized');
@@ -811,8 +745,6 @@ class TwilioProvider extends MessageProvider {
811
745
 
812
746
  try {
813
747
  const message = await this.twilioClient.messages(messageSid).fetch();
814
- // Returns the complete Twilio message object with all status information
815
- // Status-related fields: status, errorCode, errorMessage, dateCreated, dateSent, dateUpdated
816
748
  return message;
817
749
  } catch (error) {
818
750
  if (error.status === 404) {
@@ -12,18 +12,17 @@ function getProvider(name) {
12
12
  return _providers.get(String(name || '').toLowerCase());
13
13
  }
14
14
 
15
- function createProvider(name, config) {
15
+ function createMessagingProvider(name, config) {
16
16
  const ProviderClass = getProvider(name);
17
17
  if (!ProviderClass) throw new Error(`Unsupported provider: ${name}`);
18
18
  return new ProviderClass(config || {});
19
19
  }
20
20
 
21
- // Register built-ins
22
21
  registerProvider('twilio', TwilioProvider);
23
22
  registerProvider('baileys', BaileysProvider);
24
23
 
25
24
  module.exports = {
26
25
  registerProvider,
27
26
  getProvider,
28
- createProvider
29
- };
27
+ createMessagingProvider
28
+ };
@@ -1,16 +1,16 @@
1
1
  const llmConfig = require('../config/llmConfig');
2
+
3
+ const { logger } = require('../utils/logger');
4
+
2
5
  const { Thread } = require('../models/threadModel');
3
6
  const { Message } = require('../models/messageModel');
7
+
4
8
  const { formatMessage } = require('../helpers/messageHelper');
5
- const { createProvider } = require('../providers/createProvider');
6
- const { logger } = require('../utils/logger');
9
+
10
+ const { createLLMProvider } = require('../providers/createLLMProvider');
7
11
 
8
12
  const DEFAULT_MAX_HISTORICAL_MESSAGES = parseInt(process.env.MAX_HISTORICAL_MESSAGES || '50', 10);
9
13
 
10
- /**
11
- * Flexible base assistant implementation that integrates with OpenAI Threads
12
- * and supports dynamic tool registration.
13
- */
14
14
  class BaseAssistant {
15
15
  constructor(options = {}) {
16
16
  this.assistantId = options.assistantId || null;
@@ -29,7 +29,7 @@ class BaseAssistant {
29
29
 
30
30
  if (!this.provider && this.client) {
31
31
  try {
32
- const provider = createProvider({
32
+ const provider = createLLMProvider({
33
33
  client: this.client,
34
34
  variant: process.env.VARIANT || 'assistants'
35
35
  });
@@ -70,7 +70,7 @@ class BaseAssistant {
70
70
  if (!this.provider) {
71
71
  try {
72
72
  const variant = process.env.VARIANT || 'assistants';
73
- const provider = createProvider({ client: this.client, variant });
73
+ const provider = createLLMProvider({ client: this.client, variant });
74
74
  this.provider = provider;
75
75
  if (typeof llmConfig.setOpenAIProvider === 'function') {
76
76
  llmConfig.setOpenAIProvider(provider);
@@ -167,7 +167,7 @@ class BaseAssistant {
167
167
  return await this.create(code, context);
168
168
  }
169
169
 
170
- async create(code, context = {}) {
170
+ async create(code, _context = {}) {
171
171
  this._ensureClient();
172
172
  this.status = 'active';
173
173
  this.thread = { code };
@@ -195,7 +195,6 @@ class BaseAssistant {
195
195
 
196
196
  const messagesInOrder = lastMessages.reverse();
197
197
 
198
- // Messages with from_me: true are assistant messages, from_me: false are user messages
199
198
  const formattedMessages = messagesInOrder
200
199
  .filter(message => message && message.timestamp && message.body && message.body.trim() !== '')
201
200
  .flatMap(message => {
@@ -1,11 +1,11 @@
1
1
  const Airtable = require('airtable');
2
+
2
3
  const runtimeConfig = require('./runtimeConfig');
3
4
 
4
5
  const airtableConfig = {
5
6
  apiKey: runtimeConfig.get('AIRTABLE_API_KEY'),
6
7
  };
7
8
 
8
- // Configurable base IDs - users can override via environment variables
9
9
  const Calendar_ID = require('./runtimeConfig').get('AIRTABLE_CALENDAR_ID') || 'appIjEstWR6972tbF';
10
10
  const Config_ID = require('./runtimeConfig').get('AIRTABLE_CONFIG_ID') || 'app9K4EvGI8McC8jF';
11
11
  const Historial_Clinico_ID = require('./runtimeConfig').get('AIRTABLE_HISTORIAL_CLINICO_ID') || 'appdUpGUS06XIzVnY';
@@ -17,7 +17,6 @@ const Follow_Up_ID = require('./runtimeConfig').get('AIRTABLE_FOLLOW_UP_ID') ||
17
17
  const Webinars_Leads_ID = require('./runtimeConfig').get('AIRTABLE_WEBINARS_LEADS_ID') || 'appzjpVXTI0TgqGPq';
18
18
  const Product_ID = require('./runtimeConfig').get('AIRTABLE_PRODUCT_ID') || 'appu2YDW2pKDYLL5H';
19
19
 
20
- // Initialize Airtable only if API key is provided
21
20
  let airtable = null;
22
21
  if (airtableConfig.apiKey) {
23
22
  airtable = new Airtable({ apiKey: airtableConfig.apiKey });
@@ -49,7 +48,6 @@ module.exports = {
49
48
  Follow_Up_ID,
50
49
  Webinars_Leads_ID,
51
50
  Product_ID,
52
- // Helper function to get base by ID
53
51
  getBase: (baseKeyOrId = require('./runtimeConfig').get('AIRTABLE_BASE_ID')) => {
54
52
  if (!airtable) {
55
53
  throw new Error('Airtable not configured. Please set AIRTABLE_API_KEY environment variable.');
@@ -1,9 +1,10 @@
1
- const { logger } = require('../utils/logger');
2
-
3
- const AWS = require('aws-sdk');
4
1
  const fs = require('fs');
5
2
  const path = require('path');
6
3
 
4
+ const AWS = require('aws-sdk');
5
+
6
+ const { logger } = require('../utils/logger');
7
+
7
8
  AWS.config.update({
8
9
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
9
10
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
@@ -13,7 +14,6 @@ AWS.config.update({
13
14
 
14
15
  const s3 = new AWS.S3();
15
16
 
16
-
17
17
  async function uploadBufferToS3(buffer, bucketName, key, contentType, isWhatsAppMedia = false) {
18
18
  const params = {
19
19
  Bucket: bucketName,
@@ -35,4 +35,4 @@ function loadNexusConfig(userConfig) {
35
35
  }
36
36
  }
37
37
 
38
- module.exports = { loadNexusConfig, defaults };
38
+ module.exports = { loadNexusConfig, defaults };
@@ -1,6 +1,8 @@
1
- const runtimeConfig = require('./runtimeConfig');
1
+ const runtimeConfig = require('../config/runtimeConfig');
2
+
2
3
  const { logger } = require('../utils/logger');
3
- const { createProvider } = require('../providers/createProvider');
4
+
5
+ const { createLLMProvider } = require('../providers/createLLMProvider');
4
6
 
5
7
  let anthropicClient = null;
6
8
  let openaiClient = null;
@@ -40,7 +42,7 @@ const getOpenAIProvider = ({ instantiate = true, variant = providerVariant } = {
40
42
  if (providerInstance) return providerInstance;
41
43
  if (!instantiate) return null;
42
44
  if (!openaiClient) return null;
43
- const provider = createProvider({ client: openaiClient, variant });
45
+ const provider = createLLMProvider({ client: openaiClient, variant });
44
46
  setOpenAIProvider(provider);
45
47
  return provider;
46
48
  };
@@ -54,7 +56,7 @@ const requireOpenAIProvider = (options) => {
54
56
  };
55
57
 
56
58
  const configureOpenAIProvider = (config = {}) => {
57
- const provider = createProvider(config);
59
+ const provider = createLLMProvider(config);
58
60
  setOpenAIProvider(provider);
59
61
  return provider;
60
62
  };
@@ -0,0 +1,31 @@
1
+ const { logger } = require('../utils/logger');
2
+
3
+ let metaConfig = {
4
+ accessToken: null,
5
+ wabaId: null,
6
+ apiVersion: 'v21.0'
7
+ };
8
+
9
+ const setMetaConfig = ({ accessToken, wabaId, apiVersion }) => {
10
+ if (!accessToken || !wabaId) {
11
+ throw new Error('Meta API config requires accessToken and wabaId');
12
+ }
13
+ metaConfig = {
14
+ ...metaConfig,
15
+ accessToken,
16
+ wabaId,
17
+ apiVersion: apiVersion || metaConfig.apiVersion
18
+ };
19
+ logger.info('[MetaFlowAPI] Configuration updated', {
20
+ wabaId: metaConfig.wabaId,
21
+ apiVersion: metaConfig.apiVersion,
22
+ hasAccessToken: !!metaConfig.accessToken
23
+ });
24
+ };
25
+
26
+ const getMetaConfig = () => metaConfig;
27
+
28
+ module.exports = {
29
+ setMetaConfig,
30
+ getMetaConfig
31
+ };
@@ -1,6 +1,8 @@
1
+ const { randomBytes } = require('crypto');
2
+
1
3
  const { MongoClient } = require('mongodb');
2
4
  const { proto, Curve, signedKeyPair, generateRegistrationId } = require('baileys');
3
- const { randomBytes } = require('crypto');
5
+
4
6
  const { logger } = require('../utils/logger');
5
7
 
6
8
  async function connectToMongo(mongoClient) {
@@ -1,4 +1,5 @@
1
1
  const mongoose = require('mongoose');
2
+
2
3
  const { logger } = require('../utils/logger');
3
4
 
4
5
  let connection = null;
@@ -1,13 +1,15 @@
1
1
  const { Config_ID } = require('../config/airtableConfig');
2
2
 
3
+ const { logger } = require('../utils/logger');
4
+
3
5
  const { Thread } = require('../models/threadModel');
4
6
 
7
+ const { getThreadInfo } = require('../helpers/threadHelper');
8
+
5
9
  const { getRecordByFilter } = require('../services/airtableService');
6
10
  const { createAssistant, addMsgAssistant, addInsAssistant, switchAssistant } = require('../services/assistantService');
7
- const { getThreadInfo } = require('../helpers/threadHelper');
8
- const { sendMessage } = require('../core/NexusMessaging');
9
- const { logger } = require('../utils/logger');
10
11
 
12
+ const { sendMessage } = require('../core/NexusMessaging');
11
13
 
12
14
  const activeAssistantController = async (req, res) => {
13
15
  const { code, active } = req.body;
@@ -123,7 +125,6 @@ const stopAssistantController = async (req, res) => {
123
125
  }
124
126
  };
125
127
 
126
-
127
128
  module.exports = {
128
129
  activeAssistantController,
129
130
  addInsAssistantController,
@@ -1,8 +1,11 @@
1
- const { Message } = require('../models/messageModel');
2
- const { addRecord, getRecordByFilter } = require('../services/airtableService');
3
1
  const { Logging_ID } = require('../config/airtableConfig');
2
+
4
3
  const { logger } = require('../utils/logger');
5
4
 
5
+ const { Message } = require('../models/messageModel');
6
+
7
+ const { addRecord, getRecordByFilter } = require('../services/airtableService');
8
+
6
9
  async function logBugReportToAirtable(reporter, whatsapp_id, description, severity, messageIds = []) {
7
10
  try {
8
11
  let conversation = null;
@@ -1,7 +1,9 @@
1
- const { addRecord, getRecordByFilter } = require('../services/airtableService');
2
1
  const { Product_ID } = require('../config/airtableConfig');
2
+
3
3
  const { logger } = require('../utils/logger');
4
4
 
5
+ const { addRecord, getRecordByFilter } = require('../services/airtableService');
6
+
5
7
  async function logCaseDocumentationToAirtable(reporter, whatsapp_id, tableName, dynamicFields = {}) {
6
8
  try {
7
9
  let patientId = null;