@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.
- package/examples/basic-usage.js +8 -0
- package/lib/adapters/BaileysProvider.js +2 -11
- package/lib/adapters/TwilioProvider.js +9 -77
- package/lib/adapters/registry.js +3 -4
- package/lib/assistants/BaseAssistant.js +9 -10
- package/lib/config/airtableConfig.js +1 -3
- package/lib/config/awsConfig.js +4 -4
- package/lib/config/configLoader.js +1 -1
- package/lib/config/llmConfig.js +6 -4
- package/lib/config/metaConfig.js +31 -0
- package/lib/config/mongoAuthConfig.js +3 -1
- package/lib/config/mongoConfig.js +1 -0
- package/lib/controllers/assistantController.js +5 -4
- package/lib/controllers/bugReportController.js +5 -2
- package/lib/controllers/caseDocumentationController.js +3 -1
- package/lib/controllers/conversationController.js +12 -36
- package/lib/controllers/interactionController.js +6 -3
- package/lib/controllers/mediaController.js +1 -2
- package/lib/controllers/messageController.js +10 -4
- package/lib/controllers/messageStatusController.js +2 -9
- package/lib/controllers/patientController.js +3 -1
- package/lib/controllers/qualityMessageController.js +2 -1
- package/lib/controllers/templateController.js +35 -72
- package/lib/controllers/templateFlowController.js +159 -87
- package/lib/controllers/threadController.js +2 -10
- package/lib/controllers/uploadController.js +2 -0
- package/lib/core/MessageProvider.js +0 -3
- package/lib/core/NexusMessaging.js +18 -150
- package/lib/helpers/assistantHelper.js +6 -4
- package/lib/helpers/baileysHelper.js +7 -8
- package/lib/helpers/filesHelper.js +7 -4
- package/lib/helpers/llmsHelper.js +3 -16
- package/lib/helpers/mediaHelper.js +2 -0
- package/lib/helpers/messageHelper.js +3 -5
- package/lib/helpers/messageStatusHelper.js +5 -12
- package/lib/helpers/metaFlowHelper.js +13 -0
- package/lib/helpers/mongoHelper.js +1 -4
- package/lib/helpers/processHelper.js +5 -13
- package/lib/helpers/qrHelper.js +2 -2
- package/lib/helpers/templateFlowControllerHelper.js +93 -0
- package/lib/helpers/templateRecoveryHelper.js +5 -6
- package/lib/helpers/threadHelper.js +6 -3
- package/lib/helpers/threadRecoveryHelper.js +5 -3
- package/lib/helpers/twilioHelper.js +1 -7
- package/lib/helpers/twilioMediaProcessor.js +9 -6
- package/lib/helpers/whatsappHelper.js +1 -3
- package/lib/index.js +11 -96
- package/lib/memory/DefaultMemoryManager.js +9 -6
- package/lib/memory/MemoryManager.js +3 -3
- package/lib/middleware/requestId.js +2 -0
- package/lib/models/interactionModel.js +2 -0
- package/lib/models/messageModel.js +11 -20
- package/lib/models/predictionMetricsModel.js +2 -0
- package/lib/models/qualityMessageModel.js +2 -0
- package/lib/models/templateModel.js +0 -1
- package/lib/observability/index.js +2 -15
- package/lib/providers/OpenAIAssistantsProvider.js +4 -17
- package/lib/providers/OpenAIResponsesProvider.js +18 -31
- package/lib/providers/OpenAIResponsesProviderTools.js +1 -10
- package/lib/providers/{createProvider.js → createLLMProvider.js} +6 -8
- package/lib/routes/index.js +2 -8
- package/lib/services/airtableService.js +1 -5
- package/lib/services/assistantResolver.js +1 -0
- package/lib/services/assistantService.js +2 -13
- package/lib/services/assistantServiceCore.js +16 -17
- package/lib/services/conversationService.js +4 -5
- package/lib/services/metaFlowService.js +71 -0
- package/lib/services/metaService.js +169 -0
- package/lib/services/twilioService.js +0 -5
- package/lib/storage/MongoStorage.js +2 -4
- package/lib/storage/NoopStorage.js +0 -4
- package/lib/storage/registry.js +0 -3
- package/lib/templates/templateStructure.js +0 -3
- package/lib/utils/logger.js +0 -6
- package/lib/utils/messageParser.js +2 -33
- package/lib/utils/outputSanitizer.js +0 -4
- package/lib/utils/retryHelper.js +0 -25
- package/lib/utils/scheduleUtils.js +1 -3
- package/lib/utils/tracingDecorator.js +2 -7
- package/package.json +2 -1
- package/lib/assistants/index.js +0 -5
- package/lib/interactive/index.js +0 -86
- package/lib/interactive/registry.js +0 -31
- package/lib/interactive/twilioMapper.js +0 -61
- package/lib/models/index.js +0 -15
- package/lib/storage/index.js +0 -5
package/examples/basic-usage.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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) {
|
package/lib/adapters/registry.js
CHANGED
|
@@ -12,18 +12,17 @@ function getProvider(name) {
|
|
|
12
12
|
return _providers.get(String(name || '').toLowerCase());
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
const {
|
|
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 =
|
|
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 =
|
|
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,
|
|
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.');
|
package/lib/config/awsConfig.js
CHANGED
|
@@ -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,
|
package/lib/config/llmConfig.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
const runtimeConfig = require('
|
|
1
|
+
const runtimeConfig = require('../config/runtimeConfig');
|
|
2
|
+
|
|
2
3
|
const { logger } = require('../utils/logger');
|
|
3
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
5
|
+
|
|
4
6
|
const { logger } = require('../utils/logger');
|
|
5
7
|
|
|
6
8
|
async function connectToMongo(mongoClient) {
|
|
@@ -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;
|