@livekit/agents-plugin-deepgram 0.0.0-next-20260624041820
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +17 -0
- package/dist/_utils.cjs +56 -0
- package/dist/_utils.cjs.map +1 -0
- package/dist/_utils.d.cts +12 -0
- package/dist/_utils.d.ts +12 -0
- package/dist/_utils.d.ts.map +1 -0
- package/dist/_utils.js +32 -0
- package/dist/_utils.js.map +1 -0
- package/dist/index.cjs +38 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/models.cjs +17 -0
- package/dist/models.cjs.map +1 -0
- package/dist/models.d.cts +6 -0
- package/dist/models.d.ts +6 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +1 -0
- package/dist/models.js.map +1 -0
- package/dist/stt.cjs +496 -0
- package/dist/stt.cjs.map +1 -0
- package/dist/stt.d.cts +53 -0
- package/dist/stt.d.ts +53 -0
- package/dist/stt.d.ts.map +1 -0
- package/dist/stt.js +483 -0
- package/dist/stt.js.map +1 -0
- package/dist/stt.test.cjs +28 -0
- package/dist/stt.test.cjs.map +1 -0
- package/dist/stt.test.d.cts +2 -0
- package/dist/stt.test.d.ts +2 -0
- package/dist/stt.test.d.ts.map +1 -0
- package/dist/stt.test.js +27 -0
- package/dist/stt.test.js.map +1 -0
- package/dist/stt_v2.cjs +413 -0
- package/dist/stt_v2.cjs.map +1 -0
- package/dist/stt_v2.d.cts +97 -0
- package/dist/stt_v2.d.ts +97 -0
- package/dist/stt_v2.d.ts.map +1 -0
- package/dist/stt_v2.js +387 -0
- package/dist/stt_v2.js.map +1 -0
- package/dist/stt_v2.test.cjs +17 -0
- package/dist/stt_v2.test.cjs.map +1 -0
- package/dist/stt_v2.test.d.cts +2 -0
- package/dist/stt_v2.test.d.ts +2 -0
- package/dist/stt_v2.test.d.ts.map +1 -0
- package/dist/stt_v2.test.js +16 -0
- package/dist/stt_v2.test.js.map +1 -0
- package/dist/tts.cjs +385 -0
- package/dist/tts.cjs.map +1 -0
- package/dist/tts.d.cts +43 -0
- package/dist/tts.d.ts +43 -0
- package/dist/tts.d.ts.map +1 -0
- package/dist/tts.js +369 -0
- package/dist/tts.js.map +1 -0
- package/dist/tts.test.cjs +17 -0
- package/dist/tts.test.cjs.map +1 -0
- package/dist/tts.test.d.cts +2 -0
- package/dist/tts.test.d.ts +2 -0
- package/dist/tts.test.d.ts.map +1 -0
- package/dist/tts.test.js +16 -0
- package/dist/tts.test.js.map +1 -0
- package/package.json +54 -0
- package/src/_utils.ts +50 -0
- package/src/index.ts +20 -0
- package/src/models.ts +93 -0
- package/src/stt.test.ts +36 -0
- package/src/stt.ts +612 -0
- package/src/stt_v2.test.ts +19 -0
- package/src/stt_v2.ts +560 -0
- package/src/tts.test.ts +19 -0
- package/src/tts.ts +450 -0
package/dist/tts.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APIConnectionError,
|
|
3
|
+
APIError,
|
|
4
|
+
APIStatusError,
|
|
5
|
+
APITimeoutError,
|
|
6
|
+
AudioByteStream,
|
|
7
|
+
log,
|
|
8
|
+
shortuuid,
|
|
9
|
+
tokenize,
|
|
10
|
+
tts
|
|
11
|
+
} from "@livekit/agents";
|
|
12
|
+
import { request } from "node:https";
|
|
13
|
+
import { WebSocket } from "ws";
|
|
14
|
+
const AUTHORIZATION_HEADER = "Authorization";
|
|
15
|
+
const NUM_CHANNELS = 1;
|
|
16
|
+
const MIN_SENTENCE_LENGTH = 8;
|
|
17
|
+
const defaultTTSOptions = {
|
|
18
|
+
model: "aura-asteria-en",
|
|
19
|
+
encoding: "linear16",
|
|
20
|
+
sampleRate: 24e3,
|
|
21
|
+
apiKey: process.env.DEEPGRAM_API_KEY,
|
|
22
|
+
baseUrl: "https://api.deepgram.com",
|
|
23
|
+
capabilities: {
|
|
24
|
+
streaming: true
|
|
25
|
+
},
|
|
26
|
+
sentenceTokenizer: new tokenize.basic.SentenceTokenizer({
|
|
27
|
+
minSentenceLength: MIN_SENTENCE_LENGTH
|
|
28
|
+
})
|
|
29
|
+
};
|
|
30
|
+
class TTS extends tts.TTS {
|
|
31
|
+
opts;
|
|
32
|
+
label = "deepgram.TTS";
|
|
33
|
+
get model() {
|
|
34
|
+
return this.opts.model;
|
|
35
|
+
}
|
|
36
|
+
get provider() {
|
|
37
|
+
return "Deepgram";
|
|
38
|
+
}
|
|
39
|
+
constructor(opts = {}) {
|
|
40
|
+
var _a;
|
|
41
|
+
super(opts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {
|
|
42
|
+
streaming: ((_a = opts.capabilities) == null ? void 0 : _a.streaming) ?? defaultTTSOptions.capabilities.streaming
|
|
43
|
+
});
|
|
44
|
+
this.opts = {
|
|
45
|
+
...defaultTTSOptions,
|
|
46
|
+
...opts
|
|
47
|
+
};
|
|
48
|
+
if (this.opts.apiKey === void 0) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Deepgram API key is required, whether as an argument or as $DEEPGRAM_API_KEY"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (this.opts.speed !== void 0 && (this.opts.speed < 0.7 || this.opts.speed > 1.5)) {
|
|
54
|
+
throw new Error(`Deepgram TTS speed must be between 0.7 and 1.5, got ${this.opts.speed}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
synthesize(text, connOptions, abortSignal) {
|
|
58
|
+
return new ChunkedStream(this, text, this.opts, connOptions, abortSignal);
|
|
59
|
+
}
|
|
60
|
+
stream(options) {
|
|
61
|
+
return new SynthesizeStream(this, this.opts, options == null ? void 0 : options.connOptions);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class ChunkedStream extends tts.ChunkedStream {
|
|
65
|
+
label = "deepgram.ChunkedStream";
|
|
66
|
+
#logger = log();
|
|
67
|
+
opts;
|
|
68
|
+
text;
|
|
69
|
+
constructor(tts2, text, opts, connOptions, abortSignal) {
|
|
70
|
+
super(text, tts2, connOptions, abortSignal);
|
|
71
|
+
this.text = text;
|
|
72
|
+
this.opts = opts;
|
|
73
|
+
}
|
|
74
|
+
async run() {
|
|
75
|
+
const requestId = shortuuid();
|
|
76
|
+
const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);
|
|
77
|
+
const json = { text: this.text };
|
|
78
|
+
const url = new URL(`${this.opts.baseUrl}/v1/speak`);
|
|
79
|
+
url.searchParams.append("sample_rate", this.opts.sampleRate.toString());
|
|
80
|
+
url.searchParams.append("model", this.opts.model);
|
|
81
|
+
url.searchParams.append("encoding", this.opts.encoding);
|
|
82
|
+
if (this.opts.speed !== void 0) {
|
|
83
|
+
url.searchParams.append("speed", this.opts.speed.toString());
|
|
84
|
+
}
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
let settled = false;
|
|
87
|
+
const settle = (fn) => {
|
|
88
|
+
if (!settled) {
|
|
89
|
+
settled = true;
|
|
90
|
+
fn();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const req = request(
|
|
94
|
+
{
|
|
95
|
+
hostname: url.hostname,
|
|
96
|
+
port: 443,
|
|
97
|
+
path: url.pathname + url.search,
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
[AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey}`,
|
|
101
|
+
"Content-Type": "application/json"
|
|
102
|
+
},
|
|
103
|
+
signal: this.abortSignal
|
|
104
|
+
},
|
|
105
|
+
(res) => {
|
|
106
|
+
if (res.statusCode !== 200) {
|
|
107
|
+
settle(
|
|
108
|
+
() => reject(
|
|
109
|
+
new Error(
|
|
110
|
+
`Deepgram TTS HTTP request failed: ${res.statusCode} ${res.statusMessage}`
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
res.on("data", (chunk) => {
|
|
117
|
+
for (const frame of bstream.write(chunk)) {
|
|
118
|
+
if (!this.queue.closed) {
|
|
119
|
+
this.queue.put({
|
|
120
|
+
requestId,
|
|
121
|
+
frame,
|
|
122
|
+
final: false,
|
|
123
|
+
segmentId: requestId
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
res.on("error", (err) => {
|
|
129
|
+
if (err.message === "aborted") return;
|
|
130
|
+
this.#logger.error({ err }, "Deepgram TTS response error");
|
|
131
|
+
settle(() => reject(err));
|
|
132
|
+
});
|
|
133
|
+
res.on("close", () => {
|
|
134
|
+
for (const frame of bstream.flush()) {
|
|
135
|
+
if (!this.queue.closed) {
|
|
136
|
+
this.queue.put({
|
|
137
|
+
requestId,
|
|
138
|
+
frame,
|
|
139
|
+
final: false,
|
|
140
|
+
segmentId: requestId
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!this.queue.closed) {
|
|
145
|
+
this.queue.close();
|
|
146
|
+
}
|
|
147
|
+
settle(() => resolve());
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
req.on("error", (err) => {
|
|
152
|
+
if (err.name === "AbortError") return;
|
|
153
|
+
this.#logger.error({ err }, "Deepgram TTS request error");
|
|
154
|
+
settle(() => reject(err));
|
|
155
|
+
});
|
|
156
|
+
req.on("close", () => settle(() => resolve()));
|
|
157
|
+
req.write(JSON.stringify(json));
|
|
158
|
+
req.end();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
class SynthesizeStream extends tts.SynthesizeStream {
|
|
163
|
+
opts;
|
|
164
|
+
tokenizer;
|
|
165
|
+
#logger = log();
|
|
166
|
+
label = "deepgram.SynthesizeStream";
|
|
167
|
+
static FLUSH_MSG = JSON.stringify({ type: "Flush" });
|
|
168
|
+
static CLOSE_MSG = JSON.stringify({ type: "Close" });
|
|
169
|
+
constructor(tts2, opts, connOptions) {
|
|
170
|
+
super(tts2, connOptions);
|
|
171
|
+
this.opts = opts;
|
|
172
|
+
this.tokenizer = opts.sentenceTokenizer.stream();
|
|
173
|
+
}
|
|
174
|
+
async closeWebSocket(ws) {
|
|
175
|
+
try {
|
|
176
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
177
|
+
ws.send(SynthesizeStream.FLUSH_MSG);
|
|
178
|
+
ws.send(SynthesizeStream.CLOSE_MSG);
|
|
179
|
+
try {
|
|
180
|
+
await new Promise((resolve, _reject) => {
|
|
181
|
+
const timeout = setTimeout(() => {
|
|
182
|
+
resolve();
|
|
183
|
+
}, 1e3);
|
|
184
|
+
ws.once("message", () => {
|
|
185
|
+
clearTimeout(timeout);
|
|
186
|
+
resolve();
|
|
187
|
+
});
|
|
188
|
+
ws.once("close", () => {
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
resolve();
|
|
191
|
+
});
|
|
192
|
+
ws.once("error", () => {
|
|
193
|
+
clearTimeout(timeout);
|
|
194
|
+
resolve();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
} catch (e) {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch (e) {
|
|
201
|
+
console.warn(`Error during WebSocket close sequence: ${e}`);
|
|
202
|
+
} finally {
|
|
203
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
204
|
+
ws.close();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async run() {
|
|
209
|
+
const requestId = shortuuid();
|
|
210
|
+
const segmentId = shortuuid();
|
|
211
|
+
const wsUrl = this.opts.baseUrl.replace(/^http/, "ws");
|
|
212
|
+
const url = new URL(`${wsUrl}/v1/speak`);
|
|
213
|
+
url.searchParams.append("sample_rate", this.opts.sampleRate.toString());
|
|
214
|
+
url.searchParams.append("model", this.opts.model);
|
|
215
|
+
url.searchParams.append("encoding", this.opts.encoding);
|
|
216
|
+
if (this.opts.speed !== void 0) {
|
|
217
|
+
url.searchParams.append("speed", this.opts.speed.toString());
|
|
218
|
+
}
|
|
219
|
+
const ws = new WebSocket(url, {
|
|
220
|
+
headers: {
|
|
221
|
+
[AUTHORIZATION_HEADER]: `Token ${this.opts.apiKey}`
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
await new Promise((resolve, reject) => {
|
|
225
|
+
ws.on("open", resolve);
|
|
226
|
+
ws.on("error", (error) => reject(error));
|
|
227
|
+
ws.on("close", (code) => reject(`WebSocket returned ${code}`));
|
|
228
|
+
});
|
|
229
|
+
const inputTask = async () => {
|
|
230
|
+
for await (const data of this.input) {
|
|
231
|
+
if (data === SynthesizeStream.FLUSH_SENTINEL) {
|
|
232
|
+
this.tokenizer.flush();
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
this.tokenizer.pushText(data);
|
|
236
|
+
}
|
|
237
|
+
this.tokenizer.endInput();
|
|
238
|
+
this.tokenizer.close();
|
|
239
|
+
};
|
|
240
|
+
let markInputSent = () => {
|
|
241
|
+
};
|
|
242
|
+
const inputSent = new Promise((resolve) => {
|
|
243
|
+
markInputSent = resolve;
|
|
244
|
+
});
|
|
245
|
+
const sendTask = async () => {
|
|
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();
|
|
259
|
+
}
|
|
260
|
+
if (!this.abortController.signal.aborted) {
|
|
261
|
+
ws.send(SynthesizeStream.FLUSH_MSG);
|
|
262
|
+
markInputSent();
|
|
263
|
+
}
|
|
264
|
+
} finally {
|
|
265
|
+
markInputSent();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const recvTask = async () => {
|
|
269
|
+
const bstream = new AudioByteStream(this.opts.sampleRate, NUM_CHANNELS);
|
|
270
|
+
let finalReceived = false;
|
|
271
|
+
let timeout = null;
|
|
272
|
+
let lastFrame;
|
|
273
|
+
const sendLastFrame = (segmentId2, final) => {
|
|
274
|
+
if (lastFrame && !this.queue.closed) {
|
|
275
|
+
this.queue.put({ requestId, segmentId: segmentId2, frame: lastFrame, final });
|
|
276
|
+
lastFrame = void 0;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const clearMessageTimeout = () => {
|
|
280
|
+
if (timeout) {
|
|
281
|
+
clearTimeout(timeout);
|
|
282
|
+
timeout = null;
|
|
283
|
+
}
|
|
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;
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
resetMessageTimeout(reject);
|
|
295
|
+
ws.on("message", (data, isBinary) => {
|
|
296
|
+
clearMessageTimeout();
|
|
297
|
+
if (!isBinary) {
|
|
298
|
+
const message = JSON.parse(data.toString());
|
|
299
|
+
if (message.type === "Flushed") {
|
|
300
|
+
finalReceived = true;
|
|
301
|
+
for (const frame of bstream.flush()) {
|
|
302
|
+
sendLastFrame(segmentId, false);
|
|
303
|
+
lastFrame = frame;
|
|
304
|
+
}
|
|
305
|
+
sendLastFrame(segmentId, true);
|
|
306
|
+
if (!this.queue.closed) {
|
|
307
|
+
this.queue.put(SynthesizeStream.END_OF_STREAM);
|
|
308
|
+
}
|
|
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");
|
|
318
|
+
}
|
|
319
|
+
resetMessageTimeout(reject);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const buffer = data instanceof Buffer ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) : data;
|
|
323
|
+
for (const frame of bstream.write(buffer)) {
|
|
324
|
+
sendLastFrame(segmentId, false);
|
|
325
|
+
lastFrame = frame;
|
|
326
|
+
}
|
|
327
|
+
resetMessageTimeout(reject);
|
|
328
|
+
});
|
|
329
|
+
ws.on("close", (code, reason) => {
|
|
330
|
+
clearMessageTimeout();
|
|
331
|
+
if (!finalReceived) {
|
|
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;
|
|
342
|
+
}
|
|
343
|
+
resolve();
|
|
344
|
+
});
|
|
345
|
+
ws.on("error", (error) => {
|
|
346
|
+
clearMessageTimeout();
|
|
347
|
+
reject(error);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
try {
|
|
352
|
+
await Promise.all([inputTask(), sendTask(), recvTask()]);
|
|
353
|
+
} catch (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
|
+
});
|
|
359
|
+
} finally {
|
|
360
|
+
await this.closeWebSocket(ws);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
export {
|
|
365
|
+
ChunkedStream,
|
|
366
|
+
SynthesizeStream,
|
|
367
|
+
TTS
|
|
368
|
+
};
|
|
369
|
+
//# sourceMappingURL=tts.js.map
|
package/dist/tts.js.map
ADDED
|
@@ -0,0 +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 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"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_agents_plugin_openai = require("@livekit/agents-plugin-openai");
|
|
3
|
+
var import_agents_plugins_test = require("@livekit/agents-plugins-test");
|
|
4
|
+
var import_vitest = require("vitest");
|
|
5
|
+
var import_tts = require("./tts.cjs");
|
|
6
|
+
const hasDeepgramTtsConfig = Boolean(process.env.DEEPGRAM_API_KEY && process.env.OPENAI_API_KEY);
|
|
7
|
+
if (hasDeepgramTtsConfig) {
|
|
8
|
+
(0, import_vitest.describe)("Deepgram", async () => {
|
|
9
|
+
await (0, import_agents_plugins_test.tts)(new import_tts.TTS(), new import_agents_plugin_openai.STT());
|
|
10
|
+
});
|
|
11
|
+
} else {
|
|
12
|
+
(0, import_vitest.describe)("Deepgram", () => {
|
|
13
|
+
import_vitest.it.skip("requires DEEPGRAM_API_KEY and OPENAI_API_KEY", () => {
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=tts.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tts.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { STT } from '@livekit/agents-plugin-openai';\nimport { tts } from '@livekit/agents-plugins-test';\nimport { describe, it } from 'vitest';\nimport { TTS } from './tts.js';\n\nconst hasDeepgramTtsConfig = Boolean(process.env.DEEPGRAM_API_KEY && process.env.OPENAI_API_KEY);\n\nif (hasDeepgramTtsConfig) {\n describe('Deepgram', async () => {\n await tts(new TTS(), new STT());\n });\n} else {\n describe('Deepgram', () => {\n it.skip('requires DEEPGRAM_API_KEY and OPENAI_API_KEY', () => {});\n });\n}\n"],"mappings":";AAGA,kCAAoB;AACpB,iCAAoB;AACpB,oBAA6B;AAC7B,iBAAoB;AAEpB,MAAM,uBAAuB,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,cAAc;AAE/F,IAAI,sBAAsB;AACxB,8BAAS,YAAY,YAAY;AAC/B,cAAM,gCAAI,IAAI,eAAI,GAAG,IAAI,gCAAI,CAAC;AAAA,EAChC,CAAC;AACH,OAAO;AACL,8BAAS,YAAY,MAAM;AACzB,qBAAG,KAAK,gDAAgD,MAAM;AAAA,IAAC,CAAC;AAAA,EAClE,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.test.d.ts","sourceRoot":"","sources":["../src/tts.test.ts"],"names":[],"mappings":""}
|
package/dist/tts.test.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { STT } from "@livekit/agents-plugin-openai";
|
|
2
|
+
import { tts } from "@livekit/agents-plugins-test";
|
|
3
|
+
import { describe, it } from "vitest";
|
|
4
|
+
import { TTS } from "./tts.js";
|
|
5
|
+
const hasDeepgramTtsConfig = Boolean(process.env.DEEPGRAM_API_KEY && process.env.OPENAI_API_KEY);
|
|
6
|
+
if (hasDeepgramTtsConfig) {
|
|
7
|
+
describe("Deepgram", async () => {
|
|
8
|
+
await tts(new TTS(), new STT());
|
|
9
|
+
});
|
|
10
|
+
} else {
|
|
11
|
+
describe("Deepgram", () => {
|
|
12
|
+
it.skip("requires DEEPGRAM_API_KEY and OPENAI_API_KEY", () => {
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=tts.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tts.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { STT } from '@livekit/agents-plugin-openai';\nimport { tts } from '@livekit/agents-plugins-test';\nimport { describe, it } from 'vitest';\nimport { TTS } from './tts.js';\n\nconst hasDeepgramTtsConfig = Boolean(process.env.DEEPGRAM_API_KEY && process.env.OPENAI_API_KEY);\n\nif (hasDeepgramTtsConfig) {\n describe('Deepgram', async () => {\n await tts(new TTS(), new STT());\n });\n} else {\n describe('Deepgram', () => {\n it.skip('requires DEEPGRAM_API_KEY and OPENAI_API_KEY', () => {});\n });\n}\n"],"mappings":"AAGA,SAAS,WAAW;AACpB,SAAS,WAAW;AACpB,SAAS,UAAU,UAAU;AAC7B,SAAS,WAAW;AAEpB,MAAM,uBAAuB,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,cAAc;AAE/F,IAAI,sBAAsB;AACxB,WAAS,YAAY,YAAY;AAC/B,UAAM,IAAI,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC;AAAA,EAChC,CAAC;AACH,OAAO;AACL,WAAS,YAAY,MAAM;AACzB,OAAG,KAAK,gDAAgD,MAAM;AAAA,IAAC,CAAC;AAAA,EAClE,CAAC;AACH;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@livekit/agents-plugin-deepgram",
|
|
3
|
+
"version": "0.0.0-next-20260624041820",
|
|
4
|
+
"description": "Deepgram plugin for LiveKit Agents for Node.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"require": "dist/index.cjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.cts",
|
|
15
|
+
"default": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"author": "LiveKit",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"repository": "git@github.com:livekit/agents-js.git",
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@livekit/rtc-node": "^0.13.29",
|
|
29
|
+
"@microsoft/api-extractor": "^7.35.0",
|
|
30
|
+
"@types/ws": "^8.5.10",
|
|
31
|
+
"tsup": "^8.3.5",
|
|
32
|
+
"typescript": "^5.0.0",
|
|
33
|
+
"@livekit/agents": "0.0.0-next-20260624041820",
|
|
34
|
+
"@livekit/agents-plugin-openai": "^0.0.0-next-20260624041820",
|
|
35
|
+
"@livekit/agents-plugin-silero": "0.0.0-next-20260624041820",
|
|
36
|
+
"@livekit/agents-plugins-test": "0.0.0-next-20260624041820"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"ws": "^8.18.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@livekit/rtc-node": "^0.13.29",
|
|
43
|
+
"@livekit/agents": "0.0.0-next-20260624041820"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup --onSuccess \"pnpm build:types\"",
|
|
47
|
+
"build:types": "tsc --declaration --emitDeclarationOnly && node ../../scripts/copyDeclarationOutput.js",
|
|
48
|
+
"clean": "rm -rf dist",
|
|
49
|
+
"clean:build": "pnpm clean && pnpm build",
|
|
50
|
+
"lint": "eslint -f unix \"src/**/*.{ts,js}\"",
|
|
51
|
+
"api:check": "api-extractor run --typescript-compiler-folder ../../node_modules/typescript",
|
|
52
|
+
"api:update": "api-extractor run --local --typescript-compiler-folder ../../node_modules/typescript --verbose"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/_utils.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
export class PeriodicCollector<T> {
|
|
6
|
+
private duration: number;
|
|
7
|
+
private callback: (value: T) => void;
|
|
8
|
+
private lastFlushTime: number;
|
|
9
|
+
private total: T | null = null;
|
|
10
|
+
|
|
11
|
+
constructor(callback: (value: T) => void, options: { duration: number }) {
|
|
12
|
+
/**
|
|
13
|
+
* Create a new periodic collector that accumulates values and calls the callback
|
|
14
|
+
* after the specified duration if there are values to report.
|
|
15
|
+
*
|
|
16
|
+
* @param callback Function to call with accumulated value when duration expires
|
|
17
|
+
* @param options.duration Time in seconds between callback invocations
|
|
18
|
+
*/
|
|
19
|
+
this.duration = options.duration;
|
|
20
|
+
this.callback = callback;
|
|
21
|
+
this.lastFlushTime = performance.now() / 1000; // Convert to seconds
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
push(value: T): void {
|
|
25
|
+
/**
|
|
26
|
+
* Add a value to the accumulator
|
|
27
|
+
*/
|
|
28
|
+
if (this.total === null) {
|
|
29
|
+
this.total = value;
|
|
30
|
+
} else {
|
|
31
|
+
// Type assertion needed for generic addition
|
|
32
|
+
this.total = (this.total as any) + (value as any);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (performance.now() / 1000 - this.lastFlushTime >= this.duration) {
|
|
36
|
+
this.flush();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
flush(): void {
|
|
41
|
+
/**
|
|
42
|
+
* Force callback to be called with current total if non-zero
|
|
43
|
+
*/
|
|
44
|
+
if (this.total !== null) {
|
|
45
|
+
this.callback(this.total);
|
|
46
|
+
this.total = null;
|
|
47
|
+
}
|
|
48
|
+
this.lastFlushTime = performance.now() / 1000;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { Plugin } from '@livekit/agents';
|
|
5
|
+
|
|
6
|
+
export * from './stt.js';
|
|
7
|
+
export * from './stt_v2.js';
|
|
8
|
+
export * from './tts.js';
|
|
9
|
+
|
|
10
|
+
class DeepgramPlugin extends Plugin {
|
|
11
|
+
constructor() {
|
|
12
|
+
super({
|
|
13
|
+
title: 'deepgram',
|
|
14
|
+
version: __PACKAGE_VERSION__,
|
|
15
|
+
package: __PACKAGE_NAME__,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Plugin.registerPlugin(new DeepgramPlugin());
|