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/README.md CHANGED
@@ -1,188 +1,204 @@
1
1
  # pi-l1-cache
2
2
 
3
- > **L1 In-Memory Cache Extension for pi** — CPU/RAM optimized, production-ready
3
+ > **L1 In-Memory Cache Extension for pi** — with disk persistence, response replay, and working capture
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Pi Package](https://img.shields.io/badge/pi-package-blue)](https://pi.dev/packages)
7
- [![npm version](https://img.shields.io/npm/v/pi-l1-cache)](https://www.npmjs.com/package/pi-l1-cache)
8
- [![CI](https://github.com/tobias-weiss-ai-xr/pi-l1-cache/actions/workflows/ci.yml/badge.svg)](https://github.com/tobias-weiss-ai-xr/pi-l1-cache/actions/workflows/ci.yml)
7
+ [![npm version](https://www.npmjs.com/package/pi-l1-cache)](https://www.npmjs.com/package/pi-l1-cache)
9
8
 
10
- A high-performance, production-ready **L1 (in-memory) cache** extension for the [pi coding agent](https://pi.dev). Designed for stoic Unix simplicity: one file, one purpose, zero dependencies.
9
+ A high-performance, production-ready **L1 (in-memory + disk) cache** extension for the [pi coding agent](https://pi.dev). Unlike the stock API which lacks response capture, this implementation uses a **pi-core patch** to enable full caching with replay.
10
+
11
+ ## ⚡ Performance
12
+
13
+ | Scenario | Cold | Warm (replayed) | Speedup |
14
+ |---|---|---|---|
15
+ | Simple prompt | 3.2s | **1.9s** | ~1.7× |
16
+ | **Tool-calling agent loop** | 13.1s | **2.5s** | **5.2×** |
11
17
 
12
18
  ## Features
13
19
 
14
20
  | Feature | Description |
15
21
  |---------|-------------|
22
+ | **Response replay** | Cached chunks are fed through pi-ai's normal consume path — parsing, usage, tool-calls, stop-reason all identical |
23
+ | **Disk persistence** | Survives across `pi -p` process restarts (`~/.pi/agent/cache/l1-cache/`) |
16
24
  | **~138µs overhead** | End-to-end per-request cost (measured, incl. key hashing + JSON) |
17
25
  | **Fast key hash** | FNV-1a (~1.5µs/op) — no per-request CPU probe on the hot path |
18
26
  | **Memory cap** | Hard limit (20MB default) prevents RAM bloat |
19
27
  | **Auto-eviction** | LRU-style cleanup when limits reached |
20
- | **CPU-aware** | Auto-disables when CPU > 95% (checked once at startup) |
21
28
  | **TTL-based** | 1-hour default expiry for cached entries |
22
- | **Atomic cleanup** | Periodic expired entry removal |
29
+ | **Coalescing** | Adjacent content/reasoning_content deltas merged to shrink storage |
23
30
 
24
31
  ## Architecture
25
32
 
26
33
  ```
27
- pi → [L1: RAM Map] [L2: Redis via LiteLLM] → Provider
28
- ~138µs ~150ms 1-3s
34
+ pi → [L1: RAM Map + disk] → Provider
35
+ ~138µs lookup 1-3s
29
36
  ```
30
37
 
38
+ The extension uses a **pi-core patch** (`fix-l1-cache.cjs`) to add two capabilities the stock extension API lacks:
39
+
40
+ > **Bundle era (pi ≥ 0.84):** pi runs from the esbuild bundle (`dist/bundle/cli.js`), so `fix-l1-cache.cjs` patches `dist/bundle/chunks/*` (minified, anchor-matched — verified against 0.86.0). Pre-bundle pi (< 0.84) is patched in the readable pi-ai sources. The patch is idempotent and bundle-era misses degrade to pass-through rather than failing an install.
41
+
42
+ 1. **REPLAY** — an extension may serve a cached response by returning params with `__piL1Replay: [chunks]` from `before_provider_request`; pi-ai feeds the cached chunks through the normal consume path and never contacts the provider.
43
+
44
+ 2. **CAPTURE** — after a successful completion, pi-ai calls `options.onStreamComplete(allChunks, requestParams)`; `sdk.js` forwards them to extensions as a `provider_stream_complete` event so the cache can store the response.
45
+
31
46
  ## Installation
32
47
 
48
+ ### Option 1: From npm (recommended)
49
+
33
50
  ```bash
34
- # From npm (recommended)
35
51
  pi install npm:pi-l1-cache
52
+ ```
53
+
54
+ This installs the extension AND automatically applies the `fix-l1-cache.cjs` pi-core patch.
36
55
 
37
- # From GitHub
38
- pi install git:github.com/tobias-weiss-ai-xr/pi-l1-cache@main
56
+ ### Option 2: From GitHub
57
+
58
+ ```bash
59
+ pi install git:github.com:tobias-weiss-ai-xr/pi-l1-cache@main
60
+ ```
39
61
 
40
- # From local clone
62
+ ### Option 3: From local clone
63
+
64
+ ```bash
41
65
  pi install /path/to/pi-l1-cache
42
66
  ```
43
67
 
44
68
  Then **restart pi** — the extension auto-loads.
45
69
 
46
- ## Usage
70
+ ## Configuration
71
+
72
+ ### Environment Variables
47
73
 
48
74
  ```bash
49
- # Show cache stats
50
- /l1-cache
75
+ # Enable/disable
76
+ L1_CACHE_ENABLED=true
51
77
 
52
- # Show detailed stats
53
- /l1-cache stats
78
+ # Max entries (default: 200)
79
+ L1_CACHE_MAX_ENTRIES=200
54
80
 
55
- # Clear cache
56
- /l1-cache clear
81
+ # Max memory in MB (default: 20)
82
+ L1_CACHE_MAX_MB=20
57
83
 
58
- # Enable cache (if disabled)
59
- /l1-cache enable
84
+ # TTL in seconds (default: 3600)
85
+ L1_CACHE_TTL=3600
60
86
 
61
- # Disable cache (if enabled)
62
- /l1-cache disable
87
+ # Log stats on each hit/miss (default: false)
88
+ L1_CACHE_LOG=true
63
89
  ```
64
90
 
65
- ### Stats Output
66
-
67
- ```
68
- L1 cache status: ENABLED
69
- Entries: 47 / 200
70
- Memory: 4.2MB / 20MB
71
- TTL: 3600s | CPU threshold: 95%
72
- Hits: 23 | Misses: 70 | Evictions: 5
73
- Hit rate: 24.7%
74
- Init CPU: 45.2% (ok)
75
- Last cleanup: 2025-08-22T10:30:00.000Z
91
+ ### Settings (in `~/.pi/settings.json`)
92
+
93
+ ```json
94
+ {
95
+ "extensions": {
96
+ "l1-cache": {
97
+ "enabled": true,
98
+ "maxEntries": 200,
99
+ "maxMemoryBytes": 20971520,
100
+ "ttlSeconds": 3600,
101
+ "persist": true,
102
+ "logStats": false
103
+ }
104
+ }
105
+ }
76
106
  ```
77
107
 
78
- ## Configuration
108
+ ## Usage
79
109
 
80
- ### Environment Variables
110
+ ### Show cache stats
81
111
 
82
- Change defaults without modifying source code:
112
+ ```bash
113
+ /l1-cache
114
+ ```
83
115
 
84
- | Variable | Default | Description |
85
- |----------|---------|-------------|
86
- | `L1_CACHE_ENABLED` | `true` | Master enable/disable |
87
- | `L1_CACHE_MAX_ENTRIES` | `200` | Maximum number of cache entries |
88
- | `L1_CACHE_MAX_MB` | `20` | Maximum memory in MB |
89
- | `L1_CACHE_TTL` | `3600` | TTL in seconds (1 hour) |
90
- | `L1_CACHE_LOG` | `false` | Enable debug logging |
116
+ Output:
117
+ ```
118
+ L1 cache: 16 entries, 43.2KB | hits 12 (replays 12), misses 4, writes 4, evictions 0 | dir: C:/Users/Tobias/.pi/agent/cache/l1-cache
119
+ ```
91
120
 
92
- Example:
93
- ```bash
94
- # Disable cache
95
- L1_CACHE_ENABLED=false pi
121
+ ### Clear cache
96
122
 
97
- # Use 50MB cache with 30-minute TTL
98
- L1_CACHE_MAX_MB=50 L1_CACHE_TTL=1800 pi
123
+ ```bash
124
+ /l1-cache clear
99
125
  ```
100
126
 
101
- ### Default Settings
127
+ Cleared: memory + disk (all `.json` files in cache dir).
102
128
 
103
- Edit `src/index.ts` (lines 30-38) to change compiled-in defaults:
129
+ ## Key Semantics
104
130
 
105
- ```typescript
106
- const DEFAULTS: Settings = {
107
- enabled: true,
108
- maxEntries: 200,
109
- maxMemoryBytes: 20 * 1024 * 1024, // 20MB
110
- ttlSeconds: 3600, // 1 hour
111
- cpuThreshold: 95, // disable if CPU > 95%
112
- logStats: false,
113
- }
114
- ```
131
+ The cache key is a stable hash of:
132
+ - `model`
133
+ - `messages` (full conversation history)
134
+ - `tools`
135
+ - `tool_choice`
136
+ - `temperature`, `top_p`
137
+ - `reasoning_effort`, `thinking`
138
+ - `max_completion_tokens`, `max_tokens`
115
139
 
116
- ## Design Philosophy
140
+ **Volatile fields are EXCLUDED:** `prompt_cache_key`, `prompt_cache_retention`, `stream`, `stream_options`, `store`, `sessionId`.
117
141
 
118
- ### Stoic Unix Principles
119
- - **One thing, done well** Caching, and only caching
120
- - **Do not rely on external services** Pure in-memory, no Redis
121
- - **Graceful degradation** Works even on constrained systems
122
- - **Zero dependencies** Single TypeScript file
142
+ This means:
143
+ - Cross-run hits possible (same prompt, same cwd, same model)
144
+ - Tool-calling agent loops fully replayed (tool results are part of messages)
145
+ - Different conversation history = different key (expected)
146
+ - Session-derived fields don't break cross-run hits
123
147
 
124
- ### Performance Optimizations
125
- 1. **Cheap key hashing** (FNV-1a, ~1.5µs/op). Note: Node's native SHA-256 (OpenSSL) is marginally *faster* than a JS FNV loop — hashing is a rounding error next to `JSON.stringify(messages)`, and both are >1000× below provider latency. The real win is *not* a faster hash, it is skipping the provider call.
126
- 2. **L1 only** — avoid disk I/O in hot path
127
- 3. **Batch eviction** — remove 10-20% at a time, not one-by-one
128
- 4. **Periodic cleanup** — async, non-blocking garbage collection
129
- 5. **Single CPU check** — at startup only, not per-request
148
+ ## Gotchas
130
149
 
131
- ## Testing
150
+ 1. **Replays are canned** — identical input returns the stored response verbatim. For fresh answers, use `/l1-cache clear`.
132
151
 
133
- ```bash
134
- # Run all tests
135
- npm test
152
+ 2. **Print mode** (`pi -p`) reads entire stdin as ONE prompt — you cannot test two identical requests in one process this way.
136
153
 
137
- # Watch mode
138
- npm run test:watch
154
+ 3. **llm-timestamp.js** does NOT pollute provider messages (it only appends display-level `message_end` entries), so keys are stable across runs.
139
155
 
140
- # Check TypeScript
141
- npx tsc --noEmit
142
- ```
156
+ 4. **Replayed responses carry original responseId/usage** — accurate since input identical.
143
157
 
144
- 17 tests covering:
145
- - Hash consistency & collision resistance
146
- - Size estimation for various data types
147
- - LRU eviction behavior (entry count + memory)
148
- - State management & reset
149
- - Settings override
158
+ ## Troubleshooting
150
159
 
151
- ## Benchmark
160
+ ### "fix-reasoning-content.js: layout changed"
152
161
 
153
- Measured against the published npm artifact (`pi-l1-cache@1.2.1`) on Node 22, using the package's own test hooks and a mocked `ExtensionAPI` with a realistic 15-message conversation history:
162
+ The `fix-l1-cache.cjs` patch modifies the same file as `fix-reasoning-content.js`. The wrapper's postinstall chain handles this correctly `fix-reasoning-content.js` now recognizes its work via marker even after the replay branch is added.
154
163
 
155
- | Measurement | Result |
156
- |---|---|
157
- | `fastHash` (FNV-1a) standalone | ~670k ops/s — 1.49µs/op |
158
- | Node native `sha256` (for reference) | ~850k ops/s — 1.18µs/op |
159
- | Full put: hash + map set + size check | ~249k ops/s — 4.0µs/op |
160
- | Interceptor path (hit *and* miss, 15-msg history) | ~138µs/request |
161
- | Cache hit vs uncached provider round-trip (1–3s) | ~7,000–20,000× wall-clock |
162
- | Key uniqueness over 50k synthetic keys | 50,000/50,000 (no collisions) |
163
- | Eviction under 200-slot cap, 20k inserts | cap held; 19,800 evicted |
164
+ If you see this error:
165
+ 1. Ensure you're running the full postinstall chain (not individual scripts)
166
+ 2. Check that `fix-reasoning-content.js` has the marker-based detection (v1.2.3+)
167
+ 3. Verify postinstall order: `fix-reasoning-content.js` BEFORE `fix-l1-cache.cjs`
164
168
 
165
- > **Correction:** earlier versions claimed FNV-1a was "~100× faster than SHA256". That was wrong — Node's native SHA-256 is ~0.8× *faster* in practice. The hash was never the bottleneck: `JSON.stringify` dominates the ~138µs per-request cost. Cache hits are keyed by *byte-identical* requests, so real-world hit rate depends on your workload (best for retries, repeated tool calls and same-prompt reruns).
169
+ ### Cache never hits
166
170
 
167
- ## pi API compatibility
171
+ Check:
172
+ 1. `L1_CACHE_LOG=true` to see HIT/MISS/STORED logs
173
+ 2. Prompt is byte-identical (including system prompt, tools, cwd context)
174
+ 3. Disk persistence is enabled (`persist: true`)
175
+ 4. TTL hasn't expired (default 1h)
168
176
 
169
- Verified against the **pi 0.84.x** extension API — two runtime facts shape the behaviour:
177
+ ## Development
170
178
 
171
- - `before_provider_request` receives the assembled provider request as `event.payload` (model, messages, parameters); cache keys are derived from it. In current pi this hook is a payload *transform*, not a response short-circuit, so a cached response is only ever returned once a response body has actually been captured.
172
- - `after_provider_response` currently carries only `{ status, headers }` — **no response body**. Until a pi version exposes the body, responses cannot be stored; the extension detects this, logs a one-time note, and keeps `/l1-cache` stats working. It upgrades automatically (no config) on any pi version that exposes the body.
179
+ ### Testing
173
180
 
174
- On pi 0.84 the extension therefore operates as a request-key instrumentation layer (Hits/Misses/Evictions via `/l1-cache`, CPU guard, TTL bookkeeping) and only short-circuits identical requests when the API contract provides the body it needs.
181
+ ```bash
182
+ # Run unit tests
183
+ npm test
184
+
185
+ # Run the extension manually
186
+ npx tsx src/index.ts
187
+ ```
175
188
 
176
- ## Related Projects
189
+ ### Benchmark
177
190
 
178
- - **[opencode-saia-plugin](https://github.com/tobias-weiss-ai-xr/opencode-saia-plugin)** — SAIA provider for OpenCode
179
- - **[zot-saia-plugin](https://github.com/tobias-weiss-ai-xr/zot-saia-plugin)** SAIA provider for zot CLI
180
- - **[pi-saia-plugin](https://github.com/tobias-weiss-ai-xr/pi-saia-plugin)** SAIA provider for pi coding agent
191
+ ```bash
192
+ # Measure cold vs warm times
193
+ echo "What is 7*6?" | time pi -p "test" # cold
194
+ echo "What is 7*6?" | time pi -p "test" # warm (should be ~1.7× faster)
195
+ ```
181
196
 
182
197
  ## License
183
198
 
184
199
  MIT — see [LICENSE](LICENSE)
185
200
 
186
- ## Maintainer
201
+ ## Contact
187
202
 
188
- [Tobias Weiß](https://github.com/tobias-weiss-ai-xr) — info@graphwiz.ai
203
+ - Issues: https://github.com/tobias-weiss-ai-xr/pi-l1-cache/issues
204
+ - Email: info@graphwiz.ai