@relayplane/proxy 0.1.6 → 0.1.7
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/cli.js +118 -20
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +118 -20
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +118 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +118 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
53
53
|
|
|
54
54
|
// src/proxy.ts
|
|
55
55
|
var http = __toESM(require("http"));
|
|
56
|
+
var url = __toESM(require("url"));
|
|
56
57
|
|
|
57
58
|
// src/storage/store.ts
|
|
58
59
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
@@ -1623,6 +1624,11 @@ ${input.prompt}` : input.prompt;
|
|
|
1623
1624
|
};
|
|
1624
1625
|
|
|
1625
1626
|
// src/proxy.ts
|
|
1627
|
+
var VERSION = "0.1.7";
|
|
1628
|
+
var recentRuns = [];
|
|
1629
|
+
var MAX_RECENT_RUNS = 100;
|
|
1630
|
+
var modelCounts = {};
|
|
1631
|
+
var serverStartTime = 0;
|
|
1626
1632
|
var DEFAULT_ENDPOINTS = {
|
|
1627
1633
|
anthropic: {
|
|
1628
1634
|
baseUrl: "https://api.anthropic.com/v1",
|
|
@@ -1919,9 +1925,9 @@ function convertMessagesToGemini(messages) {
|
|
|
1919
1925
|
return { text: p.text };
|
|
1920
1926
|
}
|
|
1921
1927
|
if (p.type === "image_url" && p.image_url?.url) {
|
|
1922
|
-
const
|
|
1923
|
-
if (
|
|
1924
|
-
const match =
|
|
1928
|
+
const url2 = p.image_url.url;
|
|
1929
|
+
if (url2.startsWith("data:")) {
|
|
1930
|
+
const match = url2.match(/^data:([^;]+);base64,(.+)$/);
|
|
1925
1931
|
if (match) {
|
|
1926
1932
|
return {
|
|
1927
1933
|
inline_data: {
|
|
@@ -1931,7 +1937,7 @@ function convertMessagesToGemini(messages) {
|
|
|
1931
1937
|
};
|
|
1932
1938
|
}
|
|
1933
1939
|
}
|
|
1934
|
-
return { text: `[Image: ${
|
|
1940
|
+
return { text: `[Image: ${url2}]` };
|
|
1935
1941
|
}
|
|
1936
1942
|
return { text: "" };
|
|
1937
1943
|
});
|
|
@@ -2345,28 +2351,88 @@ async function startProxy(config = {}) {
|
|
|
2345
2351
|
};
|
|
2346
2352
|
const server = http.createServer(async (req, res) => {
|
|
2347
2353
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2348
|
-
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
2354
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
2349
2355
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
2350
2356
|
if (req.method === "OPTIONS") {
|
|
2351
2357
|
res.writeHead(204);
|
|
2352
2358
|
res.end();
|
|
2353
2359
|
return;
|
|
2354
2360
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
})
|
|
2367
|
-
);
|
|
2368
|
-
return;
|
|
2361
|
+
const parsedUrl = url.parse(req.url || "", true);
|
|
2362
|
+
const pathname = parsedUrl.pathname || "";
|
|
2363
|
+
if (req.method === "GET" && pathname === "/health") {
|
|
2364
|
+
const uptimeMs = Date.now() - serverStartTime;
|
|
2365
|
+
const uptimeSecs = Math.floor(uptimeMs / 1e3);
|
|
2366
|
+
const hours = Math.floor(uptimeSecs / 3600);
|
|
2367
|
+
const mins = Math.floor(uptimeSecs % 3600 / 60);
|
|
2368
|
+
const secs = uptimeSecs % 60;
|
|
2369
|
+
const providers = {};
|
|
2370
|
+
for (const [name, config2] of Object.entries(DEFAULT_ENDPOINTS)) {
|
|
2371
|
+
providers[name] = !!process.env[config2.apiKeyEnv];
|
|
2369
2372
|
}
|
|
2373
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2374
|
+
res.end(JSON.stringify({
|
|
2375
|
+
status: "ok",
|
|
2376
|
+
version: VERSION,
|
|
2377
|
+
uptime: `${hours}h ${mins}m ${secs}s`,
|
|
2378
|
+
uptimeMs,
|
|
2379
|
+
providers,
|
|
2380
|
+
totalRuns: recentRuns.length > 0 ? Object.values(modelCounts).reduce((a, b) => a + b, 0) : 0
|
|
2381
|
+
}));
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
if (req.method === "GET" && pathname === "/stats") {
|
|
2385
|
+
const stats = relay.stats();
|
|
2386
|
+
const savings = relay.savingsReport(30);
|
|
2387
|
+
const totalRuns = Object.values(modelCounts).reduce((a, b) => a + b, 0);
|
|
2388
|
+
const modelDistribution = {};
|
|
2389
|
+
for (const [model, count] of Object.entries(modelCounts)) {
|
|
2390
|
+
modelDistribution[model] = {
|
|
2391
|
+
count,
|
|
2392
|
+
percentage: totalRuns > 0 ? (count / totalRuns * 100).toFixed(1) + "%" : "0%"
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2396
|
+
res.end(JSON.stringify({
|
|
2397
|
+
totalRuns,
|
|
2398
|
+
savings: {
|
|
2399
|
+
estimatedSavingsPercent: savings.savingsPercent.toFixed(1) + "%",
|
|
2400
|
+
actualCostUsd: savings.actualCost.toFixed(4),
|
|
2401
|
+
baselineCostUsd: savings.baselineCost.toFixed(4),
|
|
2402
|
+
savedUsd: savings.savings.toFixed(4)
|
|
2403
|
+
},
|
|
2404
|
+
modelDistribution,
|
|
2405
|
+
byTaskType: stats.byTaskType,
|
|
2406
|
+
period: stats.period
|
|
2407
|
+
}));
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
if (req.method === "GET" && pathname === "/runs") {
|
|
2411
|
+
const limitParam = parsedUrl.query["limit"];
|
|
2412
|
+
const parsedLimit = limitParam ? parseInt(String(limitParam), 10) : 20;
|
|
2413
|
+
const limit = Math.min(Number.isNaN(parsedLimit) ? 20 : parsedLimit, MAX_RECENT_RUNS);
|
|
2414
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2415
|
+
res.end(JSON.stringify({
|
|
2416
|
+
runs: recentRuns.slice(0, limit),
|
|
2417
|
+
total: recentRuns.length
|
|
2418
|
+
}));
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
if (req.method === "GET" && pathname.includes("/models")) {
|
|
2422
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2423
|
+
res.end(
|
|
2424
|
+
JSON.stringify({
|
|
2425
|
+
object: "list",
|
|
2426
|
+
data: [
|
|
2427
|
+
{ id: "relayplane:auto", object: "model", owned_by: "relayplane" },
|
|
2428
|
+
{ id: "relayplane:cost", object: "model", owned_by: "relayplane" },
|
|
2429
|
+
{ id: "relayplane:quality", object: "model", owned_by: "relayplane" }
|
|
2430
|
+
]
|
|
2431
|
+
})
|
|
2432
|
+
);
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (req.method !== "POST" || !pathname.includes("/chat/completions")) {
|
|
2370
2436
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2371
2437
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
2372
2438
|
return;
|
|
@@ -2489,9 +2555,11 @@ async function startProxy(config = {}) {
|
|
|
2489
2555
|
return new Promise((resolve, reject) => {
|
|
2490
2556
|
server.on("error", reject);
|
|
2491
2557
|
server.listen(port, host, () => {
|
|
2558
|
+
serverStartTime = Date.now();
|
|
2492
2559
|
console.log(`RelayPlane proxy listening on http://${host}:${port}`);
|
|
2493
2560
|
console.log(` Models: relayplane:auto, relayplane:cost, relayplane:quality`);
|
|
2494
2561
|
console.log(` Endpoint: POST /v1/chat/completions`);
|
|
2562
|
+
console.log(` Stats: GET /stats, /runs, /health`);
|
|
2495
2563
|
console.log(` Streaming: \u2705 Enabled`);
|
|
2496
2564
|
resolve(server);
|
|
2497
2565
|
});
|
|
@@ -2554,11 +2622,26 @@ async function handleStreamingRequest(res, request, targetProvider, targetModel,
|
|
|
2554
2622
|
log(`Streaming error: ${err}`);
|
|
2555
2623
|
}
|
|
2556
2624
|
const durationMs = Date.now() - startTime;
|
|
2625
|
+
const modelKey = `${targetProvider}/${targetModel}`;
|
|
2626
|
+
modelCounts[modelKey] = (modelCounts[modelKey] || 0) + 1;
|
|
2557
2627
|
relay.run({
|
|
2558
2628
|
prompt: promptText.slice(0, 500),
|
|
2559
2629
|
taskType,
|
|
2560
2630
|
model: `${targetProvider}:${targetModel}`
|
|
2561
2631
|
}).then((runResult) => {
|
|
2632
|
+
recentRuns.unshift({
|
|
2633
|
+
runId: runResult.runId,
|
|
2634
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2635
|
+
model: modelKey,
|
|
2636
|
+
taskType,
|
|
2637
|
+
confidence,
|
|
2638
|
+
mode: routingMode,
|
|
2639
|
+
durationMs,
|
|
2640
|
+
promptPreview: promptText.slice(0, 100) + (promptText.length > 100 ? "..." : "")
|
|
2641
|
+
});
|
|
2642
|
+
if (recentRuns.length > MAX_RECENT_RUNS) {
|
|
2643
|
+
recentRuns.pop();
|
|
2644
|
+
}
|
|
2562
2645
|
log(`Completed streaming in ${durationMs}ms, runId: ${runResult.runId}`);
|
|
2563
2646
|
}).catch((err) => {
|
|
2564
2647
|
log(`Failed to record run: ${err}`);
|
|
@@ -2629,15 +2712,30 @@ async function handleNonStreamingRequest(res, request, targetProvider, targetMod
|
|
|
2629
2712
|
return;
|
|
2630
2713
|
}
|
|
2631
2714
|
const durationMs = Date.now() - startTime;
|
|
2715
|
+
const modelKey = `${targetProvider}/${targetModel}`;
|
|
2716
|
+
modelCounts[modelKey] = (modelCounts[modelKey] || 0) + 1;
|
|
2632
2717
|
try {
|
|
2633
2718
|
const runResult = await relay.run({
|
|
2634
2719
|
prompt: promptText.slice(0, 500),
|
|
2635
2720
|
taskType,
|
|
2636
2721
|
model: `${targetProvider}:${targetModel}`
|
|
2637
2722
|
});
|
|
2723
|
+
recentRuns.unshift({
|
|
2724
|
+
runId: runResult.runId,
|
|
2725
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2726
|
+
model: modelKey,
|
|
2727
|
+
taskType,
|
|
2728
|
+
confidence,
|
|
2729
|
+
mode: routingMode,
|
|
2730
|
+
durationMs,
|
|
2731
|
+
promptPreview: promptText.slice(0, 100) + (promptText.length > 100 ? "..." : "")
|
|
2732
|
+
});
|
|
2733
|
+
if (recentRuns.length > MAX_RECENT_RUNS) {
|
|
2734
|
+
recentRuns.pop();
|
|
2735
|
+
}
|
|
2638
2736
|
responseData["_relayplane"] = {
|
|
2639
2737
|
runId: runResult.runId,
|
|
2640
|
-
routedTo:
|
|
2738
|
+
routedTo: modelKey,
|
|
2641
2739
|
taskType,
|
|
2642
2740
|
confidence,
|
|
2643
2741
|
durationMs,
|