kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.4__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.

Potentially problematic release.


This version of kubiya-control-plane-api might be problematic. Click here for more details.

Files changed (185) hide show
  1. control_plane_api/README.md +266 -0
  2. control_plane_api/__init__.py +0 -0
  3. control_plane_api/__version__.py +1 -0
  4. control_plane_api/alembic/README +1 -0
  5. control_plane_api/alembic/env.py +98 -0
  6. control_plane_api/alembic/script.py.mako +28 -0
  7. control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
  8. control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
  9. control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
  10. control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
  11. control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
  12. control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
  13. control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
  14. control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
  15. control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
  16. control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
  17. control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
  18. control_plane_api/alembic.ini +148 -0
  19. control_plane_api/api/index.py +12 -0
  20. control_plane_api/app/__init__.py +11 -0
  21. control_plane_api/app/activities/__init__.py +20 -0
  22. control_plane_api/app/activities/agent_activities.py +379 -0
  23. control_plane_api/app/activities/team_activities.py +410 -0
  24. control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
  25. control_plane_api/app/config/__init__.py +35 -0
  26. control_plane_api/app/config/api_config.py +354 -0
  27. control_plane_api/app/config/model_pricing.py +318 -0
  28. control_plane_api/app/config.py +95 -0
  29. control_plane_api/app/database.py +135 -0
  30. control_plane_api/app/exceptions.py +408 -0
  31. control_plane_api/app/lib/__init__.py +11 -0
  32. control_plane_api/app/lib/job_executor.py +312 -0
  33. control_plane_api/app/lib/kubiya_client.py +235 -0
  34. control_plane_api/app/lib/litellm_pricing.py +166 -0
  35. control_plane_api/app/lib/planning_tools/__init__.py +22 -0
  36. control_plane_api/app/lib/planning_tools/agents.py +155 -0
  37. control_plane_api/app/lib/planning_tools/base.py +189 -0
  38. control_plane_api/app/lib/planning_tools/environments.py +214 -0
  39. control_plane_api/app/lib/planning_tools/resources.py +240 -0
  40. control_plane_api/app/lib/planning_tools/teams.py +198 -0
  41. control_plane_api/app/lib/policy_enforcer_client.py +939 -0
  42. control_plane_api/app/lib/redis_client.py +436 -0
  43. control_plane_api/app/lib/supabase.py +71 -0
  44. control_plane_api/app/lib/temporal_client.py +138 -0
  45. control_plane_api/app/lib/validation/__init__.py +20 -0
  46. control_plane_api/app/lib/validation/runtime_validation.py +287 -0
  47. control_plane_api/app/main.py +128 -0
  48. control_plane_api/app/middleware/__init__.py +8 -0
  49. control_plane_api/app/middleware/auth.py +513 -0
  50. control_plane_api/app/middleware/exception_handler.py +267 -0
  51. control_plane_api/app/middleware/rate_limiting.py +384 -0
  52. control_plane_api/app/middleware/request_id.py +202 -0
  53. control_plane_api/app/models/__init__.py +27 -0
  54. control_plane_api/app/models/agent.py +79 -0
  55. control_plane_api/app/models/analytics.py +206 -0
  56. control_plane_api/app/models/associations.py +81 -0
  57. control_plane_api/app/models/environment.py +63 -0
  58. control_plane_api/app/models/execution.py +93 -0
  59. control_plane_api/app/models/job.py +179 -0
  60. control_plane_api/app/models/llm_model.py +75 -0
  61. control_plane_api/app/models/presence.py +49 -0
  62. control_plane_api/app/models/project.py +47 -0
  63. control_plane_api/app/models/session.py +38 -0
  64. control_plane_api/app/models/team.py +66 -0
  65. control_plane_api/app/models/workflow.py +55 -0
  66. control_plane_api/app/policies/README.md +121 -0
  67. control_plane_api/app/policies/approved_users.rego +62 -0
  68. control_plane_api/app/policies/business_hours.rego +51 -0
  69. control_plane_api/app/policies/rate_limiting.rego +100 -0
  70. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  71. control_plane_api/app/routers/__init__.py +4 -0
  72. control_plane_api/app/routers/agents.py +364 -0
  73. control_plane_api/app/routers/agents_v2.py +1260 -0
  74. control_plane_api/app/routers/analytics.py +1014 -0
  75. control_plane_api/app/routers/context_manager.py +562 -0
  76. control_plane_api/app/routers/environment_context.py +270 -0
  77. control_plane_api/app/routers/environments.py +715 -0
  78. control_plane_api/app/routers/execution_environment.py +517 -0
  79. control_plane_api/app/routers/executions.py +1911 -0
  80. control_plane_api/app/routers/health.py +92 -0
  81. control_plane_api/app/routers/health_v2.py +326 -0
  82. control_plane_api/app/routers/integrations.py +274 -0
  83. control_plane_api/app/routers/jobs.py +1344 -0
  84. control_plane_api/app/routers/models.py +82 -0
  85. control_plane_api/app/routers/models_v2.py +361 -0
  86. control_plane_api/app/routers/policies.py +639 -0
  87. control_plane_api/app/routers/presence.py +234 -0
  88. control_plane_api/app/routers/projects.py +902 -0
  89. control_plane_api/app/routers/runners.py +379 -0
  90. control_plane_api/app/routers/runtimes.py +172 -0
  91. control_plane_api/app/routers/secrets.py +155 -0
  92. control_plane_api/app/routers/skills.py +1001 -0
  93. control_plane_api/app/routers/skills_definitions.py +140 -0
  94. control_plane_api/app/routers/task_planning.py +1256 -0
  95. control_plane_api/app/routers/task_queues.py +654 -0
  96. control_plane_api/app/routers/team_context.py +270 -0
  97. control_plane_api/app/routers/teams.py +1400 -0
  98. control_plane_api/app/routers/worker_queues.py +1545 -0
  99. control_plane_api/app/routers/workers.py +935 -0
  100. control_plane_api/app/routers/workflows.py +204 -0
  101. control_plane_api/app/runtimes/__init__.py +6 -0
  102. control_plane_api/app/runtimes/validation.py +344 -0
  103. control_plane_api/app/schemas/job_schemas.py +295 -0
  104. control_plane_api/app/services/__init__.py +1 -0
  105. control_plane_api/app/services/agno_service.py +619 -0
  106. control_plane_api/app/services/litellm_service.py +190 -0
  107. control_plane_api/app/services/policy_service.py +525 -0
  108. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  109. control_plane_api/app/skills/__init__.py +44 -0
  110. control_plane_api/app/skills/base.py +229 -0
  111. control_plane_api/app/skills/business_intelligence.py +189 -0
  112. control_plane_api/app/skills/data_visualization.py +154 -0
  113. control_plane_api/app/skills/docker.py +104 -0
  114. control_plane_api/app/skills/file_generation.py +94 -0
  115. control_plane_api/app/skills/file_system.py +110 -0
  116. control_plane_api/app/skills/python.py +92 -0
  117. control_plane_api/app/skills/registry.py +65 -0
  118. control_plane_api/app/skills/shell.py +102 -0
  119. control_plane_api/app/skills/workflow_executor.py +469 -0
  120. control_plane_api/app/utils/workflow_executor.py +354 -0
  121. control_plane_api/app/workflows/__init__.py +11 -0
  122. control_plane_api/app/workflows/agent_execution.py +507 -0
  123. control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
  124. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  125. control_plane_api/app/workflows/team_execution.py +399 -0
  126. control_plane_api/scripts/seed_models.py +239 -0
  127. control_plane_api/worker/__init__.py +0 -0
  128. control_plane_api/worker/activities/__init__.py +0 -0
  129. control_plane_api/worker/activities/agent_activities.py +1241 -0
  130. control_plane_api/worker/activities/approval_activities.py +234 -0
  131. control_plane_api/worker/activities/runtime_activities.py +388 -0
  132. control_plane_api/worker/activities/skill_activities.py +267 -0
  133. control_plane_api/worker/activities/team_activities.py +1217 -0
  134. control_plane_api/worker/config/__init__.py +31 -0
  135. control_plane_api/worker/config/worker_config.py +275 -0
  136. control_plane_api/worker/control_plane_client.py +529 -0
  137. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  138. control_plane_api/worker/models/__init__.py +1 -0
  139. control_plane_api/worker/models/inputs.py +89 -0
  140. control_plane_api/worker/runtimes/__init__.py +31 -0
  141. control_plane_api/worker/runtimes/base.py +789 -0
  142. control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
  143. control_plane_api/worker/runtimes/default_runtime.py +617 -0
  144. control_plane_api/worker/runtimes/factory.py +173 -0
  145. control_plane_api/worker/runtimes/validation.py +93 -0
  146. control_plane_api/worker/services/__init__.py +1 -0
  147. control_plane_api/worker/services/agent_executor.py +422 -0
  148. control_plane_api/worker/services/agent_executor_v2.py +383 -0
  149. control_plane_api/worker/services/analytics_collector.py +457 -0
  150. control_plane_api/worker/services/analytics_service.py +464 -0
  151. control_plane_api/worker/services/approval_tools.py +310 -0
  152. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  153. control_plane_api/worker/services/cancellation_manager.py +177 -0
  154. control_plane_api/worker/services/data_visualization.py +827 -0
  155. control_plane_api/worker/services/jira_tools.py +257 -0
  156. control_plane_api/worker/services/runtime_analytics.py +328 -0
  157. control_plane_api/worker/services/session_service.py +194 -0
  158. control_plane_api/worker/services/skill_factory.py +175 -0
  159. control_plane_api/worker/services/team_executor.py +574 -0
  160. control_plane_api/worker/services/team_executor_v2.py +465 -0
  161. control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
  162. control_plane_api/worker/tests/__init__.py +1 -0
  163. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  164. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  165. control_plane_api/worker/tests/integration/__init__.py +0 -0
  166. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  167. control_plane_api/worker/tests/unit/__init__.py +0 -0
  168. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  169. control_plane_api/worker/utils/__init__.py +1 -0
  170. control_plane_api/worker/utils/chunk_batcher.py +305 -0
  171. control_plane_api/worker/utils/retry_utils.py +60 -0
  172. control_plane_api/worker/utils/streaming_utils.py +373 -0
  173. control_plane_api/worker/worker.py +753 -0
  174. control_plane_api/worker/workflows/__init__.py +0 -0
  175. control_plane_api/worker/workflows/agent_execution.py +589 -0
  176. control_plane_api/worker/workflows/team_execution.py +429 -0
  177. kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
  178. kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
  179. kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
  180. kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
  181. kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
  182. kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
  183. kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
  184. {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
  185. {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,234 @@
1
+ from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect, status
2
+ from sqlalchemy.orm import Session
3
+ from typing import List, Dict, Set
4
+ from datetime import datetime, timedelta
5
+ from pydantic import BaseModel
6
+ import json
7
+ import asyncio
8
+
9
+ from control_plane_api.app.database import get_db
10
+ from control_plane_api.app.models.presence import UserPresence
11
+
12
+ router = APIRouter()
13
+
14
+ # WebSocket connection manager for real-time presence updates
15
+ class ConnectionManager:
16
+ def __init__(self):
17
+ # agent_id -> set of websocket connections
18
+ self.active_connections: Dict[str, Set[WebSocket]] = {}
19
+
20
+ async def connect(self, websocket: WebSocket, agent_id: str):
21
+ await websocket.accept()
22
+ if agent_id not in self.active_connections:
23
+ self.active_connections[agent_id] = set()
24
+ self.active_connections[agent_id].add(websocket)
25
+
26
+ def disconnect(self, websocket: WebSocket, agent_id: str):
27
+ if agent_id in self.active_connections:
28
+ self.active_connections[agent_id].discard(websocket)
29
+ if not self.active_connections[agent_id]:
30
+ del self.active_connections[agent_id]
31
+
32
+ async def broadcast_to_agent(self, agent_id: str, message: dict):
33
+ """Broadcast presence update to all users watching this agent"""
34
+ if agent_id in self.active_connections:
35
+ dead_connections = set()
36
+ for connection in self.active_connections[agent_id]:
37
+ try:
38
+ await connection.send_json(message)
39
+ except Exception:
40
+ dead_connections.add(connection)
41
+
42
+ # Clean up dead connections
43
+ for conn in dead_connections:
44
+ self.active_connections[agent_id].discard(conn)
45
+
46
+ manager = ConnectionManager()
47
+
48
+
49
+ # Pydantic schemas
50
+ class PresenceUpdate(BaseModel):
51
+ user_id: str
52
+ user_email: str | None = None
53
+ user_name: str | None = None
54
+ user_avatar: str | None = None
55
+ agent_id: str | None = None
56
+ session_id: str | None = None
57
+ execution_id: str | None = None
58
+ is_typing: bool = False
59
+
60
+ class PresenceResponse(BaseModel):
61
+ id: str
62
+ user_id: str
63
+ user_email: str | None
64
+ user_name: str | None
65
+ user_avatar: str | None
66
+ agent_id: str | None
67
+ session_id: str | None
68
+ execution_id: str | None
69
+ is_active: bool
70
+ is_typing: bool
71
+ last_active_at: datetime
72
+
73
+ class Config:
74
+ from_attributes = True
75
+
76
+
77
+ @router.websocket("/ws/{agent_id}")
78
+ async def websocket_presence(
79
+ websocket: WebSocket,
80
+ agent_id: str,
81
+ user_id: str,
82
+ user_email: str | None = None,
83
+ user_name: str | None = None,
84
+ user_avatar: str | None = None,
85
+ ):
86
+ """WebSocket endpoint for real-time presence updates"""
87
+ await manager.connect(websocket, agent_id)
88
+
89
+ # Get database session
90
+ db = next(get_db())
91
+
92
+ # Create or update presence record
93
+ presence = db.query(UserPresence).filter(
94
+ UserPresence.user_id == user_id,
95
+ UserPresence.agent_id == agent_id
96
+ ).first()
97
+
98
+ if not presence:
99
+ presence = UserPresence(
100
+ user_id=user_id,
101
+ user_email=user_email,
102
+ user_name=user_name,
103
+ user_avatar=user_avatar,
104
+ agent_id=agent_id,
105
+ is_active=True
106
+ )
107
+ db.add(presence)
108
+ else:
109
+ presence.is_active = True
110
+ presence.last_active_at = datetime.utcnow()
111
+
112
+ db.commit()
113
+
114
+ # Broadcast join event to other users
115
+ await manager.broadcast_to_agent(agent_id, {
116
+ "type": "user_joined",
117
+ "user": {
118
+ "user_id": user_id,
119
+ "user_email": user_email,
120
+ "user_name": user_name,
121
+ "user_avatar": user_avatar,
122
+ }
123
+ })
124
+
125
+ try:
126
+ # Keep connection alive and handle messages
127
+ while True:
128
+ data = await websocket.receive_json()
129
+
130
+ # Update presence based on message type
131
+ if data.get("type") == "typing_start":
132
+ presence.is_typing = True
133
+ elif data.get("type") == "typing_stop":
134
+ presence.is_typing = False
135
+ elif data.get("type") == "heartbeat":
136
+ presence.last_active_at = datetime.utcnow()
137
+
138
+ db.commit()
139
+
140
+ # Broadcast to other users
141
+ await manager.broadcast_to_agent(agent_id, {
142
+ "type": data.get("type"),
143
+ "user_id": user_id,
144
+ "user_name": user_name
145
+ })
146
+
147
+ except WebSocketDisconnect:
148
+ # Mark user as inactive
149
+ presence.is_active = False
150
+ presence.is_typing = False
151
+ db.commit()
152
+
153
+ # Broadcast leave event
154
+ await manager.broadcast_to_agent(agent_id, {
155
+ "type": "user_left",
156
+ "user_id": user_id
157
+ })
158
+
159
+ manager.disconnect(websocket, agent_id)
160
+ finally:
161
+ db.close()
162
+
163
+
164
+ @router.post("/heartbeat", status_code=status.HTTP_200_OK)
165
+ def update_presence_heartbeat(
166
+ presence_update: PresenceUpdate,
167
+ db: Session = Depends(get_db)
168
+ ):
169
+ """HTTP endpoint for updating presence (fallback for non-WebSocket)"""
170
+ presence = db.query(UserPresence).filter(
171
+ UserPresence.user_id == presence_update.user_id,
172
+ UserPresence.agent_id == presence_update.agent_id
173
+ ).first()
174
+
175
+ if not presence:
176
+ presence = UserPresence(
177
+ user_id=presence_update.user_id,
178
+ user_email=presence_update.user_email,
179
+ user_name=presence_update.user_name,
180
+ user_avatar=presence_update.user_avatar,
181
+ agent_id=presence_update.agent_id,
182
+ session_id=presence_update.session_id,
183
+ execution_id=presence_update.execution_id,
184
+ is_active=True,
185
+ is_typing=presence_update.is_typing
186
+ )
187
+ db.add(presence)
188
+ else:
189
+ presence.last_active_at = datetime.utcnow()
190
+ presence.is_active = True
191
+ presence.is_typing = presence_update.is_typing
192
+
193
+ db.commit()
194
+
195
+ return {"status": "ok"}
196
+
197
+
198
+ @router.get("/agent/{agent_id}", response_model=List[PresenceResponse])
199
+ def get_agent_presence(
200
+ agent_id: str,
201
+ db: Session = Depends(get_db)
202
+ ):
203
+ """Get all active users for an agent"""
204
+ # Clean up stale presence records (older than 5 minutes)
205
+ stale_cutoff = datetime.utcnow() - timedelta(minutes=5)
206
+ db.query(UserPresence).filter(
207
+ UserPresence.agent_id == agent_id,
208
+ UserPresence.last_active_at < stale_cutoff
209
+ ).update({"is_active": False})
210
+ db.commit()
211
+
212
+ # Get active presence records
213
+ presences = db.query(UserPresence).filter(
214
+ UserPresence.agent_id == agent_id,
215
+ UserPresence.is_active == True
216
+ ).all()
217
+
218
+ return presences
219
+
220
+
221
+ @router.delete("/leave", status_code=status.HTTP_204_NO_CONTENT)
222
+ def leave_presence(
223
+ user_id: str,
224
+ agent_id: str,
225
+ db: Session = Depends(get_db)
226
+ ):
227
+ """Mark user as inactive for an agent"""
228
+ db.query(UserPresence).filter(
229
+ UserPresence.user_id == user_id,
230
+ UserPresence.agent_id == agent_id
231
+ ).update({"is_active": False, "is_typing": False})
232
+ db.commit()
233
+
234
+ return None