@craftedxp/voice-js 0.4.0 → 0.4.2

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/browser.mjs CHANGED
@@ -838,9 +838,18 @@ var BrowserVoiceClient = class {
838
838
 
839
839
  // src/webrtc/createWebRtcCall.ts
840
840
  async function createWebRtcCall(opts) {
841
+ validateClientToolMap(opts.clientTools);
841
842
  const proto = createProtocolState();
842
843
  let muted = false;
843
844
  let ended = false;
845
+ const tools = opts.clientTools ?? {};
846
+ const sendControl = (frame) => {
847
+ if (dc?.readyState !== "open") return;
848
+ try {
849
+ dc.send(JSON.stringify(frame));
850
+ } catch {
851
+ }
852
+ };
844
853
  const fireState = (next) => {
845
854
  if (proto.state === next) return;
846
855
  proto.state = next;
@@ -857,8 +866,12 @@ async function createWebRtcCall(opts) {
857
866
  },
858
867
  onCallEnd: () => teardown(),
859
868
  onConnected: () => {
869
+ if (Object.keys(tools).length > 0) {
870
+ sendControl(buildRegisterFrame(tools));
871
+ }
860
872
  },
861
- onClientToolCall: () => {
873
+ onClientToolCall: (frame) => {
874
+ dispatchClientToolCall(sendControl, tools, frame);
862
875
  }
863
876
  });
864
877
  };
@@ -895,11 +908,57 @@ async function createWebRtcCall(opts) {
895
908
  dc.onerror = () => {
896
909
  opts.onError?.({ code: "socket_error", message: "control channel error" });
897
910
  };
911
+ dc.onopen = () => {
912
+ if (Object.keys(tools).length > 0) {
913
+ sendControl(buildRegisterFrame(tools));
914
+ }
915
+ };
898
916
  const gateway = opts.webrtcGatewayBase || "";
899
917
  const offerUrl = gateway ? `${gateway}/webrtc/offer?token=${encodeURIComponent(opts.token)}` : `${opts.apiBase}/v1/agents/${encodeURIComponent(opts.agentId)}/webrtc/offer?token=${encodeURIComponent(opts.token)}`;
900
918
  const iceUrl = gateway ? `${gateway}/webrtc/ice?token=${encodeURIComponent(opts.token)}` : `${opts.apiBase}/v1/agents/${encodeURIComponent(opts.agentId)}/webrtc/ice?token=${encodeURIComponent(opts.token)}`;
919
+ const teardown = () => {
920
+ if (ended) return;
921
+ ended = true;
922
+ try {
923
+ mic.getTracks().forEach((t) => t.stop());
924
+ } catch {
925
+ }
926
+ try {
927
+ pc.close();
928
+ } catch {
929
+ }
930
+ try {
931
+ audioEl.remove();
932
+ } catch {
933
+ }
934
+ fireState("ended");
935
+ opts.onEnd?.();
936
+ };
937
+ let callId = null;
938
+ const pendingCandidates = [];
939
+ const postCandidate = (candidate) => {
940
+ void fetch(iceUrl, {
941
+ method: "POST",
942
+ headers: { "content-type": "application/json" },
943
+ body: JSON.stringify({ callId, candidate })
944
+ }).catch(() => {
945
+ });
946
+ };
947
+ pc.onicecandidate = (e) => {
948
+ if (!e.candidate) return;
949
+ if (callId) postCandidate(e.candidate);
950
+ else pendingCandidates.push(e.candidate);
951
+ };
952
+ pc.onconnectionstatechange = () => {
953
+ const s = pc.connectionState;
954
+ if (s === "connected") fireState("listening");
955
+ if (s === "failed" || s === "disconnected") {
956
+ opts.onError?.({ code: "socket_error", message: `webrtc connection ${s}` });
957
+ teardown();
958
+ }
959
+ if (s === "closed" && !ended) teardown();
960
+ };
901
961
  await pc.setLocalDescription(await pc.createOffer());
902
- let callId;
903
962
  try {
904
963
  const offerRes = await fetch(offerUrl, {
905
964
  method: "POST",
@@ -918,6 +977,7 @@ async function createWebRtcCall(opts) {
918
977
  const body = await offerRes.json();
919
978
  callId = body.callId;
920
979
  await pc.setRemoteDescription({ type: "answer", sdp: body.sdp });
980
+ while (pendingCandidates.length > 0) postCandidate(pendingCandidates.shift());
921
981
  } catch (err) {
922
982
  if (!ended) {
923
983
  opts.onError?.({
@@ -931,42 +991,6 @@ async function createWebRtcCall(opts) {
931
991
  }
932
992
  throw err;
933
993
  }
934
- pc.onicecandidate = (e) => {
935
- if (!e.candidate) return;
936
- void fetch(iceUrl, {
937
- method: "POST",
938
- headers: { "content-type": "application/json" },
939
- body: JSON.stringify({ callId, candidate: e.candidate })
940
- }).catch(() => {
941
- });
942
- };
943
- pc.onconnectionstatechange = () => {
944
- const s = pc.connectionState;
945
- if (s === "connected") fireState("listening");
946
- if (s === "failed" || s === "disconnected") {
947
- opts.onError?.({ code: "socket_error", message: `webrtc connection ${s}` });
948
- teardown();
949
- }
950
- if (s === "closed" && !ended) teardown();
951
- };
952
- const teardown = () => {
953
- if (ended) return;
954
- ended = true;
955
- try {
956
- mic.getTracks().forEach((t) => t.stop());
957
- } catch {
958
- }
959
- try {
960
- pc.close();
961
- } catch {
962
- }
963
- try {
964
- audioEl.remove();
965
- } catch {
966
- }
967
- fireState("ended");
968
- opts.onEnd?.();
969
- };
970
994
  return {
971
995
  get state() {
972
996
  return proto.state;
@@ -1033,7 +1057,8 @@ var BrowserVoiceFactory = class {
1033
1057
  // tracked at 0 until the followup lands (see spec Followups section).
1034
1058
  onEnd: options.onEnd ? () => options.onEnd({ reason: "agent_ended", durationMs: 0 }) : void 0,
1035
1059
  onInterrupt: options.onInterrupt,
1036
- onAgentTurnStart: options.onAgentTurnStart
1060
+ onAgentTurnStart: options.onAgentTurnStart,
1061
+ clientTools: options.clientTools
1037
1062
  });
1038
1063
  }
1039
1064
  const client = new BrowserVoiceClient({