@absolutejs/voice 0.0.22-beta.57 → 0.0.22-beta.571

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