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,364 @@
1
+ from fastapi import APIRouter, Depends, HTTPException, status
2
+ from fastapi.responses import StreamingResponse
3
+ from sqlalchemy.orm import Session
4
+ from typing import List, Optional
5
+ from datetime import datetime
6
+
7
+ from control_plane_api.app.database import get_db
8
+ from control_plane_api.app.models.agent import Agent, AgentStatus
9
+ from control_plane_api.app.models.execution import Execution, ExecutionStatus, ExecutionType
10
+ from control_plane_api.app.services.litellm_service import litellm_service
11
+ from pydantic import BaseModel, Field
12
+
13
+ router = APIRouter()
14
+
15
+
16
+ def agent_to_response(agent: Agent) -> dict:
17
+ """Convert Agent model to response dict, mapping model_config to llm_config"""
18
+ return {
19
+ "id": agent.id,
20
+ "name": agent.name,
21
+ "description": agent.description,
22
+ "status": agent.status,
23
+ "capabilities": agent.capabilities,
24
+ "configuration": agent.configuration,
25
+ "model_id": agent.model_id,
26
+ "llm_config": agent.model_config,
27
+ "team_id": agent.team_id,
28
+ "created_at": agent.created_at,
29
+ "updated_at": agent.updated_at,
30
+ "last_active_at": agent.last_active_at,
31
+ "state": agent.state,
32
+ "error_message": agent.error_message,
33
+ }
34
+
35
+
36
+ # Pydantic schemas
37
+ class AgentCreate(BaseModel):
38
+ name: str = Field(..., description="Agent name")
39
+ description: str | None = Field(None, description="Agent description")
40
+ capabilities: list = Field(default_factory=list, description="Agent capabilities")
41
+ configuration: dict = Field(default_factory=dict, description="Agent configuration")
42
+ model_id: str | None = Field(None, description="LiteLLM model identifier")
43
+ llm_config: dict = Field(default_factory=dict, description="Model-specific configuration")
44
+ team_id: str | None = Field(None, description="Team ID to assign this agent to")
45
+
46
+
47
+ class AgentUpdate(BaseModel):
48
+ name: str | None = None
49
+ description: str | None = None
50
+ status: AgentStatus | None = None
51
+ capabilities: list | None = None
52
+ configuration: dict | None = None
53
+ state: dict | None = None
54
+ model_id: str | None = None
55
+ llm_config: dict | None = None
56
+ team_id: str | None = None
57
+
58
+
59
+ class AgentResponse(BaseModel):
60
+ id: str
61
+ name: str
62
+ description: str | None
63
+ status: AgentStatus
64
+ capabilities: list
65
+ configuration: dict
66
+ model_id: str | None
67
+ llm_config: dict
68
+ team_id: str | None
69
+ created_at: datetime
70
+ updated_at: datetime
71
+ last_active_at: datetime | None
72
+ state: dict
73
+ error_message: str | None
74
+
75
+ class Config:
76
+ from_attributes = True
77
+
78
+
79
+ class AgentExecutionRequest(BaseModel):
80
+ prompt: str = Field(..., description="The prompt to execute")
81
+ system_prompt: str | None = Field(None, description="Optional system prompt")
82
+ stream: bool = Field(False, description="Whether to stream the response")
83
+ config: dict | None = Field(None, description="Optional configuration including user metadata")
84
+
85
+
86
+ class AgentExecutionResponse(BaseModel):
87
+ execution_id: str
88
+ success: bool
89
+ response: str | None = None
90
+ error: str | None = None
91
+ model: str
92
+ usage: dict | None = None
93
+ finish_reason: str | None = None
94
+
95
+
96
+ @router.post("", response_model=AgentResponse, status_code=status.HTTP_201_CREATED)
97
+ def create_agent(agent_data: AgentCreate, db: Session = Depends(get_db)):
98
+ """Create a new agent"""
99
+ agent = Agent(
100
+ name=agent_data.name,
101
+ description=agent_data.description,
102
+ capabilities=agent_data.capabilities,
103
+ configuration=agent_data.configuration,
104
+ model_id=agent_data.model_id,
105
+ model_config=agent_data.llm_config,
106
+ team_id=agent_data.team_id,
107
+ )
108
+ db.add(agent)
109
+ db.commit()
110
+ db.refresh(agent)
111
+ return agent_to_response(agent)
112
+
113
+
114
+ @router.get("", response_model=List[AgentResponse])
115
+ def list_agents(
116
+ skip: int = 0,
117
+ limit: int = 100,
118
+ status_filter: AgentStatus | None = None,
119
+ db: Session = Depends(get_db),
120
+ ):
121
+ """List all agents"""
122
+ query = db.query(Agent)
123
+ if status_filter:
124
+ query = query.filter(Agent.status == status_filter)
125
+ agents = query.offset(skip).limit(limit).all()
126
+ return [agent_to_response(agent) for agent in agents]
127
+
128
+
129
+ @router.get("/{agent_id}", response_model=AgentResponse)
130
+ def get_agent(agent_id: str, db: Session = Depends(get_db)):
131
+ """Get a specific agent by ID"""
132
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
133
+ if not agent:
134
+ raise HTTPException(status_code=404, detail="Agent not found")
135
+ return agent_to_response(agent)
136
+
137
+
138
+ @router.patch("/{agent_id}", response_model=AgentResponse)
139
+ def update_agent(agent_id: str, agent_data: AgentUpdate, db: Session = Depends(get_db)):
140
+ """Update an agent"""
141
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
142
+ if not agent:
143
+ raise HTTPException(status_code=404, detail="Agent not found")
144
+
145
+ update_data = agent_data.model_dump(exclude_unset=True)
146
+ for field, value in update_data.items():
147
+ setattr(agent, field, value)
148
+
149
+ agent.updated_at = datetime.utcnow()
150
+ db.commit()
151
+ db.refresh(agent)
152
+ return agent_to_response(agent)
153
+
154
+
155
+ @router.delete("/{agent_id}", status_code=status.HTTP_204_NO_CONTENT)
156
+ def delete_agent(agent_id: str, db: Session = Depends(get_db)):
157
+ """Delete an agent"""
158
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
159
+ if not agent:
160
+ raise HTTPException(status_code=404, detail="Agent not found")
161
+
162
+ db.delete(agent)
163
+ db.commit()
164
+ return None
165
+
166
+
167
+ @router.post("/{agent_id}/start", response_model=AgentResponse)
168
+ def start_agent(agent_id: str, db: Session = Depends(get_db)):
169
+ """Start an agent"""
170
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
171
+ if not agent:
172
+ raise HTTPException(status_code=404, detail="Agent not found")
173
+
174
+ if agent.status == AgentStatus.RUNNING:
175
+ raise HTTPException(status_code=400, detail="Agent is already running")
176
+
177
+ agent.status = AgentStatus.RUNNING
178
+ agent.last_active_at = datetime.utcnow()
179
+ agent.error_message = None
180
+ db.commit()
181
+ db.refresh(agent)
182
+ return agent_to_response(agent)
183
+
184
+
185
+ @router.post("/{agent_id}/stop", response_model=AgentResponse)
186
+ def stop_agent(agent_id: str, db: Session = Depends(get_db)):
187
+ """Stop an agent"""
188
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
189
+ if not agent:
190
+ raise HTTPException(status_code=404, detail="Agent not found")
191
+
192
+ agent.status = AgentStatus.STOPPED
193
+ agent.last_active_at = datetime.utcnow()
194
+ db.commit()
195
+ db.refresh(agent)
196
+ return agent_to_response(agent)
197
+
198
+
199
+ @router.post("/{agent_id}/pause", response_model=AgentResponse)
200
+ def pause_agent(agent_id: str, db: Session = Depends(get_db)):
201
+ """Pause an agent"""
202
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
203
+ if not agent:
204
+ raise HTTPException(status_code=404, detail="Agent not found")
205
+
206
+ if agent.status != AgentStatus.RUNNING:
207
+ raise HTTPException(status_code=400, detail="Can only pause running agents")
208
+
209
+ agent.status = AgentStatus.PAUSED
210
+ agent.last_active_at = datetime.utcnow()
211
+ db.commit()
212
+ db.refresh(agent)
213
+ return agent_to_response(agent)
214
+
215
+
216
+ @router.post("/{agent_id}/resume", response_model=AgentResponse)
217
+ def resume_agent(agent_id: str, db: Session = Depends(get_db)):
218
+ """Resume a paused agent"""
219
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
220
+ if not agent:
221
+ raise HTTPException(status_code=404, detail="Agent not found")
222
+
223
+ if agent.status != AgentStatus.PAUSED:
224
+ raise HTTPException(status_code=400, detail="Can only resume paused agents")
225
+
226
+ agent.status = AgentStatus.RUNNING
227
+ agent.last_active_at = datetime.utcnow()
228
+ db.commit()
229
+ db.refresh(agent)
230
+ return agent_to_response(agent)
231
+
232
+
233
+ @router.post("/{agent_id}/execute", response_model=AgentExecutionResponse)
234
+ def execute_agent(
235
+ agent_id: str,
236
+ execution_request: AgentExecutionRequest,
237
+ db: Session = Depends(get_db),
238
+ ):
239
+ """Execute an agent with a prompt using LiteLLM"""
240
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
241
+ if not agent:
242
+ raise HTTPException(status_code=404, detail="Agent not found")
243
+
244
+ # Extract user metadata from config if provided
245
+ user_metadata = {}
246
+ if execution_request.config and "user_metadata" in execution_request.config:
247
+ user_metadata = execution_request.config["user_metadata"]
248
+
249
+ # Create execution record
250
+ execution = Execution(
251
+ execution_type=ExecutionType.AGENT,
252
+ entity_id=agent.id,
253
+ entity_name=agent.name,
254
+ prompt=execution_request.prompt,
255
+ system_prompt=execution_request.system_prompt,
256
+ status=ExecutionStatus.RUNNING,
257
+ execution_metadata={
258
+ "user_id": user_metadata.get("user_id"),
259
+ "user_name": user_metadata.get("user_name"),
260
+ "user_email": user_metadata.get("user_email"),
261
+ "user_avatar": user_metadata.get("user_avatar"),
262
+ },
263
+ )
264
+ db.add(execution)
265
+ db.flush() # Get execution ID without committing
266
+ execution.started_at = datetime.utcnow()
267
+
268
+ # Extract model configuration
269
+ model = agent.model_id
270
+ model_config = agent.model_config or {}
271
+
272
+ # Get system prompt from configuration if not provided
273
+ system_prompt = execution_request.system_prompt
274
+ if not system_prompt and "system_prompt" in agent.configuration:
275
+ system_prompt = agent.configuration["system_prompt"]
276
+
277
+ try:
278
+ # Execute using LiteLLM service
279
+ result = litellm_service.execute_agent(
280
+ prompt=execution_request.prompt,
281
+ model=model,
282
+ system_prompt=system_prompt,
283
+ **model_config,
284
+ )
285
+
286
+ # Update execution record with results
287
+ execution.response = result.get("response")
288
+ execution.usage = result.get("usage", {})
289
+ execution.execution_metadata = {
290
+ "model": result.get("model"),
291
+ "finish_reason": result.get("finish_reason"),
292
+ }
293
+
294
+ if result.get("success"):
295
+ execution.status = ExecutionStatus.COMPLETED
296
+ agent.status = AgentStatus.COMPLETED
297
+ else:
298
+ execution.status = ExecutionStatus.FAILED
299
+ execution.error_message = result.get("error")
300
+ agent.status = AgentStatus.FAILED
301
+ agent.error_message = result.get("error")
302
+
303
+ execution.completed_at = datetime.utcnow()
304
+
305
+ except Exception as e:
306
+ # Handle execution errors
307
+ execution.status = ExecutionStatus.FAILED
308
+ execution.error_message = str(e)
309
+ execution.completed_at = datetime.utcnow()
310
+ agent.status = AgentStatus.FAILED
311
+ agent.error_message = str(e)
312
+ result = {
313
+ "success": False,
314
+ "error": str(e),
315
+ "model": model,
316
+ "usage": None,
317
+ "finish_reason": None,
318
+ }
319
+
320
+ # Update agent state
321
+ agent.last_active_at = datetime.utcnow()
322
+ db.commit()
323
+
324
+ return AgentExecutionResponse(
325
+ execution_id=execution.id,
326
+ **result
327
+ )
328
+
329
+
330
+ @router.post("/{agent_id}/execute/stream")
331
+ def execute_agent_stream(
332
+ agent_id: str,
333
+ execution_request: AgentExecutionRequest,
334
+ db: Session = Depends(get_db),
335
+ ):
336
+ """Execute an agent with a prompt using LiteLLM (streaming response)"""
337
+ agent = db.query(Agent).filter(Agent.id == agent_id).first()
338
+ if not agent:
339
+ raise HTTPException(status_code=404, detail="Agent not found")
340
+
341
+ # Extract model configuration
342
+ model = agent.model_id
343
+ model_config = agent.model_config or {}
344
+
345
+ # Get system prompt from configuration if not provided
346
+ system_prompt = execution_request.system_prompt
347
+ if not system_prompt and "system_prompt" in agent.configuration:
348
+ system_prompt = agent.configuration["system_prompt"]
349
+
350
+ # Update agent state
351
+ agent.last_active_at = datetime.utcnow()
352
+ agent.status = AgentStatus.RUNNING
353
+ db.commit()
354
+
355
+ # Execute using LiteLLM service (streaming)
356
+ return StreamingResponse(
357
+ litellm_service.execute_agent_stream(
358
+ prompt=execution_request.prompt,
359
+ model=model,
360
+ system_prompt=system_prompt,
361
+ **model_config,
362
+ ),
363
+ media_type="text/event-stream",
364
+ )