@relayplane/proxy 0.1.4 → 0.1.6

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
@@ -75,11 +94,14 @@ RelayPlane doesn't just route — it **learns from every request**:
75
94
  - **Local Intelligence** — All learning happens in your local SQLite DB
76
95
 
77
96
  ```bash
78
- # View your routing stats
97
+ # View your routing stats (last 7 days)
79
98
  npx @relayplane/proxy stats
80
99
 
81
- # Query the raw data
82
- sqlite3 ~/.relayplane/data.db "SELECT model, outcome, COUNT(*) FROM runs GROUP BY model, outcome"
100
+ # View last 30 days
101
+ npx @relayplane/proxy stats --days 30
102
+
103
+ # Query the raw data directly
104
+ sqlite3 ~/.relayplane/data.db "SELECT model, task_type, COUNT(*) FROM runs GROUP BY model, task_type"
83
105
  ```
84
106
 
85
107
  Unlike static routing rules, RelayPlane adapts to **your** usage patterns.
package/dist/cli.js CHANGED
@@ -155,11 +155,13 @@ CREATE TABLE IF NOT EXISTS schema_version (
155
155
  INSERT OR IGNORE INTO schema_version (version) VALUES (1);
156
156
  `;
157
157
  var DEFAULT_ROUTING_RULES = [
158
- { taskType: "code_generation", preferredModel: "anthropic:claude-3-5-haiku-latest" },
159
- { taskType: "code_review", preferredModel: "anthropic:claude-3-5-haiku-latest" },
158
+ // Complex tasks Sonnet (need reasoning & quality)
159
+ { taskType: "code_generation", preferredModel: "anthropic:claude-sonnet-4-20250514" },
160
+ { taskType: "code_review", preferredModel: "anthropic:claude-sonnet-4-20250514" },
161
+ { taskType: "analysis", preferredModel: "anthropic:claude-sonnet-4-20250514" },
162
+ { taskType: "creative_writing", preferredModel: "anthropic:claude-sonnet-4-20250514" },
163
+ // Simple tasks → Haiku (cost efficient)
160
164
  { taskType: "summarization", preferredModel: "anthropic:claude-3-5-haiku-latest" },
161
- { taskType: "analysis", preferredModel: "anthropic:claude-3-5-haiku-latest" },
162
- { taskType: "creative_writing", preferredModel: "anthropic:claude-3-5-haiku-latest" },
163
165
  { taskType: "data_extraction", preferredModel: "anthropic:claude-3-5-haiku-latest" },
164
166
  { taskType: "translation", preferredModel: "anthropic:claude-3-5-haiku-latest" },
165
167
  { taskType: "question_answering", preferredModel: "anthropic:claude-3-5-haiku-latest" },
@@ -1626,11 +1628,14 @@ var MODEL_MAPPING = {
1626
1628
  "gpt-4.1": { provider: "openai", model: "gpt-4.1" }
1627
1629
  };
1628
1630
  var DEFAULT_ROUTING = {
1629
- code_generation: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1630
- code_review: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1631
+ // Complex tasks Sonnet (need reasoning & quality)
1632
+ code_review: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1633
+ analysis: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1634
+ creative_writing: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1635
+ // Medium tasks → Sonnet (benefit from better model)
1636
+ code_generation: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1637
+ // Simple tasks → Haiku (cost efficient)
1631
1638
  summarization: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1632
- analysis: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1633
- creative_writing: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1634
1639
  data_extraction: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1635
1640
  translation: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1636
1641
  question_answering: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
@@ -2615,18 +2620,28 @@ async function handleNonStreamingRequest(res, request, targetProvider, targetMod
2615
2620
  }
2616
2621
 
2617
2622
  // src/cli.ts
2623
+ var import_better_sqlite32 = __toESM(require("better-sqlite3"));
2618
2624
  function printHelp() {
2619
2625
  console.log(`
2620
2626
  RelayPlane Proxy - Intelligent AI Model Routing
2621
2627
 
2622
2628
  Usage:
2623
- npx @relayplane/proxy [options]
2624
- relayplane-proxy [options]
2629
+ npx @relayplane/proxy [command] [options]
2630
+ relayplane-proxy [command] [options]
2625
2631
 
2626
- Options:
2632
+ Commands:
2633
+ (default) Start the proxy server
2634
+ stats Show routing statistics
2635
+
2636
+ Server Options:
2627
2637
  --port <number> Port to listen on (default: 3001)
2628
2638
  --host <string> Host to bind to (default: 127.0.0.1)
2629
2639
  -v, --verbose Enable verbose logging
2640
+
2641
+ Stats Options:
2642
+ --days <number> Days of history to show (default: 7)
2643
+
2644
+ General:
2630
2645
  -h, --help Show this help message
2631
2646
 
2632
2647
  Environment Variables:
@@ -2636,26 +2651,108 @@ Environment Variables:
2636
2651
  XAI_API_KEY xAI/Grok API key (optional)
2637
2652
  MOONSHOT_API_KEY Moonshot API key (optional)
2638
2653
 
2639
- Example:
2654
+ Examples:
2640
2655
  # Start proxy on default port
2641
2656
  npx @relayplane/proxy
2642
2657
 
2643
2658
  # Start on custom port with verbose logging
2644
2659
  npx @relayplane/proxy --port 8080 -v
2645
2660
 
2646
- # Then point your SDKs to the proxy
2647
- export ANTHROPIC_BASE_URL=http://localhost:3001
2648
- export OPENAI_BASE_URL=http://localhost:3001
2661
+ # View routing stats for last 7 days
2662
+ npx @relayplane/proxy stats
2663
+
2664
+ # View stats for last 30 days
2665
+ npx @relayplane/proxy stats --days 30
2649
2666
 
2650
2667
  Learn more: https://relayplane.com/integrations/openclaw
2651
2668
  `);
2652
2669
  }
2670
+ function showStats(days) {
2671
+ const dbPath = getDefaultDbPath();
2672
+ try {
2673
+ const db = new import_better_sqlite32.default(dbPath, { readonly: true });
2674
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
2675
+ const runs = db.prepare(`
2676
+ SELECT
2677
+ model,
2678
+ task_type,
2679
+ COUNT(*) as count,
2680
+ SUM(tokens_in) as total_in,
2681
+ SUM(tokens_out) as total_out,
2682
+ AVG(duration_ms) as avg_duration,
2683
+ SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successes
2684
+ FROM runs
2685
+ WHERE created_at >= ?
2686
+ GROUP BY model
2687
+ ORDER BY count DESC
2688
+ `).all(cutoff);
2689
+ const totalRuns = runs.reduce((sum, r) => sum + r.count, 0);
2690
+ const totalTokensIn = runs.reduce((sum, r) => sum + (r.total_in || 0), 0);
2691
+ const totalTokensOut = runs.reduce((sum, r) => sum + (r.total_out || 0), 0);
2692
+ console.log("");
2693
+ 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`);
2694
+ console.log(` \u2502 RelayPlane Routing Stats \u2502`);
2695
+ console.log(` \u2502 Last ${String(days).padStart(2)} days \u2502`);
2696
+ 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`);
2697
+ console.log("");
2698
+ if (totalRuns === 0) {
2699
+ console.log(" No routing data found for this period.");
2700
+ console.log(" Start using the proxy to collect stats!");
2701
+ console.log("");
2702
+ return;
2703
+ }
2704
+ console.log(" Summary:");
2705
+ console.log(` Total requests: ${totalRuns.toLocaleString()}`);
2706
+ console.log(` Total tokens in: ${totalTokensIn.toLocaleString()}`);
2707
+ console.log(` Total tokens out: ${totalTokensOut.toLocaleString()}`);
2708
+ console.log("");
2709
+ console.log(" By Model:");
2710
+ 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");
2711
+ for (const row of runs) {
2712
+ const pct = (row.count / totalRuns * 100).toFixed(1);
2713
+ const successRate = row.count > 0 ? (row.successes / row.count * 100).toFixed(0) : "0";
2714
+ console.log(` ${row.model.padEnd(35)} ${String(row.count).padStart(6)} (${pct.padStart(5)}%) ${successRate}% ok`);
2715
+ }
2716
+ console.log("");
2717
+ const haikuRuns = runs.filter((r) => r.model.includes("haiku"));
2718
+ const haikuTokensIn = haikuRuns.reduce((sum, r) => sum + (r.total_in || 0), 0);
2719
+ const haikuTokensOut = haikuRuns.reduce((sum, r) => sum + (r.total_out || 0), 0);
2720
+ const opusCost = totalTokensIn * 15 / 1e6 + totalTokensOut * 75 / 1e6;
2721
+ const haikuCost = haikuTokensIn * 0.25 / 1e6 + haikuTokensOut * 1.25 / 1e6;
2722
+ const nonHaikuCost = (totalTokensIn - haikuTokensIn) * 3 / 1e6 + (totalTokensOut - haikuTokensOut) * 15 / 1e6;
2723
+ const actualCost = haikuCost + nonHaikuCost;
2724
+ const savings = opusCost - actualCost;
2725
+ if (savings > 0) {
2726
+ console.log(" Estimated Savings:");
2727
+ console.log(` If all Opus: $${opusCost.toFixed(2)}`);
2728
+ console.log(` With routing: $${actualCost.toFixed(2)}`);
2729
+ console.log(` Saved: $${savings.toFixed(2)} (${(savings / opusCost * 100).toFixed(0)}%)`);
2730
+ console.log("");
2731
+ }
2732
+ db.close();
2733
+ } catch (err) {
2734
+ console.error("Error reading stats:", err);
2735
+ console.log("");
2736
+ console.log(" No data found. The proxy stores data at:");
2737
+ console.log(` ${dbPath}`);
2738
+ console.log("");
2739
+ }
2740
+ }
2653
2741
  async function main() {
2654
2742
  const args = process.argv.slice(2);
2655
2743
  if (args.includes("-h") || args.includes("--help")) {
2656
2744
  printHelp();
2657
2745
  process.exit(0);
2658
2746
  }
2747
+ if (args[0] === "stats") {
2748
+ let days = 7;
2749
+ const daysIdx = args.indexOf("--days");
2750
+ if (daysIdx !== -1 && args[daysIdx + 1]) {
2751
+ days = parseInt(args[daysIdx + 1], 10) || 7;
2752
+ }
2753
+ showStats(days);
2754
+ process.exit(0);
2755
+ }
2659
2756
  let port = 3001;
2660
2757
  let host = "127.0.0.1";
2661
2758
  let verbose = false;