228 lines
6.8 KiB
Python
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"
|