@peopl-health/nexus 2.4.9-fix-mime-type → 2.4.10

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,10 +1,11 @@
1
1
  const mongoose = require('mongoose');
2
- const { fetchConversationData, processConversations } = require('../services/conversationService');
3
- const { sendMessage } = require('../core/NexusMessaging');
4
- const { Thread } = require('../models/threadModel');
5
2
  const llmConfig = require('../config/llmConfig');
6
3
  const { Historial_Clinico_ID } = require('../config/airtableConfig');
4
+ const { Thread } = require('../models/threadModel');
5
+ const { withThreadRecovery } = require('../helpers/threadRecoveryHelper');
7
6
  const { getRecordByFilter } = require('../services/airtableService');
7
+ const { fetchConversationData, processConversations } = require('../services/conversationService');
8
+ const { sendMessage } = require('../core/NexusMessaging');
8
9
  const { logger } = require('../utils/logger');
9
10
 
10
11
  const Message = mongoose.models.Message;
@@ -649,7 +650,7 @@ const getOpenAIThreadMessagesController = async (req, res) => {
649
650
  });
650
651
  }
651
652
 
652
- const thread = await Thread.findOne({
653
+ let thread = await Thread.findOne({
653
654
  code: phoneNumber,
654
655
  active: true
655
656
  }).sort({ createdAt: -1 });
@@ -662,7 +663,7 @@ const getOpenAIThreadMessagesController = async (req, res) => {
662
663
  });
663
664
  }
664
665
 
665
- const conversationId = thread.conversation_id;
666
+ let conversationId = thread.conversation_id;
666
667
  logger.info('Thread found - Conversation ID:', conversationId);
667
668
 
668
669
  const provider = llmConfig.getOpenAIProvider({ instantiate: true, variant });
@@ -683,8 +684,23 @@ const getOpenAIThreadMessagesController = async (req, res) => {
683
684
  logger.info('Including runId:', runId);
684
685
  }
685
686
 
687
+ let messages;
688
+ let threadRecreated = false;
689
+
686
690
  logger.info('Calling listMessages with params:', queryParams);
687
- const messages = await provider.listMessages(queryParams);
691
+ messages = await withThreadRecovery(
692
+ async (currentThread = thread) => {
693
+ if (currentThread !== thread) {
694
+ thread = currentThread;
695
+ conversationId = currentThread.getConversationId();
696
+ queryParams.threadId = conversationId;
697
+ threadRecreated = true;
698
+ }
699
+ return await provider.listMessages(queryParams);
700
+ },
701
+ thread,
702
+ variant
703
+ );
688
704
 
689
705
  logger.info(`Retrieved ${messages?.data?.length || 0} messages from OpenAI`);
690
706
 
