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,628 @@
1
+ """
2
+ LLM Models CRUD API with LiteLLM Integration
3
+
4
+ This router provides model management with native LiteLLM integration.
5
+ Models are fetched dynamically from the LiteLLM server with caching for performance.
6
+ """
7
+ from fastapi import APIRouter, Depends, HTTPException, status, Request, Query, BackgroundTasks
8
+ from typing import List, Optional, Dict, Any
9
+ from pydantic import BaseModel, Field
10
+ from sqlalchemy.orm import Session
11
+ from datetime import datetime, timedelta
12
+ import structlog
13
+
14
+ from control_plane_api.app.middleware.auth import get_current_organization
15
+ from control_plane_api.app.database import get_db
16
+ from control_plane_api.app.models.llm_model import LLMModel as LLMModelDB
17
+ from control_plane_api.app.services.litellm_service import litellm_service
18
+ from control_plane_api.app.config import settings
19
+
20
+ logger = structlog.get_logger()
21
+
22
+ router = APIRouter()
23
+
24
+ # Cache for LiteLLM models (in-memory cache with TTL)
25
+ _models_cache: Optional[Dict[str, Any]] = None
26
+ _cache_timestamp: Optional[datetime] = None
27
+
28
+
29
+ # ==================== Pydantic Schemas ====================
30
+
31
+ class LLMModelCreate(BaseModel):
32
+ """Schema for creating a new LLM model"""
33
+ value: str = Field(..., description="Model identifier (e.g., 'kubiya/claude-sonnet-4')")
34
+ label: str = Field(..., description="Display name (e.g., 'Claude Sonnet 4')")
35
+ provider: str = Field(..., description="Provider name (e.g., 'Anthropic', 'OpenAI')")
36
+ model_type: str = Field("text-generation", description="Model type: 'text-generation', 'embedding', 'multimodal'")
37
+ logo: Optional[str] = Field(None, description="Logo path or URL")
38
+ description: Optional[str] = Field(None, description="Model description")
39
+ enabled: bool = Field(True, description="Whether model is enabled")
40
+ recommended: bool = Field(False, description="Whether model is recommended by default")
41
+ compatible_runtimes: List[str] = Field(
42
+ default_factory=list,
43
+ description="List of compatible runtime IDs (e.g., ['default', 'claude_code'])"
44
+ )
45
+ capabilities: dict = Field(
46
+ default_factory=dict,
47
+ description="Model capabilities (e.g., {'vision': true, 'max_tokens': 4096})"
48
+ )
49
+ pricing: Optional[dict] = Field(None, description="Pricing information")
50
+ display_order: int = Field(1000, description="Display order (lower = shown first)")
51
+
52
+
53
+ class LLMModelUpdate(BaseModel):
54
+ """Schema for updating an existing LLM model"""
55
+ value: Optional[str] = None
56
+ label: Optional[str] = None
57
+ provider: Optional[str] = None
58
+ model_type: Optional[str] = None
59
+ logo: Optional[str] = None
60
+ description: Optional[str] = None
61
+ enabled: Optional[bool] = None
62
+ recommended: Optional[bool] = None
63
+ compatible_runtimes: Optional[List[str]] = None
64
+ capabilities: Optional[dict] = None
65
+ pricing: Optional[dict] = None
66
+ display_order: Optional[int] = None
67
+
68
+
69
+ class LLMModelResponse(BaseModel):
70
+ """Schema for LLM model responses"""
71
+ id: str
72
+ value: str
73
+ label: str
74
+ provider: str
75
+ model_type: str
76
+ logo: Optional[str]
77
+ description: Optional[str]
78
+ enabled: bool
79
+ recommended: bool
80
+ compatible_runtimes: List[str]
81
+ capabilities: dict
82
+ pricing: Optional[dict]
83
+ display_order: int
84
+ created_at: str
85
+ updated_at: str
86
+
87
+ class Config:
88
+ from_attributes = True
89
+
90
+
91
+ # ==================== Helper Functions ====================
92
+
93
+ async def fetch_models_from_litellm_cached(db: Optional[Session] = None) -> List[Dict[str, Any]]:
94
+ """
95
+ Fetch models from LiteLLM with caching and database fallback.
96
+
97
+ Uses in-memory cache with configurable TTL to avoid hitting LiteLLM on every request.
98
+ Falls back to:
99
+ 1. Stale cache if LiteLLM is temporarily unavailable
100
+ 2. Database models if no cache available
101
+ 3. Empty list if all sources fail
102
+
103
+ This makes the system work seamlessly in all scenarios:
104
+ - With LiteLLM gateway: Returns live models
105
+ - Without LiteLLM: Returns database models (manually configured)
106
+ - Production: Works with or without LiteLLM endpoint
107
+ """
108
+ global _models_cache, _cache_timestamp
109
+
110
+ # Check if cache is valid
111
+ if _models_cache is not None and _cache_timestamp is not None:
112
+ cache_age = datetime.utcnow() - _cache_timestamp
113
+ if cache_age.total_seconds() < settings.litellm_models_cache_ttl:
114
+ logger.debug("returning_cached_models", count=len(_models_cache.get("models", [])), source=_models_cache.get("source", "unknown"))
115
+ return _models_cache.get("models", [])
116
+
117
+ # Cache miss or expired - try to fetch from LiteLLM
118
+ try:
119
+ logger.info("fetching_models_from_litellm", base_url=settings.litellm_api_base)
120
+ models = await litellm_service.fetch_available_models()
121
+
122
+ if models:
123
+ # Update cache with LiteLLM models
124
+ _models_cache = {"models": models, "source": "litellm"}
125
+ _cache_timestamp = datetime.utcnow()
126
+
127
+ logger.info("models_fetched_from_litellm", count=len(models), ttl=settings.litellm_models_cache_ttl)
128
+ return models
129
+
130
+ except Exception as e:
131
+ logger.warning("litellm_fetch_failed", error=str(e), msg="Trying fallbacks...")
132
+
133
+ # Fallback 1: Return stale cache if available
134
+ if _models_cache is not None and _models_cache.get("models"):
135
+ cache_age = (datetime.utcnow() - _cache_timestamp).total_seconds() if _cache_timestamp else float('inf')
136
+ logger.warning("returning_stale_cache", age_seconds=cache_age, source=_models_cache.get("source", "unknown"))
137
+ return _models_cache.get("models", [])
138
+
139
+ # Fallback 2: Fetch from database if available
140
+ if db is not None:
141
+ try:
142
+ logger.info("fetching_models_from_database")
143
+ db_models = db.query(LLMModelDB).filter(LLMModelDB.enabled == True).all()
144
+
145
+ if db_models:
146
+ # Convert database models to OpenAI format for consistency
147
+ models = [
148
+ {
149
+ "id": model.value,
150
+ "object": "model",
151
+ "created": int(model.created_at.timestamp()) if model.created_at else 0,
152
+ "owned_by": model.provider
153
+ }
154
+ for model in db_models
155
+ ]
156
+
157
+ # Cache database models
158
+ _models_cache = {"models": models, "source": "database"}
159
+ _cache_timestamp = datetime.utcnow()
160
+
161
+ logger.info("models_fetched_from_database", count=len(models))
162
+ return models
163
+
164
+ except Exception as e:
165
+ logger.error("database_fetch_failed", error=str(e))
166
+
167
+ # No models available from any source
168
+ logger.error("no_models_available_from_any_source")
169
+ return []
170
+
171
+
172
+ def convert_litellm_model_to_response(litellm_model: Dict[str, Any]) -> LLMModelResponse:
173
+ """Convert a LiteLLM model dict to our response format"""
174
+ model_id = litellm_model.get("id", "")
175
+
176
+ # Provider mapping for logo resolution
177
+ provider_logo_map = {
178
+ "anthropic": "/thirdparty/logos/anthropic.svg",
179
+ "openai": "/thirdparty/logos/openai.svg",
180
+ "google": "/thirdparty/logos/google.svg",
181
+ "mistral": "/thirdparty/logos/mistral.svg",
182
+ "groq": "/thirdparty/logos/groq.svg",
183
+ "deepseek": "/thirdparty/logos/deepseek.svg",
184
+ "xai": "/thirdparty/logos/xai.svg",
185
+ "meta": "/logos/meta-logo.svg",
186
+ }
187
+
188
+ # Extract provider - handle both "provider/model" and model names
189
+ if "/" in model_id:
190
+ # Format: kubiya/claude-sonnet-4 or openai/gpt-4
191
+ prefix = model_id.split("/")[0].lower()
192
+ # Check if it's a known provider prefix
193
+ if prefix in provider_logo_map:
194
+ provider = prefix.capitalize()
195
+ else:
196
+ # It's a custom prefix (like "kubiya"), detect actual provider from model name
197
+ model_name = model_id.split("/", 1)[1].lower()
198
+ if "claude" in model_name:
199
+ provider = "Anthropic"
200
+ elif "gpt" in model_name or "o1" in model_name or "o3" in model_name:
201
+ provider = "OpenAI"
202
+ elif "gemini" in model_name:
203
+ provider = "Google"
204
+ elif "llama" in model_name:
205
+ provider = "Meta"
206
+ elif "mistral" in model_name:
207
+ provider = "Mistral"
208
+ elif "deepseek" in model_name:
209
+ provider = "DeepSeek"
210
+ elif "grok" in model_name:
211
+ provider = "xAI"
212
+ else:
213
+ provider = prefix.capitalize()
214
+ else:
215
+ # No slash - try to detect from model name
216
+ model_lower = model_id.lower()
217
+ if "claude" in model_lower:
218
+ provider = "Anthropic"
219
+ elif "gpt" in model_lower or "o1" in model_lower or "o3" in model_lower:
220
+ provider = "OpenAI"
221
+ elif "gemini" in model_lower:
222
+ provider = "Google"
223
+ elif "llama" in model_lower:
224
+ provider = "Meta"
225
+ elif "mistral" in model_lower:
226
+ provider = "Mistral"
227
+ elif "deepseek" in model_lower:
228
+ provider = "DeepSeek"
229
+ elif "grok" in model_lower:
230
+ provider = "xAI"
231
+ else:
232
+ provider = litellm_model.get("owned_by", "Unknown")
233
+
234
+ # Get logo based on provider
235
+ provider_key = provider.lower()
236
+ logo = provider_logo_map.get(provider_key)
237
+
238
+ # Generate label - remove provider prefix and clean up
239
+ if "/" in model_id:
240
+ label_base = model_id.split("/", 1)[1]
241
+ else:
242
+ label_base = model_id
243
+
244
+ # Better label formatting
245
+ label = label_base.replace("-", " ").replace("_", " ").title()
246
+ # Fix common capitalization issues
247
+ label = label.replace("Gpt", "GPT").replace("Llm", "LLM").replace("Api", "API")
248
+
249
+ # Get mode from LiteLLM and map to our model_type
250
+ litellm_mode = litellm_model.get("mode", "completion")
251
+
252
+ # Map LiteLLM mode to our model_type
253
+ model_type_map = {
254
+ "embedding": "embedding",
255
+ "chat": "text-generation",
256
+ "completion": "text-generation",
257
+ "image_generation": "multimodal",
258
+ "audio_transcription": "audio",
259
+ "moderation": "moderation"
260
+ }
261
+ model_type = model_type_map.get(litellm_mode, "text-generation")
262
+
263
+ # Determine compatible runtimes based on model ID
264
+ compatible_runtimes = ["default"]
265
+ if "claude" in model_id.lower():
266
+ compatible_runtimes.append("claude_code")
267
+
268
+ # Determine if model should be recommended
269
+ # Prioritize Claude Sonnet 4 variants
270
+ model_lower = model_id.lower()
271
+ is_recommended = (
272
+ "claude-sonnet-4" in model_lower or
273
+ "claude-4-sonnet" in model_lower or
274
+ (model_lower.endswith("claude-sonnet-4") or "claude-sonnet-4-" in model_lower)
275
+ )
276
+
277
+ # Set display order (lower = shown first)
278
+ # Recommended models get priority
279
+ if is_recommended:
280
+ display_order = 1
281
+ elif "claude" in model_lower and "sonnet" in model_lower:
282
+ display_order = 10
283
+ elif "claude" in model_lower:
284
+ display_order = 50
285
+ elif "gpt-4" in model_lower:
286
+ display_order = 100
287
+ else:
288
+ display_order = 1000
289
+
290
+ return LLMModelResponse(
291
+ id=model_id, # Use model ID as the ID for LiteLLM models
292
+ value=model_id,
293
+ label=label,
294
+ provider=provider,
295
+ model_type=model_type,
296
+ logo=logo,
297
+ description=f"{provider} model: {label}",
298
+ enabled=True,
299
+ recommended=is_recommended,
300
+ compatible_runtimes=compatible_runtimes,
301
+ capabilities={},
302
+ pricing=None,
303
+ display_order=display_order,
304
+ created_at=datetime.utcnow().isoformat(),
305
+ updated_at=datetime.utcnow().isoformat(),
306
+ )
307
+
308
+
309
+ def check_runtime_compatibility(model: LLMModelDB, runtime_id: Optional[str]) -> bool:
310
+ """Check if a model is compatible with a specific runtime"""
311
+ if not runtime_id:
312
+ return True # No filter specified
313
+ if not model.compatible_runtimes:
314
+ return True # Model doesn't specify compatibility, allow all
315
+ return runtime_id in model.compatible_runtimes
316
+
317
+
318
+ def check_runtime_compatibility_dict(compatible_runtimes: List[str], runtime_id: Optional[str]) -> bool:
319
+ """Check if a model is compatible with a specific runtime (dict version)"""
320
+ if not runtime_id:
321
+ return True # No filter specified
322
+ if not compatible_runtimes:
323
+ return True # Model doesn't specify compatibility, allow all
324
+ return runtime_id in compatible_runtimes
325
+
326
+
327
+ # ==================== CRUD Endpoints ====================
328
+
329
+ @router.post("", response_model=LLMModelResponse, status_code=status.HTTP_201_CREATED)
330
+ def create_model(
331
+ model_data: LLMModelCreate,
332
+ request: Request,
333
+ db: Session = Depends(get_db),
334
+ organization: dict = Depends(get_current_organization),
335
+ ):
336
+ """
337
+ Create a new LLM model.
338
+
339
+ Only accessible by authenticated users (org admins recommended).
340
+ """
341
+ # Check if model with this value already exists
342
+ existing = db.query(LLMModelDB).filter(LLMModelDB.value == model_data.value).first()
343
+ if existing:
344
+ raise HTTPException(
345
+ status_code=status.HTTP_409_CONFLICT,
346
+ detail=f"Model with value '{model_data.value}' already exists"
347
+ )
348
+
349
+ # Create new model
350
+ new_model = LLMModelDB(
351
+ value=model_data.value,
352
+ label=model_data.label,
353
+ provider=model_data.provider,
354
+ model_type=model_data.model_type,
355
+ logo=model_data.logo,
356
+ description=model_data.description,
357
+ enabled=model_data.enabled,
358
+ recommended=model_data.recommended,
359
+ compatible_runtimes=model_data.compatible_runtimes,
360
+ capabilities=model_data.capabilities,
361
+ pricing=model_data.pricing,
362
+ display_order=model_data.display_order,
363
+ created_by=organization.get("user_id"),
364
+ )
365
+
366
+ db.add(new_model)
367
+ db.commit()
368
+ db.refresh(new_model)
369
+
370
+ logger.info(
371
+ "llm_model_created",
372
+ model_id=new_model.id,
373
+ model_value=new_model.value,
374
+ provider=new_model.provider,
375
+ org_id=organization["id"]
376
+ )
377
+
378
+ return model_to_response(new_model)
379
+
380
+
381
+ def get_db_optional():
382
+ """Get database session, returns None if database not configured"""
383
+ try:
384
+ db = next(get_db())
385
+ try:
386
+ yield db
387
+ finally:
388
+ db.close()
389
+ except RuntimeError:
390
+ # Database not configured
391
+ yield None
392
+
393
+
394
+ @router.get("", response_model=List[LLMModelResponse])
395
+ async def list_models(
396
+ db: Optional[Session] = Depends(get_db_optional),
397
+ provider: Optional[str] = Query(None, description="Filter by provider (e.g., 'xai', 'anthropic')"),
398
+ runtime: Optional[str] = Query(None, description="Filter by compatible runtime (e.g., 'claude_code')"),
399
+ type: Optional[str] = Query(None, description="Filter by model type (e.g., 'embedding', 'text-generation')"),
400
+ skip: int = Query(0, ge=0),
401
+ limit: int = Query(100, ge=1, le=1000),
402
+ ):
403
+ """
404
+ List all LLM models from LiteLLM server with database fallback.
405
+
406
+ Models are fetched dynamically from the configured LiteLLM server with caching for performance.
407
+ Falls back to database models if LiteLLM is unavailable.
408
+
409
+ Query Parameters:
410
+ - provider: Filter by provider name (e.g., 'xai', 'anthropic', 'openai')
411
+ - runtime: Filter by compatible runtime (e.g., 'claude_code', 'default')
412
+ - type: Filter by model type (e.g., 'embedding', 'text-generation', 'multimodal')
413
+ - skip/limit: Pagination
414
+
415
+ **Example:**
416
+ ```
417
+ GET /api/v1/models
418
+ GET /api/v1/models?provider=xai
419
+ GET /api/v1/models?runtime=claude_code
420
+ GET /api/v1/models?type=embedding
421
+ GET /api/v1/models?type=text-generation&provider=anthropic
422
+ ```
423
+ """
424
+ # Fetch models from LiteLLM (with caching and database fallback)
425
+ litellm_models = await fetch_models_from_litellm_cached(db=db)
426
+
427
+ # Convert to response format
428
+ models = [convert_litellm_model_to_response(m) for m in litellm_models]
429
+
430
+ # Apply filters
431
+ if provider:
432
+ models = [m for m in models if m.provider.lower() == provider.lower()]
433
+
434
+ if runtime:
435
+ models = [m for m in models if check_runtime_compatibility_dict(m.compatible_runtimes, runtime)]
436
+
437
+ if type:
438
+ models = [m for m in models if m.model_type.lower() == type.lower()]
439
+
440
+ # Apply pagination
441
+ total = len(models)
442
+ models = models[skip : skip + limit]
443
+
444
+ logger.info("listed_models", total=total, returned=len(models), provider=provider, runtime=runtime, type=type)
445
+
446
+ return models
447
+
448
+
449
+ @router.get("/default", response_model=LLMModelResponse)
450
+ async def get_default_model(db: Optional[Session] = Depends(get_db_optional)):
451
+ """
452
+ Get the default recommended LLM model.
453
+
454
+ Returns a recommended model from LiteLLM or falls back to database/first available model.
455
+ Uses LITELLM_DEFAULT_MODEL from environment if set.
456
+ """
457
+ # Fetch models from LiteLLM (with database fallback)
458
+ litellm_models = await fetch_models_from_litellm_cached(db=db)
459
+
460
+ if not litellm_models:
461
+ raise HTTPException(
462
+ status_code=status.HTTP_404_NOT_FOUND,
463
+ detail="No models available"
464
+ )
465
+
466
+ # Try to find the default model from config
467
+ if settings.litellm_default_model:
468
+ for model in litellm_models:
469
+ if model.get("id") == settings.litellm_default_model:
470
+ return convert_litellm_model_to_response(model)
471
+
472
+ # Fallback to first available model
473
+ return convert_litellm_model_to_response(litellm_models[0])
474
+
475
+
476
+ @router.get("/providers", response_model=List[str])
477
+ async def list_providers(db: Optional[Session] = Depends(get_db_optional)):
478
+ """
479
+ Get list of unique model providers.
480
+
481
+ Returns a list of all unique provider names from available models.
482
+ """
483
+ # Fetch models (with database fallback)
484
+ litellm_models = await fetch_models_from_litellm_cached(db=db)
485
+
486
+ # Extract unique providers
487
+ providers = set()
488
+ for model in litellm_models:
489
+ model_id = model.get("id", "")
490
+ if "/" in model_id:
491
+ provider = model_id.split("/")[0]
492
+ providers.add(provider)
493
+
494
+ return sorted(list(providers))
495
+
496
+
497
+ @router.get("/{model_id:path}", response_model=LLMModelResponse)
498
+ async def get_model(model_id: str, db: Optional[Session] = Depends(get_db_optional)):
499
+ """
500
+ Get a specific LLM model by ID.
501
+
502
+ Accepts model ID in the format: provider/model (e.g., 'xai/grok-2-1212')
503
+
504
+ **Example:**
505
+ ```
506
+ GET /api/v1/models/xai/grok-2-1212
507
+ GET /api/v1/models/kubiya/claude-sonnet-4
508
+ ```
509
+ """
510
+ # Fetch models (with database fallback)
511
+ litellm_models = await fetch_models_from_litellm_cached(db=db)
512
+
513
+ # Find the model
514
+ for model in litellm_models:
515
+ if model.get("id") == model_id:
516
+ return convert_litellm_model_to_response(model)
517
+
518
+ # Model not found
519
+ raise HTTPException(
520
+ status_code=status.HTTP_404_NOT_FOUND,
521
+ detail=f"Model '{model_id}' not found"
522
+ )
523
+
524
+
525
+ @router.patch("/{model_id}", response_model=LLMModelResponse)
526
+ def update_model(
527
+ model_id: str,
528
+ model_data: LLMModelUpdate,
529
+ request: Request,
530
+ db: Session = Depends(get_db),
531
+ organization: dict = Depends(get_current_organization),
532
+ ):
533
+ """
534
+ Update an existing LLM model.
535
+
536
+ Only accessible by authenticated users (org admins recommended).
537
+ """
538
+ # Find model
539
+ model = db.query(LLMModelDB).filter(LLMModelDB.id == model_id).first()
540
+ if not model:
541
+ raise HTTPException(
542
+ status_code=status.HTTP_404_NOT_FOUND,
543
+ detail=f"Model '{model_id}' not found"
544
+ )
545
+
546
+ # Check if value is being updated and conflicts with existing
547
+ if model_data.value and model_data.value != model.value:
548
+ existing = db.query(LLMModelDB).filter(LLMModelDB.value == model_data.value).first()
549
+ if existing:
550
+ raise HTTPException(
551
+ status_code=status.HTTP_409_CONFLICT,
552
+ detail=f"Model with value '{model_data.value}' already exists"
553
+ )
554
+
555
+ # Update fields
556
+ update_dict = model_data.model_dump(exclude_unset=True)
557
+ for field, value in update_dict.items():
558
+ setattr(model, field, value)
559
+
560
+ model.updated_at = datetime.utcnow()
561
+
562
+ db.commit()
563
+ db.refresh(model)
564
+
565
+ logger.info(
566
+ "llm_model_updated",
567
+ model_id=model.id,
568
+ model_value=model.value,
569
+ updated_fields=list(update_dict.keys()),
570
+ org_id=organization["id"]
571
+ )
572
+
573
+ return model_to_response(model)
574
+
575
+
576
+ @router.delete("/{model_id}", status_code=status.HTTP_204_NO_CONTENT)
577
+ def delete_model(
578
+ model_id: str,
579
+ request: Request,
580
+ db: Session = Depends(get_db),
581
+ organization: dict = Depends(get_current_organization),
582
+ ):
583
+ """
584
+ Delete an LLM model.
585
+
586
+ Only accessible by authenticated users (org admins recommended).
587
+ """
588
+ model = db.query(LLMModelDB).filter(LLMModelDB.id == model_id).first()
589
+ if not model:
590
+ raise HTTPException(
591
+ status_code=status.HTTP_404_NOT_FOUND,
592
+ detail=f"Model '{model_id}' not found"
593
+ )
594
+
595
+ db.delete(model)
596
+ db.commit()
597
+
598
+ logger.info(
599
+ "llm_model_deleted",
600
+ model_id=model.id,
601
+ model_value=model.value,
602
+ org_id=organization["id"]
603
+ )
604
+
605
+ return None
606
+
607
+
608
+ # ==================== Helper Functions ====================
609
+
610
+ def model_to_response(model: LLMModelDB) -> LLMModelResponse:
611
+ """Convert database model to response schema"""
612
+ return LLMModelResponse(
613
+ id=model.id,
614
+ value=model.value,
615
+ label=model.label,
616
+ provider=model.provider,
617
+ model_type=model.model_type,
618
+ logo=model.logo,
619
+ description=model.description,
620
+ enabled=model.enabled,
621
+ recommended=model.recommended,
622
+ compatible_runtimes=model.compatible_runtimes or [],
623
+ capabilities=model.capabilities or {},
624
+ pricing=model.pricing,
625
+ display_order=model.display_order,
626
+ created_at=model.created_at.isoformat() if model.created_at else "",
627
+ updated_at=model.updated_at.isoformat() if model.updated_at else "",
628
+ )