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,645 @@
1
+ """Authentication middleware for multi-tenant API with Kubiya integration"""
2
+
3
+ import os
4
+ import json
5
+ import jwt
6
+ import hashlib
7
+ from datetime import datetime, timedelta
8
+ from typing import Optional, Dict, Any
9
+ from fastapi import Request, HTTPException, status
10
+ from fastapi.security import HTTPBearer
11
+ import httpx
12
+ import structlog
13
+
14
+ from control_plane_api.app.lib.redis_client import get_redis_client
15
+ from control_plane_api.app.database import get_session_local
16
+ from sqlalchemy.orm import Session
17
+ from control_plane_api.app.models.worker import WorkerHeartbeat
18
+
19
+ # Import OpenTelemetry for span enrichment (optional)
20
+ from control_plane_api.app.observability.optional import get_current_span
21
+
22
+ logger = structlog.get_logger()
23
+
24
+ security = HTTPBearer(auto_error=False)
25
+
26
+ # Cache TTL settings
27
+ DEFAULT_CACHE_TTL = 3600 # 1 hour default
28
+ MAX_CACHE_TTL = 86400 # 24 hours max
29
+
30
+ # Shared httpx client for auth validation (reuse connections)
31
+ _auth_http_client: Optional[httpx.AsyncClient] = None
32
+
33
+ def get_auth_http_client() -> httpx.AsyncClient:
34
+ """Get or create shared httpx client for auth validation"""
35
+ global _auth_http_client
36
+ if _auth_http_client is None:
37
+ _auth_http_client = httpx.AsyncClient(
38
+ timeout=3.0,
39
+ limits=httpx.Limits(max_connections=10, max_keepalive_connections=5)
40
+ )
41
+ return _auth_http_client
42
+
43
+
44
+ def get_token_cache_key(token: str) -> str:
45
+ """
46
+ Generate a unique cache key for a token using SHA256 hash.
47
+
48
+ This ensures each unique token gets its own cache entry, preventing
49
+ collisions and ensuring proper isolation between different tokens/orgs.
50
+
51
+ Args:
52
+ token: Authentication token
53
+
54
+ Returns:
55
+ Cache key in format: auth:token:sha256:{hash}
56
+ """
57
+ token_hash = hashlib.sha256(token.encode()).hexdigest()
58
+ return f"auth:token:sha256:{token_hash}"
59
+
60
+
61
+ async def extract_token_from_headers(request: Request) -> Optional[str]:
62
+ """
63
+ Extract authentication token from request headers.
64
+
65
+ Supports multiple header formats for compatibility:
66
+ - Authorization: Bearer <token>
67
+ - Authorization: UserKey <token>
68
+ - Authorization: Baerer <token> (typo compatibility)
69
+ - Authorization: <token> (raw token)
70
+ - UserKey: <token>
71
+
72
+ Args:
73
+ request: FastAPI request object
74
+
75
+ Returns:
76
+ Extracted token or None
77
+ """
78
+ # Check Authorization header
79
+ auth_header = request.headers.get("authorization")
80
+ if auth_header:
81
+ # Handle "Bearer <token>"
82
+ if auth_header.startswith("Bearer "):
83
+ return auth_header[7:]
84
+ # Handle "UserKey <token>"
85
+ elif auth_header.startswith("UserKey "):
86
+ return auth_header[8:]
87
+ # Handle "Baerer <token>" (common typo)
88
+ elif auth_header.startswith("Baerer "):
89
+ logger.warning("api_key_typo_detected", message="'Baerer' should be 'Bearer'")
90
+ return auth_header[7:]
91
+ # Handle raw token without prefix
92
+ elif " " not in auth_header:
93
+ return auth_header
94
+
95
+ # Check UserKey header (alternative)
96
+ userkey_header = request.headers.get("userkey")
97
+ if userkey_header:
98
+ return userkey_header
99
+
100
+ return None
101
+
102
+
103
+ def decode_jwt_token(token: str) -> Optional[Dict[str, Any]]:
104
+ """
105
+ Decode JWT token without verification to extract expiry and metadata.
106
+
107
+ Args:
108
+ token: JWT token string
109
+
110
+ Returns:
111
+ Decoded token payload or None
112
+ """
113
+ try:
114
+ # Decode without verification (we just need exp and other metadata)
115
+ decoded = jwt.decode(token, options={"verify_signature": False})
116
+ return decoded
117
+ except Exception as e:
118
+ logger.warning("jwt_decode_failed", error=str(e))
119
+ return None
120
+
121
+
122
+ def get_jwt_algorithm(token: str) -> Optional[str]:
123
+ """
124
+ Extract the algorithm from a JWT token's header.
125
+
126
+ Args:
127
+ token: JWT token string
128
+
129
+ Returns:
130
+ Algorithm string (e.g., "RS256", "HS256") or None
131
+ """
132
+ try:
133
+ header = jwt.get_unverified_header(token)
134
+ return header.get("alg")
135
+ except Exception:
136
+ return None
137
+
138
+
139
+ def is_kubiya_api_key_jwt(token: str) -> bool:
140
+ """
141
+ Check if a JWT token is a Kubiya API key (not an Auth0 JWT).
142
+
143
+ Kubiya API keys use HS256 signing, while Auth0 uses RS256.
144
+ Only Kubiya API keys should be validated locally.
145
+
146
+ Args:
147
+ token: JWT token string
148
+
149
+ Returns:
150
+ True if this is a Kubiya API key JWT, False otherwise
151
+ """
152
+ alg = get_jwt_algorithm(token)
153
+ # Auth0 uses RS256 (RSA), Kubiya API keys use HS256 or similar symmetric algorithms
154
+ # If it's RS256, it's an Auth0 token - don't validate locally
155
+ if alg == "RS256":
156
+ return False
157
+ return True
158
+
159
+
160
+ def get_cache_ttl_from_token(token: str) -> int:
161
+ """
162
+ Extract expiry from JWT token and calculate appropriate cache TTL.
163
+
164
+ Args:
165
+ token: JWT token string
166
+
167
+ Returns:
168
+ TTL in seconds (minimum 60s, maximum MAX_CACHE_TTL)
169
+ """
170
+ decoded = decode_jwt_token(token)
171
+ if not decoded or "exp" not in decoded:
172
+ return DEFAULT_CACHE_TTL
173
+
174
+ try:
175
+ exp_timestamp = decoded["exp"]
176
+ exp_datetime = datetime.fromtimestamp(exp_timestamp)
177
+ now = datetime.now()
178
+
179
+ # Calculate time until expiry
180
+ ttl = int((exp_datetime - now).total_seconds())
181
+
182
+ # Ensure TTL is reasonable (at least 60s, max MAX_CACHE_TTL)
183
+ ttl = max(60, min(ttl, MAX_CACHE_TTL))
184
+
185
+ logger.debug("calculated_cache_ttl", ttl=ttl, exp=exp_timestamp)
186
+ return ttl
187
+ except Exception as e:
188
+ logger.warning("ttl_calculation_failed", error=str(e))
189
+ return DEFAULT_CACHE_TTL
190
+
191
+
192
+ async def validate_kubiya_api_key(auth_header: str, use_userkey: bool = False) -> Optional[Dict[str, Any]]:
193
+ """
194
+ Validate API key with Kubiya API by calling /api/v1/users/self or /api/v1/users/current.
195
+
196
+ Args:
197
+ auth_header: Full Authorization header value (e.g., "Bearer xyz123" or "UserKey xyz123")
198
+ use_userkey: If True, use "UserKey" prefix instead of "Bearer" for worker authentication
199
+
200
+ Returns:
201
+ User object from Kubiya API or None if invalid
202
+ """
203
+ base_url = os.getenv("KUBIYA_API_BASE") or os.getenv("KUBIYA_API_URL") or "https://api.kubiya.ai"
204
+ endpoints = ["/api/v1/users/self", "/api/v1/users/current"]
205
+
206
+ # If use_userkey is True, ensure the header uses "UserKey" prefix
207
+ if use_userkey and not auth_header.startswith("UserKey "):
208
+ # Extract token and reformat with UserKey prefix
209
+ token = auth_header.replace("Bearer ", "").replace("UserKey ", "").strip()
210
+ auth_header = f"UserKey {token}"
211
+ logger.debug("reformatted_auth_header_for_worker", prefix="UserKey")
212
+
213
+ client = get_auth_http_client()
214
+
215
+ # Get current span to suppress error status for expected 401s
216
+ current_span = get_current_span()
217
+
218
+ for endpoint in endpoints:
219
+ try:
220
+ url = f"{base_url}{endpoint}"
221
+ logger.debug("validating_token_with_kubiya", url=url, use_userkey=use_userkey)
222
+
223
+ response = await client.get(
224
+ url,
225
+ headers={"Authorization": auth_header}
226
+ )
227
+
228
+ if response.status_code == 200:
229
+ user_data = response.json()
230
+ logger.info(
231
+ "token_validated_with_kubiya",
232
+ endpoint=endpoint,
233
+ user_id=user_data.get("id"),
234
+ org_id=user_data.get("organization_id"),
235
+ use_userkey=use_userkey
236
+ )
237
+ return user_data
238
+
239
+ # 401 is expected when token format doesn't match - not an error
240
+ # Log at debug level and continue to next auth method
241
+ if response.status_code == 401:
242
+ logger.debug(
243
+ "kubiya_auth_attempt_unauthorized",
244
+ endpoint=endpoint,
245
+ use_userkey=use_userkey,
246
+ note="Expected when trying different auth methods"
247
+ )
248
+ else:
249
+ logger.debug(
250
+ "kubiya_validation_attempt_failed",
251
+ endpoint=endpoint,
252
+ status=response.status_code,
253
+ use_userkey=use_userkey
254
+ )
255
+
256
+ except Exception as e:
257
+ logger.debug("kubiya_validation_error", endpoint=endpoint, error=str(e))
258
+ continue
259
+
260
+ # Only log as debug since this is expected when using cached auth
261
+ logger.debug("token_validation_skipped", use_userkey=use_userkey, note="Relying on cached authentication")
262
+ return None
263
+
264
+
265
+ async def get_cached_user_data(token: str) -> Optional[Dict[str, Any]]:
266
+ """
267
+ Get cached user data from Redis using SHA256 hash of token.
268
+
269
+ Args:
270
+ token: Authentication token (full token will be hashed)
271
+
272
+ Returns:
273
+ Cached user data or None
274
+ """
275
+ redis = get_redis_client()
276
+ if not redis:
277
+ return None
278
+
279
+ try:
280
+ cache_key = get_token_cache_key(token)
281
+ cached_data = await redis.get(cache_key)
282
+
283
+ if cached_data:
284
+ logger.debug("cache_hit", cache_key=cache_key[:40] + "...")
285
+ if isinstance(cached_data, bytes):
286
+ cached_data = cached_data.decode('utf-8')
287
+ data = json.loads(cached_data)
288
+
289
+ # Invalidate cache if RS256 token was incorrectly cached as JWT auth_type
290
+ # RS256 tokens are Auth0 JWTs and should use Bearer auth, not JWT/UserKey
291
+ cached_auth_type = data.get("_auth_type")
292
+ if cached_auth_type == "JWT":
293
+ alg = get_jwt_algorithm(token)
294
+ if alg == "RS256":
295
+ logger.info("cache_invalidated_rs256_jwt", cache_key=cache_key[:40] + "...", reason="RS256 token incorrectly cached as JWT")
296
+ await redis.delete(cache_key)
297
+ return None
298
+
299
+ return data
300
+
301
+ logger.debug("cache_miss", cache_key=cache_key[:40] + "...")
302
+ return None
303
+
304
+ except Exception as e:
305
+ logger.warning("cache_read_failed", error=str(e))
306
+ return None
307
+
308
+
309
+ async def cache_user_data(token: str, user_data: Dict[str, Any]) -> None:
310
+ """
311
+ Cache user data in Redis with TTL based on token expiry.
312
+ Uses SHA256 hash of the full token as cache key for uniqueness.
313
+
314
+ Args:
315
+ token: Authentication token (full token will be hashed)
316
+ user_data: User data to cache
317
+ """
318
+ redis = get_redis_client()
319
+ if not redis:
320
+ return
321
+
322
+ try:
323
+ cache_key = get_token_cache_key(token)
324
+ ttl = get_cache_ttl_from_token(token)
325
+
326
+ await redis.set(
327
+ cache_key,
328
+ json.dumps(user_data),
329
+ ex=ttl # Set expiry in seconds
330
+ )
331
+
332
+ logger.info("cache_write_success", cache_key=cache_key[:40] + "...", ttl=ttl)
333
+
334
+ except Exception as e:
335
+ logger.warning("cache_write_failed", error=str(e))
336
+
337
+
338
+ async def get_organization_from_worker_token(token: str) -> Optional[dict]:
339
+ """
340
+ Validate worker token and return organization data.
341
+
342
+ Worker tokens are in format: worker_{uuid} and stored in worker_heartbeats.worker_metadata
343
+
344
+ Args:
345
+ token: Worker authentication token
346
+
347
+ Returns:
348
+ Organization dict or None if invalid
349
+ """
350
+ if not token.startswith("worker_"):
351
+ return None
352
+
353
+ SessionLocal = get_session_local()
354
+ db = SessionLocal()
355
+ try:
356
+ # Query worker_heartbeats for active workers
357
+ workers = db.query(WorkerHeartbeat).filter(
358
+ WorkerHeartbeat.status == "active"
359
+ ).all()
360
+
361
+ # Find worker with matching token in metadata
362
+ for worker in workers:
363
+ worker_metadata = worker.worker_metadata or {}
364
+ if worker_metadata.get("worker_token") == token:
365
+ # Return minimal org data for worker
366
+ return {
367
+ "id": worker.organization_id,
368
+ "name": "Worker", # Workers don't need full org details
369
+ "slug": "worker",
370
+ "user_id": "worker",
371
+ "user_email": "worker@system",
372
+ "user_name": "Worker Process"
373
+ }
374
+
375
+ logger.warning("worker_token_not_found", token_prefix=token[:15])
376
+ return None
377
+
378
+ except Exception as e:
379
+ logger.error("worker_token_validation_failed", error=str(e))
380
+ return None
381
+ finally:
382
+ db.close()
383
+
384
+
385
+ async def get_organization_allow_worker(request: Request) -> dict:
386
+ """
387
+ Dependency that accepts both user tokens and worker tokens.
388
+
389
+ This is used by endpoints that workers need to call (like execution updates).
390
+
391
+ Flow:
392
+ 1. Extract token from Authorization header
393
+ 2. If token starts with "worker_", validate as worker token
394
+ 3. Otherwise, validate as user token (with caching)
395
+ 4. Return organization data
396
+
397
+ Args:
398
+ request: FastAPI request object
399
+
400
+ Returns:
401
+ Organization dict with id, name, slug, user_id, etc.
402
+
403
+ Raises:
404
+ HTTPException: 401 if authentication fails
405
+ """
406
+ # Extract token from headers
407
+ token = await extract_token_from_headers(request)
408
+
409
+ if not token:
410
+ logger.warning("auth_token_missing", path=request.url.path)
411
+ raise HTTPException(
412
+ status_code=status.HTTP_401_UNAUTHORIZED,
413
+ detail={
414
+ "error": "Unauthorized",
415
+ "message": "Authorization header is required",
416
+ "hint": "Include 'Authorization: Bearer <token>' header"
417
+ }
418
+ )
419
+
420
+ # Check if this is a worker token (fast path)
421
+ if token.startswith("worker_"):
422
+ logger.debug("validating_worker_token", path=request.url.path)
423
+ org = await get_organization_from_worker_token(token)
424
+ if org:
425
+ # Store in request state for later use
426
+ request.state.organization = org
427
+ request.state.worker_token = token
428
+
429
+ # Enrich span with worker organizational context
430
+ span = get_current_span()
431
+ if span and span.is_recording():
432
+ try:
433
+ span.set_attribute("organization.id", org["id"])
434
+ span.set_attribute("organization.name", org.get("name", ""))
435
+ span.set_attribute("auth.type", "worker_token")
436
+ if org.get("worker_id"):
437
+ span.set_attribute("worker.id", org["worker_id"])
438
+ except Exception as e:
439
+ logger.warning("worker_span_enrichment_failed", error=str(e))
440
+
441
+ logger.info(
442
+ "worker_authenticated",
443
+ org_id=org["id"],
444
+ path=request.url.path,
445
+ method=request.method,
446
+ )
447
+ return org
448
+
449
+ # Worker token invalid
450
+ logger.warning("worker_token_invalid", path=request.url.path)
451
+ raise HTTPException(
452
+ status_code=status.HTTP_401_UNAUTHORIZED,
453
+ detail={
454
+ "error": "Unauthorized",
455
+ "message": "Invalid or expired worker token",
456
+ "hint": "Worker token not found in active workers"
457
+ }
458
+ )
459
+
460
+ # Fall back to regular user authentication
461
+ return await get_current_organization(request)
462
+
463
+
464
+ async def get_current_organization(request: Request) -> dict:
465
+ """
466
+ Dependency to get current organization and validate authentication.
467
+
468
+ Flow:
469
+ 1. Extract token from Authorization header
470
+ 2. Check Redis cache for user data
471
+ 3. If not cached, validate with Kubiya API
472
+ 4. Cache the user data with TTL based on JWT expiry
473
+ 5. Return organization data
474
+
475
+ Args:
476
+ request: FastAPI request object
477
+
478
+ Returns:
479
+ Organization dict:
480
+ {
481
+ "id": "org-uuid",
482
+ "name": "Organization Name",
483
+ "slug": "org-slug",
484
+ "user_id": "user-uuid",
485
+ "user_email": "user@example.com",
486
+ "user_name": "User Name"
487
+ }
488
+
489
+ Raises:
490
+ HTTPException: 401 if authentication fails
491
+ """
492
+ # Extract token from headers
493
+ token = await extract_token_from_headers(request)
494
+
495
+ if not token:
496
+ logger.warning("auth_token_missing", path=request.url.path)
497
+ raise HTTPException(
498
+ status_code=status.HTTP_401_UNAUTHORIZED,
499
+ detail={
500
+ "error": "Unauthorized",
501
+ "message": "Authorization header is required",
502
+ "hint": "Include 'Authorization: Bearer <token>' header"
503
+ }
504
+ )
505
+
506
+ # Try to get cached user data
507
+ user_data = await get_cached_user_data(token)
508
+
509
+ # Track which auth type succeeded
510
+ auth_type = None
511
+
512
+ # If not cached, validate with Kubiya API or fallback to JWT validation
513
+ if not user_data:
514
+ # First, try to validate JWT structure locally - but only for Kubiya API keys (HS256)
515
+ # Auth0 tokens (RS256) should be validated via Kubiya API to ensure proper authorization
516
+ jwt_data = decode_jwt_token(token)
517
+
518
+ # If JWT is valid, has required fields, AND is a Kubiya API key (not Auth0), use it directly
519
+ if jwt_data and jwt_data.get("organization") and jwt_data.get("email") and is_kubiya_api_key_jwt(token):
520
+ logger.info("using_jwt_local_validation", org=jwt_data.get("organization"), email=jwt_data.get("email"))
521
+ user_data = {
522
+ "org": jwt_data.get("organization"),
523
+ "email": jwt_data.get("email"),
524
+ "uuid": jwt_data.get("token_id") or jwt_data.get("user_id"),
525
+ "name": jwt_data.get("token_name", {}).get("name") if isinstance(jwt_data.get("token_name"), dict) else jwt_data.get("name"),
526
+ }
527
+ auth_type = "JWT"
528
+ # Also support management API keys that have 'organization' but no 'email'
529
+ # These are service-level tokens with scopes like 'manage:agents'
530
+ elif jwt_data and jwt_data.get("organization") and jwt_data.get("scopes"):
531
+ logger.info("using_management_api_key", org=jwt_data.get("organization"), scopes=jwt_data.get("scopes"))
532
+ user_data = {
533
+ "org": jwt_data.get("organization"),
534
+ "email": f"management-api@{jwt_data.get('organization')}.kubiya.ai", # Synthetic email for management keys
535
+ "uuid": jwt_data.get("jti") or jwt_data.get("token_id"),
536
+ "name": "Management API Key",
537
+ }
538
+ auth_type = "ManagementKey"
539
+ else:
540
+ # Fallback to external API validation
541
+ logger.debug("validating_with_kubiya_api", path=request.url.path)
542
+ auth_header = f"Bearer {token}" if not token.startswith("Bearer ") else token
543
+
544
+ # Try Bearer first (for regular user tokens)
545
+ user_data = await validate_kubiya_api_key(auth_header, use_userkey=False)
546
+ if user_data:
547
+ auth_type = "Bearer"
548
+
549
+ # If Bearer fails, try UserKey (for API keys/worker tokens)
550
+ if not user_data:
551
+ logger.debug("bearer_auth_failed_trying_userkey", path=request.url.path)
552
+ user_data = await validate_kubiya_api_key(auth_header, use_userkey=True)
553
+ if user_data:
554
+ auth_type = "UserKey"
555
+
556
+ if not user_data:
557
+ logger.warning("authentication_failed", path=request.url.path)
558
+ raise HTTPException(
559
+ status_code=status.HTTP_401_UNAUTHORIZED,
560
+ detail={
561
+ "error": "Unauthorized",
562
+ "message": "Invalid or expired authentication token",
563
+ "hint": "Ensure you're using a valid Kubiya API token"
564
+ }
565
+ )
566
+
567
+ # Cache the validated user data with auth type
568
+ await cache_user_data(token, {**user_data, "_auth_type": auth_type})
569
+ else:
570
+ # Retrieve auth type from cache
571
+ auth_type = user_data.get("_auth_type", "Bearer")
572
+
573
+ # Extract organization slug from Kubiya API response
574
+ # Kubiya API returns the org slug in the "org" field (e.g., "kubiya-ai")
575
+ # We use this slug as the primary organization identifier throughout the system
576
+ org_slug = user_data.get("org")
577
+
578
+ if not org_slug:
579
+ logger.error("org_slug_missing_in_user_data", user_data=user_data)
580
+ raise HTTPException(
581
+ status_code=status.HTTP_401_UNAUTHORIZED,
582
+ detail={
583
+ "error": "Unauthorized",
584
+ "message": "No organization slug found in user data",
585
+ "hint": "User data must contain 'org' field from Kubiya API"
586
+ }
587
+ )
588
+
589
+ # Build organization object
590
+ # We use the org slug as the primary identifier (id field)
591
+ # This slug is used for all database operations (agents, teams, executions, etc.)
592
+ user_email = user_data.get("email")
593
+ user_name = user_data.get("name") or (user_email.split("@")[0] if user_email else None) # Fallback to email username
594
+
595
+ organization = {
596
+ "id": org_slug, # Use slug as ID (e.g., "kubiya-ai")
597
+ "name": user_data.get("org"), # Also use slug as display name
598
+ "slug": org_slug, # The slug itself
599
+ "user_id": user_data.get("uuid"), # User UUID from Kubiya
600
+ "user_email": user_email,
601
+ "user_name": user_name,
602
+ "user_avatar": user_data.get("picture") or user_data.get("avatar") or user_data.get("image"), # Avatar from Auth0/Kubiya
603
+ "user_status": user_data.get("user_status") or user_data.get("status"),
604
+ "user_groups": user_data.get("groups") or user_data.get("user_groups"),
605
+ }
606
+
607
+ # Store in request state for later use
608
+ request.state.organization = organization
609
+ request.state.kubiya_token = token
610
+ request.state.kubiya_auth_type = auth_type # Store whether to use Bearer or UserKey
611
+ request.state.user_data = user_data
612
+
613
+ # Enrich current span with organizational context for distributed tracing
614
+ span = get_current_span()
615
+ if span and span.is_recording():
616
+ try:
617
+ span.set_attribute("organization.id", organization["id"])
618
+ span.set_attribute("organization.name", organization.get("name", ""))
619
+ span.set_attribute("organization.slug", organization.get("slug", ""))
620
+
621
+ if organization.get("user_id"):
622
+ span.set_attribute("user.id", organization["user_id"])
623
+ if organization.get("user_email"):
624
+ span.set_attribute("user.email", organization["user_email"])
625
+ if organization.get("user_name"):
626
+ span.set_attribute("user.name", organization["user_name"])
627
+
628
+ logger.debug(
629
+ "span_enriched_in_auth",
630
+ org_id=organization["id"],
631
+ trace_id=format(span.get_span_context().trace_id, '032x')
632
+ )
633
+ except Exception as e:
634
+ logger.warning("span_enrichment_failed_in_auth", error=str(e))
635
+
636
+ logger.info(
637
+ "request_authenticated",
638
+ org_id=organization["id"],
639
+ user_id=organization.get("user_id"),
640
+ path=request.url.path,
641
+ method=request.method,
642
+ cached=user_data is not None
643
+ )
644
+
645
+ return organization