claude-code-cache-fix 3.5.4 → 3.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.
- package/README.md +37 -0
- package/package.json +5 -2
- package/proxy/extensions/cache-telemetry.mjs +5 -1
- package/proxy/server.mjs +97 -26
package/README.md
CHANGED
|
@@ -167,6 +167,43 @@ node "$(npm root -g)\claude-code-cache-fix\proxy\server.mjs"
|
|
|
167
167
|
|
|
168
168
|
Stderr will print `[upstream] using proxy http://proxy.corp.example:8080 ...` on first request when the agent is wired correctly. With no proxy/CA env vars set, behavior is unchanged from earlier versions (Node default agent, system trust store).
|
|
169
169
|
|
|
170
|
+
### Embedding the proxy in your own process
|
|
171
|
+
|
|
172
|
+
If you ship a Node or Bun binary that wants the cache-fix proxy in-process (e.g. a Bun-compiled agent that avoids forking a Node child), import the factory from `claude-code-cache-fix/proxy/server`:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
import { startProxy } from "claude-code-cache-fix/proxy/server";
|
|
176
|
+
|
|
177
|
+
const handle = await startProxy({
|
|
178
|
+
port: 0, // OS-assigned ephemeral port; pass a number to pin
|
|
179
|
+
bind: "127.0.0.1",
|
|
180
|
+
watch: false, // skip fs.watch — recommended for compiled binaries
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
console.log(`proxy listening on ${handle.address}:${handle.port}`);
|
|
184
|
+
|
|
185
|
+
// ...later...
|
|
186
|
+
await handle.close();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**`createProxyServer()` → `http.Server`** builds the request handler wired into an `http.Server`. The returned server is *not* listening and the extension pipeline has not been loaded — use this when you want to manage the lifecycle yourself.
|
|
190
|
+
|
|
191
|
+
**`startProxy(options?)` → `Promise<{ server, port, address, close }>`** loads the extension pipeline, optionally starts the file watcher, and starts listening. Returns a handle with the bound port (resolved when `port: 0` is requested) and a `close()` that releases the server and the watcher.
|
|
192
|
+
|
|
193
|
+
Options (all optional; all fall back to the same env vars used by the CLI):
|
|
194
|
+
|
|
195
|
+
| Option | Default | Effect |
|
|
196
|
+
|--------|---------|--------|
|
|
197
|
+
| `port` | `CACHE_FIX_PROXY_PORT` env, else `9801` | Listen port. Pass `0` for an OS-assigned ephemeral port. |
|
|
198
|
+
| `bind` | `CACHE_FIX_PROXY_BIND` env, else `127.0.0.1` | Bind address. |
|
|
199
|
+
| `extensionsDir` | package `proxy/extensions/` | Directory to load `.mjs` extensions from. |
|
|
200
|
+
| `extensionsConfig` | package `proxy/extensions.json` | Path to extension config. |
|
|
201
|
+
| `watch` | `true` | Whether to start `fs.watch` on the extensions config. Set `false` for embedded / compiled-binary use. |
|
|
202
|
+
|
|
203
|
+
**One extension registry per process.** The pipeline maintains a single shared extension registry at module scope. Hosting two `startProxy()` instances in the same process is supported (different ports, different bind addresses), but they share that registry — a subsequent `loadExtensions` call replaces it for both. If you need divergent extension configs per instance, run them in separate processes.
|
|
204
|
+
|
|
205
|
+
**CLI invocation is unchanged.** `node proxy/server.mjs`, `cache-fix-proxy server`, and the wrapper's child-fork path all auto-listen and install SIGTERM/SIGINT handlers as before. Library imports never trigger that behavior — the auto-listen is gated behind a main-module check.
|
|
206
|
+
|
|
170
207
|
## Quick Start: Preload (CC v2.1.112 and earlier)
|
|
171
208
|
|
|
172
209
|
If you're on a Node.js-based CC version (v2.1.112 or earlier), the preload interceptor works without a proxy:
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-cache-fix",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
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
|
-
"exports":
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./preload.mjs",
|
|
8
|
+
"./proxy/server": "./proxy/server.mjs"
|
|
9
|
+
},
|
|
7
10
|
"main": "./preload.mjs",
|
|
8
11
|
"bin": {
|
|
9
12
|
"cache-fix-proxy": "./bin/claude-via-proxy.mjs"
|
|
@@ -71,11 +71,15 @@ function parseHeaders(headers) {
|
|
|
71
71
|
const overage_status = get("anthropic-ratelimit-unified-overage-status");
|
|
72
72
|
const overage_util = num("anthropic-ratelimit-unified-overage-utilization");
|
|
73
73
|
const overage_reset = parseInt(get("anthropic-ratelimit-unified-overage-reset")) || 0;
|
|
74
|
+
const unified_reset = parseInt(get("anthropic-ratelimit-unified-reset")) || 0;
|
|
74
75
|
const fallback_pct = get("anthropic-ratelimit-unified-fallback-percentage");
|
|
75
76
|
const representative = get("anthropic-ratelimit-unified-representative-claim");
|
|
76
77
|
const surpassed = get("anthropic-ratelimit-unified-7d-surpassed-threshold");
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
// Accept any reset timestamp — accounts without 5h/7d quota windows (overage
|
|
80
|
+
// billing) return anthropic-ratelimit-unified-reset and/or
|
|
81
|
+
// anthropic-ratelimit-unified-overage-reset instead.
|
|
82
|
+
if (!q5h_reset && !q7d_reset && !unified_reset && !overage_reset) return null;
|
|
79
83
|
|
|
80
84
|
const now = new Date();
|
|
81
85
|
const hour = now.getUTCHours();
|
package/proxy/server.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
2
3
|
import config from "./config.mjs";
|
|
3
4
|
import { forwardRequest } from "./upstream.mjs";
|
|
4
5
|
import { streamResponse, createTelemetryRecord } from "./stream.mjs";
|
|
@@ -138,36 +139,106 @@ function handleNotFound(_req, res) {
|
|
|
138
139
|
res.end(JSON.stringify({ error: "not_found" }));
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Builds an http.Server with the proxy's request handler wired in. The
|
|
144
|
+
* returned server is **not** listening and the extension pipeline has not
|
|
145
|
+
* been initialized — callers wanting a one-call setup should use
|
|
146
|
+
* `startProxy()` instead.
|
|
147
|
+
*
|
|
148
|
+
* Exposed so callers can embed the proxy in their own process (e.g.
|
|
149
|
+
* Bun-compiled binaries, test harnesses) without forking a child or
|
|
150
|
+
* shelling out to the `cache-fix-proxy` bin.
|
|
151
|
+
*/
|
|
152
|
+
export function createProxyServer() {
|
|
153
|
+
return http.createServer((req, res) => {
|
|
154
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
155
|
+
return handleHealth(req, res);
|
|
156
|
+
}
|
|
157
|
+
if (req.method === "POST" && req.url?.startsWith("/v1/messages")) {
|
|
158
|
+
return handleMessages(req, res);
|
|
159
|
+
}
|
|
160
|
+
handleNotFound(req, res);
|
|
161
|
+
});
|
|
154
162
|
}
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Builds the server, loads the extension pipeline, optionally starts the
|
|
166
|
+
* extensions-config file watcher, and starts listening. Returns a handle
|
|
167
|
+
* with the bound port (resolved when port 0 is requested) and a `close`
|
|
168
|
+
* function for graceful shutdown.
|
|
169
|
+
*
|
|
170
|
+
* All options fall back to the same env vars / defaults used by the CLI
|
|
171
|
+
* entrypoint, so existing deployments behave identically.
|
|
172
|
+
*
|
|
173
|
+
* await startProxy() // env-driven, CLI parity
|
|
174
|
+
* await startProxy({ port: 0 }) // OS-assigned port
|
|
175
|
+
* await startProxy({ port: 0, watch: false }) // embedded, no fs.watch
|
|
176
|
+
*/
|
|
177
|
+
export async function startProxy(options = {}) {
|
|
178
|
+
const port = options.port ?? config.port;
|
|
179
|
+
const bind = options.bind ?? config.bind;
|
|
180
|
+
const extensionsDir = options.extensionsDir ?? config.extensionsDir;
|
|
181
|
+
const extensionsConfig = options.extensionsConfig ?? config.extensionsConfig;
|
|
182
|
+
const watch = options.watch !== false;
|
|
183
|
+
|
|
184
|
+
let watcher = null;
|
|
160
185
|
try {
|
|
161
|
-
await loadExtensions(
|
|
162
|
-
startWatcher(
|
|
186
|
+
await loadExtensions(extensionsDir, extensionsConfig);
|
|
187
|
+
if (watch) watcher = startWatcher(extensionsDir, extensionsConfig);
|
|
163
188
|
} catch {}
|
|
164
|
-
}
|
|
165
189
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
190
|
+
const server = createProxyServer();
|
|
191
|
+
await new Promise((resolve, reject) => {
|
|
192
|
+
server.once("error", reject);
|
|
193
|
+
server.listen(port, bind, () => {
|
|
194
|
+
server.off("error", reject);
|
|
195
|
+
resolve();
|
|
196
|
+
});
|
|
170
197
|
});
|
|
171
|
-
});
|
|
172
198
|
|
|
173
|
-
|
|
199
|
+
const addr = server.address();
|
|
200
|
+
return {
|
|
201
|
+
server,
|
|
202
|
+
port: addr.port,
|
|
203
|
+
address: addr.address,
|
|
204
|
+
close: () =>
|
|
205
|
+
new Promise((resolve, reject) => {
|
|
206
|
+
try {
|
|
207
|
+
if (watcher) watcher.close();
|
|
208
|
+
} catch {}
|
|
209
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// CLI entrypoint — preserves the v3.x behavior of `node proxy/server.mjs`
|
|
215
|
+
// (used by `cache-fix-proxy server` and by `fork(SERVER_PATH)` in the
|
|
216
|
+
// wrapper). When this module is imported as a library, none of this runs.
|
|
217
|
+
const invokedAsScript =
|
|
218
|
+
typeof process !== "undefined" &&
|
|
219
|
+
process.argv[1] &&
|
|
220
|
+
import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
221
|
+
|
|
222
|
+
if (invokedAsScript) {
|
|
223
|
+
let active;
|
|
224
|
+
startProxy()
|
|
225
|
+
.then((handle) => {
|
|
226
|
+
active = handle;
|
|
227
|
+
process.stdout.write(`proxy listening on ${handle.address}:${handle.port}\n`);
|
|
228
|
+
})
|
|
229
|
+
.catch((err) => {
|
|
230
|
+
process.stderr.write(`proxy failed to start: ${err.message}\n`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const shutdown = () => {
|
|
235
|
+
if (!active) {
|
|
236
|
+
process.exit(0);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
active.close().finally(() => process.exit(0));
|
|
240
|
+
setTimeout(() => process.exit(1), 5000).unref();
|
|
241
|
+
};
|
|
242
|
+
process.on("SIGTERM", shutdown);
|
|
243
|
+
process.on("SIGINT", shutdown);
|
|
244
|
+
}
|