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.
Files changed (479) hide show
  1. control_plane_api/LICENSE +676 -0
  2. control_plane_api/README.md +350 -0
  3. control_plane_api/__init__.py +4 -0
  4. control_plane_api/__version__.py +8 -0
  5. control_plane_api/alembic/README +1 -0
  6. control_plane_api/alembic/env.py +121 -0
  7. control_plane_api/alembic/script.py.mako +28 -0
  8. control_plane_api/alembic/versions/2613c65c3dbe_initial_database_setup.py +32 -0
  9. control_plane_api/alembic/versions/2df520d4927d_merge_heads.py +28 -0
  10. control_plane_api/alembic/versions/43abf98d6a01_add_paused_status_to_executions.py +73 -0
  11. control_plane_api/alembic/versions/6289854264cb_merge_multiple_heads.py +28 -0
  12. control_plane_api/alembic/versions/6a4d4dc3d8dc_generate_execution_transitions.py +50 -0
  13. control_plane_api/alembic/versions/87d11cf0a783_add_disconnected_status_to_worker_.py +44 -0
  14. control_plane_api/alembic/versions/add_ephemeral_queue_support.py +85 -0
  15. control_plane_api/alembic/versions/add_model_type_to_llm_models.py +31 -0
  16. control_plane_api/alembic/versions/add_plan_executions_table.py +114 -0
  17. control_plane_api/alembic/versions/add_trace_span_tables.py +154 -0
  18. control_plane_api/alembic/versions/add_user_info_to_traces.py +36 -0
  19. control_plane_api/alembic/versions/adjusting_foreign_keys.py +32 -0
  20. control_plane_api/alembic/versions/b4983d976db2_initial_tables.py +1128 -0
  21. control_plane_api/alembic/versions/d181a3b40e71_rename_custom_metadata_to_metadata_in_.py +50 -0
  22. control_plane_api/alembic/versions/df9117888e82_add_missing_columns.py +82 -0
  23. control_plane_api/alembic/versions/f25de6ad895a_missing_migrations.py +34 -0
  24. control_plane_api/alembic/versions/f71305fb69b9_fix_ephemeral_queue_deletion_foreign_key.py +54 -0
  25. control_plane_api/alembic/versions/mark_local_exec_queues_as_ephemeral.py +68 -0
  26. control_plane_api/alembic.ini +148 -0
  27. control_plane_api/api/index.py +12 -0
  28. control_plane_api/app/__init__.py +11 -0
  29. control_plane_api/app/activities/__init__.py +20 -0
  30. control_plane_api/app/activities/agent_activities.py +384 -0
  31. control_plane_api/app/activities/plan_generation_activities.py +499 -0
  32. control_plane_api/app/activities/team_activities.py +424 -0
  33. control_plane_api/app/activities/temporal_cloud_activities.py +588 -0
  34. control_plane_api/app/config/__init__.py +35 -0
  35. control_plane_api/app/config/api_config.py +469 -0
  36. control_plane_api/app/config/config_loader.py +224 -0
  37. control_plane_api/app/config/model_pricing.py +323 -0
  38. control_plane_api/app/config/storage_config.py +159 -0
  39. control_plane_api/app/config.py +115 -0
  40. control_plane_api/app/controllers/__init__.py +0 -0
  41. control_plane_api/app/controllers/execution_environment_controller.py +1315 -0
  42. control_plane_api/app/database.py +135 -0
  43. control_plane_api/app/exceptions.py +408 -0
  44. control_plane_api/app/lib/__init__.py +11 -0
  45. control_plane_api/app/lib/environment.py +65 -0
  46. control_plane_api/app/lib/event_bus/__init__.py +17 -0
  47. control_plane_api/app/lib/event_bus/base.py +136 -0
  48. control_plane_api/app/lib/event_bus/manager.py +335 -0
  49. control_plane_api/app/lib/event_bus/providers/__init__.py +6 -0
  50. control_plane_api/app/lib/event_bus/providers/http_provider.py +166 -0
  51. control_plane_api/app/lib/event_bus/providers/nats_provider.py +324 -0
  52. control_plane_api/app/lib/event_bus/providers/redis_provider.py +233 -0
  53. control_plane_api/app/lib/event_bus/providers/websocket_provider.py +497 -0
  54. control_plane_api/app/lib/job_executor.py +330 -0
  55. control_plane_api/app/lib/kubiya_client.py +293 -0
  56. control_plane_api/app/lib/litellm_pricing.py +166 -0
  57. control_plane_api/app/lib/mcp_validation.py +163 -0
  58. control_plane_api/app/lib/nats/__init__.py +13 -0
  59. control_plane_api/app/lib/nats/credentials_manager.py +288 -0
  60. control_plane_api/app/lib/nats/listener.py +374 -0
  61. control_plane_api/app/lib/planning_prompt_builder.py +153 -0
  62. control_plane_api/app/lib/planning_tools/__init__.py +41 -0
  63. control_plane_api/app/lib/planning_tools/agents.py +409 -0
  64. control_plane_api/app/lib/planning_tools/agno_toolkit.py +836 -0
  65. control_plane_api/app/lib/planning_tools/base.py +119 -0
  66. control_plane_api/app/lib/planning_tools/cognitive_memory_tools.py +403 -0
  67. control_plane_api/app/lib/planning_tools/context_graph_tools.py +545 -0
  68. control_plane_api/app/lib/planning_tools/environments.py +218 -0
  69. control_plane_api/app/lib/planning_tools/knowledge.py +204 -0
  70. control_plane_api/app/lib/planning_tools/models.py +93 -0
  71. control_plane_api/app/lib/planning_tools/planning_service.py +646 -0
  72. control_plane_api/app/lib/planning_tools/resources.py +242 -0
  73. control_plane_api/app/lib/planning_tools/teams.py +334 -0
  74. control_plane_api/app/lib/policy_enforcer_client.py +1016 -0
  75. control_plane_api/app/lib/redis_client.py +803 -0
  76. control_plane_api/app/lib/sqlalchemy_utils.py +486 -0
  77. control_plane_api/app/lib/state_transition_tools/__init__.py +7 -0
  78. control_plane_api/app/lib/state_transition_tools/execution_context.py +388 -0
  79. control_plane_api/app/lib/storage/__init__.py +20 -0
  80. control_plane_api/app/lib/storage/base_provider.py +274 -0
  81. control_plane_api/app/lib/storage/provider_factory.py +157 -0
  82. control_plane_api/app/lib/storage/vercel_blob_provider.py +468 -0
  83. control_plane_api/app/lib/supabase.py +71 -0
  84. control_plane_api/app/lib/supabase_utils.py +138 -0
  85. control_plane_api/app/lib/task_planning/__init__.py +138 -0
  86. control_plane_api/app/lib/task_planning/agent_factory.py +308 -0
  87. control_plane_api/app/lib/task_planning/agents.py +389 -0
  88. control_plane_api/app/lib/task_planning/cache.py +218 -0
  89. control_plane_api/app/lib/task_planning/entity_resolver.py +273 -0
  90. control_plane_api/app/lib/task_planning/helpers.py +293 -0
  91. control_plane_api/app/lib/task_planning/hooks.py +474 -0
  92. control_plane_api/app/lib/task_planning/models.py +503 -0
  93. control_plane_api/app/lib/task_planning/plan_validator.py +166 -0
  94. control_plane_api/app/lib/task_planning/planning_workflow.py +2911 -0
  95. control_plane_api/app/lib/task_planning/runner.py +656 -0
  96. control_plane_api/app/lib/task_planning/streaming_hook.py +213 -0
  97. control_plane_api/app/lib/task_planning/workflow.py +424 -0
  98. control_plane_api/app/lib/templating/__init__.py +88 -0
  99. control_plane_api/app/lib/templating/compiler.py +278 -0
  100. control_plane_api/app/lib/templating/engine.py +178 -0
  101. control_plane_api/app/lib/templating/parsers/__init__.py +29 -0
  102. control_plane_api/app/lib/templating/parsers/base.py +96 -0
  103. control_plane_api/app/lib/templating/parsers/env.py +85 -0
  104. control_plane_api/app/lib/templating/parsers/graph.py +112 -0
  105. control_plane_api/app/lib/templating/parsers/secret.py +87 -0
  106. control_plane_api/app/lib/templating/parsers/simple.py +81 -0
  107. control_plane_api/app/lib/templating/resolver.py +366 -0
  108. control_plane_api/app/lib/templating/types.py +214 -0
  109. control_plane_api/app/lib/templating/validator.py +201 -0
  110. control_plane_api/app/lib/temporal_client.py +232 -0
  111. control_plane_api/app/lib/temporal_credentials_cache.py +178 -0
  112. control_plane_api/app/lib/temporal_credentials_service.py +203 -0
  113. control_plane_api/app/lib/validation/__init__.py +24 -0
  114. control_plane_api/app/lib/validation/runtime_validation.py +388 -0
  115. control_plane_api/app/main.py +531 -0
  116. control_plane_api/app/middleware/__init__.py +10 -0
  117. control_plane_api/app/middleware/auth.py +645 -0
  118. control_plane_api/app/middleware/exception_handler.py +267 -0
  119. control_plane_api/app/middleware/prometheus_middleware.py +173 -0
  120. control_plane_api/app/middleware/rate_limiting.py +384 -0
  121. control_plane_api/app/middleware/request_id.py +202 -0
  122. control_plane_api/app/models/__init__.py +40 -0
  123. control_plane_api/app/models/agent.py +90 -0
  124. control_plane_api/app/models/analytics.py +206 -0
  125. control_plane_api/app/models/associations.py +107 -0
  126. control_plane_api/app/models/auth_user.py +73 -0
  127. control_plane_api/app/models/context.py +161 -0
  128. control_plane_api/app/models/custom_integration.py +99 -0
  129. control_plane_api/app/models/environment.py +64 -0
  130. control_plane_api/app/models/execution.py +125 -0
  131. control_plane_api/app/models/execution_transition.py +50 -0
  132. control_plane_api/app/models/job.py +159 -0
  133. control_plane_api/app/models/llm_model.py +78 -0
  134. control_plane_api/app/models/orchestration.py +66 -0
  135. control_plane_api/app/models/plan_execution.py +102 -0
  136. control_plane_api/app/models/presence.py +49 -0
  137. control_plane_api/app/models/project.py +61 -0
  138. control_plane_api/app/models/project_management.py +85 -0
  139. control_plane_api/app/models/session.py +29 -0
  140. control_plane_api/app/models/skill.py +155 -0
  141. control_plane_api/app/models/system_tables.py +43 -0
  142. control_plane_api/app/models/task_planning.py +372 -0
  143. control_plane_api/app/models/team.py +86 -0
  144. control_plane_api/app/models/trace.py +257 -0
  145. control_plane_api/app/models/user_profile.py +54 -0
  146. control_plane_api/app/models/worker.py +221 -0
  147. control_plane_api/app/models/workflow.py +161 -0
  148. control_plane_api/app/models/workspace.py +50 -0
  149. control_plane_api/app/observability/__init__.py +177 -0
  150. control_plane_api/app/observability/context_logging.py +475 -0
  151. control_plane_api/app/observability/decorators.py +337 -0
  152. control_plane_api/app/observability/local_span_processor.py +702 -0
  153. control_plane_api/app/observability/metrics.py +303 -0
  154. control_plane_api/app/observability/middleware.py +246 -0
  155. control_plane_api/app/observability/optional.py +115 -0
  156. control_plane_api/app/observability/tracing.py +382 -0
  157. control_plane_api/app/policies/README.md +149 -0
  158. control_plane_api/app/policies/approved_users.rego +62 -0
  159. control_plane_api/app/policies/business_hours.rego +51 -0
  160. control_plane_api/app/policies/rate_limiting.rego +100 -0
  161. control_plane_api/app/policies/tool_enforcement/README.md +336 -0
  162. control_plane_api/app/policies/tool_enforcement/bash_command_validation.rego +71 -0
  163. control_plane_api/app/policies/tool_enforcement/business_hours_enforcement.rego +82 -0
  164. control_plane_api/app/policies/tool_enforcement/mcp_tool_allowlist.rego +58 -0
  165. control_plane_api/app/policies/tool_enforcement/production_safeguards.rego +80 -0
  166. control_plane_api/app/policies/tool_enforcement/role_based_tool_access.rego +44 -0
  167. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  168. control_plane_api/app/routers/__init__.py +4 -0
  169. control_plane_api/app/routers/agents.py +382 -0
  170. control_plane_api/app/routers/agents_v2.py +1598 -0
  171. control_plane_api/app/routers/analytics.py +1310 -0
  172. control_plane_api/app/routers/auth.py +59 -0
  173. control_plane_api/app/routers/client_config.py +57 -0
  174. control_plane_api/app/routers/context_graph.py +561 -0
  175. control_plane_api/app/routers/context_manager.py +577 -0
  176. control_plane_api/app/routers/custom_integrations.py +490 -0
  177. control_plane_api/app/routers/enforcer.py +132 -0
  178. control_plane_api/app/routers/environment_context.py +252 -0
  179. control_plane_api/app/routers/environments.py +761 -0
  180. control_plane_api/app/routers/execution_environment.py +847 -0
  181. control_plane_api/app/routers/executions/__init__.py +28 -0
  182. control_plane_api/app/routers/executions/router.py +286 -0
  183. control_plane_api/app/routers/executions/services/__init__.py +22 -0
  184. control_plane_api/app/routers/executions/services/demo_worker_health.py +156 -0
  185. control_plane_api/app/routers/executions/services/status_service.py +420 -0
  186. control_plane_api/app/routers/executions/services/test_worker_health.py +480 -0
  187. control_plane_api/app/routers/executions/services/worker_health.py +514 -0
  188. control_plane_api/app/routers/executions/streaming/__init__.py +22 -0
  189. control_plane_api/app/routers/executions/streaming/deduplication.py +352 -0
  190. control_plane_api/app/routers/executions/streaming/event_buffer.py +353 -0
  191. control_plane_api/app/routers/executions/streaming/event_formatter.py +964 -0
  192. control_plane_api/app/routers/executions/streaming/history_loader.py +588 -0
  193. control_plane_api/app/routers/executions/streaming/live_source.py +693 -0
  194. control_plane_api/app/routers/executions/streaming/streamer.py +849 -0
  195. control_plane_api/app/routers/executions.py +4888 -0
  196. control_plane_api/app/routers/health.py +165 -0
  197. control_plane_api/app/routers/health_v2.py +394 -0
  198. control_plane_api/app/routers/integration_templates.py +496 -0
  199. control_plane_api/app/routers/integrations.py +287 -0
  200. control_plane_api/app/routers/jobs.py +1809 -0
  201. control_plane_api/app/routers/metrics.py +517 -0
  202. control_plane_api/app/routers/models.py +82 -0
  203. control_plane_api/app/routers/models_v2.py +628 -0
  204. control_plane_api/app/routers/plan_executions.py +1481 -0
  205. control_plane_api/app/routers/plan_generation_async.py +304 -0
  206. control_plane_api/app/routers/policies.py +669 -0
  207. control_plane_api/app/routers/presence.py +234 -0
  208. control_plane_api/app/routers/projects.py +987 -0
  209. control_plane_api/app/routers/runners.py +379 -0
  210. control_plane_api/app/routers/runtimes.py +172 -0
  211. control_plane_api/app/routers/secrets.py +171 -0
  212. control_plane_api/app/routers/skills.py +1010 -0
  213. control_plane_api/app/routers/skills_definitions.py +140 -0
  214. control_plane_api/app/routers/storage.py +456 -0
  215. control_plane_api/app/routers/task_planning.py +611 -0
  216. control_plane_api/app/routers/task_queues.py +650 -0
  217. control_plane_api/app/routers/team_context.py +274 -0
  218. control_plane_api/app/routers/teams.py +1747 -0
  219. control_plane_api/app/routers/templates.py +248 -0
  220. control_plane_api/app/routers/traces.py +571 -0
  221. control_plane_api/app/routers/websocket_client.py +479 -0
  222. control_plane_api/app/routers/websocket_executions_status.py +437 -0
  223. control_plane_api/app/routers/websocket_gateway.py +323 -0
  224. control_plane_api/app/routers/websocket_traces.py +576 -0
  225. control_plane_api/app/routers/worker_queues.py +2555 -0
  226. control_plane_api/app/routers/worker_websocket.py +419 -0
  227. control_plane_api/app/routers/workers.py +1004 -0
  228. control_plane_api/app/routers/workflows.py +204 -0
  229. control_plane_api/app/runtimes/__init__.py +6 -0
  230. control_plane_api/app/runtimes/validation.py +344 -0
  231. control_plane_api/app/schemas/__init__.py +1 -0
  232. control_plane_api/app/schemas/job_schemas.py +302 -0
  233. control_plane_api/app/schemas/mcp_schemas.py +311 -0
  234. control_plane_api/app/schemas/template_schemas.py +133 -0
  235. control_plane_api/app/schemas/trace_schemas.py +168 -0
  236. control_plane_api/app/schemas/worker_queue_observability_schemas.py +165 -0
  237. control_plane_api/app/services/__init__.py +1 -0
  238. control_plane_api/app/services/agno_planning_strategy.py +233 -0
  239. control_plane_api/app/services/agno_service.py +838 -0
  240. control_plane_api/app/services/claude_code_planning_service.py +203 -0
  241. control_plane_api/app/services/context_graph_client.py +224 -0
  242. control_plane_api/app/services/custom_integration_service.py +415 -0
  243. control_plane_api/app/services/integration_resolution_service.py +345 -0
  244. control_plane_api/app/services/litellm_service.py +394 -0
  245. control_plane_api/app/services/plan_generator.py +79 -0
  246. control_plane_api/app/services/planning_strategy.py +66 -0
  247. control_plane_api/app/services/planning_strategy_factory.py +118 -0
  248. control_plane_api/app/services/policy_service.py +615 -0
  249. control_plane_api/app/services/state_transition_service.py +755 -0
  250. control_plane_api/app/services/storage_service.py +593 -0
  251. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  252. control_plane_api/app/services/toolsets/context_graph_skill.py +432 -0
  253. control_plane_api/app/services/trace_retention.py +354 -0
  254. control_plane_api/app/services/worker_queue_metrics_service.py +190 -0
  255. control_plane_api/app/services/workflow_cancellation_manager.py +135 -0
  256. control_plane_api/app/services/workflow_operations_service.py +611 -0
  257. control_plane_api/app/skills/__init__.py +100 -0
  258. control_plane_api/app/skills/base.py +239 -0
  259. control_plane_api/app/skills/builtin/__init__.py +37 -0
  260. control_plane_api/app/skills/builtin/agent_communication/__init__.py +8 -0
  261. control_plane_api/app/skills/builtin/agent_communication/skill.py +246 -0
  262. control_plane_api/app/skills/builtin/code_ingestion/__init__.py +4 -0
  263. control_plane_api/app/skills/builtin/code_ingestion/skill.py +267 -0
  264. control_plane_api/app/skills/builtin/cognitive_memory/__init__.py +4 -0
  265. control_plane_api/app/skills/builtin/cognitive_memory/skill.py +174 -0
  266. control_plane_api/app/skills/builtin/contextual_awareness/__init__.py +4 -0
  267. control_plane_api/app/skills/builtin/contextual_awareness/skill.py +387 -0
  268. control_plane_api/app/skills/builtin/data_visualization/__init__.py +4 -0
  269. control_plane_api/app/skills/builtin/data_visualization/skill.py +154 -0
  270. control_plane_api/app/skills/builtin/docker/__init__.py +4 -0
  271. control_plane_api/app/skills/builtin/docker/skill.py +104 -0
  272. control_plane_api/app/skills/builtin/file_generation/__init__.py +4 -0
  273. control_plane_api/app/skills/builtin/file_generation/skill.py +94 -0
  274. control_plane_api/app/skills/builtin/file_system/__init__.py +4 -0
  275. control_plane_api/app/skills/builtin/file_system/skill.py +110 -0
  276. control_plane_api/app/skills/builtin/knowledge_api/__init__.py +5 -0
  277. control_plane_api/app/skills/builtin/knowledge_api/skill.py +124 -0
  278. control_plane_api/app/skills/builtin/python/__init__.py +4 -0
  279. control_plane_api/app/skills/builtin/python/skill.py +92 -0
  280. control_plane_api/app/skills/builtin/remote_filesystem/__init__.py +5 -0
  281. control_plane_api/app/skills/builtin/remote_filesystem/skill.py +170 -0
  282. control_plane_api/app/skills/builtin/shell/__init__.py +4 -0
  283. control_plane_api/app/skills/builtin/shell/skill.py +161 -0
  284. control_plane_api/app/skills/builtin/slack/__init__.py +3 -0
  285. control_plane_api/app/skills/builtin/slack/skill.py +302 -0
  286. control_plane_api/app/skills/builtin/workflow_executor/__init__.py +4 -0
  287. control_plane_api/app/skills/builtin/workflow_executor/skill.py +469 -0
  288. control_plane_api/app/skills/business_intelligence.py +189 -0
  289. control_plane_api/app/skills/config.py +63 -0
  290. control_plane_api/app/skills/loaders/__init__.py +14 -0
  291. control_plane_api/app/skills/loaders/base.py +73 -0
  292. control_plane_api/app/skills/loaders/filesystem_loader.py +199 -0
  293. control_plane_api/app/skills/registry.py +125 -0
  294. control_plane_api/app/utils/helpers.py +12 -0
  295. control_plane_api/app/utils/workflow_executor.py +354 -0
  296. control_plane_api/app/workflows/__init__.py +11 -0
  297. control_plane_api/app/workflows/agent_execution.py +520 -0
  298. control_plane_api/app/workflows/agent_execution_with_skills.py +223 -0
  299. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  300. control_plane_api/app/workflows/plan_generation.py +254 -0
  301. control_plane_api/app/workflows/team_execution.py +442 -0
  302. control_plane_api/scripts/seed_models.py +240 -0
  303. control_plane_api/scripts/validate_existing_tool_names.py +492 -0
  304. control_plane_api/shared/__init__.py +8 -0
  305. control_plane_api/shared/version.py +17 -0
  306. control_plane_api/test_deduplication.py +274 -0
  307. control_plane_api/test_executor_deduplication_e2e.py +309 -0
  308. control_plane_api/test_job_execution_e2e.py +283 -0
  309. control_plane_api/test_real_integration.py +193 -0
  310. control_plane_api/version.py +38 -0
  311. control_plane_api/worker/__init__.py +0 -0
  312. control_plane_api/worker/activities/__init__.py +0 -0
  313. control_plane_api/worker/activities/agent_activities.py +1585 -0
  314. control_plane_api/worker/activities/approval_activities.py +234 -0
  315. control_plane_api/worker/activities/job_activities.py +199 -0
  316. control_plane_api/worker/activities/runtime_activities.py +1167 -0
  317. control_plane_api/worker/activities/skill_activities.py +282 -0
  318. control_plane_api/worker/activities/team_activities.py +479 -0
  319. control_plane_api/worker/agent_runtime_server.py +370 -0
  320. control_plane_api/worker/binary_manager.py +333 -0
  321. control_plane_api/worker/config/__init__.py +31 -0
  322. control_plane_api/worker/config/worker_config.py +273 -0
  323. control_plane_api/worker/control_plane_client.py +1491 -0
  324. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  325. control_plane_api/worker/health_monitor.py +159 -0
  326. control_plane_api/worker/metrics.py +237 -0
  327. control_plane_api/worker/models/__init__.py +1 -0
  328. control_plane_api/worker/models/error_events.py +105 -0
  329. control_plane_api/worker/models/inputs.py +89 -0
  330. control_plane_api/worker/runtimes/__init__.py +35 -0
  331. control_plane_api/worker/runtimes/agent_runtime/runtime.py +485 -0
  332. control_plane_api/worker/runtimes/agno/__init__.py +34 -0
  333. control_plane_api/worker/runtimes/agno/config.py +248 -0
  334. control_plane_api/worker/runtimes/agno/hooks.py +385 -0
  335. control_plane_api/worker/runtimes/agno/mcp_builder.py +195 -0
  336. control_plane_api/worker/runtimes/agno/runtime.py +1063 -0
  337. control_plane_api/worker/runtimes/agno/utils.py +163 -0
  338. control_plane_api/worker/runtimes/base.py +979 -0
  339. control_plane_api/worker/runtimes/claude_code/__init__.py +38 -0
  340. control_plane_api/worker/runtimes/claude_code/cleanup.py +184 -0
  341. control_plane_api/worker/runtimes/claude_code/client_pool.py +529 -0
  342. control_plane_api/worker/runtimes/claude_code/config.py +829 -0
  343. control_plane_api/worker/runtimes/claude_code/hooks.py +482 -0
  344. control_plane_api/worker/runtimes/claude_code/litellm_proxy.py +1702 -0
  345. control_plane_api/worker/runtimes/claude_code/mcp_builder.py +467 -0
  346. control_plane_api/worker/runtimes/claude_code/mcp_discovery.py +558 -0
  347. control_plane_api/worker/runtimes/claude_code/runtime.py +1546 -0
  348. control_plane_api/worker/runtimes/claude_code/tool_mapper.py +403 -0
  349. control_plane_api/worker/runtimes/claude_code/utils.py +149 -0
  350. control_plane_api/worker/runtimes/factory.py +173 -0
  351. control_plane_api/worker/runtimes/model_utils.py +107 -0
  352. control_plane_api/worker/runtimes/validation.py +93 -0
  353. control_plane_api/worker/services/__init__.py +1 -0
  354. control_plane_api/worker/services/agent_communication_tools.py +908 -0
  355. control_plane_api/worker/services/agent_executor.py +485 -0
  356. control_plane_api/worker/services/agent_executor_v2.py +793 -0
  357. control_plane_api/worker/services/analytics_collector.py +457 -0
  358. control_plane_api/worker/services/analytics_service.py +464 -0
  359. control_plane_api/worker/services/approval_tools.py +310 -0
  360. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  361. control_plane_api/worker/services/cancellation_manager.py +177 -0
  362. control_plane_api/worker/services/code_ingestion_tools.py +465 -0
  363. control_plane_api/worker/services/contextual_awareness_tools.py +405 -0
  364. control_plane_api/worker/services/data_visualization.py +834 -0
  365. control_plane_api/worker/services/event_publisher.py +531 -0
  366. control_plane_api/worker/services/jira_tools.py +257 -0
  367. control_plane_api/worker/services/remote_filesystem_tools.py +498 -0
  368. control_plane_api/worker/services/runtime_analytics.py +328 -0
  369. control_plane_api/worker/services/session_service.py +365 -0
  370. control_plane_api/worker/services/skill_context_enhancement.py +181 -0
  371. control_plane_api/worker/services/skill_factory.py +471 -0
  372. control_plane_api/worker/services/system_prompt_enhancement.py +410 -0
  373. control_plane_api/worker/services/team_executor.py +715 -0
  374. control_plane_api/worker/services/team_executor_v2.py +1866 -0
  375. control_plane_api/worker/services/tool_enforcement.py +254 -0
  376. control_plane_api/worker/services/workflow_executor/__init__.py +52 -0
  377. control_plane_api/worker/services/workflow_executor/event_processor.py +287 -0
  378. control_plane_api/worker/services/workflow_executor/event_publisher.py +210 -0
  379. control_plane_api/worker/services/workflow_executor/executors/__init__.py +15 -0
  380. control_plane_api/worker/services/workflow_executor/executors/base.py +270 -0
  381. control_plane_api/worker/services/workflow_executor/executors/json_executor.py +50 -0
  382. control_plane_api/worker/services/workflow_executor/executors/python_executor.py +50 -0
  383. control_plane_api/worker/services/workflow_executor/models.py +142 -0
  384. control_plane_api/worker/services/workflow_executor_tools.py +1748 -0
  385. control_plane_api/worker/skills/__init__.py +12 -0
  386. control_plane_api/worker/skills/builtin/context_graph_search/README.md +213 -0
  387. control_plane_api/worker/skills/builtin/context_graph_search/__init__.py +5 -0
  388. control_plane_api/worker/skills/builtin/context_graph_search/agno_impl.py +808 -0
  389. control_plane_api/worker/skills/builtin/context_graph_search/skill.yaml +67 -0
  390. control_plane_api/worker/skills/builtin/contextual_awareness/__init__.py +4 -0
  391. control_plane_api/worker/skills/builtin/contextual_awareness/agno_impl.py +62 -0
  392. control_plane_api/worker/skills/builtin/data_visualization/agno_impl.py +18 -0
  393. control_plane_api/worker/skills/builtin/data_visualization/skill.yaml +84 -0
  394. control_plane_api/worker/skills/builtin/docker/agno_impl.py +65 -0
  395. control_plane_api/worker/skills/builtin/docker/skill.yaml +60 -0
  396. control_plane_api/worker/skills/builtin/file_generation/agno_impl.py +47 -0
  397. control_plane_api/worker/skills/builtin/file_generation/skill.yaml +64 -0
  398. control_plane_api/worker/skills/builtin/file_system/agno_impl.py +32 -0
  399. control_plane_api/worker/skills/builtin/file_system/skill.yaml +54 -0
  400. control_plane_api/worker/skills/builtin/knowledge_api/__init__.py +4 -0
  401. control_plane_api/worker/skills/builtin/knowledge_api/agno_impl.py +50 -0
  402. control_plane_api/worker/skills/builtin/knowledge_api/skill.yaml +66 -0
  403. control_plane_api/worker/skills/builtin/python/agno_impl.py +25 -0
  404. control_plane_api/worker/skills/builtin/python/skill.yaml +60 -0
  405. control_plane_api/worker/skills/builtin/schema_fix_mixin.py +260 -0
  406. control_plane_api/worker/skills/builtin/shell/agno_impl.py +31 -0
  407. control_plane_api/worker/skills/builtin/shell/skill.yaml +60 -0
  408. control_plane_api/worker/skills/builtin/slack/__init__.py +3 -0
  409. control_plane_api/worker/skills/builtin/slack/agno_impl.py +1282 -0
  410. control_plane_api/worker/skills/builtin/slack/skill.yaml +276 -0
  411. control_plane_api/worker/skills/builtin/workflow_executor/agno_impl.py +62 -0
  412. control_plane_api/worker/skills/builtin/workflow_executor/skill.yaml +79 -0
  413. control_plane_api/worker/skills/loaders/__init__.py +5 -0
  414. control_plane_api/worker/skills/loaders/base.py +23 -0
  415. control_plane_api/worker/skills/loaders/filesystem_loader.py +357 -0
  416. control_plane_api/worker/skills/registry.py +208 -0
  417. control_plane_api/worker/tests/__init__.py +1 -0
  418. control_plane_api/worker/tests/conftest.py +12 -0
  419. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  420. control_plane_api/worker/tests/e2e/test_context_graph_real_api.py +338 -0
  421. control_plane_api/worker/tests/e2e/test_context_graph_templates_e2e.py +523 -0
  422. control_plane_api/worker/tests/e2e/test_enforcement_e2e.py +344 -0
  423. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  424. control_plane_api/worker/tests/e2e/test_single_execution_mode.py +656 -0
  425. control_plane_api/worker/tests/integration/__init__.py +0 -0
  426. control_plane_api/worker/tests/integration/test_builtin_skills_fixes.py +245 -0
  427. control_plane_api/worker/tests/integration/test_context_graph_search_integration.py +365 -0
  428. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  429. control_plane_api/worker/tests/integration/test_hook_enforcement_integration.py +579 -0
  430. control_plane_api/worker/tests/integration/test_scheduled_job_workflow.py +237 -0
  431. control_plane_api/worker/tests/integration/test_system_prompt_enhancement_integration.py +343 -0
  432. control_plane_api/worker/tests/unit/__init__.py +0 -0
  433. control_plane_api/worker/tests/unit/test_builtin_skill_autoload.py +396 -0
  434. control_plane_api/worker/tests/unit/test_context_graph_search.py +450 -0
  435. control_plane_api/worker/tests/unit/test_context_graph_templates.py +403 -0
  436. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  437. control_plane_api/worker/tests/unit/test_control_plane_client_jobs.py +345 -0
  438. control_plane_api/worker/tests/unit/test_job_activities.py +353 -0
  439. control_plane_api/worker/tests/unit/test_skill_context_enhancement.py +321 -0
  440. control_plane_api/worker/tests/unit/test_system_prompt_enhancement.py +415 -0
  441. control_plane_api/worker/tests/unit/test_tool_enforcement.py +324 -0
  442. control_plane_api/worker/utils/__init__.py +1 -0
  443. control_plane_api/worker/utils/chunk_batcher.py +330 -0
  444. control_plane_api/worker/utils/environment.py +65 -0
  445. control_plane_api/worker/utils/error_publisher.py +260 -0
  446. control_plane_api/worker/utils/event_batcher.py +256 -0
  447. control_plane_api/worker/utils/logging_config.py +335 -0
  448. control_plane_api/worker/utils/logging_helper.py +326 -0
  449. control_plane_api/worker/utils/parameter_validator.py +120 -0
  450. control_plane_api/worker/utils/retry_utils.py +60 -0
  451. control_plane_api/worker/utils/streaming_utils.py +665 -0
  452. control_plane_api/worker/utils/tool_validation.py +332 -0
  453. control_plane_api/worker/utils/workspace_manager.py +163 -0
  454. control_plane_api/worker/websocket_client.py +393 -0
  455. control_plane_api/worker/worker.py +1297 -0
  456. control_plane_api/worker/workflows/__init__.py +0 -0
  457. control_plane_api/worker/workflows/agent_execution.py +909 -0
  458. control_plane_api/worker/workflows/scheduled_job_wrapper.py +332 -0
  459. control_plane_api/worker/workflows/team_execution.py +611 -0
  460. kubiya_control_plane_api-0.9.15.dist-info/METADATA +354 -0
  461. kubiya_control_plane_api-0.9.15.dist-info/RECORD +479 -0
  462. kubiya_control_plane_api-0.9.15.dist-info/WHEEL +5 -0
  463. kubiya_control_plane_api-0.9.15.dist-info/entry_points.txt +5 -0
  464. kubiya_control_plane_api-0.9.15.dist-info/licenses/LICENSE +676 -0
  465. kubiya_control_plane_api-0.9.15.dist-info/top_level.txt +3 -0
  466. scripts/__init__.py +1 -0
  467. scripts/migrations.py +39 -0
  468. scripts/seed_worker_queues.py +128 -0
  469. scripts/setup_agent_runtime.py +142 -0
  470. worker_internal/__init__.py +1 -0
  471. worker_internal/planner/__init__.py +1 -0
  472. worker_internal/planner/activities.py +1499 -0
  473. worker_internal/planner/agent_tools.py +197 -0
  474. worker_internal/planner/event_models.py +148 -0
  475. worker_internal/planner/event_publisher.py +67 -0
  476. worker_internal/planner/models.py +199 -0
  477. worker_internal/planner/retry_logic.py +134 -0
  478. worker_internal/planner/worker.py +300 -0
  479. 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