@peopl-health/nexus 2.4.7 → 2.4.9-fix-pdf-processing

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/examples/assistants/DoctorScheduleAssistant.js +3 -2
  2. package/lib/adapters/BaileysProvider.js +5 -4
  3. package/lib/adapters/TwilioProvider.js +20 -19
  4. package/lib/assistants/BaseAssistant.js +7 -6
  5. package/lib/config/awsConfig.js +14 -12
  6. package/lib/config/llmConfig.js +3 -2
  7. package/lib/config/mongoAuthConfig.js +5 -5
  8. package/lib/controllers/assistantController.js +12 -11
  9. package/lib/controllers/bugReportController.js +6 -5
  10. package/lib/controllers/conversationController.js +72 -71
  11. package/lib/controllers/interactionController.js +7 -6
  12. package/lib/controllers/mediaController.js +15 -13
  13. package/lib/controllers/messageController.js +7 -6
  14. package/lib/controllers/patientController.js +2 -1
  15. package/lib/controllers/qualityMessageController.js +5 -4
  16. package/lib/controllers/templateController.js +11 -9
  17. package/lib/controllers/uploadController.js +3 -1
  18. package/lib/core/NexusMessaging.js +18 -18
  19. package/lib/helpers/assistantHelper.js +8 -9
  20. package/lib/helpers/baileysHelper.js +4 -3
  21. package/lib/helpers/filesHelper.js +23 -14
  22. package/lib/helpers/llmsHelper.js +17 -10
  23. package/lib/helpers/mediaHelper.js +3 -2
  24. package/lib/helpers/messageHelper.js +12 -11
  25. package/lib/helpers/processHelper.js +2 -2
  26. package/lib/helpers/qrHelper.js +2 -1
  27. package/lib/helpers/twilioMediaProcessor.js +19 -29
  28. package/lib/helpers/whatsappHelper.js +3 -2
  29. package/lib/index.js +11 -14
  30. package/lib/interactive/index.js +11 -11
  31. package/lib/middleware/requestId.js +9 -14
  32. package/lib/models/messageModel.js +5 -4
  33. package/lib/providers/OpenAIAssistantsProvider.js +10 -9
  34. package/lib/providers/OpenAIResponsesProvider.js +24 -17
  35. package/lib/providers/OpenAIResponsesProviderTools.js +3 -5
  36. package/lib/providers/createProvider.js +2 -1
  37. package/lib/services/airtableService.js +6 -5
  38. package/lib/services/assistantService.js +73 -57
  39. package/lib/services/conversationService.js +16 -16
  40. package/lib/services/preprocessingHooks.js +3 -1
  41. package/lib/storage/MongoStorage.js +14 -14
  42. package/lib/utils/errorHandler.js +3 -1
  43. package/lib/utils/logger.js +35 -3
  44. package/lib/utils/mediaValidator.js +18 -14
  45. package/lib/utils/sanitizer.js +0 -6
  46. package/lib/utils/tracingDecorator.js +7 -1
  47. package/package.json +1 -1
@@ -5,6 +5,7 @@ const {
5
5
  handleRequiresAction: handleRequiresActionUtil,
6
6
  transformToolsForResponsesAPI: transformToolsForResponsesAPIUtil
7
7
  } = require('./OpenAIResponsesProviderTools');
8
+ const { logger } = require('../utils/logger');
8
9
 
9
10
  const CONVERSATION_PREFIX = 'conv_';
10
11
  const RESPONSE_PREFIX = 'resp_';