@@ -696,6 +712,7 @@ const getOpenAIThreadMessagesController = async (req, res) => {
696
712
  assistantId: thread.assistant_id,
697
713
  messages: messages.data || messages,
698
714
  hasMore: messages.has_more || false,
715
+ threadRecreated,
699
716
  pagination: {
700
717
  limit: parseInt(limit),
701
718
  order
@@ -3,6 +3,7 @@ const llmConfig = require('../config/llmConfig.js');
3
3
  const { Thread } = require('../models/threadModel.js');
4
4
  const { createProvider } = require('../providers/createProvider.js');
5
5
  const { withTracing } = require('../utils/tracingDecorator');
6
+ const { withThreadRecovery } = require('./threadRecoveryHelper');
6
7
 
7
8
  const { getRecordByFilter } = require('../services/airtableService.js');
8
9
  const { logger } = require('../utils/logger');
@@ -62,37 +63,43 @@ const runAssistantAndWait = async ({
62
63
  ? { ...conversationConfig, assistant }
63
64
  : conversationConfig;
64
65
 
65
- let run = await provider.runConversation({
66
- threadId: thread.getConversationId(),
67
- assistantId: thread.getAssistantId(),
68
- tools: tools.length > 0 ? tools : undefined,
69
- ...runConfigWithAssistant,
70
- });
71
-
72
- const filter = thread.code ? { code: thread.code, active: true } : null;
73
- if (filter) {
74
- await Thread.updateOne(filter, { $set: { run_id: run.id } });
75
- }
76
-
77
- const maxRetries = polling?.maxRetries ?? DEFAULT_MAX_RETRIES;
78
- let completed = false;
79
-
80
- try {
81
- logger.info('[runAssistantAndWait] Run started', { runId: run.id, threadId: thread.getConversationId(), assistantId: thread.getAssistantId() });
82
- ({run, completed} = await provider.checkRunStatus(assistant, thread.getConversationId(), run.id, 0, maxRetries));
83
- } finally {
84
- if (filter) {
85
- await Thread.updateOne(filter, { $set: { run_id: null } });
86
- }
87
- }
66
+ return await withThreadRecovery(
67
+ async (currentThread = thread) => {
68
+ let run = await provider.runConversation({
69
+ threadId: currentThread.getConversationId(),
70
+ assistantId: currentThread.getAssistantId(),
71
+ tools: tools.length > 0 ? tools : undefined,
72
+ ...runConfigWithAssistant,
73
+ });
74
+
75
+ const filter = currentThread.code ? { code: currentThread.code, active: true } : null;
76
+ if (filter) {
77
+ await Thread.updateOne(filter, { $set: { run_id: run.id } });
78
+ }
79
+
80
+ const maxRetries = polling?.maxRetries ?? DEFAULT_MAX_RETRIES;
81
+ let completed = false;
82
+
83
+ try {
84
+ logger.info('[runAssistantAndWait] Run started', { runId: run.id, threadId: currentThread.getConversationId(), assistantId: currentThread.getAssistantId() });
85
+ ({run, completed} = await provider.checkRunStatus(assistant, currentThread.getConversationId(), run.id, 0, maxRetries));
86
+ } finally {
87
+ if (filter) {
88
+ await Thread.updateOne(filter, { $set: { run_id: null } });
89
+ }
90
+ }
88
91
 
89
- if (!completed) {
90
- return { run: run, completed: false, output: '' };
91
- }
92
+ if (!completed) {
93
+ return { run: run, completed: false, output: '' };
94
+ }
92
95
 
93
- const output = await provider.getRunText({ threadId: thread.getConversationId(), runId: run.id, fallback: '' });
96
+ const output = await provider.getRunText({ threadId: currentThread.getConversationId(), runId: run.id, fallback: '' });
94
97
 
95
- return { completed: true, output };
98
+ return { completed: true, output };
99
+ },
100
+ thread,
101
+ variant
102
+ );
96
103
  };
97
104
 
98
105
  const executeAssistantAttempt = async (thread, assistant, runConfig, attemptNumber) => {
@@ -0,0 +1,58 @@
1
+ const { Thread } = require('../models/threadModel');
2
+ const { createProvider } = require('../providers/createProvider');
3
+ const { logger } = require('../utils/logger');
4
+
5
+ const isThreadNotFoundError = (error) => {
6
+ return error?.status === 404 ||
7
+ error?.code === 'thread_not_found' ||
8
+ error?.code === 'invalid_thread_id' ||
9
+ (error?.message && (
10
+ error.message.includes('No thread found') ||
11
+ error.message.includes('thread does not exist') ||
12
+ error.message.includes('Invalid thread')
13
+ ));
14
+ };
15
+
16
+ const recreateThread = async (thread, variant = 'assistants') => {
17
+ const provider = createProvider({ variant });
18
+ const newConversation = await provider.createConversation({
19
+ metadata: { phoneNumber: thread.code, recreated: true }
20
+ });
21
+
22
+ await Thread.updateOne(
23
+ { code: thread.code, active: true },
24
+ { $set: { conversation_id: newConversation.id } }
25
+ );
26
+
27
+ const updatedThread = await Thread.findOne({ code: thread.code, active: true });
28
+ logger.info('[threadRecovery] Thread recreated', {
29
+ oldId: thread.conversation_id,
30
+ newId: newConversation.id,
31
+ code: thread.code
32
+ });
33
+
34
+ return updatedThread;
35
+ };
36
+
37
+ const withThreadRecovery = async (operation, thread, variant = 'assistants') => {
38
+ try {
39
+ return await operation();
40
+ } catch (error) {
41
+ if (isThreadNotFoundError(error) && thread) {
42
+ logger.warn('[threadRecovery] Thread not found, recreating', {
43
+ conversationId: thread.getConversationId?.() || thread.conversation_id,
44
+ code: thread.code
45
+ });
46
+
47
+ const recoveredThread = await recreateThread(thread, variant);
48
+ return await operation(recoveredThread);
49
+ }
50
+ throw error;
51
+ }
52
+ };
53
+
54
+ module.exports = {
55
+ isThreadNotFoundError,
56
+ recreateThread,
57
+ withThreadRecovery
58
+ };
@@ -14,6 +14,7 @@ const { getThread, getThreadInfo } = require('../helpers/threadHelper.js');
14
14
  const { withTracing } = require('../utils/tracingDecorator.js');
15
15
  const { processThreadMessage } = require('../helpers/processHelper.js');
16
16
  const { getLastMessages, updateMessageRecord } = require('../helpers/messageHelper.js');
17
+ const { withThreadRecovery } = require('../helpers/threadRecoveryHelper.js');
17
18
  const { combineImagesToPDF, cleanupFiles } = require('../helpers/filesHelper.js');
18
19
  const { logger } = require('../utils/logger');
19
20
 
@@ -137,13 +138,19 @@ const getAssistantById = (assistant_id, thread) => {
137
138
  const createAssistant = async (code, assistant_id, messages=[], force=false) => {
138
139
  const findThread = await Thread.findOne({ code: code });
139
140
  logger.info('[createAssistant] findThread', findThread);
140
- if (findThread && findThread.getConversationId()) {
141
+ if (findThread && findThread.getConversationId() && !force) {
141
142
  logger.info('[createAssistant] Thread already exists');
142
143
  const updateFields = { active: true, stopped: false };
143
144
  Thread.setAssistantId(updateFields, assistant_id);
144
145
  await Thread.updateOne({ code: code }, { $set: updateFields });
145
146
  return findThread;
146
147
  }
148
+
149
+ if (force && findThread?.getConversationId()) {
150
+ const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
151
+ await provider.deleteConversation(findThread.getConversationId());
152
+ logger.info('[createAssistant] Deleted old conversation, will create new one');
153
+ }
147
154
 
148
155
  const curRow = await getCurRow(Historial_Clinico_ID, code);
149
156
  logger.info('[createAssistant] curRow', curRow[0]);
@@ -153,7 +160,6 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
153
160
  const assistant = getAssistantById(assistant_id, null);
154
161
  const initialThread = await assistant.create(code, curRow[0]);
155
162
 
156
- // Add new messages to memory
157
163
  const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
158
164
  for (const message of messages) {
159
165
  await provider.addMessage({
@@ -177,29 +183,32 @@ const createAssistant = async (code, assistant_id, messages=[], force=false) =>
177
183
  const updatedThread = await Thread.findOneAndUpdate(condition, {run_id: null, ...thread}, options);
178
184
  logger.info('[createAssistant] Updated thread:', updatedThread);
179
185
 
180
- // Delete previous thread
181
- if (force) {
182
- await provider.deleteConversation(findThread.getConversationId());
183
- }
184
-
185
186
  return thread;
186
187
  };
187
188
 
188
189
  const addMsgAssistant = async (code, inMessages, role = 'user', reply = false) => {
189
190
  try {
190
- const thread = await Thread.findOne({ code: code });
191
+ let thread = await Thread.findOne({ code: code });
191
192
  logger.info(thread);
192
193
  if (thread === null) return null;
193
194
 
194
195
  const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
195
- for (const message of inMessages) {
196
- logger.info(message);
197
- await provider.addMessage({
198
- threadId: thread.getConversationId(),
199
- role: role,
200
- content: message
201
- });
202
- }
196
+
197
+ await withThreadRecovery(
198
+ async (recoveredThread = thread) => {
199
+ thread = recoveredThread;
200
+ for (const message of inMessages) {
201
+ logger.info(message);
202
+ await provider.addMessage({
203
+ threadId: thread.getConversationId(),
204
+ role: role,
205
+ content: message
206
+ });
207
+ }
208
+ },
209
+ thread,
210
+ process.env.VARIANT || 'assistants'
211
+ );
203
212
 
204
213
  if (!reply) return null;
205
214
 
@@ -322,9 +331,15 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
322
331
  const allTempFiles = processResults.flatMap(r => r.tempFiles || []);
323
332
 
324
333
  if (allMessagesToAdd.length > 0) {
325
- const threadId = finalThread.getConversationId();
326
334
  logger.info(`[replyAssistantCore] Adding ${allMessagesToAdd.length} messages to thread in batch`);
327
- await provider.addMessage({ threadId, messages: allMessagesToAdd });
335
+ await withThreadRecovery(
336
+ async (thread = finalThread) => {
337
+ const threadId = thread.getConversationId();
338
+ await provider.addMessage({ threadId, messages: allMessagesToAdd });
339
+ },
340
+ finalThread,
341
+ process.env.VARIANT || 'assistants'
342
+ );
328
343
  }
329
344
 
330
345
  await Promise.all(processResults.map(r => updateMessageRecord(r.reply, finalThread)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "2.4.9-fix-mime-type",
3
+ "version": "2.4.10",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",