kubiya-control-plane-api 0.9.15__py3-none-any.whl
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.
- control_plane_api/LICENSE +676 -0
- control_plane_api/README.md +350 -0
- control_plane_api/__init__.py +4 -0
- control_plane_api/__version__.py +8 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +121 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
- control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
- control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
- control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
- control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
- control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
- control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
- control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
- control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
- control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
- control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
- control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
- control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
- control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
- control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
- control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
- control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
- control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +384 -0
- control_plane_api/app/activities/plan_generation_activities.py +499 -0
- control_plane_api/app/activities/team_activities.py +424 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +469 -0
- control_plane_api/app/config/config_loader.py +224 -0
- control_plane_api/app/config/model_pricing.py +323 -0
- control_plane_api/app/config/storage_config.py +159 -0
- control_plane_api/app/config.py +115 -0
- control_plane_api/app/controllers/__init__.py +0 -0
- control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/environment.py +65 -0
- control_plane_api/app/lib/event_bus/__init__.py +17 -0
- control_plane_api/app/lib/event_bus/base.py +136 -0
- control_plane_api/app/lib/event_bus/manager.py +335 -0
- control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
- control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
- control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
- control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
- control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
- control_plane_api/app/lib/job_executor.py +330 -0
- control_plane_api/app/lib/kubiya_client.py +293 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/mcp_validation.py +163 -0
- control_plane_api/app/lib/nats/__init__.py +13 -0
- control_plane_api/app/lib/nats/credentials_manager.py +288 -0
- control_plane_api/app/lib/nats/listener.py +374 -0
- control_plane_api/app/lib/planning_prompt_builder.py +153 -0
- control_plane_api/app/lib/planning_tools/__init__.py +41 -0
- control_plane_api/app/lib/planning_tools/agents.py +409 -0
- control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
- control_plane_api/app/lib/planning_tools/base.py +119 -0
- control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
- control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
- control_plane_api/app/lib/planning_tools/environments.py +218 -0
- control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
- control_plane_api/app/lib/planning_tools/models.py +93 -0
- control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
- control_plane_api/app/lib/planning_tools/resources.py +242 -0
- control_plane_api/app/lib/planning_tools/teams.py +334 -0
- control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
- control_plane_api/app/lib/redis_client.py +803 -0
- control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
- control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
- control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
- control_plane_api/app/lib/storage/__init__.py +20 -0
- control_plane_api/app/lib/storage/base_provider.py +274 -0
- control_plane_api/app/lib/storage/provider_factory.py +157 -0
- control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/supabase_utils.py +138 -0
- control_plane_api/app/lib/task_planning/__init__.py +138 -0
- control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
- control_plane_api/app/lib/task_planning/agents.py +389 -0
- control_plane_api/app/lib/task_planning/cache.py +218 -0
- control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
- control_plane_api/app/lib/task_planning/helpers.py +293 -0
- control_plane_api/app/lib/task_planning/hooks.py +474 -0
- control_plane_api/app/lib/task_planning/models.py +503 -0
- control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
- control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
- control_plane_api/app/lib/task_planning/runner.py +656 -0
- control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
- control_plane_api/app/lib/task_planning/workflow.py +424 -0
- control_plane_api/app/lib/templating/__init__.py +88 -0
- control_plane_api/app/lib/templating/compiler.py +278 -0
- control_plane_api/app/lib/templating/engine.py +178 -0
- control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
- control_plane_api/app/lib/templating/parsers/base.py +96 -0
- control_plane_api/app/lib/templating/parsers/env.py +85 -0
- control_plane_api/app/lib/templating/parsers/graph.py +112 -0
- control_plane_api/app/lib/templating/parsers/secret.py +87 -0
- control_plane_api/app/lib/templating/parsers/simple.py +81 -0
- control_plane_api/app/lib/templating/resolver.py +366 -0
- control_plane_api/app/lib/templating/types.py +214 -0
- control_plane_api/app/lib/templating/validator.py +201 -0
- control_plane_api/app/lib/temporal_client.py +232 -0
- control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
- control_plane_api/app/lib/temporal_credentials_service.py +203 -0
- control_plane_api/app/lib/validation/__init__.py +24 -0
- control_plane_api/app/lib/validation/runtime_validation.py +388 -0
- control_plane_api/app/main.py +531 -0
- control_plane_api/app/middleware/__init__.py +10 -0
- control_plane_api/app/middleware/auth.py +645 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/prometheus_middleware.py +173 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +40 -0
- control_plane_api/app/models/agent.py +90 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +107 -0
- control_plane_api/app/models/auth_user.py +73 -0
- control_plane_api/app/models/context.py +161 -0
- control_plane_api/app/models/custom_integration.py +99 -0
- control_plane_api/app/models/environment.py +64 -0
- control_plane_api/app/models/execution.py +125 -0
- control_plane_api/app/models/execution_transition.py +50 -0
- control_plane_api/app/models/job.py +159 -0
- control_plane_api/app/models/llm_model.py +78 -0
- control_plane_api/app/models/orchestration.py +66 -0
- control_plane_api/app/models/plan_execution.py +102 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +61 -0
- control_plane_api/app/models/project_management.py +85 -0
- control_plane_api/app/models/session.py +29 -0
- control_plane_api/app/models/skill.py +155 -0
- control_plane_api/app/models/system_tables.py +43 -0
- control_plane_api/app/models/task_planning.py +372 -0
- control_plane_api/app/models/team.py +86 -0
- control_plane_api/app/models/trace.py +257 -0
- control_plane_api/app/models/user_profile.py +54 -0
- control_plane_api/app/models/worker.py +221 -0
- control_plane_api/app/models/workflow.py +161 -0
- control_plane_api/app/models/workspace.py +50 -0
- control_plane_api/app/observability/__init__.py +177 -0
- control_plane_api/app/observability/context_logging.py +475 -0
- control_plane_api/app/observability/decorators.py +337 -0
- control_plane_api/app/observability/local_span_processor.py +702 -0
- control_plane_api/app/observability/metrics.py +303 -0
- control_plane_api/app/observability/middleware.py +246 -0
- control_plane_api/app/observability/optional.py +115 -0
- control_plane_api/app/observability/tracing.py +382 -0
- control_plane_api/app/policies/README.md +149 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_enforcement/README.md +336 -0
- control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
- control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
- control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
- control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
- control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +382 -0
- control_plane_api/app/routers/agents_v2.py +1598 -0
- control_plane_api/app/routers/analytics.py +1310 -0
- control_plane_api/app/routers/auth.py +59 -0
- control_plane_api/app/routers/client_config.py +57 -0
- control_plane_api/app/routers/context_graph.py +561 -0
- control_plane_api/app/routers/context_manager.py +577 -0
- control_plane_api/app/routers/custom_integrations.py +490 -0
- control_plane_api/app/routers/enforcer.py +132 -0
- control_plane_api/app/routers/environment_context.py +252 -0
- control_plane_api/app/routers/environments.py +761 -0
- control_plane_api/app/routers/execution_environment.py +847 -0
- control_plane_api/app/routers/executions/__init__.py +28 -0
- control_plane_api/app/routers/executions/router.py +286 -0
- control_plane_api/app/routers/executions/services/__init__.py +22 -0
- control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
- control_plane_api/app/routers/executions/services/status_service.py +420 -0
- control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
- control_plane_api/app/routers/executions/services/worker_health.py +514 -0
- control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
- control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
- control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
- control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
- control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
- control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
- control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
- control_plane_api/app/routers/executions.py +4888 -0
- control_plane_api/app/routers/health.py +165 -0
- control_plane_api/app/routers/health_v2.py +394 -0
- control_plane_api/app/routers/integration_templates.py +496 -0
- control_plane_api/app/routers/integrations.py +287 -0
- control_plane_api/app/routers/jobs.py +1809 -0
- control_plane_api/app/routers/metrics.py +517 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +628 -0
- control_plane_api/app/routers/plan_executions.py +1481 -0
- control_plane_api/app/routers/plan_generation_async.py +304 -0
- control_plane_api/app/routers/policies.py +669 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +987 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +171 -0
- control_plane_api/app/routers/skills.py +1010 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/storage.py +456 -0
- control_plane_api/app/routers/task_planning.py +611 -0
- control_plane_api/app/routers/task_queues.py +650 -0
- control_plane_api/app/routers/team_context.py +274 -0
- control_plane_api/app/routers/teams.py +1747 -0
- control_plane_api/app/routers/templates.py +248 -0
- control_plane_api/app/routers/traces.py +571 -0
- control_plane_api/app/routers/websocket_client.py +479 -0
- control_plane_api/app/routers/websocket_executions_status.py +437 -0
- control_plane_api/app/routers/websocket_gateway.py +323 -0
- control_plane_api/app/routers/websocket_traces.py +576 -0
- control_plane_api/app/routers/worker_queues.py +2555 -0
- control_plane_api/app/routers/worker_websocket.py +419 -0
- control_plane_api/app/routers/workers.py +1004 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/__init__.py +1 -0
- control_plane_api/app/schemas/job_schemas.py +302 -0
- control_plane_api/app/schemas/mcp_schemas.py +311 -0
- control_plane_api/app/schemas/template_schemas.py +133 -0
- control_plane_api/app/schemas/trace_schemas.py +168 -0
- control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_planning_strategy.py +233 -0
- control_plane_api/app/services/agno_service.py +838 -0
- control_plane_api/app/services/claude_code_planning_service.py +203 -0
- control_plane_api/app/services/context_graph_client.py +224 -0
- control_plane_api/app/services/custom_integration_service.py +415 -0
- control_plane_api/app/services/integration_resolution_service.py +345 -0
- control_plane_api/app/services/litellm_service.py +394 -0
- control_plane_api/app/services/plan_generator.py +79 -0
- control_plane_api/app/services/planning_strategy.py +66 -0
- control_plane_api/app/services/planning_strategy_factory.py +118 -0
- control_plane_api/app/services/policy_service.py +615 -0
- control_plane_api/app/services/state_transition_service.py +755 -0
- control_plane_api/app/services/storage_service.py +593 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
- control_plane_api/app/services/trace_retention.py +354 -0
- control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
- control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
- control_plane_api/app/services/workflow_operations_service.py +611 -0
- control_plane_api/app/skills/__init__.py +100 -0
- control_plane_api/app/skills/base.py +239 -0
- control_plane_api/app/skills/builtin/__init__.py +37 -0
- control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
- control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
- control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
- control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
- control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
- control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
- control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
- control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
- control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
- control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
- control_plane_api/app/skills/builtin/docker/skill.py +104 -0
- control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
- control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
- control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
- control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
- control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
- control_plane_api/app/skills/builtin/python/__init__.py +4 -0
- control_plane_api/app/skills/builtin/python/skill.py +92 -0
- control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
- control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
- control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
- control_plane_api/app/skills/builtin/shell/skill.py +161 -0
- control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/app/skills/builtin/slack/skill.py +302 -0
- control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
- control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/config.py +63 -0
- control_plane_api/app/skills/loaders/__init__.py +14 -0
- control_plane_api/app/skills/loaders/base.py +73 -0
- control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
- control_plane_api/app/skills/registry.py +125 -0
- control_plane_api/app/utils/helpers.py +12 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +520 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/plan_generation.py +254 -0
- control_plane_api/app/workflows/team_execution.py +442 -0
- control_plane_api/scripts/seed_models.py +240 -0
- control_plane_api/scripts/validate_existing_tool_names.py +492 -0
- control_plane_api/shared/__init__.py +8 -0
- control_plane_api/shared/version.py +17 -0
- control_plane_api/test_deduplication.py +274 -0
- control_plane_api/test_executor_deduplication_e2e.py +309 -0
- control_plane_api/test_job_execution_e2e.py +283 -0
- control_plane_api/test_real_integration.py +193 -0
- control_plane_api/version.py +38 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1585 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/job_activities.py +199 -0
- control_plane_api/worker/activities/runtime_activities.py +1167 -0
- control_plane_api/worker/activities/skill_activities.py +282 -0
- control_plane_api/worker/activities/team_activities.py +479 -0
- control_plane_api/worker/agent_runtime_server.py +370 -0
- control_plane_api/worker/binary_manager.py +333 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +273 -0
- control_plane_api/worker/control_plane_client.py +1491 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/health_monitor.py +159 -0
- control_plane_api/worker/metrics.py +237 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/error_events.py +105 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +35 -0
- control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
- control_plane_api/worker/runtimes/agno/__init__.py +34 -0
- control_plane_api/worker/runtimes/agno/config.py +248 -0
- control_plane_api/worker/runtimes/agno/hooks.py +385 -0
- control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
- control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
- control_plane_api/worker/runtimes/agno/utils.py +163 -0
- control_plane_api/worker/runtimes/base.py +979 -0
- control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
- control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
- control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
- control_plane_api/worker/runtimes/claude_code/config.py +829 -0
- control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
- control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
- control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
- control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
- control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
- control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
- control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/model_utils.py +107 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_communication_tools.py +908 -0
- control_plane_api/worker/services/agent_executor.py +485 -0
- control_plane_api/worker/services/agent_executor_v2.py +793 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/code_ingestion_tools.py +465 -0
- control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
- control_plane_api/worker/services/data_visualization.py +834 -0
- control_plane_api/worker/services/event_publisher.py +531 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +365 -0
- control_plane_api/worker/services/skill_context_enhancement.py +181 -0
- control_plane_api/worker/services/skill_factory.py +471 -0
- control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
- control_plane_api/worker/services/team_executor.py +715 -0
- control_plane_api/worker/services/team_executor_v2.py +1866 -0
- control_plane_api/worker/services/tool_enforcement.py +254 -0
- control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
- control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
- control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
- control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
- control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
- control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
- control_plane_api/worker/services/workflow_executor/models.py +142 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
- control_plane_api/worker/skills/__init__.py +12 -0
- control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
- control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
- control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
- control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
- control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
- control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
- control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
- control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
- control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
- control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
- control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
- control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
- control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
- control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
- control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
- control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
- control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
- control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
- control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
- control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
- control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
- control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
- control_plane_api/worker/skills/loaders/__init__.py +5 -0
- control_plane_api/worker/skills/loaders/base.py +23 -0
- control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
- control_plane_api/worker/skills/registry.py +208 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/conftest.py +12 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
- control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
- control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
- control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
- control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
- control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
- control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
- control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
- control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
- control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
- control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
- control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +330 -0
- control_plane_api/worker/utils/environment.py +65 -0
- control_plane_api/worker/utils/error_publisher.py +260 -0
- control_plane_api/worker/utils/event_batcher.py +256 -0
- control_plane_api/worker/utils/logging_config.py +335 -0
- control_plane_api/worker/utils/logging_helper.py +326 -0
- control_plane_api/worker/utils/parameter_validator.py +120 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +665 -0
- control_plane_api/worker/utils/tool_validation.py +332 -0
- control_plane_api/worker/utils/workspace_manager.py +163 -0
- control_plane_api/worker/websocket_client.py +393 -0
- control_plane_api/worker/worker.py +1297 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +909 -0
- control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
- control_plane_api/worker/workflows/team_execution.py +611 -0
- kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
- kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
- kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
- kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
- kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
- scripts/__init__.py +1 -0
- scripts/migrations.py +39 -0
- scripts/seed_worker_queues.py +128 -0
- scripts/setup_agent_runtime.py +142 -0
- worker_internal/__init__.py +1 -0
- worker_internal/planner/__init__.py +1 -0
- worker_internal/planner/activities.py +1499 -0
- worker_internal/planner/agent_tools.py +197 -0
- worker_internal/planner/event_models.py +148 -0
- worker_internal/planner/event_publisher.py +67 -0
- worker_internal/planner/models.py +199 -0
- worker_internal/planner/retry_logic.py +134 -0
- worker_internal/planner/worker.py +300 -0
- worker_internal/planner/workflows.py +970 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocket Client Endpoint for Execution Streaming.
|
|
3
|
+
|
|
4
|
+
Provides persistent WebSocket connections for frontend clients to receive
|
|
5
|
+
real-time execution updates, eliminating the 300-second HTTP timeout limitation.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Long-running execution streaming (no timeout)
|
|
9
|
+
- Authentication via JWT tokens
|
|
10
|
+
- Automatic reconnection support with Last-Event-ID
|
|
11
|
+
- Heartbeat/keepalive mechanism (ping/pong)
|
|
12
|
+
- Gap detection and recovery
|
|
13
|
+
- Graceful degradation when services unavailable
|
|
14
|
+
|
|
15
|
+
Architecture:
|
|
16
|
+
Browser → WebSocket → Control Plane API → Redis → Execution Events
|
|
17
|
+
↓
|
|
18
|
+
PostgreSQL (historical messages)
|
|
19
|
+
|
|
20
|
+
This replaces the SSE-based streaming with a more robust WebSocket solution.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, status
|
|
24
|
+
from typing import Optional, Dict, Any
|
|
25
|
+
import structlog
|
|
26
|
+
import json
|
|
27
|
+
import asyncio
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
|
+
|
|
30
|
+
from control_plane_api.app.middleware.auth import get_current_organization
|
|
31
|
+
from control_plane_api.app.lib.redis_client import get_redis_client
|
|
32
|
+
from control_plane_api.app.routers.executions.streaming.streamer import ExecutionStreamer
|
|
33
|
+
from control_plane_api.app.models.execution import Execution
|
|
34
|
+
from control_plane_api.app.database import get_db
|
|
35
|
+
from control_plane_api.app.lib.temporal_client import get_temporal_client
|
|
36
|
+
from sqlalchemy.orm import Session
|
|
37
|
+
|
|
38
|
+
logger = structlog.get_logger()
|
|
39
|
+
|
|
40
|
+
router = APIRouter()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ClientConnectionManager:
|
|
44
|
+
"""
|
|
45
|
+
Manages WebSocket connections for frontend clients.
|
|
46
|
+
|
|
47
|
+
Features:
|
|
48
|
+
- Per-organization connection limits
|
|
49
|
+
- Connection tracking and cleanup
|
|
50
|
+
- Statistics and monitoring
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
# execution_id -> WebSocket
|
|
55
|
+
self._connections: Dict[str, WebSocket] = {}
|
|
56
|
+
|
|
57
|
+
# organization_id -> Set[execution_id]
|
|
58
|
+
self._org_connections: Dict[str, set] = {}
|
|
59
|
+
|
|
60
|
+
# Connection statistics
|
|
61
|
+
self._stats = {
|
|
62
|
+
"total_connections": 0,
|
|
63
|
+
"active_connections": 0,
|
|
64
|
+
"messages_sent": 0,
|
|
65
|
+
"errors": 0,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async def connect(
|
|
69
|
+
self,
|
|
70
|
+
execution_id: str,
|
|
71
|
+
organization_id: str,
|
|
72
|
+
websocket: WebSocket,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Register a new client WebSocket connection."""
|
|
75
|
+
await websocket.accept()
|
|
76
|
+
|
|
77
|
+
# Track connection
|
|
78
|
+
self._connections[execution_id] = websocket
|
|
79
|
+
|
|
80
|
+
if organization_id not in self._org_connections:
|
|
81
|
+
self._org_connections[organization_id] = set()
|
|
82
|
+
self._org_connections[organization_id].add(execution_id)
|
|
83
|
+
|
|
84
|
+
# Update stats
|
|
85
|
+
self._stats["total_connections"] += 1
|
|
86
|
+
self._stats["active_connections"] = len(self._connections)
|
|
87
|
+
|
|
88
|
+
logger.info(
|
|
89
|
+
"client_websocket_connected",
|
|
90
|
+
execution_id=execution_id[:8],
|
|
91
|
+
organization_id=organization_id[:8],
|
|
92
|
+
active_connections=self._stats["active_connections"],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def disconnect(self, execution_id: str, organization_id: str) -> None:
|
|
96
|
+
"""Unregister a client WebSocket connection."""
|
|
97
|
+
if execution_id in self._connections:
|
|
98
|
+
del self._connections[execution_id]
|
|
99
|
+
|
|
100
|
+
if organization_id in self._org_connections:
|
|
101
|
+
self._org_connections[organization_id].discard(execution_id)
|
|
102
|
+
if not self._org_connections[organization_id]:
|
|
103
|
+
del self._org_connections[organization_id]
|
|
104
|
+
|
|
105
|
+
self._stats["active_connections"] = len(self._connections)
|
|
106
|
+
|
|
107
|
+
logger.info(
|
|
108
|
+
"client_websocket_disconnected",
|
|
109
|
+
execution_id=execution_id[:8],
|
|
110
|
+
organization_id=organization_id[:8],
|
|
111
|
+
active_connections=self._stats["active_connections"],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def get_stats(self) -> Dict[str, int]:
|
|
115
|
+
"""Get connection statistics."""
|
|
116
|
+
return self._stats.copy()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Global connection manager
|
|
120
|
+
client_manager = ClientConnectionManager()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def send_json(websocket: WebSocket, event_type: str, data: Any) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Send JSON message via WebSocket.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
websocket: WebSocket connection
|
|
129
|
+
event_type: Event type (e.g., 'message', 'status', 'connected')
|
|
130
|
+
data: Event data payload
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
message = {
|
|
134
|
+
"type": event_type,
|
|
135
|
+
**data
|
|
136
|
+
}
|
|
137
|
+
await websocket.send_text(json.dumps(message))
|
|
138
|
+
client_manager._stats["messages_sent"] += 1
|
|
139
|
+
logger.info("websocket_message_sent", event_type=event_type, data_keys=list(data.keys()))
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error("failed_to_send_websocket_message", error=str(e), event_type=event_type)
|
|
142
|
+
client_manager._stats["errors"] += 1
|
|
143
|
+
raise
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def handle_ping_pong(websocket: WebSocket) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Handle ping/pong heartbeat messages.
|
|
149
|
+
|
|
150
|
+
Responds to ping messages with pong to keep connection alive.
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
await send_json(websocket, "pong", {"timestamp": int(datetime.now(timezone.utc).timestamp() * 1000)})
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error("failed_to_send_pong", error=str(e))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def handle_auth(websocket: WebSocket, token: str) -> Optional[str]:
|
|
159
|
+
"""
|
|
160
|
+
Handle authentication message.
|
|
161
|
+
|
|
162
|
+
Supports multiple authentication methods:
|
|
163
|
+
1. JWT tokens with org_id claim (Auth0 tokens)
|
|
164
|
+
2. Kubiya API keys (validated via Kubiya API)
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
websocket: WebSocket connection
|
|
168
|
+
token: Authentication token (JWT or API key)
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
organization_id if authentication successful, None otherwise
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
# Import auth utilities
|
|
175
|
+
from control_plane_api.app.middleware.auth import (
|
|
176
|
+
decode_jwt_token,
|
|
177
|
+
validate_kubiya_api_key,
|
|
178
|
+
is_kubiya_api_key_jwt,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
organization_id = None
|
|
182
|
+
user_id = None
|
|
183
|
+
|
|
184
|
+
# First, try to decode as JWT
|
|
185
|
+
decoded = decode_jwt_token(token)
|
|
186
|
+
|
|
187
|
+
if decoded:
|
|
188
|
+
# Check for org_id in various claim locations
|
|
189
|
+
organization_id = (
|
|
190
|
+
decoded.get('https://kubiya.ai/org_id') or
|
|
191
|
+
decoded.get('org_id') or
|
|
192
|
+
decoded.get('organization_id') or
|
|
193
|
+
decoded.get('organization') # Kubiya API keys use 'organization' field
|
|
194
|
+
)
|
|
195
|
+
user_id = decoded.get('sub') or decoded.get('user_id') or decoded.get('email')
|
|
196
|
+
|
|
197
|
+
# If JWT decode failed or org_id not found, try Kubiya API validation
|
|
198
|
+
if not organization_id:
|
|
199
|
+
logger.debug("jwt_org_id_not_found_trying_api_validation")
|
|
200
|
+
|
|
201
|
+
# Try UserKey format first (for CLI API keys)
|
|
202
|
+
user_data = await validate_kubiya_api_key(f"UserKey {token}", use_userkey=True)
|
|
203
|
+
|
|
204
|
+
if not user_data:
|
|
205
|
+
# Try Bearer format
|
|
206
|
+
user_data = await validate_kubiya_api_key(f"Bearer {token}", use_userkey=False)
|
|
207
|
+
|
|
208
|
+
if user_data:
|
|
209
|
+
organization_id = user_data.get('org')
|
|
210
|
+
user_id = user_data.get('uuid') or user_data.get('email')
|
|
211
|
+
logger.info("websocket_auth_via_kubiya_api", org=organization_id)
|
|
212
|
+
|
|
213
|
+
if not organization_id:
|
|
214
|
+
logger.error("org_id_missing", decoded_claims=list(decoded.keys()) if decoded else [])
|
|
215
|
+
await send_json(websocket, "auth_error", {
|
|
216
|
+
"error": "Organization ID not found in token",
|
|
217
|
+
"code": "ORG_ID_MISSING",
|
|
218
|
+
})
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
logger.info(
|
|
222
|
+
"websocket_authenticated",
|
|
223
|
+
organization_id=organization_id[:8] if len(organization_id) > 8 else organization_id,
|
|
224
|
+
user_id=user_id[:8] if user_id and len(user_id) > 8 else user_id,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Send auth success
|
|
228
|
+
await send_json(websocket, "auth_success", {
|
|
229
|
+
"organization_id": organization_id,
|
|
230
|
+
"user_id": user_id,
|
|
231
|
+
"authenticated_at": datetime.now(timezone.utc).isoformat(),
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
return organization_id
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error("authentication_failed", error=str(e), error_type=type(e).__name__)
|
|
238
|
+
await send_json(websocket, "auth_error", {
|
|
239
|
+
"error": "Authentication failed",
|
|
240
|
+
"code": "AUTH_FAILED",
|
|
241
|
+
})
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@router.websocket("/ws/executions/{execution_id}")
|
|
246
|
+
async def websocket_execution_stream(
|
|
247
|
+
websocket: WebSocket,
|
|
248
|
+
execution_id: str,
|
|
249
|
+
last_event_id: Optional[str] = None,
|
|
250
|
+
db: Session = Depends(get_db),
|
|
251
|
+
):
|
|
252
|
+
"""
|
|
253
|
+
WebSocket endpoint for execution streaming.
|
|
254
|
+
|
|
255
|
+
Streams execution events to frontend clients with automatic reconnection support.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
websocket: WebSocket connection
|
|
259
|
+
execution_id: Execution ID to stream
|
|
260
|
+
last_event_id: Last received event ID (for resumption)
|
|
261
|
+
db: Database session
|
|
262
|
+
|
|
263
|
+
Flow:
|
|
264
|
+
1. Accept WebSocket connection
|
|
265
|
+
2. Wait for auth message with JWT token
|
|
266
|
+
3. Validate token and extract organization_id
|
|
267
|
+
4. Send 'connected' event
|
|
268
|
+
5. Load and stream historical messages (last 200)
|
|
269
|
+
6. Subscribe to Redis for live events
|
|
270
|
+
7. Stream events until execution completes or client disconnects
|
|
271
|
+
8. Handle ping/pong for keepalive
|
|
272
|
+
9. Support resumption via last_event_id
|
|
273
|
+
"""
|
|
274
|
+
organization_id: Optional[str] = None
|
|
275
|
+
authenticated = False
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
# Accept connection first (before manager tracks it)
|
|
279
|
+
await websocket.accept()
|
|
280
|
+
|
|
281
|
+
# Track connection in manager
|
|
282
|
+
client_manager._connections[execution_id] = websocket
|
|
283
|
+
client_manager._org_connections.setdefault("pending", set()).add(execution_id)
|
|
284
|
+
client_manager._stats["total_connections"] += 1
|
|
285
|
+
client_manager._stats["active_connections"] = len(client_manager._connections)
|
|
286
|
+
|
|
287
|
+
logger.info(
|
|
288
|
+
"client_websocket_connection_started",
|
|
289
|
+
execution_id=execution_id[:8],
|
|
290
|
+
last_event_id=last_event_id,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Wait for authentication message (timeout after 5 seconds)
|
|
294
|
+
try:
|
|
295
|
+
auth_message = await asyncio.wait_for(websocket.receive_text(), timeout=5.0)
|
|
296
|
+
auth_data = json.loads(auth_message)
|
|
297
|
+
|
|
298
|
+
if auth_data.get("type") == "auth" and "token" in auth_data:
|
|
299
|
+
organization_id = await handle_auth(websocket, auth_data["token"])
|
|
300
|
+
if not organization_id:
|
|
301
|
+
await websocket.close(code=4001, reason="Authentication failed")
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
authenticated = True
|
|
305
|
+
|
|
306
|
+
# Update connection with actual org_id
|
|
307
|
+
# Remove from pending
|
|
308
|
+
if "pending" in client_manager._org_connections:
|
|
309
|
+
client_manager._org_connections["pending"].discard(execution_id)
|
|
310
|
+
if not client_manager._org_connections["pending"]:
|
|
311
|
+
del client_manager._org_connections["pending"]
|
|
312
|
+
|
|
313
|
+
# Add to actual org
|
|
314
|
+
client_manager._org_connections.setdefault(organization_id, set()).add(execution_id)
|
|
315
|
+
|
|
316
|
+
except asyncio.TimeoutError:
|
|
317
|
+
logger.error("auth_timeout", execution_id=execution_id[:8])
|
|
318
|
+
await websocket.close(code=4002, reason="Authentication timeout")
|
|
319
|
+
return
|
|
320
|
+
except json.JSONDecodeError:
|
|
321
|
+
logger.error("invalid_auth_message", execution_id=execution_id[:8])
|
|
322
|
+
await websocket.close(code=4003, reason="Invalid authentication message")
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
# Send connected event
|
|
326
|
+
await send_json(websocket, "connected", {
|
|
327
|
+
"execution_id": execution_id,
|
|
328
|
+
"organization_id": organization_id,
|
|
329
|
+
"connected_at": datetime.now(timezone.utc).isoformat(),
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
# Initialize streaming
|
|
333
|
+
redis_client = get_redis_client()
|
|
334
|
+
temporal_client = await get_temporal_client()
|
|
335
|
+
|
|
336
|
+
# Get execution type from database
|
|
337
|
+
execution = db.query(Execution).filter(Execution.id == execution_id).first()
|
|
338
|
+
execution_type = execution.execution_type if execution else "AGENT"
|
|
339
|
+
|
|
340
|
+
streamer = ExecutionStreamer(
|
|
341
|
+
execution_id=execution_id,
|
|
342
|
+
organization_id=organization_id,
|
|
343
|
+
db_session=db,
|
|
344
|
+
redis_client=redis_client,
|
|
345
|
+
temporal_client=temporal_client,
|
|
346
|
+
last_event_id=last_event_id,
|
|
347
|
+
timeout_seconds=0, # No timeout for WebSocket
|
|
348
|
+
execution_type=execution_type,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
logger.info("streaming_initialized", execution_id=execution_id[:8])
|
|
352
|
+
|
|
353
|
+
# Helper to parse SSE events and convert to WebSocket JSON
|
|
354
|
+
def parse_sse_event(sse_text: str) -> tuple[str, dict]:
|
|
355
|
+
"""Parse SSE format to (event_type, data_dict)."""
|
|
356
|
+
lines = sse_text.strip().split('\n')
|
|
357
|
+
event_type = "message"
|
|
358
|
+
data_lines = []
|
|
359
|
+
|
|
360
|
+
# Check for keepalive (just comment lines, no event/data)
|
|
361
|
+
is_keepalive = all(line.startswith(':') or not line.strip() for line in lines)
|
|
362
|
+
if is_keepalive:
|
|
363
|
+
return "keepalive", {}
|
|
364
|
+
|
|
365
|
+
for line in lines:
|
|
366
|
+
if line.startswith("event: "):
|
|
367
|
+
event_type = line[7:].strip()
|
|
368
|
+
elif line.startswith("data: "):
|
|
369
|
+
# Collect data line (could be multiline)
|
|
370
|
+
data_lines.append(line[6:])
|
|
371
|
+
elif line and not line.startswith("id:") and not line.startswith(':') and data_lines:
|
|
372
|
+
# Continuation of data line
|
|
373
|
+
data_lines.append(line)
|
|
374
|
+
|
|
375
|
+
# Join all data lines
|
|
376
|
+
data_str = ''.join(data_lines).strip()
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
data = json.loads(data_str) if data_str else {}
|
|
380
|
+
except json.JSONDecodeError as e:
|
|
381
|
+
logger.warning("json_decode_error", error=str(e), data_str=data_str[:200])
|
|
382
|
+
data = {"raw": data_str}
|
|
383
|
+
|
|
384
|
+
return event_type, data
|
|
385
|
+
|
|
386
|
+
# Listen for both streaming events and client messages (ping/pong)
|
|
387
|
+
async def listen_stream():
|
|
388
|
+
"""Listen for streaming events from ExecutionStreamer."""
|
|
389
|
+
try:
|
|
390
|
+
async for sse_event in streamer.stream():
|
|
391
|
+
# Parse SSE event and convert to WebSocket JSON
|
|
392
|
+
event_type, data = parse_sse_event(sse_event)
|
|
393
|
+
|
|
394
|
+
# Skip keepalive messages
|
|
395
|
+
if event_type == "keepalive":
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
# DEBUG: Log what we're parsing
|
|
399
|
+
logger.info(
|
|
400
|
+
"parsed_sse_event",
|
|
401
|
+
event_type=event_type,
|
|
402
|
+
data_keys=list(data.keys()) if isinstance(data, dict) else [],
|
|
403
|
+
sse_preview=sse_event[:200] if len(sse_event) > 200 else sse_event
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
await send_json(websocket, event_type, data)
|
|
407
|
+
except Exception as e:
|
|
408
|
+
logger.error("streaming_error", error=str(e), error_type=type(e).__name__)
|
|
409
|
+
raise
|
|
410
|
+
|
|
411
|
+
async def listen_client():
|
|
412
|
+
"""Listen for client messages (ping, resume, etc.)."""
|
|
413
|
+
try:
|
|
414
|
+
while True:
|
|
415
|
+
message = await websocket.receive_text()
|
|
416
|
+
data = json.loads(message)
|
|
417
|
+
|
|
418
|
+
if data.get("type") == "ping":
|
|
419
|
+
await handle_ping_pong(websocket)
|
|
420
|
+
|
|
421
|
+
elif data.get("type") == "resume":
|
|
422
|
+
# Handle resume request
|
|
423
|
+
logger.info("resume_requested", execution_id=execution_id[:8], last_event_id=data.get("last_event_id"))
|
|
424
|
+
|
|
425
|
+
except WebSocketDisconnect:
|
|
426
|
+
pass
|
|
427
|
+
except Exception as e:
|
|
428
|
+
logger.error("client_message_error", error=str(e))
|
|
429
|
+
|
|
430
|
+
# Run both listeners concurrently
|
|
431
|
+
# When one finishes (e.g., client disconnect), cancel the other
|
|
432
|
+
stream_task = asyncio.create_task(listen_stream())
|
|
433
|
+
client_task = asyncio.create_task(listen_client())
|
|
434
|
+
|
|
435
|
+
# Wait for either task to complete
|
|
436
|
+
done, pending = await asyncio.wait(
|
|
437
|
+
{stream_task, client_task},
|
|
438
|
+
return_when=asyncio.FIRST_COMPLETED
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Cancel any pending tasks
|
|
442
|
+
for task in pending:
|
|
443
|
+
task.cancel()
|
|
444
|
+
try:
|
|
445
|
+
await task
|
|
446
|
+
except asyncio.CancelledError:
|
|
447
|
+
pass
|
|
448
|
+
|
|
449
|
+
except WebSocketDisconnect:
|
|
450
|
+
logger.info("client_disconnected", execution_id=execution_id[:8])
|
|
451
|
+
|
|
452
|
+
except Exception as e:
|
|
453
|
+
logger.error(
|
|
454
|
+
"websocket_error",
|
|
455
|
+
execution_id=execution_id[:8],
|
|
456
|
+
error=str(e),
|
|
457
|
+
error_type=type(e).__name__,
|
|
458
|
+
)
|
|
459
|
+
try:
|
|
460
|
+
await send_json(websocket, "error", {"error": str(e)})
|
|
461
|
+
except:
|
|
462
|
+
pass
|
|
463
|
+
|
|
464
|
+
finally:
|
|
465
|
+
# Cleanup
|
|
466
|
+
if organization_id:
|
|
467
|
+
await client_manager.disconnect(execution_id, organization_id)
|
|
468
|
+
|
|
469
|
+
logger.info("client_websocket_closed", execution_id=execution_id[:8])
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@router.get("/ws/stats")
|
|
473
|
+
async def websocket_stats():
|
|
474
|
+
"""
|
|
475
|
+
Get WebSocket connection statistics.
|
|
476
|
+
|
|
477
|
+
Returns statistics about active connections, messages sent, etc.
|
|
478
|
+
"""
|
|
479
|
+
return client_manager.get_stats()
|