@seanhogg/builderforce-memory 2026.9.29 → 2026.9.30

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.
Files changed (104) hide show
  1. package/dist/bridges/AnthropicBridge.d.ts.map +1 -1
  2. package/dist/bridges/AnthropicBridge.js +24 -65
  3. package/dist/bridges/AnthropicBridge.js.map +1 -1
  4. package/dist/bridges/OpenAIBridge.d.ts.map +1 -1
  5. package/dist/bridges/OpenAIBridge.js +12 -46
  6. package/dist/bridges/OpenAIBridge.js.map +1 -1
  7. package/dist/bridges/VertexAIBridge.d.ts.map +1 -1
  8. package/dist/bridges/VertexAIBridge.js +3 -34
  9. package/dist/bridges/VertexAIBridge.js.map +1 -1
  10. package/dist/distillation/DistillationEngine.d.ts +71 -54
  11. package/dist/distillation/DistillationEngine.d.ts.map +1 -1
  12. package/dist/distillation/DistillationEngine.js +112 -95
  13. package/dist/distillation/DistillationEngine.js.map +1 -1
  14. package/dist/distillation/index.d.ts +7 -1
  15. package/dist/distillation/index.d.ts.map +1 -1
  16. package/dist/distillation/index.js +5 -0
  17. package/dist/distillation/index.js.map +1 -1
  18. package/dist/distillation/ports.d.ts +43 -0
  19. package/dist/distillation/ports.d.ts.map +1 -0
  20. package/dist/distillation/ports.js +29 -0
  21. package/dist/distillation/ports.js.map +1 -0
  22. package/dist/eval/EvalHarness.d.ts +2 -1
  23. package/dist/eval/EvalHarness.d.ts.map +1 -1
  24. package/dist/eval/EvalHarness.js +13 -1
  25. package/dist/eval/EvalHarness.js.map +1 -1
  26. package/dist/eval/baseline.d.ts +33 -0
  27. package/dist/eval/baseline.d.ts.map +1 -0
  28. package/dist/eval/baseline.js +41 -0
  29. package/dist/eval/baseline.js.map +1 -0
  30. package/dist/eval/index.d.ts +1 -0
  31. package/dist/eval/index.d.ts.map +1 -1
  32. package/dist/eval/index.js +1 -0
  33. package/dist/eval/index.js.map +1 -1
  34. package/dist/eval/types.d.ts +7 -0
  35. package/dist/eval/types.d.ts.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/rag/EnterpriseRetriever.d.ts.map +1 -1
  41. package/dist/rag/EnterpriseRetriever.js +2 -1
  42. package/dist/rag/EnterpriseRetriever.js.map +1 -1
  43. package/dist/retrieval/HybridRetriever.d.ts.map +1 -1
  44. package/dist/retrieval/HybridRetriever.js +2 -1
  45. package/dist/retrieval/HybridRetriever.js.map +1 -1
  46. package/dist/retrieval/decay.d.ts +24 -0
  47. package/dist/retrieval/decay.d.ts.map +1 -0
  48. package/dist/retrieval/decay.js +29 -0
  49. package/dist/retrieval/decay.js.map +1 -0
  50. package/dist/retrieval/fusion.d.ts +13 -19
  51. package/dist/retrieval/fusion.d.ts.map +1 -1
  52. package/dist/retrieval/fusion.js +17 -44
  53. package/dist/retrieval/fusion.js.map +1 -1
  54. package/dist/retrieval/index.d.ts +13 -6
  55. package/dist/retrieval/index.d.ts.map +1 -1
  56. package/dist/retrieval/index.js +10 -5
  57. package/dist/retrieval/index.js.map +1 -1
  58. package/dist/retrieval/mmr.d.ts +60 -0
  59. package/dist/retrieval/mmr.d.ts.map +1 -0
  60. package/dist/retrieval/mmr.js +94 -0
  61. package/dist/retrieval/mmr.js.map +1 -0
  62. package/dist/retrieval/weightedFusion.d.ts +64 -0
  63. package/dist/retrieval/weightedFusion.d.ts.map +1 -0
  64. package/dist/retrieval/weightedFusion.js +80 -0
  65. package/dist/retrieval/weightedFusion.js.map +1 -0
  66. package/dist/similarity/index.d.ts +6 -4
  67. package/dist/similarity/index.d.ts.map +1 -1
  68. package/dist/similarity/index.js +5 -3
  69. package/dist/similarity/index.js.map +1 -1
  70. package/dist/wire/index.d.ts +18 -0
  71. package/dist/wire/index.d.ts.map +1 -0
  72. package/dist/wire/index.js +17 -0
  73. package/dist/wire/index.js.map +1 -0
  74. package/dist/wire/sse.d.ts +29 -0
  75. package/dist/wire/sse.d.ts.map +1 -0
  76. package/dist/wire/sse.js +81 -0
  77. package/dist/wire/sse.js.map +1 -0
  78. package/dist/wire/usage.d.ts +46 -0
  79. package/dist/wire/usage.d.ts.map +1 -0
  80. package/dist/wire/usage.js +78 -0
  81. package/dist/wire/usage.js.map +1 -0
  82. package/package.json +11 -3
  83. package/src/bridges/AnthropicBridge.ts +24 -68
  84. package/src/bridges/OpenAIBridge.ts +11 -50
  85. package/src/bridges/VertexAIBridge.ts +3 -32
  86. package/src/distillation/DistillationEngine.ts +176 -141
  87. package/src/distillation/index.ts +15 -1
  88. package/src/distillation/ports.ts +55 -0
  89. package/src/eval/EvalHarness.ts +12 -1
  90. package/src/eval/baseline.ts +51 -0
  91. package/src/eval/index.ts +1 -0
  92. package/src/eval/types.ts +7 -0
  93. package/src/index.ts +5 -2
  94. package/src/rag/EnterpriseRetriever.ts +2 -1
  95. package/src/retrieval/HybridRetriever.ts +2 -1
  96. package/src/retrieval/decay.ts +29 -0
  97. package/src/retrieval/fusion.ts +19 -52
  98. package/src/retrieval/index.ts +24 -6
  99. package/src/retrieval/mmr.ts +126 -0
  100. package/src/retrieval/weightedFusion.ts +101 -0
  101. package/src/similarity/index.ts +6 -4
  102. package/src/wire/index.ts +18 -0
  103. package/src/wire/sse.ts +74 -0
  104. package/src/wire/usage.ts +95 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Maximal Marginal Relevance — ONE greedy selection loop, two similarity bases.
