@absolutejs/voice 0.0.22-beta.65 → 0.0.22-beta.651

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 (514) hide show
  1. package/LICENSE +88 -0
  2. package/README.md +4550 -1180
  3. package/dist/angular/index.d.ts +36 -6
  4. package/dist/angular/index.js +4281 -472
  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 +1 -1
  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 +12 -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 +10 -4
  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 +1494 -107
  58. package/dist/client/htmxDashboardRenderers.d.ts +71 -0
  59. package/dist/client/index.d.ts +109 -25
  60. package/dist/client/index.js +10811 -532
  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 +5 -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 +3 -3
  86. package/dist/client/providerSimulationControlsWidget.d.ts +5 -5
  87. package/dist/client/providerStatus.d.ts +5 -3
  88. package/dist/client/providerStatusWidget.d.ts +6 -6
  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 +5 -3
  96. package/dist/client/routingStatusWidget.d.ts +10 -6
  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/timeStretch.d.ts +5 -0
  103. package/dist/client/traceTimeline.d.ts +21 -0
  104. package/dist/client/traceTimelineWidget.d.ts +36 -0
  105. package/dist/client/turnLatency.d.ts +24 -0
  106. package/dist/client/turnLatencyWidget.d.ts +33 -0
  107. package/dist/client/turnQuality.d.ts +21 -0
  108. package/dist/client/turnQualityWidget.d.ts +32 -0
  109. package/dist/client/voiceWidgetView.d.ts +48 -0
  110. package/dist/client/workflowStatus.d.ts +5 -3
  111. package/dist/{agent.d.ts → core/agent.d.ts} +110 -7
  112. package/dist/core/agentPerformanceReport.d.ts +40 -0
  113. package/dist/core/agentSquadContract.d.ts +98 -0
  114. package/dist/core/agentState.d.ts +12 -0
  115. package/dist/core/agentTools.d.ts +132 -0
  116. package/dist/core/aiScorecard.d.ts +32 -0
  117. package/dist/core/aiVoiceModel.d.ts +15 -0
  118. package/dist/core/amdDetector.d.ts +25 -0
  119. package/dist/{assistant.d.ts → core/assistant.d.ts} +21 -14
  120. package/dist/core/assistantExperiment.d.ts +42 -0
  121. package/dist/{assistantHealth.d.ts → core/assistantHealth.d.ts} +9 -9
  122. package/dist/{assistantMemory.d.ts → core/assistantMemory.d.ts} +10 -10
  123. package/dist/core/assistantMode.d.ts +22 -0
  124. package/dist/{audioConditioning.d.ts → core/audioConditioning.d.ts} +2 -2
  125. package/dist/core/audit.d.ts +131 -0
  126. package/dist/core/auditDeliveryRoutes.d.ts +85 -0
  127. package/dist/core/auditExport.d.ts +34 -0
  128. package/dist/core/auditRoutes.d.ts +66 -0
  129. package/dist/core/auditSinks.d.ts +151 -0
  130. package/dist/core/backchannel.d.ts +25 -0
  131. package/dist/core/bargeInDetector.d.ts +51 -0
  132. package/dist/core/bargeInRoutes.d.ts +56 -0
  133. package/dist/core/bookingFlow.d.ts +43 -0
  134. package/dist/core/browserCallProfiles.d.ts +120 -0
  135. package/dist/core/browserMediaRoutes.d.ts +62 -0
  136. package/dist/core/cachedTTS.d.ts +54 -0
  137. package/dist/core/calendarAdapter.d.ts +47 -0
  138. package/dist/core/calendarSlots.d.ts +35 -0
  139. package/dist/core/callDebugger.d.ts +66 -0
  140. package/dist/core/callDisposition.d.ts +38 -0
  141. package/dist/core/callQuota.d.ts +54 -0
  142. package/dist/core/callScorecard.d.ts +53 -0
  143. package/dist/core/callerCRMLinker.d.ts +29 -0
  144. package/dist/core/callerMemory.d.ts +37 -0
  145. package/dist/core/callingWindow.d.ts +26 -0
  146. package/dist/core/campaign.d.ts +795 -0
  147. package/dist/core/campaignControls.d.ts +37 -0
  148. package/dist/core/campaignDialers.d.ts +111 -0
  149. package/dist/core/campaignTemplate.d.ts +16 -0
  150. package/dist/core/competitiveCoverage.d.ts +141 -0
  151. package/dist/core/conversationSimulator.d.ts +73 -0
  152. package/dist/{correction.d.ts → core/correction.d.ts} +5 -4
  153. package/dist/core/costAccounting.d.ts +90 -0
  154. package/dist/core/costPredictor.d.ts +74 -0
  155. package/dist/core/crmCallLogger.d.ts +37 -0
  156. package/dist/core/crmContract.d.ts +70 -0
  157. package/dist/core/dataControl.d.ts +180 -0
  158. package/dist/core/debugTiming.d.ts +11 -0
  159. package/dist/core/defineVoiceAssistant.d.ts +68 -0
  160. package/dist/core/deliveryRuntime.d.ts +159 -0
  161. package/dist/core/deliverySinkRoutes.d.ts +117 -0
  162. package/dist/core/demoReadyRoutes.d.ts +98 -0
  163. package/dist/{diagnosticsRoutes.d.ts → core/diagnosticsRoutes.d.ts} +2 -2
  164. package/dist/core/dncRegistry.d.ts +38 -0
  165. package/dist/core/dtmfCollector.d.ts +37 -0
  166. package/dist/{evalRoutes.d.ts → core/evalRoutes.d.ts} +26 -20
  167. package/dist/{fileStore.d.ts → core/fileStore.d.ts} +34 -20
  168. package/dist/core/guardrails.d.ts +128 -0
  169. package/dist/{handoff.d.ts → core/handoff.d.ts} +10 -10
  170. package/dist/{handoffHealth.d.ts → core/handoffHealth.d.ts} +9 -9
  171. package/dist/core/hardenedFetch.d.ts +1 -0
  172. package/dist/core/holdAudio.d.ts +23 -0
  173. package/dist/{htmx.d.ts → core/htmx.d.ts} +2 -2
  174. package/dist/core/htmxDashboardRoutes.d.ts +250 -0
  175. package/dist/core/iceServers.d.ts +34 -0
  176. package/dist/core/incidentBundle.d.ts +119 -0
  177. package/dist/core/incidentTimeline.d.ts +260 -0
  178. package/dist/core/ivrPlan.d.ts +40 -0
  179. package/dist/core/latencySlo.d.ts +56 -0
  180. package/dist/core/liveCoach.d.ts +43 -0
  181. package/dist/core/liveLatency.d.ts +78 -0
  182. package/dist/core/liveOps.d.ts +190 -0
  183. package/dist/core/llmJudge.d.ts +45 -0
  184. package/dist/{logger.d.ts → core/logger.d.ts} +1 -2
  185. package/dist/core/mcpToolset.d.ts +58 -0
  186. package/dist/core/mediaPipelineRoutes.d.ts +171 -0
  187. package/dist/core/mediaPipelineSurfaces.d.ts +48 -0
  188. package/dist/{memoryStore.d.ts → core/memoryStore.d.ts} +1 -1
  189. package/dist/core/midCallSummary.d.ts +27 -0
  190. package/dist/{modelAdapters.d.ts → core/modelAdapters.d.ts} +64 -7
  191. package/dist/core/monitor.d.ts +148 -0
  192. package/dist/core/multilingualProof.d.ts +77 -0
  193. package/dist/core/noShowPredictor.d.ts +46 -0
  194. package/dist/core/numberNormalizer.d.ts +1 -0
  195. package/dist/core/oauth2TokenSource.d.ts +21 -0
  196. package/dist/core/observabilityExport.d.ts +501 -0
  197. package/dist/core/openaiTTS.d.ts +18 -0
  198. package/dist/core/operationalStatus.d.ts +87 -0
  199. package/dist/core/operationsRecord.d.ts +371 -0
  200. package/dist/{ops.d.ts → core/ops.d.ts} +70 -70
  201. package/dist/core/opsActionAuditRoutes.d.ts +99 -0
  202. package/dist/{opsConsoleRoutes.d.ts → core/opsConsoleRoutes.d.ts} +11 -8
  203. package/dist/{opsPresets.d.ts → core/opsPresets.d.ts} +2 -2
  204. package/dist/core/opsRecovery.d.ts +137 -0
  205. package/dist/{opsRuntime.d.ts → core/opsRuntime.d.ts} +6 -6
  206. package/dist/{opsSinks.d.ts → core/opsSinks.d.ts} +19 -19
  207. package/dist/core/opsStatus.d.ts +76 -0
  208. package/dist/core/opsStatusRoutes.d.ts +33 -0
  209. package/dist/{opsWebhook.d.ts → core/opsWebhook.d.ts} +15 -15
  210. package/dist/core/otelExporter.d.ts +83 -0
  211. package/dist/core/outcomeContract.d.ts +146 -0
  212. package/dist/{outcomeRecipes.d.ts → core/outcomeRecipes.d.ts} +4 -4
  213. package/dist/core/pathway.d.ts +94 -0
  214. package/dist/core/pathwayCompiler.d.ts +31 -0
  215. package/dist/core/pathwayGenerator.d.ts +27 -0
  216. package/dist/core/pathwayRuntime.d.ts +57 -0
  217. package/dist/core/pathwaySlotCollector.d.ts +29 -0
  218. package/dist/core/pathwayVisualizer.d.ts +8 -0
  219. package/dist/core/phoneAgent.d.ts +139 -0
  220. package/dist/core/phoneAgentProductionSmoke.d.ts +115 -0
  221. package/dist/core/phoneProvisioning.d.ts +29 -0
  222. package/dist/core/platformCoverage.d.ts +91 -0
  223. package/dist/{plugin.d.ts → core/plugin.d.ts} +8 -8
  224. package/dist/core/postCallAnalysis.d.ts +98 -0
  225. package/dist/core/postCallSurvey.d.ts +41 -0
  226. package/dist/{postgresStore.d.ts → core/postgresStore.d.ts} +20 -9
  227. package/dist/{presets.d.ts → core/presets.d.ts} +3 -3
  228. package/dist/core/productionReadiness.d.ts +757 -0
  229. package/dist/core/profileSwitchRecommendation.d.ts +350 -0
  230. package/dist/core/promptInjectionGuard.d.ts +30 -0
  231. package/dist/core/proofAssertions.d.ts +32 -0
  232. package/dist/core/proofPack.d.ts +211 -0
  233. package/dist/core/proofRunner.d.ts +79 -0
  234. package/dist/core/proofTrends.d.ts +966 -0
  235. package/dist/{providerAdapters.d.ts → core/providerAdapters.d.ts} +5 -5
  236. package/dist/{providerCapabilities.d.ts → core/providerCapabilities.d.ts} +9 -9
  237. package/dist/core/providerDecisionTraces.d.ts +130 -0
  238. package/dist/{providerHealth.d.ts → core/providerHealth.d.ts} +15 -5
  239. package/dist/core/providerOrchestration.d.ts +109 -0
  240. package/dist/core/providerRouterTraces.d.ts +35 -0
  241. package/dist/core/providerRoutingContract.d.ts +71 -0
  242. package/dist/core/providerSlo.d.ts +142 -0
  243. package/dist/core/providerStackRecommendations.d.ts +188 -0
  244. package/dist/core/qualityDriftDetector.d.ts +44 -0
  245. package/dist/{qualityRoutes.d.ts → core/qualityRoutes.d.ts} +7 -7
  246. package/dist/{queue.d.ts → core/queue.d.ts} +48 -39
  247. package/dist/core/ragTool.d.ts +52 -0
  248. package/dist/core/readinessProfiles.d.ts +45 -0
  249. package/dist/core/realtimeChannel.d.ts +136 -0
  250. package/dist/core/realtimeProviderContracts.d.ts +133 -0
  251. package/dist/core/reconnectContract.d.ts +177 -0
  252. package/dist/core/recordingRedaction.d.ts +47 -0
  253. package/dist/core/recordingStore.d.ts +60 -0
  254. package/dist/core/redaction.d.ts +13 -0
  255. package/dist/core/reminderScheduler.d.ts +43 -0
  256. package/dist/{resilienceRoutes.d.ts → core/resilienceRoutes.d.ts} +34 -5
  257. package/dist/core/retention.d.ts +37 -0
  258. package/dist/core/retryPolicy.d.ts +38 -0
  259. package/dist/core/routeAuth.d.ts +58 -0
  260. package/dist/{routing.d.ts → core/routing.d.ts} +2 -2
  261. package/dist/{runtimeOps.d.ts → core/runtimeOps.d.ts} +3 -3
  262. package/dist/{s3Store.d.ts → core/s3Store.d.ts} +12 -3
  263. package/dist/core/scorecardCalibration.d.ts +31 -0
  264. package/dist/core/scribe.d.ts +50 -0
  265. package/dist/core/semanticTurn.d.ts +37 -0
  266. package/dist/{session.d.ts → core/session.d.ts} +1 -1
  267. package/dist/core/sessionObservability.d.ts +145 -0
  268. package/dist/{sessionReplay.d.ts → core/sessionReplay.d.ts} +31 -19
  269. package/dist/core/sessionSnapshot.d.ts +109 -0
  270. package/dist/core/simulationSuite.d.ts +144 -0
  271. package/dist/core/sloCalibration.d.ts +185 -0
  272. package/dist/{sqliteStore.d.ts → core/sqliteStore.d.ts} +20 -9
  273. package/dist/{store.d.ts → core/store.d.ts} +1 -1
  274. package/dist/core/supervisorPermissions.d.ts +33 -0
  275. package/dist/core/supervisorPresence.d.ts +49 -0
  276. package/dist/core/telephonyMediaRoutes.d.ts +72 -0
  277. package/dist/core/telephonyOutcome.d.ts +269 -0
  278. package/dist/{toolContract.d.ts → core/toolContract.d.ts} +46 -15
  279. package/dist/{toolRuntime.d.ts → core/toolRuntime.d.ts} +4 -4
  280. package/dist/{trace.d.ts → core/trace.d.ts} +61 -22
  281. package/dist/core/traceDeliveryRoutes.d.ts +86 -0
  282. package/dist/core/traceTimeline.d.ts +97 -0
  283. package/dist/core/transcriptAnnotator.d.ts +41 -0
  284. package/dist/{turnDetection.d.ts → core/turnDetection.d.ts} +2 -1
  285. package/dist/core/turnLatency.d.ts +95 -0
  286. package/dist/core/turnProfiles.d.ts +3 -0
  287. package/dist/core/turnQuality.d.ts +94 -0
  288. package/dist/core/types.d.ts +1727 -0
  289. package/dist/core/vapiAdapter.d.ts +160 -0
  290. package/dist/core/variableAnalytics.d.ts +47 -0
  291. package/dist/core/voiceConfiguration.d.ts +8 -0
  292. package/dist/core/voiceMonitoring.d.ts +444 -0
  293. package/dist/core/webhookFanout.d.ts +48 -0
  294. package/dist/core/webhookVerification.d.ts +27 -0
  295. package/dist/core/whisperChannel.d.ts +50 -0
  296. package/dist/{workflowContract.d.ts → core/workflowContract.d.ts} +21 -21
  297. package/dist/core/writeBehindStore.d.ts +41 -0
  298. package/dist/core/zeroDataRetention.d.ts +31 -0
  299. package/dist/drizzle/assistantMemory.d.ts +100 -0
  300. package/dist/drizzle/eval.d.ts +55 -0
  301. package/dist/drizzle/handoff.d.ts +56 -0
  302. package/dist/drizzle/incidentBundle.d.ts +55 -0
  303. package/dist/drizzle/index.d.ts +991 -0
  304. package/dist/drizzle/index.js +3059 -0
  305. package/dist/drizzle/observabilityExport.d.ts +55 -0
  306. package/dist/drizzle/proofTrends.d.ts +114 -0
  307. package/dist/drizzle/runtimeStorage.d.ts +1183 -0
  308. package/dist/drizzle/shared.d.ts +69 -0
  309. package/dist/embed/index.d.ts +38 -0
  310. package/dist/embed/index.js +1838 -0
  311. package/dist/embed/voice-widget.js +10 -0
  312. package/dist/index.d.ts +379 -80
  313. package/dist/index.js +48148 -8037
  314. package/dist/internal/evidence.d.ts +10 -0
  315. package/dist/internal/html.d.ts +6 -0
  316. package/dist/internal/status.d.ts +7 -0
  317. package/dist/manifest.d.ts +17 -0
  318. package/dist/manifest.js +424 -0
  319. package/dist/manifest.json +427 -0
  320. package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
  321. package/dist/react/VoiceCallDebuggerLaunch.d.ts +6 -0
  322. package/dist/react/VoiceCallPlayer.d.ts +11 -0
  323. package/dist/react/VoiceCostDashboard.d.ts +10 -0
  324. package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
  325. package/dist/react/VoiceLiveAgentConsole.d.ts +11 -0
  326. package/dist/react/VoiceLiveCallViewer.d.ts +9 -0
  327. package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
  328. package/dist/react/VoiceOpsStatus.d.ts +1 -1
  329. package/dist/react/VoicePlatformCoverage.d.ts +6 -0
  330. package/dist/react/VoiceProfileComparison.d.ts +6 -0
  331. package/dist/react/VoiceProfileSwitchRecommendation.d.ts +6 -0
  332. package/dist/react/VoiceProofTrends.d.ts +6 -0
  333. package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
  334. package/dist/react/VoiceProviderContracts.d.ts +6 -0
  335. package/dist/react/VoiceProviderSimulationControls.d.ts +1 -1
  336. package/dist/react/VoiceProviderStatus.d.ts +1 -1
  337. package/dist/react/VoiceReadinessFailures.d.ts +6 -0
  338. package/dist/react/VoiceReconnectProfileEvidence.d.ts +6 -0
  339. package/dist/react/VoiceReplayTimeline.d.ts +6 -0
  340. package/dist/react/VoiceRoutingStatus.d.ts +1 -1
  341. package/dist/react/VoiceSessionObservability.d.ts +6 -0
  342. package/dist/react/VoiceSessionSnapshot.d.ts +6 -0
  343. package/dist/react/VoiceTraceTimeline.d.ts +6 -0
  344. package/dist/react/VoiceTurnLatency.d.ts +6 -0
  345. package/dist/react/VoiceTurnQuality.d.ts +6 -0
  346. package/dist/react/VoiceWidget.d.ts +13 -0
  347. package/dist/react/index.d.ts +80 -11
  348. package/dist/react/index.js +13610 -1595
  349. package/dist/react/useVoiceAgentSquadStatus.d.ts +8 -0
  350. package/dist/react/useVoiceCallDebugger.d.ts +8 -0
  351. package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
  352. package/dist/react/useVoiceController.d.ts +5 -1
  353. package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
  354. package/dist/react/useVoiceLiveOps.d.ts +9 -0
  355. package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
  356. package/dist/react/useVoiceOpsStatus.d.ts +8 -0
  357. package/dist/react/useVoicePlatformCoverage.d.ts +8 -0
  358. package/dist/react/useVoiceProfileComparison.d.ts +8 -0
  359. package/dist/react/useVoiceProfileSwitchRecommendation.d.ts +8 -0
  360. package/dist/react/useVoiceProofTrends.d.ts +8 -0
  361. package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
  362. package/dist/react/useVoiceProviderContracts.d.ts +8 -0
  363. package/dist/react/useVoiceProviderSimulationControls.d.ts +1 -1
  364. package/dist/react/useVoiceProviderStatus.d.ts +1 -1
  365. package/dist/react/useVoiceReadinessFailures.d.ts +8 -0
  366. package/dist/react/useVoiceReconnectProfileEvidence.d.ts +8 -0
  367. package/dist/react/useVoiceRoutingStatus.d.ts +1 -1
  368. package/dist/react/useVoiceSessionObservability.d.ts +8 -0
  369. package/dist/react/useVoiceSessionSnapshot.d.ts +9 -0
  370. package/dist/react/useVoiceStream.d.ts +5 -1
  371. package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
  372. package/dist/react/useVoiceTurnLatency.d.ts +9 -0
  373. package/dist/react/useVoiceTurnQuality.d.ts +8 -0
  374. package/dist/react/useVoiceWorkflowStatus.d.ts +1 -1
  375. package/dist/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
  376. package/dist/svelte/createVoiceCallDebugger.d.ts +10 -0
  377. package/dist/svelte/createVoiceCallPlayer.d.ts +33 -0
  378. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
  379. package/dist/svelte/createVoiceCostDashboard.d.ts +13 -0
  380. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
  381. package/dist/svelte/createVoiceLiveAgentConsole.d.ts +23 -0
  382. package/dist/svelte/createVoiceLiveCallViewer.d.ts +26 -0
  383. package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
  384. package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
  385. package/dist/svelte/createVoiceOpsStatus.d.ts +4 -4
  386. package/dist/svelte/createVoicePlatformCoverage.d.ts +7 -0
  387. package/dist/svelte/createVoiceProfileComparison.d.ts +7 -0
  388. package/dist/svelte/createVoiceProofTrends.d.ts +7 -0
  389. package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
  390. package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
  391. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +2 -2
  392. package/dist/svelte/createVoiceProviderStatus.d.ts +2 -2
  393. package/dist/svelte/createVoiceReadinessFailures.d.ts +7 -0
  394. package/dist/svelte/createVoiceReconnectProfileEvidence.d.ts +7 -0
  395. package/dist/svelte/createVoiceReplayTimeline.d.ts +13 -0
  396. package/dist/svelte/createVoiceRoutingStatus.d.ts +2 -2
  397. package/dist/svelte/createVoiceSessionObservability.d.ts +10 -0
  398. package/dist/svelte/createVoiceSessionSnapshot.d.ts +11 -0
  399. package/dist/svelte/createVoiceStream.d.ts +1 -1
  400. package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
  401. package/dist/svelte/createVoiceTurnLatency.d.ts +11 -0
  402. package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
  403. package/dist/svelte/createVoiceWidget.d.ts +19 -0
  404. package/dist/svelte/createVoiceWorkflowStatus.d.ts +2 -2
  405. package/dist/svelte/index.d.ts +37 -8
  406. package/dist/svelte/index.js +7249 -1315
  407. package/dist/telephony/contract.d.ts +61 -0
  408. package/dist/telephony/matrix.d.ts +97 -0
  409. package/dist/telephony/plivo.d.ts +303 -0
  410. package/dist/telephony/response.d.ts +1 -1
  411. package/dist/telephony/security.d.ts +182 -0
  412. package/dist/telephony/telnyx.d.ts +291 -0
  413. package/dist/telephony/twilio.d.ts +153 -15
  414. package/dist/testing/accuracy.d.ts +18 -1
  415. package/dist/testing/audioMatrix.d.ts +22 -0
  416. package/dist/testing/benchmark.d.ts +24 -15
  417. package/dist/testing/confidenceCalibration.d.ts +19 -0
  418. package/dist/testing/conformance.d.ts +11 -0
  419. package/dist/testing/corrected.d.ts +9 -9
  420. package/dist/testing/criticalFields.d.ts +22 -0
  421. package/dist/testing/duplex.d.ts +5 -5
  422. package/dist/testing/fixtures.d.ts +7 -4
  423. package/dist/testing/index.d.ts +21 -13
  424. package/dist/testing/index.js +8517 -1053
  425. package/dist/testing/ioProviderSimulator.d.ts +5 -5
  426. package/dist/testing/outcomes.d.ts +12 -0
  427. package/dist/testing/provenance.d.ts +44 -0
  428. package/dist/testing/providerSimulator.d.ts +5 -5
  429. package/dist/testing/review.d.ts +8 -8
  430. package/dist/testing/routingBenchmark.d.ts +16 -0
  431. package/dist/testing/sessionBenchmark.d.ts +13 -13
  432. package/dist/testing/statistics.d.ts +30 -0
  433. package/dist/testing/stt.d.ts +3 -3
  434. package/dist/testing/telephony.d.ts +27 -2
  435. package/dist/testing/tts.d.ts +2 -2
  436. package/dist/vue/VoiceCallDebuggerLaunch.d.ts +72 -0
  437. package/dist/vue/VoiceCallPlayer.d.ts +40 -0
  438. package/dist/vue/VoiceCostDashboard.d.ts +57 -0
  439. package/dist/vue/VoiceDeliveryRuntime.d.ts +34 -0
  440. package/dist/vue/VoiceLiveAgentConsole.d.ts +50 -0
  441. package/dist/vue/VoiceLiveCallViewer.d.ts +35 -0
  442. package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
  443. package/dist/vue/VoiceOpsStatus.d.ts +4 -0
  444. package/dist/vue/VoicePlatformCoverage.d.ts +27 -0
  445. package/dist/vue/VoiceProofTrends.d.ts +25 -0
  446. package/dist/vue/VoiceProviderCapabilities.d.ts +55 -0
  447. package/dist/vue/VoiceProviderContracts.d.ts +25 -0
  448. package/dist/vue/VoiceProviderSimulationControls.d.ts +3 -3
  449. package/dist/vue/VoiceProviderStatus.d.ts +4 -0
  450. package/dist/vue/VoiceReadinessFailures.d.ts +25 -0
  451. package/dist/vue/VoiceReconnectProfileEvidence.d.ts +25 -0
  452. package/dist/vue/VoiceReplayTimeline.d.ts +17 -0
  453. package/dist/vue/VoiceRoutingStatus.d.ts +4 -0
  454. package/dist/vue/VoiceSessionObservability.d.ts +27 -0
  455. package/dist/vue/VoiceSessionSnapshot.d.ts +72 -0
  456. package/dist/vue/VoiceTurnLatency.d.ts +73 -0
  457. package/dist/vue/VoiceTurnQuality.d.ts +55 -0
  458. package/dist/vue/VoiceWidget.d.ts +77 -0
  459. package/dist/vue/index.d.ts +49 -11
  460. package/dist/vue/index.js +12896 -1700
  461. package/dist/vue/useVoiceAgentSquadStatus.d.ts +9 -0
  462. package/dist/vue/useVoiceCallDebugger.d.ts +10 -0
  463. package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
  464. package/dist/vue/useVoiceController.d.ts +10 -7
  465. package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
  466. package/dist/vue/useVoiceLiveOps.d.ts +9 -0
  467. package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
  468. package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
  469. package/dist/vue/useVoicePlatformCoverage.d.ts +9 -0
  470. package/dist/vue/useVoiceProfileComparison.d.ts +9 -0
  471. package/dist/vue/useVoiceProofTrends.d.ts +9 -0
  472. package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
  473. package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
  474. package/dist/vue/useVoiceProviderSimulationControls.d.ts +2 -2
  475. package/dist/vue/useVoiceProviderStatus.d.ts +3 -3
  476. package/dist/vue/useVoiceReadinessFailures.d.ts +959 -0
  477. package/dist/vue/useVoiceReconnectProfileEvidence.d.ts +9 -0
  478. package/dist/vue/useVoiceRoutingStatus.d.ts +2 -2
  479. package/dist/vue/useVoiceSessionObservability.d.ts +9 -0
  480. package/dist/vue/useVoiceSessionSnapshot.d.ts +10 -0
  481. package/dist/vue/useVoiceStream.d.ts +10 -6
  482. package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
  483. package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
  484. package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
  485. package/dist/vue/useVoiceWorkflowStatus.d.ts +3 -3
  486. package/package.json +247 -256
  487. package/dist/angular/voice-app-kit-status.service.d.ts +0 -12
  488. package/dist/angular/voice-ops-status.component.d.ts +0 -15
  489. package/dist/appKit.d.ts +0 -94
  490. package/dist/client/appKitStatus.d.ts +0 -19
  491. package/dist/react/useVoiceAppKitStatus.d.ts +0 -8
  492. package/dist/svelte/createVoiceAppKitStatus.d.ts +0 -8
  493. package/dist/turnProfiles.d.ts +0 -6
  494. package/dist/types.d.ts +0 -968
  495. package/dist/vue/useVoiceAppKitStatus.d.ts +0 -9
  496. package/fixtures/README.md +0 -57
  497. package/fixtures/manifest.json +0 -199
  498. package/fixtures/pcm/dialogue-three-clean.pcm +0 -0
  499. package/fixtures/pcm/dialogue-three-mixed.pcm +0 -0
  500. package/fixtures/pcm/dialogue-two-clean.pcm +0 -0
  501. package/fixtures/pcm/dialogue-two-noisy.pcm +0 -0
  502. package/fixtures/pcm/multiturn-three-mixed.pcm +0 -0
  503. package/fixtures/pcm/multiturn-two-clean.pcm +0 -0
  504. package/fixtures/pcm/quietly-alone-clean.pcm +0 -0
  505. package/fixtures/pcm/rainstorms-noisy.pcm +0 -0
  506. package/fixtures/pcm/stella-bulgaria-bulgarian20.pcm +0 -0
  507. package/fixtures/pcm/stella-ghana-english507.pcm +0 -0
  508. package/fixtures/pcm/stella-india-english37.pcm +0 -0
  509. package/fixtures/pcm/stella-jamaica-jamaican-creole-english1.pcm +0 -0
  510. package/fixtures/pcm/stella-liberia-liberian-pidgin-english2.pcm +0 -0
  511. package/fixtures/pcm/stella-pakistan-english519.pcm +0 -0
  512. package/fixtures/pcm/stella-sierra-leone-krio5.pcm +0 -0
  513. package/fixtures/pcm/stella-singapore-english655.pcm +0 -0
  514. package/fixtures/pcm/traveled-back-route-clean.pcm +0 -0