@@ -81,7 +82,7 @@ class OpenAIResponsesProvider {
81
82
  : messages;
82
83
 
83
84
  if (messages.length > DEFAULT_MAX_HISTORICAL_MESSAGES) {
84
- console.warn(`[OpenAIResponsesProvider] Capped ${messages.length} → ${DEFAULT_MAX_HISTORICAL_MESSAGES} messages`);
85
+ logger.warn(`[OpenAIResponsesProvider] Capped ${messages.length} → ${DEFAULT_MAX_HISTORICAL_MESSAGES} messages`);
85
86
  }
86
87
 
87
88
  const allItems = this._conversationItems(messagesToProcess);
@@ -108,7 +109,7 @@ class OpenAIResponsesProvider {
108
109
  const totalBatches = Math.ceil(remainingItems.length / MAX_ITEMS_PER_BATCH);
109
110
 
110
111
  // Concise batch summary
111
- console.log(`[OpenAIResponsesProvider] Batching: ${initialItems.length} (create) + ${remainingItems.length} (${totalBatches} batches) = ${totalItems} total`);
112
+ logger.info(`[OpenAIResponsesProvider] Batching: ${initialItems.length} (create) + ${remainingItems.length} (${totalBatches} batches) = ${totalItems} total`);
112
113
 
113
114
  const payload = this._cleanObject({
114
115
  metadata,
@@ -125,7 +126,7 @@ class OpenAIResponsesProvider {
125
126
  return await this._post('/conversations', payload);
126
127
  });
127
128
  } catch (error) {
128
- console.error('[OpenAIResponsesProvider] Failed to create conversation:', error?.message || error);
129
+ logger.error('[OpenAIResponsesProvider] Failed to create conversation:', error?.message || error);
129
130
  throw error;
130
131
  }
131
132
 
@@ -134,7 +135,7 @@ class OpenAIResponsesProvider {
134
135
  try {
135
136
  await this._addItemsInBatches(conversation.id, remainingItems);
136
137
  } catch (error) {
137
- console.error('[OpenAIResponsesProvider] Failed to add remaining messages. Conversation created with partial history:', error?.message || error);
138
+ logger.error('[OpenAIResponsesProvider] Failed to add remaining messages. Conversation created with partial history:', error?.message || error);
138
139
  }
139
140
  }
140
141
 
@@ -168,12 +169,12 @@ class OpenAIResponsesProvider {
168
169
  return await this._post(`/conversations/${id}/items`, { items: batchPayload });
169
170
  });
170
171
  } catch (error) {
171
- console.error(`[OpenAIResponsesProvider] Batch ${batchNumber}/${totalBatches} failed:`, error?.message || error);
172
+ logger.error(`[OpenAIResponsesProvider] Batch ${batchNumber}/${totalBatches} failed:`, error?.message || error);
172
173
  throw error;
173
174
  }
174
175
  }
175
176
 
176
- console.log(`[OpenAIResponsesProvider] Successfully added ${items.length} messages in ${totalBatches} batches`);
177
+ logger.info(`[OpenAIResponsesProvider] Successfully added ${items.length} messages in ${totalBatches} batches`);
177
178
  }
178
179
 
