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,611 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task Planning Router - AI-powered task analysis and planning using Claude Code SDK or Agno
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, HTTPException, status, Depends, Request
|
|
8
|
+
from fastapi.responses import StreamingResponse
|
|
9
|
+
from typing import AsyncIterator, Optional
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
import structlog
|
|
12
|
+
import os
|
|
13
|
+
import traceback
|
|
14
|
+
import json
|
|
15
|
+
import asyncio
|
|
16
|
+
import re
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
from control_plane_api.app.database import get_db
|
|
20
|
+
from control_plane_api.app.lib.litellm_pricing import get_litellm_pricing
|
|
21
|
+
|
|
22
|
+
# Import OpenTelemetry for rich tracing
|
|
23
|
+
from control_plane_api.app.observability import (
|
|
24
|
+
create_span_with_context,
|
|
25
|
+
add_span_event,
|
|
26
|
+
add_span_error,
|
|
27
|
+
get_current_trace_id,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Import all models from the new models package
|
|
31
|
+
from control_plane_api.app.models.task_planning import (
|
|
32
|
+
TaskPlanRequest,
|
|
33
|
+
TaskPlanResponse,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Import public functions from task_planning library
|
|
37
|
+
from control_plane_api.app.lib.task_planning import format_sse_message
|
|
38
|
+
from control_plane_api.app.lib.task_planning.planning_workflow import (
|
|
39
|
+
create_planning_workflow as create_multistep_workflow,
|
|
40
|
+
run_planning_workflow_stream,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Import private helper functions directly from helpers module
|
|
44
|
+
from control_plane_api.app.lib.task_planning.helpers import (
|
|
45
|
+
_extract_organization_id_from_token,
|
|
46
|
+
_get_organization_id_fallback,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Import planning strategy factory
|
|
50
|
+
from control_plane_api.app.services.planning_strategy_factory import get_planning_strategy
|
|
51
|
+
|
|
52
|
+
# Import entity resolver for converting entity names to UUIDs
|
|
53
|
+
from control_plane_api.app.lib.task_planning.entity_resolver import resolve_plan_entities
|
|
54
|
+
|
|
55
|
+
router = APIRouter()
|
|
56
|
+
logger = structlog.get_logger()
|
|
57
|
+
|
|
58
|
+
# Planning timeout configuration (in seconds)
|
|
59
|
+
PLANNING_TIMEOUT = int(os.getenv("PLANNING_TIMEOUT_SECONDS", "180")) # Default: 3 minutes
|
|
60
|
+
|
|
61
|
+
# Planning retry configuration
|
|
62
|
+
MAX_PLANNING_RETRIES = int(os.getenv("MAX_PLANNING_RETRIES", "2")) # Default: 2 retries (3 total attempts)
|
|
63
|
+
|
|
64
|
+
# Planning Strategy Selection (defaults to "agno")
|
|
65
|
+
# Options: "claude_code_sdk", "agno"
|
|
66
|
+
# Like choosing transportation: train, walk, or flight!
|
|
67
|
+
PLANNING_STRATEGY = os.getenv("PLANNING_STRATEGY", "agno").lower()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.post("/tasks/plan")
|
|
71
|
+
async def plan_task(task_request: TaskPlanRequest, http_request: Request, db: Session = Depends(get_db)):
|
|
72
|
+
"""
|
|
73
|
+
Generate an AI-powered task plan (non-streaming)
|
|
74
|
+
|
|
75
|
+
Uses the same 2-step workflow as /tasks/plan/stream but returns the final plan directly.
|
|
76
|
+
This endpoint is used by the CLI for fast planning mode.
|
|
77
|
+
|
|
78
|
+
The 2-step workflow:
|
|
79
|
+
- Step 1: Analysis & Resource Selection (discovers agents/teams, selects best match)
|
|
80
|
+
- Step 2: Full Plan Generation (creates TaskPlanResponse with costs, risks, etc.)
|
|
81
|
+
|
|
82
|
+
Benefits:
|
|
83
|
+
- Faster than old 4-step workflow (45-55s vs 119s)
|
|
84
|
+
- Consistent behavior with streaming endpoint
|
|
85
|
+
- Smart pre-fetching of top 20 resources
|
|
86
|
+
"""
|
|
87
|
+
# Extract API token from Authorization header
|
|
88
|
+
auth_header = http_request.headers.get("authorization", "")
|
|
89
|
+
api_token = auth_header.replace("UserKey ", "").replace("Bearer ", "") if auth_header else None
|
|
90
|
+
|
|
91
|
+
# Extract organization ID from token (needed for entity resolution)
|
|
92
|
+
organization_id = None
|
|
93
|
+
if not api_token:
|
|
94
|
+
logger.error("no_api_token_provided")
|
|
95
|
+
raise HTTPException(
|
|
96
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
97
|
+
detail="Authentication required: No API token provided. Please configure your API token."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
organization_id = _extract_organization_id_from_token(api_token)
|
|
102
|
+
if not organization_id:
|
|
103
|
+
raise ValueError("Token does not contain organization_id")
|
|
104
|
+
logger.info("extracted_organization_id", organization_id=organization_id)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error("failed_to_extract_organization_id", error=str(e))
|
|
107
|
+
raise HTTPException(
|
|
108
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
109
|
+
detail=f"Authentication failed: Could not extract organization from token. Error: {str(e)}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Create custom span for task planning business logic
|
|
113
|
+
with create_span_with_context(
|
|
114
|
+
"task_planning.generate_plan",
|
|
115
|
+
organization_id=organization_id,
|
|
116
|
+
attributes={
|
|
117
|
+
"task.description": task_request.description[:200],
|
|
118
|
+
"task.priority": task_request.priority,
|
|
119
|
+
"task.quick_mode": task_request.quick_mode,
|
|
120
|
+
}
|
|
121
|
+
) as plan_span:
|
|
122
|
+
try:
|
|
123
|
+
trace_id = get_current_trace_id()
|
|
124
|
+
|
|
125
|
+
add_span_event("Task planning request received", {
|
|
126
|
+
"description_length": len(task_request.description),
|
|
127
|
+
"quick_mode": str(task_request.quick_mode),
|
|
128
|
+
"trace_id": trace_id,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
logger.info(
|
|
132
|
+
"task_planning_requested_nonstreaming",
|
|
133
|
+
description=task_request.description[:100],
|
|
134
|
+
quick_mode=task_request.quick_mode,
|
|
135
|
+
organization_id=organization_id,
|
|
136
|
+
trace_id=trace_id,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Let the planner fetch everything from DB (no outer_context needed)
|
|
140
|
+
outer_context = None
|
|
141
|
+
|
|
142
|
+
add_span_event("Creating 2-step planning workflow", {
|
|
143
|
+
"workflow_type": "unified_2step",
|
|
144
|
+
"quick_mode": str(task_request.quick_mode),
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
# Create the unified 2-step workflow
|
|
148
|
+
logger.info(
|
|
149
|
+
"using_unified_2step_workflow_nonstreaming",
|
|
150
|
+
message="Unified 2-step workflow (same as streaming endpoint)",
|
|
151
|
+
quick_mode=task_request.quick_mode,
|
|
152
|
+
trace_id=trace_id,
|
|
153
|
+
)
|
|
154
|
+
workflow = create_multistep_workflow(
|
|
155
|
+
db=db,
|
|
156
|
+
organization_id=organization_id,
|
|
157
|
+
api_token=api_token,
|
|
158
|
+
quick_mode=task_request.quick_mode,
|
|
159
|
+
outer_context=outer_context
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Run workflow without streaming (collect all events internally)
|
|
163
|
+
add_span_event("Executing planning workflow", {"workflow_mode": "non_streaming"})
|
|
164
|
+
logger.info("executing_workflow_nonstreaming", trace_id=trace_id)
|
|
165
|
+
|
|
166
|
+
# Create a simple event collector (we don't need to stream events to client)
|
|
167
|
+
events_collected = []
|
|
168
|
+
def collect_event(event):
|
|
169
|
+
events_collected.append(event)
|
|
170
|
+
# Also log workflow events to span
|
|
171
|
+
if isinstance(event, dict) and event.get("type"):
|
|
172
|
+
add_span_event(f"Workflow: {event.get('type')}", {"event_data": str(event)[:200]})
|
|
173
|
+
|
|
174
|
+
# Run the workflow
|
|
175
|
+
start_time = time.time()
|
|
176
|
+
plan = run_planning_workflow_stream(
|
|
177
|
+
workflow,
|
|
178
|
+
task_request,
|
|
179
|
+
collect_event, # Internal event collector
|
|
180
|
+
task_request.quick_mode
|
|
181
|
+
)
|
|
182
|
+
workflow_duration = time.time() - start_time
|
|
183
|
+
|
|
184
|
+
add_span_event("Workflow completed", {
|
|
185
|
+
"duration_seconds": f"{workflow_duration:.2f}",
|
|
186
|
+
"events_collected": len(events_collected),
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
# Resolve entity names to UUIDs before returning the plan
|
|
190
|
+
add_span_event("Resolving plan entities", {"organization_id": organization_id})
|
|
191
|
+
await resolve_plan_entities(
|
|
192
|
+
plan_response=plan,
|
|
193
|
+
organization_id=organization_id,
|
|
194
|
+
db=db
|
|
195
|
+
)
|
|
196
|
+
add_span_event("Plan entities resolved", {"plan_title": plan.title})
|
|
197
|
+
logger.info("plan_entities_resolved_nonstreaming", plan_title=plan.title, trace_id=trace_id)
|
|
198
|
+
|
|
199
|
+
plan_span.set_attribute("plan.title", plan.title)
|
|
200
|
+
plan_span.set_attribute("plan.workflow_duration_seconds", workflow_duration)
|
|
201
|
+
plan_span.set_attribute("plan.success", True)
|
|
202
|
+
|
|
203
|
+
logger.info("task_plan_generated_nonstreaming", title=plan.title, trace_id=trace_id)
|
|
204
|
+
return {"plan": plan}
|
|
205
|
+
|
|
206
|
+
except ValueError as e:
|
|
207
|
+
# Validation errors should return 422 (Unprocessable Entity)
|
|
208
|
+
add_span_error(e, {"error_type": "validation"})
|
|
209
|
+
error_msg = str(e)
|
|
210
|
+
if "validation" in error_msg.lower() or "does NOT exist" in error_msg or "does not exist" in error_msg:
|
|
211
|
+
logger.error("task_planning_validation_error", error=error_msg, trace_id=trace_id, traceback=traceback.format_exc())
|
|
212
|
+
raise HTTPException(
|
|
213
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
214
|
+
detail={
|
|
215
|
+
"error": "validation_failed",
|
|
216
|
+
"message": "The task planner generated invalid output that failed validation",
|
|
217
|
+
"details": error_msg,
|
|
218
|
+
"suggestion": "This usually means the AI tried to use non-existent agents/teams. Please try again or check your available resources."
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
# Other ValueError issues
|
|
223
|
+
logger.error("task_planning_value_error", error=str(e), traceback=traceback.format_exc())
|
|
224
|
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Task planning failed: {str(e)}")
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
add_span_error(e, {"error_type": "general"})
|
|
228
|
+
logger.error("task_planning_error", error=str(e), trace_id=trace_id, traceback=traceback.format_exc())
|
|
229
|
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Task planning failed: {str(e)}")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
async def generate_task_plan_stream(
|
|
233
|
+
request: TaskPlanRequest, db: Session, api_token: Optional[str] = None
|
|
234
|
+
) -> AsyncIterator[str]:
|
|
235
|
+
"""
|
|
236
|
+
Generate task plan using unified 2-step Agno workflow
|
|
237
|
+
|
|
238
|
+
This implementation uses a streamlined 2-step workflow with validated outputs:
|
|
239
|
+
- Step 1: Task Analysis & Resource Selection (AnalysisAndSelectionOutput schema)
|
|
240
|
+
- Step 2: Full Plan Generation with Costs (TaskPlanResponse schema)
|
|
241
|
+
|
|
242
|
+
Benefits:
|
|
243
|
+
- Faster than old 4-step workflow (45-55s vs 119s)
|
|
244
|
+
- Type-safe communication between agents (no text parsing)
|
|
245
|
+
- Validated outputs at each step (eliminates hallucination risk)
|
|
246
|
+
- Smart pre-fetching of top 20 resources (limits context window)
|
|
247
|
+
- Real-time progress updates via SSE
|
|
248
|
+
- Single unified workflow for all cases (no fast/slow path split)
|
|
249
|
+
"""
|
|
250
|
+
try:
|
|
251
|
+
# Yield initial progress with informative message
|
|
252
|
+
yield format_sse_message(
|
|
253
|
+
"progress", {"stage": "initializing", "message": "🚀 Initializing AI Task Planner - preparing to discover available agents, teams, and resources...", "progress": 10}
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
logger.info(
|
|
257
|
+
"task_planning_stream_v2_requested",
|
|
258
|
+
description=request.description[:100],
|
|
259
|
+
priority=request.priority,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Extract organization ID from token (REQUIRED for entity resolution)
|
|
263
|
+
organization_id = None
|
|
264
|
+
if not api_token:
|
|
265
|
+
logger.error("no_api_token_provided", message="API token is required for task planning")
|
|
266
|
+
yield format_sse_message("error", {
|
|
267
|
+
"message": "Authentication required: No API token provided. Please configure your API token."
|
|
268
|
+
})
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
organization_id = _extract_organization_id_from_token(api_token)
|
|
273
|
+
if not organization_id:
|
|
274
|
+
raise ValueError("Token does not contain organization_id")
|
|
275
|
+
logger.info("extracted_organization_id", organization_id=organization_id)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error("failed_to_extract_organization_id", error=str(e))
|
|
278
|
+
yield format_sse_message("error", {
|
|
279
|
+
"message": f"Authentication failed: Could not extract organization from token. Error: {str(e)}"
|
|
280
|
+
})
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
# SIMPLIFIED: Let the planner fetch everything from DB
|
|
284
|
+
# CLI no longer needs to fetch agents/teams/environments/queues
|
|
285
|
+
# This centralizes data fetching in the API where it belongs
|
|
286
|
+
outer_context = None
|
|
287
|
+
logger.info(
|
|
288
|
+
"planner_will_fetch_resources",
|
|
289
|
+
message="Planner will fetch all resources from database (agents, teams, environments, queues)"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# UNIFIED WORKFLOW: Always use the 2-step workflow (fast enough for all cases)
|
|
293
|
+
# quick_mode is passed as an optimization hint but same workflow is used
|
|
294
|
+
logger.info(
|
|
295
|
+
"using_unified_2step_workflow",
|
|
296
|
+
message="Unified 2-step workflow (fast enough for all cases)",
|
|
297
|
+
quick_mode=request.quick_mode,
|
|
298
|
+
has_outer_context=bool(outer_context)
|
|
299
|
+
)
|
|
300
|
+
workflow = create_multistep_workflow(
|
|
301
|
+
db=db,
|
|
302
|
+
organization_id=organization_id,
|
|
303
|
+
api_token=api_token,
|
|
304
|
+
quick_mode=request.quick_mode, # Passed as optimization hint
|
|
305
|
+
outer_context=outer_context
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Set up event queue for streaming progress updates
|
|
309
|
+
event_queue = asyncio.Queue()
|
|
310
|
+
|
|
311
|
+
# Capture the event loop before starting background work
|
|
312
|
+
loop = asyncio.get_event_loop()
|
|
313
|
+
|
|
314
|
+
def publish_event(event_dict):
|
|
315
|
+
"""Publish event to the queue (thread-safe)"""
|
|
316
|
+
try:
|
|
317
|
+
loop.call_soon_threadsafe(event_queue.put_nowait, event_dict)
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.error("failed_to_publish_event", error=str(e), event=event_dict)
|
|
320
|
+
|
|
321
|
+
# Run workflow in executor (blocking operation)
|
|
322
|
+
logger.info("running_workflow_in_executor")
|
|
323
|
+
|
|
324
|
+
workflow_complete = False
|
|
325
|
+
workflow_result = None
|
|
326
|
+
workflow_error = None
|
|
327
|
+
|
|
328
|
+
async def run_workflow_async():
|
|
329
|
+
"""Run the workflow in a thread pool"""
|
|
330
|
+
nonlocal workflow_complete, workflow_result, workflow_error
|
|
331
|
+
try:
|
|
332
|
+
# Run blocking workflow in thread pool
|
|
333
|
+
result = await asyncio.to_thread(
|
|
334
|
+
run_planning_workflow_stream,
|
|
335
|
+
workflow,
|
|
336
|
+
request,
|
|
337
|
+
publish_event,
|
|
338
|
+
request.quick_mode # Pass quick_mode to skip verbose reasoning
|
|
339
|
+
)
|
|
340
|
+
workflow_result = result
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.error("workflow_execution_failed", error=str(e), exc_info=True)
|
|
343
|
+
workflow_error = e
|
|
344
|
+
finally:
|
|
345
|
+
workflow_complete = True
|
|
346
|
+
|
|
347
|
+
# Start workflow task
|
|
348
|
+
workflow_task = asyncio.create_task(run_workflow_async())
|
|
349
|
+
|
|
350
|
+
# Stream events from queue as they arrive
|
|
351
|
+
while not workflow_complete:
|
|
352
|
+
try:
|
|
353
|
+
# Try to get event from queue (with short timeout for responsive UI)
|
|
354
|
+
# 0.5s provides good balance between responsiveness and CPU usage
|
|
355
|
+
event = await asyncio.wait_for(event_queue.get(), timeout=0.5)
|
|
356
|
+
|
|
357
|
+
event_type = event.get("event")
|
|
358
|
+
event_data = event.get("data", {})
|
|
359
|
+
|
|
360
|
+
# Map workflow events to UI-compatible events for backward compatibility
|
|
361
|
+
if event_type == "step_started":
|
|
362
|
+
# Map to progress event with step description
|
|
363
|
+
yield format_sse_message("progress", {
|
|
364
|
+
"message": event_data.get("step_description", "Processing..."),
|
|
365
|
+
"progress": event_data.get("progress", 0)
|
|
366
|
+
})
|
|
367
|
+
elif event_type == "step_completed":
|
|
368
|
+
# Map to progress event showing completion
|
|
369
|
+
yield format_sse_message("progress", {
|
|
370
|
+
"message": f"{event_data.get('step_name', 'Step')} completed",
|
|
371
|
+
"progress": event_data.get("progress", 0)
|
|
372
|
+
})
|
|
373
|
+
elif event_type == "tool_call":
|
|
374
|
+
# Pass through tool_call events for elegant UI display
|
|
375
|
+
# CLI will render these with nice formatting
|
|
376
|
+
yield format_sse_message("tool_call", {
|
|
377
|
+
"tool_name": event_data.get("tool_name", "unknown"),
|
|
378
|
+
"tool_id": event_data.get("tool_id"),
|
|
379
|
+
"step": event_data.get("step"),
|
|
380
|
+
"timestamp": event_data.get("timestamp"),
|
|
381
|
+
})
|
|
382
|
+
elif event_type == "tool_result":
|
|
383
|
+
# Pass through tool_result for completion feedback
|
|
384
|
+
yield format_sse_message("tool_result", {
|
|
385
|
+
"tool_name": event_data.get("tool_name", "unknown"),
|
|
386
|
+
"tool_id": event_data.get("tool_id"),
|
|
387
|
+
"status": event_data.get("status", "success"),
|
|
388
|
+
"duration": event_data.get("duration"),
|
|
389
|
+
"step": event_data.get("step"),
|
|
390
|
+
})
|
|
391
|
+
elif event_type == "validation_error":
|
|
392
|
+
# Map to error event
|
|
393
|
+
yield format_sse_message("error", {
|
|
394
|
+
"message": f"Validation error: {event_data.get('error', 'Unknown error')}"
|
|
395
|
+
})
|
|
396
|
+
else:
|
|
397
|
+
# Pass through other events as-is (progress, error, etc.)
|
|
398
|
+
yield format_sse_message(event_type, event_data)
|
|
399
|
+
|
|
400
|
+
await asyncio.sleep(0) # Flush immediately
|
|
401
|
+
|
|
402
|
+
except asyncio.TimeoutError:
|
|
403
|
+
# No event in queue, check if workflow is done
|
|
404
|
+
if workflow_task.done():
|
|
405
|
+
workflow_complete = True
|
|
406
|
+
break
|
|
407
|
+
# Otherwise continue waiting
|
|
408
|
+
continue
|
|
409
|
+
|
|
410
|
+
# Drain any remaining events (with same mapping)
|
|
411
|
+
while not event_queue.empty():
|
|
412
|
+
try:
|
|
413
|
+
event = event_queue.get_nowait()
|
|
414
|
+
event_type = event.get("event")
|
|
415
|
+
event_data = event.get("data", {})
|
|
416
|
+
|
|
417
|
+
# Apply same event mapping as main loop
|
|
418
|
+
if event_type == "step_started":
|
|
419
|
+
yield format_sse_message("progress", {
|
|
420
|
+
"message": event_data.get("step_description", "Processing..."),
|
|
421
|
+
"progress": event_data.get("progress", 0)
|
|
422
|
+
})
|
|
423
|
+
elif event_type == "step_completed":
|
|
424
|
+
yield format_sse_message("progress", {
|
|
425
|
+
"message": f"{event_data.get('step_name', 'Step')} completed",
|
|
426
|
+
"progress": event_data.get("progress", 0)
|
|
427
|
+
})
|
|
428
|
+
elif event_type == "tool_call":
|
|
429
|
+
yield format_sse_message("tool_call", {
|
|
430
|
+
"tool_name": event_data.get("tool_name", "unknown"),
|
|
431
|
+
"tool_id": event_data.get("tool_id"),
|
|
432
|
+
"step": event_data.get("step"),
|
|
433
|
+
"timestamp": event_data.get("timestamp"),
|
|
434
|
+
})
|
|
435
|
+
elif event_type == "tool_result":
|
|
436
|
+
yield format_sse_message("tool_result", {
|
|
437
|
+
"tool_name": event_data.get("tool_name", "unknown"),
|
|
438
|
+
"tool_id": event_data.get("tool_id"),
|
|
439
|
+
"status": event_data.get("status", "success"),
|
|
440
|
+
"duration": event_data.get("duration"),
|
|
441
|
+
"step": event_data.get("step"),
|
|
442
|
+
})
|
|
443
|
+
elif event_type == "validation_error":
|
|
444
|
+
yield format_sse_message("error", {
|
|
445
|
+
"message": f"Validation error: {event_data.get('error', 'Unknown error')}"
|
|
446
|
+
})
|
|
447
|
+
else:
|
|
448
|
+
yield format_sse_message(event_type, event_data)
|
|
449
|
+
|
|
450
|
+
await asyncio.sleep(0)
|
|
451
|
+
except asyncio.QueueEmpty:
|
|
452
|
+
break
|
|
453
|
+
|
|
454
|
+
# Check for workflow errors
|
|
455
|
+
if workflow_error:
|
|
456
|
+
logger.error("workflow_failed", error=str(workflow_error))
|
|
457
|
+
yield format_sse_message("error", {"message": f"Workflow failed: {str(workflow_error)}"})
|
|
458
|
+
raise workflow_error
|
|
459
|
+
|
|
460
|
+
# Validate result
|
|
461
|
+
if not workflow_result:
|
|
462
|
+
error_msg = "Workflow completed but returned no result"
|
|
463
|
+
logger.error("workflow_no_result")
|
|
464
|
+
yield format_sse_message("error", {"message": error_msg})
|
|
465
|
+
raise ValueError(error_msg)
|
|
466
|
+
|
|
467
|
+
# Validate result (always expecting TaskPlanResponse from 2-step workflow)
|
|
468
|
+
from pydantic import ValidationError
|
|
469
|
+
try:
|
|
470
|
+
if isinstance(workflow_result, TaskPlanResponse):
|
|
471
|
+
plan = workflow_result
|
|
472
|
+
elif isinstance(workflow_result, dict):
|
|
473
|
+
plan = TaskPlanResponse.model_validate(workflow_result)
|
|
474
|
+
else:
|
|
475
|
+
raise ValueError(f"Unexpected result type: {type(workflow_result)}")
|
|
476
|
+
except ValidationError as e:
|
|
477
|
+
logger.error("plan_validation_failed", errors=e.errors())
|
|
478
|
+
yield format_sse_message("error", {"message": f"Invalid plan structure: {str(e)}"})
|
|
479
|
+
raise
|
|
480
|
+
|
|
481
|
+
# Resolve entity names to UUIDs before sending final plan
|
|
482
|
+
# This is CRITICAL - without proper UUIDs, execution will fail
|
|
483
|
+
# Note: organization_id was already extracted earlier in this function (line ~268)
|
|
484
|
+
if not organization_id:
|
|
485
|
+
# If no organization_id, fail early with clear error
|
|
486
|
+
error_msg = "Failed to resolve entity IDs: No organization context available"
|
|
487
|
+
logger.error("no_organization_id_for_entity_resolution_stream")
|
|
488
|
+
yield format_sse_message("error", {"message": error_msg})
|
|
489
|
+
return # Stop here - don't send incomplete plan
|
|
490
|
+
|
|
491
|
+
await resolve_plan_entities(
|
|
492
|
+
plan_response=plan,
|
|
493
|
+
organization_id=organization_id,
|
|
494
|
+
db=db
|
|
495
|
+
)
|
|
496
|
+
logger.info("plan_entities_resolved_stream", plan_title=plan.title)
|
|
497
|
+
|
|
498
|
+
# Yield final plan event with "complete" type (CLI expects this event type)
|
|
499
|
+
logger.info("workflow_completed_successfully", title=plan.title)
|
|
500
|
+
yield format_sse_message(
|
|
501
|
+
"complete",
|
|
502
|
+
{
|
|
503
|
+
"plan": plan.model_dump(),
|
|
504
|
+
"progress": 100,
|
|
505
|
+
"message": "✅ Plan generated successfully!"
|
|
506
|
+
}
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
except ValueError as e:
|
|
510
|
+
# Validation errors - return structured error message in stream
|
|
511
|
+
error_msg = str(e)
|
|
512
|
+
error_type = "validation_error" if ("validation" in error_msg.lower() or "does NOT exist" in error_msg or "does not exist" in error_msg) else "value_error"
|
|
513
|
+
|
|
514
|
+
logger.error(f"task_planning_stream_{error_type}", error=error_msg, exc_info=True)
|
|
515
|
+
|
|
516
|
+
yield format_sse_message(
|
|
517
|
+
"error",
|
|
518
|
+
{
|
|
519
|
+
"error": error_type,
|
|
520
|
+
"message": "The task planner generated invalid output that failed validation" if error_type == "validation_error" else "Task planning failed",
|
|
521
|
+
"details": error_msg,
|
|
522
|
+
"suggestion": "This usually means the AI tried to use non-existent agents/teams. Please try again or check your available resources." if error_type == "validation_error" else None
|
|
523
|
+
}
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
except Exception as e:
|
|
527
|
+
from sqlalchemy.exc import OperationalError, DisconnectionError
|
|
528
|
+
from control_plane_api.app.database import dispose_engine, IS_SERVERLESS
|
|
529
|
+
|
|
530
|
+
error_type = type(e).__name__
|
|
531
|
+
logger.error("task_planning_stream_v2_error", error=str(e), error_type=error_type, exc_info=True)
|
|
532
|
+
|
|
533
|
+
# Specific handling for database connection errors
|
|
534
|
+
if isinstance(e, (OperationalError, DisconnectionError)):
|
|
535
|
+
error_msg = "Database connection lost. Please try again."
|
|
536
|
+
if IS_SERVERLESS:
|
|
537
|
+
dispose_engine()
|
|
538
|
+
else:
|
|
539
|
+
error_msg = f"Task planning failed: {str(e)}"
|
|
540
|
+
|
|
541
|
+
yield format_sse_message("error", {"message": error_msg})
|
|
542
|
+
finally:
|
|
543
|
+
# Cleanup database connections in serverless
|
|
544
|
+
from control_plane_api.app.database import dispose_engine, IS_SERVERLESS
|
|
545
|
+
if IS_SERVERLESS:
|
|
546
|
+
logger.info("cleaning_up_serverless_connections")
|
|
547
|
+
dispose_engine()
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
@router.post("/tasks/plan/stream")
|
|
551
|
+
async def plan_task_stream(task_request: TaskPlanRequest, http_request: Request, db: Session = Depends(get_db)):
|
|
552
|
+
"""
|
|
553
|
+
Generate an AI-powered task plan with streaming
|
|
554
|
+
|
|
555
|
+
Uses a single intelligent agent with tool streaming for real-time progress updates.
|
|
556
|
+
Streams: tool calls → thinking → plan generation → complete
|
|
557
|
+
|
|
558
|
+
The agent has access to context graph tools for intelligent resource discovery.
|
|
559
|
+
"""
|
|
560
|
+
# Extract API token from Authorization header
|
|
561
|
+
auth_header = http_request.headers.get("authorization", "")
|
|
562
|
+
api_token = auth_header.replace("UserKey ", "").replace("Bearer ", "") if auth_header else None
|
|
563
|
+
|
|
564
|
+
logger.info("task_planning_stream_requested")
|
|
565
|
+
|
|
566
|
+
# Use the robust 4-step workflow implementation
|
|
567
|
+
return StreamingResponse(
|
|
568
|
+
generate_task_plan_stream(task_request, db, api_token),
|
|
569
|
+
media_type="text/event-stream",
|
|
570
|
+
headers={
|
|
571
|
+
"Cache-Control": "no-cache",
|
|
572
|
+
"Connection": "keep-alive",
|
|
573
|
+
"X-Accel-Buffering": "no",
|
|
574
|
+
},
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
@router.get("/tasks/plan/health")
|
|
579
|
+
async def planning_health():
|
|
580
|
+
"""Health check for task planning endpoint with strategy availability info"""
|
|
581
|
+
available_strategies = []
|
|
582
|
+
|
|
583
|
+
# Check Agno availability (should always be available)
|
|
584
|
+
try:
|
|
585
|
+
from control_plane_api.app.services.agno_planning_strategy import AgnoPlanningStrategy
|
|
586
|
+
available_strategies.append("agno")
|
|
587
|
+
except ImportError:
|
|
588
|
+
pass
|
|
589
|
+
|
|
590
|
+
# Check Claude Code SDK availability
|
|
591
|
+
try:
|
|
592
|
+
from claude_agent_sdk import ClaudeSDKClient
|
|
593
|
+
# Check if Claude CLI binary is available
|
|
594
|
+
import shutil
|
|
595
|
+
if shutil.which("claude"):
|
|
596
|
+
available_strategies.append("claude_code_sdk")
|
|
597
|
+
except ImportError:
|
|
598
|
+
pass
|
|
599
|
+
|
|
600
|
+
current_strategy = os.getenv("PLANNING_STRATEGY", "agno")
|
|
601
|
+
is_healthy = current_strategy in available_strategies
|
|
602
|
+
environment_type = "serverless" if (os.getenv("VERCEL") or os.getenv("AWS_LAMBDA_FUNCTION_NAME")) else "standard"
|
|
603
|
+
|
|
604
|
+
return {
|
|
605
|
+
"status": "healthy" if is_healthy else "degraded",
|
|
606
|
+
"service": "task_planning",
|
|
607
|
+
"current_strategy": current_strategy,
|
|
608
|
+
"available_strategies": available_strategies,
|
|
609
|
+
"environment": environment_type,
|
|
610
|
+
"recommended_strategy": "agno" if environment_type == "serverless" else current_strategy,
|
|
611
|
+
}
|