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,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rate limiting middleware using token bucket algorithm.
|
|
3
|
+
|
|
4
|
+
Provides configurable rate limiting per client IP or user.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import Request, Response, HTTPException, status
|
|
8
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
9
|
+
from starlette.types import ASGIApp
|
|
10
|
+
from typing import Dict, Optional, Tuple, Any
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
import time
|
|
13
|
+
import asyncio
|
|
14
|
+
import structlog
|
|
15
|
+
import hashlib
|
|
16
|
+
from control_plane_api.app.exceptions import RateLimitError
|
|
17
|
+
|
|
18
|
+
logger = structlog.get_logger()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TokenBucket:
|
|
22
|
+
"""
|
|
23
|
+
Token bucket implementation for rate limiting.
|
|
24
|
+
|
|
25
|
+
Each bucket starts with a capacity of tokens.
|
|
26
|
+
Tokens are consumed when requests are made.
|
|
27
|
+
Tokens are refilled at a constant rate.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
capacity: int,
|
|
33
|
+
refill_rate: float,
|
|
34
|
+
refill_period: float = 60.0,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Initialize token bucket.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
capacity: Maximum number of tokens in bucket
|
|
41
|
+
refill_rate: Number of tokens to add per period
|
|
42
|
+
refill_period: Period in seconds for refilling tokens
|
|
43
|
+
"""
|
|
44
|
+
self.capacity = capacity
|
|
45
|
+
self.refill_rate = refill_rate
|
|
46
|
+
self.refill_period = refill_period
|
|
47
|
+
self.tokens = capacity
|
|
48
|
+
self.last_refill = time.time()
|
|
49
|
+
self.lock = asyncio.Lock()
|
|
50
|
+
|
|
51
|
+
async def consume(self, tokens: int = 1) -> Tuple[bool, Dict[str, Any]]:
|
|
52
|
+
"""
|
|
53
|
+
Try to consume tokens from the bucket.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
tokens: Number of tokens to consume
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Tuple of (success, info_dict)
|
|
60
|
+
"""
|
|
61
|
+
async with self.lock:
|
|
62
|
+
now = time.time()
|
|
63
|
+
|
|
64
|
+
# Refill tokens based on time elapsed
|
|
65
|
+
time_elapsed = now - self.last_refill
|
|
66
|
+
tokens_to_add = (time_elapsed / self.refill_period) * self.refill_rate
|
|
67
|
+
|
|
68
|
+
if tokens_to_add > 0:
|
|
69
|
+
self.tokens = min(self.capacity, self.tokens + tokens_to_add)
|
|
70
|
+
self.last_refill = now
|
|
71
|
+
|
|
72
|
+
# Check if we have enough tokens
|
|
73
|
+
if self.tokens >= tokens:
|
|
74
|
+
self.tokens -= tokens
|
|
75
|
+
success = True
|
|
76
|
+
retry_after = None
|
|
77
|
+
else:
|
|
78
|
+
success = False
|
|
79
|
+
# Calculate when enough tokens will be available
|
|
80
|
+
tokens_needed = tokens - self.tokens
|
|
81
|
+
time_to_wait = (tokens_needed / self.refill_rate) * self.refill_period
|
|
82
|
+
retry_after = int(time_to_wait) + 1
|
|
83
|
+
|
|
84
|
+
info = {
|
|
85
|
+
"limit": self.capacity,
|
|
86
|
+
"remaining": int(self.tokens),
|
|
87
|
+
"reset": int(now + self.refill_period),
|
|
88
|
+
"retry_after": retry_after,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return success, info
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class RateLimiter:
|
|
95
|
+
"""
|
|
96
|
+
Rate limiter managing multiple token buckets for different clients.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
requests_per_minute: int = 60,
|
|
102
|
+
burst_size: Optional[int] = None,
|
|
103
|
+
cleanup_interval: int = 300,
|
|
104
|
+
):
|
|
105
|
+
"""
|
|
106
|
+
Initialize rate limiter.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
requests_per_minute: Number of requests allowed per minute
|
|
110
|
+
burst_size: Maximum burst size (defaults to requests_per_minute // 4)
|
|
111
|
+
cleanup_interval: Interval in seconds to clean up old buckets
|
|
112
|
+
"""
|
|
113
|
+
self.requests_per_minute = requests_per_minute
|
|
114
|
+
self.burst_size = burst_size or max(10, requests_per_minute // 4)
|
|
115
|
+
self.buckets: Dict[str, TokenBucket] = {}
|
|
116
|
+
self.last_cleanup = time.time()
|
|
117
|
+
self.cleanup_interval = cleanup_interval
|
|
118
|
+
self.lock = asyncio.Lock()
|
|
119
|
+
|
|
120
|
+
async def check_rate_limit(self, identifier: str) -> Tuple[bool, Dict[str, Any]]:
|
|
121
|
+
"""
|
|
122
|
+
Check if request is within rate limit.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
identifier: Client identifier (IP, user ID, etc.)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Tuple of (allowed, headers_dict)
|
|
129
|
+
"""
|
|
130
|
+
# Clean up old buckets periodically
|
|
131
|
+
await self._cleanup_buckets()
|
|
132
|
+
|
|
133
|
+
# Get or create bucket for this identifier
|
|
134
|
+
bucket = await self._get_or_create_bucket(identifier)
|
|
135
|
+
|
|
136
|
+
# Try to consume a token
|
|
137
|
+
allowed, info = await bucket.consume()
|
|
138
|
+
|
|
139
|
+
# Build rate limit headers
|
|
140
|
+
headers = {
|
|
141
|
+
"X-RateLimit-Limit": str(info["limit"]),
|
|
142
|
+
"X-RateLimit-Remaining": str(info["remaining"]),
|
|
143
|
+
"X-RateLimit-Reset": str(info["reset"]),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if not allowed and info.get("retry_after"):
|
|
147
|
+
headers["Retry-After"] = str(info["retry_after"])
|
|
148
|
+
|
|
149
|
+
return allowed, headers
|
|
150
|
+
|
|
151
|
+
async def _get_or_create_bucket(self, identifier: str) -> TokenBucket:
|
|
152
|
+
"""Get existing bucket or create new one."""
|
|
153
|
+
async with self.lock:
|
|
154
|
+
if identifier not in self.buckets:
|
|
155
|
+
self.buckets[identifier] = TokenBucket(
|
|
156
|
+
capacity=self.burst_size,
|
|
157
|
+
refill_rate=self.requests_per_minute,
|
|
158
|
+
refill_period=60.0,
|
|
159
|
+
)
|
|
160
|
+
return self.buckets[identifier]
|
|
161
|
+
|
|
162
|
+
async def _cleanup_buckets(self):
|
|
163
|
+
"""Remove old unused buckets to prevent memory leak."""
|
|
164
|
+
now = time.time()
|
|
165
|
+
if now - self.last_cleanup < self.cleanup_interval:
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
async with self.lock:
|
|
169
|
+
# Remove buckets that haven't been used recently
|
|
170
|
+
cutoff_time = now - self.cleanup_interval
|
|
171
|
+
to_remove = []
|
|
172
|
+
|
|
173
|
+
for identifier, bucket in self.buckets.items():
|
|
174
|
+
if bucket.last_refill < cutoff_time:
|
|
175
|
+
to_remove.append(identifier)
|
|
176
|
+
|
|
177
|
+
for identifier in to_remove:
|
|
178
|
+
del self.buckets[identifier]
|
|
179
|
+
|
|
180
|
+
if to_remove:
|
|
181
|
+
logger.info(
|
|
182
|
+
"rate_limiter_cleanup",
|
|
183
|
+
removed_count=len(to_remove),
|
|
184
|
+
remaining_count=len(self.buckets),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self.last_cleanup = now
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class RateLimitMiddleware(BaseHTTPMiddleware):
|
|
191
|
+
"""
|
|
192
|
+
Rate limiting middleware for FastAPI.
|
|
193
|
+
|
|
194
|
+
Limits requests per client based on IP address or authenticated user.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def __init__(
|
|
198
|
+
self,
|
|
199
|
+
app: ASGIApp,
|
|
200
|
+
requests_per_minute: int = 60,
|
|
201
|
+
burst_size: Optional[int] = None,
|
|
202
|
+
exclude_paths: Optional[list] = None,
|
|
203
|
+
identifier_callback: Optional[callable] = None,
|
|
204
|
+
):
|
|
205
|
+
"""
|
|
206
|
+
Initialize rate limit middleware.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
app: FastAPI application
|
|
210
|
+
requests_per_minute: Default rate limit
|
|
211
|
+
burst_size: Maximum burst size
|
|
212
|
+
exclude_paths: Paths to exclude from rate limiting
|
|
213
|
+
identifier_callback: Custom function to get client identifier
|
|
214
|
+
"""
|
|
215
|
+
super().__init__(app)
|
|
216
|
+
self.rate_limiter = RateLimiter(requests_per_minute, burst_size)
|
|
217
|
+
self.exclude_paths = exclude_paths or ["/health", "/metrics", "/docs", "/openapi.json"]
|
|
218
|
+
self.identifier_callback = identifier_callback
|
|
219
|
+
|
|
220
|
+
async def dispatch(self, request: Request, call_next):
|
|
221
|
+
"""Process request with rate limiting."""
|
|
222
|
+
|
|
223
|
+
# Check if path is excluded
|
|
224
|
+
if self._is_excluded(request.url.path):
|
|
225
|
+
return await call_next(request)
|
|
226
|
+
|
|
227
|
+
# Get client identifier
|
|
228
|
+
identifier = await self._get_identifier(request)
|
|
229
|
+
|
|
230
|
+
# Check rate limit
|
|
231
|
+
allowed, headers = await self.rate_limiter.check_rate_limit(identifier)
|
|
232
|
+
|
|
233
|
+
if not allowed:
|
|
234
|
+
# Log rate limit exceeded
|
|
235
|
+
logger.warning(
|
|
236
|
+
"rate_limit_exceeded",
|
|
237
|
+
identifier=self._hash_identifier(identifier),
|
|
238
|
+
path=request.url.path,
|
|
239
|
+
method=request.method,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Raise rate limit error
|
|
243
|
+
raise RateLimitError(
|
|
244
|
+
limit=self.rate_limiter.requests_per_minute,
|
|
245
|
+
window="minute",
|
|
246
|
+
retry_after=int(headers.get("Retry-After", 60)),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Process request
|
|
250
|
+
response = await call_next(request)
|
|
251
|
+
|
|
252
|
+
# Add rate limit headers to response
|
|
253
|
+
for header, value in headers.items():
|
|
254
|
+
response.headers[header] = value
|
|
255
|
+
|
|
256
|
+
return response
|
|
257
|
+
|
|
258
|
+
def _is_excluded(self, path: str) -> bool:
|
|
259
|
+
"""Check if path is excluded from rate limiting."""
|
|
260
|
+
for excluded in self.exclude_paths:
|
|
261
|
+
if path.startswith(excluded):
|
|
262
|
+
return True
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
async def _get_identifier(self, request: Request) -> str:
|
|
266
|
+
"""
|
|
267
|
+
Get client identifier for rate limiting.
|
|
268
|
+
|
|
269
|
+
Priority:
|
|
270
|
+
1. Custom identifier callback
|
|
271
|
+
2. Authenticated user ID
|
|
272
|
+
3. Client IP address
|
|
273
|
+
"""
|
|
274
|
+
# Use custom identifier callback if provided
|
|
275
|
+
if self.identifier_callback:
|
|
276
|
+
identifier = await self.identifier_callback(request)
|
|
277
|
+
if identifier:
|
|
278
|
+
return f"custom:{identifier}"
|
|
279
|
+
|
|
280
|
+
# Check for authenticated user
|
|
281
|
+
if hasattr(request.state, "user") and request.state.user:
|
|
282
|
+
user_id = getattr(request.state.user, "id", None)
|
|
283
|
+
if user_id:
|
|
284
|
+
return f"user:{user_id}"
|
|
285
|
+
|
|
286
|
+
# Fall back to IP address
|
|
287
|
+
client_host = request.client.host if request.client else "unknown"
|
|
288
|
+
|
|
289
|
+
# Check for proxy headers
|
|
290
|
+
forwarded_for = request.headers.get("X-Forwarded-For")
|
|
291
|
+
if forwarded_for:
|
|
292
|
+
# Take the first IP in the chain
|
|
293
|
+
client_host = forwarded_for.split(",")[0].strip()
|
|
294
|
+
|
|
295
|
+
real_ip = request.headers.get("X-Real-IP")
|
|
296
|
+
if real_ip:
|
|
297
|
+
client_host = real_ip
|
|
298
|
+
|
|
299
|
+
return f"ip:{client_host}"
|
|
300
|
+
|
|
301
|
+
def _hash_identifier(self, identifier: str) -> str:
|
|
302
|
+
"""Hash identifier for logging (privacy)."""
|
|
303
|
+
return hashlib.sha256(identifier.encode()).hexdigest()[:16]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Per-endpoint rate limiting decorator
|
|
307
|
+
class EndpointRateLimiter:
|
|
308
|
+
"""
|
|
309
|
+
Decorator for per-endpoint rate limiting.
|
|
310
|
+
|
|
311
|
+
Usage:
|
|
312
|
+
rate_limiter = EndpointRateLimiter()
|
|
313
|
+
|
|
314
|
+
@app.get("/expensive-operation")
|
|
315
|
+
@rate_limiter.limit(requests_per_minute=10)
|
|
316
|
+
async def expensive_operation():
|
|
317
|
+
...
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
def __init__(self):
|
|
321
|
+
self.limiters: Dict[str, RateLimiter] = {}
|
|
322
|
+
|
|
323
|
+
def limit(
|
|
324
|
+
self,
|
|
325
|
+
requests_per_minute: int,
|
|
326
|
+
burst_size: Optional[int] = None,
|
|
327
|
+
identifier_callback: Optional[callable] = None,
|
|
328
|
+
):
|
|
329
|
+
"""
|
|
330
|
+
Create rate limit decorator for endpoint.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
requests_per_minute: Rate limit for this endpoint
|
|
334
|
+
burst_size: Burst size for this endpoint
|
|
335
|
+
identifier_callback: Custom identifier function
|
|
336
|
+
"""
|
|
337
|
+
def decorator(func):
|
|
338
|
+
# Create rate limiter for this endpoint
|
|
339
|
+
endpoint_id = f"{func.__module__}.{func.__name__}"
|
|
340
|
+
self.limiters[endpoint_id] = RateLimiter(
|
|
341
|
+
requests_per_minute=requests_per_minute,
|
|
342
|
+
burst_size=burst_size,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
async def wrapper(request: Request, *args, **kwargs):
|
|
346
|
+
# Get client identifier
|
|
347
|
+
if identifier_callback:
|
|
348
|
+
identifier = await identifier_callback(request)
|
|
349
|
+
else:
|
|
350
|
+
identifier = self._default_identifier(request)
|
|
351
|
+
|
|
352
|
+
# Check rate limit
|
|
353
|
+
limiter = self.limiters[endpoint_id]
|
|
354
|
+
allowed, headers = await limiter.check_rate_limit(identifier)
|
|
355
|
+
|
|
356
|
+
if not allowed:
|
|
357
|
+
raise RateLimitError(
|
|
358
|
+
limit=requests_per_minute,
|
|
359
|
+
window="minute",
|
|
360
|
+
retry_after=int(headers.get("Retry-After", 60)),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Add headers to response (if we have access to it)
|
|
364
|
+
response = await func(request, *args, **kwargs)
|
|
365
|
+
if isinstance(response, Response):
|
|
366
|
+
for header, value in headers.items():
|
|
367
|
+
response.headers[header] = value
|
|
368
|
+
|
|
369
|
+
return response
|
|
370
|
+
|
|
371
|
+
return wrapper
|
|
372
|
+
return decorator
|
|
373
|
+
|
|
374
|
+
def _default_identifier(self, request: Request) -> str:
|
|
375
|
+
"""Default identifier extraction."""
|
|
376
|
+
if hasattr(request.state, "user") and request.state.user:
|
|
377
|
+
return f"user:{request.state.user.id}"
|
|
378
|
+
|
|
379
|
+
client_host = request.client.host if request.client else "unknown"
|
|
380
|
+
return f"ip:{client_host}"
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# Global rate limiter instance for decorator usage
|
|
384
|
+
endpoint_rate_limiter = EndpointRateLimiter()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request ID middleware for request tracking and correlation.
|
|
3
|
+
|
|
4
|
+
Adds a unique request ID to each request for tracking through logs and services.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import Request, Response
|
|
8
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
9
|
+
from starlette.types import ASGIApp
|
|
10
|
+
import structlog
|
|
11
|
+
import logging
|
|
12
|
+
import uuid
|
|
13
|
+
import contextvars
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
logger = structlog.get_logger()
|
|
17
|
+
|
|
18
|
+
# Context variable to store request ID
|
|
19
|
+
request_id_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
|
|
20
|
+
"request_id", default=None
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RequestIDMiddleware(BaseHTTPMiddleware):
|
|
25
|
+
"""
|
|
26
|
+
Middleware to add request ID to all requests.
|
|
27
|
+
|
|
28
|
+
- Checks for existing X-Request-ID header
|
|
29
|
+
- Generates new ID if not present
|
|
30
|
+
- Adds ID to response headers
|
|
31
|
+
- Makes ID available in context for logging
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
REQUEST_ID_HEADER = "X-Request-ID"
|
|
35
|
+
|
|
36
|
+
def __init__(self, app: ASGIApp, header_name: str = None):
|
|
37
|
+
super().__init__(app)
|
|
38
|
+
if header_name:
|
|
39
|
+
self.REQUEST_ID_HEADER = header_name
|
|
40
|
+
|
|
41
|
+
async def dispatch(self, request: Request, call_next):
|
|
42
|
+
"""Process the request and add request ID."""
|
|
43
|
+
|
|
44
|
+
# Get or generate request ID
|
|
45
|
+
request_id = (
|
|
46
|
+
request.headers.get(self.REQUEST_ID_HEADER) or
|
|
47
|
+
request.headers.get(self.REQUEST_ID_HEADER.lower()) or
|
|
48
|
+
self._generate_request_id()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Validate request ID format (basic security check)
|
|
52
|
+
if not self._is_valid_request_id(request_id):
|
|
53
|
+
request_id = self._generate_request_id()
|
|
54
|
+
|
|
55
|
+
# Store in request state for easy access
|
|
56
|
+
request.state.request_id = request_id
|
|
57
|
+
|
|
58
|
+
# Set context variable for logging
|
|
59
|
+
token = request_id_context.set(request_id)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Log request start
|
|
63
|
+
logger.info(
|
|
64
|
+
"request_started",
|
|
65
|
+
request_id=request_id,
|
|
66
|
+
method=request.method,
|
|
67
|
+
path=str(request.url.path),
|
|
68
|
+
query=str(request.url.query) if request.url.query else None,
|
|
69
|
+
client=request.client.host if request.client else None,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Process request
|
|
73
|
+
response = await call_next(request)
|
|
74
|
+
|
|
75
|
+
# Add request ID to response headers
|
|
76
|
+
response.headers[self.REQUEST_ID_HEADER] = request_id
|
|
77
|
+
|
|
78
|
+
# Log request completion
|
|
79
|
+
logger.info(
|
|
80
|
+
"request_completed",
|
|
81
|
+
request_id=request_id,
|
|
82
|
+
status_code=response.status_code,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return response
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
# Log request failure
|
|
89
|
+
logger.error(
|
|
90
|
+
"request_failed",
|
|
91
|
+
request_id=request_id,
|
|
92
|
+
error=str(e),
|
|
93
|
+
error_type=type(e).__name__,
|
|
94
|
+
)
|
|
95
|
+
raise
|
|
96
|
+
|
|
97
|
+
finally:
|
|
98
|
+
# Reset context variable
|
|
99
|
+
request_id_context.reset(token)
|
|
100
|
+
|
|
101
|
+
def _generate_request_id(self) -> str:
|
|
102
|
+
"""Generate a new request ID."""
|
|
103
|
+
return str(uuid.uuid4())
|
|
104
|
+
|
|
105
|
+
def _is_valid_request_id(self, request_id: str) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Validate request ID format.
|
|
108
|
+
|
|
109
|
+
Accepts UUIDs and alphanumeric strings up to 128 characters.
|
|
110
|
+
"""
|
|
111
|
+
if not request_id or len(request_id) > 128:
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
# Allow UUIDs, alphanumeric, hyphens, and underscores
|
|
115
|
+
import re
|
|
116
|
+
return bool(re.match(r'^[a-zA-Z0-9\-_]+$', request_id))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_request_id() -> Optional[str]:
|
|
120
|
+
"""
|
|
121
|
+
Get the current request ID from context.
|
|
122
|
+
|
|
123
|
+
This can be used anywhere in the application to get the current request ID.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Request ID if in request context, None otherwise
|
|
127
|
+
"""
|
|
128
|
+
return request_id_context.get()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def set_request_id(request_id: str) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Set the request ID in context.
|
|
134
|
+
|
|
135
|
+
This is useful for background tasks or other contexts where you want
|
|
136
|
+
to maintain the request ID.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
request_id: Request ID to set
|
|
140
|
+
"""
|
|
141
|
+
request_id_context.set(request_id)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class RequestIDLogProcessor:
|
|
145
|
+
"""
|
|
146
|
+
Structlog processor to add request ID to all log entries.
|
|
147
|
+
|
|
148
|
+
Use this in your structlog configuration:
|
|
149
|
+
|
|
150
|
+
structlog.configure(
|
|
151
|
+
processors=[
|
|
152
|
+
RequestIDLogProcessor(),
|
|
153
|
+
structlog.processors.add_log_level,
|
|
154
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
155
|
+
structlog.processors.JSONRenderer(),
|
|
156
|
+
],
|
|
157
|
+
...
|
|
158
|
+
)
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __call__(self, logger, name, event_dict):
|
|
162
|
+
"""Add request ID to log entry if available."""
|
|
163
|
+
request_id = get_request_id()
|
|
164
|
+
if request_id:
|
|
165
|
+
event_dict["request_id"] = request_id
|
|
166
|
+
return event_dict
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def setup_request_id_logging():
|
|
170
|
+
"""
|
|
171
|
+
Configure structlog to include request ID in all logs.
|
|
172
|
+
|
|
173
|
+
Call this in your app initialization.
|
|
174
|
+
"""
|
|
175
|
+
import structlog
|
|
176
|
+
|
|
177
|
+
structlog.configure(
|
|
178
|
+
processors=[
|
|
179
|
+
RequestIDLogProcessor(), # Add request ID
|
|
180
|
+
structlog.contextvars.merge_contextvars,
|
|
181
|
+
structlog.processors.add_log_level,
|
|
182
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
183
|
+
structlog.processors.JSONRenderer(),
|
|
184
|
+
],
|
|
185
|
+
wrapper_class=structlog.make_filtering_bound_logger(
|
|
186
|
+
logging.INFO
|
|
187
|
+
),
|
|
188
|
+
logger_factory=structlog.PrintLoggerFactory(),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# For FastAPI dependency injection
|
|
193
|
+
async def get_request_id_from_request(request: Request) -> str:
|
|
194
|
+
"""
|
|
195
|
+
FastAPI dependency to get request ID.
|
|
196
|
+
|
|
197
|
+
Usage:
|
|
198
|
+
@app.get("/example")
|
|
199
|
+
async def example(request_id: str = Depends(get_request_id_from_request)):
|
|
200
|
+
return {"request_id": request_id}
|
|
201
|
+
"""
|
|
202
|
+
return getattr(request.state, "request_id", None) or str(uuid.uuid4())
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Database Models
|
|
2
|
+
from control_plane_api.app.models.project import Project, ProjectStatus
|
|
3
|
+
from control_plane_api.app.models.agent import Agent, AgentStatus
|
|
4
|
+
from control_plane_api.app.models.team import Team, TeamStatus
|
|
5
|
+
from control_plane_api.app.models.workflow import Workflow, WorkflowStatus
|
|
6
|
+
from control_plane_api.app.models.workspace import Workspace
|
|
7
|
+
from control_plane_api.app.models.user_profile import UserProfile
|
|
8
|
+
from control_plane_api.app.models.session import Session
|
|
9
|
+
from control_plane_api.app.models.execution import Execution, ExecutionStatus, ExecutionType, ExecutionTriggerSource
|
|
10
|
+
from control_plane_api.app.models.presence import UserPresence
|
|
11
|
+
from control_plane_api.app.models.environment import Environment, EnvironmentStatus
|
|
12
|
+
from control_plane_api.app.models.associations import AgentEnvironment, TeamEnvironment, ExecutionParticipant, ParticipantRole
|
|
13
|
+
from control_plane_api.app.models.job import Job, JobExecution, JobStatus, JobTriggerType, ExecutorType, PlanningMode
|
|
14
|
+
from control_plane_api.app.models.llm_model import LLMModel
|
|
15
|
+
from control_plane_api.app.models.orchestration import Namespace, TemporalNamespace
|
|
16
|
+
from control_plane_api.app.models.project_management import ProjectAgent
|
|
17
|
+
from control_plane_api.app.models.skill import SkillAssociation, Skill
|
|
18
|
+
from control_plane_api.app.models.worker import WorkerQueue
|
|
19
|
+
from control_plane_api.app.models.trace import Trace, Span, TraceStatus, SpanKind, SpanStatusCode
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Project", "ProjectStatus",
|
|
23
|
+
"Agent", "AgentStatus",
|
|
24
|
+
"Team", "TeamStatus",
|
|
25
|
+
"Workflow", "WorkflowStatus",
|
|
26
|
+
"Workspace",
|
|
27
|
+
"UserProfile",
|
|
28
|
+
"Session",
|
|
29
|
+
"Execution", "ExecutionStatus", "ExecutionType", "ExecutionTriggerSource",
|
|
30
|
+
"UserPresence",
|
|
31
|
+
"Environment", "EnvironmentStatus",
|
|
32
|
+
"AgentEnvironment", "TeamEnvironment",
|
|
33
|
+
"ExecutionParticipant", "ParticipantRole",
|
|
34
|
+
"Job", "JobExecution", "JobStatus", "JobTriggerType", "ExecutorType", "PlanningMode",
|
|
35
|
+
"LLMModel",
|
|
36
|
+
"Namespace", "TemporalNamespace", "ProjectAgent",
|
|
37
|
+
"SkillAssociation", "Skill",
|
|
38
|
+
"WorkerQueue",
|
|
39
|
+
"Trace", "Span", "TraceStatus", "SpanKind", "SpanStatusCode"
|
|
40
|
+
]
|