@agimon-ai/doompi-voice 0.0.1-alpha.77 → 0.0.1-alpha.78

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 (57) hide show
  1. package/README.md +2 -2
  2. package/dist/extensions/web.mjs +162 -44
  3. package/dist/extensions/web.mjs.map +1 -1
  4. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs +6 -5
  5. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs.map +1 -1
  6. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs +2 -1
  7. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs.map +1 -1
  8. package/dist/src/schemas/apiContracts.cjs +87 -5
  9. package/dist/src/schemas/apiContracts.cjs.map +1 -1
  10. package/dist/src/schemas/apiContracts.d.cts.map +1 -1
  11. package/dist/src/schemas/apiContracts.d.mts.map +1 -1
  12. package/dist/src/schemas/apiContracts.mjs +87 -5
  13. package/dist/src/schemas/apiContracts.mjs.map +1 -1
  14. package/dist/src/schemas/voiceControl.cjs +5 -1
  15. package/dist/src/schemas/voiceControl.cjs.map +1 -1
  16. package/dist/src/schemas/voiceControl.mjs +5 -1
  17. package/dist/src/schemas/voiceControl.mjs.map +1 -1
  18. package/dist/src/services/sessionVoiceOwnership/index.cjs +51 -11
  19. package/dist/src/services/sessionVoiceOwnership/index.cjs.map +1 -1
  20. package/dist/src/services/sessionVoiceOwnership/index.d.cts.map +1 -1
  21. package/dist/src/services/sessionVoiceOwnership/index.d.mts.map +1 -1
  22. package/dist/src/services/sessionVoiceOwnership/index.mjs +51 -11
  23. package/dist/src/services/sessionVoiceOwnership/index.mjs.map +1 -1
  24. package/dist/src/services/transferVoiceTool/index.cjs +20 -11
  25. package/dist/src/services/transferVoiceTool/index.cjs.map +1 -1
  26. package/dist/src/services/transferVoiceTool/index.mjs +20 -11
  27. package/dist/src/services/transferVoiceTool/index.mjs.map +1 -1
  28. package/dist/src/services/voiceMediaHubChannel/index.cjs +42 -4
  29. package/dist/src/services/voiceMediaHubChannel/index.cjs.map +1 -1
  30. package/dist/src/services/voiceMediaHubChannel/index.mjs +40 -2
  31. package/dist/src/services/voiceMediaHubChannel/index.mjs.map +1 -1
  32. package/dist/src/services/voiceOwnershipCoordinator/index.cjs +86 -21
  33. package/dist/src/services/voiceOwnershipCoordinator/index.cjs.map +1 -1
  34. package/dist/src/services/voiceOwnershipCoordinator/index.mjs +86 -21
  35. package/dist/src/services/voiceOwnershipCoordinator/index.mjs.map +1 -1
  36. package/dist/src/services/voicePeerRelay/index.cjs +335 -0
  37. package/dist/src/services/voicePeerRelay/index.cjs.map +1 -0
  38. package/dist/src/services/voicePeerRelay/index.mjs +331 -0
  39. package/dist/src/services/voicePeerRelay/index.mjs.map +1 -0
  40. package/dist/src/services/voiceReadinessApi/index.cjs +8 -4
  41. package/dist/src/services/voiceReadinessApi/index.cjs.map +1 -1
  42. package/dist/src/services/voiceReadinessApi/index.mjs +6 -2
  43. package/dist/src/services/voiceReadinessApi/index.mjs.map +1 -1
  44. package/dist/src/services/voiceServer/index.cjs +23 -11
  45. package/dist/src/services/voiceServer/index.cjs.map +1 -1
  46. package/dist/src/services/voiceServer/index.mjs +23 -11
  47. package/dist/src/services/voiceServer/index.mjs.map +1 -1
  48. package/dist/src/types/voiceOwnership.cjs +30 -11
  49. package/dist/src/types/voiceOwnership.cjs.map +1 -1
  50. package/dist/src/types/voiceOwnership.d.cts +9 -2
  51. package/dist/src/types/voiceOwnership.d.cts.map +1 -1
  52. package/dist/src/types/voiceOwnership.d.mts +9 -2
  53. package/dist/src/types/voiceOwnership.d.mts.map +1 -1
  54. package/dist/src/types/voiceOwnership.mjs +30 -11
  55. package/dist/src/types/voiceOwnership.mjs.map +1 -1
  56. package/package.json +10 -9
  57. package/src/types/voiceOwnership.ts +67 -10
