@absolutejs/voice 0.0.22-beta.90 → 0.0.22-beta.92

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.
@@ -1580,6 +1580,183 @@ var createVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
1580
1580
  getViewModel: () => createVoiceRoutingStatusViewModel(store.getSnapshot(), options)
1581
1581
  };
1582
1582
  };
1583
+ // src/client/traceTimeline.ts
1584
+ var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
1585
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1586
+ const response = await fetchImpl(path);
1587
+ if (!response.ok) {
1588
+ throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
1589
+ }
1590
+ return await response.json();
1591
+ };
1592
+ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
1593
+ const listeners = new Set;
1594
+ let closed = false;
1595
+ let timer;
1596
+ let snapshot = {
1597
+ error: null,
1598
+ isLoading: false,
1599
+ report: null
1600
+ };
1601
+ const emit = () => {
1602
+ for (const listener of listeners) {
1603
+ listener();
1604
+ }
1605
+ };
1606
+ const refresh = async () => {
1607
+ if (closed) {
1608
+ return snapshot.report;
1609
+ }
1610
+ snapshot = {
1611
+ ...snapshot,
1612
+ error: null,
1613
+ isLoading: true
1614
+ };
1615
+ emit();
1616
+ try {
1617
+ const report = await fetchVoiceTraceTimeline(path, options);
1618
+ snapshot = {
1619
+ error: null,
1620
+ isLoading: false,
1621
+ report,
1622
+ updatedAt: Date.now()
1623
+ };
1624
+ emit();
1625
+ return report;
1626
+ } catch (error) {
1627
+ snapshot = {
1628
+ ...snapshot,
1629
+ error: error instanceof Error ? error.message : String(error),
1630
+ isLoading: false
1631
+ };
1632
+ emit();
1633
+ throw error;
1634
+ }
1635
+ };
1636
+ const close = () => {
1637
+ closed = true;
1638
+ if (timer) {
1639
+ clearInterval(timer);
1640
+ timer = undefined;
1641
+ }
1642
+ listeners.clear();
1643
+ };
1644
+ if (options.intervalMs && options.intervalMs > 0) {
1645
+ timer = setInterval(() => {
1646
+ refresh().catch(() => {});
1647
+ }, options.intervalMs);
1648
+ }
1649
+ return {
1650
+ close,
1651
+ getServerSnapshot: () => snapshot,
1652
+ getSnapshot: () => snapshot,
1653
+ refresh,
1654
+ subscribe: (listener) => {
1655
+ listeners.add(listener);
1656
+ return () => {
1657
+ listeners.delete(listener);
1658
+ };
1659
+ }
1660
+ };
1661
+ };
1662
+
1663
+ // src/client/traceTimelineWidget.ts
1664
+ var DEFAULT_TITLE5 = "Voice Traces";
1665
+ var DEFAULT_DESCRIPTION5 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
1666
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1667
+ var formatMs = (value) => typeof value === "number" ? `${value}ms` : "n/a";
1668
+ var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
1669
+ var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
1670
+ const sessions = (snapshot.report?.sessions ?? []).slice(0, options.limit ?? 3).map((session) => ({
1671
+ ...session,
1672
+ detailHref: `${options.detailBasePath ?? "/traces"}/${encodeURIComponent(session.sessionId)}`,
1673
+ durationLabel: formatMs(session.summary.callDurationMs),
1674
+ label: `${session.summary.eventCount} events / ${session.summary.turnCount} turns`,
1675
+ providerLabel: formatProviders(session)
1676
+ }));
1677
+ const failed = sessions.filter((session) => session.status === "failed").length;
1678
+ const warnings = sessions.filter((session) => session.status === "warning").length;
1679
+ return {
1680
+ description: options.description ?? DEFAULT_DESCRIPTION5,
1681
+ error: snapshot.error,
1682
+ isLoading: snapshot.isLoading,
1683
+ label: snapshot.error ? "Unavailable" : failed > 0 ? `${failed} failed` : warnings > 0 ? `${warnings} warning` : sessions.length ? `${sessions.length} recent` : snapshot.isLoading ? "Checking" : "No traces yet",
1684
+ sessions,
1685
+ status: snapshot.error ? "error" : failed > 0 ? "failed" : warnings > 0 ? "warning" : sessions.length ? "ready" : snapshot.isLoading ? "loading" : "empty",
1686
+ title: options.title ?? DEFAULT_TITLE5,
1687
+ updatedAt: snapshot.updatedAt
1688
+ };
1689
+ };
1690
+ var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
1691
+ const model = createVoiceTraceTimelineViewModel(snapshot, options);
1692
+ const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml6(session.status)}">
1693
+ <header>
1694
+ <strong>${escapeHtml6(session.sessionId)}</strong>
1695
+ <span>${escapeHtml6(session.status)}</span>
1696
+ </header>
1697
+ <p>${escapeHtml6(session.label)} \xB7 ${escapeHtml6(session.durationLabel)} \xB7 ${escapeHtml6(session.providerLabel)}</p>
1698
+ <a href="${escapeHtml6(session.detailHref)}">Open timeline</a>
1699
+ </article>`).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
1700
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml6(model.status)}">
1701
+ <header class="absolute-voice-trace-timeline__header">
1702
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml6(model.title)}</span>
1703
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml6(model.label)}</strong>
1704
+ </header>
1705
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml6(model.description)}</p>
1706
+ ${sessions}
1707
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml6(model.error)}</p>` : ""}
1708
+ </section>`;
1709
+ };
1710
+ var getVoiceTraceTimelineCSS = () => `.absolute-voice-trace-timeline{border:1px solid #bad7d3;border-radius:20px;background:#f3fffb;color:#09201c;padding:18px;box-shadow:0 18px 40px rgba(9,32,28,.12);font-family:inherit}.absolute-voice-trace-timeline--error,.absolute-voice-trace-timeline--failed{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-trace-timeline--warning{border-color:#fbbf24;background:#fffaf0}.absolute-voice-trace-timeline__header,.absolute-voice-trace-timeline__session header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-trace-timeline__eyebrow{color:#17665b;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-trace-timeline__label{font-size:24px;line-height:1}.absolute-voice-trace-timeline__description,.absolute-voice-trace-timeline__session p,.absolute-voice-trace-timeline__empty{color:#35544f}.absolute-voice-trace-timeline__sessions{display:grid;gap:12px;margin-top:14px}.absolute-voice-trace-timeline__session{background:#fff;border:1px solid #cfe7e2;border-radius:16px;padding:14px}.absolute-voice-trace-timeline__session--failed{border-color:#f2a7a7}.absolute-voice-trace-timeline__session--warning{border-color:#fbbf24}.absolute-voice-trace-timeline__session p{margin:10px 0}.absolute-voice-trace-timeline__session a{color:#0f766e;font-weight:800}.absolute-voice-trace-timeline__empty{margin:14px 0 0}.absolute-voice-trace-timeline__error{color:#9f1239;font-weight:700}`;
1711
+ var mountVoiceTraceTimeline = (element, path = "/api/voice-traces", options = {}) => {
1712
+ const store = createVoiceTraceTimelineStore(path, options);
1713
+ const render = () => {
1714
+ element.innerHTML = renderVoiceTraceTimelineWidgetHTML(store.getSnapshot(), options);
1715
+ };
1716
+ const unsubscribe = store.subscribe(render);
1717
+ render();
1718
+ store.refresh().catch(() => {});
1719
+ return {
1720
+ close: () => {
1721
+ unsubscribe();
1722
+ store.close();
1723
+ },
1724
+ refresh: store.refresh
1725
+ };
1726
+ };
1727
+ var defineVoiceTraceTimelineElement = (tagName = "absolute-voice-trace-timeline") => {
1728
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1729
+ return;
1730
+ }
1731
+ customElements.define(tagName, class AbsoluteVoiceTraceTimelineElement extends HTMLElement {
1732
+ mounted;
1733
+ connectedCallback() {
1734
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1735
+ const limit = Number(this.getAttribute("limit") ?? 3);
1736
+ this.mounted = mountVoiceTraceTimeline(this, this.getAttribute("path") ?? "/api/voice-traces", {
1737
+ description: this.getAttribute("description") ?? undefined,
1738
+ detailBasePath: this.getAttribute("detail-base-path") ?? undefined,
1739
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1740
+ limit: Number.isFinite(limit) ? limit : 3,
1741
+ title: this.getAttribute("title") ?? undefined
1742
+ });
1743
+ }
1744
+ disconnectedCallback() {
1745
+ this.mounted?.close();
1746
+ this.mounted = undefined;
1747
+ }
1748
+ });
1749
+ };
1750
+
1751
+ // src/svelte/createVoiceTraceTimeline.ts
1752
+ var createVoiceTraceTimeline = (path = "/api/voice-traces", options = {}) => {
1753
+ const store = createVoiceTraceTimelineStore(path, options);
1754
+ return {
1755
+ ...store,
1756
+ getHTML: () => renderVoiceTraceTimelineWidgetHTML(store.getSnapshot(), options),
1757
+ getViewModel: () => createVoiceTraceTimelineViewModel(store.getSnapshot(), options)
1758
+ };
1759
+ };
1583
1760
  // src/client/turnQuality.ts
