@openclaw/voice-call 2026.7.2-beta.5 → 2026.7.33

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 (49) hide show
  1. package/README.md +3 -2
  2. package/dist/.setup/config-BBpso-fA.mjs +674 -0
  3. package/dist/.setup/config-compat-CYRE-SSo.mjs +156 -0
  4. package/dist/{guarded-json-api-Dxhe25Zi.js → .setup/guarded-json-api-BukKSVNW.mjs} +50 -24
  5. package/dist/{mock-YsvBTX-7.js → .setup/mock-DY4yTqR2.mjs} +1 -1
  6. package/dist/{plivo-B-KvPVji.js → .setup/plivo-DFHh72MH.mjs} +3 -2
  7. package/dist/{realtime-handler-hIVPEZxN.js → .setup/realtime-handler-DWLxvZuO.mjs} +197 -128
  8. package/dist/{response-generator-z6M9229t.js → .setup/response-generator-DoC6lfcm.mjs} +6 -21
  9. package/dist/{runtime-entry-CQtitvRd.js → .setup/runtime-entry-CH-CV0Sf.mjs} +384 -868
  10. package/dist/{store-path-nYL-yM0S.js → .setup/store-1UfyY3HS.mjs} +6 -13
  11. package/dist/{telnyx-BG56lL7z.js → .setup/telnyx-Bb8X-nin.mjs} +1 -1
  12. package/dist/{twilio-B3FQ_P9x.js → .setup/twilio-Bsbf3O9A.mjs} +11 -15
  13. package/dist/{webhook-replay-aaP79dI8.js → .setup/webhook-replay-g3cDA5wr.mjs} +1 -1
  14. package/dist/cli-metadata.js +1 -5
  15. package/dist/doctor-contract-api.js +14 -36
  16. package/dist/index.js +38 -28
  17. package/dist/runtime-entry.js +1 -1
  18. package/dist/setup-api.js +2 -86
  19. package/node_modules/commander/LICENSE +22 -0
  20. package/node_modules/commander/Readme.md +1172 -0
  21. package/node_modules/commander/index.js +21 -0
  22. package/node_modules/commander/lib/argument.js +147 -0
  23. package/node_modules/commander/lib/command.js +2790 -0
  24. package/node_modules/commander/lib/error.js +36 -0
  25. package/node_modules/commander/lib/help.js +731 -0
  26. package/node_modules/commander/lib/option.js +377 -0
  27. package/node_modules/commander/lib/suggestSimilar.js +99 -0
  28. package/node_modules/commander/package-support.json +19 -0
  29. package/node_modules/commander/package.json +64 -0
  30. package/node_modules/commander/typings/index.d.ts +1113 -0
  31. package/node_modules/typebox/build/compile/validator.d.mts +4 -1
  32. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  33. package/node_modules/typebox/build/guard/guard.mjs +2 -5
  34. package/node_modules/typebox/build/guard/string.mjs +4 -16
  35. package/node_modules/typebox/build/schema/engine/_functions.mjs +15 -34
  36. package/node_modules/typebox/build/type/action/readonly_object.d.mts +0 -5
  37. package/node_modules/typebox/build/type/action/readonly_object.mjs +0 -8
  38. package/node_modules/typebox/build/typebox.d.mts +1 -1
  39. package/node_modules/typebox/build/typebox.mjs +1 -1
  40. package/node_modules/typebox/package.json +1 -1
  41. package/node_modules/typebox/readme.md +32 -29
  42. package/node_modules/ws/lib/permessage-deflate.js +3 -1
  43. package/node_modules/ws/package.json +1 -1
  44. package/openclaw.plugin.json +15 -2
  45. package/package.json +8 -6
  46. package/dist/cli-output-mode.js +0 -27
  47. package/dist/twilio-region-4fkgz3UG.js +0 -25
  48. /package/dist/{realtime-transcription.runtime-CbJAs5t_.js → .setup/realtime-transcription.runtime-CbJAs5t_.mjs} +0 -0
  49. /package/dist/{realtime-voice.runtime-vtdCOWg-.js → .setup/realtime-voice.runtime-vtdCOWg-.mjs} +0 -0
