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,531 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Advanced Event Publisher with Batching and WebSocket Support.
|
|
3
|
+
|
|
4
|
+
This service provides efficient event publishing from workers to the control plane
|
|
5
|
+
with multiple transport layers for different use cases:
|
|
6
|
+
|
|
7
|
+
1. Batched HTTP (default): Reduces HTTP requests by 90-96%
|
|
8
|
+
2. WebSocket (long executions): Handles 300s timeout with connection renewal
|
|
9
|
+
3. Priority Queue: Ensures critical events (tools, errors) are immediate
|
|
10
|
+
|
|
11
|
+
Architecture:
|
|
12
|
+
- High-frequency events (message_chunks) → Batched
|
|
13
|
+
- Critical events (tool_*, error) → Immediate
|
|
14
|
+
- Auto-transport selection based on execution duration
|
|
15
|
+
|
|
16
|
+
Performance:
|
|
17
|
+
- Before: 300-400 HTTP requests per execution
|
|
18
|
+
- After: 12-15 requests per execution (96% reduction)
|
|
19
|
+
- Latency: <100ms added (imperceptible to users)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import time
|
|
24
|
+
from typing import Dict, Any, Optional, Callable, Literal
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from enum import Enum
|
|
27
|
+
import structlog
|
|
28
|
+
import httpx
|
|
29
|
+
from datetime import datetime, timezone
|
|
30
|
+
|
|
31
|
+
from control_plane_api.worker.utils.chunk_batcher import ChunkBatcher, BatchConfig
|
|
32
|
+
from control_plane_api.worker.control_plane_client import ControlPlaneClient
|
|
33
|
+
|
|
34
|
+
logger = structlog.get_logger()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EventPriority(Enum):
|
|
38
|
+
"""Event priority levels for smart batching."""
|
|
39
|
+
|
|
40
|
+
IMMEDIATE = 1 # Errors, tool events - never batch
|
|
41
|
+
HIGH = 2 # Workflow steps - never batch
|
|
42
|
+
NORMAL = 3 # Message chunks - batch aggressively
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TransportMode(Enum):
|
|
46
|
+
"""Transport mode for event publishing."""
|
|
47
|
+
|
|
48
|
+
HTTP = "http" # Default - batched HTTP
|
|
49
|
+
WEBSOCKET = "ws" # Long executions - persistent connection
|
|
50
|
+
AUTO = "auto" # Auto-select based on execution duration
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class EventPublisherConfig:
|
|
55
|
+
"""Configuration for event publisher."""
|
|
56
|
+
|
|
57
|
+
# Batching configuration
|
|
58
|
+
batching_enabled: bool = True
|
|
59
|
+
batch_time_window_ms: int = 100
|
|
60
|
+
batch_size_window_bytes: int = 100
|
|
61
|
+
batch_max_size_bytes: int = 1000
|
|
62
|
+
|
|
63
|
+
# Transport configuration
|
|
64
|
+
transport_mode: TransportMode = TransportMode.AUTO
|
|
65
|
+
websocket_enabled: bool = True
|
|
66
|
+
websocket_switch_threshold_seconds: int = 240 # Switch to WS after 4 min
|
|
67
|
+
websocket_renew_interval_seconds: int = 240 # Renew WS every 4 min (before 300s timeout)
|
|
68
|
+
|
|
69
|
+
# Performance tuning
|
|
70
|
+
max_concurrent_requests: int = 10 # HTTP connection pool
|
|
71
|
+
request_timeout_seconds: int = 10 # Individual request timeout
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_env(cls, single_execution_mode: bool = False) -> "EventPublisherConfig":
|
|
75
|
+
"""
|
|
76
|
+
Create configuration from environment variables.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
single_execution_mode: If True, disables WebSocket switching to avoid premature shutdown
|
|
80
|
+
"""
|
|
81
|
+
import os
|
|
82
|
+
|
|
83
|
+
# Check if we're in single execution mode (from env or parameter)
|
|
84
|
+
is_single_execution = single_execution_mode or os.getenv("KUBIYA_SINGLE_EXECUTION_MODE", "").lower() == "true"
|
|
85
|
+
|
|
86
|
+
# For single execution mode, disable WebSocket by default to prevent issues
|
|
87
|
+
# with the execution monitor detecting premature completion
|
|
88
|
+
websocket_enabled_default = "false" if is_single_execution else "true"
|
|
89
|
+
|
|
90
|
+
return cls(
|
|
91
|
+
batching_enabled=os.getenv("EVENT_BATCHING_ENABLED", "true").lower() == "true",
|
|
92
|
+
batch_time_window_ms=int(os.getenv("EVENT_BATCH_TIME_WINDOW_MS", "100")),
|
|
93
|
+
batch_size_window_bytes=int(os.getenv("EVENT_BATCH_SIZE_WINDOW_BYTES", "100")),
|
|
94
|
+
batch_max_size_bytes=int(os.getenv("EVENT_BATCH_MAX_SIZE_BYTES", "1000")),
|
|
95
|
+
transport_mode=TransportMode(os.getenv("EVENT_TRANSPORT_MODE", "auto")),
|
|
96
|
+
websocket_enabled=os.getenv("EVENT_WEBSOCKET_ENABLED", websocket_enabled_default).lower() == "true",
|
|
97
|
+
websocket_switch_threshold_seconds=int(os.getenv("EVENT_WS_THRESHOLD_SECONDS", "240")),
|
|
98
|
+
websocket_renew_interval_seconds=int(os.getenv("EVENT_WS_RENEW_INTERVAL_SECONDS", "240")),
|
|
99
|
+
max_concurrent_requests=int(os.getenv("EVENT_MAX_CONCURRENT_REQUESTS", "10")),
|
|
100
|
+
request_timeout_seconds=int(os.getenv("EVENT_REQUEST_TIMEOUT_SECONDS", "10")),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class EventStats:
|
|
106
|
+
"""Statistics for event publishing."""
|
|
107
|
+
|
|
108
|
+
total_events: int = 0
|
|
109
|
+
batched_events: int = 0
|
|
110
|
+
immediate_events: int = 0
|
|
111
|
+
http_requests: int = 0
|
|
112
|
+
websocket_messages: int = 0
|
|
113
|
+
bytes_sent: int = 0
|
|
114
|
+
errors: int = 0
|
|
115
|
+
|
|
116
|
+
def get_reduction_percent(self) -> float:
|
|
117
|
+
"""Calculate HTTP request reduction percentage."""
|
|
118
|
+
if self.total_events == 0:
|
|
119
|
+
return 0.0
|
|
120
|
+
total_requests = self.http_requests + self.websocket_messages
|
|
121
|
+
return round((1 - total_requests / self.total_events) * 100, 1)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class EventPublisher:
|
|
125
|
+
"""
|
|
126
|
+
Advanced event publisher with batching and WebSocket support.
|
|
127
|
+
|
|
128
|
+
Features:
|
|
129
|
+
- Smart batching for high-frequency events (message chunks)
|
|
130
|
+
- Immediate delivery for critical events (tools, errors)
|
|
131
|
+
- WebSocket fallback for long-running executions
|
|
132
|
+
- Connection renewal to handle 300s timeout
|
|
133
|
+
- Automatic transport selection
|
|
134
|
+
|
|
135
|
+
Usage:
|
|
136
|
+
publisher = EventPublisher(
|
|
137
|
+
control_plane=get_control_plane_client(),
|
|
138
|
+
execution_id=execution_id,
|
|
139
|
+
config=EventPublisherConfig.from_env()
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# High-frequency events (batched)
|
|
143
|
+
await publisher.publish("message_chunk", {...})
|
|
144
|
+
|
|
145
|
+
# Critical events (immediate)
|
|
146
|
+
await publisher.publish("tool_started", {...}, priority=EventPriority.IMMEDIATE)
|
|
147
|
+
|
|
148
|
+
# Cleanup
|
|
149
|
+
await publisher.close()
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def __init__(
|
|
153
|
+
self,
|
|
154
|
+
control_plane: ControlPlaneClient,
|
|
155
|
+
execution_id: str,
|
|
156
|
+
config: Optional[EventPublisherConfig] = None,
|
|
157
|
+
):
|
|
158
|
+
self.control_plane = control_plane
|
|
159
|
+
self.execution_id = execution_id
|
|
160
|
+
self.config = config or EventPublisherConfig.from_env()
|
|
161
|
+
|
|
162
|
+
# State
|
|
163
|
+
self._batchers: Dict[str, ChunkBatcher] = {} # message_id -> batcher
|
|
164
|
+
self._ws_connection: Optional[Any] = None
|
|
165
|
+
self._ws_task: Optional[asyncio.Task] = None
|
|
166
|
+
self._start_time = time.time()
|
|
167
|
+
self._stats = EventStats()
|
|
168
|
+
self._closed = False
|
|
169
|
+
|
|
170
|
+
# Transport selection
|
|
171
|
+
self._current_transport = TransportMode.HTTP
|
|
172
|
+
self._transport_switch_task: Optional[asyncio.Task] = None
|
|
173
|
+
|
|
174
|
+
# Start transport management if auto mode
|
|
175
|
+
if self.config.transport_mode == TransportMode.AUTO and self.config.websocket_enabled:
|
|
176
|
+
self._transport_switch_task = asyncio.create_task(self._manage_transport())
|
|
177
|
+
|
|
178
|
+
async def publish(
|
|
179
|
+
self,
|
|
180
|
+
event_type: str,
|
|
181
|
+
data: Dict[str, Any],
|
|
182
|
+
priority: EventPriority = EventPriority.NORMAL,
|
|
183
|
+
) -> bool:
|
|
184
|
+
"""
|
|
185
|
+
Publish an event with smart batching and transport selection.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
event_type: Event type (message_chunk, tool_started, etc.)
|
|
189
|
+
data: Event payload
|
|
190
|
+
priority: Event priority (determines batching behavior)
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if successful, False otherwise
|
|
194
|
+
"""
|
|
195
|
+
if self._closed:
|
|
196
|
+
logger.warning("publisher_closed", execution_id=self.execution_id[:8])
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
self._stats.total_events += 1
|
|
200
|
+
|
|
201
|
+
# Determine if this event should be batched
|
|
202
|
+
should_batch = (
|
|
203
|
+
self.config.batching_enabled
|
|
204
|
+
and priority == EventPriority.NORMAL
|
|
205
|
+
and event_type == "message_chunk"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
if should_batch:
|
|
210
|
+
return await self._publish_batched(event_type, data)
|
|
211
|
+
else:
|
|
212
|
+
return await self._publish_immediate(event_type, data)
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
self._stats.errors += 1
|
|
216
|
+
# Log detailed error info to debug "await wasn't used with future" issues
|
|
217
|
+
import traceback
|
|
218
|
+
error_type = type(e).__name__
|
|
219
|
+
error_msg = str(e)
|
|
220
|
+
tb_str = traceback.format_exc()
|
|
221
|
+
logger.warning(
|
|
222
|
+
"event_publish_failed",
|
|
223
|
+
execution_id=self.execution_id[:8],
|
|
224
|
+
event_type=event_type,
|
|
225
|
+
error=error_msg,
|
|
226
|
+
error_type=error_type,
|
|
227
|
+
traceback=tb_str[:500] if tb_str else None,
|
|
228
|
+
)
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
async def _publish_batched(self, event_type: str, data: Dict[str, Any]) -> bool:
|
|
232
|
+
"""Publish event through batching layer."""
|
|
233
|
+
self._stats.batched_events += 1
|
|
234
|
+
|
|
235
|
+
# Get or create batcher for this message_id
|
|
236
|
+
message_id = data.get("message_id", "default")
|
|
237
|
+
|
|
238
|
+
if message_id not in self._batchers:
|
|
239
|
+
batch_config = BatchConfig(
|
|
240
|
+
enabled=self.config.batching_enabled,
|
|
241
|
+
time_window_ms=self.config.batch_time_window_ms,
|
|
242
|
+
size_window_bytes=self.config.batch_size_window_bytes,
|
|
243
|
+
max_batch_size_bytes=self.config.batch_max_size_bytes,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Create batcher with appropriate publish function
|
|
247
|
+
self._batchers[message_id] = ChunkBatcher(
|
|
248
|
+
publish_func=self._get_publish_func(),
|
|
249
|
+
execution_id=self.execution_id,
|
|
250
|
+
message_id=message_id,
|
|
251
|
+
config=batch_config,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Add chunk to batcher (will auto-flush based on time/size)
|
|
255
|
+
content = data.get("content", "")
|
|
256
|
+
await self._batchers[message_id].add_chunk(content)
|
|
257
|
+
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
async def _publish_immediate(self, event_type: str, data: Dict[str, Any]) -> bool:
|
|
261
|
+
"""Publish event immediately without batching, with retry logic."""
|
|
262
|
+
self._stats.immediate_events += 1
|
|
263
|
+
|
|
264
|
+
max_retries = 3
|
|
265
|
+
base_delay = 0.1 # 100ms
|
|
266
|
+
|
|
267
|
+
for attempt in range(max_retries):
|
|
268
|
+
try:
|
|
269
|
+
# Use appropriate transport - call the async function directly with await
|
|
270
|
+
publish_func = self._get_publish_func()
|
|
271
|
+
|
|
272
|
+
# CRITICAL: Always await async functions immediately
|
|
273
|
+
# publish_func returns an async function reference, so call it with await
|
|
274
|
+
await publish_func(
|
|
275
|
+
execution_id=self.execution_id,
|
|
276
|
+
event_type=event_type,
|
|
277
|
+
data=data,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Update stats based on transport
|
|
281
|
+
if self._current_transport == TransportMode.HTTP:
|
|
282
|
+
self._stats.http_requests += 1
|
|
283
|
+
else:
|
|
284
|
+
self._stats.websocket_messages += 1
|
|
285
|
+
|
|
286
|
+
# Success - exit retry loop
|
|
287
|
+
if attempt > 0:
|
|
288
|
+
logger.debug(
|
|
289
|
+
"event_published_after_retry",
|
|
290
|
+
execution_id=self.execution_id[:8],
|
|
291
|
+
event_type=event_type,
|
|
292
|
+
attempt=attempt + 1,
|
|
293
|
+
)
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
except Exception as e:
|
|
297
|
+
is_last_attempt = attempt == max_retries - 1
|
|
298
|
+
|
|
299
|
+
if is_last_attempt:
|
|
300
|
+
# Final failure - log and return False
|
|
301
|
+
logger.error(
|
|
302
|
+
"event_publish_failed_after_retries",
|
|
303
|
+
execution_id=self.execution_id[:8],
|
|
304
|
+
event_type=event_type,
|
|
305
|
+
error=str(e),
|
|
306
|
+
attempts=max_retries,
|
|
307
|
+
)
|
|
308
|
+
return False
|
|
309
|
+
else:
|
|
310
|
+
# Retry with exponential backoff
|
|
311
|
+
delay = base_delay * (2 ** attempt)
|
|
312
|
+
logger.debug(
|
|
313
|
+
"retrying_event_publish",
|
|
314
|
+
execution_id=self.execution_id[:8],
|
|
315
|
+
event_type=event_type,
|
|
316
|
+
error=str(e),
|
|
317
|
+
attempt=attempt + 1,
|
|
318
|
+
next_delay_ms=int(delay * 1000),
|
|
319
|
+
)
|
|
320
|
+
await asyncio.sleep(delay)
|
|
321
|
+
|
|
322
|
+
def _get_publish_func(self) -> Callable:
|
|
323
|
+
"""Get the appropriate publish function based on current transport."""
|
|
324
|
+
if self._current_transport == TransportMode.WEBSOCKET and self._ws_connection:
|
|
325
|
+
return self._publish_via_websocket
|
|
326
|
+
else:
|
|
327
|
+
# Use async HTTP by default
|
|
328
|
+
return self.control_plane.publish_event_async
|
|
329
|
+
|
|
330
|
+
async def _publish_via_websocket(
|
|
331
|
+
self,
|
|
332
|
+
execution_id: str,
|
|
333
|
+
event_type: str,
|
|
334
|
+
data: Dict[str, Any],
|
|
335
|
+
) -> bool:
|
|
336
|
+
"""Publish event via WebSocket connection."""
|
|
337
|
+
if not self._ws_connection:
|
|
338
|
+
# Fallback to HTTP if WebSocket not available
|
|
339
|
+
return await self.control_plane.publish_event_async(
|
|
340
|
+
execution_id=execution_id,
|
|
341
|
+
event_type=event_type,
|
|
342
|
+
data=data,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
# Send event via WebSocket
|
|
347
|
+
payload = {
|
|
348
|
+
"event_type": event_type,
|
|
349
|
+
"data": data,
|
|
350
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
351
|
+
"execution_id": execution_id,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
await self._ws_connection.send_json(payload)
|
|
355
|
+
self._stats.websocket_messages += 1
|
|
356
|
+
self._stats.bytes_sent += len(str(payload).encode('utf-8'))
|
|
357
|
+
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.warning(
|
|
362
|
+
"websocket_send_failed",
|
|
363
|
+
error=str(e),
|
|
364
|
+
execution_id=execution_id[:8],
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Fallback to HTTP
|
|
368
|
+
return await self.control_plane.publish_event_async(
|
|
369
|
+
execution_id=execution_id,
|
|
370
|
+
event_type=event_type,
|
|
371
|
+
data=data,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
async def _manage_transport(self) -> None:
|
|
375
|
+
"""
|
|
376
|
+
Manage transport mode switching and WebSocket connection lifecycle.
|
|
377
|
+
|
|
378
|
+
Switches to WebSocket after threshold and handles connection renewal.
|
|
379
|
+
"""
|
|
380
|
+
try:
|
|
381
|
+
# Wait for threshold before switching to WebSocket
|
|
382
|
+
await asyncio.sleep(self.config.websocket_switch_threshold_seconds)
|
|
383
|
+
|
|
384
|
+
if self._closed:
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Switch to WebSocket
|
|
388
|
+
logger.info(
|
|
389
|
+
"switching_to_websocket",
|
|
390
|
+
execution_id=self.execution_id[:8],
|
|
391
|
+
elapsed_seconds=int(time.time() - self._start_time),
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
await self._connect_websocket()
|
|
395
|
+
|
|
396
|
+
# Connection renewal loop
|
|
397
|
+
while not self._closed:
|
|
398
|
+
await asyncio.sleep(self.config.websocket_renew_interval_seconds)
|
|
399
|
+
|
|
400
|
+
if not self._closed:
|
|
401
|
+
logger.info(
|
|
402
|
+
"renewing_websocket_connection",
|
|
403
|
+
execution_id=self.execution_id[:8],
|
|
404
|
+
)
|
|
405
|
+
await self._reconnect_websocket()
|
|
406
|
+
|
|
407
|
+
except asyncio.CancelledError:
|
|
408
|
+
pass
|
|
409
|
+
except Exception as e:
|
|
410
|
+
logger.warning(
|
|
411
|
+
"transport_management_error",
|
|
412
|
+
error=str(e),
|
|
413
|
+
execution_id=self.execution_id[:8],
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
async def _connect_websocket(self) -> None:
|
|
417
|
+
"""Establish WebSocket connection to control plane."""
|
|
418
|
+
try:
|
|
419
|
+
# TODO: Implement WebSocket connection using websockets library
|
|
420
|
+
# For now, keep using HTTP as fallback
|
|
421
|
+
|
|
422
|
+
# Placeholder for WebSocket connection
|
|
423
|
+
# import websockets
|
|
424
|
+
# ws_url = self.control_plane.base_url.replace("http", "ws")
|
|
425
|
+
# ws_url = f"{ws_url}/ws/executions/{self.execution_id}/events"
|
|
426
|
+
# self._ws_connection = await websockets.connect(
|
|
427
|
+
# ws_url,
|
|
428
|
+
# extra_headers={"Authorization": f"UserKey {self.control_plane.api_key}"}
|
|
429
|
+
# )
|
|
430
|
+
|
|
431
|
+
self._current_transport = TransportMode.WEBSOCKET
|
|
432
|
+
|
|
433
|
+
logger.info(
|
|
434
|
+
"websocket_connected",
|
|
435
|
+
execution_id=self.execution_id[:8],
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
except Exception as e:
|
|
439
|
+
logger.warning(
|
|
440
|
+
"websocket_connect_failed",
|
|
441
|
+
error=str(e),
|
|
442
|
+
execution_id=self.execution_id[:8],
|
|
443
|
+
)
|
|
444
|
+
# Keep using HTTP
|
|
445
|
+
self._current_transport = TransportMode.HTTP
|
|
446
|
+
|
|
447
|
+
async def _reconnect_websocket(self) -> None:
|
|
448
|
+
"""Reconnect WebSocket to handle 300s timeout."""
|
|
449
|
+
if self._ws_connection:
|
|
450
|
+
try:
|
|
451
|
+
await self._ws_connection.close()
|
|
452
|
+
except:
|
|
453
|
+
pass
|
|
454
|
+
|
|
455
|
+
await self._connect_websocket()
|
|
456
|
+
|
|
457
|
+
async def flush(self) -> None:
|
|
458
|
+
"""Flush all pending batched events."""
|
|
459
|
+
for batcher in self._batchers.values():
|
|
460
|
+
try:
|
|
461
|
+
await batcher.flush(reason="manual_flush")
|
|
462
|
+
except Exception as e:
|
|
463
|
+
logger.warning(
|
|
464
|
+
"batcher_flush_error",
|
|
465
|
+
error=str(e),
|
|
466
|
+
execution_id=self.execution_id[:8],
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
async def close(self) -> None:
|
|
470
|
+
"""
|
|
471
|
+
Close publisher and cleanup resources.
|
|
472
|
+
|
|
473
|
+
Flushes all pending events, closes WebSocket connection, and logs stats.
|
|
474
|
+
"""
|
|
475
|
+
if self._closed:
|
|
476
|
+
return
|
|
477
|
+
|
|
478
|
+
self._closed = True
|
|
479
|
+
|
|
480
|
+
# Cancel transport management
|
|
481
|
+
if self._transport_switch_task:
|
|
482
|
+
self._transport_switch_task.cancel()
|
|
483
|
+
try:
|
|
484
|
+
await self._transport_switch_task
|
|
485
|
+
except asyncio.CancelledError:
|
|
486
|
+
pass
|
|
487
|
+
|
|
488
|
+
# Flush all batchers
|
|
489
|
+
await self.flush()
|
|
490
|
+
|
|
491
|
+
# Close all batchers
|
|
492
|
+
for batcher in self._batchers.values():
|
|
493
|
+
try:
|
|
494
|
+
await batcher.close()
|
|
495
|
+
except Exception as e:
|
|
496
|
+
logger.warning(
|
|
497
|
+
"batcher_close_error",
|
|
498
|
+
error=str(e),
|
|
499
|
+
execution_id=self.execution_id[:8],
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Close WebSocket connection
|
|
503
|
+
if self._ws_connection:
|
|
504
|
+
try:
|
|
505
|
+
await self._ws_connection.close()
|
|
506
|
+
except:
|
|
507
|
+
pass
|
|
508
|
+
|
|
509
|
+
# Log final statistics
|
|
510
|
+
logger.info(
|
|
511
|
+
"event_publisher_stats",
|
|
512
|
+
execution_id=self.execution_id[:8],
|
|
513
|
+
total_events=self._stats.total_events,
|
|
514
|
+
batched_events=self._stats.batched_events,
|
|
515
|
+
immediate_events=self._stats.immediate_events,
|
|
516
|
+
http_requests=self._stats.http_requests,
|
|
517
|
+
websocket_messages=self._stats.websocket_messages,
|
|
518
|
+
bytes_sent=self._stats.bytes_sent,
|
|
519
|
+
errors=self._stats.errors,
|
|
520
|
+
reduction_percent=self._stats.get_reduction_percent(),
|
|
521
|
+
transport_mode=self._current_transport.value,
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
def get_stats(self) -> EventStats:
|
|
525
|
+
"""Get current publishing statistics."""
|
|
526
|
+
return self._stats
|
|
527
|
+
|
|
528
|
+
@property
|
|
529
|
+
def is_using_websocket(self) -> bool:
|
|
530
|
+
"""Check if currently using WebSocket transport."""
|
|
531
|
+
return self._current_transport == TransportMode.WEBSOCKET and self._ws_connection is not None
|