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,194 @@
1
+ """Session management service - handles loading and persisting conversation history"""
2
+
3
+ from typing import List, Dict, Any, Optional
4
+ from datetime import datetime, timezone
5
+ import structlog
6
+ import httpx
7
+
8
+ from control_plane_api.worker.control_plane_client import ControlPlaneClient
9
+ from control_plane_api.worker.utils.retry_utils import retry_with_backoff
10
+
11
+ logger = structlog.get_logger()
12
+
13
+
14
+ def _safe_timestamp_to_iso(timestamp: Any) -> str:
15
+ """
16
+ Safely convert a timestamp (int, float, datetime, or str) to ISO format string.
17
+
18
+ Args:
19
+ timestamp: Can be Unix timestamp (int/float), datetime object, or ISO string
20
+
21
+ Returns:
22
+ ISO format timestamp string
23
+ """
24
+ if isinstance(timestamp, str):
25
+ # Already a string, return as-is
26
+ return timestamp
27
+ elif isinstance(timestamp, datetime):
28
+ # datetime object, call isoformat()
29
+ return timestamp.isoformat()
30
+ elif isinstance(timestamp, (int, float)):
31
+ # Unix timestamp, convert to datetime first
32
+ return datetime.fromtimestamp(timestamp, tz=timezone.utc).isoformat()
33
+ else:
34
+ # Fallback to current time
35
+ return datetime.now(timezone.utc).isoformat()
36
+
37
+
38
+ class SessionService:
39
+ """
40
+ Manages session history loading and persistence via Control Plane API.
41
+
42
+ Workers don't have database access, so all session operations go through
43
+ the Control Plane which provides Redis caching for hot loads.
44
+ """
45
+
46
+ def __init__(self, control_plane: ControlPlaneClient):
47
+ self.control_plane = control_plane
48
+
49
+ @retry_with_backoff(max_retries=3, initial_delay=1.0)
50
+ def load_session(
51
+ self,
52
+ execution_id: str,
53
+ session_id: Optional[str] = None
54
+ ) -> List[Dict[str, Any]]:
55
+ """
56
+ Load session history from Control Plane (with retry).
57
+
58
+ Returns:
59
+ List of message dicts with role, content, timestamp, etc.
60
+ Empty list if session not found or on error.
61
+ """
62
+ if not session_id:
63
+ return []
64
+
65
+ try:
66
+ session_data = self.control_plane.get_session(
67
+ execution_id=execution_id,
68
+ session_id=session_id
69
+ )
70
+
71
+ if session_data and session_data.get("messages"):
72
+ messages = session_data["messages"]
73
+ logger.info(
74
+ "session_loaded",
75
+ execution_id=execution_id[:8],
76
+ message_count=len(messages)
77
+ )
78
+ return messages
79
+
80
+ return []
81
+
82
+ except httpx.TimeoutException:
83
+ logger.warning(
84
+ "session_load_timeout",
85
+ execution_id=execution_id[:8]
86
+ )
87
+ raise # Let retry decorator handle it
88
+ except Exception as e:
89
+ logger.warning(
90
+ "session_load_error",
91
+ execution_id=execution_id[:8],
92
+ error=str(e)
93
+ )
94
+ return [] # Don't retry on non-timeout errors
95
+
96
+ @retry_with_backoff(max_retries=3, initial_delay=1.0)
97
+ def persist_session(
98
+ self,
99
+ execution_id: str,
100
+ session_id: str,
101
+ user_id: Optional[str],
102
+ messages: List[Dict[str, Any]],
103
+ metadata: Optional[Dict[str, Any]] = None
104
+ ) -> bool:
105
+ """
106
+ Persist session history to Control Plane (with retry).
107
+
108
+ Returns:
109
+ True if successful, False otherwise
110
+ """
111
+ if not messages:
112
+ logger.info("session_persist_skipped_no_messages", execution_id=execution_id[:8])
113
+ return True
114
+
115
+ try:
116
+ success = self.control_plane.persist_session(
117
+ execution_id=execution_id,
118
+ session_id=session_id or execution_id,
119
+ user_id=user_id,
120
+ messages=messages,
121
+ metadata=metadata or {}
122
+ )
123
+
124
+ if success:
125
+ logger.info(
126
+ "session_persisted",
127
+ execution_id=execution_id[:8],
128
+ message_count=len(messages)
129
+ )
130
+
131
+ return success
132
+
133
+ except Exception as e:
134
+ logger.error(
135
+ "session_persist_error",
136
+ execution_id=execution_id[:8],
137
+ error=str(e)
138
+ )
139
+ return False
140
+
141
+ def build_conversation_context(
142
+ self,
143
+ session_messages: List[Dict[str, Any]]
144
+ ) -> List[Dict[str, str]]:
145
+ """
146
+ Convert Control Plane session messages to Agno format.
147
+
148
+ Args:
149
+ session_messages: Messages from Control Plane
150
+
151
+ Returns:
152
+ List of dicts with 'role' and 'content' for Agno
153
+ """
154
+ context = []
155
+ for msg in session_messages:
156
+ context.append({
157
+ "role": msg.get("role", "user"),
158
+ "content": msg.get("content", ""),
159
+ })
160
+ return context
161
+
162
+ def extract_messages_from_result(
163
+ self,
164
+ result: Any,
165
+ user_id: Optional[str] = None
166
+ ) -> List[Dict[str, Any]]:
167
+ """
168
+ Extract messages from Agno Agent/Team result.
169
+
170
+ Args:
171
+ result: Agno RunResponse object
172
+ user_id: Optional user ID to attach
173
+
174
+ Returns:
175
+ List of message dicts ready for persistence
176
+ """
177
+ messages = []
178
+
179
+ if hasattr(result, "messages") and result.messages:
180
+ for msg in result.messages:
181
+ messages.append({
182
+ "role": msg.role,
183
+ "content": msg.content,
184
+ "timestamp": (
185
+ _safe_timestamp_to_iso(msg.created_at)
186
+ if hasattr(msg, "created_at") and msg.created_at is not None
187
+ else datetime.now(timezone.utc).isoformat()
188
+ ),
189
+ "user_id": getattr(msg, "user_id", user_id),
190
+ "user_name": getattr(msg, "user_name", None),
191
+ "user_email": getattr(msg, "user_email", None),
192
+ })
193
+
194
+ return messages
@@ -0,0 +1,175 @@
1
+ """Skill factory - instantiates skill toolkits from Control Plane configuration"""
2
+
3
+ from typing import Optional, Any, List
4
+ from pathlib import Path
5
+ import structlog
6
+ import os
7
+
8
+ from agno.tools.shell import ShellTools
9
+ from agno.tools.python import PythonTools
10
+ from agno.tools.file import FileTools
11
+ from control_plane_api.worker.services.workflow_executor_tools import WorkflowExecutorTools
12
+
13
+ logger = structlog.get_logger()
14
+
15
+
16
+ class SkillFactory:
17
+ """
18
+ Factory for creating skill toolkit instances from Control Plane skill configurations.
19
+
20
+ Centralizes skill instantiation logic that was previously duplicated
21
+ in agent_activities.py and team_activities.py.
22
+ """
23
+
24
+ @staticmethod
25
+ def create_skill(skill_data: dict) -> Optional[Any]:
26
+ """
27
+ Create a skill toolkit from Control Plane configuration.
28
+
29
+ Args:
30
+ skill_data: Skill config from Control Plane API:
31
+ - type: Skill type (file_system, shell, python, etc.)
32
+ - name: Skill name
33
+ - configuration: Dict with skill-specific config
34
+ - enabled: Whether skill is enabled
35
+
36
+ Returns:
37
+ Instantiated skill toolkit or None if disabled/unsupported
38
+ """
39
+ if not skill_data.get("enabled", True):
40
+ logger.info(
41
+ "skill_skipped_disabled",
42
+ skill_name=skill_data.get("name")
43
+ )
44
+ return None
45
+
46
+ skill_type = skill_data.get("type", "").lower()
47
+ config = skill_data.get("configuration", {})
48
+ name = skill_data.get("name", "Unknown")
49
+
50
+ try:
51
+ # File system tools
52
+ if skill_type in ["file_system", "file", "file_generation"]:
53
+ base_dir = config.get("base_directory", "/workspace")
54
+ return FileTools(base_dir=Path(base_dir))
55
+
56
+ # Shell/terminal tools
57
+ elif skill_type in ["shell", "terminal", "bash"]:
58
+ return ShellTools()
59
+
60
+ # Python tools
61
+ elif skill_type in ["python", "python_code"]:
62
+ return PythonTools()
63
+
64
+ # Workflow executor tools
65
+ elif skill_type in ["workflow_executor", "workflow"]:
66
+ logger.info(
67
+ "🔍 Creating workflow_executor skill",
68
+ skill_name=name,
69
+ skill_type=skill_type,
70
+ )
71
+
72
+ # New multi-workflow format
73
+ workflows = config.get("workflows")
74
+
75
+ # Legacy single-workflow format
76
+ workflow_type = config.get("workflow_type", "json")
77
+ workflow_definition = config.get("workflow_definition")
78
+ python_dsl_code = config.get("python_dsl_code")
79
+
80
+ # Common configuration
81
+ validation_enabled = config.get("validation_enabled", True)
82
+ default_runner = config.get("default_runner")
83
+ timeout = config.get("timeout", 3600)
84
+ default_parameters = config.get("default_parameters")
85
+
86
+ # Get Kubiya API credentials from environment
87
+ # These are needed for remote workflow execution
88
+ kubiya_api_key = os.environ.get("KUBIYA_API_KEY")
89
+ kubiya_api_base = os.environ.get("KUBIYA_API_BASE", "https://api.kubiya.ai")
90
+
91
+ logger.info(
92
+ "workflow_executor_config",
93
+ skill_name=name,
94
+ has_workflows=bool(workflows),
95
+ workflows_count=len(workflows) if workflows else 0,
96
+ has_api_key=bool(kubiya_api_key),
97
+ api_base=kubiya_api_base,
98
+ )
99
+
100
+ if not kubiya_api_key:
101
+ logger.warning(
102
+ "workflow_executor_no_api_key",
103
+ skill_name=name,
104
+ message="KUBIYA_API_KEY not found - workflow execution will fail"
105
+ )
106
+
107
+ workflow_tool = WorkflowExecutorTools(
108
+ name=name, # Pass the skill name from configuration
109
+ workflows=workflows,
110
+ workflow_type=workflow_type,
111
+ workflow_definition=workflow_definition,
112
+ python_dsl_code=python_dsl_code,
113
+ validation_enabled=validation_enabled,
114
+ default_runner=default_runner,
115
+ timeout=timeout,
116
+ default_parameters=default_parameters,
117
+ kubiya_api_key=kubiya_api_key, # Explicitly pass API key
118
+ kubiya_api_base=kubiya_api_base, # Explicitly pass API base URL
119
+ )
120
+
121
+ logger.info(
122
+ "✅ Workflow executor skill created successfully",
123
+ skill_name=name,
124
+ tool_class=type(workflow_tool).__name__,
125
+ has_functions=hasattr(workflow_tool, 'functions'),
126
+ function_count=len(workflow_tool.functions) if hasattr(workflow_tool, 'functions') else 0,
127
+ )
128
+
129
+ return workflow_tool
130
+
131
+ else:
132
+ logger.warning(
133
+ "skill_type_not_supported",
134
+ skill_type=skill_type,
135
+ skill_name=name
136
+ )
137
+ return None
138
+
139
+ except Exception as e:
140
+ logger.error(
141
+ "skill_instantiation_failed",
142
+ skill_type=skill_type,
143
+ skill_name=name,
144
+ error=str(e)
145
+ )
146
+ return None
147
+
148
+ @classmethod
149
+ def create_skills_from_list(
150
+ cls,
151
+ skill_configs: List[dict]
152
+ ) -> List[Any]:
153
+ """
154
+ Create multiple skills from a list of configurations.
155
+
156
+ Args:
157
+ skill_configs: List of skill config dicts
158
+
159
+ Returns:
160
+ List of instantiated skills (non-None)
161
+ """
162
+ skills = []
163
+
164
+ for config in skill_configs:
165
+ skill = cls.create_skill(config)
166
+ if skill:
167
+ skills.append(skill)
168
+
169
+ logger.info(
170
+ "skills_created",
171
+ requested_count=len(skill_configs),
172
+ created_count=len(skills)
173
+ )
174
+
175
+ return skills