@peopl-health/nexus 2.4.2 → 2.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  const { MessageProvider } = require('../core/MessageProvider');
2
2
  const axios = require('axios');
3
3
  const runtimeConfig = require('../config/runtimeConfig');
4
- const { uploadMediaToS3, sanitizeFileName, getFileExtension } = require('../helpers/mediaHelper');
4
+ const { uploadMediaToS3, getFileExtension } = require('../helpers/mediaHelper');
5
+ const { sanitizeMediaFilename } = require('../utils/sanitizer');
5
6
  const { generatePresignedUrl } = require('../config/awsConfig');
6
7
  const { validateMedia, getMediaType } = require('../utils/mediaValidator');
7
8
  const { v4: uuidv4 } = require('uuid');
@@ -299,7 +300,7 @@ class TwilioProvider extends MessageProvider {
299
300
  })();
300
301
 
301
302
  const baseName = existingFileName ? existingFileName.split('.').slice(0, -1).join('.') : `${mediaType}_${Date.now()}`;
302
- const sanitizedBase = sanitizeFileName(baseName) || `${mediaType}_${Date.now()}`;
303
+ const sanitizedBase = sanitizeMediaFilename(baseName) || `${mediaType}_${Date.now()}`;
303
304
  const extension = getFileExtension(contentType) || 'bin';
304
305
  const uploadId = uuidv4();
305
306
  const key = await uploadMediaToS3(buffer, uploadId, sanitizedBase, bucketName, contentType, mediaType);
@@ -1,7 +1,7 @@
1
1
  const llmConfig = require('../config/llmConfig');
2
2
  const { Thread } = require('../models/threadModel');
3
3
  const { Message } = require('../models/messageModel');
4
- const { formatMessage } = require('../helpers/assistantHelper');
4
+ const { formatMessage } = require('../helpers/messageHelper');
5
5
  const { createProvider } = require('../providers/createProvider');
6
6
 
7
7
  const DEFAULT_MAX_HISTORICAL_MESSAGES = parseInt(process.env.MAX_HISTORICAL_MESSAGES || '50', 10);
@@ -111,6 +111,7 @@ const runAssistantWithRetries = async (thread, assistant, runConfig, patientRepl
111
111
  assistant.setReplies(patientReply);
112
112
  }
113
113
 
114
+ const startTime = Date.now();
114
115
  let run, output, completed;
115
116
  let retries = 0;
116
117
  const maxRetries = DEFAULT_MAX_RETRIES;
@@ -131,11 +132,14 @@ const runAssistantWithRetries = async (thread, assistant, runConfig, patientRepl
131
132
  if (retries < maxRetries) await new Promise(resolve => setTimeout(resolve, 2000));
132
133
  } while (retries < maxRetries && (!completed || !output));
133
134
 
135
+ const predictionTimeMs = Date.now() - startTime;
136
+
134
137
  if (run?.last_error) console.log('[runAssistantWithRetries] RUN LAST ERROR:', run.last_error);
135
138
  console.log('[runAssistantWithRetries] RUN STATUS', completed);
136
139
  console.log('[runAssistantWithRetries] OUTPUT', output);
140
+ console.log('[runAssistantWithRetries] TIMING', { predictionTimeMs, retries });
137
141
 
138
- return { run, output, completed, retries };
142
+ return { run, output, completed, retries, predictionTimeMs };
139
143
  };
140
144
 
