moru 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.
- moru/__init__.py +174 -0
- moru/api/__init__.py +164 -0
- moru/api/client/__init__.py +8 -0
- moru/api/client/api/__init__.py +1 -0
- moru/api/client/api/sandboxes/__init__.py +1 -0
- moru/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
- moru/api/client/api/sandboxes/get_sandboxes.py +176 -0
- moru/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
- moru/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
- moru/api/client/api/sandboxes/post_sandboxes.py +172 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
- moru/api/client/api/templates/__init__.py +1 -0
- moru/api/client/api/templates/delete_templates_template_id.py +157 -0
- moru/api/client/api/templates/get_templates.py +172 -0
- moru/api/client/api/templates/get_templates_template_id.py +195 -0
- moru/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +217 -0
- moru/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
- moru/api/client/api/templates/patch_templates_template_id.py +183 -0
- moru/api/client/api/templates/post_templates.py +172 -0
- moru/api/client/api/templates/post_templates_template_id.py +181 -0
- moru/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
- moru/api/client/api/templates/post_v2_templates.py +172 -0
- moru/api/client/api/templates/post_v3_templates.py +172 -0
- moru/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
- moru/api/client/client.py +286 -0
- moru/api/client/errors.py +16 -0
- moru/api/client/models/__init__.py +123 -0
- moru/api/client/models/aws_registry.py +85 -0
- moru/api/client/models/aws_registry_type.py +8 -0
- moru/api/client/models/build_log_entry.py +89 -0
- moru/api/client/models/build_status_reason.py +95 -0
- moru/api/client/models/connect_sandbox.py +59 -0
- moru/api/client/models/created_access_token.py +100 -0
- moru/api/client/models/created_team_api_key.py +166 -0
- moru/api/client/models/disk_metrics.py +91 -0
- moru/api/client/models/error.py +67 -0
- moru/api/client/models/gcp_registry.py +69 -0
- moru/api/client/models/gcp_registry_type.py +8 -0
- moru/api/client/models/general_registry.py +77 -0
- moru/api/client/models/general_registry_type.py +8 -0
- moru/api/client/models/identifier_masking_details.py +83 -0
- moru/api/client/models/listed_sandbox.py +154 -0
- moru/api/client/models/log_level.py +11 -0
- moru/api/client/models/max_team_metric.py +78 -0
- moru/api/client/models/mcp_type_0.py +44 -0
- moru/api/client/models/new_access_token.py +59 -0
- moru/api/client/models/new_sandbox.py +172 -0
- moru/api/client/models/new_team_api_key.py +59 -0
- moru/api/client/models/node.py +155 -0
- moru/api/client/models/node_detail.py +165 -0
- moru/api/client/models/node_metrics.py +122 -0
- moru/api/client/models/node_status.py +11 -0
- moru/api/client/models/node_status_change.py +79 -0
- moru/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
- moru/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
- moru/api/client/models/resumed_sandbox.py +68 -0
- moru/api/client/models/sandbox.py +145 -0
- moru/api/client/models/sandbox_detail.py +183 -0
- moru/api/client/models/sandbox_log.py +70 -0
- moru/api/client/models/sandbox_log_entry.py +93 -0
- moru/api/client/models/sandbox_log_entry_fields.py +44 -0
- moru/api/client/models/sandbox_logs.py +91 -0
- moru/api/client/models/sandbox_metric.py +118 -0
- moru/api/client/models/sandbox_network_config.py +92 -0
- moru/api/client/models/sandbox_state.py +9 -0
- moru/api/client/models/sandboxes_with_metrics.py +59 -0
- moru/api/client/models/team.py +83 -0
- moru/api/client/models/team_api_key.py +158 -0
- moru/api/client/models/team_metric.py +86 -0
- moru/api/client/models/team_user.py +68 -0
- moru/api/client/models/template.py +217 -0
- moru/api/client/models/template_build.py +139 -0
- moru/api/client/models/template_build_file_upload.py +70 -0
- moru/api/client/models/template_build_info.py +126 -0
- moru/api/client/models/template_build_request.py +115 -0
- moru/api/client/models/template_build_request_v2.py +88 -0
- moru/api/client/models/template_build_request_v3.py +88 -0
- moru/api/client/models/template_build_start_v2.py +184 -0
- moru/api/client/models/template_build_status.py +11 -0
- moru/api/client/models/template_legacy.py +207 -0
- moru/api/client/models/template_request_response_v3.py +83 -0
- moru/api/client/models/template_step.py +91 -0
- moru/api/client/models/template_update_request.py +59 -0
- moru/api/client/models/template_with_builds.py +148 -0
- moru/api/client/models/update_team_api_key.py +59 -0
- moru/api/client/py.typed +1 -0
- moru/api/client/types.py +54 -0
- moru/api/client_async/__init__.py +50 -0
- moru/api/client_sync/__init__.py +52 -0
- moru/api/metadata.py +14 -0
- moru/connection_config.py +217 -0
- moru/envd/api.py +59 -0
- moru/envd/filesystem/filesystem_connect.py +193 -0
- moru/envd/filesystem/filesystem_pb2.py +76 -0
- moru/envd/filesystem/filesystem_pb2.pyi +233 -0
- moru/envd/process/process_connect.py +155 -0
- moru/envd/process/process_pb2.py +92 -0
- moru/envd/process/process_pb2.pyi +304 -0
- moru/envd/rpc.py +61 -0
- moru/envd/versions.py +6 -0
- moru/exceptions.py +95 -0
- moru/sandbox/commands/command_handle.py +69 -0
- moru/sandbox/commands/main.py +39 -0
- moru/sandbox/filesystem/filesystem.py +94 -0
- moru/sandbox/filesystem/watch_handle.py +60 -0
- moru/sandbox/main.py +210 -0
- moru/sandbox/mcp.py +1120 -0
- moru/sandbox/network.py +8 -0
- moru/sandbox/sandbox_api.py +210 -0
- moru/sandbox/signature.py +45 -0
- moru/sandbox/utils.py +34 -0
- moru/sandbox_async/commands/command.py +336 -0
- moru/sandbox_async/commands/command_handle.py +196 -0
- moru/sandbox_async/commands/pty.py +240 -0
- moru/sandbox_async/filesystem/filesystem.py +531 -0
- moru/sandbox_async/filesystem/watch_handle.py +62 -0
- moru/sandbox_async/main.py +734 -0
- moru/sandbox_async/paginator.py +69 -0
- moru/sandbox_async/sandbox_api.py +325 -0
- moru/sandbox_async/utils.py +7 -0
- moru/sandbox_sync/commands/command.py +328 -0
- moru/sandbox_sync/commands/command_handle.py +150 -0
- moru/sandbox_sync/commands/pty.py +230 -0
- moru/sandbox_sync/filesystem/filesystem.py +518 -0
- moru/sandbox_sync/filesystem/watch_handle.py +69 -0
- moru/sandbox_sync/main.py +726 -0
- moru/sandbox_sync/paginator.py +69 -0
- moru/sandbox_sync/sandbox_api.py +308 -0
- moru/template/consts.py +30 -0
- moru/template/dockerfile_parser.py +275 -0
- moru/template/logger.py +232 -0
- moru/template/main.py +1360 -0
- moru/template/readycmd.py +138 -0
- moru/template/types.py +105 -0
- moru/template/utils.py +320 -0
- moru/template_async/build_api.py +202 -0
- moru/template_async/main.py +366 -0
- moru/template_sync/build_api.py +199 -0
- moru/template_sync/main.py +371 -0
- moru-0.1.0.dist-info/METADATA +63 -0
- moru-0.1.0.dist-info/RECORD +152 -0
- moru-0.1.0.dist-info/WHEEL +4 -0
- moru-0.1.0.dist-info/licenses/LICENSE +9 -0
- moru_connect/__init__.py +1 -0
- moru_connect/client.py +493 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Dict, List, Optional, overload
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from packaging.version import Version
|
|
9
|
+
from typing_extensions import Self, Unpack
|
|
10
|
+
|
|
11
|
+
from moru.api.client.types import Unset
|
|
12
|
+
from moru.connection_config import ApiParams, ConnectionConfig
|
|
13
|
+
from moru.envd.api import ENVD_API_HEALTH_ROUTE, ahandle_envd_api_exception
|
|
14
|
+
from moru.envd.versions import ENVD_DEBUG_FALLBACK
|
|
15
|
+
from moru.exceptions import SandboxException, format_request_timeout_error
|
|
16
|
+
from moru.sandbox.main import SandboxOpts
|
|
17
|
+
from moru.sandbox.sandbox_api import McpServer, SandboxMetrics, SandboxNetworkOpts
|
|
18
|
+
from moru.sandbox.utils import class_method_variant
|
|
19
|
+
from moru.sandbox_async.commands.command import Commands
|
|
20
|
+
from moru.sandbox_async.commands.pty import Pty
|
|
21
|
+
from moru.sandbox_async.filesystem.filesystem import Filesystem
|
|
22
|
+
from moru.sandbox_async.sandbox_api import SandboxApi, SandboxInfo
|
|
23
|
+
from moru.api.client_async import get_transport
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AsyncSandbox(SandboxApi):
|
|
29
|
+
"""
|
|
30
|
+
Moru cloud sandbox is a secure and isolated cloud environment.
|
|
31
|
+
|
|
32
|
+
The sandbox allows you to:
|
|
33
|
+
- Access Linux OS
|
|
34
|
+
- Create, list, and delete files and directories
|
|
35
|
+
- Run commands
|
|
36
|
+
- Run isolated code
|
|
37
|
+
- Access the internet
|
|
38
|
+
|
|
39
|
+
Check docs [here](https://moru.io/docs).
|
|
40
|
+
|
|
41
|
+
Use the `AsyncSandbox.create()` to create a new sandbox.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
```python
|
|
45
|
+
from moru import AsyncSandbox
|
|
46
|
+
|
|
47
|
+
sandbox = await AsyncSandbox.create()
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def files(self) -> Filesystem:
|
|
53
|
+
"""
|
|
54
|
+
Module for interacting with the sandbox filesystem.
|
|
55
|
+
"""
|
|
56
|
+
return self._filesystem
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def commands(self) -> Commands:
|
|
60
|
+
"""
|
|
61
|
+
Module for running commands in the sandbox.
|
|
62
|
+
"""
|
|
63
|
+
return self._commands
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def pty(self) -> Pty:
|
|
67
|
+
"""
|
|
68
|
+
Module for interacting with the sandbox pseudo-terminal.
|
|
69
|
+
"""
|
|
70
|
+
return self._pty
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
**opts: Unpack[SandboxOpts],
|
|
75
|
+
):
|
|
76
|
+
"""
|
|
77
|
+
Use `AsyncSandbox.create()` to create a new sandbox instead.
|
|
78
|
+
"""
|
|
79
|
+
super().__init__(**opts)
|
|
80
|
+
|
|
81
|
+
self._transport = get_transport(self.connection_config)
|
|
82
|
+
self._envd_api = httpx.AsyncClient(
|
|
83
|
+
base_url=self.connection_config.get_sandbox_url(
|
|
84
|
+
self.sandbox_id, self.sandbox_domain
|
|
85
|
+
),
|
|
86
|
+
transport=self._transport,
|
|
87
|
+
headers=self.connection_config.sandbox_headers,
|
|
88
|
+
)
|
|
89
|
+
self._filesystem = Filesystem(
|
|
90
|
+
self.envd_api_url,
|
|
91
|
+
self._envd_version,
|
|
92
|
+
self.connection_config,
|
|
93
|
+
self._transport.pool,
|
|
94
|
+
self._envd_api,
|
|
95
|
+
)
|
|
96
|
+
self._commands = Commands(
|
|
97
|
+
self.envd_api_url,
|
|
98
|
+
self.connection_config,
|
|
99
|
+
self._transport.pool,
|
|
100
|
+
self._envd_version,
|
|
101
|
+
)
|
|
102
|
+
self._pty = Pty(
|
|
103
|
+
self.envd_api_url,
|
|
104
|
+
self.connection_config,
|
|
105
|
+
self._transport.pool,
|
|
106
|
+
self._envd_version,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def is_running(self, request_timeout: Optional[float] = None) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Check if the sandbox is running.
|
|
112
|
+
|
|
113
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
114
|
+
|
|
115
|
+
:return: `True` if the sandbox is running, `False` otherwise
|
|
116
|
+
|
|
117
|
+
Example
|
|
118
|
+
```python
|
|
119
|
+
sandbox = await AsyncSandbox.create()
|
|
120
|
+
await sandbox.is_running() # Returns True
|
|
121
|
+
|
|
122
|
+
await sandbox.kill()
|
|
123
|
+
await sandbox.is_running() # Returns False
|
|
124
|
+
```
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
r = await self._envd_api.get(
|
|
128
|
+
ENVD_API_HEALTH_ROUTE,
|
|
129
|
+
timeout=self.connection_config.get_request_timeout(request_timeout),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if r.status_code == 502:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
err = await ahandle_envd_api_exception(r)
|
|
136
|
+
|
|
137
|
+
if err:
|
|
138
|
+
raise err
|
|
139
|
+
|
|
140
|
+
except httpx.TimeoutException:
|
|
141
|
+
raise format_request_timeout_error()
|
|
142
|
+
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
async def create(
|
|
147
|
+
cls,
|
|
148
|
+
template: Optional[str] = None,
|
|
149
|
+
timeout: Optional[int] = None,
|
|
150
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
151
|
+
envs: Optional[Dict[str, str]] = None,
|
|
152
|
+
secure: bool = True,
|
|
153
|
+
allow_internet_access: bool = True,
|
|
154
|
+
mcp: Optional[McpServer] = None,
|
|
155
|
+
network: Optional[SandboxNetworkOpts] = None,
|
|
156
|
+
**opts: Unpack[ApiParams],
|
|
157
|
+
) -> Self:
|
|
158
|
+
"""
|
|
159
|
+
Create a new sandbox.
|
|
160
|
+
|
|
161
|
+
By default, the sandbox is created from the default `base` sandbox template.
|
|
162
|
+
|
|
163
|
+
:param template: Sandbox template name or ID
|
|
164
|
+
:param timeout: Timeout for the sandbox in **seconds**, default to 300 seconds. The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
|
|
165
|
+
:param metadata: Custom metadata for the sandbox
|
|
166
|
+
:param envs: Custom environment variables for the sandbox
|
|
167
|
+
:param secure: Envd is secured with access token and cannot be used without it, defaults to `True`.
|
|
168
|
+
:param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. If set to `False`, it works the same as setting network `deny_out` to `[0.0.0.0/0]`.
|
|
169
|
+
:param mcp: MCP server to enable in the sandbox
|
|
170
|
+
:param network: Sandbox network configuration
|
|
171
|
+
|
|
172
|
+
:return: A Sandbox instance for the new sandbox
|
|
173
|
+
|
|
174
|
+
Use this method instead of using the constructor to create a new sandbox.
|
|
175
|
+
"""
|
|
176
|
+
if not template and mcp is not None:
|
|
177
|
+
template = cls.default_mcp_template
|
|
178
|
+
elif not template:
|
|
179
|
+
template = cls.default_template
|
|
180
|
+
|
|
181
|
+
sandbox = await cls._create(
|
|
182
|
+
template=template,
|
|
183
|
+
timeout=timeout,
|
|
184
|
+
auto_pause=False,
|
|
185
|
+
metadata=metadata,
|
|
186
|
+
envs=envs,
|
|
187
|
+
secure=secure,
|
|
188
|
+
allow_internet_access=allow_internet_access,
|
|
189
|
+
mcp=mcp,
|
|
190
|
+
network=network,
|
|
191
|
+
**opts,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if mcp is not None:
|
|
195
|
+
token = str(uuid.uuid4())
|
|
196
|
+
sandbox._mcp_token = token
|
|
197
|
+
|
|
198
|
+
res = await sandbox.commands.run(
|
|
199
|
+
f"mcp-gateway --config '{json.dumps(mcp)}'",
|
|
200
|
+
user="root",
|
|
201
|
+
envs={"GATEWAY_ACCESS_TOKEN": token},
|
|
202
|
+
)
|
|
203
|
+
if res.exit_code != 0:
|
|
204
|
+
raise Exception(f"Failed to start MCP gateway: {res.stderr}")
|
|
205
|
+
|
|
206
|
+
return sandbox
|
|
207
|
+
|
|
208
|
+
@overload
|
|
209
|
+
async def connect(
|
|
210
|
+
self,
|
|
211
|
+
timeout: Optional[int] = None,
|
|
212
|
+
**opts: Unpack[ApiParams],
|
|
213
|
+
) -> Self:
|
|
214
|
+
"""
|
|
215
|
+
Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
|
|
216
|
+
Sandbox must be either running or be paused.
|
|
217
|
+
|
|
218
|
+
With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
|
|
219
|
+
|
|
220
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
221
|
+
For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
|
|
222
|
+
:return: A running sandbox instance
|
|
223
|
+
|
|
224
|
+
@example
|
|
225
|
+
```python
|
|
226
|
+
sandbox = await AsyncSandbox.create()
|
|
227
|
+
await sandbox.beta_pause()
|
|
228
|
+
|
|
229
|
+
# Another code block
|
|
230
|
+
same_sandbox = await sandbox.connect()
|
|
231
|
+
```
|
|
232
|
+
"""
|
|
233
|
+
...
|
|
234
|
+
|
|
235
|
+
@overload
|
|
236
|
+
@classmethod
|
|
237
|
+
async def connect(
|
|
238
|
+
cls,
|
|
239
|
+
sandbox_id: str,
|
|
240
|
+
timeout: Optional[int] = None,
|
|
241
|
+
**opts: Unpack[ApiParams],
|
|
242
|
+
) -> Self:
|
|
243
|
+
"""
|
|
244
|
+
Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
|
|
245
|
+
Sandbox must be either running or be paused.
|
|
246
|
+
|
|
247
|
+
With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
|
|
248
|
+
|
|
249
|
+
:param sandbox_id: Sandbox ID
|
|
250
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
251
|
+
For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
|
|
252
|
+
:return: A running sandbox instance
|
|
253
|
+
|
|
254
|
+
@example
|
|
255
|
+
```python
|
|
256
|
+
sandbox = await AsyncSandbox.create()
|
|
257
|
+
await AsyncSandbox.beta_pause(sandbox.sandbox_id)
|
|
258
|
+
|
|
259
|
+
# Another code block
|
|
260
|
+
same_sandbox = await AsyncSandbox.connect(sandbox.sandbox_id))
|
|
261
|
+
```
|
|
262
|
+
"""
|
|
263
|
+
...
|
|
264
|
+
|
|
265
|
+
@class_method_variant("_cls_connect")
|
|
266
|
+
async def connect(
|
|
267
|
+
self,
|
|
268
|
+
timeout: Optional[int] = None,
|
|
269
|
+
**opts: Unpack[ApiParams],
|
|
270
|
+
) -> Self:
|
|
271
|
+
"""
|
|
272
|
+
Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
|
|
273
|
+
Sandbox must be either running or be paused.
|
|
274
|
+
|
|
275
|
+
With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
|
|
276
|
+
|
|
277
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
278
|
+
For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
|
|
279
|
+
:return: A running sandbox instance
|
|
280
|
+
|
|
281
|
+
@example
|
|
282
|
+
```python
|
|
283
|
+
sandbox = await AsyncSandbox.create()
|
|
284
|
+
await sandbox.beta_pause()
|
|
285
|
+
|
|
286
|
+
# Another code block
|
|
287
|
+
same_sandbox = await sandbox.connect()
|
|
288
|
+
```
|
|
289
|
+
"""
|
|
290
|
+
await SandboxApi._cls_connect(
|
|
291
|
+
sandbox_id=self.sandbox_id,
|
|
292
|
+
timeout=timeout,
|
|
293
|
+
**opts,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return self
|
|
297
|
+
|
|
298
|
+
async def __aenter__(self):
|
|
299
|
+
return self
|
|
300
|
+
|
|
301
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
302
|
+
await self.kill()
|
|
303
|
+
|
|
304
|
+
@overload
|
|
305
|
+
async def kill(
|
|
306
|
+
self,
|
|
307
|
+
**opts: Unpack[ApiParams],
|
|
308
|
+
) -> bool:
|
|
309
|
+
"""
|
|
310
|
+
Kill the sandbox.
|
|
311
|
+
|
|
312
|
+
:return: `True` if the sandbox was killed, `False` if the sandbox was not found
|
|
313
|
+
"""
|
|
314
|
+
...
|
|
315
|
+
|
|
316
|
+
@overload
|
|
317
|
+
@staticmethod
|
|
318
|
+
async def kill(
|
|
319
|
+
sandbox_id: str,
|
|
320
|
+
**opts: Unpack[ApiParams],
|
|
321
|
+
) -> bool:
|
|
322
|
+
"""
|
|
323
|
+
Kill the sandbox specified by sandbox ID.
|
|
324
|
+
|
|
325
|
+
:param sandbox_id: Sandbox ID
|
|
326
|
+
|
|
327
|
+
:return: `True` if the sandbox was killed, `False` if the sandbox was not found
|
|
328
|
+
"""
|
|
329
|
+
...
|
|
330
|
+
|
|
331
|
+
@class_method_variant("_cls_kill")
|
|
332
|
+
async def kill(
|
|
333
|
+
self,
|
|
334
|
+
**opts: Unpack[ApiParams],
|
|
335
|
+
) -> bool:
|
|
336
|
+
"""
|
|
337
|
+
Kill the sandbox specified by sandbox ID.
|
|
338
|
+
|
|
339
|
+
:return: `True` if the sandbox was killed, `False` if the sandbox was not found
|
|
340
|
+
"""
|
|
341
|
+
return await SandboxApi._cls_kill(
|
|
342
|
+
sandbox_id=self.sandbox_id,
|
|
343
|
+
**self.connection_config.get_api_params(**opts),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
@overload
|
|
347
|
+
async def set_timeout(
|
|
348
|
+
self,
|
|
349
|
+
timeout: int,
|
|
350
|
+
**opts: Unpack[ApiParams],
|
|
351
|
+
) -> None:
|
|
352
|
+
"""
|
|
353
|
+
Set the timeout of the sandbox.
|
|
354
|
+
After the timeout expires, the sandbox will be automatically killed.
|
|
355
|
+
This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to `.set_timeout`.
|
|
356
|
+
|
|
357
|
+
The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
|
|
358
|
+
|
|
359
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
360
|
+
"""
|
|
361
|
+
...
|
|
362
|
+
|
|
363
|
+
@overload
|
|
364
|
+
@staticmethod
|
|
365
|
+
async def set_timeout(
|
|
366
|
+
sandbox_id: str,
|
|
367
|
+
timeout: int,
|
|
368
|
+
**opts: Unpack[ApiParams],
|
|
369
|
+
) -> None:
|
|
370
|
+
"""
|
|
371
|
+
Set the timeout of the specified sandbox.
|
|
372
|
+
After the timeout expires, the sandbox will be automatically killed.
|
|
373
|
+
This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to `.set_timeout`.
|
|
374
|
+
|
|
375
|
+
The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
|
|
376
|
+
|
|
377
|
+
:param sandbox_id: Sandbox ID
|
|
378
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
379
|
+
"""
|
|
380
|
+
...
|
|
381
|
+
|
|
382
|
+
@class_method_variant("_cls_set_timeout")
|
|
383
|
+
async def set_timeout(
|
|
384
|
+
self,
|
|
385
|
+
timeout: int,
|
|
386
|
+
**opts: Unpack[ApiParams],
|
|
387
|
+
) -> None:
|
|
388
|
+
"""
|
|
389
|
+
Set the timeout of the specified sandbox.
|
|
390
|
+
After the timeout expires, the sandbox will be automatically killed.
|
|
391
|
+
This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to `.set_timeout`.
|
|
392
|
+
|
|
393
|
+
The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
|
|
394
|
+
|
|
395
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
396
|
+
"""
|
|
397
|
+
await SandboxApi._cls_set_timeout(
|
|
398
|
+
sandbox_id=self.sandbox_id,
|
|
399
|
+
timeout=timeout,
|
|
400
|
+
**self.connection_config.get_api_params(**opts),
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
@overload
|
|
404
|
+
async def get_info(
|
|
405
|
+
self,
|
|
406
|
+
**opts: Unpack[ApiParams],
|
|
407
|
+
) -> SandboxInfo:
|
|
408
|
+
"""
|
|
409
|
+
Get sandbox information like sandbox ID, template, metadata, started at/end at date.
|
|
410
|
+
|
|
411
|
+
:return: Sandbox info
|
|
412
|
+
"""
|
|
413
|
+
...
|
|
414
|
+
|
|
415
|
+
@overload
|
|
416
|
+
@staticmethod
|
|
417
|
+
async def get_info(
|
|
418
|
+
sandbox_id: str,
|
|
419
|
+
**opts: Unpack[ApiParams],
|
|
420
|
+
) -> SandboxInfo:
|
|
421
|
+
"""
|
|
422
|
+
Get sandbox information like sandbox ID, template, metadata, started at/end at date.
|
|
423
|
+
:param sandbox_id: Sandbox ID
|
|
424
|
+
|
|
425
|
+
:return: Sandbox info
|
|
426
|
+
"""
|
|
427
|
+
...
|
|
428
|
+
|
|
429
|
+
@class_method_variant("_cls_get_info")
|
|
430
|
+
async def get_info(
|
|
431
|
+
self,
|
|
432
|
+
**opts: Unpack[ApiParams],
|
|
433
|
+
) -> SandboxInfo:
|
|
434
|
+
"""
|
|
435
|
+
Get sandbox information like sandbox ID, template, metadata, started at/end at date.
|
|
436
|
+
|
|
437
|
+
:return: Sandbox info
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
return await SandboxApi._cls_get_info(
|
|
441
|
+
sandbox_id=self.sandbox_id,
|
|
442
|
+
**self.connection_config.get_api_params(**opts),
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
@overload
|
|
446
|
+
async def get_metrics(
|
|
447
|
+
self,
|
|
448
|
+
start: Optional[datetime.datetime] = None,
|
|
449
|
+
end: Optional[datetime.datetime] = None,
|
|
450
|
+
**opts: Unpack[ApiParams],
|
|
451
|
+
) -> List[SandboxMetrics]:
|
|
452
|
+
"""
|
|
453
|
+
Get the metrics of the current sandbox.
|
|
454
|
+
|
|
455
|
+
:param start: Start time for the metrics, defaults to the start of the sandbox
|
|
456
|
+
:param end: End time for the metrics, defaults to the current time
|
|
457
|
+
|
|
458
|
+
:return: List of sandbox metrics containing CPU, memory and disk usage information
|
|
459
|
+
"""
|
|
460
|
+
...
|
|
461
|
+
|
|
462
|
+
@overload
|
|
463
|
+
@staticmethod
|
|
464
|
+
async def get_metrics(
|
|
465
|
+
sandbox_id: str,
|
|
466
|
+
start: Optional[datetime.datetime] = None,
|
|
467
|
+
end: Optional[datetime.datetime] = None,
|
|
468
|
+
**opts: Unpack[ApiParams],
|
|
469
|
+
) -> List[SandboxMetrics]:
|
|
470
|
+
"""
|
|
471
|
+
Get the metrics of the sandbox specified by sandbox ID.
|
|
472
|
+
|
|
473
|
+
:param sandbox_id: Sandbox ID
|
|
474
|
+
:param start: Start time for the metrics, defaults to the start of the sandbox
|
|
475
|
+
:param end: End time for the metrics, defaults to the current time
|
|
476
|
+
|
|
477
|
+
:return: List of sandbox metrics containing CPU, memory and disk usage information
|
|
478
|
+
"""
|
|
479
|
+
...
|
|
480
|
+
|
|
481
|
+
@class_method_variant("_cls_get_metrics")
|
|
482
|
+
async def get_metrics(
|
|
483
|
+
self,
|
|
484
|
+
start: Optional[datetime.datetime] = None,
|
|
485
|
+
end: Optional[datetime.datetime] = None,
|
|
486
|
+
**opts: Unpack[ApiParams],
|
|
487
|
+
) -> List[SandboxMetrics]:
|
|
488
|
+
"""
|
|
489
|
+
Get the metrics of the current sandbox.
|
|
490
|
+
|
|
491
|
+
:param start: Start time for the metrics, defaults to the start of the sandbox
|
|
492
|
+
:param end: End time for the metrics, defaults to the current time
|
|
493
|
+
|
|
494
|
+
:return: List of sandbox metrics containing CPU, memory and disk usage information
|
|
495
|
+
"""
|
|
496
|
+
if self._envd_version < Version("0.1.5"):
|
|
497
|
+
raise SandboxException(
|
|
498
|
+
"Metrics are not supported in this version of the sandbox, please rebuild your template."
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
if self._envd_version < Version("0.2.4"):
|
|
502
|
+
logger.warning(
|
|
503
|
+
"Disk metrics are not supported in this version of the sandbox, please rebuild the template to get disk metrics."
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
return await SandboxApi._cls_get_metrics(
|
|
507
|
+
sandbox_id=self.sandbox_id,
|
|
508
|
+
start=start,
|
|
509
|
+
end=end,
|
|
510
|
+
**self.connection_config.get_api_params(**opts),
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
@classmethod
|
|
514
|
+
async def beta_create(
|
|
515
|
+
cls,
|
|
516
|
+
template: Optional[str] = None,
|
|
517
|
+
timeout: Optional[int] = None,
|
|
518
|
+
auto_pause: bool = False,
|
|
519
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
520
|
+
envs: Optional[Dict[str, str]] = None,
|
|
521
|
+
secure: bool = True,
|
|
522
|
+
allow_internet_access: bool = True,
|
|
523
|
+
mcp: Optional[McpServer] = None,
|
|
524
|
+
**opts: Unpack[ApiParams],
|
|
525
|
+
) -> Self:
|
|
526
|
+
"""
|
|
527
|
+
[BETA] This feature is in beta and may change in the future.
|
|
528
|
+
|
|
529
|
+
Create a new sandbox.
|
|
530
|
+
|
|
531
|
+
By default, the sandbox is created from the default `base` sandbox template.
|
|
532
|
+
|
|
533
|
+
:param template: Sandbox template name or ID
|
|
534
|
+
:param timeout: Timeout for the sandbox in **seconds**, default to 300 seconds. The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
|
|
535
|
+
:param auto_pause: Automatically pause the sandbox after the timeout expires. Defaults to `False`.
|
|
536
|
+
:param metadata: Custom metadata for the sandbox
|
|
537
|
+
:param envs: Custom environment variables for the sandbox
|
|
538
|
+
:param secure: Envd is secured with access token and cannot be used without it, defaults to `True`.
|
|
539
|
+
:param allow_internet_access: Allow sandbox to access the internet, defaults to `True`.
|
|
540
|
+
:param mcp: MCP server to enable in the sandbox
|
|
541
|
+
|
|
542
|
+
:return: A Sandbox instance for the new sandbox
|
|
543
|
+
|
|
544
|
+
Use this method instead of using the constructor to create a new sandbox.
|
|
545
|
+
"""
|
|
546
|
+
|
|
547
|
+
if not template and mcp is not None:
|
|
548
|
+
template = cls.default_mcp_template
|
|
549
|
+
elif not template:
|
|
550
|
+
template = cls.default_template
|
|
551
|
+
|
|
552
|
+
sandbox = await cls._create(
|
|
553
|
+
template=template,
|
|
554
|
+
timeout=timeout,
|
|
555
|
+
auto_pause=auto_pause,
|
|
556
|
+
metadata=metadata,
|
|
557
|
+
envs=envs,
|
|
558
|
+
secure=secure,
|
|
559
|
+
allow_internet_access=allow_internet_access,
|
|
560
|
+
mcp=mcp,
|
|
561
|
+
**opts,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
if mcp is not None:
|
|
565
|
+
token = str(uuid.uuid4())
|
|
566
|
+
sandbox._mcp_token = token
|
|
567
|
+
|
|
568
|
+
res = await sandbox.commands.run(
|
|
569
|
+
f"mcp-gateway --config '{json.dumps(mcp)}'",
|
|
570
|
+
user="root",
|
|
571
|
+
envs={"GATEWAY_ACCESS_TOKEN": token},
|
|
572
|
+
)
|
|
573
|
+
if res.exit_code != 0:
|
|
574
|
+
raise Exception(f"Failed to start MCP gateway: {res.stderr}")
|
|
575
|
+
|
|
576
|
+
return sandbox
|
|
577
|
+
|
|
578
|
+
@overload
|
|
579
|
+
async def beta_pause(
|
|
580
|
+
self,
|
|
581
|
+
**opts: Unpack[ApiParams],
|
|
582
|
+
) -> None:
|
|
583
|
+
"""
|
|
584
|
+
[BETA] This feature is in beta and may change in the future.
|
|
585
|
+
|
|
586
|
+
Pause the sandbox.
|
|
587
|
+
|
|
588
|
+
:return: Sandbox ID that can be used to resume the sandbox
|
|
589
|
+
"""
|
|
590
|
+
...
|
|
591
|
+
|
|
592
|
+
@overload
|
|
593
|
+
@staticmethod
|
|
594
|
+
async def beta_pause(
|
|
595
|
+
sandbox_id: str,
|
|
596
|
+
**opts: Unpack[ApiParams],
|
|
597
|
+
) -> None:
|
|
598
|
+
"""
|
|
599
|
+
[BETA] This feature is in beta and may change in the future.
|
|
600
|
+
|
|
601
|
+
Pause the sandbox specified by sandbox ID.
|
|
602
|
+
|
|
603
|
+
:param sandbox_id: Sandbox ID
|
|
604
|
+
|
|
605
|
+
:return: Sandbox ID that can be used to resume the sandbox
|
|
606
|
+
"""
|
|
607
|
+
...
|
|
608
|
+
|
|
609
|
+
@class_method_variant("_cls_pause")
|
|
610
|
+
async def beta_pause(
|
|
611
|
+
self,
|
|
612
|
+
**opts: Unpack[ApiParams],
|
|
613
|
+
) -> None:
|
|
614
|
+
"""
|
|
615
|
+
[BETA] This feature is in beta and may change in the future.
|
|
616
|
+
|
|
617
|
+
Pause the sandbox.
|
|
618
|
+
|
|
619
|
+
:return: Sandbox ID that can be used to resume the sandbox
|
|
620
|
+
"""
|
|
621
|
+
|
|
622
|
+
await SandboxApi._cls_pause(
|
|
623
|
+
sandbox_id=self.sandbox_id,
|
|
624
|
+
**opts,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
async def get_mcp_token(self) -> Optional[str]:
|
|
628
|
+
"""
|
|
629
|
+
Get the MCP token for the sandbox.
|
|
630
|
+
|
|
631
|
+
:return: MCP token for the sandbox, or None if MCP is not enabled.
|
|
632
|
+
"""
|
|
633
|
+
if not self._mcp_token:
|
|
634
|
+
self._mcp_token = await self.files.read(
|
|
635
|
+
"/etc/mcp-gateway/.token", user="root"
|
|
636
|
+
)
|
|
637
|
+
return self._mcp_token
|
|
638
|
+
|
|
639
|
+
@classmethod
|
|
640
|
+
async def _cls_connect(
|
|
641
|
+
cls,
|
|
642
|
+
sandbox_id: str,
|
|
643
|
+
timeout: Optional[int] = None,
|
|
644
|
+
**opts: Unpack[ApiParams],
|
|
645
|
+
) -> Self:
|
|
646
|
+
sandbox = await SandboxApi._cls_connect(
|
|
647
|
+
sandbox_id=sandbox_id,
|
|
648
|
+
timeout=timeout,
|
|
649
|
+
**opts,
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
sandbox_headers = {}
|
|
653
|
+
envd_access_token = sandbox.envd_access_token
|
|
654
|
+
if envd_access_token is not None and not isinstance(envd_access_token, Unset):
|
|
655
|
+
sandbox_headers["X-Access-Token"] = envd_access_token
|
|
656
|
+
|
|
657
|
+
connection_config = ConnectionConfig(
|
|
658
|
+
extra_sandbox_headers=sandbox_headers,
|
|
659
|
+
**opts,
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
return cls(
|
|
663
|
+
sandbox_id=sandbox.sandbox_id,
|
|
664
|
+
sandbox_domain=sandbox.domain,
|
|
665
|
+
envd_version=Version(sandbox.envd_version),
|
|
666
|
+
envd_access_token=envd_access_token,
|
|
667
|
+
traffic_access_token=sandbox.traffic_access_token,
|
|
668
|
+
connection_config=connection_config,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
@classmethod
|
|
672
|
+
async def _create(
|
|
673
|
+
cls,
|
|
674
|
+
template: Optional[str],
|
|
675
|
+
timeout: Optional[int],
|
|
676
|
+
auto_pause: bool,
|
|
677
|
+
allow_internet_access: bool,
|
|
678
|
+
metadata: Optional[Dict[str, str]],
|
|
679
|
+
envs: Optional[Dict[str, str]],
|
|
680
|
+
secure: bool,
|
|
681
|
+
mcp: Optional[McpServer] = None,
|
|
682
|
+
network: Optional[SandboxNetworkOpts] = None,
|
|
683
|
+
**opts: Unpack[ApiParams],
|
|
684
|
+
) -> Self:
|
|
685
|
+
extra_sandbox_headers = {}
|
|
686
|
+
|
|
687
|
+
debug = opts.get("debug")
|
|
688
|
+
if debug:
|
|
689
|
+
sandbox_id = "debug_sandbox_id"
|
|
690
|
+
sandbox_domain = None
|
|
691
|
+
envd_version = ENVD_DEBUG_FALLBACK
|
|
692
|
+
envd_access_token = None
|
|
693
|
+
traffic_access_token = None
|
|
694
|
+
else:
|
|
695
|
+
response = await SandboxApi._create_sandbox(
|
|
696
|
+
template=template or cls.default_template,
|
|
697
|
+
timeout=timeout or cls.default_sandbox_timeout,
|
|
698
|
+
auto_pause=auto_pause,
|
|
699
|
+
metadata=metadata,
|
|
700
|
+
env_vars=envs,
|
|
701
|
+
secure=secure,
|
|
702
|
+
allow_internet_access=allow_internet_access,
|
|
703
|
+
mcp=mcp,
|
|
704
|
+
network=network,
|
|
705
|
+
**opts,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
sandbox_id = response.sandbox_id
|
|
709
|
+
sandbox_domain = response.sandbox_domain
|
|
710
|
+
envd_version = Version(response.envd_version)
|
|
711
|
+
envd_access_token = response.envd_access_token
|
|
712
|
+
traffic_access_token = response.traffic_access_token
|
|
713
|
+
|
|
714
|
+
if envd_access_token is not None and not isinstance(
|
|
715
|
+
envd_access_token, Unset
|
|
716
|
+
):
|
|
717
|
+
extra_sandbox_headers["X-Access-Token"] = envd_access_token
|
|
718
|
+
|
|
719
|
+
extra_sandbox_headers["Moru-Sandbox-Id"] = sandbox_id
|
|
720
|
+
extra_sandbox_headers["Moru-Sandbox-Port"] = str(ConnectionConfig.envd_port)
|
|
721
|
+
|
|
722
|
+
connection_config = ConnectionConfig(
|
|
723
|
+
extra_sandbox_headers=extra_sandbox_headers,
|
|
724
|
+
**opts,
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
return cls(
|
|
728
|
+
sandbox_id=sandbox_id,
|
|
729
|
+
sandbox_domain=sandbox_domain,
|
|
730
|
+
envd_version=envd_version,
|
|
731
|
+
envd_access_token=envd_access_token,
|
|
732
|
+
traffic_access_token=traffic_access_token,
|
|
733
|
+
connection_config=connection_config,
|
|
734
|
+
)
|