@nuancelabs/js-sdk 0.12.0 → 0.13.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 +120 -0
  2. package/index.js +44 -4
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -217,6 +217,37 @@ export declare interface Conversation {
217
217
  * @returns A function that removes the handler.
218
218
  */
219
219
  on<K extends keyof SessionEventMap>(type: K, handler: (event: SessionEventMap[K]) => void): () => void;
220
+ /**
221
+ * Subscribes to the agent's calls of the conversation's tools, named when
222
+ * your server mints the session token. Run the tool in your page and answer each call with
223
+ * {@link Conversation.sendToolResult}. The agent keeps talking while a call
224
+ * is open, and uses the result when it arrives.
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * conversation.onToolCall(async (call) => {
229
+ * if (call.name === "show_confetti") {
230
+ * launchConfetti();
231
+ * await conversation.sendToolResult(call.callId, "Confetti is falling.");
232
+ * }
233
+ * });
234
+ * ```
235
+ *
236
+ * @returns A function that removes the handler.
237
+ */
238
+ onToolCall(handler: (call: ToolCall) => void): () => void;
239
+ /**
240
+ * Answers a tool call: what it produced, or with `isError` what went wrong.
241
+ * The agent keeps the first 2,000 characters of an output, so return what
242
+ * it needs, not everything you have.
243
+ *
244
+ * @param callId - The call's {@link ToolCall.callId}.
245
+ * @returns Whether the runtime took the result: `status` is `"rejected"`,
246
+ * with the reason in `error`, for an unknown or closed call.
247
+ * @throws If the conversation is not connected, or the runtime does not
248
+ * answer in time.
249
+ */
250
+ sendToolResult(callId: string, output: ToolOutput, options?: ToolResultOptions): Promise<CommandResult>;
220
251
  }
221
252
 
222
253
 
@@ -487,6 +518,33 @@ export declare interface FloorEvent extends SessionEventBase {
487
518
  state: "waiting" | "listening" | "speaking" | (string & {});
488
519
  }
489
520
 
521
+ /**
522
+ * A tool the agent may call, which your page runs: show something on the
523
+ * screen, read what the user has open, look something up in your app. The
524
+ * agent decides when to call it from its `description`, and fills its
525
+ * arguments to match its `parameters`. A conversation has up to 16 tools.
526
+ */
527
+ export declare interface FunctionTool {
528
+ /** Always `"function"`, the default. */
529
+ type?: "function";
530
+ /**
531
+ * The tool's name: letters, digits, `_` and `-`, up to 64 characters,
532
+ * unique in the conversation. `ask_user`, `get_current_time`, `web_search`
533
+ * and the agent's other own tools' names are taken.
534
+ */
535
+ name: string;
536
+ /**
537
+ * What the tool does and when to use it, written for the agent, up to
538
+ * 2,000 characters. Say when to call it, not only what it does.
539
+ */
540
+ description: string;
541
+ /**
542
+ * The arguments, as a JSON Schema whose `type` is `"object"`, up to 16,000
543
+ * characters as compact JSON. `{ type: "object", properties: {} }` takes none.
544
+ */
545
+ parameters: Record<string, unknown>;
546
+ }
547
+
490
548
  /**
491
549
  * Raised by {@link Conversation.connect} when the server refuses a guest's new
492
550
  * session because a guest limit is used up. `reason` says which: `used` (this
@@ -499,6 +557,7 @@ export declare class GuestLimitError extends Error {
499
557
  constructor(reason: string, message: string);
500
558
  }
501
559
 
560
+
502
561
  /**
503
562
  * Raised by {@link Conversation.connect} when the server refuses a new session
504
563
  * because the account has not completed onboarding or accepted the current
@@ -651,6 +710,23 @@ export declare interface SessionPersonality {
651
710
  export declare interface SessionTokenRequest {
652
711
  /** Who the avatar is in the conversation. Leave it out for the default personality. */
653
712
  personality?: SessionPersonality;
713
+ /**
714
+ * Tools the agent may call, which your page runs and answers with
715
+ * `onToolCall` and `sendToolResult`;
716
+ * up to 16. Fixed for the conversation: the page can't change them.
717
+ *
718
+ * @example
719
+ * ```ts
720
+ * tools: [
721
+ * {
722
+ * name: "show_confetti",
723
+ * description: "Plays confetti on the user's screen. Use it to celebrate with them.",
724
+ * parameters: { type: "object", properties: {} },
725
+ * },
726
+ * ]
727
+ * ```
728
+ */
729
+ tools?: FunctionTool[];
654
730
  /**
655
731
  * Your own id for the person this conversation is for, such as a hash of
656
732
  * your user id. After repeated policy violations, new conversations pause
@@ -673,9 +749,53 @@ export declare interface SessionTokenResponse {
673
749
  /** When the avatar starts speaking: see {@link CreateNuanceConversationOptions.startOn}. */
