@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.
- package/dist/core/ConvaiClient.d.ts +40 -1
- package/dist/core/ConvaiClient.d.ts.map +1 -1
- package/dist/core/ConvaiClient.js +276 -167
- package/dist/core/ConvaiClient.js.map +1 -1
- package/dist/core/connectRequest.d.ts +1 -1
- package/dist/core/connectRequest.d.ts.map +1 -1
- package/dist/core/connectRequest.js.map +1 -1
- package/dist/core/types.d.ts +50 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/react/components/rtc-widget/components/AudioVisualizer.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/AudioVisualizer.js +28 -1
- package/dist/react/components/rtc-widget/components/AudioVisualizer.js.map +1 -1
- package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js +93 -49
- package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js.map +1 -1
- package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
- package/dist/react/hooks/useConvaiClient.js +1 -0
- package/dist/react/hooks/useConvaiClient.js.map +1 -1
- package/dist/vanilla/AudioRenderer.d.ts +3 -1
- package/dist/vanilla/AudioRenderer.d.ts.map +1 -1
- package/dist/vanilla/AudioRenderer.js +2 -2
- package/dist/vanilla/AudioRenderer.js.map +1 -1
- package/dist/vanilla/ConvaiWidget.d.ts.map +1 -1
- package/dist/vanilla/ConvaiWidget.js +575 -111
- package/dist/vanilla/ConvaiWidget.js.map +1 -1
- package/dist/vanilla/icons.d.ts.map +1 -1
- package/dist/vanilla/icons.js +62 -15
- package/dist/vanilla/icons.js.map +1 -1
- package/dist/vanilla/index.d.ts +1 -1
- package/dist/vanilla/index.d.ts.map +1 -1
- package/dist/vanilla/index.js.map +1 -1
- package/dist/vanilla/styles.d.ts +18 -1
- package/dist/vanilla/styles.d.ts.map +1 -1
- package/dist/vanilla/styles.js +105 -29
- package/dist/vanilla/styles.js.map +1 -1
- package/dist/vanilla/types.d.ts +74 -0
- package/dist/vanilla/types.d.ts.map +1 -1
- package/dist/vanilla/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- 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":"AAAA,OAAO,EACL,IAAI,EAML,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,WAAW,EAEX,aAAa,EACb,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EAIjB,uBAAuB,EACvB,wBAAwB,EAExB,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,
|
|
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
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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 (
|
|
577
|
-
|
|
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
|
-
|
|
580
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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.
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
790
|
+
if (webSocketVisionEnabled &&
|
|
791
|
+
configWithDefaults.enableVideo &&
|
|
792
|
+
configWithDefaults.startWithVideoOn) {
|
|
793
|
+
await this._videoManager.enableVideo();
|
|
707
794
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
|
728
|
-
this.
|
|
820
|
+
this._audioManager.syncStateFromRoom({ emit: true });
|
|
821
|
+
this._participantSid = this._room.localParticipant.sid;
|
|
729
822
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
this.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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) {
|