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.
- control_plane_api/README.md +266 -0
- control_plane_api/__init__.py +0 -0
- control_plane_api/__version__.py +1 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +98 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
- control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
- control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
- control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
- control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
- control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
- control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
- control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
- control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
- control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
- control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +379 -0
- control_plane_api/app/activities/team_activities.py +410 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +354 -0
- control_plane_api/app/config/model_pricing.py +318 -0
- control_plane_api/app/config.py +95 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/job_executor.py +312 -0
- control_plane_api/app/lib/kubiya_client.py +235 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/planning_tools/__init__.py +22 -0
- control_plane_api/app/lib/planning_tools/agents.py +155 -0
- control_plane_api/app/lib/planning_tools/base.py +189 -0
- control_plane_api/app/lib/planning_tools/environments.py +214 -0
- control_plane_api/app/lib/planning_tools/resources.py +240 -0
- control_plane_api/app/lib/planning_tools/teams.py +198 -0
- control_plane_api/app/lib/policy_enforcer_client.py +939 -0
- control_plane_api/app/lib/redis_client.py +436 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/temporal_client.py +138 -0
- control_plane_api/app/lib/validation/__init__.py +20 -0
- control_plane_api/app/lib/validation/runtime_validation.py +287 -0
- control_plane_api/app/main.py +128 -0
- control_plane_api/app/middleware/__init__.py +8 -0
- control_plane_api/app/middleware/auth.py +513 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +27 -0
- control_plane_api/app/models/agent.py +79 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +81 -0
- control_plane_api/app/models/environment.py +63 -0
- control_plane_api/app/models/execution.py +93 -0
- control_plane_api/app/models/job.py +179 -0
- control_plane_api/app/models/llm_model.py +75 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +47 -0
- control_plane_api/app/models/session.py +38 -0
- control_plane_api/app/models/team.py +66 -0
- control_plane_api/app/models/workflow.py +55 -0
- control_plane_api/app/policies/README.md +121 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +364 -0
- control_plane_api/app/routers/agents_v2.py +1260 -0
- control_plane_api/app/routers/analytics.py +1014 -0
- control_plane_api/app/routers/context_manager.py +562 -0
- control_plane_api/app/routers/environment_context.py +270 -0
- control_plane_api/app/routers/environments.py +715 -0
- control_plane_api/app/routers/execution_environment.py +517 -0
- control_plane_api/app/routers/executions.py +1911 -0
- control_plane_api/app/routers/health.py +92 -0
- control_plane_api/app/routers/health_v2.py +326 -0
- control_plane_api/app/routers/integrations.py +274 -0
- control_plane_api/app/routers/jobs.py +1344 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +361 -0
- control_plane_api/app/routers/policies.py +639 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +902 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +155 -0
- control_plane_api/app/routers/skills.py +1001 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/task_planning.py +1256 -0
- control_plane_api/app/routers/task_queues.py +654 -0
- control_plane_api/app/routers/team_context.py +270 -0
- control_plane_api/app/routers/teams.py +1400 -0
- control_plane_api/app/routers/worker_queues.py +1545 -0
- control_plane_api/app/routers/workers.py +935 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/job_schemas.py +295 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_service.py +619 -0
- control_plane_api/app/services/litellm_service.py +190 -0
- control_plane_api/app/services/policy_service.py +525 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/skills/__init__.py +44 -0
- control_plane_api/app/skills/base.py +229 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/data_visualization.py +154 -0
- control_plane_api/app/skills/docker.py +104 -0
- control_plane_api/app/skills/file_generation.py +94 -0
- control_plane_api/app/skills/file_system.py +110 -0
- control_plane_api/app/skills/python.py +92 -0
- control_plane_api/app/skills/registry.py +65 -0
- control_plane_api/app/skills/shell.py +102 -0
- control_plane_api/app/skills/workflow_executor.py +469 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +507 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/team_execution.py +399 -0
- control_plane_api/scripts/seed_models.py +239 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1241 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/runtime_activities.py +388 -0
- control_plane_api/worker/activities/skill_activities.py +267 -0
- control_plane_api/worker/activities/team_activities.py +1217 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +275 -0
- control_plane_api/worker/control_plane_client.py +529 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +31 -0
- control_plane_api/worker/runtimes/base.py +789 -0
- control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
- control_plane_api/worker/runtimes/default_runtime.py +617 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_executor.py +422 -0
- control_plane_api/worker/services/agent_executor_v2.py +383 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/data_visualization.py +827 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +194 -0
- control_plane_api/worker/services/skill_factory.py +175 -0
- control_plane_api/worker/services/team_executor.py +574 -0
- control_plane_api/worker/services/team_executor_v2.py +465 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +305 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +373 -0
- control_plane_api/worker/worker.py +753 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +589 -0
- control_plane_api/worker/workflows/team_execution.py +429 -0
- kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
- kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
- kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
- kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
- kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
- kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
- kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
- {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
- {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
|