@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/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
- Options:
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
- Example:
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
- # Then point your SDKs to the proxy
2624
- export ANTHROPIC_BASE_URL=http://localhost:3001
2625
- export OPENAI_BASE_URL=http://localhost:3001
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;