@nuancelabs/js-sdk 0.1.2 → 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.
Files changed (3) hide show
  1. package/index.d.ts +24 -4
  2. package/index.js +30 -3
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -157,10 +157,10 @@ export declare type ConversationEvent =
157
157
  type: "avatar-ready";
158
158
  }
159
159
  /**
160
- * The avatar is presented as she should be seen: her video has reached its
161
- * full size and held it, or the wait for it ran out. Follows `avatar-ready`.
162
- * The session opens on it: her first words, and its recording. Reveal her
163
- * fully on this; between the two she is in view but not yet speaking.
160
+ * The avatar starts speaking: her first words, and the session's recording.
161
+ * Follows `avatar-ready`, at once by default. With `startOn: "full-quality"`
162
+ * it waits until her video has reached its full size and held it, or the
163
+ * wait ran out; in between she is in view but not yet speaking.
164
164
  */
165
165
  | {
166
166
  type: "avatar-presented";
@@ -296,6 +296,9 @@ export declare type ConversationStateValue = `${ConversationState}`;
296
296
  */
297
297
  export declare function createNuanceConversation(options?: CreateNuanceConversationOptions): Conversation;
298
298
 
299
+ /**
300
+ * Options for creating a Nuance conversation.
301
+ */
299
302
  export declare interface CreateNuanceConversationOptions {
300
303
  /**
301
304
  * Fetches a session token, for authenticating from a web page.
@@ -324,6 +327,20 @@ export declare interface CreateNuanceConversationOptions {
324
327
  videoCapture?: VideoCaptureConstraints;
325
328
  /** Local capture preferences, resolved once for each connection attempt. */
326
329
  mediaDevices?: MediaDevicePreferences | (() => MediaDevicePreferences);
330
+ /**
331
+ * When the avatar starts speaking.
332
+ *
333
+ * - `"first-frame"`: as soon as her first frame reaches the page.
334
+ * `avatar-presented` follows `avatar-ready` at once. Her video may be
335
+ * soft for its first few seconds while the connection ramps up.
336
+ * - `"full-quality"`: once her video has reached its full size and held
337
+ * it, or after at most about 12 seconds. She is in view but silent in
338
+ * between, so a page using this should cover her until
339
+ * `avatar-presented`.
340
+ *
341
+ * @defaultValue `"first-frame"`
342
+ */
343
+ startOn?: StartOn;
327
344
  }
328
345
 
329
346
 
@@ -412,6 +429,9 @@ export declare type SessionEndReason = "time-limit" | "ended" | "policy-violatio
412
429
 
413
430
 
414
431
 
432
+ /** When the avatar starts speaking: see {@link CreateNuanceConversationOptions.startOn}. */
433
+ export declare type StartOn = "first-frame" | "full-quality";
434
+
415
435
 
416
436
  /**
417
437
  * The local user participant in a conversation.
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.1.2"}`;
7
+ var SDK_HEADER = `js/${"0.2.1"}`;
8
8
 
9
9
  // src/utils.ts
10
10
  function normalizeHost(host) {
@@ -2086,6 +2086,8 @@ var DEFAULT_HOST = "https://api.nuancelabs.ai";
2086
2086
  var QUEUE_HEARTBEAT_INTERVAL_MS = 5000;
2087
2087
  var HEARTBEAT_INTERVAL_MS = 5000;
2088
2088
  var ADMISSION_FAILED_BEATS = 6;
2089
+ var BUSY_RETRY_LIMIT_MS = 60000;
2090
+ var BUSY_RETRY_DEFAULT_MS = 3000;
2089
2091
  var LOST_WINDOW_MS = 30000;
2090
2092
  function createNuanceConversation(options = {}) {
2091
2093
  return new NuanceConversation(options);
@@ -2115,6 +2117,9 @@ function isLegalRequiredBody(body) {
2115
2117
  function isMaintenanceBody(body) {
2116
2118
  return isRecord(body) && body.code === "maintenance";
2117
2119
  }
2120
+ function isBusyBody(body) {
2121
+ return isRecord(body) && body.code === "busy";
2122
+ }
2118
2123
  function isCurfewBody(body) {
2119
2124
  return isRecord(body) && body.code === "policy_curfew";
2120
2125
  }
@@ -2141,6 +2146,7 @@ class NuanceConversation {
2141
2146
  personality;
2142
2147
  variant;
2143
2148
  videoCapture;
2149
+ startOn;
2144
2150
  mediaDevices;
2145
2151
  resilientReconnect;
2146
2152
  delegate = null;
@@ -2176,6 +2182,7 @@ class NuanceConversation {
2176
2182
  this.personality = options.personality;
2177
2183
  this.variant = options.variant;
2178
2184
  this.videoCapture = options.videoCapture;
2185
+ this.startOn = options.startOn ?? "first-frame";
2179
2186
  this.mediaDevices = options.mediaDevices;
2180
2187
  this.resilientReconnect = options.resilientReconnect ?? false;
2181
2188
  }
@@ -2201,7 +2208,7 @@ class NuanceConversation {
2201
2208
  try {
2202
2209
  const request = {
2203
2210
  session_overrides: this.sessionOverrides,
2204
- client_ready: true
2211
+ client_ready: this.startOn === "full-quality"
2205
2212
  };
2206
2213
  const timezone = this.timezone();
2207
2214
  if (timezone)
@@ -2210,7 +2217,7 @@ class NuanceConversation {
2210
2217
  request.personality = this.personality;
2211
2218
  if (this.variant)
2212
2219
  request.variant = this.variant;
2213
- conversation = await this.api.createConversation(request);
2220
+ conversation = await this.createWhenNotBusy(request, attempt);
2214
2221
  } catch (error) {
2215
2222
  if (error instanceof ApiError && error.status === 409) {
2216
2223
  throw new ActiveSessionError;
@@ -2352,6 +2359,26 @@ class NuanceConversation {
2352
2359
  resilientReconnect: this.resilient
2353
2360
  });
2354
2361
  }
2362
+ async createWhenNotBusy(request, attempt) {
2363
+ const giveUpAt = Date.now() + BUSY_RETRY_LIMIT_MS;
2364
+ for (;; ) {
2365
+ try {
2366
+ return await this.api.createConversation(request);
2367
+ } catch (error) {
2368
+ if (!(error instanceof ApiError) || error.status !== 503 || !isBusyBody(error.body) || attempt !== this.attempt) {
2369
+ throw error;
2370
+ }
2371
+ const waitMs = typeof error.body.retry_after_seconds === "number" ? error.body.retry_after_seconds * 1000 : BUSY_RETRY_DEFAULT_MS;
2372
+ if (Date.now() + waitMs > giveUpAt)
2373
+ throw error;
2374
+ if (this._state !== "queued")
2375
+ this.setState("queued");
2376
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
2377
+ if (attempt !== this.attempt)
2378
+ throw error;
2379
+ }
2380
+ }
2381
+ }
2355
2382
  startHeartbeat(conversationId, intervalMs) {
2356
2383
  this.stopHeartbeat();
2357
2384
  this.failedBeats = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuancelabs/js-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "JavaScript SDK for Nuance Labs conversation video AI avatar",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {