@pipedog/core 0.0.0 → 0.2.0
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/LICENSE +25 -0
- package/NOTICE +9 -0
- package/dist/adapters/base_llm_adapter.d.ts +266 -0
- package/dist/adapters/base_llm_adapter.js +489 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +8 -0
- package/dist/adapters/schemas/direct_function.d.ts +202 -0
- package/dist/adapters/schemas/direct_function.js +302 -0
- package/dist/adapters/schemas/function_schema.d.ts +67 -0
- package/dist/adapters/schemas/function_schema.js +72 -0
- package/dist/adapters/schemas/index.d.ts +2 -0
- package/dist/adapters/schemas/index.js +8 -0
- package/dist/adapters/schemas/tools_schema.d.ts +57 -0
- package/dist/adapters/schemas/tools_schema.js +79 -0
- package/dist/adapters/services/index.d.ts +2 -0
- package/dist/adapters/services/index.js +8 -0
- package/dist/adapters/services/open_ai_adapter.d.ts +147 -0
- package/dist/adapters/services/open_ai_adapter.js +250 -0
- package/dist/audio/dtmf/dtmf_data.d.ts +3 -0
- package/dist/audio/dtmf/dtmf_data.js +22 -0
- package/dist/audio/dtmf/index.d.ts +2 -0
- package/dist/audio/dtmf/index.js +8 -0
- package/dist/audio/dtmf/types.d.ts +31 -0
- package/dist/audio/dtmf/types.js +35 -0
- package/dist/audio/dtmf/utils.d.ts +13 -0
- package/dist/audio/dtmf/utils.js +62 -0
- package/dist/audio/filters/base_audio_filter.d.ts +51 -0
- package/dist/audio/filters/base_audio_filter.js +17 -0
- package/dist/audio/filters/index.d.ts +2 -0
- package/dist/audio/filters/index.js +8 -0
- package/dist/audio/index.d.ts +2 -0
- package/dist/audio/index.js +8 -0
- package/dist/audio/krisp_instance.d.ts +95 -0
- package/dist/audio/krisp_instance.js +229 -0
- package/dist/audio/mixers/base_audio_mixer.d.ts +56 -0
- package/dist/audio/mixers/base_audio_mixer.js +16 -0
- package/dist/audio/mixers/index.d.ts +2 -0
- package/dist/audio/mixers/index.js +8 -0
- package/dist/audio/resamplers/base_audio_resampler.d.ts +46 -0
- package/dist/audio/resamplers/base_audio_resampler.js +35 -0
- package/dist/audio/resamplers/engine.d.ts +60 -0
- package/dist/audio/resamplers/engine.js +89 -0
- package/dist/audio/resamplers/index.d.ts +2 -0
- package/dist/audio/resamplers/index.js +8 -0
- package/dist/audio/resamplers/resampy_resampler.d.ts +10 -0
- package/dist/audio/resamplers/resampy_resampler.js +15 -0
- package/dist/audio/resamplers/sinc_engine.d.ts +10 -0
- package/dist/audio/resamplers/sinc_engine.js +235 -0
- package/dist/audio/resamplers/soxr_resampler.d.ts +58 -0
- package/dist/audio/resamplers/soxr_resampler.js +79 -0
- package/dist/audio/resamplers/soxr_stream_resampler.d.ts +78 -0
- package/dist/audio/resamplers/soxr_stream_resampler.js +159 -0
- package/dist/audio/turn/base_turn_analyzer.d.ts +84 -0
- package/dist/audio/turn/base_turn_analyzer.js +61 -0
- package/dist/audio/turn/index.d.ts +2 -0
- package/dist/audio/turn/index.js +8 -0
- package/dist/audio/turn/smart_turn/_whisper_features.d.ts +30 -0
- package/dist/audio/turn/smart_turn/_whisper_features.js +225 -0
- package/dist/audio/turn/smart_turn/base_smart_turn.d.ts +94 -0
- package/dist/audio/turn/smart_turn/base_smart_turn.js +239 -0
- package/dist/audio/turn/smart_turn/local_smart_turn_v3.d.ts +76 -0
- package/dist/audio/turn/smart_turn/local_smart_turn_v3.js +151 -0
- package/dist/audio/utils.d.ts +224 -0
- package/dist/audio/utils.js +634 -0
- package/dist/audio/vad/data/index.d.ts +2 -0
- package/dist/audio/vad/data/index.js +8 -0
- package/dist/audio/vad/index.d.ts +2 -0
- package/dist/audio/vad/index.js +8 -0
- package/dist/audio/vad/vad_analyzer.d.ts +134 -0
- package/dist/audio/vad/vad_analyzer.js +249 -0
- package/dist/audio/vad/vad_controller.d.ts +143 -0
- package/dist/audio/vad/vad_controller.js +228 -0
- package/dist/audio/volume.d.ts +38 -0
- package/dist/audio/volume.js +75 -0
- package/dist/bus/adapters/base.d.ts +35 -0
- package/dist/bus/adapters/base.js +19 -0
- package/dist/bus/adapters/index.d.ts +10 -0
- package/dist/bus/adapters/index.js +15 -0
- package/dist/bus/adapters/llm_context_adapter.d.ts +36 -0
- package/dist/bus/adapters/llm_context_adapter.js +82 -0
- package/dist/bus/adapters/tools_schema_adapter.d.ts +25 -0
- package/dist/bus/adapters/tools_schema_adapter.js +70 -0
- package/dist/bus/bridge_processor.d.ts +111 -0
- package/dist/bus/bridge_processor.js +166 -0
- package/dist/bus/bus.d.ts +110 -0
- package/dist/bus/bus.js +200 -0
- package/dist/bus/index.d.ts +22 -0
- package/dist/bus/index.js +27 -0
- package/dist/bus/local/async_queue.d.ts +12 -0
- package/dist/bus/local/async_queue.js +21 -0
- package/dist/bus/local/index.d.ts +3 -0
- package/dist/bus/local/index.js +8 -0
- package/dist/bus/messages.d.ts +378 -0
- package/dist/bus/messages.js +357 -0
- package/dist/bus/network/index.d.ts +16 -0
- package/dist/bus/network/index.js +21 -0
- package/dist/bus/queue.d.ts +27 -0
- package/dist/bus/queue.js +51 -0
- package/dist/bus/serializers/base.d.ts +25 -0
- package/dist/bus/serializers/base.js +14 -0
- package/dist/bus/serializers/index.d.ts +9 -0
- package/dist/bus/serializers/index.js +14 -0
- package/dist/bus/serializers/json.d.ts +77 -0
- package/dist/bus/serializers/json.js +367 -0
- package/dist/bus/subscriber.d.ts +46 -0
- package/dist/bus/subscriber.js +27 -0
- package/dist/bus/ui/index.d.ts +3 -0
- package/dist/bus/ui/index.js +8 -0
- package/dist/bus/ui/messages.d.ts +186 -0
- package/dist/bus/ui/messages.js +171 -0
- package/dist/classifiers/base_classifier.d.ts +263 -0
- package/dist/classifiers/base_classifier.js +346 -0
- package/dist/classifiers/cloudflare/clef/classifier.d.ts +70 -0
- package/dist/classifiers/cloudflare/clef/classifier.js +204 -0
- package/dist/classifiers/cloudflare/clef/client.d.ts +104 -0
- package/dist/classifiers/cloudflare/clef/client.js +234 -0
- package/dist/classifiers/cloudflare/clef/index.d.ts +2 -0
- package/dist/classifiers/cloudflare/clef/index.js +8 -0
- package/dist/classifiers/cloudflare/index.d.ts +2 -0
- package/dist/classifiers/cloudflare/index.js +8 -0
- package/dist/classifiers/http_client.d.ts +70 -0
- package/dist/classifiers/http_client.js +161 -0
- package/dist/classifiers/index.d.ts +2 -0
- package/dist/classifiers/index.js +8 -0
- package/dist/classifiers/jev/classifier.d.ts +8 -0
- package/dist/classifiers/jev/classifier.js +13 -0
- package/dist/classifiers/jev/client.d.ts +8 -0
- package/dist/classifiers/jev/client.js +13 -0
- package/dist/classifiers/jev/index.d.ts +2 -0
- package/dist/classifiers/jev/index.js +8 -0
- package/dist/classifiers/llm/classifier.d.ts +72 -0
- package/dist/classifiers/llm/classifier.js +304 -0
- package/dist/classifiers/llm/index.d.ts +2 -0
- package/dist/classifiers/llm/index.js +8 -0
- package/dist/classifiers/openai/decisions/classifier.d.ts +77 -0
- package/dist/classifiers/openai/decisions/classifier.js +241 -0
- package/dist/classifiers/openai/decisions/client.d.ts +108 -0
- package/dist/classifiers/openai/decisions/client.js +234 -0
- package/dist/classifiers/openai/decisions/index.d.ts +2 -0
- package/dist/classifiers/openai/decisions/index.js +8 -0
- package/dist/classifiers/openai/index.d.ts +2 -0
- package/dist/classifiers/openai/index.js +8 -0
- package/dist/classifiers/typesafe/index.d.ts +2 -0
- package/dist/classifiers/typesafe/index.js +8 -0
- package/dist/classifiers/typesafe/jev/classifier.d.ts +70 -0
- package/dist/classifiers/typesafe/jev/classifier.js +220 -0
- package/dist/classifiers/typesafe/jev/client.d.ts +105 -0
- package/dist/classifiers/typesafe/jev/client.js +233 -0
- package/dist/classifiers/typesafe/jev/index.d.ts +2 -0
- package/dist/classifiers/typesafe/jev/index.js +8 -0
- package/dist/clocks/base_clock.d.ts +13 -0
- package/dist/clocks/base_clock.js +14 -0
- package/dist/clocks/index.d.ts +2 -0
- package/dist/clocks/index.js +8 -0
- package/dist/clocks/system_clock.d.ts +15 -0
- package/dist/clocks/system_clock.js +25 -0
- package/dist/frames/frames.d.ts +2533 -0
- package/dist/frames/frames.js +2485 -0
- package/dist/frames/index.d.ts +2 -0
- package/dist/frames/index.js +8 -0
- package/dist/frames/protobufs/frames_pb2.d.ts +229 -0
- package/dist/frames/protobufs/frames_pb2.js +466 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +56 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.js +8 -0
- package/dist/metrics/metrics.d.ts +276 -0
- package/dist/metrics/metrics.js +254 -0
- package/dist/observers/base_observer.d.ts +173 -0
- package/dist/observers/base_observer.js +151 -0
- package/dist/observers/error_observer.d.ts +75 -0
- package/dist/observers/error_observer.js +87 -0
- package/dist/observers/function_call_observer.d.ts +157 -0
- package/dist/observers/function_call_observer.js +222 -0
- package/dist/observers/index.d.ts +2 -0
- package/dist/observers/index.js +8 -0
- package/dist/observers/loggers/debug_log_observer.d.ts +91 -0
- package/dist/observers/loggers/debug_log_observer.js +228 -0
- package/dist/observers/loggers/index.d.ts +2 -0
- package/dist/observers/loggers/index.js +8 -0
- package/dist/observers/loggers/llm_log_observer.d.ts +24 -0
- package/dist/observers/loggers/llm_log_observer.js +110 -0
- package/dist/observers/loggers/metrics_log_observer.d.ts +85 -0
- package/dist/observers/loggers/metrics_log_observer.js +186 -0
- package/dist/observers/loggers/transcription_log_observer.d.ts +23 -0
- package/dist/observers/loggers/transcription_log_observer.js +74 -0
- package/dist/observers/service_metrics_observer.d.ts +168 -0
- package/dist/observers/service_metrics_observer.js +236 -0
- package/dist/observers/speaking_observer.d.ts +104 -0
- package/dist/observers/speaking_observer.js +150 -0
- package/dist/observers/startup_timing_observer.d.ts +221 -0
- package/dist/observers/startup_timing_observer.js +365 -0
- package/dist/observers/turn_tracking_observer.d.ts +78 -0
- package/dist/observers/turn_tracking_observer.js +189 -0
- package/dist/observers/user_bot_latency_observer.d.ts +411 -0
- package/dist/observers/user_bot_latency_observer.js +974 -0
- package/dist/pipeline/base_pipeline.d.ts +49 -0
- package/dist/pipeline/base_pipeline.js +101 -0
- package/dist/pipeline/capabilities.d.ts +76 -0
- package/dist/pipeline/capabilities.js +97 -0
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +8 -0
- package/dist/pipeline/job_context.d.ts +317 -0
- package/dist/pipeline/job_context.js +450 -0
- package/dist/pipeline/job_decorator.d.ts +54 -0
- package/dist/pipeline/job_decorator.js +92 -0
- package/dist/pipeline/llm_switcher.d.ts +84 -0
- package/dist/pipeline/llm_switcher.js +107 -0
- package/dist/pipeline/llm_with_backend.d.ts +435 -0
- package/dist/pipeline/llm_with_backend.js +806 -0
- package/dist/pipeline/parallel_pipeline.d.ts +83 -0
- package/dist/pipeline/parallel_pipeline.js +200 -0
- package/dist/pipeline/pipeline.d.ts +126 -0
- package/dist/pipeline/pipeline.js +196 -0
- package/dist/pipeline/service_switcher.d.ts +287 -0
- package/dist/pipeline/service_switcher.js +509 -0
- package/dist/pipeline/sync_parallel_pipeline.d.ts +192 -0
- package/dist/pipeline/sync_parallel_pipeline.js +387 -0
- package/dist/pipeline/worker.d.ts +821 -0
- package/dist/pipeline/worker.js +1656 -0
- package/dist/pipeline/worker_observer.d.ts +131 -0
- package/dist/pipeline/worker_observer.js +274 -0
- package/dist/pipeline/worker_ready_decorator.d.ts +45 -0
- package/dist/pipeline/worker_ready_decorator.js +76 -0
- package/dist/processors/aggregators/async_tool_messages.d.ts +146 -0
- package/dist/processors/aggregators/async_tool_messages.js +291 -0
- package/dist/processors/aggregators/dtmf_aggregator.d.ts +61 -0
- package/dist/processors/aggregators/dtmf_aggregator.js +144 -0
- package/dist/processors/aggregators/gated.d.ts +43 -0
- package/dist/processors/aggregators/gated.js +86 -0
- package/dist/processors/aggregators/gated_llm_context.d.ts +42 -0
- package/dist/processors/aggregators/gated_llm_context.js +86 -0
- package/dist/processors/aggregators/index.d.ts +2 -0
- package/dist/processors/aggregators/index.js +8 -0
- package/dist/processors/aggregators/llm_context.d.ts +370 -0
- package/dist/processors/aggregators/llm_context.js +580 -0
- package/dist/processors/aggregators/llm_context_summarizer.d.ts +219 -0
- package/dist/processors/aggregators/llm_context_summarizer.js +447 -0
- package/dist/processors/aggregators/llm_response.d.ts +37 -0
- package/dist/processors/aggregators/llm_response.js +71 -0
- package/dist/processors/aggregators/llm_response_universal.d.ts +966 -0
- package/dist/processors/aggregators/llm_response_universal.js +2394 -0
- package/dist/processors/aggregators/llm_text_processor.d.ts +53 -0
- package/dist/processors/aggregators/llm_text_processor.js +102 -0
- package/dist/processors/aggregators/sentence.d.ts +39 -0
- package/dist/processors/aggregators/sentence.js +69 -0
- package/dist/processors/async_generator.d.ts +42 -0
- package/dist/processors/async_generator.js +76 -0
- package/dist/processors/audio/audio_buffer_processor.d.ts +241 -0
- package/dist/processors/audio/audio_buffer_processor.js +565 -0
- package/dist/processors/audio/index.d.ts +2 -0
- package/dist/processors/audio/index.js +8 -0
- package/dist/processors/audio/vad_processor.d.ts +67 -0
- package/dist/processors/audio/vad_processor.js +103 -0
- package/dist/processors/consumer_processor.d.ts +49 -0
- package/dist/processors/consumer_processor.js +85 -0
- package/dist/processors/filters/frame_filter.d.ts +39 -0
- package/dist/processors/filters/frame_filter.js +52 -0
- package/dist/processors/filters/function_filter.d.ts +59 -0
- package/dist/processors/filters/function_filter.js +78 -0
- package/dist/processors/filters/identity_filter.d.ts +33 -0
- package/dist/processors/filters/identity_filter.js +37 -0
- package/dist/processors/filters/index.d.ts +2 -0
- package/dist/processors/filters/index.js +8 -0
- package/dist/processors/filters/null_filter.d.ts +34 -0
- package/dist/processors/filters/null_filter.js +47 -0
- package/dist/processors/filters/wake_notifier_filter.d.ts +48 -0
- package/dist/processors/filters/wake_notifier_filter.js +49 -0
- package/dist/processors/frame_processor.d.ts +360 -0
- package/dist/processors/frame_processor.js +932 -0
- package/dist/processors/frameworks/index.d.ts +2 -0
- package/dist/processors/frameworks/index.js +8 -0
- package/dist/processors/frameworks/rtvi/frames.d.ts +241 -0
- package/dist/processors/frameworks/rtvi/frames.js +259 -0
- package/dist/processors/frameworks/rtvi/index.d.ts +6 -0
- package/dist/processors/frameworks/rtvi/index.js +11 -0
- package/dist/processors/frameworks/rtvi/models.d.ts +1741 -0
- package/dist/processors/frameworks/rtvi/models.js +1807 -0
- package/dist/processors/frameworks/rtvi/observer.d.ts +340 -0
- package/dist/processors/frameworks/rtvi/observer.js +1113 -0
- package/dist/processors/frameworks/rtvi/processor.d.ts +182 -0
- package/dist/processors/frameworks/rtvi/processor.js +676 -0
- package/dist/processors/idle_frame_processor.d.ts +44 -0
- package/dist/processors/idle_frame_processor.js +86 -0
- package/dist/processors/index.d.ts +2 -0
- package/dist/processors/index.js +8 -0
- package/dist/processors/logger.d.ts +42 -0
- package/dist/processors/logger.js +87 -0
- package/dist/processors/metrics/frame_processor_metrics.d.ts +172 -0
- package/dist/processors/metrics/frame_processor_metrics.js +372 -0
- package/dist/processors/metrics/index.d.ts +2 -0
- package/dist/processors/metrics/index.js +8 -0
- package/dist/processors/producer_processor.d.ts +68 -0
- package/dist/processors/producer_processor.js +78 -0
- package/dist/processors/text_transformer.d.ts +30 -0
- package/dist/processors/text_transformer.js +48 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/registry.d.ts +69 -0
- package/dist/registry/registry.js +149 -0
- package/dist/registry/types.d.ts +47 -0
- package/dist/registry/types.js +39 -0
- package/dist/serializers/base_serializer.d.ts +94 -0
- package/dist/serializers/base_serializer.js +82 -0
- package/dist/serializers/index.d.ts +2 -0
- package/dist/serializers/index.js +8 -0
- package/dist/services/ai_service.d.ts +145 -0
- package/dist/services/ai_service.js +262 -0
- package/dist/services/anthropic/index.d.ts +2 -0
- package/dist/services/anthropic/index.js +7 -0
- package/dist/services/assemblyai/index.d.ts +2 -0
- package/dist/services/assemblyai/index.js +7 -0
- package/dist/services/asyncai/index.d.ts +2 -0
- package/dist/services/asyncai/index.js +7 -0
- package/dist/services/aws/index.d.ts +2 -0
- package/dist/services/aws/index.js +7 -0
- package/dist/services/azure/index.d.ts +2 -0
- package/dist/services/azure/index.js +7 -0
- package/dist/services/baseten/index.d.ts +2 -0
- package/dist/services/baseten/index.js +7 -0
- package/dist/services/bland/index.d.ts +2 -0
- package/dist/services/bland/index.js +7 -0
- package/dist/services/camb/index.d.ts +2 -0
- package/dist/services/camb/index.js +7 -0
- package/dist/services/cartesia/index.d.ts +2 -0
- package/dist/services/cartesia/index.js +7 -0
- package/dist/services/cerebras/index.d.ts +2 -0
- package/dist/services/cerebras/index.js +7 -0
- package/dist/services/crusoe/index.d.ts +2 -0
- package/dist/services/crusoe/index.js +7 -0
- package/dist/services/deepgram/index.d.ts +2 -0
- package/dist/services/deepgram/index.js +7 -0
- package/dist/services/deepseek/index.d.ts +2 -0
- package/dist/services/deepseek/index.js +7 -0
- package/dist/services/elevenlabs/index.d.ts +2 -0
- package/dist/services/elevenlabs/index.js +7 -0
- package/dist/services/fal/index.d.ts +2 -0
- package/dist/services/fal/index.js +7 -0
- package/dist/services/fireworks/index.d.ts +2 -0
- package/dist/services/fireworks/index.js +7 -0
- package/dist/services/fish/index.d.ts +2 -0
- package/dist/services/fish/index.js +7 -0
- package/dist/services/funasr/index.d.ts +3 -0
- package/dist/services/funasr/index.js +8 -0
- package/dist/services/gladia/index.d.ts +2 -0
- package/dist/services/gladia/index.js +7 -0
- package/dist/services/google/index.d.ts +2 -0
- package/dist/services/google/index.js +7 -0
- package/dist/services/gradium/index.d.ts +2 -0
- package/dist/services/gradium/index.js +7 -0
- package/dist/services/grok/index.d.ts +2 -0
- package/dist/services/grok/index.js +7 -0
- package/dist/services/groq/index.d.ts +2 -0
- package/dist/services/groq/index.js +7 -0
- package/dist/services/heygen/index.d.ts +2 -0
- package/dist/services/heygen/index.js +7 -0
- package/dist/services/hume/index.d.ts +2 -0
- package/dist/services/hume/index.js +7 -0
- package/dist/services/image_service.d.ts +52 -0
- package/dist/services/image_service.js +60 -0
- package/dist/services/inception/index.d.ts +2 -0
- package/dist/services/inception/index.js +7 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +8 -0
- package/dist/services/inworld/index.d.ts +2 -0
- package/dist/services/inworld/index.js +7 -0
- package/dist/services/keenable/index.d.ts +2 -0
- package/dist/services/keenable/index.js +7 -0
- package/dist/services/kokoro/index.d.ts +2 -0
- package/dist/services/kokoro/index.js +7 -0
- package/dist/services/llm_service.d.ts +1106 -0
- package/dist/services/llm_service.js +2231 -0
- package/dist/services/mem0/index.d.ts +2 -0
- package/dist/services/mem0/index.js +7 -0
- package/dist/services/meta/index.d.ts +2 -0
- package/dist/services/meta/index.js +7 -0
- package/dist/services/minimax/index.d.ts +2 -0
- package/dist/services/minimax/index.js +7 -0
- package/dist/services/mistral/index.d.ts +2 -0
- package/dist/services/mistral/index.js +7 -0
- package/dist/services/moondream/index.d.ts +2 -0
- package/dist/services/moondream/index.js +7 -0
- package/dist/services/moonshine/index.d.ts +2 -0
- package/dist/services/moonshine/index.js +7 -0
- package/dist/services/nebius/index.d.ts +2 -0
- package/dist/services/nebius/index.js +7 -0
- package/dist/services/neuphonic/index.d.ts +2 -0
- package/dist/services/neuphonic/index.js +7 -0
- package/dist/services/novita/index.d.ts +2 -0
- package/dist/services/novita/index.js +7 -0
- package/dist/services/nvidia/index.d.ts +2 -0
- package/dist/services/nvidia/index.js +11 -0
- package/dist/services/ollama/index.d.ts +2 -0
- package/dist/services/ollama/index.js +7 -0
- package/dist/services/openai/index.d.ts +2 -0
- package/dist/services/openai/index.js +7 -0
- package/dist/services/openclaw/index.d.ts +14 -0
- package/dist/services/openclaw/index.js +19 -0
- package/dist/services/openrouter/index.d.ts +2 -0
- package/dist/services/openrouter/index.js +7 -0
- package/dist/services/perplexity/index.d.ts +2 -0
- package/dist/services/perplexity/index.js +7 -0
- package/dist/services/piper/index.d.ts +2 -0
- package/dist/services/piper/index.js +7 -0
- package/dist/services/pocket_tts/index.d.ts +2 -0
- package/dist/services/pocket_tts/index.js +7 -0
- package/dist/services/qwen/index.d.ts +2 -0
- package/dist/services/qwen/index.js +7 -0
- package/dist/services/resembleai/index.d.ts +2 -0
- package/dist/services/resembleai/index.js +7 -0
- package/dist/services/rime/index.d.ts +2 -0
- package/dist/services/rime/index.js +7 -0
- package/dist/services/sambanova/index.d.ts +2 -0
- package/dist/services/sambanova/index.js +7 -0
- package/dist/services/sarvam/index.d.ts +2 -0
- package/dist/services/sarvam/index.js +7 -0
- package/dist/services/settings.d.ts +356 -0
- package/dist/services/settings.js +437 -0
- package/dist/services/simli/index.d.ts +2 -0
- package/dist/services/simli/index.js +7 -0
- package/dist/services/smallest/index.d.ts +2 -0
- package/dist/services/smallest/index.js +7 -0
- package/dist/services/soniox/index.d.ts +2 -0
- package/dist/services/soniox/index.js +7 -0
- package/dist/services/speechify/index.d.ts +2 -0
- package/dist/services/speechify/index.js +7 -0
- package/dist/services/speechmatics/index.d.ts +2 -0
- package/dist/services/speechmatics/index.js +7 -0
- package/dist/services/stt_latency.d.ts +62 -0
- package/dist/services/stt_latency.js +71 -0
- package/dist/services/stt_service.d.ts +601 -0
- package/dist/services/stt_service.js +1044 -0
- package/dist/services/tavus/index.d.ts +2 -0
- package/dist/services/tavus/index.js +7 -0
- package/dist/services/together/index.d.ts +2 -0
- package/dist/services/together/index.js +7 -0
- package/dist/services/tts_service.d.ts +909 -0
- package/dist/services/tts_service.js +2018 -0
- package/dist/services/ultravox/index.d.ts +2 -0
- package/dist/services/ultravox/index.js +9 -0
- package/dist/services/vision_service.d.ts +56 -0
- package/dist/services/vision_service.js +62 -0
- package/dist/services/websocket_service.d.ts +213 -0
- package/dist/services/websocket_service.js +654 -0
- package/dist/services/whisper/index.d.ts +2 -0
- package/dist/services/whisper/index.js +9 -0
- package/dist/services/xai/index.d.ts +2 -0
- package/dist/services/xai/index.js +9 -0
- package/dist/services/xtts/index.d.ts +2 -0
- package/dist/services/xtts/index.js +9 -0
- package/dist/tests/index.d.ts +2 -0
- package/dist/tests/index.js +8 -0
- package/dist/tests/utils.d.ts +123 -0
- package/dist/tests/utils.js +212 -0
- package/dist/transcriptions/index.d.ts +2 -0
- package/dist/transcriptions/index.js +8 -0
- package/dist/transcriptions/language.d.ts +376 -0
- package/dist/transcriptions/language.js +543 -0
- package/dist/transports/base_input.d.ts +149 -0
- package/dist/transports/base_input.js +338 -0
- package/dist/transports/base_output.d.ts +432 -0
- package/dist/transports/base_output.js +1231 -0
- package/dist/transports/base_transport.d.ts +223 -0
- package/dist/transports/base_transport.js +167 -0
- package/dist/transports/daily/index.d.ts +2 -0
- package/dist/transports/daily/index.js +9 -0
- package/dist/transports/heygen/index.d.ts +2 -0
- package/dist/transports/heygen/index.js +9 -0
- package/dist/transports/index.d.ts +2 -0
- package/dist/transports/index.js +8 -0
- package/dist/transports/lemonslice/index.d.ts +2 -0
- package/dist/transports/lemonslice/index.js +9 -0
- package/dist/transports/livekit/index.d.ts +2 -0
- package/dist/transports/livekit/index.js +9 -0
- package/dist/transports/local/index.d.ts +2 -0
- package/dist/transports/local/index.js +9 -0
- package/dist/transports/moq/index.d.ts +2 -0
- package/dist/transports/moq/index.js +9 -0
- package/dist/transports/smallwebrtc/index.d.ts +2 -0
- package/dist/transports/smallwebrtc/index.js +9 -0
- package/dist/transports/tavus/index.d.ts +2 -0
- package/dist/transports/tavus/index.js +9 -0
- package/dist/transports/video_in_sampler.d.ts +157 -0
- package/dist/transports/video_in_sampler.js +258 -0
- package/dist/transports/vonage/index.d.ts +2 -0
- package/dist/transports/vonage/index.js +9 -0
- package/dist/transports/websocket/index.d.ts +2 -0
- package/dist/transports/websocket/index.js +9 -0
- package/dist/transports/whatsapp/index.d.ts +2 -0
- package/dist/transports/whatsapp/index.js +9 -0
- package/dist/turns/eager_end_of_turn_mixin.d.ts +70 -0
- package/dist/turns/eager_end_of_turn_mixin.js +122 -0
- package/dist/turns/empty_user_turn.d.ts +55 -0
- package/dist/turns/empty_user_turn.js +53 -0
- package/dist/turns/index.d.ts +2 -0
- package/dist/turns/index.js +8 -0
- package/dist/turns/speculation_gate.d.ts +169 -0
- package/dist/turns/speculation_gate.js +294 -0
- package/dist/turns/types.d.ts +33 -0
- package/dist/turns/types.js +35 -0
- package/dist/turns/user_idle_controller.d.ts +94 -0
- package/dist/turns/user_idle_controller.js +187 -0
- package/dist/turns/user_mute/always_user_mute_strategy.d.ts +15 -0
- package/dist/turns/user_mute/always_user_mute_strategy.js +29 -0
- package/dist/turns/user_mute/base_user_mute_strategy.d.ts +39 -0
- package/dist/turns/user_mute/base_user_mute_strategy.js +45 -0
- package/dist/turns/user_mute/first_speech_user_mute_strategy.d.ts +28 -0
- package/dist/turns/user_mute/first_speech_user_mute_strategy.js +52 -0
- package/dist/turns/user_mute/function_call_user_mute_strategy.d.ts +25 -0
- package/dist/turns/user_mute/function_call_user_mute_strategy.js +50 -0
- package/dist/turns/user_mute/index.d.ts +6 -0
- package/dist/turns/user_mute/index.js +11 -0
- package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.d.ts +43 -0
- package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.js +76 -0
- package/dist/turns/user_start/base_user_turn_start_strategy.d.ts +135 -0
- package/dist/turns/user_start/base_user_turn_start_strategy.js +134 -0
- package/dist/turns/user_start/external_user_turn_start_strategy.d.ts +45 -0
- package/dist/turns/user_start/external_user_turn_start_strategy.js +64 -0
- package/dist/turns/user_start/index.d.ts +8 -0
- package/dist/turns/user_start/index.js +16 -0
- package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.d.ts +133 -0
- package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.js +243 -0
- package/dist/turns/user_start/min_words_user_turn_start_strategy.d.ts +60 -0
- package/dist/turns/user_start/min_words_user_turn_start_strategy.js +102 -0
- package/dist/turns/user_start/transcription_user_turn_start_strategy.d.ts +29 -0
- package/dist/turns/user_start/transcription_user_turn_start_strategy.js +43 -0
- package/dist/turns/user_start/vad_user_turn_start_strategy.d.ts +20 -0
- package/dist/turns/user_start/vad_user_turn_start_strategy.js +31 -0
- package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.d.ts +141 -0
- package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.js +238 -0
- package/dist/turns/user_stop/base_user_turn_stop_strategy.d.ts +174 -0
- package/dist/turns/user_stop/base_user_turn_stop_strategy.js +170 -0
- package/dist/turns/user_stop/deferred_user_turn_stop_strategy.d.ts +81 -0
- package/dist/turns/user_stop/deferred_user_turn_stop_strategy.js +107 -0
- package/dist/turns/user_stop/eager_match_policy.d.ts +36 -0
- package/dist/turns/user_stop/eager_match_policy.js +50 -0
- package/dist/turns/user_stop/eager_user_turn_stop_strategy.d.ts +84 -0
- package/dist/turns/user_stop/eager_user_turn_stop_strategy.js +154 -0
- package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.d.ts +45 -0
- package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.js +55 -0
- package/dist/turns/user_stop/external_user_turn_stop_strategy.d.ts +115 -0
- package/dist/turns/user_stop/external_user_turn_stop_strategy.js +205 -0
- package/dist/turns/user_stop/index.d.ts +10 -0
- package/dist/turns/user_stop/index.js +15 -0
- package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.d.ts +44 -0
- package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.js +59 -0
- package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.d.ts +140 -0
- package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.js +303 -0
- package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.d.ts +135 -0
- package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.js +332 -0
- package/dist/turns/user_turn_completion_mixin.d.ts +147 -0
- package/dist/turns/user_turn_completion_mixin.js +591 -0
- package/dist/turns/user_turn_controller.d.ts +134 -0
- package/dist/turns/user_turn_controller.js +335 -0
- package/dist/turns/user_turn_processor.d.ts +87 -0
- package/dist/turns/user_turn_processor.js +180 -0
- package/dist/turns/user_turn_strategies.d.ts +187 -0
- package/dist/turns/user_turn_strategies.js +222 -0
- package/dist/utils/async_tool_cancellation.d.ts +35 -0
- package/dist/utils/async_tool_cancellation.js +78 -0
- package/dist/utils/asyncio/index.d.ts +2 -0
- package/dist/utils/asyncio/index.js +7 -0
- package/dist/utils/asyncio/task_manager.d.ts +49 -0
- package/dist/utils/asyncio/task_manager.js +105 -0
- package/dist/utils/base_object.d.ts +76 -0
- package/dist/utils/base_object.js +154 -0
- package/dist/utils/context/aggregated_frame_sequencer.d.ts +491 -0
- package/dist/utils/context/aggregated_frame_sequencer.js +803 -0
- package/dist/utils/context/audio_context_manager.d.ts +131 -0
- package/dist/utils/context/audio_context_manager.js +233 -0
- package/dist/utils/context/index.d.ts +2 -0
- package/dist/utils/context/index.js +7 -0
- package/dist/utils/context/llm_context_summarization.d.ts +258 -0
- package/dist/utils/context/llm_context_summarization.js +511 -0
- package/dist/utils/context/text_segment_map.d.ts +611 -0
- package/dist/utils/context/text_segment_map.js +1132 -0
- package/dist/utils/context/word_completion_tracker.d.ts +273 -0
- package/dist/utils/context/word_completion_tracker.js +441 -0
- package/dist/utils/context/word_timestamp_tracker.d.ts +72 -0
- package/dist/utils/context/word_timestamp_tracker.js +98 -0
- package/dist/utils/deprecation.d.ts +28 -0
- package/dist/utils/deprecation.js +75 -0
- package/dist/utils/env.d.ts +38 -0
- package/dist/utils/env.js +62 -0
- package/dist/utils/errors.d.ts +75 -0
- package/dist/utils/errors.js +202 -0
- package/dist/utils/file_resolver.d.ts +126 -0
- package/dist/utils/file_resolver.js +314 -0
- package/dist/utils/file_storage.d.ts +115 -0
- package/dist/utils/file_storage.js +178 -0
- package/dist/utils/frame_queue.d.ts +36 -0
- package/dist/utils/frame_queue.js +66 -0
- package/dist/utils/http.d.ts +39 -0
- package/dist/utils/http.js +53 -0
- package/dist/utils/image.d.ts +14 -0
- package/dist/utils/image.js +63 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/jpeg_encoder.d.ts +14 -0
- package/dist/utils/jpeg_encoder.js +315 -0
- package/dist/utils/log_config.d.ts +34 -0
- package/dist/utils/log_config.js +76 -0
- package/dist/utils/logger.d.ts +40 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/mixin.d.ts +14 -0
- package/dist/utils/mixin.js +63 -0
- package/dist/utils/network.d.ts +73 -0
- package/dist/utils/network.js +90 -0
- package/dist/utils/security/allowed_origins.d.ts +16 -0
- package/dist/utils/security/allowed_origins.js +34 -0
- package/dist/utils/security/index.d.ts +2 -0
- package/dist/utils/security/index.js +7 -0
- package/dist/utils/security/redaction.d.ts +27 -0
- package/dist/utils/security/redaction.js +123 -0
- package/dist/utils/security/ssrf.d.ts +79 -0
- package/dist/utils/security/ssrf.js +327 -0
- package/dist/utils/shared.d.ts +35 -0
- package/dist/utils/shared.js +136 -0
- package/dist/utils/startup.d.ts +17 -0
- package/dist/utils/startup.js +130 -0
- package/dist/utils/string.d.ts +116 -0
- package/dist/utils/string.js +460 -0
- package/dist/utils/sync/base_notifier.d.ts +25 -0
- package/dist/utils/sync/base_notifier.js +16 -0
- package/dist/utils/sync/event_notifier.d.ts +38 -0
- package/dist/utils/sync/event_notifier.js +51 -0
- package/dist/utils/sync/index.d.ts +2 -0
- package/dist/utils/sync/index.js +7 -0
- package/dist/utils/text/alnum_utils.d.ts +109 -0
- package/dist/utils/text/alnum_utils.js +262 -0
- package/dist/utils/text/base_text_aggregator.d.ts +146 -0
- package/dist/utils/text/base_text_aggregator.js +102 -0
- package/dist/utils/text/base_text_filter.d.ts +54 -0
- package/dist/utils/text/base_text_filter.js +50 -0
- package/dist/utils/text/index.d.ts +2 -0
- package/dist/utils/text/index.js +7 -0
- package/dist/utils/text/markdown_text_filter.d.ts +147 -0
- package/dist/utils/text/markdown_text_filter.js +522 -0
- package/dist/utils/text/markup_utils.d.ts +54 -0
- package/dist/utils/text/markup_utils.js +156 -0
- package/dist/utils/text/pattern_pair_aggregator.d.ts +251 -0
- package/dist/utils/text/pattern_pair_aggregator.js +478 -0
- package/dist/utils/text/phonemes.d.ts +60 -0
- package/dist/utils/text/phonemes.js +172 -0
- package/dist/utils/text/simple_text_aggregator.d.ts +135 -0
- package/dist/utils/text/simple_text_aggregator.js +254 -0
- package/dist/utils/text/skip_tags_aggregator.d.ts +85 -0
- package/dist/utils/text/skip_tags_aggregator.js +147 -0
- package/dist/utils/text/transforms/acronyms.d.ts +21 -0
- package/dist/utils/text/transforms/acronyms.js +31 -0
- package/dist/utils/text/transforms/currency.d.ts +16 -0
- package/dist/utils/text/transforms/currency.js +56 -0
- package/dist/utils/text/transforms/dates.d.ts +18 -0
- package/dist/utils/text/transforms/dates.js +102 -0
- package/dist/utils/text/transforms/email.d.ts +17 -0
- package/dist/utils/text/transforms/email.js +35 -0
- package/dist/utils/text/transforms/index.d.ts +37 -0
- package/dist/utils/text/transforms/index.js +42 -0
- package/dist/utils/text/transforms/numbers.d.ts +25 -0
- package/dist/utils/text/transforms/numbers.js +59 -0
- package/dist/utils/text/transforms/percentages.d.ts +16 -0
- package/dist/utils/text/transforms/percentages.js +32 -0
- package/dist/utils/text/transforms/phone.d.ts +21 -0
- package/dist/utils/text/transforms/phone.js +39 -0
- package/dist/utils/text/transforms/pronunciations.d.ts +65 -0
- package/dist/utils/text/transforms/pronunciations.js +112 -0
- package/dist/utils/text/transforms/replacements.d.ts +39 -0
- package/dist/utils/text/transforms/replacements.js +46 -0
- package/dist/utils/text/transforms/strip_markdown.d.ts +25 -0
- package/dist/utils/text/transforms/strip_markdown.js +60 -0
- package/dist/utils/text/transforms/units.d.ts +22 -0
- package/dist/utils/text/transforms/units.js +81 -0
- package/dist/utils/text/transforms/voice_formatter.d.ts +86 -0
- package/dist/utils/text/transforms/voice_formatter.js +98 -0
- package/dist/utils/text/unicode_digits.d.ts +3 -0
- package/dist/utils/text/unicode_digits.js +34 -0
- package/dist/utils/text/word_timestamp_utils.d.ts +26 -0
- package/dist/utils/text/word_timestamp_utils.js +52 -0
- package/dist/utils/time.d.ts +13 -0
- package/dist/utils/time.js +31 -0
- package/dist/utils/tracing/index.d.ts +3 -0
- package/dist/utils/tracing/index.js +8 -0
- package/dist/utils/tracing/service_attributes.d.ts +198 -0
- package/dist/utils/tracing/service_attributes.js +434 -0
- package/dist/utils/tracing/service_decorators.d.ts +214 -0
- package/dist/utils/tracing/service_decorators.js +1662 -0
- package/dist/utils/tracing/setup.d.ts +107 -0
- package/dist/utils/tracing/setup.js +160 -0
- package/dist/utils/tracing/tracing_context.d.ts +55 -0
- package/dist/utils/tracing/tracing_context.js +97 -0
- package/dist/utils/tracing/turn_trace_observer.d.ts +95 -0
- package/dist/utils/tracing/turn_trace_observer.js +233 -0
- package/dist/utils/types.d.ts +80 -0
- package/dist/utils/types.js +108 -0
- package/dist/utils/utils.d.ts +5 -0
- package/dist/utils/utils.js +22 -0
- package/dist/utils/yaml.d.ts +179 -0
- package/dist/utils/yaml.js +556 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +7 -0
- package/dist/workers/base_worker.d.ts +772 -0
- package/dist/workers/base_worker.js +1298 -0
- package/dist/workers/index.d.ts +2 -0
- package/dist/workers/index.js +8 -0
- package/dist/workers/llm/backend_llm_worker.d.ts +552 -0
- package/dist/workers/llm/backend_llm_worker.js +1055 -0
- package/dist/workers/llm/index.d.ts +6 -0
- package/dist/workers/llm/index.js +11 -0
- package/dist/workers/llm/llm_context_worker.d.ts +66 -0
- package/dist/workers/llm/llm_context_worker.js +72 -0
- package/dist/workers/llm/llm_worker.d.ts +180 -0
- package/dist/workers/llm/llm_worker.js +323 -0
- package/dist/workers/llm/tool_decorator.d.ts +68 -0
- package/dist/workers/llm/tool_decorator.js +60 -0
- package/dist/workers/proxy/index.d.ts +3 -0
- package/dist/workers/proxy/index.js +9 -0
- package/dist/workers/proxy/websocket/client.d.ts +110 -0
- package/dist/workers/proxy/websocket/client.js +196 -0
- package/dist/workers/proxy/websocket/index.d.ts +3 -0
- package/dist/workers/proxy/websocket/index.js +10 -0
- package/dist/workers/runner.d.ts +268 -0
- package/dist/workers/runner.js +489 -0
- package/dist/workers/ui/index.d.ts +22 -0
- package/dist/workers/ui/index.js +27 -0
- package/dist/workers/ui/ui_event_decorator.d.ts +43 -0
- package/dist/workers/ui/ui_event_decorator.js +75 -0
- package/dist/workers/ui/ui_job_context.d.ts +12 -0
- package/dist/workers/ui/ui_job_context.js +17 -0
- package/dist/workers/ui/ui_prompts.d.ts +21 -0
- package/dist/workers/ui/ui_prompts.js +41 -0
- package/dist/workers/ui/ui_tools.d.ts +32 -0
- package/dist/workers/ui/ui_tools.js +112 -0
- package/dist/workers/ui/ui_worker.d.ts +571 -0
- package/dist/workers/ui/ui_worker.js +1156 -0
- package/package.json +61 -3
- package/README.md +0 -5
|
@@ -0,0 +1,1044 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/services/stt_service.py
|
|
6
|
+
/** Base classes for Speech-to-Text services with continuous and segmented processing. */
|
|
7
|
+
import { monotonic, Queue, rethrowIfCancelled, sleep, time } from "@pipedog/asyncio";
|
|
8
|
+
import { pcmToWav } from "../audio/utils.js";
|
|
9
|
+
import { InputAudioRawFrame, InterruptionFrame, LLMContextAssistantTurnFrame, STTMetadataFrame, STTMuteFrame, STTUpdateSettingsFrame, TranscriptionFrame, UserAudioRawFrame, UserStoppedSpeakingFrame, VADUserStartedSpeakingFrame, VADUserStoppedSpeakingFrame, } from "../frames/frames.js";
|
|
10
|
+
import { STTUsage } from "../metrics/metrics.js";
|
|
11
|
+
import { FrameDirection } from "../processors/frame_processor.js";
|
|
12
|
+
import { isLanguage } from "../transcriptions/language.js";
|
|
13
|
+
import { logger } from "../utils/logger.js";
|
|
14
|
+
import { isGiven } from "../utils/types.js";
|
|
15
|
+
import { AIService } from "./ai_service.js";
|
|
16
|
+
import { STTSettings } from "./settings.js";
|
|
17
|
+
import { DEFAULT_TTFS_P99 } from "./stt_latency.js";
|
|
18
|
+
import { State, WebsocketService } from "./websocket_service.js";
|
|
19
|
+
// Duration in seconds of silent audio sent for WebSocket keepalive (100ms).
|
|
20
|
+
const KEEPALIVE_SILENCE_DURATION = 0.1;
|
|
21
|
+
function errorMessage(e) {
|
|
22
|
+
return e instanceof Error ? e.message : String(e);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Base class for speech-to-text services.
|
|
26
|
+
*
|
|
27
|
+
* Provides common functionality for STT services including audio
|
|
28
|
+
* passthrough, muting, settings management, and audio processing. Subclasses
|
|
29
|
+
* must implement the `runStt` method to provide actual speech recognition.
|
|
30
|
+
*
|
|
31
|
+
* Includes an optional keepalive mechanism that sends silent audio when no
|
|
32
|
+
* real audio has been sent for a configurable timeout, preventing servers from
|
|
33
|
+
* closing idle connections (e.g. when behind a ServiceSwitcher). Subclasses
|
|
34
|
+
* that enable keepalive must override `_sendKeepalive()` to deliver the
|
|
35
|
+
* silence in the appropriate service-specific protocol.
|
|
36
|
+
*
|
|
37
|
+
* A streaming STT reports latency through TTFB — speech end to final
|
|
38
|
+
* transcript — and not through processing metrics. Audio arrives
|
|
39
|
+
* continuously, so there is no discrete request whose duration a
|
|
40
|
+
* `FrameProcessor.startProcessingMetrics()` window could measure; anchoring
|
|
41
|
+
* one to a speech or turn boundary measures how long the user talked.
|
|
42
|
+
* SegmentedSTTService does issue a discrete request per utterance, so its
|
|
43
|
+
* subclasses time that call and report both.
|
|
44
|
+
*
|
|
45
|
+
* Event handlers:
|
|
46
|
+
*
|
|
47
|
+
* - on_connected: called when connected to the STT service.
|
|
48
|
+
* - on_disconnected: called when disconnected from the STT service.
|
|
49
|
+
* - on_connection_error: called when a connection to the STT service error
|
|
50
|
+
* occurs.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* stt.addEventHandler("on_connected", async (stt: STTService) => {
|
|
54
|
+
* logger.debug("STT connected");
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* stt.addEventHandler("on_disconnected", async (stt: STTService) => {
|
|
58
|
+
* logger.debug("STT disconnected");
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* stt.addEventHandler("on_connection_error", async (stt: STTService, error: string) => {
|
|
62
|
+
* logger.error(`STT connection error: ${error}`);
|
|
63
|
+
* });
|
|
64
|
+
*/
|
|
65
|
+
export class STTService extends AIService {
|
|
66
|
+
_audioPassthrough;
|
|
67
|
+
_initSampleRate;
|
|
68
|
+
_sampleRate = 0;
|
|
69
|
+
_muted = false;
|
|
70
|
+
_userId = "";
|
|
71
|
+
_ttfsP99Latency;
|
|
72
|
+
// STT TTFB tracking state
|
|
73
|
+
_sttTtfbTimeout;
|
|
74
|
+
_ttfbTimeoutTask = null;
|
|
75
|
+
_userSpeaking = false;
|
|
76
|
+
_finalizePending = false;
|
|
77
|
+
_finalizeRequested = false;
|
|
78
|
+
_lastTranscriptTime = 0;
|
|
79
|
+
// Keepalive state
|
|
80
|
+
_keepaliveTimeout;
|
|
81
|
+
_keepaliveInterval;
|
|
82
|
+
_keepaliveTask = null;
|
|
83
|
+
_lastAudioTime = 0;
|
|
84
|
+
// Client-measured audio seconds submitted to the service since the last
|
|
85
|
+
// usage report (see emitSttUsageMetrics).
|
|
86
|
+
_sttUsagePendingSeconds = 0.0;
|
|
87
|
+
// VAD-aware reconnect state
|
|
88
|
+
// Whether it is safe to reconnect right now (false while the user is speaking).
|
|
89
|
+
_canReconnect = true;
|
|
90
|
+
// Whether a reconnect has been requested but deferred until speaking ends.
|
|
91
|
+
_needReconnect = false;
|
|
92
|
+
// Whether a reconnect cycle is currently in progress.
|
|
93
|
+
_reconnecting = false;
|
|
94
|
+
// Audio frames received while _reconnecting is true, replayed after reconnect.
|
|
95
|
+
_reconnectAudioBuffer = [];
|
|
96
|
+
/**
|
|
97
|
+
* Initialize the STT service.
|
|
98
|
+
*
|
|
99
|
+
* @param options.audioPassthrough Whether to pass audio frames downstream
|
|
100
|
+
* after processing. Defaults to true.
|
|
101
|
+
* @param options.sampleRate The sample rate for audio input. If null, will
|
|
102
|
+
* be determined from the start frame.
|
|
103
|
+
* @param options.sttTtfbTimeout Time in seconds to wait after VAD stop
|
|
104
|
+
* before reporting TTFB. This delay allows the final transcription to
|
|
105
|
+
* arrive. Defaults to 2.0. Note: STT "TTFB" differs from traditional TTFB
|
|
106
|
+
* (which measures from a discrete request to first response byte). Since
|
|
107
|
+
* STT receives continuous audio, we measure from when the user stops
|
|
108
|
+
* speaking to when the final transcript arrives—capturing the latency that
|
|
109
|
+
* matters for voice AI applications.
|
|
110
|
+
* @param options.ttfsP99Latency P99 latency from speech end to final
|
|
111
|
+
* transcript in seconds. This is broadcast via STTMetadataFrame at
|
|
112
|
+
* pipeline start for downstream processors (e.g., turn strategies) to
|
|
113
|
+
* optimize timing. Subclasses provide measured defaults; pass a value here
|
|
114
|
+
* to override for your deployment. Turn-based services where the server
|
|
115
|
+
* defines turn boundaries should override `supportsTtfs` to return false
|
|
116
|
+
* instead of supplying a value.
|
|
117
|
+
* @param options.keepaliveTimeout Seconds of no audio before sending
|
|
118
|
+
* silence to keep the connection alive. null disables keepalive. Useful
|
|
119
|
+
* for services that close idle connections (e.g. behind a
|
|
120
|
+
* ServiceSwitcher).
|
|
121
|
+
* @param options.keepaliveInterval Seconds between idle checks when
|
|
122
|
+
* keepalive is enabled.
|
|
123
|
+
* @param options.settings The runtime-updatable settings for the STT
|
|
124
|
+
* service. Other options are passed to the parent AIService.
|
|
125
|
+
*/
|
|
126
|
+
constructor(options = {}) {
|
|
127
|
+
super({
|
|
128
|
+
...options,
|
|
129
|
+
settings: options.settings ||
|
|
130
|
+
// Here in case subclass doesn't implement more specific settings
|
|
131
|
+
// (which hopefully should be rare)
|
|
132
|
+
new STTSettings(),
|
|
133
|
+
});
|
|
134
|
+
// Convert Language enum to service-specific format at init time.
|
|
135
|
+
// Runtime updates are handled by _updateSettings(), but init-time
|
|
136
|
+
// settings bypass that path and need explicit conversion.
|
|
137
|
+
// Raw strings (e.g. "de-DE") are first checked against the Language
|
|
138
|
+
// codes so they go through the same resolution logic.
|
|
139
|
+
const language = this._settings.language;
|
|
140
|
+
if (typeof language === "string" && !isLanguage(language)) {
|
|
141
|
+
logger.debug(`Language string '${language}' is not a recognized ` +
|
|
142
|
+
"Language code. It will be passed to the service as-is.");
|
|
143
|
+
}
|
|
144
|
+
if (typeof language === "string" && isLanguage(language)) {
|
|
145
|
+
const converted = this.languageToServiceLanguage(language);
|
|
146
|
+
if (converted !== null) {
|
|
147
|
+
this._settings.language = converted;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this._audioPassthrough = options.audioPassthrough ?? true;
|
|
151
|
+
this._initSampleRate = options.sampleRate ?? null;
|
|
152
|
+
this._ttfsP99Latency = options.ttfsP99Latency ?? null;
|
|
153
|
+
this._sttTtfbTimeout = options.sttTtfbTimeout ?? 2.0;
|
|
154
|
+
this._keepaliveTimeout = options.keepaliveTimeout ?? null;
|
|
155
|
+
this._keepaliveInterval = options.keepaliveInterval ?? 5.0;
|
|
156
|
+
this._registerEventHandler("on_connected");
|
|
157
|
+
this._registerEventHandler("on_disconnected");
|
|
158
|
+
this._registerEventHandler("on_connection_error");
|
|
159
|
+
}
|
|
160
|
+
/** The service's current settings, for reading. */
|
|
161
|
+
get settings() {
|
|
162
|
+
return this._settings;
|
|
163
|
+
}
|
|
164
|
+
/** Whether the STT service is currently muted and will not process audio. */
|
|
165
|
+
get isMuted() {
|
|
166
|
+
return this._muted;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Mark that a finalize request has been sent, awaiting server confirmation.
|
|
170
|
+
*
|
|
171
|
+
* For providers that have explicit server confirmation of finalization
|
|
172
|
+
* (e.g., Deepgram's from_finalize field), call this when sending the
|
|
173
|
+
* finalize request. Then call `confirmFinalize()` when the server confirms.
|
|
174
|
+
*
|
|
175
|
+
* For providers without server confirmation, don't call this method - just
|
|
176
|
+
* send the finalize/flush/commit command and rely on the TTFB timeout.
|
|
177
|
+
*/
|
|
178
|
+
requestFinalize() {
|
|
179
|
+
this._finalizeRequested = true;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Confirm that the server has acknowledged the finalize request.
|
|
183
|
+
*
|
|
184
|
+
* Call this when the server response confirms finalization (e.g.,
|
|
185
|
+
* Deepgram's from_finalize=True). The next TranscriptionFrame pushed will be
|
|
186
|
+
* marked as finalized.
|
|
187
|
+
*
|
|
188
|
+
* Only has effect if `requestFinalize()` was previously called.
|
|
189
|
+
*/
|
|
190
|
+
confirmFinalize() {
|
|
191
|
+
if (this._finalizeRequested) {
|
|
192
|
+
this._finalizePending = true;
|
|
193
|
+
this._finalizeRequested = false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** The current sample rate for audio processing, in Hz. */
|
|
197
|
+
get sampleRate() {
|
|
198
|
+
return this._sampleRate;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Accumulate client-measured audio seconds for usage metrics.
|
|
202
|
+
*
|
|
203
|
+
* @param audio Raw 16-bit mono PCM audio bytes submitted to the service.
|
|
204
|
+
*/
|
|
205
|
+
_recordSttAudioUsage(audio) {
|
|
206
|
+
if (this.sampleRate > 0) {
|
|
207
|
+
this._sttUsagePendingSeconds += audio.length / (this.sampleRate * 2);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Emit an incremental STT usage metrics event.
|
|
212
|
+
*
|
|
213
|
+
* Reports the audio seconds accumulated since the last report as an
|
|
214
|
+
* `STTUsageMetricsData` in a `MetricsFrame`. Services call this once per
|
|
215
|
+
* billing event (e.g. on each final transcript); the base class calls it a
|
|
216
|
+
* final time on stop/cancel to flush trailing audio. Does nothing if there
|
|
217
|
+
* is no usage to report.
|
|
218
|
+
*/
|
|
219
|
+
async emitSttUsageMetrics() {
|
|
220
|
+
const audioSeconds = this._sttUsagePendingSeconds;
|
|
221
|
+
this._sttUsagePendingSeconds = 0.0;
|
|
222
|
+
if (audioSeconds === 0.0) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
await this.startSttUsageMetrics(new STTUsage({ audioSeconds }));
|
|
226
|
+
}
|
|
227
|
+
/** Flush any trailing audio usage not yet reported by a billing event. */
|
|
228
|
+
async _flushSttUsageMetrics() {
|
|
229
|
+
await this.emitSttUsageMetrics();
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Convert a language to the service-specific language format.
|
|
233
|
+
*
|
|
234
|
+
* @param language The language to convert.
|
|
235
|
+
* @returns The service-specific language identifier. Return `null` to
|
|
236
|
+
* indicate an unsupported language. This optional return is an extension
|
|
237
|
+
* hook for future or third-party subclasses; as of 2026-04-28,
|
|
238
|
+
* first-party services return a string.
|
|
239
|
+
*/
|
|
240
|
+
languageToServiceLanguage(language) {
|
|
241
|
+
return language;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Called when the assistant's turn completes with the aggregated reply text.
|
|
245
|
+
*
|
|
246
|
+
* Override in subclasses to react to each completed bot reply — for
|
|
247
|
+
* example, to feed the text to a provider-side context carryover API. The
|
|
248
|
+
* default implementation is a no-op.
|
|
249
|
+
*
|
|
250
|
+
* @param text The assistant's aggregated spoken text for this turn.
|
|
251
|
+
*/
|
|
252
|
+
async _processAssistantTurn(_text) { }
|
|
253
|
+
/**
|
|
254
|
+
* Set up the service.
|
|
255
|
+
*
|
|
256
|
+
* @param setup Configuration object containing setup parameters.
|
|
257
|
+
*/
|
|
258
|
+
async setup(setup) {
|
|
259
|
+
await super.setup(setup);
|
|
260
|
+
this._sampleRate = this._initSampleRate || setup.audioInSampleRate;
|
|
261
|
+
}
|
|
262
|
+
/** Clean up STT service resources. */
|
|
263
|
+
async cleanup() {
|
|
264
|
+
await super.cleanup();
|
|
265
|
+
await this._cancelTtfbTimeout();
|
|
266
|
+
await this._cancelKeepaliveTask();
|
|
267
|
+
this._reconnectAudioBuffer.length = 0;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Stop the STT service on a graceful end.
|
|
271
|
+
*
|
|
272
|
+
* @param frame The end frame.
|
|
273
|
+
*/
|
|
274
|
+
async stop(frame) {
|
|
275
|
+
await super.stop(frame);
|
|
276
|
+
await this._flushSttUsageMetrics();
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Cancel the STT service immediately.
|
|
280
|
+
*
|
|
281
|
+
* @param frame The cancel frame.
|
|
282
|
+
*/
|
|
283
|
+
async cancel(frame) {
|
|
284
|
+
await super.cancel(frame);
|
|
285
|
+
await this._flushSttUsageMetrics();
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Apply an STT settings delta.
|
|
289
|
+
*
|
|
290
|
+
* Handles `model` (via parent). Translates `Language` values before
|
|
291
|
+
* applying so the stored value is a service-specific string. Concrete
|
|
292
|
+
* services should override this method and handle language changes
|
|
293
|
+
* (including any reconnect logic) based on the returned changed-field
|
|
294
|
+
* record.
|
|
295
|
+
*
|
|
296
|
+
* @param delta An STT settings delta.
|
|
297
|
+
* @returns Record mapping changed field names to their previous values.
|
|
298
|
+
*/
|
|
299
|
+
async _updateSettings(delta) {
|
|
300
|
+
// Translate language *before* applying so the stored value is canonical.
|
|
301
|
+
// Raw strings are first checked against the Language codes for proper resolution.
|
|
302
|
+
const language = delta.language;
|
|
303
|
+
if (isGiven(language) && typeof language === "string" && !isLanguage(language)) {
|
|
304
|
+
logger.debug(`Language string '${language}' is not a recognized ` +
|
|
305
|
+
"Language code. It will be passed to the service as-is.");
|
|
306
|
+
}
|
|
307
|
+
if (isGiven(language) && typeof language === "string" && isLanguage(language)) {
|
|
308
|
+
const converted = this.languageToServiceLanguage(language);
|
|
309
|
+
if (converted !== null) {
|
|
310
|
+
delta.language = converted;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const changed = await super._updateSettings(delta);
|
|
314
|
+
return changed;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Process an audio frame for speech recognition.
|
|
318
|
+
*
|
|
319
|
+
* If a reconnect is in progress, the frame is buffered and replayed once
|
|
320
|
+
* the connection is restored. If the service is muted, the frame is
|
|
321
|
+
* dropped. Otherwise the frame is sent to the STT service and, if a userId
|
|
322
|
+
* is present, it is stored for use in transcription results.
|
|
323
|
+
*
|
|
324
|
+
* @param frame The audio frame to process.
|
|
325
|
+
* @param direction The direction of frame processing.
|
|
326
|
+
*/
|
|
327
|
+
async processAudioFrame(frame, direction) {
|
|
328
|
+
if (this._reconnecting) {
|
|
329
|
+
this._reconnectAudioBuffer.push([frame, direction]);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (this._muted) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
// A service that can no longer work can't transcribe anything, and
|
|
336
|
+
// services that connect on demand would attempt a handshake per chunk.
|
|
337
|
+
if (!this.isUsable) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
this._lastAudioTime = monotonic();
|
|
341
|
+
// UserAudioRawFrame contains a userId (e.g. Daily, Livekit)
|
|
342
|
+
if (frame instanceof UserAudioRawFrame) {
|
|
343
|
+
this._userId = frame.userId;
|
|
344
|
+
}
|
|
345
|
+
// InputAudioRawFrame does not have a userId (e.g. SmallWebRTCTransport, websockets)
|
|
346
|
+
else {
|
|
347
|
+
this._userId = "";
|
|
348
|
+
}
|
|
349
|
+
if (!frame.audio || frame.audio.length === 0) {
|
|
350
|
+
// Ignoring in case we don't have audio to transcribe.
|
|
351
|
+
logger.warning(`Empty audio frame received for STT service: ${this.name} ${frame.numFrames}`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
this._recordSttAudioUsage(frame.audio);
|
|
355
|
+
await this.processGenerator(this.runStt(frame.audio));
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Process frames, handling VAD events and audio segmentation.
|
|
359
|
+
*
|
|
360
|
+
* @param frame The frame to process.
|
|
361
|
+
* @param direction The direction of frame processing.
|
|
362
|
+
*/
|
|
363
|
+
async processFrame(frame, direction) {
|
|
364
|
+
await super.processFrame(frame, direction);
|
|
365
|
+
if (frame instanceof InputAudioRawFrame) {
|
|
366
|
+
// In this service we accumulate audio internally and at the end we
|
|
367
|
+
// push a TextFrame. We also push audio downstream in case someone
|
|
368
|
+
// else needs it.
|
|
369
|
+
await this.processAudioFrame(frame, direction);
|
|
370
|
+
if (this._audioPassthrough) {
|
|
371
|
+
await this.pushFrame(frame, direction);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else if (frame instanceof VADUserStartedSpeakingFrame) {
|
|
375
|
+
await this._handleVadUserStartedSpeaking(frame);
|
|
376
|
+
await this.pushFrame(frame, direction);
|
|
377
|
+
}
|
|
378
|
+
else if (frame instanceof VADUserStoppedSpeakingFrame) {
|
|
379
|
+
await this._handleVadUserStoppedSpeaking(frame);
|
|
380
|
+
await this.pushFrame(frame, direction);
|
|
381
|
+
}
|
|
382
|
+
else if (frame instanceof UserStoppedSpeakingFrame) {
|
|
383
|
+
await this._maybeReconnectOnUserStoppedSpeaking();
|
|
384
|
+
await this.pushFrame(frame, direction);
|
|
385
|
+
}
|
|
386
|
+
else if (frame instanceof STTUpdateSettingsFrame) {
|
|
387
|
+
if (frame.service !== null && frame.service !== this) {
|
|
388
|
+
await this.pushFrame(frame, direction);
|
|
389
|
+
}
|
|
390
|
+
else if (frame.delta !== null) {
|
|
391
|
+
await this._updateSettings(frame.delta);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else if (frame instanceof STTMuteFrame) {
|
|
395
|
+
this._muted = frame.mute;
|
|
396
|
+
logger.debug(`STT service ${frame.mute ? "muted" : "unmuted"}`);
|
|
397
|
+
}
|
|
398
|
+
else if (frame instanceof InterruptionFrame) {
|
|
399
|
+
await this._resetSttTtfbState();
|
|
400
|
+
await this.pushFrame(frame, direction);
|
|
401
|
+
}
|
|
402
|
+
else if (frame instanceof LLMContextAssistantTurnFrame) {
|
|
403
|
+
await this._processAssistantTurn(frame.text);
|
|
404
|
+
await this.pushFrame(frame, direction);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
await this.pushFrame(frame, direction);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Push a frame downstream, tracking TranscriptionFrame timestamps for TTFB.
|
|
412
|
+
*
|
|
413
|
+
* Stores the timestamp of each TranscriptionFrame for TTFB calculation. If
|
|
414
|
+
* the frame is marked as finalized (via requestFinalize/confirmFinalize),
|
|
415
|
+
* reports TTFB immediately and cancels any pending timeout. Otherwise, TTFB
|
|
416
|
+
* is reported after a timeout.
|
|
417
|
+
*
|
|
418
|
+
* @param frame The frame to push.
|
|
419
|
+
* @param direction The direction to push the frame.
|
|
420
|
+
*/
|
|
421
|
+
async pushFrame(frame, direction = FrameDirection.DOWNSTREAM) {
|
|
422
|
+
if (frame instanceof TranscriptionFrame) {
|
|
423
|
+
// Store the transcript time for TTFB calculation
|
|
424
|
+
this._lastTranscriptTime = time();
|
|
425
|
+
// Set finalized from pending state and auto-reset
|
|
426
|
+
if (this._finalizePending) {
|
|
427
|
+
frame.finalized = true;
|
|
428
|
+
this._finalizePending = false;
|
|
429
|
+
}
|
|
430
|
+
// If this is a finalized transcription, report TTFB immediately
|
|
431
|
+
if (frame.finalized) {
|
|
432
|
+
await this.stopTtfbMetrics();
|
|
433
|
+
// Cancel the timeout since we've already reported
|
|
434
|
+
await this._cancelTtfbTimeout();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
await super.pushFrame(frame, direction);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Whether this STT service has a meaningful TTFS-to-final-transcript metric.
|
|
441
|
+
*
|
|
442
|
+
* Returns false for turn-based STTs where the server defines the turn
|
|
443
|
+
* boundary, so there is no separate "speech end → final transcript"
|
|
444
|
+
* interval to measure. Downstream turn-stop strategies that consume
|
|
445
|
+
* STTMetadataFrame treat a 0 latency as "no extra wait."
|
|
446
|
+
*/
|
|
447
|
+
get supportsTtfs() {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Build the STT metadata frame broadcast at start.
|
|
452
|
+
*
|
|
453
|
+
* Overrides `AIService.serviceMetadataFrame()` to return an
|
|
454
|
+
* STTMetadataFrame carrying the service's TTFS P99 latency. A service that
|
|
455
|
+
* does its own server-side end-of-turn detection overrides this (calling
|
|
456
|
+
* `super`) to set `userTurnStrategies` on the returned frame.
|
|
457
|
+
*/
|
|
458
|
+
serviceMetadataFrame() {
|
|
459
|
+
let ttfs;
|
|
460
|
+
if (!this.supportsTtfs) {
|
|
461
|
+
ttfs = 0.0;
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
const latency = this._ttfsP99Latency;
|
|
465
|
+
if (latency === null) {
|
|
466
|
+
ttfs = DEFAULT_TTFS_P99;
|
|
467
|
+
logger.warning(`${this.name}: ttfs_p99_latency not set, using default ${ttfs}s`);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
ttfs = latency;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return new STTMetadataFrame({ serviceName: this.name, ttfsP99Latency: ttfs });
|
|
474
|
+
}
|
|
475
|
+
/** Cancel any pending TTFB timeout task. */
|
|
476
|
+
async _cancelTtfbTimeout() {
|
|
477
|
+
if (this._ttfbTimeoutTask) {
|
|
478
|
+
await this.cancelTask(this._ttfbTimeoutTask);
|
|
479
|
+
this._ttfbTimeoutTask = null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Reset STT TTFB measurement state.
|
|
484
|
+
*
|
|
485
|
+
* Called when starting a new utterance or on interruption to ensure we
|
|
486
|
+
* don't use stale state for TTFB calculations. This specifically guards
|
|
487
|
+
* against the case where a TranscriptionFrame is received without
|
|
488
|
+
* corresponding VADUserStartedSpeakingFrame and VADUserStoppedSpeakingFrame
|
|
489
|
+
* frames.
|
|
490
|
+
*
|
|
491
|
+
* Note: does not reset `_userSpeaking` since InterruptionFrame can arrive
|
|
492
|
+
* while user is still speaking.
|
|
493
|
+
*/
|
|
494
|
+
async _resetSttTtfbState() {
|
|
495
|
+
await this._cancelTtfbTimeout();
|
|
496
|
+
await this.cancelTtfbMetrics();
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Handle VAD user started speaking frame to start tracking transcriptions.
|
|
500
|
+
*
|
|
501
|
+
* Cancels any pending TTFB timeout, resets TTFB tracking state, and marks
|
|
502
|
+
* user as speaking. Also resets finalization state to prevent stale
|
|
503
|
+
* finalization from a previous utterance.
|
|
504
|
+
*
|
|
505
|
+
* @param frame The VAD user started speaking frame.
|
|
506
|
+
*/
|
|
507
|
+
async _handleVadUserStartedSpeaking(_frame) {
|
|
508
|
+
await this._resetSttTtfbState();
|
|
509
|
+
this._userSpeaking = true;
|
|
510
|
+
this._canReconnect = false;
|
|
511
|
+
this._finalizeRequested = false;
|
|
512
|
+
this._finalizePending = false;
|
|
513
|
+
this._lastTranscriptTime = 0;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Check if reconnection is needed after the user stops speaking.
|
|
517
|
+
*
|
|
518
|
+
* Called when the user's full turn has ended and the transcription has been
|
|
519
|
+
* received. Re-enables reconnection and triggers any deferred reconnect
|
|
520
|
+
* that was requested while the user was speaking.
|
|
521
|
+
*/
|
|
522
|
+
async _maybeReconnectOnUserStoppedSpeaking() {
|
|
523
|
+
this._canReconnect = true;
|
|
524
|
+
if (this._needReconnect) {
|
|
525
|
+
await this._reconnect();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Handle VAD user stopped speaking frame.
|
|
530
|
+
*
|
|
531
|
+
* Calculates the actual speech end time and starts a timeout task to wait
|
|
532
|
+
* for the final transcription before reporting TTFB.
|
|
533
|
+
*
|
|
534
|
+
* @param frame The VAD user stopped speaking frame.
|
|
535
|
+
*/
|
|
536
|
+
async _handleVadUserStoppedSpeaking(frame) {
|
|
537
|
+
this._userSpeaking = false;
|
|
538
|
+
// Skip TTFB measurement if stopSecs is not set
|
|
539
|
+
if (frame.stopSecs === 0.0) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// Calculate the actual speech end time (current time minus VAD stop delay).
|
|
543
|
+
// This approximates when the last user audio was sent to the STT service,
|
|
544
|
+
// which we use to measure against the eventual transcription response.
|
|
545
|
+
const speechEndTime = frame.timestamp - frame.stopSecs;
|
|
546
|
+
await this.startTtfbMetrics({ startTime: speechEndTime });
|
|
547
|
+
// Start timeout task (any previous timeout was cancelled by VADUserStartedSpeakingFrame
|
|
548
|
+
// or InterruptionFrame)
|
|
549
|
+
this._ttfbTimeoutTask = this.createTask(() => this._ttfbTimeoutHandler(), "stt_ttfb_timeout");
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Wait for timeout then report TTFB using the last transcript timestamp.
|
|
553
|
+
*
|
|
554
|
+
* This timeout allows the final transcription to arrive before we
|
|
555
|
+
* calculate and report TTFB. Uses `_lastTranscriptTime` as the end time so
|
|
556
|
+
* we measure to when the transcript actually arrived, not when the timeout
|
|
557
|
+
* fired.
|
|
558
|
+
*
|
|
559
|
+
* A transcript that predates the end of speech belongs to an earlier
|
|
560
|
+
* segment the service finalized on its own endpointing; the metrics
|
|
561
|
+
* collector refuses it rather than report the negative interval it would
|
|
562
|
+
* produce.
|
|
563
|
+
*/
|
|
564
|
+
async _ttfbTimeoutHandler() {
|
|
565
|
+
try {
|
|
566
|
+
await sleep(this._sttTtfbTimeout);
|
|
567
|
+
if (this._lastTranscriptTime > 0) {
|
|
568
|
+
await this.stopTtfbMetrics({ endTime: this._lastTranscriptTime });
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
// No transcript at all, so there is no end time to measure to.
|
|
572
|
+
// Close the measurement rather than leave it open for the next
|
|
573
|
+
// transcript to be measured against.
|
|
574
|
+
await this.cancelTtfbMetrics();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
finally {
|
|
578
|
+
// A cancellation here (new utterance or interruption) is expected
|
|
579
|
+
// behavior; it propagates so the task ends as cancelled.
|
|
580
|
+
this._ttfbTimeoutTask = null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/** Start the keepalive task if keepalive is enabled. */
|
|
584
|
+
_createKeepaliveTask() {
|
|
585
|
+
if (this._keepaliveTimeout !== null) {
|
|
586
|
+
this._lastAudioTime = monotonic();
|
|
587
|
+
this._keepaliveTask = this.createTask(() => this._keepaliveTaskHandler(), "keepalive");
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/** Stop the keepalive task if running. */
|
|
591
|
+
async _cancelKeepaliveTask() {
|
|
592
|
+
if (this._keepaliveTask) {
|
|
593
|
+
await this.cancelTask(this._keepaliveTask);
|
|
594
|
+
this._keepaliveTask = null;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Perform a full reconnect cycle with audio buffering.
|
|
599
|
+
*
|
|
600
|
+
* Sets `_reconnecting` so incoming audio frames are buffered rather than
|
|
601
|
+
* sent to a dead connection. Delegates the actual connection reset to
|
|
602
|
+
* `_doReconnect()`. After the new connection is established all buffered
|
|
603
|
+
* frames are replayed. On failure the error is reported via `pushError`
|
|
604
|
+
* and the `on_connection_error` event handler.
|
|
605
|
+
*/
|
|
606
|
+
async _reconnect() {
|
|
607
|
+
logger.info(`${this} reconnecting...`);
|
|
608
|
+
this._reconnectAudioBuffer.length = 0;
|
|
609
|
+
this._reconnecting = true;
|
|
610
|
+
this._needReconnect = false;
|
|
611
|
+
try {
|
|
612
|
+
await this._doReconnect();
|
|
613
|
+
}
|
|
614
|
+
catch (e) {
|
|
615
|
+
rethrowIfCancelled(e);
|
|
616
|
+
logger.error(`${this} reconnect failed: ${errorMessage(e)}`);
|
|
617
|
+
await this._callEventHandler("on_connection_error", errorMessage(e));
|
|
618
|
+
await this.pushError(`${this} reconnect failed: ${errorMessage(e)}`, { exception: e });
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
finally {
|
|
622
|
+
this._reconnecting = false;
|
|
623
|
+
}
|
|
624
|
+
// Replay audio frames that arrived while the connection was down.
|
|
625
|
+
for (const [bufferedFrame, bufferedDirection] of this._reconnectAudioBuffer) {
|
|
626
|
+
await this.processAudioFrame(bufferedFrame, bufferedDirection);
|
|
627
|
+
}
|
|
628
|
+
this._reconnectAudioBuffer.length = 0;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Perform the service-specific connection reset.
|
|
632
|
+
*
|
|
633
|
+
* Called by `_reconnect()` inside the reconnecting guard. The default
|
|
634
|
+
* implementation is a no-op. Subclasses that support explicit reconnection
|
|
635
|
+
* (e.g. WebsocketSTTService) should override this to tear down and
|
|
636
|
+
* re-establish their connection.
|
|
637
|
+
*/
|
|
638
|
+
async _doReconnect() { }
|
|
639
|
+
/**
|
|
640
|
+
* Reconnect immediately if safe, or defer until after the current user turn.
|
|
641
|
+
*
|
|
642
|
+
* Reconnection is unsafe while the user is speaking because the service is
|
|
643
|
+
* actively receiving audio. Calling this method while the user is speaking
|
|
644
|
+
* schedules a reconnect that fires as soon as `UserStoppedSpeakingFrame` is
|
|
645
|
+
* received.
|
|
646
|
+
*/
|
|
647
|
+
async _requestReconnect() {
|
|
648
|
+
logger.debug(`${this} requesting to reconnect!`);
|
|
649
|
+
if (this._canReconnect) {
|
|
650
|
+
await this._reconnect();
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
this._needReconnect = true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Send periodic silent audio to prevent the server from closing the
|
|
658
|
+
* connection.
|
|
659
|
+
*
|
|
660
|
+
* When keepalive is enabled, this task checks periodically if the
|
|
661
|
+
* connection has been idle (no audio sent) for longer than
|
|
662
|
+
* keepaliveTimeout seconds. If so, it generates silent 16-bit mono PCM
|
|
663
|
+
* audio and passes it to `_sendKeepalive()` for service-specific formatting
|
|
664
|
+
* and sending. A failed send is logged and the loop keeps going.
|
|
665
|
+
*/
|
|
666
|
+
async _keepaliveTaskHandler() {
|
|
667
|
+
// This task is only started when a keepalive timeout is configured.
|
|
668
|
+
const keepaliveTimeout = this._keepaliveTimeout;
|
|
669
|
+
if (keepaliveTimeout === null) {
|
|
670
|
+
throw new Error("keepalive task started without a keepalive timeout");
|
|
671
|
+
}
|
|
672
|
+
while (true) {
|
|
673
|
+
await sleep(this._keepaliveInterval);
|
|
674
|
+
try {
|
|
675
|
+
if (!this._isKeepaliveReady()) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const elapsed = monotonic() - this._lastAudioTime;
|
|
679
|
+
if (elapsed < keepaliveTimeout) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const numSamples = Math.trunc(this.sampleRate * KEEPALIVE_SILENCE_DURATION);
|
|
683
|
+
const silence = new Uint8Array(numSamples * 2);
|
|
684
|
+
await this._sendKeepalive(silence);
|
|
685
|
+
this._lastAudioTime = monotonic();
|
|
686
|
+
logger.trace(`${this} sent keepalive silence`);
|
|
687
|
+
}
|
|
688
|
+
catch (e) {
|
|
689
|
+
rethrowIfCancelled(e);
|
|
690
|
+
logger.warning(`${this} keepalive error: ${errorMessage(e)}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Check if the service is ready to send keepalive.
|
|
696
|
+
*
|
|
697
|
+
* Subclasses should override this to check their connection state.
|
|
698
|
+
*
|
|
699
|
+
* @returns True if keepalive can be sent.
|
|
700
|
+
*/
|
|
701
|
+
_isKeepaliveReady() {
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Send silent audio to keep the connection alive.
|
|
706
|
+
*
|
|
707
|
+
* Subclasses that enable keepalive must override this to deliver silence in
|
|
708
|
+
* their service-specific protocol.
|
|
709
|
+
*
|
|
710
|
+
* @param silence Silent 16-bit mono PCM audio bytes.
|
|
711
|
+
*/
|
|
712
|
+
async _sendKeepalive(_silence) {
|
|
713
|
+
throw new Error("Subclasses must override _sendKeepalive");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* STT service that processes speech in segments using VAD events.
|
|
718
|
+
*
|
|
719
|
+
* Uses Voice Activity Detection (VAD) events to detect speech segments and
|
|
720
|
+
* runs speech-to-text only on those segments, rather than continuously.
|
|
721
|
+
*
|
|
722
|
+
* Requires VAD to be enabled in the pipeline to function properly. Maintains
|
|
723
|
+
* a small audio buffer to account for the delay between actual speech start
|
|
724
|
+
* and VAD detection.
|
|
725
|
+
*
|
|
726
|
+
* The buffered segment is passed to `runStt` as a WAV container by default,
|
|
727
|
+
* which is what cloud providers want for their upload APIs. Local models that
|
|
728
|
+
* consume raw 16-bit PCM directly override `wantsWavSegments` to return false
|
|
729
|
+
* so they receive the unwrapped buffer instead. This is a subclass-level
|
|
730
|
+
* contract, not a user-configurable option: the format is dictated by what
|
|
731
|
+
* the model expects.
|
|
732
|
+
*
|
|
733
|
+
* A segment ends right where the VAD stopped, and models tend to drop or
|
|
734
|
+
* garble the final word when the audio ends that abruptly, so each segment is
|
|
735
|
+
* padded with `trailingSilenceSecs` of silence before transcription.
|
|
736
|
+
*
|
|
737
|
+
* Transcription runs off the audio path. When the VAD stops, the segment is
|
|
738
|
+
* queued and one background task transcribes queued segments in order,
|
|
739
|
+
* pushing each transcript as it completes; meanwhile audio and other frames
|
|
740
|
+
* keep flowing through the service, as they do through a streaming STT whose
|
|
741
|
+
* transcripts arrive asynchronously. A graceful stop transcribes what is
|
|
742
|
+
* queued before the service stops; a cancel drops it.
|
|
743
|
+
*/
|
|
744
|
+
export class SegmentedSTTService extends STTService {
|
|
745
|
+
_trailingSilenceSecs;
|
|
746
|
+
_content = null;
|
|
747
|
+
_wave = null;
|
|
748
|
+
_audioBuffer = new Uint8Array(0);
|
|
749
|
+
_audioBufferSize1s = 0;
|
|
750
|
+
// Segments awaiting transcription, drained in order by _segmentTask;
|
|
751
|
+
// null tells the task to finish.
|
|
752
|
+
_segmentQueue = new Queue();
|
|
753
|
+
_segmentTask = null;
|
|
754
|
+
/**
|
|
755
|
+
* Initialize the segmented STT service.
|
|
756
|
+
*
|
|
757
|
+
* @param options.sampleRate The sample rate for audio input. If null, will
|
|
758
|
+
* be determined from the start frame.
|
|
759
|
+
* @param options.trailingSilenceSecs Seconds of silence appended to each
|
|
760
|
+
* segment before it is transcribed, so the model hears the end of speech
|
|
761
|
+
* and can finish the last word. Set to 0 to disable. Other options are
|
|
762
|
+
* passed to the parent STTService.
|
|
763
|
+
*/
|
|
764
|
+
constructor(options = {}) {
|
|
765
|
+
super(options);
|
|
766
|
+
this._trailingSilenceSecs = options.trailingSilenceSecs ?? 0.5;
|
|
767
|
+
this._userSpeaking = false;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Set up the service.
|
|
771
|
+
*
|
|
772
|
+
* @param setup Configuration object containing setup parameters.
|
|
773
|
+
*/
|
|
774
|
+
async setup(setup) {
|
|
775
|
+
await super.setup(setup);
|
|
776
|
+
this._audioBufferSize1s = this.sampleRate * 2;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Start the service and its transcription task.
|
|
780
|
+
*
|
|
781
|
+
* @param frame The start frame.
|
|
782
|
+
*/
|
|
783
|
+
async start(frame) {
|
|
784
|
+
await super.start(frame);
|
|
785
|
+
this._segmentTask = this.createTask(() => this._segmentTaskHandler());
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Transcribe the queued segments, then stop the service.
|
|
789
|
+
*
|
|
790
|
+
* @param frame The end frame.
|
|
791
|
+
*/
|
|
792
|
+
async stop(frame) {
|
|
793
|
+
if (this._segmentTask !== null) {
|
|
794
|
+
await this._segmentQueue.put(null);
|
|
795
|
+
await this._segmentTask;
|
|
796
|
+
this._segmentTask = null;
|
|
797
|
+
}
|
|
798
|
+
await super.stop(frame);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Stop the service at once, dropping any queued segments.
|
|
802
|
+
*
|
|
803
|
+
* @param frame The cancel frame.
|
|
804
|
+
*/
|
|
805
|
+
async cancel(frame) {
|
|
806
|
+
await this._cancelSegmentTask();
|
|
807
|
+
await super.cancel(frame);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Whether segments are passed to `runStt` as a WAV container.
|
|
811
|
+
*
|
|
812
|
+
* Returns true (the default) for cloud providers whose upload APIs expect a
|
|
813
|
+
* WAV file. Local models that read the buffer as raw 16-bit PCM override
|
|
814
|
+
* this to return false; otherwise they would misinterpret the 44-byte WAV
|
|
815
|
+
* header as audio samples.
|
|
816
|
+
*/
|
|
817
|
+
get wantsWavSegments() {
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Push a frame, marking TranscriptionFrames as finalized.
|
|
822
|
+
*
|
|
823
|
+
* Segmented STT services process complete speech segments and return a
|
|
824
|
+
* single TranscriptionFrame per segment, so every transcription is
|
|
825
|
+
* inherently finalized.
|
|
826
|
+
*
|
|
827
|
+
* @param frame The frame to push.
|
|
828
|
+
* @param direction The direction of frame flow in the pipeline.
|
|
829
|
+
*/
|
|
830
|
+
async pushFrame(frame, direction = FrameDirection.DOWNSTREAM) {
|
|
831
|
+
if (frame instanceof TranscriptionFrame) {
|
|
832
|
+
frame.finalized = true;
|
|
833
|
+
}
|
|
834
|
+
await super.pushFrame(frame, direction);
|
|
835
|
+
}
|
|
836
|
+
/** Process frames, handling VAD events and audio segmentation. */
|
|
837
|
+
async processFrame(frame, direction) {
|
|
838
|
+
await super.processFrame(frame, direction);
|
|
839
|
+
if (frame instanceof VADUserStartedSpeakingFrame) {
|
|
840
|
+
await this._handleUserStartedSpeaking(frame);
|
|
841
|
+
}
|
|
842
|
+
else if (frame instanceof VADUserStoppedSpeakingFrame) {
|
|
843
|
+
await this._handleUserStoppedSpeaking(frame);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
async _handleUserStartedSpeaking(_frame) {
|
|
847
|
+
this._userSpeaking = true;
|
|
848
|
+
}
|
|
849
|
+
async _handleUserStoppedSpeaking(_frame) {
|
|
850
|
+
this._userSpeaking = false;
|
|
851
|
+
// A service that can no longer work can't transcribe this segment.
|
|
852
|
+
if (!this.isUsable) {
|
|
853
|
+
this._audioBuffer = new Uint8Array(0);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const trailing = this._trailingSilence();
|
|
857
|
+
const pcm = new Uint8Array(this._audioBuffer.length + trailing.length);
|
|
858
|
+
pcm.set(this._audioBuffer, 0);
|
|
859
|
+
pcm.set(trailing, this._audioBuffer.length);
|
|
860
|
+
this._audioBuffer = new Uint8Array(0);
|
|
861
|
+
// The padded segment is what the provider receives, so it is what
|
|
862
|
+
// usage measures. Usage is reported before transcription so tracing
|
|
863
|
+
// can attach it to the STT span the resulting TranscriptionFrame closes.
|
|
864
|
+
this._recordSttAudioUsage(pcm);
|
|
865
|
+
await this.emitSttUsageMetrics();
|
|
866
|
+
let audio;
|
|
867
|
+
if (this.wantsWavSegments) {
|
|
868
|
+
audio = pcmToWav(pcm, this.sampleRate);
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
// Local models read the buffer as raw 16-bit PCM; wrapping it in a
|
|
872
|
+
// WAV container would make them misread the 44-byte header as audio.
|
|
873
|
+
audio = pcm;
|
|
874
|
+
}
|
|
875
|
+
await this._segmentQueue.put(audio);
|
|
876
|
+
}
|
|
877
|
+
/** Transcribe queued segments one at a time, in order, until told to finish. */
|
|
878
|
+
async _segmentTaskHandler() {
|
|
879
|
+
let running = true;
|
|
880
|
+
while (running) {
|
|
881
|
+
const audio = await this._segmentQueue.get();
|
|
882
|
+
running = audio !== null;
|
|
883
|
+
if (audio && audio.length > 0) {
|
|
884
|
+
try {
|
|
885
|
+
await this.processGenerator(this.runStt(audio));
|
|
886
|
+
}
|
|
887
|
+
catch (e) {
|
|
888
|
+
rethrowIfCancelled(e);
|
|
889
|
+
await this.pushError(`${this}: transcription failed: ${errorMessage(e)}`, {
|
|
890
|
+
exception: e,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async _cancelSegmentTask() {
|
|
897
|
+
if (this._segmentTask !== null) {
|
|
898
|
+
await this.cancelTask(this._segmentTask);
|
|
899
|
+
this._segmentTask = null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/** Silence appended to a segment, as 16-bit mono PCM at the service sample rate. */
|
|
903
|
+
_trailingSilence() {
|
|
904
|
+
const numSamples = Math.max(0, Math.trunc(this.sampleRate * this._trailingSilenceSecs));
|
|
905
|
+
return new Uint8Array(numSamples * 2);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Process audio frames by buffering them for segmented transcription.
|
|
909
|
+
*
|
|
910
|
+
* Continuously buffers audio, growing the buffer while user is speaking and
|
|
911
|
+
* maintaining a small buffer when not speaking to account for VAD delay.
|
|
912
|
+
*
|
|
913
|
+
* If the frame has a userId, it is stored for later use in transcription.
|
|
914
|
+
*
|
|
915
|
+
* @param frame The audio frame to process.
|
|
916
|
+
* @param direction The direction of frame processing.
|
|
917
|
+
*/
|
|
918
|
+
async processAudioFrame(frame, _direction) {
|
|
919
|
+
// UserAudioRawFrame contains a userId (e.g. Daily, Livekit)
|
|
920
|
+
if (frame instanceof UserAudioRawFrame) {
|
|
921
|
+
this._userId = frame.userId;
|
|
922
|
+
}
|
|
923
|
+
// InputAudioRawFrame does not have a userId (e.g. SmallWebRTCTransport, websockets)
|
|
924
|
+
else {
|
|
925
|
+
this._userId = "";
|
|
926
|
+
}
|
|
927
|
+
// If the user is speaking the audio buffer will keep growing.
|
|
928
|
+
const grown = new Uint8Array(this._audioBuffer.length + frame.audio.length);
|
|
929
|
+
grown.set(this._audioBuffer, 0);
|
|
930
|
+
grown.set(frame.audio, this._audioBuffer.length);
|
|
931
|
+
this._audioBuffer = grown;
|
|
932
|
+
// If the user is not speaking we keep just a little bit of audio.
|
|
933
|
+
if (!this._userSpeaking && this._audioBuffer.length > this._audioBufferSize1s) {
|
|
934
|
+
const discarded = this._audioBuffer.length - this._audioBufferSize1s;
|
|
935
|
+
this._audioBuffer = this._audioBuffer.slice(discarded);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Base class for websocket-based STT services.
|
|
941
|
+
*
|
|
942
|
+
* Combines STT functionality with websocket connectivity, providing
|
|
943
|
+
* automatic error handling, reconnection capabilities, and optional
|
|
944
|
+
* silence-based keepalive.
|
|
945
|
+
*
|
|
946
|
+
* The keepalive feature (inherited from STTService) sends silent audio when
|
|
947
|
+
* no real audio has been sent for a configurable timeout, preventing servers
|
|
948
|
+
* from closing idle connections (e.g. when behind a ServiceSwitcher).
|
|
949
|
+
* Subclasses can override `_sendKeepalive()` to wrap the silence in a
|
|
950
|
+
* service-specific protocol.
|
|
951
|
+
*/
|
|
952
|
+
export class WebsocketSTTService extends WebsocketService(STTService) {
|
|
953
|
+
/**
|
|
954
|
+
* Initialize the Websocket STT service.
|
|
955
|
+
*
|
|
956
|
+
* @param options.reconnectOnError Whether to automatically reconnect on
|
|
957
|
+
* websocket errors. Other options are passed to the parent classes
|
|
958
|
+
* (including keepaliveTimeout and keepaliveInterval for STTService).
|
|
959
|
+
*/
|
|
960
|
+
constructor(options = {}) {
|
|
961
|
+
const merged = {
|
|
962
|
+
...options,
|
|
963
|
+
reconnectOnError: options.reconnectOnError ?? true,
|
|
964
|
+
};
|
|
965
|
+
super(merged);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Stop the websocket STT service on a graceful end.
|
|
969
|
+
*
|
|
970
|
+
* @param frame The end frame.
|
|
971
|
+
*/
|
|
972
|
+
async stop(frame) {
|
|
973
|
+
await super.stop(frame);
|
|
974
|
+
await this._disconnect();
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Cancel the websocket STT service immediately.
|
|
978
|
+
*
|
|
979
|
+
* Disconnecting here is the prompt teardown: the websocket receive loop
|
|
980
|
+
* runs independently and keeps reading transcripts from the provider until
|
|
981
|
+
* the socket is closed.
|
|
982
|
+
*
|
|
983
|
+
* @param frame The cancel frame.
|
|
984
|
+
*/
|
|
985
|
+
async cancel(frame) {
|
|
986
|
+
await super.cancel(frame);
|
|
987
|
+
await this._disconnect();
|
|
988
|
+
}
|
|
989
|
+
/** Release websocket STT resources at teardown. */
|
|
990
|
+
async cleanup() {
|
|
991
|
+
await super.cleanup();
|
|
992
|
+
await this._disconnect();
|
|
993
|
+
}
|
|
994
|
+
/** Connect and start keepalive task if enabled. */
|
|
995
|
+
async _connect() {
|
|
996
|
+
await super._connect();
|
|
997
|
+
this._createKeepaliveTask();
|
|
998
|
+
}
|
|
999
|
+
/** Disconnect and cancel keepalive task. */
|
|
1000
|
+
async _disconnect() {
|
|
1001
|
+
await super._disconnect();
|
|
1002
|
+
await this._cancelKeepaliveTask();
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Disconnect and reconnect the websocket.
|
|
1006
|
+
*
|
|
1007
|
+
* Called by `STTService._reconnect()` inside the reconnecting guard. Tears
|
|
1008
|
+
* down the current websocket connection and re-establishes it. Keepalive
|
|
1009
|
+
* management is handled by `_connect` / `_disconnect`.
|
|
1010
|
+
*/
|
|
1011
|
+
async _doReconnect() {
|
|
1012
|
+
await this._disconnect();
|
|
1013
|
+
await this._connect();
|
|
1014
|
+
}
|
|
1015
|
+
/** Check if the websocket is open and ready for keepalive. */
|
|
1016
|
+
_isKeepaliveReady() {
|
|
1017
|
+
return this._websocket !== null && this._websocket.state === State.OPEN;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Send silent audio over the websocket to keep the connection alive.
|
|
1021
|
+
*
|
|
1022
|
+
* The default implementation sends raw PCM bytes directly. Subclasses can
|
|
1023
|
+
* override this to wrap the silence in a service-specific protocol.
|
|
1024
|
+
*
|
|
1025
|
+
* @param silence Silent 16-bit mono PCM audio bytes.
|
|
1026
|
+
*/
|
|
1027
|
+
async _sendKeepalive(silence) {
|
|
1028
|
+
if (this._websocket === null) {
|
|
1029
|
+
// should never happen — caller should gate on _isKeepaliveReady()
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
await this._websocket.send(silence);
|
|
1033
|
+
// Keepalive silence is real audio submitted to the service, so it
|
|
1034
|
+
// counts toward usage.
|
|
1035
|
+
this._recordSttAudioUsage(silence);
|
|
1036
|
+
}
|
|
1037
|
+
async _reportError(error, options = {}) {
|
|
1038
|
+
await this._callEventHandler("on_connection_error", error.error);
|
|
1039
|
+
await this.pushErrorFrame(error, {
|
|
1040
|
+
forceTreatAsPermanent: options.forceTreatAsPermanent ?? false,
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
//# sourceMappingURL=stt_service.js.map
|