package/README.md CHANGED
@@ -22,7 +22,7 @@ Voice owns these authenticated server endpoints:
22
22
  | same | `POST /control` | Send `{action, target?}` |
23
23
 
24
24
  Control actions are `status`, `manual`, `activate`, `deactivate`, `mute`, `unmute`, `interrupt`,
25
- and `transfer` (with a target catalog order). The typed session method `voice.control` uses the
25
+ and `transfer` (with a target catalog order and its catalog revision). The typed session method `voice.control` uses the
26
26
  same operations as commands and minor-mode actions. Browser recording uploads continue to use
27
27
  `/api/sessions/:sessionId/plugin/voice-media/manual/transcribe`.
28
28
 
@@ -204,7 +204,7 @@ limited to 4,096 characters and returns `completed`, `interrupted`, `superseded`
204
204
  Once confirmed user speech begins, new narration waits in memory until that turn is synchronously delivered to Pi. Multiple waiting requests use the configured `autoCapture.model` to become one bounded spoken update, with one bounded deterministic utterance if compaction fails. Narration that was already playing is not stopped by this queue gate; the existing ranked barge-in policy remains responsible for interruption.
205
205
  Narration fails closed while Voice is starting or draining, during shutdown, reload, or
206
206
  deactivation, and when the request belongs to a stale session.
207
- Only the currently active TUI session receives Voice-owned tools.
207
+ Only the currently active TUI session receives Voice-owned tools. Paired-host targets are discovered through authenticated Voice grants and appear in the same revision-bound transfer catalog. A handoff prepares the target, releases the source, activates the target, and waits for usable media readiness. It does not transfer agent context, transcripts, pending work, or buffered source audio.
208
208
 
209
209
  If no `narrate` attempt is made before a final response, the active Voice session can produce one
210
210
  bounded fallback utterance. Short final responses use deterministic text; longer ones may use
@@ -224,7 +224,7 @@ function parseBrowserVoiceOwnershipPayload(value) {
224
224
  "type",
225
225
  "version",
226
226
  "activeSessionId"
227
- ]) || input.type !== "browser-media-session" || input.version !== 2 || input.activeSessionId !== null && !id(input.activeSessionId, 200)) return void 0;
227
+ ]) || input.type !== "browser-media-session" || input.version !== 3 || input.activeSessionId !== null && (typeof input.activeSessionId !== "string" || !id(input.activeSessionId, 200) && !/^peer\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(input.activeSessionId))) return void 0;
228
228
  return input;
229
229
  }
230
230
  //#endregion
