Files
2026-07-13 12:47:58 +08:00

228 lines
6.8 KiB
Python

import urllib.parse
from typing import Optional, TypedDict
from packaging.version import Version
from e2b.connection_config import ConnectionConfig, default_username
from e2b.envd.api import ENVD_API_FILES_ROUTE
from e2b.envd.versions import ENVD_DEFAULT_USER
from e2b.exceptions import InvalidArgumentException
from e2b.sandbox.signature import get_signature
class SandboxOpts(TypedDict):
sandbox_id: str
sandbox_domain: Optional[str]
envd_version: Version
envd_access_token: Optional[str]
traffic_access_token: Optional[str]
connection_config: ConnectionConfig
class SandboxBase:
mcp_port = 50005
default_sandbox_timeout = 300
default_template = "base"
default_mcp_template = "mcp-gateway"
def __init__(
self,
sandbox_id: str,
envd_version: Version,
envd_access_token: Optional[str],
sandbox_domain: Optional[str],
connection_config: ConnectionConfig,
traffic_access_token: Optional[str] = None,
):
self.__connection_config = connection_config
self.__sandbox_id = sandbox_id
self.__sandbox_domain = sandbox_domain or self.connection_config.domain
self.__envd_version = envd_version
self.__envd_access_token = envd_access_token
self.__traffic_access_token = traffic_access_token
self.__envd_api_url = self.connection_config.get_sandbox_url(
self.sandbox_id, self.sandbox_domain
)
self.__envd_direct_url = self.connection_config.get_sandbox_direct_url(
self.sandbox_id, self.sandbox_domain
)
self.__mcp_token: Optional[str] = None
@property
def _envd_access_token(self) -> Optional[str]:
"""Private property to access the envd token"""
return self.__envd_access_token
@property
def _mcp_token(self) -> Optional[str]:
return self.__mcp_token
@_mcp_token.setter
def _mcp_token(self, token: str) -> None:
self.__mcp_token = token
@property
def connection_config(self) -> ConnectionConfig:
return self.__connection_config
@property
def _envd_version(self) -> Version:
return self.__envd_version
@property
def traffic_access_token(self) -> Optional[str]:
return self.__traffic_access_token
@property
def sandbox_domain(self) -> str:
return self.__sandbox_domain
@property
def envd_api_url(self) -> str:
return self.__envd_api_url
@property
def envd_direct_url(self) -> str:
return self.__envd_direct_url
@property
def sandbox_id(self) -> str:
"""
Unique identifier of the sandbox.
"""
return self.__sandbox_id
def _file_url(
self,
path: str,
user: Optional[str] = None,
signature: Optional[str] = None,
signature_expiration: Optional[int] = None,
) -> str:
url = urllib.parse.urljoin(self.envd_direct_url, ENVD_API_FILES_ROUTE)
query = {"path": path} if path else {}
if user:
query["username"] = user
if signature:
query["signature"] = signature
if signature_expiration:
if signature is None:
raise ValueError("signature_expiration requires signature to be set")
query["signature_expiration"] = str(signature_expiration)
params = urllib.parse.urlencode(
query,
quote_via=urllib.parse.quote,
)
url = urllib.parse.urljoin(url, f"?{params}")
return url
def download_url(
self,
path: str,
user: Optional[str] = None,
use_signature_expiration: Optional[int] = None,
) -> str:
"""
Get the URL to download a file from the sandbox.
:param path: Path to the file to download
:param user: User to download the file as
:param use_signature_expiration: Expiration time for the signed URL in seconds
:return: URL for downloading file
"""
use_signature = self._envd_access_token is not None
if not use_signature and use_signature_expiration is not None:
raise InvalidArgumentException(
"Signature expiration can be used only when sandbox is created as secured."
)
username = user
if username is None and self._envd_version < ENVD_DEFAULT_USER:
username = default_username
if use_signature:
signature = get_signature(
path,
"read",
username,
self._envd_access_token,
use_signature_expiration,
)
return self._file_url(
path, username, signature["signature"], signature["expiration"]
)
else:
return self._file_url(path, username)
def upload_url(
self,
path: str,
user: Optional[str] = None,
use_signature_expiration: Optional[int] = None,
) -> str:
"""
Get the URL to upload a file to the sandbox.
You have to send a POST request to this URL with the file as multipart/form-data.
:param path: Path to the file to upload
:param user: User to upload the file as
:param use_signature_expiration: Expiration time for the signed URL in seconds
:return: URL for uploading file
"""
use_signature = self._envd_access_token is not None
if not use_signature and use_signature_expiration is not None:
raise InvalidArgumentException(
"Signature expiration can be used only when sandbox is created as secured."
)
username = user
if username is None and self._envd_version < ENVD_DEFAULT_USER:
username = default_username
if use_signature:
signature = get_signature(
path,
"write",
username,
self._envd_access_token,
use_signature_expiration,
)
return self._file_url(
path, username, signature["signature"], signature["expiration"]
)
else:
return self._file_url(path, username)
def get_host(self, port: int) -> str:
"""
Get the host address to connect to the sandbox.
You can then use this address to connect to the sandbox port from outside the sandbox via HTTP or WebSocket.
:param port: Port to connect to
:return: Host address to connect to
"""
return self.connection_config.get_host(
self.sandbox_id, self.sandbox_domain, port
)
def get_mcp_url(self) -> str:
"""
Get the MCP URL for the sandbox.
:returns MCP URL for the sandbox.
"""
return f"https://{self.get_host(self.mcp_port)}/mcp"