@absolutejs/voice 0.0.22-beta.285 → 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"
@@ -28166,15 +28180,17 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28166
28180
  } : undefined;
28167
28181
  const monitoringSummary = monitoring ? {
28168
28182
  criticalOpen: monitoring.summary.criticalOpen,
28183
+ elapsedMs: monitoring.elapsedMs,
28169
28184
  open: monitoring.summary.open,
28170
- status: monitoring.status,
28185
+ status: options.monitoringRunFailAfterMs !== undefined && monitoring.elapsedMs > options.monitoringRunFailAfterMs ? "fail" : monitoring.status,
28171
28186
  total: monitoring.summary.total
28172
28187
  } : undefined;
28173
28188
  const monitoringNotifierDeliverySummary = monitoringNotifierDelivery ? {
28189
+ elapsedMs: monitoringNotifierDelivery.elapsedMs,
28174
28190
  failed: monitoringNotifierDelivery.summary.failed,
28175
28191
  notifiers: monitoringNotifierDelivery.summary.notifiers,
28176
28192
  sent: monitoringNotifierDelivery.summary.sent,
28177
- status: monitoringNotifierDelivery.status,
28193
+ status: options.monitoringNotifierDeliveryFailAfterMs !== undefined && monitoringNotifierDelivery.elapsedMs > options.monitoringNotifierDeliveryFailAfterMs ? "fail" : monitoringNotifierDelivery.status,
28178
28194
  total: monitoringNotifierDelivery.summary.total
28179
28195
  } : undefined;
28180
28196
  const telephonyWebhookSecuritySummary = telephonyWebhookSecurity ? {
@@ -28184,12 +28200,17 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28184
28200
  status: telephonyWebhookSecurity.status,
28185
28201
  warned: telephonyWebhookSecurity.summary.warned
28186
28202
  } : undefined;
28187
- const reconnectContractSummary = reconnectContracts ? {
28188
- failed: reconnectContracts.filter((report) => !report.pass).length,
28189
- passed: reconnectContracts.filter((report) => report.pass).length,
28190
- status: reconnectContracts.some((report) => !report.pass) ? "fail" : reconnectContracts.length === 0 ? "warn" : "pass",
28191
- total: reconnectContracts.length
28192
- } : 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;
28193
28214
  const bargeInSummary = bargeInReports ? {
28194
28215
  failed: bargeInReports.reduce((total, report) => total + report.failed, 0),
28195
28216
  passed: bargeInReports.reduce((total, report) => total + report.passed, 0),
@@ -28406,7 +28427,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28406
28427
  }
28407
28428
  if (reconnectContractSummary) {
28408
28429
  checks.push({
28409
- 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.`,
28410
28431
  href: options.links?.reconnectContracts ?? options.links?.sessions ?? "/sessions",
28411
28432
  label: "Reconnect recovery contracts",
28412
28433
  proofSource: proofSource("reconnectContracts", "reconnect"),
@@ -28575,7 +28596,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28575
28596
  }
28576
28597
  if (monitoring && monitoringSummary) {
28577
28598
  checks.push({
28578
- 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.`,
28579
28600
  href: options.links?.monitoring ?? "/voice/monitors",
28580
28601
  label: "Monitoring issues",
28581
28602
  status: monitoringSummary.status,
@@ -28591,7 +28612,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
28591
28612
  }
28592
28613
  if (monitoringNotifierDelivery && monitoringNotifierDeliverySummary) {
28593
28614
  checks.push({
28594
- 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).`,
28595
28616
  href: options.links?.monitoringNotifierDelivery ?? "/api/voice/monitor-issues/notifications",
28596
28617
  label: "Monitor notifier delivery",
28597
28618
  status: monitoringNotifierDeliverySummary.status,
@@ -28823,7 +28844,8 @@ var createVoiceMemoryMonitorNotifierDeliveryReceiptStore = (initial = []) => {
28823
28844
  };
28824
28845
  };
28825
28846
  var buildVoiceMonitorRunReport = async (options) => {
28826
- const checkedAt = options.now ?? Date.now();
28847
+ const startedAt = Date.now();
28848
+ const checkedAt = options.now ?? startedAt;
28827
28849
  const runs = await Promise.all(options.monitors.map(async (monitor) => {
28828
28850
  const evaluation = await monitor.evaluate({
28829
28851
  evidence: options.evidence,
@@ -28863,6 +28885,7 @@ var buildVoiceMonitorRunReport = async (options) => {
28863
28885
  const criticalOpen = openIssues.filter((issue) => issue.severity === "critical").length;
28864
28886
  return {
28865
28887
  checkedAt,
28888
+ elapsedMs: Math.max(0, Date.now() - startedAt),
28866
28889
  issues,
28867
28890
  runs,
28868
28891
  status: criticalOpen > 0 ? "fail" : openIssues.length > 0 || rollupStatus4(runs) === "warn" ? "warn" : rollupStatus4(runs),
@@ -28923,7 +28946,8 @@ var createVoiceMonitorWebhookNotifier = (options) => ({
28923
28946
  }
28924
28947
  });
28925
28948
  var deliverVoiceMonitorIssueNotifications = async (options) => {
28926
- const checkedAt = options.now ?? Date.now();
28949
+ const startedAt = Date.now();
28950
+ const checkedAt = options.now ?? startedAt;
28927
28951
  const statuses = new Set(options.statuses ?? ["open"]);
28928
28952
  const issues = (await options.issueStore.list()).filter((issue) => statuses.has(issue.status));
28929
28953
  const receipts = [];
@@ -28949,6 +28973,7 @@ var deliverVoiceMonitorIssueNotifications = async (options) => {
28949
28973
  const skipped = allReceipts.filter((receipt) => receipt.status === "skipped").length;
28950
28974
  return {
28951
28975
  checkedAt,
28976
+ elapsedMs: Math.max(0, Date.now() - startedAt),
28952
28977
  receipts: allReceipts,
28953
28978
  status: failed > 0 ? "fail" : allReceipts.length === 0 ? "warn" : "pass",
28954
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.285",
3
+ "version": "0.0.22-beta.286",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",