@peopl-health/nexus 1.5.2 → 1.5.3

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.
@@ -1,5 +1,4 @@
1
1
  const { airtable, getBase } = require('../config/airtableConfig');
2
- const { convertTwilioToInternalFormat } = require('../helpers/twilioHelper');
3
2
  const { replyAssistant } = require('../services/assistantService');
4
3
  const { createProvider } = require('../adapters/registry');
5
4
 
@@ -27,7 +26,15 @@ class NexusMessaging {
27
26
  onFlow: null
28
27
  };
29
28
  this.events = new EventEmitter();
30
- this.middleware = { any: [], message: [], interactive: [], media: [], command: [], keyword: [], flow: [] };
29
+ this.middleware = {
30
+ any: [],
31
+ message: [],
32
+ interactive: [],
33
+ media: [],
34
+ command: [],
35
+ keyword: [],
36
+ flow: []
37
+ };
31
38
  }
32
39
 
33
40
  /**
@@ -118,7 +125,11 @@ class NexusMessaging {
118
125
  * Internal helper to run middleware pipeline and the final handler
119
126
  */
120
127
  async _runPipeline(type, messageData, finalHandler) {
121
- const chain = [...(this.middleware.any || []), ...(this.middleware[type] || []), async (ctx) => { return finalHandler(ctx); }];
128
+ const chain = [
129
+ ...(this.middleware.any || []),
130
+ ...(this.middleware[type] || []),
131
+ async (ctx) => await finalHandler(ctx)
132
+ ];
122
133
  let idx = -1;
123
134
  const runner = async (i) => {
124
135
  if (i <= idx) throw new Error('next() called multiple times');
@@ -316,32 +327,69 @@ class NexusMessaging {
316
327
  }
317
328
  }
318
329
 
319
- async handleMessage(messageData) {
320
- this.events.emit && this.events.emit('message:received', messageData);
321
- const final = async (ctx) => {
322
- if (this.handlers.onMessage) {
323
- return await this.handlers.onMessage(ctx, this);
324
- } else {
325
- return await this.handleMessageWithAssistant(ctx);
330
+ async _handleWithPipeline(type, handlerKey, messageData, fallback) {
331
+ this.events.emit(`${type}:received`, messageData);
332
+ const result = await this._runPipeline(type, messageData, async (ctx) => {
333
+ const handler = this.handlers[handlerKey];
334
+ if (handler) {
335
+ return await handler(ctx, this);
326
336
  }
327
- };
328
- const result = await this._runPipeline('message', messageData, final);
329
- this.events.emit && this.events.emit('message:handled', messageData);
337
+ if (fallback) {
338
+ return await fallback(ctx);
339
+ }
340
+ });
341
+ this.events.emit(`${type}:handled`, messageData);
330
342
  return result;
331
343
  }
332
344
 
345
+ _extractAssistantInputs(messageData) {
346
+ if (!messageData || typeof messageData !== 'object') {
347
+ return { from: null, message: null };
348
+ }
349
+
350
+ const from = [
351
+ messageData.from,
352
+ messageData.sender,
353
+ messageData.numero,
354
+ messageData.code,
355
+ messageData.From,
356
+ messageData.key?.remoteJid,
357
+ messageData.raw?.from,
358
+ messageData.raw?.From,
359
+ messageData.raw?.key?.remoteJid
360
+ ].find((value) => typeof value === 'string' && value.trim().length > 0) || null;
361
+
362
+ const message = [
363
+ typeof messageData.message === 'string' ? messageData.message : null,
364
+ typeof messageData.message?.conversation === 'string' ? messageData.message.conversation : null,
365
+ typeof messageData.Body === 'string' ? messageData.Body : null,
366
+ typeof messageData.raw?.message?.conversation === 'string' ? messageData.raw.message.conversation : null,
367
+ typeof messageData.raw?.Body === 'string' ? messageData.raw.Body : null
368
+ ].find((value) => typeof value === 'string' && value.trim().length > 0) || null;
369
+
370
+ return { from, message };
371
+ }
372
+
373
+ async handleMessage(messageData) {
374
+ return await this._handleWithPipeline('message', 'onMessage', messageData, async (ctx) => {
375
+ return await this.handleMessageWithAssistant(ctx);
376
+ });
377
+ }
378
+
333
379
  async handleMessageWithAssistant(messageData) {
334
380
  try {
335
- console.log('MESSAGE DATA', messageData);
381
+ const { from, message } = this._extractAssistantInputs(messageData);
336
382
 
337
- const response = await replyAssistant(
338
- messageData.from,
339
- messageData.message
340
- );
383
+ if (!from || !message) {
384
+ console.warn('Unable to resolve assistant inputs from message, skipping automatic reply.');
385
+ return;
386
+ }
387
+
388
+ const response = await replyAssistant(from, message);
341
389
 
342
390
  if (response) {
343
391
  await this.sendMessage({
344
- to: messageData.from,
392
+ to: from,
345
393
  message: response
346
394
  });
347
395
  }
@@ -354,63 +402,23 @@ class NexusMessaging {
354
402
  if (this.messageStorage) {
355
403
  await this.messageStorage.saveInteractive(messageData);
356
404
  }
357
- this.events.emit && this.events.emit('interactive:received', messageData);
358
- const final = async (ctx) => {
359
- if (this.handlers.onInteractive) {
360
- return await this.handlers.onInteractive(ctx, this);
361
- }
362
- };
363
- const result = await this._runPipeline('interactive', messageData, final);
364
- this.events.emit && this.events.emit('interactive:handled', messageData);
365
- return result;
405
+ return await this._handleWithPipeline('interactive', 'onInteractive', messageData);
366
406
  }
367
407
 
368
408
  async handleMedia(messageData) {
369
- this.events.emit && this.events.emit('media:received', messageData);
370
- const final = async (ctx) => {
371
- if (this.handlers.onMedia) {
372
- return await this.handlers.onMedia(ctx, this);
373
- }
374
- };
375
- const result = await this._runPipeline('media', messageData, final);
376
- this.events.emit && this.events.emit('media:handled', messageData);
377
- return result;
409
+ return await this._handleWithPipeline('media', 'onMedia', messageData);
378
410
  }
379
411
 
380
412
  async handleCommand(messageData) {
381
- this.events.emit && this.events.emit('command:received', messageData);
382
- const final = async (ctx) => {
383
- if (this.handlers.onCommand) {
384
- return await this.handlers.onCommand(ctx, this);
385
- }
386
- };
387
- const result = await this._runPipeline('command', messageData, final);
388
- this.events.emit && this.events.emit('command:handled', messageData);
389
- return result;
413
+ return await this._handleWithPipeline('command', 'onCommand', messageData);
390
414
  }
391
415
 
392
416
  async handleKeyword(messageData) {
393
- this.events.emit && this.events.emit('keyword:received', messageData);
394
- const final = async (ctx) => {
395
- if (this.handlers.onKeyword) {
396
- return await this.handlers.onKeyword(ctx, this);
397
- }
398
- };
399
- const result = await this._runPipeline('keyword', messageData, final);
400
- this.events.emit && this.events.emit('keyword:handled', messageData);
401
- return result;
417
+ return await this._handleWithPipeline('keyword', 'onKeyword', messageData);
402
418
  }
403
419
 
404
420
  async handleFlow(messageData) {
405
- this.events.emit && this.events.emit('flow:received', messageData);
406
- const final = async (ctx) => {
407
- if (this.handlers.onFlow) {
408
- return await this.handlers.onFlow(ctx, this);
409
- }
410
- };
411
- const result = await this._runPipeline('flow', messageData, final);
412
- this.events.emit && this.events.emit('flow:handled', messageData);
413
- return result;
421
+ return await this._handleWithPipeline('flow', 'onFlow', messageData);
414
422
  }
415
423
 
416
424
  /**
package/lib/index.js CHANGED
@@ -67,10 +67,11 @@ class Nexus {
67
67
 
68
68
  // Auto-configure template controllers with active provider when Twilio is used
69
69
  try {
70
- const activeProvider = typeof this.messaging.getProvider === 'function'
71
- ? this.messaging.getProvider()
70
+ const activeProvider = typeof this.messaging.getProvider === 'function'
71
+ ? this.messaging.getProvider()
72
72
  : null;
73
- if (activeProvider && activeProvider.constructor && activeProvider.constructor.name === 'TwilioProvider') {
73
+
74
+ if (activeProvider instanceof TwilioProvider) {
74
75
  if (typeof templateController.configureNexusProvider === 'function') {
75
76
  templateController.configureNexusProvider(activeProvider);
76
77
  }
@@ -129,28 +130,24 @@ class Nexus {
129
130
  if (llm === 'openai') {
130
131
  this.llmProvider = new DefaultLLMProvider(llmConfig);
131
132
  try {
132
- if (this.llmProvider && typeof this.llmProvider.getClient === 'function') {
133
+ if (typeof this.llmProvider.getClient === 'function') {
133
134
  llmConfigModule.openaiClient = this.llmProvider.getClient();
134
135
  }
135
136
  } catch (err) {
136
137
  console.warn('[Nexus] Failed to expose OpenAI client:', err?.message || err);
137
138
  }
139
+ }
138
140
 
139
141
  // Convenience: handle common top-level config for mongo, media bucket, airtable
140
142
  try {
141
- // Mongo URI passthrough for storage=mongo
142
- if (options.mongoUri && storage === 'mongo') {
143
- if (typeof storageConfig === 'object') {
144
- storageConfig.mongoUri = storageConfig.mongoUri || options.mongoUri;
145
- }
143
+ if (options.mongoUri && storage === 'mongo' && typeof storageConfig === 'object') {
144
+ storageConfig.mongoUri = storageConfig.mongoUri || options.mongoUri;
146
145
  }
147
146
 
148
- // Media bucket (overrides storage setting)
149
147
  if (options.media && options.media.bucketName) {
150
148
  runtimeConfig.set('AWS_S3_BUCKET_NAME', options.media.bucketName);
151
149
  }
152
150
 
153
- // Airtable base default (accepts alias like 'calendar' or an ID)
154
151
  if (options.airtable && options.airtable.base) {
155
152
  runtimeConfig.set('AIRTABLE_BASE_ID', options.airtable.base);
156
153
  }
@@ -161,13 +158,11 @@ class Nexus {
161
158
  console.warn('[Nexus] convenience config warning:', cfgErr?.message || cfgErr);
162
159
  }
163
160
 
164
- }
165
-
166
161
 
167
162
  // Configure Assistants (registry + overrides)
168
163
  const assistantsConfig = assistantsOpt || assistantOpt;
169
164
  try {
170
- if (this.llmProvider && typeof configureAssistantsLLM === 'function') {
165
+ if (this.llmProvider && typeof configureAssistantsLLM === 'function' && typeof this.llmProvider.getClient === 'function') {
171
166
  // Provide the raw OpenAI client to the assistant service
172
167
  configureAssistantsLLM(this.llmProvider.getClient());
173
168
  }
@@ -56,6 +56,7 @@ const fetchConversationData = async (filter, skip, limit) => {
56
56
  nombre_whatsapp: 1,
57
57
  from_me: 1
58
58
  }},
59
+ { $sort: { createdAt: 1, timestamp: 1 } },
59
60
  { $group: {
60
61
  _id: '$numero',
61
62
  latestMessage: { $last: '$$ROOT' },
@@ -169,8 +170,10 @@ const fetchConversationData = async (filter, skip, limit) => {
169
170
  { $project: {
170
171
  numero: 1,
171
172
  from_me: 1,
172
- createdAt: 1
173
+ createdAt: 1,
174
+ timestamp: 1
173
175
  }},
176
+ { $sort: { createdAt: -1, timestamp: -1 } },
174
177
  { $group: {
175
178
  _id: '$numero',
176
179
  latestMessage: { $first: '$$ROOT' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "publishConfig": {
6
6
  "access": "public"