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,330 @@
1
+ """
2
+ Job execution logic for routing and parameter substitution.
3
+
4
+ This module handles:
5
+ - Dynamic executor routing (auto/specific queue/environment)
6
+ - Prompt template parameter substitution
7
+ - Worker queue selection based on availability
8
+ """
9
+
10
+ import re
11
+ import structlog
12
+ from typing import Dict, Any, Optional, Tuple
13
+ from control_plane_api.app.database import get_session_local
14
+ from control_plane_api.app.models.worker import WorkerQueue
15
+ from control_plane_api.app.models.environment import Environment
16
+ from control_plane_api.app.models.agent import Agent
17
+ from control_plane_api.app.models.team import Team
18
+ from control_plane_api.app.models.workflow import Workflow
19
+
20
+ logger = structlog.get_logger()
21
+
22
+
23
+ def substitute_prompt_parameters(prompt_template: str, parameters: Dict[str, Any]) -> str:
24
+ """
25
+ Substitute parameters in prompt template.
26
+
27
+ Template variables use {{variable_name}} syntax.
28
+
29
+ Example:
30
+ prompt_template = "Run a backup of {{database}} at {{time}}"
31
+ parameters = {"database": "production", "time": "5pm"}
32
+ result = "Run a backup of production at 5pm"
33
+
34
+ Args:
35
+ prompt_template: Prompt template with {{variables}}
36
+ parameters: Dictionary of parameter values
37
+
38
+ Returns:
39
+ Prompt with substituted values
40
+
41
+ Raises:
42
+ ValueError: If required parameters are missing
43
+ """
44
+ # Find all variables in template
45
+ variables = re.findall(r"\{\{(\w+)\}\}", prompt_template)
46
+
47
+ # Check for missing parameters
48
+ missing = [var for var in variables if var not in parameters]
49
+ if missing:
50
+ raise ValueError(f"Missing required parameters: {', '.join(missing)}")
51
+
52
+ # Substitute variables
53
+ result = prompt_template
54
+ for var_name, var_value in parameters.items():
55
+ result = result.replace(f"{{{{{var_name}}}}}", str(var_value))
56
+
57
+ return result
58
+
59
+
60
+ async def get_available_worker_queues(
61
+ organization_id: str,
62
+ environment_name: Optional[str] = None
63
+ ) -> list[Dict[str, Any]]:
64
+ """
65
+ Get list of worker queues with active workers.
66
+
67
+ Queries the worker_queues table and counts active workers from Redis heartbeats.
68
+
69
+ Args:
70
+ organization_id: Organization ID for multi-tenant filtering
71
+ environment_name: Optional environment name filter
72
+
73
+ Returns:
74
+ List of worker queue dictionaries with metadata
75
+ """
76
+ from control_plane_api.app.routers.worker_queues import get_active_workers_from_redis
77
+
78
+ SessionLocal = get_session_local()
79
+ db = SessionLocal()
80
+
81
+ try:
82
+ # Query worker_queues table for active queues (excluding ephemeral queues)
83
+ query = db.query(WorkerQueue).filter(
84
+ WorkerQueue.organization_id == organization_id,
85
+ WorkerQueue.status == "active",
86
+ WorkerQueue.ephemeral == False, # Exclude ephemeral queues from job routing
87
+ ~WorkerQueue.name.startswith('local-exec') # Exclude local-exec queues
88
+ )
89
+
90
+ # Filter by environment if specified
91
+ if environment_name:
92
+ # First get the environment ID
93
+ environment = db.query(Environment).filter(
94
+ Environment.organization_id == organization_id,
95
+ Environment.name == environment_name
96
+ ).first()
97
+
98
+ if environment:
99
+ query = query.filter(WorkerQueue.environment_id == environment.id)
100
+ else:
101
+ logger.warning(
102
+ "environment_not_found",
103
+ organization_id=organization_id,
104
+ environment_name=environment_name
105
+ )
106
+ return []
107
+
108
+ worker_queues_objs = query.all()
109
+
110
+ if not worker_queues_objs:
111
+ logger.info("no_active_worker_queues_found", organization_id=organization_id)
112
+ return []
113
+
114
+ # Get active workers from Redis heartbeats
115
+ active_workers_data = await get_active_workers_from_redis(organization_id)
116
+
117
+ logger.info(
118
+ "checking_active_workers",
119
+ organization_id=organization_id,
120
+ total_queues_in_db=len(worker_queues_objs),
121
+ active_workers_count=len(active_workers_data)
122
+ )
123
+
124
+ # Count workers per queue
125
+ worker_counts = {}
126
+ for worker_id, worker_data in active_workers_data.items():
127
+ queue_id = worker_data.get("worker_queue_id")
128
+ if queue_id:
129
+ worker_counts[str(queue_id)] = worker_counts.get(str(queue_id), 0) + 1
130
+
131
+ # Transform to expected format
132
+ worker_queues = []
133
+ for queue in worker_queues_objs:
134
+ active_worker_count = worker_counts.get(str(queue.id), 0)
135
+
136
+ logger.debug(
137
+ "checking_queue_for_active_workers",
138
+ queue_id=str(queue.id),
139
+ queue_name=queue.name,
140
+ active_worker_count=active_worker_count
141
+ )
142
+
143
+ # Only include queues with active workers
144
+ if active_worker_count > 0:
145
+ # Get environment name if environment_id is set
146
+ env_name = None
147
+ if queue.environment_id:
148
+ env = db.query(Environment).filter(Environment.id == queue.environment_id).first()
149
+ env_name = env.name if env else None
150
+
151
+ worker_queues.append({
152
+ "queue_name": str(queue.id), # Use queue ID as the task queue name
153
+ "environment_name": env_name,
154
+ "active_workers": active_worker_count,
155
+ "idle_workers": 0, # Not tracked separately
156
+ "total_workers": active_worker_count,
157
+ })
158
+
159
+ logger.info(
160
+ "found_available_worker_queues",
161
+ organization_id=organization_id,
162
+ count=len(worker_queues),
163
+ worker_counts=worker_counts
164
+ )
165
+
166
+ return worker_queues
167
+
168
+ except Exception as e:
169
+ logger.error("failed_to_get_available_worker_queues", error=str(e), organization_id=organization_id)
170
+ return []
171
+ finally:
172
+ db.close()
173
+
174
+
175
+ async def select_worker_queue(
176
+ organization_id: str,
177
+ executor_type: str,
178
+ worker_queue_name: Optional[str] = None,
179
+ environment_name: Optional[str] = None,
180
+ ) -> Tuple[Optional[str], Optional[str]]:
181
+ """
182
+ Select appropriate worker queue for job execution.
183
+
184
+ Routing logic:
185
+ - AUTO: Select first available queue with idle workers (prefer idle over active)
186
+ - SPECIFIC_QUEUE: Use provided worker_queue_name
187
+ - ENVIRONMENT: Select first available queue in specified environment
188
+
189
+ Args:
190
+ organization_id: Organization ID
191
+ executor_type: Routing type ("auto", "specific_queue", "environment")
192
+ worker_queue_name: Explicit queue name (for SPECIFIC_QUEUE)
193
+ environment_name: Environment name (for ENVIRONMENT routing)
194
+
195
+ Returns:
196
+ Tuple of (worker_queue_name, environment_name) or (None, None) if no workers available
197
+ """
198
+ if executor_type == "specific_queue":
199
+ if not worker_queue_name:
200
+ raise ValueError("worker_queue_name is required for 'specific_queue' executor type")
201
+ return worker_queue_name, environment_name
202
+
203
+ # AUTO or ENVIRONMENT routing - need to find available workers
204
+ available_queues = await get_available_worker_queues(organization_id, environment_name)
205
+
206
+ if not available_queues:
207
+ logger.warning(
208
+ "no_available_worker_queues",
209
+ organization_id=organization_id,
210
+ executor_type=executor_type,
211
+ environment_name=environment_name,
212
+ )
213
+ return None, None
214
+
215
+ # Sort by idle workers first, then by total workers
216
+ # This ensures we prefer queues with capacity
217
+ available_queues.sort(
218
+ key=lambda q: (q["idle_workers"], q["total_workers"]),
219
+ reverse=True
220
+ )
221
+
222
+ selected = available_queues[0]
223
+ logger.info(
224
+ "selected_worker_queue",
225
+ organization_id=organization_id,
226
+ queue_name=selected["queue_name"],
227
+ environment_name=selected["environment_name"],
228
+ idle_workers=selected["idle_workers"],
229
+ total_workers=selected["total_workers"],
230
+ )
231
+
232
+ return selected["queue_name"], selected["environment_name"]
233
+
234
+
235
+ async def resolve_job_entity(
236
+ organization_id: str,
237
+ planning_mode: str,
238
+ entity_type: Optional[str],
239
+ entity_id: Optional[str],
240
+ ) -> Tuple[str, str, str]:
241
+ """
242
+ Resolve job entity (agent/team/workflow) and return execution details.
243
+
244
+ For predefined modes, validates that the entity exists and returns its details.
245
+ For on_the_fly mode, returns None values (planner will determine execution).
246
+
247
+ Args:
248
+ organization_id: Organization ID
249
+ planning_mode: Planning mode (on_the_fly, predefined_agent, etc.)
250
+ entity_type: Entity type (agent/team/workflow)
251
+ entity_id: Entity ID
252
+
253
+ Returns:
254
+ Tuple of (execution_type, entity_id, entity_name)
255
+
256
+ Raises:
257
+ ValueError: If entity doesn't exist or validation fails
258
+ """
259
+ if planning_mode == "on_the_fly":
260
+ # Planner will determine execution
261
+ return "agent", None, None
262
+
263
+ # Validate entity exists
264
+ if not entity_type or not entity_id:
265
+ raise ValueError(f"entity_type and entity_id are required for planning_mode '{planning_mode}'")
266
+
267
+ # Map entity type to model
268
+ model_map = {
269
+ "agent": Agent,
270
+ "team": Team,
271
+ "workflow": Workflow,
272
+ }
273
+
274
+ if entity_type not in model_map:
275
+ raise ValueError(f"Invalid entity_type: {entity_type}")
276
+
277
+ Model = model_map[entity_type]
278
+ SessionLocal = get_session_local()
279
+ db = SessionLocal()
280
+
281
+ try:
282
+ entity = db.query(Model).filter(
283
+ Model.id == entity_id,
284
+ Model.organization_id == organization_id
285
+ ).first()
286
+
287
+ if not entity:
288
+ raise ValueError(f"{entity_type} with ID {entity_id} not found")
289
+
290
+ return entity_type, str(entity.id), entity.name
291
+
292
+ except Exception as e:
293
+ logger.error(
294
+ "failed_to_resolve_job_entity",
295
+ error=str(e),
296
+ planning_mode=planning_mode,
297
+ entity_type=entity_type,
298
+ entity_id=entity_id,
299
+ )
300
+ raise ValueError(f"Failed to resolve {entity_type}: {str(e)}")
301
+ finally:
302
+ db.close()
303
+
304
+
305
+ def merge_execution_config(
306
+ base_config: Dict[str, Any],
307
+ override_config: Optional[Dict[str, Any]] = None
308
+ ) -> Dict[str, Any]:
309
+ """
310
+ Merge base job config with execution-specific overrides.
311
+
312
+ Args:
313
+ base_config: Base configuration from job definition
314
+ override_config: Optional overrides for this execution
315
+
316
+ Returns:
317
+ Merged configuration dictionary
318
+ """
319
+ if not override_config:
320
+ return base_config.copy()
321
+
322
+ # Deep merge
323
+ merged = base_config.copy()
324
+ for key, value in override_config.items():
325
+ if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
326
+ merged[key] = {**merged[key], **value}
327
+ else:
328
+ merged[key] = value
329
+
330
+ return merged
@@ -0,0 +1,293 @@
1
+ """Kubiya API client for authentication and runner management"""
2
+
3
+ import httpx
4
+ import os
5
+ from typing import Optional, Dict, List
6
+ import structlog
7
+
8
+ logger = structlog.get_logger()
9
+
10
+ KUBIYA_API_BASE = os.environ.get("KUBIYA_API_BASE", "https://api.kubiya.ai")
11
+
12
+
13
+ class KubiyaClient:
14
+ """Client for Kubiya API"""
15
+
16
+ def __init__(self, api_base: str = KUBIYA_API_BASE):
17
+ self.api_base = api_base.rstrip("/")
18
+ self.client = httpx.AsyncClient(timeout=30.0)
19
+
20
+ async def validate_token_and_get_org(self, token: str) -> Optional[Dict]:
21
+ """
22
+ Validate token with Kubiya API and get organization details.
23
+ Automatically tries both Bearer (Auth0 idToken) and UserKey (API key) authentication.
24
+
25
+ Args:
26
+ token: Authentication token (Bearer/idToken or UserKey/API key)
27
+
28
+ Returns:
29
+ Dict with organization details:
30
+ {
31
+ "id": "org-uuid",
32
+ "name": "Organization Name",
33
+ "slug": "org-slug"
34
+ }
35
+ None if invalid token
36
+ """
37
+ try:
38
+ # Try Bearer authentication first (Auth0 idToken)
39
+ response = await self.client.get(
40
+ f"{self.api_base}/api/v1/users/me",
41
+ headers={"Authorization": f"Bearer {token}"},
42
+ )
43
+
44
+ # If Bearer fails with 401, try UserKey (API key)
45
+ if response.status_code == 401:
46
+ logger.debug("kubiya_bearer_auth_failed_trying_userkey")
47
+ response = await self.client.get(
48
+ f"{self.api_base}/api/v1/users/me",
49
+ headers={"Authorization": f"UserKey {token}"},
50
+ )
51
+
52
+ if response.status_code == 200:
53
+ data = response.json()
54
+
55
+ # Log full response for debugging
56
+ logger.info(
57
+ "kubiya_api_response",
58
+ response_keys=list(data.keys()),
59
+ has_org=bool(data.get("org")),
60
+ has_org_id=bool(data.get("org_id")),
61
+ )
62
+
63
+ # Extract organization from response
64
+ # Kubiya API returns org/org_id at root level, not nested
65
+ org_id = data.get("org") or data.get("org_id") or data.get("organization", {}).get("uuid")
66
+ org_name = data.get("org_name") or data.get("organization_name") or data.get("organization", {}).get("name")
67
+ org_slug = data.get("org_slug") or data.get("organization_slug") or data.get("organization", {}).get("slug")
68
+
69
+ org_data = {
70
+ "id": org_id,
71
+ "name": org_name,
72
+ "slug": org_slug,
73
+ "user_id": data.get("uuid") or data.get("id"),
74
+ "user_email": data.get("email"),
75
+ "user_name": data.get("name"),
76
+ }
77
+
78
+ logger.info(
79
+ "kubiya_token_validated",
80
+ org_id=org_data["id"],
81
+ org_name=org_data["name"],
82
+ user_email=org_data.get("user_email"),
83
+ )
84
+
85
+ return org_data
86
+
87
+ else:
88
+ logger.warning(
89
+ "kubiya_token_invalid",
90
+ status_code=response.status_code,
91
+ )
92
+ return None
93
+
94
+ except Exception as e:
95
+ logger.error("kubiya_api_error", error=str(e))
96
+ return None
97
+
98
+ async def get_runners(self, token: str, org_id: str) -> List[Dict]:
99
+ """
100
+ Get available runners for organization from Kubiya API.
101
+ Automatically tries both Bearer and UserKey authentication methods.
102
+
103
+ Args:
104
+ token: Authentication token (Bearer/idToken or UserKey/API key)
105
+ org_id: Organization UUID
106
+
107
+ Returns:
108
+ List of runner dicts:
109
+ [
110
+ {
111
+ "name": "runner-name",
112
+ "wss_url": "...",
113
+ "task_id": "...",
114
+ ...
115
+ }
116
+ ]
117
+ """
118
+ try:
119
+ # Try Bearer authentication first (Auth0 idToken)
120
+ response = await self.client.get(
121
+ f"{self.api_base}/api/v3/runners",
122
+ headers={"Authorization": f"Bearer {token}"},
123
+ )
124
+
125
+ # If Bearer fails with 401, try UserKey (API key)
126
+ if response.status_code == 401:
127
+ logger.info(
128
+ "kubiya_runners_bearer_failed_trying_userkey",
129
+ org_id=org_id,
130
+ )
131
+ response = await self.client.get(
132
+ f"{self.api_base}/api/v3/runners",
133
+ headers={"Authorization": f"UserKey {token}"},
134
+ )
135
+
136
+ if response.status_code == 200:
137
+ runners = response.json()
138
+
139
+ # Handle both array response and object response
140
+ if isinstance(runners, dict):
141
+ # If it's a dict, extract the array from common keys
142
+ runners = runners.get('runners', runners.get('data', []))
143
+
144
+ # Ensure it's a list
145
+ if not isinstance(runners, list):
146
+ logger.warning(
147
+ "kubiya_runners_unexpected_format",
148
+ type=type(runners).__name__,
149
+ )
150
+ runners = []
151
+
152
+ logger.info(
153
+ "kubiya_runners_fetched",
154
+ org_id=org_id,
155
+ runner_count=len(runners),
156
+ )
157
+
158
+ return runners
159
+
160
+ else:
161
+ logger.warning(
162
+ "kubiya_runners_fetch_failed",
163
+ status_code=response.status_code,
164
+ )
165
+ return []
166
+
167
+ except Exception as e:
168
+ logger.error("kubiya_runners_error", error=str(e))
169
+ return []
170
+
171
+ async def get_temporal_credentials(self, token: str) -> Optional[Dict]:
172
+ """
173
+ Fetch organization-specific Temporal credentials from Kubiya API.
174
+ Automatically tries both Bearer (Auth0 idToken) and UserKey (API key) authentication.
175
+
176
+ Args:
177
+ token: Authentication token (Bearer/idToken or UserKey/API key)
178
+
179
+ Returns:
180
+ Dict with Temporal credentials:
181
+ {
182
+ "apiKey": "...",
183
+ "apiKeyId": "...",
184
+ "namespace": "kubiya-ai.lpagu",
185
+ "org": "kubiya-ai",
186
+ "ttl": "2026-01-07T14:38:20Z",
187
+ "created_at": "...",
188
+ "updated_at": "..."
189
+ }
190
+ None if request fails
191
+ """
192
+ try:
193
+ # Try Bearer authentication first (Auth0 idToken)
194
+ response = await self.client.get(
195
+ f"{self.api_base}/api/v1/org/temporal",
196
+ headers={"Authorization": f"Bearer {token}"},
197
+ )
198
+
199
+ # If Bearer fails with 401, try UserKey (API key)
200
+ if response.status_code == 401:
201
+ logger.debug("kubiya_temporal_bearer_auth_failed_trying_userkey")
202
+ response = await self.client.get(
203
+ f"{self.api_base}/api/v1/org/temporal",
204
+ headers={"Authorization": f"UserKey {token}"},
205
+ )
206
+
207
+ if response.status_code == 200:
208
+ data = response.json()
209
+ logger.info(
210
+ "kubiya_temporal_credentials_fetched",
211
+ namespace=data.get("namespace"),
212
+ org=data.get("org"),
213
+ ttl=data.get("ttl"),
214
+ has_api_key=bool(data.get("apiKey")),
215
+ )
216
+ return data
217
+ else:
218
+ logger.warning(
219
+ "kubiya_temporal_credentials_fetch_failed",
220
+ status_code=response.status_code,
221
+ response_text=response.text[:200] if hasattr(response, 'text') else None,
222
+ )
223
+ return None
224
+
225
+ except Exception as e:
226
+ logger.error("kubiya_temporal_credentials_error", error=str(e))
227
+ return None
228
+
229
+ async def register_runner_heartbeat(
230
+ self, token: str, org_id: str, runner_name: str, metadata: Dict = None
231
+ ) -> bool:
232
+ """
233
+ Register runner heartbeat with Kubiya API.
234
+
235
+ Called by workers to report they're alive and polling.
236
+
237
+ Args:
238
+ token: Service token for worker
239
+ org_id: Organization UUID
240
+ runner_name: Runner name
241
+ metadata: Additional metadata (capabilities, version, etc.)
242
+
243
+ Returns:
244
+ True if successful, False otherwise
245
+ """
246
+ try:
247
+ response = await self.client.post(
248
+ f"{self.api_base}/api/v1/runners/heartbeat",
249
+ headers={"Authorization": f"UserKey {token}"},
250
+ json={
251
+ "organization_id": org_id,
252
+ "runner_name": runner_name,
253
+ "status": "active",
254
+ "metadata": metadata or {},
255
+ "task_queue": f"{org_id}.{runner_name}",
256
+ },
257
+ )
258
+
259
+ if response.status_code in [200, 201, 204]:
260
+ logger.info(
261
+ "kubiya_heartbeat_sent",
262
+ org_id=org_id,
263
+ runner_name=runner_name,
264
+ )
265
+ return True
266
+ else:
267
+ logger.warning(
268
+ "kubiya_heartbeat_failed",
269
+ status_code=response.status_code,
270
+ )
271
+ return False
272
+
273
+ except Exception as e:
274
+ logger.error("kubiya_heartbeat_error", error=str(e))
275
+ return False
276
+
277
+ async def close(self):
278
+ """Close the HTTP client"""
279
+ await self.client.aclose()
280
+
281
+
282
+ # Singleton instance
283
+ _kubiya_client: Optional[KubiyaClient] = None
284
+
285
+
286
+ def get_kubiya_client() -> KubiyaClient:
287
+ """Get or create Kubiya client singleton"""
288
+ global _kubiya_client
289
+
290
+ if _kubiya_client is None:
291
+ _kubiya_client = KubiyaClient()
292
+
293
+ return _kubiya_client