@peopl-health/nexus 5.67.0-dev.11929 → 5.67.0-dev.11930

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.
@@ -475,14 +475,17 @@ class AnthropicProvider extends BaseLLMProvider {
475
475
  // Split placement: the inline copy carries recency on single-round turns, the hoisted developer
476
476
  // turn persists once tool results are appended after it on multi-round turns.
477
477
  _buildInput({ additionalInstructions, ...rest }) {
478
- const input = super._buildInput({ additionalInstructions, ...rest });
479
- const pinned = { role: 'developer', content: `${RISK_HEADLINE_MARKER}\n${RISK_HEADLINE_DISCLAIMER}` };
480
- const ordered = [...input.filter(i => !i[TAIL_PLACEMENT]), pinned, ...input.filter(i => i[TAIL_PLACEMENT])];
478
+ const ordered = this._orderRuntimeMessages(super._buildInput({ additionalInstructions, ...rest }));
481
479
  if (!additionalInstructions) return ordered;
482
480
  const { content } = this._operatorInstructionTurn(additionalInstructions);
483
481
  return [...ordered, { role: 'user', content: `${OPERATOR_TURN_PREFIX} ${content}`, type: 'message' }];
484
482
  }
485
483
 
484
+ _orderRuntimeMessages(input) {
485
+ const pinned = { role: 'developer', content: `${RISK_HEADLINE_MARKER}\n${RISK_HEADLINE_DISCLAIMER}` };
486
+ return [...input.filter(i => !i[TAIL_PLACEMENT]), pinned, ...input.filter(i => i[TAIL_PLACEMENT])];
487
+ }
488
+
486
489
  _mapModelConfig(modelConfig) {
487
490
  const mapped = { ...(modelConfig || {}) };
488
491
  if (mapped.max_tokens == null && mapped.max_output_tokens != null) mapped.max_tokens = mapped.max_output_tokens;
@@ -783,6 +783,11 @@ class BaseLLMProvider {
783
783
  };
784
784
  }
785
785
 
786
+ // Providers that pin runtime messages around the tail-placed block override this.
787
+ _orderRuntimeMessages(input) {
788
+ return input;
789
+ }
790
+
786
791
  _sanitizeInput(input) {
787
792
  const isMessage = (item) => !item.type || item.type === 'message';
788
793
  const items = (input || []).filter(item => isMessage(item) ? this._hasTextContent(item.content) : true);
@@ -38,6 +38,11 @@ class GatewayProvider extends BaseLLMProvider {
38
38
  return this._sub(vendor)._buildInput(config);
39
39
  }
40
40
 
41
+ _orderRuntimeMessages(input, modelConfig = null) {
42
+ const { vendor } = this._route(modelConfig?.model);
43
+ return this._sub(vendor)._orderRuntimeMessages(input);
44
+ }
45
+
41
46
  _route(model) {
42
47
  if (!model) return { vendor: 'openai', model: undefined };
43
48
  const slash = model.indexOf('/');
@@ -1,6 +1,7 @@
1
1
  const { safeParse, isPlainObject } = require('../../utils/jsonUtils');
2
2
  const { logger } = require('../../utils/logger');
3
3
  const { sanitizeInlineText } = require('../../utils/sanitizerUtils');
4
+ const { withTimeout } = require('../../utils/timeoutUtils');
4
5
  const { composePrompt } = require('./promptComposerService');
5
6
 
6
7
  const NUTRITION_GUIDE_PROMPT_ID = 'nutrition_plan_guide';
@@ -60,15 +61,14 @@ function readGuide() {
60
61
  // The guide only adds to the plan, so a slow read must not hold the patient's turn,
61
62
  // and a read that never settles must not hold every later turn either.
62
63
  async function loadNutritionGuide() {
63
- let timer;
64
64
  const read = readGuide();
65
- const timedOut = new Promise((resolve) => { timer = setTimeout(() => resolve(null), GUIDE_TIMEOUT_MS); });
66
- const guide = await Promise.race([read, timedOut]);
67
- clearTimeout(timer);
68
- if (guide !== null) return guide;
69
- if (guideRead === read) guideRead = null;
70
- logger.warn('[nutritionPlanService] nutrition guide read timed out; using the last one read', { timeoutMs: GUIDE_TIMEOUT_MS });
71
- return lastGuide;
65
+ try {
66
+ return await withTimeout(read, GUIDE_TIMEOUT_MS, 'nutrition guide read');
67
+ } catch {
68
+ if (guideRead === read) guideRead = null;
69
+ logger.warn('[nutritionPlanService] nutrition guide read timed out; using the last one read', { timeoutMs: GUIDE_TIMEOUT_MS });
70
+ return lastGuide;
71
+ }
72
72
  }
73
73
 
74
74
  async function resolveNutritionPlan(section, { now = new Date() } = {}) {
@@ -225,17 +225,21 @@ class EvalProvider {
225
225
  const clinicalMessage = typeof this.provider._buildClinicalMessage === 'function'
226
226
  ? this.provider._buildClinicalMessage(promptVariables, interpolatedVariables)
227
227
  : [];
228
- const input = this.provider._sanitizeInput([...memoryMessage, ...clinicalMessage, ...convertedMessages]);
229
228
  const isContextOnly = this.mode === 'context-only';
230
229
 
231
230
  const shouldUsePromptRegistry = !presetId && this.provider.supportsPromptRegistry && !!assistantId;
231
+ const modelConfig = shouldUsePromptRegistry ? {} : this._modelConfig(presetModelConfig);
232
+ const input = this.provider._orderRuntimeMessages(
233
+ this.provider._sanitizeInput([...memoryMessage, ...clinicalMessage, ...convertedMessages]),
234
+ modelConfig,
235
+ );
232
236
 
233
237
  return {
234
238
  instructions: devContent || '',
235
239
  input,
236
240
  toolSchemas: isContextOnly ? [] : toolSchemas,
237
241
  toolChoice: isContextOnly ? 'none' : this.toolChoice,
238
- modelConfig: shouldUsePromptRegistry ? {} : this._modelConfig(presetModelConfig),
242
+ modelConfig,
239
243
  assistant: isContextOnly ? null : assistant,
240
244
  phiProcessor: this.phiProcessor,
241
245
  promptVariables,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "5.67.0-dev.11929",
3
+ "version": "5.67.0-dev.11930",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",