179
180
  /**
@@ -211,6 +212,12 @@ class OpenAIResponsesProvider {
211
212
 
212
213
  if (payloads.length === 0) return null;
213
214
 
215
+ if (payloads.length > MAX_ITEMS_PER_BATCH) {
216
+ logger.info(`[OpenAIResponsesProvider] Batching ${payloads.length} messages into chunks of ${MAX_ITEMS_PER_BATCH}`);
217
+ await this._addItemsInBatches(id, payloads, MAX_ITEMS_PER_BATCH);
218
+ return { batched: true, count: payloads.length };
219
+ }
220
+
214
221
  return this._retryWithRateLimit(async () => {
215
222
  if (this.conversations?.items?.create) {
216
223
  return await this.conversations.items.create(id, { items: payloads });
@@ -235,7 +242,7 @@ class OpenAIResponsesProvider {
235
242
  if (items.length === 0) return;
236
243
 
237
244
  if (deleteAll) {
238
- console.log(`[OpenAIResponsesProvider] Deleting all ${items.length} items from conversation`);
245
+ logger.info(`[OpenAIResponsesProvider] Deleting all ${items.length} items from conversation`);
239
246
  for (const item of items) {
240
247
  await this.conversations.items.delete(item.id, {conversation_id: id});
241
248
  }
@@ -249,12 +256,12 @@ class OpenAIResponsesProvider {
249
256
  const hasOutput = functionOutputs.some(output => output.call_id === functionCall.call_id);
250
257
 
251
258
  if (!hasOutput) {
252
- console.log(`[OpenAIResponsesProvider] Deleting orphaned function_call: ${functionCall.id} (${functionCall.call_id})`);
259
+ logger.info(`[OpenAIResponsesProvider] Deleting orphaned function_call: ${functionCall.id} (${functionCall.call_id})`);
253
260
  await this.conversations.items.delete(functionCall.id, {conversation_id: id});
254
261
  }
255
262
  }
256
263
  } catch (error) {
257
- console.warn('[OpenAIResponsesProvider] Failed to cleanup conversation:', error?.message);
264
+ logger.warn('[OpenAIResponsesProvider] Failed to cleanup conversation:', error?.message);
258
265
  }
259
266
  }
260
267
 
@@ -288,7 +295,7 @@ class OpenAIResponsesProvider {
288
295
  toolOutputs = pendingOutputs;
289
296
  }
290
297
  } catch (error) {
291
- console.warn('[OpenAIResponsesProvider] Error checking for pending function calls:', error?.message);
298
+ logger.warn('[OpenAIResponsesProvider] Error checking for pending function calls:', error?.message);
292
299
  }
293
300
  }
294
301
 
@@ -324,7 +331,7 @@ class OpenAIResponsesProvider {
324
331
  object: response.object || 'response',
325
332
  };
326
333
  } catch (error) {
327
- console.error('[OpenAIResponsesProvider] Error running conversation:', error);
334
+ logger.error('[OpenAIResponsesProvider] Error running conversation:', error);
328
335
  throw error;
329
336
  }
330
337
  }
@@ -419,7 +426,7 @@ class OpenAIResponsesProvider {
419
426
  async checkRunStatus(assistant, thread_id, run_id, retryCount = 0, maxRetries = DEFAULT_MAX_RETRIES, actionHandled = false) {
420
427
  try {
421
428
  let run = await this.getRun({ threadId: thread_id, runId: run_id });
422
- console.log(`Status: ${run.status} ${thread_id} ${run_id} (attempt ${retryCount + 1})`);
429
+ logger.info(`Status: ${run.status} ${thread_id} ${run_id} (attempt ${retryCount + 1})`);
423
430
 
424
431
  if (run.status === 'completed') {
425
432
  return {run, completed: true};
@@ -432,12 +439,12 @@ class OpenAIResponsesProvider {
432
439
  const needsFunctionCall = run.output?.some(item => item.type === 'function_call');
433
440
  if (needsFunctionCall && !actionHandled) {
434
441
  if (retryCount >= maxRetries) {
435
- console.warn('[OpenAIResponsesProvider] Max retries reached while handling function calls');
442
+ logger.warn('[OpenAIResponsesProvider] Max retries reached while handling function calls');
436
443
  return {run, completed: false};
437
444
  }
438
445
 
439
446
  const outputs = await handleRequiresActionUtil(assistant, run);
440
- console.log('[OpenAIResponsesProvider] Function call outputs:', outputs);
447
+ logger.info('[OpenAIResponsesProvider] Function call outputs:', outputs);
441
448
 
442
449
  if (outputs.length > 0) {
443
450
  try {
@@ -451,7 +458,7 @@ class OpenAIResponsesProvider {
451
458
 
452
459
  return this.checkRunStatus(assistant, thread_id, run_id, retryCount + 1, maxRetries, true);
453
460
  } catch (submitError) {
454
- console.error('[OpenAIResponsesProvider] Error submitting tool outputs:', submitError);
461
+ logger.error('[OpenAIResponsesProvider] Error submitting tool outputs:', submitError);
455
462
  if (retryCount < maxRetries) {
456
463
  await new Promise(resolve => setTimeout(resolve, 2000));
457
464
  return this.checkRunStatus(assistant, thread_id, run_id, retryCount + 1, maxRetries, false);
@@ -459,7 +466,7 @@ class OpenAIResponsesProvider {
459
466
  return {run, completed: false};
460
467
  }
461
468
  } else {
462
- console.warn('[OpenAIResponsesProvider] Function calls detected but no outputs generated');
469
+ logger.warn('[OpenAIResponsesProvider] Function calls detected but no outputs generated');
463
470
  return {run, completed: false};
464
471
  }
465
472
  }
@@ -471,7 +478,7 @@ class OpenAIResponsesProvider {
471
478
 
472
479
  return {run, completed: false};
473
480
  } catch (error) {
474
- console.error('[OpenAIResponsesProvider] Error checking run status:', error);
481
+ logger.error('[OpenAIResponsesProvider] Error checking run status:', error);
475
482
  return {run: null, completed: false};
476
483
  }
477
484
  }
@@ -1,6 +1,4 @@
1
- /**
2
- * Tool and function call handling utilities for OpenAIResponsesProvider
3
- */
1
+ const { logger } = require('../utils/logger');
4
2
 
