@absolutejs/voice 0.0.22-beta.134 → 0.0.22-beta.136

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
@@ -12447,6 +12447,26 @@ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&l
12447
12447
  var increment4 = (record, key) => {
12448
12448
  record[key] = (record[key] ?? 0) + 1;
12449
12449
  };
12450
+ var isProviderErrorEvent = (event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string");
12451
+ var isRecoveredProviderFallbackEvent = (event) => event.type === "session.error" && (event.payload.providerStatus === "fallback" || event.payload.status === "fallback") && event.payload.recovered === true;
12452
+ var turnRecoveryKey = (event) => event.turnId ?? `session:${event.sessionId}`;
12453
+ var summarizeVoiceProviderFallbackRecovery = (events) => {
12454
+ const sorted = filterVoiceTraceEvents(events);
12455
+ const recoveredEvents = sorted.filter(isRecoveredProviderFallbackEvent);
12456
+ const recoveredKeys = new Set(recoveredEvents.map((event) => turnRecoveryKey(event)));
12457
+ const recoveredSessions = new Set(recoveredEvents.map((event) => event.sessionId));
12458
+ const unresolvedErrorEvents = sorted.filter((event) => isProviderErrorEvent(event) && !recoveredKeys.has(turnRecoveryKey(event)));
12459
+ const unresolvedSessions = new Set(unresolvedErrorEvents.map((event) => event.sessionId));
12460
+ return {
12461
+ recovered: recoveredEvents.length,
12462
+ recoveredSessions: recoveredSessions.size,
12463
+ recoveredTurns: recoveredKeys.size,
12464
+ status: unresolvedErrorEvents.length > 0 ? "fail" : "pass",
12465
+ total: recoveredEvents.length + unresolvedErrorEvents.length,
12466
+ unresolvedErrors: unresolvedErrorEvents.length,
12467
+ unresolvedSessions: unresolvedSessions.size
12468
+ };
12469
+ };
12450
12470
  var buildReplayTurns = (events) => {
12451
12471
  const turns = new Map;
12452
12472
  const getTurn = (turnId) => {
@@ -12547,12 +12567,13 @@ var summarizeVoiceSessions = async (options = {}) => {
12547
12567
  const providers = new Set;
12548
12568
  let latestOutcome;
12549
12569
  let errorCount = 0;
12570
+ const recoveredTurns = new Set(sorted.filter(isRecoveredProviderFallbackEvent).map((event) => turnRecoveryKey(event)));
12550
12571
  for (const event of sorted) {
12551
12572
  const provider = getString9(event.payload.provider);
12552
12573
  if (provider) {
12553
12574
  providers.add(provider);
12554
12575
  }
12555
- if (event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string")) {
12576
+ if (isProviderErrorEvent(event) && !recoveredTurns.has(turnRecoveryKey(event))) {
12556
12577
  errorCount += 1;
12557
12578
  increment4(providerErrors, provider ?? "unknown");
12558
12579
  }
@@ -19641,6 +19662,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
19641
19662
  const routingEvents = listVoiceRoutingEvents(events);
19642
19663
  const routingSessions = summarizeVoiceRoutingSessions(routingEvents);
19643
19664
  const liveLatency = summarizeLiveLatency(events, options);
19665
+ const providerRecovery = summarizeVoiceProviderFallbackRecovery(events);
19644
19666
  const [
19645
19667
  quality,
19646
19668
  providers,
@@ -19714,6 +19736,20 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
19714
19736
  }
19715
19737
  ] : []
19716
19738
  },
19739
+ {
19740
+ detail: providerRecovery.unresolvedErrors > 0 ? `${providerRecovery.unresolvedErrors} provider error(s) have no recovered fallback evidence.` : providerRecovery.recovered > 0 ? `${providerRecovery.recovered} provider fallback recovery event(s) kept sessions healthy.` : "No provider fallback recovery was needed in the current trace window.",
19741
+ href: options.links?.resilience ?? "/resilience",
19742
+ label: "Provider fallback recovery",
19743
+ status: providerRecovery.status,
19744
+ value: providerRecovery.total === 0 ? "0 events" : `${providerRecovery.recovered}/${providerRecovery.total}`,
19745
+ actions: providerRecovery.status === "pass" ? [] : [
19746
+ {
19747
+ description: "Open provider resilience traces and inspect unresolved provider errors.",
19748
+ href: options.links?.resilience ?? "/resilience",
19749
+ label: "Open provider recovery"
19750
+ }
19751
+ ]
19752
+ },
19717
19753
  {
19718
19754
  detail: failedSessions === 0 ? sessions.length > 0 ? "Recent sessions have no recorded provider/session failures." : "No sessions have been recorded yet; run a smoke or live session for proof." : `${failedSessions} recent session(s) have failures.`,
19719
19755
  href: options.links?.sessions ?? "/sessions",
@@ -20003,6 +20039,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
20003
20039
  degraded: degradedProviders,
20004
20040
  total: providers.length
20005
20041
  },
20042
+ providerRecovery,
20006
20043
  phoneAgentSmokes: phoneAgentSmokeSummary,
20007
20044
  providerRoutingContracts: providerRoutingContractSummary,
20008
20045
  reconnectContracts: reconnectContractSummary,
@@ -20281,6 +20318,7 @@ var summarizeVoiceOpsStatus = async (options) => {
20281
20318
  events
20282
20319
  })
20283
20320
  ]);
20321
+ const providerRecovery = shouldInclude("providerRecovery") ? summarizeVoiceProviderFallbackRecovery(events) : undefined;
20284
20322
  const surfaces = {};
20285
20323
  const statuses = [];
20286
20324
  if (quality) {
@@ -20301,6 +20339,10 @@ var summarizeVoiceOpsStatus = async (options) => {
20301
20339
  };
20302
20340
  statuses.push(status);
20303
20341
  }
20342
+ if (providerRecovery) {
20343
+ surfaces.providerRecovery = providerRecovery;
20344
+ statuses.push(providerRecovery.status);
20345
+ }
20304
20346
  if (sessions) {
20305
20347
  const failed = sessions.filter((session) => session.status === "failed").length;
20306
20348
  const status = failed > 0 ? "fail" : "pass";
@@ -20334,7 +20376,7 @@ var escapeHtml34 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&l
20334
20376
  var renderVoiceOpsStatusHTML = (report, options = {}) => {
20335
20377
  const title = options.title ?? "AbsoluteJS Voice Ops Status";
20336
20378
  const surfaces = Object.entries(report.surfaces).map(([key, surface]) => {
20337
- const value = "total" in surface ? `${Math.max(surface.total - ("failed" in surface ? surface.failed : ("degraded" in surface) ? surface.degraded : 0), 0)}/${surface.total}` : surface.status;
20379
+ const value = "recovered" in surface ? surface.total === 0 ? "0 events" : `${surface.recovered}/${surface.total}` : ("total" in surface) ? `${Math.max(surface.total - ("failed" in surface ? surface.failed : ("degraded" in surface) ? surface.degraded : 0), 0)}/${surface.total}` : surface.status;
20338
20380
  return `<article class="surface ${escapeHtml34(surface.status)}"><span>${escapeHtml34(surface.status.toUpperCase())}</span><h2>${escapeHtml34(key)}</h2><strong>${escapeHtml34(value)}</strong></article>`;
20339
20381
  }).join("");
20340
20382
  return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml34(title)}</title><style>body{background:#0d141b;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:980px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.surfaces{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.surface{background:#151d26;border:1px solid #283544;border-radius:20px;padding:18px}.surface span{color:#aab5c0;font-size:.78rem;font-weight:900;letter-spacing:.08em}.surface strong{font-size:1.5rem}.pass{border-color:rgba(34,197,94,.55)}.fail{border-color:rgba(239,68,68,.75)}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Ops status</p><h1>${escapeHtml34(title)}</h1><p>Compact pass/fail status for framework widgets, demos, and small customer-facing health badges.</p><p class="status ${escapeHtml34(report.status)}">Overall: ${escapeHtml34(report.status.toUpperCase())}</p><p>${report.passed}/${report.total} checks passing</p></section><section class="surfaces">${surfaces || '<article class="surface pass"><span>PASS</span><h2>No checks configured</h2><strong>0/0</strong></article>'}</section></main></body></html>`;
@@ -1,5 +1,6 @@
1
1
  import { type VoiceEvalLink, type VoiceEvalRoutesOptions } from './evalRoutes';
2
2
  import { type VoiceQualityRoutesOptions } from './qualityRoutes';
3
+ import { type VoiceProviderFallbackRecoverySummary } from './sessionReplay';
3
4
  import { type VoiceTraceEventStore } from './trace';
4
5
  export type VoiceOpsStatus = 'pass' | 'fail';
5
6
  export type VoiceOpsStatusLink = VoiceEvalLink & {
@@ -11,6 +12,7 @@ export type VoiceOpsStatusOptions<TProvider extends string = string> = {
11
12
  include?: {
12
13
  handoffs?: boolean;
13
14
  providers?: boolean;
15
+ providerRecovery?: boolean;
14
16
  quality?: boolean;
15
17
  sessions?: boolean;
16
18
  workflows?: boolean;
@@ -40,6 +42,7 @@ export type VoiceOpsStatusReport = {
40
42
  status: VoiceOpsStatus;
41
43
  total: number;
42
44
  };
45
+ providerRecovery?: VoiceProviderFallbackRecoverySummary;
43
46
  quality?: {
44
47
  status: VoiceOpsStatus;
45
48
  };
@@ -1,4 +1,5 @@
1
1
  import { Elysia } from 'elysia';
2
+ import { type VoiceProviderFallbackRecoverySummary } from './sessionReplay';
2
3
  import { type VoiceTelephonyCarrierMatrixInput } from './telephony/matrix';
3
4
  import type { VoiceTraceEventStore } from './trace';
4
5
  import type { VoiceTraceSinkDeliveryStore } from './trace';
@@ -83,6 +84,7 @@ export type VoiceProductionReadinessReport = {
83
84
  degraded: number;
84
85
  total: number;
85
86
  };
87
+ providerRecovery: VoiceProviderFallbackRecoverySummary;
86
88
  phoneAgentSmokes?: {
87
89
  failed: number;
88
90
  passed: number;
@@ -43,6 +43,15 @@ export type VoiceSessionListItem = {
43
43
  transcriptCount: number;
44
44
  turnCount: number;
45
45
  };
46
+ export type VoiceProviderFallbackRecoverySummary = {
47
+ recovered: number;
48
+ recoveredSessions: number;
49
+ recoveredTurns: number;
50
+ status: 'fail' | 'pass';
51
+ total: number;
52
+ unresolvedErrors: number;
53
+ unresolvedSessions: number;
54
+ };
46
55
  export type VoiceSessionListOptions = {
47
56
  events?: StoredVoiceTraceEvent[];
48
57
  limit?: number;
@@ -78,6 +87,7 @@ export type VoiceSessionReplayRoutesOptions = VoiceSessionReplayHTMLHandlerOptio
78
87
  name?: string;
79
88
  path?: string;
80
89
  };
90
+ export declare const summarizeVoiceProviderFallbackRecovery: (events: StoredVoiceTraceEvent[]) => VoiceProviderFallbackRecoverySummary;
81
91
  export declare const summarizeVoiceSessionReplay: (options: VoiceSessionReplayOptions) => Promise<VoiceSessionReplay>;
82
92
  export declare const summarizeVoiceSessions: (options?: VoiceSessionListOptions) => Promise<VoiceSessionListItem[]>;
83
93
  export declare const renderVoiceSessionsHTML: (sessions: VoiceSessionListItem[]) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.134",
3
+ "version": "0.0.22-beta.136",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",