@@ -893,6 +893,17 @@ var apiRoutes_default = defineApiRoutes({
893
893
  path: "/readiness",
894
894
  response: apiResponse()
895
895
  },
896
+ relayBinding: {
897
+ method: "POST",
898
+ path: "/relay-binding",
899
+ response: apiResponse()
900
+ },
901
+ relay: {
902
+ method: "POST",
903
+ path: "/relay",
904
+ query: ["binding"],
905
+ response: apiResponse()
906
+ },
896
907
  clientConnect: {
897
908
  method: "POST",
898
909
  path: VOICE_MEDIA_ROUTES.clientConnect,
@@ -972,7 +983,7 @@ var apiRoutes_default = defineApiRoutes({
972
983
  response: apiResponse()
973
984
  }
974
985
  });
975
- createApiClient(apiRoutes_default, {
986
+ const voice = createApiClient(apiRoutes_default, {
976
987
  scopes: [
977
988
  "global",
978
989
  "workspace",
@@ -2720,12 +2731,17 @@ function jsonBody(value) {
2720
2731
  body: JSON.stringify(value)
2721
2732
  };
2722
2733
  }
2734
+ function route(path, query = {}) {
2735
+ const url = new URL(path, "http://doompi.local");
2736
+ for (const [name, value] of Object.entries(query)) if (value !== void 0) url.searchParams.set(name, String(value));
2737
+ return `${url.pathname}${url.search}`;
2738
+ }
2723
2739
  function cancelResponseBody(response) {
2724
2740
  try {
2725
2741
  response?.body?.cancel().catch(() => void 0);
2726
2742
  } catch {}
2727
2743
  }
2728
- async function boundedControlRequest(input, init, deadlineMs, consume, parentSignal) {
2744
+ async function boundedControlRequest(input, init, deadlineMs, consume, parentSignal, transport = (input, init) => sealedTransport.fetch(input, init)) {
2729
2745
  const deadlineController = new AbortController();
2730
2746
  const signal = parentSignal ?? deadlineController.signal;
2731
2747
  let response;
@@ -2748,7 +2764,7 @@ async function boundedControlRequest(input, init, deadlineMs, consume, parentSig
2748
2764
  parentSignal?.addEventListener("abort", abortParent, { once: true });
2749
2765
  let fetched;
2750
2766
  try {
2751
- fetched = sealedTransport.fetch(input, {
2767
+ fetched = transport(input, {
2752
2768
  ...init,
2753
2769
  signal
2754
2770
  });
@@ -2777,6 +2793,7 @@ var BrowserVoiceMediaTransport = class {
2777
2793
  sessionId;
2778
2794
  push;
2779
2795
  controlLocation;
2796
+ binding;
2780
2797
  constructor(sessionId) {
2781
2798
  this.sessionId = sessionId;
2782
2799
  }
@@ -2789,6 +2806,79 @@ var BrowserVoiceMediaTransport = class {
2789
2806
  media() {
2790
2807
  return voiceMedia.session(this.sessionId);
2791
2808
  }
2809
+ remote() {
2810
+ return /^peer\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(this.sessionId);
2811
+ }
2812
+ async fetch(path, directUrl, init = {}) {
2813
+ if (!this.remote()) return sealedTransport.fetch(directUrl, init);
2814
+ const request = new Request(new URL(path, "http://doompi.local"), init);
2815
+ const bytes = new Uint8Array(await request.arrayBuffer());
2816
+ let connectionId = new URL(request.url).searchParams.get("connectionId") ?? void 0;
2817
+ if (!connectionId && bytes.byteLength > 0) try {
2818
+ const value = JSON.parse(new TextDecoder().decode(bytes));
2819
+ if (typeof value.connectionId === "string") connectionId = value.connectionId;
2820
+ } catch {
2821
+ connectionId = void 0;
2822
+ }
2823
+ if (!connectionId) throw new Error("Paired Voice request has no connection binding.");
2824
+ if (!this.binding || this.binding.connectionId !== connectionId || this.binding.expiresAt <= Date.now() + CONTROL_REQUEST_DEADLINE_MS) {
2825
+ const issued = await voice.global.relayBinding({ body: {
2826
+ target: this.sessionId,
2827
+ connectionId
2828
+ } });
2829
+ if (!issued.ok) throw resultError(issued);
2830
+ const value = issued.data;
2831
+ if (typeof value.binding !== "string" || !Number.isSafeInteger(value.expiresAt)) throw new Error("Paired Voice media binding is invalid.");
2832
+ this.binding = {
2833
+ id: value.binding,
2834
+ connectionId,
2835
+ expiresAt: value.expiresAt
2836
+ };
2837
+ }
2838
+ let binary = "";
2839
+ for (const byte of bytes) binary += String.fromCharCode(byte);
2840
+ const result = await voice.global.relay({
2841
+ query: { binding: this.binding.id },
2842
+ body: {
2843
+ targetSessionId: "",
2844
+ method: request.method === "GET" ? "GET" : "POST",
2845
+ path,
2846
+ headers: [...request.headers.entries()],
2847
+ body: btoa(binary)
2848
+ }
2849
+ });
2850
+ if (!result.ok) throw resultError(result);
2851
+ const envelope = result.data;
2852
+ if (!Number.isSafeInteger(envelope.status) || !Array.isArray(envelope.headers) || typeof envelope.body !== "string") throw new Error("Paired Voice relay response is invalid.");
2853
+ const decoded = atob(envelope.body);
2854
+ const body = Uint8Array.from(decoded, (character) => character.charCodeAt(0));
2855
+ return new Response(body.byteLength === 0 ? null : body, {
2856
+ status: envelope.status,
2857
+ headers: envelope.headers
2858
+ });
2859
+ }
2860
+ async remoteResult(path, directUrl, init) {
2861
+ const response = await this.fetch(path, directUrl, init);
2862
+ let data;
2863
+ try {
2864
+ data = response.status === 204 ? void 0 : await response.json();
2865
+ } catch {
2866
+ data = void 0;
2867
+ }
2868
+ if (response.ok) return {
2869
+ ok: true,
2870
+ status: response.status,
2871
+ data,
2872
+ response
2873
+ };
2874
+ return {
2875
+ ok: false,
2876
+ status: response.status,
2877
+ error: typeof data === "object" && data !== null && "error" in data && typeof data.error === "string" ? data.error : `Voice media request failed with status ${String(response.status)}.`,
2878
+ data,
2879
+ response
2880
+ };
2881
+ }
2792
2882
  async connect(clientId, connectionId, capabilities) {
2793
2883
  this.push = void 0;
2794
2884
  const controlLocation = sealedTransport.active() ? "remote" : "local";
@@ -2807,14 +2897,16 @@ var BrowserVoiceMediaTransport = class {
2807
2897
  }
2808
2898
  async disconnect(clientId, connectionId) {
2809
2899
  try {
2810
- const result = await this.media().clientDisconnect({ body: {
2900
+ const body = {
2811
2901
  clientId,
2812
2902
  connectionId
2813
- } });
2903
+ };
2904
+ const result = this.remote() ? await this.remoteResult(VOICE_MEDIA_ROUTES.clientDisconnect, "", jsonBody(body)) : await this.media().clientDisconnect({ body });
2814
2905
  if (!result.ok && result.status !== 409) throw resultError(result);
2815
2906
  } finally {
2816
2907
  this.push = void 0;
2817
2908
  this.controlLocation = void 0;
2909
+ this.binding = void 0;
2818
2910
  }
2819
2911
  }
2820
2912
  async nextEvent(clientId, connectionId, after, signal) {
@@ -2832,29 +2924,39 @@ var BrowserVoiceMediaTransport = class {
2832
2924
  }
2833
2925
  }
2834
2926
  declareClient(clientId, connectionId, capabilities, controlLocation) {
2835
- return this.media().clientConnect({ body: {
2927
+ const body = {
2836
2928
  version: 6,
2837
2929
  clientId,
2838
2930
  connectionId,
2839
2931
  clientKind: "browser",
2840
2932
  controlLocation,
2841
2933
  capabilities
2842
- } });
2934
+ };
2935
+ return this.remote() ? this.remoteResult(VOICE_MEDIA_ROUTES.clientConnect, "", jsonBody(body)) : this.media().clientConnect({ body });
2843
2936
  }
2844
2937
  async fetchEvent(clientId, connectionId, after, signal, nonblocking) {
2845
- return boundedControlRequest(this.media().clientEvents.url({ query: {
2938
+ const path = route(VOICE_MEDIA_ROUTES.clientEvents, {
2939
+ clientId,
2940
+ connectionId,
2941
+ after,
2942
+ ...nonblocking ? { wait: "0" } : {}
2943
+ });
2944
+ const directUrl = this.remote() ? "" : this.media().clientEvents.url({ query: {
2846
2945
  clientId,
2847
2946
  connectionId,
2848
2947
  after,
2849
2948
  ...nonblocking ? { wait: "0" } : {}
2850
- } }), {}, CONTROL_REQUEST_DEADLINE_MS, async (response) => {
2949
+ } });
2950
+ return boundedControlRequest(directUrl, {}, CONTROL_REQUEST_DEADLINE_MS, async (response) => {
2851
2951
  if (response.status === 204) return void 0;
2852
2952
  if (!response.ok) throw await responseError(response);
2853
2953
  return await response.json();
2854
- }, signal);
2954
+ }, signal, (_input, init) => this.fetch(path, directUrl, init));
2855
2955
  }
2856
2956
  heartbeat(clientId, connectionId) {
2857
- return boundedControlRequest(this.media().clientHeartbeat.url(), jsonBody({
2957
+ const path = VOICE_MEDIA_ROUTES.clientHeartbeat;
2958
+ const directUrl = this.remote() ? "" : this.media().clientHeartbeat.url();
2959
+ return boundedControlRequest(directUrl, jsonBody({
2858
2960
  clientId,
2859
2961
  connectionId
2860
2962
  }), CONTROL_REQUEST_DEADLINE_MS, async (response) => {
@@ -2862,37 +2964,45 @@ var BrowserVoiceMediaTransport = class {
2862
2964
  const wake = parseVoiceMediaWakePayload(await response.json());
2863
2965
  if (wake === null) throw new Error("Voice media heartbeat response is invalid.");
2864
2966
  return wake;
2865
- });
2967
+ }, void 0, (_input, init) => this.fetch(path, directUrl, init));
2866
2968
  }
2867
2969
  async sendAudio(clientId, connectionId, captureId, pcm, activity) {
2868
- const result = await this.media().clientAudio({
2869
- query: {
2870
- clientId,
2871
- connectionId,
2872
- captureId
2873
- },
2874
- headers: {
2875
- "content-type": VOICE_MEDIA_CONTENT_TYPE,
2876
- ...activity === void 0 ? {} : {
2877
- [VOICE_MEDIA_ACTIVITY_STATE_HEADER]: activity.state,
2878
- [VOICE_MEDIA_ACTIVITY_LEVEL_HEADER]: String(activity.levelDbfs),
2879
- [VOICE_MEDIA_ACTIVITY_ELAPSED_HEADER]: String(activity.elapsedMs),
2880
- ...activity.epoch === void 0 ? {} : { [VOICE_MEDIA_ACTIVITY_EPOCH_HEADER]: String(activity.epoch) },
2881
- ...activity.classifiedSpeechMs === void 0 ? {} : { [VOICE_MEDIA_ACTIVITY_SPEECH_MS_HEADER]: String(activity.classifiedSpeechMs) },
2882
- ...activity.echoDiscriminatedSpeechMs === void 0 ? {} : { [VOICE_MEDIA_ACTIVITY_ECHO_SPEECH_MS_HEADER]: String(activity.echoDiscriminatedSpeechMs) }
2883
- }
2884
- },
2885
- body: new Blob([new Uint8Array(pcm)], { type: VOICE_MEDIA_CONTENT_TYPE })
2970
+ const query = {
2971
+ clientId,
2972
+ connectionId,
2973
+ captureId
2974
+ };
2975
+ const headers = {
2976
+ "content-type": VOICE_MEDIA_CONTENT_TYPE,
2977
+ ...activity === void 0 ? {} : {
2978
+ [VOICE_MEDIA_ACTIVITY_STATE_HEADER]: activity.state,
2979
+ [VOICE_MEDIA_ACTIVITY_LEVEL_HEADER]: String(activity.levelDbfs),
2980
+ [VOICE_MEDIA_ACTIVITY_ELAPSED_HEADER]: String(activity.elapsedMs),
2981
+ ...activity.epoch === void 0 ? {} : { [VOICE_MEDIA_ACTIVITY_EPOCH_HEADER]: String(activity.epoch) },
2982
+ ...activity.classifiedSpeechMs === void 0 ? {} : { [VOICE_MEDIA_ACTIVITY_SPEECH_MS_HEADER]: String(activity.classifiedSpeechMs) },
2983
+ ...activity.echoDiscriminatedSpeechMs === void 0 ? {} : { [VOICE_MEDIA_ACTIVITY_ECHO_SPEECH_MS_HEADER]: String(activity.echoDiscriminatedSpeechMs) }
2984
+ }
2985
+ };
2986
+ const body = new Blob([new Uint8Array(pcm)], { type: VOICE_MEDIA_CONTENT_TYPE });
2987
+ const result = this.remote() ? await this.remoteResult(route(VOICE_MEDIA_ROUTES.clientAudio, query), "", {
2988
+ method: "POST",
2989
+ headers,
2990
+ body
2991
+ }) : await this.media().clientAudio({
2992
+ query,
2993
+ headers,
2994
+ body
2886
2995
  });
2887
2996
  if (!result.ok) throw resultError(result);
2888
2997
  }
2889
2998
  async captureStopped(clientId, connectionId, captureId, error) {
2890
- const result = await this.media().clientCaptureStopped({ body: {
2999
+ const body = {
2891
3000
  clientId,
2892
3001
  connectionId,
2893
3002
  captureId,
2894
3003
  ...error === void 0 ? {} : { error }
2895
- } });
3004
+ };
3005
+ const result = this.remote() ? await this.remoteResult(VOICE_MEDIA_ROUTES.clientCaptureStopped, "", jsonBody(body)) : await this.media().clientCaptureStopped({ body });
2896
3006
  if (!result.ok) throw resultError(result);
2897
3007
  }
2898
3008
  /**
@@ -2906,13 +3016,18 @@ var BrowserVoiceMediaTransport = class {
2906
3016
  async receivePlaybackAudio(clientId, connectionId, playbackId, signal) {
2907
3017
  const chunks = [];
2908
3018
  let byteLength = 0;
2909
- const url = this.media().clientPlaybackAudio.url({ query: {
3019
+ const path = route(VOICE_MEDIA_ROUTES.clientPlaybackAudio, {
3020
+ clientId,
3021
+ connectionId,
3022
+ playbackId
3023
+ });
3024
+ const url = this.remote() ? "" : this.media().clientPlaybackAudio.url({ query: {
2910
3025
  clientId,
2911
3026
  connectionId,
2912
3027
  playbackId
2913
3028
  } });
2914
3029
  while (!signal.aborted) {
2915
- const response = await sealedTransport.fetch(url, { signal });
3030
+ const response = await this.fetch(path, url, { signal });
2916
3031
  if (response.status === 200 && response.headers.get("content-type")?.startsWith("application/vnd.doompi.pcm-s16le")) {
2917
3032
  const chunk = new Uint8Array(await response.arrayBuffer());
2918
3033
  chunks.push(chunk);
@@ -2934,15 +3049,18 @@ var BrowserVoiceMediaTransport = class {
2934
3049
  throw new DOMException("Voice playback audio was aborted.", "AbortError");
2935
3050
  }
2936
3051
  async playbackFinished(clientId, connectionId, result) {
2937
- const answer = await this.media().clientPlaybackResult({ body: {
3052
+ const body = {
2938
3053
  clientId,
2939
3054
  connectionId,
2940
3055
  ...result
2941
- } });
3056
+ };
3057
+ const answer = this.remote() ? await this.remoteResult(VOICE_MEDIA_ROUTES.clientPlaybackResult, "", jsonBody(body)) : await this.media().clientPlaybackResult({ body });
2942
3058
  if (!answer.ok) throw resultError(answer);
2943
3059
  }
2944
3060
  async realtimeNegotiate(clientId, connectionId, activationId, sdp, signal) {
2945
- return boundedControlRequest(this.media().realtimeNegotiate.url(), jsonBody({
3061
+ const path = REALTIME_ROUTES.clientNegotiate;
3062
+ const directUrl = this.remote() ? "" : this.media().realtimeNegotiate.url();
3063
+ return boundedControlRequest(directUrl, jsonBody({
2946
3064
  clientId,
2947
3065
  connectionId,
2948
3066
  activationId,
@@ -2952,10 +3070,10 @@ var BrowserVoiceMediaTransport = class {
2952
3070
  const body = await response.json();
2953
3071
  if (typeof body !== "object" || body === null || typeof body.sdp !== "string") throw new Error("Realtime negotiation response is invalid.");
2954
3072
  return body.sdp;
2955
- }, signal);
3073
+ }, signal, (_input, init) => this.fetch(path, directUrl, init));
2956
3074
  }
2957
3075
  async realtimeEvent(clientId, connectionId, activationId, event) {
2958
- await this.postRealtime(this.media().realtimeEvent.url(), {
3076
+ await this.postRealtime(REALTIME_ROUTES.clientEvent, this.remote() ? "" : this.media().realtimeEvent.url(), {
2959
3077
  clientId,
2960
3078
  connectionId,
2961
3079
  activationId,
@@ -2963,17 +3081,17 @@ var BrowserVoiceMediaTransport = class {
2963
3081
  });
2964
3082
  }
2965
3083
  async realtimeState(clientId, connectionId, activationId, state) {
2966
- await this.postRealtime(this.media().realtimeState.url(), {
3084
+ await this.postRealtime(REALTIME_ROUTES.clientState, this.remote() ? "" : this.media().realtimeState.url(), {
2967
3085
  clientId,
2968
3086
  connectionId,
2969
3087
  activationId,
2970
3088
  state
2971
3089
  });
2972
3090
  }
2973
- postRealtime(url, body) {
2974
- return boundedControlRequest(url, jsonBody(body), CONTROL_REQUEST_DEADLINE_MS, async (response) => {
3091
+ postRealtime(path, directUrl, body) {
3092
+ return boundedControlRequest(directUrl, jsonBody(body), CONTROL_REQUEST_DEADLINE_MS, async (response) => {
2975
3093
  if (!response.ok) throw await responseError(response);
2976
- });
3094
+ }, void 0, (_input, init) => this.fetch(path, directUrl, init));
2977
3095
  }
2978
3096
  };
2979
3097
  //#endregion