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,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Storage provider factory for creating and managing storage provider instances.
|
|
3
|
+
|
|
4
|
+
Supports multiple providers with environment-based configuration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import os
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
from .base_provider import StorageProvider
|
|
12
|
+
from .vercel_blob_provider import VercelBlobStorageProvider
|
|
13
|
+
|
|
14
|
+
logger = structlog.get_logger()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StorageProviderFactory:
|
|
18
|
+
"""Factory for creating storage provider instances based on configuration."""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def create_provider(
|
|
22
|
+
provider_type: Optional[str] = None,
|
|
23
|
+
config: Optional[dict] = None
|
|
24
|
+
) -> StorageProvider:
|
|
25
|
+
"""
|
|
26
|
+
Create a storage provider instance.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
provider_type: Provider name ('vercel_blob', 's3', 'azure_blob', 'gcs')
|
|
30
|
+
If None, uses STORAGE_PROVIDER environment variable
|
|
31
|
+
config: Provider-specific configuration dict (optional)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
StorageProvider instance
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If provider is not configured, invalid, or missing required config
|
|
38
|
+
"""
|
|
39
|
+
# Get provider from parameter or environment
|
|
40
|
+
provider_type = provider_type or os.environ.get("STORAGE_PROVIDER")
|
|
41
|
+
|
|
42
|
+
if not provider_type:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"No storage provider configured. "
|
|
45
|
+
"Set STORAGE_PROVIDER environment variable to one of: "
|
|
46
|
+
"vercel_blob, s3, azure_blob, gcs"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
provider_type = provider_type.lower().strip()
|
|
50
|
+
|
|
51
|
+
logger.info("storage_provider_factory_creating", provider_type=provider_type)
|
|
52
|
+
|
|
53
|
+
# Vercel Blob Storage
|
|
54
|
+
if provider_type == "vercel_blob":
|
|
55
|
+
token = os.environ.get("VERCEL_BLOB_TOKEN")
|
|
56
|
+
if not token:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"VERCEL_BLOB_TOKEN environment variable is required for Vercel Blob provider"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
store_name = os.environ.get("VERCEL_BLOB_STORE_NAME")
|
|
62
|
+
|
|
63
|
+
logger.info(
|
|
64
|
+
"creating_vercel_blob_provider",
|
|
65
|
+
store_name=store_name if store_name else "default"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return VercelBlobStorageProvider(token=token, store_name=store_name)
|
|
69
|
+
|
|
70
|
+
# AWS S3 (future implementation)
|
|
71
|
+
elif provider_type == "s3":
|
|
72
|
+
raise NotImplementedError(
|
|
73
|
+
"S3 provider not yet implemented. "
|
|
74
|
+
"To add S3 support, implement S3StorageProvider class."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Azure Blob Storage (future implementation)
|
|
78
|
+
elif provider_type == "azure_blob":
|
|
79
|
+
raise NotImplementedError(
|
|
80
|
+
"Azure Blob provider not yet implemented. "
|
|
81
|
+
"To add Azure Blob support, implement AzureBlobStorageProvider class."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Google Cloud Storage (future implementation)
|
|
85
|
+
elif provider_type == "gcs":
|
|
86
|
+
raise NotImplementedError(
|
|
87
|
+
"GCS provider not yet implemented. "
|
|
88
|
+
"To add GCS support, implement GCSStorageProvider class."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Unknown provider
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Unknown storage provider: {provider_type}. "
|
|
95
|
+
f"Supported providers: vercel_blob, s3, azure_blob, gcs"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Singleton instance
|
|
100
|
+
_provider_instance: Optional[StorageProvider] = None
|
|
101
|
+
_provider_lock = False # Simple lock for singleton pattern
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_storage_provider(force_recreate: bool = False) -> StorageProvider:
|
|
105
|
+
"""
|
|
106
|
+
Get or create storage provider singleton.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
force_recreate: If True, recreates the provider instance
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
StorageProvider instance
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: If provider is not configured
|
|
116
|
+
|
|
117
|
+
Note:
|
|
118
|
+
This is a singleton pattern to reuse HTTP clients and connections.
|
|
119
|
+
The provider is created once per application lifecycle.
|
|
120
|
+
"""
|
|
121
|
+
global _provider_instance, _provider_lock
|
|
122
|
+
|
|
123
|
+
if force_recreate:
|
|
124
|
+
_provider_instance = None
|
|
125
|
+
|
|
126
|
+
if _provider_instance is None:
|
|
127
|
+
if _provider_lock:
|
|
128
|
+
# Simple wait if another thread is creating
|
|
129
|
+
import time
|
|
130
|
+
timeout = 5 # 5 second timeout
|
|
131
|
+
start = time.time()
|
|
132
|
+
while _provider_lock and (time.time() - start) < timeout:
|
|
133
|
+
time.sleep(0.1)
|
|
134
|
+
|
|
135
|
+
if _provider_instance is None:
|
|
136
|
+
_provider_lock = True
|
|
137
|
+
try:
|
|
138
|
+
_provider_instance = StorageProviderFactory.create_provider()
|
|
139
|
+
logger.info(
|
|
140
|
+
"storage_provider_singleton_created",
|
|
141
|
+
provider=_provider_instance.get_provider_name()
|
|
142
|
+
)
|
|
143
|
+
finally:
|
|
144
|
+
_provider_lock = False
|
|
145
|
+
|
|
146
|
+
return _provider_instance
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def reset_storage_provider():
|
|
150
|
+
"""
|
|
151
|
+
Reset the singleton instance.
|
|
152
|
+
|
|
153
|
+
Useful for testing or when configuration changes.
|
|
154
|
+
"""
|
|
155
|
+
global _provider_instance
|
|
156
|
+
_provider_instance = None
|
|
157
|
+
logger.info("storage_provider_singleton_reset")
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vercel Blob Storage provider implementation.
|
|
3
|
+
|
|
4
|
+
Documentation: https://vercel.com/docs/storage/vercel-blob
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import hashlib
|
|
9
|
+
from typing import BinaryIO, Dict, List, Optional, Tuple
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from io import BytesIO
|
|
12
|
+
import structlog
|
|
13
|
+
|
|
14
|
+
from .base_provider import StorageProvider, StorageFile, UploadResult
|
|
15
|
+
|
|
16
|
+
logger = structlog.get_logger()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VercelBlobStorageProvider(StorageProvider):
|
|
20
|
+
"""
|
|
21
|
+
Vercel Blob Storage implementation.
|
|
22
|
+
|
|
23
|
+
Uses Vercel's Blob Storage API for file storage with organization-level isolation.
|
|
24
|
+
Files are stored with organization prefix for multi-tenancy.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, token: str, store_name: Optional[str] = None):
|
|
28
|
+
"""
|
|
29
|
+
Initialize Vercel Blob provider.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
token: VERCEL_BLOB_TOKEN from environment
|
|
33
|
+
store_name: Optional store name (defaults to main store)
|
|
34
|
+
"""
|
|
35
|
+
self.token = token
|
|
36
|
+
self.store_name = store_name
|
|
37
|
+
self.base_url = "https://blob.vercel-storage.com"
|
|
38
|
+
self.client = httpx.AsyncClient(timeout=300.0)
|
|
39
|
+
|
|
40
|
+
logger.info(
|
|
41
|
+
"vercel_blob_provider_initialized",
|
|
42
|
+
store_name=store_name,
|
|
43
|
+
base_url=self.base_url
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def _get_blob_path(self, organization_id: str, file_path: str) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Construct organization-scoped blob path.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
organization_id: Organization ID
|
|
52
|
+
file_path: Virtual file path
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Full blob path with organization prefix
|
|
56
|
+
"""
|
|
57
|
+
# Ensure file_path starts with /
|
|
58
|
+
if not file_path.startswith("/"):
|
|
59
|
+
file_path = f"/{file_path}"
|
|
60
|
+
|
|
61
|
+
# Construct: {org_id}{file_path}
|
|
62
|
+
return f"{organization_id}{file_path}"
|
|
63
|
+
|
|
64
|
+
async def upload(
|
|
65
|
+
self,
|
|
66
|
+
file_content: BinaryIO,
|
|
67
|
+
file_name: str,
|
|
68
|
+
file_path: str,
|
|
69
|
+
content_type: str,
|
|
70
|
+
organization_id: str,
|
|
71
|
+
metadata: Optional[Dict[str, str]] = None
|
|
72
|
+
) -> UploadResult:
|
|
73
|
+
"""Upload file to Vercel Blob."""
|
|
74
|
+
|
|
75
|
+
# Construct org-scoped blob path
|
|
76
|
+
blob_path = self._get_blob_path(organization_id, file_path)
|
|
77
|
+
|
|
78
|
+
# Read content and calculate checksum
|
|
79
|
+
content = file_content.read()
|
|
80
|
+
if hasattr(file_content, 'seek'):
|
|
81
|
+
file_content.seek(0)
|
|
82
|
+
checksum = hashlib.sha256(content).hexdigest()
|
|
83
|
+
|
|
84
|
+
# Prepare headers
|
|
85
|
+
headers = {
|
|
86
|
+
"Authorization": f"Bearer {self.token}",
|
|
87
|
+
"X-Content-Type": content_type,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Add metadata as headers
|
|
91
|
+
if metadata:
|
|
92
|
+
for key, value in metadata.items():
|
|
93
|
+
safe_key = key.replace(" ", "_").replace("-", "_")
|
|
94
|
+
headers[f"X-Meta-{safe_key}"] = str(value)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Upload to Vercel Blob
|
|
98
|
+
logger.info(
|
|
99
|
+
"vercel_blob_upload_started",
|
|
100
|
+
blob_path=blob_path,
|
|
101
|
+
file_name=file_name,
|
|
102
|
+
size_bytes=len(content),
|
|
103
|
+
organization_id=organization_id
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
response = await self.client.put(
|
|
107
|
+
f"{self.base_url}/{blob_path}",
|
|
108
|
+
content=content,
|
|
109
|
+
headers=headers
|
|
110
|
+
)
|
|
111
|
+
response.raise_for_status()
|
|
112
|
+
|
|
113
|
+
result = response.json()
|
|
114
|
+
|
|
115
|
+
logger.info(
|
|
116
|
+
"vercel_blob_upload_completed",
|
|
117
|
+
blob_path=blob_path,
|
|
118
|
+
url=result.get("url"),
|
|
119
|
+
organization_id=organization_id
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return UploadResult(
|
|
123
|
+
file_id=result["url"], # Vercel uses URL as ID
|
|
124
|
+
url=result["url"],
|
|
125
|
+
file_size_bytes=len(content),
|
|
126
|
+
checksum=checksum,
|
|
127
|
+
provider_metadata={
|
|
128
|
+
"downloadUrl": result.get("downloadUrl"),
|
|
129
|
+
"pathname": result.get("pathname"),
|
|
130
|
+
"blob_path": blob_path,
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
except httpx.HTTPStatusError as e:
|
|
135
|
+
logger.error(
|
|
136
|
+
"vercel_blob_upload_failed",
|
|
137
|
+
error=str(e),
|
|
138
|
+
status_code=e.response.status_code,
|
|
139
|
+
blob_path=blob_path
|
|
140
|
+
)
|
|
141
|
+
raise Exception(f"Vercel Blob upload failed: {e.response.text}")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(
|
|
144
|
+
"vercel_blob_upload_error",
|
|
145
|
+
error=str(e),
|
|
146
|
+
blob_path=blob_path
|
|
147
|
+
)
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
async def download(
|
|
151
|
+
self,
|
|
152
|
+
file_id: str,
|
|
153
|
+
organization_id: str
|
|
154
|
+
) -> Tuple[BinaryIO, str, int]:
|
|
155
|
+
"""Download file from Vercel Blob."""
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
logger.info(
|
|
159
|
+
"vercel_blob_download_started",
|
|
160
|
+
file_id=file_id,
|
|
161
|
+
organization_id=organization_id
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# file_id is the blob URL for Vercel
|
|
165
|
+
response = await self.client.get(file_id, follow_redirects=True)
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
|
|
168
|
+
content_type = response.headers.get("content-type", "application/octet-stream")
|
|
169
|
+
file_size = int(response.headers.get("content-length", 0))
|
|
170
|
+
|
|
171
|
+
logger.info(
|
|
172
|
+
"vercel_blob_download_completed",
|
|
173
|
+
file_id=file_id,
|
|
174
|
+
size_bytes=file_size,
|
|
175
|
+
organization_id=organization_id
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Return as BytesIO
|
|
179
|
+
return BytesIO(response.content), content_type, file_size
|
|
180
|
+
|
|
181
|
+
except httpx.HTTPStatusError as e:
|
|
182
|
+
if e.response.status_code == 404:
|
|
183
|
+
raise FileNotFoundError(f"File not found: {file_id}")
|
|
184
|
+
logger.error(
|
|
185
|
+
"vercel_blob_download_failed",
|
|
186
|
+
error=str(e),
|
|
187
|
+
status_code=e.response.status_code,
|
|
188
|
+
file_id=file_id
|
|
189
|
+
)
|
|
190
|
+
raise Exception(f"Vercel Blob download failed: {e.response.text}")
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(
|
|
193
|
+
"vercel_blob_download_error",
|
|
194
|
+
error=str(e),
|
|
195
|
+
file_id=file_id
|
|
196
|
+
)
|
|
197
|
+
raise
|
|
198
|
+
|
|
199
|
+
async def delete(self, file_id: str, organization_id: str) -> bool:
|
|
200
|
+
"""Delete file from Vercel Blob."""
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
logger.info(
|
|
204
|
+
"vercel_blob_delete_started",
|
|
205
|
+
file_id=file_id,
|
|
206
|
+
organization_id=organization_id
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Vercel Blob delete API
|
|
210
|
+
response = await self.client.post(
|
|
211
|
+
f"{self.base_url}/delete",
|
|
212
|
+
json={"urls": [file_id]},
|
|
213
|
+
headers={"Authorization": f"Bearer {self.token}"}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
success = response.status_code == 200
|
|
217
|
+
|
|
218
|
+
if success:
|
|
219
|
+
logger.info(
|
|
220
|
+
"vercel_blob_delete_completed",
|
|
221
|
+
file_id=file_id,
|
|
222
|
+
organization_id=organization_id
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
logger.warning(
|
|
226
|
+
"vercel_blob_delete_failed",
|
|
227
|
+
status_code=response.status_code,
|
|
228
|
+
file_id=file_id
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return success
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(
|
|
235
|
+
"vercel_blob_delete_error",
|
|
236
|
+
error=str(e),
|
|
237
|
+
file_id=file_id
|
|
238
|
+
)
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
async def list_files(
|
|
242
|
+
self,
|
|
243
|
+
organization_id: str,
|
|
244
|
+
prefix: Optional[str] = None,
|
|
245
|
+
limit: int = 100,
|
|
246
|
+
cursor: Optional[str] = None
|
|
247
|
+
) -> Tuple[List[StorageFile], Optional[str]]:
|
|
248
|
+
"""
|
|
249
|
+
List files from Vercel Blob.
|
|
250
|
+
|
|
251
|
+
Note: Vercel Blob list API has limitations. For production use,
|
|
252
|
+
it's recommended to use database queries with provider_file_id instead.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
# Construct org-scoped prefix
|
|
257
|
+
org_prefix = organization_id
|
|
258
|
+
if prefix:
|
|
259
|
+
org_prefix = self._get_blob_path(organization_id, prefix)
|
|
260
|
+
|
|
261
|
+
params = {
|
|
262
|
+
"prefix": org_prefix,
|
|
263
|
+
"limit": limit
|
|
264
|
+
}
|
|
265
|
+
if cursor:
|
|
266
|
+
params["cursor"] = cursor
|
|
267
|
+
|
|
268
|
+
logger.info(
|
|
269
|
+
"vercel_blob_list_started",
|
|
270
|
+
organization_id=organization_id,
|
|
271
|
+
prefix=org_prefix,
|
|
272
|
+
limit=limit
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
response = await self.client.get(
|
|
276
|
+
f"{self.base_url}/list",
|
|
277
|
+
params=params,
|
|
278
|
+
headers={"Authorization": f"Bearer {self.token}"}
|
|
279
|
+
)
|
|
280
|
+
response.raise_for_status()
|
|
281
|
+
|
|
282
|
+
data = response.json()
|
|
283
|
+
blobs = data.get("blobs", [])
|
|
284
|
+
next_cursor = data.get("cursor")
|
|
285
|
+
|
|
286
|
+
# Convert to StorageFile objects
|
|
287
|
+
files = []
|
|
288
|
+
for blob in blobs:
|
|
289
|
+
# Extract file path by removing org prefix
|
|
290
|
+
pathname = blob.get("pathname", "")
|
|
291
|
+
if pathname.startswith(organization_id):
|
|
292
|
+
file_path = pathname[len(organization_id):]
|
|
293
|
+
else:
|
|
294
|
+
file_path = pathname
|
|
295
|
+
|
|
296
|
+
files.append(StorageFile(
|
|
297
|
+
file_id=blob["url"],
|
|
298
|
+
file_name=file_path.split("/")[-1],
|
|
299
|
+
file_path=file_path,
|
|
300
|
+
content_type=blob.get("contentType", "application/octet-stream"),
|
|
301
|
+
file_size_bytes=blob.get("size", 0),
|
|
302
|
+
checksum=None, # Vercel doesn't provide checksum in list
|
|
303
|
+
created_at=datetime.fromisoformat(blob["uploadedAt"].replace("Z", "+00:00"))
|
|
304
|
+
if "uploadedAt" in blob else None,
|
|
305
|
+
provider_metadata=blob
|
|
306
|
+
))
|
|
307
|
+
|
|
308
|
+
logger.info(
|
|
309
|
+
"vercel_blob_list_completed",
|
|
310
|
+
organization_id=organization_id,
|
|
311
|
+
files_count=len(files),
|
|
312
|
+
has_more=next_cursor is not None
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return files, next_cursor
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
logger.error(
|
|
319
|
+
"vercel_blob_list_error",
|
|
320
|
+
error=str(e),
|
|
321
|
+
organization_id=organization_id
|
|
322
|
+
)
|
|
323
|
+
raise
|
|
324
|
+
|
|
325
|
+
async def get_metadata(
|
|
326
|
+
self,
|
|
327
|
+
file_id: str,
|
|
328
|
+
organization_id: str
|
|
329
|
+
) -> StorageFile:
|
|
330
|
+
"""
|
|
331
|
+
Get file metadata.
|
|
332
|
+
|
|
333
|
+
Vercel Blob doesn't have a dedicated metadata endpoint,
|
|
334
|
+
so we use HEAD request to get headers.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
response = await self.client.head(file_id, follow_redirects=True)
|
|
339
|
+
response.raise_for_status()
|
|
340
|
+
|
|
341
|
+
# Extract metadata from headers
|
|
342
|
+
content_type = response.headers.get("content-type", "application/octet-stream")
|
|
343
|
+
file_size = int(response.headers.get("content-length", 0))
|
|
344
|
+
|
|
345
|
+
# Extract pathname from URL
|
|
346
|
+
pathname = file_id.split("/")[-1]
|
|
347
|
+
|
|
348
|
+
return StorageFile(
|
|
349
|
+
file_id=file_id,
|
|
350
|
+
file_name=pathname.split("/")[-1],
|
|
351
|
+
file_path=pathname,
|
|
352
|
+
content_type=content_type,
|
|
353
|
+
file_size_bytes=file_size,
|
|
354
|
+
checksum=None,
|
|
355
|
+
created_at=None,
|
|
356
|
+
provider_metadata={"url": file_id}
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
except httpx.HTTPStatusError as e:
|
|
360
|
+
if e.response.status_code == 404:
|
|
361
|
+
raise FileNotFoundError(f"File not found: {file_id}")
|
|
362
|
+
raise Exception(f"Failed to get metadata: {e.response.text}")
|
|
363
|
+
|
|
364
|
+
async def copy(
|
|
365
|
+
self,
|
|
366
|
+
source_file_id: str,
|
|
367
|
+
destination_path: str,
|
|
368
|
+
organization_id: str
|
|
369
|
+
) -> UploadResult:
|
|
370
|
+
"""
|
|
371
|
+
Copy file to new location.
|
|
372
|
+
|
|
373
|
+
Vercel Blob doesn't have native copy, so we download and re-upload.
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
logger.info(
|
|
378
|
+
"vercel_blob_copy_started",
|
|
379
|
+
source_file_id=source_file_id,
|
|
380
|
+
destination_path=destination_path,
|
|
381
|
+
organization_id=organization_id
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Download source file
|
|
385
|
+
file_stream, content_type, _ = await self.download(source_file_id, organization_id)
|
|
386
|
+
|
|
387
|
+
# Get filename from destination path
|
|
388
|
+
file_name = destination_path.split("/")[-1]
|
|
389
|
+
|
|
390
|
+
# Upload to new location
|
|
391
|
+
result = await self.upload(
|
|
392
|
+
file_content=file_stream,
|
|
393
|
+
file_name=file_name,
|
|
394
|
+
file_path=destination_path,
|
|
395
|
+
content_type=content_type,
|
|
396
|
+
organization_id=organization_id
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
logger.info(
|
|
400
|
+
"vercel_blob_copy_completed",
|
|
401
|
+
source_file_id=source_file_id,
|
|
402
|
+
destination_file_id=result.file_id,
|
|
403
|
+
organization_id=organization_id
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
return result
|
|
407
|
+
|
|
408
|
+
except Exception as e:
|
|
409
|
+
logger.error(
|
|
410
|
+
"vercel_blob_copy_error",
|
|
411
|
+
error=str(e),
|
|
412
|
+
source_file_id=source_file_id
|
|
413
|
+
)
|
|
414
|
+
raise
|
|
415
|
+
|
|
416
|
+
async def move(
|
|
417
|
+
self,
|
|
418
|
+
file_id: str,
|
|
419
|
+
new_path: str,
|
|
420
|
+
organization_id: str
|
|
421
|
+
) -> StorageFile:
|
|
422
|
+
"""
|
|
423
|
+
Move/rename file.
|
|
424
|
+
|
|
425
|
+
Vercel Blob doesn't have native move, so we copy and delete.
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
logger.info(
|
|
430
|
+
"vercel_blob_move_started",
|
|
431
|
+
file_id=file_id,
|
|
432
|
+
new_path=new_path,
|
|
433
|
+
organization_id=organization_id
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# Copy to new location
|
|
437
|
+
upload_result = await self.copy(file_id, new_path, organization_id)
|
|
438
|
+
|
|
439
|
+
# Delete original
|
|
440
|
+
await self.delete(file_id, organization_id)
|
|
441
|
+
|
|
442
|
+
# Get metadata of new file
|
|
443
|
+
new_file = await self.get_metadata(upload_result.file_id, organization_id)
|
|
444
|
+
|
|
445
|
+
logger.info(
|
|
446
|
+
"vercel_blob_move_completed",
|
|
447
|
+
old_file_id=file_id,
|
|
448
|
+
new_file_id=upload_result.file_id,
|
|
449
|
+
organization_id=organization_id
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
return new_file
|
|
453
|
+
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.error(
|
|
456
|
+
"vercel_blob_move_error",
|
|
457
|
+
error=str(e),
|
|
458
|
+
file_id=file_id
|
|
459
|
+
)
|
|
460
|
+
raise
|
|
461
|
+
|
|
462
|
+
def get_provider_name(self) -> str:
|
|
463
|
+
"""Return provider name."""
|
|
464
|
+
return "vercel_blob"
|
|
465
|
+
|
|
466
|
+
async def close(self):
|
|
467
|
+
"""Close HTTP client."""
|
|
468
|
+
await self.client.aclose()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Supabase client for Agent Control Plane"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from supabase import create_client, Client
|
|
6
|
+
import structlog
|
|
7
|
+
|
|
8
|
+
logger = structlog.get_logger()
|
|
9
|
+
|
|
10
|
+
_supabase_client: Optional[Client] = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_supabase() -> Client:
|
|
14
|
+
"""
|
|
15
|
+
Get or create Supabase client singleton.
|
|
16
|
+
|
|
17
|
+
Uses service role key for admin operations with RLS bypass.
|
|
18
|
+
The API will set organization context via middleware.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Supabase client instance
|
|
22
|
+
"""
|
|
23
|
+
global _supabase_client
|
|
24
|
+
|
|
25
|
+
if _supabase_client is not None:
|
|
26
|
+
return _supabase_client
|
|
27
|
+
|
|
28
|
+
supabase_url = os.environ.get("SUPABASE_URL") or os.environ.get("SUPABASE_SUPABASE_URL")
|
|
29
|
+
# Try multiple env var names for service key (Vercel Supabase integration uses different names)
|
|
30
|
+
supabase_key = (
|
|
31
|
+
os.environ.get("SUPABASE_SERVICE_KEY") or
|
|
32
|
+
os.environ.get("SUPABASE_SUPABASE_SERVICE_ROLE_KEY") or
|
|
33
|
+
os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if not supabase_url or not supabase_key:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
"SUPABASE_URL and SUPABASE_SERVICE_KEY environment variables are required. "
|
|
39
|
+
f"Found URL: {bool(supabase_url)}, Key: {bool(supabase_key)}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
_supabase_client = create_client(supabase_url, supabase_key)
|
|
43
|
+
|
|
44
|
+
logger.info("supabase_client_initialized", url=supabase_url)
|
|
45
|
+
|
|
46
|
+
return _supabase_client
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def execute_with_org_context(org_id: str, query_func):
|
|
50
|
+
"""
|
|
51
|
+
Execute a Supabase query with organization context for RLS.
|
|
52
|
+
|
|
53
|
+
Sets the app.current_org_id config parameter that RLS policies use.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
org_id: Organization UUID
|
|
57
|
+
query_func: Function that performs the database operation
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Query result
|
|
61
|
+
"""
|
|
62
|
+
client = get_supabase()
|
|
63
|
+
|
|
64
|
+
# Set organization context for RLS
|
|
65
|
+
# This uses the PostgreSQL set_config function via RPC
|
|
66
|
+
client.rpc("set_organization_context", {"org_id": org_id}).execute()
|
|
67
|
+
|
|
68
|
+
# Execute the query
|
|
69
|
+
result = query_func()
|
|
70
|
+
|
|
71
|
+
return result
|