@peopl-health/nexus 2.4.7 → 2.4.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/assistants/DoctorScheduleAssistant.js +3 -2
- package/lib/adapters/BaileysProvider.js +5 -4
- package/lib/adapters/TwilioProvider.js +20 -19
- package/lib/assistants/BaseAssistant.js +7 -6
- package/lib/config/awsConfig.js +14 -12
- package/lib/config/llmConfig.js +3 -2
- package/lib/config/mongoAuthConfig.js +5 -5
- package/lib/controllers/assistantController.js +12 -11
- package/lib/controllers/bugReportController.js +6 -5
- package/lib/controllers/conversationController.js +72 -71
- package/lib/controllers/interactionController.js +7 -6
- package/lib/controllers/mediaController.js +15 -13
- package/lib/controllers/messageController.js +7 -6
- package/lib/controllers/patientController.js +2 -1
- package/lib/controllers/qualityMessageController.js +5 -4
- package/lib/controllers/templateController.js +11 -9
- package/lib/controllers/uploadController.js +3 -1
- package/lib/core/NexusMessaging.js +18 -18
- package/lib/helpers/assistantHelper.js +8 -9
- package/lib/helpers/baileysHelper.js +4 -3
- package/lib/helpers/filesHelper.js +9 -8
- package/lib/helpers/llmsHelper.js +17 -10
- package/lib/helpers/mediaHelper.js +3 -2
- package/lib/helpers/messageHelper.js +12 -11
- package/lib/helpers/processHelper.js +2 -2
- package/lib/helpers/qrHelper.js +2 -1
- package/lib/helpers/twilioMediaProcessor.js +19 -29
- package/lib/helpers/whatsappHelper.js +3 -2
- package/lib/index.js +11 -14
- package/lib/interactive/index.js +11 -11
- package/lib/middleware/requestId.js +9 -14
- package/lib/models/messageModel.js +5 -4
- package/lib/providers/OpenAIAssistantsProvider.js +10 -9
- package/lib/providers/OpenAIResponsesProvider.js +24 -17
- package/lib/providers/OpenAIResponsesProviderTools.js +3 -5
- package/lib/providers/createProvider.js +2 -1
- package/lib/services/airtableService.js +6 -5
- package/lib/services/assistantService.js +73 -57
- package/lib/services/conversationService.js +16 -16
- package/lib/services/preprocessingHooks.js +3 -1
- package/lib/storage/MongoStorage.js +14 -14
- package/lib/utils/errorHandler.js +3 -1
- package/lib/utils/logger.js +35 -3
- package/lib/utils/mediaValidator.js +18 -14
- package/lib/utils/sanitizer.js +0 -6
- package/lib/utils/tracingDecorator.js +7 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { OpenAIAssistantsProvider } = require('./OpenAIAssistantsProvider');
|
|
2
2
|
const { OpenAIResponsesProvider } = require('./OpenAIResponsesProvider');
|
|
3
|
+
const { logger } = require('../utils/logger');
|
|
3
4
|
|
|
4
5
|
const PROVIDER_VARIANTS = {
|
|
5
6
|
assistants: OpenAIAssistantsProvider,
|
|
@@ -10,7 +11,7 @@ const PROVIDER_VARIANTS = {
|
|
|
10
11
|
* Returns the appropriate OpenAI provider implementation for the requested variant.
|
|
11
12
|
*/
|
|
12
13
|
function createProvider(config = {}) {
|
|
13
|
-
|
|
14
|
+
logger.debug('Creating OpenAI provider', { variant: config.variant || 'assistants' });
|
|
14
15
|
const variant = (config.variant || config.providerVariant || config.llmVariant || 'assistants')
|
|
15
16
|
.toString()
|
|
16
17
|
.toLowerCase();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { airtable } = require('../config/airtableConfig');
|
|
2
|
+
const { logger } = require('../utils/logger');
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
async function addRecord(baseID, tableName, fields) {
|
|
@@ -6,10 +7,10 @@ async function addRecord(baseID, tableName, fields) {
|
|
|
6
7
|
if (!airtable) throw new Error('Airtable not configured. Set AIRTABLE_API_KEY');
|
|
7
8
|
const base = airtable.base(baseID);
|
|
8
9
|
const record = await base(tableName).create(fields);
|
|
9
|
-
|
|
10
|
+
logger.info('Record added at', tableName);
|
|
10
11
|
return record;
|
|
11
12
|
} catch (error) {
|
|
12
|
-
|
|
13
|
+
logger.error('Error adding record:', error);
|
|
13
14
|
throw error;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
@@ -30,7 +31,7 @@ async function getRecords(baseID, tableName) {
|
|
|
30
31
|
});
|
|
31
32
|
return records;
|
|
32
33
|
} catch (error) {
|
|
33
|
-
|
|
34
|
+
logger.error('Error fetching records:', error);
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -51,7 +52,7 @@ async function getRecordByFilter(baseID, tableName, filter, view = 'Grid view')
|
|
|
51
52
|
});
|
|
52
53
|
return records;
|
|
53
54
|
} catch (error) {
|
|
54
|
-
|
|
55
|
+
logger.error(`Error fetching records by ${filter}:`, error);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -74,7 +75,7 @@ async function updateRecordByFilter(baseID, tableName, filter, updateFields) {
|
|
|
74
75
|
|
|
75
76
|
return updatedRecords;
|
|
76
77
|
} catch (error) {
|
|
77
|
-
|
|
78
|
+
logger.error(`Error updating records by ${filter}:`, error);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -15,7 +15,7 @@ const { withTracing } = require('../utils/tracingDecorator.js');
|
|
|
15
15
|
const { processThreadMessage } = require('../helpers/processHelper.js');
|
|
16
16
|
const { getLastMessages, updateMessageRecord } = require('../helpers/messageHelper.js');
|
|
17
17
|
const { combineImagesToPDF, cleanupFiles } = require('../helpers/filesHelper.js');
|
|
18
|
-
const { logger } = require('../
|
|
18
|
+
const { logger } = require('../utils/logger');
|
|
19
19
|
|
|
20
20
|
const DEFAULT_MAX_RETRIES = parseInt(process.env.MAX_RETRIES || '30', 10);
|
|
21
21
|
|
|
@@ -136,9 +136,9 @@ const getAssistantById = (assistant_id, thread) => {
|
|
|
136
136
|
|
|
137
137
|
const createAssistant = async (code, assistant_id, messages=[], force=false) => {
|
|
138
138
|
const findThread = await Thread.findOne({ code: code });
|
|
139
|
-
|
|
139
|
+
logger.info('[createAssistant] findThread', findThread);
|
|
140
140
|
if (findThread && findThread.getConversationId()) {
|
|
141
|
-
|
|
141
|
+
logger.info('[createAssistant] Thread already exists');
|
|
142
142
|
const updateFields = { active: true, stopped: false };
|
|
143
143
|
Thread.setAssistantId(updateFields, assistant_id);
|
|
144
144
|
await Thread.updateOne({ code: code }, { $set: updateFields });
|
|
@@ -146,7 +146,7 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
const curRow = await getCurRow(Historial_Clinico_ID, code);
|
|
149
|
-
|
|
149
|
+
logger.info('[createAssistant] curRow', curRow[0]);
|
|
150
150
|
const nombre = curRow?.[0]?.['name'] || null;
|
|
151
151
|
const patientId = curRow?.[0]?.['record_id'] || null;
|
|
152
152
|
|
|
@@ -175,7 +175,7 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
|
|
|
175
175
|
const condition = { $or: [{ code: code }] };
|
|
176
176
|
const options = { new: true, upsert: true };
|
|
177
177
|
const updatedThread = await Thread.findOneAndUpdate(condition, {run_id: null, ...thread}, options);
|
|
178
|
-
|
|
178
|
+
logger.info('[createAssistant] Updated thread:', updatedThread);
|
|
179
179
|
|
|
180
180
|
// Delete previous thread
|
|
181
181
|
if (force) {
|
|
@@ -188,12 +188,12 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
|
|
|
188
188
|
const addMsgAssistant = async (code, inMessages, role = 'user', reply = false) => {
|
|
189
189
|
try {
|
|
190
190
|
const thread = await Thread.findOne({ code: code });
|
|
191
|
-
|
|
191
|
+
logger.info(thread);
|
|
192
192
|
if (thread === null) return null;
|
|
193
193
|
|
|
194
194
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
195
195
|
for (const message of inMessages) {
|
|
196
|
-
|
|
196
|
+
logger.info(message);
|
|
197
197
|
await provider.addMessage({
|
|
198
198
|
threadId: thread.getConversationId(),
|
|
199
199
|
role: role,
|
|
@@ -209,17 +209,17 @@ const addMsgAssistant = async (code, inMessages, role = 'user', reply = false) =
|
|
|
209
209
|
const assistant = getAssistantById(thread.getAssistantId(), thread);
|
|
210
210
|
do {
|
|
211
211
|
({ output, completed } = await runAssistantAndWait({ thread, assistant }));
|
|
212
|
-
|
|
212
|
+
logger.info(`Attempt ${retries + 1}: completed=${completed}, output=${output || '(empty)'}`);
|
|
213
213
|
|
|
214
214
|
if (completed && output) break;
|
|
215
215
|
if (retries < maxRetries) await new Promise(resolve => setTimeout(resolve, 2000));
|
|
216
216
|
retries++;
|
|
217
217
|
} while (retries <= maxRetries && (!completed || !output));
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
logger.info('THE ANS IS', output);
|
|
220
220
|
return output;
|
|
221
221
|
} catch (error) {
|
|
222
|
-
|
|
222
|
+
logger.info(error);
|
|
223
223
|
return null;
|
|
224
224
|
}
|
|
225
225
|
};
|
|
@@ -254,7 +254,7 @@ const addInstructionCore = async (code, instruction, role = 'user') => {
|
|
|
254
254
|
null // no patientReply for instructions
|
|
255
255
|
);
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
logger.info('RUN RESPONSE', output);
|
|
258
258
|
return output;
|
|
259
259
|
};
|
|
260
260
|
|
|
@@ -273,33 +273,48 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
273
273
|
const timings = {};
|
|
274
274
|
const startTotal = Date.now();
|
|
275
275
|
|
|
276
|
-
|
|
277
|
-
|
|
276
|
+
const { result: thread, duration: getThreadMs } = await withTracing(
|
|
277
|
+
getThread,
|
|
278
|
+
'get_thread_operation',
|
|
278
279
|
(threadCode) => ({
|
|
279
280
|
'thread.code': threadCode,
|
|
280
281
|
'operation.type': 'thread_retrieval',
|
|
281
282
|
'thread.provided': !!thread_
|
|
282
|
-
})
|
|
283
|
+
}),
|
|
284
|
+
{ returnTiming: true }
|
|
283
285
|
)(code);
|
|
284
|
-
timings.
|
|
286
|
+
timings.get_thread_ms = getThreadMs;
|
|
285
287
|
|
|
286
|
-
if (!thread) return null;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const patientReply = await
|
|
290
|
-
|
|
288
|
+
if (!thread_ && !thread) return null;
|
|
289
|
+
const finalThread = thread_ || thread;
|
|
290
|
+
|
|
291
|
+
const { result: patientReply, duration: getMessagesMs } = await withTracing(
|
|
292
|
+
getLastMessages,
|
|
293
|
+
'get_last_messages',
|
|
294
|
+
(code) => ({ 'thread.code': code }),
|
|
295
|
+
{ returnTiming: true }
|
|
296
|
+
)(code);
|
|
297
|
+
timings.get_messages_ms = getMessagesMs;
|
|
291
298
|
|
|
292
299
|
if (!patientReply) {
|
|
293
|
-
logger.
|
|
300
|
+
logger.info('[replyAssistantCore] No relevant data found for this assistant.');
|
|
294
301
|
return null;
|
|
295
302
|
}
|
|
296
303
|
|
|
297
304
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
298
305
|
|
|
299
|
-
|
|
300
|
-
logger.log(`[replyAssistantCore] Processing ${patientReply.length} messages in parallel`);
|
|
306
|
+
logger.info(`[replyAssistantCore] Processing ${patientReply.length} messages in parallel`);
|
|
301
307
|
|
|
302
|
-
const processResults = await
|
|
308
|
+
const { result: processResults, duration: processMessagesMs } = await withTracing(
|
|
309
|
+
processThreadMessage,
|
|
310
|
+
'process_thread_messages',
|
|
311
|
+
(code, patientReply, provider) => ({
|
|
312
|
+
'messages.count': patientReply.length,
|
|
313
|
+
'thread.code': code
|
|
314
|
+
}),
|
|
315
|
+
{ returnTiming: true }
|
|
316
|
+
)(code, patientReply, provider);
|
|
317
|
+
timings.process_messages_ms = processMessagesMs;
|
|
303
318
|
|
|
304
319
|
const patientMsg = processResults.some(r => r.isPatient);
|
|
305
320
|
const urls = processResults.filter(r => r.url).map(r => ({ url: r.url }));
|
|
@@ -307,22 +322,28 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
307
322
|
const allTempFiles = processResults.flatMap(r => r.tempFiles || []);
|
|
308
323
|
|
|
309
324
|
if (allMessagesToAdd.length > 0) {
|
|
310
|
-
const threadId =
|
|
311
|
-
logger.
|
|
325
|
+
const threadId = finalThread.getConversationId();
|
|
326
|
+
logger.info(`[replyAssistantCore] Adding ${allMessagesToAdd.length} messages to thread in batch`);
|
|
312
327
|
await provider.addMessage({ threadId, messages: allMessagesToAdd });
|
|
313
328
|
}
|
|
314
329
|
|
|
315
|
-
await Promise.all(processResults.map(r => updateMessageRecord(r.reply,
|
|
330
|
+
await Promise.all(processResults.map(r => updateMessageRecord(r.reply, finalThread)));
|
|
316
331
|
await cleanupFiles(allTempFiles);
|
|
317
332
|
|
|
318
|
-
timings.processMessages = Date.now() - timings.processMessages;
|
|
319
|
-
|
|
320
333
|
if (urls.length > 0) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
334
|
+
logger.info(`[replyAssistantCore] Processing ${urls.length} URLs for PDF combination`);
|
|
335
|
+
const { result: pdfResult, duration: pdfCombinationMs } = await withTracing(
|
|
336
|
+
combineImagesToPDF,
|
|
337
|
+
'combine_images_to_pdf',
|
|
338
|
+
({ code }) => ({
|
|
339
|
+
'pdf.thread_code': code,
|
|
340
|
+
'pdf.url_count': urls.length
|
|
341
|
+
}),
|
|
342
|
+
{ returnTiming: true }
|
|
343
|
+
)({ code });
|
|
344
|
+
timings.pdf_combination_ms = pdfCombinationMs;
|
|
345
|
+
const { pdfBuffer, processedFiles } = pdfResult;
|
|
346
|
+
logger.info(`[replyAssistantCore] PDF combination complete: ${processedFiles?.length || 0} files processed`);
|
|
326
347
|
|
|
327
348
|
if (pdfBuffer) {
|
|
328
349
|
const key = `${code}-${Date.now()}-combined.pdf`;
|
|
@@ -337,48 +358,43 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
337
358
|
}
|
|
338
359
|
}
|
|
339
360
|
|
|
340
|
-
if (!patientMsg ||
|
|
361
|
+
if (!patientMsg || finalThread.stopped) return null;
|
|
341
362
|
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
const { run, output, completed, retries, predictionTimeMs } = await withTracing(
|
|
363
|
+
const assistant = getAssistantById(finalThread.getAssistantId(), finalThread);
|
|
364
|
+
const { result: runResult, duration: runAssistantMs } = await withTracing(
|
|
345
365
|
runAssistantWithRetries,
|
|
346
366
|
'run_assistant_with_retries',
|
|
347
367
|
(thread, assistant, runConfig, patientReply) => ({
|
|
348
368
|
'assistant.id': thread.getAssistantId(),
|
|
349
369
|
'assistant.max_retries': DEFAULT_MAX_RETRIES,
|
|
350
370
|
'assistant.has_patient_reply': !!patientReply
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
timings.
|
|
371
|
+
}),
|
|
372
|
+
{ returnTiming: true }
|
|
373
|
+
)(finalThread, assistant, runOptions, patientReply);
|
|
374
|
+
timings.run_assistant_ms = runAssistantMs;
|
|
375
|
+
timings.total_ms = Date.now() - startTotal;
|
|
376
|
+
|
|
377
|
+
const { run, output, completed, retries, predictionTimeMs } = runResult;
|
|
355
378
|
|
|
356
|
-
logger.
|
|
379
|
+
logger.info('[Assistant Reply Complete]', {
|
|
357
380
|
code: code ? `${code.substring(0, 3)}***${code.slice(-4)}` : 'unknown',
|
|
358
381
|
messageCount: patientReply.length,
|
|
359
382
|
hasMedia: urls.length > 0,
|
|
360
383
|
retries,
|
|
361
|
-
|
|
384
|
+
totalMs: timings.total_ms
|
|
362
385
|
});
|
|
363
386
|
|
|
364
387
|
if (output && predictionTimeMs) {
|
|
365
388
|
await PredictionMetrics.create({
|
|
366
389
|
message_id: `${code}-${Date.now()}`,
|
|
367
390
|
numero: code,
|
|
368
|
-
assistant_id:
|
|
369
|
-
thread_id:
|
|
391
|
+
assistant_id: finalThread.getAssistantId(),
|
|
392
|
+
thread_id: finalThread.getConversationId(),
|
|
370
393
|
prediction_time_ms: predictionTimeMs,
|
|
371
394
|
retry_count: retries,
|
|
372
395
|
completed: completed,
|
|
373
|
-
timing_breakdown:
|
|
374
|
-
|
|
375
|
-
get_messages_ms: timings.getMessages,
|
|
376
|
-
process_messages_ms: timings.processMessages,
|
|
377
|
-
pdf_combination_ms: timings.pdfCombination || 0,
|
|
378
|
-
run_assistant_ms: timings.runAssistant,
|
|
379
|
-
total_ms: timings.total
|
|
380
|
-
}
|
|
381
|
-
}).catch(err => console.error('[replyAssistantCore] Failed to store metrics:', err));
|
|
396
|
+
timing_breakdown: timings
|
|
397
|
+
}).catch(err => logger.error('[replyAssistantCore] Failed to store metrics:', err));
|
|
382
398
|
}
|
|
383
399
|
|
|
384
400
|
return output;
|
|
@@ -398,7 +414,7 @@ const replyAssistant = withTracing(
|
|
|
398
414
|
const switchAssistant = async (code, assistant_id) => {
|
|
399
415
|
try {
|
|
400
416
|
const thread = await Thread.findOne({ code: code });
|
|
401
|
-
|
|
417
|
+
logger.info('Inside thread', thread);
|
|
402
418
|
if (thread === null) return;
|
|
403
419
|
|
|
404
420
|
const variant = process.env.VARIANT || 'assistants';
|
|
@@ -412,7 +428,7 @@ const switchAssistant = async (code, assistant_id) => {
|
|
|
412
428
|
|
|
413
429
|
await Thread.updateOne({ code }, { $set: updateFields });
|
|
414
430
|
} catch (error) {
|
|
415
|
-
|
|
431
|
+
logger.info(error);
|
|
416
432
|
return null;
|
|
417
433
|
}
|
|
418
434
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { Message } = require('../models/messageModel');
|
|
2
2
|
const { Historial_Clinico_ID } = require('../config/airtableConfig');
|
|
3
3
|
const { getRecordByFilter } = require('./airtableService');
|
|
4
|
+
const { logger } = require('../utils/logger');
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
const fetchConversationData = async (filter, skip, limit) => {
|
|
@@ -16,11 +17,11 @@ const fetchConversationData = async (filter, skip, limit) => {
|
|
|
16
17
|
{ read: { $exists: false } }
|
|
17
18
|
]
|
|
18
19
|
};
|
|
19
|
-
|
|
20
|
+
logger.info('Applying unread filter');
|
|
20
21
|
break;
|
|
21
22
|
|
|
22
23
|
case 'no-response':
|
|
23
|
-
|
|
24
|
+
logger.info('Applying no-response filter');
|
|
24
25
|
break;
|
|
25
26
|
|
|
26
27
|
case 'recent': {
|
|
@@ -30,18 +31,18 @@ const fetchConversationData = async (filter, skip, limit) => {
|
|
|
30
31
|
is_group: false,
|
|
31
32
|
createdAt: { $gt: yesterday }
|
|
32
33
|
};
|
|
33
|
-
|
|
34
|
+
logger.info('Applying recent filter (last 24 hours)');
|
|
34
35
|
break;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
case 'all':
|
|
38
39
|
default:
|
|
39
40
|
filterConditions = { is_group: false };
|
|
40
|
-
|
|
41
|
+
logger.info('Applying all conversations filter');
|
|
41
42
|
break;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
logger.info('Executing aggregation pipeline...');
|
|
45
46
|
const aggregationStartTime = Date.now();
|
|
46
47
|
|
|
47
48
|
let aggregationPipeline = [
|
|
@@ -77,7 +78,7 @@ const fetchConversationData = async (filter, skip, limit) => {
|
|
|
77
78
|
const conversations = await Message.aggregate(aggregationPipeline);
|
|
78
79
|
|
|
79
80
|
const aggregationTime = Date.now() - aggregationStartTime;
|
|
80
|
-
|
|
81
|
+
logger.info(`Aggregation completed in ${aggregationTime}ms, found ${conversations.length} conversations`);
|
|
81
82
|
|
|
82
83
|
// Fetch names from Airtable and WhatsApp
|
|
83
84
|
const phoneNumbers = conversations.map(conv => conv._id).filter(Boolean);
|
|
@@ -89,7 +90,7 @@ const fetchConversationData = async (filter, skip, limit) => {
|
|
|
89
90
|
map[patient.whatsapp_id] = patient.name;
|
|
90
91
|
return map;
|
|
91
92
|
}, {});
|
|
92
|
-
|
|
93
|
+
logger.info(`Found ${Object.keys(airtableNameMap).length} names in Airtable`);
|
|
93
94
|
|
|
94
95
|
const contactNames = await Message.aggregate([
|
|
95
96
|
{ $match: { is_group: false, from_me: false } },
|
|
@@ -110,7 +111,7 @@ const fetchConversationData = async (filter, skip, limit) => {
|
|
|
110
111
|
}, {...airtableNameMap}) || airtableNameMap || {};
|
|
111
112
|
|
|
112
113
|
// Fetch unread counts
|
|
113
|
-
|
|
114
|
+
logger.info('Fetching unread counts using Message.aggregate');
|
|
114
115
|
const unreadCounts = await Message.aggregate([
|
|
115
116
|
{
|
|
116
117
|
$match: {
|
|
@@ -134,8 +135,8 @@ const fetchConversationData = async (filter, skip, limit) => {
|
|
|
134
135
|
}
|
|
135
136
|
return map;
|
|
136
137
|
}, {}) || {};
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
logger.info('unreadMap', JSON.stringify(unreadMap));
|
|
139
|
+
logger.info('Number of conversations found:', conversations?.length || 0);
|
|
139
140
|
|
|
140
141
|
// Calculate total count for pagination
|
|
141
142
|
let totalFilterConditions = { is_group: false };
|
|
@@ -193,14 +194,14 @@ const fetchConversationData = async (filter, skip, limit) => {
|
|
|
193
194
|
* Processes conversations to prepare them for the response
|
|
194
195
|
*/
|
|
195
196
|
const processConversations = async (conversations, nameMap, unreadMap) => {
|
|
196
|
-
|
|
197
|
+
logger.info('Processing conversations for response...');
|
|
197
198
|
|
|
198
199
|
let processedConversations = [];
|
|
199
200
|
try {
|
|
200
201
|
processedConversations = (conversations || []).map((conv, index) => {
|
|
201
202
|
try {
|
|
202
203
|
if (!conv || !conv.latestMessage) {
|
|
203
|
-
|
|
204
|
+
logger.warn(`Conversation ${index} missing latestMessage:`, conv?._id || 'unknown');
|
|
204
205
|
return {
|
|
205
206
|
phoneNumber: conv?._id || 'unknown',
|
|
206
207
|
name: 'Unknown',
|
|
@@ -245,7 +246,7 @@ const processConversations = async (conversations, nameMap, unreadMap) => {
|
|
|
245
246
|
lastMessageFromMe: conv?.latestMessage?.from_me || false
|
|
246
247
|
};
|
|
247
248
|
} catch (convError) {
|
|
248
|
-
|
|
249
|
+
logger.error(`Error processing conversation ${index}:`, convError);
|
|
249
250
|
return {
|
|
250
251
|
phoneNumber: conv?._id || `error_${index}`,
|
|
251
252
|
name: 'Error Processing',
|
|
@@ -260,11 +261,10 @@ const processConversations = async (conversations, nameMap, unreadMap) => {
|
|
|
260
261
|
}
|
|
261
262
|
});
|
|
262
263
|
|
|
263
|
-
|
|
264
|
+
logger.info(`Successfully processed ${processedConversations.length} conversations`);
|
|
264
265
|
|
|
265
266
|
} catch (mappingError) {
|
|
266
|
-
|
|
267
|
-
// Return empty conversations if mapping fails
|
|
267
|
+
logger.error('Error in conversation mapping:', mappingError);
|
|
268
268
|
processedConversations = [];
|
|
269
269
|
}
|
|
270
270
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { logger } = require('../utils/logger');
|
|
2
|
+
|
|
1
3
|
let preprocessingHandler = null;
|
|
2
4
|
|
|
3
5
|
const setPreprocessingHandler = (handler) => {
|
|
@@ -16,7 +18,7 @@ const invokePreprocessingHandler = async (payload) => {
|
|
|
16
18
|
const result = await preprocessingHandler(payload);
|
|
17
19
|
return Boolean(result);
|
|
18
20
|
} catch (error) {
|
|
19
|
-
|
|
21
|
+
logger.warn('[PreprocessingHooks] Handler threw an error:', error?.message || error);
|
|
20
22
|
return false;
|
|
21
23
|
}
|
|
22
24
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const mongoose = require('mongoose');
|
|
2
2
|
const runtimeConfig = require('../config/runtimeConfig');
|
|
3
|
+
const { logger } = require('../utils/logger');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* MongoDB storage interface for messages and interactions
|
|
@@ -34,7 +35,7 @@ class MongoStorage {
|
|
|
34
35
|
useNewUrlParser: true,
|
|
35
36
|
useUnifiedTopology: true
|
|
36
37
|
});
|
|
37
|
-
|
|
38
|
+
logger.info('MongoDB connected successfully');
|
|
38
39
|
} catch (error) {
|
|
39
40
|
throw new Error(`MongoDB connection failed: ${error.message}`);
|
|
40
41
|
}
|
|
@@ -46,10 +47,10 @@ class MongoStorage {
|
|
|
46
47
|
const values = this.buildMessageValues(enrichedMessage);
|
|
47
48
|
const { insertMessage } = require('../models/messageModel');
|
|
48
49
|
await insertMessage(values);
|
|
49
|
-
|
|
50
|
+
logger.info('[MongoStorage] Message stored');
|
|
50
51
|
return values;
|
|
51
52
|
} catch (error) {
|
|
52
|
-
|
|
53
|
+
logger.error('Error saving message:', error);
|
|
53
54
|
throw error;
|
|
54
55
|
}
|
|
55
56
|
}
|
|
@@ -64,23 +65,22 @@ class MongoStorage {
|
|
|
64
65
|
return messageData;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
logger.info('[MongoStorage] Detected Twilio media message', {
|
|
68
69
|
from: rawMessage.From,
|
|
69
70
|
numMedia
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
const bucketName = runtimeConfig.get('AWS_S3_BUCKET_NAME') || process.env.AWS_S3_BUCKET_NAME;
|
|
73
74
|
if (!bucketName) {
|
|
74
|
-
|
|
75
|
+
logger.warn('[MongoStorage] AWS_S3_BUCKET_NAME not configured. Skipping media upload.');
|
|
75
76
|
return messageData;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const { processTwilioMediaMessage } = require('../helpers/twilioMediaProcessor');
|
|
79
|
-
const { logger } = require('../utils/logger');
|
|
80
80
|
|
|
81
|
-
const mediaItems = await processTwilioMediaMessage(rawMessage,
|
|
81
|
+
const mediaItems = await processTwilioMediaMessage(rawMessage, bucketName);
|
|
82
82
|
if (!mediaItems || mediaItems.length === 0) {
|
|
83
|
-
|
|
83
|
+
logger.warn('[MongoStorage] Media processing returned no items');
|
|
84
84
|
return messageData;
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -91,7 +91,7 @@ class MongoStorage {
|
|
|
91
91
|
|
|
92
92
|
rawMessage.__nexusMediaProcessed = true;
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
logger.info('[MongoStorage] Media processed successfully', {
|
|
95
95
|
primaryType: mediaPayload.mediaType,
|
|
96
96
|
mediaCount: mediaItems.length,
|
|
97
97
|
s3Key: mediaPayload.key
|
|
@@ -107,7 +107,7 @@ class MongoStorage {
|
|
|
107
107
|
caption: primary.caption || messageData.caption
|
|
108
108
|
};
|
|
109
109
|
} catch (error) {
|
|
110
|
-
|
|
110
|
+
logger.error('[MongoStorage] Failed to enrich Twilio media message:', error);
|
|
111
111
|
return messageData;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -197,7 +197,7 @@ class MongoStorage {
|
|
|
197
197
|
.sort({ createdAt: -1 })
|
|
198
198
|
.limit(limit);
|
|
199
199
|
} catch (error) {
|
|
200
|
-
|
|
200
|
+
logger.error('Error getting messages:', error);
|
|
201
201
|
throw error;
|
|
202
202
|
}
|
|
203
203
|
}
|
|
@@ -206,7 +206,7 @@ class MongoStorage {
|
|
|
206
206
|
try {
|
|
207
207
|
return await this.schemas.Thread.findOne({ code, active: true });
|
|
208
208
|
} catch (error) {
|
|
209
|
-
|
|
209
|
+
logger.error('Error getting thread:', error);
|
|
210
210
|
throw error;
|
|
211
211
|
}
|
|
212
212
|
}
|
|
@@ -217,7 +217,7 @@ class MongoStorage {
|
|
|
217
217
|
await thread.save();
|
|
218
218
|
return thread;
|
|
219
219
|
} catch (error) {
|
|
220
|
-
|
|
220
|
+
logger.error('Error creating thread:', error);
|
|
221
221
|
throw error;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
@@ -230,7 +230,7 @@ class MongoStorage {
|
|
|
230
230
|
{ new: true }
|
|
231
231
|
);
|
|
232
232
|
} catch (error) {
|
|
233
|
-
|
|
233
|
+
logger.error('Error updating thread:', error);
|
|
234
234
|
throw error;
|
|
235
235
|
}
|
|
236
236
|
}
|
package/lib/utils/logger.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const pino = require('pino');
|
|
2
|
-
const { trace
|
|
2
|
+
const { trace } = require('@opentelemetry/api');
|
|
3
3
|
|
|
4
4
|
const createLogger = (config = {}) => {
|
|
5
5
|
const {
|
|
@@ -98,12 +98,44 @@ const createObservabilityLogger = (serviceName = 'nexus-assistant', config = {})
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
// Default logger instances
|
|
101
|
-
const
|
|
101
|
+
const baseLogger = createLogger();
|
|
102
102
|
const observabilityLogger = createObservabilityLogger();
|
|
103
103
|
|
|
104
|
+
let getRequestId = () => null;
|
|
105
|
+
|
|
106
|
+
function setRequestIdGetter(getter) {
|
|
107
|
+
getRequestId = getter;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const logger = {
|
|
111
|
+
info: (message, meta = {}) => {
|
|
112
|
+
const requestId = getRequestId();
|
|
113
|
+
baseLogger.info(requestId ? { requestId, ...meta } : meta, message);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
error: (message, meta = {}) => {
|
|
117
|
+
const requestId = getRequestId();
|
|
118
|
+
baseLogger.error(requestId ? { requestId, ...meta } : meta, message);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
warn: (message, meta = {}) => {
|
|
122
|
+
const requestId = getRequestId();
|
|
123
|
+
baseLogger.warn(requestId ? { requestId, ...meta } : meta, message);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
debug: (message, meta = {}) => {
|
|
127
|
+
const requestId = getRequestId();
|
|
128
|
+
baseLogger.debug(requestId ? { requestId, ...meta } : meta, message);
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
child: (bindings) => baseLogger.child(bindings),
|
|
132
|
+
level: baseLogger.level
|
|
133
|
+
};
|
|
134
|
+
|
|
104
135
|
module.exports = {
|
|
105
136
|
logger,
|
|
106
137
|
createLogger,
|
|
107
138
|
observabilityLogger,
|
|
108
|
-
createObservabilityLogger
|
|
139
|
+
createObservabilityLogger,
|
|
140
|
+
setRequestIdGetter
|
|
109
141
|
};
|