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,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LiteLLM Pricing Integration - Fetch and cache model pricing data
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import structlog
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
logger = structlog.get_logger()
|
|
12
|
+
|
|
13
|
+
# Cache for pricing data
|
|
14
|
+
_pricing_cache: Optional[Dict] = None
|
|
15
|
+
_cache_timestamp: Optional[datetime] = None
|
|
16
|
+
_cache_lock = asyncio.Lock()
|
|
17
|
+
|
|
18
|
+
PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/refs/heads/main/model_prices_and_context_window.json"
|
|
19
|
+
CACHE_TTL_HOURS = 24 # Refresh pricing data daily
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def get_litellm_pricing() -> Dict:
|
|
23
|
+
"""
|
|
24
|
+
Fetch LiteLLM pricing data with caching
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dict containing model pricing information
|
|
28
|
+
"""
|
|
29
|
+
global _pricing_cache, _cache_timestamp
|
|
30
|
+
|
|
31
|
+
async with _cache_lock:
|
|
32
|
+
# Check if cache is valid
|
|
33
|
+
if _pricing_cache and _cache_timestamp:
|
|
34
|
+
age = datetime.utcnow() - _cache_timestamp
|
|
35
|
+
if age < timedelta(hours=CACHE_TTL_HOURS):
|
|
36
|
+
logger.debug("litellm_pricing_cache_hit", age_hours=age.total_seconds() / 3600)
|
|
37
|
+
return _pricing_cache
|
|
38
|
+
|
|
39
|
+
# Fetch fresh pricing data
|
|
40
|
+
logger.info("fetching_litellm_pricing", url=PRICING_URL)
|
|
41
|
+
try:
|
|
42
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
43
|
+
response = await client.get(PRICING_URL)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
pricing_data = response.json()
|
|
46
|
+
|
|
47
|
+
# Update cache
|
|
48
|
+
_pricing_cache = pricing_data
|
|
49
|
+
_cache_timestamp = datetime.utcnow()
|
|
50
|
+
|
|
51
|
+
logger.info(
|
|
52
|
+
"litellm_pricing_fetched_successfully",
|
|
53
|
+
models_count=len(pricing_data),
|
|
54
|
+
cached_until=(_cache_timestamp + timedelta(hours=CACHE_TTL_HOURS)).isoformat()
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return pricing_data
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error("litellm_pricing_fetch_failed", error=str(e), error_type=type(e).__name__)
|
|
60
|
+
# Return empty dict on failure
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_model_pricing(model_id: str, pricing_data: Dict) -> Optional[Dict]:
|
|
65
|
+
"""
|
|
66
|
+
Get pricing information for a specific model
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
model_id: Model identifier (e.g., "claude-sonnet-4", "gpt-4o")
|
|
70
|
+
pricing_data: Full pricing data from LiteLLM
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict with pricing info or None if not found
|
|
74
|
+
"""
|
|
75
|
+
# Try exact match first
|
|
76
|
+
if model_id in pricing_data:
|
|
77
|
+
return pricing_data[model_id]
|
|
78
|
+
|
|
79
|
+
# Try common variations
|
|
80
|
+
variations = [
|
|
81
|
+
model_id,
|
|
82
|
+
f"openai/{model_id}",
|
|
83
|
+
f"anthropic/{model_id}",
|
|
84
|
+
f"anthropic.{model_id}",
|
|
85
|
+
f"bedrock/{model_id}",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
for variation in variations:
|
|
89
|
+
if variation in pricing_data:
|
|
90
|
+
return pricing_data[variation]
|
|
91
|
+
|
|
92
|
+
logger.warning("model_pricing_not_found", model_id=model_id, tried_variations=variations)
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def calculate_llm_cost(
|
|
97
|
+
model_id: str,
|
|
98
|
+
estimated_input_tokens: int,
|
|
99
|
+
estimated_output_tokens: int,
|
|
100
|
+
pricing_data: Dict
|
|
101
|
+
) -> tuple[float, float, float]:
|
|
102
|
+
"""
|
|
103
|
+
Calculate LLM cost for a model
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
model_id: Model identifier
|
|
107
|
+
estimated_input_tokens: Expected input tokens
|
|
108
|
+
estimated_output_tokens: Expected output tokens
|
|
109
|
+
pricing_data: Full pricing data from LiteLLM
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Tuple of (cost_per_1k_input, cost_per_1k_output, total_cost)
|
|
113
|
+
"""
|
|
114
|
+
model_pricing = get_model_pricing(model_id, pricing_data)
|
|
115
|
+
|
|
116
|
+
if not model_pricing:
|
|
117
|
+
# Fallback defaults
|
|
118
|
+
logger.warning("using_default_pricing", model_id=model_id)
|
|
119
|
+
return (0.003, 0.015, 0.0)
|
|
120
|
+
|
|
121
|
+
input_cost_per_token = model_pricing.get("input_cost_per_token", 0.000003)
|
|
122
|
+
output_cost_per_token = model_pricing.get("output_cost_per_token", 0.000015)
|
|
123
|
+
|
|
124
|
+
# Convert to per-1k pricing for display
|
|
125
|
+
input_cost_per_1k = input_cost_per_token * 1000
|
|
126
|
+
output_cost_per_1k = output_cost_per_token * 1000
|
|
127
|
+
|
|
128
|
+
# Calculate total cost
|
|
129
|
+
total_cost = (
|
|
130
|
+
(estimated_input_tokens * input_cost_per_token) +
|
|
131
|
+
(estimated_output_tokens * output_cost_per_token)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return (input_cost_per_1k, output_cost_per_1k, total_cost)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_model_display_name(model_id: str) -> str:
|
|
138
|
+
"""
|
|
139
|
+
Get human-readable model name
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
model_id: Model identifier
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Display name
|
|
146
|
+
"""
|
|
147
|
+
# Map of common model IDs to display names
|
|
148
|
+
display_names = {
|
|
149
|
+
"claude-sonnet-4": "Claude Sonnet 4",
|
|
150
|
+
"claude-3-5-sonnet-20241022": "Claude 3.5 Sonnet",
|
|
151
|
+
"claude-3-5-sonnet-20240620": "Claude 3.5 Sonnet (Legacy)",
|
|
152
|
+
"claude-3-opus-20240229": "Claude 3 Opus",
|
|
153
|
+
"claude-3-haiku-20240307": "Claude 3 Haiku",
|
|
154
|
+
"gpt-4o": "GPT-4o",
|
|
155
|
+
"gpt-4o-mini": "GPT-4o Mini",
|
|
156
|
+
"gpt-4-turbo": "GPT-4 Turbo",
|
|
157
|
+
"gpt-4": "GPT-4",
|
|
158
|
+
"gpt-3.5-turbo": "GPT-3.5 Turbo",
|
|
159
|
+
"o1": "OpenAI o1",
|
|
160
|
+
"o1-mini": "OpenAI o1-mini",
|
|
161
|
+
"gemini-2.0-flash-exp": "Gemini 2.0 Flash",
|
|
162
|
+
"gemini-1.5-pro": "Gemini 1.5 Pro",
|
|
163
|
+
"gemini-1.5-flash": "Gemini 1.5 Flash",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return display_names.get(model_id, model_id.replace("-", " ").title())
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP configuration validation helpers.
|
|
3
|
+
|
|
4
|
+
Validates MCP server configurations and template syntax.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import structlog
|
|
8
|
+
from typing import Dict, List, Any, Optional
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
11
|
+
from control_plane_api.app.schemas.mcp_schemas import MCPServerConfig
|
|
12
|
+
from control_plane_api.app.lib.templating import extract_all_variables, TemplateValidator, TemplateContext, get_default_engine
|
|
13
|
+
|
|
14
|
+
logger = structlog.get_logger()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MCPValidationError(Exception):
|
|
18
|
+
"""Raised when MCP configuration validation fails."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_mcp_server_config(
|
|
23
|
+
mcp_servers: Dict[str, Any],
|
|
24
|
+
available_secrets: Optional[List[str]] = None,
|
|
25
|
+
available_env_vars: Optional[List[str]] = None,
|
|
26
|
+
strict: bool = False
|
|
27
|
+
) -> Dict[str, Any]:
|
|
28
|
+
"""
|
|
29
|
+
Validate MCP server configuration.
|
|
30
|
+
|
|
31
|
+
Validates:
|
|
32
|
+
1. MCP server configuration schema (Pydantic validation)
|
|
33
|
+
2. Template syntax in all string fields
|
|
34
|
+
3. Referenced secrets exist (if available_secrets provided)
|
|
35
|
+
4. Referenced env vars exist (if available_env_vars provided)
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
mcp_servers: Dict of MCP server configurations
|
|
39
|
+
available_secrets: List of available secret names for validation
|
|
40
|
+
available_env_vars: List of available env var names for validation
|
|
41
|
+
strict: If True, raise error for missing secrets/env vars
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Dict with validation results:
|
|
45
|
+
{
|
|
46
|
+
"valid": bool,
|
|
47
|
+
"errors": List[str],
|
|
48
|
+
"warnings": List[str],
|
|
49
|
+
"required_secrets": List[str],
|
|
50
|
+
"required_env_vars": List[str]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
MCPValidationError: If strict=True and validation fails
|
|
55
|
+
"""
|
|
56
|
+
errors = []
|
|
57
|
+
warnings = []
|
|
58
|
+
all_required_secrets = set()
|
|
59
|
+
all_required_env_vars = set()
|
|
60
|
+
|
|
61
|
+
if not mcp_servers:
|
|
62
|
+
return {
|
|
63
|
+
"valid": True,
|
|
64
|
+
"errors": [],
|
|
65
|
+
"warnings": [],
|
|
66
|
+
"required_secrets": [],
|
|
67
|
+
"required_env_vars": []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Validate each server configuration
|
|
71
|
+
for server_name, server_config in mcp_servers.items():
|
|
72
|
+
try:
|
|
73
|
+
# Validate using Pydantic schema
|
|
74
|
+
validated_config = MCPServerConfig(**server_config)
|
|
75
|
+
|
|
76
|
+
# Extract template variables
|
|
77
|
+
variables = extract_all_variables(server_config)
|
|
78
|
+
required_secrets = variables.get("secrets", [])
|
|
79
|
+
required_env_vars = variables.get("env_vars", [])
|
|
80
|
+
|
|
81
|
+
all_required_secrets.update(required_secrets)
|
|
82
|
+
all_required_env_vars.update(required_env_vars)
|
|
83
|
+
|
|
84
|
+
# Check if required secrets are available
|
|
85
|
+
if available_secrets is not None:
|
|
86
|
+
missing_secrets = [s for s in required_secrets if s not in available_secrets]
|
|
87
|
+
if missing_secrets:
|
|
88
|
+
error_msg = f"Server '{server_name}': Missing secrets: {', '.join(missing_secrets)}"
|
|
89
|
+
if strict:
|
|
90
|
+
errors.append(error_msg)
|
|
91
|
+
else:
|
|
92
|
+
warnings.append(error_msg)
|
|
93
|
+
|
|
94
|
+
# Check if required env vars are available
|
|
95
|
+
if available_env_vars is not None:
|
|
96
|
+
missing_env_vars = [v for v in required_env_vars if v not in available_env_vars]
|
|
97
|
+
if missing_env_vars:
|
|
98
|
+
error_msg = f"Server '{server_name}': Missing environment variables: {', '.join(missing_env_vars)}"
|
|
99
|
+
if strict:
|
|
100
|
+
errors.append(error_msg)
|
|
101
|
+
else:
|
|
102
|
+
warnings.append(error_msg)
|
|
103
|
+
|
|
104
|
+
logger.debug(
|
|
105
|
+
"mcp_server_validated",
|
|
106
|
+
server_name=server_name,
|
|
107
|
+
transport_type=validated_config.get_transport_type().value,
|
|
108
|
+
required_secrets_count=len(required_secrets),
|
|
109
|
+
required_env_vars_count=len(required_env_vars)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
except ValidationError as e:
|
|
113
|
+
error_msg = f"Server '{server_name}': Invalid configuration: {str(e)}"
|
|
114
|
+
errors.append(error_msg)
|
|
115
|
+
logger.warning("mcp_server_validation_error", server_name=server_name, error=str(e))
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
error_msg = f"Server '{server_name}': Validation error: {str(e)}"
|
|
119
|
+
errors.append(error_msg)
|
|
120
|
+
logger.error("mcp_server_validation_exception", server_name=server_name, error=str(e))
|
|
121
|
+
|
|
122
|
+
result = {
|
|
123
|
+
"valid": len(errors) == 0,
|
|
124
|
+
"errors": errors,
|
|
125
|
+
"warnings": warnings,
|
|
126
|
+
"required_secrets": sorted(list(all_required_secrets)),
|
|
127
|
+
"required_env_vars": sorted(list(all_required_env_vars))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if strict and not result["valid"]:
|
|
131
|
+
error_summary = "; ".join(errors)
|
|
132
|
+
raise MCPValidationError(f"MCP configuration validation failed: {error_summary}")
|
|
133
|
+
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def validate_execution_environment_mcp(
|
|
138
|
+
execution_environment: Dict[str, Any],
|
|
139
|
+
strict: bool = False
|
|
140
|
+
) -> Dict[str, Any]:
|
|
141
|
+
"""
|
|
142
|
+
Validate MCP servers in an execution environment configuration.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
execution_environment: Execution environment dict with mcp_servers
|
|
146
|
+
strict: If True, raise error on validation failure
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Validation result dict
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
MCPValidationError: If strict=True and validation fails
|
|
153
|
+
"""
|
|
154
|
+
mcp_servers = execution_environment.get("mcp_servers", {})
|
|
155
|
+
available_secrets = execution_environment.get("secrets", [])
|
|
156
|
+
available_env_vars = list(execution_environment.get("env_vars", {}).keys())
|
|
157
|
+
|
|
158
|
+
return validate_mcp_server_config(
|
|
159
|
+
mcp_servers,
|
|
160
|
+
available_secrets=available_secrets,
|
|
161
|
+
available_env_vars=available_env_vars,
|
|
162
|
+
strict=strict
|
|
163
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""NATS integration components for control plane."""
|
|
2
|
+
|
|
3
|
+
from control_plane_api.app.lib.nats.credentials_manager import (
|
|
4
|
+
NATSCredentialsManager,
|
|
5
|
+
WorkerCredentials,
|
|
6
|
+
)
|
|
7
|
+
from control_plane_api.app.lib.nats.listener import NATSEventListener
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"NATSCredentialsManager",
|
|
11
|
+
"WorkerCredentials",
|
|
12
|
+
"NATSEventListener",
|
|
13
|
+
]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""NATS credentials manager for generating temporary worker credentials."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
import structlog
|
|
9
|
+
|
|
10
|
+
logger = structlog.get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkerCredentials(BaseModel):
|
|
14
|
+
"""NATS credentials for a worker."""
|
|
15
|
+
|
|
16
|
+
jwt: str = Field(..., description="User JWT token")
|
|
17
|
+
seed: str = Field(..., description="NKey seed (private key)")
|
|
18
|
+
public_key: str = Field(..., description="NKey public key")
|
|
19
|
+
subject_prefix: str = Field(..., description="Subject prefix for publishing")
|
|
20
|
+
expires_at: datetime = Field(..., description="Credential expiration time")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NATSCredentialsManager:
|
|
24
|
+
"""Generate temporary NATS credentials for workers."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
operator_jwt: str,
|
|
29
|
+
operator_seed: str,
|
|
30
|
+
account_public_key: Optional[str] = None,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize NATS credentials manager.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
operator_jwt: Operator JWT (not used for signing, just for reference)
|
|
37
|
+
operator_seed: Operator seed for signing JWTs
|
|
38
|
+
account_public_key: Optional account public key (extracted from operator JWT if not provided)
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
import nkeys
|
|
42
|
+
import jwt as jwt_lib
|
|
43
|
+
except ImportError:
|
|
44
|
+
raise ImportError(
|
|
45
|
+
"nkeys and PyJWT are required for NATS credentials. "
|
|
46
|
+
"Install with: pip install nkeys pyjwt"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
self.operator_jwt = operator_jwt
|
|
50
|
+
self.operator_seed = operator_seed
|
|
51
|
+
self.account_public_key = account_public_key
|
|
52
|
+
|
|
53
|
+
# Create signing key from operator seed
|
|
54
|
+
try:
|
|
55
|
+
self.signing_key = nkeys.from_seed(operator_seed.encode())
|
|
56
|
+
logger.info(
|
|
57
|
+
"nats_credentials_manager_initialized",
|
|
58
|
+
operator_key=self.signing_key.public_key.decode()[:10] + "...",
|
|
59
|
+
)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.error("nats_signing_key_init_failed", error=str(e))
|
|
62
|
+
raise
|
|
63
|
+
|
|
64
|
+
def create_worker_credentials(
|
|
65
|
+
self,
|
|
66
|
+
worker_id: str,
|
|
67
|
+
organization_id: str,
|
|
68
|
+
ttl_hours: int = 24,
|
|
69
|
+
) -> WorkerCredentials:
|
|
70
|
+
"""
|
|
71
|
+
Create temporary NATS user credentials (JWT + seed) for worker.
|
|
72
|
+
|
|
73
|
+
Permissions:
|
|
74
|
+
- Publish: events.{organization_id}.{worker_id}.>
|
|
75
|
+
- Subscribe: None (workers only publish)
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
worker_id: Worker UUID
|
|
79
|
+
organization_id: Organization ID
|
|
80
|
+
ttl_hours: Credential time-to-live in hours
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
WorkerCredentials with JWT and seed
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
Exception: If credential generation fails
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
import nkeys
|
|
90
|
+
import jwt as jwt_lib
|
|
91
|
+
|
|
92
|
+
# Generate user nkey
|
|
93
|
+
user_key = nkeys.create_user()
|
|
94
|
+
user_seed = user_key.seed.decode()
|
|
95
|
+
user_public_key = user_key.public_key.decode()
|
|
96
|
+
|
|
97
|
+
# Calculate expiration
|
|
98
|
+
now = datetime.now(timezone.utc)
|
|
99
|
+
expires_at = now + timedelta(hours=ttl_hours)
|
|
100
|
+
|
|
101
|
+
# Subject prefix for this worker
|
|
102
|
+
subject_prefix = f"events.{organization_id}.{worker_id}"
|
|
103
|
+
|
|
104
|
+
# Create user JWT claims
|
|
105
|
+
# Reference: https://docs.nats.io/running-a-nats-service/configuration/securing_nats/jwt
|
|
106
|
+
claims = {
|
|
107
|
+
"jti": f"worker-{worker_id}", # JWT ID
|
|
108
|
+
"iat": int(now.timestamp()), # Issued at
|
|
109
|
+
"iss": self.signing_key.public_key.decode(), # Issuer (operator key)
|
|
110
|
+
"sub": user_public_key, # Subject (user public key)
|
|
111
|
+
"exp": int(expires_at.timestamp()), # Expiration
|
|
112
|
+
"nats": {
|
|
113
|
+
"pub": {
|
|
114
|
+
# Allow publishing to worker-specific subjects
|
|
115
|
+
"allow": [f"{subject_prefix}.>"]
|
|
116
|
+
},
|
|
117
|
+
"sub": {
|
|
118
|
+
# Workers don't subscribe
|
|
119
|
+
"allow": []
|
|
120
|
+
},
|
|
121
|
+
"subs": -1, # Unlimited subscriptions (not used)
|
|
122
|
+
"data": -1, # Unlimited data
|
|
123
|
+
"payload": -1, # Unlimited payload size
|
|
124
|
+
"type": "user",
|
|
125
|
+
"version": 2,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Sign JWT with operator key using Ed25519
|
|
130
|
+
# nkeys uses Ed25519, so we sign manually
|
|
131
|
+
signing_input = self._encode_jwt_parts(claims)
|
|
132
|
+
signature = self.signing_key.sign(signing_input)
|
|
133
|
+
|
|
134
|
+
# Build complete JWT
|
|
135
|
+
import base64
|
|
136
|
+
|
|
137
|
+
jwt_parts = [
|
|
138
|
+
base64.urlsafe_b64encode(
|
|
139
|
+
json.dumps({"typ": "JWT", "alg": "ed25519-nkey"}).encode()
|
|
140
|
+
)
|
|
141
|
+
.decode()
|
|
142
|
+
.rstrip("="),
|
|
143
|
+
base64.urlsafe_b64encode(json.dumps(claims).encode())
|
|
144
|
+
.decode()
|
|
145
|
+
.rstrip("="),
|
|
146
|
+
base64.urlsafe_b64encode(signature).decode().rstrip("="),
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
user_jwt = ".".join(jwt_parts)
|
|
150
|
+
|
|
151
|
+
logger.info(
|
|
152
|
+
"nats_worker_credentials_created",
|
|
153
|
+
worker_id=worker_id[:8],
|
|
154
|
+
organization_id=organization_id,
|
|
155
|
+
subject_prefix=subject_prefix,
|
|
156
|
+
expires_at=expires_at.isoformat(),
|
|
157
|
+
ttl_hours=ttl_hours,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return WorkerCredentials(
|
|
161
|
+
jwt=user_jwt,
|
|
162
|
+
seed=user_seed,
|
|
163
|
+
public_key=user_public_key,
|
|
164
|
+
subject_prefix=subject_prefix,
|
|
165
|
+
expires_at=expires_at,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
except ImportError as e:
|
|
169
|
+
logger.error("nats_credentials_dependency_missing", error=str(e))
|
|
170
|
+
raise
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(
|
|
174
|
+
"nats_worker_credentials_creation_failed",
|
|
175
|
+
error=str(e),
|
|
176
|
+
worker_id=worker_id[:8],
|
|
177
|
+
organization_id=organization_id,
|
|
178
|
+
)
|
|
179
|
+
raise
|
|
180
|
+
|
|
181
|
+
def _encode_jwt_parts(self, claims: dict) -> bytes:
|
|
182
|
+
"""
|
|
183
|
+
Encode JWT header and payload for signing.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
claims: JWT claims
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Signing input (header.payload as bytes)
|
|
190
|
+
"""
|
|
191
|
+
import base64
|
|
192
|
+
import json
|
|
193
|
+
|
|
194
|
+
header = {"typ": "JWT", "alg": "ed25519-nkey"}
|
|
195
|
+
|
|
196
|
+
header_b64 = (
|
|
197
|
+
base64.urlsafe_b64encode(json.dumps(header).encode())
|
|
198
|
+
.decode()
|
|
199
|
+
.rstrip("=")
|
|
200
|
+
)
|
|
201
|
+
payload_b64 = (
|
|
202
|
+
base64.urlsafe_b64encode(json.dumps(claims).encode())
|
|
203
|
+
.decode()
|
|
204
|
+
.rstrip("=")
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
signing_input = f"{header_b64}.{payload_b64}"
|
|
208
|
+
return signing_input.encode()
|
|
209
|
+
|
|
210
|
+
def save_credentials_file(
|
|
211
|
+
self, credentials: WorkerCredentials, file_path: str
|
|
212
|
+
) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Save credentials to .creds file format.
|
|
215
|
+
|
|
216
|
+
The .creds file format contains both the JWT and seed in a specific format
|
|
217
|
+
that NATS clients can read.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
credentials: Worker credentials
|
|
221
|
+
file_path: Path to save .creds file
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
Exception: If file write fails
|
|
225
|
+
"""
|
|
226
|
+
try:
|
|
227
|
+
# Standard NATS .creds file format
|
|
228
|
+
creds_content = f"""-----BEGIN NATS USER JWT-----
|
|
229
|
+
{credentials.jwt}
|
|
230
|
+
------END NATS USER JWT------
|
|
231
|
+
|
|
232
|
+
************************* IMPORTANT *************************
|
|
233
|
+
NKEY Seed printed below can be used to sign and prove identity.
|
|
234
|
+
NKEYs are sensitive and should be treated as secrets.
|
|
235
|
+
|
|
236
|
+
-----BEGIN USER NKEY SEED-----
|
|
237
|
+
{credentials.seed}
|
|
238
|
+
------END USER NKEY SEED------
|
|
239
|
+
|
|
240
|
+
*************************************************************
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
# Write file
|
|
244
|
+
with open(file_path, "w") as f:
|
|
245
|
+
f.write(creds_content)
|
|
246
|
+
|
|
247
|
+
# Secure file permissions (owner read/write only)
|
|
248
|
+
os.chmod(file_path, 0o600)
|
|
249
|
+
|
|
250
|
+
logger.info(
|
|
251
|
+
"nats_credentials_file_saved",
|
|
252
|
+
file_path=file_path,
|
|
253
|
+
permissions="0600",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.error(
|
|
258
|
+
"nats_credentials_file_save_failed",
|
|
259
|
+
error=str(e),
|
|
260
|
+
file_path=file_path,
|
|
261
|
+
)
|
|
262
|
+
raise
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def get_credentials_string(credentials: WorkerCredentials) -> str:
|
|
266
|
+
"""
|
|
267
|
+
Get credentials as a string (for passing to workers without file).
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
credentials: Worker credentials
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Credentials in .creds file format as string
|
|
274
|
+
"""
|
|
275
|
+
return f"""-----BEGIN NATS USER JWT-----
|
|
276
|
+
{credentials.jwt}
|
|
277
|
+
------END NATS USER JWT------
|
|
278
|
+
|
|
279
|
+
************************* IMPORTANT *************************
|
|
280
|
+
NKEY Seed printed below can be used to sign and prove identity.
|
|
281
|
+
NKEYs are sensitive and should be treated as secrets.
|
|
282
|
+
|
|
283
|
+
-----BEGIN USER NKEY SEED-----
|
|
284
|
+
{credentials.seed}
|
|
285
|
+
------END USER NKEY SEED------
|
|
286
|
+
|
|
287
|
+
*************************************************************
|
|
288
|
+
"""
|