@juspay/neurolink 13.7.0 → 13.8.0
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/CHANGELOG.md +3 -3
- package/dist/browser/neurolink.min.js +480 -460
- package/dist/cli/commands/serve.js +11 -1
- package/dist/cli/commands/transcribe.js +39 -3
- package/dist/constants/langfuse.d.ts +2 -0
- package/dist/constants/langfuse.js +2 -0
- package/dist/factories/sttDescriptors.js +7 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/neurolink.js +33 -1
- package/dist/observability/exporters/langfuseExporter.d.ts +4 -0
- package/dist/observability/exporters/langfuseExporter.js +29 -8
- package/dist/observability/otlpEncoding.js +2 -2
- package/dist/server/abstract/baseServerAdapter.js +2 -0
- package/dist/server/adapters/expressAdapter.js +1 -1
- package/dist/server/adapters/fastifyAdapter.js +4 -1
- package/dist/server/adapters/honoAdapter.js +1 -1
- package/dist/server/adapters/koaAdapter.js +1 -1
- package/dist/server/routes/transcribeRoutes.d.ts +0 -8
- package/dist/server/routes/transcribeRoutes.js +130 -3
- package/dist/server/utils/validation.d.ts +409 -15
- package/dist/server/utils/validation.js +106 -4
- package/dist/server/websocket/frameQueue.d.ts +16 -0
- package/dist/server/websocket/frameQueue.js +55 -0
- package/dist/server/websocket/realtimeTranscription.d.ts +48 -0
- package/dist/server/websocket/realtimeTranscription.js +603 -0
- package/dist/server/websocket/transcribeWebSocket.d.ts +17 -1
- package/dist/server/websocket/transcribeWebSocket.js +263 -64
- package/dist/services/server/ai/observability/langfuseSetup.js +6 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/exporter.d.ts +3 -1
- package/dist/types/server.d.ts +304 -2
- package/dist/types/stt.d.ts +38 -3
- package/dist/types/transcribe.d.ts +401 -5
- package/dist/voice/audioLevel.d.ts +14 -0
- package/dist/voice/audioLevel.js +60 -0
- package/dist/voice/codeMix.d.ts +40 -0
- package/dist/voice/codeMix.js +282 -0
- package/dist/voice/correction/align.d.ts +12 -0
- package/dist/voice/correction/align.js +48 -0
- package/dist/voice/correction/guards.d.ts +39 -0
- package/dist/voice/correction/guards.js +122 -0
- package/dist/voice/correction/index.d.ts +25 -3
- package/dist/voice/correction/index.js +132 -4
- package/dist/voice/correction/rewrite.d.ts +64 -3
- package/dist/voice/correction/rewrite.js +295 -25
- package/dist/voice/eval/sttEvalLangfuse.d.ts +14 -0
- package/dist/voice/eval/sttEvalLangfuse.js +182 -0
- package/dist/voice/eval/sttEvalStore.d.ts +27 -0
- package/dist/voice/eval/sttEvalStore.js +397 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/languages.d.ts +34 -0
- package/dist/voice/languages.js +290 -0
- package/dist/voice/modelAssets.d.ts +28 -0
- package/dist/voice/modelAssets.js +113 -0
- package/dist/voice/output/render.d.ts +32 -0
- package/dist/voice/output/render.js +368 -0
- package/dist/voice/providers/OpenAISTT.d.ts +11 -1
- package/dist/voice/providers/OpenAISTT.js +57 -6
- package/dist/voice/session/transcriptionSession.d.ts +55 -0
- package/dist/voice/session/transcriptionSession.js +225 -0
- package/dist/voice/streaming/chunkedStreamAdapter.d.ts +7 -0
- package/dist/voice/streaming/chunkedStreamAdapter.js +711 -28
- package/dist/voice/streaming/endpointer.d.ts +50 -17
- package/dist/voice/streaming/endpointer.js +115 -19
- package/dist/voice/transcribe.d.ts +18 -1
- package/dist/voice/transcribe.js +459 -18
- package/dist/voice/transcribeStream.d.ts +18 -1
- package/dist/voice/transcribeStream.js +413 -16
- package/dist/voice/vad/assets.d.ts +36 -0
- package/dist/voice/vad/assets.js +142 -0
- package/dist/voice/vad/frameScorer.d.ts +28 -0
- package/dist/voice/vad/frameScorer.js +90 -0
- package/dist/voice/vad/sileroVad.d.ts +43 -0
- package/dist/voice/vad/sileroVad.js +178 -0
- package/dist/voice/whistle/assets.d.ts +3 -4
- package/dist/voice/whistle/assets.js +11 -95
- package/docs-site/static/search-index.json +21 -6
- package/models/silero-vad/README.md +19 -0
- package/models/silero-vad/silero_vad.onnx +0 -0
- package/package.json +3 -1
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* @module types/transcribe
|
|
28
28
|
*/
|
|
29
|
+
import type { ZodUnknownSchema } from "./aliases.js";
|
|
29
30
|
import type { TTSAudioFormat } from "./tts.js";
|
|
30
31
|
import type { STTHandler, STTOptions, STTResult, TranscriptionSegment, WordTiming } from "./stt.js";
|
|
31
32
|
/**
|
|
@@ -62,32 +63,61 @@ export type STTCorrectionOptions = {
|
|
|
62
63
|
* `false` disables it (dictionary-only correction). `provider`/`model` name
|
|
63
64
|
* any text provider; the instance default is used when omitted.
|
|
64
65
|
*/
|
|
65
|
-
rewrite?: false | {
|
|
66
|
+
rewrite?: false | "auto" | {
|
|
66
67
|
provider?: string;
|
|
67
68
|
model?: string;
|
|
68
69
|
/** On expiry the un-rewritten text is kept. Default 12000. */
|
|
69
70
|
timeoutMs?: number;
|
|
71
|
+
/** `"auto"` (the default) runs the rewrite only when there is something for it to do; `"always"` runs it on every text. */
|
|
72
|
+
mode?: "auto" | "always";
|
|
70
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Correct a final utterance sentence by sentence (split on terminal
|
|
76
|
+
* punctuation and the danda): each sentence decides its own script and
|
|
77
|
+
* whether it needs the rewrite, and sentences are corrected in parallel.
|
|
78
|
+
* Default true. A second opinion is whole-utterance and keeps one pass.
|
|
79
|
+
*/
|
|
80
|
+
perSentence?: boolean;
|
|
71
81
|
/**
|
|
72
82
|
* How Indic-script text is written: `"latin"` the way people type on phones
|
|
73
|
-
* (Hinglish / Tanglish
|
|
83
|
+
* (Hinglish / Tanglish), `"native"` keeps the script. Never a translation.
|
|
84
|
+
* Unset, Indian languages default to `"latin"` and every other language
|
|
85
|
+
* keeps its own script (Arabic, CJK, Cyrillic … are never romanized unless
|
|
86
|
+
* asked for). For any other rendering of the text, see `output.languages`.
|
|
74
87
|
*/
|
|
75
88
|
transliterate?: "latin" | "native";
|
|
76
89
|
/** Add punctuation and capitalization in the rewrite. Default true. */
|
|
77
90
|
punctuate?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* `"verbatim"` (the default) keeps every word, fillers and stutters
|
|
93
|
+
* included. `"clean"` lets the rewrite remove fillers ("um", "like" as a
|
|
94
|
+
* filler) and stutters ("they they they"), never content, and allows the
|
|
95
|
+
* rewrite to drop up to half the words to do so.
|
|
96
|
+
*/
|
|
97
|
+
cleanup?: "verbatim" | "clean";
|
|
78
98
|
/**
|
|
79
99
|
* A second engine run on the same audio; the rewrite reconciles both
|
|
80
100
|
* transcripts (Indian-language words from an Indic engine, English words
|
|
81
|
-
* from an English-oriented one).
|
|
101
|
+
* from an English-oriented one). `"auto"` uses the `fallback` engine as the
|
|
102
|
+
* second opinion on every utterance the primary leads, so code-mixed speech
|
|
103
|
+
* gets both readings without naming a provider twice.
|
|
82
104
|
*/
|
|
83
105
|
secondOpinion?: {
|
|
84
106
|
provider: string;
|
|
85
107
|
model?: string;
|
|
86
|
-
};
|
|
108
|
+
} | "auto";
|
|
87
109
|
/** One sentence about who is talking and about what; sent to the engines and the guard. */
|
|
88
110
|
context?: string;
|
|
89
111
|
/** A rewrite that drops more than this share of the words is discarded. Default 0.25. */
|
|
90
112
|
maxDropRatio?: number;
|
|
113
|
+
/**
|
|
114
|
+
* Corrected utterances before this one that the rewrite sees as read-only
|
|
115
|
+
* context, so names, spellings and script stay consistent across a
|
|
116
|
+
* conversation. Streaming and `chunking: "utterances"` only. Default 0
|
|
117
|
+
* (off): a rewrite may copy context into its answer, which the added-words
|
|
118
|
+
* guard rejects, so it is opt-in until measured.
|
|
119
|
+
*/
|
|
120
|
+
contextUtterances?: number;
|
|
91
121
|
};
|
|
92
122
|
/**
|
|
93
123
|
* Where an utterance goes when the primary engine cannot read it. `when`
|
|
@@ -99,6 +129,34 @@ export type STTFallbackOptions = {
|
|
|
99
129
|
model?: string;
|
|
100
130
|
when?: Array<"unsure" | "empty" | "error">;
|
|
101
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* How the engines share an utterance. With a `fallback` set, the default is
|
|
134
|
+
* to probe: the first pass of every utterance runs the primary and the
|
|
135
|
+
* fallback in parallel, and whichever reads the language leads from then on,
|
|
136
|
+
* so a sentence in the "wrong" language never waits for the slow takeover.
|
|
137
|
+
*/
|
|
138
|
+
export type STTRoutingOptions = {
|
|
139
|
+
/**
|
|
140
|
+
* Streaming: run the fallback engine beside the primary on the first pass
|
|
141
|
+
* of each utterance and lead with the one that reads the language.
|
|
142
|
+
* Batch: ask the primary for a language verdict first (its
|
|
143
|
+
* `languageProbeModel`, when it has one) and transcribe with the engine
|
|
144
|
+
* that fits. Default: on whenever a fallback is set.
|
|
145
|
+
*/
|
|
146
|
+
probe?: boolean;
|
|
147
|
+
/** Streaming: passes both engines keep running before the utterance's engine locks. Default 2. */
|
|
148
|
+
lockAfterPasses?: number;
|
|
149
|
+
/** Carry the last confidently detected language into the next utterance's detection (`"carry"`, default) or start each utterance cold (`"none"`). */
|
|
150
|
+
prior?: "carry" | "none";
|
|
151
|
+
/**
|
|
152
|
+
* Which engine transcribes which language: `{ hi: "indic", ta: "indic",
|
|
153
|
+
* en: "spark", "*": "spark" }`. A final whose detected language belongs to
|
|
154
|
+
* another engine than the one that produced it is transcribed again by
|
|
155
|
+
* that engine, and its text is used when it reads the language. `"*"` is
|
|
156
|
+
* the engine for every language not listed. Values are provider names.
|
|
157
|
+
*/
|
|
158
|
+
engines?: Record<string, string>;
|
|
159
|
+
};
|
|
102
160
|
/** Client-side streaming for engines without a native stream (and tuning for the adapter). */
|
|
103
161
|
export type STTStreamingOptions = {
|
|
104
162
|
/** `"auto"` uses the handler's native stream when it has one, else the chunked adapter. */
|
|
@@ -119,6 +177,84 @@ export type STTStreamingOptions = {
|
|
|
119
177
|
prerollMs?: number;
|
|
120
178
|
/** Shortest utterance worth a pass. Default 250. */
|
|
121
179
|
minSpeechMs?: number;
|
|
180
|
+
/**
|
|
181
|
+
* Engine passes allowed in flight at once. Live streaming keeps 1 (the
|
|
182
|
+
* default), so a caption never shows a stale pass; the file path
|
|
183
|
+
* (`chunking: "utterances"`) raises it so utterances are transcribed and
|
|
184
|
+
* corrected in parallel.
|
|
185
|
+
*/
|
|
186
|
+
concurrency?: number;
|
|
187
|
+
/**
|
|
188
|
+
* Times a failed final pass is tried again before the utterance is
|
|
189
|
+
* reported as silence. Live streaming keeps 0 (a stale retry would delay
|
|
190
|
+
* the next utterance); the file path uses 1.
|
|
191
|
+
*/
|
|
192
|
+
finalRetries?: number;
|
|
193
|
+
/** Which speech detector finds utterances, and its thresholds. Default: Silero when it loads, else the energy gate. */
|
|
194
|
+
vad?: STTVadOptions;
|
|
195
|
+
/**
|
|
196
|
+
* Raise quiet utterances to a speech level before an engine hears them
|
|
197
|
+
* (gain only goes up, capped at 8×, peaks limited). Far-field and
|
|
198
|
+
* low-gain microphones are the usual case. Default true.
|
|
199
|
+
*/
|
|
200
|
+
normalize?: boolean;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* The detector that decides where utterances start and end in a chunked
|
|
204
|
+
* stream. `"silero"` is the Silero VAD model (a 2.3 MB ONNX file shipped
|
|
205
|
+
* with the package, run through the optional `onnxruntime-node`); `"energy"`
|
|
206
|
+
* is the RMS gate with a noise-floor estimate; `"auto"` (the default) is
|
|
207
|
+
* Silero when its model and runtime load and the energy gate otherwise.
|
|
208
|
+
* `NEUROLINK_STT_VAD` sets the default.
|
|
209
|
+
*/
|
|
210
|
+
export type STTVadEngine = "auto" | "silero" | "energy";
|
|
211
|
+
/** Speech-detector choice and tuning (`streaming.vad`). */
|
|
212
|
+
export type STTVadOptions = {
|
|
213
|
+
engine?: STTVadEngine;
|
|
214
|
+
/** Speech probability at or above which an utterance may open. Default 0.6. */
|
|
215
|
+
onset?: number;
|
|
216
|
+
/** Probability at or above which an open utterance counts a block as speech. Default 0.35. */
|
|
217
|
+
sustain?: number;
|
|
218
|
+
/**
|
|
219
|
+
* Consecutive milliseconds at or above `onset` before an utterance opens,
|
|
220
|
+
* so a click or a cough does not. Default 120; the pre-roll keeps the
|
|
221
|
+
* confirmation audio, so nothing is clipped.
|
|
222
|
+
*/
|
|
223
|
+
startMs?: number;
|
|
224
|
+
/** Directory holding `silero_vad.onnx`. Default: `NEUROLINK_VAD_DIR`, `NEUROLINK_MODEL_DIR/silero-vad`, the package's own copy, then `~/.neurolink/models/silero-vad`. */
|
|
225
|
+
modelDir?: string;
|
|
226
|
+
/** Whether a missing model file may be downloaded (`NEUROLINK_VAD_AUTO_DOWNLOAD`). Default true. */
|
|
227
|
+
autoDownload?: boolean;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* A frame-level speech detector: `frameSamples` of 16 kHz mono float audio
|
|
231
|
+
* in, the probability that the frame is speech out. It keeps state across
|
|
232
|
+
* calls (`reset()` starts a new stream), and one instance serves one stream.
|
|
233
|
+
*/
|
|
234
|
+
export type STTSpeechDetector = {
|
|
235
|
+
/** Name reported on events (`vad`): `"silero"`, or whatever a caller's own detector calls itself. */
|
|
236
|
+
readonly name: string;
|
|
237
|
+
/** Samples per `score()` call at 16 kHz (Silero: 512, 32 ms). */
|
|
238
|
+
readonly frameSamples: number;
|
|
239
|
+
score(frame16k: Float32Array): Promise<number> | number;
|
|
240
|
+
reset(): void;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* The slice of onnxruntime-node's API the Silero detector uses, typed here
|
|
244
|
+
* so a typecheck does not need the optional dependency installed.
|
|
245
|
+
*/
|
|
246
|
+
export type STTOnnxTensor = {
|
|
247
|
+
readonly data: Float32Array | BigInt64Array;
|
|
248
|
+
readonly dims: readonly number[];
|
|
249
|
+
};
|
|
250
|
+
export type STTOnnxSession = {
|
|
251
|
+
run(feeds: Record<string, STTOnnxTensor>): Promise<Record<string, STTOnnxTensor>>;
|
|
252
|
+
};
|
|
253
|
+
export type STTOnnxRuntime = {
|
|
254
|
+
InferenceSession: {
|
|
255
|
+
create(path: string, options?: Record<string, unknown>): Promise<STTOnnxSession>;
|
|
256
|
+
};
|
|
257
|
+
Tensor: new (type: "float32" | "int64", data: Float32Array | BigInt64Array, dims: readonly number[]) => STTOnnxTensor;
|
|
122
258
|
};
|
|
123
259
|
/**
|
|
124
260
|
* Options for `neurolink.transcribe()`. `provider` defaults to
|
|
@@ -139,11 +275,153 @@ export type TranscribeOptions = Omit<STTOptions, "enabled" | "provider" | "langu
|
|
|
139
275
|
dictionary?: STTDictionaryEntry[];
|
|
140
276
|
correction?: STTCorrectionOptions;
|
|
141
277
|
fallback?: STTFallbackOptions;
|
|
278
|
+
routing?: STTRoutingOptions;
|
|
142
279
|
streaming?: STTStreamingOptions;
|
|
280
|
+
/**
|
|
281
|
+
* How a recording is cut. `"engine"` (the default) hands the whole file to
|
|
282
|
+
* the engine in one request. `"utterances"` runs the recording through the
|
|
283
|
+
* same pipeline as live captions: the endpointer cuts it into utterances,
|
|
284
|
+
* each utterance gets the language probe, its own engine choice, the
|
|
285
|
+
* dictionary and the per-sentence correction, several utterances at a
|
|
286
|
+
* time, and the result carries one segment per utterance with its
|
|
287
|
+
* language and engine. Needs WAV, or ffmpeg for other containers.
|
|
288
|
+
*/
|
|
289
|
+
chunking?: "engine" | "utterances";
|
|
143
290
|
/** Whole-call timeout. */
|
|
144
291
|
timeoutMs?: number;
|
|
145
292
|
/** Per-call credentials; the `stt` slice reaches the handlers. */
|
|
146
293
|
credentials?: STTCredentialsCarrier;
|
|
294
|
+
/** Renderings of the transcript in other languages or scripts (see `STTOutputOptions`). */
|
|
295
|
+
output?: STTOutputOptions;
|
|
296
|
+
};
|
|
297
|
+
/** A live list of output languages, read once per utterance (a session's subscribers, a socket's control frames). */
|
|
298
|
+
export type STTOutputLanguageSource = {
|
|
299
|
+
current(): readonly string[];
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Renderings of every utterance in other languages and scripts. Each entry
|
|
303
|
+
* of `languages` is a BCP-47 tag: `"es"`, `"fr-CA"`, `"ta"` translate;
|
|
304
|
+
* `"hi-Latn"` is Hindi written in Latin letters (romanized when Hindi was
|
|
305
|
+
* spoken, translated into Hinglish when something else was); `"source"` is
|
|
306
|
+
* the transcript itself. One model call renders all of them, from the
|
|
307
|
+
* native-script transcript and its corrected form, with earlier utterances
|
|
308
|
+
* as context. A rendering never touches `text`: a failed one is reported with
|
|
309
|
+
* its status and the transcript is unchanged.
|
|
310
|
+
*/
|
|
311
|
+
export type STTOutputOptions = {
|
|
312
|
+
languages: readonly string[] | STTOutputLanguageSource;
|
|
313
|
+
/** Text provider for the rendering; default `NEUROLINK_STT_OUTPUT_PROVIDER`, then the rewrite's. */
|
|
314
|
+
provider?: string;
|
|
315
|
+
/** Default `NEUROLINK_STT_OUTPUT_MODEL`, then the rewrite's. */
|
|
316
|
+
model?: string;
|
|
317
|
+
/** Per call; on expiry the rendering is reported as `"timeout"`. Default 15000. */
|
|
318
|
+
timeoutMs?: number;
|
|
319
|
+
/** Earlier utterances (and their renderings) given as context. Default 3; 0 turns it off. */
|
|
320
|
+
contextUtterances?: number;
|
|
321
|
+
/** Keep dictionary terms exactly as written in every language. Default true. */
|
|
322
|
+
glossary?: boolean;
|
|
323
|
+
};
|
|
324
|
+
export type STTOutputStatus =
|
|
325
|
+
/** Rendered by the model and accepted by the guards. */
|
|
326
|
+
"ok"
|
|
327
|
+
/** The target is the transcript's own language and script: the corrected text, no model call. */
|
|
328
|
+
| "same-language" | "timeout" | "error"
|
|
329
|
+
/** The model answered, and the guards refused it (empty, wrong script, untranslated). */
|
|
330
|
+
| "rejected"
|
|
331
|
+
/** A long transcript where some batches rendered and others did not. */
|
|
332
|
+
| "partial";
|
|
333
|
+
/** The transcript in one output language. `text` is empty unless the status is `ok`, `same-language` or `partial`. */
|
|
334
|
+
export type STTOutputRendering = {
|
|
335
|
+
language: string;
|
|
336
|
+
text: string;
|
|
337
|
+
status: STTOutputStatus;
|
|
338
|
+
ms: number;
|
|
339
|
+
/** Per segment (or utterance), in order, when the transcript has more than one. */
|
|
340
|
+
segments?: Array<{
|
|
341
|
+
index: number;
|
|
342
|
+
text: string;
|
|
343
|
+
}>;
|
|
344
|
+
/** Why a rendering was refused or failed. */
|
|
345
|
+
reason?: string;
|
|
346
|
+
};
|
|
347
|
+
/** One rendering request: lines of one transcript and the targets they go to. */
|
|
348
|
+
export type STTOutputRenderInput = {
|
|
349
|
+
/** The text as the engine wrote it (native script, dictionary applied), one entry per line. */
|
|
350
|
+
source: readonly string[];
|
|
351
|
+
/** The corrected text of the same lines, when correction changed it. */
|
|
352
|
+
corrected?: readonly string[];
|
|
353
|
+
/** Detected language of the speech, when known. */
|
|
354
|
+
language?: string;
|
|
355
|
+
languages: readonly string[];
|
|
356
|
+
/** Earlier lines, oldest first, with their renderings per target. */
|
|
357
|
+
previous?: ReadonlyArray<{
|
|
358
|
+
text: string;
|
|
359
|
+
outputs: Record<string, string>;
|
|
360
|
+
}>;
|
|
361
|
+
dictionary?: readonly STTDictionaryEntry[];
|
|
362
|
+
options?: Omit<STTOutputOptions, "languages">;
|
|
363
|
+
};
|
|
364
|
+
/** What one rendering request produced for one target. */
|
|
365
|
+
export type STTOutputBatchOutcome = {
|
|
366
|
+
lines?: string[];
|
|
367
|
+
status: STTOutputStatus;
|
|
368
|
+
reason?: string;
|
|
369
|
+
};
|
|
370
|
+
/** One aligned position of two readings: a word from either side, or both when they line up. */
|
|
371
|
+
export type STTWordPair = {
|
|
372
|
+
/** The Indic engine's word (native script). */
|
|
373
|
+
indic?: string;
|
|
374
|
+
/** The English-oriented engine's word. */
|
|
375
|
+
latin?: string;
|
|
376
|
+
/** Both present and phonetically alike: the speaker said the same thing in both readings. */
|
|
377
|
+
alike: boolean;
|
|
378
|
+
};
|
|
379
|
+
/** How two readings of one utterance relate (see `analyzeCodeMix`). */
|
|
380
|
+
export type STTCodeMixAnalysis = {
|
|
381
|
+
/** Share of the Indic reading's words with a phonetic partner in the English reading (0–1). */
|
|
382
|
+
englishShare: number;
|
|
383
|
+
/** Words in the Indic reading. */
|
|
384
|
+
words: number;
|
|
385
|
+
pairs: STTWordPair[];
|
|
386
|
+
};
|
|
387
|
+
/** ISO 15924 scripts the output guards recognise. */
|
|
388
|
+
export type ScriptCode = "Latn" | "Deva" | "Beng" | "Guru" | "Gujr" | "Orya" | "Taml" | "Telu" | "Knda" | "Mlym" | "Sinh" | "Arab" | "Cyrl" | "Grek" | "Hebr" | "Thai" | "Hang" | "Jpan" | "Hani" | "Geor" | "Armn" | "Ethi";
|
|
389
|
+
/** One output target, parsed: `"hi-Latn"` → `{ language: "hi", script: "Latn" }`. */
|
|
390
|
+
export type ParsedOutputTag = {
|
|
391
|
+
tag: string;
|
|
392
|
+
language: string;
|
|
393
|
+
/** The script the text must be written in: the subtag, else the language's own. */
|
|
394
|
+
script: ScriptCode;
|
|
395
|
+
/** True when the tag names a script (`hi-Latn`), not just a language. */
|
|
396
|
+
explicitScript: boolean;
|
|
397
|
+
};
|
|
398
|
+
export type TranscriptionSessionOptions = {
|
|
399
|
+
id?: string;
|
|
400
|
+
/** Utterances kept for listeners who join late. Default 50. */
|
|
401
|
+
backlog?: number;
|
|
402
|
+
/** Distinct output languages a session renders at once. Default 8. */
|
|
403
|
+
maxLanguages?: number;
|
|
404
|
+
/** Rendering settings shared by every speaker; the languages come from the listeners. */
|
|
405
|
+
output?: Omit<STTOutputOptions, "languages">;
|
|
406
|
+
};
|
|
407
|
+
/** A stream event from one speaker of a session. */
|
|
408
|
+
export type TranscriptionSessionEvent = TranscribeStreamEvent & {
|
|
409
|
+
speaker: string;
|
|
410
|
+
};
|
|
411
|
+
export type TranscriptionSubscriberOptions = {
|
|
412
|
+
/** Output languages this listener reads. Empty: the transcript only. */
|
|
413
|
+
languages?: readonly string[];
|
|
414
|
+
/** Speakers to follow; omitted, every speaker. */
|
|
415
|
+
speakers?: readonly string[];
|
|
416
|
+
/** Replay the backlog on subscribe. Default true. */
|
|
417
|
+
backlog?: boolean;
|
|
418
|
+
/** Also deliver `interim` captions. Default true. */
|
|
419
|
+
interim?: boolean;
|
|
420
|
+
};
|
|
421
|
+
export type TranscriptionSubscription = {
|
|
422
|
+
readonly languages: readonly string[];
|
|
423
|
+
update(options: Pick<TranscriptionSubscriberOptions, "languages" | "speakers">): void;
|
|
424
|
+
close(): void;
|
|
147
425
|
};
|
|
148
426
|
/** `transcribeStream()` takes frames instead of a whole buffer. */
|
|
149
427
|
export type TranscribeStreamOptions = Omit<TranscribeOptions, "audio"> & {
|
|
@@ -189,6 +467,8 @@ export type TranscribeTimings = {
|
|
|
189
467
|
decideMs?: number;
|
|
190
468
|
rewriteMs?: number;
|
|
191
469
|
correctionMs?: number;
|
|
470
|
+
/** Rendering into the output languages. */
|
|
471
|
+
outputMs?: number;
|
|
192
472
|
totalMs: number;
|
|
193
473
|
};
|
|
194
474
|
/** The result of `transcribe()`: an `STTResult` plus what the layers did to it. */
|
|
@@ -205,6 +485,8 @@ export type TranscribeResult = STTResult & {
|
|
|
205
485
|
timings: TranscribeTimings;
|
|
206
486
|
/** Human-readable trail of what happened ("dictionary 2/2", "rewrite timed out · kept text"). */
|
|
207
487
|
steps: string[];
|
|
488
|
+
/** One rendering per `output.languages` entry, keyed by the tag as given. */
|
|
489
|
+
outputs?: Record<string, STTOutputRendering>;
|
|
208
490
|
};
|
|
209
491
|
export type TranscribeStreamEvent = {
|
|
210
492
|
type: "interim";
|
|
@@ -235,6 +517,19 @@ export type TranscribeStreamEvent = {
|
|
|
235
517
|
engine: string;
|
|
236
518
|
fallbackUsed?: boolean;
|
|
237
519
|
seconds: number;
|
|
520
|
+
/** The detector that found this utterance: `"silero"`, `"energy"`, or a caller's own. */
|
|
521
|
+
vad?: string;
|
|
522
|
+
/** Seconds the detector counted as speech inside the utterance. */
|
|
523
|
+
speechSeconds?: number;
|
|
524
|
+
/**
|
|
525
|
+
* The other engine's reading of the same utterance, when the two
|
|
526
|
+
* disagreed on its language and the final ran both: code-mixed speech
|
|
527
|
+
* is reconciled from both readings (`secondOpinion: "auto"`).
|
|
528
|
+
*/
|
|
529
|
+
alternative?: {
|
|
530
|
+
engine: string;
|
|
531
|
+
text: string;
|
|
532
|
+
};
|
|
238
533
|
} | {
|
|
239
534
|
/** Partial text of the streamed rewrite, in order. */
|
|
240
535
|
type: "correcting";
|
|
@@ -248,11 +543,26 @@ export type TranscribeStreamEvent = {
|
|
|
248
543
|
decisions: STTDecisionRecord[];
|
|
249
544
|
steps: string[];
|
|
250
545
|
timings: TranscribeTimings;
|
|
546
|
+
} | {
|
|
547
|
+
/**
|
|
548
|
+
* The utterance in one output language. Follows `corrected` when
|
|
549
|
+
* correction runs (`basedOn: "corrected"`), else the `final`.
|
|
550
|
+
*/
|
|
551
|
+
type: "output";
|
|
552
|
+
utterance: number;
|
|
553
|
+
language: string;
|
|
554
|
+
text: string;
|
|
555
|
+
status: STTOutputStatus;
|
|
556
|
+
basedOn: "final" | "corrected";
|
|
557
|
+
ms: number;
|
|
558
|
+
reason?: string;
|
|
251
559
|
} | {
|
|
252
560
|
/** An utterance no engine could read; its audio length is reported so nothing disappears silently. */
|
|
253
561
|
type: "silence";
|
|
254
562
|
utterance: number;
|
|
255
563
|
seconds: number;
|
|
564
|
+
vad?: string;
|
|
565
|
+
speechSeconds?: number;
|
|
256
566
|
} | {
|
|
257
567
|
type: "error";
|
|
258
568
|
utterance?: number;
|
|
@@ -260,6 +570,18 @@ export type TranscribeStreamEvent = {
|
|
|
260
570
|
/** `true` when the stream continues with the next utterance. */
|
|
261
571
|
recoverable: boolean;
|
|
262
572
|
};
|
|
573
|
+
/** The `final` event of a stream, as a named type for code that collects them. */
|
|
574
|
+
export type TranscribeStreamFinalEvent = Extract<TranscribeStreamEvent, {
|
|
575
|
+
type: "final";
|
|
576
|
+
}>;
|
|
577
|
+
/** The `corrected` event of a stream. */
|
|
578
|
+
export type TranscribeStreamCorrectedEvent = Extract<TranscribeStreamEvent, {
|
|
579
|
+
type: "corrected";
|
|
580
|
+
}>;
|
|
581
|
+
/** The `output` event of a stream. */
|
|
582
|
+
export type TranscribeStreamOutputEvent = Extract<TranscribeStreamEvent, {
|
|
583
|
+
type: "output";
|
|
584
|
+
}>;
|
|
263
585
|
/**
|
|
264
586
|
* One OpenAI-compatible `/audio/transcriptions` endpoint registered under a
|
|
265
587
|
* name of its own, so several self-hosted engines can be told apart — and
|
|
@@ -271,6 +593,22 @@ export type STTEndpointConfig = {
|
|
|
271
593
|
apiKey?: string;
|
|
272
594
|
model?: string;
|
|
273
595
|
timeoutMs?: number;
|
|
596
|
+
/**
|
|
597
|
+
* How this server takes a language prior in its `language` field, as a
|
|
598
|
+
* template with `{lang}` (the IndicConformer wrapper: `"auto-{lang}"`).
|
|
599
|
+
* Unset, no prior is ever sent: a server that does not understand one
|
|
600
|
+
* would refuse the request.
|
|
601
|
+
*/
|
|
602
|
+
priorLanguageForm?: string;
|
|
603
|
+
/** A model name this server runs in language-identification-only mode (see `STTHandler.languageProbeModel`). */
|
|
604
|
+
lidModel?: string;
|
|
605
|
+
/**
|
|
606
|
+
* Whether the dictionary terms travel to this server as a "Names and
|
|
607
|
+
* terms used: …" prompt. Whisper biases on it; Qwen3-ASR and most others
|
|
608
|
+
* ignore it and some echo it back on a pause. Default `true`; set `false`
|
|
609
|
+
* for a server that echoes it.
|
|
610
|
+
*/
|
|
611
|
+
vocabularyPrompt?: boolean;
|
|
274
612
|
};
|
|
275
613
|
/** Credential slices for STT handlers; `credentials.stt.<key>` beats the env var. */
|
|
276
614
|
export type STTCredentials = {
|
|
@@ -281,6 +619,12 @@ export type STTCredentials = {
|
|
|
281
619
|
timeoutMs?: number;
|
|
282
620
|
/** Default model name sent to the endpoint (`whisper-1` when omitted). */
|
|
283
621
|
model?: string;
|
|
622
|
+
/** See `STTEndpointConfig.priorLanguageForm`. Env: `OPENAI_STT_PRIOR_LANGUAGE_FORM`. */
|
|
623
|
+
priorLanguageForm?: string;
|
|
624
|
+
/** See `STTEndpointConfig.lidModel`. Env: `OPENAI_STT_LID_MODEL`. */
|
|
625
|
+
lidModel?: string;
|
|
626
|
+
/** See `STTEndpointConfig.vocabularyPrompt`. Env: `OPENAI_STT_VOCABULARY_PROMPT`. */
|
|
627
|
+
vocabularyPrompt?: boolean;
|
|
284
628
|
};
|
|
285
629
|
/**
|
|
286
630
|
* Extra OpenAI-compatible endpoints, each registered as a provider under
|
|
@@ -366,6 +710,19 @@ export type STTCorrectionDeps = {
|
|
|
366
710
|
timeoutMs: number;
|
|
367
711
|
onPartial?: (text: string) => void;
|
|
368
712
|
}) => Promise<string>;
|
|
713
|
+
/**
|
|
714
|
+
* A structured text call for output rendering: the parsed object the
|
|
715
|
+
* schema describes. Absent, renderings report `"error"`.
|
|
716
|
+
*/
|
|
717
|
+
render?: (opts: {
|
|
718
|
+
system: string;
|
|
719
|
+
user: string;
|
|
720
|
+
schema: ZodUnknownSchema;
|
|
721
|
+
provider?: string;
|
|
722
|
+
model?: string;
|
|
723
|
+
maxTokens: number;
|
|
724
|
+
timeoutMs: number;
|
|
725
|
+
}) => Promise<unknown>;
|
|
369
726
|
};
|
|
370
727
|
/** Input to the correction layer, independent of where the text came from. */
|
|
371
728
|
export type STTCorrectionInput = {
|
|
@@ -376,6 +733,10 @@ export type STTCorrectionInput = {
|
|
|
376
733
|
options?: STTCorrectionOptions;
|
|
377
734
|
/** `false` when the primary engine reported a low language score: the second opinion then leads. */
|
|
378
735
|
languageDetected?: boolean;
|
|
736
|
+
/** Detected language of the speech; picks the rewrite's rules and script default. */
|
|
737
|
+
language?: string;
|
|
738
|
+
/** Corrected utterances before this one, oldest first: read-only context for the rewrite. */
|
|
739
|
+
previous?: readonly string[];
|
|
379
740
|
onPartial?: (text: string) => void;
|
|
380
741
|
};
|
|
381
742
|
/** A dictionary alias found in a transcript: a candidate substitution for the guard to judge. */
|
|
@@ -387,7 +748,7 @@ export type STTDictionaryCandidate = {
|
|
|
387
748
|
/** What one rewrite call did; `text` is always usable (the input when the rewrite was not kept). */
|
|
388
749
|
export type STTRewriteOutcome = {
|
|
389
750
|
text: string;
|
|
390
|
-
status: "ok" | "timeout" | "error" | "empty" | "dropped";
|
|
751
|
+
status: "ok" | "timeout" | "error" | "empty" | "dropped" | "added";
|
|
391
752
|
ms: number;
|
|
392
753
|
/** Share of words the rewrite dropped, when `status` is `"dropped"`. */
|
|
393
754
|
dropRatio?: number;
|
|
@@ -432,6 +793,24 @@ export type STTEndpointerConfig = {
|
|
|
432
793
|
prerollMs: number;
|
|
433
794
|
minSpeechMs: number;
|
|
434
795
|
};
|
|
796
|
+
/**
|
|
797
|
+
* A speech detector's verdicts as the endpointer reads them: the thresholds
|
|
798
|
+
* and a lookup of the probability at a point on the stream clock. Attached,
|
|
799
|
+
* it replaces the RMS rules; the energy gate stays as the fallback.
|
|
800
|
+
*/
|
|
801
|
+
export type STTEndpointerSpeechConfig = {
|
|
802
|
+
onset: number;
|
|
803
|
+
sustain: number;
|
|
804
|
+
startMs: number;
|
|
805
|
+
/** Speech probability of the frame covering `seconds`; `undefined` before the first scored frame. */
|
|
806
|
+
probabilityAt(seconds: number): number | undefined;
|
|
807
|
+
};
|
|
808
|
+
/** One block waiting in the endpointer's pre-roll: its samples, RMS and speech probability. */
|
|
809
|
+
export type STTEndpointerPreBlock = {
|
|
810
|
+
block: Float32Array;
|
|
811
|
+
rms: number;
|
|
812
|
+
speech: number;
|
|
813
|
+
};
|
|
435
814
|
/** One utterance as the energy gate builds it, at the input rate. */
|
|
436
815
|
export type STTEndpointerUtterance = {
|
|
437
816
|
/** 1-based, in order of onset. A dropped cough consumes an id, so ids can skip. */
|
|
@@ -442,6 +821,8 @@ export type STTEndpointerUtterance = {
|
|
|
442
821
|
blocks: Float32Array[];
|
|
443
822
|
/** RMS of each block, parallel to `blocks`. */
|
|
444
823
|
rms: number[];
|
|
824
|
+
/** Speech probability of each block, parallel to `blocks`, when a detector is attached. */
|
|
825
|
+
speech?: number[];
|
|
445
826
|
/** Total samples in `blocks`. */
|
|
446
827
|
length: number;
|
|
447
828
|
speechMs: number;
|
|
@@ -485,6 +866,14 @@ export type STTChunkedPassOptions = {
|
|
|
485
866
|
engine: STTStreamEngineRole;
|
|
486
867
|
/** Audio length of the utterance so far. */
|
|
487
868
|
seconds: number;
|
|
869
|
+
/** The last confidently detected language of this stream, for engines that take a prior. */
|
|
870
|
+
languagePrior?: string;
|
|
871
|
+
/**
|
|
872
|
+
* Transcribe in this language rather than detecting one: set on the
|
|
873
|
+
* fallback role once the utterance (or the stream) is settled as that
|
|
874
|
+
* language, so a short pass cannot drift into another script.
|
|
875
|
+
*/
|
|
876
|
+
languageHint?: string;
|
|
488
877
|
};
|
|
489
878
|
/** One engine pass over a whole utterance, 16 kHz mono float samples in. */
|
|
490
879
|
export type STTChunkedRun = (pcm16k: Float32Array, options: STTChunkedPassOptions) => Promise<STTResult>;
|
|
@@ -497,6 +886,13 @@ export type STTChunkedStreamOptions = STTStreamingOptions & {
|
|
|
497
886
|
label: string;
|
|
498
887
|
when?: STTFallbackOptions["when"];
|
|
499
888
|
};
|
|
889
|
+
/** Probe, lock and prior behaviour (see `STTRoutingOptions`); the defaults apply when omitted. */
|
|
890
|
+
routing?: STTRoutingOptions;
|
|
891
|
+
/**
|
|
892
|
+
* A speech detector of the caller's own, used instead of what `vad`
|
|
893
|
+
* would load; `null` keeps the energy gate. Omitted, `vad` decides.
|
|
894
|
+
*/
|
|
895
|
+
speechDetector?: STTSpeechDetector | null;
|
|
500
896
|
/**
|
|
501
897
|
* Called once per utterance, synchronously, when it ends and before its
|
|
502
898
|
* final pass is queued, with the utterance audio at 16 kHz and the role
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech level for the engines: a quiet utterance (a far-field speaker, a
|
|
3
|
+
* low-gain microphone) is raised to a normal speech level before an engine
|
|
4
|
+
* hears it. Recognisers are trained on speech near -20 dBFS and lose words,
|
|
5
|
+
* and language-ID confidence, well below it.
|
|
6
|
+
*
|
|
7
|
+
* Gain only ever goes up, is capped, and is limited by the peak so nothing
|
|
8
|
+
* clips; digital silence stays silence (zero times any gain is zero), so the
|
|
9
|
+
* silence pad an engine relies on is untouched.
|
|
10
|
+
*
|
|
11
|
+
* @module voice/audioLevel
|
|
12
|
+
*/
|
|
13
|
+
/** `pcm` with its speech raised to the target level, or `pcm` itself when it is loud enough already. */
|
|
14
|
+
export declare function normalizeSpeechLevel(pcm: Float32Array): Float32Array;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech level for the engines: a quiet utterance (a far-field speaker, a
|
|
3
|
+
* low-gain microphone) is raised to a normal speech level before an engine
|
|
4
|
+
* hears it. Recognisers are trained on speech near -20 dBFS and lose words,
|
|
5
|
+
* and language-ID confidence, well below it.
|
|
6
|
+
*
|
|
7
|
+
* Gain only ever goes up, is capped, and is limited by the peak so nothing
|
|
8
|
+
* clips; digital silence stays silence (zero times any gain is zero), so the
|
|
9
|
+
* silence pad an engine relies on is untouched.
|
|
10
|
+
*
|
|
11
|
+
* @module voice/audioLevel
|
|
12
|
+
*/
|
|
13
|
+
/** RMS the speech is raised to: about -22 dBFS. */
|
|
14
|
+
const TARGET_SPEECH_RMS = 0.08;
|
|
15
|
+
/** Most gain applied to any utterance. */
|
|
16
|
+
const MAX_GAIN = 8;
|
|
17
|
+
/** Peak after gain; leaves headroom for the engine's own resampling. */
|
|
18
|
+
const PEAK_LIMIT = 0.95;
|
|
19
|
+
/** Blocks quieter than this do not count as speech when measuring the level. */
|
|
20
|
+
const SPEECH_FLOOR_RMS = 0.002;
|
|
21
|
+
/** 20 ms at 16 kHz. */
|
|
22
|
+
const BLOCK = 320;
|
|
23
|
+
/** Gain below this is not worth a copy of the audio. */
|
|
24
|
+
const MIN_USEFUL_GAIN = 1.1;
|
|
25
|
+
/** `pcm` with its speech raised to the target level, or `pcm` itself when it is loud enough already. */
|
|
26
|
+
export function normalizeSpeechLevel(pcm) {
|
|
27
|
+
let sum = 0;
|
|
28
|
+
let count = 0;
|
|
29
|
+
let peak = 0;
|
|
30
|
+
for (let at = 0; at < pcm.length; at += BLOCK) {
|
|
31
|
+
const end = Math.min(pcm.length, at + BLOCK);
|
|
32
|
+
let block = 0;
|
|
33
|
+
for (let i = at; i < end; i++) {
|
|
34
|
+
const v = pcm[i];
|
|
35
|
+
block += v * v;
|
|
36
|
+
const a = Math.abs(v);
|
|
37
|
+
if (a > peak) {
|
|
38
|
+
peak = a;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const rms = Math.sqrt(block / Math.max(1, end - at));
|
|
42
|
+
if (rms >= SPEECH_FLOOR_RMS) {
|
|
43
|
+
sum += block;
|
|
44
|
+
count += end - at;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (count === 0 || peak === 0) {
|
|
48
|
+
return pcm;
|
|
49
|
+
}
|
|
50
|
+
const speechRms = Math.sqrt(sum / count);
|
|
51
|
+
const gain = Math.min(MAX_GAIN, TARGET_SPEECH_RMS / speechRms, PEAK_LIMIT / peak);
|
|
52
|
+
if (!(gain >= MIN_USEFUL_GAIN)) {
|
|
53
|
+
return pcm;
|
|
54
|
+
}
|
|
55
|
+
const out = new Float32Array(pcm.length);
|
|
56
|
+
for (let i = 0; i < pcm.length; i++) {
|
|
57
|
+
out[i] = pcm[i] * gain;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-mix analysis between two engines' readings of the same speech.
|
|
3
|
+
*
|
|
4
|
+
* An Indic engine writes English words phonetically in its own script
|
|
5
|
+
* ("मीटिंग" for "meeting"); an English-oriented engine writes Indian-language
|
|
6
|
+
* words as English-sounding guesses ("page dinner" for "भेज देना"). Brought
|
|
7
|
+
* to one rough phonetic form — Indic text romanized, both reduced to a
|
|
8
|
+
* consonant skeleton with voicing and aspiration folded — the two sequences
|
|
9
|
+
* line up word for word wherever the speaker spoke English, and stop lining
|
|
10
|
+
* up where they spoke an Indian language. The share of the Indic engine's
|
|
11
|
+
* words that find a phonetic partner is therefore an estimate of how much of
|
|
12
|
+
* the utterance was English, independent of either engine's own (accent-
|
|
13
|
+
* sensitive) language score; the alignment itself is what a word-level
|
|
14
|
+
* combination of the two readings is built on.
|
|
15
|
+
*
|
|
16
|
+
* @module voice/codeMix
|
|
17
|
+
*/
|
|
18
|
+
import type { STTCodeMixAnalysis, STTWordPair } from "../types/index.js";
|
|
19
|
+
/** Indic-script text in rough Latin letters; anything else passes through. */
|
|
20
|
+
export declare function romanizeIndic(text: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* A word's consonant skeleton: digraphs folded, voicing pairs merged
|
|
23
|
+
* (t/d, k/g, p/b are where an accent and a script disagree most), vowels and
|
|
24
|
+
* h dropped, repeats collapsed. "meeting" and "मीटिंग" both give "mtnk".
|
|
25
|
+
*/
|
|
26
|
+
export declare function phoneticKey(word: string): string;
|
|
27
|
+
/** Whether two skeletons sound alike: equal, or one edit apart on three or more consonants (two on six). */
|
|
28
|
+
export declare function soundsAlike(a: string, b: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Align two readings word by word (Needleman–Wunsch on skeletons): a pair
|
|
31
|
+
* for every word of either side, matched where the words sound alike.
|
|
32
|
+
*/
|
|
33
|
+
export declare function alignReadings(indic: readonly string[], latin: readonly string[]): STTWordPair[];
|
|
34
|
+
/**
|
|
35
|
+
* How much of an utterance two readings agree is English: the share of the
|
|
36
|
+
* Indic engine's words with a phonetic partner in the English-oriented
|
|
37
|
+
* engine's text. Near 1 for English an Indic engine wrote phonetically; well
|
|
38
|
+
* under it for code-mixed speech, where the Indian-language words find none.
|
|
39
|
+
*/
|
|
40
|
+
export declare function analyzeCodeMix(indicText: string, latinText: string): STTCodeMixAnalysis;
|