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,789 @@
1
+ """
2
+ Base runtime abstraction with proper ABC, registry, and Control Plane integration.
3
+
4
+ This module provides:
5
+ - Abstract base class for all runtimes
6
+ - Runtime registry for discovery and instantiation
7
+ - Lifecycle hooks for extensibility
8
+ - Control Plane integration helpers
9
+ - Configuration validation framework
10
+ """
11
+
12
+ from typing import AsyncIterator, Dict, Any, Optional, List, Callable, Type
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum
15
+ from abc import ABC, abstractmethod
16
+ import structlog
17
+
18
+ logger = structlog.get_logger(__name__)
19
+
20
+
21
+ class RuntimeType(str, Enum):
22
+ """Enumeration of supported runtime types."""
23
+
24
+ DEFAULT = "default" # Agno-based runtime
25
+ CLAUDE_CODE = "claude_code" # Claude Code SDK runtime
26
+ # Easy to add more: LANGCHAIN = "langchain", CREWAI = "crewai", etc.
27
+
28
+
29
+ @dataclass
30
+ class RuntimeExecutionResult:
31
+ """
32
+ Standardized result structure from any runtime.
33
+
34
+ This ensures all runtimes return consistent data structures that can
35
+ be consumed by the workflow and activity layers.
36
+
37
+ Analytics Integration:
38
+ The `usage` field provides standardized token usage metrics that are
39
+ automatically extracted and submitted to the analytics system.
40
+ """
41
+
42
+ response: str
43
+ """The main response text from the agent."""
44
+
45
+ usage: Dict[str, Any]
46
+ """
47
+ Token usage metrics with standardized fields:
48
+ - input_tokens (int): Number of input/prompt tokens
49
+ - output_tokens (int): Number of output/completion tokens
50
+ - total_tokens (int): Total tokens used
51
+ - cache_read_tokens (int, optional): Cached tokens read (Anthropic)
52
+ - cache_creation_tokens (int, optional): Tokens used for cache creation (Anthropic)
53
+ - prompt_tokens_details (dict, optional): Detailed breakdown from provider
54
+
55
+ Runtimes should populate this from their native usage tracking.
56
+ """
57
+
58
+ success: bool
59
+ """Whether the execution succeeded."""
60
+
61
+ finish_reason: Optional[str] = None
62
+ """Reason the execution finished (e.g., 'stop', 'length', 'tool_use')."""
63
+
64
+ run_id: Optional[str] = None
65
+ """Unique identifier for this execution run."""
66
+
67
+ model: Optional[str] = None
68
+ """Model identifier used for this execution."""
69
+
70
+ tool_execution_messages: Optional[List[Dict]] = None
71
+ """
72
+ Tool execution messages for UI display and analytics.
73
+ Format: [{"tool": "Bash", "input": {...}, "output": {...}, "success": bool, "duration_ms": int}, ...]
74
+
75
+ Analytics Integration:
76
+ These are automatically tracked in the execution_tool_calls table.
77
+ """
78
+
79
+ tool_messages: Optional[List[Dict]] = None
80
+ """
81
+ Detailed tool messages with execution metadata.
82
+ Format: [{"role": "tool", "content": "...", "tool_use_id": "..."}, ...]
83
+ """
84
+
85
+ error: Optional[str] = None
86
+ """Error message if execution failed."""
87
+
88
+ metadata: Dict[str, Any] = field(default_factory=dict)
89
+ """
90
+ Additional runtime-specific metadata.
91
+
92
+ Can include:
93
+ - turn_duration_ms (int): Duration of this turn in milliseconds
94
+ - model_provider (str): Provider name (anthropic, openai, google, etc.)
95
+ - tasks (list): Task tracking data for analytics
96
+ - any runtime-specific metrics
97
+ """
98
+
99
+
100
+ @dataclass
101
+ class RuntimeExecutionContext:
102
+ """
103
+ Context passed to runtime for execution.
104
+
105
+ This contains all the information needed for an agent to execute,
106
+ regardless of which runtime implementation is used.
107
+ """
108
+
109
+ execution_id: str
110
+ """Unique identifier for this execution."""
111
+
112
+ agent_id: str
113
+ """Unique identifier for the agent being executed."""
114
+
115
+ organization_id: str
116
+ """Organization context for this execution."""
117
+
118
+ prompt: str
119
+ """User's input prompt/message."""
120
+
121
+ system_prompt: Optional[str] = None
122
+ """System-level instructions for the agent."""
123
+
124
+ conversation_history: List[Dict[str, Any]] = field(default_factory=list)
125
+ """
126
+ Previous conversation messages.
127
+ Format: [{"role": "user|assistant|system", "content": "..."}, ...]
128
+ """
129
+
130
+ model_id: Optional[str] = None
131
+ """LiteLLM model identifier (e.g., 'gpt-4', 'claude-3-opus')."""
132
+
133
+ model_config: Optional[Dict[str, Any]] = None
134
+ """Model-specific configuration (temperature, top_p, etc.)."""
135
+
136
+ agent_config: Optional[Dict[str, Any]] = None
137
+ """Agent-specific configuration."""
138
+
139
+ skills: List[Any] = field(default_factory=list)
140
+ """Resolved skills available to the agent."""
141
+
142
+ mcp_servers: Optional[Dict[str, Any]] = None
143
+ """MCP server configurations."""
144
+
145
+ user_metadata: Optional[Dict[str, Any]] = None
146
+ """User-provided metadata for this execution."""
147
+
148
+ runtime_config: Optional[Dict[str, Any]] = None
149
+ """Runtime-specific configuration options."""
150
+
151
+
152
+ @dataclass
153
+ class RuntimeCapabilities:
154
+ """Runtime capabilities metadata."""
155
+
156
+ streaming: bool = False
157
+ """Supports streaming execution."""
158
+
159
+ tools: bool = False
160
+ """Supports tool calling."""
161
+
162
+ mcp: bool = False
163
+ """Supports MCP servers."""
164
+
165
+ hooks: bool = False
166
+ """Supports lifecycle hooks."""
167
+
168
+ cancellation: bool = False
169
+ """Supports execution cancellation."""
170
+
171
+ conversation_history: bool = False
172
+ """Supports multi-turn conversations."""
173
+
174
+ custom_tools: bool = False
175
+ """Supports custom tool registration."""
176
+
177
+
178
+ class BaseRuntime(ABC):
179
+ """
180
+ Abstract base class for all agent runtimes.
181
+
182
+ This class provides:
183
+ - Standard interface for all runtimes
184
+ - Lifecycle hooks for extensibility
185
+ - Control Plane integration helpers
186
+ - Configuration validation
187
+ - Error handling patterns
188
+
189
+ To create a new runtime:
190
+ 1. Inherit from BaseRuntime
191
+ 2. Implement abstract methods
192
+ 3. Register via @RuntimeRegistry.register()
193
+ 4. Override lifecycle hooks as needed
194
+ """
195
+
196
+ def __init__(
197
+ self,
198
+ control_plane_client: Any,
199
+ cancellation_manager: Any,
200
+ **kwargs,
201
+ ):
202
+ """
203
+ Initialize the runtime.
204
+
205
+ Args:
206
+ control_plane_client: Client for Control Plane API
207
+ cancellation_manager: Manager for execution cancellation
208
+ **kwargs: Additional configuration options
209
+ """
210
+ self.control_plane = control_plane_client
211
+ self.cancellation_manager = cancellation_manager
212
+ self.logger = structlog.get_logger(self.__class__.__name__)
213
+ self.config = kwargs
214
+
215
+ # Track active executions for cleanup
216
+ self._active_executions: Dict[str, Any] = {}
217
+
218
+ # ==================== Abstract Methods (Must Implement) ====================
219
+
220
+ @abstractmethod
221
+ async def _execute_impl(
222
+ self, context: RuntimeExecutionContext
223
+ ) -> RuntimeExecutionResult:
224
+ """
225
+ Core execution logic (non-streaming).
226
+
227
+ Implement the actual execution logic here without worrying about
228
+ lifecycle hooks, Control Plane integration, or error handling.
229
+ The base class handles those concerns.
230
+
231
+ Args:
232
+ context: Execution context
233
+
234
+ Returns:
235
+ RuntimeExecutionResult
236
+ """
237
+ pass
238
+
239
+ @abstractmethod
240
+ async def _stream_execute_impl(
241
+ self,
242
+ context: RuntimeExecutionContext,
243
+ event_callback: Optional[Callable[[Dict], None]] = None,
244
+ ) -> AsyncIterator[RuntimeExecutionResult]:
245
+ """
246
+ Core streaming execution logic.
247
+
248
+ Implement the actual streaming logic here. The base class
249
+ handles lifecycle hooks and error handling.
250
+
251
+ Args:
252
+ context: Execution context
253
+ event_callback: Optional callback for events
254
+
255
+ Yields:
256
+ RuntimeExecutionResult chunks
257
+ """
258
+ pass
259
+
260
+ @abstractmethod
261
+ def get_runtime_type(self) -> RuntimeType:
262
+ """Return the runtime type identifier."""
263
+ pass
264
+
265
+ @abstractmethod
266
+ def get_capabilities(self) -> RuntimeCapabilities:
267
+ """Return runtime capabilities."""
268
+ pass
269
+
270
+ # ==================== Public Interface (Don't Override) ====================
271
+
272
+ async def execute(
273
+ self, context: RuntimeExecutionContext
274
+ ) -> RuntimeExecutionResult:
275
+ """
276
+ Execute agent with full lifecycle management.
277
+
278
+ This method orchestrates the entire execution lifecycle:
279
+ 1. Validate configuration
280
+ 2. Call before_execute hook
281
+ 3. Cache metadata in Control Plane
282
+ 4. Execute via _execute_impl
283
+ 5. Call after_execute hook
284
+ 6. Handle errors via on_error hook
285
+ 7. Cleanup
286
+
287
+ Args:
288
+ context: Execution context
289
+
290
+ Returns:
291
+ RuntimeExecutionResult
292
+ """
293
+ execution_id = context.execution_id
294
+
295
+ try:
296
+ # 1. Validate configuration
297
+ self._validate_config(context)
298
+
299
+ # 2. Before execute hook
300
+ await self.before_execute(context)
301
+
302
+ # 3. Cache metadata in Control Plane
303
+ self._cache_execution_metadata(context)
304
+
305
+ # 4. Register for cancellation
306
+ if self.get_capabilities().cancellation:
307
+ self.cancellation_manager.register(
308
+ execution_id=execution_id,
309
+ instance=self,
310
+ instance_type=self.__class__.__name__,
311
+ )
312
+
313
+ # 5. Execute implementation
314
+ self.logger.info(
315
+ "runtime_execute_start",
316
+ execution_id=execution_id[:8],
317
+ runtime=self.get_runtime_type().value,
318
+ )
319
+
320
+ result = await self._execute_impl(context)
321
+
322
+ # 6. After execute hook
323
+ await self.after_execute(context, result)
324
+
325
+ self.logger.info(
326
+ "runtime_execute_complete",
327
+ execution_id=execution_id[:8],
328
+ success=result.success,
329
+ )
330
+
331
+ return result
332
+
333
+ except Exception as e:
334
+ # 7. Error hook
335
+ error_result = await self.on_error(context, e)
336
+ return error_result
337
+
338
+ finally:
339
+ # 8. Cleanup
340
+ if self.get_capabilities().cancellation:
341
+ self.cancellation_manager.unregister(execution_id)
342
+ self._active_executions.pop(execution_id, None)
343
+
344
+ async def stream_execute(
345
+ self,
346
+ context: RuntimeExecutionContext,
347
+ event_callback: Optional[Callable[[Dict], None]] = None,
348
+ ) -> AsyncIterator[RuntimeExecutionResult]:
349
+ """
350
+ Execute agent with streaming and full lifecycle management.
351
+
352
+ Args:
353
+ context: Execution context
354
+ event_callback: Optional callback for events
355
+
356
+ Yields:
357
+ RuntimeExecutionResult chunks
358
+ """
359
+ execution_id = context.execution_id
360
+
361
+ try:
362
+ # 1. Validate configuration
363
+ self._validate_config(context)
364
+
365
+ # 2. Before execute hook
366
+ await self.before_execute(context)
367
+
368
+ # 3. Cache metadata in Control Plane
369
+ self._cache_execution_metadata(context)
370
+
371
+ # 4. Register for cancellation
372
+ if self.get_capabilities().cancellation:
373
+ self.cancellation_manager.register(
374
+ execution_id=execution_id,
375
+ instance=self,
376
+ instance_type=self.__class__.__name__,
377
+ )
378
+
379
+ # 5. Stream implementation
380
+ self.logger.info(
381
+ "runtime_stream_start",
382
+ execution_id=execution_id[:8],
383
+ runtime=self.get_runtime_type().value,
384
+ )
385
+
386
+ final_result = None
387
+ async for chunk in self._stream_execute_impl(context, event_callback):
388
+ yield chunk
389
+ if chunk.finish_reason:
390
+ final_result = chunk
391
+
392
+ # 6. After execute hook
393
+ if final_result:
394
+ await self.after_execute(context, final_result)
395
+
396
+ self.logger.info(
397
+ "runtime_stream_complete",
398
+ execution_id=execution_id[:8],
399
+ )
400
+
401
+ except Exception as e:
402
+ # 7. Error hook
403
+ error_result = await self.on_error(context, e)
404
+ yield error_result
405
+
406
+ finally:
407
+ # 8. Cleanup
408
+ if self.get_capabilities().cancellation:
409
+ self.cancellation_manager.unregister(execution_id)
410
+ self._active_executions.pop(execution_id, None)
411
+
412
+ async def cancel(self, execution_id: str) -> bool:
413
+ """
414
+ Cancel an in-progress execution.
415
+
416
+ Override _cancel_impl() to provide runtime-specific cancellation logic.
417
+
418
+ Args:
419
+ execution_id: ID of execution to cancel
420
+
421
+ Returns:
422
+ True if cancellation succeeded
423
+ """
424
+ if not self.get_capabilities().cancellation:
425
+ self.logger.warning(
426
+ "runtime_cancel_not_supported",
427
+ runtime=self.get_runtime_type().value,
428
+ )
429
+ return False
430
+
431
+ try:
432
+ return await self._cancel_impl(execution_id)
433
+ except Exception as e:
434
+ self.logger.error(
435
+ "runtime_cancel_failed",
436
+ execution_id=execution_id[:8],
437
+ error=str(e),
438
+ )
439
+ return False
440
+
441
+ async def get_usage(self, execution_id: str) -> Dict[str, Any]:
442
+ """
443
+ Get usage metrics for an execution.
444
+
445
+ Override _get_usage_impl() to provide runtime-specific usage tracking.
446
+
447
+ Args:
448
+ execution_id: ID of execution
449
+
450
+ Returns:
451
+ Usage metrics dict
452
+ """
453
+ try:
454
+ return await self._get_usage_impl(execution_id)
455
+ except Exception as e:
456
+ self.logger.error(
457
+ "runtime_get_usage_failed",
458
+ execution_id=execution_id[:8],
459
+ error=str(e),
460
+ )
461
+ return {}
462
+
463
+ # ==================== Capabilities API ====================
464
+
465
+ def supports_streaming(self) -> bool:
466
+ """Whether this runtime supports streaming execution."""
467
+ return self.get_capabilities().streaming
468
+
469
+ def supports_tools(self) -> bool:
470
+ """Whether this runtime supports tool calling."""
471
+ return self.get_capabilities().tools
472
+
473
+ def supports_mcp(self) -> bool:
474
+ """Whether this runtime supports MCP servers."""
475
+ return self.get_capabilities().mcp
476
+
477
+ def get_runtime_info(self) -> Dict[str, Any]:
478
+ """
479
+ Get information about this runtime implementation.
480
+
481
+ Override to provide additional metadata.
482
+
483
+ Returns:
484
+ Dict with runtime metadata
485
+ """
486
+ caps = self.get_capabilities()
487
+ return {
488
+ "runtime_type": self.get_runtime_type().value,
489
+ "supports_streaming": caps.streaming,
490
+ "supports_tools": caps.tools,
491
+ "supports_mcp": caps.mcp,
492
+ "supports_hooks": caps.hooks,
493
+ "supports_cancellation": caps.cancellation,
494
+ "supports_conversation_history": caps.conversation_history,
495
+ "supports_custom_tools": caps.custom_tools,
496
+ }
497
+
498
+ # ==================== Lifecycle Hooks (Override as Needed) ====================
499
+
500
+ async def before_execute(self, context: RuntimeExecutionContext) -> None:
501
+ """
502
+ Hook called before execution starts.
503
+
504
+ Override to:
505
+ - Validate additional configuration
506
+ - Setup resources
507
+ - Initialize connections
508
+ - Log execution start
509
+
510
+ Args:
511
+ context: Execution context
512
+ """
513
+ pass
514
+
515
+ async def after_execute(
516
+ self, context: RuntimeExecutionContext, result: RuntimeExecutionResult
517
+ ) -> None:
518
+ """
519
+ Hook called after successful execution.
520
+
521
+ Override to:
522
+ - Cleanup resources
523
+ - Log metrics
524
+ - Update statistics
525
+ - Trigger webhooks
526
+
527
+ Args:
528
+ context: Execution context
529
+ result: Execution result
530
+ """
531
+ pass
532
+
533
+ async def on_error(
534
+ self, context: RuntimeExecutionContext, error: Exception
535
+ ) -> RuntimeExecutionResult:
536
+ """
537
+ Hook called when execution fails.
538
+
539
+ Override to:
540
+ - Custom error handling
541
+ - Error reporting
542
+ - Cleanup
543
+ - Fallback logic
544
+
545
+ Args:
546
+ context: Execution context
547
+ error: Exception that occurred
548
+
549
+ Returns:
550
+ RuntimeExecutionResult with error details
551
+ """
552
+ self.logger.error(
553
+ "runtime_execution_failed",
554
+ execution_id=context.execution_id[:8],
555
+ runtime=self.get_runtime_type().value,
556
+ error=str(error),
557
+ error_type=type(error).__name__,
558
+ )
559
+
560
+ return RuntimeExecutionResult(
561
+ response="",
562
+ usage={},
563
+ success=False,
564
+ error=f"{type(error).__name__}: {str(error)}",
565
+ finish_reason="error",
566
+ )
567
+
568
+ # ==================== Helper Methods (Override as Needed) ====================
569
+
570
+ async def _cancel_impl(self, execution_id: str) -> bool:
571
+ """
572
+ Runtime-specific cancellation implementation.
573
+
574
+ Override to provide custom cancellation logic.
575
+
576
+ Args:
577
+ execution_id: ID of execution to cancel
578
+
579
+ Returns:
580
+ True if successful
581
+ """
582
+ return False
583
+
584
+ async def _get_usage_impl(self, execution_id: str) -> Dict[str, Any]:
585
+ """
586
+ Runtime-specific usage tracking implementation.
587
+
588
+ Override to provide usage metrics.
589
+
590
+ Args:
591
+ execution_id: ID of execution
592
+
593
+ Returns:
594
+ Usage metrics dict
595
+ """
596
+ return {}
597
+
598
+ def _validate_config(self, context: RuntimeExecutionContext) -> None:
599
+ """
600
+ Validate runtime configuration.
601
+
602
+ Override to add custom validation logic.
603
+ Raise ValueError if configuration is invalid.
604
+
605
+ Args:
606
+ context: Execution context
607
+
608
+ Raises:
609
+ ValueError: If configuration is invalid
610
+ """
611
+ # Base validation
612
+ if not context.prompt:
613
+ raise ValueError("Prompt is required")
614
+ if not context.execution_id:
615
+ raise ValueError("Execution ID is required")
616
+
617
+ # Runtime-specific requirements validation
618
+ try:
619
+ from control_plane_api.worker.runtimes.validation import RuntimeRequirementsRegistry
620
+
621
+ is_valid, errors = RuntimeRequirementsRegistry.validate_for_runtime(
622
+ self.get_runtime_type(), context
623
+ )
624
+
625
+ if not is_valid:
626
+ error_msg = "Runtime validation failed:\n" + "\n".join(f" - {err}" for err in errors)
627
+ raise ValueError(error_msg)
628
+
629
+ except ImportError:
630
+ # Validation module not available - skip validation
631
+ self.logger.warning("Runtime validation module not available, skipping validation")
632
+ except Exception as e:
633
+ self.logger.error(
634
+ "Runtime validation error",
635
+ error=str(e),
636
+ runtime=self.get_runtime_type().value,
637
+ )
638
+ raise
639
+
640
+ def _cache_execution_metadata(self, context: RuntimeExecutionContext) -> None:
641
+ """
642
+ Cache execution metadata in Control Plane.
643
+
644
+ This enables:
645
+ - Execution tracking
646
+ - Real-time monitoring
647
+ - Analytics
648
+
649
+ Args:
650
+ context: Execution context
651
+ """
652
+ try:
653
+ self.control_plane.cache_metadata(
654
+ context.execution_id,
655
+ "AGENT",
656
+ )
657
+ except Exception as e:
658
+ self.logger.warning(
659
+ "failed_to_cache_metadata",
660
+ execution_id=context.execution_id[:8],
661
+ error=str(e),
662
+ )
663
+
664
+
665
+ class RuntimeRegistry:
666
+ """
667
+ Registry for runtime discovery and instantiation.
668
+
669
+ This registry allows runtimes to be:
670
+ - Automatically discovered
671
+ - Registered via decorator
672
+ - Instantiated by name or type
673
+ - Listed for discoverability
674
+ """
675
+
676
+ _registry: Dict[RuntimeType, Type[BaseRuntime]] = {}
677
+
678
+ @classmethod
679
+ def register(cls, runtime_type: RuntimeType):
680
+ """
681
+ Decorator to register a runtime.
682
+
683
+ Usage:
684
+ @RuntimeRegistry.register(RuntimeType.CLAUDE_CODE)
685
+ class ClaudeCodeRuntime(BaseRuntime):
686
+ ...
687
+
688
+ Args:
689
+ runtime_type: Type identifier for this runtime
690
+
691
+ Returns:
692
+ Decorator function
693
+ """
694
+
695
+ def decorator(runtime_class: Type[BaseRuntime]):
696
+ cls._registry[runtime_type] = runtime_class
697
+ logger.info(
698
+ "runtime_registered",
699
+ runtime_type=runtime_type.value,
700
+ runtime_class=runtime_class.__name__,
701
+ )
702
+ return runtime_class
703
+
704
+ return decorator
705
+
706
+ @classmethod
707
+ def get(cls, runtime_type: RuntimeType) -> Type[BaseRuntime]:
708
+ """
709
+ Get runtime class by type.
710
+
711
+ Args:
712
+ runtime_type: Runtime type to get
713
+
714
+ Returns:
715
+ Runtime class
716
+
717
+ Raises:
718
+ ValueError: If runtime type not found
719
+ """
720
+ if runtime_type not in cls._registry:
721
+ raise ValueError(
722
+ f"Runtime type '{runtime_type.value}' not registered. "
723
+ f"Available: {list(cls._registry.keys())}"
724
+ )
725
+ return cls._registry[runtime_type]
726
+
727
+ @classmethod
728
+ def create(
729
+ cls,
730
+ runtime_type: RuntimeType,
731
+ control_plane_client: Any,
732
+ cancellation_manager: Any,
733
+ **kwargs,
734
+ ) -> BaseRuntime:
735
+ """
736
+ Create runtime instance.
737
+
738
+ Args:
739
+ runtime_type: Type of runtime to create
740
+ control_plane_client: Control Plane client
741
+ cancellation_manager: Cancellation manager
742
+ **kwargs: Additional configuration
743
+
744
+ Returns:
745
+ Runtime instance
746
+
747
+ Raises:
748
+ ValueError: If runtime type not found
749
+ """
750
+ runtime_class = cls.get(runtime_type)
751
+ return runtime_class(
752
+ control_plane_client=control_plane_client,
753
+ cancellation_manager=cancellation_manager,
754
+ **kwargs,
755
+ )
756
+
757
+ @classmethod
758
+ def list_available(cls) -> List[RuntimeType]:
759
+ """
760
+ List all registered runtime types.
761
+
762
+ Returns:
763
+ List of available runtime types
764
+ """
765
+ return list(cls._registry.keys())
766
+
767
+ @classmethod
768
+ def get_runtime_info_all(cls) -> Dict[str, Dict[str, Any]]:
769
+ """
770
+ Get information about all registered runtimes.
771
+
772
+ Returns:
773
+ Dict mapping runtime type to info dict
774
+ """
775
+ info = {}
776
+ for runtime_type, runtime_class in cls._registry.items():
777
+ # Instantiate temporarily to get info (mock dependencies)
778
+ try:
779
+ from unittest.mock import MagicMock
780
+
781
+ temp_instance = runtime_class(
782
+ control_plane_client=MagicMock(),
783
+ cancellation_manager=MagicMock(),
784
+ )
785
+ info[runtime_type.value] = temp_instance.get_runtime_info()
786
+ except Exception as e:
787
+ info[runtime_type.value] = {"error": str(e)}
788
+
789
+ return info