kugelaudio 0.1.11 → 0.1.12

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.js CHANGED
@@ -135,8 +135,26 @@ function createWavBlob(audio, sampleRate) {
135
135
  return new Blob([wavBuffer], { type: "audio/wav" });
136
136
  }
137
137
 
138
+ // src/websocket.ts
139
+ var isBrowser = typeof window !== "undefined" && typeof window.WebSocket !== "undefined";
140
+ function getWebSocket() {
141
+ if (isBrowser) {
142
+ return window.WebSocket;
143
+ }
144
+ try {
145
+ const ws = require("ws");
146
+ return ws;
147
+ } catch {
148
+ throw new Error(
149
+ 'WebSocket not available. In Node.js, install the "ws" package: npm install ws'
150
+ );
151
+ }
152
+ }
153
+ var WebSocketCompat = getWebSocket();
154
+
138
155
  // src/client.ts
139
156
  var DEFAULT_API_URL = "https://api.kugelaudio.com";
157
+ var WebSocketImpl = getWebSocket();
140
158
  var ModelsResource = class {
141
159
  constructor(client) {
142
160
  this.client = client;
@@ -212,6 +230,7 @@ var VoicesResource = class {
212
230
  var TTSResource = class {
213
231
  constructor(client) {
214
232
  this.client = client;
233
+ // Using any for WebSocket to support both browser WebSocket and ws package
215
234
  this.wsConnection = null;
216
235
  this.wsUrl = null;
217
236
  this.pendingRequests = /* @__PURE__ */ new Map();
@@ -241,7 +260,7 @@ var TTSResource = class {
241
260
  * Check if WebSocket connection is established and open.
242
261
  */
243
262
  isConnected() {
244
- return this.wsConnection !== null && this.wsConnection.readyState === WebSocket.OPEN;
263
+ return this.wsConnection !== null && this.wsConnection.readyState === WebSocketImpl.OPEN;
245
264
  }
246
265
  /**
247
266
  * Generate audio from text with streaming via WebSocket.
@@ -299,7 +318,7 @@ var TTSResource = class {
299
318
  */
300
319
  async getConnection() {
301
320
  const url = this.buildWsUrl();
302
- if (this.wsConnection && this.wsUrl === url && this.wsConnection.readyState === WebSocket.OPEN) {
321
+ if (this.wsConnection && this.wsUrl === url && this.wsConnection.readyState === WebSocketImpl.OPEN) {
303
322
  return this.wsConnection;
304
323
  }
305
324
  if (this.wsConnection) {
@@ -310,7 +329,7 @@ var TTSResource = class {
310
329
  this.wsConnection = null;
311
330
  }
312
331
  return new Promise((resolve, reject) => {
313
- const ws = new WebSocket(url);
332
+ const ws = new WebSocketImpl(url);
314
333
  ws.onopen = () => {
315
334
  this.wsConnection = ws;
316
335
  this.wsUrl = url;
@@ -328,7 +347,8 @@ var TTSResource = class {
328
347
  setupMessageHandler(ws) {
329
348
  ws.onmessage = (event) => {
330
349
  try {
331
- const data = JSON.parse(event.data);
350
+ const messageData = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
351
+ const data = JSON.parse(messageData);
332
352
  const [requestId, pending] = [...this.pendingRequests.entries()][0] || [];
333
353
  if (!pending) return;
334
354
  if (data.error) {
@@ -431,7 +451,7 @@ var TTSResource = class {
431
451
  streamWithoutPooling(options, callbacks) {
432
452
  return new Promise((resolve, reject) => {
433
453
  const url = this.buildWsUrl();
434
- const ws = new WebSocket(url);
454
+ const ws = new WebSocketImpl(url);
435
455
  ws.onopen = () => {
436
456
  callbacks.onOpen?.();
437
457
  ws.send(JSON.stringify({
@@ -447,7 +467,8 @@ var TTSResource = class {
447
467
  };
448
468
  ws.onmessage = (event) => {
449
469
  try {
450
- const data = JSON.parse(event.data);
470
+ const messageData = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
471
+ const data = JSON.parse(messageData);
451
472
  if (data.error) {
452
473
  const error = this.parseError(data.error);
453
474
  callbacks.onError?.(error);
@@ -593,12 +614,13 @@ var MultiContextSession = class {
593
614
  authParam = "api_key";
594
615
  }
595
616
  const url = `${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`;
596
- this.ws = new WebSocket(url);
617
+ this.ws = new WebSocketImpl(url);
597
618
  this.ws.onopen = () => {
598
619
  };
599
620
  this.ws.onmessage = (event) => {
600
621
  try {
601
- const data = JSON.parse(event.data);
622
+ const messageData = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
623
+ const data = JSON.parse(messageData);
602
624
  if (data.error) {
603
625
  this.callbacks.onError?.(
604
626
  new KugelAudioError(data.error),
@@ -662,7 +684,7 @@ var MultiContextSession = class {
662
684
  * Create a new context with optional voice settings.
663
685
  */
664
686
  createContext(contextId, options) {
665
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
687
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) {
666
688
  throw new KugelAudioError("WebSocket not connected");
667
689
  }
668
690
  const msg = {
@@ -693,7 +715,7 @@ var MultiContextSession = class {
693
715
  * Send text to a specific context.
694
716
  */
695
717
  send(contextId, text, flush = false) {
696
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
718
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) {
697
719
  throw new KugelAudioError("WebSocket not connected");
698
720
  }
699
721
  if (!this.contexts.has(contextId) && !this.isStarted) {
@@ -709,7 +731,7 @@ var MultiContextSession = class {
709
731
  * Flush a context's buffer.
710
732
  */
711
733
  flush(contextId) {
712
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
734
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
713
735
  this.ws.send(JSON.stringify({
714
736
  flush: true,
715
737
  context_id: contextId
@@ -719,7 +741,7 @@ var MultiContextSession = class {
719
741
  * Close a specific context.
720
742
  */
721
743
  closeContext(contextId) {
722
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
744
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
723
745
  this.ws.send(JSON.stringify({
724
746
  close_context: true,
725
747
  context_id: contextId
@@ -729,7 +751,7 @@ var MultiContextSession = class {
729
751
  * Send keep-alive to reset a context's inactivity timeout.
730
752
  */
731
753
  keepAlive(contextId) {
732
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
754
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
733
755
  this.ws.send(JSON.stringify({
734
756
  text: "",
735
757
  context_id: contextId
@@ -739,7 +761,7 @@ var MultiContextSession = class {
739
761
  * Close the session and all contexts.
740
762
  */
741
763
  close() {
742
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
764
+ if (this.ws && this.ws.readyState === WebSocketImpl.OPEN) {
743
765
  this.ws.send(JSON.stringify({ close_socket: true }));
744
766
  this.ws.close();
745
767
  }
@@ -757,7 +779,7 @@ var MultiContextSession = class {
757
779
  * Check if connected.
758
780
  */
759
781
  get isConnected() {
760
- return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
782
+ return this.ws !== null && this.ws.readyState === WebSocketImpl.OPEN;
761
783
  }
762
784
  };
763
785
  var KugelAudio = class _KugelAudio {
package/dist/index.mjs CHANGED
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/errors.ts
2
9
  var KugelAudioError = class _KugelAudioError extends Error {
3
10
  constructor(message, statusCode) {
@@ -99,8 +106,26 @@ function createWavBlob(audio, sampleRate) {
99
106
  return new Blob([wavBuffer], { type: "audio/wav" });
100
107
  }
101
108
 
109
+ // src/websocket.ts
110
+ var isBrowser = typeof window !== "undefined" && typeof window.WebSocket !== "undefined";
111
+ function getWebSocket() {
112
+ if (isBrowser) {
113
+ return window.WebSocket;
114
+ }
115
+ try {
116
+ const ws = __require("ws");
117
+ return ws;
118
+ } catch {
119
+ throw new Error(
120
+ 'WebSocket not available. In Node.js, install the "ws" package: npm install ws'
121
+ );
122
+ }
123
+ }
124
+ var WebSocketCompat = getWebSocket();
125
+
102
126
  // src/client.ts
103
127
  var DEFAULT_API_URL = "https://api.kugelaudio.com";
128
+ var WebSocketImpl = getWebSocket();
104
129
  var ModelsResource = class {
105
130
  constructor(client) {
106
131
  this.client = client;
@@ -176,6 +201,7 @@ var VoicesResource = class {
176
201
  var TTSResource = class {
177
202
  constructor(client) {
178
203
  this.client = client;
204
+ // Using any for WebSocket to support both browser WebSocket and ws package
179
205
  this.wsConnection = null;
180
206
  this.wsUrl = null;
181
207
  this.pendingRequests = /* @__PURE__ */ new Map();
@@ -205,7 +231,7 @@ var TTSResource = class {
205
231
  * Check if WebSocket connection is established and open.
206
232
  */
207
233
  isConnected() {
208
- return this.wsConnection !== null && this.wsConnection.readyState === WebSocket.OPEN;
234
+ return this.wsConnection !== null && this.wsConnection.readyState === WebSocketImpl.OPEN;
209
235
  }
210
236
  /**
211
237
  * Generate audio from text with streaming via WebSocket.
@@ -263,7 +289,7 @@ var TTSResource = class {
263
289
  */
264
290
  async getConnection() {
265
291
  const url = this.buildWsUrl();
266
- if (this.wsConnection && this.wsUrl === url && this.wsConnection.readyState === WebSocket.OPEN) {
292
+ if (this.wsConnection && this.wsUrl === url && this.wsConnection.readyState === WebSocketImpl.OPEN) {
267
293
  return this.wsConnection;
268
294
  }
269
295
  if (this.wsConnection) {
@@ -274,7 +300,7 @@ var TTSResource = class {
274
300
  this.wsConnection = null;
275
301
  }
276
302
  return new Promise((resolve, reject) => {
277
- const ws = new WebSocket(url);
303
+ const ws = new WebSocketImpl(url);
278
304
  ws.onopen = () => {
279
305
  this.wsConnection = ws;
280
306
  this.wsUrl = url;
@@ -292,7 +318,8 @@ var TTSResource = class {
292
318
  setupMessageHandler(ws) {
293
319
  ws.onmessage = (event) => {
294
320
  try {
295
- const data = JSON.parse(event.data);
321
+ const messageData = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
322
+ const data = JSON.parse(messageData);
296
323
  const [requestId, pending] = [...this.pendingRequests.entries()][0] || [];
297
324
  if (!pending) return;
298
325
  if (data.error) {
@@ -395,7 +422,7 @@ var TTSResource = class {
395
422
  streamWithoutPooling(options, callbacks) {
396
423
  return new Promise((resolve, reject) => {
397
424
  const url = this.buildWsUrl();
398
- const ws = new WebSocket(url);
425
+ const ws = new WebSocketImpl(url);
399
426
  ws.onopen = () => {
400
427
  callbacks.onOpen?.();
401
428
  ws.send(JSON.stringify({
@@ -411,7 +438,8 @@ var TTSResource = class {
411
438
  };
412
439
  ws.onmessage = (event) => {
413
440
  try {
414
- const data = JSON.parse(event.data);
441
+ const messageData = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
442
+ const data = JSON.parse(messageData);
415
443
  if (data.error) {
416
444
  const error = this.parseError(data.error);
417
445
  callbacks.onError?.(error);
@@ -557,12 +585,13 @@ var MultiContextSession = class {
557
585
  authParam = "api_key";
558
586
  }
559
587
  const url = `${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`;
560
- this.ws = new WebSocket(url);
588
+ this.ws = new WebSocketImpl(url);
561
589
  this.ws.onopen = () => {
562
590
  };
563
591
  this.ws.onmessage = (event) => {
564
592
  try {
565
- const data = JSON.parse(event.data);
593
+ const messageData = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
594
+ const data = JSON.parse(messageData);
566
595
  if (data.error) {
567
596
  this.callbacks.onError?.(
568
597
  new KugelAudioError(data.error),
@@ -626,7 +655,7 @@ var MultiContextSession = class {
626
655
  * Create a new context with optional voice settings.
627
656
  */
628
657
  createContext(contextId, options) {
629
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
658
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) {
630
659
  throw new KugelAudioError("WebSocket not connected");
631
660
  }
632
661
  const msg = {
@@ -657,7 +686,7 @@ var MultiContextSession = class {
657
686
  * Send text to a specific context.
658
687
  */
659
688
  send(contextId, text, flush = false) {
660
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
689
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) {
661
690
  throw new KugelAudioError("WebSocket not connected");
662
691
  }
663
692
  if (!this.contexts.has(contextId) && !this.isStarted) {
@@ -673,7 +702,7 @@ var MultiContextSession = class {
673
702
  * Flush a context's buffer.
674
703
  */
675
704
  flush(contextId) {
676
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
705
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
677
706
  this.ws.send(JSON.stringify({
678
707
  flush: true,
679
708
  context_id: contextId
@@ -683,7 +712,7 @@ var MultiContextSession = class {
683
712
  * Close a specific context.
684
713
  */
685
714
  closeContext(contextId) {
686
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
715
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
687
716
  this.ws.send(JSON.stringify({
688
717
  close_context: true,
689
718
  context_id: contextId
@@ -693,7 +722,7 @@ var MultiContextSession = class {
693
722
  * Send keep-alive to reset a context's inactivity timeout.
694
723
  */
695
724
  keepAlive(contextId) {
696
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
725
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
697
726
  this.ws.send(JSON.stringify({
698
727
  text: "",
699
728
  context_id: contextId
@@ -703,7 +732,7 @@ var MultiContextSession = class {
703
732
  * Close the session and all contexts.
704
733
  */
705
734
  close() {
706
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
735
+ if (this.ws && this.ws.readyState === WebSocketImpl.OPEN) {
707
736
  this.ws.send(JSON.stringify({ close_socket: true }));
708
737
  this.ws.close();
709
738
  }
@@ -721,7 +750,7 @@ var MultiContextSession = class {
721
750
  * Check if connected.
722
751
  */
723
752
  get isConnected() {
724
- return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
753
+ return this.ws !== null && this.ws.readyState === WebSocketImpl.OPEN;
725
754
  }
726
755
  };
727
756
  var KugelAudio = class _KugelAudio {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kugelaudio",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Official JavaScript/TypeScript SDK for KugelAudio TTS API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -52,5 +52,9 @@
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=18.0.0"
55
+ },
56
+ "dependencies": {
57
+ "tsx": "^4.21.0",
58
+ "ws": "^8.18.0"
55
59
  }
56
60
  }
package/src/client.ts CHANGED
@@ -19,9 +19,13 @@ import type {
19
19
  Voice
20
20
  } from './types';
21
21
  import { base64ToArrayBuffer } from './utils';
22
+ import { getWebSocket } from './websocket';
22
23
 
23
24
  const DEFAULT_API_URL = 'https://api.kugelaudio.com';
24
25
 
26
+ // Get WebSocket constructor for current environment
27
+ const WebSocketImpl = getWebSocket();
28
+
25
29
  /**
26
30
  * Models resource for listing TTS models.
27
31
  */
@@ -111,7 +115,8 @@ class VoicesResource {
111
115
  * TTS resource for text-to-speech generation.
112
116
  */
113
117
  class TTSResource {
114
- private wsConnection: WebSocket | null = null;
118
+ // Using any for WebSocket to support both browser WebSocket and ws package
119
+ private wsConnection: InstanceType<typeof WebSocketImpl> | null = null;
115
120
  private wsUrl: string | null = null;
116
121
  private pendingRequests: Map<number, {
117
122
  callbacks: StreamCallbacks;
@@ -147,7 +152,7 @@ class TTSResource {
147
152
  * Check if WebSocket connection is established and open.
148
153
  */
149
154
  isConnected(): boolean {
150
- return this.wsConnection !== null && this.wsConnection.readyState === WebSocket.OPEN;
155
+ return this.wsConnection !== null && this.wsConnection.readyState === WebSocketImpl.OPEN;
151
156
  }
152
157
 
153
158
  /**
@@ -214,14 +219,14 @@ class TTSResource {
214
219
  * Get or create a WebSocket connection for connection pooling.
215
220
  * This avoids the ~220ms connect overhead on each request.
216
221
  */
217
- private async getConnection(): Promise<WebSocket> {
222
+ private async getConnection(): Promise<InstanceType<typeof WebSocketImpl>> {
218
223
  const url = this.buildWsUrl();
219
224
 
220
225
  // Return existing connection if valid
221
226
  if (
222
227
  this.wsConnection &&
223
228
  this.wsUrl === url &&
224
- this.wsConnection.readyState === WebSocket.OPEN
229
+ this.wsConnection.readyState === WebSocketImpl.OPEN
225
230
  ) {
226
231
  return this.wsConnection;
227
232
  }
@@ -238,7 +243,7 @@ class TTSResource {
238
243
 
239
244
  // Create new connection
240
245
  return new Promise((resolve, reject) => {
241
- const ws = new WebSocket(url);
246
+ const ws = new WebSocketImpl(url);
242
247
 
243
248
  ws.onopen = () => {
244
249
  this.wsConnection = ws;
@@ -256,10 +261,16 @@ class TTSResource {
256
261
  /**
257
262
  * Setup message handler for pooled connection.
258
263
  */
259
- private setupMessageHandler(ws: WebSocket): void {
260
- ws.onmessage = (event) => {
264
+ private setupMessageHandler(ws: InstanceType<typeof WebSocketImpl>): void {
265
+ ws.onmessage = (event: { data: unknown }) => {
261
266
  try {
262
- const data = JSON.parse(event.data);
267
+ // Handle both browser (string) and Node.js (Buffer) message formats
268
+ const messageData = typeof event.data === 'string'
269
+ ? event.data
270
+ : event.data instanceof Buffer
271
+ ? event.data.toString()
272
+ : String(event.data);
273
+ const data = JSON.parse(messageData);
263
274
 
264
275
  // Get the current pending request (we process one at a time)
265
276
  const [requestId, pending] = [...this.pendingRequests.entries()][0] || [];
@@ -389,7 +400,7 @@ class TTSResource {
389
400
  ): Promise<void> {
390
401
  return new Promise((resolve, reject) => {
391
402
  const url = this.buildWsUrl();
392
- const ws = new WebSocket(url);
403
+ const ws = new WebSocketImpl(url);
393
404
 
394
405
  ws.onopen = () => {
395
406
  callbacks.onOpen?.();
@@ -406,9 +417,15 @@ class TTSResource {
406
417
  }));
407
418
  };
408
419
 
409
- ws.onmessage = (event) => {
420
+ ws.onmessage = (event: { data: unknown }) => {
410
421
  try {
411
- const data = JSON.parse(event.data);
422
+ // Handle both browser (string) and Node.js (Buffer) message formats
423
+ const messageData = typeof event.data === 'string'
424
+ ? event.data
425
+ : event.data instanceof Buffer
426
+ ? event.data.toString()
427
+ : String(event.data);
428
+ const data = JSON.parse(messageData);
412
429
 
413
430
  if (data.error) {
414
431
  const error = this.parseError(data.error);
@@ -539,7 +556,7 @@ class TTSResource {
539
556
  * Multi-context WebSocket session for concurrent TTS streams.
540
557
  */
541
558
  class MultiContextSession {
542
- private ws: WebSocket | null = null;
559
+ private ws: InstanceType<typeof WebSocketImpl> | null = null;
543
560
  private config: import('./types').MultiContextConfig;
544
561
  private callbacks: import('./types').MultiContextCallbacks = {};
545
562
  private contexts: Set<string> = new Set();
@@ -580,15 +597,21 @@ class MultiContextSession {
580
597
  }
581
598
 
582
599
  const url = `${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`;
583
- this.ws = new WebSocket(url);
600
+ this.ws = new WebSocketImpl(url);
584
601
 
585
602
  this.ws.onopen = () => {
586
603
  // Connection established, ready to create contexts
587
604
  };
588
605
 
589
- this.ws.onmessage = (event) => {
606
+ this.ws.onmessage = (event: { data: unknown }) => {
590
607
  try {
591
- const data = JSON.parse(event.data);
608
+ // Handle both browser (string) and Node.js (Buffer) message formats
609
+ const messageData = typeof event.data === 'string'
610
+ ? event.data
611
+ : event.data instanceof Buffer
612
+ ? event.data.toString()
613
+ : String(event.data);
614
+ const data = JSON.parse(messageData);
592
615
 
593
616
  if (data.error) {
594
617
  this.callbacks.onError?.(
@@ -669,7 +692,7 @@ class MultiContextSession {
669
692
  voiceSettings?: import('./types').ContextVoiceSettings;
670
693
  }
671
694
  ): void {
672
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
695
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) {
673
696
  throw new KugelAudioError('WebSocket not connected');
674
697
  }
675
698
 
@@ -708,7 +731,7 @@ class MultiContextSession {
708
731
  * Send text to a specific context.
709
732
  */
710
733
  send(contextId: string, text: string, flush = false): void {
711
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
734
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) {
712
735
  throw new KugelAudioError('WebSocket not connected');
713
736
  }
714
737
 
@@ -728,7 +751,7 @@ class MultiContextSession {
728
751
  * Flush a context's buffer.
729
752
  */
730
753
  flush(contextId: string): void {
731
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
754
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
732
755
 
733
756
  this.ws.send(JSON.stringify({
734
757
  flush: true,
@@ -740,7 +763,7 @@ class MultiContextSession {
740
763
  * Close a specific context.
741
764
  */
742
765
  closeContext(contextId: string): void {
743
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
766
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
744
767
 
745
768
  this.ws.send(JSON.stringify({
746
769
  close_context: true,
@@ -752,7 +775,7 @@ class MultiContextSession {
752
775
  * Send keep-alive to reset a context's inactivity timeout.
753
776
  */
754
777
  keepAlive(contextId: string): void {
755
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
778
+ if (!this.ws || this.ws.readyState !== WebSocketImpl.OPEN) return;
756
779
 
757
780
  this.ws.send(JSON.stringify({
758
781
  text: '',
@@ -764,7 +787,7 @@ class MultiContextSession {
764
787
  * Close the session and all contexts.
765
788
  */
766
789
  close(): void {
767
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
790
+ if (this.ws && this.ws.readyState === WebSocketImpl.OPEN) {
768
791
  this.ws.send(JSON.stringify({ close_socket: true }));
769
792
  this.ws.close();
770
793
  }
@@ -784,7 +807,7 @@ class MultiContextSession {
784
807
  * Check if connected.
785
808
  */
786
809
  get isConnected(): boolean {
787
- return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
810
+ return this.ws !== null && this.ws.readyState === WebSocketImpl.OPEN;
788
811
  }
789
812
  }
790
813
 
@@ -0,0 +1,33 @@
1
+ /**
2
+ * WebSocket compatibility layer for browser and Node.js environments.
3
+ */
4
+
5
+ // Detect environment
6
+ const isBrowser = typeof window !== 'undefined' && typeof window.WebSocket !== 'undefined';
7
+
8
+ /**
9
+ * Get the WebSocket constructor for the current environment.
10
+ * Uses native WebSocket in browsers, ws package in Node.js.
11
+ */
12
+ export function getWebSocket(): typeof WebSocket {
13
+ if (isBrowser) {
14
+ return window.WebSocket;
15
+ }
16
+
17
+ // Node.js environment - use ws package
18
+ try {
19
+ // Dynamic require to avoid bundler issues
20
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
21
+ const ws = require('ws');
22
+ return ws;
23
+ } catch {
24
+ throw new Error(
25
+ 'WebSocket not available. In Node.js, install the "ws" package: npm install ws'
26
+ );
27
+ }
28
+ }
29
+
30
+ /**
31
+ * WebSocket constructor that works in both environments.
32
+ */
33
+ export const WebSocketCompat = getWebSocket();