3
+ *
4
+ * MMR reranks a relevance-ordered list so the top of it is not five near-duplicates:
5
+ * each step picks the candidate maximising `λ·relevance − (1−λ)·max sim(candidate,
6
+ * already-selected)`. λ=1 is pure relevance; lower λ buys diversity.
7
+ *
8
+ * What differs between callers is only what "similar" means:
9
+ * • {@link maximalMarginalRelevance} — cosine over embedding vectors, relevance =
10
+ * cosine to the query vector. For retrievers that hold vectors;
11
+ * • {@link textMaximalMarginalRelevance} — Jaccard over word tokens, relevance =
12
+ * the candidate's own score min-max normalised onto [0,1]. For retrievers that
13
+ * only have text and a fused score (an FTS5 + sqlite-vec hybrid, say).
14
+ *
15
+ * Both run {@link mmrSelect}, so the selection rule itself exists once.
16
+ *
17
+ * Pure and zero-dependency.
18
+ */
19
+ import { cosineSimilarity, jaccardSimilarity, tokenize } from '../similarity/index.js';
20
+ /** Greedy MMR selection. Returns the picked candidates in pick order. */
21
+ export function mmrSelect(items, opts) {
22
+ const lambda = opts.lambda ?? 0.7;
23
+ const limit = Math.min(opts.topK ?? items.length, items.length);
24
+ const remaining = [...items];
25
+ const relevance = new Map();
26
+ for (const item of remaining)
27
+ relevance.set(item, opts.relevance(item));
28
+ const selected = [];
29
+ while (selected.length < limit && remaining.length > 0) {
30
+ let bestIdx = -1;
31
+ let bestScore = -Infinity;
32
+ for (let i = 0; i < remaining.length; i++) {
33
+ const candidate = remaining[i];
34
+ let maxSimToSelected = 0;
35
+ for (const s of selected) {
36
+ const sim = opts.similarity(candidate, s);
37
+ if (sim > maxSimToSelected)
38
+ maxSimToSelected = sim;
39
+ }
40
+ const score = lambda * relevance.get(candidate) - (1 - lambda) * maxSimToSelected;
41
+ const wins = score > bestScore
42
+ || (score === bestScore && bestIdx >= 0 && opts.tieScore !== undefined
43
+ && opts.tieScore(candidate) > opts.tieScore(remaining[bestIdx]));
44
+ if (wins) {
45
+ bestScore = score;
46
+ bestIdx = i;
47
+ }
48
+ }
49
+ if (bestIdx < 0)
50
+ break;
51
+ selected.push(remaining.splice(bestIdx, 1)[0]);
52
+ }
53
+ return selected;
54
+ }
55
+ /**
56
+ * Vector MMR. Selects up to `topK` candidates by cosine relevance to `queryVec`,
57
+ * penalising cosine similarity to what is already selected. Candidates without
58
+ * vectors should be filtered out by the caller (they cannot be MMR-scored).
59
+ */
60
+ export function maximalMarginalRelevance(queryVec, candidates, topK, lambda = 0.7) {
61
+ return mmrSelect(candidates, {
62
+ relevance: (c) => cosineSimilarity(queryVec, c.vector),
63
+ similarity: (a, b) => cosineSimilarity(a.vector, b.vector),
64
+ lambda,
65
+ topK,
66
+ }).map((c) => c.id);
67
+ }
68
+ /**
69
+ * Text MMR. Reorders every item, judging diversity by Jaccard similarity of word
70
+ * tokens, and relevance by the item's `score` min-max normalised onto [0,1] (all
71
+ * equal → 1). An exact tie goes to the higher raw score. `lambda` is clamped to
72
+ * [0,1]; at 1 this is a plain score sort.
73
+ */
74
+ export function textMaximalMarginalRelevance(items, lambda = 0.7) {
75
+ if (items.length <= 1)
76
+ return [...items];
77
+ const l = Math.max(0, Math.min(1, lambda));
78
+ if (l === 1)
79
+ return [...items].sort((a, b) => b.score - a.score);
80
+ const tokens = new Map();
81
+ for (const item of items)
82
+ tokens.set(item.id, new Set(tokenize(item.content)));
83
+ const tokensOf = (item) => tokens.get(item.id) ?? new Set(tokenize(item.content));
84
+ const scores = items.map((i) => i.score);
85
+ const min = Math.min(...scores);
86
+ const range = Math.max(...scores) - min;
87
+ return mmrSelect(items, {
88
+ relevance: (item) => (range === 0 ? 1 : (item.score - min) / range),
89
+ similarity: (a, b) => jaccardSimilarity(tokensOf(a), tokensOf(b)),
90
+ lambda: l,
91
+ tieScore: (item) => item.score,
92
+ });
93
+ }
94
+ //# sourceMappingURL=mmr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mmr.js","sourceRoot":"","sources":["../../src/retrieval/mmr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Weighted-score fusion — the ONE formula for fusing a semantic arm with a lexical
3
+ * arm by SCORE, where {@link ./fusion reciprocalRankFusion} fuses by RANK.
4
+ *
5
+ * Score fusion is the choice when two different engines must rank the same way: an
6
+ * on-prem `sqlite-vec` cosine + FTS5/BM25 pair and a cloud pgvector cosine + ILIKE
7
+ * pair answer the same question, and a weighted sum of scores already on roughly
8
+ * [0,1] makes "recall ranks the same everywhere" a property of one function.
9
+ *
10
+ * Union semantics: a candidate found by only one arm scores 0 on the other rather
11
+ * than being dropped, because "the lexical arm did not match" is evidence about the
12
+ * query's wording, not about the candidate's relevance.
13
+ *
14
+ * Pure and zero-dependency.
15
+ */
16
+ /** Weight on the semantic arm when a caller states none. */
17
+ export declare const DEFAULT_HYBRID_VECTOR_WEIGHT = 0.7;
18
+ /** Weight on the lexical arm when a caller states none. */
19
+ export declare const DEFAULT_HYBRID_TEXT_WEIGHT = 0.3;
20
+ export interface HybridWeights {
21
+ vectorWeight: number;
22
+ textWeight: number;
23
+ }
24
+ /**
25
+ * Renormalise a pair of weights so it sums to 1 — a fused score is only comparable
26
+ * across queries if the weights always sum the same. A degenerate pair (both zero
27
+ * or non-finite) falls back to the defaults.
28
+ *
29
+ * The pair is a RATIO, so only the lower bound is clamped: `(3, 1)` means "three
30
+ * parts semantic to one part lexical" and must come back as 0.75/0.25, not be
31
+ * capped into an even split. Normalisation is what bounds the result.
32
+ */
33
+ export declare function normalizeHybridWeights(vectorWeight?: number, textWeight?: number): HybridWeights;
34
+ /** One candidate's per-arm scores, both already normalised to roughly [0,1]. */
35
+ export interface HybridArmScores {
36
+ vectorScore?: number;
37
+ textScore?: number;
38
+ }
39
+ /** The fused score for one candidate under the given (already normalised) weights. */
40
+ export declare function hybridScore(scores: HybridArmScores, weights: HybridWeights): number;
41
+ /**
42
+ * Map a BM25 rank (lower is better, unbounded — FTS5's `bm25()`) onto a (0,1]
43
+ * score so it can be summed with a cosine similarity. A non-finite rank is treated
44
+ * as very poor rather than as an error.
45
+ */
46
+ export declare function bm25RankToScore(rank: number): number;
47
+ /**
48
+ * Fuse two arms keyed by a caller-supplied id and return the candidates ordered
49
+ * best-first. `merge` builds the output row from the winning payload of each id;
50
+ * later duplicates of the same id keep the first payload seen (arm order decides).
51
+ */
52
+ export declare function fuseHybridArms<TVector extends {
53
+ id: string;
54
+ }, TText extends {
55
+ id: string;
56
+ }, TOut>(params: {
57
+ vector: readonly TVector[];
58
+ text: readonly TText[];
59
+ vectorScoreOf: (row: TVector) => number;
60
+ textScoreOf: (row: TText) => number;
61
+ merge: (row: TVector | TText, score: number) => TOut;
62
+ weights?: Partial<HybridWeights>;
63
+ }): TOut[];
64
+ //# sourceMappingURL=weightedFusion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weightedFusion.d.ts","sourceRoot":"","sources":["../../src/retrieval/weightedFusion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,2DAA2D;AAC3D,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,CAUhG;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAInF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IACvG,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,IAAI,EAAE,SAAS,KAAK,EAAE,CAAC;IACvB,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAC;IACpC,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACpC,GAAG,IAAI,EAAE,CAkBT"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Weighted-score fusion — the ONE formula for fusing a semantic arm with a lexical
3
+ * arm by SCORE, where {@link ./fusion reciprocalRankFusion} fuses by RANK.
4
+ *
5
+ * Score fusion is the choice when two different engines must rank the same way: an
6
+ * on-prem `sqlite-vec` cosine + FTS5/BM25 pair and a cloud pgvector cosine + ILIKE
7
+ * pair answer the same question, and a weighted sum of scores already on roughly
8
+ * [0,1] makes "recall ranks the same everywhere" a property of one function.
9
+ *
10
+ * Union semantics: a candidate found by only one arm scores 0 on the other rather
11
+ * than being dropped, because "the lexical arm did not match" is evidence about the
12
+ * query's wording, not about the candidate's relevance.
13
+ *
14
+ * Pure and zero-dependency.
15
+ */
16
+ /** Weight on the semantic arm when a caller states none. */
17
+ export const DEFAULT_HYBRID_VECTOR_WEIGHT = 0.7;
18
+ /** Weight on the lexical arm when a caller states none. */
19
+ export const DEFAULT_HYBRID_TEXT_WEIGHT = 0.3;
20
+ /**
21
+ * Renormalise a pair of weights so it sums to 1 — a fused score is only comparable
22
+ * across queries if the weights always sum the same. A degenerate pair (both zero
23
+ * or non-finite) falls back to the defaults.
24
+ *
25
+ * The pair is a RATIO, so only the lower bound is clamped: `(3, 1)` means "three
26
+ * parts semantic to one part lexical" and must come back as 0.75/0.25, not be
27
+ * capped into an even split. Normalisation is what bounds the result.
28
+ */
29
+ export function normalizeHybridWeights(vectorWeight, textWeight) {
30
+ const clamp = (n, fallback) => typeof n === 'number' && Number.isFinite(n) ? Math.max(0, n) : fallback;
31
+ const v = clamp(vectorWeight, DEFAULT_HYBRID_VECTOR_WEIGHT);
32
+ const t = clamp(textWeight, DEFAULT_HYBRID_TEXT_WEIGHT);
33
+ const sum = v + t;
34
+ if (sum <= 0) {
35
+ return { vectorWeight: DEFAULT_HYBRID_VECTOR_WEIGHT, textWeight: DEFAULT_HYBRID_TEXT_WEIGHT };
36
+ }
37
+ return { vectorWeight: v / sum, textWeight: t / sum };
38
+ }
39
+ /** The fused score for one candidate under the given (already normalised) weights. */
40
+ export function hybridScore(scores, weights) {
41
+ const v = Number.isFinite(scores.vectorScore) ? scores.vectorScore : 0;
42
+ const t = Number.isFinite(scores.textScore) ? scores.textScore : 0;
43
+ return weights.vectorWeight * v + weights.textWeight * t;
44
+ }
45
+ /**
46
+ * Map a BM25 rank (lower is better, unbounded — FTS5's `bm25()`) onto a (0,1]
47
+ * score so it can be summed with a cosine similarity. A non-finite rank is treated
48
+ * as very poor rather than as an error.
49
+ */
50
+ export function bm25RankToScore(rank) {
51
+ const normalized = Number.isFinite(rank) ? Math.max(0, rank) : 999;
52
+ return 1 / (1 + normalized);
53
+ }
54
+ /**
55
+ * Fuse two arms keyed by a caller-supplied id and return the candidates ordered
56
+ * best-first. `merge` builds the output row from the winning payload of each id;
57
+ * later duplicates of the same id keep the first payload seen (arm order decides).
58
+ */
59
+ export function fuseHybridArms(params) {
60
+ const weights = normalizeHybridWeights(params.weights?.vectorWeight, params.weights?.textWeight);
61
+ const byId = new Map();
62
+ for (const row of params.vector) {
63
+ byId.set(row.id, { row, scores: { vectorScore: params.vectorScoreOf(row), textScore: 0 } });
64
+ }
65
+ for (const row of params.text) {
66
+ const existing = byId.get(row.id);
67
+ if (existing)
68
+ existing.scores.textScore = params.textScoreOf(row);
69
+ else
70
+ byId.set(row.id, { row, scores: { vectorScore: 0, textScore: params.textScoreOf(row) } });
71
+ }
72
+ return [...byId.values()]
73
+ .map((entry) => {
74
+ const score = hybridScore(entry.scores, weights);
75
+ return { out: params.merge(entry.row, score), score };
76
+ })
77
+ .sort((a, b) => b.score - a.score)
78
+ .map((entry) => entry.out);
79
+ }
80
+ //# sourceMappingURL=weightedFusion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weightedFusion.js","sourceRoot":"","sources":["../../src/retrieval/weightedFusion.ts"],"names":[],"mappings":"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"}
@@ -10,10 +10,12 @@ export declare function tokenize(text: string): string[];
10
10
  /** Jaccard similarity between two token sets: |A ∩ B| / |A ∪ B|. */
