pi-l1-cache 1.2.2 → 1.4.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/src/index.test.ts CHANGED
@@ -1,236 +1,514 @@
1
- import { describe, it, beforeEach, before, after } from "node:test"
2
- import assert from "node:assert/strict"
3
- import init, { _testHooks } from "./index.ts"
1
+ // L1 Cache Extension Test Suite
2
+ //
3
+ // Tests for the working implementation with replay, disk persistence,
4
+ // and proper key semantics.
4
5
 
5
- const Test = _testHooks()
6
+ import { describe, it, beforeEach, afterEach } from "node:test"
7
+ import * as assert from "node:assert"
8
+ import fs from "node:fs"
9
+ import path from "node:path"
10
+ import os from "node:os"
11
+ import { fileURLToPath } from "node:url"
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
14
+
15
+ import l1CacheExtension from "./index.js"
16
+ import { cache, stats, settings, fastHash, estimateSize, coalesceChunks, evictIfNeeded, cleanupExpired, keyForPayload, persistEntry, loadPersisted, resetForTests } from "./index.js"
17
+
18
+ // Test cache directory (isolated from real cache)
19
+ const TEST_CACHE_DIR = path.join(os.tmpdir(), "l1-cache-test-" + Date.now())
20
+
21
+ // Mock ExtensionAPI
22
+ function createMockExtensionAPI(): any {
23
+ const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<any>>>()
24
+ const commands = new Map<string, any>()
25
+ let sessionShutdownHandler: ((event: any, ctx?: any) => Promise<any>) | null = null
26
+
27
+ return {
28
+ on: (event: string, handler: (event: any, ctx?: any) => Promise<any>) => {
29
+ if (event === "session_shutdown") {
30
+ sessionShutdownHandler = handler
31
+ } else {
32
+ if (!handlers.has(event)) handlers.set(event, [])
33
+ handlers.get(event)!.push(handler)
34
+ }
35
+ },
36
+ registerCommand: (name: string, config: any) => {
37
+ commands.set(name, config)
38
+ },
39
+ emit: async (event: any) => {
40
+ const eventHandlers = handlers.get(event.type) || []
41
+ for (const handler of eventHandlers) {
42
+ await handler(event, {})
43
+ }
44
+ },
45
+ _getHandlers: (event: string) => handlers.get(event) || [],
46
+ _getSessionShutdownHandler: () => sessionShutdownHandler,
47
+ _getCommands: () => commands,
48
+ }
49
+ }
50
+
51
+ describe("L1 Cache", () => {
52
+ beforeEach(() => {
53
+ // Clean test cache directory
54
+ if (fs.existsSync(TEST_CACHE_DIR)) {
55
+ fs.rmSync(TEST_CACHE_DIR, { recursive: true, force: true })
56
+ }
57
+ fs.mkdirSync(TEST_CACHE_DIR, { recursive: true })
58
+ // Isolate: redirect persistence + reset all module state
59
+ process.env.L1_CACHE_DIR = TEST_CACHE_DIR
60
+ resetForTests()
61
+ })
62
+
63
+ afterEach(() => {
64
+ // Cleanup test cache directory
65
+ if (fs.existsSync(TEST_CACHE_DIR)) {
66
+ fs.rmSync(TEST_CACHE_DIR, { recursive: true, force: true })
67
+ }
68
+ })
6
69
 
7
- describe("pi-l1-cache", () => {
8
70
  describe("fastHash", () => {
9
- it("returns consistent hash for same input", () => {
10
- const input = "test-string-123"
11
- assert.equal(Test.fastHash(input), Test.fastHash(input))
71
+ it("produces consistent hashes for identical strings", () => {
72
+ const str = "test string"
73
+ const h1 = fastHash(str)
74
+ const h2 = fastHash(str)
75
+ assert.strictEqual(h1, h2)
12
76
  })
13
77
 
14
- it("returns different hash for different input", () => {
15
- assert.notEqual(Test.fastHash("hello"), Test.fastHash("world"))
78
+ it("produces different hashes for different strings", () => {
79
+ const h1 = fastHash("string1")
80
+ const h2 = fastHash("string2")
81
+ assert.notStrictEqual(h1, h2)
16
82
  })
17
83
 
18
84
  it("handles empty string", () => {
19
- const empty = Test.fastHash("")
20
- assert.ok(empty.length > 0)
85
+ const hash = fastHash("")
86
+ assert.ok(hash.length > 0)
87
+ })
88
+
89
+ it("handles unicode characters", () => {
90
+ const hash = fastHash("🚀 test")
91
+ assert.ok(hash.length > 0)
21
92
  })
93
+ })
22
94
 
23
- it("handles unicode", () => {
24
- const emoji = Test.fastHash("🎉 pi-l1-cache 🎉")
25
- assert.ok(emoji.length > 0)
95
+ describe("keyForPayload", () => {
96
+ it("creates key from model and messages", () => {
97
+ const payload = {
98
+ model: "local/deepseek-v4",
99
+ messages: [{ role: "user", content: "test" }],
100
+ }
101
+ const key = keyForPayload(payload)
102
+ assert.ok(key)
103
+ assert.ok(key.length > 0)
26
104
  })
27
105
 
28
- it("is deterministic", () => {
29
- const obj = JSON.stringify({ a: 1, b: 2 })
30
- assert.equal(Test.fastHash(obj), Test.fastHash(obj))
106
+ it("excludes volatile fields from key", () => {
107
+ const payload1 = {
108
+ model: "local/deepseek-v4",
109
+ messages: [{ role: "user", content: "test" }],
110
+ prompt_cache_key: "session-123",
111
+ stream: true,
112
+ }
113
+ const payload2 = {
114
+ model: "local/deepseek-v4",
115
+ messages: [{ role: "user", content: "test" }],
116
+ prompt_cache_key: "session-456", // different
117
+ stream: false, // different
118
+ }
119
+ const key1 = keyForPayload(payload1)
120
+ const key2 = keyForPayload(payload2)
121
+ assert.strictEqual(key1, key2) // keys should match despite volatile differences
122
+ })
123
+
124
+ it("includes tools in key", () => {
125
+ const payload1 = {
126
+ model: "local/deepseek-v4",
127
+ messages: [{ role: "user", content: "test" }],
128
+ tools: [{ name: "tool1" }],
129
+ }
130
+ const payload2 = {
131
+ model: "local/deepseek-v4",
132
+ messages: [{ role: "user", content: "test" }],
133
+ tools: [{ name: "tool2" }], // different
134
+ }
135
+ const key1 = keyForPayload(payload1)
136
+ const key2 = keyForPayload(payload2)
137
+ assert.notStrictEqual(key1, key2)
138
+ })
139
+
140
+ it("returns null for invalid payloads", () => {
141
+ assert.strictEqual(keyForPayload(null), null)
142
+ assert.strictEqual(keyForPayload(undefined), null)
143
+ assert.strictEqual(keyForPayload({}), null)
144
+ assert.strictEqual(keyForPayload({ messages: "not-array" }), null)
145
+ })
146
+ })
147
+
148
+ describe("coalesceChunks", () => {
149
+ it("merges adjacent content deltas", () => {
150
+ const chunks = [
151
+ { choices: [{ delta: { content: "hello" } }] },
152
+ { choices: [{ delta: { content: " " } }] },
153
+ { choices: [{ delta: { content: "world" } }] },
154
+ ]
155
+ const coalesced = coalesceChunks(chunks)
156
+ assert.strictEqual(coalesced.length, 1)
157
+ assert.strictEqual(coalesced[0].choices[0].delta.content, "hello world")
158
+ })
159
+
160
+ it("merges adjacent reasoning_content deltas", () => {
161
+ const chunks = [
162
+ { choices: [{ delta: { reasoning_content: "thinking" } }] },
163
+ { choices: [{ delta: { reasoning_content: " more" } }] },
164
+ ]
165
+ const coalesced = coalesceChunks(chunks)
166
+ assert.strictEqual(coalesced.length, 1)
167
+ assert.strictEqual(coalesced[0].choices[0].delta.reasoning_content, "thinking more")
168
+ })
169
+
170
+ it("does not merge chunks with finish_reason", () => {
171
+ const chunks = [
172
+ { choices: [{ delta: { content: "hello" } }] },
173
+ { choices: [{ delta: { content: "world" }, finish_reason: "stop" }] },
174
+ ]
175
+ const coalesced = coalesceChunks(chunks)
176
+ assert.strictEqual(coalesced.length, 2)
177
+ })
178
+
179
+ it("does not merge chunks with tool_calls", () => {
180
+ const chunks = [
181
+ { choices: [{ delta: { content: "hello" } }] },
182
+ { choices: [{ delta: { tool_calls: [{ id: "call1" }] } }] },
183
+ ]
184
+ const coalesced = coalesceChunks(chunks)
185
+ assert.strictEqual(coalesced.length, 2)
186
+ })
187
+
188
+ it("handles empty chunks array", () => {
189
+ const coalesced = coalesceChunks([])
190
+ assert.strictEqual(coalesced.length, 0)
31
191
  })
32
192
  })
33
193
 
34
194
  describe("estimateSize", () => {
35
- it("estimates size of simple strings", () => {
36
- const size = Test.estimateSize("hello world")
195
+ it("estimates size for simple objects", () => {
196
+ const obj = { text: "hello" }
197
+ const size = estimateSize(obj)
37
198
  assert.ok(size > 0)
38
- assert.ok(size < 100)
39
199
  })
40
200
 
41
- it("estimates size of objects", () => {
42
- const obj = { a: 1, b: "test", c: [1, 2, 3] }
43
- const size = Test.estimateSize(obj)
44
- assert.ok(size > 0)
201
+ it("handles large objects", () => {
202
+ const obj = { text: "x".repeat(10000) }
203
+ const size = estimateSize(obj)
204
+ assert.ok(size > 10000)
205
+ })
206
+
207
+ it("handles undefined", () => {
208
+ const size = estimateSize(undefined)
209
+ assert.strictEqual(size, 4096) // fallback for non-serializable
210
+ })
211
+ })
212
+
213
+ describe("cache operations", () => {
214
+ it("stores and retrieves entries", async () => {
215
+ const payload = {
216
+ model: "local/deepseek-v4",
217
+ messages: [{ role: "user", content: "test" }],
218
+ }
219
+ const key = keyForPayload(payload)!
220
+ const chunks = [{ choices: [{ delta: { content: "answer" } }] }]
221
+ const size = estimateSize(chunks)
222
+
223
+ cache.set(key, { chunks, timestamp: Date.now(), sizeBytes: size })
224
+ evictIfNeeded()
225
+
226
+ const entry = cache.get(key)
227
+ assert.ok(entry)
228
+ assert.strictEqual(entry!.chunks.length, 1)
229
+ })
230
+
231
+ it("evicts oldest entries when over memory cap", async () => {
232
+ settings.maxEntries = 2
233
+ settings.maxMemoryBytes = 100
234
+ const chunks = [{ choices: [{ delta: { content: "x".repeat(50) } }] }]
235
+ const size = estimateSize(chunks)
236
+
237
+ // Store 3 entries (should evict to stay within cap)
238
+ for (let i = 0; i < 3; i++) {
239
+ const payload = { model: "local/deepseek-v4", messages: [{ role: "user", content: `test${i}` }] }
240
+ const key = keyForPayload(payload)!
241
+ cache.set(key, { chunks, timestamp: Date.now() + i, sizeBytes: size })
242
+ evictIfNeeded()
243
+ }
244
+
245
+ assert.ok(cache.size <= 2)
246
+ })
247
+
248
+ it("respects TTL expiry", async () => {
249
+ settings.ttlSeconds = 1
250
+ const payload = { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] }
251
+ const key = keyForPayload(payload)!
252
+ const chunks = [{ choices: [{ delta: { content: "answer" } }] }]
253
+
254
+ // Set timestamp in past (expired)
255
+ cache.set(key, { chunks, timestamp: Date.now() - 2000, sizeBytes: estimateSize(chunks) })
256
+
257
+ // Simulate cleanup
258
+ cleanupExpired()
259
+
260
+ assert.strictEqual(cache.has(key), false)
45
261
  })
262
+ })
263
+
264
+ describe("persistence", () => {
265
+ it("persists entries to disk", async () => {
266
+ const payload = { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] }
267
+ const key = keyForPayload(payload)!
268
+ const chunks = [{ choices: [{ delta: { content: "answer" } }] }]
46
269
 
47
- it("estimates size of arrays", () => {
48
- const arr = new Array(100).fill("x")
49
- const size = Test.estimateSize(arr)
50
- assert.ok(size > 100)
270
+ persistEntry(key, { chunks, timestamp: Date.now(), sizeBytes: estimateSize(chunks) })
271
+
272
+ // Verify file exists
273
+ const filePath = path.join(TEST_CACHE_DIR, key + ".json")
274
+ assert.ok(fs.existsSync(filePath))
275
+
276
+ // Load and verify
277
+ cache.clear()
278
+ loadPersisted()
279
+ assert.strictEqual(cache.size, 1)
51
280
  })
52
281
 
53
- it("returns fallback for non-serializable", () => {
54
- const circular: any = { a: 1 }
55
- circular.self = circular
56
- const size = Test.estimateSize(circular)
57
- assert.equal(size, 1024)
282
+ it("ignores expired entries on load", async () => {
283
+ settings.ttlSeconds = 1
284
+ const payload = { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] }
285
+ const key = keyForPayload(payload)!
286
+
287
+ // Persist with old timestamp
288
+ persistEntry(key, {
289
+ chunks: [{ choices: [{ delta: { content: "answer" } }] }],
290
+ timestamp: Date.now() - 2000,
291
+ sizeBytes: estimateSize([]),
292
+ })
293
+
294
+ cache.clear()
295
+ loadPersisted()
296
+ assert.strictEqual(cache.size, 0)
58
297
  })
59
298
 
60
- it("handles null and undefined", () => {
61
- assert.ok(Test.estimateSize(null) > 0)
62
- assert.ok(Test.estimateSize(undefined) > 0)
299
+ it("handles corrupted files gracefully", async () => {
300
+ // Create corrupted file
301
+ fs.writeFileSync(path.join(TEST_CACHE_DIR, "corrupt.json"), "not valid json")
302
+
303
+ cache.clear()
304
+ loadPersisted()
305
+ // Should not crash, corrupted file should be deleted
306
+ assert.strictEqual(fs.existsSync(path.join(TEST_CACHE_DIR, "corrupt.json")), false)
63
307
  })
64
308
  })
65
309
 
66
- describe("eviction logic", () => {
67
- beforeEach(() => {
68
- Test.reset()
69
- Test.setSettings({ maxEntries: 10, maxMemoryBytes: 10000 })
310
+ describe("extension integration", () => {
311
+ it("initializes and registers commands", async () => {
312
+ const mockApi = createMockExtensionAPI()
313
+ await l1CacheExtension(mockApi)
314
+
315
+ const commands = mockApi._getCommands()
316
+ assert.ok(commands.has("l1-cache"))
70
317
  })
71
318
 
72
- it("evicts oldest entries first", () => {
73
- for (let i = 0; i < 15; i++) {
74
- Test._setCache(`key-${i}`, { response: {}, timestamp: i * 1000, sizeBytes: 100 })
319
+ it("handles before_provider_request (miss)", async () => {
320
+ const mockApi = createMockExtensionAPI()
321
+ await l1CacheExtension(mockApi)
322
+
323
+ const handler = mockApi._getHandlers("before_provider_request")[0]
324
+ const event = {
325
+ payload: { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] },
75
326
  }
76
- Test.evictIfNeeded()
77
- assert.ok(Test._getCacheSize() <= 10)
327
+
328
+ const result = await handler(event, { model: { id: "local/deepseek-v4" } })
329
+ assert.strictEqual(result, undefined) // miss, no replay
330
+ })
331
+
332
+ it("handles before_provider_request (hit)", async () => {
333
+ const mockApi = createMockExtensionAPI()
334
+ await l1CacheExtension(mockApi)
335
+
336
+ const handler = mockApi._getHandlers("before_provider_request")[0]
337
+ const payload = { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] }
338
+ const event = { payload }
339
+
340
+ // First call is miss
341
+ await handler(event, { model: { id: "local/deepseek-v4" } })
342
+
343
+ // Simulate a cache entry
344
+ const key = keyForPayload(payload)!
345
+ cache.set(key, {
346
+ chunks: [{ choices: [{ delta: { content: "cached" } }] }],
347
+ timestamp: Date.now(),
348
+ sizeBytes: 100,
349
+ })
350
+
351
+ // Second call should hit
352
+ const result = await handler(event, { model: { id: "local/deepseek-v4" } })
353
+ assert.ok(result)
354
+ assert.ok((result as any).__piL1Replay)
78
355
  })
79
356
 
80
- it("evicts by memory when full", () => {
81
- Test.setSettings({ maxEntries: 1000, maxMemoryBytes: 1000 })
82
- Test.reset()
83
- for (let i = 0; i < 5; i++) {
84
- Test._setCache(`key-${i}`, { response: new Array(100).fill("x"), timestamp: Date.now(), sizeBytes: 200 })
357
+ it("handles provider_stream_complete", async () => {
358
+ const mockApi = createMockExtensionAPI()
359
+ await l1CacheExtension(mockApi)
360
+
361
+ const handler = mockApi._getHandlers("provider_stream_complete")[0]
362
+ const event = {
363
+ payload: { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] },
364
+ chunks: [{ choices: [{ delta: { content: "answer" } }] }],
85
365
  }
86
- Test.evictIfNeeded()
87
- assert.ok(Test._getTotalMemory() > 0)
88
- assert.ok(Test._getTotalMemory() <= 1500)
366
+
367
+ await handler(event)
368
+
369
+ // Verify entry was stored
370
+ const key = keyForPayload(event.payload)!
371
+ const entry = cache.get(key)
372
+ assert.ok(entry)
373
+ assert.strictEqual(entry!.chunks.length, 1)
89
374
  })
90
375
 
91
- it("evictIfNeeded enforces limits", () => {
92
- for (let i = 0; i < 20; i++) {
93
- Test._setCache(`key-${i}`, { response: {}, timestamp: i, sizeBytes: 100 })
376
+ it("handles /l1-cache clear command", async () => {
377
+ const mockApi = createMockExtensionAPI()
378
+ await l1CacheExtension(mockApi)
379
+
380
+ const command = mockApi._getCommands().get("l1-cache")
381
+ const mockCtx = {
382
+ ui: {
383
+ notify: () => {},
384
+ },
94
385
  }
95
- Test.evictIfNeeded()
96
- const after = Test._getCacheSize()
97
- assert.ok(after <= 10)
386
+
387
+ // Populate first so the assertion is meaningful
388
+ cache.set("k", { chunks: [{ choices: [{ delta: { content: "x" } }] }], timestamp: Date.now(), sizeBytes: 64 })
389
+
390
+ await command!.handler("clear", mockCtx)
391
+
392
+ assert.strictEqual(cache.size, 0)
98
393
  })
99
394
 
100
- it("evictOldest removes specified count", () => {
101
- for (let i = 0; i < 10; i++) {
102
- Test._setCache(`key-${i}`, { response: {}, timestamp: i * 1000, sizeBytes: 100 })
395
+ it("handles /l1-cache stats command", async () => {
396
+ const mockApi = createMockExtensionAPI()
397
+ await l1CacheExtension(mockApi)
398
+
399
+ const command = mockApi._getCommands().get("l1-cache")
400
+ const mockCtx = {
401
+ ui: {
402
+ notify: (msg: string) => {
403
+ assert.ok(msg.includes("L1 cache"))
404
+ },
405
+ },
103
406
  }
104
- Test.evictOldest(3)
105
- assert.equal(Test._getCacheSize(), 7)
407
+
408
+ await command!.handler("stats", mockCtx)
106
409
  })
107
410
  })
108
411
 
109
- describe("state management", () => {
110
- beforeEach(() => {
111
- Test.reset()
112
- Test.setSettings({ maxEntries: 100, maxMemoryBytes: 1000000, ttlSeconds: 3600 })
412
+ describe("edge cases", () => {
413
+ it("handles disabled cache", async () => {
414
+ settings.enabled = false
415
+
416
+ const mockApi = createMockExtensionAPI()
417
+ await l1CacheExtension(mockApi)
418
+
419
+ const handler = mockApi._getHandlers("before_provider_request")[0]
420
+ const event = {
421
+ payload: { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] },
422
+ }
423
+
424
+ const result = await handler(event, { model: { id: "local/deepseek-v4" } })
425
+ assert.strictEqual(result, undefined)
113
426
  })
114
427
 
115
- it("returns correct state after adding entries", () => {
116
- Test._setCache("k1", { response: {}, timestamp: Date.now(), sizeBytes: 100 })
117
- Test._setCache("k2", { response: {}, timestamp: Date.now(), sizeBytes: 100 })
118
- assert.equal(Test._getCacheSize(), 2)
119
- assert.ok(Test._getTotalMemory() > 0)
428
+ it("handles empty chunks (no storage)", async () => {
429
+ const mockApi = createMockExtensionAPI()
430
+ await l1CacheExtension(mockApi)
431
+
432
+ const handler = mockApi._getHandlers("provider_stream_complete")[0]
433
+ const event = {
434
+ payload: { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] },
435
+ chunks: [], // empty
436
+ }
437
+
438
+ await handler(event)
439
+ const key = keyForPayload(event.payload)!
440
+ assert.strictEqual(cache.has(key), false)
120
441
  })
121
442
 
122
- it("reset clears all state", () => {
123
- Test._setCache("k1", { response: {}, timestamp: Date.now(), sizeBytes: 100 })
124
- Test.reset()
125
- assert.equal(Test._getCacheSize(), 0)
126
- assert.equal(Test._getTotalMemory(), 0)
443
+ it("handles stale ctx gracefully", async () => {
444
+ const mockApi = createMockExtensionAPI()
445
+ await l1CacheExtension(mockApi)
446
+
447
+ const handler = mockApi._getHandlers("before_provider_request")[0]
448
+ const event = {
449
+ payload: { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] },
450
+ }
451
+
452
+ // Stale ctx (no model.id)
453
+ const result = await handler(event, { model: { id: undefined } })
454
+ assert.strictEqual(result, undefined)
127
455
  })
128
456
 
129
- it("setSettings works", () => {
130
- Test.setSettings({ maxEntries: 999, maxMemoryBytes: 999999 })
131
- const state = Test.getState()
132
- assert.equal(state.settings.maxEntries, 999)
133
- assert.equal(state.settings.maxMemoryBytes, 999999)
457
+ it("prevents re-storing replays", async () => {
458
+ const mockApi = createMockExtensionAPI()
459
+ await l1CacheExtension(mockApi)
460
+
461
+ const beforeHandler = mockApi._getHandlers("before_provider_request")[0]
462
+ const completeHandler = mockApi._getHandlers("provider_stream_complete")[0]
463
+
464
+ const payload = { model: "local/deepseek-v4", messages: [{ role: "user", content: "test" }] }
465
+ const event = { payload }
466
+
467
+ // First call is miss
468
+ await beforeHandler(event, { model: { id: "local/deepseek-v4" } })
469
+
470
+ // Simulate cache hit
471
+ const key = keyForPayload(payload)!
472
+ cache.set(key, {
473
+ chunks: [{ choices: [{ delta: { content: "cached" } }] }],
474
+ timestamp: Date.now(),
475
+ sizeBytes: 100,
476
+ })
477
+
478
+ // Second call is hit
479
+ const result = await beforeHandler(event, { model: { id: "local/deepseek-v4" } })
480
+ assert.ok(result)
481
+
482
+ // Try to store (should be prevented by lastServed guard)
483
+ const chunksBefore = cache.get(key)!.chunks.length
484
+ await completeHandler({ payload, chunks: [{ choices: [{ delta: { content: "new" } }] }] })
485
+ const chunksAfter = cache.get(key)!.chunks.length
486
+
487
+ assert.strictEqual(chunksBefore, chunksAfter) // should not re-store
134
488
  })
135
489
  })
136
490
 
137
- describe("interceptor flow (extension lifecycle)", () => {
138
- type Handler = (ev?: any, ctx?: any) => any
139
- const events: Record<string, Handler[]> = {}
140
- let shutdown: (() => void) | undefined
491
+ describe("performance", () => {
492
+ it("hash is fast (< 10µs per call)", () => {
493
+ const iterations = 1000
494
+ const start = Date.now()
141
495
 
142
- before(async () => {
143
- const api: any = {
144
- registerCommand: () => {},
145
- on: (name: string, cb: Handler) => {
146
- ;(events[name] ??= []).push(cb)
147
- },
496
+ for (let i = 0; i < iterations; i++) {
497
+ fastHash("test string " + i)
148
498
  }
149
- await init(api)
150
- const shutdownCbs = events.session_shutdown ?? []
151
- shutdown = () => shutdownCbs.forEach((cb) => cb())
152
- })
153
-
154
- after(() => {
155
- shutdown?.()
156
- })
157
-
158
- beforeEach(() => {
159
- Test.reset()
160
- Test.setSettings({ enabled: true, maxEntries: 50, maxMemoryBytes: 100000, ttlSeconds: 3600 })
161
- })
162
-
163
- const payload = () => ({ messages: [{ role: "user", content: "hello cache" }], parameters: {} })
164
- const event = (): any => ({ type: "before_provider_request", payload: payload() })
165
- const reqCtx = { model: { id: "test-model" } }
166
-
167
- it("records a miss and stamps _cacheKey on first request", async () => {
168
- const ev: any = event()
169
- const ret = await events.before_provider_request[0](ev, reqCtx)
170
- assert.equal(ret, undefined)
171
- assert.ok(ev._cacheKey, "should stamp _cacheKey on miss")
172
- assert.equal(Test.getStats().misses, 1)
173
- })
174
-
175
- it("returns the cached response on a byte-identical repeat when a body is captured", async () => {
176
- const first: any = event()
177
- await events.before_provider_request[0](first, reqCtx)
178
- // Simulate a pi API that exposes the response body
179
- await events.after_provider_response[0]({
180
- type: "after_provider_response",
181
- status: 200,
182
- headers: {},
183
- _cacheKey: first._cacheKey,
184
- response: { content: "cached!" },
185
- } as any)
186
-
187
- const second: any = event()
188
- const out = await events.before_provider_request[0](second, reqCtx)
189
- assert.ok(out, "identical repeat should short-circuit to cache")
190
- assert.equal(out.content, "cached!")
191
- assert.equal(Test.getStats().hits, 1)
192
- })
193
-
194
- it("never stores an event envelope when no response body is available", async () => {
195
- const first: any = event()
196
- await events.before_provider_request[0](first, reqCtx)
197
- // pi 0.84 + shape: status + headers only, no body
198
- await events.after_provider_response[0]({
199
- type: "after_provider_response",
200
- status: 200,
201
- headers: {},
202
- } as any)
203
- assert.equal(Test._getCacheSize(), 0, "must not cache the event envelope")
204
- assert.ok(
205
- Test.getStats().misses >= 0,
206
- "after_provider_response without a body must not poison the cache",
207
- )
208
- })
209
-
210
- it("does not touch the cache when disabled", async () => {
211
- Test.setSettings({ enabled: false })
212
- const ev: any = event()
213
- const ret = await events.before_provider_request[0](ev, reqCtx)
214
- assert.equal(ret, undefined)
215
- assert.equal(ev._cacheKey, undefined)
216
- assert.equal(Test.getStats().misses, 0)
217
- })
218
-
219
- it("keeps size within cap under interceptor churn", async () => {
220
- Test.setSettings({ maxEntries: 20 })
221
- for (let i = 0; i < 50; i++) {
222
- const ev: any = {
223
- type: "before_provider_request",
224
- payload: { messages: [{ role: "user", content: `q-${i}` }], parameters: {} },
225
- }
226
- await events.before_provider_request[0](ev, reqCtx)
227
- await events.after_provider_response[0]({
228
- _cacheKey: ev._cacheKey,
229
- response: { content: "a".repeat(50) },
230
- } as any)
231
- }
232
- assert.ok(Test._getCacheSize() <= 20)
233
- assert.ok(Test.getStats().misses >= 50)
499
+
500
+ const elapsed = Date.now() - start
501
+ const avgPerCall = (elapsed * 1000) / iterations // in µs
502
+ assert.ok(avgPerCall < 10, `Hash average ${avgPerCall}µs, expected < 10µs`)
503
+ })
504
+
505
+ it("coalescing reduces chunk count", () => {
506
+ const chunks = Array.from({ length: 100 }, (_, i) => ({
507
+ choices: [{ delta: { content: "x" } }],
508
+ }))
509
+
510
+ const coalesced = coalesceChunks(chunks)
511
+ assert.strictEqual(coalesced.length, 1) // all merged into one
234
512
  })
235
513
  })
236
514
  })