dvgateway-sdk 1.4.3 → 1.4.5

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.
@@ -0,0 +1,306 @@
1
+ /**
2
+ * DVGateway SDK Simulator
3
+ *
4
+ * Drives an existing DVGatewayClient pipeline from a local audio file
5
+ * without ever opening a real connection to a gateway or placing a real
6
+ * phone call. Intended for unit tests, CI, docs "Try it" widgets, and
7
+ * developer onboarding — `npm start` → hear your bot answer a recorded
8
+ * customer inquiry in seconds.
9
+ *
10
+ * Implementation strategy: `DVGatewayClient.simulate()` installs three
11
+ * simulated routes on the internal `WsPool`:
12
+ *
13
+ * 1. `/api/v1/ws/callinfo` — emits one synthetic `call:new` event for
14
+ * the simulated call, then a `call:ended` event after the audio
15
+ * finishes plus a grace period.
16
+ * 2. `/api/v1/ws/stream?linkedid={id}` — streams slin16 frames from
17
+ * the caller's audio file, paced at 20 ms/frame by default
18
+ * (`realtime: true`). Any inbound `thinking:start/stop` signals
19
+ * from the pipeline are silently acknowledged.
20
+ * 3. `/api/v1/ws/tts/{linkedId}` — captures injected TTS bytes into
21
+ * an in-memory buffer. Retrievable via `capturedTtsBytes()` or
22
+ * written out via `saveTts()`.
23
+ *
24
+ * The pipeline code under test doesn't change — the same five-line
25
+ * `gw.pipeline().stt().llm().tts().start()` runs, and the simulator
26
+ * simply acts as a loopback gateway. The simulator drives call
27
+ * progression (audio playback, hangup) on its own clock so a call
28
+ * still completes cleanly even when the pipeline has not subscribed
29
+ * to the audio stream (useful for unit tests of event handling).
30
+ */
31
+ import { EventEmitter } from 'events';
32
+ import { randomBytes } from 'crypto';
33
+ import { SIMULATOR_SAMPLE_RATE, writeSlin16Wav, readSlin16File } from './wav.js';
34
+ /** 20 ms slin16 frame — matches the gateway's native frame size. */
35
+ const FRAME_BYTES = 640;
36
+ const FRAME_DURATION_MS = 20;
37
+ /** Build a synthetic CallSession from user overrides + defaults. */
38
+ function buildSession(opts, linkedId) {
39
+ return {
40
+ linkedId,
41
+ dir: 'both',
42
+ caller: opts.caller ?? '+1-555-0100',
43
+ callerName: opts.callerName ?? 'Simulated Caller',
44
+ callee: opts.callee ?? '100',
45
+ did: opts.did ?? opts.callee ?? '100',
46
+ startedAt: new Date(),
47
+ streamUrl: `sim://audio/${linkedId}`,
48
+ tenantId: opts.tenantId,
49
+ };
50
+ }
51
+ /** Pick a linkedId that won't collide with real gateway IDs. */
52
+ function generateLinkedId() {
53
+ return `sim-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
54
+ }
55
+ /**
56
+ * Core factory — used by `DVGatewayClient.simulate()`. Returns a
57
+ * SimulatedCall that has already registered its WS routes; the pipeline
58
+ * will start pulling from them as soon as it subscribes.
59
+ */
60
+ export async function createSimulatedCall(ctx, opts) {
61
+ const linkedId = opts.linkedId ?? generateLinkedId();
62
+ const session = buildSession(opts, linkedId);
63
+ const hangupDelayMs = opts.hangupDelayMs ?? 5_000;
64
+ const realtime = opts.realtime !== false;
65
+ const pcm = await readSlin16File(opts.audioFile);
66
+ if (pcm.length === 0) {
67
+ throw new Error(`simulator: audio file is empty: ${opts.audioFile}`);
68
+ }
69
+ const state = new SimulatorState(ctx.logger, linkedId, session, pcm, realtime, hangupDelayMs);
70
+ // ── Route 1: call events ────────────────────────────────────────────
71
+ const callInfoRoute = {
72
+ matches: (path) => path === '/api/v1/ws/callinfo',
73
+ onSubscribe: (sub) => {
74
+ state.attachCallinfo(sub);
75
+ return {
76
+ send: () => { },
77
+ sendText: () => { },
78
+ close: () => state.detachCallinfo(sub),
79
+ };
80
+ },
81
+ };
82
+ // ── Route 2: audio stream ───────────────────────────────────────────
83
+ const audioRoute = {
84
+ matches: (path, params) => path === '/api/v1/ws/stream' && params['linkedid'] === linkedId,
85
+ onSubscribe: (sub) => {
86
+ state.attachAudio(sub);
87
+ return {
88
+ // pipeline never sends binary on the inbound-audio WS
89
+ send: () => { },
90
+ sendText: (text) => {
91
+ // thinking:start / thinking:stop — log only, no side effects
92
+ ctx.logger.debug({ linkedId, text }, 'simulator: thinking signal');
93
+ },
94
+ close: () => state.detachAudio(sub),
95
+ };
96
+ },
97
+ };
98
+ // ── Route 3: TTS capture ────────────────────────────────────────────
99
+ const ttsPath = `/api/v1/ws/tts/${encodeURIComponent(linkedId)}`;
100
+ const ttsRoute = {
101
+ matches: (path) => path === ttsPath,
102
+ onSubscribe: () => {
103
+ return {
104
+ send: (chunk) => {
105
+ state.captureTts(chunk);
106
+ opts.onTts?.(chunk);
107
+ },
108
+ sendText: () => { },
109
+ close: () => { },
110
+ };
111
+ },
112
+ };
113
+ const disposers = [
114
+ ctx.wsPool.addSimulatedRoute(callInfoRoute),
115
+ ctx.wsPool.addSimulatedRoute(audioRoute),
116
+ ctx.wsPool.addSimulatedRoute(ttsRoute),
117
+ ];
118
+ state.onStop = () => {
119
+ for (const d of disposers)
120
+ d();
121
+ };
122
+ // Kick off the call clock. Playback runs on its own timer so the
123
+ // simulation always terminates, even when nothing subscribes to the
124
+ // audio stream (e.g. a unit test that only listens for call events).
125
+ state.start();
126
+ return state.handle();
127
+ }
128
+ // ─── Private implementation ──────────────────────────────────────────
129
+ class SimulatorState {
130
+ logger;
131
+ linkedId;
132
+ session;
133
+ pcm;
134
+ realtime;
135
+ hangupDelayMs;
136
+ callNewPayload;
137
+ callNewDeliveredTo = new WeakSet();
138
+ callinfoSubs = new Set();
139
+ audioSubs = new Set();
140
+ callEndedDelivered = false;
141
+ frameIndex = 0;
142
+ totalFrames;
143
+ playbackTimer = null;
144
+ hangupTimer = null;
145
+ captured = [];
146
+ end = new EventEmitter();
147
+ stopped = false;
148
+ startedAt = 0;
149
+ onStop = () => undefined;
150
+ constructor(logger, linkedId, session, pcm, realtime, hangupDelayMs) {
151
+ this.logger = logger;
152
+ this.linkedId = linkedId;
153
+ this.session = session;
154
+ this.pcm = pcm;
155
+ this.realtime = realtime;
156
+ this.hangupDelayMs = hangupDelayMs;
157
+ this.totalFrames = Math.ceil(pcm.length / FRAME_BYTES);
158
+ this.callNewPayload = Buffer.from(JSON.stringify({
159
+ event: 'call:new',
160
+ linkedId: this.linkedId,
161
+ dir: this.session.dir,
162
+ caller: this.session.caller,
163
+ callerName: this.session.callerName,
164
+ callee: this.session.callee,
165
+ did: this.session.did,
166
+ streamUrl: this.session.streamUrl,
167
+ tenantId: this.session.tenantId,
168
+ }), 'utf8');
169
+ }
170
+ start() {
171
+ this.startedAt = Date.now();
172
+ if (this.realtime) {
173
+ // Realtime: one 20 ms frame per tick; setInterval naturally calls
174
+ // `tick()` once more after the last frame, which triggers
175
+ // `onPlaybackComplete()` via the frameIndex >= totalFrames branch.
176
+ this.playbackTimer = setInterval(() => this.tick(), FRAME_DURATION_MS);
177
+ }
178
+ else {
179
+ // Fast path for tests/CI: flush all frames on a microtask, then
180
+ // explicitly complete. Subscribers attaching synchronously after
181
+ // simulate() resolves still catch every frame (they register
182
+ // before this microtask runs).
183
+ queueMicrotask(() => {
184
+ while (!this.stopped && this.frameIndex < this.totalFrames) {
185
+ this.pushFrame();
186
+ }
187
+ if (!this.stopped)
188
+ this.onPlaybackComplete();
189
+ });
190
+ }
191
+ }
192
+ tick() {
193
+ if (this.stopped)
194
+ return;
195
+ if (this.frameIndex >= this.totalFrames) {
196
+ this.onPlaybackComplete();
197
+ return;
198
+ }
199
+ this.pushFrame();
200
+ }
201
+ pushFrame() {
202
+ const start = this.frameIndex * FRAME_BYTES;
203
+ const end = Math.min(start + FRAME_BYTES, this.pcm.length);
204
+ const frame = this.pcm.subarray(start, end);
205
+ // Share the slice; subscribers that modify must copy. All known
206
+ // SDK consumers only read.
207
+ for (const sub of this.audioSubs) {
208
+ sub.onMessage(Buffer.from(frame));
209
+ }
210
+ this.frameIndex++;
211
+ }
212
+ onPlaybackComplete() {
213
+ if (this.playbackTimer) {
214
+ clearInterval(this.playbackTimer);
215
+ this.playbackTimer = null;
216
+ }
217
+ // Close audio streams so the STT adapter sees EOF.
218
+ for (const sub of this.audioSubs) {
219
+ sub.onClose?.();
220
+ }
221
+ // Schedule the hangup so the pipeline finishes its last TTS reply.
222
+ this.hangupTimer = setTimeout(() => {
223
+ this.emitCallEnded((Date.now() - this.startedAt) / 1000);
224
+ this.end.emit('end');
225
+ }, this.hangupDelayMs);
226
+ }
227
+ attachCallinfo(sub) {
228
+ this.callinfoSubs.add(sub);
229
+ // Deliver call:new on the next microtask so subscribe() returns
230
+ // before the event lands in the caller's handler.
231
+ queueMicrotask(() => {
232
+ if (this.stopped)
233
+ return;
234
+ if (this.callNewDeliveredTo.has(sub))
235
+ return;
236
+ this.callNewDeliveredTo.add(sub);
237
+ sub.onMessage(Buffer.from(this.callNewPayload));
238
+ this.logger.info({ linkedId: this.linkedId }, 'simulator: call:new delivered');
239
+ });
240
+ }
241
+ detachCallinfo(sub) {
242
+ this.callinfoSubs.delete(sub);
243
+ }
244
+ attachAudio(sub) {
245
+ this.audioSubs.add(sub);
246
+ }
247
+ detachAudio(sub) {
248
+ this.audioSubs.delete(sub);
249
+ }
250
+ captureTts(chunk) {
251
+ this.captured.push(Buffer.from(chunk));
252
+ }
253
+ emitCallEnded(durationSec) {
254
+ if (this.callEndedDelivered)
255
+ return;
256
+ this.callEndedDelivered = true;
257
+ const payload = Buffer.from(JSON.stringify({
258
+ event: 'call:ended',
259
+ linkedId: this.linkedId,
260
+ duration: durationSec,
261
+ }), 'utf8');
262
+ for (const sub of this.callinfoSubs) {
263
+ sub.onMessage(payload);
264
+ }
265
+ this.logger.info({ linkedId: this.linkedId, durationSec }, 'simulator: call:ended delivered');
266
+ }
267
+ handle() {
268
+ const self = this;
269
+ return {
270
+ linkedId: self.linkedId,
271
+ session: self.session,
272
+ waitForEnd() {
273
+ if (self.callEndedDelivered)
274
+ return Promise.resolve();
275
+ return new Promise((resolve) => {
276
+ self.end.once('end', () => resolve());
277
+ });
278
+ },
279
+ async stop() {
280
+ if (self.stopped)
281
+ return;
282
+ self.stopped = true;
283
+ if (self.playbackTimer) {
284
+ clearInterval(self.playbackTimer);
285
+ self.playbackTimer = null;
286
+ }
287
+ if (self.hangupTimer) {
288
+ clearTimeout(self.hangupTimer);
289
+ self.hangupTimer = null;
290
+ }
291
+ self.emitCallEnded(0);
292
+ self.end.emit('end');
293
+ self.onStop();
294
+ },
295
+ capturedTtsBytes() {
296
+ return Buffer.concat(self.captured);
297
+ },
298
+ async saveTts(outputPath) {
299
+ const buf = Buffer.concat(self.captured);
300
+ await writeSlin16Wav(outputPath, buf);
301
+ },
302
+ };
303
+ }
304
+ }
305
+ export { SIMULATOR_SAMPLE_RATE };
306
+ //# sourceMappingURL=simulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulator.js","sourceRoot":"","sources":["../../src/simulator/simulator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=simulator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulator.test.d.ts","sourceRoot":"","sources":["../../src/simulator/simulator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Unit tests for the SDK simulator.
3
+ *
4
+ * Exercises the end-to-end shape of `DVGatewayClient.simulate()`:
5
+ * - synthetic `call:new` delivery,
6
+ * - paced slin16 frame playback,
7
+ * - captured-TTS buffer round-trip,
8
+ * - graceful hangup.
9
+ */
10
+ import { promises as fs } from 'fs';
11
+ import { tmpdir } from 'os';
12
+ import { join } from 'path';
13
+ import { DVGatewayClient } from '../client.js';
14
+ import { writeSlin16Wav } from './wav.js';
15
+ // 800 ms of silence at 16 kHz → 40 frames of 640 bytes each.
16
+ const SILENCE_PCM = Buffer.alloc(16_000 * 2 * 0.8);
17
+ async function tmpWav() {
18
+ const p = join(tmpdir(), `dvgateway-sim-${Date.now()}-${Math.random().toString(36).slice(2)}.wav`);
19
+ await writeSlin16Wav(p, SILENCE_PCM);
20
+ return p;
21
+ }
22
+ describe('DVGatewayClient.simulate', () => {
23
+ it('delivers a synthetic call:new event and resolves waitForEnd', async () => {
24
+ const gw = new DVGatewayClient({
25
+ baseUrl: 'http://localhost:8080',
26
+ auth: { type: 'apiKey', apiKey: 'simulation' },
27
+ });
28
+ const wavPath = await tmpWav();
29
+ try {
30
+ const events = [];
31
+ gw.onCallEvent((e) => { events.push(e.type); });
32
+ const call = await gw.simulate({
33
+ audioFile: wavPath,
34
+ caller: '+82-10-0000-1111',
35
+ realtime: false, // flush ASAP in tests
36
+ hangupDelayMs: 50, // short grace period
37
+ });
38
+ expect(call.linkedId).toMatch(/^sim-/);
39
+ expect(call.session.caller).toBe('+82-10-0000-1111');
40
+ await call.waitForEnd();
41
+ expect(events).toContain('call:new');
42
+ expect(events).toContain('call:ended');
43
+ }
44
+ finally {
45
+ gw.close();
46
+ await fs.unlink(wavPath).catch(() => undefined);
47
+ }
48
+ });
49
+ it('captures injected TTS into the handle buffer', async () => {
50
+ const gw = new DVGatewayClient({
51
+ baseUrl: 'http://localhost:8080',
52
+ auth: { type: 'apiKey', apiKey: 'simulation' },
53
+ });
54
+ const wavPath = await tmpWav();
55
+ try {
56
+ const call = await gw.simulate({
57
+ audioFile: wavPath,
58
+ realtime: false,
59
+ hangupDelayMs: 100,
60
+ });
61
+ // Generate ~200 ms of fake TTS (sine-wave placeholder values would be
62
+ // ideal, but for capture semantics any slin16 bytes suffice).
63
+ async function* fakeTts() {
64
+ for (let i = 0; i < 5; i++) {
65
+ yield Buffer.alloc(640, i + 1);
66
+ }
67
+ }
68
+ await gw.injectTts(call.linkedId, fakeTts());
69
+ const captured = call.capturedTtsBytes();
70
+ expect(captured.length).toBe(5 * 640);
71
+ const outPath = join(tmpdir(), `sim-tts-${Date.now()}.wav`);
72
+ await call.saveTts(outPath);
73
+ const onDisk = await fs.readFile(outPath);
74
+ // 44-byte WAV header + pcm
75
+ expect(onDisk.length).toBe(44 + captured.length);
76
+ await fs.unlink(outPath).catch(() => undefined);
77
+ await call.waitForEnd();
78
+ }
79
+ finally {
80
+ gw.close();
81
+ await fs.unlink(wavPath).catch(() => undefined);
82
+ }
83
+ });
84
+ it('stop() short-circuits the simulation', async () => {
85
+ const gw = new DVGatewayClient({
86
+ baseUrl: 'http://localhost:8080',
87
+ auth: { type: 'apiKey', apiKey: 'simulation' },
88
+ });
89
+ const wavPath = await tmpWav();
90
+ try {
91
+ const call = await gw.simulate({
92
+ audioFile: wavPath,
93
+ realtime: true, // would otherwise run for 800ms
94
+ hangupDelayMs: 60_000, // ridiculous grace period we want to skip
95
+ });
96
+ await call.stop();
97
+ await call.waitForEnd(); // must resolve immediately
98
+ }
99
+ finally {
100
+ gw.close();
101
+ await fs.unlink(wavPath).catch(() => undefined);
102
+ }
103
+ });
104
+ });
105
+ //# sourceMappingURL=simulator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulator.test.js","sourceRoot":"","sources":["../../src/simulator/simulator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /** Target sample rate for all gateway audio (slin16). */
2
+ export declare const SIMULATOR_SAMPLE_RATE: 16000;
3
+ /**
4
+ * Read a WAV or raw slin16 file into a single Buffer of 16-bit LE PCM.
5
+ * The returned buffer can be sliced into 640-byte frames directly.
6
+ *
7
+ * @throws If the file is a WAV with unsupported format (non-PCM,
8
+ * non-mono, or sample rate != 16 kHz).
9
+ */
10
+ export declare function readSlin16File(path: string): Promise<Buffer>;
11
+ /** Same as readSlin16File but operates on an in-memory Buffer. */
12
+ export declare function parseSlin16(data: Buffer): Buffer;
13
+ /**
14
+ * Wrap raw 16 kHz mono slin16 PCM into a minimal WAV container and write
15
+ * it to disk. Used by `SimulatedCall.saveTts()` so captured TTS can be
16
+ * played back in any audio tool.
17
+ */
18
+ export declare function writeSlin16Wav(path: string, pcm: Buffer): Promise<void>;
19
+ //# sourceMappingURL=wav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav.d.ts","sourceRoot":"","sources":["../../src/simulator/wav.ts"],"names":[],"mappings":"AAaA,yDAAyD;AACzD,eAAO,MAAM,qBAAqB,EAAG,KAAe,CAAC;AAErD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyDhD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB7E"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Minimal WAV file reader/writer for the SDK simulator.
3
+ *
4
+ * Supports the narrow format needed by DVGateway simulation:
5
+ * - PCM (format code 1), 16-bit little-endian, mono, 16 kHz.
6
+ *
7
+ * Also accepts raw slin16 files (no RIFF header) — any file whose first
8
+ * four bytes are not `RIFF` is treated as a raw slin16 stream. This lets
9
+ * callers point `audioFile` at either a standard WAV asset or a raw PCM
10
+ * capture without juggling conversions themselves.
11
+ */
12
+ import { promises as fs } from 'fs';
13
+ /** Target sample rate for all gateway audio (slin16). */
14
+ export const SIMULATOR_SAMPLE_RATE = 16_000;
15
+ /**
16
+ * Read a WAV or raw slin16 file into a single Buffer of 16-bit LE PCM.
17
+ * The returned buffer can be sliced into 640-byte frames directly.
18
+ *
19
+ * @throws If the file is a WAV with unsupported format (non-PCM,
20
+ * non-mono, or sample rate != 16 kHz).
21
+ */
22
+ export async function readSlin16File(path) {
23
+ const data = await fs.readFile(path);
24
+ return parseSlin16(data);
25
+ }
26
+ /** Same as readSlin16File but operates on an in-memory Buffer. */
27
+ export function parseSlin16(data) {
28
+ if (data.length < 4 || data.subarray(0, 4).toString('ascii') !== 'RIFF') {
29
+ // Not a RIFF container — treat as raw slin16 PCM.
30
+ return data;
31
+ }
32
+ // RIFF WAV — find fmt and data chunks.
33
+ if (data.subarray(8, 12).toString('ascii') !== 'WAVE') {
34
+ throw new Error('simulator: file starts with RIFF but is not a WAVE');
35
+ }
36
+ let offset = 12;
37
+ let audioFormat = 0;
38
+ let numChannels = 0;
39
+ let sampleRate = 0;
40
+ let bitsPerSample = 0;
41
+ let pcm = null;
42
+ while (offset + 8 <= data.length) {
43
+ const id = data.subarray(offset, offset + 4).toString('ascii');
44
+ const size = data.readUInt32LE(offset + 4);
45
+ const body = data.subarray(offset + 8, offset + 8 + size);
46
+ if (id === 'fmt ') {
47
+ audioFormat = body.readUInt16LE(0);
48
+ numChannels = body.readUInt16LE(2);
49
+ sampleRate = body.readUInt32LE(4);
50
+ bitsPerSample = body.readUInt16LE(14);
51
+ }
52
+ else if (id === 'data') {
53
+ pcm = body;
54
+ break; // we've got everything we need
55
+ }
56
+ // Chunks are word-aligned — odd sizes carry a trailing pad byte.
57
+ offset += 8 + size + (size % 2);
58
+ }
59
+ if (!pcm) {
60
+ throw new Error('simulator: WAV file has no data chunk');
61
+ }
62
+ if (audioFormat !== 1) {
63
+ throw new Error(`simulator: unsupported WAV format code ${audioFormat} (need PCM=1)`);
64
+ }
65
+ if (numChannels !== 1) {
66
+ throw new Error(`simulator: unsupported channel count ${numChannels} (need mono)`);
67
+ }
68
+ if (sampleRate !== SIMULATOR_SAMPLE_RATE) {
69
+ throw new Error(`simulator: unsupported sample rate ${sampleRate} Hz (need ${SIMULATOR_SAMPLE_RATE} Hz); ` +
70
+ `convert with: ffmpeg -i input.wav -ar 16000 -ac 1 -sample_fmt s16 output.wav`);
71
+ }
72
+ if (bitsPerSample !== 16) {
73
+ throw new Error(`simulator: unsupported bit depth ${bitsPerSample} (need 16)`);
74
+ }
75
+ return pcm;
76
+ }
77
+ /**
78
+ * Wrap raw 16 kHz mono slin16 PCM into a minimal WAV container and write
79
+ * it to disk. Used by `SimulatedCall.saveTts()` so captured TTS can be
80
+ * played back in any audio tool.
81
+ */
82
+ export async function writeSlin16Wav(path, pcm) {
83
+ const header = Buffer.alloc(44);
84
+ const byteRate = SIMULATOR_SAMPLE_RATE * 2; // 16-bit mono
85
+ header.write('RIFF', 0, 'ascii');
86
+ header.writeUInt32LE(36 + pcm.length, 4);
87
+ header.write('WAVE', 8, 'ascii');
88
+ header.write('fmt ', 12, 'ascii');
89
+ header.writeUInt32LE(16, 16); // fmt chunk size
90
+ header.writeUInt16LE(1, 20); // PCM
91
+ header.writeUInt16LE(1, 22); // mono
92
+ header.writeUInt32LE(SIMULATOR_SAMPLE_RATE, 24);
93
+ header.writeUInt32LE(byteRate, 28);
94
+ header.writeUInt16LE(2, 32); // block align
95
+ header.writeUInt16LE(16, 34); // bits per sample
96
+ header.write('data', 36, 'ascii');
97
+ header.writeUInt32LE(pcm.length, 40);
98
+ await fs.writeFile(path, Buffer.concat([header, pcm]));
99
+ }
100
+ //# sourceMappingURL=wav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav.js","sourceRoot":"","sources":["../../src/simulator/wav.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tts-stream.d.ts","sourceRoot":"","sources":["../../src/streams/tts-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM;IAM5D;;;;;;;;OAQG;IACG,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IA6ChB;;;;;;OAMG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAStE;;;;;OAKG;IACG,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;CAejB"}
1
+ {"version":3,"file":"tts-stream.d.ts","sourceRoot":"","sources":["../../src/streams/tts-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM;IAM5D;;;;;;;;OAQG;IACG,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IAwDhB;;;;;;OAMG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAStE;;;;;OAKG;IACG,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;CAejB"}
@@ -32,7 +32,29 @@ export class TtsInjector {
32
32
  const subId = `tts-ws-${linkedId}-${uuidv4()}`;
33
33
  let wsReady = false;
34
34
  let pendingSend = [];
35
- // Establish WebSocket first, buffer any early audio
35
+ // Attach the "connected" listener BEFORE calling subscribe() so we
36
+ // never miss the event. The simulator (and any future transport
37
+ // that completes the handshake synchronously) fires `connected` on
38
+ // the very next microtask after subscribe() resolves; if the for-
39
+ // await loop below runs synchronously-yielding chunks, it can drain
40
+ // its entire microtask queue before the listener registration —
41
+ // losing the event and all buffered frames. Using `on` + manual
42
+ // `off` instead of `once` also avoids the cross-subscription race
43
+ // where an unrelated `connected` (different subId) would consume
44
+ // our one-shot listener.
45
+ const onConnected = (id) => {
46
+ if (id !== subId)
47
+ return;
48
+ wsReady = true;
49
+ for (const buf of pendingSend) {
50
+ this.wsPool.emit('__send', subId, buf);
51
+ }
52
+ pendingSend = [];
53
+ this.wsPool.off('connected', onConnected);
54
+ };
55
+ this.wsPool.on('connected', onConnected);
56
+ // Establish WebSocket; early audio frames buffer in pendingSend
57
+ // until the handler above flips `wsReady`.
36
58
  await this.wsPool.subscribe({
37
59
  id: subId,
38
60
  path: `/api/v1/ws/tts/${encodeURIComponent(linkedId)}`,
@@ -42,17 +64,6 @@ export class TtsInjector {
42
64
  this.logger.debug({ linkedId }, 'TTS WebSocket closed');
43
65
  },
44
66
  });
45
- // The WsPool emits 'connected' when the handshake completes
46
- this.wsPool.once('connected', (id) => {
47
- if (id !== subId)
48
- return;
49
- wsReady = true;
50
- // Flush buffered frames
51
- for (const buf of pendingSend) {
52
- this.wsPool.emit('__send', subId, buf);
53
- }
54
- pendingSend = [];
55
- });
56
67
  // Stream audio chunks
57
68
  try {
58
69
  for await (const chunk of audioChunks) {
@@ -68,6 +79,7 @@ export class TtsInjector {
68
79
  }
69
80
  }
70
81
  finally {
82
+ this.wsPool.off('connected', onConnected);
71
83
  this.wsPool.unsubscribe(subId);
72
84
  }
73
85
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tts-stream.js","sourceRoot":"","sources":["../../src/streams/tts-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAKpC,MAAM,OAAO,WAAW;IACL,MAAM,CAAS;IACf,IAAI,CAAa;IACjB,MAAM,CAAS;IAEhC,YAAY,MAAc,EAAE,IAAgB,EAAE,MAAc;QAC1D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,eAAe,CACnB,QAAgB,EAChB,WAAkC;QAElC,MAAM,KAAK,GAAG,UAAU,QAAQ,IAAI,MAAM,EAAE,EAAE,CAAC;QAC/C,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,WAAW,GAAa,EAAE,CAAC;QAE/B,oDAAoD;QACpD,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YAC1B,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,kBAAkB,kBAAkB,CAAC,QAAQ,CAAC,EAAE;YACtD,MAAM,EAAE,EAAE;YACV,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,uBAAuB;YACnD,OAAO,EAAE,GAAG,EAAE;gBACZ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,sBAAsB,CAAC,CAAC;YAC1D,CAAC;SACF,CAAC,CAAC;QAEH,4DAA4D;QAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAU,EAAE,EAAE;YAC3C,IAAI,EAAE,KAAK,KAAK;gBAAE,OAAO;YACzB,OAAO,GAAG,IAAI,CAAC;YAEf,wBAAwB;YACxB,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;gBAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YACzC,CAAC;YACD,WAAW,GAAG,EAAE,CAAC;QACnB,CAAC,CAAC,CAAC;QAEH,sBAAsB;QACtB,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;gBACtC,IAAI,OAAO,EAAE,CAAC;oBACZ,sCAAsC;oBACtC,iDAAiD;oBACjD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBAC3C,CAAC;qBAAM,CAAC;oBACN,2BAA2B;oBAC3B,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,QAAgB,EAAE,SAAiB;QACpD,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CACxB,eAAe,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAC7C,SAAS,EACT,sBAAsB,CACvB,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,UAAU,EAAE,EAAE,uBAAuB,CAAC,CAAC;IACxF,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,qBAAqB,CACzB,MAAc,EACd,WAAkC;QAElC,yEAAyE;QACzE,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAEnC,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CACxB,oBAAoB,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAChD,IAAI,EACJ,sBAAsB,CACvB,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,6BAA6B,CAAC,CAAC;IACvF,CAAC;CACF"}
1
+ {"version":3,"file":"tts-stream.js","sourceRoot":"","sources":["../../src/streams/tts-stream.ts"],"names":[],"mappings":"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"}