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,829 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration builder for Claude Code runtime.
|
|
3
|
+
|
|
4
|
+
This module handles the construction of ClaudeAgentOptions from execution
|
|
5
|
+
context, including LiteLLM integration, MCP servers, and session management.
|
|
6
|
+
|
|
7
|
+
BUG FIX #4: Added session_id validation before use.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Dict, Any, Tuple, Optional, Callable, Set, List
|
|
11
|
+
import structlog
|
|
12
|
+
import os
|
|
13
|
+
import asyncio
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
from .tool_mapper import map_skills_to_tools, validate_tool_names
|
|
19
|
+
from .mcp_builder import build_mcp_servers
|
|
20
|
+
from .hooks import build_hooks
|
|
21
|
+
from .mcp_discovery import discover_all_mcp_resources
|
|
22
|
+
from control_plane_api.worker.services.system_prompt_enhancement import create_default_prompt_builder
|
|
23
|
+
from .litellm_proxy import (
|
|
24
|
+
get_proxy_base_url,
|
|
25
|
+
set_execution_context,
|
|
26
|
+
clear_execution_context,
|
|
27
|
+
)
|
|
28
|
+
from control_plane_api.worker.runtimes.model_utils import get_effective_model, is_model_override_active
|
|
29
|
+
|
|
30
|
+
logger = structlog.get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
# Module-level singleton for system prompt enhancement
|
|
33
|
+
# NOTE: This is now created per-execution to support dynamic skill context
|
|
34
|
+
# _prompt_builder = create_default_prompt_builder()
|
|
35
|
+
|
|
36
|
+
# Note: Claude SDK handles MCP discovery automatically - we verify connection and log errors
|
|
37
|
+
|
|
38
|
+
# MCP Discovery Cache - Reduces process spawning by 50%
|
|
39
|
+
# Key: hash of MCP server configuration, Value: discovery results
|
|
40
|
+
_mcp_discovery_cache: Dict[str, Dict[str, Any]] = {}
|
|
41
|
+
|
|
42
|
+
# Options Cache - Reduces options rebuild overhead by ~80%
|
|
43
|
+
# Key: hash of execution configuration, Value: (options, timestamp)
|
|
44
|
+
# This cache stores built ClaudeAgentOptions to avoid rebuilding when config unchanged
|
|
45
|
+
_options_cache: Dict[str, Tuple[Any, float]] = {}
|
|
46
|
+
_options_cache_ttl = int(os.getenv("CLAUDE_CODE_OPTIONS_CACHE_TTL", "3600")) # 1 hour default
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_mcp_cache_key(mcp_servers: Dict[str, Any]) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Generate cache key for MCP server configuration.
|
|
52
|
+
|
|
53
|
+
Uses hash of server configs to detect when discovery needs to be re-run.
|
|
54
|
+
Only command/args/env matter for STDIO servers, URL for HTTP servers.
|
|
55
|
+
"""
|
|
56
|
+
# Sort keys for consistent hashing
|
|
57
|
+
cache_input = {}
|
|
58
|
+
for server_name, server_config in sorted(mcp_servers.items()):
|
|
59
|
+
# Extract relevant fields for cache key
|
|
60
|
+
transport = server_config.get("transport", {})
|
|
61
|
+
transport_type = transport.get("type", "stdio")
|
|
62
|
+
|
|
63
|
+
if transport_type == "stdio":
|
|
64
|
+
# For stdio: command + args determine the process
|
|
65
|
+
cache_input[server_name] = {
|
|
66
|
+
"type": "stdio",
|
|
67
|
+
"command": transport.get("command", ""),
|
|
68
|
+
"args": transport.get("args", []),
|
|
69
|
+
"env": transport.get("env", {}),
|
|
70
|
+
}
|
|
71
|
+
elif transport_type in ["http", "sse"]:
|
|
72
|
+
# For HTTP/SSE: URL is what matters
|
|
73
|
+
cache_input[server_name] = {
|
|
74
|
+
"type": transport_type,
|
|
75
|
+
"url": transport.get("url", ""),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Hash the configuration
|
|
79
|
+
config_json = json.dumps(cache_input, sort_keys=True)
|
|
80
|
+
return hashlib.sha256(config_json.encode()).hexdigest()[:16] # Short hash
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _get_options_cache_key(context: Any) -> str:
|
|
84
|
+
"""
|
|
85
|
+
Generate cache key from execution context configuration.
|
|
86
|
+
|
|
87
|
+
This hash includes all configuration that affects options building:
|
|
88
|
+
- Model ID and system prompt
|
|
89
|
+
- Agent ID (affects permissions and config)
|
|
90
|
+
- Skills (determines tools and MCP servers)
|
|
91
|
+
- MCP servers configuration
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
context: RuntimeExecutionContext
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Cache key string (16-char hash)
|
|
98
|
+
"""
|
|
99
|
+
# Extract skills as sorted list of names/types for consistent hashing
|
|
100
|
+
skill_identifiers = []
|
|
101
|
+
if hasattr(context, 'skills') and context.skills:
|
|
102
|
+
for skill in context.skills:
|
|
103
|
+
if isinstance(skill, dict):
|
|
104
|
+
skill_identifiers.append({
|
|
105
|
+
"name": skill.get("name", ""),
|
|
106
|
+
"type": skill.get("type", ""),
|
|
107
|
+
})
|
|
108
|
+
else:
|
|
109
|
+
# Toolkit object - use class name
|
|
110
|
+
skill_identifiers.append({"type": type(skill).__name__})
|
|
111
|
+
|
|
112
|
+
# Build cache input (only config that affects options)
|
|
113
|
+
cache_input = {
|
|
114
|
+
"model_id": context.model_id or "",
|
|
115
|
+
"system_prompt_length": len(context.system_prompt or ""), # Use length to avoid huge strings
|
|
116
|
+
"agent_id": context.agent_id,
|
|
117
|
+
"skill_identifiers": sorted(skill_identifiers, key=lambda x: (x.get("type", ""), x.get("name", ""))),
|
|
118
|
+
"mcp_server_names": sorted((context.mcp_servers or {}).keys()) if hasattr(context, 'mcp_servers') else [],
|
|
119
|
+
# Don't include execution_id or session_id - those vary per execution but don't affect config
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
config_json = json.dumps(cache_input, sort_keys=True)
|
|
123
|
+
return hashlib.sha256(config_json.encode()).hexdigest()[:16] # Short hash
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def build_mcp_permission_handler(
|
|
127
|
+
mcp_servers: Dict[str, Any], allowed_tools: List[str]
|
|
128
|
+
) -> Callable:
|
|
129
|
+
"""
|
|
130
|
+
Build permission handler for MCP tools.
|
|
131
|
+
|
|
132
|
+
IMPORTANT: The SDK discovers MCP tools automatically, but does NOT
|
|
133
|
+
automatically grant permission to use them. We need to handle permissions
|
|
134
|
+
separately using the canUseTool callback.
|
|
135
|
+
|
|
136
|
+
This handler:
|
|
137
|
+
1. Allows tools in the allowed_tools list (builtin tools)
|
|
138
|
+
2. Auto-allows tools matching mcp__<server_name>__* for configured servers
|
|
139
|
+
3. Denies everything else
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
mcp_servers: Dict of MCP server configurations
|
|
143
|
+
allowed_tools: List of allowed builtin tool names
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Async permission handler for canUseTool parameter
|
|
147
|
+
"""
|
|
148
|
+
# Extract MCP server names for permission matching
|
|
149
|
+
mcp_server_names: Set[str] = set(mcp_servers.keys())
|
|
150
|
+
|
|
151
|
+
logger.info(
|
|
152
|
+
"building_mcp_permission_handler",
|
|
153
|
+
mcp_server_count=len(mcp_server_names),
|
|
154
|
+
mcp_server_names=list(mcp_server_names),
|
|
155
|
+
builtin_tools_count=len(allowed_tools),
|
|
156
|
+
note="SDK discovers tools, but we handle permissions via canUseTool"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
async def permission_handler(
|
|
160
|
+
tool_name: str, input_data: dict, context: dict
|
|
161
|
+
) -> Dict[str, Any]:
|
|
162
|
+
"""
|
|
163
|
+
Permission handler that allows:
|
|
164
|
+
1. Builtin tools from allowed_tools list
|
|
165
|
+
2. MCP tools matching mcp__<server_name>__* pattern
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
tool_name: Tool being invoked
|
|
169
|
+
input_data: Tool input parameters
|
|
170
|
+
context: Execution context
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Permission decision: {"behavior": "allow"|"deny", "updatedInput": input_data}
|
|
174
|
+
"""
|
|
175
|
+
# ALWAYS log permission checks for debugging
|
|
176
|
+
logger.info(
|
|
177
|
+
"permission_handler_called",
|
|
178
|
+
tool_name=tool_name,
|
|
179
|
+
is_builtin=tool_name in allowed_tools,
|
|
180
|
+
is_mcp=tool_name.startswith("mcp__"),
|
|
181
|
+
configured_servers=list(mcp_server_names),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Allow builtin tools
|
|
185
|
+
if tool_name in allowed_tools:
|
|
186
|
+
logger.info("permission_granted_builtin", tool_name=tool_name)
|
|
187
|
+
return {"behavior": "allow", "updatedInput": input_data}
|
|
188
|
+
|
|
189
|
+
# Allow MCP tools from configured servers
|
|
190
|
+
# Pattern: mcp__<server_name>__<tool_name>
|
|
191
|
+
if tool_name.startswith("mcp__"):
|
|
192
|
+
parts = tool_name.split("__", 2)
|
|
193
|
+
if len(parts) >= 2:
|
|
194
|
+
server_name = parts[1]
|
|
195
|
+
logger.info(
|
|
196
|
+
"checking_mcp_permission",
|
|
197
|
+
tool_name=tool_name,
|
|
198
|
+
extracted_server=server_name,
|
|
199
|
+
configured_servers=list(mcp_server_names),
|
|
200
|
+
matches=server_name in mcp_server_names,
|
|
201
|
+
)
|
|
202
|
+
if server_name in mcp_server_names:
|
|
203
|
+
logger.info(
|
|
204
|
+
"permission_granted_mcp",
|
|
205
|
+
tool_name=tool_name,
|
|
206
|
+
server_name=server_name,
|
|
207
|
+
)
|
|
208
|
+
return {"behavior": "allow", "updatedInput": input_data}
|
|
209
|
+
|
|
210
|
+
# Deny unrecognized tools
|
|
211
|
+
logger.warning(
|
|
212
|
+
"tool_permission_denied",
|
|
213
|
+
tool_name=tool_name,
|
|
214
|
+
reason="not_in_allowed_tools_or_mcp_servers",
|
|
215
|
+
available_mcp_servers=list(mcp_server_names),
|
|
216
|
+
available_builtin_count=len(allowed_tools),
|
|
217
|
+
)
|
|
218
|
+
return {
|
|
219
|
+
"behavior": "deny",
|
|
220
|
+
"updatedInput": input_data,
|
|
221
|
+
"message": f"Tool '{tool_name}' not permitted. Not in allowed tools or MCP servers."
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return permission_handler
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def validate_session_id(session_id: Optional[str]) -> Optional[str]:
|
|
228
|
+
"""
|
|
229
|
+
Validate session_id format before use.
|
|
230
|
+
|
|
231
|
+
BUG FIX #4: Ensures session_id is valid before storing for multi-turn.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
session_id: Session ID to validate
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Valid session_id or None if invalid
|
|
238
|
+
"""
|
|
239
|
+
if not session_id:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
if not isinstance(session_id, str) or len(session_id) < 10:
|
|
243
|
+
logger.warning(
|
|
244
|
+
"invalid_session_id_format",
|
|
245
|
+
session_id=session_id if isinstance(session_id, str) else None,
|
|
246
|
+
type=type(session_id).__name__,
|
|
247
|
+
length=len(session_id) if isinstance(session_id, str) else 0,
|
|
248
|
+
)
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
return session_id
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
async def build_claude_options(
|
|
255
|
+
context: Any, # RuntimeExecutionContext
|
|
256
|
+
event_callback: Optional[Callable] = None,
|
|
257
|
+
runtime: Optional[Any] = None, # ClaudeCodeRuntime instance for caching
|
|
258
|
+
) -> Tuple[Any, Dict[str, str], Set[str], Set[str]]:
|
|
259
|
+
"""
|
|
260
|
+
Build ClaudeAgentOptions from execution context.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
context: RuntimeExecutionContext with prompt, history, config
|
|
264
|
+
event_callback: Optional event callback for hooks
|
|
265
|
+
runtime: Optional ClaudeCodeRuntime instance for MCP discovery caching
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Tuple of (ClaudeAgentOptions instance, active_tools dict, started_tools set, completed_tools set)
|
|
269
|
+
"""
|
|
270
|
+
from claude_agent_sdk import ClaudeAgentOptions
|
|
271
|
+
|
|
272
|
+
# Extract configuration
|
|
273
|
+
agent_config = context.agent_config or {}
|
|
274
|
+
runtime_config = context.runtime_config or {}
|
|
275
|
+
|
|
276
|
+
# Get LiteLLM configuration (same as DefaultRuntime/Agno)
|
|
277
|
+
litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
|
|
278
|
+
litellm_api_key = os.getenv("LITELLM_API_KEY")
|
|
279
|
+
|
|
280
|
+
if not litellm_api_key:
|
|
281
|
+
raise ValueError("LITELLM_API_KEY environment variable not set")
|
|
282
|
+
|
|
283
|
+
# Determine model (use LiteLLM format) with override support
|
|
284
|
+
# Priority: KUBIYA_MODEL_OVERRIDE > context.model_id > LITELLM_DEFAULT_MODEL > default
|
|
285
|
+
model = get_effective_model(
|
|
286
|
+
context_model_id=context.model_id,
|
|
287
|
+
log_context={"execution_id": context.execution_id[:8] if context.execution_id else "unknown"},
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Map skills to Claude Code tool names (built-in tools only)
|
|
291
|
+
allowed_tools = map_skills_to_tools(context.skills)
|
|
292
|
+
|
|
293
|
+
# Build MCP servers (both from context and custom skills)
|
|
294
|
+
# SDK will discover tools automatically - we just provide configs
|
|
295
|
+
mcp_servers, _ = build_mcp_servers(
|
|
296
|
+
context.skills, context.mcp_servers
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Verify MCP server connections and discover tools (with caching)
|
|
300
|
+
# This helps us detect configuration errors early
|
|
301
|
+
# Cache reduces process spawning by 50% by reusing discovery results
|
|
302
|
+
mcp_discovery_results = {}
|
|
303
|
+
if mcp_servers:
|
|
304
|
+
try:
|
|
305
|
+
# Check cache first
|
|
306
|
+
cache_key = _get_mcp_cache_key(mcp_servers)
|
|
307
|
+
if cache_key in _mcp_discovery_cache:
|
|
308
|
+
mcp_discovery_results = _mcp_discovery_cache[cache_key]
|
|
309
|
+
logger.info(
|
|
310
|
+
"using_cached_mcp_discovery",
|
|
311
|
+
server_count=len(mcp_servers),
|
|
312
|
+
server_names=list(mcp_servers.keys()),
|
|
313
|
+
cache_key=cache_key,
|
|
314
|
+
note="ā
Using cached MCP discovery results (no processes spawned)"
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
logger.info(
|
|
318
|
+
"verifying_mcp_server_connections",
|
|
319
|
+
server_count=len(mcp_servers),
|
|
320
|
+
server_names=list(mcp_servers.keys()),
|
|
321
|
+
cache_key=cache_key,
|
|
322
|
+
note="Attempting to connect and discover tools from all MCP servers"
|
|
323
|
+
)
|
|
324
|
+
mcp_discovery_results = await discover_all_mcp_resources(mcp_servers)
|
|
325
|
+
# Cache the results for future executions
|
|
326
|
+
_mcp_discovery_cache[cache_key] = mcp_discovery_results
|
|
327
|
+
logger.info(
|
|
328
|
+
"cached_mcp_discovery_results",
|
|
329
|
+
cache_key=cache_key,
|
|
330
|
+
note="Discovery results cached for future executions"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Log results for each server
|
|
334
|
+
failed_servers = []
|
|
335
|
+
successful_servers = []
|
|
336
|
+
skipped_servers = []
|
|
337
|
+
for server_name, result in mcp_discovery_results.items():
|
|
338
|
+
# Check if server was skipped (HTTP servers use native SDK discovery)
|
|
339
|
+
if result.get("skipped"):
|
|
340
|
+
skipped_servers.append(server_name)
|
|
341
|
+
logger.info(
|
|
342
|
+
"mcp_server_skipped_native_discovery",
|
|
343
|
+
server_name=server_name,
|
|
344
|
+
status="ā” HTTP - Using SDK native discovery",
|
|
345
|
+
note="Pre-discovery skipped for HTTP servers (SDK handles them natively)"
|
|
346
|
+
)
|
|
347
|
+
elif result["connected"]:
|
|
348
|
+
tool_count = len(result["tools"])
|
|
349
|
+
successful_servers.append(server_name)
|
|
350
|
+
if tool_count == 0:
|
|
351
|
+
logger.warning(
|
|
352
|
+
"mcp_server_connected_but_no_tools",
|
|
353
|
+
server_name=server_name,
|
|
354
|
+
message=f"MCP server '{server_name}' connected successfully but discovered 0 tools",
|
|
355
|
+
recommendation="Check server implementation - it may not be exposing any tools"
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
logger.info(
|
|
359
|
+
"mcp_server_verified",
|
|
360
|
+
server_name=server_name,
|
|
361
|
+
tool_count=tool_count,
|
|
362
|
+
tool_names=result["tools"][:5] if tool_count <= 5 else [t["name"] for t in result["tools"][:5]] + [f"... and {tool_count - 5} more"],
|
|
363
|
+
status="ā
Connected and discovered tools"
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
failed_servers.append(server_name)
|
|
367
|
+
logger.error(
|
|
368
|
+
"mcp_server_connection_failed",
|
|
369
|
+
server_name=server_name,
|
|
370
|
+
error=result.get("error", "Unknown error"),
|
|
371
|
+
status="ā Failed to connect",
|
|
372
|
+
recommendation="Check server command, args, and environment variables in agent configuration"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Summary log
|
|
376
|
+
if failed_servers:
|
|
377
|
+
logger.error(
|
|
378
|
+
"mcp_verification_summary",
|
|
379
|
+
total_servers=len(mcp_servers),
|
|
380
|
+
http_skipped=len(skipped_servers),
|
|
381
|
+
successful=len(successful_servers),
|
|
382
|
+
failed=len(failed_servers),
|
|
383
|
+
failed_server_names=failed_servers,
|
|
384
|
+
message=f"ā ļø {len(failed_servers)} MCP server(s) failed to connect - agent may not have access to expected tools"
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
logger.info(
|
|
388
|
+
"mcp_verification_summary",
|
|
389
|
+
total_servers=len(mcp_servers),
|
|
390
|
+
http_skipped=len(skipped_servers),
|
|
391
|
+
successful=len(successful_servers),
|
|
392
|
+
total_tools_discovered=sum(len(r["tools"]) for r in mcp_discovery_results.values() if not r.get("skipped")),
|
|
393
|
+
message=f"ā
All MCP servers ready: {len(skipped_servers)} HTTP (native SDK) + {len(successful_servers)} pre-discovered"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
except Exception as discovery_error:
|
|
397
|
+
logger.error(
|
|
398
|
+
"mcp_discovery_process_failed",
|
|
399
|
+
error=str(discovery_error),
|
|
400
|
+
error_type=type(discovery_error).__name__,
|
|
401
|
+
message="Failed to verify MCP server connections - will proceed but tools may not be available",
|
|
402
|
+
exc_info=True
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# BUG FIX #6: Validate built-in tool names before using
|
|
406
|
+
allowed_tools, invalid_tools = validate_tool_names(allowed_tools)
|
|
407
|
+
|
|
408
|
+
# Build permission handler for MCP tools
|
|
409
|
+
# IMPORTANT: SDK discovers tools, but we must permit them via canUseTool
|
|
410
|
+
permission_handler = None
|
|
411
|
+
if mcp_servers:
|
|
412
|
+
permission_handler = build_mcp_permission_handler(mcp_servers, allowed_tools)
|
|
413
|
+
logger.info(
|
|
414
|
+
"mcp_permission_handler_configured",
|
|
415
|
+
mcp_servers=list(mcp_servers.keys()),
|
|
416
|
+
note="Will auto-allow tools matching mcp__<server_name>__* pattern"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
logger.info(
|
|
420
|
+
"claude_code_tools_configured",
|
|
421
|
+
builtin_tools_count=len(allowed_tools),
|
|
422
|
+
mcp_servers_count=len(mcp_servers),
|
|
423
|
+
mcp_server_names=list(mcp_servers.keys()) if mcp_servers else [],
|
|
424
|
+
builtin_tools=allowed_tools[:20], # Limit for readability
|
|
425
|
+
has_permission_handler=permission_handler is not None,
|
|
426
|
+
note="SDK discovers MCP tools automatically, we handle permissions via canUseTool"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Create shared active_tools dict for tool name tracking
|
|
430
|
+
# This is populated in the stream when ToolUseBlock is received,
|
|
431
|
+
# and used in hooks to look up tool names
|
|
432
|
+
active_tools: Dict[str, str] = {}
|
|
433
|
+
|
|
434
|
+
# Create shared started_tools set for tracking published start events
|
|
435
|
+
# This prevents duplicate tool_start events from hooks
|
|
436
|
+
from typing import Set
|
|
437
|
+
started_tools: Set[str] = set()
|
|
438
|
+
|
|
439
|
+
# Create shared completed_tools set for tracking published completion events
|
|
440
|
+
# This prevents duplicate tool_complete events from hooks and ToolResultBlock
|
|
441
|
+
completed_tools: Set[str] = set()
|
|
442
|
+
|
|
443
|
+
# Initialize enforcement service for policy checks
|
|
444
|
+
enforcement_context = {
|
|
445
|
+
"organization_id": context.organization_id,
|
|
446
|
+
"user_email": context.user_email,
|
|
447
|
+
"user_id": context.user_id,
|
|
448
|
+
"user_roles": context.user_roles or [],
|
|
449
|
+
"team_id": context.team_id,
|
|
450
|
+
"team_name": context.team_name,
|
|
451
|
+
"agent_id": context.agent_id,
|
|
452
|
+
"environment": context.environment,
|
|
453
|
+
"model_id": context.model_id,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# Import enforcement dependencies
|
|
457
|
+
from control_plane_api.app.lib.policy_enforcer_client import create_policy_enforcer_client
|
|
458
|
+
from control_plane_api.worker.services.tool_enforcement import ToolEnforcementService
|
|
459
|
+
|
|
460
|
+
# Get enforcer client (using the same token as the control plane)
|
|
461
|
+
enforcer_client = None
|
|
462
|
+
enforcement_service = None
|
|
463
|
+
|
|
464
|
+
# Check if enforcement is enabled (opt-in via environment variable)
|
|
465
|
+
enforcement_enabled = os.environ.get("KUBIYA_ENFORCE_ENABLED", "").lower() in ("true", "1", "yes")
|
|
466
|
+
|
|
467
|
+
if not enforcement_enabled:
|
|
468
|
+
logger.info(
|
|
469
|
+
"policy_enforcement_disabled",
|
|
470
|
+
reason="KUBIYA_ENFORCE_ENABLED not set",
|
|
471
|
+
execution_id=context.execution_id[:8],
|
|
472
|
+
note="Set KUBIYA_ENFORCE_ENABLED=true to enable policy enforcement"
|
|
473
|
+
)
|
|
474
|
+
else:
|
|
475
|
+
try:
|
|
476
|
+
# Get API key from runtime (if available)
|
|
477
|
+
api_key = runtime.control_plane.api_key if runtime and hasattr(runtime, 'control_plane') else None
|
|
478
|
+
if api_key:
|
|
479
|
+
# Get enforcer URL - default to control plane enforcer proxy
|
|
480
|
+
enforcer_url = os.environ.get("ENFORCER_SERVICE_URL")
|
|
481
|
+
if not enforcer_url:
|
|
482
|
+
# Use control plane's enforcer proxy as default
|
|
483
|
+
control_plane_url = os.environ.get("CONTROL_PLANE_URL", "http://localhost:8000")
|
|
484
|
+
enforcer_url = f"{control_plane_url.rstrip('/')}/api/v1/enforcer"
|
|
485
|
+
logger.debug(
|
|
486
|
+
"using_control_plane_enforcer_proxy",
|
|
487
|
+
enforcer_url=enforcer_url,
|
|
488
|
+
execution_id=context.execution_id[:8],
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Use async context manager properly (we're in an async function)
|
|
492
|
+
enforcer_client_context = create_policy_enforcer_client(
|
|
493
|
+
enforcer_url=enforcer_url,
|
|
494
|
+
api_key=api_key,
|
|
495
|
+
auth_type="UserKey"
|
|
496
|
+
)
|
|
497
|
+
enforcer_client = await enforcer_client_context.__aenter__()
|
|
498
|
+
if enforcer_client:
|
|
499
|
+
enforcement_service = ToolEnforcementService(enforcer_client)
|
|
500
|
+
logger.info(
|
|
501
|
+
"policy_enforcement_enabled",
|
|
502
|
+
enforcer_url=enforcer_url,
|
|
503
|
+
execution_id=context.execution_id[:8],
|
|
504
|
+
)
|
|
505
|
+
else:
|
|
506
|
+
logger.debug(
|
|
507
|
+
"enforcement_service_skipped",
|
|
508
|
+
reason="no_api_key_available",
|
|
509
|
+
execution_id=context.execution_id[:8],
|
|
510
|
+
)
|
|
511
|
+
except Exception as e:
|
|
512
|
+
logger.warning(
|
|
513
|
+
"enforcement_service_init_failed",
|
|
514
|
+
error=str(e),
|
|
515
|
+
execution_id=context.execution_id[:8],
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# Build hooks for tool execution monitoring with enforcement
|
|
519
|
+
hooks = (
|
|
520
|
+
build_hooks(
|
|
521
|
+
context.execution_id,
|
|
522
|
+
event_callback,
|
|
523
|
+
active_tools,
|
|
524
|
+
completed_tools,
|
|
525
|
+
started_tools,
|
|
526
|
+
enforcement_context=enforcement_context,
|
|
527
|
+
enforcement_service=enforcement_service,
|
|
528
|
+
)
|
|
529
|
+
if event_callback
|
|
530
|
+
else {}
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
# Build environment with LiteLLM configuration
|
|
534
|
+
env = runtime_config.get("env", {}).copy()
|
|
535
|
+
|
|
536
|
+
# Check if model override is active - if so, we'll bypass the internal proxy
|
|
537
|
+
model_override_active = is_model_override_active()
|
|
538
|
+
model_override_value = os.environ.get("KUBIYA_MODEL_OVERRIDE") if model_override_active else None
|
|
539
|
+
|
|
540
|
+
if model_override_active:
|
|
541
|
+
logger.info(
|
|
542
|
+
"model_override_detected_bypassing_internal_proxy",
|
|
543
|
+
model_override=model_override_value,
|
|
544
|
+
note="Internal LiteLLM proxy will be bypassed, using direct API configuration"
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# Extract and validate secrets from skill configurations
|
|
548
|
+
# Skills may reference secrets (e.g., Slack skill with secret_name or secrets parameters)
|
|
549
|
+
# These secrets should be resolved and injected as environment variables by the execution environment controller
|
|
550
|
+
if context.skill_configs:
|
|
551
|
+
for skill_config in context.skill_configs:
|
|
552
|
+
config = skill_config.get("configuration", {})
|
|
553
|
+
skill_name = skill_config.get("name", "unknown")
|
|
554
|
+
|
|
555
|
+
# Check for secrets (list) or secret_name (single, deprecated)
|
|
556
|
+
secrets_list = []
|
|
557
|
+
if "secrets" in config:
|
|
558
|
+
secrets = config["secrets"]
|
|
559
|
+
if isinstance(secrets, list):
|
|
560
|
+
secrets_list = secrets
|
|
561
|
+
elif isinstance(secrets, str):
|
|
562
|
+
secrets_list = [s.strip() for s in secrets.split(",") if s.strip()]
|
|
563
|
+
elif "secret_name" in config and config["secret_name"]:
|
|
564
|
+
secrets_list = [config["secret_name"]]
|
|
565
|
+
|
|
566
|
+
# Validate that configured secrets are available in environment
|
|
567
|
+
for secret_name in secrets_list:
|
|
568
|
+
if secret_name not in env:
|
|
569
|
+
logger.warning(
|
|
570
|
+
"skill_secret_not_resolved",
|
|
571
|
+
skill_name=skill_name,
|
|
572
|
+
secret_name=secret_name,
|
|
573
|
+
note=f"Secret '{secret_name}' configured in skill '{skill_name}' but not found in execution environment. "
|
|
574
|
+
f"Ensure the secret is added to the execution environment's secrets list."
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
# LOG WHAT ENV VARS WE RECEIVED FROM RUNTIME CONFIG
|
|
578
|
+
print(f"\nš CLAUDE CODE CONFIG - ENV VARS RECEIVED:")
|
|
579
|
+
print(f" Received from runtime_config: {len(env)} variables")
|
|
580
|
+
print(f" Keys: {list(env.keys())}")
|
|
581
|
+
for key, value in env.items():
|
|
582
|
+
if any(s in key.upper() for s in ["TOKEN", "KEY", "SECRET", "PASSWORD"]):
|
|
583
|
+
masked = f"{value[:10]}...{value[-5:]}" if len(value) > 15 else "***"
|
|
584
|
+
print(f" {key}: {masked} (length: {len(value)})")
|
|
585
|
+
else:
|
|
586
|
+
print(f" {key}: {value}")
|
|
587
|
+
print()
|
|
588
|
+
|
|
589
|
+
# ALWAYS use internal proxy - it handles:
|
|
590
|
+
# 1. Model override (rewrites ALL model names including subagents)
|
|
591
|
+
# 2. Langfuse metadata injection
|
|
592
|
+
# 3. Request forwarding to real LiteLLM
|
|
593
|
+
try:
|
|
594
|
+
local_proxy_url = get_proxy_base_url()
|
|
595
|
+
logger.info(
|
|
596
|
+
"local_litellm_proxy_started",
|
|
597
|
+
proxy_url=local_proxy_url,
|
|
598
|
+
real_litellm_url=litellm_api_base,
|
|
599
|
+
execution_id=context.execution_id[:8],
|
|
600
|
+
model_override_active=model_override_active,
|
|
601
|
+
model_override=model_override_value if model_override_active else None,
|
|
602
|
+
note="Internal proxy handles model override for ALL requests including subagents"
|
|
603
|
+
)
|
|
604
|
+
except Exception as proxy_error:
|
|
605
|
+
logger.error(
|
|
606
|
+
"failed_to_start_local_proxy",
|
|
607
|
+
error=str(proxy_error),
|
|
608
|
+
execution_id=context.execution_id,
|
|
609
|
+
fallback="Using direct LiteLLM connection (no metadata injection or model override)",
|
|
610
|
+
)
|
|
611
|
+
# Fallback to direct connection if proxy fails
|
|
612
|
+
local_proxy_url = litellm_api_base
|
|
613
|
+
|
|
614
|
+
# Configure Claude Code SDK to use LOCAL proxy (which forwards to real LiteLLM)
|
|
615
|
+
env["ANTHROPIC_BASE_URL"] = local_proxy_url
|
|
616
|
+
env["ANTHROPIC_API_KEY"] = litellm_api_key
|
|
617
|
+
|
|
618
|
+
# Store execution context for metadata injection
|
|
619
|
+
execution_context = {}
|
|
620
|
+
if context.user_metadata:
|
|
621
|
+
user_id = context.user_metadata.get("user_email") or context.user_metadata.get("user_id")
|
|
622
|
+
session_id = context.user_metadata.get("session_id") or context.execution_id
|
|
623
|
+
|
|
624
|
+
execution_context = {
|
|
625
|
+
"user_id": user_id,
|
|
626
|
+
"session_id": session_id,
|
|
627
|
+
"organization_id": context.organization_id,
|
|
628
|
+
"agent_id": context.agent_id,
|
|
629
|
+
"agent_name": context.user_metadata.get("agent_name") or context.agent_id,
|
|
630
|
+
"model_id": model,
|
|
631
|
+
# Pass trace_name and generation_name from user_metadata to preserve plan grouping
|
|
632
|
+
"trace_name": context.user_metadata.get("trace_name"),
|
|
633
|
+
"generation_name": context.user_metadata.get("generation_name"),
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
# Store context for proxy to use
|
|
637
|
+
set_execution_context(context.execution_id, execution_context)
|
|
638
|
+
|
|
639
|
+
logger.info(
|
|
640
|
+
"execution_context_stored_for_proxy",
|
|
641
|
+
execution_id=context.execution_id[:8],
|
|
642
|
+
has_user_id=bool(user_id),
|
|
643
|
+
has_session_id=bool(session_id),
|
|
644
|
+
metadata_keys=list(execution_context.keys()),
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
# Pass Kubiya API credentials for workflow execution
|
|
648
|
+
kubiya_api_key = os.environ.get("KUBIYA_API_KEY")
|
|
649
|
+
if kubiya_api_key:
|
|
650
|
+
env["KUBIYA_API_KEY"] = kubiya_api_key
|
|
651
|
+
logger.debug("added_kubiya_api_key_to_environment")
|
|
652
|
+
|
|
653
|
+
kubiya_api_base = os.environ.get("KUBIYA_API_BASE")
|
|
654
|
+
if kubiya_api_base:
|
|
655
|
+
env["KUBIYA_API_BASE"] = kubiya_api_base
|
|
656
|
+
logger.debug(
|
|
657
|
+
"added_kubiya_api_base_to_environment", kubiya_api_base=kubiya_api_base
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# Get session_id from previous turn for conversation continuity
|
|
661
|
+
# BUG FIX #4: Validate session_id format before use
|
|
662
|
+
previous_session_id = None
|
|
663
|
+
if context.user_metadata:
|
|
664
|
+
raw_session_id = context.user_metadata.get("claude_code_session_id")
|
|
665
|
+
previous_session_id = validate_session_id(raw_session_id)
|
|
666
|
+
|
|
667
|
+
if raw_session_id and not previous_session_id:
|
|
668
|
+
logger.warning(
|
|
669
|
+
"invalid_session_id_from_user_metadata",
|
|
670
|
+
raw_session_id=raw_session_id,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
logger.info(
|
|
674
|
+
"building_claude_code_options",
|
|
675
|
+
has_user_metadata=bool(context.user_metadata),
|
|
676
|
+
has_session_id_in_metadata=bool(previous_session_id),
|
|
677
|
+
previous_session_id_prefix=(
|
|
678
|
+
previous_session_id[:16] if previous_session_id else None
|
|
679
|
+
),
|
|
680
|
+
will_resume=bool(previous_session_id),
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# NEW: Support native subagents for team execution
|
|
684
|
+
sdk_agents = None
|
|
685
|
+
agents_config = agent_config.get('runtime_config', {}).get('agents')
|
|
686
|
+
|
|
687
|
+
if agents_config:
|
|
688
|
+
from claude_agent_sdk import AgentDefinition
|
|
689
|
+
|
|
690
|
+
sdk_agents = {}
|
|
691
|
+
for agent_id, agent_data in agents_config.items():
|
|
692
|
+
sdk_agents[agent_id] = AgentDefinition(
|
|
693
|
+
description=agent_data.get('description', ''),
|
|
694
|
+
prompt=agent_data.get('prompt', ''),
|
|
695
|
+
tools=agent_data.get('tools'),
|
|
696
|
+
model=agent_data.get('model', 'inherit'),
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
logger.info(
|
|
700
|
+
"native_subagents_configured",
|
|
701
|
+
execution_id=context.execution_id[:8] if context.execution_id else "unknown",
|
|
702
|
+
subagent_count=len(sdk_agents),
|
|
703
|
+
subagent_ids=list(sdk_agents.keys()),
|
|
704
|
+
subagent_models=[agent_data.get('model', 'inherit') for agent_data in agents_config.values()],
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
# Log detailed MCP server configuration for debugging
|
|
708
|
+
if mcp_servers:
|
|
709
|
+
logger.info(
|
|
710
|
+
"mcp_servers_being_passed_to_sdk",
|
|
711
|
+
server_count=len(mcp_servers),
|
|
712
|
+
server_names=list(mcp_servers.keys()),
|
|
713
|
+
server_configs={
|
|
714
|
+
name: {
|
|
715
|
+
"type": cfg.get("type", "stdio"),
|
|
716
|
+
"url": cfg.get("url", "N/A")[:50] if cfg.get("url") else "N/A",
|
|
717
|
+
"command": cfg.get("command", "N/A"),
|
|
718
|
+
"args": cfg.get("args", []), # Show args for debugging
|
|
719
|
+
"has_env": bool(cfg.get("env"))
|
|
720
|
+
}
|
|
721
|
+
for name, cfg in mcp_servers.items()
|
|
722
|
+
},
|
|
723
|
+
note="SDK should discover tools from these servers"
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
# Build options - SDK discovers tools, we handle permissions
|
|
727
|
+
# Enhance system prompt with runtime-specific additions
|
|
728
|
+
# Create per-execution prompt builder to support dynamic skill context and user context
|
|
729
|
+
from control_plane_api.worker.services.skill_context_enhancement import (
|
|
730
|
+
SkillContextEnhancement,
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# Create prompt builder with user context
|
|
734
|
+
prompt_builder = create_default_prompt_builder(
|
|
735
|
+
user_metadata=context.user_metadata,
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
# Add skill context enhancement if enabled and skills are configured
|
|
739
|
+
skill_context_enabled = os.getenv("ENABLE_SKILL_CONTEXT_ENHANCEMENT", "true").lower() == "true"
|
|
740
|
+
if skill_context_enabled and context.skill_configs:
|
|
741
|
+
skill_context_enhancement = SkillContextEnhancement(context.skill_configs)
|
|
742
|
+
prompt_builder.add_enhancement(skill_context_enhancement)
|
|
743
|
+
logger.info(
|
|
744
|
+
"skill_context_enhancement_enabled",
|
|
745
|
+
skill_count=len(context.skill_configs),
|
|
746
|
+
execution_id=context.execution_id[:8] if context.execution_id else "unknown",
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
enhanced_system_prompt = prompt_builder.build(
|
|
750
|
+
base_prompt=context.system_prompt,
|
|
751
|
+
runtime_type="claude_code",
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# LOG FINAL ENV VARS BEING PASSED TO CLAUDE SDK
|
|
755
|
+
print(f"\nš CLAUDE SDK OPTIONS - FINAL ENV VARS:")
|
|
756
|
+
print(f" Total env vars for SDK: {len(env)} variables")
|
|
757
|
+
print(f" Keys: {list(env.keys())}")
|
|
758
|
+
for key, value in env.items():
|
|
759
|
+
if any(s in key.upper() for s in ["TOKEN", "KEY", "SECRET", "PASSWORD"]):
|
|
760
|
+
masked = f"{value[:10]}...{value[-5:]}" if len(value) > 15 else "***"
|
|
761
|
+
print(f" {key}: {masked} (length: {len(value)})")
|
|
762
|
+
else:
|
|
763
|
+
print(f" {key}: {value}")
|
|
764
|
+
print()
|
|
765
|
+
|
|
766
|
+
# Determine working directory: user override > workspace > None (SDK default)
|
|
767
|
+
cwd_value = agent_config.get("cwd") or runtime_config.get("cwd")
|
|
768
|
+
|
|
769
|
+
if not cwd_value and context.workspace_directory:
|
|
770
|
+
cwd_value = context.workspace_directory
|
|
771
|
+
|
|
772
|
+
logger.info(
|
|
773
|
+
"claude_code_using_execution_workspace",
|
|
774
|
+
execution_id=context.execution_id[:8] if len(context.execution_id) >= 8 else context.execution_id,
|
|
775
|
+
workspace=cwd_value,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
options_dict = {
|
|
779
|
+
"system_prompt": enhanced_system_prompt,
|
|
780
|
+
"allowed_tools": allowed_tools,
|
|
781
|
+
"mcp_servers": mcp_servers, # SDK discovers tools automatically
|
|
782
|
+
"agents": sdk_agents, # NEW: Native subagent support for teams
|
|
783
|
+
"permission_mode": runtime_config.get(
|
|
784
|
+
"permission_mode",
|
|
785
|
+
os.getenv("CLAUDE_CODE_PERMISSION_MODE", "bypassPermissions")
|
|
786
|
+
),
|
|
787
|
+
"cwd": cwd_value,
|
|
788
|
+
"model": model,
|
|
789
|
+
"env": env, # ā ENVIRONMENT VARIABLES PASSED HERE!
|
|
790
|
+
"max_turns": runtime_config.get("max_turns", 50), # Default 50 turns to support complex multi-step workflows
|
|
791
|
+
"hooks": hooks,
|
|
792
|
+
"setting_sources": [], # Explicit: don't load filesystem settings
|
|
793
|
+
"include_partial_messages": True, # Enable character-by-character streaming
|
|
794
|
+
"resume": previous_session_id, # Resume previous conversation if available
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
# Extended thinking support - enables thinking/reasoning streaming
|
|
798
|
+
# Can be configured via runtime_config or environment variable
|
|
799
|
+
max_thinking_tokens = runtime_config.get(
|
|
800
|
+
"max_thinking_tokens",
|
|
801
|
+
int(os.getenv("CLAUDE_CODE_MAX_THINKING_TOKENS", "0"))
|
|
802
|
+
)
|
|
803
|
+
if max_thinking_tokens > 0:
|
|
804
|
+
options_dict["max_thinking_tokens"] = max_thinking_tokens
|
|
805
|
+
logger.info(
|
|
806
|
+
"extended_thinking_enabled",
|
|
807
|
+
max_thinking_tokens=max_thinking_tokens,
|
|
808
|
+
note="Extended thinking will stream thinking_start/thinking_delta/thinking_complete events"
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
# Add permission handler if we have MCP servers
|
|
812
|
+
# CRITICAL: SDK discovers tools but doesn't auto-permit them
|
|
813
|
+
if permission_handler:
|
|
814
|
+
options_dict["can_use_tool"] = permission_handler
|
|
815
|
+
|
|
816
|
+
options = ClaudeAgentOptions(**options_dict)
|
|
817
|
+
|
|
818
|
+
logger.info(
|
|
819
|
+
"claude_code_options_configured",
|
|
820
|
+
include_partial_messages=getattr(options, "include_partial_messages", "NOT SET"),
|
|
821
|
+
permission_mode=options.permission_mode,
|
|
822
|
+
model=options.model,
|
|
823
|
+
mcp_servers_configured=len(mcp_servers) if mcp_servers else 0,
|
|
824
|
+
has_can_use_tool=permission_handler is not None,
|
|
825
|
+
note="SDK discovers tools, canUseTool handler permits mcp__<server>__* pattern"
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
# Return options, active_tools dict, started_tools set, and completed_tools set for tracking
|
|
829
|
+
return options, active_tools, started_tools, completed_tools
|