5
3
  /**
6
4
  * Execute a function call and return the output format
@@ -19,7 +17,7 @@ async function executeFunctionCall(assistant, call) {
19
17
  output: typeof result === 'string' ? result : JSON.stringify(result)
20
18
  };
21
19
  } catch (error) {
22
- console.error('[OpenAIResponsesProvider] Tool execution failed', error);
20
+ logger.error('[OpenAIResponsesProvider] Tool execution failed', error);
23
21
  return {
24
22
  type: 'function_call_output',
25
23
  call_id: call.call_id,
@@ -46,7 +44,7 @@ async function handlePendingFunctionCalls(assistant, conversationItems) {
46
44
  return [];
47
45
  }
48
46
 
49
- console.log(`[OpenAIResponsesProvider] Found ${orphanedCalls.length} pending function calls, handling them...`);
47
+ logger.info(`[OpenAIResponsesProvider] Found ${orphanedCalls.length} pending function calls, handling them...`);
50
48
  const outputs = [];
51
49
  for (const call of orphanedCalls) {
52
50
  outputs.push(await executeFunctionCall(assistant, call));
@@ -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
- console.log('Creating OpenAI provider with config:', config);
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
- console.log('Record added at', tableName);
10
+ logger.info('Record added at', tableName);
10
11
  return record;
11
12
  } catch (error) {
12
- console.error('Error adding record:', error);
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
- console.error('Error fetching records:', error);
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
- console.error(`Error fetching records by ${filter}:`, error);
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
- console.error(`Error updating records by ${filter}:`, error);
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('../middleware/requestId');
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
- console.log('[createAssistant] findThread', findThread);
139
+ logger.info('[createAssistant] findThread', findThread);
140
140
  if (findThread && findThread.getConversationId()) {
141
- console.log('[createAssistant] Thread already exists');
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
- console.log('[createAssistant] curRow', curRow[0]);
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
- console.log('[createAssistant] Updated thread:', updatedThread);
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
- console.log(thread);
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
- console.log(message);
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
- console.log(`Attempt ${retries + 1}: completed=${completed}, output=${output || '(empty)'}`);
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
- console.log('THE ANS IS', output);
219
+ logger.info('THE ANS IS', output);
220
220
  return output;
221
221
  } catch (error) {
222
- console.log(error);
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
- console.log('RUN RESPONSE', output);
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
- timings.getThread = Date.now();
277
- const thread = thread_ || await withTracing(getThread, 'get_thread_operation',
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.getThread = Date.now() - timings.getThread;
286
+ timings.get_thread_ms = getThreadMs;
285
287
 
286
- if (!thread) return null;
287
-
288
- timings.getMessages = Date.now();
289
- const patientReply = await getLastMessages(code);
290
- timings.getMessages = Date.now() - timings.getMessages;
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.log('[replyAssistantCore] No relevant data found for this assistant.');
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
- timings.processMessages = Date.now();
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 processThreadMessage(code, patientReply, provider);
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 = thread.getConversationId();
311
- logger.log(`[replyAssistantCore] Adding ${allMessagesToAdd.length} messages to thread in batch`);
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, thread)));
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
- timings.pdfCombination = Date.now();
322
- logger.log(`[replyAssistantCore] Processing ${urls.length} URLs for PDF combination`);
323
- const { pdfBuffer, processedFiles } = await combineImagesToPDF({ code });
324
- timings.pdfCombination = Date.now() - timings.pdfCombination;
325
- logger.log(`[replyAssistantCore] PDF combination complete: ${processedFiles?.length || 0} files processed`);
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 || thread.stopped) return null;
361
+ if (!patientMsg || finalThread.stopped) return null;
341
362
 
342
- timings.runAssistant = Date.now();
343
- const assistant = getAssistantById(thread.getAssistantId(), thread);
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
- )(thread, assistant, runOptions, patientReply);
353
- timings.runAssistant = Date.now() - timings.runAssistant;
354
- timings.total = Date.now() - startTotal;
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.log('[Performance Breakdown]', {
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
- time: `${timings.total}ms`
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: thread.getAssistantId(),
369
- thread_id: thread.getConversationId(),
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
- get_thread_ms: timings.getThread,
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
- console.log('Inside thread', thread);
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
- console.log(error);
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
- console.log('Applying unread filter');
20
+ logger.info('Applying unread filter');
20
21
  break;
21
22
 
22
23
  case 'no-response':
23
- console.log('Applying no-response filter');
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
- console.log('Applying recent filter (last 24 hours)');
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
- console.log('Applying all conversations filter');
41
+ logger.info('Applying all conversations filter');
41
42
  break;
42
43
  }
43
44
 
44
- console.log('Executing aggregation pipeline...');
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
- console.log(`Aggregation completed in ${aggregationTime}ms, found ${conversations.length} conversations`);
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
- console.log(`Found ${Object.keys(airtableNameMap).length} names in Airtable`);
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
- console.log('Fetching unread counts using Message.aggregate');
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
- console.log('unreadMap', JSON.stringify(unreadMap));
138
- console.log('Number of conversations found:', conversations?.length || 0);
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
- console.log('Processing conversations for response...');
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
- console.warn(`Conversation ${index} missing latestMessage:`, conv?._id || 'unknown');
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
- console.error(`Error processing conversation ${index}:`, convError);
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
- console.log(`Successfully processed ${processedConversations.length} conversations`);
264
+ logger.info(`Successfully processed ${processedConversations.length} conversations`);
264
265
 
265
266
  } catch (mappingError) {
266
- console.error('Error in conversation mapping:', mappingError);
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
- console.warn('[PreprocessingHooks] Handler threw an error:', error?.message || error);
21
+ logger.warn('[PreprocessingHooks] Handler threw an error:', error?.message || error);
20
22
  return false;
21
23
  }
22
24
  };