@peopl-health/nexus 3.6.2 → 3.7.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/examples/basic-usage.js
CHANGED
|
@@ -81,12 +81,28 @@ async function startServer() {
|
|
|
81
81
|
apiVersion: process.env.META_API_VERSION || 'v21.0'
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
// Example: postBatch hooks for running classifiers after each batch completes
|
|
85
|
+
async function classifySymptoms({ chatId }) {
|
|
86
|
+
console.log(`[symptomClassifier] Running for ${chatId}`);
|
|
87
|
+
// Replace with actual classifier logic: check eligibility, fetch messages, classify via LLM, persist results
|
|
88
|
+
console.log(`[symptomClassifier] Done for ${chatId}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function classifyEscalation({ chatId }) {
|
|
92
|
+
console.log(`[escalationRouting] Running for ${chatId}`);
|
|
93
|
+
// Replace with actual classifier logic: extract batch, resolve active assistant, classify and route
|
|
94
|
+
console.log(`[escalationRouting] Done for ${chatId}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
// Initialize Nexus with check-after processing (immediate response, checks for new messages after)
|
|
85
98
|
const nexus = new Nexus({
|
|
86
99
|
messaging: {
|
|
87
100
|
messageBatching: {
|
|
88
101
|
enabled: true, // Enable check-after processing
|
|
89
|
-
checkDelayMs: 100
|
|
102
|
+
checkDelayMs: 100, // Delay before checking for new messages (ms)
|
|
103
|
+
hooks: {
|
|
104
|
+
postBatch: [classifySymptoms, classifyEscalation],
|
|
105
|
+
}
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
108
|
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const { logger } = require('../utils/logger');
|
|
2
2
|
const { Message } = require('../models/messageModel');
|
|
3
3
|
|
|
4
|
+
const VALID_HOOKS = ['preBatch', 'postBatch', 'onBatchError'];
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
|
-
* Manages message batching, processing locks, and
|
|
7
|
+
* Manages message batching, processing locks, run abandonment, and lifecycle hooks.
|
|
6
8
|
*/
|
|
7
9
|
class BatchingManager {
|
|
8
10
|
constructor({ provider = null, config = {} }) {
|
|
@@ -18,6 +20,32 @@ class BatchingManager {
|
|
|
18
20
|
this.activeRequests = new Map();
|
|
19
21
|
this.abandonedRuns = new Set();
|
|
20
22
|
this.typingIntervals = new Map();
|
|
23
|
+
|
|
24
|
+
this._hooks = {
|
|
25
|
+
preBatch: [],
|
|
26
|
+
postBatch: [],
|
|
27
|
+
onBatchError: [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (config.hooks) {
|
|
31
|
+
for (const [name, handlers] of Object.entries(config.hooks)) {
|
|
32
|
+
const fns = Array.isArray(handlers) ? handlers : [handlers];
|
|
33
|
+
for (const fn of fns) {
|
|
34
|
+
this.addHook(name, fn);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
addHook(name, fn) {
|
|
41
|
+
if (!VALID_HOOKS.includes(name)) {
|
|
42
|
+
throw new Error(`[BatchingManager] Unknown hook: "${name}". Valid hooks: ${VALID_HOOKS.join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
if (typeof fn !== 'function') {
|
|
45
|
+
throw new Error(`[BatchingManager] Hook "${name}" must be a function`);
|
|
46
|
+
}
|
|
47
|
+
this._hooks[name].push(fn);
|
|
48
|
+
return this;
|
|
21
49
|
}
|
|
22
50
|
|
|
23
51
|
setProvider(provider) {
|
|
@@ -57,6 +85,12 @@ class BatchingManager {
|
|
|
57
85
|
this.activeRequests.set(chatId, runId);
|
|
58
86
|
|
|
59
87
|
try {
|
|
88
|
+
const pre = await this._runSequentialHooks(chatId, runId);
|
|
89
|
+
if (pre?.skip) {
|
|
90
|
+
logger.debug('[BatchingManager] Batch skipped by preBatch hook', { chatId, runId });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
60
94
|
await this._startTypingRefresh(chatId, runId);
|
|
61
95
|
if (this._checkAbandoned(runId)) return;
|
|
62
96
|
|
|
@@ -66,9 +100,11 @@ class BatchingManager {
|
|
|
66
100
|
if (sendResponseFn && result) await sendResponseFn(result);
|
|
67
101
|
} catch (error) {
|
|
68
102
|
logger.error('[BatchingManager] Error processing messages', { chatId, error: error.message });
|
|
103
|
+
this._runDeferredHooks('onBatchError', chatId, error);
|
|
69
104
|
} finally {
|
|
70
105
|
if (this.activeRequests.get(chatId) === runId) {
|
|
71
106
|
this._clearProcessingState(chatId);
|
|
107
|
+
this._runDeferredHooks('postBatch', chatId);
|
|
72
108
|
}
|
|
73
109
|
if (this.abandonedRuns.size > 100) {
|
|
74
110
|
const toKeep = [...this.abandonedRuns].slice(-20);
|
|
@@ -84,6 +120,39 @@ class BatchingManager {
|
|
|
84
120
|
this._stopTyping(chatId);
|
|
85
121
|
}
|
|
86
122
|
|
|
123
|
+
async _runSequentialHooks(chatId, runId) {
|
|
124
|
+
const hooks = this._hooks.preBatch;
|
|
125
|
+
if (hooks.length === 0) return;
|
|
126
|
+
|
|
127
|
+
const context = { chatId, runId, metadata: {} };
|
|
128
|
+
|
|
129
|
+
for (const fn of hooks) {
|
|
130
|
+
const result = await fn(context);
|
|
131
|
+
if (result?.skip) return { skip: true };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_runDeferredHooks(name, chatId, error) {
|
|
136
|
+
const hooks = this._hooks[name];
|
|
137
|
+
if (hooks.length === 0) return;
|
|
138
|
+
|
|
139
|
+
setImmediate(() => {
|
|
140
|
+
if (this.isProcessing(chatId)) return;
|
|
141
|
+
|
|
142
|
+
const context = { chatId };
|
|
143
|
+
if (error) context.error = error;
|
|
144
|
+
|
|
145
|
+
Promise.allSettled(hooks.map(fn => fn(context)))
|
|
146
|
+
.then(results => {
|
|
147
|
+
for (const result of results) {
|
|
148
|
+
if (result.status === 'rejected') {
|
|
149
|
+
logger.error(`[BatchingManager] ${name} hook failed`, { chatId, error: result.reason?.message });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
87
156
|
_stopTyping(chatId) {
|
|
88
157
|
const interval = this.typingIntervals.get(chatId);
|
|
89
158
|
if (interval) {
|
|
@@ -59,7 +59,8 @@ class NexusMessaging {
|
|
|
59
59
|
enabled: config.messageBatching?.enabled ?? true,
|
|
60
60
|
abortOnNewMessage: config.messageBatching?.abortOnNewMessage ?? true,
|
|
61
61
|
immediateRestart: config.messageBatching?.immediateRestart ?? true,
|
|
62
|
-
typingIndicator: config.messageBatching?.typingIndicator ?? false
|
|
62
|
+
typingIndicator: config.messageBatching?.typingIndicator ?? false,
|
|
63
|
+
hooks: config.messageBatching?.hooks || {},
|
|
63
64
|
};
|
|
64
65
|
|
|
65
66
|
this.batchingManager = new BatchingManager({
|