@absolutejs/voice 0.0.22-beta.264 → 0.0.22-beta.266

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 CHANGED
@@ -40,7 +40,7 @@ export { createVoiceLiveLatencyRoutes, renderVoiceLiveLatencyHTML, summarizeVoic
40
40
  export { assertVoiceLatencySLOGate, buildVoiceLatencySLOGate, renderVoiceLatencySLOMarkdown } from './latencySlo';
41
41
  export { createVoiceTurnQualityHTMLHandler, createVoiceTurnQualityJSONHandler, createVoiceTurnQualityRoutes, renderVoiceTurnQualityHTML, summarizeVoiceTurnQuality } from './turnQuality';
42
42
  export { assertVoiceOutcomeContractEvidence, createVoiceOutcomeContractHTMLHandler, createVoiceOutcomeContractJSONHandler, createVoiceOutcomeContractRoutes, evaluateVoiceOutcomeContractEvidence, renderVoiceOutcomeContractHTML, runVoiceOutcomeContractSuite } from './outcomeContract';
43
- export { applyVoiceTelephonyOutcome, createMemoryVoiceTelephonyWebhookIdempotencyStore, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, signVoiceTwilioWebhook, verifyVoiceTwilioWebhookSignature, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
43
+ export { applyVoiceTelephonyOutcome, assertVoiceTelephonyWebhookNormalizationEvidence, createMemoryVoiceTelephonyWebhookIdempotencyStore, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, evaluateVoiceTelephonyWebhookNormalizationEvidence, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, signVoiceTwilioWebhook, verifyVoiceTwilioWebhookSignature, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
44
44
  export { assertVoicePhoneCallControlEvidence, assertVoicePhoneAssistantEvidence, createVoicePhoneAgent, evaluateVoicePhoneCallControlEvidence, evaluateVoicePhoneAssistantEvidence } from './phoneAgent';
45
45
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileIncidentBundleStore, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileAuditEventStore, createVoiceFileAuditSinkDeliveryStore, createVoiceFileCampaignStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
46
46
  export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
@@ -115,7 +115,7 @@ export type { VoiceLiveLatencyOptions, VoiceLiveLatencyReport, VoiceLiveLatencyR
115
115
  export type { VoiceLatencySLOBudget, VoiceLatencySLOGateError, VoiceLatencySLOGateOptions, VoiceLatencySLOGateReport, VoiceLatencySLOMeasurement, VoiceLatencySLOStage, VoiceLatencySLOStageSummary, VoiceLatencySLOStatus } from './latencySlo';
116
116
  export type { VoiceTurnQualityHTMLHandlerOptions, VoiceTurnQualityItem, VoiceTurnQualityOptions, VoiceTurnQualityReport, VoiceTurnQualityRoutesOptions, VoiceTurnQualityStatus } from './turnQuality';
117
117
  export type { VoiceOutcomeContractAssertionInput, VoiceOutcomeContractAssertionReport, VoiceOutcomeContractDefinition, VoiceOutcomeContractHTMLHandlerOptions, VoiceOutcomeContractIssue, VoiceOutcomeContractOptions, VoiceOutcomeContractReport, VoiceOutcomeContractRoutesOptions, VoiceOutcomeContractStatus, VoiceOutcomeContractSuiteReport } from './outcomeContract';
118
- export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookIdempotencyStore, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult, StoredVoiceTelephonyWebhookDecision } from './telephonyOutcome';
118
+ export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookIdempotencyStore, VoiceTelephonyWebhookNormalizationEvidenceDecision, VoiceTelephonyWebhookNormalizationEvidenceInput, VoiceTelephonyWebhookNormalizationEvidenceReport, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult, StoredVoiceTelephonyWebhookDecision } from './telephonyOutcome';
119
119
  export type { VoicePhoneAgentCarrier, VoicePhoneAgentCarrierSummary, VoicePhoneAssistantEvidenceInput, VoicePhoneAssistantEvidenceReport, VoicePhoneCallControlEvidenceInput, VoicePhoneCallControlEvidenceReport, VoicePhoneAgentLifecycleStage, VoicePhoneAgentPlivoCarrier, VoicePhoneAgentRoutes, VoicePhoneAgentRoutesOptions, VoicePhoneAgentSetupReport, VoicePhoneAgentTelnyxCarrier, VoicePhoneAgentTwilioCarrier } from './phoneAgent';
120
120
  export type { VoicePhoneAgentProductionSmokeIssue, VoicePhoneAgentProductionSmokeHandlerOptions, VoicePhoneAgentProductionSmokeHTMLHandlerOptions, VoicePhoneAgentProductionSmokeOptions, VoicePhoneAgentProductionSmokeReport, VoicePhoneAgentProductionSmokeRoutesOptions, VoicePhoneAgentProductionSmokeRequirement } from './phoneAgentProductionSmoke';
121
121
  export type { VoiceOpsConsoleLink, VoiceOpsConsoleReport, VoiceOpsConsoleRoutesOptions } from './opsConsoleRoutes';
package/dist/index.js CHANGED
@@ -17364,6 +17364,14 @@ var DEFAULT_MACHINE_VOICEMAIL_VALUES = [
17364
17364
  ];
17365
17365
  var DEFAULT_NO_ANSWER_SIP_CODES = [408, 480, 486, 487, 603];
17366
17366
  var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
17367
+ var uniqueSorted3 = (values) => Array.from(new Set(values)).sort();
17368
+ var findMissing3 = (values, required) => {
17369
+ if (!required?.length) {
17370
+ return [];
17371
+ }
17372
+ const valueSet = new Set(values);
17373
+ return required.filter((value) => !valueSet.has(value));
17374
+ };
17367
17375
 
17368
17376
  class VoiceTelephonyWebhookVerificationError extends Error {
17369
17377
  result;
@@ -17382,6 +17390,75 @@ var createMemoryVoiceTelephonyWebhookIdempotencyStore = () => {
17382
17390
  }
17383
17391
  };
17384
17392
  };
17393
+ var isTelephonyWebhookProvider = (value) => value === "generic" || value === "plivo" || value === "telnyx" || value === "twilio";
17394
+ var isTelephonyOutcomeAction = (value) => value === "complete" || value === "escalate" || value === "ignore" || value === "no-answer" || value === "transfer" || value === "voicemail";
17395
+ var isCallDisposition = (value) => value === "completed" || value === "escalated" || value === "failed" || value === "no-answer" || value === "transferred" || value === "voicemail";
17396
+ var evaluateVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
17397
+ const issues = [];
17398
+ const decisions = input.decisions ?? [];
17399
+ const actions = uniqueSorted3(decisions.map((decision) => decision.decision?.action ?? decision.action).filter(isTelephonyOutcomeAction));
17400
+ const dispositions = uniqueSorted3(decisions.map((decision) => decision.decision?.disposition ?? decision.disposition).filter(isCallDisposition));
17401
+ const providers = uniqueSorted3(decisions.map((decision) => decision.provider ?? decision.event?.provider).filter(isTelephonyWebhookProvider));
17402
+ const sources = uniqueSorted3(decisions.map((decision) => decision.decision?.source ?? decision.source).filter((source) => typeof source === "string"));
17403
+ const applied = decisions.filter((decision) => decision.applied === true).length;
17404
+ const duplicateDecisions = decisions.filter((decision) => decision.duplicate === true);
17405
+ const duplicateProviders = uniqueSorted3(duplicateDecisions.map((decision) => decision.provider ?? decision.event?.provider).filter(isTelephonyWebhookProvider));
17406
+ const duplicateIdempotencyKeys = new Set(duplicateDecisions.map((decision) => decision.idempotencyKey).filter((key) => typeof key === "string" && key.length > 0)).size;
17407
+ const routeResults = decisions.filter((decision) => isRecord(decision.routeResult)).length;
17408
+ const missingSessionIds = decisions.filter((decision) => !decision.sessionId).length;
17409
+ if (input.minDecisions !== undefined && decisions.length < input.minDecisions) {
17410
+ issues.push(`Expected at least ${String(input.minDecisions)} telephony webhook decision(s), found ${String(decisions.length)}.`);
17411
+ }
17412
+ if (input.minApplied !== undefined && applied < input.minApplied) {
17413
+ issues.push(`Expected at least ${String(input.minApplied)} applied telephony webhook decision(s), found ${String(applied)}.`);
17414
+ }
17415
+ if (input.minDuplicates !== undefined && duplicateDecisions.length < input.minDuplicates) {
17416
+ issues.push(`Expected at least ${String(input.minDuplicates)} duplicate telephony webhook decision(s), found ${String(duplicateDecisions.length)}.`);
17417
+ }
17418
+ if (input.minDuplicateIdempotencyKeys !== undefined && duplicateIdempotencyKeys < input.minDuplicateIdempotencyKeys) {
17419
+ issues.push(`Expected at least ${String(input.minDuplicateIdempotencyKeys)} duplicate telephony webhook idempotency key(s), found ${String(duplicateIdempotencyKeys)}.`);
17420
+ }
17421
+ if (input.maxMissingSessionIds !== undefined && missingSessionIds > input.maxMissingSessionIds) {
17422
+ issues.push(`Expected at most ${String(input.maxMissingSessionIds)} telephony webhook decision(s) without sessionId, found ${String(missingSessionIds)}.`);
17423
+ }
17424
+ if (input.requireRouteResults && routeResults < decisions.length) {
17425
+ issues.push(`Expected every telephony webhook decision to include a route result, found ${String(routeResults)} of ${String(decisions.length)}.`);
17426
+ }
17427
+ for (const provider of findMissing3(providers, input.requiredProviders)) {
17428
+ issues.push(`Missing telephony webhook provider: ${provider}.`);
17429
+ }
17430
+ for (const provider of findMissing3(duplicateProviders, input.requiredDuplicateProviders)) {
17431
+ issues.push(`Missing duplicate telephony webhook provider: ${provider}.`);
17432
+ }
17433
+ for (const action of findMissing3(actions, input.requiredActions)) {
17434
+ issues.push(`Missing telephony webhook action: ${action}.`);
17435
+ }
17436
+ for (const disposition of findMissing3(dispositions, input.requiredDispositions)) {
17437
+ issues.push(`Missing telephony webhook disposition: ${disposition}.`);
17438
+ }
17439
+ return {
17440
+ actions,
17441
+ applied,
17442
+ decisions: decisions.length,
17443
+ dispositions,
17444
+ duplicateIdempotencyKeys,
17445
+ duplicateProviders,
17446
+ duplicates: duplicateDecisions.length,
17447
+ issues,
17448
+ missingSessionIds,
17449
+ ok: issues.length === 0,
17450
+ providers,
17451
+ routeResults,
17452
+ sources
17453
+ };
17454
+ };
17455
+ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
17456
+ const assertion = evaluateVoiceTelephonyWebhookNormalizationEvidence(input);
17457
+ if (!assertion.ok) {
17458
+ throw new Error(`Voice telephony webhook normalization evidence assertion failed: ${assertion.issues.join(" ")}`);
17459
+ }
17460
+ return assertion;
17461
+ };
17385
17462
  var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
