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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solrouter",
3
- "version": "0.3.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
  }