From eaccd6e98770d4cce59988621e9d2e8a8db0744a Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Tue, 12 Nov 2024 14:01:59 -0800 Subject: [PATCH 01/53] [MVP] Add pause and resume --- packages/js-sdk/src/api/schema.gen.ts | 48 ++++++++++++ packages/js-sdk/src/sandbox/index.ts | 25 ++++++ packages/js-sdk/src/sandbox/sandboxApi.ts | 76 ++++++++++++++++++- .../js-sdk/tests/sandbox/snapshot.test.ts | 15 ++++ spec/openapi.yml | 52 ++++++++++++- 5 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 packages/js-sdk/tests/sandbox/snapshot.test.ts diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 62a662f38..f45e19c13 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -98,6 +98,26 @@ export interface paths { }; }; }; + "/sandboxes/{sandboxID}/pause": { + /** @description Pause the sandbox */ + post: { + parameters: { + path: { + sandboxID: components["parameters"]["sandboxID"]; + }; + }; + responses: { + /** @description The sandbox was paused successfully and can be resumed */ + 204: { + content: never; + }; + 401: components["responses"]["401"]; + 404: components["responses"]["404"]; + 409: components["responses"]["409"]; + 500: components["responses"]["500"]; + }; + }; + }; "/sandboxes/{sandboxID}/refreshes": { /** @description Refresh the sandbox extending its time to live */ post: { @@ -124,6 +144,28 @@ export interface paths { }; }; }; + "/sandboxes/{sandboxID}/resume": { + /** @description Resume the sandbox */ + post: { + parameters: { + path: { + sandboxID: components["parameters"]["sandboxID"]; + }; + }; + responses: { + /** @description The sandbox was resumed successfully */ + 201: { + content: { + "application/json": components["schemas"]["Sandbox"]; + }; + }; + 401: components["responses"]["401"]; + 404: components["responses"]["404"]; + 409: components["responses"]["409"]; + 500: components["responses"]["500"]; + }; + }; + }; "/sandboxes/{sandboxID}/timeout": { /** @description Set the timeout for the sandbox. The sandbox will expire x seconds from the time of the request. Calling this method multiple times overwrites the TTL, each time using the current timestamp as the starting point to measure the timeout duration. */ post: { @@ -455,6 +497,12 @@ export interface components { "application/json": components["schemas"]["Error"]; }; }; + /** @description Conflict */ + 409: { + content: { + "application/json": components["schemas"]["Error"]; + }; + }; /** @description Server error */ 500: { content: { diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index eed1978df..9d58617aa 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -217,6 +217,16 @@ export class Sandbox extends SandboxApi { return sbx } + static async resume( + this: S, + sandboxId: string, + opts?: Pick + ): Promise> { + const id = await Sandbox.resumeSandbox(sandboxId, opts) + + return this.connect(id, opts) + } + /** * Get the host address for the specified sandbox port. * You can then use this address to connect to the sandbox port from outside the sandbox via HTTP or WebSocket. @@ -316,6 +326,21 @@ export class Sandbox extends SandboxApi { await Sandbox.kill(this.sandboxId, { ...this.connectionConfig, ...opts }) } + async pause(opts?: Pick): Promise { + if (this.connectionConfig.debug) { + // Skip pausing in debug mode + return this.sandboxId + } + + return await Sandbox.pause(this.sandboxId, { ...this.connectionConfig, ...opts }) + } + + async resume(opts?: Pick) { + await Sandbox.resume(this.sandboxId, { ...this.connectionConfig, ...opts }) + + return this + } + /** * Get the URL to upload a file to the sandbox. * diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 1cf997f4d..66a0c284d 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -1,7 +1,7 @@ import { ApiClient, components, handleApiError } from '../api' import { ConnectionConfig, ConnectionOpts } from '../connectionConfig' import { compareVersions } from 'compare-versions' -import { TemplateError } from '../errors' +import { NotFoundError, SandboxError, TemplateError } from '../errors' /** * Options for request to the Sandbox API. @@ -9,7 +9,7 @@ import { TemplateError } from '../errors' export interface SandboxApiOpts extends Partial< Pick - > {} + > { } /** * Information about a sandbox. @@ -42,7 +42,7 @@ export interface SandboxInfo { } export class SandboxApi { - protected constructor() {} + protected constructor() { } /** * Kill the sandbox specified by sandbox ID. @@ -80,6 +80,39 @@ export class SandboxApi { return true } + static async pause( + sandboxId: string, + opts?: SandboxApiOpts + ): Promise { + const config = new ConnectionConfig(opts) + const client = new ApiClient(config) + + const res = await client.api.POST('/sandboxes/{sandboxID}/pause', { + params: { + path: { + sandboxID: sandboxId, + }, + }, + signal: config.getSignal(opts?.requestTimeoutMs), + }) + + if (res.error?.code === 404) { + throw new NotFoundError(`Sandbox ${sandboxId} not found`) + } + + if (res.error?.code === 409) { + // Sandbox is already paused + throw new SandboxError(`Sandbox ${sandboxId} is already paused`) + } + + const err = handleApiError(res) + if (err) { + throw err + } + + return sandboxId + } + /** * List all running sandboxes. * @@ -152,6 +185,41 @@ export class SandboxApi { } } + protected static async resumeSandbox( + sandboxId: string, + opts?: SandboxApiOpts + ): Promise { + const config = new ConnectionConfig(opts) + const client = new ApiClient(config) + + const res = await client.api.POST('/sandboxes/{sandboxID}/resume', { + params: { + path: { + sandboxID: sandboxId, + }, + }, + }) + + if (res.error?.code === 404) { + throw new NotFoundError(`Paused sandbox ${sandboxId} not found`) + } + + if (res.error?.code === 409) { + // Sandbox is not paused + throw new SandboxError(`Sandbox ${sandboxId} is not paused`) + } + + const err = handleApiError(res) + if (err) { + throw err + } + + return this.getSandboxId({ + sandboxId: res.data!.sandboxID, + clientId: res.data!.clientID, + }) + } + protected static async createSandbox( template: string, timeoutMs: number, @@ -188,7 +256,7 @@ export class SandboxApi { ) 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.' ) } return this.getSandboxId({ diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts new file mode 100644 index 000000000..a01de00b0 --- /dev/null +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -0,0 +1,15 @@ +import { assert } from 'vitest' + +import { sandboxTest, isDebug } from '../setup.js' + +sandboxTest.skipIf(isDebug)('pause and resume a sandbox', async ({ sandbox }) => { + assert.isTrue(await sandbox.isRunning()) + + await sandbox.pause() + + assert.isFalse(await sandbox.isRunning()) + + await sandbox.resume() + + assert.isTrue(await sandbox.isRunning()) +}) diff --git a/spec/openapi.yml b/spec/openapi.yml index 8fae421e5..2da660055 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -56,7 +56,12 @@ components: application/json: schema: $ref: "#/components/schemas/Error" - + "409": + description: Conflict + content: + application/json: + schema: + $ref: "#/components/schemas/Error" "500": description: Server error content: @@ -437,6 +442,51 @@ paths: "500": $ref: "#/components/responses/500" + # TODO: Pause and resume might be exposed as POST /sandboxes/{sandboxID}/snapshot and then POST /sandboxes with specified snapshotting setup + /sandboxes/{sandboxID}/pause: + post: + description: Pause the sandbox + tags: [sandboxes] + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/sandboxID" + responses: + "204": + description: The sandbox was paused successfully and can be resumed + "409": + $ref: "#/components/responses/409" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + + /sandboxes/{sandboxID}/resume: + post: + description: Resume the sandbox + tags: [sandboxes] + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/sandboxID" + responses: + "201": + description: The sandbox was resumed successfully + content: + application/json: + schema: + $ref: "#/components/schemas/Sandbox" + "409": + $ref: "#/components/responses/409" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/timeout: post: description: Set the timeout for the sandbox. The sandbox will expire x seconds from the time of the request. Calling this method multiple times overwrites the TTL, each time using the current timestamp as the starting point to measure the timeout duration. From b0b74ea58810b3c2c4159b55b65e110c142ccbb9 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Tue, 12 Nov 2024 14:27:24 -0800 Subject: [PATCH 02/53] Add timeout to resume request --- packages/js-sdk/src/api/schema.gen.ts | 13 +++++++++++++ packages/js-sdk/src/sandbox/index.ts | 4 ++-- packages/js-sdk/src/sandbox/sandboxApi.ts | 4 ++++ spec/openapi.yml | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index f45e19c13..619b5aede 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -152,6 +152,11 @@ export interface paths { sandboxID: components["parameters"]["sandboxID"]; }; }; + requestBody: { + content: { + "application/json": components["schemas"]["ResumedSandbox"]; + }; + }; responses: { /** @description The sandbox was resumed successfully */ 201: { @@ -375,6 +380,14 @@ export interface components { */ timeout?: number; }; + ResumedSandbox: { + /** + * Format: int32 + * @description Time to live for the sandbox in seconds. + * @default 15 + */ + timeout?: number; + }; RunningSandbox: { /** @description Alias of the template */ alias?: string; diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 9d58617aa..1faa4018e 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -220,9 +220,9 @@ export class Sandbox extends SandboxApi { static async resume( this: S, sandboxId: string, - opts?: Pick + opts?: Pick ): Promise> { - const id = await Sandbox.resumeSandbox(sandboxId, opts) + const id = await Sandbox.resumeSandbox(sandboxId, opts?.timeoutMs ?? this.defaultSandboxTimeoutMs, opts) return this.connect(id, opts) } diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 66a0c284d..2f9ab8f17 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -187,6 +187,7 @@ export class SandboxApi { protected static async resumeSandbox( sandboxId: string, + timeoutMs: number, opts?: SandboxApiOpts ): Promise { const config = new ConnectionConfig(opts) @@ -198,6 +199,9 @@ export class SandboxApi { sandboxID: sandboxId, }, }, + body: { + timeout: this.timeoutToSeconds(timeoutMs), + }, }) if (res.error?.code === 404) { diff --git a/spec/openapi.yml b/spec/openapi.yml index 2da660055..412c2e564 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -216,6 +216,15 @@ components: envVars: $ref: "#/components/schemas/EnvVars" + ResumedSandbox: + properties: + timeout: + type: integer + format: int32 + minimum: 0 + default: 15 + description: Time to live for the sandbox in seconds. + Template: required: - templateID @@ -471,6 +480,12 @@ paths: - ApiKeyAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ResumedSandbox" responses: "201": description: The sandbox was resumed successfully From 500e31d6a35b8da02abd9de81827af33f71a9eac Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Mon, 25 Nov 2024 14:20:27 -0800 Subject: [PATCH 03/53] Improve pause/resume SDK return types and clarify usage --- packages/js-sdk/src/sandbox/index.ts | 38 ++++++++++++++++++++--- packages/js-sdk/src/sandbox/sandboxApi.ts | 26 ++++++++++------ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 1faa4018e..e16e9ec0a 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -217,14 +217,25 @@ export class Sandbox extends SandboxApi { return sbx } + /** + * Resume the sandbox. + * + * The **default sandbox timeout of 300 seconds** ({@link Sandbox.defaultSandboxTimeoutMs}) will be used for the resumed sandbox. + * If you pass a custom timeout in the `opts` parametervia {@link SandboxOpts.timeoutMs} property, it will be used instead. + * + * @param sandboxId sandbox ID. + * @param opts connection options. + * + * @returns sandbox instance. + */ static async resume( this: S, sandboxId: string, opts?: Pick ): Promise> { - const id = await Sandbox.resumeSandbox(sandboxId, opts?.timeoutMs ?? this.defaultSandboxTimeoutMs, opts) + await Sandbox.resumeSandbox(sandboxId, opts?.timeoutMs ?? this.defaultSandboxTimeoutMs, opts) - return this.connect(id, opts) + return await this.connect(sandboxId, opts) } /** @@ -326,16 +337,33 @@ export class Sandbox extends SandboxApi { await Sandbox.kill(this.sandboxId, { ...this.connectionConfig, ...opts }) } - async pause(opts?: Pick): Promise { + /** + * Pause the sandbox. + * + * @param opts connection options. + * + * @returns `true` if the sandbox got paused, `false` if the sandbox was already paused. + */ + async pause(opts?: Pick): Promise { if (this.connectionConfig.debug) { // Skip pausing in debug mode - return this.sandboxId + return true } return await Sandbox.pause(this.sandboxId, { ...this.connectionConfig, ...opts }) } - async resume(opts?: Pick) { + /** + * Resume the sandbox. + * + * The **default sandbox timeout of 300 seconds** ({@link Sandbox.defaultSandboxTimeoutMs}) will be used for the resumed sandbox. + * If you pass a custom timeout in the `opts` parameter via {@link SandboxOpts.timeoutMs} property, it will be used instead. + * + * @param opts connection options. + * + * @returns sandbox instance. + */ + async resume(opts?: Pick): Promise { await Sandbox.resume(this.sandboxId, { ...this.connectionConfig, ...opts }) return this diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 2f9ab8f17..d42f425c5 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -1,7 +1,7 @@ import { ApiClient, components, handleApiError } from '../api' import { ConnectionConfig, ConnectionOpts } from '../connectionConfig' import { compareVersions } from 'compare-versions' -import { NotFoundError, SandboxError, TemplateError } from '../errors' +import { NotFoundError, TemplateError } from '../errors' /** * Options for request to the Sandbox API. @@ -80,10 +80,18 @@ export class SandboxApi { return true } + /** + * Pause the sandbox specified by sandbox ID. + * + * @param sandboxId sandbox ID. + * @param opts connection options. + * + * @returns `true` if the sandbox got paused, `false` if the sandbox was already paused. + */ static async pause( sandboxId: string, opts?: SandboxApiOpts - ): Promise { + ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -102,7 +110,7 @@ export class SandboxApi { if (res.error?.code === 409) { // Sandbox is already paused - throw new SandboxError(`Sandbox ${sandboxId} is already paused`) + return false } const err = handleApiError(res) @@ -110,7 +118,7 @@ export class SandboxApi { throw err } - return sandboxId + return true } /** @@ -189,7 +197,7 @@ export class SandboxApi { sandboxId: string, timeoutMs: number, opts?: SandboxApiOpts - ): Promise { + ): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -202,6 +210,7 @@ export class SandboxApi { body: { timeout: this.timeoutToSeconds(timeoutMs), }, + signal: config.getSignal(opts?.requestTimeoutMs), }) if (res.error?.code === 404) { @@ -210,7 +219,7 @@ export class SandboxApi { if (res.error?.code === 409) { // Sandbox is not paused - throw new SandboxError(`Sandbox ${sandboxId} is not paused`) + return false } const err = handleApiError(res) @@ -218,10 +227,7 @@ export class SandboxApi { throw err } - return this.getSandboxId({ - sandboxId: res.data!.sandboxID, - clientId: res.data!.clientID, - }) + return true } protected static async createSandbox( From 891a1477a8dc6de11bb8a185340ac54562c467ee Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Sun, 8 Dec 2024 13:18:52 -0800 Subject: [PATCH 04/53] Add a route for persistence to sidebar in docs --- .../components/Navigation/NavigationLink.tsx | 13 ++--- apps/web/src/components/Navigation/routes.tsx | 13 ++++- apps/web/src/components/Tag.tsx | 57 ++----------------- 3 files changed, 20 insertions(+), 63 deletions(-) diff --git a/apps/web/src/components/Navigation/NavigationLink.tsx b/apps/web/src/components/Navigation/NavigationLink.tsx index 5153a35a9..db85b6c6e 100644 --- a/apps/web/src/components/Navigation/NavigationLink.tsx +++ b/apps/web/src/components/Navigation/NavigationLink.tsx @@ -8,12 +8,10 @@ import { NavLink } from './routes' export function NavigationLink({ className, link, - tag, }: { className?: string link: NavLink - tag?: string }) { const pathname = usePathname() // Add this to get the hash @@ -41,17 +39,14 @@ export function NavigationLink({ >
{link.icon} - {tag ? ( + {link.tag ? (
- - {tag} - {link.title} + + {link.tag} +
) : ( diff --git a/apps/web/src/components/Navigation/routes.tsx b/apps/web/src/components/Navigation/routes.tsx index 466fe5f9a..dd1825729 100644 --- a/apps/web/src/components/Navigation/routes.tsx +++ b/apps/web/src/components/Navigation/routes.tsx @@ -1,9 +1,14 @@ import { Braces, CheckCircle, Home, MessagesSquare } from 'lucide-react' import sdkRefRoutesJson from './sdkRefRoutes.json' +enum Tag { + New = 'New', +} + export interface NavLink { title: string href: string + tag?: Tag icon?: React.ReactNode } @@ -270,6 +275,11 @@ export const docRoutes: NavGroup[] = [ title: 'Lifecycle', href: '/docs/sandbox', }, + { + title: 'Persistence', + tag: Tag.New, + href: '/docs/sandbox/persistence', + }, { title: 'Metadata', href: '/docs/sandbox/metadata', @@ -295,9 +305,6 @@ export const docRoutes: NavGroup[] = [ // href: '/docs/sandbox/request-timeouts', // }, // { - // title: '* Persistence', - // href: '/docs/sandbox/persistence', - // }, ], }, { diff --git a/apps/web/src/components/Tag.tsx b/apps/web/src/components/Tag.tsx index 1d17fd30a..5f1c46f05 100644 --- a/apps/web/src/components/Tag.tsx +++ b/apps/web/src/components/Tag.tsx @@ -1,61 +1,16 @@ import clsx from 'clsx' - -const variantStyles = { - small: '', - medium: 'rounded-lg px-1.5 ring-1 ring-inset', -} - -const colorStyles = { - emerald: { - small: 'text-brand-500 dark:text-brand-400', - medium: - 'ring-brand-300 dark:ring-brand-400/30 bg-brand-400/10 text-brand-500 dark:text-brand-400', - }, - sky: { - small: 'text-sky-500', - medium: - 'ring-sky-300 bg-sky-400/10 text-sky-500 dark:ring-sky-400/30 dark:bg-sky-400/10 dark:text-sky-400', - }, - amber: { - small: 'text-amber-500', - medium: - 'ring-amber-300 bg-amber-400/10 text-amber-500 dark:ring-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400', - }, - rose: { - small: 'text-red-500 dark:text-rose-500', - medium: - 'ring-rose-200 bg-rose-50 text-red-500 dark:ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-400', - }, - zinc: { - small: 'text-zinc-400 dark:text-zinc-500', - medium: - 'ring-zinc-200 bg-zinc-50 text-zinc-500 dark:ring-zinc-500/20 dark:bg-zinc-400/10 dark:text-zinc-400', - }, -} - -const valueColorMap = { - GET: 'emerald', - POST: 'sky', - PUT: 'amber', - DELETE: 'rose', -} as Record +import React from 'react' export function Tag({ - children, - variant = 'medium', - color = valueColorMap[children] ?? 'emerald', - }: { - // eslint-disable-next-line @typescript-eslint/ban-types - children: keyof typeof valueColorMap & (string | {}) - variant?: keyof typeof variantStyles - color?: keyof typeof colorStyles + children, +}: { + children: React.ReactNode }) { return ( {children} From e8b479aff9c9898132fce5e6c75b5993cc279e49 Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Sun, 8 Dec 2024 13:58:55 -0800 Subject: [PATCH 05/53] Add docs page for persistence --- apps/web/src/app/(docs)/docs/page.mdx | 3 - .../(docs)/docs/sandbox/persistence/page.mdx | 105 ++++++++++++++++-- 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/page.mdx b/apps/web/src/app/(docs)/docs/page.mdx index b0fef3d9f..cc41c762b 100644 --- a/apps/web/src/app/(docs)/docs/page.mdx +++ b/apps/web/src/app/(docs)/docs/page.mdx @@ -31,14 +31,11 @@ Some of the typical use cases for E2B are AI data analysis or visualization, run The E2B Sandbox is a small isolated VM the can be started very quickly (~150ms). You can think of it as a small computer for the AI model. You can run many sandboxes at once. Typically, you run separate sandbox for each LLM, user, or AI agent session in your app. For example, if you were building an AI data analysis chatbot, you would start the sandbox for every user session. - ## Quickstart ## Code interpreting with AI - ## Learn the core concepts - diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index bde9784e1..3327ae634 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -1,16 +1,107 @@ # Sandbox persistence -We're working on a feature that will allow you to persist sandboxes between runs. + +Sandbox persistence is currently in beta with [some limitations](#limitations-while-in-beta). -In the meantime, you can mount cloud storage like Amazon's S3, Google Cloud Storage, or Cloudflare's R2 to the sandbox's filesystem. +The persistence is free for all users during the beta. + -**Prerequisites** -- Famil +The sandbox persistence allows you to pause your sandbox and resume it later from the same state it was in when you paused it. +This includes not only state of the sandbox's filesystem but also the sandbox's memory. This means all running processes, loaded variables, data, etc. +## Pausing sandbox +When you pause a sandbox, both the sandbox's filesystem and memory state will be saved. This includes all the files in the sandbox's filesystem and all the running processes, loaded variables, data, etc. -## Amazon S3 + +```js +import { Sandbox } from 'e2b' +// or use Code Interpreter: https://github.com/e2b-dev/code-interpreter +// import { Sandbox } from '@e2b/code-interpreter' +// +// or use Desktop: https://github.com/e2b-dev/desktop +// import { Sandbox } from '@e2b/desktop' -## Google Cloud Storage +const sbx = await Sandbox.create() +console.log('Sandbox created', sbx.id) -## Cloudflare R2 +// Pause the sandbox +// You can save the resumable ID in your database +// to resume the sandbox later +const resumableId = await sbx.pause() // $HighlightLine +console.log('Sandbox paused', resumableId) // $HighlightLine +``` +```python +from e2b import Sandbox +# or use Code Interpreter: https://github.com/e2b-dev/code-interpreter +# from e2b_code_interpreter import Sandbox +# +# or use Desktop: https://github.com/e2b-dev/desktop +# from e2b_desktop import Sandbox + +sbx = Sandbox() +print('Sandbox created', sbx.id) + +# Pause the sandbox +# You can save the resumable ID in your database +# to resume the sandbox later +resumable_id = sbx.pause() # $HighlightLine +print('Sandbox paused', resumable_id) # $HighlightLine +``` + + + +## Resuming sandbox +When you resume a sandbox, it will be in the same state it was in when you paused it. +This means that all the files in the sandbox's filesystem will be restored and all the running processes, loaded variables, data, etc. will be restored. + + +```js +import { Sandbox } from 'e2b' +// or use Code Interpreter: https://github.com/e2b-dev/code-interpreter +// import { Sandbox } from '@e2b/code-interpreter' +// +// or use Desktop: https://github.com/e2b-dev/desktop +// import { Sandbox } from '@e2b/desktop' + +const sbx = await Sandbox.create() +console.log('Sandbox created', sbx.id) + +// Pause the sandbox +// You can save the resumable ID in your database +// to resume the sandbox later +const resumableId = await sbx.pause() +console.log('Sandbox paused', resumableId) + +// Resume the sandbox from the same state +const sameSbx = await Sandbox.resume(resumableId) // $HighlightLine +console.log('Sandbox resumed', sameSbx.id) // $HighlightLine +``` +```python +from e2b import Sandbox +# or use Code Interpreter: https://github.com/e2b-dev/code-interpreter +# from e2b_code_interpreter import Sandbox +# +# or use Desktop: https://github.com/e2b-dev/desktop +# from e2b_desktop import Sandbox + +sbx = Sandbox() +print('Sandbox created', sbx.id) + +# Pause the sandbox +# You can save the resumable ID in your database +# to resume the sandbox later +resumable_id = sbx.pause() +print('Sandbox paused', resumable_id) + +# Resume the sandbox from the same state +same_sbx = Sandbox.resume(resumable_id) # $HighlightLine +print('Sandbox resumed', same_sbx.id) # $HighlightLine +``` + + + +## Limitations while in beta +- It takes about X seconds to pause the sandbox +- The sandbox can be paused up to X days +- The sandbox can only be resume on the same node (todo: friendlier explanation to users and add implications) From d11b70935afefe06468cb09f1ad1e288f622dac7 Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Sun, 8 Dec 2024 14:01:32 -0800 Subject: [PATCH 06/53] Add mention of the persistence on the home page of docs --- apps/web/src/components/Concepts.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/Concepts.tsx b/apps/web/src/components/Concepts.tsx index 238633368..013494d9c 100644 --- a/apps/web/src/components/Concepts.tsx +++ b/apps/web/src/components/Concepts.tsx @@ -1,7 +1,8 @@ import { FolderTree, Terminal, - Hourglass + Hourglass, + RefreshCcw, } from 'lucide-react' import { @@ -16,6 +17,12 @@ const concepts: BoxItem[] = [ description: 'Learn about how to start the sandbox, manage its lifecycle, and interact with it.', icon: , }, + { + href: '/docs/sandbox/persistence', + title: 'Sandbox persistence', + description: 'Learn how to achieve data persistence by pausing and resuming sandboxes.', + icon: , + }, // { // href: '/docs/code-execution', // title: 'AI code execution', @@ -25,13 +32,13 @@ const concepts: BoxItem[] = [ { href: '/docs/filesystem', title: 'Filesystem', - description: 'Each sandbox has its own isolated filesystem that you can use to create, read, write, and delete files.', + description: 'Sandbox has an isolated filesystem that you can use to create, read, write, and delete files.', icon: , }, { href: '/docs/commands', title: 'Commands', - description: 'You can run terminal commands inside the Sandbox. This allows you to start any process inside the Sandbox.', + description: 'Run terminal commands inside the Sandbox and start any process inside the Sandbox.', icon: , } ] From 950190d9b222a0d9c2d73345f3f8cb829b94a21f Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Sun, 8 Dec 2024 21:27:48 -0800 Subject: [PATCH 07/53] Add notes on current limitations --- .../src/app/(docs)/docs/sandbox/persistence/page.mdx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index 3327ae634..6dd1c3c07 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -10,6 +10,9 @@ The sandbox persistence allows you to pause your sandbox and resume it later fro This includes not only state of the sandbox's filesystem but also the sandbox's memory. This means all running processes, loaded variables, data, etc. +## Use cases +Persistence is useful in cases ... + ## Pausing sandbox When you pause a sandbox, both the sandbox's filesystem and memory state will be saved. This includes all the files in the sandbox's filesystem and all the running processes, loaded variables, data, etc. @@ -102,6 +105,7 @@ print('Sandbox resumed', same_sbx.id) # $HighlightLine ## Limitations while in beta -- It takes about X seconds to pause the sandbox -- The sandbox can be paused up to X days -- The sandbox can only be resume on the same node (todo: friendlier explanation to users and add implications) +- It takes about 4 seconds per 1 GB RAM to pause the sandbox +- It takes about 2 seconds to resume the sandbox + - Soon, this will get to the same speed as calling `Sandbox.create()` +- Sandbox can be paused up to 30 days \ No newline at end of file From 1c7052c890dc8eeac4bff63116b56766e0ab25e2 Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Sun, 8 Dec 2024 21:36:43 -0800 Subject: [PATCH 08/53] Update docs --- apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index 6dd1c3c07..453e6d8ad 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -10,9 +10,6 @@ The sandbox persistence allows you to pause your sandbox and resume it later fro This includes not only state of the sandbox's filesystem but also the sandbox's memory. This means all running processes, loaded variables, data, etc. -## Use cases -Persistence is useful in cases ... - ## Pausing sandbox When you pause a sandbox, both the sandbox's filesystem and memory state will be saved. This includes all the files in the sandbox's filesystem and all the running processes, loaded variables, data, etc. From 1c82910c84efdce53791a06062ea3e8eba5d866a Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 9 Dec 2024 20:48:35 -0800 Subject: [PATCH 09/53] gen new python sdk model for snapshots --- .../post_sandboxes_sandbox_id_pause.py | 101 ++++++++++ .../post_sandboxes_sandbox_id_resume.py | 185 ++++++++++++++++++ .../e2b/api/client/models/resumed_sandbox.py | 58 ++++++ 3 files changed, 344 insertions(+) create mode 100644 packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py create mode 100644 packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py create mode 100644 packages/python-sdk/e2b/api/client/models/resumed_sandbox.py diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py new file mode 100644 index 000000000..7e7334748 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py @@ -0,0 +1,101 @@ +from http import HTTPStatus +from typing import Any, Optional, Union + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...types import Response + + +def _get_kwargs( + sandbox_id: str, +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "post", + "url": f"/sandboxes/{sandbox_id}/pause", + } + + return _kwargs + + +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == 204: + return None + if response.status_code == 409: + return None + if response.status_code == 404: + return None + if response.status_code == 401: + return None + if response.status_code == 500: + return None + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, +) -> Response[Any]: + """Pause the sandbox + + Args: + sandbox_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +async def asyncio_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, +) -> Response[Any]: + """Pause the sandbox + + Args: + sandbox_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py new file mode 100644 index 000000000..25f378779 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py @@ -0,0 +1,185 @@ +from http import HTTPStatus +from typing import Any, Optional, Union, cast + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.resumed_sandbox import ResumedSandbox +from ...models.sandbox import Sandbox +from ...types import Response + + +def _get_kwargs( + sandbox_id: str, + *, + body: ResumedSandbox, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + _kwargs: dict[str, Any] = { + "method": "post", + "url": f"/sandboxes/{sandbox_id}/resume", + } + + _body = body.to_dict() + + _kwargs["json"] = _body + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[Any, Sandbox]]: + if response.status_code == 201: + response_201 = Sandbox.from_dict(response.json()) + + return response_201 + if response.status_code == 409: + response_409 = cast(Any, None) + return response_409 + if response.status_code == 404: + response_404 = cast(Any, None) + return response_404 + if response.status_code == 401: + response_401 = cast(Any, None) + return response_401 + if response.status_code == 500: + response_500 = cast(Any, None) + return response_500 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[Any, Sandbox]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: ResumedSandbox, +) -> Response[Union[Any, Sandbox]]: + """Resume the sandbox + + Args: + sandbox_id (str): + body (ResumedSandbox): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, Sandbox]] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: ResumedSandbox, +) -> Optional[Union[Any, Sandbox]]: + """Resume the sandbox + + Args: + sandbox_id (str): + body (ResumedSandbox): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, Sandbox] + """ + + return sync_detailed( + sandbox_id=sandbox_id, + client=client, + body=body, + ).parsed + + +async def asyncio_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: ResumedSandbox, +) -> Response[Union[Any, Sandbox]]: + """Resume the sandbox + + Args: + sandbox_id (str): + body (ResumedSandbox): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, Sandbox]] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: ResumedSandbox, +) -> Optional[Union[Any, Sandbox]]: + """Resume the sandbox + + Args: + sandbox_id (str): + body (ResumedSandbox): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, Sandbox] + """ + + return ( + await asyncio_detailed( + sandbox_id=sandbox_id, + client=client, + body=body, + ) + ).parsed diff --git a/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py b/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py new file mode 100644 index 000000000..781565339 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py @@ -0,0 +1,58 @@ +from typing import Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ResumedSandbox") + + +@_attrs_define +class ResumedSandbox: + """ + Attributes: + timeout (Union[Unset, int]): Time to live for the sandbox in seconds. Default: 15. + """ + + timeout: Union[Unset, int] = 15 + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + timeout = self.timeout + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if timeout is not UNSET: + field_dict["timeout"] = timeout + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + d = src_dict.copy() + timeout = d.pop("timeout", UNSET) + + resumed_sandbox = cls( + timeout=timeout, + ) + + resumed_sandbox.additional_properties = d + return resumed_sandbox + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties From 8b5f3d017d076cd44e8a221534d7aa1961f46dd4 Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 9 Dec 2024 21:21:24 -0800 Subject: [PATCH 10/53] add resume method --- packages/python-sdk/e2b/sandbox_sync/main.py | 25 +++++++- .../e2b/sandbox_sync/sandbox_api.py | 57 +++++++++++++++---- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 2fa54ea86..de40585ec 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -7,9 +7,9 @@ from e2b.exceptions import SandboxException, format_request_timeout_error from e2b.sandbox.main import SandboxSetup from e2b.sandbox.utils import class_method_variant -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.filesystem.filesystem import Filesystem from e2b.sandbox_sync.sandbox_api import SandboxApi logger = logging.getLogger(__name__) @@ -355,3 +355,26 @@ def set_timeout( # type: ignore timeout=timeout, **self.connection_config.__dict__, ) + + @class_method_variant("_cls_resume") + def resume( + cls, + sandbox_id: str, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ): + + SandboxApi._cls_resume( + sandbox_id=sandbox_id, + request_timeout=request_timeout, + **self.connection_config.__dict__, + ) + + return cls.connect( + sandbox_id=sandbox_id, + api_key=api_key, + domain=domain, + debug=debug, + ) diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 8e37aab02..944a2d36b 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -1,19 +1,23 @@ -from httpx import HTTPTransport -from typing import Optional, Dict, List -from packaging.version import Version +from typing import Dict, List, Optional -from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase -from e2b.exceptions import TemplateException -from e2b.api import ApiClient -from e2b.api.client.models import NewSandbox, PostSandboxesSandboxIDTimeoutBody +from e2b.api import ApiClient, handle_api_exception from e2b.api.client.api.sandboxes import ( - post_sandboxes_sandbox_id_timeout, - get_sandboxes, delete_sandboxes_sandbox_id, + get_sandboxes, post_sandboxes, + post_sandboxes_sandbox_id_resume, + post_sandboxes_sandbox_id_timeout, +) +from e2b.api.client.models import ( + NewSandbox, + PostSandboxesSandboxIDTimeoutBody, + ResumedSandbox, ) from e2b.connection_config import ConnectionConfig -from e2b.api import handle_api_exception +from e2b.exceptions import TemplateException +from e2b.sandbox.sandbox_api import SandboxApiBase, SandboxInfo +from httpx import HTTPTransport +from packaging.version import Version class SandboxApi(SandboxApiBase): @@ -192,3 +196,36 @@ def _create_sandbox( res.parsed.sandbox_id, res.parsed.client_id, ) + + @classmethod + def _cls_resume( + cls, + sandbox_id: str, + timeout: int, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ) -> None: + config = ConnectionConfig( + api_key=api_key, + domain=domain, + debug=debug, + request_timeout=request_timeout, + ) + + if config.debug: + # Skip setting timeout in debug mode + return + + with ApiClient( + config, transport=HTTPTransport(limits=SandboxApiBase._limits) + ) as api_client: + res = post_sandboxes_sandbox_id_resume.sync_detailed( + sandbox_id, + client=api_client, + body=ResumedSandbox(timeout=timeout), + ) + + if res.status_code >= 300: + raise handle_api_exception(res) From 5322c1b8bf6afb5e04dc819e4c4e2b08ef62a2d1 Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 9 Dec 2024 21:22:36 -0800 Subject: [PATCH 11/53] add model init --- packages/python-sdk/e2b/api/client/models/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index d55b958c2..256d048d4 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -4,6 +4,7 @@ from .new_sandbox import NewSandbox from .post_sandboxes_sandbox_id_refreshes_body import PostSandboxesSandboxIDRefreshesBody from .post_sandboxes_sandbox_id_timeout_body import PostSandboxesSandboxIDTimeoutBody +from .resumed_sandbox import ResumedSandbox from .running_sandbox import RunningSandbox from .sandbox import Sandbox from .sandbox_log import SandboxLog @@ -19,6 +20,7 @@ "NewSandbox", "PostSandboxesSandboxIDRefreshesBody", "PostSandboxesSandboxIDTimeoutBody", + "ResumedSandbox", "RunningSandbox", "Sandbox", "SandboxLog", From 8297cb232a7b4f86eb1ccc03ce449669423bd12a Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 9 Dec 2024 22:40:34 -0800 Subject: [PATCH 12/53] add pause and resume methods to async python sdk --- packages/python-sdk/e2b/sandbox_async/main.py | 48 +++++++++++- .../e2b/sandbox_async/sandbox_api.py | 74 ++++++++++++++++--- packages/python-sdk/e2b/sandbox_sync/main.py | 11 +++ .../e2b/sandbox_sync/sandbox_api.py | 28 +++++++ packages/python-sdk/package.json | 2 +- 5 files changed, 149 insertions(+), 14 deletions(-) diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 066162d85..4cba8b810 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -1,18 +1,17 @@ import logging -import httpx - from typing import Dict, Optional, TypedDict, overload -from typing_extensions import Unpack +import httpx from e2b.connection_config import ConnectionConfig from e2b.envd.api import ENVD_API_HEALTH_ROUTE, ahandle_envd_api_exception from e2b.exceptions import format_request_timeout_error from e2b.sandbox.main import SandboxSetup from e2b.sandbox.utils import class_method_variant -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.filesystem.filesystem import Filesystem from e2b.sandbox_async.sandbox_api import SandboxApi +from typing_extensions import Unpack logger = logging.getLogger(__name__) @@ -364,3 +363,44 @@ async def set_timeout( # type: ignore timeout=timeout, **self.connection_config.__dict__, ) + + @class_method_variant("_cls_resume") + async def resume( + cls, + sandbox_id: str, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ): + await SandboxApi._cls_resume( + sandbox_id=sandbox_id, + request_timeout=request_timeout, + api_key=api_key, + domain=domain, + debug=debug, + ) + + return cls.connect( + sandbox_id=sandbox_id, + api_key=api_key, + domain=domain, + debug=debug, + ) + + @class_method_variant("_cls_pause") + async def pause( + cls, + sandbox_id: str, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ) -> None: + return await SandboxApi._cls_pause( + sandbox_id=sandbox_id, + api_key=api_key, + domain=domain, + debug=debug, + request_timeout=request_timeout, + ) diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index ea99105cc..8731b4d26 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -1,18 +1,23 @@ -from typing import Optional, Dict, List -from packaging.version import Version +from typing import Dict, List, Optional -from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase -from e2b.exceptions import TemplateException -from e2b.api import AsyncApiClient -from e2b.api.client.models import NewSandbox, PostSandboxesSandboxIDTimeoutBody +from e2b.api import AsyncApiClient, handle_api_exception from e2b.api.client.api.sandboxes import ( - post_sandboxes_sandbox_id_timeout, - get_sandboxes, delete_sandboxes_sandbox_id, + get_sandboxes, post_sandboxes, + post_sandboxes_sandbox_id_pause, + post_sandboxes_sandbox_id_resume, + post_sandboxes_sandbox_id_timeout, +) +from e2b.api.client.models import ( + NewSandbox, + PostSandboxesSandboxIDTimeoutBody, + ResumedSandbox, ) from e2b.connection_config import ConnectionConfig -from e2b.api import handle_api_exception +from e2b.exceptions import TemplateException +from e2b.sandbox.sandbox_api import SandboxApiBase, SandboxInfo +from packaging.version import Version class SandboxApi(SandboxApiBase): @@ -187,3 +192,54 @@ async def _create_sandbox( @staticmethod def _get_sandbox_id(sandbox_id: str, client_id: str) -> str: return f"{sandbox_id}-{client_id}" + + @classmethod + async def _cls_resume( + cls, + sandbox_id: str, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ) -> None: + config = ConnectionConfig( + api_key=api_key, + domain=domain, + debug=debug, + request_timeout=request_timeout, + ) + + async with AsyncApiClient(config) as api_client: + res = await post_sandboxes_sandbox_id_resume.asyncio_detailed( + sandbox_id, + client=api_client, + body=ResumedSandbox(timeout=timeout), + ) + + if res.status_code >= 300: + raise handle_api_exception(res) + + @classmethod + async def _cls_pause( + cls, + sandbox_id: str, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ) -> None: + config = ConnectionConfig( + api_key=api_key, + domain=domain, + debug=debug, + request_timeout=request_timeout, + ) + + async with AsyncApiClient(config) as api_client: + res = await post_sandboxes_sandbox_id_pause.asyncio_detailed( + sandbox_id, + client=api_client, + ) + + if res.status_code >= 300: + raise handle_api_exception(res) diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index de40585ec..49dfad737 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -378,3 +378,14 @@ def resume( domain=domain, debug=debug, ) + + @class_method_variant("_cls_pause") + def pause( + cls, + sandbox_id: str, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ) -> None: + SandboxApi._cls_pause(sandbox_id=sandbox_id, **self.connection_config.__dict__) diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 944a2d36b..9eb4efc11 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -5,6 +5,7 @@ delete_sandboxes_sandbox_id, get_sandboxes, post_sandboxes, + post_sandboxes_sandbox_id_pause, post_sandboxes_sandbox_id_resume, post_sandboxes_sandbox_id_timeout, ) @@ -229,3 +230,30 @@ def _cls_resume( if res.status_code >= 300: raise handle_api_exception(res) + + @classmethod + def _cls_pause( + cls, + sandbox_id: str, + api_key: Optional[str] = None, + domain: Optional[str] = None, + debug: Optional[bool] = None, + request_timeout: Optional[float] = None, + ) -> None: + 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 = post_sandboxes_sandbox_id_pause.sync_detailed( + sandbox_id, + client=api_client, + ) + + if res.status_code >= 300: + raise handle_api_exception(res) diff --git a/packages/python-sdk/package.json b/packages/python-sdk/package.json index 81fe61ea2..7bdf08df0 100644 --- a/packages/python-sdk/package.json +++ b/packages/python-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@e2b/python-sdk", "private": true, - "version": "1.0.3", + "version": "1.0.4", "scripts": { "example": "poetry run python example.py", "test": "poetry run pytest -n 4 --verbose -x", From 4b4922f5e45c2f2cedeba3384d8a24df4df65ddd Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Mon, 9 Dec 2024 22:42:14 -0800 Subject: [PATCH 13/53] Expose timeoutMs in resume --- packages/js-sdk/src/sandbox/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index e16e9ec0a..91ef1d5dc 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -363,7 +363,7 @@ export class Sandbox extends SandboxApi { * * @returns sandbox instance. */ - async resume(opts?: Pick): Promise { + async resume(opts?: Pick): Promise { await Sandbox.resume(this.sandboxId, { ...this.connectionConfig, ...opts }) return this From 2aeb66ccf3256baa09379051ae8017d3a12749cd Mon Sep 17 00:00:00 2001 From: 0div Date: Fri, 13 Dec 2024 11:57:02 -0800 Subject: [PATCH 14/53] make each test pause and resume the sbx --- packages/js-sdk/tests/setup.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/js-sdk/tests/setup.ts b/packages/js-sdk/tests/setup.ts index 7cdbd5e0c..a40500f59 100644 --- a/packages/js-sdk/tests/setup.ts +++ b/packages/js-sdk/tests/setup.ts @@ -1,17 +1,24 @@ import { Sandbox } from '../src' import { test as base } from 'vitest' -export const template = 'base' +export const template = 'base_01' interface SandboxFixture { sandbox: Sandbox + template: string } export const sandboxTest = base.extend({ + template, sandbox: [ async ({}, use) => { const sandbox = await Sandbox.create(template) try { + console.log('pause sandbox') + const sandboxId = await sandbox.pause() + console.log('resume sandbox', sandboxId) + await sandbox.resume() + console.log('sandbox resumed') await use(sandbox) } finally { try { From d7df1ef483085b45716a4fbf203834bf76ac246e Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Fri, 13 Dec 2024 12:12:05 -0800 Subject: [PATCH 15/53] Add prerelease tags --- packages/js-sdk/example.mts | 42 +++++++++++++++++++++++++++--- packages/js-sdk/package.json | 2 +- packages/python-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/packages/js-sdk/example.mts b/packages/js-sdk/example.mts index 041f21f29..632a47df5 100644 --- a/packages/js-sdk/example.mts +++ b/packages/js-sdk/example.mts @@ -4,8 +4,42 @@ import dotenv from 'dotenv' dotenv.config() -for (let i = 0; i < 10; i++) { - const start = Date.now() - const sbx = await Sandbox.create({ timeoutMs: 10000 }) - console.log('time', Date.now() - start) +const start = Date.now() +console.log('creating sandbox') +const sbx = await Sandbox.create('k1urqpinffy6bcost93w', { timeoutMs: 10000 }) +console.log('sandbox created', Date.now() - start) +console.log(sbx.sandboxId) + +await sbx.files.write('/home/user/test.txt', 'hello') + +const startPausing = Date.now() +console.log('pausing sandbox') +await sbx.pause() +console.log('sandbox paused', Date.now() - startPausing) + +async function wait(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) } + +await wait(20_000) + +console.log(sbx.sandboxId) + +// console.log('killing sandbox') +// await sbx.kill() +// console.log('sandbox killed') + +const resumeStart = Date.now() +console.log('resuming sandbox') +const resumed = await Sandbox.resume(sbx.sandboxId, { timeoutMs: 10000 }) +console.log('sandbox resumed', Date.now() - resumeStart) + +const content = await resumed.files.read('/home/user/test.txt') +console.log('content', content) + +const running = await resumed.isRunning() +console.log('sandbox running', running) + + + +// console.log(sbx.sandboxId) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index d57b08e15..6c492d279 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.0.5", + "version": "1.0.6-snapshots.0", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/package.json b/packages/python-sdk/package.json index b118a429d..0e23fc3e6 100644 --- a/packages/python-sdk/package.json +++ b/packages/python-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@e2b/python-sdk", "private": true, - "version": "1.0.4", + "version": "1.0.5-snapshots.0", "scripts": { "example": "poetry run python example.py", "test": "poetry run pytest -n 4 --verbose -x", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 2e676e0f4..0c2b484c8 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.0.5" +version = "1.0.5-snapshots.0" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 5053639396a0c9a13e10d6dcd11cb06d5c8cd870 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Fri, 13 Dec 2024 12:28:50 -0800 Subject: [PATCH 16/53] Temporarily disable prerelease tests --- .github/workflows/release_candidates.yml | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release_candidates.yml b/.github/workflows/release_candidates.yml index 3405514a3..61589aa5a 100644 --- a/.github/workflows/release_candidates.yml +++ b/.github/workflows/release_candidates.yml @@ -52,14 +52,14 @@ jobs: virtualenvs-in-project: true installer-parallel: true - - name: Test Python SDK - if: ${{ contains( github.event.pull_request.labels.*.name, 'python-rc') }} - working-directory: packages/python-sdk - run: | - poetry install - poetry run pytest --verbose -x - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + # - name: Test Python SDK + # if: ${{ contains( github.event.pull_request.labels.*.name, 'python-rc') }} + # working-directory: packages/python-sdk + # run: | + # poetry install + # poetry run pytest --verbose -x + # env: + # E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - name: Release Candidate if: ${{ contains( github.event.pull_request.labels.*.name, 'python-rc') }} @@ -76,14 +76,14 @@ jobs: run: | pnpm install --frozen-lockfile - - name: Test JS SDK - working-directory: packages/js-sdk - if: ${{ contains( github.event.pull_request.labels.*.name, 'js-rc') }} - run: | - npx playwright install --with-deps - pnpm run test - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + # - name: Test JS SDK + # working-directory: packages/js-sdk + # if: ${{ contains( github.event.pull_request.labels.*.name, 'js-rc') }} + # run: | + # npx playwright install --with-deps + # pnpm run test + # env: + # E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - name: Release JS Candidate working-directory: packages/js-sdk From 3610a7c86f98ff4a7db79ba70efa60f57d7af719 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Fri, 13 Dec 2024 12:36:08 -0800 Subject: [PATCH 17/53] Fix version; Add metadata --- packages/python-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/python-sdk/package.json b/packages/python-sdk/package.json index 0e23fc3e6..cbb1f4637 100644 --- a/packages/python-sdk/package.json +++ b/packages/python-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@e2b/python-sdk", "private": true, - "version": "1.0.5-snapshots.0", + "version": "1.0.5b0+snapshots", "scripts": { "example": "poetry run python example.py", "test": "poetry run pytest -n 4 --verbose -x", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 0c2b484c8..2e9e5ac4e 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.0.5-snapshots.0" +version = "1.0.5b0+snapshots" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 568ff755169101fc6a43519d032a428bd4995f34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Dec 2024 20:37:23 +0000 Subject: [PATCH 18/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 6c492d279..2e2bbe426 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.0.6-snapshots.0", + "version": "1.0.6-add-pause-and-resume-to-sdk-e2b-1190.0", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 2e9e5ac4e..b2b111cd9 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.0.5b0+snapshots" +version = "1.0.5b1" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From babdb11c4440045b12b38766f3921e2d43a112c1 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Fri, 13 Dec 2024 13:14:56 -0800 Subject: [PATCH 19/53] Cleanup pause and resume in SDKs --- .../(docs)/docs/sandbox/persistence/page.mdx | 32 +++---- packages/js-sdk/package.json | 4 +- packages/js-sdk/src/sandbox/index.ts | 42 +++------ packages/js-sdk/src/sandbox/sandboxApi.ts | 85 ++++++++++--------- packages/python-sdk/e2b/sandbox_async/main.py | 53 +++++++++--- .../e2b/sandbox_async/sandbox_api.py | 23 ++++- packages/python-sdk/e2b/sandbox_sync/main.py | 49 ++++++++--- .../e2b/sandbox_sync/sandbox_api.py | 26 ++++-- packages/python-sdk/example.py | 2 + packages/python-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 11 files changed, 193 insertions(+), 127 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index 453e6d8ad..7acb88571 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -23,13 +23,13 @@ import { Sandbox } from 'e2b' // import { Sandbox } from '@e2b/desktop' const sbx = await Sandbox.create() -console.log('Sandbox created', sbx.id) +console.log('Sandbox created', sbx.sandboxId) // Pause the sandbox // You can save the resumable ID in your database // to resume the sandbox later -const resumableId = await sbx.pause() // $HighlightLine -console.log('Sandbox paused', resumableId) // $HighlightLine +const sandboxId = await sbx.pause() // $HighlightLine +console.log('Sandbox paused', sandboxId) // $HighlightLine ``` ```python from e2b import Sandbox @@ -40,13 +40,13 @@ from e2b import Sandbox # from e2b_desktop import Sandbox sbx = Sandbox() -print('Sandbox created', sbx.id) +print('Sandbox created', sbx.sandbox_id) # Pause the sandbox # You can save the resumable ID in your database # to resume the sandbox later -resumable_id = sbx.pause() # $HighlightLine -print('Sandbox paused', resumable_id) # $HighlightLine +sandbox_id = sbx.pause() # $HighlightLine +print('Sandbox paused', sandbox_id) # $HighlightLine ``` @@ -65,17 +65,17 @@ import { Sandbox } from 'e2b' // import { Sandbox } from '@e2b/desktop' const sbx = await Sandbox.create() -console.log('Sandbox created', sbx.id) +console.log('Sandbox created', sbx.sandboxId) // Pause the sandbox // You can save the resumable ID in your database // to resume the sandbox later -const resumableId = await sbx.pause() -console.log('Sandbox paused', resumableId) +const sandboxId = await sbx.pause() +console.log('Sandbox paused', sandboxId) // Resume the sandbox from the same state -const sameSbx = await Sandbox.resume(resumableId) // $HighlightLine -console.log('Sandbox resumed', sameSbx.id) // $HighlightLine +const sameSbx = await Sandbox.resume(sandboxId) // $HighlightLine +console.log('Sandbox resumed', sameSbx.sandboxId) // $HighlightLine ``` ```python from e2b import Sandbox @@ -86,17 +86,17 @@ from e2b import Sandbox # from e2b_desktop import Sandbox sbx = Sandbox() -print('Sandbox created', sbx.id) +print('Sandbox created', sbx.sandbox_id) # Pause the sandbox # You can save the resumable ID in your database # to resume the sandbox later -resumable_id = sbx.pause() -print('Sandbox paused', resumable_id) +sandbox_id = sbx.pause() +print('Sandbox paused', sandbox_id) # Resume the sandbox from the same state -same_sbx = Sandbox.resume(resumable_id) # $HighlightLine -print('Sandbox resumed', same_sbx.id) # $HighlightLine +same_sbx = Sandbox.resume(sandbox_id) # $HighlightLine +print('Sandbox resumed', same_sbx.sandbox_id) # $HighlightLine ``` diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 2e2bbe426..3038fdd30 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.0.6-add-pause-and-resume-to-sdk-e2b-1190.0", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.0", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", @@ -92,4 +92,4 @@ "browserslist": [ "defaults" ] -} +} \ No newline at end of file diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 2f30db359..176ed1c02 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -105,9 +105,8 @@ export class Sandbox extends SandboxApi { this.sandboxId = opts.sandboxId this.connectionConfig = new ConnectionConfig(opts) - this.envdApiUrl = `${ - this.connectionConfig.debug ? 'http' : 'https' - }://${this.getHost(this.envdPort)}` + this.envdApiUrl = `${this.connectionConfig.debug ? 'http' : 'https' + }://${this.getHost(this.envdPort)}` const rpcTransport = createConnectTransport({ baseUrl: this.envdApiUrl, @@ -180,10 +179,10 @@ export class Sandbox extends SandboxApi { const sandboxId = config.debug ? 'debug_sandbox_id' : await this.createSandbox( - template, - sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs, - sandboxOpts - ) + template, + sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs, + sandboxOpts + ) const sbx = new this({ sandboxId, ...config }) as InstanceType return sbx @@ -227,7 +226,7 @@ export class Sandbox extends SandboxApi { * @param sandboxId sandbox ID. * @param opts connection options. * - * @returns sandbox instance. + * @returns a running sandbox instance. */ static async resume( this: S, @@ -343,31 +342,12 @@ export class Sandbox extends SandboxApi { * * @param opts connection options. * - * @returns `true` if the sandbox got paused, `false` if the sandbox was already paused. + * @returns sandbox ID that can be used to resume the sandbox. */ - async pause(opts?: Pick): Promise { - if (this.connectionConfig.debug) { - // Skip pausing in debug mode - return true - } - - return await Sandbox.pause(this.sandboxId, { ...this.connectionConfig, ...opts }) - } - - /** - * Resume the sandbox. - * - * The **default sandbox timeout of 300 seconds** ({@link Sandbox.defaultSandboxTimeoutMs}) will be used for the resumed sandbox. - * If you pass a custom timeout in the `opts` parameter via {@link SandboxOpts.timeoutMs} property, it will be used instead. - * - * @param opts connection options. - * - * @returns sandbox instance. - */ - async resume(opts?: Pick): Promise { - await Sandbox.resume(this.sandboxId, { ...this.connectionConfig, ...opts }) + async pause(opts?: Pick): Promise { + await Sandbox.pauseSandbox(this.sandboxId, { ...this.connectionConfig, ...opts }) - return this + return this.sandboxId } /** diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index d42f425c5..0304def5e 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -80,47 +80,6 @@ export class SandboxApi { return true } - /** - * Pause the sandbox specified by sandbox ID. - * - * @param sandboxId sandbox ID. - * @param opts connection options. - * - * @returns `true` if the sandbox got paused, `false` if the sandbox was already paused. - */ - static async pause( - sandboxId: string, - opts?: SandboxApiOpts - ): Promise { - const config = new ConnectionConfig(opts) - const client = new ApiClient(config) - - const res = await client.api.POST('/sandboxes/{sandboxID}/pause', { - params: { - path: { - sandboxID: sandboxId, - }, - }, - signal: config.getSignal(opts?.requestTimeoutMs), - }) - - if (res.error?.code === 404) { - throw new NotFoundError(`Sandbox ${sandboxId} not found`) - } - - if (res.error?.code === 409) { - // Sandbox is already paused - return false - } - - const err = handleApiError(res) - if (err) { - throw err - } - - return true - } - /** * List all running sandboxes. * @@ -193,6 +152,48 @@ export class SandboxApi { } } + /** + * Pause the sandbox specified by sandbox ID. + * + * @param sandboxId sandbox ID. + * @param opts connection options. + * + * @returns `true` if the sandbox got paused, `false` if the sandbox was already paused. + */ + protected static async pauseSandbox( + sandboxId: string, + opts?: SandboxApiOpts + ): Promise { + const config = new ConnectionConfig(opts) + const client = new ApiClient(config) + + const res = await client.api.POST('/sandboxes/{sandboxID}/pause', { + params: { + path: { + sandboxID: sandboxId, + }, + }, + signal: config.getSignal(opts?.requestTimeoutMs), + }) + + if (res.error?.code === 404) { + throw new NotFoundError(`Sandbox ${sandboxId} not found`) + } + + if (res.error?.code === 409) { + // Sandbox is already paused + return false + } + + const err = handleApiError(res) + if (err) { + throw err + } + + return true + } + + protected static async resumeSandbox( sandboxId: string, timeoutMs: number, @@ -218,7 +219,7 @@ export class SandboxApi { } if (res.error?.code === 409) { - // Sandbox is not paused + // Sandbox is already running return false } diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 4cba8b810..5635b6a8d 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -364,43 +364,68 @@ async def set_timeout( # type: ignore **self.connection_config.__dict__, ) - @class_method_variant("_cls_resume") + @classmethod async def resume( cls, sandbox_id: str, + timeout: Optional[int] = None, api_key: Optional[str] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, ): + """ + Resume the sandbox. + + The **default sandbox timeout of 300 seconds** will be used for the resumed sandbox. + If you pass a custom timeout via the `timeout` parameter, it will be used instead. + + :param sandbox_id: sandbox ID + :param timeout: Timeout for the sandbox in **seconds** + :param api_key: E2B API Key to use for authentication + :param domain: Domain of the sandbox server + :param debug: Enable debug mode + :param request_timeout: Timeout for the request in **seconds** + + :return: A running sandbox instance + """ + + timeout = timeout or cls.default_sandbox_timeout + await SandboxApi._cls_resume( sandbox_id=sandbox_id, request_timeout=request_timeout, + timeout=timeout, api_key=api_key, domain=domain, debug=debug, ) - return cls.connect( + return await cls.connect( sandbox_id=sandbox_id, api_key=api_key, domain=domain, debug=debug, ) - @class_method_variant("_cls_pause") async def pause( - cls, - sandbox_id: str, - api_key: Optional[str] = None, - domain: Optional[str] = None, - debug: Optional[bool] = None, + self, request_timeout: Optional[float] = None, - ) -> None: - return await SandboxApi._cls_pause( - sandbox_id=sandbox_id, - api_key=api_key, - domain=domain, - debug=debug, + ) -> str: + """ + Pause the sandbox. + + :param request_timeout: Timeout for the request in **seconds** + + :return: sandbox ID that can be used to resume the sandbox + """ + + await SandboxApi._cls_pause( + sandbox_id=self.sandbox_id, + api_key=self.connection_config.api_key, + domain=self.connection_config.domain, + debug=self.connection_config.debug, request_timeout=request_timeout, ) + + return self.sandbox_id diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index 8731b4d26..76f01b1c6 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -15,7 +15,7 @@ ResumedSandbox, ) from e2b.connection_config import ConnectionConfig -from e2b.exceptions import TemplateException +from e2b.exceptions import TemplateException, NotFoundException from e2b.sandbox.sandbox_api import SandboxApiBase, SandboxInfo from packaging.version import Version @@ -197,11 +197,12 @@ def _get_sandbox_id(sandbox_id: str, client_id: str) -> str: async def _cls_resume( cls, sandbox_id: str, + timeout: int, api_key: Optional[str] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, - ) -> None: + ) -> bool: config = ConnectionConfig( api_key=api_key, domain=domain, @@ -216,9 +217,17 @@ async def _cls_resume( body=ResumedSandbox(timeout=timeout), ) + if res.status_code == 404: + raise NotFoundException(f"Paused sandbox {sandbox_id} not found") + + if res.status_code == 409: + return False + if res.status_code >= 300: raise handle_api_exception(res) + return True + @classmethod async def _cls_pause( cls, @@ -227,7 +236,7 @@ async def _cls_pause( domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, - ) -> None: + ) -> bool: config = ConnectionConfig( api_key=api_key, domain=domain, @@ -241,5 +250,13 @@ async def _cls_pause( client=api_client, ) + if res.status_code == 404: + raise NotFoundException(f"Sandbox {sandbox_id} not found") + + if res.status_code == 409: + return False + if res.status_code >= 300: raise handle_api_exception(res) + + return True diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 49dfad737..03ef11ebd 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -356,20 +356,38 @@ def set_timeout( # type: ignore **self.connection_config.__dict__, ) - @class_method_variant("_cls_resume") + @classmethod def resume( cls, sandbox_id: str, + timeout: Optional[int] = None, api_key: Optional[str] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, ): + """ + Resume the sandbox. + + The **default sandbox timeout of 300 seconds** will be used for the resumed sandbox. + If you pass a custom timeout via the `timeout` parameter, it will be used instead. + + :param sandbox_id: sandbox ID + :param timeout: Timeout for the sandbox in **seconds** + :param api_key: E2B API Key to use for authentication + :param domain: Domain of the sandbox server + :param debug: Enable debug mode + :param request_timeout: Timeout for the request in **seconds** + + :return: A running sandbox instance + """ + + timeout = timeout or cls.default_sandbox_timeout SandboxApi._cls_resume( sandbox_id=sandbox_id, request_timeout=request_timeout, - **self.connection_config.__dict__, + timeout=timeout, ) return cls.connect( @@ -379,13 +397,24 @@ def resume( debug=debug, ) - @class_method_variant("_cls_pause") def pause( - cls, - sandbox_id: str, - api_key: Optional[str] = None, - domain: Optional[str] = None, - debug: Optional[bool] = None, + self, request_timeout: Optional[float] = None, - ) -> None: - SandboxApi._cls_pause(sandbox_id=sandbox_id, **self.connection_config.__dict__) + ) -> str: + """ + Pause the sandbox. + + :param request_timeout: Timeout for the request in **seconds** + + :return: sandbox ID that can be used to resume the sandbox + """ + + SandboxApi._cls_pause( + sandbox_id=self.sandbox_id, + api_key=self.connection_config.api_key, + domain=self.connection_config.domain, + debug=self.connection_config.debug, + request_timeout=request_timeout, + ) + + return self.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 9eb4efc11..9e65ad876 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -15,7 +15,7 @@ ResumedSandbox, ) from e2b.connection_config import ConnectionConfig -from e2b.exceptions import TemplateException +from e2b.exceptions import TemplateException, NotFoundException from e2b.sandbox.sandbox_api import SandboxApiBase, SandboxInfo from httpx import HTTPTransport from packaging.version import Version @@ -207,7 +207,7 @@ def _cls_resume( domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, - ) -> None: + ) -> bool: config = ConnectionConfig( api_key=api_key, domain=domain, @@ -215,10 +215,6 @@ def _cls_resume( request_timeout=request_timeout, ) - if config.debug: - # Skip setting timeout in debug mode - return - with ApiClient( config, transport=HTTPTransport(limits=SandboxApiBase._limits) ) as api_client: @@ -228,9 +224,17 @@ def _cls_resume( body=ResumedSandbox(timeout=timeout), ) + if res.status_code == 404: + raise NotFoundException(f"Paused sandbox {sandbox_id} not found") + + if res.status_code == 409: + return False + if res.status_code >= 300: raise handle_api_exception(res) + return True + @classmethod def _cls_pause( cls, @@ -239,7 +243,7 @@ def _cls_pause( domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, - ) -> None: + ) -> bool: config = ConnectionConfig( api_key=api_key, domain=domain, @@ -255,5 +259,13 @@ def _cls_pause( client=api_client, ) + if res.status_code == 404: + raise NotFoundException(f"Sandbox {sandbox_id} not found") + + if res.status_code == 409: + return False + if res.status_code >= 300: raise handle_api_exception(res) + + return True diff --git a/packages/python-sdk/example.py b/packages/python-sdk/example.py index 1da1ffda3..f15f5eaff 100644 --- a/packages/python-sdk/example.py +++ b/packages/python-sdk/example.py @@ -12,7 +12,9 @@ async def main(): sbx = await AsyncSandbox.create(timeout=10, debug=True) await sbx.set_timeout(20) + id = await sbx.pause() + sbx = await AsyncSandbox.resume(id) if __name__ == "__main__": asyncio.run(main()) diff --git a/packages/python-sdk/package.json b/packages/python-sdk/package.json index cbb1f4637..813c05107 100644 --- a/packages/python-sdk/package.json +++ b/packages/python-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@e2b/python-sdk", "private": true, - "version": "1.0.5b0+snapshots", + "version": "1.1.0b0", "scripts": { "example": "poetry run python example.py", "test": "poetry run pytest -n 4 --verbose -x", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index b2b111cd9..6c6b36d6c 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.0.5b1" +version = "1.1.0b0" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From a9814e5253649bbdbed47835f9d1c6ecf710f81d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Dec 2024 21:16:06 +0000 Subject: [PATCH 20/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 4 ++-- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 3038fdd30..9599e77af 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.0", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.1", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", @@ -92,4 +92,4 @@ "browserslist": [ "defaults" ] -} \ No newline at end of file +} diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 6c6b36d6c..2882a9d38 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b0" +version = "1.1.0b1" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From b0c4cf19b76ba25af2c21a35d5e14a9263b1a0c5 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Fri, 13 Dec 2024 13:33:15 -0800 Subject: [PATCH 21/53] Cleanup --- apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index 7acb88571..8e6c45152 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -26,7 +26,7 @@ const sbx = await Sandbox.create() console.log('Sandbox created', sbx.sandboxId) // Pause the sandbox -// You can save the resumable ID in your database +// You can save the sandbox ID in your database // to resume the sandbox later const sandboxId = await sbx.pause() // $HighlightLine console.log('Sandbox paused', sandboxId) // $HighlightLine @@ -43,7 +43,7 @@ sbx = Sandbox() print('Sandbox created', sbx.sandbox_id) # Pause the sandbox -# You can save the resumable ID in your database +# You can save the sandbox ID in your database # to resume the sandbox later sandbox_id = sbx.pause() # $HighlightLine print('Sandbox paused', sandbox_id) # $HighlightLine @@ -68,7 +68,7 @@ const sbx = await Sandbox.create() console.log('Sandbox created', sbx.sandboxId) // Pause the sandbox -// You can save the resumable ID in your database +// You can save the sandbox ID in your database // to resume the sandbox later const sandboxId = await sbx.pause() console.log('Sandbox paused', sandboxId) @@ -89,7 +89,7 @@ sbx = Sandbox() print('Sandbox created', sbx.sandbox_id) # Pause the sandbox -# You can save the resumable ID in your database +# You can save the sandbox ID in your database # to resume the sandbox later sandbox_id = sbx.pause() print('Sandbox paused', sandbox_id) From 9f9a36b039068535e3118ef0f6c86be806103960 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Dec 2024 21:34:33 +0000 Subject: [PATCH 22/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 9599e77af..298508d54 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.1", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.2", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 2882a9d38..ee9a1f04b 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b1" +version = "1.1.0b2" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 0d0ccabb602df2d81afafefc196bf3cd0395fc85 Mon Sep 17 00:00:00 2001 From: 0div Date: Sat, 14 Dec 2024 11:31:17 -0800 Subject: [PATCH 23/53] add basic snapshot testing to python-sdk --- .../tests/async/sandbox_async/test_snapshot.py | 17 +++++++++++++++++ packages/python-sdk/tests/conftest.py | 13 ++++++++----- .../tests/sync/sandbox_sync/test_snapshot.py | 17 +++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 packages/python-sdk/tests/async/sandbox_async/test_snapshot.py create mode 100644 packages/python-sdk/tests/sync/sandbox_sync/test_snapshot.py diff --git a/packages/python-sdk/tests/async/sandbox_async/test_snapshot.py b/packages/python-sdk/tests/async/sandbox_async/test_snapshot.py new file mode 100644 index 000000000..b5e2d3a2f --- /dev/null +++ b/packages/python-sdk/tests/async/sandbox_async/test_snapshot.py @@ -0,0 +1,17 @@ +import pytest +from e2b import AsyncSandbox + + +@pytest.mark.skip_debug() +async def test_snapshot(template): + sbx = await AsyncSandbox.create(template, timeout=5) + try: + assert await sbx.is_running() + + sandbox_id = await sbx.pause() + assert not await sbx.is_running() + + await sbx.resume(sandbox_id) + assert await sbx.is_running() + finally: + await sbx.kill() diff --git a/packages/python-sdk/tests/conftest.py b/packages/python-sdk/tests/conftest.py index 7de27aa39..2fa1d6a6a 100644 --- a/packages/python-sdk/tests/conftest.py +++ b/packages/python-sdk/tests/conftest.py @@ -1,15 +1,14 @@ -import pytest -import pytest_asyncio import os - from logging import warning -from e2b import Sandbox, AsyncSandbox +import pytest +import pytest_asyncio +from e2b import AsyncSandbox, Sandbox @pytest.fixture() def template(): - return "base" + return "base_01" @pytest.fixture() @@ -17,6 +16,8 @@ def sandbox(template, debug): sandbox = Sandbox(template) try: + sandbox_id = sandbox.pause() + sandbox.resume(sandbox_id) yield sandbox finally: try: @@ -33,6 +34,8 @@ async def async_sandbox(template, debug): sandbox = await AsyncSandbox.create(template) try: + sandbox_id = await sandbox.pause() + await sandbox.resume(sandbox_id) yield sandbox finally: try: diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot.py b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot.py new file mode 100644 index 000000000..a7fef2902 --- /dev/null +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot.py @@ -0,0 +1,17 @@ +import pytest +from e2b import Sandbox + + +@pytest.mark.skip_debug() +def test_snapshot(template): + sbx = Sandbox(template, timeout=5) + try: + assert sbx.is_running() + + sandbox_id = sbx.pause() + assert not sbx.is_running() + + sbx.resume(sandbox_id) + assert sbx.is_running() + finally: + sbx.kill() From c0591c946e0b5467480ff58d0f86d861f68cfb69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Dec 2024 19:32:36 +0000 Subject: [PATCH 24/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 298508d54..a6e980129 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.2", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.3", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index ee9a1f04b..6fc9ccff6 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b2" +version = "1.1.0b3" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 484a69df9494ce4696ff8c5bbeed08adca48631d Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Sat, 14 Dec 2024 18:53:37 -0800 Subject: [PATCH 25/53] Tweak default request timeout --- packages/js-sdk/src/connectionConfig.ts | 4 ++-- packages/python-sdk/e2b/connection_config.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/js-sdk/src/connectionConfig.ts b/packages/js-sdk/src/connectionConfig.ts index 2c7ae03fd..33b30876c 100644 --- a/packages/js-sdk/src/connectionConfig.ts +++ b/packages/js-sdk/src/connectionConfig.ts @@ -1,7 +1,7 @@ import { Logger } from './logs' import { getEnvVar } from './api/metadata' -const REQUEST_TIMEOUT_MS = 30_000 // 30 seconds +const REQUEST_TIMEOUT_MS = 60_000 // 60 seconds export const KEEPALIVE_PING_INTERVAL_SEC = 50 // 50 seconds export const KEEPALIVE_PING_HEADER = 'Keepalive-Ping-Interval' @@ -37,7 +37,7 @@ export interface ConnectionOpts { /** * Timeout for requests to the API in **milliseconds**. * - * @default 30_000 // 30 seconds + * @default 60_000 // 60 seconds */ requestTimeoutMs?: number /** diff --git a/packages/python-sdk/e2b/connection_config.py b/packages/python-sdk/e2b/connection_config.py index e1e610e72..093ec5c50 100644 --- a/packages/python-sdk/e2b/connection_config.py +++ b/packages/python-sdk/e2b/connection_config.py @@ -2,7 +2,7 @@ from typing import Literal, Optional -REQUEST_TIMEOUT: float = 30.0 # 30 seconds +REQUEST_TIMEOUT: float = 60.0 # 60 seconds KEEPALIVE_PING_INTERVAL_SEC = 50 # 50 seconds KEEPALIVE_PING_HEADER = "Keepalive-Ping-Interval" From ba882259badf0eb7363d5edff554a03d198d140c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Dec 2024 08:06:12 +0000 Subject: [PATCH 26/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index a6e980129..6566c17ef 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.3", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.4", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 6fc9ccff6..fcf8bec1b 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b3" +version = "1.1.0b4" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 16dcf6dccf3556b6fe3e2ab2e1502be28e5487d7 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Sun, 15 Dec 2024 02:39:27 -0800 Subject: [PATCH 27/53] Update test --- packages/js-sdk/tests/sandbox/snapshot.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts index a01de00b0..1f944870c 100644 --- a/packages/js-sdk/tests/sandbox/snapshot.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -1,5 +1,6 @@ import { assert } from 'vitest' +import { Sandbox } from '../../src' import { sandboxTest, isDebug } from '../setup.js' sandboxTest.skipIf(isDebug)('pause and resume a sandbox', async ({ sandbox }) => { @@ -9,7 +10,7 @@ sandboxTest.skipIf(isDebug)('pause and resume a sandbox', async ({ sandbox }) => assert.isFalse(await sandbox.isRunning()) - await sandbox.resume() + await Sandbox.resume(sandbox.sandboxId) assert.isTrue(await sandbox.isRunning()) }) From 5e545e2317969c37fe6ce4bd6b696139116decad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Dec 2024 10:40:44 +0000 Subject: [PATCH 28/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 6566c17ef..10e5b0565 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.4", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.5", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index fcf8bec1b..0bcc12922 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b4" +version = "1.1.0b5" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 7156e4043b46451bd8c45327a626d151131f1367 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Sun, 15 Dec 2024 20:25:26 -0800 Subject: [PATCH 29/53] Parametrize template in tests --- packages/js-sdk/tests/api/list.test.ts | 4 ++-- packages/js-sdk/tests/runtimes/bun/run.test.ts | 4 ++-- packages/js-sdk/tests/runtimes/deno/run.test.ts | 4 ++-- packages/js-sdk/tests/sandbox/commands/envVars.test.ts | 4 ++-- packages/js-sdk/tests/setup.ts | 9 ++------- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index 3b720ffdd..5d721c3f0 100644 --- a/packages/js-sdk/tests/api/list.test.ts +++ b/packages/js-sdk/tests/api/list.test.ts @@ -1,9 +1,9 @@ import { assert } from 'vitest' import { Sandbox } from '../../src' -import { isDebug, sandboxTest } from '../setup.js' +import { sandboxTest } from '../setup.js' -sandboxTest.skipIf(isDebug)('list sandboxes', async ({ sandbox }) => { +sandboxTest.skipIf(true)('list sandboxes', async ({ sandbox }) => { const sandboxes = await Sandbox.list() assert.isAtLeast(sandboxes.length, 1) assert.include( diff --git a/packages/js-sdk/tests/runtimes/bun/run.test.ts b/packages/js-sdk/tests/runtimes/bun/run.test.ts index 6fc440243..ad67966d1 100644 --- a/packages/js-sdk/tests/runtimes/bun/run.test.ts +++ b/packages/js-sdk/tests/runtimes/bun/run.test.ts @@ -4,8 +4,8 @@ import { Sandbox } from '../../../src' test( 'Bun test', - async () => { - const sbx = await Sandbox.create('base', { timeoutMs: 5_000 }) + async ({ template }) => { + const sbx = await Sandbox.create(template, { timeoutMs: 5_000 }) try { const isRunning = await sbx.isRunning() expect(isRunning).toBeTruthy() diff --git a/packages/js-sdk/tests/runtimes/deno/run.test.ts b/packages/js-sdk/tests/runtimes/deno/run.test.ts index c52feb265..6a88cde28 100644 --- a/packages/js-sdk/tests/runtimes/deno/run.test.ts +++ b/packages/js-sdk/tests/runtimes/deno/run.test.ts @@ -6,8 +6,8 @@ await load({ envPath: '.env', export: true }) import { Sandbox } from '../../../dist/index.mjs' -Deno.test('Deno test', async () => { - const sbx = await Sandbox.create('base', { timeoutMs: 5_000 }) +Deno.test('Deno test', async ({ template }) => { + const sbx = await Sandbox.create(template, { timeoutMs: 5_000 }) try { const isRunning = await sbx.isRunning() assert(isRunning) diff --git a/packages/js-sdk/tests/sandbox/commands/envVars.test.ts b/packages/js-sdk/tests/sandbox/commands/envVars.test.ts index 148b0a4f5..cf337382b 100644 --- a/packages/js-sdk/tests/sandbox/commands/envVars.test.ts +++ b/packages/js-sdk/tests/sandbox/commands/envVars.test.ts @@ -10,8 +10,8 @@ sandboxTest.skipIf(isDebug)('env vars', async ({ sandbox }) => { assert.equal(cmd.stdout.trim(), 'bar') }) -sandboxTest.skipIf(isDebug)('env vars on sandbox', async () => { - const sandbox = await Sandbox.create({ envs: { FOO: 'bar' } }) +sandboxTest.skipIf(isDebug)('env vars on sandbox', async ({ template }) => { + const sandbox = await Sandbox.create(template, { envs: { FOO: 'bar' } }) try { const cmd = await sandbox.commands.run('echo "$FOO"') diff --git a/packages/js-sdk/tests/setup.ts b/packages/js-sdk/tests/setup.ts index a40500f59..d3ff8f067 100644 --- a/packages/js-sdk/tests/setup.ts +++ b/packages/js-sdk/tests/setup.ts @@ -9,16 +9,11 @@ interface SandboxFixture { } export const sandboxTest = base.extend({ - template, + template, sandbox: [ - async ({}, use) => { + async ({ }, use) => { const sandbox = await Sandbox.create(template) try { - console.log('pause sandbox') - const sandboxId = await sandbox.pause() - console.log('resume sandbox', sandboxId) - await sandbox.resume() - console.log('sandbox resumed') await use(sandbox) } finally { try { From 39aa3a372acbbc6f77010adc57abe41f28fab3ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 04:34:00 +0000 Subject: [PATCH 30/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 10e5b0565..3b6d47b6d 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.5", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.6", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 0bcc12922..662ab05c3 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b5" +version = "1.1.0b6" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 36c1054cc47676a6a7f22fea24f6cf1065cc0503 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Sun, 15 Dec 2024 23:00:44 -0800 Subject: [PATCH 31/53] Enable passing connect opts to resume class method --- packages/js-sdk/src/sandbox/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 176ed1c02..6a80efa61 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -221,7 +221,7 @@ export class Sandbox extends SandboxApi { * Resume the sandbox. * * The **default sandbox timeout of 300 seconds** ({@link Sandbox.defaultSandboxTimeoutMs}) will be used for the resumed sandbox. - * If you pass a custom timeout in the `opts` parametervia {@link SandboxOpts.timeoutMs} property, it will be used instead. + * If you pass a custom timeout in the `opts` parameter via {@link SandboxOpts.timeoutMs} property, it will be used instead. * * @param sandboxId sandbox ID. * @param opts connection options. @@ -231,7 +231,7 @@ export class Sandbox extends SandboxApi { static async resume( this: S, sandboxId: string, - opts?: Pick + opts?: Omit ): Promise> { await Sandbox.resumeSandbox(sandboxId, opts?.timeoutMs ?? this.defaultSandboxTimeoutMs, opts) From 636d3a33f2d73ed99555420e313690d43eb51c95 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 07:01:57 +0000 Subject: [PATCH 32/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 3b6d47b6d..9ad02032d 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.6", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.7", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 662ab05c3..d60ad82aa 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b6" +version = "1.1.0b7" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 6d67e9286d28dfc47933a13991b00456b777353e Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Sun, 15 Dec 2024 23:05:31 -0800 Subject: [PATCH 33/53] Allow passing access token to api by parameter --- packages/js-sdk/src/sandbox/sandboxApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 0304def5e..2d5cb9dcb 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -8,7 +8,7 @@ import { NotFoundError, TemplateError } from '../errors' */ export interface SandboxApiOpts extends Partial< - Pick + Pick > { } /** From b4520b8477c9214c5beda06f2aeb44925913a332 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 07:06:38 +0000 Subject: [PATCH 34/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 9ad02032d..09085ea23 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.7", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.8", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index d60ad82aa..333b98717 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b7" +version = "1.1.0b8" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From fc3c18fa216e4a81af64d0bb06f3b3bff102e742 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Sun, 15 Dec 2024 23:14:05 -0800 Subject: [PATCH 35/53] Revert "Allow passing access token to api by parameter" This reverts commit 6d67e9286d28dfc47933a13991b00456b777353e. --- packages/js-sdk/src/sandbox/sandboxApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 2d5cb9dcb..0304def5e 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -8,7 +8,7 @@ import { NotFoundError, TemplateError } from '../errors' */ export interface SandboxApiOpts extends Partial< - Pick + Pick > { } /** From 12898d812758ece9b47e885b5616bca3be81dca6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 07:15:19 +0000 Subject: [PATCH 36/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 09085ea23..422cc91f7 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.8", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.9", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 333b98717..b619263c2 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b8" +version = "1.1.0b9" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From d0acaf51134799c2e9589784cf238fa093d5d8a2 Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Sun, 15 Dec 2024 23:25:43 -0800 Subject: [PATCH 37/53] Pass api key and domain in sync python resume --- packages/python-sdk/e2b/sandbox_sync/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 03ef11ebd..7004231c0 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -388,6 +388,9 @@ def resume( sandbox_id=sandbox_id, request_timeout=request_timeout, timeout=timeout, + api_key=api_key, + domain=domain, + debug=debug, ) return cls.connect( From d451e032984714b29c06f33e11c9ac1b567a1504 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 07:26:49 +0000 Subject: [PATCH 38/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 422cc91f7..fed92433b 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.9", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.10", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index b619263c2..bc9939793 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b9" +version = "1.1.0b10" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 4437a779eb9a3c757d9e97878b3b1d3f08334be1 Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 16 Dec 2024 11:02:50 -0800 Subject: [PATCH 39/53] add deeper snapshot tests around memory, disk and long running processes --- .../js-sdk/tests/sandbox/snapshot.test.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts index 1f944870c..6dfc57be7 100644 --- a/packages/js-sdk/tests/sandbox/snapshot.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -14,3 +14,85 @@ sandboxTest.skipIf(isDebug)('pause and resume a sandbox', async ({ sandbox }) => assert.isTrue(await sandbox.isRunning()) }) + +sandboxTest.skipIf(isDebug)('pause and resume a sandbox with env vars', async ({ template }) => { + // Environment variables of a process exist at runtime, and are not stored in some file or so. + // They are stored in the process's own memory + const sandbox = await Sandbox.create(template, { envs: { TEST_VAR: 'sfisback' } }) + + try { + const cmd = await sandbox.commands.run('echo "$TEST_VAR"') + + assert.equal(cmd.exitCode, 0) + assert.equal(cmd.stdout.trim(), 'sfisback') + } catch { + sandbox.kill() + } + + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) + + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) + + try { + const cmd = await sandbox.commands.run('echo "$TEST_VAR"') + + assert.equal(cmd.exitCode, 0) + assert.equal(cmd.stdout.trim(), 'sfisback') + } finally { + await sandbox.kill() + } +}) + +sandboxTest.skipIf(isDebug)('pause and resume a sandbox with file', async ({ sandbox }) => { + const filename = 'test_snapshot.txt' + const content = 'This is a snapshot test file.' + + const info = await sandbox.files.write(filename, content) + assert.equal(info.name, filename) + assert.equal(info.type, 'file') + assert.equal(info.path, `/home/user/${filename}`) + + const exists = await sandbox.files.exists(filename) + assert.isTrue(exists) + const readContent = await sandbox.files.read(filename) + assert.equal(readContent, content) + + + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) + + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) + + const exists2 = await sandbox.files.exists(filename) + assert.isTrue(exists2) + const readContent2 = await sandbox.files.read(filename) + assert.equal(readContent2, content) +}) + +sandboxTest.skipIf(isDebug)('pause and resume a sandbox long running process', async ({ sandbox }) => { + const filename = 'test_long_running.txt' + + sandbox.commands.run(`sleep 2 && echo "done" > /home/user/${filename}`) + + // the file should not exist before 2 seconds have elapsed + const exists = await sandbox.files.exists(filename) + assert.isFalse(exists) + + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) + + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) + + // the file should be created after more than 2 seconds have elapsed + await new Promise(resolve => setTimeout(resolve, 2000)) + + const exists2 = await sandbox.files.exists(filename) + assert.isTrue(exists2) + const readContent2 = await sandbox.files.read(filename) + assert.equal(readContent2.trim(), 'done') +}) + \ No newline at end of file From 7714e01bf55b06f8cc14f07fdaa42583810ffc9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 19:04:02 +0000 Subject: [PATCH 40/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index fed92433b..96f11beda 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.10", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.11", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index bc9939793..eee832719 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b10" +version = "1.1.0b11" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From fd94afc5f07a2707d7ecc1cf51152ec1c4913160 Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 16 Dec 2024 12:08:26 -0800 Subject: [PATCH 41/53] remove base_01 template name and add http server test --- .../js-sdk/tests/sandbox/snapshot.test.ts | 24 ++++++++++++++++--- packages/js-sdk/tests/setup.ts | 2 +- packages/python-sdk/tests/conftest.py | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts index 6dfc57be7..8c11c288b 100644 --- a/packages/js-sdk/tests/sandbox/snapshot.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -72,10 +72,10 @@ sandboxTest.skipIf(isDebug)('pause and resume a sandbox with file', async ({ san assert.equal(readContent2, content) }) -sandboxTest.skipIf(isDebug)('pause and resume a sandbox long running process', async ({ sandbox }) => { +sandboxTest.skipIf(isDebug)('pause and resume a sandbox with long running process', async ({ sandbox }) => { const filename = 'test_long_running.txt' - sandbox.commands.run(`sleep 2 && echo "done" > /home/user/${filename}`) + sandbox.commands.run(`sleep 2 && echo "done" > /home/user/${filename}`, { background: true }) // the file should not exist before 2 seconds have elapsed const exists = await sandbox.files.exists(filename) @@ -95,4 +95,22 @@ sandboxTest.skipIf(isDebug)('pause and resume a sandbox long running process', a const readContent2 = await sandbox.files.read(filename) assert.equal(readContent2.trim(), 'done') }) - \ No newline at end of file + +sandboxTest.skipIf(isDebug)('pause and resume a sandbox with http server', async ({ sandbox }) => { + await sandbox.commands.run('python3 -m http.server 8000', { background: true }) + + let url = await sandbox.getHost(8000) + console.log(url) + const response1 = await fetch(`https://${url}`) + assert.equal(response1.status, 200) + + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) + + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) + + url = await sandbox.getHost(8000) + const response2 = await fetch(`https://${url}`) + assert.equal(response2.status, 200) +}) diff --git a/packages/js-sdk/tests/setup.ts b/packages/js-sdk/tests/setup.ts index d3ff8f067..b3f540706 100644 --- a/packages/js-sdk/tests/setup.ts +++ b/packages/js-sdk/tests/setup.ts @@ -1,7 +1,7 @@ import { Sandbox } from '../src' import { test as base } from 'vitest' -export const template = 'base_01' +export const template = 'base' interface SandboxFixture { sandbox: Sandbox diff --git a/packages/python-sdk/tests/conftest.py b/packages/python-sdk/tests/conftest.py index 2fa1d6a6a..d06e1dfa6 100644 --- a/packages/python-sdk/tests/conftest.py +++ b/packages/python-sdk/tests/conftest.py @@ -8,7 +8,7 @@ @pytest.fixture() def template(): - return "base_01" + return "base" @pytest.fixture() From b7c8d716f701261c96bf2bab00671ae35fb74139 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 20:09:42 +0000 Subject: [PATCH 42/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 96f11beda..ff8faaf5f 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.11", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.12", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index eee832719..949baab1b 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b11" +version = "1.1.0b12" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 1d9bee909b69639a6fb34e20bf179d5bb0cc7f57 Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 16 Dec 2024 12:16:41 -0800 Subject: [PATCH 43/53] fix snapshot http server test --- packages/js-sdk/tests/sandbox/snapshot.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts index 8c11c288b..4c95d2600 100644 --- a/packages/js-sdk/tests/sandbox/snapshot.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -1,3 +1,4 @@ + import { assert } from 'vitest' import { Sandbox } from '../../src' @@ -100,7 +101,9 @@ sandboxTest.skipIf(isDebug)('pause and resume a sandbox with http server', async await sandbox.commands.run('python3 -m http.server 8000', { background: true }) let url = await sandbox.getHost(8000) - console.log(url) + + await new Promise(resolve => setTimeout(resolve, 3000)) + const response1 = await fetch(`https://${url}`) assert.equal(response1.status, 200) From db8d8463b40ba6b671faca71d25629eb948a2efa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 20:53:27 +0000 Subject: [PATCH 44/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index ff8faaf5f..42b943d88 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.12", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.13", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 949baab1b..4cab09b6a 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b12" +version = "1.1.0b13" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 1358b1193df0eea9cf03fae87f7afe71f7a843df Mon Sep 17 00:00:00 2001 From: 0div Date: Mon, 16 Dec 2024 13:14:51 -0800 Subject: [PATCH 45/53] eslint --- .../js-sdk/tests/sandbox/snapshot.test.ts | 209 ++++++++++-------- 1 file changed, 114 insertions(+), 95 deletions(-) diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts index 4c95d2600..d241242d3 100644 --- a/packages/js-sdk/tests/sandbox/snapshot.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -1,119 +1,138 @@ - import { assert } from 'vitest' import { Sandbox } from '../../src' import { sandboxTest, isDebug } from '../setup.js' -sandboxTest.skipIf(isDebug)('pause and resume a sandbox', async ({ sandbox }) => { - assert.isTrue(await sandbox.isRunning()) - - await sandbox.pause() - - assert.isFalse(await sandbox.isRunning()) - - await Sandbox.resume(sandbox.sandboxId) - - assert.isTrue(await sandbox.isRunning()) -}) +sandboxTest.skipIf(isDebug)( + 'pause and resume a sandbox', + async ({ sandbox }) => { + assert.isTrue(await sandbox.isRunning()) -sandboxTest.skipIf(isDebug)('pause and resume a sandbox with env vars', async ({ template }) => { - // Environment variables of a process exist at runtime, and are not stored in some file or so. - // They are stored in the process's own memory - const sandbox = await Sandbox.create(template, { envs: { TEST_VAR: 'sfisback' } }) + await sandbox.pause() - try { - const cmd = await sandbox.commands.run('echo "$TEST_VAR"') + assert.isFalse(await sandbox.isRunning()) - assert.equal(cmd.exitCode, 0) - assert.equal(cmd.stdout.trim(), 'sfisback') - } catch { - sandbox.kill() - } + await Sandbox.resume(sandbox.sandboxId) - await sandbox.pause() - assert.isFalse(await sandbox.isRunning()) - - await Sandbox.resume(sandbox.sandboxId) - assert.isTrue(await sandbox.isRunning()) - - try { - const cmd = await sandbox.commands.run('echo "$TEST_VAR"') - - assert.equal(cmd.exitCode, 0) - assert.equal(cmd.stdout.trim(), 'sfisback') - } finally { - await sandbox.kill() + assert.isTrue(await sandbox.isRunning()) } -}) - -sandboxTest.skipIf(isDebug)('pause and resume a sandbox with file', async ({ sandbox }) => { - const filename = 'test_snapshot.txt' - const content = 'This is a snapshot test file.' - - const info = await sandbox.files.write(filename, content) - assert.equal(info.name, filename) - assert.equal(info.type, 'file') - assert.equal(info.path, `/home/user/${filename}`) - - const exists = await sandbox.files.exists(filename) - assert.isTrue(exists) - const readContent = await sandbox.files.read(filename) - assert.equal(readContent, content) - - - await sandbox.pause() - assert.isFalse(await sandbox.isRunning()) +) + +sandboxTest.skipIf(isDebug)( + 'pause and resume a sandbox with env vars', + async ({ template }) => { + // Environment variables of a process exist at runtime, and are not stored in some file or so. + // They are stored in the process's own memory + const sandbox = await Sandbox.create(template, { + envs: { TEST_VAR: 'sfisback' }, + }) + + try { + const cmd = await sandbox.commands.run('echo "$TEST_VAR"') + + assert.equal(cmd.exitCode, 0) + assert.equal(cmd.stdout.trim(), 'sfisback') + } catch { + sandbox.kill() + } + + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) + + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) + + try { + const cmd = await sandbox.commands.run('echo "$TEST_VAR"') + + assert.equal(cmd.exitCode, 0) + assert.equal(cmd.stdout.trim(), 'sfisback') + } finally { + await sandbox.kill() + } + } +) + +sandboxTest.skipIf(isDebug)( + 'pause and resume a sandbox with file', + async ({ sandbox }) => { + const filename = 'test_snapshot.txt' + const content = 'This is a snapshot test file.' + + const info = await sandbox.files.write(filename, content) + assert.equal(info.name, filename) + assert.equal(info.type, 'file') + assert.equal(info.path, `/home/user/${filename}`) + + const exists = await sandbox.files.exists(filename) + assert.isTrue(exists) + const readContent = await sandbox.files.read(filename) + assert.equal(readContent, content) + + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) + + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) + + const exists2 = await sandbox.files.exists(filename) + assert.isTrue(exists2) + const readContent2 = await sandbox.files.read(filename) + assert.equal(readContent2, content) + } +) - await Sandbox.resume(sandbox.sandboxId) - assert.isTrue(await sandbox.isRunning()) +sandboxTest.skipIf(isDebug)( + 'pause and resume a sandbox with long running process', + async ({ sandbox }) => { + const filename = 'test_long_running.txt' - const exists2 = await sandbox.files.exists(filename) - assert.isTrue(exists2) - const readContent2 = await sandbox.files.read(filename) - assert.equal(readContent2, content) -}) + sandbox.commands.run(`sleep 2 && echo "done" > /home/user/${filename}`, { + background: true, + }) -sandboxTest.skipIf(isDebug)('pause and resume a sandbox with long running process', async ({ sandbox }) => { - const filename = 'test_long_running.txt' + // the file should not exist before 2 seconds have elapsed + const exists = await sandbox.files.exists(filename) + assert.isFalse(exists) - sandbox.commands.run(`sleep 2 && echo "done" > /home/user/${filename}`, { background: true }) + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) - // the file should not exist before 2 seconds have elapsed - const exists = await sandbox.files.exists(filename) - assert.isFalse(exists) + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) - await sandbox.pause() - assert.isFalse(await sandbox.isRunning()) + // the file should be created after more than 2 seconds have elapsed + await new Promise((resolve) => setTimeout(resolve, 2000)) - await Sandbox.resume(sandbox.sandboxId) - assert.isTrue(await sandbox.isRunning()) + const exists2 = await sandbox.files.exists(filename) + assert.isTrue(exists2) + const readContent2 = await sandbox.files.read(filename) + assert.equal(readContent2.trim(), 'done') + } +) - // the file should be created after more than 2 seconds have elapsed - await new Promise(resolve => setTimeout(resolve, 2000)) +sandboxTest.skipIf(isDebug)( + 'pause and resume a sandbox with http server', + async ({ sandbox }) => { + await sandbox.commands.run('python3 -m http.server 8000', { + background: true, + }) - const exists2 = await sandbox.files.exists(filename) - assert.isTrue(exists2) - const readContent2 = await sandbox.files.read(filename) - assert.equal(readContent2.trim(), 'done') -}) - -sandboxTest.skipIf(isDebug)('pause and resume a sandbox with http server', async ({ sandbox }) => { - await sandbox.commands.run('python3 -m http.server 8000', { background: true }) + let url = await sandbox.getHost(8000) - let url = await sandbox.getHost(8000) + await new Promise((resolve) => setTimeout(resolve, 3000)) - await new Promise(resolve => setTimeout(resolve, 3000)) - - const response1 = await fetch(`https://${url}`) - assert.equal(response1.status, 200) + const response1 = await fetch(`https://${url}`) + assert.equal(response1.status, 200) - await sandbox.pause() - assert.isFalse(await sandbox.isRunning()) + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) - await Sandbox.resume(sandbox.sandboxId) - assert.isTrue(await sandbox.isRunning()) + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) - url = await sandbox.getHost(8000) - const response2 = await fetch(`https://${url}`) - assert.equal(response2.status, 200) -}) + url = await sandbox.getHost(8000) + const response2 = await fetch(`https://${url}`) + assert.equal(response2.status, 200) + } +) From 20ce5ac07d2631eee6d9c9392f7189a9ebeb0966 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 21:16:01 +0000 Subject: [PATCH 46/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 42b943d88..b6c091141 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.13", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.14", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 4cab09b6a..b52328624 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b13" +version = "1.1.0b14" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 78dc519b4a7c118a6fab1ce57a8fae474a8025da Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Mon, 16 Dec 2024 13:37:34 -0800 Subject: [PATCH 47/53] Add notes to docs about sandbox's timeout and network behavior --- .../(docs)/docs/sandbox/persistence/page.mdx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index 8e6c45152..e0ea7a09b 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -100,6 +100,29 @@ print('Sandbox resumed', same_sbx.sandbox_id) # $HighlightLine ``` +## Sandbox's timeout +When you resume a sandbox, the sandbox's timeout is reset to the default timeout of an E2B sandbox - 5 minutes. + + +You can pass a custom timeout to the `Sandbox.resume()` method like this: + + +```js +import { Sandbox } from 'e2b' + +const sbx = await Sandbox.resume(sandboxId, { timeoutMs: 60 * 1000 }) // 60 seconds +``` +```python +from e2b import Sandbox + +sbx = Sandbox.resume(sandbox_id, timeout=60) # 60 seconds +``` + + +## Network +If you have a service (for example a server) running inside your sandbox and you pause the sandbox, the service won't be accessible from the outside and all the clients will be disconnected. +If you resume the sandbox, the service will be accessible again but you need to connect clients again. + ## Limitations while in beta - It takes about 4 seconds per 1 GB RAM to pause the sandbox From 2d56f00592f9796978c5b1fd367669b509cf75d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Dec 2024 21:38:53 +0000 Subject: [PATCH 48/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index b6c091141..7e51bb8c4 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.14", + "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.15", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index b52328624..c89a2e084 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b14" +version = "1.1.0b15" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From a5d97cd5d52866f95b61ffccf378e016755aa4be Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Mon, 16 Dec 2024 16:47:40 -0800 Subject: [PATCH 49/53] Change the preid to beta --- .github/workflows/release_candidates.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_candidates.yml b/.github/workflows/release_candidates.yml index 61589aa5a..0e6bb020c 100644 --- a/.github/workflows/release_candidates.yml +++ b/.github/workflows/release_candidates.yml @@ -89,7 +89,7 @@ jobs: working-directory: packages/js-sdk if: ${{ contains( github.event.pull_request.labels.*.name, 'js-rc') }} run: | - npm version prerelease --preid=${{ github.head_ref }} + npm version prerelease --preid="beta" npm publish --tag rc env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -103,7 +103,7 @@ jobs: working-directory: packages/cli if: ${{ contains( github.event.pull_request.labels.*.name, 'cli-rc') }} run: | - npm version prerelease --preid=${{ github.head_ref }} + npm version prerelease --preid="beta" npm publish --tag rc env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 9f21cced7cc7872c34afea1e3fc7d14441826fed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Dec 2024 00:48:53 +0000 Subject: [PATCH 50/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 7e51bb8c4..a65fa770c 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-add-pause-and-resume-to-sdk-e2b-1190.15", + "version": "1.1.0-beta.0", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index c89a2e084..b25982612 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b15" +version = "1.1.0b16" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From dc90abbdb2fcbbee91d465caed5f7d0bb345f67e Mon Sep 17 00:00:00 2001 From: Tomas Valenta Date: Mon, 16 Dec 2024 16:58:01 -0800 Subject: [PATCH 51/53] Change tag to beta --- .github/workflows/release_candidates.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_candidates.yml b/.github/workflows/release_candidates.yml index 0e6bb020c..d31a792b2 100644 --- a/.github/workflows/release_candidates.yml +++ b/.github/workflows/release_candidates.yml @@ -90,7 +90,7 @@ jobs: if: ${{ contains( github.event.pull_request.labels.*.name, 'js-rc') }} run: | npm version prerelease --preid="beta" - npm publish --tag rc + npm publish --tag beta env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -104,7 +104,7 @@ jobs: if: ${{ contains( github.event.pull_request.labels.*.name, 'cli-rc') }} run: | npm version prerelease --preid="beta" - npm publish --tag rc + npm publish --tag beta env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 4bf8c631be8bd289d87f80fcd3988e2fbfaa7e61 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Dec 2024 00:59:17 +0000 Subject: [PATCH 52/53] [skip ci] Release new versions --- packages/js-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index a65fa770c..62f1c9617 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "e2b", - "version": "1.1.0-beta.0", + "version": "1.1.0-beta.1", "description": "E2B SDK that give agents cloud environments", "homepage": "https://e2b.dev", "license": "MIT", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index b25982612..2d555ea03 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "e2b" -version = "1.1.0b16" +version = "1.1.0b17" description = "E2B SDK that give agents cloud environments" authors = ["e2b "] license = "MIT" From 67dc422e547556074c07044eabaabb4d9d88d0fe Mon Sep 17 00:00:00 2001 From: 0div Date: Tue, 17 Dec 2024 11:16:50 -0800 Subject: [PATCH 53/53] add test for ongoing long process listing and connection; added some cleanup routines as well --- .../js-sdk/tests/sandbox/snapshot.test.ts | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts index d241242d3..03ecdfe5a 100644 --- a/packages/js-sdk/tests/sandbox/snapshot.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -1,4 +1,4 @@ -import { assert } from 'vitest' +import { assert, onTestFinished } from 'vitest' import { Sandbox } from '../../src' import { sandboxTest, isDebug } from '../setup.js' @@ -83,13 +83,44 @@ sandboxTest.skipIf(isDebug)( ) sandboxTest.skipIf(isDebug)( - 'pause and resume a sandbox with long running process', + 'pause and resume a sandbox with ongoing long running process', async ({ sandbox }) => { - const filename = 'test_long_running.txt' + const cmd = await sandbox.commands.run('sleep 3600', { background: true }) + const expectedPid = cmd.pid - sandbox.commands.run(`sleep 2 && echo "done" > /home/user/${filename}`, { - background: true, + await sandbox.pause() + assert.isFalse(await sandbox.isRunning()) + + await Sandbox.resume(sandbox.sandboxId) + assert.isTrue(await sandbox.isRunning()) + + // First check that the command is in list + const list = await sandbox.commands.list() + assert.isTrue(list.some((c) => c.pid === expectedPid)) + + // Make sure we can connect to it + const processInfo = await sandbox.commands.connect(expectedPid) + + assert.isObject(processInfo) + assert.equal(processInfo.pid, expectedPid) + + onTestFinished(() => { + sandbox.commands.kill(expectedPid) }) + } +) + +sandboxTest.skipIf(isDebug)( + 'pause and resume a sandbox with completed long running process', + async ({ sandbox }) => { + const filename = 'test_long_running.txt' + + const cmd = await sandbox.commands.run( + `sleep 2 && echo "done" > /home/user/${filename}`, + { + background: true, + } + ) // the file should not exist before 2 seconds have elapsed const exists = await sandbox.files.exists(filename) @@ -108,19 +139,23 @@ sandboxTest.skipIf(isDebug)( assert.isTrue(exists2) const readContent2 = await sandbox.files.read(filename) assert.equal(readContent2.trim(), 'done') + + onTestFinished(() => { + sandbox.commands.kill(cmd.pid) + }) } ) sandboxTest.skipIf(isDebug)( 'pause and resume a sandbox with http server', async ({ sandbox }) => { - await sandbox.commands.run('python3 -m http.server 8000', { + const cmd = await sandbox.commands.run('python3 -m http.server 8000', { background: true, }) let url = await sandbox.getHost(8000) - await new Promise((resolve) => setTimeout(resolve, 3000)) + await new Promise((resolve) => setTimeout(resolve, 5000)) const response1 = await fetch(`https://${url}`) assert.equal(response1.status, 200) @@ -134,5 +169,9 @@ sandboxTest.skipIf(isDebug)( url = await sandbox.getHost(8000) const response2 = await fetch(`https://${url}`) assert.equal(response2.status, 200) + + onTestFinished(() => { + sandbox.commands.kill(cmd.pid) + }) } )