ms-enclave 0.0.0__py3-none-any.whl → 0.0.1__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.

Potentially problematic release.


This version of ms-enclave might be problematic. Click here for more details.

Files changed (43) hide show
  1. ms_enclave/__init__.py +2 -2
  2. ms_enclave/cli/__init__.py +1 -0
  3. ms_enclave/cli/base.py +20 -0
  4. ms_enclave/cli/cli.py +27 -0
  5. ms_enclave/cli/start_server.py +84 -0
  6. ms_enclave/sandbox/__init__.py +27 -0
  7. ms_enclave/sandbox/boxes/__init__.py +16 -0
  8. ms_enclave/sandbox/boxes/base.py +267 -0
  9. ms_enclave/sandbox/boxes/docker_notebook.py +216 -0
  10. ms_enclave/sandbox/boxes/docker_sandbox.py +252 -0
  11. ms_enclave/sandbox/manager/__init__.py +11 -0
  12. ms_enclave/sandbox/manager/base.py +155 -0
  13. ms_enclave/sandbox/manager/http_manager.py +405 -0
  14. ms_enclave/sandbox/manager/local_manager.py +295 -0
  15. ms_enclave/sandbox/model/__init__.py +21 -0
  16. ms_enclave/sandbox/model/base.py +36 -0
  17. ms_enclave/sandbox/model/config.py +97 -0
  18. ms_enclave/sandbox/model/requests.py +57 -0
  19. ms_enclave/sandbox/model/responses.py +57 -0
  20. ms_enclave/sandbox/server/__init__.py +0 -0
  21. ms_enclave/sandbox/server/server.py +195 -0
  22. ms_enclave/sandbox/tools/__init__.py +4 -0
  23. ms_enclave/sandbox/tools/base.py +95 -0
  24. ms_enclave/sandbox/tools/sandbox_tool.py +46 -0
  25. ms_enclave/sandbox/tools/sandbox_tools/__init__.py +4 -0
  26. ms_enclave/sandbox/tools/sandbox_tools/file_operation.py +215 -0
  27. ms_enclave/sandbox/tools/sandbox_tools/notebook_executor.py +167 -0
  28. ms_enclave/sandbox/tools/sandbox_tools/python_executor.py +87 -0
  29. ms_enclave/sandbox/tools/sandbox_tools/shell_executor.py +63 -0
  30. ms_enclave/sandbox/tools/tool_info.py +141 -0
  31. ms_enclave/utils/__init__.py +1 -0
  32. ms_enclave/utils/json_schema.py +208 -0
  33. ms_enclave/utils/logger.py +106 -0
  34. ms_enclave/version.py +2 -2
  35. ms_enclave-0.0.1.dist-info/METADATA +314 -0
  36. ms_enclave-0.0.1.dist-info/RECORD +40 -0
  37. {ms_enclave-0.0.0.dist-info → ms_enclave-0.0.1.dist-info}/WHEEL +1 -1
  38. ms_enclave-0.0.1.dist-info/entry_points.txt +2 -0
  39. ms_enclave/run_server.py +0 -21
  40. ms_enclave-0.0.0.dist-info/METADATA +0 -329
  41. ms_enclave-0.0.0.dist-info/RECORD +0 -8
  42. {ms_enclave-0.0.0.dist-info → ms_enclave-0.0.1.dist-info}/licenses/LICENSE +0 -0
  43. {ms_enclave-0.0.0.dist-info → ms_enclave-0.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,21 @@
1
+ """Data models for sandbox system."""
2
+
3
+ from .base import ExecutionStatus, SandboxStatus, SandboxType, ToolType
4
+ from .config import (
5
+ DockerNotebookConfig,
6
+ DockerSandboxConfig,
7
+ FileOperationConfig,
8
+ PythonExecutorConfig,
9
+ SandboxConfig,
10
+ ShellExecutorConfig,
11
+ ToolConfig,
12
+ )
13
+ from .requests import (
14
+ ExecuteCodeRequest,
15
+ ExecuteCommandRequest,
16
+ FileOperationRequest,
17
+ ReadFileRequest,
18
+ ToolExecutionRequest,
19
+ WriteFileRequest,
20
+ )
21
+ from .responses import CommandResult, HealthCheckResult, SandboxInfo, ToolResult
@@ -0,0 +1,36 @@
1
+ """Base data models."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class SandboxStatus(str, Enum):
7
+ """Sandbox status enumeration."""
8
+
9
+ INITIALIZING = 'initializing'
10
+ RUNNING = 'running'
11
+ STOPPING = 'stopping'
12
+ STOPPED = 'stopped'
13
+ ERROR = 'error'
14
+
15
+
16
+ class SandboxType(str, Enum):
17
+ """Sandbox type enumeration."""
18
+ DOCKER = 'docker'
19
+ DOCKER_NOTEBOOK = 'docker_notebook'
20
+ DUMMY = 'dummy'
21
+
22
+
23
+ class ToolType(str, Enum):
24
+ """Tool type enumeration."""
25
+ SANDBOX = 'sandbox'
26
+ FUNCTION = 'function'
27
+ EXTERNAL = 'external'
28
+
29
+
30
+ class ExecutionStatus(str, Enum):
31
+ """Execution status enumeration."""
32
+
33
+ SUCCESS = 'success'
34
+ ERROR = 'error'
35
+ TIMEOUT = 'timeout'
36
+ CANCELLED = 'cancelled'
@@ -0,0 +1,97 @@
1
+ """Configuration data models."""
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from pydantic import BaseModel, Field, field_validator
6
+
7
+
8
+ class SandboxConfig(BaseModel):
9
+ """Base sandbox configuration."""
10
+
11
+ timeout: int = Field(default=30, description='Default timeout in seconds')
12
+ tools_config: Dict[str, Dict[
13
+ str, Any]] = Field(default_factory=dict, description='Configuration for tools within the sandbox')
14
+ working_dir: str = Field(default='/sandbox', description='Default working directory')
15
+ env_vars: Dict[str, str] = Field(default_factory=dict, description='Environment variables')
16
+ resource_limits: Dict[str, Any] = Field(default_factory=dict, description='Resource limits')
17
+
18
+
19
+ class DockerSandboxConfig(SandboxConfig):
20
+ """Docker-specific sandbox configuration."""
21
+
22
+ image: str = Field('python:3.11-slim', description='Docker image name')
23
+ command: Optional[Union[str, List[str]]] = Field(None, description='Container command')
24
+ volumes: Dict[str, Dict[str, str]] = Field(
25
+ default_factory=dict,
26
+ description="Volume mounts. Format: { host_path: {'bind': container_path, 'mode': 'rw|ro'} }"
27
+ )
28
+ ports: Dict[str, str] = Field(default_factory=dict, description='Port mappings')
29
+ network: Optional[str] = Field('bridge', description='Network name')
30
+ memory_limit: str = Field(default='1g', description='Memory limit')
31
+ cpu_limit: float = Field(default=1.0, description='CPU limit')
32
+ network_enabled: bool = Field(default=True, description='Enable network access')
33
+ privileged: bool = Field(default=False, description='Run in privileged mode')
34
+ remove_on_exit: bool = Field(default=True, description='Remove container on exit')
35
+
36
+ @field_validator('memory_limit')
37
+ def validate_memory_limit(cls, v):
38
+ """Validate memory limit format."""
39
+ if not isinstance(v, str):
40
+ raise ValueError('Memory limit must be a string')
41
+ # Basic validation for memory format (e.g., '512m', '1g', '2G')
42
+ import re
43
+ if not re.match(r'^\d+[kmgKMG]?$', v):
44
+ raise ValueError('Invalid memory limit format')
45
+ return v
46
+
47
+ @field_validator('cpu_limit')
48
+ def validate_cpu_limit(cls, v):
49
+ """Validate CPU limit."""
50
+ if v <= 0:
51
+ raise ValueError('CPU limit must be positive')
52
+ return v
53
+
54
+
55
+ class DockerNotebookConfig(DockerSandboxConfig):
56
+ """Docker Notebook-specific sandbox configuration."""
57
+
58
+ image: str = Field('jupyter-kernel-gateway', description='Docker image name for Jupyter Notebook')
59
+ host: str = Field('127.0.0.1', description='Host for Jupyter Notebook')
60
+ port: int = Field(8888, description='Port for Jupyter Notebook')
61
+ token: Optional[str] = Field(None, description='Token for Jupyter Notebook access')
62
+
63
+
64
+ class ToolConfig(BaseModel):
65
+ """Tool configuration."""
66
+
67
+ enabled: bool = Field(default=True, description='Whether tool is enabled')
68
+ timeout: int = Field(default=30, description='Tool execution timeout')
69
+ parameters: Dict[str, Any] = Field(default_factory=dict, description='Tool parameters')
70
+ restrictions: Dict[str, Any] = Field(default_factory=dict, description='Tool restrictions')
71
+
72
+
73
+ class PythonExecutorConfig(ToolConfig):
74
+ """Python executor configuration."""
75
+
76
+ python_path: str = Field(default='python3', description='Python executable path')
77
+ allowed_modules: Optional[List[str]] = Field(None, description='Allowed modules (None = all)')
78
+ blocked_modules: List[str] = Field(default_factory=list, description='Blocked modules')
79
+ max_output_size: int = Field(default=1024 * 1024, description='Maximum output size in bytes')
80
+
81
+
82
+ class ShellExecutorConfig(ToolConfig):
83
+ """Shell executor configuration."""
84
+
85
+ shell_path: str = Field(default='/bin/bash', description='Shell executable path')
86
+ allowed_commands: Optional[List[str]] = Field(None, description='Allowed commands (None = all)')
87
+ blocked_commands: List[str] = Field(default_factory=list, description='Blocked commands')
88
+ max_output_size: int = Field(default=1024 * 1024, description='Maximum output size in bytes')
89
+
90
+
91
+ class FileOperationConfig(ToolConfig):
92
+ """File operation configuration."""
93
+
94
+ allowed_paths: Optional[List[str]] = Field(None, description='Allowed paths (None = all)')
95
+ blocked_paths: List[str] = Field(default_factory=list, description='Blocked paths')
96
+ max_file_size: int = Field(default=10 * 1024 * 1024, description='Maximum file size in bytes')
97
+ allowed_extensions: Optional[List[str]] = Field(None, description='Allowed file extensions')
@@ -0,0 +1,57 @@
1
+ """Request data models."""
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ExecuteCodeRequest(BaseModel):
9
+ """Request model for code execution."""
10
+
11
+ code: str = Field(..., description='Code to execute')
12
+ language: str = Field(default='python', description='Programming language')
13
+ timeout: Optional[int] = Field(None, description='Execution timeout in seconds')
14
+ working_dir: Optional[str] = Field(None, description='Working directory')
15
+ env: Dict[str, str] = Field(default_factory=dict, description='Environment variables')
16
+ capture_output: bool = Field(True, description='Whether to capture stdout/stderr')
17
+
18
+
19
+ class ExecuteCommandRequest(BaseModel):
20
+ """Request model for shell command execution."""
21
+
22
+ command: Union[str, List[str]] = Field(..., description='Command to execute')
23
+ timeout: Optional[int] = Field(None, description='Execution timeout in seconds')
24
+ working_dir: Optional[str] = Field(None, description='Working directory')
25
+ env: Dict[str, str] = Field(default_factory=dict, description='Environment variables')
26
+ shell: bool = Field(True, description='Execute in shell')
27
+
28
+
29
+ class FileOperationRequest(BaseModel):
30
+ """Base request model for file operations."""
31
+
32
+ path: str = Field(..., description='File path')
33
+
34
+
35
+ class ReadFileRequest(FileOperationRequest):
36
+ """Request model for reading files."""
37
+
38
+ encoding: Optional[str] = Field('utf-8', description='File encoding')
39
+ binary: bool = Field(False, description='Read as binary')
40
+
41
+
42
+ class WriteFileRequest(FileOperationRequest):
43
+ """Request model for writing files."""
44
+
45
+ content: Union[str, bytes] = Field(..., description='File content')
46
+ encoding: Optional[str] = Field('utf-8', description='File encoding')
47
+ binary: bool = Field(False, description='Write as binary')
48
+ create_dirs: bool = Field(True, description='Create parent directories if needed')
49
+
50
+
51
+ class ToolExecutionRequest(BaseModel):
52
+ """Request model for tool execution."""
53
+
54
+ sandbox_id: str = Field(..., description='Sandbox identifier')
55
+ tool_name: str = Field(..., description='Name of tool to execute')
56
+ parameters: Dict[str, Any] = Field(default_factory=dict, description='Tool parameters')
57
+ timeout: Optional[int] = Field(None, description='Execution timeout in seconds')
@@ -0,0 +1,57 @@
1
+ """Response data models."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Dict, List, Optional, Union
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from .base import ExecutionStatus, SandboxStatus
9
+
10
+
11
+ class SandboxInfo(BaseModel):
12
+ """Information about a sandbox instance."""
13
+
14
+ id: str = Field(..., description='Sandbox identifier')
15
+ status: SandboxStatus = Field(..., description='Current status')
16
+ type: str = Field(..., description="Sandbox type (e.g., 'docker')")
17
+ config: Dict[str, Any] = Field(default_factory=dict, description='Sandbox configuration')
18
+ created_at: datetime = Field(default_factory=datetime.now, description='Creation timestamp')
19
+ updated_at: datetime = Field(default_factory=datetime.now, description='Last update timestamp')
20
+ metadata: Dict[str, Any] = Field(default_factory=dict, description='Additional metadata')
21
+ available_tools: Dict[str, Any] = Field(default_factory=dict, description='Available tools')
22
+
23
+
24
+ class ExecutionResult(BaseModel):
25
+ """Base class for execution results."""
26
+
27
+ status: ExecutionStatus = Field(..., description='Execution status')
28
+ execution_time: Optional[float] = Field(None, description='Execution time in seconds')
29
+ timestamp: datetime = Field(default_factory=datetime.now, description='Execution timestamp')
30
+
31
+
32
+ class ToolResult(ExecutionResult):
33
+ """Result of tool execution."""
34
+
35
+ tool_name: str = Field(..., description='Name of tool executed')
36
+ output: Any = Field(None, description='Tool execution result')
37
+ metadata: Dict[str, Any] = Field(default_factory=dict, description='Additional metadata')
38
+ error: Optional[str] = Field(None, description='Error message if failed')
39
+
40
+
41
+ class CommandResult(ExecutionResult):
42
+ """Command execution result."""
43
+
44
+ command: Union[str, List[str]] = Field(..., description='Executed command')
45
+ exit_code: int = Field(..., description='Exit code of the command')
46
+ stdout: Optional[str] = Field(None, description='Standard output')
47
+ stderr: Optional[str] = Field(None, description='Standard error output')
48
+
49
+
50
+ class HealthCheckResult(BaseModel):
51
+ """Health check result."""
52
+
53
+ healthy: bool = Field(..., description='Health status')
54
+ version: str = Field(..., description='System version')
55
+ uptime: float = Field(..., description='System uptime in seconds')
56
+ active_sandboxes: int = Field(..., description='Number of active sandboxes')
57
+ system_info: Dict[str, Any] = Field(default_factory=dict, description='System information')
File without changes
@@ -0,0 +1,195 @@
1
+ """FastAPI server for sandbox system with optional API key authentication."""
2
+
3
+ import time
4
+ from contextlib import asynccontextmanager
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, Request
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.responses import JSONResponse
10
+
11
+ from ..manager import LocalSandboxManager
12
+ from ..model import (
13
+ DockerSandboxConfig,
14
+ HealthCheckResult,
15
+ SandboxConfig,
16
+ SandboxInfo,
17
+ SandboxStatus,
18
+ SandboxType,
19
+ ToolExecutionRequest,
20
+ ToolResult,
21
+ )
22
+
23
+
24
+ class SandboxServer:
25
+ """FastAPI-based sandbox server.
26
+ """
27
+
28
+ def __init__(self, cleanup_interval: int = 300, api_key: Optional[str] = None):
29
+ """Initialize sandbox server.
30
+
31
+ Args:
32
+ cleanup_interval: Cleanup interval in seconds
33
+ api_key: Optional API key to protect endpoints. If None, auth is disabled.
34
+ """
35
+ self.manager = LocalSandboxManager(cleanup_interval)
36
+ self.api_key: Optional[str] = api_key
37
+ self.app = FastAPI(
38
+ title='Sandbox API',
39
+ description='Agent sandbox execution environment',
40
+ version='1.0.0',
41
+ lifespan=self.lifespan
42
+ )
43
+ self._setup_middleware()
44
+ self._setup_auth_middleware()
45
+ self._setup_routes()
46
+ self.start_time = time.time()
47
+
48
+ @asynccontextmanager
49
+ async def lifespan(self, app: FastAPI):
50
+ """Application lifespan management."""
51
+ # Startup
52
+ await self.manager.start()
53
+ yield
54
+ # Shutdown
55
+ await self.manager.stop()
56
+
57
+ def _setup_middleware(self):
58
+ """Setup middleware."""
59
+ self.app.add_middleware(
60
+ CORSMiddleware,
61
+ allow_origins=['*'],
62
+ allow_credentials=True,
63
+ allow_methods=['*'],
64
+ allow_headers=['*'],
65
+ )
66
+
67
+ def _setup_auth_middleware(self) -> None:
68
+ """Setup optional API key authentication middleware.
69
+
70
+ When ``self.api_key`` is None, this middleware is a no-op.
71
+ Otherwise, it enforces that every request includes the correct API key
72
+ either via ``X-API-Key`` header or ``api_key`` query parameter.
73
+ """
74
+
75
+ @self.app.middleware('http')
76
+ async def auth_middleware(request: Request, call_next): # type: ignore[unused-ignore]
77
+ # Fast path when auth is disabled
78
+ if not self.api_key:
79
+ return await call_next(request)
80
+
81
+ provided = request.headers.get('x-api-key') or request.query_params.get('api_key')
82
+ if provided == self.api_key:
83
+ return await call_next(request)
84
+
85
+ return JSONResponse(status_code=401, content={'detail': 'Unauthorized'})
86
+
87
+ def _setup_routes(self):
88
+ """Setup API routes."""
89
+
90
+ # Health check
91
+ @self.app.get('/health', response_model=HealthCheckResult)
92
+ async def health_check():
93
+ """Health check endpoint."""
94
+ stats = await self.manager.get_stats()
95
+ return HealthCheckResult(
96
+ healthy=True,
97
+ version='1.0.0',
98
+ uptime=time.time() - self.start_time,
99
+ active_sandboxes=stats['total_sandboxes'],
100
+ system_info=stats
101
+ )
102
+
103
+ # Sandbox management
104
+ @self.app.post('/sandbox/create')
105
+ async def create_sandbox(sandbox_type: SandboxType, config: Optional[Dict] = None):
106
+ """Create a new sandbox."""
107
+ try:
108
+ sandbox_id = await self.manager.create_sandbox(sandbox_type, config)
109
+
110
+ return {'sandbox_id': sandbox_id}
111
+
112
+ except Exception as e:
113
+ raise HTTPException(status_code=400, detail=str(e))
114
+
115
+ @self.app.get('/sandboxes')
116
+ async def list_sandboxes(status: Optional[SandboxStatus] = None):
117
+ """List all sandboxes."""
118
+ sandboxes = await self.manager.list_sandboxes(status)
119
+ return sandboxes
120
+
121
+ @self.app.get('/sandbox/{sandbox_id}', response_model=SandboxInfo)
122
+ async def get_sandbox_info(sandbox_id: str):
123
+ """Get sandbox information."""
124
+ info = await self.manager.get_sandbox_info(sandbox_id)
125
+ if not info:
126
+ raise HTTPException(status_code=404, detail='Sandbox not found')
127
+ return info
128
+
129
+ @self.app.post('/sandbox/{sandbox_id}/stop')
130
+ async def stop_sandbox(sandbox_id: str):
131
+ """Stop a sandbox."""
132
+ success = await self.manager.stop_sandbox(sandbox_id)
133
+ if not success:
134
+ raise HTTPException(status_code=404, detail='Sandbox not found')
135
+ return {'message': 'Sandbox stopped successfully'}
136
+
137
+ @self.app.delete('/sandbox/{sandbox_id}')
138
+ async def delete_sandbox(sandbox_id: str):
139
+ """Delete a sandbox."""
140
+ success = await self.manager.delete_sandbox(sandbox_id)
141
+ if not success:
142
+ raise HTTPException(status_code=404, detail='Sandbox not found')
143
+ return {'message': 'Sandbox deleted successfully'}
144
+
145
+ # Tool execution
146
+ @self.app.post('/sandbox/tool/execute', response_model=ToolResult)
147
+ async def execute_tool(request: ToolExecutionRequest):
148
+ """Execute tool in sandbox."""
149
+ try:
150
+ result = await self.manager.execute_tool(request.sandbox_id, request.tool_name, request.parameters)
151
+ return result
152
+ except ValueError as e:
153
+ raise HTTPException(status_code=404, detail=str(e))
154
+ except Exception as e:
155
+ raise HTTPException(status_code=500, detail=str(e))
156
+
157
+ @self.app.get('/sandbox/{sandbox_id}/tools')
158
+ async def get_sandbox_tools(sandbox_id: str):
159
+ """Get available tools for a sandbox."""
160
+ try:
161
+ tools = await self.manager.get_sandbox_tools(sandbox_id)
162
+ return tools
163
+ except ValueError as e:
164
+ raise HTTPException(status_code=404, detail=str(e))
165
+
166
+ # System info
167
+ @self.app.get('/stats')
168
+ async def get_stats():
169
+ """Get system statistics."""
170
+ return await self.manager.get_stats()
171
+
172
+ def run(self, host: str = '0.0.0.0', port: int = 8000, **kwargs):
173
+ """Run the server.
174
+
175
+ Args:
176
+ host: Host to bind to
177
+ port: Port to bind to
178
+ **kwargs: Additional uvicorn arguments
179
+ """
180
+ import uvicorn
181
+ uvicorn.run(self.app, host=host, port=port, **kwargs)
182
+
183
+
184
+ # Create a default server instance
185
+ def create_server(cleanup_interval: int = 300, api_key: Optional[str] = None) -> SandboxServer:
186
+ """Create a sandbox server instance.
187
+
188
+ Args:
189
+ cleanup_interval: Cleanup interval in seconds
190
+ api_key: Optional API key to protect endpoints. If None, auth is disabled.
191
+
192
+ Returns:
193
+ Sandbox server instance
194
+ """
195
+ return SandboxServer(cleanup_interval, api_key)
@@ -0,0 +1,4 @@
1
+ """Tool interfaces and implementations."""
2
+
3
+ from .base import Tool, ToolFactory, register_tool
4
+ from .sandbox_tools import FileOperation, PythonExecutor, ShellExecutor
@@ -0,0 +1,95 @@
1
+ """Base tool interface and factory."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Type
5
+
6
+ from ..model import SandboxType, ToolResult
7
+ from .tool_info import ToolParams
8
+
9
+ if TYPE_CHECKING:
10
+ from ms_enclave.sandbox.boxes import Sandbox
11
+
12
+
13
+ class Tool(ABC):
14
+ """Abstract base class for all tools."""
15
+
16
+ def __init__(
17
+ self,
18
+ *,
19
+ name: Optional[str] = None,
20
+ description: Optional[str] = None,
21
+ parameters: Optional[ToolParams] = None,
22
+ enabled: bool = True,
23
+ timeout: Optional[int] = None,
24
+ **kwargs,
25
+ ):
26
+ self._name = name or self.__class__.__name__
27
+ self._description = description
28
+ self._parameters = parameters
29
+ self.enabled = enabled
30
+ self.timeout = timeout
31
+
32
+ @property
33
+ def name(self) -> str:
34
+ return self._name
35
+
36
+ @property
37
+ def description(self) -> Optional[str]:
38
+ return self._description
39
+
40
+ @property
41
+ def parameters(self) -> Optional[ToolParams]:
42
+ return self._parameters
43
+
44
+ @property
45
+ def schema(self) -> Dict:
46
+ return {
47
+ 'type': 'function',
48
+ 'function': {
49
+ 'name': self.name,
50
+ 'description': self._description,
51
+ 'parameters': self._parameters.model_dump(exclude_none=True) if self._parameters else {},
52
+ },
53
+ }
54
+
55
+ @property
56
+ @abstractmethod
57
+ def required_sandbox_type(self) -> Optional[SandboxType]:
58
+ """Get the required sandbox type for this tool."""
59
+ pass
60
+
61
+ @abstractmethod
62
+ async def execute(self, sandbox_context: 'Sandbox', **kwargs) -> ToolResult:
63
+ """Execute the tool with given sandbox context and parameters."""
64
+ pass
65
+
66
+
67
+ class ToolFactory:
68
+ """Factory for creating tool instances."""
69
+
70
+ _tools: Dict[str, Type[Tool]] = {}
71
+
72
+ @classmethod
73
+ def register_tool(cls, tool_name: str, tool_class: Type[Tool]):
74
+ cls._tools[tool_name] = tool_class
75
+
76
+ @classmethod
77
+ def create_tool(cls, tool_name: str, **kwargs) -> Tool:
78
+ if tool_name not in cls._tools:
79
+ raise ValueError(f'Tool name {tool_name} is not registered')
80
+
81
+ tool_class = cls._tools[tool_name]
82
+ return tool_class(**kwargs)
83
+
84
+ @classmethod
85
+ def get_available_tools(cls) -> list[str]:
86
+ return list(cls._tools.keys())
87
+
88
+
89
+ def register_tool(tool_name: str):
90
+
91
+ def decorator(tool_class: Type[Tool]):
92
+ ToolFactory.register_tool(tool_name, tool_class)
93
+ return tool_class
94
+
95
+ return decorator
@@ -0,0 +1,46 @@
1
+ from typing import Any, Dict, Optional
2
+
3
+ from ..model import SandboxType
4
+ from .base import Tool
5
+ from .tool_info import ToolParams
6
+
7
+
8
+ class SandboxTool(Tool):
9
+ """Built-in tool class"""
10
+
11
+ _name: Optional[str] = None
12
+ _description: Optional[str] = None
13
+ _parameters: Optional[ToolParams] = None
14
+ _sandbox_type: Optional[SandboxType] = None
15
+
16
+ def __init__(
17
+ self,
18
+ *,
19
+ name: Optional[str] = None,
20
+ description: Optional[str] = None,
21
+ parameters: Optional[ToolParams] = None,
22
+ sandbox: Optional[Any] = None,
23
+ sandbox_type: Optional[SandboxType] = None,
24
+ **kwargs,
25
+ ):
26
+ """
27
+ Initialize the tool.
28
+
29
+ Note: Once the sandbox is set, it does not change.
30
+ """
31
+ super().__init__(
32
+ name=name,
33
+ description=description,
34
+ parameters=parameters,
35
+ **kwargs,
36
+ )
37
+ self._sandbox = sandbox
38
+ self._name = name or self.__class__._name
39
+ self._description = description or self.__class__._description
40
+ self._parameters = parameters or self.__class__._parameters
41
+ self._sandbox_type = sandbox_type or self.__class__._sandbox_type
42
+
43
+ @property
44
+ def required_sandbox_type(self) -> Optional[SandboxType]:
45
+ """Get the required sandbox type for this tool."""
46
+ return self._sandbox_type
@@ -0,0 +1,4 @@
1
+ from .file_operation import FileOperation
2
+ from .notebook_executor import NotebookExecutor
3
+ from .python_executor import PythonExecutor
4
+ from .shell_executor import ShellExecutor