17386
17463
  var firstString2 = (source, keys) => {
17387
17464
  for (const key of keys) {
@@ -20020,8 +20097,8 @@ var loadCarrierMatrixInputs = async (input) => {
20020
20097
  };
20021
20098
  var carrierAnswerLabel = (provider) => provider === "telnyx" ? "TeXML URL" : provider === "plivo" ? "Answer URL" : "TwiML URL";
20022
20099
  var findCarrierMatrixEntry = (matrix, carrier) => matrix?.entries.find((candidate) => candidate.provider === carrier.provider && (candidate.name === carrier.name || candidate.name === (carrier.name ?? carrier.provider)));
20023
- var uniqueSorted3 = (values) => Array.from(new Set(values)).sort();
20024
- var findMissing3 = (values, required) => {
20100
+ var uniqueSorted4 = (values) => Array.from(new Set(values)).sort();
20101
+ var findMissing4 = (values, required) => {
20025
20102
  if (!required?.length) {
20026
20103
  return [];
20027
20104
  }
@@ -20030,8 +20107,8 @@ var findMissing3 = (values, required) => {
20030
20107
  };
20031
20108
  var evaluateVoicePhoneAssistantEvidence = (report, input = {}) => {
20032
20109
  const issues = [];
20033
- const providers = uniqueSorted3(report.carriers.map((carrier) => carrier.provider));
20034
- const lifecycleStages = uniqueSorted3(report.lifecycleStages);
20110
+ const providers = uniqueSorted4(report.carriers.map((carrier) => carrier.provider));
20111
+ const lifecycleStages = uniqueSorted4(report.lifecycleStages);
20035
20112
  const carrierSummary = report.matrix?.summary;
20036
20113
  const carrierFailures = carrierSummary?.failing ?? 0;
20037
20114
  const carrierWarnings = carrierSummary?.warnings ?? 0;
@@ -20071,10 +20148,10 @@ var evaluateVoicePhoneAssistantEvidence = (report, input = {}) => {
20071
20148
  if (input.maxCarrierWarnings !== undefined && carrierWarnings > input.maxCarrierWarnings) {
20072
20149
  issues.push(`Expected at most ${String(input.maxCarrierWarnings)} phone carrier warning(s), found ${String(carrierWarnings)}.`);
20073
20150
  }
20074
- for (const provider of findMissing3(providers, input.requiredProviders)) {
20151
+ for (const provider of findMissing4(providers, input.requiredProviders)) {
20075
20152
  issues.push(`Missing phone carrier provider: ${provider}.`);
20076
20153
  }
20077
- for (const stage of findMissing3(lifecycleStages, input.requiredLifecycleStages)) {
20154
+ for (const stage of findMissing4(lifecycleStages, input.requiredLifecycleStages)) {
20078
20155
  issues.push(`Missing phone lifecycle stage: ${stage}.`);
20079
20156
  }
20080
20157
  const dialer = input.dialerProof ? evaluateVoiceCampaignDialerProofEvidence(input.dialerProof, {
@@ -20117,9 +20194,9 @@ var evaluateVoicePhoneCallControlEvidence = (input = {}) => {
20117
20194
  const issues = [];
20118
20195
  const setup = input.setup;
20119
20196
  const productionSmokes = input.productionSmokes ?? [];
20120
- const lifecycleStages = uniqueSorted3(setup?.lifecycleStages ?? []);
20121
- const providers = uniqueSorted3(productionSmokes.map((report) => report.provider).filter((provider) => Boolean(provider)));
20122
- const outcomes = uniqueSorted3(productionSmokes.flatMap((report) => report.observed.lifecycleOutcomes));
20197
+ const lifecycleStages = uniqueSorted4(setup?.lifecycleStages ?? []);
20198
+ const providers = uniqueSorted4(productionSmokes.map((report) => report.provider).filter((provider) => Boolean(provider)));
20199
+ const outcomes = uniqueSorted4(productionSmokes.flatMap((report) => report.observed.lifecycleOutcomes));
20123
20200
  const failedSmokeReports = productionSmokes.filter((report) => !report.pass).length;
20124
20201
  const passingSmokeReports = productionSmokes.length - failedSmokeReports;
20125
20202
  if (productionSmokes.length === 0) {
@@ -20129,7 +20206,7 @@ var evaluateVoicePhoneCallControlEvidence = (input = {}) => {
20129
20206
  if (!setup) {
20130
20207
  issues.push("Expected phone setup report to be present.");
20131
20208
  }
20132
- for (const stage of findMissing3(lifecycleStages, input.requiredLifecycleStages)) {
20209
+ for (const stage of findMissing4(lifecycleStages, input.requiredLifecycleStages)) {
20133
20210
  issues.push(`Missing phone call-control lifecycle stage: ${stage}.`);
20134
20211
  }
20135
20212
  }
@@ -20139,10 +20216,10 @@ var evaluateVoicePhoneCallControlEvidence = (input = {}) => {
20139
20216
  if (input.maxFailedSmokeReports !== undefined && failedSmokeReports > input.maxFailedSmokeReports) {
20140
20217
  issues.push(`Expected at most ${String(input.maxFailedSmokeReports)} failing phone call-control smoke report(s), found ${String(failedSmokeReports)}.`);
20141
20218
  }
20142
- for (const provider of findMissing3(providers, input.requiredProviders)) {
20219
+ for (const provider of findMissing4(providers, input.requiredProviders)) {
20143
20220
  issues.push(`Missing phone call-control provider: ${provider}.`);
20144
20221
  }
20145
- for (const outcome of findMissing3(outcomes, input.requiredOutcomes)) {
20222
+ for (const outcome of findMissing4(outcomes, input.requiredOutcomes)) {
20146
20223
  issues.push(`Missing phone call-control outcome: ${outcome}.`);
20147
20224
  }
20148
20225
  return {
@@ -23095,7 +23172,7 @@ var statusRank = {
23095
23172
  var escapeHtml36 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
23096
23173
  var roundMetric3 = (value) => Math.round(value * 1e4) / 1e4;
23097
23174
  var rate3 = (count, total) => count / Math.max(1, total);
23098
- var uniqueSorted4 = (values) => [
23175
+ var uniqueSorted5 = (values) => [
23099
23176
  ...new Set(values.filter((value) => typeof value === "string"))
23100
23177
  ].sort();
23101
23178
  var percentile3 = (values, rank) => {
@@ -23278,7 +23355,7 @@ var buildVoiceProviderSloReport = async (options = {}) => {
23278
23355
  var evaluateVoiceProviderSloEvidence = (report, input = {}) => {
23279
23356
  const issues = [];
23280
23357
  const kindReports = Object.values(report.kinds);
23281
- const providers = uniqueSorted4(kindReports.flatMap((kind) => kind.providers));
23358
+ const providers = uniqueSorted5(kindReports.flatMap((kind) => kind.providers));
23282
23359
  const kinds = providerKinds.filter((kind) => report.kinds[kind].events > 0);
23283
23360
  const fallbacks = kindReports.reduce((total, kind) => total + kind.fallbacks, 0);
23284
23361
  const timeouts = kindReports.reduce((total, kind) => total + kind.timeouts, 0);
@@ -23999,7 +24076,7 @@ var hasPayloadValue = (payload, key, values) => {
23999
24076
  return typeof value === "string" && values.has(value);
24000
24077
  };
24001
24078
  var countIntegrationDeliveryStatus = (events, status) => events.filter((event) => event.deliveryStatus === status).length;
24002
- var uniqueSorted5 = (values) => [
24079
+ var uniqueSorted6 = (values) => [
24003
24080
  ...new Set(values.filter((value) => typeof value === "string"))
24004
24081
  ].sort();
24005
24082
  var pushMissingValuesIssue = (input) => {
@@ -24178,11 +24255,11 @@ var buildVoiceOperationsRecord = async (options) => {
24178
24255
  var evaluateVoiceOperationsRecordGuardrails = (record, input = {}) => {
24179
24256
  const issues = [];
24180
24257
  const decisions = record.guardrails.decisions;
24181
- const proofs = uniqueSorted5(decisions.map((decision) => decision.proof));
24182
- const ruleIds = uniqueSorted5(decisions.flatMap((decision) => decision.findings.map((finding) => finding.ruleId)));
24183
- const stages = uniqueSorted5(decisions.map((decision) => decision.stage));
24184
- const statuses = uniqueSorted5(decisions.map((decision) => decision.status));
24185
- const toolNames = uniqueSorted5(decisions.map((decision) => decision.toolName));
24258
+ const proofs = uniqueSorted6(decisions.map((decision) => decision.proof));
24259
+ const ruleIds = uniqueSorted6(decisions.flatMap((decision) => decision.findings.map((finding) => finding.ruleId)));
24260
+ const stages = uniqueSorted6(decisions.map((decision) => decision.stage));
24261
+ const statuses = uniqueSorted6(decisions.map((decision) => decision.status));
24262
+ const toolNames = uniqueSorted6(decisions.map((decision) => decision.toolName));
24186
24263
  const minDecisions = input.minDecisions ?? 1;
24187
24264
  if (record.guardrails.total < minDecisions) {
24188
24265
  issues.push(`Expected at least ${String(minDecisions)} guardrail decisions, found ${String(record.guardrails.total)}.`);
@@ -31419,6 +31496,7 @@ export {
31419
31496
  exportVoiceAuditTrail,
31420
31497
  evaluateVoiceTrace,
31421
31498
  evaluateVoiceToolContractEvidence,
31499
+ evaluateVoiceTelephonyWebhookNormalizationEvidence,
31422
31500
  evaluateVoiceTelephonyContract,
31423
31501
  evaluateVoiceSimulationSuiteEvidence,
31424
31502
  evaluateVoiceQuality,
@@ -31755,6 +31833,7 @@ export {
31755
31833
  buildEmptyVoiceProofTrendReport,
31756
31834
  assignVoiceOpsTask,
31757
31835
  assertVoiceToolContractEvidence,
31836
+ assertVoiceTelephonyWebhookNormalizationEvidence,
31758
31837
  assertVoiceSimulationSuiteEvidence,
31759
31838
  assertVoiceProviderStackEvidence,
31760
31839
  assertVoiceProviderSloEvidence,
@@ -65,6 +65,51 @@ export type StoredVoiceTelephonyWebhookDecision<TResult = unknown> = VoiceTeleph
65
65
  createdAt: number;
66
66
  updatedAt: number;
67
67
  };
68
+ export type VoiceTelephonyWebhookNormalizationEvidenceDecision = {
69
+ action?: VoiceTelephonyOutcomeAction | string;
70
+ applied?: boolean;
71
+ decision?: {
72
+ action?: VoiceTelephonyOutcomeAction | string;
73
+ disposition?: VoiceCallDisposition | string;
74
+ source?: VoiceTelephonyOutcomeDecision['source'] | string;
75
+ };
76
+ disposition?: VoiceCallDisposition | string;
77
+ duplicate?: boolean;
78
+ event?: VoiceTelephonyOutcomeProviderEvent;
79
+ idempotencyKey?: string;
80
+ provider?: VoiceTelephonyWebhookProvider | string;
81
+ routeResult?: unknown;
82
+ sessionId?: string;
83
+ source?: VoiceTelephonyOutcomeDecision['source'] | string;
84
+ };
85
+ export type VoiceTelephonyWebhookNormalizationEvidenceInput = {
86
+ decisions?: VoiceTelephonyWebhookNormalizationEvidenceDecision[];
87
+ maxMissingSessionIds?: number;
88
+ minApplied?: number;
89
+ minDecisions?: number;
90
+ minDuplicateIdempotencyKeys?: number;
91
+ minDuplicates?: number;
92
+ requiredActions?: VoiceTelephonyOutcomeAction[];
93
+ requiredDispositions?: VoiceCallDisposition[];
94
+ requiredDuplicateProviders?: VoiceTelephonyWebhookProvider[];
95
+ requiredProviders?: VoiceTelephonyWebhookProvider[];
96
+ requireRouteResults?: boolean;
97
+ };
98
+ export type VoiceTelephonyWebhookNormalizationEvidenceReport = {
99
+ actions: VoiceTelephonyOutcomeAction[];
100
+ applied: number;
101
+ decisions: number;
102
+ dispositions: VoiceCallDisposition[];
103
+ duplicateIdempotencyKeys: number;
104
+ duplicateProviders: VoiceTelephonyWebhookProvider[];
105
+ duplicates: number;
106
+ issues: string[];
107
+ missingSessionIds: number;
108
+ ok: boolean;
109
+ providers: VoiceTelephonyWebhookProvider[];
110
+ routeResults: number;
111
+ sources: string[];
112
+ };
68
113
  export type VoiceTelephonyWebhookIdempotencyStore<TResult = unknown> = {
69
114
  get: (key: string) => Promise<StoredVoiceTelephonyWebhookDecision<TResult> | undefined> | StoredVoiceTelephonyWebhookDecision<TResult> | undefined;
70
115
  set: (key: string, decision: StoredVoiceTelephonyWebhookDecision<TResult>) => Promise<void> | void;
@@ -139,6 +184,8 @@ export declare class VoiceTelephonyWebhookVerificationError extends Error {
139
184
  constructor(result: VoiceTelephonyWebhookVerificationResult);
140
185
  }
141
186
  export declare const createMemoryVoiceTelephonyWebhookIdempotencyStore: <TResult = unknown>() => VoiceTelephonyWebhookIdempotencyStore<TResult>;
187
+ export declare const evaluateVoiceTelephonyWebhookNormalizationEvidence: (input?: VoiceTelephonyWebhookNormalizationEvidenceInput) => VoiceTelephonyWebhookNormalizationEvidenceReport;
188
+ export declare const assertVoiceTelephonyWebhookNormalizationEvidence: (input?: VoiceTelephonyWebhookNormalizationEvidenceInput) => VoiceTelephonyWebhookNormalizationEvidenceReport;
142
189
  export declare const createVoiceTelephonyOutcomePolicy: (policy?: VoiceTelephonyOutcomePolicy) => Required<Pick<VoiceTelephonyOutcomePolicy, "completedStatuses" | "escalationStatuses" | "failedAsNoAnswer" | "failedStatuses" | "includeProviderPayload" | "machineDetectionVoicemailValues" | "noAnswerOnZeroDuration" | "noAnswerSipCodes" | "noAnswerStatuses" | "transferStatuses" | "voicemailStatuses">> & VoiceTelephonyOutcomePolicy;
143
190
  export declare const resolveVoiceTelephonyOutcome: (event: VoiceTelephonyOutcomeProviderEvent, policyInput?: VoiceTelephonyOutcomePolicy) => VoiceTelephonyOutcomeDecision;
144
191
  export declare const voiceTelephonyOutcomeToRouteResult: <TResult = unknown>(decision: VoiceTelephonyOutcomeDecision, result?: TResult) => VoiceTelephonyOutcomeRouteResult<TResult>;
@@ -8219,6 +8219,14 @@ var DEFAULT_MACHINE_VOICEMAIL_VALUES = [
8219
8219
  ];
8220
8220
  var DEFAULT_NO_ANSWER_SIP_CODES = [408, 480, 486, 487, 603];
8221
8221
  var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
8222
+ var uniqueSorted = (values) => Array.from(new Set(values)).sort();
8223
+ var findMissing = (values, required) => {
8224
+ if (!required?.length) {
8225
+ return [];
8226
+ }
8227
+ const valueSet = new Set(values);
8228
+ return required.filter((value) => !valueSet.has(value));
8229
+ };
8222
8230
 
8223
8231
  class VoiceTelephonyWebhookVerificationError extends Error {
8224
8232
  result;
@@ -8237,6 +8245,75 @@ var createMemoryVoiceTelephonyWebhookIdempotencyStore = () => {
8237
8245
  }
8238
8246
  };
8239
8247
  };
8248
+ var isTelephonyWebhookProvider = (value) => value === "generic" || value === "plivo" || value === "telnyx" || value === "twilio";
8249
+ var isTelephonyOutcomeAction = (value) => value === "complete" || value === "escalate" || value === "ignore" || value === "no-answer" || value === "transfer" || value === "voicemail";
8250
+ var isCallDisposition = (value) => value === "completed" || value === "escalated" || value === "failed" || value === "no-answer" || value === "transferred" || value === "voicemail";
8251
+ var evaluateVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
8252
+ const issues = [];
8253
+ const decisions = input.decisions ?? [];
8254
+ const actions = uniqueSorted(decisions.map((decision) => decision.decision?.action ?? decision.action).filter(isTelephonyOutcomeAction));
8255
+ const dispositions = uniqueSorted(decisions.map((decision) => decision.decision?.disposition ?? decision.disposition).filter(isCallDisposition));
8256
+ const providers = uniqueSorted(decisions.map((decision) => decision.provider ?? decision.event?.provider).filter(isTelephonyWebhookProvider));
8257
+ const sources = uniqueSorted(decisions.map((decision) => decision.decision?.source ?? decision.source).filter((source) => typeof source === "string"));
8258
+ const applied = decisions.filter((decision) => decision.applied === true).length;
8259
+ const duplicateDecisions = decisions.filter((decision) => decision.duplicate === true);
8260
+ const duplicateProviders = uniqueSorted(duplicateDecisions.map((decision) => decision.provider ?? decision.event?.provider).filter(isTelephonyWebhookProvider));
8261
+ const duplicateIdempotencyKeys = new Set(duplicateDecisions.map((decision) => decision.idempotencyKey).filter((key) => typeof key === "string" && key.length > 0)).size;
8262
+ const routeResults = decisions.filter((decision) => isRecord(decision.routeResult)).length;
8263
+ const missingSessionIds = decisions.filter((decision) => !decision.sessionId).length;
8264
+ if (input.minDecisions !== undefined && decisions.length < input.minDecisions) {
8265
+ issues.push(`Expected at least ${String(input.minDecisions)} telephony webhook decision(s), found ${String(decisions.length)}.`);
8266
+ }
8267
+ if (input.minApplied !== undefined && applied < input.minApplied) {
8268
+ issues.push(`Expected at least ${String(input.minApplied)} applied telephony webhook decision(s), found ${String(applied)}.`);
8269
+ }
8270
+ if (input.minDuplicates !== undefined && duplicateDecisions.length < input.minDuplicates) {
8271
+ issues.push(`Expected at least ${String(input.minDuplicates)} duplicate telephony webhook decision(s), found ${String(duplicateDecisions.length)}.`);
8272
+ }
8273
+ if (input.minDuplicateIdempotencyKeys !== undefined && duplicateIdempotencyKeys < input.minDuplicateIdempotencyKeys) {
8274
+ issues.push(`Expected at least ${String(input.minDuplicateIdempotencyKeys)} duplicate telephony webhook idempotency key(s), found ${String(duplicateIdempotencyKeys)}.`);
8275
+ }
8276
+ if (input.maxMissingSessionIds !== undefined && missingSessionIds > input.maxMissingSessionIds) {
8277
+ issues.push(`Expected at most ${String(input.maxMissingSessionIds)} telephony webhook decision(s) without sessionId, found ${String(missingSessionIds)}.`);
8278
+ }
8279
+ if (input.requireRouteResults && routeResults < decisions.length) {
8280
+ issues.push(`Expected every telephony webhook decision to include a route result, found ${String(routeResults)} of ${String(decisions.length)}.`);
8281
+ }
8282
+ for (const provider of findMissing(providers, input.requiredProviders)) {
8283
+ issues.push(`Missing telephony webhook provider: ${provider}.`);
8284
+ }
8285
+ for (const provider of findMissing(duplicateProviders, input.requiredDuplicateProviders)) {
8286
+ issues.push(`Missing duplicate telephony webhook provider: ${provider}.`);
8287
+ }
8288
+ for (const action of findMissing(actions, input.requiredActions)) {
8289
+ issues.push(`Missing telephony webhook action: ${action}.`);
8290
+ }
8291
+ for (const disposition of findMissing(dispositions, input.requiredDispositions)) {
8292
+ issues.push(`Missing telephony webhook disposition: ${disposition}.`);
8293
+ }
8294
+ return {
8295
+ actions,
8296
+ applied,
8297
+ decisions: decisions.length,
8298
+ dispositions,
8299
+ duplicateIdempotencyKeys,
8300
+ duplicateProviders,
8301
+ duplicates: duplicateDecisions.length,
8302
+ issues,
8303
+ missingSessionIds,
8304
+ ok: issues.length === 0,
8305
+ providers,
8306
+ routeResults,
8307
+ sources
8308
+ };
8309
+ };
8310
+ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
8311
+ const assertion = evaluateVoiceTelephonyWebhookNormalizationEvidence(input);
8312
+ if (!assertion.ok) {
8313
+ throw new Error(`Voice telephony webhook normalization evidence assertion failed: ${assertion.issues.join(" ")}`);
8314
+ }
8315
+ return assertion;
8316
+ };
8240
8317
  var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
8241
8318
  var firstString = (source, keys) => {
8242
8319
  for (const key of keys) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.264",
3
+ "version": "0.0.22-beta.266",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",