@peopl-health/nexus 2.5.7 → 2.5.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 +2 -2
- package/lib/controllers/assistantController.js +1 -1
- package/lib/controllers/threadController.js +151 -0
- package/lib/core/NexusMessaging.js +21 -1
- package/lib/helpers/filesHelper.js +1 -1
- package/lib/helpers/threadHelper.js +44 -13
- package/lib/helpers/twilioHelper.js +30 -6
- package/lib/index.js +2 -2
- package/lib/models/threadModel.js +4 -3
- package/lib/providers/OpenAIAssistantsProvider.js +2 -2
- package/lib/providers/OpenAIResponsesProvider.js +2 -2
- package/lib/providers/OpenAIResponsesProviderTools.js +1 -1
- package/lib/routes/index.js +15 -1
- package/lib/services/airtableService.js +3 -3
- package/lib/services/assistantServiceCore.js +7 -7
- package/lib/services/conversationService.js +2 -2
- package/lib/services/preprocessingHooks.js +1 -1
- package/lib/storage/MongoStorage.js +6 -6
- package/lib/utils/errorHandler.js +1 -1
- package/package.json +1 -1
|
@@ -39,7 +39,7 @@ class DoctorScheduleAssistant extends BaseAssistant {
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
handler: async (payload = {}) => {
|
|
42
|
-
logger.info('[DoctorScheduleAssistant] storePatientAppointment', payload);
|
|
42
|
+
logger.info('[DoctorScheduleAssistant] storePatientAppointment', { payload });
|
|
43
43
|
|
|
44
44
|
if (!payload.contact_number || !payload.appointment_type) {
|
|
45
45
|
return JSON.stringify({
|
|
@@ -59,7 +59,7 @@ class DoctorScheduleAssistant extends BaseAssistant {
|
|
|
59
59
|
notes: payload.notes || null
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
logger.info('[DoctorScheduleAssistant] Appointment captured', appointment);
|
|
62
|
+
logger.info('[DoctorScheduleAssistant] Appointment captured', { appointment });
|
|
63
63
|
|
|
64
64
|
return JSON.stringify({ success: true, appointment });
|
|
65
65
|
}
|
|
@@ -35,7 +35,7 @@ const addInsAssistantController = async (req, res) => {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const addMsgAssistantController = async (req, res) => {
|
|
38
|
-
const { code, messages, role = '
|
|
38
|
+
const { code, messages, role = 'system', reply = false } = req.body;
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
41
|
const assistantReply = await addMsgAssistant(code, messages, role, reply);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const { Thread } = require('../models/threadModel');
|
|
2
|
+
const { logger } = require('../utils/logger');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Update review status for a specific thread by code
|
|
6
|
+
*/
|
|
7
|
+
const updateThreadReviewStatus = async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const { code } = req.params;
|
|
10
|
+
const { review } = req.body;
|
|
11
|
+
|
|
12
|
+
if (typeof review !== 'boolean') {
|
|
13
|
+
return res.status(400).json({
|
|
14
|
+
success: false,
|
|
15
|
+
message: 'Review status must be a boolean value'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const thread = await Thread.findOneAndUpdate(
|
|
20
|
+
{ code },
|
|
21
|
+
{ review },
|
|
22
|
+
{ new: true }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!thread) {
|
|
26
|
+
return res.status(404).json({
|
|
27
|
+
success: false,
|
|
28
|
+
message: 'Thread not found'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
logger.info('[updateThreadReviewStatus] Thread review status updated', {
|
|
33
|
+
code: code.substring(0, 3) + '***' + code.slice(-4),
|
|
34
|
+
review
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
res.json({
|
|
38
|
+
success: true,
|
|
39
|
+
message: 'Thread review status updated successfully',
|
|
40
|
+
thread: {
|
|
41
|
+
code: thread.code,
|
|
42
|
+
review: thread.review,
|
|
43
|
+
updatedAt: thread.updatedAt
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error('[updateThreadReviewStatus] Error updating thread review status', { error });
|
|
48
|
+
res.status(500).json({
|
|
49
|
+
success: false,
|
|
50
|
+
message: 'Internal server error'
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Update review status for all threads at once
|
|
57
|
+
*/
|
|
58
|
+
const updateAllThreadsReviewStatus = async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const { review } = req.body;
|
|
61
|
+
|
|
62
|
+
if (typeof review !== 'boolean') {
|
|
63
|
+
return res.status(400).json({
|
|
64
|
+
success: false,
|
|
65
|
+
message: 'Review status must be a boolean value'
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = await Thread.updateMany(
|
|
70
|
+
{},
|
|
71
|
+
{ review }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
logger.info('[updateAllThreadsReviewStatus] All threads review status updated', {
|
|
75
|
+
review,
|
|
76
|
+
modifiedCount: result.modifiedCount
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
res.json({
|
|
80
|
+
success: true,
|
|
81
|
+
message: `Successfully updated review status for ${result.modifiedCount} threads`,
|
|
82
|
+
modifiedCount: result.modifiedCount,
|
|
83
|
+
review
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
logger.error('[updateAllThreadsReviewStatus] Error updating all threads review status', { error });
|
|
87
|
+
res.status(500).json({
|
|
88
|
+
success: false,
|
|
89
|
+
message: 'Internal server error'
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get threads by review status
|
|
96
|
+
*/
|
|
97
|
+
const getThreadsByReviewStatus = async (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
const { review } = req.query;
|
|
100
|
+
const { page = 1, limit = 20 } = req.query;
|
|
101
|
+
|
|
102
|
+
const filter = {};
|
|
103
|
+
if (review !== undefined) {
|
|
104
|
+
filter.review = review === 'true';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const skip = (parseInt(page) - 1) * parseInt(limit);
|
|
108
|
+
|
|
109
|
+
const threads = await Thread.find(filter)
|
|
110
|
+
.select('code review active stopped createdAt updatedAt')
|
|
111
|
+
.sort({ updatedAt: -1 })
|
|
112
|
+
.skip(skip)
|
|
113
|
+
.limit(parseInt(limit));
|
|
114
|
+
|
|
115
|
+
const total = await Thread.countDocuments(filter);
|
|
116
|
+
|
|
117
|
+
logger.info('[getThreadsByReviewStatus] Retrieved threads by review status', {
|
|
118
|
+
filter,
|
|
119
|
+
count: threads.length,
|
|
120
|
+
total,
|
|
121
|
+
page: parseInt(page),
|
|
122
|
+
limit: parseInt(limit)
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
res.json({
|
|
126
|
+
success: true,
|
|
127
|
+
threads: threads.map(thread => ({
|
|
128
|
+
...thread.toObject(),
|
|
129
|
+
code: thread.code.substring(0, 3) + '***' + thread.code.slice(-4)
|
|
130
|
+
})),
|
|
131
|
+
pagination: {
|
|
132
|
+
page: parseInt(page),
|
|
133
|
+
limit: parseInt(limit),
|
|
134
|
+
total,
|
|
135
|
+
totalPages: Math.ceil(total / parseInt(limit))
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
logger.error('[getThreadsByReviewStatus] Error retrieving threads by review status', { error });
|
|
140
|
+
res.status(500).json({
|
|
141
|
+
success: false,
|
|
142
|
+
message: 'Internal server error'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
updateThreadReviewStatus,
|
|
149
|
+
updateAllThreadsReviewStatus,
|
|
150
|
+
getThreadsByReviewStatus
|
|
151
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const { airtable, getBase } = require('../config/airtableConfig');
|
|
2
2
|
const { Message } = require('../models/messageModel');
|
|
3
|
+
const { Thread } = require('../models/threadModel');
|
|
3
4
|
const { addMsgAssistant, replyAssistant } = require('../services/assistantService');
|
|
4
5
|
const { createProvider } = require('../adapters/registry');
|
|
5
6
|
const runtimeConfig = require('../config/runtimeConfig');
|
|
6
7
|
const { hasPreprocessingHandler, invokePreprocessingHandler } = require('../services/preprocessingHooks');
|
|
8
|
+
const { ensureThreadExists } = require('../helpers/threadHelper');
|
|
7
9
|
const { logger } = require('../utils/logger');
|
|
8
10
|
|
|
9
11
|
const mongoose = require('mongoose');
|
|
@@ -312,7 +314,7 @@ class NexusMessaging {
|
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
if (messageData.origin !== 'assistant' && messageData.code && messageContent) {
|
|
315
|
-
const skipSystemMessage = messageData._fromConversationReply === true;
|
|
317
|
+
const skipSystemMessage = messageData._fromConversationReply === true || providerStoresMessage;
|
|
316
318
|
await addMsgAssistant(
|
|
317
319
|
messageData.code,
|
|
318
320
|
[messageContent],
|
|
@@ -354,6 +356,24 @@ class NexusMessaging {
|
|
|
354
356
|
|
|
355
357
|
const chatId = messageData.from || messageData.From;
|
|
356
358
|
|
|
359
|
+
// Ensure thread exists in background for new numbers
|
|
360
|
+
if (chatId) {
|
|
361
|
+
ensureThreadExists(chatId);
|
|
362
|
+
|
|
363
|
+
// Reset review status to false when new message arrives
|
|
364
|
+
try {
|
|
365
|
+
await Thread.updateOne(
|
|
366
|
+
{ code: chatId },
|
|
367
|
+
{ review: false }
|
|
368
|
+
);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.error('[processIncomingMessage] Failed to reset thread review status', {
|
|
371
|
+
code: chatId.substring(0, 3) + '***' + chatId.slice(-4),
|
|
372
|
+
error
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
357
377
|
if (chatId && hasPreprocessingHandler()) {
|
|
358
378
|
const stop = await invokePreprocessingHandler({
|
|
359
379
|
code: chatId,
|
|
@@ -21,7 +21,7 @@ async function convertPdfToImages(pdfName, existingPdfPath = null) {
|
|
|
21
21
|
|
|
22
22
|
return new Promise((resolve, reject) => {
|
|
23
23
|
const args = ['-jpeg', pdfPath, outputPattern];
|
|
24
|
-
logger.info('[convertPdfToImages] Running: pdftoppm', args.join(' '));
|
|
24
|
+
logger.info('[convertPdfToImages] Running: pdftoppm', { args: args.join(' ') });
|
|
25
25
|
|
|
26
26
|
const timeout = 30000;
|
|
27
27
|
let timedOut = false;
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
const { Thread } = require('../models/threadModel.js');
|
|
2
2
|
const { createProvider } = require('../providers/createProvider.js');
|
|
3
3
|
const llmConfig = require('../config/llmConfig.js');
|
|
4
|
-
|
|
5
|
-
const log = (level, context, data, error = null) => {
|
|
6
|
-
const safeData = { ...data };
|
|
7
|
-
if (safeData.code) safeData.code = `${safeData.code.substring(0, 3)}***${safeData.code.slice(-4)}`;
|
|
8
|
-
console[level](`[${context}]`, error ? { error: error?.message || String(error), ...safeData } : safeData);
|
|
9
|
-
};
|
|
4
|
+
const { logger } = require('../utils/logger');
|
|
10
5
|
|
|
11
6
|
const TERMINAL_STATUSES = ['cancelled', 'expired', 'completed', 'failed', 'incomplete'];
|
|
12
7
|
const MAX_ATTEMPTS = 30;
|
|
@@ -15,7 +10,7 @@ const MAX_WAIT_MS = 30000;
|
|
|
15
10
|
const getThread = async (code, message = null) => {
|
|
16
11
|
try {
|
|
17
12
|
let thread = await Thread.findOne({ code: code });
|
|
18
|
-
|
|
13
|
+
logger.info('[getThread]', { code: code, hasThread: !!thread, hasRunId: !!(thread?.run_id) });
|
|
19
14
|
|
|
20
15
|
if (!thread && message) {
|
|
21
16
|
thread = new Thread({ code, active: true });
|
|
@@ -32,18 +27,18 @@ const getThread = async (code, message = null) => {
|
|
|
32
27
|
const run = await provider.getRun({ threadId: thread.getConversationId(), runId: thread.run_id });
|
|
33
28
|
|
|
34
29
|
if (TERMINAL_STATUSES.includes(run.status)) {
|
|
35
|
-
|
|
30
|
+
logger.info('[getThread_terminal]', { code: code, status: run.status, attempts });
|
|
36
31
|
break;
|
|
37
32
|
}
|
|
38
33
|
} catch (error) {
|
|
39
|
-
|
|
34
|
+
logger.error('[getThread_getRun_failed]', { code: code, attempt: attempts, error });
|
|
40
35
|
break;
|
|
41
36
|
}
|
|
42
37
|
await new Promise(r => setTimeout(r, 1000));
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
if (attempts >= MAX_ATTEMPTS || (Date.now() - start) >= MAX_WAIT_MS) {
|
|
46
|
-
|
|
41
|
+
logger.warn('[getThread_timeout]', { code: code, attempts, elapsed: Date.now() - start });
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
await Thread.updateOne({ code, active: true }, { $set: { run_id: null } });
|
|
@@ -52,7 +47,7 @@ const getThread = async (code, message = null) => {
|
|
|
52
47
|
|
|
53
48
|
return thread;
|
|
54
49
|
} catch (error) {
|
|
55
|
-
|
|
50
|
+
logger.error('[getThread]', { code: code, error });
|
|
56
51
|
return null;
|
|
57
52
|
}
|
|
58
53
|
};
|
|
@@ -62,12 +57,48 @@ const getThreadInfo = async (code) => {
|
|
|
62
57
|
let thread = await Thread.findOne({ code: code });
|
|
63
58
|
return thread;
|
|
64
59
|
} catch (error) {
|
|
65
|
-
|
|
60
|
+
logger.error('[getThreadInfo]', { code: code, error });
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const createPlaceholderThread = async (code) => {
|
|
66
|
+
try {
|
|
67
|
+
const existingThread = await Thread.findOne({ code });
|
|
68
|
+
if (existingThread) {
|
|
69
|
+
logger.info('[createPlaceholderThread_exists]', { code: code });
|
|
70
|
+
return existingThread;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const placeholderThread = new Thread({
|
|
74
|
+
code,
|
|
75
|
+
assistant_id: null,
|
|
76
|
+
conversation_id: null,
|
|
77
|
+
stopped: true
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await placeholderThread.save();
|
|
81
|
+
logger.info('[createPlaceholderThread_created]', { code: code });
|
|
82
|
+
return placeholderThread;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
logger.error('[createPlaceholderThread]', { code: code, error });
|
|
66
85
|
return null;
|
|
67
86
|
}
|
|
68
87
|
};
|
|
69
88
|
|
|
89
|
+
const ensureThreadExists = (code) => {
|
|
90
|
+
setImmediate(async () => {
|
|
91
|
+
try {
|
|
92
|
+
await createPlaceholderThread(code);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
logger.error('[ensureThreadExists_background]', { code: code, error });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
70
99
|
module.exports = {
|
|
71
100
|
getThread,
|
|
72
|
-
getThreadInfo
|
|
101
|
+
getThreadInfo,
|
|
102
|
+
createPlaceholderThread,
|
|
103
|
+
ensureThreadExists
|
|
73
104
|
};
|
|
@@ -26,22 +26,46 @@ function convertTwilioToInternalFormat(twilioMessage) {
|
|
|
26
26
|
|
|
27
27
|
async function downloadMediaFromTwilio(mediaUrl, logger) {
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
const authHeader = `Basic ${Buffer.from(
|
|
30
|
+
`${process.env.TWILIO_ACCOUNT_SID}:${process.env.TWILIO_AUTH_TOKEN}`
|
|
31
|
+
).toString('base64')}`;
|
|
32
|
+
|
|
33
|
+
logger.info('[TwilioMedia] Starting download', {
|
|
34
|
+
url: mediaUrl,
|
|
35
|
+
hasAccountSid: !!process.env.TWILIO_ACCOUNT_SID,
|
|
36
|
+
hasAuthToken: !!process.env.TWILIO_AUTH_TOKEN,
|
|
37
|
+
accountSidLength: process.env.TWILIO_ACCOUNT_SID?.length || 0,
|
|
38
|
+
authHeaderSample: authHeader.substring(0, 20) + '...'
|
|
39
|
+
});
|
|
30
40
|
|
|
31
41
|
const response = await axios({
|
|
32
42
|
method: 'GET',
|
|
33
43
|
url: mediaUrl,
|
|
34
44
|
responseType: 'arraybuffer',
|
|
45
|
+
timeout: 30000,
|
|
35
46
|
headers: {
|
|
36
|
-
'Authorization':
|
|
37
|
-
`${process.env.TWILIO_ACCOUNT_SID}:${process.env.TWILIO_AUTH_TOKEN}`
|
|
38
|
-
).toString('base64')}`
|
|
47
|
+
'Authorization': authHeader
|
|
39
48
|
}
|
|
40
49
|
});
|
|
41
|
-
|
|
50
|
+
|
|
51
|
+
logger.info('[TwilioMedia] Download successful', {
|
|
52
|
+
status: response.status,
|
|
53
|
+
contentType: response.headers['content-type'],
|
|
54
|
+
contentLength: response.headers['content-length'],
|
|
55
|
+
dataSize: response.data?.length || 0
|
|
56
|
+
});
|
|
57
|
+
|
|
42
58
|
return Buffer.from(response.data);
|
|
43
59
|
} catch (error) {
|
|
44
|
-
logger.error(
|
|
60
|
+
logger.error('[TwilioMedia] Download failed', {
|
|
61
|
+
message: error.message,
|
|
62
|
+
status: error.response?.status,
|
|
63
|
+
statusText: error.response?.statusText,
|
|
64
|
+
responseHeaders: error.response?.headers,
|
|
65
|
+
responseData: error.response?.data ? error.response.data.toString().substring(0, 500) : null,
|
|
66
|
+
url: mediaUrl,
|
|
67
|
+
hasCredentials: !!(process.env.TWILIO_ACCOUNT_SID && process.env.TWILIO_AUTH_TOKEN)
|
|
68
|
+
});
|
|
45
69
|
throw error;
|
|
46
70
|
}
|
|
47
71
|
}
|
package/lib/index.js
CHANGED
|
@@ -39,7 +39,7 @@ class Nexus {
|
|
|
39
39
|
try {
|
|
40
40
|
setDefaultInstance(this.messaging);
|
|
41
41
|
} catch (err) {
|
|
42
|
-
logger.warn('[Nexus] Failed to set default messaging instance
|
|
42
|
+
logger.warn('[Nexus] Failed to set default messaging instance', { error: err?.message || err });
|
|
43
43
|
}
|
|
44
44
|
this.storage = null;
|
|
45
45
|
this.messageParser = null;
|
|
@@ -147,7 +147,7 @@ class Nexus {
|
|
|
147
147
|
llmConfigModule.setOpenAIClient(providerInstance.getClient());
|
|
148
148
|
}
|
|
149
149
|
} catch (err) {
|
|
150
|
-
logger.warn('[Nexus] Failed to expose OpenAI provider
|
|
150
|
+
logger.warn('[Nexus] Failed to expose OpenAI provider', { error: err?.message || err });
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -2,15 +2,16 @@ const mongoose = require('mongoose');
|
|
|
2
2
|
|
|
3
3
|
const threadSchema = new mongoose.Schema({
|
|
4
4
|
code: { type: String, required: true },
|
|
5
|
-
assistant_id: { type: String,
|
|
6
|
-
conversation_id: { type: String,
|
|
5
|
+
assistant_id: { type: String, default: null },
|
|
6
|
+
conversation_id: { type: String, default: null },
|
|
7
7
|
prompt_id: { type: String, default: null },
|
|
8
|
-
thread_id: { type: String,
|
|
8
|
+
thread_id: { type: String, default: null },
|
|
9
9
|
patient_id: { type: String, default: null },
|
|
10
10
|
run_id: { type: String, default: null },
|
|
11
11
|
nombre: { type: String, default: null },
|
|
12
12
|
active: { type: Boolean, default: true },
|
|
13
13
|
stopped: { type: Boolean, default: false },
|
|
14
|
+
review: { type: Boolean, default: false },
|
|
14
15
|
nextSid: { type: [String], default: [] }
|
|
15
16
|
}, { timestamps: true });
|
|
16
17
|
|
|
@@ -282,7 +282,7 @@ class OpenAIAssistantsProvider {
|
|
|
282
282
|
const result = await assistant.executeTool(name, args);
|
|
283
283
|
outputs.push({ tool_call_id: call.id, output: typeof result === 'string' ? result : JSON.stringify(result) });
|
|
284
284
|
} catch (error) {
|
|
285
|
-
logger.error('[OpenAIAssistantsProvider] Tool execution failed', error);
|
|
285
|
+
logger.error('[OpenAIAssistantsProvider] Tool execution failed', { error });
|
|
286
286
|
outputs.push({
|
|
287
287
|
tool_call_id: call.id,
|
|
288
288
|
output: JSON.stringify({ success: false, error: error?.message || 'Tool execution failed' })
|
|
@@ -331,7 +331,7 @@ class OpenAIAssistantsProvider {
|
|
|
331
331
|
|
|
332
332
|
return this.checkRunStatus(assistant, threadId, runId, attempt + 1, maxRetries);
|
|
333
333
|
} catch (error) {
|
|
334
|
-
logger.error('Error checking run status
|
|
334
|
+
logger.error('Error checking run status', { error });
|
|
335
335
|
return { run: null, completed: false };
|
|
336
336
|
}
|
|
337
337
|
}
|
|
@@ -258,7 +258,7 @@ class OpenAIResponsesProvider {
|
|
|
258
258
|
topP,
|
|
259
259
|
temperature,
|
|
260
260
|
maxOutputTokens,
|
|
261
|
-
truncationStrategy,
|
|
261
|
+
truncationStrategy = 'auto',
|
|
262
262
|
tools = [],
|
|
263
263
|
model,
|
|
264
264
|
assistant,
|
|
@@ -287,7 +287,7 @@ class OpenAIResponsesProvider {
|
|
|
287
287
|
instructions: additionalInstructions || instructions,
|
|
288
288
|
input: inputData,
|
|
289
289
|
metadata, top_p: topP, temperature, max_output_tokens: maxOutputTokens,
|
|
290
|
-
|
|
290
|
+
truncation: truncationStrategy,
|
|
291
291
|
tools: transformToolsForResponsesAPIUtil(this.variant, tools),
|
|
292
292
|
}), { providerName: PROVIDER_NAME });
|
|
293
293
|
|
|
@@ -11,7 +11,7 @@ async function executeFunctionCall(assistant, call, metadata = {}) {
|
|
|
11
11
|
} catch (error) {
|
|
12
12
|
result = { error: error?.message || 'Tool execution failed' };
|
|
13
13
|
success = false;
|
|
14
|
-
logger.error('[OpenAIResponsesProvider] Tool execution failed', error);
|
|
14
|
+
logger.error('[OpenAIResponsesProvider] Tool execution failed', { error });
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const toolData = {
|
package/lib/routes/index.js
CHANGED
|
@@ -66,6 +66,12 @@ const templateRouteDefinitions = {
|
|
|
66
66
|
'DELETE /:id': 'deleteTemplate'
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
const threadRouteDefinitions = {
|
|
70
|
+
'PUT /review/:code': 'updateThreadReviewStatus',
|
|
71
|
+
'PUT /review/all': 'updateAllThreadsReviewStatus',
|
|
72
|
+
'GET /review': 'getThreadsByReviewStatus'
|
|
73
|
+
};
|
|
74
|
+
|
|
69
75
|
// Helper function to create Express router from route definitions
|
|
70
76
|
const createRouter = (routeDefinitions, controllers) => {
|
|
71
77
|
const router = express.Router();
|
|
@@ -93,6 +99,7 @@ const patientController = require('../controllers/patientController');
|
|
|
93
99
|
const qualityMessageController = require('../controllers/qualityMessageController');
|
|
94
100
|
const templateController = require('../controllers/templateController');
|
|
95
101
|
const templateFlowController = require('../controllers/templateFlowController');
|
|
102
|
+
const threadController = require('../controllers/threadController');
|
|
96
103
|
const uploadController = require('../controllers/uploadController');
|
|
97
104
|
|
|
98
105
|
// Built-in controllers mapping
|
|
@@ -152,7 +159,12 @@ const builtInControllers = {
|
|
|
152
159
|
deleteFlow: templateFlowController.deleteFlow,
|
|
153
160
|
submitForApproval: templateController.submitForApproval,
|
|
154
161
|
checkApprovalStatus: templateController.checkApprovalStatus,
|
|
155
|
-
deleteTemplate: templateController.deleteTemplate
|
|
162
|
+
deleteTemplate: templateController.deleteTemplate,
|
|
163
|
+
|
|
164
|
+
// Thread controllers
|
|
165
|
+
updateThreadReviewStatus: threadController.updateThreadReviewStatus,
|
|
166
|
+
updateAllThreadsReviewStatus: threadController.updateAllThreadsReviewStatus,
|
|
167
|
+
getThreadsByReviewStatus: threadController.getThreadsByReviewStatus
|
|
156
168
|
};
|
|
157
169
|
|
|
158
170
|
// Helper function to setup all default routes using built-in controllers
|
|
@@ -164,6 +176,7 @@ const setupDefaultRoutes = (app) => {
|
|
|
164
176
|
app.use('/api/message', createRouter(messageRouteDefinitions, builtInControllers));
|
|
165
177
|
app.use('/api/patient', createRouter(patientRouteDefinitions, builtInControllers));
|
|
166
178
|
app.use('/api/template', createRouter(templateRouteDefinitions, builtInControllers));
|
|
179
|
+
app.use('/api/thread', createRouter(threadRouteDefinitions, builtInControllers));
|
|
167
180
|
};
|
|
168
181
|
|
|
169
182
|
module.exports = {
|
|
@@ -175,6 +188,7 @@ module.exports = {
|
|
|
175
188
|
messageRoutes: messageRouteDefinitions,
|
|
176
189
|
patientRoutes: patientRouteDefinitions,
|
|
177
190
|
templateRoutes: templateRouteDefinitions,
|
|
191
|
+
threadRoutes: threadRouteDefinitions,
|
|
178
192
|
|
|
179
193
|
// Helper functions
|
|
180
194
|
createRouter,
|
|
@@ -10,7 +10,7 @@ async function addRecord(baseID, tableName, fields) {
|
|
|
10
10
|
logger.info('Record added at', { tableName });
|
|
11
11
|
return record;
|
|
12
12
|
} catch (error) {
|
|
13
|
-
logger.error('Error adding record
|
|
13
|
+
logger.error('Error adding record', { error });
|
|
14
14
|
throw error;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -31,7 +31,7 @@ async function getRecords(baseID, tableName) {
|
|
|
31
31
|
});
|
|
32
32
|
return records;
|
|
33
33
|
} catch (error) {
|
|
34
|
-
logger.error('Error fetching records
|
|
34
|
+
logger.error('Error fetching records', { error });
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -105,7 +105,7 @@ async function addLinkedRecord(baseID, targetTable, fields, linkConfig) {
|
|
|
105
105
|
const record = await addRecord(baseID, targetTable, [{ fields }]);
|
|
106
106
|
return record;
|
|
107
107
|
} catch (error) {
|
|
108
|
-
|
|
108
|
+
logger.error('[addLinkedRecord] Error adding linked record', { error });
|
|
109
109
|
throw error;
|
|
110
110
|
}
|
|
111
111
|
}
|
|
@@ -17,7 +17,7 @@ const { logger } = require('../utils/logger');
|
|
|
17
17
|
|
|
18
18
|
const createAssistantCore = async (code, assistant_id, messages = [], force = false) => {
|
|
19
19
|
const findThread = await Thread.findOne({ code: code });
|
|
20
|
-
logger.info('[createAssistantCore] findThread', findThread);
|
|
20
|
+
logger.info('[createAssistantCore] findThread', { findThread });
|
|
21
21
|
|
|
22
22
|
if (findThread && findThread.getConversationId() && !force) {
|
|
23
23
|
logger.info('[createAssistantCore] Thread already exists');
|
|
@@ -34,7 +34,7 @@ const createAssistantCore = async (code, assistant_id, messages = [], force = fa
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const curRow = await getCurRow(Historial_Clinico_ID, code);
|
|
37
|
-
logger.info('[createAssistantCore] curRow', curRow[0]);
|
|
37
|
+
logger.info('[createAssistantCore] curRow', { curRow: curRow[0] });
|
|
38
38
|
const nombre = curRow?.[0]?.['name'] || null;
|
|
39
39
|
const patientId = curRow?.[0]?.['record_id'] || null;
|
|
40
40
|
|
|
@@ -63,7 +63,7 @@ const createAssistantCore = async (code, assistant_id, messages = [], force = fa
|
|
|
63
63
|
const condition = { $or: [{ code: code }] };
|
|
64
64
|
const options = { new: true, upsert: true };
|
|
65
65
|
const updatedThread = await Thread.findOneAndUpdate(condition, { run_id: null, ...thread }, options);
|
|
66
|
-
logger.info('[createAssistantCore] Updated thread
|
|
66
|
+
logger.info('[createAssistantCore] Updated thread', { updatedThread });
|
|
67
67
|
|
|
68
68
|
return { success: true, assistant_id, thread: updatedThread };
|
|
69
69
|
} catch (error) {
|
|
@@ -72,7 +72,7 @@ const createAssistantCore = async (code, assistant_id, messages = [], force = fa
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
const addMsgAssistantCore = async (code, inMessages, role = '
|
|
75
|
+
const addMsgAssistantCore = async (code, inMessages, role = 'system', reply = false, skipSystemMessage = false) => {
|
|
76
76
|
const thread = await getThread(code);
|
|
77
77
|
if (!thread) return null;
|
|
78
78
|
|
|
@@ -105,7 +105,7 @@ const addMsgAssistantCore = async (code, inMessages, role = 'user', reply = fals
|
|
|
105
105
|
raw: { role: role }
|
|
106
106
|
});
|
|
107
107
|
} catch (err) {
|
|
108
|
-
logger.error('[addMsgAssistantCore] Error saving system message
|
|
108
|
+
logger.error('[addMsgAssistantCore] Error saving system message', { err });
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
@@ -121,7 +121,7 @@ const addMsgAssistantCore = async (code, inMessages, role = 'user', reply = fals
|
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
const addInstructionCore = async (code, instruction, role = '
|
|
124
|
+
const addInstructionCore = async (code, instruction, role = 'system') => {
|
|
125
125
|
const thread = await getThread(code);
|
|
126
126
|
if (!thread) return null;
|
|
127
127
|
|
|
@@ -149,7 +149,7 @@ const addInstructionCore = async (code, instruction, role = 'user') => {
|
|
|
149
149
|
raw: { role: role }
|
|
150
150
|
});
|
|
151
151
|
} catch (err) {
|
|
152
|
-
logger.error('[addInstructionCore] Error saving instruction message
|
|
152
|
+
logger.error('[addInstructionCore] Error saving instruction message', { err });
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
logger.info('[addInstructionCore] Run response', { output: runResult?.output });
|
|
@@ -89,7 +89,7 @@ const processConversations = async (conversations, nameMap, unreadMap) => {
|
|
|
89
89
|
}
|
|
90
90
|
return createConversation(conv, index);
|
|
91
91
|
} catch (error) {
|
|
92
|
-
logger.error(`Error processing conversation ${index}
|
|
92
|
+
logger.error(`Error processing conversation ${index}`, { error });
|
|
93
93
|
return createConversation(conv, index, { name: 'Error Processing', lastMessage: 'Error processing conversation' });
|
|
94
94
|
}
|
|
95
95
|
});
|
|
@@ -97,7 +97,7 @@ const processConversations = async (conversations, nameMap, unreadMap) => {
|
|
|
97
97
|
logger.info(`Processed ${processedConversations.length} conversations`);
|
|
98
98
|
return processedConversations;
|
|
99
99
|
} catch (error) {
|
|
100
|
-
logger.error('Error in conversation mapping
|
|
100
|
+
logger.error('Error in conversation mapping', { error });
|
|
101
101
|
return [];
|
|
102
102
|
}
|
|
103
103
|
};
|
|
@@ -18,7 +18,7 @@ const invokePreprocessingHandler = async (payload) => {
|
|
|
18
18
|
const result = await preprocessingHandler(payload);
|
|
19
19
|
return Boolean(result);
|
|
20
20
|
} catch (error) {
|
|
21
|
-
logger.warn('[PreprocessingHooks] Handler threw an error
|
|
21
|
+
logger.warn('[PreprocessingHooks] Handler threw an error', { error: error?.message || error });
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
24
|
};
|
|
@@ -52,7 +52,7 @@ class MongoStorage {
|
|
|
52
52
|
logger.info('[MongoStorage] Message stored');
|
|
53
53
|
return values;
|
|
54
54
|
} catch (error) {
|
|
55
|
-
logger.error('Error saving message
|
|
55
|
+
logger.error('Error saving message', { error });
|
|
56
56
|
throw error;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -107,7 +107,7 @@ class MongoStorage {
|
|
|
107
107
|
caption: primary.caption || messageData.caption
|
|
108
108
|
};
|
|
109
109
|
} catch (error) {
|
|
110
|
-
logger.error('[MongoStorage] Failed to enrich Twilio media message
|
|
110
|
+
logger.error('[MongoStorage] Failed to enrich Twilio media message', { error });
|
|
111
111
|
return messageData;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -168,7 +168,7 @@ class MongoStorage {
|
|
|
168
168
|
.sort({ createdAt: -1 })
|
|
169
169
|
.limit(limit);
|
|
170
170
|
} catch (error) {
|
|
171
|
-
logger.error('Error getting messages
|
|
171
|
+
logger.error('Error getting messages', { error });
|
|
172
172
|
throw error;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -177,7 +177,7 @@ class MongoStorage {
|
|
|
177
177
|
try {
|
|
178
178
|
return await this.schemas.Thread.findOne({ code, active: true });
|
|
179
179
|
} catch (error) {
|
|
180
|
-
logger.error('Error getting thread
|
|
180
|
+
logger.error('Error getting thread', { error });
|
|
181
181
|
throw error;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
@@ -188,7 +188,7 @@ class MongoStorage {
|
|
|
188
188
|
await thread.save();
|
|
189
189
|
return thread;
|
|
190
190
|
} catch (error) {
|
|
191
|
-
logger.error('Error creating thread
|
|
191
|
+
logger.error('Error creating thread', { error });
|
|
192
192
|
throw error;
|
|
193
193
|
}
|
|
194
194
|
}
|
|
@@ -201,7 +201,7 @@ class MongoStorage {
|
|
|
201
201
|
{ new: true }
|
|
202
202
|
);
|
|
203
203
|
} catch (error) {
|
|
204
|
-
logger.error('Error updating thread
|
|
204
|
+
logger.error('Error updating thread', { error });
|
|
205
205
|
throw error;
|
|
206
206
|
}
|
|
207
207
|
}
|