@absolutejs/voice 0.0.22-beta.100 → 0.0.22-beta.102

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.
@@ -1758,6 +1758,23 @@ var createVoiceLiveTurnLatencyMonitor = (options = {}) => {
1758
1758
  listener();
1759
1759
  }
1760
1760
  };
1761
+ const reportCompletedEvent = async (event) => {
1762
+ await options.onComplete?.(event);
1763
+ if (!options.reportPath) {
1764
+ return;
1765
+ }
1766
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1767
+ const response = await fetchImpl(options.reportPath, {
1768
+ body: JSON.stringify(event),
1769
+ headers: {
1770
+ "content-type": "application/json"
1771
+ },
1772
+ method: "POST"
1773
+ });
1774
+ if (!response.ok) {
1775
+ throw new Error(`Voice live turn latency report failed: HTTP ${response.status}`);
1776
+ }
1777
+ };
1761
1778
  const completePending = (input) => {
1762
1779
  if (!pending) {
1763
1780
  return;
@@ -1773,6 +1790,7 @@ var createVoiceLiveTurnLatencyMonitor = (options = {}) => {
1773
1790
  status
1774
1791
  };
1775
1792
  events = [pending, ...events].slice(0, maxEvents);
1793
+ reportCompletedEvent(pending).catch(() => {});
1776
1794
  pending = undefined;
1777
1795
  emit();
1778
1796
  };
@@ -26,7 +26,10 @@ export type VoiceLiveTurnLatencySnapshot = {
26
26
  export type VoiceLiveTurnLatencyMonitorOptions = {
27
27
  clock?: () => number;
28
28
  failAfterMs?: number;
29
+ fetch?: typeof fetch;
29
30
  maxEvents?: number;
31
+ onComplete?: (event: VoiceLiveTurnLatencyEvent) => Promise<void> | void;
32
+ reportPath?: string;
30
33
  speechThreshold?: number;
31
34
  warnAfterMs?: number;
32
35
  };
package/dist/index.js CHANGED
@@ -9744,12 +9744,28 @@ var resolveCarriers = async (options, input) => {
9744
9744
  providers: [...providers]
9745
9745
  });
9746
9746
  };
