@relayplane/proxy 0.1.3 → 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 CHANGED
@@ -20,6 +20,25 @@ Or run directly:
20
20
  npx @relayplane/proxy
21
21
  ```
22
22
 
23
+ ## CLI Commands
24
+
25
+ ```bash
26
+ # Start the proxy server
27
+ npx @relayplane/proxy
28
+
29
+ # Start on custom port
30
+ npx @relayplane/proxy --port 8080
31
+
32
+ # View routing statistics
33
+ npx @relayplane/proxy stats
34
+
35
+ # View stats for last 30 days
36
+ npx @relayplane/proxy stats --days 30
37
+
38
+ # Show help
39
+ npx @relayplane/proxy --help
40
+ ```
41
+
23
42
  ## Quick Start
24
43
 
25
44
  ### 1. Set your API keys
@@ -88,10 +107,10 @@ Unlike static routing rules, RelayPlane adapts to **your** usage patterns.
88
107
 
89
108
  | Provider | Models | Streaming | Tools |
90
109
  |----------|--------|-----------|-------|
91
- | **Anthropic** | Claude Opus, Sonnet, Haiku | ✓ | ✓ |
92
- | **OpenAI** | GPT-4o, GPT-4o-mini, o1, o3 | ✓ | ✓ |
93
- | **Google** | Gemini 2.0 Flash, 1.5 Pro | ✓ | |
94
- | **xAI** | Grok-2, Grok-2-mini | ✓ | ✓ |
110
+ | **Anthropic** | Claude 4.5 (Opus, Sonnet, Haiku) | ✓ | ✓ |
111
+ | **OpenAI** | GPT-5.2, GPT-5.2-Codex, o1, o3 | ✓ | ✓ |
112
+ | **Google** | Gemini 2.0 Flash, 2.0 Pro | ✓ | |
113
+ | **xAI** | Grok-3, Grok-3-mini | ✓ | ✓ |
95
114
  | **Moonshot** | v1-8k, v1-32k, v1-128k | ✓ | ✓ |
96
115
 
97
116
  ## Routing Modes
package/dist/cli.js CHANGED
@@ -2400,7 +2400,7 @@ async function startProxy(config = {}) {
2400
2400
  targetProvider = "anthropic";
2401
2401
  }
2402
2402
  } else if (routingMode === "quality") {
2403
- const qualityModel = process.env["RELAYPLANE_QUALITY_MODEL"] || "claude-3-5-sonnet-latest";
2403
+ const qualityModel = process.env["RELAYPLANE_QUALITY_MODEL"] || "claude-sonnet-4-20250514";
2404
2404
  targetModel = qualityModel;
2405
2405
  targetProvider = "anthropic";
2406
2406
  }
@@ -2615,18 +2615,28 @@ async function handleNonStreamingRequest(res, request, targetProvider, targetMod
2615
2615
  }
2616
2616
 
2617
2617
  // src/cli.ts
2618
+ var import_better_sqlite32 = __toESM(require("better-sqlite3"));
2618
2619
  function printHelp() {
2619
2620
  console.log(`
2620
2621
  RelayPlane Proxy - Intelligent AI Model Routing
2621
2622
 
2622
2623
  Usage:
2623
- npx @relayplane/proxy [options]
2624
- relayplane-proxy [options]
2624
+ npx @relayplane/proxy [command] [options]
2625
+ relayplane-proxy [command] [options]
2625
2626
 
2626
- Options:
2627
+ Commands:
2628
+ (default) Start the proxy server
2629
+ stats Show routing statistics
2630
+
2631
+ Server Options:
2627
2632
  --port <number> Port to listen on (default: 3001)
2628
2633
  --host <string> Host to bind to (default: 127.0.0.1)
2629
2634
  -v, --verbose Enable verbose logging
2635
+
2636
+ Stats Options:
2637
+ --days <number> Days of history to show (default: 7)
2638
+
2639
+ General:
2630
2640
  -h, --help Show this help message
2631
2641
 
2632
2642
  Environment Variables:
@@ -2636,26 +2646,108 @@ Environment Variables:
2636
2646
  XAI_API_KEY xAI/Grok API key (optional)
2637
2647
  MOONSHOT_API_KEY Moonshot API key (optional)
2638
2648
 
2639
- Example:
2649
+ Examples:
2640
2650
  # Start proxy on default port
2641
2651
  npx @relayplane/proxy
2642
2652
 
2643
2653
  # Start on custom port with verbose logging
2644
2654
  npx @relayplane/proxy --port 8080 -v
2645
2655
 
2646
- # Then point your SDKs to the proxy
2647
- export ANTHROPIC_BASE_URL=http://localhost:3001
2648
- export OPENAI_BASE_URL=http://localhost:3001
2656
+ # View routing stats for last 7 days
2657
+ npx @relayplane/proxy stats
2658
+
2659
+ # View stats for last 30 days
2660
+ npx @relayplane/proxy stats --days 30
2649
2661
 
