@lov3kaizen/agentsea-cache 0.5.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.
@@ -0,0 +1,1147 @@
1
+ import { LRUCache } from 'lru-cache';
2
+ import 'nanoid';
3
+
4
+ // src/stores/BaseCacheStore.ts
5
+ var BaseCacheStore = class {
6
+ /** Store configuration */
7
+ config;
8
+ /** Store metrics */
9
+ metrics = {
10
+ gets: 0,
11
+ sets: 0,
12
+ deletes: 0,
13
+ hits: 0,
14
+ misses: 0
15
+ };
16
+ constructor(config) {
17
+ this.config = {
18
+ namespace: config.namespace ?? "default",
19
+ ...config
20
+ };
21
+ }
22
+ /**
23
+ * Get the store namespace
24
+ */
25
+ get namespace() {
26
+ return this.config.namespace ?? "default";
27
+ }
28
+ /**
29
+ * Get store metrics
30
+ */
31
+ getMetrics() {
32
+ return { ...this.metrics };
33
+ }
34
+ /**
35
+ * Reset store metrics
36
+ */
37
+ resetMetrics() {
38
+ this.metrics = {
39
+ gets: 0,
40
+ sets: 0,
41
+ deletes: 0,
42
+ hits: 0,
43
+ misses: 0
44
+ };
45
+ }
46
+ /**
47
+ * Increment a metric counter
48
+ */
49
+ incrementMetric(metric, amount = 1) {
50
+ if (typeof this.metrics[metric] === "number") {
51
+ this.metrics[metric] += amount;
52
+ }
53
+ }
54
+ };
55
+ function now() {
56
+ return Date.now();
57
+ }
58
+ function estimateEntrySize(entry) {
59
+ const vectorSize = (entry.embedding?.length ?? 0) * 4;
60
+ const messageSize = entry.request.messages.reduce(
61
+ (acc, m) => acc + (m.content?.length ?? 0) * 2,
62
+ 0
63
+ );
64
+ const responseSize = (entry.response.content?.length ?? 0) * 2;
65
+ const overheadSize = 500;
66
+ return vectorSize + messageSize + responseSize + overheadSize;
67
+ }
68
+
69
+ // src/stores/MemoryCacheStore.ts
70
+ var DEFAULT_CONFIG = {
71
+ maxEntries: 1e4,
72
+ maxSizeBytes: 1024 * 1024 * 1024,
73
+ // 1GB
74
+ evictionPolicy: "lru"
75
+ };
76
+ var MemoryCacheStore = class extends BaseCacheStore {
77
+ storeType = "memory";
78
+ cache;
79
+ vectors = /* @__PURE__ */ new Map();
80
+ memoryConfig;
81
+ closed = false;
82
+ constructor(config = { type: "memory" }) {
83
+ super(config);
84
+ this.memoryConfig = { ...DEFAULT_CONFIG, ...config };
85
+ this.cache = new LRUCache({
86
+ max: this.memoryConfig.maxEntries ?? 1e4,
87
+ maxSize: this.memoryConfig.maxSizeBytes ?? 1024 * 1024 * 1024,
88
+ sizeCalculation: (entry) => estimateEntrySize(entry),
89
+ ttl: 0,
90
+ // TTL handled per-entry
91
+ updateAgeOnGet: true,
92
+ allowStale: false
93
+ });
94
+ }
95
+ get(key) {
96
+ this.incrementMetric("gets");
97
+ const entry = this.cache.get(key);
98
+ if (entry) {
99
+ this.incrementMetric("hits");
100
+ entry.metadata.accessedAt = now();
101
+ entry.metadata.accessCount++;
102
+ return Promise.resolve(entry);
103
+ }
104
+ this.incrementMetric("misses");
105
+ return Promise.resolve(void 0);
106
+ }
107
+ set(key, entry) {
108
+ const startTime = performance.now();
109
+ this.incrementMetric("sets");
110
+ const ttlMs = entry.metadata.ttl > 0 ? entry.metadata.ttl * 1e3 : void 0;
111
+ this.cache.set(key, entry, { ttl: ttlMs });
112
+ if (entry.embedding && entry.embedding.length > 0) {
113
+ this.vectors.set(key, {
114
+ id: entry.id,
115
+ vector: entry.embedding
116
+ });
117
+ }
118
+ return Promise.resolve({
119
+ success: true,
120
+ id: entry.id,
121
+ durationMs: performance.now() - startTime
122
+ });
123
+ }
124
+ has(key) {
125
+ return Promise.resolve(this.cache.has(key));
126
+ }
127
+ delete(key) {
128
+ this.incrementMetric("deletes");
129
+ const existed = this.cache.has(key);
130
+ this.cache.delete(key);
131
+ this.vectors.delete(key);
132
+ return Promise.resolve(existed);
133
+ }
134
+ clear() {
135
+ this.cache.clear();
136
+ this.vectors.clear();
137
+ return Promise.resolve();
138
+ }
139
+ size() {
140
+ return Promise.resolve(this.cache.size);
141
+ }
142
+ keys() {
143
+ return Promise.resolve(Array.from(this.cache.keys()));
144
+ }
145
+ query(vector, options) {
146
+ const startTime = performance.now();
147
+ const topK = options?.topK ?? 10;
148
+ const minSimilarity = options?.minSimilarity ?? 0;
149
+ const results = [];
150
+ for (const [key, stored] of this.vectors) {
151
+ const entry = this.cache.get(key);
152
+ if (!entry) continue;
153
+ if (options?.namespace && entry.metadata.namespace !== options.namespace) {
154
+ continue;
155
+ }
156
+ const similarity = this.cosineSimilarity(vector, stored.vector);
157
+ if (similarity >= minSimilarity) {
158
+ results.push({ ...entry, score: similarity });
159
+ }
160
+ }
161
+ results.sort((a, b) => b.score - a.score);
162
+ return Promise.resolve({
163
+ entries: results.slice(0, topK),
164
+ durationMs: performance.now() - startTime
165
+ });
166
+ }
167
+ checkHealth() {
168
+ return Promise.resolve({
169
+ healthy: !this.closed,
170
+ latencyMs: 0,
171
+ lastCheck: now(),
172
+ error: this.closed ? "Store is closed" : void 0
173
+ });
174
+ }
175
+ close() {
176
+ this.closed = true;
177
+ this.cache.clear();
178
+ this.vectors.clear();
179
+ return Promise.resolve();
180
+ }
181
+ /**
182
+ * Compute cosine similarity between two vectors
183
+ */
184
+ cosineSimilarity(a, b) {
185
+ if (a.length !== b.length) return 0;
186
+ let dotProduct = 0;
187
+ let normA = 0;
188
+ let normB = 0;
189
+ for (let i = 0; i < a.length; i++) {
190
+ dotProduct += a[i] * b[i];
191
+ normA += a[i] * a[i];
192
+ normB += b[i] * b[i];
193
+ }
194
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
195
+ if (denominator === 0) return 0;
196
+ return dotProduct / denominator;
197
+ }
198
+ /**
199
+ * Get memory usage information
200
+ */
201
+ getMemoryInfo() {
202
+ return {
203
+ entries: this.cache.size,
204
+ calculatedSize: this.cache.calculatedSize ?? 0,
205
+ maxSize: this.memoryConfig.maxSizeBytes ?? 0,
206
+ vectorCount: this.vectors.size
207
+ };
208
+ }
209
+ /**
210
+ * Prune expired entries
211
+ */
212
+ prune() {
213
+ this.cache.purgeStale();
214
+ let pruned = 0;
215
+ for (const key of this.vectors.keys()) {
216
+ if (!this.cache.has(key)) {
217
+ this.vectors.delete(key);
218
+ pruned++;
219
+ }
220
+ }
221
+ return Promise.resolve(pruned);
222
+ }
223
+ };
224
+ function createMemoryCacheStore(config) {
225
+ return new MemoryCacheStore({ type: "memory", ...config });
226
+ }
227
+
228
+ // src/similarity/metrics/SimilarityMetrics.ts
229
+ function cosineSimilarity(a, b) {
230
+ if (a.length !== b.length) return 0;
231
+ let dotProduct = 0;
232
+ let normA = 0;
233
+ let normB = 0;
234
+ for (let i = 0; i < a.length; i++) {
235
+ dotProduct += a[i] * b[i];
236
+ normA += a[i] * a[i];
237
+ normB += b[i] * b[i];
238
+ }
239
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
240
+ if (denominator === 0) return 0;
241
+ return dotProduct / denominator;
242
+ }
243
+
244
+ // src/stores/RedisCacheStore.ts
245
+ var DEFAULT_CONFIG2 = {
246
+ host: "localhost",
247
+ port: 6379,
248
+ db: 0,
249
+ keyPrefix: "llm-cache",
250
+ connectTimeout: 1e4
251
+ };
252
+ var RedisCacheStore = class extends BaseCacheStore {
253
+ storeType = "redis";
254
+ client = null;
255
+ redisConfig;
256
+ connected = false;
257
+ constructor(config) {
258
+ super(config);
259
+ this.redisConfig = { ...DEFAULT_CONFIG2, ...config };
260
+ }
261
+ /**
262
+ * Connect to Redis
263
+ */
264
+ async connect() {
265
+ if (this.connected) return;
266
+ try {
267
+ const { Redis } = await import('ioredis');
268
+ if (this.redisConfig.url) {
269
+ this.client = new Redis(this.redisConfig.url, {
270
+ connectTimeout: this.redisConfig.connectTimeout ?? 1e4,
271
+ lazyConnect: false,
272
+ tls: this.redisConfig.tls ? {} : void 0
273
+ });
274
+ } else {
275
+ this.client = new Redis({
276
+ host: this.redisConfig.host ?? "localhost",
277
+ port: this.redisConfig.port ?? 6379,
278
+ password: this.redisConfig.password,
279
+ db: this.redisConfig.db ?? 0,
280
+ connectTimeout: this.redisConfig.connectTimeout ?? 1e4,
281
+ tls: this.redisConfig.tls ? {} : void 0
282
+ });
283
+ }
284
+ await this.client.ping();
285
+ this.connected = true;
286
+ } catch (error) {
287
+ throw new Error(
288
+ `Failed to connect to Redis: ${error.message}`
289
+ );
290
+ }
291
+ }
292
+ async ensureConnected() {
293
+ if (!this.connected || !this.client) {
294
+ await this.connect();
295
+ }
296
+ if (!this.client) {
297
+ throw new Error("Redis client not initialized");
298
+ }
299
+ return this.client;
300
+ }
301
+ prefixKey(key) {
302
+ const prefix = this.redisConfig.keyPrefix ?? "llm-cache";
303
+ return `${prefix}:${this.namespace}:${key}`;
304
+ }
305
+ async get(key) {
306
+ this.incrementMetric("gets");
307
+ const client = await this.ensureConnected();
308
+ const data = await client.get(this.prefixKey(key));
309
+ if (!data) {
310
+ this.incrementMetric("misses");
311
+ return void 0;
312
+ }
313
+ this.incrementMetric("hits");
314
+ try {
315
+ const entry = JSON.parse(data);
316
+ entry.metadata.accessedAt = now();
317
+ entry.metadata.accessCount++;
318
+ client.set(this.prefixKey(key), JSON.stringify(entry)).catch(() => {
319
+ });
320
+ return entry;
321
+ } catch {
322
+ return void 0;
323
+ }
324
+ }
325
+ async set(key, entry) {
326
+ const startTime = performance.now();
327
+ this.incrementMetric("sets");
328
+ const client = await this.ensureConnected();
329
+ await client.set(this.prefixKey(key), JSON.stringify(entry));
330
+ if (entry.metadata.ttl > 0) {
331
+ await client.expire(this.prefixKey(key), entry.metadata.ttl);
332
+ }
333
+ return {
334
+ success: true,
335
+ id: entry.id,
336
+ durationMs: performance.now() - startTime
337
+ };
338
+ }
339
+ async has(key) {
340
+ const client = await this.ensureConnected();
341
+ return await client.exists(this.prefixKey(key)) > 0;
342
+ }
343
+ async delete(key) {
344
+ this.incrementMetric("deletes");
345
+ const client = await this.ensureConnected();
346
+ return await client.del(this.prefixKey(key)) > 0;
347
+ }
348
+ async clear() {
349
+ const client = await this.ensureConnected();
350
+ const pattern = this.prefixKey("*");
351
+ const keys = await client.keys(pattern);
352
+ if (keys.length > 0) {
353
+ await client.del(...keys);
354
+ }
355
+ }
356
+ async size() {
357
+ const client = await this.ensureConnected();
358
+ const keys = await client.keys(this.prefixKey("*"));
359
+ return keys.length;
360
+ }
361
+ async keys() {
362
+ const client = await this.ensureConnected();
363
+ const keys = await client.keys(this.prefixKey("*"));
364
+ const prefix = this.prefixKey("");
365
+ return keys.map((k) => k.slice(prefix.length));
366
+ }
367
+ async query(vector, options) {
368
+ const startTime = performance.now();
369
+ const allKeys = await this.keys();
370
+ const entries = [];
371
+ const keysToProcess = allKeys.slice(0, 1e3);
372
+ for (const key of keysToProcess) {
373
+ const entry = await this.get(key);
374
+ if (entry?.embedding) {
375
+ const score = cosineSimilarity(vector, entry.embedding);
376
+ if (score >= (options?.minSimilarity ?? 0)) {
377
+ if (options?.namespace && entry.metadata.namespace !== options.namespace) {
378
+ continue;
379
+ }
380
+ entries.push({ ...entry, score });
381
+ }
382
+ }
383
+ }
384
+ entries.sort((a, b) => b.score - a.score);
385
+ return {
386
+ entries: entries.slice(0, options?.topK ?? 10),
387
+ durationMs: performance.now() - startTime
388
+ };
389
+ }
390
+ async checkHealth() {
391
+ const startTime = performance.now();
392
+ try {
393
+ const client = await this.ensureConnected();
394
+ await client.ping();
395
+ return {
396
+ healthy: true,
397
+ latencyMs: performance.now() - startTime,
398
+ lastCheck: now()
399
+ };
400
+ } catch (error) {
401
+ return {
402
+ healthy: false,
403
+ latencyMs: performance.now() - startTime,
404
+ lastCheck: now(),
405
+ error: error.message
406
+ };
407
+ }
408
+ }
409
+ async close() {
410
+ if (this.client) {
411
+ await this.client.quit();
412
+ this.client = null;
413
+ this.connected = false;
414
+ }
415
+ }
416
+ /**
417
+ * Check if connected to Redis
418
+ */
419
+ isConnected() {
420
+ return this.connected;
421
+ }
422
+ };
423
+ function createRedisCacheStore(config) {
424
+ return new RedisCacheStore(config);
425
+ }
426
+
427
+ // src/stores/SQLiteCacheStore.ts
428
+ var DEFAULT_CONFIG3 = {
429
+ dbPath: "cache.db",
430
+ inMemory: false,
431
+ enableVector: false
432
+ };
433
+ var SQLiteCacheStore = class extends BaseCacheStore {
434
+ storeType = "sqlite";
435
+ db = null;
436
+ sqliteConfig;
437
+ initialized = false;
438
+ constructor(config) {
439
+ super(config);
440
+ this.sqliteConfig = { ...DEFAULT_CONFIG3, ...config };
441
+ }
442
+ /**
443
+ * Initialize the database
444
+ */
445
+ async init() {
446
+ if (this.initialized) return;
447
+ try {
448
+ const BetterSqlite3 = (await import('better-sqlite3')).default;
449
+ const db = new BetterSqlite3(
450
+ this.sqliteConfig.inMemory ? ":memory:" : this.sqliteConfig.dbPath ?? "cache.db"
451
+ );
452
+ this.db = db;
453
+ db.exec(`
454
+ CREATE TABLE IF NOT EXISTS cache_entries (
455
+ key TEXT PRIMARY KEY,
456
+ id TEXT NOT NULL,
457
+ data TEXT NOT NULL,
458
+ embedding BLOB,
459
+ model TEXT NOT NULL,
460
+ namespace TEXT,
461
+ created_at INTEGER NOT NULL,
462
+ accessed_at INTEGER NOT NULL,
463
+ ttl INTEGER DEFAULT 0
464
+ );
465
+
466
+ CREATE INDEX IF NOT EXISTS idx_namespace ON cache_entries(namespace);
467
+ CREATE INDEX IF NOT EXISTS idx_model ON cache_entries(model);
468
+ CREATE INDEX IF NOT EXISTS idx_created_at ON cache_entries(created_at);
469
+ CREATE INDEX IF NOT EXISTS idx_accessed_at ON cache_entries(accessed_at);
470
+ `);
471
+ this.initialized = true;
472
+ } catch (error) {
473
+ throw new Error(
474
+ `Failed to initialize SQLite database: ${error.message}`
475
+ );
476
+ }
477
+ }
478
+ ensureInitialized() {
479
+ if (!this.initialized || !this.db) {
480
+ throw new Error("SQLite store not initialized. Call init() first.");
481
+ }
482
+ return this.db;
483
+ }
484
+ get(key) {
485
+ this.incrementMetric("gets");
486
+ const db = this.ensureInitialized();
487
+ const row = db.prepare("SELECT data FROM cache_entries WHERE key = ?").get(key);
488
+ if (!row) {
489
+ this.incrementMetric("misses");
490
+ return Promise.resolve(void 0);
491
+ }
492
+ this.incrementMetric("hits");
493
+ try {
494
+ const entry = JSON.parse(row.data);
495
+ entry.metadata.accessedAt = now();
496
+ entry.metadata.accessCount++;
497
+ db.prepare(
498
+ "UPDATE cache_entries SET accessed_at = ?, data = ? WHERE key = ?"
499
+ ).run(now(), JSON.stringify(entry), key);
500
+ return Promise.resolve(entry);
501
+ } catch {
502
+ return Promise.resolve(void 0);
503
+ }
504
+ }
505
+ set(key, entry) {
506
+ const startTime = performance.now();
507
+ this.incrementMetric("sets");
508
+ const db = this.ensureInitialized();
509
+ const embedding = entry.embedding ? Buffer.from(new Float32Array(entry.embedding).buffer) : null;
510
+ db.prepare(
511
+ `
512
+ INSERT OR REPLACE INTO cache_entries
513
+ (key, id, data, embedding, model, namespace, created_at, accessed_at, ttl)
514
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
515
+ `
516
+ ).run(
517
+ key,
518
+ entry.id,
519
+ JSON.stringify(entry),
520
+ embedding,
521
+ entry.request.model,
522
+ entry.metadata.namespace ?? null,
523
+ entry.metadata.createdAt,
524
+ entry.metadata.accessedAt,
525
+ entry.metadata.ttl
526
+ );
527
+ return Promise.resolve({
528
+ success: true,
529
+ id: entry.id,
530
+ durationMs: performance.now() - startTime
531
+ });
532
+ }
533
+ has(key) {
534
+ const db = this.ensureInitialized();
535
+ const row = db.prepare("SELECT 1 FROM cache_entries WHERE key = ?").get(key);
536
+ return Promise.resolve(!!row);
537
+ }
538
+ delete(key) {
539
+ this.incrementMetric("deletes");
540
+ const db = this.ensureInitialized();
541
+ const result = db.prepare("DELETE FROM cache_entries WHERE key = ?").run(key);
542
+ return Promise.resolve(result.changes > 0);
543
+ }
544
+ clear() {
545
+ const db = this.ensureInitialized();
546
+ if (this.namespace === "default") {
547
+ db.prepare("DELETE FROM cache_entries").run();
548
+ } else {
549
+ db.prepare("DELETE FROM cache_entries WHERE namespace = ?").run(
550
+ this.namespace
551
+ );
552
+ }
553
+ return Promise.resolve();
554
+ }
555
+ size() {
556
+ const db = this.ensureInitialized();
557
+ const row = db.prepare("SELECT COUNT(*) as count FROM cache_entries").get();
558
+ return Promise.resolve(row.count);
559
+ }
560
+ keys() {
561
+ const db = this.ensureInitialized();
562
+ const rows = db.prepare("SELECT key FROM cache_entries").all();
563
+ return Promise.resolve(rows.map((r) => r.key));
564
+ }
565
+ query(vector, options) {
566
+ const startTime = performance.now();
567
+ const db = this.ensureInitialized();
568
+ let sql = `
569
+ SELECT key, data, embedding FROM cache_entries
570
+ WHERE embedding IS NOT NULL
571
+ `;
572
+ const params = [];
573
+ if (options?.namespace) {
574
+ sql += " AND namespace = ?";
575
+ params.push(options.namespace);
576
+ }
577
+ const rows = db.prepare(sql).all(...params);
578
+ const results = [];
579
+ for (const row of rows) {
580
+ const stored = new Float32Array(
581
+ row.embedding.buffer,
582
+ row.embedding.byteOffset,
583
+ row.embedding.length / 4
584
+ );
585
+ const similarity = cosineSimilarity(vector, Array.from(stored));
586
+ if (similarity >= (options?.minSimilarity ?? 0)) {
587
+ try {
588
+ const entry = JSON.parse(row.data);
589
+ results.push({ ...entry, score: similarity });
590
+ } catch {
591
+ }
592
+ }
593
+ }
594
+ results.sort((a, b) => b.score - a.score);
595
+ return Promise.resolve({
596
+ entries: results.slice(0, options?.topK ?? 10),
597
+ durationMs: performance.now() - startTime
598
+ });
599
+ }
600
+ checkHealth() {
601
+ const startTime = performance.now();
602
+ try {
603
+ this.ensureInitialized();
604
+ return Promise.resolve({
605
+ healthy: true,
606
+ latencyMs: performance.now() - startTime,
607
+ lastCheck: now()
608
+ });
609
+ } catch (error) {
610
+ return Promise.resolve({
611
+ healthy: false,
612
+ latencyMs: performance.now() - startTime,
613
+ lastCheck: now(),
614
+ error: error.message
615
+ });
616
+ }
617
+ }
618
+ close() {
619
+ if (this.db) {
620
+ this.db.close();
621
+ this.db = null;
622
+ this.initialized = false;
623
+ }
624
+ return Promise.resolve();
625
+ }
626
+ /**
627
+ * Prune expired entries
628
+ */
629
+ pruneExpired() {
630
+ const db = this.ensureInitialized();
631
+ const currentTime = now();
632
+ const result = db.prepare(
633
+ `
634
+ DELETE FROM cache_entries
635
+ WHERE ttl > 0 AND (created_at + (ttl * 1000)) < ?
636
+ `
637
+ ).run(currentTime);
638
+ return Promise.resolve(result.changes);
639
+ }
640
+ /**
641
+ * Get database file size (for non-memory databases)
642
+ */
643
+ async getDbSize() {
644
+ if (this.sqliteConfig.inMemory) return null;
645
+ try {
646
+ const { statSync } = await import('fs');
647
+ const stats = statSync(this.sqliteConfig.dbPath ?? "cache.db");
648
+ return stats.size;
649
+ } catch {
650
+ return null;
651
+ }
652
+ }
653
+ /**
654
+ * Check if database is initialized
655
+ */
656
+ isInitialized() {
657
+ return this.initialized;
658
+ }
659
+ };
660
+ function createSQLiteCacheStore(config) {
661
+ return new SQLiteCacheStore(config);
662
+ }
663
+
664
+ // src/stores/TieredCacheStore.ts
665
+ var TieredCacheStore = class extends BaseCacheStore {
666
+ storeType = "tiered";
667
+ tiers;
668
+ accessCounts = /* @__PURE__ */ new Map();
669
+ constructor(config) {
670
+ super(config);
671
+ const validTiers = config.tiers.filter(
672
+ (t) => t.store !== void 0
673
+ );
674
+ if (validTiers.length === 0) {
675
+ throw new Error(
676
+ "TieredCacheStore requires at least one tier with a store"
677
+ );
678
+ }
679
+ this.tiers = validTiers.sort((a, b) => a.priority - b.priority);
680
+ }
681
+ async get(key) {
682
+ this.incrementMetric("gets");
683
+ for (let i = 0; i < this.tiers.length; i++) {
684
+ const tier = this.tiers[i];
685
+ const entry = await tier.store.get(key);
686
+ if (entry) {
687
+ this.incrementMetric("hits");
688
+ const accessCount = (this.accessCounts.get(key) ?? 0) + 1;
689
+ this.accessCounts.set(key, accessCount);
690
+ if (i > 0) {
691
+ await this.checkPromotion(key, entry, i, accessCount);
692
+ }
693
+ return entry;
694
+ }
695
+ }
696
+ this.incrementMetric("misses");
697
+ return void 0;
698
+ }
699
+ async set(key, entry) {
700
+ const startTime = performance.now();
701
+ this.incrementMetric("sets");
702
+ const result = await this.tiers[0].store.set(key, entry);
703
+ this.accessCounts.set(key, 0);
704
+ await this.checkDemotion(0);
705
+ return {
706
+ ...result,
707
+ durationMs: performance.now() - startTime
708
+ };
709
+ }
710
+ async has(key) {
711
+ for (const tier of this.tiers) {
712
+ if (await tier.store.has(key)) {
713
+ return true;
714
+ }
715
+ }
716
+ return false;
717
+ }
718
+ async delete(key) {
719
+ this.incrementMetric("deletes");
720
+ let deleted = false;
721
+ for (const tier of this.tiers) {
722
+ if (await tier.store.delete(key)) {
723
+ deleted = true;
724
+ }
725
+ }
726
+ this.accessCounts.delete(key);
727
+ return deleted;
728
+ }
729
+ async clear() {
730
+ for (const tier of this.tiers) {
731
+ await tier.store.clear();
732
+ }
733
+ this.accessCounts.clear();
734
+ }
735
+ async size() {
736
+ const allKeys = /* @__PURE__ */ new Set();
737
+ for (const tier of this.tiers) {
738
+ const keys = await tier.store.keys();
739
+ keys.forEach((k) => allKeys.add(k));
740
+ }
741
+ return allKeys.size;
742
+ }
743
+ async keys() {
744
+ const allKeys = /* @__PURE__ */ new Set();
745
+ for (const tier of this.tiers) {
746
+ const keys = await tier.store.keys();
747
+ keys.forEach((k) => allKeys.add(k));
748
+ }
749
+ return Array.from(allKeys);
750
+ }
751
+ async query(vector, options) {
752
+ const startTime = performance.now();
753
+ const entriesMap = /* @__PURE__ */ new Map();
754
+ for (const tier of this.tiers) {
755
+ const result = await tier.store.query(vector, options);
756
+ for (const entry of result.entries) {
757
+ const existing = entriesMap.get(entry.key);
758
+ if (!existing || entry.score > existing.score) {
759
+ entriesMap.set(entry.key, entry);
760
+ }
761
+ }
762
+ }
763
+ const entries = Array.from(entriesMap.values()).sort((a, b) => b.score - a.score).slice(0, options?.topK ?? 10);
764
+ return {
765
+ entries,
766
+ durationMs: performance.now() - startTime
767
+ };
768
+ }
769
+ async checkHealth() {
770
+ const startTime = performance.now();
771
+ const tierHealths = [];
772
+ for (const tier of this.tiers) {
773
+ const health = await tier.store.checkHealth();
774
+ tierHealths.push({ name: tier.name, healthy: health.healthy });
775
+ }
776
+ const allHealthy = tierHealths.every((t) => t.healthy);
777
+ return {
778
+ healthy: allHealthy,
779
+ latencyMs: performance.now() - startTime,
780
+ lastCheck: now(),
781
+ error: allHealthy ? void 0 : `Unhealthy tiers: ${tierHealths.filter((t) => !t.healthy).map((t) => t.name).join(", ")}`
782
+ };
783
+ }
784
+ async close() {
785
+ for (const tier of this.tiers) {
786
+ await tier.store.close();
787
+ }
788
+ }
789
+ /**
790
+ * Get tier statistics
791
+ */
792
+ async getTierStats() {
793
+ const stats = [];
794
+ for (const tier of this.tiers) {
795
+ stats.push({
796
+ name: tier.name,
797
+ priority: tier.priority,
798
+ size: await tier.store.size(),
799
+ maxSize: tier.maxSize
800
+ });
801
+ }
802
+ return stats;
803
+ }
804
+ /**
805
+ * Manually promote an entry to a higher tier
806
+ */
807
+ async promote(key, targetTierIndex = 0) {
808
+ for (let i = targetTierIndex + 1; i < this.tiers.length; i++) {
809
+ const entry = await this.tiers[i].store.get(key);
810
+ if (entry) {
811
+ await this.tiers[targetTierIndex].store.set(key, entry);
812
+ await this.tiers[i].store.delete(key);
813
+ return true;
814
+ }
815
+ }
816
+ return false;
817
+ }
818
+ /**
819
+ * Manually demote an entry to a lower tier
820
+ */
821
+ async demote(key, targetTierIndex) {
822
+ for (let i = 0; i < this.tiers.length - 1; i++) {
823
+ const entry = await this.tiers[i].store.get(key);
824
+ if (entry) {
825
+ const target = targetTierIndex ?? i + 1;
826
+ if (target >= this.tiers.length) return false;
827
+ await this.tiers[target].store.set(key, entry);
828
+ await this.tiers[i].store.delete(key);
829
+ return true;
830
+ }
831
+ }
832
+ return false;
833
+ }
834
+ async checkPromotion(key, entry, currentTierIndex, accessCount) {
835
+ for (let i = currentTierIndex - 1; i >= 0; i--) {
836
+ const tier = this.tiers[i];
837
+ const threshold = tier.promotionThreshold ?? 3;
838
+ if (accessCount >= threshold) {
839
+ await tier.store.set(key, entry);
840
+ await this.tiers[currentTierIndex].store.delete(key);
841
+ break;
842
+ }
843
+ }
844
+ }
845
+ async checkDemotion(tierIndex) {
846
+ const tier = this.tiers[tierIndex];
847
+ if (!tier.maxSize) return;
848
+ const size = await tier.store.size();
849
+ if (size <= tier.maxSize) return;
850
+ const demotionTarget = tier.demotionTarget ?? 0.9;
851
+ const targetSize = Math.floor(tier.maxSize * demotionTarget);
852
+ const toRemove = size - targetSize;
853
+ if (toRemove <= 0) return;
854
+ const keys = await tier.store.keys();
855
+ const keysByAccess = keys.map((k) => ({ key: k, count: this.accessCounts.get(k) ?? 0 })).sort((a, b) => a.count - b.count);
856
+ const nextTierIndex = tierIndex + 1;
857
+ if (nextTierIndex >= this.tiers.length) {
858
+ for (let i = 0; i < toRemove && i < keysByAccess.length; i++) {
859
+ await tier.store.delete(keysByAccess[i].key);
860
+ this.accessCounts.delete(keysByAccess[i].key);
861
+ }
862
+ } else {
863
+ for (let i = 0; i < toRemove && i < keysByAccess.length; i++) {
864
+ const key = keysByAccess[i].key;
865
+ const entry = await tier.store.get(key);
866
+ if (entry) {
867
+ await this.tiers[nextTierIndex].store.set(key, entry);
868
+ await tier.store.delete(key);
869
+ }
870
+ }
871
+ }
872
+ }
873
+ };
874
+ function createTieredCacheStore(config) {
875
+ return new TieredCacheStore(config);
876
+ }
877
+
878
+ // src/stores/PineconeCacheStore.ts
879
+ var PineconeCacheStore = class extends BaseCacheStore {
880
+ storeType = "pinecone";
881
+ client = null;
882
+ index = null;
883
+ ns = null;
884
+ pineconeConfig;
885
+ connected = false;
886
+ constructor(config) {
887
+ super(config);
888
+ this.pineconeConfig = config;
889
+ }
890
+ /**
891
+ * Connect to Pinecone
892
+ */
893
+ async connect() {
894
+ if (this.connected) return;
895
+ try {
896
+ const { Pinecone } = await import('@pinecone-database/pinecone');
897
+ this.client = new Pinecone({
898
+ apiKey: this.pineconeConfig.apiKey
899
+ });
900
+ this.index = this.client.Index(this.pineconeConfig.index);
901
+ this.ns = this.index.namespace(this.namespace);
902
+ this.connected = true;
903
+ } catch (error) {
904
+ throw new Error(
905
+ `Failed to connect to Pinecone: ${error.message}`
906
+ );
907
+ }
908
+ }
909
+ async ensureConnected() {
910
+ if (!this.connected || !this.ns) {
911
+ await this.connect();
912
+ }
913
+ if (!this.ns) {
914
+ throw new Error("Pinecone namespace not initialized");
915
+ }
916
+ return this.ns;
917
+ }
918
+ async get(key) {
919
+ this.incrementMetric("gets");
920
+ const ns = await this.ensureConnected();
921
+ try {
922
+ const result = await ns.fetch([key]);
923
+ if (!result.records[key]) {
924
+ this.incrementMetric("misses");
925
+ return void 0;
926
+ }
927
+ this.incrementMetric("hits");
928
+ const record = result.records[key];
929
+ const metadata = record.metadata;
930
+ const entry = JSON.parse(metadata.entryData);
931
+ entry.metadata.accessedAt = now();
932
+ entry.metadata.accessCount++;
933
+ this.updateAccessMetadata(key, record.values, metadata).catch(() => {
934
+ });
935
+ return entry;
936
+ } catch {
937
+ this.incrementMetric("misses");
938
+ return void 0;
939
+ }
940
+ }
941
+ async updateAccessMetadata(key, values, metadata) {
942
+ const ns = await this.ensureConnected();
943
+ const updatedEntry = JSON.parse(metadata.entryData);
944
+ updatedEntry.metadata.accessedAt = now();
945
+ updatedEntry.metadata.accessCount++;
946
+ await ns.upsert([
947
+ {
948
+ id: key,
949
+ values,
950
+ metadata: {
951
+ ...metadata,
952
+ accessedAt: now(),
953
+ accessCount: metadata.accessCount + 1,
954
+ entryData: JSON.stringify(updatedEntry)
955
+ }
956
+ }
957
+ ]);
958
+ }
959
+ async set(key, entry) {
960
+ const startTime = performance.now();
961
+ this.incrementMetric("sets");
962
+ const ns = await this.ensureConnected();
963
+ if (!entry.embedding || entry.embedding.length === 0) {
964
+ return {
965
+ success: false,
966
+ id: entry.id,
967
+ durationMs: performance.now() - startTime
968
+ };
969
+ }
970
+ const metadata = {
971
+ key,
972
+ model: entry.request.model,
973
+ content: entry.response.content.substring(0, 3e4),
974
+ // Pinecone metadata limit
975
+ createdAt: entry.metadata.createdAt,
976
+ accessedAt: entry.metadata.accessedAt,
977
+ accessCount: entry.metadata.accessCount,
978
+ hitCount: entry.metadata.hitCount,
979
+ ttl: entry.metadata.ttl,
980
+ namespace: entry.metadata.namespace ?? this.namespace,
981
+ tags: entry.metadata.tags ?? [],
982
+ entryData: JSON.stringify(entry)
983
+ };
984
+ try {
985
+ await ns.upsert([
986
+ {
987
+ id: key,
988
+ values: entry.embedding,
989
+ metadata
990
+ }
991
+ ]);
992
+ return {
993
+ success: true,
994
+ id: entry.id,
995
+ durationMs: performance.now() - startTime
996
+ };
997
+ } catch (error) {
998
+ return {
999
+ success: false,
1000
+ id: entry.id,
1001
+ durationMs: performance.now() - startTime
1002
+ };
1003
+ }
1004
+ }
1005
+ async has(key) {
1006
+ const ns = await this.ensureConnected();
1007
+ try {
1008
+ const result = await ns.fetch([key]);
1009
+ return !!result.records[key];
1010
+ } catch {
1011
+ return false;
1012
+ }
1013
+ }
1014
+ async delete(key) {
1015
+ this.incrementMetric("deletes");
1016
+ const ns = await this.ensureConnected();
1017
+ try {
1018
+ await ns.deleteOne(key);
1019
+ return true;
1020
+ } catch {
1021
+ return false;
1022
+ }
1023
+ }
1024
+ async clear() {
1025
+ const ns = await this.ensureConnected();
1026
+ await ns.deleteAll();
1027
+ }
1028
+ async size() {
1029
+ if (!this.index) {
1030
+ await this.connect();
1031
+ }
1032
+ try {
1033
+ const stats = await this.index.describeIndexStats();
1034
+ return stats.namespaces[this.namespace]?.recordCount ?? 0;
1035
+ } catch {
1036
+ return 0;
1037
+ }
1038
+ }
1039
+ async keys() {
1040
+ const ns = await this.ensureConnected();
1041
+ try {
1042
+ const result = await ns.listPaginated({ limit: 1e4 });
1043
+ return result.vectors.map((v) => v.id);
1044
+ } catch {
1045
+ return [];
1046
+ }
1047
+ }
1048
+ async query(vector, options) {
1049
+ const startTime = performance.now();
1050
+ const ns = await this.ensureConnected();
1051
+ const queryOptions = {
1052
+ vector,
1053
+ topK: options?.topK ?? 10,
1054
+ includeMetadata: true,
1055
+ includeValues: options?.includeEmbedding ?? false
1056
+ };
1057
+ if (options?.filter) {
1058
+ queryOptions.filter = options.filter;
1059
+ }
1060
+ try {
1061
+ const result = await ns.query(queryOptions);
1062
+ const entries = [];
1063
+ for (const match of result.matches) {
1064
+ if (options?.minSimilarity && match.score < options.minSimilarity) {
1065
+ continue;
1066
+ }
1067
+ const metadata = match.metadata;
1068
+ if (metadata?.entryData) {
1069
+ try {
1070
+ const entry = JSON.parse(metadata.entryData);
1071
+ if (options?.includeEmbedding && match.values) {
1072
+ entry.embedding = match.values;
1073
+ }
1074
+ entries.push({
1075
+ ...entry,
1076
+ score: match.score
1077
+ });
1078
+ } catch {
1079
+ }
1080
+ }
1081
+ }
1082
+ return {
1083
+ entries,
1084
+ durationMs: performance.now() - startTime
1085
+ };
1086
+ } catch (error) {
1087
+ return {
1088
+ entries: [],
1089
+ durationMs: performance.now() - startTime
1090
+ };
1091
+ }
1092
+ }
1093
+ async checkHealth() {
1094
+ const startTime = performance.now();
1095
+ try {
1096
+ if (!this.index) {
1097
+ await this.connect();
1098
+ }
1099
+ await this.index.describeIndexStats();
1100
+ return {
1101
+ healthy: true,
1102
+ latencyMs: performance.now() - startTime,
1103
+ lastCheck: now()
1104
+ };
1105
+ } catch (error) {
1106
+ return {
1107
+ healthy: false,
1108
+ latencyMs: performance.now() - startTime,
1109
+ lastCheck: now(),
1110
+ error: error.message
1111
+ };
1112
+ }
1113
+ }
1114
+ close() {
1115
+ this.client = null;
1116
+ this.index = null;
1117
+ this.ns = null;
1118
+ this.connected = false;
1119
+ return Promise.resolve();
1120
+ }
1121
+ /**
1122
+ * Check if connected to Pinecone
1123
+ */
1124
+ isConnected() {
1125
+ return this.connected;
1126
+ }
1127
+ /**
1128
+ * Get index stats
1129
+ */
1130
+ async getIndexStats() {
1131
+ if (!this.index) {
1132
+ await this.connect();
1133
+ }
1134
+ try {
1135
+ return await this.index.describeIndexStats();
1136
+ } catch {
1137
+ return null;
1138
+ }
1139
+ }
1140
+ };
1141
+ function createPineconeCacheStore(config) {
1142
+ return new PineconeCacheStore(config);
1143
+ }
1144
+
1145
+ export { BaseCacheStore, MemoryCacheStore, PineconeCacheStore, RedisCacheStore, SQLiteCacheStore, TieredCacheStore, createMemoryCacheStore, createPineconeCacheStore, createRedisCacheStore, createSQLiteCacheStore, createTieredCacheStore };
1146
+ //# sourceMappingURL=index.js.map
1147
+ //# sourceMappingURL=index.js.map