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.
- skilllite/__init__.py +1 -1
- skilllite/cli/__init__.py +19 -0
- skilllite/cli/__main__.py +10 -0
- skilllite/cli/binary.py +93 -0
- skilllite/cli/integrations/__init__.py +8 -0
- skilllite/cli/integrations/opencode.py +316 -0
- skilllite/cli/main.py +142 -0
- skilllite/cli/mcp.py +29 -0
- skilllite/core/__init__.py +2 -0
- skilllite/core/adapters/__init__.py +74 -0
- skilllite/core/adapters/langchain.py +362 -0
- skilllite/core/adapters/llamaindex.py +264 -0
- skilllite/core/handler.py +179 -4
- skilllite/core/loops.py +180 -15
- skilllite/core/manager.py +82 -15
- skilllite/core/metadata.py +14 -7
- skilllite/core/security.py +420 -0
- skilllite/mcp/server.py +537 -49
- skilllite/quick.py +14 -4
- skilllite/sandbox/context.py +155 -0
- skilllite/sandbox/execution_service.py +254 -0
- skilllite/sandbox/skillbox/executor.py +124 -19
- skilllite/sandbox/unified_executor.py +359 -0
- {skilllite-0.1.0.dist-info → skilllite-0.1.2.dist-info}/METADATA +151 -1
- skilllite-0.1.2.dist-info/RECORD +45 -0
- skilllite/cli.py +0 -217
- skilllite-0.1.0.dist-info/RECORD +0 -32
- {skilllite-0.1.0.dist-info → skilllite-0.1.2.dist-info}/WHEEL +0 -0
- {skilllite-0.1.0.dist-info → skilllite-0.1.2.dist-info}/entry_points.txt +0 -0
- {skilllite-0.1.0.dist-info → skilllite-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {skilllite-0.1.0.dist-info → skilllite-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
|