@relayplane/proxy 0.1.8 → 0.1.9
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 +49 -0
- package/dist/cli.js +60 -16
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +60 -16
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +60 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -248,6 +248,55 @@ curl "http://localhost:3001/runs?limit=10"
|
|
|
248
248
|
}
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
+
## Configuration
|
|
252
|
+
|
|
253
|
+
RelayPlane creates a config file on first run at `~/.relayplane/config.json`:
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"strategies": {
|
|
258
|
+
"code_review": { "model": "anthropic:claude-sonnet-4-20250514" },
|
|
259
|
+
"code_generation": { "model": "anthropic:claude-3-5-haiku-latest" },
|
|
260
|
+
"analysis": { "model": "anthropic:claude-sonnet-4-20250514" },
|
|
261
|
+
"summarization": { "model": "anthropic:claude-3-5-haiku-latest" },
|
|
262
|
+
"creative_writing": { "model": "anthropic:claude-sonnet-4-20250514" },
|
|
263
|
+
"data_extraction": { "model": "anthropic:claude-3-5-haiku-latest" },
|
|
264
|
+
"translation": { "model": "anthropic:claude-3-5-haiku-latest" },
|
|
265
|
+
"question_answering": { "model": "anthropic:claude-3-5-haiku-latest" },
|
|
266
|
+
"general": { "model": "anthropic:claude-3-5-haiku-latest" }
|
|
267
|
+
},
|
|
268
|
+
"defaults": {
|
|
269
|
+
"qualityModel": "claude-sonnet-4-20250514",
|
|
270
|
+
"costModel": "claude-3-5-haiku-latest"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Edit and save — changes apply instantly** (hot-reload, no restart needed).
|
|
276
|
+
|
|
277
|
+
### Strategy Options
|
|
278
|
+
|
|
279
|
+
| Field | Description |
|
|
280
|
+
|-------|-------------|
|
|
281
|
+
| `model` | Provider and model in format `provider:model` |
|
|
282
|
+
| `minConfidence` | Optional. Only use this strategy if confidence >= threshold |
|
|
283
|
+
| `fallback` | Optional. Fallback model if primary fails |
|
|
284
|
+
|
|
285
|
+
### Examples
|
|
286
|
+
|
|
287
|
+
Route all analysis tasks to GPT-4o:
|
|
288
|
+
```json
|
|
289
|
+
"analysis": { "model": "openai:gpt-4o" }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Use Opus for code review with fallback:
|
|
293
|
+
```json
|
|
294
|
+
"code_review": {
|
|
295
|
+
"model": "anthropic:claude-opus-4-5-20250514",
|
|
296
|
+
"fallback": "anthropic:claude-sonnet-4-20250514"
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
251
300
|
## Data Storage
|
|
252
301
|
|
|
253
302
|
All data stored locally at `~/.relayplane/data.db` (SQLite).
|
package/dist/cli.js
CHANGED
|
@@ -1601,12 +1601,19 @@ var StrategySchema = import_zod.z.object({
|
|
|
1601
1601
|
minConfidence: import_zod.z.number().min(0).max(1).optional(),
|
|
1602
1602
|
fallback: import_zod.z.string().optional()
|
|
1603
1603
|
});
|
|
1604
|
+
var AuthSchema = import_zod.z.object({
|
|
1605
|
+
anthropicApiKey: import_zod.z.string().optional(),
|
|
1606
|
+
anthropicMaxToken: import_zod.z.string().optional(),
|
|
1607
|
+
useMaxForModels: import_zod.z.array(import_zod.z.string()).optional()
|
|
1608
|
+
// Default: ['opus']
|
|
1609
|
+
}).optional();
|
|
1604
1610
|
var ConfigSchema = import_zod.z.object({
|
|
1605
1611
|
strategies: import_zod.z.record(import_zod.z.string(), StrategySchema).optional(),
|
|
1606
1612
|
defaults: import_zod.z.object({
|
|
1607
1613
|
qualityModel: import_zod.z.string().optional(),
|
|
1608
1614
|
costModel: import_zod.z.string().optional()
|
|
1609
|
-
}).optional()
|
|
1615
|
+
}).optional(),
|
|
1616
|
+
auth: AuthSchema
|
|
1610
1617
|
});
|
|
1611
1618
|
var DEFAULT_CONFIG = {
|
|
1612
1619
|
strategies: {
|
|
@@ -1666,6 +1673,19 @@ function loadConfig() {
|
|
|
1666
1673
|
function getStrategy(config, taskType) {
|
|
1667
1674
|
return config.strategies?.[taskType] ?? null;
|
|
1668
1675
|
}
|
|
1676
|
+
function getAnthropicAuth(config, model) {
|
|
1677
|
+
const auth = config.auth;
|
|
1678
|
+
const useMaxForModels = auth?.useMaxForModels ?? ["opus"];
|
|
1679
|
+
const shouldUseMax = useMaxForModels.some((m) => model.toLowerCase().includes(m.toLowerCase()));
|
|
1680
|
+
if (shouldUseMax && auth?.anthropicMaxToken) {
|
|
1681
|
+
return { type: "max", value: auth.anthropicMaxToken };
|
|
1682
|
+
}
|
|
1683
|
+
const apiKey = auth?.anthropicApiKey ?? process.env["ANTHROPIC_API_KEY"];
|
|
1684
|
+
if (apiKey) {
|
|
1685
|
+
return { type: "apiKey", value: apiKey };
|
|
1686
|
+
}
|
|
1687
|
+
return null;
|
|
1688
|
+
}
|
|
1669
1689
|
function watchConfig(onChange) {
|
|
1670
1690
|
const configPath = getConfigPath();
|
|
1671
1691
|
const dir = path2.dirname(configPath);
|
|
@@ -1686,7 +1706,7 @@ function watchConfig(onChange) {
|
|
|
1686
1706
|
}
|
|
1687
1707
|
|
|
1688
1708
|
// src/proxy.ts
|
|
1689
|
-
var VERSION = "0.1.
|
|
1709
|
+
var VERSION = "0.1.9";
|
|
1690
1710
|
var recentRuns = [];
|
|
1691
1711
|
var MAX_RECENT_RUNS = 100;
|
|
1692
1712
|
var modelCounts = {};
|
|
@@ -1754,13 +1774,17 @@ function extractPromptText(messages) {
|
|
|
1754
1774
|
return "";
|
|
1755
1775
|
}).join("\n");
|
|
1756
1776
|
}
|
|
1757
|
-
async function forwardToAnthropic(request, targetModel,
|
|
1777
|
+
async function forwardToAnthropic(request, targetModel, auth, betaHeaders) {
|
|
1758
1778
|
const anthropicBody = buildAnthropicBody(request, targetModel, false);
|
|
1759
1779
|
const headers = {
|
|
1760
1780
|
"Content-Type": "application/json",
|
|
1761
|
-
"x-api-key": apiKey,
|
|
1762
1781
|
"anthropic-version": "2023-06-01"
|
|
1763
1782
|
};
|
|
1783
|
+
if (auth.type === "max") {
|
|
1784
|
+
headers["Authorization"] = `Bearer ${auth.value}`;
|
|
1785
|
+
} else {
|
|
1786
|
+
headers["x-api-key"] = auth.value;
|
|
1787
|
+
}
|
|
1764
1788
|
if (betaHeaders) {
|
|
1765
1789
|
headers["anthropic-beta"] = betaHeaders;
|
|
1766
1790
|
}
|
|
@@ -1771,13 +1795,17 @@ async function forwardToAnthropic(request, targetModel, apiKey, betaHeaders) {
|
|
|
1771
1795
|
});
|
|
1772
1796
|
return response;
|
|
1773
1797
|
}
|
|
1774
|
-
async function forwardToAnthropicStream(request, targetModel,
|
|
1798
|
+
async function forwardToAnthropicStream(request, targetModel, auth, betaHeaders) {
|
|
1775
1799
|
const anthropicBody = buildAnthropicBody(request, targetModel, true);
|
|
1776
1800
|
const headers = {
|
|
1777
1801
|
"Content-Type": "application/json",
|
|
1778
|
-
"x-api-key": apiKey,
|
|
1779
1802
|
"anthropic-version": "2023-06-01"
|
|
1780
1803
|
};
|
|
1804
|
+
if (auth.type === "max") {
|
|
1805
|
+
headers["Authorization"] = `Bearer ${auth.value}`;
|
|
1806
|
+
} else {
|
|
1807
|
+
headers["x-api-key"] = auth.value;
|
|
1808
|
+
}
|
|
1781
1809
|
if (betaHeaders) {
|
|
1782
1810
|
headers["anthropic-beta"] = betaHeaders;
|
|
1783
1811
|
}
|
|
@@ -2583,12 +2611,24 @@ async function startProxy(config = {}) {
|
|
|
2583
2611
|
}
|
|
2584
2612
|
}
|
|
2585
2613
|
log(`Routing to: ${targetProvider}/${targetModel}`);
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
if (
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2614
|
+
let apiKey;
|
|
2615
|
+
let anthropicAuth = null;
|
|
2616
|
+
if (targetProvider === "anthropic") {
|
|
2617
|
+
anthropicAuth = getAnthropicAuth(currentConfig, targetModel);
|
|
2618
|
+
if (!anthropicAuth) {
|
|
2619
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2620
|
+
res.end(JSON.stringify({ error: "No Anthropic auth configured (set ANTHROPIC_API_KEY or config.auth.anthropicMaxToken)" }));
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
log(`Using ${anthropicAuth.type === "max" ? "MAX token" : "API key"} auth for ${targetModel}`);
|
|
2624
|
+
} else {
|
|
2625
|
+
const apiKeyEnv = DEFAULT_ENDPOINTS[targetProvider]?.apiKeyEnv ?? `${targetProvider.toUpperCase()}_API_KEY`;
|
|
2626
|
+
apiKey = process.env[apiKeyEnv];
|
|
2627
|
+
if (!apiKey) {
|
|
2628
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2629
|
+
res.end(JSON.stringify({ error: `Missing ${apiKeyEnv} environment variable` }));
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2592
2632
|
}
|
|
2593
2633
|
const startTime = Date.now();
|
|
2594
2634
|
const betaHeaders = req.headers["anthropic-beta"];
|
|
@@ -2599,6 +2639,7 @@ async function startProxy(config = {}) {
|
|
|
2599
2639
|
targetProvider,
|
|
2600
2640
|
targetModel,
|
|
2601
2641
|
apiKey,
|
|
2642
|
+
anthropicAuth,
|
|
2602
2643
|
relay,
|
|
2603
2644
|
promptText,
|
|
2604
2645
|
taskType,
|
|
@@ -2615,6 +2656,7 @@ async function startProxy(config = {}) {
|
|
|
2615
2656
|
targetProvider,
|
|
2616
2657
|
targetModel,
|
|
2617
2658
|
apiKey,
|
|
2659
|
+
anthropicAuth,
|
|
2618
2660
|
relay,
|
|
2619
2661
|
promptText,
|
|
2620
2662
|
taskType,
|
|
@@ -2644,12 +2686,13 @@ async function startProxy(config = {}) {
|
|
|
2644
2686
|
});
|
|
2645
2687
|
});
|
|
2646
2688
|
}
|
|
2647
|
-
async function handleStreamingRequest(res, request, targetProvider, targetModel, apiKey, relay, promptText, taskType, confidence, routingMode, startTime, log, betaHeaders) {
|
|
2689
|
+
async function handleStreamingRequest(res, request, targetProvider, targetModel, apiKey, anthropicAuth, relay, promptText, taskType, confidence, routingMode, startTime, log, betaHeaders) {
|
|
2648
2690
|
let providerResponse;
|
|
2649
2691
|
try {
|
|
2650
2692
|
switch (targetProvider) {
|
|
2651
2693
|
case "anthropic":
|
|
2652
|
-
|
|
2694
|
+
if (!anthropicAuth) throw new Error("No Anthropic auth");
|
|
2695
|
+
providerResponse = await forwardToAnthropicStream(request, targetModel, anthropicAuth, betaHeaders);
|
|
2653
2696
|
break;
|
|
2654
2697
|
case "google":
|
|
2655
2698
|
providerResponse = await forwardToGeminiStream(request, targetModel, apiKey);
|
|
@@ -2727,13 +2770,14 @@ async function handleStreamingRequest(res, request, targetProvider, targetModel,
|
|
|
2727
2770
|
});
|
|
2728
2771
|
res.end();
|
|
2729
2772
|
}
|
|
2730
|
-
async function handleNonStreamingRequest(res, request, targetProvider, targetModel, apiKey, relay, promptText, taskType, confidence, routingMode, startTime, log, betaHeaders) {
|
|
2773
|
+
async function handleNonStreamingRequest(res, request, targetProvider, targetModel, apiKey, anthropicAuth, relay, promptText, taskType, confidence, routingMode, startTime, log, betaHeaders) {
|
|
2731
2774
|
let providerResponse;
|
|
2732
2775
|
let responseData;
|
|
2733
2776
|
try {
|
|
2734
2777
|
switch (targetProvider) {
|
|
2735
2778
|
case "anthropic": {
|
|
2736
|
-
|
|
2779
|
+
if (!anthropicAuth) throw new Error("No Anthropic auth");
|
|
2780
|
+
providerResponse = await forwardToAnthropic(request, targetModel, anthropicAuth, betaHeaders);
|
|
2737
2781
|
const rawData = await providerResponse.json();
|
|
2738
2782
|
if (!providerResponse.ok) {
|
|
2739
2783
|
res.writeHead(providerResponse.status, { "Content-Type": "application/json" });
|