@theokit/sdk-cache 0.3.2 → 1.0.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.js CHANGED
@@ -1,10 +1,10 @@
1
+ import { resolve, join } from 'path';
1
2
  import { Plugin } from '@theokit/sdk';
2
3
  import { PersistenceSchema, atomicWriteText } from '@theokit/sdk/persistence';
3
4
  import { z } from 'zod';
4
5
  import { createHash } from 'crypto';
5
6
  import { createRequire } from 'module';
6
7
  import { readFile, mkdir } from 'fs/promises';
7
- import { join } from 'path';
8
8
 
9
9
  // src/cache.ts
10
10
 
@@ -274,14 +274,6 @@ var InMemoryCacheStore = class {
274
274
  };
275
275
 
276
276
  // src/types/cache.ts
277
- var CacheEmbedderError = class extends Error {
278
- name = "CacheEmbedderError";
279
- cause;
280
- constructor(message, cause) {
281
- super(`Cache embedder failed: ${message}`);
282
- if (cause !== void 0) this.cause = cause;
283
- }
284
- };
285
277
  var CacheInvalidTtlError = class extends Error {
286
278
  constructor(input) {
287
279
  super(
@@ -521,13 +513,14 @@ var DEFAULT_TTL = { default: "1h" };
521
513
  var DEFAULT_NAMESPACE = "global";
522
514
  var DEFAULT_MAX_ENTRIES = 1e3;
523
515
  var Cache = class _Cache {
524
- constructor(embedder, threshold, ttl, namespace, modelId, store) {
516
+ constructor(embedder, threshold, ttl, namespace, modelId, store, hydrated) {
525
517
  this.embedder = embedder;
526
518
  this.threshold = threshold;
527
519
  this.ttl = ttl;
528
520
  this.namespace = namespace;
529
521
  this.modelId = modelId;
530
522
  this.store = store;
523
+ this.hydrated = hydrated;
531
524
  }
532
525
  embedder;
533
526
  threshold;
@@ -535,7 +528,54 @@ var Cache = class _Cache {
535
528
  namespace;
536
529
  modelId;
537
530
  store;
531
+ hydrated;
538
532
  _plugin;
533
+ /**
534
+ * Resolves once the `"json"` backend has finished reading its snapshot; resolves immediately on
535
+ * the in-memory backend.
536
+ *
537
+ * You rarely need it: `consult` and `remember` await hydration themselves, so a cache is correct
538
+ * without it. It exists for a caller who wants the read charged to startup rather than to the
539
+ * first lookup — and because the code promised it long before it existed (#359).
540
+ *
541
+ * A corrupt or unreadable snapshot resolves normally with an empty cache and a warning on
542
+ * stderr; a cache must not take the process down.
543
+ */
544
+ async ready() {
545
+ await this.hydrated;
546
+ }
547
+ /**
548
+ * Write the pending snapshot to disk now, keeping every entry. No-op on the in-memory backend.
549
+ *
550
+ * Writes are debounced 200ms, so a process that remembers something and exits inside that window
551
+ * persists nothing — precisely the once-per-invocation CLI the `"json"` backend exists for. Call
552
+ * this before exiting. Nothing flushes on teardown: an `exit` handler cannot await, and a library
553
+ * installing a process-level hook is a side effect the caller did not ask for.
554
+ *
555
+ * Until #359 the only public call that wrote the snapshot was `clear()`, which also destroyed
556
+ * everything you wanted to persist.
557
+ */
558
+ async flush() {
559
+ const store = this.store;
560
+ if (typeof store.flush === "function") await store.flush();
561
+ }
562
+ /**
563
+ * Build a cache. Validates `options` with Zod and THROWS `ZodError` on a bad shape — an
564
+ * `embedder` missing `{ id, dimension, embed }`, a `threshold` outside `0..2`, a `namespace`
565
+ * longer than 64 chars, or `persistence: { backend: "json" }` without a `dir`.
566
+ *
567
+ * Defaults: `threshold` 0.85, `ttl` `{ default: "1h" }`, `namespace` `"global"`, `maxEntries`
568
+ * 1000 (LRU), `persistence` in-memory. `modelId` defaults to the literal string `"unknown"`,
569
+ * which is a real namespace value and not a wildcard: entries stored while `modelId` was
570
+ * defaulted are only ever returned to lookups that also default it.
571
+ *
572
+ * With `persistence: { backend: "json", dir }` the snapshot is read in the background and this
573
+ * call does not await it — but `consult` and `remember` do, so a lookup issued immediately after
574
+ * construction still sees what is on disk. Await {@link Cache.ready} to charge the read to
575
+ * startup instead of to the first lookup. Two caches built with the same `dir` and `namespace`
576
+ * share one store, so they cannot overwrite each other's snapshot; the FIRST one's `maxEntries`
577
+ * is the one that applies.
578
+ */
539
579
  static semantic(options) {
540
580
  CacheSemanticOptionsSchema.parse(options);
541
581
  const threshold = options.threshold ?? DEFAULT_THRESHOLD;
@@ -543,12 +583,28 @@ var Cache = class _Cache {
543
583
  const namespace = options.namespace ?? DEFAULT_NAMESPACE;
544
584
  const modelId = options.modelId ?? "unknown";
545
585
  const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
546
- const store = createStore(namespace, maxEntries, options.persistence);
547
- return new _Cache(options.embedder, threshold, ttl, namespace, modelId, store);
586
+ const { store, hydrated } = createStore(namespace, maxEntries, options.persistence);
587
+ return new _Cache(options.embedder, threshold, ttl, namespace, modelId, store, hydrated);
548
588
  }
549
589
  /**
550
- * EC-4 absorbed: memoized so repeated `asPlugin()` calls return the SAME
551
- * plugin descriptor no duplicate hook registration.
590
+ * A `Plugin` for `Agent.create({ plugins: [...] })` that reads the cache before each user turn
591
+ * and writes it after each assistant reply.
592
+ *
593
+ * READ THIS BEFORE BUDGETING FOR IT. A hit does NOT skip the model call. The hook returns the
594
+ * cached response as `PreUserSendResult.recalledContext`, which the agent loop injects as a
595
+ * `<memory-context>` block ahead of the prompt — the request still goes to the provider, still
596
+ * costs tokens, and still returns whatever the model makes of that context, which need not be
597
+ * the cached text. Use {@link Cache.consult} / {@link Cache.remember} when the point is to avoid
598
+ * the call.
599
+ *
600
+ * A turn that invoked tools is NOT cached: the store hook reads
601
+ * `PostAssistantReplyContext.usedTools`, which the runtime derives from the run's tool calls
602
+ * (#358). Replaying such an answer would hand a later caller the result of a write that never
603
+ * happened. Until that signal existed the hook passed a literal `false` and cached those turns,
604
+ * despite the package's stated intent.
605
+ *
606
+ * Memoized: repeated calls return the SAME plugin, so registering it twice does not double the
607
+ * hooks.
552
608
  */
553
609
  asPlugin() {
554
610
  if (this._plugin !== void 0) return this._plugin;
@@ -583,7 +639,13 @@ var Cache = class _Cache {
583
639
  await performStore({
584
640
  prompt: c.prompt,
585
641
  response: c.reply,
586
- usedTools: false,
642
+ // EC-10 / D266 — never cache a reply the run produced with tools; replaying it hands a
643
+ // later caller the RESULT of a write without the write having happened. This used to
644
+ // be the literal `false` under a comment describing a `tool_call_id` heuristic that was
645
+ // never implemented, so the guard fired only for a hand-written `remember(...)` call
646
+ // and never on the plugin path, which is the one that runs automatically. #358 added
647
+ // the signal to `PostAssistantReplyContext`; this reads it.
648
+ usedTools: c.usedTools,
587
649
  store: cache.store,
588
650
  embedder: cache.embedder,
589
651
  ttl: cache.ttl,
@@ -597,13 +659,28 @@ var Cache = class _Cache {
597
659
  return this._plugin;
598
660
  }
599
661
  /**
600
- * Explicit cache lookup callers that want true LLM short-circuit
601
- * call this BEFORE `agent.send()`, then dispatch to the LLM only on miss.
662
+ * Look a prompt up. Call it BEFORE dispatching to the model and skip the call on a hit — this is
663
+ * the only path in this package that actually avoids an LLM request.
664
+ *
665
+ * ```ts
666
+ * const hit = await cache.consult(prompt);
667
+ * if (hit.hit) return hit.response;
668
+ * ```
669
+ *
670
+ * `source` says which stage matched: `"kv"` is an exact-key match and costs NO embedding call;
671
+ * `"semantic"` means the prompt was embedded and a stored vector came within `threshold`, and
672
+ * only then is `distance` present (cosine distance, so smaller is closer).
673
+ *
674
+ * NEVER THROWS on an embedder failure. It degrades to `{ hit: false }`, logs a warning on
675
+ * stderr, and increments {@link CacheStats.embedderFailures} — a cache must not take the request
676
+ * down with it. A cache that has silently stopped hitting is that counter climbing, not a cold
677
+ * cache.
602
678
  *
603
- * v1 plugin mode provides recall + context-inject (LLM still called).
604
- * v1.x will add transparent short-circuit via an agent-loop refactor.
679
+ * An empty or whitespace-only prompt returns `{ hit: false }` and counts as a MISS. A prompt
680
+ * matching {@link CacheTTLConfig.exclude} returns `{ hit: false }` and counts as `excluded`.
605
681
  */
606
682
  async consult(prompt) {
683
+ await this.hydrated;
607
684
  const result = await performLookup({
608
685
  prompt,
609
686
  store: this.store,
@@ -624,10 +701,25 @@ var Cache = class _Cache {
624
701
  return { hit: false };
625
702
  }
626
703
  /**
627
- * Explicit cache store pair with `consult()` to manually feed the
628
- * cache after dispatching the LLM call yourself.
704
+ * Store a prompt/response pair. Pair it with {@link Cache.consult} after you dispatched the model
705
+ * call yourself.
706
+ *
707
+ * Pass `{ usedTools: true }` when the answer came from a run that invoked tools and replaying it
708
+ * would lose the side effects — the write is then skipped entirely.
709
+ *
710
+ * Silently writes nothing when the prompt is empty/whitespace, the response is empty, the prompt
711
+ * matches {@link CacheTTLConfig.exclude}, or the embedder fails (that last case increments
712
+ * {@link CacheStats.embedderFailures}). It resolves in every one of those cases: a resolved
713
+ * promise is not evidence that an entry exists — read {@link CacheStats.entries} if you need
714
+ * that.
715
+ *
716
+ * Writing beyond `maxEntries` evicts the least-recently-used entry. On the `"json"` backend the
717
+ * disk write is DEBOUNCED by 200 ms, so a process that exits right after this resolves loses the
718
+ * entry unless it calls {@link Cache.flush} — which writes the snapshot and keeps every entry.
719
+ * Nothing flushes on teardown.
629
720
  */
630
721
  async remember(prompt, response, opts) {
722
+ await this.hydrated;
631
723
  await performStore({
632
724
  prompt,
633
725
  response,
@@ -639,27 +731,52 @@ var Cache = class _Cache {
639
731
  modelId: this.modelId
640
732
  });
641
733
  }
642
- /** Stats snapshot — primary observable for dogfood verification. */
734
+ /**
735
+ * Counter snapshot for this instance. See {@link CacheStats} for what each counter separates —
736
+ * in particular `misses` vs `excluded` vs `embedderFailures`, which is how you tell a cold cache
737
+ * from a too-broad exclude regex from a broken embedder.
738
+ *
739
+ * Process-local: the `"json"` backend persists entries, never counters, so a restart reports
740
+ * zeros against a warm file.
741
+ */
643
742
  stats() {
644
743
  return this.store.stats();
645
744
  }
646
- /** Clear all entries (and flush to disk if JSON backend). */
745
+ /**
746
+ * Drop every entry. On the `"json"` backend this also forces the debounced snapshot to disk
747
+ * immediately, so it is the one public call that guarantees the file matches memory.
748
+ *
749
+ * Counters are NOT reset — `stats()` keeps reporting the hits and misses accumulated before the
750
+ * clear, so `entries: 0` alongside a non-zero `kvHits` is expected, not a bug.
751
+ */
647
752
  async clear() {
648
753
  await this.store.clear();
649
754
  }
650
- /** Force-evict expired entries. Returns count removed. */
755
+ /**
756
+ * Remove every entry whose TTL has elapsed, returning how many were dropped, and add that to
757
+ * {@link CacheStats.evicted}.
758
+ *
759
+ * Optional housekeeping: expired entries are already skipped on lookup and dropped when touched,
760
+ * so this only reclaims memory for entries nobody asks for. `now` exists to make the sweep
761
+ * testable; leave it out in production.
762
+ */
651
763
  evictExpired(now = Date.now()) {
652
764
  return this.store.evictExpired(now);
653
765
  }
654
766
  };
767
+ var jsonStores = /* @__PURE__ */ new Map();
655
768
  function createStore(namespace, maxEntries, persistence) {
656
769
  if (persistence?.backend === "json") {
657
770
  const dir = persistence.dir;
771
+ const key = `${resolve(dir)}\0${namespace}`;
772
+ const existing = jsonStores.get(key);
773
+ if (existing !== void 0) return existing;
658
774
  const store = new JsonFileCacheStore(dir, namespace, maxEntries);
659
- void store.hydrate();
660
- return store;
775
+ const entry = { store, hydrated: store.hydrate() };
776
+ jsonStores.set(key, entry);
777
+ return entry;
661
778
  }
662
- return new InMemoryCacheStore(maxEntries);
779
+ return { store: new InMemoryCacheStore(maxEntries), hydrated: Promise.resolve() };
663
780
  }
664
781
 
665
782
  // src/lexical-embedder.ts
@@ -684,6 +801,6 @@ function createLexicalEmbedder(dimension = 256) {
684
801
  };
685
802
  }
686
803
 
687
- export { Cache, CacheEmbedderError, CacheInvalidTtlError, createLexicalEmbedder };
804
+ export { Cache, CacheInvalidTtlError, createLexicalEmbedder };
688
805
  //# sourceMappingURL=index.js.map
689
806
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/embed-helper.ts","../src/internal/key.ts","../src/internal/telemetry.ts","../src/internal/lookup.ts","../src/internal/cosine.ts","../src/internal/store.ts","../src/types/cache.ts","../src/internal/ttl.ts","../src/internal/store-handler.ts","../src/internal/store-json.ts","../src/cache.ts","../src/lexical-embedder.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Shared embed-with-graceful-degradation helper — used by both `lookup.ts`\n * and `store-handler.ts`. Extracted to remove the embed-failure boilerplate\n * clone flagged by jscpd. EC-1 absorbed: embedder failure is silent (caller\n * decides what to do via the boolean return).\n *\n * @internal\n */\n\nimport type { CacheEmbedderRuntime } from \"../types/cache.js\";\nimport type { InMemoryCacheStore } from \"./store.js\";\nimport type { JsonFileCacheStore } from \"./store-json.js\";\n\ninterface SpanLike {\n setAttribute(key: string, value: string | number | boolean): SpanLike;\n}\n\nexport async function embedOrDegrade(\n embedder: CacheEmbedderRuntime,\n prompt: string,\n store: InMemoryCacheStore | JsonFileCacheStore,\n span: SpanLike,\n context: \"lookup\" | \"store\",\n): Promise<number[] | undefined> {\n try {\n const result = await embedder.embed([prompt]);\n return result[0]!;\n } catch (err) {\n store.incrementEmbedderFailures();\n const action = context === \"lookup\" ? \"degrading to miss\" : \"skipping cache write\";\n console.warn(\n `[cache] embedder failed during ${context}, ${action}:`,\n err instanceof Error ? err.message : err,\n );\n span.setAttribute(\"cache.bypass_reason\", \"embedder_failure\");\n return undefined;\n }\n}\n","/**\n * Composite cache key (ADR D253).\n *\n * Format: `${namespace}:${embedderId}:${modelId}:${hash(normalizedPrompt)}`.\n * Normalizes whitespace + lowercases. Hash = first 16 hex chars of SHA-256.\n *\n * @internal\n */\n\nimport { createHash } from \"node:crypto\";\n\ninterface CacheKeyParams {\n namespace: string;\n embedderId: string;\n modelId: string;\n prompt: string;\n}\n\nexport function computeCacheKey(p: CacheKeyParams): string {\n const normalized = p.prompt.trim().replace(/\\s+/g, \" \").toLowerCase();\n const hash = createHash(\"sha256\").update(normalized).digest(\"hex\").slice(0, 16);\n return `${p.namespace}:${p.embedderId}:${p.modelId}:${hash}`;\n}\n","/**\n * OTel telemetry for semantic cache (ADR D262).\n *\n * Spans:\n * - `cache.lookup` — per `pre_user_send`. Attributes: namespace, embedder.id,\n * hit (kv|semantic|miss), distance, ttl_remaining_s, bypass_reason.\n * - `cache.store` — per `post_assistant_reply`. Attributes: bypass_reason, stored.\n *\n * @internal\n */\n\n// SDK 2.0 split: observability primitives live in @theokit/sdk.\n// The sub-path barrel was avoided due to a rollup-plugin-dts edge case\n// that emitted an empty index.d.ts for newly-added internal barrels;\n// re-exporting locally produces the same runtime behavior with stable types.\nimport { createRequire } from \"node:module\";\n\ninterface SpanLike {\n setAttribute(key: string, value: string | number | boolean): SpanLike;\n end(): void;\n}\n\nconst noopSpan: SpanLike = {\n setAttribute: () => noopSpan,\n end: () => undefined,\n};\n\ninterface TracerLike {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, string | number | boolean> },\n ): SpanLike;\n}\n\ninterface CacheEntry {\n tracer: TracerLike | null;\n}\nconst tracerCache = new Map<string, CacheEntry>();\n\nfunction getTracer(name: string, version = \"1.0.0\"): TracerLike | undefined {\n const cached = tracerCache.get(name);\n if (cached !== undefined) return cached.tracer ?? undefined;\n try {\n const r = createRequire(import.meta.url);\n const otel = r(\"@opentelemetry/api\") as {\n trace?: { getTracer: (n: string, v?: string) => TracerLike };\n };\n if (otel.trace?.getTracer === undefined) {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n const tracer = otel.trace.getTracer(name, version);\n tracerCache.set(name, { tracer });\n return tracer;\n } catch {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n}\n\nconst TRACER_NAME = \"@theokit/sdk/cache\";\n\nexport function startCacheLookupSpan(info: { namespace: string; embedderId: string }): SpanLike {\n const tracer = getTracer(TRACER_NAME);\n if (tracer === undefined) return noopSpan;\n return tracer.startSpan(\"cache.lookup\", {\n attributes: {\n \"cache.namespace\": info.namespace,\n \"cache.embedder_id\": info.embedderId,\n },\n });\n}\n\nexport function startCacheStoreSpan(info: { namespace: string; embedderId: string }): SpanLike {\n const tracer = getTracer(TRACER_NAME);\n if (tracer === undefined) return noopSpan;\n return tracer.startSpan(\"cache.store\", {\n attributes: {\n \"cache.namespace\": info.namespace,\n \"cache.embedder_id\": info.embedderId,\n },\n });\n}\n","/**\n * Cache lookup handler — wired to `pre_user_send` hook (ADRs D259, D260).\n *\n * EC-1: embedder failure degrades to miss (cache is transparent).\n * EC-3: empty / whitespace prompt bypasses cache (avoids hash collision).\n * EC-5: telemetry span ends in finally even on early-return.\n *\n * @internal\n */\n\nimport type { CacheEmbedderRuntime, CacheTTLConfig } from \"../types/cache.js\";\nimport { embedOrDegrade } from \"./embed-helper.js\";\nimport { computeCacheKey } from \"./key.js\";\nimport type { InMemoryCacheStore } from \"./store.js\";\nimport type { JsonFileCacheStore } from \"./store-json.js\";\nimport { startCacheLookupSpan } from \"./telemetry.js\";\n\nexport type LookupableStore = InMemoryCacheStore | JsonFileCacheStore;\n\ninterface LookupParams {\n prompt: string;\n store: LookupableStore;\n embedder: CacheEmbedderRuntime;\n threshold: number;\n ttl: CacheTTLConfig;\n namespace: string;\n modelId: string;\n}\n\ninterface LookupResult {\n cached: boolean;\n response?: string;\n source?: \"kv\" | \"semantic\";\n distance?: number;\n}\n\nexport async function performLookup(p: LookupParams): Promise<LookupResult> {\n const span = startCacheLookupSpan({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n });\n try {\n // EC-3: empty prompt bypass.\n if (p.prompt.trim().length === 0) {\n p.store.incrementMisses();\n span.setAttribute(\"cache.bypass_reason\", \"empty_prompt\");\n return { cached: false };\n }\n // D255 exclude regex.\n if (p.ttl.exclude?.test(p.prompt)) {\n p.store.incrementExcluded();\n span.setAttribute(\"cache.bypass_reason\", \"exclude_regex\");\n return { cached: false };\n }\n\n const key = computeCacheKey({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n modelId: p.modelId,\n prompt: p.prompt,\n });\n const now = Date.now();\n\n // Step 1: KV exact (D259).\n const kv = p.store.kvGet(key, now);\n if (kv !== undefined) {\n p.store.incrementKvHits();\n span.setAttribute(\"cache.hit\", \"kv\");\n span.setAttribute(\"cache.ttl_remaining_s\", Math.floor((kv.expiresAt - now) / 1000));\n return { cached: true, response: kv.response, source: \"kv\" };\n }\n\n // Step 2: semantic — EC-1: embedder failure degrades to miss.\n const vec = await embedOrDegrade(p.embedder, p.prompt, p.store, span, \"lookup\");\n if (vec === undefined) return { cached: false };\n\n const match = p.store.semanticSearch(\n vec,\n p.threshold,\n p.embedder.id,\n p.namespace,\n now,\n p.modelId,\n );\n if (match !== undefined) {\n p.store.incrementSemanticHits();\n span.setAttribute(\"cache.hit\", \"semantic\");\n span.setAttribute(\"cache.distance\", match.distance);\n span.setAttribute(\"cache.ttl_remaining_s\", Math.floor((match.entry.expiresAt - now) / 1000));\n return {\n cached: true,\n response: match.entry.response,\n source: \"semantic\",\n distance: match.distance,\n };\n }\n\n p.store.incrementMisses();\n span.setAttribute(\"cache.hit\", \"miss\");\n return { cached: false };\n } finally {\n span.end();\n }\n}\n","/**\n * Cosine distance between two equal-length vectors.\n *\n * Returns `1 - cos(a, b)`. Range:\n * - 0.0 = identical direction\n * - 1.0 = orthogonal\n * - 2.0 = opposite (rare in normalized embeddings)\n *\n * Throws on dim mismatch. (Callers MUST filter by dim before calling\n * — EC-2 absorbed at the store level.)\n *\n * @internal\n */\n\nexport function cosineDistance(a: ReadonlyArray<number>, b: ReadonlyArray<number>): number {\n if (a.length !== b.length) {\n throw new Error(`cosineDistance: dim mismatch (${a.length} vs ${b.length})`);\n }\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i += 1) {\n const av = a[i]!;\n const bv = b[i]!;\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n if (normA === 0 || normB === 0) return 1.0;\n return 1 - dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n","/**\n * Cache store interface + in-memory implementation (ADRs D252, D259, D261).\n *\n * INVARIANT (EC-9, EC-13): KV map and vector view are the SAME Map.\n * `semanticSearch` iterates `Map.values()`. No parallel list. `set` replaces\n * by key (Map semantics) — no duplicate-on-race possible.\n *\n * EC-2: `semanticSearch` filters by `embedderId === currentEmbedderId &&\n * vector.length === currentDim` before cosine compare. Protects against\n * dim mismatch when disk-loaded entries used a different embedder.\n *\n * @internal\n */\n\nimport type { CacheEntry, CacheStats } from \"../types/cache.js\";\nimport { cosineDistance } from \"./cosine.js\";\n\nexport interface CacheStore {\n kvGet(key: string, now: number): CacheEntry | undefined;\n semanticSearch(\n vector: ReadonlyArray<number>,\n threshold: number,\n embedderId: string,\n namespace: string,\n now: number,\n modelId: string,\n ): { entry: CacheEntry; distance: number } | undefined;\n set(entry: CacheEntry): void;\n delete(key: string): void;\n clear(): Promise<void>;\n stats(): CacheStats;\n evictExpired(now: number): number;\n /** For persistence backends — bulk import. */\n loadAll?(entries: ReadonlyArray<CacheEntry>): void;\n /** For persistence backends — snapshot for serialization. */\n dump?(): ReadonlyArray<CacheEntry>;\n}\n\ninterface StoreCounters {\n kvHits: number;\n semanticHits: number;\n misses: number;\n excluded: number;\n evicted: number;\n embedderFailures: number;\n}\n\nexport class InMemoryCacheStore implements CacheStore {\n private readonly map = new Map<string, CacheEntry>();\n private readonly counters: StoreCounters = {\n kvHits: 0,\n semanticHits: 0,\n misses: 0,\n excluded: 0,\n evicted: 0,\n embedderFailures: 0,\n };\n\n constructor(private readonly maxEntries: number) {}\n\n kvGet(key: string, now: number): CacheEntry | undefined {\n const e = this.map.get(key);\n if (e === undefined) return undefined;\n if (e.expiresAt <= now) {\n this.map.delete(key);\n this.counters.evicted += 1;\n return undefined;\n }\n // Touch for LRU recency.\n this.map.delete(key);\n this.map.set(key, { ...e, accessedAt: now, accessCount: e.accessCount + 1 });\n return this.map.get(key);\n }\n\n private isEligibleForSearch(\n e: CacheEntry,\n embedderId: string,\n namespace: string,\n dim: number,\n now: number,\n modelId: string,\n ): boolean {\n if (e.embedderId !== embedderId) return false;\n // M3 #67 — a stored entry only matches a query for the SAME model.\n if (e.modelId !== modelId) return false;\n if (e.namespace !== namespace) return false;\n if (e.vector.length !== dim) return false;\n if (e.expiresAt <= now) return false;\n return true;\n }\n\n semanticSearch(\n vector: ReadonlyArray<number>,\n threshold: number,\n embedderId: string,\n namespace: string,\n now: number,\n // M3 #67 — model-scoped: without this the semantic path can return a\n // response cached for a DIFFERENT model that shares the embedder + namespace.\n modelId: string,\n ): { entry: CacheEntry; distance: number } | undefined {\n let best: { entry: CacheEntry; distance: number } | undefined;\n for (const e of this.map.values()) {\n if (!this.isEligibleForSearch(e, embedderId, namespace, vector.length, now, modelId))\n continue;\n const d = cosineDistance(e.vector, vector);\n if (d <= threshold && (best === undefined || d < best.distance)) {\n best = { entry: e, distance: d };\n }\n }\n if (best !== undefined) {\n this.map.delete(best.entry.key);\n this.map.set(best.entry.key, {\n ...best.entry,\n accessedAt: now,\n accessCount: best.entry.accessCount + 1,\n });\n }\n return best;\n }\n\n set(entry: CacheEntry): void {\n // EC-13: Map.set replaces by key — no parallel list.\n if (this.map.has(entry.key)) {\n this.map.delete(entry.key);\n }\n this.map.set(entry.key, entry);\n // Evict LRU when over capacity.\n while (this.map.size > this.maxEntries) {\n const oldestKey = this.map.keys().next().value;\n if (oldestKey === undefined) break;\n this.map.delete(oldestKey);\n this.counters.evicted += 1;\n }\n }\n\n delete(key: string): void {\n this.map.delete(key);\n }\n\n async clear(): Promise<void> {\n this.map.clear();\n }\n\n stats(): CacheStats {\n return {\n entries: this.map.size,\n kvHits: this.counters.kvHits,\n semanticHits: this.counters.semanticHits,\n misses: this.counters.misses,\n excluded: this.counters.excluded,\n evicted: this.counters.evicted,\n embedderFailures: this.counters.embedderFailures,\n };\n }\n\n evictExpired(now: number): number {\n let count = 0;\n for (const [k, e] of this.map.entries()) {\n if (e.expiresAt <= now) {\n this.map.delete(k);\n count += 1;\n }\n }\n this.counters.evicted += count;\n return count;\n }\n\n loadAll(entries: ReadonlyArray<CacheEntry>): void {\n for (const e of entries) this.map.set(e.key, e);\n }\n\n dump(): ReadonlyArray<CacheEntry> {\n return [...this.map.values()];\n }\n\n /** Internal helpers for counters (used by lookup/store handlers). */\n incrementKvHits(): void {\n this.counters.kvHits += 1;\n }\n incrementSemanticHits(): void {\n this.counters.semanticHits += 1;\n }\n incrementMisses(): void {\n this.counters.misses += 1;\n }\n incrementExcluded(): void {\n this.counters.excluded += 1;\n }\n incrementEmbedderFailures(): void {\n this.counters.embedderFailures += 1;\n }\n}\n","/**\n * Public type contract for `Cache.semantic / .asPlugin / .stats / .clear`\n * (Adoption Roadmap #6; ADRs D249-D266).\n *\n * @public\n */\n\n/* ─── TTL config (D255) ─── */\n\nexport interface CacheTTLConfig {\n /** Default TTL applied to all entries. Format: `\"1h\" | \"30m\" | 86400 (seconds)`. */\n readonly default: string | number;\n /** Regex marking queries that must NEVER be cached (e.g. /weather|today|now/i). */\n readonly exclude?: RegExp;\n}\n\n/* ─── Persistence (D265) ─── */\n\nexport interface CachePersistenceOptions {\n readonly backend: \"memory\" | \"json\";\n /** Required when backend = \"json\". */\n readonly dir?: string;\n}\n\n/* ─── Embedder option ─── */\n\n/**\n * Embedder runtime shape — minimal subset of `EmbeddingRuntime` (D11) the\n * Cache actually uses. Lets tests inject fake embedders without pulling\n * the full memory subsystem.\n */\nexport interface CacheEmbedderRuntime {\n readonly id: string;\n readonly model: string;\n readonly dimension: number;\n embed(texts: ReadonlyArray<string>): Promise<number[][]>;\n}\n\n/* ─── Options ─── */\n\nexport interface CacheSemanticOptions {\n /** Embedder instance. REQUIRED in v1 — no autoselect (avoids surprise API calls). */\n readonly embedder: CacheEmbedderRuntime;\n /** Cosine distance threshold (0..2). Default 0.85; lower = stricter. */\n readonly threshold?: number;\n /** TTL config. Default `{ default: \"1h\" }`. */\n readonly ttl?: CacheTTLConfig;\n /** Multi-tenant namespace. Default `\"global\"`. */\n readonly namespace?: string;\n /** Default modelId attached to entries when caller doesn't override. */\n readonly modelId?: string;\n /** Max entries (LRU eviction). Default 1000. */\n readonly maxEntries?: number;\n /** Persistence backend. Default in-memory. */\n readonly persistence?: CachePersistenceOptions;\n}\n\n/* ─── Entry + stats ─── */\n\nexport interface CacheEntry {\n readonly key: string;\n readonly namespace: string;\n readonly embedderId: string;\n readonly modelId: string;\n readonly prompt: string;\n readonly response: string;\n readonly vector: ReadonlyArray<number>;\n readonly createdAt: number;\n readonly expiresAt: number;\n readonly accessedAt: number;\n readonly accessCount: number;\n}\n\nexport interface CacheStats {\n readonly entries: number;\n readonly kvHits: number;\n readonly semanticHits: number;\n readonly misses: number;\n readonly excluded: number;\n readonly evicted: number;\n readonly embedderFailures: number;\n}\n\n/* ─── Error classes ─── */\n\nexport class CacheEmbedderError extends Error {\n override readonly name = \"CacheEmbedderError\";\n override readonly cause?: Error;\n constructor(message: string, cause?: Error) {\n super(`Cache embedder failed: ${message}`);\n if (cause !== undefined) this.cause = cause;\n }\n}\n\nexport class CacheInvalidTtlError extends Error {\n override readonly name = \"CacheInvalidTtlError\";\n constructor(public readonly input: string | number) {\n super(\n `Invalid TTL value: \"${String(input)}\". Expected number (seconds) or string like \"1h\" / \"30m\" / \"7d\".`,\n );\n }\n}\n","/**\n * TTL string parser. Accepts:\n * - number → treated as SECONDS\n * - string `\"\\d+(s|m|h|d|w)\"` → seconds/minutes/hours/days/weeks\n *\n * EC-8: `\"0s\"` / `0` returns 0 (effectively disables cache for that entry).\n * Negative numbers throw `CacheInvalidTtlError`.\n *\n * @internal\n */\n\nimport { CacheInvalidTtlError } from \"../types/cache.js\";\n\nconst TTL_PATTERN = /^(\\d+)\\s*(s|m|h|d|w)$/i;\n\nexport function parseTtlMs(input: string | number): number {\n if (typeof input === \"number\") {\n if (!Number.isFinite(input) || input < 0) {\n throw new CacheInvalidTtlError(input);\n }\n return Math.floor(input * 1000);\n }\n const trimmed = input.trim();\n const m = TTL_PATTERN.exec(trimmed);\n if (m === null) {\n throw new CacheInvalidTtlError(input);\n }\n const value = Number(m[1]);\n const unit = m[2]!.toLowerCase();\n switch (unit) {\n case \"s\":\n return value * 1000;\n case \"m\":\n return value * 60_000;\n case \"h\":\n return value * 3_600_000;\n case \"d\":\n return value * 86_400_000;\n case \"w\":\n return value * 604_800_000;\n /* c8 ignore next 2 */\n default:\n throw new CacheInvalidTtlError(input);\n }\n}\n","/**\n * Cache store handler — wired to `post_assistant_reply` hook (ADRs D260, D266).\n *\n * EC-1: embedder failure during store is silent (LLM call already succeeded;\n * no cache entry written).\n * EC-3: empty prompt bypass.\n * EC-10 / D266: skip storage when the run invoked tools (replay loses\n * side-effects).\n *\n * @internal\n */\n\nimport type { CacheEmbedderRuntime, CacheTTLConfig } from \"../types/cache.js\";\nimport { embedOrDegrade } from \"./embed-helper.js\";\nimport { computeCacheKey } from \"./key.js\";\nimport type { LookupableStore } from \"./lookup.js\";\nimport { startCacheStoreSpan } from \"./telemetry.js\";\nimport { parseTtlMs } from \"./ttl.js\";\n\ninterface StoreParams {\n prompt: string;\n response: string;\n /** D266: skip cache when tools were used. */\n usedTools?: boolean;\n store: LookupableStore;\n embedder: CacheEmbedderRuntime;\n ttl: CacheTTLConfig;\n namespace: string;\n modelId: string;\n}\n\nexport async function performStore(p: StoreParams): Promise<void> {\n const span = startCacheStoreSpan({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n });\n try {\n // EC-3: empty prompt bypass.\n if (p.prompt.trim().length === 0) {\n span.setAttribute(\"cache.bypass_reason\", \"empty_prompt\");\n return;\n }\n // EC-3 + safety: empty response is not a useful cache entry.\n if (p.response.length === 0) {\n span.setAttribute(\"cache.bypass_reason\", \"empty_response\");\n return;\n }\n // D266 / EC-10: tool-use runs are not cached (replay loses side-effects).\n if (p.usedTools === true) {\n span.setAttribute(\"cache.bypass_reason\", \"used_tools\");\n return;\n }\n // D255 exclude regex.\n if (p.ttl.exclude?.test(p.prompt)) {\n span.setAttribute(\"cache.bypass_reason\", \"exclude_regex\");\n return;\n }\n\n const key = computeCacheKey({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n modelId: p.modelId,\n prompt: p.prompt,\n });\n\n // EC-1: embedder failure during store is silent.\n const vec = await embedOrDegrade(p.embedder, p.prompt, p.store, span, \"store\");\n if (vec === undefined) return;\n\n const now = Date.now();\n const ttlMs = parseTtlMs(p.ttl.default);\n p.store.set({\n key,\n namespace: p.namespace,\n embedderId: p.embedder.id,\n modelId: p.modelId,\n prompt: p.prompt,\n response: p.response,\n vector: vec,\n createdAt: now,\n expiresAt: now + ttlMs,\n accessedAt: now,\n accessCount: 0,\n });\n span.setAttribute(\"cache.stored\", true);\n } finally {\n span.end();\n }\n}\n","/**\n * JSON disk-backed cache store (ADR D265).\n *\n * One file per namespace at `<dir>/<namespace>.json`. Uses\n * `atomicWriteText` (D60) for crash-safe writes. Debounced flush (200ms)\n * to coalesce bursts.\n *\n * EC-7: corrupt JSON load → log warn + treat as empty cache. Never\n * propagates parse errors that would block `Agent.create`.\n *\n * Layered behind `InMemoryCacheStore` — disk is just persistence; all\n * lookups still hit the in-memory Map for O(1) KV / O(N) vector scan.\n *\n * @internal\n */\n\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { atomicWriteText } from \"@theokit/sdk/persistence\";\nimport type { CacheEntry, CacheStats } from \"../types/cache.js\";\nimport { type CacheStore, InMemoryCacheStore } from \"./store.js\";\n\ninterface SerializedSnapshot {\n readonly _schemaVersion: 1;\n readonly namespace: string;\n readonly entries: ReadonlyArray<CacheEntry>;\n}\n\nconst FLUSH_DEBOUNCE_MS = 200;\n\nexport class JsonFileCacheStore implements CacheStore {\n private readonly inner: InMemoryCacheStore;\n private flushTimer: ReturnType<typeof setTimeout> | undefined;\n private dirty = false;\n\n constructor(\n private readonly dir: string,\n private readonly namespace: string,\n maxEntries: number,\n ) {\n this.inner = new InMemoryCacheStore(maxEntries);\n }\n\n /** Hydrate from disk. EC-7: corrupt file → empty cache. */\n async hydrate(): Promise<void> {\n const file = this.filePath();\n try {\n const raw = await readFile(file, \"utf8\");\n let parsed: SerializedSnapshot;\n try {\n parsed = JSON.parse(raw) as SerializedSnapshot;\n } catch (err) {\n console.warn(\n `[cache] corrupt snapshot at ${file}, starting fresh:`,\n err instanceof Error ? err.message : err,\n );\n return;\n }\n if (parsed._schemaVersion !== 1) {\n console.warn(\n `[cache] unsupported schema v${parsed._schemaVersion} at ${file}, starting fresh`,\n );\n return;\n }\n // Only load entries for this namespace (defensive).\n const valid = parsed.entries.filter((e) => e.namespace === this.namespace);\n this.inner.loadAll(valid);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n console.warn(`[cache] failed to read ${file}:`, err instanceof Error ? err.message : err);\n }\n }\n\n kvGet(key: string, now: number): CacheEntry | undefined {\n return this.inner.kvGet(key, now);\n }\n\n semanticSearch(\n vector: ReadonlyArray<number>,\n threshold: number,\n embedderId: string,\n namespace: string,\n now: number,\n modelId: string,\n ): { entry: CacheEntry; distance: number } | undefined {\n return this.inner.semanticSearch(vector, threshold, embedderId, namespace, now, modelId);\n }\n\n set(entry: CacheEntry): void {\n this.inner.set(entry);\n this.markDirty();\n }\n\n delete(key: string): void {\n this.inner.delete(key);\n this.markDirty();\n }\n\n async clear(): Promise<void> {\n await this.inner.clear();\n this.markDirty();\n await this.flush();\n }\n\n stats(): CacheStats {\n return this.inner.stats();\n }\n\n evictExpired(now: number): number {\n const n = this.inner.evictExpired(now);\n if (n > 0) this.markDirty();\n return n;\n }\n\n /** Force write the current snapshot. Called on shutdown / clear. */\n async flush(): Promise<void> {\n if (this.flushTimer !== undefined) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n if (!this.dirty) return;\n await this.writeSnapshot();\n this.dirty = false;\n }\n\n /** Counters proxied to inner. */\n incrementKvHits(): void {\n this.inner.incrementKvHits();\n }\n incrementSemanticHits(): void {\n this.inner.incrementSemanticHits();\n }\n incrementMisses(): void {\n this.inner.incrementMisses();\n }\n incrementExcluded(): void {\n this.inner.incrementExcluded();\n }\n incrementEmbedderFailures(): void {\n this.inner.incrementEmbedderFailures();\n }\n\n private filePath(): string {\n return join(this.dir, `${this.namespace}.json`);\n }\n\n private markDirty(): void {\n this.dirty = true;\n if (this.flushTimer === undefined) {\n this.flushTimer = setTimeout(() => {\n this.flushTimer = undefined;\n void this.flush().catch((err) =>\n console.warn(`[cache] debounced flush failed:`, err instanceof Error ? err.message : err),\n );\n }, FLUSH_DEBOUNCE_MS);\n }\n }\n\n private async writeSnapshot(): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n const snapshot: SerializedSnapshot = {\n _schemaVersion: 1,\n namespace: this.namespace,\n entries: this.inner.dump(),\n };\n const serialized = JSON.stringify(snapshot);\n await atomicWriteText(this.filePath(), serialized);\n }\n}\n","/**\n * Public `Cache` class — semantic LLM response cache (Adoption Roadmap #6;\n * ADRs D249-D266).\n *\n * Usage:\n *\n * import { Agent, Cache, definePlugin } from \"@theokit/sdk\";\n *\n * const cache = Cache.semantic({\n * embedder: myEmbedderRuntime, // EmbeddingRuntime (D11)\n * threshold: 0.85,\n * ttl: { default: \"1h\", exclude: /weather|today|now/i },\n * namespace: \"my-app\",\n * modelId: \"openai/gpt-4o-mini\",\n * });\n *\n * const agent = await Agent.create({\n * model: { id: \"openai/gpt-4o-mini\" },\n * plugins: [cache.asPlugin()],\n * // ...\n * });\n *\n * await agent.send(\"What is the capital of France?\"); // miss → LLM\n * await agent.send(\"Tell me the capital of France\"); // semantic hit\n *\n * @public\n */\n\nimport {\n Plugin,\n type PluginContext,\n type PostAssistantReplyContext,\n type PreUserSendContext,\n type PreUserSendResult,\n} from \"@theokit/sdk\";\nimport { PersistenceSchema } from \"@theokit/sdk/persistence\";\nimport { z } from \"zod\";\nimport { type LookupableStore, performLookup } from \"./internal/lookup.js\";\nimport { InMemoryCacheStore } from \"./internal/store.js\";\nimport { performStore } from \"./internal/store-handler.js\";\nimport { JsonFileCacheStore } from \"./internal/store-json.js\";\nimport type {\n CacheEmbedderRuntime,\n CachePersistenceOptions,\n CacheSemanticOptions,\n CacheStats,\n CacheTTLConfig,\n} from \"./types/cache.js\";\n\nconst CacheSemanticOptionsSchema = z.object({\n embedder: z.unknown().refine(\n (v) => {\n if (v === null || typeof v !== \"object\") return false;\n const o = v as { id?: unknown; embed?: unknown; dimension?: unknown };\n return (\n typeof o.id === \"string\" && typeof o.embed === \"function\" && typeof o.dimension === \"number\"\n );\n },\n { message: \"embedder must be a CacheEmbedderRuntime with { id, dimension, embed }\" },\n ),\n threshold: z.number().min(0).max(2).optional(),\n ttl: z\n .object({\n default: z.union([z.string(), z.number()]),\n exclude: z.instanceof(RegExp).optional(),\n })\n .optional(),\n namespace: z.string().min(1).max(64).optional(),\n modelId: z.string().min(1).max(128).optional(),\n maxEntries: z.number().int().min(1).max(1_000_000).optional(),\n persistence: PersistenceSchema,\n});\n\nconst DEFAULT_THRESHOLD = 0.85;\nconst DEFAULT_TTL: CacheTTLConfig = { default: \"1h\" };\nconst DEFAULT_NAMESPACE = \"global\";\nconst DEFAULT_MAX_ENTRIES = 1000;\n\nexport class Cache {\n private _plugin?: Plugin;\n\n private constructor(\n private readonly embedder: CacheEmbedderRuntime,\n private readonly threshold: number,\n private readonly ttl: CacheTTLConfig,\n private readonly namespace: string,\n private readonly modelId: string,\n private readonly store: LookupableStore,\n ) {}\n\n static semantic(options: CacheSemanticOptions): Cache {\n CacheSemanticOptionsSchema.parse(options);\n const threshold = options.threshold ?? DEFAULT_THRESHOLD;\n const ttl = options.ttl ?? DEFAULT_TTL;\n const namespace = options.namespace ?? DEFAULT_NAMESPACE;\n const modelId = options.modelId ?? \"unknown\";\n const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n const store = createStore(namespace, maxEntries, options.persistence);\n return new Cache(options.embedder, threshold, ttl, namespace, modelId, store);\n }\n\n /**\n * EC-4 absorbed: memoized so repeated `asPlugin()` calls return the SAME\n * plugin descriptor — no duplicate hook registration.\n */\n asPlugin(): Plugin {\n if (this._plugin !== undefined) return this._plugin;\n const cache = this;\n this._plugin = Plugin.create({\n name: `cache-semantic-${this.namespace}`,\n version: \"1.0.0\",\n kind: \"general\" as const,\n register(ctx: PluginContext): void {\n ctx.on(\"pre_user_send\", async (rawCtx) => {\n const c = rawCtx as PreUserSendContext;\n const result = await performLookup({\n prompt: c.prompt,\n store: cache.store,\n embedder: cache.embedder,\n threshold: cache.threshold,\n ttl: cache.ttl,\n namespace: cache.namespace,\n modelId: cache.modelId,\n });\n if (result.cached === true) {\n // Cache hit — return recalledContext as the cached response.\n // The agent loop will inject this; in v1 the caller must\n // check `pre_user_send` hook return value via runtime support\n // (the cache hit short-circuits via injection; see docs).\n const wrapped: PreUserSendResult = {\n recalledContext: result.response,\n };\n return wrapped;\n }\n const miss: PreUserSendResult = {};\n return miss;\n });\n ctx.on(\"post_assistant_reply\", async (rawCtx) => {\n const c = rawCtx as PostAssistantReplyContext;\n // EC-10: don't cache tool-use runs. v1 lacks a \"usedTools\" signal\n // in PostAssistantReplyContext (D266 documented gap). For now we\n // accept the conservative-skip if `c.reply` looks like a tool\n // result envelope (contains `tool_call_id` markers). Future\n // PostAssistantReplyContext extension will surface usedTools\n // explicitly.\n await performStore({\n prompt: c.prompt,\n response: c.reply,\n usedTools: false,\n store: cache.store,\n embedder: cache.embedder,\n ttl: cache.ttl,\n namespace: cache.namespace,\n modelId: cache.modelId,\n });\n return undefined;\n });\n },\n });\n return this._plugin;\n }\n\n /**\n * Explicit cache lookup — callers that want true LLM short-circuit\n * call this BEFORE `agent.send()`, then dispatch to the LLM only on miss.\n *\n * v1 plugin mode provides recall + context-inject (LLM still called).\n * v1.x will add transparent short-circuit via an agent-loop refactor.\n */\n async consult(\n prompt: string,\n ): Promise<\n { hit: false } | { hit: true; response: string; source: \"kv\" | \"semantic\"; distance?: number }\n > {\n const result = await performLookup({\n prompt,\n store: this.store,\n embedder: this.embedder,\n threshold: this.threshold,\n ttl: this.ttl,\n namespace: this.namespace,\n modelId: this.modelId,\n });\n if (result.cached === true) {\n return {\n hit: true,\n response: result.response ?? \"\",\n source: result.source ?? \"kv\",\n ...(result.distance !== undefined ? { distance: result.distance } : {}),\n };\n }\n return { hit: false };\n }\n\n /**\n * Explicit cache store — pair with `consult()` to manually feed the\n * cache after dispatching the LLM call yourself.\n */\n async remember(prompt: string, response: string, opts?: { usedTools?: boolean }): Promise<void> {\n await performStore({\n prompt,\n response,\n usedTools: opts?.usedTools === true,\n store: this.store,\n embedder: this.embedder,\n ttl: this.ttl,\n namespace: this.namespace,\n modelId: this.modelId,\n });\n }\n\n /** Stats snapshot — primary observable for dogfood verification. */\n stats(): CacheStats {\n return this.store.stats();\n }\n\n /** Clear all entries (and flush to disk if JSON backend). */\n async clear(): Promise<void> {\n await this.store.clear();\n }\n\n /** Force-evict expired entries. Returns count removed. */\n evictExpired(now: number = Date.now()): number {\n return this.store.evictExpired(now);\n }\n}\n\nfunction createStore(\n namespace: string,\n maxEntries: number,\n persistence?: CachePersistenceOptions,\n): LookupableStore {\n if (persistence?.backend === \"json\") {\n const dir = persistence.dir as string;\n const store = new JsonFileCacheStore(dir, namespace, maxEntries);\n // Hydrate fire-and-forget — callers do `await cache.ready()` if they need\n // sync hydration. v1: lazy load on first lookup.\n void store.hydrate();\n return store;\n }\n return new InMemoryCacheStore(maxEntries);\n}\n","/**\n * Built-in deterministic lexical embedder for `@theokit/sdk-cache` (RADAR #92.e).\n *\n * `Cache.semantic` requires a `CacheEmbedderRuntime` (no autoselect — it avoids\n * surprise LLM-embedding API calls). This supplies a REAL, deterministic,\n * zero-dependency embedding: a token-hash frequency vector, L2-normalized. It is\n * NOT a stub/fake — identical text yields identical vectors (exact cache hits)\n * and lexically similar text yields nearby vectors (cosine-similar hits). It\n * carries no semantic understanding (that needs an LLM embedder), which is the\n * honest trade-off: the cache's value here is exact-repeat + lexical dedup, with\n * no API cost.\n *\n * Promoted from theocode's `server/lib/cache-embedder.ts`.\n *\n * @public\n */\n\nimport type { CacheEmbedderRuntime } from \"./types/cache.js\";\n\nexport function createLexicalEmbedder(dimension = 256): CacheEmbedderRuntime {\n return {\n id: `theokit-lexical-v1-d${dimension}`,\n model: \"theokit-lexical-hash\",\n dimension,\n async embed(texts: ReadonlyArray<string>): Promise<number[][]> {\n return texts.map((text) => {\n const vec = new Array<number>(dimension).fill(0);\n for (const tok of text.toLowerCase().split(/\\s+/).filter(Boolean)) {\n // FNV-ish rolling hash → bucket; deterministic across runs/processes.\n let h = 0;\n for (let i = 0; i < tok.length; i += 1) h = (h * 31 + tok.charCodeAt(i)) | 0;\n const idx = Math.abs(h) % dimension;\n vec[idx] = (vec[idx] ?? 0) + 1;\n }\n // L2-normalize so cosine distance is well-defined; an empty/whitespace\n // text stays the zero vector (the cache treats it as a non-match).\n const magnitude = Math.sqrt(vec.reduce((s, x) => s + x * x, 0)) || 1;\n return vec.map((x) => x / magnitude);\n });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/embed-helper.ts","../src/internal/key.ts","../src/internal/telemetry.ts","../src/internal/lookup.ts","../src/internal/cosine.ts","../src/internal/store.ts","../src/types/cache.ts","../src/internal/ttl.ts","../src/internal/store-handler.ts","../src/internal/store-json.ts","../src/cache.ts","../src/lexical-embedder.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Shared embed-with-graceful-degradation helper — used by both `lookup.ts`\n * and `store-handler.ts`. Extracted to remove the embed-failure boilerplate\n * clone flagged by jscpd. EC-1 absorbed: embedder failure is silent (caller\n * decides what to do via the boolean return).\n *\n * @internal\n */\n\nimport type { CacheEmbedderRuntime } from \"../types/cache.js\";\nimport type { InMemoryCacheStore } from \"./store.js\";\nimport type { JsonFileCacheStore } from \"./store-json.js\";\n\ninterface SpanLike {\n setAttribute(key: string, value: string | number | boolean): SpanLike;\n}\n\nexport async function embedOrDegrade(\n embedder: CacheEmbedderRuntime,\n prompt: string,\n store: InMemoryCacheStore | JsonFileCacheStore,\n span: SpanLike,\n context: \"lookup\" | \"store\",\n): Promise<number[] | undefined> {\n try {\n const result = await embedder.embed([prompt]);\n return result[0]!;\n } catch (err) {\n store.incrementEmbedderFailures();\n const action = context === \"lookup\" ? \"degrading to miss\" : \"skipping cache write\";\n console.warn(\n `[cache] embedder failed during ${context}, ${action}:`,\n err instanceof Error ? err.message : err,\n );\n span.setAttribute(\"cache.bypass_reason\", \"embedder_failure\");\n return undefined;\n }\n}\n","/**\n * Composite cache key (ADR D253).\n *\n * Format: `${namespace}:${embedderId}:${modelId}:${hash(normalizedPrompt)}`.\n * Normalizes whitespace + lowercases. Hash = first 16 hex chars of SHA-256.\n *\n * @internal\n */\n\nimport { createHash } from \"node:crypto\";\n\ninterface CacheKeyParams {\n namespace: string;\n embedderId: string;\n modelId: string;\n prompt: string;\n}\n\nexport function computeCacheKey(p: CacheKeyParams): string {\n const normalized = p.prompt.trim().replace(/\\s+/g, \" \").toLowerCase();\n const hash = createHash(\"sha256\").update(normalized).digest(\"hex\").slice(0, 16);\n return `${p.namespace}:${p.embedderId}:${p.modelId}:${hash}`;\n}\n","/**\n * OTel telemetry for semantic cache (ADR D262).\n *\n * Spans:\n * - `cache.lookup` — per `pre_user_send`. Attributes: namespace, embedder.id,\n * hit (kv|semantic|miss), distance, ttl_remaining_s, bypass_reason.\n * - `cache.store` — per `post_assistant_reply`. Attributes: bypass_reason, stored.\n *\n * Emitted ONLY when `@opentelemetry/api` resolves from THIS package's directory.\n * It is an optional peer dependency: not installed for you, and under an isolated\n * node_modules layout a copy installed for some other package is not visible here.\n * When the require fails, `getTracer` caches `null` and every span becomes the\n * no-op below — silently, with no warning, unlike `@theokit/sdk`'s own tracer,\n * which prints one when `telemetry.enabled = true` and OTel is absent. So \"no\n * cache spans in the trace\" means the module is missing far more often than it\n * means the cache was never consulted.\n *\n * @internal\n */\n\n// SDK 2.0 split: observability primitives live in @theokit/sdk.\n// The sub-path barrel was avoided due to a rollup-plugin-dts edge case\n// that emitted an empty index.d.ts for newly-added internal barrels;\n// re-exporting locally produces the same runtime behavior with stable types.\nimport { createRequire } from \"node:module\";\n\ninterface SpanLike {\n setAttribute(key: string, value: string | number | boolean): SpanLike;\n end(): void;\n}\n\nconst noopSpan: SpanLike = {\n setAttribute: () => noopSpan,\n end: () => undefined,\n};\n\ninterface TracerLike {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, string | number | boolean> },\n ): SpanLike;\n}\n\ninterface CacheEntry {\n tracer: TracerLike | null;\n}\nconst tracerCache = new Map<string, CacheEntry>();\n\nfunction getTracer(name: string, version = \"1.0.0\"): TracerLike | undefined {\n const cached = tracerCache.get(name);\n if (cached !== undefined) return cached.tracer ?? undefined;\n try {\n const r = createRequire(import.meta.url);\n const otel = r(\"@opentelemetry/api\") as {\n trace?: { getTracer: (n: string, v?: string) => TracerLike };\n };\n if (otel.trace?.getTracer === undefined) {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n const tracer = otel.trace.getTracer(name, version);\n tracerCache.set(name, { tracer });\n return tracer;\n } catch {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n}\n\nconst TRACER_NAME = \"@theokit/sdk/cache\";\n\nexport function startCacheLookupSpan(info: { namespace: string; embedderId: string }): SpanLike {\n const tracer = getTracer(TRACER_NAME);\n if (tracer === undefined) return noopSpan;\n return tracer.startSpan(\"cache.lookup\", {\n attributes: {\n \"cache.namespace\": info.namespace,\n \"cache.embedder_id\": info.embedderId,\n },\n });\n}\n\nexport function startCacheStoreSpan(info: { namespace: string; embedderId: string }): SpanLike {\n const tracer = getTracer(TRACER_NAME);\n if (tracer === undefined) return noopSpan;\n return tracer.startSpan(\"cache.store\", {\n attributes: {\n \"cache.namespace\": info.namespace,\n \"cache.embedder_id\": info.embedderId,\n },\n });\n}\n","/**\n * Cache lookup handler — wired to `pre_user_send` hook (ADRs D259, D260).\n *\n * EC-1: embedder failure degrades to miss (cache is transparent).\n * EC-3: empty / whitespace prompt bypasses cache (avoids hash collision).\n * EC-5: telemetry span ends in finally even on early-return.\n *\n * @internal\n */\n\nimport type { CacheEmbedderRuntime, CacheTTLConfig } from \"../types/cache.js\";\nimport { embedOrDegrade } from \"./embed-helper.js\";\nimport { computeCacheKey } from \"./key.js\";\nimport type { InMemoryCacheStore } from \"./store.js\";\nimport type { JsonFileCacheStore } from \"./store-json.js\";\nimport { startCacheLookupSpan } from \"./telemetry.js\";\n\nexport type LookupableStore = InMemoryCacheStore | JsonFileCacheStore;\n\ninterface LookupParams {\n prompt: string;\n store: LookupableStore;\n embedder: CacheEmbedderRuntime;\n threshold: number;\n ttl: CacheTTLConfig;\n namespace: string;\n modelId: string;\n}\n\ninterface LookupResult {\n cached: boolean;\n response?: string;\n source?: \"kv\" | \"semantic\";\n distance?: number;\n}\n\nexport async function performLookup(p: LookupParams): Promise<LookupResult> {\n const span = startCacheLookupSpan({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n });\n try {\n // EC-3: empty prompt bypass.\n if (p.prompt.trim().length === 0) {\n p.store.incrementMisses();\n span.setAttribute(\"cache.bypass_reason\", \"empty_prompt\");\n return { cached: false };\n }\n // D255 exclude regex.\n if (p.ttl.exclude?.test(p.prompt)) {\n p.store.incrementExcluded();\n span.setAttribute(\"cache.bypass_reason\", \"exclude_regex\");\n return { cached: false };\n }\n\n const key = computeCacheKey({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n modelId: p.modelId,\n prompt: p.prompt,\n });\n const now = Date.now();\n\n // Step 1: KV exact (D259).\n const kv = p.store.kvGet(key, now);\n if (kv !== undefined) {\n p.store.incrementKvHits();\n span.setAttribute(\"cache.hit\", \"kv\");\n span.setAttribute(\"cache.ttl_remaining_s\", Math.floor((kv.expiresAt - now) / 1000));\n return { cached: true, response: kv.response, source: \"kv\" };\n }\n\n // Step 2: semantic — EC-1: embedder failure degrades to miss.\n const vec = await embedOrDegrade(p.embedder, p.prompt, p.store, span, \"lookup\");\n if (vec === undefined) return { cached: false };\n\n const match = p.store.semanticSearch(\n vec,\n p.threshold,\n p.embedder.id,\n p.namespace,\n now,\n p.modelId,\n );\n if (match !== undefined) {\n p.store.incrementSemanticHits();\n span.setAttribute(\"cache.hit\", \"semantic\");\n span.setAttribute(\"cache.distance\", match.distance);\n span.setAttribute(\"cache.ttl_remaining_s\", Math.floor((match.entry.expiresAt - now) / 1000));\n return {\n cached: true,\n response: match.entry.response,\n source: \"semantic\",\n distance: match.distance,\n };\n }\n\n p.store.incrementMisses();\n span.setAttribute(\"cache.hit\", \"miss\");\n return { cached: false };\n } finally {\n span.end();\n }\n}\n","/**\n * Cosine distance between two equal-length vectors.\n *\n * Returns `1 - cos(a, b)`. Range:\n * - 0.0 = identical direction\n * - 1.0 = orthogonal\n * - 2.0 = opposite (rare in normalized embeddings)\n *\n * Throws on dim mismatch. (Callers MUST filter by dim before calling\n * — EC-2 absorbed at the store level.)\n *\n * @internal\n */\n\nexport function cosineDistance(a: ReadonlyArray<number>, b: ReadonlyArray<number>): number {\n if (a.length !== b.length) {\n throw new Error(`cosineDistance: dim mismatch (${a.length} vs ${b.length})`);\n }\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i += 1) {\n const av = a[i]!;\n const bv = b[i]!;\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n if (normA === 0 || normB === 0) return 1.0;\n return 1 - dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n","/**\n * Cache store interface + in-memory implementation (ADRs D252, D259, D261).\n *\n * INVARIANT (EC-9, EC-13): KV map and vector view are the SAME Map.\n * `semanticSearch` iterates `Map.values()`. No parallel list. `set` replaces\n * by key (Map semantics) — no duplicate-on-race possible.\n *\n * EC-2: `semanticSearch` filters by `embedderId === currentEmbedderId &&\n * vector.length === currentDim` before cosine compare. Protects against\n * dim mismatch when disk-loaded entries used a different embedder.\n *\n * @internal\n */\n\nimport type { CacheEntry, CacheStats } from \"../types/cache.js\";\nimport { cosineDistance } from \"./cosine.js\";\n\nexport interface CacheStore {\n kvGet(key: string, now: number): CacheEntry | undefined;\n semanticSearch(\n vector: ReadonlyArray<number>,\n threshold: number,\n embedderId: string,\n namespace: string,\n now: number,\n modelId: string,\n ): { entry: CacheEntry; distance: number } | undefined;\n set(entry: CacheEntry): void;\n delete(key: string): void;\n clear(): Promise<void>;\n stats(): CacheStats;\n evictExpired(now: number): number;\n /** For persistence backends — bulk import. */\n loadAll?(entries: ReadonlyArray<CacheEntry>): void;\n /** For persistence backends — snapshot for serialization. */\n dump?(): ReadonlyArray<CacheEntry>;\n}\n\ninterface StoreCounters {\n kvHits: number;\n semanticHits: number;\n misses: number;\n excluded: number;\n evicted: number;\n embedderFailures: number;\n}\n\nexport class InMemoryCacheStore implements CacheStore {\n private readonly map = new Map<string, CacheEntry>();\n private readonly counters: StoreCounters = {\n kvHits: 0,\n semanticHits: 0,\n misses: 0,\n excluded: 0,\n evicted: 0,\n embedderFailures: 0,\n };\n\n constructor(private readonly maxEntries: number) {}\n\n kvGet(key: string, now: number): CacheEntry | undefined {\n const e = this.map.get(key);\n if (e === undefined) return undefined;\n if (e.expiresAt <= now) {\n this.map.delete(key);\n this.counters.evicted += 1;\n return undefined;\n }\n // Touch for LRU recency.\n this.map.delete(key);\n this.map.set(key, { ...e, accessedAt: now, accessCount: e.accessCount + 1 });\n return this.map.get(key);\n }\n\n private isEligibleForSearch(\n e: CacheEntry,\n embedderId: string,\n namespace: string,\n dim: number,\n now: number,\n modelId: string,\n ): boolean {\n if (e.embedderId !== embedderId) return false;\n // M3 #67 — a stored entry only matches a query for the SAME model.\n if (e.modelId !== modelId) return false;\n if (e.namespace !== namespace) return false;\n if (e.vector.length !== dim) return false;\n if (e.expiresAt <= now) return false;\n return true;\n }\n\n semanticSearch(\n vector: ReadonlyArray<number>,\n threshold: number,\n embedderId: string,\n namespace: string,\n now: number,\n // M3 #67 — model-scoped: without this the semantic path can return a\n // response cached for a DIFFERENT model that shares the embedder + namespace.\n modelId: string,\n ): { entry: CacheEntry; distance: number } | undefined {\n let best: { entry: CacheEntry; distance: number } | undefined;\n for (const e of this.map.values()) {\n if (!this.isEligibleForSearch(e, embedderId, namespace, vector.length, now, modelId))\n continue;\n const d = cosineDistance(e.vector, vector);\n if (d <= threshold && (best === undefined || d < best.distance)) {\n best = { entry: e, distance: d };\n }\n }\n if (best !== undefined) {\n this.map.delete(best.entry.key);\n this.map.set(best.entry.key, {\n ...best.entry,\n accessedAt: now,\n accessCount: best.entry.accessCount + 1,\n });\n }\n return best;\n }\n\n set(entry: CacheEntry): void {\n // EC-13: Map.set replaces by key — no parallel list.\n if (this.map.has(entry.key)) {\n this.map.delete(entry.key);\n }\n this.map.set(entry.key, entry);\n // Evict LRU when over capacity.\n while (this.map.size > this.maxEntries) {\n const oldestKey = this.map.keys().next().value;\n if (oldestKey === undefined) break;\n this.map.delete(oldestKey);\n this.counters.evicted += 1;\n }\n }\n\n delete(key: string): void {\n this.map.delete(key);\n }\n\n async clear(): Promise<void> {\n this.map.clear();\n }\n\n stats(): CacheStats {\n return {\n entries: this.map.size,\n kvHits: this.counters.kvHits,\n semanticHits: this.counters.semanticHits,\n misses: this.counters.misses,\n excluded: this.counters.excluded,\n evicted: this.counters.evicted,\n embedderFailures: this.counters.embedderFailures,\n };\n }\n\n evictExpired(now: number): number {\n let count = 0;\n for (const [k, e] of this.map.entries()) {\n if (e.expiresAt <= now) {\n this.map.delete(k);\n count += 1;\n }\n }\n this.counters.evicted += count;\n return count;\n }\n\n loadAll(entries: ReadonlyArray<CacheEntry>): void {\n for (const e of entries) this.map.set(e.key, e);\n }\n\n dump(): ReadonlyArray<CacheEntry> {\n return [...this.map.values()];\n }\n\n /** Internal helpers for counters (used by lookup/store handlers). */\n incrementKvHits(): void {\n this.counters.kvHits += 1;\n }\n incrementSemanticHits(): void {\n this.counters.semanticHits += 1;\n }\n incrementMisses(): void {\n this.counters.misses += 1;\n }\n incrementExcluded(): void {\n this.counters.excluded += 1;\n }\n incrementEmbedderFailures(): void {\n this.counters.embedderFailures += 1;\n }\n}\n","/**\n * Public type contract for `Cache.semantic / .asPlugin / .stats / .clear`\n * (Adoption Roadmap #6; ADRs D249-D266).\n *\n * @public\n */\n\n/* ─── TTL config (D255) ─── */\n\n/**\n * How long entries live, and which prompts never become entries at all.\n *\n * @public\n */\nexport interface CacheTTLConfig {\n /**\n * Lifetime applied to every entry written, evaluated at write time.\n *\n * A NUMBER is SECONDS (`3600` is an hour). A STRING needs a unit suffix — `s`, `m`, `h`, `d`, `w`\n * (`\"30m\"`, `\"1h\"`, `\"7d\"`). A bare numeric string is not a duration: `\"3600\"` throws\n * {@link CacheInvalidTtlError}, and so do a negative number and an unknown unit. `0` / `\"0s\"`\n * parses fine and writes entries that are already expired, which disables the cache without\n * disabling the embedding calls.\n *\n * The throw happens on the WRITE, not at `Cache.semantic(...)` — a bad value survives\n * construction and surfaces on the first `remember()` or the first cached assistant reply.\n */\n readonly default: string | number;\n /**\n * Prompts matching this regex are never cached — e.g. `/weather|today|now/i` for anything whose\n * answer goes stale.\n *\n * Applies to BOTH directions: a matching prompt is not looked up (it counts as\n * {@link CacheStats.excluded}, not a miss) and not stored. Test it against your real prompts —\n * a regex broad enough to match every question disables the cache while every counter still\n * looks healthy.\n *\n * Bring your own regex object; a `/g` flag is a hazard here because `RegExp.test` is stateful\n * with it and would match every other call.\n */\n readonly exclude?: RegExp;\n}\n\n/* ─── Persistence (D265) ─── */\n\n/**\n * Where cached entries live between process restarts.\n *\n * `\"memory\"` (the default when {@link CacheSemanticOptions.persistence} is omitted) keeps everything\n * in the process and loses it on exit — right for a request-scoped worker, wrong for a CLI that runs\n * once per invocation and would never see a hit.\n *\n * `\"json\"` writes the whole entry set, VECTORS INCLUDED, to a file under `dir`. That file grows with\n * `maxEntries` × the embedder's dimension, so a 1000-entry cache over a 1536-dimension embedder is\n * on the order of megabytes, and it is plaintext: every prompt and response is readable. Do not point\n * `dir` at a directory that gets committed.\n */\nexport interface CachePersistenceOptions {\n /** `\"memory\"` for process-local, `\"json\"` for a file under {@link CachePersistenceOptions.dir}. */\n readonly backend: \"memory\" | \"json\";\n /**\n * Directory holding `<namespace>.json`. REQUIRED when `backend` is `\"json\"` — omitting it makes\n * `Cache.semantic(...)` throw `ZodError`, it is not silently downgraded to memory.\n *\n * Created recursively on the first write. Writes are atomic but DEBOUNCED by 200 ms, and loading\n * is fire-and-forget, so the file is eventually-consistent with memory in both directions. A\n * corrupt or wrong-schema file is logged and treated as an empty cache — it never blocks startup.\n */\n readonly dir?: string;\n}\n\n/* ─── Embedder option ─── */\n\n/**\n * Embedder runtime shape — minimal subset of `EmbeddingRuntime` (D11) the\n * Cache actually uses. Lets tests inject fake embedders without pulling\n * the full memory subsystem.\n *\n * `@theokit/sdk-cache` ships one implementation, `createLexicalEmbedder()`; anything with these\n * four members works, including a wrapper around a provider's embedding endpoint.\n *\n * @public\n */\nexport interface CacheEmbedderRuntime {\n /**\n * Stable identity of this embedding SPACE, not of the object.\n *\n * It is part of the exact-match key and of the semantic eligibility filter, so changing it\n * invalidates every existing entry — which is the point: vectors from two different embedders\n * are not comparable, and a shared id would let one embedder's vectors be matched against\n * another's. Version it whenever the model, its parameters or the dimension change.\n */\n readonly id: string;\n /** Human-facing model name. Recorded for diagnostics; the cache never keys on it. */\n readonly model: string;\n /**\n * Length of the vectors `embed` returns.\n *\n * Entries whose stored vector has a different length are skipped during the semantic scan rather\n * than compared, so a dimension change silently costs you the whole warm cache instead of\n * throwing.\n */\n readonly dimension: number;\n /**\n * Embed a batch; the cache always passes exactly one text and reads `result[0]`.\n *\n * A rejection is NOT propagated to the caller: the cache logs it, counts it in\n * {@link CacheStats.embedderFailures} and treats the operation as a miss / skipped write. Return\n * a zero vector only if you want it treated as a non-match, since cosine distance against it is\n * defined as 1.0.\n */\n embed(texts: ReadonlyArray<string>): Promise<number[][]>;\n}\n\n/* ─── Options ─── */\n\n/**\n * Configuration for `Cache.semantic(...)`.\n *\n * Only `embedder` is required, and deliberately so: autoselecting one would make an agent start\n * calling an embedding API because a cache was enabled, which is a surprise bill rather than a\n * default. Everything else has a working default.\n *\n * The lookup runs in two stages — an exact key match first, then a vector search — so an identical\n * prompt never pays for an embedding call. Only the second stage consults `threshold`.\n */\nexport interface CacheSemanticOptions {\n /** Embedder instance. REQUIRED in v1 — no autoselect (avoids surprise API calls). */\n readonly embedder: CacheEmbedderRuntime;\n /**\n * Maximum cosine DISTANCE (`1 - cosine similarity`) at which a stored entry counts as a match.\n * Default 0.85. Lower is stricter; 0 requires an identical direction, 1 accepts orthogonal\n * vectors, and the accepted range is 0..2.\n *\n * It is a distance and not a similarity, so raising it LOOSENS matching — 0.85 is already\n * permissive for normalized embeddings and will return semantically unrelated answers if your\n * embedder spreads vectors narrowly. Only the vector stage consults it; an exact-key hit ignores\n * it entirely. Watch {@link CacheStats.semanticHits} when you tune it.\n */\n readonly threshold?: number;\n /** TTL config. Default `{ default: \"1h\" }`. */\n readonly ttl?: CacheTTLConfig;\n /**\n * Isolation bucket, 1..64 chars. Default `\"global\"`. Entries never match across namespaces.\n *\n * It is also the plugin name (`cache-semantic-<namespace>`) and, under `\"json\"` persistence, the\n * FILE name. Two caches sharing a namespace and a `dir` therefore share ONE store rather than\n * racing over one file (#359) — with the consequence that the second one's `maxEntries` is\n * ignored, since the store already exists.\n */\n readonly namespace?: string;\n /**\n * Model id stamped on every entry, and part of both the exact key and the semantic eligibility\n * filter — a response cached for one model is never returned for another.\n *\n * Defaults to the literal string `\"unknown\"`, which is an ORDINARY value rather than a wildcard:\n * entries written by a cache that defaulted it are visible only to another cache that also\n * defaults it. Set it to the same id you pass to `Agent.create({ model })`.\n */\n readonly modelId?: string;\n /**\n * Ceiling on stored entries. Default 1000. Exceeding it evicts the least recently used entry and\n * increments {@link CacheStats.evicted}.\n *\n * Sizes the JSON snapshot too: the file holds every entry's full embedding, so this multiplied by\n * the embedder dimension is the file's order of magnitude.\n */\n readonly maxEntries?: number;\n /** Persistence backend. Default in-memory. */\n readonly persistence?: CachePersistenceOptions;\n}\n\n/* ─── Entry + stats ─── */\n\n/**\n * One cached prompt/response pair, as `Cache` stores it.\n *\n * Read-only from the outside: entries are produced by the cache, and reach a caller only through a\n * `\"json\"` persistence dump. `vector` is the embedding of `prompt`, which is what makes the file\n * large; `accessedAt` / `accessCount` are what LRU eviction reads when `maxEntries` is reached.\n *\n * `key` is the exact-match key (a hash of namespace, embedder, model and prompt), so two entries with\n * the same prompt under different models are different entries — a cached answer never crosses a\n * model boundary.\n */\nexport interface CacheEntry {\n readonly key: string;\n readonly namespace: string;\n readonly embedderId: string;\n readonly modelId: string;\n readonly prompt: string;\n readonly response: string;\n readonly vector: ReadonlyArray<number>;\n readonly createdAt: number;\n readonly expiresAt: number;\n readonly accessedAt: number;\n readonly accessCount: number;\n}\n\n/**\n * Counters returned by `Cache.stats()`. All monotonic within a process; a `\"json\"` backend does not\n * restore them, so they count THIS process's traffic, not the file's history.\n *\n * The three miss-shaped counters are distinct on purpose, and the distinction is the point of\n * reading stats at all:\n *\n * - `misses` — looked up, nothing matched. The cache is working and cold. An empty or\n * whitespace-only prompt also lands here, having never been looked up at all.\n * - `excluded` — {@link CacheTTLConfig.exclude} matched the prompt, so no lookup happened. High and\n * unexpected means the regex is too broad.\n * - `embedderFailures` — the embedder threw. The lookup DEGRADES to a miss rather than failing the\n * call, so a broken embedder shows up here as a rising number and nowhere else. A cache that\n * suddenly stops hitting, with this climbing, is an embedder outage — not a cold cache.\n *\n * `kvHits` counts exact-key matches (no embedding call); `semanticHits` counts vector matches.\n * A `semanticHits` of zero with healthy `kvHits` means `threshold` is too strict.\n */\nexport interface CacheStats {\n readonly entries: number;\n readonly kvHits: number;\n readonly semanticHits: number;\n readonly misses: number;\n readonly excluded: number;\n readonly evicted: number;\n readonly embedderFailures: number;\n}\n\n/* ─── Error classes ─── */\n\n/**\n * A TTL value that could not be parsed, thrown at configuration time rather than on first use.\n *\n * Accepts a number of SECONDS, or a string with a unit suffix `s` / `m` / `h` / `d` / `w`\n * (`\"30m\"`, `\"1h\"`, `\"7d\"`). A bare numeric string is not a duration — `\"3600\"` is rejected,\n * `3600` is an hour. Also rejected: a negative or non-finite number, and any unit outside that\n * set. `input` carries what was passed, so the message names the offending value rather than the\n * field.\n *\n * \"Configuration time\" means the first WRITE that applies the TTL, not `Cache.semantic(...)` —\n * `CacheSemanticOptions` is validated for shape, never for TTL parseability.\n */\nexport class CacheInvalidTtlError extends Error {\n override readonly name = \"CacheInvalidTtlError\";\n constructor(public readonly input: string | number) {\n super(\n `Invalid TTL value: \"${String(input)}\". Expected number (seconds) or string like \"1h\" / \"30m\" / \"7d\".`,\n );\n }\n}\n","/**\n * TTL string parser. Accepts:\n * - number → treated as SECONDS\n * - string `\"\\d+(s|m|h|d|w)\"` → seconds/minutes/hours/days/weeks\n *\n * EC-8: `\"0s\"` / `0` returns 0 (effectively disables cache for that entry).\n * Negative numbers throw `CacheInvalidTtlError`.\n *\n * @internal\n */\n\nimport { CacheInvalidTtlError } from \"../types/cache.js\";\n\nconst TTL_PATTERN = /^(\\d+)\\s*(s|m|h|d|w)$/i;\n\nexport function parseTtlMs(input: string | number): number {\n if (typeof input === \"number\") {\n if (!Number.isFinite(input) || input < 0) {\n throw new CacheInvalidTtlError(input);\n }\n return Math.floor(input * 1000);\n }\n const trimmed = input.trim();\n const m = TTL_PATTERN.exec(trimmed);\n if (m === null) {\n throw new CacheInvalidTtlError(input);\n }\n const value = Number(m[1]);\n const unit = m[2]!.toLowerCase();\n switch (unit) {\n case \"s\":\n return value * 1000;\n case \"m\":\n return value * 60_000;\n case \"h\":\n return value * 3_600_000;\n case \"d\":\n return value * 86_400_000;\n case \"w\":\n return value * 604_800_000;\n /* c8 ignore next 2 */\n default:\n throw new CacheInvalidTtlError(input);\n }\n}\n","/**\n * Cache store handler — wired to `post_assistant_reply` hook (ADRs D260, D266).\n *\n * EC-1: embedder failure during store is silent (LLM call already succeeded;\n * no cache entry written).\n * EC-3: empty prompt bypass.\n * EC-10 / D266: skip storage when the run invoked tools (replay loses\n * side-effects).\n *\n * @internal\n */\n\nimport type { CacheEmbedderRuntime, CacheTTLConfig } from \"../types/cache.js\";\nimport { embedOrDegrade } from \"./embed-helper.js\";\nimport { computeCacheKey } from \"./key.js\";\nimport type { LookupableStore } from \"./lookup.js\";\nimport { startCacheStoreSpan } from \"./telemetry.js\";\nimport { parseTtlMs } from \"./ttl.js\";\n\ninterface StoreParams {\n prompt: string;\n response: string;\n /** D266: skip cache when tools were used. */\n usedTools?: boolean;\n store: LookupableStore;\n embedder: CacheEmbedderRuntime;\n ttl: CacheTTLConfig;\n namespace: string;\n modelId: string;\n}\n\nexport async function performStore(p: StoreParams): Promise<void> {\n const span = startCacheStoreSpan({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n });\n try {\n // EC-3: empty prompt bypass.\n if (p.prompt.trim().length === 0) {\n span.setAttribute(\"cache.bypass_reason\", \"empty_prompt\");\n return;\n }\n // EC-3 + safety: empty response is not a useful cache entry.\n if (p.response.length === 0) {\n span.setAttribute(\"cache.bypass_reason\", \"empty_response\");\n return;\n }\n // D266 / EC-10: tool-use runs are not cached (replay loses side-effects).\n if (p.usedTools === true) {\n span.setAttribute(\"cache.bypass_reason\", \"used_tools\");\n return;\n }\n // D255 exclude regex.\n if (p.ttl.exclude?.test(p.prompt)) {\n span.setAttribute(\"cache.bypass_reason\", \"exclude_regex\");\n return;\n }\n\n const key = computeCacheKey({\n namespace: p.namespace,\n embedderId: p.embedder.id,\n modelId: p.modelId,\n prompt: p.prompt,\n });\n\n // EC-1: embedder failure during store is silent.\n const vec = await embedOrDegrade(p.embedder, p.prompt, p.store, span, \"store\");\n if (vec === undefined) return;\n\n const now = Date.now();\n const ttlMs = parseTtlMs(p.ttl.default);\n p.store.set({\n key,\n namespace: p.namespace,\n embedderId: p.embedder.id,\n modelId: p.modelId,\n prompt: p.prompt,\n response: p.response,\n vector: vec,\n createdAt: now,\n expiresAt: now + ttlMs,\n accessedAt: now,\n accessCount: 0,\n });\n span.setAttribute(\"cache.stored\", true);\n } finally {\n span.end();\n }\n}\n","/**\n * JSON disk-backed cache store (ADR D265).\n *\n * One file per namespace at `<dir>/<namespace>.json`. Uses\n * `atomicWriteText` (D60) for crash-safe writes. Debounced flush (200ms)\n * to coalesce bursts.\n *\n * EC-7: corrupt JSON load → log warn + treat as empty cache. Never\n * propagates parse errors that would block `Agent.create`.\n *\n * Layered behind `InMemoryCacheStore` — disk is just persistence; all\n * lookups still hit the in-memory Map for O(1) KV / O(N) vector scan.\n *\n * @internal\n */\n\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { atomicWriteText } from \"@theokit/sdk/persistence\";\nimport type { CacheEntry, CacheStats } from \"../types/cache.js\";\nimport { type CacheStore, InMemoryCacheStore } from \"./store.js\";\n\ninterface SerializedSnapshot {\n readonly _schemaVersion: 1;\n readonly namespace: string;\n readonly entries: ReadonlyArray<CacheEntry>;\n}\n\nconst FLUSH_DEBOUNCE_MS = 200;\n\nexport class JsonFileCacheStore implements CacheStore {\n private readonly inner: InMemoryCacheStore;\n private flushTimer: ReturnType<typeof setTimeout> | undefined;\n private dirty = false;\n\n constructor(\n private readonly dir: string,\n private readonly namespace: string,\n maxEntries: number,\n ) {\n this.inner = new InMemoryCacheStore(maxEntries);\n }\n\n /** Hydrate from disk. EC-7: corrupt file → empty cache. */\n async hydrate(): Promise<void> {\n const file = this.filePath();\n try {\n const raw = await readFile(file, \"utf8\");\n let parsed: SerializedSnapshot;\n try {\n parsed = JSON.parse(raw) as SerializedSnapshot;\n } catch (err) {\n console.warn(\n `[cache] corrupt snapshot at ${file}, starting fresh:`,\n err instanceof Error ? err.message : err,\n );\n return;\n }\n if (parsed._schemaVersion !== 1) {\n console.warn(\n `[cache] unsupported schema v${parsed._schemaVersion} at ${file}, starting fresh`,\n );\n return;\n }\n // Only load entries for this namespace (defensive).\n const valid = parsed.entries.filter((e) => e.namespace === this.namespace);\n this.inner.loadAll(valid);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n console.warn(`[cache] failed to read ${file}:`, err instanceof Error ? err.message : err);\n }\n }\n\n kvGet(key: string, now: number): CacheEntry | undefined {\n return this.inner.kvGet(key, now);\n }\n\n semanticSearch(\n vector: ReadonlyArray<number>,\n threshold: number,\n embedderId: string,\n namespace: string,\n now: number,\n modelId: string,\n ): { entry: CacheEntry; distance: number } | undefined {\n return this.inner.semanticSearch(vector, threshold, embedderId, namespace, now, modelId);\n }\n\n set(entry: CacheEntry): void {\n this.inner.set(entry);\n this.markDirty();\n }\n\n delete(key: string): void {\n this.inner.delete(key);\n this.markDirty();\n }\n\n async clear(): Promise<void> {\n await this.inner.clear();\n this.markDirty();\n await this.flush();\n }\n\n stats(): CacheStats {\n return this.inner.stats();\n }\n\n evictExpired(now: number): number {\n const n = this.inner.evictExpired(now);\n if (n > 0) this.markDirty();\n return n;\n }\n\n /** Force write the current snapshot. Called on shutdown / clear. */\n async flush(): Promise<void> {\n if (this.flushTimer !== undefined) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n if (!this.dirty) return;\n await this.writeSnapshot();\n this.dirty = false;\n }\n\n /** Counters proxied to inner. */\n incrementKvHits(): void {\n this.inner.incrementKvHits();\n }\n incrementSemanticHits(): void {\n this.inner.incrementSemanticHits();\n }\n incrementMisses(): void {\n this.inner.incrementMisses();\n }\n incrementExcluded(): void {\n this.inner.incrementExcluded();\n }\n incrementEmbedderFailures(): void {\n this.inner.incrementEmbedderFailures();\n }\n\n private filePath(): string {\n return join(this.dir, `${this.namespace}.json`);\n }\n\n private markDirty(): void {\n this.dirty = true;\n if (this.flushTimer === undefined) {\n this.flushTimer = setTimeout(() => {\n this.flushTimer = undefined;\n void this.flush().catch((err) =>\n console.warn(`[cache] debounced flush failed:`, err instanceof Error ? err.message : err),\n );\n }, FLUSH_DEBOUNCE_MS);\n }\n }\n\n private async writeSnapshot(): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n const snapshot: SerializedSnapshot = {\n _schemaVersion: 1,\n namespace: this.namespace,\n entries: this.inner.dump(),\n };\n const serialized = JSON.stringify(snapshot);\n await atomicWriteText(this.filePath(), serialized);\n }\n}\n","/**\n * Public `Cache` class — semantic LLM response cache (Adoption Roadmap #6;\n * ADRs D249-D266).\n *\n * Usage:\n *\n * import { Agent } from \"@theokit/sdk\";\n * import { Cache, createLexicalEmbedder } from \"@theokit/sdk-cache\";\n *\n * const cache = Cache.semantic({\n * embedder: createLexicalEmbedder(), // or any CacheEmbedderRuntime\n * threshold: 0.85,\n * ttl: { default: \"1h\", exclude: /weather|today|now/i },\n * namespace: \"my-app\",\n * modelId: \"openai/gpt-4o-mini\",\n * });\n *\n * // (a) Plugin mode — the cached answer is INJECTED as context; the LLM is still called.\n * const agent = await Agent.create({\n * model: { id: \"openai/gpt-4o-mini\" },\n * plugins: [cache.asPlugin()],\n * });\n *\n * // (b) Explicit mode — this is the one that skips the LLM call.\n * const hit = await cache.consult(prompt);\n * const answer = hit.hit ? hit.response : await callTheModel(prompt);\n * if (!hit.hit) await cache.remember(prompt, answer);\n *\n * The distinction between (a) and (b) is the single most important thing to know about this\n * package — see `Cache.asPlugin`.\n *\n * @public\n */\n\nimport { resolve } from \"node:path\";\nimport {\n Plugin,\n type PluginContext,\n type PostAssistantReplyContext,\n type PreUserSendContext,\n type PreUserSendResult,\n} from \"@theokit/sdk\";\nimport { PersistenceSchema } from \"@theokit/sdk/persistence\";\nimport { z } from \"zod\";\nimport { type LookupableStore, performLookup } from \"./internal/lookup.js\";\nimport { InMemoryCacheStore } from \"./internal/store.js\";\nimport { performStore } from \"./internal/store-handler.js\";\nimport { JsonFileCacheStore } from \"./internal/store-json.js\";\nimport type {\n CacheEmbedderRuntime,\n CachePersistenceOptions,\n CacheSemanticOptions,\n CacheStats,\n CacheTTLConfig,\n} from \"./types/cache.js\";\n\nconst CacheSemanticOptionsSchema = z.object({\n embedder: z.unknown().refine(\n (v) => {\n if (v === null || typeof v !== \"object\") return false;\n const o = v as { id?: unknown; embed?: unknown; dimension?: unknown };\n return (\n typeof o.id === \"string\" && typeof o.embed === \"function\" && typeof o.dimension === \"number\"\n );\n },\n { message: \"embedder must be a CacheEmbedderRuntime with { id, dimension, embed }\" },\n ),\n threshold: z.number().min(0).max(2).optional(),\n ttl: z\n .object({\n default: z.union([z.string(), z.number()]),\n exclude: z.instanceof(RegExp).optional(),\n })\n .optional(),\n namespace: z.string().min(1).max(64).optional(),\n modelId: z.string().min(1).max(128).optional(),\n maxEntries: z.number().int().min(1).max(1_000_000).optional(),\n persistence: PersistenceSchema,\n});\n\nconst DEFAULT_THRESHOLD = 0.85;\nconst DEFAULT_TTL: CacheTTLConfig = { default: \"1h\" };\nconst DEFAULT_NAMESPACE = \"global\";\nconst DEFAULT_MAX_ENTRIES = 1000;\n\n/**\n * A semantic response cache: an exact-key lookup, then a vector-similarity lookup, over\n * prompt/response pairs the caller has stored.\n *\n * Build one with {@link Cache.semantic}; `new Cache()` is a compile error. One instance owns one\n * store, so two `Cache.semantic(...)` calls never share entries even under the same `namespace`\n * and the same `dir` — the JSON backend will have both instances writing the same file.\n *\n * Two ways to use it, and they do NOT save the same thing:\n *\n * | | {@link Cache.consult} + {@link Cache.remember} | {@link Cache.asPlugin} |\n * |---|---|---|\n * | LLM call on a hit | skipped | still made |\n * | What you save | the whole call | nothing, today |\n * | Who drives it | you | the agent loop |\n *\n * @public\n */\nexport class Cache {\n private _plugin?: Plugin;\n\n private constructor(\n private readonly embedder: CacheEmbedderRuntime,\n private readonly threshold: number,\n private readonly ttl: CacheTTLConfig,\n private readonly namespace: string,\n private readonly modelId: string,\n private readonly store: LookupableStore,\n private readonly hydrated: Promise<void>,\n ) {}\n\n /**\n * Resolves once the `\"json\"` backend has finished reading its snapshot; resolves immediately on\n * the in-memory backend.\n *\n * You rarely need it: `consult` and `remember` await hydration themselves, so a cache is correct\n * without it. It exists for a caller who wants the read charged to startup rather than to the\n * first lookup — and because the code promised it long before it existed (#359).\n *\n * A corrupt or unreadable snapshot resolves normally with an empty cache and a warning on\n * stderr; a cache must not take the process down.\n */\n async ready(): Promise<void> {\n await this.hydrated;\n }\n\n /**\n * Write the pending snapshot to disk now, keeping every entry. No-op on the in-memory backend.\n *\n * Writes are debounced 200ms, so a process that remembers something and exits inside that window\n * persists nothing — precisely the once-per-invocation CLI the `\"json\"` backend exists for. Call\n * this before exiting. Nothing flushes on teardown: an `exit` handler cannot await, and a library\n * installing a process-level hook is a side effect the caller did not ask for.\n *\n * Until #359 the only public call that wrote the snapshot was `clear()`, which also destroyed\n * everything you wanted to persist.\n */\n async flush(): Promise<void> {\n const store = this.store as { flush?: () => Promise<void> };\n if (typeof store.flush === \"function\") await store.flush();\n }\n\n /**\n * Build a cache. Validates `options` with Zod and THROWS `ZodError` on a bad shape — an\n * `embedder` missing `{ id, dimension, embed }`, a `threshold` outside `0..2`, a `namespace`\n * longer than 64 chars, or `persistence: { backend: \"json\" }` without a `dir`.\n *\n * Defaults: `threshold` 0.85, `ttl` `{ default: \"1h\" }`, `namespace` `\"global\"`, `maxEntries`\n * 1000 (LRU), `persistence` in-memory. `modelId` defaults to the literal string `\"unknown\"`,\n * which is a real namespace value and not a wildcard: entries stored while `modelId` was\n * defaulted are only ever returned to lookups that also default it.\n *\n * With `persistence: { backend: \"json\", dir }` the snapshot is read in the background and this\n * call does not await it — but `consult` and `remember` do, so a lookup issued immediately after\n * construction still sees what is on disk. Await {@link Cache.ready} to charge the read to\n * startup instead of to the first lookup. Two caches built with the same `dir` and `namespace`\n * share one store, so they cannot overwrite each other's snapshot; the FIRST one's `maxEntries`\n * is the one that applies.\n */\n static semantic(options: CacheSemanticOptions): Cache {\n CacheSemanticOptionsSchema.parse(options);\n const threshold = options.threshold ?? DEFAULT_THRESHOLD;\n const ttl = options.ttl ?? DEFAULT_TTL;\n const namespace = options.namespace ?? DEFAULT_NAMESPACE;\n const modelId = options.modelId ?? \"unknown\";\n const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n const { store, hydrated } = createStore(namespace, maxEntries, options.persistence);\n return new Cache(options.embedder, threshold, ttl, namespace, modelId, store, hydrated);\n }\n\n /**\n * A `Plugin` for `Agent.create({ plugins: [...] })` that reads the cache before each user turn\n * and writes it after each assistant reply.\n *\n * READ THIS BEFORE BUDGETING FOR IT. A hit does NOT skip the model call. The hook returns the\n * cached response as `PreUserSendResult.recalledContext`, which the agent loop injects as a\n * `<memory-context>` block ahead of the prompt — the request still goes to the provider, still\n * costs tokens, and still returns whatever the model makes of that context, which need not be\n * the cached text. Use {@link Cache.consult} / {@link Cache.remember} when the point is to avoid\n * the call.\n *\n * A turn that invoked tools is NOT cached: the store hook reads\n * `PostAssistantReplyContext.usedTools`, which the runtime derives from the run's tool calls\n * (#358). Replaying such an answer would hand a later caller the result of a write that never\n * happened. Until that signal existed the hook passed a literal `false` and cached those turns,\n * despite the package's stated intent.\n *\n * Memoized: repeated calls return the SAME plugin, so registering it twice does not double the\n * hooks.\n */\n asPlugin(): Plugin {\n if (this._plugin !== undefined) return this._plugin;\n const cache = this;\n this._plugin = Plugin.create({\n name: `cache-semantic-${this.namespace}`,\n version: \"1.0.0\",\n kind: \"general\" as const,\n register(ctx: PluginContext): void {\n ctx.on(\"pre_user_send\", async (rawCtx) => {\n const c = rawCtx as PreUserSendContext;\n const result = await performLookup({\n prompt: c.prompt,\n store: cache.store,\n embedder: cache.embedder,\n threshold: cache.threshold,\n ttl: cache.ttl,\n namespace: cache.namespace,\n modelId: cache.modelId,\n });\n if (result.cached === true) {\n // Cache hit — return recalledContext as the cached response.\n // The agent loop will inject this; in v1 the caller must\n // check `pre_user_send` hook return value via runtime support\n // (the cache hit short-circuits via injection; see docs).\n const wrapped: PreUserSendResult = {\n recalledContext: result.response,\n };\n return wrapped;\n }\n const miss: PreUserSendResult = {};\n return miss;\n });\n ctx.on(\"post_assistant_reply\", async (rawCtx) => {\n const c = rawCtx as PostAssistantReplyContext;\n await performStore({\n prompt: c.prompt,\n response: c.reply,\n // EC-10 / D266 — never cache a reply the run produced with tools; replaying it hands a\n // later caller the RESULT of a write without the write having happened. This used to\n // be the literal `false` under a comment describing a `tool_call_id` heuristic that was\n // never implemented, so the guard fired only for a hand-written `remember(...)` call\n // and never on the plugin path, which is the one that runs automatically. #358 added\n // the signal to `PostAssistantReplyContext`; this reads it.\n usedTools: c.usedTools,\n store: cache.store,\n embedder: cache.embedder,\n ttl: cache.ttl,\n namespace: cache.namespace,\n modelId: cache.modelId,\n });\n return undefined;\n });\n },\n });\n return this._plugin;\n }\n\n /**\n * Look a prompt up. Call it BEFORE dispatching to the model and skip the call on a hit — this is\n * the only path in this package that actually avoids an LLM request.\n *\n * ```ts\n * const hit = await cache.consult(prompt);\n * if (hit.hit) return hit.response;\n * ```\n *\n * `source` says which stage matched: `\"kv\"` is an exact-key match and costs NO embedding call;\n * `\"semantic\"` means the prompt was embedded and a stored vector came within `threshold`, and\n * only then is `distance` present (cosine distance, so smaller is closer).\n *\n * NEVER THROWS on an embedder failure. It degrades to `{ hit: false }`, logs a warning on\n * stderr, and increments {@link CacheStats.embedderFailures} — a cache must not take the request\n * down with it. A cache that has silently stopped hitting is that counter climbing, not a cold\n * cache.\n *\n * An empty or whitespace-only prompt returns `{ hit: false }` and counts as a MISS. A prompt\n * matching {@link CacheTTLConfig.exclude} returns `{ hit: false }` and counts as `excluded`.\n */\n async consult(\n prompt: string,\n ): Promise<\n { hit: false } | { hit: true; response: string; source: \"kv\" | \"semantic\"; distance?: number }\n > {\n await this.hydrated; // #359 — never answer \"miss\" from a snapshot that is still being read.\n const result = await performLookup({\n prompt,\n store: this.store,\n embedder: this.embedder,\n threshold: this.threshold,\n ttl: this.ttl,\n namespace: this.namespace,\n modelId: this.modelId,\n });\n if (result.cached === true) {\n return {\n hit: true,\n response: result.response ?? \"\",\n source: result.source ?? \"kv\",\n ...(result.distance !== undefined ? { distance: result.distance } : {}),\n };\n }\n return { hit: false };\n }\n\n /**\n * Store a prompt/response pair. Pair it with {@link Cache.consult} after you dispatched the model\n * call yourself.\n *\n * Pass `{ usedTools: true }` when the answer came from a run that invoked tools and replaying it\n * would lose the side effects — the write is then skipped entirely.\n *\n * Silently writes nothing when the prompt is empty/whitespace, the response is empty, the prompt\n * matches {@link CacheTTLConfig.exclude}, or the embedder fails (that last case increments\n * {@link CacheStats.embedderFailures}). It resolves in every one of those cases: a resolved\n * promise is not evidence that an entry exists — read {@link CacheStats.entries} if you need\n * that.\n *\n * Writing beyond `maxEntries` evicts the least-recently-used entry. On the `\"json\"` backend the\n * disk write is DEBOUNCED by 200 ms, so a process that exits right after this resolves loses the\n * entry unless it calls {@link Cache.flush} — which writes the snapshot and keeps every entry.\n * Nothing flushes on teardown.\n */\n async remember(prompt: string, response: string, opts?: { usedTools?: boolean }): Promise<void> {\n await this.hydrated; // #359 — a write that lands before hydration would be erased by it.\n await performStore({\n prompt,\n response,\n usedTools: opts?.usedTools === true,\n store: this.store,\n embedder: this.embedder,\n ttl: this.ttl,\n namespace: this.namespace,\n modelId: this.modelId,\n });\n }\n\n /**\n * Counter snapshot for this instance. See {@link CacheStats} for what each counter separates —\n * in particular `misses` vs `excluded` vs `embedderFailures`, which is how you tell a cold cache\n * from a too-broad exclude regex from a broken embedder.\n *\n * Process-local: the `\"json\"` backend persists entries, never counters, so a restart reports\n * zeros against a warm file.\n */\n stats(): CacheStats {\n return this.store.stats();\n }\n\n /**\n * Drop every entry. On the `\"json\"` backend this also forces the debounced snapshot to disk\n * immediately, so it is the one public call that guarantees the file matches memory.\n *\n * Counters are NOT reset — `stats()` keeps reporting the hits and misses accumulated before the\n * clear, so `entries: 0` alongside a non-zero `kvHits` is expected, not a bug.\n */\n async clear(): Promise<void> {\n await this.store.clear();\n }\n\n /**\n * Remove every entry whose TTL has elapsed, returning how many were dropped, and add that to\n * {@link CacheStats.evicted}.\n *\n * Optional housekeeping: expired entries are already skipped on lookup and dropped when touched,\n * so this only reclaims memory for entries nobody asks for. `now` exists to make the sweep\n * testable; leave it out in production.\n */\n evictExpired(now: number = Date.now()): number {\n return this.store.evictExpired(now);\n }\n}\n\n/**\n * One `JsonFileCacheStore` per `(dir, namespace)`, with the promise of its hydration.\n *\n * #359 — the file path is `<dir>/<namespace>.json` and each `Cache` used to own a private store,\n * so two caches with the same dir and namespace — trivially, two agents in one process — each\n * wrote their own FULL snapshot and the last flush erased the other's entries, silently. One file\n * has to mean one store; anything else is two writers racing over a document neither one owns.\n *\n * The first construction's `maxEntries` wins, because the store is already built by the time a\n * second caller asks. That is a real limitation and is documented on `CachePersistenceOptions`\n * rather than papered over.\n *\n * Entries are never released: the map is keyed by a pair a program chooses deliberately, so it is\n * bounded by configuration rather than by traffic.\n */\nconst jsonStores = new Map<string, { store: JsonFileCacheStore; hydrated: Promise<void> }>();\n\nfunction createStore(\n namespace: string,\n maxEntries: number,\n persistence?: CachePersistenceOptions,\n): { store: LookupableStore; hydrated: Promise<void> } {\n if (persistence?.backend === \"json\") {\n const dir = persistence.dir as string;\n const key = `${resolve(dir)}\\u0000${namespace}`;\n const existing = jsonStores.get(key);\n if (existing !== undefined) return existing;\n const store = new JsonFileCacheStore(dir, namespace, maxEntries);\n // Started here, awaited by `ready()` and by the first `consult` / `remember` (#359). It used\n // to be `void store.hydrate()` under a comment promising a `ready()` that was never\n // implemented, so a lookup issued right after construction raced the read and missed on an\n // entry that was on disk.\n const entry = { store, hydrated: store.hydrate() };\n jsonStores.set(key, entry);\n return entry;\n }\n return { store: new InMemoryCacheStore(maxEntries), hydrated: Promise.resolve() };\n}\n","/**\n * Built-in deterministic lexical embedder for `@theokit/sdk-cache` (RADAR #92.e).\n *\n * `Cache.semantic` requires a `CacheEmbedderRuntime` (no autoselect — it avoids\n * surprise LLM-embedding API calls). This supplies a REAL, deterministic,\n * zero-dependency embedding: a token-hash frequency vector, L2-normalized. It is\n * NOT a stub/fake — identical text yields identical vectors (exact cache hits)\n * and lexically similar text yields nearby vectors (cosine-similar hits). It\n * carries no semantic understanding (that needs an LLM embedder), which is the\n * honest trade-off: the cache's value here is exact-repeat + lexical dedup, with\n * no API cost.\n *\n * Promoted from theocode's `server/lib/cache-embedder.ts`.\n *\n * @public\n */\n\nimport type { CacheEmbedderRuntime } from \"./types/cache.js\";\n\n/**\n * Build the built-in lexical embedder — a zero-dependency, zero-cost `CacheEmbedderRuntime`.\n *\n * ```ts\n * const cache = Cache.semantic({ embedder: createLexicalEmbedder() });\n * ```\n *\n * Matches on SHARED WORDS, not on meaning. \"What is the capital of France?\" and \"Tell me the\n * capital of France\" land close together; \"capital of France\" and \"French capital\" do not. If you\n * need paraphrase-level hits, wrap a real embedding API instead — that is what\n * `CacheEmbedderRuntime` is for.\n *\n * `dimension` (default 256) is the number of hash buckets. Raising it reduces collisions between\n * unrelated words, which is the failure mode here: a collision makes two unrelated prompts look\n * similar and can serve a wrong cached answer. It is also baked into the returned `id`\n * (`theokit-lexical-v1-d<dimension>`), so CHANGING IT INVALIDATES every entry already stored — the\n * cache keys on the embedder id and skips vectors of a different length.\n *\n * Never rejects, and never calls the network.\n */\nexport function createLexicalEmbedder(dimension = 256): CacheEmbedderRuntime {\n return {\n id: `theokit-lexical-v1-d${dimension}`,\n model: \"theokit-lexical-hash\",\n dimension,\n async embed(texts: ReadonlyArray<string>): Promise<number[][]> {\n return texts.map((text) => {\n const vec = new Array<number>(dimension).fill(0);\n for (const tok of text.toLowerCase().split(/\\s+/).filter(Boolean)) {\n // FNV-ish rolling hash → bucket; deterministic across runs/processes.\n let h = 0;\n for (let i = 0; i < tok.length; i += 1) h = (h * 31 + tok.charCodeAt(i)) | 0;\n const idx = Math.abs(h) % dimension;\n vec[idx] = (vec[idx] ?? 0) + 1;\n }\n // L2-normalize so cosine distance is well-defined; an empty/whitespace\n // text stays the zero vector (the cache treats it as a non-match).\n const magnitude = Math.sqrt(vec.reduce((s, x) => s + x * x, 0)) || 1;\n return vec.map((x) => x / magnitude);\n });\n },\n };\n}\n"]}