@convai/web-sdk 1.7.0 → 1.8.0-beta.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 (44) hide show
  1. package/dist/core/ConvaiClient.d.ts +40 -1
  2. package/dist/core/ConvaiClient.d.ts.map +1 -1
  3. package/dist/core/ConvaiClient.js +276 -167
  4. package/dist/core/ConvaiClient.js.map +1 -1
  5. package/dist/core/connectRequest.d.ts +1 -1
  6. package/dist/core/connectRequest.d.ts.map +1 -1
  7. package/dist/core/connectRequest.js.map +1 -1
  8. package/dist/core/types.d.ts +50 -2
  9. package/dist/core/types.d.ts.map +1 -1
  10. package/dist/core/types.js.map +1 -1
  11. package/dist/react/components/rtc-widget/components/AudioVisualizer.d.ts.map +1 -1
  12. package/dist/react/components/rtc-widget/components/AudioVisualizer.js +28 -1
  13. package/dist/react/components/rtc-widget/components/AudioVisualizer.js.map +1 -1
  14. package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts.map +1 -1
  15. package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js +93 -49
  16. package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js.map +1 -1
  17. package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
  18. package/dist/react/hooks/useConvaiClient.js +1 -0
  19. package/dist/react/hooks/useConvaiClient.js.map +1 -1
  20. package/dist/vanilla/AudioRenderer.d.ts +3 -1
  21. package/dist/vanilla/AudioRenderer.d.ts.map +1 -1
  22. package/dist/vanilla/AudioRenderer.js +2 -2
  23. package/dist/vanilla/AudioRenderer.js.map +1 -1
  24. package/dist/vanilla/ConvaiWidget.d.ts.map +1 -1
  25. package/dist/vanilla/ConvaiWidget.js +575 -111
  26. package/dist/vanilla/ConvaiWidget.js.map +1 -1
  27. package/dist/vanilla/icons.d.ts.map +1 -1
  28. package/dist/vanilla/icons.js +62 -15
  29. package/dist/vanilla/icons.js.map +1 -1
  30. package/dist/vanilla/index.d.ts +1 -1
  31. package/dist/vanilla/index.d.ts.map +1 -1
  32. package/dist/vanilla/index.js.map +1 -1
  33. package/dist/vanilla/styles.d.ts +18 -1
  34. package/dist/vanilla/styles.d.ts.map +1 -1
  35. package/dist/vanilla/styles.js +105 -29
  36. package/dist/vanilla/styles.js.map +1 -1
  37. package/dist/vanilla/types.d.ts +74 -0
  38. package/dist/vanilla/types.d.ts.map +1 -1
  39. package/dist/vanilla/types.js.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.d.ts.map +1 -1
  42. package/dist/version.js +1 -1
  43. package/dist/version.js.map +1 -1
  44. package/package.json +14 -7
@@ -1,5 +1,5 @@
1
1
  import { Room } from "livekit-client";
2
- import { ConvaiConfig, ConvaiClientState, ChatMessage, IConvaiClient, AudioControls, VideoControls, ScreenShareControls, DynamicInfo, ContextUpdateOptions, UploadFileOptions, WebSocketSessionFactory, RespondModeUpdateOptions, UpdateSceneMetadataOptions, VisionStatusOptions, VisionTriggerOptions } from "./types";
2
+ import { ConvaiConfig, ConvaiClientState, ChatMessage, IConvaiClient, AudioControls, VideoControls, ScreenShareControls, DynamicInfo, ContextUpdateOptions, UploadFileOptions, WebSocketSessionFactory, RespondModeUpdateOptions, UpdateSceneMetadataOptions, VisionStatusOptions, VisionTriggerOptions, ConnectionData } from "./types";
3
3
  import { EventEmitter } from "./EventEmitter";
4
4
  import { BlendshapeQueue } from "./BlendshapeQueue";
5
5
  import { MemoryManager } from "./MemoryManager";
@@ -126,6 +126,45 @@ export declare class ConvaiClient extends EventEmitter implements IConvaiClient
126
126
  * Connect to a Convai character
127
127
  */
128
128
  connect(config?: ConvaiConfig): Promise<void>;
