@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/dist/index.mjs CHANGED
@@ -1570,12 +1570,107 @@ ${input.prompt}` : input.prompt;
1570
1570
  }
1571
1571
  };
1572
1572
 
1573
+ // src/config.ts
1574
+ import * as fs2 from "fs";
1575
+ import * as path2 from "path";
1576
+ import * as os2 from "os";
1577
+ import { z } from "zod";
1578
+ var StrategySchema = z.object({
1579
+ model: z.string(),
1580
+ minConfidence: z.number().min(0).max(1).optional(),
1581
+ fallback: z.string().optional()
1582
+ });
1583
+ var ConfigSchema = z.object({
1584
+ strategies: z.record(z.string(), StrategySchema).optional(),
1585
+ defaults: z.object({
1586
+ qualityModel: z.string().optional(),
1587
+ costModel: z.string().optional()
1588
+ }).optional()
1589
+ });
1590
+ var DEFAULT_CONFIG = {
1591
+ strategies: {
1592
+ code_review: { model: "anthropic:claude-sonnet-4-20250514" },
1593
+ code_generation: { model: "anthropic:claude-3-5-haiku-latest" },
1594
+ analysis: { model: "anthropic:claude-sonnet-4-20250514" },
1595
+ summarization: { model: "anthropic:claude-3-5-haiku-latest" },
1596
+ creative_writing: { model: "anthropic:claude-sonnet-4-20250514" },
1597
+ data_extraction: { model: "anthropic:claude-3-5-haiku-latest" },
1598
+ translation: { model: "anthropic:claude-3-5-haiku-latest" },
1599
+ question_answering: { model: "anthropic:claude-3-5-haiku-latest" },
1600
+ general: { model: "anthropic:claude-3-5-haiku-latest" }
1601
+ },
1602
+ defaults: {
1603
+ qualityModel: "claude-sonnet-4-20250514",
1604
+ costModel: "claude-3-5-haiku-latest"
1605
+ }
1606
+ };
1607
+ function getConfigPath() {
1608
+ return path2.join(os2.homedir(), ".relayplane", "config.json");
1609
+ }
1610
+ function writeDefaultConfig() {
1611
+ const configPath = getConfigPath();
1612
+ const dir = path2.dirname(configPath);
1613
+ if (!fs2.existsSync(dir)) {
1614
+ fs2.mkdirSync(dir, { recursive: true });
1615
+ }
1616
+ if (!fs2.existsSync(configPath)) {
1617
+ fs2.writeFileSync(
1618
+ configPath,
1619
+ JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n",
1620
+ "utf-8"
1621
+ );
1622
+ console.log(`[relayplane] Created default config at ${configPath}`);
1623
+ }
1624
+ }
1625
+ function loadConfig() {
1626
+ const configPath = getConfigPath();
1627
+ writeDefaultConfig();
1628
+ try {
1629
+ const raw = fs2.readFileSync(configPath, "utf-8");
1630
+ const parsed = JSON.parse(raw);
1631
+ const validated = ConfigSchema.parse(parsed);
1632
+ return validated;
1633
+ } catch (err) {
1634
+ if (err instanceof z.ZodError) {
1635
+ console.error(`[relayplane] Invalid config: ${err.message}`);
1636
+ } else if (err instanceof SyntaxError) {
1637
+ console.error(`[relayplane] Config JSON parse error: ${err.message}`);
1638
+ } else {
1639
+ console.error(`[relayplane] Failed to load config: ${err}`);
1640
+ }
1641
+ console.log("[relayplane] Using default config");
1642
+ return DEFAULT_CONFIG;
1643
+ }
1644
+ }
1645
+ function getStrategy(config, taskType) {
1646
+ return config.strategies?.[taskType] ?? null;
1647
+ }
1648
+ function watchConfig(onChange) {
1649
+ const configPath = getConfigPath();
1650
+ const dir = path2.dirname(configPath);
1651
+ if (!fs2.existsSync(dir)) {
1652
+ fs2.mkdirSync(dir, { recursive: true });
1653
+ }
1654
+ let debounceTimer = null;
1655
+ fs2.watch(dir, (eventType, filename) => {
1656
+ if (filename === "config.json") {
1657
+ if (debounceTimer) clearTimeout(debounceTimer);
1658
+ debounceTimer = setTimeout(() => {
1659
+ console.log("[relayplane] Config file changed, reloading...");
1660
+ const newConfig = loadConfig();
1661
+ onChange(newConfig);
1662
+ }, 100);
1663
+ }
1664
+ });
1665
+ }
1666
+
1573
1667
  // src/proxy.ts
1574
- var VERSION = "0.1.7";
1668
+ var VERSION = "0.1.8";
1575
1669
  var recentRuns = [];
1576
1670
  var MAX_RECENT_RUNS = 100;
1577
1671
  var modelCounts = {};
1578
1672
  var serverStartTime = 0;
1673
+ var currentConfig = loadConfig();
1579
1674
  var DEFAULT_ENDPOINTS = {
1580
1675
  anthropic: {
1581
1676
  baseUrl: "https://api.anthropic.com/v1",
@@ -2426,33 +2521,44 @@ async function startProxy(config = {}) {
2426
2521
  const confidence = getInferenceConfidence(promptText, taskType);
2427
2522
  log(`Inferred task: ${taskType} (confidence: ${confidence.toFixed(2)})`);
2428
2523
  if (routingMode !== "passthrough") {
2429
- const rule = relay.routing.get(taskType);
2430
- if (rule && rule.preferredModel) {
2431
- const parsed = parsePreferredModel(rule.preferredModel);
2524
+ const configStrategy = getStrategy(currentConfig, taskType);
2525
+ if (configStrategy) {
2526
+ const parsed = parsePreferredModel(configStrategy.model);
2432
2527
  if (parsed) {
2433
2528
  targetProvider = parsed.provider;
2434
2529
  targetModel = parsed.model;
2435
- log(`Using learned rule: ${rule.preferredModel}`);
2530
+ log(`Using config strategy: ${configStrategy.model}`);
2531
+ }
2532
+ }
2533
+ if (!configStrategy) {
2534
+ const rule = relay.routing.get(taskType);
2535
+ if (rule && rule.preferredModel) {
2536
+ const parsed = parsePreferredModel(rule.preferredModel);
2537
+ if (parsed) {
2538
+ targetProvider = parsed.provider;
2539
+ targetModel = parsed.model;
2540
+ log(`Using learned rule: ${rule.preferredModel}`);
2541
+ } else {
2542
+ const defaultRoute = DEFAULT_ROUTING[taskType];
2543
+ targetProvider = defaultRoute.provider;
2544
+ targetModel = defaultRoute.model;
2545
+ }
2436
2546
  } else {
2437
2547
  const defaultRoute = DEFAULT_ROUTING[taskType];
2438
2548
  targetProvider = defaultRoute.provider;
2439
2549
  targetModel = defaultRoute.model;
2440
2550
  }
2441
- } else {
2442
- const defaultRoute = DEFAULT_ROUTING[taskType];
2443
- targetProvider = defaultRoute.provider;
2444
- targetModel = defaultRoute.model;
2445
2551
  }
2446
2552
  if (routingMode === "cost") {
2447
- const simpleTasks = ["summarization", "data_extraction", "translation", "question_answering"];
2448
- if (simpleTasks.includes(taskType)) {
2449
- targetModel = "claude-3-5-haiku-latest";
2450
- targetProvider = "anthropic";
2451
- }
2553
+ const costModel = currentConfig.defaults?.costModel || "claude-3-5-haiku-latest";
2554
+ targetModel = costModel;
2555
+ targetProvider = "anthropic";
2556
+ log(`Cost mode: using ${costModel}`);
2452
2557
  } else if (routingMode === "quality") {
2453
- const qualityModel = process.env["RELAYPLANE_QUALITY_MODEL"] || "claude-sonnet-4-20250514";
2558
+ const qualityModel = currentConfig.defaults?.qualityModel || process.env["RELAYPLANE_QUALITY_MODEL"] || "claude-sonnet-4-20250514";
2454
2559
  targetModel = qualityModel;
2455
2560
  targetProvider = "anthropic";
2561
+ log(`Quality mode: using ${qualityModel}`);
2456
2562
  }
2457
2563
  }
2458
2564
  log(`Routing to: ${targetProvider}/${targetModel}`);
@@ -2499,6 +2605,10 @@ async function startProxy(config = {}) {
2499
2605
  );
2500
2606
  }
2501
2607
  });
2608
+ watchConfig((newConfig) => {
2609
+ currentConfig = newConfig;
2610
+ console.log("[relayplane] Config reloaded");
2611
+ });
2502
2612
  return new Promise((resolve, reject) => {
2503
2613
  server.on("error", reject);
2504
2614
  server.listen(port, host, () => {
@@ -2507,6 +2617,7 @@ async function startProxy(config = {}) {
2507
2617
  console.log(` Models: relayplane:auto, relayplane:cost, relayplane:quality`);
2508
2618
  console.log(` Endpoint: POST /v1/chat/completions`);
2509
2619
  console.log(` Stats: GET /stats, /runs, /health`);
2620
+ console.log(` Config: ~/.relayplane/config.json (hot-reload enabled)`);
2510
2621
  console.log(` Streaming: \u2705 Enabled`);
2511
2622
  resolve(server);
2512
2623
  });
@@ -2697,7 +2808,7 @@ async function handleNonStreamingRequest(res, request, targetProvider, targetMod
2697
2808
  }
2698
2809
 
2699
2810
  // src/types.ts
2700
- import { z } from "zod";
2811
+ import { z as z2 } from "zod";
2701
2812
  var TaskTypes = [
2702
2813
  "code_generation",
2703
2814
  "code_review",
@@ -2709,63 +2820,64 @@ var TaskTypes = [
2709
2820
  "question_answering",
2710
2821
  "general"
2711
2822
  ];
2712
- var TaskTypeSchema = z.enum(TaskTypes);
2823
+ var TaskTypeSchema = z2.enum(TaskTypes);
2713
2824
  var Providers = ["openai", "anthropic", "google", "xai", "moonshot", "local"];
2714
- var ProviderSchema = z.enum(Providers);
2715
- var RelayPlaneConfigSchema = z.object({
2716
- dbPath: z.string().optional(),
2717
- providers: z.record(ProviderSchema, z.object({
2718
- apiKey: z.string().optional(),
2719
- baseUrl: z.string().optional()
2825
+ var ProviderSchema = z2.enum(Providers);
2826
+ var RelayPlaneConfigSchema = z2.object({
2827
+ dbPath: z2.string().optional(),
2828
+ providers: z2.record(ProviderSchema, z2.object({
2829
+ apiKey: z2.string().optional(),
2830
+ baseUrl: z2.string().optional()
2720
2831
  })).optional(),
2721
2832
  defaultProvider: ProviderSchema.optional(),
2722
- defaultModel: z.string().optional()
2833
+ defaultModel: z2.string().optional()
2723
2834
  });
2724
- var RunInputSchema = z.object({
2725
- prompt: z.string().min(1),
2726
- systemPrompt: z.string().optional(),
2835
+ var RunInputSchema = z2.object({
2836
+ prompt: z2.string().min(1),
2837
+ systemPrompt: z2.string().optional(),
2727
2838
  taskType: TaskTypeSchema.optional(),
2728
- model: z.string().optional(),
2729
- metadata: z.record(z.unknown()).optional()
2839
+ model: z2.string().optional(),
2840
+ metadata: z2.record(z2.unknown()).optional()
2730
2841
  });
2731
2842
  var RuleSources = ["default", "user", "learned"];
2732
- var RoutingRuleSchema = z.object({
2733
- id: z.string(),
2843
+ var RoutingRuleSchema = z2.object({
2844
+ id: z2.string(),
2734
2845
  taskType: TaskTypeSchema,
2735
- preferredModel: z.string(),
2736
- source: z.enum(RuleSources),
2737
- confidence: z.number().min(0).max(1).optional(),
2738
- sampleCount: z.number().int().positive().optional(),
2739
- createdAt: z.string(),
2740
- updatedAt: z.string()
2846
+ preferredModel: z2.string(),
2847
+ source: z2.enum(RuleSources),
2848
+ confidence: z2.number().min(0).max(1).optional(),
2849
+ sampleCount: z2.number().int().positive().optional(),
2850
+ createdAt: z2.string(),
2851
+ updatedAt: z2.string()
2741
2852
  });
2742
2853
  var OutcomeQualities = ["excellent", "good", "acceptable", "poor", "failed"];
2743
- var OutcomeInputSchema = z.object({
2744
- runId: z.string().min(1),
2745
- success: z.boolean(),
2746
- quality: z.enum(OutcomeQualities).optional(),
2747
- latencySatisfactory: z.boolean().optional(),
2748
- costSatisfactory: z.boolean().optional(),
2749
- feedback: z.string().optional()
2854
+ var OutcomeInputSchema = z2.object({
2855
+ runId: z2.string().min(1),
2856
+ success: z2.boolean(),
2857
+ quality: z2.enum(OutcomeQualities).optional(),
2858
+ latencySatisfactory: z2.boolean().optional(),
2859
+ costSatisfactory: z2.boolean().optional(),
2860
+ feedback: z2.string().optional()
2750
2861
  });
2751
- var SuggestionSchema = z.object({
2752
- id: z.string(),
2862
+ var SuggestionSchema = z2.object({
2863
+ id: z2.string(),
2753
2864
  taskType: TaskTypeSchema,
2754
- currentModel: z.string(),
2755
- suggestedModel: z.string(),
2756
- reason: z.string(),
2757
- confidence: z.number().min(0).max(1),
2758
- expectedImprovement: z.object({
2759
- successRate: z.number().optional(),
2760
- latency: z.number().optional(),
2761
- cost: z.number().optional()
2865
+ currentModel: z2.string(),
2866
+ suggestedModel: z2.string(),
2867
+ reason: z2.string(),
2868
+ confidence: z2.number().min(0).max(1),
2869
+ expectedImprovement: z2.object({
2870
+ successRate: z2.number().optional(),
2871
+ latency: z2.number().optional(),
2872
+ cost: z2.number().optional()
2762
2873
  }),
2763
- sampleCount: z.number().int().positive(),
2764
- createdAt: z.string(),
2765
- accepted: z.boolean().optional(),
2766
- acceptedAt: z.string().optional()
2874
+ sampleCount: z2.number().int().positive(),
2875
+ createdAt: z2.string(),
2876
+ accepted: z2.boolean().optional(),
2877
+ acceptedAt: z2.string().optional()
2767
2878
  });
2768
2879
  export {
2880
+ DEFAULT_CONFIG,
2769
2881
  DEFAULT_ENDPOINTS,
2770
2882
  MODEL_MAPPING,
2771
2883
  MODEL_PRICING,
@@ -2780,9 +2892,13 @@ export {
2780
2892
  TaskTypes,
2781
2893
  calculateCost,
2782
2894
  calculateSavings,
2895
+ getConfigPath,
2783
2896
  getInferenceConfidence,
2784
2897
  getModelPricing,
2898
+ getStrategy,
2785
2899
  inferTaskType,
2786
- startProxy
2900
+ loadConfig,
2901
+ startProxy,
2902
+ watchConfig
2787
2903
  };
2788
2904
  //# sourceMappingURL=index.mjs.map