@nuancelabs/js-sdk 0.5.0 → 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.
Files changed (3) hide show
  1. package/index.d.ts +22 -3
  2. package/index.js +66 -14
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -212,6 +212,8 @@ export declare type ConversationEvent =
212
212
  * Emitted only while waiting. `estimatedWaitSeconds` never increases for a
213
213
  * given session: a wait that grows reads as the service failing, and arriving
214
214
  * early is forgiven. Null when there is no serving capacity to estimate from.
215
+ * `preparing` is true once the session is next and an avatar is held for it
216
+ * while it gets ready; the session connects when it is.
215
217
  */
216
218
  /**
217
219
  * How long the live session has left, when it is capped.
@@ -229,16 +231,20 @@ export declare type ConversationEvent =
229
231
  * A runtime that stops at its limit does not necessarily leave the transport,
230
232
  * so a caller cannot infer this from the connection alone. `reason` is
231
233
  * `"time-limit"` when the session was capped and ran out its clock,
232
- * `"policy-violation"` when the runtime ended it for content.
234
+ * `"policy-violation"` when the runtime ended it for content, `"refused"`
235
+ * when the runtime would not start it, with its reason in `message` when it
236
+ * gave one.
233
237
  */
234
238
  | {
235
239
  type: "session-ended";
236
240
  reason: SessionEndReason;
241
+ message?: string;
237
242
  } | {
238
243
  type: "queue-changed";
239
244
  position: number;
240
245
  estimatedWaitSeconds: number | null;
241
246
  poolSlots: number | null;
247
+ preparing: boolean;
242
248
  };
243
249
 
244
250
  /**
@@ -338,6 +344,12 @@ export declare interface CreateNuanceConversationOptions {
338
344
  * ```
339
345
  */
340
346
  personality?: PersonalityChoice;
347
+ /**
348
+ * The longest the conversation may run, in seconds. The server holds it to
349
+ * the account's maximum, so a longer value runs for that maximum; left out,
350
+ * the conversation runs for the maximum.
351
+ */
352
+ maxSessionSeconds?: number;
341
353
  /**
342
354
  * Capture constraints for the user's camera.
343
355
  *
@@ -444,9 +456,10 @@ export declare class PolicyCurfewError extends Error {
444
456
  /**
445
457
  * Why a session is over. `"time-limit"`: the capped session ran out its clock.
446
458
  * `"policy-violation"`: the runtime ended it for content that breaks the usage
447
- * policy. `"ended"`: any other server-side end.
459
+ * policy. `"refused"`: the runtime would not start it, for the reason in the
460
+ * event's `message`. `"ended"`: any other server-side end.
448
461
  */
449
- export declare type SessionEndReason = "time-limit" | "ended" | "policy-violation" | "connection-lost";
462
+ export declare type SessionEndReason = "time-limit" | "ended" | "policy-violation" | "refused" | "connection-lost";
450
463
 
451
464
 
452
465
 
@@ -458,6 +471,12 @@ export declare interface SessionPersonality {
458
471
  * before a call, in plain text.
459
472
  */
460
473
  system_prompt?: string;
474
+ /**
475
+ * The face the avatar is drawn as, by the id Nuance gave your organization.
476
+ * Leave it out for the default face. A face your organization has not been
477
+ * given is refused when the token is minted.
478
+ */
479
+ face_id?: string;
461
480
  }
462
481
 
