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"