import crypto from 'crypto' import { redisGet, redisSet } from './redis' import { getRedis } from './redis' const CACHE_TTL = 1 * 60 * 60 // 1 hour in seconds (reduced from 7 days) const CACHE_PREFIX = 'cache:prompt:' interface CachedResponse { response: string model: string timestamp: number promptId: string } /** * Generate cache key from prompt inputs * Uses SHA256 hash of: promptId + sorted inputs + model */ export function generateCacheKey( promptId: string, inputs: Record, model: string ): string { // Sort inputs by key for consistent hashing const sortedInputs = Object.keys(inputs) .sort() .reduce((acc, key) => { acc[key] = inputs[key] return acc }, {} as Record) const data = JSON.stringify({ promptId, inputs: sortedInputs, model, }) const hash = crypto.createHash('sha256').update(data).digest('hex') return `${CACHE_PREFIX}${hash}` } /** * Get cached AI response if available */ export async function getCachedResponse( promptId: string, inputs: Record, model: string ): Promise { const key = generateCacheKey(promptId, inputs, model) // Check if cache has been invalidated via version key const client = getRedis() if (client) { const versionKey = `cache:version:${promptId}` const invalidatedAt = await client.get(versionKey) if (invalidatedAt) { const cached = await redisGet(key) // If the cached entry is older than the invalidation, skip it if (cached && cached.timestamp < parseInt(invalidatedAt)) { return null } } } const cached = await redisGet(key) if (!cached) { return null } // Verify the cached data is for the correct prompt/model if (cached.promptId !== promptId || cached.model !== model) { return null } return cached.response } /** * Store AI response in cache */ export async function cacheResponse( promptId: string, inputs: Record, model: string, response: string ): Promise { const key = generateCacheKey(promptId, inputs, model) const data: CachedResponse = { response, model, timestamp: Date.now(), promptId, } return await redisSet(key, data, CACHE_TTL) } /** * Invalidate all cached responses for a prompt. * Uses a prompt-specific version key to effectively invalidate all entries. */ export async function invalidatePromptCache(promptId: string): Promise { const client = getRedis() if (!client) return try { // Set a version key for this prompt — any cached entries with an older // version will be considered stale const versionKey = `cache:version:${promptId}` await client.set(versionKey, Date.now().toString()) } catch (error) { console.error('Cache invalidation error:', error) } }