@jchaffin/voicekit 0.2.0 → 0.2.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/adapters/deepgram.js +12 -6
- package/dist/adapters/deepgram.mjs +12 -6
- package/dist/adapters/elevenlabs.js +11 -5
- package/dist/adapters/elevenlabs.mjs +11 -5
- package/dist/adapters/livekit.js +9 -3
- package/dist/adapters/livekit.mjs +9 -3
- package/dist/adapters/openai.js +5 -1
- package/dist/adapters/openai.mjs +5 -1
- package/dist/index.js +17 -9
- package/dist/index.mjs +17 -9
- package/package.json +1 -1
|
@@ -73,12 +73,18 @@ var DeepgramSession = class extends EventEmitter {
|
|
|
73
73
|
if (this.options.model) url.searchParams.set("model", this.options.model);
|
|
74
74
|
if (this.options.language) url.searchParams.set("language", this.options.language);
|
|
75
75
|
this.ws = new WebSocket(url.toString());
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
try {
|
|
77
|
+
await new Promise((resolve, reject) => {
|
|
78
|
+
const ws = this.ws;
|
|
79
|
+
ws.onopen = () => resolve();
|
|
80
|
+
ws.onerror = () => reject(new Error("WebSocket connection failed"));
|
|
81
|
+
ws.onclose = () => this.emit("status_change", "DISCONNECTED");
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.ws?.close();
|
|
85
|
+
this.ws = null;
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
82
88
|
this.ws.onmessage = (event) => {
|
|
83
89
|
try {
|
|
84
90
|
const msg = JSON.parse(event.data);
|
|
@@ -19,12 +19,18 @@ var DeepgramSession = class extends EventEmitter {
|
|
|
19
19
|
if (this.options.model) url.searchParams.set("model", this.options.model);
|
|
20
20
|
if (this.options.language) url.searchParams.set("language", this.options.language);
|
|
21
21
|
this.ws = new WebSocket(url.toString());
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
try {
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
const ws = this.ws;
|
|
25
|
+
ws.onopen = () => resolve();
|
|
26
|
+
ws.onerror = () => reject(new Error("WebSocket connection failed"));
|
|
27
|
+
ws.onclose = () => this.emit("status_change", "DISCONNECTED");
|
|
28
|
+
});
|
|
29
|
+
} catch (err) {
|
|
30
|
+
this.ws?.close();
|
|
31
|
+
this.ws = null;
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
28
34
|
this.ws.onmessage = (event) => {
|
|
29
35
|
try {
|
|
30
36
|
const msg = JSON.parse(event.data);
|
|
@@ -73,11 +73,17 @@ var ElevenLabsSession = class extends EventEmitter {
|
|
|
73
73
|
async connect(config) {
|
|
74
74
|
const wsUrl = config.authToken?.startsWith("wss://") ? config.authToken : `${ELEVENLABS_WS_BASE}?agent_id=${this.agentId}`;
|
|
75
75
|
this.ws = new WebSocket(wsUrl);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
try {
|
|
77
|
+
await new Promise((resolve, reject) => {
|
|
78
|
+
const ws = this.ws;
|
|
79
|
+
ws.onopen = () => resolve();
|
|
80
|
+
ws.onerror = () => reject(new Error("ElevenLabs WebSocket connection failed"));
|
|
81
|
+
});
|
|
82
|
+
} catch (err) {
|
|
83
|
+
this.ws?.close();
|
|
84
|
+
this.ws = null;
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
81
87
|
this.ws.onmessage = (event) => {
|
|
82
88
|
try {
|
|
83
89
|
const msg = JSON.parse(event.data);
|
|
@@ -19,11 +19,17 @@ var ElevenLabsSession = class extends EventEmitter {
|
|
|
19
19
|
async connect(config) {
|
|
20
20
|
const wsUrl = config.authToken?.startsWith("wss://") ? config.authToken : `${ELEVENLABS_WS_BASE}?agent_id=${this.agentId}`;
|
|
21
21
|
this.ws = new WebSocket(wsUrl);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
try {
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
const ws = this.ws;
|
|
25
|
+
ws.onopen = () => resolve();
|
|
26
|
+
ws.onerror = () => reject(new Error("ElevenLabs WebSocket connection failed"));
|
|
27
|
+
});
|
|
28
|
+
} catch (err) {
|
|
29
|
+
this.ws?.close();
|
|
30
|
+
this.ws = null;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
27
33
|
this.ws.onmessage = (event) => {
|
|
28
34
|
try {
|
|
29
35
|
const msg = JSON.parse(event.data);
|
package/dist/adapters/livekit.js
CHANGED
|
@@ -106,9 +106,15 @@ var LiveKitSession = class extends EventEmitter {
|
|
|
106
106
|
this.room.on(RoomEvent.Reconnected, () => {
|
|
107
107
|
this.emit("status_change", "CONNECTED");
|
|
108
108
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
try {
|
|
110
|
+
await this.room.connect(this.serverUrl, config.authToken);
|
|
111
|
+
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
112
|
+
this.emit("status_change", "CONNECTED");
|
|
113
|
+
} catch (err) {
|
|
114
|
+
await this.room.disconnect();
|
|
115
|
+
this.room = null;
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
112
118
|
}
|
|
113
119
|
async disconnect() {
|
|
114
120
|
if (this.room) {
|
|
@@ -42,9 +42,15 @@ var LiveKitSession = class extends EventEmitter {
|
|
|
42
42
|
this.room.on(RoomEvent.Reconnected, () => {
|
|
43
43
|
this.emit("status_change", "CONNECTED");
|
|
44
44
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
try {
|
|
46
|
+
await this.room.connect(this.serverUrl, config.authToken);
|
|
47
|
+
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
48
|
+
this.emit("status_change", "CONNECTED");
|
|
49
|
+
} catch (err) {
|
|
50
|
+
await this.room.disconnect();
|
|
51
|
+
this.room = null;
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
async disconnect() {
|
|
50
56
|
if (this.room) {
|
package/dist/adapters/openai.js
CHANGED
|
@@ -152,12 +152,16 @@ var OpenAISession = class extends EventEmitter {
|
|
|
152
152
|
await new Promise((resolve) => {
|
|
153
153
|
const onDone = (event) => {
|
|
154
154
|
if (event.type === "response.done" || event.type === "response.cancelled") {
|
|
155
|
+
clearTimeout(timeoutId);
|
|
155
156
|
this.off("raw_event", onDone);
|
|
156
157
|
resolve();
|
|
157
158
|
}
|
|
158
159
|
};
|
|
159
160
|
this.on("raw_event", onDone);
|
|
160
|
-
setTimeout(
|
|
161
|
+
const timeoutId = setTimeout(() => {
|
|
162
|
+
this.off("raw_event", onDone);
|
|
163
|
+
resolve();
|
|
164
|
+
}, 1500);
|
|
161
165
|
});
|
|
162
166
|
}
|
|
163
167
|
this.session.sendMessage(text);
|
package/dist/adapters/openai.mjs
CHANGED
|
@@ -96,12 +96,16 @@ var OpenAISession = class extends EventEmitter {
|
|
|
96
96
|
await new Promise((resolve) => {
|
|
97
97
|
const onDone = (event) => {
|
|
98
98
|
if (event.type === "response.done" || event.type === "response.cancelled") {
|
|
99
|
+
clearTimeout(timeoutId);
|
|
99
100
|
this.off("raw_event", onDone);
|
|
100
101
|
resolve();
|
|
101
102
|
}
|
|
102
103
|
};
|
|
103
104
|
this.on("raw_event", onDone);
|
|
104
|
-
setTimeout(
|
|
105
|
+
const timeoutId = setTimeout(() => {
|
|
106
|
+
this.off("raw_event", onDone);
|
|
107
|
+
resolve();
|
|
108
|
+
}, 1500);
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
this.session.sendMessage(text);
|
package/dist/index.js
CHANGED
|
@@ -217,6 +217,7 @@ function VoiceProvider({
|
|
|
217
217
|
}, 500);
|
|
218
218
|
} catch (error) {
|
|
219
219
|
console.error("VoiceKit connection failed:", error);
|
|
220
|
+
sessionRef.current = null;
|
|
220
221
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
221
222
|
updateStatus("DISCONNECTED");
|
|
222
223
|
}
|
|
@@ -823,6 +824,7 @@ function useAudioRecorder() {
|
|
|
823
824
|
if (mediaRecorderRef.current?.state === "recording") {
|
|
824
825
|
return;
|
|
825
826
|
}
|
|
827
|
+
recordedChunksRef.current = [];
|
|
826
828
|
try {
|
|
827
829
|
const mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
|
|
828
830
|
mediaRecorder.ondataavailable = (event) => {
|
|
@@ -1219,6 +1221,7 @@ function useSessionHistory() {
|
|
|
1219
1221
|
const itemId = item.item_id;
|
|
1220
1222
|
if (interruptedItemsRef.current.has(itemId)) return;
|
|
1221
1223
|
if (itemId) {
|
|
1224
|
+
const displayedText = displayedTextRef.current.get(itemId) ?? accumulatedTextRef.current.get(itemId);
|
|
1222
1225
|
const timer = deltaTimerRef.current.get(itemId);
|
|
1223
1226
|
if (timer) clearTimeout(timer);
|
|
1224
1227
|
deltaTimerRef.current.delete(itemId);
|
|
@@ -1227,14 +1230,13 @@ function useSessionHistory() {
|
|
|
1227
1230
|
displayedTextRef.current.delete(itemId);
|
|
1228
1231
|
accumulatedTextRef.current.delete(itemId);
|
|
1229
1232
|
totalAudioDurationRef.current.delete(itemId);
|
|
1230
|
-
const displayedText = displayedTextRef.current.get(itemId);
|
|
1231
1233
|
const finalText = displayedText || item.transcript || "";
|
|
1232
1234
|
const stripped = finalText.replace(/[\s.…]+/g, "");
|
|
1233
1235
|
if (stripped.length > 0) {
|
|
1234
1236
|
updateTranscriptMessage(itemId, finalText, false);
|
|
1235
1237
|
}
|
|
1236
1238
|
updateTranscriptItem(itemId, { status: "DONE" });
|
|
1237
|
-
const transcriptItem =
|
|
1239
|
+
const transcriptItem = transcriptItemsRef.current.find((i) => i.itemId === itemId);
|
|
1238
1240
|
if (transcriptItem?.guardrailResult?.status === "IN_PROGRESS") {
|
|
1239
1241
|
updateTranscriptItem(itemId, {
|
|
1240
1242
|
guardrailResult: {
|
|
@@ -1259,7 +1261,8 @@ function useSessionHistory() {
|
|
|
1259
1261
|
const category = moderation.moderationCategory ?? "NONE";
|
|
1260
1262
|
const rationale = moderation.moderationRationale ?? "";
|
|
1261
1263
|
const offendingText = moderation.testText;
|
|
1262
|
-
|
|
1264
|
+
const assistantItemId = lastAssistant.itemId ?? lastAssistant.id;
|
|
1265
|
+
updateTranscriptItem(assistantItemId, {
|
|
1263
1266
|
guardrailResult: {
|
|
1264
1267
|
status: "DONE",
|
|
1265
1268
|
category,
|
|
@@ -1326,13 +1329,15 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1326
1329
|
const [status, setStatus] = (0, import_react8.useState)("DISCONNECTED");
|
|
1327
1330
|
const { logClientEvent, logServerEvent } = useEvent();
|
|
1328
1331
|
const codecParamRef = (0, import_react8.useRef)("opus");
|
|
1332
|
+
const callbacksRef = (0, import_react8.useRef)(callbacks);
|
|
1333
|
+
callbacksRef.current = callbacks;
|
|
1329
1334
|
const updateStatus = (0, import_react8.useCallback)(
|
|
1330
1335
|
(s) => {
|
|
1331
1336
|
setStatus(s);
|
|
1332
|
-
|
|
1337
|
+
callbacksRef.current.onConnectionChange?.(s);
|
|
1333
1338
|
logClientEvent({}, s);
|
|
1334
1339
|
},
|
|
1335
|
-
[
|
|
1340
|
+
[logClientEvent]
|
|
1336
1341
|
);
|
|
1337
1342
|
const historyHandlers = useSessionHistory().current;
|
|
1338
1343
|
const interruptedRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
|
|
@@ -1392,7 +1397,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1392
1397
|
);
|
|
1393
1398
|
});
|
|
1394
1399
|
session.on("agent_handoff", (_from, to) => {
|
|
1395
|
-
|
|
1400
|
+
callbacksRef.current.onAgentHandoff?.(to);
|
|
1396
1401
|
});
|
|
1397
1402
|
session.on("guardrail_tripped", (info) => {
|
|
1398
1403
|
historyHandlers.handleGuardrailTripped(
|
|
@@ -1429,7 +1434,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1429
1434
|
console.error("Session error:", msg);
|
|
1430
1435
|
logServerEvent({ type: "error", message: msg });
|
|
1431
1436
|
});
|
|
1432
|
-
}, [
|
|
1437
|
+
}, [historyHandlers, logServerEvent]);
|
|
1433
1438
|
const connect = (0, import_react8.useCallback)(
|
|
1434
1439
|
async ({
|
|
1435
1440
|
getEphemeralKey,
|
|
@@ -1445,6 +1450,9 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1445
1450
|
"useRealtimeSession: `adapter` is required in ConnectOptions. Pass an adapter like openai() from @jchaffin/voicekit/openai."
|
|
1446
1451
|
);
|
|
1447
1452
|
}
|
|
1453
|
+
if (!initialAgents?.length) {
|
|
1454
|
+
throw new Error("useRealtimeSession: `initialAgents` must be a non-empty array.");
|
|
1455
|
+
}
|
|
1448
1456
|
updateStatus("CONNECTING");
|
|
1449
1457
|
const ek = await getEphemeralKey();
|
|
1450
1458
|
const rootAgent = initialAgents[0];
|
|
@@ -1667,8 +1675,8 @@ function SuggestionProvider({
|
|
|
1667
1675
|
(0, import_react9.useEffect)(() => {
|
|
1668
1676
|
const handler = (e) => {
|
|
1669
1677
|
const detail = e.detail;
|
|
1670
|
-
if (detail
|
|
1671
|
-
setSuggestionsState(detail.group);
|
|
1678
|
+
if (detail) {
|
|
1679
|
+
setSuggestionsState(detail.group ?? null);
|
|
1672
1680
|
}
|
|
1673
1681
|
};
|
|
1674
1682
|
window.addEventListener(SUGGESTION_EVENT, handler);
|
package/dist/index.mjs
CHANGED
|
@@ -159,6 +159,7 @@ function VoiceProvider({
|
|
|
159
159
|
}, 500);
|
|
160
160
|
} catch (error) {
|
|
161
161
|
console.error("VoiceKit connection failed:", error);
|
|
162
|
+
sessionRef.current = null;
|
|
162
163
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
163
164
|
updateStatus("DISCONNECTED");
|
|
164
165
|
}
|
|
@@ -604,6 +605,7 @@ function useAudioRecorder() {
|
|
|
604
605
|
if (mediaRecorderRef.current?.state === "recording") {
|
|
605
606
|
return;
|
|
606
607
|
}
|
|
608
|
+
recordedChunksRef.current = [];
|
|
607
609
|
try {
|
|
608
610
|
const mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
|
|
609
611
|
mediaRecorder.ondataavailable = (event) => {
|
|
@@ -1005,6 +1007,7 @@ function useSessionHistory() {
|
|
|
1005
1007
|
const itemId = item.item_id;
|
|
1006
1008
|
if (interruptedItemsRef.current.has(itemId)) return;
|
|
1007
1009
|
if (itemId) {
|
|
1010
|
+
const displayedText = displayedTextRef.current.get(itemId) ?? accumulatedTextRef.current.get(itemId);
|
|
1008
1011
|
const timer = deltaTimerRef.current.get(itemId);
|
|
1009
1012
|
if (timer) clearTimeout(timer);
|
|
1010
1013
|
deltaTimerRef.current.delete(itemId);
|
|
@@ -1013,14 +1016,13 @@ function useSessionHistory() {
|
|
|
1013
1016
|
displayedTextRef.current.delete(itemId);
|
|
1014
1017
|
accumulatedTextRef.current.delete(itemId);
|
|
1015
1018
|
totalAudioDurationRef.current.delete(itemId);
|
|
1016
|
-
const displayedText = displayedTextRef.current.get(itemId);
|
|
1017
1019
|
const finalText = displayedText || item.transcript || "";
|
|
1018
1020
|
const stripped = finalText.replace(/[\s.…]+/g, "");
|
|
1019
1021
|
if (stripped.length > 0) {
|
|
1020
1022
|
updateTranscriptMessage(itemId, finalText, false);
|
|
1021
1023
|
}
|
|
1022
1024
|
updateTranscriptItem(itemId, { status: "DONE" });
|
|
1023
|
-
const transcriptItem =
|
|
1025
|
+
const transcriptItem = transcriptItemsRef.current.find((i) => i.itemId === itemId);
|
|
1024
1026
|
if (transcriptItem?.guardrailResult?.status === "IN_PROGRESS") {
|
|
1025
1027
|
updateTranscriptItem(itemId, {
|
|
1026
1028
|
guardrailResult: {
|
|
@@ -1045,7 +1047,8 @@ function useSessionHistory() {
|
|
|
1045
1047
|
const category = moderation.moderationCategory ?? "NONE";
|
|
1046
1048
|
const rationale = moderation.moderationRationale ?? "";
|
|
1047
1049
|
const offendingText = moderation.testText;
|
|
1048
|
-
|
|
1050
|
+
const assistantItemId = lastAssistant.itemId ?? lastAssistant.id;
|
|
1051
|
+
updateTranscriptItem(assistantItemId, {
|
|
1049
1052
|
guardrailResult: {
|
|
1050
1053
|
status: "DONE",
|
|
1051
1054
|
category,
|
|
@@ -1112,13 +1115,15 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1112
1115
|
const [status, setStatus] = useState5("DISCONNECTED");
|
|
1113
1116
|
const { logClientEvent, logServerEvent } = useEvent();
|
|
1114
1117
|
const codecParamRef = useRef6("opus");
|
|
1118
|
+
const callbacksRef = useRef6(callbacks);
|
|
1119
|
+
callbacksRef.current = callbacks;
|
|
1115
1120
|
const updateStatus = useCallback6(
|
|
1116
1121
|
(s) => {
|
|
1117
1122
|
setStatus(s);
|
|
1118
|
-
|
|
1123
|
+
callbacksRef.current.onConnectionChange?.(s);
|
|
1119
1124
|
logClientEvent({}, s);
|
|
1120
1125
|
},
|
|
1121
|
-
[
|
|
1126
|
+
[logClientEvent]
|
|
1122
1127
|
);
|
|
1123
1128
|
const historyHandlers = useSessionHistory().current;
|
|
1124
1129
|
const interruptedRef = useRef6(/* @__PURE__ */ new Set());
|
|
@@ -1178,7 +1183,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1178
1183
|
);
|
|
1179
1184
|
});
|
|
1180
1185
|
session.on("agent_handoff", (_from, to) => {
|
|
1181
|
-
|
|
1186
|
+
callbacksRef.current.onAgentHandoff?.(to);
|
|
1182
1187
|
});
|
|
1183
1188
|
session.on("guardrail_tripped", (info) => {
|
|
1184
1189
|
historyHandlers.handleGuardrailTripped(
|
|
@@ -1215,7 +1220,7 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1215
1220
|
console.error("Session error:", msg);
|
|
1216
1221
|
logServerEvent({ type: "error", message: msg });
|
|
1217
1222
|
});
|
|
1218
|
-
}, [
|
|
1223
|
+
}, [historyHandlers, logServerEvent]);
|
|
1219
1224
|
const connect = useCallback6(
|
|
1220
1225
|
async ({
|
|
1221
1226
|
getEphemeralKey,
|
|
@@ -1231,6 +1236,9 @@ function useRealtimeSession(callbacks = {}) {
|
|
|
1231
1236
|
"useRealtimeSession: `adapter` is required in ConnectOptions. Pass an adapter like openai() from @jchaffin/voicekit/openai."
|
|
1232
1237
|
);
|
|
1233
1238
|
}
|
|
1239
|
+
if (!initialAgents?.length) {
|
|
1240
|
+
throw new Error("useRealtimeSession: `initialAgents` must be a non-empty array.");
|
|
1241
|
+
}
|
|
1234
1242
|
updateStatus("CONNECTING");
|
|
1235
1243
|
const ek = await getEphemeralKey();
|
|
1236
1244
|
const rootAgent = initialAgents[0];
|
|
@@ -1453,8 +1461,8 @@ function SuggestionProvider({
|
|
|
1453
1461
|
useEffect6(() => {
|
|
1454
1462
|
const handler = (e) => {
|
|
1455
1463
|
const detail = e.detail;
|
|
1456
|
-
if (detail
|
|
1457
|
-
setSuggestionsState(detail.group);
|
|
1464
|
+
if (detail) {
|
|
1465
|
+
setSuggestionsState(detail.group ?? null);
|
|
1458
1466
|
}
|
|
1459
1467
|
};
|
|
1460
1468
|
window.addEventListener(SUGGESTION_EVENT, handler);
|