@livekit/agents-plugin-deepgram 1.4.2 → 1.4.4
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/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/tts.cjs +65 -28
- package/dist/tts.cjs.map +1 -1
- package/dist/tts.d.cts +5 -2
- package/dist/tts.d.ts +5 -2
- package/dist/tts.d.ts.map +1 -1
- package/dist/tts.js +69 -28
- package/dist/tts.js.map +1 -1
- package/package.json +6 -6
- package/src/tts.ts +73 -30
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/tts.cjs
CHANGED
|
@@ -72,8 +72,8 @@ class TTS extends import_agents.tts.TTS {
|
|
|
72
72
|
synthesize(text, connOptions, abortSignal) {
|
|
73
73
|
return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);
|
|
74
74
|
}
|
|
75
|
-
stream() {
|
|
76
|
-
return new SynthesizeStream(this, this.opts);
|
|
75
|
+
stream(options) {
|
|
76
|
+
return new SynthesizeStream(this, this.opts, options == null ? void 0 : options.connOptions);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
class ChunkedStream extends import_agents.tts.ChunkedStream {
|
|
@@ -177,11 +177,12 @@ class ChunkedStream extends import_agents.tts.ChunkedStream {
|
|
|
177
177
|
class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
178
178
|
opts;
|
|
179
179
|
tokenizer;
|
|
180
|
+
#logger = (0, import_agents.log)();
|
|
180
181
|
label = "deepgram.SynthesizeStream";
|
|
181
182
|
static FLUSH_MSG = JSON.stringify({ type: "Flush" });
|
|
182
183
|
static CLOSE_MSG = JSON.stringify({ type: "Close" });
|
|
183
|
-
constructor(tts2, opts) {
|
|
184
|
-
super(tts2);
|
|
184
|
+
constructor(tts2, opts, connOptions) {
|
|
185
|
+
super(tts2, connOptions);
|
|
185
186
|
this.opts = opts;
|
|
186
187
|
this.tokenizer = opts.sentenceTokenizer.stream();
|
|
187
188
|
}
|
|
@@ -251,21 +252,32 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
251
252
|
this.tokenizer.endInput();
|
|
252
253
|
this.tokenizer.close();
|
|
253
254
|
};
|
|
255
|
+
let markInputSent = () => {
|
|
256
|
+
};
|
|
257
|
+
const inputSent = new Promise((resolve) => {
|
|
258
|
+
markInputSent = resolve;
|
|
259
|
+
});
|
|
254
260
|
const sendTask = async () => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
text
|
|
261
|
+
try {
|
|
262
|
+
for await (const event of this.tokenizer) {
|
|
263
|
+
if (this.abortController.signal.aborted) break;
|
|
264
|
+
let text = event.token;
|
|
265
|
+
if (!text.endsWith(" ")) {
|
|
266
|
+
text += " ";
|
|
267
|
+
}
|
|
268
|
+
const message = JSON.stringify({
|
|
269
|
+
type: "Speak",
|
|
270
|
+
text
|
|
271
|
+
});
|
|
272
|
+
ws.send(message);
|
|
273
|
+
markInputSent();
|
|
260
274
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (!this.abortController.signal.aborted) {
|
|
268
|
-
ws.send(SynthesizeStream.FLUSH_MSG);
|
|
275
|
+
if (!this.abortController.signal.aborted) {
|
|
276
|
+
ws.send(SynthesizeStream.FLUSH_MSG);
|
|
277
|
+
markInputSent();
|
|
278
|
+
}
|
|
279
|
+
} finally {
|
|
280
|
+
markInputSent();
|
|
269
281
|
}
|
|
270
282
|
};
|
|
271
283
|
const recvTask = async () => {
|
|
@@ -285,14 +297,22 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
285
297
|
timeout = null;
|
|
286
298
|
}
|
|
287
299
|
};
|
|
300
|
+
const resetMessageTimeout = (reject) => {
|
|
301
|
+
clearMessageTimeout();
|
|
302
|
+
timeout = setTimeout(() => {
|
|
303
|
+
reject(new import_agents.APITimeoutError({ message: "Deepgram TTS recv idle timeout" }));
|
|
304
|
+
}, this.connOptions.timeoutMs);
|
|
305
|
+
};
|
|
306
|
+
await inputSent;
|
|
307
|
+
if (this.abortController.signal.aborted) return;
|
|
288
308
|
return new Promise((resolve, reject) => {
|
|
309
|
+
resetMessageTimeout(reject);
|
|
289
310
|
ws.on("message", (data, isBinary) => {
|
|
290
311
|
clearMessageTimeout();
|
|
291
312
|
if (!isBinary) {
|
|
292
313
|
const message = JSON.parse(data.toString());
|
|
293
314
|
if (message.type === "Flushed") {
|
|
294
315
|
finalReceived = true;
|
|
295
|
-
clearMessageTimeout();
|
|
296
316
|
for (const frame of bstream.flush()) {
|
|
297
317
|
sendLastFrame(segmentId, false);
|
|
298
318
|
lastFrame = frame;
|
|
@@ -302,7 +322,16 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
302
322
|
this.queue.put(SynthesizeStream.END_OF_STREAM);
|
|
303
323
|
}
|
|
304
324
|
resolve();
|
|
325
|
+
return;
|
|
326
|
+
} else if (message.type === "Warning") {
|
|
327
|
+
this.#logger.warn(`Deepgram warning: ${message.warn_msg}`);
|
|
328
|
+
} else if (message.type === "Error" || message.type === "error") {
|
|
329
|
+
reject(new import_agents.APIError("Deepgram TTS returned error", { body: message }));
|
|
330
|
+
return;
|
|
331
|
+
} else if (message.type !== "Metadata") {
|
|
332
|
+
this.#logger.warn({ message }, "Unknown Deepgram message type");
|
|
305
333
|
}
|
|
334
|
+
resetMessageTimeout(reject);
|
|
306
335
|
return;
|
|
307
336
|
}
|
|
308
337
|
const buffer = data instanceof Buffer ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) : data;
|
|
@@ -310,17 +339,21 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
310
339
|
sendLastFrame(segmentId, false);
|
|
311
340
|
lastFrame = frame;
|
|
312
341
|
}
|
|
342
|
+
resetMessageTimeout(reject);
|
|
313
343
|
});
|
|
314
|
-
ws.on("close", (
|
|
344
|
+
ws.on("close", (code, reason) => {
|
|
345
|
+
clearMessageTimeout();
|
|
315
346
|
if (!finalReceived) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
347
|
+
reject(
|
|
348
|
+
new import_agents.APIStatusError({
|
|
349
|
+
message: "Deepgram websocket connection closed unexpectedly",
|
|
350
|
+
options: {
|
|
351
|
+
statusCode: code || -1,
|
|
352
|
+
body: { reason: reason.toString() }
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
);
|
|
356
|
+
return;
|
|
324
357
|
}
|
|
325
358
|
resolve();
|
|
326
359
|
});
|
|
@@ -333,7 +366,11 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
333
366
|
try {
|
|
334
367
|
await Promise.all([inputTask(), sendTask(), recvTask()]);
|
|
335
368
|
} catch (e) {
|
|
336
|
-
|
|
369
|
+
if (this.abortController.signal.aborted) return;
|
|
370
|
+
if (e instanceof import_agents.APIError) throw e;
|
|
371
|
+
throw new import_agents.APIConnectionError({
|
|
372
|
+
message: `Deepgram TTS WebSocket failed: ${e.message ?? "unknown error"}`
|
|
373
|
+
});
|
|
337
374
|
} finally {
|
|
338
375
|
await this.closeWebSocket(ws);
|
|
339
376
|
}
|
package/dist/tts.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n AudioByteStream,\n log,\n shortuuid,\n tokenize,\n tts,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { request } from 'node:https';\nimport { type RawData, WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\nconst AUTHORIZATION_HEADER = 'Authorization';\nconst NUM_CHANNELS = 1;\nconst MIN_SENTENCE_LENGTH = 8;\n\nexport interface TTSOptions {\n model: TTSModels | string;\n encoding: TTSEncoding;\n sampleRate: number;\n speed?: number;\n apiKey?: string;\n baseUrl?: string;\n sentenceTokenizer: tokenize.SentenceTokenizer;\n capabilities: tts.TTSCapabilities;\n}\n\nconst defaultTTSOptions: TTSOptions = {\n model: 'aura-asteria-en',\n encoding: 'linear16',\n sampleRate: 24000,\n apiKey: process.env.DEEPGRAM_API_KEY,\n baseUrl: 'https://api.deepgram.com',\n capabilities: {\n streaming: true,\n },\n sentenceTokenizer: new tokenize.basic.SentenceTokenizer({\n minSentenceLength: MIN_SENTENCE_LENGTH,\n }),\n};\n\nexport class TTS extends tts.TTS {\n private opts: TTSOptions;\n label = 'deepgram.TTS';\n\n get model(): string {\n return this.opts.model;\n }\n\n get provider(): string {\n return 'Deepgram';\n }\n\n constructor(opts: Partial<TTSOptions> = {}) {\n super(opts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {\n streaming: opts.capabilities?.streaming ?? defaultTTSOptions.capabilities.streaming,\n });\n\n this.opts = {\n ...defaultTTSOptions,\n ...opts,\n };\n\n if (this.opts.apiKey === undefined) {\n throw new Error(\n 'Deepgram API key is required, whether as an argument or as $DEEPGRAM_API_KEY',\n );\n }\n\n if (this.opts.speed !== undefined && (this.opts.speed < 0.7 || this.opts.speed > 1.5)) {\n throw new Error(`Deepgram TTS speed must be between 0.7 and 1.5, got ${this.opts.speed}`);\n }\n }\n\n synthesize(\n text: string,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ): tts.ChunkedStream {\n return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);\n }\n\n stream(): tts.SynthesizeStream {\n return new SynthesizeStream(this, this.opts);\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n label = 'deepgram.ChunkedStream';\n #logger = log();\n private opts: TTSOptions;\n private text: string;\n\n constructor(\n tts: TTS,\n text: string,\n opts: TTSOptions,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ) {\n super(text, tts, connOptions, abortSignal);\n this.text = text;\n this.opts = opts;\n }\n\n protected async run() {\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n const json = { text: this.text };\n const url = new URL(`${this.opts.baseUrl!}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => {\n if (!settled) {\n settled = true;\n fn();\n }\n };\n\n const req = request(\n {\n hostname: url.hostname,\n port: 443,\n path: url.pathname + url.search,\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n 'Content-Type': 'application/json',\n },\n signal: this.abortSignal,\n },\n (res) => {\n if (res.statusCode !== 200) {\n settle(() =>\n reject(\n new Error(\n `Deepgram TTS HTTP request failed: ${res.statusCode} ${res.statusMessage}`,\n ),\n ),\n );\n return;\n }\n\n res.on('data', (chunk) => {\n for (const frame of bstream.write(chunk)) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n });\n\n res.on('error', (err) => {\n if (err.message === 'aborted') return;\n this.#logger.error({ err }, 'Deepgram TTS response error');\n settle(() => reject(err));\n });\n\n res.on('close', () => {\n for (const frame of bstream.flush()) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n if (!this.queue.closed) {\n this.queue.close();\n }\n settle(() => resolve());\n });\n },\n );\n\n req.on('error', (err) => {\n if (err.name === 'AbortError') return;\n this.#logger.error({ err }, 'Deepgram TTS request error');\n settle(() => reject(err));\n });\n\n req.on('close', () => settle(() => resolve()));\n req.write(JSON.stringify(json));\n req.end();\n });\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n private opts: TTSOptions;\n private tokenizer: tokenize.SentenceStream;\n label = 'deepgram.SynthesizeStream';\n\n private static readonly FLUSH_MSG = JSON.stringify({ type: 'Flush' });\n private static readonly CLOSE_MSG = JSON.stringify({ type: 'Close' });\n\n constructor(tts: TTS, opts: TTSOptions) {\n super(tts);\n this.opts = opts;\n this.tokenizer = opts.sentenceTokenizer.stream();\n }\n\n private async closeWebSocket(ws: WebSocket): Promise<void> {\n try {\n // Send Flush and Close messages to ensure Deepgram processes all remaining audio\n // and properly terminates the session, preventing lingering TTS sessions\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n ws.send(SynthesizeStream.CLOSE_MSG);\n\n // Wait for server acknowledgment to prevent race conditions and ensure\n // proper cleanup, avoiding 429 Too Many Requests errors from lingering sessions\n try {\n await new Promise<void>((resolve, _reject) => {\n const timeout = setTimeout(() => {\n resolve();\n }, 1000);\n\n ws.once('message', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('close', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('error', () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n } catch (e) {\n // Ignore timeout or other errors during close sequence\n }\n }\n } catch (e) {\n console.warn(`Error during WebSocket close sequence: ${e}`);\n } finally {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n }\n }\n\n protected async run() {\n const requestId = shortuuid();\n const segmentId = shortuuid();\n\n const wsUrl = this.opts.baseUrl!.replace(/^http/, 'ws');\n const url = new URL(`${wsUrl}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n const ws = new WebSocket(url, {\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n },\n });\n\n await new Promise((resolve, reject) => {\n ws.on('open', resolve);\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(`WebSocket returned ${code}`));\n });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.tokenizer.flush();\n continue;\n }\n this.tokenizer.pushText(data);\n }\n this.tokenizer.endInput();\n this.tokenizer.close();\n };\n\n const sendTask = async () => {\n for await (const event of this.tokenizer) {\n if (this.abortController.signal.aborted) break;\n\n let text = event.token;\n if (!text.endsWith(' ')) {\n text += ' ';\n }\n\n const message = JSON.stringify({\n type: 'Speak',\n text: text,\n });\n\n ws.send(message);\n }\n\n if (!this.abortController.signal.aborted) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n }\n };\n\n const recvTask = async () => {\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n let finalReceived = false;\n let timeout: NodeJS.Timeout | null = null;\n let lastFrame: AudioFrame | undefined;\n\n const sendLastFrame = (segmentId: string, final: boolean) => {\n if (lastFrame && !this.queue.closed) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final });\n lastFrame = undefined;\n }\n };\n\n const clearMessageTimeout = () => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n };\n\n return new Promise<void>((resolve, reject) => {\n ws.on('message', (data: RawData, isBinary: boolean) => {\n clearMessageTimeout();\n\n if (!isBinary) {\n const message = JSON.parse(data.toString());\n if (message.type === 'Flushed') {\n finalReceived = true;\n clearMessageTimeout();\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n resolve();\n }\n\n return;\n }\n\n const buffer =\n data instanceof Buffer\n ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)\n : (data as ArrayBuffer);\n for (const frame of bstream.write(buffer as ArrayBuffer)) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n });\n\n ws.on('close', (_code, _reason) => {\n if (!finalReceived) {\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n }\n resolve();\n });\n\n ws.on('error', (error) => {\n clearMessageTimeout();\n reject(error);\n });\n });\n };\n\n try {\n await Promise.all([inputTask(), sendTask(), recvTask()]);\n } catch (e) {\n throw new Error(`failed in main task: ${e}`);\n } finally {\n await this.closeWebSocket(ws);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAOO;AAEP,wBAAwB;AACxB,gBAAwC;AAGxC,MAAM,uBAAuB;AAC7B,MAAM,eAAe;AACrB,MAAM,sBAAsB;AAa5B,MAAM,oBAAgC;AAAA,EACpC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,QAAQ,QAAQ,IAAI;AAAA,EACpB,SAAS;AAAA,EACT,cAAc;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA,mBAAmB,IAAI,uBAAS,MAAM,kBAAkB;AAAA,IACtD,mBAAmB;AAAA,EACrB,CAAC;AACH;AAEO,MAAM,YAAY,kBAAI,IAAI;AAAA,EACvB;AAAA,EACR,QAAQ;AAAA,EAER,IAAI,QAAgB;AAClB,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,IAAI,WAAmB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,OAA4B,CAAC,GAAG;AAzD9C;AA0DI,UAAM,KAAK,cAAc,kBAAkB,YAAY,cAAc;AAAA,MACnE,aAAW,UAAK,iBAAL,mBAAmB,cAAa,kBAAkB,aAAa;AAAA,IAC5E,CAAC;AAED,SAAK,OAAO;AAAA,MACV,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAEA,QAAI,KAAK,KAAK,WAAW,QAAW;AAClC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,KAAK,KAAK,UAAU,WAAc,KAAK,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,MAAM;AACrF,YAAM,IAAI,MAAM,uDAAuD,KAAK,KAAK,KAAK,EAAE;AAAA,IAC1F;AAAA,EACF;AAAA,EAEA,WACE,MACA,aACA,aACmB;AACnB,WAAO,IAAI,cAAc,MAAM,MAAM,KAAK,MAAM,aAAa,WAAW;AAAA,EAC1E;AAAA,EAEA,SAA+B;AAC7B,WAAO,IAAI,iBAAiB,MAAM,KAAK,IAAI;AAAA,EAC7C;AACF;AAEO,MAAM,sBAAsB,kBAAI,cAAc;AAAA,EACnD,QAAQ;AAAA,EACR,cAAU,mBAAI;AAAA,EACN;AAAA,EACA;AAAA,EAER,YACEA,MACA,MACA,MACA,aACA,aACA;AACA,UAAM,MAAMA,MAAK,aAAa,WAAW;AACzC,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,MAAgB,MAAM;AACpB,UAAM,gBAAY,yBAAU;AAC5B,UAAM,UAAU,IAAI,8BAAgB,KAAK,KAAK,YAAY,YAAY;AACtE,UAAM,OAAO,EAAE,MAAM,KAAK,KAAK;AAC/B,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,KAAK,OAAQ,WAAW;AACpD,QAAI,aAAa,OAAO,eAAe,KAAK,KAAK,WAAW,SAAS,CAAC;AACtE,QAAI,aAAa,OAAO,SAAS,KAAK,KAAK,KAAK;AAChD,QAAI,aAAa,OAAO,YAAY,KAAK,KAAK,QAAQ;AACtD,QAAI,KAAK,KAAK,UAAU,QAAW;AACjC,UAAI,aAAa,OAAO,SAAS,KAAK,KAAK,MAAM,SAAS,CAAC;AAAA,IAC7D;AAEA,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAI,UAAU;AACd,YAAM,SAAS,CAAC,OAAmB;AACjC,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,aAAG;AAAA,QACL;AAAA,MACF;AAEA,YAAM,UAAM;AAAA,QACV;AAAA,UACE,UAAU,IAAI;AAAA,UACd,MAAM;AAAA,UACN,MAAM,IAAI,WAAW,IAAI;AAAA,UACzB,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,CAAC,oBAAoB,GAAG,SAAS,KAAK,KAAK,MAAO;AAAA,YAClD,gBAAgB;AAAA,UAClB;AAAA,UACA,QAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,QAAQ;AACP,cAAI,IAAI,eAAe,KAAK;AAC1B;AAAA,cAAO,MACL;AAAA,gBACE,IAAI;AAAA,kBACF,qCAAqC,IAAI,UAAU,IAAI,IAAI,aAAa;AAAA,gBAC1E;AAAA,cACF;AAAA,YACF;AACA;AAAA,UACF;AAEA,cAAI,GAAG,QAAQ,CAAC,UAAU;AACxB,uBAAW,SAAS,QAAQ,MAAM,KAAK,GAAG;AACxC,kBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,qBAAK,MAAM,IAAI;AAAA,kBACb;AAAA,kBACA;AAAA,kBACA,OAAO;AAAA,kBACP,WAAW;AAAA,gBACb,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF,CAAC;AAED,cAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,gBAAI,IAAI,YAAY,UAAW;AAC/B,iBAAK,QAAQ,MAAM,EAAE,IAAI,GAAG,6BAA6B;AACzD,mBAAO,MAAM,OAAO,GAAG,CAAC;AAAA,UAC1B,CAAC;AAED,cAAI,GAAG,SAAS,MAAM;AACpB,uBAAW,SAAS,QAAQ,MAAM,GAAG;AACnC,kBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,qBAAK,MAAM,IAAI;AAAA,kBACb;AAAA,kBACA;AAAA,kBACA,OAAO;AAAA,kBACP,WAAW;AAAA,gBACb,CAAC;AAAA,cACH;AAAA,YACF;AACA,gBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,mBAAK,MAAM,MAAM;AAAA,YACnB;AACA,mBAAO,MAAM,QAAQ,CAAC;AAAA,UACxB,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,YAAI,IAAI,SAAS,aAAc;AAC/B,aAAK,QAAQ,MAAM,EAAE,IAAI,GAAG,4BAA4B;AACxD,eAAO,MAAM,OAAO,GAAG,CAAC;AAAA,MAC1B,CAAC;AAED,UAAI,GAAG,SAAS,MAAM,OAAO,MAAM,QAAQ,CAAC,CAAC;AAC7C,UAAI,MAAM,KAAK,UAAU,IAAI,CAAC;AAC9B,UAAI,IAAI;AAAA,IACV,CAAC;AAAA,EACH;AACF;AAEO,MAAM,yBAAyB,kBAAI,iBAAiB;AAAA,EACjD;AAAA,EACA;AAAA,EACR,QAAQ;AAAA,EAER,OAAwB,YAAY,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAAA,EACpE,OAAwB,YAAY,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAAA,EAEpE,YAAYA,MAAU,MAAkB;AACtC,UAAMA,IAAG;AACT,SAAK,OAAO;AACZ,SAAK,YAAY,KAAK,kBAAkB,OAAO;AAAA,EACjD;AAAA,EAEA,MAAc,eAAe,IAA8B;AACzD,QAAI;AAGF,UAAI,GAAG,eAAe,oBAAU,MAAM;AACpC,WAAG,KAAK,iBAAiB,SAAS;AAClC,WAAG,KAAK,iBAAiB,SAAS;AAIlC,YAAI;AACF,gBAAM,IAAI,QAAc,CAAC,SAAS,YAAY;AAC5C,kBAAM,UAAU,WAAW,MAAM;AAC/B,sBAAQ;AAAA,YACV,GAAG,GAAI;AAEP,eAAG,KAAK,WAAW,MAAM;AACvB,2BAAa,OAAO;AACpB,sBAAQ;AAAA,YACV,CAAC;AAED,eAAG,KAAK,SAAS,MAAM;AACrB,2BAAa,OAAO;AACpB,sBAAQ;AAAA,YACV,CAAC;AAED,eAAG,KAAK,SAAS,MAAM;AACrB,2BAAa,OAAO;AACpB,sBAAQ;AAAA,YACV,CAAC;AAAA,UACH,CAAC;AAAA,QACH,SAAS,GAAG;AAAA,QAEZ;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,cAAQ,KAAK,0CAA0C,CAAC,EAAE;AAAA,IAC5D,UAAE;AACA,UAAI,GAAG,eAAe,oBAAU,QAAQ,GAAG,eAAe,oBAAU,YAAY;AAC9E,WAAG,MAAM;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,MAAM;AACpB,UAAM,gBAAY,yBAAU;AAC5B,UAAM,gBAAY,yBAAU;AAE5B,UAAM,QAAQ,KAAK,KAAK,QAAS,QAAQ,SAAS,IAAI;AACtD,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,WAAW;AACvC,QAAI,aAAa,OAAO,eAAe,KAAK,KAAK,WAAW,SAAS,CAAC;AACtE,QAAI,aAAa,OAAO,SAAS,KAAK,KAAK,KAAK;AAChD,QAAI,aAAa,OAAO,YAAY,KAAK,KAAK,QAAQ;AACtD,QAAI,KAAK,KAAK,UAAU,QAAW;AACjC,UAAI,aAAa,OAAO,SAAS,KAAK,KAAK,MAAM,SAAS,CAAC;AAAA,IAC7D;AAEA,UAAM,KAAK,IAAI,oBAAU,KAAK;AAAA,MAC5B,SAAS;AAAA,QACP,CAAC,oBAAoB,GAAG,SAAS,KAAK,KAAK,MAAO;AAAA,MACpD;AAAA,IACF,CAAC;AAED,UAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AACrC,SAAG,GAAG,QAAQ,OAAO;AACrB,SAAG,GAAG,SAAS,CAAC,UAAU,OAAO,KAAK,CAAC;AACvC,SAAG,GAAG,SAAS,CAAC,SAAS,OAAO,sBAAsB,IAAI,EAAE,CAAC;AAAA,IAC/D,CAAC;AAED,UAAM,YAAY,YAAY;AAC5B,uBAAiB,QAAQ,KAAK,OAAO;AACnC,YAAI,SAAS,iBAAiB,gBAAgB;AAC5C,eAAK,UAAU,MAAM;AACrB;AAAA,QACF;AACA,aAAK,UAAU,SAAS,IAAI;AAAA,MAC9B;AACA,WAAK,UAAU,SAAS;AACxB,WAAK,UAAU,MAAM;AAAA,IACvB;AAEA,UAAM,WAAW,YAAY;AAC3B,uBAAiB,SAAS,KAAK,WAAW;AACxC,YAAI,KAAK,gBAAgB,OAAO,QAAS;AAEzC,YAAI,OAAO,MAAM;AACjB,YAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACvB,kBAAQ;AAAA,QACV;AAEA,cAAM,UAAU,KAAK,UAAU;AAAA,UAC7B,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAED,WAAG,KAAK,OAAO;AAAA,MACjB;AAEA,UAAI,CAAC,KAAK,gBAAgB,OAAO,SAAS;AACxC,WAAG,KAAK,iBAAiB,SAAS;AAAA,MACpC;AAAA,IACF;AAEA,UAAM,WAAW,YAAY;AAC3B,YAAM,UAAU,IAAI,8BAAgB,KAAK,KAAK,YAAY,YAAY;AACtE,UAAI,gBAAgB;AACpB,UAAI,UAAiC;AACrC,UAAI;AAEJ,YAAM,gBAAgB,CAACC,YAAmB,UAAmB;AAC3D,YAAI,aAAa,CAAC,KAAK,MAAM,QAAQ;AACnC,eAAK,MAAM,IAAI,EAAE,WAAW,WAAAA,YAAW,OAAO,WAAW,MAAM,CAAC;AAChE,sBAAY;AAAA,QACd;AAAA,MACF;AAEA,YAAM,sBAAsB,MAAM;AAChC,YAAI,SAAS;AACX,uBAAa,OAAO;AACpB,oBAAU;AAAA,QACZ;AAAA,MACF;AAEA,aAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAG,GAAG,WAAW,CAAC,MAAe,aAAsB;AACrD,8BAAoB;AAEpB,cAAI,CAAC,UAAU;AACb,kBAAM,UAAU,KAAK,MAAM,KAAK,SAAS,CAAC;AAC1C,gBAAI,QAAQ,SAAS,WAAW;AAC9B,8BAAgB;AAChB,kCAAoB;AACpB,yBAAW,SAAS,QAAQ,MAAM,GAAG;AACnC,8BAAc,WAAW,KAAK;AAC9B,4BAAY;AAAA,cACd;AACA,4BAAc,WAAW,IAAI;AAE7B,kBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,qBAAK,MAAM,IAAI,iBAAiB,aAAa;AAAA,cAC/C;AACA,sBAAQ;AAAA,YACV;AAEA;AAAA,UACF;AAEA,gBAAM,SACJ,gBAAgB,SACZ,KAAK,OAAO,MAAM,KAAK,YAAY,KAAK,aAAa,KAAK,UAAU,IACnE;AACP,qBAAW,SAAS,QAAQ,MAAM,MAAqB,GAAG;AACxD,0BAAc,WAAW,KAAK;AAC9B,wBAAY;AAAA,UACd;AAAA,QACF,CAAC;AAED,WAAG,GAAG,SAAS,CAAC,OAAO,YAAY;AACjC,cAAI,CAAC,eAAe;AAClB,uBAAW,SAAS,QAAQ,MAAM,GAAG;AACnC,4BAAc,WAAW,KAAK;AAC9B,0BAAY;AAAA,YACd;AACA,0BAAc,WAAW,IAAI;AAE7B,gBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,mBAAK,MAAM,IAAI,iBAAiB,aAAa;AAAA,YAC/C;AAAA,UACF;AACA,kBAAQ;AAAA,QACV,CAAC;AAED,WAAG,GAAG,SAAS,CAAC,UAAU;AACxB,8BAAoB;AACpB,iBAAO,KAAK;AAAA,QACd,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,QAAI;AACF,YAAM,QAAQ,IAAI,CAAC,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC;AAAA,IACzD,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,wBAAwB,CAAC,EAAE;AAAA,IAC7C,UAAE;AACA,YAAM,KAAK,eAAe,EAAE;AAAA,IAC9B;AAAA,EACF;AACF;","names":["tts","segmentId"]}
|
|
1
|
+
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n APIConnectionError,\n APIError,\n APIStatusError,\n APITimeoutError,\n AudioByteStream,\n log,\n shortuuid,\n tokenize,\n tts,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { request } from 'node:https';\nimport { type RawData, WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\nconst AUTHORIZATION_HEADER = 'Authorization';\nconst NUM_CHANNELS = 1;\nconst MIN_SENTENCE_LENGTH = 8;\n\nexport interface TTSOptions {\n model: TTSModels | string;\n encoding: TTSEncoding;\n sampleRate: number;\n speed?: number;\n apiKey?: string;\n baseUrl?: string;\n sentenceTokenizer: tokenize.SentenceTokenizer;\n capabilities: tts.TTSCapabilities;\n}\n\nconst defaultTTSOptions: TTSOptions = {\n model: 'aura-asteria-en',\n encoding: 'linear16',\n sampleRate: 24000,\n apiKey: process.env.DEEPGRAM_API_KEY,\n baseUrl: 'https://api.deepgram.com',\n capabilities: {\n streaming: true,\n },\n sentenceTokenizer: new tokenize.basic.SentenceTokenizer({\n minSentenceLength: MIN_SENTENCE_LENGTH,\n }),\n};\n\nexport class TTS extends tts.TTS {\n private opts: TTSOptions;\n label = 'deepgram.TTS';\n\n get model(): string {\n return this.opts.model;\n }\n\n get provider(): string {\n return 'Deepgram';\n }\n\n constructor(opts: Partial<TTSOptions> = {}) {\n super(opts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {\n streaming: opts.capabilities?.streaming ?? defaultTTSOptions.capabilities.streaming,\n });\n\n this.opts = {\n ...defaultTTSOptions,\n ...opts,\n };\n\n if (this.opts.apiKey === undefined) {\n throw new Error(\n 'Deepgram API key is required, whether as an argument or as $DEEPGRAM_API_KEY',\n );\n }\n\n if (this.opts.speed !== undefined && (this.opts.speed < 0.7 || this.opts.speed > 1.5)) {\n throw new Error(`Deepgram TTS speed must be between 0.7 and 1.5, got ${this.opts.speed}`);\n }\n }\n\n synthesize(\n text: string,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ): tts.ChunkedStream {\n return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): tts.SynthesizeStream {\n return new SynthesizeStream(this, this.opts, options?.connOptions);\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n label = 'deepgram.ChunkedStream';\n #logger = log();\n private opts: TTSOptions;\n private text: string;\n\n constructor(\n tts: TTS,\n text: string,\n opts: TTSOptions,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ) {\n super(text, tts, connOptions, abortSignal);\n this.text = text;\n this.opts = opts;\n }\n\n protected async run() {\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n const json = { text: this.text };\n const url = new URL(`${this.opts.baseUrl!}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => {\n if (!settled) {\n settled = true;\n fn();\n }\n };\n\n const req = request(\n {\n hostname: url.hostname,\n port: 443,\n path: url.pathname + url.search,\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n 'Content-Type': 'application/json',\n },\n signal: this.abortSignal,\n },\n (res) => {\n if (res.statusCode !== 200) {\n settle(() =>\n reject(\n new Error(\n `Deepgram TTS HTTP request failed: ${res.statusCode} ${res.statusMessage}`,\n ),\n ),\n );\n return;\n }\n\n res.on('data', (chunk) => {\n for (const frame of bstream.write(chunk)) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n });\n\n res.on('error', (err) => {\n if (err.message === 'aborted') return;\n this.#logger.error({ err }, 'Deepgram TTS response error');\n settle(() => reject(err));\n });\n\n res.on('close', () => {\n for (const frame of bstream.flush()) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n if (!this.queue.closed) {\n this.queue.close();\n }\n settle(() => resolve());\n });\n },\n );\n\n req.on('error', (err) => {\n if (err.name === 'AbortError') return;\n this.#logger.error({ err }, 'Deepgram TTS request error');\n settle(() => reject(err));\n });\n\n req.on('close', () => settle(() => resolve()));\n req.write(JSON.stringify(json));\n req.end();\n });\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n private opts: TTSOptions;\n private tokenizer: tokenize.SentenceStream;\n #logger = log();\n label = 'deepgram.SynthesizeStream';\n\n private static readonly FLUSH_MSG = JSON.stringify({ type: 'Flush' });\n private static readonly CLOSE_MSG = JSON.stringify({ type: 'Close' });\n\n constructor(tts: TTS, opts: TTSOptions, connOptions?: APIConnectOptions) {\n super(tts, connOptions);\n this.opts = opts;\n this.tokenizer = opts.sentenceTokenizer.stream();\n }\n\n private async closeWebSocket(ws: WebSocket): Promise<void> {\n try {\n // Send Flush and Close messages to ensure Deepgram processes all remaining audio\n // and properly terminates the session, preventing lingering TTS sessions\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n ws.send(SynthesizeStream.CLOSE_MSG);\n\n // Wait for server acknowledgment to prevent race conditions and ensure\n // proper cleanup, avoiding 429 Too Many Requests errors from lingering sessions\n try {\n await new Promise<void>((resolve, _reject) => {\n const timeout = setTimeout(() => {\n resolve();\n }, 1000);\n\n ws.once('message', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('close', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('error', () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n } catch (e) {\n // Ignore timeout or other errors during close sequence\n }\n }\n } catch (e) {\n console.warn(`Error during WebSocket close sequence: ${e}`);\n } finally {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n }\n }\n\n protected async run() {\n const requestId = shortuuid();\n const segmentId = shortuuid();\n\n const wsUrl = this.opts.baseUrl!.replace(/^http/, 'ws');\n const url = new URL(`${wsUrl}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n const ws = new WebSocket(url, {\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n },\n });\n\n await new Promise((resolve, reject) => {\n ws.on('open', resolve);\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(`WebSocket returned ${code}`));\n });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.tokenizer.flush();\n continue;\n }\n this.tokenizer.pushText(data);\n }\n this.tokenizer.endInput();\n this.tokenizer.close();\n };\n\n let markInputSent: () => void = () => {};\n const inputSent = new Promise<void>((resolve) => {\n markInputSent = resolve;\n });\n\n const sendTask = async () => {\n try {\n for await (const event of this.tokenizer) {\n if (this.abortController.signal.aborted) break;\n\n let text = event.token;\n if (!text.endsWith(' ')) {\n text += ' ';\n }\n\n const message = JSON.stringify({\n type: 'Speak',\n text: text,\n });\n\n ws.send(message);\n markInputSent();\n }\n\n if (!this.abortController.signal.aborted) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n markInputSent();\n }\n } finally {\n markInputSent();\n }\n };\n\n const recvTask = async () => {\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n let finalReceived = false;\n let timeout: NodeJS.Timeout | null = null;\n let lastFrame: AudioFrame | undefined;\n\n const sendLastFrame = (segmentId: string, final: boolean) => {\n if (lastFrame && !this.queue.closed) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final });\n lastFrame = undefined;\n }\n };\n\n const clearMessageTimeout = () => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n };\n\n const resetMessageTimeout = (reject: (reason?: unknown) => void) => {\n clearMessageTimeout();\n timeout = setTimeout(() => {\n reject(new APITimeoutError({ message: 'Deepgram TTS recv idle timeout' }));\n }, this.connOptions.timeoutMs);\n };\n\n await inputSent;\n if (this.abortController.signal.aborted) return;\n\n return new Promise<void>((resolve, reject) => {\n resetMessageTimeout(reject);\n\n ws.on('message', (data: RawData, isBinary: boolean) => {\n clearMessageTimeout();\n\n if (!isBinary) {\n const message = JSON.parse(data.toString());\n if (message.type === 'Flushed') {\n finalReceived = true;\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n resolve();\n return;\n } else if (message.type === 'Warning') {\n this.#logger.warn(`Deepgram warning: ${message.warn_msg}`);\n } else if (message.type === 'Error' || message.type === 'error') {\n reject(new APIError('Deepgram TTS returned error', { body: message }));\n return;\n } else if (message.type !== 'Metadata') {\n this.#logger.warn({ message }, 'Unknown Deepgram message type');\n }\n\n resetMessageTimeout(reject);\n return;\n }\n\n const buffer =\n data instanceof Buffer\n ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)\n : (data as ArrayBuffer);\n for (const frame of bstream.write(buffer as ArrayBuffer)) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n resetMessageTimeout(reject);\n });\n\n ws.on('close', (code, reason) => {\n clearMessageTimeout();\n if (!finalReceived) {\n reject(\n new APIStatusError({\n message: 'Deepgram websocket connection closed unexpectedly',\n options: {\n statusCode: code || -1,\n body: { reason: reason.toString() },\n },\n }),\n );\n return;\n }\n resolve();\n });\n\n ws.on('error', (error) => {\n clearMessageTimeout();\n reject(error);\n });\n });\n };\n\n try {\n await Promise.all([inputTask(), sendTask(), recvTask()]);\n } catch (e) {\n if (this.abortController.signal.aborted) return;\n if (e instanceof APIError) throw e;\n throw new APIConnectionError({\n message: `Deepgram TTS WebSocket failed: ${(e as Error).message ?? 'unknown error'}`,\n });\n } finally {\n await this.closeWebSocket(ws);\n }\n }\n}\n"],"mappings":"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","names":["tts","segmentId"]}
|
package/dist/tts.d.cts
CHANGED
|
@@ -17,7 +17,9 @@ export declare class TTS extends tts.TTS {
|
|
|
17
17
|
get provider(): string;
|
|
18
18
|
constructor(opts?: Partial<TTSOptions>);
|
|
19
19
|
synthesize(text: string, connOptions?: APIConnectOptions, abortSignal?: AbortSignal): tts.ChunkedStream;
|
|
20
|
-
stream(
|
|
20
|
+
stream(options?: {
|
|
21
|
+
connOptions?: APIConnectOptions;
|
|
22
|
+
}): tts.SynthesizeStream;
|
|
21
23
|
}
|
|
22
24
|
export declare class ChunkedStream extends tts.ChunkedStream {
|
|
23
25
|
#private;
|
|
@@ -28,12 +30,13 @@ export declare class ChunkedStream extends tts.ChunkedStream {
|
|
|
28
30
|
protected run(): Promise<void>;
|
|
29
31
|
}
|
|
30
32
|
export declare class SynthesizeStream extends tts.SynthesizeStream {
|
|
33
|
+
#private;
|
|
31
34
|
private opts;
|
|
32
35
|
private tokenizer;
|
|
33
36
|
label: string;
|
|
34
37
|
private static readonly FLUSH_MSG;
|
|
35
38
|
private static readonly CLOSE_MSG;
|
|
36
|
-
constructor(tts: TTS, opts: TTSOptions);
|
|
39
|
+
constructor(tts: TTS, opts: TTSOptions, connOptions?: APIConnectOptions);
|
|
37
40
|
private closeWebSocket;
|
|
38
41
|
protected run(): Promise<void>;
|
|
39
42
|
}
|
package/dist/tts.d.ts
CHANGED
|
@@ -17,7 +17,9 @@ export declare class TTS extends tts.TTS {
|
|
|
17
17
|
get provider(): string;
|
|
18
18
|
constructor(opts?: Partial<TTSOptions>);
|
|
19
19
|
synthesize(text: string, connOptions?: APIConnectOptions, abortSignal?: AbortSignal): tts.ChunkedStream;
|
|
20
|
-
stream(
|
|
20
|
+
stream(options?: {
|
|
21
|
+
connOptions?: APIConnectOptions;
|
|
22
|
+
}): tts.SynthesizeStream;
|
|
21
23
|
}
|
|
22
24
|
export declare class ChunkedStream extends tts.ChunkedStream {
|
|
23
25
|
#private;
|
|
@@ -28,12 +30,13 @@ export declare class ChunkedStream extends tts.ChunkedStream {
|
|
|
28
30
|
protected run(): Promise<void>;
|
|
29
31
|
}
|
|
30
32
|
export declare class SynthesizeStream extends tts.SynthesizeStream {
|
|
33
|
+
#private;
|
|
31
34
|
private opts;
|
|
32
35
|
private tokenizer;
|
|
33
36
|
label: string;
|
|
34
37
|
private static readonly FLUSH_MSG;
|
|
35
38
|
private static readonly CLOSE_MSG;
|
|
36
|
-
constructor(tts: TTS, opts: TTSOptions);
|
|
39
|
+
constructor(tts: TTS, opts: TTSOptions, connOptions?: APIConnectOptions);
|
|
37
40
|
private closeWebSocket;
|
|
38
41
|
protected run(): Promise<void>;
|
|
39
42
|
}
|
package/dist/tts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EAQtB,QAAQ,EACR,GAAG,EACJ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAM1D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,eAAe,CAAC;CACnC;AAgBD,qBAAa,GAAI,SAAQ,GAAG,CAAC,GAAG;IAC9B,OAAO,CAAC,IAAI,CAAa;IACzB,KAAK,SAAkB;IAEvB,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;gBAEW,IAAI,GAAE,OAAO,CAAC,UAAU,CAAM;IAqB1C,UAAU,CACR,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,iBAAiB,EAC/B,WAAW,CAAC,EAAE,WAAW,GACxB,GAAG,CAAC,aAAa;IAIpB,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,iBAAiB,CAAA;KAAE,GAAG,GAAG,CAAC,gBAAgB;CAG5E;AAED,qBAAa,aAAc,SAAQ,GAAG,CAAC,aAAa;;IAClD,KAAK,SAA4B;IAEjC,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAS;gBAGnB,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,WAAW,CAAC,EAAE,WAAW;cAOX,GAAG;CA8FpB;AAED,qBAAa,gBAAiB,SAAQ,GAAG,CAAC,gBAAgB;;IACxD,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,SAAS,CAA0B;IAE3C,KAAK,SAA+B;IAEpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAqC;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAqC;gBAE1D,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,iBAAiB;YAMzD,cAAc;cA4CZ,GAAG;CAqLpB"}
|
package/dist/tts.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
APIConnectionError,
|
|
3
|
+
APIError,
|
|
4
|
+
APIStatusError,
|
|
5
|
+
APITimeoutError,
|
|
2
6
|
AudioByteStream,
|
|
3
7
|
log,
|
|
4
8
|
shortuuid,
|
|
@@ -53,8 +57,8 @@ class TTS extends tts.TTS {
|
|
|
53
57
|
synthesize(text, connOptions, abortSignal) {
|
|
54
58
|
return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);
|
|
55
59
|
}
|
|
56
|
-
stream() {
|
|
57
|
-
return new SynthesizeStream(this, this.opts);
|
|
60
|
+
stream(options) {
|
|
61
|
+
return new SynthesizeStream(this, this.opts, options == null ? void 0 : options.connOptions);
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
class ChunkedStream extends tts.ChunkedStream {
|
|
@@ -158,11 +162,12 @@ class ChunkedStream extends tts.ChunkedStream {
|
|
|
158
162
|
class SynthesizeStream extends tts.SynthesizeStream {
|
|
159
163
|
opts;
|
|
160
164
|
tokenizer;
|
|
165
|
+
#logger = log();
|
|
161
166
|
label = "deepgram.SynthesizeStream";
|
|
162
167
|
static FLUSH_MSG = JSON.stringify({ type: "Flush" });
|
|
163
168
|
static CLOSE_MSG = JSON.stringify({ type: "Close" });
|
|
164
|
-
constructor(tts2, opts) {
|
|
165
|
-
super(tts2);
|
|
169
|
+
constructor(tts2, opts, connOptions) {
|
|
170
|
+
super(tts2, connOptions);
|
|
166
171
|
this.opts = opts;
|
|
167
172
|
this.tokenizer = opts.sentenceTokenizer.stream();
|
|
168
173
|
}
|
|
@@ -232,21 +237,32 @@ class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
232
237
|
this.tokenizer.endInput();
|
|
233
238
|
this.tokenizer.close();
|
|
234
239
|
};
|
|
240
|
+
let markInputSent = () => {
|
|
241
|
+
};
|
|
242
|
+
const inputSent = new Promise((resolve) => {
|
|
243
|
+
markInputSent = resolve;
|
|
244
|
+
});
|
|
235
245
|
const sendTask = async () => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
text
|
|
246
|
+
try {
|
|
247
|
+
for await (const event of this.tokenizer) {
|
|
248
|
+
if (this.abortController.signal.aborted) break;
|
|
249
|
+
let text = event.token;
|
|
250
|
+
if (!text.endsWith(" ")) {
|
|
251
|
+
text += " ";
|
|
252
|
+
}
|
|
253
|
+
const message = JSON.stringify({
|
|
254
|
+
type: "Speak",
|
|
255
|
+
text
|
|
256
|
+
});
|
|
257
|
+
ws.send(message);
|
|
258
|
+
markInputSent();
|
|
241
259
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (!this.abortController.signal.aborted) {
|
|
249
|
-
ws.send(SynthesizeStream.FLUSH_MSG);
|
|
260
|
+
if (!this.abortController.signal.aborted) {
|
|
261
|
+
ws.send(SynthesizeStream.FLUSH_MSG);
|
|
262
|
+
markInputSent();
|
|
263
|
+
}
|
|
264
|
+
} finally {
|
|
265
|
+
markInputSent();
|
|
250
266
|
}
|
|
251
267
|
};
|
|
252
268
|
const recvTask = async () => {
|
|
@@ -266,14 +282,22 @@ class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
266
282
|
timeout = null;
|
|
267
283
|
}
|
|
268
284
|
};
|
|
285
|
+
const resetMessageTimeout = (reject) => {
|
|
286
|
+
clearMessageTimeout();
|
|
287
|
+
timeout = setTimeout(() => {
|
|
288
|
+
reject(new APITimeoutError({ message: "Deepgram TTS recv idle timeout" }));
|
|
289
|
+
}, this.connOptions.timeoutMs);
|
|
290
|
+
};
|
|
291
|
+
await inputSent;
|
|
292
|
+
if (this.abortController.signal.aborted) return;
|
|
269
293
|
return new Promise((resolve, reject) => {
|
|
294
|
+
resetMessageTimeout(reject);
|
|
270
295
|
ws.on("message", (data, isBinary) => {
|
|
271
296
|
clearMessageTimeout();
|
|
272
297
|
if (!isBinary) {
|
|
273
298
|
const message = JSON.parse(data.toString());
|
|
274
299
|
if (message.type === "Flushed") {
|
|
275
300
|
finalReceived = true;
|
|
276
|
-
clearMessageTimeout();
|
|
277
301
|
for (const frame of bstream.flush()) {
|
|
278
302
|
sendLastFrame(segmentId, false);
|
|
279
303
|
lastFrame = frame;
|
|
@@ -283,7 +307,16 @@ class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
283
307
|
this.queue.put(SynthesizeStream.END_OF_STREAM);
|
|
284
308
|
}
|
|
285
309
|
resolve();
|
|
310
|
+
return;
|
|
311
|
+
} else if (message.type === "Warning") {
|
|
312
|
+
this.#logger.warn(`Deepgram warning: ${message.warn_msg}`);
|
|
313
|
+
} else if (message.type === "Error" || message.type === "error") {
|
|
314
|
+
reject(new APIError("Deepgram TTS returned error", { body: message }));
|
|
315
|
+
return;
|
|
316
|
+
} else if (message.type !== "Metadata") {
|
|
317
|
+
this.#logger.warn({ message }, "Unknown Deepgram message type");
|
|
286
318
|
}
|
|
319
|
+
resetMessageTimeout(reject);
|
|
287
320
|
return;
|
|
288
321
|
}
|
|
289
322
|
const buffer = data instanceof Buffer ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) : data;
|
|
@@ -291,17 +324,21 @@ class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
291
324
|
sendLastFrame(segmentId, false);
|
|
292
325
|
lastFrame = frame;
|
|
293
326
|
}
|
|
327
|
+
resetMessageTimeout(reject);
|
|
294
328
|
});
|
|
295
|
-
ws.on("close", (
|
|
329
|
+
ws.on("close", (code, reason) => {
|
|
330
|
+
clearMessageTimeout();
|
|
296
331
|
if (!finalReceived) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
332
|
+
reject(
|
|
333
|
+
new APIStatusError({
|
|
334
|
+
message: "Deepgram websocket connection closed unexpectedly",
|
|
335
|
+
options: {
|
|
336
|
+
statusCode: code || -1,
|
|
337
|
+
body: { reason: reason.toString() }
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
);
|
|
341
|
+
return;
|
|
305
342
|
}
|
|
306
343
|
resolve();
|
|
307
344
|
});
|
|
@@ -314,7 +351,11 @@ class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
314
351
|
try {
|
|
315
352
|
await Promise.all([inputTask(), sendTask(), recvTask()]);
|
|
316
353
|
} catch (e) {
|
|
317
|
-
|
|
354
|
+
if (this.abortController.signal.aborted) return;
|
|
355
|
+
if (e instanceof APIError) throw e;
|
|
356
|
+
throw new APIConnectionError({
|
|
357
|
+
message: `Deepgram TTS WebSocket failed: ${e.message ?? "unknown error"}`
|
|
358
|
+
});
|
|
318
359
|
} finally {
|
|
319
360
|
await this.closeWebSocket(ws);
|
|
320
361
|
}
|
package/dist/tts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n AudioByteStream,\n log,\n shortuuid,\n tokenize,\n tts,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { request } from 'node:https';\nimport { type RawData, WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\nconst AUTHORIZATION_HEADER = 'Authorization';\nconst NUM_CHANNELS = 1;\nconst MIN_SENTENCE_LENGTH = 8;\n\nexport interface TTSOptions {\n model: TTSModels | string;\n encoding: TTSEncoding;\n sampleRate: number;\n speed?: number;\n apiKey?: string;\n baseUrl?: string;\n sentenceTokenizer: tokenize.SentenceTokenizer;\n capabilities: tts.TTSCapabilities;\n}\n\nconst defaultTTSOptions: TTSOptions = {\n model: 'aura-asteria-en',\n encoding: 'linear16',\n sampleRate: 24000,\n apiKey: process.env.DEEPGRAM_API_KEY,\n baseUrl: 'https://api.deepgram.com',\n capabilities: {\n streaming: true,\n },\n sentenceTokenizer: new tokenize.basic.SentenceTokenizer({\n minSentenceLength: MIN_SENTENCE_LENGTH,\n }),\n};\n\nexport class TTS extends tts.TTS {\n private opts: TTSOptions;\n label = 'deepgram.TTS';\n\n get model(): string {\n return this.opts.model;\n }\n\n get provider(): string {\n return 'Deepgram';\n }\n\n constructor(opts: Partial<TTSOptions> = {}) {\n super(opts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {\n streaming: opts.capabilities?.streaming ?? defaultTTSOptions.capabilities.streaming,\n });\n\n this.opts = {\n ...defaultTTSOptions,\n ...opts,\n };\n\n if (this.opts.apiKey === undefined) {\n throw new Error(\n 'Deepgram API key is required, whether as an argument or as $DEEPGRAM_API_KEY',\n );\n }\n\n if (this.opts.speed !== undefined && (this.opts.speed < 0.7 || this.opts.speed > 1.5)) {\n throw new Error(`Deepgram TTS speed must be between 0.7 and 1.5, got ${this.opts.speed}`);\n }\n }\n\n synthesize(\n text: string,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ): tts.ChunkedStream {\n return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);\n }\n\n stream(): tts.SynthesizeStream {\n return new SynthesizeStream(this, this.opts);\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n label = 'deepgram.ChunkedStream';\n #logger = log();\n private opts: TTSOptions;\n private text: string;\n\n constructor(\n tts: TTS,\n text: string,\n opts: TTSOptions,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ) {\n super(text, tts, connOptions, abortSignal);\n this.text = text;\n this.opts = opts;\n }\n\n protected async run() {\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n const json = { text: this.text };\n const url = new URL(`${this.opts.baseUrl!}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => {\n if (!settled) {\n settled = true;\n fn();\n }\n };\n\n const req = request(\n {\n hostname: url.hostname,\n port: 443,\n path: url.pathname + url.search,\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n 'Content-Type': 'application/json',\n },\n signal: this.abortSignal,\n },\n (res) => {\n if (res.statusCode !== 200) {\n settle(() =>\n reject(\n new Error(\n `Deepgram TTS HTTP request failed: ${res.statusCode} ${res.statusMessage}`,\n ),\n ),\n );\n return;\n }\n\n res.on('data', (chunk) => {\n for (const frame of bstream.write(chunk)) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n });\n\n res.on('error', (err) => {\n if (err.message === 'aborted') return;\n this.#logger.error({ err }, 'Deepgram TTS response error');\n settle(() => reject(err));\n });\n\n res.on('close', () => {\n for (const frame of bstream.flush()) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n if (!this.queue.closed) {\n this.queue.close();\n }\n settle(() => resolve());\n });\n },\n );\n\n req.on('error', (err) => {\n if (err.name === 'AbortError') return;\n this.#logger.error({ err }, 'Deepgram TTS request error');\n settle(() => reject(err));\n });\n\n req.on('close', () => settle(() => resolve()));\n req.write(JSON.stringify(json));\n req.end();\n });\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n private opts: TTSOptions;\n private tokenizer: tokenize.SentenceStream;\n label = 'deepgram.SynthesizeStream';\n\n private static readonly FLUSH_MSG = JSON.stringify({ type: 'Flush' });\n private static readonly CLOSE_MSG = JSON.stringify({ type: 'Close' });\n\n constructor(tts: TTS, opts: TTSOptions) {\n super(tts);\n this.opts = opts;\n this.tokenizer = opts.sentenceTokenizer.stream();\n }\n\n private async closeWebSocket(ws: WebSocket): Promise<void> {\n try {\n // Send Flush and Close messages to ensure Deepgram processes all remaining audio\n // and properly terminates the session, preventing lingering TTS sessions\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n ws.send(SynthesizeStream.CLOSE_MSG);\n\n // Wait for server acknowledgment to prevent race conditions and ensure\n // proper cleanup, avoiding 429 Too Many Requests errors from lingering sessions\n try {\n await new Promise<void>((resolve, _reject) => {\n const timeout = setTimeout(() => {\n resolve();\n }, 1000);\n\n ws.once('message', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('close', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('error', () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n } catch (e) {\n // Ignore timeout or other errors during close sequence\n }\n }\n } catch (e) {\n console.warn(`Error during WebSocket close sequence: ${e}`);\n } finally {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n }\n }\n\n protected async run() {\n const requestId = shortuuid();\n const segmentId = shortuuid();\n\n const wsUrl = this.opts.baseUrl!.replace(/^http/, 'ws');\n const url = new URL(`${wsUrl}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n const ws = new WebSocket(url, {\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n },\n });\n\n await new Promise((resolve, reject) => {\n ws.on('open', resolve);\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(`WebSocket returned ${code}`));\n });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.tokenizer.flush();\n continue;\n }\n this.tokenizer.pushText(data);\n }\n this.tokenizer.endInput();\n this.tokenizer.close();\n };\n\n const sendTask = async () => {\n for await (const event of this.tokenizer) {\n if (this.abortController.signal.aborted) break;\n\n let text = event.token;\n if (!text.endsWith(' ')) {\n text += ' ';\n }\n\n const message = JSON.stringify({\n type: 'Speak',\n text: text,\n });\n\n ws.send(message);\n }\n\n if (!this.abortController.signal.aborted) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n }\n };\n\n const recvTask = async () => {\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n let finalReceived = false;\n let timeout: NodeJS.Timeout | null = null;\n let lastFrame: AudioFrame | undefined;\n\n const sendLastFrame = (segmentId: string, final: boolean) => {\n if (lastFrame && !this.queue.closed) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final });\n lastFrame = undefined;\n }\n };\n\n const clearMessageTimeout = () => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n };\n\n return new Promise<void>((resolve, reject) => {\n ws.on('message', (data: RawData, isBinary: boolean) => {\n clearMessageTimeout();\n\n if (!isBinary) {\n const message = JSON.parse(data.toString());\n if (message.type === 'Flushed') {\n finalReceived = true;\n clearMessageTimeout();\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n resolve();\n }\n\n return;\n }\n\n const buffer =\n data instanceof Buffer\n ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)\n : (data as ArrayBuffer);\n for (const frame of bstream.write(buffer as ArrayBuffer)) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n });\n\n ws.on('close', (_code, _reason) => {\n if (!finalReceived) {\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n }\n resolve();\n });\n\n ws.on('error', (error) => {\n clearMessageTimeout();\n reject(error);\n });\n });\n };\n\n try {\n await Promise.all([inputTask(), sendTask(), recvTask()]);\n } catch (e) {\n throw new Error(`failed in main task: ${e}`);\n } finally {\n await this.closeWebSocket(ws);\n }\n }\n}\n"],"mappings":"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","names":["tts","segmentId"]}
|
|
1
|
+
{"version":3,"sources":["../src/tts.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n APIConnectionError,\n APIError,\n APIStatusError,\n APITimeoutError,\n AudioByteStream,\n log,\n shortuuid,\n tokenize,\n tts,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { request } from 'node:https';\nimport { type RawData, WebSocket } from 'ws';\nimport type { TTSEncoding, TTSModels } from './models.js';\n\nconst AUTHORIZATION_HEADER = 'Authorization';\nconst NUM_CHANNELS = 1;\nconst MIN_SENTENCE_LENGTH = 8;\n\nexport interface TTSOptions {\n model: TTSModels | string;\n encoding: TTSEncoding;\n sampleRate: number;\n speed?: number;\n apiKey?: string;\n baseUrl?: string;\n sentenceTokenizer: tokenize.SentenceTokenizer;\n capabilities: tts.TTSCapabilities;\n}\n\nconst defaultTTSOptions: TTSOptions = {\n model: 'aura-asteria-en',\n encoding: 'linear16',\n sampleRate: 24000,\n apiKey: process.env.DEEPGRAM_API_KEY,\n baseUrl: 'https://api.deepgram.com',\n capabilities: {\n streaming: true,\n },\n sentenceTokenizer: new tokenize.basic.SentenceTokenizer({\n minSentenceLength: MIN_SENTENCE_LENGTH,\n }),\n};\n\nexport class TTS extends tts.TTS {\n private opts: TTSOptions;\n label = 'deepgram.TTS';\n\n get model(): string {\n return this.opts.model;\n }\n\n get provider(): string {\n return 'Deepgram';\n }\n\n constructor(opts: Partial<TTSOptions> = {}) {\n super(opts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {\n streaming: opts.capabilities?.streaming ?? defaultTTSOptions.capabilities.streaming,\n });\n\n this.opts = {\n ...defaultTTSOptions,\n ...opts,\n };\n\n if (this.opts.apiKey === undefined) {\n throw new Error(\n 'Deepgram API key is required, whether as an argument or as $DEEPGRAM_API_KEY',\n );\n }\n\n if (this.opts.speed !== undefined && (this.opts.speed < 0.7 || this.opts.speed > 1.5)) {\n throw new Error(`Deepgram TTS speed must be between 0.7 and 1.5, got ${this.opts.speed}`);\n }\n }\n\n synthesize(\n text: string,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ): tts.ChunkedStream {\n return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): tts.SynthesizeStream {\n return new SynthesizeStream(this, this.opts, options?.connOptions);\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n label = 'deepgram.ChunkedStream';\n #logger = log();\n private opts: TTSOptions;\n private text: string;\n\n constructor(\n tts: TTS,\n text: string,\n opts: TTSOptions,\n connOptions?: APIConnectOptions,\n abortSignal?: AbortSignal,\n ) {\n super(text, tts, connOptions, abortSignal);\n this.text = text;\n this.opts = opts;\n }\n\n protected async run() {\n const requestId = shortuuid();\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n const json = { text: this.text };\n const url = new URL(`${this.opts.baseUrl!}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => {\n if (!settled) {\n settled = true;\n fn();\n }\n };\n\n const req = request(\n {\n hostname: url.hostname,\n port: 443,\n path: url.pathname + url.search,\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n 'Content-Type': 'application/json',\n },\n signal: this.abortSignal,\n },\n (res) => {\n if (res.statusCode !== 200) {\n settle(() =>\n reject(\n new Error(\n `Deepgram TTS HTTP request failed: ${res.statusCode} ${res.statusMessage}`,\n ),\n ),\n );\n return;\n }\n\n res.on('data', (chunk) => {\n for (const frame of bstream.write(chunk)) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n });\n\n res.on('error', (err) => {\n if (err.message === 'aborted') return;\n this.#logger.error({ err }, 'Deepgram TTS response error');\n settle(() => reject(err));\n });\n\n res.on('close', () => {\n for (const frame of bstream.flush()) {\n if (!this.queue.closed) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n }\n if (!this.queue.closed) {\n this.queue.close();\n }\n settle(() => resolve());\n });\n },\n );\n\n req.on('error', (err) => {\n if (err.name === 'AbortError') return;\n this.#logger.error({ err }, 'Deepgram TTS request error');\n settle(() => reject(err));\n });\n\n req.on('close', () => settle(() => resolve()));\n req.write(JSON.stringify(json));\n req.end();\n });\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n private opts: TTSOptions;\n private tokenizer: tokenize.SentenceStream;\n #logger = log();\n label = 'deepgram.SynthesizeStream';\n\n private static readonly FLUSH_MSG = JSON.stringify({ type: 'Flush' });\n private static readonly CLOSE_MSG = JSON.stringify({ type: 'Close' });\n\n constructor(tts: TTS, opts: TTSOptions, connOptions?: APIConnectOptions) {\n super(tts, connOptions);\n this.opts = opts;\n this.tokenizer = opts.sentenceTokenizer.stream();\n }\n\n private async closeWebSocket(ws: WebSocket): Promise<void> {\n try {\n // Send Flush and Close messages to ensure Deepgram processes all remaining audio\n // and properly terminates the session, preventing lingering TTS sessions\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n ws.send(SynthesizeStream.CLOSE_MSG);\n\n // Wait for server acknowledgment to prevent race conditions and ensure\n // proper cleanup, avoiding 429 Too Many Requests errors from lingering sessions\n try {\n await new Promise<void>((resolve, _reject) => {\n const timeout = setTimeout(() => {\n resolve();\n }, 1000);\n\n ws.once('message', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('close', () => {\n clearTimeout(timeout);\n resolve();\n });\n\n ws.once('error', () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n } catch (e) {\n // Ignore timeout or other errors during close sequence\n }\n }\n } catch (e) {\n console.warn(`Error during WebSocket close sequence: ${e}`);\n } finally {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n }\n }\n\n protected async run() {\n const requestId = shortuuid();\n const segmentId = shortuuid();\n\n const wsUrl = this.opts.baseUrl!.replace(/^http/, 'ws');\n const url = new URL(`${wsUrl}/v1/speak`);\n url.searchParams.append('sample_rate', this.opts.sampleRate.toString());\n url.searchParams.append('model', this.opts.model);\n url.searchParams.append('encoding', this.opts.encoding);\n if (this.opts.speed !== undefined) {\n url.searchParams.append('speed', this.opts.speed.toString());\n }\n\n const ws = new WebSocket(url, {\n headers: {\n [AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey!}`,\n },\n });\n\n await new Promise((resolve, reject) => {\n ws.on('open', resolve);\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(`WebSocket returned ${code}`));\n });\n\n const inputTask = async () => {\n for await (const data of this.input) {\n if (data === SynthesizeStream.FLUSH_SENTINEL) {\n this.tokenizer.flush();\n continue;\n }\n this.tokenizer.pushText(data);\n }\n this.tokenizer.endInput();\n this.tokenizer.close();\n };\n\n let markInputSent: () => void = () => {};\n const inputSent = new Promise<void>((resolve) => {\n markInputSent = resolve;\n });\n\n const sendTask = async () => {\n try {\n for await (const event of this.tokenizer) {\n if (this.abortController.signal.aborted) break;\n\n let text = event.token;\n if (!text.endsWith(' ')) {\n text += ' ';\n }\n\n const message = JSON.stringify({\n type: 'Speak',\n text: text,\n });\n\n ws.send(message);\n markInputSent();\n }\n\n if (!this.abortController.signal.aborted) {\n ws.send(SynthesizeStream.FLUSH_MSG);\n markInputSent();\n }\n } finally {\n markInputSent();\n }\n };\n\n const recvTask = async () => {\n const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);\n let finalReceived = false;\n let timeout: NodeJS.Timeout | null = null;\n let lastFrame: AudioFrame | undefined;\n\n const sendLastFrame = (segmentId: string, final: boolean) => {\n if (lastFrame && !this.queue.closed) {\n this.queue.put({ requestId, segmentId, frame: lastFrame, final });\n lastFrame = undefined;\n }\n };\n\n const clearMessageTimeout = () => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n };\n\n const resetMessageTimeout = (reject: (reason?: unknown) => void) => {\n clearMessageTimeout();\n timeout = setTimeout(() => {\n reject(new APITimeoutError({ message: 'Deepgram TTS recv idle timeout' }));\n }, this.connOptions.timeoutMs);\n };\n\n await inputSent;\n if (this.abortController.signal.aborted) return;\n\n return new Promise<void>((resolve, reject) => {\n resetMessageTimeout(reject);\n\n ws.on('message', (data: RawData, isBinary: boolean) => {\n clearMessageTimeout();\n\n if (!isBinary) {\n const message = JSON.parse(data.toString());\n if (message.type === 'Flushed') {\n finalReceived = true;\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n resolve();\n return;\n } else if (message.type === 'Warning') {\n this.#logger.warn(`Deepgram warning: ${message.warn_msg}`);\n } else if (message.type === 'Error' || message.type === 'error') {\n reject(new APIError('Deepgram TTS returned error', { body: message }));\n return;\n } else if (message.type !== 'Metadata') {\n this.#logger.warn({ message }, 'Unknown Deepgram message type');\n }\n\n resetMessageTimeout(reject);\n return;\n }\n\n const buffer =\n data instanceof Buffer\n ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)\n : (data as ArrayBuffer);\n for (const frame of bstream.write(buffer as ArrayBuffer)) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n resetMessageTimeout(reject);\n });\n\n ws.on('close', (code, reason) => {\n clearMessageTimeout();\n if (!finalReceived) {\n reject(\n new APIStatusError({\n message: 'Deepgram websocket connection closed unexpectedly',\n options: {\n statusCode: code || -1,\n body: { reason: reason.toString() },\n },\n }),\n );\n return;\n }\n resolve();\n });\n\n ws.on('error', (error) => {\n clearMessageTimeout();\n reject(error);\n });\n });\n };\n\n try {\n await Promise.all([inputTask(), sendTask(), recvTask()]);\n } catch (e) {\n if (this.abortController.signal.aborted) return;\n if (e instanceof APIError) throw e;\n throw new APIConnectionError({\n message: `Deepgram TTS WebSocket failed: ${(e as Error).message ?? 'unknown error'}`,\n });\n } finally {\n await this.closeWebSocket(ws);\n }\n }\n}\n"],"mappings":"AAGA;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,eAAe;AACxB,SAAuB,iBAAiB;AAGxC,MAAM,uBAAuB;AAC7B,MAAM,eAAe;AACrB,MAAM,sBAAsB;AAa5B,MAAM,oBAAgC;AAAA,EACpC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,QAAQ,QAAQ,IAAI;AAAA,EACpB,SAAS;AAAA,EACT,cAAc;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA,mBAAmB,IAAI,SAAS,MAAM,kBAAkB;AAAA,IACtD,mBAAmB;AAAA,EACrB,CAAC;AACH;AAEO,MAAM,YAAY,IAAI,IAAI;AAAA,EACvB;AAAA,EACR,QAAQ;AAAA,EAER,IAAI,QAAgB;AAClB,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,IAAI,WAAmB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,OAA4B,CAAC,GAAG;AA7D9C;AA8DI,UAAM,KAAK,cAAc,kBAAkB,YAAY,cAAc;AAAA,MACnE,aAAW,UAAK,iBAAL,mBAAmB,cAAa,kBAAkB,aAAa;AAAA,IAC5E,CAAC;AAED,SAAK,OAAO;AAAA,MACV,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAEA,QAAI,KAAK,KAAK,WAAW,QAAW;AAClC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,KAAK,KAAK,UAAU,WAAc,KAAK,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,MAAM;AACrF,YAAM,IAAI,MAAM,uDAAuD,KAAK,KAAK,KAAK,EAAE;AAAA,IAC1F;AAAA,EACF;AAAA,EAEA,WACE,MACA,aACA,aACmB;AACnB,WAAO,IAAI,cAAc,MAAM,MAAM,KAAK,MAAM,aAAa,WAAW;AAAA,EAC1E;AAAA,EAEA,OAAO,SAAqE;AAC1E,WAAO,IAAI,iBAAiB,MAAM,KAAK,MAAM,mCAAS,WAAW;AAAA,EACnE;AACF;AAEO,MAAM,sBAAsB,IAAI,cAAc;AAAA,EACnD,QAAQ;AAAA,EACR,UAAU,IAAI;AAAA,EACN;AAAA,EACA;AAAA,EAER,YACEA,MACA,MACA,MACA,aACA,aACA;AACA,UAAM,MAAMA,MAAK,aAAa,WAAW;AACzC,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,MAAgB,MAAM;AACpB,UAAM,YAAY,UAAU;AAC5B,UAAM,UAAU,IAAI,gBAAgB,KAAK,KAAK,YAAY,YAAY;AACtE,UAAM,OAAO,EAAE,MAAM,KAAK,KAAK;AAC/B,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,KAAK,OAAQ,WAAW;AACpD,QAAI,aAAa,OAAO,eAAe,KAAK,KAAK,WAAW,SAAS,CAAC;AACtE,QAAI,aAAa,OAAO,SAAS,KAAK,KAAK,KAAK;AAChD,QAAI,aAAa,OAAO,YAAY,KAAK,KAAK,QAAQ;AACtD,QAAI,KAAK,KAAK,UAAU,QAAW;AACjC,UAAI,aAAa,OAAO,SAAS,KAAK,KAAK,MAAM,SAAS,CAAC;AAAA,IAC7D;AAEA,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAI,UAAU;AACd,YAAM,SAAS,CAAC,OAAmB;AACjC,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,aAAG;AAAA,QACL;AAAA,MACF;AAEA,YAAM,MAAM;AAAA,QACV;AAAA,UACE,UAAU,IAAI;AAAA,UACd,MAAM;AAAA,UACN,MAAM,IAAI,WAAW,IAAI;AAAA,UACzB,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,CAAC,oBAAoB,GAAG,SAAS,KAAK,KAAK,MAAO;AAAA,YAClD,gBAAgB;AAAA,UAClB;AAAA,UACA,QAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,QAAQ;AACP,cAAI,IAAI,eAAe,KAAK;AAC1B;AAAA,cAAO,MACL;AAAA,gBACE,IAAI;AAAA,kBACF,qCAAqC,IAAI,UAAU,IAAI,IAAI,aAAa;AAAA,gBAC1E;AAAA,cACF;AAAA,YACF;AACA;AAAA,UACF;AAEA,cAAI,GAAG,QAAQ,CAAC,UAAU;AACxB,uBAAW,SAAS,QAAQ,MAAM,KAAK,GAAG;AACxC,kBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,qBAAK,MAAM,IAAI;AAAA,kBACb;AAAA,kBACA;AAAA,kBACA,OAAO;AAAA,kBACP,WAAW;AAAA,gBACb,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF,CAAC;AAED,cAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,gBAAI,IAAI,YAAY,UAAW;AAC/B,iBAAK,QAAQ,MAAM,EAAE,IAAI,GAAG,6BAA6B;AACzD,mBAAO,MAAM,OAAO,GAAG,CAAC;AAAA,UAC1B,CAAC;AAED,cAAI,GAAG,SAAS,MAAM;AACpB,uBAAW,SAAS,QAAQ,MAAM,GAAG;AACnC,kBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,qBAAK,MAAM,IAAI;AAAA,kBACb;AAAA,kBACA;AAAA,kBACA,OAAO;AAAA,kBACP,WAAW;AAAA,gBACb,CAAC;AAAA,cACH;AAAA,YACF;AACA,gBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,mBAAK,MAAM,MAAM;AAAA,YACnB;AACA,mBAAO,MAAM,QAAQ,CAAC;AAAA,UACxB,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,YAAI,IAAI,SAAS,aAAc;AAC/B,aAAK,QAAQ,MAAM,EAAE,IAAI,GAAG,4BAA4B;AACxD,eAAO,MAAM,OAAO,GAAG,CAAC;AAAA,MAC1B,CAAC;AAED,UAAI,GAAG,SAAS,MAAM,OAAO,MAAM,QAAQ,CAAC,CAAC;AAC7C,UAAI,MAAM,KAAK,UAAU,IAAI,CAAC;AAC9B,UAAI,IAAI;AAAA,IACV,CAAC;AAAA,EACH;AACF;AAEO,MAAM,yBAAyB,IAAI,iBAAiB;AAAA,EACjD;AAAA,EACA;AAAA,EACR,UAAU,IAAI;AAAA,EACd,QAAQ;AAAA,EAER,OAAwB,YAAY,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAAA,EACpE,OAAwB,YAAY,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAAA,EAEpE,YAAYA,MAAU,MAAkB,aAAiC;AACvE,UAAMA,MAAK,WAAW;AACtB,SAAK,OAAO;AACZ,SAAK,YAAY,KAAK,kBAAkB,OAAO;AAAA,EACjD;AAAA,EAEA,MAAc,eAAe,IAA8B;AACzD,QAAI;AAGF,UAAI,GAAG,eAAe,UAAU,MAAM;AACpC,WAAG,KAAK,iBAAiB,SAAS;AAClC,WAAG,KAAK,iBAAiB,SAAS;AAIlC,YAAI;AACF,gBAAM,IAAI,QAAc,CAAC,SAAS,YAAY;AAC5C,kBAAM,UAAU,WAAW,MAAM;AAC/B,sBAAQ;AAAA,YACV,GAAG,GAAI;AAEP,eAAG,KAAK,WAAW,MAAM;AACvB,2BAAa,OAAO;AACpB,sBAAQ;AAAA,YACV,CAAC;AAED,eAAG,KAAK,SAAS,MAAM;AACrB,2BAAa,OAAO;AACpB,sBAAQ;AAAA,YACV,CAAC;AAED,eAAG,KAAK,SAAS,MAAM;AACrB,2BAAa,OAAO;AACpB,sBAAQ;AAAA,YACV,CAAC;AAAA,UACH,CAAC;AAAA,QACH,SAAS,GAAG;AAAA,QAEZ;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,cAAQ,KAAK,0CAA0C,CAAC,EAAE;AAAA,IAC5D,UAAE;AACA,UAAI,GAAG,eAAe,UAAU,QAAQ,GAAG,eAAe,UAAU,YAAY;AAC9E,WAAG,MAAM;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,MAAM;AACpB,UAAM,YAAY,UAAU;AAC5B,UAAM,YAAY,UAAU;AAE5B,UAAM,QAAQ,KAAK,KAAK,QAAS,QAAQ,SAAS,IAAI;AACtD,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,WAAW;AACvC,QAAI,aAAa,OAAO,eAAe,KAAK,KAAK,WAAW,SAAS,CAAC;AACtE,QAAI,aAAa,OAAO,SAAS,KAAK,KAAK,KAAK;AAChD,QAAI,aAAa,OAAO,YAAY,KAAK,KAAK,QAAQ;AACtD,QAAI,KAAK,KAAK,UAAU,QAAW;AACjC,UAAI,aAAa,OAAO,SAAS,KAAK,KAAK,MAAM,SAAS,CAAC;AAAA,IAC7D;AAEA,UAAM,KAAK,IAAI,UAAU,KAAK;AAAA,MAC5B,SAAS;AAAA,QACP,CAAC,oBAAoB,GAAG,SAAS,KAAK,KAAK,MAAO;AAAA,MACpD;AAAA,IACF,CAAC;AAED,UAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AACrC,SAAG,GAAG,QAAQ,OAAO;AACrB,SAAG,GAAG,SAAS,CAAC,UAAU,OAAO,KAAK,CAAC;AACvC,SAAG,GAAG,SAAS,CAAC,SAAS,OAAO,sBAAsB,IAAI,EAAE,CAAC;AAAA,IAC/D,CAAC;AAED,UAAM,YAAY,YAAY;AAC5B,uBAAiB,QAAQ,KAAK,OAAO;AACnC,YAAI,SAAS,iBAAiB,gBAAgB;AAC5C,eAAK,UAAU,MAAM;AACrB;AAAA,QACF;AACA,aAAK,UAAU,SAAS,IAAI;AAAA,MAC9B;AACA,WAAK,UAAU,SAAS;AACxB,WAAK,UAAU,MAAM;AAAA,IACvB;AAEA,QAAI,gBAA4B,MAAM;AAAA,IAAC;AACvC,UAAM,YAAY,IAAI,QAAc,CAAC,YAAY;AAC/C,sBAAgB;AAAA,IAClB,CAAC;AAED,UAAM,WAAW,YAAY;AAC3B,UAAI;AACF,yBAAiB,SAAS,KAAK,WAAW;AACxC,cAAI,KAAK,gBAAgB,OAAO,QAAS;AAEzC,cAAI,OAAO,MAAM;AACjB,cAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACvB,oBAAQ;AAAA,UACV;AAEA,gBAAM,UAAU,KAAK,UAAU;AAAA,YAC7B,MAAM;AAAA,YACN;AAAA,UACF,CAAC;AAED,aAAG,KAAK,OAAO;AACf,wBAAc;AAAA,QAChB;AAEA,YAAI,CAAC,KAAK,gBAAgB,OAAO,SAAS;AACxC,aAAG,KAAK,iBAAiB,SAAS;AAClC,wBAAc;AAAA,QAChB;AAAA,MACF,UAAE;AACA,sBAAc;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,WAAW,YAAY;AAC3B,YAAM,UAAU,IAAI,gBAAgB,KAAK,KAAK,YAAY,YAAY;AACtE,UAAI,gBAAgB;AACpB,UAAI,UAAiC;AACrC,UAAI;AAEJ,YAAM,gBAAgB,CAACC,YAAmB,UAAmB;AAC3D,YAAI,aAAa,CAAC,KAAK,MAAM,QAAQ;AACnC,eAAK,MAAM,IAAI,EAAE,WAAW,WAAAA,YAAW,OAAO,WAAW,MAAM,CAAC;AAChE,sBAAY;AAAA,QACd;AAAA,MACF;AAEA,YAAM,sBAAsB,MAAM;AAChC,YAAI,SAAS;AACX,uBAAa,OAAO;AACpB,oBAAU;AAAA,QACZ;AAAA,MACF;AAEA,YAAM,sBAAsB,CAAC,WAAuC;AAClE,4BAAoB;AACpB,kBAAU,WAAW,MAAM;AACzB,iBAAO,IAAI,gBAAgB,EAAE,SAAS,iCAAiC,CAAC,CAAC;AAAA,QAC3E,GAAG,KAAK,YAAY,SAAS;AAAA,MAC/B;AAEA,YAAM;AACN,UAAI,KAAK,gBAAgB,OAAO,QAAS;AAEzC,aAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,4BAAoB,MAAM;AAE1B,WAAG,GAAG,WAAW,CAAC,MAAe,aAAsB;AACrD,8BAAoB;AAEpB,cAAI,CAAC,UAAU;AACb,kBAAM,UAAU,KAAK,MAAM,KAAK,SAAS,CAAC;AAC1C,gBAAI,QAAQ,SAAS,WAAW;AAC9B,8BAAgB;AAChB,yBAAW,SAAS,QAAQ,MAAM,GAAG;AACnC,8BAAc,WAAW,KAAK;AAC9B,4BAAY;AAAA,cACd;AACA,4BAAc,WAAW,IAAI;AAE7B,kBAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,qBAAK,MAAM,IAAI,iBAAiB,aAAa;AAAA,cAC/C;AACA,sBAAQ;AACR;AAAA,YACF,WAAW,QAAQ,SAAS,WAAW;AACrC,mBAAK,QAAQ,KAAK,qBAAqB,QAAQ,QAAQ,EAAE;AAAA,YAC3D,WAAW,QAAQ,SAAS,WAAW,QAAQ,SAAS,SAAS;AAC/D,qBAAO,IAAI,SAAS,+BAA+B,EAAE,MAAM,QAAQ,CAAC,CAAC;AACrE;AAAA,YACF,WAAW,QAAQ,SAAS,YAAY;AACtC,mBAAK,QAAQ,KAAK,EAAE,QAAQ,GAAG,+BAA+B;AAAA,YAChE;AAEA,gCAAoB,MAAM;AAC1B;AAAA,UACF;AAEA,gBAAM,SACJ,gBAAgB,SACZ,KAAK,OAAO,MAAM,KAAK,YAAY,KAAK,aAAa,KAAK,UAAU,IACnE;AACP,qBAAW,SAAS,QAAQ,MAAM,MAAqB,GAAG;AACxD,0BAAc,WAAW,KAAK;AAC9B,wBAAY;AAAA,UACd;AACA,8BAAoB,MAAM;AAAA,QAC5B,CAAC;AAED,WAAG,GAAG,SAAS,CAAC,MAAM,WAAW;AAC/B,8BAAoB;AACpB,cAAI,CAAC,eAAe;AAClB;AAAA,cACE,IAAI,eAAe;AAAA,gBACjB,SAAS;AAAA,gBACT,SAAS;AAAA,kBACP,YAAY,QAAQ;AAAA,kBACpB,MAAM,EAAE,QAAQ,OAAO,SAAS,EAAE;AAAA,gBACpC;AAAA,cACF,CAAC;AAAA,YACH;AACA;AAAA,UACF;AACA,kBAAQ;AAAA,QACV,CAAC;AAED,WAAG,GAAG,SAAS,CAAC,UAAU;AACxB,8BAAoB;AACpB,iBAAO,KAAK;AAAA,QACd,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,QAAI;AACF,YAAM,QAAQ,IAAI,CAAC,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC;AAAA,IACzD,SAAS,GAAG;AACV,UAAI,KAAK,gBAAgB,OAAO,QAAS;AACzC,UAAI,aAAa,SAAU,OAAM;AACjC,YAAM,IAAI,mBAAmB;AAAA,QAC3B,SAAS,kCAAmC,EAAY,WAAW,eAAe;AAAA,MACpF,CAAC;AAAA,IACH,UAAE;AACA,YAAM,KAAK,eAAe,EAAE;AAAA,IAC9B;AAAA,EACF;AACF;","names":["tts","segmentId"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livekit/agents-plugin-deepgram",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "Deepgram plugin for LiveKit Agents for Node.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"require": "dist/index.cjs",
|
|
@@ -30,17 +30,17 @@
|
|
|
30
30
|
"@types/ws": "^8.5.10",
|
|
31
31
|
"tsup": "^8.3.5",
|
|
32
32
|
"typescript": "^5.0.0",
|
|
33
|
-
"@livekit/agents": "1.4.
|
|
34
|
-
"@livekit/agents-plugin-openai": "^1.4.
|
|
35
|
-
"@livekit/agents-plugin-silero": "1.4.
|
|
36
|
-
"@livekit/agents-plugins-test": "1.4.
|
|
33
|
+
"@livekit/agents": "1.4.4",
|
|
34
|
+
"@livekit/agents-plugin-openai": "^1.4.4",
|
|
35
|
+
"@livekit/agents-plugin-silero": "1.4.4",
|
|
36
|
+
"@livekit/agents-plugins-test": "1.4.4"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"ws": "^8.18.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@livekit/rtc-node": "^0.13.27",
|
|
43
|
-
"@livekit/agents": "1.4.
|
|
43
|
+
"@livekit/agents": "1.4.4"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup --onSuccess \"pnpm build:types\"",
|
package/src/tts.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import {
|
|
5
5
|
type APIConnectOptions,
|
|
6
|
+
APIConnectionError,
|
|
7
|
+
APIError,
|
|
8
|
+
APIStatusError,
|
|
9
|
+
APITimeoutError,
|
|
6
10
|
AudioByteStream,
|
|
7
11
|
log,
|
|
8
12
|
shortuuid,
|
|
@@ -84,8 +88,8 @@ export class TTS extends tts.TTS {
|
|
|
84
88
|
return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
stream(): tts.SynthesizeStream {
|
|
88
|
-
return new SynthesizeStream(this, this.opts);
|
|
91
|
+
stream(options?: { connOptions?: APIConnectOptions }): tts.SynthesizeStream {
|
|
92
|
+
return new SynthesizeStream(this, this.opts, options?.connOptions);
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
|
|
@@ -206,13 +210,14 @@ export class ChunkedStream extends tts.ChunkedStream {
|
|
|
206
210
|
export class SynthesizeStream extends tts.SynthesizeStream {
|
|
207
211
|
private opts: TTSOptions;
|
|
208
212
|
private tokenizer: tokenize.SentenceStream;
|
|
213
|
+
#logger = log();
|
|
209
214
|
label = 'deepgram.SynthesizeStream';
|
|
210
215
|
|
|
211
216
|
private static readonly FLUSH_MSG = JSON.stringify({ type: 'Flush' });
|
|
212
217
|
private static readonly CLOSE_MSG = JSON.stringify({ type: 'Close' });
|
|
213
218
|
|
|
214
|
-
constructor(tts: TTS, opts: TTSOptions) {
|
|
215
|
-
super(tts);
|
|
219
|
+
constructor(tts: TTS, opts: TTSOptions, connOptions?: APIConnectOptions) {
|
|
220
|
+
super(tts, connOptions);
|
|
216
221
|
this.opts = opts;
|
|
217
222
|
this.tokenizer = opts.sentenceTokenizer.stream();
|
|
218
223
|
}
|
|
@@ -298,25 +303,36 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
298
303
|
this.tokenizer.close();
|
|
299
304
|
};
|
|
300
305
|
|
|
306
|
+
let markInputSent: () => void = () => {};
|
|
307
|
+
const inputSent = new Promise<void>((resolve) => {
|
|
308
|
+
markInputSent = resolve;
|
|
309
|
+
});
|
|
310
|
+
|
|
301
311
|
const sendTask = async () => {
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
try {
|
|
313
|
+
for await (const event of this.tokenizer) {
|
|
314
|
+
if (this.abortController.signal.aborted) break;
|
|
304
315
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
316
|
+
let text = event.token;
|
|
317
|
+
if (!text.endsWith(' ')) {
|
|
318
|
+
text += ' ';
|
|
319
|
+
}
|
|
309
320
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
321
|
+
const message = JSON.stringify({
|
|
322
|
+
type: 'Speak',
|
|
323
|
+
text: text,
|
|
324
|
+
});
|
|
314
325
|
|
|
315
|
-
|
|
316
|
-
|
|
326
|
+
ws.send(message);
|
|
327
|
+
markInputSent();
|
|
328
|
+
}
|
|
317
329
|
|
|
318
|
-
|
|
319
|
-
|
|
330
|
+
if (!this.abortController.signal.aborted) {
|
|
331
|
+
ws.send(SynthesizeStream.FLUSH_MSG);
|
|
332
|
+
markInputSent();
|
|
333
|
+
}
|
|
334
|
+
} finally {
|
|
335
|
+
markInputSent();
|
|
320
336
|
}
|
|
321
337
|
};
|
|
322
338
|
|
|
@@ -340,7 +356,19 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
340
356
|
}
|
|
341
357
|
};
|
|
342
358
|
|
|
359
|
+
const resetMessageTimeout = (reject: (reason?: unknown) => void) => {
|
|
360
|
+
clearMessageTimeout();
|
|
361
|
+
timeout = setTimeout(() => {
|
|
362
|
+
reject(new APITimeoutError({ message: 'Deepgram TTS recv idle timeout' }));
|
|
363
|
+
}, this.connOptions.timeoutMs);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
await inputSent;
|
|
367
|
+
if (this.abortController.signal.aborted) return;
|
|
368
|
+
|
|
343
369
|
return new Promise<void>((resolve, reject) => {
|
|
370
|
+
resetMessageTimeout(reject);
|
|
371
|
+
|
|
344
372
|
ws.on('message', (data: RawData, isBinary: boolean) => {
|
|
345
373
|
clearMessageTimeout();
|
|
346
374
|
|
|
@@ -348,7 +376,6 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
348
376
|
const message = JSON.parse(data.toString());
|
|
349
377
|
if (message.type === 'Flushed') {
|
|
350
378
|
finalReceived = true;
|
|
351
|
-
clearMessageTimeout();
|
|
352
379
|
for (const frame of bstream.flush()) {
|
|
353
380
|
sendLastFrame(segmentId, false);
|
|
354
381
|
lastFrame = frame;
|
|
@@ -359,8 +386,17 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
359
386
|
this.queue.put(SynthesizeStream.END_OF_STREAM);
|
|
360
387
|
}
|
|
361
388
|
resolve();
|
|
389
|
+
return;
|
|
390
|
+
} else if (message.type === 'Warning') {
|
|
391
|
+
this.#logger.warn(`Deepgram warning: ${message.warn_msg}`);
|
|
392
|
+
} else if (message.type === 'Error' || message.type === 'error') {
|
|
393
|
+
reject(new APIError('Deepgram TTS returned error', { body: message }));
|
|
394
|
+
return;
|
|
395
|
+
} else if (message.type !== 'Metadata') {
|
|
396
|
+
this.#logger.warn({ message }, 'Unknown Deepgram message type');
|
|
362
397
|
}
|
|
363
398
|
|
|
399
|
+
resetMessageTimeout(reject);
|
|
364
400
|
return;
|
|
365
401
|
}
|
|
366
402
|
|
|
@@ -372,19 +408,22 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
372
408
|
sendLastFrame(segmentId, false);
|
|
373
409
|
lastFrame = frame;
|
|
374
410
|
}
|
|
411
|
+
resetMessageTimeout(reject);
|
|
375
412
|
});
|
|
376
413
|
|
|
377
|
-
ws.on('close', (
|
|
414
|
+
ws.on('close', (code, reason) => {
|
|
415
|
+
clearMessageTimeout();
|
|
378
416
|
if (!finalReceived) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
417
|
+
reject(
|
|
418
|
+
new APIStatusError({
|
|
419
|
+
message: 'Deepgram websocket connection closed unexpectedly',
|
|
420
|
+
options: {
|
|
421
|
+
statusCode: code || -1,
|
|
422
|
+
body: { reason: reason.toString() },
|
|
423
|
+
},
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
return;
|
|
388
427
|
}
|
|
389
428
|
resolve();
|
|
390
429
|
});
|
|
@@ -399,7 +438,11 @@ export class SynthesizeStream extends tts.SynthesizeStream {
|
|
|
399
438
|
try {
|
|
400
439
|
await Promise.all([inputTask(), sendTask(), recvTask()]);
|
|
401
440
|
} catch (e) {
|
|
402
|
-
|
|
441
|
+
if (this.abortController.signal.aborted) return;
|
|
442
|
+
if (e instanceof APIError) throw e;
|
|
443
|
+
throw new APIConnectionError({
|
|
444
|
+
message: `Deepgram TTS WebSocket failed: ${(e as Error).message ?? 'unknown error'}`,
|
|
445
|
+
});
|
|
403
446
|
} finally {
|
|
404
447
|
await this.closeWebSocket(ws);
|
|
405
448
|
}
|