@absolutejs/voice 0.0.22-beta.54 → 0.0.22-beta.540

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.
Files changed (493) hide show
  1. package/README.md +4518 -951
  2. package/dist/angular/index.d.ts +36 -5
  3. package/dist/angular/index.js +4189 -390
  4. package/dist/angular/voice-agent-squad-status.service.d.ts +12 -0
  5. package/dist/angular/voice-call-debugger.service.d.ts +12 -0
  6. package/dist/angular/voice-call-player.service.d.ts +19 -0
  7. package/dist/angular/voice-campaign-dialer-proof.service.d.ts +14 -0
  8. package/dist/angular/voice-controller.service.d.ts +3 -1
  9. package/dist/angular/voice-cost-dashboard.service.d.ts +15 -0
  10. package/dist/angular/voice-delivery-runtime.service.d.ts +16 -0
  11. package/dist/angular/voice-live-agent-console.service.d.ts +16 -0
  12. package/dist/angular/voice-live-call-viewer.service.d.ts +16 -0
  13. package/dist/angular/voice-live-ops.service.d.ts +11 -0
  14. package/dist/angular/voice-ops-action-center.service.d.ts +13 -0
  15. package/dist/angular/voice-ops-status.service.d.ts +12 -0
  16. package/dist/angular/voice-platform-coverage.service.d.ts +12 -0
  17. package/dist/angular/voice-profile-comparison.service.d.ts +12 -0
  18. package/dist/angular/voice-proof-trends.service.d.ts +12 -0
  19. package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
  20. package/dist/angular/voice-provider-contracts.service.d.ts +12 -0
  21. package/dist/angular/voice-provider-status.service.d.ts +2 -2
  22. package/dist/angular/voice-readiness-failures.service.d.ts +13 -0
  23. package/dist/angular/voice-reconnect-profile-evidence.service.d.ts +12 -0
  24. package/dist/angular/voice-replay-timeline.service.d.ts +13 -0
  25. package/dist/angular/voice-routing-status.service.d.ts +11 -0
  26. package/dist/angular/voice-session-observability.service.d.ts +12 -0
  27. package/dist/angular/voice-session-snapshot.service.d.ts +13 -0
  28. package/dist/angular/voice-stream.service.d.ts +4 -1
  29. package/dist/angular/voice-trace-timeline.service.d.ts +12 -0
  30. package/dist/angular/voice-turn-latency.service.d.ts +13 -0
  31. package/dist/angular/voice-turn-quality.service.d.ts +12 -0
  32. package/dist/angular/voice-widget.service.d.ts +18 -0
  33. package/dist/angular/voice-workflow-status.service.d.ts +2 -2
  34. package/dist/client/actions.d.ts +95 -1
  35. package/dist/client/agentSquadStatus.d.ts +37 -0
  36. package/dist/client/agentSquadStatusWidget.d.ts +24 -0
  37. package/dist/client/audioPlayer.d.ts +2 -2
  38. package/dist/client/bargeInMonitor.d.ts +7 -0
  39. package/dist/client/browserMedia.d.ts +8 -0
  40. package/dist/client/browserNoiseSuppression.d.ts +42 -0
  41. package/dist/client/browserVoiceSupport.d.ts +22 -0
  42. package/dist/client/callDebugger.d.ts +21 -0
  43. package/dist/client/callDebuggerWidget.d.ts +30 -0
  44. package/dist/client/callPlayer.d.ts +41 -0
  45. package/dist/client/campaignDialerProof.d.ts +25 -0
  46. package/dist/client/connection.d.ts +4 -3
  47. package/dist/client/controller.d.ts +1 -1
  48. package/dist/client/conversationAnalytics.d.ts +30 -0
  49. package/dist/client/costDashboard.d.ts +27 -0
  50. package/dist/client/createVoiceStream.d.ts +1 -1
  51. package/dist/client/deliveryRuntime.d.ts +36 -0
  52. package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
  53. package/dist/client/duplex.d.ts +2 -2
  54. package/dist/client/htmx.d.ts +1 -1
  55. package/dist/client/htmxAttributes.d.ts +24 -0
  56. package/dist/client/htmxBootstrap.js +1029 -73
  57. package/dist/client/htmxDashboardRenderers.d.ts +71 -0
  58. package/dist/client/index.d.ts +106 -15
  59. package/dist/client/index.js +10494 -279
  60. package/dist/client/liveAgentConsole.d.ts +28 -0
  61. package/dist/client/liveCallViewer.d.ts +42 -0
  62. package/dist/client/liveOps.d.ts +22 -0
  63. package/dist/client/liveOpsWidget.d.ts +23 -0
  64. package/dist/client/liveTurnLatency.d.ts +41 -0
  65. package/dist/client/microphone.d.ts +4 -4
  66. package/dist/client/opsActionCenter.d.ts +56 -0
  67. package/dist/client/opsActionCenterWidget.d.ts +29 -0
  68. package/dist/client/opsActionHistory.d.ts +21 -0
  69. package/dist/client/opsActionHistoryWidget.d.ts +11 -0
  70. package/dist/client/opsStatus.d.ts +21 -0
  71. package/dist/client/opsStatusWidget.d.ts +10 -10
  72. package/dist/client/platformCoverage.d.ts +21 -0
  73. package/dist/client/platformCoverageWidget.d.ts +38 -0
  74. package/dist/client/profileComparison.d.ts +21 -0
  75. package/dist/client/profileComparisonWidget.d.ts +41 -0
  76. package/dist/client/profileSwitchRecommendation.d.ts +21 -0
  77. package/dist/client/profileSwitchRecommendationWidget.d.ts +12 -0
  78. package/dist/client/proofTrends.d.ts +21 -0
  79. package/dist/client/proofTrendsWidget.d.ts +38 -0
  80. package/dist/client/providerCapabilities.d.ts +21 -0
  81. package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
  82. package/dist/client/providerContracts.d.ts +21 -0
  83. package/dist/client/providerContractsWidget.d.ts +37 -0
  84. package/dist/client/providerSimulationControls.d.ts +33 -0
  85. package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
  86. package/dist/client/providerStatus.d.ts +5 -3
  87. package/dist/client/providerStatusWidget.d.ts +32 -0
  88. package/dist/client/reactiveSource.d.ts +8 -0
  89. package/dist/client/readinessFailures.d.ts +21 -0
  90. package/dist/client/readinessFailuresWidget.d.ts +42 -0
  91. package/dist/client/reconnectProfileEvidence.d.ts +21 -0
  92. package/dist/client/reconnectProfileEvidenceWidget.d.ts +39 -0
  93. package/dist/client/replayTimeline.d.ts +26 -0
  94. package/dist/client/routingStatus.d.ts +21 -0
  95. package/dist/client/routingStatusWidget.d.ts +32 -0
  96. package/dist/client/sessionObservability.d.ts +21 -0
  97. package/dist/client/sessionObservabilityWidget.d.ts +31 -0
  98. package/dist/client/sessionSnapshot.d.ts +23 -0
  99. package/dist/client/sessionSnapshotWidget.d.ts +33 -0
  100. package/dist/client/store.d.ts +1 -1
  101. package/dist/client/traceTimeline.d.ts +21 -0
  102. package/dist/client/traceTimelineWidget.d.ts +36 -0
  103. package/dist/client/turnLatency.d.ts +24 -0
  104. package/dist/client/turnLatencyWidget.d.ts +33 -0
  105. package/dist/client/turnQuality.d.ts +21 -0
  106. package/dist/client/turnQualityWidget.d.ts +32 -0
  107. package/dist/client/voiceWidgetView.d.ts +48 -0
  108. package/dist/client/workflowStatus.d.ts +5 -3
  109. package/dist/{agent.d.ts → core/agent.d.ts} +81 -6
  110. package/dist/core/agentPerformanceReport.d.ts +40 -0
  111. package/dist/core/agentSquadContract.d.ts +98 -0
  112. package/dist/core/agentState.d.ts +12 -0
  113. package/dist/core/agentTools.d.ts +132 -0
  114. package/dist/core/aiScorecard.d.ts +32 -0
  115. package/dist/core/aiVoiceModel.d.ts +15 -0
  116. package/dist/core/amdDetector.d.ts +25 -0
  117. package/dist/{assistant.d.ts → core/assistant.d.ts} +14 -14
  118. package/dist/core/assistantExperiment.d.ts +42 -0
  119. package/dist/{assistantHealth.d.ts → core/assistantHealth.d.ts} +9 -9
  120. package/dist/{assistantMemory.d.ts → core/assistantMemory.d.ts} +10 -10
  121. package/dist/core/assistantMode.d.ts +22 -0
  122. package/dist/{audioConditioning.d.ts → core/audioConditioning.d.ts} +2 -2
  123. package/dist/core/audit.d.ts +131 -0
  124. package/dist/core/auditDeliveryRoutes.d.ts +85 -0
  125. package/dist/core/auditExport.d.ts +34 -0
  126. package/dist/core/auditRoutes.d.ts +66 -0
  127. package/dist/core/auditSinks.d.ts +151 -0
  128. package/dist/core/backchannel.d.ts +18 -0
  129. package/dist/core/bargeInRoutes.d.ts +56 -0
  130. package/dist/core/bookingFlow.d.ts +43 -0
  131. package/dist/core/browserCallProfiles.d.ts +120 -0
  132. package/dist/core/browserMediaRoutes.d.ts +62 -0
  133. package/dist/core/calendarAdapter.d.ts +47 -0
  134. package/dist/core/calendarSlots.d.ts +35 -0
  135. package/dist/core/callDebugger.d.ts +66 -0
  136. package/dist/core/callDisposition.d.ts +38 -0
  137. package/dist/core/callQuota.d.ts +54 -0
  138. package/dist/core/callScorecard.d.ts +53 -0
  139. package/dist/core/callerCRMLinker.d.ts +29 -0
  140. package/dist/core/callerMemory.d.ts +37 -0
  141. package/dist/core/callingWindow.d.ts +26 -0
  142. package/dist/core/campaign.d.ts +795 -0
  143. package/dist/core/campaignControls.d.ts +37 -0
  144. package/dist/core/campaignDialers.d.ts +111 -0
  145. package/dist/core/campaignTemplate.d.ts +16 -0
  146. package/dist/core/competitiveCoverage.d.ts +141 -0
  147. package/dist/core/conversationSimulator.d.ts +73 -0
  148. package/dist/{correction.d.ts → core/correction.d.ts} +5 -4
  149. package/dist/core/costAccounting.d.ts +76 -0
  150. package/dist/core/costPredictor.d.ts +74 -0
  151. package/dist/core/crmCallLogger.d.ts +37 -0
  152. package/dist/core/crmContract.d.ts +70 -0
  153. package/dist/core/dataControl.d.ts +180 -0
  154. package/dist/core/defineVoiceAssistant.d.ts +68 -0
  155. package/dist/core/deliveryRuntime.d.ts +159 -0
  156. package/dist/core/deliverySinkRoutes.d.ts +117 -0
  157. package/dist/core/demoReadyRoutes.d.ts +98 -0
  158. package/dist/{diagnosticsRoutes.d.ts → core/diagnosticsRoutes.d.ts} +2 -2
  159. package/dist/core/dncRegistry.d.ts +38 -0
  160. package/dist/core/dtmfCollector.d.ts +37 -0
  161. package/dist/{evalRoutes.d.ts → core/evalRoutes.d.ts} +26 -20
  162. package/dist/{fileStore.d.ts → core/fileStore.d.ts} +34 -20
  163. package/dist/core/guardrails.d.ts +128 -0
  164. package/dist/{handoff.d.ts → core/handoff.d.ts} +10 -10
  165. package/dist/{handoffHealth.d.ts → core/handoffHealth.d.ts} +9 -9
  166. package/dist/core/holdAudio.d.ts +23 -0
  167. package/dist/{htmx.d.ts → core/htmx.d.ts} +2 -2
  168. package/dist/core/htmxDashboardRoutes.d.ts +250 -0
  169. package/dist/core/iceServers.d.ts +34 -0
  170. package/dist/core/incidentBundle.d.ts +119 -0
  171. package/dist/core/incidentTimeline.d.ts +260 -0
  172. package/dist/core/ivrPlan.d.ts +40 -0
  173. package/dist/core/latencySlo.d.ts +56 -0
  174. package/dist/core/liveCoach.d.ts +43 -0
  175. package/dist/core/liveLatency.d.ts +78 -0
  176. package/dist/core/liveOps.d.ts +190 -0
  177. package/dist/core/llmJudge.d.ts +45 -0
  178. package/dist/{logger.d.ts → core/logger.d.ts} +1 -2
  179. package/dist/core/mcpToolset.d.ts +58 -0
  180. package/dist/core/mediaPipelineRoutes.d.ts +171 -0
  181. package/dist/core/mediaPipelineSurfaces.d.ts +48 -0
  182. package/dist/{memoryStore.d.ts → core/memoryStore.d.ts} +1 -1
  183. package/dist/core/midCallSummary.d.ts +27 -0
  184. package/dist/{modelAdapters.d.ts → core/modelAdapters.d.ts} +59 -7
  185. package/dist/core/monitor.d.ts +148 -0
  186. package/dist/core/multilingualProof.d.ts +77 -0
  187. package/dist/core/noShowPredictor.d.ts +46 -0
  188. package/dist/core/oauth2TokenSource.d.ts +21 -0
  189. package/dist/core/observabilityExport.d.ts +501 -0
  190. package/dist/core/openaiTTS.d.ts +18 -0
  191. package/dist/core/operationalStatus.d.ts +87 -0
  192. package/dist/core/operationsRecord.d.ts +371 -0
  193. package/dist/{ops.d.ts → core/ops.d.ts} +70 -70
  194. package/dist/core/opsActionAuditRoutes.d.ts +99 -0
  195. package/dist/{opsConsoleRoutes.d.ts → core/opsConsoleRoutes.d.ts} +11 -8
  196. package/dist/{opsPresets.d.ts → core/opsPresets.d.ts} +2 -2
  197. package/dist/core/opsRecovery.d.ts +137 -0
  198. package/dist/{opsRuntime.d.ts → core/opsRuntime.d.ts} +6 -6
  199. package/dist/{opsSinks.d.ts → core/opsSinks.d.ts} +19 -19
  200. package/dist/core/opsStatus.d.ts +76 -0
  201. package/dist/core/opsStatusRoutes.d.ts +33 -0
  202. package/dist/{opsWebhook.d.ts → core/opsWebhook.d.ts} +15 -15
  203. package/dist/core/otelExporter.d.ts +83 -0
  204. package/dist/core/outcomeContract.d.ts +146 -0
  205. package/dist/{outcomeRecipes.d.ts → core/outcomeRecipes.d.ts} +4 -4
  206. package/dist/core/pathway.d.ts +94 -0
  207. package/dist/core/pathwayCompiler.d.ts +31 -0
  208. package/dist/core/pathwayGenerator.d.ts +27 -0
  209. package/dist/core/pathwayRuntime.d.ts +57 -0
  210. package/dist/core/pathwaySlotCollector.d.ts +29 -0
  211. package/dist/core/pathwayVisualizer.d.ts +8 -0
  212. package/dist/core/phoneAgent.d.ts +139 -0
  213. package/dist/core/phoneAgentProductionSmoke.d.ts +115 -0
  214. package/dist/core/phoneProvisioning.d.ts +29 -0
  215. package/dist/core/platformCoverage.d.ts +91 -0
  216. package/dist/{plugin.d.ts → core/plugin.d.ts} +2 -2
  217. package/dist/core/postCallAnalysis.d.ts +98 -0
  218. package/dist/core/postCallSurvey.d.ts +41 -0
  219. package/dist/{postgresStore.d.ts → core/postgresStore.d.ts} +20 -9
  220. package/dist/{presets.d.ts → core/presets.d.ts} +3 -3
  221. package/dist/core/productionReadiness.d.ts +757 -0
  222. package/dist/core/profileSwitchRecommendation.d.ts +350 -0
  223. package/dist/core/promptInjectionGuard.d.ts +30 -0
  224. package/dist/core/proofAssertions.d.ts +32 -0
  225. package/dist/core/proofPack.d.ts +211 -0
  226. package/dist/core/proofRunner.d.ts +79 -0
  227. package/dist/core/proofTrends.d.ts +966 -0
  228. package/dist/{providerAdapters.d.ts → core/providerAdapters.d.ts} +16 -5
  229. package/dist/core/providerCapabilities.d.ts +92 -0
  230. package/dist/core/providerDecisionTraces.d.ts +130 -0
  231. package/dist/{providerHealth.d.ts → core/providerHealth.d.ts} +15 -5
  232. package/dist/core/providerOrchestration.d.ts +109 -0
  233. package/dist/core/providerRouterTraces.d.ts +35 -0
  234. package/dist/core/providerRoutingContract.d.ts +71 -0
  235. package/dist/core/providerSlo.d.ts +142 -0
  236. package/dist/core/providerStackRecommendations.d.ts +188 -0
  237. package/dist/core/qualityDriftDetector.d.ts +44 -0
  238. package/dist/{qualityRoutes.d.ts → core/qualityRoutes.d.ts} +7 -7
  239. package/dist/{queue.d.ts → core/queue.d.ts} +48 -39
  240. package/dist/core/ragTool.d.ts +52 -0
  241. package/dist/core/readinessProfiles.d.ts +45 -0
  242. package/dist/core/realtimeChannel.d.ts +136 -0
  243. package/dist/core/realtimeProviderContracts.d.ts +133 -0
  244. package/dist/core/reconnectContract.d.ts +177 -0
  245. package/dist/core/recordingRedaction.d.ts +47 -0
  246. package/dist/core/recordingStore.d.ts +60 -0
  247. package/dist/core/redaction.d.ts +13 -0
  248. package/dist/core/reminderScheduler.d.ts +43 -0
  249. package/dist/{resilienceRoutes.d.ts → core/resilienceRoutes.d.ts} +45 -5
  250. package/dist/core/retention.d.ts +37 -0
  251. package/dist/core/retryPolicy.d.ts +38 -0
  252. package/dist/core/routeAuth.d.ts +58 -0
  253. package/dist/{routing.d.ts → core/routing.d.ts} +2 -2
  254. package/dist/{runtimeOps.d.ts → core/runtimeOps.d.ts} +3 -3
  255. package/dist/{s3Store.d.ts → core/s3Store.d.ts} +12 -3
  256. package/dist/core/scorecardCalibration.d.ts +31 -0
  257. package/dist/core/semanticTurn.d.ts +27 -0
  258. package/dist/{session.d.ts → core/session.d.ts} +1 -1
  259. package/dist/core/sessionObservability.d.ts +145 -0
  260. package/dist/{sessionReplay.d.ts → core/sessionReplay.d.ts} +31 -19
  261. package/dist/core/sessionSnapshot.d.ts +109 -0
  262. package/dist/core/simulationSuite.d.ts +144 -0
  263. package/dist/core/sloCalibration.d.ts +185 -0
  264. package/dist/{sqliteStore.d.ts → core/sqliteStore.d.ts} +20 -9
  265. package/dist/{store.d.ts → core/store.d.ts} +1 -1
  266. package/dist/core/supervisorPermissions.d.ts +33 -0
  267. package/dist/core/supervisorPresence.d.ts +49 -0
  268. package/dist/core/telephonyMediaRoutes.d.ts +72 -0
  269. package/dist/core/telephonyOutcome.d.ts +269 -0
  270. package/dist/core/toolContract.d.ts +161 -0
  271. package/dist/core/toolRuntime.d.ts +50 -0
  272. package/dist/{trace.d.ts → core/trace.d.ts} +61 -22
  273. package/dist/core/traceDeliveryRoutes.d.ts +86 -0
  274. package/dist/core/traceTimeline.d.ts +97 -0
  275. package/dist/core/transcriptAnnotator.d.ts +41 -0
  276. package/dist/{turnDetection.d.ts → core/turnDetection.d.ts} +1 -1
  277. package/dist/core/turnLatency.d.ts +95 -0
  278. package/dist/core/turnProfiles.d.ts +3 -0
  279. package/dist/core/turnQuality.d.ts +94 -0
  280. package/dist/{types.d.ts → core/types.d.ts} +416 -81
  281. package/dist/core/vapiAdapter.d.ts +160 -0
  282. package/dist/core/variableAnalytics.d.ts +47 -0
  283. package/dist/core/voiceConfiguration.d.ts +8 -0
  284. package/dist/core/voiceMonitoring.d.ts +444 -0
  285. package/dist/core/webhookFanout.d.ts +48 -0
  286. package/dist/core/webhookVerification.d.ts +27 -0
  287. package/dist/core/whisperChannel.d.ts +50 -0
  288. package/dist/{workflowContract.d.ts → core/workflowContract.d.ts} +21 -21
  289. package/dist/core/zeroDataRetention.d.ts +31 -0
  290. package/dist/drizzle/assistantMemory.d.ts +112 -0
  291. package/dist/drizzle/eval.d.ts +61 -0
  292. package/dist/drizzle/handoff.d.ts +62 -0
  293. package/dist/drizzle/incidentBundle.d.ts +61 -0
  294. package/dist/drizzle/index.d.ts +1105 -0
  295. package/dist/drizzle/index.js +3059 -0
  296. package/dist/drizzle/observabilityExport.d.ts +61 -0
  297. package/dist/drizzle/proofTrends.d.ts +126 -0
  298. package/dist/drizzle/runtimeStorage.d.ts +1315 -0
  299. package/dist/drizzle/shared.d.ts +76 -0
  300. package/dist/embed/index.d.ts +38 -0
  301. package/dist/embed/index.js +1710 -0
  302. package/dist/embed/voice-widget.js +10 -0
  303. package/dist/index.d.ts +369 -74
  304. package/dist/index.js +45779 -7413
  305. package/dist/internal/evidence.d.ts +10 -0
  306. package/dist/internal/html.d.ts +6 -0
  307. package/dist/internal/status.d.ts +7 -0
  308. package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
  309. package/dist/react/VoiceCallDebuggerLaunch.d.ts +6 -0
  310. package/dist/react/VoiceCallPlayer.d.ts +11 -0
  311. package/dist/react/VoiceCostDashboard.d.ts +10 -0
  312. package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
  313. package/dist/react/VoiceLiveAgentConsole.d.ts +11 -0
  314. package/dist/react/VoiceLiveCallViewer.d.ts +9 -0
  315. package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
  316. package/dist/react/VoiceOpsStatus.d.ts +1 -1
  317. package/dist/react/VoicePlatformCoverage.d.ts +6 -0
  318. package/dist/react/VoiceProfileComparison.d.ts +6 -0
  319. package/dist/react/VoiceProfileSwitchRecommendation.d.ts +6 -0
  320. package/dist/react/VoiceProofTrends.d.ts +6 -0
  321. package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
  322. package/dist/react/VoiceProviderContracts.d.ts +6 -0
  323. package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
  324. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  325. package/dist/react/VoiceReadinessFailures.d.ts +6 -0
  326. package/dist/react/VoiceReconnectProfileEvidence.d.ts +6 -0
  327. package/dist/react/VoiceReplayTimeline.d.ts +6 -0
  328. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  329. package/dist/react/VoiceSessionObservability.d.ts +6 -0
  330. package/dist/react/VoiceSessionSnapshot.d.ts +6 -0
  331. package/dist/react/VoiceTraceTimeline.d.ts +6 -0
  332. package/dist/react/VoiceTurnLatency.d.ts +6 -0
  333. package/dist/react/VoiceTurnQuality.d.ts +6 -0
  334. package/dist/react/VoiceWidget.d.ts +13 -0
  335. package/dist/react/index.d.ts +80 -6
  336. package/dist/react/index.js +12900 -320
  337. package/dist/react/useVoiceAgentSquadStatus.d.ts +8 -0
  338. package/dist/react/useVoiceCallDebugger.d.ts +8 -0
  339. package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
  340. package/dist/react/useVoiceController.d.ts +4 -1
  341. package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
  342. package/dist/react/useVoiceLiveOps.d.ts +9 -0
  343. package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
  344. package/dist/react/useVoiceOpsStatus.d.ts +8 -0
  345. package/dist/react/useVoicePlatformCoverage.d.ts +8 -0
  346. package/dist/react/useVoiceProfileComparison.d.ts +8 -0
  347. package/dist/react/useVoiceProfileSwitchRecommendation.d.ts +8 -0
  348. package/dist/react/useVoiceProofTrends.d.ts +8 -0
  349. package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
  350. package/dist/react/useVoiceProviderContracts.d.ts +8 -0
  351. package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
  352. package/dist/react/useVoiceProviderStatus.d.ts +1 -1
  353. package/dist/react/useVoiceReadinessFailures.d.ts +8 -0
  354. package/dist/react/useVoiceReconnectProfileEvidence.d.ts +8 -0
  355. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  356. package/dist/react/useVoiceSessionObservability.d.ts +8 -0
  357. package/dist/react/useVoiceSessionSnapshot.d.ts +9 -0
  358. package/dist/react/useVoiceStream.d.ts +4 -1
  359. package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
  360. package/dist/react/useVoiceTurnLatency.d.ts +9 -0
  361. package/dist/react/useVoiceTurnQuality.d.ts +8 -0
  362. package/dist/react/useVoiceWorkflowStatus.d.ts +1 -1
  363. package/dist/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
  364. package/dist/svelte/createVoiceCallDebugger.d.ts +10 -0
  365. package/dist/svelte/createVoiceCallPlayer.d.ts +33 -0
  366. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
  367. package/dist/svelte/createVoiceCostDashboard.d.ts +13 -0
  368. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
  369. package/dist/svelte/createVoiceLiveAgentConsole.d.ts +23 -0
  370. package/dist/svelte/createVoiceLiveCallViewer.d.ts +26 -0
  371. package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
  372. package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
  373. package/dist/svelte/createVoiceOpsStatus.d.ts +4 -4
  374. package/dist/svelte/createVoicePlatformCoverage.d.ts +7 -0
  375. package/dist/svelte/createVoiceProfileComparison.d.ts +7 -0
  376. package/dist/svelte/createVoiceProofTrends.d.ts +7 -0
  377. package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
  378. package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
  379. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
  380. package/dist/svelte/createVoiceProviderStatus.d.ts +5 -3
  381. package/dist/svelte/createVoiceReadinessFailures.d.ts +7 -0
  382. package/dist/svelte/createVoiceReconnectProfileEvidence.d.ts +7 -0
  383. package/dist/svelte/createVoiceReplayTimeline.d.ts +13 -0
  384. package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
  385. package/dist/svelte/createVoiceSessionObservability.d.ts +10 -0
  386. package/dist/svelte/createVoiceSessionSnapshot.d.ts +11 -0
  387. package/dist/svelte/createVoiceStream.d.ts +1 -1
  388. package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
  389. package/dist/svelte/createVoiceTurnLatency.d.ts +11 -0
  390. package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
  391. package/dist/svelte/createVoiceWidget.d.ts +19 -0
  392. package/dist/svelte/createVoiceWorkflowStatus.d.ts +2 -2
  393. package/dist/svelte/index.d.ts +37 -6
  394. package/dist/svelte/index.js +7047 -768
  395. package/dist/telephony/contract.d.ts +61 -0
  396. package/dist/telephony/matrix.d.ts +97 -0
  397. package/dist/telephony/plivo.d.ts +303 -0
  398. package/dist/telephony/response.d.ts +1 -1
  399. package/dist/telephony/security.d.ts +182 -0
  400. package/dist/telephony/telnyx.d.ts +291 -0
  401. package/dist/telephony/twilio.d.ts +149 -15
  402. package/dist/testing/accuracy.d.ts +1 -1
  403. package/dist/testing/benchmark.d.ts +15 -15
  404. package/dist/testing/corrected.d.ts +9 -9
  405. package/dist/testing/duplex.d.ts +5 -5
  406. package/dist/testing/fixtures.d.ts +4 -4
  407. package/dist/testing/index.d.ts +13 -13
  408. package/dist/testing/index.js +5756 -636
  409. package/dist/testing/ioProviderSimulator.d.ts +5 -5
  410. package/dist/testing/providerSimulator.d.ts +5 -5
  411. package/dist/testing/review.d.ts +8 -8
  412. package/dist/testing/sessionBenchmark.d.ts +13 -13
  413. package/dist/testing/stt.d.ts +3 -3
  414. package/dist/testing/telephony.d.ts +27 -2
  415. package/dist/testing/tts.d.ts +2 -2
  416. package/dist/vue/VoiceCallDebuggerLaunch.d.ts +72 -0
  417. package/dist/vue/VoiceCallPlayer.d.ts +40 -0
  418. package/dist/vue/VoiceCostDashboard.d.ts +57 -0
  419. package/dist/vue/VoiceDeliveryRuntime.d.ts +34 -0
  420. package/dist/vue/VoiceLiveAgentConsole.d.ts +50 -0
  421. package/dist/vue/VoiceLiveCallViewer.d.ts +35 -0
  422. package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
  423. package/dist/vue/VoiceOpsStatus.d.ts +4 -0
  424. package/dist/vue/VoicePlatformCoverage.d.ts +27 -0
  425. package/dist/vue/VoiceProofTrends.d.ts +25 -0
  426. package/dist/vue/VoiceProviderCapabilities.d.ts +55 -0
  427. package/dist/vue/VoiceProviderContracts.d.ts +25 -0
  428. package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
  429. package/dist/vue/VoiceProviderStatus.d.ts +55 -0
  430. package/dist/vue/VoiceReadinessFailures.d.ts +25 -0
  431. package/dist/vue/VoiceReconnectProfileEvidence.d.ts +25 -0
  432. package/dist/vue/VoiceReplayTimeline.d.ts +17 -0
  433. package/dist/vue/VoiceRoutingStatus.d.ts +55 -0
  434. package/dist/vue/VoiceSessionObservability.d.ts +27 -0
  435. package/dist/vue/VoiceSessionSnapshot.d.ts +72 -0
  436. package/dist/vue/VoiceTurnLatency.d.ts +73 -0
  437. package/dist/vue/VoiceTurnQuality.d.ts +55 -0
  438. package/dist/vue/VoiceWidget.d.ts +77 -0
  439. package/dist/vue/index.d.ts +49 -6
  440. package/dist/vue/index.js +12193 -402
  441. package/dist/vue/useVoiceAgentSquadStatus.d.ts +9 -0
  442. package/dist/vue/useVoiceCallDebugger.d.ts +10 -0
  443. package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
  444. package/dist/vue/useVoiceController.d.ts +9 -7
  445. package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
  446. package/dist/vue/useVoiceLiveOps.d.ts +9 -0
  447. package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
  448. package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
  449. package/dist/vue/useVoicePlatformCoverage.d.ts +9 -0
  450. package/dist/vue/useVoiceProfileComparison.d.ts +9 -0
  451. package/dist/vue/useVoiceProofTrends.d.ts +9 -0
  452. package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
  453. package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
  454. package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
  455. package/dist/vue/useVoiceProviderStatus.d.ts +3 -3
  456. package/dist/vue/useVoiceReadinessFailures.d.ts +959 -0
  457. package/dist/vue/useVoiceReconnectProfileEvidence.d.ts +9 -0
  458. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  459. package/dist/vue/useVoiceSessionObservability.d.ts +9 -0
  460. package/dist/vue/useVoiceSessionSnapshot.d.ts +10 -0
  461. package/dist/vue/useVoiceStream.d.ts +9 -6
  462. package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
  463. package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
  464. package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
  465. package/dist/vue/useVoiceWorkflowStatus.d.ts +3 -3
  466. package/package.json +154 -256
  467. package/dist/angular/voice-app-kit-status.service.d.ts +0 -12
  468. package/dist/angular/voice-ops-status.component.d.ts +0 -15
  469. package/dist/appKit.d.ts +0 -92
  470. package/dist/client/appKitStatus.d.ts +0 -19
  471. package/dist/react/useVoiceAppKitStatus.d.ts +0 -8
  472. package/dist/svelte/createVoiceAppKitStatus.d.ts +0 -8
  473. package/dist/turnProfiles.d.ts +0 -6
  474. package/dist/vue/useVoiceAppKitStatus.d.ts +0 -9
  475. package/fixtures/README.md +0 -57
  476. package/fixtures/manifest.json +0 -199
  477. package/fixtures/pcm/dialogue-three-clean.pcm +0 -0
  478. package/fixtures/pcm/dialogue-three-mixed.pcm +0 -0
  479. package/fixtures/pcm/dialogue-two-clean.pcm +0 -0
  480. package/fixtures/pcm/dialogue-two-noisy.pcm +0 -0
  481. package/fixtures/pcm/multiturn-three-mixed.pcm +0 -0
  482. package/fixtures/pcm/multiturn-two-clean.pcm +0 -0
  483. package/fixtures/pcm/quietly-alone-clean.pcm +0 -0
  484. package/fixtures/pcm/rainstorms-noisy.pcm +0 -0
  485. package/fixtures/pcm/stella-bulgaria-bulgarian20.pcm +0 -0
  486. package/fixtures/pcm/stella-ghana-english507.pcm +0 -0
  487. package/fixtures/pcm/stella-india-english37.pcm +0 -0
  488. package/fixtures/pcm/stella-jamaica-jamaican-creole-english1.pcm +0 -0
  489. package/fixtures/pcm/stella-liberia-liberian-pidgin-english2.pcm +0 -0
  490. package/fixtures/pcm/stella-pakistan-english519.pcm +0 -0
  491. package/fixtures/pcm/stella-sierra-leone-krio5.pcm +0 -0
  492. package/fixtures/pcm/stella-singapore-english655.pcm +0 -0
  493. package/fixtures/pcm/traveled-back-route-clean.pcm +0 -0
