@juspay/neurolink 13.6.5 → 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.
Files changed (87) hide show
  1. package/CHANGELOG.md +6 -2
  2. package/dist/auth/sessionManager.js +8 -3
  3. package/dist/browser/neurolink.min.js +480 -460
  4. package/dist/cli/commands/serve.js +11 -1
  5. package/dist/cli/commands/transcribe.js +39 -3
  6. package/dist/constants/langfuse.d.ts +2 -0
  7. package/dist/constants/langfuse.js +2 -0
  8. package/dist/core/redisConversationMemoryManager.js +6 -16
  9. package/dist/factories/sttDescriptors.js +7 -0
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.js +4 -2
  12. package/dist/neurolink.js +33 -1
  13. package/dist/observability/exporters/langfuseExporter.d.ts +4 -0
  14. package/dist/observability/exporters/langfuseExporter.js +29 -8
  15. package/dist/observability/otlpEncoding.js +2 -2
  16. package/dist/server/abstract/baseServerAdapter.js +2 -0
  17. package/dist/server/adapters/expressAdapter.js +1 -1
  18. package/dist/server/adapters/fastifyAdapter.js +4 -1
  19. package/dist/server/adapters/honoAdapter.js +1 -1
  20. package/dist/server/adapters/koaAdapter.js +1 -1
  21. package/dist/server/routes/transcribeRoutes.d.ts +0 -8
  22. package/dist/server/routes/transcribeRoutes.js +130 -3
  23. package/dist/server/utils/validation.d.ts +409 -15
  24. package/dist/server/utils/validation.js +106 -4
  25. package/dist/server/websocket/frameQueue.d.ts +16 -0
  26. package/dist/server/websocket/frameQueue.js +55 -0
  27. package/dist/server/websocket/realtimeTranscription.d.ts +48 -0
  28. package/dist/server/websocket/realtimeTranscription.js +603 -0
  29. package/dist/server/websocket/transcribeWebSocket.d.ts +17 -1
  30. package/dist/server/websocket/transcribeWebSocket.js +263 -64
  31. package/dist/services/server/ai/observability/langfuseSetup.js +6 -1
  32. package/dist/telemetry/attributes.d.ts +1 -1
  33. package/dist/telemetry/attributes.js +84 -8
  34. package/dist/types/cli.d.ts +6 -0
  35. package/dist/types/exporter.d.ts +3 -1
  36. package/dist/types/server.d.ts +304 -2
  37. package/dist/types/stt.d.ts +38 -3
  38. package/dist/types/transcribe.d.ts +401 -5
  39. package/dist/utils/redis.d.ts +17 -0
  40. package/dist/utils/redis.js +31 -0
  41. package/dist/voice/audioLevel.d.ts +14 -0
  42. package/dist/voice/audioLevel.js +60 -0
  43. package/dist/voice/codeMix.d.ts +40 -0
  44. package/dist/voice/codeMix.js +282 -0
  45. package/dist/voice/correction/align.d.ts +12 -0
  46. package/dist/voice/correction/align.js +48 -0
  47. package/dist/voice/correction/guards.d.ts +39 -0
  48. package/dist/voice/correction/guards.js +122 -0
  49. package/dist/voice/correction/index.d.ts +25 -3
  50. package/dist/voice/correction/index.js +132 -4
  51. package/dist/voice/correction/rewrite.d.ts +64 -3
  52. package/dist/voice/correction/rewrite.js +295 -25
  53. package/dist/voice/eval/sttEvalLangfuse.d.ts +14 -0
  54. package/dist/voice/eval/sttEvalLangfuse.js +182 -0
  55. package/dist/voice/eval/sttEvalStore.d.ts +27 -0
  56. package/dist/voice/eval/sttEvalStore.js +397 -0
  57. package/dist/voice/index.js +3 -0
  58. package/dist/voice/languages.d.ts +34 -0
  59. package/dist/voice/languages.js +290 -0
  60. package/dist/voice/modelAssets.d.ts +28 -0
  61. package/dist/voice/modelAssets.js +113 -0
  62. package/dist/voice/output/render.d.ts +32 -0
  63. package/dist/voice/output/render.js +368 -0
  64. package/dist/voice/providers/OpenAISTT.d.ts +11 -1
  65. package/dist/voice/providers/OpenAISTT.js +57 -6
  66. package/dist/voice/session/transcriptionSession.d.ts +55 -0
  67. package/dist/voice/session/transcriptionSession.js +225 -0
  68. package/dist/voice/streaming/chunkedStreamAdapter.d.ts +7 -0
  69. package/dist/voice/streaming/chunkedStreamAdapter.js +711 -28
  70. package/dist/voice/streaming/endpointer.d.ts +50 -17
  71. package/dist/voice/streaming/endpointer.js +115 -19
  72. package/dist/voice/transcribe.d.ts +18 -1
  73. package/dist/voice/transcribe.js +459 -18
  74. package/dist/voice/transcribeStream.d.ts +18 -1
  75. package/dist/voice/transcribeStream.js +413 -16
  76. package/dist/voice/vad/assets.d.ts +36 -0
  77. package/dist/voice/vad/assets.js +142 -0
  78. package/dist/voice/vad/frameScorer.d.ts +28 -0
  79. package/dist/voice/vad/frameScorer.js +90 -0
  80. package/dist/voice/vad/sileroVad.d.ts +43 -0
  81. package/dist/voice/vad/sileroVad.js +178 -0
  82. package/dist/voice/whistle/assets.d.ts +3 -4
  83. package/dist/voice/whistle/assets.js +11 -95
  84. package/docs-site/static/search-index.json +21 -6
  85. package/models/silero-vad/README.md +19 -0
  86. package/models/silero-vad/silero_vad.onnx +0 -0
  87. package/package.json +4 -1
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Transcription sessions: one call, several speakers, every listener in the
3
+ * languages they chose.
4
+ *
5
+ * Each speaker is its own `transcribeStream()` (a call platform gives one
6
+ * track per participant). The session's output languages are the union of
7
+ * what its listeners read right now, read once per utterance, so ten
8
+ * listeners in Spanish cost one rendering and a language nobody reads costs
9
+ * nothing. A listener receives the transcript events of the speakers it
10
+ * follows and only the `output` events of its own languages.
11
+ *
12
+ * Late joiners get the backlog: the last `backlog` utterances, with every
13
+ * rendering that was made for them. A language added later applies from the
14
+ * next utterance on.
15
+ *
16
+ * @module voice/session/transcriptionSession
17
+ */
18
+ import { randomUUID } from "node:crypto";
19
+ import { logger } from "../../utils/logger.js";
20
+ import { MAX_OUTPUT_LANGUAGES, outputLanguagesOf } from "../output/render.js";
21
+ const DEFAULT_BACKLOG = 50;
22
+ function normalizeTags(tags) {
23
+ const seen = new Set();
24
+ const out = [];
25
+ for (const tag of tags ?? []) {
26
+ const trimmed = typeof tag === "string" ? tag.trim() : "";
27
+ if (trimmed && !seen.has(trimmed.toLowerCase())) {
28
+ seen.add(trimmed.toLowerCase());
29
+ out.push(trimmed);
30
+ }
31
+ }
32
+ return out;
33
+ }
34
+ class Subscriber {
35
+ deliver;
36
+ interim;
37
+ languages;
38
+ speakers;
39
+ constructor(options, deliver, interim) {
40
+ this.deliver = deliver;
41
+ this.interim = interim;
42
+ this.languages = normalizeTags(options.languages);
43
+ this.speakers = options.speakers?.length
44
+ ? new Set(options.speakers)
45
+ : undefined;
46
+ }
47
+ wants(event) {
48
+ if (this.speakers && !this.speakers.has(event.speaker)) {
49
+ return false;
50
+ }
51
+ if (!this.interim &&
52
+ (event.type === "interim" || event.type === "correcting")) {
53
+ return false;
54
+ }
55
+ if (event.type === "output") {
56
+ const tag = event.language.toLowerCase();
57
+ return this.languages.some((l) => l.toLowerCase() === tag);
58
+ }
59
+ return true;
60
+ }
61
+ }
62
+ export class TranscriptionSession {
63
+ neurolink;
64
+ options;
65
+ id;
66
+ subscribers = new Set();
67
+ /** Settled events per `speaker:utterance`, oldest group first. */
68
+ backlog = new Map();
69
+ backlogSize;
70
+ maxLanguages;
71
+ speakers = new Set();
72
+ _unnamed = 0;
73
+ closed = false;
74
+ constructor(neurolink, options = {}) {
75
+ this.neurolink = neurolink;
76
+ this.options = options;
77
+ this.id = options.id?.trim() || randomUUID();
78
+ this.backlogSize = Math.max(0, Math.floor(options.backlog ?? DEFAULT_BACKLOG));
79
+ this.maxLanguages = Math.max(1, Math.min(MAX_OUTPUT_LANGUAGES, Math.floor(options.maxLanguages ?? MAX_OUTPUT_LANGUAGES)));
80
+ }
81
+ /** The output languages listeners read now, in order of first request, capped. */
82
+ languages() {
83
+ return normalizeTags([...this.subscribers].flatMap((s) => s.languages)).slice(0, this.maxLanguages);
84
+ }
85
+ /** Speakers whose audio is being transcribed now. */
86
+ activeSpeakers() {
87
+ return [...this.speakers];
88
+ }
89
+ get listenerCount() {
90
+ return this.subscribers.size;
91
+ }
92
+ get isClosed() {
93
+ return this.closed;
94
+ }
95
+ /** A fallback name no speaker in this session holds (a counter, so a leaver's name is not reused). */
96
+ _unnamedSpeaker() {
97
+ let name;
98
+ do {
99
+ this._unnamed++;
100
+ name = `speaker-${this._unnamed}`;
101
+ } while (this.speakers.has(name));
102
+ return name;
103
+ }
104
+ /**
105
+ * Transcribe one speaker until its audio ends (or the session closes).
106
+ * Every event goes to the session's listeners; `onEvent`, when given, sees
107
+ * them too (the speaker's own captions). The speaker's own
108
+ * `output.languages`, if any, are rendered in addition to the listeners'.
109
+ */
110
+ async addSpeaker(speaker, options, onEvent) {
111
+ if (this.closed) {
112
+ throw new Error(`Transcription session ${this.id} is closed`);
113
+ }
114
+ const name = speaker.trim() || this._unnamedSpeaker();
115
+ if (this.speakers.has(name)) {
116
+ throw new Error(`Speaker "${name}" is already in session ${this.id}`);
117
+ }
118
+ this.speakers.add(name);
119
+ const own = options.output;
120
+ const languages = {
121
+ current: () => normalizeTags([...outputLanguagesOf(own), ...this.languages()]).slice(0, MAX_OUTPUT_LANGUAGES),
122
+ };
123
+ try {
124
+ for await (const event of this.neurolink.transcribeStream({
125
+ ...options,
126
+ output: { ...this.options.output, ...own, languages },
127
+ })) {
128
+ if (this.closed) {
129
+ break;
130
+ }
131
+ const tagged = { ...event, speaker: name };
132
+ this.remember(tagged);
133
+ this.broadcast(tagged);
134
+ try {
135
+ onEvent?.(tagged);
136
+ }
137
+ catch (error) {
138
+ logger.warn("[TranscriptionSession] speaker callback failed", {
139
+ error: error instanceof Error ? error.message : String(error),
140
+ });
141
+ }
142
+ }
143
+ }
144
+ finally {
145
+ this.speakers.delete(name);
146
+ }
147
+ }
148
+ /** Follow the session. With `backlog` (the default), the last utterances are replayed first. */
149
+ subscribe(options, onEvent) {
150
+ const subscriber = new Subscriber(options, onEvent, options.interim !== false);
151
+ if (!this.closed) {
152
+ this.subscribers.add(subscriber);
153
+ }
154
+ if (options.backlog !== false) {
155
+ for (const group of this.backlog.values()) {
156
+ for (const event of group) {
157
+ if (subscriber.wants(event)) {
158
+ this.safeDeliver(subscriber, event);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ const subscribers = this.subscribers;
164
+ return {
165
+ get languages() {
166
+ return subscriber.languages;
167
+ },
168
+ update: (next) => {
169
+ if (next.languages) {
170
+ subscriber.languages = normalizeTags(next.languages);
171
+ }
172
+ if (next.speakers) {
173
+ subscriber.speakers = next.speakers.length
174
+ ? new Set(next.speakers)
175
+ : undefined;
176
+ }
177
+ },
178
+ close: () => {
179
+ subscribers.delete(subscriber);
180
+ },
181
+ };
182
+ }
183
+ /** Stop delivering; speakers' streams end at their next event. */
184
+ close() {
185
+ this.closed = true;
186
+ this.subscribers.clear();
187
+ this.backlog.clear();
188
+ }
189
+ remember(event) {
190
+ if (this.backlogSize === 0 ||
191
+ event.type === "interim" ||
192
+ event.type === "correcting" ||
193
+ event.utterance === undefined) {
194
+ return;
195
+ }
196
+ const key = `${event.speaker}:${event.utterance}`;
197
+ const group = this.backlog.get(key) ?? [];
198
+ group.push(event);
199
+ this.backlog.set(key, group);
200
+ while (this.backlog.size > this.backlogSize) {
201
+ const oldest = this.backlog.keys().next().value;
202
+ if (oldest === undefined) {
203
+ break;
204
+ }
205
+ this.backlog.delete(oldest);
206
+ }
207
+ }
208
+ broadcast(event) {
209
+ for (const subscriber of this.subscribers) {
210
+ if (subscriber.wants(event)) {
211
+ this.safeDeliver(subscriber, event);
212
+ }
213
+ }
214
+ }
215
+ safeDeliver(subscriber, event) {
216
+ try {
217
+ subscriber.deliver(event);
218
+ }
219
+ catch (error) {
220
+ logger.warn("[TranscriptionSession] listener callback failed", {
221
+ error: error instanceof Error ? error.message : String(error),
222
+ });
223
+ }
224
+ }
225
+ }
@@ -1,4 +1,11 @@
1
1
  import type { STTChunkedRun, STTChunkedStreamOptions, TranscribeStreamEvent } from "../../types/index.js";
2
+ /**
3
+ * Digital silence appended to every chunk an engine gets, so it always ends
4
+ * in a clean pause: a chunk cut off right after speech sent Qwen3-ASR into a
5
+ * decoding loop until its timeout (180 s on a file, the utterance lost);
6
+ * 200 ms of zeros was enough to stop it, this is the margin.
7
+ */
8
+ export declare const ENGINE_TAIL_PAD_MS = 400;
2
9
  /** Minimal unbounded async queue: the producer never waits for the consumer. */
3
10
  export declare class StreamEventQueue<T> {
4
11
  private readonly items;