@absolutejs/voice 0.0.22-beta.91 → 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.
package/dist/appKit.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import { type VoiceAssistantHealthRoutesOptions } from './assistantHealth';
3
+ import { type VoiceBargeInRoutesOptions } from './bargeInRoutes';
3
4
  import { type VoiceDiagnosticsRoutesOptions } from './diagnosticsRoutes';
4
5
  import { type VoiceEvalRoutesOptions, type VoiceEvalLink } from './evalRoutes';
5
6
  import { type VoiceHandoffHealthRoutesOptions } from './handoffHealth';
@@ -12,7 +13,7 @@ import { type VoiceResilienceRoutesOptions } from './resilienceRoutes';
12
13
  import { type VoiceSessionListRoutesOptions, type VoiceSessionReplayRoutesOptions } from './sessionReplay';
13
14
  import { type VoiceTraceEventStore } from './trace';
14
15
  import { type VoiceTraceTimelineRoutesOptions } from './traceTimeline';
15
- export type VoiceAppKitSurface = 'assistantHealth' | 'diagnostics' | 'evals' | 'handoffs' | 'opsConsole' | 'providerCapabilities' | 'providerHealth' | 'productionReadiness' | 'quality' | 'resilience' | 'sessionReplay' | 'sessions' | 'traceTimeline';
16
+ export type VoiceAppKitSurface = 'assistantHealth' | 'bargeIn' | 'diagnostics' | 'evals' | 'handoffs' | 'opsConsole' | 'providerCapabilities' | 'providerHealth' | 'productionReadiness' | 'quality' | 'resilience' | 'sessionReplay' | 'sessions' | 'traceTimeline';
16
17
  export type VoiceAppKitLink = VoiceEvalLink & {
17
18
  description?: string;
18
19
  statusHref?: string;
@@ -20,6 +21,7 @@ export type VoiceAppKitLink = VoiceEvalLink & {
20
21
  export type VoiceAppKitRoutesOptions<TProvider extends string = string> = {
21
22
  appStatus?: false | VoiceAppKitStatusOptions;
22
23
  assistantHealth?: false | Partial<VoiceAssistantHealthRoutesOptions<TProvider>>;
24
+ bargeIn?: false | Partial<VoiceBargeInRoutesOptions>;
23
25
  diagnostics?: false | Partial<Omit<VoiceDiagnosticsRoutesOptions, 'store'>>;
24
26
  evals?: false | Partial<VoiceEvalRoutesOptions>;
25
27
  handoffs?: false | Partial<VoiceHandoffHealthRoutesOptions>;
@@ -0,0 +1,56 @@
1
+ import { Elysia } from 'elysia';
2
+ import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
3
+ import type { VoiceBargeInMonitorSnapshot } from './types';
4
+ export type VoiceBargeInRoutesOptions = {
5
+ headers?: HeadersInit;
6
+ htmlPath?: string;
7
+ name?: string;
8
+ path?: string;
9
+ store: VoiceTraceEventStore;
10
+ thresholdMs?: number;
11
+ title?: string;
12
+ };
13
+ export type VoiceBargeInReport = VoiceBargeInMonitorSnapshot & {
14
+ checkedAt: number;
15
+ sessions: Array<{
16
+ averageLatencyMs?: number;
17
+ failed: number;
18
+ passed: number;
19
+ sessionId: string;
20
+ total: number;
21
+ }>;
22
+ };
23
+ export declare const summarizeVoiceBargeIn: (events: StoredVoiceTraceEvent[], options?: {
24
+ thresholdMs?: number;
25
+ }) => VoiceBargeInReport;
26
+ export declare const renderVoiceBargeInHTML: (report: VoiceBargeInReport, options?: {
27
+ title?: string;
28
+ }) => string;
29
+ export declare const createVoiceBargeInRoutes: (options: VoiceBargeInRoutesOptions) => Elysia<"", {
30
+ decorator: {};
31
+ store: {};
32
+ derive: {};
33
+ resolve: {};
34
+ }, {
35
+ typebox: {};
36
+ error: {};
37
+ }, {
38
+ schema: {};
39
+ standaloneSchema: {};
40
+ macro: {};
41
+ macroFn: {};
42
+ parser: {};
43
+ response: {};
44
+ }, {}, {
45
+ derive: {};
46
+ resolve: {};
47
+ schema: {};
48
+ standaloneSchema: {};
49
+ response: {};
50
+ }, {
51
+ derive: {};
52
+ resolve: {};
53
+ schema: {};
54
+ standaloneSchema: {};
55
+ response: {};
56
+ }>;
@@ -0,0 +1,7 @@
1
+ import type { VoiceBargeInMonitor } from '../types';
2
+ export type VoiceBargeInMonitorOptions = {
3
+ fetch?: typeof fetch;
4
+ path?: string;
5
+ thresholdMs?: number;
6
+ };
7
+ export declare const createVoiceBargeInMonitor: (options?: VoiceBargeInMonitorOptions) => VoiceBargeInMonitor;
@@ -1,3 +1,3 @@
1
1
  import type { VoiceAudioPlayer, VoiceBargeInBinding, VoiceBargeInOptions, VoiceController, VoiceDuplexController, VoiceDuplexControllerOptions } from '../types';
2
- export declare const bindVoiceBargeIn: <TResult = unknown>(controller: Pick<VoiceController<TResult>, "partial" | "sendAudio" | "subscribe">, player: Pick<VoiceAudioPlayer, "interrupt" | "isPlaying">, options?: VoiceBargeInOptions) => VoiceBargeInBinding;
2
+ export declare const bindVoiceBargeIn: <TResult = unknown>(controller: Pick<VoiceController<TResult>, "partial" | "sendAudio" | "sessionId" | "subscribe">, player: Pick<VoiceAudioPlayer, "interrupt" | "isPlaying" | "lastInterruptLatencyMs" | "lastPlaybackStopLatencyMs">, options?: VoiceBargeInOptions) => VoiceBargeInBinding;
3
3
  export declare const createVoiceDuplexController: <TResult = unknown>(path: string, options?: VoiceDuplexControllerOptions) => VoiceDuplexController<TResult>;
@@ -5,6 +5,7 @@ export { createVoiceController } from './controller';
5
5
  export { bindVoiceBargeIn, createVoiceDuplexController } from './duplex';
6
6
  export { bindVoiceHTMX } from './htmx';
7
7
  export { createMicrophoneCapture } from './microphone';
8
+ export { createVoiceBargeInMonitor } from './bargeInMonitor';
8
9
  export { createVoiceAppKitStatusStore, fetchVoiceAppKitStatus } from './appKitStatus';
9
10
  export { createVoiceOpsStatusViewModel, defineVoiceOpsStatusElement, getVoiceOpsStatusCSS, getVoiceOpsStatusLabel, mountVoiceOpsStatus, renderVoiceOpsStatusHTML } from './opsStatusWidget';
10
11
  export { createVoiceRoutingStatusStore, fetchVoiceRoutingStatus } from './routingStatus';
@@ -21,6 +22,7 @@ export { createVoiceTurnQualityViewModel, defineVoiceTurnQualityElement, getVoic
21
22
  export { createVoiceTraceTimelineViewModel, defineVoiceTraceTimelineElement, getVoiceTraceTimelineCSS, mountVoiceTraceTimeline, renderVoiceTraceTimelineWidgetHTML } from './traceTimelineWidget';
22
23
  export { createVoiceWorkflowStatusStore, fetchVoiceWorkflowStatus } from './workflowStatus';
23
24
  export type { VoiceAppKitStatusClientOptions, VoiceAppKitStatusSnapshot } from './appKitStatus';
25
+ export type { VoiceBargeInMonitorOptions } from './bargeInMonitor';
24
26
  export type { VoiceOpsStatusSurfaceView, VoiceOpsStatusViewModel, VoiceOpsStatusWidgetOptions } from './opsStatusWidget';
25
27
  export type { VoiceRoutingStatusClientOptions, VoiceRoutingStatusSnapshot } from './routingStatus';
26
28
  export type { VoiceRoutingStatusViewModel, VoiceRoutingStatusWidgetOptions } from './routingStatusWidget';
@@ -1560,11 +1560,26 @@ var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
1560
1560
  var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
1561
1561
  var bindVoiceBargeIn = (controller, player, options = {}) => {
1562
1562
  let lastPartial = controller.partial;
1563
- const interruptIfPlaying = () => {
1563
+ const interruptIfPlaying = (reason) => {
1564
1564
  if (!player.isPlaying || options.enabled === false) {
1565
+ options.monitor?.recordSkipped({
1566
+ reason,
1567
+ sessionId: controller.sessionId
1568
+ });
1565
1569
  return;
1566
1570
  }
1567
- player.interrupt();
1571
+ options.monitor?.recordRequested({
1572
+ reason,
1573
+ sessionId: controller.sessionId
1574
+ });
1575
+ player.interrupt().then(() => {
1576
+ options.monitor?.recordStopped({
1577
+ latencyMs: player.lastInterruptLatencyMs,
1578
+ playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
1579
+ reason,
1580
+ sessionId: controller.sessionId
1581
+ });
1582
+ });
1568
1583
  };
1569
1584
  const unsubscribe = controller.subscribe(() => {
1570
1585
  if (options.interruptOnPartial === false) {
@@ -1572,7 +1587,7 @@ var bindVoiceBargeIn = (controller, player, options = {}) => {
1572
1587
  return;
1573
1588
  }
1574
1589
  if (!lastPartial && controller.partial) {
1575
- interruptIfPlaying();
1590
+ interruptIfPlaying("partial-transcript");
1576
1591
  }
1577
1592
  lastPartial = controller.partial;
1578
1593
  });
@@ -1582,11 +1597,11 @@ var bindVoiceBargeIn = (controller, player, options = {}) => {
1582
1597
  },
1583
1598
  handleLevel: (level) => {
1584
1599
  if (shouldInterruptForLevel(level, options)) {
1585
- interruptIfPlaying();
1600
+ interruptIfPlaying("input-level");
1586
1601
  }
1587
1602
  },
1588
1603
  sendAudio: (audio) => {
1589
- interruptIfPlaying();
1604
+ interruptIfPlaying("manual-audio");
1590
1605
  controller.sendAudio(audio);
1591
1606
  }
1592
1607
  };
@@ -1616,13 +1631,93 @@ var createVoiceDuplexController = (path, options = {}) => {
1616
1631
  audioPlayer,
1617
1632
  close,
1618
1633
  interruptAssistant: async () => {
1634
+ options.bargeIn?.monitor?.recordRequested({
1635
+ reason: "manual-interrupt",
1636
+ sessionId: controller.sessionId
1637
+ });
1619
1638
  await audioPlayer.interrupt();
1639
+ options.bargeIn?.monitor?.recordStopped({
1640
+ latencyMs: audioPlayer.lastInterruptLatencyMs,
1641
+ playbackStopLatencyMs: audioPlayer.lastPlaybackStopLatencyMs,
1642
+ reason: "manual-interrupt",
1643
+ sessionId: controller.sessionId
1644
+ });
1620
1645
  },
1621
1646
  sendAudio: (audio) => {
1622
1647
  bargeInBinding?.sendAudio(audio);
1623
1648
  }
1624
1649
  };
1625
1650
  };
1651
+ // src/client/bargeInMonitor.ts
1652
+ var DEFAULT_THRESHOLD_MS = 250;
1653
+ var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
1654
+ var summarize = (events, thresholdMs) => {
1655
+ const stopped = events.filter((event) => event.status === "stopped");
1656
+ const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
1657
+ const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
1658
+ const passed = stopped.length - failed;
1659
+ return {
1660
+ averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
1661
+ events: [...events],
1662
+ failed,
1663
+ lastEvent: events.at(-1),
1664
+ passed,
1665
+ status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
1666
+ thresholdMs,
1667
+ total: stopped.length
1668
+ };
1669
+ };
1670
+ var createVoiceBargeInMonitor = (options = {}) => {
1671
+ const listeners = new Set;
1672
+ const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
1673
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1674
+ const events = [];
1675
+ const emit = () => {
1676
+ for (const listener of listeners) {
1677
+ listener();
1678
+ }
1679
+ };
1680
+ const postEvent = (event) => {
1681
+ if (!options.path || typeof fetchImpl !== "function") {
1682
+ return;
1683
+ }
1684
+ fetchImpl(options.path, {
1685
+ body: JSON.stringify(event),
1686
+ headers: {
1687
+ "Content-Type": "application/json"
1688
+ },
1689
+ method: "POST"
1690
+ }).catch(() => {});
1691
+ };
1692
+ const record = (status, input) => {
1693
+ const event = {
1694
+ at: Date.now(),
1695
+ id: createEventId(),
1696
+ latencyMs: input.latencyMs,
1697
+ playbackStopLatencyMs: input.playbackStopLatencyMs,
1698
+ reason: input.reason,
1699
+ sessionId: input.sessionId,
1700
+ status,
1701
+ thresholdMs
1702
+ };
1703
+ events.push(event);
1704
+ postEvent(event);
1705
+ emit();
1706
+ return event;
1707
+ };
1708
+ return {
1709
+ getSnapshot: () => summarize(events, thresholdMs),
1710
+ recordRequested: (input) => record("requested", input),
1711
+ recordSkipped: (input) => record("skipped", input),
1712
+ recordStopped: (input) => record("stopped", input),
1713
+ subscribe: (subscriber) => {
1714
+ listeners.add(subscriber);
1715
+ return () => {
1716
+ listeners.delete(subscriber);
1717
+ };
1718
+ }
1719
+ };
1720
+ };
1626
1721
  // src/client/appKitStatus.ts
1627
1722
  var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
1628
1723
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -3046,6 +3141,7 @@ export {
3046
3141
  createVoiceDuplexController,
3047
3142
  createVoiceController,
3048
3143
  createVoiceConnection,
3144
+ createVoiceBargeInMonitor,
3049
3145
  createVoiceAudioPlayer,
3050
3146
  createVoiceAppKitStatusStore,
3051
3147
  createMicrophoneCapture,
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { voice } from './plugin';
2
2
  export { createVoiceAppKit, createVoiceAppKitRoutes, summarizeVoiceAppKitStatus } from './appKit';
3
3
  export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
4
4
  export { createVoiceAssistantHealthHTMLHandler, createVoiceAssistantHealthJSONHandler, createVoiceAssistantHealthRoutes, renderVoiceAssistantHealthHTML, summarizeVoiceAssistantHealth } from './assistantHealth';
5
+ export { createVoiceBargeInRoutes, renderVoiceBargeInHTML, summarizeVoiceBargeIn } from './bargeInRoutes';
5
6
  export { buildVoiceDiagnosticsMarkdown, createVoiceDiagnosticsRoutes, resolveVoiceDiagnosticsTraceFilter } from './diagnosticsRoutes';
6
7
  export { compareVoiceEvalBaseline, createVoiceFileEvalBaselineStore, createVoiceFileScenarioFixtureStore, createVoiceEvalRoutes, renderVoiceEvalBaselineHTML, renderVoiceEvalHTML, renderVoiceScenarioEvalHTML, renderVoiceScenarioFixtureEvalHTML, runVoiceScenarioEvals, runVoiceScenarioFixtureEvals, runVoiceSessionEvals } from './evalRoutes';
7
8
  export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult } from './workflowContract';
@@ -48,6 +49,7 @@ export { resolveVoiceRuntimePreset } from './presets';
48
49
  export { resolveTurnDetectionConfig, TURN_PROFILE_DEFAULTS } from './turnProfiles';
49
50
  export { createVoiceCallReviewFromLiveTelephonyReport, createVoiceCallReviewRecorder, renderVoiceCallReviewHTML, renderVoiceCallReviewMarkdown } from './testing/review';
50
51
  export type { VoiceAppKitLink, VoiceAppKitRoutes, VoiceAppKitRoutesOptions, VoiceAppKitStatus, VoiceAppKitStatusOptions, VoiceAppKitStatusReport, VoiceAppKitSurface } from './appKit';
52
+ export type { VoiceBargeInReport, VoiceBargeInRoutesOptions } from './bargeInRoutes';
51
53
  export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperiment, VoiceAssistantExperimentOptions, VoiceAssistantGuardrailInput, VoiceAssistantGuardrails, VoiceAssistantMemoryLifecycle, VoiceAssistantMemoryLifecycleInput, VoiceAssistantOptions, VoiceAssistantOutputGuardrailInput, VoiceAssistantPreset, VoiceAssistantRunsSummary, VoiceAssistantRunSummary, VoiceAssistantVariant } from './assistant';
52
54
  export type { VoiceAssistantHealthFailure, VoiceAssistantHealthHTMLHandlerOptions, VoiceAssistantHealthRoutesOptions, VoiceAssistantHealthSummary, VoiceAssistantHealthSummaryOptions } from './assistantHealth';
53
55
  export type { VoiceAssistantMemoryBinding, VoiceAssistantMemoryHandle, VoiceAssistantMemoryOptions, VoiceAssistantMemoryRecord, VoiceAssistantMemoryStore } from './assistantMemory';