1584
1761
  var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
1585
1762
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -1660,9 +1837,9 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
1660
1837
  };
1661
1838
 
1662
1839
  // src/client/turnQualityWidget.ts
1663
- var DEFAULT_TITLE5 = "Turn Quality";
1664
- var DEFAULT_DESCRIPTION5 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
1665
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1840
+ var DEFAULT_TITLE6 = "Turn Quality";
1841
+ var DEFAULT_DESCRIPTION6 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
1842
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1666
1843
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
1667
1844
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
1668
1845
  var getTurnDetail = (turn) => {
@@ -1700,37 +1877,37 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
1700
1877
  const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
1701
1878
  const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
1702
1879
  return {
1703
- description: options.description ?? DEFAULT_DESCRIPTION5,
1880
+ description: options.description ?? DEFAULT_DESCRIPTION6,
1704
1881
  error: snapshot.error,
1705
1882
  isLoading: snapshot.isLoading,
1706
1883
  label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
1707
1884
  status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
1708
- title: options.title ?? DEFAULT_TITLE5,
1885
+ title: options.title ?? DEFAULT_TITLE6,
1709
1886
  turns,
1710
1887
  updatedAt: snapshot.updatedAt
1711
1888
  };
1712
1889
  };
1713
1890
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
1714
1891
  const model = createVoiceTurnQualityViewModel(snapshot, options);
