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,490 @@
1
+ """
2
+ Custom Integrations Router
3
+
4
+ API endpoints for managing custom user-defined integrations with:
5
+ - Environment variables
6
+ - Secrets (vault references)
7
+ - Files (configs, certificates)
8
+ - Contextual prompts
9
+ """
10
+ from fastapi import APIRouter, Depends, HTTPException, Query
11
+ from typing import List, Optional, Dict, Any
12
+ from sqlalchemy.orm import Session
13
+ from pydantic import BaseModel, Field
14
+ import structlog
15
+
16
+ from control_plane_api.app.database import get_db
17
+ from control_plane_api.app.middleware.auth import get_current_organization
18
+ from control_plane_api.app.models.custom_integration import CustomIntegration, CustomIntegrationStatus
19
+
20
+ logger = structlog.get_logger()
21
+
22
+ router = APIRouter(prefix="/custom-integrations", tags=["custom-integrations"])
23
+
24
+
25
+ # Pydantic Models
26
+ class FileConfig(BaseModel):
27
+ """Configuration for a file to be written to workspace"""
28
+ path: str = Field(..., description="File path (e.g., ~/.postgresql/client.crt)")
29
+ content: Optional[str] = Field(None, description="File content (direct)")
30
+ secret_ref: Optional[str] = Field(None, description="Secret name to load content from")
31
+ mode: Optional[str] = Field("0644", description="File permissions (octal)")
32
+ description: Optional[str] = Field(None, description="Description of this file")
33
+
34
+ class Config:
35
+ json_schema_extra = {
36
+ "example": {
37
+ "path": "~/.postgresql/client.crt",
38
+ "content": "-----BEGIN CERTIFICATE-----\n...",
39
+ "mode": "0600",
40
+ "description": "PostgreSQL client certificate"
41
+ }
42
+ }
43
+
44
+
45
+ class ConnectionTest(BaseModel):
46
+ """Configuration for testing integration connectivity"""
47
+ enabled: bool = Field(True, description="Whether to test connection")
48
+ command: str = Field(..., description="Command to run for testing")
49
+ timeout: int = Field(5, description="Timeout in seconds")
50
+
51
+ class Config:
52
+ json_schema_extra = {
53
+ "example": {
54
+ "enabled": True,
55
+ "command": "pg_isready -h $DB_HOST -p $DB_PORT",
56
+ "timeout": 5
57
+ }
58
+ }
59
+
60
+
61
+ class CustomIntegrationConfig(BaseModel):
62
+ """Custom integration configuration"""
63
+ env_vars: Dict[str, str] = Field(default_factory=dict, description="Environment variables")
64
+ secrets: List[str] = Field(default_factory=list, description="Secret names from vault")
65
+ files: List[FileConfig] = Field(default_factory=list, description="Files to create")
66
+ context_prompt: Optional[str] = Field(None, description="Contextual guidance for agent")
67
+ connection_test: Optional[ConnectionTest] = Field(None, description="Connection test config")
68
+
69
+ class Config:
70
+ json_schema_extra = {
71
+ "example": {
72
+ "env_vars": {
73
+ "DB_HOST": "postgres.prod.example.com",
74
+ "DB_PORT": "5432",
75
+ "DB_NAME": "production"
76
+ },
77
+ "secrets": ["DB_PASSWORD", "DB_SSL_CERT"],
78
+ "files": [
79
+ {
80
+ "path": "~/.postgresql/client.crt",
81
+ "secret_ref": "POSTGRES_CLIENT_CERT",
82
+ "mode": "0600"
83
+ }
84
+ ],
85
+ "context_prompt": "Production PostgreSQL database. Use connection pooling."
86
+ }
87
+ }
88
+
89
+
90
+ class CreateCustomIntegrationRequest(BaseModel):
91
+ """Request to create a custom integration"""
92
+ name: str = Field(..., min_length=1, max_length=255, description="Integration name")
93
+ integration_type: str = Field(..., min_length=1, max_length=100, description="Integration type")
94
+ description: Optional[str] = Field(None, description="Description")
95
+ config: CustomIntegrationConfig = Field(..., description="Integration configuration")
96
+ tags: List[str] = Field(default_factory=list, description="Tags")
97
+
98
+ class Config:
99
+ json_schema_extra = {
100
+ "example": {
101
+ "name": "production-database",
102
+ "integration_type": "postgres",
103
+ "description": "Production PostgreSQL database",
104
+ "config": {
105
+ "env_vars": {
106
+ "DB_HOST": "postgres.prod.example.com",
107
+ "DB_PORT": "5432"
108
+ },
109
+ "secrets": ["DB_PASSWORD"],
110
+ "context_prompt": "Production database - handle with care"
111
+ },
112
+ "tags": ["production", "database"]
113
+ }
114
+ }
115
+
116
+
117
+ class UpdateCustomIntegrationRequest(BaseModel):
118
+ """Request to update a custom integration"""
119
+ name: Optional[str] = Field(None, min_length=1, max_length=255)
120
+ integration_type: Optional[str] = Field(None, min_length=1, max_length=100)
121
+ description: Optional[str] = None
122
+ config: Optional[CustomIntegrationConfig] = None
123
+ status: Optional[CustomIntegrationStatus] = None
124
+ tags: Optional[List[str]] = None
125
+
126
+
127
+ class CustomIntegrationResponse(BaseModel):
128
+ """Custom integration response"""
129
+ id: str
130
+ organization_id: str
131
+ name: str
132
+ integration_type: str
133
+ description: Optional[str]
134
+ status: str
135
+ config: Dict[str, Any]
136
+ tags: List[str]
137
+ created_at: str
138
+ updated_at: str
139
+ created_by: Optional[str]
140
+
141
+ class Config:
142
+ from_attributes = True
143
+
144
+
145
+ # API Endpoints
146
+
147
+ @router.get(
148
+ "",
149
+ response_model=List[CustomIntegrationResponse],
150
+ summary="List Custom Integrations",
151
+ description="""
152
+ List all custom integrations for the organization.
153
+
154
+ **Filtering Options:**
155
+ - `integration_type`: Filter by type (postgres, mongodb, redis, etc.)
156
+ - `status`: Filter by status (active, inactive, deleted)
157
+ - `tags`: Filter by tags (comma-separated, e.g., "production,database")
158
+
159
+ **Default Behavior:**
160
+ - Excludes deleted integrations unless explicitly filtered
161
+ - Results sorted by creation date (newest first)
162
+
163
+ **Example:**
164
+ ```
165
+ GET /api/v1/custom-integrations?integration_type=postgres&tags=production
166
+ ```
167
+ """,
168
+ responses={
169
+ 200: {
170
+ "description": "List of custom integrations",
171
+ "content": {
172
+ "application/json": {
173
+ "example": [
174
+ {
175
+ "id": "550e8400-e29b-41d4-a716-446655440000",
176
+ "organization_id": "org-123",
177
+ "name": "production-postgres",
178
+ "integration_type": "postgres",
179
+ "description": "Production PostgreSQL database",
180
+ "status": "active",
181
+ "config": {
182
+ "env_vars": {"DB_HOST": "postgres.example.com"},
183
+ "secrets": ["DB_PASSWORD"]
184
+ },
185
+ "tags": ["production", "database"],
186
+ "created_at": "2025-12-16T10:00:00Z",
187
+ "updated_at": "2025-12-16T10:00:00Z",
188
+ "created_by": "user-123"
189
+ }
190
+ ]
191
+ }
192
+ }
193
+ }
194
+ }
195
+ )
196
+ async def list_custom_integrations(
197
+ organization: dict = Depends(get_current_organization),
198
+ db: Session = Depends(get_db),
199
+ integration_type: Optional[str] = Query(None, description="Filter by integration type (e.g., postgres, mongodb, redis)"),
200
+ status: Optional[CustomIntegrationStatus] = Query(None, description="Filter by status (active, inactive, deleted)"),
201
+ tags: Optional[str] = Query(None, description="Filter by tags - comma-separated (e.g., production,database)"),
202
+ ):
203
+ """List all custom integrations for the organization with optional filtering."""
204
+ org_id = organization["id"]
205
+
206
+ query = db.query(CustomIntegration).filter(
207
+ CustomIntegration.organization_id == org_id
208
+ )
209
+
210
+ if integration_type:
211
+ query = query.filter(CustomIntegration.integration_type == integration_type)
212
+
213
+ if status:
214
+ query = query.filter(CustomIntegration.status == status)
215
+ else:
216
+ # By default, exclude deleted integrations
217
+ query = query.filter(CustomIntegration.status != CustomIntegrationStatus.DELETED)
218
+
219
+ if tags:
220
+ tag_list = [t.strip() for t in tags.split(",")]
221
+ # Filter integrations that have ANY of the specified tags
222
+ for tag in tag_list:
223
+ query = query.filter(CustomIntegration.tags.contains([tag]))
224
+
225
+ integrations = query.order_by(CustomIntegration.created_at.desc()).all()
226
+
227
+ logger.info(
228
+ "custom_integrations_listed",
229
+ org_id=org_id,
230
+ count=len(integrations),
231
+ filters={"type": integration_type, "status": status, "tags": tags}
232
+ )
233
+
234
+ return integrations
235
+
236
+
237
+ @router.get(
238
+ "/{integration_id}",
239
+ response_model=CustomIntegrationResponse,
240
+ summary="Get Custom Integration",
241
+ description="""
242
+ Retrieve detailed information about a specific custom integration.
243
+
244
+ Returns the complete configuration including:
245
+ - Environment variables
246
+ - Secret references
247
+ - File configurations
248
+ - Context prompt
249
+ - Tags and metadata
250
+
251
+ **Example:**
252
+ ```
253
+ GET /api/v1/custom-integrations/550e8400-e29b-41d4-a716-446655440000
254
+ ```
255
+ """,
256
+ responses={
257
+ 200: {"description": "Custom integration details"},
258
+ 404: {"description": "Integration not found"}
259
+ }
260
+ )
261
+ async def get_custom_integration(
262
+ integration_id: str,
263
+ organization: dict = Depends(get_current_organization),
264
+ db: Session = Depends(get_db),
265
+ ):
266
+ """Retrieve a specific custom integration by ID."""
267
+ org_id = organization["id"]
268
+
269
+ integration = db.query(CustomIntegration).filter(
270
+ CustomIntegration.id == integration_id,
271
+ CustomIntegration.organization_id == org_id
272
+ ).first()
273
+
274
+ if not integration:
275
+ raise HTTPException(status_code=404, detail="Custom integration not found")
276
+
277
+ logger.info(
278
+ "custom_integration_fetched",
279
+ org_id=org_id,
280
+ integration_id=integration_id[:8],
281
+ integration_name=integration.name
282
+ )
283
+
284
+ return integration
285
+
286
+
287
+ @router.post(
288
+ "",
289
+ response_model=CustomIntegrationResponse,
290
+ status_code=201,
291
+ summary="Create Custom Integration",
292
+ description="""
293
+ Create a new custom integration instance.
294
+
295
+ **Configuration Options:**
296
+ - `env_vars`: Key-value pairs for environment variables
297
+ - `secrets`: List of secret names to resolve from vault
298
+ - `files`: List of files to create in workspace
299
+ - `context_prompt`: Contextual guidance for AI agents
300
+ - `connection_test`: Optional command to test connectivity
301
+
302
+ **Name Requirements:**
303
+ - Must be unique within the organization
304
+ - Cannot be empty
305
+ - Alphanumeric and hyphens recommended
306
+
307
+ **Example Request:**
308
+ ```json
309
+ {
310
+ "name": "production-postgres",
311
+ "integration_type": "postgres",
312
+ "description": "Production PostgreSQL database",
313
+ "config": {
314
+ "env_vars": {
315
+ "DB_HOST": "postgres.prod.example.com",
316
+ "DB_PORT": "5432",
317
+ "DB_NAME": "production"
318
+ },
319
+ "secrets": ["DB_PASSWORD"],
320
+ "files": [
321
+ {
322
+ "path": "~/.postgresql/client.crt",
323
+ "secret_ref": "POSTGRES_CLIENT_CERT",
324
+ "mode": "0600"
325
+ }
326
+ ],
327
+ "context_prompt": "Production database - use connection pooling"
328
+ },
329
+ "tags": ["production", "database"]
330
+ }
331
+ ```
332
+ """,
333
+ responses={
334
+ 201: {"description": "Integration created successfully"},
335
+ 409: {"description": "Integration with this name already exists"},
336
+ 422: {"description": "Validation error"}
337
+ }
338
+ )
339
+ async def create_custom_integration(
340
+ request: CreateCustomIntegrationRequest,
341
+ organization: dict = Depends(get_current_organization),
342
+ db: Session = Depends(get_db),
343
+ ):
344
+ """Create a new custom integration for the organization."""
345
+ org_id = organization["id"]
346
+ user_id = organization.get("user_id")
347
+
348
+ # Check if integration with this name already exists
349
+ existing = db.query(CustomIntegration).filter(
350
+ CustomIntegration.organization_id == org_id,
351
+ CustomIntegration.name == request.name,
352
+ CustomIntegration.status != CustomIntegrationStatus.DELETED
353
+ ).first()
354
+
355
+ if existing:
356
+ raise HTTPException(
357
+ status_code=409,
358
+ detail=f"Custom integration with name '{request.name}' already exists"
359
+ )
360
+
361
+ # Create the integration
362
+ integration = CustomIntegration(
363
+ organization_id=org_id,
364
+ name=request.name,
365
+ integration_type=request.integration_type,
366
+ description=request.description,
367
+ config=request.config.model_dump(),
368
+ tags=request.tags,
369
+ status=CustomIntegrationStatus.ACTIVE,
370
+ created_by=user_id
371
+ )
372
+
373
+ db.add(integration)
374
+ db.commit()
375
+ db.refresh(integration)
376
+
377
+ logger.info(
378
+ "custom_integration_created",
379
+ org_id=org_id,
380
+ integration_id=str(integration.id)[:8],
381
+ integration_name=integration.name,
382
+ integration_type=integration.integration_type
383
+ )
384
+
385
+ return integration
386
+
387
+
388
+ @router.put("/{integration_id}", response_model=CustomIntegrationResponse)
389
+ async def update_custom_integration(
390
+ integration_id: str,
391
+ request: UpdateCustomIntegrationRequest,
392
+ organization: dict = Depends(get_current_organization),
393
+ db: Session = Depends(get_db),
394
+ ):
395
+ """Update a custom integration."""
396
+ org_id = organization["id"]
397
+
398
+ integration = db.query(CustomIntegration).filter(
399
+ CustomIntegration.id == integration_id,
400
+ CustomIntegration.organization_id == org_id
401
+ ).first()
402
+
403
+ if not integration:
404
+ raise HTTPException(status_code=404, detail="Custom integration not found")
405
+
406
+ # Update fields
407
+ if request.name is not None:
408
+ # Check name uniqueness
409
+ existing = db.query(CustomIntegration).filter(
410
+ CustomIntegration.organization_id == org_id,
411
+ CustomIntegration.name == request.name,
412
+ CustomIntegration.id != integration_id,
413
+ CustomIntegration.status != CustomIntegrationStatus.DELETED
414
+ ).first()
415
+ if existing:
416
+ raise HTTPException(
417
+ status_code=409,
418
+ detail=f"Custom integration with name '{request.name}' already exists"
419
+ )
420
+ integration.name = request.name
421
+
422
+ if request.integration_type is not None:
423
+ integration.integration_type = request.integration_type
424
+
425
+ if request.description is not None:
426
+ integration.description = request.description
427
+
428
+ if request.config is not None:
429
+ integration.config = request.config.model_dump()
430
+
431
+ if request.status is not None:
432
+ integration.status = request.status
433
+
434
+ if request.tags is not None:
435
+ integration.tags = request.tags
436
+
437
+ db.commit()
438
+ db.refresh(integration)
439
+
440
+ logger.info(
441
+ "custom_integration_updated",
442
+ org_id=org_id,
443
+ integration_id=integration_id[:8],
444
+ integration_name=integration.name
445
+ )
446
+
447
+ return integration
448
+
449
+
450
+ @router.delete("/{integration_id}", status_code=204)
451
+ async def delete_custom_integration(
452
+ integration_id: str,
453
+ organization: dict = Depends(get_current_organization),
454
+ db: Session = Depends(get_db),
455
+ hard_delete: bool = Query(False, description="Permanently delete (vs soft delete)"),
456
+ ):
457
+ """
458
+ Delete a custom integration.
459
+
460
+ By default, this is a soft delete (sets status to DELETED).
461
+ Use hard_delete=true to permanently remove from database.
462
+ """
463
+ org_id = organization["id"]
464
+
465
+ integration = db.query(CustomIntegration).filter(
466
+ CustomIntegration.id == integration_id,
467
+ CustomIntegration.organization_id == org_id
468
+ ).first()
469
+
470
+ if not integration:
471
+ raise HTTPException(status_code=404, detail="Custom integration not found")
472
+
473
+ if hard_delete:
474
+ db.delete(integration)
475
+ logger.info(
476
+ "custom_integration_hard_deleted",
477
+ org_id=org_id,
478
+ integration_id=integration_id[:8],
479
+ integration_name=integration.name
480
+ )
481
+ else:
482
+ integration.status = CustomIntegrationStatus.DELETED
483
+ logger.info(
484
+ "custom_integration_soft_deleted",
485
+ org_id=org_id,
486
+ integration_id=integration_id[:8],
487
+ integration_name=integration.name
488
+ )
489
+
490
+ db.commit()
@@ -0,0 +1,132 @@
1
+ """
2
+ Enforcer Proxy Router - Proxy to OPA Watchdog Enforcer Service
3
+
4
+ This router provides a transparent proxy to the OPA Watchdog enforcer service,
5
+ allowing workers to call the enforcer through the control plane without knowing
6
+ the actual enforcer service URL.
7
+
8
+ All requests to /api/v1/enforcer/* are forwarded to the enforcer service.
9
+ """
10
+
11
+ import os
12
+ import httpx
13
+ from fastapi import APIRouter, Request, Response, status, HTTPException
14
+ from fastapi.responses import StreamingResponse
15
+ import structlog
16
+
17
+ from control_plane_api.app.middleware.auth import get_current_organization
18
+ from fastapi import Depends
19
+
20
+ logger = structlog.get_logger()
21
+
22
+ router = APIRouter(prefix="/enforcer", tags=["enforcer"])
23
+
24
+ # Get the actual enforcer service URL from environment
25
+ # Default to the hosted enforcer service
26
+ ENFORCER_SERVICE_URL = os.environ.get("ENFORCER_SERVICE_URL", "https://enforcer-psi.vercel.app")
27
+
28
+
29
+ async def proxy_enforcer_request(
30
+ request: Request,
31
+ path: str = "",
32
+ ) -> Response:
33
+ """
34
+ Generic proxy function for Enforcer API requests.
35
+
36
+ Args:
37
+ request: FastAPI request object
38
+ path: Additional path to append after /enforcer
39
+
40
+ Returns:
41
+ Response from the enforcer service
42
+ """
43
+ # Build target URL
44
+ target_url = f"{ENFORCER_SERVICE_URL.rstrip('/')}/{path.lstrip('/')}"
45
+
46
+ # Get request body if present
47
+ body = None
48
+ if request.method in ["POST", "PUT", "PATCH"]:
49
+ body = await request.body()
50
+
51
+ # Get query parameters
52
+ query_params = dict(request.query_params)
53
+
54
+ # Forward headers (including Authorization)
55
+ headers = dict(request.headers)
56
+ # Remove host header to avoid conflicts
57
+ headers.pop("host", None)
58
+
59
+ logger.debug(
60
+ "proxy_enforcer_request",
61
+ method=request.method,
62
+ target_url=target_url,
63
+ has_body=body is not None,
64
+ query_params=query_params,
65
+ )
66
+
67
+ try:
68
+ async with httpx.AsyncClient(timeout=30.0) as client:
69
+ response = await client.request(
70
+ method=request.method,
71
+ url=target_url,
72
+ params=query_params,
73
+ content=body,
74
+ headers=headers,
75
+ )
76
+
77
+ # Return response
78
+ return Response(
79
+ content=response.content,
80
+ status_code=response.status_code,
81
+ headers=dict(response.headers),
82
+ )
83
+ except httpx.TimeoutException:
84
+ logger.error("enforcer_proxy_timeout", target_url=target_url)
85
+ raise HTTPException(
86
+ status_code=status.HTTP_504_GATEWAY_TIMEOUT,
87
+ detail="Enforcer service request timed out"
88
+ )
89
+ except httpx.RequestError as e:
90
+ logger.error("enforcer_proxy_error", error=str(e), target_url=target_url)
91
+ raise HTTPException(
92
+ status_code=status.HTTP_502_BAD_GATEWAY,
93
+ detail=f"Failed to connect to enforcer service: {str(e)}"
94
+ )
95
+
96
+
97
+ @router.get("/health")
98
+ async def health_check():
99
+ """Health check endpoint for enforcer proxy"""
100
+ return {
101
+ "status": "healthy",
102
+ "enforcer_url": ENFORCER_SERVICE_URL,
103
+ "proxy": "enabled"
104
+ }
105
+
106
+
107
+ @router.get("/status")
108
+ async def status_check(request: Request):
109
+ """Proxy status endpoint to enforcer service"""
110
+ return await proxy_enforcer_request(request, "status")
111
+
112
+
113
+ @router.api_route("/api/v1/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
114
+ async def proxy_api_v1(
115
+ request: Request,
116
+ path: str,
117
+ ):
118
+ """Proxy all /api/v1/* requests to enforcer service"""
119
+ return await proxy_enforcer_request(request, f"api/v1/{path}")
120
+
121
+
122
+ @router.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
123
+ async def proxy_all(
124
+ request: Request,
125
+ path: str,
126
+ ):
127
+ """Proxy all other requests to enforcer service"""
128
+ # Skip if already handled by other routes
129
+ if path in ["health", "status"] or path.startswith("api/v1/"):
130
+ return
131
+
132
+ return await proxy_enforcer_request(request, path)