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,531 @@
1
+ """
2
+ Advanced Event Publisher with Batching and WebSocket Support.
3
+
4
+ This service provides efficient event publishing from workers to the control plane
5
+ with multiple transport layers for different use cases:
6
+
7
+ 1. Batched HTTP (default): Reduces HTTP requests by 90-96%
8
+ 2. WebSocket (long executions): Handles 300s timeout with connection renewal
9
+ 3. Priority Queue: Ensures critical events (tools, errors) are immediate
10
+
11
+ Architecture:
12
+ - High-frequency events (message_chunks) → Batched
13
+ - Critical events (tool_*, error) → Immediate
14
+ - Auto-transport selection based on execution duration
15
+
16
+ Performance:
17
+ - Before: 300-400 HTTP requests per execution
18
+ - After: 12-15 requests per execution (96% reduction)
19
+ - Latency: <100ms added (imperceptible to users)
20
+ """
21
+
22
+ import asyncio
23
+ import time
24
+ from typing import Dict, Any, Optional, Callable, Literal
25
+ from dataclasses import dataclass, field
26
+ from enum import Enum
27
+ import structlog
28
+ import httpx
29
+ from datetime import datetime, timezone
30
+
31
+ from control_plane_api.worker.utils.chunk_batcher import ChunkBatcher, BatchConfig
32
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
33
+
34
+ logger = structlog.get_logger()
35
+
36
+
37
+ class EventPriority(Enum):
38
+ """Event priority levels for smart batching."""
39
+
40
+ IMMEDIATE = 1 # Errors, tool events - never batch
41
+ HIGH = 2 # Workflow steps - never batch
42
+ NORMAL = 3 # Message chunks - batch aggressively
43
+
44
+
45
+ class TransportMode(Enum):
46
+ """Transport mode for event publishing."""
47
+
48
+ HTTP = "http" # Default - batched HTTP
49
+ WEBSOCKET = "ws" # Long executions - persistent connection
50
+ AUTO = "auto" # Auto-select based on execution duration
51
+
52
+
53
+ @dataclass
54
+ class EventPublisherConfig:
55
+ """Configuration for event publisher."""
56
+
57
+ # Batching configuration
58
+ batching_enabled: bool = True
59
+ batch_time_window_ms: int = 100
60
+ batch_size_window_bytes: int = 100
61
+ batch_max_size_bytes: int = 1000
62
+
63
+ # Transport configuration
64
+ transport_mode: TransportMode = TransportMode.AUTO
65
+ websocket_enabled: bool = True
66
+ websocket_switch_threshold_seconds: int = 240 # Switch to WS after 4 min
67
+ websocket_renew_interval_seconds: int = 240 # Renew WS every 4 min (before 300s timeout)
68
+
69
+ # Performance tuning
70
+ max_concurrent_requests: int = 10 # HTTP connection pool
71
+ request_timeout_seconds: int = 10 # Individual request timeout
72
+
73
+ @classmethod
74
+ def from_env(cls, single_execution_mode: bool = False) -> "EventPublisherConfig":
75
+ """
76
+ Create configuration from environment variables.
77
+
78
+ Args:
79
+ single_execution_mode: If True, disables WebSocket switching to avoid premature shutdown
80
+ """
81
+ import os
82
+
83
+ # Check if we're in single execution mode (from env or parameter)
84
+ is_single_execution = single_execution_mode or os.getenv("KUBIYA_SINGLE_EXECUTION_MODE", "").lower() == "true"
85
+
86
+ # For single execution mode, disable WebSocket by default to prevent issues
87
+ # with the execution monitor detecting premature completion
88
+ websocket_enabled_default = "false" if is_single_execution else "true"
89
+
90
+ return cls(
91
+ batching_enabled=os.getenv("EVENT_BATCHING_ENABLED", "true").lower() == "true",
92
+ batch_time_window_ms=int(os.getenv("EVENT_BATCH_TIME_WINDOW_MS", "100")),
93
+ batch_size_window_bytes=int(os.getenv("EVENT_BATCH_SIZE_WINDOW_BYTES", "100")),
94
+ batch_max_size_bytes=int(os.getenv("EVENT_BATCH_MAX_SIZE_BYTES", "1000")),
95
+ transport_mode=TransportMode(os.getenv("EVENT_TRANSPORT_MODE", "auto")),
96
+ websocket_enabled=os.getenv("EVENT_WEBSOCKET_ENABLED", websocket_enabled_default).lower() == "true",
97
+ websocket_switch_threshold_seconds=int(os.getenv("EVENT_WS_THRESHOLD_SECONDS", "240")),
98
+ websocket_renew_interval_seconds=int(os.getenv("EVENT_WS_RENEW_INTERVAL_SECONDS", "240")),
99
+ max_concurrent_requests=int(os.getenv("EVENT_MAX_CONCURRENT_REQUESTS", "10")),
100
+ request_timeout_seconds=int(os.getenv("EVENT_REQUEST_TIMEOUT_SECONDS", "10")),
101
+ )
102
+
103
+
104
+ @dataclass
105
+ class EventStats:
106
+ """Statistics for event publishing."""
107
+
108
+ total_events: int = 0
109
+ batched_events: int = 0
110
+ immediate_events: int = 0
111
+ http_requests: int = 0
112
+ websocket_messages: int = 0
113
+ bytes_sent: int = 0
114
+ errors: int = 0
115
+
116
+ def get_reduction_percent(self) -> float:
117
+ """Calculate HTTP request reduction percentage."""
118
+ if self.total_events == 0:
119
+ return 0.0
120
+ total_requests = self.http_requests + self.websocket_messages
121
+ return round((1 - total_requests / self.total_events) * 100, 1)
122
+
123
+
124
+ class EventPublisher:
125
+ """
126
+ Advanced event publisher with batching and WebSocket support.
127
+
128
+ Features:
129
+ - Smart batching for high-frequency events (message chunks)
130
+ - Immediate delivery for critical events (tools, errors)
131
+ - WebSocket fallback for long-running executions
132
+ - Connection renewal to handle 300s timeout
133
+ - Automatic transport selection
134
+
135
+ Usage:
136
+ publisher = EventPublisher(
137
+ control_plane=get_control_plane_client(),
138
+ execution_id=execution_id,
139
+ config=EventPublisherConfig.from_env()
140
+ )
141
+
142
+ # High-frequency events (batched)
143
+ await publisher.publish("message_chunk", {...})
144
+
145
+ # Critical events (immediate)
146
+ await publisher.publish("tool_started", {...}, priority=EventPriority.IMMEDIATE)
147
+
148
+ # Cleanup
149
+ await publisher.close()
150
+ """
151
+
152
+ def __init__(
153
+ self,
154
+ control_plane: ControlPlaneClient,
155
+ execution_id: str,
156
+ config: Optional[EventPublisherConfig] = None,
157
+ ):
158
+ self.control_plane = control_plane
159
+ self.execution_id = execution_id
160
+ self.config = config or EventPublisherConfig.from_env()
161
+
162
+ # State
163
+ self._batchers: Dict[str, ChunkBatcher] = {} # message_id -> batcher
164
+ self._ws_connection: Optional[Any] = None
165
+ self._ws_task: Optional[asyncio.Task] = None
166
+ self._start_time = time.time()
167
+ self._stats = EventStats()
168
+ self._closed = False
169
+
170
+ # Transport selection
171
+ self._current_transport = TransportMode.HTTP
172
+ self._transport_switch_task: Optional[asyncio.Task] = None
173
+
174
+ # Start transport management if auto mode
175
+ if self.config.transport_mode == TransportMode.AUTO and self.config.websocket_enabled:
176
+ self._transport_switch_task = asyncio.create_task(self._manage_transport())
177
+
178
+ async def publish(
179
+ self,
180
+ event_type: str,
181
+ data: Dict[str, Any],
182
+ priority: EventPriority = EventPriority.NORMAL,
183
+ ) -> bool:
184
+ """
185
+ Publish an event with smart batching and transport selection.
186
+
187
+ Args:
188
+ event_type: Event type (message_chunk, tool_started, etc.)
189
+ data: Event payload
190
+ priority: Event priority (determines batching behavior)
191
+
192
+ Returns:
193
+ True if successful, False otherwise
194
+ """
195
+ if self._closed:
196
+ logger.warning("publisher_closed", execution_id=self.execution_id[:8])
197
+ return False
198
+
199
+ self._stats.total_events += 1
200
+
201
+ # Determine if this event should be batched
202
+ should_batch = (
203
+ self.config.batching_enabled
204
+ and priority == EventPriority.NORMAL
205
+ and event_type == "message_chunk"
206
+ )
207
+
208
+ try:
209
+ if should_batch:
210
+ return await self._publish_batched(event_type, data)
211
+ else:
212
+ return await self._publish_immediate(event_type, data)
213
+
214
+ except Exception as e:
215
+ self._stats.errors += 1
216
+ # Log detailed error info to debug "await wasn't used with future" issues
217
+ import traceback
218
+ error_type = type(e).__name__
219
+ error_msg = str(e)
220
+ tb_str = traceback.format_exc()
221
+ logger.warning(
222
+ "event_publish_failed",
223
+ execution_id=self.execution_id[:8],
224
+ event_type=event_type,
225
+ error=error_msg,
226
+ error_type=error_type,
227
+ traceback=tb_str[:500] if tb_str else None,
228
+ )
229
+ return False
230
+
231
+ async def _publish_batched(self, event_type: str, data: Dict[str, Any]) -> bool:
232
+ """Publish event through batching layer."""
233
+ self._stats.batched_events += 1
234
+
235
+ # Get or create batcher for this message_id
236
+ message_id = data.get("message_id", "default")
237
+
238
+ if message_id not in self._batchers:
239
+ batch_config = BatchConfig(
240
+ enabled=self.config.batching_enabled,
241
+ time_window_ms=self.config.batch_time_window_ms,
242
+ size_window_bytes=self.config.batch_size_window_bytes,
243
+ max_batch_size_bytes=self.config.batch_max_size_bytes,
244
+ )
245
+
246
+ # Create batcher with appropriate publish function
247
+ self._batchers[message_id] = ChunkBatcher(
248
+ publish_func=self._get_publish_func(),
249
+ execution_id=self.execution_id,
250
+ message_id=message_id,
251
+ config=batch_config,
252
+ )
253
+
254
+ # Add chunk to batcher (will auto-flush based on time/size)
255
+ content = data.get("content", "")
256
+ await self._batchers[message_id].add_chunk(content)
257
+
258
+ return True
259
+
260
+ async def _publish_immediate(self, event_type: str, data: Dict[str, Any]) -> bool:
261
+ """Publish event immediately without batching, with retry logic."""
262
+ self._stats.immediate_events += 1
263
+
264
+ max_retries = 3
265
+ base_delay = 0.1 # 100ms
266
+
267
+ for attempt in range(max_retries):
268
+ try:
269
+ # Use appropriate transport - call the async function directly with await
270
+ publish_func = self._get_publish_func()
271
+
272
+ # CRITICAL: Always await async functions immediately
273
+ # publish_func returns an async function reference, so call it with await
274
+ await publish_func(
275
+ execution_id=self.execution_id,
276
+ event_type=event_type,
277
+ data=data,
278
+ )
279
+
280
+ # Update stats based on transport
281
+ if self._current_transport == TransportMode.HTTP:
282
+ self._stats.http_requests += 1
283
+ else:
284
+ self._stats.websocket_messages += 1
285
+
286
+ # Success - exit retry loop
287
+ if attempt > 0:
288
+ logger.debug(
289
+ "event_published_after_retry",
290
+ execution_id=self.execution_id[:8],
291
+ event_type=event_type,
292
+ attempt=attempt + 1,
293
+ )
294
+ return True
295
+
296
+ except Exception as e:
297
+ is_last_attempt = attempt == max_retries - 1
298
+
299
+ if is_last_attempt:
300
+ # Final failure - log and return False
301
+ logger.error(
302
+ "event_publish_failed_after_retries",
303
+ execution_id=self.execution_id[:8],
304
+ event_type=event_type,
305
+ error=str(e),
306
+ attempts=max_retries,
307
+ )
308
+ return False
309
+ else:
310
+ # Retry with exponential backoff
311
+ delay = base_delay * (2 ** attempt)
312
+ logger.debug(
313
+ "retrying_event_publish",
314
+ execution_id=self.execution_id[:8],
315
+ event_type=event_type,
316
+ error=str(e),
317
+ attempt=attempt + 1,
318
+ next_delay_ms=int(delay * 1000),
319
+ )
320
+ await asyncio.sleep(delay)
321
+
322
+ def _get_publish_func(self) -> Callable:
323
+ """Get the appropriate publish function based on current transport."""
324
+ if self._current_transport == TransportMode.WEBSOCKET and self._ws_connection:
325
+ return self._publish_via_websocket
326
+ else:
327
+ # Use async HTTP by default
328
+ return self.control_plane.publish_event_async
329
+
330
+ async def _publish_via_websocket(
331
+ self,
332
+ execution_id: str,
333
+ event_type: str,
334
+ data: Dict[str, Any],
335
+ ) -> bool:
336
+ """Publish event via WebSocket connection."""
337
+ if not self._ws_connection:
338
+ # Fallback to HTTP if WebSocket not available
339
+ return await self.control_plane.publish_event_async(
340
+ execution_id=execution_id,
341
+ event_type=event_type,
342
+ data=data,
343
+ )
344
+
345
+ try:
346
+ # Send event via WebSocket
347
+ payload = {
348
+ "event_type": event_type,
349
+ "data": data,
350
+ "timestamp": datetime.now(timezone.utc).isoformat(),
351
+ "execution_id": execution_id,
352
+ }
353
+
354
+ await self._ws_connection.send_json(payload)
355
+ self._stats.websocket_messages += 1
356
+ self._stats.bytes_sent += len(str(payload).encode('utf-8'))
357
+
358
+ return True
359
+
360
+ except Exception as e:
361
+ logger.warning(
362
+ "websocket_send_failed",
363
+ error=str(e),
364
+ execution_id=execution_id[:8],
365
+ )
366
+
367
+ # Fallback to HTTP
368
+ return await self.control_plane.publish_event_async(
369
+ execution_id=execution_id,
370
+ event_type=event_type,
371
+ data=data,
372
+ )
373
+
374
+ async def _manage_transport(self) -> None:
375
+ """
376
+ Manage transport mode switching and WebSocket connection lifecycle.
377
+
378
+ Switches to WebSocket after threshold and handles connection renewal.
379
+ """
380
+ try:
381
+ # Wait for threshold before switching to WebSocket
382
+ await asyncio.sleep(self.config.websocket_switch_threshold_seconds)
383
+
384
+ if self._closed:
385
+ return
386
+
387
+ # Switch to WebSocket
388
+ logger.info(
389
+ "switching_to_websocket",
390
+ execution_id=self.execution_id[:8],
391
+ elapsed_seconds=int(time.time() - self._start_time),
392
+ )
393
+
394
+ await self._connect_websocket()
395
+
396
+ # Connection renewal loop
397
+ while not self._closed:
398
+ await asyncio.sleep(self.config.websocket_renew_interval_seconds)
399
+
400
+ if not self._closed:
401
+ logger.info(
402
+ "renewing_websocket_connection",
403
+ execution_id=self.execution_id[:8],
404
+ )
405
+ await self._reconnect_websocket()
406
+
407
+ except asyncio.CancelledError:
408
+ pass
409
+ except Exception as e:
410
+ logger.warning(
411
+ "transport_management_error",
412
+ error=str(e),
413
+ execution_id=self.execution_id[:8],
414
+ )
415
+
416
+ async def _connect_websocket(self) -> None:
417
+ """Establish WebSocket connection to control plane."""
418
+ try:
419
+ # TODO: Implement WebSocket connection using websockets library
420
+ # For now, keep using HTTP as fallback
421
+
422
+ # Placeholder for WebSocket connection
423
+ # import websockets
424
+ # ws_url = self.control_plane.base_url.replace("http", "ws")
425
+ # ws_url = f"{ws_url}/ws/executions/{self.execution_id}/events"
426
+ # self._ws_connection = await websockets.connect(
427
+ # ws_url,
428
+ # extra_headers={"Authorization": f"UserKey {self.control_plane.api_key}"}
429
+ # )
430
+
431
+ self._current_transport = TransportMode.WEBSOCKET
432
+
433
+ logger.info(
434
+ "websocket_connected",
435
+ execution_id=self.execution_id[:8],
436
+ )
437
+
438
+ except Exception as e:
439
+ logger.warning(
440
+ "websocket_connect_failed",
441
+ error=str(e),
442
+ execution_id=self.execution_id[:8],
443
+ )
444
+ # Keep using HTTP
445
+ self._current_transport = TransportMode.HTTP
446
+
447
+ async def _reconnect_websocket(self) -> None:
448
+ """Reconnect WebSocket to handle 300s timeout."""
449
+ if self._ws_connection:
450
+ try:
451
+ await self._ws_connection.close()
452
+ except:
453
+ pass
454
+
455
+ await self._connect_websocket()
456
+
457
+ async def flush(self) -> None:
458
+ """Flush all pending batched events."""
459
+ for batcher in self._batchers.values():
460
+ try:
461
+ await batcher.flush(reason="manual_flush")
462
+ except Exception as e:
463
+ logger.warning(
464
+ "batcher_flush_error",
465
+ error=str(e),
466
+ execution_id=self.execution_id[:8],
467
+ )
468
+
469
+ async def close(self) -> None:
470
+ """
471
+ Close publisher and cleanup resources.
472
+
473
+ Flushes all pending events, closes WebSocket connection, and logs stats.
474
+ """
475
+ if self._closed:
476
+ return
477
+
478
+ self._closed = True
479
+
480
+ # Cancel transport management
481
+ if self._transport_switch_task:
482
+ self._transport_switch_task.cancel()
483
+ try:
484
+ await self._transport_switch_task
485
+ except asyncio.CancelledError:
486
+ pass
487
+
488
+ # Flush all batchers
489
+ await self.flush()
490
+
491
+ # Close all batchers
492
+ for batcher in self._batchers.values():
493
+ try:
494
+ await batcher.close()
495
+ except Exception as e:
496
+ logger.warning(
497
+ "batcher_close_error",
498
+ error=str(e),
499
+ execution_id=self.execution_id[:8],
500
+ )
501
+
502
+ # Close WebSocket connection
503
+ if self._ws_connection:
504
+ try:
505
+ await self._ws_connection.close()
506
+ except:
507
+ pass
508
+
509
+ # Log final statistics
510
+ logger.info(
511
+ "event_publisher_stats",
512
+ execution_id=self.execution_id[:8],
513
+ total_events=self._stats.total_events,
514
+ batched_events=self._stats.batched_events,
515
+ immediate_events=self._stats.immediate_events,
516
+ http_requests=self._stats.http_requests,
517
+ websocket_messages=self._stats.websocket_messages,
518
+ bytes_sent=self._stats.bytes_sent,
519
+ errors=self._stats.errors,
520
+ reduction_percent=self._stats.get_reduction_percent(),
521
+ transport_mode=self._current_transport.value,
522
+ )
523
+
524
+ def get_stats(self) -> EventStats:
525
+ """Get current publishing statistics."""
526
+ return self._stats
527
+
528
+ @property
529
+ def is_using_websocket(self) -> bool:
530
+ """Check if currently using WebSocket transport."""
531
+ return self._current_transport == TransportMode.WEBSOCKET and self._ws_connection is not None