claude-code-cache-fix 3.0.3 → 3.0.4
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 +14 -0
- package/package.json +1 -1
- package/proxy/extensions/cache-telemetry.mjs +89 -1
package/README.md
CHANGED
|
@@ -89,6 +89,20 @@ curl http://127.0.0.1:9801/health
|
|
|
89
89
|
# {"status":"ok"}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
### Proxy configuration
|
|
93
|
+
|
|
94
|
+
All proxy settings are controlled via environment variables. Set them before starting the proxy server.
|
|
95
|
+
|
|
96
|
+
| Variable | Default | Description |
|
|
97
|
+
|----------|---------|-------------|
|
|
98
|
+
| `CACHE_FIX_PROXY_PORT` | `9801` | Listen port |
|
|
99
|
+
| `CACHE_FIX_PROXY_BIND` | `127.0.0.1` | Bind address |
|
|
100
|
+
| `CACHE_FIX_PROXY_UPSTREAM` | `https://api.anthropic.com` | Upstream URL. Change to chain another proxy (e.g. `http://localhost:8080`) |
|
|
101
|
+
| `CACHE_FIX_PROXY_TIMEOUT` | `600000` | Request timeout in milliseconds |
|
|
102
|
+
| `CACHE_FIX_EXTENSIONS_DIR` | `proxy/extensions/` | Directory for extension `.mjs` files |
|
|
103
|
+
| `CACHE_FIX_EXTENSIONS_CONFIG` | `proxy/extensions.json` | Extension configuration file |
|
|
104
|
+
| `CACHE_FIX_DEBUG` | `0` | Enable debug logging |
|
|
105
|
+
|
|
92
106
|
### Corporate environments (proxies, custom CAs)
|
|
93
107
|
|
|
94
108
|
The proxy honors the following environment variables when forwarding to `api.anthropic.com`. Behind Zscaler / Netskope / Forcepoint / Bluecoat / corporate squid, set these in the proxy's environment.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-cache-fix",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Cache optimization proxy and interceptor for Claude Code. Fixes prompt cache bugs, stabilizes prefix, reduces quota burn.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./preload.mjs",
|
|
@@ -1,8 +1,63 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
const QUOTA_PATH = join(homedir(), ".claude", "quota-status.json");
|
|
6
|
+
|
|
7
|
+
function parseHeaders(headers) {
|
|
8
|
+
const get = (key) => headers[key] || "";
|
|
9
|
+
const num = (key) => parseFloat(get(key)) || 0;
|
|
10
|
+
|
|
11
|
+
const q5h_util = num("anthropic-ratelimit-unified-5h-utilization");
|
|
12
|
+
const q7d_util = num("anthropic-ratelimit-unified-7d-utilization");
|
|
13
|
+
const q5h_reset = parseInt(get("anthropic-ratelimit-unified-5h-reset")) || 0;
|
|
14
|
+
const q7d_reset = parseInt(get("anthropic-ratelimit-unified-7d-reset")) || 0;
|
|
15
|
+
const status = get("anthropic-ratelimit-unified-status") || get("anthropic-ratelimit-unified-5h-status");
|
|
16
|
+
const overage_status = get("anthropic-ratelimit-unified-overage-status");
|
|
17
|
+
const overage_util = num("anthropic-ratelimit-unified-overage-utilization");
|
|
18
|
+
const overage_reset = parseInt(get("anthropic-ratelimit-unified-overage-reset")) || 0;
|
|
19
|
+
const fallback_pct = get("anthropic-ratelimit-unified-fallback-percentage");
|
|
20
|
+
const representative = get("anthropic-ratelimit-unified-representative-claim");
|
|
21
|
+
const surpassed = get("anthropic-ratelimit-unified-7d-surpassed-threshold");
|
|
22
|
+
|
|
23
|
+
if (!q5h_reset && !q7d_reset) return null;
|
|
24
|
+
|
|
25
|
+
const now = new Date();
|
|
26
|
+
const hour = now.getUTCHours();
|
|
27
|
+
const day = now.getUTCDay();
|
|
28
|
+
const peak = day >= 1 && day <= 5 && hour >= 13 && hour < 19;
|
|
29
|
+
|
|
30
|
+
const allHeaders = {};
|
|
31
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
32
|
+
if (k.startsWith("anthropic-") || k === "cf-ray" || k === "request-id") {
|
|
33
|
+
allHeaders[k] = v;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
five_hour: { utilization: q5h_util, pct: Math.round(q5h_util * 100), resets_at: q5h_reset },
|
|
39
|
+
seven_day: { utilization: q7d_util, pct: Math.round(q7d_util * 100), resets_at: q7d_reset },
|
|
40
|
+
status: status || "unknown",
|
|
41
|
+
overage_status: overage_status || "unknown",
|
|
42
|
+
peak_hour: peak,
|
|
43
|
+
all_headers: allHeaders,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
1
47
|
export default {
|
|
2
48
|
name: "cache-telemetry",
|
|
3
|
-
description: "Extract cache
|
|
49
|
+
description: "Extract cache stats from response stream, persist quota state to ~/.claude/quota-status.json",
|
|
4
50
|
order: 600,
|
|
5
51
|
|
|
52
|
+
async onResponseStart(ctx) {
|
|
53
|
+
if (!ctx.headers) return;
|
|
54
|
+
|
|
55
|
+
const quota = parseHeaders(ctx.headers);
|
|
56
|
+
if (!quota) return;
|
|
57
|
+
|
|
58
|
+
ctx.meta._quotaData = quota;
|
|
59
|
+
},
|
|
60
|
+
|
|
6
61
|
async onStreamEvent(ctx) {
|
|
7
62
|
const { event, telemetry } = ctx;
|
|
8
63
|
if (!event || !telemetry) return;
|
|
@@ -19,6 +74,39 @@ export default {
|
|
|
19
74
|
if (event.type === "message_delta" && event.usage) {
|
|
20
75
|
if (!ctx.meta.cacheStats) ctx.meta.cacheStats = {};
|
|
21
76
|
ctx.meta.cacheStats.outputTokens = event.usage.output_tokens || 0;
|
|
77
|
+
|
|
78
|
+
const stats = ctx.meta.cacheStats;
|
|
79
|
+
const quota = ctx.meta._quotaData;
|
|
80
|
+
if (!quota) return;
|
|
81
|
+
|
|
82
|
+
const cr = stats.cacheRead || 0;
|
|
83
|
+
const cc = stats.cacheCreation || 0;
|
|
84
|
+
const total = cr + cc;
|
|
85
|
+
const hitRate = total > 0 ? ((cr / total) * 100).toFixed(1) : "N/A";
|
|
86
|
+
|
|
87
|
+
const ephemeral1h = cc;
|
|
88
|
+
const ephemeral5m = 0;
|
|
89
|
+
|
|
90
|
+
const ttl = cr > 0 ? "1h" : (cc > 0 ? "5m" : "unknown");
|
|
91
|
+
|
|
92
|
+
const output = {
|
|
93
|
+
cache: {
|
|
94
|
+
ttl_tier: ttl,
|
|
95
|
+
cache_creation: cc,
|
|
96
|
+
cache_read: cr,
|
|
97
|
+
ephemeral_1h: ephemeral1h,
|
|
98
|
+
ephemeral_5m: ephemeral5m,
|
|
99
|
+
hit_rate: hitRate,
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
},
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
...quota,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
mkdirSync(join(homedir(), ".claude"), { recursive: true });
|
|
108
|
+
writeFileSync(QUOTA_PATH, JSON.stringify(output, null, 2));
|
|
109
|
+
} catch {}
|
|
22
110
|
}
|
|
23
111
|
},
|
|
24
112
|
};
|