@relayplane/proxy 0.1.7 → 0.1.8
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 +73 -1
- package/dist/cli.js +126 -15
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +126 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +96 -1
- package/dist/index.d.ts +96 -1
- package/dist/index.js +181 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +175 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1568,12 +1568,107 @@ ${input.prompt}` : input.prompt;
|
|
|
1568
1568
|
}
|
|
1569
1569
|
};
|
|
1570
1570
|
|
|
1571
|
+
// src/config.ts
|
|
1572
|
+
import * as fs2 from "fs";
|
|
1573
|
+
import * as path2 from "path";
|
|
1574
|
+
import * as os2 from "os";
|
|
1575
|
+
import { z } from "zod";
|
|
1576
|
+
var StrategySchema = z.object({
|
|
1577
|
+
model: z.string(),
|
|
1578
|
+
minConfidence: z.number().min(0).max(1).optional(),
|
|
1579
|
+
fallback: z.string().optional()
|
|
1580
|
+
});
|
|
1581
|
+
var ConfigSchema = z.object({
|
|
1582
|
+
strategies: z.record(z.string(), StrategySchema).optional(),
|
|
1583
|
+
defaults: z.object({
|
|
1584
|
+
qualityModel: z.string().optional(),
|
|
1585
|
+
costModel: z.string().optional()
|
|
1586
|
+
}).optional()
|
|
1587
|
+
});
|
|
1588
|
+
var DEFAULT_CONFIG = {
|
|
1589
|
+
strategies: {
|
|
1590
|
+
code_review: { model: "anthropic:claude-sonnet-4-20250514" },
|
|
1591
|
+
code_generation: { model: "anthropic:claude-3-5-haiku-latest" },
|
|
1592
|
+
analysis: { model: "anthropic:claude-sonnet-4-20250514" },
|
|
1593
|
+
summarization: { model: "anthropic:claude-3-5-haiku-latest" },
|
|
1594
|
+
creative_writing: { model: "anthropic:claude-sonnet-4-20250514" },
|
|
1595
|
+
data_extraction: { model: "anthropic:claude-3-5-haiku-latest" },
|
|
1596
|
+
translation: { model: "anthropic:claude-3-5-haiku-latest" },
|
|
1597
|
+
question_answering: { model: "anthropic:claude-3-5-haiku-latest" },
|
|
1598
|
+
general: { model: "anthropic:claude-3-5-haiku-latest" }
|
|
1599
|
+
},
|
|
1600
|
+
defaults: {
|
|
1601
|
+
qualityModel: "claude-sonnet-4-20250514",
|
|
1602
|
+
costModel: "claude-3-5-haiku-latest"
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
function getConfigPath() {
|
|
1606
|
+
return path2.join(os2.homedir(), ".relayplane", "config.json");
|
|
1607
|
+
}
|
|
1608
|
+
function writeDefaultConfig() {
|
|
1609
|
+
const configPath = getConfigPath();
|
|
1610
|
+
const dir = path2.dirname(configPath);
|
|
1611
|
+
if (!fs2.existsSync(dir)) {
|
|
1612
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1613
|
+
}
|
|
1614
|
+
if (!fs2.existsSync(configPath)) {
|
|
1615
|
+
fs2.writeFileSync(
|
|
1616
|
+
configPath,
|
|
1617
|
+
JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n",
|
|
1618
|
+
"utf-8"
|
|
1619
|
+
);
|
|
1620
|
+
console.log(`[relayplane] Created default config at ${configPath}`);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
function loadConfig() {
|
|
1624
|
+
const configPath = getConfigPath();
|
|
1625
|
+
writeDefaultConfig();
|
|
1626
|
+
try {
|
|
1627
|
+
const raw = fs2.readFileSync(configPath, "utf-8");
|
|
1628
|
+
const parsed = JSON.parse(raw);
|
|
1629
|
+
const validated = ConfigSchema.parse(parsed);
|
|
1630
|
+
return validated;
|
|
1631
|
+
} catch (err) {
|
|
1632
|
+
if (err instanceof z.ZodError) {
|
|
1633
|
+
console.error(`[relayplane] Invalid config: ${err.message}`);
|
|
1634
|
+
} else if (err instanceof SyntaxError) {
|
|
1635
|
+
console.error(`[relayplane] Config JSON parse error: ${err.message}`);
|
|
1636
|
+
} else {
|
|
1637
|
+
console.error(`[relayplane] Failed to load config: ${err}`);
|
|
1638
|
+
}
|
|
1639
|
+
console.log("[relayplane] Using default config");
|
|
1640
|
+
return DEFAULT_CONFIG;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function getStrategy(config, taskType) {
|
|
1644
|
+
return config.strategies?.[taskType] ?? null;
|
|
1645
|
+
}
|
|
1646
|
+
function watchConfig(onChange) {
|
|
1647
|
+
const configPath = getConfigPath();
|
|
1648
|
+
const dir = path2.dirname(configPath);
|
|
1649
|
+
if (!fs2.existsSync(dir)) {
|
|
1650
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1651
|
+
}
|
|
1652
|
+
let debounceTimer = null;
|
|
1653
|
+
fs2.watch(dir, (eventType, filename) => {
|
|
1654
|
+
if (filename === "config.json") {
|
|
1655
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1656
|
+
debounceTimer = setTimeout(() => {
|
|
1657
|
+
console.log("[relayplane] Config file changed, reloading...");
|
|
1658
|
+
const newConfig = loadConfig();
|
|
1659
|
+
onChange(newConfig);
|
|
1660
|
+
}, 100);
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1571
1665
|
// src/proxy.ts
|
|
1572
|
-
var VERSION = "0.1.
|
|
1666
|
+
var VERSION = "0.1.8";
|
|
1573
1667
|
var recentRuns = [];
|
|
1574
1668
|
var MAX_RECENT_RUNS = 100;
|
|
1575
1669
|
var modelCounts = {};
|
|
1576
1670
|
var serverStartTime = 0;
|
|
1671
|
+
var currentConfig = loadConfig();
|
|
1577
1672
|
var DEFAULT_ENDPOINTS = {
|
|
1578
1673
|
anthropic: {
|
|
1579
1674
|
baseUrl: "https://api.anthropic.com/v1",
|
|
@@ -2424,33 +2519,44 @@ async function startProxy(config = {}) {
|
|
|
2424
2519
|
const confidence = getInferenceConfidence(promptText, taskType);
|
|
2425
2520
|
log(`Inferred task: ${taskType} (confidence: ${confidence.toFixed(2)})`);
|
|
2426
2521
|
if (routingMode !== "passthrough") {
|
|
2427
|
-
const
|
|
2428
|
-
if (
|
|
2429
|
-
const parsed = parsePreferredModel(
|
|
2522
|
+
const configStrategy = getStrategy(currentConfig, taskType);
|
|
2523
|
+
if (configStrategy) {
|
|
2524
|
+
const parsed = parsePreferredModel(configStrategy.model);
|
|
2430
2525
|
if (parsed) {
|
|
2431
2526
|
targetProvider = parsed.provider;
|
|
2432
2527
|
targetModel = parsed.model;
|
|
2433
|
-
log(`Using
|
|
2528
|
+
log(`Using config strategy: ${configStrategy.model}`);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
if (!configStrategy) {
|
|
2532
|
+
const rule = relay.routing.get(taskType);
|
|
2533
|
+
if (rule && rule.preferredModel) {
|
|
2534
|
+
const parsed = parsePreferredModel(rule.preferredModel);
|
|
2535
|
+
if (parsed) {
|
|
2536
|
+
targetProvider = parsed.provider;
|
|
2537
|
+
targetModel = parsed.model;
|
|
2538
|
+
log(`Using learned rule: ${rule.preferredModel}`);
|
|
2539
|
+
} else {
|
|
2540
|
+
const defaultRoute = DEFAULT_ROUTING[taskType];
|
|
2541
|
+
targetProvider = defaultRoute.provider;
|
|
2542
|
+
targetModel = defaultRoute.model;
|
|
2543
|
+
}
|
|
2434
2544
|
} else {
|
|
2435
2545
|
const defaultRoute = DEFAULT_ROUTING[taskType];
|
|
2436
2546
|
targetProvider = defaultRoute.provider;
|
|
2437
2547
|
targetModel = defaultRoute.model;
|
|
2438
2548
|
}
|
|
2439
|
-
} else {
|
|
2440
|
-
const defaultRoute = DEFAULT_ROUTING[taskType];
|
|
2441
|
-
targetProvider = defaultRoute.provider;
|
|
2442
|
-
targetModel = defaultRoute.model;
|
|
2443
2549
|
}
|
|
2444
2550
|
if (routingMode === "cost") {
|
|
2445
|
-
const
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
}
|
|
2551
|
+
const costModel = currentConfig.defaults?.costModel || "claude-3-5-haiku-latest";
|
|
2552
|
+
targetModel = costModel;
|
|
2553
|
+
targetProvider = "anthropic";
|
|
2554
|
+
log(`Cost mode: using ${costModel}`);
|
|
2450
2555
|
} else if (routingMode === "quality") {
|
|
2451
|
-
const qualityModel = process.env["RELAYPLANE_QUALITY_MODEL"] || "claude-sonnet-4-20250514";
|
|
2556
|
+
const qualityModel = currentConfig.defaults?.qualityModel || process.env["RELAYPLANE_QUALITY_MODEL"] || "claude-sonnet-4-20250514";
|
|
2452
2557
|
targetModel = qualityModel;
|
|
2453
2558
|
targetProvider = "anthropic";
|
|
2559
|
+
log(`Quality mode: using ${qualityModel}`);
|
|
2454
2560
|
}
|
|
2455
2561
|
}
|
|
2456
2562
|
log(`Routing to: ${targetProvider}/${targetModel}`);
|
|
@@ -2497,6 +2603,10 @@ async function startProxy(config = {}) {
|
|
|
2497
2603
|
);
|
|
2498
2604
|
}
|
|
2499
2605
|
});
|
|
2606
|
+
watchConfig((newConfig) => {
|
|
2607
|
+
currentConfig = newConfig;
|
|
2608
|
+
console.log("[relayplane] Config reloaded");
|
|
2609
|
+
});
|
|
2500
2610
|
return new Promise((resolve, reject) => {
|
|
2501
2611
|
server.on("error", reject);
|
|
2502
2612
|
server.listen(port, host, () => {
|
|
@@ -2505,6 +2615,7 @@ async function startProxy(config = {}) {
|
|
|
2505
2615
|
console.log(` Models: relayplane:auto, relayplane:cost, relayplane:quality`);
|
|
2506
2616
|
console.log(` Endpoint: POST /v1/chat/completions`);
|
|
2507
2617
|
console.log(` Stats: GET /stats, /runs, /health`);
|
|
2618
|
+
console.log(` Config: ~/.relayplane/config.json (hot-reload enabled)`);
|
|
2508
2619
|
console.log(` Streaming: \u2705 Enabled`);
|
|
2509
2620
|
resolve(server);
|
|
2510
2621
|
});
|