@relayplane/proxy 0.1.4 → 0.1.5
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 +19 -0
- package/dist/cli.js +99 -7
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +99 -7
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2592,18 +2592,28 @@ async function handleNonStreamingRequest(res, request, targetProvider, targetMod
|
|
|
2592
2592
|
}
|
|
2593
2593
|
|
|
2594
2594
|
// src/cli.ts
|
|
2595
|
+
import Database2 from "better-sqlite3";
|
|
2595
2596
|
function printHelp() {
|
|
2596
2597
|
console.log(`
|
|
2597
2598
|
RelayPlane Proxy - Intelligent AI Model Routing
|
|
2598
2599
|
|
|
2599
2600
|
Usage:
|
|
2600
|
-
npx @relayplane/proxy [options]
|
|
2601
|
-
relayplane-proxy [options]
|
|
2601
|
+
npx @relayplane/proxy [command] [options]
|
|
2602
|
+
relayplane-proxy [command] [options]
|
|
2602
2603
|
|
|
2603
|
-
|
|
2604
|
+
Commands:
|
|
2605
|
+
(default) Start the proxy server
|
|
2606
|
+
stats Show routing statistics
|
|
2607
|
+
|
|
2608
|
+
Server Options:
|
|
2604
2609
|
--port <number> Port to listen on (default: 3001)
|
|
2605
2610
|
--host <string> Host to bind to (default: 127.0.0.1)
|
|
2606
2611
|
-v, --verbose Enable verbose logging
|
|
2612
|
+
|
|
2613
|
+
Stats Options:
|
|
2614
|
+
--days <number> Days of history to show (default: 7)
|
|
2615
|
+
|
|
2616
|
+
General:
|
|
2607
2617
|
-h, --help Show this help message
|
|
2608
2618
|
|
|
2609
2619
|
Environment Variables:
|
|
@@ -2613,26 +2623,108 @@ Environment Variables:
|
|
|
2613
2623
|
XAI_API_KEY xAI/Grok API key (optional)
|
|
2614
2624
|
MOONSHOT_API_KEY Moonshot API key (optional)
|
|
2615
2625
|
|
|
2616
|
-
|
|
2626
|
+
Examples:
|
|
2617
2627
|
# Start proxy on default port
|
|
2618
2628
|
npx @relayplane/proxy
|
|
2619
2629
|
|
|
2620
2630
|
# Start on custom port with verbose logging
|
|
2621
2631
|
npx @relayplane/proxy --port 8080 -v
|
|
2622
2632
|
|
|
2623
|
-
#
|
|
2624
|
-
|
|
2625
|
-
|
|
2633
|
+
# View routing stats for last 7 days
|
|
2634
|
+
npx @relayplane/proxy stats
|
|
2635
|
+
|
|
2636
|
+
# View stats for last 30 days
|
|
2637
|
+
npx @relayplane/proxy stats --days 30
|
|
2626
2638
|
|
|
2627
2639
|
Learn more: https://relayplane.com/integrations/openclaw
|
|
2628
2640
|
`);
|
|
2629
2641
|
}
|
|
2642
|
+
function showStats(days) {
|
|
2643
|
+
const dbPath = getDefaultDbPath();
|
|
2644
|
+
try {
|
|
2645
|
+
const db = new Database2(dbPath, { readonly: true });
|
|
2646
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
2647
|
+
const runs = db.prepare(`
|
|
2648
|
+
SELECT
|
|
2649
|
+
model,
|
|
2650
|
+
task_type,
|
|
2651
|
+
COUNT(*) as count,
|
|
2652
|
+
SUM(tokens_in) as total_in,
|
|
2653
|
+
SUM(tokens_out) as total_out,
|
|
2654
|
+
AVG(duration_ms) as avg_duration,
|
|
2655
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successes
|
|
2656
|
+
FROM runs
|
|
2657
|
+
WHERE created_at >= ?
|
|
2658
|
+
GROUP BY model
|
|
2659
|
+
ORDER BY count DESC
|
|
2660
|
+
`).all(cutoff);
|
|
2661
|
+
const totalRuns = runs.reduce((sum, r) => sum + r.count, 0);
|
|
2662
|
+
const totalTokensIn = runs.reduce((sum, r) => sum + (r.total_in || 0), 0);
|
|
2663
|
+
const totalTokensOut = runs.reduce((sum, r) => sum + (r.total_out || 0), 0);
|
|
2664
|
+
console.log("");
|
|
2665
|
+
console.log(` \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E`);
|
|
2666
|
+
console.log(` \u2502 RelayPlane Routing Stats \u2502`);
|
|
2667
|
+
console.log(` \u2502 Last ${String(days).padStart(2)} days \u2502`);
|
|
2668
|
+
console.log(` \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F`);
|
|
2669
|
+
console.log("");
|
|
2670
|
+
if (totalRuns === 0) {
|
|
2671
|
+
console.log(" No routing data found for this period.");
|
|
2672
|
+
console.log(" Start using the proxy to collect stats!");
|
|
2673
|
+
console.log("");
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
console.log(" Summary:");
|
|
2677
|
+
console.log(` Total requests: ${totalRuns.toLocaleString()}`);
|
|
2678
|
+
console.log(` Total tokens in: ${totalTokensIn.toLocaleString()}`);
|
|
2679
|
+
console.log(` Total tokens out: ${totalTokensOut.toLocaleString()}`);
|
|
2680
|
+
console.log("");
|
|
2681
|
+
console.log(" By Model:");
|
|
2682
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2683
|
+
for (const row of runs) {
|
|
2684
|
+
const pct = (row.count / totalRuns * 100).toFixed(1);
|
|
2685
|
+
const successRate = row.count > 0 ? (row.successes / row.count * 100).toFixed(0) : "0";
|
|
2686
|
+
console.log(` ${row.model.padEnd(35)} ${String(row.count).padStart(6)} (${pct.padStart(5)}%) ${successRate}% ok`);
|
|
2687
|
+
}
|
|
2688
|
+
console.log("");
|
|
2689
|
+
const haikuRuns = runs.filter((r) => r.model.includes("haiku"));
|
|
2690
|
+
const haikuTokensIn = haikuRuns.reduce((sum, r) => sum + (r.total_in || 0), 0);
|
|
2691
|
+
const haikuTokensOut = haikuRuns.reduce((sum, r) => sum + (r.total_out || 0), 0);
|
|
2692
|
+
const opusCost = totalTokensIn * 15 / 1e6 + totalTokensOut * 75 / 1e6;
|
|
2693
|
+
const haikuCost = haikuTokensIn * 0.25 / 1e6 + haikuTokensOut * 1.25 / 1e6;
|
|
2694
|
+
const nonHaikuCost = (totalTokensIn - haikuTokensIn) * 3 / 1e6 + (totalTokensOut - haikuTokensOut) * 15 / 1e6;
|
|
2695
|
+
const actualCost = haikuCost + nonHaikuCost;
|
|
2696
|
+
const savings = opusCost - actualCost;
|
|
2697
|
+
if (savings > 0) {
|
|
2698
|
+
console.log(" Estimated Savings:");
|
|
2699
|
+
console.log(` If all Opus: $${opusCost.toFixed(2)}`);
|
|
2700
|
+
console.log(` With routing: $${actualCost.toFixed(2)}`);
|
|
2701
|
+
console.log(` Saved: $${savings.toFixed(2)} (${(savings / opusCost * 100).toFixed(0)}%)`);
|
|
2702
|
+
console.log("");
|
|
2703
|
+
}
|
|
2704
|
+
db.close();
|
|
2705
|
+
} catch (err) {
|
|
2706
|
+
console.error("Error reading stats:", err);
|
|
2707
|
+
console.log("");
|
|
2708
|
+
console.log(" No data found. The proxy stores data at:");
|
|
2709
|
+
console.log(` ${dbPath}`);
|
|
2710
|
+
console.log("");
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2630
2713
|
async function main() {
|
|
2631
2714
|
const args = process.argv.slice(2);
|
|
2632
2715
|
if (args.includes("-h") || args.includes("--help")) {
|
|
2633
2716
|
printHelp();
|
|
2634
2717
|
process.exit(0);
|
|
2635
2718
|
}
|
|
2719
|
+
if (args[0] === "stats") {
|
|
2720
|
+
let days = 7;
|
|
2721
|
+
const daysIdx = args.indexOf("--days");
|
|
2722
|
+
if (daysIdx !== -1 && args[daysIdx + 1]) {
|
|
2723
|
+
days = parseInt(args[daysIdx + 1], 10) || 7;
|
|
2724
|
+
}
|
|
2725
|
+
showStats(days);
|
|
2726
|
+
process.exit(0);
|
|
2727
|
+
}
|
|
2636
2728
|
let port = 3001;
|
|
2637
2729
|
let host = "127.0.0.1";
|
|
2638
2730
|
let verbose = false;
|