@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/dist/cli.mjs CHANGED
@@ -132,11 +132,13 @@ CREATE TABLE IF NOT EXISTS schema_version (
132
132
  INSERT OR IGNORE INTO schema_version (version) VALUES (1);
133
133
  `;
134
134
  var DEFAULT_ROUTING_RULES = [
135
- { taskType: "code_generation", preferredModel: "anthropic:claude-3-5-haiku-latest" },
136
- { taskType: "code_review", preferredModel: "anthropic:claude-3-5-haiku-latest" },
135
+ // Complex tasks Sonnet (need reasoning & quality)
136
+ { taskType: "code_generation", preferredModel: "anthropic:claude-sonnet-4-20250514" },
137
+ { taskType: "code_review", preferredModel: "anthropic:claude-sonnet-4-20250514" },
138
+ { taskType: "analysis", preferredModel: "anthropic:claude-sonnet-4-20250514" },
139
+ { taskType: "creative_writing", preferredModel: "anthropic:claude-sonnet-4-20250514" },
140
+ // Simple tasks → Haiku (cost efficient)
137
141
  { taskType: "summarization", preferredModel: "anthropic:claude-3-5-haiku-latest" },
138
- { taskType: "analysis", preferredModel: "anthropic:claude-3-5-haiku-latest" },
139
- { taskType: "creative_writing", preferredModel: "anthropic:claude-3-5-haiku-latest" },
140
142
  { taskType: "data_extraction", preferredModel: "anthropic:claude-3-5-haiku-latest" },
141
143
  { taskType: "translation", preferredModel: "anthropic:claude-3-5-haiku-latest" },
142
144
  { taskType: "question_answering", preferredModel: "anthropic:claude-3-5-haiku-latest" },
@@ -1603,11 +1605,14 @@ var MODEL_MAPPING = {
1603
1605
  "gpt-4.1": { provider: "openai", model: "gpt-4.1" }
1604
1606
  };
1605
1607
  var DEFAULT_ROUTING = {
1606
- code_generation: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1607
- code_review: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1608
+ // Complex tasks Sonnet (need reasoning & quality)
1609
+ code_review: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1610
+ analysis: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1611
+ creative_writing: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1612
+ // Medium tasks → Sonnet (benefit from better model)
1613
+ code_generation: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
1614
+ // Simple tasks → Haiku (cost efficient)
1608
1615
  summarization: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1609
- analysis: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1610
- creative_writing: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1611
1616
  data_extraction: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1612
1617
  translation: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
1613
1618
  question_answering: { provider: "anthropic", model: "claude-3-5-haiku-latest" },
@@ -2592,18 +2597,28 @@ async function handleNonStreamingRequest(res, request, targetProvider, targetMod
2592
2597
  }
2593
2598
 
2594
2599
  // src/cli.ts
2600
+ import Database2 from "better-sqlite3";
2595
2601
  function printHelp() {
2596
2602
  console.log(`
2597
2603
  RelayPlane Proxy - Intelligent AI Model Routing
2598
2604
 
2599
2605
  Usage:
2600
- npx @relayplane/proxy [options]
2601
- relayplane-proxy [options]
2606
+ npx @relayplane/proxy [command] [options]
2607
+ relayplane-proxy [command] [options]
2602
2608
 
2603
- Options:
2609
+ Commands:
2610
+ (default) Start the proxy server
2611
+ stats Show routing statistics
2612
+
2613
+ Server Options:
2604
2614
  --port <number> Port to listen on (default: 3001)
2605
2615
  --host <string> Host to bind to (default: 127.0.0.1)
2606
2616
  -v, --verbose Enable verbose logging
2617
+
2618
+ Stats Options:
2619
+ --days <number> Days of history to show (default: 7)
2620
+
2621
+ General:
2607
2622
  -h, --help Show this help message
2608
2623
 
2609
2624
  Environment Variables:
@@ -2613,26 +2628,108 @@ Environment Variables:
2613
2628
  XAI_API_KEY xAI/Grok API key (optional)
2614
2629
  MOONSHOT_API_KEY Moonshot API key (optional)
2615
2630
 
2616
- Example:
2631
+ Examples:
2617
2632
  # Start proxy on default port
2618
2633
  npx @relayplane/proxy
2619
2634
 
2620
2635
  # Start on custom port with verbose logging
2621
2636
  npx @relayplane/proxy --port 8080 -v
2622
2637
 
2623
- # Then point your SDKs to the proxy
2624
- export ANTHROPIC_BASE_URL=http://localhost:3001
2625
- export OPENAI_BASE_URL=http://localhost:3001
2638
+ # View routing stats for last 7 days
2639
+ npx @relayplane/proxy stats
2640
+
2641
+ # View stats for last 30 days
2642
+ npx @relayplane/proxy stats --days 30
2626
2643
 
2627
2644
  Learn more: https://relayplane.com/integrations/openclaw
2628
2645
  `);
2629
2646
  }
2647
+ function showStats(days) {
2648
+ const dbPath = getDefaultDbPath();
2649
+ try {
2650
+ const db = new Database2(dbPath, { readonly: true });
2651
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
2652
+ const runs = db.prepare(`
2653
+ SELECT
2654
+ model,
2655
+ task_type,
2656
+ COUNT(*) as count,
2657
+ SUM(tokens_in) as total_in,
2658
+ SUM(tokens_out) as total_out,
2659
+ AVG(duration_ms) as avg_duration,
2660
+ SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successes
2661
+ FROM runs
2662
+ WHERE created_at >= ?
2663
+ GROUP BY model
2664
+ ORDER BY count DESC
2665
+ `).all(cutoff);
2666
+ const totalRuns = runs.reduce((sum, r) => sum + r.count, 0);
2667
+ const totalTokensIn = runs.reduce((sum, r) => sum + (r.total_in || 0), 0);
2668
+ const totalTokensOut = runs.reduce((sum, r) => sum + (r.total_out || 0), 0);
2669
+ console.log("");
2670
+ 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`);
2671
+ console.log(` \u2502 RelayPlane Routing Stats \u2502`);
2672
+ console.log(` \u2502 Last ${String(days).padStart(2)} days \u2502`);
2673
+ 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`);
2674
+ console.log("");
2675
+ if (totalRuns === 0) {
2676
+ console.log(" No routing data found for this period.");
2677
+ console.log(" Start using the proxy to collect stats!");
2678
+ console.log("");
2679
+ return;
2680
+ }
2681
+ console.log(" Summary:");
2682
+ console.log(` Total requests: ${totalRuns.toLocaleString()}`);
2683
+ console.log(` Total tokens in: ${totalTokensIn.toLocaleString()}`);
2684
+ console.log(` Total tokens out: ${totalTokensOut.toLocaleString()}`);
2685
+ console.log("");
2686
+ console.log(" By Model:");
2687
+ 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");
2688
+ for (const row of runs) {
2689
+ const pct = (row.count / totalRuns * 100).toFixed(1);
2690
+ const successRate = row.count > 0 ? (row.successes / row.count * 100).toFixed(0) : "0";
2691
+ console.log(` ${row.model.padEnd(35)} ${String(row.count).padStart(6)} (${pct.padStart(5)}%) ${successRate}% ok`);
2692
+ }
2693
+ console.log("");
2694
+ const haikuRuns = runs.filter((r) => r.model.includes("haiku"));
2695
+ const haikuTokensIn = haikuRuns.reduce((sum, r) => sum + (r.total_in || 0), 0);
2696
+ const haikuTokensOut = haikuRuns.reduce((sum, r) => sum + (r.total_out || 0), 0);
2697
+ const opusCost = totalTokensIn * 15 / 1e6 + totalTokensOut * 75 / 1e6;
2698
+ const haikuCost = haikuTokensIn * 0.25 / 1e6 + haikuTokensOut * 1.25 / 1e6;
2699
+ const nonHaikuCost = (totalTokensIn - haikuTokensIn) * 3 / 1e6 + (totalTokensOut - haikuTokensOut) * 15 / 1e6;
2700
+ const actualCost = haikuCost + nonHaikuCost;
2701
+ const savings = opusCost - actualCost;
2702
+ if (savings > 0) {
2703
+ console.log(" Estimated Savings:");
2704
+ console.log(` If all Opus: $${opusCost.toFixed(2)}`);
2705
+ console.log(` With routing: $${actualCost.toFixed(2)}`);
2706
+ console.log(` Saved: $${savings.toFixed(2)} (${(savings / opusCost * 100).toFixed(0)}%)`);
2707
+ console.log("");
2708
+ }
2709
+ db.close();
2710
+ } catch (err) {
2711
+ console.error("Error reading stats:", err);
2712
+ console.log("");
2713
+ console.log(" No data found. The proxy stores data at:");
2714
+ console.log(` ${dbPath}`);
2715
+ console.log("");
2716
+ }
2717
+ }
2630
2718
  async function main() {
2631
2719
  const args = process.argv.slice(2);
2632
2720
  if (args.includes("-h") || args.includes("--help")) {
2633
2721
  printHelp();
2634
2722
  process.exit(0);
2635
2723
  }
2724
+ if (args[0] === "stats") {
2725
+ let days = 7;
2726
+ const daysIdx = args.indexOf("--days");
2727
+ if (daysIdx !== -1 && args[daysIdx + 1]) {
2728
+ days = parseInt(args[daysIdx + 1], 10) || 7;
2729
+ }
2730
+ showStats(days);
2731
+ process.exit(0);
2732
+ }
2636
2733
  let port = 3001;
2637
2734
  let host = "127.0.0.1";
2638
2735
  let verbose = false;