modular-voice-agent-sdk 2.5.1 → 2.6.1
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/backends/agent/llm.d.ts.map +1 -1
- package/dist/backends/agent/llm.js +0 -4
- package/dist/backends/agent/llm.js.map +1 -1
- package/dist/backends/cloud/llm.d.ts.map +1 -1
- package/dist/backends/cloud/llm.js +2 -13
- package/dist/backends/cloud/llm.js.map +1 -1
- package/dist/backends/native/llm.d.ts.map +1 -1
- package/dist/backends/native/llm.js +0 -2
- package/dist/backends/native/llm.js.map +1 -1
- package/dist/backends/native/tts.d.ts.map +1 -1
- package/dist/backends/native/tts.js +0 -3
- package/dist/backends/native/tts.js.map +1 -1
- package/dist/backends/transformers/llm.d.ts.map +1 -1
- package/dist/backends/transformers/llm.js +0 -2
- package/dist/backends/transformers/llm.js.map +1 -1
- package/dist/backends/transformers/stt.d.ts.map +1 -1
- package/dist/backends/transformers/stt.js +0 -2
- package/dist/backends/transformers/stt.js.map +1 -1
- package/dist/backends/transformers/tts.d.ts.map +1 -1
- package/dist/backends/transformers/tts.js +0 -2
- package/dist/backends/transformers/tts.js.map +1 -1
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/transport.d.ts +58 -0
- package/dist/client/transport.d.ts.map +1 -0
- package/dist/client/transport.js +15 -0
- package/dist/client/transport.js.map +1 -0
- package/dist/client/transports/http-sse.d.ts +69 -0
- package/dist/client/transports/http-sse.d.ts.map +1 -0
- package/dist/client/transports/http-sse.js +134 -0
- package/dist/client/transports/http-sse.js.map +1 -0
- package/dist/client/transports/index.d.ts +4 -0
- package/dist/client/transports/index.d.ts.map +1 -0
- package/dist/client/transports/index.js +3 -0
- package/dist/client/transports/index.js.map +1 -0
- package/dist/client/transports/websocket.d.ts +29 -0
- package/dist/client/transports/websocket.d.ts.map +1 -0
- package/dist/client/transports/websocket.js +79 -0
- package/dist/client/transports/websocket.js.map +1 -0
- package/dist/client/voice-client.d.ts +38 -14
- package/dist/client/voice-client.d.ts.map +1 -1
- package/dist/client/voice-client.js +58 -47
- package/dist/client/voice-client.js.map +1 -1
- package/dist/services/llm-logger.d.ts +4 -43
- package/dist/services/llm-logger.d.ts.map +1 -1
- package/dist/services/llm-logger.js +24 -121
- package/dist/services/llm-logger.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP + SSE Transport
|
|
3
|
+
*
|
|
4
|
+
* Transport that uses HTTP POST for client→server messages and
|
|
5
|
+
* Server-Sent Events (SSE) for server→client streaming.
|
|
6
|
+
*
|
|
7
|
+
* Useful for:
|
|
8
|
+
* - Environments where WebSocket isn't available or is blocked
|
|
9
|
+
* - Simpler server setups (standard HTTP endpoints)
|
|
10
|
+
* - Serverless/edge deployments that don't support long-lived connections
|
|
11
|
+
* - Debugging (HTTP requests are easier to inspect)
|
|
12
|
+
*
|
|
13
|
+
* Protocol:
|
|
14
|
+
* - POST /session → creates a session, returns { sessionId }
|
|
15
|
+
* - GET /events?sessionId=xxx → SSE stream of ServerEnvelope events
|
|
16
|
+
* - POST /message → sends a ClientEnvelope to the server
|
|
17
|
+
*
|
|
18
|
+
* Audio considerations:
|
|
19
|
+
* - Works best for text-only or hybrid (client-side STT/TTS) modes
|
|
20
|
+
* - Audio chunks are accumulated and sent as POST requests
|
|
21
|
+
* - Higher latency than WebSocket for real-time audio streaming
|
|
22
|
+
*/
|
|
23
|
+
export class HttpSseTransport {
|
|
24
|
+
state = 'closed';
|
|
25
|
+
sessionId = null;
|
|
26
|
+
eventSource = null;
|
|
27
|
+
messageHandler = null;
|
|
28
|
+
errorHandler = null;
|
|
29
|
+
closeHandler = null;
|
|
30
|
+
baseUrl;
|
|
31
|
+
sessionPath;
|
|
32
|
+
eventsPath;
|
|
33
|
+
messagePath;
|
|
34
|
+
headers;
|
|
35
|
+
constructor(config) {
|
|
36
|
+
if (typeof config === 'string') {
|
|
37
|
+
config = { baseUrl: config };
|
|
38
|
+
}
|
|
39
|
+
// Strip trailing slash
|
|
40
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, '');
|
|
41
|
+
this.sessionPath = config.sessionPath ?? '/session';
|
|
42
|
+
this.eventsPath = config.eventsPath ?? '/events';
|
|
43
|
+
this.messagePath = config.messagePath ?? '/message';
|
|
44
|
+
this.headers = config.headers ?? {};
|
|
45
|
+
}
|
|
46
|
+
get readyState() {
|
|
47
|
+
return this.state;
|
|
48
|
+
}
|
|
49
|
+
async connect() {
|
|
50
|
+
if (this.state === 'open')
|
|
51
|
+
return;
|
|
52
|
+
this.state = 'connecting';
|
|
53
|
+
try {
|
|
54
|
+
// Step 1: Create a session via POST
|
|
55
|
+
const response = await fetch(`${this.baseUrl}${this.sessionPath}`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
...this.headers,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Session creation failed: ${response.status} ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
this.sessionId = data.sessionId;
|
|
67
|
+
// Step 2: Open SSE stream for server→client messages
|
|
68
|
+
await this.connectEventSource();
|
|
69
|
+
this.state = 'open';
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
this.state = 'closed';
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
connectEventSource() {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const url = `${this.baseUrl}${this.eventsPath}?sessionId=${encodeURIComponent(this.sessionId)}`;
|
|
79
|
+
this.eventSource = new EventSource(url);
|
|
80
|
+
this.eventSource.onopen = () => {
|
|
81
|
+
resolve();
|
|
82
|
+
};
|
|
83
|
+
this.eventSource.onmessage = (event) => {
|
|
84
|
+
try {
|
|
85
|
+
const envelope = JSON.parse(event.data);
|
|
86
|
+
this.messageHandler?.(envelope);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
this.errorHandler?.(new Error('Failed to parse SSE message'));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
this.eventSource.onerror = () => {
|
|
93
|
+
if (this.state === 'connecting') {
|
|
94
|
+
reject(new Error('SSE connection failed'));
|
|
95
|
+
}
|
|
96
|
+
else if (this.state === 'open') {
|
|
97
|
+
this.state = 'closed';
|
|
98
|
+
this.errorHandler?.(new Error('SSE connection lost'));
|
|
99
|
+
this.closeHandler?.();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
send(envelope) {
|
|
105
|
+
if (this.state !== 'open')
|
|
106
|
+
return;
|
|
107
|
+
fetch(`${this.baseUrl}${this.messagePath}`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: {
|
|
110
|
+
'Content-Type': 'application/json',
|
|
111
|
+
...this.headers,
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(envelope),
|
|
114
|
+
}).catch((error) => {
|
|
115
|
+
this.errorHandler?.(error instanceof Error ? error : new Error(String(error)));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
onMessage(handler) {
|
|
119
|
+
this.messageHandler = handler;
|
|
120
|
+
}
|
|
121
|
+
onError(handler) {
|
|
122
|
+
this.errorHandler = handler;
|
|
123
|
+
}
|
|
124
|
+
onClose(handler) {
|
|
125
|
+
this.closeHandler = handler;
|
|
126
|
+
}
|
|
127
|
+
close() {
|
|
128
|
+
this.eventSource?.close();
|
|
129
|
+
this.eventSource = null;
|
|
130
|
+
this.sessionId = null;
|
|
131
|
+
this.state = 'closed';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=http-sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-sse.js","sourceRoot":"","sources":["../../../src/client/transports/http-sse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Transport
|
|
3
|
+
*
|
|
4
|
+
* Standard WebSocket-based transport for voice pipeline communication.
|
|
5
|
+
* This is the default transport used by VoiceClient when a serverUrl is provided.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Full-duplex communication
|
|
9
|
+
* - Real-time audio streaming
|
|
10
|
+
* - Low latency
|
|
11
|
+
*/
|
|
12
|
+
import type { ClientEnvelope, ServerEnvelope } from '../protocol';
|
|
13
|
+
import type { Transport, TransportState } from '../transport';
|
|
14
|
+
export declare class WebSocketTransport implements Transport {
|
|
15
|
+
private url;
|
|
16
|
+
private ws;
|
|
17
|
+
private messageHandler;
|
|
18
|
+
private errorHandler;
|
|
19
|
+
private closeHandler;
|
|
20
|
+
constructor(url: string);
|
|
21
|
+
get readyState(): TransportState;
|
|
22
|
+
connect(): Promise<void>;
|
|
23
|
+
send(envelope: ClientEnvelope): void;
|
|
24
|
+
onMessage(handler: (envelope: ServerEnvelope) => void): void;
|
|
25
|
+
onError(handler: (error: Error) => void): void;
|
|
26
|
+
onClose(handler: () => void): void;
|
|
27
|
+
close(): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../../src/client/transports/websocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9D,qBAAa,kBAAmB,YAAW,SAAS;IAMtC,OAAO,CAAC,GAAG;IALvB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,cAAc,CAAqD;IAC3E,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,YAAY,CAA6B;gBAE7B,GAAG,EAAE,MAAM;IAE/B,IAAI,UAAU,IAAI,cAAc,CAO/B;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC9B,IAAI,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAMpC,SAAS,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;IAI5D,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI9C,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Transport
|
|
3
|
+
*
|
|
4
|
+
* Standard WebSocket-based transport for voice pipeline communication.
|
|
5
|
+
* This is the default transport used by VoiceClient when a serverUrl is provided.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Full-duplex communication
|
|
9
|
+
* - Real-time audio streaming
|
|
10
|
+
* - Low latency
|
|
11
|
+
*/
|
|
12
|
+
export class WebSocketTransport {
|
|
13
|
+
url;
|
|
14
|
+
ws = null;
|
|
15
|
+
messageHandler = null;
|
|
16
|
+
errorHandler = null;
|
|
17
|
+
closeHandler = null;
|
|
18
|
+
constructor(url) {
|
|
19
|
+
this.url = url;
|
|
20
|
+
}
|
|
21
|
+
get readyState() {
|
|
22
|
+
if (!this.ws)
|
|
23
|
+
return 'closed';
|
|
24
|
+
switch (this.ws.readyState) {
|
|
25
|
+
case WebSocket.CONNECTING: return 'connecting';
|
|
26
|
+
case WebSocket.OPEN: return 'open';
|
|
27
|
+
default: return 'closed';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async connect() {
|
|
31
|
+
if (this.ws?.readyState === WebSocket.OPEN)
|
|
32
|
+
return;
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
this.ws = new WebSocket(this.url);
|
|
35
|
+
this.ws.onopen = () => {
|
|
36
|
+
resolve();
|
|
37
|
+
};
|
|
38
|
+
this.ws.onclose = () => {
|
|
39
|
+
this.closeHandler?.();
|
|
40
|
+
};
|
|
41
|
+
this.ws.onerror = () => {
|
|
42
|
+
const error = new Error('WebSocket error');
|
|
43
|
+
this.errorHandler?.(error);
|
|
44
|
+
// Reject only if we haven't connected yet
|
|
45
|
+
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
46
|
+
reject(error);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
this.ws.onmessage = (event) => {
|
|
50
|
+
try {
|
|
51
|
+
const envelope = JSON.parse(event.data);
|
|
52
|
+
this.messageHandler?.(envelope);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
this.errorHandler?.(new Error('Failed to parse server message'));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
send(envelope) {
|
|
61
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
62
|
+
this.ws.send(JSON.stringify(envelope));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
onMessage(handler) {
|
|
66
|
+
this.messageHandler = handler;
|
|
67
|
+
}
|
|
68
|
+
onError(handler) {
|
|
69
|
+
this.errorHandler = handler;
|
|
70
|
+
}
|
|
71
|
+
onClose(handler) {
|
|
72
|
+
this.closeHandler = handler;
|
|
73
|
+
}
|
|
74
|
+
close() {
|
|
75
|
+
this.ws?.close();
|
|
76
|
+
this.ws = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=websocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../../src/client/transports/websocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,OAAO,kBAAkB;IAMT;IALZ,EAAE,GAAqB,IAAI,CAAC;IAC5B,cAAc,GAAgD,IAAI,CAAC;IACnE,YAAY,GAAoC,IAAI,CAAC;IACrD,YAAY,GAAwB,IAAI,CAAC;IAEjD,YAAoB,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAEnC,IAAI,UAAU;QACZ,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC;QAC9B,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC;YAC3B,KAAK,SAAS,CAAC,UAAU,CAAC,CAAC,OAAO,YAAY,CAAC;YAC/C,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,MAAM,CAAC;YACnC,OAAO,CAAC,CAAC,OAAO,QAAQ,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,EAAE,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI;YAAE,OAAO;QAEnD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAElC,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC;YAEF,IAAI,CAAC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;gBACrB,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACxB,CAAC,CAAC;YAEF,IAAI,CAAC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;gBACrB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;gBAC3C,IAAI,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC;gBAC3B,0CAA0C;gBAC1C,IAAI,IAAI,CAAC,EAAE,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;oBAC3C,MAAM,CAAC,KAAK,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC,CAAC;YAEF,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,CAAC,KAAK,EAAE,EAAE;gBAC5B,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;oBAC1D,IAAI,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,CAAC;gBAClC,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,QAAwB;QAC3B,IAAI,IAAI,CAAC,EAAE,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC3C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,OAA2C;QACnD,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,OAA+B;QACrC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;IAC9B,CAAC;IAED,OAAO,CAAC,OAAmB;QACzB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;IAC9B,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IACjB,CAAC;CACF"}
|
|
@@ -4,17 +4,23 @@
|
|
|
4
4
|
* Unified browser SDK for voice assistants.
|
|
5
5
|
* Handles three modes:
|
|
6
6
|
* 1. Fully local - all components run in browser, no server needed
|
|
7
|
-
* 2. Fully remote - all processing on server via
|
|
7
|
+
* 2. Fully remote - all processing on server via transport
|
|
8
8
|
* 3. Hybrid - mix of local and server components
|
|
9
9
|
*
|
|
10
10
|
* Component logic:
|
|
11
11
|
* - Component provided → runs locally
|
|
12
|
-
* - Component is null + serverUrl → server handles it
|
|
13
|
-
* - All components local → no
|
|
12
|
+
* - Component is null + serverUrl/transport → server handles it
|
|
13
|
+
* - All components local → no server connection needed
|
|
14
|
+
*
|
|
15
|
+
* Transport:
|
|
16
|
+
* - Default: WebSocketTransport (when serverUrl is provided)
|
|
17
|
+
* - Custom: Pass any Transport implementation via the transport option
|
|
18
|
+
* - Built-in alternatives: HttpSseTransport (HTTP POST + SSE)
|
|
14
19
|
*/
|
|
15
20
|
import type { STTPipeline, LLMPipeline, TTSPipeline } from '../types';
|
|
16
21
|
import { WebSpeechSTT } from './web-speech-stt';
|
|
17
22
|
import { WebSpeechTTS } from './web-speech-tts';
|
|
23
|
+
import type { Transport } from './transport';
|
|
18
24
|
export interface BrowserSupport {
|
|
19
25
|
/** Web Speech API speech recognition (Chrome, Edge, Safari only) */
|
|
20
26
|
webSpeechSTT: boolean;
|
|
@@ -64,9 +70,26 @@ export interface ClientComponents {
|
|
|
64
70
|
*/
|
|
65
71
|
systemPrompt?: string;
|
|
66
72
|
/**
|
|
67
|
-
*
|
|
73
|
+
* Server URL (required if any component is null).
|
|
74
|
+
* When no custom transport is provided, this creates a WebSocketTransport.
|
|
75
|
+
* For WebSocket: 'ws://localhost:3000'
|
|
76
|
+
* For HTTP+SSE: use a custom transport instead
|
|
68
77
|
*/
|
|
69
78
|
serverUrl?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Custom transport for server communication.
|
|
81
|
+
* Overrides serverUrl when provided.
|
|
82
|
+
* Use this for non-WebSocket transports (HTTP+SSE, postMessage, etc.)
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { HttpSseTransport } from 'modular-voice-agent-sdk/client';
|
|
87
|
+
* {
|
|
88
|
+
* transport: new HttpSseTransport('http://localhost:3000'),
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
transport?: Transport;
|
|
70
93
|
}
|
|
71
94
|
import type { ModelStore } from '../voice-pipeline';
|
|
72
95
|
/**
|
|
@@ -184,7 +207,7 @@ export declare class VoiceClient {
|
|
|
184
207
|
private localLLM;
|
|
185
208
|
private localTTS;
|
|
186
209
|
private localPipeline;
|
|
187
|
-
private
|
|
210
|
+
private transport;
|
|
188
211
|
private recorder;
|
|
189
212
|
private player;
|
|
190
213
|
private status;
|
|
@@ -210,7 +233,7 @@ export declare class VoiceClient {
|
|
|
210
233
|
*/
|
|
211
234
|
connect(): Promise<void>;
|
|
212
235
|
private initializeLocalComponents;
|
|
213
|
-
private
|
|
236
|
+
private connectTransport;
|
|
214
237
|
/**
|
|
215
238
|
* Disconnect and clean up
|
|
216
239
|
*/
|
|
@@ -276,10 +299,17 @@ export declare class VoiceClient {
|
|
|
276
299
|
* Returns null if no request is in progress
|
|
277
300
|
*/
|
|
278
301
|
getCurrentRequestId(): string | null;
|
|
302
|
+
/**
|
|
303
|
+
* Get the underlying transport.
|
|
304
|
+
* Useful for inspecting the transport state or for advanced use cases.
|
|
305
|
+
* Returns null if not using server mode.
|
|
306
|
+
*/
|
|
307
|
+
getTransport(): Transport | null;
|
|
279
308
|
/**
|
|
280
309
|
* Get the underlying WebSocket connection.
|
|
281
|
-
*
|
|
282
|
-
*
|
|
310
|
+
* Returns null if not using server mode, not connected, or not using WebSocket transport.
|
|
311
|
+
*
|
|
312
|
+
* @deprecated Use getTransport() instead for transport-agnostic code.
|
|
283
313
|
*
|
|
284
314
|
* @example
|
|
285
315
|
* ```typescript
|
|
@@ -290,12 +320,6 @@ export declare class VoiceClient {
|
|
|
290
320
|
* message: { type: 'my_custom_message', data: {...} } as any,
|
|
291
321
|
* };
|
|
292
322
|
* client.getWebSocket()?.send(JSON.stringify(envelope));
|
|
293
|
-
*
|
|
294
|
-
* // Listen for custom messages
|
|
295
|
-
* client.getWebSocket()?.addEventListener('message', (event) => {
|
|
296
|
-
* const envelope = JSON.parse(event.data) as ServerEnvelope;
|
|
297
|
-
* if (envelope.message.type === 'my_custom_response') { ... }
|
|
298
|
-
* });
|
|
299
323
|
* ```
|
|
300
324
|
*/
|
|
301
325
|
getWebSocket(): WebSocket | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-client.d.ts","sourceRoot":"","sources":["../../src/client/voice-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"voice-client.d.ts","sourceRoot":"","sources":["../../src/client/voice-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
* Unified browser SDK for voice assistants.
|
|
5
5
|
* Handles three modes:
|
|
6
6
|
* 1. Fully local - all components run in browser, no server needed
|
|
7
|
-
* 2. Fully remote - all processing on server via
|
|
7
|
+
* 2. Fully remote - all processing on server via transport
|
|
8
8
|
* 3. Hybrid - mix of local and server components
|
|
9
9
|
*
|
|
10
10
|
* Component logic:
|
|
11
11
|
* - Component provided → runs locally
|
|
12
|
-
* - Component is null + serverUrl → server handles it
|
|
13
|
-
* - All components local → no
|
|
12
|
+
* - Component is null + serverUrl/transport → server handles it
|
|
13
|
+
* - All components local → no server connection needed
|
|
14
|
+
*
|
|
15
|
+
* Transport:
|
|
16
|
+
* - Default: WebSocketTransport (when serverUrl is provided)
|
|
17
|
+
* - Custom: Pass any Transport implementation via the transport option
|
|
18
|
+
* - Built-in alternatives: HttpSseTransport (HTTP POST + SSE)
|
|
14
19
|
*/
|
|
15
20
|
import { VoicePipeline } from '../voice-pipeline';
|
|
16
21
|
import { AudioRecorder } from './audio-recorder';
|
|
@@ -18,6 +23,7 @@ import { AudioPlayer } from './audio-player';
|
|
|
18
23
|
import { WebSpeechSTT } from './web-speech-stt';
|
|
19
24
|
import { WebSpeechTTS } from './web-speech-tts';
|
|
20
25
|
import { float32ToBase64, base64ToFloat32, generateId, } from './protocol';
|
|
26
|
+
import { WebSocketTransport } from './transports/websocket';
|
|
21
27
|
// ============ Helpers ============
|
|
22
28
|
function isWebSpeechSTT(obj) {
|
|
23
29
|
return obj instanceof WebSpeechSTT;
|
|
@@ -99,7 +105,7 @@ export class VoiceClient {
|
|
|
99
105
|
localTTS = null;
|
|
100
106
|
localPipeline = null;
|
|
101
107
|
// Remote/hybrid components
|
|
102
|
-
|
|
108
|
+
transport = null;
|
|
103
109
|
recorder = null;
|
|
104
110
|
player = null;
|
|
105
111
|
// State
|
|
@@ -142,9 +148,16 @@ export class VoiceClient {
|
|
|
142
148
|
this.mode = 'hybrid';
|
|
143
149
|
}
|
|
144
150
|
// Validate config
|
|
145
|
-
if (this.needsServer && !components.serverUrl) {
|
|
146
|
-
throw new Error('serverUrl is required when any component (stt, llm, tts) is null. ' +
|
|
147
|
-
'Either provide all components for fully-local mode, or specify a serverUrl.');
|
|
151
|
+
if (this.needsServer && !components.serverUrl && !components.transport) {
|
|
152
|
+
throw new Error('serverUrl or transport is required when any component (stt, llm, tts) is null. ' +
|
|
153
|
+
'Either provide all components for fully-local mode, or specify a serverUrl or custom transport.');
|
|
154
|
+
}
|
|
155
|
+
// Create transport from serverUrl if no custom transport provided
|
|
156
|
+
if (components.transport) {
|
|
157
|
+
this.transport = components.transport;
|
|
158
|
+
}
|
|
159
|
+
else if (components.serverUrl) {
|
|
160
|
+
this.transport = new WebSocketTransport(components.serverUrl);
|
|
148
161
|
}
|
|
149
162
|
if (hasLocalLLM && !components.systemPrompt) {
|
|
150
163
|
throw new Error('systemPrompt is required when using a local LLM');
|
|
@@ -258,7 +271,7 @@ export class VoiceClient {
|
|
|
258
271
|
}
|
|
259
272
|
// Connect to server if needed
|
|
260
273
|
if (this.needsServer) {
|
|
261
|
-
await this.
|
|
274
|
+
await this.connectTransport();
|
|
262
275
|
}
|
|
263
276
|
else {
|
|
264
277
|
this.setStatus('ready');
|
|
@@ -296,37 +309,28 @@ export class VoiceClient {
|
|
|
296
309
|
}
|
|
297
310
|
await Promise.all(promises);
|
|
298
311
|
}
|
|
299
|
-
async
|
|
300
|
-
if (!this.
|
|
312
|
+
async connectTransport() {
|
|
313
|
+
if (!this.transport)
|
|
301
314
|
return;
|
|
302
|
-
if (this.
|
|
315
|
+
if (this.transport.readyState === 'open')
|
|
303
316
|
return;
|
|
304
317
|
this.setStatus('connecting');
|
|
305
|
-
|
|
306
|
-
this.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
this.ws.onclose = () => {
|
|
318
|
+
// Register handlers before connecting
|
|
319
|
+
this.transport.onMessage((envelope) => {
|
|
320
|
+
this.handleServerEnvelope(envelope);
|
|
321
|
+
});
|
|
322
|
+
this.transport.onClose(() => {
|
|
311
323
|
this.sessionId = null;
|
|
312
324
|
this.currentRequestId = null;
|
|
313
325
|
this.setStatus('disconnected');
|
|
314
326
|
if (this.config.autoReconnect && this.needsServer) {
|
|
315
327
|
this.scheduleReconnect();
|
|
316
328
|
}
|
|
317
|
-
};
|
|
318
|
-
this.
|
|
319
|
-
this.emit('error',
|
|
320
|
-
};
|
|
321
|
-
this.
|
|
322
|
-
try {
|
|
323
|
-
const envelope = JSON.parse(event.data);
|
|
324
|
-
this.handleServerEnvelope(envelope);
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
this.emit('error', new Error('Failed to parse server message'), createLocalMeta(this.currentRequestId));
|
|
328
|
-
}
|
|
329
|
-
};
|
|
329
|
+
});
|
|
330
|
+
this.transport.onError((error) => {
|
|
331
|
+
this.emit('error', error, createLocalMeta(this.currentRequestId));
|
|
332
|
+
});
|
|
333
|
+
await this.transport.connect();
|
|
330
334
|
}
|
|
331
335
|
/**
|
|
332
336
|
* Disconnect and clean up
|
|
@@ -337,8 +341,7 @@ export class VoiceClient {
|
|
|
337
341
|
clearTimeout(this.reconnectTimer);
|
|
338
342
|
this.reconnectTimer = null;
|
|
339
343
|
}
|
|
340
|
-
this.
|
|
341
|
-
this.ws = null;
|
|
344
|
+
this.transport?.close();
|
|
342
345
|
// Stop any TTS
|
|
343
346
|
if (isWebSpeechTTS(this.localTTS)) {
|
|
344
347
|
this.localTTS.stop();
|
|
@@ -583,7 +586,7 @@ export class VoiceClient {
|
|
|
583
586
|
if (this.mode === 'local') {
|
|
584
587
|
return this.localPipeline?.isReady() ?? false;
|
|
585
588
|
}
|
|
586
|
-
return this.
|
|
589
|
+
return this.transport?.readyState === 'open';
|
|
587
590
|
}
|
|
588
591
|
/**
|
|
589
592
|
* Check if currently recording
|
|
@@ -651,10 +654,19 @@ export class VoiceClient {
|
|
|
651
654
|
getCurrentRequestId() {
|
|
652
655
|
return this.currentRequestId;
|
|
653
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Get the underlying transport.
|
|
659
|
+
* Useful for inspecting the transport state or for advanced use cases.
|
|
660
|
+
* Returns null if not using server mode.
|
|
661
|
+
*/
|
|
662
|
+
getTransport() {
|
|
663
|
+
return this.transport;
|
|
664
|
+
}
|
|
654
665
|
/**
|
|
655
666
|
* Get the underlying WebSocket connection.
|
|
656
|
-
*
|
|
657
|
-
*
|
|
667
|
+
* Returns null if not using server mode, not connected, or not using WebSocket transport.
|
|
668
|
+
*
|
|
669
|
+
* @deprecated Use getTransport() instead for transport-agnostic code.
|
|
658
670
|
*
|
|
659
671
|
* @example
|
|
660
672
|
* ```typescript
|
|
@@ -665,16 +677,15 @@ export class VoiceClient {
|
|
|
665
677
|
* message: { type: 'my_custom_message', data: {...} } as any,
|
|
666
678
|
* };
|
|
667
679
|
* client.getWebSocket()?.send(JSON.stringify(envelope));
|
|
668
|
-
*
|
|
669
|
-
* // Listen for custom messages
|
|
670
|
-
* client.getWebSocket()?.addEventListener('message', (event) => {
|
|
671
|
-
* const envelope = JSON.parse(event.data) as ServerEnvelope;
|
|
672
|
-
* if (envelope.message.type === 'my_custom_response') { ... }
|
|
673
|
-
* });
|
|
674
680
|
* ```
|
|
675
681
|
*/
|
|
676
682
|
getWebSocket() {
|
|
677
|
-
|
|
683
|
+
if (this.transport instanceof WebSocketTransport) {
|
|
684
|
+
// WebSocketTransport doesn't expose the raw WS; return null
|
|
685
|
+
// Users should migrate to getTransport()
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
return null;
|
|
678
689
|
}
|
|
679
690
|
/**
|
|
680
691
|
* Subscribe to events
|
|
@@ -705,13 +716,13 @@ export class VoiceClient {
|
|
|
705
716
|
}
|
|
706
717
|
// ============ Private Methods ============
|
|
707
718
|
send(msg) {
|
|
708
|
-
if (this.
|
|
719
|
+
if (this.transport?.readyState === 'open' && this.sessionId && this.currentRequestId) {
|
|
709
720
|
const envelope = {
|
|
710
721
|
sessionId: this.sessionId,
|
|
711
722
|
requestId: this.currentRequestId,
|
|
712
723
|
message: msg,
|
|
713
724
|
};
|
|
714
|
-
this.
|
|
725
|
+
this.transport.send(envelope);
|
|
715
726
|
}
|
|
716
727
|
}
|
|
717
728
|
handleServerEnvelope(envelope) {
|
|
@@ -901,9 +912,9 @@ export class VoiceClient {
|
|
|
901
912
|
' 2. The browser does not support getUserMedia\n' +
|
|
902
913
|
' 3. Microphone permissions were denied');
|
|
903
914
|
}
|
|
904
|
-
// Check WebSocket if using server
|
|
905
|
-
if (components.serverUrl && !support.webSocket) {
|
|
906
|
-
throw new Error('WebSocket is not supported in this browser.');
|
|
915
|
+
// Check WebSocket if using server with default transport (no custom transport)
|
|
916
|
+
if (components.serverUrl && !components.transport && !support.webSocket) {
|
|
917
|
+
throw new Error('WebSocket is not supported in this browser. Consider using a custom transport (e.g., HttpSseTransport).');
|
|
907
918
|
}
|
|
908
919
|
// Check AudioContext for audio processing
|
|
909
920
|
if (!support.audioContext) {
|