129
+ /**
130
+ * Complete a connection from an already-fetched /connect response body.
131
+ *
132
+ * `connect()` fetches /connect and then calls this internally. Call it
133
+ * directly when the response was obtained elsewhere — a server-side session
134
+ * manager, or the embed's connect-proxy flow, where the API key must never
135
+ * reach the browser.
136
+ *
137
+ * @example
138
+ * // Server (customer's backend): holds the API key, calls Convai's
139
+ * // /connect, and relays the response body verbatim.
140
+ * // Browser: never sees the API key, only the relayed response.
141
+ * const data = await fetch('/api/convai-connect', {
142
+ * method: 'POST',
143
+ * headers: { 'Content-Type': 'application/json' },
144
+ * body: JSON.stringify({ characterId: 'your-character-id' }),
145
+ * }).then(r => r.json())
146
+ * await client.connectWithConnectionData(data)
147
+ */
148
+ connectWithConnectionData(data: ConnectionData, config?: ConvaiConfig): Promise<void>;
149
+ /**
150
+ * Add default URL, apply RTVI-logging config, and validate that either an
151
+ * API key or auth token plus a character ID are present.
152
+ * Shared by connect() and connectWithConnectionData() so both derive
153
+ * `configWithDefaults` identically.
154
+ *
155
+ * `connect()` performs its own /connect fetch and always needs a credential
156
+ * to do so, so it uses the default `requireCredential: true`.
157
+ * `connectWithConnectionData()` consumes a response obtained elsewhere (e.g.
158
+ * a proxy that holds the credential server-side) and passes
159
+ * `requireCredential: false` — a character ID is still required either way.
160
+ */
161
+ private applyConfigDefaults;
162
+ /**
163
+ * Consume a /connect response and bring up the transport.
164
+ * `connect()` calls this after its own fetch; external callers use it
165
+ * when the response came from a proxy.
166
+ */
167
+ private consumeConnectionData;
129
168
  private cleanupWebSocketSessionAfterConnectFailure;
