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
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,
|
skilllite/core/loops.py
CHANGED
|
@@ -56,11 +56,12 @@ class AgenticLoop:
|
|
|
56
56
|
custom_tool_handler: Optional[Callable] = None,
|
|
57
57
|
enable_task_planning: bool = True,
|
|
58
58
|
verbose: bool = True,
|
|
59
|
+
confirmation_callback: Optional[Callable[[str, str], bool]] = None,
|
|
59
60
|
**kwargs
|
|
60
61
|
):
|
|
61
62
|
"""
|
|
62
63
|
Initialize the agentic loop.
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
Args:
|
|
65
66
|
manager: SkillManager instance
|
|
66
67
|
client: LLM client (OpenAI or Anthropic)
|
|
@@ -71,6 +72,9 @@ class AgenticLoop:
|
|
|
71
72
|
custom_tool_handler: Optional custom tool handler function
|
|
72
73
|
enable_task_planning: Whether to generate task list before execution
|
|
73
74
|
verbose: Whether to print detailed logs
|
|
75
|
+
confirmation_callback: Callback for security confirmation (sandbox_level=3).
|
|
76
|
+
Signature: (security_report: str, scan_id: str) -> bool
|
|
77
|
+
If None and sandbox_level=3, will use interactive terminal confirmation.
|
|
74
78
|
**kwargs: Additional arguments passed to the LLM
|
|
75
79
|
"""
|
|
76
80
|
self.manager = manager
|
|
@@ -82,14 +86,157 @@ class AgenticLoop:
|
|
|
82
86
|
self.custom_tool_handler = custom_tool_handler
|
|
83
87
|
self.enable_task_planning = enable_task_planning
|
|
84
88
|
self.verbose = verbose
|
|
89
|
+
self.confirmation_callback = confirmation_callback
|
|
85
90
|
self.extra_kwargs = kwargs
|
|
86
91
|
self.task_list: List[Dict] = []
|
|
87
|
-
|
|
92
|
+
|
|
93
|
+
# Initialize security scanner for sandbox_level=3
|
|
94
|
+
self._security_scanner = None
|
|
95
|
+
self._pending_confirmation = False # Track if confirmation is pending
|
|
96
|
+
|
|
88
97
|
def _log(self, message: str) -> None:
|
|
89
98
|
"""Print log message if verbose mode is enabled."""
|
|
90
99
|
if self.verbose:
|
|
91
100
|
print(message)
|
|
92
|
-
|
|
101
|
+
|
|
102
|
+
def _get_security_scanner(self):
|
|
103
|
+
"""Get or lazily initialize the security scanner."""
|
|
104
|
+
if self._security_scanner is None:
|
|
105
|
+
from .security import SecurityScanner
|
|
106
|
+
self._security_scanner = SecurityScanner()
|
|
107
|
+
return self._security_scanner
|
|
108
|
+
|
|
109
|
+
def _should_perform_security_scan(self) -> bool:
|
|
110
|
+
"""Check if security scanning should be performed."""
|
|
111
|
+
import os
|
|
112
|
+
sandbox_level = os.environ.get("SKILLBOX_SANDBOX_LEVEL", "3")
|
|
113
|
+
return sandbox_level == "3" and self.confirmation_callback is not None
|
|
114
|
+
|
|
115
|
+
def _interactive_confirmation(self, report: str, scan_id: str) -> bool:
|
|
116
|
+
"""Default interactive terminal confirmation."""
|
|
117
|
+
self._log(f"\n{report}")
|
|
118
|
+
self._log("\n" + "=" * 60)
|
|
119
|
+
while True:
|
|
120
|
+
response = input("⚠️ Allow execution? (y/n): ").strip().lower()
|
|
121
|
+
if response in ['y', 'yes']:
|
|
122
|
+
return True
|
|
123
|
+
elif response in ['n', 'no']:
|
|
124
|
+
return False
|
|
125
|
+
self._log("Please enter 'y' or 'n'")
|
|
126
|
+
|
|
127
|
+
def _perform_security_confirmation_for_tools(
|
|
128
|
+
self,
|
|
129
|
+
tool_calls: List[Any]
|
|
130
|
+
) -> bool:
|
|
131
|
+
"""
|
|
132
|
+
Perform security scan and confirmation for tool calls.
|
|
133
|
+
|
|
134
|
+
Returns True if execution should proceed, False if denied.
|
|
135
|
+
Also handles skills that require elevated permissions.
|
|
136
|
+
"""
|
|
137
|
+
import os
|
|
138
|
+
from .security import SecurityScanResult
|
|
139
|
+
|
|
140
|
+
sandbox_level = os.environ.get("SKILLBOX_SANDBOX_LEVEL", "3")
|
|
141
|
+
if sandbox_level != "3":
|
|
142
|
+
return True # No confirmation needed for levels 1-2
|
|
143
|
+
|
|
144
|
+
# Get skill tool names
|
|
145
|
+
skill_tool_names = set(self.manager.skill_names())
|
|
146
|
+
skill_tool_names.update(self.manager._registry.list_multi_script_tools())
|
|
147
|
+
|
|
148
|
+
# Scan each skill tool call
|
|
149
|
+
combined_issues = []
|
|
150
|
+
scanned_skills = set()
|
|
151
|
+
requires_elevated = False # Track if any skill requires elevated permissions
|
|
152
|
+
|
|
153
|
+
for tc in tool_calls:
|
|
154
|
+
tool_name = tc.function.name if hasattr(tc, 'function') else tc.get('name', '')
|
|
155
|
+
|
|
156
|
+
# Only scan skill tools, not custom tools
|
|
157
|
+
if tool_name not in skill_tool_names:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
# Get skill info
|
|
161
|
+
skill_name = tool_name.split('__')[0] if '__' in tool_name else tool_name
|
|
162
|
+
if skill_name in scanned_skills:
|
|
163
|
+
continue
|
|
164
|
+
scanned_skills.add(skill_name)
|
|
165
|
+
|
|
166
|
+
skill_info = self.manager.get_skill(skill_name)
|
|
167
|
+
if not skill_info:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# Check if skill requires elevated permissions
|
|
171
|
+
if skill_info.metadata and getattr(skill_info.metadata, 'requires_elevated_permissions', False):
|
|
172
|
+
requires_elevated = True
|
|
173
|
+
self._log(f"🔓 Skill '{skill_name}' requires elevated permissions")
|
|
174
|
+
|
|
175
|
+
# Parse input data
|
|
176
|
+
try:
|
|
177
|
+
import json
|
|
178
|
+
input_data = json.loads(tc.function.arguments) if hasattr(tc, 'function') else {}
|
|
179
|
+
except (json.JSONDecodeError, AttributeError):
|
|
180
|
+
input_data = {}
|
|
181
|
+
|
|
182
|
+
# Perform security scan
|
|
183
|
+
scanner = self._get_security_scanner()
|
|
184
|
+
result = scanner.scan_skill(skill_info, input_data)
|
|
185
|
+
combined_issues.extend(result.issues)
|
|
186
|
+
|
|
187
|
+
# If skill requires elevated permissions, downgrade sandbox level
|
|
188
|
+
if requires_elevated and not combined_issues:
|
|
189
|
+
self._log("✅ Skill requires elevated permissions, downgrading to sandbox level 1...")
|
|
190
|
+
os.environ["SKILLBOX_SANDBOX_LEVEL"] = "1"
|
|
191
|
+
self._pending_confirmation = True
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
if not combined_issues:
|
|
195
|
+
return True # No issues found
|
|
196
|
+
|
|
197
|
+
# Create combined scan result
|
|
198
|
+
high_count = sum(1 for i in combined_issues if i.get("severity") in ["Critical", "High"])
|
|
199
|
+
medium_count = sum(1 for i in combined_issues if i.get("severity") == "Medium")
|
|
200
|
+
low_count = sum(1 for i in combined_issues if i.get("severity") == "Low")
|
|
201
|
+
|
|
202
|
+
combined_result = SecurityScanResult(
|
|
203
|
+
is_safe=high_count == 0,
|
|
204
|
+
issues=combined_issues,
|
|
205
|
+
scan_id=f"batch-{len(scanned_skills)}",
|
|
206
|
+
high_severity_count=high_count,
|
|
207
|
+
medium_severity_count=medium_count,
|
|
208
|
+
low_severity_count=low_count,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if not combined_result.requires_confirmation:
|
|
212
|
+
return True # Only low/medium issues, proceed
|
|
213
|
+
|
|
214
|
+
# Ask for confirmation
|
|
215
|
+
report = combined_result.format_report()
|
|
216
|
+
self._log(f"\n🔒 Security scan detected potential issues:")
|
|
217
|
+
|
|
218
|
+
if self.confirmation_callback:
|
|
219
|
+
confirmed = self.confirmation_callback(report, combined_result.scan_id)
|
|
220
|
+
else:
|
|
221
|
+
confirmed = self._interactive_confirmation(report, combined_result.scan_id)
|
|
222
|
+
|
|
223
|
+
if confirmed:
|
|
224
|
+
# Temporarily downgrade sandbox level to allow execution
|
|
225
|
+
self._log("✅ User confirmed. Executing with sandbox level 1...")
|
|
226
|
+
os.environ["SKILLBOX_SANDBOX_LEVEL"] = "1"
|
|
227
|
+
self._pending_confirmation = True
|
|
228
|
+
return True
|
|
229
|
+
else:
|
|
230
|
+
self._log("❌ User denied execution.")
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
def _restore_sandbox_level(self, original_level: str) -> None:
|
|
234
|
+
"""Restore original sandbox level after execution."""
|
|
235
|
+
import os
|
|
236
|
+
if self._pending_confirmation:
|
|
237
|
+
os.environ["SKILLBOX_SANDBOX_LEVEL"] = original_level
|
|
238
|
+
self._pending_confirmation = False
|
|
239
|
+
|
|
93
240
|
def _get_execution_system_prompt(self) -> str:
|
|
94
241
|
"""
|
|
95
242
|
Generate the main execution system prompt for skill selection and file operations.
|
|
@@ -534,15 +681,18 @@ Based on the documentation, call the tools with correct parameters.
|
|
|
534
681
|
# No tool calls
|
|
535
682
|
if not message.tool_calls:
|
|
536
683
|
self._log("📝 LLM did not call any tools")
|
|
537
|
-
|
|
684
|
+
|
|
538
685
|
if self.enable_task_planning:
|
|
539
686
|
completed_id = self._check_task_completion_in_content(message.content)
|
|
540
687
|
if completed_id:
|
|
541
688
|
self._update_task_list(completed_id)
|
|
542
|
-
|
|
689
|
+
|
|
543
690
|
if self._check_all_tasks_completed():
|
|
544
691
|
self._log("🎯 All tasks completed, ending iteration")
|
|
545
692
|
return response
|
|
693
|
+
else:
|
|
694
|
+
# Tasks not complete and no tool calls - continue to next iteration
|
|
695
|
+
continue
|
|
546
696
|
else:
|
|
547
697
|
return response
|
|
548
698
|
|
|
@@ -570,17 +720,25 @@ Based on the documentation, call the tools with correct parameters.
|
|
|
570
720
|
continue
|
|
571
721
|
|
|
572
722
|
messages.append(message)
|
|
573
|
-
|
|
723
|
+
|
|
724
|
+
# Execute tools using unified execution service
|
|
574
725
|
self._log(f"\n⚙️ Executing tools...")
|
|
726
|
+
|
|
575
727
|
if self.custom_tool_handler:
|
|
728
|
+
# Custom tool handler takes precedence
|
|
576
729
|
tool_results = self.custom_tool_handler(
|
|
577
730
|
response, self.manager, allow_network, timeout
|
|
578
731
|
)
|
|
579
732
|
else:
|
|
580
|
-
|
|
581
|
-
|
|
733
|
+
# Use unified execution service with confirmation callback
|
|
734
|
+
# This handles security scanning, confirmation, and sandbox level management
|
|
735
|
+
tool_results = self.manager.handle_tool_calls_with_unified_service(
|
|
736
|
+
response,
|
|
737
|
+
confirmation_callback=self.confirmation_callback or self._interactive_confirmation,
|
|
738
|
+
allow_network=allow_network,
|
|
739
|
+
timeout=timeout
|
|
582
740
|
)
|
|
583
|
-
|
|
741
|
+
|
|
584
742
|
self._log(f"\n📊 Tool execution results:")
|
|
585
743
|
for idx, (result, tc) in enumerate(zip(tool_results, message.tool_calls), 1):
|
|
586
744
|
output = result.content
|
|
@@ -588,7 +746,7 @@ Based on the documentation, call the tools with correct parameters.
|
|
|
588
746
|
output = output[:500] + "... (truncated)"
|
|
589
747
|
self._log(f" {idx}. {tc.function.name}")
|
|
590
748
|
self._log(f" Result: {output}")
|
|
591
|
-
|
|
749
|
+
|
|
592
750
|
for result in tool_results:
|
|
593
751
|
messages.append(result.to_openai_format())
|
|
594
752
|
|
|
@@ -690,19 +848,26 @@ Based on the documentation, call the tools with correct parameters.
|
|
|
690
848
|
self._log(f" Arguments: {json.dumps(block.input, ensure_ascii=False)}")
|
|
691
849
|
|
|
692
850
|
messages.append({"role": "assistant", "content": response.content})
|
|
693
|
-
|
|
851
|
+
|
|
852
|
+
# Execute tools using unified execution service
|
|
694
853
|
self._log(f"\n⚙️ Executing tools...")
|
|
695
|
-
|
|
696
|
-
|
|
854
|
+
|
|
855
|
+
# Use unified execution service with confirmation callback
|
|
856
|
+
# This handles security scanning, confirmation, and sandbox level management
|
|
857
|
+
tool_results = self.manager.handle_tool_calls_claude_native_with_unified_service(
|
|
858
|
+
response,
|
|
859
|
+
confirmation_callback=self.confirmation_callback or self._interactive_confirmation,
|
|
860
|
+
allow_network=allow_network,
|
|
861
|
+
timeout=timeout
|
|
697
862
|
)
|
|
698
|
-
|
|
863
|
+
|
|
699
864
|
self._log(f"\n📊 Tool execution results:")
|
|
700
865
|
for idx, result in enumerate(tool_results, 1):
|
|
701
866
|
output = result.content
|
|
702
867
|
if len(output) > 500:
|
|
703
868
|
output = output[:500] + "... (truncated)"
|
|
704
869
|
self._log(f" {idx}. Result: {output}")
|
|
705
|
-
|
|
870
|
+
|
|
706
871
|
formatted_results = self.manager.format_tool_results_claude_native(tool_results)
|
|
707
872
|
messages.append({"role": "user", "content": formatted_results})
|
|
708
873
|
|
skilllite/core/manager.py
CHANGED
|
@@ -304,7 +304,64 @@ class SkillManager:
|
|
|
304
304
|
) -> List[ToolResult]:
|
|
305
305
|
"""Parse and execute all tool calls from Claude's native API response."""
|
|
306
306
|
return self._handler.handle_tool_calls_claude_native(response, allow_network, timeout)
|
|
307
|
-
|
|
307
|
+
|
|
308
|
+
def handle_tool_calls_with_unified_service(
|
|
309
|
+
self,
|
|
310
|
+
response: Any,
|
|
311
|
+
confirmation_callback: Optional[Callable[[str, str], bool]] = None,
|
|
312
|
+
allow_network: Optional[bool] = None,
|
|
313
|
+
timeout: Optional[int] = None
|
|
314
|
+
) -> List[ToolResult]:
|
|
315
|
+
"""
|
|
316
|
+
Parse and execute all tool calls using UnifiedExecutionService.
|
|
317
|
+
|
|
318
|
+
This method uses the unified execution layer which:
|
|
319
|
+
1. Reads sandbox level at runtime
|
|
320
|
+
2. Handles security scanning and confirmation per-skill
|
|
321
|
+
3. Properly downgrades sandbox level after confirmation
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
response: Response from OpenAI-compatible API
|
|
325
|
+
confirmation_callback: Callback for security confirmation
|
|
326
|
+
allow_network: Whether to allow network access
|
|
327
|
+
timeout: Execution timeout in seconds
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
List of ToolResult objects
|
|
331
|
+
"""
|
|
332
|
+
return self._handler.handle_tool_calls_with_unified_service(
|
|
333
|
+
response,
|
|
334
|
+
confirmation_callback=confirmation_callback,
|
|
335
|
+
allow_network=allow_network,
|
|
336
|
+
timeout=timeout
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def handle_tool_calls_claude_native_with_unified_service(
|
|
340
|
+
self,
|
|
341
|
+
response: Any,
|
|
342
|
+
confirmation_callback: Optional[Callable[[str, str], bool]] = None,
|
|
343
|
+
allow_network: Optional[bool] = None,
|
|
344
|
+
timeout: Optional[int] = None
|
|
345
|
+
) -> List[ToolResult]:
|
|
346
|
+
"""
|
|
347
|
+
Parse and execute all Claude tool calls using UnifiedExecutionService.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
response: Response from Claude's native API
|
|
351
|
+
confirmation_callback: Callback for security confirmation
|
|
352
|
+
allow_network: Whether to allow network access
|
|
353
|
+
timeout: Execution timeout in seconds
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
List of ToolResult objects
|
|
357
|
+
"""
|
|
358
|
+
return self._handler.handle_tool_calls_claude_native_with_unified_service(
|
|
359
|
+
response,
|
|
360
|
+
confirmation_callback=confirmation_callback,
|
|
361
|
+
allow_network=allow_network,
|
|
362
|
+
timeout=timeout
|
|
363
|
+
)
|
|
364
|
+
|
|
308
365
|
def format_tool_results_claude_native(self, results: List[ToolResult]) -> List[Dict[str, Any]]:
|
|
309
366
|
"""Format tool results for Claude's native API."""
|
|
310
367
|
return self._handler.format_tool_results_claude_native(results)
|
|
@@ -335,6 +392,7 @@ class SkillManager:
|
|
|
335
392
|
custom_tool_handler: Optional[Callable] = None,
|
|
336
393
|
enable_task_planning: bool = True,
|
|
337
394
|
verbose: bool = True,
|
|
395
|
+
confirmation_callback: Optional[Callable[[str, str], bool]] = None,
|
|
338
396
|
**kwargs
|
|
339
397
|
) -> AgenticLoop:
|
|
340
398
|
"""
|
|
@@ -351,15 +409,16 @@ class SkillManager:
|
|
|
351
409
|
custom_tool_handler: Optional custom tool handler
|
|
352
410
|
enable_task_planning: Whether to generate task list before execution
|
|
353
411
|
verbose: Whether to print detailed logs
|
|
412
|
+
confirmation_callback: Callback for security confirmation (sandbox_level=3)
|
|
354
413
|
**kwargs: Additional arguments passed to the LLM
|
|
355
|
-
|
|
414
|
+
|
|
356
415
|
Returns:
|
|
357
416
|
AgenticLoop instance
|
|
358
|
-
|
|
417
|
+
|
|
359
418
|
Example:
|
|
360
419
|
# OpenAI-compatible (default)
|
|
361
420
|
loop = manager.create_agentic_loop(client, "gpt-4")
|
|
362
|
-
|
|
421
|
+
|
|
363
422
|
# Claude native API
|
|
364
423
|
loop = manager.create_agentic_loop(client, "claude-3-opus",
|
|
365
424
|
api_format="claude_native")
|
|
@@ -375,6 +434,7 @@ class SkillManager:
|
|
|
375
434
|
custom_tool_handler=custom_tool_handler,
|
|
376
435
|
enable_task_planning=enable_task_planning,
|
|
377
436
|
verbose=verbose,
|
|
437
|
+
confirmation_callback=confirmation_callback,
|
|
378
438
|
**kwargs
|
|
379
439
|
)
|
|
380
440
|
|
|
@@ -421,14 +481,15 @@ class SkillManager:
|
|
|
421
481
|
custom_tool_executor: Optional[Callable] = None,
|
|
422
482
|
enable_task_planning: bool = True,
|
|
423
483
|
verbose: bool = True,
|
|
484
|
+
confirmation_callback: Optional[Callable[[str, str], bool]] = None,
|
|
424
485
|
**kwargs
|
|
425
486
|
) -> AgenticLoop:
|
|
426
487
|
"""
|
|
427
488
|
Create an enhanced agentic loop with custom tools support.
|
|
428
|
-
|
|
489
|
+
|
|
429
490
|
This method creates an AgenticLoop that can handle both skill tools
|
|
430
491
|
and custom tools (like file operations).
|
|
431
|
-
|
|
492
|
+
|
|
432
493
|
Args:
|
|
433
494
|
client: LLM client (OpenAI-compatible)
|
|
434
495
|
model: Model name to use
|
|
@@ -438,27 +499,32 @@ class SkillManager:
|
|
|
438
499
|
custom_tool_executor: Executor function for custom tools
|
|
439
500
|
enable_task_planning: Whether to generate task list before execution
|
|
440
501
|
verbose: Whether to print detailed logs
|
|
502
|
+
confirmation_callback: Callback for security confirmation (sandbox_level=3)
|
|
441
503
|
**kwargs: Additional arguments passed to the LLM
|
|
442
|
-
|
|
504
|
+
|
|
443
505
|
Returns:
|
|
444
506
|
AgenticLoop instance with enhanced capabilities
|
|
445
507
|
"""
|
|
446
508
|
# Create custom tool handler that combines skill tools and custom tools
|
|
447
509
|
def combined_tool_handler(response, manager, allow_network, timeout):
|
|
448
510
|
from .tools import ToolUseRequest, ToolResult
|
|
449
|
-
|
|
511
|
+
|
|
450
512
|
requests = ToolUseRequest.parse_from_openai_response(response)
|
|
451
513
|
results = []
|
|
452
|
-
|
|
514
|
+
|
|
453
515
|
# Get skill tool names
|
|
454
516
|
skill_tool_names = set(self.skill_names())
|
|
455
517
|
skill_tool_names.update(self._registry.list_multi_script_tools())
|
|
456
|
-
|
|
518
|
+
|
|
457
519
|
for request in requests:
|
|
458
520
|
if request.name in skill_tool_names:
|
|
459
|
-
# Execute as skill tool
|
|
460
|
-
|
|
461
|
-
|
|
521
|
+
# Execute as skill tool using UnifiedExecutionService
|
|
522
|
+
# This handles security scanning, confirmation, and proper sandbox level
|
|
523
|
+
result = self._handler.execute_tool_call_with_unified_service(
|
|
524
|
+
request,
|
|
525
|
+
confirmation_callback=confirmation_callback,
|
|
526
|
+
allow_network=allow_network,
|
|
527
|
+
timeout=timeout
|
|
462
528
|
)
|
|
463
529
|
results.append(result)
|
|
464
530
|
elif custom_tool_executor:
|
|
@@ -473,9 +539,9 @@ class SkillManager:
|
|
|
473
539
|
results.append(ToolResult.error(
|
|
474
540
|
request.id, f"No executor found for tool: {request.name}"
|
|
475
541
|
))
|
|
476
|
-
|
|
542
|
+
|
|
477
543
|
return results
|
|
478
|
-
|
|
544
|
+
|
|
479
545
|
return AgenticLoop(
|
|
480
546
|
manager=self,
|
|
481
547
|
client=client,
|
|
@@ -486,6 +552,7 @@ class SkillManager:
|
|
|
486
552
|
custom_tool_handler=combined_tool_handler if custom_tool_executor else None,
|
|
487
553
|
enable_task_planning=enable_task_planning,
|
|
488
554
|
verbose=verbose,
|
|
555
|
+
confirmation_callback=confirmation_callback,
|
|
489
556
|
**kwargs
|
|
490
557
|
)
|
|
491
558
|
|