@seanhogg/builderforce-memory 2026.7.12 → 2026.9.27
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/README.md +10 -0
- package/dist/bridges/AnthropicBridge.d.ts +9 -1
- package/dist/bridges/AnthropicBridge.d.ts.map +1 -1
- package/dist/bridges/AnthropicBridge.js +58 -2
- package/dist/bridges/AnthropicBridge.js.map +1 -1
- package/dist/bridges/CachingBridge.d.ts +9 -1
- package/dist/bridges/CachingBridge.d.ts.map +1 -1
- package/dist/bridges/CachingBridge.js +24 -1
- package/dist/bridges/CachingBridge.js.map +1 -1
- package/dist/bridges/OpenAIBridge.d.ts +4 -1
- package/dist/bridges/OpenAIBridge.d.ts.map +1 -1
- package/dist/bridges/OpenAIBridge.js +40 -2
- package/dist/bridges/OpenAIBridge.js.map +1 -1
- package/dist/bridges/SemanticCachingBridge.d.ts +8 -1
- package/dist/bridges/SemanticCachingBridge.d.ts.map +1 -1
- package/dist/bridges/SemanticCachingBridge.js +26 -1
- package/dist/bridges/SemanticCachingBridge.js.map +1 -1
- package/dist/bridges/TransformerBridge.d.ts +13 -0
- package/dist/bridges/TransformerBridge.d.ts.map +1 -1
- package/dist/bridges/VertexAIBridge.d.ts +102 -0
- package/dist/bridges/VertexAIBridge.d.ts.map +1 -0
- package/dist/bridges/VertexAIBridge.js +224 -0
- package/dist/bridges/VertexAIBridge.js.map +1 -0
- package/dist/bridges/index.d.ts +3 -1
- package/dist/bridges/index.d.ts.map +1 -1
- package/dist/bridges/index.js +1 -0
- package/dist/bridges/index.js.map +1 -1
- package/dist/eval/EvalHarness.d.ts +37 -0
- package/dist/eval/EvalHarness.d.ts.map +1 -0
- package/dist/eval/EvalHarness.js +178 -0
- package/dist/eval/EvalHarness.js.map +1 -0
- package/dist/eval/graders.d.ts +76 -0
- package/dist/eval/graders.d.ts.map +1 -0
- package/dist/eval/graders.js +168 -0
- package/dist/eval/graders.js.map +1 -0
- package/dist/eval/index.d.ts +26 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +26 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/types.d.ts +97 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +5 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/evermind/contract.d.ts +40 -0
- package/dist/evermind/contract.d.ts.map +1 -0
- package/dist/evermind/contract.js +13 -0
- package/dist/evermind/contract.js.map +1 -0
- package/dist/evermind/index.d.ts +15 -0
- package/dist/evermind/index.d.ts.map +1 -0
- package/dist/evermind/index.js +4 -0
- package/dist/evermind/index.js.map +1 -0
- package/dist/evermind/memoryBlock.d.ts +7 -0
- package/dist/evermind/memoryBlock.d.ts.map +1 -0
- package/dist/evermind/memoryBlock.js +37 -0
- package/dist/evermind/memoryBlock.js.map +1 -0
- package/dist/evermind/recall.d.ts +55 -0
- package/dist/evermind/recall.d.ts.map +1 -0
- package/dist/evermind/recall.js +105 -0
- package/dist/evermind/recall.js.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/ingest/IngestionPipeline.d.ts +95 -0
- package/dist/ingest/IngestionPipeline.d.ts.map +1 -0
- package/dist/ingest/IngestionPipeline.js +295 -0
- package/dist/ingest/IngestionPipeline.js.map +1 -0
- package/dist/ingest/index.d.ts +10 -0
- package/dist/ingest/index.d.ts.map +1 -0
- package/dist/ingest/index.js +8 -0
- package/dist/ingest/index.js.map +1 -0
- package/dist/ingest/parsers.d.ts +53 -0
- package/dist/ingest/parsers.d.ts.map +1 -0
- package/dist/ingest/parsers.js +306 -0
- package/dist/ingest/parsers.js.map +1 -0
- package/dist/ingest/types.d.ts +114 -0
- package/dist/ingest/types.d.ts.map +1 -0
- package/dist/ingest/types.js +19 -0
- package/dist/ingest/types.js.map +1 -0
- package/dist/memory/MemoryStore.d.ts +33 -0
- package/dist/memory/MemoryStore.d.ts.map +1 -1
- package/dist/memory/MemoryStore.js +29 -3
- package/dist/memory/MemoryStore.js.map +1 -1
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/orchestration/AgentGraph.d.ts +104 -0
- package/dist/orchestration/AgentGraph.d.ts.map +1 -0
- package/dist/orchestration/AgentGraph.js +364 -0
- package/dist/orchestration/AgentGraph.js.map +1 -0
- package/dist/orchestration/checkpoint.d.ts +58 -0
- package/dist/orchestration/checkpoint.d.ts.map +1 -0
- package/dist/orchestration/checkpoint.js +92 -0
- package/dist/orchestration/checkpoint.js.map +1 -0
- package/dist/orchestration/index.d.ts +14 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +10 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/patterns.d.ts +159 -0
- package/dist/orchestration/patterns.d.ts.map +1 -0
- package/dist/orchestration/patterns.js +371 -0
- package/dist/orchestration/patterns.js.map +1 -0
- package/dist/orchestration/types.d.ts +146 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/orchestration/types.js +20 -0
- package/dist/orchestration/types.js.map +1 -0
- package/dist/rag/EnterpriseRetriever.d.ts +114 -0
- package/dist/rag/EnterpriseRetriever.d.ts.map +1 -0
- package/dist/rag/EnterpriseRetriever.js +240 -0
- package/dist/rag/EnterpriseRetriever.js.map +1 -0
- package/dist/rag/index.d.ts +6 -0
- package/dist/rag/index.d.ts.map +1 -0
- package/dist/rag/index.js +5 -0
- package/dist/rag/index.js.map +1 -0
- package/dist/retrieval/bm25.d.ts +48 -1
- package/dist/retrieval/bm25.d.ts.map +1 -1
- package/dist/retrieval/bm25.js +59 -13
- package/dist/retrieval/bm25.js.map +1 -1
- package/dist/retrieval/index.d.ts +2 -2
- package/dist/retrieval/index.d.ts.map +1 -1
- package/dist/retrieval/index.js +1 -1
- package/dist/retrieval/index.js.map +1 -1
- package/dist/telemetry/InstrumentedBridge.d.ts +62 -0
- package/dist/telemetry/InstrumentedBridge.d.ts.map +1 -0
- package/dist/telemetry/InstrumentedBridge.js +120 -0
- package/dist/telemetry/InstrumentedBridge.js.map +1 -0
- package/dist/telemetry/MetricsRegistry.d.ts +88 -0
- package/dist/telemetry/MetricsRegistry.d.ts.map +1 -0
- package/dist/telemetry/MetricsRegistry.js +167 -0
- package/dist/telemetry/MetricsRegistry.js.map +1 -0
- package/dist/telemetry/Tracer.d.ts +137 -0
- package/dist/telemetry/Tracer.d.ts.map +1 -0
- package/dist/telemetry/Tracer.js +244 -0
- package/dist/telemetry/Tracer.js.map +1 -0
- package/dist/telemetry/exporters.d.ts +68 -0
- package/dist/telemetry/exporters.d.ts.map +1 -0
- package/dist/telemetry/exporters.js +145 -0
- package/dist/telemetry/exporters.js.map +1 -0
- package/dist/telemetry/index.d.ts +19 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +12 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/pricing.d.ts +74 -0
- package/dist/telemetry/pricing.d.ts.map +1 -0
- package/dist/telemetry/pricing.js +98 -0
- package/dist/telemetry/pricing.js.map +1 -0
- package/dist/telemetry/traceparent.d.ts +50 -0
- package/dist/telemetry/traceparent.d.ts.map +1 -0
- package/dist/telemetry/traceparent.js +77 -0
- package/dist/telemetry/traceparent.js.map +1 -0
- package/dist/telemetry/types.d.ts +90 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +17 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/vectorstore/MemoryVectorStore.d.ts +72 -0
- package/dist/vectorstore/MemoryVectorStore.d.ts.map +1 -0
- package/dist/vectorstore/MemoryVectorStore.js +205 -0
- package/dist/vectorstore/MemoryVectorStore.js.map +1 -0
- package/dist/vectorstore/RestVectorStore.d.ts +79 -0
- package/dist/vectorstore/RestVectorStore.d.ts.map +1 -0
- package/dist/vectorstore/RestVectorStore.js +170 -0
- package/dist/vectorstore/RestVectorStore.js.map +1 -0
- package/dist/vectorstore/dialects.d.ts +101 -0
- package/dist/vectorstore/dialects.d.ts.map +1 -0
- package/dist/vectorstore/dialects.js +618 -0
- package/dist/vectorstore/dialects.js.map +1 -0
- package/dist/vectorstore/filter.d.ts +42 -0
- package/dist/vectorstore/filter.d.ts.map +1 -0
- package/dist/vectorstore/filter.js +132 -0
- package/dist/vectorstore/filter.js.map +1 -0
- package/dist/vectorstore/index.d.ts +15 -0
- package/dist/vectorstore/index.d.ts.map +1 -0
- package/dist/vectorstore/index.js +11 -0
- package/dist/vectorstore/index.js.map +1 -0
- package/dist/vectorstore/types.d.ts +151 -0
- package/dist/vectorstore/types.d.ts.map +1 -0
- package/dist/vectorstore/types.js +30 -0
- package/dist/vectorstore/types.js.map +1 -0
- package/package.json +39 -5
- package/src/bridges/AnthropicBridge.ts +68 -3
- package/src/bridges/CachingBridge.ts +28 -2
- package/src/bridges/OpenAIBridge.ts +48 -3
- package/src/bridges/SemanticCachingBridge.ts +29 -2
- package/src/bridges/TransformerBridge.ts +16 -0
- package/src/bridges/VertexAIBridge.ts +297 -0
- package/src/bridges/index.ts +3 -1
- package/src/eval/EvalHarness.ts +214 -0
- package/src/eval/graders.ts +218 -0
- package/src/eval/index.ts +26 -0
- package/src/eval/types.ts +107 -0
- package/src/evermind/contract.ts +39 -0
- package/src/evermind/index.ts +21 -0
- package/src/evermind/memoryBlock.ts +38 -0
- package/src/evermind/recall.ts +122 -0
- package/src/index.ts +232 -0
- package/src/ingest/IngestionPipeline.ts +378 -0
- package/src/ingest/index.ts +46 -0
- package/src/ingest/parsers.ts +318 -0
- package/src/ingest/types.ts +116 -0
- package/src/memory/MemoryStore.ts +55 -3
- package/src/memory/index.ts +1 -1
- package/src/orchestration/AgentGraph.ts +462 -0
- package/src/orchestration/checkpoint.ts +129 -0
- package/src/orchestration/index.ts +63 -0
- package/src/orchestration/patterns.ts +532 -0
- package/src/orchestration/types.ts +128 -0
- package/src/rag/EnterpriseRetriever.ts +348 -0
- package/src/rag/index.ts +18 -0
- package/src/retrieval/bm25.ts +71 -13
- package/src/retrieval/index.ts +2 -2
- package/src/telemetry/InstrumentedBridge.ts +152 -0
- package/src/telemetry/MetricsRegistry.ts +263 -0
- package/src/telemetry/Tracer.ts +310 -0
- package/src/telemetry/exporters.ts +187 -0
- package/src/telemetry/index.ts +56 -0
- package/src/telemetry/pricing.ts +129 -0
- package/src/telemetry/traceparent.ts +89 -0
- package/src/telemetry/types.ts +105 -0
- package/src/vectorstore/MemoryVectorStore.ts +250 -0
- package/src/vectorstore/RestVectorStore.ts +236 -0
- package/src/vectorstore/dialects.ts +702 -0
- package/src/vectorstore/filter.ts +148 -0
- package/src/vectorstore/index.ts +53 -0
- package/src/vectorstore/types.ts +149 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telemetry/pricing.ts — cost-per-request, computed from real token counts.
|
|
3
|
+
*
|
|
4
|
+
* "Cost per request" is an LLM-native metric that cannot be derived from latency
|
|
5
|
+
* or call count: it depends on the model, the input/output split, AND how much of
|
|
6
|
+
* the input was a cache read (cheap) versus a cache write (dear). This module is
|
|
7
|
+
* the ONE place that arithmetic lives, so the bridges, the metrics registry, and
|
|
8
|
+
* the eval harness cannot disagree about what a run cost.
|
|
9
|
+
*
|
|
10
|
+
* Rates are DATA, not code: an enterprise on a negotiated rate card, a partner
|
|
11
|
+
* platform (Bedrock/Vertex bill separately from the first-party API), or a model
|
|
12
|
+
* released after this file was written is handled by passing a {@link PriceBook},
|
|
13
|
+
* never by editing a branch. Unknown models cost `undefined` — reported as an
|
|
14
|
+
* `unpricedCalls` count rather than silently billed at zero, because a silent
|
|
15
|
+
* zero is how cost dashboards lie.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_CACHE_READ_MULTIPLIER = 0.1;
|
|
18
|
+
export const DEFAULT_CACHE_WRITE_MULTIPLIER = 1.25;
|
|
19
|
+
/**
|
|
20
|
+
* First-party Anthropic API rates (USD / 1M tokens), current as of 2026-08-20.
|
|
21
|
+
*
|
|
22
|
+
* Bedrock and Vertex are partner-operated and priced separately — supply those
|
|
23
|
+
* rates via a custom {@link PriceBook} rather than assuming these apply.
|
|
24
|
+
*/
|
|
25
|
+
export const ANTHROPIC_PRICE_BOOK = {
|
|
26
|
+
'claude-fable-5': { inputPerMTok: 10, outputPerMTok: 50 },
|
|
27
|
+
'claude-mythos-5': { inputPerMTok: 10, outputPerMTok: 50 },
|
|
28
|
+
'claude-opus-5': { inputPerMTok: 5, outputPerMTok: 25 },
|
|
29
|
+
'claude-opus-4-8': { inputPerMTok: 5, outputPerMTok: 25 },
|
|
30
|
+
'claude-opus-4-7': { inputPerMTok: 5, outputPerMTok: 25 },
|
|
31
|
+
'claude-opus-4-6': { inputPerMTok: 5, outputPerMTok: 25 },
|
|
32
|
+
'claude-sonnet-5': { inputPerMTok: 3, outputPerMTok: 15 },
|
|
33
|
+
'claude-sonnet-4-6': { inputPerMTok: 3, outputPerMTok: 15 },
|
|
34
|
+
'claude-haiku-4-5': { inputPerMTok: 1, outputPerMTok: 5 },
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Evermind serves `evermind/<ref>` traffic on-device, so it has no per-token
|
|
38
|
+
* vendor bill. It is priced explicitly at zero rather than left unknown — the
|
|
39
|
+
* whole point of routing to Evermind is that the saving is *measurable*.
|
|
40
|
+
*/
|
|
41
|
+
export const EVERMIND_PRICE_BOOK = {
|
|
42
|
+
'evermind': { inputPerMTok: 0, outputPerMTok: 0 },
|
|
43
|
+
'ssm': { inputPerMTok: 0, outputPerMTok: 0 },
|
|
44
|
+
};
|
|
45
|
+
export const DEFAULT_PRICE_BOOK = { ...ANTHROPIC_PRICE_BOOK, ...EVERMIND_PRICE_BOOK };
|
|
46
|
+
/**
|
|
47
|
+
* Resolves a model id to a rate: exact match first, then the longest key the
|
|
48
|
+
* model id starts with. Prefix matching is what makes dated snapshots
|
|
49
|
+
* (`claude-opus-4-5@20251101`) and platform-prefixed ids (`anthropic.claude-opus-5`)
|
|
50
|
+
* resolve without a per-variant entry.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveRate(model, book = DEFAULT_PRICE_BOOK) {
|
|
53
|
+
const exact = book[model];
|
|
54
|
+
if (exact)
|
|
55
|
+
return exact;
|
|
56
|
+
let best;
|
|
57
|
+
let bestLen = 0;
|
|
58
|
+
for (const [key, rate] of Object.entries(book)) {
|
|
59
|
+
if (key.length > bestLen && (model.startsWith(key) || model.includes(`/${key}`))) {
|
|
60
|
+
best = rate;
|
|
61
|
+
bestLen = key.length;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return best;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Cost in USD for one call, or `undefined` when the model is not in the book.
|
|
68
|
+
*
|
|
69
|
+
* A locally-served call (`localCacheHit`) is free by construction — it never
|
|
70
|
+
* reached a provider — so it costs 0 even for an unpriced model.
|
|
71
|
+
*/
|
|
72
|
+
export function estimateCostUsd(usage, book = DEFAULT_PRICE_BOOK) {
|
|
73
|
+
if (usage.localCacheHit)
|
|
74
|
+
return 0;
|
|
75
|
+
const rate = resolveRate(usage.model, book);
|
|
76
|
+
if (!rate)
|
|
77
|
+
return undefined;
|
|
78
|
+
const cacheRead = rate.cacheReadMultiplier ?? DEFAULT_CACHE_READ_MULTIPLIER;
|
|
79
|
+
const cacheWrite = rate.cacheWriteMultiplier ?? DEFAULT_CACHE_WRITE_MULTIPLIER;
|
|
80
|
+
const perToken = (perMTok) => perMTok / 1_000_000;
|
|
81
|
+
return (usage.inputTokens * perToken(rate.inputPerMTok) +
|
|
82
|
+
usage.outputTokens * perToken(rate.outputPerMTok) +
|
|
83
|
+
(usage.cachedInputTokens ?? 0) * perToken(rate.inputPerMTok * cacheRead) +
|
|
84
|
+
(usage.cacheWriteTokens ?? 0) * perToken(rate.inputPerMTok * cacheWrite));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Character-based token estimate — the fallback when a provider reports no usage.
|
|
88
|
+
*
|
|
89
|
+
* ~4 characters per token is the standard English approximation. Estimates are
|
|
90
|
+
* flagged (`estimated: true`) all the way through to the metric snapshot so a
|
|
91
|
+
* cost figure never silently mixes measured and guessed numbers.
|
|
92
|
+
*/
|
|
93
|
+
export function estimateTokens(text) {
|
|
94
|
+
if (!text)
|
|
95
|
+
return 0;
|
|
96
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/telemetry/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwBH,MAAM,CAAC,MAAM,6BAA6B,GAAG,GAAG,CAAC;AACjD,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAc;IAC3C,gBAAgB,EAAK,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;IAC5D,iBAAiB,EAAI,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;IAC5D,eAAe,EAAM,EAAE,YAAY,EAAE,CAAC,EAAG,aAAa,EAAE,EAAE,EAAE;IAC5D,iBAAiB,EAAI,EAAE,YAAY,EAAE,CAAC,EAAG,aAAa,EAAE,EAAE,EAAE;IAC5D,iBAAiB,EAAI,EAAE,YAAY,EAAE,CAAC,EAAG,aAAa,EAAE,EAAE,EAAE;IAC5D,iBAAiB,EAAI,EAAE,YAAY,EAAE,CAAC,EAAG,aAAa,EAAE,EAAE,EAAE;IAC5D,iBAAiB,EAAI,EAAE,YAAY,EAAE,CAAC,EAAG,aAAa,EAAE,EAAE,EAAE;IAC5D,mBAAmB,EAAE,EAAE,YAAY,EAAE,CAAC,EAAG,aAAa,EAAE,EAAE,EAAE;IAC5D,kBAAkB,EAAG,EAAE,YAAY,EAAE,CAAC,EAAG,aAAa,EAAE,CAAC,EAAG;CAC/D,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAc;IAC1C,UAAU,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE;IACjD,KAAK,EAAO,EAAE,YAAY,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE;CACpD,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAc,EAAE,GAAG,oBAAoB,EAAE,GAAG,mBAAmB,EAAE,CAAC;AAEjG;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,OAAkB,kBAAkB;IAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IAExB,IAAI,IAA2B,CAAC;IAChC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,GAAG,CAAC,MAAM,GAAG,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;YAC/E,IAAI,GAAG,IAAI,CAAC;YACZ,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;QACzB,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAe,EAAE,OAAkB,kBAAkB;IACjF,IAAI,KAAK,CAAC,aAAa;QAAE,OAAO,CAAC,CAAC;IAElC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,SAAS,GAAI,IAAI,CAAC,mBAAmB,IAAK,6BAA6B,CAAC;IAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,oBAAoB,IAAI,8BAA8B,CAAC;IAE/E,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,GAAG,SAAS,CAAC;IAE1D,OAAO,CACH,KAAK,CAAC,WAAW,GAAgB,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;QAC5D,KAAK,CAAC,YAAY,GAAe,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC;QAC7D,CAAC,KAAK,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QACxE,CAAC,KAAK,CAAC,gBAAgB,IAAK,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,CAC5E,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACvC,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telemetry/traceparent.ts — W3C Trace Context propagation.
|
|
3
|
+
*
|
|
4
|
+
* A {@link Tracer} on its own produces one span tree per PROCESS. A cloud run is not
|
|
5
|
+
* one process: portal → api → relay → runtime is four, and without propagation each
|
|
6
|
+
* hop opens its own root trace, so the operator gets four unrelated trees and has to
|
|
7
|
+
* correlate them by timestamp. Head-based propagation is what makes them one tree —
|
|
8
|
+
* the caller serialises `{ traceId, spanId, sampled }` into a `traceparent` header,
|
|
9
|
+
* the callee parses it and starts its root span as a CHILD of the remote span.
|
|
10
|
+
*
|
|
11
|
+
* The sampling flag travels with it on purpose: a sampling decision taken per-hop
|
|
12
|
+
* would record hop 1 and drop hop 3, which is a tree with holes in it — the same
|
|
13
|
+
* failure the in-process `_sampled` map exists to prevent, one network hop out.
|
|
14
|
+
*
|
|
15
|
+
* Pure string work, zero dependencies, no OpenTelemetry SDK — the wire format is a
|
|
16
|
+
* 55-character ASCII string and re-implementing it costs less than the dependency.
|
|
17
|
+
*
|
|
18
|
+
* @see https://www.w3.org/TR/trace-context/
|
|
19
|
+
*/
|
|
20
|
+
/** A trace position received from (or sent to) another process. */
|
|
21
|
+
export interface TraceContext {
|
|
22
|
+
/** 32 lowercase hex chars, never all-zero. */
|
|
23
|
+
traceId: string;
|
|
24
|
+
/** 16 lowercase hex chars, never all-zero — the REMOTE span, parent of ours. */
|
|
25
|
+
spanId: string;
|
|
26
|
+
/** The `sampled` flag (bit 0x01). False means the caller decided to drop the trace. */
|
|
27
|
+
sampled: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a `traceparent` header, or `null` when it is absent or unusable.
|
|
31
|
+
*
|
|
32
|
+
* Rejecting rather than repairing is deliberate: a malformed header means the caller
|
|
33
|
+
* is not actually in the trace we would be joining, and inventing a link to it
|
|
34
|
+
* produces a tree that misattributes latency. `null` makes the callee start a fresh
|
|
35
|
+
* root, which is at least true.
|
|
36
|
+
*
|
|
37
|
+
* Unknown future versions are accepted by reading the first four fields — that is
|
|
38
|
+
* what the spec requires, so a later spec revision does not silently break every
|
|
39
|
+
* hop. Version `ff` is invalid by definition and is rejected.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseTraceparent(header: string | null | undefined): TraceContext | null;
|
|
42
|
+
/**
|
|
43
|
+
* Serialise a trace position as a version-`00` `traceparent` header.
|
|
44
|
+
*
|
|
45
|
+
* Ids are normalised to the wire widths — left-padded when short, right-trimmed when
|
|
46
|
+
* long — so a tracer configured with a non-standard id factory still emits a legal
|
|
47
|
+
* header rather than one every downstream collector drops.
|
|
48
|
+
*/
|
|
49
|
+
export declare function formatTraceparent(ctx: TraceContext): string;
|
|
50
|
+
//# sourceMappingURL=traceparent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traceparent.d.ts","sourceRoot":"","sources":["../../src/telemetry/traceparent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,mEAAmE;AACnE,MAAM,WAAW,YAAY;IACzB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,OAAO,EAAE,OAAO,CAAC;CACpB;AAeD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAavF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAK3D"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telemetry/traceparent.ts — W3C Trace Context propagation.
|
|
3
|
+
*
|
|
4
|
+
* A {@link Tracer} on its own produces one span tree per PROCESS. A cloud run is not
|
|
5
|
+
* one process: portal → api → relay → runtime is four, and without propagation each
|
|
6
|
+
* hop opens its own root trace, so the operator gets four unrelated trees and has to
|
|
7
|
+
* correlate them by timestamp. Head-based propagation is what makes them one tree —
|
|
8
|
+
* the caller serialises `{ traceId, spanId, sampled }` into a `traceparent` header,
|
|
9
|
+
* the callee parses it and starts its root span as a CHILD of the remote span.
|
|
10
|
+
*
|
|
11
|
+
* The sampling flag travels with it on purpose: a sampling decision taken per-hop
|
|
12
|
+
* would record hop 1 and drop hop 3, which is a tree with holes in it — the same
|
|
13
|
+
* failure the in-process `_sampled` map exists to prevent, one network hop out.
|
|
14
|
+
*
|
|
15
|
+
* Pure string work, zero dependencies, no OpenTelemetry SDK — the wire format is a
|
|
16
|
+
* 55-character ASCII string and re-implementing it costs less than the dependency.
|
|
17
|
+
*
|
|
18
|
+
* @see https://www.w3.org/TR/trace-context/
|
|
19
|
+
*/
|
|
20
|
+
/** The only trace-flag bit W3C defines today. */
|
|
21
|
+
const FLAG_SAMPLED = 0x01;
|
|
22
|
+
const TRACE_ID_LENGTH = 32;
|
|
23
|
+
const SPAN_ID_LENGTH = 16;
|
|
24
|
+
const HEX = /^[0-9a-f]+$/;
|
|
25
|
+
/** True when `value` is `length` lowercase hex chars and not entirely zeros. */
|
|
26
|
+
function isUsableId(value, length) {
|
|
27
|
+
return value.length === length && HEX.test(value) && /[1-9a-f]/.test(value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a `traceparent` header, or `null` when it is absent or unusable.
|
|
31
|
+
*
|
|
32
|
+
* Rejecting rather than repairing is deliberate: a malformed header means the caller
|
|
33
|
+
* is not actually in the trace we would be joining, and inventing a link to it
|
|
34
|
+
* produces a tree that misattributes latency. `null` makes the callee start a fresh
|
|
35
|
+
* root, which is at least true.
|
|
36
|
+
*
|
|
37
|
+
* Unknown future versions are accepted by reading the first four fields — that is
|
|
38
|
+
* what the spec requires, so a later spec revision does not silently break every
|
|
39
|
+
* hop. Version `ff` is invalid by definition and is rejected.
|
|
40
|
+
*/
|
|
41
|
+
export function parseTraceparent(header) {
|
|
42
|
+
if (typeof header !== 'string')
|
|
43
|
+
return null;
|
|
44
|
+
const parts = header.trim().toLowerCase().split('-');
|
|
45
|
+
if (parts.length < 4)
|
|
46
|
+
return null;
|
|
47
|
+
const [version, traceId, spanId, flags] = parts;
|
|
48
|
+
if (version.length !== 2 || !HEX.test(version) || version === 'ff')
|
|
49
|
+
return null;
|
|
50
|
+
// Version 00 is exactly four fields; a longer header at version 00 is malformed.
|
|
51
|
+
if (version === '00' && parts.length !== 4)
|
|
52
|
+
return null;
|
|
53
|
+
if (!isUsableId(traceId, TRACE_ID_LENGTH) || !isUsableId(spanId, SPAN_ID_LENGTH))
|
|
54
|
+
return null;
|
|
55
|
+
if (flags.length !== 2 || !HEX.test(flags))
|
|
56
|
+
return null;
|
|
57
|
+
return { traceId, spanId, sampled: (parseInt(flags, 16) & FLAG_SAMPLED) !== 0 };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Serialise a trace position as a version-`00` `traceparent` header.
|
|
61
|
+
*
|
|
62
|
+
* Ids are normalised to the wire widths — left-padded when short, right-trimmed when
|
|
63
|
+
* long — so a tracer configured with a non-standard id factory still emits a legal
|
|
64
|
+
* header rather than one every downstream collector drops.
|
|
65
|
+
*/
|
|
66
|
+
export function formatTraceparent(ctx) {
|
|
67
|
+
const traceId = normaliseId(ctx.traceId, TRACE_ID_LENGTH);
|
|
68
|
+
const spanId = normaliseId(ctx.spanId, SPAN_ID_LENGTH);
|
|
69
|
+
const flags = (ctx.sampled ? FLAG_SAMPLED : 0).toString(16).padStart(2, '0');
|
|
70
|
+
return `00-${traceId}-${spanId}-${flags}`;
|
|
71
|
+
}
|
|
72
|
+
/** Pad/trim an id to the wire width, dropping any non-hex byte to `0`. */
|
|
73
|
+
function normaliseId(value, length) {
|
|
74
|
+
const hex = (value ?? '').toLowerCase().replace(/[^0-9a-f]/g, '');
|
|
75
|
+
return hex.length >= length ? hex.slice(0, length) : hex.padStart(length, '0');
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=traceparent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traceparent.js","sourceRoot":"","sources":["../../src/telemetry/traceparent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAYH,iDAAiD;AACjD,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,MAAM,GAAG,GAAG,aAAa,CAAC;AAE1B,gFAAgF;AAChF,SAAS,UAAU,CAAC,KAAa,EAAE,MAAc;IAC7C,OAAO,KAAK,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAiC;IAC9D,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,KAAyC,CAAC;IACpF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChF,iFAAiF;IACjF,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,cAAc,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9F,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAExD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;AACpF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IAC/C,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7E,OAAO,MAAM,OAAO,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED,0EAA0E;AAC1E,SAAS,WAAW,CAAC,KAAa,EAAE,MAAc;IAC9C,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAClE,OAAO,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telemetry/types.ts — the observability vocabulary shared by every enterprise
|
|
3
|
+
* subsystem (ingest, retrieval, orchestration, eval, bridges).
|
|
4
|
+
*
|
|
5
|
+
* Enterprise buyers do not ask "is it fast", they ask "show me the span tree for
|
|
6
|
+
* this request, the token count per model, and what it cost". That needs one
|
|
7
|
+
* canonical record, not per-subsystem logging. A {@link SpanData} is that record:
|
|
8
|
+
* a tracing span that ALSO carries LLM-native usage and cost, so a single
|
|
9
|
+
* exporter pipeline feeds both a trace viewer and the metric aggregates.
|
|
10
|
+
*
|
|
11
|
+
* The shape is deliberately OTLP-compatible (trace/span ids, parent linkage,
|
|
12
|
+
* attributes, events, status) so `toOtlpSpan()` is a pure rename rather than a
|
|
13
|
+
* re-model — but nothing here depends on the OpenTelemetry SDK, which keeps the
|
|
14
|
+
* package zero-dependency and browser-safe.
|
|
15
|
+
*/
|
|
16
|
+
/** Attribute values are kept primitive so a span serializes to JSON losslessly. */
|
|
17
|
+
export type AttributeValue = string | number | boolean;
|
|
18
|
+
export type SpanKind = 'llm' | 'embedding' | 'retrieval' | 'ingest' | 'agent' | 'tool' | 'graph' | 'internal';
|
|
19
|
+
export type SpanStatus = 'unset' | 'ok' | 'error';
|
|
20
|
+
export interface SpanEvent {
|
|
21
|
+
name: string;
|
|
22
|
+
/** Epoch millis. */
|
|
23
|
+
at: number;
|
|
24
|
+
attributes?: Record<string, AttributeValue>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Token accounting for one model call.
|
|
28
|
+
*
|
|
29
|
+
* `cachedInputTokens` and `cacheWriteTokens` are split out because they are
|
|
30
|
+
* billed at different multipliers than plain input (see {@link PriceBook}); a
|
|
31
|
+
* cost model that folds them into `inputTokens` overstates spend by ~10x on a
|
|
32
|
+
* cache-heavy workload, which is exactly the workload enterprises run.
|
|
33
|
+
*/
|
|
34
|
+
export interface LlmUsage {
|
|
35
|
+
model: string;
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
outputTokens: number;
|
|
38
|
+
/** Input tokens served from the provider's prompt cache (billed at a discount). */
|
|
39
|
+
cachedInputTokens?: number;
|
|
40
|
+
/** Input tokens written INTO the provider's prompt cache (billed at a premium). */
|
|
41
|
+
cacheWriteTokens?: number;
|
|
42
|
+
/** True when the whole call was served locally (exact/semantic cache) — cost 0. */
|
|
43
|
+
localCacheHit?: boolean;
|
|
44
|
+
/** True when token counts are inferred rather than reported by the provider. */
|
|
45
|
+
estimated?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** A completed (or in-flight) span, ready to export. */
|
|
48
|
+
export interface SpanData {
|
|
49
|
+
traceId: string;
|
|
50
|
+
spanId: string;
|
|
51
|
+
parentSpanId?: string;
|
|
52
|
+
name: string;
|
|
53
|
+
kind: SpanKind;
|
|
54
|
+
/** Epoch millis. */
|
|
55
|
+
startedAt: number;
|
|
56
|
+
/** Epoch millis; absent while the span is still open. */
|
|
57
|
+
endedAt?: number;
|
|
58
|
+
durationMs?: number;
|
|
59
|
+
attributes: Record<string, AttributeValue>;
|
|
60
|
+
events: SpanEvent[];
|
|
61
|
+
status: SpanStatus;
|
|
62
|
+
error?: string;
|
|
63
|
+
/** Present on `llm` spans. */
|
|
64
|
+
usage?: LlmUsage;
|
|
65
|
+
/** Derived from `usage` + the tracer's price book, in USD. */
|
|
66
|
+
costUsd?: number;
|
|
67
|
+
/** Millis from request start to the first streamed token, when streaming. */
|
|
68
|
+
timeToFirstTokenMs?: number;
|
|
69
|
+
}
|
|
70
|
+
/** Sink for finished spans. Implementations must not throw into the caller. */
|
|
71
|
+
export interface SpanExporter {
|
|
72
|
+
export(spans: readonly SpanData[]): void | Promise<void>;
|
|
73
|
+
/** Optional flush hook for buffered/remote exporters. */
|
|
74
|
+
shutdown?(): void | Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A bridge that reports what its last call actually consumed.
|
|
78
|
+
*
|
|
79
|
+
* Optional on {@link TransformerBridge} so third-party bridges stay valid; when
|
|
80
|
+
* present, {@link InstrumentedBridge} prefers these provider-reported numbers
|
|
81
|
+
* over its own character-based estimate.
|
|
82
|
+
*/
|
|
83
|
+
export interface BridgeCallInfo {
|
|
84
|
+
usage?: LlmUsage;
|
|
85
|
+
/** True when the call never reached the provider (exact or semantic cache hit). */
|
|
86
|
+
cacheHit?: boolean;
|
|
87
|
+
/** Which cache tier served it, when `cacheHit`. */
|
|
88
|
+
cacheTier?: string;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEvD,MAAM,MAAM,QAAQ,GACd,KAAK,GACL,WAAW,GACX,WAAW,GACX,QAAQ,GACR,OAAO,GACP,MAAM,GACN,OAAO,GACP,UAAU,CAAC;AAEjB,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;AAElD,MAAM,WAAW,SAAS;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wDAAwD;AACxD,MAAM,WAAW,QAAQ;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IACzB,MAAM,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,yDAAyD;IACzD,QAAQ,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telemetry/types.ts — the observability vocabulary shared by every enterprise
|
|
3
|
+
* subsystem (ingest, retrieval, orchestration, eval, bridges).
|
|
4
|
+
*
|
|
5
|
+
* Enterprise buyers do not ask "is it fast", they ask "show me the span tree for
|
|
6
|
+
* this request, the token count per model, and what it cost". That needs one
|
|
7
|
+
* canonical record, not per-subsystem logging. A {@link SpanData} is that record:
|
|
8
|
+
* a tracing span that ALSO carries LLM-native usage and cost, so a single
|
|
9
|
+
* exporter pipeline feeds both a trace viewer and the metric aggregates.
|
|
10
|
+
*
|
|
11
|
+
* The shape is deliberately OTLP-compatible (trace/span ids, parent linkage,
|
|
12
|
+
* attributes, events, status) so `toOtlpSpan()` is a pure rename rather than a
|
|
13
|
+
* re-model — but nothing here depends on the OpenTelemetry SDK, which keeps the
|
|
14
|
+
* package zero-dependency and browser-safe.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vectorstore/MemoryVectorStore.ts — the in-process reference adapter.
|
|
3
|
+
*
|
|
4
|
+
* It is the default store, the test double for every layer above it, and the
|
|
5
|
+
* conformance benchmark a remote adapter is checked against. It is also the
|
|
6
|
+
* honest answer for edge deployments (a Worker, a browser tab, an on-prem box)
|
|
7
|
+
* where standing up a vector database is not on the table.
|
|
8
|
+
*
|
|
9
|
+
* Performance notes, because "in-memory" is not an excuse for O(N) per query:
|
|
10
|
+
* • The HNSW graph is built ONCE and extended incrementally on upsert. The
|
|
11
|
+
* shared `denseSearch` helper rebuilds its index per call, which is right for
|
|
12
|
+
* a one-shot rerank over a candidate set and wrong for a store that is queried
|
|
13
|
+
* repeatedly — so this adapter owns a persistent index instead.
|
|
14
|
+
* • Deletes tombstone rather than rebuild; the graph is rebuilt only once
|
|
15
|
+
* tombstones pass a share of the index, amortising the cost.
|
|
16
|
+
* • Filtered queries over-fetch from the graph and post-filter, falling back to
|
|
17
|
+
* an exact scan only when the filter is selective enough that the graph could
|
|
18
|
+
* not fill `topK`. Correct in both regimes, fast in the common one.
|
|
19
|
+
*/
|
|
20
|
+
import { type HnswOptions } from '../retrieval/hnsw.js';
|
|
21
|
+
import type { AccessScope, MetadataFilter, UpsertResult, VectorMatch, VectorQuery, VectorRecord, VectorStore } from './types.js';
|
|
22
|
+
export interface MemoryVectorStoreOptions {
|
|
23
|
+
/** Adapter name for telemetry. Default `'memory'`. */
|
|
24
|
+
name?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Record count at/above which dense search uses the HNSW graph instead of an
|
|
27
|
+
* exact cosine scan. Below it, a scan is both simpler and faster. Default 256.
|
|
28
|
+
*/
|
|
29
|
+
annThreshold?: number;
|
|
30
|
+
/** HNSW construction parameters. */
|
|
31
|
+
hnsw?: HnswOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Over-fetch multiplier for filtered ANN queries — how many graph candidates
|
|
34
|
+
* to pull per requested result before post-filtering. Default 4.
|
|
35
|
+
*/
|
|
36
|
+
filterOverfetch?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Rebuild the graph once tombstoned nodes exceed this share of it. Default 0.2.
|
|
39
|
+
*/
|
|
40
|
+
rebuildTombstoneRatio?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare class MemoryVectorStore implements VectorStore {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
private readonly _records;
|
|
45
|
+
private readonly _annThreshold;
|
|
46
|
+
private readonly _hnswOpts;
|
|
47
|
+
private readonly _overfetch;
|
|
48
|
+
private readonly _rebuildRatio;
|
|
49
|
+
private _index;
|
|
50
|
+
/** Ids present in the graph but deleted from the store. */
|
|
51
|
+
private readonly _tombstones;
|
|
52
|
+
constructor(opts?: MemoryVectorStoreOptions);
|
|
53
|
+
get size(): number;
|
|
54
|
+
upsert(records: readonly VectorRecord[]): Promise<UpsertResult>;
|
|
55
|
+
fetch(ids: readonly string[]): Promise<VectorRecord[]>;
|
|
56
|
+
delete(ids: readonly string[]): Promise<number>;
|
|
57
|
+
deleteByFilter(filter: MetadataFilter, scope?: AccessScope): Promise<number>;
|
|
58
|
+
count(filter?: MetadataFilter, scope?: AccessScope): Promise<number>;
|
|
59
|
+
query(query: VectorQuery): Promise<VectorMatch[]>;
|
|
60
|
+
/**
|
|
61
|
+
* BM25 over the visible corpus. Present because lexical recall catches the
|
|
62
|
+
* exact identifiers (error codes, SKUs, function names) that embeddings blur —
|
|
63
|
+
* the failure mode enterprise RAG evaluations reliably surface.
|
|
64
|
+
*/
|
|
65
|
+
keywordSearch(query: VectorQuery): Promise<VectorMatch[]>;
|
|
66
|
+
private _denseScore;
|
|
67
|
+
private _visible;
|
|
68
|
+
private _toMatch;
|
|
69
|
+
private _ensureIndex;
|
|
70
|
+
private _maybeRebuild;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=MemoryVectorStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MemoryVectorStore.d.ts","sourceRoot":"","sources":["../../src/vectorstore/MemoryVectorStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EACR,WAAW,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,wBAAwB;IACrC,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,qBAAa,iBAAkB,YAAW,WAAW;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,MAAM,CAAwB;IACtC,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;gBAErC,IAAI,GAAE,wBAA6B;IAQ/C,IAAI,IAAI,IAAI,MAAM,CAA+B;IAE3C,MAAM,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAuB/D,KAAK,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAStD,MAAM,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAW/C,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ5E,KAAK,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAUpE,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAoBvD;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAoB/D,OAAO,CAAC,WAAW;IAkCnB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,aAAa;CAOxB"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vectorstore/MemoryVectorStore.ts — the in-process reference adapter.
|
|
3
|
+
*
|
|
4
|
+
* It is the default store, the test double for every layer above it, and the
|
|
5
|
+
* conformance benchmark a remote adapter is checked against. It is also the
|
|
6
|
+
* honest answer for edge deployments (a Worker, a browser tab, an on-prem box)
|
|
7
|
+
* where standing up a vector database is not on the table.
|
|
8
|
+
*
|
|
9
|
+
* Performance notes, because "in-memory" is not an excuse for O(N) per query:
|
|
10
|
+
* • The HNSW graph is built ONCE and extended incrementally on upsert. The
|
|
11
|
+
* shared `denseSearch` helper rebuilds its index per call, which is right for
|
|
12
|
+
* a one-shot rerank over a candidate set and wrong for a store that is queried
|
|
13
|
+
* repeatedly — so this adapter owns a persistent index instead.
|
|
14
|
+
* • Deletes tombstone rather than rebuild; the graph is rebuilt only once
|
|
15
|
+
* tombstones pass a share of the index, amortising the cost.
|
|
16
|
+
* • Filtered queries over-fetch from the graph and post-filter, falling back to
|
|
17
|
+
* an exact scan only when the filter is selective enough that the graph could
|
|
18
|
+
* not fill `topK`. Correct in both regimes, fast in the common one.
|
|
19
|
+
*/
|
|
20
|
+
import { bm25Search } from '../retrieval/bm25.js';
|
|
21
|
+
import { HnswIndex } from '../retrieval/hnsw.js';
|
|
22
|
+
import { cosineSimilarity } from '../similarity/index.js';
|
|
23
|
+
import { combineFilters, matchesFilter } from './filter.js';
|
|
24
|
+
export class MemoryVectorStore {
|
|
25
|
+
name;
|
|
26
|
+
_records = new Map();
|
|
27
|
+
_annThreshold;
|
|
28
|
+
_hnswOpts;
|
|
29
|
+
_overfetch;
|
|
30
|
+
_rebuildRatio;
|
|
31
|
+
_index;
|
|
32
|
+
/** Ids present in the graph but deleted from the store. */
|
|
33
|
+
_tombstones = new Set();
|
|
34
|
+
constructor(opts = {}) {
|
|
35
|
+
this.name = opts.name ?? 'memory';
|
|
36
|
+
this._annThreshold = Math.max(1, opts.annThreshold ?? 256);
|
|
37
|
+
this._hnswOpts = opts.hnsw ?? {};
|
|
38
|
+
this._overfetch = Math.max(1, opts.filterOverfetch ?? 4);
|
|
39
|
+
this._rebuildRatio = opts.rebuildTombstoneRatio ?? 0.2;
|
|
40
|
+
}
|
|
41
|
+
get size() { return this._records.size; }
|
|
42
|
+
async upsert(records) {
|
|
43
|
+
let upserted = 0;
|
|
44
|
+
for (const record of records) {
|
|
45
|
+
const previous = this._records.get(record.id);
|
|
46
|
+
this._records.set(record.id, record);
|
|
47
|
+
upserted += 1;
|
|
48
|
+
if (!record.vector)
|
|
49
|
+
continue;
|
|
50
|
+
// A changed vector under an existing id invalidates its graph node;
|
|
51
|
+
// tombstone the old one so search cannot return the stale neighbour.
|
|
52
|
+
if (previous?.vector && this._index?.has(record.id)) {
|
|
53
|
+
this._tombstones.add(record.id);
|
|
54
|
+
this._maybeRebuild();
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (this._index && !this._index.has(record.id)) {
|
|
58
|
+
this._index.add(record.id, record.vector);
|
|
59
|
+
this._tombstones.delete(record.id);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { upserted, skipped: 0 };
|
|
63
|
+
}
|
|
64
|
+
async fetch(ids) {
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
const record = this._records.get(id);
|
|
68
|
+
if (record)
|
|
69
|
+
out.push(record);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
async delete(ids) {
|
|
74
|
+
let removed = 0;
|
|
75
|
+
for (const id of ids) {
|
|
76
|
+
if (!this._records.delete(id))
|
|
77
|
+
continue;
|
|
78
|
+
removed += 1;
|
|
79
|
+
if (this._index?.has(id))
|
|
80
|
+
this._tombstones.add(id);
|
|
81
|
+
}
|
|
82
|
+
this._maybeRebuild();
|
|
83
|
+
return removed;
|
|
84
|
+
}
|
|
85
|
+
async deleteByFilter(filter, scope) {
|
|
86
|
+
const effective = combineFilters(filter, scope);
|
|
87
|
+
const doomed = [...this._records.values()]
|
|
88
|
+
.filter((r) => matchesFilter(r.metadata, effective))
|
|
89
|
+
.map((r) => r.id);
|
|
90
|
+
return this.delete(doomed);
|
|
91
|
+
}
|
|
92
|
+
async count(filter, scope) {
|
|
93
|
+
const effective = combineFilters(filter, scope);
|
|
94
|
+
if (!effective)
|
|
95
|
+
return this._records.size;
|
|
96
|
+
let n = 0;
|
|
97
|
+
for (const record of this._records.values()) {
|
|
98
|
+
if (matchesFilter(record.metadata, effective))
|
|
99
|
+
n += 1;
|
|
100
|
+
}
|
|
101
|
+
return n;
|
|
102
|
+
}
|
|
103
|
+
async query(query) {
|
|
104
|
+
const topK = Math.max(1, query.topK ?? 10);
|
|
105
|
+
const minScore = query.minScore ?? 0;
|
|
106
|
+
const filter = combineFilters(query.filter, query.scope);
|
|
107
|
+
if (!query.vector) {
|
|
108
|
+
// No embedding: degrade to a filter-only listing rather than returning
|
|
109
|
+
// nothing, so metadata-only lookups do not need a second code path.
|
|
110
|
+
return this._visible(filter)
|
|
111
|
+
.slice(0, topK)
|
|
112
|
+
.map((r) => this._toMatch(r, 1, query.includeVectors));
|
|
113
|
+
}
|
|
114
|
+
const scored = this._denseScore(query.vector, topK, filter);
|
|
115
|
+
return scored
|
|
116
|
+
.filter((hit) => hit.score >= minScore)
|
|
117
|
+
.slice(0, topK)
|
|
118
|
+
.map(({ record, score }) => this._toMatch(record, score, query.includeVectors));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* BM25 over the visible corpus. Present because lexical recall catches the
|
|
122
|
+
* exact identifiers (error codes, SKUs, function names) that embeddings blur —
|
|
123
|
+
* the failure mode enterprise RAG evaluations reliably surface.
|
|
124
|
+
*/
|
|
125
|
+
async keywordSearch(query) {
|
|
126
|
+
const text = query.text?.trim();
|
|
127
|
+
if (!text)
|
|
128
|
+
return [];
|
|
129
|
+
const topK = Math.max(1, query.topK ?? 10);
|
|
130
|
+
const filter = combineFilters(query.filter, query.scope);
|
|
131
|
+
const visible = this._visible(filter);
|
|
132
|
+
if (visible.length === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const hits = bm25Search(text, visible.map((r) => ({ id: r.id, text: r.text })));
|
|
135
|
+
const best = hits[0]?.score ?? 0;
|
|
136
|
+
return hits.slice(0, topK).map((hit) => {
|
|
137
|
+
const record = this._records.get(hit.id);
|
|
138
|
+
// BM25 scores are unbounded; normalising against the top hit puts them
|
|
139
|
+
// on the same [0,1] footing as cosine so fusion weights mean something.
|
|
140
|
+
return this._toMatch(record, best > 0 ? hit.score / best : 0, query.includeVectors);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
_denseScore(vector, topK, filter) {
|
|
144
|
+
const embedded = [...this._records.values()].filter((r) => r.vector);
|
|
145
|
+
const exactScan = () => embedded
|
|
146
|
+
.filter((r) => matchesFilter(r.metadata, filter))
|
|
147
|
+
.map((r) => ({ record: r, score: cosineSimilarity(vector, r.vector) }))
|
|
148
|
+
.sort((a, b) => b.score - a.score);
|
|
149
|
+
if (embedded.length < this._annThreshold)
|
|
150
|
+
return exactScan();
|
|
151
|
+
this._ensureIndex(embedded);
|
|
152
|
+
const index = this._index;
|
|
153
|
+
const wanted = filter ? topK * this._overfetch : topK;
|
|
154
|
+
const hits = index.search(vector, wanted + this._tombstones.size);
|
|
155
|
+
const matched = [];
|
|
156
|
+
for (const hit of hits) {
|
|
157
|
+
if (this._tombstones.has(hit.id))
|
|
158
|
+
continue;
|
|
159
|
+
const record = this._records.get(hit.id);
|
|
160
|
+
if (!record || !matchesFilter(record.metadata, filter))
|
|
161
|
+
continue;
|
|
162
|
+
matched.push({ record, score: hit.score });
|
|
163
|
+
}
|
|
164
|
+
// The graph could not fill the page under this filter — the filter is more
|
|
165
|
+
// selective than the over-fetch anticipated, so pay for an exact scan
|
|
166
|
+
// rather than return a short, silently-truncated result set.
|
|
167
|
+
if (matched.length < topK && filter)
|
|
168
|
+
return exactScan();
|
|
169
|
+
return matched;
|
|
170
|
+
}
|
|
171
|
+
_visible(filter) {
|
|
172
|
+
const all = [...this._records.values()];
|
|
173
|
+
return filter ? all.filter((r) => matchesFilter(r.metadata, filter)) : all;
|
|
174
|
+
}
|
|
175
|
+
_toMatch(record, score, includeVectors) {
|
|
176
|
+
const match = {
|
|
177
|
+
id: record.id,
|
|
178
|
+
text: record.text,
|
|
179
|
+
score,
|
|
180
|
+
metadata: record.metadata,
|
|
181
|
+
};
|
|
182
|
+
if (includeVectors && record.vector)
|
|
183
|
+
match.vector = record.vector;
|
|
184
|
+
return match;
|
|
185
|
+
}
|
|
186
|
+
_ensureIndex(embedded) {
|
|
187
|
+
if (this._index)
|
|
188
|
+
return;
|
|
189
|
+
const index = new HnswIndex(this._hnswOpts);
|
|
190
|
+
for (const record of embedded)
|
|
191
|
+
index.add(record.id, record.vector);
|
|
192
|
+
this._index = index;
|
|
193
|
+
this._tombstones.clear();
|
|
194
|
+
}
|
|
195
|
+
_maybeRebuild() {
|
|
196
|
+
const index = this._index;
|
|
197
|
+
if (!index || index.size === 0)
|
|
198
|
+
return;
|
|
199
|
+
if (this._tombstones.size / index.size < this._rebuildRatio)
|
|
200
|
+
return;
|
|
201
|
+
this._index = undefined;
|
|
202
|
+
this._tombstones.clear();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=MemoryVectorStore.js.map
|