@peopl-health/nexus 3.1.5 → 3.2.0
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/lib/core/NexusMessaging.js +15 -3
- package/lib/helpers/messageHelper.js +9 -3
- package/lib/models/messageModel.js +3 -1
- package/lib/models/threadModel.js +1 -0
- package/lib/providers/OpenAIResponsesProvider.js +10 -6
- package/lib/services/assistantServiceCore.js +6 -2
- package/lib/storage/MongoStorage.js +2 -0
- package/package.json +1 -1
- package/MIGRATION_GUIDE.md +0 -80
|
@@ -483,6 +483,8 @@ class NexusMessaging {
|
|
|
483
483
|
const result = await this._processMessages(from, () => replyAssistant(from, body));
|
|
484
484
|
const response = typeof result === 'string' ? result : result?.output;
|
|
485
485
|
const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
|
|
486
|
+
const prompt = typeof result === 'object' ? result?.prompt : undefined;
|
|
487
|
+
const response_id = typeof result === 'object' ? result?.response_id : undefined;
|
|
486
488
|
|
|
487
489
|
if (response) {
|
|
488
490
|
await this.sendMessage({
|
|
@@ -490,7 +492,9 @@ class NexusMessaging {
|
|
|
490
492
|
body: response,
|
|
491
493
|
processed: true,
|
|
492
494
|
origin: 'assistant',
|
|
493
|
-
tools_executed
|
|
495
|
+
tools_executed,
|
|
496
|
+
prompt,
|
|
497
|
+
response_id
|
|
494
498
|
});
|
|
495
499
|
}
|
|
496
500
|
} catch (error) {
|
|
@@ -550,6 +554,8 @@ class NexusMessaging {
|
|
|
550
554
|
const result = await this._processMessages(from, () => replyAssistant(from, fallbackMessage));
|
|
551
555
|
const response = typeof result === 'string' ? result : result?.output;
|
|
552
556
|
const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
|
|
557
|
+
const prompt = typeof result === 'object' ? result?.prompt : undefined;
|
|
558
|
+
const response_id = typeof result === 'object' ? result?.response_id : undefined;
|
|
553
559
|
|
|
554
560
|
if (response) {
|
|
555
561
|
await this.sendMessage({
|
|
@@ -557,7 +563,9 @@ class NexusMessaging {
|
|
|
557
563
|
body: response,
|
|
558
564
|
processed: true,
|
|
559
565
|
origin: 'assistant',
|
|
560
|
-
tools_executed
|
|
566
|
+
tools_executed,
|
|
567
|
+
prompt,
|
|
568
|
+
response_id
|
|
561
569
|
});
|
|
562
570
|
}
|
|
563
571
|
} catch (error) {
|
|
@@ -728,6 +736,8 @@ class NexusMessaging {
|
|
|
728
736
|
|
|
729
737
|
const botResponse = typeof result === 'string' ? result : result?.output;
|
|
730
738
|
const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
|
|
739
|
+
const prompt = typeof result === 'object' ? result?.prompt : undefined;
|
|
740
|
+
const response_id = typeof result === 'object' ? result?.response_id : undefined;
|
|
731
741
|
|
|
732
742
|
if (typingInterval) {
|
|
733
743
|
clearInterval(typingInterval);
|
|
@@ -741,7 +751,9 @@ class NexusMessaging {
|
|
|
741
751
|
body: botResponse,
|
|
742
752
|
processed: true,
|
|
743
753
|
origin: 'assistant',
|
|
744
|
-
tools_executed
|
|
754
|
+
tools_executed,
|
|
755
|
+
prompt,
|
|
756
|
+
response_id
|
|
745
757
|
});
|
|
746
758
|
}
|
|
747
759
|
|
|
@@ -59,11 +59,17 @@ async function getLastMessages(code) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
// Create a variable to use as a chackpoint
|
|
63
|
+
async function getLastNMessages(code, n, before=null) {
|
|
63
64
|
try {
|
|
64
|
-
const
|
|
65
|
+
const query = { numero: code };
|
|
66
|
+
|
|
67
|
+
if (before) query.createdAt = { $lte: before };
|
|
68
|
+
|
|
69
|
+
const lastMessages = await Message.find(query)
|
|
65
70
|
.sort({ createdAt: -1 })
|
|
66
|
-
.limit(n)
|
|
71
|
+
.limit(n)
|
|
72
|
+
.lean();
|
|
67
73
|
|
|
68
74
|
if (!lastMessages || lastMessages.length === 0) {
|
|
69
75
|
logger.info(`[getLastNMessages] No messages found for code: ${code ? `${code.substring(0, 3)}***${code.slice(-4)}` : 'unknown'}, limit: ${n}`);
|
|
@@ -73,7 +73,9 @@ const messageSchema = new mongoose.Schema({
|
|
|
73
73
|
errorCode: { type: String, default: null },
|
|
74
74
|
errorMessage: { type: String, default: null },
|
|
75
75
|
updatedAt: { type: Date, default: null }
|
|
76
|
-
}
|
|
76
|
+
},
|
|
77
|
+
prompt: { type: Object, default: null },
|
|
78
|
+
response_id: { type: String, default: null }
|
|
77
79
|
}, { timestamps: true });
|
|
78
80
|
|
|
79
81
|
messageSchema.index({ numero: 1, createdAt: -1 });
|
|
@@ -12,6 +12,7 @@ const threadSchema = new mongoose.Schema({
|
|
|
12
12
|
active: { type: Boolean, default: true },
|
|
13
13
|
stopped: { type: Boolean, default: false },
|
|
14
14
|
review: { type: Boolean, default: false },
|
|
15
|
+
version: { type: Number, default: null },
|
|
15
16
|
nextSid: { type: [String], default: [] }
|
|
16
17
|
}, { timestamps: true });
|
|
17
18
|
|
|
@@ -175,10 +175,12 @@ class OpenAIResponsesProvider {
|
|
|
175
175
|
*/
|
|
176
176
|
async executeRun({ thread, assistant, tools = [], config = {} }) {
|
|
177
177
|
const { conversationId, assistantId } = this._normalizeThread(thread);
|
|
178
|
+
const promptVersion = thread?.version || null;
|
|
178
179
|
|
|
179
180
|
logger.info('[OpenAIResponsesProvider] Starting run', {
|
|
180
181
|
conversationId,
|
|
181
|
-
assistantId
|
|
182
|
+
assistantId,
|
|
183
|
+
promptVersion
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
try {
|
|
@@ -199,7 +201,7 @@ class OpenAIResponsesProvider {
|
|
|
199
201
|
logger.info('[OpenAIResponsesProvider] Context built', {
|
|
200
202
|
conversationId,
|
|
201
203
|
assistantId,
|
|
202
|
-
|
|
204
|
+
context
|
|
203
205
|
});
|
|
204
206
|
|
|
205
207
|
const filter = thread.code ? { code: thread.code, active: true } : null;
|
|
@@ -219,6 +221,7 @@ class OpenAIResponsesProvider {
|
|
|
219
221
|
tools,
|
|
220
222
|
context,
|
|
221
223
|
promptVariables,
|
|
224
|
+
promptVersion,
|
|
222
225
|
assistant,
|
|
223
226
|
...config
|
|
224
227
|
});
|
|
@@ -275,7 +278,8 @@ class OpenAIResponsesProvider {
|
|
|
275
278
|
model,
|
|
276
279
|
assistant,
|
|
277
280
|
toolMetadata,
|
|
278
|
-
|
|
281
|
+
promptVersion = null,
|
|
282
|
+
promptVariables = null
|
|
279
283
|
} = {}) {
|
|
280
284
|
try {
|
|
281
285
|
const id = this._ensurethreadId(threadId);
|
|
@@ -286,9 +290,9 @@ class OpenAIResponsesProvider {
|
|
|
286
290
|
|
|
287
291
|
input = input.filter(item => item.type !== 'function_call' && item.type !== 'function_call_output');
|
|
288
292
|
|
|
289
|
-
const promptConfig =
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
const promptConfig = { id: assistantId };
|
|
294
|
+
if (promptVariables) promptConfig.variables = promptVariables;
|
|
295
|
+
if (promptVersion) promptConfig.version = String(promptVersion);
|
|
292
296
|
logger.info('[OpenAIResponsesProvider] Prompt config', { promptConfig });
|
|
293
297
|
|
|
294
298
|
const makeAPICall = (inputData) => retryWithBackoff(() =>
|
|
@@ -158,7 +158,9 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
158
158
|
const finalThread = thread;
|
|
159
159
|
|
|
160
160
|
const messagesStart = Date.now();
|
|
161
|
-
const
|
|
161
|
+
const beforeCheckpoint = message_?.createdAt ?
|
|
162
|
+
(message_.createdAt.$date ? new Date(message_.createdAt.$date) : message_.createdAt) : null;
|
|
163
|
+
const lastMessage = await getLastNMessages(code, 1, beforeCheckpoint);
|
|
162
164
|
timings.get_messages_ms = Date.now() - messagesStart;
|
|
163
165
|
|
|
164
166
|
if (!lastMessage || lastMessage.length === 0 || lastMessage[0].from_me) {
|
|
@@ -232,6 +234,8 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
232
234
|
timings.total_ms = Date.now() - startTotal;
|
|
233
235
|
|
|
234
236
|
const { output: rawOutput, completed, retries, predictionTimeMs, tools_executed } = runResult;
|
|
237
|
+
const prompt = runResult.run?.prompt || null;
|
|
238
|
+
const response_id = runResult.run?.id || null;
|
|
235
239
|
|
|
236
240
|
const output = sanitizeOutput(rawOutput);
|
|
237
241
|
if (rawOutput !== output) {
|
|
@@ -269,7 +273,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
|
|
|
269
273
|
}).catch(err => logger.error('[replyAssistantCore] Failed to store metrics', { error: err.message }));
|
|
270
274
|
}
|
|
271
275
|
|
|
272
|
-
return { output, tools_executed };
|
|
276
|
+
return { output, tools_executed, prompt, response_id };
|
|
273
277
|
} catch (error) {
|
|
274
278
|
logger.error('[replyAssistantCore] Error in reply', {
|
|
275
279
|
error: error.message,
|
|
@@ -157,6 +157,8 @@ class MongoStorage {
|
|
|
157
157
|
raw: messageData.raw || null,
|
|
158
158
|
origin: messageData.origin || 'whatsapp_platform',
|
|
159
159
|
tools_executed: messageData.tools_executed,
|
|
160
|
+
prompt: messageData.prompt || null,
|
|
161
|
+
response_id: messageData.response_id || null,
|
|
160
162
|
statusInfo
|
|
161
163
|
};
|
|
162
164
|
}
|
package/package.json
CHANGED
package/MIGRATION_GUIDE.md
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# Migration Guide
|
|
2
|
-
|
|
3
|
-
This guide summarizes changes introduced in the current Nexus library refresh to help you migrate quickly with minimal breakage.
|
|
4
|
-
|
|
5
|
-
## TL;DR
|
|
6
|
-
- Twilio remains first‑class; Baileys is supported for messaging but not for templates/flows (returns clear errors).
|
|
7
|
-
- Message sends accept both `to` (preferred) and legacy `code` (normalized internally).
|
|
8
|
-
- Templates and flows are supported through Twilio Content API; provider is auto‑injected into template controllers on `Nexus.initialize()` when Twilio is active.
|
|
9
|
-
- New event bus + middleware model; existing handlers continue to work.
|
|
10
|
-
- Storage adapter registry; use built‑in `mongo`/`noop` or plug your own adapter or instance.
|
|
11
|
-
- Interactive/flows now have a provider‑agnostic API with Twilio mapping and event‑driven routing.
|
|
12
|
-
- Assistant registry with optional override for `getAssistantById`.
|
|
13
|
-
|
|
14
|
-
## Messaging API
|
|
15
|
-
- BREAKING (soft): Prefer `to` over `code` in `sendMessage`. Legacy `code` is still accepted and normalized internally.
|
|
16
|
-
|
|
17
|
-
Before:
|
|
18
|
-
```js
|
|
19
|
-
await nexus.sendMessage({ code: 'whatsapp:+521555...', message: 'Hi' });
|
|
20
|
-
```
|
|
21
|
-
After (preferred):
|
|
22
|
-
```js
|
|
23
|
-
await nexus.sendMessage({ code: '+521555...', message: 'Hi' });
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Templates & Flows (Twilio)
|
|
27
|
-
- Twilio provider now implements content operations: `listTemplates`, `getTemplate`, `createTemplate`, `deleteTemplate`, `submitForApproval`, `checkApprovalStatus`.
|
|
28
|
-
- On `Nexus.initialize()` with Twilio, the provider is auto‑injected into template controllers — default routes under `/api/template` work immediately.
|
|
29
|
-
- Baileys: content/template operations are not supported; a clear error is thrown if called.
|
|
30
|
-
|
|
31
|
-
## Interactive & Flows (Provider‑agnostic)
|
|
32
|
-
- New helper APIs:
|
|
33
|
-
- `registerFlow(id, spec)`, `sendInteractive(nexus, { to, id | spec, variables })`.
|
|
34
|
-
- Twilio: spec is converted to Content API payload and sent; if `spec.contentSid` is provided, it is used directly.
|
|
35
|
-
- Baileys: currently unsupported.
|
|
36
|
-
- Event‑driven routing:
|
|
37
|
-
- `registerInteractiveHandler(match, handler)` and `attachInteractiveRouter(nexus)`; handlers are called when interactive messages are received.
|
|
38
|
-
|
|
39
|
-
## Middleware & Events
|
|
40
|
-
- New event bus on `NexusMessaging`: subscribe to `*:received` and `*:handled` (message, media, interactive, command, keyword, flow).
|
|
41
|
-
- New middleware: `nexus.getMessaging().use(type?, async (msg, nexus, next) => { ... })`.
|
|
42
|
-
- Existing `setHandlers` and `onMessage`/`onInteractive`/etc. continue to work.
|
|
43
|
-
|
|
44
|
-
## Storage
|
|
45
|
-
- Storage is now pluggable via a registry:
|
|
46
|
-
- Built‑ins: `mongo` (default), `noop`.
|
|
47
|
-
- Register your adapter: `registerStorage('src', MyStorageClass)` then `storage: 'src'`.
|
|
48
|
-
- Or pass an instance: `storage: new MyStorageClass()`.
|
|
49
|
-
- If the adapter has `connect()`, Nexus calls it automatically.
|
|
50
|
-
- Controllers that rely on your Mongo models can continue to do so — no change required.
|
|
51
|
-
|
|
52
|
-
## Assistants
|
|
53
|
-
- Register assistant classes at init:
|
|
54
|
-
```js
|
|
55
|
-
await nexus.initialize({
|
|
56
|
-
provider: 'twilio',
|
|
57
|
-
llm: 'openai', llmConfig: { apiKey: process.env.OPENAI_API_KEY },
|
|
58
|
-
assistants: {
|
|
59
|
-
registry: { SUPPORT: SupportAssistantClass, SALES: SalesAssistantClass },
|
|
60
|
-
getAssistantById: (id, thread) => null // optional override
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
```
|
|
64
|
-
- An internal override for `getAssistantById` is supported; if provided, it is tried first, then fallback to registry.
|
|
65
|
-
|
|
66
|
-
## Utilities & Fixes
|
|
67
|
-
- Utils index corrected to export existing files: `{ DefaultLLMProvider, MessageParser, logger }`.
|
|
68
|
-
- Default OpenAI import fixed for CommonJS: `const OpenAI = require('openai');`.
|
|
69
|
-
- Message model helpers unified; `LegacyMessage` references replaced with `Message` and a new exported `insertMessage`.
|
|
70
|
-
- Types `declare module` now matches package name: `@peopl-health/nexus`.
|
|
71
|
-
|
|
72
|
-
## Routes
|
|
73
|
-
- Built‑in route bundles remain available and importable via `setupDefaultRoutes(app)` or per‑group via `routes` + `createRouter()`.
|
|
74
|
-
|
|
75
|
-
## Testing
|
|
76
|
-
- If your environment restricts forking, run Jest in‑band: `jest --runInBand`.
|
|
77
|
-
|
|
78
|
-
## Notes
|
|
79
|
-
- Twilio approvals data shape varies by account/region. The provider looks up `content.links` and falls back to the documented REST path; data is normalized where possible.
|
|
80
|
-
- Baileys: templates/flows remain unsupported; messaging and media send/receive are supported.
|