@peopl-health/nexus 5.71.9 → 5.71.10

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.
@@ -78,11 +78,18 @@ async function extractClinicalMentions({ text, turnId, code, provider, detectedT
78
78
  const { datosClinicos, contextGaps } = await loadPatientContext(code);
79
79
  const vagueWords = await getVagueEntityWords();
80
80
 
81
- const { result, durationMs } = await withTiming(() => provider.runStructured({
82
- presetId,
83
- input: [{ role: 'user', content: buildExtractorInput(text, detectedTags, curatedRows, template, { priorTurns, patientContext: datosClinicos }) }],
84
- metadata: { extractor: 'clinical-mention', turnId },
85
- }));
81
+ let timed;
82
+ try {
83
+ timed = await withTiming(() => provider.runStructured({
84
+ presetId,
85
+ input: [{ role: 'user', content: buildExtractorInput(text, detectedTags, curatedRows, template, { priorTurns, patientContext: datosClinicos }) }],
86
+ metadata: { extractor: 'clinical-mention', turnId },
87
+ }));
88
+ } catch (err) {
89
+ logger.error('[clinicalExtraction] extractor provider unavailable; returning deterministic safety flags only', { turnId, error: err?.message });
90
+ return { completed: false, unavailable: Boolean(patterns?.length), clinicalMentions: [], safetyFlags: [...flags], safetyFlagSources: sources() };
91
+ }
92
+ const { result, durationMs } = timed;
86
93
  trace?.addSubCall({ response: result, durationMs, presetId });
87
94
 
88
95
  if (result?.status && result.status !== 'completed') throw new Error(`clinical extraction incomplete: status=${result.status}`);
@@ -38,6 +38,8 @@ function nonClinicalRequestSchema() {
38
38
  }
39
39
 
40
40
  const MAX_CONTEXT_TURNS = 6;
41
+ const EXTRACTOR_UNAVAILABLE = 'extractor_unavailable';
42
+ const ESCALATION_WHILE_UNAVAILABLE = 'described in the patient\'s words and graded on the symptom itself; never mention the extractor or a technical failure, to the patient or the team.';
41
43
 
42
44
  const definition = {
43
45
  name: 'extractClinicalInfo',
@@ -239,13 +241,19 @@ async function handler(args, context = {}) {
239
241
  });
240
242
  }
241
243
 
244
+ if (!intakeCompleted && extraction.unavailable && !safetyFlags.length && !detectedTags.includes('symptom')) {
245
+ return degradedIntake(trace);
246
+ }
247
+
242
248
  if (!intakeCompleted) {
243
249
  return JSON.stringify({
244
250
  success: false,
245
251
  error: 'clinical_intake_incomplete',
246
252
  data: {
247
253
  safety_flags_received: safetyFlags,
248
- recovery: 'Retry extractClinicalInfo with the complete patient message and all applicable supported tags.',
254
+ recovery: extraction.unavailable
255
+ ? `The clinical extractor is unavailable and a retry will not succeed. Send what the message reports to the team with reportMedicalEscalation (reportCrisis for a crisis), ${ESCALATION_WHILE_UNAVAILABLE}`
256
+ : 'Retry extractClinicalInfo with the complete patient message and all applicable supported tags.',
249
257
  },
250
258
  });
251
259
  }
@@ -312,6 +320,10 @@ async function declareNonClinical({ declaredRequest, rawMessage, intakeSource, p
312
320
  });
313
321
  }
314
322
 
323
+ if (check?.unavailable && !safetyFlags.length) {
324
+ return degradedIntake(trace, { non_clinical_request: declaredRequest });
325
+ }
326
+
315
327
  trace?.setSignals?.({ clinicalIntakeCompleted: false, clinicalMentions: [], safetyFlags });
316
328
  const supported = [...SUPPORTED_TAGS].sort();
317
329
  const reason = found
@@ -325,6 +337,20 @@ async function declareNonClinical({ declaredRequest, rawMessage, intakeSource, p
325
337
  });
326
338
  }
327
339
 
340
+ function degradedIntake(trace, data = {}) {
341
+ trace?.setSignals?.({ clinicalIntakeCompleted: true, clinicalIntakeDegraded: EXTRACTOR_UNAVAILABLE, clinicalMentions: [], safetyFlags: [] });
342
+ return JSON.stringify({
343
+ success: true,
344
+ data: {
345
+ ...data,
346
+ degraded: true,
347
+ degraded_reasons: [EXTRACTOR_UNAVAILABLE],
348
+ clinical_facts: [],
349
+ agent_hint: `The clinical extractor is unavailable, so nothing from this message was recorded as clinical information. Do not call extractClinicalInfo again this turn. A new or worsening symptom goes to the team with reportMedicalEscalation, ${ESCALATION_WHILE_UNAVAILABLE} Anything else gets a reply only.`,
350
+ },
351
+ });
352
+ }
353
+
328
354
  async function scanSafetyFlags(text) {
329
355
  try {
330
356
  const patterns = await getSafetyFlagPatterns();
@@ -53,6 +53,7 @@ function isAbortError(error) {
53
53
  function isRetryableError(error) {
54
54
  if (!error) return false;
55
55
  if (isAbortError(error)) return false;
56
+ if (error.code === 'insufficient_quota') return false;
56
57
 
57
58
  const status = error?.status;
58
59
  const code = error?.code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "5.71.9",
3
+ "version": "5.71.10",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",