@chatpanel/gateway 0.1.3 → 0.1.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/ner.js +56 -51
- package/src/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/gateway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Local privacy gateway — redacts PII out of OpenAI/Anthropic API traffic before it reaches a model, then restores it in the reply. Point opencode, codex, aider, Claude Code, etc. at it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/ner.js
CHANGED
|
@@ -14,78 +14,83 @@ const NER_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'ner');
|
|
|
14
14
|
|
|
15
15
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Probe the ACTUAL /ner contract (POST {text} -> {entities}), not /health: many
|
|
18
|
+
// NER servers (incl. a user's own) expose only /ner. If anything answers here, we
|
|
19
|
+
// can use it as the detector.
|
|
20
|
+
async function nerReachable(port, signal) {
|
|
18
21
|
try {
|
|
19
|
-
const res = await fetch(`http://127.0.0.1:${port}/
|
|
22
|
+
const res = await fetch(`http://127.0.0.1:${port}/ner`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'content-type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ text: 'ping' }),
|
|
26
|
+
signal,
|
|
27
|
+
});
|
|
20
28
|
return res.ok;
|
|
21
29
|
} catch {
|
|
22
30
|
return false;
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
// Launch + supervise the NER server
|
|
27
|
-
// Mutates cfg.redaction once
|
|
34
|
+
// Launch + supervise the NER server (or adopt one already on the port). Returns
|
|
35
|
+
// { stop() } or null if not started. Mutates cfg.redaction once NER answers.
|
|
28
36
|
export function startNer(cfg) {
|
|
29
37
|
const n = cfg.ner;
|
|
30
38
|
if (!n || !n.autostart) return null;
|
|
31
39
|
|
|
32
|
-
// Respect an explicitly-configured detector — don't
|
|
40
|
+
// Respect an explicitly-configured detector — don't touch it.
|
|
33
41
|
const det = cfg.redaction?.detection;
|
|
34
42
|
if (det && det.backend && det.backend !== 'off') {
|
|
35
|
-
console.log(`[ner] detection already configured (${det.backend}) —
|
|
43
|
+
console.log(`[ner] detection already configured (${det.backend}) — leaving it as-is`);
|
|
36
44
|
return null;
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
const port = n.port || 9009;
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
cwd: NER_DIR,
|
|
44
|
-
env: { ...process.env, PORT: String(port) },
|
|
45
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
46
|
-
});
|
|
47
|
-
} catch (e) {
|
|
48
|
-
console.log(`[ner] could not launch bundled NER (${e.message}) — deterministic redaction only`);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
48
|
+
let stopped = false;
|
|
49
|
+
let child = null;
|
|
50
|
+
const ac = new AbortController();
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log('[ner] first run: creating venv + installing spaCy (one-time, may take a minute)…');
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
child.stderr?.on('data', () => { /* uvicorn logs to stderr; swallow */ });
|
|
61
|
-
child.on('error', (e) => {
|
|
62
|
-
console.log(`[ner] failed to start (${e.message}). Is python3 installed? Falling back to deterministic redaction.`);
|
|
63
|
-
});
|
|
64
|
-
child.on('exit', (code) => {
|
|
65
|
-
if (code && code !== 0 && !stopped) {
|
|
66
|
-
console.log(`[ner] server exited (code ${code}); redaction continues deterministic-only.`);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
52
|
+
const wire = (how) => {
|
|
53
|
+
cfg.redaction.detection = { backend: 'endpoint', url: `http://127.0.0.1:${port}/ner`, timeoutMs: 1500, maxChars: 8000 };
|
|
54
|
+
if (n.enableFullTier && cfg.redaction.tier !== 'full') cfg.redaction.tier = 'full';
|
|
55
|
+
console.log(`[ner] ${how} on http://127.0.0.1:${port}/ner — entity detection active (tier: ${cfg.redaction.tier})`);
|
|
56
|
+
};
|
|
69
57
|
|
|
70
|
-
// Poll for readiness without blocking server start; wire detection when up.
|
|
71
|
-
// `stopped` MUST be declared before the async poller below references it (else a
|
|
72
|
-
// temporal-dead-zone ReferenceError crashes startup when autostart is on).
|
|
73
|
-
const ac = new AbortController();
|
|
74
|
-
let stopped = false;
|
|
75
58
|
(async () => {
|
|
59
|
+
// 1) Adopt an existing NER already serving on the port (e.g. the user's own).
|
|
60
|
+
if (await nerReachable(port, ac.signal)) { wire('using existing NER'); return; }
|
|
61
|
+
|
|
62
|
+
// 2) Otherwise launch the bundled spaCy server.
|
|
63
|
+
try {
|
|
64
|
+
child = spawn('bash', ['run.sh'], {
|
|
65
|
+
cwd: NER_DIR,
|
|
66
|
+
env: { ...process.env, PORT: String(port) },
|
|
67
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
68
|
+
});
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.log(`[ner] could not launch bundled NER (${e.message}) — deterministic redaction only`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let firstRun = true;
|
|
74
|
+
child.stdout?.on('data', (b) => {
|
|
75
|
+
if (firstRun && /installing dependencies/i.test(b.toString())) {
|
|
76
|
+
firstRun = false;
|
|
77
|
+
console.log('[ner] first run: creating venv + installing spaCy (one-time, may take a minute)…');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
child.stderr?.on('data', () => { /* uvicorn logs to stderr; swallow */ });
|
|
81
|
+
child.on('error', (e) => console.log(`[ner] failed to start (${e.message}). Is python3 installed? Falling back to deterministic redaction.`));
|
|
82
|
+
child.on('exit', (code) => {
|
|
83
|
+
if (code && code !== 0 && !stopped) {
|
|
84
|
+
// The bundled one couldn't bind (often the port is taken by another NER).
|
|
85
|
+
// If SOMETHING answers /ner there, adopt it instead of giving up.
|
|
86
|
+
nerReachable(port, ac.signal).then((ok) => { if (ok && !stopped) wire('adopted NER'); else if (!stopped) console.log(`[ner] server exited (code ${code}); deterministic-only.`); });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 3) Poll for readiness; wire detection when up.
|
|
76
91
|
const deadline = Date.now() + 120_000; // generous: first run installs deps
|
|
77
92
|
while (Date.now() < deadline && !stopped) {
|
|
78
|
-
if (await
|
|
79
|
-
cfg.redaction.detection = {
|
|
80
|
-
backend: 'endpoint',
|
|
81
|
-
url: `http://127.0.0.1:${port}/ner`,
|
|
82
|
-
timeoutMs: 1500,
|
|
83
|
-
maxChars: 8000,
|
|
84
|
-
};
|
|
85
|
-
if (n.enableFullTier && cfg.redaction.tier !== 'full') cfg.redaction.tier = 'full';
|
|
86
|
-
console.log(`[ner] ready on http://127.0.0.1:${port}/ner — entity redaction active (tier: ${cfg.redaction.tier})`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
93
|
+
if (await nerReachable(port, ac.signal)) { wire('ready'); return; }
|
|
89
94
|
await sleep(1000);
|
|
90
95
|
}
|
|
91
96
|
if (!stopped) console.log('[ner] not ready after 120s — continuing deterministic-only (run ./ner/run.sh manually to debug).');
|
|
@@ -95,7 +100,7 @@ export function startNer(cfg) {
|
|
|
95
100
|
if (stopped) return;
|
|
96
101
|
stopped = true;
|
|
97
102
|
ac.abort();
|
|
98
|
-
try { child
|
|
103
|
+
try { child?.kill('SIGTERM'); } catch { /* ignore */ }
|
|
99
104
|
};
|
|
100
105
|
return { stop };
|
|
101
106
|
}
|
package/src/server.js
CHANGED
|
@@ -31,7 +31,7 @@ import * as openai from './openai.js';
|
|
|
31
31
|
import * as responses from './responses.js';
|
|
32
32
|
import * as anthropic from './anthropic.js';
|
|
33
33
|
|
|
34
|
-
export const VERSION = '0.1.
|
|
34
|
+
export const VERSION = '0.1.4';
|
|
35
35
|
|
|
36
36
|
const KNOWN_AGENTS = new Set(['codex', 'claude', 'opencode', 'pi', 'kiro', 'antigravity']);
|
|
37
37
|
|