@absolutejs/voice 0.0.22-beta.5 → 0.0.22-beta.500
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 +4556 -603
- package/dist/agent.d.ts +79 -5
- package/dist/agentSquadContract.d.ts +98 -0
- package/dist/agentState.d.ts +12 -0
- package/dist/agentTools.d.ts +133 -0
- package/dist/aiVoiceModel.d.ts +15 -0
- package/dist/amdDetector.d.ts +25 -0
- package/dist/angular/index.d.ts +26 -2
- package/dist/angular/index.js +3703 -246
- 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-campaign-dialer-proof.service.d.ts +14 -0
- package/dist/angular/voice-controller.service.d.ts +3 -1
- package/dist/angular/voice-delivery-runtime.component.d.ts +17 -0
- package/dist/angular/voice-delivery-runtime.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.component.d.ts +15 -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 +12 -0
- 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-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 +6 -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 +12 -0
- package/dist/assistant.d.ts +12 -13
- package/dist/assistantHealth.d.ts +81 -0
- package/dist/assistantMemory.d.ts +3 -3
- package/dist/assistantMode.d.ts +22 -0
- package/dist/audioConditioning.d.ts +1 -1
- package/dist/audit.d.ts +131 -0
- package/dist/auditDeliveryRoutes.d.ts +85 -0
- package/dist/auditExport.d.ts +34 -0
- package/dist/auditRoutes.d.ts +66 -0
- package/dist/auditSinks.d.ts +151 -0
- package/dist/backchannel.d.ts +18 -0
- package/dist/bargeInRoutes.d.ts +56 -0
- package/dist/browserCallProfiles.d.ts +120 -0
- package/dist/browserMediaRoutes.d.ts +62 -0
- package/dist/callDebugger.d.ts +66 -0
- package/dist/callQuota.d.ts +54 -0
- package/dist/callerMemory.d.ts +37 -0
- package/dist/campaign.d.ts +794 -0
- package/dist/campaignControls.d.ts +37 -0
- package/dist/campaignDialers.d.ts +111 -0
- package/dist/client/actions.d.ts +117 -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/callDebugger.d.ts +19 -0
- package/dist/client/callDebuggerWidget.d.ts +30 -0
- package/dist/client/campaignDialerProof.d.ts +23 -0
- package/dist/client/connection.d.ts +5 -1
- package/dist/client/controller.d.ts +1 -1
- package/dist/client/costDashboard.d.ts +27 -0
- package/dist/client/createVoiceStream.d.ts +1 -1
- package/dist/client/deliveryRuntime.d.ts +34 -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/htmxBootstrap.js +1011 -16
- package/dist/client/index.d.ts +104 -7
- package/dist/client/index.js +10409 -10
- 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 +54 -0
- package/dist/client/opsActionCenterWidget.d.ts +29 -0
- package/dist/client/opsActionHistory.d.ts +19 -0
- package/dist/client/opsActionHistoryWidget.d.ts +11 -0
- package/dist/client/opsStatus.d.ts +19 -0
- package/dist/client/opsStatusWidget.d.ts +40 -0
- package/dist/client/platformCoverage.d.ts +19 -0
- package/dist/client/platformCoverageWidget.d.ts +37 -0
- package/dist/client/profileComparison.d.ts +19 -0
- package/dist/client/profileComparisonWidget.d.ts +41 -0
- package/dist/client/profileSwitchRecommendation.d.ts +19 -0
- package/dist/client/profileSwitchRecommendationWidget.d.ts +12 -0
- package/dist/client/proofTrends.d.ts +19 -0
- package/dist/client/proofTrendsWidget.d.ts +37 -0
- package/dist/client/providerCapabilities.d.ts +19 -0
- package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
- package/dist/client/providerContracts.d.ts +19 -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 +19 -0
- package/dist/client/providerStatusWidget.d.ts +32 -0
- package/dist/client/readinessFailures.d.ts +19 -0
- package/dist/client/readinessFailuresWidget.d.ts +42 -0
- package/dist/client/reconnectProfileEvidence.d.ts +19 -0
- package/dist/client/reconnectProfileEvidenceWidget.d.ts +39 -0
- package/dist/client/replayTimeline.d.ts +26 -0
- package/dist/client/routingStatus.d.ts +19 -0
- package/dist/client/routingStatusWidget.d.ts +32 -0
- package/dist/client/sessionObservability.d.ts +19 -0
- package/dist/client/sessionObservabilityWidget.d.ts +31 -0
- package/dist/client/sessionSnapshot.d.ts +21 -0
- package/dist/client/sessionSnapshotWidget.d.ts +33 -0
- package/dist/client/store.d.ts +1 -1
- package/dist/client/traceTimeline.d.ts +19 -0
- package/dist/client/traceTimelineWidget.d.ts +36 -0
- package/dist/client/turnLatency.d.ts +22 -0
- package/dist/client/turnLatencyWidget.d.ts +33 -0
- package/dist/client/turnQuality.d.ts +19 -0
- package/dist/client/turnQualityWidget.d.ts +32 -0
- package/dist/client/voiceWidgetView.d.ts +50 -0
- package/dist/client/workflowStatus.d.ts +19 -0
- package/dist/competitiveCoverage.d.ts +141 -0
- package/dist/correction.d.ts +2 -2
- package/dist/costAccounting.d.ts +76 -0
- package/dist/dataControl.d.ts +180 -0
- package/dist/defineVoiceAssistant.d.ts +68 -0
- package/dist/deliveryRuntime.d.ts +158 -0
- package/dist/deliverySinkRoutes.d.ts +117 -0
- package/dist/demoReadyRoutes.d.ts +98 -0
- package/dist/diagnosticsRoutes.d.ts +44 -0
- package/dist/evalRoutes.d.ts +219 -0
- package/dist/fileStore.d.ts +21 -7
- package/dist/generated/htmxBootstrapBundle.d.ts +1 -0
- package/dist/guardrails.d.ts +128 -0
- package/dist/handoff.d.ts +54 -0
- package/dist/handoffHealth.d.ts +94 -0
- package/dist/htmx.d.ts +1 -1
- package/dist/incidentBundle.d.ts +119 -0
- package/dist/incidentTimeline.d.ts +260 -0
- package/dist/index.d.ts +278 -46
- package/dist/index.js +42417 -4242
- package/dist/ivrPlan.d.ts +40 -0
- package/dist/latencySlo.d.ts +56 -0
- package/dist/liveLatency.d.ts +78 -0
- package/dist/liveOps.d.ts +190 -0
- package/dist/llmJudge.d.ts +45 -0
- package/dist/logger.d.ts +1 -1
- package/dist/mediaPipelineRoutes.d.ts +171 -0
- package/dist/mediaPipelineSurfaces.d.ts +48 -0
- package/dist/memoryStore.d.ts +1 -1
- package/dist/midCallSummary.d.ts +27 -0
- package/dist/modelAdapters.d.ts +135 -2
- package/dist/monitor.d.ts +148 -0
- package/dist/multilingualProof.d.ts +77 -0
- package/dist/oauth2TokenSource.d.ts +21 -0
- package/dist/observabilityExport.d.ts +501 -0
- package/dist/openaiTTS.d.ts +18 -0
- package/dist/operationalStatus.d.ts +87 -0
- package/dist/operationsRecord.d.ts +370 -0
- package/dist/ops.d.ts +12 -12
- package/dist/opsActionAuditRoutes.d.ts +99 -0
- package/dist/opsConsoleRoutes.d.ts +80 -0
- package/dist/opsPresets.d.ts +2 -2
- package/dist/opsRecovery.d.ts +137 -0
- package/dist/opsRuntime.d.ts +6 -6
- package/dist/opsSinks.d.ts +13 -13
- package/dist/opsStatus.d.ts +76 -0
- package/dist/opsStatusRoutes.d.ts +33 -0
- package/dist/opsWebhook.d.ts +126 -0
- package/dist/otelExporter.d.ts +83 -0
- package/dist/outcomeContract.d.ts +146 -0
- package/dist/outcomeRecipes.d.ts +4 -4
- package/dist/phoneAgent.d.ts +139 -0
- package/dist/phoneAgentProductionSmoke.d.ts +115 -0
- package/dist/platformCoverage.d.ts +91 -0
- package/dist/plugin.d.ts +2 -2
- package/dist/postCallAnalysis.d.ts +98 -0
- package/dist/postgresStore.d.ts +17 -6
- package/dist/presets.d.ts +3 -3
- package/dist/productionReadiness.d.ts +756 -0
- package/dist/profileSwitchRecommendation.d.ts +350 -0
- package/dist/proofAssertions.d.ts +32 -0
- package/dist/proofPack.d.ts +211 -0
- package/dist/proofRunner.d.ts +79 -0
- package/dist/proofTrends.d.ts +966 -0
- package/dist/providerAdapters.d.ts +48 -0
- package/dist/providerCapabilities.d.ts +92 -0
- package/dist/providerDecisionTraces.d.ts +130 -0
- package/dist/providerHealth.d.ts +79 -0
- package/dist/providerOrchestration.d.ts +109 -0
- package/dist/providerRouterTraces.d.ts +35 -0
- package/dist/providerRoutingContract.d.ts +71 -0
- package/dist/providerSlo.d.ts +142 -0
- package/dist/providerStackRecommendations.d.ts +187 -0
- package/dist/qualityRoutes.d.ts +76 -0
- package/dist/queue.d.ts +72 -11
- package/dist/ragTool.d.ts +57 -0
- package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
- package/dist/react/VoiceCallDebuggerLaunch.d.ts +6 -0
- package/dist/react/VoiceCostDashboard.d.ts +10 -0
- package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
- package/dist/react/VoiceLiveCallViewer.d.ts +9 -0
- package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
- package/dist/react/VoiceOpsStatus.d.ts +6 -0
- 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 +55 -2
- package/dist/react/index.js +12290 -15
- 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 +6 -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 +8 -0
- 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 +6 -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 +8 -0
- package/dist/readinessProfiles.d.ts +45 -0
- package/dist/realtimeChannel.d.ts +136 -0
- package/dist/realtimeProviderContracts.d.ts +133 -0
- package/dist/reconnectContract.d.ts +176 -0
- package/dist/recordingRedaction.d.ts +18 -0
- package/dist/recordingStore.d.ts +60 -0
- package/dist/redaction.d.ts +13 -0
- package/dist/resilienceRoutes.d.ts +146 -0
- package/dist/retention.d.ts +37 -0
- package/dist/routeAuth.d.ts +58 -0
- package/dist/routing.d.ts +1 -1
- package/dist/runtimeOps.d.ts +3 -3
- package/dist/s3Store.d.ts +18 -3
- package/dist/semanticTurn.d.ts +27 -0
- package/dist/session.d.ts +1 -1
- package/dist/sessionObservability.d.ts +145 -0
- package/dist/sessionReplay.d.ts +187 -0
- package/dist/sessionSnapshot.d.ts +109 -0
- package/dist/simulationSuite.d.ts +143 -0
- package/dist/sloCalibration.d.ts +185 -0
- package/dist/sqliteStore.d.ts +17 -6
- package/dist/store.d.ts +1 -1
- package/dist/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
- package/dist/svelte/createVoiceCallDebugger.d.ts +12 -0
- package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
- package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
- package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
- package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
- package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
- 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 +10 -0
- package/dist/svelte/createVoiceReadinessFailures.d.ts +7 -0
- package/dist/svelte/createVoiceReconnectProfileEvidence.d.ts +7 -0
- package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
- package/dist/svelte/createVoiceSessionObservability.d.ts +10 -0
- package/dist/svelte/createVoiceSessionSnapshot.d.ts +13 -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 +8 -0
- package/dist/svelte/index.d.ts +27 -2
- package/dist/svelte/index.js +6010 -150
- 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 +147 -13
- package/dist/telephonyMediaRoutes.d.ts +72 -0
- package/dist/telephonyOutcome.d.ts +273 -0
- package/dist/testing/accuracy.d.ts +1 -1
- package/dist/testing/benchmark.d.ts +9 -9
- package/dist/testing/corrected.d.ts +5 -5
- package/dist/testing/duplex.d.ts +3 -3
- package/dist/testing/fixtures.d.ts +3 -3
- package/dist/testing/index.d.ts +13 -11
- package/dist/testing/index.js +9117 -2592
- package/dist/testing/ioProviderSimulator.d.ts +41 -0
- package/dist/testing/providerSimulator.d.ts +44 -0
- package/dist/testing/review.d.ts +4 -4
- package/dist/testing/sessionBenchmark.d.ts +3 -3
- package/dist/testing/stt.d.ts +3 -3
- package/dist/testing/telephony.d.ts +25 -0
- package/dist/testing/tts.d.ts +1 -1
- package/dist/toolContract.d.ts +161 -0
- package/dist/toolRuntime.d.ts +50 -0
- package/dist/trace.d.ts +46 -7
- package/dist/traceDeliveryRoutes.d.ts +86 -0
- package/dist/traceTimeline.d.ts +97 -0
- package/dist/turnDetection.d.ts +1 -1
- package/dist/turnLatency.d.ts +95 -0
- package/dist/turnProfiles.d.ts +2 -2
- package/dist/turnQuality.d.ts +94 -0
- package/dist/types.d.ts +366 -71
- package/dist/vapiAdapter.d.ts +160 -0
- package/dist/voiceMonitoring.d.ts +444 -0
- package/dist/vue/VoiceCallDebuggerLaunch.d.ts +68 -0
- package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
- package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
- package/dist/vue/VoiceOpsStatus.d.ts +30 -0
- package/dist/vue/VoicePlatformCoverage.d.ts +23 -0
- package/dist/vue/VoiceProofTrends.d.ts +21 -0
- package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
- package/dist/vue/VoiceProviderContracts.d.ts +21 -0
- package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
- package/dist/vue/VoiceProviderStatus.d.ts +51 -0
- package/dist/vue/VoiceReadinessFailures.d.ts +21 -0
- package/dist/vue/VoiceReconnectProfileEvidence.d.ts +21 -0
- package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
- package/dist/vue/VoiceSessionObservability.d.ts +23 -0
- package/dist/vue/VoiceSessionSnapshot.d.ts +68 -0
- package/dist/vue/VoiceTurnLatency.d.ts +69 -0
- package/dist/vue/VoiceTurnQuality.d.ts +51 -0
- package/dist/vue/VoiceWidget.d.ts +77 -0
- package/dist/vue/index.d.ts +44 -2
- package/dist/vue/index.js +11027 -31
- 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 +4 -2
- 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 +9 -0
- 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 +7 -2
- 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 +9 -0
- package/dist/webhookVerification.d.ts +27 -0
- package/dist/workflowContract.d.ts +91 -0
- package/fixtures/manifest.json +356 -197
- package/package.json +265 -256
|
@@ -188,6 +188,17 @@ var serverMessageToAction = (message) => {
|
|
|
188
188
|
sessionId: message.sessionId,
|
|
189
189
|
type: "complete"
|
|
190
190
|
};
|
|
191
|
+
case "connection":
|
|
192
|
+
return {
|
|
193
|
+
reconnect: message.reconnect,
|
|
194
|
+
type: "connection"
|
|
195
|
+
};
|
|
196
|
+
case "call_lifecycle":
|
|
197
|
+
return {
|
|
198
|
+
event: message.event,
|
|
199
|
+
sessionId: message.sessionId,
|
|
200
|
+
type: "call_lifecycle"
|
|
201
|
+
};
|
|
191
202
|
case "error":
|
|
192
203
|
return {
|
|
193
204
|
message: normalizeErrorMessage(message.message),
|
|
@@ -203,9 +214,22 @@ var serverMessageToAction = (message) => {
|
|
|
203
214
|
transcript: message.transcript,
|
|
204
215
|
type: "partial"
|
|
205
216
|
};
|
|
217
|
+
case "replay":
|
|
218
|
+
return {
|
|
219
|
+
assistantTexts: message.assistantTexts,
|
|
220
|
+
call: message.call,
|
|
221
|
+
partial: message.partial,
|
|
222
|
+
scenarioId: message.scenarioId,
|
|
223
|
+
sessionId: message.sessionId,
|
|
224
|
+
sessionMetadata: message.sessionMetadata,
|
|
225
|
+
status: message.status,
|
|
226
|
+
turns: message.turns,
|
|
227
|
+
type: "replay"
|
|
228
|
+
};
|
|
206
229
|
case "session":
|
|
207
230
|
return {
|
|
208
231
|
sessionId: message.sessionId,
|
|
232
|
+
sessionMetadata: message.sessionMetadata,
|
|
209
233
|
scenarioId: message.scenarioId,
|
|
210
234
|
status: message.status,
|
|
211
235
|
type: "session"
|
|
@@ -220,6 +244,232 @@ var serverMessageToAction = (message) => {
|
|
|
220
244
|
}
|
|
221
245
|
};
|
|
222
246
|
|
|
247
|
+
// node_modules/@absolutejs/media/dist/index.js
|
|
248
|
+
var pushIssue = (issues, severity, code, message) => {
|
|
249
|
+
issues.push({ code, message, severity });
|
|
250
|
+
};
|
|
251
|
+
var average = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
|
|
252
|
+
var max = (values) => values.length === 0 ? undefined : Math.max(...values);
|
|
253
|
+
var numericStat = (stat, key) => {
|
|
254
|
+
const value = stat[key];
|
|
255
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
256
|
+
};
|
|
257
|
+
var booleanStat = (stat, key) => {
|
|
258
|
+
const value = stat[key];
|
|
259
|
+
return typeof value === "boolean" ? value : undefined;
|
|
260
|
+
};
|
|
261
|
+
var stringStat = (stat, key) => {
|
|
262
|
+
const value = stat[key];
|
|
263
|
+
return typeof value === "string" ? value : undefined;
|
|
264
|
+
};
|
|
265
|
+
var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
|
|
266
|
+
var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
|
|
267
|
+
var normalizeWebRTCStat = (stat) => {
|
|
268
|
+
const sample = {};
|
|
269
|
+
for (const [key, value] of Object.entries(stat)) {
|
|
270
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
271
|
+
sample[key] = value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return sample;
|
|
275
|
+
};
|
|
276
|
+
var buildMediaWebRTCStatsReport = (input = {}) => {
|
|
277
|
+
const stats = input.stats ?? [];
|
|
278
|
+
const issues = [];
|
|
279
|
+
const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
280
|
+
const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
281
|
+
const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
|
|
282
|
+
const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
|
|
283
|
+
const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
|
|
284
|
+
const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
|
|
285
|
+
const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
|
|
286
|
+
const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
|
|
287
|
+
const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
|
|
288
|
+
const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
|
|
289
|
+
const packetLossDenominator = inboundPackets + packetsLost;
|
|
290
|
+
const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
|
|
291
|
+
const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
|
|
292
|
+
const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
|
|
293
|
+
const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
|
|
294
|
+
const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
|
|
295
|
+
const jitterBufferDelayMs = max(inbound.map((stat) => {
|
|
296
|
+
const delay = numericStat(stat, "jitterBufferDelay");
|
|
297
|
+
const emitted = numericStat(stat, "jitterBufferEmittedCount");
|
|
298
|
+
return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
|
|
299
|
+
}).filter((value) => value !== undefined));
|
|
300
|
+
const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
|
|
301
|
+
if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
|
|
302
|
+
pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
|
|
303
|
+
}
|
|
304
|
+
if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
|
|
305
|
+
pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
|
|
306
|
+
}
|
|
307
|
+
if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
|
|
308
|
+
pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
|
|
309
|
+
}
|
|
310
|
+
if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
|
|
311
|
+
pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
|
|
312
|
+
}
|
|
313
|
+
if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
|
|
314
|
+
pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
activeCandidatePairs,
|
|
318
|
+
audioLevelAverage: average(audioLevels),
|
|
319
|
+
bytesReceived,
|
|
320
|
+
bytesSent,
|
|
321
|
+
checkedAt: Date.now(),
|
|
322
|
+
endedAudioTracks,
|
|
323
|
+
inboundPackets,
|
|
324
|
+
issues,
|
|
325
|
+
jitterBufferDelayMs,
|
|
326
|
+
jitterMs,
|
|
327
|
+
liveAudioTracks,
|
|
328
|
+
outboundPackets,
|
|
329
|
+
packetLossRatio,
|
|
330
|
+
packetsLost,
|
|
331
|
+
roundTripTimeMs,
|
|
332
|
+
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
333
|
+
totalStats: stats.length
|
|
334
|
+
};
|
|
335
|
+
};
|
|
336
|
+
var collectMediaWebRTCStats = async (input) => {
|
|
337
|
+
const report = await input.peerConnection.getStats(input.selector ?? null);
|
|
338
|
+
return [...report.values()].map(normalizeWebRTCStat);
|
|
339
|
+
};
|
|
340
|
+
var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
|
|
341
|
+
const stats = input.stats ?? [];
|
|
342
|
+
const previousStats = input.previousStats ?? [];
|
|
343
|
+
const issues = [];
|
|
344
|
+
const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
|
|
345
|
+
const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
|
|
346
|
+
const streams = audioRtp.map((stat) => {
|
|
347
|
+
const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
|
|
348
|
+
const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
|
|
349
|
+
const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
|
|
350
|
+
const previous = previousByKey.get(statKey(stat));
|
|
351
|
+
const currentPackets = numericStat(stat, packetsKey);
|
|
352
|
+
const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
|
|
353
|
+
const currentBytes = numericStat(stat, bytesKey);
|
|
354
|
+
const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
|
|
355
|
+
const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
|
|
356
|
+
return {
|
|
357
|
+
bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
|
|
358
|
+
currentPackets,
|
|
359
|
+
direction,
|
|
360
|
+
id: statKey(stat),
|
|
361
|
+
packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
|
|
362
|
+
previousPackets,
|
|
363
|
+
timeDeltaMs
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
const inbound = streams.filter((stream) => stream.direction === "inbound");
|
|
367
|
+
const outbound = streams.filter((stream) => stream.direction === "outbound");
|
|
368
|
+
const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
|
|
369
|
+
const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
370
|
+
const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
371
|
+
if (input.requireInboundAudio && inbound.length === 0) {
|
|
372
|
+
pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
|
|
373
|
+
}
|
|
374
|
+
if (input.requireOutboundAudio && outbound.length === 0) {
|
|
375
|
+
pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
|
|
376
|
+
}
|
|
377
|
+
if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
|
|
378
|
+
pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
|
|
379
|
+
}
|
|
380
|
+
if (stalledInboundStreams > 0) {
|
|
381
|
+
pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
|
|
382
|
+
}
|
|
383
|
+
if (stalledOutboundStreams > 0) {
|
|
384
|
+
pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
checkedAt: Date.now(),
|
|
388
|
+
inboundAudioStreams: inbound.length,
|
|
389
|
+
issues,
|
|
390
|
+
maxObservedGapMs,
|
|
391
|
+
outboundAudioStreams: outbound.length,
|
|
392
|
+
stalledInboundStreams,
|
|
393
|
+
stalledOutboundStreams,
|
|
394
|
+
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
395
|
+
streams,
|
|
396
|
+
totalStats: stats.length
|
|
397
|
+
};
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/client/browserMedia.ts
|
|
401
|
+
var DEFAULT_BROWSER_MEDIA_PATH = "/api/voice/browser-media";
|
|
402
|
+
var DEFAULT_BROWSER_MEDIA_INTERVAL_MS = 5000;
|
|
403
|
+
var resolvePeerConnection = async (options) => options.peerConnection ?? await options.getPeerConnection?.() ?? null;
|
|
404
|
+
var postBrowserMediaReport = async (payload, options) => {
|
|
405
|
+
const requestFetch = options.fetch ?? globalThis.fetch;
|
|
406
|
+
if (!requestFetch) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
await requestFetch(options.path ?? DEFAULT_BROWSER_MEDIA_PATH, {
|
|
410
|
+
body: JSON.stringify(payload),
|
|
411
|
+
headers: {
|
|
412
|
+
"Content-Type": "application/json"
|
|
413
|
+
},
|
|
414
|
+
keepalive: true,
|
|
415
|
+
method: "POST"
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
var createVoiceBrowserMediaReporter = (options) => {
|
|
419
|
+
let interval = null;
|
|
420
|
+
let previousStats = [];
|
|
421
|
+
const reportOnce = async () => {
|
|
422
|
+
const peerConnection = await resolvePeerConnection(options);
|
|
423
|
+
if (!peerConnection) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const stats = await collectMediaWebRTCStats({ peerConnection });
|
|
427
|
+
const report = buildMediaWebRTCStatsReport({
|
|
428
|
+
...options,
|
|
429
|
+
stats
|
|
430
|
+
});
|
|
431
|
+
const continuity = options.continuity === false ? undefined : buildMediaWebRTCStreamContinuityReport({
|
|
432
|
+
...options.continuity,
|
|
433
|
+
previousStats,
|
|
434
|
+
stats
|
|
435
|
+
});
|
|
436
|
+
const payload = {
|
|
437
|
+
at: Date.now(),
|
|
438
|
+
continuity,
|
|
439
|
+
report,
|
|
440
|
+
scenarioId: options.getScenarioId?.() ?? null,
|
|
441
|
+
sessionId: options.getSessionId?.() ?? null
|
|
442
|
+
};
|
|
443
|
+
previousStats = stats;
|
|
444
|
+
options.onReport?.(payload);
|
|
445
|
+
await postBrowserMediaReport(payload, options);
|
|
446
|
+
return payload;
|
|
447
|
+
};
|
|
448
|
+
const run = () => {
|
|
449
|
+
reportOnce().catch((error) => {
|
|
450
|
+
options.onError?.(error);
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
const stop = () => {
|
|
454
|
+
if (interval) {
|
|
455
|
+
clearInterval(interval);
|
|
456
|
+
interval = null;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
return {
|
|
460
|
+
close: stop,
|
|
461
|
+
reportOnce,
|
|
462
|
+
start: () => {
|
|
463
|
+
if (interval) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
run();
|
|
467
|
+
interval = setInterval(run, options.intervalMs ?? DEFAULT_BROWSER_MEDIA_INTERVAL_MS);
|
|
468
|
+
},
|
|
469
|
+
stop
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
|
|
223
473
|
// src/client/connection.ts
|
|
224
474
|
var WS_OPEN = 1;
|
|
225
475
|
var WS_CLOSED = 3;
|
|
@@ -231,7 +481,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
|
231
481
|
var noop = () => {};
|
|
232
482
|
var noopUnsubscribe = () => noop;
|
|
233
483
|
var NOOP_CONNECTION = {
|
|
234
|
-
|
|
484
|
+
callControl: noop,
|
|
235
485
|
close: noop,
|
|
236
486
|
endTurn: noop,
|
|
237
487
|
getReadyState: () => WS_CLOSED,
|
|
@@ -239,6 +489,8 @@ var NOOP_CONNECTION = {
|
|
|
239
489
|
getSessionId: () => "",
|
|
240
490
|
send: noop,
|
|
241
491
|
sendAudio: noop,
|
|
492
|
+
simulateDisconnect: noop,
|
|
493
|
+
start: () => {},
|
|
242
494
|
subscribe: noopUnsubscribe
|
|
243
495
|
};
|
|
244
496
|
var createSessionId = () => crypto.randomUUID();
|
|
@@ -260,11 +512,14 @@ var isVoiceServerMessage = (value) => {
|
|
|
260
512
|
switch (value.type) {
|
|
261
513
|
case "audio":
|
|
262
514
|
case "assistant":
|
|
515
|
+
case "call_lifecycle":
|
|
263
516
|
case "complete":
|
|
517
|
+
case "connection":
|
|
264
518
|
case "error":
|
|
265
519
|
case "final":
|
|
266
520
|
case "partial":
|
|
267
521
|
case "pong":
|
|
522
|
+
case "replay":
|
|
268
523
|
case "session":
|
|
269
524
|
case "turn":
|
|
270
525
|
return true;
|
|
@@ -301,6 +556,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
301
556
|
sessionId: options.sessionId ?? createSessionId(),
|
|
302
557
|
ws: null
|
|
303
558
|
};
|
|
559
|
+
const emitConnection = (reconnect) => {
|
|
560
|
+
listeners.forEach((listener) => listener(reconnect));
|
|
561
|
+
};
|
|
304
562
|
const clearTimers = () => {
|
|
305
563
|
if (state.pingInterval) {
|
|
306
564
|
clearInterval(state.pingInterval);
|
|
@@ -323,9 +581,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
323
581
|
}
|
|
324
582
|
};
|
|
325
583
|
const scheduleReconnect = () => {
|
|
584
|
+
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
326
585
|
state.reconnectAttempts += 1;
|
|
586
|
+
emitConnection({
|
|
587
|
+
reconnect: {
|
|
588
|
+
attempts: state.reconnectAttempts,
|
|
589
|
+
lastDisconnectAt: Date.now(),
|
|
590
|
+
maxAttempts: maxReconnectAttempts,
|
|
591
|
+
nextAttemptAt,
|
|
592
|
+
status: "reconnecting"
|
|
593
|
+
},
|
|
594
|
+
type: "connection"
|
|
595
|
+
});
|
|
327
596
|
state.reconnectTimeout = setTimeout(() => {
|
|
328
597
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
598
|
+
emitConnection({
|
|
599
|
+
reconnect: {
|
|
600
|
+
attempts: state.reconnectAttempts,
|
|
601
|
+
maxAttempts: maxReconnectAttempts,
|
|
602
|
+
status: "exhausted"
|
|
603
|
+
},
|
|
604
|
+
type: "connection"
|
|
605
|
+
});
|
|
329
606
|
return;
|
|
330
607
|
}
|
|
331
608
|
connect();
|
|
@@ -335,9 +612,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
335
612
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
336
613
|
ws.binaryType = "arraybuffer";
|
|
337
614
|
ws.onopen = () => {
|
|
615
|
+
const wasReconnecting = state.reconnectAttempts > 0;
|
|
338
616
|
state.isConnected = true;
|
|
339
|
-
state.reconnectAttempts = 0;
|
|
340
617
|
flushPendingMessages();
|
|
618
|
+
if (wasReconnecting) {
|
|
619
|
+
emitConnection({
|
|
620
|
+
reconnect: {
|
|
621
|
+
attempts: state.reconnectAttempts,
|
|
622
|
+
lastResumedAt: Date.now(),
|
|
623
|
+
maxAttempts: maxReconnectAttempts,
|
|
624
|
+
status: "resumed"
|
|
625
|
+
},
|
|
626
|
+
type: "connection"
|
|
627
|
+
});
|
|
628
|
+
state.reconnectAttempts = 0;
|
|
629
|
+
}
|
|
341
630
|
listeners.forEach((listener) => listener({
|
|
342
631
|
scenarioId: state.scenarioId ?? undefined,
|
|
343
632
|
sessionId: state.sessionId,
|
|
@@ -367,6 +656,16 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
367
656
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
368
657
|
if (reconnectable) {
|
|
369
658
|
scheduleReconnect();
|
|
659
|
+
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
660
|
+
emitConnection({
|
|
661
|
+
reconnect: {
|
|
662
|
+
attempts: state.reconnectAttempts,
|
|
663
|
+
lastDisconnectAt: Date.now(),
|
|
664
|
+
maxAttempts: maxReconnectAttempts,
|
|
665
|
+
status: "exhausted"
|
|
666
|
+
},
|
|
667
|
+
type: "connection"
|
|
668
|
+
});
|
|
370
669
|
}
|
|
371
670
|
};
|
|
372
671
|
state.ws = ws;
|
|
@@ -400,6 +699,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
400
699
|
const endTurn = () => {
|
|
401
700
|
send({ type: "end_turn" });
|
|
402
701
|
};
|
|
702
|
+
const callControl = (message) => {
|
|
703
|
+
send({
|
|
704
|
+
...message,
|
|
705
|
+
type: "call_control"
|
|
706
|
+
});
|
|
707
|
+
};
|
|
403
708
|
const close = () => {
|
|
404
709
|
clearTimers();
|
|
405
710
|
if (state.ws) {
|
|
@@ -409,6 +714,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
409
714
|
state.isConnected = false;
|
|
410
715
|
listeners.clear();
|
|
411
716
|
};
|
|
717
|
+
const simulateDisconnect = () => {
|
|
718
|
+
if (state.ws?.readyState === WS_OPEN) {
|
|
719
|
+
state.ws.close(4000, "absolutejs-voice-reconnect-proof");
|
|
720
|
+
}
|
|
721
|
+
};
|
|
412
722
|
const subscribe = (callback) => {
|
|
413
723
|
listeners.add(callback);
|
|
414
724
|
return () => {
|
|
@@ -417,7 +727,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
417
727
|
};
|
|
418
728
|
connect();
|
|
419
729
|
return {
|
|
420
|
-
|
|
730
|
+
callControl,
|
|
421
731
|
close,
|
|
422
732
|
endTurn,
|
|
423
733
|
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
@@ -425,18 +735,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
425
735
|
getSessionId: () => state.sessionId,
|
|
426
736
|
send,
|
|
427
737
|
sendAudio,
|
|
738
|
+
simulateDisconnect,
|
|
739
|
+
start,
|
|
428
740
|
subscribe
|
|
429
741
|
};
|
|
430
742
|
};
|
|
431
743
|
|
|
432
744
|
// src/client/store.ts
|
|
745
|
+
var createInitialReconnectState = () => ({
|
|
746
|
+
attempts: 0,
|
|
747
|
+
maxAttempts: 0,
|
|
748
|
+
status: "idle"
|
|
749
|
+
});
|
|
433
750
|
var createInitialState = () => ({
|
|
434
751
|
assistantAudio: [],
|
|
435
752
|
assistantTexts: [],
|
|
753
|
+
call: null,
|
|
436
754
|
error: null,
|
|
437
755
|
isConnected: false,
|
|
756
|
+
sessionMetadata: null,
|
|
438
757
|
scenarioId: null,
|
|
439
758
|
partial: "",
|
|
759
|
+
reconnect: createInitialReconnectState(),
|
|
440
760
|
sessionId: null,
|
|
441
761
|
status: "idle",
|
|
442
762
|
turns: []
|
|
@@ -476,10 +796,36 @@ var createVoiceStreamStore = () => {
|
|
|
476
796
|
status: "completed"
|
|
477
797
|
};
|
|
478
798
|
break;
|
|
799
|
+
case "call_lifecycle":
|
|
800
|
+
state = {
|
|
801
|
+
...state,
|
|
802
|
+
call: {
|
|
803
|
+
...state.call,
|
|
804
|
+
disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
|
|
805
|
+
endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
|
|
806
|
+
events: [...state.call?.events ?? [], action.event],
|
|
807
|
+
lastEventAt: action.event.at,
|
|
808
|
+
startedAt: state.call?.startedAt ?? action.event.at
|
|
809
|
+
},
|
|
810
|
+
sessionId: action.sessionId
|
|
811
|
+
};
|
|
812
|
+
break;
|
|
479
813
|
case "connected":
|
|
480
814
|
state = {
|
|
481
815
|
...state,
|
|
482
|
-
isConnected: true
|
|
816
|
+
isConnected: true,
|
|
817
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
818
|
+
...state.reconnect,
|
|
819
|
+
lastResumedAt: Date.now(),
|
|
820
|
+
nextAttemptAt: undefined,
|
|
821
|
+
status: "resumed"
|
|
822
|
+
} : state.reconnect
|
|
823
|
+
};
|
|
824
|
+
break;
|
|
825
|
+
case "connection":
|
|
826
|
+
state = {
|
|
827
|
+
...state,
|
|
828
|
+
reconnect: action.reconnect
|
|
483
829
|
};
|
|
484
830
|
break;
|
|
485
831
|
case "disconnected":
|
|
@@ -507,6 +853,27 @@ var createVoiceStreamStore = () => {
|
|
|
507
853
|
partial: action.transcript.text
|
|
508
854
|
};
|
|
509
855
|
break;
|
|
856
|
+
case "replay":
|
|
857
|
+
state = {
|
|
858
|
+
...state,
|
|
859
|
+
assistantTexts: [...action.assistantTexts],
|
|
860
|
+
call: action.call ?? null,
|
|
861
|
+
error: null,
|
|
862
|
+
isConnected: action.status === "active",
|
|
863
|
+
partial: action.partial,
|
|
864
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
865
|
+
...state.reconnect,
|
|
866
|
+
lastResumedAt: Date.now(),
|
|
867
|
+
nextAttemptAt: undefined,
|
|
868
|
+
status: "resumed"
|
|
869
|
+
} : state.reconnect,
|
|
870
|
+
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
871
|
+
sessionId: action.sessionId,
|
|
872
|
+
sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
|
|
873
|
+
status: action.status,
|
|
874
|
+
turns: [...action.turns]
|
|
875
|
+
};
|
|
876
|
+
break;
|
|
510
877
|
case "session":
|
|
511
878
|
state = {
|
|
512
879
|
...state,
|
|
@@ -514,6 +881,7 @@ var createVoiceStreamStore = () => {
|
|
|
514
881
|
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
515
882
|
isConnected: action.status === "active",
|
|
516
883
|
sessionId: action.sessionId,
|
|
884
|
+
sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
|
|
517
885
|
status: action.status
|
|
518
886
|
};
|
|
519
887
|
break;
|
|
@@ -544,26 +912,60 @@ var createVoiceStreamStore = () => {
|
|
|
544
912
|
var createVoiceStream = (path, options = {}) => {
|
|
545
913
|
const connection = createVoiceConnection(path, options);
|
|
546
914
|
const store = createVoiceStreamStore();
|
|
915
|
+
const browserMediaReporter = options.browserMedia && typeof window !== "undefined" ? createVoiceBrowserMediaReporter({
|
|
916
|
+
...options.browserMedia,
|
|
917
|
+
getScenarioId: () => options.browserMedia ? options.browserMedia.getScenarioId?.() ?? connection.getScenarioId() : connection.getScenarioId(),
|
|
918
|
+
getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
|
|
919
|
+
}) : null;
|
|
547
920
|
const subscribers = new Set;
|
|
548
921
|
const start = (input) => Promise.resolve().then(() => {
|
|
549
922
|
if (!input?.sessionId && !input?.scenarioId) {
|
|
550
923
|
return;
|
|
551
924
|
}
|
|
552
925
|
connection.start(input);
|
|
926
|
+
browserMediaReporter?.start();
|
|
553
927
|
});
|
|
554
928
|
const notify = () => {
|
|
555
929
|
subscribers.forEach((subscriber) => subscriber());
|
|
556
930
|
};
|
|
931
|
+
const reportReconnect = () => {
|
|
932
|
+
if (!options.reconnectReportPath || typeof fetch === "undefined") {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const snapshot = store.getSnapshot();
|
|
936
|
+
const body = JSON.stringify({
|
|
937
|
+
at: Date.now(),
|
|
938
|
+
reconnect: snapshot.reconnect,
|
|
939
|
+
scenarioId: snapshot.scenarioId,
|
|
940
|
+
sessionId: connection.getSessionId(),
|
|
941
|
+
turnIds: snapshot.turns.map((turn) => turn.id)
|
|
942
|
+
});
|
|
943
|
+
fetch(options.reconnectReportPath, {
|
|
944
|
+
body,
|
|
945
|
+
headers: {
|
|
946
|
+
"Content-Type": "application/json"
|
|
947
|
+
},
|
|
948
|
+
keepalive: true,
|
|
949
|
+
method: "POST"
|
|
950
|
+
}).catch(() => {});
|
|
951
|
+
};
|
|
557
952
|
const unsubscribeConnection = connection.subscribe((message) => {
|
|
558
953
|
const action = serverMessageToAction(message);
|
|
559
954
|
if (action) {
|
|
560
955
|
store.dispatch(action);
|
|
956
|
+
if (message.type === "connection") {
|
|
957
|
+
reportReconnect();
|
|
958
|
+
}
|
|
561
959
|
notify();
|
|
562
960
|
}
|
|
563
961
|
});
|
|
564
962
|
return {
|
|
963
|
+
callControl(message) {
|
|
964
|
+
connection.callControl(message);
|
|
965
|
+
},
|
|
565
966
|
close() {
|
|
566
967
|
unsubscribeConnection();
|
|
968
|
+
browserMediaReporter?.close();
|
|
567
969
|
connection.close();
|
|
568
970
|
store.dispatch({ type: "disconnected" });
|
|
569
971
|
notify();
|
|
@@ -586,10 +988,16 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
586
988
|
get scenarioId() {
|
|
587
989
|
return store.getSnapshot().scenarioId;
|
|
588
990
|
},
|
|
991
|
+
get sessionMetadata() {
|
|
992
|
+
return store.getSnapshot().sessionMetadata;
|
|
993
|
+
},
|
|
589
994
|
start,
|
|
590
995
|
get partial() {
|
|
591
996
|
return store.getSnapshot().partial;
|
|
592
997
|
},
|
|
998
|
+
get reconnect() {
|
|
999
|
+
return store.getSnapshot().reconnect;
|
|
1000
|
+
},
|
|
593
1001
|
get sessionId() {
|
|
594
1002
|
return connection.getSessionId();
|
|
595
1003
|
},
|
|
@@ -605,9 +1013,15 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
605
1013
|
get assistantAudio() {
|
|
606
1014
|
return store.getSnapshot().assistantAudio;
|
|
607
1015
|
},
|
|
1016
|
+
get call() {
|
|
1017
|
+
return store.getSnapshot().call;
|
|
1018
|
+
},
|
|
608
1019
|
sendAudio(audio) {
|
|
609
1020
|
connection.sendAudio(audio);
|
|
610
1021
|
},
|
|
1022
|
+
simulateDisconnect() {
|
|
1023
|
+
connection.simulateDisconnect();
|
|
1024
|
+
},
|
|
611
1025
|
subscribe(subscriber) {
|
|
612
1026
|
subscribers.add(subscriber);
|
|
613
1027
|
return () => {
|
|
@@ -900,12 +1314,15 @@ var resolveVoiceRuntimePreset = (name = "default") => {
|
|
|
900
1314
|
var createInitialState2 = (stream) => ({
|
|
901
1315
|
assistantAudio: [...stream.assistantAudio],
|
|
902
1316
|
assistantTexts: [...stream.assistantTexts],
|
|
1317
|
+
call: stream.call,
|
|
903
1318
|
error: stream.error,
|
|
904
1319
|
isConnected: stream.isConnected,
|
|
905
1320
|
isRecording: false,
|
|
906
1321
|
partial: stream.partial,
|
|
1322
|
+
reconnect: stream.reconnect,
|
|
907
1323
|
recordingError: null,
|
|
908
1324
|
sessionId: stream.sessionId,
|
|
1325
|
+
sessionMetadata: stream.sessionMetadata,
|
|
909
1326
|
scenarioId: stream.scenarioId,
|
|
910
1327
|
status: stream.status,
|
|
911
1328
|
turns: [...stream.turns]
|
|
@@ -929,10 +1346,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
929
1346
|
...state,
|
|
930
1347
|
assistantAudio: [...stream.assistantAudio],
|
|
931
1348
|
assistantTexts: [...stream.assistantTexts],
|
|
1349
|
+
call: stream.call,
|
|
932
1350
|
error: stream.error,
|
|
933
1351
|
isConnected: stream.isConnected,
|
|
934
1352
|
partial: stream.partial,
|
|
1353
|
+
reconnect: stream.reconnect,
|
|
935
1354
|
sessionId: stream.sessionId,
|
|
1355
|
+
sessionMetadata: stream.sessionMetadata,
|
|
936
1356
|
scenarioId: stream.scenarioId,
|
|
937
1357
|
status: stream.status,
|
|
938
1358
|
turns: [...stream.turns]
|
|
@@ -956,7 +1376,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
956
1376
|
capture = createMicrophoneCapture({
|
|
957
1377
|
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
958
1378
|
onLevel: options.capture?.onLevel,
|
|
959
|
-
onAudio: (audio) =>
|
|
1379
|
+
onAudio: (audio) => {
|
|
1380
|
+
if (options.capture?.onAudio) {
|
|
1381
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
stream.sendAudio(audio);
|
|
1385
|
+
},
|
|
960
1386
|
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
961
1387
|
});
|
|
962
1388
|
return capture;
|
|
@@ -1006,6 +1432,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1006
1432
|
bindHTMX(bindingOptions) {
|
|
1007
1433
|
return bindVoiceHTMX(stream, bindingOptions);
|
|
1008
1434
|
},
|
|
1435
|
+
callControl: (message) => stream.callControl(message),
|
|
1009
1436
|
close,
|
|
1010
1437
|
endTurn: () => stream.endTurn(),
|
|
1011
1438
|
get error() {
|
|
@@ -1025,10 +1452,17 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1025
1452
|
get recordingError() {
|
|
1026
1453
|
return state.recordingError;
|
|
1027
1454
|
},
|
|
1455
|
+
get reconnect() {
|
|
1456
|
+
return state.reconnect;
|
|
1457
|
+
},
|
|
1028
1458
|
sendAudio: (audio) => stream.sendAudio(audio),
|
|
1459
|
+
simulateDisconnect: () => stream.simulateDisconnect(),
|
|
1029
1460
|
get sessionId() {
|
|
1030
1461
|
return state.sessionId;
|
|
1031
1462
|
},
|
|
1463
|
+
get sessionMetadata() {
|
|
1464
|
+
return state.sessionMetadata;
|
|
1465
|
+
},
|
|
1032
1466
|
get scenarioId() {
|
|
1033
1467
|
return state.scenarioId;
|
|
1034
1468
|
},
|
|
@@ -1058,6 +1492,478 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1058
1492
|
},
|
|
1059
1493
|
get assistantAudio() {
|
|
1060
1494
|
return state.assistantAudio;
|
|
1495
|
+
},
|
|
1496
|
+
get call() {
|
|
1497
|
+
return state.call;
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
// src/client/audioPlayer.ts
|
|
1503
|
+
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1504
|
+
var createInitialState3 = () => ({
|
|
1505
|
+
activeSourceCount: 0,
|
|
1506
|
+
error: null,
|
|
1507
|
+
isActive: false,
|
|
1508
|
+
isPlaying: false,
|
|
1509
|
+
lastInterruptLatencyMs: undefined,
|
|
1510
|
+
lastPlaybackStopLatencyMs: undefined,
|
|
1511
|
+
processedChunkCount: 0,
|
|
1512
|
+
queuedChunkCount: 0
|
|
1513
|
+
});
|
|
1514
|
+
var getAudioContextCtor = () => {
|
|
1515
|
+
if (typeof window === "undefined") {
|
|
1516
|
+
return typeof AudioContext === "undefined" ? undefined : AudioContext;
|
|
1517
|
+
}
|
|
1518
|
+
return window.AudioContext ?? window.webkitAudioContext;
|
|
1519
|
+
};
|
|
1520
|
+
var decodePCM16LEChunk = (audioContext, chunk) => {
|
|
1521
|
+
const format = chunk.format;
|
|
1522
|
+
if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
|
|
1523
|
+
throw new Error(`Unsupported assistant audio format: ${format.container}/${format.encoding}`);
|
|
1524
|
+
}
|
|
1525
|
+
const bytes = chunk.chunk;
|
|
1526
|
+
const channels = Math.max(1, format.channels);
|
|
1527
|
+
const sampleCount = Math.floor(bytes.byteLength / 2);
|
|
1528
|
+
const frameCount = Math.max(1, Math.floor(sampleCount / channels));
|
|
1529
|
+
const audioBuffer = audioContext.createBuffer(channels, frameCount, format.sampleRateHz);
|
|
1530
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1531
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1532
|
+
const channelData = audioBuffer.getChannelData(channelIndex);
|
|
1533
|
+
for (let frameIndex = 0;frameIndex < frameCount; frameIndex += 1) {
|
|
1534
|
+
const sampleIndex = frameIndex * channels + channelIndex;
|
|
1535
|
+
const sampleOffset = sampleIndex * 2;
|
|
1536
|
+
if (sampleOffset + 1 >= bytes.byteLength) {
|
|
1537
|
+
channelData[frameIndex] = 0;
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
channelData[frameIndex] = view.getInt16(sampleOffset, true) / 32768;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return audioBuffer;
|
|
1544
|
+
};
|
|
1545
|
+
var createVoiceAudioPlayer = (source, options = {}) => {
|
|
1546
|
+
const subscribers = new Set;
|
|
1547
|
+
const sourceNodes = new Set;
|
|
1548
|
+
const lookaheadSeconds = (options.lookaheadMs ?? DEFAULT_LOOKAHEAD_MS) / 1000;
|
|
1549
|
+
let state = createInitialState3();
|
|
1550
|
+
let audioContext = null;
|
|
1551
|
+
let outputNode = null;
|
|
1552
|
+
let queueEndTime = 0;
|
|
1553
|
+
let syncPromise = Promise.resolve();
|
|
1554
|
+
let interruptStartedAt = null;
|
|
1555
|
+
let interruptPromise = null;
|
|
1556
|
+
let resolveInterruptPromise = null;
|
|
1557
|
+
let interruptFallbackTimer = null;
|
|
1558
|
+
const notify = () => {
|
|
1559
|
+
for (const subscriber of subscribers) {
|
|
1560
|
+
subscriber();
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
const setState = (next) => {
|
|
1564
|
+
state = {
|
|
1565
|
+
...state,
|
|
1566
|
+
...next
|
|
1567
|
+
};
|
|
1568
|
+
notify();
|
|
1569
|
+
};
|
|
1570
|
+
const clearError = () => {
|
|
1571
|
+
if (state.error !== null) {
|
|
1572
|
+
setState({ error: null });
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
const clearInterruptTimer = () => {
|
|
1576
|
+
if (interruptFallbackTimer !== null) {
|
|
1577
|
+
clearTimeout(interruptFallbackTimer);
|
|
1578
|
+
interruptFallbackTimer = null;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
const resolveInterrupt = (latencyMs) => {
|
|
1582
|
+
clearInterruptTimer();
|
|
1583
|
+
interruptStartedAt = null;
|
|
1584
|
+
setState({
|
|
1585
|
+
activeSourceCount: sourceNodes.size,
|
|
1586
|
+
isPlaying: false,
|
|
1587
|
+
lastInterruptLatencyMs: latencyMs,
|
|
1588
|
+
lastPlaybackStopLatencyMs: state.lastPlaybackStopLatencyMs ?? latencyMs
|
|
1589
|
+
});
|
|
1590
|
+
resolveInterruptPromise?.();
|
|
1591
|
+
resolveInterruptPromise = null;
|
|
1592
|
+
interruptPromise = null;
|
|
1593
|
+
};
|
|
1594
|
+
const estimateOutputStopLatencyMs = (context) => {
|
|
1595
|
+
if (!context) {
|
|
1596
|
+
return 0;
|
|
1597
|
+
}
|
|
1598
|
+
return Math.max(0, ((context.baseLatency ?? 0) + (context.outputLatency ?? 0)) * 1000);
|
|
1599
|
+
};
|
|
1600
|
+
const restoreOutputGain = (context) => {
|
|
1601
|
+
if (!outputNode) {
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
const gainValue = 1;
|
|
1605
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1606
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
outputNode.gain.value = gainValue;
|
|
1610
|
+
};
|
|
1611
|
+
const muteOutputGain = (context) => {
|
|
1612
|
+
if (!outputNode) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
const gainValue = 0;
|
|
1616
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1617
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
outputNode.gain.value = gainValue;
|
|
1621
|
+
};
|
|
1622
|
+
const maybeResolveInterrupt = () => {
|
|
1623
|
+
if (interruptStartedAt === null || sourceNodes.size > 0) {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
resolveInterrupt(Date.now() - interruptStartedAt);
|
|
1627
|
+
};
|
|
1628
|
+
const ensureAudioContext = async () => {
|
|
1629
|
+
if (audioContext) {
|
|
1630
|
+
return audioContext;
|
|
1631
|
+
}
|
|
1632
|
+
if (options.createAudioContext) {
|
|
1633
|
+
audioContext = options.createAudioContext();
|
|
1634
|
+
} else {
|
|
1635
|
+
const AudioContextCtor = getAudioContextCtor();
|
|
1636
|
+
if (!AudioContextCtor) {
|
|
1637
|
+
throw new Error("Assistant audio playback requires AudioContext support.");
|
|
1638
|
+
}
|
|
1639
|
+
audioContext = new AudioContextCtor;
|
|
1640
|
+
}
|
|
1641
|
+
if (audioContext.createGain) {
|
|
1642
|
+
outputNode = audioContext.createGain();
|
|
1643
|
+
outputNode.connect?.(audioContext.destination);
|
|
1644
|
+
}
|
|
1645
|
+
queueEndTime = audioContext.currentTime;
|
|
1646
|
+
return audioContext;
|
|
1647
|
+
};
|
|
1648
|
+
const scheduleChunk = async (chunk) => {
|
|
1649
|
+
const context = await ensureAudioContext();
|
|
1650
|
+
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1651
|
+
const node = context.createBufferSource();
|
|
1652
|
+
node.buffer = buffer;
|
|
1653
|
+
node.connect(outputNode ?? context.destination);
|
|
1654
|
+
node.onended = () => {
|
|
1655
|
+
sourceNodes.delete(node);
|
|
1656
|
+
node.disconnect?.();
|
|
1657
|
+
setState({
|
|
1658
|
+
activeSourceCount: sourceNodes.size,
|
|
1659
|
+
isPlaying: sourceNodes.size > 0 && state.isActive
|
|
1660
|
+
});
|
|
1661
|
+
maybeResolveInterrupt();
|
|
1662
|
+
};
|
|
1663
|
+
const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
|
|
1664
|
+
queueEndTime = startAt + buffer.duration;
|
|
1665
|
+
sourceNodes.add(node);
|
|
1666
|
+
setState({
|
|
1667
|
+
activeSourceCount: sourceNodes.size,
|
|
1668
|
+
isPlaying: true
|
|
1669
|
+
});
|
|
1670
|
+
node.start(startAt);
|
|
1671
|
+
};
|
|
1672
|
+
const stopQueuedPlayback = (options2) => {
|
|
1673
|
+
for (const node of [...sourceNodes]) {
|
|
1674
|
+
node.stop?.();
|
|
1675
|
+
}
|
|
1676
|
+
queueEndTime = audioContext ? audioContext.currentTime : 0;
|
|
1677
|
+
if (options2?.forceClear) {
|
|
1678
|
+
for (const node of sourceNodes) {
|
|
1679
|
+
node.disconnect?.();
|
|
1680
|
+
}
|
|
1681
|
+
sourceNodes.clear();
|
|
1682
|
+
maybeResolveInterrupt();
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
const sync = async () => {
|
|
1686
|
+
if (!state.isActive) {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
const nextChunks = source.assistantAudio.slice(state.processedChunkCount);
|
|
1690
|
+
if (nextChunks.length === 0) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
try {
|
|
1694
|
+
clearError();
|
|
1695
|
+
for (const chunk of nextChunks) {
|
|
1696
|
+
await scheduleChunk(chunk);
|
|
1697
|
+
}
|
|
1698
|
+
setState({
|
|
1699
|
+
processedChunkCount: source.assistantAudio.length,
|
|
1700
|
+
queuedChunkCount: state.queuedChunkCount + nextChunks.length
|
|
1701
|
+
});
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
setState({
|
|
1704
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
const queueSync = () => {
|
|
1709
|
+
syncPromise = syncPromise.then(() => sync(), () => sync());
|
|
1710
|
+
return syncPromise;
|
|
1711
|
+
};
|
|
1712
|
+
const unsubscribeSource = source.subscribe(() => {
|
|
1713
|
+
if (options.autoStart && !state.isActive && source.assistantAudio.length > 0) {
|
|
1714
|
+
player.start();
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (state.isActive) {
|
|
1718
|
+
queueSync();
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
const player = {
|
|
1722
|
+
close: async () => {
|
|
1723
|
+
unsubscribeSource();
|
|
1724
|
+
stopQueuedPlayback({ forceClear: true });
|
|
1725
|
+
clearInterruptTimer();
|
|
1726
|
+
resolveInterruptPromise?.();
|
|
1727
|
+
resolveInterruptPromise = null;
|
|
1728
|
+
interruptPromise = null;
|
|
1729
|
+
interruptStartedAt = null;
|
|
1730
|
+
if (audioContext && audioContext.state !== "closed") {
|
|
1731
|
+
await audioContext.close();
|
|
1732
|
+
}
|
|
1733
|
+
audioContext = null;
|
|
1734
|
+
outputNode?.disconnect?.();
|
|
1735
|
+
outputNode = null;
|
|
1736
|
+
queueEndTime = 0;
|
|
1737
|
+
setState({
|
|
1738
|
+
activeSourceCount: 0,
|
|
1739
|
+
isActive: false,
|
|
1740
|
+
isPlaying: false
|
|
1741
|
+
});
|
|
1742
|
+
},
|
|
1743
|
+
get activeSourceCount() {
|
|
1744
|
+
return state.activeSourceCount;
|
|
1745
|
+
},
|
|
1746
|
+
get error() {
|
|
1747
|
+
return state.error;
|
|
1748
|
+
},
|
|
1749
|
+
getSnapshot: () => state,
|
|
1750
|
+
get isActive() {
|
|
1751
|
+
return state.isActive;
|
|
1752
|
+
},
|
|
1753
|
+
get isPlaying() {
|
|
1754
|
+
return state.isPlaying;
|
|
1755
|
+
},
|
|
1756
|
+
interrupt: async () => {
|
|
1757
|
+
const startedAt = Date.now();
|
|
1758
|
+
const context = await ensureAudioContext();
|
|
1759
|
+
interruptStartedAt = startedAt;
|
|
1760
|
+
muteOutputGain(context);
|
|
1761
|
+
const playbackStopLatencyMs = Date.now() - startedAt + estimateOutputStopLatencyMs(context);
|
|
1762
|
+
setState({
|
|
1763
|
+
isActive: false,
|
|
1764
|
+
isPlaying: sourceNodes.size > 0,
|
|
1765
|
+
lastPlaybackStopLatencyMs: playbackStopLatencyMs
|
|
1766
|
+
});
|
|
1767
|
+
if (sourceNodes.size === 0) {
|
|
1768
|
+
resolveInterrupt(playbackStopLatencyMs);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
if (!interruptPromise) {
|
|
1772
|
+
interruptPromise = new Promise((resolve) => {
|
|
1773
|
+
resolveInterruptPromise = resolve;
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
clearInterruptTimer();
|
|
1777
|
+
interruptFallbackTimer = setTimeout(() => {
|
|
1778
|
+
for (const node of sourceNodes) {
|
|
1779
|
+
node.disconnect?.();
|
|
1780
|
+
}
|
|
1781
|
+
sourceNodes.clear();
|
|
1782
|
+
resolveInterrupt(Date.now() - startedAt);
|
|
1783
|
+
}, 250);
|
|
1784
|
+
stopQueuedPlayback();
|
|
1785
|
+
await interruptPromise;
|
|
1786
|
+
},
|
|
1787
|
+
get lastInterruptLatencyMs() {
|
|
1788
|
+
return state.lastInterruptLatencyMs;
|
|
1789
|
+
},
|
|
1790
|
+
get lastPlaybackStopLatencyMs() {
|
|
1791
|
+
return state.lastPlaybackStopLatencyMs;
|
|
1792
|
+
},
|
|
1793
|
+
pause: async () => {
|
|
1794
|
+
if (!audioContext) {
|
|
1795
|
+
setState({
|
|
1796
|
+
activeSourceCount: 0,
|
|
1797
|
+
isActive: false,
|
|
1798
|
+
isPlaying: false
|
|
1799
|
+
});
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
await audioContext.suspend();
|
|
1803
|
+
setState({
|
|
1804
|
+
activeSourceCount: sourceNodes.size,
|
|
1805
|
+
isActive: false,
|
|
1806
|
+
isPlaying: false
|
|
1807
|
+
});
|
|
1808
|
+
},
|
|
1809
|
+
get processedChunkCount() {
|
|
1810
|
+
return state.processedChunkCount;
|
|
1811
|
+
},
|
|
1812
|
+
get queuedChunkCount() {
|
|
1813
|
+
return state.queuedChunkCount;
|
|
1814
|
+
},
|
|
1815
|
+
start: async () => {
|
|
1816
|
+
try {
|
|
1817
|
+
clearError();
|
|
1818
|
+
const context = await ensureAudioContext();
|
|
1819
|
+
restoreOutputGain(context);
|
|
1820
|
+
if (context.state === "suspended") {
|
|
1821
|
+
await context.resume();
|
|
1822
|
+
}
|
|
1823
|
+
setState({
|
|
1824
|
+
activeSourceCount: sourceNodes.size,
|
|
1825
|
+
isActive: true,
|
|
1826
|
+
isPlaying: context.state === "running"
|
|
1827
|
+
});
|
|
1828
|
+
await queueSync();
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
setState({
|
|
1831
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1832
|
+
isActive: false,
|
|
1833
|
+
isPlaying: false
|
|
1834
|
+
});
|
|
1835
|
+
throw error;
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
subscribe: (subscriber) => {
|
|
1839
|
+
subscribers.add(subscriber);
|
|
1840
|
+
return () => {
|
|
1841
|
+
subscribers.delete(subscriber);
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
return player;
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
// src/client/bargeInMonitor.ts
|
|
1849
|
+
var DEFAULT_THRESHOLD_MS = 250;
|
|
1850
|
+
var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
1851
|
+
var summarize = (events, thresholdMs) => {
|
|
1852
|
+
const stopped = events.filter((event) => event.status === "stopped");
|
|
1853
|
+
const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
1854
|
+
const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
1855
|
+
const passed = stopped.length - failed;
|
|
1856
|
+
return {
|
|
1857
|
+
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
1858
|
+
events: [...events],
|
|
1859
|
+
failed,
|
|
1860
|
+
lastEvent: events.at(-1),
|
|
1861
|
+
passed,
|
|
1862
|
+
status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
|
|
1863
|
+
thresholdMs,
|
|
1864
|
+
total: stopped.length
|
|
1865
|
+
};
|
|
1866
|
+
};
|
|
1867
|
+
var createVoiceBargeInMonitor = (options = {}) => {
|
|
1868
|
+
const listeners = new Set;
|
|
1869
|
+
const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
|
|
1870
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1871
|
+
const events = [];
|
|
1872
|
+
const emit = () => {
|
|
1873
|
+
for (const listener of listeners) {
|
|
1874
|
+
listener();
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
const postEvent = (event) => {
|
|
1878
|
+
if (!options.path || typeof fetchImpl !== "function") {
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
fetchImpl(options.path, {
|
|
1882
|
+
body: JSON.stringify(event),
|
|
1883
|
+
headers: {
|
|
1884
|
+
"Content-Type": "application/json"
|
|
1885
|
+
},
|
|
1886
|
+
method: "POST"
|
|
1887
|
+
}).catch(() => {});
|
|
1888
|
+
};
|
|
1889
|
+
const record = (status, input) => {
|
|
1890
|
+
const event = {
|
|
1891
|
+
at: Date.now(),
|
|
1892
|
+
id: createEventId(),
|
|
1893
|
+
latencyMs: input.latencyMs,
|
|
1894
|
+
playbackStopLatencyMs: input.playbackStopLatencyMs,
|
|
1895
|
+
reason: input.reason,
|
|
1896
|
+
sessionId: input.sessionId,
|
|
1897
|
+
status,
|
|
1898
|
+
thresholdMs
|
|
1899
|
+
};
|
|
1900
|
+
events.push(event);
|
|
1901
|
+
postEvent(event);
|
|
1902
|
+
emit();
|
|
1903
|
+
return event;
|
|
1904
|
+
};
|
|
1905
|
+
return {
|
|
1906
|
+
getSnapshot: () => summarize(events, thresholdMs),
|
|
1907
|
+
recordRequested: (input) => record("requested", input),
|
|
1908
|
+
recordSkipped: (input) => record("skipped", input),
|
|
1909
|
+
recordStopped: (input) => record("stopped", input),
|
|
1910
|
+
subscribe: (subscriber) => {
|
|
1911
|
+
listeners.add(subscriber);
|
|
1912
|
+
return () => {
|
|
1913
|
+
listeners.delete(subscriber);
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
// src/client/duplex.ts
|
|
1920
|
+
var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
|
|
1921
|
+
var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
|
|
1922
|
+
var bindVoiceBargeIn = (controller, player, options = {}) => {
|
|
1923
|
+
let lastPartial = controller.partial;
|
|
1924
|
+
const interruptIfPlaying = (reason) => {
|
|
1925
|
+
if (!player.isPlaying || options.enabled === false) {
|
|
1926
|
+
options.monitor?.recordSkipped({
|
|
1927
|
+
reason,
|
|
1928
|
+
sessionId: controller.sessionId
|
|
1929
|
+
});
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
options.monitor?.recordRequested({
|
|
1933
|
+
reason,
|
|
1934
|
+
sessionId: controller.sessionId
|
|
1935
|
+
});
|
|
1936
|
+
player.interrupt().then(() => {
|
|
1937
|
+
options.monitor?.recordStopped({
|
|
1938
|
+
latencyMs: player.lastInterruptLatencyMs,
|
|
1939
|
+
playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
|
|
1940
|
+
reason,
|
|
1941
|
+
sessionId: controller.sessionId
|
|
1942
|
+
});
|
|
1943
|
+
});
|
|
1944
|
+
};
|
|
1945
|
+
const unsubscribe = controller.subscribe(() => {
|
|
1946
|
+
if (options.interruptOnPartial === false) {
|
|
1947
|
+
lastPartial = controller.partial;
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
if (!lastPartial && controller.partial) {
|
|
1951
|
+
interruptIfPlaying("partial-transcript");
|
|
1952
|
+
}
|
|
1953
|
+
lastPartial = controller.partial;
|
|
1954
|
+
});
|
|
1955
|
+
return {
|
|
1956
|
+
close: () => {
|
|
1957
|
+
unsubscribe();
|
|
1958
|
+
},
|
|
1959
|
+
handleLevel: (level) => {
|
|
1960
|
+
if (shouldInterruptForLevel(level, options)) {
|
|
1961
|
+
interruptIfPlaying("input-level");
|
|
1962
|
+
}
|
|
1963
|
+
},
|
|
1964
|
+
sendAudio: (audio) => {
|
|
1965
|
+
interruptIfPlaying("manual-audio");
|
|
1966
|
+
controller.sendAudio(audio);
|
|
1061
1967
|
}
|
|
1062
1968
|
};
|
|
1063
1969
|
};
|
|
@@ -1084,7 +1990,7 @@ var DEFAULT_GUIDED_PROMPTS = [
|
|
|
1084
1990
|
"Now describe what you are trying to do or test.",
|
|
1085
1991
|
"Finish with any detail that feels blocked, risky, or unclear."
|
|
1086
1992
|
];
|
|
1087
|
-
var clamp = (value, min,
|
|
1993
|
+
var clamp = (value, min, max2) => Math.min(max2, Math.max(min, value));
|
|
1088
1994
|
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1089
1995
|
var readErrorField = (value, key) => {
|
|
1090
1996
|
const candidate = value[key];
|
|
@@ -1118,6 +2024,17 @@ var formatErrorMessage = (error) => {
|
|
|
1118
2024
|
}
|
|
1119
2025
|
return "Unexpected error";
|
|
1120
2026
|
};
|
|
2027
|
+
var formatReconnectState = (reconnect) => {
|
|
2028
|
+
const pieces = [reconnect.status];
|
|
2029
|
+
if (reconnect.attempts > 0 || reconnect.maxAttempts > 0) {
|
|
2030
|
+
pieces.push(`${reconnect.attempts}/${reconnect.maxAttempts} attempts`);
|
|
2031
|
+
}
|
|
2032
|
+
if (reconnect.nextAttemptAt) {
|
|
2033
|
+
const waitMs = Math.max(0, reconnect.nextAttemptAt - Date.now());
|
|
2034
|
+
pieces.push(`retry in ${Math.ceil(waitMs / 100) / 10}s`);
|
|
2035
|
+
}
|
|
2036
|
+
return pieces.join(" · ");
|
|
2037
|
+
};
|
|
1121
2038
|
var createInitialVoiceWaveLevels = (count = VOICE_WAVE_POINTS) => Array.from({ length: count }, () => 0);
|
|
1122
2039
|
var pushVoiceWaveLevel = (levels, nextLevel, count = VOICE_WAVE_POINTS) => {
|
|
1123
2040
|
const next = levels.slice(-(count - 1));
|
|
@@ -1174,6 +2091,20 @@ var parsePromptList = (value) => {
|
|
|
1174
2091
|
} catch {}
|
|
1175
2092
|
return DEFAULT_GUIDED_PROMPTS;
|
|
1176
2093
|
};
|
|
2094
|
+
var parseOptionalNumber = (value) => {
|
|
2095
|
+
if (!value) {
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const parsed = Number(value);
|
|
2099
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
2100
|
+
};
|
|
2101
|
+
var resolveElement2 = (root, selector, ctor) => {
|
|
2102
|
+
if (!selector) {
|
|
2103
|
+
return null;
|
|
2104
|
+
}
|
|
2105
|
+
const value = document.querySelector(selector);
|
|
2106
|
+
return value instanceof ctor ? value : null;
|
|
2107
|
+
};
|
|
1177
2108
|
var requireElement = (root, selector, ctor, name) => {
|
|
1178
2109
|
const value = selector ? document.querySelector(selector) : null;
|
|
1179
2110
|
if (value instanceof ctor) {
|
|
@@ -1224,11 +2155,20 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1224
2155
|
const guidedPrompts = parsePromptList(root.dataset.voiceGuidedPrompts);
|
|
1225
2156
|
const guidedLabel = root.dataset.voiceGuidedLabel ?? DEFAULT_GUIDED_LABEL;
|
|
1226
2157
|
const generalLabel = root.dataset.voiceGeneralLabel ?? DEFAULT_GENERAL_LABEL;
|
|
2158
|
+
const reconnectReportPath = root.dataset.voiceReconnectReportPath;
|
|
2159
|
+
const bargeInPath = root.dataset.voiceBargeInPath;
|
|
2160
|
+
const bargeInMonitor = bargeInPath ? createVoiceBargeInMonitor({
|
|
2161
|
+
path: bargeInPath,
|
|
2162
|
+
thresholdMs: parseOptionalNumber(root.dataset.voiceBargeInThresholdMs)
|
|
2163
|
+
}) : null;
|
|
2164
|
+
const bargeInRecentWindowMs = parseOptionalNumber(root.dataset.voiceBargeInRecentWindowMs) ?? 4000;
|
|
2165
|
+
const bargeInSpeechThreshold = parseOptionalNumber(root.dataset.voiceBargeInSpeechThreshold) ?? 0.04;
|
|
1227
2166
|
const syncElement = requireElement(document, root.dataset.voiceSync, HTMLElement, "voice-htmx-sync");
|
|
1228
2167
|
const connectionMetric = requireElement(root, root.dataset.voiceConnection, HTMLElement, "metric-connection");
|
|
1229
2168
|
const errorStatus = requireElement(root, root.dataset.voiceError, HTMLElement, "status-error");
|
|
1230
2169
|
const microphoneStatus = requireElement(root, root.dataset.voiceMicrophone, HTMLElement, "status-mic");
|
|
1231
2170
|
const promptStatus = requireElement(root, root.dataset.voicePrompt, HTMLElement, "status-prompt");
|
|
2171
|
+
const reconnectStatus = resolveElement2(root, root.dataset.voiceReconnect, HTMLElement);
|
|
1232
2172
|
const chatList = requireElement(root, root.dataset.voiceChat, HTMLElement, "chat-list");
|
|
1233
2173
|
const startGuidedButton = requireElement(root, root.dataset.voiceStartGuided, HTMLButtonElement, "start-guided");
|
|
1234
2174
|
const startGeneralButton = requireElement(root, root.dataset.voiceStartGeneral, HTMLButtonElement, "start-general");
|
|
@@ -1237,35 +2177,70 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1237
2177
|
const voiceMonitorCopy = requireElement(root, root.dataset.voiceMonitorCopy, HTMLElement, "voice-monitor-copy");
|
|
1238
2178
|
const voiceWaveGlow = requireElement(root, root.dataset.voiceWaveGlow, SVGPathElement, "voice-wave-glow");
|
|
1239
2179
|
const voiceWavePath = requireElement(root, root.dataset.voiceWavePath, SVGPathElement, "voice-wave-path");
|
|
2180
|
+
let activeMode = null;
|
|
2181
|
+
let hasStartedModes = {
|
|
2182
|
+
general: false,
|
|
2183
|
+
guided: false
|
|
2184
|
+
};
|
|
2185
|
+
let isCapturing = false;
|
|
2186
|
+
let micError = null;
|
|
2187
|
+
let waveLevels = createInitialVoiceWaveLevels();
|
|
2188
|
+
let guidedBargeInBinding = null;
|
|
2189
|
+
let generalBargeInBinding = null;
|
|
1240
2190
|
const guidedVoice = createVoiceController(guidedPath, {
|
|
1241
2191
|
capture: {
|
|
2192
|
+
onAudio: (audio, sendAudio) => {
|
|
2193
|
+
if (guidedBargeInBinding) {
|
|
2194
|
+
guidedBargeInBinding.sendAudio(audio);
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
sendAudio(audio);
|
|
2198
|
+
},
|
|
1242
2199
|
onLevel: (level) => {
|
|
2200
|
+
guidedBargeInBinding?.handleLevel(level);
|
|
1243
2201
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1244
2202
|
renderWave();
|
|
1245
2203
|
}
|
|
1246
2204
|
},
|
|
2205
|
+
connection: {
|
|
2206
|
+
reconnectReportPath
|
|
2207
|
+
},
|
|
1247
2208
|
preset: "guided-intake"
|
|
1248
2209
|
});
|
|
1249
2210
|
const generalVoice = createVoiceController(generalPath, {
|
|
1250
2211
|
capture: {
|
|
2212
|
+
onAudio: (audio, sendAudio) => {
|
|
2213
|
+
if (generalBargeInBinding) {
|
|
2214
|
+
generalBargeInBinding.sendAudio(audio);
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
sendAudio(audio);
|
|
2218
|
+
},
|
|
1251
2219
|
onLevel: (level) => {
|
|
2220
|
+
generalBargeInBinding?.handleLevel(level);
|
|
1252
2221
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1253
2222
|
renderWave();
|
|
1254
2223
|
}
|
|
1255
2224
|
},
|
|
2225
|
+
connection: {
|
|
2226
|
+
reconnectReportPath
|
|
2227
|
+
},
|
|
1256
2228
|
preset: "dictation"
|
|
1257
2229
|
});
|
|
1258
2230
|
const stopGuidedBinding = guidedVoice.bindHTMX({ element: syncElement });
|
|
1259
2231
|
const stopGeneralBinding = generalVoice.bindHTMX({ element: syncElement });
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
2232
|
+
const guidedAudioPlayer = createVoiceAudioPlayer(guidedVoice);
|
|
2233
|
+
const generalAudioPlayer = createVoiceAudioPlayer(generalVoice);
|
|
2234
|
+
guidedBargeInBinding = bindVoiceBargeIn(guidedVoice, guidedAudioPlayer, {
|
|
2235
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
2236
|
+
monitor: bargeInMonitor ?? undefined
|
|
2237
|
+
});
|
|
2238
|
+
generalBargeInBinding = bindVoiceBargeIn(generalVoice, generalAudioPlayer, {
|
|
2239
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
2240
|
+
monitor: bargeInMonitor ?? undefined
|
|
2241
|
+
});
|
|
1268
2242
|
const currentVoice = () => activeMode === "general" ? generalVoice : guidedVoice;
|
|
2243
|
+
const currentAudioPlayer = () => activeMode === "general" ? generalAudioPlayer : guidedAudioPlayer;
|
|
1269
2244
|
const renderWave = () => {
|
|
1270
2245
|
const path = createVoiceWavePath(waveLevels);
|
|
1271
2246
|
voiceWaveGlow.setAttribute("d", path);
|
|
@@ -1280,6 +2255,9 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1280
2255
|
const status = voice.status;
|
|
1281
2256
|
connectionMetric.textContent = voice.isConnected ? "Connected" : "Waiting";
|
|
1282
2257
|
errorStatus.textContent = micError || voice.error || "None";
|
|
2258
|
+
if (reconnectStatus) {
|
|
2259
|
+
reconnectStatus.textContent = formatReconnectState(voice.reconnect);
|
|
2260
|
+
}
|
|
1283
2261
|
microphoneStatus.textContent = isCapturing ? DEFAULT_MIC_LIVE : DEFAULT_MIC_IDLE;
|
|
1284
2262
|
promptStatus.textContent = resolvePromptMessage({
|
|
1285
2263
|
guidedPrompts,
|
|
@@ -1343,8 +2321,18 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1343
2321
|
render();
|
|
1344
2322
|
}
|
|
1345
2323
|
};
|
|
1346
|
-
guidedVoice.subscribe(
|
|
1347
|
-
|
|
2324
|
+
guidedVoice.subscribe(() => {
|
|
2325
|
+
if (guidedVoice.assistantAudio.length > 0) {
|
|
2326
|
+
guidedAudioPlayer.start().catch(() => {});
|
|
2327
|
+
}
|
|
2328
|
+
render();
|
|
2329
|
+
});
|
|
2330
|
+
generalVoice.subscribe(() => {
|
|
2331
|
+
if (generalVoice.assistantAudio.length > 0) {
|
|
2332
|
+
generalAudioPlayer.start().catch(() => {});
|
|
2333
|
+
}
|
|
2334
|
+
render();
|
|
2335
|
+
});
|
|
1348
2336
|
startGuidedButton.addEventListener("click", () => {
|
|
1349
2337
|
startMode("guided");
|
|
1350
2338
|
});
|
|
@@ -1354,9 +2342,16 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1354
2342
|
stopButton.addEventListener("click", () => {
|
|
1355
2343
|
stopMic();
|
|
1356
2344
|
});
|
|
2345
|
+
root.addEventListener("absolute-voice-simulate-disconnect", () => {
|
|
2346
|
+
currentVoice().simulateDisconnect();
|
|
2347
|
+
});
|
|
1357
2348
|
window.addEventListener("beforeunload", () => {
|
|
1358
2349
|
guidedVoice.stopRecording();
|
|
1359
2350
|
generalVoice.stopRecording();
|
|
2351
|
+
guidedBargeInBinding?.close();
|
|
2352
|
+
generalBargeInBinding?.close();
|
|
2353
|
+
guidedAudioPlayer.close();
|
|
2354
|
+
generalAudioPlayer.close();
|
|
1360
2355
|
stopGuidedBinding();
|
|
1361
2356
|
stopGeneralBinding();
|
|
1362
2357
|
guidedVoice.close();
|