@relayplane/proxy 1.8.10 → 1.8.11
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/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -4
- package/dist/config.js.map +1 -1
- package/dist/estimate.d.ts +97 -0
- package/dist/estimate.d.ts.map +1 -0
- package/dist/estimate.js +257 -0
- package/dist/estimate.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/osmosis-store.d.ts +33 -0
- package/dist/osmosis-store.d.ts.map +1 -0
- package/dist/osmosis-store.js +181 -0
- package/dist/osmosis-store.js.map +1 -0
- package/dist/recovery-mesh-server.d.ts +49 -0
- package/dist/recovery-mesh-server.d.ts.map +1 -0
- package/dist/recovery-mesh-server.js +333 -0
- package/dist/recovery-mesh-server.js.map +1 -0
- package/dist/recovery-mesh.d.ts +207 -0
- package/dist/recovery-mesh.d.ts.map +1 -0
- package/dist/recovery-mesh.js +426 -0
- package/dist/recovery-mesh.js.map +1 -0
- package/dist/recovery.d.ts +262 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +570 -0
- package/dist/recovery.js.map +1 -0
- package/dist/standalone-proxy.d.ts.map +1 -1
- package/dist/standalone-proxy.js +40 -1
- package/dist/standalone-proxy.js.map +1 -1
- package/dist/telemetry.d.ts +8 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +3 -2
- package/dist/telemetry.js.map +1 -1
- package/package.json +3 -2
package/dist/standalone-proxy.js
CHANGED
|
@@ -85,6 +85,12 @@ const agent_tracker_js_1 = require("./agent-tracker.js");
|
|
|
85
85
|
const version_status_js_1 = require("./utils/version-status.js");
|
|
86
86
|
const signup_nudge_js_1 = require("./signup-nudge.js");
|
|
87
87
|
const star_nudge_js_1 = require("./star-nudge.js");
|
|
88
|
+
const estimate_js_1 = require("./estimate.js");
|
|
89
|
+
// Per-IP rate limit state for /v1/estimate (60 req/min per IP)
|
|
90
|
+
const estimateRateMap = new Map();
|
|
91
|
+
// Fix A: Purge expired rate-limit entries every 5 minutes to prevent memory leak.
|
|
92
|
+
// Without this, IPs that make one request and disappear stay in the map forever.
|
|
93
|
+
setInterval(() => (0, estimate_js_1.purgeExpiredRateLimitEntries)(estimateRateMap, Date.now()), 5 * 60 * 1000);
|
|
88
94
|
const PROXY_VERSION = (() => {
|
|
89
95
|
try {
|
|
90
96
|
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
@@ -3899,6 +3905,39 @@ async function startProxy(config = {}) {
|
|
|
3899
3905
|
}
|
|
3900
3906
|
return;
|
|
3901
3907
|
}
|
|
3908
|
+
// === Pre-flight cost estimation endpoint (Pro-tier) ===
|
|
3909
|
+
if (req.method === 'POST' && (url === '/v1/estimate' || url.endsWith('/v1/estimate'))) {
|
|
3910
|
+
log('Pre-flight estimate request');
|
|
3911
|
+
// --- Per-IP rate limit: 60 requests/minute ---
|
|
3912
|
+
// Fix B: Use only the raw socket address — never x-forwarded-for.
|
|
3913
|
+
// x-forwarded-for is a client-controlled header and is trivially spoofed;
|
|
3914
|
+
// any attacker can send "X-Forwarded-For: 1.2.3.4" to bypass per-IP limits.
|
|
3915
|
+
// The socket remoteAddress reflects the actual TCP connection and cannot be faked.
|
|
3916
|
+
const clientIp = req.socket?.remoteAddress ?? 'unknown';
|
|
3917
|
+
const now = Date.now();
|
|
3918
|
+
// Fix C: Delegate rate limit logic to the testable checkEstimateRateLimit() function
|
|
3919
|
+
// (extracted in estimate.ts so it can be unit-tested in isolation).
|
|
3920
|
+
const rateLimitResult = (0, estimate_js_1.checkEstimateRateLimit)(estimateRateMap, clientIp, now);
|
|
3921
|
+
if (!rateLimitResult.allowed) {
|
|
3922
|
+
res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': '60' });
|
|
3923
|
+
res.end(JSON.stringify({ error: 'rate_limit_exceeded', message: 'Too many estimate requests. Limit: 60/minute.' }));
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
// --- Read body with size limit (uses existing MAX_BODY_SIZE helper) ---
|
|
3927
|
+
let body;
|
|
3928
|
+
try {
|
|
3929
|
+
body = await readRequestBody(req);
|
|
3930
|
+
}
|
|
3931
|
+
catch (err) {
|
|
3932
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
3933
|
+
res.end(JSON.stringify({ error: 'payload_too_large', message: 'Request body too large (max 10MB)' }));
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
const result = (0, estimate_js_1.handleEstimateRequest)(body);
|
|
3937
|
+
res.writeHead(result.status, { 'Content-Type': 'application/json' });
|
|
3938
|
+
res.end(JSON.stringify(result.body));
|
|
3939
|
+
return;
|
|
3940
|
+
}
|
|
3902
3941
|
// === Token counting endpoint ===
|
|
3903
3942
|
if (req.method === 'POST' && url.includes('/v1/messages/count_tokens')) {
|
|
3904
3943
|
log('Token count request');
|
|
@@ -3946,7 +3985,7 @@ async function startProxy(config = {}) {
|
|
|
3946
3985
|
// === OpenAI-compatible /v1/chat/completions endpoint ===
|
|
3947
3986
|
if (req.method !== 'POST' || !url.includes('/chat/completions')) {
|
|
3948
3987
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
3949
|
-
res.end(JSON.stringify({ error: 'Not found. Supported: POST /v1/messages, POST /v1/chat/completions, GET /v1/models' }));
|
|
3988
|
+
res.end(JSON.stringify({ error: 'Not found. Supported: POST /v1/messages, POST /v1/chat/completions, POST /v1/estimate, GET /v1/models' }));
|
|
3950
3989
|
return;
|
|
3951
3990
|
}
|
|
3952
3991
|
// Parse request body
|