9747
+ var summarizeLiveLatency = (events, options) => {
9748
+ const warnAfterMs = options.liveLatencyWarnAfterMs ?? 1800;
9749
+ const failAfterMs = options.liveLatencyFailAfterMs ?? 3200;
9750
+ const latencies = events.filter((event) => event.type === "client.live_latency").map((event) => typeof event.payload.latencyMs === "number" ? event.payload.latencyMs : typeof event.payload.elapsedMs === "number" ? event.payload.elapsedMs : undefined).filter((value) => typeof value === "number");
9751
+ const failed = latencies.filter((value) => value > failAfterMs).length;
9752
+ const warnings = latencies.filter((value) => value > warnAfterMs && value <= failAfterMs).length;
9753
+ const averageLatencyMs = latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined;
9754
+ return {
9755
+ averageLatencyMs,
9756
+ failed,
9757
+ status: latencies.length === 0 ? "warn" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
9758
+ total: latencies.length,
9759
+ warnings
9760
+ };
9761
+ };
9747
9762
  var buildVoiceProductionReadinessReport = async (options, input = {}) => {
9748
9763
  const request = input.request ?? new Request("http://localhost/");
9749
9764
  const query = input.query ?? {};
9750
9765
  const events = await options.store.list();
9751
9766
  const routingEvents = listVoiceRoutingEvents(events);
9752
9767
  const routingSessions = summarizeVoiceRoutingSessions(routingEvents);
9768
+ const liveLatency = summarizeLiveLatency(events, options);
9753
9769
  const [quality, providers, sessions, handoffs, carriers] = await Promise.all([
9754
9770
  evaluateVoiceQuality({ events }),
9755
9771
  Promise.all([
@@ -9856,6 +9872,20 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
9856
9872
  ]
9857
9873
  }
9858
9874
  ];
9875
+ checks.push({
9876
+ detail: liveLatency.total === 0 ? "No browser live-latency measurements are recorded yet." : liveLatency.status === "pass" ? `Live browser turn latency averages ${liveLatency.averageLatencyMs}ms.` : `${liveLatency.failed} failed and ${liveLatency.warnings} warned live-latency measurement(s).`,
9877
+ href: options.links?.liveLatency ?? "/traces",
9878
+ label: "Live latency proof",
9879
+ status: liveLatency.status,
9880
+ value: liveLatency.averageLatencyMs === undefined ? `${liveLatency.total} samples` : `${liveLatency.averageLatencyMs}ms avg`,
9881
+ actions: liveLatency.status === "pass" ? [] : [
9882
+ {
9883
+ description: "Run a live browser voice turn and inspect the persisted latency trace.",
9884
+ href: options.links?.liveLatency ?? "/traces",
9885
+ label: "Open live latency traces"
9886
+ }
9887
+ ]
9888
+ });
9859
9889
  const carrierSummary = carriers ? {
9860
9890
  failing: carriers.summary.failing,
9861
9891
  providers: carriers.summary.providers,
@@ -9886,6 +9916,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
9886
9916
  carriers: "/carriers",
9887
9917
  handoffs: "/handoffs",
9888
9918
  handoffRetry: "/api/voice-handoffs/retry",
9919
+ liveLatency: "/traces",
9889
9920
  quality: "/quality",
9890
9921
  resilience: "/resilience",
9891
9922
  sessions: "/sessions",
@@ -9898,6 +9929,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
9898
9929
  failed: handoffs.failed,
9899
9930
  total: handoffs.total
9900
9931
  },
9932
+ liveLatency,
9901
9933
  providers: {
9902
9934
  degraded: degradedProviders,
9903
9935
  total: providers.length
@@ -10017,6 +10049,8 @@ var timelineLabel = (event) => {
10017
10049
  return `Guardrail ${eventStatus(event) ?? "check"}`;
10018
10050
  case "call.handoff":
10019
10051
  return `Call handoff ${eventStatus(event) ?? ""}`.trim();
10052
+ case "client.live_latency":
10053
+ return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
10020
10054
  case "session.error":
10021
10055
  return `Error${getString9(event.payload.error) ? `: ${getString9(event.payload.error)}` : ""}`;
10022
10056
  case "turn.cost":
@@ -23,6 +23,7 @@ export type VoiceProductionReadinessReport = {
23
23
  carriers?: string;
24
24
  handoffs?: string;
25
25
  handoffRetry?: string;
26
+ liveLatency?: string;
26
27
  quality?: string;
27
28
  resilience?: string;
28
29
  sessions?: string;
@@ -40,6 +41,13 @@ export type VoiceProductionReadinessReport = {
40
41
  failed: number;
41
42
  total: number;
42
43
  };
44
+ liveLatency: {
45
+ averageLatencyMs?: number;
46
+ failed: number;
47
+ status: VoiceProductionReadinessStatus;
48
+ total: number;
49
+ warnings: number;
50
+ };
43
51
  providers: {
44
52
  degraded: number;
45
53
  total: number;
@@ -73,6 +81,8 @@ export type VoiceProductionReadinessRoutesOptions = {
73
81
  sttProviders?: readonly string[];
74
82
  title?: string;
75
83
  ttsProviders?: readonly string[];
84
+ liveLatencyWarnAfterMs?: number;
85
+ liveLatencyFailAfterMs?: number;
76
86
  };
77
87
  export declare const buildVoiceProductionReadinessReport: (options: VoiceProductionReadinessRoutesOptions, input?: {
78
88
  query?: Record<string, unknown>;
package/dist/trace.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.memory' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.handoff' | 'call.lifecycle' | 'client.barge_in' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn_latency.stage' | 'turn.transcript' | 'workflow.contract';
1
+ export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.memory' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.handoff' | 'call.lifecycle' | 'client.barge_in' | 'client.live_latency' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn_latency.stage' | 'turn.transcript' | 'workflow.contract';
2
2
  export type VoiceTraceEvent<TPayload extends Record<string, unknown> = Record<string, unknown>> = {
3
3
  at: number;
4
4
  id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.100",
3
+ "version": "0.0.22-beta.102",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",