@livekit/agents-plugin-cartesia 1.0.39 → 1.0.41
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/tts.cjs +132 -74
- package/dist/tts.cjs.map +1 -1
- package/dist/tts.d.cts +6 -0
- package/dist/tts.d.ts +6 -0
- package/dist/tts.d.ts.map +1 -1
- package/dist/tts.js +140 -74
- package/dist/tts.js.map +1 -1
- package/dist/types.cjs +124 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +133 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +87 -0
- package/dist/types.js.map +1 -0
- package/package.json +6 -5
- package/src/tts.ts +199 -82
- package/src/types.ts +116 -0
package/dist/tts.cjs
CHANGED
|
@@ -27,6 +27,7 @@ var import_agents = require("@livekit/agents");
|
|
|
27
27
|
var import_node_https = require("node:https");
|
|
28
28
|
var import_ws = require("ws");
|
|
29
29
|
var import_models = require("./models.cjs");
|
|
30
|
+
var import_types = require("./types.cjs");
|
|
30
31
|
const AUTHORIZATION_HEADER = "X-API-Key";
|
|
31
32
|
const VERSION_HEADER = "Cartesia-Version";
|
|
32
33
|
const VERSION = "2024-06-10";
|
|
@@ -40,19 +41,22 @@ const defaultTTSOptions = {
|
|
|
40
41
|
apiKey: process.env.CARTESIA_API_KEY,
|
|
41
42
|
language: "en",
|
|
42
43
|
baseUrl: "https://api.cartesia.ai",
|
|
43
|
-
chunkTimeout: 5e3
|
|
44
|
+
chunkTimeout: 5e3,
|
|
45
|
+
wordTimestamps: true
|
|
44
46
|
};
|
|
45
47
|
class TTS extends import_agents.tts.TTS {
|
|
46
48
|
#opts;
|
|
47
49
|
label = "cartesia.TTS";
|
|
48
50
|
constructor(opts = {}) {
|
|
49
|
-
|
|
50
|
-
streaming: true
|
|
51
|
-
});
|
|
52
|
-
this.#opts = {
|
|
51
|
+
const resolvedOpts = {
|
|
53
52
|
...defaultTTSOptions,
|
|
54
53
|
...opts
|
|
55
54
|
};
|
|
55
|
+
super(resolvedOpts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {
|
|
56
|
+
streaming: true,
|
|
57
|
+
alignedTranscript: resolvedOpts.wordTimestamps ?? true
|
|
58
|
+
});
|
|
59
|
+
this.#opts = resolvedOpts;
|
|
56
60
|
if (this.#opts.apiKey === void 0) {
|
|
57
61
|
throw new Error(
|
|
58
62
|
"Cartesia API key is required, whether as an argument or as $CARTESIA_API_KEY"
|
|
@@ -174,26 +178,26 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
174
178
|
async run() {
|
|
175
179
|
const requestId = (0, import_agents.shortuuid)();
|
|
176
180
|
let closing = false;
|
|
181
|
+
let sentenceStreamClosed = false;
|
|
177
182
|
const sentenceStreamTask = async (ws2) => {
|
|
178
|
-
const packet = toCartesiaOptions(this.#opts);
|
|
183
|
+
const packet = toCartesiaOptions(this.#opts, true);
|
|
179
184
|
for await (const event of this.#tokenizer) {
|
|
180
|
-
|
|
181
|
-
JSON.stringify({
|
|
182
|
-
...packet,
|
|
183
|
-
context_id: requestId,
|
|
184
|
-
transcript: event.token + " ",
|
|
185
|
-
continue: true
|
|
186
|
-
})
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
ws2.send(
|
|
190
|
-
JSON.stringify({
|
|
185
|
+
const msg = {
|
|
191
186
|
...packet,
|
|
192
187
|
context_id: requestId,
|
|
193
|
-
transcript: " ",
|
|
194
|
-
continue:
|
|
195
|
-
}
|
|
196
|
-
|
|
188
|
+
transcript: event.token + " ",
|
|
189
|
+
continue: true
|
|
190
|
+
};
|
|
191
|
+
ws2.send(JSON.stringify(msg));
|
|
192
|
+
}
|
|
193
|
+
const endMsg = {
|
|
194
|
+
...packet,
|
|
195
|
+
context_id: requestId,
|
|
196
|
+
transcript: " ",
|
|
197
|
+
continue: false
|
|
198
|
+
};
|
|
199
|
+
ws2.send(JSON.stringify(endMsg));
|
|
200
|
+
sentenceStreamClosed = true;
|
|
197
201
|
};
|
|
198
202
|
const inputTask = async () => {
|
|
199
203
|
for await (const data of this.input) {
|
|
@@ -207,14 +211,21 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
207
211
|
this.#tokenizer.close();
|
|
208
212
|
};
|
|
209
213
|
const recvTask = async (ws2) => {
|
|
210
|
-
let finalReceived = false;
|
|
211
|
-
let shouldExit = false;
|
|
212
214
|
const bstream = new import_agents.AudioByteStream(this.#opts.sampleRate, NUM_CHANNELS);
|
|
215
|
+
const eventChannel = import_agents.stream.createStreamChannel();
|
|
213
216
|
let lastFrame;
|
|
217
|
+
let pendingTimedTranscripts = [];
|
|
214
218
|
const sendLastFrame = (segmentId, final) => {
|
|
215
219
|
if (lastFrame && !this.queue.closed) {
|
|
216
|
-
this.queue.put({
|
|
220
|
+
this.queue.put({
|
|
221
|
+
requestId,
|
|
222
|
+
segmentId,
|
|
223
|
+
frame: lastFrame,
|
|
224
|
+
final,
|
|
225
|
+
timedTranscripts: pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : void 0
|
|
226
|
+
});
|
|
217
227
|
lastFrame = void 0;
|
|
228
|
+
pendingTimedTranscripts = [];
|
|
218
229
|
}
|
|
219
230
|
};
|
|
220
231
|
let timeout = null;
|
|
@@ -224,41 +235,81 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
224
235
|
timeout = null;
|
|
225
236
|
}
|
|
226
237
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
238
|
+
const onMessage = (data) => {
|
|
239
|
+
void eventChannel.write(data).catch((error) => {
|
|
240
|
+
this.#logger.debug({ error }, "Failed writing Cartesia event to channel (likely closed)");
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
const onClose = (code, reason) => {
|
|
244
|
+
if (!closing) {
|
|
245
|
+
this.#logger.debug(`WebSocket closed with code ${code}: ${reason.toString()}`);
|
|
246
|
+
}
|
|
247
|
+
clearTTSChunkTimeout();
|
|
248
|
+
void eventChannel.close();
|
|
249
|
+
};
|
|
250
|
+
const onError = (err) => {
|
|
251
|
+
this.#logger.error({ err }, "Cartesia WebSocket error");
|
|
252
|
+
void eventChannel.close();
|
|
253
|
+
};
|
|
254
|
+
ws2.on("message", onMessage);
|
|
255
|
+
ws2.on("close", onClose);
|
|
256
|
+
ws2.on("error", onError);
|
|
257
|
+
try {
|
|
258
|
+
const reader = eventChannel.stream().getReader();
|
|
259
|
+
while (!this.closed && !this.abortController.signal.aborted) {
|
|
260
|
+
const result = await reader.read();
|
|
261
|
+
if (result.done) break;
|
|
262
|
+
const rawMsg = result.value;
|
|
263
|
+
let serverMsg;
|
|
264
|
+
try {
|
|
265
|
+
const json = JSON.parse(rawMsg.toString());
|
|
266
|
+
serverMsg = import_types.cartesiaMessageSchema.parse(json);
|
|
267
|
+
} catch (parseErr) {
|
|
268
|
+
this.#logger.warn({ parseErr }, "Failed to parse Cartesia message");
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if ((0, import_types.isErrorMessage)(serverMsg)) {
|
|
272
|
+
this.#logger.error({ error: serverMsg.error }, "Cartesia returned error");
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const segmentId = serverMsg.context_id;
|
|
276
|
+
if (this.#opts.wordTimestamps !== false && (0, import_types.hasWordTimestamps)(serverMsg)) {
|
|
277
|
+
const wordTimestamps = serverMsg.word_timestamps;
|
|
278
|
+
for (let i = 0; i < wordTimestamps.words.length; i++) {
|
|
279
|
+
const word = wordTimestamps.words[i];
|
|
280
|
+
const startTime = wordTimestamps.start[i];
|
|
281
|
+
const endTime = wordTimestamps.end[i];
|
|
282
|
+
if (word !== void 0 && startTime !== void 0 && endTime !== void 0) {
|
|
283
|
+
pendingTimedTranscripts.push(
|
|
284
|
+
(0, import_agents.createTimedString)({
|
|
285
|
+
text: word + " ",
|
|
286
|
+
// Add space after word for consistency
|
|
287
|
+
startTime,
|
|
288
|
+
endTime
|
|
289
|
+
})
|
|
257
290
|
);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if ((0, import_types.isChunkMessage)(serverMsg)) {
|
|
295
|
+
const audioBuffer = Buffer.from(serverMsg.data, "base64");
|
|
296
|
+
const audioData = audioBuffer.buffer.slice(
|
|
297
|
+
audioBuffer.byteOffset,
|
|
298
|
+
audioBuffer.byteOffset + audioBuffer.byteLength
|
|
299
|
+
);
|
|
300
|
+
for (const frame of bstream.write(audioData)) {
|
|
301
|
+
sendLastFrame(segmentId, false);
|
|
302
|
+
lastFrame = frame;
|
|
303
|
+
}
|
|
304
|
+
clearTTSChunkTimeout();
|
|
305
|
+
timeout = setTimeout(() => {
|
|
306
|
+
this.#logger.debug(
|
|
307
|
+
`Cartesia WebSocket TTS chunk stream timeout after ${this.#opts.chunkTimeout}ms`
|
|
308
|
+
);
|
|
309
|
+
ws2.close();
|
|
310
|
+
}, this.#opts.chunkTimeout);
|
|
311
|
+
} else if ((0, import_types.isDoneMessage)(serverMsg)) {
|
|
312
|
+
if (sentenceStreamClosed) {
|
|
262
313
|
for (const frame of bstream.flush()) {
|
|
263
314
|
sendLastFrame(segmentId, false);
|
|
264
315
|
lastFrame = frame;
|
|
@@ -269,26 +320,29 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
269
320
|
}
|
|
270
321
|
if (segmentId === requestId) {
|
|
271
322
|
closing = true;
|
|
272
|
-
shouldExit = true;
|
|
273
323
|
clearTTSChunkTimeout();
|
|
274
324
|
ws2.close();
|
|
325
|
+
break;
|
|
275
326
|
}
|
|
276
327
|
}
|
|
277
|
-
});
|
|
278
|
-
} catch (err) {
|
|
279
|
-
if (err instanceof Error && !err.message.includes("WebSocket closed")) {
|
|
280
|
-
if (err.message.includes("Queue is closed")) {
|
|
281
|
-
this.#logger.warn(
|
|
282
|
-
{ err },
|
|
283
|
-
"Queue closed during transcript processing (expected during disconnect)"
|
|
284
|
-
);
|
|
285
|
-
} else {
|
|
286
|
-
this.#logger.error({ err }, "Error in recvTask from Cartesia WebSocket");
|
|
287
|
-
}
|
|
288
328
|
}
|
|
289
|
-
clearTTSChunkTimeout();
|
|
290
|
-
break;
|
|
291
329
|
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
if (err instanceof Error && !err.message.includes("WebSocket closed")) {
|
|
332
|
+
if (err.message.includes("Queue is closed") || err.message.includes("Channel is closed")) {
|
|
333
|
+
this.#logger.warn(
|
|
334
|
+
{ err },
|
|
335
|
+
"Channel closed during transcript processing (expected during disconnect)"
|
|
336
|
+
);
|
|
337
|
+
} else {
|
|
338
|
+
this.#logger.error({ err }, "Error in recvTask from Cartesia WebSocket");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} finally {
|
|
342
|
+
ws2.off("message", onMessage);
|
|
343
|
+
ws2.off("close", onClose);
|
|
344
|
+
ws2.off("error", onError);
|
|
345
|
+
clearTTSChunkTimeout();
|
|
292
346
|
}
|
|
293
347
|
};
|
|
294
348
|
const wsUrl = this.#opts.baseUrl.replace(/^http/, "ws");
|
|
@@ -306,7 +360,7 @@ class SynthesizeStream extends import_agents.tts.SynthesizeStream {
|
|
|
306
360
|
}
|
|
307
361
|
}
|
|
308
362
|
}
|
|
309
|
-
const toCartesiaOptions = (opts) => {
|
|
363
|
+
const toCartesiaOptions = (opts, streaming = false) => {
|
|
310
364
|
const voice = {};
|
|
311
365
|
if (typeof opts.voice === "string") {
|
|
312
366
|
voice.mode = "id";
|
|
@@ -325,7 +379,7 @@ const toCartesiaOptions = (opts) => {
|
|
|
325
379
|
if (Object.keys(voiceControls).length) {
|
|
326
380
|
voice.__experimental_controls = voiceControls;
|
|
327
381
|
}
|
|
328
|
-
|
|
382
|
+
const result = {
|
|
329
383
|
model_id: opts.model,
|
|
330
384
|
voice,
|
|
331
385
|
output_format: {
|
|
@@ -335,6 +389,10 @@ const toCartesiaOptions = (opts) => {
|
|
|
335
389
|
},
|
|
336
390
|
language: opts.language
|
|
337
391
|
};
|
|
392
|
+
if (streaming && opts.wordTimestamps !== false) {
|
|
393
|
+
result.add_timestamps = true;
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
338
396
|
};
|
|
339
397
|
// Annotate the CommonJS export names for ESM import in node:
|
|
340
398
|
0 && (module.exports = {
|
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 Future,\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 {\n TTSDefaultVoiceId,\n type TTSEncoding,\n type TTSModels,\n type TTSVoiceEmotion,\n type TTSVoiceSpeed,\n} from './models.js';\n\nconst AUTHORIZATION_HEADER = 'X-API-Key';\nconst VERSION_HEADER = 'Cartesia-Version';\nconst VERSION = '2024-06-10';\nconst NUM_CHANNELS = 1;\nconst BUFFERED_WORDS_COUNT = 8;\n\nexport interface TTSOptions {\n model: TTSModels | string;\n encoding: TTSEncoding;\n sampleRate: number;\n voice: string | number[];\n speed?: TTSVoiceSpeed | number;\n emotion?: (TTSVoiceEmotion | string)[];\n apiKey?: string;\n language: string;\n baseUrl: string;\n\n /**\n * The timeout for the next chunk to be received from the Cartesia API.\n */\n chunkTimeout: number;\n}\n\nconst defaultTTSOptions: TTSOptions = {\n model: 'sonic-2',\n encoding: 'pcm_s16le',\n sampleRate: 24000,\n voice: TTSDefaultVoiceId,\n apiKey: process.env.CARTESIA_API_KEY,\n language: 'en',\n baseUrl: 'https://api.cartesia.ai',\n chunkTimeout: 5000,\n};\n\nexport class TTS extends tts.TTS {\n #opts: TTSOptions;\n label = 'cartesia.TTS';\n\n constructor(opts: Partial<TTSOptions> = {}) {\n super(opts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {\n streaming: true,\n });\n\n this.#opts = {\n ...defaultTTSOptions,\n ...opts,\n };\n\n if (this.#opts.apiKey === undefined) {\n throw new Error(\n 'Cartesia API key is required, whether as an argument or as $CARTESIA_API_KEY',\n );\n }\n\n if ((this.#opts.speed || this.#opts.emotion) && this.#opts.model !== 'sonic-2-2025-03-07') {\n const logger = log();\n logger.warn(\n { model: this.#opts.model, speed: this.#opts.speed, emotion: this.#opts.emotion },\n \"speed and emotion controls are only supported for model 'sonic-2-2025-03-07', see https://docs.cartesia.ai/developer-tools/changelog for details\",\n );\n }\n }\n\n updateOptions(opts: Partial<TTSOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n if ((this.#opts.speed || this.#opts.emotion) && this.#opts.model !== 'sonic-2-2025-03-07') {\n const logger = log();\n logger.warn(\n { model: this.#opts.model, speed: this.#opts.speed, emotion: this.#opts.emotion },\n \"speed and emotion controls are only supported for model 'sonic-2-2025-03-07', see https://docs.cartesia.ai/developer-tools/changelog for details\",\n );\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(): SynthesizeStream {\n return new SynthesizeStream(this, this.#opts);\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n label = 'cartesia.ChunkedStream';\n #logger = log();\n #opts: TTSOptions;\n #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 = toCartesiaOptions(this.#opts);\n json.transcript = this.#text;\n\n const baseUrl = new URL(this.#opts.baseUrl);\n const doneFut = new Future<void>();\n\n const req = request(\n {\n hostname: baseUrl.hostname,\n port: parseInt(baseUrl.port) || (baseUrl.protocol === 'https:' ? 443 : 80),\n path: '/tts/bytes',\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: this.#opts.apiKey!,\n [VERSION_HEADER]: VERSION,\n },\n signal: this.abortSignal,\n },\n (res) => {\n res.on('data', (chunk) => {\n for (const frame of bstream.write(chunk)) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n });\n res.on('close', () => {\n for (const frame of bstream.flush()) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n this.queue.close();\n doneFut.resolve();\n });\n res.on('error', (err) => {\n if (err.message === 'aborted') return;\n this.#logger.error({ err }, 'Cartesia TTS response error');\n });\n },\n );\n\n req.on('error', (err) => {\n if (err.name === 'AbortError') return;\n this.#logger.error({ err }, 'Cartesia TTS request error');\n });\n req.on('close', () => doneFut.resolve());\n req.write(JSON.stringify(json));\n req.end();\n\n await doneFut.await;\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n #opts: TTSOptions;\n #logger = log();\n #tokenizer = new tokenize.basic.SentenceTokenizer({\n minSentenceLength: BUFFERED_WORDS_COUNT,\n }).stream();\n label = 'cartesia.SynthesizeStream';\n\n constructor(tts: TTS, opts: TTSOptions) {\n super(tts);\n this.#opts = opts;\n }\n\n updateOptions(opts: Partial<TTSOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n if ((this.#opts.speed || this.#opts.emotion) && this.#opts.model !== 'sonic-2-2025-03-07') {\n this.#logger.warn(\n { model: this.#opts.model, speed: this.#opts.speed, emotion: this.#opts.emotion },\n \"speed and emotion controls are only supported for model 'sonic-2-2025-03-07', see https://docs.cartesia.ai/developer-tools/changelog for details\",\n );\n }\n }\n\n protected async run() {\n const requestId = shortuuid();\n let closing = false;\n\n const sentenceStreamTask = async (ws: WebSocket) => {\n const packet = toCartesiaOptions(this.#opts);\n for await (const event of this.#tokenizer) {\n ws.send(\n JSON.stringify({\n ...packet,\n context_id: requestId,\n transcript: event.token + ' ',\n continue: true,\n }),\n );\n }\n\n ws.send(\n JSON.stringify({\n ...packet,\n context_id: requestId,\n transcript: ' ',\n continue: false,\n }),\n );\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 recvTask = async (ws: WebSocket) => {\n let finalReceived = false;\n let shouldExit = false;\n const bstream = new AudioByteStream(this.#opts.sampleRate, NUM_CHANNELS);\n\n let lastFrame: AudioFrame | undefined;\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 let timeout: NodeJS.Timeout | null = null;\n\n const clearTTSChunkTimeout = () => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n };\n\n while (!this.closed && !this.abortController.signal.aborted && !shouldExit) {\n try {\n await new Promise<RawData | null>((resolve, reject) => {\n ws.removeAllListeners();\n ws.on('message', (data) => resolve(data));\n ws.on('close', (code, reason) => {\n if (!closing) {\n this.#logger.debug(`WebSocket closed with code ${code}: ${reason}`);\n }\n\n clearTTSChunkTimeout();\n if (!finalReceived) {\n reject(new Error('WebSocket closed'));\n } else {\n // If we've received the final message, resolve with empty to exit gracefully\n resolve(null);\n }\n });\n }).then((msg) => {\n if (!msg) return;\n\n const json = JSON.parse(msg.toString());\n const segmentId = json.context_id;\n if ('data' in json) {\n const data = new Int8Array(Buffer.from(json.data, 'base64'));\n for (const frame of bstream.write(data)) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n\n // IMPORTANT: close WS if TTS chunk stream been stuck too long\n // this allows unblock the current \"broken\" TTS node so that any future TTS nodes\n // can continue to process the stream without been blocked by the stuck node\n clearTTSChunkTimeout();\n timeout = setTimeout(() => {\n // cartesia chunk timeout quite often, so we make it a debug log\n this.#logger.debug(\n `Cartesia WebSocket STT chunk stream timeout after ${this.#opts.chunkTimeout}ms`,\n );\n ws.close();\n }, this.#opts.chunkTimeout);\n } else if ('done' in json) {\n finalReceived = true;\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n\n if (segmentId === requestId) {\n closing = true;\n shouldExit = true;\n clearTTSChunkTimeout();\n ws.close();\n }\n }\n });\n } catch (err) {\n // skip log error for normal websocket close\n if (err instanceof Error && !err.message.includes('WebSocket closed')) {\n if (err.message.includes('Queue is closed')) {\n this.#logger.warn(\n { err },\n 'Queue closed during transcript processing (expected during disconnect)',\n );\n } else {\n this.#logger.error({ err }, 'Error in recvTask from Cartesia WebSocket');\n }\n }\n clearTTSChunkTimeout();\n break;\n }\n }\n };\n\n const wsUrl = this.#opts.baseUrl.replace(/^http/, 'ws');\n const url = `${wsUrl}/tts/websocket?api_key=${this.#opts.apiKey}&cartesia_version=${VERSION}`;\n const ws = new WebSocket(url);\n\n try {\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 await Promise.all([inputTask(), sentenceStreamTask(ws), recvTask(ws)]);\n } catch (e) {\n throw new Error(`failed to connect to Cartesia: ${e}`);\n }\n }\n}\n\nconst toCartesiaOptions = (opts: TTSOptions): { [id: string]: unknown } => {\n const voice: { [id: string]: unknown } = {};\n if (typeof opts.voice === 'string') {\n voice.mode = 'id';\n voice.id = opts.voice;\n } else {\n voice.mode = 'embedding';\n voice.embedding = opts.voice;\n }\n\n const voiceControls: { [id: string]: unknown } = {};\n if (opts.speed) {\n voiceControls.speed = opts.speed;\n }\n if (opts.emotion) {\n voiceControls.emotion = opts.emotion;\n }\n\n if (Object.keys(voiceControls).length) {\n voice.__experimental_controls = voiceControls;\n }\n\n return {\n model_id: opts.model,\n voice,\n output_format: {\n container: 'raw',\n encoding: opts.encoding,\n sample_rate: opts.sampleRate,\n },\n language: opts.language,\n };\n};\n"],"mappings":"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","names":["tts","ws"]}
|
|
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 Future,\n type TimedString,\n createTimedString,\n log,\n shortuuid,\n stream,\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 {\n TTSDefaultVoiceId,\n type TTSEncoding,\n type TTSModels,\n type TTSVoiceEmotion,\n type TTSVoiceSpeed,\n} from './models.js';\nimport {\n type CartesiaServerMessage,\n cartesiaMessageSchema,\n hasWordTimestamps,\n isChunkMessage,\n isDoneMessage,\n isErrorMessage,\n} from './types.js';\n\nconst AUTHORIZATION_HEADER = 'X-API-Key';\nconst VERSION_HEADER = 'Cartesia-Version';\nconst VERSION = '2024-06-10';\nconst NUM_CHANNELS = 1;\nconst BUFFERED_WORDS_COUNT = 8;\n\nexport interface TTSOptions {\n model: TTSModels | string;\n encoding: TTSEncoding;\n sampleRate: number;\n voice: string | number[];\n speed?: TTSVoiceSpeed | number;\n emotion?: (TTSVoiceEmotion | string)[];\n apiKey?: string;\n language: string;\n baseUrl: string;\n\n /**\n * The timeout for the next chunk to be received from the Cartesia API.\n */\n chunkTimeout: number;\n\n /**\n * Whether to add word timestamps to the output. When enabled, the TTS will return\n * timing information for each word in the transcript.\n * @defaultValue true\n */\n wordTimestamps?: boolean;\n}\n\nconst defaultTTSOptions: TTSOptions = {\n model: 'sonic-2',\n encoding: 'pcm_s16le',\n sampleRate: 24000,\n voice: TTSDefaultVoiceId,\n apiKey: process.env.CARTESIA_API_KEY,\n language: 'en',\n baseUrl: 'https://api.cartesia.ai',\n chunkTimeout: 5000,\n wordTimestamps: true,\n};\n\nexport class TTS extends tts.TTS {\n #opts: TTSOptions;\n label = 'cartesia.TTS';\n\n constructor(opts: Partial<TTSOptions> = {}) {\n const resolvedOpts = {\n ...defaultTTSOptions,\n ...opts,\n };\n\n super(resolvedOpts.sampleRate || defaultTTSOptions.sampleRate, NUM_CHANNELS, {\n streaming: true,\n alignedTranscript: resolvedOpts.wordTimestamps ?? true,\n });\n\n this.#opts = resolvedOpts;\n\n if (this.#opts.apiKey === undefined) {\n throw new Error(\n 'Cartesia API key is required, whether as an argument or as $CARTESIA_API_KEY',\n );\n }\n\n if ((this.#opts.speed || this.#opts.emotion) && this.#opts.model !== 'sonic-2-2025-03-07') {\n const logger = log();\n logger.warn(\n { model: this.#opts.model, speed: this.#opts.speed, emotion: this.#opts.emotion },\n \"speed and emotion controls are only supported for model 'sonic-2-2025-03-07', see https://docs.cartesia.ai/developer-tools/changelog for details\",\n );\n }\n }\n\n updateOptions(opts: Partial<TTSOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n if ((this.#opts.speed || this.#opts.emotion) && this.#opts.model !== 'sonic-2-2025-03-07') {\n const logger = log();\n logger.warn(\n { model: this.#opts.model, speed: this.#opts.speed, emotion: this.#opts.emotion },\n \"speed and emotion controls are only supported for model 'sonic-2-2025-03-07', see https://docs.cartesia.ai/developer-tools/changelog for details\",\n );\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(): SynthesizeStream {\n return new SynthesizeStream(this, this.#opts);\n }\n}\n\nexport class ChunkedStream extends tts.ChunkedStream {\n label = 'cartesia.ChunkedStream';\n #logger = log();\n #opts: TTSOptions;\n #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 = toCartesiaOptions(this.#opts);\n json.transcript = this.#text;\n\n const baseUrl = new URL(this.#opts.baseUrl);\n const doneFut = new Future<void>();\n\n const req = request(\n {\n hostname: baseUrl.hostname,\n port: parseInt(baseUrl.port) || (baseUrl.protocol === 'https:' ? 443 : 80),\n path: '/tts/bytes',\n method: 'POST',\n headers: {\n [AUTHORIZATION_HEADER]: this.#opts.apiKey!,\n [VERSION_HEADER]: VERSION,\n },\n signal: this.abortSignal,\n },\n (res) => {\n res.on('data', (chunk) => {\n for (const frame of bstream.write(chunk)) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n });\n res.on('close', () => {\n for (const frame of bstream.flush()) {\n this.queue.put({\n requestId,\n frame,\n final: false,\n segmentId: requestId,\n });\n }\n this.queue.close();\n doneFut.resolve();\n });\n res.on('error', (err) => {\n if (err.message === 'aborted') return;\n this.#logger.error({ err }, 'Cartesia TTS response error');\n });\n },\n );\n\n req.on('error', (err) => {\n if (err.name === 'AbortError') return;\n this.#logger.error({ err }, 'Cartesia TTS request error');\n });\n req.on('close', () => doneFut.resolve());\n req.write(JSON.stringify(json));\n req.end();\n\n await doneFut.await;\n }\n}\n\nexport class SynthesizeStream extends tts.SynthesizeStream {\n #opts: TTSOptions;\n #logger = log();\n #tokenizer = new tokenize.basic.SentenceTokenizer({\n minSentenceLength: BUFFERED_WORDS_COUNT,\n }).stream();\n label = 'cartesia.SynthesizeStream';\n\n constructor(tts: TTS, opts: TTSOptions) {\n super(tts);\n this.#opts = opts;\n }\n\n updateOptions(opts: Partial<TTSOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n if ((this.#opts.speed || this.#opts.emotion) && this.#opts.model !== 'sonic-2-2025-03-07') {\n this.#logger.warn(\n { model: this.#opts.model, speed: this.#opts.speed, emotion: this.#opts.emotion },\n \"speed and emotion controls are only supported for model 'sonic-2-2025-03-07', see https://docs.cartesia.ai/developer-tools/changelog for details\",\n );\n }\n }\n\n protected async run() {\n const requestId = shortuuid();\n let closing = false;\n // Only close WebSocket when both: 1) Cartesia returns done, AND 2) all sentences have been sent\n let sentenceStreamClosed = false;\n\n const sentenceStreamTask = async (ws: WebSocket) => {\n const packet = toCartesiaOptions(this.#opts, true);\n for await (const event of this.#tokenizer) {\n const msg = {\n ...packet,\n context_id: requestId,\n transcript: event.token + ' ',\n continue: true,\n };\n ws.send(JSON.stringify(msg));\n }\n\n const endMsg = {\n ...packet,\n context_id: requestId,\n transcript: ' ',\n continue: false,\n };\n ws.send(JSON.stringify(endMsg));\n // Mark sentence stream as closed\n sentenceStreamClosed = true;\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 // Use event channel and set up listeners ONCE to avoid missing messages during listener re-registration\n const recvTask = async (ws: WebSocket) => {\n const bstream = new AudioByteStream(this.#opts.sampleRate, NUM_CHANNELS);\n\n // Create event channel to buffer incoming messages\n // This prevents message loss between listener re-registrations\n const eventChannel = stream.createStreamChannel<RawData>();\n\n let lastFrame: AudioFrame | undefined;\n let pendingTimedTranscripts: TimedString[] = [];\n\n const sendLastFrame = (segmentId: string, final: boolean) => {\n if (lastFrame && !this.queue.closed) {\n // Include timedTranscripts with the audio frame\n this.queue.put({\n requestId,\n segmentId,\n frame: lastFrame,\n final,\n timedTranscripts:\n pendingTimedTranscripts.length > 0 ? pendingTimedTranscripts : undefined,\n });\n lastFrame = undefined;\n pendingTimedTranscripts = [];\n }\n };\n\n let timeout: NodeJS.Timeout | null = null;\n\n const clearTTSChunkTimeout = () => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n };\n\n // Set up WebSocket listeners ONCE (not in a loop)\n const onMessage = (data: RawData) => {\n void eventChannel.write(data).catch((error: unknown) => {\n this.#logger.debug({ error }, 'Failed writing Cartesia event to channel (likely closed)');\n });\n };\n\n const onClose = (code: number, reason: Buffer) => {\n if (!closing) {\n this.#logger.debug(`WebSocket closed with code ${code}: ${reason.toString()}`);\n }\n clearTTSChunkTimeout();\n void eventChannel.close();\n };\n\n const onError = (err: Error) => {\n this.#logger.error({ err }, 'Cartesia WebSocket error');\n void eventChannel.close();\n };\n\n // Attach listeners ONCE\n ws.on('message', onMessage);\n ws.on('close', onClose);\n ws.on('error', onError);\n\n try {\n // Process messages from the channel\n const reader = eventChannel.stream().getReader();\n\n while (!this.closed && !this.abortController.signal.aborted) {\n const result = await reader.read();\n if (result.done) break;\n\n const rawMsg = result.value;\n\n // Parse message with Zod schema for type safety\n let serverMsg: CartesiaServerMessage;\n try {\n const json = JSON.parse(rawMsg.toString());\n serverMsg = cartesiaMessageSchema.parse(json);\n } catch (parseErr) {\n this.#logger.warn({ parseErr }, 'Failed to parse Cartesia message');\n continue;\n }\n\n // Handle error messages\n if (isErrorMessage(serverMsg)) {\n this.#logger.error({ error: serverMsg.error }, 'Cartesia returned error');\n continue;\n }\n\n const segmentId = serverMsg.context_id;\n\n // Process word timestamps if present (typed via Zod schema)\n if (this.#opts.wordTimestamps !== false && hasWordTimestamps(serverMsg)) {\n const wordTimestamps = serverMsg.word_timestamps;\n for (let i = 0; i < wordTimestamps.words.length; i++) {\n const word = wordTimestamps.words[i];\n const startTime = wordTimestamps.start[i];\n const endTime = wordTimestamps.end[i];\n if (word !== undefined && startTime !== undefined && endTime !== undefined) {\n pendingTimedTranscripts.push(\n createTimedString({\n text: word + ' ', // Add space after word for consistency\n startTime,\n endTime,\n }),\n );\n }\n }\n }\n\n // Handle audio chunk messages\n if (isChunkMessage(serverMsg)) {\n const audioBuffer = Buffer.from(serverMsg.data, 'base64');\n // Extract ArrayBuffer from Buffer for AudioByteStream compatibility\n const audioData = audioBuffer.buffer.slice(\n audioBuffer.byteOffset,\n audioBuffer.byteOffset + audioBuffer.byteLength,\n );\n for (const frame of bstream.write(audioData)) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n\n // IMPORTANT: close WS if TTS chunk stream been stuck too long\n // this allows unblock the current \"broken\" TTS node so that any future TTS nodes\n // can continue to process the stream without been blocked by the stuck node\n clearTTSChunkTimeout();\n timeout = setTimeout(() => {\n // cartesia chunk timeout quite often, so we make it a debug log\n this.#logger.debug(\n `Cartesia WebSocket TTS chunk stream timeout after ${this.#opts.chunkTimeout}ms`,\n );\n ws.close();\n }, this.#opts.chunkTimeout);\n } else if (isDoneMessage(serverMsg)) {\n // This ensures all sentences have been sent before closing\n if (sentenceStreamClosed) {\n for (const frame of bstream.flush()) {\n sendLastFrame(segmentId, false);\n lastFrame = frame;\n }\n sendLastFrame(segmentId, true);\n if (!this.queue.closed) {\n this.queue.put(SynthesizeStream.END_OF_STREAM);\n }\n\n if (segmentId === requestId) {\n closing = true;\n clearTTSChunkTimeout();\n ws.close();\n break; // Exit the loop\n }\n }\n // If sentenceStreamClosed is false, continue receiving - more done messages will come\n }\n }\n } catch (err) {\n // skip log error for normal websocket close\n if (err instanceof Error && !err.message.includes('WebSocket closed')) {\n if (\n err.message.includes('Queue is closed') ||\n err.message.includes('Channel is closed')\n ) {\n this.#logger.warn(\n { err },\n 'Channel closed during transcript processing (expected during disconnect)',\n );\n } else {\n this.#logger.error({ err }, 'Error in recvTask from Cartesia WebSocket');\n }\n }\n } finally {\n // IMPORTANT: Remove listeners so connection can be reused\n ws.off('message', onMessage);\n ws.off('close', onClose);\n ws.off('error', onError);\n clearTTSChunkTimeout();\n }\n };\n\n const wsUrl = this.#opts.baseUrl.replace(/^http/, 'ws');\n const url = `${wsUrl}/tts/websocket?api_key=${this.#opts.apiKey}&cartesia_version=${VERSION}`;\n const ws = new WebSocket(url);\n\n try {\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 await Promise.all([inputTask(), sentenceStreamTask(ws), recvTask(ws)]);\n } catch (e) {\n throw new Error(`failed to connect to Cartesia: ${e}`);\n }\n }\n}\n\n/**\n * Convert TTSOptions to Cartesia API format.\n *\n * @param opts - TTS options\n * @param streaming - Whether this is for streaming (WebSocket) or non-streaming (HTTP)\n */\nconst toCartesiaOptions = (\n opts: TTSOptions,\n streaming: boolean = false,\n): { [id: string]: unknown } => {\n const voice: { [id: string]: unknown } = {};\n if (typeof opts.voice === 'string') {\n voice.mode = 'id';\n voice.id = opts.voice;\n } else {\n voice.mode = 'embedding';\n voice.embedding = opts.voice;\n }\n\n const voiceControls: { [id: string]: unknown } = {};\n if (opts.speed) {\n voiceControls.speed = opts.speed;\n }\n if (opts.emotion) {\n voiceControls.emotion = opts.emotion;\n }\n\n if (Object.keys(voiceControls).length) {\n voice.__experimental_controls = voiceControls;\n }\n\n const result: { [id: string]: unknown } = {\n model_id: opts.model,\n voice,\n output_format: {\n container: 'raw',\n encoding: opts.encoding,\n sample_rate: opts.sampleRate,\n },\n language: opts.language,\n };\n\n if (streaming && opts.wordTimestamps !== false) {\n result.add_timestamps = true;\n }\n\n return result;\n};\n"],"mappings":"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","names":["tts","ws"]}
|
package/dist/tts.d.cts
CHANGED
|
@@ -14,6 +14,12 @@ export interface TTSOptions {
|
|
|
14
14
|
* The timeout for the next chunk to be received from the Cartesia API.
|
|
15
15
|
*/
|
|
16
16
|
chunkTimeout: number;
|
|
17
|
+
/**
|
|
18
|
+
* Whether to add word timestamps to the output. When enabled, the TTS will return
|
|
19
|
+
* timing information for each word in the transcript.
|
|
20
|
+
* @defaultValue true
|
|
21
|
+
*/
|
|
22
|
+
wordTimestamps?: boolean;
|
|
17
23
|
}
|
|
18
24
|
export declare class TTS extends tts.TTS {
|
|
19
25
|
#private;
|
package/dist/tts.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface TTSOptions {
|
|
|
14
14
|
* The timeout for the next chunk to be received from the Cartesia API.
|
|
15
15
|
*/
|
|
16
16
|
chunkTimeout: number;
|
|
17
|
+
/**
|
|
18
|
+
* Whether to add word timestamps to the output. When enabled, the TTS will return
|
|
19
|
+
* timing information for each word in the transcript.
|
|
20
|
+
* @defaultValue true
|
|
21
|
+
*/
|
|
22
|
+
wordTimestamps?: boolean;
|
|
17
23
|
}
|
|
18
24
|
export declare class TTS extends tts.TTS {
|
|
19
25
|
#private;
|
package/dist/tts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EAStB,GAAG,EACJ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAgBrB,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAcD,qBAAa,GAAI,SAAQ,GAAG,CAAC,GAAG;;IAE9B,KAAK,SAAkB;gBAEX,IAAI,GAAE,OAAO,CAAC,UAAU,CAAM;IA4B1C,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC;IAYvC,UAAU,CACR,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,iBAAiB,EAC/B,WAAW,CAAC,EAAE,WAAW,GACxB,GAAG,CAAC,aAAa;IAIpB,MAAM,IAAI,gBAAgB;CAG3B;AAED,qBAAa,aAAc,SAAQ,GAAG,CAAC,aAAa;;IAClD,KAAK,SAA4B;gBAM/B,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,WAAW,CAAC,EAAE,WAAW;cAOX,GAAG;CA6DpB;AAED,qBAAa,gBAAiB,SAAQ,GAAG,CAAC,gBAAgB;;IAMxD,KAAK,SAA+B;gBAExB,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU;IAKtC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC;cAWvB,GAAG;CA2OpB"}
|