674
750
  export declare type StartOn = "first-frame" | "full-quality";
675
751
 
752
+ /**
753
+ * The agent's call of one of the conversation's tools: run it, then answer
754
+ * with {@link Conversation.sendToolResult} and its `callId`.
755
+ */
756
+ export declare interface ToolCall {
757
+ /** Identifies the call; answer with it. */
758
+ callId: string;
759
+ /** The tool, by its `name`. */
760
+ name: string;
761
+ /** The agent's own short name for this use of the tool, such as `"confetti"`. */
762
+ label: string;
763
+ /** The call's arguments, held to the tool's `parameters` schema. */
764
+ arguments: Record<string, unknown>;
765
+ }
676
766
 
767
+ /**
768
+ * An image a tool produced, for the agent to look at: a screenshot, say, or
769
+ * a page of a document. Up to 8,000,000 base64 characters (about 6 MB); the
770
+ * runtime fits it to 1920 px on its long side.
771
+ */
772
+ export declare interface ToolImage {
773
+ mediaType: "image/jpeg" | "image/png";
774
+ /** The encoded image, base64. */
775
+ data: string;
776
+ }
677
777
 
778
+ /**
779
+ * What a tool call produced: text, or JSON the agent reads as it is. Up to
780
+ * 24,000 characters (its JSON, for an object); the agent keeps the first 2,000.
781
+ */
782
+ export declare type ToolOutput = string | Record<string, unknown> | unknown[];
678
783
 
784
+ /**
785
+ * How {@link Conversation.sendToolResult} reports a result.
786
+ */
787
+ export declare interface ToolResultOptions {
788
+ /** Images the tool produced, up to 4; the agent looks at them, in order,
789
+ * where the result enters the conversation. */
790
+ images?: ToolImage[];
791
+ /** The output says what went wrong rather than what the tool produced. */
792
+ isError?: boolean;
793
+ /** Whether this is the call's last part; a part with `final: false` adds to
794
+ * what the agent knows and keeps the call open.
795
+ *
796
+ * @defaultValue `true` */
797
+ final?: boolean;
798
+ }
679
799
 