11
11
  export declare function jaccardSimilarity(a: Set<string>, b: Set<string>): number;
12
12
  /**
13
- * Cosine similarity between two vectors in the range [-1, 1] (0 when either is a
14
- * zero vector). Compares over the shorter length when lengths differ. Vectors
13
+ * Cosine similarity between two vectors in the range [-1, 1] (0 when either is empty
14
+ * or a zero vector). Compares over the shorter length when lengths differ. Vectors
15
15
  * from MambaSession.embed() are already L2-normalised — this reduces to a dot
16
- * product — but we normalise defensively for vectors from other sources.
16
+ * product — but we normalise defensively for vectors from other sources. Takes any
17
+ * numeric array, so a `Float32Array` embedding and a JSON-decoded `number[]` (a
18
+ * pgvector row, a KV entry) go through the same function.
17
19
  */
18
- export declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
20
+ export declare function cosineSimilarity(a: ArrayLike<number>, b: ArrayLike<number>): number;
19
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/similarity/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CASxE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM,CAUzE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/similarity/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CASxE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAUnF"}
@@ -23,10 +23,12 @@ export function jaccardSimilarity(a, b) {
23
23
  return intersection / union;
24
24
  }
25
25
  /**
26
- * Cosine similarity between two vectors in the range [-1, 1] (0 when either is a
27
- * zero vector). Compares over the shorter length when lengths differ. Vectors
26
+ * Cosine similarity between two vectors in the range [-1, 1] (0 when either is empty
27
+ * or a zero vector). Compares over the shorter length when lengths differ. Vectors
28
28
  * from MambaSession.embed() are already L2-normalised — this reduces to a dot
29
- * product — but we normalise defensively for vectors from other sources.
29
+ * product — but we normalise defensively for vectors from other sources. Takes any
30
+ * numeric array, so a `Float32Array` embedding and a JSON-decoded `number[]` (a
31
+ * pgvector row, a KV entry) go through the same function.
30
32
  */
