Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/seven-cooks-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"e2b": patch
"@e2b/python-sdk": patch
---

Support overriding sandbox API URL
13 changes: 7 additions & 6 deletions .github/workflows/cli_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ permissions:

jobs:
test:
defaults:
run:
working-directory: ./packages/cli
name: CLI - Build
runs-on: ubuntu-22.04
steps:
Expand All @@ -32,7 +29,6 @@ jobs:

- name: Install pnpm
uses: pnpm/action-setup@v4
id: pnpm-install
with:
version: '${{ env.TOOL_VERSION_PNPM }}'

Expand All @@ -47,15 +43,20 @@ jobs:
- name: Configure pnpm
run: |
pnpm config set auto-install-peers true
pnpm config set exclude-links-from-lockfile true

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Test build
- name: Build the SDK (pre-requisite for the tests)
run: pnpm build
working-directory: ./packages/js-sdk

- name: Build the CLI
run: pnpm build
working-directory: ./packages/cli

- name: Run tests
run: pnpm test
working-directory: ./packages/cli
env:
E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
34 changes: 34 additions & 0 deletions packages/js-sdk/src/connectionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export interface ConnectionOpts {
* @default E2B_API_URL // environment variable or `https://api.${domain}`
*/
apiUrl?: string
/**
* Sandbox Url to use for the API.
* @internal
* @default E2B_SANDBOX_URL // environment variable or `https://${port}-${sandboxID}.${domain}`
*/
sandboxUrl?: string
/**
* If true the SDK starts in the debug mode and connects to the local envd API server.
* @internal
Expand Down Expand Up @@ -62,9 +68,12 @@ export interface ConnectionOpts {
* Configuration for connecting to the API.
*/
export class ConnectionConfig {
public static envdPort = 49983

readonly debug: boolean
readonly domain: string
readonly apiUrl: string
readonly sandboxUrl?: string
readonly logger?: Logger

readonly requestTimeoutMs: number
Expand All @@ -88,6 +97,8 @@ export class ConnectionConfig {
opts?.apiUrl ||
ConnectionConfig.apiUrl ||
(this.debug ? 'http://localhost:3000' : `https://api.${this.domain}`)

this.sandboxUrl = opts?.sandboxUrl || ConnectionConfig.sandboxUrl
}

private static get domain() {
Expand All @@ -98,6 +109,10 @@ export class ConnectionConfig {
return getEnvVar('E2B_API_URL')
}

private static get sandboxUrl() {
return getEnvVar('E2B_SANDBOX_URL')
}

private static get debug() {
return (getEnvVar('E2B_DEBUG') || 'false').toLowerCase() === 'true'
}
Expand All @@ -115,6 +130,25 @@ export class ConnectionConfig {

return timeout ? AbortSignal.timeout(timeout) : undefined
}

getSandboxUrl(
sandboxId: string,
opts: { sandboxDomain: string; envdPort: number }
) {
if (this.sandboxUrl) {
return this.sandboxUrl
}

return `${this.debug ? 'http' : 'https'}://${this.getHost(sandboxId, opts.envdPort, opts.sandboxDomain)}`
}

getHost(sandboxId: string, port: number, sandboxDomain: string) {
if (this.debug) {
return `localhost:${port}`
}

return `${port}-${sandboxId}.${sandboxDomain ?? this.domain}`
}
}

/**
Expand Down
25 changes: 17 additions & 8 deletions packages/js-sdk/src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,15 @@ export class Sandbox extends SandboxApi {
this.sandboxDomain = opts.sandboxDomain ?? this.connectionConfig.domain

this.envdAccessToken = opts.envdAccessToken
this.envdApiUrl = `${
this.connectionConfig.debug ? 'http' : 'https'
}://${this.getHost(this.envdPort)}`
this.envdApiUrl = this.connectionConfig.getSandboxUrl(this.sandboxId, {
sandboxDomain: this.sandboxDomain,
envdPort: this.envdPort,
})

const sandboxHeaders = {
'E2b-Sandbox-Id': this.sandboxId,
'E2b-Sandbox-Port': this.envdPort.toString(),
}

const rpcTransport = createConnectTransport({
baseUrl: this.envdApiUrl,
Expand All @@ -141,6 +147,9 @@ export class Sandbox extends SandboxApi {
new Headers(options?.headers).forEach((value, key) =>
headers.append(key, value)
)
new Headers(sandboxHeaders).forEach((value, key) =>
headers.append(key, value)
)

if (this.envdAccessToken) {
headers.append('X-Access-Token', this.envdAccessToken)
Expand Down Expand Up @@ -459,11 +468,11 @@ export class Sandbox extends SandboxApi {
* ```
*/
getHost(port: number) {
if (this.connectionConfig.debug) {
return `localhost:${port}`
}

return `${port}-${this.sandboxId}.${this.sandboxDomain}`
return this.connectionConfig.getHost(
this.sandboxId,
port,
this.sandboxDomain
)
}

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/js-sdk/src/sandbox/sandboxApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ export interface SandboxOpts extends ConnectionOpts {
* @default undefined
*/
mcp?: McpServer

/**
* Sandbox URL. Used for local development
*/
sandboxUrl?: string
}

export type SandboxBetaCreateOpts = SandboxOpts & {
Expand Down
34 changes: 34 additions & 0 deletions packages/python-sdk/e2b/connection_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,17 @@ class ApiParams(TypedDict, total=False):
proxy: Optional[ProxyTypes]
"""Proxy to use for the request. In case of a sandbox it applies to all **requests made to the returned sandbox**."""

sandbox_url: Optional[str]
"""URL to connect to sandbox, defaults to `E2B_SANDBOX_URL` environment variable."""


class ConnectionConfig:
"""
Configuration for the connection to the API.
"""

envd_port = 49983

@staticmethod
def _domain():
return os.getenv("E2B_DOMAIN") or "e2b.app"
Expand All @@ -62,6 +67,10 @@ def _api_key():
def _api_url():
return os.getenv("E2B_API_URL")

@staticmethod
def _sandbox_url():
return os.getenv("E2B_SANDBOX_URL")

@staticmethod
def _access_token():
return os.getenv("E2B_ACCESS_TOKEN")
Expand All @@ -72,6 +81,7 @@ def __init__(
debug: Optional[bool] = None,
api_key: Optional[str] = None,
api_url: Optional[str] = None,
sandbox_url: Optional[str] = None,
access_token: Optional[str] = None,
request_timeout: Optional[float] = None,
headers: Optional[Dict[str, str]] = None,
Expand Down Expand Up @@ -106,6 +116,8 @@ def __init__(
or ("http://localhost:3000" if self.debug else f"https://api.{self.domain}")
)

self._sandbox_url = sandbox_url or ConnectionConfig._sandbox_url()

@staticmethod
def _get_request_timeout(
default_timeout: Optional[float],
Expand All @@ -121,6 +133,28 @@ def _get_request_timeout(
def get_request_timeout(self, request_timeout: Optional[float] = None):
return self._get_request_timeout(self.request_timeout, request_timeout)

def get_sandbox_url(self, sandbox_id: str, sandbox_domain: str) -> str:
if self._sandbox_url:
return self._sandbox_url

return f"{'http' if self.debug else 'https'}://{self.get_host(sandbox_id, sandbox_domain, self.envd_port)}"

def get_host(self, sandbox_id: str, sandbox_domain: str, 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
:param sandbox_domain: Domain to connect to
:param sandbox_id: Sandbox to connect to

:return: Host address to connect to
"""
if self.debug:
return f"localhost:{port}"

return f"{port}-{sandbox_id}.{sandbox_domain}"

def get_api_params(
self,
**opts: Unpack[ApiParams],
Expand Down
13 changes: 7 additions & 6 deletions packages/python-sdk/e2b/sandbox/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class SandboxOpts(TypedDict):
sandbox_domain: Optional[str]
envd_version: Version
envd_access_token: Optional[str]
sandbox_url: Optional[str]
connection_config: ConnectionConfig


Expand All @@ -25,7 +26,6 @@ class SandboxBase:
keepalive_expiry=300,
)

envd_port = 49983
mcp_port = 50005

default_sandbox_timeout = 300
Expand All @@ -46,7 +46,9 @@ def __init__(
self.__sandbox_domain = sandbox_domain or self.connection_config.domain
self.__envd_version = envd_version
self.__envd_access_token = envd_access_token
self.__envd_api_url = f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(self.envd_port)}"
self.__envd_api_url = self.connection_config.get_sandbox_url(
self.sandbox_id, self.sandbox_domain
)
self.__mcp_token: Optional[str] = None

@property
Expand Down Expand Up @@ -195,10 +197,9 @@ def get_host(self, port: int) -> str:

:return: Host address to connect to
"""
if self.connection_config.debug:
return f"localhost:{port}"

return f"{port}-{self.sandbox_id}.{self.sandbox_domain}"
return self.connection_config.get_host(
self.sandbox_id, self.sandbox_domain, port
)

def get_mcp_url(self) -> str:
"""
Expand Down
7 changes: 6 additions & 1 deletion packages/python-sdk/e2b/sandbox_async/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ def __init__(self, **opts: Unpack[SandboxOpts]):
limits=self._limits, proxy=self.connection_config.proxy
)
self._envd_api = httpx.AsyncClient(
base_url=self.envd_api_url,
base_url=self.connection_config.get_sandbox_url(
self.sandbox_id, self.sandbox_domain
),
transport=self._transport,
headers=self.connection_config.sandbox_headers,
)
Expand Down Expand Up @@ -721,6 +723,9 @@ async def _create(
):
extra_sandbox_headers["X-Access-Token"] = envd_access_token

extra_sandbox_headers["E2b-Sandbox-Id"] = sandbox_id
extra_sandbox_headers["E2b-Sandbox-Port"] = str(ConnectionConfig.envd_port)

connection_config = ConnectionConfig(
extra_sandbox_headers=extra_sandbox_headers,
**opts,
Expand Down
4 changes: 4 additions & 0 deletions packages/python-sdk/e2b/sandbox_async/sandbox_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ async def _cls_connect(
async with AsyncApiClient(
config,
limits=SandboxBase._limits,
headers={
"E2b-Sandbox-Id": sandbox_id,
"E2b-Sandbox-Port": config.envd_port,
},
) as api_client:
res = await post_sandboxes_sandbox_id_connect.asyncio_detailed(
sandbox_id,
Expand Down
3 changes: 3 additions & 0 deletions packages/python-sdk/e2b/sandbox_sync/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ def _create(
):
extra_sandbox_headers["X-Access-Token"] = envd_access_token

extra_sandbox_headers["E2b-Sandbox-Id"] = sandbox_id
extra_sandbox_headers["E2b-Sandbox-Port"] = str(ConnectionConfig.envd_port)

connection_config = ConnectionConfig(
extra_sandbox_headers=extra_sandbox_headers,
**opts,
Expand Down
4 changes: 4 additions & 0 deletions packages/python-sdk/e2b/sandbox_sync/sandbox_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ def _cls_connect(
with ApiClient(
config,
limits=SandboxBase._limits,
headers={
"E2b-Sandbox-Id": sandbox_id,
"E2b-Sandbox-Port": config.envd_port,
},
) as api_client:
res = post_sandboxes_sandbox_id_connect.sync_detailed(
sandbox_id,
Expand Down
3 changes: 2 additions & 1 deletion packages/python-sdk/e2b_connect/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,10 @@ def _prepare_server_stream_request(
req,
request_timeout=None,
timeout=None,
headers={},
headers=None,
**opts,
):
headers = headers or {}
data = self._codec.encode(req)
flags = EnvelopeFlags(0)

Expand Down
19 changes: 1 addition & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.