-
Notifications
You must be signed in to change notification settings - Fork 876
autoresume: lifecycle component in sdk #1146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1053eb5
6940ddb
3cc4d0b
6872824
e2d651a
16f5233
a46094d
65089d9
986c81d
a9c906e
3f6977b
d7de490
f1e0ba8
039f079
67e8298
fd2764a
066a464
8058d8f
57a5086
97bfc71
320be8e
ff36ac3
d7389df
9b4c636
cf1e611
1195b99
f1c0b2a
fa2bb09
b19c775
df58ad4
daaa8e6
a3fd14f
3267a46
1a8306f
d057951
bd2c505
8e43a25
0d22776
d7cd50c
4885cab
ba065e3
cff6e50
c279d38
504de2c
616ffb9
f15223a
fcfaf65
f20f06c
515f9b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@e2b/python-sdk': minor | ||
| 'e2b': minor | ||
| --- | ||
|
|
||
| Adds lifecycle prop to control pausing and auto-resume |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,21 @@ export type SandboxNetworkOpts = { | |
| maskRequestHost?: string | ||
| } | ||
|
|
||
| export type SandboxLifecycle = { | ||
| /** | ||
| * Action to take when sandbox timeout is reached. | ||
| * @default "kill" | ||
| */ | ||
| onTimeout: 'pause' | 'kill' | ||
|
|
||
| /** | ||
| * Auto-resume enabled flag. | ||
| * @default false | ||
| * Can be `true` only when `onTimeout` is `pause`. | ||
| */ | ||
| autoResume?: boolean | ||
| } | ||
|
|
||
| /** | ||
| * Options for request to the Sandbox API. | ||
| */ | ||
|
|
@@ -133,10 +148,17 @@ export interface SandboxOpts extends ConnectionOpts { | |
| * Sandbox URL. Used for local development | ||
| */ | ||
| sandboxUrl?: string | ||
|
|
||
| /** | ||
| * Sandbox lifecycle configuration. | ||
| */ | ||
| lifecycle?: SandboxLifecycle | ||
| } | ||
|
|
||
| export type SandboxBetaCreateOpts = SandboxOpts & { | ||
| /** | ||
| * @deprecated Use `lifecycle.onTimeout = "pause"` instead. | ||
| * | ||
| * Automatically pause the sandbox after the timeout expires. | ||
| * @default false | ||
| */ | ||
|
|
@@ -330,6 +352,26 @@ export interface SandboxMetrics { | |
| diskTotal: number | ||
| } | ||
|
|
||
| function getLifecycle( | ||
| opts?: Pick<SandboxBetaCreateOpts, 'lifecycle' | 'autoPause'> | ||
| ): SandboxLifecycle { | ||
| if (opts?.lifecycle) { | ||
| return opts.lifecycle | ||
| } | ||
|
|
||
| if (opts?.autoPause) { | ||
| return { | ||
| onTimeout: 'pause', | ||
| autoResume: false, | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| onTimeout: 'kill', | ||
| autoResume: false, | ||
| } | ||
| } | ||
|
|
||
| export class SandboxApi { | ||
| protected constructor() {} | ||
|
|
||
|
|
@@ -529,7 +571,7 @@ export class SandboxApi { | |
| * | ||
| * @returns `true` if the sandbox got paused, `false` if the sandbox was already paused. | ||
| */ | ||
| static async betaPause( | ||
| static async pause( | ||
| sandboxId: string, | ||
| opts?: SandboxApiOpts | ||
| ): Promise<boolean> { | ||
|
|
@@ -562,6 +604,16 @@ export class SandboxApi { | |
| return true | ||
| } | ||
|
|
||
| /** | ||
| * @deprecated Use {@link SandboxApi.pause} instead. | ||
| */ | ||
| static async betaPause( | ||
| sandboxId: string, | ||
| opts?: SandboxApiOpts | ||
| ): Promise<boolean> { | ||
| return this.pause(sandboxId, opts) | ||
| } | ||
|
|
||
| /** | ||
| * Create a snapshot from a sandbox. | ||
| * | ||
|
|
@@ -659,19 +711,30 @@ export class SandboxApi { | |
| ) { | ||
| const config = new ConnectionConfig(opts) | ||
| const client = new ApiClient(config) | ||
| const lifecycle = getLifecycle(opts) | ||
| const autoPause = lifecycle.onTimeout === 'pause' | ||
| const autoResumeEnabled = | ||
| lifecycle.onTimeout === 'pause' | ||
| ? (lifecycle.autoResume ?? false) | ||
| : undefined | ||
|
|
||
| const body: components['schemas']['NewSandbox'] = { | ||
| templateID: template, | ||
| metadata: opts?.metadata, | ||
| mcp: opts?.mcp as Record<string, unknown> | undefined, | ||
| envVars: opts?.envs, | ||
| timeout: timeoutToSeconds(timeoutMs), | ||
| secure: opts?.secure ?? true, | ||
| allow_internet_access: opts?.allowInternetAccess ?? true, | ||
| network: opts?.network, | ||
| ...(autoPause !== undefined ? { autoPause } : {}), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conditional
|
||
| ...(autoResumeEnabled !== undefined | ||
| ? { autoResume: { enabled: autoResumeEnabled } } | ||
| : {}), | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| const res = await client.api.POST('/sandboxes', { | ||
| body: { | ||
| autoPause: opts?.autoPause ?? false, | ||
| templateID: template, | ||
| metadata: opts?.metadata, | ||
| mcp: opts?.mcp as Record<string, unknown> | undefined, | ||
| envVars: opts?.envs, | ||
| timeout: timeoutToSeconds(timeoutMs), | ||
| secure: opts?.secure ?? true, | ||
| allow_internet_access: opts?.allowInternetAccess ?? true, | ||
| network: opts?.network, | ||
| }, | ||
| body, | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| signal: config.getSignal(opts?.requestTimeoutMs), | ||
| }) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { assert, test } from 'vitest' | ||
|
|
||
| import { Sandbox } from '../../src' | ||
| import { isDebug, template, wait } from '../setup.js' | ||
|
|
||
| test.skipIf(isDebug)( | ||
| 'auto-pause without auto-resume requires connect to wake', | ||
| async () => { | ||
| const sandbox = await Sandbox.create(template, { | ||
| timeoutMs: 3_000, | ||
| lifecycle: { | ||
| onTimeout: 'pause', | ||
| autoResume: false, | ||
| }, | ||
| }) | ||
|
|
||
| try { | ||
| await wait(5_000) | ||
|
|
||
| assert.equal((await sandbox.getInfo()).state, 'paused') | ||
| assert.isFalse(await sandbox.isRunning()) | ||
|
|
||
| await sandbox.connect() | ||
|
|
||
| assert.equal((await sandbox.getInfo()).state, 'running') | ||
| assert.isTrue(await sandbox.isRunning()) | ||
| } finally { | ||
| await sandbox.kill().catch(() => {}) | ||
| } | ||
| }, | ||
| 60_000 | ||
| ) | ||
|
|
||
| test.skipIf(isDebug)( | ||
| 'auto-resume wakes paused sandbox on http request', | ||
| async () => { | ||
| const sandbox = await Sandbox.create(template, { | ||
| timeoutMs: 3_000, | ||
| lifecycle: { | ||
| onTimeout: 'pause', | ||
| autoResume: true, | ||
| }, | ||
| }) | ||
|
|
||
| try { | ||
| await sandbox.commands.run('python3 -m http.server 8000', { | ||
| background: true, | ||
| }) | ||
|
|
||
| await wait(5_000) | ||
|
|
||
| const url = `https://${sandbox.getHost(8000)}` | ||
| const res = await fetch(url, { signal: AbortSignal.timeout(15_000) }) | ||
|
|
||
| assert.equal(res.status, 200) | ||
| assert.equal((await sandbox.getInfo()).state, 'running') | ||
| assert.isTrue(await sandbox.isRunning()) | ||
| } finally { | ||
| await sandbox.kill().catch(() => {}) | ||
| } | ||
| }, | ||
| 60_000 | ||
| ) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.


Uh oh!
There was an error while loading. Please reload this page.