@@ -0,0 +1,156 @@
1
+ import { t as VoiceCallConfigSchema } from "./config-BBpso-fA.mjs";
2
+ import { asOptionalRecord, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ //#region extensions/voice-call/src/config-compat.ts
4
+ /** Version where legacy voice-call config shape support is removed. */
5
+ const VOICE_CALL_LEGACY_CONFIG_REMOVAL_VERSION = "2026.6.0";
6
+ const asObject = asOptionalRecord;
7
+ const getString = readStringField;
8
+ /** Read finite numeric config values. */
9
+ function getNumber(obj, key) {
10
+ const value = obj?.[key];
11
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12
+ }
13
+ /** Merge legacy provider-specific values into the canonical providers map. */
14
+ function mergeProviderConfig(providersValue, providerId, compatValues) {
15
+ if (Object.keys(compatValues).length === 0) return asObject(providersValue);
16
+ const providers = asObject(providersValue) ?? {};
17
+ const existing = asObject(providers[providerId]) ?? {};
18
+ return {
19
+ ...providers,
20
+ [providerId]: {
21
+ ...existing,
22
+ ...compatValues
23
+ }
24
+ };
25
+ }
26
+ /** Collect legacy voice-call config keys that should be migrated. */
27
+ function collectVoiceCallLegacyConfigIssues(value) {
28
+ const raw = asObject(value) ?? {};
29
+ const realtime = asObject(raw.realtime);
30
+ const realtimeAgentContext = asObject(realtime?.agentContext);
31
+ const twilio = asObject(raw.twilio);
32
+ const streaming = asObject(raw.streaming);
33
+ const issues = [];
34
+ if (raw.provider === "log") issues.push({
35
+ path: "provider",
36
+ replacement: "provider",
37
+ message: "Replace provider \"log\" with \"mock\"."
38
+ });
39
+ if (typeof twilio?.from === "string") issues.push({
40
+ path: "twilio.from",
41
+ replacement: "fromNumber",
42
+ message: "Move twilio.from to fromNumber."
43
+ });
44
+ if (typeof streaming?.sttProvider === "string") issues.push({
45
+ path: "streaming.sttProvider",
46
+ replacement: "streaming.provider",
47
+ message: "Move streaming.sttProvider to streaming.provider."
48
+ });
49
+ if (typeof streaming?.openaiApiKey === "string") issues.push({
50
+ path: "streaming.openaiApiKey",
51
+ replacement: "streaming.providers.openai.apiKey",
52
+ message: "Move streaming.openaiApiKey to streaming.providers.openai.apiKey."
53
+ });
54
+ if (typeof streaming?.sttModel === "string") issues.push({
55
+ path: "streaming.sttModel",
56
+ replacement: "streaming.providers.openai.model",
57
+ message: "Move streaming.sttModel to streaming.providers.openai.model."
58
+ });
59
+ if (typeof streaming?.silenceDurationMs === "number") issues.push({
60
+ path: "streaming.silenceDurationMs",
61
+ replacement: "streaming.providers.openai.silenceDurationMs",
62
+ message: "Move streaming.silenceDurationMs to streaming.providers.openai.silenceDurationMs."
63
+ });
64
+ if (typeof streaming?.vadThreshold === "number") issues.push({
65
+ path: "streaming.vadThreshold",
66
+ replacement: "streaming.providers.openai.vadThreshold",
67
+ message: "Move streaming.vadThreshold to streaming.providers.openai.vadThreshold."
68
+ });
69
+ if (realtimeAgentContext && Object.hasOwn(realtimeAgentContext, "includeSystemPrompt")) issues.push({
70
+ path: "realtime.agentContext.includeSystemPrompt",
71
+ replacement: "realtime.agentContext",
72
+ message: "Remove realtime.agentContext.includeSystemPrompt; realtime context now uses the generated agent prompt."
73
+ });
74
+ return issues;
75
+ }
76
+ /** Format runtime warnings for legacy voice-call config keys. */
77
+ function formatVoiceCallLegacyConfigWarnings(params) {
78
+ const issues = collectVoiceCallLegacyConfigIssues(params.value);
79
+ if (issues.length === 0) return [];
80
+ return [`[voice-call] legacy config keys detected under ${params.configPathPrefix}; runtime loading will not rewrite them, and support for the legacy shape will be removed in ${VOICE_CALL_LEGACY_CONFIG_REMOVAL_VERSION}. Run "${params.doctorFixCommand}".`, ...issues.map((issue) => `[voice-call] ${params.configPathPrefix}.${issue.path}: ${issue.message}`)];
81
+ }
82
+ /** Migrate legacy voice-call config input to the current canonical shape. */
83
+ function migrateVoiceCallLegacyConfigInput(params) {
84
+ const raw = asObject(params.value) ?? {};
85
+ const realtime = asObject(raw.realtime);
86
+ const realtimeAgentContext = asObject(realtime?.agentContext);
87
+ const twilio = asObject(raw.twilio);
88
+ const streaming = asObject(raw.streaming);
89
+ const configPathPrefix = params.configPathPrefix ?? "plugins.entries.voice-call.config";
90
+ const issues = collectVoiceCallLegacyConfigIssues(raw);
91
+ const legacyStreamingOpenAICompat = {};
92
+ const streamingOpenAIApiKey = getString(streaming, "openaiApiKey");
93
+ if (streamingOpenAIApiKey) legacyStreamingOpenAICompat.apiKey = streamingOpenAIApiKey;
94
+ const streamingSttModel = getString(streaming, "sttModel");
95
+ if (streamingSttModel) legacyStreamingOpenAICompat.model = streamingSttModel;
96
+ const streamingSilenceDurationMs = getNumber(streaming, "silenceDurationMs");
97
+ if (streamingSilenceDurationMs !== void 0) legacyStreamingOpenAICompat.silenceDurationMs = streamingSilenceDurationMs;
98
+ const streamingVadThreshold = getNumber(streaming, "vadThreshold");
99
+ if (streamingVadThreshold !== void 0) legacyStreamingOpenAICompat.vadThreshold = streamingVadThreshold;
100
+ const streamingProvider = getString(streaming, "provider");
101
+ const legacyStreamingProvider = getString(streaming, "sttProvider");
102
+ const normalizedStreaming = streaming ? {
103
+ ...streaming,
104
+ provider: streamingProvider ?? legacyStreamingProvider,
105
+ providers: mergeProviderConfig(streaming.providers, "openai", legacyStreamingOpenAICompat)
106
+ } : void 0;
107
+ if (normalizedStreaming) {
108
+ delete normalizedStreaming.sttProvider;
109
+ delete normalizedStreaming.openaiApiKey;
110
+ delete normalizedStreaming.sttModel;
111
+ delete normalizedStreaming.silenceDurationMs;
112
+ delete normalizedStreaming.vadThreshold;
113
+ }
114
+ const normalizedTwilio = twilio ? { ...twilio } : void 0;
115
+ if (normalizedTwilio) delete normalizedTwilio.from;
116
+ const normalizedRealtimeAgentContext = realtimeAgentContext ? { ...realtimeAgentContext } : void 0;
117
+ if (normalizedRealtimeAgentContext) delete normalizedRealtimeAgentContext.includeSystemPrompt;
118
+ const normalizedRealtime = realtime ? {
119
+ ...realtime,
120
+ agentContext: normalizedRealtimeAgentContext ?? realtime.agentContext
121
+ } : void 0;
122
+ const config = {
123
+ ...raw,
124
+ provider: raw.provider === "log" ? "mock" : raw.provider,
125
+ fromNumber: raw.fromNumber ?? (typeof twilio?.from === "string" ? twilio.from : void 0),
126
+ twilio: normalizedTwilio,
127
+ streaming: normalizedStreaming,
128
+ realtime: normalizedRealtime
129
+ };
130
+ const changes = [];
131
+ if (raw.provider === "log") changes.push(`Moved ${configPathPrefix}.provider "log" → "mock".`);
132
+ if (typeof twilio?.from === "string" && typeof raw.fromNumber !== "string") changes.push(`Moved ${configPathPrefix}.twilio.from → ${configPathPrefix}.fromNumber.`);
133
+ if (typeof streaming?.sttProvider === "string") changes.push(`Moved ${configPathPrefix}.streaming.sttProvider → ${configPathPrefix}.streaming.provider.`);
134
+ if (typeof streaming?.openaiApiKey === "string") changes.push(`Moved ${configPathPrefix}.streaming.openaiApiKey → ${configPathPrefix}.streaming.providers.openai.apiKey.`);
135
+ if (typeof streaming?.sttModel === "string") changes.push(`Moved ${configPathPrefix}.streaming.sttModel → ${configPathPrefix}.streaming.providers.openai.model.`);
136
+ if (getNumber(streaming, "silenceDurationMs") !== void 0) changes.push(`Moved ${configPathPrefix}.streaming.silenceDurationMs → ${configPathPrefix}.streaming.providers.openai.silenceDurationMs.`);
137
+ else if (typeof streaming?.silenceDurationMs === "number") changes.push(`Removed invalid ${configPathPrefix}.streaming.silenceDurationMs.`);
138
+ if (getNumber(streaming, "vadThreshold") !== void 0) changes.push(`Moved ${configPathPrefix}.streaming.vadThreshold → ${configPathPrefix}.streaming.providers.openai.vadThreshold.`);
139
+ else if (typeof streaming?.vadThreshold === "number") changes.push(`Removed invalid ${configPathPrefix}.streaming.vadThreshold.`);
140
+ if (realtimeAgentContext && Object.hasOwn(realtimeAgentContext, "includeSystemPrompt")) changes.push(`Removed ${configPathPrefix}.realtime.agentContext.includeSystemPrompt.`);
141
+ return {
142
+ config,
143
+ changes,
144
+ issues
145
+ };
146
+ }
147
+ /** Normalize legacy voice-call config input without returning migration metadata. */
148
+ function normalizeVoiceCallLegacyConfigInput(value) {
149
+ return migrateVoiceCallLegacyConfigInput({ value }).config;
150
+ }
151
+ /** Parse voice-call plugin config after applying legacy normalization. */
152
+ function parseVoiceCallPluginConfig(value) {
153
+ return VoiceCallConfigSchema.parse(normalizeVoiceCallLegacyConfigInput(value));
154
+ }
155
+ //#endregion
156
+ export { parseVoiceCallPluginConfig as i, migrateVoiceCallLegacyConfigInput as n, normalizeVoiceCallLegacyConfigInput as r, formatVoiceCallLegacyConfigWarnings as t };
@@ -1,13 +1,12 @@
1
- import { fetchWithSsrFGuard } from "./runtime-api.js";
2
- import "./api.js";
3
- import { n as normalizeProxyIp, s as getHeader } from "./runtime-entry-CQtitvRd.js";
4
- import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-aaP79dI8.js";
1
+ import { fetchWithSsrFGuard } from "../runtime-api.js";
2
+ import "../api.js";
3
+ import { n as normalizeProxyIp, o as getHeader } from "./runtime-entry-CH-CV0Sf.mjs";
4
+ import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-g3cDA5wr.mjs";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
6
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
7
7
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
8
8
  import crypto from "node:crypto";
9
9
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
10
- import { readResponseTextPrefix, readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
11
10
  //#region extensions/voice-call/src/webhook-security.ts
12
11
  const twilioReplayCache = createWebhookReplayCache();
13
12
  const plivoReplayCache = createWebhookReplayCache();
@@ -29,7 +28,8 @@ function createSkippedVerificationReplayKey(provider, ctx) {
29
28
  function validateTwilioSignature(authToken, signature, url, params) {
30
29
  if (!signature) return false;
31
30
  const dataToSign = buildTwilioDataToSign(url, params);
32
- return safeEqualSecret(signature, crypto.createHmac("sha1", authToken).update(dataToSign).digest("base64"));
31
+ const expectedSignature = crypto.createHmac("sha1", authToken).update(dataToSign).digest("base64");
32
+ return safeEqualSecret(signature, expectedSignature);
33
33
  }
34
34
  function buildTwilioDataToSign(url, params) {
35
35
  let dataToSign = url;
@@ -54,14 +54,16 @@ function isValidHostname(hostname) {
54
54
  */
55
55
  function extractHostname(hostHeader) {
56
56
  if (!hostHeader) return null;
57
+ let hostname;
57
58
  if (hostHeader.startsWith("[")) {
58
59
  const endBracket = hostHeader.indexOf("]");
59
60
  if (endBracket === -1) return null;
60
- return normalizeLowercaseStringOrEmpty(hostHeader.slice(1, endBracket));
61
+ hostname = hostHeader.slice(1, endBracket);
62
+ return normalizeLowercaseStringOrEmpty(hostname);
61
63
  }
62
64
  if (hostHeader.includes("@")) return null;
63
- const hostname = hostHeader.split(":").at(0);
64
- if (!hostname || !isValidHostname(hostname)) return null;
65
+ hostname = hostHeader.split(":")[0];
66
+ if (!isValidHostname(hostname)) return null;
65
67
  return normalizeLowercaseStringOrEmpty(hostname);
66
68
  }
67
69
  function extractHostnameFromHeader(headerValue) {
@@ -272,7 +274,7 @@ function verifyTelnyxWebhook(ctx, publicKey, options) {
272
274
  ok: false,
273
275
  reason: "Invalid signature"
274
276
  };
275
- const maxSkewMs = options?.maxSkewMs ?? 300 * 1e3;
277
+ const maxSkewMs = options?.maxSkewMs ?? 3e5;
276
278
  const eventTimeMs = eventTimeSec * 1e3;
277
279
  if (Math.abs(Date.now() - eventTimeMs) > maxSkewMs) return {
278
280
  ok: false,
@@ -381,7 +383,9 @@ function createPlivoV3ReplayKey(params) {
381
383
  }
382
384
  function validatePlivoV2Signature(params) {
383
385
  const baseUrl = getBaseUrlNoQuery(params.url);
384
- return safeEqualSecret(normalizeSignatureBase64(crypto.createHmac("sha256", params.authToken).update(baseUrl + params.nonce).digest("base64")), normalizeSignatureBase64(params.signature));
386
+ const expected = normalizeSignatureBase64(crypto.createHmac("sha256", params.authToken).update(baseUrl + params.nonce).digest("base64"));
387
+ const provided = normalizeSignatureBase64(params.signature);
388
+ return safeEqualSecret(expected, provided);
385
389
  }
386
390
  function toParamMapFromSearchParams(sp) {
387
391
  const map = {};
@@ -393,18 +397,16 @@ function toParamMapFromSearchParams(sp) {
393
397
  }
394
398
  function sortedQueryString(params) {
395
399
  const parts = [];
396
- const entries = Object.entries(params).toSorted(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
397
- for (const [key, entryValues] of entries) {
398
- const values = [...entryValues].toSorted();
400
+ for (const key of Object.keys(params).toSorted()) {
401
+ const values = [...params[key]].toSorted();
399
402
  for (const value of values) parts.push(`${key}=${value}`);
400
403
  }
401
404
  return parts.join("&");
402
405
  }
403
406
  function sortedParamsString(params) {
404
407
  const parts = [];
405
- const entries = Object.entries(params).toSorted(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
406
- for (const [key, entryValues] of entries) {
407
- const values = [...entryValues].toSorted();
408
+ for (const key of Object.keys(params).toSorted()) {
409
+ const values = [...params[key]].toSorted();
408
410
  for (const value of values) parts.push(`${key}${value}`);
409
411
  }
410
412
  return parts.join("");
@@ -462,6 +464,7 @@ function verifyPlivoWebhook(ctx, authToken, options) {
462
464
  if (options?.publicUrl) try {
463
465
  const req = new URL(reconstructed);
464
466
  const base = new URL(options.publicUrl);
467
+ base.pathname = req.pathname;
465
468
  base.search = req.search;
466
469
  verificationUrl = base.toString();
467
470
  } catch {
@@ -534,8 +537,8 @@ function verifyPlivoWebhook(ctx, authToken, options) {
534
537
  }
535
538
  //#endregion
536
539
  //#region extensions/voice-call/src/providers/shared/response-body.ts
537
- const PROVIDER_JSON_RESPONSE_MAX_BYTES = 1 * 1024 * 1024;
538
- const PROVIDER_ERROR_RESPONSE_MAX_BYTES = 8 * 1024;
540
+ const PROVIDER_JSON_RESPONSE_MAX_BYTES = 1048576;
541
+ const PROVIDER_ERROR_RESPONSE_MAX_BYTES = 8192;
539
542
  const TRUNCATED_SUFFIX = "... [truncated]";
540
543
  async function cancelProviderResponseBody(response) {
541
544
  await response.body?.cancel().catch(() => void 0);
@@ -544,12 +547,35 @@ function appendTruncatedSuffix(text) {
544
547
  return `${text.trimEnd()}${TRUNCATED_SUFFIX}`;
545
548
  }
546
549
  async function readProviderResponseTextWithLimit(params) {
547
- if (params.truncateOnLimit) {
548
- const prefix = await readResponseTextPrefix(params.response, params.maxBytes);
549
- return prefix.truncated ? appendTruncatedSuffix(prefix.text) : prefix.text;
550
+ if (!params.response.body) return "";
551
+ const reader = params.response.body.getReader();
552
+ const decoder = new TextDecoder();
553
+ let totalBytes = 0;
554
+ let text = "";
555
+ try {
556
+ while (true) {
557
+ const { done, value } = await reader.read();
558
+ if (done) return text + decoder.decode();
559
+ if (!value?.byteLength) continue;
560
+ const remainingBytes = params.maxBytes - totalBytes;
561
+ if (value.byteLength > remainingBytes) {
562
+ if (params.truncateOnLimit) {
563
+ const clipped = remainingBytes > 0 ? value.slice(0, remainingBytes) : void 0;
564
+ if (clipped) text += decoder.decode(clipped, { stream: true });
565
+ await reader.cancel().catch(() => void 0);
566
+ return appendTruncatedSuffix(text + decoder.decode());
567
+ }
568
+ await reader.cancel().catch(() => void 0);
569
+ throw new Error(`provider response body too large: ${totalBytes + value.byteLength} bytes (limit: ${params.maxBytes} bytes)`);
570
+ }
571
+ text += decoder.decode(value, { stream: true });
572
+ totalBytes += value.byteLength;
573
+ }
574
+ } finally {
575
+ try {
576
+ reader.releaseLock();
577
+ } catch {}
550
578
  }
551
- const body = await readResponseWithLimit(params.response, params.maxBytes, { onOverflow: ({ size, maxBytes }) => /* @__PURE__ */ new Error(`provider response body too large: ${size} bytes (limit: ${maxBytes} bytes)`) });
552
- return new TextDecoder().decode(body);
553
579
  }
554
580
  async function readProviderJsonResponseText(response) {
555
581
  return await readProviderResponseTextWithLimit({
@@ -1,4 +1,4 @@
1
- import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-aaP79dI8.js";
1
+ import { n as markWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-g3cDA5wr.mjs";
2
2
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import crypto from "node:crypto";
4
4
  //#region extensions/voice-call/src/providers/mock.ts
@@ -1,5 +1,5 @@
1
- import { _ as escapeXml, s as getHeader } from "./runtime-entry-CQtitvRd.js";
2
- import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
1
+ import { g as escapeXml, o as getHeader } from "./runtime-entry-CH-CV0Sf.mjs";
2
+ import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-BukKSVNW.mjs";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import crypto from "node:crypto";
5
5
  //#region extensions/voice-call/src/providers/plivo.ts
@@ -371,6 +371,7 @@ var PlivoProvider = class PlivoProvider {
371
371
  try {
372
372
  if (this.options.publicUrl) {
373
373
  const base = new URL(this.options.publicUrl);
374
+ base.pathname = new URL(ctx.url).pathname;
374
375
  return `${base.origin}${base.pathname}`;
375
376
  }
376
377
  const u = new URL(reconstructWebhookUrl(ctx, {