aether-core 0.1.0

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 ADDED
@@ -0,0 +1,1071 @@
1
+ // src/retrieval/strategies/semantic.strategy.ts
2
+ var SemanticStrategy = class {
3
+ type = "semantic";
4
+ async retrieve(query, context, options) {
5
+ const queryEmbedding = await context.embeddings.embed(query);
6
+ const filters = {};
7
+ if (options.actor) {
8
+ filters["actor"] = options.actor;
9
+ }
10
+ if (options.contextFilters) {
11
+ Object.assign(filters, options.contextFilters);
12
+ }
13
+ const results = await context.storage.searchByVector(queryEmbedding, {
14
+ limit: options.limit ?? 10,
15
+ threshold: options.threshold ?? 0.5,
16
+ filters: Object.keys(filters).length > 0 ? filters : void 0
17
+ });
18
+ return results;
19
+ }
20
+ };
21
+
22
+ // src/retrieval/strategies/temporal.strategy.ts
23
+ var TemporalStrategy = class {
24
+ type = "temporal";
25
+ /** Decay rate for recency scoring (higher = faster decay) */
26
+ decayRate;
27
+ constructor(decayRate = 1e-4) {
28
+ this.decayRate = decayRate;
29
+ }
30
+ async retrieve(_query, context, options) {
31
+ if (!options.actor) {
32
+ return [];
33
+ }
34
+ const events = await context.storage.queryByActor(options.actor, {
35
+ limit: options.limit ?? 20,
36
+ order: "desc"
37
+ // Most recent first
38
+ });
39
+ const now = Date.now();
40
+ const scored = events.map((event) => {
41
+ const eventTime = new Date(event.timestamp).getTime();
42
+ const age = now - eventTime;
43
+ const score = Math.exp(-this.decayRate * age);
44
+ return { ...event, score };
45
+ });
46
+ return scored;
47
+ }
48
+ };
49
+
50
+ // src/retrieval/strategies/relational.strategy.ts
51
+ var DEFAULT_ENTITY_PATTERNS = [
52
+ { key: "cartId", pattern: /cart[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
53
+ { key: "orderId", pattern: /order[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
54
+ { key: "userId", pattern: /user[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
55
+ { key: "productId", pattern: /product[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
56
+ { key: "sessionId", pattern: /session[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
57
+ { key: "transactionId", pattern: /transaction[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
58
+ { key: "customerId", pattern: /customer[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
59
+ { key: "invoiceId", pattern: /invoice[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
60
+ { key: "ticketId", pattern: /ticket[_\s-]?id[:\s=]+["']?(\w+)["']?/i },
61
+ { key: "email", pattern: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i }
62
+ ];
63
+ var PatternEntityExtractor = class {
64
+ patterns;
65
+ constructor(patterns) {
66
+ this.patterns = patterns ?? DEFAULT_ENTITY_PATTERNS;
67
+ }
68
+ /**
69
+ * Add a new pattern.
70
+ */
71
+ addPattern(key, pattern) {
72
+ this.patterns.push({ key, pattern });
73
+ }
74
+ /**
75
+ * Add multiple patterns at once.
76
+ */
77
+ addPatterns(patterns) {
78
+ this.patterns.push(...patterns);
79
+ }
80
+ /**
81
+ * Remove a pattern by key.
82
+ */
83
+ removePattern(key) {
84
+ this.patterns = this.patterns.filter((p) => p.key !== key);
85
+ }
86
+ /**
87
+ * Clear all patterns.
88
+ */
89
+ clearPatterns() {
90
+ this.patterns = [];
91
+ }
92
+ /**
93
+ * Get current patterns.
94
+ */
95
+ getPatterns() {
96
+ return [...this.patterns];
97
+ }
98
+ extract(query) {
99
+ const extracted = {};
100
+ for (const { key, pattern } of this.patterns) {
101
+ const match = query.match(pattern);
102
+ if (match?.[1]) {
103
+ extracted[key] = match[1];
104
+ }
105
+ }
106
+ return extracted;
107
+ }
108
+ };
109
+ var CompositeEntityExtractor = class {
110
+ extractors;
111
+ constructor(extractors = []) {
112
+ this.extractors = extractors;
113
+ }
114
+ addExtractor(extractor) {
115
+ this.extractors.push(extractor);
116
+ }
117
+ async extract(query) {
118
+ const results = {};
119
+ for (const extractor of this.extractors) {
120
+ const extracted = await extractor.extract(query);
121
+ Object.assign(results, extracted);
122
+ }
123
+ return results;
124
+ }
125
+ };
126
+ var RelationalStrategy = class {
127
+ type = "relational";
128
+ entityExtractor;
129
+ constructor(config) {
130
+ if (config?.entityExtractor) {
131
+ this.entityExtractor = config.entityExtractor;
132
+ } else {
133
+ let patterns;
134
+ if (config?.patterns) {
135
+ if (config.includeDefaultPatterns) {
136
+ patterns = [...DEFAULT_ENTITY_PATTERNS, ...config.patterns];
137
+ } else {
138
+ patterns = config.patterns;
139
+ }
140
+ } else {
141
+ patterns = DEFAULT_ENTITY_PATTERNS;
142
+ }
143
+ this.entityExtractor = new PatternEntityExtractor(patterns);
144
+ }
145
+ }
146
+ /**
147
+ * Get the entity extractor (for testing or modification).
148
+ */
149
+ getEntityExtractor() {
150
+ return this.entityExtractor;
151
+ }
152
+ async retrieve(query, context, options) {
153
+ const filters = await this.buildFilters(query, options.contextFilters);
154
+ if (Object.keys(filters).length === 0) {
155
+ return [];
156
+ }
157
+ const events = await context.storage.queryByContext(filters, {
158
+ limit: options.limit ?? 20
159
+ });
160
+ const totalFilters = Object.keys(filters).length;
161
+ const scored = events.map((event) => {
162
+ let matchCount = 0;
163
+ for (const [key, value] of Object.entries(filters)) {
164
+ if (event.context[key] === value) {
165
+ matchCount++;
166
+ }
167
+ }
168
+ const score = matchCount / totalFilters;
169
+ return { ...event, score };
170
+ });
171
+ scored.sort((a, b) => b.score - a.score);
172
+ return scored;
173
+ }
174
+ /**
175
+ * Build filters from explicit context filters or by extracting entities from query.
176
+ */
177
+ async buildFilters(query, contextFilters) {
178
+ if (contextFilters && Object.keys(contextFilters).length > 0) {
179
+ return contextFilters;
180
+ }
181
+ return this.entityExtractor.extract(query);
182
+ }
183
+ };
184
+
185
+ // src/retrieval/strategies/hybrid.strategy.ts
186
+ var DEFAULT_WEIGHTS = {
187
+ semantic: 0.5,
188
+ temporal: 0.3,
189
+ relational: 0.2
190
+ };
191
+ var HybridStrategy = class {
192
+ type = "hybrid";
193
+ semanticStrategy;
194
+ temporalStrategy;
195
+ relationalStrategy;
196
+ weights;
197
+ constructor(weights) {
198
+ this.semanticStrategy = new SemanticStrategy();
199
+ this.temporalStrategy = new TemporalStrategy();
200
+ this.relationalStrategy = new RelationalStrategy();
201
+ this.weights = { ...DEFAULT_WEIGHTS, ...weights };
202
+ const total = this.weights.semantic + this.weights.temporal + this.weights.relational;
203
+ if (total > 0) {
204
+ this.weights.semantic /= total;
205
+ this.weights.temporal /= total;
206
+ this.weights.relational /= total;
207
+ }
208
+ }
209
+ async retrieve(query, context, options) {
210
+ const [semanticResults, temporalResults, relationalResults] = await Promise.all([
211
+ this.semanticStrategy.retrieve(query, context, options),
212
+ // Only run temporal if actor is specified
213
+ options.actor ? this.temporalStrategy.retrieve(query, context, options) : Promise.resolve([]),
214
+ this.relationalStrategy.retrieve(query, context, options)
215
+ ]);
216
+ const combinedMap = /* @__PURE__ */ new Map();
217
+ for (const event of semanticResults) {
218
+ const weightedScore = event.score * this.weights.semantic;
219
+ combinedMap.set(event.id, { ...event, score: weightedScore });
220
+ }
221
+ for (const event of temporalResults) {
222
+ const weightedScore = event.score * this.weights.temporal;
223
+ const existing = combinedMap.get(event.id);
224
+ if (existing) {
225
+ existing.score += weightedScore;
226
+ } else {
227
+ combinedMap.set(event.id, { ...event, score: weightedScore });
228
+ }
229
+ }
230
+ for (const event of relationalResults) {
231
+ const weightedScore = event.score * this.weights.relational;
232
+ const existing = combinedMap.get(event.id);
233
+ if (existing) {
234
+ existing.score += weightedScore;
235
+ } else {
236
+ combinedMap.set(event.id, { ...event, score: weightedScore });
237
+ }
238
+ }
239
+ const results = Array.from(combinedMap.values()).sort((a, b) => b.score - a.score).slice(0, options.limit ?? 10);
240
+ return results;
241
+ }
242
+ };
243
+
244
+ // src/retrieval/retrieval-engine.ts
245
+ var RetrievalEngine = class {
246
+ storage;
247
+ embeddings;
248
+ config;
249
+ strategies;
250
+ constructor(storage, embeddings, config = {}) {
251
+ this.storage = storage;
252
+ this.embeddings = embeddings;
253
+ this.config = {
254
+ defaultLimit: config.defaultLimit ?? 10,
255
+ defaultType: config.defaultType ?? "hybrid",
256
+ hybridWeights: config.hybridWeights ?? {
257
+ semantic: 0.5,
258
+ temporal: 0.3,
259
+ relational: 0.2
260
+ },
261
+ ...config
262
+ };
263
+ this.strategies = /* @__PURE__ */ new Map();
264
+ this.strategies.set("semantic", new SemanticStrategy());
265
+ this.strategies.set("temporal", new TemporalStrategy());
266
+ this.strategies.set("relational", new RelationalStrategy());
267
+ this.strategies.set("hybrid", new HybridStrategy(this.config.hybridWeights));
268
+ }
269
+ /**
270
+ * Retrieve relevant memories based on a query.
271
+ * @param query The search query
272
+ * @param options Retrieval options
273
+ * @returns Array of relevant memory events
274
+ */
275
+ async retrieve(query, options = {}) {
276
+ const type = options.retrievalType ?? this.config.defaultType ?? "hybrid";
277
+ const limit = options.limit ?? this.config.defaultLimit ?? 10;
278
+ const strategy = this.strategies.get(type);
279
+ if (!strategy) {
280
+ throw new Error(`Unknown retrieval type: ${type}`);
281
+ }
282
+ const context = {
283
+ storage: this.storage,
284
+ embeddings: this.embeddings
285
+ };
286
+ const mergedOptions = {
287
+ ...options,
288
+ limit
289
+ };
290
+ const results = await strategy.retrieve(query, context, mergedOptions);
291
+ if (this.config.reranker?.enabled) {
292
+ return this.rerank(query, results, this.config.reranker.topK ?? limit);
293
+ }
294
+ if (!options.includeEmbeddings) {
295
+ return results.map(({ score: _, embedding: __, ...event }) => event);
296
+ }
297
+ return results.map(({ score: _, ...event }) => event);
298
+ }
299
+ /**
300
+ * Get a specific retrieval strategy.
301
+ */
302
+ getStrategy(type) {
303
+ return this.strategies.get(type);
304
+ }
305
+ /**
306
+ * Register a custom retrieval strategy.
307
+ */
308
+ registerStrategy(type, strategy) {
309
+ this.strategies.set(type, strategy);
310
+ }
311
+ /**
312
+ * Rerank results (placeholder - can be extended with cross-encoder models).
313
+ */
314
+ async rerank(_query, results, topK) {
315
+ return results.sort((a, b) => b.score - a.score).slice(0, topK).map(({ score: _, ...event }) => event);
316
+ }
317
+ };
318
+
319
+ // src/utils/id-generator.ts
320
+ import { ulid } from "ulid";
321
+ function generateId() {
322
+ return ulid();
323
+ }
324
+ function getTimestampFromId(id) {
325
+ const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
326
+ const TIME_LEN = 10;
327
+ let time = 0;
328
+ for (let i = 0; i < TIME_LEN; i++) {
329
+ const char = id.charAt(i).toUpperCase();
330
+ const index = ENCODING.indexOf(char);
331
+ if (index === -1) {
332
+ throw new Error(`Invalid ULID character: ${char}`);
333
+ }
334
+ time = time * 32 + index;
335
+ }
336
+ return time;
337
+ }
338
+
339
+ // src/aether.ts
340
+ var Aether = class {
341
+ storage;
342
+ embeddings;
343
+ retrieval;
344
+ config;
345
+ listeners = /* @__PURE__ */ new Set();
346
+ initialized = false;
347
+ constructor(config) {
348
+ this.config = config;
349
+ this.storage = config.storage.adapter;
350
+ this.embeddings = config.embeddings.provider;
351
+ this.retrieval = new RetrievalEngine(this.storage, this.embeddings, config.retrieval);
352
+ }
353
+ /**
354
+ * Initialize the memory system.
355
+ * Called automatically on first operation if not called explicitly.
356
+ */
357
+ async initialize() {
358
+ if (this.initialized) return;
359
+ await this.storage.initialize();
360
+ this.initialized = true;
361
+ }
362
+ async add(contentOrEvent, context) {
363
+ await this.initialize();
364
+ const input = typeof contentOrEvent === "string" ? { content: contentOrEvent, context } : contentOrEvent;
365
+ const embedding = await this.embeddings.embed(input.content);
366
+ const event = {
367
+ id: generateId(),
368
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
369
+ actor: input.actor ?? "unknown",
370
+ action: input.action ?? this.inferAction(input.content),
371
+ content: input.content,
372
+ context: input.context ?? {},
373
+ embedding,
374
+ relations: input.relations
375
+ };
376
+ const stored = await this.storage.store(event);
377
+ this.notifyListeners(stored);
378
+ return stored;
379
+ }
380
+ /**
381
+ * Add multiple memory events in a batch.
382
+ * More efficient than calling add() multiple times.
383
+ */
384
+ async addBatch(events) {
385
+ await this.initialize();
386
+ const contents = events.map((e) => e.content);
387
+ const embeddings = await this.embeddings.embedBatch(contents);
388
+ const fullEvents = events.map((input, i) => ({
389
+ id: generateId(),
390
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
391
+ actor: input.actor ?? "unknown",
392
+ action: input.action ?? this.inferAction(input.content),
393
+ content: input.content,
394
+ context: input.context ?? {},
395
+ embedding: embeddings[i],
396
+ relations: input.relations
397
+ }));
398
+ const stored = await this.storage.storeBatch(fullEvents);
399
+ for (const event of stored) {
400
+ this.notifyListeners(event);
401
+ }
402
+ return stored;
403
+ }
404
+ /**
405
+ * Retrieve relevant memories based on a query.
406
+ * Uses multi-modal retrieval (semantic, temporal, relational).
407
+ *
408
+ * @example Basic retrieval
409
+ * ```typescript
410
+ * const memories = await aether.retrieve('What products did the user view?');
411
+ * ```
412
+ *
413
+ * @example With options
414
+ * ```typescript
415
+ * const memories = await aether.retrieve('recent actions', {
416
+ * actor: 'user123',
417
+ * retrievalType: 'temporal',
418
+ * limit: 10
419
+ * });
420
+ * ```
421
+ */
422
+ async retrieve(query, options) {
423
+ await this.initialize();
424
+ return this.retrieval.retrieve(query, options);
425
+ }
426
+ /**
427
+ * Get the history of events for a specific actor.
428
+ * Events are returned in reverse chronological order (most recent first).
429
+ */
430
+ async getHistory(actor, limit) {
431
+ await this.initialize();
432
+ return this.storage.queryByActor(actor, {
433
+ limit: limit ?? 20,
434
+ order: "desc"
435
+ });
436
+ }
437
+ /**
438
+ * Get a specific event by ID.
439
+ */
440
+ async get(id) {
441
+ await this.initialize();
442
+ return this.storage.getById(id);
443
+ }
444
+ /**
445
+ * Delete a specific event by ID.
446
+ */
447
+ async delete(id) {
448
+ await this.initialize();
449
+ return this.storage.delete(id);
450
+ }
451
+ /**
452
+ * Advanced multi-modal search with fine-grained control.
453
+ */
454
+ async search(options) {
455
+ await this.initialize();
456
+ const retrievalOptions = {
457
+ limit: options.limit ?? 10,
458
+ retrievalType: "hybrid"
459
+ };
460
+ const query = options.semantic?.query ?? "";
461
+ if (options.temporal?.actor) {
462
+ retrievalOptions.actor = options.temporal.actor;
463
+ }
464
+ if (options.relational?.contextKeys && options.relational?.values) {
465
+ retrievalOptions.contextFilters = {};
466
+ for (let i = 0; i < options.relational.contextKeys.length; i++) {
467
+ const key = options.relational.contextKeys[i];
468
+ const value = options.relational.values[i];
469
+ if (key !== void 0) {
470
+ retrievalOptions.contextFilters[key] = value;
471
+ }
472
+ }
473
+ }
474
+ return this.retrieval.retrieve(query, retrievalOptions);
475
+ }
476
+ /**
477
+ * Get events related to a specific event.
478
+ * Follows the relations defined on the event.
479
+ */
480
+ async getRelated(eventId, depth = 1) {
481
+ await this.initialize();
482
+ const visited = /* @__PURE__ */ new Set();
483
+ const related = [];
484
+ const traverse = async (id, currentDepth) => {
485
+ if (currentDepth > depth || visited.has(id)) return;
486
+ visited.add(id);
487
+ const event = await this.storage.getById(id);
488
+ if (!event) return;
489
+ if (id !== eventId) {
490
+ related.push(event);
491
+ }
492
+ if (event.relations) {
493
+ for (const relation of event.relations) {
494
+ await traverse(relation.targetEventId, currentDepth + 1);
495
+ }
496
+ }
497
+ };
498
+ await traverse(eventId, 0);
499
+ return related;
500
+ }
501
+ /**
502
+ * Subscribe to new memory events.
503
+ * Returns an unsubscribe function.
504
+ */
505
+ subscribe(callback) {
506
+ this.listeners.add(callback);
507
+ return () => {
508
+ this.listeners.delete(callback);
509
+ };
510
+ }
511
+ /**
512
+ * Get statistics about the memory store.
513
+ */
514
+ async stats() {
515
+ await this.initialize();
516
+ const actors = await this.storage.getActors();
517
+ const count = await this.storage.count();
518
+ let oldestEvent;
519
+ let newestEvent;
520
+ if (count > 0 && actors.length > 0) {
521
+ const allEvents = await this.storage.queryByActor(actors[0], { limit: 1, order: "asc" });
522
+ if (allEvents.length > 0) {
523
+ oldestEvent = allEvents[0].timestamp;
524
+ }
525
+ const recentEvents = await this.storage.queryByActor(actors[0], { limit: 1, order: "desc" });
526
+ if (recentEvents.length > 0) {
527
+ newestEvent = recentEvents[0].timestamp;
528
+ }
529
+ }
530
+ return {
531
+ totalEvents: count,
532
+ uniqueActors: actors.length,
533
+ oldestEvent,
534
+ newestEvent,
535
+ storageAdapter: this.storage.name,
536
+ embeddingProvider: this.embeddings.name
537
+ };
538
+ }
539
+ /**
540
+ * Close the memory system and release resources.
541
+ */
542
+ async close() {
543
+ await this.storage.close();
544
+ this.listeners.clear();
545
+ this.initialized = false;
546
+ }
547
+ /**
548
+ * Clear all stored memories.
549
+ * Use with caution!
550
+ */
551
+ async clear() {
552
+ await this.initialize();
553
+ await this.storage.clear();
554
+ }
555
+ /**
556
+ * Infer an action type from content.
557
+ */
558
+ inferAction(content) {
559
+ const lowerContent = content.toLowerCase();
560
+ const actionPatterns = [
561
+ [/\b(clicked|click|pressed|tapped)\b/, "click"],
562
+ [/\b(viewed|view|looked|saw|opened)\b/, "view"],
563
+ [/\b(searched|search|queried|query)\b/, "search"],
564
+ [/\b(purchased|bought|ordered|paid)\b/, "purchase"],
565
+ [/\b(added|add)\b.*\b(cart|basket)\b/, "add_to_cart"],
566
+ [/\b(removed|remove)\b.*\b(cart|basket)\b/, "remove_from_cart"],
567
+ [/\b(selected|chose|picked)\b/, "select"],
568
+ [/\b(submitted|submit|sent|send)\b/, "submit"],
569
+ [/\b(logged|login|signed)\s*(in|on)\b/, "login"],
570
+ [/\b(logged|signed)\s*(out|off)\b/, "logout"],
571
+ [/\b(created|create|made|new)\b/, "create"],
572
+ [/\b(updated|update|changed|modified)\b/, "update"],
573
+ [/\b(deleted|delete|removed)\b/, "delete"],
574
+ [/\b(said|asked|responded|replied|wrote)\b/, "message"]
575
+ ];
576
+ for (const [pattern, action] of actionPatterns) {
577
+ if (pattern.test(lowerContent)) {
578
+ return action;
579
+ }
580
+ }
581
+ return "event";
582
+ }
583
+ /**
584
+ * Notify all listeners of a new event.
585
+ */
586
+ notifyListeners(event) {
587
+ for (const listener of this.listeners) {
588
+ try {
589
+ listener(event);
590
+ } catch (error) {
591
+ console.error("Error in event listener:", error);
592
+ }
593
+ }
594
+ }
595
+ };
596
+
597
+ // src/utils/math.ts
598
+ function dotProduct(a, b) {
599
+ if (a.length !== b.length) {
600
+ throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
601
+ }
602
+ let sum = 0;
603
+ for (let i = 0; i < a.length; i++) {
604
+ sum += a[i] * b[i];
605
+ }
606
+ return sum;
607
+ }
608
+ function magnitude(v) {
609
+ let sum = 0;
610
+ for (let i = 0; i < v.length; i++) {
611
+ sum += v[i] * v[i];
612
+ }
613
+ return Math.sqrt(sum);
614
+ }
615
+ function normalize(v) {
616
+ const mag = magnitude(v);
617
+ if (mag === 0) {
618
+ return v.map(() => 0);
619
+ }
620
+ return v.map((x) => x / mag);
621
+ }
622
+ function cosineSimilarity(a, b) {
623
+ const dot = dotProduct(a, b);
624
+ const magA = magnitude(a);
625
+ const magB = magnitude(b);
626
+ if (magA === 0 || magB === 0) {
627
+ return 0;
628
+ }
629
+ return dot / (magA * magB);
630
+ }
631
+ function euclideanDistance(a, b) {
632
+ if (a.length !== b.length) {
633
+ throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
634
+ }
635
+ let sum = 0;
636
+ for (let i = 0; i < a.length; i++) {
637
+ const diff = a[i] - b[i];
638
+ sum += diff * diff;
639
+ }
640
+ return Math.sqrt(sum);
641
+ }
642
+
643
+ // src/storage/in-memory.adapter.ts
644
+ var InMemoryStorageAdapter = class {
645
+ name = "in-memory";
646
+ /** Primary storage: id -> event */
647
+ events = /* @__PURE__ */ new Map();
648
+ /** Actor index: actor -> Set<event ids> */
649
+ actorIndex = /* @__PURE__ */ new Map();
650
+ /** Context index: context key -> value -> Set<event ids> */
651
+ contextIndex = /* @__PURE__ */ new Map();
652
+ /** Timestamp index: sorted list of [timestamp, id] for range queries */
653
+ timestampIndex = [];
654
+ async initialize() {
655
+ }
656
+ async store(event) {
657
+ this.events.set(event.id, event);
658
+ if (!this.actorIndex.has(event.actor)) {
659
+ this.actorIndex.set(event.actor, /* @__PURE__ */ new Set());
660
+ }
661
+ this.actorIndex.get(event.actor).add(event.id);
662
+ for (const [key, value] of Object.entries(event.context)) {
663
+ if (!this.contextIndex.has(key)) {
664
+ this.contextIndex.set(key, /* @__PURE__ */ new Map());
665
+ }
666
+ const valueMap = this.contextIndex.get(key);
667
+ if (!valueMap.has(value)) {
668
+ valueMap.set(value, /* @__PURE__ */ new Set());
669
+ }
670
+ valueMap.get(value).add(event.id);
671
+ }
672
+ const ts = new Date(event.timestamp).getTime();
673
+ const insertIdx = this.binarySearchInsert(ts);
674
+ this.timestampIndex.splice(insertIdx, 0, { timestamp: ts, id: event.id });
675
+ return event;
676
+ }
677
+ async storeBatch(events) {
678
+ const results = [];
679
+ for (const event of events) {
680
+ results.push(await this.store(event));
681
+ }
682
+ return results;
683
+ }
684
+ async getById(id) {
685
+ return this.events.get(id) ?? null;
686
+ }
687
+ async getByIds(ids) {
688
+ const results = [];
689
+ for (const id of ids) {
690
+ const event = this.events.get(id);
691
+ if (event) {
692
+ results.push(event);
693
+ }
694
+ }
695
+ return results;
696
+ }
697
+ async queryByActor(actor, options) {
698
+ const eventIds = this.actorIndex.get(actor);
699
+ if (!eventIds || eventIds.size === 0) {
700
+ return [];
701
+ }
702
+ let events = [];
703
+ for (const id of eventIds) {
704
+ const event = this.events.get(id);
705
+ if (event) {
706
+ events.push(event);
707
+ }
708
+ }
709
+ const order = options?.order ?? "desc";
710
+ events.sort((a, b) => {
711
+ const timeA = new Date(a.timestamp).getTime();
712
+ const timeB = new Date(b.timestamp).getTime();
713
+ return order === "asc" ? timeA - timeB : timeB - timeA;
714
+ });
715
+ const offset = options?.offset ?? 0;
716
+ const limit = options?.limit ?? events.length;
717
+ return events.slice(offset, offset + limit);
718
+ }
719
+ async queryByContext(filters, options) {
720
+ const filterEntries = Object.entries(filters);
721
+ if (filterEntries.length === 0) {
722
+ return [];
723
+ }
724
+ let matchingIds = null;
725
+ for (const [key, value] of filterEntries) {
726
+ const keyIndex = this.contextIndex.get(key);
727
+ if (!keyIndex) {
728
+ return [];
729
+ }
730
+ const valueIds = keyIndex.get(value);
731
+ if (!valueIds) {
732
+ return [];
733
+ }
734
+ if (matchingIds === null) {
735
+ matchingIds = new Set(valueIds);
736
+ } else {
737
+ const newSet = /* @__PURE__ */ new Set();
738
+ for (const id of matchingIds) {
739
+ if (valueIds.has(id)) {
740
+ newSet.add(id);
741
+ }
742
+ }
743
+ matchingIds = newSet;
744
+ }
745
+ if (matchingIds.size === 0) {
746
+ return [];
747
+ }
748
+ }
749
+ if (!matchingIds) {
750
+ return [];
751
+ }
752
+ const events = [];
753
+ for (const id of matchingIds) {
754
+ const event = this.events.get(id);
755
+ if (event) {
756
+ events.push(event);
757
+ }
758
+ }
759
+ events.sort((a, b) => {
760
+ return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
761
+ });
762
+ const offset = options?.offset ?? 0;
763
+ const limit = options?.limit ?? events.length;
764
+ return events.slice(offset, offset + limit);
765
+ }
766
+ async queryByTimeRange(range, options) {
767
+ const startTs = new Date(range.start).getTime();
768
+ const endTs = new Date(range.end).getTime();
769
+ let left = 0;
770
+ let right = this.timestampIndex.length;
771
+ while (left < right) {
772
+ const mid = Math.floor((left + right) / 2);
773
+ if (this.timestampIndex[mid].timestamp < startTs) {
774
+ left = mid + 1;
775
+ } else {
776
+ right = mid;
777
+ }
778
+ }
779
+ const events = [];
780
+ for (let i = left; i < this.timestampIndex.length; i++) {
781
+ const entry = this.timestampIndex[i];
782
+ if (entry.timestamp > endTs) {
783
+ break;
784
+ }
785
+ const event = this.events.get(entry.id);
786
+ if (event) {
787
+ events.push(event);
788
+ }
789
+ }
790
+ const offset = options?.offset ?? 0;
791
+ const limit = options?.limit ?? events.length;
792
+ return events.slice(offset, offset + limit);
793
+ }
794
+ async searchByVector(vector, options) {
795
+ const results = [];
796
+ const threshold = options?.threshold ?? 0;
797
+ for (const event of this.events.values()) {
798
+ if (!event.embedding) {
799
+ continue;
800
+ }
801
+ if (options?.filters) {
802
+ let matches = true;
803
+ for (const [key, value] of Object.entries(options.filters)) {
804
+ if (event.context[key] !== value) {
805
+ matches = false;
806
+ break;
807
+ }
808
+ }
809
+ if (!matches) {
810
+ continue;
811
+ }
812
+ }
813
+ const score = cosineSimilarity(vector, event.embedding);
814
+ if (score >= threshold) {
815
+ results.push({ ...event, score });
816
+ }
817
+ }
818
+ results.sort((a, b) => b.score - a.score);
819
+ const limit = options?.limit ?? results.length;
820
+ return results.slice(0, limit);
821
+ }
822
+ async delete(id) {
823
+ const event = this.events.get(id);
824
+ if (!event) {
825
+ return false;
826
+ }
827
+ this.events.delete(id);
828
+ const actorIds = this.actorIndex.get(event.actor);
829
+ if (actorIds) {
830
+ actorIds.delete(id);
831
+ if (actorIds.size === 0) {
832
+ this.actorIndex.delete(event.actor);
833
+ }
834
+ }
835
+ for (const [key, value] of Object.entries(event.context)) {
836
+ const keyIndex = this.contextIndex.get(key);
837
+ if (keyIndex) {
838
+ const valueIds = keyIndex.get(value);
839
+ if (valueIds) {
840
+ valueIds.delete(id);
841
+ if (valueIds.size === 0) {
842
+ keyIndex.delete(value);
843
+ }
844
+ }
845
+ if (keyIndex.size === 0) {
846
+ this.contextIndex.delete(key);
847
+ }
848
+ }
849
+ }
850
+ const ts = new Date(event.timestamp).getTime();
851
+ const idx = this.timestampIndex.findIndex((e) => e.id === id && e.timestamp === ts);
852
+ if (idx !== -1) {
853
+ this.timestampIndex.splice(idx, 1);
854
+ }
855
+ return true;
856
+ }
857
+ async deleteBatch(ids) {
858
+ let count = 0;
859
+ for (const id of ids) {
860
+ if (await this.delete(id)) {
861
+ count++;
862
+ }
863
+ }
864
+ return count;
865
+ }
866
+ async clear() {
867
+ this.events.clear();
868
+ this.actorIndex.clear();
869
+ this.contextIndex.clear();
870
+ this.timestampIndex = [];
871
+ }
872
+ async count(filters) {
873
+ if (!filters || Object.keys(filters).length === 0) {
874
+ return this.events.size;
875
+ }
876
+ const events = await this.queryByContext(filters);
877
+ return events.length;
878
+ }
879
+ async getActors() {
880
+ return Array.from(this.actorIndex.keys());
881
+ }
882
+ async close() {
883
+ await this.clear();
884
+ }
885
+ /**
886
+ * Binary search to find insertion point for a timestamp.
887
+ */
888
+ binarySearchInsert(timestamp) {
889
+ let left = 0;
890
+ let right = this.timestampIndex.length;
891
+ while (left < right) {
892
+ const mid = Math.floor((left + right) / 2);
893
+ if (this.timestampIndex[mid].timestamp < timestamp) {
894
+ left = mid + 1;
895
+ } else {
896
+ right = mid;
897
+ }
898
+ }
899
+ return left;
900
+ }
901
+ };
902
+
903
+ // src/embeddings/mock.provider.ts
904
+ var MockEmbeddingProvider = class {
905
+ name = "mock";
906
+ dimensions;
907
+ constructor(dimensions = 384) {
908
+ this.dimensions = dimensions;
909
+ }
910
+ async embed(text) {
911
+ return this.hashToVector(text);
912
+ }
913
+ async embedBatch(texts) {
914
+ return texts.map((t) => this.hashToVector(t));
915
+ }
916
+ similarity(a, b) {
917
+ return cosineSimilarity(a, b);
918
+ }
919
+ /**
920
+ * Generate a deterministic vector from text hash.
921
+ * Same text always produces the same vector.
922
+ */
923
+ hashToVector(text) {
924
+ const hash = this.simpleHash(text);
925
+ const vector = [];
926
+ let seed = hash;
927
+ for (let i = 0; i < this.dimensions; i++) {
928
+ seed = seed * 1103515245 + 12345 & 2147483647;
929
+ vector.push(seed / 2147483647 * 2 - 1);
930
+ }
931
+ return normalize(vector);
932
+ }
933
+ /**
934
+ * Simple string hash function.
935
+ */
936
+ simpleHash(str) {
937
+ let hash = 0;
938
+ for (let i = 0; i < str.length; i++) {
939
+ const char = str.charCodeAt(i);
940
+ hash = (hash << 5) - hash + char;
941
+ hash = hash & hash;
942
+ }
943
+ return Math.abs(hash);
944
+ }
945
+ };
946
+
947
+ // src/embeddings/voyage.provider.ts
948
+ var MODEL_DIMENSIONS = {
949
+ "voyage-3": 1024,
950
+ "voyage-3-lite": 512,
951
+ "voyage-code-3": 1024,
952
+ "voyage-finance-2": 1024,
953
+ "voyage-law-2": 1024,
954
+ "voyage-code-2": 1536,
955
+ "voyage-2": 1024,
956
+ "voyage-large-2": 1536,
957
+ "voyage-large-2-instruct": 1024
958
+ };
959
+ var VoyageProvider = class {
960
+ name = "voyage";
961
+ dimensions;
962
+ apiKey;
963
+ model;
964
+ baseUrl;
965
+ batchSize;
966
+ timeout;
967
+ maxRetries;
968
+ constructor(config) {
969
+ if (!config.apiKey) {
970
+ throw new Error("VoyageAI API key is required");
971
+ }
972
+ this.apiKey = config.apiKey;
973
+ this.model = config.model ?? "voyage-2";
974
+ this.baseUrl = config.baseUrl ?? "https://api.voyageai.com/v1";
975
+ this.batchSize = config.batchSize ?? 128;
976
+ this.timeout = config.timeout ?? 3e4;
977
+ this.maxRetries = config.maxRetries ?? 3;
978
+ this.dimensions = MODEL_DIMENSIONS[this.model] ?? 1024;
979
+ }
980
+ async embed(text) {
981
+ const results = await this.embedBatch([text]);
982
+ return results[0];
983
+ }
984
+ async embedBatch(texts) {
985
+ if (texts.length === 0) {
986
+ return [];
987
+ }
988
+ const results = [];
989
+ for (let i = 0; i < texts.length; i += this.batchSize) {
990
+ const batch = texts.slice(i, i + this.batchSize);
991
+ const batchResults = await this.embedWithRetry(batch);
992
+ results.push(...batchResults);
993
+ }
994
+ return results;
995
+ }
996
+ similarity(a, b) {
997
+ return cosineSimilarity(a, b);
998
+ }
999
+ /**
1000
+ * Embed a batch of texts with retry logic.
1001
+ */
1002
+ async embedWithRetry(texts) {
1003
+ let lastError = null;
1004
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
1005
+ try {
1006
+ return await this.callApi(texts);
1007
+ } catch (error) {
1008
+ lastError = error;
1009
+ if (error instanceof Error && error.message.includes("4")) {
1010
+ throw error;
1011
+ }
1012
+ const delay = Math.pow(2, attempt) * 1e3;
1013
+ await this.sleep(delay);
1014
+ }
1015
+ }
1016
+ throw lastError ?? new Error("Failed to embed texts");
1017
+ }
1018
+ /**
1019
+ * Call the VoyageAI API.
1020
+ */
1021
+ async callApi(texts) {
1022
+ const controller = new AbortController();
1023
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1024
+ try {
1025
+ const response = await fetch(`${this.baseUrl}/embeddings`, {
1026
+ method: "POST",
1027
+ headers: {
1028
+ "Content-Type": "application/json",
1029
+ Authorization: `Bearer ${this.apiKey}`
1030
+ },
1031
+ body: JSON.stringify({
1032
+ input: texts,
1033
+ model: this.model
1034
+ }),
1035
+ signal: controller.signal
1036
+ });
1037
+ if (!response.ok) {
1038
+ const errorText = await response.text();
1039
+ throw new Error(`VoyageAI API error (${response.status}): ${errorText}`);
1040
+ }
1041
+ const data = await response.json();
1042
+ const sorted = data.data.sort((a, b) => a.index - b.index);
1043
+ return sorted.map((item) => item.embedding);
1044
+ } finally {
1045
+ clearTimeout(timeoutId);
1046
+ }
1047
+ }
1048
+ sleep(ms) {
1049
+ return new Promise((resolve) => setTimeout(resolve, ms));
1050
+ }
1051
+ };
1052
+ export {
1053
+ Aether,
1054
+ CompositeEntityExtractor,
1055
+ DEFAULT_ENTITY_PATTERNS,
1056
+ HybridStrategy,
1057
+ InMemoryStorageAdapter,
1058
+ MockEmbeddingProvider,
1059
+ PatternEntityExtractor,
1060
+ RelationalStrategy,
1061
+ RetrievalEngine,
1062
+ SemanticStrategy,
1063
+ TemporalStrategy,
1064
+ VoyageProvider,
1065
+ cosineSimilarity,
1066
+ euclideanDistance,
1067
+ generateId,
1068
+ getTimestampFromId,
1069
+ normalize
1070
+ };
1071
+ //# sourceMappingURL=index.js.map