@whyparabola/cabar 0.0.0-stage → 0.1.1

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/dist/index.cjs ADDED
@@ -0,0 +1,681 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ADAPTIVE_DEFAULTS: () => ADAPTIVE_DEFAULTS,
24
+ CHAR4_COUNTER: () => CHAR4_COUNTER,
25
+ CabarSelector: () => CabarSelector,
26
+ DEFAULTS: () => DEFAULTS,
27
+ DEFAULT_RECENCY_DECAY_DAYS: () => DEFAULT_RECENCY_DECAY_DAYS,
28
+ PrismSelector: () => PrismSelector,
29
+ claimsRendering: () => claimsRendering,
30
+ cosineSimilarity: () => cosineSimilarity,
31
+ embeddingSimilarity: () => embeddingSimilarity,
32
+ estimateTokens: () => estimateTokens,
33
+ extract: () => extract,
34
+ jaccard: () => jaccard,
35
+ mention: () => mention,
36
+ normalizeScores: () => normalizeScores,
37
+ recencyDecay: () => recencyDecay,
38
+ shadowReport: () => shadowReport,
39
+ splitSentences: () => splitSentences,
40
+ toContext: () => toContext,
41
+ tokenize: () => tokenize,
42
+ truncateToTokens: () => truncateToTokens,
43
+ verbatim: () => verbatim
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // src/shared.ts
48
+ function estimateTokens(text) {
49
+ return Math.ceil(text.length / 4);
50
+ }
51
+ var CHAR4_COUNTER = { name: "char4", count: estimateTokens };
52
+ function cosineSimilarity(a, b) {
53
+ if (a.length !== b.length) throw new RangeError("cosineSimilarity: vectors must have the same length");
54
+ let dot = 0, na = 0, nb = 0;
55
+ for (let i = 0; i < a.length; i++) {
56
+ dot += a[i] * b[i];
57
+ na += a[i] * a[i];
58
+ nb += b[i] * b[i];
59
+ }
60
+ if (na === 0 || nb === 0) return 0;
61
+ return Math.max(0, Math.min(1, dot / Math.sqrt(na * nb)));
62
+ }
63
+ function embeddingSimilarity(a, b) {
64
+ if (!a.embedding || !b.embedding) throw new Error(`embeddingSimilarity: unit ${!a.embedding ? a.id : b.id} has no embedding`);
65
+ return cosineSimilarity(a.embedding, b.embedding);
66
+ }
67
+ var DEFAULT_RECENCY_DECAY_DAYS = 365;
68
+ function recencyDecay(ageInDays, decayDays = DEFAULT_RECENCY_DECAY_DAYS) {
69
+ if (ageInDays < 0) throw new RangeError("ageInDays must be >= 0");
70
+ if (decayDays <= 0) throw new RangeError("decayDays must be > 0");
71
+ return Math.exp(-ageInDays / decayDays);
72
+ }
73
+ function stripUndefined(obj) {
74
+ const out = {};
75
+ for (const key of Object.keys(obj)) {
76
+ if (obj[key] !== void 0) out[key] = obj[key];
77
+ }
78
+ return out;
79
+ }
80
+
81
+ // src/renderings.ts
82
+ var EXTRACT_TARGET_RATIO = 0.4;
83
+ var EXTRACT_FIRST_SENTENCE_BONUS = 1;
84
+ var MENTION_MAX_TOKENS = 25;
85
+ function tokenize(text) {
86
+ const matches = text.toLowerCase().match(/[a-z0-9]{3,}/g);
87
+ return new Set(matches ?? []);
88
+ }
89
+ function jaccard(a, b) {
90
+ if (a.size === 0 || b.size === 0) return 0;
91
+ let intersection = 0;
92
+ for (const t of a) {
93
+ if (b.has(t)) intersection++;
94
+ }
95
+ const union = a.size + b.size - intersection;
96
+ return union === 0 ? 0 : intersection / union;
97
+ }
98
+ function splitSentences(text) {
99
+ return text.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter(Boolean);
100
+ }
101
+ function truncateToTokens(text, maxTokens, counter) {
102
+ if (counter.count(text) <= maxTokens) return text;
103
+ const words = text.split(/\s+/).filter(Boolean);
104
+ let out = "";
105
+ for (const w of words) {
106
+ const candidate = out ? `${out} ${w}` : w;
107
+ if (counter.count(`${candidate}\u2026`) > maxTokens) break;
108
+ out = candidate;
109
+ }
110
+ if (!out) out = words[0] ?? "";
111
+ return `${out}\u2026`;
112
+ }
113
+ function verbatim(u) {
114
+ return u.content;
115
+ }
116
+ function extract(u, query, counter) {
117
+ const sentences = splitSentences(u.content);
118
+ if (sentences.length <= 1) return u.content.trim();
119
+ const queryTokens = tokenize(query);
120
+ const target = Math.max(1, Math.floor(counter.count(u.content) * EXTRACT_TARGET_RATIO));
121
+ const scored = [];
122
+ const seen = /* @__PURE__ */ new Set();
123
+ sentences.forEach((text, order) => {
124
+ const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
125
+ if (seen.has(normalized)) return;
126
+ seen.add(normalized);
127
+ let overlap = 0;
128
+ for (const t of tokenize(text)) {
129
+ if (queryTokens.has(t)) overlap++;
130
+ }
131
+ scored.push({
132
+ text,
133
+ order,
134
+ score: overlap + (order === 0 ? EXTRACT_FIRST_SENTENCE_BONUS : 0),
135
+ cost: counter.count(text)
136
+ });
137
+ });
138
+ const byScore = [...scored].sort((a, b) => b.score - a.score || a.order - b.order);
139
+ const chosen = [];
140
+ let used = 0;
141
+ for (const s of byScore) {
142
+ if (used + s.cost > target) continue;
143
+ chosen.push(s);
144
+ used += s.cost;
145
+ }
146
+ if (chosen.length === 0) chosen.push(byScore[0]);
147
+ chosen.sort((a, b) => a.order - b.order);
148
+ return chosen.map((s) => s.text).join(" ");
149
+ }
150
+ function claimsRendering(u) {
151
+ if (!u.claims?.length) return null;
152
+ return u.claims.map((c) => `- ${c}`).join("\n");
153
+ }
154
+ function mention(u, counter) {
155
+ const first = splitSentences(u.content)[0] ?? u.content.trim();
156
+ const snippet = truncateToTokens(first, MENTION_MAX_TOKENS, counter);
157
+ const idPart = u.timestamp ? `${u.id}@${u.timestamp.toISOString().slice(0, 10)}` : u.id;
158
+ return `[${idPart}] ${snippet}`;
159
+ }
160
+
161
+ // src/cabar.ts
162
+ var DEFAULTS = {
163
+ budgetTokens: 4e3,
164
+ maxUnits: 8,
165
+ tailBudgetTokens: 600,
166
+ tauFloor: 0.15,
167
+ recencyLambda: 0.3,
168
+ recencyDecayDays: DEFAULT_RECENCY_DECAY_DAYS,
169
+ noveltyWeight: 1,
170
+ guaranteedTokens: 0,
171
+ costExponent: 1
172
+ };
173
+ var ADAPTIVE_DEFAULTS = {
174
+ minKeep: 3,
175
+ relFloor: 0.4,
176
+ novFloor: 0.2,
177
+ maxCeilingTokens: 4e3
178
+ };
179
+ var MS_PER_DAY = 864e5;
180
+ function tokenize2(text) {
181
+ const matches = text.toLowerCase().match(/[a-z0-9]{3,}/g);
182
+ return new Set(matches ?? []);
183
+ }
184
+ function jaccard2(a, b) {
185
+ if (a.size === 0 || b.size === 0) return 0;
186
+ let intersection = 0;
187
+ for (const t of a) {
188
+ if (b.has(t)) intersection++;
189
+ }
190
+ const union = a.size + b.size - intersection;
191
+ return union === 0 ? 0 : intersection / union;
192
+ }
193
+ var CabarSelector = class {
194
+ /**
195
+ * Select which units fit the budget, greedily maximizing utility/cost
196
+ * density, with a best-single-vs-greedy swap (budgeted-max-coverage 2-approx
197
+ * guarantee) and an extractive tail summary for the rest.
198
+ */
199
+ static select(units, query, opts = {}) {
200
+ const cfg = { ...DEFAULTS, ...stripUndefined(opts) };
201
+ const now = opts.now ?? /* @__PURE__ */ new Date();
202
+ const counter = opts.tokenCounter ?? CHAR4_COUNTER;
203
+ const count = (t) => counter.count(t);
204
+ const adaptive = opts.adaptive === true;
205
+ const minKeep = opts.minKeep ?? ADAPTIVE_DEFAULTS.minKeep;
206
+ const relFloor = opts.relFloor ?? ADAPTIVE_DEFAULTS.relFloor;
207
+ const novFloor = opts.novFloor ?? ADAPTIVE_DEFAULTS.novFloor;
208
+ const maxCeilingTokens = opts.maxCeilingTokens ?? ADAPTIVE_DEFAULTS.maxCeilingTokens;
209
+ const capToMaxUnits = opts.capToMaxUnits === true;
210
+ const topScore = units.length > 0 ? Math.max(...units.map((u) => u.score)) : 1;
211
+ const costOf = /* @__PURE__ */ new Map();
212
+ const tokensOf = /* @__PURE__ */ new Map();
213
+ let candidateTokens = 0;
214
+ for (const u of units) {
215
+ const cost = count(u.content);
216
+ costOf.set(u.id, cost);
217
+ tokensOf.set(u.id, tokenize2(u.content));
218
+ candidateTokens += cost;
219
+ }
220
+ const effectiveBudget = adaptive ? maxCeilingTokens : cfg.budgetTokens;
221
+ const remainingBudget = Math.max(0, effectiveBudget - cfg.guaranteedTokens);
222
+ const recencyOf = (u) => {
223
+ if (!u.timestamp || cfg.recencyLambda === 0) return 1;
224
+ const ageInDays = Math.max(0, (now.getTime() - u.timestamp.getTime()) / MS_PER_DAY);
225
+ const decay = recencyDecay(ageInDays, cfg.recencyDecayDays);
226
+ return cfg.recencyLambda * decay + (1 - cfg.recencyLambda);
227
+ };
228
+ const noveltyOf = (u, selectedSoFar) => {
229
+ if (selectedSoFar.length === 0) return 1;
230
+ const a = tokensOf.get(u.id);
231
+ let maxSim = 0;
232
+ for (const s of selectedSoFar) {
233
+ const sim2 = opts.similarity ? opts.similarity(u, s) : jaccard2(a, tokensOf.get(s.id));
234
+ if (sim2 > maxSim) maxSim = sim2;
235
+ }
236
+ return 1 - maxSim;
237
+ };
238
+ const utilityOf = (u, selectedSoFar) => {
239
+ const r = u.score;
240
+ const rho = recencyOf(u);
241
+ const noveltyRaw = noveltyOf(u, selectedSoFar);
242
+ const effectiveNovelty = 1 - cfg.noveltyWeight * (1 - noveltyRaw);
243
+ return r * rho * effectiveNovelty;
244
+ };
245
+ const selected = [];
246
+ const selectedIds = /* @__PURE__ */ new Set();
247
+ let selectedTokens = 0;
248
+ let greedyTotalUtility = 0;
249
+ while (adaptive && !capToMaxUnits || selected.length < cfg.maxUnits) {
250
+ let best = null;
251
+ for (const u of units) {
252
+ if (selectedIds.has(u.id)) continue;
253
+ const cost = costOf.get(u.id);
254
+ if (selectedTokens + cost > remainingBudget) continue;
255
+ const utility = utilityOf(u, selected);
256
+ if (utility <= 0) continue;
257
+ const density = cost > 0 ? utility / Math.pow(cost, cfg.costExponent) : Infinity;
258
+ if (!best || density > best.density) {
259
+ best = { unit: u, utility, density };
260
+ }
261
+ }
262
+ if (!best) break;
263
+ if (adaptive && selected.length >= minKeep) {
264
+ const relFrac = best.unit.score / (topScore || 1);
265
+ const nov = noveltyOf(best.unit, selected);
266
+ if (relFrac < relFloor || nov < novFloor) break;
267
+ }
268
+ selected.push(best.unit);
269
+ selectedIds.add(best.unit.id);
270
+ selectedTokens += costOf.get(best.unit.id);
271
+ greedyTotalUtility += best.utility;
272
+ }
273
+ let swapped = false;
274
+ if (adaptive || cfg.maxUnits >= 1) {
275
+ let bestSingle = null;
276
+ for (const u of units) {
277
+ const cost = costOf.get(u.id);
278
+ if (cost > remainingBudget) continue;
279
+ const utility = utilityOf(u, []);
280
+ if (!bestSingle || utility > bestSingle.utility) {
281
+ bestSingle = { unit: u, utility };
282
+ }
283
+ }
284
+ if (bestSingle && bestSingle.utility > greedyTotalUtility) {
285
+ swapped = true;
286
+ selected.length = 0;
287
+ selected.push(bestSingle.unit);
288
+ selectedIds.clear();
289
+ selectedIds.add(bestSingle.unit.id);
290
+ selectedTokens = costOf.get(bestSingle.unit.id);
291
+ }
292
+ }
293
+ const tail = units.filter((u) => !selectedIds.has(u.id) && u.score >= cfg.tauFloor);
294
+ const extractive = buildTailSummary(tail, query, cfg.tailBudgetTokens, count);
295
+ const verbatim2 = tail.map((u) => u.content.trim()).join("\n");
296
+ let tailSummary = tail.length === 0 ? "" : count(verbatim2) < count(extractive) ? verbatim2 : extractive;
297
+ if (count(tailSummary) > cfg.tailBudgetTokens) {
298
+ tailSummary = counter === CHAR4_COUNTER ? truncateToTokens2(tailSummary, cfg.tailBudgetTokens) : truncateToTokens(tailSummary, cfg.tailBudgetTokens, counter);
299
+ }
300
+ const tailSummaryTokens = count(tailSummary);
301
+ const result = {
302
+ selected,
303
+ tail,
304
+ tailSummary,
305
+ stats: {
306
+ candidateCount: units.length,
307
+ candidateTokens,
308
+ selectedTokens,
309
+ tailSummaryTokens,
310
+ budgetTokens: effectiveBudget,
311
+ utilizedTokens: cfg.guaranteedTokens + selectedTokens + tailSummaryTokens
312
+ }
313
+ };
314
+ if (!opts.explain) return result;
315
+ const sim = (a, b) => opts.similarity ? opts.similarity(a, b) : jaccard2(tokensOf.get(a.id), tokensOf.get(b.id));
316
+ const unitCap = !adaptive || capToMaxUnits;
317
+ result.decisions = units.map((u) => {
318
+ const tokens = costOf.get(u.id);
319
+ const others = selected.filter((s) => s.id !== u.id);
320
+ let maxSim = 0;
321
+ let closest;
322
+ for (const s of others) {
323
+ const v = sim(u, s);
324
+ if (v > maxSim) [maxSim, closest] = [v, s.id];
325
+ }
326
+ const base = { id: u.id, tokens, relevance: u.score, recency: recencyOf(u), novelty: 1 - maxSim };
327
+ if (selectedIds.has(u.id)) {
328
+ return { ...base, status: "selected", reason: swapped ? "best single unit: worth more alone than the greedy set" : "selected" };
329
+ }
330
+ if (u.score < cfg.tauFloor) {
331
+ return { ...base, status: "dropped", reason: `relevance ${u.score.toFixed(2)} is below tauFloor ${cfg.tauFloor}` };
332
+ }
333
+ let reason;
334
+ if (swapped) reason = "a single unit beat the greedy set";
335
+ else if (maxSim >= 1 - (adaptive ? novFloor : 0.2) && closest)
336
+ reason = `redundant with ${closest} (similarity ${maxSim.toFixed(2)})`;
337
+ else if (selectedTokens + tokens > remainingBudget) reason = `doesn't fit: needs ${tokens} tokens, ${Math.max(0, remainingBudget - selectedTokens)} left`;
338
+ else if (unitCap && selected.length >= cfg.maxUnits) reason = `maxUnits (${cfg.maxUnits}) reached`;
339
+ else if (adaptive && u.score / (topScore || 1) < relFloor) reason = `below the adaptive relevance cliff (${(u.score / (topScore || 1)).toFixed(2)} of the top score)`;
340
+ else reason = "adaptive stop: lower value per token than what was kept";
341
+ return { ...base, status: "tail", reason, ...reason.startsWith("redundant") ? { redundantWith: closest } : {} };
342
+ });
343
+ return result;
344
+ }
345
+ };
346
+ function truncateToTokens2(text, budgetTokens) {
347
+ const maxChars = budgetTokens * 4;
348
+ if (text.length <= maxChars) return text;
349
+ const cut = text.slice(0, maxChars);
350
+ const lastSpace = cut.lastIndexOf(" ");
351
+ return (lastSpace > maxChars * 0.8 ? cut.slice(0, lastSpace) : cut).trimEnd() + "\u2026";
352
+ }
353
+ function buildTailSummary(tail, query, tailBudgetTokens, count) {
354
+ if (tail.length === 0 || tailBudgetTokens <= 0) return "";
355
+ const queryTokens = tokenize2(query);
356
+ const sentences = [];
357
+ let order = 0;
358
+ for (const unit of tail) {
359
+ const parts = unit.content.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter(Boolean);
360
+ for (const text of parts) {
361
+ const tokens = tokenize2(text);
362
+ let overlap = 0;
363
+ for (const t of tokens) {
364
+ if (queryTokens.has(t)) overlap++;
365
+ }
366
+ sentences.push({
367
+ text,
368
+ score: overlap + unit.score,
369
+ unitId: unit.id,
370
+ timestamp: unit.timestamp,
371
+ order: order++
372
+ });
373
+ }
374
+ }
375
+ sentences.sort((a, b) => b.score - a.score || a.order - b.order);
376
+ const seen = /* @__PURE__ */ new Set();
377
+ const lines = [];
378
+ let usedTokens = 0;
379
+ for (const s of sentences) {
380
+ const normalized = s.text.toLowerCase().replace(/\s+/g, " ").trim();
381
+ if (seen.has(normalized)) continue;
382
+ const idPart = s.timestamp ? `${s.unitId}@${s.timestamp.toISOString().slice(0, 10)}` : s.unitId;
383
+ const line = `[${idPart}] ${s.text}`;
384
+ const lineTokens = count(lines.length === 0 ? line : `
385
+ ${line}`);
386
+ if (usedTokens + lineTokens > tailBudgetTokens) break;
387
+ seen.add(normalized);
388
+ lines.push(line);
389
+ usedTokens += lineTokens;
390
+ }
391
+ return lines.join("\n");
392
+ }
393
+
394
+ // src/prism.ts
395
+ var FIDELITY_RANK = { mention: 0, claims: 1, extract: 2, verbatim: 3 };
396
+ var DEFAULTS2 = {
397
+ budgetTokens: 4e3,
398
+ maxUnits: 12,
399
+ tailBudgetTokens: 600,
400
+ tauFloor: 0.15,
401
+ recencyLambda: 0.3,
402
+ recencyDecayDays: DEFAULT_RECENCY_DECAY_DAYS,
403
+ noveltyWeight: 1,
404
+ guaranteedTokens: 0,
405
+ hysteresisBonus: 0
406
+ };
407
+ var ADAPTIVE_DEFAULTS2 = {
408
+ minKeep: 3,
409
+ relFloor: 0.4,
410
+ novFloor: 0.2,
411
+ maxCeilingTokens: 4e3
412
+ };
413
+ var DEFAULT_FIDELITY_FACTORS = {
414
+ verbatim: 1,
415
+ extract: 0.85,
416
+ claims: 0.75,
417
+ mention: 0.3
418
+ };
419
+ var MS_PER_DAY2 = 864e5;
420
+ var ALL_FIDELITIES = ["verbatim", "extract", "claims", "mention"];
421
+ var PrismSelector = class {
422
+ /**
423
+ * MCKP-greedy multi-fidelity selection: build each unit's rendering menu,
424
+ * greedily apply the densest feasible ADD/UPGRADE move, run the KMN
425
+ * best-single swap, then floor the remainder into a mention tail.
426
+ */
427
+ static select(units, query, opts = {}) {
428
+ const cfg = { ...DEFAULTS2, ...stripUndefined(opts) };
429
+ const now = opts.now ?? /* @__PURE__ */ new Date();
430
+ const counter = opts.tokenCounter ?? CHAR4_COUNTER;
431
+ const phi = { ...DEFAULT_FIDELITY_FACTORS, ...opts.fidelityFactors };
432
+ const adaptive = opts.adaptive === true;
433
+ const minKeep = opts.minKeep ?? ADAPTIVE_DEFAULTS2.minKeep;
434
+ const relFloor = opts.relFloor ?? ADAPTIVE_DEFAULTS2.relFloor;
435
+ const novFloor = opts.novFloor ?? ADAPTIVE_DEFAULTS2.novFloor;
436
+ const maxCeilingTokens = opts.maxCeilingTokens ?? ADAPTIVE_DEFAULTS2.maxCeilingTokens;
437
+ const topScore = units.length > 0 ? Math.max(...units.map((u) => u.score)) : 1;
438
+ const effectiveBudget = adaptive ? maxCeilingTokens : cfg.budgetTokens;
439
+ const budget = Math.max(0, effectiveBudget - cfg.guaranteedTokens);
440
+ const unitById = /* @__PURE__ */ new Map();
441
+ const inputIndex = /* @__PURE__ */ new Map();
442
+ units.forEach((u, i) => {
443
+ unitById.set(u.id, u);
444
+ inputIndex.set(u.id, i);
445
+ });
446
+ const menuOf = /* @__PURE__ */ new Map();
447
+ let candidateTokens = 0;
448
+ for (const u of units) {
449
+ const vCost = counter.count(u.content);
450
+ candidateTokens += vCost;
451
+ const menu = [];
452
+ for (const f of ALL_FIDELITIES) {
453
+ const rendered = renderFidelity(u, f, query, counter);
454
+ if (rendered === null) continue;
455
+ const cost = counter.count(rendered);
456
+ if (f !== "verbatim" && cost >= vCost) continue;
457
+ menu.push({ fidelity: f, rendered, cost, tokens: tokenize(rendered), phi: phi[f], rank: FIDELITY_RANK[f] });
458
+ }
459
+ menuOf.set(u.id, menu);
460
+ }
461
+ const recencyOf = (u) => {
462
+ if (!u.timestamp || cfg.recencyLambda === 0) return 1;
463
+ const ageInDays = Math.max(0, (now.getTime() - u.timestamp.getTime()) / MS_PER_DAY2);
464
+ const decay = recencyDecay(ageInDays, cfg.recencyDecayDays);
465
+ return cfg.recencyLambda * decay + (1 - cfg.recencyLambda);
466
+ };
467
+ const chosen = /* @__PURE__ */ new Map();
468
+ let selectedTokens = 0;
469
+ let totalUtility = 0;
470
+ let moves = 0;
471
+ const prevRank = /* @__PURE__ */ new Map();
472
+ for (const p of opts.prevSelected ?? []) {
473
+ const r = FIDELITY_RANK[p.fidelity];
474
+ const existing = prevRank.get(p.unitId);
475
+ if (existing === void 0 || r < existing) prevRank.set(p.unitId, r);
476
+ }
477
+ const noveltyOf = (u, tokens, excludeId) => {
478
+ let maxSim = 0;
479
+ for (const [id, r] of chosen) {
480
+ if (id === excludeId) continue;
481
+ const sim = opts.similarity ? opts.similarity(u, unitById.get(id)) : jaccard(tokens, r.tokens);
482
+ if (sim > maxSim) maxSim = sim;
483
+ }
484
+ return 1 - maxSim;
485
+ };
486
+ const utilityOf = (u, r, excludeId) => {
487
+ const noveltyRaw = noveltyOf(u, r.tokens, excludeId);
488
+ const effectiveNovelty = 1 - cfg.noveltyWeight * (1 - noveltyRaw);
489
+ let g = u.score * recencyOf(u) * effectiveNovelty * r.phi;
490
+ const pr = prevRank.get(u.id);
491
+ if (cfg.hysteresisBonus > 0 && pr !== void 0 && r.rank >= pr) {
492
+ g *= 1 + cfg.hysteresisBonus;
493
+ }
494
+ return g;
495
+ };
496
+ const better = (a, b) => b === null || a.density > b.density || a.density === b.density && a.deltaG > b.deltaG;
497
+ for (; ; ) {
498
+ let best = null;
499
+ for (const u of units) {
500
+ const menu = menuOf.get(u.id);
501
+ const current = chosen.get(u.id);
502
+ if (!current) {
503
+ if (!adaptive && chosen.size >= cfg.maxUnits) continue;
504
+ for (const r of menu) {
505
+ if (r.cost <= 0) continue;
506
+ if (selectedTokens + r.cost > budget) continue;
507
+ if (adaptive && chosen.size >= minKeep) {
508
+ const relFrac = u.score / (topScore || 1);
509
+ const nov = noveltyOf(u, r.tokens, null);
510
+ if (relFrac < relFloor || nov < novFloor) continue;
511
+ }
512
+ const deltaG = utilityOf(u, r, null);
513
+ if (deltaG <= 0) continue;
514
+ const move = { unitId: u.id, rendering: r, deltaG, deltaC: r.cost, density: deltaG / r.cost };
515
+ if (better(move, best)) best = move;
516
+ }
517
+ } else {
518
+ const baseG = utilityOf(u, current, u.id);
519
+ for (const r of menu) {
520
+ const deltaC = r.cost - current.cost;
521
+ if (deltaC <= 0) continue;
522
+ if (selectedTokens + deltaC > budget) continue;
523
+ const deltaG = utilityOf(u, r, u.id) - baseG;
524
+ if (deltaG <= 0) continue;
525
+ const move = { unitId: u.id, rendering: r, deltaG, deltaC, density: deltaG / deltaC };
526
+ if (better(move, best)) best = move;
527
+ }
528
+ }
529
+ }
530
+ if (!best) break;
531
+ selectedTokens += best.deltaC;
532
+ totalUtility += best.deltaG;
533
+ chosen.set(best.unitId, best.rendering);
534
+ moves++;
535
+ }
536
+ if (adaptive || cfg.maxUnits >= 1) {
537
+ let bestSingle = null;
538
+ for (const u of units) {
539
+ const vr = menuOf.get(u.id).find((r) => r.fidelity === "verbatim");
540
+ if (!vr || vr.cost > budget) continue;
541
+ const utility = u.score * recencyOf(u) * vr.phi;
542
+ if (!bestSingle || utility > bestSingle.utility) bestSingle = { unit: u, rendering: vr, utility };
543
+ }
544
+ if (bestSingle && bestSingle.utility > totalUtility) {
545
+ chosen.clear();
546
+ chosen.set(bestSingle.unit.id, bestSingle.rendering);
547
+ selectedTokens = bestSingle.rendering.cost;
548
+ totalUtility = bestSingle.utility;
549
+ }
550
+ }
551
+ const eligible = units.filter((u) => !chosen.has(u.id) && u.score >= cfg.tauFloor).sort((a, b) => b.score - a.score || inputIndex.get(a.id) - inputIndex.get(b.id));
552
+ const mentionLines = [];
553
+ let mentionUsed = 0;
554
+ for (const u of eligible) {
555
+ const line = mention(u, counter);
556
+ const lineTokens = counter.count(mentionLines.length === 0 ? line : `
557
+ ${line}`);
558
+ if (mentionUsed + lineTokens > cfg.tailBudgetTokens) break;
559
+ mentionLines.push(line);
560
+ mentionUsed += lineTokens;
561
+ }
562
+ const mentionBlock = mentionLines.join("\n");
563
+ const verbatimBlock = eligible.map((u) => u.content.trim()).join("\n");
564
+ let tailBlock = eligible.length === 0 ? "" : counter.count(verbatimBlock) < counter.count(mentionBlock) ? verbatimBlock : mentionBlock;
565
+ if (counter.count(tailBlock) > cfg.tailBudgetTokens) {
566
+ tailBlock = truncateToTokens(tailBlock, cfg.tailBudgetTokens, counter);
567
+ }
568
+ const tailTokens = counter.count(tailBlock);
569
+ const selected = [...chosen.entries()].map(([id, r]) => ({ unit: unitById.get(id), fidelity: r.fidelity, rendered: r.rendered, tokens: r.cost })).sort((a, b) => tsKey(a.unit) - tsKey(b.unit) || a.unit.id.localeCompare(b.unit.id));
570
+ const fidelityHistogram = { verbatim: 0, extract: 0, claims: 0, mention: 0 };
571
+ for (const s of selected) fidelityHistogram[s.fidelity]++;
572
+ return {
573
+ selected,
574
+ tail: eligible,
575
+ tailBlock,
576
+ stats: {
577
+ candidateTokens,
578
+ selectedTokens,
579
+ tailTokens,
580
+ budgetTokens: effectiveBudget,
581
+ utilizedTokens: cfg.guaranteedTokens + selectedTokens + tailTokens,
582
+ fidelityHistogram,
583
+ moves
584
+ }
585
+ };
586
+ }
587
+ };
588
+ function renderFidelity(u, f, query, counter) {
589
+ switch (f) {
590
+ case "verbatim":
591
+ return verbatim(u);
592
+ case "extract":
593
+ return extract(u, query, counter);
594
+ case "claims":
595
+ return claimsRendering(u);
596
+ case "mention":
597
+ return mention(u, counter);
598
+ default: {
599
+ const _exhaustive = f;
600
+ return _exhaustive;
601
+ }
602
+ }
603
+ }
604
+ function tsKey(u) {
605
+ return u.timestamp ? u.timestamp.getTime() : 0;
606
+ }
607
+
608
+ // src/normalize.ts
609
+ function normalizeScores(units, opts = {}) {
610
+ if (units.length === 0) return [];
611
+ const method = opts.method ?? "minmax";
612
+ const raw = units.map((u) => opts.higherIsBetter === false ? -u.score : u.score);
613
+ for (const s of raw) {
614
+ if (!Number.isFinite(s)) throw new RangeError("normalizeScores: every score must be a finite number");
615
+ }
616
+ const hi = Math.max(...raw);
617
+ const lo = Math.min(...raw);
618
+ let map;
619
+ if (method === "max") {
620
+ if (lo < 0) throw new RangeError("normalizeScores: method 'max' needs non-negative scores, use 'minmax'");
621
+ map = (s) => hi === 0 ? 1 : s / hi;
622
+ } else {
623
+ map = (s) => hi === lo ? 1 : (s - lo) / (hi - lo);
624
+ }
625
+ return units.map((u, i) => ({ ...u, score: map(raw[i]) }));
626
+ }
627
+
628
+ // src/context.ts
629
+ function isPrism(r) {
630
+ return "tailBlock" in r;
631
+ }
632
+ function toContext(result, opts = {}) {
633
+ const separator = opts.separator ?? "\n\n";
634
+ const heading = opts.tailHeading ?? "Other possibly relevant notes:";
635
+ const format = opts.format ?? ((text) => text);
636
+ const parts = isPrism(result) ? result.selected.map((s) => format(s.rendered, s.unit.id)) : result.selected.map((u) => format(u.content, u.id));
637
+ const tail = isPrism(result) ? result.tailBlock : result.tailSummary;
638
+ if (tail) parts.push(heading ? `${heading}
639
+ ${tail}` : tail);
640
+ return parts.join(separator);
641
+ }
642
+ function shadowReport(units, query, opts = {}) {
643
+ const counter = opts.tokenCounter ?? CHAR4_COUNTER;
644
+ const result = CabarSelector.select(units, query, opts);
645
+ const sendAllTokens = units.reduce((n, u) => n + counter.count(u.content), 0);
646
+ const cabarTokens = result.stats.selectedTokens + result.stats.tailSummaryTokens;
647
+ const kept = new Set([...result.selected, ...result.tail].map((u) => u.id));
648
+ return {
649
+ sendAllTokens,
650
+ cabarTokens,
651
+ savedFraction: sendAllTokens === 0 ? 0 : 1 - cabarTokens / sendAllTokens,
652
+ selectedIds: result.selected.map((u) => u.id),
653
+ tailIds: result.tail.map((u) => u.id),
654
+ droppedIds: units.filter((u) => !kept.has(u.id)).map((u) => u.id),
655
+ result
656
+ };
657
+ }
658
+ // Annotate the CommonJS export names for ESM import in node:
659
+ 0 && (module.exports = {
660
+ ADAPTIVE_DEFAULTS,
661
+ CHAR4_COUNTER,
662
+ CabarSelector,
663
+ DEFAULTS,
664
+ DEFAULT_RECENCY_DECAY_DAYS,
665
+ PrismSelector,
666
+ claimsRendering,
667
+ cosineSimilarity,
668
+ embeddingSimilarity,
669
+ estimateTokens,
670
+ extract,
671
+ jaccard,
672
+ mention,
673
+ normalizeScores,
674
+ recencyDecay,
675
+ shadowReport,
676
+ splitSentences,
677
+ toContext,
678
+ tokenize,
679
+ truncateToTokens,
680
+ verbatim
681
+ });