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,166 @@
1
+ """
2
+ LiteLLM Pricing Integration - Fetch and cache model pricing data
3
+ """
4
+
5
+ import httpx
6
+ import structlog
7
+ from typing import Dict, Optional
8
+ from datetime import datetime, timedelta
9
+ import asyncio
10
+
11
+ logger = structlog.get_logger()
12
+
13
+ # Cache for pricing data
14
+ _pricing_cache: Optional[Dict] = None
15
+ _cache_timestamp: Optional[datetime] = None
16
+ _cache_lock = asyncio.Lock()
17
+
18
+ PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/refs/heads/main/model_prices_and_context_window.json"
19
+ CACHE_TTL_HOURS = 24 # Refresh pricing data daily
20
+
21
+
22
+ async def get_litellm_pricing() -> Dict:
23
+ """
24
+ Fetch LiteLLM pricing data with caching
25
+
26
+ Returns:
27
+ Dict containing model pricing information
28
+ """
29
+ global _pricing_cache, _cache_timestamp
30
+
31
+ async with _cache_lock:
32
+ # Check if cache is valid
33
+ if _pricing_cache and _cache_timestamp:
34
+ age = datetime.utcnow() - _cache_timestamp
35
+ if age < timedelta(hours=CACHE_TTL_HOURS):
36
+ logger.debug("litellm_pricing_cache_hit", age_hours=age.total_seconds() / 3600)
37
+ return _pricing_cache
38
+
39
+ # Fetch fresh pricing data
40
+ logger.info("fetching_litellm_pricing", url=PRICING_URL)
41
+ try:
42
+ async with httpx.AsyncClient(timeout=30.0) as client:
43
+ response = await client.get(PRICING_URL)
44
+ response.raise_for_status()
45
+ pricing_data = response.json()
46
+
47
+ # Update cache
48
+ _pricing_cache = pricing_data
49
+ _cache_timestamp = datetime.utcnow()
50
+
51
+ logger.info(
52
+ "litellm_pricing_fetched_successfully",
53
+ models_count=len(pricing_data),
54
+ cached_until=(_cache_timestamp + timedelta(hours=CACHE_TTL_HOURS)).isoformat()
55
+ )
56
+
57
+ return pricing_data
58
+ except Exception as e:
59
+ logger.error("litellm_pricing_fetch_failed", error=str(e), error_type=type(e).__name__)
60
+ # Return empty dict on failure
61
+ return {}
62
+
63
+
64
+ def get_model_pricing(model_id: str, pricing_data: Dict) -> Optional[Dict]:
65
+ """
66
+ Get pricing information for a specific model
67
+
68
+ Args:
69
+ model_id: Model identifier (e.g., "claude-sonnet-4", "gpt-4o")
70
+ pricing_data: Full pricing data from LiteLLM
71
+
72
+ Returns:
73
+ Dict with pricing info or None if not found
74
+ """
75
+ # Try exact match first
76
+ if model_id in pricing_data:
77
+ return pricing_data[model_id]
78
+
79
+ # Try common variations
80
+ variations = [
81
+ model_id,
82
+ f"openai/{model_id}",
83
+ f"anthropic/{model_id}",
84
+ f"anthropic.{model_id}",
85
+ f"bedrock/{model_id}",
86
+ ]
87
+
88
+ for variation in variations:
89
+ if variation in pricing_data:
90
+ return pricing_data[variation]
91
+
92
+ logger.warning("model_pricing_not_found", model_id=model_id, tried_variations=variations)
93
+ return None
94
+
95
+
96
+ def calculate_llm_cost(
97
+ model_id: str,
98
+ estimated_input_tokens: int,
99
+ estimated_output_tokens: int,
100
+ pricing_data: Dict
101
+ ) -> tuple[float, float, float]:
102
+ """
103
+ Calculate LLM cost for a model
104
+
105
+ Args:
106
+ model_id: Model identifier
107
+ estimated_input_tokens: Expected input tokens
108
+ estimated_output_tokens: Expected output tokens
109
+ pricing_data: Full pricing data from LiteLLM
110
+
111
+ Returns:
112
+ Tuple of (cost_per_1k_input, cost_per_1k_output, total_cost)
113
+ """
114
+ model_pricing = get_model_pricing(model_id, pricing_data)
115
+
116
+ if not model_pricing:
117
+ # Fallback defaults
118
+ logger.warning("using_default_pricing", model_id=model_id)
119
+ return (0.003, 0.015, 0.0)
120
+
121
+ input_cost_per_token = model_pricing.get("input_cost_per_token", 0.000003)
122
+ output_cost_per_token = model_pricing.get("output_cost_per_token", 0.000015)
123
+
124
+ # Convert to per-1k pricing for display
125
+ input_cost_per_1k = input_cost_per_token * 1000
126
+ output_cost_per_1k = output_cost_per_token * 1000
127
+
128
+ # Calculate total cost
129
+ total_cost = (
130
+ (estimated_input_tokens * input_cost_per_token) +
131
+ (estimated_output_tokens * output_cost_per_token)
132
+ )
133
+
134
+ return (input_cost_per_1k, output_cost_per_1k, total_cost)
135
+
136
+
137
+ def get_model_display_name(model_id: str) -> str:
138
+ """
139
+ Get human-readable model name
140
+
141
+ Args:
142
+ model_id: Model identifier
143
+
144
+ Returns:
145
+ Display name
146
+ """
147
+ # Map of common model IDs to display names
148
+ display_names = {
149
+ "claude-sonnet-4": "Claude Sonnet 4",
150
+ "claude-3-5-sonnet-20241022": "Claude 3.5 Sonnet",
151
+ "claude-3-5-sonnet-20240620": "Claude 3.5 Sonnet (Legacy)",
152
+ "claude-3-opus-20240229": "Claude 3 Opus",
153
+ "claude-3-haiku-20240307": "Claude 3 Haiku",
154
+ "gpt-4o": "GPT-4o",
155
+ "gpt-4o-mini": "GPT-4o Mini",
156
+ "gpt-4-turbo": "GPT-4 Turbo",
157
+ "gpt-4": "GPT-4",
158
+ "gpt-3.5-turbo": "GPT-3.5 Turbo",
159
+ "o1": "OpenAI o1",
160
+ "o1-mini": "OpenAI o1-mini",
161
+ "gemini-2.0-flash-exp": "Gemini 2.0 Flash",
162
+ "gemini-1.5-pro": "Gemini 1.5 Pro",
163
+ "gemini-1.5-flash": "Gemini 1.5 Flash",
164
+ }
165
+
166
+ return display_names.get(model_id, model_id.replace("-", " ").title())
@@ -0,0 +1,163 @@
1
+ """
2
+ MCP configuration validation helpers.
3
+
4
+ Validates MCP server configurations and template syntax.
5
+ """
6
+
7
+ import structlog
8
+ from typing import Dict, List, Any, Optional
9
+ from pydantic import ValidationError
10
+
11
+ from control_plane_api.app.schemas.mcp_schemas import MCPServerConfig
12
+ from control_plane_api.app.lib.templating import extract_all_variables, TemplateValidator, TemplateContext, get_default_engine
13
+
14
+ logger = structlog.get_logger()
15
+
16
+
17
+ class MCPValidationError(Exception):
18
+ """Raised when MCP configuration validation fails."""
19
+ pass
20
+
21
+
22
+ def validate_mcp_server_config(
23
+ mcp_servers: Dict[str, Any],
24
+ available_secrets: Optional[List[str]] = None,
25
+ available_env_vars: Optional[List[str]] = None,
26
+ strict: bool = False
27
+ ) -> Dict[str, Any]:
28
+ """
29
+ Validate MCP server configuration.
30
+
31
+ Validates:
32
+ 1. MCP server configuration schema (Pydantic validation)
33
+ 2. Template syntax in all string fields
34
+ 3. Referenced secrets exist (if available_secrets provided)
35
+ 4. Referenced env vars exist (if available_env_vars provided)
36
+
37
+ Args:
38
+ mcp_servers: Dict of MCP server configurations
39
+ available_secrets: List of available secret names for validation
40
+ available_env_vars: List of available env var names for validation
41
+ strict: If True, raise error for missing secrets/env vars
42
+
43
+ Returns:
44
+ Dict with validation results:
45
+ {
46
+ "valid": bool,
47
+ "errors": List[str],
48
+ "warnings": List[str],
49
+ "required_secrets": List[str],
50
+ "required_env_vars": List[str]
51
+ }
52
+
53
+ Raises:
54
+ MCPValidationError: If strict=True and validation fails
55
+ """
56
+ errors = []
57
+ warnings = []
58
+ all_required_secrets = set()
59
+ all_required_env_vars = set()
60
+
61
+ if not mcp_servers:
62
+ return {
63
+ "valid": True,
64
+ "errors": [],
65
+ "warnings": [],
66
+ "required_secrets": [],
67
+ "required_env_vars": []
68
+ }
69
+
70
+ # Validate each server configuration
71
+ for server_name, server_config in mcp_servers.items():
72
+ try:
73
+ # Validate using Pydantic schema
74
+ validated_config = MCPServerConfig(**server_config)
75
+
76
+ # Extract template variables
77
+ variables = extract_all_variables(server_config)
78
+ required_secrets = variables.get("secrets", [])
79
+ required_env_vars = variables.get("env_vars", [])
80
+
81
+ all_required_secrets.update(required_secrets)
82
+ all_required_env_vars.update(required_env_vars)
83
+
84
+ # Check if required secrets are available
85
+ if available_secrets is not None:
86
+ missing_secrets = [s for s in required_secrets if s not in available_secrets]
87
+ if missing_secrets:
88
+ error_msg = f"Server '{server_name}': Missing secrets: {', '.join(missing_secrets)}"
89
+ if strict:
90
+ errors.append(error_msg)
91
+ else:
92
+ warnings.append(error_msg)
93
+
94
+ # Check if required env vars are available
95
+ if available_env_vars is not None:
96
+ missing_env_vars = [v for v in required_env_vars if v not in available_env_vars]
97
+ if missing_env_vars:
98
+ error_msg = f"Server '{server_name}': Missing environment variables: {', '.join(missing_env_vars)}"
99
+ if strict:
100
+ errors.append(error_msg)
101
+ else:
102
+ warnings.append(error_msg)
103
+
104
+ logger.debug(
105
+ "mcp_server_validated",
106
+ server_name=server_name,
107
+ transport_type=validated_config.get_transport_type().value,
108
+ required_secrets_count=len(required_secrets),
109
+ required_env_vars_count=len(required_env_vars)
110
+ )
111
+
112
+ except ValidationError as e:
113
+ error_msg = f"Server '{server_name}': Invalid configuration: {str(e)}"
114
+ errors.append(error_msg)
115
+ logger.warning("mcp_server_validation_error", server_name=server_name, error=str(e))
116
+
117
+ except Exception as e:
118
+ error_msg = f"Server '{server_name}': Validation error: {str(e)}"
119
+ errors.append(error_msg)
120
+ logger.error("mcp_server_validation_exception", server_name=server_name, error=str(e))
121
+
122
+ result = {
123
+ "valid": len(errors) == 0,
124
+ "errors": errors,
125
+ "warnings": warnings,
126
+ "required_secrets": sorted(list(all_required_secrets)),
127
+ "required_env_vars": sorted(list(all_required_env_vars))
128
+ }
129
+
130
+ if strict and not result["valid"]:
131
+ error_summary = "; ".join(errors)
132
+ raise MCPValidationError(f"MCP configuration validation failed: {error_summary}")
133
+
134
+ return result
135
+
136
+
137
+ def validate_execution_environment_mcp(
138
+ execution_environment: Dict[str, Any],
139
+ strict: bool = False
140
+ ) -> Dict[str, Any]:
141
+ """
142
+ Validate MCP servers in an execution environment configuration.
143
+
144
+ Args:
145
+ execution_environment: Execution environment dict with mcp_servers
146
+ strict: If True, raise error on validation failure
147
+
148
+ Returns:
149
+ Validation result dict
150
+
151
+ Raises:
152
+ MCPValidationError: If strict=True and validation fails
153
+ """
154
+ mcp_servers = execution_environment.get("mcp_servers", {})
155
+ available_secrets = execution_environment.get("secrets", [])
156
+ available_env_vars = list(execution_environment.get("env_vars", {}).keys())
157
+
158
+ return validate_mcp_server_config(
159
+ mcp_servers,
160
+ available_secrets=available_secrets,
161
+ available_env_vars=available_env_vars,
162
+ strict=strict
163
+ )
@@ -0,0 +1,13 @@
1
+ """NATS integration components for control plane."""
2
+
3
+ from control_plane_api.app.lib.nats.credentials_manager import (
4
+ NATSCredentialsManager,
5
+ WorkerCredentials,
6
+ )
7
+ from control_plane_api.app.lib.nats.listener import NATSEventListener
8
+
9
+ __all__ = [
10
+ "NATSCredentialsManager",
11
+ "WorkerCredentials",
12
+ "NATSEventListener",
13
+ ]
@@ -0,0 +1,288 @@
1
+ """NATS credentials manager for generating temporary worker credentials."""
2
+
3
+ import os
4
+ import json
5
+ from datetime import datetime, timedelta, timezone
6
+ from typing import Optional
7
+ from pydantic import BaseModel, Field
8
+ import structlog
9
+
10
+ logger = structlog.get_logger(__name__)
11
+
12
+
13
+ class WorkerCredentials(BaseModel):
14
+ """NATS credentials for a worker."""
15
+
16
+ jwt: str = Field(..., description="User JWT token")
17
+ seed: str = Field(..., description="NKey seed (private key)")
18
+ public_key: str = Field(..., description="NKey public key")
19
+ subject_prefix: str = Field(..., description="Subject prefix for publishing")
20
+ expires_at: datetime = Field(..., description="Credential expiration time")
21
+
22
+
23
+ class NATSCredentialsManager:
24
+ """Generate temporary NATS credentials for workers."""
25
+
26
+ def __init__(
27
+ self,
28
+ operator_jwt: str,
29
+ operator_seed: str,
30
+ account_public_key: Optional[str] = None,
31
+ ):
32
+ """
33
+ Initialize NATS credentials manager.
34
+
35
+ Args:
36
+ operator_jwt: Operator JWT (not used for signing, just for reference)
37
+ operator_seed: Operator seed for signing JWTs
38
+ account_public_key: Optional account public key (extracted from operator JWT if not provided)
39
+ """
40
+ try:
41
+ import nkeys
42
+ import jwt as jwt_lib
43
+ except ImportError:
44
+ raise ImportError(
45
+ "nkeys and PyJWT are required for NATS credentials. "
46
+ "Install with: pip install nkeys pyjwt"
47
+ )
48
+
49
+ self.operator_jwt = operator_jwt
50
+ self.operator_seed = operator_seed
51
+ self.account_public_key = account_public_key
52
+
53
+ # Create signing key from operator seed
54
+ try:
55
+ self.signing_key = nkeys.from_seed(operator_seed.encode())
56
+ logger.info(
57
+ "nats_credentials_manager_initialized",
58
+ operator_key=self.signing_key.public_key.decode()[:10] + "...",
59
+ )
60
+ except Exception as e:
61
+ logger.error("nats_signing_key_init_failed", error=str(e))
62
+ raise
63
+
64
+ def create_worker_credentials(
65
+ self,
66
+ worker_id: str,
67
+ organization_id: str,
68
+ ttl_hours: int = 24,
69
+ ) -> WorkerCredentials:
70
+ """
71
+ Create temporary NATS user credentials (JWT + seed) for worker.
72
+
73
+ Permissions:
74
+ - Publish: events.{organization_id}.{worker_id}.>
75
+ - Subscribe: None (workers only publish)
76
+
77
+ Args:
78
+ worker_id: Worker UUID
79
+ organization_id: Organization ID
80
+ ttl_hours: Credential time-to-live in hours
81
+
82
+ Returns:
83
+ WorkerCredentials with JWT and seed
84
+
85
+ Raises:
86
+ Exception: If credential generation fails
87
+ """
88
+ try:
89
+ import nkeys
90
+ import jwt as jwt_lib
91
+
92
+ # Generate user nkey
93
+ user_key = nkeys.create_user()
94
+ user_seed = user_key.seed.decode()
95
+ user_public_key = user_key.public_key.decode()
96
+
97
+ # Calculate expiration
98
+ now = datetime.now(timezone.utc)
99
+ expires_at = now + timedelta(hours=ttl_hours)
100
+
101
+ # Subject prefix for this worker
102
+ subject_prefix = f"events.{organization_id}.{worker_id}"
103
+
104
+ # Create user JWT claims
105
+ # Reference: https://docs.nats.io/running-a-nats-service/configuration/securing_nats/jwt
106
+ claims = {
107
+ "jti": f"worker-{worker_id}", # JWT ID
108
+ "iat": int(now.timestamp()), # Issued at
109
+ "iss": self.signing_key.public_key.decode(), # Issuer (operator key)
110
+ "sub": user_public_key, # Subject (user public key)
111
+ "exp": int(expires_at.timestamp()), # Expiration
112
+ "nats": {
113
+ "pub": {
114
+ # Allow publishing to worker-specific subjects
115
+ "allow": [f"{subject_prefix}.>"]
116
+ },
117
+ "sub": {
118
+ # Workers don't subscribe
119
+ "allow": []
120
+ },
121
+ "subs": -1, # Unlimited subscriptions (not used)
122
+ "data": -1, # Unlimited data
123
+ "payload": -1, # Unlimited payload size
124
+ "type": "user",
125
+ "version": 2,
126
+ },
127
+ }
128
+
129
+ # Sign JWT with operator key using Ed25519
130
+ # nkeys uses Ed25519, so we sign manually
131
+ signing_input = self._encode_jwt_parts(claims)
132
+ signature = self.signing_key.sign(signing_input)
133
+
134
+ # Build complete JWT
135
+ import base64
136
+
137
+ jwt_parts = [
138
+ base64.urlsafe_b64encode(
139
+ json.dumps({"typ": "JWT", "alg": "ed25519-nkey"}).encode()
140
+ )
141
+ .decode()
142
+ .rstrip("="),
143
+ base64.urlsafe_b64encode(json.dumps(claims).encode())
144
+ .decode()
145
+ .rstrip("="),
146
+ base64.urlsafe_b64encode(signature).decode().rstrip("="),
147
+ ]
148
+
149
+ user_jwt = ".".join(jwt_parts)
150
+
151
+ logger.info(
152
+ "nats_worker_credentials_created",
153
+ worker_id=worker_id[:8],
154
+ organization_id=organization_id,
155
+ subject_prefix=subject_prefix,
156
+ expires_at=expires_at.isoformat(),
157
+ ttl_hours=ttl_hours,
158
+ )
159
+
160
+ return WorkerCredentials(
161
+ jwt=user_jwt,
162
+ seed=user_seed,
163
+ public_key=user_public_key,
164
+ subject_prefix=subject_prefix,
165
+ expires_at=expires_at,
166
+ )
167
+
168
+ except ImportError as e:
169
+ logger.error("nats_credentials_dependency_missing", error=str(e))
170
+ raise
171
+
172
+ except Exception as e:
173
+ logger.error(
174
+ "nats_worker_credentials_creation_failed",
175
+ error=str(e),
176
+ worker_id=worker_id[:8],
177
+ organization_id=organization_id,
178
+ )
179
+ raise
180
+
181
+ def _encode_jwt_parts(self, claims: dict) -> bytes:
182
+ """
183
+ Encode JWT header and payload for signing.
184
+
185
+ Args:
186
+ claims: JWT claims
187
+
188
+ Returns:
189
+ Signing input (header.payload as bytes)
190
+ """
191
+ import base64
192
+ import json
193
+
194
+ header = {"typ": "JWT", "alg": "ed25519-nkey"}
195
+
196
+ header_b64 = (
197
+ base64.urlsafe_b64encode(json.dumps(header).encode())
198
+ .decode()
199
+ .rstrip("=")
200
+ )
201
+ payload_b64 = (
202
+ base64.urlsafe_b64encode(json.dumps(claims).encode())
203
+ .decode()
204
+ .rstrip("=")
205
+ )
206
+
207
+ signing_input = f"{header_b64}.{payload_b64}"
208
+ return signing_input.encode()
209
+
210
+ def save_credentials_file(
211
+ self, credentials: WorkerCredentials, file_path: str
212
+ ) -> None:
213
+ """
214
+ Save credentials to .creds file format.
215
+
216
+ The .creds file format contains both the JWT and seed in a specific format
217
+ that NATS clients can read.
218
+
219
+ Args:
220
+ credentials: Worker credentials
221
+ file_path: Path to save .creds file
222
+
223
+ Raises:
224
+ Exception: If file write fails
225
+ """
226
+ try:
227
+ # Standard NATS .creds file format
228
+ creds_content = f"""-----BEGIN NATS USER JWT-----
229
+ {credentials.jwt}
230
+ ------END NATS USER JWT------
231
+
232
+ ************************* IMPORTANT *************************
233
+ NKEY Seed printed below can be used to sign and prove identity.
234
+ NKEYs are sensitive and should be treated as secrets.
235
+
236
+ -----BEGIN USER NKEY SEED-----
237
+ {credentials.seed}
238
+ ------END USER NKEY SEED------
239
+
240
+ *************************************************************
241
+ """
242
+
243
+ # Write file
244
+ with open(file_path, "w") as f:
245
+ f.write(creds_content)
246
+
247
+ # Secure file permissions (owner read/write only)
248
+ os.chmod(file_path, 0o600)
249
+
250
+ logger.info(
251
+ "nats_credentials_file_saved",
252
+ file_path=file_path,
253
+ permissions="0600",
254
+ )
255
+
256
+ except Exception as e:
257
+ logger.error(
258
+ "nats_credentials_file_save_failed",
259
+ error=str(e),
260
+ file_path=file_path,
261
+ )
262
+ raise
263
+
264
+ @staticmethod
265
+ def get_credentials_string(credentials: WorkerCredentials) -> str:
266
+ """
267
+ Get credentials as a string (for passing to workers without file).
268
+
269
+ Args:
270
+ credentials: Worker credentials
271
+
272
+ Returns:
273
+ Credentials in .creds file format as string
274
+ """
275
+ return f"""-----BEGIN NATS USER JWT-----
276
+ {credentials.jwt}
277
+ ------END NATS USER JWT------
278
+
279
+ ************************* IMPORTANT *************************
280
+ NKEY Seed printed below can be used to sign and prove identity.
281
+ NKEYs are sensitive and should be treated as secrets.
282
+
283
+ -----BEGIN USER NKEY SEED-----
284
+ {credentials.seed}
285
+ ------END USER NKEY SEED------
286
+
287
+ *************************************************************
288
+ """