@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,1639 @@
|
|
|
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/pipeline/worker.py
|
|
6
|
+
/**
|
|
7
|
+
* Pipeline worker implementation for managing frame processing pipelines.
|
|
8
|
+
*
|
|
9
|
+
* Provides the main PipelineWorker class that orchestrates pipeline
|
|
10
|
+
* execution, frame routing, lifecycle management, and monitoring capabilities
|
|
11
|
+
* including heartbeats, idle detection, and observer integration.
|
|
12
|
+
*
|
|
13
|
+
* Porting notes: the deprecated `PipelineTask` and `PipelineTaskParams`
|
|
14
|
+
* aliases, the `toolResources` option, the `ErrorFrame.fatal` branch, the
|
|
15
|
+
* deprecated `StartFrame` configuration fields and the legacy
|
|
16
|
+
* `setup_pipeline_task` setup hook name are not ported.
|
|
17
|
+
*/
|
|
18
|
+
import { CancelledError, Event, monotonic, Queue, shield, sleep, TimeoutError, wait, waitFor, } from "@pipedog/asyncio";
|
|
19
|
+
import { _BusEdgeProcessor } from "../bus/bridge_processor.js";
|
|
20
|
+
import { BusCancelWorkerMessage, BusFlushProgressMessage, BusFrameMessage, BusTTSSpeakMessage, } from "../bus/index.js";
|
|
21
|
+
import { BusUICommandMessage, BusUIDataMessage, BusUIEventMessage, BusUIJobCompletedMessage, BusUIJobGroupCompletedMessage, BusUIJobGroupStartedMessage, BusUIJobUpdateMessage, UI_CANCEL_JOB_GROUP_EVENT_NAME, UI_SNAPSHOT_EVENT_NAME, } from "../bus/ui/messages.js";
|
|
22
|
+
import { SystemClock } from "../clocks/system_clock.js";
|
|
23
|
+
import { BotSpeakingFrame, CancelFrame, CancelWorkerFrame, EndFrame, EndWorkerFrame, ErrorFrame, HeartbeatFrame, InterimTranscriptionFrame, InterruptionFrame, InterruptionWorkerFrame, MetricsFrame, PipelineFlushFrame, StartFrame, StopFrame, StopWorkerFrame, TranscriptionFrame, TTSSpeakFrame, UserSpeakingFrame, UserStartedSpeakingFrame, } from "../frames/frames.js";
|
|
24
|
+
import { ProcessingMetricsData, TTFBMetricsData } from "../metrics/metrics.js";
|
|
25
|
+
import { BaseObserver } from "../observers/base_observer.js";
|
|
26
|
+
import { TurnTrackingObserver } from "../observers/turn_tracking_observer.js";
|
|
27
|
+
import { UserBotLatencyObserver } from "../observers/user_bot_latency_observer.js";
|
|
28
|
+
import { FrameDirection, FrameProcessorSetup, } from "../processors/frame_processor.js";
|
|
29
|
+
import { RTVIUICommandFrame, RTVIUIJobGroupFrame } from "../processors/frameworks/rtvi/frames.js";
|
|
30
|
+
import { RTVIObserver, RTVIProcessor, } from "../processors/frameworks/rtvi/index.js";
|
|
31
|
+
import * as RTVI from "../processors/frameworks/rtvi/models.js";
|
|
32
|
+
import { BaseInputTransport } from "../transports/base_input.js";
|
|
33
|
+
import { BaseOutputTransport } from "../transports/base_output.js";
|
|
34
|
+
import { logger } from "../utils/logger.js";
|
|
35
|
+
import { runSetupHook } from "../utils/startup.js";
|
|
36
|
+
import { isTracingAvailable } from "../utils/tracing/setup.js";
|
|
37
|
+
import { TracingContext } from "../utils/tracing/tracing_context.js";
|
|
38
|
+
import { TurnTraceObserver } from "../utils/tracing/turn_trace_observer.js";
|
|
39
|
+
import { BaseWorker, } from "../workers/base_worker.js";
|
|
40
|
+
import { BotCapabilities } from "./capabilities.js";
|
|
41
|
+
import { Pipeline, PipelineSink, PipelineSource } from "./pipeline.js";
|
|
42
|
+
import { WorkerObserver } from "./worker_observer.js";
|
|
43
|
+
export const HEARTBEAT_SECS = 1.0;
|
|
44
|
+
export const HEARTBEAT_MONITOR_SECS = 10.0;
|
|
45
|
+
/** How often a pipeline answering someone else's flush says it is still working. */
|
|
46
|
+
export const FLUSH_PROGRESS_PERIOD_SECS = 1.0;
|
|
47
|
+
export const IDLE_TIMEOUT_SECS = 300;
|
|
48
|
+
export const CANCEL_TIMEOUT_SECS = 20.0;
|
|
49
|
+
export const SETUP_TIMEOUT_SECS = 20.0;
|
|
50
|
+
export const START_TIMEOUT_SECS = 20.0;
|
|
51
|
+
/**
|
|
52
|
+
* Idle timeout observer.
|
|
53
|
+
*
|
|
54
|
+
* Waits for specific frames being generated in the pipeline. If the frames
|
|
55
|
+
* are generated the given event is set. If the event is not set it means the
|
|
56
|
+
* pipeline is probably idle.
|
|
57
|
+
*/
|
|
58
|
+
export class IdleFrameObserver extends BaseObserver {
|
|
59
|
+
_idleEvent;
|
|
60
|
+
_idleTimeoutFrames;
|
|
61
|
+
/**
|
|
62
|
+
* Initialize the observer.
|
|
63
|
+
*
|
|
64
|
+
* @param options.idleEvent The event to set if the idle timeout frames are being pushed.
|
|
65
|
+
* @param options.idleTimeoutFrames The frames that should set the event when received.
|
|
66
|
+
*/
|
|
67
|
+
constructor(options) {
|
|
68
|
+
super({ observeEveryPush: false });
|
|
69
|
+
this._idleEvent = options.idleEvent;
|
|
70
|
+
this._idleTimeoutFrames = options.idleTimeoutFrames;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Callback executed when a frame is pushed in the pipeline.
|
|
74
|
+
*
|
|
75
|
+
* @param data The frame push event data.
|
|
76
|
+
*/
|
|
77
|
+
async onPushFrame(data) {
|
|
78
|
+
if (data.frame instanceof StartFrame ||
|
|
79
|
+
this._idleTimeoutFrames.some((cls) => data.frame instanceof cls)) {
|
|
80
|
+
this._idleEvent.set();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* What a pipeline worker does when a processor can no longer do its job.
|
|
86
|
+
*
|
|
87
|
+
* An unusable processor keeps failing for as long as the pipeline keeps using
|
|
88
|
+
* it, so the pipeline has to decide whether it is still worth running. A
|
|
89
|
+
* processor becomes unusable through a permanent error category or through
|
|
90
|
+
* `FrameProcessor.pushError` with `forceTreatAsPermanent: true`.
|
|
91
|
+
*
|
|
92
|
+
* - `CONTINUE`: report the error and keep running. The application can decide
|
|
93
|
+
* what to do, for example by failing over to another provider.
|
|
94
|
+
* - `END`: end the pipeline gracefully, letting queued frames drain first.
|
|
95
|
+
* - `CANCEL`: cancel the pipeline immediately, abandoning queued frames.
|
|
96
|
+
*/
|
|
97
|
+
export const ProcessorUnusablePolicy = {
|
|
98
|
+
CONTINUE: "continue",
|
|
99
|
+
END: "end",
|
|
100
|
+
CANCEL: "cancel",
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Configuration parameters for pipeline execution.
|
|
104
|
+
*
|
|
105
|
+
* These parameters are usually passed to all frame processors through
|
|
106
|
+
* FrameProcessorSetup. For other generic pipeline worker parameters use
|
|
107
|
+
* PipelineWorker constructor options instead.
|
|
108
|
+
*
|
|
109
|
+
* - `audioInSampleRate`: input audio sample rate in Hz.
|
|
110
|
+
* - `audioOutSampleRate`: output audio sample rate in Hz.
|
|
111
|
+
* - `enableHeartbeats`: whether to enable heartbeat monitoring.
|
|
112
|
+
* - `enableMetrics`: whether to enable metrics collection.
|
|
113
|
+
* - `enableUsageMetrics`: whether to enable usage metrics.
|
|
114
|
+
* - `heartbeatsPeriodSecs`: period between heartbeats in seconds.
|
|
115
|
+
* - `heartbeatsMonitorSecs`: timeout (in seconds) before warning about missed
|
|
116
|
+
* heartbeats. Defaults to 10 seconds.
|
|
117
|
+
* - `reportOnlyInitialTtfb`: whether to report only initial time to first byte.
|
|
118
|
+
* - `sendInitialEmptyMetrics`: whether to send initial empty metrics.
|
|
119
|
+
* - `startMetadata`: additional metadata for pipeline start.
|
|
120
|
+
*/
|
|
121
|
+
export class PipelineParams {
|
|
122
|
+
audioInSampleRate;
|
|
123
|
+
audioOutSampleRate;
|
|
124
|
+
enableHeartbeats;
|
|
125
|
+
enableMetrics;
|
|
126
|
+
enableUsageMetrics;
|
|
127
|
+
heartbeatsPeriodSecs;
|
|
128
|
+
heartbeatsMonitorSecs;
|
|
129
|
+
reportOnlyInitialTtfb;
|
|
130
|
+
sendInitialEmptyMetrics;
|
|
131
|
+
startMetadata;
|
|
132
|
+
constructor(options = {}) {
|
|
133
|
+
this.audioInSampleRate = options.audioInSampleRate ?? 16000;
|
|
134
|
+
this.audioOutSampleRate = options.audioOutSampleRate ?? 24000;
|
|
135
|
+
this.enableHeartbeats = options.enableHeartbeats ?? false;
|
|
136
|
+
this.enableMetrics = options.enableMetrics ?? false;
|
|
137
|
+
this.enableUsageMetrics = options.enableUsageMetrics ?? false;
|
|
138
|
+
this.heartbeatsPeriodSecs = options.heartbeatsPeriodSecs ?? HEARTBEAT_SECS;
|
|
139
|
+
this.heartbeatsMonitorSecs = options.heartbeatsMonitorSecs ?? HEARTBEAT_MONITOR_SECS;
|
|
140
|
+
this.reportOnlyInitialTtfb = options.reportOnlyInitialTtfb ?? false;
|
|
141
|
+
this.sendInitialEmptyMetrics = options.sendInitialEmptyMetrics ?? true;
|
|
142
|
+
this.startMetadata = options.startMetadata ?? {};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Manages the execution of a pipeline, handling frame processing and worker
|
|
147
|
+
* lifecycle.
|
|
148
|
+
*
|
|
149
|
+
* Orchestrates pipeline execution with comprehensive monitoring, event
|
|
150
|
+
* handling, and lifecycle management. Provides event handlers for various
|
|
151
|
+
* pipeline states and frame types, idle detection, heartbeat monitoring, and
|
|
152
|
+
* observer integration.
|
|
153
|
+
*
|
|
154
|
+
* Event handlers available:
|
|
155
|
+
*
|
|
156
|
+
* - on_frame_reached_upstream: called when upstream frames reach the source
|
|
157
|
+
* - on_frame_reached_downstream: called when downstream frames reach the sink
|
|
158
|
+
* - on_heartbeat_timeout: called when a heartbeat frame is not received within
|
|
159
|
+
* the monitor timeout. Fires repeatedly every `heartbeatsMonitorSecs` for as
|
|
160
|
+
* long as the stall persists.
|
|
161
|
+
* - on_idle_timeout: called when pipeline is idle beyond timeout threshold
|
|
162
|
+
* - on_pipeline_started: called when pipeline starts with StartFrame
|
|
163
|
+
* - on_pipeline_finished: called after the pipeline has reached any terminal
|
|
164
|
+
* state. This includes:
|
|
165
|
+
* - StopFrame: pipeline was stopped (processors keep connections open)
|
|
166
|
+
* - EndFrame: pipeline ended normally
|
|
167
|
+
* - CancelFrame: pipeline was cancelled
|
|
168
|
+
*
|
|
169
|
+
* Use this event for cleanup, logging, or post-processing tasks. Users can
|
|
170
|
+
* inspect the frame if they need to handle specific cases.
|
|
171
|
+
* - on_pipeline_timeout: called when a frame the worker was waiting on never
|
|
172
|
+
* reached the end of the pipeline, meaning a processor is blocked. Inspect
|
|
173
|
+
* the frame to tell the cases apart:
|
|
174
|
+
* - StartFrame: the pipeline never started and is being torn down
|
|
175
|
+
* - CancelFrame: the pipeline was cancelled but did not drain
|
|
176
|
+
*
|
|
177
|
+
* There is no EndFrame case: ending flushes whatever is queued, so the
|
|
178
|
+
* worker waits for it however long that takes.
|
|
179
|
+
* - on_pipeline_error: called when an error occurs with ErrorFrame. Handler
|
|
180
|
+
* can read `frame.processor.isUsable` to distinguish between errors that
|
|
181
|
+
* end a processor's usability from those that don't.
|
|
182
|
+
* - on_setup_timeout: called when setting the pipeline up times out.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* worker.eventHandler("on_frame_reached_upstream", async (worker, frame) => { ... });
|
|
186
|
+
* worker.eventHandler("on_heartbeat_timeout", async (worker) => { ... });
|
|
187
|
+
* worker.eventHandler("on_idle_timeout", async (worker) => { ... });
|
|
188
|
+
* worker.eventHandler("on_pipeline_started", async (worker, frame) => { ... });
|
|
189
|
+
* worker.eventHandler("on_pipeline_finished", async (worker, frame) => { ... });
|
|
190
|
+
* worker.eventHandler("on_pipeline_timeout", async (worker, frame) => { ... });
|
|
191
|
+
* worker.eventHandler("on_pipeline_error", async (worker, frame) => { ... });
|
|
192
|
+
* worker.eventHandler("on_setup_timeout", async (worker) => { ... });
|
|
193
|
+
*/
|
|
194
|
+
export class PipelineWorker extends BaseWorker {
|
|
195
|
+
_bridged;
|
|
196
|
+
_handleFlushFrame;
|
|
197
|
+
_params;
|
|
198
|
+
_capabilitiesOverride;
|
|
199
|
+
_additionalSpanAttributes;
|
|
200
|
+
_cancelOnIdleTimeout;
|
|
201
|
+
_cancelRunnerOnIdleTimeout;
|
|
202
|
+
_cancelTimeoutSecs;
|
|
203
|
+
// Frames that have reached the sink. A flush watches this to tell a
|
|
204
|
+
// pipeline that is still working from one that is stuck.
|
|
205
|
+
_framesReachingSink = 0;
|
|
206
|
+
// Probes from other workers that this pipeline is answering, by flush id,
|
|
207
|
+
// and the progress reported back for our own.
|
|
208
|
+
_foreignProbes = new Map();
|
|
209
|
+
_flushProgress = new Map();
|
|
210
|
+
_flushProgressSent = 0.0;
|
|
211
|
+
_setupTimeoutSecs;
|
|
212
|
+
_startTimeoutSecs;
|
|
213
|
+
_clock;
|
|
214
|
+
_conversationId;
|
|
215
|
+
_enableTracing;
|
|
216
|
+
_enableTurnTracking;
|
|
217
|
+
_idleTimeoutSecs;
|
|
218
|
+
_appResources;
|
|
219
|
+
_processorUnusablePolicy;
|
|
220
|
+
// Processors the policy has already been applied for, so a service that
|
|
221
|
+
// keeps failing is acted on once instead of on every frame it can't handle.
|
|
222
|
+
_unusableProcessors = new Set();
|
|
223
|
+
_turnTrackingObserver = null;
|
|
224
|
+
_userBotLatencyObserver = null;
|
|
225
|
+
_turnTraceObserver = null;
|
|
226
|
+
_tracingContext = null;
|
|
227
|
+
_finished = false;
|
|
228
|
+
_cancelled = false;
|
|
229
|
+
// This queue is the queue used to push frames to the pipeline.
|
|
230
|
+
_pushQueue = new Queue();
|
|
231
|
+
_processPushTask = null;
|
|
232
|
+
_pipelineSetupTask = null;
|
|
233
|
+
_pipelineStartTask = null;
|
|
234
|
+
_setupWatchTask = null;
|
|
235
|
+
// This is the heartbeat queue. When a heartbeat frame is received in the
|
|
236
|
+
// down queue we add it to the heartbeat queue for processing.
|
|
237
|
+
_heartbeatQueue = new Queue();
|
|
238
|
+
_heartbeatPushTask = null;
|
|
239
|
+
_heartbeatMonitorTask = null;
|
|
240
|
+
// RTVI support
|
|
241
|
+
_rtvi = null;
|
|
242
|
+
// This is the idle event. When selected frames are pushed from any
|
|
243
|
+
// processor we consider the pipeline is not idle. We use an observer which
|
|
244
|
+
// will be listening any part of the pipeline.
|
|
245
|
+
_idleEvent = new Event();
|
|
246
|
+
_idleMonitorTask = null;
|
|
247
|
+
// This event is used to indicate the StartFrame has been received at the
|
|
248
|
+
// end of the pipeline.
|
|
249
|
+
_pipelineStartEvent = new Event();
|
|
250
|
+
// This event is used to indicate a finalize frame (e.g. EndFrame,
|
|
251
|
+
// StopFrame) has been received at the end of the pipeline.
|
|
252
|
+
_pipelineEndEvent = new Event();
|
|
253
|
+
_source;
|
|
254
|
+
_sink;
|
|
255
|
+
_pipeline;
|
|
256
|
+
_observer;
|
|
257
|
+
// These events can be used to check which frames make it to the source or
|
|
258
|
+
// sink processors. Instead of calling the event handlers for every frame
|
|
259
|
+
// the user needs to specify which events they are interested in. This is
|
|
260
|
+
// mainly for efficiency reason because each event handler creates a task
|
|
261
|
+
// and most likely you only care about one or two frame types.
|
|
262
|
+
_reachedUpstreamTypes = new Set();
|
|
263
|
+
_reachedDownstreamTypes = new Set();
|
|
264
|
+
/**
|
|
265
|
+
* Initialize the PipelineWorker.
|
|
266
|
+
*
|
|
267
|
+
* @param pipeline The pipeline to execute.
|
|
268
|
+
* @param options See `PipelineWorkerOptions`.
|
|
269
|
+
*/
|
|
270
|
+
constructor(pipeline, options = {}) {
|
|
271
|
+
super({
|
|
272
|
+
name: options.name ?? null,
|
|
273
|
+
active: options.active ?? true,
|
|
274
|
+
taskManager: options.taskManager ?? null,
|
|
275
|
+
checkDanglingTasks: options.checkDanglingTasks ?? true,
|
|
276
|
+
});
|
|
277
|
+
const bridged = options.bridged ?? null;
|
|
278
|
+
this._bridged = bridged;
|
|
279
|
+
this._handleFlushFrame =
|
|
280
|
+
options.handleFlushFrame !== null && options.handleFlushFrame !== undefined
|
|
281
|
+
? options.handleFlushFrame
|
|
282
|
+
: bridged === null;
|
|
283
|
+
this._params = options.params ?? new PipelineParams();
|
|
284
|
+
this._capabilitiesOverride = options.capabilities ?? new BotCapabilities();
|
|
285
|
+
this._additionalSpanAttributes = options.additionalSpanAttributes ?? {};
|
|
286
|
+
this._cancelOnIdleTimeout = options.cancelOnIdleTimeout ?? true;
|
|
287
|
+
this._cancelRunnerOnIdleTimeout = options.cancelRunnerOnIdleTimeout ?? true;
|
|
288
|
+
this._cancelTimeoutSecs = options.cancelTimeoutSecs ?? CANCEL_TIMEOUT_SECS;
|
|
289
|
+
this._setupTimeoutSecs = options.setupTimeoutSecs ?? SETUP_TIMEOUT_SECS;
|
|
290
|
+
this._startTimeoutSecs = options.startTimeoutSecs ?? START_TIMEOUT_SECS;
|
|
291
|
+
this._clock = options.clock ?? new SystemClock();
|
|
292
|
+
this._conversationId = options.conversationId ?? null;
|
|
293
|
+
this._enableTracing = (options.enableTracing ?? false) && isTracingAvailable();
|
|
294
|
+
this._enableTurnTracking = options.enableTurnTracking ?? true;
|
|
295
|
+
this._idleTimeoutSecs =
|
|
296
|
+
options.idleTimeoutSecs === undefined ? IDLE_TIMEOUT_SECS : options.idleTimeoutSecs;
|
|
297
|
+
this._appResources = options.appResources ?? null;
|
|
298
|
+
this._processorUnusablePolicy =
|
|
299
|
+
options.processorUnusablePolicy ?? ProcessorUnusablePolicy.CONTINUE;
|
|
300
|
+
const idleTimeoutFrames = options.idleTimeoutFrames ?? [
|
|
301
|
+
BotSpeakingFrame,
|
|
302
|
+
InterimTranscriptionFrame,
|
|
303
|
+
TranscriptionFrame,
|
|
304
|
+
UserSpeakingFrame,
|
|
305
|
+
UserStartedSpeakingFrame,
|
|
306
|
+
];
|
|
307
|
+
// Like upstream (`observers or []`), observers are added to the caller's
|
|
308
|
+
// list, unless it is empty (falsy in Python), in which case a new one is used.
|
|
309
|
+
const observers = options.observers && options.observers.length > 0 ? options.observers : [];
|
|
310
|
+
if (this._enableTurnTracking) {
|
|
311
|
+
this._turnTrackingObserver = new TurnTrackingObserver();
|
|
312
|
+
observers.push(this._turnTrackingObserver);
|
|
313
|
+
}
|
|
314
|
+
if (this._enableTracing && this._turnTrackingObserver) {
|
|
315
|
+
// Create pipeline-scoped tracing context
|
|
316
|
+
this._tracingContext = new TracingContext();
|
|
317
|
+
// Create latency observer for tracing
|
|
318
|
+
this._userBotLatencyObserver = new UserBotLatencyObserver();
|
|
319
|
+
observers.push(this._userBotLatencyObserver);
|
|
320
|
+
// Create turn trace observer with latency tracking
|
|
321
|
+
this._turnTraceObserver = new TurnTraceObserver({
|
|
322
|
+
turnTracker: this._turnTrackingObserver,
|
|
323
|
+
latencyTracker: this._userBotLatencyObserver,
|
|
324
|
+
conversationId: this._conversationId,
|
|
325
|
+
additionalSpanAttributes: this._additionalSpanAttributes,
|
|
326
|
+
tracingContext: this._tracingContext,
|
|
327
|
+
});
|
|
328
|
+
observers.push(this._turnTraceObserver);
|
|
329
|
+
}
|
|
330
|
+
// RTVI support
|
|
331
|
+
const enableRtvi = options.enableRtvi ?? bridged === null;
|
|
332
|
+
let prependRtvi = false;
|
|
333
|
+
const externalRtvi = firstOf(this._iterProcessors(pipeline, [RTVIProcessor]));
|
|
334
|
+
const externalObserverFound = observers.some((o) => o instanceof RTVIObserver);
|
|
335
|
+
if (externalRtvi && !externalObserverFound) {
|
|
336
|
+
logger.error(`${this}: RTVIProcessor found in pipeline but no RTVIObserver in observers. ` +
|
|
337
|
+
"Make sure to add both.");
|
|
338
|
+
}
|
|
339
|
+
else if (!externalRtvi && externalObserverFound) {
|
|
340
|
+
logger.error(`${this}: RTVIObserver found in observers but no RTVIProcessor in pipeline. ` +
|
|
341
|
+
"Make sure to add both.");
|
|
342
|
+
}
|
|
343
|
+
else if (externalRtvi && externalObserverFound) {
|
|
344
|
+
logger.warning(`${this}: RTVIProcessor and RTVIObserver found, skipping default ones. ` +
|
|
345
|
+
"They are both added by default, no need to add them yourself.");
|
|
346
|
+
this._rtvi = externalRtvi;
|
|
347
|
+
}
|
|
348
|
+
else if (enableRtvi) {
|
|
349
|
+
this._rtvi = options.rtviProcessor ?? new RTVIProcessor();
|
|
350
|
+
observers.push(this._rtvi.createRtviObserver({ params: options.rtviObserverParams ?? null }));
|
|
351
|
+
prependRtvi = true;
|
|
352
|
+
}
|
|
353
|
+
if (this._rtvi) {
|
|
354
|
+
// Automatically call RTVIProcessor.setBotReady()
|
|
355
|
+
this.rtvi.eventHandler("on_client_ready", async (rtvi) => {
|
|
356
|
+
await rtvi.setBotReady();
|
|
357
|
+
});
|
|
358
|
+
// Republish inbound client UI messages onto the bus so UIWorker
|
|
359
|
+
// subscribers can dispatch them.
|
|
360
|
+
this.rtvi.eventHandler("on_ui_message", async (_rtvi, message) => {
|
|
361
|
+
await this._republishUiMessageOnBus(message);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
if (this._idleTimeoutSecs) {
|
|
365
|
+
const idleFrameObserver = new IdleFrameObserver({
|
|
366
|
+
idleEvent: this._idleEvent,
|
|
367
|
+
idleTimeoutFrames,
|
|
368
|
+
});
|
|
369
|
+
observers.push(idleFrameObserver);
|
|
370
|
+
}
|
|
371
|
+
// When bridged, wrap the user pipeline with bus edge processors so frames
|
|
372
|
+
// tee onto the bus at the source/sink and incoming bus frames are
|
|
373
|
+
// injected back into the local pipeline. The edges read the worker's bus
|
|
374
|
+
// lazily, so the bus only needs to be set (via `attach()`) before `run()`
|
|
375
|
+
// is called.
|
|
376
|
+
let userPipeline = pipeline;
|
|
377
|
+
if (bridged !== null) {
|
|
378
|
+
const edgeSource = new _BusEdgeProcessor({
|
|
379
|
+
worker: this,
|
|
380
|
+
direction: FrameDirection.UPSTREAM,
|
|
381
|
+
excludeFrames: options.excludeFrames ?? null,
|
|
382
|
+
name: `${this}::EdgeSource`,
|
|
383
|
+
});
|
|
384
|
+
const edgeSink = new _BusEdgeProcessor({
|
|
385
|
+
worker: this,
|
|
386
|
+
direction: FrameDirection.DOWNSTREAM,
|
|
387
|
+
excludeFrames: options.excludeFrames ?? null,
|
|
388
|
+
name: `${this}::EdgeSink`,
|
|
389
|
+
});
|
|
390
|
+
userPipeline = new Pipeline([edgeSource, pipeline, edgeSink]);
|
|
391
|
+
}
|
|
392
|
+
// This is the final pipeline. It is composed of a source processor,
|
|
393
|
+
// followed by the user pipeline, and ending with a sink processor. The
|
|
394
|
+
// source allows us to receive and react to upstream frames, and the sink
|
|
395
|
+
// allows us to receive and react to downstream frames.
|
|
396
|
+
this._source = new PipelineSource((frame, direction) => this._sourcePushFrame(frame, direction), {
|
|
397
|
+
name: `${this}::Source`,
|
|
398
|
+
});
|
|
399
|
+
this._sink = new PipelineSink((frame, direction) => this._sinkPushFrame(frame, direction), {
|
|
400
|
+
name: `${this}::Sink`,
|
|
401
|
+
});
|
|
402
|
+
// Only prepend the RTVIProcessor if we created it ourselves. When the
|
|
403
|
+
// user already placed it inside their pipeline we must not insert it
|
|
404
|
+
// again or it will appear twice in the frame chain.
|
|
405
|
+
const processors = prependRtvi && this._rtvi ? [this._rtvi, userPipeline] : [userPipeline];
|
|
406
|
+
this._pipeline = new Pipeline(processors, { source: this._source, sink: this._sink });
|
|
407
|
+
// The worker observer acts as a proxy to the provided observers. This
|
|
408
|
+
// way, we only need to pass a single observer (using the
|
|
409
|
+
// FrameProcessorSetup) which then just acts as a proxy.
|
|
410
|
+
this._observer = new WorkerObserver({ observers });
|
|
411
|
+
this._registerEventHandler("on_frame_reached_upstream");
|
|
412
|
+
this._registerEventHandler("on_frame_reached_downstream");
|
|
413
|
+
this._registerEventHandler("on_heartbeat_timeout");
|
|
414
|
+
this._registerEventHandler("on_idle_timeout");
|
|
415
|
+
this._registerEventHandler("on_pipeline_started");
|
|
416
|
+
this._registerEventHandler("on_pipeline_finished");
|
|
417
|
+
this._registerEventHandler("on_pipeline_timeout");
|
|
418
|
+
this._registerEventHandler("on_setup_timeout");
|
|
419
|
+
this._registerEventHandler("on_pipeline_error");
|
|
420
|
+
// Bridge pipeline lifecycle to the BaseWorker lifecycle so the bus
|
|
421
|
+
// registry sees this worker as ready/finished.
|
|
422
|
+
this.eventHandler("on_pipeline_started", async (_task, _frame) => {
|
|
423
|
+
await this.start();
|
|
424
|
+
});
|
|
425
|
+
this.eventHandler("on_pipeline_finished", async (_task, _frame) => {
|
|
426
|
+
await this.stop();
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/** The pipeline parameters for this worker. */
|
|
430
|
+
get params() {
|
|
431
|
+
return this._params;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* What the bot does in this session.
|
|
435
|
+
*
|
|
436
|
+
* Derived from the media the pipeline's transports send and receive and
|
|
437
|
+
* from `PipelineParams.enableMetrics`, with the `capabilities` passed to the
|
|
438
|
+
* constructor applied on top. A field no transport reports is `null`
|
|
439
|
+
* (unknown). RTVI sends this to the client in `bot-ready`.
|
|
440
|
+
*/
|
|
441
|
+
get capabilities() {
|
|
442
|
+
let derived = new BotCapabilities({ metrics: this._params.enableMetrics });
|
|
443
|
+
for (const processor of this._iterProcessors(this._pipeline, [BaseInputTransport, BaseOutputTransport])) {
|
|
444
|
+
derived = derived.combine(processor.capabilities);
|
|
445
|
+
}
|
|
446
|
+
return derived.override(this._capabilitiesOverride);
|
|
447
|
+
}
|
|
448
|
+
/** Whether this pipeline is bridged onto the bus. */
|
|
449
|
+
get bridged() {
|
|
450
|
+
return this._bridged !== null;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The application-defined resources passed to this worker.
|
|
454
|
+
*
|
|
455
|
+
* This is the same object passed to the constructor as `appResources`. Tool
|
|
456
|
+
* handlers can also access it via `FunctionCallParams.appResources`. The
|
|
457
|
+
* framework returns the original reference; mutations are visible to all
|
|
458
|
+
* callers. `null` if none were passed.
|
|
459
|
+
*/
|
|
460
|
+
get appResources() {
|
|
461
|
+
return this._appResources;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* The full pipeline managed by this pipeline worker. This also includes any
|
|
465
|
+
* internal processors added by the pipeline worker.
|
|
466
|
+
*/
|
|
467
|
+
get pipeline() {
|
|
468
|
+
return this._pipeline;
|
|
469
|
+
}
|
|
470
|
+
/** The turn tracking observer if enabled, or null. */
|
|
471
|
+
get turnTrackingObserver() {
|
|
472
|
+
return this._turnTrackingObserver;
|
|
473
|
+
}
|
|
474
|
+
/** The turn trace observer if enabled, or null. */
|
|
475
|
+
get turnTraceObserver() {
|
|
476
|
+
return this._turnTraceObserver;
|
|
477
|
+
}
|
|
478
|
+
/** The RTVI processor added to the pipeline when RTVI is enabled. */
|
|
479
|
+
get rtvi() {
|
|
480
|
+
if (!this._rtvi) {
|
|
481
|
+
throw new Error(`${this} RTVI is not enabled.`);
|
|
482
|
+
}
|
|
483
|
+
return this._rtvi;
|
|
484
|
+
}
|
|
485
|
+
/** Frame types that trigger the on_frame_reached_upstream event. */
|
|
486
|
+
get reachedUpstreamTypes() {
|
|
487
|
+
return [...this._reachedUpstreamTypes];
|
|
488
|
+
}
|
|
489
|
+
/** Frame types that trigger the on_frame_reached_downstream event. */
|
|
490
|
+
get reachedDownstreamTypes() {
|
|
491
|
+
return [...this._reachedDownstreamTypes];
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Add an observer to monitor pipeline execution.
|
|
495
|
+
*
|
|
496
|
+
* @param observer The observer to add to the pipeline monitoring.
|
|
497
|
+
*/
|
|
498
|
+
addObserver(observer) {
|
|
499
|
+
this._observer.addObserver(observer);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Remove an observer from pipeline monitoring.
|
|
503
|
+
*
|
|
504
|
+
* @param observer The observer to remove from pipeline monitoring.
|
|
505
|
+
*/
|
|
506
|
+
async removeObserver(observer) {
|
|
507
|
+
await this._observer.removeObserver(observer);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Set which frame types trigger the on_frame_reached_upstream event.
|
|
511
|
+
*
|
|
512
|
+
* @param types Frame types to monitor for upstream events.
|
|
513
|
+
*/
|
|
514
|
+
setReachedUpstreamFilter(types) {
|
|
515
|
+
this._reachedUpstreamTypes = new Set(types);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Set which frame types trigger the on_frame_reached_downstream event.
|
|
519
|
+
*
|
|
520
|
+
* @param types Frame types to monitor for downstream events.
|
|
521
|
+
*/
|
|
522
|
+
setReachedDownstreamFilter(types) {
|
|
523
|
+
this._reachedDownstreamTypes = new Set(types);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Add frame types to trigger the on_frame_reached_upstream event.
|
|
527
|
+
*
|
|
528
|
+
* @param types Frame types to add to upstream monitoring.
|
|
529
|
+
*/
|
|
530
|
+
addReachedUpstreamFilter(types) {
|
|
531
|
+
for (const t of types)
|
|
532
|
+
this._reachedUpstreamTypes.add(t);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Add frame types to trigger the on_frame_reached_downstream event.
|
|
536
|
+
*
|
|
537
|
+
* @param types Frame types to add to downstream monitoring.
|
|
538
|
+
*/
|
|
539
|
+
addReachedDownstreamFilter(types) {
|
|
540
|
+
for (const t of types)
|
|
541
|
+
this._reachedDownstreamTypes.add(t);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Check if the pipeline worker has finished execution.
|
|
545
|
+
*
|
|
546
|
+
* This indicates whether the worker has finished, meaning all processors
|
|
547
|
+
* have stopped.
|
|
548
|
+
*
|
|
549
|
+
* @returns True if all processors have stopped and the worker is complete.
|
|
550
|
+
*/
|
|
551
|
+
hasFinished() {
|
|
552
|
+
return this._finished;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Schedule the pipeline to stop after processing all queued frames.
|
|
556
|
+
*
|
|
557
|
+
* Sends an EndFrame to gracefully terminate the pipeline once all current
|
|
558
|
+
* processing is complete.
|
|
559
|
+
*/
|
|
560
|
+
async stopWhenDone() {
|
|
561
|
+
logger.debug(`Task ${this} scheduled to stop when done`);
|
|
562
|
+
await this.queueFrame(new EndFrame());
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Request a graceful end of the session, draining the pipeline first.
|
|
566
|
+
*
|
|
567
|
+
* Whatever this worker has already pushed reaches the end of the pipeline
|
|
568
|
+
* before the session goes away, rather than being cut off wherever it
|
|
569
|
+
* happened to be.
|
|
570
|
+
*
|
|
571
|
+
* @param options.reason Optional human-readable reason for ending.
|
|
572
|
+
*/
|
|
573
|
+
async end(options = {}) {
|
|
574
|
+
await this._drainPipeline();
|
|
575
|
+
await super.end({ reason: options.reason ?? null });
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Activate a worker by name, draining this pipeline when handing over.
|
|
579
|
+
*
|
|
580
|
+
* Deactivating this worker before its pipeline drains would let the target
|
|
581
|
+
* start producing while this worker's output is still in flight, and both
|
|
582
|
+
* would arrive interleaved. A worker that stays active is handing nothing
|
|
583
|
+
* over, so it doesn't wait: the first activation of a session would
|
|
584
|
+
* otherwise wait on the very worker it is about to wake.
|
|
585
|
+
*
|
|
586
|
+
* @param workerName The name of the worker to activate.
|
|
587
|
+
* @param options.args Optional `WorkerActivationArgs` forwarded to the
|
|
588
|
+
* target worker's `onActivated`.
|
|
589
|
+
* @param options.deactivateSelf Whether to deactivate this worker before
|
|
590
|
+
* activating the target. Deactivating this worker drains its pipeline
|
|
591
|
+
* first; staying active does not. A worker that stays active and wants to
|
|
592
|
+
* drain anyway can call `flushPipeline` before this.
|
|
593
|
+
*/
|
|
594
|
+
async activateWorker(workerName, options = {}) {
|
|
595
|
+
const deactivateSelf = options.deactivateSelf ?? false;
|
|
596
|
+
if (deactivateSelf) {
|
|
597
|
+
await this._drainPipeline();
|
|
598
|
+
}
|
|
599
|
+
await super.activateWorker(workerName, { args: options.args ?? null, deactivateSelf });
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Wait for in-flight frames to be processed, if any can be.
|
|
603
|
+
*
|
|
604
|
+
* A pipeline that is not running has no one left to bounce the flush probe
|
|
605
|
+
* back, so waiting on it would only spend the flush timeout.
|
|
606
|
+
* `_processPushTask` is created with the worker's tasks and cleared once
|
|
607
|
+
* they are torn down, so it stands for whether there is a pipeline to
|
|
608
|
+
* drain.
|
|
609
|
+
*/
|
|
610
|
+
async _drainPipeline() {
|
|
611
|
+
if (this._processPushTask === null || this.hasFinished()) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (!(await this.flushPipeline())) {
|
|
615
|
+
logger.warning(`${this}: proceeding without draining; whatever is still in flight will be cut off`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Request the running pipeline to cancel.
|
|
620
|
+
*
|
|
621
|
+
* @param options.reason Optional reason to indicate why the pipeline is being cancelled.
|
|
622
|
+
*/
|
|
623
|
+
async cancel(options = {}) {
|
|
624
|
+
if (!this._finished) {
|
|
625
|
+
await this._cancel({ reason: options.reason ?? null });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Start and manage the pipeline execution until completion or cancellation.
|
|
630
|
+
*
|
|
631
|
+
* @param params Configuration parameters for pipeline execution.
|
|
632
|
+
*/
|
|
633
|
+
async run(params) {
|
|
634
|
+
if (this.hasFinished()) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
// Set the worker up and start setting processors up. Processors are set
|
|
639
|
+
// up in the background, see _watchPipelineSetup().
|
|
640
|
+
if (!(await this._setupWithinTimeout(params))) {
|
|
641
|
+
// Nothing was pushed into the pipeline, so there is nothing to drain:
|
|
642
|
+
// release whatever was set up and give up.
|
|
643
|
+
await this._cleanup(true);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
// Create the worker's tasks and wait for the push task, which feeds
|
|
647
|
+
// frames to the very beginning of our pipeline (i.e. to our controlled
|
|
648
|
+
// source processor).
|
|
649
|
+
await this._createTasks();
|
|
650
|
+
try {
|
|
651
|
+
// Wait for pipeline to finish.
|
|
652
|
+
await this._waitForPipelineFinished();
|
|
653
|
+
}
|
|
654
|
+
catch (e) {
|
|
655
|
+
if (!(e instanceof CancelledError))
|
|
656
|
+
throw e;
|
|
657
|
+
logger.debug(`Pipeline worker ${this} got cancelled from outside...`);
|
|
658
|
+
// We have been cancelled from outside, let's just cancel everything.
|
|
659
|
+
await this._cancel();
|
|
660
|
+
// Wait again for pipeline to finish. This time we have really
|
|
661
|
+
// cancelled, so it should really finish.
|
|
662
|
+
await this._waitForPipelineFinished();
|
|
663
|
+
// Re-throw in case there's more cleanup to do.
|
|
664
|
+
throw e;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
finally {
|
|
668
|
+
// We can reach this point for different reasons:
|
|
669
|
+
//
|
|
670
|
+
// 1. The pipeline worker has finished (try case).
|
|
671
|
+
// 2. By a task cancellation (catch case).
|
|
672
|
+
logger.debug(`Pipeline worker ${this} is finishing...`);
|
|
673
|
+
await this._cancelTasks();
|
|
674
|
+
this._printDanglingTasks();
|
|
675
|
+
await this._cancelChildren();
|
|
676
|
+
this._finished = true;
|
|
677
|
+
logger.debug(`Pipeline worker ${this} has finished`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Queue a single frame to be pushed through the pipeline.
|
|
682
|
+
*
|
|
683
|
+
* Downstream frames are pushed from the beginning of the pipeline. Upstream
|
|
684
|
+
* frames are pushed from the end of the pipeline.
|
|
685
|
+
*
|
|
686
|
+
* @param frame The frame to be processed.
|
|
687
|
+
* @param direction The direction to push the frame. Defaults to downstream.
|
|
688
|
+
*/
|
|
689
|
+
async queueFrame(frame, direction = FrameDirection.DOWNSTREAM) {
|
|
690
|
+
if (direction === FrameDirection.DOWNSTREAM) {
|
|
691
|
+
await this._pushQueue.put(frame);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
await this._sink.queueFrame(frame, direction);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Queue multiple frames to be pushed through the pipeline.
|
|
699
|
+
*
|
|
700
|
+
* Downstream frames are pushed from the beginning of the pipeline. Upstream
|
|
701
|
+
* frames are pushed from the end of the pipeline.
|
|
702
|
+
*
|
|
703
|
+
* @param frames An iterable or async iterable of frames to be processed.
|
|
704
|
+
* @param direction The direction to push the frames. Defaults to downstream.
|
|
705
|
+
*/
|
|
706
|
+
async queueFrames(frames, direction = FrameDirection.DOWNSTREAM) {
|
|
707
|
+
if (Symbol.asyncIterator in frames) {
|
|
708
|
+
for await (const frame of frames) {
|
|
709
|
+
await this.queueFrame(frame, direction);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
else if (Symbol.iterator in frames) {
|
|
713
|
+
for (const frame of frames) {
|
|
714
|
+
await this.queueFrame(frame, direction);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Flush all in-flight frames from the pipeline and wait for it to drain.
|
|
720
|
+
*
|
|
721
|
+
* Pushes a `PipelineFlushFrame` downstream; the sink bounces it back
|
|
722
|
+
* upstream, the source turns it around, and its event is set when it
|
|
723
|
+
* reaches the sink a second time. By then every frame queued ahead of it
|
|
724
|
+
* has been processed, along with anything a processor started by pushing
|
|
725
|
+
* upstream. The probe goes on the worker's push queue, behind whatever is
|
|
726
|
+
* already waiting there, and bypasses any `queueFrame` override (e.g.
|
|
727
|
+
* tool-call deferral).
|
|
728
|
+
*
|
|
729
|
+
* @param timeout Seconds of no progress before giving up. Progress is a
|
|
730
|
+
* frame reaching this worker's sink, or a report from the pipeline
|
|
731
|
+
* answering the probe when it crossed into another worker. A pipeline
|
|
732
|
+
* still working keeps the wait alive, however long it takes; one that is
|
|
733
|
+
* stuck, or that nobody will answer, gives up after this much quiet. On
|
|
734
|
+
* giving up a warning is logged and `false` is returned rather than
|
|
735
|
+
* blocking forever.
|
|
736
|
+
* @returns True if the pipeline drained, false if it went quiet first.
|
|
737
|
+
*/
|
|
738
|
+
async flushPipeline(timeout = 5.0) {
|
|
739
|
+
const event = new Event();
|
|
740
|
+
const probe = new PipelineFlushFrame({ event, origin: this.name });
|
|
741
|
+
logger.debug(`${this}: pushing flush probe downstream`);
|
|
742
|
+
this._flushProgress.set(probe.id, 0);
|
|
743
|
+
try {
|
|
744
|
+
await this._pushQueue.put(probe);
|
|
745
|
+
for (;;) {
|
|
746
|
+
const frames = this._framesReachingSink;
|
|
747
|
+
const reports = this._flushProgress.get(probe.id);
|
|
748
|
+
try {
|
|
749
|
+
await waitFor(() => event.wait(), timeout);
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
catch (e) {
|
|
753
|
+
if (!(e instanceof TimeoutError))
|
|
754
|
+
throw e;
|
|
755
|
+
const reported = this._flushProgress.get(probe.id) !== reports;
|
|
756
|
+
if (this._framesReachingSink === frames && !reported) {
|
|
757
|
+
logger.warning(`${this}: pipeline flush gave up after ${timeout}s of no progress`);
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
if (reported) {
|
|
761
|
+
logger.debug(`${this}: flush still in flight, whoever has it is working`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
finally {
|
|
767
|
+
this._flushProgress.delete(probe.id);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Report progress on a probe from another worker while we hold it.
|
|
772
|
+
*
|
|
773
|
+
* Called by whoever brings the probe into this pipeline, since only they
|
|
774
|
+
* know it is really coming in: every worker on the bus sees it, but most of
|
|
775
|
+
* them have nowhere to put it.
|
|
776
|
+
*
|
|
777
|
+
* @param frame The flush probe entering this pipeline.
|
|
778
|
+
*/
|
|
779
|
+
trackFlushProbe(frame) {
|
|
780
|
+
if (this._handleFlushFrame && frame.origin && frame.origin !== this.name) {
|
|
781
|
+
this._foreignProbes.set(frame.id, frame.origin);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Handle outbound bus messages: TTS playback and RTVI UI translation.
|
|
786
|
+
*
|
|
787
|
+
* Runs the base lifecycle/job dispatch first. A `BusTTSSpeakMessage`
|
|
788
|
+
* targeted at this worker is queued as a `TTSSpeakFrame` (pipelines without
|
|
789
|
+
* a TTS service let it flow through). When this worker owns the RTVI
|
|
790
|
+
* processor, UI carriers produced by a `UIWorker` (`BusUIDataMessage`
|
|
791
|
+
* subclasses) are translated into RTVI frames by `_handleUiBusMessage`;
|
|
792
|
+
* other workers skip the translation.
|
|
793
|
+
*/
|
|
794
|
+
async onBusMessage(message) {
|
|
795
|
+
await super.onBusMessage(message);
|
|
796
|
+
// `BaseWorker.onBusMessage` already drops targeted messages for other
|
|
797
|
+
// workers, but it returns early before reaching here -- re-apply the
|
|
798
|
+
// filter before queueing pipeline frames.
|
|
799
|
+
if (message.target && message.target !== this.name) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (message instanceof BusFrameMessage) {
|
|
803
|
+
await this._queueBridgedFrame(message);
|
|
804
|
+
}
|
|
805
|
+
else if (message instanceof BusFlushProgressMessage) {
|
|
806
|
+
const progress = this._flushProgress.get(message.flushId);
|
|
807
|
+
if (progress !== undefined) {
|
|
808
|
+
this._flushProgress.set(message.flushId, progress + 1);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else if (message instanceof BusTTSSpeakMessage) {
|
|
812
|
+
await this.queueFrame(new TTSSpeakFrame({ text: message.text, appendToContext: message.appendToContext }));
|
|
813
|
+
}
|
|
814
|
+
else if (this._rtvi && message instanceof BusUIDataMessage) {
|
|
815
|
+
await this._handleUiBusMessage(message);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Queue a frame that reached this worker over the bus.
|
|
820
|
+
*
|
|
821
|
+
* Queued rather than pushed from the edge that captured the outbound
|
|
822
|
+
* direction, so bus inbound serialises with the frames this worker queues
|
|
823
|
+
* itself (e.g. those a flow framework enqueues from `setNode`).
|
|
824
|
+
*
|
|
825
|
+
* @param message The frame carrier to inject.
|
|
826
|
+
*/
|
|
827
|
+
async _queueBridgedFrame(message) {
|
|
828
|
+
if (this._bridged === null) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (message.source === this.name) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (this._bridged.length > 0 && !this._bridged.includes(message.bridge)) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
await this.queueFrame(message.frame, message.direction);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Translate a UI carrier into the matching RTVI frame and queue it.
|
|
841
|
+
*
|
|
842
|
+
* Called only when this worker owns the RTVI processor. The `RTVIObserver`
|
|
843
|
+
* later wraps the queued frame into a typed `ui-command` / `ui-job-group`
|
|
844
|
+
* envelope for the client. Inbound carriers (e.g. `BusUIEventMessage`)
|
|
845
|
+
* match no branch and are ignored.
|
|
846
|
+
*/
|
|
847
|
+
async _handleUiBusMessage(message) {
|
|
848
|
+
let frame = null;
|
|
849
|
+
if (message instanceof BusUICommandMessage) {
|
|
850
|
+
frame = new RTVIUICommandFrame({
|
|
851
|
+
command: message.commandName,
|
|
852
|
+
payload: message.payload,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
else if (message instanceof BusUIJobGroupStartedMessage) {
|
|
856
|
+
frame = new RTVIUIJobGroupFrame({
|
|
857
|
+
data: new RTVI.UIJobGroupStartedData({
|
|
858
|
+
jobId: message.jobId,
|
|
859
|
+
workers: [...(message.workers ?? [])],
|
|
860
|
+
label: message.label,
|
|
861
|
+
cancellable: message.cancellable,
|
|
862
|
+
at: message.at,
|
|
863
|
+
}),
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
else if (message instanceof BusUIJobUpdateMessage) {
|
|
867
|
+
frame = new RTVIUIJobGroupFrame({
|
|
868
|
+
data: new RTVI.UIJobUpdateData({
|
|
869
|
+
jobId: message.jobId,
|
|
870
|
+
workerName: message.workerName,
|
|
871
|
+
data: message.data,
|
|
872
|
+
at: message.at,
|
|
873
|
+
}),
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
else if (message instanceof BusUIJobCompletedMessage) {
|
|
877
|
+
frame = new RTVIUIJobGroupFrame({
|
|
878
|
+
data: new RTVI.UIJobCompletedData({
|
|
879
|
+
jobId: message.jobId,
|
|
880
|
+
workerName: message.workerName,
|
|
881
|
+
status: message.status,
|
|
882
|
+
response: message.response,
|
|
883
|
+
at: message.at,
|
|
884
|
+
}),
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
else if (message instanceof BusUIJobGroupCompletedMessage) {
|
|
888
|
+
frame = new RTVIUIJobGroupFrame({
|
|
889
|
+
data: new RTVI.UIJobGroupCompletedData({
|
|
890
|
+
jobId: message.jobId,
|
|
891
|
+
at: message.at,
|
|
892
|
+
}),
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
if (frame !== null) {
|
|
896
|
+
await this.queueFrame(frame);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Republish an inbound client UI message onto the bus.
|
|
901
|
+
*
|
|
902
|
+
* Translates a typed RTVI UI message from the client (`UIEventMessage`,
|
|
903
|
+
* `UISnapshotMessage`, or `UICancelJobGroupMessage`) into a single
|
|
904
|
+
* `BusUIEventMessage` carrier so `UIWorker` subscribers can dispatch it.
|
|
905
|
+
* This is the inbound counterpart of `onBusMessage`. Unrecognized message
|
|
906
|
+
* types are ignored.
|
|
907
|
+
*/
|
|
908
|
+
async _republishUiMessageOnBus(message) {
|
|
909
|
+
let eventName;
|
|
910
|
+
let payload;
|
|
911
|
+
if (message instanceof RTVI.UIEventMessage) {
|
|
912
|
+
eventName = message.data.event;
|
|
913
|
+
payload = message.data.payload;
|
|
914
|
+
}
|
|
915
|
+
else if (message instanceof RTVI.UISnapshotMessage) {
|
|
916
|
+
eventName = UI_SNAPSHOT_EVENT_NAME;
|
|
917
|
+
payload = message.data.tree.modelDump({ excludeNone: true });
|
|
918
|
+
}
|
|
919
|
+
else if (message instanceof RTVI.UICancelJobGroupMessage) {
|
|
920
|
+
eventName = UI_CANCEL_JOB_GROUP_EVENT_NAME;
|
|
921
|
+
payload = {
|
|
922
|
+
job_id: message.data.jobId,
|
|
923
|
+
reason: message.data.reason,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
await this.sendBusMessage(new BusUIEventMessage({
|
|
930
|
+
source: this.name,
|
|
931
|
+
target: null,
|
|
932
|
+
eventName,
|
|
933
|
+
payload,
|
|
934
|
+
}));
|
|
935
|
+
}
|
|
936
|
+
/** Stop processors still setting up; they keep holding the StartFrame. */
|
|
937
|
+
async _cancelPipelineSetup() {
|
|
938
|
+
if (this._pipelineSetupTask && !this._pipelineSetupTask.done()) {
|
|
939
|
+
await this.cancelTask(this._pipelineSetupTask);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/** Stop waiting for the pipeline to start, so it is only cleaned up. */
|
|
943
|
+
async _cancelPipelineStart() {
|
|
944
|
+
if (this._pipelineStartTask && !this._pipelineStartTask.done()) {
|
|
945
|
+
await this.cancelTask(this._pipelineStartTask);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Internal cancellation logic for the pipeline worker.
|
|
950
|
+
*
|
|
951
|
+
* @param options.reason Optional reason to indicate why the pipeline is being cancelled.
|
|
952
|
+
*/
|
|
953
|
+
async _cancel(options = {}) {
|
|
954
|
+
if (!this._cancelled) {
|
|
955
|
+
logger.debug(`Cancelling pipeline worker ${this}`);
|
|
956
|
+
this._cancelled = true;
|
|
957
|
+
// Only pushed into a started pipeline: if the StartFrame hasn't reached
|
|
958
|
+
// the end, cancelling the start task below means the pipeline is only
|
|
959
|
+
// cleaned up and this CancelFrame is never pushed.
|
|
960
|
+
await this.queueFrame(new CancelFrame({ reason: options.reason ?? null }));
|
|
961
|
+
// Only request them: a processor's setup() may be what is cancelling,
|
|
962
|
+
// and awaiting the setup task from inside it never returns.
|
|
963
|
+
if (this._pipelineSetupTask && !this._pipelineSetupTask.done()) {
|
|
964
|
+
this._pipelineSetupTask.cancel();
|
|
965
|
+
}
|
|
966
|
+
if (this._pipelineStartTask && !this._pipelineStartTask.done()) {
|
|
967
|
+
this._pipelineStartTask.cancel();
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/** Create and start all pipeline processing tasks. */
|
|
972
|
+
async _createTasks() {
|
|
973
|
+
this._processPushTask = this.createTask(() => this._processPushQueue(), "_process_push_queue");
|
|
974
|
+
this._setupWatchTask = this.createTask(() => this._watchPipelineSetup(), "_watch_pipeline_setup");
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Give up on a pipeline whose processors don't finish setting up.
|
|
978
|
+
*
|
|
979
|
+
* Setting up is cancelled, so the processors still being set up keep
|
|
980
|
+
* holding the StartFrame: the pipeline never starts and is only cleaned up,
|
|
981
|
+
* as when setting the worker up times out.
|
|
982
|
+
*/
|
|
983
|
+
async _watchPipelineSetup() {
|
|
984
|
+
const setupTask = this._pipelineSetupTask;
|
|
985
|
+
if (!setupTask) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
try {
|
|
989
|
+
await waitFor(() => shield(setupTask), this._setupTimeoutSecs);
|
|
990
|
+
}
|
|
991
|
+
catch (e) {
|
|
992
|
+
if (!(e instanceof TimeoutError))
|
|
993
|
+
throw e;
|
|
994
|
+
logger.error(`${this}: timeout setting the pipeline up ` +
|
|
995
|
+
"(a processor blocked while connecting?), stopping the pipeline.");
|
|
996
|
+
await this._callEventHandler("on_setup_timeout");
|
|
997
|
+
// Not _cancel(), which does nothing if the worker was cancelled before
|
|
998
|
+
// setting up began.
|
|
999
|
+
await this._cancelPipelineSetup();
|
|
1000
|
+
await this._cancelPipelineStart();
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/** Start heartbeat tasks if heartbeats are enabled and not already running. */
|
|
1004
|
+
_maybeStartHeartbeatTasks() {
|
|
1005
|
+
if (this._params.enableHeartbeats && this._heartbeatPushTask === null) {
|
|
1006
|
+
this._heartbeatPushTask = this.createTask(() => this._heartbeatPushHandler(), "_heartbeat_push_handler");
|
|
1007
|
+
this._heartbeatMonitorTask = this.createTask(() => this._heartbeatMonitorHandler(), "_heartbeat_monitor_handler");
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
/** Start the idle monitoring task if an idle timeout is configured. */
|
|
1011
|
+
_maybeStartIdleTask() {
|
|
1012
|
+
if (this._idleTimeoutSecs) {
|
|
1013
|
+
this._idleMonitorTask = this.createTask(() => this._idleMonitorHandler(), "_idle_monitor_handler");
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/** Cancel all running pipeline tasks. */
|
|
1017
|
+
async _cancelTasks() {
|
|
1018
|
+
await this._cancelPipelineSetup();
|
|
1019
|
+
await this._cancelPipelineStart();
|
|
1020
|
+
if (this._setupWatchTask) {
|
|
1021
|
+
await this.cancelTask(this._setupWatchTask);
|
|
1022
|
+
this._setupWatchTask = null;
|
|
1023
|
+
}
|
|
1024
|
+
if (this._processPushTask) {
|
|
1025
|
+
await this.cancelTask(this._processPushTask);
|
|
1026
|
+
this._processPushTask = null;
|
|
1027
|
+
}
|
|
1028
|
+
await this._maybeCancelHeartbeatTasks();
|
|
1029
|
+
await this._maybeCancelIdleTask();
|
|
1030
|
+
}
|
|
1031
|
+
/** Cancel heartbeat tasks if they are running. */
|
|
1032
|
+
async _maybeCancelHeartbeatTasks() {
|
|
1033
|
+
if (!this._params.enableHeartbeats) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
if (this._heartbeatPushTask) {
|
|
1037
|
+
await this.cancelTask(this._heartbeatPushTask);
|
|
1038
|
+
this._heartbeatPushTask = null;
|
|
1039
|
+
}
|
|
1040
|
+
if (this._heartbeatMonitorTask) {
|
|
1041
|
+
await this.cancelTask(this._heartbeatMonitorTask);
|
|
1042
|
+
this._heartbeatMonitorTask = null;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
/** Cancel the idle monitoring task if it is running. */
|
|
1046
|
+
async _maybeCancelIdleTask() {
|
|
1047
|
+
if (this._idleMonitorTask) {
|
|
1048
|
+
await this.cancelTask(this._idleMonitorTask);
|
|
1049
|
+
this._idleMonitorTask = null;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/** Create an initial metrics frame with zero values for all processors. */
|
|
1053
|
+
_initialMetricsFrame() {
|
|
1054
|
+
const processors = this._pipeline.processorsWithMetrics();
|
|
1055
|
+
const data = [];
|
|
1056
|
+
for (const p of processors) {
|
|
1057
|
+
data.push(new TTFBMetricsData({ processor: p.name, value: 0.0 }));
|
|
1058
|
+
data.push(new ProcessingMetricsData({ processor: p.name, value: 0.0 }));
|
|
1059
|
+
}
|
|
1060
|
+
return new MetricsFrame({ data });
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Push the StartFrame and wait for the pipeline to start.
|
|
1064
|
+
*
|
|
1065
|
+
* Pushing it waits too, while the pipeline is still held, so cancelling
|
|
1066
|
+
* this task gives up on both.
|
|
1067
|
+
*
|
|
1068
|
+
* @returns Whether the pipeline started; see `_waitForPipelineStart`.
|
|
1069
|
+
*/
|
|
1070
|
+
async _startPipeline() {
|
|
1071
|
+
// Processors read the pipeline configuration from FrameProcessorSetup.
|
|
1072
|
+
const frame = new StartFrame();
|
|
1073
|
+
frame.metadata = this._createStartMetadata();
|
|
1074
|
+
await this._pipeline.queueFrame(frame);
|
|
1075
|
+
return await this._waitForPipelineStart(frame);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Wait for the start task, and return whether it started the pipeline (a
|
|
1079
|
+
* cancelled one didn't).
|
|
1080
|
+
*/
|
|
1081
|
+
async _waitForStartTask() {
|
|
1082
|
+
const startTask = this._pipelineStartTask;
|
|
1083
|
+
if (!startTask) {
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
await wait([startTask]);
|
|
1087
|
+
if (startTask.cancelled()) {
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
return Boolean(startTask.result());
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Wait for the specified start frame to reach the end of the pipeline.
|
|
1094
|
+
*
|
|
1095
|
+
* @returns Whether the pipeline started. A pipeline that doesn't start
|
|
1096
|
+
* within `startTimeoutSecs` is torn down, since nothing pushed into it
|
|
1097
|
+
* afterwards would be processed.
|
|
1098
|
+
*/
|
|
1099
|
+
async _waitForPipelineStart(frame) {
|
|
1100
|
+
logger.debug(`${this}: Starting. Waiting for ${frame} to reach the end of the pipeline...`);
|
|
1101
|
+
// Setting up has its own timeout, so the start timeout only counts from
|
|
1102
|
+
// the moment every processor is set up.
|
|
1103
|
+
if (this._pipelineSetupTask) {
|
|
1104
|
+
await wait([this._pipelineSetupTask]);
|
|
1105
|
+
}
|
|
1106
|
+
try {
|
|
1107
|
+
await waitFor(() => this._pipelineStartEvent.wait(), this._startTimeoutSecs);
|
|
1108
|
+
}
|
|
1109
|
+
catch (e) {
|
|
1110
|
+
if (!(e instanceof TimeoutError))
|
|
1111
|
+
throw e;
|
|
1112
|
+
logger.error(`${this}: timeout waiting for ${frame} to reach the end of the pipeline ` +
|
|
1113
|
+
"(being blocked somewhere?), stopping the pipeline.");
|
|
1114
|
+
await this._callEventHandler("on_pipeline_timeout", frame);
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
this._pipelineStartEvent.clear();
|
|
1118
|
+
logger.debug(`${this}: ${frame} reached the end of the pipeline, pipeline is now ready.`);
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
/** Wait for the specified frame to reach the end of the pipeline. */
|
|
1122
|
+
async _waitForPipelineEnd(frame) {
|
|
1123
|
+
const waitForCancel = async () => {
|
|
1124
|
+
try {
|
|
1125
|
+
await waitFor(() => this._pipelineEndEvent.wait(), this._cancelTimeoutSecs);
|
|
1126
|
+
logger.debug(`${this}: ${frame} reached the end of the pipeline.`);
|
|
1127
|
+
}
|
|
1128
|
+
catch (e) {
|
|
1129
|
+
if (!(e instanceof TimeoutError))
|
|
1130
|
+
throw e;
|
|
1131
|
+
logger.warning(`${this}: timeout waiting for ${frame} to reach the end of the pipeline (being blocked somewhere?).`);
|
|
1132
|
+
await this._callEventHandler("on_pipeline_timeout", frame);
|
|
1133
|
+
}
|
|
1134
|
+
finally {
|
|
1135
|
+
await this._callEventHandler("on_pipeline_finished", frame);
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
logger.debug(`${this}: Closing. Waiting for ${frame} to reach the end of the pipeline...`);
|
|
1139
|
+
if (frame instanceof CancelFrame) {
|
|
1140
|
+
await waitForCancel();
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
// Ending flushes what is queued, so cutting the wait short would drop
|
|
1144
|
+
// the audio the EndFrame exists to play out. A processor that could
|
|
1145
|
+
// hold it up watches for that itself.
|
|
1146
|
+
await this._pipelineEndEvent.wait();
|
|
1147
|
+
// In asyncio the processor that pushed the EndFrame keeps running until
|
|
1148
|
+
// it next suspends, so work it does right after pushing it (e.g. the
|
|
1149
|
+
// user aggregator flushing its turn at session end) completes before
|
|
1150
|
+
// teardown. Promise continuations interleave instead, so yield one
|
|
1151
|
+
// macrotask to let that processor finish before cleaning up.
|
|
1152
|
+
await sleep(0);
|
|
1153
|
+
logger.debug(`${this}: ${frame} reached the end of the pipeline, pipeline is closing.`);
|
|
1154
|
+
}
|
|
1155
|
+
this._pipelineEndEvent.clear();
|
|
1156
|
+
// We are really done. Setting `_finishedEvent` makes `BaseWorker.wait()`
|
|
1157
|
+
// resolve for callers awaiting this worker.
|
|
1158
|
+
this._finishedEvent.set();
|
|
1159
|
+
}
|
|
1160
|
+
async _waitForPipelineFinished() {
|
|
1161
|
+
await this._finishedEvent.wait();
|
|
1162
|
+
// Make sure we wait for the push task to complete.
|
|
1163
|
+
if (this._processPushTask) {
|
|
1164
|
+
await this._processPushTask;
|
|
1165
|
+
this._processPushTask = null;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Set up the pipeline worker, bounded by a timeout.
|
|
1170
|
+
*
|
|
1171
|
+
* Processors are only started being set up here; their setup is bounded
|
|
1172
|
+
* separately by `_watchPipelineSetup`.
|
|
1173
|
+
*
|
|
1174
|
+
* @returns Whether the worker was set up. Setup files and observers that
|
|
1175
|
+
* block never let the pipeline start, so setting up is abandoned once
|
|
1176
|
+
* `setupTimeoutSecs` elapses.
|
|
1177
|
+
*/
|
|
1178
|
+
async _setupWithinTimeout(params) {
|
|
1179
|
+
try {
|
|
1180
|
+
await waitFor(() => this._setup(params), this._setupTimeoutSecs);
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
catch (e) {
|
|
1184
|
+
if (!(e instanceof TimeoutError))
|
|
1185
|
+
throw e;
|
|
1186
|
+
logger.error(`${this}: timeout setting the pipeline up ` +
|
|
1187
|
+
"(a processor blocked while connecting?), stopping the pipeline.");
|
|
1188
|
+
await this._callEventHandler("on_setup_timeout");
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
/** Set up the pipeline worker and all processors. */
|
|
1193
|
+
async _setup(params) {
|
|
1194
|
+
// Processors connect while they are set up and push frames as they do, so
|
|
1195
|
+
// the clock runs from here rather than from the StartFrame, which would
|
|
1196
|
+
// leave those frames timestamped zero.
|
|
1197
|
+
this._clock.start();
|
|
1198
|
+
await super.setup(this._taskManager ?? params.taskManager);
|
|
1199
|
+
// Do any additional pipeline worker setup externally.
|
|
1200
|
+
await this._loadSetupFiles();
|
|
1201
|
+
// Start worker observer.
|
|
1202
|
+
await this._observer.setup(this.taskManager);
|
|
1203
|
+
// Setup processors
|
|
1204
|
+
const setup = new FrameProcessorSetup({
|
|
1205
|
+
audioInSampleRate: this._params.audioInSampleRate,
|
|
1206
|
+
audioOutSampleRate: this._params.audioOutSampleRate,
|
|
1207
|
+
clock: this._clock,
|
|
1208
|
+
enableMetrics: this._params.enableMetrics,
|
|
1209
|
+
enableTracing: this._enableTracing,
|
|
1210
|
+
enableUsageMetrics: this._params.enableUsageMetrics,
|
|
1211
|
+
observer: this._observer,
|
|
1212
|
+
pipelineWorker: this,
|
|
1213
|
+
reportOnlyInitialTtfb: this._params.reportOnlyInitialTtfb,
|
|
1214
|
+
taskManager: this.taskManager,
|
|
1215
|
+
tracingContext: this._tracingContext,
|
|
1216
|
+
});
|
|
1217
|
+
this._pipeline._setupStarted();
|
|
1218
|
+
const setupTask = this.createTask(() => this._pipeline.setup(setup), "setup");
|
|
1219
|
+
this._pipelineSetupTask = setupTask;
|
|
1220
|
+
// The StartFrame is pushed right away and each processor holds it until
|
|
1221
|
+
// its own setup is done (see BasePipeline._setupProcessors()).
|
|
1222
|
+
setupTask.addDoneCallback((task) => this._pipelineSetupDone(task));
|
|
1223
|
+
// Created together, so a cancel always finds both: with the setup
|
|
1224
|
+
// cancelled but no start task to cancel, the start would wait forever on
|
|
1225
|
+
// a pipeline that is never released.
|
|
1226
|
+
this._pipelineStartTask = this.createTask(() => this._startPipeline(), "_start_pipeline");
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Release the pipeline once it is set up, unless setting up was abandoned.
|
|
1230
|
+
*
|
|
1231
|
+
* A cancelled setup (timed out, or the worker was cancelled) keeps the
|
|
1232
|
+
* StartFrame held, so the pipeline never starts and is only cleaned up.
|
|
1233
|
+
*/
|
|
1234
|
+
_pipelineSetupDone(task) {
|
|
1235
|
+
if (!task.cancelled()) {
|
|
1236
|
+
this._pipeline._setupFinished();
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
/** Clean up the pipeline worker and processors. */
|
|
1240
|
+
async _cleanup(cleanupPipeline) {
|
|
1241
|
+
// Cleanup base object.
|
|
1242
|
+
await this.cleanup();
|
|
1243
|
+
// Cleanup observers.
|
|
1244
|
+
await this._observer.cleanup();
|
|
1245
|
+
// End conversation tracing if it's active - this will also close any
|
|
1246
|
+
// active turn span
|
|
1247
|
+
if (this._enableTracing && this._turnTraceObserver) {
|
|
1248
|
+
this._turnTraceObserver.endConversationTracing();
|
|
1249
|
+
}
|
|
1250
|
+
// Cleanup pipeline processors.
|
|
1251
|
+
if (cleanupPipeline) {
|
|
1252
|
+
await this._pipeline.cleanup();
|
|
1253
|
+
}
|
|
1254
|
+
// Nothing left to answer a probe we are still holding.
|
|
1255
|
+
this._foreignProbes.clear();
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Cancel the children once this worker's pipeline is over.
|
|
1259
|
+
*
|
|
1260
|
+
* A pipeline that ends on its own (an `EndFrame` it queued itself, an idle
|
|
1261
|
+
* timeout, a permanent error) is never told to end over the bus, so nothing
|
|
1262
|
+
* has passed the end on to its children. A child that has already finished
|
|
1263
|
+
* takes no notice.
|
|
1264
|
+
*/
|
|
1265
|
+
async _cancelChildren() {
|
|
1266
|
+
for (const child of this._children) {
|
|
1267
|
+
await this.sendBusMessage(new BusCancelWorkerMessage({
|
|
1268
|
+
source: this.name,
|
|
1269
|
+
target: child.name,
|
|
1270
|
+
reason: `${this.name} finished`,
|
|
1271
|
+
}));
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* End the pipeline after propagating end to children.
|
|
1276
|
+
*
|
|
1277
|
+
* Drives shutdown through the pipeline (`EndFrame`) so `_finishedEvent`
|
|
1278
|
+
* fires once the frame drains through the sink, rather than calling
|
|
1279
|
+
* `stop()` directly.
|
|
1280
|
+
*/
|
|
1281
|
+
async _handleWorkerEnd(message) {
|
|
1282
|
+
logger.debug(`Worker '${this}': received end`);
|
|
1283
|
+
await this._propagateEndToChildren(message);
|
|
1284
|
+
await this.queueFrame(new EndFrame({ reason: message.reason }));
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Cancel the pipeline after propagating cancel to children.
|
|
1288
|
+
*
|
|
1289
|
+
* Drives shutdown through the pipeline (`CancelFrame`) so `_finishedEvent`
|
|
1290
|
+
* fires once the frame drains, rather than calling `stop()` directly.
|
|
1291
|
+
*
|
|
1292
|
+
* Children are told whatever state this worker is in: one that cancelled
|
|
1293
|
+
* itself earlier, on an idle timeout or a permanent error, still has to
|
|
1294
|
+
* pass the runner's cancel on.
|
|
1295
|
+
*/
|
|
1296
|
+
async _handleWorkerCancel(message) {
|
|
1297
|
+
logger.debug(`Worker '${this}': received cancel`);
|
|
1298
|
+
await this._propagateCancelToChildren(message);
|
|
1299
|
+
await this.cancel({ reason: message.reason });
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Process frames from the push queue and send them through the pipeline.
|
|
1303
|
+
*
|
|
1304
|
+
* Once the start task has started the pipeline, it pushes the frames queued
|
|
1305
|
+
* by the user. It runs until the worker is cancelled or stopped (e.g. with
|
|
1306
|
+
* an EndFrame).
|
|
1307
|
+
*/
|
|
1308
|
+
async _processPushQueue() {
|
|
1309
|
+
// No other frame goes in before the pipeline has started; one that
|
|
1310
|
+
// hasn't started is only cleaned up.
|
|
1311
|
+
let running = await this._waitForStartTask();
|
|
1312
|
+
if (running) {
|
|
1313
|
+
this._maybeStartIdleTask();
|
|
1314
|
+
}
|
|
1315
|
+
if (running && this._params.enableMetrics && this._params.sendInitialEmptyMetrics) {
|
|
1316
|
+
await this._pipeline.queueFrame(this._initialMetricsFrame());
|
|
1317
|
+
}
|
|
1318
|
+
// A pipeline that never started can't process anything we push into it,
|
|
1319
|
+
// so skip straight to cleanup.
|
|
1320
|
+
let cleanupPipeline = true;
|
|
1321
|
+
while (running) {
|
|
1322
|
+
const frame = await this._pushQueue.get();
|
|
1323
|
+
await this._pipeline.queueFrame(frame);
|
|
1324
|
+
const isLast = frame instanceof CancelFrame || frame instanceof EndFrame || frame instanceof StopFrame;
|
|
1325
|
+
if (isLast) {
|
|
1326
|
+
await this._waitForPipelineEnd(frame);
|
|
1327
|
+
}
|
|
1328
|
+
running = !isLast;
|
|
1329
|
+
cleanupPipeline = !(frame instanceof StopFrame);
|
|
1330
|
+
this._pushQueue.taskDone();
|
|
1331
|
+
}
|
|
1332
|
+
await this._cleanup(cleanupPipeline);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Move a flush probe along its trip, settling it once it is done.
|
|
1336
|
+
*
|
|
1337
|
+
* The probe travels down to the sink, back up to the source, then down
|
|
1338
|
+
* again, and only the second arrival at the sink settles it. The extra leg
|
|
1339
|
+
* is what makes the probe wait for work a processor starts by pushing
|
|
1340
|
+
* upstream: an `LLMContextFrame` pushed up after a function call result,
|
|
1341
|
+
* say, whose response only comes back down afterwards. A probe that stopped
|
|
1342
|
+
* at the source would return while that response was still being
|
|
1343
|
+
* generated, or still being synthesized.
|
|
1344
|
+
*
|
|
1345
|
+
* @param frame The probe, carrying the event its initiator awaits.
|
|
1346
|
+
* @param atSink Whether the probe has arrived at the sink or the source.
|
|
1347
|
+
*/
|
|
1348
|
+
async _advanceFlushProbe(frame, atSink) {
|
|
1349
|
+
if (!atSink) {
|
|
1350
|
+
logger.debug(`${this}: flush probe reached source — sending it down again`);
|
|
1351
|
+
frame.returning = true;
|
|
1352
|
+
await this._source.pushFrame(frame, FrameDirection.DOWNSTREAM);
|
|
1353
|
+
}
|
|
1354
|
+
else if (!frame.returning) {
|
|
1355
|
+
logger.debug(`${this}: flush probe reached sink — bouncing upstream`);
|
|
1356
|
+
await this._sink.pushFrame(frame, FrameDirection.UPSTREAM);
|
|
1357
|
+
}
|
|
1358
|
+
else {
|
|
1359
|
+
logger.debug(`${this}: flush probe reached sink again — pipeline drained`);
|
|
1360
|
+
this._foreignProbes.delete(frame.id);
|
|
1361
|
+
if (frame.event) {
|
|
1362
|
+
frame.event.set();
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Tell whoever is waiting on a probe we hold that we are still working.
|
|
1368
|
+
*
|
|
1369
|
+
* Their own sink sees nothing while we drain, so without this they can only
|
|
1370
|
+
* conclude the pipeline has gone quiet. Sent no more than once a second: it
|
|
1371
|
+
* says the flush is alive, not how much is left.
|
|
1372
|
+
*/
|
|
1373
|
+
async _reportFlushProgress() {
|
|
1374
|
+
const now = monotonic();
|
|
1375
|
+
if (now - this._flushProgressSent < FLUSH_PROGRESS_PERIOD_SECS) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
this._flushProgressSent = now;
|
|
1379
|
+
// Over a snapshot: sending suspends on a network bus, and a probe arriving
|
|
1380
|
+
// meanwhile would change the map under the loop.
|
|
1381
|
+
for (const [flushId, origin] of [...this._foreignProbes.entries()]) {
|
|
1382
|
+
await this.bus.send(new BusFlushProgressMessage({ source: this.name, target: origin, flushId }));
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Process frames coming upstream from the pipeline.
|
|
1387
|
+
*
|
|
1388
|
+
* These frames might indicate, for example, that we want the pipeline to be
|
|
1389
|
+
* stopped (e.g. EndWorkerFrame) in which case we would send an EndFrame
|
|
1390
|
+
* down the pipeline.
|
|
1391
|
+
*/
|
|
1392
|
+
async _sourcePushFrame(frame, _direction) {
|
|
1393
|
+
if (isInstanceOfAny(frame, this._reachedUpstreamTypes)) {
|
|
1394
|
+
await this._callEventHandler("on_frame_reached_upstream", frame);
|
|
1395
|
+
}
|
|
1396
|
+
if (frame instanceof EndWorkerFrame) {
|
|
1397
|
+
// Tell the worker we should end nicely.
|
|
1398
|
+
logger.debug(`${this}: received end worker frame upstream ${frame}`);
|
|
1399
|
+
await this.queueFrame(new EndFrame({ reason: frame.reason }));
|
|
1400
|
+
}
|
|
1401
|
+
else if (frame instanceof CancelWorkerFrame) {
|
|
1402
|
+
// Tell the worker we should end right away.
|
|
1403
|
+
logger.debug(`${this}: received cancel worker frame upstream ${frame}`);
|
|
1404
|
+
await this.queueFrame(new CancelFrame({ reason: frame.reason }));
|
|
1405
|
+
}
|
|
1406
|
+
else if (frame instanceof StopWorkerFrame) {
|
|
1407
|
+
// Tell the worker we should stop nicely.
|
|
1408
|
+
logger.debug(`${this}: received stop worker frame upstream ${frame}`);
|
|
1409
|
+
await this.queueFrame(new StopFrame());
|
|
1410
|
+
}
|
|
1411
|
+
else if (frame instanceof InterruptionWorkerFrame) {
|
|
1412
|
+
// Tell the worker we should interrupt the pipeline. Note that we are
|
|
1413
|
+
// bypassing the push queue and directly queue into the pipeline. This
|
|
1414
|
+
// is in case the push task is blocked waiting for a pipeline-ending
|
|
1415
|
+
// frame to finish traversing the pipeline.
|
|
1416
|
+
logger.debug(`${this}: received interruption worker frame upstream ${frame}`);
|
|
1417
|
+
await this._pipeline.queueFrame(new InterruptionFrame());
|
|
1418
|
+
}
|
|
1419
|
+
else if (frame instanceof ErrorFrame) {
|
|
1420
|
+
await this._callEventHandler("on_pipeline_error", frame);
|
|
1421
|
+
if (frame.processor && !frame.processor.isUsable) {
|
|
1422
|
+
await this._handleUnusableProcessor(frame.processor);
|
|
1423
|
+
}
|
|
1424
|
+
else {
|
|
1425
|
+
logger.warning(`${this}: Something went wrong: ${frame}`);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
else if (frame instanceof PipelineFlushFrame && this._handleFlushFrame) {
|
|
1429
|
+
await this._advanceFlushProbe(frame, false);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Apply the unusable-processor policy, once per processor.
|
|
1434
|
+
*
|
|
1435
|
+
* A processor that can no longer do its job keeps failing for as long as
|
|
1436
|
+
* the pipeline keeps using it, so the policy is applied to the first error
|
|
1437
|
+
* that says so and not to the ones that follow.
|
|
1438
|
+
*
|
|
1439
|
+
* @param processor The processor that can no longer do its job.
|
|
1440
|
+
*/
|
|
1441
|
+
async _handleUnusableProcessor(processor) {
|
|
1442
|
+
if (this._unusableProcessors.has(processor)) {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
this._unusableProcessors.add(processor);
|
|
1446
|
+
logger.error(`${this}: ${processor} can no longer do its job`);
|
|
1447
|
+
if (this._processorUnusablePolicy === ProcessorUnusablePolicy.END) {
|
|
1448
|
+
await this.stopWhenDone();
|
|
1449
|
+
}
|
|
1450
|
+
else if (this._processorUnusablePolicy === ProcessorUnusablePolicy.CANCEL) {
|
|
1451
|
+
await this._cancel({ reason: `${processor} can no longer do its job` });
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Process frames coming downstream from the pipeline.
|
|
1456
|
+
*
|
|
1457
|
+
* Processes frames coming downstream from the pipeline. For example,
|
|
1458
|
+
* heartbeat frames or an EndFrame which would indicate all processors have
|
|
1459
|
+
* handled the EndFrame and therefore we can exit the worker cleanly.
|
|
1460
|
+
*/
|
|
1461
|
+
async _sinkPushFrame(frame, _direction) {
|
|
1462
|
+
if (isInstanceOfAny(frame, this._reachedDownstreamTypes)) {
|
|
1463
|
+
await this._callEventHandler("on_frame_reached_downstream", frame);
|
|
1464
|
+
}
|
|
1465
|
+
// Heartbeats don't count: they arrive whether or not the pipeline is
|
|
1466
|
+
// doing anything, so counting them would make it always look busy.
|
|
1467
|
+
if (!(frame instanceof HeartbeatFrame)) {
|
|
1468
|
+
this._framesReachingSink += 1;
|
|
1469
|
+
if (this._foreignProbes.size > 0) {
|
|
1470
|
+
await this._reportFlushProgress();
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (frame instanceof StartFrame) {
|
|
1474
|
+
await this._callEventHandler("on_pipeline_started", frame);
|
|
1475
|
+
await this._observer.onPipelineStarted();
|
|
1476
|
+
// Start heartbeat tasks now that StartFrame has been processed by all
|
|
1477
|
+
// processors in the pipeline
|
|
1478
|
+
this._maybeStartHeartbeatTasks();
|
|
1479
|
+
this._pipelineStartEvent.set();
|
|
1480
|
+
}
|
|
1481
|
+
else if (frame instanceof EndFrame) {
|
|
1482
|
+
await this._callEventHandler("on_pipeline_finished", frame);
|
|
1483
|
+
this._pipelineEndEvent.set();
|
|
1484
|
+
}
|
|
1485
|
+
else if (frame instanceof StopFrame) {
|
|
1486
|
+
await this._callEventHandler("on_pipeline_finished", frame);
|
|
1487
|
+
this._pipelineEndEvent.set();
|
|
1488
|
+
}
|
|
1489
|
+
else if (frame instanceof CancelFrame) {
|
|
1490
|
+
this._pipelineEndEvent.set();
|
|
1491
|
+
}
|
|
1492
|
+
else if (frame instanceof HeartbeatFrame) {
|
|
1493
|
+
await this._heartbeatQueue.put(frame);
|
|
1494
|
+
}
|
|
1495
|
+
else if (frame instanceof EndWorkerFrame) {
|
|
1496
|
+
logger.debug(`${this}: received end worker frame downstream ${frame}`);
|
|
1497
|
+
await this.queueFrame(new EndWorkerFrame({ reason: frame.reason }), FrameDirection.UPSTREAM);
|
|
1498
|
+
}
|
|
1499
|
+
else if (frame instanceof StopWorkerFrame) {
|
|
1500
|
+
logger.debug(`${this}: received stop worker frame downstream ${frame}`);
|
|
1501
|
+
await this.queueFrame(new StopWorkerFrame(), FrameDirection.UPSTREAM);
|
|
1502
|
+
}
|
|
1503
|
+
else if (frame instanceof CancelWorkerFrame) {
|
|
1504
|
+
logger.debug(`${this}: received cancel worker frame downstream ${frame}`);
|
|
1505
|
+
await this.queueFrame(new CancelWorkerFrame({ reason: frame.reason }), FrameDirection.UPSTREAM);
|
|
1506
|
+
}
|
|
1507
|
+
else if (frame instanceof InterruptionWorkerFrame) {
|
|
1508
|
+
logger.debug(`${this}: received interruption worker frame downstream ${frame}`);
|
|
1509
|
+
await this.queueFrame(new InterruptionWorkerFrame(), FrameDirection.UPSTREAM);
|
|
1510
|
+
}
|
|
1511
|
+
else if (frame instanceof PipelineFlushFrame && this._handleFlushFrame) {
|
|
1512
|
+
await this._advanceFlushProbe(frame, true);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/** Push heartbeat frames at regular intervals. */
|
|
1516
|
+
async _heartbeatPushHandler() {
|
|
1517
|
+
for (;;) {
|
|
1518
|
+
// Don't use `queueFrame()` because if an EndFrame is queued the worker
|
|
1519
|
+
// will just stop waiting for the pipeline to finish not allowing more
|
|
1520
|
+
// frames to be pushed.
|
|
1521
|
+
await this._pipeline.queueFrame(new HeartbeatFrame({ timestamp: this._clock.getTime() }));
|
|
1522
|
+
await sleep(this._params.heartbeatsPeriodSecs);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Monitor heartbeat frames for processing time and timeout detection.
|
|
1527
|
+
*
|
|
1528
|
+
* Logs the time each heartbeat takes to traverse the pipeline. If no
|
|
1529
|
+
* heartbeat arrives within `heartbeatsMonitorSecs`, logs a warning and
|
|
1530
|
+
* fires `on_heartbeat_timeout`. The event fires repeatedly every
|
|
1531
|
+
* `heartbeatsMonitorSecs` for as long as the stall persists.
|
|
1532
|
+
*/
|
|
1533
|
+
async _heartbeatMonitorHandler() {
|
|
1534
|
+
const waitTime = this._params.heartbeatsMonitorSecs;
|
|
1535
|
+
for (;;) {
|
|
1536
|
+
try {
|
|
1537
|
+
const frame = await waitFor(() => this._heartbeatQueue.get(), waitTime);
|
|
1538
|
+
const processTime = (this._clock.getTime() - frame.timestamp) / 1_000_000_000;
|
|
1539
|
+
logger.trace(`${this}: heartbeat frame processed in ${processTime} seconds`);
|
|
1540
|
+
this._heartbeatQueue.taskDone();
|
|
1541
|
+
}
|
|
1542
|
+
catch (e) {
|
|
1543
|
+
if (!(e instanceof TimeoutError))
|
|
1544
|
+
throw e;
|
|
1545
|
+
logger.warning(`${this}: heartbeat frame not received for more than ${waitTime} seconds`);
|
|
1546
|
+
await this._callEventHandler("on_heartbeat_timeout");
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Monitor pipeline activity and detect idle conditions.
|
|
1552
|
+
*
|
|
1553
|
+
* Tracks frame activity and triggers idle timeout events when the pipeline
|
|
1554
|
+
* hasn't received relevant frames within the timeout period.
|
|
1555
|
+
*
|
|
1556
|
+
* Note: Heartbeats are excluded from idle detection.
|
|
1557
|
+
*/
|
|
1558
|
+
async _idleMonitorHandler() {
|
|
1559
|
+
let running = true;
|
|
1560
|
+
while (running) {
|
|
1561
|
+
try {
|
|
1562
|
+
await waitFor(() => this._idleEvent.wait(), this._idleTimeoutSecs);
|
|
1563
|
+
this._idleEvent.clear();
|
|
1564
|
+
}
|
|
1565
|
+
catch (e) {
|
|
1566
|
+
if (!(e instanceof TimeoutError))
|
|
1567
|
+
throw e;
|
|
1568
|
+
running = await this._idleTimeoutDetected();
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Handle idle timeout detection and optional cancellation.
|
|
1574
|
+
*
|
|
1575
|
+
* @returns Whether the pipeline worker should continue running.
|
|
1576
|
+
*/
|
|
1577
|
+
async _idleTimeoutDetected() {
|
|
1578
|
+
// If we are cancelling, just exit the task.
|
|
1579
|
+
if (this._cancelled) {
|
|
1580
|
+
return false;
|
|
1581
|
+
}
|
|
1582
|
+
logger.warning("Idle timeout detected.");
|
|
1583
|
+
await this._callEventHandler("on_idle_timeout");
|
|
1584
|
+
if (!this._cancelOnIdleTimeout) {
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
logger.warning("Idle pipeline detected, cancelling pipeline worker...");
|
|
1588
|
+
await this.cancel({ reason: "idle timeout" });
|
|
1589
|
+
if (this._cancelRunnerOnIdleTimeout) {
|
|
1590
|
+
logger.warning("...and cancelling the runner.");
|
|
1591
|
+
// `BaseWorker.cancel` sends `BusCancelMessage` on the bus so the runner
|
|
1592
|
+
// broadcasts cancellation to every other root worker too. This
|
|
1593
|
+
// worker's pipeline is already cancelling from the call above.
|
|
1594
|
+
await BaseWorker.prototype.cancel.call(this, { reason: "idle timeout" });
|
|
1595
|
+
}
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Run `setupPipelineWorker` from each file in `PIPECAT_SETUP_FILES`.
|
|
1600
|
+
*
|
|
1601
|
+
* A setup file may define `setupPipelineWorker(worker)` to attach event
|
|
1602
|
+
* handlers, observers, or other per-worker wiring.
|
|
1603
|
+
*/
|
|
1604
|
+
async _loadSetupFiles() {
|
|
1605
|
+
await runSetupHook({
|
|
1606
|
+
target: this,
|
|
1607
|
+
functionName: "setup_pipeline_worker",
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
/** Build and return start metadata including user-provided values. */
|
|
1611
|
+
_createStartMetadata() {
|
|
1612
|
+
const startMetadata = {};
|
|
1613
|
+
// Update with user provided metadata.
|
|
1614
|
+
Object.assign(startMetadata, this._params.startMetadata);
|
|
1615
|
+
return startMetadata;
|
|
1616
|
+
}
|
|
1617
|
+
/** Recursively yield the processors of the given type(s) in the pipeline. */
|
|
1618
|
+
*_iterProcessors(processor, processorTypes) {
|
|
1619
|
+
if (processorTypes.some((cls) => processor instanceof cls)) {
|
|
1620
|
+
yield processor;
|
|
1621
|
+
}
|
|
1622
|
+
for (const p of processor.processors) {
|
|
1623
|
+
yield* this._iterProcessors(p, processorTypes);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function firstOf(iterable) {
|
|
1628
|
+
for (const item of iterable)
|
|
1629
|
+
return item;
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
function isInstanceOfAny(frame, types) {
|
|
1633
|
+
for (const cls of types) {
|
|
1634
|
+
if (frame instanceof cls)
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1639
|
+
//# sourceMappingURL=worker.js.map
|