1715
- const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml6(turn.status)}">
1892
+ const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml7(turn.status)}">
1716
1893
  <header>
1717
- <strong>${escapeHtml6(turn.label)}</strong>
1718
- <span>${escapeHtml6(turn.status)}</span>
1894
+ <strong>${escapeHtml7(turn.label)}</strong>
1895
+ <span>${escapeHtml7(turn.status)}</span>
1719
1896
  </header>
1720
- <p>${escapeHtml6(turn.detail)}</p>
1897
+ <p>${escapeHtml7(turn.detail)}</p>
1721
1898
  <dl>${turn.rows.map((row) => `<div>
1722
- <dt>${escapeHtml6(row.label)}</dt>
1723
- <dd>${escapeHtml6(row.value)}</dd>
1899
+ <dt>${escapeHtml7(row.label)}</dt>
1900
+ <dd>${escapeHtml7(row.value)}</dd>
1724
1901
  </div>`).join("")}</dl>
1725
1902
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
1726
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml6(model.status)}">
1903
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml7(model.status)}">
1727
1904
  <header class="absolute-voice-turn-quality__header">
1728
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml6(model.title)}</span>
1729
- <strong class="absolute-voice-turn-quality__label">${escapeHtml6(model.label)}</strong>
1905
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml7(model.title)}</span>
1906
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml7(model.label)}</strong>
1730
1907
  </header>
