@peopl-health/nexus 2.4.6 → 2.4.8
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 +24 -9
- package/lib/helpers/mediaHelper.js +3 -2
- package/lib/helpers/messageHelper.js +12 -11
- package/lib/helpers/processHelper.js +24 -10
- package/lib/helpers/qrHelper.js +2 -1
- package/lib/helpers/twilioMediaProcessor.js +45 -35
- 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 +18 -17
- package/lib/providers/OpenAIResponsesProviderTools.js +3 -5
- package/lib/providers/createProvider.js +2 -1
- package/lib/services/airtableService.js +39 -6
- package/lib/services/assistantService.js +20 -20
- 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/sanitizer.js +0 -6
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
+
logger.error(`[OpenAIResponsesProvider] Batch ${batchNumber}/${totalBatches} failed:`, error?.message || error);
|
|
172
173
|
throw error;
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
|
|
177
|
+
logger.info(`[OpenAIResponsesProvider] Successfully added ${items.length} messages in ${totalBatches} batches`);
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
/**
|
|
@@ -235,7 +236,7 @@ class OpenAIResponsesProvider {
|
|
|
235
236
|
if (items.length === 0) return;
|
|
236
237
|
|
|
237
238
|
if (deleteAll) {
|
|
238
|
-
|
|
239
|
+
logger.info(`[OpenAIResponsesProvider] Deleting all ${items.length} items from conversation`);
|
|
239
240
|
for (const item of items) {
|
|
240
241
|
await this.conversations.items.delete(item.id, {conversation_id: id});
|
|
241
242
|
}
|
|
@@ -249,12 +250,12 @@ class OpenAIResponsesProvider {
|
|
|
249
250
|
const hasOutput = functionOutputs.some(output => output.call_id === functionCall.call_id);
|
|
250
251
|
|
|
251
252
|
if (!hasOutput) {
|
|
252
|
-
|
|
253
|
+
logger.info(`[OpenAIResponsesProvider] Deleting orphaned function_call: ${functionCall.id} (${functionCall.call_id})`);
|
|
253
254
|
await this.conversations.items.delete(functionCall.id, {conversation_id: id});
|
|
254
255
|
}
|
|
255
256
|
}
|
|
256
257
|
} catch (error) {
|
|
257
|
-
|
|
258
|
+
logger.warn('[OpenAIResponsesProvider] Failed to cleanup conversation:', error?.message);
|
|
258
259
|
}
|
|
259
260
|
}
|
|
260
261
|
|
|
@@ -288,7 +289,7 @@ class OpenAIResponsesProvider {
|
|
|
288
289
|
toolOutputs = pendingOutputs;
|
|
289
290
|
}
|
|
290
291
|
} catch (error) {
|
|
291
|
-
|
|
292
|
+
logger.warn('[OpenAIResponsesProvider] Error checking for pending function calls:', error?.message);
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
|
|
@@ -324,7 +325,7 @@ class OpenAIResponsesProvider {
|
|
|
324
325
|
object: response.object || 'response',
|
|
325
326
|
};
|
|
326
327
|
} catch (error) {
|
|
327
|
-
|
|
328
|
+
logger.error('[OpenAIResponsesProvider] Error running conversation:', error);
|
|
328
329
|
throw error;
|
|
329
330
|
}
|
|
330
331
|
}
|
|
@@ -419,7 +420,7 @@ class OpenAIResponsesProvider {
|
|
|
419
420
|
async checkRunStatus(assistant, thread_id, run_id, retryCount = 0, maxRetries = DEFAULT_MAX_RETRIES, actionHandled = false) {
|
|
420
421
|
try {
|
|
421
422
|
let run = await this.getRun({ threadId: thread_id, runId: run_id });
|
|
422
|
-
|
|
423
|
+
logger.info(`Status: ${run.status} ${thread_id} ${run_id} (attempt ${retryCount + 1})`);
|
|
423
424
|
|
|
424
425
|
if (run.status === 'completed') {
|
|
425
426
|
return {run, completed: true};
|
|
@@ -432,12 +433,12 @@ class OpenAIResponsesProvider {
|
|
|
432
433
|
const needsFunctionCall = run.output?.some(item => item.type === 'function_call');
|
|
433
434
|
if (needsFunctionCall && !actionHandled) {
|
|
434
435
|
if (retryCount >= maxRetries) {
|
|
435
|
-
|
|
436
|
+
logger.warn('[OpenAIResponsesProvider] Max retries reached while handling function calls');
|
|
436
437
|
return {run, completed: false};
|
|
437
438
|
}
|
|
438
439
|
|
|
439
440
|
const outputs = await handleRequiresActionUtil(assistant, run);
|
|
440
|
-
|
|
441
|
+
logger.info('[OpenAIResponsesProvider] Function call outputs:', outputs);
|
|
441
442
|
|
|
442
443
|
if (outputs.length > 0) {
|
|
443
444
|
try {
|
|
@@ -451,7 +452,7 @@ class OpenAIResponsesProvider {
|
|
|
451
452
|
|
|
452
453
|
return this.checkRunStatus(assistant, thread_id, run_id, retryCount + 1, maxRetries, true);
|
|
453
454
|
} catch (submitError) {
|
|
454
|
-
|
|
455
|
+
logger.error('[OpenAIResponsesProvider] Error submitting tool outputs:', submitError);
|
|
455
456
|
if (retryCount < maxRetries) {
|
|
456
457
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
457
458
|
return this.checkRunStatus(assistant, thread_id, run_id, retryCount + 1, maxRetries, false);
|
|
@@ -459,7 +460,7 @@ class OpenAIResponsesProvider {
|
|
|
459
460
|
return {run, completed: false};
|
|
460
461
|
}
|
|
461
462
|
} else {
|
|
462
|
-
|
|
463
|
+
logger.warn('[OpenAIResponsesProvider] Function calls detected but no outputs generated');
|
|
463
464
|
return {run, completed: false};
|
|
464
465
|
}
|
|
465
466
|
}
|
|
@@ -471,7 +472,7 @@ class OpenAIResponsesProvider {
|
|
|
471
472
|
|
|
472
473
|
return {run, completed: false};
|
|
473
474
|
} catch (error) {
|
|
474
|
-
|
|
475
|
+
logger.error('[OpenAIResponsesProvider] Error checking run status:', error);
|
|
475
476
|
return {run: null, completed: false};
|
|
476
477
|
}
|
|
477
478
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,38 @@ 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);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async function addLinkedRecord(baseID, targetTable, fields, linkConfig) {
|
|
84
|
+
if (!baseID) {
|
|
85
|
+
throw new Error('[addLinkedRecord] Base ID is required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
if (linkConfig) {
|
|
90
|
+
const { referenceTable, referenceFilter, linkFieldName } = linkConfig;
|
|
91
|
+
|
|
92
|
+
const base = airtable.base(baseID);
|
|
93
|
+
const referenceRecords = await base(referenceTable).select({
|
|
94
|
+
filterByFormula: referenceFilter
|
|
95
|
+
}).firstPage();
|
|
96
|
+
|
|
97
|
+
if (linkFieldName && referenceRecords && referenceRecords.length > 0) {
|
|
98
|
+
const recordId = referenceRecords[0].id;
|
|
99
|
+
if (recordId) {
|
|
100
|
+
fields[linkFieldName] = [recordId];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const record = await addRecord(baseID, targetTable, [{ fields }]);
|
|
106
|
+
return record;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('[addLinkedRecord] Error adding linked record:', error);
|
|
109
|
+
throw error;
|
|
78
110
|
}
|
|
79
111
|
}
|
|
80
112
|
|
|
@@ -82,5 +114,6 @@ module.exports = {
|
|
|
82
114
|
addRecord,
|
|
83
115
|
getRecords,
|
|
84
116
|
getRecordByFilter,
|
|
85
|
-
updateRecordByFilter
|
|
117
|
+
updateRecordByFilter,
|
|
118
|
+
addLinkedRecord
|
|
86
119
|
};
|
|
@@ -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
|
|
|
@@ -290,14 +290,14 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
290
290
|
timings.getMessages = Date.now() - timings.getMessages;
|
|
291
291
|
|
|
292
292
|
if (!patientReply) {
|
|
293
|
-
logger.
|
|
293
|
+
logger.info('[replyAssistantCore] No relevant data found for this assistant.');
|
|
294
294
|
return null;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
298
298
|
|
|
299
299
|
timings.processMessages = Date.now();
|
|
300
|
-
logger.
|
|
300
|
+
logger.info(`[replyAssistantCore] Processing ${patientReply.length} messages in parallel`);
|
|
301
301
|
|
|
302
302
|
const processResults = await processThreadMessage(code, patientReply, provider);
|
|
303
303
|
|
|
@@ -308,7 +308,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
308
308
|
|
|
309
309
|
if (allMessagesToAdd.length > 0) {
|
|
310
310
|
const threadId = thread.getConversationId();
|
|
311
|
-
logger.
|
|
311
|
+
logger.info(`[replyAssistantCore] Adding ${allMessagesToAdd.length} messages to thread in batch`);
|
|
312
312
|
await provider.addMessage({ threadId, messages: allMessagesToAdd });
|
|
313
313
|
}
|
|
314
314
|
|
|
@@ -319,10 +319,10 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
319
319
|
|
|
320
320
|
if (urls.length > 0) {
|
|
321
321
|
timings.pdfCombination = Date.now();
|
|
322
|
-
logger.
|
|
322
|
+
logger.info(`[replyAssistantCore] Processing ${urls.length} URLs for PDF combination`);
|
|
323
323
|
const { pdfBuffer, processedFiles } = await combineImagesToPDF({ code });
|
|
324
324
|
timings.pdfCombination = Date.now() - timings.pdfCombination;
|
|
325
|
-
logger.
|
|
325
|
+
logger.info(`[replyAssistantCore] PDF combination complete: ${processedFiles?.length || 0} files processed`);
|
|
326
326
|
|
|
327
327
|
if (pdfBuffer) {
|
|
328
328
|
const key = `${code}-${Date.now()}-combined.pdf`;
|
|
@@ -353,7 +353,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
353
353
|
timings.runAssistant = Date.now() - timings.runAssistant;
|
|
354
354
|
timings.total = Date.now() - startTotal;
|
|
355
355
|
|
|
356
|
-
logger.
|
|
356
|
+
logger.info('[Performance Breakdown]', {
|
|
357
357
|
code: code ? `${code.substring(0, 3)}***${code.slice(-4)}` : 'unknown',
|
|
358
358
|
messageCount: patientReply.length,
|
|
359
359
|
hasMedia: urls.length > 0,
|
|
@@ -378,7 +378,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
378
378
|
run_assistant_ms: timings.runAssistant,
|
|
379
379
|
total_ms: timings.total
|
|
380
380
|
}
|
|
381
|
-
}).catch(err =>
|
|
381
|
+
}).catch(err => logger.error('[replyAssistantCore] Failed to store metrics:', err));
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
return output;
|
|
@@ -398,7 +398,7 @@ const replyAssistant = withTracing(
|
|
|
398
398
|
const switchAssistant = async (code, assistant_id) => {
|
|
399
399
|
try {
|
|
400
400
|
const thread = await Thread.findOne({ code: code });
|
|
401
|
-
|
|
401
|
+
logger.info('Inside thread', thread);
|
|
402
402
|
if (thread === null) return;
|
|
403
403
|
|
|
404
404
|
const variant = process.env.VARIANT || 'assistants';
|
|
@@ -412,7 +412,7 @@ const switchAssistant = async (code, assistant_id) => {
|
|
|
412
412
|
|
|
413
413
|
await Thread.updateOne({ code }, { $set: updateFields });
|
|
414
414
|
} catch (error) {
|
|
415
|
-
|
|
415
|
+
logger.info(error);
|
|
416
416
|
return null;
|
|
417
417
|
}
|
|
418
418
|
};
|
|
@@ -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
|
}
|