solrouter 0.3.3 → 0.3.4
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/package.json +1 -1
- package/src/providers.js +14 -0
- package/src/server.js +24 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solrouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Local privacy router for coding tools: pseudonymises every request before it leaves your machine, and sends work a frontier model would refuse to an uncensored open-weight model you control.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/providers.js
CHANGED
|
@@ -107,6 +107,20 @@ export async function callSolrouterAgent(leg, prompt) {
|
|
|
107
107
|
return { ok: res.ok && json?.success !== false, status: res.status, reply: json?.reply ?? "", raw: json };
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Transparent passthrough to the frontier for endpoints we don't transform —
|
|
112
|
+
* e.g. Claude Code's /v1/messages/count_tokens preflight. Returns the raw body so
|
|
113
|
+
* the response is relayed verbatim.
|
|
114
|
+
*/
|
|
115
|
+
export async function forward(leg, { method = "POST", path, body, clientHeaders }) {
|
|
116
|
+
const base = leg.baseUrl.replace(/\/$/, "");
|
|
117
|
+
const init = { method, headers: authHeaders(leg, clientHeaders) };
|
|
118
|
+
if (body !== undefined) init.body = JSON.stringify(withModel(body, leg));
|
|
119
|
+
const res = await fetch(base + path, init);
|
|
120
|
+
const text = await res.text();
|
|
121
|
+
return { ok: res.ok, status: res.status, text, contentType: res.headers.get("content-type") };
|
|
122
|
+
}
|
|
123
|
+
|
|
110
124
|
/** Streaming call. Returns { ok, status, stream } (raw SSE ReadableStream). */
|
|
111
125
|
export async function streamLeg(leg, body, clientHeaders) {
|
|
112
126
|
const res = await fetch(endpoint(leg), {
|
package/src/server.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// localhost only — nothing here should be reachable off the machine.
|
|
8
8
|
|
|
9
9
|
import { createServer } from "node:http";
|
|
10
|
-
import { handle, handleStream } from "./index.js";
|
|
10
|
+
import { handle, handleStream, createMasker } from "./index.js";
|
|
11
|
+
import { forward } from "./providers.js";
|
|
12
|
+
import { dialect, rewriteText } from "./wire.js";
|
|
11
13
|
import { writeReceipt } from "./receipt.js";
|
|
12
14
|
|
|
13
15
|
function readBody(req) {
|
|
@@ -26,6 +28,27 @@ export function startServer(cfg) {
|
|
|
26
28
|
const send = (code, obj) => { res.writeHead(code, { "content-type": "application/json" }); res.end(JSON.stringify(obj)); };
|
|
27
29
|
|
|
28
30
|
if (req.method === "GET" && req.url === "/health") return send(200, { ok: true, proxy: "solrouter", mode: cfg.mode });
|
|
31
|
+
|
|
32
|
+
// Transparent passthrough for other Anthropic endpoints the client probes —
|
|
33
|
+
// notably /v1/messages/count_tokens, which Claude Code calls before every
|
|
34
|
+
// request and treats a 404 on as "model unavailable". Forward to the frontier.
|
|
35
|
+
if (/^\/v1\//.test(req.url) && !/^\/(v1\/chat\/completions|v1\/messages)$/.test(req.url)) {
|
|
36
|
+
let body;
|
|
37
|
+
if (req.method === "POST") {
|
|
38
|
+
try { body = await readBody(req); } catch { return send(400, { error: "invalid JSON body" }); }
|
|
39
|
+
if (body && cfg.masking?.enabled !== false) {
|
|
40
|
+
const m = createMasker(cfg.masking);
|
|
41
|
+
body = rewriteText(body, dialect(body, req.url), s => m.mask(s).text);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const r = await forward(cfg.frontier, { method: req.method, path: req.url, body, clientHeaders: lower(req.headers) });
|
|
46
|
+
res.writeHead(r.status, { "content-type": r.contentType || "application/json" });
|
|
47
|
+
res.end(r.text);
|
|
48
|
+
} catch (e) { send(502, { error: "proxy error", detail: String(e?.message || e) }); }
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
if (req.method !== "POST" || !/^\/(v1\/chat\/completions|v1\/messages)$/.test(req.url)) {
|
|
30
53
|
return send(404, { error: "not found" });
|
|
31
54
|
}
|