@pipedog/core 0.0.0 → 0.1.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/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/soxr_resampler.d.ts +54 -0
- package/dist/audio/resamplers/soxr_resampler.js +69 -0
- package/dist/audio/resamplers/soxr_stream_resampler.d.ts +75 -0
- package/dist/audio/resamplers/soxr_stream_resampler.js +146 -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 +143 -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 +126 -0
- package/dist/audio/vad/vad_analyzer.js +211 -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 +814 -0
- package/dist/pipeline/worker.js +1639 -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 +99 -0
- package/dist/utils/file_storage.js +100 -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 +15 -0
- package/dist/utils/image.js +30 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +8 -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/pronunciations.d.ts +65 -0
- package/dist/utils/text/transforms/pronunciations.js +111 -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 +95 -0
- package/dist/utils/tracing/setup.js +110 -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 +90 -0
- package/dist/utils/tracing/turn_trace_observer.js +219 -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 +64 -0
- package/dist/utils/yaml.js +41 -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/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 +46 -3
- package/README.md +0 -5
|
@@ -0,0 +1,803 @@
|
|
|
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/utils/context/aggregated_frame_sequencer.py
|
|
6
|
+
/** Ordered sequencer for AggregatedTextFrame slots through TTS processing. */
|
|
7
|
+
import { AggregatedTextFrame, AggregatedTextProgressFrame, TextType, TTSTextFrame, } from "../../frames/frames.js";
|
|
8
|
+
import { logger } from "../logger.js";
|
|
9
|
+
import { matchEndofsentence } from "../string.js";
|
|
10
|
+
import { SimpleTextAggregator } from "../text/simple_text_aggregator.js";
|
|
11
|
+
import { WordCompletionTracker } from "./word_completion_tracker.js";
|
|
12
|
+
/** Python's `repr()` of a string, for log lines. */
|
|
13
|
+
function repr(text) {
|
|
14
|
+
return `'${text.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n")}'`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Internal: groups streamed tokens back into sentences for the sequencer.
|
|
18
|
+
*
|
|
19
|
+
* Used by `AggregatedFrameSequencer` when a TTS service streams tokens
|
|
20
|
+
* individually (`TextAggregationMode.TOKEN`) but still needs whole-sentence
|
|
21
|
+
* units for word-timestamp tracking and RTVI progress. Each token contributes
|
|
22
|
+
* one part to each of the three channels (tts / llm / user-facing); sentence
|
|
23
|
+
* completion is driven by the **TTS text** and completes all three together.
|
|
24
|
+
*
|
|
25
|
+
* Boundary timing: a sentence-ending boundary is only *confirmed* by
|
|
26
|
+
* lookahead — the first non-whitespace character of the *next* sentence. So
|
|
27
|
+
* the underlying `SimpleTextAggregator` only yields "Hi there!" once the
|
|
28
|
+
* following non-whitespace character has arrived.
|
|
29
|
+
*
|
|
30
|
+
* Splitting granularity: token streams are not guaranteed to be one word or
|
|
31
|
+
* one punctuation mark per token — an upstream can deliver a coarse chunk that
|
|
32
|
+
* carries the tail of one sentence *and* the head of the next (e.g. "Hey" then
|
|
33
|
+
* " there! I'm ..."). Two cases:
|
|
34
|
+
*
|
|
35
|
+
* - When every token accumulated for the pending sentence has identical text
|
|
36
|
+
* in all three channels (the common case — no TTS transform/tag rewriting is
|
|
37
|
+
* active), a boundary is sliced *inside* the triggering token at its
|
|
38
|
+
* confirmed offset, so " there!" stays with "Hey" and only " I'm ..."
|
|
39
|
+
* carries over.
|
|
40
|
+
* - When a transform has made the channels differ in length, the channels can
|
|
41
|
+
* no longer be split at a shared character offset, so the boundary is cut at
|
|
42
|
+
* the token boundary instead: the text accumulated *before* the triggering
|
|
43
|
+
* token is emitted and the whole triggering token begins the next sentence's
|
|
44
|
+
* buffer.
|
|
45
|
+
*/
|
|
46
|
+
export class _ParallelSentenceAggregator {
|
|
47
|
+
_aggregator;
|
|
48
|
+
// Tokens accumulated since the last emitted sentence, per channel.
|
|
49
|
+
_tts = "";
|
|
50
|
+
_llm = "";
|
|
51
|
+
_user = "";
|
|
52
|
+
// Whether every token accumulated for the pending sentence has identical
|
|
53
|
+
// text across the three channels. While true the tts buffer mirrors the
|
|
54
|
+
// inner aggregator's buffer, so a boundary can be sliced inside a token.
|
|
55
|
+
_aligned = true;
|
|
56
|
+
/**
|
|
57
|
+
* Initialize empty channels using the context's sentence language.
|
|
58
|
+
*
|
|
59
|
+
* @param language Language code for this context.
|
|
60
|
+
*/
|
|
61
|
+
constructor(language = null) {
|
|
62
|
+
// A plain SENTENCE-mode aggregator drives boundary detection on the TTS
|
|
63
|
+
// text. The TTS text is already post-transform, so tag/pattern-aware
|
|
64
|
+
// boundary rules are not needed here.
|
|
65
|
+
this._aggregator = new SimpleTextAggregator({
|
|
66
|
+
aggregationType: TextType.SENTENCE,
|
|
67
|
+
language,
|
|
68
|
+
});
|
|
69
|
+
this._reset();
|
|
70
|
+
}
|
|
71
|
+
/** Use the supplied language for subsequent text without clearing buffers. */
|
|
72
|
+
setLanguage(language) {
|
|
73
|
+
this._aggregator.setLanguage(language);
|
|
74
|
+
}
|
|
75
|
+
_reset() {
|
|
76
|
+
this._tts = "";
|
|
77
|
+
this._llm = "";
|
|
78
|
+
this._user = "";
|
|
79
|
+
this._aligned = true;
|
|
80
|
+
}
|
|
81
|
+
/** The inner aggregator's buffer (its protected `_text`). */
|
|
82
|
+
get _aggregatorText() {
|
|
83
|
+
return this._aggregator._text;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Feed one token (all three channels) and yield any completed sentences.
|
|
87
|
+
*
|
|
88
|
+
* @param ttsText The token as sent to the TTS service.
|
|
89
|
+
* @param llmText The token in the original LLM text.
|
|
90
|
+
* @param userFacingText The token as shown to the user.
|
|
91
|
+
* @returns A `_ParallelAggregation` for each sentence this token confirms.
|
|
92
|
+
* Usually zero or one, but a coarse chunk carrying several sentence
|
|
93
|
+
* endings can confirm more than one at once.
|
|
94
|
+
*/
|
|
95
|
+
async *aggregate(ttsText, llmText, userFacingText) {
|
|
96
|
+
const tokenIdentical = ttsText === llmText && llmText === userFacingText;
|
|
97
|
+
// The inner SENTENCE aggregator is the source of truth for *when* and *how
|
|
98
|
+
// many* boundaries are confirmed (it applies the lookahead rule char by
|
|
99
|
+
// char on the TTS text).
|
|
100
|
+
let boundaryCount = 0;
|
|
101
|
+
for await (const _ of this._aggregator.aggregate(ttsText)) {
|
|
102
|
+
boundaryCount += 1;
|
|
103
|
+
}
|
|
104
|
+
if (boundaryCount && this._aligned && tokenIdentical) {
|
|
105
|
+
// Channels are identical for the whole pending sentence, so the tts
|
|
106
|
+
// buffer mirrors the inner aggregator's buffer and we can slice inside
|
|
107
|
+
// this token at each confirmed boundary, keeping the channels aligned.
|
|
108
|
+
const combined = this._tts + ttsText;
|
|
109
|
+
let idx = 0;
|
|
110
|
+
for (let n = 0; n < boundaryCount; n++) {
|
|
111
|
+
const boundary = matchEndofsentence(combined.slice(idx), {
|
|
112
|
+
language: this._aggregator.language,
|
|
113
|
+
});
|
|
114
|
+
if (boundary <= 0) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
const sentence = combined.slice(idx, idx + boundary);
|
|
118
|
+
yield { ttsText: sentence, llmText: sentence, userFacingText: sentence };
|
|
119
|
+
idx += boundary;
|
|
120
|
+
}
|
|
121
|
+
// The remainder came from identical channels, so the next pending
|
|
122
|
+
// sentence starts aligned and its buffer still mirrors the inner one.
|
|
123
|
+
const rest = combined.slice(idx);
|
|
124
|
+
this._tts = rest;
|
|
125
|
+
this._llm = rest;
|
|
126
|
+
this._user = rest;
|
|
127
|
+
this._aligned = true;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// A transform has diverged the channels (or nothing is buffered before this
|
|
131
|
+
// token): we can only cut at the token boundary. Emit whatever was buffered
|
|
132
|
+
// before this token and let this token begin the next sentence's buffer.
|
|
133
|
+
if (boundaryCount && (this._tts || this._llm || this._user)) {
|
|
134
|
+
yield { ttsText: this._tts, llmText: this._llm, userFacingText: this._user };
|
|
135
|
+
this._tts = "";
|
|
136
|
+
this._llm = "";
|
|
137
|
+
this._user = "";
|
|
138
|
+
}
|
|
139
|
+
// Plain concatenation: LLM tokens already carry their own spacing.
|
|
140
|
+
this._tts += ttsText;
|
|
141
|
+
this._llm += llmText;
|
|
142
|
+
this._user += userFacingText;
|
|
143
|
+
// Re-derive alignment from ground truth rather than assuming a token-boundary
|
|
144
|
+
// cut restores it.
|
|
145
|
+
this._aligned =
|
|
146
|
+
this._tts === this._llm && this._llm === this._user && this._tts === this._aggregatorText;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Emit any trailing partial sentence at end of turn.
|
|
150
|
+
*
|
|
151
|
+
* @returns A `_ParallelAggregation` for the accumulated-but-unemitted text,
|
|
152
|
+
* or `null` when nothing substantive is buffered.
|
|
153
|
+
*/
|
|
154
|
+
async flush() {
|
|
155
|
+
await this._aggregator.flush();
|
|
156
|
+
if (this._user.trim()) {
|
|
157
|
+
const result = { ttsText: this._tts, llmText: this._llm, userFacingText: this._user };
|
|
158
|
+
this._reset();
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
/** Discard all buffered text (called on interruption/reset). */
|
|
164
|
+
async handleInterruption() {
|
|
165
|
+
await this._aggregator.handleInterruption();
|
|
166
|
+
this._reset();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function newSlot(options) {
|
|
170
|
+
return {
|
|
171
|
+
tracker: null,
|
|
172
|
+
transportDestination: null,
|
|
173
|
+
complete: false,
|
|
174
|
+
includesInterFrameSpaces: false,
|
|
175
|
+
...options,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Sequences AggregatedTextFrame slots to preserve TTS context ordering.
|
|
180
|
+
*
|
|
181
|
+
* Manages an ordered queue of spoken and skipped TTS slots. Spoken slots are
|
|
182
|
+
* tracked via a `WordCompletionTracker`; skipped slots (e.g. code blocks
|
|
183
|
+
* excluded from TTS synthesis) wait in-place until all preceding spoken slots
|
|
184
|
+
* are complete, then are flushed downstream with `appendToContext=true`.
|
|
185
|
+
*
|
|
186
|
+
* Most methods are synchronous and return lists of frames the caller should
|
|
187
|
+
* push downstream, making the sequencer easily testable. The exceptions are
|
|
188
|
+
* `registerSpoken`, `registerSkipped`, and `finalize`, which are async because
|
|
189
|
+
* — when the sequencer is built with `streaming: true` — they drive an async
|
|
190
|
+
* `_ParallelSentenceAggregator` to group streamed tokens into sentences.
|
|
191
|
+
*
|
|
192
|
+
* Contexts can be in flight concurrently (e.g. two back-to-back
|
|
193
|
+
* `TTSSpeakFrame` utterances on a websocket TTS service whose `runTts`
|
|
194
|
+
* returns before synthesis finishes). State is organised in three tiers so
|
|
195
|
+
* concurrent contexts never bleed into each other:
|
|
196
|
+
*
|
|
197
|
+
* - `_slots`: the single global ordered timeline across all contexts.
|
|
198
|
+
* Ordering is the whole point of the sequencer, so this stays one list.
|
|
199
|
+
* - `_contextAppendToContext`: a per-context flag whose presence also marks
|
|
200
|
+
* the context as *live* — an entry existing is the "process this context's
|
|
201
|
+
* words" signal in `processWord`'s stale gate. Created at slot registration,
|
|
202
|
+
* removed when the context is fully done (`forceComplete`).
|
|
203
|
+
* - `_streamingContexts`: transient per-context pending-sentence state
|
|
204
|
+
* (`_StreamingContext`), present only while a sentence is being accumulated
|
|
205
|
+
* from tokens. Released at end of text input (`finalize`).
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* const sequencer = new AggregatedFrameSequencer();
|
|
209
|
+
* await sequencer.registerSpoken(frame, ctxId, ttsText, true);
|
|
210
|
+
* for (const f of sequencer.processWord("hello", 1000, ctxId)) {
|
|
211
|
+
* await this.pushFrame(f);
|
|
212
|
+
* }
|
|
213
|
+
*/
|
|
214
|
+
export class AggregatedFrameSequencer {
|
|
215
|
+
_name;
|
|
216
|
+
_streaming;
|
|
217
|
+
_slots = [];
|
|
218
|
+
_contextAppendToContext = new Map();
|
|
219
|
+
// Transient per-context pending-sentence state, keyed by context ID. An entry
|
|
220
|
+
// exists only while a sentence is being accumulated from tokens for that
|
|
221
|
+
// context (streaming mode only). Aggregators are created lazily per context.
|
|
222
|
+
_streamingContexts = new Map();
|
|
223
|
+
_bufferedWords = [];
|
|
224
|
+
/**
|
|
225
|
+
* Initialize the sequencer.
|
|
226
|
+
*
|
|
227
|
+
* @param options.name Label used in log messages. Defaults to
|
|
228
|
+
* "AggregatedFrameSequencer".
|
|
229
|
+
* @param options.streaming True when tokens are dispatched to the TTS
|
|
230
|
+
* individually (see `AggregatedFrameSequencerOptions.streaming`).
|
|
231
|
+
*/
|
|
232
|
+
constructor(options = {}) {
|
|
233
|
+
this._name = options.name ?? "AggregatedFrameSequencer";
|
|
234
|
+
this._streaming = options.streaming ?? false;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Register a spoken AggregatedTextFrame slot.
|
|
238
|
+
*
|
|
239
|
+
* Called from _handleAggregatedText for every frame sent to the TTS service
|
|
240
|
+
* (one call per token when `streaming`, one call per complete unit
|
|
241
|
+
* otherwise). Builds the `WordCompletionTracker` internally when
|
|
242
|
+
* `buildTracker` is set — callers never construct one themselves. A
|
|
243
|
+
* registered slot is marked complete either via `processWord`
|
|
244
|
+
* (word-timestamp services) or `completeSpokenSlot` (pushTextFrames=true
|
|
245
|
+
* services).
|
|
246
|
+
*
|
|
247
|
+
* When the sequencer is non-streaming, this registers a slot immediately.
|
|
248
|
+
* When streaming — whether or not `buildTracker` is set — the call instead
|
|
249
|
+
* feeds this token to the `_ParallelSentenceAggregator` and only registers a
|
|
250
|
+
* real slot once a sentence boundary is confirmed there; a
|
|
251
|
+
* pushTextFrames=true (no-tracker) slot completes immediately on promotion
|
|
252
|
+
* (see `_promote`) since there is no word-timestamp signal to complete it
|
|
253
|
+
* progressively.
|
|
254
|
+
*
|
|
255
|
+
* @param frame The AggregatedTextFrame being spoken (one token when streaming).
|
|
256
|
+
* @param contextId The TTS context ID assigned to this frame.
|
|
257
|
+
* @param ttsText The text actually sent to the TTS for this call (may differ
|
|
258
|
+
* from `frame.text` after filters/transforms).
|
|
259
|
+
* @param appendToContext Whether word frames built for this context should
|
|
260
|
+
* carry appendToContext=true.
|
|
261
|
+
* @param options See `RegisterSpokenOptions`.
|
|
262
|
+
* @returns Frames unblocked by this call (a promoted sentence and/or
|
|
263
|
+
* buffered words replayed once a pending sentence promotes). Always empty
|
|
264
|
+
* for the non-streaming case.
|
|
265
|
+
*/
|
|
266
|
+
async registerSpoken(frame, contextId, ttsText, appendToContext, options = {}) {
|
|
267
|
+
const buildTracker = options.buildTracker ?? true;
|
|
268
|
+
const includesInterFrameSpaces = options.includesInterFrameSpaces ?? false;
|
|
269
|
+
const language = options.language ?? null;
|
|
270
|
+
if (!this._streaming) {
|
|
271
|
+
this._appendSpokenSlot(frame, contextId, buildTracker
|
|
272
|
+
? new WordCompletionTracker(ttsText, {
|
|
273
|
+
llmText: frame.rawText || frame.text,
|
|
274
|
+
userFacingText: frame.text,
|
|
275
|
+
})
|
|
276
|
+
: null, appendToContext, includesInterFrameSpaces);
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
let sc = this._streamingContexts.get(contextId);
|
|
280
|
+
if (!sc) {
|
|
281
|
+
sc = {
|
|
282
|
+
aggregator: new _ParallelSentenceAggregator(language),
|
|
283
|
+
appendToContext,
|
|
284
|
+
buildTracker,
|
|
285
|
+
textType: frame.textType === TextType.TOKEN ? TextType.SENTENCE : frame.textType,
|
|
286
|
+
};
|
|
287
|
+
this._streamingContexts.set(contextId, sc);
|
|
288
|
+
}
|
|
289
|
+
sc.aggregator.setLanguage(language);
|
|
290
|
+
const frames = [];
|
|
291
|
+
for await (const agg of sc.aggregator.aggregate(ttsText, frame.rawText || frame.text, frame.text)) {
|
|
292
|
+
frames.push(...this._promote(agg, contextId, sc));
|
|
293
|
+
}
|
|
294
|
+
return frames;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Register a skipped AggregatedTextFrame and attempt an immediate flush.
|
|
298
|
+
*
|
|
299
|
+
* Any sentence still pending for this context is finalized first, so a real
|
|
300
|
+
* spoken slot exists immediately before the skipped slot in the queue —
|
|
301
|
+
* `flush`'s "stop at first incomplete spoken slot" logic then blocks this
|
|
302
|
+
* skipped frame correctly until that sentence is actually spoken.
|
|
303
|
+
*
|
|
304
|
+
* The frame is appended as a skipped slot. If no incomplete spoken slot
|
|
305
|
+
* precedes it, the frame is returned right away; otherwise it waits until a
|
|
306
|
+
* later `flush` unblocks it.
|
|
307
|
+
*
|
|
308
|
+
* @param frame The skipped AggregatedTextFrame (e.g. a code block).
|
|
309
|
+
* @param contextId The context ID assigned in _handleAggregatedText.
|
|
310
|
+
* @param transportDestination Transport routing value to attach at flush time.
|
|
311
|
+
* @returns The frames to push downstream — any sentence promoted by the
|
|
312
|
+
* initial `finalize` (streaming mode), followed by this skipped frame once
|
|
313
|
+
* it is unblocked. The skipped frame itself is absent while a preceding
|
|
314
|
+
* spoken slot is still incomplete — the promoted-sentence frame can still
|
|
315
|
+
* be returned in that case, so the list is not necessarily empty when
|
|
316
|
+
* blocked.
|
|
317
|
+
*/
|
|
318
|
+
async registerSkipped(frame, contextId, transportDestination) {
|
|
319
|
+
const frames = await this.finalize(contextId);
|
|
320
|
+
frame.contextId = contextId;
|
|
321
|
+
this._slots.push(newSlot({
|
|
322
|
+
frame,
|
|
323
|
+
contextId,
|
|
324
|
+
spoken: false,
|
|
325
|
+
transportDestination,
|
|
326
|
+
}));
|
|
327
|
+
frames.push(...this.flush());
|
|
328
|
+
return frames;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Force-promote a context's still-pending sentence into a real slot.
|
|
332
|
+
*
|
|
333
|
+
* Called at end of text input for a context (no more tokens are coming), to
|
|
334
|
+
* handle a response that ends with no terminal punctuation. The context's
|
|
335
|
+
* pending-sentence state is released here; its `_contextAppendToContext`
|
|
336
|
+
* entry is deliberately kept, since word-timestamp events for the
|
|
337
|
+
* just-promoted slot still arrive later (during audio playback) and must be
|
|
338
|
+
* recognised as live. A no-op when nothing is pending for the context (or
|
|
339
|
+
* the sequencer is not streaming).
|
|
340
|
+
*
|
|
341
|
+
* @param contextId The context whose pending sentence should be finalized.
|
|
342
|
+
* @returns Frames unblocked by finalizing (e.g. buffered words that can now
|
|
343
|
+
* be replayed against the newly-registered slot).
|
|
344
|
+
*/
|
|
345
|
+
async finalize(contextId) {
|
|
346
|
+
if (!this._streaming || contextId === null) {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
const sc = this._streamingContexts.get(contextId);
|
|
350
|
+
this._streamingContexts.delete(contextId);
|
|
351
|
+
if (!sc) {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
const agg = await sc.aggregator.flush();
|
|
355
|
+
return agg ? this._promote(agg, contextId, sc) : [];
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Process one word-timestamp event and return frames to push downstream.
|
|
359
|
+
*
|
|
360
|
+
* Locates the active slot (the first incomplete spoken slot with a tracker
|
|
361
|
+
* for this `contextId`), advances it by the incoming word, and builds a
|
|
362
|
+
* `TTSTextFrame`. Handles:
|
|
363
|
+
*
|
|
364
|
+
* - Words from a context that was never registered, was wiped by `clear` on
|
|
365
|
+
* interruption, or has already completed (`forceComplete` removes its
|
|
366
|
+
* entry): dropped as stale (returns an empty list).
|
|
367
|
+
* - Normal words that fit entirely within the active slot.
|
|
368
|
+
* - Overflow words straddling two slot boundaries.
|
|
369
|
+
* - Force-complete when the TTS drops an event (word belongs to the next slot).
|
|
370
|
+
* - Drop for a word no slot recognises, which is not this turn's text. The
|
|
371
|
+
* slot is left where it is, and the next word that does match carries the
|
|
372
|
+
* text this one should have covered; only what no later word accounts for
|
|
373
|
+
* waits for `forceComplete` at the end of the audio context. When
|
|
374
|
+
* streaming, such a word is buffered instead, since the slot it belongs to
|
|
375
|
+
* may simply not be promoted yet.
|
|
376
|
+
* - Flushes any skipped slots unblocked by slot completion.
|
|
377
|
+
*
|
|
378
|
+
* @param word A word token from the TTS service word-timestamp stream.
|
|
379
|
+
* @param pts Presentation timestamp (nanoseconds) to assign to the frame.
|
|
380
|
+
* @param contextId TTS context ID from the word-timestamp event.
|
|
381
|
+
* @param includesInterFrameSpaces Stamped onto the emitted TTSTextFrame so
|
|
382
|
+
* downstream consumers know not to inject extra spaces between frames.
|
|
383
|
+
* @returns Ordered list of frames (TTSTextFrame and/or AggregatedTextFrame)
|
|
384
|
+
* to push.
|
|
385
|
+
*/
|
|
386
|
+
processWord(word, pts, contextId, includesInterFrameSpaces = false) {
|
|
387
|
+
// Drop words from contexts we never registered, that were wiped by clear() on
|
|
388
|
+
// interruption, or that have already fully completed (forceComplete removes the
|
|
389
|
+
// entry). Such a word is stale (e.g. delayed word-timestamps the TTS server
|
|
390
|
+
// delivers seconds after the context was cancelled); emitting it would interleave
|
|
391
|
+
// it into the current turn's transcript. A null contextId is left untouched:
|
|
392
|
+
// services without audio contexts legitimately use the passthrough path below. A
|
|
393
|
+
// word for a context still streaming a pending sentence (no slot promoted yet) is
|
|
394
|
+
// not stale — it's handled by the buffering below.
|
|
395
|
+
const isPendingStreamingCtx = contextId !== null && this._streamingContexts.has(contextId);
|
|
396
|
+
if (contextId !== null &&
|
|
397
|
+
!this._contextAppendToContext.has(contextId) &&
|
|
398
|
+
!isPendingStreamingCtx) {
|
|
399
|
+
logger.debug(`${this._name} Dropping stale word '${word}' from unknown/cleared context ${contextId}`);
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
const active = this._getActiveSlot(contextId);
|
|
403
|
+
let isComplete = false;
|
|
404
|
+
let rawOverflowWord = null;
|
|
405
|
+
if (active?.tracker) {
|
|
406
|
+
if (!active.tracker.wordBelongsHere(word)) {
|
|
407
|
+
const nextSlot = this._getNextActiveSlot(active, contextId);
|
|
408
|
+
const wordFitsNext = nextSlot?.tracker?.wordBelongsHere(word) ?? false;
|
|
409
|
+
if (!wordFitsNext) {
|
|
410
|
+
if (this._streaming) {
|
|
411
|
+
this._bufferedWords.push({ word, pts, contextId, includesInterFrameSpaces });
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
// Nothing left in this turn's text can be what was reported.
|
|
415
|
+
// Emitting it anyway would write words the LLM never wrote into
|
|
416
|
+
// the conversation context. Dropping it loses nothing: the slot
|
|
417
|
+
// keeps its place, so the next word that does match is matched
|
|
418
|
+
// past this one and carries the text it should have covered.
|
|
419
|
+
// Only text no later word accounts for waits for
|
|
420
|
+
// forceComplete, when the audio context ends.
|
|
421
|
+
logger.warning(`${this._name} Dropping word '${word}' not recognised by any slot.`);
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
isComplete = active.tracker.addWordAndCheckComplete(word);
|
|
426
|
+
rawOverflowWord = active.tracker.getOverflowWord();
|
|
427
|
+
}
|
|
428
|
+
else if (this._streaming && active === null) {
|
|
429
|
+
this._bufferedWords.push({ word, pts, contextId, includesInterFrameSpaces });
|
|
430
|
+
return [];
|
|
431
|
+
}
|
|
432
|
+
// Give preference to the per-call flag; fall back to the slot's flag.
|
|
433
|
+
// Also propagate the per-call flag onto the slot so forceComplete inherits it.
|
|
434
|
+
if (active && includesInterFrameSpaces) {
|
|
435
|
+
active.includesInterFrameSpaces = true;
|
|
436
|
+
}
|
|
437
|
+
const slotIfs = includesInterFrameSpaces || (active ? active.includesInterFrameSpaces : false);
|
|
438
|
+
const frameText = active?.tracker ? active.tracker.getWordForFrame() : word;
|
|
439
|
+
const rawText = active?.tracker ? active.tracker.getLlmConsumed() : null;
|
|
440
|
+
const suppress = active?.tracker ? active.tracker.suppressInContext() : false;
|
|
441
|
+
const emitContextId = active ? active.contextId : contextId;
|
|
442
|
+
const frames = [];
|
|
443
|
+
if (frameText) {
|
|
444
|
+
frames.push(this._buildWordFrame(frameText, pts, emitContextId, {
|
|
445
|
+
segmentId: active ? active.frame.id : null,
|
|
446
|
+
rawText,
|
|
447
|
+
suppressInContext: suppress,
|
|
448
|
+
includesInterFrameSpaces: slotIfs,
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
if (active?.tracker && !suppress) {
|
|
452
|
+
frames.push(this._buildProgressFrame(active, pts));
|
|
453
|
+
}
|
|
454
|
+
if (isComplete && active) {
|
|
455
|
+
active.complete = true;
|
|
456
|
+
frames.push(...this.flush(pts));
|
|
457
|
+
if (rawOverflowWord) {
|
|
458
|
+
logger.debug(`${this._name} Emitting overflow word '${rawOverflowWord}'`);
|
|
459
|
+
frames.push(...this.processWord(rawOverflowWord, pts, contextId));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return frames;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Mark the first pending spoken slot complete and flush unblocked skipped frames.
|
|
466
|
+
*
|
|
467
|
+
* Used by pushTextFrames=true services: non-streaming callers invoke this
|
|
468
|
+
* directly after appending the unit's TTSTextFrame to the audio context;
|
|
469
|
+
* `_promote` calls it internally for a streaming (no-tracker) sentence,
|
|
470
|
+
* immediately on promotion. Only ever targets a buildTracker=false slot (a
|
|
471
|
+
* word-timestamp slot completes via `processWord` instead), so "first
|
|
472
|
+
* pending" is always the caller's own slot even with multiple contexts in
|
|
473
|
+
* flight — such slots complete synchronously, one at a time, in the same
|
|
474
|
+
* call chain that registers them.
|
|
475
|
+
*
|
|
476
|
+
* @returns AggregatedTextFrame(s) that are now unblocked and should be pushed.
|
|
477
|
+
*/
|
|
478
|
+
completeSpokenSlot() {
|
|
479
|
+
const slot = this._slots.find((s) => s.spoken && !s.complete);
|
|
480
|
+
if (slot) {
|
|
481
|
+
slot.complete = true;
|
|
482
|
+
}
|
|
483
|
+
return this.flush();
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Walk the slot queue and return all skipped frames that are now unblocked.
|
|
487
|
+
*
|
|
488
|
+
* Removes complete spoken slots from the head of the queue, then emits (and
|
|
489
|
+
* removes) skipped slots whose preceding spoken slots are all done. Stops at
|
|
490
|
+
* the first incomplete spoken slot.
|
|
491
|
+
*
|
|
492
|
+
* @param lastWordPts When provided, skipped frames receive this PTS so they
|
|
493
|
+
* appear immediately after the last spoken word in the timeline.
|
|
494
|
+
* @returns AggregatedTextFrame(s) ready to be pushed downstream.
|
|
495
|
+
*/
|
|
496
|
+
flush(lastWordPts = null) {
|
|
497
|
+
const frames = [];
|
|
498
|
+
while (this._slots.length > 0) {
|
|
499
|
+
const slot = this._slots[0];
|
|
500
|
+
if (slot.spoken && slot.complete) {
|
|
501
|
+
this._slots.shift();
|
|
502
|
+
}
|
|
503
|
+
else if (!slot.spoken && !slot.complete) {
|
|
504
|
+
slot.frame.appendToContext = true;
|
|
505
|
+
slot.frame.transportDestination = slot.transportDestination;
|
|
506
|
+
if (lastWordPts) {
|
|
507
|
+
slot.frame.pts = lastWordPts;
|
|
508
|
+
}
|
|
509
|
+
logger.debug(`${this._name}: Flushing Aggregated Frame ${slot.frame}`);
|
|
510
|
+
frames.push(slot.frame);
|
|
511
|
+
slot.complete = true;
|
|
512
|
+
this._slots.shift();
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
break; // spoken but not yet complete — wait
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return frames;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Force-complete a context's incomplete spoken slots and flush skipped frames.
|
|
522
|
+
*
|
|
523
|
+
* Called at the end of an audio context to handle TTS providers that
|
|
524
|
+
* silently drop word-timestamp events. Emits a TTSTextFrame for any
|
|
525
|
+
* remaining unspoken text in each incomplete slot *belonging to this
|
|
526
|
+
* context*, marks it complete, then flushes all now-unblocked skipped
|
|
527
|
+
* frames. Slots for other contexts still in flight are left untouched so
|
|
528
|
+
* their own word events (or their own forceComplete) finish them.
|
|
529
|
+
*
|
|
530
|
+
* Audio contexts are drained one at a time in registration order (a
|
|
531
|
+
* context's whole `_handleAudioContext` runs, ending with its own
|
|
532
|
+
* forceComplete, before the next starts), so an earlier context is never
|
|
533
|
+
* still open here — every earlier context has already been force-completed
|
|
534
|
+
* by the time this one ends.
|
|
535
|
+
*
|
|
536
|
+
* The context is fully done once this returns, so its live-context and
|
|
537
|
+
* pending state are forgotten — any word that arrives afterwards is dropped
|
|
538
|
+
* as stale.
|
|
539
|
+
*
|
|
540
|
+
* @param contextId The audio context that has ended.
|
|
541
|
+
* @param lastWordPts PTS of the last received word frame, used as the PTS
|
|
542
|
+
* for force-completed frames and forwarded to `flush`.
|
|
543
|
+
* @returns Combined list of TTSTextFrames (for incomplete spoken slots),
|
|
544
|
+
* AggregatedTextProgressFrames pairing the forced text, and
|
|
545
|
+
* AggregatedTextFrames (skipped slots now unblocked), in emission order.
|
|
546
|
+
*/
|
|
547
|
+
forceComplete(contextId, lastWordPts) {
|
|
548
|
+
const frames = [];
|
|
549
|
+
if (this._streaming) {
|
|
550
|
+
this._discardBufferedWords(contextId);
|
|
551
|
+
}
|
|
552
|
+
for (const slot of this._slots) {
|
|
553
|
+
if (slot.spoken && !slot.complete && slot.contextId === contextId) {
|
|
554
|
+
if (slot.tracker) {
|
|
555
|
+
const remainingText = slot.tracker.getRemainingTtsText();
|
|
556
|
+
let rawRemaining = slot.tracker.getRemainingLlmText();
|
|
557
|
+
if (rawRemaining && remainingText && !rawRemaining.includes(remainingText)) {
|
|
558
|
+
logger.warning(`${this._name} force-complete: raw_remaining ${repr(rawRemaining)} ` +
|
|
559
|
+
`does not contain remaining_text ${repr(remainingText)}, discarding`);
|
|
560
|
+
rawRemaining = null;
|
|
561
|
+
}
|
|
562
|
+
if (remainingText) {
|
|
563
|
+
logger.debug(`${this._name} force-completing slot with remaining text ${repr(remainingText)}`);
|
|
564
|
+
frames.push(this._buildWordFrame(remainingText, lastWordPts, slot.contextId, {
|
|
565
|
+
segmentId: slot.frame.id,
|
|
566
|
+
rawText: rawRemaining,
|
|
567
|
+
includesInterFrameSpaces: slot.includesInterFrameSpaces,
|
|
568
|
+
}));
|
|
569
|
+
// That frame carries the rest of the text, so the progress view
|
|
570
|
+
// has to reach the end alongside it.
|
|
571
|
+
slot.tracker.takeRemainingAsSpoken();
|
|
572
|
+
frames.push(this._buildProgressFrame(slot, lastWordPts));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
slot.complete = true;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
frames.push(...this.flush(lastWordPts));
|
|
579
|
+
// Context is fully done: forget it so any later word is dropped as stale.
|
|
580
|
+
this._contextAppendToContext.delete(contextId);
|
|
581
|
+
this._streamingContexts.delete(contextId);
|
|
582
|
+
return frames;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Clear slots and metadata, optionally preserving selected contexts.
|
|
586
|
+
*
|
|
587
|
+
* @param options.keepContexts Synthesis contexts that survive the interruption.
|
|
588
|
+
*/
|
|
589
|
+
clear(options = {}) {
|
|
590
|
+
const keep = options.keepContexts ?? new Set();
|
|
591
|
+
this._slots = this._slots.filter((slot) => keep.has(slot.contextId));
|
|
592
|
+
this._contextAppendToContext = new Map([...this._contextAppendToContext].filter(([key]) => keep.has(key)));
|
|
593
|
+
this._bufferedWords = this._bufferedWords.filter((word) => word.contextId !== null && keep.has(word.contextId));
|
|
594
|
+
this._streamingContexts = new Map([...this._streamingContexts].filter(([key]) => keep.has(key)));
|
|
595
|
+
}
|
|
596
|
+
// -------------------------------------------------------------------------
|
|
597
|
+
// Internal helpers
|
|
598
|
+
// -------------------------------------------------------------------------
|
|
599
|
+
/**
|
|
600
|
+
* Append a real, immediately-registered spoken slot.
|
|
601
|
+
*
|
|
602
|
+
* Shared by the non-streaming path of `registerSpoken` and by `_promote`
|
|
603
|
+
* once a streamed sentence's boundary is confirmed.
|
|
604
|
+
*/
|
|
605
|
+
_appendSpokenSlot(frame, contextId, tracker, appendToContext, includesInterFrameSpaces) {
|
|
606
|
+
this._contextAppendToContext.set(contextId, appendToContext);
|
|
607
|
+
this._slots.push(newSlot({
|
|
608
|
+
frame,
|
|
609
|
+
contextId,
|
|
610
|
+
spoken: true,
|
|
611
|
+
tracker,
|
|
612
|
+
includesInterFrameSpaces,
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Turn a completed parallel-aggregated sentence into a real spoken slot.
|
|
617
|
+
*
|
|
618
|
+
* Builds an AggregatedTextFrame of the context's text type (usually
|
|
619
|
+
* `TextType.SENTENCE`) from the three aggregated text channels and appends
|
|
620
|
+
* the slot for `contextId`, then replays any words that were buffered
|
|
621
|
+
* waiting for it.
|
|
622
|
+
*
|
|
623
|
+
* The sentence frame is **emitted downstream** (first in the returned list)
|
|
624
|
+
* with `willBeSpoken=true`: it is the streaming-mode equivalent of the
|
|
625
|
+
* pre-synthesis sentence frame that SENTENCE mode pushes, giving RTVI
|
|
626
|
+
* clients the initial `spoken_status="new"` event whose `segment_id` the
|
|
627
|
+
* subsequent progress frames (built from this same frame's id) reference.
|
|
628
|
+
* `appendToContext` is false on it — the conversation context is built from
|
|
629
|
+
* the per-word TTSTextFrames (or, for pushTextFrames=true services, the
|
|
630
|
+
* whole-sentence TTSTextFrame built below), not this announcement.
|
|
631
|
+
*
|
|
632
|
+
* When `buildTracker` is true (word-timestamp services), the slot gets a
|
|
633
|
+
* real `WordCompletionTracker` and completes progressively as `processWord`
|
|
634
|
+
* events arrive. When false (pushTextFrames=true services, e.g.
|
|
635
|
+
* streamed-token providers with no word-timestamp signal), the slot gets no
|
|
636
|
+
* tracker and is completed immediately: by the time a sentence promotes
|
|
637
|
+
* (confirmed only by the *next* sentence's first token, or by `finalize` at
|
|
638
|
+
* end of turn), every token making it up has already been sent to the TTS,
|
|
639
|
+
* so there is nothing further to wait for. A whole-sentence TTSTextFrame is
|
|
640
|
+
* built here to carry that content into the conversation context, mirroring
|
|
641
|
+
* the single TTSTextFrame a non-streaming pushTextFrames=true service emits
|
|
642
|
+
* per unit.
|
|
643
|
+
*
|
|
644
|
+
* The slot's `includesInterFrameSpaces` is left false: for a streamed
|
|
645
|
+
* (TOKEN-mode) sentence, per-word CJK spacing is stamped by `processWord`
|
|
646
|
+
* from `addWordTimestamps`, never from the incoming LLM token's own
|
|
647
|
+
* inter-frame-space flag.
|
|
648
|
+
*
|
|
649
|
+
* @param agg The completed sentence, in the three parallel text channels.
|
|
650
|
+
* @param contextId The context the promoted slot belongs to.
|
|
651
|
+
* @param sc The context's streaming state.
|
|
652
|
+
* @returns The sentence frame, followed — for pushTextFrames=true services —
|
|
653
|
+
* by the whole-sentence TTSTextFrame and any skipped frames it unblocks,
|
|
654
|
+
* followed by any frames unblocked by replaying previously-buffered words.
|
|
655
|
+
* Empty if the aggregated text is entirely whitespace.
|
|
656
|
+
*/
|
|
657
|
+
_promote(agg, contextId, sc) {
|
|
658
|
+
if (!agg.userFacingText.trim()) {
|
|
659
|
+
return [];
|
|
660
|
+
}
|
|
661
|
+
const frame = new AggregatedTextFrame({
|
|
662
|
+
text: agg.userFacingText,
|
|
663
|
+
textType: sc.textType,
|
|
664
|
+
rawText: agg.llmText || null,
|
|
665
|
+
});
|
|
666
|
+
frame.contextId = contextId;
|
|
667
|
+
frame.willBeSpoken = true;
|
|
668
|
+
frame.appendToContext = false;
|
|
669
|
+
const tracker = sc.buildTracker
|
|
670
|
+
? new WordCompletionTracker(agg.ttsText, {
|
|
671
|
+
llmText: agg.llmText || null,
|
|
672
|
+
userFacingText: agg.userFacingText,
|
|
673
|
+
})
|
|
674
|
+
: null;
|
|
675
|
+
this._appendSpokenSlot(frame, contextId, tracker, sc.appendToContext, false);
|
|
676
|
+
const frames = [frame];
|
|
677
|
+
if (!sc.buildTracker) {
|
|
678
|
+
const wordFrame = new TTSTextFrame({
|
|
679
|
+
text: agg.userFacingText,
|
|
680
|
+
textType: sc.textType,
|
|
681
|
+
rawText: agg.llmText || null,
|
|
682
|
+
segmentId: frame.id,
|
|
683
|
+
});
|
|
684
|
+
wordFrame.contextId = contextId;
|
|
685
|
+
wordFrame.willBeSpoken = true;
|
|
686
|
+
wordFrame.appendToContext = sc.appendToContext;
|
|
687
|
+
frames.push(wordFrame);
|
|
688
|
+
frames.push(...this.completeSpokenSlot());
|
|
689
|
+
}
|
|
690
|
+
frames.push(...this._drainBufferedWords());
|
|
691
|
+
return frames;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Replay previously-buffered word events now that a new slot may match them.
|
|
695
|
+
*
|
|
696
|
+
* Snapshots and clears the buffer before replaying so a word that still
|
|
697
|
+
* doesn't match anything (it belongs to a later, still-pending sentence)
|
|
698
|
+
* gets re-buffered by `processWord` itself and waits for the next
|
|
699
|
+
* promotion, rather than looping here.
|
|
700
|
+
*/
|
|
701
|
+
_drainBufferedWords() {
|
|
702
|
+
const buffered = this._bufferedWords;
|
|
703
|
+
this._bufferedWords = [];
|
|
704
|
+
const frames = [];
|
|
705
|
+
for (const w of buffered) {
|
|
706
|
+
frames.push(...this.processWord(w.word, w.pts, w.contextId, w.includesInterFrameSpaces));
|
|
707
|
+
}
|
|
708
|
+
return frames;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Drop this context's still-buffered words, which no slot ever matched.
|
|
712
|
+
*
|
|
713
|
+
* Sentence mode drops an unrecognised word where it arrives; streaming only
|
|
714
|
+
* knows one will never match once the context it belongs to has ended.
|
|
715
|
+
*/
|
|
716
|
+
_discardBufferedWords(contextId) {
|
|
717
|
+
const keep = [];
|
|
718
|
+
for (const w of this._bufferedWords) {
|
|
719
|
+
if (w.contextId !== contextId) {
|
|
720
|
+
keep.push(w);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
logger.warning(`${this._name} Dropping buffered word '${w.word}' not recognised by any slot.`);
|
|
724
|
+
}
|
|
725
|
+
this._bufferedWords = keep;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Whether `slot` is an eligible active slot for `contextId`.
|
|
729
|
+
*
|
|
730
|
+
* A `null` contextId (legacy providers with no per-context word tagging,
|
|
731
|
+
* where concurrency can't occur) matches any slot, preserving the
|
|
732
|
+
* untargeted behaviour. Otherwise word routing is scoped to the slot's own
|
|
733
|
+
* context so concurrently-live contexts never consume each other's words.
|
|
734
|
+
*/
|
|
735
|
+
_slotMatchesContext(slot, contextId) {
|
|
736
|
+
if (!(slot.spoken && !slot.complete && slot.tracker !== null)) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
return contextId === null || slot.contextId === contextId;
|
|
740
|
+
}
|
|
741
|
+
/** Return the first incomplete spoken slot with a tracker for `contextId`. */
|
|
742
|
+
_getActiveSlot(contextId = null) {
|
|
743
|
+
return this._slots.find((s) => this._slotMatchesContext(s, contextId)) ?? null;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Return the first incomplete spoken slot with a tracker for `contextId`
|
|
747
|
+
* after `current`.
|
|
748
|
+
*/
|
|
749
|
+
_getNextActiveSlot(current, contextId = null) {
|
|
750
|
+
let found = false;
|
|
751
|
+
for (const s of this._slots) {
|
|
752
|
+
if (s === current) {
|
|
753
|
+
found = true;
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (found && this._slotMatchesContext(s, contextId)) {
|
|
757
|
+
return s;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Build an AggregatedTextProgressFrame reflecting the current
|
|
764
|
+
* spoken/remaining state of a slot.
|
|
765
|
+
*/
|
|
766
|
+
_buildProgressFrame(slot, pts) {
|
|
767
|
+
const tracker = slot.tracker;
|
|
768
|
+
if (tracker === null) {
|
|
769
|
+
throw new Error("_buildProgressFrame requires a slot with a tracker");
|
|
770
|
+
}
|
|
771
|
+
const frame = new AggregatedTextProgressFrame({
|
|
772
|
+
segmentId: slot.frame.id,
|
|
773
|
+
contextId: slot.contextId,
|
|
774
|
+
text: slot.frame.text,
|
|
775
|
+
textType: slot.frame.textType,
|
|
776
|
+
accumulatedText: tracker.getAccumulatedUserFacingText(),
|
|
777
|
+
remainingText: tracker.getRemainingUserFacingText(false),
|
|
778
|
+
});
|
|
779
|
+
frame.pts = pts;
|
|
780
|
+
return frame;
|
|
781
|
+
}
|
|
782
|
+
/** Build a TTSTextFrame with all standard word-timestamp attributes set. */
|
|
783
|
+
_buildWordFrame(text, pts, contextId, options = {}) {
|
|
784
|
+
const frame = new TTSTextFrame({
|
|
785
|
+
text,
|
|
786
|
+
textType: TextType.WORD,
|
|
787
|
+
segmentId: options.segmentId ?? null,
|
|
788
|
+
});
|
|
789
|
+
frame.pts = pts;
|
|
790
|
+
frame.contextId = contextId;
|
|
791
|
+
if (options.suppressInContext) {
|
|
792
|
+
frame.appendToContext = false;
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
frame.appendToContext =
|
|
796
|
+
contextId !== null ? (this._contextAppendToContext.get(contextId) ?? true) : true;
|
|
797
|
+
}
|
|
798
|
+
frame.rawText = options.rawText ?? null;
|
|
799
|
+
frame.includesInterFrameSpaces = options.includesInterFrameSpaces ?? false;
|
|
800
|
+
return frame;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
//# sourceMappingURL=aggregated_frame_sequencer.js.map
|