@@ -1,3 +1,6 @@
1
+ // src/internal/html.ts
2
+ var escapeHtml = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3
+
1
4
  // src/client/htmx.ts
2
5
  var DEFAULT_EVENT_NAME = "voice-refresh";
3
6
  var DEFAULT_QUERY_PARAM = "sessionId";
@@ -188,6 +191,11 @@ var serverMessageToAction = (message) => {
188
191
  sessionId: message.sessionId,
189
192
  type: "complete"
190
193
  };
194
+ case "connection":
195
+ return {
196
+ reconnect: message.reconnect,
197
+ type: "connection"
198
+ };
191
199
  case "call_lifecycle":
192
200
  return {
193
201
  event: message.event,
@@ -209,9 +217,22 @@ var serverMessageToAction = (message) => {
209
217
  transcript: message.transcript,
210
218
  type: "partial"
211
219
  };
220
+ case "replay":
221
+ return {
222
+ assistantTexts: message.assistantTexts,
223
+ call: message.call,
224
+ partial: message.partial,
225
+ scenarioId: message.scenarioId,
226
+ sessionId: message.sessionId,
227
+ sessionMetadata: message.sessionMetadata,
228
+ status: message.status,
229
+ turns: message.turns,
230
+ type: "replay"
231
+ };
212
232
  case "session":
213
233
  return {
214
234
  sessionId: message.sessionId,
235
+ sessionMetadata: message.sessionMetadata,
215
236
  scenarioId: message.scenarioId,
216
237
  status: message.status,
217
238
  type: "session"
@@ -226,6 +247,233 @@ var serverMessageToAction = (message) => {
226
247
  }
227
248
  };
228
249
 
250
+ // node_modules/@absolutejs/media/dist/index.js
251
+ var TAU = Math.PI * 2;
252
+ var pushIssue = (issues, severity, code, message) => {
253
+ issues.push({ code, message, severity });
254
+ };
255
+ var average = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
256
+ var max = (values) => values.length === 0 ? undefined : Math.max(...values);
257
+ var numericStat = (stat, key) => {
258
+ const value = stat[key];
259
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
260
+ };
261
+ var booleanStat = (stat, key) => {
262
+ const value = stat[key];
263
+ return typeof value === "boolean" ? value : undefined;
264
+ };
265
+ var stringStat = (stat, key) => {
266
+ const value = stat[key];
267
+ return typeof value === "string" ? value : undefined;
268
+ };
269
+ var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
270
+ var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
271
+ var normalizeWebRTCStat = (stat) => {
272
+ const sample = {};
273
+ for (const [key, value] of Object.entries(stat)) {
274
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
275
+ sample[key] = value;
276
+ }
277
+ }
278
+ return sample;
279
+ };
280
+ var buildMediaWebRTCStatsReport = (input = {}) => {
281
+ const stats = input.stats ?? [];
282
+ const issues = [];
283
+ const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
284
+ const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
285
+ const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
286
+ const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
287
+ const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
288
+ const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
289
+ const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
290
+ const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
291
+ const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
292
+ const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
293
+ const packetLossDenominator = inboundPackets + packetsLost;
294
+ const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
295
+ const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
296
+ const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
297
+ const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
298
+ const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
299
+ const jitterBufferDelayMs = max(inbound.map((stat) => {
300
+ const delay = numericStat(stat, "jitterBufferDelay");
301
+ const emitted = numericStat(stat, "jitterBufferEmittedCount");
302
+ return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
303
+ }).filter((value) => value !== undefined));
304
+ const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
305
+ if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
306
+ pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
307
+ }
308
+ if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
309
+ pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
310
+ }
311
+ if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
312
+ pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
313
+ }
314
+ if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
315
+ pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
316
+ }
317
+ if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
318
+ pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
319
+ }
320
+ return {
321
+ activeCandidatePairs,
322
+ audioLevelAverage: average(audioLevels),
323
+ bytesReceived,
324
+ bytesSent,
325
+ checkedAt: Date.now(),
326
+ endedAudioTracks,
327
+ inboundPackets,
328
+ issues,
329
+ jitterBufferDelayMs,
330
+ jitterMs,
331
+ liveAudioTracks,
332
+ outboundPackets,
333
+ packetLossRatio,
334
+ packetsLost,
335
+ roundTripTimeMs,
336
+ status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
337
+ totalStats: stats.length
338
+ };
339
+ };
340
+ var collectMediaWebRTCStats = async (input) => {
341
+ const report = await input.peerConnection.getStats(input.selector ?? null);
342
+ return [...report.values()].map(normalizeWebRTCStat);
343
+ };
344
+ var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
345
+ const stats = input.stats ?? [];
346
+ const previousStats = input.previousStats ?? [];
347
+ const issues = [];
348
+ const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
349
+ const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
350
+ const streams = audioRtp.map((stat) => {
351
+ const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
352
+ const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
353
+ const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
354
+ const previous = previousByKey.get(statKey(stat));
355
+ const currentPackets = numericStat(stat, packetsKey);
356
+ const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
357
+ const currentBytes = numericStat(stat, bytesKey);
358
+ const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
359
+ const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
360
+ return {
361
+ bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
362
+ currentPackets,
363
+ direction,
364
+ id: statKey(stat),
365
+ packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
366
+ previousPackets,
367
+ timeDeltaMs
368
+ };
369
+ });
370
+ const inbound = streams.filter((stream) => stream.direction === "inbound");
371
+ const outbound = streams.filter((stream) => stream.direction === "outbound");
372
+ const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
373
+ const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
374
+ const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
375
+ if (input.requireInboundAudio && inbound.length === 0) {
376
+ pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
377
+ }
378
+ if (input.requireOutboundAudio && outbound.length === 0) {
379
+ pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
380
+ }
381
+ if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
382
+ pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
383
+ }
384
+ if (stalledInboundStreams > 0) {
385
+ pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
386
+ }
387
+ if (stalledOutboundStreams > 0) {
388
+ pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
389
+ }
390
+ return {
391
+ checkedAt: Date.now(),
392
+ inboundAudioStreams: inbound.length,
393
+ issues,
394
+ maxObservedGapMs,
395
+ outboundAudioStreams: outbound.length,
396
+ stalledInboundStreams,
397
+ stalledOutboundStreams,
398
+ status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
399
+ streams,
400
+ totalStats: stats.length
401
+ };
402
+ };
403
+
404
+ // src/client/browserMedia.ts
405
+ var DEFAULT_BROWSER_MEDIA_PATH = "/api/voice/browser-media";
406
+ var DEFAULT_BROWSER_MEDIA_INTERVAL_MS = 5000;
407
+ var resolvePeerConnection = async (options) => options.peerConnection ?? await options.getPeerConnection?.() ?? null;
408
+ var postBrowserMediaReport = async (payload, options) => {
409
+ const requestFetch = options.fetch ?? globalThis.fetch;
410
+ if (!requestFetch) {
411
+ return;
412
+ }
413
+ await requestFetch(options.path ?? DEFAULT_BROWSER_MEDIA_PATH, {
414
+ body: JSON.stringify(payload),
415
+ headers: {
416
+ "Content-Type": "application/json"
417
+ },
418
+ keepalive: true,
419
+ method: "POST"
420
+ });
421
+ };
422
+ var createVoiceBrowserMediaReporter = (options) => {
423
+ let interval = null;
424
+ let previousStats = [];
425
+ const reportOnce = async () => {
426
+ const peerConnection = await resolvePeerConnection(options);
427
+ if (!peerConnection) {
428
+ return;
429
+ }
430
+ const stats = await collectMediaWebRTCStats({ peerConnection });
431
+ const report = buildMediaWebRTCStatsReport({
432
+ ...options,
433
+ stats
434
+ });
435
+ const continuity = options.continuity === false ? undefined : buildMediaWebRTCStreamContinuityReport({
436
+ ...options.continuity,
437
+ previousStats,
438
+ stats
439
+ });
440
+ const payload = {
441
+ at: Date.now(),
442
+ continuity,
443
+ report,
444
+ scenarioId: options.getScenarioId?.() ?? null,
445
+ sessionId: options.getSessionId?.() ?? null
446
+ };
447
+ previousStats = stats;
448
+ options.onReport?.(payload);
449
+ await postBrowserMediaReport(payload, options);
450
+ return payload;
451
+ };
452
+ const run = () => {
453
+ reportOnce().catch((error) => {
454
+ options.onError?.(error);
455
+ });
456
+ };
457
+ const stop = () => {
458
+ if (interval) {
459
+ clearInterval(interval);
460
+ interval = null;
461
+ }
462
+ };
463
+ return {
464
+ close: stop,
465
+ reportOnce,
466
+ stop,
467
+ start: () => {
468
+ if (interval) {
469
+ return;
470
+ }
471
+ run();
472
+ interval = setInterval(run, options.intervalMs ?? DEFAULT_BROWSER_MEDIA_INTERVAL_MS);
473
+ }
474
+ };
475
+ };
476
+
229
477
  // src/client/connection.ts
