@nuancelabs/js-sdk 0.11.0 → 0.12.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/index.d.ts +205 -0
- package/index.js +154 -14
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -7,6 +7,85 @@ export declare class ActiveSessionError extends Error {
|
|
|
7
7
|
constructor();
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The turn's first audio. `atMs` is when it plays, which can be after the
|
|
12
|
+
* event arrives.
|
|
13
|
+
*/
|
|
14
|
+
export declare interface AgentSpeechStartedEvent extends SessionEventBase {
|
|
15
|
+
type: "agent.speech.started";
|
|
16
|
+
/** The turn, as {@link AgentTurnStartedEvent.turnId} names it. */
|
|
17
|
+
turnId: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The agent's words, in whole words, as they are committed to the
|
|
22
|
+
* conversation. They arrive ahead of their audio: `atMs` is when the first
|
|
23
|
+
* of them plays. When the agent's turn is interrupted, words whose `atMs` is
|
|
24
|
+
* later than the {@link AgentTurnEndedEvent}'s were never heard.
|
|
25
|
+
*/
|
|
26
|
+
export declare interface AgentTextEvent extends SessionEventBase {
|
|
27
|
+
type: "agent.text";
|
|
28
|
+
/**
|
|
29
|
+
* The turn the words belong to, as {@link AgentTurnStartedEvent.turnId}
|
|
30
|
+
* names it; null for words outside any turn.
|
|
31
|
+
*/
|
|
32
|
+
turnId: string | null;
|
|
33
|
+
/** The words, with the spacing to join them to the turn's other words. */
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The agent's turn is over. `atMs` is when the turn's audio ends: for an
|
|
39
|
+
* interrupted turn, where the agent was cut off.
|
|
40
|
+
*/
|
|
41
|
+
export declare interface AgentTurnEndedEvent extends SessionEventBase {
|
|
42
|
+
type: "agent.turn.ended";
|
|
43
|
+
/** The turn, as {@link AgentTurnStartedEvent.turnId} names it. */
|
|
44
|
+
turnId: string;
|
|
45
|
+
/**
|
|
46
|
+
* `"completed"`: the agent said all of it. `"interrupted"`: the user cut
|
|
47
|
+
* in. `"stopped"`: a command stopped the agent. `"abandoned"`: the turn
|
|
48
|
+
* gave up, as when nothing of it was heard for a while or it had nothing
|
|
49
|
+
* to say; `spoken` is then the runtime's estimate of what played. A newer
|
|
50
|
+
* runtime may send other values.
|
|
51
|
+
*/
|
|
52
|
+
reason: "completed" | "interrupted" | "stopped" | "abandoned" | (string & {});
|
|
53
|
+
/**
|
|
54
|
+
* Every word the turn committed, as its {@link AgentTextEvent}s carried
|
|
55
|
+
* them. After an interruption it includes words that were cut before they
|
|
56
|
+
* played; their `agent.text` events have an `atMs` later than this
|
|
57
|
+
* event's.
|
|
58
|
+
*/
|
|
59
|
+
spoken: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The agent took the floor, before its first audio. Its words follow as
|
|
64
|
+
* {@link AgentTextEvent}s and the turn closes with an
|
|
65
|
+
* {@link AgentTurnEndedEvent}, both with the same `turnId`. `atMs` is where
|
|
66
|
+
* on the timeline the agent took it.
|
|
67
|
+
*/
|
|
68
|
+
export declare interface AgentTurnStartedEvent extends SessionEventBase {
|
|
69
|
+
type: "agent.turn.started";
|
|
70
|
+
/** The turn's id, unique within the session. */
|
|
71
|
+
turnId: string;
|
|
72
|
+
/**
|
|
73
|
+
* What opened the turn. `"user_done"`: the agent judged the user had
|
|
74
|
+
* finished. `"silence"`: the user went quiet long enough. `"check_in"`:
|
|
75
|
+
* both were quiet and the agent spoke first, as in its opening greeting.
|
|
76
|
+
* `"command"`: a command asked for the turn. `"tool"`: a tool's result
|
|
77
|
+
* reached the agent while both were quiet. A newer runtime may send other
|
|
78
|
+
* values.
|
|
79
|
+
*/
|
|
80
|
+
openedBy: "user_done" | "silence" | "check_in" | "command" | "tool" | (string & {});
|
|
81
|
+
/**
|
|
82
|
+
* `"reply"`: the agent wrote the turn's words itself. `"say"`: the agent
|
|
83
|
+
* says a line it was given, word for word. A newer runtime may send other
|
|
84
|
+
* values.
|
|
85
|
+
*/
|
|
86
|
+
source: "reply" | "say" | (string & {});
|
|
87
|
+
}
|
|
88
|
+
|
|
10
89
|
/**
|
|
11
90
|
* An audio track that can be attached to or detached from an audio element.
|
|
12
91
|
*/
|
|
@@ -119,6 +198,25 @@ export declare interface Conversation {
|
|
|
119
198
|
* @returns A function that removes the event handler.
|
|
120
199
|
*/
|
|
121
200
|
onChange(handler: (event: ConversationEvent) => void): () => void;
|
|
201
|
+
/**
|
|
202
|
+
* Subscribes to one type of the session's events: what the user and the
|
|
203
|
+
* agent say, their turns, and who has the floor.
|
|
204
|
+
*
|
|
205
|
+
* Each event arrives once, in the session's order (its `seq`). Events the
|
|
206
|
+
* connection missed while it was down are asked of the runtime again and
|
|
207
|
+
* delivered in their place. Subscribe before `connect()` to see the
|
|
208
|
+
* session's first events.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const stop = conversation.on("agent.text", (event) => {
|
|
213
|
+
* transcript.textContent += event.text;
|
|
214
|
+
* });
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @returns A function that removes the handler.
|
|
218
|
+
*/
|
|
219
|
+
on<K extends keyof SessionEventMap>(type: K, handler: (event: SessionEventMap[K]) => void): () => void;
|
|
122
220
|
}
|
|
123
221
|
|
|
124
222
|
|
|
@@ -376,6 +474,19 @@ export declare interface CreateNuanceConversationOptions {
|
|
|
376
474
|
}
|
|
377
475
|
|
|
378
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Who has the floor changed. `atMs` is when the change plays.
|
|
479
|
+
*/
|
|
480
|
+
export declare interface FloorEvent extends SessionEventBase {
|
|
481
|
+
type: "floor";
|
|
482
|
+
/**
|
|
483
|
+
* `"speaking"`: the agent has the floor. `"listening"`: the user has it
|
|
484
|
+
* and the agent listens. `"waiting"`: nobody has it; the agent waits for
|
|
485
|
+
* the user. A newer runtime may send other values.
|
|
486
|
+
*/
|
|
487
|
+
state: "waiting" | "listening" | "speaking" | (string & {});
|
|
488
|
+
}
|
|
489
|
+
|
|
379
490
|
/**
|
|
380
491
|
* Raised by {@link Conversation.connect} when the server refuses a guest's new
|
|
381
492
|
* session because a guest limit is used up. `reason` says which: `used` (this
|
|
@@ -473,6 +584,47 @@ export declare class PolicyCurfewError extends Error {
|
|
|
473
584
|
*/
|
|
474
585
|
export declare type SessionEndReason = "time-limit" | "ended" | "policy-violation" | "refused" | "connection-lost";
|
|
475
586
|
|
|
587
|
+
/** Any one of the session's events. */
|
|
588
|
+
export declare type SessionEvent = SessionEventMap[keyof SessionEventMap];
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* What every session event carries: its place in the session's order and on
|
|
592
|
+
* the session's timeline.
|
|
593
|
+
*/
|
|
594
|
+
export declare interface SessionEventBase {
|
|
595
|
+
/**
|
|
596
|
+
* The event's number in the session's sequence: from 1, one more for each
|
|
597
|
+
* event the runtime sends, of every kind, so the events one handler sees
|
|
598
|
+
* skip numbers. The SDK delivers events in this order, each once. 0 for an
|
|
599
|
+
* event the runtime did not number.
|
|
600
|
+
*/
|
|
601
|
+
seq: number;
|
|
602
|
+
/**
|
|
603
|
+
* Where the event falls on the session's timeline, in milliseconds. The
|
|
604
|
+
* timeline counts the user's audio from the start of the session. A user
|
|
605
|
+
* event is stamped on the user's audio, as where the user said it; an
|
|
606
|
+
* agent event where the agent's audio for it plays, which can be ahead of
|
|
607
|
+
* when the event arrives. Every event of a session is on this one timeline, so
|
|
608
|
+
* their `atMs` compare. Null when the runtime gives no time.
|
|
609
|
+
*/
|
|
610
|
+
atMs: number | null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* The session's events by their `type`, for {@link Conversation.on}.
|
|
615
|
+
*/
|
|
616
|
+
export declare interface SessionEventMap {
|
|
617
|
+
"user.transcript": UserTranscriptEvent;
|
|
618
|
+
"user.speech.started": UserSpeechStartedEvent;
|
|
619
|
+
"user.speech.ended": UserSpeechEndedEvent;
|
|
620
|
+
"user.turn.ended": UserTurnEndedEvent;
|
|
621
|
+
"agent.turn.started": AgentTurnStartedEvent;
|
|
622
|
+
"agent.speech.started": AgentSpeechStartedEvent;
|
|
623
|
+
"agent.text": AgentTextEvent;
|
|
624
|
+
"agent.turn.ended": AgentTurnEndedEvent;
|
|
625
|
+
floor: FloorEvent;
|
|
626
|
+
}
|
|
627
|
+
|
|
476
628
|
|
|
477
629
|
|
|
478
630
|
/** Who the avatar is in a conversation. */
|
|
@@ -561,6 +713,59 @@ export declare interface UserParticipant {
|
|
|
561
713
|
readonly isSpeaking: boolean;
|
|
562
714
|
}
|
|
563
715
|
|
|
716
|
+
/**
|
|
717
|
+
* The user's voice went off, as the same detection reads it. `atMs` is where
|
|
718
|
+
* on the timeline it went off.
|
|
719
|
+
*/
|
|
720
|
+
export declare interface UserSpeechEndedEvent extends SessionEventBase {
|
|
721
|
+
type: "user.speech.ended";
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* The user's voice came on, as the runtime's voice-activity detection reads
|
|
726
|
+
* the microphone. `atMs` is where on the timeline it came on.
|
|
727
|
+
*/
|
|
728
|
+
export declare interface UserSpeechStartedEvent extends SessionEventBase {
|
|
729
|
+
type: "user.speech.started";
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* The user's words, live: one recognized segment at a time, as the runtime
|
|
734
|
+
* hears them. A later {@link UserTurnEndedEvent} may read the same speech
|
|
735
|
+
* differently; its `text` is the record of what the agent answered.
|
|
736
|
+
*/
|
|
737
|
+
export declare interface UserTranscriptEvent extends SessionEventBase {
|
|
738
|
+
type: "user.transcript";
|
|
739
|
+
/** The segment's words. */
|
|
740
|
+
text: string;
|
|
741
|
+
/**
|
|
742
|
+
* `"asr"`: recognized from the user's microphone. `"text"`: a line given
|
|
743
|
+
* as the user's turn in text rather than spoken. `"forced"`: a transcript
|
|
744
|
+
* supplied for the user's audio in place of recognition. A newer runtime
|
|
745
|
+
* may send other values.
|
|
746
|
+
*/
|
|
747
|
+
source: "asr" | "forced" | "text" | (string & {});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* The agent is taking the turn to answer the user; sent each time it does.
|
|
752
|
+
* `atMs` is the point in the user's audio where the agent took the turn: the
|
|
753
|
+
* same moment as the turn's {@link AgentTurnStartedEvent}, stamped on the
|
|
754
|
+
* user's side of the timeline, so it is earlier than that event's `atMs` by
|
|
755
|
+
* the delay before the agent's audio plays.
|
|
756
|
+
*/
|
|
757
|
+
export declare interface UserTurnEndedEvent extends SessionEventBase {
|
|
758
|
+
type: "user.turn.ended";
|
|
759
|
+
/**
|
|
760
|
+
* The user's words since the agent last spoke, as the agent read them: a
|
|
761
|
+
* second, more careful hearing's words in place of the live
|
|
762
|
+
* {@link UserTranscriptEvent}s' where the runtime has one. When an agent
|
|
763
|
+
* turn ends before it said a word, the next `user.turn.ended` covers the
|
|
764
|
+
* same words again.
|
|
765
|
+
*/
|
|
766
|
+
text: string;
|
|
767
|
+
}
|
|
768
|
+
|
|
564
769
|
/**
|
|
565
770
|
* Capture constraints for the user's published camera track.
|
|
566
771
|
*
|
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@ function isRecord(value) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// src/core/sdk-version.ts
|
|
7
|
-
var SDK_HEADER = `js/${"0.
|
|
7
|
+
var SDK_HEADER = `js/${"0.12.0"}`;
|
|
8
8
|
|
|
9
9
|
// src/utils.ts
|
|
10
10
|
function normalizeHost(host) {
|
|
@@ -185,6 +185,15 @@ class PolicyCurfewError extends Error {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// src/core/session-events.ts
|
|
189
|
+
function sessionEventsOf(type, handler) {
|
|
190
|
+
return (change) => {
|
|
191
|
+
if (change.type === "session-event" && change.event.type === type) {
|
|
192
|
+
handler(change.event);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
188
197
|
// src/core/ticker.ts
|
|
189
198
|
var WORKER_SOURCE = "onmessage = (e) => { setInterval(() => postMessage(0), e.data); };";
|
|
190
199
|
function backgroundTicker(host) {
|
|
@@ -1059,6 +1068,69 @@ async function avatarFramesDecoded(room) {
|
|
|
1059
1068
|
return null;
|
|
1060
1069
|
}
|
|
1061
1070
|
|
|
1071
|
+
// src/providers/livekit/sequence.ts
|
|
1072
|
+
class EventSequence {
|
|
1073
|
+
deliver;
|
|
1074
|
+
replay;
|
|
1075
|
+
last = 0;
|
|
1076
|
+
held = new Map;
|
|
1077
|
+
replaying = false;
|
|
1078
|
+
stopped = false;
|
|
1079
|
+
constructor(deliver, replay) {
|
|
1080
|
+
this.deliver = deliver;
|
|
1081
|
+
this.replay = replay;
|
|
1082
|
+
}
|
|
1083
|
+
repeats(seq) {
|
|
1084
|
+
return seq !== 0 && (seq <= this.last || this.held.has(seq));
|
|
1085
|
+
}
|
|
1086
|
+
accept(seq, item) {
|
|
1087
|
+
if (this.stopped || this.repeats(seq))
|
|
1088
|
+
return;
|
|
1089
|
+
if (seq === 0) {
|
|
1090
|
+
this.deliver(item);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
if (seq !== this.last + 1) {
|
|
1094
|
+
this.held.set(seq, item);
|
|
1095
|
+
this.requestReplay();
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
this.last = seq;
|
|
1099
|
+
this.deliver(item);
|
|
1100
|
+
this.release(false);
|
|
1101
|
+
}
|
|
1102
|
+
catchUp() {
|
|
1103
|
+
if (!this.stopped)
|
|
1104
|
+
this.requestReplay();
|
|
1105
|
+
}
|
|
1106
|
+
stop() {
|
|
1107
|
+
this.stopped = true;
|
|
1108
|
+
this.held.clear();
|
|
1109
|
+
}
|
|
1110
|
+
requestReplay() {
|
|
1111
|
+
if (this.replaying)
|
|
1112
|
+
return;
|
|
1113
|
+
this.replaying = true;
|
|
1114
|
+
this.replay(this.last).catch(() => {
|
|
1115
|
+
return;
|
|
1116
|
+
}).finally(() => {
|
|
1117
|
+
this.replaying = false;
|
|
1118
|
+
this.release(true);
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
release(all) {
|
|
1122
|
+
const pending = [...this.held.keys()].sort((a, b) => a - b);
|
|
1123
|
+
for (const seq of pending) {
|
|
1124
|
+
if (this.stopped || !all && seq !== this.last + 1)
|
|
1125
|
+
return;
|
|
1126
|
+
const item = this.held.get(seq);
|
|
1127
|
+
this.held.delete(seq);
|
|
1128
|
+
this.last = seq;
|
|
1129
|
+
this.deliver(item);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1062
1134
|
// src/providers/livekit/signals.ts
|
|
1063
1135
|
function decode(payload) {
|
|
1064
1136
|
try {
|
|
@@ -1082,6 +1154,10 @@ function parseSessionEvent(payload) {
|
|
|
1082
1154
|
const decoded = decode(payload);
|
|
1083
1155
|
if (!decoded)
|
|
1084
1156
|
return null;
|
|
1157
|
+
const seq = Number.isSafeInteger(decoded.seq) && decoded.seq > 0 ? decoded.seq : 0;
|
|
1158
|
+
return { seq, event: protocolEvent(decoded, seq) };
|
|
1159
|
+
}
|
|
1160
|
+
function protocolEvent(decoded, seq) {
|
|
1085
1161
|
if (decoded.type === "session.output_ready") {
|
|
1086
1162
|
return { type: "output-ready" };
|
|
1087
1163
|
}
|
|
@@ -1095,9 +1171,6 @@ function parseSessionEvent(payload) {
|
|
|
1095
1171
|
}
|
|
1096
1172
|
};
|
|
1097
1173
|
}
|
|
1098
|
-
if (decoded.type === "floor") {
|
|
1099
|
-
return { type: "floor", speaking: decoded.state === "speaking" };
|
|
1100
|
-
}
|
|
1101
1174
|
if (decoded.type === "tool.call" && typeof decoded.call_id === "string" && typeof decoded.name === "string" && isRecord(decoded.arguments)) {
|
|
1102
1175
|
return {
|
|
1103
1176
|
type: "tool-call",
|
|
@@ -1130,7 +1203,44 @@ function parseSessionEvent(payload) {
|
|
|
1130
1203
|
if (decoded.type === "error" && decoded.code === "bad_command") {
|
|
1131
1204
|
return { type: "command-unreadable" };
|
|
1132
1205
|
}
|
|
1133
|
-
|
|
1206
|
+
const event = sessionEvent(decoded, seq);
|
|
1207
|
+
return event ? { type: "session-event", event } : { type: "other" };
|
|
1208
|
+
}
|
|
1209
|
+
var isString = (value) => typeof value === "string";
|
|
1210
|
+
function defaulted(value, absent) {
|
|
1211
|
+
if (value === undefined)
|
|
1212
|
+
return absent;
|
|
1213
|
+
return isString(value) ? value : null;
|
|
1214
|
+
}
|
|
1215
|
+
var READERS = {
|
|
1216
|
+
"user.transcript": (wire) => {
|
|
1217
|
+
const source = defaulted(wire.source, "asr");
|
|
1218
|
+
return isString(wire.text) && source !== null ? { text: wire.text, source } : null;
|
|
1219
|
+
},
|
|
1220
|
+
"user.speech.started": () => ({}),
|
|
1221
|
+
"user.speech.ended": () => ({}),
|
|
1222
|
+
"user.turn.ended": (wire) => isString(wire.text) ? { text: wire.text } : null,
|
|
1223
|
+
"agent.turn.started": (wire) => {
|
|
1224
|
+
const source = defaulted(wire.source, "reply");
|
|
1225
|
+
return isString(wire.turn_id) && isString(wire.opened_by) && source !== null ? { turnId: wire.turn_id, openedBy: wire.opened_by, source } : null;
|
|
1226
|
+
},
|
|
1227
|
+
"agent.speech.started": (wire) => isString(wire.turn_id) ? { turnId: wire.turn_id } : null,
|
|
1228
|
+
"agent.text": (wire) => {
|
|
1229
|
+
const turnId = wire.turn_id ?? null;
|
|
1230
|
+
return (turnId === null || isString(turnId)) && isString(wire.text) ? { turnId, text: wire.text } : null;
|
|
1231
|
+
},
|
|
1232
|
+
"agent.turn.ended": (wire) => isString(wire.turn_id) && isString(wire.reason) && isString(wire.spoken) ? { turnId: wire.turn_id, reason: wire.reason, spoken: wire.spoken } : null,
|
|
1233
|
+
floor: (wire) => isString(wire.state) ? { state: wire.state } : null
|
|
1234
|
+
};
|
|
1235
|
+
function sessionEvent(decoded, seq) {
|
|
1236
|
+
const type = decoded.type;
|
|
1237
|
+
if (!isString(type) || !Object.hasOwn(READERS, type))
|
|
1238
|
+
return null;
|
|
1239
|
+
const fields = READERS[type](decoded);
|
|
1240
|
+
if (fields === null)
|
|
1241
|
+
return null;
|
|
1242
|
+
const atMs = Number.isFinite(decoded.at_ms) ? decoded.at_ms : null;
|
|
1243
|
+
return { type, seq, atMs, ...fields };
|
|
1134
1244
|
}
|
|
1135
1245
|
function parseRuntimeSignal(payload) {
|
|
1136
1246
|
const decoded = decode(payload);
|
|
@@ -1616,6 +1726,7 @@ class LiveKitConversation {
|
|
|
1616
1726
|
refusal = null;
|
|
1617
1727
|
speaksProtocol = false;
|
|
1618
1728
|
avatarSpeaking = false;
|
|
1729
|
+
sequence = this.newSequence();
|
|
1619
1730
|
answered = null;
|
|
1620
1731
|
commands = new Map;
|
|
1621
1732
|
commandCount = 0;
|
|
@@ -1666,6 +1777,8 @@ class LiveKitConversation {
|
|
|
1666
1777
|
this.setState("connecting");
|
|
1667
1778
|
this.sessionEnded = false;
|
|
1668
1779
|
this.speaksProtocol = false;
|
|
1780
|
+
this.sequence.stop();
|
|
1781
|
+
this.sequence = this.newSequence();
|
|
1669
1782
|
this.readyOwed = false;
|
|
1670
1783
|
this.everConnected = false;
|
|
1671
1784
|
if (!this.eventsRegistered) {
|
|
@@ -1739,6 +1852,7 @@ class LiveKitConversation {
|
|
|
1739
1852
|
this._user?.releaseDevices();
|
|
1740
1853
|
await this.flushStats();
|
|
1741
1854
|
await this.sendSessionEnd();
|
|
1855
|
+
this.sequence.stop();
|
|
1742
1856
|
await this.room.disconnect();
|
|
1743
1857
|
this._avatar.updateParticipant(null);
|
|
1744
1858
|
this._user = null;
|
|
@@ -1794,10 +1908,13 @@ class LiveKitConversation {
|
|
|
1794
1908
|
}
|
|
1795
1909
|
onChange(handler) {
|
|
1796
1910
|
return this.onEvent((event) => {
|
|
1797
|
-
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call")
|
|
1911
|
+
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call" && event.type !== "session-event")
|
|
1798
1912
|
handler(event);
|
|
1799
1913
|
});
|
|
1800
1914
|
}
|
|
1915
|
+
on(type, handler) {
|
|
1916
|
+
return this.onEvent(sessionEventsOf(type, handler));
|
|
1917
|
+
}
|
|
1801
1918
|
onEvent(handler) {
|
|
1802
1919
|
this.changeHandlers.add(handler);
|
|
1803
1920
|
return () => {
|
|
@@ -1899,6 +2016,7 @@ class LiveKitConversation {
|
|
|
1899
2016
|
this.awaitAvatarReturn();
|
|
1900
2017
|
}
|
|
1901
2018
|
});
|
|
2019
|
+
this.room.on(RoomEvent3.Reconnected, () => this.catchUp());
|
|
1902
2020
|
this.room.on(RoomEvent3.ParticipantConnected, (participant) => {
|
|
1903
2021
|
if (this.isAvatarParticipant(participant)) {
|
|
1904
2022
|
const tracksChanged = this._avatar.updateParticipant(participant);
|
|
@@ -1908,6 +2026,7 @@ class LiveKitConversation {
|
|
|
1908
2026
|
}
|
|
1909
2027
|
if (this.away)
|
|
1910
2028
|
this.avatarBack();
|
|
2029
|
+
this.catchUp();
|
|
1911
2030
|
}
|
|
1912
2031
|
});
|
|
1913
2032
|
this.room.on(RoomEvent3.ParticipantDisconnected, (participant) => {
|
|
@@ -1929,6 +2048,7 @@ class LiveKitConversation {
|
|
|
1929
2048
|
this.room.on(RoomEvent3.Disconnected, () => {
|
|
1930
2049
|
this.stats?.abandon();
|
|
1931
2050
|
this.stats = null;
|
|
2051
|
+
this.sequence.stop();
|
|
1932
2052
|
this.setAvatarSpeaking(false);
|
|
1933
2053
|
this.stopPresentation();
|
|
1934
2054
|
this.stopReadyFallback();
|
|
@@ -2026,13 +2146,14 @@ class LiveKitConversation {
|
|
|
2026
2146
|
this.answered = null;
|
|
2027
2147
|
}
|
|
2028
2148
|
handleSessionEvent(payload) {
|
|
2029
|
-
const
|
|
2030
|
-
if (
|
|
2149
|
+
const packet = parseSessionEvent(payload);
|
|
2150
|
+
if (packet === null)
|
|
2031
2151
|
return;
|
|
2032
2152
|
this.speaksProtocol = true;
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2153
|
+
const { seq, event } = packet;
|
|
2154
|
+
if (this.sequence.repeats(seq))
|
|
2155
|
+
return;
|
|
2156
|
+
if (event.type === "command-result") {
|
|
2036
2157
|
if (event.id === SESSION_END_ID)
|
|
2037
2158
|
this.answered?.();
|
|
2038
2159
|
else if (event.id !== null) {
|
|
@@ -2045,16 +2166,32 @@ class LiveKitConversation {
|
|
|
2045
2166
|
this.commands.delete(id);
|
|
2046
2167
|
settle({ status: "rejected", error: `unknown command ${type}` });
|
|
2047
2168
|
}
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2169
|
+
}
|
|
2170
|
+
this.sequence.accept(seq, event);
|
|
2171
|
+
}
|
|
2172
|
+
deliver(event) {
|
|
2173
|
+
if (event.type === "output-ready") {
|
|
2174
|
+
this.markAvatarReady();
|
|
2050
2175
|
} else if (event.type === "tool-call") {
|
|
2051
2176
|
this.emit({ type: "tool-call", call: event.call });
|
|
2052
2177
|
} else if (event.type === "ended") {
|
|
2053
2178
|
this.answered?.();
|
|
2054
2179
|
if (!event.byClient)
|
|
2055
2180
|
this.reportEnd(event.reason, event.message);
|
|
2181
|
+
} else if (event.type === "session-event") {
|
|
2182
|
+
if (event.event.type === "floor") {
|
|
2183
|
+
this.setAvatarSpeaking(event.event.state === "speaking");
|
|
2184
|
+
}
|
|
2185
|
+
this.emit({ type: "session-event", event: event.event });
|
|
2056
2186
|
}
|
|
2057
2187
|
}
|
|
2188
|
+
newSequence() {
|
|
2189
|
+
return new EventSequence((event) => this.deliver(event), (afterSeq) => this.command({ type: "session.replay", after_seq: afterSeq }));
|
|
2190
|
+
}
|
|
2191
|
+
catchUp() {
|
|
2192
|
+
if (this.speaksProtocol)
|
|
2193
|
+
this.sequence.catchUp();
|
|
2194
|
+
}
|
|
2058
2195
|
handleRuntimeSignal(payload) {
|
|
2059
2196
|
const signal = parseRuntimeSignal(payload);
|
|
2060
2197
|
if (signal?.type === "session-ended") {
|
|
@@ -2497,10 +2634,13 @@ class NuanceConversation {
|
|
|
2497
2634
|
}
|
|
2498
2635
|
onChange(handler) {
|
|
2499
2636
|
return this.onEvent((event) => {
|
|
2500
|
-
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call")
|
|
2637
|
+
if (event.type !== "data-event" && event.type !== "screen-share-changed" && event.type !== "tool-call" && event.type !== "session-event")
|
|
2501
2638
|
handler(event);
|
|
2502
2639
|
});
|
|
2503
2640
|
}
|
|
2641
|
+
on(type, handler) {
|
|
2642
|
+
return this.onEvent(sessionEventsOf(type, handler));
|
|
2643
|
+
}
|
|
2504
2644
|
onEvent(handler) {
|
|
2505
2645
|
this.changeHandlers.add(handler);
|
|
2506
2646
|
return () => {
|