@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/cli.js
CHANGED
|
@@ -25,6 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/proxy.ts
|
|
27
27
|
var http = __toESM(require("http"));
|
|
28
|
+
var url = __toESM(require("url"));
|
|
28
29
|
|
|
29
30
|
// src/storage/store.ts
|
|
30
31
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
@@ -1591,6 +1592,11 @@ ${input.prompt}` : input.prompt;
|
|
|
1591
1592
|
};
|
|
1592
1593
|
|
|
1593
1594
|
// src/proxy.ts
|
|
1595
|
+
var VERSION = "0.1.7";
|
|
1596
|
+
var recentRuns = [];
|
|
1597
|
+
var MAX_RECENT_RUNS = 100;
|
|
1598
|
+
var modelCounts = {};
|
|
1599
|
+
var serverStartTime = 0;
|
|
1594
1600
|
var DEFAULT_ENDPOINTS = {
|
|
1595
1601
|
anthropic: {
|
|
1596
1602
|
baseUrl: "https://api.anthropic.com/v1",
|
|
@@ -1887,9 +1893,9 @@ function convertMessagesToGemini(messages) {
|
|
|
1887
1893
|
return { text: p.text };
|
|
1888
1894
|
}
|
|
1889
1895
|
if (p.type === "image_url" && p.image_url?.url) {
|
|
1890
|
-
const
|
|
1891
|
-
if (
|
|
1892
|
-
const match =
|
|
1896
|
+
const url2 = p.image_url.url;
|
|
1897
|
+
if (url2.startsWith("data:")) {
|
|
1898
|
+
const match = url2.match(/^data:([^;]+);base64,(.+)$/);
|
|
1893
1899
|
if (match) {
|
|
1894
1900
|
return {
|
|
1895
1901
|
inline_data: {
|
|
@@ -1899,7 +1905,7 @@ function convertMessagesToGemini(messages) {
|
|
|
1899
1905
|
};
|
|
1900
1906
|
}
|
|
1901
1907
|
}
|
|
1902
|
-
return { text: `[Image: ${
|
|
1908
|
+
return { text: `[Image: ${url2}]` };
|
|
1903
1909
|
}
|
|
1904
1910
|
return { text: "" };
|
|
1905
1911
|
});
|
|
@@ -2313,28 +2319,88 @@ async function startProxy(config = {}) {
|
|
|
2313
2319
|
};
|
|
2314
2320
|
const server = http.createServer(async (req, res) => {
|
|
2315
2321
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2316
|
-
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
2322
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
2317
2323
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
2318
2324
|
if (req.method === "OPTIONS") {
|
|
2319
2325
|
res.writeHead(204);
|
|
2320
2326
|
res.end();
|
|
2321
2327
|
return;
|
|
2322
2328
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
})
|
|
2335
|
-
);
|
|
2336
|
-
return;
|
|
2329
|
+
const parsedUrl = url.parse(req.url || "", true);
|
|
2330
|
+
const pathname = parsedUrl.pathname || "";
|
|
2331
|
+
if (req.method === "GET" && pathname === "/health") {
|
|
2332
|
+
const uptimeMs = Date.now() - serverStartTime;
|
|
2333
|
+
const uptimeSecs = Math.floor(uptimeMs / 1e3);
|
|
2334
|
+
const hours = Math.floor(uptimeSecs / 3600);
|
|
2335
|
+
const mins = Math.floor(uptimeSecs % 3600 / 60);
|
|
2336
|
+
const secs = uptimeSecs % 60;
|
|
2337
|
+
const providers = {};
|
|
2338
|
+
for (const [name, config2] of Object.entries(DEFAULT_ENDPOINTS)) {
|
|
2339
|
+
providers[name] = !!process.env[config2.apiKeyEnv];
|
|
2337
2340
|
}
|
|
2341
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2342
|
+
res.end(JSON.stringify({
|
|
2343
|
+
status: "ok",
|
|
2344
|
+
version: VERSION,
|
|
2345
|
+
uptime: `${hours}h ${mins}m ${secs}s`,
|
|
2346
|
+
uptimeMs,
|
|
2347
|
+
providers,
|
|
2348
|
+
totalRuns: recentRuns.length > 0 ? Object.values(modelCounts).reduce((a, b) => a + b, 0) : 0
|
|
2349
|
+
}));
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
if (req.method === "GET" && pathname === "/stats") {
|
|
2353
|
+
const stats = relay.stats();
|
|
2354
|
+
const savings = relay.savingsReport(30);
|
|
2355
|
+
const totalRuns = Object.values(modelCounts).reduce((a, b) => a + b, 0);
|
|
2356
|
+
const modelDistribution = {};
|
|
2357
|
+
for (const [model, count] of Object.entries(modelCounts)) {
|
|
2358
|
+
modelDistribution[model] = {
|
|
2359
|
+
count,
|
|
2360
|
+
percentage: totalRuns > 0 ? (count / totalRuns * 100).toFixed(1) + "%" : "0%"
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2364
|
+
res.end(JSON.stringify({
|
|
2365
|
+
totalRuns,
|
|
2366
|
+
savings: {
|
|
2367
|
+
estimatedSavingsPercent: savings.savingsPercent.toFixed(1) + "%",
|
|
2368
|
+
actualCostUsd: savings.actualCost.toFixed(4),
|
|
2369
|
+
baselineCostUsd: savings.baselineCost.toFixed(4),
|
|
2370
|
+
savedUsd: savings.savings.toFixed(4)
|
|
2371
|
+
},
|
|
2372
|
+
modelDistribution,
|
|
2373
|
+
byTaskType: stats.byTaskType,
|
|
2374
|
+
period: stats.period
|
|
2375
|
+
}));
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
if (req.method === "GET" && pathname === "/runs") {
|
|
2379
|
+
const limitParam = parsedUrl.query["limit"];
|
|
2380
|
+
const parsedLimit = limitParam ? parseInt(String(limitParam), 10) : 20;
|
|
2381
|
+
const limit = Math.min(Number.isNaN(parsedLimit) ? 20 : parsedLimit, MAX_RECENT_RUNS);
|
|
2382
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2383
|
+
res.end(JSON.stringify({
|
|
2384
|
+
runs: recentRuns.slice(0, limit),
|
|
2385
|
+
total: recentRuns.length
|
|
2386
|
+
}));
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
if (req.method === "GET" && pathname.includes("/models")) {
|
|
2390
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2391
|
+
res.end(
|
|
2392
|
+
JSON.stringify({
|
|
2393
|
+
object: "list",
|
|
2394
|
+
data: [
|
|
2395
|
+
{ id: "relayplane:auto", object: "model", owned_by: "relayplane" },
|
|
2396
|
+
{ id: "relayplane:cost", object: "model", owned_by: "relayplane" },
|
|
2397
|
+
{ id: "relayplane:quality", object: "model", owned_by: "relayplane" }
|
|
2398
|
+
]
|
|
2399
|
+
})
|
|
2400
|
+
);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
if (req.method !== "POST" || !pathname.includes("/chat/completions")) {
|
|
2338
2404
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2339
2405
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
2340
2406
|
return;
|
|
@@ -2457,9 +2523,11 @@ async function startProxy(config = {}) {
|
|
|
2457
2523
|
return new Promise((resolve, reject) => {
|
|
2458
2524
|
server.on("error", reject);
|
|
2459
2525
|
server.listen(port, host, () => {
|
|
2526
|
+
serverStartTime = Date.now();
|
|
2460
2527
|
console.log(`RelayPlane proxy listening on http://${host}:${port}`);
|
|
2461
2528
|
console.log(` Models: relayplane:auto, relayplane:cost, relayplane:quality`);
|
|
2462
2529
|
console.log(` Endpoint: POST /v1/chat/completions`);
|
|
2530
|
+
console.log(` Stats: GET /stats, /runs, /health`);
|
|
2463
2531
|
console.log(` Streaming: \u2705 Enabled`);
|
|
2464
2532
|
resolve(server);
|
|
2465
2533
|
});
|
|
@@ -2522,11 +2590,26 @@ async function handleStreamingRequest(res, request, targetProvider, targetModel,
|
|
|
2522
2590
|
log(`Streaming error: ${err}`);
|
|
2523
2591
|
}
|
|
2524
2592
|
const durationMs = Date.now() - startTime;
|
|
2593
|
+
const modelKey = `${targetProvider}/${targetModel}`;
|
|
2594
|
+
modelCounts[modelKey] = (modelCounts[modelKey] || 0) + 1;
|
|
2525
2595
|
relay.run({
|
|
2526
2596
|
prompt: promptText.slice(0, 500),
|
|
2527
2597
|
taskType,
|
|
2528
2598
|
model: `${targetProvider}:${targetModel}`
|
|
2529
2599
|
}).then((runResult) => {
|
|
2600
|
+
recentRuns.unshift({
|
|
2601
|
+
runId: runResult.runId,
|
|
2602
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2603
|
+
model: modelKey,
|
|
2604
|
+
taskType,
|
|
2605
|
+
confidence,
|
|
2606
|
+
mode: routingMode,
|
|
2607
|
+
durationMs,
|
|
2608
|
+
promptPreview: promptText.slice(0, 100) + (promptText.length > 100 ? "..." : "")
|
|
2609
|
+
});
|
|
2610
|
+
if (recentRuns.length > MAX_RECENT_RUNS) {
|
|
2611
|
+
recentRuns.pop();
|
|
2612
|
+
}
|
|
2530
2613
|
log(`Completed streaming in ${durationMs}ms, runId: ${runResult.runId}`);
|
|
2531
2614
|
}).catch((err) => {
|
|
2532
2615
|
log(`Failed to record run: ${err}`);
|
|
@@ -2597,15 +2680,30 @@ async function handleNonStreamingRequest(res, request, targetProvider, targetMod
|
|
|
2597
2680
|
return;
|
|
2598
2681
|
}
|
|
2599
2682
|
const durationMs = Date.now() - startTime;
|
|
2683
|
+
const modelKey = `${targetProvider}/${targetModel}`;
|
|
2684
|
+
modelCounts[modelKey] = (modelCounts[modelKey] || 0) + 1;
|
|
2600
2685
|
try {
|
|
2601
2686
|
const runResult = await relay.run({
|
|
2602
2687
|
prompt: promptText.slice(0, 500),
|
|
2603
2688
|
taskType,
|
|
2604
2689
|
model: `${targetProvider}:${targetModel}`
|
|
2605
2690
|
});
|
|
2691
|
+
recentRuns.unshift({
|
|
2692
|
+
runId: runResult.runId,
|
|
2693
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2694
|
+
model: modelKey,
|
|
2695
|
+
taskType,
|
|
2696
|
+
confidence,
|
|
2697
|
+
mode: routingMode,
|
|
2698
|
+
durationMs,
|
|
2699
|
+
promptPreview: promptText.slice(0, 100) + (promptText.length > 100 ? "..." : "")
|
|
2700
|
+
});
|
|
2701
|
+
if (recentRuns.length > MAX_RECENT_RUNS) {
|
|
2702
|
+
recentRuns.pop();
|
|
2703
|
+
}
|
|
2606
2704
|
responseData["_relayplane"] = {
|
|
2607
2705
|
runId: runResult.runId,
|
|
2608
|
-
routedTo:
|
|
2706
|
+
routedTo: modelKey,
|
|
2609
2707
|
taskType,
|
|
2610
2708
|
confidence,
|
|
2611
2709
|
durationMs,
|