import { Router } from 'express'; import { MemoryRepository } from './memoryRepository.js'; import { MemoryEntityInput, CmaContextRequest } from '@widget-tdc/mcp-types'; export const memoryRouter = Router(); const memoryRepo = new MemoryRepository(); // Simple in-memory cache with TTL class SimpleCache { private cache = new Map(); set(key: string, data: any, ttlMs: number = 300000): void { // 5 min default this.cache.set(key, { data, expiry: Date.now() + ttlMs }); } get(key: string): any | null { const entry = this.cache.get(key); if (!entry) return null; if (Date.now() > entry.expiry) { this.cache.delete(key); return null; } return entry.data; } clear(): void { this.cache.clear(); } } const contextCache = new SimpleCache(); // Ingest a memory entity memoryRouter.post('/ingest', async (req, res) => { try { const input: MemoryEntityInput = req.body; if (!input.orgId || !input.entityType || !input.content) { return res.status(400).json({ error: 'Missing required fields: orgId, entityType, content', }); } const entityId = await memoryRepo.ingestEntity(input); // Clear context cache when new memory is added to ensure freshness contextCache.clear(); res.json({ success: true, id: entityId, }); } catch (error: any) { console.error('Memory ingest error:', error); res.status(500).json({ success: false, error: error.message, }); } }); // Get contextual prompt with memories memoryRouter.post('/contextual-prompt', async (req, res) => { try { const request: CmaContextRequest = req.body; if (!request.orgId || !request.userId || !request.userQuery) { return res.status(400).json({ error: 'Missing required fields: orgId, userId, userQuery', }); } // Create cache key from request parameters const cacheKey = `${request.orgId}:${request.userId}:${request.userQuery}:${JSON.stringify(request.keywords || [])}:${request.widgetData || ''}`; // Check cache first const cachedResult = contextCache.get(cacheKey); if (cachedResult) { return res.json({ success: true, ...cachedResult, cached: true, }); } // Search for relevant memories with enhanced semantic search const memories = await memoryRepo.searchEntities({ orgId: request.orgId, userId: request.userId, keywords: request.keywords || [], limit: 5, }); // Build enhanced contextual prompt with semantic scoring const memoryContext = memories.map(m => { const score = m.semanticScore ? ` (semantic score: ${(m.semanticScore * 100).toFixed(1)}%)` : ''; return `[${m.entity_type}] ${m.content} (importance: ${m.importance})${score}`; }).join('\n'); const prompt = ` Context from memory (enhanced with semantic search): ${memoryContext} Widget data: ${request.widgetData || 'None'} User query: ${request.userQuery} Please provide a hyper-contextual response considering the semantic relationships and importance scores above. `.trim(); const result = { prompt, memories: memories.map(m => ({ id: m.id, content: m.content, importance: m.importance, semanticScore: m.semanticScore, })), }; // Cache the result for 5 minutes contextCache.set(cacheKey, result, 300000); res.json({ success: true, ...result, cached: false, }); } catch (error: any) { console.error('Contextual prompt error:', error); res.status(500).json({ success: false, error: error.message, }); } }); // Search memories memoryRouter.post('/search', async (req, res) => { try { const query = req.body; const memories = await memoryRepo.searchEntities(query); res.json({ success: true, memories, count: memories.length, }); } catch (error: any) { console.error('Memory search error:', error); res.status(500).json({ success: false, error: error.message, }); } });