From 8131eb4781aba81d5467c5e4de004d548593dfdd Mon Sep 17 00:00:00 2001 From: Joe Lombrozo Date: Mon, 10 Nov 2025 17:29:48 -0800 Subject: [PATCH 1/5] use headers to route to sandboxes --- .github/workflows/cli_tests.yml | 13 ++-- packages/js-sdk/src/connectionConfig.ts | 34 +++++++++++ packages/js-sdk/src/sandbox/index.ts | 59 +++++++++++-------- packages/js-sdk/src/sandbox/sandboxApi.ts | 25 ++++---- packages/python-sdk/e2b/connection_config.py | 34 +++++++++++ packages/python-sdk/e2b/sandbox/main.py | 13 ++-- packages/python-sdk/e2b/sandbox_async/main.py | 7 ++- packages/python-sdk/e2b/sandbox_sync/main.py | 3 + packages/python-sdk/e2b_connect/client.py | 3 +- pnpm-lock.yaml | 19 +----- 10 files changed, 143 insertions(+), 67 deletions(-) diff --git a/.github/workflows/cli_tests.yml b/.github/workflows/cli_tests.yml index 59ba494477..fd105a721b 100644 --- a/.github/workflows/cli_tests.yml +++ b/.github/workflows/cli_tests.yml @@ -14,9 +14,6 @@ permissions: jobs: test: - defaults: - run: - working-directory: ./packages/cli name: CLI - Build runs-on: ubuntu-22.04 steps: @@ -32,7 +29,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - id: pnpm-install with: version: '${{ env.TOOL_VERSION_PNPM }}' @@ -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 }} diff --git a/packages/js-sdk/src/connectionConfig.ts b/packages/js-sdk/src/connectionConfig.ts index edcb9ef222..8396e68e4b 100644 --- a/packages/js-sdk/src/connectionConfig.ts +++ b/packages/js-sdk/src/connectionConfig.ts @@ -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 @@ -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 @@ -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() { @@ -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' } @@ -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}` + } } /** diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 9d08bed898..4dd335f9aa 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -113,7 +113,7 @@ export class Sandbox extends SandboxApi { sandboxDomain?: string envdVersion: string envdAccessToken?: string - } + }, ) { super() @@ -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, @@ -139,7 +145,10 @@ export class Sandbox extends SandboxApi { const headers = new Headers(this.connectionConfig.headers) new Headers(options?.headers).forEach((value, key) => - headers.append(key, value) + headers.append(key, value), + ) + new Headers(sandboxHeaders).forEach((value, key) => + headers.append(key, value), ) if (this.envdAccessToken) { @@ -167,12 +176,12 @@ export class Sandbox extends SandboxApi { }, { version: opts.envdVersion, - } + }, ) this.files = new Filesystem( rpcTransport, this.envdApi, - this.connectionConfig + this.connectionConfig, ) this.commands = new Commands(rpcTransport, this.connectionConfig, { version: opts.envdVersion, @@ -233,7 +242,7 @@ export class Sandbox extends SandboxApi { static async create( this: S, templateOrOpts?: SandboxOpts | string, - opts?: SandboxOpts + opts?: SandboxOpts, ): Promise> { const { template, sandboxOpts } = typeof templateOrOpts === 'string' @@ -260,7 +269,7 @@ export class Sandbox extends SandboxApi { const sandboxInfo = await SandboxApi.createSandbox( template, sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs, - sandboxOpts + sandboxOpts, ) const sandbox = new this({ ...sandboxInfo, ...config }) as InstanceType @@ -274,7 +283,7 @@ export class Sandbox extends SandboxApi { envs: { GATEWAY_ACCESS_TOKEN: sandbox.mcpToken ?? '', }, - } + }, ) if (res.exitCode !== 0) { throw new Error(`Failed to start MCP gateway: ${res.stderr}`) @@ -328,7 +337,7 @@ export class Sandbox extends SandboxApi { static async betaCreate( this: S, templateOrOpts?: SandboxBetaCreateOpts | string, - opts?: SandboxBetaCreateOpts + opts?: SandboxBetaCreateOpts, ): Promise> { const { template, sandboxOpts } = typeof templateOrOpts === 'string' @@ -355,7 +364,7 @@ export class Sandbox extends SandboxApi { const sandboxInfo = await SandboxApi.createSandbox( template, sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs, - sandboxOpts + sandboxOpts, ) const sandbox = new this({ ...sandboxInfo, ...config }) as InstanceType @@ -369,7 +378,7 @@ export class Sandbox extends SandboxApi { envs: { GATEWAY_ACCESS_TOKEN: sandbox.mcpToken ?? '', }, - } + }, ) if (res.exitCode !== 0) { throw new Error(`Failed to start MCP gateway: ${res.stderr}`) @@ -402,7 +411,7 @@ export class Sandbox extends SandboxApi { static async connect( this: S, sandboxId: string, - opts?: SandboxConnectOpts + opts?: SandboxConnectOpts, ): Promise> { const sandbox = await SandboxApi.connectSandbox(sandboxId, opts) const config = new ConnectionConfig(opts) @@ -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, + ) } /** @@ -481,7 +490,7 @@ export class Sandbox extends SandboxApi { * ``` */ async isRunning( - opts?: Pick + opts?: Pick, ): Promise { const signal = this.connectionConfig.getSignal(opts?.requestTimeoutMs) @@ -513,7 +522,7 @@ export class Sandbox extends SandboxApi { */ async setTimeout( timeoutMs: number, - opts?: Pick + opts?: Pick, ) { if (this.connectionConfig.debug) { // Skip timeout in debug mode @@ -596,7 +605,7 @@ export class Sandbox extends SandboxApi { if (!useSignature && opts.useSignatureExpiration != undefined) { throw new Error( - 'Signature expiration can be used only when sandbox is created as secured.' + 'Signature expiration can be used only when sandbox is created as secured.', ) } @@ -648,7 +657,7 @@ export class Sandbox extends SandboxApi { if (!useSignature && opts.useSignatureExpiration != undefined) { throw new Error( - 'Signature expiration can be used only when sandbox is created as secured.' + 'Signature expiration can be used only when sandbox is created as secured.', ) } @@ -709,13 +718,13 @@ export class Sandbox extends SandboxApi { if (compareVersions(this.envdApi.version, '0.1.5') < 0) { throw new SandboxError( 'You need to update the template to use the new SDK. ' + - 'You can do this by running `e2b template build` in the directory with the template.' + 'You can do this by running `e2b template build` in the directory with the template.', ) } if (compareVersions(this.envdApi.version, '0.2.4') < 0) { this.connectionConfig.logger?.warn?.( - 'Disk metrics are not supported in this version of the sandbox, please rebuild the template to get disk metrics.' + 'Disk metrics are not supported in this version of the sandbox, please rebuild the template to get disk metrics.', ) } } diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 6599d46e83..2c22d5b76c 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -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 & { @@ -267,7 +272,7 @@ export class SandboxApi { */ static async kill( sandboxId: string, - opts?: SandboxApiOpts + opts?: SandboxApiOpts, ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -303,7 +308,7 @@ export class SandboxApi { */ static async getInfo( sandboxId: string, - opts?: SandboxApiOpts + opts?: SandboxApiOpts, ): Promise { const fullInfo = await this.getFullInfo(sandboxId, opts) delete fullInfo.envdAccessToken @@ -322,7 +327,7 @@ export class SandboxApi { */ static async getMetrics( sandboxId: string, - opts?: SandboxMetricsOpts + opts?: SandboxMetricsOpts, ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -371,7 +376,7 @@ export class SandboxApi { static async setTimeout( sandboxId: string, timeoutMs: number, - opts?: SandboxApiOpts + opts?: SandboxApiOpts, ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -450,7 +455,7 @@ export class SandboxApi { */ static async betaPause( sandboxId: string, - opts?: SandboxApiOpts + opts?: SandboxApiOpts, ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -484,7 +489,7 @@ export class SandboxApi { protected static async createSandbox( template: string, timeoutMs: number, - opts?: SandboxBetaCreateOpts + opts?: SandboxBetaCreateOpts, ) { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -512,7 +517,7 @@ export class SandboxApi { await this.kill(res.data!.sandboxID, opts) throw new TemplateError( 'You need to update the template to use the new SDK. ' + - 'You can do this by running `e2b template build` in the directory with the template.' + 'You can do this by running `e2b template build` in the directory with the template.', ) } @@ -526,7 +531,7 @@ export class SandboxApi { protected static async connectSandbox( sandboxId: string, - opts?: SandboxConnectOpts + opts?: SandboxConnectOpts, ) { const timeoutMs = opts?.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS @@ -629,7 +634,7 @@ export class SandboxPaginator { Object.entries(this.query.metadata).map(([key, value]) => [ encodeURIComponent(key), encodeURIComponent(value), - ]) + ]), ) metadata = new URLSearchParams(encodedPairs).toString() @@ -668,7 +673,7 @@ export class SandboxPaginator { cpuCount: sandbox.cpuCount, memoryMB: sandbox.memoryMB, envdVersion: sandbox.envdVersion, - }) + }), ) } } diff --git a/packages/python-sdk/e2b/connection_config.py b/packages/python-sdk/e2b/connection_config.py index e20ae4ba07..efc89091a6 100644 --- a/packages/python-sdk/e2b/connection_config.py +++ b/packages/python-sdk/e2b/connection_config.py @@ -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" @@ -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") @@ -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, @@ -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], @@ -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], diff --git a/packages/python-sdk/e2b/sandbox/main.py b/packages/python-sdk/e2b/sandbox/main.py index b0a5f3b221..c1b028a705 100644 --- a/packages/python-sdk/e2b/sandbox/main.py +++ b/packages/python-sdk/e2b/sandbox/main.py @@ -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 @@ -25,7 +26,6 @@ class SandboxBase: keepalive_expiry=300, ) - envd_port = 49983 mcp_port = 50005 default_sandbox_timeout = 300 @@ -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 @@ -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: """ diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 66ad933064..6e514c9222 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -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, ) @@ -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, diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 9c39f74327..1e82a4bca5 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -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, diff --git a/packages/python-sdk/e2b_connect/client.py b/packages/python-sdk/e2b_connect/client.py index 863ddf3d61..034a8478df 100644 --- a/packages/python-sdk/e2b_connect/client.py +++ b/packages/python-sdk/e2b_connect/client.py @@ -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) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6707e60ff..54724ea9f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -273,7 +273,7 @@ importers: version: 0.6.1 e2b: specifier: ^2.6.1 - version: 2.6.1 + version: link:../js-sdk handlebars: specifier: ^4.7.8 version: 4.7.8 @@ -4078,10 +4078,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - e2b@2.6.1: - resolution: {integrity: sha512-KYQOza4bYNWTbbwbTHNlgOVWXKtlq2c1H0I5jW1Q1kEFYqoKxjfZGtkuJjnOtmVpeiIKzsMtQ9n0e3WKHBDysQ==} - engines: {node: '>=20'} - e2b@2.6.4: resolution: {integrity: sha512-IhtNZxXomub24lSmq/eldOC4HfK8YqTpxGqL3MAxNlF4ggyS6hde9D0euW40zZL3VoCFD8wjcwKrdYtM1tVDZw==} engines: {node: '>=20'} @@ -12158,19 +12154,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - e2b@2.6.1: - dependencies: - '@bufbuild/protobuf': 2.6.2 - '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.6.2) - '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.6.2)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.6.2)) - chalk: 5.3.0 - compare-versions: 6.1.1 - dockerfile-ast: 0.7.1 - glob: 11.0.3 - openapi-fetch: 0.14.1 - platform: 1.3.6 - tar: 7.4.3 - e2b@2.6.4: dependencies: '@bufbuild/protobuf': 2.6.2 From b4d48c3af61563e66551c9334a40d236c6f1a04d Mon Sep 17 00:00:00 2001 From: Joe Lombrozo Date: Mon, 10 Nov 2025 17:33:00 -0800 Subject: [PATCH 2/5] reformat --- packages/js-sdk/src/connectionConfig.ts | 2 +- packages/js-sdk/src/sandbox/index.ts | 38 +++++++++++------------ packages/js-sdk/src/sandbox/sandboxApi.ts | 20 ++++++------ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/js-sdk/src/connectionConfig.ts b/packages/js-sdk/src/connectionConfig.ts index 8396e68e4b..3afe8f5ef6 100644 --- a/packages/js-sdk/src/connectionConfig.ts +++ b/packages/js-sdk/src/connectionConfig.ts @@ -133,7 +133,7 @@ export class ConnectionConfig { getSandboxUrl( sandboxId: string, - opts: { sandboxDomain: string; envdPort: number }, + opts: { sandboxDomain: string; envdPort: number } ) { if (this.sandboxUrl) { return this.sandboxUrl diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 4dd335f9aa..524bf33602 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -113,7 +113,7 @@ export class Sandbox extends SandboxApi { sandboxDomain?: string envdVersion: string envdAccessToken?: string - }, + } ) { super() @@ -145,10 +145,10 @@ export class Sandbox extends SandboxApi { const headers = new Headers(this.connectionConfig.headers) new Headers(options?.headers).forEach((value, key) => - headers.append(key, value), + headers.append(key, value) ) new Headers(sandboxHeaders).forEach((value, key) => - headers.append(key, value), + headers.append(key, value) ) if (this.envdAccessToken) { @@ -176,12 +176,12 @@ export class Sandbox extends SandboxApi { }, { version: opts.envdVersion, - }, + } ) this.files = new Filesystem( rpcTransport, this.envdApi, - this.connectionConfig, + this.connectionConfig ) this.commands = new Commands(rpcTransport, this.connectionConfig, { version: opts.envdVersion, @@ -242,7 +242,7 @@ export class Sandbox extends SandboxApi { static async create( this: S, templateOrOpts?: SandboxOpts | string, - opts?: SandboxOpts, + opts?: SandboxOpts ): Promise> { const { template, sandboxOpts } = typeof templateOrOpts === 'string' @@ -269,7 +269,7 @@ export class Sandbox extends SandboxApi { const sandboxInfo = await SandboxApi.createSandbox( template, sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs, - sandboxOpts, + sandboxOpts ) const sandbox = new this({ ...sandboxInfo, ...config }) as InstanceType @@ -283,7 +283,7 @@ export class Sandbox extends SandboxApi { envs: { GATEWAY_ACCESS_TOKEN: sandbox.mcpToken ?? '', }, - }, + } ) if (res.exitCode !== 0) { throw new Error(`Failed to start MCP gateway: ${res.stderr}`) @@ -337,7 +337,7 @@ export class Sandbox extends SandboxApi { static async betaCreate( this: S, templateOrOpts?: SandboxBetaCreateOpts | string, - opts?: SandboxBetaCreateOpts, + opts?: SandboxBetaCreateOpts ): Promise> { const { template, sandboxOpts } = typeof templateOrOpts === 'string' @@ -364,7 +364,7 @@ export class Sandbox extends SandboxApi { const sandboxInfo = await SandboxApi.createSandbox( template, sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs, - sandboxOpts, + sandboxOpts ) const sandbox = new this({ ...sandboxInfo, ...config }) as InstanceType @@ -378,7 +378,7 @@ export class Sandbox extends SandboxApi { envs: { GATEWAY_ACCESS_TOKEN: sandbox.mcpToken ?? '', }, - }, + } ) if (res.exitCode !== 0) { throw new Error(`Failed to start MCP gateway: ${res.stderr}`) @@ -411,7 +411,7 @@ export class Sandbox extends SandboxApi { static async connect( this: S, sandboxId: string, - opts?: SandboxConnectOpts, + opts?: SandboxConnectOpts ): Promise> { const sandbox = await SandboxApi.connectSandbox(sandboxId, opts) const config = new ConnectionConfig(opts) @@ -471,7 +471,7 @@ export class Sandbox extends SandboxApi { return this.connectionConfig.getHost( this.sandboxId, port, - this.sandboxDomain, + this.sandboxDomain ) } @@ -490,7 +490,7 @@ export class Sandbox extends SandboxApi { * ``` */ async isRunning( - opts?: Pick, + opts?: Pick ): Promise { const signal = this.connectionConfig.getSignal(opts?.requestTimeoutMs) @@ -522,7 +522,7 @@ export class Sandbox extends SandboxApi { */ async setTimeout( timeoutMs: number, - opts?: Pick, + opts?: Pick ) { if (this.connectionConfig.debug) { // Skip timeout in debug mode @@ -605,7 +605,7 @@ export class Sandbox extends SandboxApi { if (!useSignature && opts.useSignatureExpiration != undefined) { throw new Error( - 'Signature expiration can be used only when sandbox is created as secured.', + 'Signature expiration can be used only when sandbox is created as secured.' ) } @@ -657,7 +657,7 @@ export class Sandbox extends SandboxApi { if (!useSignature && opts.useSignatureExpiration != undefined) { throw new Error( - 'Signature expiration can be used only when sandbox is created as secured.', + 'Signature expiration can be used only when sandbox is created as secured.' ) } @@ -718,13 +718,13 @@ export class Sandbox extends SandboxApi { if (compareVersions(this.envdApi.version, '0.1.5') < 0) { throw new SandboxError( 'You need to update the template to use the new SDK. ' + - 'You can do this by running `e2b template build` in the directory with the template.', + 'You can do this by running `e2b template build` in the directory with the template.' ) } if (compareVersions(this.envdApi.version, '0.2.4') < 0) { this.connectionConfig.logger?.warn?.( - 'Disk metrics are not supported in this version of the sandbox, please rebuild the template to get disk metrics.', + 'Disk metrics are not supported in this version of the sandbox, please rebuild the template to get disk metrics.' ) } } diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 2c22d5b76c..0c49d349b2 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -272,7 +272,7 @@ export class SandboxApi { */ static async kill( sandboxId: string, - opts?: SandboxApiOpts, + opts?: SandboxApiOpts ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -308,7 +308,7 @@ export class SandboxApi { */ static async getInfo( sandboxId: string, - opts?: SandboxApiOpts, + opts?: SandboxApiOpts ): Promise { const fullInfo = await this.getFullInfo(sandboxId, opts) delete fullInfo.envdAccessToken @@ -327,7 +327,7 @@ export class SandboxApi { */ static async getMetrics( sandboxId: string, - opts?: SandboxMetricsOpts, + opts?: SandboxMetricsOpts ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -376,7 +376,7 @@ export class SandboxApi { static async setTimeout( sandboxId: string, timeoutMs: number, - opts?: SandboxApiOpts, + opts?: SandboxApiOpts ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -455,7 +455,7 @@ export class SandboxApi { */ static async betaPause( sandboxId: string, - opts?: SandboxApiOpts, + opts?: SandboxApiOpts ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -489,7 +489,7 @@ export class SandboxApi { protected static async createSandbox( template: string, timeoutMs: number, - opts?: SandboxBetaCreateOpts, + opts?: SandboxBetaCreateOpts ) { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -517,7 +517,7 @@ export class SandboxApi { await this.kill(res.data!.sandboxID, opts) throw new TemplateError( 'You need to update the template to use the new SDK. ' + - 'You can do this by running `e2b template build` in the directory with the template.', + 'You can do this by running `e2b template build` in the directory with the template.' ) } @@ -531,7 +531,7 @@ export class SandboxApi { protected static async connectSandbox( sandboxId: string, - opts?: SandboxConnectOpts, + opts?: SandboxConnectOpts ) { const timeoutMs = opts?.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS @@ -634,7 +634,7 @@ export class SandboxPaginator { Object.entries(this.query.metadata).map(([key, value]) => [ encodeURIComponent(key), encodeURIComponent(value), - ]), + ]) ) metadata = new URLSearchParams(encodedPairs).toString() @@ -673,7 +673,7 @@ export class SandboxPaginator { cpuCount: sandbox.cpuCount, memoryMB: sandbox.memoryMB, envdVersion: sandbox.envdVersion, - }), + }) ) } } From 7814e2cc96d8bccaaf2518011b19cfd11ac7067b Mon Sep 17 00:00:00 2001 From: Joe Lombrozo Date: Mon, 10 Nov 2025 17:48:04 -0800 Subject: [PATCH 3/5] add headers when connecting --- packages/python-sdk/e2b/sandbox_async/sandbox_api.py | 4 ++++ packages/python-sdk/e2b/sandbox_sync/sandbox_api.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index a41b75f27e..84bf11fa2b 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -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, diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 7052a1e9ff..1600e19dcd 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -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, From bace14599a253b754eb100a76658c61a6c3074ff Mon Sep 17 00:00:00 2001 From: Mish <10400064+mishushakov@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:30:51 +0100 Subject: [PATCH 4/5] updated comment --- packages/js-sdk/src/connectionConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-sdk/src/connectionConfig.ts b/packages/js-sdk/src/connectionConfig.ts index 3afe8f5ef6..298dff1dd9 100644 --- a/packages/js-sdk/src/connectionConfig.ts +++ b/packages/js-sdk/src/connectionConfig.ts @@ -38,7 +38,7 @@ export interface ConnectionOpts { /** * Sandbox Url to use for the API. * @internal - * @default E2B_SANDBOX_URL // environment variable or `https://${port}.${sandboxID}.${domain}` + * @default E2B_SANDBOX_URL // environment variable or `https://${port}-${sandboxID}.${domain}` */ sandboxUrl?: string /** From 5dc58171af6c170f8640f49d736dbe9c571f2b21 Mon Sep 17 00:00:00 2001 From: Joseph Lombrozo Date: Fri, 14 Nov 2025 09:48:20 -0800 Subject: [PATCH 5/5] Create seven-cooks-tie.md --- .changeset/seven-cooks-tie.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/seven-cooks-tie.md diff --git a/.changeset/seven-cooks-tie.md b/.changeset/seven-cooks-tie.md new file mode 100644 index 0000000000..119f267b16 --- /dev/null +++ b/.changeset/seven-cooks-tie.md @@ -0,0 +1,6 @@ +--- +"e2b": patch +"@e2b/python-sdk": patch +--- + +Support overriding sandbox API URL