diff --git a/AUTO_RESUME.md b/AUTO_RESUME.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/web/src/app/(docs)/docs/sdk-reference/js-sdk/v2.12.0/sandbox/page.mdx b/apps/web/src/app/(docs)/docs/sdk-reference/js-sdk/v2.12.0/sandbox/page.mdx index 5230498480..9279c0d6e8 100644 --- a/apps/web/src/app/(docs)/docs/sdk-reference/js-sdk/v2.12.0/sandbox/page.mdx +++ b/apps/web/src/app/(docs)/docs/sdk-reference/js-sdk/v2.12.0/sandbox/page.mdx @@ -494,6 +494,12 @@ sandbox instance for the new sandbox. const sandbox = await Sandbox.create() ``` +```ts +const sandbox = await Sandbox.create({ + autoResume: { policy: 'any' }, +}) +``` + ###### Constructs Sandbox diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 70c98a1919..badee55b47 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -1638,6 +1638,7 @@ export interface components { * @default false */ autoPause?: boolean; + autoResume?: components["schemas"]["SandboxAutoResumeConfig"]; envVars?: components["schemas"]["EnvVars"]; mcp?: components["schemas"]["Mcp"]; metadata?: components["schemas"]["SandboxMetadata"]; @@ -1939,6 +1940,16 @@ export interface components { /** @description Specify host mask which will be used for all sandbox requests */ maskRequestHost?: string; }; + /** @description Auto-resume configuration for paused sandboxes. Default is off. */ + SandboxAutoResumeConfig: { + policy?: components["schemas"]["SandboxAutoResumePolicy"]; + }; + /** + * @description Auto-resume policy for paused sandboxes. Default is off. + * @default off + * @enum {string} + */ + SandboxAutoResumePolicy: "any" | "off"; /** * @description State of the sandbox * @enum {string} diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 4878e8b736..54c78fde10 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -64,6 +64,15 @@ export type SandboxNetworkOpts = { maskRequestHost?: string } +export type SandboxAutoResumePolicy = 'any' | 'off' + +export type SandboxAutoResumeConfig = { + /** + * Auto-resume policy for paused sandboxes. Default is `off`. + */ + policy?: SandboxAutoResumePolicy +} + /** * Options for request to the Sandbox API. */ @@ -118,6 +127,12 @@ export interface SandboxOpts extends ConnectionOpts { */ allowInternetAccess?: boolean + /** + * Auto-resume configuration for paused sandboxes. Omit to disable auto-resume. + * @default undefined + */ + autoResume?: SandboxAutoResumeConfig + /** * MCP server to enable in the sandbox * @default undefined @@ -537,18 +552,21 @@ export class SandboxApi { const config = new ConnectionConfig(opts) const client = new ApiClient(config) + const body = { + autoPause: opts?.autoPause ?? false, + ...(opts?.autoResume == null ? {} : { autoResume: opts.autoResume }), + templateID: template, + metadata: opts?.metadata, + mcp: opts?.mcp as Record | undefined, + envVars: opts?.envs, + timeout: timeoutToSeconds(timeoutMs), + secure: opts?.secure ?? true, + allow_internet_access: opts?.allowInternetAccess ?? true, + network: opts?.network, + } + const res = await client.api.POST('/sandboxes', { - body: { - autoPause: opts?.autoPause ?? false, - templateID: template, - metadata: opts?.metadata, - mcp: opts?.mcp as Record | undefined, - envVars: opts?.envs, - timeout: timeoutToSeconds(timeoutMs), - secure: opts?.secure ?? true, - allow_internet_access: opts?.allowInternetAccess ?? true, - network: opts?.network, - }, + body, signal: config.getSignal(opts?.requestTimeoutMs), }) diff --git a/packages/js-sdk/tests/sandbox/autoResume.test.ts b/packages/js-sdk/tests/sandbox/autoResume.test.ts new file mode 100644 index 0000000000..be91870a09 --- /dev/null +++ b/packages/js-sdk/tests/sandbox/autoResume.test.ts @@ -0,0 +1,60 @@ +import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vitest' + +import { http, HttpResponse } from 'msw' +import { setupServer } from 'msw/node' + +import { Sandbox } from '../../src' +import { apiUrl } from '../setup' + +const templateId = 'test-template' +const sandboxResponse = { + templateID: templateId, + sandboxID: 'sandbox-123', + clientID: 'client-123', + envdVersion: '0.2.4', + envdAccessToken: 'envd-access-token', + trafficAccessToken: null, + domain: 'e2b.app', +} + +let lastBody: Record | undefined + +const server = setupServer( + http.post(apiUrl('/sandboxes'), async ({ request }) => { + lastBody = (await request.clone().json()) as Record + return HttpResponse.json(sandboxResponse, { status: 201 }) + }) +) + +describe('Sandbox.create autoResume', () => { + beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) + afterAll(() => server.close()) + afterEach(() => { + lastBody = undefined + server.resetHandlers() + }) + + test('sends autoResume when provided', async () => { + await Sandbox.create(templateId, { + apiKey: 'test-api-key', + autoResume: { policy: 'any' }, + apiUrl: apiUrl(''), + debug: false, + }) + + expect(lastBody?.autoResume).toEqual({ policy: 'any' }) + }) + + test('omits autoResume when not provided', async () => { + await Sandbox.create(templateId, { + apiKey: 'test-api-key', + apiUrl: apiUrl(''), + debug: false, + }) + + expect(lastBody).toBeDefined() + expect(Object.prototype.hasOwnProperty.call(lastBody, 'autoResume')).toBe( + false + ) + }) +}) diff --git a/packages/python-sdk/e2b/api/client/models/new_sandbox.py b/packages/python-sdk/e2b/api/client/models/new_sandbox.py index 1f80c900c4..610a28e6da 100644 --- a/packages/python-sdk/e2b/api/client/models/new_sandbox.py +++ b/packages/python-sdk/e2b/api/client/models/new_sandbox.py @@ -22,6 +22,7 @@ class NewSandbox: allow_internet_access (Union[Unset, bool]): Allow sandbox to access the internet. When set to false, it behaves the same as specifying denyOut to 0.0.0.0/0 in the network config. auto_pause (Union[Unset, bool]): Automatically pauses the sandbox after the timeout Default: False. + auto_resume (Union[Unset, Any]): Auto-resume configuration for paused sandboxes. Use {"policy": "any"} to allow any request. Omit or use {"policy": "off"} to disable auto-resume. env_vars (Union[Unset, Any]): mcp (Union['McpType0', None, Unset]): MCP configuration for the sandbox metadata (Union[Unset, Any]): @@ -33,6 +34,7 @@ class NewSandbox: template_id: str allow_internet_access: Union[Unset, bool] = UNSET auto_pause: Union[Unset, bool] = False + auto_resume: Union[Unset, Any] = UNSET env_vars: Union[Unset, Any] = UNSET mcp: Union["McpType0", None, Unset] = UNSET metadata: Union[Unset, Any] = UNSET @@ -50,6 +52,8 @@ def to_dict(self) -> dict[str, Any]: auto_pause = self.auto_pause + auto_resume = self.auto_resume + env_vars = self.env_vars mcp: Union[None, Unset, dict[str, Any]] @@ -81,6 +85,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["allow_internet_access"] = allow_internet_access if auto_pause is not UNSET: field_dict["autoPause"] = auto_pause + if auto_resume is not UNSET and auto_resume is not None: + field_dict["autoResume"] = auto_resume if env_vars is not UNSET: field_dict["envVars"] = env_vars if mcp is not UNSET: @@ -108,6 +114,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: auto_pause = d.pop("autoPause", UNSET) + auto_resume = d.pop("autoResume", UNSET) + env_vars = d.pop("envVars", UNSET) def _parse_mcp(data: object) -> Union["McpType0", None, Unset]: @@ -144,6 +152,7 @@ def _parse_mcp(data: object) -> Union["McpType0", None, Unset]: template_id=template_id, allow_internet_access=allow_internet_access, auto_pause=auto_pause, + auto_resume=auto_resume, env_vars=env_vars, mcp=mcp, metadata=metadata, diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 3245a512f8..9d16f332b7 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from datetime import datetime -from typing import Any, Dict, List, Optional, TypedDict, Union +from typing import Any, Dict, List, Literal, Optional, TypedDict, Union from typing_extensions import NotRequired, Unpack @@ -76,6 +76,20 @@ class SandboxNetworkOpts(TypedDict): """ +SandboxAutoResumePolicy = Literal["any", "off"] + + +class SandboxAutoResumeConfig(TypedDict): + """ + Auto-resume configuration for paused sandboxes. + """ + + policy: NotRequired[SandboxAutoResumePolicy] + """ + Auto-resume policy for paused sandboxes. Default is "off". + """ + + @dataclass class SandboxInfo: """Information about a sandbox.""" diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index bc592f139e..1ed251a8a6 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -15,7 +15,12 @@ from e2b.envd.versions import ENVD_DEBUG_FALLBACK from e2b.exceptions import SandboxException, format_request_timeout_error from e2b.sandbox.main import SandboxOpts -from e2b.sandbox.sandbox_api import McpServer, SandboxMetrics, SandboxNetworkOpts +from e2b.sandbox.sandbox_api import ( + McpServer, + SandboxAutoResumeConfig, + SandboxMetrics, + SandboxNetworkOpts, +) from e2b.sandbox.utils import class_method_variant from e2b.sandbox_async.commands.command import Commands from e2b.sandbox_async.commands.pty import Pty @@ -162,6 +167,7 @@ async def create( allow_internet_access: bool = True, mcp: Optional[McpServer] = None, network: Optional[SandboxNetworkOpts] = None, + auto_resume: Optional[SandboxAutoResumeConfig] = None, **opts: Unpack[ApiParams], ) -> Self: """ @@ -177,6 +183,7 @@ async def create( :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. If set to `False`, it works the same as setting network `deny_out` to `[0.0.0.0/0]`. :param mcp: MCP server to enable in the sandbox :param network: Sandbox network configuration + :param auto_resume: Auto-resume configuration for paused sandboxes. Use `{"policy": "any" | "off"}` (default is "off"). Set to `{"policy": "any"}` to allow any request, or omit/`{"policy": "off"}` to disable. :return: A Sandbox instance for the new sandbox @@ -191,6 +198,7 @@ async def create( template=template, timeout=timeout, auto_pause=False, + auto_resume=auto_resume, metadata=metadata, envs=envs, secure=secure, @@ -527,6 +535,7 @@ async def beta_create( secure: bool = True, allow_internet_access: bool = True, mcp: Optional[McpServer] = None, + auto_resume: Optional[SandboxAutoResumeConfig] = None, **opts: Unpack[ApiParams], ) -> Self: """ @@ -544,6 +553,7 @@ async def beta_create( :param secure: Envd is secured with access token and cannot be used without it, defaults to `True`. :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. :param mcp: MCP server to enable in the sandbox + :param auto_resume: Auto-resume configuration for paused sandboxes. Use `{"policy": "any"}` to allow any request. Omit or use `{"policy": "off"}` to disable auto-resume. :return: A Sandbox instance for the new sandbox @@ -559,6 +569,7 @@ async def beta_create( template=template, timeout=timeout, auto_pause=auto_pause, + auto_resume=auto_resume, metadata=metadata, envs=envs, secure=secure, @@ -680,6 +691,7 @@ async def _create( template: Optional[str], timeout: Optional[int], auto_pause: bool, + auto_resume: Optional[SandboxAutoResumeConfig], allow_internet_access: bool, metadata: Optional[Dict[str, str]], envs: Optional[Dict[str, str]], @@ -702,6 +714,7 @@ async def _create( template=template or cls.default_template, timeout=timeout or cls.default_sandbox_timeout, auto_pause=auto_pause, + auto_resume=auto_resume, metadata=metadata, env_vars=envs, secure=secure, diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index aabbd709c3..8168ca4627 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -29,6 +29,7 @@ from e2b.sandbox.main import SandboxBase from e2b.sandbox.sandbox_api import ( McpServer, + SandboxAutoResumeConfig, SandboxInfo, SandboxMetrics, SandboxNetworkOpts, @@ -153,6 +154,7 @@ async def _create_sandbox( template: str, timeout: int, auto_pause: bool, + auto_resume: Optional[SandboxAutoResumeConfig], allow_internet_access: bool, metadata: Optional[Dict[str, str]], env_vars: Optional[Dict[str, str]], @@ -168,6 +170,7 @@ async def _create_sandbox( body=NewSandbox( template_id=template, auto_pause=auto_pause, + auto_resume=auto_resume, metadata=metadata or {}, timeout=timeout, env_vars=env_vars or {}, diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 366def2234..03ad15f430 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -15,7 +15,12 @@ from e2b.envd.versions import ENVD_DEBUG_FALLBACK from e2b.exceptions import SandboxException, format_request_timeout_error from e2b.sandbox.main import SandboxOpts -from e2b.sandbox.sandbox_api import McpServer, SandboxMetrics, SandboxNetworkOpts +from e2b.sandbox.sandbox_api import ( + McpServer, + SandboxAutoResumeConfig, + SandboxMetrics, + SandboxNetworkOpts, +) from e2b.sandbox.utils import class_method_variant from e2b.sandbox_sync.commands.command import Commands from e2b.sandbox_sync.commands.pty import Pty @@ -160,6 +165,7 @@ def create( allow_internet_access: bool = True, mcp: Optional[McpServer] = None, network: Optional[SandboxNetworkOpts] = None, + auto_resume: Optional[SandboxAutoResumeConfig] = None, **opts: Unpack[ApiParams], ) -> Self: """ @@ -175,6 +181,7 @@ def create( :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. If set to `False`, it works the same as setting network `deny_out` to `[0.0.0.0/0]`. :param mcp: MCP server to enable in the sandbox :param network: Sandbox network configuration + :param auto_resume: Auto-resume configuration for paused sandboxes. Use `{"policy": "any" | "off"}` (default is "off"). Set to `{"policy": "any"}` to allow any request, or omit/`{"policy": "off"}` to disable. :return: A Sandbox instance for the new sandbox @@ -188,6 +195,7 @@ def create( sandbox = cls._create( template=template, auto_pause=False, + auto_resume=auto_resume, timeout=timeout, metadata=metadata, envs=envs, @@ -528,6 +536,7 @@ def beta_create( secure: bool = True, allow_internet_access: bool = True, mcp: Optional[McpServer] = None, + auto_resume: Optional[SandboxAutoResumeConfig] = None, **opts: Unpack[ApiParams], ) -> Self: """ @@ -545,6 +554,7 @@ def beta_create( :param secure: Envd is secured with access token and cannot be used without it, defaults to `True`. :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. :param mcp: MCP server to enable in the sandbox + :param auto_resume: Auto-resume configuration for paused sandboxes. Use `{"policy": "any"}` to allow any request. Omit or use `{"policy": "off"}` to disable auto-resume. :return: A Sandbox instance for the new sandbox @@ -559,6 +569,7 @@ def beta_create( sandbox = cls._create( template=template, auto_pause=auto_pause, + auto_resume=auto_resume, timeout=timeout, metadata=metadata, envs=envs, @@ -672,6 +683,7 @@ def _create( template: Optional[str], timeout: Optional[int], auto_pause: bool, + auto_resume: Optional[SandboxAutoResumeConfig], metadata: Optional[Dict[str, str]], envs: Optional[Dict[str, str]], secure: bool, @@ -694,6 +706,7 @@ def _create( template=template or cls.default_template, timeout=timeout or cls.default_sandbox_timeout, auto_pause=auto_pause, + auto_resume=auto_resume, metadata=metadata, env_vars=envs, secure=secure, diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 18a3af798d..cf789cfd96 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -28,6 +28,7 @@ from e2b.sandbox.main import SandboxBase from e2b.sandbox.sandbox_api import ( McpServer, + SandboxAutoResumeConfig, SandboxInfo, SandboxMetrics, SandboxNetworkOpts, @@ -152,6 +153,7 @@ def _create_sandbox( template: str, timeout: int, auto_pause: bool, + auto_resume: Optional[SandboxAutoResumeConfig], allow_internet_access: bool, metadata: Optional[Dict[str, str]], env_vars: Optional[Dict[str, str]], @@ -167,6 +169,7 @@ def _create_sandbox( body=NewSandbox( template_id=template, auto_pause=auto_pause, + auto_resume=auto_resume, metadata=metadata or {}, timeout=timeout, env_vars=env_vars or {}, diff --git a/packages/python-sdk/tests/sync/api/test_new_sandbox.py b/packages/python-sdk/tests/sync/api/test_new_sandbox.py new file mode 100644 index 0000000000..7c24a7605a --- /dev/null +++ b/packages/python-sdk/tests/sync/api/test_new_sandbox.py @@ -0,0 +1,20 @@ +from e2b.api.client.models.new_sandbox import NewSandbox +from e2b.api.client.types import UNSET + + +def test_new_sandbox_includes_auto_resume_when_set(): + payload = NewSandbox(template_id="base", auto_resume={"policy": "any"}).to_dict() + + assert payload["autoResume"] == {"policy": "any"} + + +def test_new_sandbox_omits_auto_resume_when_unset(): + payload = NewSandbox(template_id="base", auto_resume=UNSET).to_dict() + + assert "autoResume" not in payload + + +def test_new_sandbox_omits_auto_resume_when_none(): + payload = NewSandbox(template_id="base", auto_resume=None).to_dict() + + assert "autoResume" not in payload diff --git a/spec/openapi.yml b/spec/openapi.yml index dab7c95535..de231abaec 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -255,6 +255,21 @@ components: type: string description: Specify host mask which will be used for all sandbox requests + SandboxAutoResumePolicy: + type: string + description: Auto-resume policy for paused sandboxes. Default is off. + default: "off" + enum: + - any + - off + + SandboxAutoResumeConfig: + type: object + description: Auto-resume configuration for paused sandboxes. Default is off. + properties: + policy: + $ref: "#/components/schemas/SandboxAutoResumePolicy" + SandboxLog: description: Log entry with timestamp and line required: @@ -306,6 +321,17 @@ components: items: $ref: "#/components/schemas/SandboxLogEntry" + SandboxLogsV2Response: + required: + - logs + properties: + logs: + default: [] + description: Sandbox logs structured + type: array + items: + $ref: "#/components/schemas/SandboxLogEntry" + SandboxMetric: description: Metric entry with timestamp and line required: @@ -512,6 +538,8 @@ components: type: boolean default: false description: Automatically pauses the sandbox after the timeout + autoResume: + $ref: "#/components/schemas/SandboxAutoResumeConfig" secure: type: boolean description: Secure all system communication with sandbox