680
800
  /**
681
801
  * 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.12.0"}`;
7
+ var SDK_HEADER = `js/${"0.13.1"}`;
8
8
 
9
9
  // src/utils.ts
10
10
  function normalizeHost(host) {
@@ -1676,6 +1676,10 @@ var COMMAND_TIMEOUT_MS = 5000;
1676
1676
  var STREAM_TIMEOUT_MS = 30000;
1677
1677
  var BACKGROUND_ID = /^[a-z0-9][a-z0-9_-]{0,63}$/;
1678
1678
  var AVATAR_RETURN_TIMEOUT_MS = 8000;
1679
+ function contextItem(item) {
1680
+ const name = item.name === undefined ? {} : { name: item.name };
1681
+ return item.type === "text" ? { type: "text", text: item.text, ...name } : { type: "image", media_type: item.mediaType, data: item.data, ...name };
1682
+ }
1679
1683
  function isReconnecting(lkState) {
1680
1684
  return lkState === ConnectionState2.Reconnecting || lkState === ConnectionState2.SignalReconnecting;
1681
1685
  }
@@ -1736,6 +1740,7 @@ class LiveKitConversation {
1736
1740
  awayTimer = null;
1737
1741
  presentationPaused = false;
1738
1742
  readySending = false;
1743
+ contextSent = null;
1739
1744
  leaving = null;
1740
1745
  connectGeneration = 0;
1741
1746
  _id;
@@ -1744,6 +1749,8 @@ class LiveKitConversation {
1744
1749
  cameraFacing;
1745
1750
  tokenProvider;
1746
1751
  opensOnReady;
1752
+ presentsAtFullSize;
1753
+ initialContext;
1747
1754
  resilient;
1748
1755
  constructor(config) {
1749
1756
  this.room = new Room({
@@ -1765,6 +1772,8 @@ class LiveKitConversation {
1765
1772
  this.cameraFacing = config.mediaDevices?.cameraFacing;
1766
1773
  this.tokenProvider = config.tokenProvider;
1767
1774
  this.opensOnReady = config.opensOnReady ?? false;
1775
+ this.presentsAtFullSize = config.presentsAtFullSize ?? this.opensOnReady;
1776
+ this.initialContext = config.initialContext ?? [];
1768
1777
  this.resilient = config.resilientReconnect ?? false;
1769
1778
  }
1770
1779
  get id() {
@@ -1780,6 +1789,7 @@ class LiveKitConversation {
1780
1789
  this.sequence.stop();
1781
1790
  this.sequence = this.newSequence();
1782
1791
  this.readyOwed = false;
1792
+ this.contextSent = null;
1783
1793
  this.everConnected = false;
1784
1794
  if (!this.eventsRegistered) {
1785
1795
  this.registerRoomEvents();
@@ -2150,6 +2160,7 @@ class LiveKitConversation {
2150
2160
  if (packet === null)
2151
2161
  return;
2152
2162
  this.speaksProtocol = true;
2163
+ this.sendContext();
2153
2164
  const { seq, event } = packet;
2154
2165
  if (this.sequence.repeats(seq))
2155
2166
  return;
@@ -2243,6 +2254,10 @@ class LiveKitConversation {
2243
2254
  this.emit({ type: "avatar-presented" });
2244
2255
  return;
2245
2256
  }
2257
+ if (!this.presentsAtFullSize) {
2258
+ this.markAvatarPresented();
2259
+ return;
2260
+ }
2246
2261
  this.startPresentation();
2247
2262
  }
2248
2263
  startPresentation() {
@@ -2253,7 +2268,7 @@ class LiveKitConversation {
2253
2268
  markAvatarPresented() {
2254
2269
  this.presentation = null;
2255
2270
  if (this.speaksProtocol && this.room.state === ConnectionState2.Connected) {
2256
- this.command({ type: "session.ready" }).catch(() => {
2271
+ this.ready().catch(() => {
2257
2272
  return;
2258
2273
  });
2259
2274
  } else if (this.resilient && this.speaksProtocol && isReconnecting(this.room.state)) {
@@ -2261,11 +2276,28 @@ class LiveKitConversation {
2261
2276
  }
2262
2277
  this.emit({ type: "avatar-presented" });
2263
2278
  }
2279
+ sendContext() {
2280
+ if (this.contextSent !== null || this.initialContext.length === 0)
2281
+ return;
2282
+ this.contextSent = this.command({
2283
+ type: "session.context",
2284
+ items: this.initialContext.map(contextItem)
2285
+ }).then(() => {
2286
+ return;
2287
+ }, () => {
2288
+ return;
2289
+ });
2290
+ }
2291
+ ready() {
2292
+ this.sendContext();
2293
+ const ready = () => this.command({ type: "session.ready" });
2294
+ return this.contextSent === null ? ready() : this.contextSent.then(ready);
2295
+ }
2264
2296
  sendOwedReady() {
2265
2297
  if (!this.readyOwed || !this.speaksProtocol || this.readySending)
2266
2298
  return;
2267
2299
  this.readySending = true;
2268
- this.command({ type: "session.ready" }).then(() => {
2300
+ this.ready().then(() => {
2269
2301
  this.readyOwed = false;
2270
2302
  }).catch(() => {
2271
2303
  return;
@@ -2428,6 +2460,8 @@ class NuanceConversation {
2428
2460
  ticker;
2429
2461
  sessionOverrides;
2430
2462
  personality;
2463
+ tools;
2464
+ initialContext;
2431
2465
  variant;
2432
2466
  maxSessionSeconds;
2433
2467
  videoCapture;
@@ -2466,6 +2500,8 @@ class NuanceConversation {
2466
2500
  this.ticker = deps.ticker ?? pageTicker;
2467
2501
  this.sessionOverrides = options.sessionOverrides ?? {};
2468
2502
  this.personality = options.personality;
2503
+ this.tools = options.tools;
2504
+ this.initialContext = options.initialContext ?? [];
2469
2505
  this.variant = options.variant;
2470
2506
  this.maxSessionSeconds = options.maxSessionSeconds;
2471
2507
  this.videoCapture = options.videoCapture;
@@ -2495,13 +2531,15 @@ class NuanceConversation {
2495
2531
  try {
2496
2532
  const request = {
2497
2533
  session_overrides: this.sessionOverrides,
2498
- client_ready: this.startOn === "full-quality"
2534
+ client_ready: this.startOn === "full-quality" || this.initialContext.length > 0
2499
2535
  };
2500
2536
  const timezone = this.timezone();
2501
2537
  if (timezone)
2502
2538
  request.timezone = timezone;
2503
2539
  if (this.personality)
2504
2540
  request.personality = this.personality;
2541
+ if (this.tools)
2542
+ request.tools = this.tools;
2505
2543
  if (this.variant)
2506
2544
  request.variant = this.variant;
2507
2545
  if (this.maxSessionSeconds !== undefined) {
@@ -2805,6 +2843,8 @@ class NuanceConversation {
2805
2843
  cameraEnabled: !this.sessionOverrides.disable_camera && mediaDevices.cameraEnabled !== false,
2806
2844
  videoCapture: this.videoCapture,
2807
2845
  opensOnReady: conversation.provider_metadata?.client_ready === true,
2846
+ presentsAtFullSize: this.startOn === "full-quality",
2847
+ initialContext: this.initialContext,
2808
2848
  resilientReconnect: this.resilient
2809
2849
  });
2810
2850
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuancelabs/js-sdk",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "JavaScript SDK for Nuance Labs conversation video AI avatar",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {