@absolutejs/voice 0.0.22-beta.284 → 0.0.22-beta.286

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.js CHANGED
@@ -10798,6 +10798,18 @@ var findDuplicateTurnIds = (snapshots) => {
10798
10798
  }
10799
10799
  return [...duplicates].sort();
10800
10800
  };
10801
+ var percentile = (values, rank) => {
10802
+ if (values.length === 0) {
10803
+ return;
10804
+ }
10805
+ const sorted = [...values].sort((left, right) => left - right);
10806
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(rank / 100 * sorted.length) - 1));
10807
+ return sorted[index];
10808
+ };
10809
+ var getResumeLatencies = (snapshots) => snapshots.filter((snapshot) => snapshot.reconnect.status === "resumed" && typeof snapshot.reconnect.lastResumedAt === "number").map((snapshot) => {
10810
+ const previousReconnect = snapshots.filter((candidate) => candidate.at <= snapshot.at && candidate.reconnect.status === "reconnecting" && typeof candidate.reconnect.lastDisconnectAt === "number").at(-1);
10811
+ return previousReconnect?.reconnect.lastDisconnectAt === undefined ? undefined : snapshot.reconnect.lastResumedAt - previousReconnect.reconnect.lastDisconnectAt;
10812
+ }).filter((value) => typeof value === "number" && value >= 0);
10801
10813
  var runVoiceReconnectContract = (options) => {
10802
10814
  const snapshots = [...options.snapshots].sort((left, right) => left.at - right.at);
10803
10815
  const issues = [];
@@ -10808,6 +10820,7 @@ var runVoiceReconnectContract = (options) => {
10808
10820
  const resumed = statuses.includes("resumed");
10809
10821
  const exhausted = statuses.includes("exhausted");
10810
10822
  const duplicateTurnIds = findDuplicateTurnIds(snapshots);
10823
+ const resumeLatencyP95Ms = percentile(getResumeLatencies(snapshots), 95);
10811
10824
  const requireReconnect = options.requireReconnect ?? true;
10812
10825
  const requireResume = options.requireResume ?? true;
10813
10826
  const requireReplayProtection = options.requireReplayProtection ?? true;
@@ -10861,6 +10874,7 @@ var runVoiceReconnectContract = (options) => {
10861
10874
  checkedAt: Date.now(),
10862
10875
  issues,
10863
10876
  pass,
10877
+ resumeLatencyP95Ms,
10864
10878
  snapshotCount: snapshots.length,
10865
10879
  statuses,
10866
10880
  summary: {
@@ -12628,7 +12642,7 @@ var DEFAULT_WARN_RATIO = 0.8;
12628
12642
  var DEFAULT_MIN_PASSING_RUNS = 3;
12629
12643
  var roundMs = (value) => Math.max(1, Math.ceil(value));
12630
12644
  var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
12631
- var percentile = (values, rank) => {
12645
+ var percentile2 = (values, rank) => {
12632
12646
  if (values.length === 0) {
12633
12647
  return;
12634
12648
  }
@@ -12651,7 +12665,7 @@ var normalizeSample = (input) => {
12651
12665
  return input;
12652
12666
  };
12653
12667
  var createThreshold = (metric, values, options) => {
12654
- const baselineP95Ms = percentile(values, 95);
12668
+ const baselineP95Ms = percentile2(values, 95);
12655
12669
  const maxObservedMs = values.length > 0 ? Math.max(...values) : undefined;
12656
12670
  const recommendedMs = baselineP95Ms === undefined ? undefined : roundMs(Math.max(options.minimumMs, baselineP95Ms * options.headroomMultiplier));
12657
12671
  const warnAfterMs = recommendedMs === undefined ? undefined : roundMs(Math.max(options.minimumMs, recommendedMs * options.warnRatio));
@@ -17030,7 +17044,7 @@ var createVoiceTurnLatencyRoutes = (options) => {
17030
17044
  // src/liveLatency.ts
17031
17045
  import { Elysia as Elysia27 } from "elysia";
17032
17046
  var escapeHtml26 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
17033
- var percentile2 = (values, percentileValue) => {
17047
+ var percentile3 = (values, percentileValue) => {
17034
17048
  if (values.length === 0) {
17035
17049
  return;
17036
17050
  }
@@ -17066,8 +17080,8 @@ var summarizeVoiceLiveLatency = async (options) => {
17066
17080
  averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
17067
17081
  checkedAt: Date.now(),
17068
17082
  failed,
17069
- p50LatencyMs: percentile2(latencies, 50),
17070
- p95LatencyMs: percentile2(latencies, 95),
17083
+ p50LatencyMs: percentile3(latencies, 50),
17084
+ p95LatencyMs: percentile3(latencies, 95),
17071
17085
  recent,
17072
17086
  status: latencies.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
17073
17087
  total: latencies.length,
@@ -17143,7 +17157,7 @@ var TRACE_TYPES = [
17143
17157
  ];
17144
17158
  var getNumber6 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
17145
17159
  var getString12 = (value) => typeof value === "string" && value.trim() ? value : undefined;
17146
- var percentile3 = (values, percentileValue) => {
17160
+ var percentile4 = (values, percentileValue) => {
17147
17161
  if (values.length === 0) {
17148
17162
  return;
17149
17163
  }
@@ -17351,8 +17365,8 @@ var summarizeStage = (stage, measurements, options) => {
17351
17365
  label: STAGE_LABELS[stage],
17352
17366
  maxMs: latencies.length > 0 ? Math.max(...latencies) : undefined,
17353
17367
  measurements: stageMeasurements,
17354
- p50Ms: percentile3(latencies, 50),
17355
- p95Ms: percentile3(latencies, 95),
17368
+ p50Ms: percentile4(latencies, 50),
17369
+ p95Ms: percentile4(latencies, 95),
17356
17370
  stage,
17357
17371
  status: stageMeasurements.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
17358
17372
  total: stageMeasurements.length,
@@ -23790,7 +23804,7 @@ var rate3 = (count, total) => count / Math.max(1, total);
23790
23804
  var uniqueSorted5 = (values) => [
23791
23805
  ...new Set(values.filter((value) => typeof value === "string"))
23792
23806
  ].sort();
23793
- var percentile4 = (values, rank) => {
23807
+ var percentile5 = (values, rank) => {
23794
23808
  if (values.length === 0) {
23795
23809
  return 0;
23796
23810
  }
@@ -23848,7 +23862,7 @@ var summarizeKind = (kind, events, thresholds, required) => {
23848
23862
  unit: "rate"
23849
23863
  }),
23850
23864
  p95ElapsedMs: createMetric2({
23851
- actual: percentile4(latencies, 95),
23865
+ actual: percentile5(latencies, 95),
23852
23866
  label: "P95 latency",
23853
23867
  threshold: thresholds.maxP95ElapsedMs,
23854
23868
  unit: "ms"
@@ -27857,6 +27871,7 @@ var voiceOperationsRecordHref = (base, sessionId) => {
27857
27871
  };
27858
27872
  var buildOperationsRecordLinks = (input) => {
27859
27873
  const failedSessionSet = new Set(input.failedSessionIds);
27874
+ const minLiveLatencyAt = typeof input.liveLatencyMaxAgeMs === "number" && Number.isFinite(input.liveLatencyMaxAgeMs) && input.liveLatencyMaxAgeMs > 0 ? Date.now() - input.liveLatencyMaxAgeMs : undefined;
27860
27875
  const providerErrors = input.events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string")).map((event) => ({
27861
27876
  detail: getString18(event.payload.error),
27862
27877
  href: voiceOperationsRecordHref(input.base, event.sessionId),
@@ -27864,7 +27879,7 @@ var buildOperationsRecordLinks = (input) => {
27864
27879
  sessionId: event.sessionId,
27865
27880
  status: "fail"
27866
27881
  }));
27867
- const failingLatency = input.events.filter((event) => event.type === "client.live_latency").map((event) => ({
27882
+ const failingLatency = input.events.filter((event) => event.type === "client.live_latency" && (minLiveLatencyAt === undefined || event.at >= minLiveLatencyAt)).map((event) => ({
27868
27883
  event,
27869
27884
  latencyMs: getNumber10(event.payload.latencyMs) ?? getNumber10(event.payload.elapsedMs)
27870
27885
  })).filter((entry) => entry.latencyMs !== undefined && entry.latencyMs > input.liveLatencyWarnAfterMs).map(({ event, latencyMs }) => ({
@@ -27973,6 +27988,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
27973
27988
  events,
27974
27989
  failedSessionIds: failedSessionItems.map((session) => session.sessionId),
27975
27990
  liveLatencyFailAfterMs: options.liveLatencyFailAfterMs ?? 3200,
27991
+ liveLatencyMaxAgeMs: options.liveLatencyMaxAgeMs,
27976
27992
  liveLatencyWarnAfterMs: options.liveLatencyWarnAfterMs ?? 1800
27977
27993
  });
27978
27994
  const checks = [
@@ -28164,15 +28180,17 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28164
28180
  } : undefined;
28165
28181
  const monitoringSummary = monitoring ? {
28166
28182
  criticalOpen: monitoring.summary.criticalOpen,
28183
+ elapsedMs: monitoring.elapsedMs,
28167
28184
  open: monitoring.summary.open,
28168
- status: monitoring.status,
28185
+ status: options.monitoringRunFailAfterMs !== undefined && monitoring.elapsedMs > options.monitoringRunFailAfterMs ? "fail" : monitoring.status,
28169
28186
  total: monitoring.summary.total
28170
28187
  } : undefined;
28171
28188
  const monitoringNotifierDeliverySummary = monitoringNotifierDelivery ? {
28189
+ elapsedMs: monitoringNotifierDelivery.elapsedMs,
28172
28190
  failed: monitoringNotifierDelivery.summary.failed,
28173
28191
  notifiers: monitoringNotifierDelivery.summary.notifiers,
28174
28192
  sent: monitoringNotifierDelivery.summary.sent,
28175
- status: monitoringNotifierDelivery.status,
28193
+ status: options.monitoringNotifierDeliveryFailAfterMs !== undefined && monitoringNotifierDelivery.elapsedMs > options.monitoringNotifierDeliveryFailAfterMs ? "fail" : monitoringNotifierDelivery.status,
28176
28194
  total: monitoringNotifierDelivery.summary.total
28177
28195
  } : undefined;
28178
28196
  const telephonyWebhookSecuritySummary = telephonyWebhookSecurity ? {
@@ -28182,12 +28200,17 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28182
28200
  status: telephonyWebhookSecurity.status,
28183
28201
  warned: telephonyWebhookSecurity.summary.warned
28184
28202
  } : undefined;
28185
- const reconnectContractSummary = reconnectContracts ? {
28186
- failed: reconnectContracts.filter((report) => !report.pass).length,
28187
- passed: reconnectContracts.filter((report) => report.pass).length,
28188
- status: reconnectContracts.some((report) => !report.pass) ? "fail" : reconnectContracts.length === 0 ? "warn" : "pass",
28189
- total: reconnectContracts.length
28190
- } : undefined;
28203
+ const reconnectContractSummary = reconnectContracts ? (() => {
28204
+ const failedReports = reconnectContracts.filter((report) => !report.pass || options.reconnectResumeFailAfterMs !== undefined && report.resumeLatencyP95Ms !== undefined && report.resumeLatencyP95Ms > options.reconnectResumeFailAfterMs);
28205
+ const resumeLatencies = reconnectContracts.map((report) => report.resumeLatencyP95Ms).filter((value) => typeof value === "number");
28206
+ return {
28207
+ failed: failedReports.length,
28208
+ passed: reconnectContracts.length - failedReports.length,
28209
+ resumeLatencyP95Ms: resumeLatencies.length > 0 ? Math.max(...resumeLatencies) : undefined,
28210
+ status: failedReports.length > 0 ? "fail" : reconnectContracts.length === 0 ? "warn" : "pass",
28211
+ total: reconnectContracts.length
28212
+ };
28213
+ })() : undefined;
28191
28214
  const bargeInSummary = bargeInReports ? {
28192
28215
  failed: bargeInReports.reduce((total, report) => total + report.failed, 0),
28193
28216
  passed: bargeInReports.reduce((total, report) => total + report.passed, 0),
@@ -28404,7 +28427,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28404
28427
  }
28405
28428
  if (reconnectContractSummary) {
28406
28429
  checks.push({
28407
- detail: reconnectContractSummary.status === "pass" ? `${reconnectContractSummary.passed} reconnect contract(s) are passing.` : reconnectContractSummary.total === 0 ? "No reconnect contracts are configured." : `${reconnectContractSummary.failed} reconnect contract(s) failed.`,
28430
+ detail: reconnectContractSummary.status === "pass" ? `${reconnectContractSummary.passed} reconnect contract(s) are passing.` : reconnectContractSummary.total === 0 ? "No reconnect contracts are configured." : options.reconnectResumeFailAfterMs !== undefined && reconnectContractSummary.resumeLatencyP95Ms !== undefined && reconnectContractSummary.resumeLatencyP95Ms > options.reconnectResumeFailAfterMs ? `Reconnect resume p95 ${reconnectContractSummary.resumeLatencyP95Ms}ms exceeded ${options.reconnectResumeFailAfterMs}ms.` : `${reconnectContractSummary.failed} reconnect contract(s) failed.`,
28408
28431
  href: options.links?.reconnectContracts ?? options.links?.sessions ?? "/sessions",
28409
28432
  label: "Reconnect recovery contracts",
28410
28433
  proofSource: proofSource("reconnectContracts", "reconnect"),
@@ -28573,7 +28596,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28573
28596
  }
28574
28597
  if (monitoring && monitoringSummary) {
28575
28598
  checks.push({
28576
- detail: monitoringSummary.status === "pass" ? `${monitoringSummary.total} monitor(s) are passing with no open issues.` : `${monitoringSummary.open} monitor issue(s) open, ${monitoringSummary.criticalOpen} critical.`,
28599
+ detail: monitoringSummary.status === "pass" ? `${monitoringSummary.total} monitor(s) are passing with no open issues.` : options.monitoringRunFailAfterMs !== undefined && monitoringSummary.elapsedMs !== undefined && monitoringSummary.elapsedMs > options.monitoringRunFailAfterMs ? `Monitor run took ${monitoringSummary.elapsedMs}ms, above ${options.monitoringRunFailAfterMs}ms.` : `${monitoringSummary.open} monitor issue(s) open, ${monitoringSummary.criticalOpen} critical.`,
28577
28600
  href: options.links?.monitoring ?? "/voice/monitors",
28578
28601
  label: "Monitoring issues",
28579
28602
  status: monitoringSummary.status,
@@ -28589,7 +28612,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28589
28612
  }
28590
28613
  if (monitoringNotifierDelivery && monitoringNotifierDeliverySummary) {
28591
28614
  checks.push({
28592
- detail: monitoringNotifierDeliverySummary.status === "pass" ? `${monitoringNotifierDeliverySummary.sent} monitor notification(s) delivered.` : `${monitoringNotifierDeliverySummary.failed} monitor notification delivery failure(s).`,
28615
+ detail: monitoringNotifierDeliverySummary.status === "pass" ? `${monitoringNotifierDeliverySummary.sent} monitor notification(s) delivered.` : options.monitoringNotifierDeliveryFailAfterMs !== undefined && monitoringNotifierDeliverySummary.elapsedMs !== undefined && monitoringNotifierDeliverySummary.elapsedMs > options.monitoringNotifierDeliveryFailAfterMs ? `Monitor notification delivery took ${monitoringNotifierDeliverySummary.elapsedMs}ms, above ${options.monitoringNotifierDeliveryFailAfterMs}ms.` : `${monitoringNotifierDeliverySummary.failed} monitor notification delivery failure(s).`,
28593
28616
  href: options.links?.monitoringNotifierDelivery ?? "/api/voice/monitor-issues/notifications",
28594
28617
  label: "Monitor notifier delivery",
28595
28618
  status: monitoringNotifierDeliverySummary.status,
@@ -28821,7 +28844,8 @@ var createVoiceMemoryMonitorNotifierDeliveryReceiptStore = (initial = []) => {
28821
28844
  };
28822
28845
  };
28823
28846
  var buildVoiceMonitorRunReport = async (options) => {
28824
- const checkedAt = options.now ?? Date.now();
28847
+ const startedAt = Date.now();
28848
+ const checkedAt = options.now ?? startedAt;
28825
28849
  const runs = await Promise.all(options.monitors.map(async (monitor) => {
28826
28850
  const evaluation = await monitor.evaluate({
28827
28851
  evidence: options.evidence,
@@ -28861,6 +28885,7 @@ var buildVoiceMonitorRunReport = async (options) => {
28861
28885
  const criticalOpen = openIssues.filter((issue) => issue.severity === "critical").length;
28862
28886
  return {
28863
28887
  checkedAt,
28888
+ elapsedMs: Math.max(0, Date.now() - startedAt),
28864
28889
  issues,
28865
28890
  runs,
28866
28891
  status: criticalOpen > 0 ? "fail" : openIssues.length > 0 || rollupStatus4(runs) === "warn" ? "warn" : rollupStatus4(runs),
@@ -28921,7 +28946,8 @@ var createVoiceMonitorWebhookNotifier = (options) => ({
28921
28946
  }
28922
28947
  });
28923
28948
  var deliverVoiceMonitorIssueNotifications = async (options) => {
28924
- const checkedAt = options.now ?? Date.now();
28949
+ const startedAt = Date.now();
28950
+ const checkedAt = options.now ?? startedAt;
28925
28951
  const statuses = new Set(options.statuses ?? ["open"]);
28926
28952
  const issues = (await options.issueStore.list()).filter((issue) => statuses.has(issue.status));
28927
28953
  const receipts = [];
@@ -28947,6 +28973,7 @@ var deliverVoiceMonitorIssueNotifications = async (options) => {
28947
28973
  const skipped = allReceipts.filter((receipt) => receipt.status === "skipped").length;
28948
28974
  return {
28949
28975
  checkedAt,
28976
+ elapsedMs: Math.max(0, Date.now() - startedAt),
28950
28977
  receipts: allReceipts,
28951
28978
  status: failed > 0 ? "fail" : allReceipts.length === 0 ? "warn" : "pass",
28952
28979
  summary: {
@@ -181,11 +181,13 @@ export type VoiceProductionReadinessReport = {
181
181
  };
182
182
  monitoring?: {
183
183
  criticalOpen: number;
184
+ elapsedMs?: number;
184
185
  open: number;
185
186
  status: VoiceProductionReadinessStatus;
186
187
  total: number;
187
188
  };
188
189
  monitoringNotifierDelivery?: {
190
+ elapsedMs?: number;
189
191
  failed: number;
190
192
  notifiers: number;
191
193
  sent: number;
@@ -259,6 +261,7 @@ export type VoiceProductionReadinessReport = {
259
261
  reconnectContracts?: {
260
262
  failed: number;
261
263
  passed: number;
264
+ resumeLatencyP95Ms?: number;
262
265
  status: VoiceProductionReadinessStatus;
263
266
  total: number;
264
267
  };
@@ -482,6 +485,9 @@ export type VoiceProductionReadinessRoutesOptions = {
482
485
  liveLatencyWarnAfterMs?: number;
483
486
  liveLatencyFailAfterMs?: number;
484
487
  liveLatencyMaxAgeMs?: number;
488
+ monitoringRunFailAfterMs?: number;
489
+ monitoringNotifierDeliveryFailAfterMs?: number;
490
+ reconnectResumeFailAfterMs?: number;
485
491
  };
486
492
  export declare const summarizeVoiceProductionReadinessGate: (report: VoiceProductionReadinessReport, options?: VoiceProductionReadinessGateOptions) => VoiceProductionReadinessGateReport;
487
493
  export declare const evaluateVoiceProductionReadinessEvidence: (report: VoiceProductionReadinessReport, input?: VoiceProductionReadinessAssertionInput) => VoiceProductionReadinessAssertionReport;
@@ -15,6 +15,7 @@ export type VoiceReconnectContractReport = {
15
15
  checkedAt: number;
16
16
  issues: VoiceReconnectContractIssue[];
17
17
  pass: boolean;
18
+ resumeLatencyP95Ms?: number;
18
19
  snapshotCount: number;
19
20
  statuses: VoiceReconnectClientState['status'][];
20
21
  summary: {
@@ -83,6 +83,7 @@ export type VoiceMonitorNotifierDeliveryReceiptStore = {
83
83
  };
84
84
  export type VoiceMonitorNotifierDeliveryReport = {
85
85
  checkedAt: number;
86
+ elapsedMs: number;
86
87
  receipts: VoiceMonitorNotifierDeliveryReceipt[];
87
88
  status: VoiceMonitorStatus;
88
89
  summary: {
@@ -95,6 +96,7 @@ export type VoiceMonitorNotifierDeliveryReport = {
95
96
  };
96
97
  export type VoiceMonitorRunReport = {
97
98
  checkedAt: number;
99
+ elapsedMs: number;
98
100
  issues: VoiceMonitorIssue[];
99
101
  runs: VoiceMonitorRun[];
100
102
  status: VoiceMonitorStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.284",
3
+ "version": "0.0.22-beta.286",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",