@peopl-health/nexus 3.7.3 → 3.8.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.
@@ -0,0 +1,94 @@
1
+ const { Config_ID } = require('../config/airtableConfig');
2
+
3
+ const { logger } = require('../utils/logger');
4
+ const MapCache = require('../utils/MapCache');
5
+
6
+ const { getRecordByFilter } = require('./airtableService');
7
+
8
+ const SNIPPET_TYPE_ORDER = ['safety', 'persona', 'context', 'clinical', 'instructions'];
9
+ const CACHE_TTL = 5 * 60 * 1000;
10
+
11
+ const promptCache = new MapCache({ maxSize: 50, ttl: CACHE_TTL });
12
+ const snippetCache = new MapCache({ maxSize: 200, ttl: CACHE_TTL });
13
+
14
+ async function fetchBasePrompt(promptId) {
15
+ const cacheKey = `prompt:${promptId}`;
16
+ const cached = promptCache.get(cacheKey);
17
+ if (cached) return cached;
18
+
19
+ const records = await getRecordByFilter(Config_ID, 'responses', `{prompt_id} = "${promptId}"`);
20
+ const record = records?.[0] || null;
21
+ if (record) promptCache.set(cacheKey, record);
22
+ return record;
23
+ }
24
+
25
+ async function fetchSnippets(promptId) {
26
+ const cacheKey = `snippets:${promptId}`;
27
+ const cached = snippetCache.get(cacheKey);
28
+ if (cached) return cached;
29
+
30
+ try {
31
+ const records = await getRecordByFilter(
32
+ Config_ID,
33
+ 'context_snippets',
34
+ `FIND("${promptId}", ARRAYJOIN({prompts}))`,
35
+ );
36
+ const snippets = records || [];
37
+ snippetCache.set(cacheKey, snippets);
38
+ return snippets;
39
+ } catch (error) {
40
+ logger.warn('[promptComposer] Failed to fetch snippets, continuing without them', {
41
+ promptId, error: error.message,
42
+ });
43
+ return [];
44
+ }
45
+ }
46
+
47
+ function sortSnippets(snippets) {
48
+ return [...snippets].sort((a, b) => {
49
+ const aIndex = SNIPPET_TYPE_ORDER.indexOf(a.type || '');
50
+ const bIndex = SNIPPET_TYPE_ORDER.indexOf(b.type || '');
51
+ return (aIndex === -1 ? 999 : aIndex) - (bIndex === -1 ? 999 : bIndex);
52
+ });
53
+ }
54
+
55
+ function applyVariables(text, variables) {
56
+ if (!variables || !text) return text || '';
57
+ return text.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] ?? '');
58
+ }
59
+
60
+ async function composePrompt({ promptId, variables = null, status = null }) {
61
+ const baseRecord = await fetchBasePrompt(promptId);
62
+ let baseContent = baseRecord?.content || '';
63
+
64
+ const snippets = await fetchSnippets(promptId);
65
+ const activeSnippets = status
66
+ ? snippets.filter(s => !s.status || s.status === status)
67
+ : snippets;
68
+
69
+ const sorted = sortSnippets(activeSnippets);
70
+ const snippetIds = sorted.map(s => s.snippet_id || s.name || 'unknown');
71
+
72
+ if (sorted.length > 0) {
73
+ const snippetTexts = sorted.map(s => s.content).filter(Boolean);
74
+ baseContent = [baseContent, ...snippetTexts].join('\n\n');
75
+ }
76
+
77
+ const resolvedPrompt = applyVariables(baseContent, variables);
78
+
79
+ logger.debug('[promptComposer] Prompt composed', {
80
+ promptId,
81
+ snippetCount: sorted.length,
82
+ snippetIds,
83
+ resolvedLength: resolvedPrompt.length,
84
+ });
85
+
86
+ return { resolvedPrompt, snippetIds };
87
+ }
88
+
89
+ function clearCache() {
90
+ promptCache.clear();
91
+ snippetCache.clear();
92
+ }
93
+
94
+ module.exports = { composePrompt, clearCache, _test: { applyVariables, sortSnippets } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.7.3",
3
+ "version": "3.8.0",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",