@visorcraft/idlehands 2.3.4 → 3.0.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.
- package/dist/agent/exec-helpers.js +57 -0
- package/dist/agent/exec-helpers.js.map +1 -1
- package/dist/agent/file-prefetch.js +196 -0
- package/dist/agent/file-prefetch.js.map +1 -0
- package/dist/agent/index.js +54 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/predictive-compaction.js +139 -0
- package/dist/agent/predictive-compaction.js.map +1 -0
- package/dist/agent/query-classifier-fast.js +174 -0
- package/dist/agent/query-classifier-fast.js.map +1 -0
- package/dist/agent/read-ahead-buffer.js +260 -0
- package/dist/agent/read-ahead-buffer.js.map +1 -0
- package/dist/agent/response-cache.js +23 -4
- package/dist/agent/response-cache.js.map +1 -1
- package/dist/agent/schema-optimizer.js +228 -0
- package/dist/agent/schema-optimizer.js.map +1 -0
- package/dist/agent/tool-loop-guard.js +10 -4
- package/dist/agent/tool-loop-guard.js.map +1 -1
- package/dist/agent.js +47 -6
- package/dist/agent.js.map +1 -1
- package/dist/bot/discord.js +5 -5
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/runtime-model-picker.js +4 -0
- package/dist/bot/runtime-model-picker.js.map +1 -1
- package/dist/bot/session-manager.js +6 -6
- package/dist/bot/session-manager.js.map +1 -1
- package/dist/client.js +15 -0
- package/dist/client.js.map +1 -1
- package/dist/runtime/store.js +10 -0
- package/dist/runtime/store.js.map +1 -1
- package/dist/tui/controller.js +2 -0
- package/dist/tui/controller.js.map +1 -1
- package/dist/upgrade.js +3 -3
- package/dist/upgrade.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fast Query Classifier
|
|
3
|
+
*
|
|
4
|
+
* Wraps the standard query classifier with:
|
|
5
|
+
* - LRU caching for repeated/similar queries
|
|
6
|
+
* - Early exit for trivial cases (commands, very short messages)
|
|
7
|
+
* - Lightweight pre-filter before full classification
|
|
8
|
+
*/
|
|
9
|
+
import { classifyWithDecision, } from './query-classifier.js';
|
|
10
|
+
/**
|
|
11
|
+
* Fast classifier with caching and early-exit optimizations.
|
|
12
|
+
*/
|
|
13
|
+
export class FastQueryClassifier {
|
|
14
|
+
cache = new Map();
|
|
15
|
+
maxCacheSize;
|
|
16
|
+
// Stats
|
|
17
|
+
totalCalls = 0;
|
|
18
|
+
cacheHits = 0;
|
|
19
|
+
earlyExits = 0;
|
|
20
|
+
fullClassifications = 0;
|
|
21
|
+
constructor(opts) {
|
|
22
|
+
this.maxCacheSize = opts?.maxCacheSize ?? 200;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Classify a message with caching and early-exit optimizations.
|
|
26
|
+
*/
|
|
27
|
+
classify(config, message) {
|
|
28
|
+
this.totalCalls++;
|
|
29
|
+
// Early exit: disabled or no rules
|
|
30
|
+
if (!config.enabled || config.rules.length === 0) {
|
|
31
|
+
this.earlyExits++;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// Early exit: empty or whitespace-only
|
|
35
|
+
const trimmed = message.trim();
|
|
36
|
+
if (!trimmed) {
|
|
37
|
+
this.earlyExits++;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Early exit: slash commands (handled elsewhere)
|
|
41
|
+
if (trimmed.startsWith('/')) {
|
|
42
|
+
this.earlyExits++;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
// Early exit: very short greetings → fast hint
|
|
46
|
+
if (trimmed.length <= 10 && /^(hi|hey|hello|yo|sup|thanks|thx|ok|yes|no|k|y|n)$/i.test(trimmed)) {
|
|
47
|
+
this.earlyExits++;
|
|
48
|
+
return { hint: 'fast', priority: 1 };
|
|
49
|
+
}
|
|
50
|
+
// Check cache
|
|
51
|
+
const cacheKey = this.computeCacheKey(message);
|
|
52
|
+
const cached = this.cache.get(cacheKey);
|
|
53
|
+
if (cached) {
|
|
54
|
+
cached.accessedAt = Date.now();
|
|
55
|
+
this.cacheHits++;
|
|
56
|
+
return cached.decision;
|
|
57
|
+
}
|
|
58
|
+
// Fast pre-filter: check for code patterns before full classification
|
|
59
|
+
const prefilterResult = this.prefilter(trimmed);
|
|
60
|
+
if (prefilterResult) {
|
|
61
|
+
this.cacheResult(cacheKey, prefilterResult);
|
|
62
|
+
this.earlyExits++;
|
|
63
|
+
return prefilterResult;
|
|
64
|
+
}
|
|
65
|
+
// Full classification
|
|
66
|
+
const decision = classifyWithDecision(config, message);
|
|
67
|
+
this.cacheResult(cacheKey, decision);
|
|
68
|
+
this.fullClassifications++;
|
|
69
|
+
return decision;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Lightweight pre-filter for obvious cases.
|
|
73
|
+
* Returns a decision if matched, null to proceed to full classification.
|
|
74
|
+
*/
|
|
75
|
+
prefilter(message) {
|
|
76
|
+
// Code patterns: high confidence code hint
|
|
77
|
+
const codePatterns = [
|
|
78
|
+
/^```\w*/, // Code block start
|
|
79
|
+
/^(import|export|from)\s/, // JS/TS imports
|
|
80
|
+
/^(def|class|async def)\s/, // Python
|
|
81
|
+
/^(fn|pub fn|impl|struct)\s/, // Rust
|
|
82
|
+
/^(func|type|package)\s/, // Go
|
|
83
|
+
/^(public|private|protected)\s/, // Java/C#
|
|
84
|
+
];
|
|
85
|
+
for (const pattern of codePatterns) {
|
|
86
|
+
if (pattern.test(message)) {
|
|
87
|
+
return { hint: 'code', priority: 10 };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Question patterns: reasoning hint
|
|
91
|
+
if (message.length > 50 && /^(why|how|what|explain|compare|analyze)\s/i.test(message)) {
|
|
92
|
+
return { hint: 'reasoning', priority: 3 };
|
|
93
|
+
}
|
|
94
|
+
// Single word or very short → fast
|
|
95
|
+
if (message.length < 20 && !/\s/.test(message.trim())) {
|
|
96
|
+
return { hint: 'fast', priority: 1 };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Compute cache key from message.
|
|
102
|
+
* Normalizes whitespace and truncates for memory efficiency.
|
|
103
|
+
*/
|
|
104
|
+
computeCacheKey(message) {
|
|
105
|
+
// Normalize whitespace and truncate to first 500 chars
|
|
106
|
+
const normalized = message.trim().replace(/\s+/g, ' ').slice(0, 500);
|
|
107
|
+
return normalized.toLowerCase();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Cache a classification result.
|
|
111
|
+
*/
|
|
112
|
+
cacheResult(key, decision) {
|
|
113
|
+
// Evict oldest if at capacity
|
|
114
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
115
|
+
this.evictOldest();
|
|
116
|
+
}
|
|
117
|
+
this.cache.set(key, {
|
|
118
|
+
decision,
|
|
119
|
+
accessedAt: Date.now(),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Evict the least recently accessed entry.
|
|
124
|
+
*/
|
|
125
|
+
evictOldest() {
|
|
126
|
+
let oldestKey = null;
|
|
127
|
+
let oldestTime = Infinity;
|
|
128
|
+
for (const [key, entry] of this.cache) {
|
|
129
|
+
if (entry.accessedAt < oldestTime) {
|
|
130
|
+
oldestTime = entry.accessedAt;
|
|
131
|
+
oldestKey = key;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (oldestKey) {
|
|
135
|
+
this.cache.delete(oldestKey);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Clear the classification cache.
|
|
140
|
+
*/
|
|
141
|
+
clearCache() {
|
|
142
|
+
this.cache.clear();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get classifier statistics.
|
|
146
|
+
*/
|
|
147
|
+
stats() {
|
|
148
|
+
const total = this.totalCalls || 1;
|
|
149
|
+
return {
|
|
150
|
+
totalCalls: this.totalCalls,
|
|
151
|
+
cacheHits: this.cacheHits,
|
|
152
|
+
earlyExits: this.earlyExits,
|
|
153
|
+
fullClassifications: this.fullClassifications,
|
|
154
|
+
hitRate: this.cacheHits / total,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Singleton instance for shared use.
|
|
160
|
+
*/
|
|
161
|
+
let defaultInstance = null;
|
|
162
|
+
export function getDefaultFastClassifier() {
|
|
163
|
+
if (!defaultInstance) {
|
|
164
|
+
defaultInstance = new FastQueryClassifier();
|
|
165
|
+
}
|
|
166
|
+
return defaultInstance;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Convenience function: classify with the default fast classifier.
|
|
170
|
+
*/
|
|
171
|
+
export function fastClassify(config, message) {
|
|
172
|
+
return getDefaultFastClassifier().classify(config, message);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=query-classifier-fast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-classifier-fast.js","sourceRoot":"","sources":["../../src/agent/query-classifier-fast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-Ahead Buffer
|
|
3
|
+
*
|
|
4
|
+
* When reading a file section, pre-caches nearby lines to speed up
|
|
5
|
+
* sequential reads. Common pattern: model reads lines 1-50, then 51-100, etc.
|
|
6
|
+
*
|
|
7
|
+
* Also provides write coalescing for multiple small writes to the same file.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
export class ReadAheadBuffer {
|
|
11
|
+
cache = new Map();
|
|
12
|
+
maxCacheSize;
|
|
13
|
+
maxFileSize;
|
|
14
|
+
ttlMs;
|
|
15
|
+
// Stats
|
|
16
|
+
hits = 0;
|
|
17
|
+
misses = 0;
|
|
18
|
+
fullReads = 0;
|
|
19
|
+
constructor(opts) {
|
|
20
|
+
this.maxCacheSize = opts?.maxCacheSize ?? 30;
|
|
21
|
+
this.maxFileSize = opts?.maxFileSize ?? 1024 * 1024;
|
|
22
|
+
this.ttlMs = opts?.ttlMs ?? 60000;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Read lines from a file with read-ahead caching.
|
|
26
|
+
*
|
|
27
|
+
* @param absPath Absolute path to file
|
|
28
|
+
* @param offset 1-indexed line offset (default: 1)
|
|
29
|
+
* @param limit Maximum lines to return
|
|
30
|
+
* @returns Array of lines
|
|
31
|
+
*/
|
|
32
|
+
async readLines(absPath, offset = 1, limit) {
|
|
33
|
+
const entry = await this.getOrLoad(absPath);
|
|
34
|
+
if (!entry) {
|
|
35
|
+
this.misses++;
|
|
36
|
+
// Fallback to direct read
|
|
37
|
+
return this.directRead(absPath, offset, limit);
|
|
38
|
+
}
|
|
39
|
+
this.hits++;
|
|
40
|
+
const startIdx = Math.max(0, offset - 1);
|
|
41
|
+
const endIdx = limit ? startIdx + limit : entry.lines.length;
|
|
42
|
+
return {
|
|
43
|
+
lines: entry.lines.slice(startIdx, endIdx),
|
|
44
|
+
totalLines: entry.lines.length,
|
|
45
|
+
fromCache: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get cached entry or load file into cache.
|
|
50
|
+
*/
|
|
51
|
+
async getOrLoad(absPath) {
|
|
52
|
+
const cached = this.cache.get(absPath);
|
|
53
|
+
if (cached) {
|
|
54
|
+
// Check TTL
|
|
55
|
+
if (Date.now() - cached.cachedAt > this.ttlMs) {
|
|
56
|
+
this.cache.delete(absPath);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Check mtime for invalidation
|
|
60
|
+
try {
|
|
61
|
+
const stat = await fs.stat(absPath);
|
|
62
|
+
if (stat.mtimeMs === cached.mtime) {
|
|
63
|
+
return cached;
|
|
64
|
+
}
|
|
65
|
+
// File changed, invalidate
|
|
66
|
+
this.cache.delete(absPath);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
this.cache.delete(absPath);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Load file into cache
|
|
75
|
+
return this.loadFile(absPath);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Load a file into the cache.
|
|
79
|
+
*/
|
|
80
|
+
async loadFile(absPath) {
|
|
81
|
+
try {
|
|
82
|
+
const stat = await fs.stat(absPath);
|
|
83
|
+
// Skip if too large
|
|
84
|
+
if (stat.size > this.maxFileSize) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
// Skip if not a regular file
|
|
88
|
+
if (!stat.isFile()) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const content = await fs.readFile(absPath, 'utf8');
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
this.fullReads++;
|
|
94
|
+
const entry = {
|
|
95
|
+
lines,
|
|
96
|
+
mtime: stat.mtimeMs,
|
|
97
|
+
cachedAt: Date.now(),
|
|
98
|
+
size: stat.size,
|
|
99
|
+
};
|
|
100
|
+
// Evict if over capacity
|
|
101
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
102
|
+
this.evictOldest();
|
|
103
|
+
}
|
|
104
|
+
this.cache.set(absPath, entry);
|
|
105
|
+
return entry;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Direct read without caching (fallback for large files).
|
|
113
|
+
*/
|
|
114
|
+
async directRead(absPath, offset, limit) {
|
|
115
|
+
try {
|
|
116
|
+
const content = await fs.readFile(absPath, 'utf8');
|
|
117
|
+
const allLines = content.split('\n');
|
|
118
|
+
const startIdx = Math.max(0, offset - 1);
|
|
119
|
+
const endIdx = limit ? startIdx + limit : allLines.length;
|
|
120
|
+
return {
|
|
121
|
+
lines: allLines.slice(startIdx, endIdx),
|
|
122
|
+
totalLines: allLines.length,
|
|
123
|
+
fromCache: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
return { lines: [], totalLines: 0, fromCache: false };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Evict the oldest cached entry.
|
|
132
|
+
*/
|
|
133
|
+
evictOldest() {
|
|
134
|
+
let oldestKey = null;
|
|
135
|
+
let oldestTime = Infinity;
|
|
136
|
+
for (const [key, entry] of this.cache) {
|
|
137
|
+
if (entry.cachedAt < oldestTime) {
|
|
138
|
+
oldestTime = entry.cachedAt;
|
|
139
|
+
oldestKey = key;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (oldestKey) {
|
|
143
|
+
this.cache.delete(oldestKey);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Invalidate a cached file (e.g., after write).
|
|
148
|
+
*/
|
|
149
|
+
invalidate(absPath) {
|
|
150
|
+
this.cache.delete(absPath);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Invalidate all files in a directory.
|
|
154
|
+
*/
|
|
155
|
+
invalidateDir(dirPath) {
|
|
156
|
+
for (const key of this.cache.keys()) {
|
|
157
|
+
if (key.startsWith(dirPath)) {
|
|
158
|
+
this.cache.delete(key);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Clear all cached entries.
|
|
164
|
+
*/
|
|
165
|
+
clear() {
|
|
166
|
+
this.cache.clear();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get buffer statistics.
|
|
170
|
+
*/
|
|
171
|
+
stats() {
|
|
172
|
+
let cachedBytes = 0;
|
|
173
|
+
for (const entry of this.cache.values()) {
|
|
174
|
+
cachedBytes += entry.size;
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
hits: this.hits,
|
|
178
|
+
misses: this.misses,
|
|
179
|
+
fullReads: this.fullReads,
|
|
180
|
+
hitRate: this.hits + this.misses > 0 ? this.hits / (this.hits + this.misses) : 0,
|
|
181
|
+
cachedFiles: this.cache.size,
|
|
182
|
+
cachedBytes,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if a file is cached.
|
|
187
|
+
*/
|
|
188
|
+
isCached(absPath) {
|
|
189
|
+
return this.cache.has(absPath);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get all cached file paths.
|
|
193
|
+
*/
|
|
194
|
+
getCachedPaths() {
|
|
195
|
+
return [...this.cache.keys()];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Write coalescing buffer.
|
|
200
|
+
* Batches multiple small writes to the same file.
|
|
201
|
+
*/
|
|
202
|
+
export class WriteCoalescer {
|
|
203
|
+
pending = new Map();
|
|
204
|
+
flushDelayMs;
|
|
205
|
+
flushTimer = null;
|
|
206
|
+
constructor(opts) {
|
|
207
|
+
this.flushDelayMs = opts?.flushDelayMs ?? 100;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Schedule a write to be coalesced.
|
|
211
|
+
* Returns a promise that resolves when the write is flushed.
|
|
212
|
+
*/
|
|
213
|
+
async write(absPath, content) {
|
|
214
|
+
this.pending.set(absPath, { content, scheduledAt: Date.now() });
|
|
215
|
+
// Schedule flush if not already scheduled
|
|
216
|
+
if (!this.flushTimer) {
|
|
217
|
+
this.flushTimer = setTimeout(() => this.flush(), this.flushDelayMs);
|
|
218
|
+
}
|
|
219
|
+
// Return immediately - write will happen on flush
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Flush all pending writes immediately.
|
|
223
|
+
*/
|
|
224
|
+
async flush() {
|
|
225
|
+
if (this.flushTimer) {
|
|
226
|
+
clearTimeout(this.flushTimer);
|
|
227
|
+
this.flushTimer = null;
|
|
228
|
+
}
|
|
229
|
+
const writes = [...this.pending.entries()];
|
|
230
|
+
this.pending.clear();
|
|
231
|
+
// Write all files in parallel
|
|
232
|
+
await Promise.all(writes.map(async ([absPath, { content }]) => {
|
|
233
|
+
try {
|
|
234
|
+
await fs.writeFile(absPath, content, 'utf8');
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
console.error(`[write-coalescer] Failed to write ${absPath}:`, e);
|
|
238
|
+
}
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get pending write for a file (if any).
|
|
243
|
+
*/
|
|
244
|
+
getPending(absPath) {
|
|
245
|
+
return this.pending.get(absPath)?.content ?? null;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Cancel a pending write.
|
|
249
|
+
*/
|
|
250
|
+
cancel(absPath) {
|
|
251
|
+
return this.pending.delete(absPath);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get number of pending writes.
|
|
255
|
+
*/
|
|
256
|
+
pendingCount() {
|
|
257
|
+
return this.pending.size;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=read-ahead-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-ahead-buffer.js","sourceRoot":"","sources":["../../src/agent/read-ahead-buffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,11 @@ export class ResponseCache {
|
|
|
24
24
|
db;
|
|
25
25
|
ttlMinutes;
|
|
26
26
|
maxEntries;
|
|
27
|
+
// Runtime metrics (not persisted, reset on restart)
|
|
28
|
+
lookups = 0;
|
|
29
|
+
hits = 0;
|
|
30
|
+
misses = 0;
|
|
31
|
+
evictions = 0;
|
|
27
32
|
constructor(options) {
|
|
28
33
|
this.ttlMinutes = options.ttlMinutes ?? 60;
|
|
29
34
|
this.maxEntries = options.maxEntries ?? 500;
|
|
@@ -62,20 +67,25 @@ export class ResponseCache {
|
|
|
62
67
|
* Look up a cached response. Returns null on miss or expired entry.
|
|
63
68
|
*/
|
|
64
69
|
get(model, systemPrompt, userPrompt) {
|
|
70
|
+
this.lookups++;
|
|
65
71
|
const key = this.computeKey(model, systemPrompt, userPrompt);
|
|
66
72
|
const row = this.db.prepare(`SELECT response, created_at FROM response_cache WHERE prompt_hash = ?`).get(key);
|
|
67
|
-
if (!row)
|
|
73
|
+
if (!row) {
|
|
74
|
+
this.misses++;
|
|
68
75
|
return null;
|
|
76
|
+
}
|
|
69
77
|
// Check TTL
|
|
70
78
|
const createdAt = new Date(row.created_at).getTime();
|
|
71
79
|
const now = Date.now();
|
|
72
80
|
if (now - createdAt > this.ttlMinutes * 60 * 1000) {
|
|
73
81
|
// Expired — delete and return miss
|
|
74
82
|
this.db.prepare('DELETE FROM response_cache WHERE prompt_hash = ?').run(key);
|
|
83
|
+
this.misses++;
|
|
75
84
|
return null;
|
|
76
85
|
}
|
|
77
86
|
// Update access time and hit count
|
|
78
87
|
this.db.prepare(`UPDATE response_cache SET accessed_at = ?, hit_count = hit_count + 1 WHERE prompt_hash = ?`).run(nowIso(), key);
|
|
88
|
+
this.hits++;
|
|
79
89
|
return row.response;
|
|
80
90
|
}
|
|
81
91
|
/**
|
|
@@ -97,7 +107,8 @@ export class ResponseCache {
|
|
|
97
107
|
evict() {
|
|
98
108
|
// Remove expired entries
|
|
99
109
|
const cutoff = new Date(Date.now() - this.ttlMinutes * 60 * 1000).toISOString();
|
|
100
|
-
this.db.prepare('DELETE FROM response_cache WHERE created_at < ?').run(cutoff);
|
|
110
|
+
const expiredResult = this.db.prepare('DELETE FROM response_cache WHERE created_at < ?').run(cutoff);
|
|
111
|
+
this.evictions += Number(expiredResult.changes);
|
|
101
112
|
// Enforce max entries (LRU eviction)
|
|
102
113
|
const count = this.db.prepare('SELECT COUNT(*) as c FROM response_cache').get().c;
|
|
103
114
|
if (count > this.maxEntries) {
|
|
@@ -105,14 +116,22 @@ export class ResponseCache {
|
|
|
105
116
|
this.db.prepare(`DELETE FROM response_cache WHERE prompt_hash IN (
|
|
106
117
|
SELECT prompt_hash FROM response_cache ORDER BY accessed_at ASC LIMIT ?
|
|
107
118
|
)`).run(excess);
|
|
119
|
+
this.evictions += excess;
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
/**
|
|
111
|
-
* Get cache statistics.
|
|
123
|
+
* Get cache statistics including runtime metrics.
|
|
112
124
|
*/
|
|
113
125
|
stats() {
|
|
114
126
|
const row = this.db.prepare('SELECT COUNT(*) as entries, COALESCE(SUM(hit_count), 0) as totalHits FROM response_cache').get();
|
|
115
|
-
return
|
|
127
|
+
return {
|
|
128
|
+
...row,
|
|
129
|
+
lookups: this.lookups,
|
|
130
|
+
hits: this.hits,
|
|
131
|
+
misses: this.misses,
|
|
132
|
+
evictions: this.evictions,
|
|
133
|
+
hitRate: this.lookups > 0 ? this.hits / this.lookups : 0,
|
|
134
|
+
};
|
|
116
135
|
}
|
|
117
136
|
/**
|
|
118
137
|
* Clear the entire cache.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-cache.js","sourceRoot":"","sources":["../../src/agent/response-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,MAAM;IACb,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;
|
|
1
|
+
{"version":3,"file":"response-cache.js","sourceRoot":"","sources":["../../src/agent/response-cache.ts"],"names":[],"mappings":"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"}
|