@peopl-health/nexus 1.1.8 → 1.3.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.
- package/MIGRATION_GUIDE.md +71 -379
- package/README.md +80 -559
- package/lib/adapters/BaileysProvider.js +25 -1
- package/lib/adapters/TwilioProvider.js +107 -0
- package/lib/adapters/registry.js +29 -0
- package/lib/config/configLoader.js +38 -0
- package/lib/controllers/assistantController.js +3 -3
- package/lib/controllers/messageController.js +31 -1
- package/lib/core/NexusMessaging.js +151 -35
- package/lib/helpers/assistantHelper.js +6 -6
- package/lib/helpers/baileysHelper.js +3 -3
- package/lib/helpers/twilioHelper.js +3 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +84 -9
- package/lib/interactive/index.js +86 -0
- package/lib/interactive/registry.js +31 -0
- package/lib/interactive/twilioMapper.js +60 -0
- package/lib/models/messageModel.js +8 -0
- package/lib/routes/index.js +3 -1
- package/lib/services/airtableService.js +4 -0
- package/lib/services/assistantService.js +16 -2
- package/lib/storage/NoopStorage.js +19 -0
- package/lib/storage/registry.js +31 -0
- package/lib/templates/predefinedTemplates.js +1 -3
- package/lib/utils/defaultLLMProvider.js +1 -1
- package/lib/utils/index.js +3 -5
- package/package.json +2 -1
package/lib/index.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
const { NexusMessaging } = require('./core/NexusMessaging');
|
|
2
|
-
const { TwilioProvider } = require('./adapters/TwilioProvider');
|
|
3
|
-
const { BaileysProvider } = require('./adapters/BaileysProvider');
|
|
4
2
|
const { MongoStorage } = require('./storage/MongoStorage');
|
|
5
3
|
const { MessageParser } = require('./utils/messageParser');
|
|
6
4
|
const { DefaultLLMProvider } = require('./utils/defaultLLMProvider');
|
|
5
|
+
const { loadNexusConfig } = require('./config/configLoader');
|
|
6
|
+
const templateController = require('./controllers/templateController');
|
|
7
|
+
const templateFlowController = require('./controllers/templateFlowController');
|
|
8
|
+
const interactive = require('./interactive');
|
|
9
|
+
const {
|
|
10
|
+
configureLLMProvider: configureAssistantsLLM,
|
|
11
|
+
registerAssistant,
|
|
12
|
+
overrideGetAssistantById,
|
|
13
|
+
configureAssistants: setAssistantsConfig
|
|
14
|
+
} = require('./services/assistantService');
|
|
15
|
+
const { TwilioProvider } = require('./adapters/TwilioProvider');
|
|
16
|
+
const { BaileysProvider } = require('./adapters/BaileysProvider');
|
|
7
17
|
|
|
8
18
|
/**
|
|
9
19
|
* Main Nexus class that orchestrates all components
|
|
@@ -40,17 +50,50 @@ class Nexus {
|
|
|
40
50
|
parser = 'MessageParser',
|
|
41
51
|
parserConfig = {},
|
|
42
52
|
llm = 'openai',
|
|
43
|
-
llmConfig = {}
|
|
53
|
+
llmConfig = {},
|
|
54
|
+
assistants: assistantsOpt = undefined,
|
|
55
|
+
assistant: assistantOpt = undefined
|
|
44
56
|
} = options;
|
|
45
57
|
|
|
46
58
|
// Initialize messaging provider
|
|
47
59
|
await this.messaging.initializeProvider(provider, providerConfig);
|
|
48
60
|
|
|
61
|
+
// Auto-configure template controllers with active provider when Twilio is used
|
|
62
|
+
try {
|
|
63
|
+
const activeProvider = typeof this.messaging.getProvider === 'function'
|
|
64
|
+
? this.messaging.getProvider()
|
|
65
|
+
: null;
|
|
66
|
+
if (activeProvider && activeProvider.constructor && activeProvider.constructor.name === 'TwilioProvider') {
|
|
67
|
+
if (typeof templateController.configureNexusProvider === 'function') {
|
|
68
|
+
templateController.configureNexusProvider(activeProvider);
|
|
69
|
+
}
|
|
70
|
+
if (typeof templateFlowController.configureNexusProvider === 'function') {
|
|
71
|
+
templateFlowController.configureNexusProvider(activeProvider);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.warn('Warning: failed to auto-configure template providers:', e?.message || e);
|
|
76
|
+
}
|
|
77
|
+
|
|
49
78
|
// Initialize storage if provided
|
|
50
|
-
if (storage
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
if (storage) {
|
|
80
|
+
try {
|
|
81
|
+
const { createStorage } = require('./storage/registry');
|
|
82
|
+
if (typeof storage === 'string') {
|
|
83
|
+
this.storage = createStorage(storage, storageConfig || {});
|
|
84
|
+
} else if (typeof storage === 'object' && (storage.saveMessage || storage.saveInteractive)) {
|
|
85
|
+
this.storage = storage; // external adapter instance
|
|
86
|
+
} else {
|
|
87
|
+
// default to mongo if truthy but not recognized
|
|
88
|
+
this.storage = createStorage('mongo', storageConfig || {});
|
|
89
|
+
}
|
|
90
|
+
if (this.storage && typeof this.storage.connect === 'function') {
|
|
91
|
+
await this.storage.connect();
|
|
92
|
+
}
|
|
93
|
+
this.messaging.setMessageStorage(this.storage);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.warn('Warning: storage initialization failed:', e?.message || e);
|
|
96
|
+
}
|
|
54
97
|
}
|
|
55
98
|
|
|
56
99
|
// Initialize message parser if provided
|
|
@@ -63,6 +106,31 @@ class Nexus {
|
|
|
63
106
|
this.llmProvider = new DefaultLLMProvider(llmConfig);
|
|
64
107
|
}
|
|
65
108
|
|
|
109
|
+
|
|
110
|
+
// Configure Assistants (registry + overrides)
|
|
111
|
+
const assistantsConfig = assistantsOpt || assistantOpt;
|
|
112
|
+
try {
|
|
113
|
+
if (this.llmProvider && typeof configureAssistantsLLM === 'function') {
|
|
114
|
+
// Provide the raw OpenAI client to the assistant service
|
|
115
|
+
configureAssistantsLLM(this.llmProvider.getClient());
|
|
116
|
+
}
|
|
117
|
+
if (assistantsConfig) {
|
|
118
|
+
if (assistantsConfig.registry && typeof assistantsConfig.registry === 'object') {
|
|
119
|
+
for (const [id, AssistantClass] of Object.entries(assistantsConfig.registry)) {
|
|
120
|
+
registerAssistant(id, AssistantClass);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (typeof assistantsConfig.getAssistantById === 'function') {
|
|
124
|
+
overrideGetAssistantById(assistantsConfig.getAssistantById);
|
|
125
|
+
}
|
|
126
|
+
if (typeof setAssistantsConfig === 'function') {
|
|
127
|
+
setAssistantsConfig(assistantsConfig);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.warn('Warning: failed to configure assistants:', e?.message || e);
|
|
132
|
+
}
|
|
133
|
+
|
|
66
134
|
this.isInitialized = true;
|
|
67
135
|
}
|
|
68
136
|
|
|
@@ -193,7 +261,14 @@ module.exports = {
|
|
|
193
261
|
MessageParser,
|
|
194
262
|
DefaultLLMProvider,
|
|
195
263
|
routes,
|
|
196
|
-
// Direct access to route utilities for convenience
|
|
197
264
|
setupDefaultRoutes: routes.setupDefaultRoutes,
|
|
198
|
-
createRouter: routes.createRouter
|
|
265
|
+
createRouter: routes.createRouter,
|
|
266
|
+
loadNexusConfig,
|
|
267
|
+
interactive,
|
|
268
|
+
registerFlow: interactive.registerFlow,
|
|
269
|
+
getFlow: interactive.getFlow,
|
|
270
|
+
listFlows: interactive.listFlows,
|
|
271
|
+
sendInteractive: interactive.sendInteractive,
|
|
272
|
+
registerInteractiveHandler: interactive.registerInteractiveHandler,
|
|
273
|
+
attachInteractiveRouter: interactive.attachInteractiveRouter
|
|
199
274
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
|
|
2
|
+
const { toTwilioContent } = require('./twilioMapper');
|
|
3
|
+
const { registerFlow, getFlow, listFlows, registerInteractiveHandler, listInteractiveHandlers } = require('./registry');
|
|
4
|
+
|
|
5
|
+
async function sendInteractive(nexusOrMessaging, params) {
|
|
6
|
+
const { to, spec, id, variables } = params || {};
|
|
7
|
+
if (!nexusOrMessaging) throw new Error('sendInteractive requires a Nexus or NexusMessaging instance');
|
|
8
|
+
const messaging = typeof nexusOrMessaging.getMessaging === 'function' ? nexusOrMessaging.getMessaging() : nexusOrMessaging;
|
|
9
|
+
const provider = typeof messaging.getProvider === 'function' ? messaging.getProvider() : null;
|
|
10
|
+
if (!provider) throw new Error('No active provider');
|
|
11
|
+
|
|
12
|
+
const useSpec = spec || (id ? getFlow(id) : null);
|
|
13
|
+
if (!useSpec) throw new Error('Interactive spec not found');
|
|
14
|
+
|
|
15
|
+
// If user supplied a contentSid directly in spec, just send it
|
|
16
|
+
if (useSpec.contentSid) {
|
|
17
|
+
return await provider.sendMessage({ to, contentSid: useSpec.contentSid, variables });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Twilio mapping
|
|
21
|
+
if (provider.constructor && provider.constructor.name === 'TwilioProvider') {
|
|
22
|
+
const content = toTwilioContent(useSpec);
|
|
23
|
+
const created = await provider.createTemplate(content);
|
|
24
|
+
return await provider.sendMessage({ to, contentSid: created.sid, variables });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Baileys or others: not supported yet
|
|
28
|
+
throw new Error('Interactive/flows not supported for this provider');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
registerFlow,
|
|
33
|
+
getFlow,
|
|
34
|
+
listFlows,
|
|
35
|
+
sendInteractive,
|
|
36
|
+
registerInteractiveHandler,
|
|
37
|
+
attachInteractiveRouter
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
function _matchInteractive(match, interactive) {
|
|
42
|
+
if (!match) return true;
|
|
43
|
+
if (!interactive) return false;
|
|
44
|
+
// Type match (button, list, flow)
|
|
45
|
+
if (match.type && String(match.type).toLowerCase() !== String(interactive.type || '').toLowerCase()) return false;
|
|
46
|
+
// ID match (e.g., ListId)
|
|
47
|
+
if (match.id) {
|
|
48
|
+
const targetId = interactive.id || interactive.payload || interactive.title || '';
|
|
49
|
+
if (match.id instanceof RegExp) {
|
|
50
|
+
if (!match.id.test(String(targetId))) return false;
|
|
51
|
+
} else if (String(match.id) != String(targetId)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Payload includes
|
|
56
|
+
if (match.payloadIncludes) {
|
|
57
|
+
const blob = JSON.stringify(interactive);
|
|
58
|
+
if (!blob.includes(match.payloadIncludes)) return false;
|
|
59
|
+
}
|
|
60
|
+
// Custom predicate
|
|
61
|
+
if (typeof match.predicate === 'function' && !match.predicate(interactive)) return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function attachInteractiveRouter(nexusOrMessaging) {
|
|
66
|
+
const messaging = typeof nexusOrMessaging.getMessaging === 'function' ? nexusOrMessaging.getMessaging() : nexusOrMessaging;
|
|
67
|
+
const bus = typeof messaging.getEventBus === 'function' ? messaging.getEventBus() : null;
|
|
68
|
+
if (!bus) throw new Error('Interactive router requires event bus support');
|
|
69
|
+
|
|
70
|
+
const handler = async (messageData) => {
|
|
71
|
+
const interactive = messageData && messageData.interactive ? messageData.interactive : null;
|
|
72
|
+
const items = listInteractiveHandlers();
|
|
73
|
+
for (const { match, handler } of items) {
|
|
74
|
+
try {
|
|
75
|
+
if (_matchInteractive(match, interactive)) {
|
|
76
|
+
await handler(messageData, messaging);
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.warn('Interactive handler error:', e && e.message || e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
bus.on('interactive:received', handler);
|
|
85
|
+
return () => bus.off && bus.off('interactive:received', handler);
|
|
86
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
// Simple in-memory registry for interactive specs (flows, quick replies)
|
|
3
|
+
const _flows = new Map();
|
|
4
|
+
const _handlers = [];
|
|
5
|
+
|
|
6
|
+
function registerFlow(id, spec) {
|
|
7
|
+
if (!id || typeof id !== 'string') throw new Error('registerFlow requires id');
|
|
8
|
+
if (!spec || typeof spec !== 'object') throw new Error('registerFlow requires spec object');
|
|
9
|
+
_flows.set(id, spec);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getFlow(id) {
|
|
13
|
+
return _flows.get(id) || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function listFlows() {
|
|
17
|
+
return Array.from(_flows.entries()).map(([id, spec]) => ({ id, spec }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { registerFlow, getFlow, listFlows, registerInteractiveHandler, listInteractiveHandlers };
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
function registerInteractiveHandler(match, handler) {
|
|
24
|
+
if (typeof handler !== 'function') throw new Error('Handler must be a function');
|
|
25
|
+
const m = match || {};
|
|
26
|
+
_handlers.push({ match: m, handler });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function listInteractiveHandlers() {
|
|
30
|
+
return _handlers.slice();
|
|
31
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
// Map a provider-agnostic interactive spec to Twilio Content API payload
|
|
3
|
+
|
|
4
|
+
function toTwilioContent(spec) {
|
|
5
|
+
if (!spec || typeof spec !== 'object') throw new Error('Interactive spec must be an object');
|
|
6
|
+
const { type = 'text', language = 'es', body = '', footer, variables, buttons, flow } = spec;
|
|
7
|
+
|
|
8
|
+
const content = {
|
|
9
|
+
friendly_name: spec.friendlyName || (spec.name || ('interactive_' + Date.now())),
|
|
10
|
+
language,
|
|
11
|
+
variables: {},
|
|
12
|
+
types: {}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Build variables map from array/object
|
|
16
|
+
if (Array.isArray(variables)) {
|
|
17
|
+
variables.forEach((v, idx) => {
|
|
18
|
+
content.variables[String(idx + 1)] = typeof v === 'string' ? v : (v.example || '');
|
|
19
|
+
});
|
|
20
|
+
} else if (variables && typeof variables === 'object') {
|
|
21
|
+
Object.keys(variables).forEach(k => {
|
|
22
|
+
content.variables[k] = variables[k];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (type === 'flow') {
|
|
27
|
+
content.types['twilio/flows'] = {
|
|
28
|
+
body: flow?.body || body || '',
|
|
29
|
+
button_text: flow?.buttonText || spec.buttonText || undefined,
|
|
30
|
+
subtitle: flow?.subtitle || spec.subtitle || undefined,
|
|
31
|
+
pages: flow?.pages || spec.pages || []
|
|
32
|
+
};
|
|
33
|
+
} else if (type === 'quick-reply') {
|
|
34
|
+
const actions = (buttons || []).slice(0, 3).map((b, i) => ({
|
|
35
|
+
title: b.text || b.title || ('Button ' + (i + 1)),
|
|
36
|
+
id: b.id || ('button_' + (i + 1))
|
|
37
|
+
}));
|
|
38
|
+
content.types['twilio/quick-reply'] = {
|
|
39
|
+
body: body || '',
|
|
40
|
+
actions
|
|
41
|
+
};
|
|
42
|
+
// Also include base text body type
|
|
43
|
+
content.types['twilio/text'] = { body: body || '' };
|
|
44
|
+
} else {
|
|
45
|
+
// Plain text
|
|
46
|
+
content.types['twilio/text'] = { body: body || '' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (footer) {
|
|
50
|
+
['twilio/text', 'twilio/quick-reply'].forEach(t => {
|
|
51
|
+
if (content.types[t]) {
|
|
52
|
+
content.types[t].footer = footer;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return content;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { toTwilioContent };
|
|
@@ -51,6 +51,12 @@ messageSchema.pre('save', function (next) {
|
|
|
51
51
|
|
|
52
52
|
const Message = mongoose.model('Message', messageSchema);
|
|
53
53
|
|
|
54
|
+
async function insertMessage(values) {
|
|
55
|
+
const msg = new Message(values);
|
|
56
|
+
await msg.save();
|
|
57
|
+
return msg;
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
function formatTimestamp(unixTimestamp) {
|
|
55
61
|
const date = new Date(unixTimestamp * 1000);
|
|
56
62
|
return date.toLocaleString('sv-MX', {
|
|
@@ -86,6 +92,8 @@ function getMessageValues(message, content, reply, is_media) {
|
|
|
86
92
|
|
|
87
93
|
module.exports = {
|
|
88
94
|
Message,
|
|
95
|
+
// Backward-compatible helper used by helpers
|
|
96
|
+
insertMessage,
|
|
89
97
|
getMessageValues,
|
|
90
98
|
formatTimestamp
|
|
91
99
|
};
|
package/lib/routes/index.js
CHANGED
|
@@ -31,7 +31,8 @@ const mediaRouteDefinitions = {
|
|
|
31
31
|
const messageRouteDefinitions = {
|
|
32
32
|
'POST /send': 'sendMessageController',
|
|
33
33
|
'POST /send-bulk': 'sendBulkMessageController',
|
|
34
|
-
'POST /send-bulk-airtable': 'sendBulkMessageAirtableController'
|
|
34
|
+
'POST /send-bulk-airtable': 'sendBulkMessageAirtableController',
|
|
35
|
+
'POST /get-last': 'getLastInteractionController'
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const templateRouteDefinitions = {
|
|
@@ -103,6 +104,7 @@ const builtInControllers = {
|
|
|
103
104
|
sendMessageController: messageController.sendMessageController,
|
|
104
105
|
sendBulkMessageController: messageController.sendBulkMessageController,
|
|
105
106
|
sendBulkMessageAirtableController: messageController.sendBulkMessageAirtableController,
|
|
107
|
+
getLastInteractionController: messageController.getLastInteractionController,
|
|
106
108
|
|
|
107
109
|
// Template controllers
|
|
108
110
|
createTemplate: templateController.createTemplate,
|
|
@@ -3,6 +3,7 @@ const { airtable } = require('../config/airtableConfig');
|
|
|
3
3
|
|
|
4
4
|
async function addRecord(baseID, tableName, fields) {
|
|
5
5
|
try {
|
|
6
|
+
if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
|
|
6
7
|
const base = airtable.base(baseID);
|
|
7
8
|
const record = await base(tableName).create(fields);
|
|
8
9
|
console.log('Record added at', tableName);
|
|
@@ -17,6 +18,7 @@ async function addRecord(baseID, tableName, fields) {
|
|
|
17
18
|
async function getRecords(baseID, tableName) {
|
|
18
19
|
try {
|
|
19
20
|
const records = [];
|
|
21
|
+
if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
|
|
20
22
|
const base = airtable.base(baseID);
|
|
21
23
|
await base(tableName).select({
|
|
22
24
|
maxRecords: 3
|
|
@@ -36,6 +38,7 @@ async function getRecords(baseID, tableName) {
|
|
|
36
38
|
async function getRecordByFilter(baseID, tableName, filter, view = 'Grid view') {
|
|
37
39
|
try {
|
|
38
40
|
const records = [];
|
|
41
|
+
if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
|
|
39
42
|
const base = airtable.base(baseID);
|
|
40
43
|
await base(tableName).select({
|
|
41
44
|
filterByFormula: `${filter}`,
|
|
@@ -55,6 +58,7 @@ async function getRecordByFilter(baseID, tableName, filter, view = 'Grid view')
|
|
|
55
58
|
|
|
56
59
|
async function updateRecordByFilter(baseID, tableName, filter, updateFields) {
|
|
57
60
|
try {
|
|
61
|
+
if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
|
|
58
62
|
const base = airtable.base(baseID);
|
|
59
63
|
const updatedRecords = [];
|
|
60
64
|
|
|
@@ -7,6 +7,7 @@ const configureLLMProvider = (provider) => {
|
|
|
7
7
|
|
|
8
8
|
let assistantConfig = null;
|
|
9
9
|
let assistantRegistry = {};
|
|
10
|
+
let customGetAssistantById = null;
|
|
10
11
|
|
|
11
12
|
const { Message, formatTimestamp } = require('../models/messageModel.js');
|
|
12
13
|
const { Thread } = require('../models/threadModel.js');
|
|
@@ -26,9 +27,21 @@ const registerAssistant = (assistantId, AssistantClass) => {
|
|
|
26
27
|
assistantRegistry[assistantId] = AssistantClass;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
const overrideGetAssistantById = (resolverFn) => {
|
|
31
|
+
if (typeof resolverFn === 'function') {
|
|
32
|
+
customGetAssistantById = resolverFn;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
29
36
|
const getAssistantById = (assistant_id, thread) => {
|
|
37
|
+
if (customGetAssistantById) {
|
|
38
|
+
const inst = customGetAssistantById(assistant_id, thread);
|
|
39
|
+
if (inst) return inst;
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
if (!assistantConfig) {
|
|
31
|
-
|
|
43
|
+
// Provide a more permissive default: allow registry usage without full config
|
|
44
|
+
assistantConfig = {};
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
const AssistantClass = assistantRegistry[assistant_id];
|
|
@@ -288,5 +301,6 @@ module.exports = {
|
|
|
288
301
|
switchAssistant,
|
|
289
302
|
configureAssistants,
|
|
290
303
|
registerAssistant,
|
|
291
|
-
configureLLMProvider
|
|
304
|
+
configureLLMProvider,
|
|
305
|
+
overrideGetAssistantById
|
|
292
306
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Noop storage adapter - implements the storage interface but does nothing.
|
|
4
|
+
*/
|
|
5
|
+
class NoopStorage {
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
async connect() {}
|
|
10
|
+
async saveMessage() { return null; }
|
|
11
|
+
async saveInteractive() { return null; }
|
|
12
|
+
async getMessages() { return []; }
|
|
13
|
+
async getThread() { return null; }
|
|
14
|
+
async createThread() { return null; }
|
|
15
|
+
async updateThread() { return null; }
|
|
16
|
+
async disconnect() {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { NoopStorage };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
const { MongoStorage } = require('./MongoStorage');
|
|
3
|
+
const { NoopStorage } = require('./NoopStorage');
|
|
4
|
+
|
|
5
|
+
const _storages = new Map();
|
|
6
|
+
|
|
7
|
+
function registerStorage(name, StorageClassOrFactory) {
|
|
8
|
+
if (!name || !StorageClassOrFactory) throw new Error('registerStorage requires a name and class/factory');
|
|
9
|
+
_storages.set(String(name).toLowerCase(), StorageClassOrFactory);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getStorage(name) {
|
|
13
|
+
return _storages.get(String(name || '').toLowerCase());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createStorage(name, config) {
|
|
17
|
+
const Entry = getStorage(name);
|
|
18
|
+
if (!Entry) throw new Error(`Unsupported storage: ${name}`);
|
|
19
|
+
if (typeof Entry === 'function' && /^class\s/.test(Function.prototype.toString.call(Entry))) {
|
|
20
|
+
return new Entry(config || {});
|
|
21
|
+
}
|
|
22
|
+
// factory function
|
|
23
|
+
if (typeof Entry === 'function') return Entry(config || {});
|
|
24
|
+
throw new Error('Invalid storage registry entry');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Register built-ins
|
|
28
|
+
registerStorage('mongo', MongoStorage);
|
|
29
|
+
registerStorage('noop', NoopStorage);
|
|
30
|
+
|
|
31
|
+
module.exports = { registerStorage, getStorage, createStorage };
|
package/lib/utils/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const logger = require('./logger');
|
|
1
|
+
const { DefaultLLMProvider } = require('./defaultLLMProvider');
|
|
2
|
+
const { MessageParser } = require('./messageParser');
|
|
3
|
+
const { logger } = require('./logger');
|
|
5
4
|
|
|
6
5
|
module.exports = {
|
|
7
|
-
AssistantManager,
|
|
8
6
|
DefaultLLMProvider,
|
|
9
7
|
MessageParser,
|
|
10
8
|
logger
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peopl-health/nexus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Core messaging and assistant library for WhatsApp communication platforms",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"build": "tsc",
|
|
35
35
|
"dev": "tsc --watch",
|
|
36
36
|
"test": "jest",
|
|
37
|
+
"test:inband": "jest --runInBand",
|
|
37
38
|
"lint": "eslint lib/**/*.js",
|
|
38
39
|
"prepublishOnly": "npm test && npm run lint",
|
|
39
40
|
"version": "npm run prepublishOnly && git add -A lib",
|