130
169
  /**
131
170
  * RTVI client-ready handshake. Server-side gates bot-ready emission on
@@ -1 +1 @@
1
- {"version":3,"file":"ConvaiClient.d.ts","sourceRoot":"","sources":["../../src/core/ConvaiClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ConvaiClient.d.ts","sourceRoot":"","sources":["../../src/core/ConvaiClient.ts"],"names":[],"mappings":"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"}
@@ -413,18 +413,7 @@ export class ConvaiClient extends EventEmitter {
413
413
  finalConfig.characterSessionId !== "-1") {
414
414
  this._characterSessionId = finalConfig.characterSessionId;
415
415
  }
416
- // Add default URL if not provided
417
- const configWithDefaults = {
418
- url: "https://realtime-api.convai.com",
419
- ...finalConfig,
420
- };
421
- this._logRtviMessages = configWithDefaults.logRtviMessages !== false;
422
- this._messageHandler.setRtviMessageLogging(this._logRtviMessages);
423
- const hasApiKey = Boolean(configWithDefaults.apiKey);
424
- const hasAuthToken = Boolean(configWithDefaults.authToken);
425
- if ((!hasApiKey && !hasAuthToken) || !configWithDefaults.characterId) {
426
- throw new Error("Either apiKey or authToken is required, and characterId is required");
427
- }
416
+ const configWithDefaults = this.applyConfigDefaults(finalConfig);
428
417
  this.updateState({ isConnecting: true });
429
418
  try {
430
419
  // Store connection config
@@ -565,177 +554,297 @@ export class ConvaiClient extends EventEmitter {
565
554
  throw new Error(errorMessage);
566
555
  }
567
556
  const connectionData = await response.json();
568
- // Capture session identifiers from /connect response
569
- if (connectionData.character_session_id) {
570
- this._characterSessionId = connectionData.character_session_id;
571
- this._storedConfig = {
572
- ...this._storedConfig,
573
- characterSessionId: connectionData.character_session_id,
574
- };
557
+ return await this.consumeConnectionData(connectionData, configWithDefaults);
558
+ }
559
+ catch (error) {
560
+ await this.cleanupWebSocketSessionAfterConnectFailure();
561
+ this.updateState({
562
+ isConnected: false,
563
+ isConnecting: false,
564
+ });
565
+ this.emit("error", error);
566
+ throw error;
567
+ }
568
+ }
569
+ /**
570
+ * Complete a connection from an already-fetched /connect response body.
571
+ *
572
+ * `connect()` fetches /connect and then calls this internally. Call it
573
+ * directly when the response was obtained elsewhere — a server-side session
574
+ * manager, or the embed's connect-proxy flow, where the API key must never
575
+ * reach the browser.
576
+ *
577
+ * @example
578
+ * // Server (customer's backend): holds the API key, calls Convai's
579
+ * // /connect, and relays the response body verbatim.
580
+ * // Browser: never sees the API key, only the relayed response.
581
+ * const data = await fetch('/api/convai-connect', {
582
+ * method: 'POST',
583
+ * headers: { 'Content-Type': 'application/json' },
584
+ * body: JSON.stringify({ characterId: 'your-character-id' }),
585
+ * }).then(r => r.json())
586
+ * await client.connectWithConnectionData(data)
587
+ */
588
+ async connectWithConnectionData(data, config) {
589
+ const finalConfig = config || this._storedConfig;
590
+ if (!finalConfig) {
591
+ throw new Error("No configuration provided. Pass config to connectWithConnectionData() or store it in the client");
592
+ }
593
+ this._storedConfig = { ...finalConfig };
594
+ // Use characterSessionId from config when reconnecting (so we resume the same session)
595
+ if (finalConfig.characterSessionId &&
596
+ finalConfig.characterSessionId !== "-1") {
597
+ this._characterSessionId = finalConfig.characterSessionId;
598
+ }
599
+ const configWithDefaults = this.applyConfigDefaults(finalConfig, {
600
+ requireCredential: false,
601
+ });
602
+ // Symmetric with connect(): the catch below emits isConnecting: false, so
603
+ // without this a UI bound to stateChange never sees a connecting state on
604
+ // the proxy path -- and on failure receives a transition out of a state it
605
+ // was never told about. consumeConnectionData() clears the flag on success.
606
+ this.updateState({ isConnecting: true });
607
+ try {
608
+ return await this.consumeConnectionData(data, configWithDefaults);
609
+ }
610
+ catch (error) {
611
+ await this.cleanupWebSocketSessionAfterConnectFailure();
612
+ this.updateState({
613
+ isConnected: false,
614
+ isConnecting: false,
615
+ });
616
+ this.emit("error", error);
617
+ throw error;
618
+ }
619
+ }
620
+ /**
621
+ * Add default URL, apply RTVI-logging config, and validate that either an
622
+ * API key or auth token plus a character ID are present.
623
+ * Shared by connect() and connectWithConnectionData() so both derive
624
+ * `configWithDefaults` identically.
625
+ *
626
+ * `connect()` performs its own /connect fetch and always needs a credential
627
+ * to do so, so it uses the default `requireCredential: true`.
628
+ * `connectWithConnectionData()` consumes a response obtained elsewhere (e.g.
629
+ * a proxy that holds the credential server-side) and passes
630
+ * `requireCredential: false` — a character ID is still required either way.
631
+ */
632
+ applyConfigDefaults(finalConfig, opts = {}) {
633
+ const { requireCredential = true } = opts;
634
+ // Add default URL if not provided
635
+ const configWithDefaults = {
636
+ url: "https://realtime-api.convai.com",
637
+ ...finalConfig,
638
+ };
639
+ this._logRtviMessages = configWithDefaults.logRtviMessages !== false;
640
+ this._messageHandler.setRtviMessageLogging(this._logRtviMessages);
641
+ const hasApiKey = Boolean(configWithDefaults.apiKey);
642
+ const hasAuthToken = Boolean(configWithDefaults.authToken);
643
+ const missingCredential = requireCredential && !hasApiKey && !hasAuthToken;
644
+ if (missingCredential || !configWithDefaults.characterId) {
645
+ throw new Error("Either apiKey or authToken is required, and characterId is required");
646
+ }
647
+ return configWithDefaults;
648
+ }
649
+ /**
650
+ * Consume a /connect response and bring up the transport.
651
+ * `connect()` calls this after its own fetch; external callers use it
652
+ * when the response came from a proxy.
653
+ */
654
+ async consumeConnectionData(connectionData, configWithDefaults) {
655
+ // Store connection config (mirrors connect()'s pre-fetch setup so this
656
+ // method is self-sufficient when called directly, without connect()
657
+ // having run first).
658
+ //
659
+ // Known race, judged acceptable: when called from connect(), that method
660
+ // sets these same fields once, before the /connect fetch, then awaits
661
+ // the network round trip. If disconnect() -> clearAllManagers() lands in
662
+ // that await window, the pre-split code left these fields null (nothing
663
+ // re-set them post-fetch), whereas re-deriving them here re-arms them
664
+ // once the response arrives. Checked `_isDisconnecting` as a guard: it
665
+ // doesn't cover this window — it's flipped back to false (in a
666
+ // `finally`) before clearAllManagers() runs on the websocket path, and
667
+ // disconnect()'s LiveKit-room path never sets it at all — so it can't
668
+ // reliably detect "a disconnect landed mid-connect" here. Even
669
+ // pre-split, connect() never checked for a concurrent disconnect after
670
+ // the fetch resolved; it always went on to establish a new room/session
671
+ // regardless. Re-arming these fields keeps them consistent with the
672
+ // connection actually being brought up in that case, rather than
673
+ // silently stale. Building a proper cancellation token for
674
+ // connect()-vs-disconnect() races is out of scope for this additive
675
+ // split.
676
+ this._apiKey = configWithDefaults.apiKey ?? null;
677
+ this._authToken = configWithDefaults.authToken ?? null;
678
+ this._characterId = configWithDefaults.characterId;
679
+ const connType = configWithDefaults.enableVideo ? "video" : "audio";
680
+ this._connectionType = connType;
681
+ const transportType = configWithDefaults.transport ?? "livekit";
682
+ this._activeTransport = transportType;
683
+ const effectiveVisionInputConfig = resolveVisionInputConfig(configWithDefaults);
684
+ // Capture session identifiers from /connect response
685
+ if (connectionData.character_session_id) {
686
+ this._characterSessionId = connectionData.character_session_id;
687
+ this._storedConfig = {
688
+ ...this._storedConfig,
689
+ characterSessionId: connectionData.character_session_id,
690
+ };
691
+ }
692
+ if (connectionData.end_user_id) {
693
+ this._endUserId = connectionData.end_user_id;
694
+ }
695
+ if (connectionData.end_user_metadata) {
696
+ this._endUserMetadata = connectionData.end_user_metadata;
697
+ }
698
+ if (transportType === "websocket") {
699
+ // ── WebSocket (Pipecat) transport path ────────────────────────────
700
+ // Server returns the WebSocket URL in room_url (same field as LiveKit).
701
+ const wsUrl = connectionData.room_url;
702
+ if (!wsUrl) {
703
+ throw new Error("No WebSocket URL returned from /connect endpoint (expected room_url)");
575
704
  }
576
- if (connectionData.end_user_id) {
577
- this._endUserId = connectionData.end_user_id;
705
+ if (!_wsFactory) {
706
+ throw new Error("[ConvaiClient] WebSocket transport is not registered. " +
707
+ "Add `import '@convai/web-sdk/vanilla/websocket'` before calling connect().");
578
708
  }
579
- if (connectionData.end_user_metadata) {
580
- this._endUserMetadata = connectionData.end_user_metadata;
709
+ // Always enable mic at construction so WavMediaManager initializes the
710
+ // audio stream during connect(). If startWithAudioOn is false, we mute
711
+ // after the connection is established (enableMic(false) works once the
712
+ // stream exists, but not before it is initialized).
713
+ this._wsSession = _wsFactory((payload) => this._messageHandler.handleDataReceivedPublic(payload), true);
714
+ const webSocketVisionEnabled = configWithDefaults.enableVideo === true &&
715
+ effectiveVisionInputConfig?.enabled !== false;
716
+ if (webSocketVisionEnabled) {
717
+ this._videoManager.setWebSocketVisionMessageSender((type, data) => {
718
+ if (this._activeTransport !== "websocket" ||
719
+ !this._wsSession?.isConnected) {
720
+ return;
721
+ }
722
+ this._wsSession.sendMessage(type, data);
723
+ });
581
724
  }
582
- if (transportType === "websocket") {
583
- // ── WebSocket (Pipecat) transport path ────────────────────────────
584
- // Server returns the WebSocket URL in room_url (same field as LiveKit).
585
- const wsUrl = connectionData.room_url;
586
- if (!wsUrl) {
587
- throw new Error("No WebSocket URL returned from /connect endpoint (expected room_url)");
588
- }
589
- if (!_wsFactory) {
590
- throw new Error("[ConvaiClient] WebSocket transport is not registered. " +
591
- "Add `import '@convai/web-sdk/vanilla/websocket'` before calling connect().");
592
- }
593
- // Always enable mic at construction so WavMediaManager initializes the
594
- // audio stream during connect(). If startWithAudioOn is false, we mute
595
- // after the connection is established (enableMic(false) works once the
596
- // stream exists, but not before it is initialized).
597
- this._wsSession = _wsFactory((payload) => this._messageHandler.handleDataReceivedPublic(payload), true);
598
- const webSocketVisionEnabled = configWithDefaults.enableVideo === true &&
599
- effectiveVisionInputConfig?.enabled !== false;
600
- if (webSocketVisionEnabled) {
601
- this._videoManager.setWebSocketVisionMessageSender((type, data) => {
602
- if (this._activeTransport !== "websocket" ||
603
- !this._wsSession?.isConnected) {
604
- return;
605
- }
606
- this._wsSession.sendMessage(type, data);
607
- });
608
- }
609
- else {
610
- this._videoManager.setWebSocketVisionDisabled();
725
+ else {
726
+ this._videoManager.setWebSocketVisionDisabled();
727
+ }
728
+ const wsSession = this._wsSession;
729
+ wsSession.on("botAudioTrack", (track) => {
730
+ this.emit("botAudioTrack", track);
731
+ });
732
+ wsSession.on("disconnected", () => {
733
+ if (this._wsSession !== wsSession || this._isDisconnecting) {
734
+ return;
611
735
  }
612
- const wsSession = this._wsSession;
613
- wsSession.on("botAudioTrack", (track) => {
614
- this.emit("botAudioTrack", track);
615
- });
616
- wsSession.on("disconnected", () => {
617
- if (this._wsSession !== wsSession || this._isDisconnecting) {
736
+ this._connectionStateHandler.handleDisconnected();
737
+ this.clearAllManagers();
738
+ });
739
+ this._audioManager.setWebSocketSession(this._wsSession);
740
+ // Wait for WebSocket transport to connect (fires before bot-ready)
741
+ await new Promise((resolve, reject) => {
742
+ let settled = false;
743
+ const cleanupConnectListeners = () => {
744
+ wsSession.off("connected", onConnected);
745
+ wsSession.off("error", onError);
746
+ wsSession.off("disconnected", onDisconnected);
747
+ };
748
+ const settle = (callback) => {
749
+ if (settled) {
618
750
  return;
619
751
  }
620
- this._connectionStateHandler.handleDisconnected();
621
- this.clearAllManagers();
622
- });
623
- this._audioManager.setWebSocketSession(this._wsSession);
624
- // Wait for WebSocket transport to connect (fires before bot-ready)
625
- await new Promise((resolve, reject) => {
626
- let settled = false;
627
- const cleanupConnectListeners = () => {
628
- wsSession.off("connected", onConnected);
629
- wsSession.off("error", onError);
630
- wsSession.off("disconnected", onDisconnected);
631
- };
632
- const settle = (callback) => {
633
- if (settled) {
634
- return;
635
- }
636
- settled = true;
637
- cleanupConnectListeners();
638
- callback();
639
- };
640
- const onConnected = () => {
641
- settle(resolve);
642
- };
643
- const onError = (err) => {
644
- settle(() => {
645
- reject(err instanceof Error ? err : new Error(String(err)));
646
- });
647
- };
648
- const onDisconnected = () => {
649
- settle(() => {
650
- reject(new Error("WebSocket disconnected before connect completed"));
651
- });
652
- };
653
- wsSession.on("connected", onConnected);
654
- wsSession.on("error", onError);
655
- wsSession.on("disconnected", onDisconnected);
656
- // connectWithUrl calls initDevices() then connect({ wsUrl }) — matches sandbox flow
657
- wsSession.connectWithUrl(wsUrl).catch((err) => {
658
- settle(() => {
659
- reject(err instanceof Error ? err : new Error(String(err)));
660
- });
752
+ settled = true;
753
+ cleanupConnectListeners();
754
+ callback();
755
+ };
756
+ const onConnected = () => {
757
+ settle(resolve);
758
+ };
759
+ const onError = (err) => {
760
+ settle(() => {
761
+ reject(err instanceof Error ? err : new Error(String(err)));
762
+ });
763
+ };
764
+ const onDisconnected = () => {
765
+ settle(() => {
766
+ reject(new Error("WebSocket disconnected before connect completed"));
767
+ });
768
+ };
769
+ wsSession.on("connected", onConnected);
770
+ wsSession.on("error", onError);
771
+ wsSession.on("disconnected", onDisconnected);
772
+ // connectWithUrl calls initDevices() then connect({ wsUrl }) — matches sandbox flow
773
+ wsSession.connectWithUrl(wsUrl).catch((err) => {
774
+ settle(() => {
775
+ reject(err instanceof Error ? err : new Error(String(err)));
661
776
  });
662
777
  });
663
- // For WebSocket, mic is already streaming after connect.
664
- // Only mute if the caller explicitly sets startWithAudioOn: false.
665
- // Defaulting to on matches the sandbox behavior and ensures the bot
666
- // sees audio when it initializes (avoids server "tap mic to talk" prompt).
667
- if (configWithDefaults.startWithAudioOn === false) {
668
- this._wsSession.enableMic(false);
669
- this._audioManager.syncWsAudioState(false);
670
- }
671
- else {
672
- this._audioManager.syncWsAudioState(true);
673
- }
674
- if (webSocketVisionEnabled &&
675
- configWithDefaults.enableVideo &&
676
- configWithDefaults.startWithVideoOn) {
677
- await this._videoManager.enableVideo();
678
- }
778
+ });
779
+ // For WebSocket, mic is already streaming after connect.
780
+ // Only mute if the caller explicitly sets startWithAudioOn: false.
781
+ // Defaulting to on matches the sandbox behavior and ensures the bot
782
+ // sees audio when it initializes (avoids server "tap mic to talk" prompt).
783
+ if (configWithDefaults.startWithAudioOn === false) {
784
+ this._wsSession.enableMic(false);
785
+ this._audioManager.syncWsAudioState(false);
679
786
  }
680
787
  else {
681
- this._videoManager.setWebSocketVisionMessageSender(null);
682
- // ── LiveKit transport path (default) ─────────────────────────────
683
- await this._room.connect(connectionData.room_url, connectionData.token, {
684
- rtcConfig: {
685
- iceTransportPolicy: "relay",
686
- },
687
- });
688
- if (configWithDefaults.startWithAudioOn) {
689
- await this._room.localParticipant.setMicrophoneEnabled(true, {
690
- echoCancellation: this._audioSettings.echoCancellation,
691
- noiseSuppression: this._audioSettings.noiseSuppression,
692
- autoGainControl: this._audioSettings.autoGainControl,
693
- sampleRate: this._audioSettings.sampleRate,
694
- channelCount: this._audioSettings.channelCount,
695
- });
696
- }
697
- if (configWithDefaults.enableVideo &&
698
- configWithDefaults.startWithVideoOn) {
699
- await this._room.localParticipant.setCameraEnabled(true);
700
- }
701
- this._audioManager.syncStateFromRoom({ emit: true });
702
- this._participantSid = this._room.localParticipant.sid;
788
+ this._audioManager.syncWsAudioState(true);
703
789
  }
704
- // Apply custom mapper to blendshape queue if provided
705
- if (configWithDefaults.blendshapeConfig?.customMapper) {
706
- this.blendshapeQueue.setMapper(configWithDefaults.blendshapeConfig.customMapper);
790
+ if (webSocketVisionEnabled &&
791
+ configWithDefaults.enableVideo &&
792
+ configWithDefaults.startWithVideoOn) {
793
+ await this._videoManager.enableVideo();
707
794
  }
708
- this.updateState({
709
- isConnected: true,
710
- isConnecting: false,
711
- endUserId: this._endUserId,
712
- endUserMetadata: this._endUserMetadata,
795
+ }
796
+ else {
797
+ this._videoManager.setWebSocketVisionMessageSender(null);
798
+ // ── LiveKit transport path (default) ─────────────────────────────
799
+ await this._room.connect(connectionData.room_url, connectionData.token, {
800
+ rtcConfig: {
801
+ // Defaults to "relay" (TURN-only) for reliable NAT traversal
802
+ // against Convai's hosted LiveKit. Set iceTransportPolicy: "all"
803
+ // when pointing at a LiveKit without TURN (e.g. local dev).
804
+ iceTransportPolicy: configWithDefaults.iceTransportPolicy ?? "relay",
805
+ },
713
806
  });
714
- // Initialize memory manager if we have authentication and endUserId
715
- if (this._endUserId) {
716
- if (this._apiKey) {
717
- // Use API key authentication
718
- this._memoryManager = new MemoryManager(this._apiKey, this._characterId, this._endUserId, undefined, // Use default base URL
719
- false);
720
- }
721
- else if (this._authToken) {
722
- // Use auth token authentication
723
- this._memoryManager = new MemoryManager(this._authToken, this._characterId, this._endUserId, undefined, // Use default base URL
724
- true);
725
- }
807
+ if (configWithDefaults.startWithAudioOn) {
808
+ await this._room.localParticipant.setMicrophoneEnabled(true, {
809
+ echoCancellation: this._audioSettings.echoCancellation,
810
+ noiseSuppression: this._audioSettings.noiseSuppression,
811
+ autoGainControl: this._audioSettings.autoGainControl,
812
+ sampleRate: this._audioSettings.sampleRate,
813
+ channelCount: this._audioSettings.channelCount,
814
+ });
815
+ }
816
+ if (configWithDefaults.enableVideo &&
817
+ configWithDefaults.startWithVideoOn) {
818
+ await this._room.localParticipant.setCameraEnabled(true);
726
819
  }
727
- this.emit("connect");
728
- this._startClientReadyHandshake();
820
+ this._audioManager.syncStateFromRoom({ emit: true });
821
+ this._participantSid = this._room.localParticipant.sid;
729
822
  }
730
- catch (error) {
731
- await this.cleanupWebSocketSessionAfterConnectFailure();
732
- this.updateState({
733
- isConnected: false,
734
- isConnecting: false,
735
- });
736
- this.emit("error", error);
737
- throw error;
823
+ // Apply custom mapper to blendshape queue if provided
824
+ if (configWithDefaults.blendshapeConfig?.customMapper) {
825
+ this.blendshapeQueue.setMapper(configWithDefaults.blendshapeConfig.customMapper);
826
+ }
827
+ this.updateState({
828
+ isConnected: true,
829
+ isConnecting: false,
830
+ endUserId: this._endUserId,
831
+ endUserMetadata: this._endUserMetadata,
832
+ });
833
+ // Initialize memory manager if we have authentication and endUserId
834
+ if (this._endUserId) {
835
+ if (this._apiKey) {
836
+ // Use API key authentication
837
+ this._memoryManager = new MemoryManager(this._apiKey, this._characterId, this._endUserId, undefined, // Use default base URL
838
+ false);
839
+ }
840
+ else if (this._authToken) {
841
+ // Use auth token authentication
842
+ this._memoryManager = new MemoryManager(this._authToken, this._characterId, this._endUserId, undefined, // Use default base URL
843
+ true);
844
+ }
738
845
  }
846
+ this.emit("connect");
847
+ this._startClientReadyHandshake();
739
848
  }
740
849
  async cleanupWebSocketSessionAfterConnectFailure() {
741
850
  if (this._activeTransport !== "websocket" || !this._wsSession) {