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,394 @@
1
+ """
2
+ LiteLLM Service
3
+
4
+ This service provides a wrapper around LiteLLM for agent execution.
5
+ """
6
+
7
+ import os
8
+ from typing import Dict, List, Optional, Any
9
+ import litellm
10
+ from litellm import completion, get_valid_models
11
+ import logging
12
+ import httpx
13
+
14
+ from control_plane_api.app.config import settings
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class LiteLLMService:
20
+ """Service for interacting with LiteLLM"""
21
+
22
+ def __init__(self):
23
+ """Initialize LiteLLM service with configuration"""
24
+ # Set LiteLLM configuration
25
+ if settings.litellm_api_key:
26
+ os.environ["LITELLM_API_KEY"] = settings.litellm_api_key
27
+ litellm.api_base = settings.litellm_api_base
28
+ litellm.drop_params = True # Drop unsupported params instead of failing
29
+
30
+ # Configure timeout
31
+ litellm.request_timeout = settings.litellm_timeout
32
+
33
+ logger.info(f"LiteLLM Service initialized with base URL: {settings.litellm_api_base}")
34
+
35
+ async def fetch_available_models(self, base_url: Optional[str] = None) -> List[Dict[str, Any]]:
36
+ """
37
+ Fetch available models from the LiteLLM proxy server.
38
+
39
+ This method tries multiple approaches:
40
+ 1. HTTP API call to /model/info endpoint (includes mode field)
41
+ 2. Fallback to /v1/models endpoint with mode detection
42
+ 3. Fallback to using LiteLLM SDK's get_valid_models() if available
43
+
44
+ Args:
45
+ base_url: Optional override for the LiteLLM base URL
46
+
47
+ Returns:
48
+ List of model objects with mode field from the LiteLLM server
49
+
50
+ Raises:
51
+ Exception if all methods fail
52
+ """
53
+ # Use provided base_url or fall back to settings
54
+ api_base = base_url or settings.litellm_api_base
55
+
56
+ # Try Method 1: /model/info endpoint (includes mode)
57
+ try:
58
+ model_info_url = f"{api_base.rstrip('/')}/model/info"
59
+ logger.info(f"Fetching models from LiteLLM /model/info endpoint: {model_info_url}")
60
+
61
+ async with httpx.AsyncClient(timeout=30.0) as client:
62
+ headers = {}
63
+ if settings.litellm_api_key:
64
+ headers["Authorization"] = f"Bearer {settings.litellm_api_key}"
65
+
66
+ response = await client.get(model_info_url, headers=headers)
67
+ response.raise_for_status()
68
+
69
+ data = response.json()
70
+
71
+ # Parse model_info response
72
+ models = []
73
+ for model_data in data.get("data", []):
74
+ models.append({
75
+ "id": model_data.get("model_name"),
76
+ "object": "model",
77
+ "created": 0,
78
+ "owned_by": model_data.get("litellm_provider", "unknown"),
79
+ "mode": model_data.get("mode", "completion") # Include mode from LiteLLM
80
+ })
81
+
82
+ logger.info(f"Successfully fetched {len(models)} models from LiteLLM /model/info with mode information")
83
+ return models
84
+
85
+ except httpx.HTTPStatusError as e:
86
+ logger.warning(
87
+ f"/model/info endpoint failed: {e.response.status_code}. "
88
+ f"Trying fallback to /v1/models..."
89
+ )
90
+
91
+ except Exception as e:
92
+ logger.warning(f"/model/info request failed: {str(e)}. Trying fallback to /v1/models...")
93
+
94
+ # Try Method 2: /v1/models endpoint (detect mode from model name)
95
+ try:
96
+ models_url = f"{api_base.rstrip('/')}/v1/models"
97
+ logger.info(f"Fetching models from LiteLLM server: {models_url}")
98
+
99
+ async with httpx.AsyncClient(timeout=30.0) as client:
100
+ headers = {}
101
+ if settings.litellm_api_key:
102
+ headers["Authorization"] = f"Bearer {settings.litellm_api_key}"
103
+
104
+ response = await client.get(models_url, headers=headers)
105
+ response.raise_for_status()
106
+
107
+ data = response.json()
108
+
109
+ # LiteLLM returns models in OpenAI format: {"data": [...], "object": "list"}
110
+ raw_models = data.get("data", [])
111
+
112
+ # Add mode detection from model name
113
+ models = []
114
+ for model in raw_models:
115
+ model_id = model.get("id", "")
116
+ model_lower = model_id.lower()
117
+
118
+ # Detect mode from model name - expanded detection for embedding models
119
+ # Check for embedding keywords (including common embedding model patterns)
120
+ embedding_keywords = [
121
+ "embedding", "embed", "embeddings",
122
+ "ada-002", "text-embedding",
123
+ "bge-", "gte-", "e5-", # Common embedding model prefixes
124
+ "voyage-", "cohere-embed", # Provider-specific embedding models
125
+ "-embed-", "_embed_", "/embed", # Pattern matching
126
+ ]
127
+
128
+ if any(keyword in model_lower for keyword in embedding_keywords):
129
+ mode = "embedding"
130
+ elif any(keyword in model_lower for keyword in ["gpt", "claude", "llama", "mistral", "gemini", "deepseek", "qwen"]):
131
+ mode = "chat"
132
+ else:
133
+ mode = "completion"
134
+
135
+ model["mode"] = mode
136
+ models.append(model)
137
+
138
+ logger.info(f"Successfully fetched {len(models)} models from LiteLLM server via /v1/models with mode detection")
139
+ return models
140
+
141
+ except httpx.HTTPStatusError as e:
142
+ logger.warning(
143
+ f"HTTP endpoint failed: {e.response.status_code}. "
144
+ f"Trying SDK fallback method..."
145
+ )
146
+
147
+ except Exception as e:
148
+ logger.warning(f"HTTP request failed: {str(e)}. Trying SDK fallback method...")
149
+
150
+ # Try Method 3: Use LiteLLM Python SDK (synchronous, needs to be run in executor)
151
+ try:
152
+ import asyncio
153
+ from concurrent.futures import ThreadPoolExecutor
154
+
155
+ def _get_models_sync():
156
+ """Synchronous function to get models using LiteLLM SDK"""
157
+ try:
158
+ # Set environment for LiteLLM
159
+ if settings.litellm_api_key:
160
+ os.environ["LITELLM_API_KEY"] = settings.litellm_api_key
161
+
162
+ # Use get_valid_models with provider endpoint check
163
+ model_names = get_valid_models(check_provider_endpoint=True)
164
+
165
+ # Convert model strings to OpenAI-like format with mode detection
166
+ models = []
167
+ for model_name in model_names:
168
+ model_lower = model_name.lower()
169
+
170
+ # Detect mode from model name - expanded detection for embedding models
171
+ # Check for embedding keywords (including common embedding model patterns)
172
+ embedding_keywords = [
173
+ "embedding", "embed", "embeddings",
174
+ "ada-002", "text-embedding",
175
+ "bge-", "gte-", "e5-", # Common embedding model prefixes
176
+ "voyage-", "cohere-embed", # Provider-specific embedding models
177
+ "-embed-", "_embed_", "/embed", # Pattern matching
178
+ ]
179
+
180
+ if any(keyword in model_lower for keyword in embedding_keywords):
181
+ mode = "embedding"
182
+ elif any(keyword in model_lower for keyword in ["gpt", "claude", "llama", "mistral", "gemini", "deepseek", "qwen"]):
183
+ mode = "chat"
184
+ else:
185
+ mode = "completion"
186
+
187
+ models.append({
188
+ "id": model_name,
189
+ "object": "model",
190
+ "created": 0,
191
+ "owned_by": "litellm",
192
+ "mode": mode
193
+ })
194
+
195
+ return models
196
+ except Exception as e:
197
+ logger.error(f"LiteLLM SDK get_valid_models failed: {str(e)}")
198
+ return []
199
+
200
+ # Run synchronous function in thread pool
201
+ loop = asyncio.get_event_loop()
202
+ with ThreadPoolExecutor() as executor:
203
+ models = await loop.run_in_executor(executor, _get_models_sync)
204
+
205
+ if models:
206
+ logger.info(f"Successfully fetched {len(models)} models using LiteLLM SDK with mode detection")
207
+ return models
208
+
209
+ except Exception as e:
210
+ logger.error(f"LiteLLM SDK method failed: {str(e)}")
211
+
212
+ # If all methods failed, raise exception
213
+ raise Exception(
214
+ f"Failed to fetch models from LiteLLM server. "
215
+ f"Tried HTTP endpoint and SDK method. "
216
+ f"Please ensure LiteLLM proxy is running at {api_base}"
217
+ )
218
+
219
+ def validate_model(self, model_id: str) -> bool:
220
+ """
221
+ Validate if a model ID is supported by the LiteLLM server.
222
+
223
+ Args:
224
+ model_id: The model identifier to validate
225
+
226
+ Returns:
227
+ True if the model is valid, False otherwise
228
+ """
229
+ try:
230
+ # For now, we'll accept any model ID that follows the provider/model format
231
+ # More sophisticated validation can be added later by checking against
232
+ # the models list from the server
233
+ return bool(model_id and "/" in model_id)
234
+ except Exception as e:
235
+ logger.error(f"Error validating model {model_id}: {str(e)}")
236
+ return False
237
+
238
+
239
+ def execute_agent(
240
+ self,
241
+ prompt: str,
242
+ model: Optional[str] = None,
243
+ system_prompt: Optional[str] = None,
244
+ temperature: float = 0.7,
245
+ max_tokens: Optional[int] = None,
246
+ top_p: Optional[float] = None,
247
+ **kwargs: Any,
248
+ ) -> Dict[str, Any]:
249
+ """
250
+ Execute an agent with LiteLLM
251
+
252
+ Args:
253
+ prompt: The user prompt
254
+ model: Model identifier (defaults to configured default)
255
+ system_prompt: System prompt for the agent
256
+ temperature: Temperature for response generation
257
+ max_tokens: Maximum tokens to generate
258
+ top_p: Top-p sampling parameter
259
+ **kwargs: Additional parameters to pass to LiteLLM
260
+
261
+ Returns:
262
+ Dict containing the response and metadata
263
+ """
264
+ try:
265
+ # Use default model if not specified
266
+ if not model:
267
+ model = settings.litellm_default_model
268
+
269
+ # Build messages
270
+ messages = []
271
+ if system_prompt:
272
+ messages.append({"role": "system", "content": system_prompt})
273
+ messages.append({"role": "user", "content": prompt})
274
+
275
+ # Prepare completion parameters
276
+ # For custom proxies, use openai/ prefix to force OpenAI-compatible mode
277
+ # This tells LiteLLM to use the base_url as an OpenAI-compatible endpoint
278
+ completion_params = {
279
+ "model": f"openai/{model}", # Use openai/ prefix for custom proxy
280
+ "messages": messages,
281
+ "temperature": temperature,
282
+ "api_key": settings.litellm_api_key or "dummy-key", # Fallback for when key is not set
283
+ "base_url": settings.litellm_api_base,
284
+ }
285
+
286
+ if max_tokens:
287
+ completion_params["max_tokens"] = max_tokens
288
+ if top_p:
289
+ completion_params["top_p"] = top_p
290
+
291
+ # Add any additional kwargs
292
+ completion_params.update(kwargs)
293
+
294
+ logger.info(f"Executing agent with model: {model} (using openai/{model})")
295
+
296
+ # Make the completion request
297
+ response = completion(**completion_params)
298
+
299
+ # Extract response content
300
+ result = {
301
+ "success": True,
302
+ "response": response.choices[0].message.content,
303
+ "model": model,
304
+ "usage": {
305
+ "prompt_tokens": response.usage.prompt_tokens,
306
+ "completion_tokens": response.usage.completion_tokens,
307
+ "total_tokens": response.usage.total_tokens,
308
+ },
309
+ "finish_reason": response.choices[0].finish_reason,
310
+ }
311
+
312
+ logger.info(f"Agent execution successful. Tokens used: {result['usage']['total_tokens']}")
313
+ return result
314
+
315
+ except Exception as e:
316
+ logger.error(f"Error executing agent: {str(e)}")
317
+ return {
318
+ "success": False,
319
+ "error": str(e),
320
+ "model": model or settings.litellm_default_model,
321
+ }
322
+
323
+ def execute_agent_stream(
324
+ self,
325
+ prompt: str,
326
+ model: Optional[str] = None,
327
+ system_prompt: Optional[str] = None,
328
+ temperature: float = 0.7,
329
+ max_tokens: Optional[int] = None,
330
+ top_p: Optional[float] = None,
331
+ **kwargs: Any,
332
+ ):
333
+ """
334
+ Execute an agent with streaming response
335
+
336
+ Args:
337
+ prompt: The user prompt
338
+ model: Model identifier (defaults to configured default)
339
+ system_prompt: System prompt for the agent
340
+ temperature: Temperature for response generation
341
+ max_tokens: Maximum tokens to generate
342
+ top_p: Top-p sampling parameter
343
+ **kwargs: Additional parameters to pass to LiteLLM
344
+
345
+ Yields:
346
+ Response chunks as they arrive
347
+ """
348
+ try:
349
+ # Use default model if not specified
350
+ if not model:
351
+ model = settings.litellm_default_model
352
+
353
+ # Build messages
354
+ messages = []
355
+ if system_prompt:
356
+ messages.append({"role": "system", "content": system_prompt})
357
+ messages.append({"role": "user", "content": prompt})
358
+
359
+ # Prepare completion parameters
360
+ # For custom proxies, use openai/ prefix to force OpenAI-compatible mode
361
+ # This tells LiteLLM to use the base_url as an OpenAI-compatible endpoint
362
+ completion_params = {
363
+ "model": f"openai/{model}", # Use openai/ prefix for custom proxy
364
+ "messages": messages,
365
+ "temperature": temperature,
366
+ "stream": True,
367
+ "api_key": settings.litellm_api_key or "dummy-key", # Fallback for when key is not set
368
+ "base_url": settings.litellm_api_base,
369
+ }
370
+
371
+ if max_tokens:
372
+ completion_params["max_tokens"] = max_tokens
373
+ if top_p:
374
+ completion_params["top_p"] = top_p
375
+
376
+ # Add any additional kwargs
377
+ completion_params.update(kwargs)
378
+
379
+ logger.info(f"Executing agent (streaming) with model: {model} (using openai/{model})")
380
+
381
+ # Make the streaming completion request
382
+ response = completion(**completion_params)
383
+
384
+ for chunk in response:
385
+ if chunk.choices[0].delta.content:
386
+ yield chunk.choices[0].delta.content
387
+
388
+ except Exception as e:
389
+ logger.error(f"Error executing agent (streaming): {str(e)}")
390
+ yield f"Error: {str(e)}"
391
+
392
+
393
+ # Singleton instance
394
+ litellm_service = LiteLLMService()
@@ -0,0 +1,79 @@
1
+ """
2
+ Shared plan generation service - used by both sync and async endpoints.
3
+ Clean code with zero duplication.
4
+ """
5
+ from typing import Dict, Any, Callable
6
+ from sqlalchemy.orm import Session
7
+ import structlog
8
+
9
+ from control_plane_api.app.models.task_planning import TaskPlanRequest, TaskPlanResponse
10
+ from control_plane_api.app.lib.task_planning.planning_workflow import (
11
+ create_planning_workflow as create_multistep_workflow,
12
+ run_planning_workflow_stream,
13
+ )
14
+ from control_plane_api.app.lib.task_planning.entity_resolver import resolve_plan_entities
15
+
16
+ logger = structlog.get_logger()
17
+
18
+
19
+ async def generate_plan(
20
+ request: TaskPlanRequest,
21
+ organization_id: str,
22
+ api_token: str,
23
+ db: Session,
24
+ event_callback: Callable[[Dict[str, Any]], None] = None,
25
+ ) -> TaskPlanResponse:
26
+ """
27
+ Generate a plan using the Agno workflow.
28
+
29
+ This is the SINGLE source of truth for plan generation,
30
+ used by both /tasks/plan/stream and async plan generation.
31
+
32
+ Args:
33
+ request: Task plan request
34
+ organization_id: Organization ID
35
+ api_token: API token for authentication
36
+ db: Database session
37
+ event_callback: Optional callback for streaming events
38
+
39
+ Returns:
40
+ Generated TaskPlanResponse with resolved entities
41
+ """
42
+ logger.info(
43
+ "generating_plan",
44
+ description=request.description[:100],
45
+ organization_id=organization_id,
46
+ quick_mode=request.quick_mode,
47
+ )
48
+
49
+ # Create the 2-step planning workflow
50
+ workflow = create_multistep_workflow(
51
+ db=db,
52
+ organization_id=organization_id,
53
+ api_token=api_token,
54
+ quick_mode=request.quick_mode,
55
+ outer_context=None,
56
+ )
57
+
58
+ logger.info("planning_workflow_created")
59
+
60
+ # Run the workflow with event streaming
61
+ plan = run_planning_workflow_stream(
62
+ workflow,
63
+ request,
64
+ event_callback or (lambda x: None), # No-op callback if not provided
65
+ request.quick_mode,
66
+ )
67
+
68
+ logger.info("plan_generated", plan_title=plan.title)
69
+
70
+ # Resolve entity names to UUIDs
71
+ await resolve_plan_entities(
72
+ plan_response=plan,
73
+ organization_id=organization_id,
74
+ db=db,
75
+ )
76
+
77
+ logger.info("plan_entities_resolved", plan_title=plan.title)
78
+
79
+ return plan
@@ -0,0 +1,66 @@
1
+ """
2
+ Planning Strategy Pattern
3
+
4
+ Defines the interface for different task planning implementations (Agno, Claude Code SDK, etc.)
5
+ Like choosing transportation: train, walk, or flight - same destination, different approaches.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Dict, Any, AsyncIterator
10
+ from sqlalchemy.orm import Session
11
+
12
+ from control_plane_api.app.models.task_planning import TaskPlanResponse
13
+
14
+
15
+ class PlanningStrategy(ABC):
16
+ """
17
+ Abstract base class for task planning strategies.
18
+
19
+ Each strategy implements the same interface but uses different underlying
20
+ technology (Agno, Claude Code SDK, etc.)
21
+ """
22
+
23
+ def __init__(self, db: Session, organization_id: str = None, api_token: str = None):
24
+ """
25
+ Initialize strategy.
26
+
27
+ Args:
28
+ db: Database session
29
+ organization_id: Organization ID
30
+ api_token: API token for authentication
31
+ """
32
+ self.db = db
33
+ self.organization_id = organization_id
34
+ self.api_token = api_token
35
+
36
+ @abstractmethod
37
+ async def plan_task(self, planning_prompt: str) -> TaskPlanResponse:
38
+ """
39
+ Generate a task plan (non-streaming).
40
+
41
+ Args:
42
+ planning_prompt: The complete planning prompt
43
+
44
+ Returns:
45
+ TaskPlanResponse with the generated plan
46
+ """
47
+ pass
48
+
49
+ @abstractmethod
50
+ async def plan_task_stream(self, planning_prompt: str) -> AsyncIterator[Dict[str, Any]]:
51
+ """
52
+ Generate a task plan with streaming events.
53
+
54
+ Args:
55
+ planning_prompt: The complete planning prompt
56
+
57
+ Yields:
58
+ Dict with event type and data for SSE streaming
59
+ """
60
+ pass
61
+
62
+ @property
63
+ @abstractmethod
64
+ def name(self) -> str:
65
+ """Return the strategy name for logging"""
66
+ pass
@@ -0,0 +1,118 @@
1
+ """
2
+ Planning Strategy Factory
3
+
4
+ Factory for creating the appropriate planning strategy based on configuration.
5
+ Like a travel agency - chooses the best transportation mode for you!
6
+ """
7
+
8
+ import os
9
+ import structlog
10
+ from typing import Literal
11
+ from sqlalchemy.orm import Session
12
+
13
+ from control_plane_api.app.services.planning_strategy import PlanningStrategy
14
+ from control_plane_api.app.services.claude_code_planning_service import ClaudeCodePlanningStrategy
15
+ from control_plane_api.app.services.agno_planning_strategy import AgnoPlanningStrategy
16
+
17
+ logger = structlog.get_logger(__name__)
18
+
19
+ # Supported strategy types
20
+ StrategyType = Literal["claude_code_sdk", "agno"]
21
+
22
+
23
+ def get_planning_strategy(
24
+ strategy_type: StrategyType = None,
25
+ db: Session = None,
26
+ organization_id: str = None,
27
+ api_token: str = None
28
+ ) -> PlanningStrategy:
29
+ """
30
+ Factory function to create the appropriate planning strategy.
31
+
32
+ Args:
33
+ strategy_type: Which strategy to use ("claude_code_sdk" or "agno")
34
+ If None, uses PLANNING_STRATEGY env var (defaults to "agno")
35
+ db: Database session
36
+ organization_id: Organization ID
37
+ api_token: API token
38
+
39
+ Returns:
40
+ Concrete PlanningStrategy implementation
41
+
42
+ Example:
43
+ # Use Claude Code SDK (default)
44
+ strategy = get_planning_strategy(db=db)
45
+
46
+ # Explicitly use Agno
47
+ strategy = get_planning_strategy(strategy_type="agno", db=db)
48
+
49
+ # Use both in your code
50
+ plan = await strategy.plan_task(prompt) # Same interface!
51
+ """
52
+ # Determine strategy from parameter or env var
53
+ if strategy_type is None:
54
+ strategy_type = os.getenv("PLANNING_STRATEGY", "agno").lower()
55
+
56
+ logger.info(
57
+ "creating_planning_strategy",
58
+ strategy_type=strategy_type,
59
+ has_db=db is not None,
60
+ has_org_id=organization_id is not None,
61
+ )
62
+
63
+ # Validate strategy availability and detect CLI
64
+ if strategy_type == "claude_code_sdk":
65
+ # Check if SDK is available
66
+ try:
67
+ from claude_agent_sdk import ClaudeSDKClient
68
+ except ImportError:
69
+ logger.error(
70
+ "strategy_unavailable",
71
+ strategy_type=strategy_type,
72
+ message="claude-agent-sdk not available, falling back to Agno"
73
+ )
74
+ strategy_type = "agno" # Fallback
75
+ else:
76
+ # Check if Claude CLI binary is available
77
+ import shutil
78
+ claude_cli = shutil.which("claude")
79
+ if not claude_cli:
80
+ logger.warning(
81
+ "claude_cli_not_found",
82
+ message="Claude Code CLI binary not found in PATH. Falling back to Agno.",
83
+ hint="Install @anthropic-ai/claude-code npm package or use PLANNING_STRATEGY=agno"
84
+ )
85
+ strategy_type = "agno" # Auto-fallback
86
+ else:
87
+ logger.info(
88
+ "claude_cli_found",
89
+ cli_path=claude_cli,
90
+ message="Claude Code CLI available, using claude_code_sdk strategy"
91
+ )
92
+
93
+ # Create the appropriate strategy
94
+ if strategy_type == "claude_code_sdk":
95
+ return ClaudeCodePlanningStrategy(
96
+ db=db,
97
+ organization_id=organization_id,
98
+ api_token=api_token
99
+ )
100
+ elif strategy_type == "agno":
101
+ return AgnoPlanningStrategy(
102
+ db=db,
103
+ organization_id=organization_id,
104
+ api_token=api_token
105
+ )
106
+ else:
107
+ logger.error("unknown_strategy_type", strategy_type=strategy_type)
108
+ # Default to Claude Code SDK
109
+ logger.warning("defaulting_to_claude_code_sdk")
110
+ return ClaudeCodePlanningStrategy(
111
+ db=db,
112
+ organization_id=organization_id,
113
+ api_token=api_token
114
+ )
115
+
116
+
117
+ # Convenience alias for backward compatibility
118
+ get_claude_code_planning_service = get_planning_strategy