skilllite 0.1.1__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,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
+
skilllite/core/handler.py CHANGED
@@ -5,16 +5,22 @@ This module handles:
5
5
  - Parsing tool calls from LLM responses
6
6
  - Executing tool calls
7
7
  - Formatting tool results
8
+
9
+ Updated to support UnifiedExecutionService for consistent sandbox level handling.
8
10
  """
9
11
 
10
12
  import json
11
- from typing import Any, Dict, List, Optional, TYPE_CHECKING
13
+ from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
12
14
 
13
15
  from .executor import ExecutionResult, SkillExecutor
14
16
  from .tools import ToolResult, ToolUseRequest
15
17
 
16
18
  if TYPE_CHECKING:
17
19
  from .registry import SkillRegistry
20
+ from ..sandbox.execution_service import UnifiedExecutionService
21
+
22
+ # Type alias for confirmation callback
23
+ ConfirmationCallback = Callable[[str, str], bool]
18
24
 
19
25
 
20
26
  class ToolCallHandler:
@@ -95,7 +101,71 @@ class ToolCallHandler:
95
101
  allow_network=allow_network,
96
102
  timeout=timeout
97
103
  )
98
-
104
+
105
+ def execute_with_unified_service(
106
+ self,
107
+ skill_name: str,
108
+ input_data: Dict[str, Any],
109
+ confirmation_callback: Optional[ConfirmationCallback] = None,
110
+ allow_network: Optional[bool] = None,
111
+ timeout: Optional[int] = None
112
+ ) -> ExecutionResult:
113
+ """
114
+ Execute a skill using the UnifiedExecutionService.
115
+
116
+ This method uses the unified execution layer which:
117
+ 1. Reads sandbox level at runtime (not from instance variables)
118
+ 2. Handles security scanning and confirmation
119
+ 3. Properly downgrades sandbox level after confirmation
120
+
121
+ Args:
122
+ skill_name: Name of the skill or multi-script tool
123
+ input_data: Input data for the skill
124
+ confirmation_callback: Callback for security confirmation
125
+ allow_network: Whether to allow network access
126
+ timeout: Execution timeout in seconds
127
+
128
+ Returns:
129
+ ExecutionResult with output or error
130
+ """
131
+ from ..sandbox.execution_service import UnifiedExecutionService
132
+
133
+ service = UnifiedExecutionService.get_instance()
134
+
135
+ # Check if it's a multi-script tool
136
+ tool_info = self._registry.get_multi_script_tool_info(skill_name)
137
+ if tool_info:
138
+ parent_skill = self._registry.get_skill(tool_info["skill_name"])
139
+ if not parent_skill:
140
+ return ExecutionResult(
141
+ success=False,
142
+ error=f"Parent skill not found: {tool_info['skill_name']}"
143
+ )
144
+ return service.execute_skill(
145
+ skill_info=parent_skill,
146
+ input_data=input_data,
147
+ entry_point=tool_info["script_path"],
148
+ confirmation_callback=confirmation_callback,
149
+ allow_network=allow_network,
150
+ timeout=timeout,
151
+ )
152
+
153
+ # Regular skill execution
154
+ info = self._registry.get_skill(skill_name)
155
+ if not info:
156
+ return ExecutionResult(
157
+ success=False,
158
+ error=f"Skill not found: {skill_name}"
159
+ )
160
+
161
+ return service.execute_skill(
162
+ skill_info=info,
163
+ input_data=input_data,
164
+ confirmation_callback=confirmation_callback,
165
+ allow_network=allow_network,
166
+ timeout=timeout,
167
+ )
168
+
99
169
  def execute_tool_call(
100
170
  self,
101
171
  request: ToolUseRequest,
@@ -130,7 +200,45 @@ class ToolCallHandler:
130
200
  tool_use_id=request.id,
131
201
  error=result.error or "Unknown error"
132
202
  )
133
-
203
+
204
+ def execute_tool_call_with_unified_service(
205
+ self,
206
+ request: ToolUseRequest,
207
+ confirmation_callback: Optional[ConfirmationCallback] = None,
208
+ allow_network: Optional[bool] = None,
209
+ timeout: Optional[int] = None
210
+ ) -> ToolResult:
211
+ """
212
+ Execute a tool call using the UnifiedExecutionService.
213
+
214
+ Args:
215
+ request: Tool use request from LLM
216
+ confirmation_callback: Callback for security confirmation
217
+ allow_network: Whether to allow network access
218
+ timeout: Execution timeout in seconds
219
+
220
+ Returns:
221
+ ToolResult with success or error
222
+ """
223
+ result = self.execute_with_unified_service(
224
+ skill_name=request.name,
225
+ input_data=request.input,
226
+ confirmation_callback=confirmation_callback,
227
+ allow_network=allow_network,
228
+ timeout=timeout
229
+ )
230
+
231
+ if result.success:
232
+ return ToolResult.success(
233
+ tool_use_id=request.id,
234
+ content=result.output
235
+ )
236
+ else:
237
+ return ToolResult.error(
238
+ tool_use_id=request.id,
239
+ error=result.error or "Unknown error"
240
+ )
241
+
134
242
  # ==================== LLM Response Parsing ====================
135
243
 
136
244
  def parse_tool_calls(self, response: Any) -> List[ToolUseRequest]:
@@ -186,7 +294,74 @@ class ToolCallHandler:
186
294
  )
187
295
  results.append(result)
188
296
  return results
189
-
297
+
298
+ def handle_tool_calls_with_unified_service(
299
+ self,
300
+ response: Any,
301
+ confirmation_callback: Optional[ConfirmationCallback] = None,
302
+ allow_network: Optional[bool] = None,
303
+ timeout: Optional[int] = None
304
+ ) -> List[ToolResult]:
305
+ """
306
+ Parse and execute all tool calls using UnifiedExecutionService.
307
+
308
+ This method uses the unified execution layer which:
309
+ 1. Reads sandbox level at runtime
310
+ 2. Handles security scanning and confirmation per-skill
311
+ 3. Properly downgrades sandbox level after confirmation
312
+
313
+ Args:
314
+ response: Response from OpenAI-compatible API
315
+ confirmation_callback: Callback for security confirmation
316
+ allow_network: Whether to allow network access
317
+ timeout: Execution timeout in seconds
318
+
319
+ Returns:
320
+ List of ToolResult objects
321
+ """
322
+ requests = self.parse_tool_calls(response)
323
+ results = []
324
+ for request in requests:
325
+ result = self.execute_tool_call_with_unified_service(
326
+ request,
327
+ confirmation_callback=confirmation_callback,
328
+ allow_network=allow_network,
329
+ timeout=timeout
330
+ )
331
+ results.append(result)
332
+ return results
333
+
334
+ def handle_tool_calls_claude_native_with_unified_service(
335
+ self,
336
+ response: Any,
337
+ confirmation_callback: Optional[ConfirmationCallback] = None,
338
+ allow_network: Optional[bool] = None,
339
+ timeout: Optional[int] = None
340
+ ) -> List[ToolResult]:
341
+ """
342
+ Parse and execute all Claude tool calls using UnifiedExecutionService.
343
+
344
+ Args:
345
+ response: Response from Claude's native API
346
+ confirmation_callback: Callback for security confirmation
347
+ allow_network: Whether to allow network access
348
+ timeout: Execution timeout in seconds
349
+
350
+ Returns:
351
+ List of ToolResult objects
352
+ """
353
+ requests = self.parse_tool_calls_claude_native(response)
354
+ results = []
355
+ for request in requests:
356
+ result = self.execute_tool_call_with_unified_service(
357
+ request,
358
+ confirmation_callback=confirmation_callback,
359
+ allow_network=allow_network,
360
+ timeout=timeout
361
+ )
362
+ results.append(result)
363
+ return results
364
+
190
365
  def handle_tool_calls_claude_native(
191
366
  self,
192
367
  response: Any,