@twelvehart/supermemory-runtime 1.0.0-next.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.
Files changed (156) hide show
  1. package/.env.example +57 -0
  2. package/README.md +374 -0
  3. package/dist/index.js +189 -0
  4. package/dist/mcp/index.js +1132 -0
  5. package/docker-compose.prod.yml +91 -0
  6. package/docker-compose.yml +358 -0
  7. package/drizzle/0000_dapper_the_professor.sql +159 -0
  8. package/drizzle/0001_api_keys.sql +51 -0
  9. package/drizzle/meta/0000_snapshot.json +1532 -0
  10. package/drizzle/meta/_journal.json +13 -0
  11. package/drizzle.config.ts +20 -0
  12. package/package.json +114 -0
  13. package/scripts/add-extraction-job.ts +122 -0
  14. package/scripts/benchmark-pgvector.ts +122 -0
  15. package/scripts/bootstrap.sh +209 -0
  16. package/scripts/check-runtime-pack.ts +111 -0
  17. package/scripts/claude-mcp-config.ts +336 -0
  18. package/scripts/docker-entrypoint.sh +183 -0
  19. package/scripts/doctor.ts +377 -0
  20. package/scripts/init-db.sql +33 -0
  21. package/scripts/install.sh +1110 -0
  22. package/scripts/mcp-setup.ts +271 -0
  23. package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
  24. package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
  25. package/scripts/migrations/003_create_hnsw_index.sql +94 -0
  26. package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
  27. package/scripts/migrations/005_create_chunks_table.sql +95 -0
  28. package/scripts/migrations/006_create_processing_queue.sql +45 -0
  29. package/scripts/migrations/generate_test_data.sql +42 -0
  30. package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
  31. package/scripts/migrations/run_migrations.sh +286 -0
  32. package/scripts/migrations/test_hnsw_index.sql +255 -0
  33. package/scripts/pre-commit-secrets +282 -0
  34. package/scripts/run-extraction-worker.ts +46 -0
  35. package/scripts/run-phase1-tests.sh +291 -0
  36. package/scripts/setup.ts +222 -0
  37. package/scripts/smoke-install.sh +12 -0
  38. package/scripts/test-health-endpoint.sh +328 -0
  39. package/src/api/index.ts +2 -0
  40. package/src/api/middleware/auth.ts +80 -0
  41. package/src/api/middleware/csrf.ts +308 -0
  42. package/src/api/middleware/errorHandler.ts +166 -0
  43. package/src/api/middleware/rateLimit.ts +360 -0
  44. package/src/api/middleware/validation.ts +514 -0
  45. package/src/api/routes/documents.ts +286 -0
  46. package/src/api/routes/profiles.ts +237 -0
  47. package/src/api/routes/search.ts +71 -0
  48. package/src/api/stores/index.ts +58 -0
  49. package/src/config/bootstrap-env.ts +3 -0
  50. package/src/config/env.ts +71 -0
  51. package/src/config/feature-flags.ts +25 -0
  52. package/src/config/index.ts +140 -0
  53. package/src/config/secrets.config.ts +291 -0
  54. package/src/db/client.ts +92 -0
  55. package/src/db/index.ts +73 -0
  56. package/src/db/postgres.ts +72 -0
  57. package/src/db/schema/chunks.schema.ts +31 -0
  58. package/src/db/schema/containers.schema.ts +46 -0
  59. package/src/db/schema/documents.schema.ts +49 -0
  60. package/src/db/schema/embeddings.schema.ts +32 -0
  61. package/src/db/schema/index.ts +11 -0
  62. package/src/db/schema/memories.schema.ts +72 -0
  63. package/src/db/schema/profiles.schema.ts +34 -0
  64. package/src/db/schema/queue.schema.ts +59 -0
  65. package/src/db/schema/relationships.schema.ts +42 -0
  66. package/src/db/schema.ts +223 -0
  67. package/src/db/worker-connection.ts +47 -0
  68. package/src/index.ts +235 -0
  69. package/src/mcp/CLAUDE.md +1 -0
  70. package/src/mcp/index.ts +1380 -0
  71. package/src/mcp/legacyState.ts +22 -0
  72. package/src/mcp/rateLimit.ts +358 -0
  73. package/src/mcp/resources.ts +309 -0
  74. package/src/mcp/results.ts +104 -0
  75. package/src/mcp/tools.ts +401 -0
  76. package/src/queues/config.ts +119 -0
  77. package/src/queues/index.ts +289 -0
  78. package/src/sdk/client.ts +225 -0
  79. package/src/sdk/errors.ts +266 -0
  80. package/src/sdk/http.ts +560 -0
  81. package/src/sdk/index.ts +244 -0
  82. package/src/sdk/resources/base.ts +65 -0
  83. package/src/sdk/resources/connections.ts +204 -0
  84. package/src/sdk/resources/documents.ts +163 -0
  85. package/src/sdk/resources/index.ts +10 -0
  86. package/src/sdk/resources/memories.ts +150 -0
  87. package/src/sdk/resources/search.ts +60 -0
  88. package/src/sdk/resources/settings.ts +36 -0
  89. package/src/sdk/types.ts +674 -0
  90. package/src/services/chunking/index.ts +451 -0
  91. package/src/services/chunking.service.ts +650 -0
  92. package/src/services/csrf.service.ts +252 -0
  93. package/src/services/documents.repository.ts +219 -0
  94. package/src/services/documents.service.ts +191 -0
  95. package/src/services/embedding.service.ts +404 -0
  96. package/src/services/extraction.service.ts +300 -0
  97. package/src/services/extractors/code.extractor.ts +451 -0
  98. package/src/services/extractors/index.ts +9 -0
  99. package/src/services/extractors/markdown.extractor.ts +461 -0
  100. package/src/services/extractors/pdf.extractor.ts +315 -0
  101. package/src/services/extractors/text.extractor.ts +118 -0
  102. package/src/services/extractors/url.extractor.ts +243 -0
  103. package/src/services/index.ts +235 -0
  104. package/src/services/ingestion.service.ts +177 -0
  105. package/src/services/llm/anthropic.ts +400 -0
  106. package/src/services/llm/base.ts +460 -0
  107. package/src/services/llm/contradiction-detector.service.ts +526 -0
  108. package/src/services/llm/heuristics.ts +148 -0
  109. package/src/services/llm/index.ts +309 -0
  110. package/src/services/llm/memory-classifier.service.ts +383 -0
  111. package/src/services/llm/memory-extension-detector.service.ts +523 -0
  112. package/src/services/llm/mock.ts +470 -0
  113. package/src/services/llm/openai.ts +398 -0
  114. package/src/services/llm/prompts.ts +438 -0
  115. package/src/services/llm/types.ts +373 -0
  116. package/src/services/memory.repository.ts +1769 -0
  117. package/src/services/memory.service.ts +1338 -0
  118. package/src/services/memory.types.ts +234 -0
  119. package/src/services/persistence/index.ts +295 -0
  120. package/src/services/pipeline.service.ts +509 -0
  121. package/src/services/profile.repository.ts +436 -0
  122. package/src/services/profile.service.ts +560 -0
  123. package/src/services/profile.types.ts +270 -0
  124. package/src/services/relationships/detector.ts +1128 -0
  125. package/src/services/relationships/index.ts +268 -0
  126. package/src/services/relationships/memory-integration.ts +459 -0
  127. package/src/services/relationships/strategies.ts +132 -0
  128. package/src/services/relationships/types.ts +370 -0
  129. package/src/services/search.service.ts +761 -0
  130. package/src/services/search.types.ts +220 -0
  131. package/src/services/secrets.service.ts +384 -0
  132. package/src/services/vectorstore/base.ts +327 -0
  133. package/src/services/vectorstore/index.ts +444 -0
  134. package/src/services/vectorstore/memory.ts +286 -0
  135. package/src/services/vectorstore/migration.ts +295 -0
  136. package/src/services/vectorstore/mock.ts +403 -0
  137. package/src/services/vectorstore/pgvector.ts +695 -0
  138. package/src/services/vectorstore/types.ts +247 -0
  139. package/src/startup.ts +389 -0
  140. package/src/types/api.types.ts +193 -0
  141. package/src/types/document.types.ts +103 -0
  142. package/src/types/index.ts +241 -0
  143. package/src/types/profile.base.ts +133 -0
  144. package/src/utils/errors.ts +447 -0
  145. package/src/utils/id.ts +15 -0
  146. package/src/utils/index.ts +101 -0
  147. package/src/utils/logger.ts +313 -0
  148. package/src/utils/sanitization.ts +501 -0
  149. package/src/utils/secret-validation.ts +273 -0
  150. package/src/utils/synonyms.ts +188 -0
  151. package/src/utils/validation.ts +581 -0
  152. package/src/workers/chunking.worker.ts +242 -0
  153. package/src/workers/embedding.worker.ts +358 -0
  154. package/src/workers/extraction.worker.ts +346 -0
  155. package/src/workers/indexing.worker.ts +505 -0
  156. package/tsconfig.json +38 -0
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Base LLM Provider Class
3
+ *
4
+ * Provides common functionality for all LLM providers including:
5
+ * - Retry logic with exponential backoff
6
+ * - Caching
7
+ * - Error handling
8
+ * - Rate limit handling
9
+ */
10
+
11
+ import { createHash } from 'crypto'
12
+ import { getLogger } from '../../utils/logger.js'
13
+ import { AppError, ErrorCode } from '../../utils/errors.js'
14
+ import type {
15
+ LLMProvider,
16
+ LLMProviderType,
17
+ LLMExtractionResult,
18
+ LLMRelationshipResult,
19
+ ExtractedMemory,
20
+ DetectedRelationship,
21
+ ExtractionOptions,
22
+ RelationshipDetectionOptions,
23
+ ProviderHealthStatus,
24
+ BaseLLMConfig,
25
+ CacheEntry,
26
+ CacheConfig,
27
+ LLMErrorCodeType,
28
+ } from './types.js'
29
+ import { LLMErrorCode } from './types.js'
30
+ import type { MemoryType } from '../../types/index.js'
31
+
32
+ const logger = getLogger('LLMProvider')
33
+
34
+ // ============================================================================
35
+ // Default Configuration
36
+ // ============================================================================
37
+
38
+ export const DEFAULT_LLM_CONFIG: Required<BaseLLMConfig> = {
39
+ maxTokens: 2000,
40
+ temperature: 0.1,
41
+ timeoutMs: 30000,
42
+ maxRetries: 3,
43
+ retryDelayMs: 1000,
44
+ }
45
+
46
+ export const DEFAULT_CACHE_CONFIG: CacheConfig = {
47
+ enabled: true,
48
+ ttlMs: 15 * 60 * 1000, // 15 minutes
49
+ maxSize: 1000,
50
+ }
51
+
52
+ // ============================================================================
53
+ // LLM Error Class
54
+ // ============================================================================
55
+
56
+ export class LLMError extends AppError {
57
+ readonly llmCode: LLMErrorCodeType
58
+ readonly provider: LLMProviderType
59
+ readonly retryable: boolean
60
+ readonly retryAfterMs?: number
61
+
62
+ constructor(
63
+ message: string,
64
+ llmCode: LLMErrorCodeType,
65
+ provider: LLMProviderType,
66
+ retryable: boolean = false,
67
+ retryAfterMs?: number
68
+ ) {
69
+ super(message, ErrorCode.EXTERNAL_SERVICE_ERROR, {
70
+ llmCode,
71
+ provider,
72
+ retryable,
73
+ retryAfterMs,
74
+ })
75
+ this.name = 'LLMError'
76
+ this.llmCode = llmCode
77
+ this.provider = provider
78
+ this.retryable = retryable
79
+ this.retryAfterMs = retryAfterMs
80
+ }
81
+
82
+ static rateLimited(provider: LLMProviderType, retryAfterMs?: number): LLMError {
83
+ return new LLMError(`Rate limited by ${provider}`, LLMErrorCode.RATE_LIMITED, provider, true, retryAfterMs)
84
+ }
85
+
86
+ static timeout(provider: LLMProviderType): LLMError {
87
+ return new LLMError(`Request to ${provider} timed out`, LLMErrorCode.TIMEOUT, provider, true)
88
+ }
89
+
90
+ static invalidApiKey(provider: LLMProviderType): LLMError {
91
+ return new LLMError(`Invalid API key for ${provider}`, LLMErrorCode.INVALID_API_KEY, provider, false)
92
+ }
93
+
94
+ static invalidResponse(provider: LLMProviderType, details?: string): LLMError {
95
+ return new LLMError(
96
+ `Invalid response from ${provider}${details ? `: ${details}` : ''}`,
97
+ LLMErrorCode.INVALID_RESPONSE,
98
+ provider,
99
+ true
100
+ )
101
+ }
102
+
103
+ static providerUnavailable(provider: LLMProviderType): LLMError {
104
+ return new LLMError(`${provider} provider is unavailable`, LLMErrorCode.PROVIDER_UNAVAILABLE, provider, true)
105
+ }
106
+ }
107
+
108
+ // ============================================================================
109
+ // Base LLM Provider
110
+ // ============================================================================
111
+
112
+ export abstract class BaseLLMProvider implements LLMProvider {
113
+ abstract readonly type: LLMProviderType
114
+
115
+ protected config: Required<BaseLLMConfig>
116
+ protected cacheConfig: CacheConfig
117
+ protected cache: Map<string, CacheEntry<LLMExtractionResult>> = new Map()
118
+ protected lastSuccess?: Date
119
+
120
+ constructor(config: Partial<BaseLLMConfig> = {}, cacheConfig: Partial<CacheConfig> = {}) {
121
+ this.config = { ...DEFAULT_LLM_CONFIG, ...config }
122
+ this.cacheConfig = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig }
123
+ }
124
+
125
+ // ============================================================================
126
+ // Abstract Methods - Must be implemented by subclasses
127
+ // ============================================================================
128
+
129
+ /**
130
+ * Perform the actual LLM API call for memory extraction
131
+ */
132
+ protected abstract doExtractMemories(
133
+ text: string,
134
+ options: ExtractionOptions
135
+ ): Promise<{
136
+ memories: ExtractedMemory[]
137
+ rawResponse?: string
138
+ tokensUsed?: { prompt: number; completion: number; total: number }
139
+ }>
140
+
141
+ /**
142
+ * Perform the actual LLM API call for relationship detection
143
+ */
144
+ protected abstract doDetectRelationships(
145
+ newMemory: { id: string; content: string; type: MemoryType },
146
+ existingMemories: Array<{ id: string; content: string; type: MemoryType }>,
147
+ options: RelationshipDetectionOptions
148
+ ): Promise<{
149
+ relationships: DetectedRelationship[]
150
+ supersededMemoryIds: string[]
151
+ }>
152
+
153
+ /**
154
+ * Perform a generic JSON-only prompt task.
155
+ */
156
+ protected abstract doGenerateJson(
157
+ systemPrompt: string,
158
+ userPrompt: string
159
+ ): Promise<{
160
+ rawResponse: string
161
+ tokensUsed?: { prompt: number; completion: number; total: number }
162
+ }>
163
+
164
+ /**
165
+ * Check if the provider is configured and available
166
+ */
167
+ abstract isAvailable(): boolean
168
+
169
+ // ============================================================================
170
+ // Public Interface
171
+ // ============================================================================
172
+
173
+ async extractMemories(text: string, options: ExtractionOptions = {}): Promise<LLMExtractionResult> {
174
+ const startTime = Date.now()
175
+
176
+ // Check cache first
177
+ if (this.cacheConfig.enabled) {
178
+ const cached = this.getCachedResult(text, options)
179
+ if (cached) {
180
+ logger.debug('Cache hit for memory extraction', {
181
+ provider: this.type,
182
+ textLength: text.length,
183
+ })
184
+ return {
185
+ ...cached,
186
+ cached: true,
187
+ processingTimeMs: Date.now() - startTime,
188
+ }
189
+ }
190
+ }
191
+
192
+ // Perform extraction with retries
193
+ try {
194
+ const result = await this.withRetry(() => this.doExtractMemories(text, options), 'extractMemories')
195
+
196
+ const llmResult: LLMExtractionResult = {
197
+ memories: result.memories,
198
+ rawResponse: result.rawResponse,
199
+ tokensUsed: result.tokensUsed,
200
+ processingTimeMs: Date.now() - startTime,
201
+ cached: false,
202
+ provider: this.type,
203
+ }
204
+
205
+ // Cache the result
206
+ if (this.cacheConfig.enabled) {
207
+ this.cacheResult(text, options, llmResult)
208
+ }
209
+
210
+ this.lastSuccess = new Date()
211
+ logger.info('Memories extracted successfully', {
212
+ provider: this.type,
213
+ count: result.memories.length,
214
+ processingTimeMs: llmResult.processingTimeMs,
215
+ })
216
+
217
+ return llmResult
218
+ } catch (error) {
219
+ logger.errorWithException('Failed to extract memories', error, {
220
+ provider: this.type,
221
+ textLength: text.length,
222
+ })
223
+ throw error
224
+ }
225
+ }
226
+
227
+ async generateJson(
228
+ systemPrompt: string,
229
+ userPrompt: string
230
+ ): Promise<{
231
+ rawResponse: string
232
+ tokensUsed?: { prompt: number; completion: number; total: number }
233
+ provider: LLMProviderType
234
+ }> {
235
+ try {
236
+ const result = await this.withRetry(() => this.doGenerateJson(systemPrompt, userPrompt), 'generateJson')
237
+
238
+ return {
239
+ rawResponse: result.rawResponse,
240
+ tokensUsed: result.tokensUsed,
241
+ provider: this.type,
242
+ }
243
+ } catch (error) {
244
+ if (error instanceof LLMError) {
245
+ throw error
246
+ }
247
+ throw LLMError.invalidResponse(this.type, error instanceof Error ? error.message : String(error))
248
+ }
249
+ }
250
+
251
+ async detectRelationships(
252
+ newMemory: { id: string; content: string; type: MemoryType },
253
+ existingMemories: Array<{ id: string; content: string; type: MemoryType }>,
254
+ options: RelationshipDetectionOptions = {}
255
+ ): Promise<LLMRelationshipResult> {
256
+ const startTime = Date.now()
257
+
258
+ try {
259
+ const result = await this.withRetry(
260
+ () => this.doDetectRelationships(newMemory, existingMemories, options),
261
+ 'detectRelationships'
262
+ )
263
+
264
+ const llmResult: LLMRelationshipResult = {
265
+ relationships: result.relationships,
266
+ supersededMemoryIds: result.supersededMemoryIds,
267
+ processingTimeMs: Date.now() - startTime,
268
+ provider: this.type,
269
+ }
270
+
271
+ this.lastSuccess = new Date()
272
+ logger.info('Relationships detected successfully', {
273
+ provider: this.type,
274
+ count: result.relationships.length,
275
+ processingTimeMs: llmResult.processingTimeMs,
276
+ })
277
+
278
+ return llmResult
279
+ } catch (error) {
280
+ logger.errorWithException('Failed to detect relationships', error, {
281
+ provider: this.type,
282
+ newMemoryId: newMemory.id,
283
+ existingCount: existingMemories.length,
284
+ })
285
+ throw error
286
+ }
287
+ }
288
+
289
+ async getHealthStatus(): Promise<ProviderHealthStatus> {
290
+ if (!this.isAvailable()) {
291
+ return {
292
+ healthy: false,
293
+ provider: this.type,
294
+ error: 'Provider not configured',
295
+ }
296
+ }
297
+
298
+ try {
299
+ // Simple health check - extract from minimal text
300
+ const startTime = Date.now()
301
+ await this.extractMemories('Health check: The system is operational.', {
302
+ maxMemories: 1,
303
+ minConfidence: 0,
304
+ })
305
+
306
+ return {
307
+ healthy: true,
308
+ provider: this.type,
309
+ latencyMs: Date.now() - startTime,
310
+ lastSuccess: this.lastSuccess,
311
+ }
312
+ } catch (error) {
313
+ return {
314
+ healthy: false,
315
+ provider: this.type,
316
+ error: error instanceof Error ? error.message : String(error),
317
+ lastSuccess: this.lastSuccess,
318
+ }
319
+ }
320
+ }
321
+
322
+ // ============================================================================
323
+ // Retry Logic
324
+ // ============================================================================
325
+
326
+ protected async withRetry<T>(operation: () => Promise<T>, operationName: string): Promise<T> {
327
+ let lastError: Error | undefined
328
+
329
+ for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {
330
+ try {
331
+ return await this.withTimeout(operation(), this.config.timeoutMs)
332
+ } catch (error) {
333
+ lastError = error instanceof Error ? error : new Error(String(error))
334
+
335
+ const isRetryable = error instanceof LLMError ? error.retryable : this.isRetryableError(error)
336
+
337
+ if (!isRetryable || attempt === this.config.maxRetries) {
338
+ throw error
339
+ }
340
+
341
+ // Calculate delay with exponential backoff
342
+ const delay =
343
+ error instanceof LLMError && error.retryAfterMs
344
+ ? error.retryAfterMs
345
+ : this.config.retryDelayMs * Math.pow(2, attempt - 1)
346
+
347
+ logger.warn(`Retrying ${operationName} after error`, {
348
+ provider: this.type,
349
+ attempt,
350
+ maxRetries: this.config.maxRetries,
351
+ delayMs: delay,
352
+ error: lastError.message,
353
+ })
354
+
355
+ await this.sleep(delay)
356
+ }
357
+ }
358
+
359
+ throw lastError
360
+ }
361
+
362
+ protected async withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
363
+ return Promise.race([
364
+ promise,
365
+ new Promise<never>((_, reject) => setTimeout(() => reject(LLMError.timeout(this.type)), timeoutMs)),
366
+ ])
367
+ }
368
+
369
+ protected isRetryableError(error: unknown): boolean {
370
+ if (error instanceof LLMError) {
371
+ return error.retryable
372
+ }
373
+
374
+ // Check for common retryable error patterns
375
+ const message = error instanceof Error ? error.message.toLowerCase() : ''
376
+ return (
377
+ message.includes('timeout') ||
378
+ message.includes('rate limit') ||
379
+ message.includes('429') ||
380
+ message.includes('503') ||
381
+ message.includes('network')
382
+ )
383
+ }
384
+
385
+ protected sleep(ms: number): Promise<void> {
386
+ return new Promise((resolve) => setTimeout(resolve, ms))
387
+ }
388
+
389
+ // ============================================================================
390
+ // Caching
391
+ // ============================================================================
392
+
393
+ protected getCacheKey(text: string, options: ExtractionOptions): string {
394
+ const keyData = JSON.stringify({
395
+ text: text.substring(0, 5000), // Limit for hashing
396
+ provider: this.type,
397
+ options,
398
+ })
399
+ return createHash('sha256').update(keyData).digest('hex')
400
+ }
401
+
402
+ protected getCachedResult(text: string, options: ExtractionOptions): LLMExtractionResult | null {
403
+ const key = this.getCacheKey(text, options)
404
+ const entry = this.cache.get(key)
405
+
406
+ if (!entry) {
407
+ return null
408
+ }
409
+
410
+ // Check if expired
411
+ if (entry.expiresAt < new Date()) {
412
+ this.cache.delete(key)
413
+ return null
414
+ }
415
+
416
+ return entry.value
417
+ }
418
+
419
+ protected cacheResult(text: string, options: ExtractionOptions, result: LLMExtractionResult): void {
420
+ // Enforce cache size limit
421
+ if (this.cache.size >= this.cacheConfig.maxSize) {
422
+ // Remove oldest entries
423
+ const entries = Array.from(this.cache.entries())
424
+ entries.sort((a, b) => a[1].createdAt.getTime() - b[1].createdAt.getTime())
425
+ const toRemove = entries.slice(0, Math.floor(this.cacheConfig.maxSize * 0.1))
426
+ for (const [key] of toRemove) {
427
+ this.cache.delete(key)
428
+ }
429
+ }
430
+
431
+ const key = this.getCacheKey(text, options)
432
+ const now = new Date()
433
+
434
+ this.cache.set(key, {
435
+ value: result,
436
+ createdAt: now,
437
+ expiresAt: new Date(now.getTime() + this.cacheConfig.ttlMs),
438
+ inputHash: key,
439
+ })
440
+ }
441
+
442
+ /**
443
+ * Clear all cached results
444
+ */
445
+ clearCache(): void {
446
+ this.cache.clear()
447
+ logger.debug('Cache cleared', { provider: this.type })
448
+ }
449
+
450
+ /**
451
+ * Get cache statistics
452
+ */
453
+ getCacheStats(): { size: number; maxSize: number; ttlMs: number } {
454
+ return {
455
+ size: this.cache.size,
456
+ maxSize: this.cacheConfig.maxSize,
457
+ ttlMs: this.cacheConfig.ttlMs,
458
+ }
459
+ }
460
+ }