2650
2662
  Learn more: https://relayplane.com/integrations/openclaw
2651
2663
  `);
2652
2664
  }
2665
+ function showStats(days) {
2666
+ const dbPath = getDefaultDbPath();
2667
+ try {
2668
+ const db = new import_better_sqlite32.default(dbPath, { readonly: true });
2669
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
2670
+ const runs = db.prepare(`
2671
+ SELECT
2672
+ model,
2673
+ task_type,
2674
+ COUNT(*) as count,
2675
+ SUM(tokens_in) as total_in,
2676
+ SUM(tokens_out) as total_out,
2677
+ AVG(duration_ms) as avg_duration,
2678
+ SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successes
2679
+ FROM runs
2680
+ WHERE created_at >= ?
2681
+ GROUP BY model
2682
+ ORDER BY count DESC
2683
+ `).all(cutoff);
2684
+ const totalRuns = runs.reduce((sum, r) => sum + r.count, 0);
2685
+ const totalTokensIn = runs.reduce((sum, r) => sum + (r.total_in || 0), 0);
2686
+ const totalTokensOut = runs.reduce((sum, r) => sum + (r.total_out || 0), 0);
2687
+ console.log("");
2688
+ 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`);
2689
+ console.log(` \u2502 RelayPlane Routing Stats \u2502`);
2690
+ console.log(` \u2502 Last ${String(days).padStart(2)} days \u2502`);
2691
+ 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`);
2692
+ console.log("");
2693
+ if (totalRuns === 0) {
2694
+ console.log(" No routing data found for this period.");
2695
+ console.log(" Start using the proxy to collect stats!");
2696
+ console.log("");
2697
+ return;
2698
+ }
2699
+ console.log(" Summary:");
2700
+ console.log(` Total requests: ${totalRuns.toLocaleString()}`);
2701
+ console.log(` Total tokens in: ${totalTokensIn.toLocaleString()}`);
2702
+ console.log(` Total tokens out: ${totalTokensOut.toLocaleString()}`);
2703
+ console.log("");
2704
+ console.log(" By Model:");
2705
+ 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");
2706
+ for (const row of runs) {
2707
+ const pct = (row.count / totalRuns * 100).toFixed(1);
2708
+ const successRate = row.count > 0 ? (row.successes / row.count * 100).toFixed(0) : "0";
2709
+ console.log(` ${row.model.padEnd(35)} ${String(row.count).padStart(6)} (${pct.padStart(5)}%) ${successRate}% ok`);
2710
+ }
2711
+ console.log("");
2712
+ const haikuRuns = runs.filter((r) => r.model.includes("haiku"));
2713
+ const haikuTokensIn = haikuRuns.reduce((sum, r) => sum + (r.total_in || 0), 0);
2714
+ const haikuTokensOut = haikuRuns.reduce((sum, r) => sum + (r.total_out || 0), 0);
2715
+ const opusCost = totalTokensIn * 15 / 1e6 + totalTokensOut * 75 / 1e6;
2716
+ const haikuCost = haikuTokensIn * 0.25 / 1e6 + haikuTokensOut * 1.25 / 1e6;
2717
+ const nonHaikuCost = (totalTokensIn - haikuTokensIn) * 3 / 1e6 + (totalTokensOut - haikuTokensOut) * 15 / 1e6;
2718
+ const actualCost = haikuCost + nonHaikuCost;
2719
+ const savings = opusCost - actualCost;
2720
+ if (savings > 0) {
2721
+ console.log(" Estimated Savings:");
2722
+ console.log(` If all Opus: $${opusCost.toFixed(2)}`);
2723
+ console.log(` With routing: $${actualCost.toFixed(2)}`);
2724
+ console.log(` Saved: $${savings.toFixed(2)} (${(savings / opusCost * 100).toFixed(0)}%)`);
2725
+ console.log("");
2726
+ }
2727
+ db.close();
2728
+ } catch (err) {
2729
+ console.error("Error reading stats:", err);
2730
+ console.log("");
2731
+ console.log(" No data found. The proxy stores data at:");
2732
+ console.log(` ${dbPath}`);
2733
+ console.log("");
2734
+ }
2735
+ }
2653
2736
  async function main() {
2654
2737
  const args = process.argv.slice(2);
2655
2738
  if (args.includes("-h") || args.includes("--help")) {
2656
2739
  printHelp();
2657
2740
  process.exit(0);
2658
2741
  }
2742
+ if (args[0] === "stats") {
2743
+ let days = 7;
2744
+ const daysIdx = args.indexOf("--days");
2745
+ if (daysIdx !== -1 && args[daysIdx + 1]) {
2746
+ days = parseInt(args[daysIdx + 1], 10) || 7;
2747
+ }
2748
+ showStats(days);
2749
+ process.exit(0);
2750
+ }
2659
2751
  let port = 3001;
2660
2752
  let host = "127.0.0.1";
2661
2753
  let verbose = false;