Skip to content

Commit bbeff74

Browse files
djeebusmishushakov
andauthored
Support overriding envd API URL (#1013)
This requires [infra#1448](e2b-dev/infra#1448) first. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds support to override the sandbox API URL (E2B_SANDBOX_URL) across JS/Python SDKs, centralizes sandbox host/url logic with headers, and updates CI to build the SDK before CLI. > > - **SDKs (JS & Python)** > - Add `sandboxUrl` support in `ConnectionConfig` (env var `E2B_SANDBOX_URL`), with new helpers `getSandboxUrl`/`getHost` and shared `envdPort`. > - Refactor sandbox initialization to use `ConnectionConfig.getSandboxUrl(...)` and `getHost(...)`. > - Always attach sandbox headers `E2b-Sandbox-Id` and `E2b-Sandbox-Port` to sandbox and connect requests. > - Python: thread `sandbox_url` through opts; update async/sync connect calls to pass headers; minor fix to default `headers=None` in `e2b_connect.client.Client` stream prep. > - **CI** > - Build `packages/js-sdk` before `packages/cli`; set step `working-directory` for build/test. > - **Dependencies** > - Point `e2b` dependency in lockfile to local `../js-sdk` link. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5dc5817. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Mish <[email protected]>
1 parent d2e22e3 commit bbeff74

File tree

13 files changed

+130
-40
lines changed

13 files changed

+130
-40
lines changed

.changeset/seven-cooks-tie.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"e2b": patch
3+
"@e2b/python-sdk": patch
4+
---
5+
6+
Support overriding sandbox API URL

.github/workflows/cli_tests.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ permissions:
1414

1515
jobs:
1616
test:
17-
defaults:
18-
run:
19-
working-directory: ./packages/cli
2017
name: CLI - Build
2118
runs-on: ubuntu-22.04
2219
steps:
@@ -32,7 +29,6 @@ jobs:
3229

3330
- name: Install pnpm
3431
uses: pnpm/action-setup@v4
35-
id: pnpm-install
3632
with:
3733
version: '${{ env.TOOL_VERSION_PNPM }}'
3834

@@ -47,15 +43,20 @@ jobs:
4743
- name: Configure pnpm
4844
run: |
4945
pnpm config set auto-install-peers true
50-
pnpm config set exclude-links-from-lockfile true
5146
5247
- name: Install dependencies
5348
run: pnpm install --frozen-lockfile
5449

55-
- name: Test build
50+
- name: Build the SDK (pre-requisite for the tests)
5651
run: pnpm build
52+
working-directory: ./packages/js-sdk
53+
54+
- name: Build the CLI
55+
run: pnpm build
56+
working-directory: ./packages/cli
5757

5858
- name: Run tests
5959
run: pnpm test
60+
working-directory: ./packages/cli
6061
env:
6162
E2B_API_KEY: ${{ secrets.E2B_API_KEY }}

packages/js-sdk/src/connectionConfig.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ export interface ConnectionOpts {
3535
* @default E2B_API_URL // environment variable or `https://api.${domain}`
3636
*/
3737
apiUrl?: string
38+
/**
39+
* Sandbox Url to use for the API.
40+
* @internal
41+
* @default E2B_SANDBOX_URL // environment variable or `https://${port}-${sandboxID}.${domain}`
42+
*/
43+
sandboxUrl?: string
3844
/**
3945
* If true the SDK starts in the debug mode and connects to the local envd API server.
4046
* @internal
@@ -62,9 +68,12 @@ export interface ConnectionOpts {
6268
* Configuration for connecting to the API.
6369
*/
6470
export class ConnectionConfig {
71+
public static envdPort = 49983
72+
6573
readonly debug: boolean
6674
readonly domain: string
6775
readonly apiUrl: string
76+
readonly sandboxUrl?: string
6877
readonly logger?: Logger
6978

7079
readonly requestTimeoutMs: number
@@ -88,6 +97,8 @@ export class ConnectionConfig {
8897
opts?.apiUrl ||
8998
ConnectionConfig.apiUrl ||
9099
(this.debug ? 'http://localhost:3000' : `https://api.${this.domain}`)
100+
101+
this.sandboxUrl = opts?.sandboxUrl || ConnectionConfig.sandboxUrl
91102
}
92103

93104
private static get domain() {
@@ -98,6 +109,10 @@ export class ConnectionConfig {
98109
return getEnvVar('E2B_API_URL')
99110
}
100111

112+
private static get sandboxUrl() {
113+
return getEnvVar('E2B_SANDBOX_URL')
114+
}
115+
101116
private static get debug() {
102117
return (getEnvVar('E2B_DEBUG') || 'false').toLowerCase() === 'true'
103118
}
@@ -115,6 +130,25 @@ export class ConnectionConfig {
115130

116131
return timeout ? AbortSignal.timeout(timeout) : undefined
117132
}
133+
134+
getSandboxUrl(
135+
sandboxId: string,
136+
opts: { sandboxDomain: string; envdPort: number }
137+
) {
138+
if (this.sandboxUrl) {
139+
return this.sandboxUrl
140+
}
141+
142+
return `${this.debug ? 'http' : 'https'}://${this.getHost(sandboxId, opts.envdPort, opts.sandboxDomain)}`
143+
}
144+
145+
getHost(sandboxId: string, port: number, sandboxDomain: string) {
146+
if (this.debug) {
147+
return `localhost:${port}`
148+
}
149+
150+
return `${port}-${sandboxId}.${sandboxDomain ?? this.domain}`
151+
}
118152
}
119153

120154
/**

packages/js-sdk/src/sandbox/index.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,15 @@ export class Sandbox extends SandboxApi {
123123
this.sandboxDomain = opts.sandboxDomain ?? this.connectionConfig.domain
124124

125125
this.envdAccessToken = opts.envdAccessToken
126-
this.envdApiUrl = `${
127-
this.connectionConfig.debug ? 'http' : 'https'
128-
}://${this.getHost(this.envdPort)}`
126+
this.envdApiUrl = this.connectionConfig.getSandboxUrl(this.sandboxId, {
127+
sandboxDomain: this.sandboxDomain,
128+
envdPort: this.envdPort,
129+
})
130+
131+
const sandboxHeaders = {
132+
'E2b-Sandbox-Id': this.sandboxId,
133+
'E2b-Sandbox-Port': this.envdPort.toString(),
134+
}
129135

130136
const rpcTransport = createConnectTransport({
131137
baseUrl: this.envdApiUrl,
@@ -141,6 +147,9 @@ export class Sandbox extends SandboxApi {
141147
new Headers(options?.headers).forEach((value, key) =>
142148
headers.append(key, value)
143149
)
150+
new Headers(sandboxHeaders).forEach((value, key) =>
151+
headers.append(key, value)
152+
)
144153

145154
if (this.envdAccessToken) {
146155
headers.append('X-Access-Token', this.envdAccessToken)
@@ -459,11 +468,11 @@ export class Sandbox extends SandboxApi {
459468
* ```
460469
*/
461470
getHost(port: number) {
462-
if (this.connectionConfig.debug) {
463-
return `localhost:${port}`
464-
}
465-
466-
return `${port}-${this.sandboxId}.${this.sandboxDomain}`
471+
return this.connectionConfig.getHost(
472+
this.sandboxId,
473+
port,
474+
this.sandboxDomain
475+
)
467476
}
468477

469478
/**

packages/js-sdk/src/sandbox/sandboxApi.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ export interface SandboxOpts extends ConnectionOpts {
9090
* @default undefined
9191
*/
9292
mcp?: McpServer
93+
94+
/**
95+
* Sandbox URL. Used for local development
96+
*/
97+
sandboxUrl?: string
9398
}
9499

95100
export type SandboxBetaCreateOpts = SandboxOpts & {

packages/python-sdk/e2b/connection_config.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,17 @@ class ApiParams(TypedDict, total=False):
4040
proxy: Optional[ProxyTypes]
4141
"""Proxy to use for the request. In case of a sandbox it applies to all **requests made to the returned sandbox**."""
4242

43+
sandbox_url: Optional[str]
44+
"""URL to connect to sandbox, defaults to `E2B_SANDBOX_URL` environment variable."""
45+
4346

4447
class ConnectionConfig:
4548
"""
4649
Configuration for the connection to the API.
4750
"""
4851

52+
envd_port = 49983
53+
4954
@staticmethod
5055
def _domain():
5156
return os.getenv("E2B_DOMAIN") or "e2b.app"
@@ -62,6 +67,10 @@ def _api_key():
6267
def _api_url():
6368
return os.getenv("E2B_API_URL")
6469

70+
@staticmethod
71+
def _sandbox_url():
72+
return os.getenv("E2B_SANDBOX_URL")
73+
6574
@staticmethod
6675
def _access_token():
6776
return os.getenv("E2B_ACCESS_TOKEN")
@@ -72,6 +81,7 @@ def __init__(
7281
debug: Optional[bool] = None,
7382
api_key: Optional[str] = None,
7483
api_url: Optional[str] = None,
84+
sandbox_url: Optional[str] = None,
7585
access_token: Optional[str] = None,
7686
request_timeout: Optional[float] = None,
7787
headers: Optional[Dict[str, str]] = None,
@@ -106,6 +116,8 @@ def __init__(
106116
or ("http://localhost:3000" if self.debug else f"https://api.{self.domain}")
107117
)
108118

119+
self._sandbox_url = sandbox_url or ConnectionConfig._sandbox_url()
120+
109121
@staticmethod
110122
def _get_request_timeout(
111123
default_timeout: Optional[float],
@@ -121,6 +133,28 @@ def _get_request_timeout(
121133
def get_request_timeout(self, request_timeout: Optional[float] = None):
122134
return self._get_request_timeout(self.request_timeout, request_timeout)
123135

136+
def get_sandbox_url(self, sandbox_id: str, sandbox_domain: str) -> str:
137+
if self._sandbox_url:
138+
return self._sandbox_url
139+
140+
return f"{'http' if self.debug else 'https'}://{self.get_host(sandbox_id, sandbox_domain, self.envd_port)}"
141+
142+
def get_host(self, sandbox_id: str, sandbox_domain: str, port: int) -> str:
143+
"""
144+
Get the host address to connect to the sandbox.
145+
You can then use this address to connect to the sandbox port from outside the sandbox via HTTP or WebSocket.
146+
147+
:param port: Port to connect to
148+
:param sandbox_domain: Domain to connect to
149+
:param sandbox_id: Sandbox to connect to
150+
151+
:return: Host address to connect to
152+
"""
153+
if self.debug:
154+
return f"localhost:{port}"
155+
156+
return f"{port}-{sandbox_id}.{sandbox_domain}"
157+
124158
def get_api_params(
125159
self,
126160
**opts: Unpack[ApiParams],

packages/python-sdk/e2b/sandbox/main.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class SandboxOpts(TypedDict):
1515
sandbox_domain: Optional[str]
1616
envd_version: Version
1717
envd_access_token: Optional[str]
18+
sandbox_url: Optional[str]
1819
connection_config: ConnectionConfig
1920

2021

@@ -25,7 +26,6 @@ class SandboxBase:
2526
keepalive_expiry=300,
2627
)
2728

28-
envd_port = 49983
2929
mcp_port = 50005
3030

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

5254
@property
@@ -195,10 +197,9 @@ def get_host(self, port: int) -> str:
195197
196198
:return: Host address to connect to
197199
"""
198-
if self.connection_config.debug:
199-
return f"localhost:{port}"
200-
201-
return f"{port}-{self.sandbox_id}.{self.sandbox_domain}"
200+
return self.connection_config.get_host(
201+
self.sandbox_id, self.sandbox_domain, port
202+
)
202203

203204
def get_mcp_url(self) -> str:
204205
"""

packages/python-sdk/e2b/sandbox_async/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ def __init__(self, **opts: Unpack[SandboxOpts]):
9595
limits=self._limits, proxy=self.connection_config.proxy
9696
)
9797
self._envd_api = httpx.AsyncClient(
98-
base_url=self.envd_api_url,
98+
base_url=self.connection_config.get_sandbox_url(
99+
self.sandbox_id, self.sandbox_domain
100+
),
99101
transport=self._transport,
100102
headers=self.connection_config.sandbox_headers,
101103
)
@@ -721,6 +723,9 @@ async def _create(
721723
):
722724
extra_sandbox_headers["X-Access-Token"] = envd_access_token
723725

726+
extra_sandbox_headers["E2b-Sandbox-Id"] = sandbox_id
727+
extra_sandbox_headers["E2b-Sandbox-Port"] = str(ConnectionConfig.envd_port)
728+
724729
connection_config = ConnectionConfig(
725730
extra_sandbox_headers=extra_sandbox_headers,
726731
**opts,

packages/python-sdk/e2b/sandbox_async/sandbox_api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ async def _cls_connect(
310310
async with AsyncApiClient(
311311
config,
312312
limits=SandboxBase._limits,
313+
headers={
314+
"E2b-Sandbox-Id": sandbox_id,
315+
"E2b-Sandbox-Port": config.envd_port,
316+
},
313317
) as api_client:
314318
res = await post_sandboxes_sandbox_id_connect.asyncio_detailed(
315319
sandbox_id,

packages/python-sdk/e2b/sandbox_sync/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ def _create(
718718
):
719719
extra_sandbox_headers["X-Access-Token"] = envd_access_token
720720

721+
extra_sandbox_headers["E2b-Sandbox-Id"] = sandbox_id
722+
extra_sandbox_headers["E2b-Sandbox-Port"] = str(ConnectionConfig.envd_port)
723+
721724
connection_config = ConnectionConfig(
722725
extra_sandbox_headers=extra_sandbox_headers,
723726
**opts,

0 commit comments

Comments
 (0)