diff --git a/.changeset/khaki-shirts-switch.md b/.changeset/khaki-shirts-switch.md
new file mode 100644
index 0000000000..49c5f9b8e6
--- /dev/null
+++ b/.changeset/khaki-shirts-switch.md
@@ -0,0 +1,6 @@
+---
+'@e2b/python-sdk': patch
+'e2b': patch
+---
+
+added getInfo methods
diff --git a/apps/web/src/app/(docs)/docs/sandbox/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/page.mdx
index d6599f1c3b..f0aea3e8a1 100644
--- a/apps/web/src/app/(docs)/docs/sandbox/page.mdx
+++ b/apps/web/src/app/(docs)/docs/sandbox/page.mdx
@@ -56,6 +56,37 @@ sandbox.set_timeout(30)
```
+## Retrieve sandbox information
+
+You can retrieve sandbox information like sandbox id, template, metadata, started at/end at date by calling the `getInfo` method in JavaScript or `get_info` method in Python.
+
+
+```js
+import { Sandbox } from '@e2b/code-interpreter'
+
+// Create sandbox with and keep it running for 60 seconds.
+const sandbox = await Sandbox.create({ timeoutMs: 60_000 })
+
+// Retrieve sandbox information.
+const info = await sandbox.getInfo()
+
+// Retrieve sandbox expiration date.
+const expirationDate = info.endAt
+console.log(expirationDate)
+```
+
+```python
+from e2b_code_interpreter import Sandbox
+
+# Create sandbox with and keep it running for 60 seconds.
+sandbox = Sandbox(timeout=60)
+
+# Retrieve sandbox expiration date.
+expiration_date = sandbox.get_info().end_at
+print(expiration_date)
+```
+
+
## Shutdown sandbox
You can shutdown the sandbox any time even before the timeout is up by calling the `kill` method.
diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts
index ac8249d4f8..c23a447405 100644
--- a/packages/js-sdk/src/sandbox/index.ts
+++ b/packages/js-sdk/src/sandbox/index.ts
@@ -122,7 +122,7 @@ export class Sandbox extends SandboxApi {
logger: opts?.logger,
},
{
- version: opts?.envdVersion
+ version: opts?.envdVersion,
}
)
this.files = new Filesystem(
@@ -184,12 +184,15 @@ export class Sandbox extends SandboxApi {
const config = new ConnectionConfig(sandboxOpts)
if (config.debug) {
- return new this({ sandboxId: 'debug_sandbox_id', ...config }) as InstanceType
- } else {
- const sandbox = await this.createSandbox(
- template,
- sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs,
- sandboxOpts
+ return new this({
+ sandboxId: 'debug_sandbox_id',
+ ...config,
+ }) as InstanceType
+ } else {
+ const sandbox = await this.createSandbox(
+ template,
+ sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs,
+ sandboxOpts
)
return new this({ ...sandbox, ...config }) as InstanceType
}
@@ -356,4 +359,18 @@ export class Sandbox extends SandboxApi {
return url.toString()
}
+
+ /**
+ * Get sandbox information like sandbox id, template, metadata, started at/end at date.
+ *
+ * @param opts connection options.
+ *
+ * @returns information about the sandbox
+ */
+ async getInfo(opts?: Pick) {
+ return await Sandbox.getInfo(this.sandboxId, {
+ ...this.connectionConfig,
+ ...opts,
+ })
+ }
}
diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts
index a18429a59a..031aaf59c9 100644
--- a/packages/js-sdk/src/sandbox/sandboxApi.ts
+++ b/packages/js-sdk/src/sandbox/sandboxApi.ts
@@ -15,7 +15,7 @@ export interface SandboxListOpts extends SandboxApiOpts {
/**
* Filter the list of sandboxes, e.g. by metadata `metadata:{"key": "value"}`, if there are multiple filters they are combined with AND.
*/
- query?: {metadata?: Record}
+ query?: { metadata?: Record }
}
/**
@@ -46,6 +46,11 @@ export interface SandboxInfo {
* Sandbox start time.
*/
startedAt: Date
+
+ /**
+ * Sandbox expiration date.
+ */
+ endAt: Date
}
export class SandboxApi {
@@ -94,23 +99,27 @@ export class SandboxApi {
*
* @returns list of running sandboxes.
*/
- static async list(
- opts?: SandboxListOpts): Promise {
+ static async list(opts?: SandboxListOpts): Promise {
const config = new ConnectionConfig(opts)
const client = new ApiClient(config)
let metadata = undefined
if (opts?.query) {
if (opts.query.metadata) {
- const encodedPairs: Record = Object.fromEntries(Object.entries(opts.query.metadata).map(([key, value]) => [encodeURIComponent(key), encodeURIComponent(value)]))
+ const encodedPairs: Record = Object.fromEntries(
+ Object.entries(opts.query.metadata).map(([key, value]) => [
+ encodeURIComponent(key),
+ encodeURIComponent(value),
+ ])
+ )
metadata = new URLSearchParams(encodedPairs).toString()
}
}
const res = await client.api.GET('/sandboxes', {
- params: {
- query: {metadata},
- },
+ params: {
+ query: { metadata },
+ },
signal: config.getSignal(opts?.requestTimeoutMs),
})
@@ -129,10 +138,57 @@ export class SandboxApi {
...(sandbox.alias && { name: sandbox.alias }),
metadata: sandbox.metadata ?? {},
startedAt: new Date(sandbox.startedAt),
+ endAt: new Date(sandbox.endAt),
})) ?? []
)
}
+ /**
+ * Get sandbox information like sandbox id, template, metadata, started at/end at date.
+ *
+ * @param sandboxId sandbox ID.
+ * @param opts connection options.
+ *
+ * @returns sandbox information.
+ */
+ static async getInfo(
+ sandboxId: string,
+ opts?: SandboxApiOpts
+ ): Promise {
+ const config = new ConnectionConfig(opts)
+ const client = new ApiClient(config)
+
+ const res = await client.api.GET('/sandboxes/{sandboxID}', {
+ params: {
+ path: {
+ sandboxID: sandboxId,
+ },
+ },
+ signal: config.getSignal(opts?.requestTimeoutMs),
+ })
+
+ const err = handleApiError(res)
+ if (err) {
+ throw err
+ }
+
+ if (!res.data) {
+ throw new Error('Sandbox not found')
+ }
+
+ return {
+ sandboxId: this.getSandboxId({
+ sandboxId: res.data.sandboxID,
+ clientId: res.data.clientID,
+ }),
+ templateId: res.data.templateID,
+ ...(res.data.alias && { name: res.data.alias }),
+ metadata: res.data.metadata ?? {},
+ startedAt: new Date(res.data.startedAt),
+ endAt: new Date(res.data.endAt),
+ }
+ }
+
/**
* Set the timeout of the specified sandbox.
* After the timeout expires the sandbox will be automatically killed.
@@ -219,7 +275,7 @@ export class SandboxApi {
sandboxId: res.data!.sandboxID,
clientId: res.data!.clientID,
}),
- envdVersion: res.data!.envdVersion
+ envdVersion: res.data!.envdVersion,
}
}
diff --git a/packages/js-sdk/tests/sandbox/timeout.test.ts b/packages/js-sdk/tests/sandbox/timeout.test.ts
index 6fab68f93d..d38b0b4504 100644
--- a/packages/js-sdk/tests/sandbox/timeout.test.ts
+++ b/packages/js-sdk/tests/sandbox/timeout.test.ts
@@ -19,5 +19,10 @@ sandboxTest.skipIf(isDebug)('shorten then lenghten timeout', async ({ sandbox })
await wait(6000)
- await sandbox.isRunning()
+ expect(await sandbox.isRunning()).toBeTruthy()
})
+
+sandboxTest.skipIf(isDebug)('get sandbox timeout', async ({ sandbox }) => {
+ const { endAt } = await sandbox.getInfo()
+ expect(endAt).toBeInstanceOf(Date)
+})
\ No newline at end of file
diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py
index c746de575f..51175d745f 100644
--- a/packages/python-sdk/e2b/sandbox/sandbox_api.py
+++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py
@@ -19,6 +19,8 @@ class SandboxInfo:
"""Saved sandbox metadata."""
started_at: datetime
"""Sandbox start time."""
+ end_at: datetime
+ """Sandbox expiration date."""
@dataclass
diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py
index cb2a393282..091206bf5b 100644
--- a/packages/python-sdk/e2b/sandbox_async/main.py
+++ b/packages/python-sdk/e2b/sandbox_async/main.py
@@ -12,7 +12,7 @@
from e2b.sandbox_async.filesystem.filesystem import Filesystem
from e2b.sandbox_async.commands.command import Commands
from e2b.sandbox_async.commands.pty import Pty
-from e2b.sandbox_async.sandbox_api import SandboxApi
+from e2b.sandbox_async.sandbox_api import SandboxApi, SandboxInfo
logger = logging.getLogger(__name__)
@@ -371,3 +371,25 @@ async def set_timeout( # type: ignore
timeout=timeout,
**self.connection_config.__dict__,
)
+
+ async def get_info( # type: ignore
+ self,
+ request_timeout: Optional[float] = None,
+ ) -> SandboxInfo:
+ """
+ Get sandbox information like sandbox id, template, metadata, started at/end at date.
+ :param request_timeout: Timeout for the request in **seconds**
+ :return: Sandbox info
+ """
+
+ config_dict = self.connection_config.__dict__
+ config_dict.pop("access_token", None)
+ config_dict.pop("api_url", None)
+
+ if request_timeout:
+ config_dict["request_timeout"] = request_timeout
+
+ return await SandboxApi.get_info(
+ sandbox_id=self.sandbox_id,
+ **self.connection_config.__dict__,
+ )
\ No newline at end of file
diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py
index 9268024767..7e7bc29b5e 100644
--- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py
+++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py
@@ -7,6 +7,7 @@
from e2b.api import AsyncApiClient, SandboxCreateResponse
from e2b.api.client.models import NewSandbox, PostSandboxesSandboxIDTimeoutBody
from e2b.api.client.api.sandboxes import (
+ get_sandboxes_sandbox_id,
post_sandboxes_sandbox_id_timeout,
get_sandboxes,
delete_sandboxes_sandbox_id,
@@ -78,9 +79,61 @@ async def list(
sandbox.metadata if isinstance(sandbox.metadata, dict) else {}
),
started_at=sandbox.started_at,
+ end_at=sandbox.end_at,
)
for sandbox in res.parsed
]
+
+ @classmethod
+ async def get_info(
+ cls,
+ sandbox_id: str,
+ api_key: Optional[str] = None,
+ domain: Optional[str] = None,
+ debug: Optional[bool] = None,
+ request_timeout: Optional[float] = None,
+ ) -> SandboxInfo:
+ """
+ Get the sandbox info.
+ :param sandbox_id: Sandbox ID
+ :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable
+ :param domain: Domain to use for the request, defaults to `E2B_DOMAIN` environment variable
+ :param debug: Debug mode, defaults to `E2B_DEBUG` environment variable
+ :param request_timeout: Timeout for the request in **seconds**
+ :return: Sandbox info
+ """
+ config = ConnectionConfig(
+ api_key=api_key,
+ domain=domain,
+ debug=debug,
+ request_timeout=request_timeout,
+ )
+
+ async with AsyncApiClient(config) as api_client:
+ res = await get_sandboxes_sandbox_id.asyncio_detailed(
+ sandbox_id,
+ client=api_client,
+ )
+
+ if res.status_code >= 300:
+ raise handle_api_exception(res)
+
+ if res.parsed is None:
+ raise Exception("Body of the request is None")
+
+ return SandboxInfo(
+ sandbox_id=SandboxApi._get_sandbox_id(
+ res.parsed.sandbox_id,
+ res.parsed.client_id,
+ ),
+ template_id=res.parsed.template_id,
+ name=res.parsed.alias if isinstance(res.parsed.alias, str) else None,
+ metadata=(
+ res.parsed.metadata if isinstance(res.parsed.metadata, dict) else {}
+ ),
+ started_at=res.parsed.started_at,
+ end_at=res.parsed.end_at,
+ )
@classmethod
async def _cls_kill(
diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py
index 8922b2dc4d..ebfd49d8f4 100644
--- a/packages/python-sdk/e2b/sandbox_sync/main.py
+++ b/packages/python-sdk/e2b/sandbox_sync/main.py
@@ -10,7 +10,7 @@
from e2b.sandbox_sync.filesystem.filesystem import Filesystem
from e2b.sandbox_sync.commands.command import Commands
from e2b.sandbox_sync.commands.pty import Pty
-from e2b.sandbox_sync.sandbox_api import SandboxApi
+from e2b.sandbox_sync.sandbox_api import SandboxApi, SandboxInfo
logger = logging.getLogger(__name__)
@@ -361,3 +361,24 @@ def set_timeout( # type: ignore
timeout=timeout,
**self.connection_config.__dict__,
)
+
+ def get_info( # type: ignore
+ self,
+ request_timeout: Optional[float] = None,
+ ) -> SandboxInfo:
+ """
+ Get sandbox information like sandbox id, template, metadata, started at/end at date.
+ :param request_timeout: Timeout for the request in **seconds**
+ :return: Sandbox info
+ """
+ config_dict = self.connection_config.__dict__
+ config_dict.pop("access_token", None)
+ config_dict.pop("api_url", None)
+
+ if request_timeout:
+ config_dict["request_timeout"] = request_timeout
+
+ return SandboxApi.get_info(
+ sandbox_id=self.sandbox_id,
+ **self.connection_config.__dict__,
+ )
\ No newline at end of file
diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py
index 92460e7526..da4c5920c7 100644
--- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py
+++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py
@@ -9,6 +9,7 @@
from e2b.api import ApiClient, SandboxCreateResponse
from e2b.api.client.models import NewSandbox, PostSandboxesSandboxIDTimeoutBody
from e2b.api.client.api.sandboxes import (
+ get_sandboxes_sandbox_id,
post_sandboxes_sandbox_id_timeout,
get_sandboxes,
delete_sandboxes_sandbox_id,
@@ -79,9 +80,63 @@ def list(
sandbox.metadata if isinstance(sandbox.metadata, dict) else {}
),
started_at=sandbox.started_at,
+ end_at=sandbox.end_at,
)
for sandbox in res.parsed
]
+
+ @classmethod
+ def get_info(
+ cls,
+ sandbox_id: str,
+ api_key: Optional[str] = None,
+ domain: Optional[str] = None,
+ debug: Optional[bool] = None,
+ request_timeout: Optional[float] = None,
+ ) -> SandboxInfo:
+ """
+ Get the sandbox info.
+ :param sandbox_id: Sandbox ID
+ :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable
+ :param domain: Domain to use for the request, defaults to `E2B_DOMAIN` environment variable
+ :param debug: Debug mode, defaults to `E2B_DEBUG` environment variable
+ :param request_timeout: Timeout for the request in **seconds**
+ :return: Sandbox info
+ """
+ config = ConnectionConfig(
+ api_key=api_key,
+ domain=domain,
+ debug=debug,
+ request_timeout=request_timeout,
+ )
+
+ with ApiClient(
+ config, transport=HTTPTransport(limits=SandboxApiBase._limits)
+ ) as api_client:
+ res = get_sandboxes_sandbox_id.sync_detailed(
+ sandbox_id,
+ client=api_client,
+ )
+
+ if res.status_code >= 300:
+ raise handle_api_exception(res)
+
+ if res.parsed is None:
+ raise Exception("Body of the request is None")
+
+ return SandboxInfo(
+ sandbox_id=SandboxApi._get_sandbox_id(
+ res.parsed.sandbox_id,
+ res.parsed.client_id,
+ ),
+ template_id=res.parsed.template_id,
+ name=res.parsed.alias if isinstance(res.parsed.alias, str) else None,
+ metadata=(
+ res.parsed.metadata if isinstance(res.parsed.metadata, dict) else {}
+ ),
+ started_at=res.parsed.started_at,
+ end_at=res.parsed.end_at,
+ )
@classmethod
def _cls_kill(
diff --git a/packages/python-sdk/tests/async/sandbox_async/test_timeout.py b/packages/python-sdk/tests/async/sandbox_async/test_timeout.py
index be6715bc4d..4ed1e05a70 100644
--- a/packages/python-sdk/tests/async/sandbox_async/test_timeout.py
+++ b/packages/python-sdk/tests/async/sandbox_async/test_timeout.py
@@ -1,4 +1,5 @@
import pytest
+from datetime import datetime
from time import sleep
@@ -21,3 +22,8 @@ async def test_shorten_then_lengthen_timeout(async_sandbox: AsyncSandbox):
await async_sandbox.set_timeout(10)
sleep(6)
await async_sandbox.is_running()
+
+@pytest.mark.skip_debug()
+async def test_get_timeout(async_sandbox: AsyncSandbox):
+ info = await async_sandbox.get_info()
+ assert isinstance(info.end_at, datetime)
\ No newline at end of file
diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_timeout.py b/packages/python-sdk/tests/sync/sandbox_sync/test_timeout.py
index e8015fdfb3..1d7a5d9d12 100644
--- a/packages/python-sdk/tests/sync/sandbox_sync/test_timeout.py
+++ b/packages/python-sdk/tests/sync/sandbox_sync/test_timeout.py
@@ -1,4 +1,5 @@
from time import sleep
+from datetime import datetime
import pytest
@@ -19,3 +20,8 @@ def test_shorten_then_lengthen_timeout(sandbox):
sandbox.set_timeout(10)
sleep(6)
sandbox.is_running()
+
+@pytest.mark.skip_debug()
+def test_get_timeout(sandbox):
+ info = sandbox.get_info()
+ assert isinstance(info.end_at, datetime)
\ No newline at end of file