@yamo/memory-mesh 2.3.2 → 3.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.
Files changed (124) hide show
  1. package/README.md +8 -2
  2. package/bin/memory_mesh.js +1 -1
  3. package/lib/llm/client.d.ts +86 -0
  4. package/lib/llm/client.js +300 -357
  5. package/lib/llm/client.ts +334 -0
  6. package/lib/llm/index.d.ts +17 -0
  7. package/lib/llm/index.js +16 -8
  8. package/lib/llm/index.ts +18 -0
  9. package/lib/memory/adapters/client.d.ts +120 -0
  10. package/lib/memory/adapters/client.js +519 -0
  11. package/lib/memory/adapters/client.ts +519 -0
  12. package/lib/memory/adapters/config.d.ts +130 -0
  13. package/lib/memory/adapters/config.js +190 -0
  14. package/lib/memory/adapters/config.ts +190 -0
  15. package/lib/memory/adapters/errors.d.ts +84 -0
  16. package/lib/memory/adapters/errors.js +129 -0
  17. package/lib/memory/adapters/errors.ts +129 -0
  18. package/lib/memory/context-manager.d.ts +41 -0
  19. package/lib/memory/context-manager.js +345 -0
  20. package/lib/memory/context-manager.ts +345 -0
  21. package/lib/memory/embeddings/factory.d.ts +57 -0
  22. package/lib/memory/embeddings/factory.js +149 -0
  23. package/lib/memory/embeddings/factory.ts +149 -0
  24. package/lib/memory/embeddings/index.d.ts +2 -0
  25. package/lib/memory/embeddings/index.js +3 -0
  26. package/lib/memory/embeddings/index.ts +3 -0
  27. package/lib/memory/embeddings/service.d.ts +134 -0
  28. package/lib/memory/embeddings/service.js +516 -0
  29. package/lib/memory/embeddings/service.ts +516 -0
  30. package/lib/memory/index.d.ts +9 -0
  31. package/lib/memory/index.js +10 -1
  32. package/lib/memory/index.ts +10 -0
  33. package/lib/memory/memory-mesh.d.ts +332 -0
  34. package/lib/memory/memory-mesh.js +1470 -678
  35. package/lib/memory/memory-mesh.ts +1517 -0
  36. package/lib/memory/memory-translator.d.ts +14 -0
  37. package/lib/memory/memory-translator.js +126 -0
  38. package/lib/memory/memory-translator.ts +126 -0
  39. package/lib/memory/schema.d.ts +130 -0
  40. package/lib/memory/schema.js +184 -0
  41. package/lib/memory/schema.ts +184 -0
  42. package/lib/memory/scorer.d.ts +25 -0
  43. package/lib/memory/scorer.js +78 -0
  44. package/lib/memory/scorer.ts +78 -0
  45. package/lib/memory/search/index.d.ts +1 -0
  46. package/lib/memory/search/index.js +2 -0
  47. package/lib/memory/search/index.ts +2 -0
  48. package/lib/memory/search/keyword-search.d.ts +46 -0
  49. package/lib/memory/search/keyword-search.js +136 -0
  50. package/lib/memory/search/keyword-search.ts +136 -0
  51. package/lib/scrubber/config/defaults.d.ts +46 -0
  52. package/lib/scrubber/config/defaults.js +50 -57
  53. package/lib/scrubber/config/defaults.ts +55 -0
  54. package/lib/scrubber/errors/scrubber-error.d.ts +22 -0
  55. package/lib/scrubber/errors/scrubber-error.js +28 -32
  56. package/lib/scrubber/errors/scrubber-error.ts +44 -0
  57. package/lib/scrubber/index.d.ts +5 -0
  58. package/lib/scrubber/index.js +4 -23
  59. package/lib/scrubber/index.ts +6 -0
  60. package/lib/scrubber/scrubber.d.ts +44 -0
  61. package/lib/scrubber/scrubber.js +100 -121
  62. package/lib/scrubber/scrubber.ts +109 -0
  63. package/lib/scrubber/stages/chunker.d.ts +25 -0
  64. package/lib/scrubber/stages/chunker.js +74 -91
  65. package/lib/scrubber/stages/chunker.ts +104 -0
  66. package/lib/scrubber/stages/metadata-annotator.d.ts +17 -0
  67. package/lib/scrubber/stages/metadata-annotator.js +55 -65
  68. package/lib/scrubber/stages/metadata-annotator.ts +75 -0
  69. package/lib/scrubber/stages/normalizer.d.ts +16 -0
  70. package/lib/scrubber/stages/normalizer.js +42 -50
  71. package/lib/scrubber/stages/normalizer.ts +60 -0
  72. package/lib/scrubber/stages/semantic-filter.d.ts +16 -0
  73. package/lib/scrubber/stages/semantic-filter.js +42 -52
  74. package/lib/scrubber/stages/semantic-filter.ts +62 -0
  75. package/lib/scrubber/stages/structural-cleaner.d.ts +18 -0
  76. package/lib/scrubber/stages/structural-cleaner.js +66 -75
  77. package/lib/scrubber/stages/structural-cleaner.ts +83 -0
  78. package/lib/scrubber/stages/validator.d.ts +17 -0
  79. package/lib/scrubber/stages/validator.js +46 -56
  80. package/lib/scrubber/stages/validator.ts +67 -0
  81. package/lib/scrubber/telemetry.d.ts +29 -0
  82. package/lib/scrubber/telemetry.js +54 -58
  83. package/lib/scrubber/telemetry.ts +62 -0
  84. package/lib/scrubber/utils/hash.d.ts +14 -0
  85. package/lib/scrubber/utils/hash.js +30 -32
  86. package/lib/scrubber/utils/hash.ts +40 -0
  87. package/lib/scrubber/utils/html-parser.d.ts +14 -0
  88. package/lib/scrubber/utils/html-parser.js +32 -39
  89. package/lib/scrubber/utils/html-parser.ts +46 -0
  90. package/lib/scrubber/utils/pattern-matcher.d.ts +12 -0
  91. package/lib/scrubber/utils/pattern-matcher.js +48 -57
  92. package/lib/scrubber/utils/pattern-matcher.ts +64 -0
  93. package/lib/scrubber/utils/token-counter.d.ts +18 -0
  94. package/lib/scrubber/utils/token-counter.js +24 -25
  95. package/lib/scrubber/utils/token-counter.ts +32 -0
  96. package/lib/utils/logger.d.ts +19 -0
  97. package/lib/utils/logger.js +65 -0
  98. package/lib/utils/logger.ts +65 -0
  99. package/lib/utils/skill-metadata.d.ts +24 -0
  100. package/lib/utils/skill-metadata.js +133 -0
  101. package/lib/utils/skill-metadata.ts +133 -0
  102. package/lib/yamo/emitter.d.ts +46 -0
  103. package/lib/yamo/emitter.js +79 -143
  104. package/lib/yamo/emitter.ts +171 -0
  105. package/lib/yamo/index.d.ts +14 -0
  106. package/lib/yamo/index.js +6 -7
  107. package/lib/yamo/index.ts +16 -0
  108. package/lib/yamo/schema.d.ts +56 -0
  109. package/lib/yamo/schema.js +82 -108
  110. package/lib/yamo/schema.ts +133 -0
  111. package/package.json +13 -8
  112. package/index.d.ts +0 -111
  113. package/lib/embeddings/factory.js +0 -151
  114. package/lib/embeddings/index.js +0 -2
  115. package/lib/embeddings/service.js +0 -586
  116. package/lib/index.js +0 -6
  117. package/lib/lancedb/client.js +0 -633
  118. package/lib/lancedb/config.js +0 -215
  119. package/lib/lancedb/errors.js +0 -144
  120. package/lib/lancedb/index.js +0 -4
  121. package/lib/lancedb/schema.js +0 -217
  122. package/lib/search/index.js +0 -1
  123. package/lib/search/keyword-search.js +0 -144
  124. package/lib/utils/index.js +0 -1