230
478
  var WS_OPEN = 1;
231
479
  var WS_CLOSED = 3;
@@ -240,13 +488,14 @@ var NOOP_CONNECTION = {
240
488
  callControl: noop,
241
489
  close: noop,
242
490
  endTurn: noop,
491
+ send: noop,
492
+ sendAudio: noop,
493
+ simulateDisconnect: noop,
494
+ subscribe: noopUnsubscribe,
243
495
  getReadyState: () => WS_CLOSED,
244
496
  getScenarioId: () => "",
245
497
  getSessionId: () => "",
246
- send: noop,
247
- sendAudio: noop,
248
- start: () => {},
249
- subscribe: noopUnsubscribe
498
+ start: () => {}
250
499
  };
251
500
  var createSessionId = () => crypto.randomUUID();
252
501
  var buildWsUrl = (path, sessionId, scenarioId) => {
@@ -269,10 +518,12 @@ var isVoiceServerMessage = (value) => {
269
518
  case "assistant":
270
519
  case "call_lifecycle":
271
520
  case "complete":
521
+ case "connection":
272
522
  case "error":
273
523
  case "final":
274
524
  case "partial":
275
525
  case "pong":
526
+ case "replay":
276
527
  case "session":
277
528
  case "turn":
278
529
  return true;
@@ -309,6 +560,9 @@ var createVoiceConnection = (path, options = {}) => {
309
560
  sessionId: options.sessionId ?? createSessionId(),
310
561
  ws: null
311
562
  };
563
+ const emitConnection = (reconnect) => {
564
+ listeners.forEach((listener) => listener(reconnect));
565
+ };
312
566
  const clearTimers = () => {
313
567
  if (state.pingInterval) {
314
568
  clearInterval(state.pingInterval);
@@ -331,9 +585,28 @@ var createVoiceConnection = (path, options = {}) => {
331
585
  }
332
586
  };
333
587
  const scheduleReconnect = () => {
588
+ const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
334
589
  state.reconnectAttempts += 1;
590
+ emitConnection({
591
+ reconnect: {
592
+ attempts: state.reconnectAttempts,
593
+ lastDisconnectAt: Date.now(),
594
+ maxAttempts: maxReconnectAttempts,
595
+ nextAttemptAt,
596
+ status: "reconnecting"
597
+ },
598
+ type: "connection"
599
+ });
335
600
  state.reconnectTimeout = setTimeout(() => {
336
601
  if (state.reconnectAttempts > maxReconnectAttempts) {
602
+ emitConnection({
603
+ reconnect: {
604
+ attempts: state.reconnectAttempts,
605
+ maxAttempts: maxReconnectAttempts,
606
+ status: "exhausted"
607
+ },
608
+ type: "connection"
609
+ });
337
610
  return;
338
611
  }
339
612
  connect();
@@ -343,9 +616,21 @@ var createVoiceConnection = (path, options = {}) => {
343
616
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
344
617
  ws.binaryType = "arraybuffer";
345
618
  ws.onopen = () => {
619
+ const wasReconnecting = state.reconnectAttempts > 0;
346
620
  state.isConnected = true;
347
- state.reconnectAttempts = 0;
348
621
  flushPendingMessages();
622
+ if (wasReconnecting) {
623
+ emitConnection({
624
+ reconnect: {
625
+ attempts: state.reconnectAttempts,
626
+ lastResumedAt: Date.now(),
627
+ maxAttempts: maxReconnectAttempts,
628
+ status: "resumed"
629
+ },
630
+ type: "connection"
631
+ });
632
+ state.reconnectAttempts = 0;
633
+ }
349
634
  listeners.forEach((listener) => listener({
350
635
  scenarioId: state.scenarioId ?? undefined,
351
636
  sessionId: state.sessionId,
@@ -375,6 +660,16 @@ var createVoiceConnection = (path, options = {}) => {
375
660
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
376
661
  if (reconnectable) {
377
662
  scheduleReconnect();
663
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
664
+ emitConnection({
665
+ reconnect: {
666
+ attempts: state.reconnectAttempts,
667
+ lastDisconnectAt: Date.now(),
668
+ maxAttempts: maxReconnectAttempts,
669
+ status: "exhausted"
670
+ },
671
+ type: "connection"
672
+ });
378
673
  }
379
674
  };
380
675
  state.ws = ws;
@@ -397,9 +692,9 @@ var createVoiceConnection = (path, options = {}) => {
397
692
  state.scenarioId = input.scenarioId;
398
693
  }
399
694
  send({
400
- type: "start",
695
+ scenarioId: state.scenarioId ?? undefined,
401
696
  sessionId: state.sessionId,
402
- scenarioId: state.scenarioId ?? undefined
697
+ type: "start"
403
698
  });
404
699
  };
405
700
  const sendAudio = (audio) => {
@@ -423,6 +718,11 @@ var createVoiceConnection = (path, options = {}) => {
423
718
  state.isConnected = false;
424
719
  listeners.clear();
425
720
  };
721
+ const simulateDisconnect = () => {
722
+ if (state.ws?.readyState === WS_OPEN) {
723
+ state.ws.close(4000, "absolutejs-voice-reconnect-proof");
724
+ }
725
+ };
426
726
  const subscribe = (callback) => {
427
727
  listeners.add(callback);
428
728
  return () => {
@@ -434,26 +734,34 @@ var createVoiceConnection = (path, options = {}) => {
434
734
  callControl,
435
735
  close,
436
736
  endTurn,
437
- getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
438
- getScenarioId: () => state.scenarioId ?? "",
439
- getSessionId: () => state.sessionId,
440
737
  send,
441
738
  sendAudio,
739
+ simulateDisconnect,
442
740
  start,
443
- subscribe
741
+ subscribe,
742
+ getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
743
+ getScenarioId: () => state.scenarioId ?? "",
744
+ getSessionId: () => state.sessionId
444
745
  };
445
746
  };
446
747
 
447
748
  // src/client/store.ts
749
+ var createInitialReconnectState = () => ({
750
+ attempts: 0,
751
+ maxAttempts: 0,
752
+ status: "idle"
753
+ });
448
754
  var createInitialState = () => ({
449
755
  assistantAudio: [],
450
756
  assistantTexts: [],
451
757
  call: null,
452
758
  error: null,
453
759
  isConnected: false,
454
- scenarioId: null,
455
760
  partial: "",
761
+ reconnect: createInitialReconnectState(),
762
+ scenarioId: null,
456
763
  sessionId: null,
764
+ sessionMetadata: null,
457
765
  status: "idle",
458
766
  turns: []
459
767
  });
@@ -509,7 +817,19 @@ var createVoiceStreamStore = () => {
509
817
  case "connected":
510
818
  state = {
511
819
  ...state,
512
- isConnected: true
820
+ isConnected: true,
821
+ reconnect: state.reconnect.status === "reconnecting" ? {
822
+ ...state.reconnect,
823
+ lastResumedAt: Date.now(),
824
+ nextAttemptAt: undefined,
825
+ status: "resumed"
826
+ } : state.reconnect
827
+ };
828
+ break;
829
+ case "connection":
830
+ state = {
831
+ ...state,
832
+ reconnect: action.reconnect
513
833
  };
514
834
  break;
515
835
  case "disconnected":
@@ -537,6 +857,27 @@ var createVoiceStreamStore = () => {
537
857
  partial: action.transcript.text
538
858
  };
539
859
  break;
860
+ case "replay":
861
+ state = {
862
+ ...state,
863
+ assistantTexts: [...action.assistantTexts],
864
+ call: action.call ?? null,
865
+ error: null,
866
+ isConnected: action.status === "active",
867
+ partial: action.partial,
868
+ reconnect: state.reconnect.status === "reconnecting" ? {
869
+ ...state.reconnect,
870
+ lastResumedAt: Date.now(),
871
+ nextAttemptAt: undefined,
872
+ status: "resumed"
873
+ } : state.reconnect,
874
+ scenarioId: action.scenarioId ?? state.scenarioId,
875
+ sessionId: action.sessionId,
876
+ sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
877
+ status: action.status,
878
+ turns: [...action.turns]
879
+ };
880
+ break;
540
881
  case "session":
541
882
  state = {
542
883
  ...state,
@@ -544,6 +885,7 @@ var createVoiceStreamStore = () => {
544
885
  scenarioId: action.scenarioId ?? state.scenarioId,
545
886
  isConnected: action.status === "active",
546
887
  sessionId: action.sessionId,
888
+ sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
547
889
  status: action.status
548
890
  };
549
891
  break;
@@ -574,29 +916,70 @@ var createVoiceStreamStore = () => {
574
916
  var createVoiceStream = (path, options = {}) => {
575
917
  const connection = createVoiceConnection(path, options);
576
918
  const store = createVoiceStreamStore();
919
+ const browserMediaReporter = options.browserMedia && typeof window !== "undefined" ? createVoiceBrowserMediaReporter({
920
+ ...options.browserMedia,
921
+ getScenarioId: () => options.browserMedia ? options.browserMedia.getScenarioId?.() ?? connection.getScenarioId() : connection.getScenarioId(),
922
+ getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
923
+ }) : null;
577
924
  const subscribers = new Set;
578
925
  const start = (input) => Promise.resolve().then(() => {
579
926
  if (!input?.sessionId && !input?.scenarioId) {
580
927
  return;
581
928
  }
582
929
  connection.start(input);
930
+ browserMediaReporter?.start();
583
931
  });
584
932
  const notify = () => {
585
933
  subscribers.forEach((subscriber) => subscriber());
586
934
  };
935
+ const reportReconnect = () => {
936
+ if (!options.reconnectReportPath || typeof fetch === "undefined") {
937
+ return;
938
+ }
939
+ const snapshot = store.getSnapshot();
940
+ const body = JSON.stringify({
941
+ at: Date.now(),
942
+ reconnect: snapshot.reconnect,
943
+ scenarioId: snapshot.scenarioId,
944
+ sessionId: connection.getSessionId(),
945
+ turnIds: snapshot.turns.map((turn) => turn.id)
946
+ });
947
+ fetch(options.reconnectReportPath, {
948
+ body,
949
+ headers: {
950
+ "Content-Type": "application/json"
951
+ },
952
+ keepalive: true,
953
+ method: "POST"
954
+ }).catch(() => {});
955
+ };
587
956
  const unsubscribeConnection = connection.subscribe((message) => {
588
957
  const action = serverMessageToAction(message);
589
958
  if (action) {
590
959
  store.dispatch(action);
960
+ if (message.type === "connection") {
961
+ reportReconnect();
962
+ }
591
963
  notify();
592
964
  }
593
965
  });
594
966
  return {
967
+ start,
968
+ get assistantAudio() {
969
+ return store.getSnapshot().assistantAudio;
970
+ },
971
+ get assistantTexts() {
972
+ return store.getSnapshot().assistantTexts;
973
+ },
974
+ get call() {
975
+ return store.getSnapshot().call;
976
+ },
595
977
  callControl(message) {
596
978
  connection.callControl(message);
597
979
  },
598
980
  close() {
599
981
  unsubscribeConnection();
982
+ browserMediaReporter?.close();
600
983
  connection.close();
601
984
  store.dispatch({ type: "disconnected" });
602
985
  notify();
@@ -616,44 +999,43 @@ var createVoiceStream = (path, options = {}) => {
616
999
  get isConnected() {
617
1000
  return store.getSnapshot().isConnected;
618
1001
  },
619
- get scenarioId() {
620
- return store.getSnapshot().scenarioId;
621
- },
622
- start,
623
1002
  get partial() {
624
1003
  return store.getSnapshot().partial;
625
1004
  },
626
- get sessionId() {
627
- return connection.getSessionId();
1005
+ get reconnect() {
1006
+ return store.getSnapshot().reconnect;
628
1007
  },
629
- get status() {
630
- return store.getSnapshot().status;
1008
+ get scenarioId() {
1009
+ return store.getSnapshot().scenarioId;
631
1010
  },
632
- get turns() {
633
- return store.getSnapshot().turns;
1011
+ sendAudio(audio) {
1012
+ connection.sendAudio(audio);
634
1013
  },
635
- get assistantTexts() {
636
- return store.getSnapshot().assistantTexts;
1014
+ get sessionId() {
1015
+ return connection.getSessionId();
637
1016
  },
638
- get assistantAudio() {
639
- return store.getSnapshot().assistantAudio;
1017
+ get sessionMetadata() {
1018
+ return store.getSnapshot().sessionMetadata;
640
1019
  },
641
- get call() {
642
- return store.getSnapshot().call;
1020
+ simulateDisconnect() {
1021
+ connection.simulateDisconnect();
643
1022
  },
644
- sendAudio(audio) {
645
- connection.sendAudio(audio);
1023
+ get status() {
1024
+ return store.getSnapshot().status;
646
1025
  },
647
1026
  subscribe(subscriber) {
648
1027
  subscribers.add(subscriber);
649
1028
  return () => {
650
1029
  subscribers.delete(subscriber);
651
1030
  };
1031
+ },
1032
+ get turns() {
1033
+ return store.getSnapshot().turns;
652
1034
  }
653
1035
  };
654
1036
  };
655
1037
 
656
- // src/audioConditioning.ts
1038
+ // src/core/audioConditioning.ts
657
1039
  var DEFAULT_TARGET_LEVEL = 0.08;
658
1040
  var DEFAULT_MAX_GAIN = 3;
659
1041
  var DEFAULT_NOISE_GATE_THRESHOLD = 0.006;
@@ -671,7 +1053,7 @@ var resolveAudioConditioningConfig = (config) => {
671
1053
  };
672
1054
  };
673
1055
 
674
- // src/turnProfiles.ts
1056
+ // src/core/turnProfiles.ts
675
1057
  var TURN_PROFILE_DEFAULTS = {
676
1058
  balanced: {
677
1059
  qualityProfile: "general",
@@ -693,12 +1075,12 @@ var TURN_PROFILE_DEFAULTS = {
693
1075
  }
694
1076
  };
695
1077
  var QUALITY_PROFILE_DEFAULTS = {
696
- general: {},
697
1078
  "accent-heavy": {
698
1079
  silenceMs: 1200,
699
1080
  speechThreshold: 0.01,
700
1081
  transcriptStabilityMs: 1200
701
1082
  },
1083
+ general: {},
702
1084
  "noisy-room": {
703
1085
  silenceMs: 2000,
704
1086
  speechThreshold: 0.02,
@@ -726,7 +1108,7 @@ var resolveTurnDetectionConfig = (config) => {
726
1108
  };
727
1109
  };
728
1110
 
729
- // src/presets.ts
1111
+ // src/core/presets.ts
730
1112
  var PRESET_INPUTS = {
731
1113
  chat: {
732
1114
  audioConditioning: {
@@ -747,8 +1129,8 @@ var PRESET_INPUTS = {
747
1129
  },
748
1130
  sttLifecycle: "continuous",
749
1131
  turnDetection: {
750
- qualityProfile: "short-command",
751
- profile: "balanced"
1132
+ profile: "balanced",
1133
+ qualityProfile: "short-command"
752
1134
  }
753
1135
  },
754
1136
  default: {
@@ -763,8 +1145,8 @@ var PRESET_INPUTS = {
763
1145
  },
764
1146
  sttLifecycle: "continuous",
765
1147
  turnDetection: {
766
- qualityProfile: "general",
767
- profile: "fast"
1148
+ profile: "fast",
1149
+ qualityProfile: "general"
768
1150
  }
769
1151
  },
770
1152
  dictation: {
@@ -786,8 +1168,8 @@ var PRESET_INPUTS = {
786
1168
  },
787
1169
  sttLifecycle: "continuous",
788
1170
  turnDetection: {
789
- qualityProfile: "accent-heavy",
790
- profile: "long-form"
1171
+ profile: "long-form",
1172
+ qualityProfile: "accent-heavy"
791
1173
  }
792
1174
  },
793
1175
  "guided-intake": {
@@ -809,8 +1191,8 @@ var PRESET_INPUTS = {
809
1191
  },
810
1192
  sttLifecycle: "turn-scoped",
811
1193
  turnDetection: {
812
- qualityProfile: "accent-heavy",
813
- profile: "long-form"
1194
+ profile: "long-form",
1195
+ qualityProfile: "accent-heavy"
814
1196
  }
815
1197
  },
816
1198
  "noisy-room": {
@@ -832,8 +1214,8 @@ var PRESET_INPUTS = {
832
1214
  },
833
1215
  sttLifecycle: "continuous",
834
1216
  turnDetection: {
835
- qualityProfile: "noisy-room",
836
1217
  profile: "long-form",
1218
+ qualityProfile: "noisy-room",
837
1219
  silenceMs: 2100,
838
1220
  speechThreshold: 0.02,
839
1221
  transcriptStabilityMs: 1650
@@ -858,8 +1240,8 @@ var PRESET_INPUTS = {
858
1240
  },
859
1241
  sttLifecycle: "continuous",
860
1242
  turnDetection: {
861
- qualityProfile: "noisy-room",
862
1243
  profile: "long-form",
1244
+ qualityProfile: "noisy-room",
863
1245
  silenceMs: 660,
864
1246
  speechThreshold: 0.012,
865
1247
  transcriptStabilityMs: 300
@@ -884,8 +1266,8 @@ var PRESET_INPUTS = {
884
1266
  },
885
1267
  sttLifecycle: "continuous",
886
1268
  turnDetection: {
887
- qualityProfile: "noisy-room",
888
1269
  profile: "long-form",
1270
+ qualityProfile: "noisy-room",
889
1271
  silenceMs: 620,
890
1272
  speechThreshold: 0.012,
891
1273
  transcriptStabilityMs: 280
@@ -910,8 +1292,8 @@ var PRESET_INPUTS = {
910
1292
  },
911
1293
  sttLifecycle: "continuous",
912
1294
  turnDetection: {
913
- qualityProfile: "noisy-room",
914
- profile: "long-form"
1295
+ profile: "long-form",
1296
+ qualityProfile: "noisy-room"
915
1297
  }
916
1298
  }
917
1299
  };
@@ -941,8 +1323,10 @@ var createInitialState2 = (stream) => ({
941
1323
  isConnected: stream.isConnected,
942
1324
  isRecording: false,
943
1325
  partial: stream.partial,
1326
+ reconnect: stream.reconnect,
944
1327
  recordingError: null,
945
1328
  sessionId: stream.sessionId,
1329
+ sessionMetadata: stream.sessionMetadata,
946
1330
  scenarioId: stream.scenarioId,
947
1331
  status: stream.status,
948
1332
  turns: [...stream.turns]
@@ -970,7 +1354,9 @@ var createVoiceController = (path, options = {}) => {
970
1354
  error: stream.error,
971
1355
  isConnected: stream.isConnected,
972
1356
  partial: stream.partial,
1357
+ reconnect: stream.reconnect,
973
1358
  sessionId: stream.sessionId,
1359
+ sessionMetadata: stream.sessionMetadata,
974
1360
  scenarioId: stream.scenarioId,
975
1361
  status: stream.status,
976
1362
  turns: [...stream.turns]
@@ -994,7 +1380,13 @@ var createVoiceController = (path, options = {}) => {
994
1380
  capture = createMicrophoneCapture({
995
1381
  channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
996
1382
  onLevel: options.capture?.onLevel,
997
- onAudio: (audio) => stream.sendAudio(audio),
1383
+ onAudio: (audio) => {
1384
+ if (options.capture?.onAudio) {
1385
+ options.capture.onAudio(audio, stream.sendAudio);
1386
+ return;
1387
+ }
1388
+ stream.sendAudio(audio);
1389
+ },
998
1390
  sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
999
1391
  });
1000
1392
  return capture;
@@ -1041,11 +1433,22 @@ var createVoiceController = (path, options = {}) => {
1041
1433
  stream.close();
1042
1434
  };
1043
1435
  return {
1436
+ close,
1437
+ startRecording,
1438
+ stopRecording,
1439
+ get assistantAudio() {
1440
+ return state.assistantAudio;
1441
+ },
1442
+ get assistantTexts() {
1443
+ return state.assistantTexts;
1444
+ },
1044
1445
  bindHTMX(bindingOptions) {
1045
1446
  return bindVoiceHTMX(stream, bindingOptions);
1046
1447
  },
1448
+ get call() {
1449
+ return state.call;
1450
+ },
1047
1451
  callControl: (message) => stream.callControl(message),
1048
- close,
1049
1452
  endTurn: () => stream.endTurn(),
1050
1453
  get error() {
1051
1454
  return state.error;
@@ -1061,21 +1464,26 @@ var createVoiceController = (path, options = {}) => {
1061
1464
  get partial() {
1062
1465
  return state.partial;
1063
1466
  },
1467
+ get reconnect() {
1468
+ return state.reconnect;
1469
+ },
1064
1470
  get recordingError() {
1065
1471
  return state.recordingError;
1066
1472
  },
1473
+ get scenarioId() {
1474
+ return state.scenarioId;
1475
+ },
1067
1476
  sendAudio: (audio) => stream.sendAudio(audio),
1068
1477
  get sessionId() {
1069
1478
  return state.sessionId;
1070
1479
  },
1071
- get scenarioId() {
1072
- return state.scenarioId;
1480
+ get sessionMetadata() {
1481
+ return state.sessionMetadata;
1073
1482
  },
1074
- startRecording,
1483
+ simulateDisconnect: () => stream.simulateDisconnect(),
1075
1484
  get status() {
1076
1485
  return state.status;
1077
1486
  },
1078
- stopRecording,
1079
1487
  subscribe: (subscriber) => {
1080
1488
  subscribers.add(subscriber);
1081
1489
  return () => {
@@ -1091,15 +1499,475 @@ var createVoiceController = (path, options = {}) => {
1091
1499
  },
1092
1500
  get turns() {
1093
1501
  return state.turns;
1502
+ }
1503
+ };
1504
+ };
1505
+
1506
+ // src/client/audioPlayer.ts
1507
+ var DEFAULT_LOOKAHEAD_MS = 15;
1508
+ var createInitialState3 = () => ({
1509
+ activeSourceCount: 0,
1510
+ error: null,
1511
+ isActive: false,
1512
+ isPlaying: false,
1513
+ lastInterruptLatencyMs: undefined,
1514
+ lastPlaybackStopLatencyMs: undefined,
1515
+ processedChunkCount: 0,
1516
+ queuedChunkCount: 0
1517
+ });
1518
+ var getAudioContextCtor = () => {
1519
+ if (typeof window === "undefined") {
1520
+ return typeof AudioContext === "undefined" ? undefined : AudioContext;
1521
+ }
1522
+ return window.AudioContext ?? window.webkitAudioContext;
1523
+ };
1524
+ var decodePCM16LEChunk = (audioContext, chunk) => {
1525
+ const { format } = chunk;
1526
+ if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
1527
+ throw new Error(`Unsupported assistant audio format: ${format.container}/${format.encoding}`);
1528
+ }
1529
+ const bytes = chunk.chunk;
1530
+ const channels = Math.max(1, format.channels);
1531
+ const sampleCount = Math.floor(bytes.byteLength / 2);
1532
+ const frameCount = Math.max(1, Math.floor(sampleCount / channels));
1533
+ const audioBuffer = audioContext.createBuffer(channels, frameCount, format.sampleRateHz);
1534
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1535
+ for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
1536
+ const channelData = audioBuffer.getChannelData(channelIndex);
1537
+ for (let frameIndex = 0;frameIndex < frameCount; frameIndex += 1) {
1538
+ const sampleIndex = frameIndex * channels + channelIndex;
1539
+ const sampleOffset = sampleIndex * 2;
1540
+ if (sampleOffset + 1 >= bytes.byteLength) {
1541
+ channelData[frameIndex] = 0;
1542
+ continue;
1543
+ }
1544
+ channelData[frameIndex] = view.getInt16(sampleOffset, true) / 32768;
1545
+ }
1546
+ }
1547
+ return audioBuffer;
1548
+ };
1549
+ var createVoiceAudioPlayer = (source, options = {}) => {
1550
+ const subscribers = new Set;
1551
+ const sourceNodes = new Set;
1552
+ const lookaheadSeconds = (options.lookaheadMs ?? DEFAULT_LOOKAHEAD_MS) / 1000;
1553
+ let state = createInitialState3();
1554
+ let audioContext = null;
1555
+ let outputNode = null;
1556
+ let queueEndTime = 0;
1557
+ let syncPromise = Promise.resolve();
1558
+ let interruptStartedAt = null;
1559
+ let interruptPromise = null;
1560
+ let resolveInterruptPromise = null;
1561
+ let interruptFallbackTimer = null;
1562
+ const notify = () => {
1563
+ for (const subscriber of subscribers) {
1564
+ subscriber();
1565
+ }
1566
+ };
1567
+ const setState = (next) => {
1568
+ state = {
1569
+ ...state,
1570
+ ...next
1571
+ };
1572
+ notify();
1573
+ };
1574
+ const clearError = () => {
1575
+ if (state.error !== null) {
1576
+ setState({ error: null });
1577
+ }
1578
+ };
1579
+ const clearInterruptTimer = () => {
1580
+ if (interruptFallbackTimer !== null) {
1581
+ clearTimeout(interruptFallbackTimer);
1582
+ interruptFallbackTimer = null;
1583
+ }
1584
+ };
1585
+ const resolveInterrupt = (latencyMs) => {
1586
+ clearInterruptTimer();
1587
+ interruptStartedAt = null;
1588
+ setState({
1589
+ activeSourceCount: sourceNodes.size,
1590
+ isPlaying: false,
1591
+ lastInterruptLatencyMs: latencyMs,
1592
+ lastPlaybackStopLatencyMs: state.lastPlaybackStopLatencyMs ?? latencyMs
1593
+ });
1594
+ resolveInterruptPromise?.();
1595
+ resolveInterruptPromise = null;
1596
+ interruptPromise = null;
1597
+ };
1598
+ const estimateOutputStopLatencyMs = (context) => {
1599
+ if (!context) {
1600
+ return 0;
1601
+ }
1602
+ return Math.max(0, ((context.baseLatency ?? 0) + (context.outputLatency ?? 0)) * 1000);
1603
+ };
1604
+ const restoreOutputGain = (context) => {
1605
+ if (!outputNode) {
1606
+ return;
1607
+ }
1608
+ const gainValue = 1;
1609
+ if (outputNode.gain.setValueAtTime) {
1610
+ outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
1611
+ return;
1612
+ }
1613
+ outputNode.gain.value = gainValue;
1614
+ };
1615
+ const muteOutputGain = (context) => {
1616
+ if (!outputNode) {
1617
+ return;
1618
+ }
1619
+ const gainValue = 0;
1620
+ if (outputNode.gain.setValueAtTime) {
1621
+ outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
1622
+ return;
1623
+ }
1624
+ outputNode.gain.value = gainValue;
1625
+ };
1626
+ const maybeResolveInterrupt = () => {
1627
+ if (interruptStartedAt === null || sourceNodes.size > 0) {
1628
+ return;
1629
+ }
1630
+ resolveInterrupt(Date.now() - interruptStartedAt);
1631
+ };
1632
+ const ensureAudioContext = async () => {
1633
+ if (audioContext) {
1634
+ return audioContext;
1635
+ }
1636
+ if (options.createAudioContext) {
1637
+ audioContext = options.createAudioContext();
1638
+ } else {
1639
+ const AudioContextCtor = getAudioContextCtor();
1640
+ if (!AudioContextCtor) {
1641
+ throw new Error("Assistant audio playback requires AudioContext support.");
1642
+ }
1643
+ audioContext = new AudioContextCtor;
1644
+ }
1645
+ if (audioContext.createGain) {
1646
+ outputNode = audioContext.createGain();
1647
+ outputNode.connect?.(audioContext.destination);
1648
+ }
1649
+ queueEndTime = audioContext.currentTime;
1650
+ return audioContext;
1651
+ };
1652
+ const scheduleChunk = async (chunk) => {
1653
+ const context = await ensureAudioContext();
1654
+ const buffer = decodePCM16LEChunk(context, chunk);
1655
+ const node = context.createBufferSource();
1656
+ node.buffer = buffer;
1657
+ node.connect(outputNode ?? context.destination);
1658
+ node.onended = () => {
1659
+ sourceNodes.delete(node);
1660
+ node.disconnect?.();
1661
+ setState({
1662
+ activeSourceCount: sourceNodes.size,
1663
+ isPlaying: sourceNodes.size > 0 && state.isActive
1664
+ });
1665
+ maybeResolveInterrupt();
1666
+ };
1667
+ const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
1668
+ queueEndTime = startAt + buffer.duration;
1669
+ sourceNodes.add(node);
1670
+ setState({
1671
+ activeSourceCount: sourceNodes.size,
1672
+ isPlaying: true
1673
+ });
1674
+ node.start(startAt);
1675
+ };
1676
+ const stopQueuedPlayback = (options2) => {
1677
+ for (const node of [...sourceNodes]) {
1678
+ node.stop?.();
1679
+ }
1680
+ queueEndTime = audioContext ? audioContext.currentTime : 0;
1681
+ if (options2?.forceClear) {
1682
+ for (const node of sourceNodes) {
1683
+ node.disconnect?.();
1684
+ }
1685
+ sourceNodes.clear();
1686
+ maybeResolveInterrupt();
1687
+ }
1688
+ };
1689
+ const sync = async () => {
1690
+ if (!state.isActive) {
1691
+ return;
1692
+ }
1693
+ const nextChunks = source.assistantAudio.slice(state.processedChunkCount);
1694
+ if (nextChunks.length === 0) {
1695
+ return;
1696
+ }
1697
+ try {
1698
+ clearError();
1699
+ for (const chunk of nextChunks) {
1700
+ await scheduleChunk(chunk);
1701
+ }
1702
+ setState({
1703
+ processedChunkCount: source.assistantAudio.length,
1704
+ queuedChunkCount: state.queuedChunkCount + nextChunks.length
1705
+ });
1706
+ } catch (error) {
1707
+ setState({
1708
+ error: error instanceof Error ? error.message : String(error)
1709
+ });
1710
+ }
1711
+ };
1712
+ const queueSync = () => {
1713
+ syncPromise = syncPromise.then(() => sync(), () => sync());
1714
+ return syncPromise;
1715
+ };
1716
+ const unsubscribeSource = source.subscribe(() => {
1717
+ if (options.autoStart && !state.isActive && source.assistantAudio.length > 0) {
1718
+ player.start();
1719
+ return;
1720
+ }
1721
+ if (state.isActive) {
1722
+ queueSync();
1723
+ }
1724
+ });
1725
+ const player = {
1726
+ get activeSourceCount() {
1727
+ return state.activeSourceCount;
1094
1728
  },
1095
- get assistantTexts() {
1096
- return state.assistantTexts;
1729
+ close: async () => {
1730
+ unsubscribeSource();
1731
+ stopQueuedPlayback({ forceClear: true });
1732
+ clearInterruptTimer();
1733
+ resolveInterruptPromise?.();
1734
+ resolveInterruptPromise = null;
1735
+ interruptPromise = null;
1736
+ interruptStartedAt = null;
1737
+ if (audioContext && audioContext.state !== "closed") {
1738
+ await audioContext.close();
1739
+ }
1740
+ audioContext = null;
1741
+ outputNode?.disconnect?.();
1742
+ outputNode = null;
1743
+ queueEndTime = 0;
1744
+ setState({
1745
+ activeSourceCount: 0,
1746
+ isActive: false,
1747
+ isPlaying: false
1748
+ });
1097
1749
  },
1098
- get assistantAudio() {
1099
- return state.assistantAudio;
1750
+ get error() {
1751
+ return state.error;
1100
1752
  },
1101
- get call() {
1102
- return state.call;
1753
+ getSnapshot: () => state,
1754
+ interrupt: async () => {
1755
+ const startedAt = Date.now();
1756
+ const context = await ensureAudioContext();
1757
+ interruptStartedAt = startedAt;
1758
+ muteOutputGain(context);
1759
+ const playbackStopLatencyMs = Date.now() - startedAt + estimateOutputStopLatencyMs(context);
1760
+ setState({
1761
+ isActive: false,
1762
+ isPlaying: sourceNodes.size > 0,
1763
+ lastPlaybackStopLatencyMs: playbackStopLatencyMs
1764
+ });
1765
+ if (sourceNodes.size === 0) {
1766
+ resolveInterrupt(playbackStopLatencyMs);
1767
+ return;
1768
+ }
1769
+ if (!interruptPromise) {
1770
+ interruptPromise = new Promise((resolve) => {
1771
+ resolveInterruptPromise = resolve;
1772
+ });
1773
+ }
1774
+ clearInterruptTimer();
1775
+ interruptFallbackTimer = setTimeout(() => {
1776
+ for (const node of sourceNodes) {
1777
+ node.disconnect?.();
1778
+ }
1779
+ sourceNodes.clear();
1780
+ resolveInterrupt(Date.now() - startedAt);
1781
+ }, 250);
1782
+ stopQueuedPlayback();
1783
+ await interruptPromise;
1784
+ },
1785
+ get isActive() {
1786
+ return state.isActive;
1787
+ },
1788
+ get isPlaying() {
1789
+ return state.isPlaying;
1790
+ },
1791
+ get lastInterruptLatencyMs() {
1792
+ return state.lastInterruptLatencyMs;
1793
+ },
1794
+ get lastPlaybackStopLatencyMs() {
1795
+ return state.lastPlaybackStopLatencyMs;
1796
+ },
1797
+ pause: async () => {
1798
+ if (!audioContext) {
1799
+ setState({
1800
+ activeSourceCount: 0,
1801
+ isActive: false,
1802
+ isPlaying: false
1803
+ });
1804
+ return;
1805
+ }
1806
+ await audioContext.suspend();
1807
+ setState({
1808
+ activeSourceCount: sourceNodes.size,
1809
+ isActive: false,
1810
+ isPlaying: false
1811
+ });
1812
+ },
1813
+ get processedChunkCount() {
1814
+ return state.processedChunkCount;
1815
+ },
1816
+ get queuedChunkCount() {
1817
+ return state.queuedChunkCount;
1818
+ },
1819
+ start: async () => {
1820
+ try {
1821
+ clearError();
1822
+ const context = await ensureAudioContext();
1823
+ restoreOutputGain(context);
1824
+ if (context.state === "suspended") {
1825
+ await context.resume();
1826
+ }
1827
+ setState({
1828
+ activeSourceCount: sourceNodes.size,
1829
+ isActive: true,
1830
+ isPlaying: context.state === "running"
1831
+ });
1832
+ await queueSync();
1833
+ } catch (error) {
1834
+ setState({
1835
+ error: error instanceof Error ? error.message : String(error),
1836
+ isActive: false,
1837
+ isPlaying: false
1838
+ });
1839
+ throw error;
1840
+ }
1841
+ },
1842
+ subscribe: (subscriber) => {
1843
+ subscribers.add(subscriber);
1844
+ return () => {
1845
+ subscribers.delete(subscriber);
1846
+ };
1847
+ }
1848
+ };
1849
+ return player;
1850
+ };
1851
+
1852
+ // src/client/bargeInMonitor.ts
1853
+ var DEFAULT_THRESHOLD_MS = 250;
1854
+ var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
1855
+ var summarize = (events, thresholdMs) => {
1856
+ const stopped = events.filter((event) => event.status === "stopped");
1857
+ const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
1858
+ const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
1859
+ const passed = stopped.length - failed;
1860
+ return {
1861
+ averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
1862
+ events: [...events],
1863
+ failed,
1864
+ lastEvent: events.at(-1),
1865
+ passed,
1866
+ status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
1867
+ thresholdMs,
1868
+ total: stopped.length
1869
+ };
1870
+ };
1871
+ var createVoiceBargeInMonitor = (options = {}) => {
1872
+ const listeners = new Set;
1873
+ const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
1874
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1875
+ const events = [];
1876
+ const emit = () => {
1877
+ for (const listener of listeners) {
1878
+ listener();
1879
+ }
1880
+ };
1881
+ const postEvent = (event) => {
1882
+ if (!options.path || typeof fetchImpl !== "function") {
1883
+ return;
1884
+ }
1885
+ fetchImpl(options.path, {
1886
+ body: JSON.stringify(event),
1887
+ headers: {
1888
+ "Content-Type": "application/json"
1889
+ },
1890
+ method: "POST"
1891
+ }).catch(() => {});
1892
+ };
1893
+ const record = (status, input) => {
1894
+ const event = {
1895
+ at: Date.now(),
1896
+ id: createEventId(),
1897
+ latencyMs: input.latencyMs,
1898
+ playbackStopLatencyMs: input.playbackStopLatencyMs,
1899
+ reason: input.reason,
1900
+ sessionId: input.sessionId,
1901
+ status,
1902
+ thresholdMs
1903
+ };
1904
+ events.push(event);
1905
+ postEvent(event);
1906
+ emit();
1907
+ return event;
1908
+ };
1909
+ return {
1910
+ getSnapshot: () => summarize(events, thresholdMs),
1911
+ recordRequested: (input) => record("requested", input),
1912
+ recordSkipped: (input) => record("skipped", input),
1913
+ recordStopped: (input) => record("stopped", input),
1914
+ subscribe: (subscriber) => {
1915
+ listeners.add(subscriber);
1916
+ return () => {
1917
+ listeners.delete(subscriber);
1918
+ };
1919
+ }
1920
+ };
1921
+ };
1922
+
1923
+ // src/client/duplex.ts
1924
+ var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
1925
+ var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
1926
+ var bindVoiceBargeIn = (controller, player, options = {}) => {
1927
+ let lastPartial = controller.partial;
1928
+ const interruptIfPlaying = (reason) => {
1929
+ if (!player.isPlaying || options.enabled === false) {
1930
+ options.monitor?.recordSkipped({
1931
+ reason,
1932
+ sessionId: controller.sessionId
1933
+ });
1934
+ return;
1935
+ }
1936
+ options.monitor?.recordRequested({
1937
+ reason,
1938
+ sessionId: controller.sessionId
1939
+ });
1940
+ player.interrupt().then(() => {
1941
+ options.monitor?.recordStopped({
1942
+ latencyMs: player.lastInterruptLatencyMs,
1943
+ playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
1944
+ reason,
1945
+ sessionId: controller.sessionId
1946
+ });
1947
+ });
1948
+ };
1949
+ const unsubscribe = controller.subscribe(() => {
1950
+ if (options.interruptOnPartial === false) {
1951
+ lastPartial = controller.partial;
1952
+ return;
1953
+ }
1954
+ if (!lastPartial && controller.partial) {
1955
+ interruptIfPlaying("partial-transcript");
1956
+ }
1957
+ lastPartial = controller.partial;
1958
+ });
1959
+ return {
1960
+ close: () => {
1961
+ unsubscribe();
1962
+ },
1963
+ handleLevel: (level) => {
1964
+ if (shouldInterruptForLevel(level, options)) {
1965
+ interruptIfPlaying("input-level");
1966
+ }
1967
+ },
1968
+ sendAudio: (audio) => {
1969
+ interruptIfPlaying("manual-audio");
1970
+ controller.sendAudio(audio);
1103
1971
  }
1104
1972
  };
1105
1973
  };
@@ -1126,8 +1994,7 @@ var DEFAULT_GUIDED_PROMPTS = [
1126
1994
  "Now describe what you are trying to do or test.",
1127
1995
  "Finish with any detail that feels blocked, risky, or unclear."
1128
1996
  ];
1129
- var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
1130
- var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1997
+ var clamp = (value, min, max2) => Math.min(max2, Math.max(min, value));
1131
1998
  var readErrorField = (value, key) => {
1132
1999
  const candidate = value[key];
1133
2000
  if (typeof candidate === "string" && candidate.trim()) {
@@ -1160,6 +2027,17 @@ var formatErrorMessage = (error) => {
1160
2027
  }
1161
2028
  return "Unexpected error";
1162
2029
  };
2030
+ var formatReconnectState = (reconnect) => {
2031
+ const pieces = [reconnect.status];
2032
+ if (reconnect.attempts > 0 || reconnect.maxAttempts > 0) {
2033
+ pieces.push(`${reconnect.attempts}/${reconnect.maxAttempts} attempts`);
2034
+ }
2035
+ if (reconnect.nextAttemptAt) {
2036
+ const waitMs = Math.max(0, reconnect.nextAttemptAt - Date.now());
2037
+ pieces.push(`retry in ${Math.ceil(waitMs / 100) / 10}s`);
2038
+ }
2039
+ return pieces.join(" · ");
2040
+ };
1163
2041
  var createInitialVoiceWaveLevels = (count = VOICE_WAVE_POINTS) => Array.from({ length: count }, () => 0);
1164
2042
  var pushVoiceWaveLevel = (levels, nextLevel, count = VOICE_WAVE_POINTS) => {
1165
2043
  const next = levels.slice(-(count - 1));
@@ -1216,6 +2094,20 @@ var parsePromptList = (value) => {
1216
2094
  } catch {}
1217
2095
  return DEFAULT_GUIDED_PROMPTS;
1218
2096
  };
2097
+ var parseOptionalNumber = (value) => {
2098
+ if (!value) {
2099
+ return;
2100
+ }
2101
+ const parsed = Number(value);
2102
+ return Number.isFinite(parsed) ? parsed : undefined;
2103
+ };
2104
+ var resolveElement2 = (root, selector, ctor) => {
2105
+ if (!selector) {
2106
+ return null;
2107
+ }
2108
+ const value = document.querySelector(selector);
2109
+ return value instanceof ctor ? value : null;
2110
+ };
1219
2111
  var requireElement = (root, selector, ctor, name) => {
1220
2112
  const value = selector ? document.querySelector(selector) : null;
1221
2113
  if (value instanceof ctor) {
@@ -1266,11 +2158,20 @@ var initVoiceHTMXRoot = (root) => {
1266
2158
  const guidedPrompts = parsePromptList(root.dataset.voiceGuidedPrompts);
1267
2159
  const guidedLabel = root.dataset.voiceGuidedLabel ?? DEFAULT_GUIDED_LABEL;
1268
2160
  const generalLabel = root.dataset.voiceGeneralLabel ?? DEFAULT_GENERAL_LABEL;
2161
+ const reconnectReportPath = root.dataset.voiceReconnectReportPath;
2162
+ const bargeInPath = root.dataset.voiceBargeInPath;
2163
+ const bargeInMonitor = bargeInPath ? createVoiceBargeInMonitor({
2164
+ path: bargeInPath,
2165
+ thresholdMs: parseOptionalNumber(root.dataset.voiceBargeInThresholdMs)
2166
+ }) : null;
2167
+ const bargeInRecentWindowMs = parseOptionalNumber(root.dataset.voiceBargeInRecentWindowMs) ?? 4000;
2168
+ const bargeInSpeechThreshold = parseOptionalNumber(root.dataset.voiceBargeInSpeechThreshold) ?? 0.04;
1269
2169
  const syncElement = requireElement(document, root.dataset.voiceSync, HTMLElement, "voice-htmx-sync");
1270
2170
  const connectionMetric = requireElement(root, root.dataset.voiceConnection, HTMLElement, "metric-connection");
1271
2171
  const errorStatus = requireElement(root, root.dataset.voiceError, HTMLElement, "status-error");
1272
2172
  const microphoneStatus = requireElement(root, root.dataset.voiceMicrophone, HTMLElement, "status-mic");
1273
2173
  const promptStatus = requireElement(root, root.dataset.voicePrompt, HTMLElement, "status-prompt");
2174
+ const reconnectStatus = resolveElement2(root, root.dataset.voiceReconnect, HTMLElement);
1274
2175
  const chatList = requireElement(root, root.dataset.voiceChat, HTMLElement, "chat-list");
1275
2176
  const startGuidedButton = requireElement(root, root.dataset.voiceStartGuided, HTMLButtonElement, "start-guided");
1276
2177
  const startGeneralButton = requireElement(root, root.dataset.voiceStartGeneral, HTMLButtonElement, "start-general");
@@ -1279,35 +2180,70 @@ var initVoiceHTMXRoot = (root) => {
1279
2180
  const voiceMonitorCopy = requireElement(root, root.dataset.voiceMonitorCopy, HTMLElement, "voice-monitor-copy");
1280
2181
  const voiceWaveGlow = requireElement(root, root.dataset.voiceWaveGlow, SVGPathElement, "voice-wave-glow");
1281
2182
  const voiceWavePath = requireElement(root, root.dataset.voiceWavePath, SVGPathElement, "voice-wave-path");
2183
+ let activeMode = null;
2184
+ let hasStartedModes = {
2185
+ general: false,
2186
+ guided: false
2187
+ };
2188
+ let isCapturing = false;
2189
+ let micError = null;
2190
+ let waveLevels = createInitialVoiceWaveLevels();
2191
+ let guidedBargeInBinding = null;
2192
+ let generalBargeInBinding = null;
1282
2193
  const guidedVoice = createVoiceController(guidedPath, {
1283
2194
  capture: {
2195
+ onAudio: (audio, sendAudio) => {
2196
+ if (guidedBargeInBinding) {
2197
+ guidedBargeInBinding.sendAudio(audio);
2198
+ return;
2199
+ }
2200
+ sendAudio(audio);
2201
+ },
1284
2202
  onLevel: (level) => {
2203
+ guidedBargeInBinding?.handleLevel(level);
1285
2204
  waveLevels = pushVoiceWaveLevel(waveLevels, level);
1286
2205
  renderWave();
1287
2206
  }
1288
2207
  },
2208
+ connection: {
2209
+ reconnectReportPath
2210
+ },
1289
2211
  preset: "guided-intake"
1290
2212
  });
1291
2213
  const generalVoice = createVoiceController(generalPath, {
1292
2214
  capture: {
2215
+ onAudio: (audio, sendAudio) => {
2216
+ if (generalBargeInBinding) {
2217
+ generalBargeInBinding.sendAudio(audio);
2218
+ return;
2219
+ }
2220
+ sendAudio(audio);
2221
+ },
1293
2222
  onLevel: (level) => {
2223
+ generalBargeInBinding?.handleLevel(level);
1294
2224
  waveLevels = pushVoiceWaveLevel(waveLevels, level);
1295
2225
  renderWave();
1296
2226
  }
1297
2227
  },
2228
+ connection: {
2229
+ reconnectReportPath
2230
+ },
1298
2231
  preset: "dictation"
1299
2232
  });
1300
2233
  const stopGuidedBinding = guidedVoice.bindHTMX({ element: syncElement });
1301
2234
  const stopGeneralBinding = generalVoice.bindHTMX({ element: syncElement });
1302
- let activeMode = null;
1303
- let hasStartedModes = {
1304
- general: false,
1305
- guided: false
1306
- };
1307
- let isCapturing = false;
1308
- let micError = null;
1309
- let waveLevels = createInitialVoiceWaveLevels();
2235
+ const guidedAudioPlayer = createVoiceAudioPlayer(guidedVoice);
2236
+ const generalAudioPlayer = createVoiceAudioPlayer(generalVoice);
2237
+ guidedBargeInBinding = bindVoiceBargeIn(guidedVoice, guidedAudioPlayer, {
2238
+ interruptThreshold: bargeInSpeechThreshold,
2239
+ monitor: bargeInMonitor ?? undefined
2240
+ });
2241
+ generalBargeInBinding = bindVoiceBargeIn(generalVoice, generalAudioPlayer, {
2242
+ interruptThreshold: bargeInSpeechThreshold,
2243
+ monitor: bargeInMonitor ?? undefined
2244
+ });
1310
2245
  const currentVoice = () => activeMode === "general" ? generalVoice : guidedVoice;
2246
+ const currentAudioPlayer = () => activeMode === "general" ? generalAudioPlayer : guidedAudioPlayer;
1311
2247
  const renderWave = () => {
1312
2248
  const path = createVoiceWavePath(waveLevels);
1313
2249
  voiceWaveGlow.setAttribute("d", path);
@@ -1319,9 +2255,12 @@ var initVoiceHTMXRoot = (root) => {
1319
2255
  const render = () => {
1320
2256
  const voice = currentVoice();
1321
2257
  const hasStarted = (activeMode ? hasStartedModes[activeMode] : false) || voice.turns.length > 0;
1322
- const status = voice.status;
2258
+ const { status } = voice;
1323
2259
  connectionMetric.textContent = voice.isConnected ? "Connected" : "Waiting";
1324
2260
  errorStatus.textContent = micError || voice.error || "None";
2261
+ if (reconnectStatus) {
2262
+ reconnectStatus.textContent = formatReconnectState(voice.reconnect);
2263
+ }
1325
2264
  microphoneStatus.textContent = isCapturing ? DEFAULT_MIC_LIVE : DEFAULT_MIC_IDLE;
1326
2265
  promptStatus.textContent = resolvePromptMessage({
1327
2266
  guidedPrompts,
@@ -1385,8 +2324,18 @@ var initVoiceHTMXRoot = (root) => {
1385
2324
  render();
1386
2325
  }
1387
2326
  };
1388
- guidedVoice.subscribe(render);
1389
- generalVoice.subscribe(render);
2327
+ guidedVoice.subscribe(() => {
2328
+ if (guidedVoice.assistantAudio.length > 0) {
2329
+ guidedAudioPlayer.start().catch(() => {});
2330
+ }
2331
+ render();
2332
+ });
2333
+ generalVoice.subscribe(() => {
2334
+ if (generalVoice.assistantAudio.length > 0) {
2335
+ generalAudioPlayer.start().catch(() => {});
2336
+ }
2337
+ render();
2338
+ });
1390
2339
  startGuidedButton.addEventListener("click", () => {
1391
2340
  startMode("guided");
1392
2341
  });
@@ -1396,9 +2345,16 @@ var initVoiceHTMXRoot = (root) => {
1396
2345
  stopButton.addEventListener("click", () => {
1397
2346
  stopMic();
1398
2347
  });
2348
+ root.addEventListener("absolute-voice-simulate-disconnect", () => {
2349
+ currentVoice().simulateDisconnect();
2350
+ });
1399
2351
  window.addEventListener("beforeunload", () => {
1400
2352
  guidedVoice.stopRecording();
1401
2353
  generalVoice.stopRecording();
2354
+ guidedBargeInBinding?.close();
2355
+ generalBargeInBinding?.close();
2356
+ guidedAudioPlayer.close();
2357
+ generalAudioPlayer.close();
1402
2358
  stopGuidedBinding();
1403
2359
  stopGeneralBinding();
1404
2360
  guidedVoice.close();