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,388 @@
1
+ """
2
+ Execution context tools for state transition decisions
3
+
4
+ Provides tools for the AI agent to gather context about executions,
5
+ turns, conversations, and tool usage patterns.
6
+ """
7
+
8
+ from typing import Optional, Dict, Any, List
9
+ import structlog
10
+ import httpx
11
+ from agno.tools.toolkit import Toolkit
12
+
13
+ logger = structlog.get_logger()
14
+
15
+
16
+ class ExecutionContextTools(Toolkit):
17
+ """
18
+ Tools for gathering execution context to inform state transition decisions
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ base_url: str = "http://localhost:8000",
24
+ organization_id: Optional[str] = None,
25
+ timeout: int = 30,
26
+ ):
27
+ """
28
+ Initialize execution context tools
29
+
30
+ Args:
31
+ base_url: Base URL for the control plane API
32
+ organization_id: Organization context for filtering
33
+ timeout: HTTP request timeout in seconds
34
+ """
35
+ super().__init__(name="execution_context_tools")
36
+ self.base_url = base_url.rstrip("/")
37
+ self.organization_id = organization_id
38
+ self.timeout = timeout
39
+ self._client: Optional[httpx.AsyncClient] = None
40
+
41
+ async def _get_client(self) -> httpx.AsyncClient:
42
+ """Get or create HTTP client"""
43
+ if self._client is None:
44
+ self._client = httpx.AsyncClient(
45
+ base_url=self.base_url,
46
+ timeout=self.timeout,
47
+ )
48
+ return self._client
49
+
50
+ async def _make_request(
51
+ self,
52
+ method: str,
53
+ endpoint: str,
54
+ params: Optional[Dict[str, Any]] = None,
55
+ json: Optional[Dict[str, Any]] = None,
56
+ ) -> Dict[str, Any]:
57
+ """Make an HTTP request to the control plane API"""
58
+ client = await self._get_client()
59
+ response = await client.request(
60
+ method=method,
61
+ url=endpoint,
62
+ params=params,
63
+ json=json,
64
+ )
65
+ response.raise_for_status()
66
+ return response.json()
67
+
68
+ async def get_execution_details(self, execution_id: str) -> str:
69
+ """
70
+ Get detailed information about an execution
71
+
72
+ Args:
73
+ execution_id: The execution ID to fetch
74
+
75
+ Returns:
76
+ Formatted string with execution details including status, metadata, timestamps
77
+ """
78
+ try:
79
+ logger.info("fetching_execution_details", execution_id=execution_id)
80
+
81
+ result = await self._make_request(
82
+ method="GET",
83
+ endpoint=f"/api/v1/executions/{execution_id}",
84
+ )
85
+
86
+ execution = result.get("execution", result)
87
+
88
+ # Format key information
89
+ output = [
90
+ f"Execution Details (ID: {execution_id}):",
91
+ f" Status: {execution.get('status', 'unknown')}",
92
+ f" Prompt: {execution.get('prompt', 'N/A')[:200]}...",
93
+ f" Started: {execution.get('started_at', 'N/A')}",
94
+ f" Completed: {execution.get('completed_at', 'N/A')}",
95
+ f" Response Preview: {execution.get('response', '')[:300]}...",
96
+ f" Error Message: {execution.get('error_message', 'None')}",
97
+ f" Agent ID: {execution.get('agent_id', 'N/A')}",
98
+ f" Team ID: {execution.get('team_id', 'N/A')}",
99
+ ]
100
+
101
+ # Add metadata if available
102
+ if execution.get('execution_metadata'):
103
+ output.append(f" Metadata: {execution['execution_metadata']}")
104
+
105
+ return "\n".join(output)
106
+
107
+ except Exception as e:
108
+ logger.error(
109
+ "get_execution_details_failed",
110
+ execution_id=execution_id,
111
+ error=str(e),
112
+ )
113
+ return f"Error fetching execution details: {str(e)}"
114
+
115
+ async def get_recent_turns(self, execution_id: str, limit: int = 5) -> str:
116
+ """
117
+ Get recent turns for an execution with metrics
118
+
119
+ Args:
120
+ execution_id: The execution ID
121
+ limit: Maximum number of recent turns to fetch (default: 5)
122
+
123
+ Returns:
124
+ Formatted string with turn information including tokens, costs, finish reasons, tools called
125
+ """
126
+ try:
127
+ logger.info(
128
+ "fetching_recent_turns",
129
+ execution_id=execution_id,
130
+ limit=limit,
131
+ )
132
+
133
+ result = await self._make_request(
134
+ method="GET",
135
+ endpoint="/api/v1/analytics/turns",
136
+ params={"execution_id": execution_id, "limit": limit},
137
+ )
138
+
139
+ turns = result.get("turns", [])
140
+
141
+ if not turns:
142
+ return f"No turns found for execution {execution_id}"
143
+
144
+ output = [f"Recent Turns ({len(turns)} total, showing last {min(len(turns), limit)}):"]
145
+
146
+ for turn in turns[-limit:]:
147
+ turn_num = turn.get("turn_number", "?")
148
+ output.append(f"\n--- Turn {turn_num} ---")
149
+ output.append(f" Model: {turn.get('model', 'N/A')}")
150
+ output.append(f" Finish Reason: {turn.get('finish_reason', 'N/A')}")
151
+ output.append(f" Duration: {turn.get('duration_ms', 0)}ms")
152
+ output.append(f" Tokens (in/out): {turn.get('input_tokens', 0)}/{turn.get('output_tokens', 0)}")
153
+ output.append(f" Tools Called: {turn.get('tools_called_count', 0)}")
154
+
155
+ if turn.get('tools_called_names'):
156
+ output.append(f" Tool Names: {', '.join(turn['tools_called_names'])}")
157
+
158
+ if turn.get('response_preview'):
159
+ output.append(f" Response: {turn['response_preview'][:150]}...")
160
+
161
+ if turn.get('error_message'):
162
+ output.append(f" Error: {turn['error_message'][:200]}")
163
+
164
+ return "\n".join(output)
165
+
166
+ except Exception as e:
167
+ logger.error(
168
+ "get_recent_turns_failed",
169
+ execution_id=execution_id,
170
+ error=str(e),
171
+ )
172
+ return f"Error fetching recent turns: {str(e)}"
173
+
174
+ async def get_conversation_messages(self, execution_id: str, limit: int = 10) -> str:
175
+ """
176
+ Get conversation message history for an execution
177
+
178
+ Args:
179
+ execution_id: The execution ID
180
+ limit: Maximum number of recent messages (default: 10)
181
+
182
+ Returns:
183
+ Formatted string with conversation messages
184
+ """
185
+ try:
186
+ logger.info(
187
+ "fetching_conversation_messages",
188
+ execution_id=execution_id,
189
+ limit=limit,
190
+ )
191
+
192
+ result = await self._make_request(
193
+ method="GET",
194
+ endpoint=f"/api/v1/executions/{execution_id}/messages",
195
+ params={"limit": limit},
196
+ )
197
+
198
+ messages = result.get("messages", [])
199
+
200
+ if not messages:
201
+ return f"No conversation messages found for execution {execution_id}"
202
+
203
+ output = [f"Conversation Messages ({len(messages)} total, showing last {limit}):"]
204
+
205
+ for idx, msg in enumerate(messages[-limit:], 1):
206
+ role = msg.get("role", "unknown")
207
+ content = msg.get("content", "")
208
+ timestamp = msg.get("timestamp", "N/A")
209
+
210
+ output.append(f"\n{idx}. [{role.upper()}] at {timestamp}:")
211
+ output.append(f" {content[:300]}...")
212
+
213
+ return "\n".join(output)
214
+
215
+ except Exception as e:
216
+ logger.error(
217
+ "get_conversation_messages_failed",
218
+ execution_id=execution_id,
219
+ error=str(e),
220
+ )
221
+ # Return empty conversation if endpoint doesn't exist yet
222
+ return f"Conversation history not available: {str(e)}"
223
+
224
+ async def get_tool_call_patterns(self, execution_id: str) -> str:
225
+ """
226
+ Analyze tool call patterns for an execution
227
+
228
+ Args:
229
+ execution_id: The execution ID
230
+
231
+ Returns:
232
+ Formatted string with tool usage statistics and patterns
233
+ """
234
+ try:
235
+ logger.info(
236
+ "analyzing_tool_call_patterns",
237
+ execution_id=execution_id,
238
+ )
239
+
240
+ result = await self._make_request(
241
+ method="GET",
242
+ endpoint="/api/v1/analytics/tool-calls",
243
+ params={"execution_id": execution_id},
244
+ )
245
+
246
+ tool_calls = result.get("tool_calls", [])
247
+
248
+ if not tool_calls:
249
+ return f"No tool calls found for execution {execution_id}"
250
+
251
+ # Analyze patterns
252
+ total_calls = len(tool_calls)
253
+ successful_calls = sum(1 for tc in tool_calls if tc.get("success", False))
254
+ failed_calls = total_calls - successful_calls
255
+
256
+ # Count by tool name
257
+ tool_counts: Dict[str, int] = {}
258
+ tool_failures: Dict[str, int] = {}
259
+
260
+ for tc in tool_calls:
261
+ tool_name = tc.get("tool_name", "unknown")
262
+ tool_counts[tool_name] = tool_counts.get(tool_name, 0) + 1
263
+ if not tc.get("success", False):
264
+ tool_failures[tool_name] = tool_failures.get(tool_name, 0) + 1
265
+
266
+ output = [
267
+ f"Tool Call Analysis:",
268
+ f" Total Calls: {total_calls}",
269
+ f" Successful: {successful_calls} ({100*successful_calls//total_calls if total_calls > 0 else 0}%)",
270
+ f" Failed: {failed_calls} ({100*failed_calls//total_calls if total_calls > 0 else 0}%)",
271
+ f"\nMost Used Tools:",
272
+ ]
273
+
274
+ for tool_name, count in sorted(tool_counts.items(), key=lambda x: x[1], reverse=True)[:5]:
275
+ failures = tool_failures.get(tool_name, 0)
276
+ output.append(f" - {tool_name}: {count} calls ({failures} failed)")
277
+
278
+ return "\n".join(output)
279
+
280
+ except Exception as e:
281
+ logger.error(
282
+ "get_tool_call_patterns_failed",
283
+ execution_id=execution_id,
284
+ error=str(e),
285
+ )
286
+ return f"Tool call analysis not available: {str(e)}"
287
+
288
+ async def check_error_recoverability(self, execution_id: str, error_message: Optional[str]) -> str:
289
+ """
290
+ Determine if an error is recoverable or requires user intervention
291
+
292
+ Args:
293
+ execution_id: The execution ID
294
+ error_message: The error message to analyze
295
+
296
+ Returns:
297
+ Assessment of error recoverability
298
+ """
299
+ try:
300
+ if not error_message:
301
+ return "No error detected - execution appears healthy"
302
+
303
+ error_lower = error_message.lower()
304
+
305
+ # Unrecoverable errors (need user intervention)
306
+ unrecoverable_patterns = [
307
+ "authentication failed",
308
+ "unauthorized",
309
+ "forbidden",
310
+ "access denied",
311
+ "invalid credentials",
312
+ "permission denied",
313
+ "quota exceeded",
314
+ "rate limit exceeded",
315
+ "timeout",
316
+ "network unreachable",
317
+ "connection refused",
318
+ "not found",
319
+ "does not exist",
320
+ ]
321
+
322
+ # Recoverable errors (can retry)
323
+ recoverable_patterns = [
324
+ "temporary",
325
+ "retry",
326
+ "throttled",
327
+ "busy",
328
+ "unavailable",
329
+ ]
330
+
331
+ is_unrecoverable = any(pattern in error_lower for pattern in unrecoverable_patterns)
332
+ is_recoverable = any(pattern in error_lower for pattern in recoverable_patterns)
333
+
334
+ if is_unrecoverable:
335
+ return (
336
+ f"Error Assessment: UNRECOVERABLE\n"
337
+ f" Error: {error_message[:200]}\n"
338
+ f" Recommendation: Requires user intervention. Execution should be marked as FAILED."
339
+ )
340
+ elif is_recoverable:
341
+ return (
342
+ f"Error Assessment: RECOVERABLE\n"
343
+ f" Error: {error_message[:200]}\n"
344
+ f" Recommendation: Can be retried. Execution can continue or wait for retry."
345
+ )
346
+ else:
347
+ return (
348
+ f"Error Assessment: UNCERTAIN\n"
349
+ f" Error: {error_message[:200]}\n"
350
+ f" Recommendation: Use caution. Consider context and turn history to decide."
351
+ )
352
+
353
+ except Exception as e:
354
+ logger.error(
355
+ "check_error_recoverability_failed",
356
+ execution_id=execution_id,
357
+ error=str(e),
358
+ )
359
+ return f"Error recoverability check failed: {str(e)}"
360
+
361
+ async def get_current_state(self, execution_id: str) -> str:
362
+ """
363
+ Get the current state of an execution
364
+
365
+ Args:
366
+ execution_id: The execution ID
367
+
368
+ Returns:
369
+ Current execution state
370
+ """
371
+ try:
372
+ result = await self._make_request(
373
+ method="GET",
374
+ endpoint=f"/api/v1/executions/{execution_id}",
375
+ )
376
+
377
+ execution = result.get("execution", result)
378
+ current_status = execution.get("status", "unknown")
379
+
380
+ return f"Current State: {current_status}"
381
+
382
+ except Exception as e:
383
+ logger.error(
384
+ "get_current_state_failed",
385
+ execution_id=execution_id,
386
+ error=str(e),
387
+ )
388
+ return f"Unable to determine current state: {str(e)}"
@@ -0,0 +1,20 @@
1
+ """
2
+ Storage provider module for remote filesystem support.
3
+
4
+ Provides abstract base classes and implementations for different storage providers:
5
+ - Vercel Blob Storage
6
+ - AWS S3 (future)
7
+ - Azure Blob Storage (future)
8
+ - Google Cloud Storage (future)
9
+ """
10
+
11
+ from .base_provider import StorageProvider, StorageFile, UploadResult
12
+ from .provider_factory import StorageProviderFactory, get_storage_provider
13
+
14
+ __all__ = [
15
+ "StorageProvider",
16
+ "StorageFile",
17
+ "UploadResult",
18
+ "StorageProviderFactory",
19
+ "get_storage_provider",
20
+ ]
@@ -0,0 +1,274 @@
1
+ """
2
+ Abstract base class for storage providers.
3
+
4
+ All storage provider implementations must inherit from StorageProvider
5
+ and implement all abstract methods to ensure consistent behavior.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import BinaryIO, Dict, List, Optional, Tuple
10
+ from dataclasses import dataclass
11
+ from datetime import datetime
12
+
13
+
14
+ @dataclass
15
+ class StorageFile:
16
+ """
17
+ File metadata returned from storage operations.
18
+
19
+ Attributes:
20
+ file_id: Provider-specific unique identifier
21
+ file_name: Original filename
22
+ file_path: Organization-scoped virtual path
23
+ content_type: MIME type of the file
24
+ file_size_bytes: File size in bytes
25
+ checksum: File checksum (MD5 or SHA256)
26
+ created_at: Creation timestamp
27
+ provider_metadata: Provider-specific additional metadata
28
+ """
29
+ file_id: str
30
+ file_name: str
31
+ file_path: str
32
+ content_type: str
33
+ file_size_bytes: int
34
+ checksum: Optional[str] = None
35
+ created_at: Optional[datetime] = None
36
+ provider_metadata: Optional[Dict[str, any]] = None
37
+
38
+
39
+ @dataclass
40
+ class UploadResult:
41
+ """
42
+ Result of file upload operation.
43
+
44
+ Attributes:
45
+ file_id: Provider-specific unique identifier for the uploaded file
46
+ url: Access URL for the file (may be provider-specific)
47
+ file_size_bytes: Actual size of uploaded file
48
+ checksum: File checksum for integrity verification
49
+ provider_metadata: Provider-specific metadata from upload
50
+ """
51
+ file_id: str
52
+ url: str
53
+ file_size_bytes: int
54
+ checksum: str
55
+ provider_metadata: Dict[str, any]
56
+
57
+
58
+ class StorageProvider(ABC):
59
+ """
60
+ Abstract base class for storage providers.
61
+
62
+ All providers must implement this interface to ensure
63
+ consistent behavior across different storage backends.
64
+
65
+ Each provider should handle:
66
+ - Organization-level isolation (typically via path prefixes or separate buckets)
67
+ - Authentication with the underlying storage service
68
+ - Error handling and retry logic
69
+ - Streaming support for large files
70
+ """
71
+
72
+ @abstractmethod
73
+ async def upload(
74
+ self,
75
+ file_content: BinaryIO,
76
+ file_name: str,
77
+ file_path: str,
78
+ content_type: str,
79
+ organization_id: str,
80
+ metadata: Optional[Dict[str, str]] = None
81
+ ) -> UploadResult:
82
+ """
83
+ Upload a file to storage.
84
+
85
+ Args:
86
+ file_content: Binary file content (file-like object)
87
+ file_name: Original filename
88
+ file_path: Organization-scoped virtual path (e.g., /folder/file.txt)
89
+ content_type: MIME type of the file
90
+ organization_id: Organization ID for namespacing
91
+ metadata: Optional additional metadata to store with the file
92
+
93
+ Returns:
94
+ UploadResult with file ID, URL, and metadata
95
+
96
+ Raises:
97
+ Exception: If upload fails (provider-specific exceptions)
98
+ """
99
+ pass
100
+
101
+ @abstractmethod
102
+ async def download(
103
+ self,
104
+ file_id: str,
105
+ organization_id: str
106
+ ) -> Tuple[BinaryIO, str, int]:
107
+ """
108
+ Download file content.
109
+
110
+ Args:
111
+ file_id: Provider-specific file identifier
112
+ organization_id: Organization ID for validation
113
+
114
+ Returns:
115
+ Tuple of (file_stream, content_type, file_size_bytes)
116
+
117
+ Raises:
118
+ FileNotFoundError: If file doesn't exist
119
+ Exception: If download fails
120
+ """
121
+ pass
122
+
123
+ @abstractmethod
124
+ async def delete(
125
+ self,
126
+ file_id: str,
127
+ organization_id: str
128
+ ) -> bool:
129
+ """
130
+ Delete a file from storage.
131
+
132
+ Args:
133
+ file_id: Provider-specific file identifier
134
+ organization_id: Organization ID for validation
135
+
136
+ Returns:
137
+ True if deletion was successful, False otherwise
138
+
139
+ Raises:
140
+ Exception: If deletion fails
141
+ """
142
+ pass
143
+
144
+ @abstractmethod
145
+ async def list_files(
146
+ self,
147
+ organization_id: str,
148
+ prefix: Optional[str] = None,
149
+ limit: int = 100,
150
+ cursor: Optional[str] = None
151
+ ) -> Tuple[List[StorageFile], Optional[str]]:
152
+ """
153
+ List files with pagination support.
154
+
155
+ Args:
156
+ organization_id: Organization ID for filtering
157
+ prefix: Optional path prefix filter (e.g., /folder/)
158
+ limit: Maximum number of files to return
159
+ cursor: Pagination cursor from previous request
160
+
161
+ Returns:
162
+ Tuple of (files_list, next_cursor)
163
+ next_cursor is None if no more files
164
+
165
+ Raises:
166
+ Exception: If listing fails
167
+ """
168
+ pass
169
+
170
+ @abstractmethod
171
+ async def get_metadata(
172
+ self,
173
+ file_id: str,
174
+ organization_id: str
175
+ ) -> StorageFile:
176
+ """
177
+ Get file metadata without downloading content.
178
+
179
+ Args:
180
+ file_id: Provider-specific file identifier
181
+ organization_id: Organization ID for validation
182
+
183
+ Returns:
184
+ StorageFile with metadata
185
+
186
+ Raises:
187
+ FileNotFoundError: If file doesn't exist
188
+ Exception: If metadata retrieval fails
189
+ """
190
+ pass
191
+
192
+ @abstractmethod
193
+ async def copy(
194
+ self,
195
+ source_file_id: str,
196
+ destination_path: str,
197
+ organization_id: str
198
+ ) -> UploadResult:
199
+ """
200
+ Copy file to new location within same organization.
201
+
202
+ Args:
203
+ source_file_id: Source file identifier
204
+ destination_path: Destination virtual path
205
+ organization_id: Organization ID
206
+
207
+ Returns:
208
+ UploadResult for the new file
209
+
210
+ Raises:
211
+ FileNotFoundError: If source file doesn't exist
212
+ Exception: If copy fails
213
+ """
214
+ pass
215
+
216
+ @abstractmethod
217
+ async def move(
218
+ self,
219
+ file_id: str,
220
+ new_path: str,
221
+ organization_id: str
222
+ ) -> StorageFile:
223
+ """
224
+ Move/rename file to new location.
225
+
226
+ Args:
227
+ file_id: File identifier to move
228
+ new_path: New virtual path
229
+ organization_id: Organization ID
230
+
231
+ Returns:
232
+ StorageFile with updated metadata
233
+
234
+ Raises:
235
+ FileNotFoundError: If file doesn't exist
236
+ Exception: If move fails
237
+ """
238
+ pass
239
+
240
+ @abstractmethod
241
+ def get_provider_name(self) -> str:
242
+ """
243
+ Return provider name identifier.
244
+
245
+ Returns:
246
+ Provider name (e.g., 'vercel_blob', 's3', 'azure_blob')
247
+ """
248
+ pass
249
+
250
+ async def exists(
251
+ self,
252
+ file_id: str,
253
+ organization_id: str
254
+ ) -> bool:
255
+ """
256
+ Check if a file exists.
257
+
258
+ Default implementation uses get_metadata, but providers
259
+ can override with more efficient implementation.
260
+
261
+ Args:
262
+ file_id: File identifier
263
+ organization_id: Organization ID
264
+
265
+ Returns:
266
+ True if file exists, False otherwise
267
+ """
268
+ try:
269
+ await self.get_metadata(file_id, organization_id)
270
+ return True
271
+ except FileNotFoundError:
272
+ return False
273
+ except Exception:
274
+ return False