463
482
  /**
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.5.0"}`;
7
+ var SDK_HEADER = `js/${"0.9.0"}`;
8
8
 
9
9
  // src/utils.ts
10
10
  function normalizeHost(host) {
@@ -1063,6 +1063,9 @@ function endReason(reason) {
1063
1063
  return "policy-violation";
1064
1064
  if (reason === "time_limit")
1065
1065
  return "time-limit";
1066
+ if (reason === "invalid_spec" || reason === "runtime_failed") {
1067
+ return "refused";
1068
+ }
1066
1069
  return "ended";
1067
1070
  }
1068
1071
  function parseSessionEvent(payload) {
@@ -1093,7 +1096,15 @@ function parseSessionEvent(payload) {
1093
1096
  };
1094
1097
  }
1095
1098
  if (decoded.type === "error" && decoded.fatal === true) {
1096
- return { type: "ended", reason: endReason(decoded.code), byClient: false };
1099
+ const ended = {
1100
+ type: "ended",
1101
+ reason: endReason(decoded.code),
1102
+ byClient: false
1103
+ };
1104
+ if (typeof decoded.message === "string" && decoded.message !== "") {
1105
+ ended.message = decoded.message;
1106
+ }
1107
+ return ended;
1097
1108
  }
1098
1109
  if (decoded.type === "error" && decoded.code === "bad_command") {
1099
1110
  return { type: "command-unreadable" };
@@ -1528,6 +1539,7 @@ var CLOCK_SYNC_TOPIC = "clock-sync";
1528
1539
  var STATS_FLUSH_TIMEOUT_MS = 300;
1529
1540
  var SESSION_END_TIMEOUT_MS = 300;
1530
1541
  var SESSION_END_ID = "client-session-end";
1542
+ var REFUSAL_REASON_WAIT_MS = 500;
1531
1543
  var COMMAND_TIMEOUT_MS = 5000;
1532
1544
  var BACKGROUND_ID = /^[a-z0-9][a-z0-9_-]{0,63}$/;
1533
1545
  var AVATAR_RETURN_TIMEOUT_MS = 8000;
@@ -1578,6 +1590,7 @@ class LiveKitConversation {
1578
1590
  network = "good";
1579
1591
  eventsRegistered = false;
1580
1592
  sessionEnded = false;
1593
+ refusal = null;
1581
1594
  speaksProtocol = false;
1582
1595
  avatarSpeaking = false;
1583
1596
  answered = null;
@@ -1698,6 +1711,7 @@ class LiveKitConversation {
1698
1711
  this.stopReadyFallback();
1699
1712
  this.stopLayerFallback();
1700
1713
  this.clearAway();
1714
+ this.stopRefusal();
1701
1715
  this._user?.cleanup();
1702
1716
  this._user?.releaseDevices();
1703
1717
  await this.flushStats();
@@ -1731,6 +1745,15 @@ class LiveKitConversation {
1731
1745
  throw new Error("Nothing to send: the text is blank");
1732
1746
  return this.command({ type: "input.text", text: line, respond: true });
1733
1747
  }
1748
+ async holdAgent() {
1749
+ await this.command({ type: "agent.stop" }).catch(() => {
1750
+ return;
1751
+ });
1752
+ return this.command({ type: "floor.hold" });
1753
+ }
1754
+ async releaseAgent() {
1755
+ return this.command({ type: "floor.release" });
1756
+ }
1734
1757
  onChange(handler) {
1735
1758
  return this.onEvent((event) => {
1736
1759
  if (event.type !== "data-event" && event.type !== "screen-share-changed")
@@ -1976,12 +1999,16 @@ class LiveKitConversation {
1976
1999
  } else if (event.type === "ended") {
1977
2000
  this.answered?.();
1978
2001
  if (!event.byClient)
1979
- this.reportEnd(event.reason);
2002
+ this.reportEnd(event.reason, event.message);
1980
2003
  }
1981
2004
  }
1982
2005
  handleRuntimeSignal(payload) {
1983
2006
  const signal = parseRuntimeSignal(payload);
1984
2007
  if (signal?.type === "session-ended") {
2008
+ if (signal.reason === "refused") {
2009
+ this.refusal ??= setTimeout(() => this.reportEnd("refused"), REFUSAL_REASON_WAIT_MS);
2010
+ return;
2011
+ }
1985
2012
  this.reportEnd(signal.reason);
1986
2013
  } else if (signal?.type === "output-ready") {
1987
2014
  this.markAvatarReady();
@@ -2120,11 +2147,18 @@ class LiveKitConversation {
2120
2147
  this.presentation?.stop();
2121
2148
  this.presentation = null;
2122
2149
  }
2123
- reportEnd(reason) {
2150
+ reportEnd(reason, message) {
2151
+ this.stopRefusal();
2124
2152
  if (this.sessionEnded)
2125
2153
  return;
2126
2154
  this.sessionEnded = true;
2127
- this.emit({ type: "session-ended", reason });
2155
+ this.emit(message === undefined ? { type: "session-ended", reason } : { type: "session-ended", reason, message });
2156
+ }
2157
+ stopRefusal() {
2158
+ if (this.refusal === null)
2159
+ return;
2160
+ clearTimeout(this.refusal);
2161
+ this.refusal = null;
2128
2162
  }
2129
2163
  handleClockSyncPing(payload) {
2130
2164
  const reply = clockSyncPong(payload, Date.now() / 1000, () => Date.now() / 1000);
@@ -2202,6 +2236,7 @@ class NuanceConversation {
2202
2236
  sessionOverrides;
2203
2237
  personality;
2204
2238
  variant;
2239
+ maxSessionSeconds;
2205
2240
  videoCapture;
2206
2241
  startOn;
2207
2242
  mediaDevices;
@@ -2239,6 +2274,7 @@ class NuanceConversation {
2239
2274
  this.sessionOverrides = options.sessionOverrides ?? {};
2240
2275
  this.personality = options.personality;
2241
2276
  this.variant = options.variant;
2277
+ this.maxSessionSeconds = options.maxSessionSeconds;
2242
2278
  this.videoCapture = options.videoCapture;
2243
2279
  this.startOn = options.startOn ?? "first-frame";
2244
2280
  this.mediaDevices = options.mediaDevices;
@@ -2275,6 +2311,9 @@ class NuanceConversation {
2275
2311
  request.personality = this.personality;
2276
2312
  if (this.variant)
2277
2313
  request.variant = this.variant;
2314
+ if (this.maxSessionSeconds !== undefined) {
2315
+ request.max_session_seconds = this.maxSessionSeconds;
2316
+ }
2278
2317
  conversation = await this.createWhenNotBusy(request, attempt);
2279
2318
  } catch (error) {
2280
2319
  if (error instanceof ApiError && error.status === 409) {
@@ -2305,10 +2344,10 @@ class NuanceConversation {
2305
2344
  this.setState(event.state);
2306
2345
  return;
2307
2346
  }
2308
- if (event.type === "session-ended" && event.reason === "connection-lost") {
2347
+ if (event.type === "session-ended" && (event.reason === "connection-lost" || event.reason === "refused")) {
2309
2348
  const held = this.held;
2310
2349
  this.held = null;
2311
- this.endSession(event.reason);
2350
+ this.endSession(event.reason, event.message);
2312
2351
  this.disconnect().catch(() => {});
2313
2352
  if (held !== null) {
2314
2353
  this.api.abandonConversation(held).catch(() => {});
@@ -2316,7 +2355,7 @@ class NuanceConversation {
2316
2355
  return;
2317
2356
  }
2318
2357
  if (event.type === "session-ended") {
2319
- this.endSession(event.reason);
2358
+ this.endSession(event.reason, event.message);
2320
2359
  return;
2321
2360
  }
2322
2361
  this.emit(event);
@@ -2379,6 +2418,18 @@ class NuanceConversation {
2379
2418
  }
2380
2419
  return this.delegate.sendText(text);
2381
2420
  }
2421
+ async holdAgent() {
2422
+ if (!this.delegate) {
2423
+ throw new Error("Not connected — call connect() first");
2424
+ }
2425
+ return this.delegate.holdAgent();
2426
+ }
2427
+ async releaseAgent() {
2428
+ if (!this.delegate) {
2429
+ throw new Error("Not connected — call connect() first");
2430
+ }
2431
+ return this.delegate.releaseAgent();
2432
+ }
2382
2433
  onChange(handler) {
2383
2434
  return this.onEvent((event) => {
2384
2435
  if (event.type !== "data-event" && event.type !== "screen-share-changed")
@@ -2455,7 +2506,7 @@ class NuanceConversation {
2455
2506
  throw new TerminalStatusError(event.reason);
2456
2507
  }
2457
2508
  if (event.type === "queued" && event.position != null) {
2458
- this.reportPlace(event.position, event.estimated_wait_seconds ?? null, null);
2509
+ this.reportPlace(event.position, event.estimated_wait_seconds ?? null, null, event.preparing ?? false);
2459
2510
  }
2460
2511
  }
2461
2512
  }
@@ -2639,9 +2690,9 @@ class NuanceConversation {
2639
2690
  if (state.status !== "queued" || state.position == null) {
2640
2691
  return;
2641
2692
  }
2642
- this.reportPlace(state.position, state.estimated_wait_seconds ?? null, state.pool_slots ?? null);
2693
+ this.reportPlace(state.position, state.estimated_wait_seconds ?? null, state.pool_slots ?? null, state.preparing ?? false);
2643
2694
  }
2644
- reportPlace(position, estimate, poolSlots) {
2695
+ reportPlace(position, estimate, poolSlots, preparing) {
2645
2696
  if (this._state !== "queued") {
2646
2697
  this.setState("queued");
2647
2698
  }
@@ -2649,7 +2700,8 @@ class NuanceConversation {
2649
2700
  type: "queue-changed",
2650
2701
  position,
2651
2702
  estimatedWaitSeconds: this.smoothEstimate(position, estimate),
2652
- poolSlots
2703
+ poolSlots,
2704
+ preparing
2653
2705
  });
2654
2706
  }
2655
2707
  smoothEstimate(position, estimate) {
@@ -2685,14 +2737,14 @@ class NuanceConversation {
2685
2737
  }
2686
2738
  await this.api.abandonConversation(held).catch(() => {});
2687
2739
  }
2688
- endSession(reason) {
2740
+ endSession(reason, message) {
2689
2741
  if (this.ended) {
2690
2742
  return;
2691
2743
  }
2692
2744
  this.ended = true;
2693
2745
  this.clearLimit();
2694
2746
  this.stopHeartbeat();
2695
- this.emit({ type: "session-ended", reason });
2747
+ this.emit(message === undefined ? { type: "session-ended", reason } : { type: "session-ended", reason, message });
2696
2748
  }
2697
2749
  stopHeartbeat() {
2698
2750
  this.stopBeats?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuancelabs/js-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.9.0",
4
4
  "description": "JavaScript SDK for Nuance Labs conversation video AI avatar",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {