agent-cache-optimizer 0.5.4 → 0.6.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.
@@ -0,0 +1,287 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { coldStartScore, classify } from "../heuristics"
3
+ import { splitBlock } from "../splitting"
4
+ import { emptyDB } from "../core"
5
+
6
+ // ── coldStartScore ─────────────────────────────────────────────────────
7
+
8
+ describe("coldStartScore", () => {
9
+ it("returns <=0.15 for currentDate-like line", () => {
10
+ const score = coldStartScore("currentDate: 2025-06-25", 5, 10)
11
+ expect(score).toBeLessThanOrEqual(0.15)
12
+ })
13
+
14
+ it("returns <=0.25 for 'Current date:' line", () => {
15
+ const score = coldStartScore("Current date: Tuesday, June 25th 2026", 3, 10)
16
+ expect(score).toBeLessThanOrEqual(0.25)
17
+ })
18
+
19
+ it("returns <=0.25 for 'Today is' line", () => {
20
+ const score = coldStartScore("Today is a great day to build", 4, 10)
21
+ expect(score).toBeLessThanOrEqual(0.25)
22
+ })
23
+
24
+ it("returns <=0.25 for 'Session:' line", () => {
25
+ const score = coldStartScore("Session: abc-123-def", 2, 10)
26
+ expect(score).toBeLessThanOrEqual(0.25)
27
+ })
28
+
29
+ it("returns <=0.25 for 'session id' line", () => {
30
+ const score = coldStartScore("session id: xyz-987", 6, 10)
31
+ expect(score).toBeLessThanOrEqual(0.25)
32
+ })
33
+
34
+ it("returns <=0.25 for 'timestamp' line", () => {
35
+ const score = coldStartScore("timestamp: 1719300000", 3, 10)
36
+ expect(score).toBeLessThanOrEqual(0.25)
37
+ })
38
+
39
+ it("returns <=0.25 for 'Last updated' line", () => {
40
+ const score = coldStartScore("Last updated: 2026-06-25 13:00:00", 5, 10)
41
+ expect(score).toBeLessThanOrEqual(0.25)
42
+ })
43
+
44
+ it("returns <=0.25 for 'ISO timestamp' line", () => {
45
+ const score = coldStartScore("ISO timestamp: 2026-06-25T13:00:00Z", 4, 10)
46
+ expect(score).toBeLessThanOrEqual(0.25)
47
+ })
48
+
49
+ it("matches dynamic metadata labels case-insensitively", () => {
50
+ const filler = " stable-looking content".repeat(20)
51
+ expect(coldStartScore(`Timestamp: 1719300000${filler}`, 3, 10)).toBeLessThanOrEqual(0.25)
52
+ expect(coldStartScore(`Session ID: xyz-987${filler}`, 3, 10)).toBeLessThanOrEqual(0.25)
53
+ expect(coldStartScore(`LAST UPDATED: 2026-06-25${filler}`, 3, 10)).toBeLessThanOrEqual(0.25)
54
+ })
55
+ })
56
+
57
+ // ── classify unknown tiering ───────────────────────────────────────────
58
+
59
+ describe("classify", () => {
60
+ it("routes coldStart=0.5 blocks to stable instead of unknown", () => {
61
+ // 150-200 char block at index 2 with total ~8 → baseline score 0.5
62
+ // With new 0.5 threshold → stable (was unknown with old 0.7)
63
+ const db = emptyDB()
64
+ const block =
65
+ "A moderate length prompt block that does not trigger any special heuristic in coldStartScore and gets the baseline of 0.5 which is now our stable threshold."
66
+ const blocks = ["a", "b", block, "c", "d", "e", "f", "g"]
67
+ const result = classify(blocks, db, { warmThreshold: 0 })
68
+ expect(result.stable).toContain(block)
69
+ expect(result.unknown).toHaveLength(0)
70
+ })
71
+
72
+ it("routes short/metadata blocks to dynamic instead of unknown", () => {
73
+ const db = emptyDB()
74
+ const block = "Short meta block" // <100 chars → score ≤0.2 → dynamic
75
+ const result = classify(["prefix", block, "suffix"], db, { warmThreshold: 0 })
76
+ expect(result.dynamic).toContain(block)
77
+ expect(result.unknown).toHaveLength(0)
78
+ })
79
+
80
+ it("reduces unknown bucket size with tiered classification", () => {
81
+ const db = emptyDB()
82
+ const blocks = [
83
+ '{"role": "system", "content": "You are a helpful assistant."}', // structured, ~0.8 → stable
84
+ "currentDate: 2026-06-25", // dynamic pattern, ≤0.15 → dynamic
85
+ "50 char block that sits right in our heuristics", // short (< 100) → ≤0.2 → dynamic
86
+ ]
87
+ const result = classify(blocks, db, { warmThreshold: 0 })
88
+ expect(result.unknown.length).toBeLessThan(blocks.length)
89
+ })
90
+
91
+ it("lets contentScore 0.2-0.7 range fall through to coldStartScore tiering", () => {
92
+ // When contentScore is in the middle range, it should fall through
93
+ // to coldStartScore which uses the new thresholds
94
+ const db = emptyDB()
95
+ const block = "A short-ish block that would have mid content score"
96
+ const result = classify(["p1", block, "p2"], db, { warmThreshold: 0 })
97
+ // All 3 blocks should be classified (none stuck in unknown limbo)
98
+ expect(result.dynamic.length + result.stable.length).toBe(3)
99
+ })
100
+
101
+ it("passes through structured stable blocks correctly", () => {
102
+ const db = emptyDB()
103
+ // JSON block at index 0 with total=3 → structured boost max(0.15, 0.8) = 0.8
104
+ const stable = '{"key": "value", "nested": {"a": 1}}'
105
+ const result = classify([stable, "p1", "p2"], db, { warmThreshold: 0 })
106
+ expect(result.stable).toContain(stable)
107
+ })
108
+
109
+ it("keeps structured currentDate metadata dynamic after structural boosts", () => {
110
+ const db = emptyDB()
111
+ const volatile = '{"currentDate": "2026-06-25", "instructions": "You are stable."}'
112
+ const result = classify([volatile, "p1", "p2"], db, { warmThreshold: 0 })
113
+ expect(result.dynamic).toContain(volatile)
114
+ expect(result.stable).not.toContain(volatile)
115
+ })
116
+ })
117
+
118
+ // ── splitBlock Markdown ### ────────────────────────────────────────────
119
+
120
+ describe("splitBlock Markdown ###", () => {
121
+ it("splits long blocks at top-level # section headers", () => {
122
+ const block = `# Setup
123
+ Install the package
124
+
125
+ # Usage
126
+ Run the command
127
+
128
+ # Troubleshooting
129
+ Inspect logs`
130
+ const result = splitBlock(block, 50)
131
+ expect(result).toHaveLength(3)
132
+ expect(result[0]).toContain("# Setup")
133
+ expect(result[1]).toContain("# Usage")
134
+ expect(result[2]).toContain("# Troubleshooting")
135
+ })
136
+
137
+ it("splits long blocks at ### section headers", () => {
138
+ const block = `# Overview
139
+ Some intro text
140
+
141
+ ### Installation
142
+ Install via npm
143
+
144
+ ### Configuration
145
+ Configure the tool
146
+
147
+ ### Usage
148
+ Run the command`
149
+ const result = splitBlock(block, 50) // low threshold forces splitting
150
+ expect(result.length).toBeGreaterThanOrEqual(3)
151
+ expect(result[0]).toContain("# Overview")
152
+ expect(result[1]).toContain("### Installation")
153
+ expect(result[2]).toContain("### Configuration")
154
+ })
155
+
156
+ it("does not split at ### when block is small enough", () => {
157
+ const block = `### Small
158
+ Content`
159
+ const result = splitBlock(block, 4000)
160
+ expect(result).toEqual([block])
161
+ })
162
+
163
+ it("splits at ### when no ## exist and block exceeds threshold", () => {
164
+ // Has ### but no ## (exactly two hashes)
165
+ const block = `# Title
166
+ Some text
167
+
168
+ ### Section One
169
+ Content here
170
+
171
+ ### Section Two
172
+ More content here`
173
+ const result = splitBlock(block, 20)
174
+ expect(result.length).toBeGreaterThanOrEqual(2)
175
+ })
176
+
177
+ it("does not split headings inside fenced code blocks", () => {
178
+ const block = `# Real Section
179
+ Content
180
+
181
+ \`\`\`md
182
+ # Not A Section
183
+ ## Still Code
184
+ \`\`\`
185
+
186
+ # Next Section
187
+ More content`
188
+ const result = splitBlock(block, 50)
189
+ expect(result).toHaveLength(2)
190
+ expect(result[0]).toContain("# Not A Section")
191
+ expect(result[1]).toContain("# Next Section")
192
+ })
193
+
194
+ it("splits long markdown lists by top-level list items", () => {
195
+ const block = `- Alpha item
196
+ continued alpha
197
+ - Beta item
198
+ continued beta
199
+ - Gamma item
200
+ continued gamma`
201
+ const result = splitBlock(block, 30)
202
+ expect(result).toEqual([
203
+ "- Alpha item\n continued alpha",
204
+ "- Beta item\n continued beta",
205
+ "- Gamma item\n continued gamma",
206
+ ])
207
+ })
208
+ })
209
+
210
+ // ── splitBlock JSON tool schema ────────────────────────────────────────
211
+
212
+ describe("splitBlock JSON arrays", () => {
213
+ it("splits a JSON array of tool definitions", () => {
214
+ const block = JSON.stringify(
215
+ [
216
+ { name: "tool_a", description: "Tool A", parameters: { type: "object", properties: {} } },
217
+ { name: "tool_b", description: "Tool B", parameters: { type: "object", properties: {} } },
218
+ { name: "tool_c", description: "Tool C", parameters: { type: "object", properties: {} } },
219
+ ],
220
+ null,
221
+ 2,
222
+ )
223
+ const result = splitBlock(block, 100)
224
+ expect(result.length).toBeGreaterThanOrEqual(3)
225
+ expect(result[0]).toContain("tool_a")
226
+ expect(result[1]).toContain("tool_b")
227
+ })
228
+
229
+ it("splits consecutive JSON objects not in an array", () => {
230
+ const block = `{"name": "tool_a", "description": "First tool"}
231
+ {"name": "tool_b", "description": "Second tool"}
232
+ {"name": "tool_c", "description": "Third tool"}`
233
+ const result = splitBlock(block, 50)
234
+ expect(result.length).toBeGreaterThanOrEqual(3)
235
+ expect(result[0]).toContain("tool_a")
236
+ expect(result[1]).toContain("tool_b")
237
+ })
238
+
239
+ it("splits deeply nested JSON objects in an array", () => {
240
+ const block = JSON.stringify(
241
+ [
242
+ {
243
+ name: "complex_tool",
244
+ parameters: {
245
+ type: "object",
246
+ properties: {
247
+ field1: { type: "string", description: "A field" },
248
+ field2: { type: "number", description: "Another field" },
249
+ },
250
+ required: ["field1"],
251
+ },
252
+ },
253
+ {
254
+ name: "another_tool",
255
+ parameters: {
256
+ type: "object",
257
+ properties: {
258
+ param_a: { type: "string" },
259
+ },
260
+ },
261
+ },
262
+ ],
263
+ null,
264
+ 2,
265
+ )
266
+ const result = splitBlock(block, 200)
267
+ expect(result.length).toBeGreaterThanOrEqual(2)
268
+ expect(result[0]).toContain("complex_tool")
269
+ expect(result[1]).toContain("another_tool")
270
+ })
271
+ })
272
+
273
+ // ── splitBlock XML siblings ───────────────────────────────────────────
274
+
275
+ describe("splitBlock XML siblings", () => {
276
+ it("splits top-level XML sibling elements", () => {
277
+ const block = `<tool><name>a</name><description>first</description></tool>
278
+ <tool><name>b</name><description>second</description></tool>
279
+ <tool><name>c</name><description>third</description></tool>`
280
+ const result = splitBlock(block, 50)
281
+ expect(result).toEqual([
282
+ "<tool><name>a</name><description>first</description></tool>",
283
+ "<tool><name>b</name><description>second</description></tool>",
284
+ "<tool><name>c</name><description>third</description></tool>",
285
+ ])
286
+ })
287
+ })