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,665 @@
|
|
|
1
|
+
"""Streaming utilities for agent and team execution"""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Callable, Optional, List
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
logger = structlog.get_logger()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StreamingHelper:
|
|
10
|
+
"""
|
|
11
|
+
Helper for handling streaming from Agno Agent/Team executions.
|
|
12
|
+
|
|
13
|
+
Provides utilities for:
|
|
14
|
+
- Publishing events to Control Plane
|
|
15
|
+
- Tracking run_id from streaming chunks
|
|
16
|
+
- Collecting response content
|
|
17
|
+
- Publishing tool execution events
|
|
18
|
+
- Handling member message streaming
|
|
19
|
+
- Tracking tool IDs for proper start/complete matching
|
|
20
|
+
- Splitting assistant messages into pre-tool and post-tool phases
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, control_plane_client, execution_id: str):
|
|
24
|
+
self.control_plane = control_plane_client
|
|
25
|
+
self.execution_id = execution_id
|
|
26
|
+
self.run_id_published = False
|
|
27
|
+
self.response_content = []
|
|
28
|
+
self.member_message_ids = {} # Track message_id per member
|
|
29
|
+
self.active_streaming_member = None # Track which member is streaming
|
|
30
|
+
self.tool_execution_ids = {} # Track tool IDs for matching start/complete events
|
|
31
|
+
self.tool_messages = [] # Track tool messages for session persistence
|
|
32
|
+
|
|
33
|
+
# NEW: Track message phases for proper assistant message splitting
|
|
34
|
+
self.pre_tool_content = [] # Content before first tool use
|
|
35
|
+
self.post_tool_content = [] # Content after tools complete
|
|
36
|
+
self.tool_phase = "pre" # Current phase: "pre", "during", or "post"
|
|
37
|
+
self.first_tool_timestamp = None # Timestamp when first tool started
|
|
38
|
+
self.tools_complete_timestamp = None # Timestamp when all tools completed
|
|
39
|
+
self.has_any_tools = False # Track if any tools were executed
|
|
40
|
+
|
|
41
|
+
def handle_run_id(self, chunk: Any, on_run_id: Optional[Callable[[str], None]] = None) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Capture and publish run_id from first streaming chunk.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
chunk: Streaming chunk from Agno
|
|
47
|
+
on_run_id: Optional callback when run_id is captured
|
|
48
|
+
"""
|
|
49
|
+
if not self.run_id_published and hasattr(chunk, 'run_id') and chunk.run_id:
|
|
50
|
+
run_id = chunk.run_id
|
|
51
|
+
|
|
52
|
+
logger.info("run_id_captured", execution_id=self.execution_id[:8], run_id=run_id[:16])
|
|
53
|
+
|
|
54
|
+
# Publish to Control Plane for UI
|
|
55
|
+
self.control_plane.publish_event(
|
|
56
|
+
execution_id=self.execution_id,
|
|
57
|
+
event_type="run_started",
|
|
58
|
+
data={
|
|
59
|
+
"run_id": run_id,
|
|
60
|
+
"execution_id": self.execution_id,
|
|
61
|
+
"cancellable": True,
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.run_id_published = True
|
|
66
|
+
|
|
67
|
+
# Call callback if provided (for cancellation manager)
|
|
68
|
+
if on_run_id:
|
|
69
|
+
on_run_id(run_id)
|
|
70
|
+
|
|
71
|
+
async def handle_content_chunk(
|
|
72
|
+
self,
|
|
73
|
+
chunk: Any,
|
|
74
|
+
message_id: str,
|
|
75
|
+
print_to_console: bool = True
|
|
76
|
+
) -> Optional[str]:
|
|
77
|
+
"""
|
|
78
|
+
Handle content chunk from streaming response.
|
|
79
|
+
|
|
80
|
+
Tracks content in different phases (pre-tool, during-tool, post-tool)
|
|
81
|
+
to enable proper message splitting around tool usage.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
chunk: Streaming chunk
|
|
85
|
+
message_id: Unique message ID for this turn
|
|
86
|
+
print_to_console: Whether to print to stdout
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Content string if present, None otherwise
|
|
90
|
+
"""
|
|
91
|
+
# Check for both 'response' (RuntimeExecutionResult) and 'content' (legacy/Agno)
|
|
92
|
+
content = None
|
|
93
|
+
|
|
94
|
+
if hasattr(chunk, 'response') and chunk.response:
|
|
95
|
+
content = str(chunk.response)
|
|
96
|
+
elif hasattr(chunk, 'content') and chunk.content:
|
|
97
|
+
content = str(chunk.content)
|
|
98
|
+
|
|
99
|
+
if content:
|
|
100
|
+
# Track content in appropriate phase
|
|
101
|
+
if self.tool_phase == "pre":
|
|
102
|
+
self.pre_tool_content.append(content)
|
|
103
|
+
elif self.tool_phase == "post":
|
|
104
|
+
self.post_tool_content.append(content)
|
|
105
|
+
# Note: During "during" phase, we don't collect assistant content
|
|
106
|
+
# as tools are executing
|
|
107
|
+
|
|
108
|
+
self.response_content.append(content)
|
|
109
|
+
|
|
110
|
+
if print_to_console:
|
|
111
|
+
print(content, end='', flush=True)
|
|
112
|
+
|
|
113
|
+
# Stream to Control Plane for real-time UI updates
|
|
114
|
+
# Use async version since we're in an async context
|
|
115
|
+
try:
|
|
116
|
+
await self.control_plane.publish_event_async(
|
|
117
|
+
execution_id=self.execution_id,
|
|
118
|
+
event_type="message_chunk",
|
|
119
|
+
data={
|
|
120
|
+
"role": "assistant",
|
|
121
|
+
"content": content,
|
|
122
|
+
"is_chunk": True,
|
|
123
|
+
"message_id": message_id,
|
|
124
|
+
"phase": self.tool_phase, # NEW: Include current phase
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
except Exception as publish_err:
|
|
128
|
+
# Log but don't fail if event publishing fails
|
|
129
|
+
logger.warning(
|
|
130
|
+
"async_event_publish_failed",
|
|
131
|
+
execution_id=self.execution_id[:8],
|
|
132
|
+
error=str(publish_err)[:200],
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return content
|
|
136
|
+
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
def publish_content_chunk(self, content: str, message_id: str) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Publish content chunk event (sync wrapper for streaming events).
|
|
142
|
+
|
|
143
|
+
This method is called from sync event callbacks in the runtime streaming path.
|
|
144
|
+
It tracks content in the appropriate phase and publishes to Control Plane.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
content: Content string to publish
|
|
148
|
+
message_id: Unique message ID for this turn
|
|
149
|
+
"""
|
|
150
|
+
# Track content
|
|
151
|
+
self.response_content.append(content)
|
|
152
|
+
|
|
153
|
+
# Track in appropriate phase for message splitting
|
|
154
|
+
if self.tool_phase == "pre":
|
|
155
|
+
self.pre_tool_content.append(content)
|
|
156
|
+
elif self.tool_phase == "post":
|
|
157
|
+
self.post_tool_content.append(content)
|
|
158
|
+
# Note: During "during" phase, we don't collect assistant content
|
|
159
|
+
# as tools are executing
|
|
160
|
+
|
|
161
|
+
# Publish to Control Plane (use sync publish_event, not async)
|
|
162
|
+
# Note: This is called from a sync callback, so we can't use await
|
|
163
|
+
try:
|
|
164
|
+
self.control_plane.publish_event(
|
|
165
|
+
execution_id=self.execution_id,
|
|
166
|
+
event_type="message_chunk",
|
|
167
|
+
data={
|
|
168
|
+
"role": "assistant",
|
|
169
|
+
"content": content,
|
|
170
|
+
"is_chunk": True,
|
|
171
|
+
"message_id": message_id,
|
|
172
|
+
"phase": self.tool_phase, # Include current phase
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
except Exception as publish_err:
|
|
176
|
+
# Log but don't fail if event publishing fails
|
|
177
|
+
logger.warning(
|
|
178
|
+
"sync_event_publish_failed",
|
|
179
|
+
execution_id=self.execution_id[:8],
|
|
180
|
+
error=str(publish_err)[:200],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def get_full_response(self) -> str:
|
|
184
|
+
"""Get the complete response accumulated from all chunks."""
|
|
185
|
+
return ''.join(self.response_content)
|
|
186
|
+
|
|
187
|
+
def handle_member_content_chunk(
|
|
188
|
+
self,
|
|
189
|
+
member_name: str,
|
|
190
|
+
content: str,
|
|
191
|
+
print_to_console: bool = True
|
|
192
|
+
) -> str:
|
|
193
|
+
"""
|
|
194
|
+
Handle content chunk from a team member.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
member_name: Name of the team member
|
|
198
|
+
content: Content string
|
|
199
|
+
print_to_console: Whether to print to stdout
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
The member's message_id
|
|
203
|
+
"""
|
|
204
|
+
import time
|
|
205
|
+
|
|
206
|
+
# Generate unique message ID for this member if not exists
|
|
207
|
+
if member_name not in self.member_message_ids:
|
|
208
|
+
self.member_message_ids[member_name] = f"{self.execution_id}_{member_name}_{int(time.time() * 1000000)}"
|
|
209
|
+
|
|
210
|
+
# Print member name header once when they start
|
|
211
|
+
if print_to_console:
|
|
212
|
+
print(f"\n[{member_name}] ", end='', flush=True)
|
|
213
|
+
|
|
214
|
+
# If switching to a different member, mark the previous one as complete
|
|
215
|
+
if self.active_streaming_member and self.active_streaming_member != member_name:
|
|
216
|
+
self.publish_member_complete(self.active_streaming_member)
|
|
217
|
+
|
|
218
|
+
# Track that this member is now actively streaming
|
|
219
|
+
self.active_streaming_member = member_name
|
|
220
|
+
|
|
221
|
+
# Print content without repeated member name prefix
|
|
222
|
+
if print_to_console:
|
|
223
|
+
print(content, end='', flush=True)
|
|
224
|
+
|
|
225
|
+
# Stream member chunk to Control Plane
|
|
226
|
+
message_id = self.member_message_ids[member_name]
|
|
227
|
+
self.control_plane.publish_event(
|
|
228
|
+
execution_id=self.execution_id,
|
|
229
|
+
event_type="member_message_chunk",
|
|
230
|
+
data={
|
|
231
|
+
"role": "assistant",
|
|
232
|
+
"content": content,
|
|
233
|
+
"is_chunk": True,
|
|
234
|
+
"message_id": message_id,
|
|
235
|
+
"source": "team_member",
|
|
236
|
+
"member_name": member_name,
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return message_id
|
|
241
|
+
|
|
242
|
+
def publish_member_complete(self, member_name: str) -> None:
|
|
243
|
+
"""
|
|
244
|
+
Publish member_message_complete event and clear the message_id.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
member_name: Name of the member to mark as complete
|
|
248
|
+
"""
|
|
249
|
+
if member_name in self.member_message_ids:
|
|
250
|
+
self.control_plane.publish_event(
|
|
251
|
+
execution_id=self.execution_id,
|
|
252
|
+
event_type="member_message_complete",
|
|
253
|
+
data={
|
|
254
|
+
"message_id": self.member_message_ids[member_name],
|
|
255
|
+
"member_name": member_name,
|
|
256
|
+
"source": "team_member",
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# CRITICAL: Clear the message_id for this member after completing
|
|
261
|
+
# This ensures a NEW message_id is generated for their next turn
|
|
262
|
+
# Without this, all turns from the same member would edit the same message!
|
|
263
|
+
del self.member_message_ids[member_name]
|
|
264
|
+
logger.info("member_message_id_cleared", member_name=member_name, execution_id=self.execution_id[:8])
|
|
265
|
+
|
|
266
|
+
def finalize_streaming(self) -> None:
|
|
267
|
+
"""
|
|
268
|
+
Finalize streaming by marking any active member as complete.
|
|
269
|
+
Call this when streaming ends.
|
|
270
|
+
"""
|
|
271
|
+
if self.active_streaming_member:
|
|
272
|
+
self.publish_member_complete(self.active_streaming_member)
|
|
273
|
+
self.active_streaming_member = None
|
|
274
|
+
|
|
275
|
+
def get_tool_messages(self) -> List[Dict[str, Any]]:
|
|
276
|
+
"""
|
|
277
|
+
Get all tool messages collected during streaming for session persistence.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
List of tool message dicts with role='system', tool metadata, and timestamps
|
|
281
|
+
"""
|
|
282
|
+
return self.tool_messages
|
|
283
|
+
|
|
284
|
+
def publish_tool_start(
|
|
285
|
+
self,
|
|
286
|
+
tool_name: str,
|
|
287
|
+
tool_execution_id: str,
|
|
288
|
+
tool_args: Optional[Dict[str, Any]] = None,
|
|
289
|
+
source: str = "agent",
|
|
290
|
+
member_name: Optional[str] = None
|
|
291
|
+
) -> str:
|
|
292
|
+
"""
|
|
293
|
+
Publish tool execution start event.
|
|
294
|
+
|
|
295
|
+
Also transitions message phase from "pre" to "during" on first tool use.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
tool_name: Name of the tool
|
|
299
|
+
tool_execution_id: Unique ID for this tool execution
|
|
300
|
+
tool_args: Tool arguments
|
|
301
|
+
source: "agent" or "team_member" or "team_leader" or "team"
|
|
302
|
+
member_name: Name of member (if tool is from a member)
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
message_id for this tool execution
|
|
306
|
+
"""
|
|
307
|
+
import time
|
|
308
|
+
from datetime import datetime, timezone
|
|
309
|
+
|
|
310
|
+
# Mark transition to "during" phase on first tool
|
|
311
|
+
if self.tool_phase == "pre":
|
|
312
|
+
self.tool_phase = "during"
|
|
313
|
+
self.first_tool_timestamp = datetime.now(timezone.utc).isoformat()
|
|
314
|
+
self.has_any_tools = True
|
|
315
|
+
logger.info(
|
|
316
|
+
"phase_transition_to_during",
|
|
317
|
+
execution_id=self.execution_id[:8],
|
|
318
|
+
tool_name=tool_name,
|
|
319
|
+
pre_tool_content_length=len(''.join(self.pre_tool_content))
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
message_id = f"{self.execution_id}_tool_{tool_execution_id}"
|
|
323
|
+
is_member_tool = member_name is not None
|
|
324
|
+
parent_message_id = self.member_message_ids.get(member_name) if is_member_tool else None
|
|
325
|
+
|
|
326
|
+
# Store tool info for matching with completion event
|
|
327
|
+
tool_key = f"{member_name or 'leader'}_{tool_name}_{int(time.time())}"
|
|
328
|
+
self.tool_execution_ids[tool_key] = {
|
|
329
|
+
"tool_execution_id": tool_execution_id,
|
|
330
|
+
"message_id": message_id,
|
|
331
|
+
"tool_name": tool_name,
|
|
332
|
+
"member_name": member_name,
|
|
333
|
+
"parent_message_id": parent_message_id,
|
|
334
|
+
"tool_args": tool_args, # Store args for persistence
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
event_type = "member_tool_started" if is_member_tool else "tool_started"
|
|
338
|
+
|
|
339
|
+
self.control_plane.publish_event(
|
|
340
|
+
execution_id=self.execution_id,
|
|
341
|
+
event_type=event_type,
|
|
342
|
+
data={
|
|
343
|
+
"tool_name": tool_name,
|
|
344
|
+
"tool_execution_id": tool_execution_id,
|
|
345
|
+
"message_id": message_id,
|
|
346
|
+
"tool_arguments": tool_args,
|
|
347
|
+
"source": "team_member" if is_member_tool else "team_leader",
|
|
348
|
+
"member_name": member_name,
|
|
349
|
+
"parent_message_id": parent_message_id,
|
|
350
|
+
"message": f"🔧 Executing tool: {tool_name}",
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return message_id
|
|
355
|
+
|
|
356
|
+
def publish_tool_complete(
|
|
357
|
+
self,
|
|
358
|
+
tool_name: str,
|
|
359
|
+
tool_execution_id: str,
|
|
360
|
+
status: str = "success",
|
|
361
|
+
output: Optional[str] = None,
|
|
362
|
+
error: Optional[str] = None,
|
|
363
|
+
source: str = "agent",
|
|
364
|
+
member_name: Optional[str] = None
|
|
365
|
+
) -> None:
|
|
366
|
+
"""
|
|
367
|
+
Publish tool execution completion event.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
tool_name: Name of the tool
|
|
371
|
+
tool_execution_id: Unique ID for this tool execution
|
|
372
|
+
status: "success" or "failed"
|
|
373
|
+
output: Tool output (if successful)
|
|
374
|
+
error: Error message (if failed)
|
|
375
|
+
source: "agent" or "team_member" or "team_leader" or "team"
|
|
376
|
+
member_name: Name of member (if tool is from a member)
|
|
377
|
+
"""
|
|
378
|
+
import time
|
|
379
|
+
|
|
380
|
+
# Find the stored tool info from the start event
|
|
381
|
+
tool_key_pattern = f"{member_name or 'leader'}_{tool_name}"
|
|
382
|
+
matching_tool = None
|
|
383
|
+
for key, tool_info in list(self.tool_execution_ids.items()):
|
|
384
|
+
if key.startswith(tool_key_pattern):
|
|
385
|
+
matching_tool = tool_info
|
|
386
|
+
# Remove from tracking dict
|
|
387
|
+
del self.tool_execution_ids[key]
|
|
388
|
+
break
|
|
389
|
+
|
|
390
|
+
if matching_tool:
|
|
391
|
+
message_id = matching_tool["message_id"]
|
|
392
|
+
parent_message_id = matching_tool["parent_message_id"]
|
|
393
|
+
# Use the stored tool_execution_id from the start event
|
|
394
|
+
tool_execution_id = matching_tool["tool_execution_id"]
|
|
395
|
+
tool_args = matching_tool.get("tool_args") # Get stored args
|
|
396
|
+
else:
|
|
397
|
+
# Fallback if start event wasn't captured
|
|
398
|
+
message_id = f"{self.execution_id}_tool_{tool_execution_id}"
|
|
399
|
+
parent_message_id = self.member_message_ids.get(member_name) if member_name else None
|
|
400
|
+
tool_args = None
|
|
401
|
+
logger.warning("tool_completion_without_start", tool_name=tool_name, member_name=member_name)
|
|
402
|
+
|
|
403
|
+
is_member_tool = member_name is not None
|
|
404
|
+
event_type = "member_tool_completed" if is_member_tool else "tool_completed"
|
|
405
|
+
|
|
406
|
+
tool_data = {
|
|
407
|
+
"tool_name": tool_name,
|
|
408
|
+
"tool_execution_id": tool_execution_id, # Now uses the stored ID from start event
|
|
409
|
+
"message_id": message_id,
|
|
410
|
+
"status": status,
|
|
411
|
+
"tool_output": output[:50000] if output else None, # Increased from 1000 to 50000 to preserve Metabase URLs and other important data
|
|
412
|
+
"tool_error": error,
|
|
413
|
+
"source": "team_member" if is_member_tool else "team_leader",
|
|
414
|
+
"member_name": member_name,
|
|
415
|
+
"parent_message_id": parent_message_id,
|
|
416
|
+
"message": f"{'✅' if status == 'success' else '❌'} Tool {status}: {tool_name}",
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
self.control_plane.publish_event(
|
|
420
|
+
execution_id=self.execution_id,
|
|
421
|
+
event_type=event_type,
|
|
422
|
+
data=tool_data
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Store tool message for session persistence
|
|
426
|
+
# NEW: Tool messages are now role="user" with tool_result content blocks
|
|
427
|
+
# This aligns with Claude API best practices where tool results come from user
|
|
428
|
+
from datetime import datetime, timezone
|
|
429
|
+
|
|
430
|
+
tool_result_content = output[:50000] if output and status == "success" else (error or "")
|
|
431
|
+
|
|
432
|
+
self.tool_messages.append({
|
|
433
|
+
"role": "user", # CHANGED: Tool results are user role (Claude API format)
|
|
434
|
+
"content": [
|
|
435
|
+
{
|
|
436
|
+
"type": "tool_result",
|
|
437
|
+
"tool_use_id": tool_execution_id,
|
|
438
|
+
"content": tool_result_content,
|
|
439
|
+
"is_error": status != "success",
|
|
440
|
+
}
|
|
441
|
+
],
|
|
442
|
+
"message_type": "tool_result", # NEW: Mark as tool result message
|
|
443
|
+
"tool_name": tool_name,
|
|
444
|
+
"tool_execution_id": tool_execution_id,
|
|
445
|
+
"tool_input": tool_args, # Frontend expects "tool_input" not "tool_arguments"
|
|
446
|
+
"tool_output": output[:50000] if output else None,
|
|
447
|
+
"tool_error": error,
|
|
448
|
+
"tool_status": status, # Frontend expects "tool_status" not "status"
|
|
449
|
+
"member_name": member_name,
|
|
450
|
+
"message_id": message_id,
|
|
451
|
+
"parent_message_id": parent_message_id,
|
|
452
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
logger.info(
|
|
456
|
+
"tool_result_message_created",
|
|
457
|
+
execution_id=self.execution_id[:8],
|
|
458
|
+
tool_name=tool_name,
|
|
459
|
+
status=status,
|
|
460
|
+
has_output=bool(output),
|
|
461
|
+
has_error=bool(error)
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
def transition_to_post_tool_phase(self):
|
|
465
|
+
"""
|
|
466
|
+
Transition from "during" phase to "post" phase.
|
|
467
|
+
|
|
468
|
+
Call this after all tools have completed to start collecting
|
|
469
|
+
post-tool assistant content.
|
|
470
|
+
"""
|
|
471
|
+
if self.tool_phase == "during":
|
|
472
|
+
from datetime import datetime, timezone
|
|
473
|
+
self.tool_phase = "post"
|
|
474
|
+
self.tools_complete_timestamp = datetime.now(timezone.utc).isoformat()
|
|
475
|
+
logger.info(
|
|
476
|
+
"phase_transition_to_post",
|
|
477
|
+
execution_id=self.execution_id[:8],
|
|
478
|
+
tools_completed=len(self.tool_messages)
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def finalize_streaming(self) -> None:
|
|
482
|
+
"""
|
|
483
|
+
Finalize streaming by marking any active member as complete
|
|
484
|
+
and transitioning to post-tool phase if needed.
|
|
485
|
+
|
|
486
|
+
Call this when streaming ends.
|
|
487
|
+
"""
|
|
488
|
+
# Transition to post phase if we had tools
|
|
489
|
+
if self.has_any_tools and self.tool_phase == "during":
|
|
490
|
+
self.transition_to_post_tool_phase()
|
|
491
|
+
|
|
492
|
+
# Handle team member completion
|
|
493
|
+
if self.active_streaming_member:
|
|
494
|
+
self.publish_member_complete(self.active_streaming_member)
|
|
495
|
+
self.active_streaming_member = None
|
|
496
|
+
|
|
497
|
+
def get_assistant_message_parts(self) -> List[Dict[str, Any]]:
|
|
498
|
+
"""
|
|
499
|
+
Get assistant messages split into pre-tool and post-tool parts.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
List of assistant message dicts. May contain:
|
|
503
|
+
- Pre-tool message (if content exists before first tool)
|
|
504
|
+
- Post-tool message (if content exists after tools complete)
|
|
505
|
+
- Single message (if no tools were used)
|
|
506
|
+
"""
|
|
507
|
+
from datetime import datetime, timezone
|
|
508
|
+
|
|
509
|
+
assistant_parts = []
|
|
510
|
+
|
|
511
|
+
# If no tools were used, return single message with all content
|
|
512
|
+
if not self.has_any_tools:
|
|
513
|
+
full_content = ''.join(self.response_content)
|
|
514
|
+
if full_content:
|
|
515
|
+
assistant_parts.append({
|
|
516
|
+
"content": full_content,
|
|
517
|
+
"phase": "complete",
|
|
518
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
519
|
+
})
|
|
520
|
+
return assistant_parts
|
|
521
|
+
|
|
522
|
+
# If tools were used, split into pre and post parts
|
|
523
|
+
pre_content = ''.join(self.pre_tool_content)
|
|
524
|
+
if pre_content:
|
|
525
|
+
assistant_parts.append({
|
|
526
|
+
"content": pre_content,
|
|
527
|
+
"phase": "pre",
|
|
528
|
+
"timestamp": self.first_tool_timestamp or datetime.now(timezone.utc).isoformat(),
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
post_content = ''.join(self.post_tool_content)
|
|
532
|
+
if post_content:
|
|
533
|
+
assistant_parts.append({
|
|
534
|
+
"content": post_content,
|
|
535
|
+
"phase": "post",
|
|
536
|
+
"timestamp": self.tools_complete_timestamp or datetime.now(timezone.utc).isoformat(),
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
return assistant_parts
|
|
540
|
+
|
|
541
|
+
def build_structured_messages(
|
|
542
|
+
self,
|
|
543
|
+
execution_id: str,
|
|
544
|
+
turn_number: int,
|
|
545
|
+
user_message: Dict[str, Any],
|
|
546
|
+
) -> List[Dict[str, Any]]:
|
|
547
|
+
"""
|
|
548
|
+
Build structured message list with proper interleaving.
|
|
549
|
+
|
|
550
|
+
Creates proper message flow:
|
|
551
|
+
1. User message
|
|
552
|
+
2. Assistant message (pre-tool) if tools were used
|
|
553
|
+
3. Tool result messages (role="user")
|
|
554
|
+
4. Assistant message (post-tool) if tools were used
|
|
555
|
+
OR
|
|
556
|
+
1. User message
|
|
557
|
+
2. Assistant message (complete) if no tools
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
execution_id: Execution ID
|
|
561
|
+
turn_number: Turn number in conversation
|
|
562
|
+
user_message: User message dict (already constructed)
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
List of messages in proper order with message_ids assigned
|
|
566
|
+
"""
|
|
567
|
+
messages = [user_message]
|
|
568
|
+
|
|
569
|
+
assistant_parts = self.get_assistant_message_parts()
|
|
570
|
+
|
|
571
|
+
if not self.has_any_tools:
|
|
572
|
+
# Simple case: no tools, single assistant message
|
|
573
|
+
if assistant_parts:
|
|
574
|
+
part = assistant_parts[0]
|
|
575
|
+
messages.append({
|
|
576
|
+
"role": "assistant",
|
|
577
|
+
"content": part["content"],
|
|
578
|
+
"timestamp": part["timestamp"],
|
|
579
|
+
"message_id": f"{execution_id}_assistant_{turn_number}",
|
|
580
|
+
})
|
|
581
|
+
else:
|
|
582
|
+
# Complex case: tools were used, split assistant messages
|
|
583
|
+
for i, part in enumerate(assistant_parts):
|
|
584
|
+
phase_suffix = f"_{part['phase']}" if part['phase'] in ['pre', 'post'] else ""
|
|
585
|
+
messages.append({
|
|
586
|
+
"role": "assistant",
|
|
587
|
+
"content": part["content"],
|
|
588
|
+
"timestamp": part["timestamp"],
|
|
589
|
+
"message_id": f"{execution_id}_assistant_{turn_number}{phase_suffix}",
|
|
590
|
+
"phase": part["phase"],
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
# After pre-tool message, insert tool result messages
|
|
594
|
+
if part["phase"] == "pre":
|
|
595
|
+
messages.extend(self.tool_messages)
|
|
596
|
+
|
|
597
|
+
# If we only have pre-tool content (no post), still add tool messages at end
|
|
598
|
+
if assistant_parts and assistant_parts[-1]["phase"] == "pre":
|
|
599
|
+
messages.extend(self.tool_messages)
|
|
600
|
+
# If we have no assistant parts but have tools, add tool messages
|
|
601
|
+
elif not assistant_parts and self.tool_messages:
|
|
602
|
+
messages.extend(self.tool_messages)
|
|
603
|
+
|
|
604
|
+
logger.info(
|
|
605
|
+
"structured_messages_built",
|
|
606
|
+
execution_id=execution_id[:8],
|
|
607
|
+
turn_number=turn_number,
|
|
608
|
+
total_messages=len(messages),
|
|
609
|
+
has_tools=self.has_any_tools,
|
|
610
|
+
assistant_parts=len(assistant_parts),
|
|
611
|
+
tool_messages=len(self.tool_messages)
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
return messages
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def create_tool_hook(control_plane_client, execution_id: str):
|
|
618
|
+
"""
|
|
619
|
+
Create a tool hook function for Agno Agent/Team.
|
|
620
|
+
|
|
621
|
+
This hook is called before and after each tool execution
|
|
622
|
+
to publish real-time updates to the Control Plane.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
control_plane_client: Control Plane client instance
|
|
626
|
+
execution_id: Execution ID
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
Hook function compatible with Agno tool_hooks
|
|
630
|
+
"""
|
|
631
|
+
import time
|
|
632
|
+
|
|
633
|
+
def tool_hook(tool_name: str, tool_args: dict, result: Any = None, error: Exception = None):
|
|
634
|
+
"""Tool hook for real-time updates"""
|
|
635
|
+
tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
|
|
636
|
+
|
|
637
|
+
if error is None and result is None:
|
|
638
|
+
# Tool starting
|
|
639
|
+
control_plane_client.publish_event(
|
|
640
|
+
execution_id=execution_id,
|
|
641
|
+
event_type="tool_started",
|
|
642
|
+
data={
|
|
643
|
+
"tool_name": tool_name,
|
|
644
|
+
"tool_execution_id": tool_execution_id,
|
|
645
|
+
"tool_arguments": tool_args,
|
|
646
|
+
"message": f"🔧 Starting: {tool_name}",
|
|
647
|
+
}
|
|
648
|
+
)
|
|
649
|
+
else:
|
|
650
|
+
# Tool completed
|
|
651
|
+
status = "failed" if error else "success"
|
|
652
|
+
control_plane_client.publish_event(
|
|
653
|
+
execution_id=execution_id,
|
|
654
|
+
event_type="tool_completed",
|
|
655
|
+
data={
|
|
656
|
+
"tool_name": tool_name,
|
|
657
|
+
"tool_execution_id": tool_execution_id,
|
|
658
|
+
"status": status,
|
|
659
|
+
"tool_output": str(result)[:1000] if result else None,
|
|
660
|
+
"tool_error": str(error) if error else None,
|
|
661
|
+
"message": f"{'✅' if status == 'success' else '❌'} {status}: {tool_name}",
|
|
662
|
+
}
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
return tool_hook
|