claude-code-cache-fix 3.0.2 → 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.ko.md +186 -174
- package/README.md +38 -1
- package/README.zh.md +153 -195
- package/package.json +4 -1
- package/proxy/config.mjs +15 -0
- package/proxy/extensions/cache-telemetry.mjs +89 -1
- package/proxy/upstream.mjs +102 -0
package/proxy/upstream.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import https from "node:https";
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import { URL } from "node:url";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
|
|
4
6
|
import config from "./config.mjs";
|
|
5
7
|
|
|
6
8
|
const STRIP_REQUEST_HEADERS = new Set([
|
|
@@ -46,6 +48,105 @@ function filterResponseHeaders(rawHeaders) {
|
|
|
46
48
|
return headers;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// --- HTTP proxy and custom CA support ---
|
|
52
|
+
|
|
53
|
+
const _agents = new Map(); // cache key → Agent | null
|
|
54
|
+
const _loggedProxies = new Set(); // dedupe stderr "using proxy" lines per (url, isHTTPS)
|
|
55
|
+
let _warnedTlsDisabled = false;
|
|
56
|
+
|
|
57
|
+
function shouldBypassProxy(hostname) {
|
|
58
|
+
if (!config.noProxy) return false;
|
|
59
|
+
const list = config.noProxy.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
60
|
+
const host = hostname.toLowerCase();
|
|
61
|
+
for (const pattern of list) {
|
|
62
|
+
if (pattern === "*") return true;
|
|
63
|
+
if (pattern.startsWith(".")) {
|
|
64
|
+
// ".example.com" matches "foo.example.com" and "example.com"
|
|
65
|
+
const bare = pattern.slice(1);
|
|
66
|
+
if (host === bare || host.endsWith(pattern)) return true;
|
|
67
|
+
} else if (host === pattern || host.endsWith("." + pattern)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function loadCa() {
|
|
75
|
+
if (!config.caFile) return undefined;
|
|
76
|
+
try {
|
|
77
|
+
return readFileSync(config.caFile);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
process.stderr.write(`[upstream] CACHE_FIX_PROXY_CA_FILE read failed: ${err.message}\n`);
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Pick the proxy URL for an upstream, matching curl/Python/Go semantics:
|
|
85
|
+
// https upstream → HTTPS_PROXY, falling back to HTTP_PROXY if unset
|
|
86
|
+
// http upstream → HTTP_PROXY only (HTTPS_PROXY does NOT apply to plain HTTP)
|
|
87
|
+
//
|
|
88
|
+
// Exported for direct unit testing — tests against the live forwardRequest path
|
|
89
|
+
// can't easily reload a fresh config across cases (config is a single module
|
|
90
|
+
// instance), so we expose the pure function for table-driven coverage.
|
|
91
|
+
export function selectProxyUrl(isHTTPS) {
|
|
92
|
+
if (isHTTPS) return config.httpsProxy || config.httpProxy || "";
|
|
93
|
+
return config.httpProxy || "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildAgent(isHTTPS, proxyUrl) {
|
|
97
|
+
const ca = loadCa();
|
|
98
|
+
if (proxyUrl) {
|
|
99
|
+
const opts = {
|
|
100
|
+
keepAlive: true,
|
|
101
|
+
proxy: proxyUrl,
|
|
102
|
+
rejectUnauthorized: config.rejectUnauthorized,
|
|
103
|
+
...(ca ? { ca } : {}),
|
|
104
|
+
};
|
|
105
|
+
return isHTTPS ? new HttpsProxyAgent(opts) : new HttpProxyAgent(opts);
|
|
106
|
+
}
|
|
107
|
+
// No proxy. Only build a custom agent when CA or insecure mode warrants it;
|
|
108
|
+
// otherwise return null so Node uses its global default agent (preserves the
|
|
109
|
+
// pre-change behavior end-to-end, including connection pooling).
|
|
110
|
+
if (ca || !config.rejectUnauthorized) {
|
|
111
|
+
if (isHTTPS) {
|
|
112
|
+
return new https.Agent({
|
|
113
|
+
keepAlive: true,
|
|
114
|
+
rejectUnauthorized: config.rejectUnauthorized,
|
|
115
|
+
...(ca ? { ca } : {}),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return new http.Agent({ keepAlive: true });
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getAgent(isHTTPS, hostname) {
|
|
124
|
+
if (!_warnedTlsDisabled && !config.rejectUnauthorized) {
|
|
125
|
+
_warnedTlsDisabled = true;
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
`[upstream] WARNING: TLS verification disabled (CACHE_FIX_PROXY_REJECT_UNAUTHORIZED=0). This is insecure!\n`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const bypass = shouldBypassProxy(hostname);
|
|
132
|
+
const proxyUrl = bypass ? "" : selectProxyUrl(isHTTPS);
|
|
133
|
+
const cacheKey = `${isHTTPS ? "https" : "http"}|${proxyUrl}|${config.caFile}|${config.rejectUnauthorized}`;
|
|
134
|
+
|
|
135
|
+
let agent = _agents.get(cacheKey);
|
|
136
|
+
if (agent === undefined) {
|
|
137
|
+
agent = buildAgent(isHTTPS, proxyUrl);
|
|
138
|
+
_agents.set(cacheKey, agent);
|
|
139
|
+
if (proxyUrl && !_loggedProxies.has(`${proxyUrl}|${isHTTPS}`)) {
|
|
140
|
+
_loggedProxies.add(`${proxyUrl}|${isHTTPS}`);
|
|
141
|
+
process.stderr.write(
|
|
142
|
+
`[upstream] using proxy ${proxyUrl} for ${isHTTPS ? "https" : "http"} upstream ` +
|
|
143
|
+
`(rejectUnauthorized=${config.rejectUnauthorized}, ca=${config.caFile || "default"})\n`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return agent;
|
|
148
|
+
}
|
|
149
|
+
|
|
49
150
|
export function forwardRequest(clientReq, body, signal) {
|
|
50
151
|
return new Promise((resolve, reject) => {
|
|
51
152
|
const upstreamUrl = new URL(clientReq.url, config.upstream);
|
|
@@ -66,6 +167,7 @@ export function forwardRequest(clientReq, body, signal) {
|
|
|
66
167
|
method: clientReq.method,
|
|
67
168
|
headers,
|
|
68
169
|
timeout: config.timeout,
|
|
170
|
+
agent: getAgent(isHTTPS, upstreamUrl.hostname),
|
|
69
171
|
};
|
|
70
172
|
|
|
71
173
|
const upstreamReq = transport.request(options, (upstreamRes) => {
|