skilllite 0.1.0__py3-none-any.whl → 0.1.2__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.
@@ -0,0 +1,74 @@
1
+ """
2
+ SkillLite Adapters - Framework adapters for LangChain, LlamaIndex, etc.
3
+
4
+ This module provides adapters for integrating SkillLite with popular AI frameworks:
5
+ - LangChain: SkillLiteTool, SkillLiteToolkit
6
+ - LlamaIndex: SkillLiteToolSpec
7
+
8
+ Both adapters support sandbox security confirmation (sandbox_level=3):
9
+ - SecurityScanResult: Contains scan results with severity counts
10
+ - ConfirmationCallback: Type alias for (report: str, scan_id: str) -> bool
11
+
12
+ Usage:
13
+ # LangChain (requires: pip install skilllite[langchain])
14
+ from skilllite.core.adapters.langchain import SkillLiteTool, SkillLiteToolkit
15
+
16
+ # LlamaIndex (requires: pip install skilllite[llamaindex])
17
+ from skilllite.core.adapters.llamaindex import SkillLiteToolSpec
18
+
19
+ # Security confirmation callback
20
+ def confirm(report: str, scan_id: str) -> bool:
21
+ print(report)
22
+ return input("Continue? [y/N]: ").lower() == 'y'
23
+
24
+ toolkit = SkillLiteToolkit.from_manager(
25
+ manager, sandbox_level=3, confirmation_callback=confirm
26
+ )
27
+ """
28
+
29
+ __all__ = [
30
+ "SkillLiteTool",
31
+ "SkillLiteToolkit",
32
+ "SkillLiteToolSpec",
33
+ "SecurityScanResult",
34
+ "ConfirmationCallback",
35
+ "AsyncConfirmationCallback",
36
+ ]
37
+
38
+
39
+ def __getattr__(name: str):
40
+ """Lazy import to avoid requiring all dependencies at import time."""
41
+ if name in ("SkillLiteTool", "SkillLiteToolkit", "SecurityScanResult",
42
+ "ConfirmationCallback", "AsyncConfirmationCallback"):
43
+ try:
44
+ from .langchain import (
45
+ SkillLiteTool, SkillLiteToolkit, SecurityScanResult,
46
+ ConfirmationCallback, AsyncConfirmationCallback
47
+ )
48
+ return {
49
+ "SkillLiteTool": SkillLiteTool,
50
+ "SkillLiteToolkit": SkillLiteToolkit,
51
+ "SecurityScanResult": SecurityScanResult,
52
+ "ConfirmationCallback": ConfirmationCallback,
53
+ "AsyncConfirmationCallback": AsyncConfirmationCallback,
54
+ }[name]
55
+ except ImportError as e:
56
+ raise ImportError(
57
+ f"LangChain adapter requires langchain. "
58
+ f"Install with: pip install skilllite[langchain]\n"
59
+ f"Original error: {e}"
60
+ ) from e
61
+
62
+ if name == "SkillLiteToolSpec":
63
+ try:
64
+ from .llamaindex import SkillLiteToolSpec
65
+ return SkillLiteToolSpec
66
+ except ImportError as e:
67
+ raise ImportError(
68
+ f"LlamaIndex adapter requires llama-index. "
69
+ f"Install with: pip install skilllite[llamaindex]\n"
70
+ f"Original error: {e}"
71
+ ) from e
72
+
73
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
74
+
@@ -0,0 +1,362 @@
1
+ """
2
+ LangChain adapter for SkillLite.
3
+
4
+ Provides SkillLiteTool and SkillLiteToolkit for integrating SkillLite
5
+ skills into LangChain agents.
6
+
7
+ Usage:
8
+ from skilllite import SkillManager
9
+ from skilllite.core.adapters.langchain import SkillLiteToolkit
10
+
11
+ manager = SkillManager(skills_dir="./skills")
12
+ tools = SkillLiteToolkit.from_manager(manager)
13
+
14
+ # Use with LangChain agent
15
+ from langchain.agents import create_openai_tools_agent, AgentExecutor
16
+ agent = create_openai_tools_agent(llm, tools, prompt)
17
+ executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools)
18
+
19
+ Security Confirmation:
20
+ For sandbox level 3, the adapter supports security confirmation callbacks:
21
+
22
+ def my_confirmation_callback(security_report: str, scan_id: str) -> bool:
23
+ print(security_report)
24
+ return input("Continue? [y/N]: ").lower() == 'y'
25
+
26
+ tools = SkillLiteToolkit.from_manager(
27
+ manager,
28
+ sandbox_level=3,
29
+ confirmation_callback=my_confirmation_callback
30
+ )
31
+
32
+ Requirements:
33
+ pip install skilllite[langchain]
34
+ """
35
+
36
+ from dataclasses import dataclass, field
37
+ from typing import Any, Callable, Dict, List, Optional, Type, TYPE_CHECKING
38
+ import asyncio
39
+ import time
40
+
41
+ try:
42
+ from langchain_core.tools import BaseTool
43
+ from langchain_core.callbacks import CallbackManagerForToolRun, AsyncCallbackManagerForToolRun
44
+ from pydantic import BaseModel, Field, ConfigDict
45
+ except ImportError as e:
46
+ raise ImportError(
47
+ "LangChain adapter requires langchain-core. "
48
+ "Install with: pip install skilllite[langchain]"
49
+ ) from e
50
+
51
+ if TYPE_CHECKING:
52
+ from ..manager import SkillManager
53
+
54
+
55
+ # Type alias for confirmation callback
56
+ # Signature: (security_report: str, scan_id: str) -> bool
57
+ ConfirmationCallback = Callable[[str, str], bool]
58
+ AsyncConfirmationCallback = Callable[[str, str], "asyncio.Future[bool]"]
59
+
60
+
61
+ @dataclass
62
+ class SecurityScanResult:
63
+ """Result of a security scan for LangChain adapter."""
64
+
65
+ is_safe: bool
66
+ issues: List[Dict[str, Any]] = field(default_factory=list)
67
+ scan_id: str = ""
68
+ code_hash: str = ""
69
+ high_severity_count: int = 0
70
+ medium_severity_count: int = 0
71
+ low_severity_count: int = 0
72
+ timestamp: float = field(default_factory=time.time)
73
+
74
+ @property
75
+ def requires_confirmation(self) -> bool:
76
+ """Check if user confirmation is required."""
77
+ return self.high_severity_count > 0
78
+
79
+ def to_dict(self) -> Dict[str, Any]:
80
+ return {
81
+ "is_safe": self.is_safe,
82
+ "issues": self.issues,
83
+ "scan_id": self.scan_id,
84
+ "code_hash": self.code_hash,
85
+ "high_severity_count": self.high_severity_count,
86
+ "medium_severity_count": self.medium_severity_count,
87
+ "low_severity_count": self.low_severity_count,
88
+ "requires_confirmation": self.requires_confirmation,
89
+ }
90
+
91
+ def format_report(self) -> str:
92
+ """Format a human-readable security report."""
93
+ if not self.issues:
94
+ return "✅ Security scan passed. No issues found."
95
+
96
+ lines = [
97
+ f"📋 Security Scan Report (ID: {self.scan_id[:8]})",
98
+ f" Found {len(self.issues)} item(s) for review:",
99
+ "",
100
+ ]
101
+
102
+ severity_icons = {
103
+ "Critical": "🔴",
104
+ "High": "🟠",
105
+ "Medium": "🟡",
106
+ "Low": "🟢",
107
+ }
108
+
109
+ for idx, issue in enumerate(self.issues, 1):
110
+ severity = issue.get("severity", "Medium")
111
+ icon = severity_icons.get(severity, "⚪")
112
+ lines.append(f" {icon} #{idx} [{severity}] {issue.get('issue_type', 'Unknown')}")
113
+ lines.append(f" ├─ Rule: {issue.get('rule_id', 'N/A')}")
114
+ lines.append(f" ├─ Line {issue.get('line_number', '?')}: {issue.get('description', '')}")
115
+ snippet = issue.get('code_snippet', '')
116
+ lines.append(f" └─ Code: {snippet[:60]}{'...' if len(snippet) > 60 else ''}")
117
+ lines.append("")
118
+
119
+ if self.high_severity_count > 0:
120
+ lines.append("⚠️ High severity issues found. Confirmation required to execute.")
121
+ else:
122
+ lines.append("ℹ️ Only low/medium severity issues found. Safe to execute.")
123
+
124
+ return "\n".join(lines)
125
+
126
+
127
+ class SkillLiteTool(BaseTool):
128
+ """
129
+ LangChain BaseTool adapter for a single SkillLite skill.
130
+
131
+ This wraps a SkillLite skill as a LangChain tool, enabling it to be
132
+ used with LangChain agents.
133
+
134
+ Attributes:
135
+ name: Tool name (same as skill name)
136
+ description: Tool description
137
+ manager: SkillManager instance
138
+ skill_name: Name of the skill to execute
139
+ allow_network: Whether to allow network access
140
+ timeout: Execution timeout in seconds
141
+ sandbox_level: Sandbox security level (1/2/3, default: 3)
142
+ confirmation_callback: Callback for security confirmation (sync)
143
+ async_confirmation_callback: Callback for security confirmation (async)
144
+ """
145
+
146
+ name: str = Field(description="Tool name")
147
+ description: str = Field(description="Tool description")
148
+ args_schema: Optional[Type[BaseModel]] = Field(default=None, description="Pydantic schema for arguments")
149
+
150
+ # SkillLite specific fields
151
+ manager: Any = Field(exclude=True) # SkillManager instance
152
+ skill_name: str = Field(description="SkillLite skill name")
153
+ allow_network: bool = Field(default=False, description="Allow network access")
154
+ timeout: Optional[int] = Field(default=None, description="Execution timeout in seconds")
155
+
156
+ # Security confirmation fields
157
+ sandbox_level: int = Field(default=3, description="Sandbox security level (1/2/3)")
158
+ confirmation_callback: Optional[Any] = Field(
159
+ default=None,
160
+ exclude=True,
161
+ description="Sync callback for security confirmation: (report: str, scan_id: str) -> bool"
162
+ )
163
+ async_confirmation_callback: Optional[Any] = Field(
164
+ default=None,
165
+ exclude=True,
166
+ description="Async callback for security confirmation: (report: str, scan_id: str) -> Future[bool]"
167
+ )
168
+
169
+ model_config = ConfigDict(arbitrary_types_allowed=True)
170
+
171
+ def _run(
172
+ self,
173
+ run_manager: Optional[CallbackManagerForToolRun] = None,
174
+ **kwargs: Any
175
+ ) -> str:
176
+ """
177
+ Execute the skill synchronously using UnifiedExecutionService.
178
+
179
+ This method uses the unified execution layer which:
180
+ 1. Reads sandbox level at runtime
181
+ 2. Handles security scanning and confirmation
182
+ 3. Properly downgrades sandbox level after confirmation
183
+ """
184
+ try:
185
+ # Get skill info
186
+ skill_info = self.manager._registry.get_skill(self.skill_name)
187
+ if not skill_info:
188
+ return f"Error: Skill '{self.skill_name}' not found"
189
+
190
+ # Use UnifiedExecutionService
191
+ from ...sandbox.execution_service import UnifiedExecutionService
192
+
193
+ service = UnifiedExecutionService.get_instance()
194
+ result = service.execute_skill(
195
+ skill_info=skill_info,
196
+ input_data=kwargs,
197
+ confirmation_callback=self.confirmation_callback,
198
+ allow_network=self.allow_network,
199
+ timeout=self.timeout,
200
+ )
201
+
202
+ if result.success:
203
+ return result.output or "Execution completed successfully"
204
+ else:
205
+ return f"Error: {result.error}"
206
+ except Exception as e:
207
+ return f"Execution failed: {str(e)}"
208
+
209
+ async def _arun(
210
+ self,
211
+ run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
212
+ **kwargs: Any
213
+ ) -> str:
214
+ """
215
+ Execute the skill asynchronously using UnifiedExecutionService.
216
+
217
+ This method uses the unified execution layer which:
218
+ 1. Reads sandbox level at runtime
219
+ 2. Handles security scanning and confirmation
220
+ 3. Properly downgrades sandbox level after confirmation
221
+ """
222
+ try:
223
+ # Get skill info
224
+ skill_info = self.manager._registry.get_skill(self.skill_name)
225
+ if not skill_info:
226
+ return f"Error: Skill '{self.skill_name}' not found"
227
+
228
+ # Use UnifiedExecutionService in thread pool
229
+ from ...sandbox.execution_service import UnifiedExecutionService
230
+
231
+ def execute_sync():
232
+ service = UnifiedExecutionService.get_instance()
233
+ # Use async confirmation callback if available, otherwise sync
234
+ callback = self.confirmation_callback
235
+ return service.execute_skill(
236
+ skill_info=skill_info,
237
+ input_data=kwargs,
238
+ confirmation_callback=callback,
239
+ allow_network=self.allow_network,
240
+ timeout=self.timeout,
241
+ )
242
+
243
+ result = await asyncio.to_thread(execute_sync)
244
+
245
+ if result.success:
246
+ return result.output or "Execution completed successfully"
247
+ else:
248
+ return f"Error: {result.error}"
249
+ except Exception as e:
250
+ return f"Execution failed: {str(e)}"
251
+
252
+
253
+ class SkillLiteToolkit:
254
+ """
255
+ LangChain Toolkit for SkillLite.
256
+
257
+ Provides a convenient way to create LangChain tools from all skills
258
+ registered in a SkillManager.
259
+
260
+ Usage:
261
+ manager = SkillManager(skills_dir="./skills")
262
+ tools = SkillLiteToolkit.from_manager(manager)
263
+
264
+ # Or with options
265
+ tools = SkillLiteToolkit.from_manager(
266
+ manager,
267
+ skill_names=["calculator", "web_search"], # Only specific skills
268
+ allow_network=True,
269
+ timeout=60
270
+ )
271
+
272
+ # With security confirmation callback (for sandbox level 3)
273
+ def confirm_execution(report: str, scan_id: str) -> bool:
274
+ print(report)
275
+ return input("Continue? [y/N]: ").lower() == 'y'
276
+
277
+ tools = SkillLiteToolkit.from_manager(
278
+ manager,
279
+ sandbox_level=3,
280
+ confirmation_callback=confirm_execution
281
+ )
282
+ """
283
+
284
+ @staticmethod
285
+ def from_manager(
286
+ manager: "SkillManager",
287
+ skill_names: Optional[List[str]] = None,
288
+ allow_network: bool = False,
289
+ timeout: Optional[int] = None,
290
+ sandbox_level: int = 3,
291
+ confirmation_callback: Optional[ConfirmationCallback] = None,
292
+ async_confirmation_callback: Optional[AsyncConfirmationCallback] = None,
293
+ ) -> List[SkillLiteTool]:
294
+ """
295
+ Create LangChain tools from a SkillManager.
296
+
297
+ Args:
298
+ manager: SkillManager instance with registered skills
299
+ skill_names: Optional list of skill names to include (default: all)
300
+ allow_network: Whether to allow network access for all tools
301
+ timeout: Execution timeout in seconds for all tools
302
+ sandbox_level: Sandbox security level (1/2/3, default: 3)
303
+ - Level 1: No sandbox - direct execution
304
+ - Level 2: Sandbox isolation only
305
+ - Level 3: Sandbox isolation + security scanning (requires confirmation for high-severity issues)
306
+ confirmation_callback: Sync callback for security confirmation.
307
+ Signature: (security_report: str, scan_id: str) -> bool
308
+ Return True to proceed, False to cancel.
309
+ async_confirmation_callback: Async callback for security confirmation.
310
+ Signature: (security_report: str, scan_id: str) -> Future[bool]
311
+ Return True to proceed, False to cancel.
312
+
313
+ Returns:
314
+ List of SkillLiteTool instances
315
+
316
+ Example with confirmation callback:
317
+ def my_callback(report: str, scan_id: str) -> bool:
318
+ print(f"Security Report:\\n{report}")
319
+ response = input("Proceed with execution? [y/N]: ")
320
+ return response.lower() == 'y'
321
+
322
+ tools = SkillLiteToolkit.from_manager(
323
+ manager,
324
+ sandbox_level=3,
325
+ confirmation_callback=my_callback
326
+ )
327
+ """
328
+ tools = []
329
+
330
+ # Get executable skills
331
+ skills = manager.list_executable_skills()
332
+
333
+ for skill in skills:
334
+ # Filter by name if specified
335
+ if skill_names and skill.name not in skill_names:
336
+ continue
337
+
338
+ # Create tool with security confirmation support
339
+ tool = SkillLiteTool(
340
+ name=skill.name,
341
+ description=skill.description or f"Execute the {skill.name} skill",
342
+ manager=manager,
343
+ skill_name=skill.name,
344
+ allow_network=allow_network,
345
+ timeout=timeout,
346
+ sandbox_level=sandbox_level,
347
+ confirmation_callback=confirmation_callback,
348
+ async_confirmation_callback=async_confirmation_callback,
349
+ )
350
+ tools.append(tool)
351
+
352
+ return tools
353
+
354
+
355
+ __all__ = [
356
+ "SkillLiteTool",
357
+ "SkillLiteToolkit",
358
+ "SecurityScanResult",
359
+ "ConfirmationCallback",
360
+ "AsyncConfirmationCallback",
361
+ ]
362
+
@@ -0,0 +1,264 @@
1
+ """
2
+ LlamaIndex adapter for SkillLite.
3
+
4
+ Provides SkillLiteToolSpec for integrating SkillLite skills into LlamaIndex agents.
5
+
6
+ Usage:
7
+ from skilllite import SkillManager
8
+ from skilllite.core.adapters.llamaindex import SkillLiteToolSpec
9
+
10
+ manager = SkillManager(skills_dir="./skills")
11
+ tool_spec = SkillLiteToolSpec.from_manager(manager)
12
+ tools = tool_spec.to_tool_list()
13
+
14
+ # Use with LlamaIndex agent
15
+ from llama_index.core.agent import ReActAgent
16
+ agent = ReActAgent.from_tools(tools, llm=llm, verbose=True)
17
+
18
+ Security Confirmation:
19
+ For sandbox level 3, the adapter supports security confirmation callbacks:
20
+
21
+ def my_confirmation_callback(security_report: str, scan_id: str) -> bool:
22
+ print(security_report)
23
+ return input("Continue? [y/N]: ").lower() == 'y'
24
+
25
+ tool_spec = SkillLiteToolSpec.from_manager(
26
+ manager,
27
+ sandbox_level=3,
28
+ confirmation_callback=my_confirmation_callback
29
+ )
30
+
31
+ Requirements:
32
+ pip install skilllite[llamaindex]
33
+ """
34
+
35
+ from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
36
+
37
+ try:
38
+ from llama_index.core.tools import FunctionTool, ToolMetadata
39
+ from llama_index.core.tools.types import BaseTool as LlamaBaseTool
40
+ except ImportError as e:
41
+ raise ImportError(
42
+ "LlamaIndex adapter requires llama-index. "
43
+ "Install with: pip install skilllite[llamaindex]"
44
+ ) from e
45
+
46
+ if TYPE_CHECKING:
47
+ from ..manager import SkillManager
48
+
49
+
50
+ # Type alias for confirmation callback
51
+ # Signature: (security_report: str, scan_id: str) -> bool
52
+ ConfirmationCallback = Callable[[str, str], bool]
53
+
54
+
55
+ # Import SecurityScanResult from langchain adapter to share the implementation
56
+ # This avoids code duplication
57
+ try:
58
+ from .langchain import SecurityScanResult
59
+ except ImportError:
60
+ # Fallback: define a minimal SecurityScanResult if langchain adapter not available
61
+ from dataclasses import dataclass, field
62
+
63
+ @dataclass
64
+ class SecurityScanResult:
65
+ """Result of a security scan for LlamaIndex adapter."""
66
+
67
+ is_safe: bool
68
+ issues: List[Dict[str, Any]] = field(default_factory=list)
69
+ scan_id: str = ""
70
+ code_hash: str = ""
71
+ high_severity_count: int = 0
72
+ medium_severity_count: int = 0
73
+ low_severity_count: int = 0
74
+ timestamp: float = field(default_factory=time.time)
75
+
76
+ @property
77
+ def requires_confirmation(self) -> bool:
78
+ return self.high_severity_count > 0
79
+
80
+ def format_report(self) -> str:
81
+ if not self.issues:
82
+ return "✅ Security scan passed. No issues found."
83
+
84
+ lines = [
85
+ f"📋 Security Scan Report (ID: {self.scan_id[:8]})",
86
+ f" Found {len(self.issues)} item(s) for review:",
87
+ "",
88
+ ]
89
+
90
+ severity_icons = {"Critical": "🔴", "High": "🟠", "Medium": "🟡", "Low": "🟢"}
91
+
92
+ for idx, issue in enumerate(self.issues, 1):
93
+ severity = issue.get("severity", "Medium")
94
+ icon = severity_icons.get(severity, "⚪")
95
+ lines.append(f" {icon} #{idx} [{severity}] {issue.get('issue_type', 'Unknown')}")
96
+ lines.append(f" └─ {issue.get('description', '')}")
97
+
98
+ if self.high_severity_count > 0:
99
+ lines.append("\n⚠️ High severity issues found. Confirmation required.")
100
+
101
+ return "\n".join(lines)
102
+
103
+
104
+ class SkillLiteToolSpec:
105
+ """
106
+ LlamaIndex ToolSpec for SkillLite.
107
+
108
+ Provides a way to create LlamaIndex tools from SkillLite skills.
109
+
110
+ Usage:
111
+ manager = SkillManager(skills_dir="./skills")
112
+ tool_spec = SkillLiteToolSpec.from_manager(manager)
113
+ tools = tool_spec.to_tool_list()
114
+
115
+ # Use with ReActAgent
116
+ agent = ReActAgent.from_tools(tools, llm=llm)
117
+ response = agent.chat("Your query")
118
+
119
+ Security Confirmation (sandbox_level=3):
120
+ def confirm(report: str, scan_id: str) -> bool:
121
+ print(report)
122
+ return input("Continue? [y/N]: ").lower() == 'y'
123
+
124
+ tool_spec = SkillLiteToolSpec.from_manager(
125
+ manager, sandbox_level=3, confirmation_callback=confirm
126
+ )
127
+ """
128
+
129
+ def __init__(
130
+ self,
131
+ manager: "SkillManager",
132
+ skill_names: Optional[List[str]] = None,
133
+ allow_network: bool = False,
134
+ timeout: Optional[int] = None,
135
+ sandbox_level: int = 3,
136
+ confirmation_callback: Optional[ConfirmationCallback] = None
137
+ ):
138
+ """
139
+ Initialize SkillLiteToolSpec.
140
+
141
+ Args:
142
+ manager: SkillManager instance with registered skills
143
+ skill_names: Optional list of skill names to include (default: all)
144
+ allow_network: Whether to allow network access
145
+ timeout: Execution timeout in seconds
146
+ sandbox_level: Sandbox security level (1=no sandbox, 2=sandbox only, 3=sandbox+scan)
147
+ confirmation_callback: Callback for security confirmation (report, scan_id) -> bool
148
+ """
149
+ self.manager = manager
150
+ self.skill_names = skill_names
151
+ self.allow_network = allow_network
152
+ self.timeout = timeout
153
+ self.sandbox_level = sandbox_level
154
+ self.confirmation_callback = confirmation_callback
155
+
156
+ @classmethod
157
+ def from_manager(
158
+ cls,
159
+ manager: "SkillManager",
160
+ skill_names: Optional[List[str]] = None,
161
+ allow_network: bool = False,
162
+ timeout: Optional[int] = None,
163
+ sandbox_level: int = 3,
164
+ confirmation_callback: Optional[ConfirmationCallback] = None
165
+ ) -> "SkillLiteToolSpec":
166
+ """
167
+ Create a SkillLiteToolSpec from a SkillManager.
168
+
169
+ Args:
170
+ manager: SkillManager instance
171
+ skill_names: Optional list of skill names to include
172
+ allow_network: Whether to allow network access
173
+ timeout: Execution timeout in seconds
174
+ sandbox_level: Sandbox security level (1/2/3, default: 3)
175
+ confirmation_callback: Callback for security confirmation
176
+
177
+ Returns:
178
+ SkillLiteToolSpec instance
179
+ """
180
+ return cls(
181
+ manager=manager,
182
+ skill_names=skill_names,
183
+ allow_network=allow_network,
184
+ timeout=timeout,
185
+ sandbox_level=sandbox_level,
186
+ confirmation_callback=confirmation_callback
187
+ )
188
+
189
+ def _create_skill_function(self, skill_name: str):
190
+ """
191
+ Create a callable function for a skill using UnifiedExecutionService.
192
+
193
+ This method uses the unified execution layer which:
194
+ 1. Reads sandbox level at runtime
195
+ 2. Handles security scanning and confirmation
196
+ 3. Properly downgrades sandbox level after confirmation
197
+ """
198
+ def skill_fn(**kwargs) -> str:
199
+ try:
200
+ # Get skill info
201
+ skill_info = self.manager.get_skill(skill_name)
202
+ if not skill_info:
203
+ return f"Error: Skill '{skill_name}' not found"
204
+
205
+ # Use UnifiedExecutionService
206
+ from ...sandbox.execution_service import UnifiedExecutionService
207
+
208
+ service = UnifiedExecutionService.get_instance()
209
+ result = service.execute_skill(
210
+ skill_info=skill_info,
211
+ input_data=kwargs,
212
+ confirmation_callback=self.confirmation_callback,
213
+ allow_network=self.allow_network,
214
+ timeout=self.timeout,
215
+ )
216
+
217
+ if result.success:
218
+ return result.output or "Execution completed successfully"
219
+ else:
220
+ return f"Error: {result.error}"
221
+ except Exception as e:
222
+ return f"Execution failed: {str(e)}"
223
+
224
+ return skill_fn
225
+
226
+ def to_tool_list(self) -> List[LlamaBaseTool]:
227
+ """
228
+ Convert all skills to a list of LlamaIndex tools.
229
+
230
+ Returns:
231
+ List of FunctionTool instances
232
+ """
233
+ tools = []
234
+
235
+ # Get executable skills
236
+ skills = self.manager.list_executable_skills()
237
+
238
+ for skill in skills:
239
+ # Filter by name if specified
240
+ if self.skill_names and skill.name not in self.skill_names:
241
+ continue
242
+
243
+ # Create function for this skill
244
+ fn = self._create_skill_function(skill.name)
245
+
246
+ # Create tool metadata
247
+ metadata = ToolMetadata(
248
+ name=skill.name,
249
+ description=skill.description or f"Execute the {skill.name} skill"
250
+ )
251
+
252
+ # Create FunctionTool
253
+ tool = FunctionTool.from_defaults(
254
+ fn=fn,
255
+ name=skill.name,
256
+ description=skill.description or f"Execute the {skill.name} skill"
257
+ )
258
+ tools.append(tool)
259
+
260
+ return tools
261
+
262
+
263
+ __all__ = ["SkillLiteToolSpec", "SecurityScanResult", "ConfirmationCallback"]
264
+