UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

131 lines 4.27 kB
import WebSocket from "ws"; import { EventEmitter } from "events"; import { logger } from "../../utils/logger.js"; import { withTimeout } from "../../utils/async/withTimeout.js"; export function getCartesiaWsUrl() { const baseUrl = process.env.CARTESIA_WS_BASE_URL ?? "wss://api.cartesia.ai/tts/websocket"; const cartesiaVersion = process.env.CARTESIA_API_VERSION ?? "2025-04-16"; const wsUrl = new URL(baseUrl); wsUrl.searchParams.set("cartesia_version", cartesiaVersion); return wsUrl.toString(); } export class CartesiaStream extends EventEmitter { ws = null; contextId; isReady = false; constructor(contextId) { super(); this.contextId = contextId; const apiKey = process.env.CARTESIA_API_KEY; if (!apiKey) { throw new Error("CARTESIA_API_KEY is not set in environment"); } this.ws = new WebSocket(getCartesiaWsUrl(), { headers: { "X-API-Key": apiKey }, }); this.ws.on("open", () => { this.isReady = true; logger.info("[CARTESIA] WS connected"); this.emit("ready"); }); this.ws.on("message", (data) => { let msg; try { msg = JSON.parse(data.toString()); } catch { logger.error("[CARTESIA] Failed to parse message"); return; } // Handle error first so it always surfaces, even mid-stream if (msg.error) { const err = new Error(msg.error); if (this.listenerCount("error") > 0) { this.emit("error", err); } else { logger.error("[CARTESIA] Unhandled error:", msg.error); } return; } if (msg.data) { const audio = Buffer.from(msg.data, "base64"); this.emit("audio", audio); } if (msg.done) { this.emit("done"); } }); this.ws.on("error", (err) => { if (this.listenerCount("error") > 0) { this.emit("error", err); } else { logger.error("[CARTESIA] Unhandled WebSocket error:", err.message); } }); this.ws.on("close", () => { this.isReady = false; this.emit("close"); }); } async ready() { if (this.isReady) { return; } const connectPromise = new Promise((resolve, reject) => { if (!this.ws) { reject(new Error("Cartesia WebSocket is not initialized")); return; } this.ws.once("open", resolve); this.ws.once("error", reject); this.ws.once("close", () => reject(new Error("Cartesia WebSocket closed before ready"))); }); return withTimeout(connectPromise, 5000, "Cartesia WS connect timed out"); } send(text, cont = true) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { return; } this.ws.send(JSON.stringify({ context_id: this.contextId, model_id: "sonic-3", transcript: text, voice: { mode: "id", id: "694f9389-aac1-45b6-b726-9d9369183238", }, output_format: { container: "raw", encoding: "pcm_s16le", sample_rate: 24000, }, continue: cont, })); } flush() { this.send("", false); } close() { if (!this.ws) { return; } const ws = this.ws; this.ws = null; this.isReady = false; if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) { try { ws.close(); } catch (error) { logger.warn("[CARTESIA] Failed to close WebSocket cleanly", error); } } ws.once("close", () => { ws.removeAllListeners(); }); } } //# sourceMappingURL=cartesiaHandler.js.map