31
33
  export function cosineSimilarity(a, b) {
32
34
  const n = Math.min(a.length, b.length);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/similarity/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC/D,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,CAAc,EAAE,CAAc;IAC5D,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,YAAY,EAAE,CAAC;IACrC,CAAC;IACD,8DAA8D;IAC9D,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,YAAY,CAAC;IAC7C,OAAO,YAAY,GAAG,KAAK,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAe,EAAE,CAAe;IAC7D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,GAAG,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACrB,EAAE,IAAK,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACrB,EAAE,IAAK,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACzB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/similarity/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC/D,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,CAAc,EAAE,CAAc;IAC5D,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,YAAY,EAAE,CAAC;IACrC,CAAC;IACD,8DAA8D;IAC9D,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,YAAY,CAAC;IAC7C,OAAO,YAAY,GAAG,KAAK,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAoB,EAAE,CAAoB;IACvE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,GAAG,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACrB,EAAE,IAAK,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACrB,EAAE,IAAK,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACzB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC;AACzC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * LLM wire primitives — the protocol-level pieces every client of an
3
+ * OpenAI/Anthropic/Gemini-style HTTP API needs, in ONE place:
4
+ *
5
+ * • Server-Sent-Events `data:` framing — one line rule ({@link parseSseDataLine}),
6
+ * applied to a whole buffered body ({@link parseSseDataFrames}) or to a live
7
+ * byte stream ({@link readSseDataFrames});
8
+ * • token-usage field reading across vendor shapes ({@link readUsageFields}) and
9
+ * the Anthropic streaming usage fold ({@link AnthropicStreamUsage}).
10
+ *
11
+ * The package's bridges and the Builderforce gateway both frame and meter with
12
+ * these, so a fix to the line rule (a spaceless `data:{…}` frame, say) lands once.
13
+ * Zero-dependency and engine-free: the `@seanhogg/builderforce-memory/wire` subpath.
14
+ */
15
+ export { parseSseDataLine, parseSseDataFrames, readSseDataFrames, isSseDoneLine } from './sse.js';
16
+ export { finiteNumber, readUsageFields, AnthropicStreamUsage } from './usage.js';
17
+ export type { UsageFields, AnthropicUsageTotals } from './usage.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/wire/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjF,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * LLM wire primitives — the protocol-level pieces every client of an
3
+ * OpenAI/Anthropic/Gemini-style HTTP API needs, in ONE place:
4
+ *
5
+ * • Server-Sent-Events `data:` framing — one line rule ({@link parseSseDataLine}),
6
+ * applied to a whole buffered body ({@link parseSseDataFrames}) or to a live
7
+ * byte stream ({@link readSseDataFrames});
8
+ * • token-usage field reading across vendor shapes ({@link readUsageFields}) and
9
+ * the Anthropic streaming usage fold ({@link AnthropicStreamUsage}).
10
+ *
11
+ * The package's bridges and the Builderforce gateway both frame and meter with
12
+ * these, so a fix to the line rule (a spaceless `data:{…}` frame, say) lands once.
13
+ * Zero-dependency and engine-free: the `@seanhogg/builderforce-memory/wire` subpath.
14
+ */
15
+ export { parseSseDataLine, parseSseDataFrames, readSseDataFrames, isSseDoneLine } from './sse.js';
16
+ export { finiteNumber, readUsageFields, AnthropicStreamUsage } from './usage.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/wire/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Server-Sent-Events `data:` framing.
3
+ *
4
+ * Every consumer of an SSE LLM stream does the same line-level dance: trim the line,
5
+ * keep only `data:` frames, skip empty payloads, `JSON.parse` the rest and skip
6
+ * anything malformed — and stop at the `[DONE]` sentinel, which ends an
7
+ * OpenAI-style stream (anything after it is not part of the response).
8
+ *
9
+ * The line rule is `slice(5).trim()`, which accepts both `data: {…}` and the
10
+ * spaceless `data:{…}` some providers emit — `slice(6)` silently drops the latter.
11
+ *
12
+ * Pure and defensive: a malformed frame is skipped, never thrown.
13
+ */
14
+ /**
15
+ * One SSE line's decoded JSON payload, or `undefined` when the line is not a usable
16
+ * data frame (not a `data:` line, the `[DONE]` sentinel, empty, or malformed JSON).
17
+ */
18
+ export declare function parseSseDataLine(line: string): unknown | undefined;
19
+ /** True for the `data: [DONE]` line that ends an OpenAI-style stream. */
20
+ export declare function isSseDoneLine(line: string): boolean;
21
+ /** Every parsed `data:` frame of a whole, already-buffered SSE body, up to `[DONE]`. */
22
+ export declare function parseSseDataFrames(raw: string): Generator<unknown>;
23
+ /**
24
+ * Every parsed `data:` frame of a live SSE byte stream, as it arrives, up to `[DONE]`.
25
+ * A frame split across chunks is held until its line completes; the reader lock is
26
+ * released however iteration ends.
27
+ */
28
+ export declare function readSseDataFrames(body: ReadableStream<Uint8Array>): AsyncGenerator<unknown>;
29
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/wire/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAUlE;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED,wFAAwF;AACxF,wBAAiB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAMnE;AAED;;;;GAIG;AACH,wBAAuB,iBAAiB,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAuBlG"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Server-Sent-Events `data:` framing.
3
+ *
4
+ * Every consumer of an SSE LLM stream does the same line-level dance: trim the line,
5
+ * keep only `data:` frames, skip empty payloads, `JSON.parse` the rest and skip
6
+ * anything malformed — and stop at the `[DONE]` sentinel, which ends an
7
+ * OpenAI-style stream (anything after it is not part of the response).
8
+ *
9
+ * The line rule is `slice(5).trim()`, which accepts both `data: {…}` and the
10
+ * spaceless `data:{…}` some providers emit — `slice(6)` silently drops the latter.
11
+ *
12
+ * Pure and defensive: a malformed frame is skipped, never thrown.
13
+ */
14
+ /**
15
+ * One SSE line's decoded JSON payload, or `undefined` when the line is not a usable
16
+ * data frame (not a `data:` line, the `[DONE]` sentinel, empty, or malformed JSON).
17
+ */
18
+ export function parseSseDataLine(line) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed.startsWith('data:'))
21
+ return undefined;
22
+ const data = trimmed.slice(5).trim();
23
+ if (!data || data === '[DONE]')
24
+ return undefined;
25
+ try {
26
+ return JSON.parse(data);
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ /** True for the `data: [DONE]` line that ends an OpenAI-style stream. */
33
+ export function isSseDoneLine(line) {
34
+ const trimmed = line.trim();
35
+ return trimmed.startsWith('data:') && trimmed.slice(5).trim() === '[DONE]';
36
+ }
37
+ /** Every parsed `data:` frame of a whole, already-buffered SSE body, up to `[DONE]`. */
38
+ export function* parseSseDataFrames(raw) {
39
+ for (const line of raw.split('\n')) {
40
+ if (isSseDoneLine(line))
41
+ return;
42
+ const parsed = parseSseDataLine(line);
43
+ if (parsed !== undefined)
44
+ yield parsed;
45
+ }
46
+ }
47
+ /**
48
+ * Every parsed `data:` frame of a live SSE byte stream, as it arrives, up to `[DONE]`.
49
+ * A frame split across chunks is held until its line completes; the reader lock is
50
+ * released however iteration ends.
51
+ */
52
+ export async function* readSseDataFrames(body) {
53
+ const reader = body.getReader();
54
+ const decoder = new TextDecoder();
55
+ let buffer = '';
56
+ try {
57
+ while (true) {
58
+ const { done, value } = await reader.read();
59
+ if (done)
60
+ break;
61
+ buffer += decoder.decode(value, { stream: true });
62
+ const lines = buffer.split('\n');
63
+ buffer = lines.pop(); // split() always yields ≥1 element
64
+ for (const line of lines) {
65
+ if (isSseDoneLine(line))
66
+ return;
67
+ const parsed = parseSseDataLine(line);
68
+ if (parsed !== undefined)
69
+ yield parsed;
70
+ }
71
+ }
72
+ buffer += decoder.decode();
73
+ const tail = parseSseDataLine(buffer);
74
+ if (tail !== undefined)
75
+ yield tail;
76
+ }
77
+ finally {
78
+ reader.releaseLock();
79
+ }
80
+ }
81
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/wire/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,CAAC;IACrB,CAAC;AACL,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,OAAO,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,QAAQ,CAAC;AAC/E,CAAC;AAED,wFAAwF;AACxF,MAAM,SAAS,CAAC,CAAC,kBAAkB,CAAC,GAAW;IAC3C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,IAAI,aAAa,CAAC,IAAI,CAAC;YAAE,OAAO;QAChC,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,MAAM,CAAC;IAC3C,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,iBAAiB,CAAC,IAAgC;IACrE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,CAAC;QACD,OAAO,IAAI,EAAE,CAAC;YACV,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,GAAG,KAAK,CAAC,GAAG,EAAY,CAAC,CAAC,mCAAmC;YACnE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACvB,IAAI,aAAa,CAAC,IAAI,CAAC;oBAAE,OAAO;gBAChC,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;gBACtC,IAAI,MAAM,KAAK,SAAS;oBAAE,MAAM,MAAM,CAAC;YAC3C,CAAC;QACL,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,CAAC;IACvC,CAAC;YAAS,CAAC;QACP,MAAM,CAAC,WAAW,EAAE,CAAC;IACzB,CAAC;AACL,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Token-usage reading across vendor shapes.
3
+ *
4
+ * Two wire shapes reach a client: OpenAI-style `prompt_tokens`/`completion_tokens`
5
+ * (cached reads under `prompt_tokens_details.cached_tokens`, INCLUDED in
6
+ * `prompt_tokens`) and Anthropic-style `input_tokens`/`output_tokens` (cache reads
7
+ * and writes as separate top-level fields, EXCLUDED from `input_tokens`).
8
+ * {@link readUsageFields} reads either into one set of optional fields, keeping
9
+ * "absent" distinct from 0; each consumer then maps them to its own accounting.
10
+ *
11
+ * Streaming Anthropic responses split usage across events — input and cache on
12
+ * `message_start.message.usage`, output accumulating on `message_delta.usage` — and
13
+ * every count is cumulative, so {@link AnthropicStreamUsage} keeps the largest seen.
14
+ */
15
+ /** A finite number, or `undefined` for null/undefined/non-numeric input. */
16
+ export declare function finiteNumber(v: unknown): number | undefined;
17
+ /** Usage as the vendor reported it. Every field is absent when the vendor omitted it. */
18
+ export interface UsageFields {
19
+ /** `prompt_tokens`, else `input_tokens`. */
20
+ promptTokens?: number;
21
+ /** `completion_tokens`, else `output_tokens`. */
22
+ completionTokens?: number;
23
+ totalTokens?: number;
24
+ /** `cache_read_input_tokens`, else `prompt_tokens_details.cached_tokens`. */
25
+ cacheReadTokens?: number;
26
+ /** `cache_creation_input_tokens`. */
27
+ cacheCreationTokens?: number;
28
+ }
29
+ /** Reads a vendor `usage` object of either shape. Anything that is not an object reads as empty. */
30
+ export declare function readUsageFields(u: unknown): UsageFields;
31
+ /** The totals of one Anthropic stream. Input excludes both cache counts. */
32
+ export interface AnthropicUsageTotals {
33
+ inputTokens: number;
34
+ outputTokens: number;
35
+ cacheReadTokens: number;
36
+ cacheCreationTokens: number;
37
+ /** `message_start.message.model`, when the stream named it. */
38
+ model?: string;
39
+ }
40
+ /** Folds the usage events of one Anthropic Messages stream. Feed it every parsed frame. */
41
+ export declare class AnthropicStreamUsage {
42
+ private readonly totals;
43
+ observe(frame: unknown): void;
44
+ result(): AnthropicUsageTotals;
45
+ }
46
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/wire/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI3D;AAED,yFAAyF;AACzF,MAAM,WAAW,WAAW;IACxB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qCAAqC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,WAAW,CAoBvD;AAED,4EAA4E;AAC5E,MAAM,WAAW,oBAAoB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,2FAA2F;AAC3F,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyG;IAEhI,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAmB7B,MAAM,IAAI,oBAAoB;CAGjC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Token-usage reading across vendor shapes.
3
+ *
4
+ * Two wire shapes reach a client: OpenAI-style `prompt_tokens`/`completion_tokens`
5
+ * (cached reads under `prompt_tokens_details.cached_tokens`, INCLUDED in
6
+ * `prompt_tokens`) and Anthropic-style `input_tokens`/`output_tokens` (cache reads
7
+ * and writes as separate top-level fields, EXCLUDED from `input_tokens`).
8
+ * {@link readUsageFields} reads either into one set of optional fields, keeping
9
+ * "absent" distinct from 0; each consumer then maps them to its own accounting.
10
+ *
11
+ * Streaming Anthropic responses split usage across events — input and cache on
12
+ * `message_start.message.usage`, output accumulating on `message_delta.usage` — and
13
+ * every count is cumulative, so {@link AnthropicStreamUsage} keeps the largest seen.
14
+ */
15
+ /** A finite number, or `undefined` for null/undefined/non-numeric input. */
16
+ export function finiteNumber(v) {
17
+ if (v === null || v === undefined)
18
+ return undefined;
19
+ const n = Number(v);
20
+ return Number.isFinite(n) ? n : undefined;
21
+ }
22
+ /** Reads a vendor `usage` object of either shape. Anything that is not an object reads as empty. */
23
+ export function readUsageFields(u) {
24
+ const out = {};
25
+ if (!u || typeof u !== 'object')
26
+ return out;
27
+ const usage = u;
28
+ const prompt = finiteNumber(usage['prompt_tokens'] ?? usage['input_tokens']);
29
+ const completion = finiteNumber(usage['completion_tokens'] ?? usage['output_tokens']);
30
+ const total = finiteNumber(usage['total_tokens']);
31
+ if (prompt !== undefined)
32
+ out.promptTokens = prompt;
33
+ if (completion !== undefined)
34
+ out.completionTokens = completion;
35
+ if (total !== undefined)
36
+ out.totalTokens = total;
37
+ const details = usage['prompt_tokens_details'];
38
+ const cachedFromDetails = details && typeof details === 'object'
39
+ ? finiteNumber(details['cached_tokens'])
40
+ : undefined;
41
+ const cacheRead = finiteNumber(usage['cache_read_input_tokens']) ?? cachedFromDetails;
42
+ const cacheCreation = finiteNumber(usage['cache_creation_input_tokens']);
43
+ if (cacheRead !== undefined)
44
+ out.cacheReadTokens = cacheRead;
45
+ if (cacheCreation !== undefined)
46
+ out.cacheCreationTokens = cacheCreation;
47
+ return out;
48
+ }
49
+ /** Folds the usage events of one Anthropic Messages stream. Feed it every parsed frame. */
50
+ export class AnthropicStreamUsage {
51
+ totals = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };
52
+ observe(frame) {
53
+ if (!frame || typeof frame !== 'object')
54
+ return;
55
+ const ev = frame;
56
+ let usage;
57
+ if (ev.type === 'message_start') {
58
+ usage = ev.message?.usage;
59
+ if (typeof ev.message?.model === 'string')
60
+ this.totals.model = ev.message.model;
61
+ }
62
+ else if (ev.type === 'message_delta') {
63
+ usage = ev.usage;
64
+ }
65
+ if (!usage || typeof usage !== 'object')
66
+ return;
67
+ const u = usage;
68
+ const keep = (current, v) => Math.max(current, finiteNumber(v) ?? 0);
69
+ this.totals.inputTokens = keep(this.totals.inputTokens, u['input_tokens']);
70
+ this.totals.outputTokens = keep(this.totals.outputTokens, u['output_tokens']);
71
+ this.totals.cacheReadTokens = keep(this.totals.cacheReadTokens, u['cache_read_input_tokens']);
72
+ this.totals.cacheCreationTokens = keep(this.totals.cacheCreationTokens, u['cache_creation_input_tokens']);
73
+ }
74
+ result() {
75
+ return { ...this.totals };
76
+ }
77
+ }
78
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/wire/usage.ts"],"names":[],"mappings":"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"}