@juspay/neurolink 13.7.0 → 13.8.1

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +3 -6
  2. package/dist/browser/neurolink.min.js +480 -460
  3. package/dist/cli/commands/serve.js +11 -1
  4. package/dist/cli/commands/transcribe.js +39 -3
  5. package/dist/constants/langfuse.d.ts +2 -0
  6. package/dist/constants/langfuse.js +2 -0
  7. package/dist/factories/sttDescriptors.js +7 -0
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.js +4 -2
  10. package/dist/mcp/externalServerManager.js +4 -1
  11. package/dist/mcp/mcpClientFactory.d.ts +6 -0
  12. package/dist/mcp/mcpClientFactory.js +44 -0
  13. package/dist/neurolink.js +42 -3
  14. package/dist/observability/exporters/langfuseExporter.d.ts +4 -0
  15. package/dist/observability/exporters/langfuseExporter.js +29 -8
  16. package/dist/observability/otlpEncoding.js +2 -2
  17. package/dist/server/abstract/baseServerAdapter.js +2 -0
  18. package/dist/server/adapters/expressAdapter.js +1 -1
  19. package/dist/server/adapters/fastifyAdapter.js +4 -1
  20. package/dist/server/adapters/honoAdapter.js +1 -1
  21. package/dist/server/adapters/koaAdapter.js +1 -1
  22. package/dist/server/routes/transcribeRoutes.d.ts +0 -8
  23. package/dist/server/routes/transcribeRoutes.js +130 -3
  24. package/dist/server/utils/validation.d.ts +409 -15
  25. package/dist/server/utils/validation.js +106 -4
  26. package/dist/server/websocket/frameQueue.d.ts +16 -0
  27. package/dist/server/websocket/frameQueue.js +55 -0
  28. package/dist/server/websocket/realtimeTranscription.d.ts +48 -0
  29. package/dist/server/websocket/realtimeTranscription.js +603 -0
  30. package/dist/server/websocket/transcribeWebSocket.d.ts +17 -1
  31. package/dist/server/websocket/transcribeWebSocket.js +263 -64
  32. package/dist/services/server/ai/observability/langfuseSetup.js +6 -1
  33. package/dist/types/cli.d.ts +6 -0
  34. package/dist/types/exporter.d.ts +3 -1
  35. package/dist/types/server.d.ts +304 -2
  36. package/dist/types/stt.d.ts +38 -3
  37. package/dist/types/transcribe.d.ts +401 -5
  38. package/dist/voice/audioLevel.d.ts +14 -0
  39. package/dist/voice/audioLevel.js +60 -0
  40. package/dist/voice/codeMix.d.ts +40 -0
  41. package/dist/voice/codeMix.js +282 -0
  42. package/dist/voice/correction/align.d.ts +12 -0
  43. package/dist/voice/correction/align.js +48 -0
  44. package/dist/voice/correction/guards.d.ts +39 -0
  45. package/dist/voice/correction/guards.js +122 -0
  46. package/dist/voice/correction/index.d.ts +25 -3
  47. package/dist/voice/correction/index.js +132 -4
  48. package/dist/voice/correction/rewrite.d.ts +64 -3
  49. package/dist/voice/correction/rewrite.js +295 -25
  50. package/dist/voice/eval/sttEvalLangfuse.d.ts +14 -0
  51. package/dist/voice/eval/sttEvalLangfuse.js +182 -0
  52. package/dist/voice/eval/sttEvalStore.d.ts +27 -0
  53. package/dist/voice/eval/sttEvalStore.js +397 -0
  54. package/dist/voice/index.js +3 -0
  55. package/dist/voice/languages.d.ts +34 -0
  56. package/dist/voice/languages.js +290 -0
  57. package/dist/voice/modelAssets.d.ts +28 -0
  58. package/dist/voice/modelAssets.js +113 -0
  59. package/dist/voice/output/render.d.ts +32 -0
  60. package/dist/voice/output/render.js +368 -0
  61. package/dist/voice/providers/OpenAISTT.d.ts +11 -1
  62. package/dist/voice/providers/OpenAISTT.js +57 -6
  63. package/dist/voice/session/transcriptionSession.d.ts +55 -0
  64. package/dist/voice/session/transcriptionSession.js +225 -0
  65. package/dist/voice/streaming/chunkedStreamAdapter.d.ts +7 -0
  66. package/dist/voice/streaming/chunkedStreamAdapter.js +711 -28
  67. package/dist/voice/streaming/endpointer.d.ts +50 -17
  68. package/dist/voice/streaming/endpointer.js +115 -19
  69. package/dist/voice/transcribe.d.ts +18 -1
  70. package/dist/voice/transcribe.js +459 -18
  71. package/dist/voice/transcribeStream.d.ts +18 -1
  72. package/dist/voice/transcribeStream.js +413 -16
  73. package/dist/voice/vad/assets.d.ts +36 -0
  74. package/dist/voice/vad/assets.js +142 -0
  75. package/dist/voice/vad/frameScorer.d.ts +28 -0
  76. package/dist/voice/vad/frameScorer.js +90 -0
  77. package/dist/voice/vad/sileroVad.d.ts +43 -0
  78. package/dist/voice/vad/sileroVad.js +178 -0
  79. package/dist/voice/whistle/assets.d.ts +3 -4
  80. package/dist/voice/whistle/assets.js +11 -95
  81. package/docs-site/static/search-index.json +21 -6
  82. package/models/silero-vad/README.md +19 -0
  83. package/models/silero-vad/silero_vad.onnx +0 -0
  84. package/package.json +3 -1
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Language codes and writing systems for the transcribe pipeline.
3
+ *
4
+ * Engines report languages in different shapes ("hi", "hin", "Hindi",
5
+ * "en-US"); routing, correction and output rendering need one: a lower-case
6
+ * ISO 639 code. Output targets are BCP-47 tags whose script subtag says how
7
+ * the text is written (`hi-Latn` is Hindi in Latin letters), and the guards
8
+ * on a rendering check the text against that script.
9
+ *
10
+ * @module voice/languages
11
+ */
12
+ /** Indian languages: the ones the rewrite writes in Latin letters by default. */
13
+ const INDIC_LANGUAGES = new Set([
14
+ "as",
15
+ "bn",
16
+ "brx",
17
+ "doi",
18
+ "gu",
19
+ "hi",
20
+ "kn",
21
+ "kok",
22
+ "ks",
23
+ "mai",
24
+ "ml",
25
+ "mni",
26
+ "mr",
27
+ "ne",
28
+ "or",
29
+ "pa",
30
+ "sa",
31
+ "sat",
32
+ "sd",
33
+ "ta",
34
+ "te",
35
+ "ur",
36
+ ]);
37
+ /** English names (and ISO 639-2/3 codes) engines report instead of a 639-1 code. */
38
+ const LANGUAGE_ALIASES = {
39
+ english: "en",
40
+ eng: "en",
41
+ hindi: "hi",
42
+ hin: "hi",
43
+ bengali: "bn",
44
+ bangla: "bn",
45
+ ben: "bn",
46
+ marathi: "mr",
47
+ mar: "mr",
48
+ gujarati: "gu",
49
+ guj: "gu",
50
+ punjabi: "pa",
51
+ panjabi: "pa",
52
+ pan: "pa",
53
+ odia: "or",
54
+ oriya: "or",
55
+ ori: "or",
56
+ assamese: "as",
57
+ asm: "as",
58
+ urdu: "ur",
59
+ urd: "ur",
60
+ tamil: "ta",
61
+ tam: "ta",
62
+ telugu: "te",
63
+ tel: "te",
64
+ kannada: "kn",
65
+ kan: "kn",
66
+ malayalam: "ml",
67
+ mal: "ml",
68
+ nepali: "ne",
69
+ nep: "ne",
70
+ sanskrit: "sa",
71
+ san: "sa",
72
+ sindhi: "sd",
73
+ kashmiri: "ks",
74
+ konkani: "kok",
75
+ maithili: "mai",
76
+ spanish: "es",
77
+ spa: "es",
78
+ french: "fr",
79
+ fra: "fr",
80
+ german: "de",
81
+ deu: "de",
82
+ italian: "it",
83
+ portuguese: "pt",
84
+ russian: "ru",
85
+ japanese: "ja",
86
+ jpn: "ja",
87
+ korean: "ko",
88
+ kor: "ko",
89
+ chinese: "zh",
90
+ mandarin: "zh",
91
+ cantonese: "yue",
92
+ zho: "zh",
93
+ arabic: "ar",
94
+ ara: "ar",
95
+ persian: "fa",
96
+ turkish: "tr",
97
+ dutch: "nl",
98
+ indonesian: "id",
99
+ vietnamese: "vi",
100
+ thai: "th",
101
+ };
102
+ /**
103
+ * The language part of whatever an engine reported, as a lower-case ISO 639
104
+ * code: "en-US" → "en", "Hindi" → "hi", "auto-ta" → "ta". `undefined` for
105
+ * nothing, "auto" and "und".
106
+ */
107
+ export function normalizeLanguageCode(value) {
108
+ const trimmed = value?.trim().toLowerCase();
109
+ if (!trimmed || trimmed === "auto" || trimmed === "und") {
110
+ return undefined;
111
+ }
112
+ const bare = trimmed.replace(/^auto[-_]/u, "");
113
+ // Own keys only: "constructor" must not resolve to Object.prototype's.
114
+ if (Object.hasOwn(LANGUAGE_ALIASES, bare)) {
115
+ return LANGUAGE_ALIASES[bare];
116
+ }
117
+ const primary = bare.split(/[-_]/u)[0];
118
+ if (Object.hasOwn(LANGUAGE_ALIASES, primary)) {
119
+ return LANGUAGE_ALIASES[primary];
120
+ }
121
+ return /^[a-z]{2,3}$/u.test(primary) ? primary : undefined;
122
+ }
123
+ export function isIndicLanguage(language) {
124
+ const code = normalizeLanguageCode(language);
125
+ return code !== undefined && INDIC_LANGUAGES.has(code);
126
+ }
127
+ /** Letter ranges per script; `Jpan` counts kana and kanji, `Hani` kanji/hanzi only. */
128
+ const SCRIPT_RANGES = [
129
+ ["Latn", /\p{Script=Latin}/u],
130
+ ["Deva", /\p{Script=Devanagari}/u],
131
+ ["Beng", /\p{Script=Bengali}/u],
132
+ ["Guru", /\p{Script=Gurmukhi}/u],
133
+ ["Gujr", /\p{Script=Gujarati}/u],
134
+ ["Orya", /\p{Script=Oriya}/u],
135
+ ["Taml", /\p{Script=Tamil}/u],
136
+ ["Telu", /\p{Script=Telugu}/u],
137
+ ["Knda", /\p{Script=Kannada}/u],
138
+ ["Mlym", /\p{Script=Malayalam}/u],
139
+ ["Sinh", /\p{Script=Sinhala}/u],
140
+ ["Arab", /\p{Script=Arabic}/u],
141
+ ["Cyrl", /\p{Script=Cyrillic}/u],
142
+ ["Grek", /\p{Script=Greek}/u],
143
+ ["Hebr", /\p{Script=Hebrew}/u],
144
+ ["Thai", /\p{Script=Thai}/u],
145
+ ["Hang", /\p{Script=Hangul}/u],
146
+ ["Jpan", /[\p{Script=Hiragana}\p{Script=Katakana}]/u],
147
+ ["Hani", /\p{Script=Han}/u],
148
+ ["Geor", /\p{Script=Georgian}/u],
149
+ ["Armn", /\p{Script=Armenian}/u],
150
+ ["Ethi", /\p{Script=Ethiopic}/u],
151
+ ];
152
+ /** The script a language is written in when no script subtag says otherwise. */
153
+ const DEFAULT_SCRIPT = {
154
+ hi: "Deva",
155
+ mr: "Deva",
156
+ ne: "Deva",
157
+ sa: "Deva",
158
+ mai: "Deva",
159
+ kok: "Deva",
160
+ doi: "Deva",
161
+ brx: "Deva",
162
+ bn: "Beng",
163
+ as: "Beng",
164
+ mni: "Beng",
165
+ pa: "Guru",
166
+ gu: "Gujr",
167
+ or: "Orya",
168
+ ta: "Taml",
169
+ te: "Telu",
170
+ kn: "Knda",
171
+ ml: "Mlym",
172
+ si: "Sinh",
173
+ ur: "Arab",
174
+ ar: "Arab",
175
+ fa: "Arab",
176
+ ps: "Arab",
177
+ sd: "Arab",
178
+ ks: "Arab",
179
+ ru: "Cyrl",
180
+ uk: "Cyrl",
181
+ bg: "Cyrl",
182
+ sr: "Cyrl",
183
+ mk: "Cyrl",
184
+ be: "Cyrl",
185
+ kk: "Cyrl",
186
+ el: "Grek",
187
+ he: "Hebr",
188
+ yi: "Hebr",
189
+ th: "Thai",
190
+ ko: "Hang",
191
+ ja: "Jpan",
192
+ zh: "Hani",
193
+ yue: "Hani",
194
+ ka: "Geor",
195
+ hy: "Armn",
196
+ am: "Ethi",
197
+ };
198
+ /** Letters of each script in `text`; scripts with none are absent. */
199
+ export function scriptCounts(text) {
200
+ const counts = new Map();
201
+ for (const ch of text) {
202
+ for (const [script, re] of SCRIPT_RANGES) {
203
+ if (re.test(ch)) {
204
+ counts.set(script, (counts.get(script) ?? 0) + 1);
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ return counts;
210
+ }
211
+ /** The script most letters of `text` are in, `undefined` for text without letters. */
212
+ export function dominantScript(text) {
213
+ let best;
214
+ let bestCount = 0;
215
+ for (const [script, count] of scriptCounts(text)) {
216
+ if (count > bestCount) {
217
+ best = script;
218
+ bestCount = count;
219
+ }
220
+ }
221
+ return best;
222
+ }
223
+ const INDIC_SCRIPT = /[\p{Script=Devanagari}\p{Script=Bengali}\p{Script=Gurmukhi}\p{Script=Gujarati}\p{Script=Oriya}\p{Script=Tamil}\p{Script=Telugu}\p{Script=Kannada}\p{Script=Malayalam}\p{Script=Sinhala}]/u;
224
+ /** Whether `text` holds letters of an Indic script (Devanagari through Sinhala). */
225
+ export function hasIndicScript(text) {
226
+ return INDIC_SCRIPT.test(text);
227
+ }
228
+ /**
229
+ * A BCP-47 output tag: language, optional script, optional region
230
+ * (`es`, `fr-CA`, `hi-Latn`, `zh-Hant`). `undefined` for anything else.
231
+ */
232
+ export function parseOutputTag(tag) {
233
+ const match = /^([a-z]{2,3})(?:-([A-Za-z]{4}))?(?:-([A-Za-z]{2}|\d{3}))?$/iu.exec(tag.trim());
234
+ if (!match) {
235
+ return undefined;
236
+ }
237
+ const language = match[1].toLowerCase();
238
+ const subtag = match[2]
239
+ ? `${match[2][0].toUpperCase()}${match[2].slice(1).toLowerCase()}`
240
+ : undefined;
241
+ // Traditional and Simplified Chinese are both Han for the guard.
242
+ const script = subtag === "Hant" || subtag === "Hans"
243
+ ? "Hani"
244
+ : (SCRIPT_RANGES.find(([s]) => s === subtag)?.[0] ??
245
+ (subtag ? undefined : (DEFAULT_SCRIPT[language] ?? "Latn")));
246
+ if (!script) {
247
+ return undefined;
248
+ }
249
+ return {
250
+ tag: tag.trim(),
251
+ language,
252
+ script,
253
+ explicitScript: subtag !== undefined,
254
+ };
255
+ }
256
+ /** Share of `text`'s letters written in `script` (Japanese accepts kanji as well as kana). */
257
+ export function scriptShare(text, script) {
258
+ const counts = scriptCounts(text);
259
+ let total = 0;
260
+ for (const count of counts.values()) {
261
+ total += count;
262
+ }
263
+ if (total === 0) {
264
+ return 1;
265
+ }
266
+ const own = (counts.get(script) ?? 0) +
267
+ (script === "Jpan" ? (counts.get("Hani") ?? 0) : 0);
268
+ return own / total;
269
+ }
270
+ /** "es" → "Spanish", "hi-Latn" → "Hindi (Latin script)"; the tag itself when the runtime cannot name it. */
271
+ export function describeOutputTag(tag) {
272
+ const parsed = parseOutputTag(tag);
273
+ if (!parsed) {
274
+ return tag;
275
+ }
276
+ const name = displayName("language", parsed.language);
277
+ if (!parsed.explicitScript) {
278
+ return name;
279
+ }
280
+ return `${name} (${displayName("script", parsed.script)} script)`;
281
+ }
282
+ /** The English name of a language or script code; the code itself when the runtime has no name for it. */
283
+ function displayName(type, code) {
284
+ try {
285
+ return new Intl.DisplayNames(["en"], { type }).of(code) ?? code;
286
+ }
287
+ catch {
288
+ return code;
289
+ }
290
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared plumbing for the model files NeuroLink ships or fetches on first
3
+ * use (Whistle's runtime and model, the Silero VAD model): where the
4
+ * package's own `models/` directory is, whether a file on disk is the pinned
5
+ * one, and a verified atomic download. Each model keeps its own manifest and
6
+ * directory rules; this module only ever looks at one file at a time.
7
+ *
8
+ * @module voice/modelAssets
9
+ */
10
+ import type { LocalModelAsset } from "../types/index.js";
11
+ /** Per-file download deadline; generous because Whistle's model is 17 MB. */
12
+ export declare const MODEL_DOWNLOAD_TIMEOUT_MS: number;
13
+ /**
14
+ * The package's own `models` directory, found by walking up from this module
15
+ * to the directory holding NeuroLink's `package.json`. Works from the source
16
+ * tree, from `dist/` and from the single-file bundles, since all of them sit
17
+ * below the package root; `null` when nothing above looks like the package.
18
+ */
19
+ export declare function packageModelsDir(): string | null;
20
+ /** Whether the file at `path` is `asset`: size first (cheap), then the SHA-256. */
21
+ export declare function checkModelFile(path: string, asset: LocalModelAsset): Promise<"ok" | "missing" | "corrupt">;
22
+ export declare function hashModelFile(path: string): Promise<string>;
23
+ /**
24
+ * Fetches `asset` into `dir`, verifying the SHA-256 before anything is
25
+ * written and writing atomically (temp file + rename), so an interrupted
26
+ * download never leaves a file that looks complete. `label` prefixes the log.
27
+ */
28
+ export declare function downloadModelAsset(asset: LocalModelAsset, dir: string, label: string): Promise<void>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shared plumbing for the model files NeuroLink ships or fetches on first
3
+ * use (Whistle's runtime and model, the Silero VAD model): where the
4
+ * package's own `models/` directory is, whether a file on disk is the pinned
5
+ * one, and a verified atomic download. Each model keeps its own manifest and
6
+ * directory rules; this module only ever looks at one file at a time.
7
+ *
8
+ * @module voice/modelAssets
9
+ */
10
+ import { createHash, randomBytes } from "node:crypto";
11
+ import { createReadStream, existsSync, readFileSync } from "node:fs";
12
+ import { rename, rm, stat, writeFile } from "node:fs/promises";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { logger } from "../utils/logger.js";
16
+ /** Per-file download deadline; generous because Whistle's model is 17 MB. */
17
+ export const MODEL_DOWNLOAD_TIMEOUT_MS = 10 * 60_000;
18
+ let modelsDir;
19
+ /**
20
+ * The package's own `models` directory, found by walking up from this module
21
+ * to the directory holding NeuroLink's `package.json`. Works from the source
22
+ * tree, from `dist/` and from the single-file bundles, since all of them sit
23
+ * below the package root; `null` when nothing above looks like the package.
24
+ */
25
+ export function packageModelsDir() {
26
+ if (modelsDir !== undefined) {
27
+ return modelsDir;
28
+ }
29
+ modelsDir = null;
30
+ let dir;
31
+ try {
32
+ dir = dirname(fileURLToPath(import.meta.url));
33
+ }
34
+ catch {
35
+ return modelsDir;
36
+ }
37
+ for (let depth = 0; depth < 8; depth += 1) {
38
+ const manifest = join(dir, "package.json");
39
+ if (existsSync(manifest)) {
40
+ try {
41
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
42
+ if (parsed.name === "@juspay/neurolink") {
43
+ modelsDir = join(dir, "models");
44
+ return modelsDir;
45
+ }
46
+ }
47
+ catch {
48
+ // Not a manifest we can read; keep walking up.
49
+ }
50
+ }
51
+ const parent = dirname(dir);
52
+ if (parent === dir) {
53
+ break;
54
+ }
55
+ dir = parent;
56
+ }
57
+ return modelsDir;
58
+ }
59
+ /** Whether the file at `path` is `asset`: size first (cheap), then the SHA-256. */
60
+ export async function checkModelFile(path, asset) {
61
+ try {
62
+ const info = await stat(path);
63
+ if (!info.isFile()) {
64
+ return "missing";
65
+ }
66
+ if (info.size !== asset.bytes) {
67
+ return "corrupt";
68
+ }
69
+ }
70
+ catch {
71
+ return "missing";
72
+ }
73
+ return (await hashModelFile(path)) === asset.sha256 ? "ok" : "corrupt";
74
+ }
75
+ export function hashModelFile(path) {
76
+ return new Promise((resolvePromise, reject) => {
77
+ const hash = createHash("sha256");
78
+ createReadStream(path)
79
+ .on("data", (chunk) => hash.update(chunk))
80
+ .on("error", reject)
81
+ .on("end", () => resolvePromise(hash.digest("hex")));
82
+ });
83
+ }
84
+ /**
85
+ * Fetches `asset` into `dir`, verifying the SHA-256 before anything is
86
+ * written and writing atomically (temp file + rename), so an interrupted
87
+ * download never leaves a file that looks complete. `label` prefixes the log.
88
+ */
89
+ export async function downloadModelAsset(asset, dir, label) {
90
+ const response = await fetch(asset.url, {
91
+ signal: AbortSignal.timeout(MODEL_DOWNLOAD_TIMEOUT_MS),
92
+ redirect: "follow",
93
+ });
94
+ if (!response.ok) {
95
+ throw new Error(`${asset.name}: HTTP ${response.status}`);
96
+ }
97
+ const body = Buffer.from(await response.arrayBuffer());
98
+ const digest = createHash("sha256").update(body).digest("hex");
99
+ if (digest !== asset.sha256) {
100
+ throw new Error(`${asset.name}: SHA-256 mismatch (expected ${asset.sha256}, got ${digest})`);
101
+ }
102
+ const target = join(dir, asset.name);
103
+ const temp = join(dir, `.${asset.name}.${process.pid}.${randomBytes(4).toString("hex")}.part`);
104
+ try {
105
+ await writeFile(temp, body);
106
+ await rename(temp, target);
107
+ }
108
+ catch (error) {
109
+ await rm(temp, { force: true }).catch(() => undefined);
110
+ throw error;
111
+ }
112
+ logger.debug(`[${label}] ${asset.name} verified and saved to ${target}`);
113
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Output languages: the transcript rendered in other languages and scripts.
3
+ *
4
+ * One structured model call renders every requested target for a batch of
5
+ * lines (an utterance, or up to 20 segments of a recording), from the
6
+ * native-script engine text plus its corrected form, with earlier lines and
7
+ * their renderings as context. Targets that are the transcript's own
8
+ * language and script need no call at all.
9
+ *
10
+ * A rendering is never allowed to cost the transcript anything: it runs
11
+ * after the text is settled, fails open, and reports what happened in its
12
+ * `status`. The guards refuse an answer that is empty, in the wrong script
13
+ * for its tag, or the source handed back untranslated; a refused target gets
14
+ * one retry on its own.
15
+ *
16
+ * @module voice/output/render
17
+ */
18
+ import type { STTCorrectionDeps, STTOutputOptions, STTOutputRenderInput, STTOutputRendering } from "../../types/index.js";
19
+ /** Distinct targets accepted per request. */
20
+ export declare const MAX_OUTPUT_LANGUAGES = 8;
21
+ /** The tag that means "the transcript as it is". */
22
+ export declare const SOURCE_OUTPUT = "source";
23
+ /**
24
+ * The output languages in effect right now: read from a live source when
25
+ * one was given, trimmed, de-duplicated (case-insensitively) and capped.
26
+ */
27
+ export declare function outputLanguagesOf(output: STTOutputOptions | undefined): string[];
28
+ /**
29
+ * Render one transcript (one or more lines) into every requested language.
30
+ * Returns one entry per requested tag, keyed by the tag as given. Never throws.
31
+ */
32
+ export declare function renderOutputs(input: STTOutputRenderInput, deps: Pick<STTCorrectionDeps, "render">): Promise<Record<string, STTOutputRendering>>;