@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,1156 @@
|
|
|
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/workers/ui/ui_worker.py
|
|
6
|
+
/**
|
|
7
|
+
* UIWorker: an LLM worker that observes and drives a client GUI over RTVI.
|
|
8
|
+
*
|
|
9
|
+
* Porting notes:
|
|
10
|
+
* - Deprecated upstream and not ported: `UIWorker.ui_job_group` and
|
|
11
|
+
* `UIWorker.start_ui_job_group` (since 1.8.0; use `jobGroup` /
|
|
12
|
+
* `requestJobGroup`), and the `tts_speak` option of `respond_to_job`
|
|
13
|
+
* (since 1.12.0; respond with the answer and let the voice LLM say it).
|
|
14
|
+
* - `sendCommand` converts an RTVI `BaseModel` payload with `modelDump()`;
|
|
15
|
+
* other objects are forwarded as-is (JavaScript has no dataclasses).
|
|
16
|
+
* - `renderUiEvent` encodes the payload as Python's `json.dumps(payload,
|
|
17
|
+
* default=str)` does (`", "` / `": "` separators, non-ASCII escaped), so the
|
|
18
|
+
* injected `<ui_event>` text is identical across ports.
|
|
19
|
+
*/
|
|
20
|
+
import { Event, rethrowIfCancelled, time } from "@pipedog/asyncio";
|
|
21
|
+
import { BusUICommandMessage, BusUIEventMessage, BusUIJobCompletedMessage, BusUIJobGroupCompletedMessage, BusUIJobGroupStartedMessage, BusUIJobUpdateMessage, UI_CANCEL_JOB_GROUP_EVENT_NAME, UI_SNAPSHOT_EVENT_NAME, } from "../../bus/ui/messages.js";
|
|
22
|
+
import { ChoiceQuestion, ClassifierError, YesNoQuestion, } from "../../classifiers/base_classifier.js";
|
|
23
|
+
import { LLMClassifier } from "../../classifiers/llm/classifier.js";
|
|
24
|
+
import { LLMContextFrame, LLMMessagesAppendFrame, LLMMessagesUpdateFrame, } from "../../frames/frames.js";
|
|
25
|
+
import { JobStatus } from "../../pipeline/job_context.js";
|
|
26
|
+
import { job } from "../../pipeline/job_decorator.js";
|
|
27
|
+
import { LLMSpecificMessage } from "../../processors/aggregators/llm_context.js";
|
|
28
|
+
import { BaseModel, Click, Highlight, ScrollTo, SelectText, SetInputValue, } from "../../processors/frameworks/rtvi/models.js";
|
|
29
|
+
import { logger } from "../../utils/logger.js";
|
|
30
|
+
import { LLMContextWorker } from "../llm/llm_context_worker.js";
|
|
31
|
+
import { _collectUiEventHandlers } from "./ui_event_decorator.js";
|
|
32
|
+
import { UI_STATE_PROMPT_GUIDE } from "./ui_prompts.js";
|
|
33
|
+
/** The confidence below which the classifier's pick of an element is no answer. */
|
|
34
|
+
const ELEMENT_THRESHOLD = 0.5;
|
|
35
|
+
/** The text the user has selected on the page, and the element it is in. */
|
|
36
|
+
export class UISelection {
|
|
37
|
+
ref;
|
|
38
|
+
text;
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.ref = options.ref;
|
|
41
|
+
this.text = options.text;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** What `act` can do to an element, each a command helper on the worker. */
|
|
45
|
+
const ACTIONS = new Set([
|
|
46
|
+
"click",
|
|
47
|
+
"scroll_to",
|
|
48
|
+
"highlight",
|
|
49
|
+
"select_text",
|
|
50
|
+
"set_input_value",
|
|
51
|
+
]);
|
|
52
|
+
/** The method on the worker for each of `ACTIONS` (Python's `getattr(self, action)`). */
|
|
53
|
+
const ACTION_METHODS = {
|
|
54
|
+
click: "click",
|
|
55
|
+
scroll_to: "scrollTo",
|
|
56
|
+
highlight: "highlight",
|
|
57
|
+
select_text: "selectText",
|
|
58
|
+
};
|
|
59
|
+
function isPlainObject(value) {
|
|
60
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61
|
+
}
|
|
62
|
+
function isInt(value) {
|
|
63
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
64
|
+
}
|
|
65
|
+
/** Python's `repr()` of a string (or None), for log lines. */
|
|
66
|
+
function pyRepr(value) {
|
|
67
|
+
if (value === null || value === undefined)
|
|
68
|
+
return "None";
|
|
69
|
+
if (typeof value === "string")
|
|
70
|
+
return `'${value}'`;
|
|
71
|
+
return String(value);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Encode a value as Python's `json.dumps(value, default=str)` does: `", "` and
|
|
75
|
+
* `": "` separators, non-ASCII characters escaped, and anything that is not
|
|
76
|
+
* JSON encoded as its string form.
|
|
77
|
+
*/
|
|
78
|
+
function pyJsonDumps(value) {
|
|
79
|
+
const encodeString = (s) => JSON.stringify(s).replace(/[\u0080-]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
80
|
+
if (value === null || value === undefined)
|
|
81
|
+
return "null";
|
|
82
|
+
if (typeof value === "boolean")
|
|
83
|
+
return value ? "true" : "false";
|
|
84
|
+
if (typeof value === "number") {
|
|
85
|
+
if (Number.isNaN(value))
|
|
86
|
+
return "NaN";
|
|
87
|
+
if (value === Number.POSITIVE_INFINITY)
|
|
88
|
+
return "Infinity";
|
|
89
|
+
if (value === Number.NEGATIVE_INFINITY)
|
|
90
|
+
return "-Infinity";
|
|
91
|
+
return String(value);
|
|
92
|
+
}
|
|
93
|
+
if (typeof value === "string")
|
|
94
|
+
return encodeString(value);
|
|
95
|
+
if (Array.isArray(value))
|
|
96
|
+
return `[${value.map((v) => pyJsonDumps(v)).join(", ")}]`;
|
|
97
|
+
if (isPlainObject(value) && Object.getPrototypeOf(value) === Object.prototype) {
|
|
98
|
+
const entries = Object.entries(value).map(([k, v]) => `${encodeString(k)}: ${pyJsonDumps(v)}`);
|
|
99
|
+
return `{${entries.join(", ")}}`;
|
|
100
|
+
}
|
|
101
|
+
return encodeString(String(value));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Raised by `UIWorker.act` for an action that is not one of the five
|
|
105
|
+
* (upstream: `ValueError`).
|
|
106
|
+
*/
|
|
107
|
+
export class UnknownScreenActionError extends Error {
|
|
108
|
+
name = "UnknownScreenActionError";
|
|
109
|
+
}
|
|
110
|
+
/** A pending job answer (upstream: an `asyncio.Future`). */
|
|
111
|
+
class _PendingAnswer {
|
|
112
|
+
#event = new Event();
|
|
113
|
+
#result = null;
|
|
114
|
+
done() {
|
|
115
|
+
return this.#event.isSet();
|
|
116
|
+
}
|
|
117
|
+
setResult(result) {
|
|
118
|
+
if (this.done()) {
|
|
119
|
+
throw new Error("result is already set");
|
|
120
|
+
}
|
|
121
|
+
this.#result = result;
|
|
122
|
+
this.#event.set();
|
|
123
|
+
}
|
|
124
|
+
async wait() {
|
|
125
|
+
await this.#event.wait();
|
|
126
|
+
return this.#result;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* LLM worker that reads and drives a client GUI over the RTVI UI channel.
|
|
131
|
+
*
|
|
132
|
+
* A `UIWorker` connects an LLM to whatever the user is looking at: it sees the
|
|
133
|
+
* screen as accessibility snapshots, reacts to the user's UI events, and acts
|
|
134
|
+
* on the page by sending commands to the client. It is the delegate side of a
|
|
135
|
+
* voice/UI split -- a voice layer (the main pipeline's LLM, or a separate
|
|
136
|
+
* `LLMWorker`) handles speech and hands screen-relevant work to this worker.
|
|
137
|
+
*
|
|
138
|
+
* Capabilities:
|
|
139
|
+
*
|
|
140
|
+
* - See the screen. The latest accessibility snapshot is rendered as
|
|
141
|
+
* `<ui_state>` and auto-injected into the LLM context before each
|
|
142
|
+
* inference. Code reads it through `snapshot` and the user's `selection`.
|
|
143
|
+
* - React to UI events, dispatched to `uiEvent(name)` handlers.
|
|
144
|
+
* - Drive the UI with `sendCommand` and the `scrollTo` / `highlight` /
|
|
145
|
+
* `selectText` / `click` / `setInputValue` helpers.
|
|
146
|
+
* - Decide small things with a classifier, not an LLM turn: whether a UI
|
|
147
|
+
* event deserves a comment (`shouldRespond`), which element on screen the
|
|
148
|
+
* user means (`whichElement`), whether something is true of the screen
|
|
149
|
+
* (`checkScreen`), which elements match a description (`selectElements`),
|
|
150
|
+
* and `act` on an element named in words.
|
|
151
|
+
* - Answer a voice LLM's questions about the screen through the `screen`
|
|
152
|
+
* job: find an element, check whether something is true, select the
|
|
153
|
+
* elements matching a description, list what is on screen, read the user's
|
|
154
|
+
* selection, or click, scroll to, highlight, select or fill an element.
|
|
155
|
+
* Every answer is short data and never the page; `screenTools` gives the
|
|
156
|
+
* voice LLM the tool that sends it.
|
|
157
|
+
* - Answer as a delegate. The built-in single-flight `respond` job runs one
|
|
158
|
+
* screen-grounded LLM turn and answers with the reply the LLM writes. A
|
|
159
|
+
* `tool` that calls `respondToJob` answers instead when it needs to decide
|
|
160
|
+
* how the answer reaches the user.
|
|
161
|
+
* - Surface long work. Every job group this worker dispatches is reported to
|
|
162
|
+
* the client as it goes: a card when the group starts, a line per worker's
|
|
163
|
+
* progress and completion, and the close when the group completes, whether
|
|
164
|
+
* normally, by cancellation or by timeout. The client can cancel a group
|
|
165
|
+
* dispatched as cancellable.
|
|
166
|
+
*
|
|
167
|
+
* `PipelineWorker` connects a UIWorker to the client automatically when RTVI
|
|
168
|
+
* is enabled -- no extra wiring. A working worker needs only an LLM; subclass
|
|
169
|
+
* it to react to UI events or add jobs and tools, and override `renderQuery`
|
|
170
|
+
* to read a non-default job payload.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* class MyUIWorker extends UIWorker {
|
|
174
|
+
* async onNav(message: BusUIEventMessage) {
|
|
175
|
+
* const view = (message.payload as Record<string, unknown>).view;
|
|
176
|
+
* ...
|
|
177
|
+
* }
|
|
178
|
+
* static {
|
|
179
|
+
* uiEvent("nav_click")(this.prototype.onNav);
|
|
180
|
+
* }
|
|
181
|
+
* }
|
|
182
|
+
*
|
|
183
|
+
* const worker = new MyUIWorker({ name: "ui", llm: new OpenAILLMService({ apiKey: "..." }) });
|
|
184
|
+
*
|
|
185
|
+
* Note: with client `trackViewport` on (the default), off-screen nodes carry
|
|
186
|
+
* `[offscreen]` in `<ui_state>`; `scrollTo` before acting on them.
|
|
187
|
+
*/
|
|
188
|
+
export class UIWorker extends LLMContextWorker {
|
|
189
|
+
_classifier;
|
|
190
|
+
_injectEvents;
|
|
191
|
+
_autoInjectUiState;
|
|
192
|
+
_keepHistory;
|
|
193
|
+
_uiEventHandlers;
|
|
194
|
+
// Latest accessibility snapshot received from the client. Updated in
|
|
195
|
+
// `onBusMessage` when a `__ui_snapshot` event arrives. Rendered into LLM
|
|
196
|
+
// context via `injectUiState`.
|
|
197
|
+
_latestSnapshot = null;
|
|
198
|
+
// Job currently being processed by this worker. Set in `_runLlmTurn`,
|
|
199
|
+
// cleared when the job completes. Lets `tool` methods close out the job
|
|
200
|
+
// without having to thread the job id through every call.
|
|
201
|
+
_currentJob = null;
|
|
202
|
+
// Resolved by `respondToJob` to hand the result back to the in-flight
|
|
203
|
+
// `_runLlmTurn` handler, which then sends the job response.
|
|
204
|
+
_pending = null;
|
|
205
|
+
/** Initialize the UIWorker. */
|
|
206
|
+
constructor(options) {
|
|
207
|
+
super({
|
|
208
|
+
name: options.name,
|
|
209
|
+
llm: options.llm,
|
|
210
|
+
active: true,
|
|
211
|
+
deferToolFrames: true,
|
|
212
|
+
context: options.context ?? null,
|
|
213
|
+
assistantParams: options.assistantParams ?? null,
|
|
214
|
+
});
|
|
215
|
+
// Auto-append the UI wire-format guide to the LLM's system instruction so
|
|
216
|
+
// the author doesn't have to concatenate it manually and it survives the
|
|
217
|
+
// per-job context reset. Pass a string to `promptGuide` to override the
|
|
218
|
+
// text, or null to disable.
|
|
219
|
+
const promptGuide = options.promptGuide === undefined ? UI_STATE_PROMPT_GUIDE : options.promptGuide;
|
|
220
|
+
if (promptGuide) {
|
|
221
|
+
this.llm.appendSystemInstruction(promptGuide);
|
|
222
|
+
}
|
|
223
|
+
this._classifier = options.classifier || new LLMClassifier({ llm: options.llm });
|
|
224
|
+
this._injectEvents = options.injectEvents ?? true;
|
|
225
|
+
this._autoInjectUiState = options.autoInjectUiState ?? true;
|
|
226
|
+
this._keepHistory = options.keepHistory ?? false;
|
|
227
|
+
this._uiEventHandlers = _collectUiEventHandlers(this);
|
|
228
|
+
// Auto-inject the current `<ui_state>` snapshot into the context just
|
|
229
|
+
// before each inference. Driven by the LLM's `on_before_process_frame` so
|
|
230
|
+
// it fires whenever the worker runs its LLM (e.g. a `respond` job),
|
|
231
|
+
// appending the snapshot to the same context the request is built from.
|
|
232
|
+
// The snapshot is a normal, persistent developer message; growth is
|
|
233
|
+
// managed by `keepHistory` + context summarization.
|
|
234
|
+
this.llm.addEventHandler("on_before_process_frame", async (_llm, frame) => {
|
|
235
|
+
if (!(this._autoInjectUiState && frame instanceof LLMContextFrame)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// Only inject on a user-turn-initiating inference, not the follow-up
|
|
239
|
+
// inference the LLM runs after a tool result (which would stack a
|
|
240
|
+
// duplicate `<ui_state>` within the same turn).
|
|
241
|
+
if (!_isUserTurn(frame.context)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const content = this.renderUiState();
|
|
245
|
+
if (content) {
|
|
246
|
+
frame.context.addMessage({ role: "developer", content });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
// The reply the LLM writes answers the in-flight job, unless a tool is
|
|
250
|
+
// answering it: a reply that comes with tool calls is a preamble.
|
|
251
|
+
this.assistantAggregator.addEventHandler("on_assistant_turn_stopped", async (aggregator, message) => {
|
|
252
|
+
if (message.interrupted || !message.content) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (aggregator.hasFunctionCallsInProgress) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
await this.respondToJob(message.content);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/** The classifier this worker asks the small questions about the screen. */
|
|
262
|
+
get classifier() {
|
|
263
|
+
return this._classifier;
|
|
264
|
+
}
|
|
265
|
+
/** The latest accessibility snapshot of the page, or null before the first. */
|
|
266
|
+
get snapshot() {
|
|
267
|
+
return this._latestSnapshot;
|
|
268
|
+
}
|
|
269
|
+
/** The text the user has selected on the page, or null when nothing is. */
|
|
270
|
+
get selection() {
|
|
271
|
+
const selection = this._latestSnapshot?.selection;
|
|
272
|
+
if (!isPlainObject(selection)) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
const ref = selection.ref;
|
|
276
|
+
const text = selection.text;
|
|
277
|
+
if (typeof ref !== "string" || !ref || typeof text !== "string" || !text.trim()) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
return new UISelection({ ref, text: text.trim() });
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Set the classifier up with this worker's task manager, then activate as
|
|
284
|
+
* usual.
|
|
285
|
+
*
|
|
286
|
+
* @param args Optional activation arguments.
|
|
287
|
+
*/
|
|
288
|
+
async onActivated(args) {
|
|
289
|
+
await this._classifier.setup(this.taskManager);
|
|
290
|
+
await super.onActivated(args);
|
|
291
|
+
}
|
|
292
|
+
/** Clean up the classifier along with the worker. */
|
|
293
|
+
async cleanup() {
|
|
294
|
+
await this._classifier.cleanup();
|
|
295
|
+
await super.cleanup();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Send a named UI command to the client.
|
|
299
|
+
*
|
|
300
|
+
* Publishes a `BusUICommandMessage`; when RTVI is enabled, `PipelineWorker`
|
|
301
|
+
* translates it into an `RTVIUICommandFrame` on the pipeline. Client-side
|
|
302
|
+
* handlers subscribed to `RTVIEvent.UICommand` (or React's
|
|
303
|
+
* `useUICommandHandler`) dispatch on the command name.
|
|
304
|
+
*
|
|
305
|
+
* @param name App-defined command name (e.g. `"toast"`, `"navigate"`, or any
|
|
306
|
+
* app-specific name).
|
|
307
|
+
* @param payload One of:
|
|
308
|
+
* - An RTVI `BaseModel` instance (including the built-in command models
|
|
309
|
+
* in `processors/frameworks/rtvi/models`). Converted to a plain object
|
|
310
|
+
* with `modelDump()`.
|
|
311
|
+
* - A plain object, forwarded as-is.
|
|
312
|
+
* - null, forwarded as an empty object.
|
|
313
|
+
*/
|
|
314
|
+
async sendCommand(name, payload = null) {
|
|
315
|
+
let serialized;
|
|
316
|
+
if (payload === null || payload === undefined) {
|
|
317
|
+
serialized = {};
|
|
318
|
+
}
|
|
319
|
+
else if (payload instanceof BaseModel) {
|
|
320
|
+
serialized = payload.modelDump();
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
serialized = payload;
|
|
324
|
+
}
|
|
325
|
+
await this.sendBusMessage(new BusUICommandMessage({
|
|
326
|
+
source: this.name,
|
|
327
|
+
target: null,
|
|
328
|
+
commandName: name,
|
|
329
|
+
payload: serialized,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Send a `scroll_to` UI command to bring an element into view.
|
|
334
|
+
*
|
|
335
|
+
* Convenience wrapper around `sendCommand("scroll_to", new ScrollTo({ ref }))`.
|
|
336
|
+
* These `scrollTo` / `highlight` / `selectText` / `click` / `setInputValue`
|
|
337
|
+
* helpers are plain methods, not LLM tools: compose them inside a `job`
|
|
338
|
+
* handler or a custom `tool` body.
|
|
339
|
+
*
|
|
340
|
+
* @param ref Snapshot ref (e.g. `"e42"`) from the latest `<ui_state>`.
|
|
341
|
+
*/
|
|
342
|
+
async scrollTo(ref) {
|
|
343
|
+
await this.sendCommand("scroll_to", new ScrollTo({ ref }));
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Send a `highlight` UI command to briefly flash an element.
|
|
347
|
+
*
|
|
348
|
+
* @param ref Snapshot ref (e.g. `"e42"`) from the latest `<ui_state>`.
|
|
349
|
+
*/
|
|
350
|
+
async highlight(ref) {
|
|
351
|
+
await this.sendCommand("highlight", new Highlight({ ref }));
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Send a `select_text` UI command to select an element's text.
|
|
355
|
+
*
|
|
356
|
+
* Selects the whole element by default, or the `startOffset`..`endOffset`
|
|
357
|
+
* character sub-range (over the element's concatenated `textContent`) when
|
|
358
|
+
* both are given. Used for deixis -- pointing at content via the page's
|
|
359
|
+
* text selection.
|
|
360
|
+
*
|
|
361
|
+
* @param ref Snapshot ref (e.g. `"e42"`) from the latest `<ui_state>`.
|
|
362
|
+
* @param options.startOffset Optional start character offset of the selection.
|
|
363
|
+
* @param options.endOffset Optional end character offset (exclusive).
|
|
364
|
+
*/
|
|
365
|
+
async selectText(ref, options = {}) {
|
|
366
|
+
await this.sendCommand("select_text", new SelectText({
|
|
367
|
+
ref,
|
|
368
|
+
startOffset: options.startOffset ?? null,
|
|
369
|
+
endOffset: options.endOffset ?? null,
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Send a `click` UI command (checkboxes, radios, submit buttons).
|
|
374
|
+
*
|
|
375
|
+
* The standard client handler no-ops on `disabled` targets, so the worker
|
|
376
|
+
* can't bypass affordances meant to be user-controlled.
|
|
377
|
+
*
|
|
378
|
+
* @param ref Snapshot ref (e.g. `"e42"`) from the latest `<ui_state>`.
|
|
379
|
+
*/
|
|
380
|
+
async click(ref) {
|
|
381
|
+
await this.sendCommand("click", new Click({ ref }));
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Send a `set_input_value` UI command to fill a text input/textarea.
|
|
385
|
+
*
|
|
386
|
+
* @param ref Snapshot ref (e.g. `"e42"`) of the input or textarea.
|
|
387
|
+
* @param value Text to write into the field.
|
|
388
|
+
* @param options.replace When true (the default), overwrite the field; when
|
|
389
|
+
* false, append (e.g. to continue a long answer in a textarea).
|
|
390
|
+
*/
|
|
391
|
+
async setInputValue(ref, value, options = {}) {
|
|
392
|
+
await this.sendCommand("set_input_value", new SetInputValue({ ref, value, replace: options.replace ?? true }));
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Ask the classifier whether a UI event calls for the assistant to speak.
|
|
396
|
+
*
|
|
397
|
+
* Most clicks and edits need no comment, and a handler that reacts to
|
|
398
|
+
* events has to tell the few that do apart without an LLM turn. The
|
|
399
|
+
* question carries the event and the latest `<ui_state>` snapshot.
|
|
400
|
+
*
|
|
401
|
+
* @param message The UI event.
|
|
402
|
+
* @param criteria What is being checked for, as a yes or no question.
|
|
403
|
+
* @returns Whether the assistant should respond to the event.
|
|
404
|
+
* @throws ClassifierError If the classifier could not answer.
|
|
405
|
+
*/
|
|
406
|
+
async shouldRespond(message, criteria = "the assistant should say something about what the user just did") {
|
|
407
|
+
const state = {
|
|
408
|
+
event: { name: message.eventName, payload: message.payload },
|
|
409
|
+
};
|
|
410
|
+
const screen = this.renderUiState();
|
|
411
|
+
if (screen) {
|
|
412
|
+
state.screen = screen;
|
|
413
|
+
}
|
|
414
|
+
const question = new YesNoQuestion({
|
|
415
|
+
instructions: criteria,
|
|
416
|
+
yes: "the event changes what the user is doing or asks for the assistant's attention",
|
|
417
|
+
no: "a routine click, scroll, hover or edit that needs no comment",
|
|
418
|
+
});
|
|
419
|
+
const result = (await this._classifier.yesNo(state, { respond: question }))
|
|
420
|
+
.respond;
|
|
421
|
+
logger.debug(`${this.name}: respond to '${message.eventName}'? ${result.probability.toFixed(2)}`);
|
|
422
|
+
return result.isYes;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Ask the classifier which element on screen the user means.
|
|
426
|
+
*
|
|
427
|
+
* The candidates are the snapshot's named elements, described by their role
|
|
428
|
+
* and name. The user's words and the screen are the state.
|
|
429
|
+
*
|
|
430
|
+
* @param description What the user said, such as "the blue button".
|
|
431
|
+
* @param threshold The probability below which no element is returned.
|
|
432
|
+
* @returns The element's snapshot ref, or null when there is no snapshot,
|
|
433
|
+
* no named element, or no confident answer.
|
|
434
|
+
* @throws ClassifierError If the classifier could not answer.
|
|
435
|
+
*/
|
|
436
|
+
async whichElement(description, threshold = ELEMENT_THRESHOLD) {
|
|
437
|
+
const found = await this._find(description);
|
|
438
|
+
if (found.confidence < threshold) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
return found.ref;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Ask the classifier whether something is true of the screen.
|
|
445
|
+
*
|
|
446
|
+
* @param criteria What is being checked for, as a yes or no question, such
|
|
447
|
+
* as "is anything on the list still unchecked?".
|
|
448
|
+
* @returns How likely the answer is yes.
|
|
449
|
+
* @throws ClassifierError If the classifier could not answer.
|
|
450
|
+
*/
|
|
451
|
+
async checkScreen(criteria) {
|
|
452
|
+
const question = new YesNoQuestion({ instructions: criteria });
|
|
453
|
+
const state = { screen: this.renderUiState() };
|
|
454
|
+
const result = (await this._classifier.yesNo(state, { check: question })).check;
|
|
455
|
+
logger.debug(`${this.name}: '${criteria}'? ${result.probability.toFixed(2)}`);
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Ask the classifier which named elements on screen match a description.
|
|
460
|
+
*
|
|
461
|
+
* One yes or no question per element, all in one call.
|
|
462
|
+
*
|
|
463
|
+
* @param criteria What the elements should be, such as "dairy products".
|
|
464
|
+
* @returns The matching elements, each as `ref`, `label` and `probability`,
|
|
465
|
+
* most likely first. Empty when nothing on screen matches or there is no
|
|
466
|
+
* snapshot.
|
|
467
|
+
* @throws ClassifierError If the classifier could not answer.
|
|
468
|
+
*/
|
|
469
|
+
async selectElements(criteria) {
|
|
470
|
+
const elements = this._namedElements();
|
|
471
|
+
if (elements.length === 0) {
|
|
472
|
+
return [];
|
|
473
|
+
}
|
|
474
|
+
const questions = {};
|
|
475
|
+
for (const e of elements) {
|
|
476
|
+
questions[e.ref] = new YesNoQuestion({
|
|
477
|
+
instructions: `${criteria}: does ${e.role} "${e.name}" match?`,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const state = { criteria, screen: this.renderUiState() };
|
|
481
|
+
const results = await this._classifier.yesNo(state, questions);
|
|
482
|
+
const matches = elements
|
|
483
|
+
.filter((e) => results[e.ref].isYes)
|
|
484
|
+
.map((e) => ({
|
|
485
|
+
ref: e.ref,
|
|
486
|
+
label: e.name,
|
|
487
|
+
probability: results[e.ref].probability,
|
|
488
|
+
}));
|
|
489
|
+
// Stable sort, highest probability first (Python's sort(reverse=True)).
|
|
490
|
+
matches.sort((a, b) => b.probability - a.probability);
|
|
491
|
+
logger.debug(`${this.name}: '${criteria}' -> [${matches.map((m) => pyRepr(m.label)).join(", ")}]`);
|
|
492
|
+
return matches;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Find the element the description means and act on it.
|
|
496
|
+
*
|
|
497
|
+
* @param action One of `click`, `scroll_to`, `highlight`, `select_text` or
|
|
498
|
+
* `set_input_value`.
|
|
499
|
+
* @param description The element in words, such as "the checkout button".
|
|
500
|
+
* @param options.value The text to write, for `set_input_value`.
|
|
501
|
+
* @returns The ref of the element acted on, or null when no element matched
|
|
502
|
+
* with enough confidence.
|
|
503
|
+
* @throws UnknownScreenActionError If `action` is not one of the five.
|
|
504
|
+
* @throws ClassifierError If the classifier could not answer.
|
|
505
|
+
*/
|
|
506
|
+
async act(action, description, options = {}) {
|
|
507
|
+
if (!ACTIONS.has(action)) {
|
|
508
|
+
const sorted = [...ACTIONS].sort().map((a) => pyRepr(a));
|
|
509
|
+
throw new UnknownScreenActionError(`unknown screen action ${pyRepr(action)}, not one of [${sorted.join(", ")}]`);
|
|
510
|
+
}
|
|
511
|
+
const ref = await this.whichElement(description);
|
|
512
|
+
if (!ref) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
if (action === "set_input_value") {
|
|
516
|
+
await this.setInputValue(ref, options.value || "");
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
const method = ACTION_METHODS[action];
|
|
520
|
+
await this[method](ref);
|
|
521
|
+
}
|
|
522
|
+
return ref;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* The named elements on screen, without their refs.
|
|
526
|
+
*
|
|
527
|
+
* @param role Only elements of this role, such as `checkbox`; all when null.
|
|
528
|
+
* @returns One entry per element: `role`, `name`, its `state` tags and, for
|
|
529
|
+
* an input, its `value`.
|
|
530
|
+
*/
|
|
531
|
+
listElements(role = null) {
|
|
532
|
+
return this._namedElements()
|
|
533
|
+
.filter((e) => role === null || e.role === role)
|
|
534
|
+
.map((e) => ({ role: e.role, name: e.name, state: e.state, value: e.value }));
|
|
535
|
+
}
|
|
536
|
+
/** Dispatch UI events alongside base lifecycle handling. */
|
|
537
|
+
async onBusMessage(message) {
|
|
538
|
+
await super.onBusMessage(message);
|
|
539
|
+
if (!(message instanceof BusUIEventMessage)) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (message.target && message.target !== this.name) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
// Reserved snapshot event: store and return without dispatch or
|
|
546
|
+
// `<ui_event>` injection. Apps render via `injectUiState`.
|
|
547
|
+
if (message.eventName === UI_SNAPSHOT_EVENT_NAME) {
|
|
548
|
+
if (isPlainObject(message.payload)) {
|
|
549
|
+
this._latestSnapshot = message.payload;
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
// Reserved cancel event: a cancel request, never an app event.
|
|
554
|
+
if (message.eventName === UI_CANCEL_JOB_GROUP_EVENT_NAME) {
|
|
555
|
+
await this._handleCancelJobEvent(message);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
await this._handleUiEvent(message);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* The job this worker is currently processing, or null when idle.
|
|
562
|
+
*
|
|
563
|
+
* Set when a respond turn starts and cleared when the job completes. Lets
|
|
564
|
+
* `tool` methods inspect the in-flight job without threading the message
|
|
565
|
+
* through every call.
|
|
566
|
+
*
|
|
567
|
+
* @returns The in-flight `BusJobRequestMessage`, or null when idle.
|
|
568
|
+
*/
|
|
569
|
+
get currentJob() {
|
|
570
|
+
return this._currentJob;
|
|
571
|
+
}
|
|
572
|
+
async _respondJob(message) {
|
|
573
|
+
await this._runLlmTurn(message);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Answer a question about the screen, or act on it, for a voice LLM.
|
|
577
|
+
*
|
|
578
|
+
* The payload names the `action`, its `target` and, for a fill, the
|
|
579
|
+
* `value`. Every answer is short data and never the page.
|
|
580
|
+
*/
|
|
581
|
+
async _screenJob(message) {
|
|
582
|
+
const payload = message.payload ?? {};
|
|
583
|
+
const action = this._text(message, "action");
|
|
584
|
+
const target = this._text(message, "target");
|
|
585
|
+
const value = payload.value;
|
|
586
|
+
let answer;
|
|
587
|
+
try {
|
|
588
|
+
answer = await this._screen(action, target, value ? String(value) : null);
|
|
589
|
+
}
|
|
590
|
+
catch (e) {
|
|
591
|
+
rethrowIfCancelled(e);
|
|
592
|
+
if (!(e instanceof ClassifierError) && !(e instanceof UnknownScreenActionError))
|
|
593
|
+
throw e;
|
|
594
|
+
logger.warning(`${this.name}: screen ${pyRepr(action)} failed: ${e.message}`);
|
|
595
|
+
await this.sendJobResponse(message.jobId, { error: e.message }, { status: JobStatus.ERROR });
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
await this.sendJobResponse(message.jobId, answer);
|
|
599
|
+
}
|
|
600
|
+
static {
|
|
601
|
+
job({ name: "respond", sequential: true })(UIWorker.prototype._respondJob);
|
|
602
|
+
job({ name: "screen" })(UIWorker.prototype._screenJob);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Extract the user's query text from a job request.
|
|
606
|
+
*
|
|
607
|
+
* Override to read a different payload shape. The returned string is
|
|
608
|
+
* appended to the LLM context as a user message before the LLM runs. The
|
|
609
|
+
* default reads `payload["query"]`.
|
|
610
|
+
*
|
|
611
|
+
* @param message The inbound job request.
|
|
612
|
+
* @returns The query text to feed into the LLM.
|
|
613
|
+
*/
|
|
614
|
+
renderQuery(message) {
|
|
615
|
+
const payload = message.payload ?? {};
|
|
616
|
+
return ("query" in payload ? payload.query : "");
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Run one LLM turn for a job and respond when the turn answers it.
|
|
620
|
+
*
|
|
621
|
+
* Body of the built-in `respond` job. Records the in-flight job, clears the
|
|
622
|
+
* context when `keepHistory` is false, appends the rendered query, and runs
|
|
623
|
+
* the LLM (the current `<ui_state>` is injected by the
|
|
624
|
+
* `on_before_process_frame` hook). Then blocks until the LLM's reply ends
|
|
625
|
+
* the turn or a `tool` calls `respondToJob`, which chooses how the answer
|
|
626
|
+
* is delivered, and sends the job response.
|
|
627
|
+
*
|
|
628
|
+
* Spanning the full round-trip is what makes the job single-flight
|
|
629
|
+
* (`job({ ..., sequential: true })`).
|
|
630
|
+
*
|
|
631
|
+
* @param message The inbound job request.
|
|
632
|
+
*/
|
|
633
|
+
async _runLlmTurn(message) {
|
|
634
|
+
this._currentJob = message;
|
|
635
|
+
const pending = new _PendingAnswer();
|
|
636
|
+
this._pending = pending;
|
|
637
|
+
try {
|
|
638
|
+
if (!this._keepHistory) {
|
|
639
|
+
await this._resetContext();
|
|
640
|
+
}
|
|
641
|
+
// The query goes in as a "user" message, not "developer": it's the
|
|
642
|
+
// request to act on, whereas the SDK-injected <ui_state> / <ui_event>
|
|
643
|
+
// messages are the "developer" (programmatic context) content. The
|
|
644
|
+
// "user" role also marks the turn boundary that gates <ui_state>
|
|
645
|
+
// injection -- see _isUserTurn() and the on_before_process_frame hook
|
|
646
|
+
// in the constructor (a non-user tail would skip the snapshot).
|
|
647
|
+
await this.queueFrame(new LLMMessagesAppendFrame({
|
|
648
|
+
messages: [{ role: "user", content: this.renderQuery(message) }],
|
|
649
|
+
runLlm: true,
|
|
650
|
+
}));
|
|
651
|
+
const result = await pending.wait();
|
|
652
|
+
await this.sendJobResponse(message.jobId, result.response, { status: result.status });
|
|
653
|
+
}
|
|
654
|
+
finally {
|
|
655
|
+
this._currentJob = null;
|
|
656
|
+
this._pending = null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Clear the LLM conversation history.
|
|
661
|
+
*
|
|
662
|
+
* Replaces all messages in the running context with an empty list via
|
|
663
|
+
* `LLMMessagesUpdateFrame`. The system prompt (`systemInstruction`) is
|
|
664
|
+
* unaffected, but messages seeded via `context` live in the same mutable
|
|
665
|
+
* list and ARE cleared. `keepHistory: false` workers reset automatically
|
|
666
|
+
* per job; `keepHistory: true` workers call this to deliberately start
|
|
667
|
+
* over.
|
|
668
|
+
*/
|
|
669
|
+
async _resetContext() {
|
|
670
|
+
await this.queueFrame(new LLMMessagesUpdateFrame({ messages: [], runLlm: false }));
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Complete the in-flight job with the worker's answer.
|
|
674
|
+
*
|
|
675
|
+
* The reply the LLM writes completes the job on its own; call this from a
|
|
676
|
+
* `tool` to answer with something else. The job responds with
|
|
677
|
+
* `{"answer": answer}` for the requester's voice LLM to phrase, and a falsy
|
|
678
|
+
* `answer` completes the turn silently. No-op when no job is in flight or
|
|
679
|
+
* it was already answered.
|
|
680
|
+
*
|
|
681
|
+
* @param answer The worker's answer, handed to the requester's voice LLM to
|
|
682
|
+
* phrase.
|
|
683
|
+
* @param options.status Completion status. Defaults to `JobStatus.COMPLETED`.
|
|
684
|
+
*/
|
|
685
|
+
async respondToJob(answer = null, options = {}) {
|
|
686
|
+
const status = options.status ?? JobStatus.COMPLETED;
|
|
687
|
+
const pending = this._pending;
|
|
688
|
+
if (pending === null || pending.done() || this._currentJob === null) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const response = answer ? { answer } : null;
|
|
692
|
+
pending.setResult({ response, status });
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Dispatch a job group and announce it to the client.
|
|
696
|
+
*
|
|
697
|
+
* @param workerNames Names of the workers to send the job to.
|
|
698
|
+
* @param options Everything `BaseWorker.createJobGroupAndRequestJob` takes,
|
|
699
|
+
* forwarded unchanged.
|
|
700
|
+
* @returns The created `JobGroup`.
|
|
701
|
+
*/
|
|
702
|
+
async createJobGroupAndRequestJob(workerNames, options = {}) {
|
|
703
|
+
const group = await super.createJobGroupAndRequestJob(workerNames, options);
|
|
704
|
+
await this.sendBusMessage(new BusUIJobGroupStartedMessage({
|
|
705
|
+
source: this.name,
|
|
706
|
+
target: null,
|
|
707
|
+
jobId: group.jobId,
|
|
708
|
+
workers: [...group.workerNames],
|
|
709
|
+
label: group.label,
|
|
710
|
+
cancellable: group.cancellable,
|
|
711
|
+
at: Math.trunc(time() * 1000),
|
|
712
|
+
}));
|
|
713
|
+
return group;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Cancel a running job group and complete its client card.
|
|
717
|
+
*
|
|
718
|
+
* @param jobId The job identifier to cancel.
|
|
719
|
+
* @param options.reason Optional human-readable reason for cancellation.
|
|
720
|
+
*/
|
|
721
|
+
async cancelJobGroup(jobId, options = {}) {
|
|
722
|
+
// Capture the group before `super` tears it down: the client's card is
|
|
723
|
+
// completed from it below.
|
|
724
|
+
const group = this._jobGroups.get(jobId);
|
|
725
|
+
await super.cancelJobGroup(jobId, { reason: options.reason ?? null });
|
|
726
|
+
if (!group) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
// The workers' own CANCELLED responses arrive after the group is gone,
|
|
730
|
+
// so synthesize the terminal envelope for every worker the cancellation
|
|
731
|
+
// actually cut short, deterministically instead of racing the round trip.
|
|
732
|
+
// Workers that already finished keep the status the client saw.
|
|
733
|
+
for (const workerName of group.workerNames) {
|
|
734
|
+
if (group.terminated.has(workerName)) {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
await this._sendJobCompleted({
|
|
738
|
+
jobId,
|
|
739
|
+
workerName,
|
|
740
|
+
status: JobStatus.CANCELLED,
|
|
741
|
+
response: null,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
await this._sendGroupCompleted(jobId);
|
|
745
|
+
}
|
|
746
|
+
/** Forward a worker's progress update to the client. */
|
|
747
|
+
async onJobUpdate(message) {
|
|
748
|
+
await super.onJobUpdate(message);
|
|
749
|
+
// A group torn down by a cancellation still has messages in flight from
|
|
750
|
+
// its workers; the client's card is already closed.
|
|
751
|
+
if (!this._jobGroups.has(message.jobId)) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
await this.sendBusMessage(new BusUIJobUpdateMessage({
|
|
755
|
+
source: this.name,
|
|
756
|
+
target: null,
|
|
757
|
+
jobId: message.jobId,
|
|
758
|
+
workerName: message.source,
|
|
759
|
+
data: message.update,
|
|
760
|
+
at: Math.trunc(time() * 1000),
|
|
761
|
+
}));
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Forward a worker's response to the client as its terminal envelope.
|
|
765
|
+
*
|
|
766
|
+
* Runs before the group is torn down, so on an error status (with
|
|
767
|
+
* `cancelOnError`) the client learns which worker failed before the card
|
|
768
|
+
* closes.
|
|
769
|
+
*/
|
|
770
|
+
async onJobResponse(message) {
|
|
771
|
+
await super.onJobResponse(message);
|
|
772
|
+
// A cancelled group is already gone, and every worker it cut short was
|
|
773
|
+
// reported at cancellation; their own CANCELLED responses land here
|
|
774
|
+
// afterwards and would double up.
|
|
775
|
+
if (!this._jobGroups.has(message.jobId)) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
await this._sendJobCompleted({
|
|
779
|
+
jobId: message.jobId,
|
|
780
|
+
workerName: message.source,
|
|
781
|
+
status: String(message.status),
|
|
782
|
+
response: message.response,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Forward a worker's stream end as its terminal envelope.
|
|
787
|
+
*
|
|
788
|
+
* A worker may finish by ending its stream instead of responding; the
|
|
789
|
+
* client is told it completed, with the final stream data as the response
|
|
790
|
+
* payload.
|
|
791
|
+
*/
|
|
792
|
+
async onJobStreamEnd(message) {
|
|
793
|
+
await super.onJobStreamEnd(message);
|
|
794
|
+
if (!this._jobGroups.has(message.jobId)) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
await this._sendJobCompleted({
|
|
798
|
+
jobId: message.jobId,
|
|
799
|
+
workerName: message.source,
|
|
800
|
+
status: JobStatus.COMPLETED,
|
|
801
|
+
response: message.data,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
/** Complete the client's card for a group whose workers all finished. */
|
|
805
|
+
async onJobCompleted(result) {
|
|
806
|
+
await super.onJobCompleted(result);
|
|
807
|
+
await this._sendGroupCompleted(result.jobId);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Render the latest accessibility snapshot as a `<ui_state>` block.
|
|
811
|
+
*
|
|
812
|
+
* Produces Playwright-MCP-style indented text with stable element refs.
|
|
813
|
+
* Apps inject the output via `injectUiState()` when they want the LLM to
|
|
814
|
+
* see what's on screen.
|
|
815
|
+
*
|
|
816
|
+
* When the snapshot carries a current text selection, a nested
|
|
817
|
+
* `<selection ref="...">...</selection>` block is appended inside
|
|
818
|
+
* `<ui_state>` so the LLM can resolve deictic references ("this
|
|
819
|
+
* paragraph", "what I selected") against on-page content.
|
|
820
|
+
*
|
|
821
|
+
* Override to customize the rendered form.
|
|
822
|
+
*
|
|
823
|
+
* @returns The `<ui_state>` block, or an empty string if no snapshot has
|
|
824
|
+
* been received yet.
|
|
825
|
+
*/
|
|
826
|
+
renderUiState() {
|
|
827
|
+
if (!this._latestSnapshot || Object.keys(this._latestSnapshot).length === 0) {
|
|
828
|
+
return "";
|
|
829
|
+
}
|
|
830
|
+
const root = this._latestSnapshot.root;
|
|
831
|
+
if (!isPlainObject(root)) {
|
|
832
|
+
return "";
|
|
833
|
+
}
|
|
834
|
+
const lines = ["<ui_state>"];
|
|
835
|
+
_renderNode(root, { depth: 0, lines });
|
|
836
|
+
const selection = this._latestSnapshot.selection;
|
|
837
|
+
if (isPlainObject(selection)) {
|
|
838
|
+
_renderSelection(selection, lines);
|
|
839
|
+
}
|
|
840
|
+
lines.push("</ui_state>");
|
|
841
|
+
return lines.join("\n");
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Append the latest `<ui_state>` block to the LLM context.
|
|
845
|
+
*
|
|
846
|
+
* No-op when no snapshot has been received. Frame has `runLlm: false` —
|
|
847
|
+
* the snapshot is context, not a user turn.
|
|
848
|
+
*/
|
|
849
|
+
async injectUiState() {
|
|
850
|
+
const content = this.renderUiState();
|
|
851
|
+
if (!content) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
await this.queueFrame(new LLMMessagesAppendFrame({
|
|
855
|
+
messages: [{ role: "developer", content }],
|
|
856
|
+
runLlm: false,
|
|
857
|
+
}));
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Render a UI event as a string for LLM context injection.
|
|
861
|
+
*
|
|
862
|
+
* Override to customize the injected content. The default wraps the event
|
|
863
|
+
* in a single `<ui_event>` XML tag with a `name` attribute and a
|
|
864
|
+
* JSON-encoded payload as inner text.
|
|
865
|
+
*
|
|
866
|
+
* @param message The UI event to render.
|
|
867
|
+
* @returns A string to append to the LLM context as a developer message.
|
|
868
|
+
*/
|
|
869
|
+
renderUiEvent(message) {
|
|
870
|
+
const payloadRepr = pyJsonDumps(message.payload);
|
|
871
|
+
return `<ui_event name="${message.eventName}">${payloadRepr}</ui_event>`;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Inject the event into LLM context, then dispatch to the handler.
|
|
875
|
+
*
|
|
876
|
+
* Injection runs synchronously first so the `<ui_event>` developer message
|
|
877
|
+
* lands in the context before any side effects the handler triggers. The
|
|
878
|
+
* matching `uiEvent` handler then runs in its own task so the bus
|
|
879
|
+
* dispatcher isn't held open while the handler awaits downstream work (job
|
|
880
|
+
* requests, network calls). Events with no registered handler are a no-op
|
|
881
|
+
* after injection.
|
|
882
|
+
*/
|
|
883
|
+
async _handleUiEvent(message) {
|
|
884
|
+
if (this._injectEvents) {
|
|
885
|
+
const content = this.renderUiEvent(message);
|
|
886
|
+
if (content) {
|
|
887
|
+
await this.queueFrame(new LLMMessagesAppendFrame({
|
|
888
|
+
messages: [{ role: "developer", content }],
|
|
889
|
+
runLlm: false,
|
|
890
|
+
}));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const handler = this._uiEventHandlers.get(message.eventName);
|
|
894
|
+
if (handler === undefined) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
// Handlers run in their own task so the bus dispatcher is never held open
|
|
898
|
+
// while a handler awaits downstream work (job requests, network calls,
|
|
899
|
+
// etc.). Same pattern as `job`.
|
|
900
|
+
this.createTask(() => handler(message), `${this.name}::ui_event_${message.eventName}`);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* The element the description means: `ref`, `label` and `confidence`.
|
|
904
|
+
*
|
|
905
|
+
* The candidates are the snapshot's named elements, described by role and
|
|
906
|
+
* name; `ref` and `label` are null when there are none.
|
|
907
|
+
*/
|
|
908
|
+
async _find(description) {
|
|
909
|
+
const elements = this._namedElements();
|
|
910
|
+
if (elements.length === 0) {
|
|
911
|
+
return { ref: null, label: null, confidence: 0.0 };
|
|
912
|
+
}
|
|
913
|
+
const options = {};
|
|
914
|
+
for (const e of elements) {
|
|
915
|
+
options[e.ref] = `${e.role} "${e.name}"`;
|
|
916
|
+
}
|
|
917
|
+
const state = { utterance: description, screen: this.renderUiState() };
|
|
918
|
+
const question = new ChoiceQuestion({
|
|
919
|
+
instructions: "the element on screen the user is referring to",
|
|
920
|
+
options,
|
|
921
|
+
});
|
|
922
|
+
const result = (await this._classifier.choice(state, { element: question })).element;
|
|
923
|
+
if (result === undefined) {
|
|
924
|
+
throw new ClassifierError("the classifier returned no answer for 'element'");
|
|
925
|
+
}
|
|
926
|
+
logger.debug(`${this.name}: '${description}' -> ${result.choice} (${result.confidence.toFixed(2)})`);
|
|
927
|
+
const match = elements.find((e) => e.ref === result.choice);
|
|
928
|
+
if (match === undefined) {
|
|
929
|
+
// Python's next() on an exhausted generator raises StopIteration.
|
|
930
|
+
throw new Error(`the classifier chose an unknown element: ${pyRepr(result.choice)}`);
|
|
931
|
+
}
|
|
932
|
+
return { ref: result.choice, label: match.name, confidence: result.confidence };
|
|
933
|
+
}
|
|
934
|
+
/** The snapshot's named elements, breadth first. */
|
|
935
|
+
_namedElements() {
|
|
936
|
+
const elements = [];
|
|
937
|
+
const root = this._latestSnapshot?.root;
|
|
938
|
+
if (!isPlainObject(root)) {
|
|
939
|
+
return elements;
|
|
940
|
+
}
|
|
941
|
+
const pending = [root];
|
|
942
|
+
while (pending.length > 0) {
|
|
943
|
+
const node = pending.shift();
|
|
944
|
+
const ref = node.ref;
|
|
945
|
+
const name = node.name;
|
|
946
|
+
if (typeof ref === "string" && ref && typeof name === "string" && name) {
|
|
947
|
+
const rawState = Array.isArray(node.state) ? node.state : [];
|
|
948
|
+
const state = rawState.filter((s) => typeof s === "string");
|
|
949
|
+
const value = node.value;
|
|
950
|
+
elements.push({
|
|
951
|
+
ref,
|
|
952
|
+
role: String("role" in node ? node.role : "element"),
|
|
953
|
+
name,
|
|
954
|
+
state,
|
|
955
|
+
value: typeof value === "string" ? value : null,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
959
|
+
pending.push(...children.filter((c) => isPlainObject(c)));
|
|
960
|
+
}
|
|
961
|
+
return elements;
|
|
962
|
+
}
|
|
963
|
+
/** The string a job's payload carries under `key`, empty when missing. */
|
|
964
|
+
_text(message, key) {
|
|
965
|
+
const value = message.payload?.[key];
|
|
966
|
+
return typeof value === "string" ? value : "";
|
|
967
|
+
}
|
|
968
|
+
/** The answer to one screen action, as the `screen` job returns it. */
|
|
969
|
+
async _screen(action, target, value) {
|
|
970
|
+
if (action === "find") {
|
|
971
|
+
const found = await this._find(target);
|
|
972
|
+
const confident = found.confidence >= ELEMENT_THRESHOLD;
|
|
973
|
+
return {
|
|
974
|
+
label: confident ? found.label : null,
|
|
975
|
+
confidence: found.confidence,
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
if (action === "check") {
|
|
979
|
+
const result = await this.checkScreen(target);
|
|
980
|
+
return { yes: result.isYes, probability: result.probability };
|
|
981
|
+
}
|
|
982
|
+
if (action === "select") {
|
|
983
|
+
const matches = await this.selectElements(target);
|
|
984
|
+
return { matches: matches.map((m) => ({ label: m.label, probability: m.probability })) };
|
|
985
|
+
}
|
|
986
|
+
if (action === "list") {
|
|
987
|
+
return { elements: this.listElements(target || null) };
|
|
988
|
+
}
|
|
989
|
+
if (action === "selection") {
|
|
990
|
+
const selection = this.selection;
|
|
991
|
+
return { text: selection ? selection.text : null };
|
|
992
|
+
}
|
|
993
|
+
const command = action === "fill" ? "set_input_value" : action;
|
|
994
|
+
const ref = await this.act(command, target, { value });
|
|
995
|
+
const label = this._namedElements().find((e) => e.ref === ref)?.name ?? null;
|
|
996
|
+
return { done: ref !== null, label };
|
|
997
|
+
}
|
|
998
|
+
/** Publish one worker's terminal envelope. */
|
|
999
|
+
async _sendJobCompleted(options) {
|
|
1000
|
+
await this.sendBusMessage(new BusUIJobCompletedMessage({
|
|
1001
|
+
source: this.name,
|
|
1002
|
+
target: null,
|
|
1003
|
+
jobId: options.jobId,
|
|
1004
|
+
workerName: options.workerName,
|
|
1005
|
+
status: options.status,
|
|
1006
|
+
response: options.response,
|
|
1007
|
+
at: Math.trunc(time() * 1000),
|
|
1008
|
+
}));
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Publish the envelope that closes the client's card.
|
|
1012
|
+
*
|
|
1013
|
+
* Reached once per group: a group either completes with every worker
|
|
1014
|
+
* having finished, or is cancelled, and the two paths are exclusive.
|
|
1015
|
+
*/
|
|
1016
|
+
async _sendGroupCompleted(jobId) {
|
|
1017
|
+
await this.sendBusMessage(new BusUIJobGroupCompletedMessage({
|
|
1018
|
+
source: this.name,
|
|
1019
|
+
target: null,
|
|
1020
|
+
jobId,
|
|
1021
|
+
at: Math.trunc(time() * 1000),
|
|
1022
|
+
}));
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Translate the client's cancel event into a cancel request.
|
|
1026
|
+
*
|
|
1027
|
+
* Hands the request to `BaseWorker.requestCancelJobGroup`, which refuses it
|
|
1028
|
+
* for a group that is unknown or was dispatched as non-cancellable.
|
|
1029
|
+
*/
|
|
1030
|
+
async _handleCancelJobEvent(message) {
|
|
1031
|
+
const payload = isPlainObject(message.payload) ? message.payload : {};
|
|
1032
|
+
const jobId = payload.job_id;
|
|
1033
|
+
if (typeof jobId !== "string" || !jobId) {
|
|
1034
|
+
logger.warning(`${this.name}: received a cancel event with no job_id; ignoring`);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
let reason = payload.reason;
|
|
1038
|
+
if (reason !== null && reason !== undefined && typeof reason !== "string") {
|
|
1039
|
+
reason = null;
|
|
1040
|
+
}
|
|
1041
|
+
const cancelled = await this.requestCancelJobGroup(jobId, {
|
|
1042
|
+
reason: reason || "cancelled by user",
|
|
1043
|
+
});
|
|
1044
|
+
if (!cancelled) {
|
|
1045
|
+
logger.debug(`${this.name}: cancel event for unknown or non-cancellable group ${jobId}; ignoring`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Whether the context's last message is the user's turn.
|
|
1051
|
+
*
|
|
1052
|
+
* Distinguishes a fresh user-turn inference (tail is the user message) from
|
|
1053
|
+
* the follow-up inference the LLM runs after a tool result (tail is the tool
|
|
1054
|
+
* result / assistant output), so the `<ui_state>` snapshot is injected once
|
|
1055
|
+
* per turn rather than again on each tool round.
|
|
1056
|
+
*/
|
|
1057
|
+
export function _isUserTurn(context) {
|
|
1058
|
+
const messages = context.messages;
|
|
1059
|
+
if (messages.length === 0) {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
const last = messages[messages.length - 1];
|
|
1063
|
+
return isPlainObject(last) && !(last instanceof LLMSpecificMessage) && last.role === "user";
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Render one A11yNode object as Playwright-MCP-style indented text.
|
|
1067
|
+
*
|
|
1068
|
+
* Format per node:
|
|
1069
|
+
*
|
|
1070
|
+
* - role "name" [level=N] [cols=N] [rows=N] [state1] [state2] [ref=eN]:
|
|
1071
|
+
*
|
|
1072
|
+
* Trailing `:` when the node has children. `name`, `level`, grid dims, and
|
|
1073
|
+
* state tags are emitted only when present on the node.
|
|
1074
|
+
*/
|
|
1075
|
+
export function _renderNode(node, options) {
|
|
1076
|
+
const { depth, lines } = options;
|
|
1077
|
+
const role = "role" in node ? node.role : "generic";
|
|
1078
|
+
const name = node.name;
|
|
1079
|
+
const value = node.value;
|
|
1080
|
+
const state = node.state || [];
|
|
1081
|
+
const level = node.level;
|
|
1082
|
+
const colcount = node.colcount;
|
|
1083
|
+
const rowcount = node.rowcount;
|
|
1084
|
+
const ref = "ref" in node ? node.ref : "";
|
|
1085
|
+
const children = node.children || [];
|
|
1086
|
+
const parts = [`- ${role}`];
|
|
1087
|
+
if (typeof name === "string" && name) {
|
|
1088
|
+
parts.push(`"${name}"`);
|
|
1089
|
+
}
|
|
1090
|
+
if (typeof value === "string" && value) {
|
|
1091
|
+
parts.push(`= "${value}"`);
|
|
1092
|
+
}
|
|
1093
|
+
if (isInt(level)) {
|
|
1094
|
+
parts.push(`[level=${level}]`);
|
|
1095
|
+
}
|
|
1096
|
+
if (isInt(colcount)) {
|
|
1097
|
+
parts.push(`[cols=${colcount}]`);
|
|
1098
|
+
}
|
|
1099
|
+
if (isInt(rowcount)) {
|
|
1100
|
+
parts.push(`[rows=${rowcount}]`);
|
|
1101
|
+
}
|
|
1102
|
+
if (Array.isArray(state)) {
|
|
1103
|
+
for (const s of state) {
|
|
1104
|
+
if (typeof s === "string" && s) {
|
|
1105
|
+
parts.push(`[${s}]`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (typeof ref === "string" && ref) {
|
|
1110
|
+
parts.push(`[ref=${ref}]`);
|
|
1111
|
+
}
|
|
1112
|
+
const indent = " ".repeat(depth);
|
|
1113
|
+
let line = indent + parts.join(" ");
|
|
1114
|
+
const hasChildren = Array.isArray(children)
|
|
1115
|
+
? children.length > 0
|
|
1116
|
+
: isPlainObject(children)
|
|
1117
|
+
? Object.keys(children).length > 0
|
|
1118
|
+
: Boolean(children);
|
|
1119
|
+
if (hasChildren) {
|
|
1120
|
+
line += ":";
|
|
1121
|
+
}
|
|
1122
|
+
lines.push(line);
|
|
1123
|
+
if (Array.isArray(children)) {
|
|
1124
|
+
for (const child of children) {
|
|
1125
|
+
if (isPlainObject(child)) {
|
|
1126
|
+
_renderNode(child, { depth: depth + 1, lines });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Render an `A11ySelection` object as a `<selection>` block.
|
|
1133
|
+
*
|
|
1134
|
+
* Emitted at the root of `<ui_state>` (no leading indent) so the LLM can spot
|
|
1135
|
+
* it without parsing the tree:
|
|
1136
|
+
*
|
|
1137
|
+
* <selection ref="e42">
|
|
1138
|
+
* the actual selected text
|
|
1139
|
+
* </selection>
|
|
1140
|
+
*
|
|
1141
|
+
* No-op when the selection lacks a `ref` or `text`.
|
|
1142
|
+
*/
|
|
1143
|
+
export function _renderSelection(selection, lines) {
|
|
1144
|
+
const ref = selection.ref;
|
|
1145
|
+
const text = selection.text;
|
|
1146
|
+
if (typeof ref !== "string" || !ref) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (typeof text !== "string" || !text) {
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
lines.push(`<selection ref="${ref}">`);
|
|
1153
|
+
lines.push(text);
|
|
1154
|
+
lines.push("</selection>");
|
|
1155
|
+
}
|
|
1156
|
+
//# sourceMappingURL=ui_worker.js.map
|