@openclaw/voice-call 2026.7.2-beta.2 → 2026.7.2-beta.4
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 +1 -1
- package/dist/doctor-contract-api.js +1 -0
- package/dist/{guarded-json-api-BKOgUU1q.js → guarded-json-api-Dxhe25Zi.js} +1 -2
- package/dist/index.js +10 -6
- package/dist/{plivo-ByAIoPFp.js → plivo-B-KvPVji.js} +2 -3
- package/dist/{realtime-handler-DJk-eYyf.js → realtime-handler-hIVPEZxN.js} +95 -175
- package/dist/{response-generator-Cdj8EuLg.js → response-generator-z6M9229t.js} +1 -1
- package/dist/{runtime-entry-DSgyqIwE.js → runtime-entry-CQtitvRd.js} +89 -152
- package/dist/runtime-entry.js +1 -1
- package/dist/{telnyx-DMbLYwj4.js → telnyx-BG56lL7z.js} +1 -1
- package/dist/{twilio-BTKKlkLT.js → twilio-B3FQ_P9x.js} +9 -5
- package/node_modules/typebox/build/compile/validator.d.mts +1 -4
- package/node_modules/typebox/build/guard/guard.d.mts +1 -1
- package/node_modules/typebox/build/guard/guard.mjs +5 -2
- package/node_modules/typebox/build/guard/string.mjs +16 -4
- package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
- package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
- package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
- package/node_modules/typebox/build/typebox.d.mts +1 -1
- package/node_modules/typebox/build/typebox.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +29 -32
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/npm-shrinkwrap.json +11 -11
- package/openclaw.plugin.json +1 -15
- package/package.json +6 -6
|
@@ -8,6 +8,7 @@ import { MAX_TIMER_TIMEOUT_MS, asDateTimestampMs, resolveExpiresAtMsFromDuration
|
|
|
8
8
|
import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
9
9
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
10
10
|
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
|
11
|
+
import { resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
|
11
12
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
12
13
|
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES, assertRealtimeVoiceAgentConsultModelSelectionUnlocked, buildRealtimeVoiceAgentConsultPolicyInstructions, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createTalkSessionController, recordTalkObservabilityEvent, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow, resolveRealtimeVoiceFastContextConsult } from "openclaw/plugin-sdk/realtime-voice";
|
|
13
14
|
import { mergeDeep } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
@@ -23,13 +24,13 @@ import path from "node:path";
|
|
|
23
24
|
import os from "node:os";
|
|
24
25
|
import { root } from "openclaw/plugin-sdk/security-runtime";
|
|
25
26
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
26
|
-
import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
|
|
27
27
|
import { spawn } from "node:child_process";
|
|
28
28
|
import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
|
|
29
29
|
import http from "node:http";
|
|
30
30
|
import { URL as URL$1 } from "node:url";
|
|
31
31
|
import { resolveConfiguredCapabilityProvider } from "openclaw/plugin-sdk/provider-selection-runtime";
|
|
32
32
|
import { WebSocket, WebSocketServer } from "ws";
|
|
33
|
+
import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
|
|
33
34
|
//#region extensions/voice-call/src/realtime-defaults.ts
|
|
34
35
|
/** Baseline instructions that keep realtime calls brief and route deep work to agent consult. */
|
|
35
36
|
const DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS = `You are OpenClaw's phone-call realtime voice interface. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call ${REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME} before answering.`;
|
|
@@ -405,7 +406,7 @@ const VoiceCallConfigSchema = z.object({
|
|
|
405
406
|
publicUrl: z.string().url().optional(),
|
|
406
407
|
/** Skip webhook signature verification (development only, NOT for production) */
|
|
407
408
|
skipSignatureVerification: z.boolean().default(false),
|
|
408
|
-
/** TTS override (deep-merges with core
|
|
409
|
+
/** TTS override (deep-merges with core tts) */
|
|
409
410
|
tts: TtsConfigSchema,
|
|
410
411
|
/** Store path for call logs */
|
|
411
412
|
store: z.string().optional(),
|
|
@@ -1839,7 +1840,7 @@ async function buildRealtimeVoiceInstructions(params) {
|
|
|
1839
1840
|
if (consultGuidance) sections.push(consultGuidance);
|
|
1840
1841
|
const contextConfig = config.realtime.agentContext;
|
|
1841
1842
|
if (!contextConfig.enabled) return sections.filter(Boolean).join("\n\n");
|
|
1842
|
-
const agentId =
|
|
1843
|
+
const { agentId } = params;
|
|
1843
1844
|
const capsule = [
|
|
1844
1845
|
"OpenClaw agent voice context:",
|
|
1845
1846
|
`- Agent id: ${agentId}`,
|
|
@@ -1913,26 +1914,26 @@ function chunkAudio(audio, chunkSize = 160) {
|
|
|
1913
1914
|
/** Default timeout for one telephony synthesis request. */
|
|
1914
1915
|
const TELEPHONY_DEFAULT_TTS_TIMEOUT_MS = 8e3;
|
|
1915
1916
|
/** Create a TTS provider that honors voice-call overrides and converts PCM to mulaw. */
|
|
1916
|
-
function createTelephonyTtsProvider(params) {
|
|
1917
|
+
async function createTelephonyTtsProvider(params) {
|
|
1917
1918
|
const { coreConfig, ttsOverride, runtime, logger } = params;
|
|
1918
|
-
const
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1919
|
+
const preparedConfig = await runtime.prepareTtsRequest({
|
|
1920
|
+
cfg: coreConfig,
|
|
1921
|
+
override: ttsOverride,
|
|
1922
|
+
text: ""
|
|
1923
|
+
});
|
|
1923
1924
|
return {
|
|
1924
|
-
synthesisTimeoutMs: resolveTimerTimeoutMs(
|
|
1925
|
+
synthesisTimeoutMs: resolveTimerTimeoutMs(preparedConfig.cfg.tts?.timeoutMs, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS),
|
|
1925
1926
|
synthesizeForTelephony: async (text) => {
|
|
1926
|
-
const
|
|
1927
|
-
cfg:
|
|
1928
|
-
|
|
1929
|
-
preferredProviderId: activeProvider
|
|
1927
|
+
const prepared = await runtime.prepareTtsRequest({
|
|
1928
|
+
cfg: preparedConfig.cfg,
|
|
1929
|
+
text
|
|
1930
1930
|
});
|
|
1931
|
+
const directives = prepared.directives;
|
|
1931
1932
|
if (directives.warnings.length > 0) logger?.warn?.(`[voice-call] Ignored telephony TTS directive overrides (${directives.warnings.join("; ")})`);
|
|
1932
1933
|
const cleanText = directives.hasDirective ? directives.ttsText?.trim() || directives.cleanedText.trim() : text;
|
|
1933
1934
|
const result = await runtime.textToSpeechTelephony({
|
|
1934
1935
|
text: cleanText,
|
|
1935
|
-
cfg:
|
|
1936
|
+
cfg: prepared.cfg,
|
|
1936
1937
|
overrides: directives.overrides
|
|
1937
1938
|
});
|
|
1938
1939
|
if (!result.success || !result.audioBuffer || !result.sampleRate) throw new Error(result.error ?? "TTS conversion failed");
|
|
@@ -1944,94 +1945,6 @@ function createTelephonyTtsProvider(params) {
|
|
|
1944
1945
|
}
|
|
1945
1946
|
};
|
|
1946
1947
|
}
|
|
1947
|
-
/** Apply voice-call TTS overrides to core config without mutating the original object. */
|
|
1948
|
-
function applyTtsOverride(coreConfig, override) {
|
|
1949
|
-
if (!override) return coreConfig;
|
|
1950
|
-
const base = coreConfig.messages?.tts;
|
|
1951
|
-
const merged = mergeTtsConfig(base, override);
|
|
1952
|
-
if (!merged) return coreConfig;
|
|
1953
|
-
return {
|
|
1954
|
-
...coreConfig,
|
|
1955
|
-
messages: {
|
|
1956
|
-
...coreConfig.messages,
|
|
1957
|
-
tts: merged
|
|
1958
|
-
}
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
/** Merge core and voice-call TTS config, keeping undefined override fields out. */
|
|
1962
|
-
function mergeTtsConfig(base, override) {
|
|
1963
|
-
if (!base && !override) return;
|
|
1964
|
-
if (!override) return base;
|
|
1965
|
-
if (!base) return override;
|
|
1966
|
-
return mergeDeep(base, override);
|
|
1967
|
-
}
|
|
1968
|
-
/** Resolve directive override policy for telephony synthesis. */
|
|
1969
|
-
function resolveTelephonyModelOverridePolicy(overrides) {
|
|
1970
|
-
if (!(overrides?.enabled ?? true)) return {
|
|
1971
|
-
enabled: false,
|
|
1972
|
-
allowText: false,
|
|
1973
|
-
allowProvider: false,
|
|
1974
|
-
allowVoice: false,
|
|
1975
|
-
allowModelId: false,
|
|
1976
|
-
allowVoiceSettings: false,
|
|
1977
|
-
allowNormalization: false,
|
|
1978
|
-
allowSeed: false
|
|
1979
|
-
};
|
|
1980
|
-
const allow = (value, defaultValue = true) => value ?? defaultValue;
|
|
1981
|
-
return {
|
|
1982
|
-
enabled: true,
|
|
1983
|
-
allowText: allow(overrides?.allowText),
|
|
1984
|
-
allowProvider: allow(overrides?.allowProvider, false),
|
|
1985
|
-
allowVoice: allow(overrides?.allowVoice),
|
|
1986
|
-
allowModelId: allow(overrides?.allowModelId),
|
|
1987
|
-
allowVoiceSettings: allow(overrides?.allowVoiceSettings),
|
|
1988
|
-
allowNormalization: allow(overrides?.allowNormalization),
|
|
1989
|
-
allowSeed: allow(overrides?.allowSeed)
|
|
1990
|
-
};
|
|
1991
|
-
}
|
|
1992
|
-
/** Read model override policy from TTS config when present. */
|
|
1993
|
-
function readTelephonyModelOverrides(ttsConfig) {
|
|
1994
|
-
const value = ttsConfig?.modelOverrides;
|
|
1995
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1996
|
-
}
|
|
1997
|
-
/** Normalize provider ids for config lookup. */
|
|
1998
|
-
function normalizeProviderId(value) {
|
|
1999
|
-
return typeof value === "string" ? value.trim().toLowerCase() || void 0 : void 0;
|
|
2000
|
-
}
|
|
2001
|
-
/** Coerce provider config objects while rejecting arrays and primitives. */
|
|
2002
|
-
function asProviderConfig(value) {
|
|
2003
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2004
|
-
}
|
|
2005
|
-
/** Collect named provider configs from canonical and legacy TTS config shapes. */
|
|
2006
|
-
function collectTelephonyProviderConfigs(ttsConfig) {
|
|
2007
|
-
if (!ttsConfig) return {};
|
|
2008
|
-
const entries = {};
|
|
2009
|
-
const rawProviders = ttsConfig.providers && typeof ttsConfig.providers === "object" && !Array.isArray(ttsConfig.providers) ? ttsConfig.providers : {};
|
|
2010
|
-
for (const [providerId, value] of Object.entries(rawProviders)) {
|
|
2011
|
-
const normalized = normalizeProviderId(providerId) ?? providerId;
|
|
2012
|
-
entries[normalized] = asProviderConfig(value);
|
|
2013
|
-
}
|
|
2014
|
-
const reservedKeys = /* @__PURE__ */ new Set([
|
|
2015
|
-
"auto",
|
|
2016
|
-
"enabled",
|
|
2017
|
-
"maxTextLength",
|
|
2018
|
-
"mode",
|
|
2019
|
-
"modelOverrides",
|
|
2020
|
-
"persona",
|
|
2021
|
-
"personas",
|
|
2022
|
-
"prefsPath",
|
|
2023
|
-
"provider",
|
|
2024
|
-
"providers",
|
|
2025
|
-
"summaryModel",
|
|
2026
|
-
"timeoutMs"
|
|
2027
|
-
]);
|
|
2028
|
-
for (const [key, value] of Object.entries(ttsConfig)) {
|
|
2029
|
-
if (reservedKeys.has(key) || typeof value !== "object" || value === null || Array.isArray(value)) continue;
|
|
2030
|
-
const normalized = normalizeProviderId(key) ?? key;
|
|
2031
|
-
entries[normalized] ??= asProviderConfig(value);
|
|
2032
|
-
}
|
|
2033
|
-
return entries;
|
|
2034
|
-
}
|
|
2035
1948
|
//#endregion
|
|
2036
1949
|
//#region extensions/voice-call/src/bounded-child-output.ts
|
|
2037
1950
|
const DEFAULT_MAX_OUTPUT_CHARS = 16384;
|
|
@@ -2159,6 +2072,30 @@ async function cleanupTailscaleExposure(config) {
|
|
|
2159
2072
|
const NGROK_LOG_BUFFER_MAX_CHARS = 16384;
|
|
2160
2073
|
const NGROK_ERROR_MARKER = "ERR_NGROK";
|
|
2161
2074
|
const TUNNEL_COMMAND_OUTPUT_MAX_BYTES = 16384;
|
|
2075
|
+
const NGROK_STOP_GRACE_MS = 2e3;
|
|
2076
|
+
const NGROK_FORCE_KILL_WAIT_MS = 1e3;
|
|
2077
|
+
async function terminateNgrokProcess(proc, isClosed) {
|
|
2078
|
+
if (isClosed()) return;
|
|
2079
|
+
await new Promise((resolve) => {
|
|
2080
|
+
let finished = false;
|
|
2081
|
+
let forceKillWaitTimer;
|
|
2082
|
+
const finish = () => {
|
|
2083
|
+
if (finished) return;
|
|
2084
|
+
finished = true;
|
|
2085
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
2086
|
+
if (forceKillWaitTimer) clearTimeout(forceKillWaitTimer);
|
|
2087
|
+
proc.off("close", finish);
|
|
2088
|
+
resolve();
|
|
2089
|
+
};
|
|
2090
|
+
proc.once("close", finish);
|
|
2091
|
+
const forceKillTimer = setTimeout(() => {
|
|
2092
|
+
forceKillWaitTimer = setTimeout(finish, NGROK_FORCE_KILL_WAIT_MS);
|
|
2093
|
+
if (!isClosed()) proc.kill("SIGKILL");
|
|
2094
|
+
}, NGROK_STOP_GRACE_MS);
|
|
2095
|
+
proc.kill("SIGTERM");
|
|
2096
|
+
if (isClosed()) finish();
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2162
2099
|
function listenForChildStreamErrors(proc, onError) {
|
|
2163
2100
|
proc.stdout.on("error", (error) => onError("stdout", error));
|
|
2164
2101
|
proc.stderr.on("error", (error) => onError("stderr", error));
|
|
@@ -2194,23 +2131,25 @@ async function startNgrokTunnel(config) {
|
|
|
2194
2131
|
"pipe",
|
|
2195
2132
|
"pipe"
|
|
2196
2133
|
] });
|
|
2197
|
-
let
|
|
2198
|
-
let
|
|
2134
|
+
let startupSettled = false;
|
|
2135
|
+
let childClosed = false;
|
|
2199
2136
|
let publicUrl = null;
|
|
2200
2137
|
let outputBuffer = "";
|
|
2201
2138
|
let stderrTail = "";
|
|
2202
2139
|
const timeout = setTimeout(() => {
|
|
2203
|
-
if (!
|
|
2204
|
-
|
|
2205
|
-
proc.
|
|
2206
|
-
|
|
2140
|
+
if (!startupSettled) {
|
|
2141
|
+
startupSettled = true;
|
|
2142
|
+
terminateNgrokProcess(proc, () => childClosed).then(() => {
|
|
2143
|
+
reject(/* @__PURE__ */ new Error("ngrok startup timed out (30s)"));
|
|
2144
|
+
});
|
|
2207
2145
|
}
|
|
2208
2146
|
}, 3e4);
|
|
2147
|
+
timeout.unref();
|
|
2209
2148
|
const rejectIfPending = (message, kill = false) => {
|
|
2210
|
-
if (!
|
|
2211
|
-
|
|
2149
|
+
if (!startupSettled) {
|
|
2150
|
+
startupSettled = true;
|
|
2212
2151
|
clearTimeout(timeout);
|
|
2213
|
-
if (kill && !
|
|
2152
|
+
if (kill && !childClosed) proc.kill("SIGKILL");
|
|
2214
2153
|
reject(new Error(message));
|
|
2215
2154
|
}
|
|
2216
2155
|
};
|
|
@@ -2219,8 +2158,8 @@ async function startNgrokTunnel(config) {
|
|
|
2219
2158
|
const log = JSON.parse(line);
|
|
2220
2159
|
if (log.msg === "started tunnel" && log.url) publicUrl = log.url;
|
|
2221
2160
|
if (log.addr && log.url && !publicUrl) publicUrl = log.url;
|
|
2222
|
-
if (publicUrl && !
|
|
2223
|
-
|
|
2161
|
+
if (publicUrl && !startupSettled) {
|
|
2162
|
+
startupSettled = true;
|
|
2224
2163
|
clearTimeout(timeout);
|
|
2225
2164
|
const fullUrl = publicUrl + config.path;
|
|
2226
2165
|
console.log(`[voice-call] ngrok tunnel active: ${fullUrl}`);
|
|
@@ -2228,38 +2167,22 @@ async function startNgrokTunnel(config) {
|
|
|
2228
2167
|
publicUrl: fullUrl,
|
|
2229
2168
|
provider: "ngrok",
|
|
2230
2169
|
stop: async () => {
|
|
2231
|
-
|
|
2232
|
-
await new Promise((res) => {
|
|
2233
|
-
let finished = false;
|
|
2234
|
-
const finish = () => {
|
|
2235
|
-
if (finished) return;
|
|
2236
|
-
finished = true;
|
|
2237
|
-
clearTimeout(fallback);
|
|
2238
|
-
proc.off("close", finish);
|
|
2239
|
-
res();
|
|
2240
|
-
};
|
|
2241
|
-
if (closed) {
|
|
2242
|
-
res();
|
|
2243
|
-
return;
|
|
2244
|
-
}
|
|
2245
|
-
proc.once("close", finish);
|
|
2246
|
-
const fallback = setTimeout(finish, 2e3);
|
|
2247
|
-
proc.kill("SIGTERM");
|
|
2248
|
-
if (closed) finish();
|
|
2249
|
-
});
|
|
2170
|
+
await terminateNgrokProcess(proc, () => childClosed);
|
|
2250
2171
|
}
|
|
2251
2172
|
});
|
|
2252
2173
|
}
|
|
2253
2174
|
} catch {}
|
|
2254
2175
|
};
|
|
2255
|
-
proc.stdout.
|
|
2256
|
-
|
|
2176
|
+
proc.stdout.setEncoding("utf8");
|
|
2177
|
+
proc.stderr.setEncoding("utf8");
|
|
2178
|
+
proc.stdout.on("data", (chunk) => {
|
|
2179
|
+
const lines = (outputBuffer + chunk).split("\n");
|
|
2257
2180
|
outputBuffer = lines.pop() || "";
|
|
2258
2181
|
if (outputBuffer.length > NGROK_LOG_BUFFER_MAX_CHARS) outputBuffer = sliceUtf16Safe(outputBuffer, -16384);
|
|
2259
2182
|
for (const line of lines) if (line.trim()) processLine(line);
|
|
2260
2183
|
});
|
|
2261
|
-
proc.stderr.on("data", (
|
|
2262
|
-
const combined = stderrTail +
|
|
2184
|
+
proc.stderr.on("data", (chunk) => {
|
|
2185
|
+
const combined = stderrTail + chunk;
|
|
2263
2186
|
if (combined.includes(NGROK_ERROR_MARKER)) rejectIfPending(`ngrok error: ${formatBoundedChildOutput(appendBoundedChildOutput(emptyBoundedChildOutput(), combined))}`, true);
|
|
2264
2187
|
stderrTail = sliceUtf16Safe(combined, -8);
|
|
2265
2188
|
});
|
|
@@ -2270,9 +2193,9 @@ async function startNgrokTunnel(config) {
|
|
|
2270
2193
|
rejectIfPending(`Failed to start ngrok: ${err.message}`);
|
|
2271
2194
|
});
|
|
2272
2195
|
proc.on("close", (code) => {
|
|
2273
|
-
|
|
2274
|
-
if (!
|
|
2275
|
-
|
|
2196
|
+
childClosed = true;
|
|
2197
|
+
if (!startupSettled) {
|
|
2198
|
+
startupSettled = true;
|
|
2276
2199
|
clearTimeout(timeout);
|
|
2277
2200
|
reject(/* @__PURE__ */ new Error(`ngrok exited unexpectedly with code ${code}`));
|
|
2278
2201
|
}
|
|
@@ -2446,6 +2369,11 @@ function getHeader(headers, name) {
|
|
|
2446
2369
|
return value;
|
|
2447
2370
|
}
|
|
2448
2371
|
//#endregion
|
|
2372
|
+
//#region extensions/voice-call/src/media-base64.ts
|
|
2373
|
+
function canonicalizeVoiceCallMediaBase64(payloadBase64) {
|
|
2374
|
+
return canonicalizeBase64(payloadBase64.replaceAll("-", "+").replaceAll("_", "/"));
|
|
2375
|
+
}
|
|
2376
|
+
//#endregion
|
|
2449
2377
|
//#region extensions/voice-call/src/media-stream.ts
|
|
2450
2378
|
const DEFAULT_PRE_START_TIMEOUT_MS = 5e3;
|
|
2451
2379
|
const DEFAULT_MAX_PENDING_CONNECTIONS = 32;
|
|
@@ -2560,7 +2488,9 @@ var MediaStreamHandler = class {
|
|
|
2560
2488
|
break;
|
|
2561
2489
|
case "media":
|
|
2562
2490
|
if (session && message.media?.payload) {
|
|
2563
|
-
const
|
|
2491
|
+
const canonicalPayload = canonicalizeVoiceCallMediaBase64(message.media.payload);
|
|
2492
|
+
if (!canonicalPayload) break;
|
|
2493
|
+
const audioBuffer = Buffer.from(canonicalPayload, "base64");
|
|
2564
2494
|
const turnId = this.ensureActiveTurn(session);
|
|
2565
2495
|
this.emitTalkEvent(session, {
|
|
2566
2496
|
type: "input.audio.delta",
|
|
@@ -3141,7 +3071,7 @@ const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
|
|
|
3141
3071
|
const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
|
|
3142
3072
|
const STREAM_DISCONNECT_HANGUP_GRACE_MS = 2e3;
|
|
3143
3073
|
const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
|
|
3144
|
-
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-
|
|
3074
|
+
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-z6M9229t.js"));
|
|
3145
3075
|
function appendRecentTalkEventMetadata(call, event) {
|
|
3146
3076
|
const metadata = call.metadata ?? {};
|
|
3147
3077
|
const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
@@ -3816,12 +3746,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
|
|
|
3816
3746
|
"Do not print secret values or dump environment variables; only check whether required configuration is present.",
|
|
3817
3747
|
"Be accurate, brief, and speakable."
|
|
3818
3748
|
].join(" ");
|
|
3819
|
-
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-
|
|
3820
|
-
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-
|
|
3821
|
-
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-
|
|
3749
|
+
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-BG56lL7z.js"));
|
|
3750
|
+
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-B3FQ_P9x.js"));
|
|
3751
|
+
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-B-KvPVji.js"));
|
|
3822
3752
|
const loadMockProvider = createLazyRuntimeModule(() => import("./mock-YsvBTX-7.js"));
|
|
3823
3753
|
const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
|
|
3824
|
-
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-
|
|
3754
|
+
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-hIVPEZxN.js"));
|
|
3825
3755
|
function resolveVoiceCallConsultSessionKey(call) {
|
|
3826
3756
|
return resolveVoiceCallSessionKey({
|
|
3827
3757
|
config: call.config,
|
|
@@ -3946,7 +3876,8 @@ async function createRealtimeInstructionsResolver(params) {
|
|
|
3946
3876
|
baseInstructions: params.config.realtime.instructions,
|
|
3947
3877
|
config: genericConfig,
|
|
3948
3878
|
coreConfig: params.coreConfig,
|
|
3949
|
-
agentRuntime: params.agentRuntime
|
|
3879
|
+
agentRuntime: params.agentRuntime,
|
|
3880
|
+
agentId: params.config.agentId
|
|
3950
3881
|
});
|
|
3951
3882
|
const entries = await Promise.all(listRealtimeAgentIds(params.config, params.coreConfig).map(async (agentId) => {
|
|
3952
3883
|
return [agentId, await buildRealtimeVoiceInstructions({
|
|
@@ -3956,7 +3887,8 @@ async function createRealtimeInstructionsResolver(params) {
|
|
|
3956
3887
|
agentId
|
|
3957
3888
|
},
|
|
3958
3889
|
coreConfig: params.coreConfig,
|
|
3959
|
-
agentRuntime: params.agentRuntime
|
|
3890
|
+
agentRuntime: params.agentRuntime,
|
|
3891
|
+
agentId
|
|
3960
3892
|
})];
|
|
3961
3893
|
}));
|
|
3962
3894
|
const instructionsByAgentId = new Map(entries);
|
|
@@ -3974,8 +3906,13 @@ async function createVoiceCallRuntime(params) {
|
|
|
3974
3906
|
error: console.error,
|
|
3975
3907
|
debug: console.debug
|
|
3976
3908
|
};
|
|
3977
|
-
const config = resolveVoiceCallConfig(rawConfig);
|
|
3978
3909
|
const cfg = fullConfig ?? coreConfig;
|
|
3910
|
+
const unresolvedConfig = resolveVoiceCallConfig(rawConfig);
|
|
3911
|
+
const configuredAgentId = unresolvedConfig.agentId ? normalizeAgentId(unresolvedConfig.agentId) : resolveDefaultAgentId(cfg);
|
|
3912
|
+
const config = {
|
|
3913
|
+
...unresolvedConfig,
|
|
3914
|
+
agentId: configuredAgentId
|
|
3915
|
+
};
|
|
3979
3916
|
if (!config.enabled) throw new Error("Voice call disabled. Enable the plugin entry in config.");
|
|
3980
3917
|
if (config.skipSignatureVerification) log.warn("[voice-call] SECURITY WARNING: skipSignatureVerification=true disables webhook signature verification (development only). Do not use in production.");
|
|
3981
3918
|
const validation = validateProviderConfig(config);
|
|
@@ -4101,8 +4038,8 @@ async function createVoiceCallRuntime(params) {
|
|
|
4101
4038
|
if (provider.name === "twilio" && config.streaming?.enabled) {
|
|
4102
4039
|
const twilioProvider = provider;
|
|
4103
4040
|
if (ttsRuntime?.textToSpeechTelephony) try {
|
|
4104
|
-
const ttsProvider = createTelephonyTtsProvider({
|
|
4105
|
-
coreConfig,
|
|
4041
|
+
const ttsProvider = await createTelephonyTtsProvider({
|
|
4042
|
+
coreConfig: cfg,
|
|
4106
4043
|
ttsOverride: config.tts,
|
|
4107
4044
|
runtime: ttsRuntime,
|
|
4108
4045
|
logger: log
|
|
@@ -4140,4 +4077,4 @@ async function createVoiceCallRuntime(params) {
|
|
|
4140
4077
|
}
|
|
4141
4078
|
}
|
|
4142
4079
|
//#endregion
|
|
4143
|
-
export {
|
|
4080
|
+
export { validateProviderConfig as S, escapeXml as _, normalizeProviderStatus as a, resolveVoiceCallConfig as b, resolveWebhookExposureStatus as c, setupTailscaleExposureRoute as d, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS as f, resolveUserPath as g, resolveCallAgentId as h, mapProviderStatusToEndReason as i, cleanupTailscaleExposureRoute as l, resolveVoiceResponseModel as m, normalizeProxyIp as n, canonicalizeVoiceCallMediaBase64 as o, chunkAudio as p, isProviderStatusTerminal as r, getHeader as s, createVoiceCallRuntime as t, getTailscaleSelfInfo as u, mapVoiceToPolly as v, resolveVoiceCallSessionKey as x, VoiceCallConfigSchema as y };
|
package/dist/runtime-entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createVoiceCallRuntime } from "./runtime-entry-
|
|
1
|
+
import { t as createVoiceCallRuntime } from "./runtime-entry-CQtitvRd.js";
|
|
2
2
|
export { createVoiceCallRuntime };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
1
|
+
import { s as verifyTelnyxWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
//#region extensions/voice-call/src/providers/telnyx.ts
|
|
4
4
|
function normalizeTelnyxDirection(direction) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { fetchWithSsrFGuard } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
3
|
import { n as requireSupportedTwilioApiHostname, r as resolveTwilioApiBaseUrl } from "./twilio-region-4fkgz3UG.js";
|
|
4
|
-
import { _ as
|
|
5
|
-
import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
4
|
+
import { _ as escapeXml, a as normalizeProviderStatus, i as mapProviderStatusToEndReason, p as chunkAudio, r as isProviderStatusTerminal, s as getHeader, v as mapVoiceToPolly } from "./runtime-entry-CQtitvRd.js";
|
|
5
|
+
import { c as verifyTwilioWebhook, i as readProviderJsonResponseText, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-Dxhe25Zi.js";
|
|
6
6
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
|
+
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
|
7
8
|
import crypto from "node:crypto";
|
|
8
9
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
9
10
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -628,9 +629,12 @@ var TwilioProvider = class TwilioProvider {
|
|
|
628
629
|
chunkAttempts += 1;
|
|
629
630
|
if (sendAudioChunk(chunk).sent) chunkDelivered += 1;
|
|
630
631
|
const waitMs = nextChunkDueAt - Date.now();
|
|
631
|
-
if (waitMs > 0)
|
|
632
|
-
|
|
633
|
-
})
|
|
632
|
+
if (waitMs > 0) try {
|
|
633
|
+
await sleepWithAbort(Math.ceil(waitMs), signal);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
if (!signal.aborted) throw error;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
634
638
|
nextChunkDueAt += CHUNK_DELAY_MS;
|
|
635
639
|
if (signal.aborted) break;
|
|
636
640
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { type TLocalizedValidationError } from '../error/index.mjs';
|
|
2
2
|
import { type StaticDecode, type StaticEncode, type TProperties, type TSchema } from '../type/index.mjs';
|
|
3
|
-
import { BuildResult, EvaluateResult } from '../schema/index.mjs';
|
|
4
3
|
export declare class Validator<Context extends TProperties = TProperties, Type extends TSchema = TSchema, Encode extends unknown = StaticEncode<Type, Context>, Decode extends unknown = StaticDecode<Type, Context>> {
|
|
5
4
|
private readonly hasCodec;
|
|
6
5
|
private readonly buildResult;
|
|
7
6
|
private readonly evaluateResult;
|
|
8
|
-
/** Constructs a Validator
|
|
7
|
+
/** Constructs a Validator. */
|
|
9
8
|
constructor(context: Context, type: Type);
|
|
10
|
-
/** Constructs a Validator with the given arguments. */
|
|
11
|
-
constructor(hasCodec: boolean, buildResult: BuildResult, evaluateResult: EvaluateResult);
|
|
12
9
|
/** Returns true if this Validator is using JIT acceleration. */
|
|
13
10
|
IsAccelerated(): boolean;
|
|
14
11
|
/** Returns the Context for this validator. */
|
|
@@ -61,5 +61,5 @@ export declare function Keys(value: Record<PropertyKey, unknown>): string[];
|
|
|
61
61
|
export declare function Symbols(value: Record<PropertyKey, unknown>): symbol[];
|
|
62
62
|
/** Returns the property values for the given object via `Object.values()` */
|
|
63
63
|
export declare function Values(value: Record<PropertyKey, unknown>): unknown[];
|
|
64
|
-
/**
|
|
64
|
+
/** Returns true if left and right values are structurally equal */
|
|
65
65
|
export declare function IsDeepEqual(left: unknown, right: unknown): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// deno-fmt-ignore-file
|
|
1
2
|
import * as String from './string.mjs';
|
|
2
3
|
// --------------------------------------------------------------------------
|
|
3
4
|
// Guards
|
|
@@ -203,7 +204,9 @@ function DeepEqualArray(left, right) {
|
|
|
203
204
|
return IsArray(right) && IsEqual(left.length, right.length) &&
|
|
204
205
|
left.every((_, index) => IsDeepEqual(left[index], right[index]));
|
|
205
206
|
}
|
|
206
|
-
/**
|
|
207
|
+
/** Returns true if left and right values are structurally equal */
|
|
207
208
|
export function IsDeepEqual(left, right) {
|
|
208
|
-
return (IsArray(left) ? DeepEqualArray(left, right) :
|
|
209
|
+
return (IsArray(left) ? DeepEqualArray(left, right) :
|
|
210
|
+
IsObject(left) ? DeepEqualObject(left, right) :
|
|
211
|
+
IsEqual(left, right));
|
|
209
212
|
}
|
|
@@ -5,6 +5,18 @@ function IsBetween(value, min, max) {
|
|
|
5
5
|
return value >= min && value <= max;
|
|
6
6
|
}
|
|
7
7
|
// --------------------------------------------------------------------------
|
|
8
|
+
// IsZeroWidthJoiner
|
|
9
|
+
// --------------------------------------------------------------------------
|
|
10
|
+
function IsZeroWidthJoiner(value) {
|
|
11
|
+
return (value === 0x200D);
|
|
12
|
+
}
|
|
13
|
+
// --------------------------------------------------------------------------
|
|
14
|
+
// IsHighSurrogate
|
|
15
|
+
// --------------------------------------------------------------------------
|
|
16
|
+
function IsHighSurrogate(value) {
|
|
17
|
+
return IsBetween(value, 0xD800, 0xDBFF);
|
|
18
|
+
}
|
|
19
|
+
// --------------------------------------------------------------------------
|
|
8
20
|
// IsRegionalIndicator
|
|
9
21
|
// --------------------------------------------------------------------------
|
|
10
22
|
function IsRegionalIndicator(value) {
|
|
@@ -72,10 +84,10 @@ function NextGraphemeClusterIndex(value, clusterStart) {
|
|
|
72
84
|
// IsGraphemeCodePoint
|
|
73
85
|
// --------------------------------------------------------------------------
|
|
74
86
|
function IsGraphemeCodePoint(value) {
|
|
75
|
-
return (
|
|
76
|
-
|
|
77
|
-
(value
|
|
78
|
-
|
|
87
|
+
return (IsHighSurrogate(value) ||
|
|
88
|
+
IsCombiningMark(value) ||
|
|
89
|
+
IsVariationSelector(value) ||
|
|
90
|
+
IsZeroWidthJoiner(value));
|
|
79
91
|
}
|
|
80
92
|
// --------------------------------------------------------------------------
|
|
81
93
|
// GraphemeCount
|
|
@@ -1,47 +1,66 @@
|
|
|
1
1
|
// deno-fmt-ignore-file
|
|
2
|
-
import * as Schema from '../types/index.mjs';
|
|
3
2
|
import { Hashing } from '../../system/hashing/index.mjs';
|
|
4
3
|
import { EmitGuard as E } from '../../guard/index.mjs';
|
|
5
4
|
import { BuildSchema } from './schema.mjs';
|
|
6
|
-
const
|
|
5
|
+
const index = [0];
|
|
6
|
+
const names = new Map;
|
|
7
|
+
const funcs = new Map();
|
|
8
|
+
// ------------------------------------------------------------------
|
|
9
|
+
// CreateName
|
|
10
|
+
// ------------------------------------------------------------------
|
|
11
|
+
function NextName() {
|
|
12
|
+
return Hashing.Hash(index[0]++);
|
|
13
|
+
}
|
|
14
|
+
function CreateName(schema, href) {
|
|
15
|
+
if (!names.has(schema))
|
|
16
|
+
names.set(schema, new Map());
|
|
17
|
+
const hrefs = names.get(schema);
|
|
18
|
+
if (hrefs.has(href))
|
|
19
|
+
return hrefs.get(href);
|
|
20
|
+
const name = NextName();
|
|
21
|
+
hrefs.set(href, name);
|
|
22
|
+
return name;
|
|
23
|
+
}
|
|
7
24
|
// ------------------------------------------------------------------
|
|
8
25
|
// CreateCallExpression
|
|
9
26
|
// ------------------------------------------------------------------
|
|
10
|
-
function CreateCallExpression(context, _schema,
|
|
27
|
+
function CreateCallExpression(context, _schema, name, value) {
|
|
11
28
|
return context.UseUnevaluated()
|
|
12
|
-
? E.Call(`check_${
|
|
13
|
-
: E.Call(`check_${
|
|
29
|
+
? E.Call(`check_${name}`, ['context', value])
|
|
30
|
+
: E.Call(`check_${name}`, [value]);
|
|
14
31
|
}
|
|
15
32
|
// ------------------------------------------------------------------
|
|
16
33
|
// CreateFunctionExpression
|
|
17
34
|
// ------------------------------------------------------------------
|
|
18
|
-
function CreateFunctionExpression(stack, context, schema,
|
|
35
|
+
function CreateFunctionExpression(stack, context, schema, name) {
|
|
19
36
|
const expression = BuildSchema(stack, context, schema, 'value');
|
|
20
37
|
return context.UseUnevaluated()
|
|
21
|
-
? E.ConstDeclaration(`check_${
|
|
22
|
-
: E.ConstDeclaration(`check_${
|
|
38
|
+
? E.ConstDeclaration(`check_${name}`, E.ArrowFunction(['context', 'value'], expression))
|
|
39
|
+
: E.ConstDeclaration(`check_${name}`, E.ArrowFunction(['value'], expression));
|
|
23
40
|
}
|
|
24
41
|
// ------------------------------------------------------------------
|
|
25
42
|
// ResetFunctions
|
|
26
43
|
// ------------------------------------------------------------------
|
|
27
44
|
export function ResetFunctions() {
|
|
28
|
-
|
|
45
|
+
index[0] = 0;
|
|
46
|
+
names.clear();
|
|
47
|
+
funcs.clear();
|
|
29
48
|
}
|
|
30
49
|
// ------------------------------------------------------------------
|
|
31
50
|
// GetFunctions
|
|
32
51
|
// ------------------------------------------------------------------
|
|
33
52
|
export function GetFunctions() {
|
|
34
|
-
return [...
|
|
53
|
+
return [...funcs.values()];
|
|
35
54
|
}
|
|
36
55
|
// ------------------------------------------------------------------
|
|
37
56
|
// CreateFunction
|
|
38
57
|
// ------------------------------------------------------------------
|
|
39
58
|
export function CreateFunction(stack, context, schema, value) {
|
|
40
|
-
const
|
|
41
|
-
const call = CreateCallExpression(context, schema,
|
|
42
|
-
if (
|
|
59
|
+
const name = CreateName(schema, stack.BaseURL().href);
|
|
60
|
+
const call = CreateCallExpression(context, schema, name, value);
|
|
61
|
+
if (funcs.has(name))
|
|
43
62
|
return call;
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
funcs.set(name, '');
|
|
64
|
+
funcs.set(name, CreateFunctionExpression(stack, context, schema, name));
|
|
46
65
|
return call;
|
|
47
66
|
}
|
|
@@ -9,3 +9,8 @@ export declare function ReadonlyObjectDeferred<Type extends TSchema>(type: Type,
|
|
|
9
9
|
export type TReadonlyObject<Type extends TSchema> = (TReadonlyObjectAction<Type>);
|
|
10
10
|
/** This type is an alias for TypeScript's `Readonly<T>` utility type. It will make all properties of a TObject readonly or marks an TArray or TTuple as immutable `readonly T[]`. */
|
|
11
11
|
export declare function ReadonlyObject<Type extends TSchema>(type: Type, options?: TSchemaOptions): TReadonlyObject<Type>;
|
|
12
|
+
/**
|
|
13
|
+
* This type has been renamed to ReadonlyObject.
|
|
14
|
+
* @deprecated
|
|
15
|
+
*/
|
|
16
|
+
export declare const ReadonlyType: typeof ReadonlyObject;
|
|
@@ -9,3 +9,11 @@ export function ReadonlyObjectDeferred(type, options = {}) {
|
|
|
9
9
|
export function ReadonlyObject(type, options = {}) {
|
|
10
10
|
return ReadonlyObjectAction(type, options);
|
|
11
11
|
}
|
|
12
|
+
// ------------------------------------------------------------------
|
|
13
|
+
// Alias
|
|
14
|
+
// ------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* This type has been renamed to ReadonlyObject.
|
|
17
|
+
* @deprecated
|
|
18
|
+
*/
|
|
19
|
+
export const ReadonlyType = ReadonlyObject;
|
|
@@ -19,7 +19,7 @@ export { Omit, type TOmit, type TOmitDeferred } from './type/action/omit.mjs';
|
|
|
19
19
|
export { Parameters, type TParameters, type TParametersDeferred } from './type/action/parameters.mjs';
|
|
20
20
|
export { Partial, type TPartial, type TPartialDeferred } from './type/action/partial.mjs';
|
|
21
21
|
export { Pick, type TPick, type TPickDeferred } from './type/action/pick.mjs';
|
|
22
|
-
export { ReadonlyObject, type TReadonlyObject, type TReadonlyObjectDeferred } from './type/action/readonly_object.mjs';
|
|
22
|
+
export { ReadonlyObject, ReadonlyType, type TReadonlyObject, type TReadonlyObjectDeferred } from './type/action/readonly_object.mjs';
|
|
23
23
|
export { Required, type TRequired, type TRequiredDeferred } from './type/action/required.mjs';
|
|
24
24
|
export { ReturnType, type TReturnType, type TReturnTypeDeferred } from './type/action/return_type.mjs';
|
|
25
25
|
export { type TUncapitalize, type TUncapitalizeDeferred, Uncapitalize } from './type/action/uncapitalize.mjs';
|