@nuancelabs/js-sdk 0.10.1 → 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.
Files changed (3) hide show
  1. package/index.d.ts +214 -0
  2. package/index.js +154 -14
  3. 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. */
@@ -499,6 +651,13 @@ export declare interface SessionPersonality {
499
651
  export declare interface SessionTokenRequest {
500
652
  /** Who the avatar is in the conversation. Leave it out for the default personality. */
501
653
  personality?: SessionPersonality;
654
+ /**
655
+ * Your own id for the person this conversation is for, such as a hash of
656
+ * your user id. After repeated policy violations, new conversations pause
657
+ * for this person only, not for your whole app. Letters, digits and
658
+ * `_ . : -`, up to 128 characters: never an email address or a name.
659
+ */
660
+ end_user_id?: string;
502
661
  }
503
662
 
504
663
  /** What `POST /session-tokens` returns. */
@@ -507,6 +666,8 @@ export declare interface SessionTokenResponse {
507
666
  token: string;
508
667
  /** When the token stops being able to start a conversation (UTC, ISO 8601). */
509
668
  expires_at: string;
669
+ /** What to change in how you mint tokens; empty when nothing. */
670
+ warnings?: string[];
510
671
  }
511
672
 
512
673
  /** When the avatar starts speaking: see {@link CreateNuanceConversationOptions.startOn}. */
@@ -552,6 +713,59 @@ export declare interface UserParticipant {
552
713
  readonly isSpeaking: boolean;
553
714
  }
554
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
+
555
769
  /**
556
770
  * Capture constraints for the user's published camera track.
557
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.10.1"}`;
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
- return { type: "other" };
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 event = parseSessionEvent(payload);
2030
- if (event === null)
2149
+ const packet = parseSessionEvent(payload);
2150
+ if (packet === null)
2031
2151
  return;
2032
2152
  this.speaksProtocol = true;
2033
- if (event.type === "output-ready") {
2034
- this.markAvatarReady();
2035
- } else if (event.type === "command-result") {
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
- } else if (event.type === "floor") {
2049
- this.setAvatarSpeaking(event.speaking);
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 () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuancelabs/js-sdk",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "description": "JavaScript SDK for Nuance Labs conversation video AI avatar",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {