Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2fe412b
Regenerate spec with custom client
jakubno Aug 8, 2025
61c1a58
Beta submodule in Python SDK
jakubno Aug 10, 2025
52eba58
Beta submodule in JS SDK
jakubno Aug 10, 2025
7aa8f05
Update CLI
jakubno Aug 10, 2025
7d84107
Add changeset
jakubno Aug 10, 2025
80b51a1
Use the cache
jakubno Aug 10, 2025
c4dc29f
Add docs strings
jakubno Aug 10, 2025
cf952b5
Refactor beta module in JS SDK
jakubno Aug 11, 2025
e27f7ae
Rename sandboxType to sandboxTestId
jakubno Aug 11, 2025
c82f134
Improve tests
jakubno Aug 11, 2025
bf42cca
Export types
jakubno Aug 11, 2025
1ef6b5f
Improve docstring
jakubno Aug 11, 2025
af77619
Update docs
jakubno Aug 11, 2025
70f58ff
Fix test
jakubno Aug 11, 2025
b30934c
Fix docs
jakubno Aug 12, 2025
b860480
Add missing `.beta`
jakubno Aug 12, 2025
3a0ff1a
Change Sync Python Sandbox() to Sandbox.create()
jakubno Aug 14, 2025
3fb1918
Add create to Python SDK
jakubno Aug 15, 2025
94a415a
Update docs
jakubno Aug 15, 2025
3d432b2
Add create to JS SDk
jakubno Aug 15, 2025
ed04892
Add comments
jakubno Aug 15, 2025
6c92d9f
Small refactor JS SDK
jakubno Aug 15, 2025
51996ea
Increase the number of tries
jakubno Aug 15, 2025
9bed9e8
Lint
jakubno Aug 15, 2025
dcb3d2c
Refactor method to use `beta` prefix
jakubno Aug 15, 2025
098d778
Set version
jakubno Aug 15, 2025
2197e1e
Add beta create
jakubno Aug 15, 2025
7abf7c5
Bump version
jakubno Aug 15, 2025
7498b28
Add auto pause to beta create
jakubno Aug 15, 2025
d940f89
Add docstrings
jakubno Aug 18, 2025
e018c02
Docs
jakubno Aug 18, 2025
84c23fa
Lint
jakubno Aug 18, 2025
1734e13
Refactor connect
jakubno Aug 18, 2025
c3fbc73
Rename spawn to create
jakubno Aug 18, 2025
154e628
Lint
jakubno Aug 18, 2025
e996d92
Lint
jakubno Aug 18, 2025
ec65310
Simplify create
jakubno Aug 18, 2025
e1a8a72
Update docs
jakubno Aug 18, 2025
5fedaa2
Unify
jakubno Aug 18, 2025
f20fd68
Unify
jakubno Aug 18, 2025
d9e483f
Revert
jakubno Aug 18, 2025
d7f25dc
Improve docstrings
ValentaTomas Aug 18, 2025
d7a63a8
Remove changeset
jakubno Aug 19, 2025
130a3f4
Revert docs
jakubno Aug 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .changeset/strange-hornets-judge.md

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/generated_files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
cache-to: type=gha,mode=max

- name: Run codegen
run: make codegen
run: CODEGEN_IMAGE=codegen-env:latest make codegen

- name: Check for uncommitted changes
run: |
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
.PHONY: codegen
codegen:
@echo "Generating SDK code from openapi and envd spec"
@docker run -v "$$(pwd):/workspace" $$(docker build -q -t codegen-env . -f codegen.Dockerfile)
@CODEGEN_IMAGE=$${CODEGEN_IMAGE:-$$(docker build -q -t codegen-env . -f codegen.Dockerfile)} ; \
echo "Using codegen image: $$CODEGEN_IMAGE" \
&& docker run -v $$PWD:/workspace $$CODEGEN_IMAGE make generate

generate: generate-js generate-python

generate-js:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Python
```py
from e2b_code_interpreter import Sandbox

with Sandbox() as sandbox:
with Sandbox.create() as sandbox:
sandbox.run_code("x = 1")
execution = sandbox.run_code("x+=1; x")
print(execution.text) # outputs 2
Expand Down
5 changes: 3 additions & 2 deletions codegen.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ COPY --from=0 /go /go
# Add Go binary to PATH
ENV PATH="/go/bin:${PATH}"

# Install Python deps
RUN pip install black==23.7.0 pyyaml==6.0.2 openapi-python-client==0.24.3
# Install Python deps (e2b-openapi-python-client is patched version to fix issue with explode)
# https://github.com/openapi-generators/openapi-python-client/pull/1296
RUN pip install black==23.7.0 pyyaml==6.0.2 e2b-openapi-python-client==0.26.2

# Install Node.js and npm
RUN apt-get update && \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ import { getConfigPath, loadConfig } from '../../config'
import fs from 'fs'
import { configOption, pathOption } from '../../options'

export const spawnCommand = new commander.Command('spawn')
.description('spawn sandbox and connect terminal to it')
export const createCommand = new commander.Command('create')
.description('create sandbox and connect terminal to it')
.argument(
'[template]',
`spawn and connect to sandbox specified by ${asBold('[template]')}`,
`create and connect to sandbox specified by ${asBold('[template]')}`
)
.addOption(pathOption)
.addOption(configOption)
.alias('sp')
.alias('cr')
.action(
async (
template: string | undefined,
opts: {
name?: string
path?: string
config?: string
},
}
) => {
try {
const apiKey = ensureAPIKey()
Expand All @@ -49,15 +49,15 @@ export const spawnCommand = new commander.Command('spawn')
? [config.template_name]
: undefined,
},
relativeConfigPath,
)}`,
relativeConfigPath
)}`
)
templateID = config.template_id
}

if (!templateID) {
console.error(
'You need to specify sandbox template ID or path to sandbox template config',
'You need to specify sandbox template ID or path to sandbox template config'
)
process.exit(1)
}
Expand All @@ -68,7 +68,7 @@ export const spawnCommand = new commander.Command('spawn')
console.error(err)
process.exit(1)
}
},
}
)

export async function connectSandbox({
Expand All @@ -87,8 +87,8 @@ export async function connectSandbox({

console.log(
`Terminal connecting to template ${asFormattedSandboxTemplate(
template,
)} with sandbox ID ${asBold(`${sandbox.sandboxId}`)}`,
template
)} with sandbox ID ${asBold(`${sandbox.sandboxId}`)}`
)
try {
await spawnConnectedTerminal(sandbox)
Expand All @@ -97,8 +97,8 @@ export async function connectSandbox({
await sandbox.kill()
console.log(
`Closing terminal connection to template ${asFormattedSandboxTemplate(
template,
)} with sandbox ID ${asBold(`${sandbox.sandboxId}`)}`,
template
)} with sandbox ID ${asBold(`${sandbox.sandboxId}`)}`
)
}
}
4 changes: 2 additions & 2 deletions packages/cli/src/commands/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as commander from 'commander'
import { connectCommand } from './connect'
import { listCommand } from './list'
import { killCommand } from './kill'
import { spawnCommand } from './spawn'
import { createCommand } from './create'
import { logsCommand } from './logs'
import { metricsCommand } from './metrics'

Expand All @@ -13,6 +13,6 @@ export const sandboxCommand = new commander.Command('sandbox')
.addCommand(connectCommand)
.addCommand(listCommand)
.addCommand(killCommand)
.addCommand(spawnCommand)
.addCommand(createCommand)
.addCommand(logsCommand)
.addCommand(metricsCommand)
18 changes: 10 additions & 8 deletions packages/cli/src/commands/sandbox/kill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as commander from 'commander'
import { ensureAPIKey } from 'src/api'
import { asBold } from 'src/utils/format'
import * as e2b from 'e2b'
import { listSandboxes } from './list'

async function killSandbox(sandboxID: string, apiKey: string) {
const killed = await e2b.Sandbox.kill(sandboxID, { apiKey })
Expand All @@ -17,7 +18,7 @@ export const killCommand = new commander.Command('kill')
.description('kill sandbox')
.argument(
'[sandboxID]',
`kill the sandbox specified by ${asBold('[sandboxID]')}`,
`kill the sandbox specified by ${asBold('[sandboxID]')}`
)
.alias('kl')
.option('-a, --all', 'kill all running sandboxes')
Expand All @@ -28,31 +29,32 @@ export const killCommand = new commander.Command('kill')
if (!sandboxID && !all) {
console.error(
`You need to specify ${asBold('[sandboxID]')} or use ${asBold(
'-a/--all',
)} flag`,
'-a/--all'
)} flag`
)
process.exit(1)
}

if (all && sandboxID) {
console.error(
`You cannot use ${asBold('-a/--all')} flag while specifying ${asBold(
'[sandboxID]',
)}`,
'[sandboxID]'
)}`
)
process.exit(1)
}

if (all) {
const sandboxes = await e2b.Sandbox.list({ apiKey })

const sandboxes = await listSandboxes({
state: ['running'],
})
if (sandboxes.length === 0) {
console.log('No running sandboxes')
process.exit(0)
}

await Promise.all(
sandboxes.map((sandbox) => killSandbox(sandbox.sandboxId, apiKey)),
sandboxes.map((sandbox) => killSandbox(sandbox.sandboxID, apiKey))
)
} else {
await killSandbox(sandboxID, apiKey)
Expand Down
76 changes: 67 additions & 9 deletions packages/cli/src/commands/sandbox/list.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
import * as tablePrinter from 'console-table-printer'
import * as commander from 'commander'
import * as e2b from 'e2b'
import { components } from 'e2b'

import { client, connectionConfig, ensureAPIKey } from 'src/api'
import { handleE2BRequestError } from '../../utils/errors'

export const listCommand = new commander.Command('list')
.description('list all running sandboxes')
.alias('ls')
.action(async () => {
.option(
'-s, --state <state>',
'filter by state, eg. running, stopped',
(value) => value.split(',')
)
.option(
'-m, --metadata <metadata>',
'filter by metadata, eg. key1=value1',
(value) => value.replace(/,/g, '&')
)
.option(
'-l, --limit <limit>',
'limit the number of sandboxes returned',
(value) => parseInt(value)
)
.action(async (options) => {
try {
const sandboxes = await listSandboxes()
const sandboxes = await listSandboxes({
limit: options.limit,
state: options.state,
metadata: options.metadata,
})

if (!sandboxes?.length) {
console.log('No running sandboxes.')
console.log('No sandboxes found')
} else {
const table = new tablePrinter.Table({
title: 'Running sandboxes',
Expand All @@ -28,6 +47,7 @@ export const listCommand = new commander.Command('list')
{ name: 'alias', alignment: 'left', title: 'Alias' },
{ name: 'startedAt', alignment: 'left', title: 'Started at' },
{ name: 'endAt', alignment: 'left', title: 'End at' },
{ name: 'state', alignment: 'left', title: 'State' },
{ name: 'cpuCount', alignment: 'left', title: 'vCPUs' },
{ name: 'memoryMB', alignment: 'left', title: 'RAM MiB' },
{ name: 'metadata', alignment: 'left', title: 'Metadata' },
Expand All @@ -39,6 +59,8 @@ export const listCommand = new commander.Command('list')
sandboxID: sandbox.sandboxID,
startedAt: new Date(sandbox.startedAt).toLocaleString(),
endAt: new Date(sandbox.endAt).toLocaleString(),
state:
sandbox.state.charAt(0).toUpperCase() + sandbox.state.slice(1), // capitalize
metadata: JSON.stringify(sandbox.metadata),
}))
.sort(
Expand Down Expand Up @@ -81,15 +103,51 @@ export const listCommand = new commander.Command('list')
}
})

export async function listSandboxes(): Promise<
e2b.components['schemas']['ListedSandbox'][]
type ListSandboxesOptions = {
limit?: number
state?: components['schemas']['SandboxState'][]
metadata?: string
}

export async function listSandboxes({
limit,
state,
metadata,
}: ListSandboxesOptions = {}): Promise<
components['schemas']['ListedSandbox'][]
> {
ensureAPIKey()

const signal = connectionConfig.getSignal()
const res = await client.api.GET('/sandboxes', { signal })

handleE2BRequestError(res, 'Error getting running sandboxes')
let hasNext = true
let nextToken: string | undefined
let remainingLimit: number | undefined = limit

const sandboxes: components['schemas']['ListedSandbox'][] = []

while (hasNext && (!limit || (remainingLimit && remainingLimit > 0))) {
const res = await client.api.GET('/v2/sandboxes', {
params: {
query: {
state,
metadata,
nextToken,
limit: remainingLimit,
},
},
signal,
})

handleE2BRequestError(res, 'Error getting running sandboxes')

nextToken = res.response.headers.get('x-next-token') || undefined
hasNext = !!nextToken
sandboxes.push(...res.data)
if (limit && remainingLimit) {
remainingLimit -= res.data.length
}
}

return res.data
return sandboxes
}
2 changes: 1 addition & 1 deletion packages/cli/src/commands/sandbox/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const logsCommand = new commander.Command('logs')
console.log(`\nLogs for sandbox ${asBold(sandboxID)}:`)
}

const isRunningPromise = listSandboxes()
const isRunningPromise = listSandboxes({ state: ['running'] })
.then((r) => r.find((s) => s.sandboxID === getShortID(sandboxID)))
.then((s) => !!s)

Expand Down
6 changes: 6 additions & 0 deletions packages/js-sdk/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ class ApiClient {
}),
...config.headers,
},
querySerializer: {
array: {
style: 'form',
explode: false,
},
},
})

if (config.logger) {
Expand Down
3 changes: 2 additions & 1 deletion packages/js-sdk/src/connectionConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Logger } from './logs'
import { getEnvVar, version } from './api/metadata'

const REQUEST_TIMEOUT_MS = 60_000 // 60 seconds
export const REQUEST_TIMEOUT_MS = 60_000 // 60 seconds
export const DEFAULT_SANDBOX_TIMEOUT_MS = 300_000 // 300 seconds
export const KEEPALIVE_PING_INTERVAL_SEC = 50 // 50 seconds

export const KEEPALIVE_PING_HEADER = 'Keepalive-Ping-Interval'
Expand Down
15 changes: 12 additions & 3 deletions packages/js-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ export type {
PtyOutput,
CommandHandle,
} from './sandbox/commands/commandHandle'
export type { SandboxApiOpts, SandboxCreateOpts } from './sandbox/sandboxApi'
export type {
SandboxInfo,
SandboxMetrics,
SandboxOpts,
SandboxApiOpts,
SandboxConnectOpts,
SandboxBetaCreateOpts,
SandboxMetricsOpts,
SandboxState,
SandboxListOpts,
SandboxPaginator,
} from './sandbox/sandboxApi'

export type {
ProcessInfo,
Expand All @@ -44,8 +55,6 @@ export type {
Pty,
} from './sandbox/commands'

export type { SandboxOpts } from './sandbox'
export type { SandboxInfo, SandboxMetrics } from './sandbox/sandboxApi'
export { Sandbox }
import { Sandbox } from './sandbox'
export default Sandbox
Loading