@juspay/neurolink 9.53.0 → 9.54.0

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/adapters/tts/cartesiaHandler.d.ts +12 -0
  3. package/dist/adapters/tts/cartesiaHandler.js +130 -0
  4. package/dist/browser/neurolink.min.js +1 -1
  5. package/dist/cli/commands/voiceServer.d.ts +6 -0
  6. package/dist/cli/commands/voiceServer.js +17 -0
  7. package/dist/cli/parser.js +4 -1
  8. package/dist/lib/adapters/tts/cartesiaHandler.d.ts +12 -0
  9. package/dist/lib/adapters/tts/cartesiaHandler.js +131 -0
  10. package/dist/lib/providers/azureOpenai.d.ts +4 -1
  11. package/dist/lib/providers/azureOpenai.js +9 -3
  12. package/dist/lib/server/voice/frameBus.d.ts +8 -0
  13. package/dist/lib/server/voice/frameBus.js +25 -0
  14. package/dist/lib/server/voice/turnManager.d.ts +15 -0
  15. package/dist/lib/server/voice/turnManager.js +36 -0
  16. package/dist/lib/server/voice/types.d.ts +20 -0
  17. package/dist/lib/server/voice/types.js +2 -0
  18. package/dist/lib/server/voice/voiceServerApp.d.ts +1 -0
  19. package/dist/lib/server/voice/voiceServerApp.js +118 -0
  20. package/dist/lib/server/voice/voiceWebSocketHandler.d.ts +11 -0
  21. package/dist/lib/server/voice/voiceWebSocketHandler.js +536 -0
  22. package/dist/providers/azureOpenai.d.ts +4 -1
  23. package/dist/providers/azureOpenai.js +9 -3
  24. package/dist/server/voice/frameBus.d.ts +8 -0
  25. package/dist/server/voice/frameBus.js +24 -0
  26. package/dist/server/voice/public/app.js +275 -0
  27. package/dist/server/voice/public/index.html +18 -0
  28. package/dist/server/voice/public/pcm-worklet.js +67 -0
  29. package/dist/server/voice/public/styles.css +102 -0
  30. package/dist/server/voice/turnManager.d.ts +15 -0
  31. package/dist/server/voice/turnManager.js +35 -0
  32. package/dist/server/voice/types.d.ts +20 -0
  33. package/dist/server/voice/types.js +1 -0
  34. package/dist/server/voice/voiceServerApp.d.ts +1 -0
  35. package/dist/server/voice/voiceServerApp.js +117 -0
  36. package/dist/server/voice/voiceWebSocketHandler.d.ts +11 -0
  37. package/dist/server/voice/voiceWebSocketHandler.js +535 -0
  38. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [9.54.0](https://github.com/juspay/neurolink/compare/v9.53.0...v9.54.0) (2026-04-12)
2
+
3
+ ### Features
4
+
5
+ - **(voice):** add real-time voice agent server ([f0d298d](https://github.com/juspay/neurolink/commit/f0d298d6f8f303c3df34c7ff77b175d47f6f3f10))
6
+
1
7
  ## [9.53.0](https://github.com/juspay/neurolink/compare/v9.52.0...v9.53.0) (2026-04-12)
2
8
 
3
9
  ### Features
@@ -0,0 +1,12 @@
1
+ import { EventEmitter } from "events";
2
+ export declare function getCartesiaWsUrl(): string;
3
+ export declare class CartesiaStream extends EventEmitter {
4
+ private ws;
5
+ private contextId;
6
+ private isReady;
7
+ constructor(contextId: string);
8
+ ready(): Promise<void>;
9
+ send(text: string, cont?: boolean): void;
10
+ flush(): void;
11
+ close(): void;
12
+ }
@@ -0,0 +1,130 @@
1
+ import WebSocket from "ws";
2
+ import { EventEmitter } from "events";
3
+ import { logger } from "../../utils/logger.js";
4
+ import { withTimeout } from "../../utils/async/withTimeout.js";
5
+ export function getCartesiaWsUrl() {
6
+ const baseUrl = process.env.CARTESIA_WS_BASE_URL ?? "wss://api.cartesia.ai/tts/websocket";
7
+ const cartesiaVersion = process.env.CARTESIA_API_VERSION ?? "2025-04-16";
8
+ const wsUrl = new URL(baseUrl);
9
+ wsUrl.searchParams.set("cartesia_version", cartesiaVersion);
10
+ return wsUrl.toString();
11
+ }
12
+ export class CartesiaStream extends EventEmitter {
13
+ ws = null;
14
+ contextId;
15
+ isReady = false;
16
+ constructor(contextId) {
17
+ super();
18
+ this.contextId = contextId;
19
+ const apiKey = process.env.CARTESIA_API_KEY;
20
+ if (!apiKey) {
21
+ throw new Error("CARTESIA_API_KEY is not set in environment");
22
+ }
23
+ this.ws = new WebSocket(getCartesiaWsUrl(), {
24
+ headers: { "X-API-Key": apiKey },
25
+ });
26
+ this.ws.on("open", () => {
27
+ this.isReady = true;
28
+ logger.info("[CARTESIA] WS connected");
29
+ this.emit("ready");
30
+ });
31
+ this.ws.on("message", (data) => {
32
+ let msg;
33
+ try {
34
+ msg = JSON.parse(data.toString());
35
+ }
36
+ catch {
37
+ logger.error("[CARTESIA] Failed to parse message");
38
+ return;
39
+ }
40
+ // Handle error first so it always surfaces, even mid-stream
41
+ if (msg.error) {
42
+ const err = new Error(msg.error);
43
+ if (this.listenerCount("error") > 0) {
44
+ this.emit("error", err);
45
+ }
46
+ else {
47
+ logger.error("[CARTESIA] Unhandled error:", msg.error);
48
+ }
49
+ return;
50
+ }
51
+ if (msg.data) {
52
+ const audio = Buffer.from(msg.data, "base64");
53
+ this.emit("audio", audio);
54
+ }
55
+ if (msg.done) {
56
+ this.emit("done");
57
+ }
58
+ });
59
+ this.ws.on("error", (err) => {
60
+ if (this.listenerCount("error") > 0) {
61
+ this.emit("error", err);
62
+ }
63
+ else {
64
+ logger.error("[CARTESIA] Unhandled WebSocket error:", err.message);
65
+ }
66
+ });
67
+ this.ws.on("close", () => {
68
+ this.isReady = false;
69
+ this.emit("close");
70
+ });
71
+ }
72
+ async ready() {
73
+ if (this.isReady) {
74
+ return;
75
+ }
76
+ const connectPromise = new Promise((resolve, reject) => {
77
+ if (!this.ws) {
78
+ reject(new Error("Cartesia WebSocket is not initialized"));
79
+ return;
80
+ }
81
+ this.ws.once("open", resolve);
82
+ this.ws.once("error", reject);
83
+ this.ws.once("close", () => reject(new Error("Cartesia WebSocket closed before ready")));
84
+ });
85
+ return withTimeout(connectPromise, 5000, "Cartesia WS connect timed out");
86
+ }
87
+ send(text, cont = true) {
88
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
89
+ return;
90
+ }
91
+ this.ws.send(JSON.stringify({
92
+ context_id: this.contextId,
93
+ model_id: "sonic-3",
94
+ transcript: text,
95
+ voice: {
96
+ mode: "id",
97
+ id: "694f9389-aac1-45b6-b726-9d9369183238",
98
+ },
99
+ output_format: {
100
+ container: "raw",
101
+ encoding: "pcm_s16le",
102
+ sample_rate: 24000,
103
+ },
104
+ continue: cont,
105
+ }));
106
+ }
107
+ flush() {
108
+ this.send("", false);
109
+ }
110
+ close() {
111
+ if (!this.ws) {
112
+ return;
113
+ }
114
+ const ws = this.ws;
115
+ this.ws = null;
116
+ this.isReady = false;
117
+ if (ws.readyState === WebSocket.OPEN ||
118
+ ws.readyState === WebSocket.CONNECTING) {
119
+ try {
120
+ ws.close();
121
+ }
122
+ catch (error) {
123
+ logger.warn("[CARTESIA] Failed to close WebSocket cleanly", error);
124
+ }
125
+ }
126
+ ws.once("close", () => {
127
+ ws.removeAllListeners();
128
+ });
129
+ }
130
+ }