@chatpanel/gateway 0.6.58 → 0.6.59
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/server.js +4 -2
- package/src/stt-engine.js +15 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/gateway",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.59",
|
|
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/server.js
CHANGED
|
@@ -54,7 +54,7 @@ import * as openai from './openai.js';
|
|
|
54
54
|
import * as responses from './responses.js';
|
|
55
55
|
import * as anthropic from './anthropic.js';
|
|
56
56
|
|
|
57
|
-
export const VERSION = '0.6.
|
|
57
|
+
export const VERSION = '0.6.59';
|
|
58
58
|
|
|
59
59
|
// WARM search tier — SQLite + FTS5 record store (falls back to an encrypted-JSON
|
|
60
60
|
// store if SQLite can't load), fed by the extension's ingest sync + backup-ingest.
|
|
@@ -1389,7 +1389,9 @@ export function createGateway(cfg = loadConfig()) {
|
|
|
1389
1389
|
// same composable model as everything else: any stage, with or without).
|
|
1390
1390
|
// `diarize: true` (+ optional `speakerLabel` to pin the mic channel to a
|
|
1391
1391
|
// name) attaches a speaker to each final.
|
|
1392
|
-
|
|
1392
|
+
// `endSilenceMs` (additive) lets a voice conversation wait longer for a
|
|
1393
|
+
// sentence to finish than dictation into a text box needs to.
|
|
1394
|
+
const { id } = sttEngine.createSession({ lang: body?.lang, redact: body?.redact === true, diarize: wantDiarize, speakerLabel: body?.speakerLabel, endSilenceMs: body?.endSilenceMs });
|
|
1393
1395
|
return sendJson(res, 201, { id, state: sttEngine.state() });
|
|
1394
1396
|
} catch (e) {
|
|
1395
1397
|
return sendJson(res, e.code === 'too_many_sessions' ? 429 : 500, { error: { message: e.message, type: e.code || 'stt_error' } });
|
package/src/stt-engine.js
CHANGED
|
@@ -238,13 +238,26 @@ let _decodeChain = Promise.resolve(); // whisper is effectively single-threaded
|
|
|
238
238
|
export function sessionCount() { return _sessions.size; }
|
|
239
239
|
|
|
240
240
|
/** @param {{ lang?: string, redact?: boolean, diarize?: boolean, speakerLabel?: string }} [opts] */
|
|
241
|
-
|
|
241
|
+
// How long a pause commits a segment. The default suits dictation into a text box,
|
|
242
|
+
// where a final just appends and a short pause costs nothing. In a VOICE
|
|
243
|
+
// conversation every final is SENT as a question, so a 700ms pause mid-thought
|
|
244
|
+
// ("I want to… um… go to Google") sends half a sentence. Callers may ask for more.
|
|
245
|
+
const END_SILENCE_MIN_MS = 300;
|
|
246
|
+
const END_SILENCE_MAX_MS = 3000;
|
|
247
|
+
export function clampEndSilence(ms) {
|
|
248
|
+
const n = Number(ms);
|
|
249
|
+
if (!Number.isFinite(n)) return SILENCE_FINAL_MS;
|
|
250
|
+
return Math.min(END_SILENCE_MAX_MS, Math.max(END_SILENCE_MIN_MS, Math.round(n)));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function createSession({ lang, redact = false, diarize: diarizeOpt = false, speakerLabel = null, endSilenceMs = SILENCE_FINAL_MS } = {}) {
|
|
242
254
|
if (_sessions.size >= MAX_SESSIONS) {
|
|
243
255
|
const e = /** @type {Error & { code?: string }} */ (new Error('too many concurrent dictation sessions'));
|
|
244
256
|
e.code = 'too_many_sessions'; throw e;
|
|
245
257
|
}
|
|
246
258
|
const s = {
|
|
247
259
|
id: randomUUID(),
|
|
260
|
+
endSilenceMs: clampEndSilence(endSilenceMs),
|
|
248
261
|
lang: typeof lang === 'string' && lang ? lang.slice(0, 12) : null,
|
|
249
262
|
langTried: false, // language auto-detect runs once per session (multilingual models)
|
|
250
263
|
// Opaque to this engine: the server applies the redaction hop to finals when
|
|
@@ -374,7 +387,7 @@ async function decodeSession(s, { flush = false } = {}) {
|
|
|
374
387
|
s.lastDecodeAt = Date.now();
|
|
375
388
|
|
|
376
389
|
const audio = concatBuffer(s);
|
|
377
|
-
const tail = Math.round((SILENCE_FINAL_MS / 1000) * SAMPLE_RATE);
|
|
390
|
+
const tail = Math.round(((s.endSilenceMs || SILENCE_FINAL_MS) / 1000) * SAMPLE_RATE);
|
|
378
391
|
const trailingQuiet = audio.length > tail && rms(audio, audio.length - tail) < SILENCE_RMS;
|
|
379
392
|
|
|
380
393
|
// Nothing but room tone? Don't decode (whisper hallucinates on silence) and
|