@absolutejs/voice 0.0.22-beta.70 → 0.0.22-beta.72
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/index.d.ts +2 -2
- package/dist/index.js +194 -14
- package/dist/telephonyOutcome.d.ts +59 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRun
|
|
|
11
11
|
export { createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract } from './toolContract';
|
|
12
12
|
export { createVoiceTurnQualityHTMLHandler, createVoiceTurnQualityJSONHandler, createVoiceTurnQualityRoutes, renderVoiceTurnQualityHTML, summarizeVoiceTurnQuality } from './turnQuality';
|
|
13
13
|
export { createVoiceOutcomeContractHTMLHandler, createVoiceOutcomeContractJSONHandler, createVoiceOutcomeContractRoutes, renderVoiceOutcomeContractHTML, runVoiceOutcomeContractSuite } from './outcomeContract';
|
|
14
|
-
export { applyVoiceTelephonyOutcome, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
|
|
14
|
+
export { applyVoiceTelephonyOutcome, createMemoryVoiceTelephonyWebhookIdempotencyStore, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, signVoiceTwilioWebhook, verifyVoiceTwilioWebhookSignature, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
|
|
15
15
|
export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
|
|
16
16
|
export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
|
|
17
17
|
export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
|
|
@@ -57,7 +57,7 @@ export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProvid
|
|
|
57
57
|
export type { VoiceProviderCapabilityDefinition, VoiceProviderCapabilityHandlerOptions, VoiceProviderCapabilityHTMLHandlerOptions, VoiceProviderCapabilityKind, VoiceProviderCapabilityOptions, VoiceProviderCapabilityReport, VoiceProviderCapabilityRoutesOptions, VoiceProviderCapabilitySummary } from './providerCapabilities';
|
|
58
58
|
export type { VoiceTurnQualityHTMLHandlerOptions, VoiceTurnQualityItem, VoiceTurnQualityOptions, VoiceTurnQualityReport, VoiceTurnQualityRoutesOptions, VoiceTurnQualityStatus } from './turnQuality';
|
|
59
59
|
export type { VoiceOutcomeContractDefinition, VoiceOutcomeContractHTMLHandlerOptions, VoiceOutcomeContractIssue, VoiceOutcomeContractOptions, VoiceOutcomeContractReport, VoiceOutcomeContractRoutesOptions, VoiceOutcomeContractStatus, VoiceOutcomeContractSuiteReport } from './outcomeContract';
|
|
60
|
-
export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions } from './telephonyOutcome';
|
|
60
|
+
export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookIdempotencyStore, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult, StoredVoiceTelephonyWebhookDecision } from './telephonyOutcome';
|
|
61
61
|
export type { VoiceOpsConsoleLink, VoiceOpsConsoleReport, VoiceOpsConsoleRoutesOptions } from './opsConsoleRoutes';
|
|
62
62
|
export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
|
|
63
63
|
export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind } from './resilienceRoutes';
|
package/dist/index.js
CHANGED
|
@@ -2990,7 +2990,7 @@ var toVoiceSessionSummary = (session) => ({
|
|
|
2990
2990
|
});
|
|
2991
2991
|
|
|
2992
2992
|
// src/session.ts
|
|
2993
|
-
import { Buffer } from "buffer";
|
|
2993
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
2994
2994
|
|
|
2995
2995
|
// src/handoff.ts
|
|
2996
2996
|
var toHex3 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -3425,7 +3425,7 @@ var createEmptyCurrentTurn = () => ({
|
|
|
3425
3425
|
transcripts: []
|
|
3426
3426
|
});
|
|
3427
3427
|
var cloneTranscript = (transcript) => ({ ...transcript });
|
|
3428
|
-
var encodeBase64 = (chunk) =>
|
|
3428
|
+
var encodeBase64 = (chunk) => Buffer2.from(chunk).toString("base64");
|
|
3429
3429
|
var countWords2 = (text) => text.trim().split(/\s+/).filter(Boolean).length;
|
|
3430
3430
|
var normalizeText2 = (text) => text.trim().replace(/\s+/g, " ");
|
|
3431
3431
|
var getAudioChunkDurationMs = (chunk) => chunk.byteLength / (DEFAULT_FORMAT.sampleRateHz * DEFAULT_FORMAT.channels * 2) * 1000;
|
|
@@ -10601,6 +10601,24 @@ var DEFAULT_MACHINE_VOICEMAIL_VALUES = [
|
|
|
10601
10601
|
];
|
|
10602
10602
|
var DEFAULT_NO_ANSWER_SIP_CODES = [408, 480, 486, 487, 603];
|
|
10603
10603
|
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
10604
|
+
|
|
10605
|
+
class VoiceTelephonyWebhookVerificationError extends Error {
|
|
10606
|
+
result;
|
|
10607
|
+
constructor(result) {
|
|
10608
|
+
super(result.ok ? "telephony webhook verified" : result.reason);
|
|
10609
|
+
this.name = "VoiceTelephonyWebhookVerificationError";
|
|
10610
|
+
this.result = result;
|
|
10611
|
+
}
|
|
10612
|
+
}
|
|
10613
|
+
var createMemoryVoiceTelephonyWebhookIdempotencyStore = () => {
|
|
10614
|
+
const decisions = new Map;
|
|
10615
|
+
return {
|
|
10616
|
+
get: (key) => decisions.get(key),
|
|
10617
|
+
set: (key, decision) => {
|
|
10618
|
+
decisions.set(key, decision);
|
|
10619
|
+
}
|
|
10620
|
+
};
|
|
10621
|
+
};
|
|
10604
10622
|
var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
|
|
10605
10623
|
var firstString = (source, keys) => {
|
|
10606
10624
|
for (const key of keys) {
|
|
@@ -10649,6 +10667,30 @@ var flattenPayload = (value) => {
|
|
|
10649
10667
|
...isRecord(data?.payload) ? data.payload : undefined
|
|
10650
10668
|
};
|
|
10651
10669
|
};
|
|
10670
|
+
var toBase64 = (bytes) => Buffer.from(new Uint8Array(bytes)).toString("base64");
|
|
10671
|
+
var timingSafeEqual = (left, right) => {
|
|
10672
|
+
const encoder = new TextEncoder;
|
|
10673
|
+
const leftBytes = encoder.encode(left);
|
|
10674
|
+
const rightBytes = encoder.encode(right);
|
|
10675
|
+
if (leftBytes.length !== rightBytes.length) {
|
|
10676
|
+
return false;
|
|
10677
|
+
}
|
|
10678
|
+
let diff = 0;
|
|
10679
|
+
for (let index = 0;index < leftBytes.length; index += 1) {
|
|
10680
|
+
diff |= leftBytes[index] ^ rightBytes[index];
|
|
10681
|
+
}
|
|
10682
|
+
return diff === 0;
|
|
10683
|
+
};
|
|
10684
|
+
var signHmacSHA1Base64 = async (secret, payload) => {
|
|
10685
|
+
const encoder = new TextEncoder;
|
|
10686
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
10687
|
+
hash: "SHA-1",
|
|
10688
|
+
name: "HMAC"
|
|
10689
|
+
}, false, ["sign"]);
|
|
10690
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
|
|
10691
|
+
return toBase64(signature);
|
|
10692
|
+
};
|
|
10693
|
+
var sortedParamsForSignature = (body) => Object.entries(flattenPayload(body)).filter(([, value]) => value !== undefined && value !== null).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}${String(value)}`).join("");
|
|
10652
10694
|
var normalizeList = (values, fallback) => new Set((values ?? fallback).map(normalizeToken).filter(Boolean));
|
|
10653
10695
|
var metadataValue = (metadata, keys) => {
|
|
10654
10696
|
for (const key of keys) {
|
|
@@ -10882,9 +10924,8 @@ var applyVoiceTelephonyOutcome = async (api, decision, result) => {
|
|
|
10882
10924
|
break;
|
|
10883
10925
|
}
|
|
10884
10926
|
};
|
|
10885
|
-
var
|
|
10886
|
-
const contentType
|
|
10887
|
-
const text = await request.text();
|
|
10927
|
+
var parseRequestBodyText = (input) => {
|
|
10928
|
+
const { contentType, text } = input;
|
|
10888
10929
|
if (!text) {
|
|
10889
10930
|
return {};
|
|
10890
10931
|
}
|
|
@@ -10896,6 +10937,58 @@ var parseRequestBody = async (request) => {
|
|
|
10896
10937
|
}
|
|
10897
10938
|
return parseMaybeJSON(text) ?? Object.fromEntries(new URLSearchParams(text));
|
|
10898
10939
|
};
|
|
10940
|
+
var readRequestBody = async (request) => {
|
|
10941
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
10942
|
+
const text = await request.text();
|
|
10943
|
+
return {
|
|
10944
|
+
body: parseRequestBodyText({ contentType, text }),
|
|
10945
|
+
rawBody: text
|
|
10946
|
+
};
|
|
10947
|
+
};
|
|
10948
|
+
var signVoiceTwilioWebhook = async (input) => signHmacSHA1Base64(input.authToken, `${input.url}${sortedParamsForSignature(input.body ?? {})}`);
|
|
10949
|
+
var verifyVoiceTwilioWebhookSignature = async (input) => {
|
|
10950
|
+
if (!input.authToken) {
|
|
10951
|
+
return { ok: false, reason: "missing-secret" };
|
|
10952
|
+
}
|
|
10953
|
+
const signature = input.headers.get("x-twilio-signature");
|
|
10954
|
+
if (!signature) {
|
|
10955
|
+
return { ok: false, reason: "missing-signature" };
|
|
10956
|
+
}
|
|
10957
|
+
const expected = await signVoiceTwilioWebhook({
|
|
10958
|
+
authToken: input.authToken,
|
|
10959
|
+
body: input.body,
|
|
10960
|
+
url: input.url
|
|
10961
|
+
});
|
|
10962
|
+
return timingSafeEqual(signature, expected) ? { ok: true } : { ok: false, reason: "invalid-signature" };
|
|
10963
|
+
};
|
|
10964
|
+
var resolveVerificationUrl = (option, input) => typeof option === "function" ? option(input) : option ?? input.request.url;
|
|
10965
|
+
var verifyVoiceTelephonyWebhook = async (input) => {
|
|
10966
|
+
if (input.options.verify) {
|
|
10967
|
+
return input.options.verify({
|
|
10968
|
+
body: input.body,
|
|
10969
|
+
headers: input.request.headers,
|
|
10970
|
+
provider: input.provider,
|
|
10971
|
+
query: input.query,
|
|
10972
|
+
rawBody: input.rawBody,
|
|
10973
|
+
request: input.request
|
|
10974
|
+
});
|
|
10975
|
+
}
|
|
10976
|
+
if (!input.options.signingSecret) {
|
|
10977
|
+
return input.options.requireVerification ? { ok: false, reason: "missing-secret" } : { ok: true };
|
|
10978
|
+
}
|
|
10979
|
+
if (input.provider !== "twilio") {
|
|
10980
|
+
return { ok: false, reason: "unsupported-provider" };
|
|
10981
|
+
}
|
|
10982
|
+
return verifyVoiceTwilioWebhookSignature({
|
|
10983
|
+
authToken: input.options.signingSecret,
|
|
10984
|
+
body: input.body,
|
|
10985
|
+
headers: input.request.headers,
|
|
10986
|
+
url: resolveVerificationUrl(input.options.verificationUrl, {
|
|
10987
|
+
query: input.query,
|
|
10988
|
+
request: input.request
|
|
10989
|
+
})
|
|
10990
|
+
});
|
|
10991
|
+
};
|
|
10899
10992
|
var durationMsFromSeconds = (value) => typeof value === "number" ? value * 1000 : undefined;
|
|
10900
10993
|
var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
10901
10994
|
const payload = flattenPayload(input.body);
|
|
@@ -10975,10 +11068,46 @@ var defaultSessionId = (input) => {
|
|
|
10975
11068
|
"call_control_id"
|
|
10976
11069
|
]) ?? (typeof metadataSessionId === "string" ? metadataSessionId : undefined);
|
|
10977
11070
|
};
|
|
11071
|
+
var defaultIdempotencyKey = (input) => {
|
|
11072
|
+
const payload = flattenPayload(input.body);
|
|
11073
|
+
const eventId = firstString(payload, [
|
|
11074
|
+
"id",
|
|
11075
|
+
"event_id",
|
|
11076
|
+
"eventId",
|
|
11077
|
+
"EventSid",
|
|
11078
|
+
"event_sid",
|
|
11079
|
+
"MessageSid",
|
|
11080
|
+
"message_sid",
|
|
11081
|
+
"CallSid",
|
|
11082
|
+
"call_sid",
|
|
11083
|
+
"CallUUID",
|
|
11084
|
+
"call_uuid",
|
|
11085
|
+
"callControlId",
|
|
11086
|
+
"call_control_id"
|
|
11087
|
+
]);
|
|
11088
|
+
const status = normalizeToken(input.event.status) ?? "unknown";
|
|
11089
|
+
if (eventId) {
|
|
11090
|
+
return `${input.provider}:${eventId}:${status}`;
|
|
11091
|
+
}
|
|
11092
|
+
if (input.sessionId) {
|
|
11093
|
+
return `${input.provider}:${input.sessionId}:${status}`;
|
|
11094
|
+
}
|
|
11095
|
+
};
|
|
10978
11096
|
var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
10979
11097
|
const provider = options.provider ?? "generic";
|
|
10980
11098
|
const query = input.query ?? {};
|
|
10981
|
-
const body = await
|
|
11099
|
+
const { body, rawBody } = await readRequestBody(input.request);
|
|
11100
|
+
const verification = await verifyVoiceTelephonyWebhook({
|
|
11101
|
+
body,
|
|
11102
|
+
options,
|
|
11103
|
+
provider,
|
|
11104
|
+
query,
|
|
11105
|
+
rawBody,
|
|
11106
|
+
request: input.request
|
|
11107
|
+
});
|
|
11108
|
+
if (!verification.ok) {
|
|
11109
|
+
throw new VoiceTelephonyWebhookVerificationError(verification);
|
|
11110
|
+
}
|
|
10982
11111
|
const event = options.parse ? await options.parse({
|
|
10983
11112
|
body,
|
|
10984
11113
|
headers: input.request.headers,
|
|
@@ -10998,6 +11127,31 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
10998
11127
|
query,
|
|
10999
11128
|
request: input.request
|
|
11000
11129
|
}) ?? defaultSessionId({ body, event, query }));
|
|
11130
|
+
const idempotencyEnabled = options.idempotency?.enabled !== false;
|
|
11131
|
+
const idempotencyKey = idempotencyEnabled ? await (options.idempotency?.key?.({
|
|
11132
|
+
body,
|
|
11133
|
+
event,
|
|
11134
|
+
provider,
|
|
11135
|
+
query,
|
|
11136
|
+
request: input.request,
|
|
11137
|
+
sessionId
|
|
11138
|
+
}) ?? defaultIdempotencyKey({ body, event, provider, sessionId })) : undefined;
|
|
11139
|
+
const idempotencyStore = options.idempotency?.store;
|
|
11140
|
+
if (idempotencyKey && idempotencyStore) {
|
|
11141
|
+
const existing = await idempotencyStore.get(idempotencyKey);
|
|
11142
|
+
if (existing) {
|
|
11143
|
+
const duplicateDecision = {
|
|
11144
|
+
...existing,
|
|
11145
|
+
duplicate: true
|
|
11146
|
+
};
|
|
11147
|
+
await options.onDecision?.({
|
|
11148
|
+
...duplicateDecision,
|
|
11149
|
+
context: options.context,
|
|
11150
|
+
request: input.request
|
|
11151
|
+
});
|
|
11152
|
+
return duplicateDecision;
|
|
11153
|
+
}
|
|
11154
|
+
}
|
|
11001
11155
|
const decision = resolveVoiceTelephonyOutcome(event, options.policy);
|
|
11002
11156
|
const resultResolver = options.result;
|
|
11003
11157
|
const result = typeof resultResolver === "function" ? await resultResolver({
|
|
@@ -11031,9 +11185,18 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
11031
11185
|
applied,
|
|
11032
11186
|
decision,
|
|
11033
11187
|
event,
|
|
11188
|
+
idempotencyKey,
|
|
11034
11189
|
routeResult,
|
|
11035
11190
|
sessionId
|
|
11036
11191
|
};
|
|
11192
|
+
if (idempotencyKey && idempotencyStore) {
|
|
11193
|
+
const now = Date.now();
|
|
11194
|
+
await idempotencyStore.set(idempotencyKey, {
|
|
11195
|
+
...webhookDecision,
|
|
11196
|
+
createdAt: now,
|
|
11197
|
+
updatedAt: now
|
|
11198
|
+
});
|
|
11199
|
+
}
|
|
11037
11200
|
await options.onDecision?.({
|
|
11038
11201
|
...webhookDecision,
|
|
11039
11202
|
context: options.context,
|
|
@@ -11046,7 +11209,21 @@ var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
|
11046
11209
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
11047
11210
|
return new Elysia16({
|
|
11048
11211
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
11049
|
-
}).post(path, async ({ query, request }) =>
|
|
11212
|
+
}).post(path, async ({ query, request }) => {
|
|
11213
|
+
try {
|
|
11214
|
+
return await handler({ query, request });
|
|
11215
|
+
} catch (error) {
|
|
11216
|
+
if (error instanceof VoiceTelephonyWebhookVerificationError) {
|
|
11217
|
+
return new Response(JSON.stringify({ verification: error.result }), {
|
|
11218
|
+
headers: {
|
|
11219
|
+
"content-type": "application/json"
|
|
11220
|
+
},
|
|
11221
|
+
status: 401
|
|
11222
|
+
});
|
|
11223
|
+
}
|
|
11224
|
+
throw error;
|
|
11225
|
+
}
|
|
11226
|
+
});
|
|
11050
11227
|
};
|
|
11051
11228
|
// src/fileStore.ts
|
|
11052
11229
|
import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
@@ -13152,7 +13329,7 @@ var signVoiceOpsWebhookBody = async (input) => {
|
|
|
13152
13329
|
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(`${input.timestamp}.${input.body}`));
|
|
13153
13330
|
return `sha256=${toHex5(new Uint8Array(signature))}`;
|
|
13154
13331
|
};
|
|
13155
|
-
var
|
|
13332
|
+
var timingSafeEqual2 = (left, right) => {
|
|
13156
13333
|
const encoder = new TextEncoder;
|
|
13157
13334
|
const leftBytes = encoder.encode(left);
|
|
13158
13335
|
const rightBytes = encoder.encode(right);
|
|
@@ -13259,7 +13436,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
13259
13436
|
secret: input.secret,
|
|
13260
13437
|
timestamp: input.timestamp
|
|
13261
13438
|
});
|
|
13262
|
-
if (!
|
|
13439
|
+
if (!timingSafeEqual2(expected, input.signature)) {
|
|
13263
13440
|
return {
|
|
13264
13441
|
ok: false,
|
|
13265
13442
|
reason: "invalid-signature"
|
|
@@ -14978,7 +15155,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
|
|
|
14978
15155
|
return createPhraseHintCorrectionHandler();
|
|
14979
15156
|
};
|
|
14980
15157
|
// src/telephony/twilio.ts
|
|
14981
|
-
import { Buffer as
|
|
15158
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
14982
15159
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
14983
15160
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
14984
15161
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -15083,7 +15260,7 @@ var bytesToInt16Array = (bytes) => {
|
|
|
15083
15260
|
return output;
|
|
15084
15261
|
};
|
|
15085
15262
|
var decodeTwilioMulawBase64 = (payload) => {
|
|
15086
|
-
const bytes = Uint8Array.from(
|
|
15263
|
+
const bytes = Uint8Array.from(Buffer3.from(payload, "base64"));
|
|
15087
15264
|
const samples = new Int16Array(bytes.length);
|
|
15088
15265
|
for (let index = 0;index < bytes.length; index += 1) {
|
|
15089
15266
|
samples[index] = decodeMulawSample(bytes[index] ?? 0);
|
|
@@ -15095,7 +15272,7 @@ var encodeTwilioMulawBase64 = (samples) => {
|
|
|
15095
15272
|
for (let index = 0;index < samples.length; index += 1) {
|
|
15096
15273
|
bytes[index] = encodeMulawSample(samples[index] ?? 0);
|
|
15097
15274
|
}
|
|
15098
|
-
return
|
|
15275
|
+
return Buffer3.from(bytes).toString("base64");
|
|
15099
15276
|
};
|
|
15100
15277
|
var transcodeTwilioInboundPayloadToPCM16 = (payload) => {
|
|
15101
15278
|
const narrowband = decodeTwilioMulawBase64(payload);
|
|
@@ -15104,7 +15281,7 @@ var transcodeTwilioInboundPayloadToPCM16 = (payload) => {
|
|
|
15104
15281
|
};
|
|
15105
15282
|
var transcodePCMToTwilioOutboundPayload = (chunk, format) => {
|
|
15106
15283
|
if (format.container === "raw" && format.encoding === "mulaw" && format.channels === 1 && format.sampleRateHz === TWILIO_MULAW_SAMPLE_RATE) {
|
|
15107
|
-
return
|
|
15284
|
+
return Buffer3.from(chunk).toString("base64");
|
|
15108
15285
|
}
|
|
15109
15286
|
if (format.encoding !== "pcm_s16le") {
|
|
15110
15287
|
throw new Error(`Unsupported outbound telephony audio format: ${format.container}/${format.encoding}`);
|
|
@@ -15145,7 +15322,7 @@ var createTwilioSocketAdapter = (socket, getState) => ({
|
|
|
15145
15322
|
return;
|
|
15146
15323
|
}
|
|
15147
15324
|
if (message.type === "audio") {
|
|
15148
|
-
const payload = transcodePCMToTwilioOutboundPayload(Uint8Array.from(
|
|
15325
|
+
const payload = transcodePCMToTwilioOutboundPayload(Uint8Array.from(Buffer3.from(message.chunkBase64, "base64")), message.format);
|
|
15149
15326
|
state.hasOutboundAudioSinceLastInbound = true;
|
|
15150
15327
|
state.reviewRecorder?.recordTwilioOutbound({
|
|
15151
15328
|
bytes: payload.length,
|
|
@@ -15415,6 +15592,7 @@ export {
|
|
|
15415
15592
|
withVoiceIntegrationEventId,
|
|
15416
15593
|
voiceTelephonyOutcomeToRouteResult,
|
|
15417
15594
|
voice,
|
|
15595
|
+
verifyVoiceTwilioWebhookSignature,
|
|
15418
15596
|
verifyVoiceOpsWebhookSignature,
|
|
15419
15597
|
validateVoiceWorkflowRouteResult,
|
|
15420
15598
|
transcodeTwilioInboundPayloadToPCM16,
|
|
@@ -15437,6 +15615,7 @@ export {
|
|
|
15437
15615
|
summarizeVoiceAssistantHealth,
|
|
15438
15616
|
summarizeVoiceAppKitStatus,
|
|
15439
15617
|
startVoiceOpsTask,
|
|
15618
|
+
signVoiceTwilioWebhook,
|
|
15440
15619
|
shapeTelephonyAssistantText,
|
|
15441
15620
|
selectVoiceTraceEventsForPrune,
|
|
15442
15621
|
runVoiceToolContractSuite,
|
|
@@ -15657,6 +15836,7 @@ export {
|
|
|
15657
15836
|
createRiskyTurnCorrectionHandler,
|
|
15658
15837
|
createPhraseHintCorrectionHandler,
|
|
15659
15838
|
createOpenAIVoiceAssistantModel,
|
|
15839
|
+
createMemoryVoiceTelephonyWebhookIdempotencyStore,
|
|
15660
15840
|
createJSONVoiceAssistantModel,
|
|
15661
15841
|
createId,
|
|
15662
15842
|
createGeminiVoiceAssistantModel,
|
|
@@ -55,10 +55,26 @@ export type VoiceTelephonyWebhookParseInput = {
|
|
|
55
55
|
export type VoiceTelephonyWebhookDecision<TResult = unknown> = {
|
|
56
56
|
applied: boolean;
|
|
57
57
|
decision: VoiceTelephonyOutcomeDecision;
|
|
58
|
+
duplicate?: boolean;
|
|
58
59
|
event: VoiceTelephonyOutcomeProviderEvent;
|
|
60
|
+
idempotencyKey?: string;
|
|
59
61
|
routeResult: VoiceTelephonyOutcomeRouteResult<TResult>;
|
|
60
62
|
sessionId?: string;
|
|
61
63
|
};
|
|
64
|
+
export type StoredVoiceTelephonyWebhookDecision<TResult = unknown> = VoiceTelephonyWebhookDecision<TResult> & {
|
|
65
|
+
createdAt: number;
|
|
66
|
+
updatedAt: number;
|
|
67
|
+
};
|
|
68
|
+
export type VoiceTelephonyWebhookIdempotencyStore<TResult = unknown> = {
|
|
69
|
+
get: (key: string) => Promise<StoredVoiceTelephonyWebhookDecision<TResult> | undefined> | StoredVoiceTelephonyWebhookDecision<TResult> | undefined;
|
|
70
|
+
set: (key: string, decision: StoredVoiceTelephonyWebhookDecision<TResult>) => Promise<void> | void;
|
|
71
|
+
};
|
|
72
|
+
export type VoiceTelephonyWebhookVerificationResult = {
|
|
73
|
+
ok: true;
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
reason: 'invalid-signature' | 'missing-secret' | 'missing-signature' | 'unsupported-provider';
|
|
77
|
+
};
|
|
62
78
|
export type VoiceTelephonyWebhookHandlerOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
63
79
|
apply?: boolean | ((input: VoiceTelephonyWebhookDecision<TResult>) => boolean);
|
|
64
80
|
context?: TContext;
|
|
@@ -69,6 +85,18 @@ export type VoiceTelephonyWebhookHandlerOptions<TContext = unknown, TSession ext
|
|
|
69
85
|
request: Request;
|
|
70
86
|
sessionId?: string;
|
|
71
87
|
}) => Promise<VoiceSessionHandle<TContext, TSession, TResult> | undefined> | VoiceSessionHandle<TContext, TSession, TResult> | undefined;
|
|
88
|
+
idempotency?: {
|
|
89
|
+
enabled?: boolean;
|
|
90
|
+
key?: (input: {
|
|
91
|
+
body: unknown;
|
|
92
|
+
event: VoiceTelephonyOutcomeProviderEvent;
|
|
93
|
+
provider: VoiceTelephonyWebhookProvider;
|
|
94
|
+
query: Record<string, unknown>;
|
|
95
|
+
request: Request;
|
|
96
|
+
sessionId?: string;
|
|
97
|
+
}) => Promise<string | undefined> | string | undefined;
|
|
98
|
+
store?: VoiceTelephonyWebhookIdempotencyStore<TResult>;
|
|
99
|
+
};
|
|
72
100
|
onDecision?: (input: VoiceTelephonyWebhookDecision<TResult> & {
|
|
73
101
|
context: TContext;
|
|
74
102
|
request: Request;
|
|
@@ -76,6 +104,7 @@ export type VoiceTelephonyWebhookHandlerOptions<TContext = unknown, TSession ext
|
|
|
76
104
|
parse?: (input: VoiceTelephonyWebhookParseInput) => Promise<VoiceTelephonyOutcomeProviderEvent> | VoiceTelephonyOutcomeProviderEvent;
|
|
77
105
|
policy?: VoiceTelephonyOutcomePolicy;
|
|
78
106
|
provider?: VoiceTelephonyWebhookProvider;
|
|
107
|
+
requireVerification?: boolean;
|
|
79
108
|
resolveSessionId?: (input: {
|
|
80
109
|
body: unknown;
|
|
81
110
|
event: VoiceTelephonyOutcomeProviderEvent;
|
|
@@ -87,15 +116,44 @@ export type VoiceTelephonyWebhookHandlerOptions<TContext = unknown, TSession ext
|
|
|
87
116
|
event: VoiceTelephonyOutcomeProviderEvent;
|
|
88
117
|
sessionId?: string;
|
|
89
118
|
}) => Promise<TResult | undefined> | TResult | undefined);
|
|
119
|
+
signingSecret?: string;
|
|
120
|
+
verificationUrl?: string | ((input: {
|
|
121
|
+
query: Record<string, unknown>;
|
|
122
|
+
request: Request;
|
|
123
|
+
}) => string);
|
|
124
|
+
verify?: (input: {
|
|
125
|
+
body: unknown;
|
|
126
|
+
headers: Headers;
|
|
127
|
+
provider: VoiceTelephonyWebhookProvider;
|
|
128
|
+
query: Record<string, unknown>;
|
|
129
|
+
rawBody: string;
|
|
130
|
+
request: Request;
|
|
131
|
+
}) => Promise<VoiceTelephonyWebhookVerificationResult> | VoiceTelephonyWebhookVerificationResult;
|
|
90
132
|
};
|
|
91
133
|
export type VoiceTelephonyWebhookRoutesOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = VoiceTelephonyWebhookHandlerOptions<TContext, TSession, TResult> & {
|
|
92
134
|
name?: string;
|
|
93
135
|
path?: string;
|
|
94
136
|
};
|
|
137
|
+
export declare class VoiceTelephonyWebhookVerificationError extends Error {
|
|
138
|
+
result: VoiceTelephonyWebhookVerificationResult;
|
|
139
|
+
constructor(result: VoiceTelephonyWebhookVerificationResult);
|
|
140
|
+
}
|
|
141
|
+
export declare const createMemoryVoiceTelephonyWebhookIdempotencyStore: <TResult = unknown>() => VoiceTelephonyWebhookIdempotencyStore<TResult>;
|
|
95
142
|
export declare const createVoiceTelephonyOutcomePolicy: (policy?: VoiceTelephonyOutcomePolicy) => Required<Pick<VoiceTelephonyOutcomePolicy, "completedStatuses" | "escalationStatuses" | "failedAsNoAnswer" | "failedStatuses" | "includeProviderPayload" | "machineDetectionVoicemailValues" | "noAnswerOnZeroDuration" | "noAnswerSipCodes" | "noAnswerStatuses" | "transferStatuses" | "voicemailStatuses">> & VoiceTelephonyOutcomePolicy;
|
|
96
143
|
export declare const resolveVoiceTelephonyOutcome: (event: VoiceTelephonyOutcomeProviderEvent, policyInput?: VoiceTelephonyOutcomePolicy) => VoiceTelephonyOutcomeDecision;
|
|
97
144
|
export declare const voiceTelephonyOutcomeToRouteResult: <TResult = unknown>(decision: VoiceTelephonyOutcomeDecision, result?: TResult) => VoiceTelephonyOutcomeRouteResult<TResult>;
|
|
98
145
|
export declare const applyVoiceTelephonyOutcome: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(api: VoiceSessionHandle<TContext, TSession, TResult>, decision: VoiceTelephonyOutcomeDecision, result?: TResult) => Promise<void>;
|
|
146
|
+
export declare const signVoiceTwilioWebhook: (input: {
|
|
147
|
+
authToken: string;
|
|
148
|
+
body?: unknown;
|
|
149
|
+
url: string;
|
|
150
|
+
}) => Promise<string>;
|
|
151
|
+
export declare const verifyVoiceTwilioWebhookSignature: (input: {
|
|
152
|
+
authToken?: string;
|
|
153
|
+
body?: unknown;
|
|
154
|
+
headers: Headers;
|
|
155
|
+
url: string;
|
|
156
|
+
}) => Promise<VoiceTelephonyWebhookVerificationResult>;
|
|
99
157
|
export declare const parseVoiceTelephonyWebhookEvent: (input: VoiceTelephonyWebhookParseInput) => VoiceTelephonyOutcomeProviderEvent;
|
|
100
158
|
export declare const createVoiceTelephonyWebhookHandler: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options?: VoiceTelephonyWebhookHandlerOptions<TContext, TSession, TResult>) => (input: {
|
|
101
159
|
query?: Record<string, unknown>;
|
|
@@ -124,7 +182,7 @@ export declare const createVoiceTelephonyWebhookRoutes: <TContext = unknown, TSe
|
|
|
124
182
|
query: unknown;
|
|
125
183
|
headers: unknown;
|
|
126
184
|
response: {
|
|
127
|
-
200: VoiceTelephonyWebhookDecision<TResult>;
|
|
185
|
+
200: Response | VoiceTelephonyWebhookDecision<TResult>;
|
|
128
186
|
};
|
|
129
187
|
};
|
|
130
188
|
};
|