@craftedxp/voice-js 0.10.0 → 0.12.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/README.md +21 -19
- package/dist/assistant.d.mts +3 -3
- package/dist/assistant.d.ts +3 -3
- package/dist/assistant.js +210 -22
- package/dist/assistant.js.map +1 -1
- package/dist/assistant.mjs +2 -1
- package/dist/browser.d.mts +3 -3
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +201 -34
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +3 -2
- package/dist/browser.mjs.map +1 -1
- package/dist/{chunk-3BYNC25N.mjs → chunk-C2U6V5NI.mjs} +191 -23
- package/dist/chunk-C2U6V5NI.mjs.map +1 -0
- package/dist/chunk-OS2GZAR7.mjs +18 -0
- package/dist/chunk-OS2GZAR7.mjs.map +1 -0
- package/dist/{chunk-LV7JGPYW.mjs → chunk-VSY43NGD.mjs} +6 -13
- package/dist/chunk-VSY43NGD.mjs.map +1 -0
- package/dist/{config-D2TbvIqT.d.mts → config-DfbPB1Tq.d.mts} +22 -3
- package/dist/{config-D2TbvIqT.d.ts → config-DfbPB1Tq.d.ts} +22 -3
- package/dist/embed.iife.js +42 -5
- package/dist/{incomingCall-CfRRzj2P.d.ts → incomingCall-2B3XB8pM.d.mts} +3 -1
- package/dist/{incomingCall-CfRRzj2P.d.mts → incomingCall-CYmeQkIw.d.ts} +3 -1
- package/dist/node.d.mts +26 -3
- package/dist/node.d.ts +26 -3
- package/dist/node.js +11 -2
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +11 -2
- package/dist/node.mjs.map +1 -1
- package/dist/room.js +15 -12
- package/dist/room.js.map +1 -1
- package/dist/room.mjs +2 -1
- package/dist/transcribe.d.mts +2 -2
- package/dist/transcribe.d.ts +2 -2
- package/dist/transcribe.js +209 -21
- package/dist/transcribe.js.map +1 -1
- package/dist/transcribe.mjs +2 -1
- package/dist/transcribe.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-3BYNC25N.mjs.map +0 -1
- package/dist/chunk-LV7JGPYW.mjs.map +0 -1
package/dist/browser.mjs
CHANGED
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
normalizeConfig,
|
|
10
10
|
parseIncomingCall,
|
|
11
11
|
startTextSession
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-C2U6V5NI.mjs";
|
|
13
13
|
import {
|
|
14
14
|
joinRoom
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VSY43NGD.mjs";
|
|
16
|
+
import "./chunk-OS2GZAR7.mjs";
|
|
16
17
|
|
|
17
18
|
// src/browser.ts
|
|
18
19
|
function configureVoiceClient2(config) {
|
package/dist/browser.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts"],"sourcesContent":["// Browser entry — `import { configureVoiceClient } from '@craftedxp/voice-js'`\n//\n// @deprecated Import from '@craftedxp/voice-js/assistant', '@craftedxp/voice-js/room',\n// or '@craftedxp/voice-js/transcribe' instead. This barrel will be removed in 1.0.\n//\n// Backwards-compatible re-export barrel. The factory returned here also\n// exposes `joinRoom` as a method so consumers who call `voice.joinRoom()`\n// keep working unchanged.\n//\n// Pre-injects:\n// - native `WebSocket` (globalThis.WebSocket)\n// - browser audio adapter (AudioContext + AudioWorklet + getUserMedia)\n//\n// Electron renderer + browser tabs both pick this entry via the\n// `browser` condition in package.json `exports`.\n\nimport { configureVoiceClient as configureAssistant } from './assistant'\nimport { joinRoom as joinRoomImpl } from './room'\nimport { normalizeConfig } from './config'\nimport type { VoiceClientConfig, VoiceClientFactory } from './config'\nimport type { JoinRoomOptions, RoomSession } from './room'\n\n/**\n * One-time SDK setup. Returns a factory you call `startCall` on for\n * every voice call. Also exposes `joinRoom` as a method for multi-party\n * room joining (back-compat; prefer `@craftedxp/voice-js/room` for new code).\n */\nexport function configureVoiceClient(config: VoiceClientConfig): VoiceClientFactory & {\n joinRoom: (opts: Omit<JoinRoomOptions, 'apiBase'>) => Promise<RoomSession>\n} {\n const normalized = normalizeConfig(config)\n const factory = configureAssistant(normalized)\n return {\n ...factory,\n // Re-compose joinRoom onto the factory so barrel consumers who call\n // `voice.joinRoom(...)` keep working unchanged.\n joinRoom: (opts: Omit<JoinRoomOptions, 'apiBase'>) =>\n joinRoomImpl({ apiBase: normalized.apiBase, ...opts }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public re-exports — assistant surface (types + advanced primitives).\n// ---------------------------------------------------------------------------\n\nexport type {\n Call,\n ClientTool,\n ClientToolMap,\n FetchToken,\n FetchTokenArgs,\n FetchTokenResult,\n StartCallOptions,\n VoiceClientConfig,\n VoiceClientFactory,\n} from './config'\n\nexport type {\n CallEndEvent,\n CallEndReason,\n CallError,\n CallErrorCode,\n CallState,\n TranscriptEntry,\n VolumeEvent,\n} from './protocol'\n\n// Low-level primitives — exposed for advanced consumers who want to\n// drive just the mic, just the playback, or build a custom transport on\n// top of the protocol decoder. Most users should stick to\n// `configureVoiceClient().startCall()`.\nexport { createAudioCapture } from './AudioCapture'\nexport type { CaptureController, CaptureOptions, OnChunk, OnError, OnVolume } from './AudioCapture'\n\nexport { createAudioPlayback } from './AudioPlayback'\nexport type { PlaybackController, PlaybackOptions, OnAgentSpeakingChange } from './AudioPlayback'\n\nexport { createReconnectingWebSocket } from './ReconnectingWebSocket'\nexport type {\n ReconnectingWebSocket,\n RWSEvent,\n RWSOptions,\n WebSocketFactory,\n WebSocketLike,\n} from './ReconnectingWebSocket'\n\nexport { handleServerMessage, createProtocolState, buildWsUrl } from './protocol'\nexport type { ProtocolState, ProtocolCallbacks, ServerMessage } from './protocol'\n\n// Multi-party room (Phase 7 video) surface. Exposed as a method on the\n// factory (`voice.joinRoom({...})`) and re-exported here as a free\n// function for consumers that prefer not to instantiate a full factory\n// just to attend a room as a guest.\nexport { joinRoom } from './room'\nexport type {\n JoinRoomOptions,\n RoomEventName,\n RoomEventPayloads,\n RoomParticipantInfo,\n RoomSession,\n} from './room'\nexport type { SystemMessage, TranscriptMessage, AnalysisMessage } from './roomProtocol'\n\nexport { parseIncomingCall } from './incomingCall'\nexport type { IncomingCallPayload } from './incomingCall'\n\nexport { startTextSession } from './textSession'\nexport type { ChatEvent, StartTextSessionOpts, TextSession } from './textSession'\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts"],"sourcesContent":["// Browser entry — `import { configureVoiceClient } from '@craftedxp/voice-js'`\n//\n// @deprecated Import from '@craftedxp/voice-js/assistant', '@craftedxp/voice-js/room',\n// or '@craftedxp/voice-js/transcribe' instead. This barrel will be removed in 1.0.\n//\n// Backwards-compatible re-export barrel. The factory returned here also\n// exposes `joinRoom` as a method so consumers who call `voice.joinRoom()`\n// keep working unchanged.\n//\n// Pre-injects:\n// - native `WebSocket` (globalThis.WebSocket)\n// - browser audio adapter (AudioContext + AudioWorklet + getUserMedia)\n//\n// Electron renderer + browser tabs both pick this entry via the\n// `browser` condition in package.json `exports`.\n\nimport { configureVoiceClient as configureAssistant } from './assistant'\nimport { joinRoom as joinRoomImpl } from './room'\nimport { normalizeConfig } from './config'\nimport type { VoiceClientConfig, VoiceClientFactory } from './config'\nimport type { JoinRoomOptions, RoomSession } from './room'\n\n/**\n * One-time SDK setup. Returns a factory you call `startCall` on for\n * every voice call. Also exposes `joinRoom` as a method for multi-party\n * room joining (back-compat; prefer `@craftedxp/voice-js/room` for new code).\n */\nexport function configureVoiceClient(config: VoiceClientConfig): VoiceClientFactory & {\n joinRoom: (opts: Omit<JoinRoomOptions, 'apiBase'>) => Promise<RoomSession>\n} {\n const normalized = normalizeConfig(config)\n const factory = configureAssistant(normalized)\n return {\n ...factory,\n // Re-compose joinRoom onto the factory so barrel consumers who call\n // `voice.joinRoom(...)` keep working unchanged.\n joinRoom: (opts: Omit<JoinRoomOptions, 'apiBase'>) =>\n joinRoomImpl({ apiBase: normalized.apiBase, ...opts }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public re-exports — assistant surface (types + advanced primitives).\n// ---------------------------------------------------------------------------\n\nexport type {\n Call,\n CallTransport,\n ClientTool,\n ClientToolMap,\n FetchToken,\n FetchTokenArgs,\n FetchTokenResult,\n StartCallOptions,\n TransportChangeEvent,\n VoiceClientConfig,\n VoiceClientFactory,\n} from './config'\n\nexport type {\n CallEndEvent,\n CallEndReason,\n CallError,\n CallErrorCode,\n CallState,\n TranscriptEntry,\n VolumeEvent,\n} from './protocol'\n\n// Low-level primitives — exposed for advanced consumers who want to\n// drive just the mic, just the playback, or build a custom transport on\n// top of the protocol decoder. Most users should stick to\n// `configureVoiceClient().startCall()`.\nexport { createAudioCapture } from './AudioCapture'\nexport type { CaptureController, CaptureOptions, OnChunk, OnError, OnVolume } from './AudioCapture'\n\nexport { createAudioPlayback } from './AudioPlayback'\nexport type { PlaybackController, PlaybackOptions, OnAgentSpeakingChange } from './AudioPlayback'\n\nexport { createReconnectingWebSocket } from './ReconnectingWebSocket'\nexport type {\n ReconnectingWebSocket,\n RWSEvent,\n RWSOptions,\n WebSocketFactory,\n WebSocketLike,\n} from './ReconnectingWebSocket'\n\nexport { handleServerMessage, createProtocolState, buildWsUrl } from './protocol'\nexport type { ProtocolState, ProtocolCallbacks, ServerMessage } from './protocol'\n\n// Multi-party room (Phase 7 video) surface. Exposed as a method on the\n// factory (`voice.joinRoom({...})`) and re-exported here as a free\n// function for consumers that prefer not to instantiate a full factory\n// just to attend a room as a guest.\nexport { joinRoom } from './room'\nexport type {\n JoinRoomOptions,\n RoomEventName,\n RoomEventPayloads,\n RoomParticipantInfo,\n RoomSession,\n} from './room'\nexport type { SystemMessage, TranscriptMessage, AnalysisMessage } from './roomProtocol'\n\nexport { parseIncomingCall } from './incomingCall'\nexport type { IncomingCallPayload } from './incomingCall'\n\nexport { startTextSession } from './textSession'\nexport type { ChatEvent, StartTextSessionOpts, TextSession } from './textSession'\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2BO,SAASA,sBAAqB,QAEnC;AACA,QAAM,aAAa,gBAAgB,MAAM;AACzC,QAAM,UAAU,qBAAmB,UAAU;AAC7C,SAAO;AAAA,IACL,GAAG;AAAA;AAAA;AAAA,IAGH,UAAU,CAAC,SACT,SAAa,EAAE,SAAS,WAAW,SAAS,GAAG,KAAK,CAAC;AAAA,EACzD;AACF;","names":["configureVoiceClient"]}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadLiveKit
|
|
3
|
+
} from "./chunk-OS2GZAR7.mjs";
|
|
4
|
+
|
|
1
5
|
// src/config.ts
|
|
2
6
|
function normalizeConfig(config) {
|
|
3
7
|
if (!config) throw new Error("configureVoiceClient: config is required");
|
|
@@ -478,7 +482,8 @@ function buildWsUrl(args) {
|
|
|
478
482
|
const base = new URL(args.apiBase);
|
|
479
483
|
const proto = base.protocol === "https:" ? "wss:" : "ws:";
|
|
480
484
|
const bargeQS = args.bargeIn === false ? "&barge=off" : "";
|
|
481
|
-
|
|
485
|
+
const mediaQS = args.media === "livekit" ? "&media=livekit" : "";
|
|
486
|
+
return `${proto}//${base.host}/v1/agents/${encodeURIComponent(args.agentId)}/call?token=${encodeURIComponent(args.token)}${bargeQS}${mediaQS}`;
|
|
482
487
|
}
|
|
483
488
|
|
|
484
489
|
// src/clientTools.ts
|
|
@@ -632,7 +637,75 @@ var createClientMarksBuffer = (args) => {
|
|
|
632
637
|
};
|
|
633
638
|
};
|
|
634
639
|
|
|
640
|
+
// src/livekit/createLiveKitMedia.ts
|
|
641
|
+
var toError = (e) => e instanceof Error ? e : new Error(String(e));
|
|
642
|
+
function createLiveKitMedia(lk, opts = {}) {
|
|
643
|
+
let room = null;
|
|
644
|
+
let audioEl = null;
|
|
645
|
+
let muted = false;
|
|
646
|
+
const attach = (track) => {
|
|
647
|
+
if (track.kind !== lk.Track.Kind.Audio) return;
|
|
648
|
+
if (typeof document === "undefined") return;
|
|
649
|
+
if (!audioEl) {
|
|
650
|
+
audioEl = document.createElement("audio");
|
|
651
|
+
audioEl.autoplay = true;
|
|
652
|
+
audioEl.style.display = "none";
|
|
653
|
+
document.body.appendChild(audioEl);
|
|
654
|
+
}
|
|
655
|
+
track.attach(audioEl);
|
|
656
|
+
};
|
|
657
|
+
return {
|
|
658
|
+
connect: async (url, token) => {
|
|
659
|
+
const r = new lk.Room({ adaptiveStream: false, dynacast: false });
|
|
660
|
+
room = r;
|
|
661
|
+
r.on(lk.RoomEvent.TrackSubscribed, (track) => attach(track));
|
|
662
|
+
await r.connect(url, token);
|
|
663
|
+
if (room !== r) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
await r.localParticipant.setMicrophoneEnabled(!muted);
|
|
667
|
+
if (room !== r) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
await r.startAudio();
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
disconnect: () => {
|
|
676
|
+
const r = room;
|
|
677
|
+
room = null;
|
|
678
|
+
if (r) void r.disconnect().catch(() => void 0);
|
|
679
|
+
audioEl?.remove();
|
|
680
|
+
audioEl = null;
|
|
681
|
+
},
|
|
682
|
+
setMuted: (m) => {
|
|
683
|
+
muted = m;
|
|
684
|
+
const r = room;
|
|
685
|
+
if (!r) return;
|
|
686
|
+
void r.localParticipant.setMicrophoneEnabled(!m).catch((e) => opts.onError?.(toError(e)));
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
635
691
|
// src/VoiceClient.ts
|
|
692
|
+
var parseMediaFrame = (raw) => {
|
|
693
|
+
if (!raw.includes('"livekit_')) return null;
|
|
694
|
+
try {
|
|
695
|
+
const m = JSON.parse(raw);
|
|
696
|
+
if (m.type === "livekit_join" && typeof m.url === "string" && typeof m.token === "string") {
|
|
697
|
+
return { type: "livekit_join", url: m.url, token: m.token };
|
|
698
|
+
}
|
|
699
|
+
if (m.type === "livekit_fallback") {
|
|
700
|
+
return {
|
|
701
|
+
type: "livekit_fallback",
|
|
702
|
+
reason: typeof m.reason === "string" ? m.reason : void 0
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
return null;
|
|
708
|
+
};
|
|
636
709
|
var BrowserVoiceClient = class {
|
|
637
710
|
constructor(args) {
|
|
638
711
|
this.rws = null;
|
|
@@ -644,6 +717,9 @@ var BrowserVoiceClient = class {
|
|
|
644
717
|
this.startedAt = null;
|
|
645
718
|
this.endedFired = false;
|
|
646
719
|
this.lastError = null;
|
|
720
|
+
this.mediaMode = "ws";
|
|
721
|
+
this.lkMedia = null;
|
|
722
|
+
this.lkJoinSeen = false;
|
|
647
723
|
this.end = () => {
|
|
648
724
|
this.teardown("user_hangup");
|
|
649
725
|
};
|
|
@@ -651,11 +727,73 @@ var BrowserVoiceClient = class {
|
|
|
651
727
|
if (this.muted) return;
|
|
652
728
|
this.muted = true;
|
|
653
729
|
this.capture?.mute(true);
|
|
730
|
+
this.lkMedia?.setMuted(true);
|
|
654
731
|
};
|
|
655
732
|
this.unmute = () => {
|
|
656
733
|
if (!this.muted) return;
|
|
657
734
|
this.muted = false;
|
|
658
735
|
this.capture?.mute(false);
|
|
736
|
+
this.lkMedia?.setMuted(false);
|
|
737
|
+
};
|
|
738
|
+
this.ensureWsPlayback = async () => {
|
|
739
|
+
if (this.playback) return;
|
|
740
|
+
const createPlayback = this.args.audio?.createPlayback ?? createAudioPlayback;
|
|
741
|
+
this.playback = createPlayback({
|
|
742
|
+
onVolume: (v) => {
|
|
743
|
+
this.outputVolume = v;
|
|
744
|
+
this.args.options.onVolume?.({ input: this.inputVolume, output: v });
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
try {
|
|
748
|
+
await this.playback.resume();
|
|
749
|
+
} catch {
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
// ---------------------------------------------------------------
|
|
753
|
+
// LiveKit media lifecycle (phase 35)
|
|
754
|
+
// ---------------------------------------------------------------
|
|
755
|
+
this.joinLiveKit = async (url, token) => {
|
|
756
|
+
this.lkJoinSeen = true;
|
|
757
|
+
const lk = this.args.media?.lk;
|
|
758
|
+
if (!lk) {
|
|
759
|
+
this.fallbackToWs("livekit-client unavailable");
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const media = createLiveKitMedia(lk, {
|
|
763
|
+
onError: (e) => this.emitError({ code: "mic_start_failed", message: e.message })
|
|
764
|
+
});
|
|
765
|
+
this.lkMedia = media;
|
|
766
|
+
if (this.muted) media.setMuted(true);
|
|
767
|
+
try {
|
|
768
|
+
await media.connect(url, token);
|
|
769
|
+
if (this.lkMedia !== media) return;
|
|
770
|
+
this.mediaMode = "livekit";
|
|
771
|
+
this.rws?.send(JSON.stringify({ type: "livekit_ready" }));
|
|
772
|
+
} catch (err) {
|
|
773
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
774
|
+
media.disconnect();
|
|
775
|
+
if (this.lkMedia === media) this.lkMedia = null;
|
|
776
|
+
this.rws?.send(JSON.stringify({ type: "livekit_failed", reason }));
|
|
777
|
+
this.fallbackToWs(reason);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
// Idempotent: whichever side gives up first (client join error, server
|
|
781
|
+
// livekit_fallback, `connected` without a join) lands here once.
|
|
782
|
+
this.fallbackToWs = (reason) => {
|
|
783
|
+
if (this.mediaMode === "ws") return;
|
|
784
|
+
this.mediaMode = "ws";
|
|
785
|
+
this.lkMedia?.disconnect();
|
|
786
|
+
this.lkMedia = null;
|
|
787
|
+
void this.ensureWsPlayback();
|
|
788
|
+
void this.startCapture();
|
|
789
|
+
this.args.options.onTransportChange?.({ requested: "livekit", actual: "ws", reason });
|
|
790
|
+
};
|
|
791
|
+
this.resetLiveKitForReconnect = () => {
|
|
792
|
+
if (this.args.media?.kind !== "livekit") return;
|
|
793
|
+
this.lkMedia?.disconnect();
|
|
794
|
+
this.lkMedia = null;
|
|
795
|
+
this.lkJoinSeen = false;
|
|
796
|
+
this.mediaMode = "livekit-pending";
|
|
659
797
|
};
|
|
660
798
|
// ---------------------------------------------------------------
|
|
661
799
|
// Internal
|
|
@@ -676,17 +814,27 @@ var BrowserVoiceClient = class {
|
|
|
676
814
|
this.handleSocketEvent = (ev) => {
|
|
677
815
|
switch (ev.type) {
|
|
678
816
|
case "open":
|
|
679
|
-
void this.startCapture();
|
|
817
|
+
if (this.mediaMode === "ws") void this.startCapture();
|
|
680
818
|
break;
|
|
681
819
|
case "reconnected":
|
|
682
820
|
this.proto.transcript = [];
|
|
683
821
|
this.proto.agentBubbleId = null;
|
|
684
822
|
this.args.options.onTranscript?.(this.proto.transcript);
|
|
685
|
-
|
|
823
|
+
this.resetLiveKitForReconnect();
|
|
824
|
+
if (this.mediaMode === "ws") void this.startCapture();
|
|
686
825
|
this.setState("listening");
|
|
687
826
|
break;
|
|
688
827
|
case "message":
|
|
689
828
|
if (typeof ev.data === "string") {
|
|
829
|
+
const media = parseMediaFrame(ev.data);
|
|
830
|
+
if (media?.type === "livekit_join") {
|
|
831
|
+
if (this.mediaMode === "livekit-pending") void this.joinLiveKit(media.url, media.token);
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
if (media?.type === "livekit_fallback") {
|
|
835
|
+
this.fallbackToWs(media.reason ?? "server_fallback");
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
690
838
|
handleServerMessage(ev.data, this.proto, {
|
|
691
839
|
onState: this.setState,
|
|
692
840
|
onTranscript: (entries) => this.args.options.onTranscript?.(entries),
|
|
@@ -703,7 +851,12 @@ var BrowserVoiceClient = class {
|
|
|
703
851
|
if (typeof seq === "number") this.marks.onAgentTurnEnd(seq);
|
|
704
852
|
},
|
|
705
853
|
onCallEnd: (reason) => this.teardown(reason),
|
|
706
|
-
onConnected: () =>
|
|
854
|
+
onConnected: () => {
|
|
855
|
+
if (this.mediaMode === "livekit-pending" && !this.lkJoinSeen) {
|
|
856
|
+
this.fallbackToWs("server_declined");
|
|
857
|
+
}
|
|
858
|
+
this.sendClientToolsRegister();
|
|
859
|
+
},
|
|
707
860
|
onClientToolCall: (frame) => dispatchClientToolCall(
|
|
708
861
|
(f) => this.rws?.send(JSON.stringify(f)),
|
|
709
862
|
this.args.options.clientTools ?? {},
|
|
@@ -711,6 +864,8 @@ var BrowserVoiceClient = class {
|
|
|
711
864
|
)
|
|
712
865
|
});
|
|
713
866
|
} else {
|
|
867
|
+
if (this.mediaMode === "livekit") break;
|
|
868
|
+
if (!this.playback) void this.ensureWsPlayback();
|
|
714
869
|
this.marks.markFirstAudibleOutput();
|
|
715
870
|
this.playback?.enqueue(ev.data);
|
|
716
871
|
}
|
|
@@ -728,7 +883,8 @@ var BrowserVoiceClient = class {
|
|
|
728
883
|
};
|
|
729
884
|
this.startCapture = async () => {
|
|
730
885
|
if (this.capture?.isCapturing()) return;
|
|
731
|
-
this.
|
|
886
|
+
const createCapture = this.args.audio?.createCapture ?? createAudioCapture;
|
|
887
|
+
this.capture = createCapture({
|
|
732
888
|
onChunk: (pcm) => {
|
|
733
889
|
this.marks.markFirstOutboundAudio();
|
|
734
890
|
this.rws?.send(pcm);
|
|
@@ -757,6 +913,8 @@ var BrowserVoiceClient = class {
|
|
|
757
913
|
}
|
|
758
914
|
this.capture?.stop();
|
|
759
915
|
this.capture = null;
|
|
916
|
+
this.lkMedia?.disconnect();
|
|
917
|
+
this.lkMedia = null;
|
|
760
918
|
this.playback?.close();
|
|
761
919
|
this.playback = null;
|
|
762
920
|
try {
|
|
@@ -809,28 +967,17 @@ var BrowserVoiceClient = class {
|
|
|
809
967
|
async start() {
|
|
810
968
|
this.setState("connecting");
|
|
811
969
|
this.startedAt = Date.now();
|
|
970
|
+
this.mediaMode = this.args.media?.kind === "livekit" ? "livekit-pending" : "ws";
|
|
812
971
|
const url = buildWsUrl({
|
|
813
972
|
apiBase: this.args.config.apiBase,
|
|
814
973
|
agentId: this.args.options.agentId,
|
|
815
974
|
token: this.args.token,
|
|
816
|
-
bargeIn: this.args.options.bargeIn
|
|
817
|
-
|
|
818
|
-
this.playback = createAudioPlayback({
|
|
819
|
-
onVolume: (v) => {
|
|
820
|
-
this.outputVolume = v;
|
|
821
|
-
this.args.options.onVolume?.({ input: this.inputVolume, output: v });
|
|
822
|
-
}
|
|
975
|
+
bargeIn: this.args.options.bargeIn,
|
|
976
|
+
media: this.mediaMode === "ws" ? void 0 : "livekit"
|
|
823
977
|
});
|
|
824
|
-
|
|
825
|
-
await this.playback.resume();
|
|
826
|
-
} catch {
|
|
827
|
-
}
|
|
978
|
+
if (this.mediaMode === "ws") await this.ensureWsPlayback();
|
|
828
979
|
this.rws = createReconnectingWebSocket(
|
|
829
|
-
{
|
|
830
|
-
url,
|
|
831
|
-
wsFactory: this.args.wsFactory,
|
|
832
|
-
maxRetries: 3
|
|
833
|
-
},
|
|
980
|
+
{ url, wsFactory: this.args.wsFactory, maxRetries: 3 },
|
|
834
981
|
(ev) => this.handleSocketEvent(ev)
|
|
835
982
|
);
|
|
836
983
|
}
|
|
@@ -1111,7 +1258,7 @@ var parseIncomingCall = (raw) => {
|
|
|
1111
1258
|
if (typeof p.agentId !== "string" || p.agentId.length === 0) {
|
|
1112
1259
|
throw new Error("parseIncomingCall: missing `agentId`");
|
|
1113
1260
|
}
|
|
1114
|
-
const transport = p.transport === "webrtc" ? "webrtc" : "ws";
|
|
1261
|
+
const transport = p.transport === "webrtc" ? "webrtc" : p.transport === "livekit" ? "livekit" : "ws";
|
|
1115
1262
|
const out = { token: p.token, agentId: p.agentId, transport };
|
|
1116
1263
|
if (transport === "webrtc" && typeof p.webrtcGatewayBase === "string") {
|
|
1117
1264
|
out.webrtcGatewayBase = p.webrtcGatewayBase;
|
|
@@ -1168,6 +1315,27 @@ var AssistantVoiceFactory = class {
|
|
|
1168
1315
|
clientTools: options.clientTools
|
|
1169
1316
|
});
|
|
1170
1317
|
}
|
|
1318
|
+
if (resolved.transport === "livekit") {
|
|
1319
|
+
let lk = null;
|
|
1320
|
+
let loadError = "";
|
|
1321
|
+
try {
|
|
1322
|
+
lk = await loadLiveKit("transport 'livekit'");
|
|
1323
|
+
} catch (e) {
|
|
1324
|
+
loadError = e instanceof Error ? e.message : String(e);
|
|
1325
|
+
}
|
|
1326
|
+
if (!lk) {
|
|
1327
|
+
options.onTransportChange?.({ requested: "livekit", actual: "ws", reason: loadError });
|
|
1328
|
+
}
|
|
1329
|
+
const client2 = new BrowserVoiceClient({
|
|
1330
|
+
config: this.config,
|
|
1331
|
+
options: { ...options, context, metadata },
|
|
1332
|
+
token: resolved.token,
|
|
1333
|
+
wsFactory: browserWsFactory,
|
|
1334
|
+
media: lk ? { kind: "livekit", lk } : void 0
|
|
1335
|
+
});
|
|
1336
|
+
await client2.start();
|
|
1337
|
+
return client2;
|
|
1338
|
+
}
|
|
1171
1339
|
const client = new BrowserVoiceClient({
|
|
1172
1340
|
config: this.config,
|
|
1173
1341
|
// Carry merged context/metadata through to startCall so server can
|
|
@@ -1205,4 +1373,4 @@ export {
|
|
|
1205
1373
|
parseIncomingCall,
|
|
1206
1374
|
configureVoiceClient
|
|
1207
1375
|
};
|
|
1208
|
-
//# sourceMappingURL=chunk-
|
|
1376
|
+
//# sourceMappingURL=chunk-C2U6V5NI.mjs.map
|