@openclaw/voice-call 2026.8.1-beta.3 → 2026.8.1
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/doctor-contract-api.js +9 -5
- package/dist/{guarded-json-api-j4OEG0fR.js → guarded-json-api-CQXCe4aR.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{plivo-BbVnahnQ.js → plivo-Ct5BT5LQ.js} +2 -2
- package/dist/{realtime-handler-BaMEKbmJ.js → realtime-handler-CSK1A1Gg.js} +32 -20
- package/dist/{response-generator-tjyX4tDq.js → response-generator-xbT37lYx.js} +1 -1
- package/dist/{runtime-entry-DK8kPCGC.js → runtime-entry-q02qkX8F.js} +18 -39
- package/dist/runtime-entry.js +1 -1
- package/dist/{store-path-51_oC_xn.js → store-path-Cf6k3F2U.js} +3 -10
- package/dist/{telnyx-DO8dFQcb.js → telnyx-C9Yizvox.js} +1 -1
- package/dist/{twilio-JDt_1nlz.js → twilio-BLjVv246.js} +2 -2
- package/node_modules/typebox/build/format/date.d.mts +1 -1
- package/node_modules/typebox/build/format/date.mjs +1 -1
- package/node_modules/typebox/build/format/date_time.d.mts +2 -2
- package/node_modules/typebox/build/format/date_time.mjs +3 -3
- package/node_modules/typebox/build/format/email.d.mts +1 -1
- package/node_modules/typebox/build/format/email.mjs +2 -2
- package/node_modules/typebox/build/format/hostname.mjs +2 -10
- package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
- package/node_modules/typebox/build/format/idn_email.mjs +3 -3
- package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
- package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
- package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
- package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +23 -0
- package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
- package/node_modules/typebox/build/format/idna/index.mjs +2 -0
- package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
- package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
- package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
- package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
- package/node_modules/typebox/build/format/ipv4.mjs +2 -32
- package/node_modules/typebox/build/format/ipv6.mjs +2 -61
- package/node_modules/typebox/build/format/iri.d.mts +2 -2
- package/node_modules/typebox/build/format/iri.mjs +26 -8
- package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
- package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
- package/node_modules/typebox/build/format/regex.d.mts +1 -2
- package/node_modules/typebox/build/format/regex.mjs +2 -6
- package/node_modules/typebox/build/format/time.d.mts +2 -2
- package/node_modules/typebox/build/format/time.mjs +19 -14
- package/node_modules/typebox/build/format/uri.d.mts +1 -1
- package/node_modules/typebox/build/format/uri.mjs +3 -130
- package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
- package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
- package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
- package/node_modules/typebox/build/format/uri_template.mjs +4 -5
- package/node_modules/typebox/build/format/url.d.mts +3 -3
- package/node_modules/typebox/build/format/url.mjs +4 -5
- package/node_modules/typebox/build/guard/emit.d.mts +3 -1
- package/node_modules/typebox/build/guard/emit.mjs +12 -5
- package/node_modules/typebox/build/guard/guard.d.mts +8 -2
- package/node_modules/typebox/build/guard/guard.mjs +25 -4
- package/node_modules/typebox/build/guard/string.d.mts +2 -2
- package/node_modules/typebox/build/guard/string.mjs +7 -7
- package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
- package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
- package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
- package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
- package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
- package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
- package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
- package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
- package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
- package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
- package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
- package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
- package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
- package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
- package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
- package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
- package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
- package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
- package/node_modules/typebox/build/schema/types/index.mjs +0 -2
- package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
- package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
- package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
- package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
- package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
- package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
- package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
- package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
- package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
- package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
- package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
- package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
- package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
- package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
- package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
- package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
- package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
- package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
- package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
- package/node_modules/typebox/build/type/script/parser.mjs +27 -23
- package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
- package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
- package/node_modules/typebox/package.json +27 -27
- package/node_modules/typebox/readme.md +79 -82
- package/node_modules/ws/lib/permessage-deflate.js +3 -1
- package/node_modules/ws/package.json +1 -1
- package/package.json +6 -6
- package/node_modules/typebox/build/format/_idna.d.mts +0 -2
- package/node_modules/typebox/build/format/_idna.mjs +0 -246
- package/node_modules/typebox/build/format/_puny.d.mts +0 -1
- package/node_modules/typebox/build/format/_puny.mjs +0 -77
- package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
- package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
- package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
- package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as CALL_RECORD_EVENT_META_MAX_ENTRIES, c as buildVoiceCallLegacyJsonlEventKey, f as parseVoiceCallRecordLine, h as resolveVoiceCallLegacyCallLogPath, i as CALL_RECORD_EVENT_CHUNKS_NAMESPACE, m as prepareVoiceCallRecordForStorage, n as CALL_RECORD_CHUNK_MAX_ENTRIES, o as MAX_CALL_RECORD_EVENTS, r as CALL_RECORD_EVENTS_NAMESPACE, s as RAW_CALL_RECORD_CHUNK_BYTES, t as resolveDefaultVoiceCallStoreDir } from "./store-path-
|
|
1
|
+
import { a as CALL_RECORD_EVENT_META_MAX_ENTRIES, c as buildVoiceCallLegacyJsonlEventKey, f as parseVoiceCallRecordLine, h as resolveVoiceCallLegacyCallLogPath, i as CALL_RECORD_EVENT_CHUNKS_NAMESPACE, m as prepareVoiceCallRecordForStorage, n as CALL_RECORD_CHUNK_MAX_ENTRIES, o as MAX_CALL_RECORD_EVENTS, r as CALL_RECORD_EVENTS_NAMESPACE, s as RAW_CALL_RECORD_CHUNK_BYTES, t as resolveDefaultVoiceCallStoreDir } from "./store-path-Cf6k3F2U.js";
|
|
2
2
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
3
3
|
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import path from "node:path";
|
|
@@ -14,7 +14,7 @@ function resolveHome(env) {
|
|
|
14
14
|
function resolveUserPath(input, env) {
|
|
15
15
|
const trimmed = input.trim();
|
|
16
16
|
if (!trimmed) return trimmed;
|
|
17
|
-
if (trimmed.startsWith("~")) return path.resolve(trimmed.replace(/^~(?=$|[\\/])/, resolveHome(env)));
|
|
17
|
+
if (trimmed.startsWith("~")) return path.resolve(trimmed.replace(/^~(?=$|[\\/])/, () => resolveHome(env)));
|
|
18
18
|
return path.resolve(trimmed);
|
|
19
19
|
}
|
|
20
20
|
/** Read the configured voice-call store path from either package id. */
|
|
@@ -61,7 +61,13 @@ function describeVoiceCallSchemaMigration(migration) {
|
|
|
61
61
|
case "agent-databases-composite-primary-key": return "agent database registry primary key -> agent_id,path";
|
|
62
62
|
case "agent-databases-relative-paths-v9": return "agent database registry paths -> state-relative paths";
|
|
63
63
|
case "audit-events-v2": return "audit event ledger -> versioned message lifecycle schema";
|
|
64
|
-
case "commitments-retirement-v7": return "retired commitments storage ->
|
|
64
|
+
case "commitments-retirement-v7": return "retired commitments storage -> discarded rows, table, and indexes";
|
|
65
|
+
case "state-table-retirement-v10": return "retired shared-state tables -> removed tables and indexes";
|
|
66
|
+
case "state-table-retirement-v11": return "retired skill curator tables -> removed tables and indexes";
|
|
67
|
+
case "singleton-state-foldin-v12": return "singleton state tables -> shared configuration state";
|
|
68
|
+
case "state-consolidation-v13": return "cron jobs and subagent runs -> canonical JSON storage";
|
|
69
|
+
case "creator-namespace-v14": return "cron creators -> explicit principal namespaces";
|
|
70
|
+
case "conversation-binding-targets-v15": return "conversation bindings -> exact target keys without agent/session projections";
|
|
65
71
|
case "worker-placement-execution-mode-v8": return "cloud worker placements -> execution-mode claims";
|
|
66
72
|
case "operator-approvals-system-agent": return "operator approvals -> OpenClaw system changes";
|
|
67
73
|
case "session-watch-cursor-provenance-v4": return "session watch cursors -> provenance column";
|
|
@@ -69,7 +75,6 @@ function describeVoiceCallSchemaMigration(migration) {
|
|
|
69
75
|
}
|
|
70
76
|
return migration.kind;
|
|
71
77
|
}
|
|
72
|
-
/** Return true when a path exists and is a file. */
|
|
73
78
|
/** Build the plugin state key for one migrated event chunk. */
|
|
74
79
|
function buildChunkKey(eventKey, index) {
|
|
75
80
|
return `${eventKey}:chunk:${String(index).padStart(4, "0")}`;
|
|
@@ -139,7 +144,6 @@ async function readLegacyCallRecords(filePath) {
|
|
|
139
144
|
warnings
|
|
140
145
|
};
|
|
141
146
|
}
|
|
142
|
-
/** Archive the legacy JSONL source after a complete migration. */
|
|
143
147
|
/** Select newest missing records that fit remaining plugin state capacity. */
|
|
144
148
|
async function selectEntriesForImport(params) {
|
|
145
149
|
const existingEventKeys = new Set((await params.eventStore.entries()).map((entry) => entry.key));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchWithSsrFGuard } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
|
-
import { n as normalizeProxyIp, s as getHeader } from "./runtime-entry-
|
|
3
|
+
import { n as normalizeProxyIp, s as getHeader } from "./runtime-entry-q02qkX8F.js";
|
|
4
4
|
import { n as reserveWebhookReplay, t as createWebhookReplayCache } from "./webhook-replay-FQrEGVSM.js";
|
|
5
5
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
6
6
|
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { definePluginEntry, sleep } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
3
|
import { VOICE_CALL_CLI_DESCRIPTOR } from "./cli-output-mode.js";
|
|
4
|
-
import { C as resolveVoiceCallStreamExposurePaths, b as resolveVoiceCallConfig, c as resolveWebhookExposureStatus, d as setupTailscaleExposureRoutes, g as resolveUserPath, l as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime, u as getTailscaleSelfInfo, w as validateProviderConfig, y as VoiceCallConfigSchema } from "./runtime-entry-
|
|
5
|
-
import { b as setVoiceCallStateRuntime, d as loadActiveCallsFromStore, l as findCallMatchesInStore, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-
|
|
4
|
+
import { C as resolveVoiceCallStreamExposurePaths, b as resolveVoiceCallConfig, c as resolveWebhookExposureStatus, d as setupTailscaleExposureRoutes, g as resolveUserPath, l as cleanupTailscaleExposureRoute, t as createVoiceCallRuntime, u as getTailscaleSelfInfo, w as validateProviderConfig, y as VoiceCallConfigSchema } from "./runtime-entry-q02qkX8F.js";
|
|
5
|
+
import { b as setVoiceCallStateRuntime, d as loadActiveCallsFromStore, l as findCallMatchesInStore, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore } from "./store-path-Cf6k3F2U.js";
|
|
6
6
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
7
|
import { ErrorCodes, callGatewayFromCli, errorShape, isGatewayClientRequestError, isGatewayTransportError, redactSensitiveUrlLikeString } from "openclaw/plugin-sdk/gateway-runtime";
|
|
8
8
|
import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as escapeXml, s as getHeader } from "./runtime-entry-
|
|
2
|
-
import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-
|
|
1
|
+
import { _ as escapeXml, s as getHeader } from "./runtime-entry-q02qkX8F.js";
|
|
2
|
+
import { a as reconstructWebhookUrl, o as verifyPlivoWebhook, t as guardedJsonApiRequest } from "./guarded-json-api-CQXCe4aR.js";
|
|
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
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { T as REALTIME_VOICE_END_CALL_TOOL_NAME, o as canonicalizeVoiceCallMediaBase64, x as resolveVoiceCallPublicPathPrefix } from "./runtime-entry-
|
|
1
|
+
import { T as REALTIME_VOICE_END_CALL_TOOL_NAME, o as canonicalizeVoiceCallMediaBase64, x as resolveVoiceCallPublicPathPrefix } from "./runtime-entry-q02qkX8F.js";
|
|
2
2
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
3
3
|
import { asNullableRecord, asOptionalObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
|
5
|
-
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, buildRealtimeVoiceAgentConsultWorkingResponse, calculateMulawRms, createRealtimeVoiceSessionHarness, createSpeechThresholdGate, readRealtimeVoiceConsultQuestion, readSpeakableRealtimeVoiceToolResult } from "openclaw/plugin-sdk/realtime-voice";
|
|
5
|
+
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ, buildRealtimeVoiceAgentConsultWorkingResponse, calculateMulawRms, createRealtimeVoiceSessionHarness, createSpeechThresholdGate, readRealtimeVoiceConsultQuestion, readSpeakableRealtimeVoiceToolResult } from "openclaw/plugin-sdk/realtime-voice";
|
|
6
6
|
import { normalizeWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
|
|
7
7
|
import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
@@ -821,6 +821,8 @@ var RealtimeCallHandler = class {
|
|
|
821
821
|
});
|
|
822
822
|
const interruptResponseOnInputAudio = typeof providerConfig.interruptResponseOnInputAudio === "boolean" ? providerConfig.interruptResponseOnInputAudio : void 0;
|
|
823
823
|
const nativeConsultOwner = {};
|
|
824
|
+
let provisionalCloseReason;
|
|
825
|
+
let sessionClosed = false;
|
|
824
826
|
const userTranscriptAdoption = this.beginUserTranscriptOwnerAdoption(callId);
|
|
825
827
|
const userTranscriptOwner = userTranscriptAdoption.owner;
|
|
826
828
|
const bridgeParams = {
|
|
@@ -828,6 +830,7 @@ var RealtimeCallHandler = class {
|
|
|
828
830
|
cfg: this.coreConfig,
|
|
829
831
|
agentId,
|
|
830
832
|
providerConfig,
|
|
833
|
+
audioFormat: REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ,
|
|
831
834
|
interruptResponseOnInputAudio,
|
|
832
835
|
instructions,
|
|
833
836
|
tools: this.config.tools,
|
|
@@ -856,7 +859,7 @@ var RealtimeCallHandler = class {
|
|
|
856
859
|
},
|
|
857
860
|
onTranscript: (role, text, isFinal) => {
|
|
858
861
|
const owner = nativeConsultOwner.current;
|
|
859
|
-
if (!this.getUserTranscriptState(callId, userTranscriptOwner) || owner && !this.isActiveBridgeOwner(callId, owner)) return;
|
|
862
|
+
if (provisionalCloseReason || !this.getUserTranscriptState(callId, userTranscriptOwner) || owner && !this.isActiveBridgeOwner(callId, owner)) return;
|
|
860
863
|
const turnId = harness.ensureTurn();
|
|
861
864
|
const eventType = role === "assistant" ? isFinal ? "output.text.done" : "output.text.delta" : isFinal ? "transcript.done" : "transcript.delta";
|
|
862
865
|
const payload = role === "assistant" ? { text } : {
|
|
@@ -1013,38 +1016,48 @@ var RealtimeCallHandler = class {
|
|
|
1013
1016
|
});
|
|
1014
1017
|
},
|
|
1015
1018
|
onClose: (reason) => {
|
|
1016
|
-
const owner = nativeConsultOwner.current;
|
|
1017
|
-
const ownsCallState = owner ? this.isActiveBridgeOwner(callId, owner) : false;
|
|
1018
|
-
if (owner) {
|
|
1019
|
-
this.clearActiveBridgeMappings(callId, callSid, owner);
|
|
1020
|
-
this.cancelConsultSession(callId, owner);
|
|
1021
|
-
}
|
|
1022
|
-
if (ownsCallState) this.clearUserTranscriptState(callId, userTranscriptOwner);
|
|
1023
1019
|
harness.finishOutputAudio(reason);
|
|
1024
1020
|
harness.emit({
|
|
1025
1021
|
type: "session.closed",
|
|
1026
1022
|
payload: { reason },
|
|
1027
1023
|
final: true
|
|
1028
1024
|
});
|
|
1029
|
-
|
|
1025
|
+
const owner = nativeConsultOwner.current;
|
|
1026
|
+
if (!owner) {
|
|
1027
|
+
provisionalCloseReason ??= reason;
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const ownsCallState = this.isActiveBridgeOwner(callId, owner);
|
|
1031
|
+
this.clearActiveBridgeMappings(callId, callSid, owner);
|
|
1032
|
+
this.cancelConsultSession(callId, owner);
|
|
1033
|
+
if (ownsCallState) this.clearUserTranscriptState(callId, userTranscriptOwner);
|
|
1034
|
+
if (reason === "completed" && (!ownsCallState || sessionClosed)) return;
|
|
1030
1035
|
this.streamDisconnectLifecycle.retire(callSid, streamSid);
|
|
1031
|
-
if (ws.readyState === WebSocket$1.OPEN) ws.close(1011, "Bridge disconnected");
|
|
1032
|
-
if (
|
|
1033
|
-
emitCallEnd("error");
|
|
1036
|
+
if (ws.readyState === WebSocket$1.OPEN) ws.close(reason === "error" ? 1011 : 1e3, "Bridge disconnected");
|
|
1037
|
+
if (ownsCallState) emitCallEnd(reason);
|
|
1034
1038
|
}
|
|
1035
1039
|
};
|
|
1036
|
-
let
|
|
1040
|
+
let candidate;
|
|
1037
1041
|
try {
|
|
1038
|
-
|
|
1042
|
+
candidate = harness.createBridge(bridgeParams);
|
|
1039
1043
|
} catch (error) {
|
|
1044
|
+
console.error("[voice-call] Failed to create realtime bridge:", error);
|
|
1045
|
+
}
|
|
1046
|
+
if (!candidate || provisionalCloseReason) {
|
|
1040
1047
|
this.rollbackUserTranscriptOwnerAdoption(callId, userTranscriptAdoption);
|
|
1048
|
+
try {
|
|
1049
|
+
candidate?.close();
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
console.warn(`[voice-call] Failed to close realtime bridge ${callSid}: ${formatErrorMessage(error)}`);
|
|
1052
|
+
}
|
|
1041
1053
|
harness.close();
|
|
1042
1054
|
audioPacer.close();
|
|
1043
|
-
|
|
1044
|
-
if (
|
|
1045
|
-
|
|
1055
|
+
const reason = provisionalCloseReason ?? "error";
|
|
1056
|
+
if (!hadPredecessorOnAdmission || !this.activeBridgesByCallId.has(callId)) emitCallEnd(reason);
|
|
1057
|
+
if (ws.readyState === WebSocket$1.OPEN) ws.close(reason === "error" ? 1011 : 1e3, "Failed to create realtime bridge");
|
|
1046
1058
|
return null;
|
|
1047
1059
|
}
|
|
1060
|
+
const session = candidate;
|
|
1048
1061
|
this.commitUserTranscriptOwnerAdoption(callId, userTranscriptAdoption);
|
|
1049
1062
|
nativeConsultOwner.current = session;
|
|
1050
1063
|
providerHandlesInputAudioBargeIn = session.bridge.handlesInputAudioBargeIn ?? providerHandlesInputAudioBargeIn;
|
|
@@ -1069,7 +1082,6 @@ var RealtimeCallHandler = class {
|
|
|
1069
1082
|
sendAudioToSession(audio);
|
|
1070
1083
|
};
|
|
1071
1084
|
const closeSession = session.close.bind(session);
|
|
1072
|
-
let sessionClosed = false;
|
|
1073
1085
|
session.close = () => {
|
|
1074
1086
|
if (sessionClosed) return;
|
|
1075
1087
|
sessionClosed = true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as resolveVoiceCallSessionKey, h as resolveCallAgentId, m as resolveVoiceResponseModel } from "./runtime-entry-
|
|
1
|
+
import { S as resolveVoiceCallSessionKey, h as resolveCallAgentId, m as resolveVoiceResponseModel } from "./runtime-entry-q02qkX8F.js";
|
|
2
2
|
import { filterStringEntries, isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
3
|
import { resolveAgentConfig } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
4
4
|
import crypto from "node:crypto";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TtsConfigSchema, isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "./runtime-api.js";
|
|
2
2
|
import "./api.js";
|
|
3
3
|
import { t as TWILIO_REGIONS } from "./twilio-region-4fkgz3UG.js";
|
|
4
|
-
import { _ as releaseRejectedProviderCall, b as setVoiceCallStateRuntime, d as loadActiveCallsFromStore, g as appendCallReplayKey, l as findCallMatchesInStore, p as persistCallRecord, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore, v as rememberManagerReplayKey, x as TerminalStates, y as reserveRejectedProviderCall } from "./store-path-
|
|
4
|
+
import { _ as releaseRejectedProviderCall, b as setVoiceCallStateRuntime, d as loadActiveCallsFromStore, g as appendCallReplayKey, l as findCallMatchesInStore, p as persistCallRecord, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore, v as rememberManagerReplayKey, x as TerminalStates, y as reserveRejectedProviderCall } from "./store-path-Cf6k3F2U.js";
|
|
5
5
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
6
6
|
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
|
7
7
|
import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
@@ -1646,7 +1646,7 @@ function resolveUserPath(input) {
|
|
|
1646
1646
|
const trimmed = input.trim();
|
|
1647
1647
|
if (!trimmed) return trimmed;
|
|
1648
1648
|
if (trimmed.startsWith("~")) {
|
|
1649
|
-
const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir());
|
|
1649
|
+
const expanded = trimmed.replace(/^~(?=$|[\\/])/, () => os.homedir());
|
|
1650
1650
|
return path.resolve(expanded);
|
|
1651
1651
|
}
|
|
1652
1652
|
return path.resolve(trimmed);
|
|
@@ -2240,7 +2240,6 @@ async function cleanupTailscaleExposure(config) {
|
|
|
2240
2240
|
//#region extensions/voice-call/src/tunnel.ts
|
|
2241
2241
|
const NGROK_LOG_BUFFER_MAX_CHARS = 16384;
|
|
2242
2242
|
const NGROK_ERROR_MARKER = "ERR_NGROK";
|
|
2243
|
-
const TUNNEL_COMMAND_OUTPUT_MAX_BYTES = 16384;
|
|
2244
2243
|
const NGROK_STOP_GRACE_MS = 2e3;
|
|
2245
2244
|
const NGROK_FORCE_KILL_WAIT_MS = 1e3;
|
|
2246
2245
|
async function terminateNgrokProcess(proc, isClosed) {
|
|
@@ -2280,11 +2279,6 @@ function listenForChildStreamErrors(proc, onError) {
|
|
|
2280
2279
|
* // Later: await tunnel.stop();
|
|
2281
2280
|
*/
|
|
2282
2281
|
async function startNgrokTunnel(config) {
|
|
2283
|
-
if (config.authToken) await runNgrokCommand([
|
|
2284
|
-
"config",
|
|
2285
|
-
"add-authtoken",
|
|
2286
|
-
config.authToken
|
|
2287
|
-
]);
|
|
2288
2282
|
const args = [
|
|
2289
2283
|
"http",
|
|
2290
2284
|
String(config.port),
|
|
@@ -2295,11 +2289,17 @@ async function startNgrokTunnel(config) {
|
|
|
2295
2289
|
];
|
|
2296
2290
|
if (config.domain) args.push("--domain", config.domain);
|
|
2297
2291
|
return new Promise((resolve, reject) => {
|
|
2298
|
-
const proc = spawn("ngrok", args, {
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2292
|
+
const proc = spawn("ngrok", args, {
|
|
2293
|
+
stdio: [
|
|
2294
|
+
"ignore",
|
|
2295
|
+
"pipe",
|
|
2296
|
+
"pipe"
|
|
2297
|
+
],
|
|
2298
|
+
...config.authToken ? { env: {
|
|
2299
|
+
...process.env,
|
|
2300
|
+
NGROK_AUTHTOKEN: config.authToken
|
|
2301
|
+
} } : {}
|
|
2302
|
+
});
|
|
2303
2303
|
let startupSettled = false;
|
|
2304
2304
|
let childClosed = false;
|
|
2305
2305
|
let publicUrl = null;
|
|
@@ -2372,27 +2372,6 @@ async function startNgrokTunnel(config) {
|
|
|
2372
2372
|
});
|
|
2373
2373
|
}
|
|
2374
2374
|
/**
|
|
2375
|
-
* Run an ngrok command and wait for completion.
|
|
2376
|
-
*/
|
|
2377
|
-
async function runNgrokCommand(args) {
|
|
2378
|
-
const result = await runCommandWithTimeout(["ngrok", ...args], {
|
|
2379
|
-
killProcessTree: true,
|
|
2380
|
-
maxOutputBytes: TUNNEL_COMMAND_OUTPUT_MAX_BYTES,
|
|
2381
|
-
outputCapture: "tail",
|
|
2382
|
-
timeoutMs: 3e4
|
|
2383
|
-
});
|
|
2384
|
-
if (result.termination === "timeout") throw new Error("ngrok command timed out");
|
|
2385
|
-
if (result.code === 0) return result.stdout;
|
|
2386
|
-
const output = result.stderr ? {
|
|
2387
|
-
text: result.stderr,
|
|
2388
|
-
truncated: Boolean(result.stderrTruncatedBytes)
|
|
2389
|
-
} : {
|
|
2390
|
-
text: result.stdout,
|
|
2391
|
-
truncated: Boolean(result.stdoutTruncatedBytes)
|
|
2392
|
-
};
|
|
2393
|
-
throw new Error(`ngrok command failed: ${formatBoundedChildOutput(output)}`);
|
|
2394
|
-
}
|
|
2395
|
-
/**
|
|
2396
2375
|
* Start a Tailscale serve/funnel tunnel.
|
|
2397
2376
|
*/
|
|
2398
2377
|
async function startTailscaleTunnel(config) {
|
|
@@ -3362,7 +3341,7 @@ const MAX_WEBHOOK_BODY_BYTES = WEBHOOK_BODY_READ_DEFAULTS.preAuth.maxBytes;
|
|
|
3362
3341
|
const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
|
|
3363
3342
|
const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
|
|
3364
3343
|
const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
|
|
3365
|
-
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-
|
|
3344
|
+
const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-xbT37lYx.js"));
|
|
3366
3345
|
function appendRecentTalkEventMetadata(call, event) {
|
|
3367
3346
|
const metadata = call.metadata ?? {};
|
|
3368
3347
|
const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
@@ -4061,12 +4040,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
|
|
|
4061
4040
|
"Do not print secret values or dump environment variables; only check whether required configuration is present.",
|
|
4062
4041
|
"Be accurate, brief, and speakable."
|
|
4063
4042
|
].join(" ");
|
|
4064
|
-
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-
|
|
4065
|
-
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-
|
|
4066
|
-
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-
|
|
4043
|
+
const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-C9Yizvox.js"));
|
|
4044
|
+
const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-BLjVv246.js"));
|
|
4045
|
+
const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-Ct5BT5LQ.js"));
|
|
4067
4046
|
const loadMockProvider = createLazyRuntimeModule(() => import("./mock-Dv7KxYGt.js"));
|
|
4068
4047
|
const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
|
|
4069
|
-
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-
|
|
4048
|
+
const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-CSK1A1Gg.js"));
|
|
4070
4049
|
function resolveVoiceCallConsultSessionKey(call) {
|
|
4071
4050
|
return resolveVoiceCallSessionKey({
|
|
4072
4051
|
config: call.config,
|
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-q02qkX8F.js";
|
|
2
2
|
export { createVoiceCallRuntime };
|
|
@@ -133,14 +133,7 @@ const { setRuntime: setVoiceCallStateRuntime, tryGetRuntime: getOptionalVoiceCal
|
|
|
133
133
|
//#region extensions/voice-call/src/manager/replay-keys.ts
|
|
134
134
|
/** Match the existing voice-call webhook replay-cache cardinality. */
|
|
135
135
|
const MAX_MANAGER_REPLAY_KEYS = 1e4;
|
|
136
|
-
function
|
|
137
|
-
while (keys.size > maxEntries) {
|
|
138
|
-
const oldest = keys.values().next().value;
|
|
139
|
-
if (oldest === void 0) break;
|
|
140
|
-
keys.delete(oldest);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
function pruneOldestMapEntries(keys, maxEntries) {
|
|
136
|
+
function pruneOldestEntries(keys, maxEntries) {
|
|
144
137
|
while (keys.size > maxEntries) {
|
|
145
138
|
const oldest = keys.keys().next().value;
|
|
146
139
|
if (oldest === void 0) break;
|
|
@@ -150,7 +143,7 @@ function pruneOldestMapEntries(keys, maxEntries) {
|
|
|
150
143
|
/** Remember one manager replay key without refreshing duplicate insertion order. */
|
|
151
144
|
function rememberManagerReplayKey(keys, key, maxEntries = MAX_MANAGER_REPLAY_KEYS) {
|
|
152
145
|
keys.add(key);
|
|
153
|
-
|
|
146
|
+
pruneOldestEntries(keys, maxEntries);
|
|
154
147
|
}
|
|
155
148
|
/** Append one per-call replay key and retain only the newest bounded suffix. */
|
|
156
149
|
function appendCallReplayKey(keys, key, maxEntries = 500) {
|
|
@@ -170,7 +163,7 @@ function reserveRejectedProviderCall(calls, providerCallId, maxEntries = MAX_MAN
|
|
|
170
163
|
if (calls.has(providerCallId)) return;
|
|
171
164
|
const reservation = Symbol(providerCallId);
|
|
172
165
|
calls.set(providerCallId, reservation);
|
|
173
|
-
|
|
166
|
+
pruneOldestEntries(calls, maxEntries);
|
|
174
167
|
return reservation;
|
|
175
168
|
}
|
|
176
169
|
/** Release a rejected-call reservation only when the failing attempt still owns it. */
|
|
@@ -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-CQXCe4aR.js";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
//#region extensions/voice-call/src/providers/telnyx.ts
|
|
4
4
|
function normalizeTelnyxDirection(direction) {
|
|
@@ -1,8 +1,8 @@
|
|
|
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 escapeXml, a as normalizeProviderStatus, i as mapProviderStatusToEndReason, p as chunkAudio, r as isProviderStatusTerminal, s as getHeader, v as mapVoiceToPolly } from "./runtime-entry-
|
|
5
|
-
import { c as verifyTwilioWebhook, i as readVoiceCallProviderJsonResponse, 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-q02qkX8F.js";
|
|
5
|
+
import { c as verifyTwilioWebhook, i as readVoiceCallProviderJsonResponse, n as cancelProviderResponseBody, r as readProviderErrorResponseSnippet, t as guardedJsonApiRequest } from "./guarded-json-api-CQXCe4aR.js";
|
|
6
6
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
7
|
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
|
8
8
|
import crypto from "node:crypto";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Returns true if the value is a ISO8601 DateTime string
|
|
3
|
-
* @
|
|
3
|
+
* @specification https://datatracker.ietf.org/doc/html/rfc3339
|
|
4
4
|
* @example `2020-12-12T20:20:40+00:00`
|
|
5
5
|
*/
|
|
6
|
-
export declare function IsDateTime(value: string
|
|
6
|
+
export declare function IsDateTime(value: string): boolean;
|
|
@@ -2,10 +2,10 @@ import { IsDate } from './date.mjs';
|
|
|
2
2
|
import { IsTime } from './time.mjs';
|
|
3
3
|
/**
|
|
4
4
|
* Returns true if the value is a ISO8601 DateTime string
|
|
5
|
-
* @
|
|
5
|
+
* @specification https://datatracker.ietf.org/doc/html/rfc3339
|
|
6
6
|
* @example `2020-12-12T20:20:40+00:00`
|
|
7
7
|
*/
|
|
8
|
-
export function IsDateTime(value
|
|
8
|
+
export function IsDateTime(value) {
|
|
9
9
|
const dateTime = value.split(/T/i);
|
|
10
|
-
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1]
|
|
10
|
+
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1]);
|
|
11
11
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const Email = /^(
|
|
1
|
+
const Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\.)*")@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*|\[(?:IPv6:[a-f0-9:]+|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})\])$/i;
|
|
2
2
|
/**
|
|
3
3
|
* Returns true if the value is an Email
|
|
4
|
-
* @specification
|
|
4
|
+
* @specification https://datatracker.ietf.org/doc/html/rfc5322
|
|
5
5
|
*/
|
|
6
6
|
export function IsEmail(value) {
|
|
7
7
|
return Email.test(value);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as Idna from './
|
|
1
|
+
import * as Idna from './idna/index.mjs';
|
|
2
2
|
/**
|
|
3
3
|
* Returns true if the value is a valid hostname.
|
|
4
4
|
* @specification https://tools.ietf.org/html/rfc1123
|
|
@@ -6,13 +6,5 @@ import * as Idna from './_idna.mjs';
|
|
|
6
6
|
* @specification https://tools.ietf.org/html/rfc5892
|
|
7
7
|
*/
|
|
8
8
|
export function IsHostname(value) {
|
|
9
|
-
|
|
10
|
-
return false;
|
|
11
|
-
if (value.charCodeAt(value.length - 1) === 46)
|
|
12
|
-
return false;
|
|
13
|
-
for (const label of value.split('.')) {
|
|
14
|
-
if (!Idna.IsLabel(label))
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
9
|
+
return Idna.IsHostname(value);
|
|
18
10
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const IdnEmail = /^(
|
|
1
|
+
const IdnEmail = /^(?:[A-Za-z0-9!#$%&'*+\/=?^_`{|}~\u{0080}-\u{10FFFF}-]+(?:\.[A-Za-z0-9!#$%&'*+\/=?^_`{|}~\u{0080}-\u{10FFFF}-]+)*|"(?:[^"\\]|\\.)*")@[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,62})(?<!-)(?:\.[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,62})(?<!-))*$/iu;
|
|
2
2
|
/**
|
|
3
3
|
* Returns true if the value is an IdnEmail
|
|
4
|
-
* @specification
|
|
4
|
+
* @specification https://datatracker.ietf.org/doc/html/rfc4952
|
|
5
5
|
*/
|
|
6
6
|
export function IsIdnEmail(value) {
|
|
7
|
-
return IdnEmail.test(value);
|
|
7
|
+
return IdnEmail.test(value.normalize('NFC'));
|
|
8
8
|
}
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
* @specification https://tools.ietf.org/html/rfc3490
|
|
4
4
|
* @specification https://tools.ietf.org/html/rfc5891
|
|
5
5
|
* @specification https://tools.ietf.org/html/rfc5892
|
|
6
|
+
* @specification https://tools.ietf.org/html/rfc5893
|
|
6
7
|
*/
|
|
7
8
|
export declare function IsIdnHostname(value: string): boolean;
|
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Idna from './idna/index.mjs';
|
|
2
2
|
/**
|
|
3
3
|
* Returns true if the value is a valid internationalized (IDN) hostname.
|
|
4
4
|
* @specification https://tools.ietf.org/html/rfc3490
|
|
5
5
|
* @specification https://tools.ietf.org/html/rfc5891
|
|
6
6
|
* @specification https://tools.ietf.org/html/rfc5892
|
|
7
|
+
* @specification https://tools.ietf.org/html/rfc5893
|
|
7
8
|
*/
|
|
8
9
|
export function IsIdnHostname(value) {
|
|
9
|
-
|
|
10
|
-
return false;
|
|
11
|
-
const canonical = value.normalize('NFC').replace(/[\u002E\u3002\uFF0E\uFF61]/g, '.');
|
|
12
|
-
if (canonical.length > 253)
|
|
13
|
-
return false;
|
|
14
|
-
for (const label of canonical.split('.')) {
|
|
15
|
-
if (!IsIdnLabel(label))
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
return true;
|
|
10
|
+
return Idna.IsIdnHostname(value);
|
|
19
11
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// deno-fmt-ignore-file
|
|
2
|
+
import * as FormatPuny from './puny.mjs';
|
|
3
|
+
import * as Pattern from '../pattern/pattern.mjs';
|
|
4
|
+
// ------------------------------------------------------------------
|
|
5
|
+
// LOCAL REGEX: Allowed Bidi classes per direction (RFC 5893)
|
|
6
|
+
// ------------------------------------------------------------------
|
|
7
|
+
const RE_RTL_ALLOWED = /^(?:R|AL|AN|EN|ES|CS|ET|ON|BN|NSM)$/;
|
|
8
|
+
const RE_LTR_ALLOWED = /^(?:L|EN|ES|CS|ET|ON|BN|NSM)$/;
|
|
9
|
+
const RE_RTL_CLASSES = /^(?:R|AL|AN)$/;
|
|
10
|
+
// ------------------------------------------------------------------
|
|
11
|
+
// HasBidiChars
|
|
12
|
+
//
|
|
13
|
+
// Decodes a punycode label to its Unicode form (if it is one) and
|
|
14
|
+
// checks whether the result is an RTL label per RFC 5893 §1.4.
|
|
15
|
+
// ------------------------------------------------------------------
|
|
16
|
+
export function HasBidiChars(value) {
|
|
17
|
+
if (FormatPuny.IsAcePrefixed(value)) {
|
|
18
|
+
try {
|
|
19
|
+
return HasRightToLeftCharacters(FormatPuny.Decode(value.slice(4).toLowerCase()));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return HasRightToLeftCharacters(value);
|
|
26
|
+
}
|
|
27
|
+
// ------------------------------------------------------------------
|
|
28
|
+
// GetBidiClass
|
|
29
|
+
// ------------------------------------------------------------------
|
|
30
|
+
function GetBidiClass(codePoint) {
|
|
31
|
+
const char = String.fromCodePoint(codePoint);
|
|
32
|
+
return (Pattern.RE_EUROPEAN_NUMBER.test(char) ? 'EN' :
|
|
33
|
+
Pattern.RE_ARABIC_INDIC_DIGIT.test(char) ? 'AN' :
|
|
34
|
+
// Pattern.RE_EUROPEAN_SEPARATOR.test(char) ? 'ES' : // (no-spec-coverage)
|
|
35
|
+
// Pattern.RE_COMMON_SEPARATOR.test(char) ? 'CS' : // (no-spec-coverage)
|
|
36
|
+
Pattern.RE_MARK_NONSPACING.test(char) ? 'NSM' :
|
|
37
|
+
Pattern.RE_SCRIPT_HEBREW.test(char) ? 'R' :
|
|
38
|
+
Pattern.RE_SCRIPT_ARABIC_LETTER.test(char) ? 'AL' :
|
|
39
|
+
Pattern.RE_LETTER.test(char) ? 'L' :
|
|
40
|
+
'ON');
|
|
41
|
+
}
|
|
42
|
+
// ------------------------------------------------------------------
|
|
43
|
+
// HasRightToLeftCharacters
|
|
44
|
+
//
|
|
45
|
+
// Per RFC 5893 §1.4: "An RTL label is a label that contains at least
|
|
46
|
+
// one character of type R, AL, or AN." Used to determine whether a
|
|
47
|
+
// domain name is a "Bidi domain name" (RFC 5893 §1.4), in which case
|
|
48
|
+
// the Bidi Rule applies to every label in the domain.
|
|
49
|
+
// ------------------------------------------------------------------
|
|
50
|
+
export function HasRightToLeftCharacters(value) {
|
|
51
|
+
for (const ch of value)
|
|
52
|
+
if (RE_RTL_CLASSES.test(GetBidiClass(ch.codePointAt(0))))
|
|
53
|
+
return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// ------------------------------------------------------------------
|
|
57
|
+
// SatisfiesBidiRule
|
|
58
|
+
// ------------------------------------------------------------------
|
|
59
|
+
export function SatisfiesBidiRule(value) {
|
|
60
|
+
let isRtl = false;
|
|
61
|
+
let allowed = RE_LTR_ALLOWED;
|
|
62
|
+
let sawEN = false;
|
|
63
|
+
let sawAN = false;
|
|
64
|
+
let isFirst = true;
|
|
65
|
+
// let lastClass: string | null = null // (no-spec-coverage)
|
|
66
|
+
for (const ch of value) {
|
|
67
|
+
const bidiClass = GetBidiClass(ch.codePointAt(0));
|
|
68
|
+
if (isFirst) {
|
|
69
|
+
// 1. The first character must be a character with Bidi property L, R, or AL.
|
|
70
|
+
if (bidiClass !== 'L' && bidiClass !== 'R' && bidiClass !== 'AL')
|
|
71
|
+
return false;
|
|
72
|
+
isRtl = bidiClass === 'R' || bidiClass === 'AL';
|
|
73
|
+
allowed = isRtl ? RE_RTL_ALLOWED : RE_LTR_ALLOWED;
|
|
74
|
+
isFirst = false;
|
|
75
|
+
}
|
|
76
|
+
// 2 / 5. Only characters in the allowed set for this direction may appear.
|
|
77
|
+
if (!allowed.test(bidiClass))
|
|
78
|
+
return false;
|
|
79
|
+
if (bidiClass === 'EN')
|
|
80
|
+
sawEN = true;
|
|
81
|
+
else if (bidiClass === 'AN')
|
|
82
|
+
sawAN = true;
|
|
83
|
+
// if (bidiClass !== 'NSM') lastClass = bidiClass // (no-spec-coverage)
|
|
84
|
+
}
|
|
85
|
+
if (isRtl && sawEN && sawAN)
|
|
86
|
+
return false; // spec coverage path
|
|
87
|
+
// (no-spec-coverage)
|
|
88
|
+
// if (isRtl) {
|
|
89
|
+
// // 3. The end of the label must be R, AL, EN, or AN (ignoring trailing NSM).
|
|
90
|
+
// // if (lastClass !== 'R' && lastClass !== 'AL' && lastClass !== 'EN' && lastClass !== 'AN') return false
|
|
91
|
+
// // 4. If an EN is present, no AN may be present, and vice versa.
|
|
92
|
+
// if (sawEN && sawAN) return false
|
|
93
|
+
// } else {
|
|
94
|
+
// // 6. The end of the label must be L or EN (ignoring trailing NSM).
|
|
95
|
+
// // if (lastClass !== 'L' && lastClass !== 'EN') return false
|
|
96
|
+
// }
|
|
97
|
+
return true;
|
|
98
|
+
}
|