@@ -0,0 +1,345 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * MemoryContextManager - High-level memory management for YAMO
4
+ */
5
+ import { MemoryMesh } from "./memory-mesh.js";
6
+ import { MemoryScorer } from "./scorer.js";
7
+ import { MemoryTranslator } from "./memory-translator.js";
8
+ import { createLogger } from "../utils/logger.js";
9
+ const logger = createLogger("context-manager");
10
+ export class MemoryContextManager {
11
+ #config;
12
+ #mesh;
13
+ #scorer;
14
+ #initialized = false;
15
+ #queryCache = new Map();
16
+ #cacheConfig = {
17
+ maxSize: 100,
18
+ ttlMs: 2 * 60 * 1000, // 2 minutes
19
+ };
20
+ #cleanupTimer = null;
21
+ /**
22
+ * Create a new MemoryContextManager
23
+ */
24
+ constructor(config = {}) {
25
+ this.#config = {
26
+ autoInit: true,
27
+ enableCache: true,
28
+ recallLimit: 5,
29
+ minImportance: 0.1,
30
+ silent: config.silent !== false,
31
+ ...config,
32
+ };
33
+ // Use provided mesh or create new instance
34
+ this.#mesh = config.mesh || new MemoryMesh();
35
+ this.#scorer = new MemoryScorer(this.#mesh);
36
+ // Start periodic cleanup timer (every 60 seconds)
37
+ this.#startCleanupTimer();
38
+ }
39
+ /**
40
+ * Initialize the memory context manager
41
+ */
42
+ async initialize() {
43
+ if (this.#initialized) {
44
+ return;
45
+ }
46
+ try {
47
+ await this.#mesh.init();
48
+ this.#initialized = true;
49
+ }
50
+ catch (error) {
51
+ this.#logWarn(`Initialization failed: ${error.message}`);
52
+ this.#initialized = false;
53
+ }
54
+ }
55
+ /**
56
+ * Capture an interaction as memory
57
+ */
58
+ async captureInteraction(prompt, response, context = {}) {
59
+ try {
60
+ if (this.#config.autoInit && !this.#initialized) {
61
+ await this.initialize();
62
+ }
63
+ if (!this.#initialized) {
64
+ return null;
65
+ }
66
+ const content = this.#formatInteraction(prompt, response);
67
+ const metadata = this.#buildMetadata(context);
68
+ const isDuplicate = await this.#scorer.isDuplicate(content);
69
+ if (isDuplicate) {
70
+ return null;
71
+ }
72
+ const importance = this.#scorer.calculateImportance(content, metadata);
73
+ if (importance < (this.#config.minImportance ?? 0.1)) {
74
+ return null;
75
+ }
76
+ const memory = await this.#mesh.add(content, {
77
+ ...metadata,
78
+ importanceScore: importance,
79
+ });
80
+ return memory;
81
+ }
82
+ catch (error) {
83
+ this.#logWarn(`Failed to capture interaction: ${error.message}`);
84
+ return null;
85
+ }
86
+ }
87
+ /**
88
+ * Recall relevant memories for a query
89
+ */
90
+ async recallMemories(query, options = {}) {
91
+ try {
92
+ if (this.#config.autoInit && !this.#initialized) {
93
+ await this.initialize();
94
+ }
95
+ if (!this.#initialized) {
96
+ return [];
97
+ }
98
+ const { limit = this.#config.recallLimit, useCache = this.#config.enableCache, memoryType = null, skillName = null, } = options;
99
+ if (useCache) {
100
+ const cacheKey = this.#cacheKey(query, {
101
+ limit,
102
+ memoryType,
103
+ skillName,
104
+ });
105
+ const cached = this.#getCached(cacheKey);
106
+ if (cached) {
107
+ return cached;
108
+ }
109
+ }
110
+ const filter = memoryType ? `memoryType == '${memoryType}'` : null;
111
+ // Fetch extra when skill-scoping — some results will be filtered out post-query
112
+ const fetchLimit = skillName ? limit * 2 : limit;
113
+ let memories = [];
114
+ if (memoryType === "synthesized_skill" &&
115
+ typeof this.#mesh.searchSkills === "function") {
116
+ memories = await this.#mesh.searchSkills(query, { limit: fetchLimit });
117
+ }
118
+ else {
119
+ memories = await this.#mesh.search(query, {
120
+ limit: fetchLimit,
121
+ filter,
122
+ useCache: false,
123
+ });
124
+ }
125
+ memories = memories.map((memory) => {
126
+ const metadata = typeof memory.metadata === "string"
127
+ ? JSON.parse(memory.metadata)
128
+ : memory.metadata || {};
129
+ return {
130
+ ...memory,
131
+ importanceScore: memory.score || metadata.importanceScore || 0,
132
+ memoryType: metadata.memoryType ||
133
+ (memoryType === "synthesized_skill"
134
+ ? "synthesized_skill"
135
+ : "global"),
136
+ };
137
+ });
138
+ // Deduplicate by content — results are already sorted by score, so first occurrence wins
139
+ const seen = new Set();
140
+ memories = memories.filter((memory) => {
141
+ if (seen.has(memory.content)) {
142
+ return false;
143
+ }
144
+ seen.add(memory.content);
145
+ return true;
146
+ });
147
+ // Skill-scope filter: keep memories tagged with this skill OR untagged (global).
148
+ // Untagged memories are shared context; tagged memories are skill-private.
149
+ if (skillName) {
150
+ memories = memories.filter((memory) => {
151
+ const meta = typeof memory.metadata === "string"
152
+ ? JSON.parse(memory.metadata)
153
+ : memory.metadata || {};
154
+ return !meta.skill_name || meta.skill_name === skillName;
155
+ });
156
+ memories = memories.slice(0, limit);
157
+ }
158
+ if (useCache) {
159
+ const cacheKey = this.#cacheKey(query, {
160
+ limit,
161
+ memoryType,
162
+ skillName,
163
+ });
164
+ this.#setCached(cacheKey, memories);
165
+ }
166
+ return memories;
167
+ }
168
+ catch (error) {
169
+ this.#logWarn(`Failed to recall memories: ${error.message}`);
170
+ return [];
171
+ }
172
+ }
173
+ /**
174
+ * Format memories for inclusion in prompt
175
+ */
176
+ formatMemoriesForPrompt(memories, options = {}) {
177
+ try {
178
+ if (!memories || memories.length === 0) {
179
+ return "";
180
+ }
181
+ return MemoryTranslator.toYAMOContext(memories, options);
182
+ }
183
+ catch (error) {
184
+ this.#logWarn(`Failed to format memories: ${error.message}`);
185
+ return "";
186
+ }
187
+ }
188
+ #logWarn(message) {
189
+ if (!this.#config.silent || process.env.YAMO_DEBUG === "true") {
190
+ logger.warn(message);
191
+ }
192
+ }
193
+ #formatInteraction(prompt, response) {
194
+ const lines = [
195
+ `[USER] ${prompt}`,
196
+ `[ASSISTANT] ${response.substring(0, 500)}${response.length > 500 ? "..." : ""}`,
197
+ ];
198
+ return lines.join("\n\n");
199
+ }
200
+ #buildMetadata(context) {
201
+ const metadata = {
202
+ interaction_type: context.interactionType || "llm_response",
203
+ created_at: new Date().toISOString(),
204
+ };
205
+ if (context.toolsUsed?.length > 0) {
206
+ metadata.tools_used = context.toolsUsed;
207
+ }
208
+ if (context.filesInvolved?.length > 0) {
209
+ metadata.files_involved = context.filesInvolved;
210
+ }
211
+ if (context.tags?.length > 0) {
212
+ metadata.tags = context.tags;
213
+ }
214
+ if (context.skillName) {
215
+ metadata.skill_name = context.skillName;
216
+ }
217
+ if (context.sessionId) {
218
+ metadata.session_id = context.sessionId;
219
+ }
220
+ return metadata;
221
+ }
222
+ #cacheKey(query, options) {
223
+ return `recall:${query}:${JSON.stringify(options)}`;
224
+ }
225
+ /**
226
+ * Get cached result if valid
227
+ * Race condition fix: Update timestamp atomically for LRU tracking
228
+ */
229
+ #getCached(key) {
230
+ const entry = this.#queryCache.get(key);
231
+ if (!entry) {
232
+ return null;
233
+ }
234
+ // Check TTL before any mutation
235
+ const now = Date.now();
236
+ if (now - entry.timestamp > this.#cacheConfig.ttlMs) {
237
+ this.#queryCache.delete(key);
238
+ return null;
239
+ }
240
+ // Move to end (most recently used) - delete and re-add with updated timestamp
241
+ this.#queryCache.delete(key);
242
+ this.#queryCache.set(key, {
243
+ ...entry,
244
+ timestamp: now, // Update timestamp for LRU tracking
245
+ });
246
+ return entry.result;
247
+ }
248
+ #setCached(key, result) {
249
+ if (this.#queryCache.size >= this.#cacheConfig.maxSize) {
250
+ const firstKey = this.#queryCache.keys().next().value;
251
+ if (firstKey !== undefined) {
252
+ this.#queryCache.delete(firstKey);
253
+ }
254
+ }
255
+ this.#queryCache.set(key, {
256
+ result,
257
+ timestamp: Date.now(),
258
+ });
259
+ }
260
+ clearCache() {
261
+ this.#queryCache.clear();
262
+ }
263
+ getCacheStats() {
264
+ return {
265
+ size: this.#queryCache.size,
266
+ maxSize: this.#cacheConfig.maxSize,
267
+ ttlMs: this.#cacheConfig.ttlMs,
268
+ };
269
+ }
270
+ async healthCheck() {
271
+ const health = {
272
+ status: "healthy",
273
+ timestamp: new Date().toISOString(),
274
+ initialized: this.#initialized,
275
+ checks: {},
276
+ };
277
+ try {
278
+ health.checks.mesh = await this.#mesh.stats(); // brain.ts has stats()
279
+ if (health.checks.mesh.isConnected === false) {
280
+ health.status = "degraded";
281
+ }
282
+ }
283
+ catch (error) {
284
+ health.checks.mesh = {
285
+ status: "error",
286
+ error: error.message,
287
+ };
288
+ health.status = "unhealthy";
289
+ }
290
+ health.checks.cache = {
291
+ status: "up",
292
+ size: this.#queryCache.size,
293
+ maxSize: this.#cacheConfig.maxSize,
294
+ };
295
+ return health;
296
+ }
297
+ /**
298
+ * Start periodic cleanup timer to remove expired cache entries
299
+ * @private
300
+ */
301
+ #startCleanupTimer() {
302
+ // Clear any existing timer
303
+ if (this.#cleanupTimer) {
304
+ clearInterval(this.#cleanupTimer);
305
+ }
306
+ // Run cleanup every 60 seconds
307
+ this.#cleanupTimer = setInterval(() => {
308
+ this.#cleanupExpired();
309
+ }, 60000);
310
+ }
311
+ /**
312
+ * Clean up expired cache entries
313
+ * @private
314
+ */
315
+ #cleanupExpired() {
316
+ const now = Date.now();
317
+ const expiredKeys = [];
318
+ // Find expired entries
319
+ for (const [key, entry] of this.#queryCache.entries()) {
320
+ if (now - entry.timestamp > this.#cacheConfig.ttlMs) {
321
+ expiredKeys.push(key);
322
+ }
323
+ }
324
+ // Remove expired entries
325
+ for (const key of expiredKeys) {
326
+ this.#queryCache.delete(key);
327
+ }
328
+ if (expiredKeys.length > 0 &&
329
+ (process.env.YAMO_DEBUG === "true" || !this.#config.silent)) {
330
+ logger.debug({ count: expiredKeys.length }, "Cleaned up expired cache entries");
331
+ }
332
+ }
333
+ /**
334
+ * Dispose of resources (cleanup timer and cache)
335
+ * Call this when the MemoryContextManager is no longer needed
336
+ */
337
+ dispose() {
338
+ if (this.#cleanupTimer) {
339
+ clearInterval(this.#cleanupTimer);
340
+ this.#cleanupTimer = null;
341
+ }
342
+ this.clearCache();
343
+ }
344
+ }
345
+ export default MemoryContextManager;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * EmbeddingFactory - Multi-provider embedding with automatic fallback
3
+ * Manages primary and fallback embedding services
4
+ */
5
+ import EmbeddingService from "./service.js";
6
+ declare class EmbeddingFactory {
7
+ primaryService: any;
8
+ fallbackServices: any;
9
+ configured: any;
10
+ ServiceClass: any;
11
+ constructor(ServiceClass?: typeof EmbeddingService);
12
+ /**
13
+ * Configure embedding services with fallback chain
14
+ * @param {Array} configs - Array of { modelType, modelName, priority, apiKey }
15
+ * @returns {Object} Success status
16
+ */
17
+ configure(configs: any): {
18
+ success: boolean;
19
+ };
20
+ /**
21
+ * Initialize all configured services
22
+ * @returns {Promise<Object>} Initialization status
23
+ */
24
+ init(): Promise<{
25
+ success: boolean;
26
+ primary: any;
27
+ fallbacks: any;
28
+ }>;
29
+ /**
30
+ * Generate embedding with automatic fallback
31
+ * @param {string} text - Text to embed
32
+ * @param {Object} options - Options
33
+ * @returns {Promise<number[]>} Embedding vector
34
+ */
35
+ embed(text: any, options?: {}): Promise<any>;
36
+ /**
37
+ * Generate embeddings for batch of texts
38
+ * @param {string[]} texts - Texts to embed
39
+ * @param {Object} options - Options
40
+ * @returns {Promise<number[][]>} Array of embedding vectors
41
+ */
42
+ embedBatch(texts: any, options?: {}): Promise<any>;
43
+ /**
44
+ * Get factory statistics
45
+ * @returns {Object} Statistics
46
+ */
47
+ getStats(): {
48
+ configured: any;
49
+ primary: any;
50
+ fallbacks: any;
51
+ };
52
+ /**
53
+ * Clear all caches
54
+ */
55
+ clearCache(): void;
56
+ }
57
+ export default EmbeddingFactory;
@@ -0,0 +1,149 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * EmbeddingFactory - Multi-provider embedding with automatic fallback
4
+ * Manages primary and fallback embedding services
5
+ */
6
+ import EmbeddingService from "./service.js";
7
+ import { ConfigurationError, EmbeddingError } from "../adapters/errors.js";
8
+ import { createLogger } from "../../utils/logger.js";
9
+ const logger = createLogger("embedding-factory");
10
+ class EmbeddingFactory {
11
+ primaryService;
12
+ fallbackServices;
13
+ configured;
14
+ ServiceClass;
15
+ constructor(ServiceClass = EmbeddingService) {
16
+ this.primaryService = null;
17
+ this.fallbackServices = [];
18
+ this.configured = false;
19
+ this.ServiceClass = ServiceClass;
20
+ }
21
+ /**
22
+ * Configure embedding services with fallback chain
23
+ * @param {Array} configs - Array of { modelType, modelName, priority, apiKey }
24
+ * @returns {Object} Success status
25
+ */
26
+ configure(configs) {
27
+ // Sort by priority (lower = higher priority)
28
+ configs.sort((a, b) => (a.priority || 0) - (b.priority || 0));
29
+ if (configs.length > 0) {
30
+ this.primaryService = new this.ServiceClass(configs[0]);
31
+ }
32
+ if (configs.length > 1) {
33
+ this.fallbackServices = configs
34
+ .slice(1)
35
+ .map((c) => new this.ServiceClass(c));
36
+ }
37
+ this.configured = true;
38
+ return { success: true };
39
+ }
40
+ /**
41
+ * Initialize all configured services
42
+ * @returns {Promise<Object>} Initialization status
43
+ */
44
+ async init() {
45
+ if (!this.configured) {
46
+ throw new ConfigurationError("EmbeddingFactory not configured. Call configure() first.");
47
+ }
48
+ // Initialize primary service
49
+ if (this.primaryService && !this.primaryService.initialized) {
50
+ await this.primaryService.init();
51
+ }
52
+ // Initialize fallback services lazily (on first use)
53
+ return {
54
+ success: true,
55
+ primary: this.primaryService ? this.primaryService.modelName : null,
56
+ fallbacks: this.fallbackServices.map((s) => s.modelName),
57
+ };
58
+ }
59
+ /**
60
+ * Generate embedding with automatic fallback
61
+ * @param {string} text - Text to embed
62
+ * @param {Object} options - Options
63
+ * @returns {Promise<number[]>} Embedding vector
64
+ */
65
+ async embed(text, options = {}) {
66
+ if (!this.configured || !this.primaryService) {
67
+ throw new ConfigurationError("EmbeddingFactory not configured");
68
+ }
69
+ // Try primary service
70
+ try {
71
+ if (!this.primaryService.initialized) {
72
+ await this.primaryService.init();
73
+ }
74
+ return await this.primaryService.embed(text, options);
75
+ }
76
+ catch (error) {
77
+ const errorMessage = error instanceof Error ? error.message : String(error);
78
+ logger.warn({ err: error, primaryService: this.primaryService?.modelName }, "Primary service failed");
79
+ // Try fallback services in order
80
+ for (const fallback of this.fallbackServices) {
81
+ try {
82
+ if (!fallback.initialized) {
83
+ await fallback.init();
84
+ }
85
+ logger.info({ fallbackModel: fallback.modelName }, "Using fallback service");
86
+ return await fallback.embed(text, options);
87
+ }
88
+ catch (fallbackError) {
89
+ logger.warn({ err: fallbackError, fallbackModel: fallback.modelName }, "Fallback service failed");
90
+ }
91
+ }
92
+ throw new EmbeddingError("All embedding services failed", {
93
+ primaryError: errorMessage,
94
+ fallbackCount: this.fallbackServices.length,
95
+ });
96
+ }
97
+ }
98
+ /**
99
+ * Generate embeddings for batch of texts
100
+ * @param {string[]} texts - Texts to embed
101
+ * @param {Object} options - Options
102
+ * @returns {Promise<number[][]>} Array of embedding vectors
103
+ */
104
+ async embedBatch(texts, options = {}) {
105
+ if (!this.configured || !this.primaryService) {
106
+ throw new ConfigurationError("EmbeddingFactory not configured");
107
+ }
108
+ // Try primary service
109
+ try {
110
+ if (!this.primaryService.initialized) {
111
+ await this.primaryService.init();
112
+ }
113
+ return await this.primaryService.embedBatch(texts, options);
114
+ }
115
+ catch (error) {
116
+ logger.warn({
117
+ err: error,
118
+ primaryService: this.primaryService?.modelName,
119
+ batchSize: texts.length,
120
+ }, "Primary batch embedding failed, falling back to individual embeddings");
121
+ // Fallback to individual embedding with fallback services
122
+ const results = [];
123
+ for (const text of texts) {
124
+ results.push(await this.embed(text, options));
125
+ }
126
+ return results;
127
+ }
128
+ }
129
+ /**
130
+ * Get factory statistics
131
+ * @returns {Object} Statistics
132
+ */
133
+ getStats() {
134
+ const stats = {
135
+ configured: this.configured,
136
+ primary: this.primaryService?.getStats() || null,
137
+ fallbacks: this.fallbackServices.map((s) => s.getStats()),
138
+ };
139
+ return stats;
140
+ }
141
+ /**
142
+ * Clear all caches
143
+ */
144
+ clearCache() {
145
+ this.primaryService?.clearCache();
146
+ this.fallbackServices.forEach((s) => s.clearCache());
147
+ }
148
+ }
149
+ export default EmbeddingFactory;
@@ -0,0 +1,149 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * EmbeddingFactory - Multi-provider embedding with automatic fallback
4
+ * Manages primary and fallback embedding services
5
+ */
6
+ import EmbeddingService from "./service.js";
7
+ import { ConfigurationError, EmbeddingError } from "../adapters/errors.js";
8
+ import { createLogger } from "../../utils/logger.js";
9
+ const logger = createLogger("embedding-factory");
10
+ class EmbeddingFactory {
11
+ primaryService;
12
+ fallbackServices;
13
+ configured;
14
+ ServiceClass;
15
+ constructor(ServiceClass = EmbeddingService) {
16
+ this.primaryService = null;
17
+ this.fallbackServices = [];
18
+ this.configured = false;
19
+ this.ServiceClass = ServiceClass;
20
+ }
21
+ /**
22
+ * Configure embedding services with fallback chain
23
+ * @param {Array} configs - Array of { modelType, modelName, priority, apiKey }
24
+ * @returns {Object} Success status
25
+ */
26
+ configure(configs) {
27
+ // Sort by priority (lower = higher priority)
28
+ configs.sort((a, b) => (a.priority || 0) - (b.priority || 0));
29
+ if (configs.length > 0) {
30
+ this.primaryService = new this.ServiceClass(configs[0]);
31
+ }
32
+ if (configs.length > 1) {
33
+ this.fallbackServices = configs
34
+ .slice(1)
35
+ .map((c) => new this.ServiceClass(c));
36
+ }
37
+ this.configured = true;
38
+ return { success: true };
39
+ }
40
+ /**
41
+ * Initialize all configured services
42
+ * @returns {Promise<Object>} Initialization status
43
+ */
44
+ async init() {
45
+ if (!this.configured) {
46
+ throw new ConfigurationError("EmbeddingFactory not configured. Call configure() first.");
47
+ }
48
+ // Initialize primary service
49
+ if (this.primaryService && !this.primaryService.initialized) {
50
+ await this.primaryService.init();
51
+ }
52
+ // Initialize fallback services lazily (on first use)
53
+ return {
54
+ success: true,
55
+ primary: this.primaryService ? this.primaryService.modelName : null,
56
+ fallbacks: this.fallbackServices.map((s) => s.modelName),
57
+ };
58
+ }
59
+ /**
60
+ * Generate embedding with automatic fallback
61
+ * @param {string} text - Text to embed
62
+ * @param {Object} options - Options
63
+ * @returns {Promise<number[]>} Embedding vector
64
+ */
65
+ async embed(text, options = {}) {
66
+ if (!this.configured || !this.primaryService) {
67
+ throw new ConfigurationError("EmbeddingFactory not configured");
68
+ }
69
+ // Try primary service
70
+ try {
71
+ if (!this.primaryService.initialized) {
72
+ await this.primaryService.init();
73
+ }
74
+ return await this.primaryService.embed(text, options);
75
+ }
76
+ catch (error) {
77
+ const errorMessage = error instanceof Error ? error.message : String(error);
78
+ logger.warn({ err: error, primaryService: this.primaryService?.modelName }, "Primary service failed");
79
+ // Try fallback services in order
80
+ for (const fallback of this.fallbackServices) {
81
+ try {
82
+ if (!fallback.initialized) {
83
+ await fallback.init();
84
+ }
85
+ logger.info({ fallbackModel: fallback.modelName }, "Using fallback service");
86
+ return await fallback.embed(text, options);
87
+ }
88
+ catch (fallbackError) {
89
+ logger.warn({ err: fallbackError, fallbackModel: fallback.modelName }, "Fallback service failed");
90
+ }
91
+ }
92
+ throw new EmbeddingError("All embedding services failed", {
93
+ primaryError: errorMessage,
94
+ fallbackCount: this.fallbackServices.length,
95
+ });
96
+ }
97
+ }
98
+ /**
99
+ * Generate embeddings for batch of texts
100
+ * @param {string[]} texts - Texts to embed
101
+ * @param {Object} options - Options
102
+ * @returns {Promise<number[][]>} Array of embedding vectors
103
+ */
104
+ async embedBatch(texts, options = {}) {
105
+ if (!this.configured || !this.primaryService) {
106
+ throw new ConfigurationError("EmbeddingFactory not configured");
107
+ }
108
+ // Try primary service
109
+ try {
110
+ if (!this.primaryService.initialized) {
111
+ await this.primaryService.init();
112
+ }
113
+ return await this.primaryService.embedBatch(texts, options);
114
+ }
115
+ catch (error) {
116
+ logger.warn({
117
+ err: error,
118
+ primaryService: this.primaryService?.modelName,
119
+ batchSize: texts.length,
120
+ }, "Primary batch embedding failed, falling back to individual embeddings");
121
+ // Fallback to individual embedding with fallback services
122
+ const results = [];
123
+ for (const text of texts) {
124
+ results.push(await this.embed(text, options));
125
+ }
126
+ return results;
127
+ }
128
+ }
129
+ /**
130
+ * Get factory statistics
131
+ * @returns {Object} Statistics
132
+ */
133
+ getStats() {
134
+ const stats = {
135
+ configured: this.configured,
136
+ primary: this.primaryService?.getStats() || null,
137
+ fallbacks: this.fallbackServices.map((s) => s.getStats()),
138
+ };
139
+ return stats;
140
+ }
141
+ /**
142
+ * Clear all caches
143
+ */
144
+ clearCache() {
145
+ this.primaryService?.clearCache();
146
+ this.fallbackServices.forEach((s) => s.clearCache());
147
+ }
148
+ }
149
+ export default EmbeddingFactory;
@@ -0,0 +1,2 @@
1
+ export { default as EmbeddingService } from "./service.js";
2
+ export { default as EmbeddingFactory } from "./factory.js";