1731
- <p class="absolute-voice-turn-quality__description">${escapeHtml6(model.description)}</p>
1908
+ <p class="absolute-voice-turn-quality__description">${escapeHtml7(model.description)}</p>
1732
1909
  ${turns}
1733
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml6(model.error)}</p>` : ""}
1910
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml7(model.error)}</p>` : ""}
1734
1911
  </section>`;
1735
1912
  };
1736
1913
  var getVoiceTurnQualityCSS = () => `.absolute-voice-turn-quality{border:1px solid #e4d1a3;border-radius:20px;background:#fff9eb;color:#17120a;padding:18px;box-shadow:0 18px 40px rgba(73,48,14,.12);font-family:inherit}.absolute-voice-turn-quality--error,.absolute-voice-turn-quality--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-turn-quality__header,.absolute-voice-turn-quality__turn header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-turn-quality__eyebrow{color:#8a5a0a;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-turn-quality__label{font-size:24px;line-height:1}.absolute-voice-turn-quality__description,.absolute-voice-turn-quality__turn p,.absolute-voice-turn-quality__turn dt,.absolute-voice-turn-quality__empty{color:#5a4930}.absolute-voice-turn-quality__turns{display:grid;gap:12px;margin-top:14px}.absolute-voice-turn-quality__turn{background:#fff;border:1px solid #f0dfba;border-radius:16px;padding:14px}.absolute-voice-turn-quality__turn--pass{border-color:#86efac}.absolute-voice-turn-quality__turn--warn,.absolute-voice-turn-quality__turn--unknown{border-color:#fbbf24}.absolute-voice-turn-quality__turn--fail{border-color:#f2a7a7}.absolute-voice-turn-quality__turn p{margin:10px 0}.absolute-voice-turn-quality__turn dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-turn-quality__turn div{background:#fff9eb;border:1px solid #f0dfba;border-radius:12px;padding:8px}.absolute-voice-turn-quality__turn dt{font-size:12px}.absolute-voice-turn-quality__turn dd{font-weight:800;margin:4px 0 0}.absolute-voice-turn-quality__empty{margin:14px 0 0}.absolute-voice-turn-quality__error{color:#9f1239;font-weight:700}`;
@@ -2493,6 +2670,7 @@ var createVoiceController = (path, options = {}) => {
2493
2670
  export {
2494
2671
  createVoiceWorkflowStatus,
2495
2672
  createVoiceTurnQuality,
2673
+ createVoiceTraceTimeline,
2496
2674
  createVoiceStream2 as createVoiceStream,
2497
2675
  createVoiceRoutingStatus,
2498
2676
  createVoiceProviderStatus,
@@ -3063,11 +3063,26 @@ var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
3063
3063
  var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
3064
3064
  var bindVoiceBargeIn = (controller, player, options = {}) => {
3065
3065
  let lastPartial = controller.partial;
3066
- const interruptIfPlaying = () => {
3066
+ const interruptIfPlaying = (reason) => {
3067
3067
  if (!player.isPlaying || options.enabled === false) {
3068
+ options.monitor?.recordSkipped({
3069
+ reason,
3070
+ sessionId: controller.sessionId
3071
+ });
3068
3072
  return;
3069
3073
  }
3070
- player.interrupt();
3074
+ options.monitor?.recordRequested({
3075
+ reason,
3076
+ sessionId: controller.sessionId
3077
+ });
3078
+ player.interrupt().then(() => {
3079
+ options.monitor?.recordStopped({
3080
+ latencyMs: player.lastInterruptLatencyMs,
3081
+ playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
3082
+ reason,
3083
+ sessionId: controller.sessionId
3084
+ });
3085
+ });
3071
3086
  };
3072
3087
  const unsubscribe = controller.subscribe(() => {
3073
3088
  if (options.interruptOnPartial === false) {
@@ -3075,7 +3090,7 @@ var bindVoiceBargeIn = (controller, player, options = {}) => {
3075
3090
  return;
3076
3091
  }
3077
3092
  if (!lastPartial && controller.partial) {
3078
- interruptIfPlaying();
3093
+ interruptIfPlaying("partial-transcript");
3079
3094
  }
3080
3095
  lastPartial = controller.partial;
3081
3096
  });
@@ -3085,11 +3100,11 @@ var bindVoiceBargeIn = (controller, player, options = {}) => {
3085
3100
  },
3086
3101
  handleLevel: (level) => {
3087
3102
  if (shouldInterruptForLevel(level, options)) {
3088
- interruptIfPlaying();
3103
+ interruptIfPlaying("input-level");
3089
3104
  }
3090
3105
  },
3091
3106
  sendAudio: (audio) => {
3092
- interruptIfPlaying();
3107
+ interruptIfPlaying("manual-audio");
3093
3108
  controller.sendAudio(audio);
3094
3109
  }
3095
3110
  };
@@ -3119,7 +3134,17 @@ var createVoiceDuplexController = (path, options = {}) => {
3119
3134
  audioPlayer,
3120
3135
  close,
3121
3136
  interruptAssistant: async () => {
3137
+ options.bargeIn?.monitor?.recordRequested({
3138
+ reason: "manual-interrupt",
3139
+ sessionId: controller.sessionId
3140
+ });
3122
3141
  await audioPlayer.interrupt();
3142
+ options.bargeIn?.monitor?.recordStopped({
3143
+ latencyMs: audioPlayer.lastInterruptLatencyMs,
3144
+ playbackStopLatencyMs: audioPlayer.lastPlaybackStopLatencyMs,
3145
+ reason: "manual-interrupt",
3146
+ sessionId: controller.sessionId
3147
+ });
3123
3148
  },
3124
3149
  sendAudio: (audio) => {
3125
3150
  bargeInBinding?.sendAudio(audio);
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' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | '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' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | '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/dist/types.d.ts CHANGED
@@ -747,6 +747,46 @@ export type VoiceBargeInOptions = {
747
747
  enabled?: boolean;
748
748
  interruptOnPartial?: boolean;
749
749
  interruptThreshold?: number;
750
+ monitor?: VoiceBargeInMonitor;
751
+ };
752
+ export type VoiceBargeInTriggerReason = 'input-level' | 'manual-audio' | 'manual-interrupt' | 'partial-transcript';
753
+ export type VoiceBargeInMonitorEvent = {
754
+ at: number;
755
+ id: string;
756
+ latencyMs?: number;
757
+ playbackStopLatencyMs?: number;
758
+ reason: VoiceBargeInTriggerReason;
759
+ sessionId?: string | null;
760
+ status: 'requested' | 'stopped' | 'skipped';
761
+ thresholdMs?: number;
762
+ };
763
+ export type VoiceBargeInMonitorSnapshot = {
764
+ averageLatencyMs?: number;
765
+ events: VoiceBargeInMonitorEvent[];
766
+ failed: number;
767
+ lastEvent?: VoiceBargeInMonitorEvent;
768
+ passed: number;
769
+ status: 'empty' | 'fail' | 'pass' | 'warn';
770
+ thresholdMs: number;
771
+ total: number;
772
+ };
773
+ export type VoiceBargeInMonitor = {
774
+ getSnapshot: () => VoiceBargeInMonitorSnapshot;
775
+ recordRequested: (input: {
776
+ reason: VoiceBargeInTriggerReason;
777
+ sessionId?: string | null;
778
+ }) => VoiceBargeInMonitorEvent;
779
+ recordSkipped: (input: {
780
+ reason: VoiceBargeInTriggerReason;
781
+ sessionId?: string | null;
782
+ }) => VoiceBargeInMonitorEvent;
783
+ recordStopped: (input: {
784
+ latencyMs?: number;
785
+ playbackStopLatencyMs?: number;
786
+ reason: VoiceBargeInTriggerReason;
787
+ sessionId?: string | null;
788
+ }) => VoiceBargeInMonitorEvent;
789
+ subscribe: (subscriber: () => void) => () => void;
750
790
  };
751
791
  export type VoiceAudioPlayerOptions = {
752
792
  autoStart?: boolean;
@@ -11,5 +11,6 @@ export { useVoiceProviderStatus } from './useVoiceProviderStatus';
11
11
  export { useVoiceProviderCapabilities } from './useVoiceProviderCapabilities';
12
12
  export { useVoiceProviderSimulationControls } from './useVoiceProviderSimulationControls';
13
13
  export { useVoiceRoutingStatus } from './useVoiceRoutingStatus';
14
+ export { useVoiceTraceTimeline } from './useVoiceTraceTimeline';
14
15
  export { useVoiceTurnQuality } from './useVoiceTurnQuality';
15
16
  export { useVoiceWorkflowStatus } from './useVoiceWorkflowStatus';
package/dist/vue/index.js CHANGED
@@ -3053,9 +3053,121 @@ var useVoiceController = (path, options = {}) => {
3053
3053
  turns
3054
3054
  };
3055
3055
  };
3056
- // src/vue/useVoiceWorkflowStatus.ts
3056
+ // src/vue/useVoiceTraceTimeline.ts
3057
3057
  import { onUnmounted as onUnmounted9, ref as ref7, shallowRef as shallowRef8 } from "vue";
3058
3058
 
3059
+ // src/client/traceTimeline.ts
3060
+ var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
3061
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3062
+ const response = await fetchImpl(path);
3063
+ if (!response.ok) {
3064
+ throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
3065
+ }
3066
+ return await response.json();
3067
+ };
3068
+ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
3069
+ const listeners = new Set;
3070
+ let closed = false;
3071
+ let timer;
3072
+ let snapshot = {
3073
+ error: null,
3074
+ isLoading: false,
3075
+ report: null
3076
+ };
3077
+ const emit = () => {
3078
+ for (const listener of listeners) {
3079
+ listener();
3080
+ }
3081
+ };
3082
+ const refresh = async () => {
3083
+ if (closed) {
3084
+ return snapshot.report;
3085
+ }
3086
+ snapshot = {
3087
+ ...snapshot,
3088
+ error: null,
3089
+ isLoading: true
3090
+ };
3091
+ emit();
3092
+ try {
3093
+ const report = await fetchVoiceTraceTimeline(path, options);
3094
+ snapshot = {
3095
+ error: null,
3096
+ isLoading: false,
3097
+ report,
3098
+ updatedAt: Date.now()
3099
+ };
3100
+ emit();
3101
+ return report;
3102
+ } catch (error) {
3103
+ snapshot = {
3104
+ ...snapshot,
3105
+ error: error instanceof Error ? error.message : String(error),
3106
+ isLoading: false
3107
+ };
3108
+ emit();
3109
+ throw error;
3110
+ }
3111
+ };
3112
+ const close = () => {
3113
+ closed = true;
3114
+ if (timer) {
3115
+ clearInterval(timer);
3116
+ timer = undefined;
3117
+ }
3118
+ listeners.clear();
3119
+ };
3120
+ if (options.intervalMs && options.intervalMs > 0) {
3121
+ timer = setInterval(() => {
3122
+ refresh().catch(() => {});
3123
+ }, options.intervalMs);
3124
+ }
3125
+ return {
3126
+ close,
3127
+ getServerSnapshot: () => snapshot,
3128
+ getSnapshot: () => snapshot,
3129
+ refresh,
3130
+ subscribe: (listener) => {
3131
+ listeners.add(listener);
3132
+ return () => {
3133
+ listeners.delete(listener);
3134
+ };
3135
+ }
3136
+ };
3137
+ };
3138
+
3139
+ // src/vue/useVoiceTraceTimeline.ts
3140
+ var useVoiceTraceTimeline = (path = "/api/voice-traces", options = {}) => {
3141
+ const store = createVoiceTraceTimelineStore(path, options);
3142
+ const error = ref7(null);
3143
+ const isLoading = ref7(false);
3144
+ const report = shallowRef8(null);
3145
+ const updatedAt = ref7(undefined);
3146
+ const sync = () => {
3147
+ const snapshot = store.getSnapshot();
3148
+ error.value = snapshot.error;
3149
+ isLoading.value = snapshot.isLoading;
3150
+ report.value = snapshot.report;
3151
+ updatedAt.value = snapshot.updatedAt;
3152
+ };
3153
+ const unsubscribe = store.subscribe(sync);
3154
+ sync();
3155
+ store.refresh().catch(() => {});
3156
+ onUnmounted9(() => {
3157
+ unsubscribe();
3158
+ store.close();
3159
+ });
3160
+ return {
3161
+ error,
3162
+ isLoading,
3163
+ refresh: store.refresh,
3164
+ report,
3165
+ updatedAt
3166
+ };
3167
+ };
3168
+ // src/vue/useVoiceWorkflowStatus.ts
3169
+ import { onUnmounted as onUnmounted10, ref as ref8, shallowRef as shallowRef9 } from "vue";
3170
+
3059
3171
  // src/client/workflowStatus.ts
3060
3172
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
3061
3173
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -3138,10 +3250,10 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
3138
3250
  // src/vue/useVoiceWorkflowStatus.ts
3139
3251
  var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
3140
3252
  const store = createVoiceWorkflowStatusStore(path, options);
3141
- const error = ref7(null);
3142
- const isLoading = ref7(false);
3143
- const report = shallowRef8(undefined);
3144
- const updatedAt = ref7(undefined);
3253
+ const error = ref8(null);
3254
+ const isLoading = ref8(false);
3255
+ const report = shallowRef9(undefined);
3256
+ const updatedAt = ref8(undefined);
3145
3257
  const sync = () => {
3146
3258
  const snapshot = store.getSnapshot();
3147
3259
  error.value = snapshot.error;
@@ -3154,7 +3266,7 @@ var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
3154
3266
  if (typeof window !== "undefined") {
3155
3267
  store.refresh().catch(() => {});
3156
3268
  }
3157
- onUnmounted9(() => {
3269
+ onUnmounted10(() => {
3158
3270
  unsubscribe();
3159
3271
  store.close();
3160
3272
  });
@@ -3169,6 +3281,7 @@ var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
3169
3281
  export {
3170
3282
  useVoiceWorkflowStatus,
3171
3283
  useVoiceTurnQuality,
3284
+ useVoiceTraceTimeline,
3172
3285
  useVoiceStream,
3173
3286
  useVoiceRoutingStatus,
3174
3287
  useVoiceProviderStatus,
@@ -0,0 +1,9 @@
1
+ import { type VoiceTraceTimelineClientOptions } from '../client/traceTimeline';
2
+ import type { VoiceTraceTimelineReport } from '../traceTimeline';
3
+ export declare const useVoiceTraceTimeline: (path?: string, options?: VoiceTraceTimelineClientOptions) => {
4
+ error: import("vue").Ref<string | null, string | null>;
5
+ isLoading: import("vue").Ref<boolean, boolean>;
6
+ refresh: () => Promise<VoiceTraceTimelineReport | null>;
7
+ report: import("vue").ShallowRef<VoiceTraceTimelineReport | null, VoiceTraceTimelineReport | null>;
8
+ updatedAt: import("vue").Ref<number | undefined, number | undefined>;
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.90",
3
+ "version": "0.0.22-beta.92",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",