@maximem/synap-js-sdk 0.2.4 → 0.4.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/MIGRATING.md +147 -0
- package/README.md +95 -82
- package/bin/synap-js-sdk.js +45 -122
- package/dist/anticipation-cache-BgkqcTPJ.d.cts +180 -0
- package/dist/anticipation-cache-BgkqcTPJ.d.ts +180 -0
- package/dist/anticipation-cache-D5YHEPV3.js +10 -0
- package/dist/anticipation-cache-D5YHEPV3.js.map +1 -0
- package/dist/anticipation-cache-JBZHTJ3O.cjs +10 -0
- package/dist/anticipation-cache-JBZHTJ3O.cjs.map +1 -0
- package/dist/chunk-5SC3ELNK.js +856 -0
- package/dist/chunk-5SC3ELNK.js.map +1 -0
- package/dist/chunk-HR5Y3KXS.cjs +172 -0
- package/dist/chunk-HR5Y3KXS.cjs.map +1 -0
- package/dist/chunk-NIDPTBNC.cjs +800 -0
- package/dist/chunk-NIDPTBNC.cjs.map +1 -0
- package/dist/chunk-QBCVMPGK.js +28 -0
- package/dist/chunk-QBCVMPGK.js.map +1 -0
- package/dist/chunk-T3NZBIV2.js +172 -0
- package/dist/chunk-T3NZBIV2.js.map +1 -0
- package/dist/chunk-TCEP5OVR.js +800 -0
- package/dist/chunk-TCEP5OVR.js.map +1 -0
- package/dist/chunk-VXRCBMCY.cjs +856 -0
- package/dist/chunk-VXRCBMCY.cjs.map +1 -0
- package/dist/chunk-ZKOJDUN4.cjs +28 -0
- package/dist/chunk-ZKOJDUN4.cjs.map +1 -0
- package/dist/env-55ULHNJZ.cjs +11 -0
- package/dist/env-55ULHNJZ.cjs.map +1 -0
- package/dist/env-QCUKKW36.js +11 -0
- package/dist/env-QCUKKW36.js.map +1 -0
- package/dist/grpc/index.cjs +87 -0
- package/dist/grpc/index.cjs.map +1 -0
- package/dist/grpc/index.d.cts +162 -0
- package/dist/grpc/index.d.ts +162 -0
- package/dist/grpc/index.js +87 -0
- package/dist/grpc/index.js.map +1 -0
- package/dist/index.cjs +2111 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1477 -0
- package/dist/index.d.ts +1477 -0
- package/dist/index.js +2111 -0
- package/dist/index.js.map +1 -0
- package/dist/stream-client-ME5N5NK4.js +7 -0
- package/dist/stream-client-ME5N5NK4.js.map +1 -0
- package/dist/stream-client-ZN6ADZBR.cjs +7 -0
- package/dist/stream-client-ZN6ADZBR.cjs.map +1 -0
- package/package.json +68 -19
- package/.env.example +0 -39
- package/bridge/synap_bridge.py +0 -708
- package/src/bridge-manager.js +0 -262
- package/src/errors.js +0 -151
- package/src/index.js +0 -13
- package/src/runtime.js +0 -161
- package/src/setup-typescript.js +0 -161
- package/src/synap-client.js +0 -363
- package/types/index.d.ts +0 -384
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkZKOJDUN4cjs = require('./chunk-ZKOJDUN4.cjs');
|
|
5
|
+
|
|
6
|
+
// src/behavior/anticipation.json
|
|
7
|
+
var anticipation_default = {
|
|
8
|
+
$comment: "SOURCE OF TRUTH for anticipation-cache behavior, shared by the Python and JS/TS SDKs. Do not duplicate these values into either SDK's source. See ../README.md. Regexes are restricted to the intersection of Python 're' and ECMAScript; both SDKs have a parity test.",
|
|
9
|
+
version: 1,
|
|
10
|
+
recall_bypass: {
|
|
11
|
+
note: "WS4 of the playground query-hygiene plan. A recall-shaped question never serves from the anticipation cache. Asymmetric cost is why: a stale false-hit makes the agent answer 'I don't see that' while the store holds the fact (the BZQR6J cross-session failure), whereas bypassing costs only cold-fetch latency on a turn where the user already expects a lookup. Stem coverage cannot gate this \u2014 false hits were measured at coverage=1.00. Ties break toward INCLUDING a pattern.",
|
|
12
|
+
env_flag: "SYNAP_SDK_CACHE_RECALL_BYPASS",
|
|
13
|
+
default: false,
|
|
14
|
+
patterns: [
|
|
15
|
+
{
|
|
16
|
+
pattern: "\\bremind me\\b",
|
|
17
|
+
flags: "i",
|
|
18
|
+
note: "Original set (WS4, playground query-hygiene plan)."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pattern: "\\bon file\\b",
|
|
22
|
+
flags: "i",
|
|
23
|
+
note: "Original set (WS4)."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
pattern: "\\b(your|my|the) records\\b",
|
|
27
|
+
flags: "i",
|
|
28
|
+
note: "Original set (WS4)."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
pattern: "\\bdo you (have|know|remember|recall|see)\\b",
|
|
32
|
+
flags: "i",
|
|
33
|
+
note: "Original set (WS4)."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pattern: "\\bwhat (do|did) (i|you) (say|tell|mention|have)\\b",
|
|
37
|
+
flags: "i",
|
|
38
|
+
note: "Original set (WS4)."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
pattern: "\\bwhat('s| is) my\\b",
|
|
42
|
+
flags: "i",
|
|
43
|
+
note: "Original set (WS4)."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
pattern: "\\bwhere (am i|do i live)\\b",
|
|
47
|
+
flags: "i",
|
|
48
|
+
note: "Original set (WS4)."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
pattern: "\\bhow long have i\\b",
|
|
52
|
+
flags: "i",
|
|
53
|
+
note: "Original set (WS4)."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pattern: "\\bwhich of my\\b",
|
|
57
|
+
flags: "i",
|
|
58
|
+
note: "Original set (WS4)."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: "\\bwhat .{0,40}\\b(again|earlier|last time|previously|yesterday)\\b",
|
|
62
|
+
flags: "i",
|
|
63
|
+
note: "Original set (WS4). 'again' alone is deliberately NOT a marker: 'book me a ride again' is a task query, not a recall query."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
pattern: "\\bwill you use to (contact|reach|notify)\\b",
|
|
67
|
+
flags: "i",
|
|
68
|
+
note: "Original set (WS4)."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
pattern: "\\b(confirm|verify|double.?check)\\b.{0,40}\\bmy\\b",
|
|
72
|
+
flags: "i",
|
|
73
|
+
note: "2026-07-16 eval register issue #11: 'can you confirm my email address?' served a stale bundle. Also catches 'confirm my booking' \u2014 an intentional over-match, costing one cloud fetch."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
pattern: "\\b(linked to|associated with|registered (to|on|with)) my\\b",
|
|
77
|
+
flags: "i",
|
|
78
|
+
note: "2026-07-16 eval register issue #11: 'what name is linked to my Uber account?' served a stale bundle."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
pattern: "\\b(best|preferred) way to (reach|contact|update|notify) me\\b",
|
|
82
|
+
flags: "i",
|
|
83
|
+
note: "2026-07-16 eval register issue #11: 'best way to contact me with updates' served a stale bundle."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
pattern: "\\bhow (do|will|can|should) you (reach|contact|notify) me\\b",
|
|
87
|
+
flags: "i",
|
|
88
|
+
note: "2026-07-16 eval register issue #11, sibling phrasing of the above."
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
pattern: "\\bwhich .{0,40}\\b(are|will) you (going to )?(use|using)\\b",
|
|
92
|
+
flags: "i",
|
|
93
|
+
note: "2026-07-16 eval register issue #11: 'which email are you going to use?' served a stale bundle."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
pattern: "\\bon (my|the) account\\b",
|
|
97
|
+
flags: "i",
|
|
98
|
+
note: "2026-07-16 eval register issue #11."
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
thresholds: {
|
|
103
|
+
bm25: {
|
|
104
|
+
value: 1.5,
|
|
105
|
+
note: "Nominal threshold. The value actually applied is derived per query: max(effective_floor, min(bm25, query_token_scale * len(query_tokens))). Short queries cannot reach the nominal score, so holding them to it would miss every time."
|
|
106
|
+
},
|
|
107
|
+
novel_term: {
|
|
108
|
+
value: 0.45,
|
|
109
|
+
note: "Fraction of query stems absent from the cached corpus that forces a miss."
|
|
110
|
+
},
|
|
111
|
+
min_corpus_for_novel_gate: {
|
|
112
|
+
value: 200,
|
|
113
|
+
note: "The novel-term gate is meaningless on a small corpus, where the ratio is dominated by ordinary English word forms. Measured: a fresh playground conversation reaches ~60 stems after the first push and would trip the 0.45 gate on essentially every second-turn query. 200 means the gate engages once a customer has ~3-4 typical bundles cached."
|
|
114
|
+
},
|
|
115
|
+
effective_floor: {
|
|
116
|
+
value: 0.6,
|
|
117
|
+
note: "Lower bound on the derived per-query threshold. Below this, BM25 stops discriminating."
|
|
118
|
+
},
|
|
119
|
+
query_token_scale: {
|
|
120
|
+
value: 0.3,
|
|
121
|
+
note: "Scales the threshold down for short queries: 0.3 points of score per query token."
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
bm25: {
|
|
125
|
+
k1: 1.5,
|
|
126
|
+
b: 0.75,
|
|
127
|
+
idf_floor: 0.5,
|
|
128
|
+
min_token_len: 2,
|
|
129
|
+
min_stem_len: 4,
|
|
130
|
+
suffixes: [
|
|
131
|
+
"ences",
|
|
132
|
+
"ances",
|
|
133
|
+
"ation",
|
|
134
|
+
"tion",
|
|
135
|
+
"sion",
|
|
136
|
+
"ence",
|
|
137
|
+
"ance",
|
|
138
|
+
"ness",
|
|
139
|
+
"ment",
|
|
140
|
+
"able",
|
|
141
|
+
"ible",
|
|
142
|
+
"ical",
|
|
143
|
+
"ally",
|
|
144
|
+
"ies",
|
|
145
|
+
"ive",
|
|
146
|
+
"ful",
|
|
147
|
+
"ous",
|
|
148
|
+
"ing",
|
|
149
|
+
"ary",
|
|
150
|
+
"ly",
|
|
151
|
+
"ed",
|
|
152
|
+
"es",
|
|
153
|
+
"s"
|
|
154
|
+
],
|
|
155
|
+
stop_words: [
|
|
156
|
+
"a",
|
|
157
|
+
"about",
|
|
158
|
+
"after",
|
|
159
|
+
"an",
|
|
160
|
+
"and",
|
|
161
|
+
"are",
|
|
162
|
+
"at",
|
|
163
|
+
"be",
|
|
164
|
+
"been",
|
|
165
|
+
"before",
|
|
166
|
+
"being",
|
|
167
|
+
"but",
|
|
168
|
+
"by",
|
|
169
|
+
"can",
|
|
170
|
+
"could",
|
|
171
|
+
"did",
|
|
172
|
+
"do",
|
|
173
|
+
"does",
|
|
174
|
+
"for",
|
|
175
|
+
"from",
|
|
176
|
+
"had",
|
|
177
|
+
"has",
|
|
178
|
+
"have",
|
|
179
|
+
"how",
|
|
180
|
+
"if",
|
|
181
|
+
"in",
|
|
182
|
+
"into",
|
|
183
|
+
"is",
|
|
184
|
+
"it",
|
|
185
|
+
"its",
|
|
186
|
+
"may",
|
|
187
|
+
"might",
|
|
188
|
+
"no",
|
|
189
|
+
"nor",
|
|
190
|
+
"not",
|
|
191
|
+
"of",
|
|
192
|
+
"on",
|
|
193
|
+
"or",
|
|
194
|
+
"over",
|
|
195
|
+
"should",
|
|
196
|
+
"so",
|
|
197
|
+
"than",
|
|
198
|
+
"that",
|
|
199
|
+
"the",
|
|
200
|
+
"then",
|
|
201
|
+
"these",
|
|
202
|
+
"this",
|
|
203
|
+
"those",
|
|
204
|
+
"to",
|
|
205
|
+
"was",
|
|
206
|
+
"were",
|
|
207
|
+
"what",
|
|
208
|
+
"when",
|
|
209
|
+
"where",
|
|
210
|
+
"which",
|
|
211
|
+
"who",
|
|
212
|
+
"whom",
|
|
213
|
+
"why",
|
|
214
|
+
"will",
|
|
215
|
+
"with",
|
|
216
|
+
"would"
|
|
217
|
+
],
|
|
218
|
+
alias_token: {
|
|
219
|
+
pattern: "\\[\\[([A-Za-z][A-Za-z0-9_]*_[a-z0-9]{12})\\]\\]",
|
|
220
|
+
note: "An alias label as it appears in text: [[PERSON_PHONE_h2n7v5cx8m0d]]. Kept whole rather than split, because 'person' and 'phone' are boilerplate shared by every label of that field type and would make two different people's memories score alike. Mirrors synap/cloud/shared/tokenizer.py \u2014 keep the two in step."
|
|
221
|
+
},
|
|
222
|
+
suffix_note: "Order matters: longest-first, because _stem returns on first match. Reordering this list silently changes every stem and therefore every BM25 score."
|
|
223
|
+
},
|
|
224
|
+
env_flags: {
|
|
225
|
+
$comment: "Read at CALL time, never at module load. Python does os.environ.get inside the function bodies; JS must do the same or late process.env mutation stops working and the flag looks broken. See gotcha G-S.",
|
|
226
|
+
SYNAP_SDK_CACHE_RECALL_BYPASS: {
|
|
227
|
+
default: false,
|
|
228
|
+
truthy: [
|
|
229
|
+
"true",
|
|
230
|
+
"1",
|
|
231
|
+
"yes"
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
SYNAP_SDK_CACHE_HONOR_TTL_HINT: {
|
|
235
|
+
default: false,
|
|
236
|
+
truthy: [
|
|
237
|
+
"true",
|
|
238
|
+
"1",
|
|
239
|
+
"yes"
|
|
240
|
+
],
|
|
241
|
+
note: "A bundle's server ttl_hint_seconds may only SHORTEN the global TTL, never extend it."
|
|
242
|
+
},
|
|
243
|
+
SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE: {
|
|
244
|
+
default: false,
|
|
245
|
+
truthy: [
|
|
246
|
+
"true",
|
|
247
|
+
"1",
|
|
248
|
+
"yes"
|
|
249
|
+
],
|
|
250
|
+
note: "2026-07-16 eval register issue #8: the cloud invalidates its own Redis caches on ingest, but that stops at the process boundary."
|
|
251
|
+
},
|
|
252
|
+
SYNAP_SDK_CACHE_MAX_ENTRY_AGE: {
|
|
253
|
+
default: null,
|
|
254
|
+
note: "2026-07-16 eval register issue #6: stored_at is a sliding lease renewed on hit, so under steady traffic a pre-update snapshot never expired. Defaults to 2x the sliding TTL; 0 disables."
|
|
255
|
+
},
|
|
256
|
+
SYNAP_SDK_CACHE_COVERAGE_MIN: {
|
|
257
|
+
default: null
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
coverage_gate: {
|
|
261
|
+
note: "Fraction of the query's stems present in the picked items. A bundle can clear the BM25 threshold on one shared token ('account') while lacking the asked-for fact entirely: the false HIT behind the eval's 'forgot the name' failure. ALWAYS computed and reported; ENFORCED only when SYNAP_SDK_CACHE_COVERAGE_MIN is explicitly set, so default behavior is unchanged. When it does fire it must gate BEFORE any side effect, so a rejected lookup leaves cache state untouched.",
|
|
262
|
+
env_flag: "SYNAP_SDK_CACHE_COVERAGE_MIN",
|
|
263
|
+
default: null,
|
|
264
|
+
empty_query_coverage: 1
|
|
265
|
+
},
|
|
266
|
+
telemetry: {
|
|
267
|
+
rejected_items_cap: {
|
|
268
|
+
value: 20,
|
|
269
|
+
note: "Rejected items reported per lookup, highest score first, so a large cache cannot bloat the payload."
|
|
270
|
+
},
|
|
271
|
+
content_preview_chars: 120
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/behavior/contract.ts
|
|
276
|
+
var CONTRACT = anticipation_default;
|
|
277
|
+
var ALLOWED_FLAGS = /* @__PURE__ */ new Set(["i", "m", "s"]);
|
|
278
|
+
function compile(pattern, flags) {
|
|
279
|
+
for (const ch of flags) {
|
|
280
|
+
if (!ALLOWED_FLAGS.has(ch)) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Unsupported regex flag '${ch}' in behavior contract. Flags must be valid in both Python and ECMAScript.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return new RegExp(pattern, flags);
|
|
287
|
+
}
|
|
288
|
+
var RECALL_PATTERNS = CONTRACT.recall_bypass.patterns.map(
|
|
289
|
+
(p) => compile(p.pattern, _nullishCoalesce(p.flags, () => ( "")))
|
|
290
|
+
);
|
|
291
|
+
var THRESHOLDS = {
|
|
292
|
+
bm25: CONTRACT.thresholds.bm25.value,
|
|
293
|
+
novelTerm: CONTRACT.thresholds.novel_term.value,
|
|
294
|
+
minCorpusForNovelGate: CONTRACT.thresholds.min_corpus_for_novel_gate.value,
|
|
295
|
+
/** Lower bound on the derived per-query threshold. */
|
|
296
|
+
effectiveFloor: CONTRACT.thresholds.effective_floor.value,
|
|
297
|
+
/** Points of score allowed per query token when deriving the threshold. */
|
|
298
|
+
queryTokenScale: CONTRACT.thresholds.query_token_scale.value
|
|
299
|
+
};
|
|
300
|
+
var BM25_PARAMS = CONTRACT.bm25;
|
|
301
|
+
var STOP_WORDS = new Set(CONTRACT.bm25.stop_words);
|
|
302
|
+
var SUFFIXES = CONTRACT.bm25.suffixes;
|
|
303
|
+
var ALIAS_TOKEN_SOURCE = CONTRACT.bm25.alias_token.pattern;
|
|
304
|
+
function isRecallQuery(searchQuery) {
|
|
305
|
+
for (const q of _nullishCoalesce(searchQuery, () => ( []))) {
|
|
306
|
+
if (!q) continue;
|
|
307
|
+
for (const pattern of RECALL_PATTERNS) {
|
|
308
|
+
if (pattern.test(q)) return true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/cache/bm25.ts
|
|
315
|
+
var MIN_STEM_LEN = BM25_PARAMS.min_stem_len;
|
|
316
|
+
var MIN_TOKEN_LEN = BM25_PARAMS.min_token_len;
|
|
317
|
+
function stem(word) {
|
|
318
|
+
for (const suffix of SUFFIXES) {
|
|
319
|
+
if (word.endsWith(suffix) && word.length - suffix.length >= MIN_STEM_LEN) {
|
|
320
|
+
return word.slice(0, word.length - suffix.length);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return word;
|
|
324
|
+
}
|
|
325
|
+
function aliasTokenRe() {
|
|
326
|
+
return new RegExp(ALIAS_TOKEN_SOURCE, "g");
|
|
327
|
+
}
|
|
328
|
+
var WORD_RE = /[a-z0-9]+/g;
|
|
329
|
+
function tokenize(text) {
|
|
330
|
+
if (!text) return [];
|
|
331
|
+
const labels = [];
|
|
332
|
+
const remainder = text.replace(aliasTokenRe(), (_match, captured) => {
|
|
333
|
+
labels.push(captured.toLowerCase());
|
|
334
|
+
return " ";
|
|
335
|
+
});
|
|
336
|
+
const words = _nullishCoalesce(remainder.toLowerCase().match(WORD_RE), () => ( []));
|
|
337
|
+
const out = labels.slice();
|
|
338
|
+
for (const w of words) {
|
|
339
|
+
if (w.length >= MIN_TOKEN_LEN && !STOP_WORDS.has(w)) out.push(stem(w));
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
var BM25 = (_class = class _BM25 {
|
|
344
|
+
static __initStatic() {this.IDF_FLOOR = BM25_PARAMS.idf_floor}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
constructor(corpus, k1 = BM25_PARAMS.k1, b = BM25_PARAMS.b) {
|
|
353
|
+
this.k1 = k1;
|
|
354
|
+
this.b = b;
|
|
355
|
+
this.corpus = corpus;
|
|
356
|
+
this.docCount = corpus.length;
|
|
357
|
+
this.docLens = corpus.map((doc) => doc.length);
|
|
358
|
+
this.avgdl = this.docCount ? this.docLens.reduce((a, n) => a + n, 0) / this.docCount : 1;
|
|
359
|
+
const idf = /* @__PURE__ */ new Map();
|
|
360
|
+
const allTerms = /* @__PURE__ */ new Set();
|
|
361
|
+
for (const doc of corpus) for (const t of doc) allTerms.add(t);
|
|
362
|
+
for (const term of allTerms) {
|
|
363
|
+
let df = 0;
|
|
364
|
+
for (const doc of corpus) if (doc.includes(term)) df++;
|
|
365
|
+
const raw = Math.log((this.docCount - df + 0.5) / (df + 0.5) + 1);
|
|
366
|
+
idf.set(term, Math.max(raw, _BM25.IDF_FLOOR));
|
|
367
|
+
}
|
|
368
|
+
this.idf = idf;
|
|
369
|
+
}
|
|
370
|
+
score(queryTokens, docIdx) {
|
|
371
|
+
const doc = this.corpus[docIdx];
|
|
372
|
+
if (doc === void 0) return 0;
|
|
373
|
+
const dl = _nullishCoalesce(this.docLens[docIdx], () => ( 0));
|
|
374
|
+
const tfMap = /* @__PURE__ */ new Map();
|
|
375
|
+
for (const t of doc) tfMap.set(t, (_nullishCoalesce(tfMap.get(t), () => ( 0))) + 1);
|
|
376
|
+
let s = 0;
|
|
377
|
+
for (const term of queryTokens) {
|
|
378
|
+
const tf = tfMap.get(term);
|
|
379
|
+
if (tf === void 0) continue;
|
|
380
|
+
const idf = _nullishCoalesce(this.idf.get(term), () => ( _BM25.IDF_FLOOR));
|
|
381
|
+
const numerator = tf * (this.k1 + 1);
|
|
382
|
+
const denominator = tf + this.k1 * (1 - this.b + this.b * dl / this.avgdl);
|
|
383
|
+
s += idf * numerator / denominator;
|
|
384
|
+
}
|
|
385
|
+
return s;
|
|
386
|
+
}
|
|
387
|
+
scores(queryTokens) {
|
|
388
|
+
return this.corpus.map((_, i) => this.score(queryTokens, i));
|
|
389
|
+
}
|
|
390
|
+
}, _class.__initStatic(), _class);
|
|
391
|
+
|
|
392
|
+
// src/context/anticipation-cache.ts
|
|
393
|
+
var EFFECTIVE_FLOOR = CONTRACT.thresholds.effective_floor.value;
|
|
394
|
+
var QUERY_TOKEN_SCALE = CONTRACT.thresholds.query_token_scale.value;
|
|
395
|
+
var REJECTED_CAP = CONTRACT.telemetry.rejected_items_cap.value;
|
|
396
|
+
var PREVIEW_CHARS = CONTRACT.telemetry.content_preview_chars;
|
|
397
|
+
var ANY_SCOPE = "_any";
|
|
398
|
+
var DEFAULT_TTL_SECONDS = 1800;
|
|
399
|
+
var DEFAULT_MAX_BUNDLES = 50;
|
|
400
|
+
var AnticipationCache = (_class2 = class {
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
__init() {this.entries = /* @__PURE__ */ new Map()}
|
|
406
|
+
__init2() {this.items = []}
|
|
407
|
+
__init3() {this.corpusVocab = /* @__PURE__ */ new Set()}
|
|
408
|
+
__init4() {this.bm25 = null}
|
|
409
|
+
__init5() {this.bm25Dirty = true}
|
|
410
|
+
constructor(options = {}) {;_class2.prototype.__init.call(this);_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);
|
|
411
|
+
this.ttlSeconds = _nullishCoalesce(options.ttlSeconds, () => ( DEFAULT_TTL_SECONDS));
|
|
412
|
+
this.maxBundles = _nullishCoalesce(options.maxBundles, () => ( DEFAULT_MAX_BUNDLES));
|
|
413
|
+
this.now = _nullishCoalesce(options.now, () => ( monotonicNow));
|
|
414
|
+
this.onLookup = options.onLookup;
|
|
415
|
+
}
|
|
416
|
+
get size() {
|
|
417
|
+
return this.entries.size;
|
|
418
|
+
}
|
|
419
|
+
get itemCount() {
|
|
420
|
+
return this.items.length;
|
|
421
|
+
}
|
|
422
|
+
store(bundle) {
|
|
423
|
+
if (!bundle.bundleId) return;
|
|
424
|
+
this.evictExpired();
|
|
425
|
+
const entityId = bundle.entityId || ANY_SCOPE;
|
|
426
|
+
if (this.entries.has(bundle.bundleId)) this.dropBundle(bundle.bundleId);
|
|
427
|
+
const now = this.now();
|
|
428
|
+
this.entries.set(bundle.bundleId, {
|
|
429
|
+
bundleId: bundle.bundleId,
|
|
430
|
+
entityId,
|
|
431
|
+
conversationId: _nullishCoalesce(bundle.conversationId, () => ( null)),
|
|
432
|
+
bundleType: _nullishCoalesce(bundle.bundleType, () => ( null)),
|
|
433
|
+
searchQueries: [..._nullishCoalesce(bundle.searchQueries, () => ( []))],
|
|
434
|
+
storedAt: now,
|
|
435
|
+
createdAt: now,
|
|
436
|
+
ttlHintSeconds: _nullishCoalesce(bundle.ttlHintSeconds, () => ( null))
|
|
437
|
+
});
|
|
438
|
+
for (const [itemType, list] of Object.entries(_nullishCoalesce(bundle.itemsByType, () => ( {})))) {
|
|
439
|
+
for (const item of _nullishCoalesce(list, () => ( []))) {
|
|
440
|
+
const content = String(_nullishCoalesce(item.content, () => ( "")));
|
|
441
|
+
const tokens = tokenize(content);
|
|
442
|
+
this.items.push({ bundleId: bundle.bundleId, itemType, content, tokens, itemDict: item });
|
|
443
|
+
for (const t of tokens) this.corpusVocab.add(t);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
this.bm25Dirty = true;
|
|
447
|
+
while (this.entries.size > this.maxBundles) {
|
|
448
|
+
let oldest = null;
|
|
449
|
+
for (const e of this.entries.values()) {
|
|
450
|
+
if (oldest === null || e.storedAt < oldest.storedAt) oldest = e;
|
|
451
|
+
}
|
|
452
|
+
if (oldest === null) break;
|
|
453
|
+
this.dropBundle(oldest.bundleId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
lookup(params) {
|
|
457
|
+
const {
|
|
458
|
+
searchQuery = null,
|
|
459
|
+
entityId = null,
|
|
460
|
+
customerId = null,
|
|
461
|
+
clientId = null,
|
|
462
|
+
conversationId = null,
|
|
463
|
+
maxItems = 10
|
|
464
|
+
} = params;
|
|
465
|
+
this.evictExpired();
|
|
466
|
+
const accepted = this.buildAcceptedScope(entityId, customerId, clientId);
|
|
467
|
+
const telemetry = {
|
|
468
|
+
searchQuery: (_nullishCoalesce(searchQuery, () => ( []))).filter((q) => Boolean(q)),
|
|
469
|
+
entityId,
|
|
470
|
+
customerId,
|
|
471
|
+
clientId,
|
|
472
|
+
conversationId,
|
|
473
|
+
scopeFilterAccepted: [...accepted].sort(),
|
|
474
|
+
novelTermRatio: null,
|
|
475
|
+
bm25Threshold: null,
|
|
476
|
+
bm25QueryTokens: [],
|
|
477
|
+
itemsPicked: [],
|
|
478
|
+
itemsRejected: [],
|
|
479
|
+
coverage: null,
|
|
480
|
+
hit: false,
|
|
481
|
+
exitReason: "empty"
|
|
482
|
+
};
|
|
483
|
+
if (this.entries.size === 0 || this.items.length === 0) {
|
|
484
|
+
return this.miss(telemetry, "empty");
|
|
485
|
+
}
|
|
486
|
+
const hasQuery = (_nullishCoalesce(searchQuery, () => ( []))).some((q) => q && q.trim() !== "");
|
|
487
|
+
if (!hasQuery) {
|
|
488
|
+
return this.freshnessLookup(entityId, maxItems, telemetry);
|
|
489
|
+
}
|
|
490
|
+
if (_chunkZKOJDUN4cjs.getEnvFlag.call(void 0, CONTRACT.recall_bypass.env_flag) && isRecallQuery(searchQuery)) {
|
|
491
|
+
return this.miss(telemetry, "recall_bypass");
|
|
492
|
+
}
|
|
493
|
+
const queryTokens = (_nullishCoalesce(searchQuery, () => ( []))).flatMap((q) => tokenize(q));
|
|
494
|
+
telemetry.bm25QueryTokens = queryTokens;
|
|
495
|
+
if (queryTokens.length === 0) {
|
|
496
|
+
return this.miss(telemetry, "no_query_tokens");
|
|
497
|
+
}
|
|
498
|
+
const uniqueStems = new Set(queryTokens);
|
|
499
|
+
let novel = 0;
|
|
500
|
+
for (const s of uniqueStems) if (!this.corpusVocab.has(s)) novel++;
|
|
501
|
+
const novelRatio = uniqueStems.size ? novel / uniqueStems.size : 0;
|
|
502
|
+
telemetry.novelTermRatio = round4(novelRatio);
|
|
503
|
+
if (this.corpusVocab.size >= THRESHOLDS.minCorpusForNovelGate && novelRatio >= THRESHOLDS.novelTerm) {
|
|
504
|
+
return this.miss(telemetry, "novel_term_gate");
|
|
505
|
+
}
|
|
506
|
+
if (this.bm25Dirty || this.bm25 === null) {
|
|
507
|
+
if (this.items.length === 0) return this.miss(telemetry, "empty_corpus");
|
|
508
|
+
this.bm25 = new BM25(this.items.map((i) => i.tokens));
|
|
509
|
+
this.bm25Dirty = false;
|
|
510
|
+
}
|
|
511
|
+
const scores = this.bm25.scores(queryTokens);
|
|
512
|
+
const validBundles = this.getValidBundleIds(accepted, conversationId);
|
|
513
|
+
const effectiveThreshold = Math.max(
|
|
514
|
+
EFFECTIVE_FLOOR,
|
|
515
|
+
Math.min(THRESHOLDS.bm25, QUERY_TOKEN_SCALE * queryTokens.length)
|
|
516
|
+
);
|
|
517
|
+
telemetry.bm25Threshold = round4(effectiveThreshold);
|
|
518
|
+
const picked = [];
|
|
519
|
+
const rejected = [];
|
|
520
|
+
for (let idx = 0; idx < scores.length; idx++) {
|
|
521
|
+
const item = this.items[idx];
|
|
522
|
+
const score = scores[idx];
|
|
523
|
+
const reject = (reason) => rejected.push({
|
|
524
|
+
score,
|
|
525
|
+
payload: {
|
|
526
|
+
item_index: idx,
|
|
527
|
+
bundle_id: item.bundleId,
|
|
528
|
+
item_type: item.itemType,
|
|
529
|
+
content_first_120: item.content.slice(0, PREVIEW_CHARS),
|
|
530
|
+
bm25_score: round4(score),
|
|
531
|
+
reason
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
if (score < effectiveThreshold) {
|
|
535
|
+
reject("below_threshold");
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (!validBundles.has(item.bundleId)) {
|
|
539
|
+
reject("scope_filter_excluded");
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
picked.push({ score, item });
|
|
543
|
+
}
|
|
544
|
+
rejected.sort((a, b) => b.score - a.score);
|
|
545
|
+
telemetry.itemsRejected = rejected.slice(0, REJECTED_CAP).map((r) => r.payload);
|
|
546
|
+
if (picked.length === 0) {
|
|
547
|
+
return this.miss(telemetry, "no_items_above_threshold");
|
|
548
|
+
}
|
|
549
|
+
picked.sort((a, b) => b.score - a.score);
|
|
550
|
+
const top = picked.slice(0, maxItems);
|
|
551
|
+
const pickedStems = /* @__PURE__ */ new Set();
|
|
552
|
+
for (const { item } of top) for (const t of item.tokens) pickedStems.add(t);
|
|
553
|
+
let overlap = 0;
|
|
554
|
+
for (const s of uniqueStems) if (pickedStems.has(s)) overlap++;
|
|
555
|
+
const coverage = uniqueStems.size ? overlap / uniqueStems.size : 1;
|
|
556
|
+
telemetry.coverage = round4(coverage);
|
|
557
|
+
const coverageMin = _chunkZKOJDUN4cjs.getEnvFloat.call(void 0, "SYNAP_SDK_CACHE_COVERAGE_MIN");
|
|
558
|
+
if (coverageMin !== null && coverage < coverageMin) {
|
|
559
|
+
return this.miss(telemetry, "coverage_gate");
|
|
560
|
+
}
|
|
561
|
+
const itemsByType = {};
|
|
562
|
+
const bundleIds = /* @__PURE__ */ new Set();
|
|
563
|
+
for (const { item } of top) {
|
|
564
|
+
(itemsByType[item.itemType] ??= []).push(item.itemDict);
|
|
565
|
+
bundleIds.add(item.bundleId);
|
|
566
|
+
}
|
|
567
|
+
const refreshOnHit = !_chunkZKOJDUN4cjs.getEnvFlag.call(void 0, "SYNAP_SDK_CACHE_HONOR_TTL_HINT");
|
|
568
|
+
if (refreshOnHit) {
|
|
569
|
+
const now = this.now();
|
|
570
|
+
for (const id of bundleIds) {
|
|
571
|
+
const entry = this.entries.get(id);
|
|
572
|
+
if (entry) entry.storedAt = now;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
telemetry.hit = true;
|
|
576
|
+
telemetry.exitReason = "hit";
|
|
577
|
+
telemetry.itemsPicked = top.map(({ score, item }) => ({
|
|
578
|
+
bundle_id: item.bundleId,
|
|
579
|
+
item_type: item.itemType,
|
|
580
|
+
content_first_120: item.content.slice(0, PREVIEW_CHARS),
|
|
581
|
+
bm25_score: round4(score)
|
|
582
|
+
}));
|
|
583
|
+
this.fire(telemetry);
|
|
584
|
+
return {
|
|
585
|
+
itemsByType,
|
|
586
|
+
bundleIds: [...bundleIds],
|
|
587
|
+
score: top[0].score,
|
|
588
|
+
coverage
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Drop this entity's bundles after a write, so the cache stops serving a
|
|
593
|
+
* pre-write view. Gated on the flag: this is the write-path hook.
|
|
594
|
+
*/
|
|
595
|
+
invalidateEntity(entityId) {
|
|
596
|
+
if (!_chunkZKOJDUN4cjs.getEnvFlag.call(void 0, "SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE")) return 0;
|
|
597
|
+
return this.dropEntity(entityId);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Unconditionally drop this entity's bundles. Backs `cache.clear_user` and
|
|
601
|
+
* `cache.clear_customer`, which are explicit user requests and so are not
|
|
602
|
+
* gated on the invalidate-on-write flag.
|
|
603
|
+
*/
|
|
604
|
+
dropEntity(entityId) {
|
|
605
|
+
let dropped = 0;
|
|
606
|
+
for (const entry of [...this.entries.values()]) {
|
|
607
|
+
if (entry.entityId === ANY_SCOPE) continue;
|
|
608
|
+
if (entry.entityId === entityId) {
|
|
609
|
+
this.dropBundle(entry.bundleId);
|
|
610
|
+
dropped++;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return dropped;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* A read-only diagnostic projection of the cache.
|
|
617
|
+
*
|
|
618
|
+
* Backs `client.anticipation_cache_snapshot()` and mirrors Python's shape
|
|
619
|
+
* key for key, including snake_case keys: this output gets logged, diffed
|
|
620
|
+
* and pasted into issues across both SDKs, so it is a wire format.
|
|
621
|
+
*
|
|
622
|
+
* Note the 140-character content previews. That is Python's literal, and it
|
|
623
|
+
* is deliberately NOT the 120 of `telemetry.content_preview_chars` from the
|
|
624
|
+
* contract, which caps lookup telemetry rather than this snapshot.
|
|
625
|
+
*/
|
|
626
|
+
snapshot() {
|
|
627
|
+
const SNAPSHOT_PREVIEW_CHARS = 140;
|
|
628
|
+
const overallScope = /* @__PURE__ */ new Map();
|
|
629
|
+
const itemsByBundle = /* @__PURE__ */ new Map();
|
|
630
|
+
for (const record of this.items) {
|
|
631
|
+
const list = itemsByBundle.get(record.bundleId);
|
|
632
|
+
if (list === void 0) itemsByBundle.set(record.bundleId, [record]);
|
|
633
|
+
else list.push(record);
|
|
634
|
+
}
|
|
635
|
+
const bundles = [];
|
|
636
|
+
for (const [bundleId, entry] of this.entries) {
|
|
637
|
+
const perBundle = /* @__PURE__ */ new Map();
|
|
638
|
+
const itemPreviews = [];
|
|
639
|
+
for (const record of _nullishCoalesce(itemsByBundle.get(bundleId), () => ( []))) {
|
|
640
|
+
const scope = _nullishCoalesce(record.itemDict["scope"], () => ( null));
|
|
641
|
+
const key = scope === null ? "" : scope;
|
|
642
|
+
overallScope.set(key, (_nullishCoalesce(overallScope.get(key), () => ( 0))) + 1);
|
|
643
|
+
perBundle.set(key, (_nullishCoalesce(perBundle.get(key), () => ( 0))) + 1);
|
|
644
|
+
itemPreviews.push({
|
|
645
|
+
type: record.itemType,
|
|
646
|
+
scope,
|
|
647
|
+
content: record.content.slice(0, SNAPSHOT_PREVIEW_CHARS)
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
let totalItems = 0;
|
|
651
|
+
for (const n of perBundle.values()) totalItems += n;
|
|
652
|
+
bundles.push({
|
|
653
|
+
bundle_id: bundleId,
|
|
654
|
+
entity_id: entry.entityId,
|
|
655
|
+
conversation_id: entry.conversationId,
|
|
656
|
+
bundle_type: entry.bundleType,
|
|
657
|
+
search_queries: [...entry.searchQueries],
|
|
658
|
+
scope_counts: Object.fromEntries(perBundle),
|
|
659
|
+
total_items: totalItems,
|
|
660
|
+
items: itemPreviews
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
total_entries: this.entries.size,
|
|
665
|
+
total_item_records: this.items.length,
|
|
666
|
+
scope_breakdown_overall: Object.fromEntries(overallScope),
|
|
667
|
+
corpus_vocab_size: this.corpusVocab.size,
|
|
668
|
+
corpus_vocab_sample: [...this.corpusVocab].sort().slice(0, 80),
|
|
669
|
+
item_records: this.items.slice(0, 20).map((rec) => ({
|
|
670
|
+
bundle_id: rec.bundleId,
|
|
671
|
+
item_type: rec.itemType,
|
|
672
|
+
tokens: rec.tokens.slice(0, 25),
|
|
673
|
+
content: rec.content.slice(0, SNAPSHOT_PREVIEW_CHARS)
|
|
674
|
+
})),
|
|
675
|
+
bundles
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Drop every bundle belonging to one conversation.
|
|
680
|
+
*
|
|
681
|
+
* Backs the `compaction_update` signal on the Listen stream: a compaction
|
|
682
|
+
* rewrites the conversation's summary, so any bundle built from the previous
|
|
683
|
+
* state is stale and must not keep serving for the rest of its TTL.
|
|
684
|
+
*
|
|
685
|
+
* Unlike `dropEntity`, client-scope bundles are NOT spared: a bundle is only
|
|
686
|
+
* dropped here if it is bound to this specific conversation, and a shared
|
|
687
|
+
* client-scope bundle has no conversation id to match.
|
|
688
|
+
*/
|
|
689
|
+
dropConversation(conversationId) {
|
|
690
|
+
if (!conversationId) return 0;
|
|
691
|
+
let dropped = 0;
|
|
692
|
+
for (const entry of [...this.entries.values()]) {
|
|
693
|
+
if (entry.conversationId === conversationId) {
|
|
694
|
+
this.dropBundle(entry.bundleId);
|
|
695
|
+
dropped++;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return dropped;
|
|
699
|
+
}
|
|
700
|
+
clear() {
|
|
701
|
+
this.entries.clear();
|
|
702
|
+
this.items = [];
|
|
703
|
+
this.corpusVocab.clear();
|
|
704
|
+
this.bm25 = null;
|
|
705
|
+
this.bm25Dirty = true;
|
|
706
|
+
}
|
|
707
|
+
// ── internals ───────────────────────────────────────────────────────────────
|
|
708
|
+
miss(telemetry, reason) {
|
|
709
|
+
telemetry.exitReason = reason;
|
|
710
|
+
telemetry.hit = false;
|
|
711
|
+
this.fire(telemetry);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
fire(telemetry) {
|
|
715
|
+
try {
|
|
716
|
+
_optionalChain([this, 'access', _2 => _2.onLookup, 'optionalCall', _3 => _3(telemetry)]);
|
|
717
|
+
} catch (e2) {
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
freshnessLookup(entityId, maxItems, telemetry) {
|
|
721
|
+
const candidates = [...this.entries.values()].filter((e) => entityId === null || e.entityId === entityId || e.entityId === ANY_SCOPE).sort((a, b) => b.storedAt - a.storedAt);
|
|
722
|
+
if (candidates.length === 0) return this.miss(telemetry, "empty");
|
|
723
|
+
const newest = candidates[0];
|
|
724
|
+
const itemsByType = {};
|
|
725
|
+
let n = 0;
|
|
726
|
+
for (const item of this.items) {
|
|
727
|
+
if (item.bundleId !== newest.bundleId) continue;
|
|
728
|
+
if (n >= maxItems) break;
|
|
729
|
+
(itemsByType[item.itemType] ??= []).push(item.itemDict);
|
|
730
|
+
n++;
|
|
731
|
+
}
|
|
732
|
+
if (n === 0) return this.miss(telemetry, "empty");
|
|
733
|
+
telemetry.hit = true;
|
|
734
|
+
telemetry.exitReason = "hit";
|
|
735
|
+
this.fire(telemetry);
|
|
736
|
+
return { itemsByType, bundleIds: [newest.bundleId], score: 0, coverage: 1 };
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* The widened scope-match set for a request.
|
|
740
|
+
*
|
|
741
|
+
* A narrower-scope request also matches broader-scope bundles for the same
|
|
742
|
+
* customer or client. Falsy ids are dropped so an empty customer_id cannot
|
|
743
|
+
* accidentally match bundles keyed at "".
|
|
744
|
+
*/
|
|
745
|
+
buildAcceptedScope(entityId, customerId, clientId) {
|
|
746
|
+
const accepted = /* @__PURE__ */ new Set([ANY_SCOPE]);
|
|
747
|
+
if (entityId) accepted.add(entityId);
|
|
748
|
+
if (customerId) accepted.add(customerId);
|
|
749
|
+
if (clientId) accepted.add(clientId);
|
|
750
|
+
return accepted;
|
|
751
|
+
}
|
|
752
|
+
getValidBundleIds(accepted, conversationId) {
|
|
753
|
+
const valid = /* @__PURE__ */ new Set();
|
|
754
|
+
for (const entry of this.entries.values()) {
|
|
755
|
+
if (!accepted.has(entry.entityId)) continue;
|
|
756
|
+
if (entry.conversationId && conversationId && entry.conversationId !== conversationId) continue;
|
|
757
|
+
valid.add(entry.bundleId);
|
|
758
|
+
}
|
|
759
|
+
return valid;
|
|
760
|
+
}
|
|
761
|
+
effectiveTtl(entry) {
|
|
762
|
+
if (!_chunkZKOJDUN4cjs.getEnvFlag.call(void 0, "SYNAP_SDK_CACHE_HONOR_TTL_HINT")) return this.ttlSeconds;
|
|
763
|
+
if (entry.ttlHintSeconds == null || entry.ttlHintSeconds <= 0) return this.ttlSeconds;
|
|
764
|
+
return Math.min(this.ttlSeconds, entry.ttlHintSeconds);
|
|
765
|
+
}
|
|
766
|
+
evictExpired() {
|
|
767
|
+
const now = this.now();
|
|
768
|
+
const configuredMaxAge = _chunkZKOJDUN4cjs.getEnvFloat.call(void 0, "SYNAP_SDK_CACHE_MAX_ENTRY_AGE");
|
|
769
|
+
const maxAge = _nullishCoalesce(configuredMaxAge, () => ( this.ttlSeconds * 2));
|
|
770
|
+
for (const entry of [...this.entries.values()]) {
|
|
771
|
+
const idleExpired = now - entry.storedAt > this.effectiveTtl(entry);
|
|
772
|
+
const ageExpired = maxAge > 0 && now - entry.createdAt > maxAge;
|
|
773
|
+
if (idleExpired || ageExpired) this.dropBundle(entry.bundleId);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
dropBundle(bundleId) {
|
|
777
|
+
if (!this.entries.delete(bundleId)) return;
|
|
778
|
+
this.items = this.items.filter((i) => i.bundleId !== bundleId);
|
|
779
|
+
this.corpusVocab = new Set(this.items.flatMap((i) => i.tokens));
|
|
780
|
+
this.bm25Dirty = true;
|
|
781
|
+
}
|
|
782
|
+
}, _class2);
|
|
783
|
+
function monotonicNow() {
|
|
784
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() / 1e3 : Date.now() / 1e3;
|
|
785
|
+
}
|
|
786
|
+
function round4(n) {
|
|
787
|
+
return Number(n.toFixed(4));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
exports.CONTRACT = CONTRACT; exports.THRESHOLDS = THRESHOLDS; exports.isRecallQuery = isRecallQuery; exports.stem = stem; exports.tokenize = tokenize; exports.BM25 = BM25; exports.ANY_SCOPE = ANY_SCOPE; exports.AnticipationCache = AnticipationCache;
|
|
800
|
+
//# sourceMappingURL=chunk-NIDPTBNC.cjs.map
|