skilllite 0.1.0__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 +159 -0
- skilllite/analyzer.py +391 -0
- skilllite/builtin_tools.py +240 -0
- skilllite/cli.py +217 -0
- skilllite/core/__init__.py +65 -0
- skilllite/core/executor.py +182 -0
- skilllite/core/handler.py +332 -0
- skilllite/core/loops.py +770 -0
- skilllite/core/manager.py +507 -0
- skilllite/core/metadata.py +338 -0
- skilllite/core/prompt_builder.py +321 -0
- skilllite/core/registry.py +185 -0
- skilllite/core/skill_info.py +181 -0
- skilllite/core/tool_builder.py +338 -0
- skilllite/core/tools.py +253 -0
- skilllite/mcp/__init__.py +45 -0
- skilllite/mcp/server.py +734 -0
- skilllite/quick.py +420 -0
- skilllite/sandbox/__init__.py +36 -0
- skilllite/sandbox/base.py +93 -0
- skilllite/sandbox/config.py +229 -0
- skilllite/sandbox/skillbox/__init__.py +44 -0
- skilllite/sandbox/skillbox/binary.py +421 -0
- skilllite/sandbox/skillbox/executor.py +608 -0
- skilllite/sandbox/utils.py +77 -0
- skilllite/validation.py +137 -0
- skilllite-0.1.0.dist-info/METADATA +293 -0
- skilllite-0.1.0.dist-info/RECORD +32 -0
- skilllite-0.1.0.dist-info/WHEEL +5 -0
- skilllite-0.1.0.dist-info/entry_points.txt +3 -0
- skilllite-0.1.0.dist-info/licenses/LICENSE +21 -0
- skilllite-0.1.0.dist-info/top_level.txt +1 -0
skilllite/quick.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SkillLite Quick Start - Minimal wrapper for running Skills with one line of code.
|
|
3
|
+
|
|
4
|
+
Provides out-of-the-box convenience functions without manual LLM calls and tool calls handling.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
from skilllite import quick_run
|
|
9
|
+
|
|
10
|
+
# Run with one line of code
|
|
11
|
+
result = quick_run("Calculate 15 times 27 for me")
|
|
12
|
+
print(result)
|
|
13
|
+
```
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
19
|
+
|
|
20
|
+
from .core import SkillManager, AgenticLoop
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_env(env_file: Optional[Union[str, Path]] = None) -> Dict[str, str]:
|
|
24
|
+
"""
|
|
25
|
+
Load .env file into environment variables.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
env_file: Path to .env file, defaults to .env in current directory
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary of loaded environment variables
|
|
32
|
+
"""
|
|
33
|
+
if env_file is None:
|
|
34
|
+
env_file = Path.cwd() / ".env"
|
|
35
|
+
else:
|
|
36
|
+
env_file = Path(env_file)
|
|
37
|
+
|
|
38
|
+
loaded = {}
|
|
39
|
+
if env_file.exists():
|
|
40
|
+
for line in env_file.read_text().splitlines():
|
|
41
|
+
line = line.strip()
|
|
42
|
+
if line and not line.startswith("#") and "=" in line:
|
|
43
|
+
key, value = line.split("=", 1)
|
|
44
|
+
key, value = key.strip(), value.strip()
|
|
45
|
+
if value:
|
|
46
|
+
os.environ.setdefault(key, value)
|
|
47
|
+
loaded[key] = value
|
|
48
|
+
return loaded
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SkillRunner:
|
|
52
|
+
"""
|
|
53
|
+
Minimal Skill Runner - Encapsulates all initialization and invocation logic.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
```python
|
|
57
|
+
from skilllite import SkillRunner
|
|
58
|
+
|
|
59
|
+
# Method 1: Use .env configuration
|
|
60
|
+
runner = SkillRunner()
|
|
61
|
+
result = runner.run("Calculate 15 times 27 for me")
|
|
62
|
+
|
|
63
|
+
# Method 2: Explicitly pass configuration
|
|
64
|
+
runner = SkillRunner(
|
|
65
|
+
base_url="https://api.deepseek.com",
|
|
66
|
+
api_key="sk-xxx",
|
|
67
|
+
model="deepseek-chat",
|
|
68
|
+
skills_dir="./.skills"
|
|
69
|
+
)
|
|
70
|
+
result = runner.run("Calculate 15 times 27 for me")
|
|
71
|
+
```
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
base_url: Optional[str] = None,
|
|
77
|
+
api_key: Optional[str] = None,
|
|
78
|
+
model: Optional[str] = None,
|
|
79
|
+
skills_dir: Optional[Union[str, Path]] = None,
|
|
80
|
+
env_file: Optional[Union[str, Path]] = None,
|
|
81
|
+
include_full_instructions: bool = True,
|
|
82
|
+
include_references: bool = True,
|
|
83
|
+
include_assets: bool = True,
|
|
84
|
+
context_mode: str = "full",
|
|
85
|
+
max_tokens_per_skill: Optional[int] = None,
|
|
86
|
+
max_iterations: int = 10,
|
|
87
|
+
verbose: bool = False,
|
|
88
|
+
custom_tools: Optional[List[Dict[str, Any]]] = None,
|
|
89
|
+
custom_tool_executor: Optional[Callable] = None,
|
|
90
|
+
use_enhanced_loop: bool = True,
|
|
91
|
+
enable_builtin_tools: bool = True,
|
|
92
|
+
allow_network: Optional[bool] = None,
|
|
93
|
+
enable_sandbox: Optional[bool] = None,
|
|
94
|
+
execution_timeout: Optional[int] = None,
|
|
95
|
+
max_memory_mb: Optional[int] = None
|
|
96
|
+
):
|
|
97
|
+
"""
|
|
98
|
+
Initialize SkillRunner.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
base_url: LLM API URL, defaults to BASE_URL environment variable
|
|
102
|
+
api_key: API key, defaults to API_KEY environment variable
|
|
103
|
+
model: Model name, defaults to MODEL environment variable or "deepseek-chat"
|
|
104
|
+
skills_dir: Skills directory, defaults to "./.skills"
|
|
105
|
+
env_file: Path to .env file, defaults to .env in current directory
|
|
106
|
+
include_full_instructions: Whether to include full SKILL.md in system prompt (legacy)
|
|
107
|
+
include_references: Whether to include references directory content
|
|
108
|
+
include_assets: Whether to include assets directory content
|
|
109
|
+
context_mode: System prompt mode:
|
|
110
|
+
- "summary": Most minimal, only name, description and brief summary
|
|
111
|
+
- "standard": Balanced mode, includes input_schema and usage summary
|
|
112
|
+
- "full": Full mode, includes complete SKILL.md content
|
|
113
|
+
- "progressive": Progressive, summary + on-demand detail prompts
|
|
114
|
+
max_tokens_per_skill: Maximum tokens per skill (for truncation)
|
|
115
|
+
max_iterations: Maximum tool call iterations
|
|
116
|
+
verbose: Whether to output detailed logs
|
|
117
|
+
custom_tools: Custom tools list (e.g., file operation tools)
|
|
118
|
+
custom_tool_executor: Custom tool executor function
|
|
119
|
+
use_enhanced_loop: Whether to use enhanced AgenticLoop (default: True)
|
|
120
|
+
enable_builtin_tools: Whether to enable built-in file operation tools (default: True)
|
|
121
|
+
allow_network: Whether to allow skill network access (defaults from .env or False)
|
|
122
|
+
enable_sandbox: Whether to enable sandbox protection (defaults from .env or True)
|
|
123
|
+
execution_timeout: Skill execution timeout in seconds (defaults from .env or 120)
|
|
124
|
+
max_memory_mb: Maximum memory limit in MB (defaults from .env or 512)
|
|
125
|
+
"""
|
|
126
|
+
# Load .env
|
|
127
|
+
load_env(env_file)
|
|
128
|
+
|
|
129
|
+
# Configuration
|
|
130
|
+
self.base_url = base_url or os.environ.get("BASE_URL")
|
|
131
|
+
self.api_key = api_key or os.environ.get("API_KEY")
|
|
132
|
+
self.model = model or os.environ.get("MODEL", "deepseek-chat")
|
|
133
|
+
self.skills_dir = skills_dir or "./.skills"
|
|
134
|
+
self.include_full_instructions = include_full_instructions
|
|
135
|
+
self.include_references = include_references
|
|
136
|
+
self.include_assets = include_assets
|
|
137
|
+
self.context_mode = context_mode
|
|
138
|
+
self.max_tokens_per_skill = max_tokens_per_skill
|
|
139
|
+
self.max_iterations = max_iterations
|
|
140
|
+
self.verbose = verbose
|
|
141
|
+
self.enable_builtin_tools = enable_builtin_tools
|
|
142
|
+
|
|
143
|
+
# Sandbox and network configuration (read from .env or use defaults)
|
|
144
|
+
self.allow_network = allow_network if allow_network is not None else \
|
|
145
|
+
(os.environ.get("ALLOW_NETWORK", "false").lower() == "true")
|
|
146
|
+
self.enable_sandbox = enable_sandbox if enable_sandbox is not None else \
|
|
147
|
+
(os.environ.get("ENABLE_SANDBOX", "true").lower() == "true")
|
|
148
|
+
self.execution_timeout = execution_timeout or \
|
|
149
|
+
int(os.environ.get("EXECUTION_TIMEOUT", "120"))
|
|
150
|
+
self.max_memory_mb = max_memory_mb or \
|
|
151
|
+
int(os.environ.get("MAX_MEMORY_MB", "512"))
|
|
152
|
+
# Read sandbox security level (from .env or default to 3)
|
|
153
|
+
self.sandbox_level = os.environ.get("SKILLBOX_SANDBOX_LEVEL", "3")
|
|
154
|
+
|
|
155
|
+
# Merge built-in tools and custom tools
|
|
156
|
+
self.custom_tools = custom_tools or []
|
|
157
|
+
if enable_builtin_tools:
|
|
158
|
+
from .builtin_tools import get_builtin_file_tools
|
|
159
|
+
builtin_tools = get_builtin_file_tools()
|
|
160
|
+
self.custom_tools = builtin_tools + self.custom_tools
|
|
161
|
+
|
|
162
|
+
self.custom_tool_executor = custom_tool_executor
|
|
163
|
+
self.use_enhanced_loop = use_enhanced_loop
|
|
164
|
+
|
|
165
|
+
# Lazy initialization
|
|
166
|
+
self._client = None
|
|
167
|
+
self._manager = None
|
|
168
|
+
self._system_context = None
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def client(self):
|
|
172
|
+
"""Get OpenAI client (lazy initialization)"""
|
|
173
|
+
if self._client is None:
|
|
174
|
+
from openai import OpenAI
|
|
175
|
+
self._client = OpenAI(base_url=self.base_url, api_key=self.api_key)
|
|
176
|
+
return self._client
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def manager(self) -> SkillManager:
|
|
180
|
+
"""Get SkillManager (lazy initialization)"""
|
|
181
|
+
if self._manager is None:
|
|
182
|
+
self._manager = SkillManager(
|
|
183
|
+
skills_dir=self.skills_dir,
|
|
184
|
+
allow_network=self.allow_network,
|
|
185
|
+
enable_sandbox=self.enable_sandbox,
|
|
186
|
+
execution_timeout=self.execution_timeout,
|
|
187
|
+
max_memory_mb=self.max_memory_mb,
|
|
188
|
+
sandbox_level=self.sandbox_level
|
|
189
|
+
)
|
|
190
|
+
if self.verbose:
|
|
191
|
+
print(f"📦 Loaded Skills: {self._manager.skill_names()}")
|
|
192
|
+
return self._manager
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def system_context(self) -> str:
|
|
196
|
+
"""Get system prompt context"""
|
|
197
|
+
if self._system_context is None:
|
|
198
|
+
# Basic skill context, using new mode parameter
|
|
199
|
+
skill_context = self.manager.get_system_prompt_context(
|
|
200
|
+
include_full_instructions=self.include_full_instructions,
|
|
201
|
+
include_references=self.include_references,
|
|
202
|
+
include_assets=self.include_assets,
|
|
203
|
+
mode=self.context_mode,
|
|
204
|
+
max_tokens_per_skill=self.max_tokens_per_skill
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Add tool calling guidance
|
|
208
|
+
tool_guidance = """
|
|
209
|
+
# Tool Calling Guidelines
|
|
210
|
+
|
|
211
|
+
When calling tools, follow these rules:
|
|
212
|
+
|
|
213
|
+
1. **Sequential Dependencies**: If a task depends on the result of a previous task, you MUST wait for the previous tool call to complete before making the next one. Do NOT use placeholders like `<result_of_xxx>` - always use actual values.
|
|
214
|
+
|
|
215
|
+
2. **Parallel Independence**: If multiple tasks are independent of each other, you can call them in parallel in a single turn.
|
|
216
|
+
|
|
217
|
+
3. **Always Use Real Values**: Tool parameters must be concrete values (numbers, strings, etc.), never references to other tool results.
|
|
218
|
+
|
|
219
|
+
Example of WRONG approach (don't do this):
|
|
220
|
+
- Task: "Calculate 100+200, then multiply by 3"
|
|
221
|
+
- Wrong: Call calculator(add, 100, 200) AND calculator(multiply, <result>, 3) in same turn
|
|
222
|
+
|
|
223
|
+
Example of CORRECT approach:
|
|
224
|
+
- Turn 1: Call calculator(add, 100, 200) → get result 300
|
|
225
|
+
- Turn 2: Call calculator(multiply, 300, 3) → get result 900
|
|
226
|
+
|
|
227
|
+
"""
|
|
228
|
+
self._system_context = tool_guidance + skill_context
|
|
229
|
+
|
|
230
|
+
if self.verbose:
|
|
231
|
+
estimated_tokens = self.manager.estimate_context_tokens(
|
|
232
|
+
mode=self.context_mode,
|
|
233
|
+
include_references=self.include_references,
|
|
234
|
+
include_assets=self.include_assets
|
|
235
|
+
)
|
|
236
|
+
print(f"📊 System Prompt estimated tokens: ~{estimated_tokens}")
|
|
237
|
+
return self._system_context
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def tools(self) -> List[Dict[str, Any]]:
|
|
241
|
+
"""Get tool definitions list"""
|
|
242
|
+
return self.manager.get_tools()
|
|
243
|
+
|
|
244
|
+
def run(self, user_message: str, stream: bool = False) -> str:
|
|
245
|
+
"""
|
|
246
|
+
Run Skill and return final result.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
user_message: User input message
|
|
250
|
+
stream: Whether to use streaming output (not supported yet)
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Final response content from LLM
|
|
254
|
+
"""
|
|
255
|
+
if self.verbose:
|
|
256
|
+
print(f"👤 User: {user_message}")
|
|
257
|
+
print(f"⏳ Calling LLM...")
|
|
258
|
+
|
|
259
|
+
# Prepare tool executor
|
|
260
|
+
tool_executor = self.custom_tool_executor
|
|
261
|
+
if self.enable_builtin_tools and tool_executor is None:
|
|
262
|
+
# Create a combined executor that handles both built-in and custom tools
|
|
263
|
+
from .builtin_tools import execute_builtin_file_tool
|
|
264
|
+
|
|
265
|
+
def combined_executor(tool_input: Dict[str, Any]) -> str:
|
|
266
|
+
tool_name = tool_input.get("tool_name")
|
|
267
|
+
builtin_names = {"read_file", "write_file", "list_directory", "file_exists"}
|
|
268
|
+
|
|
269
|
+
if tool_name in builtin_names:
|
|
270
|
+
return execute_builtin_file_tool(tool_name, tool_input)
|
|
271
|
+
elif self.custom_tool_executor:
|
|
272
|
+
return self.custom_tool_executor(tool_input)
|
|
273
|
+
else:
|
|
274
|
+
return f"Error: No executor found for tool: {tool_name}"
|
|
275
|
+
|
|
276
|
+
tool_executor = combined_executor
|
|
277
|
+
|
|
278
|
+
# Use enhanced AgenticLoop to handle complete conversation flow
|
|
279
|
+
if self.use_enhanced_loop:
|
|
280
|
+
loop = self.manager.create_enhanced_agentic_loop(
|
|
281
|
+
client=self.client,
|
|
282
|
+
model=self.model,
|
|
283
|
+
max_iterations=self.max_iterations,
|
|
284
|
+
custom_tools=self.custom_tools if self.custom_tools else None,
|
|
285
|
+
custom_tool_executor=tool_executor
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
# Use basic AgenticLoop (backward compatible)
|
|
289
|
+
loop = self.manager.create_agentic_loop(
|
|
290
|
+
client=self.client,
|
|
291
|
+
model=self.model,
|
|
292
|
+
system_prompt=self.system_context,
|
|
293
|
+
max_iterations=self.max_iterations
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
response = loop.run(user_message)
|
|
297
|
+
result = response.choices[0].message.content or ""
|
|
298
|
+
|
|
299
|
+
if self.verbose:
|
|
300
|
+
print(f"🤖 Assistant: {result}")
|
|
301
|
+
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
def run_with_details(self, user_message: str) -> Dict[str, Any]:
|
|
305
|
+
"""
|
|
306
|
+
Run Skill and return detailed results (including intermediate process).
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
user_message: User input message
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Dictionary containing complete information
|
|
313
|
+
"""
|
|
314
|
+
messages = []
|
|
315
|
+
if self.system_context:
|
|
316
|
+
messages.append({"role": "system", "content": self.system_context})
|
|
317
|
+
messages.append({"role": "user", "content": user_message})
|
|
318
|
+
|
|
319
|
+
tools = self.tools
|
|
320
|
+
tool_calls_history = []
|
|
321
|
+
iterations = 0
|
|
322
|
+
|
|
323
|
+
for _ in range(self.max_iterations):
|
|
324
|
+
iterations += 1
|
|
325
|
+
response = self.client.chat.completions.create(
|
|
326
|
+
model=self.model,
|
|
327
|
+
tools=tools if tools else None,
|
|
328
|
+
messages=messages
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
message = response.choices[0].message
|
|
332
|
+
|
|
333
|
+
if not message.tool_calls:
|
|
334
|
+
return {
|
|
335
|
+
"content": message.content,
|
|
336
|
+
"iterations": iterations,
|
|
337
|
+
"tool_calls": tool_calls_history,
|
|
338
|
+
"final_response": response
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# Record tool calls
|
|
342
|
+
messages.append(message)
|
|
343
|
+
results = self.manager.handle_tool_calls(response)
|
|
344
|
+
|
|
345
|
+
for tc, result in zip(message.tool_calls, results):
|
|
346
|
+
tool_calls_history.append({
|
|
347
|
+
"name": tc.function.name,
|
|
348
|
+
"arguments": tc.function.arguments,
|
|
349
|
+
"result": result.content
|
|
350
|
+
})
|
|
351
|
+
messages.append({
|
|
352
|
+
"role": "tool",
|
|
353
|
+
"tool_call_id": tc.id,
|
|
354
|
+
"content": result.content
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
"content": message.content if message else None,
|
|
359
|
+
"iterations": iterations,
|
|
360
|
+
"tool_calls": tool_calls_history,
|
|
361
|
+
"final_response": response
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ==================== Convenience Functions ====================
|
|
366
|
+
|
|
367
|
+
_default_runner: Optional[SkillRunner] = None
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def get_runner(**kwargs) -> SkillRunner:
|
|
371
|
+
"""
|
|
372
|
+
Get default SkillRunner instance (singleton pattern).
|
|
373
|
+
|
|
374
|
+
Creates instance on first call, subsequent calls return the same instance.
|
|
375
|
+
Passed parameters will override default configuration.
|
|
376
|
+
"""
|
|
377
|
+
global _default_runner
|
|
378
|
+
if _default_runner is None or kwargs:
|
|
379
|
+
_default_runner = SkillRunner(**kwargs)
|
|
380
|
+
return _default_runner
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def quick_run(
|
|
384
|
+
user_message: str,
|
|
385
|
+
skills_dir: Optional[str] = None,
|
|
386
|
+
verbose: bool = False,
|
|
387
|
+
**kwargs
|
|
388
|
+
) -> str:
|
|
389
|
+
"""
|
|
390
|
+
Run Skill with one line of code.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
user_message: User input message
|
|
394
|
+
skills_dir: Skills directory, defaults to "./.skills"
|
|
395
|
+
verbose: Whether to output detailed logs
|
|
396
|
+
**kwargs: Other parameters passed to SkillRunner
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Final response content from LLM
|
|
400
|
+
|
|
401
|
+
Example:
|
|
402
|
+
```python
|
|
403
|
+
from skilllite import quick_run
|
|
404
|
+
|
|
405
|
+
# Simplest usage (requires .env configuration)
|
|
406
|
+
result = quick_run("Calculate 15 times 27 for me")
|
|
407
|
+
|
|
408
|
+
# With detailed output
|
|
409
|
+
result = quick_run("Calculate 15 times 27 for me", verbose=True)
|
|
410
|
+
|
|
411
|
+
# Specify skills directory
|
|
412
|
+
result = quick_run("Calculate 15 times 27 for me", skills_dir="./my_skills")
|
|
413
|
+
```
|
|
414
|
+
"""
|
|
415
|
+
if skills_dir:
|
|
416
|
+
kwargs["skills_dir"] = skills_dir
|
|
417
|
+
kwargs["verbose"] = verbose
|
|
418
|
+
|
|
419
|
+
runner = get_runner(**kwargs)
|
|
420
|
+
return runner.run(user_message)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sandbox module - provides sandboxed execution environments.
|
|
3
|
+
|
|
4
|
+
This module abstracts different sandbox implementations, with skillbox
|
|
5
|
+
(Rust-based sandbox) as the primary implementation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base import SandboxExecutor, ExecutionResult
|
|
9
|
+
from .config import (
|
|
10
|
+
SandboxConfig,
|
|
11
|
+
DEFAULT_EXECUTION_TIMEOUT,
|
|
12
|
+
DEFAULT_MAX_MEMORY_MB,
|
|
13
|
+
DEFAULT_SANDBOX_LEVEL,
|
|
14
|
+
DEFAULT_ALLOW_NETWORK,
|
|
15
|
+
DEFAULT_ENABLE_SANDBOX,
|
|
16
|
+
)
|
|
17
|
+
from .skillbox import SkillboxExecutor, install, uninstall, find_binary, ensure_installed
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Base classes
|
|
21
|
+
"SandboxExecutor",
|
|
22
|
+
"ExecutionResult",
|
|
23
|
+
# Configuration
|
|
24
|
+
"SandboxConfig",
|
|
25
|
+
"DEFAULT_EXECUTION_TIMEOUT",
|
|
26
|
+
"DEFAULT_MAX_MEMORY_MB",
|
|
27
|
+
"DEFAULT_SANDBOX_LEVEL",
|
|
28
|
+
"DEFAULT_ALLOW_NETWORK",
|
|
29
|
+
"DEFAULT_ENABLE_SANDBOX",
|
|
30
|
+
# Skillbox implementation
|
|
31
|
+
"SkillboxExecutor",
|
|
32
|
+
"install",
|
|
33
|
+
"uninstall",
|
|
34
|
+
"find_binary",
|
|
35
|
+
"ensure_installed",
|
|
36
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for sandbox executors.
|
|
3
|
+
|
|
4
|
+
This module defines the abstract interface that all sandbox implementations
|
|
5
|
+
must follow, enabling easy switching between different sandbox backends.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ExecutionResult:
|
|
16
|
+
"""Result of a sandbox execution."""
|
|
17
|
+
success: bool
|
|
18
|
+
output: Optional[Dict[str, Any]] = None
|
|
19
|
+
error: Optional[str] = None
|
|
20
|
+
exit_code: int = 0
|
|
21
|
+
stdout: str = ""
|
|
22
|
+
stderr: str = ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SandboxExecutor(ABC):
|
|
26
|
+
"""
|
|
27
|
+
Abstract base class for sandbox executors.
|
|
28
|
+
|
|
29
|
+
All sandbox implementations (skillbox, docker, pyodide, etc.) should
|
|
30
|
+
inherit from this class and implement the required methods.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def execute(
|
|
35
|
+
self,
|
|
36
|
+
skill_dir: Path,
|
|
37
|
+
input_data: Dict[str, Any],
|
|
38
|
+
allow_network: Optional[bool] = None,
|
|
39
|
+
timeout: Optional[int] = None,
|
|
40
|
+
entry_point: Optional[str] = None
|
|
41
|
+
) -> ExecutionResult:
|
|
42
|
+
"""
|
|
43
|
+
Execute a skill with the given input.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
skill_dir: Path to the skill directory
|
|
47
|
+
input_data: Input data for the skill
|
|
48
|
+
allow_network: Whether to allow network access
|
|
49
|
+
timeout: Execution timeout in seconds
|
|
50
|
+
entry_point: Optional specific script to execute
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
ExecutionResult with the output or error
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def exec_script(
|
|
59
|
+
self,
|
|
60
|
+
skill_dir: Path,
|
|
61
|
+
script_path: str,
|
|
62
|
+
input_data: Dict[str, Any],
|
|
63
|
+
args: Optional[list] = None,
|
|
64
|
+
allow_network: Optional[bool] = None,
|
|
65
|
+
timeout: Optional[int] = None
|
|
66
|
+
) -> ExecutionResult:
|
|
67
|
+
"""
|
|
68
|
+
Execute a specific script directly.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
skill_dir: Path to the skill directory
|
|
72
|
+
script_path: Relative path to the script
|
|
73
|
+
input_data: Input data for the script
|
|
74
|
+
args: Optional command line arguments
|
|
75
|
+
allow_network: Whether to allow network access
|
|
76
|
+
timeout: Execution timeout in seconds
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
ExecutionResult with the output or error
|
|
80
|
+
"""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def is_available(self) -> bool:
|
|
86
|
+
"""Check if this sandbox executor is available and ready to use."""
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def name(self) -> str:
|
|
92
|
+
"""Return the name of this sandbox implementation."""
|
|
93
|
+
pass
|