@ariaflowagents/livekit-plugin-transport-ws 0.9.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.
- package/dist/audio_input.d.ts +25 -0
- package/dist/audio_input.d.ts.map +1 -0
- package/dist/audio_input.js +93 -0
- package/dist/audio_input.js.map +1 -0
- package/dist/audio_output.d.ts +26 -0
- package/dist/audio_output.d.ts.map +1 -0
- package/dist/audio_output.js +134 -0
- package/dist/audio_output.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +65 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +44 -0
- package/dist/protocol.js.map +1 -0
- package/dist/realtime_bridge.d.ts +43 -0
- package/dist/realtime_bridge.d.ts.map +1 -0
- package/dist/realtime_bridge.js +192 -0
- package/dist/realtime_bridge.js.map +1 -0
- package/dist/server.d.ts +73 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +247 -0
- package/dist/server.js.map +1 -0
- package/dist/text_output.d.ts +17 -0
- package/dist/text_output.d.ts.map +1 -0
- package/dist/text_output.js +77 -0
- package/dist/text_output.js.map +1 -0
- package/dist/transport_adapter.d.ts +34 -0
- package/dist/transport_adapter.d.ts.map +1 -0
- package/dist/transport_adapter.js +72 -0
- package/dist/transport_adapter.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// ─── PCM Conversion Utilities ──────────────────────────────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* Convert float32 PCM samples to int16 PCM bytes (Uint8Array).
|
|
4
|
+
* GeminiLiveSession and RealtimeRuntime expect int16 PCM.
|
|
5
|
+
* LiveKit AudioFrame uses float32 samples internally.
|
|
6
|
+
*/
|
|
7
|
+
export function float32ToInt16Bytes(float32) {
|
|
8
|
+
const int16 = new Int16Array(float32.length);
|
|
9
|
+
for (let i = 0; i < float32.length; i++) {
|
|
10
|
+
const clamped = Math.max(-1, Math.min(1, float32[i] ?? 0));
|
|
11
|
+
int16[i] = Math.round(clamped * 0x7fff);
|
|
12
|
+
}
|
|
13
|
+
return new Uint8Array(int16.buffer);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Convert int16 PCM bytes (Uint8Array) to float32 PCM samples.
|
|
17
|
+
*/
|
|
18
|
+
export function int16BytesToFloat32(pcmBytes) {
|
|
19
|
+
const int16View = new Int16Array(pcmBytes.buffer, pcmBytes.byteOffset, Math.floor(pcmBytes.byteLength / 2));
|
|
20
|
+
const float32 = new Float32Array(int16View.length);
|
|
21
|
+
for (let i = 0; i < int16View.length; i++) {
|
|
22
|
+
float32[i] = (int16View[i] ?? 0) / 0x7fff;
|
|
23
|
+
}
|
|
24
|
+
return float32;
|
|
25
|
+
}
|
|
26
|
+
// ─── Raw WebSocket Bridge ──────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Bridge a raw WebSocket connection to a RealtimeTransportSession.
|
|
29
|
+
*
|
|
30
|
+
* This is the primary bridge used by WebSocketAgentServer.startNativeSession().
|
|
31
|
+
* Binary WS messages are passed through directly as Uint8Array (int16 PCM)
|
|
32
|
+
* without intermediate float32 conversion.
|
|
33
|
+
*
|
|
34
|
+
* The WebSocket client and the model client must agree on sample rate and
|
|
35
|
+
* encoding. No resampling is performed in this bridge.
|
|
36
|
+
*/
|
|
37
|
+
export function bridgeWebSocketToRealtimeTransport(ws, options) {
|
|
38
|
+
const audioHandlers = [];
|
|
39
|
+
const closeHandlers = [];
|
|
40
|
+
let closed = false;
|
|
41
|
+
function fireClose() {
|
|
42
|
+
if (closed)
|
|
43
|
+
return;
|
|
44
|
+
closed = true;
|
|
45
|
+
for (const handler of closeHandlers) {
|
|
46
|
+
handler();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Named handlers so they can be removed on close()
|
|
50
|
+
const onMessage = (data, isBinary) => {
|
|
51
|
+
if (!isBinary || closed)
|
|
52
|
+
return;
|
|
53
|
+
const uint8 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
54
|
+
for (const handler of audioHandlers) {
|
|
55
|
+
handler(uint8);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const onClose = () => fireClose();
|
|
59
|
+
const onError = () => fireClose();
|
|
60
|
+
ws.on('message', onMessage);
|
|
61
|
+
ws.on('close', onClose);
|
|
62
|
+
ws.on('error', onError);
|
|
63
|
+
function removeListeners() {
|
|
64
|
+
ws.removeListener('message', onMessage);
|
|
65
|
+
ws.removeListener('close', onClose);
|
|
66
|
+
ws.removeListener('error', onError);
|
|
67
|
+
}
|
|
68
|
+
const interruptHandlers = [];
|
|
69
|
+
return {
|
|
70
|
+
sendAudio(data) {
|
|
71
|
+
if (closed || ws.readyState !== ws.OPEN)
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
ws.send(Buffer.from(data), { binary: true });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// WS already closed
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
onAudio(handler) {
|
|
81
|
+
audioHandlers.push(handler);
|
|
82
|
+
},
|
|
83
|
+
onClose(handler) {
|
|
84
|
+
closeHandlers.push(handler);
|
|
85
|
+
},
|
|
86
|
+
onInterrupted(handler) {
|
|
87
|
+
interruptHandlers.push(handler);
|
|
88
|
+
},
|
|
89
|
+
clearAudioBuffer() {
|
|
90
|
+
// Signal the client to stop playback.
|
|
91
|
+
// Send a JSON control message that the frontend can act on.
|
|
92
|
+
if (closed || ws.readyState !== ws.OPEN)
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
ws.send(JSON.stringify({ type: 'clear_audio' }));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// WS closed
|
|
99
|
+
}
|
|
100
|
+
for (const handler of interruptHandlers) {
|
|
101
|
+
handler();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
close() {
|
|
105
|
+
if (closed)
|
|
106
|
+
return;
|
|
107
|
+
closed = true;
|
|
108
|
+
removeListeners();
|
|
109
|
+
if (ws.readyState === ws.OPEN) {
|
|
110
|
+
ws.close(1000, 'Session ended');
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// ─── Adapter-Based Bridge ──────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Bridge a WebSocketTransportAdapter to a RealtimeTransportSession.
|
|
118
|
+
*
|
|
119
|
+
* This variant works at the adapter level, converting between LiveKit's
|
|
120
|
+
* float32 AudioFrame format and RealtimeRuntime's int16 PCM Uint8Array.
|
|
121
|
+
*
|
|
122
|
+
* Use this when you already have a WebSocketTransportAdapter instance
|
|
123
|
+
* (e.g., from the server's onConnection callback) and want to route it
|
|
124
|
+
* through RealtimeRuntime instead of AriaFlowVoiceSession.
|
|
125
|
+
*
|
|
126
|
+
* Note: This bridge subscribes to the raw WS binary messages directly
|
|
127
|
+
* (same as the simple bridge) because WebSocketAudioInput doesn't expose
|
|
128
|
+
* a frame event. Output is sent as raw binary on the WS, bypassing
|
|
129
|
+
* WebSocketAudioOutput's AudioFrame machinery.
|
|
130
|
+
*/
|
|
131
|
+
export function bridgeAdapterToRealtimeTransport(adapter) {
|
|
132
|
+
const audioHandlers = [];
|
|
133
|
+
const closeHandlers = [];
|
|
134
|
+
let closed = false;
|
|
135
|
+
// Use the public rawSocket getter to access the underlying WebSocket.
|
|
136
|
+
// The adapter's audioInput already listens on the WS for binary messages
|
|
137
|
+
// and converts to AudioFrames. For the realtime bridge, we need the raw
|
|
138
|
+
// int16 PCM bytes. We attach a second binary listener on the same WS.
|
|
139
|
+
const ws = adapter.rawSocket;
|
|
140
|
+
function fireClose() {
|
|
141
|
+
if (closed)
|
|
142
|
+
return;
|
|
143
|
+
closed = true;
|
|
144
|
+
for (const handler of closeHandlers) {
|
|
145
|
+
handler();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const onMessage = (data, isBinary) => {
|
|
149
|
+
if (!isBinary || closed)
|
|
150
|
+
return;
|
|
151
|
+
const uint8 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
152
|
+
for (const handler of audioHandlers) {
|
|
153
|
+
handler(uint8);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const onClose = () => fireClose();
|
|
157
|
+
const onError = () => fireClose();
|
|
158
|
+
ws.on('message', onMessage);
|
|
159
|
+
ws.on('close', onClose);
|
|
160
|
+
ws.on('error', onError);
|
|
161
|
+
function removeListeners() {
|
|
162
|
+
ws.removeListener('message', onMessage);
|
|
163
|
+
ws.removeListener('close', onClose);
|
|
164
|
+
ws.removeListener('error', onError);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
sendAudio(data) {
|
|
168
|
+
if (closed || !adapter.isOpen)
|
|
169
|
+
return;
|
|
170
|
+
try {
|
|
171
|
+
ws.send(data, { binary: true });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// WS closed
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
onAudio(handler) {
|
|
178
|
+
audioHandlers.push(handler);
|
|
179
|
+
},
|
|
180
|
+
onClose(handler) {
|
|
181
|
+
closeHandlers.push(handler);
|
|
182
|
+
},
|
|
183
|
+
close() {
|
|
184
|
+
if (closed)
|
|
185
|
+
return;
|
|
186
|
+
closed = true;
|
|
187
|
+
removeListeners();
|
|
188
|
+
adapter.close().catch(() => { });
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=realtime_bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime_bridge.js","sourceRoot":"","sources":["../src/realtime_bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { SessionManager, type AriaFlowVoiceSession } from '@ariaflow/livekit-plugin';
|
|
2
|
+
import type { voice } from '@livekit/agents';
|
|
3
|
+
import type { RealtimeRuntime, RealtimeAudioClient, RealtimeSessionHandle } from '@ariaflowagents/core/realtime';
|
|
4
|
+
import { WebSocketTransportAdapter } from './transport_adapter.js';
|
|
5
|
+
import type { WebSocketServerOptions } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Options for starting a native audio session through RealtimeRuntime.
|
|
8
|
+
*/
|
|
9
|
+
export interface NativeSessionOptions {
|
|
10
|
+
/** The RealtimeRuntime instance to route audio through. */
|
|
11
|
+
runtime: RealtimeRuntime;
|
|
12
|
+
/** Factory that creates a RealtimeAudioClient for each session. */
|
|
13
|
+
createModelClient: () => RealtimeAudioClient;
|
|
14
|
+
/** Optional session ID (generated if not provided). */
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
/** Optional user ID for session scoping. */
|
|
17
|
+
userId?: string;
|
|
18
|
+
/** Optional agent ID override. */
|
|
19
|
+
agentId?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A WebSocket server that accepts connections and creates agent sessions.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* const server = new WebSocketAgentServer({ port: 8080 });
|
|
26
|
+
*
|
|
27
|
+
* server.onConnection(async (transport) => {
|
|
28
|
+
* const voiceSession = new AriaFlowVoiceSession({
|
|
29
|
+
* runtime: runtime,
|
|
30
|
+
* stt: new GeminiLiveSTT(),
|
|
31
|
+
* tts: new GeminiLiveTTS(),
|
|
32
|
+
* });
|
|
33
|
+
* await server.startSession(transport, voiceSession);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* await server.listen();
|
|
37
|
+
*/
|
|
38
|
+
export declare class WebSocketAgentServer {
|
|
39
|
+
private options;
|
|
40
|
+
private wss;
|
|
41
|
+
private sessionManager;
|
|
42
|
+
private nativeSessions;
|
|
43
|
+
private connectionHandler;
|
|
44
|
+
constructor(options?: WebSocketServerOptions);
|
|
45
|
+
onConnection(handler: (adapter: WebSocketTransportAdapter) => void | Promise<void>): void;
|
|
46
|
+
startSession(adapter: WebSocketTransportAdapter, voiceSession: AriaFlowVoiceSession): Promise<voice.AgentSession>;
|
|
47
|
+
/**
|
|
48
|
+
* Start a native audio session that routes audio directly through
|
|
49
|
+
* RealtimeRuntime instead of the cascaded STT→LLM→TTS pipeline.
|
|
50
|
+
*
|
|
51
|
+
* This uses the WS-to-RealtimeTransport bridge to convert the raw
|
|
52
|
+
* WebSocket connection into a RealtimeTransportSession, then starts
|
|
53
|
+
* a session via RealtimeRuntime.startSession().
|
|
54
|
+
*
|
|
55
|
+
* The model client handles STT, reasoning, and TTS in a single
|
|
56
|
+
* persistent connection (e.g., Gemini Live, OpenAI Realtime).
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* server.onConnection(async (transport) => {
|
|
61
|
+
* await server.startNativeSession(transport, {
|
|
62
|
+
* runtime: realtimeRuntime,
|
|
63
|
+
* createModelClient: () => new GeminiLiveSession({ apiKey, model }),
|
|
64
|
+
* });
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
startNativeSession(adapter: WebSocketTransportAdapter, options: NativeSessionOptions): Promise<RealtimeSessionHandle>;
|
|
69
|
+
listen(): Promise<void>;
|
|
70
|
+
get sessions(): SessionManager;
|
|
71
|
+
close(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,OAAO,EAAE,eAAe,CAAC;IACzB,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;IAC7C,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,oBAAoB;IAQnB,OAAO,CAAC,OAAO;IAP3B,OAAO,CAAC,GAAG,CAAyB;IACpC,OAAO,CAAC,cAAc,CAAwC;IAC9D,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,iBAAiB,CAET;gBAEI,OAAO,GAAE,sBAA2B;IAExD,YAAY,CACV,OAAO,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACpE,IAAI;IAID,YAAY,CAChB,OAAO,EAAE,yBAAyB,EAClC,YAAY,EAAE,oBAAoB,GACjC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;IAI9B;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,kBAAkB,CACtB,OAAO,EAAE,yBAAyB,EAClC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA4C3B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAqJ7B,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAc7B"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { WebSocketServer as WSServer } from 'ws';
|
|
2
|
+
import { SessionManager } from '@ariaflow/livekit-plugin';
|
|
3
|
+
import { WebSocketTransportAdapter } from './transport_adapter.js';
|
|
4
|
+
import { bridgeWebSocketToRealtimeTransport } from './realtime_bridge.js';
|
|
5
|
+
import { parseClientMessage, serializeServerMessage } from './protocol.js';
|
|
6
|
+
/**
|
|
7
|
+
* A WebSocket server that accepts connections and creates agent sessions.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const server = new WebSocketAgentServer({ port: 8080 });
|
|
11
|
+
*
|
|
12
|
+
* server.onConnection(async (transport) => {
|
|
13
|
+
* const voiceSession = new AriaFlowVoiceSession({
|
|
14
|
+
* runtime: runtime,
|
|
15
|
+
* stt: new GeminiLiveSTT(),
|
|
16
|
+
* tts: new GeminiLiveTTS(),
|
|
17
|
+
* });
|
|
18
|
+
* await server.startSession(transport, voiceSession);
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await server.listen();
|
|
22
|
+
*/
|
|
23
|
+
export class WebSocketAgentServer {
|
|
24
|
+
options;
|
|
25
|
+
wss = null;
|
|
26
|
+
sessionManager = new SessionManager();
|
|
27
|
+
nativeSessions = new Map();
|
|
28
|
+
connectionHandler = null;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.options = options;
|
|
31
|
+
}
|
|
32
|
+
onConnection(handler) {
|
|
33
|
+
this.connectionHandler = handler;
|
|
34
|
+
}
|
|
35
|
+
async startSession(adapter, voiceSession) {
|
|
36
|
+
return this.sessionManager.startSession(adapter, voiceSession);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Start a native audio session that routes audio directly through
|
|
40
|
+
* RealtimeRuntime instead of the cascaded STT→LLM→TTS pipeline.
|
|
41
|
+
*
|
|
42
|
+
* This uses the WS-to-RealtimeTransport bridge to convert the raw
|
|
43
|
+
* WebSocket connection into a RealtimeTransportSession, then starts
|
|
44
|
+
* a session via RealtimeRuntime.startSession().
|
|
45
|
+
*
|
|
46
|
+
* The model client handles STT, reasoning, and TTS in a single
|
|
47
|
+
* persistent connection (e.g., Gemini Live, OpenAI Realtime).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* server.onConnection(async (transport) => {
|
|
52
|
+
* await server.startNativeSession(transport, {
|
|
53
|
+
* runtime: realtimeRuntime,
|
|
54
|
+
* createModelClient: () => new GeminiLiveSession({ apiKey, model }),
|
|
55
|
+
* });
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
async startNativeSession(adapter, options) {
|
|
60
|
+
const ws = adapter.rawSocket;
|
|
61
|
+
const realtimeTransport = bridgeWebSocketToRealtimeTransport(ws, {
|
|
62
|
+
sessionId: adapter.id,
|
|
63
|
+
});
|
|
64
|
+
const modelClient = options.createModelClient();
|
|
65
|
+
const handle = await options.runtime.startSession({
|
|
66
|
+
modelClient,
|
|
67
|
+
transport: realtimeTransport,
|
|
68
|
+
sessionId: options.sessionId ?? adapter.id,
|
|
69
|
+
userId: options.userId,
|
|
70
|
+
agentId: options.agentId,
|
|
71
|
+
});
|
|
72
|
+
this.nativeSessions.set(adapter.id, handle);
|
|
73
|
+
// Wire cleanup on WS close
|
|
74
|
+
ws.on('close', () => {
|
|
75
|
+
const nativeHandle = this.nativeSessions.get(adapter.id);
|
|
76
|
+
if (nativeHandle) {
|
|
77
|
+
this.nativeSessions.delete(adapter.id);
|
|
78
|
+
nativeHandle.stop().catch((err) => {
|
|
79
|
+
console.error('[WebSocketServer] native session stop error:', {
|
|
80
|
+
error: err instanceof Error ? err.message : String(err),
|
|
81
|
+
adapterId: adapter.id,
|
|
82
|
+
timestamp: new Date().toISOString(),
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
console.info('[WebSocketServer] native session started', {
|
|
88
|
+
adapterId: adapter.id,
|
|
89
|
+
sessionId: handle.sessionId,
|
|
90
|
+
callId: handle.callId,
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
});
|
|
93
|
+
return handle;
|
|
94
|
+
}
|
|
95
|
+
async listen() {
|
|
96
|
+
const port = this.options.port ?? 8080;
|
|
97
|
+
const host = this.options.host ?? '0.0.0.0';
|
|
98
|
+
this.wss = new WSServer({ port, host });
|
|
99
|
+
this.wss.on('connection', async (ws, req) => {
|
|
100
|
+
const connectedAt = Date.now();
|
|
101
|
+
let binaryMessageCount = 0;
|
|
102
|
+
let firstBinaryAt = null;
|
|
103
|
+
// Authentication
|
|
104
|
+
if (this.options.authenticate) {
|
|
105
|
+
const allowed = await this.options.authenticate(req);
|
|
106
|
+
if (!allowed) {
|
|
107
|
+
ws.close(4001, 'Unauthorized');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const sampleRate = this.options.defaultSampleRate ?? 24000;
|
|
112
|
+
const numChannels = this.options.defaultNumChannels ?? 1;
|
|
113
|
+
const adapter = new WebSocketTransportAdapter(ws, {
|
|
114
|
+
sampleRate,
|
|
115
|
+
numChannels,
|
|
116
|
+
});
|
|
117
|
+
console.info('[WebSocketServer] client connected', {
|
|
118
|
+
adapterId: adapter.id,
|
|
119
|
+
remoteAddress: req.socket.remoteAddress,
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
});
|
|
122
|
+
// Send session started
|
|
123
|
+
ws.send(serializeServerMessage({
|
|
124
|
+
type: 'session_started',
|
|
125
|
+
sessionId: adapter.id,
|
|
126
|
+
config: {
|
|
127
|
+
sampleRate,
|
|
128
|
+
numChannels,
|
|
129
|
+
encoding: 'pcm_s16le',
|
|
130
|
+
},
|
|
131
|
+
}));
|
|
132
|
+
console.info('[WebSocketServer] session_started sent', {
|
|
133
|
+
adapterId: adapter.id,
|
|
134
|
+
sampleRate,
|
|
135
|
+
numChannels,
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
});
|
|
138
|
+
// Handle text and control messages
|
|
139
|
+
ws.on('message', (data, isBinary) => {
|
|
140
|
+
if (isBinary) {
|
|
141
|
+
binaryMessageCount += 1;
|
|
142
|
+
if (!firstBinaryAt) {
|
|
143
|
+
firstBinaryAt = Date.now();
|
|
144
|
+
console.info('[WebSocketServer] first binary audio packet received', {
|
|
145
|
+
adapterId: adapter.id,
|
|
146
|
+
bytes: data.byteLength,
|
|
147
|
+
msSinceConnect: firstBinaryAt - connectedAt,
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else if (binaryMessageCount % 100 === 0) {
|
|
152
|
+
console.debug('[WebSocketServer] binary audio packet progress', {
|
|
153
|
+
adapterId: adapter.id,
|
|
154
|
+
count: binaryMessageCount,
|
|
155
|
+
bytes: data.byteLength,
|
|
156
|
+
timestamp: new Date().toISOString(),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const msg = parseClientMessage(data.toString());
|
|
162
|
+
if (!msg) {
|
|
163
|
+
console.warn('[WebSocketServer] unrecognized client message', {
|
|
164
|
+
adapterId: adapter.id,
|
|
165
|
+
payloadPreview: data.toString().slice(0, 120),
|
|
166
|
+
timestamp: new Date().toISOString(),
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (msg.type === 'end_of_audio') {
|
|
171
|
+
console.info('[WebSocketServer] end_of_audio received', {
|
|
172
|
+
adapterId: adapter.id,
|
|
173
|
+
binaryMessageCount,
|
|
174
|
+
timestamp: new Date().toISOString(),
|
|
175
|
+
});
|
|
176
|
+
adapter.audioInput.endOfAudio();
|
|
177
|
+
}
|
|
178
|
+
else if (msg.type === 'user_text') {
|
|
179
|
+
console.info('[WebSocketServer] user_text received', {
|
|
180
|
+
adapterId: adapter.id,
|
|
181
|
+
chars: msg.text.length,
|
|
182
|
+
timestamp: new Date().toISOString(),
|
|
183
|
+
});
|
|
184
|
+
const session = this.sessionManager.getSession(adapter.id);
|
|
185
|
+
if (session) {
|
|
186
|
+
try {
|
|
187
|
+
const handle = session.generateReply({ userInput: msg.text });
|
|
188
|
+
handle.addDoneCallback(() => {
|
|
189
|
+
// Intentionally noop: callback is used to attach completion lifecycle
|
|
190
|
+
// in one place for future telemetry hooks.
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
console.error('[WebSocketServer] generateReply error:', {
|
|
195
|
+
error: err instanceof Error ? err.message : String(err),
|
|
196
|
+
adapterId: adapter.id,
|
|
197
|
+
timestamp: new Date().toISOString(),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
// Handle disconnect
|
|
204
|
+
ws.on('close', (code, reasonBuffer) => {
|
|
205
|
+
const disconnectedAt = Date.now();
|
|
206
|
+
console.info('[WebSocketServer] client disconnected', {
|
|
207
|
+
adapterId: adapter.id,
|
|
208
|
+
code,
|
|
209
|
+
reason: reasonBuffer.toString(),
|
|
210
|
+
binaryMessageCount,
|
|
211
|
+
sessionDurationMs: disconnectedAt - connectedAt,
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
});
|
|
214
|
+
this.sessionManager.closeSession(adapter.id).catch((err) => {
|
|
215
|
+
console.error('[WebSocketServer] Error closing session:', {
|
|
216
|
+
error: err instanceof Error ? err.message : String(err),
|
|
217
|
+
adapterId: adapter.id,
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
if (this.connectionHandler) {
|
|
223
|
+
await this.connectionHandler(adapter);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
return new Promise((resolve) => {
|
|
227
|
+
this.wss.on('listening', () => {
|
|
228
|
+
resolve();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
get sessions() {
|
|
233
|
+
return this.sessionManager;
|
|
234
|
+
}
|
|
235
|
+
async close() {
|
|
236
|
+
// Stop all native sessions
|
|
237
|
+
const nativeStops = Array.from(this.nativeSessions.values()).map((handle) => handle.stop().catch(() => { }));
|
|
238
|
+
await Promise.allSettled(nativeStops);
|
|
239
|
+
this.nativeSessions.clear();
|
|
240
|
+
// Close all cascaded sessions
|
|
241
|
+
await this.sessionManager.closeAll();
|
|
242
|
+
if (this.wss) {
|
|
243
|
+
this.wss.close();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TextOutput, type TimedString } from '@ariaflow/livekit-plugin';
|
|
2
|
+
import type { WebSocket } from 'ws';
|
|
3
|
+
/**
|
|
4
|
+
* Sends agent transcription text to the WebSocket client as JSON messages.
|
|
5
|
+
*/
|
|
6
|
+
export declare class WebSocketTextOutput extends TextOutput {
|
|
7
|
+
private ws;
|
|
8
|
+
private contextLabel;
|
|
9
|
+
private closed;
|
|
10
|
+
private segmentIndex;
|
|
11
|
+
private segmentStartedAt;
|
|
12
|
+
constructor(ws: WebSocket, contextLabel?: string, nextInChain?: TextOutput);
|
|
13
|
+
captureText(text: string | TimedString): Promise<void>;
|
|
14
|
+
flush(): void;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=text_output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text_output.d.ts","sourceRoot":"","sources":["../src/text_output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAiB,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAGpC;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,UAAU;IAM/C,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,YAAY;IANtB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,gBAAgB,CAAuB;gBAGrC,EAAE,EAAE,SAAS,EACb,YAAY,GAAE,MAAkB,EACxC,WAAW,CAAC,EAAE,UAAU;IAKpB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC5D,KAAK,IAAI,IAAI;IA6BP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { TextOutput, isTimedString } from '@ariaflow/livekit-plugin';
|
|
2
|
+
import { serializeServerMessage } from './protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sends agent transcription text to the WebSocket client as JSON messages.
|
|
5
|
+
*/
|
|
6
|
+
export class WebSocketTextOutput extends TextOutput {
|
|
7
|
+
ws;
|
|
8
|
+
contextLabel;
|
|
9
|
+
closed = false;
|
|
10
|
+
segmentIndex = 0;
|
|
11
|
+
segmentStartedAt = null;
|
|
12
|
+
constructor(ws, contextLabel = 'unknown', nextInChain) {
|
|
13
|
+
super(nextInChain);
|
|
14
|
+
this.ws = ws;
|
|
15
|
+
this.contextLabel = contextLabel;
|
|
16
|
+
}
|
|
17
|
+
async captureText(text) {
|
|
18
|
+
if (this.closed)
|
|
19
|
+
return;
|
|
20
|
+
const textContent = isTimedString(text) ? text.text : text;
|
|
21
|
+
if (textContent.length > 0 && this.segmentStartedAt === null) {
|
|
22
|
+
this.segmentIndex += 1;
|
|
23
|
+
this.segmentStartedAt = Date.now();
|
|
24
|
+
console.info('[WebSocketTextOutput] first chunk', {
|
|
25
|
+
context: this.contextLabel,
|
|
26
|
+
segmentIndex: this.segmentIndex,
|
|
27
|
+
chars: textContent.length,
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const msg = {
|
|
32
|
+
type: 'agent_text',
|
|
33
|
+
text: textContent,
|
|
34
|
+
isFinal: false,
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
this.ws.send(serializeServerMessage(msg));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// WebSocket closed
|
|
41
|
+
}
|
|
42
|
+
if (this.nextInChain) {
|
|
43
|
+
await this.nextInChain.captureText(text);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
flush() {
|
|
47
|
+
if (!this.closed) {
|
|
48
|
+
if (this.segmentStartedAt !== null) {
|
|
49
|
+
console.info('[WebSocketTextOutput] segment flushed', {
|
|
50
|
+
context: this.contextLabel,
|
|
51
|
+
segmentIndex: this.segmentIndex,
|
|
52
|
+
durationMs: Date.now() - this.segmentStartedAt,
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
this.segmentStartedAt = null;
|
|
57
|
+
const msg = {
|
|
58
|
+
type: 'agent_text',
|
|
59
|
+
text: '',
|
|
60
|
+
isFinal: true,
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
this.ws.send(serializeServerMessage(msg));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// WebSocket closed
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (this.nextInChain) {
|
|
70
|
+
this.nextInChain.flush();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async close() {
|
|
74
|
+
this.closed = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=text_output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text_output.js","sourceRoot":"","sources":["../src/text_output.ts"],"names":[],"mappings":"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"}
|