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