141
145
  module.exports = {
@@ -15,7 +15,7 @@ async function convertPdfToImages(pdfName) {
15
15
  const pdfPath = path.join(outputDir, `${sanitizedName}.pdf`);
16
16
  const outputPattern = path.join(outputDir, sanitizedName);
17
17
 
18
- await fs.promises.mkdir(outputDir, { recursive: true });
18
+ await fs.mkdir(outputDir, { recursive: true });
19
19
 
20
20
  return new Promise((resolve, reject) => {
21
21
  const args = ['-jpeg', pdfPath, outputPattern];
@@ -49,7 +49,7 @@ async function combineImagesToPDF(config) {
49
49
  } = config;
50
50
 
51
51
  const inputDir = path.join(__dirname, 'assets', 'tmp');
52
- const files = await fs.promises.readdir(inputDir);
52
+ const files = await fs.readdir(inputDir);
53
53
 
54
54
  const imageFiles = files.filter(file => {
55
55
  const ext = path.extname(file).toLowerCase().substring(1);
@@ -81,7 +81,7 @@ async function combineImagesToPDF(config) {
81
81
  const filePath = path.join(inputDir, file);
82
82
  console.log(`Processing file ${index + 1}/${imageFiles.length}: ${file}`);
83
83
 
84
- const imageBuffer = await fs.promises.readFile(filePath);
84
+ const imageBuffer = await fs.readFile(filePath);
85
85
  const pngBuffer = await sharp(imageBuffer)
86
86
  .toFormat('png')
87
87
  .toBuffer();
@@ -116,7 +116,7 @@ const cleanupFiles = async (files) => {
116
116
 
117
117
  await Promise.all(files.map(async (filePath) => {
118
118
  try {
119
- await fs.promises.unlink(filePath);
119
+ await fs.unlink(filePath);
120
120
  } catch (error) {
121
121
  if (error?.code !== 'ENOENT') {
122
122
  const safeFileName = filePath ? filePath.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-') : 'unknown';
@@ -154,7 +154,7 @@ async function downloadMediaAndCreateFile(code, reply) {
154
154
  const sourceFile = `${sanitizedCode}-${sanitizedSubType}-${sanitizedFileName}`;
155
155
  const downloadPath = path.join(__dirname, 'assets', 'tmp', sourceFile);
156
156
 
157
- await fs.promises.mkdir(path.dirname(downloadPath), { recursive: true });
157
+ await fs.mkdir(path.dirname(downloadPath), { recursive: true });
158
158
  await downloadFileFromS3(bucketName, key, downloadPath);
159
159
 
160
160
  const { name: baseName } = path.parse(sourceFile);
@@ -163,7 +163,7 @@ async function downloadMediaAndCreateFile(code, reply) {
163
163
  : [downloadPath];
164
164
 
165
165
  if (subType === 'document' || subType === 'application') {
166
- await fs.promises.unlink(downloadPath);
166
+ await fs.unlink(downloadPath);
167
167
  }
168
168
 
169
169
  return fileNames;
@@ -0,0 +1,20 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const predictionMetricsSchema = new mongoose.Schema({
4
+ message_id: { type: String, required: true, index: true },
5
+ numero: { type: String, required: true, index: true },
6
+ assistant_id: { type: String, required: true, index: true },
7
+ thread_id: { type: String, required: true },
8
+ prediction_time_ms: { type: Number, required: true },
9
+ retry_count: { type: Number, required: true, default: 1 },
10
+ completed: { type: Boolean, default: true },
11
+ error: { type: String, default: null }
12
+ }, { timestamps: true });
13
+
14
+ predictionMetricsSchema.index({ createdAt: -1 });
15
+ predictionMetricsSchema.index({ assistant_id: 1, createdAt: -1 });
16
+ predictionMetricsSchema.index({ numero: 1, createdAt: -1 });
17
+
18
+ const PredictionMetrics = mongoose.model('PredictionMetrics', predictionMetricsSchema);
19
+
20
+ module.exports = { PredictionMetrics, predictionMetricsSchema };
@@ -6,6 +6,7 @@ const { BaseAssistant } = require('../assistants/BaseAssistant');
6
6
  const { createProvider } = require('../providers/createProvider');
7
7
 
8
8
  const { Thread } = require('../models/threadModel.js');
9
+ const { PredictionMetrics } = require('../models/predictionMetricsModel');
9
10
 
10
11
  const { getCurRow } = require('../helpers/assistantHelper.js');
11
12
  const { runAssistantAndWait, runAssistantWithRetries } = require('../helpers/assistantHelper.js');
@@ -316,7 +317,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
316
317
  if (!patientMsg || thread.stopped) return null;
317
318
 
318
319
  const assistant = getAssistantById(thread.getAssistantId(), thread);
319
- const { run, output, completed, retries } = await withTracing(
320
+ const { run, output, completed, retries, predictionTimeMs } = await withTracing(
320
321
  runAssistantWithRetries,
321
322
  'run_assistant_with_retries',
322
323
  (thread, assistant, runConfig, patientReply) => ({
@@ -326,6 +327,19 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
326
327
  })
327
328
  )(thread, assistant, runOptions, patientReply);
328
329
 
330
+ // Store prediction metrics
331
+ if (output && predictionTimeMs) {
332
+ await PredictionMetrics.create({
333
+ message_id: `${code}-${Date.now()}`,
334
+ numero: code,
335
+ assistant_id: thread.getAssistantId(),
336
+ thread_id: thread.getConversationId(),
337
+ prediction_time_ms: predictionTimeMs,
338
+ retry_count: retries,
339
+ completed: completed
340
+ }).catch(err => console.error('[replyAssistantCore] Failed to store metrics:', err));
341
+ }
342
+
329
343
  return output;
330
344
  };
331
345
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",