@jamesaphoenix/tx-test-utils 0.4.2
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/database/index.d.ts +8 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +7 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/test-database.d.ts +101 -0
- package/dist/database/test-database.d.ts.map +1 -0
- package/dist/database/test-database.js +130 -0
- package/dist/database/test-database.js.map +1 -0
- package/dist/factories/anchor.factory.d.ts +117 -0
- package/dist/factories/anchor.factory.d.ts.map +1 -0
- package/dist/factories/anchor.factory.js +201 -0
- package/dist/factories/anchor.factory.js.map +1 -0
- package/dist/factories/candidate.factory.d.ts +151 -0
- package/dist/factories/candidate.factory.d.ts.map +1 -0
- package/dist/factories/candidate.factory.js +194 -0
- package/dist/factories/candidate.factory.js.map +1 -0
- package/dist/factories/edge.factory.d.ts +119 -0
- package/dist/factories/edge.factory.d.ts.map +1 -0
- package/dist/factories/edge.factory.js +191 -0
- package/dist/factories/edge.factory.js.map +1 -0
- package/dist/factories/factories.test.d.ts +8 -0
- package/dist/factories/factories.test.d.ts.map +1 -0
- package/dist/factories/factories.test.js +419 -0
- package/dist/factories/factories.test.js.map +1 -0
- package/dist/factories/index.d.ts +15 -0
- package/dist/factories/index.d.ts.map +1 -0
- package/dist/factories/index.js +21 -0
- package/dist/factories/index.js.map +1 -0
- package/dist/factories/learning.factory.d.ts +107 -0
- package/dist/factories/learning.factory.d.ts.map +1 -0
- package/dist/factories/learning.factory.js +150 -0
- package/dist/factories/learning.factory.js.map +1 -0
- package/dist/factories/task.factory.d.ts +106 -0
- package/dist/factories/task.factory.d.ts.map +1 -0
- package/dist/factories/task.factory.js +151 -0
- package/dist/factories/task.factory.js.map +1 -0
- package/dist/fixtures/index.d.ts +36 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +47 -0
- package/dist/fixtures/index.js.map +1 -0
- package/dist/helpers/effect.d.ts +186 -0
- package/dist/helpers/effect.d.ts.map +1 -0
- package/dist/helpers/effect.js +298 -0
- package/dist/helpers/effect.js.map +1 -0
- package/dist/helpers/effect.test.d.ts +7 -0
- package/dist/helpers/effect.test.d.ts.map +1 -0
- package/dist/helpers/effect.test.js +271 -0
- package/dist/helpers/effect.test.js.map +1 -0
- package/dist/helpers/index.d.ts +7 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +11 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-cache/cache.d.ts +152 -0
- package/dist/llm-cache/cache.d.ts.map +1 -0
- package/dist/llm-cache/cache.js +199 -0
- package/dist/llm-cache/cache.js.map +1 -0
- package/dist/llm-cache/cache.test.d.ts +7 -0
- package/dist/llm-cache/cache.test.d.ts.map +1 -0
- package/dist/llm-cache/cache.test.js +310 -0
- package/dist/llm-cache/cache.test.js.map +1 -0
- package/dist/llm-cache/cli.d.ts +113 -0
- package/dist/llm-cache/cli.d.ts.map +1 -0
- package/dist/llm-cache/cli.js +248 -0
- package/dist/llm-cache/cli.js.map +1 -0
- package/dist/llm-cache/index.d.ts +31 -0
- package/dist/llm-cache/index.d.ts.map +1 -0
- package/dist/llm-cache/index.js +31 -0
- package/dist/llm-cache/index.js.map +1 -0
- package/dist/mocks/anthropic.mock.d.ts +173 -0
- package/dist/mocks/anthropic.mock.d.ts.map +1 -0
- package/dist/mocks/anthropic.mock.js +125 -0
- package/dist/mocks/anthropic.mock.js.map +1 -0
- package/dist/mocks/ast-grep.mock.d.ts +216 -0
- package/dist/mocks/ast-grep.mock.d.ts.map +1 -0
- package/dist/mocks/ast-grep.mock.js +164 -0
- package/dist/mocks/ast-grep.mock.js.map +1 -0
- package/dist/mocks/file-system.mock.d.ts +181 -0
- package/dist/mocks/file-system.mock.d.ts.map +1 -0
- package/dist/mocks/file-system.mock.js +280 -0
- package/dist/mocks/file-system.mock.js.map +1 -0
- package/dist/mocks/index.d.ts +10 -0
- package/dist/mocks/index.d.ts.map +1 -0
- package/dist/mocks/index.js +16 -0
- package/dist/mocks/index.js.map +1 -0
- package/dist/mocks/mocks.test.d.ts +10 -0
- package/dist/mocks/mocks.test.d.ts.map +1 -0
- package/dist/mocks/mocks.test.js +961 -0
- package/dist/mocks/mocks.test.js.map +1 -0
- package/dist/mocks/openai.mock.d.ts +205 -0
- package/dist/mocks/openai.mock.d.ts.map +1 -0
- package/dist/mocks/openai.mock.js +178 -0
- package/dist/mocks/openai.mock.js.map +1 -0
- package/dist/setup/index.d.ts +7 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +9 -0
- package/dist/setup/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM response caching with SHA256 keys.
|
|
3
|
+
*
|
|
4
|
+
* Caches expensive LLM API calls by hashing inputs with SHA256.
|
|
5
|
+
* Supports version-based cache invalidation and various bypass modes.
|
|
6
|
+
*
|
|
7
|
+
* @module @tx/test-utils/llm-cache/cache
|
|
8
|
+
*/
|
|
9
|
+
import * as crypto from "crypto";
|
|
10
|
+
import * as fs from "fs/promises";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
/**
|
|
13
|
+
* Get the default cache directory.
|
|
14
|
+
* Checks TX_LLM_CACHE_DIR environment variable first, then falls back to default.
|
|
15
|
+
*/
|
|
16
|
+
const getDefaultCacheDir = () => {
|
|
17
|
+
return process.env.TX_LLM_CACHE_DIR || "test/fixtures/llm-cache";
|
|
18
|
+
};
|
|
19
|
+
let config = {
|
|
20
|
+
cacheDir: getDefaultCacheDir(),
|
|
21
|
+
logging: !process.env.CI
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Configure LLM cache settings.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* configureLLMCache({
|
|
29
|
+
* cacheDir: 'custom/cache/path',
|
|
30
|
+
* logging: false
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const configureLLMCache = (options) => {
|
|
35
|
+
config = { ...config, ...options };
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Get current cache configuration.
|
|
39
|
+
*/
|
|
40
|
+
export const getCacheConfig = () => config;
|
|
41
|
+
/**
|
|
42
|
+
* Reset cache configuration to defaults.
|
|
43
|
+
*/
|
|
44
|
+
export const resetCacheConfig = () => {
|
|
45
|
+
config = {
|
|
46
|
+
cacheDir: getDefaultCacheDir(),
|
|
47
|
+
logging: !process.env.CI
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Core Functions
|
|
52
|
+
// =============================================================================
|
|
53
|
+
/**
|
|
54
|
+
* Compute SHA256 hash of input string for cache key.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const hash = hashInput("What is the capital of France?")
|
|
59
|
+
* // -> "a1b2c3d4e5f6..." (64 hex chars)
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export const hashInput = (input) => {
|
|
63
|
+
return crypto.createHash("sha256").update(input).digest("hex");
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Internal logging helper.
|
|
67
|
+
*/
|
|
68
|
+
const log = (message) => {
|
|
69
|
+
if (config.logging) {
|
|
70
|
+
console.log(message);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Get cache file path for a given hash.
|
|
75
|
+
*/
|
|
76
|
+
const getCacheFilePath = (hash) => {
|
|
77
|
+
return path.join(config.cacheDir, `${hash}.json`);
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Get cached LLM response or execute and cache.
|
|
81
|
+
*
|
|
82
|
+
* Features:
|
|
83
|
+
* - TX_NO_LLM_CACHE=1 env var bypasses cache entirely
|
|
84
|
+
* - forceRefresh option forces fresh call even if cached
|
|
85
|
+
* - version option enables cache invalidation on version mismatch
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const result = await cachedLLMCall(
|
|
90
|
+
* "What is the capital of France?",
|
|
91
|
+
* "claude-sonnet-4",
|
|
92
|
+
* async () => {
|
|
93
|
+
* const response = await anthropic.messages.create({...})
|
|
94
|
+
* return response.content[0].text
|
|
95
|
+
* },
|
|
96
|
+
* { version: 2 }
|
|
97
|
+
* )
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export const cachedLLMCall = async (input, model, call, options = {}) => {
|
|
101
|
+
// Bypass cache if env var is set or forceRefresh is true
|
|
102
|
+
if (process.env.TX_NO_LLM_CACHE === "1" || options.forceRefresh) {
|
|
103
|
+
log(`[LLM Cache BYPASS] Calling ${model} directly`);
|
|
104
|
+
return call();
|
|
105
|
+
}
|
|
106
|
+
const inputHash = hashInput(input);
|
|
107
|
+
const cacheFile = getCacheFilePath(inputHash);
|
|
108
|
+
// Try to read from cache
|
|
109
|
+
try {
|
|
110
|
+
const content = await fs.readFile(cacheFile, "utf-8");
|
|
111
|
+
const cached = JSON.parse(content);
|
|
112
|
+
// Version mismatch triggers cache miss
|
|
113
|
+
if (options.version !== undefined && cached.version !== options.version) {
|
|
114
|
+
log(`[LLM Cache VERSION MISMATCH] ${inputHash.slice(0, 12)}... (cached: v${cached.version}, requested: v${options.version})`);
|
|
115
|
+
throw new Error("Version mismatch");
|
|
116
|
+
}
|
|
117
|
+
log(`[LLM Cache HIT] ${inputHash.slice(0, 12)}...`);
|
|
118
|
+
return cached.response;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Cache miss - file doesn't exist, is corrupted, or version mismatch
|
|
122
|
+
}
|
|
123
|
+
// Execute the call
|
|
124
|
+
log(`[LLM Cache MISS] ${inputHash.slice(0, 12)}... calling ${model}`);
|
|
125
|
+
const response = await call();
|
|
126
|
+
// Store in cache
|
|
127
|
+
try {
|
|
128
|
+
await fs.mkdir(config.cacheDir, { recursive: true });
|
|
129
|
+
const entry = {
|
|
130
|
+
inputHash,
|
|
131
|
+
input: input.slice(0, 1000), // Truncate for readability
|
|
132
|
+
response,
|
|
133
|
+
model,
|
|
134
|
+
cachedAt: new Date().toISOString(),
|
|
135
|
+
version: options.version ?? 1
|
|
136
|
+
};
|
|
137
|
+
await fs.writeFile(cacheFile, JSON.stringify(entry, null, 2), "utf-8");
|
|
138
|
+
log(`[LLM Cache STORED] ${inputHash.slice(0, 12)}...`);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
// Don't fail on cache write errors - just log and continue
|
|
142
|
+
log(`[LLM Cache WRITE ERROR] ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
143
|
+
}
|
|
144
|
+
return response;
|
|
145
|
+
};
|
|
146
|
+
// =============================================================================
|
|
147
|
+
// Higher-Order Function Wrapper
|
|
148
|
+
// =============================================================================
|
|
149
|
+
/**
|
|
150
|
+
* Wrap an async function to use LLM caching.
|
|
151
|
+
*
|
|
152
|
+
* Creates a cached version of any async function. The input is serialized
|
|
153
|
+
* and hashed to create a cache key.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* // Wrap an extraction function
|
|
158
|
+
* const cachedExtract = withLLMCache(
|
|
159
|
+
* async (transcript: string) => {
|
|
160
|
+
* const response = await anthropic.messages.create({
|
|
161
|
+
* model: "claude-sonnet-4-20250514",
|
|
162
|
+
* messages: [{ role: "user", content: extractPrompt(transcript) }]
|
|
163
|
+
* })
|
|
164
|
+
* return JSON.parse(response.content[0].text)
|
|
165
|
+
* },
|
|
166
|
+
* { model: "claude-sonnet-4", version: 1 }
|
|
167
|
+
* )
|
|
168
|
+
*
|
|
169
|
+
* // Use it - results are cached
|
|
170
|
+
* const result1 = await cachedExtract("some transcript")
|
|
171
|
+
* const result2 = await cachedExtract("some transcript") // cache hit!
|
|
172
|
+
* ```
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* // With custom serializer for complex input
|
|
177
|
+
* interface ComplexInput {
|
|
178
|
+
* transcript: string
|
|
179
|
+
* metadata: { source: string }
|
|
180
|
+
* }
|
|
181
|
+
*
|
|
182
|
+
* const cachedProcess = withLLMCache(
|
|
183
|
+
* async (input: ComplexInput) => processInput(input),
|
|
184
|
+
* {
|
|
185
|
+
* model: "claude-sonnet-4",
|
|
186
|
+
* serialize: (input) => `${input.metadata.source}::${input.transcript}`,
|
|
187
|
+
* version: 2
|
|
188
|
+
* }
|
|
189
|
+
* )
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export const withLLMCache = (fn, options) => {
|
|
193
|
+
const serialize = options.serialize ?? JSON.stringify;
|
|
194
|
+
return async (input) => {
|
|
195
|
+
const serialized = serialize(input);
|
|
196
|
+
return cachedLLMCall(serialized, options.model, () => fn(input), { version: options.version });
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/llm-cache/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.d.ts","sourceRoot":"","sources":["../../src/llm-cache/cache.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for LLM response caching.
|
|
3
|
+
*
|
|
4
|
+
* @module @tx/test-utils/llm-cache/cache.test
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
7
|
+
import * as fs from "fs/promises";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import { hashInput, cachedLLMCall, withLLMCache, configureLLMCache, resetCacheConfig, getCacheStats, clearCache, formatCacheStats, getCacheEntry, listCacheEntries } from "./index.js";
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Test Setup
|
|
13
|
+
// =============================================================================
|
|
14
|
+
describe("LLM Cache", () => {
|
|
15
|
+
let tempDir;
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
// Create isolated temp directory for each test
|
|
18
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "tx-llm-cache-test-"));
|
|
19
|
+
configureLLMCache({ cacheDir: tempDir, logging: false });
|
|
20
|
+
});
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
// Clean up temp directory
|
|
23
|
+
resetCacheConfig();
|
|
24
|
+
try {
|
|
25
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Ignore cleanup errors
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// ===========================================================================
|
|
32
|
+
// hashInput Tests
|
|
33
|
+
// ===========================================================================
|
|
34
|
+
describe("hashInput", () => {
|
|
35
|
+
it("should return deterministic SHA256 hash", () => {
|
|
36
|
+
const input = "What is the capital of France?";
|
|
37
|
+
const hash1 = hashInput(input);
|
|
38
|
+
const hash2 = hashInput(input);
|
|
39
|
+
expect(hash1).toBe(hash2);
|
|
40
|
+
expect(hash1).toHaveLength(64); // SHA256 hex = 64 chars
|
|
41
|
+
expect(hash1).toMatch(/^[a-f0-9]{64}$/);
|
|
42
|
+
});
|
|
43
|
+
it("should produce different hashes for different inputs", () => {
|
|
44
|
+
const hash1 = hashInput("input one");
|
|
45
|
+
const hash2 = hashInput("input two");
|
|
46
|
+
expect(hash1).not.toBe(hash2);
|
|
47
|
+
});
|
|
48
|
+
it("should handle empty string", () => {
|
|
49
|
+
const hash = hashInput("");
|
|
50
|
+
expect(hash).toHaveLength(64);
|
|
51
|
+
// SHA256 of empty string is a known value
|
|
52
|
+
expect(hash).toBe("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
|
|
53
|
+
});
|
|
54
|
+
it("should handle unicode input", () => {
|
|
55
|
+
const hash = hashInput("こんにちは世界 🌍");
|
|
56
|
+
expect(hash).toHaveLength(64);
|
|
57
|
+
expect(hashInput("こんにちは世界 🌍")).toBe(hash);
|
|
58
|
+
});
|
|
59
|
+
it("should handle very long input", () => {
|
|
60
|
+
const longInput = "a".repeat(100000);
|
|
61
|
+
const hash = hashInput(longInput);
|
|
62
|
+
expect(hash).toHaveLength(64);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
// ===========================================================================
|
|
66
|
+
// cachedLLMCall Tests
|
|
67
|
+
// ===========================================================================
|
|
68
|
+
describe("cachedLLMCall", () => {
|
|
69
|
+
it("should call function on cache miss and store result", async () => {
|
|
70
|
+
const mockCall = vi.fn().mockResolvedValue({ answer: "Paris" });
|
|
71
|
+
const result = await cachedLLMCall("What is the capital of France?", "claude-sonnet-4", mockCall);
|
|
72
|
+
expect(result).toEqual({ answer: "Paris" });
|
|
73
|
+
expect(mockCall).toHaveBeenCalledTimes(1);
|
|
74
|
+
// Verify cache file was created
|
|
75
|
+
const files = await fs.readdir(tempDir);
|
|
76
|
+
expect(files.length).toBe(1);
|
|
77
|
+
expect(files[0]).toMatch(/^[a-f0-9]{64}\.json$/);
|
|
78
|
+
});
|
|
79
|
+
it("should return cached value on cache hit without calling function", async () => {
|
|
80
|
+
const mockCall = vi.fn().mockResolvedValue({ answer: "Paris" });
|
|
81
|
+
const input = "What is the capital of France?";
|
|
82
|
+
// First call - cache miss
|
|
83
|
+
await cachedLLMCall(input, "claude-sonnet-4", mockCall);
|
|
84
|
+
expect(mockCall).toHaveBeenCalledTimes(1);
|
|
85
|
+
// Second call - cache hit
|
|
86
|
+
const result = await cachedLLMCall(input, "claude-sonnet-4", mockCall);
|
|
87
|
+
expect(result).toEqual({ answer: "Paris" });
|
|
88
|
+
expect(mockCall).toHaveBeenCalledTimes(1); // Not called again
|
|
89
|
+
});
|
|
90
|
+
it("should bypass cache when TX_NO_LLM_CACHE=1", async () => {
|
|
91
|
+
const originalEnv = process.env.TX_NO_LLM_CACHE;
|
|
92
|
+
process.env.TX_NO_LLM_CACHE = "1";
|
|
93
|
+
try {
|
|
94
|
+
const mockCall = vi.fn().mockResolvedValue({ answer: "Paris" });
|
|
95
|
+
const input = "What is the capital of France?";
|
|
96
|
+
// First call
|
|
97
|
+
await cachedLLMCall(input, "claude-sonnet-4", mockCall);
|
|
98
|
+
// Second call - should still call function
|
|
99
|
+
await cachedLLMCall(input, "claude-sonnet-4", mockCall);
|
|
100
|
+
expect(mockCall).toHaveBeenCalledTimes(2);
|
|
101
|
+
// Cache should not have been written
|
|
102
|
+
const files = await fs.readdir(tempDir);
|
|
103
|
+
expect(files.length).toBe(0);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
if (originalEnv === undefined) {
|
|
107
|
+
delete process.env.TX_NO_LLM_CACHE;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
process.env.TX_NO_LLM_CACHE = originalEnv;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
it("should bypass cache when forceRefresh=true", async () => {
|
|
115
|
+
const mockCall = vi.fn()
|
|
116
|
+
.mockResolvedValueOnce({ answer: "Paris" })
|
|
117
|
+
.mockResolvedValueOnce({ answer: "Paris (updated)" });
|
|
118
|
+
const input = "What is the capital of France?";
|
|
119
|
+
// First call - cache miss
|
|
120
|
+
await cachedLLMCall(input, "claude-sonnet-4", mockCall);
|
|
121
|
+
// Second call with forceRefresh - should call function
|
|
122
|
+
const result = await cachedLLMCall(input, "claude-sonnet-4", mockCall, { forceRefresh: true });
|
|
123
|
+
expect(result).toEqual({ answer: "Paris (updated)" });
|
|
124
|
+
expect(mockCall).toHaveBeenCalledTimes(2);
|
|
125
|
+
});
|
|
126
|
+
it("should trigger cache miss on version mismatch", async () => {
|
|
127
|
+
const mockCall = vi.fn()
|
|
128
|
+
.mockResolvedValueOnce({ answer: "Paris v1" })
|
|
129
|
+
.mockResolvedValueOnce({ answer: "Paris v2" });
|
|
130
|
+
const input = "What is the capital of France?";
|
|
131
|
+
// First call with version 1
|
|
132
|
+
await cachedLLMCall(input, "claude-sonnet-4", mockCall, { version: 1 });
|
|
133
|
+
// Second call with version 2 - should be cache miss
|
|
134
|
+
const result = await cachedLLMCall(input, "claude-sonnet-4", mockCall, { version: 2 });
|
|
135
|
+
expect(result).toEqual({ answer: "Paris v2" });
|
|
136
|
+
expect(mockCall).toHaveBeenCalledTimes(2);
|
|
137
|
+
});
|
|
138
|
+
it("should store correct cache entry format", async () => {
|
|
139
|
+
const mockCall = vi.fn().mockResolvedValue({ answer: "Paris" });
|
|
140
|
+
const input = "What is the capital of France?";
|
|
141
|
+
await cachedLLMCall(input, "claude-sonnet-4", mockCall, { version: 3 });
|
|
142
|
+
const hash = hashInput(input);
|
|
143
|
+
const cacheFile = path.join(tempDir, `${hash}.json`);
|
|
144
|
+
const content = JSON.parse(await fs.readFile(cacheFile, "utf-8"));
|
|
145
|
+
expect(content.inputHash).toBe(hash);
|
|
146
|
+
expect(content.input).toBe(input);
|
|
147
|
+
expect(content.response).toEqual({ answer: "Paris" });
|
|
148
|
+
expect(content.model).toBe("claude-sonnet-4");
|
|
149
|
+
expect(content.version).toBe(3);
|
|
150
|
+
expect(content.cachedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
151
|
+
});
|
|
152
|
+
it("should truncate long input in cache entry", async () => {
|
|
153
|
+
const mockCall = vi.fn().mockResolvedValue({ answer: "42" });
|
|
154
|
+
const longInput = "a".repeat(2000);
|
|
155
|
+
await cachedLLMCall(longInput, "claude-sonnet-4", mockCall);
|
|
156
|
+
const hash = hashInput(longInput);
|
|
157
|
+
const cacheFile = path.join(tempDir, `${hash}.json`);
|
|
158
|
+
const content = JSON.parse(await fs.readFile(cacheFile, "utf-8"));
|
|
159
|
+
expect(content.input.length).toBe(1000); // Truncated
|
|
160
|
+
expect(content.inputHash).toBe(hash); // Full hash preserved
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
// ===========================================================================
|
|
164
|
+
// withLLMCache Tests
|
|
165
|
+
// ===========================================================================
|
|
166
|
+
describe("withLLMCache", () => {
|
|
167
|
+
it("should wrap function with caching", async () => {
|
|
168
|
+
const mockFn = vi.fn().mockResolvedValue("result");
|
|
169
|
+
const cachedFn = withLLMCache(mockFn, { model: "claude-sonnet-4" });
|
|
170
|
+
const result1 = await cachedFn("input");
|
|
171
|
+
const result2 = await cachedFn("input");
|
|
172
|
+
expect(result1).toBe("result");
|
|
173
|
+
expect(result2).toBe("result");
|
|
174
|
+
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
175
|
+
});
|
|
176
|
+
it("should use custom serializer", async () => {
|
|
177
|
+
const mockFn = vi.fn().mockResolvedValue("result");
|
|
178
|
+
const cachedFn = withLLMCache(mockFn, {
|
|
179
|
+
model: "claude-sonnet-4",
|
|
180
|
+
// Only use query for cache key (ignore context)
|
|
181
|
+
serialize: (input) => input.query
|
|
182
|
+
});
|
|
183
|
+
// Same query, different context - should hit cache
|
|
184
|
+
await cachedFn({ query: "test", context: ["a"] });
|
|
185
|
+
await cachedFn({ query: "test", context: ["b", "c"] });
|
|
186
|
+
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
187
|
+
});
|
|
188
|
+
it("should pass version to cachedLLMCall", async () => {
|
|
189
|
+
const mockFn = vi.fn()
|
|
190
|
+
.mockResolvedValueOnce("v1 result")
|
|
191
|
+
.mockResolvedValueOnce("v2 result");
|
|
192
|
+
const cachedFnV1 = withLLMCache(mockFn, { model: "claude-sonnet-4", version: 1 });
|
|
193
|
+
const cachedFnV2 = withLLMCache(mockFn, { model: "claude-sonnet-4", version: 2 });
|
|
194
|
+
await cachedFnV1("input");
|
|
195
|
+
const result = await cachedFnV2("input");
|
|
196
|
+
expect(result).toBe("v2 result");
|
|
197
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
// ===========================================================================
|
|
201
|
+
// CLI Utilities Tests
|
|
202
|
+
// ===========================================================================
|
|
203
|
+
describe("getCacheStats", () => {
|
|
204
|
+
it("should return empty stats for empty cache", async () => {
|
|
205
|
+
const stats = await getCacheStats();
|
|
206
|
+
expect(stats.count).toBe(0);
|
|
207
|
+
expect(stats.totalBytes).toBe(0);
|
|
208
|
+
expect(stats.oldestDate).toBeNull();
|
|
209
|
+
expect(stats.newestDate).toBeNull();
|
|
210
|
+
expect(stats.byModel).toEqual({});
|
|
211
|
+
expect(stats.byVersion).toEqual({});
|
|
212
|
+
});
|
|
213
|
+
it("should return correct stats for populated cache", async () => {
|
|
214
|
+
// Create some cache entries
|
|
215
|
+
await cachedLLMCall("input1", "claude-sonnet-4", () => Promise.resolve("r1"), { version: 1 });
|
|
216
|
+
await cachedLLMCall("input2", "claude-sonnet-4", () => Promise.resolve("r2"), { version: 1 });
|
|
217
|
+
await cachedLLMCall("input3", "claude-haiku", () => Promise.resolve("r3"), { version: 2 });
|
|
218
|
+
const stats = await getCacheStats();
|
|
219
|
+
expect(stats.count).toBe(3);
|
|
220
|
+
expect(stats.totalBytes).toBeGreaterThan(0);
|
|
221
|
+
expect(stats.oldestDate).toBeInstanceOf(Date);
|
|
222
|
+
expect(stats.newestDate).toBeInstanceOf(Date);
|
|
223
|
+
expect(stats.byModel).toEqual({
|
|
224
|
+
"claude-sonnet-4": 2,
|
|
225
|
+
"claude-haiku": 1
|
|
226
|
+
});
|
|
227
|
+
expect(stats.byVersion).toEqual({
|
|
228
|
+
1: 2,
|
|
229
|
+
2: 1
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
describe("clearCache", () => {
|
|
234
|
+
it("should clear all entries with all=true", async () => {
|
|
235
|
+
await cachedLLMCall("input1", "claude-sonnet-4", () => Promise.resolve("r1"));
|
|
236
|
+
await cachedLLMCall("input2", "claude-sonnet-4", () => Promise.resolve("r2"));
|
|
237
|
+
const deleted = await clearCache({ all: true });
|
|
238
|
+
expect(deleted).toBe(-1); // Unknown count for all
|
|
239
|
+
const files = await fs.readdir(tempDir);
|
|
240
|
+
expect(files.length).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
it("should clear entries by model", async () => {
|
|
243
|
+
await cachedLLMCall("input1", "claude-sonnet-4", () => Promise.resolve("r1"));
|
|
244
|
+
await cachedLLMCall("input2", "claude-haiku", () => Promise.resolve("r2"));
|
|
245
|
+
const deleted = await clearCache({ model: "claude-sonnet-4" });
|
|
246
|
+
expect(deleted).toBe(1);
|
|
247
|
+
const stats = await getCacheStats();
|
|
248
|
+
expect(stats.count).toBe(1);
|
|
249
|
+
expect(stats.byModel).toEqual({ "claude-haiku": 1 });
|
|
250
|
+
});
|
|
251
|
+
it("should clear entries by version", async () => {
|
|
252
|
+
await cachedLLMCall("input1", "claude-sonnet-4", () => Promise.resolve("r1"), { version: 1 });
|
|
253
|
+
await cachedLLMCall("input2", "claude-sonnet-4", () => Promise.resolve("r2"), { version: 2 });
|
|
254
|
+
const deleted = await clearCache({ version: 1 });
|
|
255
|
+
expect(deleted).toBe(1);
|
|
256
|
+
const stats = await getCacheStats();
|
|
257
|
+
expect(stats.byVersion).toEqual({ 2: 1 });
|
|
258
|
+
});
|
|
259
|
+
it("should clear entries older than date", async () => {
|
|
260
|
+
await cachedLLMCall("input1", "claude-sonnet-4", () => Promise.resolve("r1"));
|
|
261
|
+
// Clear entries older than 1 second in the future (all entries)
|
|
262
|
+
const futureDate = new Date(Date.now() + 1000);
|
|
263
|
+
const deleted = await clearCache({ olderThan: futureDate });
|
|
264
|
+
expect(deleted).toBe(1);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
describe("formatCacheStats", () => {
|
|
268
|
+
it("should format stats as readable string", async () => {
|
|
269
|
+
await cachedLLMCall("input1", "claude-sonnet-4", () => Promise.resolve("r1"), { version: 1 });
|
|
270
|
+
await cachedLLMCall("input2", "claude-haiku", () => Promise.resolve("r2"), { version: 2 });
|
|
271
|
+
const stats = await getCacheStats();
|
|
272
|
+
const formatted = formatCacheStats(stats);
|
|
273
|
+
expect(formatted).toContain("LLM Cache Statistics:");
|
|
274
|
+
expect(formatted).toContain("Entries: 2");
|
|
275
|
+
expect(formatted).toContain("By model:");
|
|
276
|
+
expect(formatted).toContain("claude-sonnet-4: 1");
|
|
277
|
+
expect(formatted).toContain("claude-haiku: 1");
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
describe("getCacheEntry", () => {
|
|
281
|
+
it("should return entry by hash", async () => {
|
|
282
|
+
const input = "test input";
|
|
283
|
+
await cachedLLMCall(input, "claude-sonnet-4", () => Promise.resolve("result"));
|
|
284
|
+
const hash = hashInput(input);
|
|
285
|
+
const entry = await getCacheEntry(hash);
|
|
286
|
+
expect(entry).not.toBeNull();
|
|
287
|
+
expect(entry?.response).toBe("result");
|
|
288
|
+
expect(entry?.model).toBe("claude-sonnet-4");
|
|
289
|
+
});
|
|
290
|
+
it("should return null for non-existent hash", async () => {
|
|
291
|
+
const entry = await getCacheEntry("nonexistent");
|
|
292
|
+
expect(entry).toBeNull();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
describe("listCacheEntries", () => {
|
|
296
|
+
it("should list all cache entry hashes", async () => {
|
|
297
|
+
await cachedLLMCall("input1", "claude-sonnet-4", () => Promise.resolve("r1"));
|
|
298
|
+
await cachedLLMCall("input2", "claude-sonnet-4", () => Promise.resolve("r2"));
|
|
299
|
+
const entries = await listCacheEntries();
|
|
300
|
+
expect(entries.length).toBe(2);
|
|
301
|
+
expect(entries[0]).toHaveLength(64);
|
|
302
|
+
expect(entries[1]).toHaveLength(64);
|
|
303
|
+
});
|
|
304
|
+
it("should return empty array for empty cache", async () => {
|
|
305
|
+
const entries = await listCacheEntries();
|
|
306
|
+
expect(entries).toEqual([]);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
//# sourceMappingURL=cache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.js","sourceRoot":"","sources":["../../src/llm-cache/cache.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI utilities for LLM cache management.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to inspect, clear, and manage the LLM response cache.
|
|
5
|
+
*
|
|
6
|
+
* @module @tx/test-utils/llm-cache/cli
|
|
7
|
+
*/
|
|
8
|
+
import { CacheEntry } from "./cache.js";
|
|
9
|
+
/**
|
|
10
|
+
* Statistics about the LLM cache.
|
|
11
|
+
*/
|
|
12
|
+
export interface CacheStats {
|
|
13
|
+
/** Total number of cache entries */
|
|
14
|
+
count: number;
|
|
15
|
+
/** Total size in bytes */
|
|
16
|
+
totalBytes: number;
|
|
17
|
+
/** Oldest cache entry date (null if empty) */
|
|
18
|
+
oldestDate: Date | null;
|
|
19
|
+
/** Newest cache entry date (null if empty) */
|
|
20
|
+
newestDate: Date | null;
|
|
21
|
+
/** Count of entries by model */
|
|
22
|
+
byModel: Record<string, number>;
|
|
23
|
+
/** Count of entries by version */
|
|
24
|
+
byVersion: Record<number, number>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Options for clearing cache.
|
|
28
|
+
*/
|
|
29
|
+
export interface ClearCacheOptions {
|
|
30
|
+
/** Delete entries older than this date */
|
|
31
|
+
olderThan?: Date;
|
|
32
|
+
/** Delete entries for specific model */
|
|
33
|
+
model?: string;
|
|
34
|
+
/** Delete entries with specific version */
|
|
35
|
+
version?: number;
|
|
36
|
+
/** Delete all entries */
|
|
37
|
+
all?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get statistics about the LLM cache.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const stats = await getCacheStats()
|
|
45
|
+
* console.log(`Cache has ${stats.count} entries using ${stats.totalBytes} bytes`)
|
|
46
|
+
* console.log(`Models: ${Object.keys(stats.byModel).join(', ')}`)
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare const getCacheStats: () => Promise<CacheStats>;
|
|
50
|
+
/**
|
|
51
|
+
* Clear cache entries based on options.
|
|
52
|
+
*
|
|
53
|
+
* @returns Number of entries deleted (-1 if unknown when using `all`)
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* // Clear all cache
|
|
58
|
+
* await clearCache({ all: true })
|
|
59
|
+
*
|
|
60
|
+
* // Clear entries older than 30 days
|
|
61
|
+
* const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
|
62
|
+
* const deleted = await clearCache({ olderThan: thirtyDaysAgo })
|
|
63
|
+
*
|
|
64
|
+
* // Clear entries for specific model
|
|
65
|
+
* await clearCache({ model: 'claude-haiku' })
|
|
66
|
+
*
|
|
67
|
+
* // Clear entries with specific version (useful after schema changes)
|
|
68
|
+
* await clearCache({ version: 1 })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare const clearCache: (options?: ClearCacheOptions) => Promise<number>;
|
|
72
|
+
/**
|
|
73
|
+
* Format cache statistics for display.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const stats = await getCacheStats()
|
|
78
|
+
* console.log(formatCacheStats(stats))
|
|
79
|
+
* // Output:
|
|
80
|
+
* // LLM Cache Statistics:
|
|
81
|
+
* // Entries: 142
|
|
82
|
+
* // Size: 3.2 MB
|
|
83
|
+
* // Date range: 2024-01-15 to 2024-02-01
|
|
84
|
+
* // By model:
|
|
85
|
+
* // claude-sonnet-4: 98
|
|
86
|
+
* // claude-haiku: 44
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare const formatCacheStats: (stats: CacheStats) => string;
|
|
90
|
+
/**
|
|
91
|
+
* Get a specific cache entry by hash.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const entry = await getCacheEntry('a1b2c3d4...')
|
|
96
|
+
* if (entry) {
|
|
97
|
+
* console.log(`Cached at: ${entry.cachedAt}`)
|
|
98
|
+
* console.log(`Model: ${entry.model}`)
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare const getCacheEntry: <T = unknown>(hash: string) => Promise<CacheEntry<T> | null>;
|
|
103
|
+
/**
|
|
104
|
+
* List all cache entry hashes.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const hashes = await listCacheEntries()
|
|
109
|
+
* console.log(`Found ${hashes.length} cached responses`)
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare const listCacheEntries: () => Promise<string[]>;
|
|
113
|
+
//# sourceMappingURL=cli.d.ts.map
|