@chatpanel/gateway 0.6.27 → 0.6.28

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": "@chatpanel/gateway",
3
- "version": "0.6.27",
3
+ "version": "0.6.28",
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": {
@@ -99,9 +99,17 @@ export function download({ onLog } = {}) {
99
99
  return _initPromise;
100
100
  }
101
101
 
102
- // Embed a mono 16 kHz Float32 segment → Float32Array x-vector, or null if not ready.
103
- export async function embed(audio) {
104
- if (!isReady() || !(audio instanceof Float32Array) || !audio.length) return null;
102
+ const SR = 16000;
103
+
104
+ function l2norm(v) {
105
+ let n = 0; for (const x of v) n += x * x;
106
+ n = Math.sqrt(n) || 1;
107
+ const o = new Float32Array(v.length);
108
+ for (let i = 0; i < v.length; i++) o[i] = v[i] / n;
109
+ return o;
110
+ }
111
+
112
+ async function embedOne(audio) {
105
113
  try {
106
114
  const inputs = await _processor(audio);
107
115
  const out = await _model(inputs);
@@ -110,6 +118,29 @@ export async function embed(audio) {
110
118
  } catch { return null; }
111
119
  }
112
120
 
121
+ // Embed a mono 16 kHz Float32 segment → L2-normalized x-vector, or null if not
122
+ // ready. For longer turns we embed several overlapping ~2 s windows and AVERAGE
123
+ // the normalized vectors — a more stable speaker fingerprint than one embed of
124
+ // the whole (noisy) segment. Cheap on the native runtime (runs once per turn).
125
+ export async function embed(audio) {
126
+ if (!isReady() || !(audio instanceof Float32Array) || !audio.length) return null;
127
+ const win = 2 * SR;
128
+ const hop = Math.round(1.3 * SR);
129
+ if (audio.length <= Math.round(win * 1.4)) {
130
+ const v = await embedOne(audio);
131
+ return v ? l2norm(v) : null;
132
+ }
133
+ const vecs = [];
134
+ for (let s = 0; s + win <= audio.length; s += hop) {
135
+ const v = await embedOne(audio.subarray(s, s + win));
136
+ if (v) vecs.push(l2norm(v));
137
+ }
138
+ if (!vecs.length) { const v = await embedOne(audio); return v ? l2norm(v) : null; }
139
+ const avg = new Float32Array(vecs[0].length);
140
+ for (const v of vecs) for (let i = 0; i < avg.length; i++) avg[i] += v[i];
141
+ return l2norm(avg); // mean of unit vectors, renormalized
142
+ }
143
+
113
144
  export function _reset() { _state = 'off'; _model = null; _processor = null; _err = null; _initPromise = null; _progress = null; }
114
145
  export function _setForTest(model, processor) { _model = model; _processor = processor; _state = model ? 'ready' : 'off'; }
115
146
 
package/src/server.js CHANGED
@@ -43,7 +43,7 @@ import * as openai from './openai.js';
43
43
  import * as responses from './responses.js';
44
44
  import * as anthropic from './anthropic.js';
45
45
 
46
- export const VERSION = '0.6.27';
46
+ export const VERSION = '0.6.28';
47
47
 
48
48
  // WARM search tier — SQLite + FTS5 record store (falls back to an encrypted-JSON
49
49
  // store if SQLite can't load), fed by the extension's ingest sync + backup-ingest.