@workadventure/livekit-agent-plugin-fake-stt 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WorkAdventure
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # @workadventure/livekit-agent-plugin-fake-stt
2
+
3
+ Deterministic fake STT provider for `@livekit/agents`.
4
+
5
+ This plugin is designed for tests and local development where you need predictable
6
+ streaming STT events without calling external APIs.
7
+
8
+ ## Features
9
+
10
+ - deterministic streaming event sequence
11
+ - no network calls, no credentials
12
+ - configurable scripts with interim/final steps
13
+ - per-step delays and deterministic jitter
14
+ - stable `seed` + `offset` controls
15
+ - optional `RECOGNITION_USAGE` events
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @workadventure/livekit-agent-plugin-fake-stt
21
+ ```
22
+
23
+ Peer requirements:
24
+
25
+ - `@livekit/agents`
26
+ - `@livekit/rtc-node`
27
+
28
+ ## Quick Start
29
+
30
+ ```ts
31
+ import { stt } from '@livekit/agents';
32
+ import type { AudioFrame } from '@livekit/rtc-node';
33
+ import { STT } from '@workadventure/livekit-agent-plugin-fake-stt';
34
+
35
+ const fakeStt = new STT({
36
+ emitRecognitionUsage: true,
37
+ script: [['hello partial', 'hello final']],
38
+ });
39
+
40
+ const stream = fakeStt.stream();
41
+ const frame = {
42
+ data: new Int16Array(1600),
43
+ sampleRate: 16000,
44
+ channels: 1,
45
+ samplesPerChannel: 1600,
46
+ } as unknown as AudioFrame;
47
+
48
+ stream.pushFrame(frame);
49
+ stream.endInput();
50
+
51
+ for await (const event of stream) {
52
+ switch (event.type) {
53
+ case stt.SpeechEventType.INTERIM_TRANSCRIPT:
54
+ case stt.SpeechEventType.FINAL_TRANSCRIPT:
55
+ console.log(event.alternatives?.[0]?.text ?? '');
56
+ break;
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Event Model
62
+
63
+ For each utterance (audio frames up to `flush()` or `endInput()`), the stream emits:
64
+
65
+ 1. `START_OF_SPEECH`
66
+ 2. one or more `INTERIM_TRANSCRIPT` / `FINAL_TRANSCRIPT` steps from the configured script
67
+ 3. `END_OF_SPEECH`
68
+ 4. optional `RECOGNITION_USAGE`
69
+
70
+ Default script:
71
+
72
+ - Segment A: interim, interim, final
73
+ - Segment B: interim, final
74
+
75
+ Segments are reused in deterministic round-robin order.
76
+
77
+ ## Configuration
78
+
79
+ ```ts
80
+ import { STT } from '@workadventure/livekit-agent-plugin-fake-stt';
81
+
82
+ const fakeStt = new STT({
83
+ language: 'en-US',
84
+ defaultStepDelayMs: 10,
85
+ jitterMs: 2,
86
+ seed: 1234,
87
+ offset: 1,
88
+ emitRecognitionUsage: true,
89
+ script: [
90
+ [
91
+ { text: 'segment-1 partial', final: false, delayMs: 20 },
92
+ { text: 'segment-1 final', final: true },
93
+ ],
94
+ {
95
+ emitUsage: false,
96
+ steps: ['segment-2 partial', 'segment-2 final'],
97
+ },
98
+ ],
99
+ });
100
+ ```
101
+
102
+ ### `FakeSTTOptions`
103
+
104
+ - `script`: deterministic transcript script. Array of segments.
105
+ - `language`: default language on transcript alternatives.
106
+ - `defaultStepDelayMs`: delay used when a step has no explicit `delayMs`.
107
+ - `jitterMs`: deterministic signed jitter added to each step delay.
108
+ - `seed`: stable seed used for script offset and jitter generation.
109
+ - `offset`: deterministic script cursor offset.
110
+ - `emitRecognitionUsage`: emit usage events for each utterance.
111
+ - `sampleRate`: expected input sample rate for stream resampling behavior.
112
+
113
+ ## Deterministic Testing Recipes
114
+
115
+ ### Stable transcript snapshots
116
+
117
+ - set `defaultStepDelayMs: 0`
118
+ - set `jitterMs: 0`
119
+ - use explicit script text values
120
+
121
+ ### Seeded timing simulation
122
+
123
+ - set `defaultStepDelayMs` and `jitterMs`
124
+ - pin `seed` so delay jitter stays reproducible in CI
125
+
126
+ ### Script partitioning by utterance
127
+
128
+ - call `flush()` between user turns
129
+ - each flush/end advances to the next script segment
130
+
131
+ ## Examples
132
+
133
+ - `examples/basic-stream.mjs`
134
+ - `examples/seeded-script.mjs`
135
+
136
+ Build first, then run:
137
+
138
+ ```bash
139
+ npm run build
140
+ node examples/basic-stream.mjs
141
+ ```
142
+
143
+ ## Development
144
+
145
+ ```bash
146
+ npm install
147
+ npm run lint
148
+ npm run typecheck
149
+ npm test
150
+ npm run build
151
+ ```
152
+
153
+ ## License
154
+
155
+ MIT
@@ -0,0 +1,71 @@
1
+ import { stt, AudioBuffer, APIConnectOptions } from '@livekit/agents';
2
+
3
+ interface FakeSTTScriptStep {
4
+ text: string;
5
+ final?: boolean;
6
+ delayMs?: number;
7
+ confidence?: number;
8
+ language?: string;
9
+ }
10
+ type FakeSTTScriptStepInput = string | FakeSTTScriptStep;
11
+ interface FakeSTTScriptSegment {
12
+ steps: FakeSTTScriptStepInput[];
13
+ emitUsage?: boolean;
14
+ usageAudioDuration?: number;
15
+ }
16
+ type FakeSTTScriptSegmentInput = FakeSTTScriptSegment | FakeSTTScriptStepInput[];
17
+ interface FakeSTTOptions {
18
+ script?: FakeSTTScriptSegmentInput[];
19
+ language?: string;
20
+ defaultStepDelayMs?: number;
21
+ jitterMs?: number;
22
+ seed?: number;
23
+ offset?: number;
24
+ emitRecognitionUsage?: boolean;
25
+ sampleRate?: number;
26
+ silenceDurationMsToCommit?: number;
27
+ silenceAmplitudeThreshold?: number;
28
+ }
29
+ interface NormalizedStep {
30
+ text: string;
31
+ final: boolean;
32
+ delayMs?: number;
33
+ confidence?: number;
34
+ language?: string;
35
+ }
36
+ interface NormalizedSegment {
37
+ steps: NormalizedStep[];
38
+ emitUsage?: boolean;
39
+ usageAudioDuration?: number;
40
+ }
41
+ interface ResolvedOptions {
42
+ script: NormalizedSegment[];
43
+ language: string;
44
+ defaultStepDelayMs: number;
45
+ jitterMs: number;
46
+ seed: number;
47
+ initialOffset: number;
48
+ emitRecognitionUsage: boolean;
49
+ sampleRate: number;
50
+ silenceDurationMsToCommit: number;
51
+ silenceAmplitudeThreshold: number;
52
+ }
53
+ declare const DEFAULT_SCRIPT: FakeSTTScriptSegmentInput[];
54
+ declare class STT extends stt.STT {
55
+ #private;
56
+ readonly label = "fake.STT";
57
+ constructor(options?: FakeSTTOptions);
58
+ get options(): Readonly<ResolvedOptions>;
59
+ protected _recognize(frame: AudioBuffer, abortSignal?: AbortSignal): Promise<stt.SpeechEvent>;
60
+ stream(options?: {
61
+ connOptions?: APIConnectOptions;
62
+ }): SpeechStream;
63
+ }
64
+ declare class SpeechStream extends stt.SpeechStream {
65
+ #private;
66
+ readonly label = "fake.SpeechStream";
67
+ constructor(sttImpl: STT, options: ResolvedOptions, connOptions?: APIConnectOptions);
68
+ protected run(): Promise<void>;
69
+ }
70
+
71
+ export { DEFAULT_SCRIPT, type FakeSTTOptions, type FakeSTTScriptSegment, type FakeSTTScriptSegmentInput, type FakeSTTScriptStep, type FakeSTTScriptStepInput, STT, SpeechStream };
package/dist/index.js ADDED
@@ -0,0 +1,377 @@
1
+ import { stt, mergeFrames } from '@livekit/agents';
2
+
3
+ // src/stt.ts
4
+ var DEFAULT_SCRIPT = [
5
+ [
6
+ { text: "segment-a partial-1", final: false },
7
+ { text: "segment-a partial-2", final: false },
8
+ { text: "segment-a final", final: true }
9
+ ],
10
+ [
11
+ { text: "segment-b partial", final: false },
12
+ { text: "segment-b final", final: true }
13
+ ]
14
+ ];
15
+ var DEFAULT_OPTIONS = {
16
+ language: "en-US",
17
+ defaultStepDelayMs: 0,
18
+ jitterMs: 0,
19
+ seed: 0,
20
+ offset: 0,
21
+ emitRecognitionUsage: false,
22
+ sampleRate: 16e3,
23
+ silenceDurationMsToCommit: 400,
24
+ silenceAmplitudeThreshold: 150
25
+ };
26
+ var STT = class extends stt.STT {
27
+ label = "fake.STT";
28
+ #options;
29
+ #recognizeCount = 0;
30
+ constructor(options = {}) {
31
+ super({
32
+ streaming: true,
33
+ interimResults: true,
34
+ alignedTranscript: false
35
+ });
36
+ this.#options = resolveOptions(options);
37
+ }
38
+ get options() {
39
+ return this.#options;
40
+ }
41
+ async _recognize(frame, abortSignal) {
42
+ if (abortSignal?.aborted) {
43
+ throw abortError();
44
+ }
45
+ const merged = mergeFrames(frame);
46
+ const duration = frameDurationSeconds(merged.samplesPerChannel, merged.sampleRate);
47
+ const segment = this.#segmentForRecognize();
48
+ const transcript = segment.steps.filter((step) => step.final).map((step) => step.text).join(" ").trim() || segment.steps[segment.steps.length - 1]?.text || "";
49
+ return {
50
+ type: stt.SpeechEventType.FINAL_TRANSCRIPT,
51
+ requestId: `fake-stt-recognize-${this.#recognizeCount - 1}`,
52
+ alternatives: [
53
+ {
54
+ text: transcript,
55
+ language: this.#options.language,
56
+ startTime: 0,
57
+ endTime: duration,
58
+ confidence: 1
59
+ }
60
+ ]
61
+ };
62
+ }
63
+ stream(options) {
64
+ return new SpeechStream(this, this.#options, options?.connOptions);
65
+ }
66
+ #segmentForRecognize() {
67
+ const segment = getSegmentAt(
68
+ this.#options.script,
69
+ positiveModulo(
70
+ this.#options.initialOffset + this.#recognizeCount,
71
+ this.#options.script.length
72
+ )
73
+ );
74
+ this.#recognizeCount += 1;
75
+ return segment;
76
+ }
77
+ };
78
+ var SpeechStream = class _SpeechStream extends stt.SpeechStream {
79
+ label = "fake.SpeechStream";
80
+ #options;
81
+ #cursor;
82
+ #utteranceCount = 0;
83
+ #pendingAudioDuration = 0;
84
+ #hasPendingAudio = false;
85
+ #hasDetectedSpeech = false;
86
+ #pendingSilenceDurationMs = 0;
87
+ #processedAudioDuration = 0;
88
+ constructor(sttImpl, options, connOptions) {
89
+ super(sttImpl, options.sampleRate, connOptions);
90
+ this.#options = options;
91
+ this.#cursor = options.initialOffset;
92
+ }
93
+ async run() {
94
+ try {
95
+ for await (const item of this.input) {
96
+ if (this.abortSignal.aborted || this.closed) {
97
+ return;
98
+ }
99
+ if (item === _SpeechStream.FLUSH_SENTINEL) {
100
+ await this.#emitPendingUtterance();
101
+ continue;
102
+ }
103
+ const duration = frameDurationSeconds(item.samplesPerChannel, item.sampleRate);
104
+ if (duration <= 0) {
105
+ continue;
106
+ }
107
+ this.#hasPendingAudio = true;
108
+ this.#pendingAudioDuration += duration;
109
+ const frameDurationMs = duration * 1e3;
110
+ if (isSilentAudioFrame(item, this.#options.silenceAmplitudeThreshold)) {
111
+ if (this.#hasDetectedSpeech) {
112
+ this.#pendingSilenceDurationMs += frameDurationMs;
113
+ }
114
+ if (this.#hasDetectedSpeech && this.#pendingSilenceDurationMs >= this.#options.silenceDurationMsToCommit) {
115
+ await this.#emitPendingUtterance();
116
+ }
117
+ continue;
118
+ }
119
+ this.#hasDetectedSpeech = true;
120
+ this.#pendingSilenceDurationMs = 0;
121
+ }
122
+ await this.#emitPendingUtterance();
123
+ } finally {
124
+ this.closed = true;
125
+ }
126
+ }
127
+ async #emitPendingUtterance() {
128
+ if (!this.#hasPendingAudio || this.abortSignal.aborted || this.closed) {
129
+ this.#resetPendingAudio();
130
+ return;
131
+ }
132
+ const utteranceIndex = this.#utteranceCount;
133
+ const requestId = `fake-stt-${utteranceIndex}`;
134
+ const segmentAudioDuration = this.#pendingAudioDuration;
135
+ const segmentStart = this.startTimeOffset + this.#processedAudioDuration;
136
+ const segment = this.#nextSegment();
137
+ this.#processedAudioDuration += segmentAudioDuration;
138
+ this.#utteranceCount += 1;
139
+ this.#resetPendingAudio();
140
+ if (!this.#putEvent({ type: stt.SpeechEventType.START_OF_SPEECH, requestId })) {
141
+ return;
142
+ }
143
+ const stepsCount = segment.steps.length;
144
+ for (const [stepIndex, step] of segment.steps.entries()) {
145
+ const delayMs = this.#computeDelay(step, utteranceIndex, stepIndex);
146
+ const continueEmission = await sleepWithAbort(delayMs, this.abortSignal);
147
+ if (!continueEmission || this.closed) {
148
+ return;
149
+ }
150
+ const eventType = step.final ? stt.SpeechEventType.FINAL_TRANSCRIPT : stt.SpeechEventType.INTERIM_TRANSCRIPT;
151
+ const stepEnd = segmentStart + segmentAudioDuration * ((stepIndex + 1) / stepsCount);
152
+ const transcriptEvent = {
153
+ type: eventType,
154
+ requestId,
155
+ alternatives: [
156
+ {
157
+ text: step.text,
158
+ language: step.language ?? this.#options.language,
159
+ startTime: segmentStart,
160
+ endTime: stepEnd,
161
+ confidence: step.confidence ?? (step.final ? 0.95 : 0.65)
162
+ }
163
+ ]
164
+ };
165
+ if (!this.#putEvent(transcriptEvent)) {
166
+ return;
167
+ }
168
+ }
169
+ if (!this.#putEvent({ type: stt.SpeechEventType.END_OF_SPEECH, requestId })) {
170
+ return;
171
+ }
172
+ const shouldEmitUsage = segment.emitUsage ?? this.#options.emitRecognitionUsage;
173
+ if (!shouldEmitUsage) {
174
+ return;
175
+ }
176
+ this.#putEvent({
177
+ type: stt.SpeechEventType.RECOGNITION_USAGE,
178
+ requestId,
179
+ recognitionUsage: {
180
+ audioDuration: segment.usageAudioDuration ?? segmentAudioDuration
181
+ }
182
+ });
183
+ }
184
+ #nextSegment() {
185
+ const segment = getSegmentAt(this.#options.script, this.#cursor);
186
+ this.#cursor = positiveModulo(this.#cursor + 1, this.#options.script.length);
187
+ return segment;
188
+ }
189
+ #computeDelay(step, utteranceIndex, stepIndex) {
190
+ const baseDelay = step.delayMs ?? this.#options.defaultStepDelayMs;
191
+ if (this.#options.jitterMs <= 0) {
192
+ return baseDelay;
193
+ }
194
+ const mixed = hash32(
195
+ toUint32(this.#options.seed) ^ toUint32((utteranceIndex + 1) * 2654435761) ^ toUint32((stepIndex + 1) * 2246822507)
196
+ );
197
+ const unit = mixed / 4294967295;
198
+ const jitter = Math.round((unit * 2 - 1) * this.#options.jitterMs);
199
+ return Math.max(0, baseDelay + jitter);
200
+ }
201
+ #putEvent(event) {
202
+ if (this.closed || this.abortSignal.aborted || this.queue.closed) {
203
+ return false;
204
+ }
205
+ try {
206
+ this.queue.put(event);
207
+ return true;
208
+ } catch {
209
+ return false;
210
+ }
211
+ }
212
+ #resetPendingAudio() {
213
+ this.#hasPendingAudio = false;
214
+ this.#hasDetectedSpeech = false;
215
+ this.#pendingAudioDuration = 0;
216
+ this.#pendingSilenceDurationMs = 0;
217
+ }
218
+ };
219
+ function resolveOptions(options) {
220
+ const script = normalizeScript(options.script ?? DEFAULT_SCRIPT);
221
+ if (script.length === 0) {
222
+ throw new Error("Fake STT script must contain at least one segment");
223
+ }
224
+ const seed = asInteger(options.seed ?? DEFAULT_OPTIONS.seed);
225
+ const offset = asInteger(options.offset ?? DEFAULT_OPTIONS.offset);
226
+ const seedOffset = options.seed === void 0 ? 0 : positiveModulo(hash32(seed), script.length);
227
+ const initialOffset = positiveModulo(offset + seedOffset, script.length);
228
+ return {
229
+ script,
230
+ language: options.language ?? DEFAULT_OPTIONS.language,
231
+ defaultStepDelayMs: clampNumber(
232
+ options.defaultStepDelayMs ?? DEFAULT_OPTIONS.defaultStepDelayMs,
233
+ 0
234
+ ),
235
+ jitterMs: clampNumber(options.jitterMs ?? DEFAULT_OPTIONS.jitterMs, 0),
236
+ seed,
237
+ initialOffset,
238
+ emitRecognitionUsage: options.emitRecognitionUsage ?? DEFAULT_OPTIONS.emitRecognitionUsage,
239
+ sampleRate: clampNumber(options.sampleRate ?? DEFAULT_OPTIONS.sampleRate, 1),
240
+ silenceDurationMsToCommit: clampNumber(
241
+ options.silenceDurationMsToCommit ?? DEFAULT_OPTIONS.silenceDurationMsToCommit,
242
+ 0
243
+ ),
244
+ silenceAmplitudeThreshold: clampNumber(
245
+ options.silenceAmplitudeThreshold ?? DEFAULT_OPTIONS.silenceAmplitudeThreshold,
246
+ 0
247
+ )
248
+ };
249
+ }
250
+ function normalizeScript(script) {
251
+ return script.map((segmentInput, segmentIndex) => {
252
+ const segment = Array.isArray(segmentInput) ? { steps: segmentInput } : segmentInput;
253
+ if (!segment.steps.length) {
254
+ throw new Error(`Fake STT segment at index ${segmentIndex} is empty`);
255
+ }
256
+ const steps = segment.steps.map(
257
+ (stepInput, stepIndex) => normalizeStep(stepInput, segmentIndex, stepIndex)
258
+ );
259
+ if (!steps.some((step) => step.final)) {
260
+ const last = steps[steps.length - 1];
261
+ if (!last) {
262
+ throw new Error(`Fake STT segment at index ${segmentIndex} is empty`);
263
+ }
264
+ last.final = true;
265
+ }
266
+ const normalized = { steps };
267
+ if (segment.emitUsage !== void 0) {
268
+ normalized.emitUsage = segment.emitUsage;
269
+ }
270
+ if (segment.usageAudioDuration !== void 0) {
271
+ normalized.usageAudioDuration = segment.usageAudioDuration;
272
+ }
273
+ return normalized;
274
+ });
275
+ }
276
+ function normalizeStep(stepInput, segmentIndex, stepIndex) {
277
+ const step = typeof stepInput === "string" ? { text: stepInput } : stepInput;
278
+ if (!step.text) {
279
+ throw new Error(`Fake STT step at segment ${segmentIndex}, index ${stepIndex} is missing text`);
280
+ }
281
+ const normalized = {
282
+ text: step.text,
283
+ final: Boolean(step.final)
284
+ };
285
+ if (step.delayMs !== void 0) {
286
+ normalized.delayMs = step.delayMs;
287
+ }
288
+ if (step.confidence !== void 0) {
289
+ normalized.confidence = step.confidence;
290
+ }
291
+ if (step.language !== void 0) {
292
+ normalized.language = step.language;
293
+ }
294
+ return normalized;
295
+ }
296
+ function frameDurationSeconds(samplesPerChannel, sampleRate) {
297
+ if (samplesPerChannel <= 0 || sampleRate <= 0) {
298
+ return 0;
299
+ }
300
+ return samplesPerChannel / sampleRate;
301
+ }
302
+ function isSilentAudioFrame(frame, silenceAmplitudeThreshold) {
303
+ if (frame.samplesPerChannel <= 0) {
304
+ return true;
305
+ }
306
+ for (let i = 0; i < frame.data.length; i++) {
307
+ const sample = frame.data[i];
308
+ if (sample !== void 0 && Math.abs(sample) > silenceAmplitudeThreshold) {
309
+ return false;
310
+ }
311
+ }
312
+ return true;
313
+ }
314
+ function abortError() {
315
+ const err = new Error("The operation was aborted");
316
+ err.name = "AbortError";
317
+ return err;
318
+ }
319
+ function asInteger(value) {
320
+ if (!Number.isFinite(value)) {
321
+ return 0;
322
+ }
323
+ return Math.trunc(value);
324
+ }
325
+ function clampNumber(value, minimum) {
326
+ if (!Number.isFinite(value)) {
327
+ return minimum;
328
+ }
329
+ return Math.max(minimum, value);
330
+ }
331
+ function positiveModulo(value, modulus) {
332
+ return (value % modulus + modulus) % modulus;
333
+ }
334
+ function toUint32(value) {
335
+ return value >>> 0;
336
+ }
337
+ function hash32(seed) {
338
+ let x = toUint32(seed) || 2654435769;
339
+ x ^= x << 13;
340
+ x ^= x >>> 17;
341
+ x ^= x << 5;
342
+ return toUint32(x);
343
+ }
344
+ function getSegmentAt(script, index) {
345
+ const segment = script[index];
346
+ if (!segment) {
347
+ throw new Error(`Missing script segment at index ${index}`);
348
+ }
349
+ return segment;
350
+ }
351
+ async function sleepWithAbort(ms, signal) {
352
+ if (ms <= 0) {
353
+ return !signal.aborted;
354
+ }
355
+ if (signal.aborted) {
356
+ return false;
357
+ }
358
+ return new Promise((resolve) => {
359
+ const timer = setTimeout(() => {
360
+ cleanup();
361
+ resolve(true);
362
+ }, ms);
363
+ const onAbort = () => {
364
+ cleanup();
365
+ resolve(false);
366
+ };
367
+ const cleanup = () => {
368
+ clearTimeout(timer);
369
+ signal.removeEventListener("abort", onAbort);
370
+ };
371
+ signal.addEventListener("abort", onAbort, { once: true });
372
+ });
373
+ }
374
+
375
+ export { DEFAULT_SCRIPT, STT, SpeechStream };
376
+ //# sourceMappingURL=index.js.map
377
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stt.ts"],"names":[],"mappings":";;;AA6DA,IAAM,cAAA,GAA8C;AAAA,EAClD;AAAA,IACE,EAAE,IAAA,EAAM,qBAAA,EAAuB,KAAA,EAAO,KAAA,EAAM;AAAA,IAC5C,EAAE,IAAA,EAAM,qBAAA,EAAuB,KAAA,EAAO,KAAA,EAAM;AAAA,IAC5C,EAAE,IAAA,EAAM,iBAAA,EAAmB,KAAA,EAAO,IAAA;AAAK,GACzC;AAAA,EACA;AAAA,IACE,EAAE,IAAA,EAAM,mBAAA,EAAqB,KAAA,EAAO,KAAA,EAAM;AAAA,IAC1C,EAAE,IAAA,EAAM,iBAAA,EAAmB,KAAA,EAAO,IAAA;AAAK;AAE3C;AAEA,IAAM,eAAA,GAA4D;AAAA,EAChE,QAAA,EAAU,OAAA;AAAA,EACV,kBAAA,EAAoB,CAAA;AAAA,EACpB,QAAA,EAAU,CAAA;AAAA,EACV,IAAA,EAAM,CAAA;AAAA,EACN,MAAA,EAAQ,CAAA;AAAA,EACR,oBAAA,EAAsB,KAAA;AAAA,EACtB,UAAA,EAAY,IAAA;AAAA,EACZ,yBAAA,EAA2B,GAAA;AAAA,EAC3B,yBAAA,EAA2B;AAC7B,CAAA;AAEO,IAAM,GAAA,GAAN,cAAkB,GAAA,CAAI,GAAA,CAAI;AAAA,EACtB,KAAA,GAAQ,UAAA;AAAA,EAEjB,QAAA;AAAA,EACA,eAAA,GAAkB,CAAA;AAAA,EAElB,WAAA,CAAY,OAAA,GAA0B,EAAC,EAAG;AACxC,IAAA,KAAA,CAAM;AAAA,MACJ,SAAA,EAAW,IAAA;AAAA,MACX,cAAA,EAAgB,IAAA;AAAA,MAChB,iBAAA,EAAmB;AAAA,KACpB,CAAA;AAED,IAAA,IAAA,CAAK,QAAA,GAAW,eAAe,OAAO,CAAA;AAAA,EACxC;AAAA,EAEA,IAAI,OAAA,GAAqC;AACvC,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA,EAEA,MAAgB,UAAA,CACd,KAAA,EACA,WAAA,EAC0B;AAC1B,IAAA,IAAI,aAAa,OAAA,EAAS;AACxB,MAAA,MAAM,UAAA,EAAW;AAAA,IACnB;AAEA,IAAA,MAAM,MAAA,GAAS,YAAY,KAAK,CAAA;AAChC,IAAA,MAAM,QAAA,GAAW,oBAAA,CAAqB,MAAA,CAAO,iBAAA,EAAmB,OAAO,UAAU,CAAA;AACjF,IAAA,MAAM,OAAA,GAAU,KAAK,oBAAA,EAAqB;AAC1C,IAAA,MAAM,UAAA,GACJ,OAAA,CAAQ,KAAA,CACL,MAAA,CAAO,CAAC,IAAA,KAAS,IAAA,CAAK,KAAK,CAAA,CAC3B,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,IAAI,CAAA,CACvB,IAAA,CAAK,GAAG,CAAA,CACR,IAAA,EAAK,IACR,OAAA,CAAQ,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,EAAG,IAAA,IACzC,EAAA;AAEF,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAI,eAAA,CAAgB,gBAAA;AAAA,MAC1B,SAAA,EAAW,CAAA,mBAAA,EAAsB,IAAA,CAAK,eAAA,GAAkB,CAAC,CAAA,CAAA;AAAA,MACzD,YAAA,EAAc;AAAA,QACZ;AAAA,UACE,IAAA,EAAM,UAAA;AAAA,UACN,QAAA,EAAU,KAAK,QAAA,CAAS,QAAA;AAAA,UACxB,SAAA,EAAW,CAAA;AAAA,UACX,OAAA,EAAS,QAAA;AAAA,UACT,UAAA,EAAY;AAAA;AACd;AACF,KACF;AAAA,EACF;AAAA,EAEA,OAAO,OAAA,EAA6D;AAClE,IAAA,OAAO,IAAI,YAAA,CAAa,IAAA,EAAM,IAAA,CAAK,QAAA,EAAU,SAAS,WAAW,CAAA;AAAA,EACnE;AAAA,EAEA,oBAAA,GAA0C;AACxC,IAAA,MAAM,OAAA,GAAU,YAAA;AAAA,MACd,KAAK,QAAA,CAAS,MAAA;AAAA,MACd,cAAA;AAAA,QACE,IAAA,CAAK,QAAA,CAAS,aAAA,GAAgB,IAAA,CAAK,eAAA;AAAA,QACnC,IAAA,CAAK,SAAS,MAAA,CAAO;AAAA;AACvB,KACF;AAEA,IAAA,IAAA,CAAK,eAAA,IAAmB,CAAA;AACxB,IAAA,OAAO,OAAA;AAAA,EACT;AACF;AAEO,IAAM,YAAA,GAAN,MAAM,aAAA,SAAqB,GAAA,CAAI,YAAA,CAAa;AAAA,EACxC,KAAA,GAAQ,mBAAA;AAAA,EAEjB,QAAA;AAAA,EACA,OAAA;AAAA,EACA,eAAA,GAAkB,CAAA;AAAA,EAClB,qBAAA,GAAwB,CAAA;AAAA,EACxB,gBAAA,GAAmB,KAAA;AAAA,EACnB,kBAAA,GAAqB,KAAA;AAAA,EACrB,yBAAA,GAA4B,CAAA;AAAA,EAC5B,uBAAA,GAA0B,CAAA;AAAA,EAE1B,WAAA,CAAY,OAAA,EAAc,OAAA,EAA0B,WAAA,EAAiC;AACnF,IAAA,KAAA,CAAM,OAAA,EAAS,OAAA,CAAQ,UAAA,EAAY,WAAW,CAAA;AAC9C,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAChB,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,aAAA;AAAA,EACzB;AAAA,EAEA,MAAgB,GAAA,GAAqB;AACnC,IAAA,IAAI;AACF,MAAA,WAAA,MAAiB,IAAA,IAAQ,KAAK,KAAA,EAAO;AACnC,QAAA,IAAI,IAAA,CAAK,WAAA,CAAY,OAAA,IAAW,IAAA,CAAK,MAAA,EAAQ;AAC3C,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,IAAA,KAAS,cAAa,cAAA,EAAgB;AACxC,UAAA,MAAM,KAAK,qBAAA,EAAsB;AACjC,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,QAAA,GAAW,oBAAA,CAAqB,IAAA,CAAK,iBAAA,EAAmB,KAAK,UAAU,CAAA;AAC7E,QAAA,IAAI,YAAY,CAAA,EAAG;AACjB,UAAA;AAAA,QACF;AAEA,QAAA,IAAA,CAAK,gBAAA,GAAmB,IAAA;AACxB,QAAA,IAAA,CAAK,qBAAA,IAAyB,QAAA;AAE9B,QAAA,MAAM,kBAAkB,QAAA,GAAW,GAAA;AACnC,QAAA,IAAI,kBAAA,CAAmB,IAAA,EAAM,IAAA,CAAK,QAAA,CAAS,yBAAyB,CAAA,EAAG;AACrE,UAAA,IAAI,KAAK,kBAAA,EAAoB;AAC3B,YAAA,IAAA,CAAK,yBAAA,IAA6B,eAAA;AAAA,UACpC;AAEA,UAAA,IACE,KAAK,kBAAA,IACL,IAAA,CAAK,yBAAA,IAA6B,IAAA,CAAK,SAAS,yBAAA,EAChD;AACA,YAAA,MAAM,KAAK,qBAAA,EAAsB;AAAA,UACnC;AACA,UAAA;AAAA,QACF;AAEA,QAAA,IAAA,CAAK,kBAAA,GAAqB,IAAA;AAC1B,QAAA,IAAA,CAAK,yBAAA,GAA4B,CAAA;AAAA,MACnC;AAEA,MAAA,MAAM,KAAK,qBAAA,EAAsB;AAAA,IACnC,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAM,qBAAA,GAAuC;AAC3C,IAAA,IAAI,CAAC,IAAA,CAAK,gBAAA,IAAoB,KAAK,WAAA,CAAY,OAAA,IAAW,KAAK,MAAA,EAAQ;AACrE,MAAA,IAAA,CAAK,kBAAA,EAAmB;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,iBAAiB,IAAA,CAAK,eAAA;AAC5B,IAAA,MAAM,SAAA,GAAY,YAAY,cAAc,CAAA,CAAA;AAC5C,IAAA,MAAM,uBAAuB,IAAA,CAAK,qBAAA;AAClC,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,eAAA,GAAkB,IAAA,CAAK,uBAAA;AACjD,IAAA,MAAM,OAAA,GAAU,KAAK,YAAA,EAAa;AAElC,IAAA,IAAA,CAAK,uBAAA,IAA2B,oBAAA;AAChC,IAAA,IAAA,CAAK,eAAA,IAAmB,CAAA;AACxB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AAExB,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,IAAI,eAAA,CAAgB,eAAA,EAAiB,SAAA,EAAW,CAAA,EAAG;AAC7E,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAAa,QAAQ,KAAA,CAAM,MAAA;AACjC,IAAA,KAAA,MAAW,CAAC,SAAA,EAAW,IAAI,KAAK,OAAA,CAAQ,KAAA,CAAM,SAAQ,EAAG;AACvD,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,aAAA,CAAc,IAAA,EAAM,gBAAgB,SAAS,CAAA;AAClE,MAAA,MAAM,gBAAA,GAAmB,MAAM,cAAA,CAAe,OAAA,EAAS,KAAK,WAAW,CAAA;AACvE,MAAA,IAAI,CAAC,gBAAA,IAAoB,IAAA,CAAK,MAAA,EAAQ;AACpC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,YAAY,IAAA,CAAK,KAAA,GACnB,IAAI,eAAA,CAAgB,gBAAA,GACpB,IAAI,eAAA,CAAgB,kBAAA;AAExB,MAAA,MAAM,OAAA,GAAU,YAAA,GAAe,oBAAA,IAAA,CAAyB,SAAA,GAAY,CAAA,IAAK,UAAA,CAAA;AACzE,MAAA,MAAM,eAAA,GAAmC;AAAA,QACvC,IAAA,EAAM,SAAA;AAAA,QACN,SAAA;AAAA,QACA,YAAA,EAAc;AAAA,UACZ;AAAA,YACE,MAAM,IAAA,CAAK,IAAA;AAAA,YACX,QAAA,EAAU,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,QAAA;AAAA,YACzC,SAAA,EAAW,YAAA;AAAA,YACX,OAAA,EAAS,OAAA;AAAA,YACT,UAAA,EAAY,IAAA,CAAK,UAAA,KAAe,IAAA,CAAK,QAAQ,IAAA,GAAO,IAAA;AAAA;AACtD;AACF,OACF;AAEA,MAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,eAAe,CAAA,EAAG;AACpC,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,IAAI,eAAA,CAAgB,aAAA,EAAe,SAAA,EAAW,CAAA,EAAG;AAC3E,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,SAAA,IAAa,IAAA,CAAK,QAAA,CAAS,oBAAA;AAC3D,IAAA,IAAI,CAAC,eAAA,EAAiB;AACpB,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,SAAA,CAAU;AAAA,MACb,IAAA,EAAM,IAAI,eAAA,CAAgB,iBAAA;AAAA,MAC1B,SAAA;AAAA,MACA,gBAAA,EAAkB;AAAA,QAChB,aAAA,EAAe,QAAQ,kBAAA,IAAsB;AAAA;AAC/C,KACD,CAAA;AAAA,EACH;AAAA,EAEA,YAAA,GAAkC;AAChC,IAAA,MAAM,UAAU,YAAA,CAAa,IAAA,CAAK,QAAA,CAAS,MAAA,EAAQ,KAAK,OAAO,CAAA;AAC/D,IAAA,IAAA,CAAK,OAAA,GAAU,eAAe,IAAA,CAAK,OAAA,GAAU,GAAG,IAAA,CAAK,QAAA,CAAS,OAAO,MAAM,CAAA;AAC3E,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEA,aAAA,CAAc,IAAA,EAAsB,cAAA,EAAwB,SAAA,EAA2B;AACrF,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,QAAA,CAAS,kBAAA;AAChD,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,QAAA,IAAY,CAAA,EAAG;AAC/B,MAAA,OAAO,SAAA;AAAA,IACT;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAA;AAAA,MACZ,QAAA,CAAS,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,GACzB,QAAA,CAAA,CAAU,cAAA,GAAiB,CAAA,IAAK,UAAU,CAAA,GAC1C,QAAA,CAAA,CAAU,SAAA,GAAY,KAAK,UAAU;AAAA,KACzC;AAEA,IAAA,MAAM,OAAO,KAAA,GAAQ,UAAA;AACrB,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAA,CAAO,IAAA,GAAO,IAAI,CAAA,IAAK,IAAA,CAAK,SAAS,QAAQ,CAAA;AACjE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,SAAA,GAAY,MAAM,CAAA;AAAA,EACvC;AAAA,EAEA,UAAU,KAAA,EAAiC;AACzC,IAAA,IAAI,KAAK,MAAA,IAAU,IAAA,CAAK,YAAY,OAAA,IAAW,IAAA,CAAK,MAAM,MAAA,EAAQ;AAChE,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,IAAI;AACF,MAAA,IAAA,CAAK,KAAA,CAAM,IAAI,KAAK,CAAA;AACpB,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,kBAAA,GAA2B;AACzB,IAAA,IAAA,CAAK,gBAAA,GAAmB,KAAA;AACxB,IAAA,IAAA,CAAK,kBAAA,GAAqB,KAAA;AAC1B,IAAA,IAAA,CAAK,qBAAA,GAAwB,CAAA;AAC7B,IAAA,IAAA,CAAK,yBAAA,GAA4B,CAAA;AAAA,EACnC;AACF;AAEA,SAAS,eAAe,OAAA,EAA0C;AAChE,EAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,OAAA,CAAQ,MAAA,IAAU,cAAc,CAAA;AAC/D,EAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AAEA,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,OAAA,CAAQ,IAAA,IAAQ,gBAAgB,IAAI,CAAA;AAC3D,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,MAAA,IAAU,gBAAgB,MAAM,CAAA;AACjE,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,IAAA,KAAS,MAAA,GAAY,CAAA,GAAI,eAAe,MAAA,CAAO,IAAI,CAAA,EAAG,MAAA,CAAO,MAAM,CAAA;AAC9F,EAAA,MAAM,aAAA,GAAgB,cAAA,CAAe,MAAA,GAAS,UAAA,EAAY,OAAO,MAAM,CAAA;AAEvE,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY,eAAA,CAAgB,QAAA;AAAA,IAC9C,kBAAA,EAAoB,WAAA;AAAA,MAClB,OAAA,CAAQ,sBAAsB,eAAA,CAAgB,kBAAA;AAAA,MAC9C;AAAA,KACF;AAAA,IACA,UAAU,WAAA,CAAY,OAAA,CAAQ,QAAA,IAAY,eAAA,CAAgB,UAAU,CAAC,CAAA;AAAA,IACrE,IAAA;AAAA,IACA,aAAA;AAAA,IACA,oBAAA,EAAsB,OAAA,CAAQ,oBAAA,IAAwB,eAAA,CAAgB,oBAAA;AAAA,IACtE,YAAY,WAAA,CAAY,OAAA,CAAQ,UAAA,IAAc,eAAA,CAAgB,YAAY,CAAC,CAAA;AAAA,IAC3E,yBAAA,EAA2B,WAAA;AAAA,MACzB,OAAA,CAAQ,6BAA6B,eAAA,CAAgB,yBAAA;AAAA,MACrD;AAAA,KACF;AAAA,IACA,yBAAA,EAA2B,WAAA;AAAA,MACzB,OAAA,CAAQ,6BAA6B,eAAA,CAAgB,yBAAA;AAAA,MACrD;AAAA;AACF,GACF;AACF;AAEA,SAAS,gBAAgB,MAAA,EAA0D;AACjF,EAAA,OAAO,MAAA,CAAO,GAAA,CAAI,CAAC,YAAA,EAAc,YAAA,KAAiB;AAChD,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,YAAY,IAAI,EAAE,KAAA,EAAO,cAAa,GAAI,YAAA;AACxE,IAAA,IAAI,CAAC,OAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ;AACzB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,YAAY,CAAA,SAAA,CAAW,CAAA;AAAA,IACtE;AAEA,IAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,CAAM,GAAA;AAAA,MAAI,CAAC,SAAA,EAAW,SAAA,KAC1C,aAAA,CAAc,SAAA,EAAW,cAAc,SAAS;AAAA,KAClD;AACA,IAAA,IAAI,CAAC,KAAA,CAAM,IAAA,CAAK,CAAC,IAAA,KAAS,IAAA,CAAK,KAAK,CAAA,EAAG;AACrC,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AACnC,MAAA,IAAI,CAAC,IAAA,EAAM;AACT,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,YAAY,CAAA,SAAA,CAAW,CAAA;AAAA,MACtE;AACA,MAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AAAA,IACf;AAEA,IAAA,MAAM,UAAA,GAAgC,EAAE,KAAA,EAAM;AAC9C,IAAA,IAAI,OAAA,CAAQ,cAAc,MAAA,EAAW;AACnC,MAAA,UAAA,CAAW,YAAY,OAAA,CAAQ,SAAA;AAAA,IACjC;AACA,IAAA,IAAI,OAAA,CAAQ,uBAAuB,MAAA,EAAW;AAC5C,MAAA,UAAA,CAAW,qBAAqB,OAAA,CAAQ,kBAAA;AAAA,IAC1C;AACA,IAAA,OAAO,UAAA;AAAA,EACT,CAAC,CAAA;AACH;AAEA,SAAS,aAAA,CACP,SAAA,EACA,YAAA,EACA,SAAA,EACgB;AAChB,EAAA,MAAM,OAAO,OAAO,SAAA,KAAc,WAAW,EAAE,IAAA,EAAM,WAAU,GAAI,SAAA;AAEnE,EAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AACd,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,YAAY,CAAA,QAAA,EAAW,SAAS,CAAA,gBAAA,CAAkB,CAAA;AAAA,EAChG;AAEA,EAAA,MAAM,UAAA,GAA6B;AAAA,IACjC,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,KAAA,EAAO,OAAA,CAAQ,IAAA,CAAK,KAAK;AAAA,GAC3B;AAEA,EAAA,IAAI,IAAA,CAAK,YAAY,MAAA,EAAW;AAC9B,IAAA,UAAA,CAAW,UAAU,IAAA,CAAK,OAAA;AAAA,EAC5B;AACA,EAAA,IAAI,IAAA,CAAK,eAAe,MAAA,EAAW;AACjC,IAAA,UAAA,CAAW,aAAa,IAAA,CAAK,UAAA;AAAA,EAC/B;AACA,EAAA,IAAI,IAAA,CAAK,aAAa,MAAA,EAAW;AAC/B,IAAA,UAAA,CAAW,WAAW,IAAA,CAAK,QAAA;AAAA,EAC7B;AAEA,EAAA,OAAO,UAAA;AACT;AAEA,SAAS,oBAAA,CAAqB,mBAA2B,UAAA,EAA4B;AACnF,EAAA,IAAI,iBAAA,IAAqB,CAAA,IAAK,UAAA,IAAc,CAAA,EAAG;AAC7C,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,OAAO,iBAAA,GAAoB,UAAA;AAC7B;AAEA,SAAS,kBAAA,CAAmB,OAAmB,yBAAA,EAA4C;AACzF,EAAA,IAAI,KAAA,CAAM,qBAAqB,CAAA,EAAG;AAChC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AAC1C,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA;AAC3B,IAAA,IAAI,WAAW,MAAA,IAAa,IAAA,CAAK,GAAA,CAAI,MAAM,IAAI,yBAAA,EAA2B;AACxE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,UAAA,GAAoB;AAC3B,EAAA,MAAM,GAAA,GAAM,IAAI,KAAA,CAAM,2BAA2B,CAAA;AACjD,EAAA,GAAA,CAAI,IAAA,GAAO,YAAA;AACX,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,UAAU,KAAA,EAAuB;AACxC,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AAC3B,IAAA,OAAO,CAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA,CAAK,MAAM,KAAK,CAAA;AACzB;AAEA,SAAS,WAAA,CAAY,OAAe,OAAA,EAAyB;AAC3D,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AAC3B,IAAA,OAAO,OAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,KAAK,CAAA;AAChC;AAEA,SAAS,cAAA,CAAe,OAAe,OAAA,EAAyB;AAC9D,EAAA,OAAA,CAAS,KAAA,GAAQ,UAAW,OAAA,IAAW,OAAA;AACzC;AAEA,SAAS,SAAS,KAAA,EAAuB;AACvC,EAAA,OAAO,KAAA,KAAU,CAAA;AACnB;AAEA,SAAS,OAAO,IAAA,EAAsB;AACpC,EAAA,IAAI,CAAA,GAAI,QAAA,CAAS,IAAI,CAAA,IAAK,UAAA;AAC1B,EAAA,CAAA,IAAK,CAAA,IAAK,EAAA;AACV,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AACX,EAAA,CAAA,IAAK,CAAA,IAAK,CAAA;AACV,EAAA,OAAO,SAAS,CAAC,CAAA;AACnB;AAEA,SAAS,YAAA,CAAa,QAA6B,KAAA,EAAkC;AACnF,EAAA,MAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC5B,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,KAAK,CAAA,CAAE,CAAA;AAAA,EAC5D;AACA,EAAA,OAAO,OAAA;AACT;AAEA,eAAe,cAAA,CAAe,IAAY,MAAA,EAAuC;AAC/E,EAAA,IAAI,MAAM,CAAA,EAAG;AACX,IAAA,OAAO,CAAC,MAAA,CAAO,OAAA;AAAA,EACjB;AAEA,EAAA,IAAI,OAAO,OAAA,EAAS;AAClB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAI,OAAA,CAAiB,CAAC,OAAA,KAAY;AACvC,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,OAAA,EAAQ;AACR,MAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,IACd,GAAG,EAAE,CAAA;AAEL,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,OAAA,EAAQ;AACR,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IACf,CAAA;AAEA,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAAA,IAC7C,CAAA;AAEA,IAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,OAAA,EAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,EAC1D,CAAC,CAAA;AACH","file":"index.js","sourcesContent":["import { mergeFrames, stt, type APIConnectOptions, type AudioBuffer } from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\n\nexport interface FakeSTTScriptStep {\n text: string;\n final?: boolean;\n delayMs?: number;\n confidence?: number;\n language?: string;\n}\n\nexport type FakeSTTScriptStepInput = string | FakeSTTScriptStep;\n\nexport interface FakeSTTScriptSegment {\n steps: FakeSTTScriptStepInput[];\n emitUsage?: boolean;\n usageAudioDuration?: number;\n}\n\nexport type FakeSTTScriptSegmentInput = FakeSTTScriptSegment | FakeSTTScriptStepInput[];\n\nexport interface FakeSTTOptions {\n script?: FakeSTTScriptSegmentInput[];\n language?: string;\n defaultStepDelayMs?: number;\n jitterMs?: number;\n seed?: number;\n offset?: number;\n emitRecognitionUsage?: boolean;\n sampleRate?: number;\n silenceDurationMsToCommit?: number;\n silenceAmplitudeThreshold?: number;\n}\n\ninterface NormalizedStep {\n text: string;\n final: boolean;\n delayMs?: number;\n confidence?: number;\n language?: string;\n}\n\ninterface NormalizedSegment {\n steps: NormalizedStep[];\n emitUsage?: boolean;\n usageAudioDuration?: number;\n}\n\ninterface ResolvedOptions {\n script: NormalizedSegment[];\n language: string;\n defaultStepDelayMs: number;\n jitterMs: number;\n seed: number;\n initialOffset: number;\n emitRecognitionUsage: boolean;\n sampleRate: number;\n silenceDurationMsToCommit: number;\n silenceAmplitudeThreshold: number;\n}\n\nconst DEFAULT_SCRIPT: FakeSTTScriptSegmentInput[] = [\n [\n { text: 'segment-a partial-1', final: false },\n { text: 'segment-a partial-2', final: false },\n { text: 'segment-a final', final: true },\n ],\n [\n { text: 'segment-b partial', final: false },\n { text: 'segment-b final', final: true },\n ],\n];\n\nconst DEFAULT_OPTIONS: Required<Omit<FakeSTTOptions, 'script'>> = {\n language: 'en-US',\n defaultStepDelayMs: 0,\n jitterMs: 0,\n seed: 0,\n offset: 0,\n emitRecognitionUsage: false,\n sampleRate: 16000,\n silenceDurationMsToCommit: 400,\n silenceAmplitudeThreshold: 150,\n};\n\nexport class STT extends stt.STT {\n readonly label = 'fake.STT';\n\n #options: ResolvedOptions;\n #recognizeCount = 0;\n\n constructor(options: FakeSTTOptions = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: false,\n });\n\n this.#options = resolveOptions(options);\n }\n\n get options(): Readonly<ResolvedOptions> {\n return this.#options;\n }\n\n protected async _recognize(\n frame: AudioBuffer,\n abortSignal?: AbortSignal\n ): Promise<stt.SpeechEvent> {\n if (abortSignal?.aborted) {\n throw abortError();\n }\n\n const merged = mergeFrames(frame);\n const duration = frameDurationSeconds(merged.samplesPerChannel, merged.sampleRate);\n const segment = this.#segmentForRecognize();\n const transcript =\n segment.steps\n .filter((step) => step.final)\n .map((step) => step.text)\n .join(' ')\n .trim() ||\n segment.steps[segment.steps.length - 1]?.text ||\n '';\n\n return {\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n requestId: `fake-stt-recognize-${this.#recognizeCount - 1}`,\n alternatives: [\n {\n text: transcript,\n language: this.#options.language,\n startTime: 0,\n endTime: duration,\n confidence: 1,\n },\n ],\n };\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n return new SpeechStream(this, this.#options, options?.connOptions);\n }\n\n #segmentForRecognize(): NormalizedSegment {\n const segment = getSegmentAt(\n this.#options.script,\n positiveModulo(\n this.#options.initialOffset + this.#recognizeCount,\n this.#options.script.length\n )\n );\n\n this.#recognizeCount += 1;\n return segment;\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n readonly label = 'fake.SpeechStream';\n\n #options: ResolvedOptions;\n #cursor: number;\n #utteranceCount = 0;\n #pendingAudioDuration = 0;\n #hasPendingAudio = false;\n #hasDetectedSpeech = false;\n #pendingSilenceDurationMs = 0;\n #processedAudioDuration = 0;\n\n constructor(sttImpl: STT, options: ResolvedOptions, connOptions?: APIConnectOptions) {\n super(sttImpl, options.sampleRate, connOptions);\n this.#options = options;\n this.#cursor = options.initialOffset;\n }\n\n protected async run(): Promise<void> {\n try {\n for await (const item of this.input) {\n if (this.abortSignal.aborted || this.closed) {\n return;\n }\n\n if (item === SpeechStream.FLUSH_SENTINEL) {\n await this.#emitPendingUtterance();\n continue;\n }\n\n const duration = frameDurationSeconds(item.samplesPerChannel, item.sampleRate);\n if (duration <= 0) {\n continue;\n }\n\n this.#hasPendingAudio = true;\n this.#pendingAudioDuration += duration;\n\n const frameDurationMs = duration * 1000;\n if (isSilentAudioFrame(item, this.#options.silenceAmplitudeThreshold)) {\n if (this.#hasDetectedSpeech) {\n this.#pendingSilenceDurationMs += frameDurationMs;\n }\n\n if (\n this.#hasDetectedSpeech &&\n this.#pendingSilenceDurationMs >= this.#options.silenceDurationMsToCommit\n ) {\n await this.#emitPendingUtterance();\n }\n continue;\n }\n\n this.#hasDetectedSpeech = true;\n this.#pendingSilenceDurationMs = 0;\n }\n\n await this.#emitPendingUtterance();\n } finally {\n this.closed = true;\n }\n }\n\n async #emitPendingUtterance(): Promise<void> {\n if (!this.#hasPendingAudio || this.abortSignal.aborted || this.closed) {\n this.#resetPendingAudio();\n return;\n }\n\n const utteranceIndex = this.#utteranceCount;\n const requestId = `fake-stt-${utteranceIndex}`;\n const segmentAudioDuration = this.#pendingAudioDuration;\n const segmentStart = this.startTimeOffset + this.#processedAudioDuration;\n const segment = this.#nextSegment();\n\n this.#processedAudioDuration += segmentAudioDuration;\n this.#utteranceCount += 1;\n this.#resetPendingAudio();\n\n if (!this.#putEvent({ type: stt.SpeechEventType.START_OF_SPEECH, requestId })) {\n return;\n }\n\n const stepsCount = segment.steps.length;\n for (const [stepIndex, step] of segment.steps.entries()) {\n const delayMs = this.#computeDelay(step, utteranceIndex, stepIndex);\n const continueEmission = await sleepWithAbort(delayMs, this.abortSignal);\n if (!continueEmission || this.closed) {\n return;\n }\n\n const eventType = step.final\n ? stt.SpeechEventType.FINAL_TRANSCRIPT\n : stt.SpeechEventType.INTERIM_TRANSCRIPT;\n\n const stepEnd = segmentStart + segmentAudioDuration * ((stepIndex + 1) / stepsCount);\n const transcriptEvent: stt.SpeechEvent = {\n type: eventType,\n requestId,\n alternatives: [\n {\n text: step.text,\n language: step.language ?? this.#options.language,\n startTime: segmentStart,\n endTime: stepEnd,\n confidence: step.confidence ?? (step.final ? 0.95 : 0.65),\n },\n ],\n };\n\n if (!this.#putEvent(transcriptEvent)) {\n return;\n }\n }\n\n if (!this.#putEvent({ type: stt.SpeechEventType.END_OF_SPEECH, requestId })) {\n return;\n }\n\n const shouldEmitUsage = segment.emitUsage ?? this.#options.emitRecognitionUsage;\n if (!shouldEmitUsage) {\n return;\n }\n\n this.#putEvent({\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n requestId,\n recognitionUsage: {\n audioDuration: segment.usageAudioDuration ?? segmentAudioDuration,\n },\n });\n }\n\n #nextSegment(): NormalizedSegment {\n const segment = getSegmentAt(this.#options.script, this.#cursor);\n this.#cursor = positiveModulo(this.#cursor + 1, this.#options.script.length);\n return segment;\n }\n\n #computeDelay(step: NormalizedStep, utteranceIndex: number, stepIndex: number): number {\n const baseDelay = step.delayMs ?? this.#options.defaultStepDelayMs;\n if (this.#options.jitterMs <= 0) {\n return baseDelay;\n }\n\n const mixed = hash32(\n toUint32(this.#options.seed) ^\n toUint32((utteranceIndex + 1) * 0x9e3779b1) ^\n toUint32((stepIndex + 1) * 0x85ebca6b)\n );\n\n const unit = mixed / 0xffffffff;\n const jitter = Math.round((unit * 2 - 1) * this.#options.jitterMs);\n return Math.max(0, baseDelay + jitter);\n }\n\n #putEvent(event: stt.SpeechEvent): boolean {\n if (this.closed || this.abortSignal.aborted || this.queue.closed) {\n return false;\n }\n\n try {\n this.queue.put(event);\n return true;\n } catch {\n return false;\n }\n }\n\n #resetPendingAudio(): void {\n this.#hasPendingAudio = false;\n this.#hasDetectedSpeech = false;\n this.#pendingAudioDuration = 0;\n this.#pendingSilenceDurationMs = 0;\n }\n}\n\nfunction resolveOptions(options: FakeSTTOptions): ResolvedOptions {\n const script = normalizeScript(options.script ?? DEFAULT_SCRIPT);\n if (script.length === 0) {\n throw new Error('Fake STT script must contain at least one segment');\n }\n\n const seed = asInteger(options.seed ?? DEFAULT_OPTIONS.seed);\n const offset = asInteger(options.offset ?? DEFAULT_OPTIONS.offset);\n const seedOffset = options.seed === undefined ? 0 : positiveModulo(hash32(seed), script.length);\n const initialOffset = positiveModulo(offset + seedOffset, script.length);\n\n return {\n script,\n language: options.language ?? DEFAULT_OPTIONS.language,\n defaultStepDelayMs: clampNumber(\n options.defaultStepDelayMs ?? DEFAULT_OPTIONS.defaultStepDelayMs,\n 0\n ),\n jitterMs: clampNumber(options.jitterMs ?? DEFAULT_OPTIONS.jitterMs, 0),\n seed,\n initialOffset,\n emitRecognitionUsage: options.emitRecognitionUsage ?? DEFAULT_OPTIONS.emitRecognitionUsage,\n sampleRate: clampNumber(options.sampleRate ?? DEFAULT_OPTIONS.sampleRate, 1),\n silenceDurationMsToCommit: clampNumber(\n options.silenceDurationMsToCommit ?? DEFAULT_OPTIONS.silenceDurationMsToCommit,\n 0\n ),\n silenceAmplitudeThreshold: clampNumber(\n options.silenceAmplitudeThreshold ?? DEFAULT_OPTIONS.silenceAmplitudeThreshold,\n 0\n ),\n };\n}\n\nfunction normalizeScript(script: FakeSTTScriptSegmentInput[]): NormalizedSegment[] {\n return script.map((segmentInput, segmentIndex) => {\n const segment = Array.isArray(segmentInput) ? { steps: segmentInput } : segmentInput;\n if (!segment.steps.length) {\n throw new Error(`Fake STT segment at index ${segmentIndex} is empty`);\n }\n\n const steps = segment.steps.map((stepInput, stepIndex) =>\n normalizeStep(stepInput, segmentIndex, stepIndex)\n );\n if (!steps.some((step) => step.final)) {\n const last = steps[steps.length - 1];\n if (!last) {\n throw new Error(`Fake STT segment at index ${segmentIndex} is empty`);\n }\n last.final = true;\n }\n\n const normalized: NormalizedSegment = { steps };\n if (segment.emitUsage !== undefined) {\n normalized.emitUsage = segment.emitUsage;\n }\n if (segment.usageAudioDuration !== undefined) {\n normalized.usageAudioDuration = segment.usageAudioDuration;\n }\n return normalized;\n });\n}\n\nfunction normalizeStep(\n stepInput: FakeSTTScriptStepInput,\n segmentIndex: number,\n stepIndex: number\n): NormalizedStep {\n const step = typeof stepInput === 'string' ? { text: stepInput } : stepInput;\n\n if (!step.text) {\n throw new Error(`Fake STT step at segment ${segmentIndex}, index ${stepIndex} is missing text`);\n }\n\n const normalized: NormalizedStep = {\n text: step.text,\n final: Boolean(step.final),\n };\n\n if (step.delayMs !== undefined) {\n normalized.delayMs = step.delayMs;\n }\n if (step.confidence !== undefined) {\n normalized.confidence = step.confidence;\n }\n if (step.language !== undefined) {\n normalized.language = step.language;\n }\n\n return normalized;\n}\n\nfunction frameDurationSeconds(samplesPerChannel: number, sampleRate: number): number {\n if (samplesPerChannel <= 0 || sampleRate <= 0) {\n return 0;\n }\n\n return samplesPerChannel / sampleRate;\n}\n\nfunction isSilentAudioFrame(frame: AudioFrame, silenceAmplitudeThreshold: number): boolean {\n if (frame.samplesPerChannel <= 0) {\n return true;\n }\n\n for (let i = 0; i < frame.data.length; i++) {\n const sample = frame.data[i];\n if (sample !== undefined && Math.abs(sample) > silenceAmplitudeThreshold) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction abortError(): Error {\n const err = new Error('The operation was aborted');\n err.name = 'AbortError';\n return err;\n}\n\nfunction asInteger(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n return Math.trunc(value);\n}\n\nfunction clampNumber(value: number, minimum: number): number {\n if (!Number.isFinite(value)) {\n return minimum;\n }\n return Math.max(minimum, value);\n}\n\nfunction positiveModulo(value: number, modulus: number): number {\n return ((value % modulus) + modulus) % modulus;\n}\n\nfunction toUint32(value: number): number {\n return value >>> 0;\n}\n\nfunction hash32(seed: number): number {\n let x = toUint32(seed) || 0x9e3779b9;\n x ^= x << 13;\n x ^= x >>> 17;\n x ^= x << 5;\n return toUint32(x);\n}\n\nfunction getSegmentAt(script: NormalizedSegment[], index: number): NormalizedSegment {\n const segment = script[index];\n if (!segment) {\n throw new Error(`Missing script segment at index ${index}`);\n }\n return segment;\n}\n\nasync function sleepWithAbort(ms: number, signal: AbortSignal): Promise<boolean> {\n if (ms <= 0) {\n return !signal.aborted;\n }\n\n if (signal.aborted) {\n return false;\n }\n\n return new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => {\n cleanup();\n resolve(true);\n }, ms);\n\n const onAbort = () => {\n cleanup();\n resolve(false);\n };\n\n const cleanup = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nexport { DEFAULT_SCRIPT };\n"]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@workadventure/livekit-agent-plugin-fake-stt",
3
+ "version": "0.1.0",
4
+ "description": "Deterministic fake STT provider for LiveKit Agents tests",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=24"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/workadventure/livekit-agent-plugin-fake-stt.git"
13
+ },
14
+ "homepage": "https://github.com/workadventure/livekit-agent-plugin-fake-stt#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/workadventure/livekit-agent-plugin-fake-stt/issues"
17
+ },
18
+ "keywords": [
19
+ "livekit",
20
+ "agents",
21
+ "stt",
22
+ "speech-to-text",
23
+ "testing",
24
+ "deterministic"
25
+ ],
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
39
+ "sideEffects": false,
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "clean": "rm -rf dist coverage",
43
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
44
+ "lint": "eslint src test examples eslint.config.js vitest.config.ts tsup.config.ts",
45
+ "format": "prettier --check .",
46
+ "format:write": "prettier --write .",
47
+ "test": "vitest run --coverage",
48
+ "test:watch": "vitest",
49
+ "prepack": "npm run clean && npm run build"
50
+ },
51
+ "peerDependencies": {
52
+ "@livekit/agents": "^1.0.48",
53
+ "@livekit/rtc-node": "^0.13.24"
54
+ },
55
+ "devDependencies": {
56
+ "@eslint/js": "^9.39.1",
57
+ "@livekit/agents": "^1.0.48",
58
+ "@livekit/rtc-node": "^0.13.24",
59
+ "@types/node": "^24.10.1",
60
+ "@vitest/coverage-v8": "^4.0.0",
61
+ "eslint": "^9.39.1",
62
+ "globals": "^17.4.0",
63
+ "prettier": "^3.6.2",
64
+ "tsup": "^8.5.0",
65
+ "typescript": "^5.9.3",
66
+ "typescript-eslint": "^8.46.2",
67
+ "vitest": "^4.0.0"
68
+ }
69
+ }