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,65 @@
1
+ """
2
+ Environment detection utilities for worker deployment context.
3
+
4
+ Provides functions to detect serverless environments and determine
5
+ whether WebSocket connections should be used.
6
+
7
+ Serverless environments detected:
8
+ - AWS Lambda
9
+ - Vercel Functions
10
+ - Google Cloud Functions
11
+ - Azure Functions
12
+ """
13
+
14
+ from typing import Literal
15
+ import os
16
+
17
+
18
+ def detect_environment() -> Literal["standard", "serverless"]:
19
+ """
20
+ Detect if running in a serverless environment.
21
+
22
+ Returns:
23
+ "serverless" if running in a serverless environment (Lambda, Vercel, GCF, Azure Functions)
24
+ "standard" for traditional server environments
25
+ """
26
+ # AWS Lambda
27
+ if os.environ.get("AWS_LAMBDA_FUNCTION_NAME"):
28
+ return "serverless"
29
+
30
+ # Vercel Functions
31
+ if os.environ.get("VERCEL"):
32
+ return "serverless"
33
+
34
+ # Google Cloud Functions
35
+ if os.environ.get("FUNCTION_TARGET") or os.environ.get("K_SERVICE"):
36
+ return "serverless"
37
+
38
+ # Azure Functions
39
+ if os.environ.get("AZURE_FUNCTIONS_ENVIRONMENT"):
40
+ return "serverless"
41
+
42
+ return "standard"
43
+
44
+
45
+ def should_use_websocket() -> bool:
46
+ """
47
+ Determine if WebSocket should be used based on environment.
48
+
49
+ WebSocket is disabled in serverless environments due to limitations:
50
+ - Short-lived execution contexts
51
+ - No persistent connections
52
+ - Function timeout constraints
53
+
54
+ Returns:
55
+ True if WebSocket should be used, False otherwise
56
+ """
57
+ # Never use WebSocket in serverless
58
+ if detect_environment() == "serverless":
59
+ return False
60
+
61
+ # Check explicit disable
62
+ if os.environ.get("WEBSOCKET_ENABLED", "true").lower() == "false":
63
+ return False
64
+
65
+ return True
@@ -0,0 +1,260 @@
1
+ """
2
+ Error event publisher for standardized error handling.
3
+
4
+ This module provides utilities for publishing error events to the Control Plane
5
+ with consistent structure, user-friendly messages, and recovery suggestions.
6
+ """
7
+
8
+ import structlog
9
+ import traceback
10
+ from typing import Optional, List, Dict, Any
11
+ from datetime import datetime, timezone
12
+
13
+ from control_plane_api.worker.models.error_events import (
14
+ ErrorEvent, ErrorSeverity, ErrorCategory, ErrorContext,
15
+ ErrorDetails, ErrorRecovery
16
+ )
17
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
18
+
19
+ logger = structlog.get_logger()
20
+
21
+
22
+ class ErrorEventPublisher:
23
+ """Helper class for publishing standardized error events."""
24
+
25
+ def __init__(self, control_plane: ControlPlaneClient):
26
+ self.control_plane = control_plane
27
+
28
+ async def publish_error(
29
+ self,
30
+ execution_id: str,
31
+ exception: Exception,
32
+ severity: ErrorSeverity,
33
+ category: ErrorCategory,
34
+ stage: str,
35
+ component: str,
36
+ operation: Optional[str] = None,
37
+ recovery_actions: Optional[List[str]] = None,
38
+ metadata: Optional[Dict[str, Any]] = None,
39
+ include_stack_trace: bool = True,
40
+ ) -> bool:
41
+ """
42
+ Publish a standardized error event to Control Plane.
43
+
44
+ Args:
45
+ execution_id: Execution ID
46
+ exception: The exception that occurred
47
+ severity: Error severity level
48
+ category: Error category
49
+ stage: Execution stage where error occurred
50
+ component: Component that generated error
51
+ operation: Specific operation that failed
52
+ recovery_actions: List of user-actionable recovery steps
53
+ metadata: Additional metadata
54
+ include_stack_trace: Whether to include stack trace
55
+
56
+ Returns:
57
+ True if published successfully
58
+ """
59
+ try:
60
+ # Extract stack trace
61
+ stack_trace = None
62
+ code_location = None
63
+ if include_stack_trace:
64
+ tb_lines = traceback.format_exception(
65
+ type(exception), exception, exception.__traceback__
66
+ )
67
+ stack_trace = ''.join(tb_lines)[-2000:] # Last 2000 chars
68
+
69
+ # Extract code location from traceback
70
+ tb = exception.__traceback__
71
+ if tb:
72
+ frame = traceback.extract_tb(tb)[-1]
73
+ code_location = f"{frame.filename}:{frame.lineno}"
74
+
75
+ # Create user-friendly error message
76
+ user_message = self._create_user_friendly_message(
77
+ exception, category, component
78
+ )
79
+
80
+ # Determine if retryable
81
+ is_retryable = self._is_retryable_error(exception, category)
82
+
83
+ # Get default recovery actions if none provided
84
+ if not recovery_actions:
85
+ recovery_actions = self._get_default_recovery_actions(
86
+ category, is_retryable
87
+ )
88
+
89
+ # Get documentation URL
90
+ doc_url = self._get_documentation_url(category)
91
+
92
+ # Build error event
93
+ error_event = ErrorEvent(
94
+ severity=severity,
95
+ category=category,
96
+ context=ErrorContext(
97
+ execution_id=execution_id,
98
+ timestamp=datetime.now(timezone.utc).isoformat(),
99
+ stage=stage,
100
+ component=component,
101
+ operation=operation,
102
+ ),
103
+ details=ErrorDetails(
104
+ error_type=type(exception).__name__,
105
+ error_message=user_message,
106
+ technical_message=str(exception),
107
+ stack_trace=stack_trace,
108
+ code_location=code_location,
109
+ ),
110
+ recovery=ErrorRecovery(
111
+ is_retryable=is_retryable,
112
+ retry_suggested=is_retryable and severity != ErrorSeverity.CRITICAL,
113
+ recovery_actions=recovery_actions,
114
+ documentation_url=doc_url,
115
+ ),
116
+ metadata=metadata,
117
+ )
118
+
119
+ # Publish error event
120
+ success = await self.control_plane.publish_event_async(
121
+ execution_id=execution_id,
122
+ event_type="error",
123
+ data=error_event.to_event_data(),
124
+ )
125
+
126
+ if success:
127
+ logger.info(
128
+ "error_event_published",
129
+ execution_id=execution_id[:8] if len(execution_id) >= 8 else execution_id,
130
+ severity=severity.value,
131
+ category=category.value,
132
+ error_type=type(exception).__name__,
133
+ )
134
+ else:
135
+ logger.warning(
136
+ "error_event_publish_failed",
137
+ execution_id=execution_id[:8] if len(execution_id) >= 8 else execution_id,
138
+ error=str(exception)[:200],
139
+ )
140
+
141
+ return success
142
+
143
+ except Exception as publish_error:
144
+ # Never let error publishing break the main execution
145
+ logger.error(
146
+ "failed_to_publish_error_event",
147
+ execution_id=execution_id[:8] if len(execution_id) >= 8 else execution_id,
148
+ error=str(publish_error),
149
+ )
150
+ return False
151
+
152
+ def _create_user_friendly_message(
153
+ self, exception: Exception, category: ErrorCategory, component: str
154
+ ) -> str:
155
+ """Create user-friendly error message."""
156
+
157
+ messages = {
158
+ ErrorCategory.RUNTIME_INIT: f"Failed to initialize {component}",
159
+ ErrorCategory.SKILL_LOADING: f"Could not load skill: {str(exception)[:100]}",
160
+ ErrorCategory.MCP_CONNECTION: f"Failed to connect to MCP server",
161
+ ErrorCategory.TOOL_EXECUTION: f"Tool execution failed: {str(exception)[:100]}",
162
+ ErrorCategory.TIMEOUT: f"Operation timed out in {component}",
163
+ ErrorCategory.MODEL_ERROR: f"LLM model error: {str(exception)[:100]}",
164
+ ErrorCategory.AUTHENTICATION: "Authentication failed - please check credentials",
165
+ ErrorCategory.NETWORK: "Network connection error",
166
+ ErrorCategory.VALIDATION: f"Validation error: {str(exception)[:100]}",
167
+ }
168
+
169
+ return messages.get(category, str(exception)[:200])
170
+
171
+ def _is_retryable_error(
172
+ self, exception: Exception, category: ErrorCategory
173
+ ) -> bool:
174
+ """Determine if error is retryable."""
175
+
176
+ # Network and timeout errors are generally retryable
177
+ if category in [ErrorCategory.NETWORK, ErrorCategory.TIMEOUT]:
178
+ return True
179
+
180
+ # Authentication errors not retryable without credential changes
181
+ if category == ErrorCategory.AUTHENTICATION:
182
+ return False
183
+
184
+ # Validation errors not retryable without input changes
185
+ if category == ErrorCategory.VALIDATION:
186
+ return False
187
+
188
+ # Check exception type
189
+ retryable_types = [
190
+ "TimeoutError",
191
+ "ConnectionError",
192
+ "HTTPError",
193
+ "TemporaryFailure",
194
+ ]
195
+
196
+ return type(exception).__name__ in retryable_types
197
+
198
+ def _get_default_recovery_actions(
199
+ self, category: ErrorCategory, is_retryable: bool
200
+ ) -> List[str]:
201
+ """Get default recovery actions for error category."""
202
+
203
+ actions = {
204
+ ErrorCategory.RUNTIME_INIT: [
205
+ "Check that all required dependencies are installed",
206
+ "Verify runtime configuration is correct",
207
+ "Check worker logs for detailed error information",
208
+ ],
209
+ ErrorCategory.SKILL_LOADING: [
210
+ "Verify skill configuration is correct",
211
+ "Check that skill dependencies are available",
212
+ "Review skill permissions and access",
213
+ ],
214
+ ErrorCategory.MCP_CONNECTION: [
215
+ "Verify MCP server is running and accessible",
216
+ "Check network connectivity",
217
+ "Review MCP server configuration and credentials",
218
+ ],
219
+ ErrorCategory.TIMEOUT: [
220
+ "Consider increasing timeout settings",
221
+ "Check system resources and load",
222
+ "Simplify the operation if possible",
223
+ ],
224
+ ErrorCategory.MODEL_ERROR: [
225
+ "Verify API credentials are valid",
226
+ "Check API rate limits and quotas",
227
+ "Try a different model if available",
228
+ ],
229
+ ErrorCategory.AUTHENTICATION: [
230
+ "Verify API credentials are correct",
231
+ "Check that credentials have required permissions",
232
+ "Regenerate credentials if expired",
233
+ ],
234
+ }
235
+
236
+ default_actions = actions.get(category, [
237
+ "Review error details above",
238
+ "Check system logs for more information",
239
+ "Contact support if issue persists",
240
+ ])
241
+
242
+ if is_retryable:
243
+ default_actions.insert(0, "Retry the operation")
244
+
245
+ return default_actions
246
+
247
+ def _get_documentation_url(self, category: ErrorCategory) -> Optional[str]:
248
+ """Get documentation URL for error category."""
249
+
250
+ base_url = "https://docs.kubiya.ai/troubleshooting"
251
+
252
+ urls = {
253
+ ErrorCategory.RUNTIME_INIT: f"{base_url}/runtime-initialization",
254
+ ErrorCategory.SKILL_LOADING: f"{base_url}/skill-configuration",
255
+ ErrorCategory.MCP_CONNECTION: f"{base_url}/mcp-servers",
256
+ ErrorCategory.MODEL_ERROR: f"{base_url}/llm-configuration",
257
+ ErrorCategory.AUTHENTICATION: f"{base_url}/authentication",
258
+ }
259
+
260
+ return urls.get(category, base_url)
@@ -0,0 +1,256 @@
1
+ """
2
+ Event Batcher for efficient HTTP event publishing.
3
+
4
+ Batches multiple events into a single HTTP request to reduce overhead,
5
+ especially important for serverless deployments like Vercel where you
6
+ pay per request.
7
+
8
+ Features:
9
+ - Time-based flushing (default 50ms)
10
+ - Size-based flushing (default 10 events)
11
+ - Priority-based immediate flushing for critical events
12
+ - Automatic background flushing task
13
+ - Graceful shutdown with pending event flush
14
+
15
+ Usage:
16
+ batcher = EventBatcher(
17
+ flush_callback=send_batch_to_server,
18
+ max_batch_size=10,
19
+ max_batch_time_ms=50
20
+ )
21
+
22
+ await batcher.start()
23
+ await batcher.add_event(event_data, priority="normal")
24
+ await batcher.add_event(critical_event, priority="critical") # Flushes immediately
25
+ await batcher.stop()
26
+ """
27
+
28
+ import asyncio
29
+ import time
30
+ from typing import Dict, Any, Callable, Literal, List, Optional
31
+ import structlog
32
+
33
+ logger = structlog.get_logger()
34
+
35
+
36
+ class EventBatcher:
37
+ """
38
+ Batches events for efficient HTTP publishing.
39
+
40
+ Reduces HTTP request count by batching multiple events together,
41
+ with smart flushing based on time, size, and priority.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ flush_callback: Callable[[List[Dict[str, Any]]], Any],
47
+ max_batch_size: int = 10,
48
+ max_batch_time_ms: int = 50,
49
+ enabled: bool = True
50
+ ):
51
+ """
52
+ Initialize event batcher.
53
+
54
+ Args:
55
+ flush_callback: Async callback to send batch (receives list of events)
56
+ max_batch_size: Maximum events per batch before auto-flush
57
+ max_batch_time_ms: Maximum milliseconds to wait before auto-flush
58
+ enabled: Whether batching is enabled (if False, events flush immediately)
59
+ """
60
+ self.flush_callback = flush_callback
61
+ self.max_batch_size = max_batch_size
62
+ self.max_batch_time_ms = max_batch_time_ms
63
+ self.enabled = enabled
64
+
65
+ # Batch state
66
+ self.batch: List[Dict[str, Any]] = []
67
+ self.batch_lock = asyncio.Lock()
68
+ self.last_flush_time = time.time()
69
+
70
+ # Background flush task
71
+ self._flush_task: Optional[asyncio.Task] = None
72
+ self._running = False
73
+
74
+ # Statistics
75
+ self.stats = {
76
+ "total_events": 0,
77
+ "total_batches": 0,
78
+ "total_flushes": 0,
79
+ "time_based_flushes": 0,
80
+ "size_based_flushes": 0,
81
+ "critical_flushes": 0,
82
+ "events_per_batch_avg": 0.0
83
+ }
84
+
85
+ async def start(self):
86
+ """Start the background flush task."""
87
+ if not self.enabled:
88
+ logger.info("event_batching_disabled")
89
+ return
90
+
91
+ if self._running:
92
+ logger.warning("event_batcher_already_running")
93
+ return
94
+
95
+ self._running = True
96
+ self._flush_task = asyncio.create_task(self._flush_loop())
97
+ logger.info(
98
+ "event_batcher_started",
99
+ max_batch_size=self.max_batch_size,
100
+ max_batch_time_ms=self.max_batch_time_ms
101
+ )
102
+
103
+ async def stop(self):
104
+ """Stop the batcher and flush any pending events."""
105
+ self._running = False
106
+
107
+ # Cancel background task
108
+ if self._flush_task and not self._flush_task.done():
109
+ self._flush_task.cancel()
110
+ try:
111
+ await self._flush_task
112
+ except asyncio.CancelledError:
113
+ pass
114
+
115
+ # Flush any remaining events
116
+ await self._flush_batch("shutdown")
117
+
118
+ logger.info(
119
+ "event_batcher_stopped",
120
+ stats=self.stats
121
+ )
122
+
123
+ async def add_event(
124
+ self,
125
+ event: Dict[str, Any],
126
+ priority: Literal["normal", "critical"] = "normal"
127
+ ) -> bool:
128
+ """
129
+ Add an event to the batch.
130
+
131
+ Args:
132
+ event: Event data to batch
133
+ priority: "normal" for regular events, "critical" for immediate flush
134
+
135
+ Returns:
136
+ True if event was added successfully
137
+ """
138
+ if not self.enabled:
139
+ # If batching disabled, send immediately
140
+ try:
141
+ result = self.flush_callback([event])
142
+ if asyncio.iscoroutine(result):
143
+ await result
144
+ return True
145
+ except Exception as e:
146
+ logger.error("event_send_failed", error=str(e))
147
+ return False
148
+
149
+ async with self.batch_lock:
150
+ self.batch.append(event)
151
+ self.stats["total_events"] += 1
152
+
153
+ # Critical events flush immediately
154
+ if priority == "critical":
155
+ await self._flush_batch("critical")
156
+ return True
157
+
158
+ # Size-based flush
159
+ if len(self.batch) >= self.max_batch_size:
160
+ await self._flush_batch("size")
161
+ return True
162
+
163
+ return True
164
+
165
+ async def _flush_loop(self):
166
+ """Background task that flushes batches based on time."""
167
+ while self._running:
168
+ try:
169
+ # Check every 10ms (more responsive than max_batch_time_ms)
170
+ await asyncio.sleep(0.01)
171
+
172
+ # Check if we need time-based flush
173
+ async with self.batch_lock:
174
+ if not self.batch:
175
+ continue
176
+
177
+ elapsed_ms = (time.time() - self.last_flush_time) * 1000
178
+ if elapsed_ms >= self.max_batch_time_ms:
179
+ await self._flush_batch("time")
180
+
181
+ except asyncio.CancelledError:
182
+ break
183
+ except Exception as e:
184
+ logger.error("flush_loop_error", error=str(e))
185
+ # Continue running even if flush fails
186
+
187
+ async def _flush_batch(self, reason: str):
188
+ """
189
+ Flush the current batch.
190
+
191
+ Args:
192
+ reason: Reason for flush (time/size/critical/shutdown)
193
+
194
+ Note: Caller must hold batch_lock
195
+ """
196
+ if not self.batch:
197
+ return
198
+
199
+ batch_to_send = self.batch.copy()
200
+ batch_size = len(batch_to_send)
201
+
202
+ # Clear batch immediately (unlock for new events)
203
+ self.batch.clear()
204
+ self.last_flush_time = time.time()
205
+
206
+ # Update statistics
207
+ self.stats["total_batches"] += 1
208
+ self.stats["total_flushes"] += 1
209
+
210
+ if reason == "time":
211
+ self.stats["time_based_flushes"] += 1
212
+ elif reason == "size":
213
+ self.stats["size_based_flushes"] += 1
214
+ elif reason == "critical":
215
+ self.stats["critical_flushes"] += 1
216
+
217
+ # Calculate average batch size
218
+ self.stats["events_per_batch_avg"] = (
219
+ self.stats["total_events"] / self.stats["total_batches"]
220
+ )
221
+
222
+ # Send batch (outside lock)
223
+ try:
224
+ logger.debug(
225
+ "flushing_event_batch",
226
+ batch_size=batch_size,
227
+ reason=reason,
228
+ avg_batch_size=f"{self.stats['events_per_batch_avg']:.1f}"
229
+ )
230
+
231
+ result = self.flush_callback(batch_to_send)
232
+ if asyncio.iscoroutine(result):
233
+ await result
234
+
235
+ logger.debug(
236
+ "event_batch_flushed",
237
+ batch_size=batch_size,
238
+ reason=reason
239
+ )
240
+
241
+ except Exception as e:
242
+ logger.error(
243
+ "batch_flush_failed",
244
+ error=str(e),
245
+ batch_size=batch_size,
246
+ reason=reason
247
+ )
248
+
249
+ def get_stats(self) -> Dict[str, Any]:
250
+ """Get batching statistics."""
251
+ return self.stats.copy()
252
+
253
+ async def force_flush(self):
254
+ """Force flush any pending events (useful for testing or critical sections)."""
255
+ async with self.batch_lock:
256
+ await self._flush_batch("manual")