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,204 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
2
|
+
from sqlalchemy.orm import Session
|
|
3
|
+
from typing import List
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from control_plane_api.app.database import get_db
|
|
7
|
+
from control_plane_api.app.models.workflow import Workflow, WorkflowStatus
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
router = APIRouter()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Pydantic schemas
|
|
14
|
+
class WorkflowCreate(BaseModel):
|
|
15
|
+
name: str = Field(..., description="Workflow name")
|
|
16
|
+
description: str | None = Field(None, description="Workflow description")
|
|
17
|
+
steps: list = Field(default_factory=list, description="Workflow steps")
|
|
18
|
+
configuration: dict = Field(default_factory=dict, description="Workflow configuration")
|
|
19
|
+
team_id: str | None = Field(None, description="Team ID")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkflowUpdate(BaseModel):
|
|
23
|
+
name: str | None = None
|
|
24
|
+
description: str | None = None
|
|
25
|
+
status: WorkflowStatus | None = None
|
|
26
|
+
steps: list | None = None
|
|
27
|
+
current_step: str | None = None
|
|
28
|
+
configuration: dict | None = None
|
|
29
|
+
state: dict | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WorkflowResponse(BaseModel):
|
|
33
|
+
id: str
|
|
34
|
+
name: str
|
|
35
|
+
description: str | None
|
|
36
|
+
status: WorkflowStatus
|
|
37
|
+
steps: list
|
|
38
|
+
current_step: str | None
|
|
39
|
+
configuration: dict
|
|
40
|
+
team_id: str | None
|
|
41
|
+
created_at: datetime
|
|
42
|
+
updated_at: datetime
|
|
43
|
+
started_at: datetime | None
|
|
44
|
+
completed_at: datetime | None
|
|
45
|
+
state: dict
|
|
46
|
+
error_message: str | None
|
|
47
|
+
|
|
48
|
+
class Config:
|
|
49
|
+
from_attributes = True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.post("", response_model=WorkflowResponse, status_code=status.HTTP_201_CREATED)
|
|
53
|
+
def create_workflow(workflow_data: WorkflowCreate, db: Session = Depends(get_db)):
|
|
54
|
+
"""Create a new workflow"""
|
|
55
|
+
workflow = Workflow(
|
|
56
|
+
name=workflow_data.name,
|
|
57
|
+
description=workflow_data.description,
|
|
58
|
+
steps=workflow_data.steps,
|
|
59
|
+
configuration=workflow_data.configuration,
|
|
60
|
+
team_id=workflow_data.team_id,
|
|
61
|
+
)
|
|
62
|
+
db.add(workflow)
|
|
63
|
+
db.commit()
|
|
64
|
+
db.refresh(workflow)
|
|
65
|
+
return workflow
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.get("", response_model=List[WorkflowResponse])
|
|
69
|
+
def list_workflows(
|
|
70
|
+
skip: int = 0,
|
|
71
|
+
limit: int = 100,
|
|
72
|
+
status_filter: WorkflowStatus | None = None,
|
|
73
|
+
team_id: str | None = None,
|
|
74
|
+
db: Session = Depends(get_db),
|
|
75
|
+
):
|
|
76
|
+
"""List all workflows"""
|
|
77
|
+
query = db.query(Workflow)
|
|
78
|
+
if status_filter:
|
|
79
|
+
query = query.filter(Workflow.status == status_filter)
|
|
80
|
+
if team_id:
|
|
81
|
+
query = query.filter(Workflow.team_id == team_id)
|
|
82
|
+
workflows = query.offset(skip).limit(limit).all()
|
|
83
|
+
return workflows
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@router.get("/{workflow_id}", response_model=WorkflowResponse)
|
|
87
|
+
def get_workflow(workflow_id: str, db: Session = Depends(get_db)):
|
|
88
|
+
"""Get a specific workflow by ID"""
|
|
89
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
90
|
+
if not workflow:
|
|
91
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
92
|
+
return workflow
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@router.patch("/{workflow_id}", response_model=WorkflowResponse)
|
|
96
|
+
def update_workflow(workflow_id: str, workflow_data: WorkflowUpdate, db: Session = Depends(get_db)):
|
|
97
|
+
"""Update a workflow"""
|
|
98
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
99
|
+
if not workflow:
|
|
100
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
101
|
+
|
|
102
|
+
update_data = workflow_data.model_dump(exclude_unset=True)
|
|
103
|
+
for field, value in update_data.items():
|
|
104
|
+
setattr(workflow, field, value)
|
|
105
|
+
|
|
106
|
+
workflow.updated_at = datetime.utcnow()
|
|
107
|
+
db.commit()
|
|
108
|
+
db.refresh(workflow)
|
|
109
|
+
return workflow
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@router.delete("/{workflow_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
113
|
+
def delete_workflow(workflow_id: str, db: Session = Depends(get_db)):
|
|
114
|
+
"""Delete a workflow"""
|
|
115
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
116
|
+
if not workflow:
|
|
117
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
118
|
+
|
|
119
|
+
db.delete(workflow)
|
|
120
|
+
db.commit()
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@router.post("/{workflow_id}/start", response_model=WorkflowResponse)
|
|
125
|
+
def start_workflow(workflow_id: str, db: Session = Depends(get_db)):
|
|
126
|
+
"""Start a workflow"""
|
|
127
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
128
|
+
if not workflow:
|
|
129
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
130
|
+
|
|
131
|
+
if workflow.status == WorkflowStatus.RUNNING:
|
|
132
|
+
raise HTTPException(status_code=400, detail="Workflow is already running")
|
|
133
|
+
|
|
134
|
+
workflow.status = WorkflowStatus.RUNNING
|
|
135
|
+
workflow.started_at = datetime.utcnow()
|
|
136
|
+
workflow.error_message = None
|
|
137
|
+
if workflow.steps and len(workflow.steps) > 0:
|
|
138
|
+
workflow.current_step = workflow.steps[0].get("id", workflow.steps[0].get("name"))
|
|
139
|
+
db.commit()
|
|
140
|
+
db.refresh(workflow)
|
|
141
|
+
return workflow
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@router.post("/{workflow_id}/pause", response_model=WorkflowResponse)
|
|
145
|
+
def pause_workflow(workflow_id: str, db: Session = Depends(get_db)):
|
|
146
|
+
"""Pause a workflow"""
|
|
147
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
148
|
+
if not workflow:
|
|
149
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
150
|
+
|
|
151
|
+
if workflow.status != WorkflowStatus.RUNNING:
|
|
152
|
+
raise HTTPException(status_code=400, detail="Can only pause running workflows")
|
|
153
|
+
|
|
154
|
+
workflow.status = WorkflowStatus.PAUSED
|
|
155
|
+
db.commit()
|
|
156
|
+
db.refresh(workflow)
|
|
157
|
+
return workflow
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@router.post("/{workflow_id}/resume", response_model=WorkflowResponse)
|
|
161
|
+
def resume_workflow(workflow_id: str, db: Session = Depends(get_db)):
|
|
162
|
+
"""Resume a paused workflow"""
|
|
163
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
164
|
+
if not workflow:
|
|
165
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
166
|
+
|
|
167
|
+
if workflow.status != WorkflowStatus.PAUSED:
|
|
168
|
+
raise HTTPException(status_code=400, detail="Can only resume paused workflows")
|
|
169
|
+
|
|
170
|
+
workflow.status = WorkflowStatus.RUNNING
|
|
171
|
+
db.commit()
|
|
172
|
+
db.refresh(workflow)
|
|
173
|
+
return workflow
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@router.post("/{workflow_id}/cancel", response_model=WorkflowResponse)
|
|
177
|
+
def cancel_workflow(workflow_id: str, db: Session = Depends(get_db)):
|
|
178
|
+
"""Cancel a workflow"""
|
|
179
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
180
|
+
if not workflow:
|
|
181
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
182
|
+
|
|
183
|
+
if workflow.status in [WorkflowStatus.COMPLETED, WorkflowStatus.CANCELLED]:
|
|
184
|
+
raise HTTPException(status_code=400, detail="Workflow is already completed or cancelled")
|
|
185
|
+
|
|
186
|
+
workflow.status = WorkflowStatus.CANCELLED
|
|
187
|
+
workflow.completed_at = datetime.utcnow()
|
|
188
|
+
db.commit()
|
|
189
|
+
db.refresh(workflow)
|
|
190
|
+
return workflow
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@router.post("/{workflow_id}/complete", response_model=WorkflowResponse)
|
|
194
|
+
def complete_workflow(workflow_id: str, db: Session = Depends(get_db)):
|
|
195
|
+
"""Mark a workflow as completed"""
|
|
196
|
+
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
|
|
197
|
+
if not workflow:
|
|
198
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
199
|
+
|
|
200
|
+
workflow.status = WorkflowStatus.COMPLETED
|
|
201
|
+
workflow.completed_at = datetime.utcnow()
|
|
202
|
+
db.commit()
|
|
203
|
+
db.refresh(workflow)
|
|
204
|
+
return workflow
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Runtime validation system with model compatibility and requirements checking.
|
|
3
|
+
|
|
4
|
+
This module provides:
|
|
5
|
+
- Model compatibility validation per runtime
|
|
6
|
+
- Runtime requirements specification
|
|
7
|
+
- Agent/Team configuration validation
|
|
8
|
+
- End-to-end validation before execution
|
|
9
|
+
|
|
10
|
+
This is shared between the API and worker for consistent validation.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import List, Optional, Dict, Any, Set
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from enum import Enum
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RuntimeType(str, Enum):
|
|
20
|
+
"""Agent runtime type enumeration"""
|
|
21
|
+
DEFAULT = "default" # Agno-based runtime
|
|
22
|
+
CLAUDE_CODE = "claude_code" # Claude Code SDK runtime
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ValidationError(Exception):
|
|
26
|
+
"""Raised when validation fails."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, message: str, field: Optional[str] = None, details: Optional[Dict[str, Any]] = None):
|
|
29
|
+
self.message = message
|
|
30
|
+
self.field = field
|
|
31
|
+
self.details = details or {}
|
|
32
|
+
super().__init__(message)
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
35
|
+
"""Convert to dict for API responses."""
|
|
36
|
+
result = {"error": self.message}
|
|
37
|
+
if self.field:
|
|
38
|
+
result["field"] = self.field
|
|
39
|
+
if self.details:
|
|
40
|
+
result["details"] = self.details
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ModelRequirement:
|
|
46
|
+
"""Model requirement specification for a runtime."""
|
|
47
|
+
|
|
48
|
+
# Patterns that model IDs must match (any match is valid)
|
|
49
|
+
model_id_patterns: List[str]
|
|
50
|
+
|
|
51
|
+
# Model providers that are supported (e.g., "anthropic", "openai")
|
|
52
|
+
supported_providers: Set[str]
|
|
53
|
+
|
|
54
|
+
# Specific model families supported (e.g., "claude", "gpt")
|
|
55
|
+
supported_families: Set[str]
|
|
56
|
+
|
|
57
|
+
# Minimum model version (if applicable)
|
|
58
|
+
min_version: Optional[str] = None
|
|
59
|
+
|
|
60
|
+
# Human-readable description
|
|
61
|
+
description: str = ""
|
|
62
|
+
|
|
63
|
+
# Examples of valid model IDs
|
|
64
|
+
examples: List[str] = None
|
|
65
|
+
|
|
66
|
+
def __post_init__(self):
|
|
67
|
+
if self.examples is None:
|
|
68
|
+
self.examples = []
|
|
69
|
+
|
|
70
|
+
def validate(self, model_id: Optional[str]) -> tuple[bool, Optional[str]]:
|
|
71
|
+
"""
|
|
72
|
+
Validate a model ID against this requirement.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
model_id: Model ID to validate
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Tuple of (is_valid, error_message)
|
|
79
|
+
"""
|
|
80
|
+
if not model_id:
|
|
81
|
+
return False, "Model ID is required for this runtime"
|
|
82
|
+
|
|
83
|
+
# Check pattern matching
|
|
84
|
+
for pattern in self.model_id_patterns:
|
|
85
|
+
if re.search(pattern, model_id, re.IGNORECASE):
|
|
86
|
+
return True, None
|
|
87
|
+
|
|
88
|
+
# Check provider/family
|
|
89
|
+
model_lower = model_id.lower()
|
|
90
|
+
|
|
91
|
+
# Check if any supported family is in the model ID
|
|
92
|
+
for family in self.supported_families:
|
|
93
|
+
if family.lower() in model_lower:
|
|
94
|
+
return True, None
|
|
95
|
+
|
|
96
|
+
# Provide helpful error message
|
|
97
|
+
examples_str = ", ".join(self.examples) if self.examples else "N/A"
|
|
98
|
+
return False, (
|
|
99
|
+
f"Model '{model_id}' is not compatible with this runtime. "
|
|
100
|
+
f"Expected models matching: {', '.join(self.model_id_patterns)}. "
|
|
101
|
+
f"Supported families: {', '.join(self.supported_families)}. "
|
|
102
|
+
f"Examples: {examples_str}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class RuntimeRequirements:
|
|
108
|
+
"""Requirements specification for a runtime."""
|
|
109
|
+
|
|
110
|
+
runtime_type: RuntimeType
|
|
111
|
+
|
|
112
|
+
# Model requirements
|
|
113
|
+
model_requirement: ModelRequirement
|
|
114
|
+
|
|
115
|
+
# Required fields in agent/team config
|
|
116
|
+
required_config_fields: List[str] = None
|
|
117
|
+
|
|
118
|
+
# Optional but recommended fields
|
|
119
|
+
recommended_config_fields: List[str] = None
|
|
120
|
+
|
|
121
|
+
# Maximum conversation history length
|
|
122
|
+
max_history_length: Optional[int] = None
|
|
123
|
+
|
|
124
|
+
# Whether system prompt is required
|
|
125
|
+
requires_system_prompt: bool = False
|
|
126
|
+
|
|
127
|
+
# Whether tools/skills are required
|
|
128
|
+
requires_tools: bool = False
|
|
129
|
+
|
|
130
|
+
def __post_init__(self):
|
|
131
|
+
if self.required_config_fields is None:
|
|
132
|
+
self.required_config_fields = []
|
|
133
|
+
if self.recommended_config_fields is None:
|
|
134
|
+
self.recommended_config_fields = []
|
|
135
|
+
|
|
136
|
+
def validate_model(self, model_id: Optional[str]) -> tuple[bool, Optional[str]]:
|
|
137
|
+
"""Validate model compatibility."""
|
|
138
|
+
return self.model_requirement.validate(model_id)
|
|
139
|
+
|
|
140
|
+
def validate_config(self, config: Optional[Dict[str, Any]]) -> List[str]:
|
|
141
|
+
"""
|
|
142
|
+
Validate agent/team configuration.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List of validation errors (empty if valid)
|
|
146
|
+
"""
|
|
147
|
+
errors = []
|
|
148
|
+
|
|
149
|
+
if not config:
|
|
150
|
+
if self.required_config_fields:
|
|
151
|
+
errors.append(
|
|
152
|
+
f"Configuration is required for this runtime. "
|
|
153
|
+
f"Required fields: {', '.join(self.required_config_fields)}"
|
|
154
|
+
)
|
|
155
|
+
return errors
|
|
156
|
+
|
|
157
|
+
# Check required fields
|
|
158
|
+
for field in self.required_config_fields:
|
|
159
|
+
if field not in config or config[field] is None:
|
|
160
|
+
errors.append(f"Required field '{field}' is missing in configuration")
|
|
161
|
+
|
|
162
|
+
return errors
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class RuntimeRequirementsRegistry:
|
|
166
|
+
"""Registry of runtime requirements for validation."""
|
|
167
|
+
|
|
168
|
+
_requirements: Dict[RuntimeType, RuntimeRequirements] = {}
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def register(cls, requirements: RuntimeRequirements):
|
|
172
|
+
"""Register requirements for a runtime type."""
|
|
173
|
+
cls._requirements[requirements.runtime_type] = requirements
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def get(cls, runtime_type: RuntimeType) -> Optional[RuntimeRequirements]:
|
|
177
|
+
"""Get requirements for a runtime type."""
|
|
178
|
+
return cls._requirements.get(runtime_type)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ==================== Default Runtime Requirements ====================
|
|
182
|
+
|
|
183
|
+
# Agno/Default Runtime - Flexible, supports most models
|
|
184
|
+
DEFAULT_RUNTIME_REQUIREMENTS = RuntimeRequirements(
|
|
185
|
+
runtime_type=RuntimeType.DEFAULT,
|
|
186
|
+
model_requirement=ModelRequirement(
|
|
187
|
+
model_id_patterns=[
|
|
188
|
+
r".*", # Accept all models
|
|
189
|
+
],
|
|
190
|
+
supported_providers={"openai", "anthropic", "azure", "google", "mistral", "cohere"},
|
|
191
|
+
supported_families={"gpt", "claude", "gemini", "mistral", "command"},
|
|
192
|
+
description="Default runtime supports most LiteLLM-compatible models",
|
|
193
|
+
examples=[
|
|
194
|
+
"gpt-4",
|
|
195
|
+
"gpt-4-turbo",
|
|
196
|
+
"gpt-3.5-turbo",
|
|
197
|
+
"claude-3-opus",
|
|
198
|
+
"claude-3-sonnet",
|
|
199
|
+
"gemini-pro",
|
|
200
|
+
"mistral-large",
|
|
201
|
+
],
|
|
202
|
+
),
|
|
203
|
+
max_history_length=100, # Reasonable default
|
|
204
|
+
requires_system_prompt=False,
|
|
205
|
+
requires_tools=False,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Claude Code Runtime - Requires Claude models
|
|
209
|
+
CLAUDE_CODE_RUNTIME_REQUIREMENTS = RuntimeRequirements(
|
|
210
|
+
runtime_type=RuntimeType.CLAUDE_CODE,
|
|
211
|
+
model_requirement=ModelRequirement(
|
|
212
|
+
model_id_patterns=[
|
|
213
|
+
r"claude", # Must contain "claude"
|
|
214
|
+
r"kubiya/claude", # LiteLLM proxy format
|
|
215
|
+
r"anthropic\.claude", # Alternative format
|
|
216
|
+
],
|
|
217
|
+
supported_providers={"anthropic"},
|
|
218
|
+
supported_families={"claude"},
|
|
219
|
+
description=(
|
|
220
|
+
"Claude Code runtime requires Anthropic Claude models. "
|
|
221
|
+
"This runtime leverages Claude's advanced capabilities including "
|
|
222
|
+
"extended context, tool use, and code understanding."
|
|
223
|
+
),
|
|
224
|
+
examples=[
|
|
225
|
+
"claude-3-opus-20240229",
|
|
226
|
+
"claude-3-sonnet-20240229",
|
|
227
|
+
"claude-3-5-sonnet-20241022",
|
|
228
|
+
"claude-3-haiku-20240307",
|
|
229
|
+
"claude-sonnet-4",
|
|
230
|
+
"claude-opus-4",
|
|
231
|
+
"kubiya/claude-sonnet-4",
|
|
232
|
+
"kubiya/claude-opus-4",
|
|
233
|
+
],
|
|
234
|
+
),
|
|
235
|
+
max_history_length=200, # Claude handles longer contexts well
|
|
236
|
+
requires_system_prompt=False, # Optional but recommended
|
|
237
|
+
requires_tools=False, # Claude Code adds tools automatically
|
|
238
|
+
recommended_config_fields=["timeout", "max_tokens"],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Register default requirements
|
|
243
|
+
RuntimeRequirementsRegistry.register(DEFAULT_RUNTIME_REQUIREMENTS)
|
|
244
|
+
RuntimeRequirementsRegistry.register(CLAUDE_CODE_RUNTIME_REQUIREMENTS)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ==================== Validation Helpers ====================
|
|
248
|
+
|
|
249
|
+
def validate_agent_for_runtime(
|
|
250
|
+
runtime_type: str,
|
|
251
|
+
model_id: Optional[str],
|
|
252
|
+
agent_config: Optional[Dict[str, Any]] = None,
|
|
253
|
+
system_prompt: Optional[str] = None,
|
|
254
|
+
) -> tuple[bool, List[str]]:
|
|
255
|
+
"""
|
|
256
|
+
Validate agent configuration for a runtime (for API validation).
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
runtime_type: Runtime type string
|
|
260
|
+
model_id: Model ID to validate
|
|
261
|
+
agent_config: Agent configuration dict
|
|
262
|
+
system_prompt: System prompt
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Tuple of (is_valid, error_messages)
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
rt = RuntimeType(runtime_type)
|
|
269
|
+
except ValueError:
|
|
270
|
+
return False, [f"Invalid runtime type: {runtime_type}"]
|
|
271
|
+
|
|
272
|
+
requirements = RuntimeRequirementsRegistry.get(rt)
|
|
273
|
+
if not requirements:
|
|
274
|
+
# No requirements - allow
|
|
275
|
+
return True, []
|
|
276
|
+
|
|
277
|
+
errors = []
|
|
278
|
+
|
|
279
|
+
# Validate model
|
|
280
|
+
is_valid, error = requirements.validate_model(model_id)
|
|
281
|
+
if not is_valid:
|
|
282
|
+
errors.append(error)
|
|
283
|
+
|
|
284
|
+
# Validate config
|
|
285
|
+
config_errors = requirements.validate_config(agent_config)
|
|
286
|
+
errors.extend(config_errors)
|
|
287
|
+
|
|
288
|
+
# Validate system prompt if required
|
|
289
|
+
if requirements.requires_system_prompt and not system_prompt:
|
|
290
|
+
errors.append("System prompt is required for this runtime")
|
|
291
|
+
|
|
292
|
+
return len(errors) == 0, errors
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def get_runtime_requirements_info(runtime_type: str) -> Dict[str, Any]:
|
|
296
|
+
"""
|
|
297
|
+
Get human-readable requirements info for a runtime.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
runtime_type: Runtime type string
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Dict with requirements information
|
|
304
|
+
"""
|
|
305
|
+
try:
|
|
306
|
+
rt = RuntimeType(runtime_type)
|
|
307
|
+
except ValueError:
|
|
308
|
+
return {"error": f"Invalid runtime type: {runtime_type}"}
|
|
309
|
+
|
|
310
|
+
requirements = RuntimeRequirementsRegistry.get(rt)
|
|
311
|
+
if not requirements:
|
|
312
|
+
return {
|
|
313
|
+
"runtime_type": runtime_type,
|
|
314
|
+
"model_requirement": "No specific requirements",
|
|
315
|
+
"flexible": True,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
"runtime_type": runtime_type,
|
|
320
|
+
"model_requirement": {
|
|
321
|
+
"description": requirements.model_requirement.description,
|
|
322
|
+
"supported_providers": list(requirements.model_requirement.supported_providers),
|
|
323
|
+
"supported_families": list(requirements.model_requirement.supported_families),
|
|
324
|
+
"examples": requirements.model_requirement.examples,
|
|
325
|
+
},
|
|
326
|
+
"required_config_fields": requirements.required_config_fields,
|
|
327
|
+
"recommended_config_fields": requirements.recommended_config_fields,
|
|
328
|
+
"max_history_length": requirements.max_history_length,
|
|
329
|
+
"requires_system_prompt": requirements.requires_system_prompt,
|
|
330
|
+
"requires_tools": requirements.requires_tools,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def list_all_runtime_requirements() -> Dict[str, Dict[str, Any]]:
|
|
335
|
+
"""
|
|
336
|
+
Get requirements for all registered runtimes.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Dict mapping runtime type to requirements info
|
|
340
|
+
"""
|
|
341
|
+
return {
|
|
342
|
+
rt.value: get_runtime_requirements_info(rt.value)
|
|
343
|
+
for rt in RuntimeRequirementsRegistry._requirements.keys()
|
|
344
|
+
}
|