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,847 @@
1
+ """
2
+ Execution Environment Router - Resolve execution environment for agents/teams
3
+
4
+ This router provides workers with resolved execution environment configuration:
5
+ - Fetches agent/team execution_environment from database
6
+ - Resolves secret names to actual values from Kubiya API
7
+ - Resolves integration IDs to actual tokens from Kubiya API
8
+ - Maps integration tokens to specific env var names (GH_TOKEN, JIRA_TOKEN, etc.)
9
+ - Returns complete env var dict ready for worker to inject into execution
10
+ """
11
+
12
+ import httpx
13
+ from fastapi import APIRouter, Depends, HTTPException, Request
14
+ from typing import Dict, Any, Optional
15
+ import structlog
16
+ from sqlalchemy.orm import Session
17
+
18
+ from control_plane_api.app.middleware.auth import get_current_organization
19
+ from control_plane_api.app.database import get_db
20
+ from control_plane_api.app.models import Environment, AgentEnvironment, TeamEnvironment
21
+ from control_plane_api.app.lib.sqlalchemy_utils import model_to_dict
22
+ from control_plane_api.app.lib.kubiya_client import KUBIYA_API_BASE
23
+ from control_plane_api.app.lib.templating import TemplateContext, resolve_templates
24
+
25
+ logger = structlog.get_logger()
26
+
27
+ router = APIRouter(prefix="/execution-environment", tags=["execution-environment"])
28
+
29
+
30
+ # Integration type to environment variable name mapping
31
+ INTEGRATION_ENV_VAR_MAP = {
32
+ "github": "GH_TOKEN",
33
+ "github_app": "GITHUB_TOKEN",
34
+ "jira": "JIRA_TOKEN",
35
+ "slack": "SLACK_TOKEN",
36
+ "aws": "AWS_ACCESS_KEY_ID", # Note: AWS might need multiple vars
37
+ "aws-serviceaccount": "AWS_ROLE_ARN",
38
+ "kubernetes": "KUBECONFIG",
39
+ }
40
+
41
+
42
+ async def resolve_secret_value(
43
+ secret_name: str,
44
+ token: str,
45
+ org_id: str,
46
+ auth_type: str = "UserKey",
47
+ ) -> str:
48
+ """
49
+ Resolve a secret name to its actual value from Kubiya API.
50
+
51
+ Args:
52
+ secret_name: Name of the secret to resolve
53
+ token: Kubiya API token
54
+ org_id: Organization ID
55
+ auth_type: Authorization type ("UserKey" for API keys, "Bearer" for JWT tokens)
56
+
57
+ Returns:
58
+ Secret value as string
59
+ """
60
+ # Map auth types to what Kubiya API accepts
61
+ # "JWT" is used internally for Kubiya API keys that are JWTs - Kubiya API expects "UserKey" for these
62
+ kubiya_auth_type = "UserKey" if auth_type in ("JWT", "UserKey") else auth_type
63
+ headers = {
64
+ "Authorization": f"{kubiya_auth_type} {token}",
65
+ "Accept": "application/json",
66
+ "Content-Type": "application/json",
67
+ "X-Kubiya-Client": "agent-control-plane",
68
+ "X-Organization-ID": org_id,
69
+ }
70
+
71
+ async with httpx.AsyncClient(timeout=30.0) as client:
72
+ response = await client.get(
73
+ f"{KUBIYA_API_BASE}/api/v2/secrets/get_value/{secret_name}",
74
+ headers=headers,
75
+ )
76
+
77
+ if response.status_code == 200:
78
+ return response.text
79
+ else:
80
+ logger.warning(
81
+ "secret_resolution_failed",
82
+ secret_name=secret_name[:20],
83
+ status=response.status_code,
84
+ )
85
+ raise HTTPException(
86
+ status_code=response.status_code,
87
+ detail=f"Failed to resolve secret '{secret_name}': {response.text[:200]}",
88
+ )
89
+
90
+
91
+ async def resolve_integration_token(
92
+ integration_id: str,
93
+ integration_type: str,
94
+ token: str,
95
+ org_id: str,
96
+ auth_type: str = "UserKey",
97
+ ) -> Dict[str, str]:
98
+ """
99
+ Resolve an integration ID to its actual token from Kubiya API.
100
+
101
+ Args:
102
+ integration_id: Integration UUID
103
+ integration_type: Type of integration (github, jira, etc.)
104
+ token: Kubiya API token
105
+ org_id: Organization ID
106
+ auth_type: Authorization type ("UserKey" for API keys, "Bearer" for JWT tokens)
107
+
108
+ Returns:
109
+ Dict with env_var_name and token value
110
+ """
111
+ # Map auth types to what Kubiya API accepts
112
+ kubiya_auth_type = "UserKey" if auth_type in ("JWT", "UserKey") else auth_type
113
+ headers = {
114
+ "Authorization": f"{kubiya_auth_type} {token}",
115
+ "Accept": "application/json",
116
+ "Content-Type": "application/json",
117
+ "X-Kubiya-Client": "agent-control-plane",
118
+ "X-Organization-ID": org_id,
119
+ }
120
+
121
+ # Build token URL based on integration type
122
+ integration_type_lower = integration_type.lower()
123
+
124
+ if integration_type_lower == "github":
125
+ token_url = f"{KUBIYA_API_BASE}/api/v1/integration/github/token/{integration_id}"
126
+ elif integration_type_lower == "github_app":
127
+ token_url = f"{KUBIYA_API_BASE}/api/v1/integration/github_app/token/{integration_id}"
128
+ elif integration_type_lower == "jira":
129
+ token_url = f"{KUBIYA_API_BASE}/api/v1/integration/jira/token/{integration_id}"
130
+ else:
131
+ logger.warning(
132
+ "unsupported_integration_type",
133
+ integration_type=integration_type,
134
+ integration_id=integration_id[:8],
135
+ )
136
+ # For unsupported types, skip
137
+ return {}
138
+
139
+ async with httpx.AsyncClient(timeout=30.0) as client:
140
+ response = await client.get(token_url, headers=headers)
141
+
142
+ if response.status_code == 200:
143
+ # Try to parse as JSON first
144
+ try:
145
+ token_data = response.json()
146
+ token_value = token_data.get("token", response.text)
147
+ except:
148
+ # If not JSON, use plain text
149
+ token_value = response.text
150
+
151
+ # Map to env var name
152
+ env_var_name = INTEGRATION_ENV_VAR_MAP.get(integration_type_lower, f"{integration_type.upper()}_TOKEN")
153
+
154
+ return {env_var_name: token_value}
155
+ else:
156
+ logger.warning(
157
+ "integration_token_resolution_failed",
158
+ integration_id=integration_id[:8],
159
+ integration_type=integration_type,
160
+ status=response.status_code,
161
+ )
162
+ # Don't fail the entire request for one integration
163
+ return {}
164
+
165
+
166
+ async def resolve_environment_configs(
167
+ environment_ids: list[str],
168
+ org_id: str,
169
+ db: Session,
170
+ ) -> Dict[str, Any]:
171
+ """
172
+ Resolve execution environment configs from a list of environment IDs.
173
+ Merges configs from all environments.
174
+
175
+ Args:
176
+ environment_ids: List of environment IDs
177
+ org_id: Organization ID
178
+ db: Database session
179
+
180
+ Returns:
181
+ Merged execution environment dict with env_vars, secrets, integration_ids, mcp_servers
182
+ """
183
+ if not environment_ids:
184
+ return {"env_vars": {}, "secrets": [], "integration_ids": [], "mcp_servers": {}}
185
+
186
+ # Fetch all environments
187
+ environments = (
188
+ db.query(Environment)
189
+ .filter(
190
+ Environment.id.in_(environment_ids),
191
+ Environment.organization_id == org_id
192
+ )
193
+ .all()
194
+ )
195
+
196
+ # Merge all environment configs
197
+ merged_env_vars = {}
198
+ merged_secrets = set()
199
+ merged_integration_ids = set()
200
+ merged_mcp_servers = {}
201
+
202
+ for env in environments:
203
+ env_config = env.execution_environment or {}
204
+
205
+ # Merge env vars (later environments override earlier ones)
206
+ merged_env_vars.update(env_config.get("env_vars", {}))
207
+
208
+ # Collect secrets (union)
209
+ merged_secrets.update(env_config.get("secrets", []))
210
+
211
+ # Collect integration IDs (union)
212
+ merged_integration_ids.update(env_config.get("integration_ids", []))
213
+
214
+ # Merge MCP servers (later environments override earlier ones)
215
+ merged_mcp_servers.update(env_config.get("mcp_servers", {}))
216
+
217
+ return {
218
+ "env_vars": merged_env_vars,
219
+ "secrets": list(merged_secrets),
220
+ "integration_ids": list(merged_integration_ids),
221
+ "mcp_servers": merged_mcp_servers,
222
+ }
223
+
224
+
225
+ def apply_template_resolution(
226
+ config: Dict[str, Any],
227
+ resolved_secrets: Dict[str, str],
228
+ resolved_env_vars: Dict[str, str],
229
+ ) -> Dict[str, Any]:
230
+ """
231
+ Apply template resolution to a configuration object.
232
+
233
+ Resolves all templates in the config using resolved secrets and env vars.
234
+ Templates are resolved recursively in all string fields, including:
235
+ - system_prompt
236
+ - description
237
+ - mcp_servers (all fields)
238
+ - runtime_config (all fields)
239
+ - Any other text-based fields
240
+
241
+ Args:
242
+ config: Configuration dict with potential templates
243
+ resolved_secrets: Map of secret names to resolved values
244
+ resolved_env_vars: Map of env var names to values
245
+
246
+ Returns:
247
+ Configuration with all templates resolved
248
+ """
249
+ try:
250
+ # Build template context
251
+ context = TemplateContext(
252
+ variables={}, # Simple variables not used in execution environment
253
+ secrets=resolved_secrets,
254
+ env_vars=resolved_env_vars
255
+ )
256
+
257
+ # Apply template resolution recursively to entire config
258
+ resolved_config = resolve_templates(config, context, skip_on_error=True)
259
+
260
+ logger.info(
261
+ "template_resolution_applied",
262
+ config_keys=list(config.keys()),
263
+ secrets_count=len(resolved_secrets),
264
+ env_vars_count=len(resolved_env_vars)
265
+ )
266
+
267
+ return resolved_config
268
+
269
+ except Exception as e:
270
+ logger.error(
271
+ "template_resolution_failed",
272
+ error=str(e),
273
+ config_keys=list(config.keys())
274
+ )
275
+ # Return original config on error to avoid breaking execution
276
+ return config
277
+
278
+
279
+ @router.get("/agents/{agent_id}/resolved")
280
+ async def get_agent_execution_environment(
281
+ agent_id: str,
282
+ request: Request,
283
+ organization: dict = Depends(get_current_organization),
284
+ db: Session = Depends(get_db),
285
+ ) -> Dict[str, str]:
286
+ """
287
+ Get resolved execution environment for an agent.
288
+
289
+ This endpoint:
290
+ 1. Fetches agent's execution_environment and environment_ids from database
291
+ 2. Fetches and merges execution configs from all associated environments
292
+ 3. Merges agent's own execution_environment (agent config overrides environment)
293
+ 4. Resolves all secret names to actual values
294
+ 5. Resolves all integration IDs to actual tokens
295
+ 6. Maps integration tokens to specific env var names
296
+ 7. Returns merged env var dict
297
+
298
+ Inheritance order (later overrides earlier):
299
+ - Environment 1 execution_environment
300
+ - Environment 2 execution_environment
301
+ - ...
302
+ - Agent execution_environment
303
+
304
+ Returns:
305
+ Dict of environment variables ready to inject into agent execution
306
+ """
307
+ try:
308
+ token = request.state.kubiya_token
309
+ auth_type = getattr(request.state, "kubiya_auth_type", "UserKey")
310
+ org_id = organization["id"]
311
+
312
+ # Import Agent model locally to avoid circular dependency
313
+ from control_plane_api.app.models import Agent
314
+
315
+ # Fetch agent with execution environment
316
+ agent = (
317
+ db.query(Agent)
318
+ .filter(Agent.id == agent_id, Agent.organization_id == org_id)
319
+ .first()
320
+ )
321
+
322
+ if not agent:
323
+ raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
324
+
325
+ # Get environment associations from join table
326
+ env_associations = (
327
+ db.query(AgentEnvironment)
328
+ .filter(AgentEnvironment.agent_id == agent_id)
329
+ .all()
330
+ )
331
+ environment_ids = [str(assoc.environment_id) for assoc in env_associations]
332
+ env_config = await resolve_environment_configs(environment_ids, org_id, db)
333
+
334
+ # Get agent-level config
335
+ agent_config = agent.execution_environment or {}
336
+
337
+ # Merge: environment config + agent config (agent overrides environment)
338
+ execution_environment = {
339
+ "env_vars": {**env_config.get("env_vars", {}), **agent_config.get("env_vars", {})},
340
+ "secrets": list(set(env_config.get("secrets", []) + agent_config.get("secrets", []))),
341
+ "integration_ids": list(set(env_config.get("integration_ids", []) + agent_config.get("integration_ids", []))),
342
+ }
343
+
344
+ # Start with custom env vars
345
+ resolved_env_vars = dict(execution_environment.get("env_vars", {}))
346
+
347
+ # Resolve secrets
348
+ secrets = execution_environment.get("secrets", [])
349
+ for secret_name in secrets:
350
+ try:
351
+ secret_value = await resolve_secret_value(secret_name, token, org_id, auth_type)
352
+ resolved_env_vars[secret_name] = secret_value
353
+ logger.info(
354
+ "secret_resolved",
355
+ agent_id=agent_id[:8],
356
+ secret_name=secret_name[:20],
357
+ )
358
+ except Exception as e:
359
+ logger.error(
360
+ "secret_resolution_error",
361
+ agent_id=agent_id[:8],
362
+ secret_name=secret_name[:20],
363
+ error=str(e),
364
+ )
365
+ # Continue with other secrets even if one fails
366
+
367
+ # Resolve integrations
368
+ integration_ids = execution_environment.get("integration_ids", [])
369
+ if integration_ids:
370
+ # First, fetch integration details to get types
371
+ # Map auth types to what Kubiya API accepts
372
+ kubiya_auth_type = "UserKey" if auth_type in ("JWT", "UserKey") else auth_type
373
+ headers = {
374
+ "Authorization": f"{kubiya_auth_type} {token}",
375
+ "Accept": "application/json",
376
+ "Content-Type": "application/json",
377
+ "X-Kubiya-Client": "agent-control-plane",
378
+ "X-Organization-ID": org_id,
379
+ }
380
+
381
+ async with httpx.AsyncClient(timeout=30.0) as client:
382
+ response = await client.get(
383
+ f"{KUBIYA_API_BASE}/api/v2/integrations?full=true",
384
+ headers=headers,
385
+ )
386
+
387
+ if response.status_code == 200:
388
+ all_integrations = response.json()
389
+
390
+ for integration_id in integration_ids:
391
+ # Find integration by UUID
392
+ integration = next(
393
+ (i for i in all_integrations if i.get("uuid") == integration_id),
394
+ None
395
+ )
396
+
397
+ if integration:
398
+ integration_type = integration.get("integration_type", "")
399
+ try:
400
+ token_env_vars = await resolve_integration_token(
401
+ integration_id,
402
+ integration_type,
403
+ token,
404
+ org_id,
405
+ auth_type,
406
+ )
407
+ resolved_env_vars.update(token_env_vars)
408
+ logger.info(
409
+ "integration_resolved",
410
+ agent_id=agent_id[:8],
411
+ integration_id=integration_id[:8],
412
+ integration_type=integration_type,
413
+ env_vars=list(token_env_vars.keys()),
414
+ )
415
+ except Exception as e:
416
+ logger.error(
417
+ "integration_resolution_error",
418
+ agent_id=agent_id[:8],
419
+ integration_id=integration_id[:8],
420
+ error=str(e),
421
+ )
422
+ else:
423
+ logger.warning(
424
+ "integration_not_found",
425
+ agent_id=agent_id[:8],
426
+ integration_id=integration_id[:8],
427
+ )
428
+
429
+ logger.info(
430
+ "execution_environment_resolved",
431
+ agent_id=agent_id[:8],
432
+ env_var_count=len(resolved_env_vars),
433
+ env_var_keys=list(resolved_env_vars.keys()),
434
+ )
435
+
436
+ return resolved_env_vars
437
+
438
+ except HTTPException:
439
+ raise
440
+ except Exception as e:
441
+ logger.error(
442
+ "execution_environment_resolution_error",
443
+ agent_id=agent_id[:8],
444
+ error=str(e),
445
+ error_type=type(e).__name__,
446
+ )
447
+ raise HTTPException(status_code=500, detail=f"Failed to resolve execution environment: {str(e)}")
448
+
449
+
450
+ @router.get("/teams/{team_id}/resolved")
451
+ async def get_team_execution_environment(
452
+ team_id: str,
453
+ request: Request,
454
+ organization: dict = Depends(get_current_organization),
455
+ db: Session = Depends(get_db),
456
+ ) -> Dict[str, str]:
457
+ """
458
+ Get resolved execution environment for a team.
459
+
460
+ This endpoint:
461
+ 1. Fetches team's execution_environment and environment_ids from database
462
+ 2. Fetches and merges execution configs from all associated environments
463
+ 3. Merges team's own execution_environment (team config overrides environment)
464
+ 4. Resolves all secret names to actual values
465
+ 5. Resolves all integration IDs to actual tokens
466
+ 6. Maps integration tokens to specific env var names
467
+ 7. Returns merged env var dict
468
+
469
+ Inheritance order (later overrides earlier):
470
+ - Environment 1 execution_environment
471
+ - Environment 2 execution_environment
472
+ - ...
473
+ - Team execution_environment
474
+
475
+ Returns:
476
+ Dict of environment variables ready to inject into team execution
477
+ """
478
+ try:
479
+ token = request.state.kubiya_token
480
+ auth_type = getattr(request.state, "kubiya_auth_type", "UserKey")
481
+ org_id = organization["id"]
482
+
483
+ # Import Team model locally to avoid circular dependency
484
+ from control_plane_api.app.models import Team
485
+
486
+ # Fetch team with environment associations
487
+ team = (
488
+ db.query(Team)
489
+ .filter(Team.id == team_id, Team.organization_id == org_id)
490
+ .first()
491
+ )
492
+
493
+ if not team:
494
+ raise HTTPException(status_code=404, detail=f"Team {team_id} not found")
495
+
496
+ # Get environment-level configs first
497
+ environment_ids = team.environment_ids or []
498
+ env_config = await resolve_environment_configs(environment_ids, org_id, db)
499
+
500
+ # Get team-level config
501
+ team_config = team.execution_environment or {}
502
+
503
+ # Merge: environment config + team config (team overrides environment)
504
+ execution_environment = {
505
+ "env_vars": {**env_config.get("env_vars", {}), **team_config.get("env_vars", {})},
506
+ "secrets": list(set(env_config.get("secrets", []) + team_config.get("secrets", []))),
507
+ "integration_ids": list(set(env_config.get("integration_ids", []) + team_config.get("integration_ids", []))),
508
+ }
509
+
510
+ # Start with custom env vars
511
+ resolved_env_vars = dict(execution_environment.get("env_vars", {}))
512
+
513
+ # Resolve secrets
514
+ secrets = execution_environment.get("secrets", [])
515
+ for secret_name in secrets:
516
+ try:
517
+ secret_value = await resolve_secret_value(secret_name, token, org_id, auth_type)
518
+ resolved_env_vars[secret_name] = secret_value
519
+ logger.info(
520
+ "secret_resolved",
521
+ team_id=team_id[:8],
522
+ secret_name=secret_name[:20],
523
+ )
524
+ except Exception as e:
525
+ logger.error(
526
+ "secret_resolution_error",
527
+ team_id=team_id[:8],
528
+ secret_name=secret_name[:20],
529
+ error=str(e),
530
+ )
531
+ # Continue with other secrets even if one fails
532
+
533
+ # Resolve integrations
534
+ integration_ids = execution_environment.get("integration_ids", [])
535
+ if integration_ids:
536
+ # First, fetch integration details to get types
537
+ # Map auth types to what Kubiya API accepts
538
+ kubiya_auth_type = "UserKey" if auth_type in ("JWT", "UserKey") else auth_type
539
+ headers = {
540
+ "Authorization": f"{kubiya_auth_type} {token}",
541
+ "Accept": "application/json",
542
+ "Content-Type": "application/json",
543
+ "X-Kubiya-Client": "agent-control-plane",
544
+ "X-Organization-ID": org_id,
545
+ }
546
+
547
+ async with httpx.AsyncClient(timeout=30.0) as client:
548
+ response = await client.get(
549
+ f"{KUBIYA_API_BASE}/api/v2/integrations?full=true",
550
+ headers=headers,
551
+ )
552
+
553
+ if response.status_code == 200:
554
+ all_integrations = response.json()
555
+
556
+ for integration_id in integration_ids:
557
+ # Find integration by UUID
558
+ integration = next(
559
+ (i for i in all_integrations if i.get("uuid") == integration_id),
560
+ None
561
+ )
562
+
563
+ if integration:
564
+ integration_type = integration.get("integration_type", "")
565
+ try:
566
+ token_env_vars = await resolve_integration_token(
567
+ integration_id,
568
+ integration_type,
569
+ token,
570
+ org_id,
571
+ auth_type,
572
+ )
573
+ resolved_env_vars.update(token_env_vars)
574
+ logger.info(
575
+ "integration_resolved",
576
+ team_id=team_id[:8],
577
+ integration_id=integration_id[:8],
578
+ integration_type=integration_type,
579
+ env_vars=list(token_env_vars.keys()),
580
+ )
581
+ except Exception as e:
582
+ logger.error(
583
+ "integration_resolution_error",
584
+ team_id=team_id[:8],
585
+ integration_id=integration_id[:8],
586
+ error=str(e),
587
+ )
588
+ else:
589
+ logger.warning(
590
+ "integration_not_found",
591
+ team_id=team_id[:8],
592
+ integration_id=integration_id[:8],
593
+ )
594
+
595
+ logger.info(
596
+ "execution_environment_resolved",
597
+ team_id=team_id[:8],
598
+ env_var_count=len(resolved_env_vars),
599
+ env_var_keys=list(resolved_env_vars.keys()),
600
+ )
601
+
602
+ return resolved_env_vars
603
+
604
+ except HTTPException:
605
+ raise
606
+ except Exception as e:
607
+ logger.error(
608
+ "execution_environment_resolution_error",
609
+ team_id=team_id[:8],
610
+ error=str(e),
611
+ error_type=type(e).__name__,
612
+ )
613
+ raise HTTPException(status_code=500, detail=f"Failed to resolve execution environment: {str(e)}")
614
+
615
+
616
+ async def resolve_agent_execution_environment_internal(
617
+ agent_id: str,
618
+ org_id: str,
619
+ db: Session,
620
+ token: Optional[str] = None,
621
+ auth_type: str = "UserKey",
622
+ ) -> Dict[str, Any]:
623
+ """
624
+ Internal function to resolve execution environment (can be called directly).
625
+
626
+ This bypasses HTTP/auth and can be called from other endpoints directly.
627
+ Token is optional - when None, secrets and integrations won't be resolved from Kubiya API.
628
+ Auth_type specifies the authorization type ("UserKey" for API keys, "Bearer" for JWT tokens).
629
+ """
630
+ try:
631
+ # Import Agent model locally to avoid circular dependency
632
+ from control_plane_api.app.models import Agent
633
+
634
+ # Fetch agent with configuration fields
635
+ agent = (
636
+ db.query(Agent)
637
+ .filter(Agent.id == agent_id, Agent.organization_id == org_id)
638
+ .first()
639
+ )
640
+
641
+ if not agent:
642
+ raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
643
+
644
+ # Get environment associations from join table
645
+ env_associations = (
646
+ db.query(AgentEnvironment)
647
+ .filter(AgentEnvironment.agent_id == agent_id)
648
+ .all()
649
+ )
650
+ environment_ids = [str(assoc.environment_id) for assoc in env_associations]
651
+ env_config = await resolve_environment_configs(environment_ids, org_id, db)
652
+
653
+ # Get agent-level config
654
+ agent_exec_env = agent.execution_environment or {}
655
+
656
+ # Get system_prompt from configuration, description from agent column
657
+ agent_configuration = agent.configuration or {}
658
+
659
+ # Merge: environment config + agent config (agent overrides environment)
660
+ execution_environment = {
661
+ "env_vars": {**env_config.get("env_vars", {}), **agent_exec_env.get("env_vars", {})},
662
+ "secrets": list(set(env_config.get("secrets", []) + agent_exec_env.get("secrets", []))),
663
+ "integration_ids": list(set(env_config.get("integration_ids", []) + agent_exec_env.get("integration_ids", []))),
664
+ "mcp_servers": {**env_config.get("mcp_servers", {}), **agent_exec_env.get("mcp_servers", {})},
665
+ }
666
+
667
+ # Start with custom env vars
668
+ resolved_env_vars = dict(execution_environment.get("env_vars", {}))
669
+ resolved_secrets = {}
670
+
671
+ # Resolve secrets (only if token is provided)
672
+ secrets = execution_environment.get("secrets", [])
673
+ if token:
674
+ for secret_name in secrets:
675
+ try:
676
+ secret_value = await resolve_secret_value(secret_name, token, org_id, auth_type)
677
+ resolved_env_vars[secret_name] = secret_value
678
+ resolved_secrets[secret_name] = secret_value # Store for template context
679
+ logger.debug("secret_resolved", agent_id=agent_id[:8], secret_name=secret_name[:20])
680
+ except Exception as e:
681
+ logger.error("secret_resolution_error", agent_id=agent_id[:8], secret_name=secret_name[:20], error=str(e))
682
+
683
+ # Resolve integrations (only if token is provided)
684
+ integration_ids = execution_environment.get("integration_ids", [])
685
+ if integration_ids and token:
686
+ # Map auth types to what Kubiya API accepts
687
+ kubiya_auth_type = "UserKey" if auth_type in ("JWT", "UserKey") else auth_type
688
+ headers = {
689
+ "Authorization": f"{kubiya_auth_type} {token}",
690
+ "Accept": "application/json",
691
+ "Content-Type": "application/json",
692
+ "X-Kubiya-Client": "agent-control-plane",
693
+ "X-Organization-ID": org_id,
694
+ }
695
+
696
+ async with httpx.AsyncClient(timeout=30.0) as client:
697
+ response = await client.get(f"{KUBIYA_API_BASE}/api/v2/integrations?full=true", headers=headers)
698
+
699
+ if response.status_code == 200:
700
+ all_integrations = response.json()
701
+
702
+ for integration_id in integration_ids:
703
+ integration = next((i for i in all_integrations if i.get("uuid") == integration_id), None)
704
+
705
+ if integration:
706
+ integration_type = integration.get("integration_type", "")
707
+ try:
708
+ token_env_vars = await resolve_integration_token(integration_id, integration_type, token, org_id, auth_type)
709
+ resolved_env_vars.update(token_env_vars)
710
+ logger.debug("integration_resolved", agent_id=agent_id[:8], integration_id=integration_id[:8])
711
+ except Exception as e:
712
+ logger.error("integration_resolution_error", agent_id=agent_id[:8], integration_id=integration_id[:8], error=str(e))
713
+
714
+ # Build complete config to resolve templates
715
+ complete_config = {
716
+ "system_prompt": agent_configuration.get("system_prompt"),
717
+ "description": agent.description, # From agents table column
718
+ "configuration": agent_configuration,
719
+ "mcp_servers": execution_environment.get("mcp_servers", {}),
720
+ "env_vars": execution_environment.get("env_vars", {}),
721
+ }
722
+
723
+ # Apply template resolution to ENTIRE config
724
+ resolved_config = apply_template_resolution(
725
+ complete_config,
726
+ resolved_secrets,
727
+ resolved_env_vars
728
+ )
729
+
730
+ mcp_servers_resolved = resolved_config.get("mcp_servers", {})
731
+
732
+ logger.info(
733
+ "full_execution_environment_resolved",
734
+ agent_id=agent_id[:8],
735
+ env_var_count=len(resolved_env_vars),
736
+ mcp_server_count=len(mcp_servers_resolved),
737
+ mcp_server_names=list(mcp_servers_resolved.keys()),
738
+ secrets_count=len(resolved_secrets)
739
+ )
740
+
741
+ # Debug log each MCP server after resolution
742
+ for server_name, server_config in mcp_servers_resolved.items():
743
+ logger.debug(
744
+ "mcp_server_after_template_resolution",
745
+ server_name=server_name,
746
+ config=server_config,
747
+ )
748
+
749
+ return {
750
+ "env_vars": resolved_env_vars,
751
+ "mcp_servers": mcp_servers_resolved,
752
+ "system_prompt": resolved_config.get("system_prompt"),
753
+ "description": resolved_config.get("description"),
754
+ "configuration": resolved_config.get("configuration", {}),
755
+ "secrets_resolved": resolved_secrets, # For debugging/logging only
756
+ }
757
+
758
+ except HTTPException:
759
+ raise
760
+ except Exception as e:
761
+ logger.error("full_execution_environment_resolution_error", agent_id=agent_id[:8], error=str(e), exc_info=True)
762
+ raise HTTPException(status_code=500, detail=f"Failed to resolve full execution environment: {str(e)}")
763
+
764
+
765
+ @router.get("/agents/{agent_id}/resolved/full")
766
+ async def get_agent_execution_environment_full(
767
+ agent_id: str,
768
+ request: Request,
769
+ organization: dict = Depends(get_current_organization),
770
+ db: Session = Depends(get_db),
771
+ ) -> Dict[str, Any]:
772
+ """
773
+ Get FULL resolved execution environment for an agent with template resolution.
774
+
775
+ This endpoint extends the basic /resolved endpoint by:
776
+ 1. Returning the complete execution environment (not just env vars)
777
+ 2. Including MCP servers with templates resolved
778
+ 3. Resolving templates in ALL text fields (system_prompt, description, etc.)
779
+ 4. Providing resolved secrets dict (for template context)
780
+
781
+ Returns:
782
+ Complete execution environment dict with:
783
+ - env_vars: Resolved environment variables
784
+ - mcp_servers: MCP server configs with templates resolved
785
+ - secrets_resolved: Map of secret names to values (for templates)
786
+ - raw_config: Original config before template resolution
787
+ """
788
+ from control_plane_api.app.controllers.execution_environment_controller import (
789
+ resolve_agent_execution_environment,
790
+ ExecutionEnvironmentResolutionError,
791
+ )
792
+ import os
793
+
794
+ try:
795
+ org_id = organization["id"]
796
+
797
+ # Determine which token to use for Kubiya API calls
798
+ # If authenticated with UserKey or JWT (Kubiya API keys), use that token from the request
799
+ # If authenticated with Bearer (Auth0 user JWT), fall back to environment KUBIYA_API_KEY
800
+ auth_type = getattr(request.state, "kubiya_auth_type", "Bearer")
801
+ if auth_type in ("UserKey", "JWT", "ManagementKey"):
802
+ # Authenticated with Kubiya API key - use it directly for Kubiya API calls
803
+ kubiya_api_key = request.state.kubiya_token
804
+ else:
805
+ # Authenticated with Auth0 user JWT - fall back to environment API key
806
+ kubiya_api_key = os.environ.get("KUBIYA_API_KEY")
807
+
808
+ return await resolve_agent_execution_environment(agent_id, org_id, db, kubiya_api_key)
809
+ except ExecutionEnvironmentResolutionError as e:
810
+ raise HTTPException(status_code=404 if "not found" in str(e).lower() else 500, detail=str(e))
811
+
812
+
813
+ @router.get("/teams/{team_id}/resolved/full")
814
+ async def get_team_execution_environment_full(
815
+ team_id: str,
816
+ request: Request,
817
+ organization: dict = Depends(get_current_organization),
818
+ db: Session = Depends(get_db),
819
+ ) -> Dict[str, Any]:
820
+ """
821
+ Get FULL resolved execution environment for a team with template resolution.
822
+
823
+ Similar to agent endpoint but for teams.
824
+ """
825
+ from control_plane_api.app.controllers.execution_environment_controller import (
826
+ resolve_team_execution_environment,
827
+ ExecutionEnvironmentResolutionError,
828
+ )
829
+ import os
830
+
831
+ try:
832
+ org_id = organization["id"]
833
+
834
+ # Determine which token to use for Kubiya API calls
835
+ # If authenticated with UserKey or JWT (Kubiya API keys), use that token from the request
836
+ # If authenticated with Bearer (Auth0 user JWT), fall back to environment KUBIYA_API_KEY
837
+ auth_type = getattr(request.state, "kubiya_auth_type", "Bearer")
838
+ if auth_type in ("UserKey", "JWT", "ManagementKey"):
839
+ # Authenticated with Kubiya API key - use it directly for Kubiya API calls
840
+ kubiya_api_key = request.state.kubiya_token
841
+ else:
842
+ # Authenticated with Auth0 user JWT - fall back to environment API key
843
+ kubiya_api_key = os.environ.get("KUBIYA_API_KEY")
844
+
845
+ return await resolve_team_execution_environment(team_id, org_id, db, kubiya_api_key)
846
+ except ExecutionEnvironmentResolutionError as e:
847
+ raise HTTPException(status_code=404 if "not found" in str(e).lower() else 500, detail=str(e))