@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 CHANGED
@@ -23,7 +23,7 @@ class DeepgramPlugin extends import_agents.Plugin {
23
23
  constructor() {
24
24
  super({
25
25
  title: "deepgram",
26
- version: "1.4.2",
26
+ version: "1.4.4",
27
27
  package: "@livekit/agents-plugin-deepgram"
28
28
  });
29
29
  }
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ class DeepgramPlugin extends Plugin {
6
6
  constructor() {
7
7
  super({
8
8
  title: "deepgram",
9
- version: "1.4.2",
9
+ version: "1.4.4",
10
10
  package: "@livekit/agents-plugin-deepgram"
11
11
  });
12
12
  }
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
- for await (const event of this.tokenizer) {
256
- if (this.abortController.signal.aborted) break;
257
- let text = event.token;
258
- if (!text.endsWith(" ")) {
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
- const message = JSON.stringify({
262
- type: "Speak",
263
- text
264
- });
265
- ws.send(message);
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", (_code, _reason) => {
344
+ ws.on("close", (code, reason) => {
345
+ clearMessageTimeout();
315
346
  if (!finalReceived) {
316
- for (const frame of bstream.flush()) {
317
- sendLastFrame(segmentId, false);
318
- lastFrame = frame;
319
- }
320
- sendLastFrame(segmentId, true);
321
- if (!this.queue.closed) {
322
- this.queue.put(SynthesizeStream.END_OF_STREAM);
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
- throw new Error(`failed in main task: ${e}`);
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(): tts.SynthesizeStream;
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(): tts.SynthesizeStream;
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,EAItB,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,IAAI,GAAG,CAAC,gBAAgB;CAG/B;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;IAC3C,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;YAMxB,cAAc;cA4CZ,GAAG;CA+IpB"}
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
- for await (const event of this.tokenizer) {
237
- if (this.abortController.signal.aborted) break;
238
- let text = event.token;
239
- if (!text.endsWith(" ")) {
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
- const message = JSON.stringify({
243
- type: "Speak",
244
- text
245
- });
246
- ws.send(message);
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", (_code, _reason) => {
329
+ ws.on("close", (code, reason) => {
330
+ clearMessageTimeout();
296
331
  if (!finalReceived) {
297
- for (const frame of bstream.flush()) {
298
- sendLastFrame(segmentId, false);
299
- lastFrame = frame;
300
- }
301
- sendLastFrame(segmentId, true);
302
- if (!this.queue.closed) {
303
- this.queue.put(SynthesizeStream.END_OF_STREAM);
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
- throw new Error(`failed in main task: ${e}`);
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":"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","names":["tts","segmentId"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/agents-plugin-deepgram",
3
- "version": "1.4.2",
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.2",
34
- "@livekit/agents-plugin-openai": "^1.4.2",
35
- "@livekit/agents-plugin-silero": "1.4.2",
36
- "@livekit/agents-plugins-test": "1.4.2"
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.2"
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
- for await (const event of this.tokenizer) {
303
- if (this.abortController.signal.aborted) break;
312
+ try {
313
+ for await (const event of this.tokenizer) {
314
+ if (this.abortController.signal.aborted) break;
304
315
 
305
- let text = event.token;
306
- if (!text.endsWith(' ')) {
307
- text += ' ';
308
- }
316
+ let text = event.token;
317
+ if (!text.endsWith(' ')) {
318
+ text += ' ';
319
+ }
309
320
 
310
- const message = JSON.stringify({
311
- type: 'Speak',
312
- text: text,
313
- });
321
+ const message = JSON.stringify({
322
+ type: 'Speak',
323
+ text: text,
324
+ });
314
325
 
315
- ws.send(message);
316
- }
326
+ ws.send(message);
327
+ markInputSent();
328
+ }
317
329
 
318
- if (!this.abortController.signal.aborted) {
319
- ws.send(SynthesizeStream.FLUSH_MSG);
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', (_code, _reason) => {
414
+ ws.on('close', (code, reason) => {
415
+ clearMessageTimeout();
378
416
  if (!finalReceived) {
379
- for (const frame of bstream.flush()) {
380
- sendLastFrame(segmentId, false);
381
- lastFrame = frame;
382
- }
383
- sendLastFrame(segmentId, true);
384
-
385
- if (!this.queue.closed) {
386
- this.queue.put(SynthesizeStream.END_OF_STREAM);
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
- throw new Error(`failed in main task: ${e}`);
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
  }