| 70 | } |
| 71 | } |
| 72 | class PgCache { |
| 73 | async get(key) { |
| 74 | // Lazy import — keeps tests that don't exercise PG mode free of pg dependency. |
| 75 | // eslint-disable-next-line @typescript-eslint/no-require-imports |
| 76 | const { query } = require("../db"); |
| 77 | const rows = await query(`UPDATE prompt_cache SET hits = hits + 1, last_hit_at = NOW() |
| 78 | WHERE cache_key = $1 AND expires_at > NOW() RETURNING *`, [key]); |
| 79 | if (rows.length === 0) |
| 80 | return null; |
| 81 | const r = rows[0]; |
| 82 | return { |
| 83 | value: r.output_value, |
| 84 | prompt_tokens: r.prompt_tokens, |
| 85 | completion_tokens: r.completion_tokens, |
| 86 | cost_usd: r.cost_usd === null ? null : Number(r.cost_usd), |
| 87 | cached_at: new Date(r.created_at).getTime(), |
| 88 | expires_at: new Date(r.expires_at).getTime(), |
| 89 | }; |
| 90 | } |
| 91 | async put(key, prompt_name, model_id, value, ttl_ms, usage) { |
| 92 | // eslint-disable-next-line @typescript-eslint/no-require-imports |
| 93 | const { query } = require("../db"); |
| 94 | await query(`INSERT INTO prompt_cache (cache_key, prompt_name, model_id, output_value, prompt_tokens, completion_tokens, cost_usd, expires_at) |
| 95 | VALUES ($1, $2, $3, $4, $5, $6, $7, NOW() + ($8 || ' milliseconds')::interval) |
| 96 | ON CONFLICT (cache_key) DO UPDATE SET |
| 97 | output_value = EXCLUDED.output_value, |
| 98 | expires_at = EXCLUDED.expires_at, |
| 99 | last_hit_at = NULL, |
| 100 | hits = 0`, [key, prompt_name, model_id, JSON.stringify(value), usage.prompt_tokens ?? null, usage.completion_tokens ?? null, usage.cost_usd ?? null, ttl_ms]); |
| 101 | } |
| 102 | } |
| 103 | function chooseBackend() { |
| 104 | const mode = process.env.LLM_CACHE_MODE || "memory"; |
| 105 | if (mode === "pg") |
nothing calls this directly
no outgoing calls
no test coverage detected