@@ -0,0 +1,1838 @@
1
+ // src/client/htmx.ts
2
+ var DEFAULT_EVENT_NAME = "voice-refresh";
3
+ var DEFAULT_QUERY_PARAM = "sessionId";
4
+ var resolveElement = (input) => {
5
+ if (typeof input !== "string") {
6
+ return input;
7
+ }
8
+ return document.querySelector(input);
9
+ };
10
+ var buildRoute = (element, route, queryParam, sessionId) => {
11
+ const baseRoute = route ?? element.getAttribute("hx-get") ?? "";
12
+ if (!baseRoute) {
13
+ return "";
14
+ }
15
+ const url = new URL(baseRoute, window.location.origin);
16
+ if (sessionId) {
17
+ url.searchParams.set(queryParam, sessionId);
18
+ } else {
19
+ url.searchParams.delete(queryParam);
20
+ }
21
+ return `${url.pathname}${url.search}${url.hash}`;
22
+ };
23
+ var bindVoiceHTMX = (stream, options) => {
24
+ if (typeof window === "undefined" || typeof document === "undefined") {
25
+ return () => {};
26
+ }
27
+ const element = resolveElement(options.element);
28
+ if (!element) {
29
+ return () => {};
30
+ }
31
+ const eventName = options.eventName ?? DEFAULT_EVENT_NAME;
32
+ const queryParam = options.sessionQueryParam ?? DEFAULT_QUERY_PARAM;
33
+ const sync = () => {
34
+ const htmxWindow = window;
35
+ const nextRoute = buildRoute(element, options.route, queryParam, stream.sessionId);
36
+ if (nextRoute) {
37
+ element.setAttribute("hx-get", nextRoute);
38
+ }
39
+ htmxWindow.htmx?.process?.(element);
40
+ htmxWindow.htmx?.trigger?.(element, eventName);
41
+ };
42
+ const unsubscribe = stream.subscribe(sync);
43
+ sync();
44
+ return () => {
45
+ unsubscribe();
46
+ };
47
+ };
48
+
49
+ // src/client/microphone.ts
50
+ var clampSample = (value) => Math.max(-1, Math.min(1, value));
51
+ var floatTo16BitPCM = (input) => {
52
+ const output = new Int16Array(input.length);
53
+ for (let index = 0;index < input.length; index += 1) {
54
+ const sample = clampSample(input[index] ?? 0);
55
+ output[index] = sample < 0 ? sample * 32768 : sample * 32767;
56
+ }
57
+ return new Uint8Array(output.buffer);
58
+ };
59
+ var getPcmLevel = (audio) => {
60
+ const bytes = audio instanceof Uint8Array ? audio : new Uint8Array(audio);
61
+ if (bytes.byteLength < 2) {
62
+ return 0;
63
+ }
64
+ const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
65
+ if (samples.length === 0) {
66
+ return 0;
67
+ }
68
+ let sumSquares = 0;
69
+ for (const sample of samples) {
70
+ const normalized = sample / 32768;
71
+ sumSquares += normalized * normalized;
72
+ }
73
+ return Math.min(1, Math.max(0, Math.sqrt(sumSquares / samples.length) * 5.5));
74
+ };
75
+ var downsampleBuffer = (input, sourceRate, targetRate) => {
76
+ if (sourceRate === targetRate) {
77
+ return input;
78
+ }
79
+ const ratio = sourceRate / targetRate;
80
+ const length = Math.round(input.length / ratio);
81
+ const output = new Float32Array(length);
82
+ let offsetResult = 0;
83
+ let offsetBuffer = 0;
84
+ while (offsetResult < output.length) {
85
+ const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio);
86
+ let accum = 0;
87
+ let count = 0;
88
+ for (let index = offsetBuffer;index < nextOffsetBuffer && index < input.length; index += 1) {
89
+ accum += input[index] ?? 0;
90
+ count += 1;
91
+ }
92
+ output[offsetResult] = count > 0 ? accum / count : 0;
93
+ offsetResult += 1;
94
+ offsetBuffer = nextOffsetBuffer;
95
+ }
96
+ return output;
97
+ };
98
+ var createMicrophoneCapture = (options) => {
99
+ let audioContext = null;
100
+ let sourceNode = null;
101
+ let processorNode = null;
102
+ let mediaStream = null;
103
+ const start = async () => {
104
+ if (!options.stream && (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia)) {
105
+ throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
106
+ }
107
+ const AudioContextCtor = (typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : undefined) ?? AudioContext;
108
+ if (!AudioContextCtor) {
109
+ throw new Error("Browser microphone capture requires AudioContext support.");
110
+ }
111
+ try {
112
+ mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
113
+ audio: {
114
+ autoGainControl: true,
115
+ channelCount: options.channelCount ?? 1,
116
+ echoCancellation: true,
117
+ noiseSuppression: true
118
+ }
119
+ });
120
+ audioContext = new AudioContextCtor;
121
+ if (audioContext.state === "suspended") {
122
+ await audioContext.resume();
123
+ }
124
+ sourceNode = audioContext.createMediaStreamSource(mediaStream);
125
+ processorNode = audioContext.createScriptProcessor(4096, 1, 1);
126
+ processorNode.onaudioprocess = (event) => {
127
+ const channel = event.inputBuffer.getChannelData(0);
128
+ const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
129
+ const pcm = floatTo16BitPCM(downsampled);
130
+ options.onLevel?.(getPcmLevel(pcm));
131
+ options.onAudio(pcm);
132
+ };
133
+ sourceNode.connect(processorNode);
134
+ processorNode.connect(audioContext.destination);
135
+ } catch (error) {
136
+ stop();
137
+ throw error;
138
+ }
139
+ };
140
+ const stop = () => {
141
+ processorNode?.disconnect();
142
+ sourceNode?.disconnect();
143
+ mediaStream?.getTracks().forEach((track) => track.stop());
144
+ audioContext?.close();
145
+ options.onLevel?.(0);
146
+ audioContext = null;
147
+ mediaStream = null;
148
+ processorNode = null;
149
+ sourceNode = null;
150
+ };
151
+ return { start, stop };
152
+ };
153
+
154
+ // src/client/actions.ts
155
+ var normalizeErrorMessage = (value) => {
156
+ if (typeof value === "string" && value.trim()) {
157
+ return value;
158
+ }
159
+ if (value instanceof Error && value.message.trim()) {
160
+ return value.message;
161
+ }
162
+ if (value && typeof value === "object") {
163
+ const record = value;
164
+ for (const key of ["message", "reason", "description"]) {
165
+ const candidate = record[key];
166
+ if (typeof candidate === "string" && candidate.trim()) {
167
+ return candidate;
168
+ }
169
+ }
170
+ if ("error" in record) {
171
+ return normalizeErrorMessage(record.error);
172
+ }
173
+ if ("cause" in record) {
174
+ return normalizeErrorMessage(record.cause);
175
+ }
176
+ try {
177
+ return JSON.stringify(value);
178
+ } catch {}
179
+ }
180
+ return "Unexpected error";
181
+ };
182
+ var serverMessageToAction = (message) => {
183
+ switch (message.type) {
184
+ case "audio":
185
+ return {
186
+ chunk: Uint8Array.from(atob(message.chunkBase64), (char) => char.charCodeAt(0)),
187
+ format: message.format,
188
+ receivedAt: message.receivedAt,
189
+ turnId: message.turnId,
190
+ type: "audio"
191
+ };
192
+ case "assistant":
193
+ return {
194
+ text: message.text,
195
+ turnId: message.turnId,
196
+ type: "assistant"
197
+ };
198
+ case "assistant_delta":
199
+ return {
200
+ delta: message.delta,
201
+ turnId: message.turnId,
202
+ type: "assistant_delta"
203
+ };
204
+ case "complete":
205
+ return {
206
+ sessionId: message.sessionId,
207
+ type: "complete"
208
+ };
209
+ case "connection":
210
+ return {
211
+ reconnect: message.reconnect,
212
+ type: "connection"
213
+ };
214
+ case "call_lifecycle":
215
+ return {
216
+ event: message.event,
217
+ sessionId: message.sessionId,
218
+ type: "call_lifecycle"
219
+ };
220
+ case "error":
221
+ return {
222
+ message: normalizeErrorMessage(message.message),
223
+ type: "error"
224
+ };
225
+ case "final":
226
+ return {
227
+ transcript: message.transcript,
228
+ type: "final"
229
+ };
230
+ case "partial":
231
+ return {
232
+ transcript: message.transcript,
233
+ type: "partial"
234
+ };
235
+ case "replay":
236
+ return {
237
+ assistantTexts: message.assistantTexts,
238
+ call: message.call,
239
+ partial: message.partial,
240
+ scenarioId: message.scenarioId,
241
+ sessionId: message.sessionId,
242
+ sessionMetadata: message.sessionMetadata,
243
+ status: message.status,
244
+ turns: message.turns,
245
+ type: "replay"
246
+ };
247
+ case "session":
248
+ return {
249
+ sessionId: message.sessionId,
250
+ sessionMetadata: message.sessionMetadata,
251
+ scenarioId: message.scenarioId,
252
+ status: message.status,
253
+ type: "session"
254
+ };
255
+ case "turn":
256
+ return {
257
+ turn: message.turn,
258
+ type: "turn"
259
+ };
260
+ default:
261
+ return null;
262
+ }
263
+ };
264
+
265
+ // node_modules/@absolutejs/media/dist/index.js
266
+ var TAU = Math.PI * 2;
267
+ var pushIssue = (issues, severity, code, message) => {
268
+ issues.push({ code, message, severity });
269
+ };
270
+ var average = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
271
+ var max = (values) => values.length === 0 ? undefined : Math.max(...values);
272
+ var numericStat = (stat, key) => {
273
+ const value = stat[key];
274
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
275
+ };
276
+ var booleanStat = (stat, key) => {
277
+ const value = stat[key];
278
+ return typeof value === "boolean" ? value : undefined;
279
+ };
280
+ var stringStat = (stat, key) => {
281
+ const value = stat[key];
282
+ return typeof value === "string" ? value : undefined;
283
+ };
284
+ var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
285
+ var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
286
+ var normalizeWebRTCStat = (stat) => {
287
+ const sample = {};
288
+ for (const [key, value] of Object.entries(stat)) {
289
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
290
+ sample[key] = value;
291
+ }
292
+ }
293
+ return sample;
294
+ };
295
+ var buildMediaWebRTCStatsReport = (input = {}) => {
296
+ const stats = input.stats ?? [];
297
+ const issues = [];
298
+ const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
299
+ const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
300
+ const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
301
+ const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
302
+ const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
303
+ const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
304
+ const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
305
+ const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
306
+ const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
307
+ const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
308
+ const packetLossDenominator = inboundPackets + packetsLost;
309
+ const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
310
+ const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
311
+ const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
312
+ const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
313
+ const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
314
+ const jitterBufferDelayMs = max(inbound.map((stat) => {
315
+ const delay = numericStat(stat, "jitterBufferDelay");
316
+ const emitted = numericStat(stat, "jitterBufferEmittedCount");
317
+ return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
318
+ }).filter((value) => value !== undefined));
319
+ const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
320
+ if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
321
+ pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
322
+ }
323
+ if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
324
+ pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
325
+ }
326
+ if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
327
+ pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
328
+ }
329
+ if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
330
+ pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
331
+ }
332
+ if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
333
+ pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
334
+ }
335
+ return {
336
+ activeCandidatePairs,
337
+ audioLevelAverage: average(audioLevels),
338
+ bytesReceived,
339
+ bytesSent,
340
+ checkedAt: Date.now(),
341
+ endedAudioTracks,
342
+ inboundPackets,
343
+ issues,
344
+ jitterBufferDelayMs,
345
+ jitterMs,
346
+ liveAudioTracks,
347
+ outboundPackets,
348
+ packetLossRatio,
349
+ packetsLost,
350
+ roundTripTimeMs,
351
+ status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
352
+ totalStats: stats.length
353
+ };
354
+ };
355
+ var collectMediaWebRTCStats = async (input) => {
356
+ const report = await input.peerConnection.getStats(input.selector ?? null);
357
+ return [...report.values()].map(normalizeWebRTCStat);
358
+ };
359
+ var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
360
+ const stats = input.stats ?? [];
361
+ const previousStats = input.previousStats ?? [];
362
+ const issues = [];
363
+ const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
364
+ const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
365
+ const streams = audioRtp.map((stat) => {
366
+ const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
367
+ const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
368
+ const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
369
+ const previous = previousByKey.get(statKey(stat));
370
+ const currentPackets = numericStat(stat, packetsKey);
371
+ const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
372
+ const currentBytes = numericStat(stat, bytesKey);
373
+ const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
374
+ const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
375
+ return {
376
+ bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
377
+ currentPackets,
378
+ direction,
379
+ id: statKey(stat),
380
+ packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
381
+ previousPackets,
382
+ timeDeltaMs
383
+ };
384
+ });
385
+ const inbound = streams.filter((stream) => stream.direction === "inbound");
386
+ const outbound = streams.filter((stream) => stream.direction === "outbound");
387
+ const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
388
+ const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
389
+ const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
390
+ if (input.requireInboundAudio && inbound.length === 0) {
391
+ pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
392
+ }
393
+ if (input.requireOutboundAudio && outbound.length === 0) {
394
+ pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
395
+ }
396
+ if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
397
+ pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
398
+ }
399
+ if (stalledInboundStreams > 0) {
400
+ pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
401
+ }
402
+ if (stalledOutboundStreams > 0) {
403
+ pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
404
+ }
405
+ return {
406
+ checkedAt: Date.now(),
407
+ inboundAudioStreams: inbound.length,
408
+ issues,
409
+ maxObservedGapMs,
410
+ outboundAudioStreams: outbound.length,
411
+ stalledInboundStreams,
412
+ stalledOutboundStreams,
413
+ status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
414
+ streams,
415
+ totalStats: stats.length
416
+ };
417
+ };
418
+
419
+ // src/client/browserMedia.ts
420
+ var DEFAULT_BROWSER_MEDIA_PATH = "/api/voice/browser-media";
421
+ var DEFAULT_BROWSER_MEDIA_INTERVAL_MS = 5000;
422
+ var resolvePeerConnection = async (options) => options.peerConnection ?? await options.getPeerConnection?.() ?? null;
423
+ var postBrowserMediaReport = async (payload, options) => {
424
+ const requestFetch = options.fetch ?? globalThis.fetch;
425
+ if (!requestFetch) {
426
+ return;
427
+ }
428
+ await requestFetch(options.path ?? DEFAULT_BROWSER_MEDIA_PATH, {
429
+ body: JSON.stringify(payload),
430
+ headers: {
431
+ "Content-Type": "application/json"
432
+ },
433
+ keepalive: true,
434
+ method: "POST"
435
+ });
436
+ };
437
+ var createVoiceBrowserMediaReporter = (options) => {
438
+ let interval = null;
439
+ let previousStats = [];
440
+ const reportOnce = async () => {
441
+ const peerConnection = await resolvePeerConnection(options);
442
+ if (!peerConnection) {
443
+ return;
444
+ }
445
+ const stats = await collectMediaWebRTCStats({ peerConnection });
446
+ const report = buildMediaWebRTCStatsReport({
447
+ ...options,
448
+ stats
449
+ });
450
+ const continuity = options.continuity === false ? undefined : buildMediaWebRTCStreamContinuityReport({
451
+ ...options.continuity,
452
+ previousStats,
453
+ stats
454
+ });
455
+ const payload = {
456
+ at: Date.now(),
457
+ continuity,
458
+ report,
459
+ scenarioId: options.getScenarioId?.() ?? null,
460
+ sessionId: options.getSessionId?.() ?? null
461
+ };
462
+ previousStats = stats;
463
+ options.onReport?.(payload);
464
+ await postBrowserMediaReport(payload, options);
465
+ return payload;
466
+ };
467
+ const run = () => {
468
+ reportOnce().catch((error) => {
469
+ options.onError?.(error);
470
+ });
471
+ };
472
+ const stop = () => {
473
+ if (interval) {
474
+ clearInterval(interval);
475
+ interval = null;
476
+ }
477
+ };
478
+ return {
479
+ close: stop,
480
+ reportOnce,
481
+ stop,
482
+ start: () => {
483
+ if (interval) {
484
+ return;
485
+ }
486
+ run();
487
+ interval = setInterval(run, options.intervalMs ?? DEFAULT_BROWSER_MEDIA_INTERVAL_MS);
488
+ }
489
+ };
490
+ };
491
+
492
+ // src/client/connection.ts
493
+ var WS_OPEN = 1;
494
+ var WS_CLOSED = 3;
495
+ var WS_NORMAL_CLOSURE = 1000;
496
+ var WS_UNAUTHORIZED = 4401;
497
+ var WS_FORBIDDEN = 4403;
498
+ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
499
+ var DEFAULT_PING_INTERVAL = 30000;
500
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
501
+ var RECONNECT_BASE_DELAY_MS = 500;
502
+ var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
503
+ var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
504
+ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
505
+ var noop = () => {};
506
+ var noopUnsubscribe = () => noop;
507
+ var NOOP_CONNECTION = {
508
+ callControl: noop,
509
+ close: noop,
510
+ disconnect: noop,
511
+ endTurn: noop,
512
+ send: noop,
513
+ sendAudio: noop,
514
+ simulateDisconnect: noop,
515
+ subscribe: noopUnsubscribe,
516
+ getReadyState: () => WS_CLOSED,
517
+ getScenarioId: () => "",
518
+ getSessionId: () => "",
519
+ start: () => {}
520
+ };
521
+ var createSessionId = () => crypto.randomUUID();
522
+ var buildWsUrl = (path, sessionId, scenarioId, query) => {
523
+ const { hostname, port, protocol } = window.location;
524
+ const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
525
+ const portSuffix = port ? `:${port}` : "";
526
+ const url = new URL(`${wsProtocol}//${hostname}${portSuffix}${path}`);
527
+ url.searchParams.set("sessionId", sessionId);
528
+ if (scenarioId) {
529
+ url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
530
+ }
531
+ for (const [key, value] of Object.entries(query)) {
532
+ if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
533
+ url.searchParams.set(key, value);
534
+ }
535
+ }
536
+ return url.toString();
537
+ };
538
+ var isVoiceServerMessage = (value) => {
539
+ if (!value || typeof value !== "object" || !("type" in value)) {
540
+ return false;
541
+ }
542
+ switch (value.type) {
543
+ case "audio":
544
+ case "assistant":
545
+ case "call_lifecycle":
546
+ case "complete":
547
+ case "connection":
548
+ case "error":
549
+ case "final":
550
+ case "partial":
551
+ case "pong":
552
+ case "replay":
553
+ case "session":
554
+ case "turn":
555
+ return true;
556
+ default:
557
+ return false;
558
+ }
559
+ };
560
+ var parseServerMessage = (event) => {
561
+ if (typeof event.data !== "string") {
562
+ return null;
563
+ }
564
+ try {
565
+ const parsed = JSON.parse(event.data);
566
+ return isVoiceServerMessage(parsed) ? parsed : null;
567
+ } catch {
568
+ return null;
569
+ }
570
+ };
571
+ var createVoiceConnection = (path, options = {}) => {
572
+ if (typeof window === "undefined") {
573
+ return NOOP_CONNECTION;
574
+ }
575
+ const listeners = new Set;
576
+ const shouldReconnect = options.reconnect !== false;
577
+ const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
578
+ const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
579
+ const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
580
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
581
+ const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
582
+ const state = {
583
+ isConnected: false,
584
+ pendingMessages: [],
585
+ scenarioId: options.scenarioId ?? null,
586
+ pingInterval: null,
587
+ reconnectAttempts: 0,
588
+ reconnectResetTimeout: null,
589
+ reconnectTimeout: null,
590
+ sessionId: options.sessionId ?? createSessionId(),
591
+ ws: null
592
+ };
593
+ const emitConnection = (reconnect) => {
594
+ listeners.forEach((listener) => listener(reconnect));
595
+ };
596
+ const clearTimers = () => {
597
+ if (state.pingInterval) {
598
+ clearInterval(state.pingInterval);
599
+ state.pingInterval = null;
600
+ }
601
+ if (state.reconnectTimeout) {
602
+ clearTimeout(state.reconnectTimeout);
603
+ state.reconnectTimeout = null;
604
+ }
605
+ if (state.reconnectResetTimeout) {
606
+ clearTimeout(state.reconnectResetTimeout);
607
+ state.reconnectResetTimeout = null;
608
+ }
609
+ };
610
+ const flushPendingMessages = () => {
611
+ if (state.ws?.readyState !== WS_OPEN) {
612
+ return;
613
+ }
614
+ while (state.pendingMessages.length > 0) {
615
+ const next = state.pendingMessages.shift();
616
+ if (next !== undefined) {
617
+ state.ws.send(next);
618
+ }
619
+ }
620
+ };
621
+ const scheduleReconnect = () => {
622
+ state.reconnectAttempts += 1;
623
+ const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
624
+ const nextAttemptAt = Date.now() + delayMs;
625
+ emitConnection({
626
+ reconnect: {
627
+ attempts: state.reconnectAttempts,
628
+ lastDisconnectAt: Date.now(),
629
+ maxAttempts: maxReconnectAttempts,
630
+ nextAttemptAt,
631
+ status: "reconnecting"
632
+ },
633
+ type: "connection"
634
+ });
635
+ state.reconnectTimeout = setTimeout(() => {
636
+ if (state.reconnectAttempts > maxReconnectAttempts) {
637
+ emitConnection({
638
+ reconnect: {
639
+ attempts: state.reconnectAttempts,
640
+ maxAttempts: maxReconnectAttempts,
641
+ status: "exhausted"
642
+ },
643
+ type: "connection"
644
+ });
645
+ return;
646
+ }
647
+ connect();
648
+ }, delayMs);
649
+ };
650
+ const connect = () => {
651
+ const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
652
+ ws.binaryType = "arraybuffer";
653
+ ws.onopen = () => {
654
+ if (state.ws !== ws) {
655
+ ws.close(WS_NORMAL_CLOSURE);
656
+ return;
657
+ }
658
+ const wasReconnecting = state.reconnectAttempts > 0;
659
+ state.isConnected = true;
660
+ flushPendingMessages();
661
+ if (wasReconnecting) {
662
+ emitConnection({
663
+ reconnect: {
664
+ attempts: state.reconnectAttempts,
665
+ lastResumedAt: Date.now(),
666
+ maxAttempts: maxReconnectAttempts,
667
+ status: "resumed"
668
+ },
669
+ type: "connection"
670
+ });
671
+ state.reconnectResetTimeout = setTimeout(() => {
672
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
673
+ state.reconnectAttempts = 0;
674
+ }
675
+ state.reconnectResetTimeout = null;
676
+ }, reconnectResetAfterMs);
677
+ }
678
+ listeners.forEach((listener) => listener({
679
+ scenarioId: state.scenarioId ?? undefined,
680
+ sessionId: state.sessionId,
681
+ status: "active",
682
+ type: "session"
683
+ }));
684
+ state.pingInterval = setInterval(() => {
685
+ if (ws.readyState === WS_OPEN) {
686
+ ws.send(JSON.stringify({ type: "ping" }));
687
+ }
688
+ }, pingInterval);
689
+ };
690
+ ws.onmessage = (event) => {
691
+ if (state.ws !== ws)
692
+ return;
693
+ const parsed = parseServerMessage(event);
694
+ if (!parsed) {
695
+ return;
696
+ }
697
+ if (parsed.type === "session") {
698
+ state.sessionId = parsed.sessionId;
699
+ state.scenarioId = parsed.scenarioId ?? state.scenarioId;
700
+ }
701
+ listeners.forEach((listener) => listener(parsed));
702
+ };
703
+ ws.onclose = (event) => {
704
+ if (state.ws !== ws)
705
+ return;
706
+ state.ws = null;
707
+ state.isConnected = false;
708
+ clearTimers();
709
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
710
+ if (reconnectable) {
711
+ scheduleReconnect();
712
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
713
+ emitConnection({
714
+ reconnect: {
715
+ attempts: state.reconnectAttempts,
716
+ lastDisconnectAt: Date.now(),
717
+ maxAttempts: maxReconnectAttempts,
718
+ status: "exhausted"
719
+ },
720
+ type: "connection"
721
+ });
722
+ }
723
+ };
724
+ state.ws = ws;
725
+ };
726
+ const sendSerialized = (value) => {
727
+ if (state.ws?.readyState === WS_OPEN) {
728
+ state.ws.send(value);
729
+ return;
730
+ }
731
+ state.pendingMessages.push(value);
732
+ };
733
+ const send = (message) => {
734
+ sendSerialized(JSON.stringify(message));
735
+ };
736
+ const start = (input = {}) => {
737
+ if (input.sessionId) {
738
+ state.sessionId = input.sessionId;
739
+ }
740
+ if (input.scenarioId) {
741
+ state.scenarioId = input.scenarioId;
742
+ }
743
+ send({
744
+ scenarioId: state.scenarioId ?? undefined,
745
+ sessionId: state.sessionId,
746
+ type: "start"
747
+ });
748
+ };
749
+ const sendAudio = (audio) => {
750
+ sendSerialized(audio);
751
+ };
752
+ const endTurn = () => {
753
+ send({ type: "end_turn" });
754
+ };
755
+ const callControl = (message) => {
756
+ send({
757
+ ...message,
758
+ type: "call_control"
759
+ });
760
+ };
761
+ const close = (reason = "client-close") => {
762
+ clearTimers();
763
+ if (state.ws) {
764
+ const ws = state.ws;
765
+ state.ws = null;
766
+ if (ws.readyState === WS_OPEN) {
767
+ ws.send(JSON.stringify({ reason, type: "close" }));
768
+ }
769
+ ws.close(WS_NORMAL_CLOSURE);
770
+ }
771
+ state.isConnected = false;
772
+ listeners.clear();
773
+ };
774
+ const disconnect = () => {
775
+ clearTimers();
776
+ if (state.ws) {
777
+ const ws = state.ws;
778
+ state.ws = null;
779
+ ws.close(WS_NORMAL_CLOSURE);
780
+ }
781
+ state.isConnected = false;
782
+ listeners.clear();
783
+ };
784
+ const simulateDisconnect = () => {
785
+ if (state.ws?.readyState === WS_OPEN) {
786
+ state.ws.close(4000, "absolutejs-voice-reconnect-proof");
787
+ }
788
+ };
789
+ const subscribe = (callback) => {
790
+ listeners.add(callback);
791
+ return () => {
792
+ listeners.delete(callback);
793
+ };
794
+ };
795
+ connect();
796
+ return {
797
+ callControl,
798
+ close,
799
+ disconnect,
800
+ endTurn,
801
+ send,
802
+ sendAudio,
803
+ simulateDisconnect,
804
+ start,
805
+ subscribe,
806
+ getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
807
+ getScenarioId: () => state.scenarioId ?? "",
808
+ getSessionId: () => state.sessionId
809
+ };
810
+ };
811
+
812
+ // src/client/store.ts
813
+ var createInitialReconnectState = () => ({
814
+ attempts: 0,
815
+ maxAttempts: 0,
816
+ status: "idle"
817
+ });
818
+ var appendSegmentText = (accumulated, next) => {
819
+ const nextText = next.trim().replace(/\s+/g, " ");
820
+ if (!nextText)
821
+ return accumulated;
822
+ if (!accumulated)
823
+ return nextText;
824
+ if (accumulated === nextText || accumulated.endsWith(nextText)) {
825
+ return accumulated;
826
+ }
827
+ if (nextText.includes(accumulated))
828
+ return nextText;
829
+ return `${accumulated} ${nextText}`;
830
+ };
831
+ var joinPartial = (finalized, interim) => {
832
+ const interimText = interim.trim().replace(/\s+/g, " ");
833
+ if (!finalized)
834
+ return interimText;
835
+ if (!interimText || finalized.endsWith(interimText))
836
+ return finalized;
837
+ return `${finalized} ${interimText}`;
838
+ };
839
+ var createInitialState = () => ({
840
+ assistantAudio: [],
841
+ assistantStreamingText: "",
842
+ assistantTexts: [],
843
+ call: null,
844
+ error: null,
845
+ isConnected: false,
846
+ partial: "",
847
+ reconnect: createInitialReconnectState(),
848
+ scenarioId: null,
849
+ sessionId: null,
850
+ sessionMetadata: null,
851
+ status: "idle",
852
+ turns: []
853
+ });
854
+ var createVoiceStreamStore = () => {
855
+ let state = createInitialState();
856
+ let turnFinalText = "";
857
+ const subscribers = new Set;
858
+ const notify = () => {
859
+ subscribers.forEach((subscriber) => subscriber());
860
+ };
861
+ const dispatch = (action) => {
862
+ switch (action.type) {
863
+ case "audio":
864
+ state = {
865
+ ...state,
866
+ assistantAudio: [
867
+ ...state.assistantAudio,
868
+ {
869
+ chunk: action.chunk,
870
+ format: action.format,
871
+ receivedAt: action.receivedAt,
872
+ turnId: action.turnId
873
+ }
874
+ ]
875
+ };
876
+ break;
877
+ case "assistant":
878
+ state = {
879
+ ...state,
880
+ assistantStreamingText: "",
881
+ assistantTexts: [...state.assistantTexts, action.text]
882
+ };
883
+ break;
884
+ case "assistant_delta":
885
+ state = {
886
+ ...state,
887
+ assistantStreamingText: `${state.assistantStreamingText}${action.delta}`
888
+ };
889
+ break;
890
+ case "complete":
891
+ state = {
892
+ ...state,
893
+ sessionId: action.sessionId,
894
+ status: "completed"
895
+ };
896
+ break;
897
+ case "call_lifecycle":
898
+ state = {
899
+ ...state,
900
+ call: {
901
+ ...state.call,
902
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
903
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
904
+ events: [...state.call?.events ?? [], action.event],
905
+ lastEventAt: action.event.at,
906
+ startedAt: state.call?.startedAt ?? action.event.at
907
+ },
908
+ sessionId: action.sessionId
909
+ };
910
+ break;
911
+ case "connected":
912
+ state = {
913
+ ...state,
914
+ isConnected: true,
915
+ reconnect: state.reconnect.status === "reconnecting" ? {
916
+ ...state.reconnect,
917
+ lastResumedAt: Date.now(),
918
+ nextAttemptAt: undefined,
919
+ status: "resumed"
920
+ } : state.reconnect
921
+ };
922
+ break;
923
+ case "connection":
924
+ state = {
925
+ ...state,
926
+ reconnect: action.reconnect
927
+ };
928
+ break;
929
+ case "disconnected":
930
+ state = {
931
+ ...state,
932
+ isConnected: false
933
+ };
934
+ break;
935
+ case "error":
936
+ state = {
937
+ ...state,
938
+ error: action.message
939
+ };
940
+ break;
941
+ case "final":
942
+ turnFinalText = appendSegmentText(turnFinalText, action.transcript.text);
943
+ state = {
944
+ ...state,
945
+ partial: turnFinalText
946
+ };
947
+ break;
948
+ case "partial":
949
+ state = {
950
+ ...state,
951
+ partial: joinPartial(turnFinalText, action.transcript.text)
952
+ };
953
+ break;
954
+ case "replay":
955
+ turnFinalText = action.partial;
956
+ state = {
957
+ ...state,
958
+ assistantStreamingText: "",
959
+ assistantTexts: [...action.assistantTexts],
960
+ call: action.call ?? null,
961
+ error: null,
962
+ isConnected: action.status === "active",
963
+ partial: action.partial,
964
+ reconnect: state.reconnect.status === "reconnecting" ? {
965
+ ...state.reconnect,
966
+ lastResumedAt: Date.now(),
967
+ nextAttemptAt: undefined,
968
+ status: "resumed"
969
+ } : state.reconnect,
970
+ scenarioId: action.scenarioId ?? state.scenarioId,
971
+ sessionId: action.sessionId,
972
+ sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
973
+ status: action.status,
974
+ turns: [...action.turns]
975
+ };
976
+ break;
977
+ case "session":
978
+ state = {
979
+ ...state,
980
+ error: null,
981
+ scenarioId: action.scenarioId ?? state.scenarioId,
982
+ isConnected: action.status === "active",
983
+ sessionId: action.sessionId,
984
+ sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
985
+ status: action.status
986
+ };
987
+ break;
988
+ case "turn":
989
+ turnFinalText = "";
990
+ state = {
991
+ ...state,
992
+ partial: "",
993
+ turns: [...state.turns, action.turn]
994
+ };
995
+ break;
996
+ }
997
+ notify();
998
+ };
999
+ return {
1000
+ dispatch,
1001
+ getServerSnapshot: () => state,
1002
+ getSnapshot: () => state,
1003
+ subscribe: (subscriber) => {
1004
+ subscribers.add(subscriber);
1005
+ return () => {
1006
+ subscribers.delete(subscriber);
1007
+ };
1008
+ }
1009
+ };
1010
+ };
1011
+
1012
+ // src/client/createVoiceStream.ts
1013
+ var createVoiceStream = (path, options = {}) => {
1014
+ const connection = createVoiceConnection(path, options);
1015
+ const store = createVoiceStreamStore();
1016
+ const browserMediaReporter = options.browserMedia && typeof window !== "undefined" ? createVoiceBrowserMediaReporter({
1017
+ ...options.browserMedia,
1018
+ getScenarioId: () => options.browserMedia ? options.browserMedia.getScenarioId?.() ?? connection.getScenarioId() : connection.getScenarioId(),
1019
+ getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
1020
+ }) : null;
1021
+ const subscribers = new Set;
1022
+ const start = (input) => Promise.resolve().then(() => {
1023
+ if (!input?.sessionId && !input?.scenarioId) {
1024
+ return;
1025
+ }
1026
+ connection.start(input);
1027
+ browserMediaReporter?.start();
1028
+ });
1029
+ const notify = () => {
1030
+ subscribers.forEach((subscriber) => subscriber());
1031
+ };
1032
+ const reportReconnect = () => {
1033
+ if (!options.reconnectReportPath || typeof fetch === "undefined") {
1034
+ return;
1035
+ }
1036
+ const snapshot = store.getSnapshot();
1037
+ const body = JSON.stringify({
1038
+ at: Date.now(),
1039
+ reconnect: snapshot.reconnect,
1040
+ scenarioId: snapshot.scenarioId,
1041
+ sessionId: connection.getSessionId(),
1042
+ turnIds: snapshot.turns.map((turn) => turn.id)
1043
+ });
1044
+ fetch(options.reconnectReportPath, {
1045
+ body,
1046
+ headers: {
1047
+ "Content-Type": "application/json"
1048
+ },
1049
+ keepalive: true,
1050
+ method: "POST"
1051
+ }).catch(() => {});
1052
+ };
1053
+ const unsubscribeConnection = connection.subscribe((message) => {
1054
+ const action = serverMessageToAction(message);
1055
+ if (action) {
1056
+ store.dispatch(action);
1057
+ if (message.type === "connection") {
1058
+ reportReconnect();
1059
+ }
1060
+ notify();
1061
+ }
1062
+ });
1063
+ return {
1064
+ start,
1065
+ get assistantAudio() {
1066
+ return store.getSnapshot().assistantAudio;
1067
+ },
1068
+ get assistantTexts() {
1069
+ return store.getSnapshot().assistantTexts;
1070
+ },
1071
+ get assistantStreamingText() {
1072
+ return store.getSnapshot().assistantStreamingText;
1073
+ },
1074
+ get call() {
1075
+ return store.getSnapshot().call;
1076
+ },
1077
+ callControl(message) {
1078
+ connection.callControl(message);
1079
+ },
1080
+ close(reason) {
1081
+ unsubscribeConnection();
1082
+ browserMediaReporter?.close();
1083
+ connection.close(reason);
1084
+ store.dispatch({ type: "disconnected" });
1085
+ notify();
1086
+ },
1087
+ disconnect() {
1088
+ unsubscribeConnection();
1089
+ browserMediaReporter?.close();
1090
+ connection.disconnect();
1091
+ store.dispatch({ type: "disconnected" });
1092
+ notify();
1093
+ },
1094
+ endTurn() {
1095
+ connection.endTurn();
1096
+ },
1097
+ get error() {
1098
+ return store.getSnapshot().error;
1099
+ },
1100
+ getServerSnapshot() {
1101
+ return store.getServerSnapshot();
1102
+ },
1103
+ getSnapshot() {
1104
+ return store.getSnapshot();
1105
+ },
1106
+ get isConnected() {
1107
+ return store.getSnapshot().isConnected;
1108
+ },
1109
+ get partial() {
1110
+ return store.getSnapshot().partial;
1111
+ },
1112
+ get reconnect() {
1113
+ return store.getSnapshot().reconnect;
1114
+ },
1115
+ get scenarioId() {
1116
+ return store.getSnapshot().scenarioId;
1117
+ },
1118
+ sendAudio(audio) {
1119
+ connection.sendAudio(audio);
1120
+ },
1121
+ get sessionId() {
1122
+ return connection.getSessionId();
1123
+ },
1124
+ get sessionMetadata() {
1125
+ return store.getSnapshot().sessionMetadata;
1126
+ },
1127
+ simulateDisconnect() {
1128
+ connection.simulateDisconnect();
1129
+ },
1130
+ get status() {
1131
+ return store.getSnapshot().status;
1132
+ },
1133
+ subscribe(subscriber) {
1134
+ subscribers.add(subscriber);
1135
+ return () => {
1136
+ subscribers.delete(subscriber);
1137
+ };
1138
+ },
1139
+ get turns() {
1140
+ return store.getSnapshot().turns;
1141
+ }
1142
+ };
1143
+ };
1144
+
1145
+ // src/core/audioConditioning.ts
1146
+ var DEFAULT_TARGET_LEVEL = 0.08;
1147
+ var DEFAULT_MAX_GAIN = 3;
1148
+ var DEFAULT_NOISE_GATE_THRESHOLD = 0.006;
1149
+ var DEFAULT_NOISE_GATE_ATTENUATION = 0.15;
1150
+ var resolveAudioConditioningConfig = (config) => {
1151
+ if (!config || config.enabled === false) {
1152
+ return;
1153
+ }
1154
+ return {
1155
+ enabled: true,
1156
+ maxGain: config.maxGain ?? DEFAULT_MAX_GAIN,
1157
+ noiseGateAttenuation: config.noiseGateAttenuation ?? DEFAULT_NOISE_GATE_ATTENUATION,
1158
+ noiseGateThreshold: config.noiseGateThreshold ?? DEFAULT_NOISE_GATE_THRESHOLD,
1159
+ targetLevel: config.targetLevel ?? DEFAULT_TARGET_LEVEL
1160
+ };
1161
+ };
1162
+
1163
+ // src/core/turnProfiles.ts
1164
+ var TURN_PROFILE_DEFAULTS = {
1165
+ balanced: {
1166
+ qualityProfile: "general",
1167
+ minSilenceMs: 400,
1168
+ silenceMs: 1400,
1169
+ speechThreshold: 0.012,
1170
+ transcriptStabilityMs: 1000
1171
+ },
1172
+ fast: {
1173
+ qualityProfile: "general",
1174
+ minSilenceMs: 300,
1175
+ silenceMs: 700,
1176
+ speechThreshold: 0.015,
1177
+ transcriptStabilityMs: 450
1178
+ },
1179
+ "long-form": {
1180
+ qualityProfile: "general",
1181
+ minSilenceMs: 600,
1182
+ silenceMs: 2200,
1183
+ speechThreshold: 0.01,
1184
+ transcriptStabilityMs: 1500
1185
+ }
1186
+ };
1187
+ var QUALITY_PROFILE_DEFAULTS = {
1188
+ "accent-heavy": {
1189
+ silenceMs: 1200,
1190
+ speechThreshold: 0.01,
1191
+ transcriptStabilityMs: 1200
1192
+ },
1193
+ general: {},
1194
+ "noisy-room": {
1195
+ silenceMs: 2000,
1196
+ speechThreshold: 0.02,
1197
+ transcriptStabilityMs: 1600
1198
+ },
1199
+ "short-command": {
1200
+ silenceMs: 500,
1201
+ speechThreshold: 0.016,
1202
+ transcriptStabilityMs: 420
1203
+ }
1204
+ };
1205
+ var DEFAULT_TURN_PROFILE = "fast";
1206
+ var DEFAULT_QUALITY_PROFILE = "general";
1207
+ var resolveTurnDetectionConfig = (config) => {
1208
+ const profile = config?.profile ?? DEFAULT_TURN_PROFILE;
1209
+ const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
1210
+ const preset = TURN_PROFILE_DEFAULTS[profile];
1211
+ const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
1212
+ const silenceMs = config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs;
1213
+ return {
1214
+ profile,
1215
+ qualityProfile,
1216
+ minSilenceMs: Math.min(silenceMs, config?.minSilenceMs ?? quality.minSilenceMs ?? preset.minSilenceMs),
1217
+ silenceMs,
1218
+ speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
1219
+ transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
1220
+ };
1221
+ };
1222
+
1223
+ // src/core/presets.ts
1224
+ var PRESET_INPUTS = {
1225
+ chat: {
1226
+ audioConditioning: {
1227
+ enabled: true,
1228
+ maxGain: 2.5,
1229
+ noiseGateAttenuation: 0,
1230
+ noiseGateThreshold: 0.004,
1231
+ targetLevel: 0.08
1232
+ },
1233
+ capture: {
1234
+ channelCount: 1,
1235
+ sampleRateHz: 16000
1236
+ },
1237
+ connection: {
1238
+ maxReconnectAttempts: 10,
1239
+ pingInterval: 30000,
1240
+ reconnect: true
1241
+ },
1242
+ sttLifecycle: "continuous",
1243
+ turnDetection: {
1244
+ profile: "balanced",
1245
+ qualityProfile: "short-command"
1246
+ }
1247
+ },
1248
+ default: {
1249
+ capture: {
1250
+ channelCount: 1,
1251
+ sampleRateHz: 16000
1252
+ },
1253
+ connection: {
1254
+ maxReconnectAttempts: 10,
1255
+ pingInterval: 30000,
1256
+ reconnect: true
1257
+ },
1258
+ sttLifecycle: "continuous",
1259
+ turnDetection: {
1260
+ profile: "fast",
1261
+ qualityProfile: "general"
1262
+ }
1263
+ },
1264
+ dictation: {
1265
+ audioConditioning: {
1266
+ enabled: true,
1267
+ maxGain: 2.25,
1268
+ noiseGateAttenuation: 0.05,
1269
+ noiseGateThreshold: 0.003,
1270
+ targetLevel: 0.08
1271
+ },
1272
+ capture: {
1273
+ channelCount: 1,
1274
+ sampleRateHz: 16000
1275
+ },
1276
+ connection: {
1277
+ maxReconnectAttempts: 12,
1278
+ pingInterval: 30000,
1279
+ reconnect: true
1280
+ },
1281
+ sttLifecycle: "continuous",
1282
+ turnDetection: {
1283
+ profile: "long-form",
1284
+ qualityProfile: "accent-heavy"
1285
+ }
1286
+ },
1287
+ "guided-intake": {
1288
+ audioConditioning: {
1289
+ enabled: true,
1290
+ maxGain: 2.5,
1291
+ noiseGateAttenuation: 0,
1292
+ noiseGateThreshold: 0.004,
1293
+ targetLevel: 0.08
1294
+ },
1295
+ capture: {
1296
+ channelCount: 1,
1297
+ sampleRateHz: 16000
1298
+ },
1299
+ connection: {
1300
+ maxReconnectAttempts: 12,
1301
+ pingInterval: 30000,
1302
+ reconnect: true
1303
+ },
1304
+ sttLifecycle: "turn-scoped",
1305
+ turnDetection: {
1306
+ profile: "long-form",
1307
+ qualityProfile: "accent-heavy"
1308
+ }
1309
+ },
1310
+ "noisy-room": {
1311
+ audioConditioning: {
1312
+ enabled: true,
1313
+ maxGain: 3,
1314
+ noiseGateAttenuation: 0.12,
1315
+ noiseGateThreshold: 0.006,
1316
+ targetLevel: 0.085
1317
+ },
1318
+ capture: {
1319
+ channelCount: 1,
1320
+ sampleRateHz: 16000
1321
+ },
1322
+ connection: {
1323
+ maxReconnectAttempts: 14,
1324
+ pingInterval: 45000,
1325
+ reconnect: true
1326
+ },
1327
+ sttLifecycle: "continuous",
1328
+ turnDetection: {
1329
+ profile: "long-form",
1330
+ qualityProfile: "noisy-room",
1331
+ silenceMs: 2100,
1332
+ speechThreshold: 0.02,
1333
+ transcriptStabilityMs: 1650
1334
+ }
1335
+ },
1336
+ "pstn-balanced": {
1337
+ audioConditioning: {
1338
+ enabled: true,
1339
+ maxGain: 2.8,
1340
+ noiseGateAttenuation: 0.07,
1341
+ noiseGateThreshold: 0.005,
1342
+ targetLevel: 0.08
1343
+ },
1344
+ capture: {
1345
+ channelCount: 1,
1346
+ sampleRateHz: 16000
1347
+ },
1348
+ connection: {
1349
+ maxReconnectAttempts: 14,
1350
+ pingInterval: 45000,
1351
+ reconnect: true
1352
+ },
1353
+ sttLifecycle: "continuous",
1354
+ turnDetection: {
1355
+ profile: "long-form",
1356
+ qualityProfile: "noisy-room",
1357
+ silenceMs: 660,
1358
+ speechThreshold: 0.012,
1359
+ transcriptStabilityMs: 300
1360
+ }
1361
+ },
1362
+ "pstn-fast": {
1363
+ audioConditioning: {
1364
+ enabled: true,
1365
+ maxGain: 2.75,
1366
+ noiseGateAttenuation: 0.06,
1367
+ noiseGateThreshold: 0.005,
1368
+ targetLevel: 0.08
1369
+ },
1370
+ capture: {
1371
+ channelCount: 1,
1372
+ sampleRateHz: 16000
1373
+ },
1374
+ connection: {
1375
+ maxReconnectAttempts: 14,
1376
+ pingInterval: 45000,
1377
+ reconnect: true
1378
+ },
1379
+ sttLifecycle: "continuous",
1380
+ turnDetection: {
1381
+ profile: "long-form",
1382
+ qualityProfile: "noisy-room",
1383
+ silenceMs: 620,
1384
+ speechThreshold: 0.012,
1385
+ transcriptStabilityMs: 280
1386
+ }
1387
+ },
1388
+ reliability: {
1389
+ audioConditioning: {
1390
+ enabled: true,
1391
+ maxGain: 2.9,
1392
+ noiseGateAttenuation: 0.08,
1393
+ noiseGateThreshold: 0.005,
1394
+ targetLevel: 0.08
1395
+ },
1396
+ capture: {
1397
+ channelCount: 1,
1398
+ sampleRateHz: 16000
1399
+ },
1400
+ connection: {
1401
+ maxReconnectAttempts: 14,
1402
+ pingInterval: 45000,
1403
+ reconnect: true
1404
+ },
1405
+ sttLifecycle: "continuous",
1406
+ turnDetection: {
1407
+ profile: "long-form",
1408
+ qualityProfile: "noisy-room"
1409
+ }
1410
+ }
1411
+ };
1412
+ var resolveVoiceRuntimePreset = (name = "default") => {
1413
+ const preset = PRESET_INPUTS[name];
1414
+ return {
1415
+ audioConditioning: resolveAudioConditioningConfig(preset.audioConditioning),
1416
+ capture: {
1417
+ channelCount: preset.capture?.channelCount ?? 1,
1418
+ sampleRateHz: preset.capture?.sampleRateHz ?? 16000
1419
+ },
1420
+ connection: {
1421
+ ...preset.connection
1422
+ },
1423
+ name,
1424
+ sttLifecycle: preset.sttLifecycle ?? "continuous",
1425
+ turnDetection: resolveTurnDetectionConfig(preset.turnDetection)
1426
+ };
1427
+ };
1428
+
1429
+ // src/client/controller.ts
1430
+ var createInitialState2 = (stream) => ({
1431
+ assistantAudio: [...stream.assistantAudio],
1432
+ assistantStreamingText: stream.assistantStreamingText,
1433
+ assistantTexts: [...stream.assistantTexts],
1434
+ call: stream.call,
1435
+ error: stream.error,
1436
+ isConnected: stream.isConnected,
1437
+ isRecording: false,
1438
+ partial: stream.partial,
1439
+ reconnect: stream.reconnect,
1440
+ recordingError: null,
1441
+ sessionId: stream.sessionId,
1442
+ sessionMetadata: stream.sessionMetadata,
1443
+ scenarioId: stream.scenarioId,
1444
+ status: stream.status,
1445
+ turns: [...stream.turns]
1446
+ });
1447
+ var createVoiceController = (path, options = {}) => {
1448
+ const preset = resolveVoiceRuntimePreset(options.preset);
1449
+ const stream = createVoiceStream(path, {
1450
+ ...preset.connection,
1451
+ ...options.connection
1452
+ });
1453
+ let capture = null;
1454
+ let state = createInitialState2(stream);
1455
+ const subscribers = new Set;
1456
+ const notify = () => {
1457
+ for (const subscriber of subscribers) {
1458
+ subscriber();
1459
+ }
1460
+ };
1461
+ const sync = () => {
1462
+ state = {
1463
+ ...state,
1464
+ assistantAudio: [...stream.assistantAudio],
1465
+ assistantStreamingText: stream.assistantStreamingText,
1466
+ assistantTexts: [...stream.assistantTexts],
1467
+ call: stream.call,
1468
+ error: stream.error,
1469
+ isConnected: stream.isConnected,
1470
+ partial: stream.partial,
1471
+ reconnect: stream.reconnect,
1472
+ sessionId: stream.sessionId,
1473
+ sessionMetadata: stream.sessionMetadata,
1474
+ scenarioId: stream.scenarioId,
1475
+ status: stream.status,
1476
+ turns: [...stream.turns]
1477
+ };
1478
+ if (options.autoStopOnComplete !== false && state.status === "completed" && state.isRecording) {
1479
+ capture?.stop();
1480
+ capture = null;
1481
+ state = {
1482
+ ...state,
1483
+ isRecording: false
1484
+ };
1485
+ }
1486
+ notify();
1487
+ };
1488
+ const unsubscribeStream = stream.subscribe(sync);
1489
+ sync();
1490
+ const ensureCapture = () => {
1491
+ if (capture) {
1492
+ return capture;
1493
+ }
1494
+ capture = createMicrophoneCapture({
1495
+ channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
1496
+ onLevel: options.capture?.onLevel,
1497
+ onAudio: (audio) => {
1498
+ if (options.capture?.onAudio) {
1499
+ options.capture.onAudio(audio, stream.sendAudio);
1500
+ return;
1501
+ }
1502
+ stream.sendAudio(audio);
1503
+ },
1504
+ sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz,
1505
+ ...options.capture?.stream ? { stream: options.capture.stream } : {}
1506
+ });
1507
+ return capture;
1508
+ };
1509
+ const stopRecording = () => {
1510
+ capture?.stop();
1511
+ capture = null;
1512
+ state = {
1513
+ ...state,
1514
+ isRecording: false
1515
+ };
1516
+ notify();
1517
+ };
1518
+ const startRecording = async () => {
1519
+ if (state.isRecording) {
1520
+ return;
1521
+ }
1522
+ try {
1523
+ state = {
1524
+ ...state,
1525
+ recordingError: null
1526
+ };
1527
+ notify();
1528
+ await ensureCapture().start();
1529
+ state = {
1530
+ ...state,
1531
+ isRecording: true
1532
+ };
1533
+ notify();
1534
+ } catch (error) {
1535
+ capture?.stop();
1536
+ capture = null;
1537
+ state = {
1538
+ ...state,
1539
+ isRecording: false,
1540
+ recordingError: error instanceof Error ? error.message : String(error)
1541
+ };
1542
+ notify();
1543
+ throw error;
1544
+ }
1545
+ };
1546
+ const close = (reason) => {
1547
+ unsubscribeStream();
1548
+ stopRecording();
1549
+ stream.close(reason);
1550
+ };
1551
+ const disconnect = () => {
1552
+ unsubscribeStream();
1553
+ stopRecording();
1554
+ stream.disconnect();
1555
+ };
1556
+ return {
1557
+ close,
1558
+ disconnect,
1559
+ startRecording,
1560
+ stopRecording,
1561
+ get assistantAudio() {
1562
+ return state.assistantAudio;
1563
+ },
1564
+ get assistantTexts() {
1565
+ return state.assistantTexts;
1566
+ },
1567
+ get assistantStreamingText() {
1568
+ return state.assistantStreamingText;
1569
+ },
1570
+ bindHTMX(bindingOptions) {
1571
+ return bindVoiceHTMX(stream, bindingOptions);
1572
+ },
1573
+ get call() {
1574
+ return state.call;
1575
+ },
1576
+ callControl: (message) => stream.callControl(message),
1577
+ endTurn: () => stream.endTurn(),
1578
+ get error() {
1579
+ return state.error;
1580
+ },
1581
+ getServerSnapshot: () => state,
1582
+ getSnapshot: () => state,
1583
+ get isConnected() {
1584
+ return state.isConnected;
1585
+ },
1586
+ get isRecording() {
1587
+ return state.isRecording;
1588
+ },
1589
+ get partial() {
1590
+ return state.partial;
1591
+ },
1592
+ get reconnect() {
1593
+ return state.reconnect;
1594
+ },
1595
+ get recordingError() {
1596
+ return state.recordingError;
1597
+ },
1598
+ get scenarioId() {
1599
+ return state.scenarioId;
1600
+ },
1601
+ sendAudio: (audio) => stream.sendAudio(audio),
1602
+ get sessionId() {
1603
+ return state.sessionId;
1604
+ },
1605
+ get sessionMetadata() {
1606
+ return state.sessionMetadata;
1607
+ },
1608
+ simulateDisconnect: () => stream.simulateDisconnect(),
1609
+ get status() {
1610
+ return state.status;
1611
+ },
1612
+ subscribe: (subscriber) => {
1613
+ subscribers.add(subscriber);
1614
+ return () => {
1615
+ subscribers.delete(subscriber);
1616
+ };
1617
+ },
1618
+ toggleRecording: async () => {
1619
+ if (state.isRecording) {
1620
+ stopRecording();
1621
+ return;
1622
+ }
1623
+ await startRecording();
1624
+ },
1625
+ get turns() {
1626
+ return state.turns;
1627
+ }
1628
+ };
1629
+ };
1630
+
1631
+ // src/internal/html.ts
1632
+ var escapeHtml = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1633
+
1634
+ // src/core/agentState.ts
1635
+ var deriveVoiceAgentUIState = (input) => {
1636
+ if (!input.isConnected) {
1637
+ return "idle";
1638
+ }
1639
+ if (input.isPlaying) {
1640
+ return "speaking";
1641
+ }
1642
+ if (input.isRecording && input.hasActivePartial) {
1643
+ return "listening";
1644
+ }
1645
+ if (input.isRecording) {
1646
+ return "listening";
1647
+ }
1648
+ if (input.lastTranscriptAt && !input.lastAssistantAt) {
1649
+ return "thinking";
1650
+ }
1651
+ if (input.lastTranscriptAt && input.lastAssistantAt && input.lastTranscriptAt > input.lastAssistantAt) {
1652
+ return "thinking";
1653
+ }
1654
+ return "idle";
1655
+ };
1656
+
1657
+ // src/client/voiceWidgetView.ts
1658
+ var DEFAULT_VOICE_WIDGET_THEME = {
1659
+ accent: "#3b82f6",
1660
+ background: "#0f172a",
1661
+ errorAccent: "#ef4444",
1662
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
1663
+ foreground: "#f8fafc",
1664
+ radius: 16
1665
+ };
1666
+ var DEFAULT_VOICE_WIDGET_LABELS = {
1667
+ callEnded: "Call ended",
1668
+ connecting: "Connecting…",
1669
+ endCall: "End call",
1670
+ idle: "Idle",
1671
+ listening: "Listening",
1672
+ mute: "Mute",
1673
+ speaking: "Speaking",
1674
+ startCall: "Start call",
1675
+ thinking: "Thinking",
1676
+ unmute: "Unmute"
1677
+ };
1678
+ var stateLabel = (state, labels) => {
1679
+ switch (state) {
1680
+ case "listening":
1681
+ return labels.listening;
1682
+ case "speaking":
1683
+ return labels.speaking;
1684
+ case "thinking":
1685
+ return labels.thinking;
1686
+ case "idle":
1687
+ return labels.idle;
1688
+ }
1689
+ };
1690
+ var createVoiceWidgetViewModel = (input) => {
1691
+ const theme = { ...DEFAULT_VOICE_WIDGET_THEME, ...input.theme };
1692
+ const labels = { ...DEFAULT_VOICE_WIDGET_LABELS, ...input.labels };
1693
+ const lastAssistantAt = input.state.assistantAudio.at(-1)?.receivedAt;
1694
+ const lastTranscriptAt = input.state.turns.at(-1)?.committedAt;
1695
+ const agentState = deriveVoiceAgentUIState({
1696
+ hasActivePartial: input.state.partial.length > 0,
1697
+ isConnected: input.state.isConnected,
1698
+ isPlaying: false,
1699
+ isRecording: input.state.isRecording,
1700
+ lastAssistantAt,
1701
+ lastTranscriptAt
1702
+ });
1703
+ const connecting = !input.state.isConnected && input.state.status !== "idle" && !input.state.error;
1704
+ const statusLabel = input.state.error ? "Error" : connecting ? labels.connecting : input.state.status === "completed" ? labels.callEnded : stateLabel(agentState, labels);
1705
+ return {
1706
+ agentState,
1707
+ classes: {
1708
+ container: `absolute-voice-widget absolute-voice-widget--${agentState}`,
1709
+ dot: `absolute-voice-widget__dot${input.state.error ? " absolute-voice-widget__dot--error" : ""}`
1710
+ },
1711
+ controls: {
1712
+ canEnd: input.state.isConnected,
1713
+ canMute: input.state.isRecording,
1714
+ canStart: !input.state.isRecording && input.state.status !== "completed"
1715
+ },
1716
+ errorMessage: input.state.error ?? undefined,
1717
+ labels,
1718
+ partial: input.state.partial || undefined,
1719
+ statusLabel,
1720
+ theme,
1721
+ title: input.title ?? "Voice"
1722
+ };
1723
+ };
1724
+ var resolveRadius = (radius) => typeof radius === "number" ? `${radius}px` : radius;
1725
+ var renderVoiceWidgetHTML = (model) => {
1726
+ const t = model.theme;
1727
+ const containerStyle = `background:${t.background};border-radius:${resolveRadius(t.radius)};color:${t.foreground};font-family:${t.fontFamily};min-width:240px;padding:20px 22px;`;
1728
+ const dotStyle = `background:${model.errorMessage ? t.errorAccent : model.agentState === "idle" ? "rgba(148,163,184,0.6)" : t.accent};border-radius:50%;height:10px;width:10px;`;
1729
+ const buttons = [];
1730
+ if (model.controls.canStart) {
1731
+ buttons.push(`<button type="button" data-action="start" style="background:${t.accent};border:none;border-radius:12px;color:${t.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${escapeHtml(model.labels.startCall)}</button>`);
1732
+ }
1733
+ if (model.controls.canMute) {
1734
+ buttons.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${t.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${escapeHtml(model.labels.mute)}</button>`);
1735
+ }
1736
+ if (model.controls.canEnd) {
1737
+ buttons.push(`<button type="button" data-action="end" style="background:${t.errorAccent};border:none;border-radius:12px;color:${t.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${escapeHtml(model.labels.endCall)}</button>`);
1738
+ }
1739
+ return `<div role="region" aria-live="polite" data-agent-state="${model.agentState}" class="${escapeHtml(model.classes.container)}" style="${containerStyle}">
1740
+ <div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
1741
+ <span aria-hidden="true" class="${escapeHtml(model.classes.dot)}" style="${dotStyle}"></span>
1742
+ <strong style="font-size:15px;">${escapeHtml(model.title)}</strong>
1743
+ <span style="font-size:13px;margin-left:auto;opacity:0.7;">${escapeHtml(model.statusLabel)}</span>
1744
+ </div>
1745
+ ${model.partial ? `<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${escapeHtml(model.partial)}”</p>` : ""}
1746
+ <div style="display:flex;gap:10px;">${buttons.join("")}</div>
1747
+ ${model.errorMessage ? `<p style="color:${t.errorAccent};font-size:12px;margin-top:12px;">${escapeHtml(model.errorMessage)}</p>` : ""}
1748
+ </div>`;
1749
+ };
1750
+
1751
+ // src/embed/index.ts
1752
+ var resolveTarget = (target) => {
1753
+ if (typeof target !== "string")
1754
+ return target;
1755
+ const el = document.querySelector(target);
1756
+ if (!el) {
1757
+ throw new Error(`AbsoluteVoice.mount: no element matches "${target}"`);
1758
+ }
1759
+ return el;
1760
+ };
1761
+ var mount = (target, options = {}) => {
1762
+ const host = resolveTarget(target);
1763
+ const controller = createVoiceController(options.path ?? "/voice", options.controllerOptions);
1764
+ let lastError = null;
1765
+ let lastStatus = null;
1766
+ const render = () => {
1767
+ const model = createVoiceWidgetViewModel({
1768
+ ...options.labels !== undefined ? { labels: options.labels } : {},
1769
+ state: {
1770
+ assistantAudio: controller.assistantAudio,
1771
+ error: controller.error,
1772
+ isConnected: controller.isConnected,
1773
+ isRecording: controller.isRecording,
1774
+ partial: controller.partial,
1775
+ status: controller.status,
1776
+ turns: controller.turns
1777
+ },
1778
+ ...options.theme !== undefined ? { theme: options.theme } : {},
1779
+ ...options.title !== undefined ? { title: options.title } : {}
1780
+ });
1781
+ host.innerHTML = renderVoiceWidgetHTML(model);
1782
+ for (const button of host.querySelectorAll("button[data-action]")) {
1783
+ const { action } = button.dataset;
1784
+ button.addEventListener("click", () => {
1785
+ if (action === "start")
1786
+ controller.startRecording();
1787
+ else if (action === "mute")
1788
+ controller.stopRecording();
1789
+ else if (action === "end")
1790
+ controller.close();
1791
+ });
1792
+ }
1793
+ if (controller.error && controller.error !== lastError) {
1794
+ lastError = controller.error;
1795
+ options.onError?.(controller.error);
1796
+ }
1797
+ if (controller.status !== lastStatus) {
1798
+ lastStatus = controller.status;
1799
+ options.onStatusChange?.(controller.status);
1800
+ }
1801
+ };
1802
+ const unsubscribe = controller.subscribe(render);
1803
+ render();
1804
+ if (options.autoStart) {
1805
+ controller.startRecording();
1806
+ }
1807
+ return {
1808
+ controller,
1809
+ async end() {
1810
+ await controller.close();
1811
+ },
1812
+ mute() {
1813
+ controller.stopRecording();
1814
+ },
1815
+ async start() {
1816
+ await controller.startRecording();
1817
+ },
1818
+ unmount() {
1819
+ unsubscribe();
1820
+ controller.close();
1821
+ host.innerHTML = "";
1822
+ }
1823
+ };
1824
+ };
1825
+ var VOICE_EMBED_VERSION = "0.0.22-beta.516";
1826
+ var globalApi = {
1827
+ mount,
1828
+ version: VOICE_EMBED_VERSION
1829
+ };
1830
+ if (typeof globalThis !== "undefined") {
1831
+ globalThis.AbsoluteVoice = globalApi;
1832
+ }
1833
+ var embed_default = globalApi;
1834
+ export {
1835
+ mount,
1836
+ embed_default as default,
1837
+ VOICE_EMBED_VERSION
1838
+ };