@peopl-health/nexus 3.3.18-fix-model → 3.3.19

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.
@@ -23,9 +23,10 @@ const runAssistantAndWait = async ({ thread, assistant, message = null, runConfi
23
23
  const provider = createLLMProvider({ variant: process.env.VARIANT || 'assistants' });
24
24
  const { polling, tools: configTools, ...config } = runConfig;
25
25
  const tools = assistant.getToolSchemas?.() || configTools || [];
26
+ const messageForRun = Array.isArray(message) && message.length > 0 ? message[0] : message;
26
27
 
27
28
  return withThreadRecovery(
28
- (currentThread = thread) => provider.executeRun({ thread: currentThread, message, assistant, tools, config, polling }),
29
+ (currentThread = thread) => provider.executeRun({ thread: currentThread, message: messageForRun, assistant, tools, config, polling }),
29
30
  thread
30
31
  );
31
32
  };
@@ -99,6 +99,49 @@ function formatMessage(reply) {
99
99
  }
100
100
  }
101
101
 
102
+ function getMessageTools(reply) {
103
+ const tools = reply?.tools_executed;
104
+ if (!tools?.length) return [];
105
+
106
+ const msgId = reply?.message_id ?? `msg_${Date.now()}`;
107
+ const items = [];
108
+
109
+ try {
110
+ // API requires function_call id to start with "fc" and function_call_output to reference it via call_id.
111
+ tools.forEach((t, i) => {
112
+ const safeId = `${msgId}_${i}`.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80);
113
+ const functionCallId = `fc_${safeId}`;
114
+ const argsStr = JSON.stringify(t.tool_arguments ?? {});
115
+ let outputStr;
116
+ if (t.tool_output != null) {
117
+ outputStr = typeof t.tool_output === 'string'
118
+ ? t.tool_output
119
+ : JSON.stringify(t.tool_output);
120
+ } else {
121
+ outputStr = JSON.stringify({ success: t.success !== false });
122
+ }
123
+
124
+ items.push({
125
+ type: 'function_call',
126
+ id: functionCallId,
127
+ call_id: functionCallId,
128
+ name: t.tool_name,
129
+ arguments: argsStr
130
+ });
131
+ items.push({
132
+ type: 'function_call_output',
133
+ id: `fco_${safeId}`,
134
+ call_id: functionCallId,
135
+ output: outputStr
136
+ });
137
+ });
138
+ return items;
139
+ } catch (error) {
140
+ logger.error('[getMessageTools] Error', { messageId: reply?.message_id, error: error.message });
141
+ return [];
142
+ }
143
+ }
144
+
102
145
  async function isRecentMessage(chatId) {
103
146
  const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
104
147
  const recent = await Message.findOne({
@@ -113,5 +156,6 @@ module.exports = {
113
156
  getLastMessages,
114
157
  getLastNMessages,
115
158
  formatMessage,
159
+ getMessageTools,
116
160
  isRecentMessage
117
161
  };
@@ -4,7 +4,7 @@ const { logger } = require('../utils/logger');
4
4
 
5
5
  const { MemoryManager } = require('../memory/MemoryManager');
6
6
 
7
- const { getLastNMessages, formatMessage } = require('../helpers/messageHelper');
7
+ const { getLastNMessages, getMessageTools, formatMessage } = require('../helpers/messageHelper');
8
8
 
9
9
  const { handlePendingFunctionCalls: handlePendingFunctionCallsUtil } = require('../providers/OpenAIResponsesProviderTools');
10
10
 
@@ -20,7 +20,7 @@ class DefaultMemoryManager extends MemoryManager {
20
20
  this._logActivity('Building context', { threadCode: thread.code });
21
21
 
22
22
  try {
23
- const beforeCheckpoint = message ? message.createdAt : null;
23
+ const beforeCheckpoint = config.beforeCheckpoint ?? message?.createdAt ?? null;
24
24
  const allMessages = await getLastNMessages(thread.code, this.maxHistoricalMessages, beforeCheckpoint, {
25
25
  query: { origin: { $ne: 'instruction' } }
26
26
  });
@@ -32,12 +32,16 @@ class DefaultMemoryManager extends MemoryManager {
32
32
 
33
33
  const roleMap = { system: 'system', patient: 'user' };
34
34
  const context = allMessages.reverse().flatMap(msg => {
35
- const formattedContents = formatMessage(msg);
36
- return formattedContents.map(content => ({
37
- role: roleMap[msg.origin] || 'assistant',
38
- content: content || msg.body || msg.content || ''
39
- }));
40
- }).filter(msg => msg.content);
35
+ const toolItems = getMessageTools(msg);
36
+ const contentItems = formatMessage(msg)
37
+ .map(content => ({
38
+ role: roleMap[msg.origin] || 'assistant',
39
+ content: content,
40
+ type: 'message'
41
+ }))
42
+ .filter(item => item.content);
43
+ return [...toolItems, ...contentItems];
44
+ });
41
45
 
42
46
  return [...context, ...additional];
43
47
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  const { trace, metrics } = require('@opentelemetry/api');
2
2
 
3
- const { initTelemetry, shutdownTelemetry, getMetricsRequestHandler } = require('../observability/telemetry');
3
+ const { initTelemetry, shutdownTelemetry } = require('../observability/telemetry');
4
4
 
5
5
  const tracer = trace.getTracer('nexus-assistant');
6
6
  const meter = metrics.getMeter('nexus-assistant');
@@ -149,7 +149,6 @@ function recordFileOperation(operationType, attributes = {}) {
149
149
  module.exports = {
150
150
  init,
151
151
  shutdown: shutdownTelemetry,
152
- getMetricsRequestHandler,
153
152
  traceOperation,
154
153
  createSpan,
155
154
  tracer,
@@ -1,6 +1,5 @@
1
1
  const { NodeSDK } = require('@opentelemetry/sdk-node');
2
2
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
3
- const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
4
3
  const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
5
4
  const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
6
5
  const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-node');
@@ -11,13 +10,12 @@ const runtimeConfig = require('../config/runtimeConfig');
11
10
 
12
11
  const nodeEnv = runtimeConfig.get('NODE_ENV') || '';
13
12
  const isProd = nodeEnv === 'production' || nodeEnv === 'prod';
14
- const IGNORED_PATHS = ['/health', '/metrics', '/'];
13
+ const IGNORED_PATHS = ['/health', '/'];
15
14
 
16
15
  class TelemetryManager {
17
16
  constructor() {
18
17
  this.sdk = null;
19
18
  this.isInitialized = false;
20
- this.prometheusExporter = null;
21
19
  }
22
20
 
23
21
  async init(config = {}) {
@@ -32,8 +30,6 @@ class TelemetryManager {
32
30
  jaegerEndpoint = 'http://localhost:14268/api/traces',
33
31
  otlpEndpoint = null,
34
32
  otlpHeaders = {},
35
- prometheusEndpoint = '/metrics',
36
- prometheusPort = 9090
37
33
  } = config;
38
34
 
39
35
  try {
@@ -75,18 +71,12 @@ class TelemetryManager {
75
71
  exportTimeoutMillis: isProd ? 5000 : 2000,
76
72
  scheduledDelayMillis: isProd ? 2000 : 500,
77
73
  }),
78
- metricReader: this.prometheusExporter = new PrometheusExporter({
79
- endpoint: prometheusEndpoint,
80
- port: prometheusPort,
81
- preventServerStart: true,
82
- }),
83
74
  });
84
75
 
85
76
  await this.sdk.start();
86
77
  this.isInitialized = true;
87
78
 
88
79
  console.log(`🚀 OpenTelemetry initialized for "${serviceName}"`);
89
- console.log(`📊 Metrics: available at ${prometheusEndpoint} (mounted on Express app)`);
90
80
  console.log(`🔍 Traces: ${otlpEndpoint || jaegerEndpoint}`);
91
81
  } catch (error) {
92
82
  console.error('❌ Failed to initialize OpenTelemetry:', error.message);
@@ -115,8 +105,4 @@ module.exports = {
115
105
  telemetryManager,
116
106
  initTelemetry: (config) => telemetryManager.init(config),
117
107
  shutdownTelemetry: () => telemetryManager.shutdown(),
118
- getMetricsRequestHandler: () => {
119
- if (!telemetryManager.prometheusExporter) return null;
120
- return (req, res) => telemetryManager.prometheusExporter.getMetricsRequestHandler(req, res);
121
- }
122
108
  };
@@ -115,11 +115,17 @@ class OpenAIResponsesProvider {
115
115
  }
116
116
 
117
117
  _convertItemsToApiFormat(items) {
118
- return items.map(item => ({
119
- role: item.role || 'user',
120
- content: this._normalizeContent(item.content),
121
- type: item.type || 'message',
122
- }));
118
+ return items.map(item => {
119
+ const type = item.type || 'message';
120
+ if (type === 'function_call' || type === 'function_call_output') {
121
+ return { ...item, type };
122
+ }
123
+ return {
124
+ role: item.role || 'user',
125
+ type,
126
+ content: this._normalizeContent(item.content)
127
+ };
128
+ });
123
129
  }
124
130
 
125
131
  _normalizeContent(content) {
@@ -322,8 +328,8 @@ class OpenAIResponsesProvider {
322
328
  devContent += `\n\nYou only have access to these tools: ${toolNames}. Do not call or reference any tools not listed here.`;
323
329
  }
324
330
 
325
- const messages = (context || this._convertItemsToApiFormat(additionalMessages))
326
- .filter(item => item.type !== 'function_call' && item.type !== 'function_call_output');
331
+ const rawMessages = context || additionalMessages;
332
+ const messages = this._convertItemsToApiFormat(rawMessages);
327
333
  const input = [{ role: 'developer', content: devContent }, ...messages];
328
334
 
329
335
  const promptConfig = { id: assistantId };
@@ -176,12 +176,6 @@ const builtInControllers = {
176
176
  };
177
177
 
178
178
  const setupDefaultRoutes = (app) => {
179
- const { getMetricsRequestHandler } = require('../observability');
180
- const metricsHandler = getMetricsRequestHandler();
181
- if (metricsHandler) {
182
- app.get('/metrics', metricsHandler);
183
- }
184
-
185
179
  app.use('/api/assistant', createRouter(assistantRouteDefinitions, builtInControllers));
186
180
  app.use('/api/conversation', createRouter(conversationRouteDefinitions, builtInControllers));
187
181
  app.use('/api/interaction', createRouter(interactionRouteDefinitions, builtInControllers));
@@ -159,8 +159,7 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
159
159
  if (!thread) return null;
160
160
 
161
161
  const messagesStart = Date.now();
162
- const beforeCheckpoint = message_?.createdAt ?
163
- (message_.createdAt.$date ? new Date(message_.createdAt.$date) : message_.createdAt) : null;
162
+ const beforeCheckpoint = message_?.createdAt ?? null;
164
163
  const lastMessage = await getLastNMessages(code, 1, beforeCheckpoint, {
165
164
  query: { from_me: false }
166
165
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.3.18-fix-model",
3
+ "version": "3.3.19",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",
@@ -58,19 +58,18 @@
58
58
  "postversion": "git push && git push --tags"
59
59
  },
60
60
  "dependencies": {
61
- "body-parser": "^1.20.2",
62
61
  "@opentelemetry/api": "1.9.0",
63
62
  "@opentelemetry/auto-instrumentations-node": "0.67.0",
64
63
  "@opentelemetry/exporter-jaeger": "1.28.0",
65
- "@opentelemetry/exporter-prometheus": "0.56.0",
66
64
  "@opentelemetry/exporter-trace-otlp-http": "0.56.0",
67
65
  "@opentelemetry/resources": "1.28.0",
68
66
  "@opentelemetry/sdk-node": "0.56.0",
69
67
  "@opentelemetry/sdk-trace-node": "1.28.0",
70
68
  "@opentelemetry/semantic-conventions": "1.28.0",
71
69
  "airtable": "^0.12.2",
72
- "aws-sdk": "2.1693.0",
70
+ "aws-sdk": "^2.1693.0",
73
71
  "axios": "^1.5.0",
72
+ "body-parser": "^1.20.2",
74
73
  "dotenv": "^16.6.1",
75
74
  "moment-timezone": "^0.5.43",
76
75
  "mongoose": "^7.5.0",
@@ -108,6 +107,9 @@
108
107
  "engines": {
109
108
  "node": ">=20.0.0"
110
109
  },
110
+ "overrides": {
111
+ "p-limit": "2.3.0"
112
+ },
111
113
  "publishConfig": {
112
114
  "access": "public"
113
115
  }