@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 +25 -3
- package/dist/cli.js +112 -15
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +112 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +13 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
#
|
|
82
|
-
|
|
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
|
-
|
|
159
|
-
{ taskType: "
|
|
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
|
-
|
|
1630
|
-
code_review: { provider: "anthropic", model: "claude-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
2647
|
-
|
|
2648
|
-
|
|
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;
|