Skip to content
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

Add multi-file upload and less flaky metrics test for 1.2.0 beta.1 release #626

Open
wants to merge 220 commits into
base: beta
Choose a base branch
from
Open
Changes from 53 commits
Commits
Show all changes
220 commits
Select commit Hold shift + click to select a range
b2e80e1
Added multi-file write support
0div Oct 4, 2024
9e6bc17
address PR comments
0div Oct 4, 2024
966f0c7
[WIP] Cleanup
ValentaTomas Oct 5, 2024
eda88d0
address PR comments
0div Oct 7, 2024
a2d4ad7
boyscouting: fix some docstrings
0div Oct 7, 2024
a379a58
Add multi-file write support for python-sdk sync
0div Oct 7, 2024
608ef45
Use `@overload`
0div Oct 8, 2024
68ac038
merge beta
0div Oct 8, 2024
f010211
adapt multi file write tests for nested dirs
0div Oct 8, 2024
fbc4af4
allow passing empty array of files in python-sdk
0div Oct 8, 2024
2afb6d1
allow passing empty array of files in js-sdk
0div Oct 8, 2024
68e5efa
address PR comments
0div Oct 9, 2024
365af43
add extra tests to sandbox_sync write
0div Oct 9, 2024
7767d8f
updated js-sdk tests to check empty path behavior
0div Oct 9, 2024
f5cd1c0
add multifile upload to sanbox_async
0div Oct 9, 2024
834f84c
merge beta
0div Oct 10, 2024
2956a6e
better error messages in python-sdk
0div Oct 10, 2024
c277f9f
better error messages in js-sdk
0div Oct 10, 2024
86262f1
docstring for dataclass
0div Oct 10, 2024
97d0cc1
merge main
0div Dec 12, 2024
cf262ae
fix errors.ts comment
0div Dec 12, 2024
4ca2414
fixed typing syntax and watch tests
0div Dec 12, 2024
93dc1f9
update docs
0div Dec 12, 2024
741b329
add minor changeset
0div Dec 12, 2024
bbeeb04
upadte upload docs and improve read-write-docs
0div Dec 12, 2024
0e2a684
fix indentation in upload docs
0div Dec 13, 2024
b867fe4
Update apps/web/src/app/(docs)/docs/filesystem/read-write/page.mdx
0div Dec 13, 2024
e730810
Update apps/web/src/app/(docs)/docs/filesystem/read-write/page.mdx
0div Dec 13, 2024
d3b99fe
Update apps/web/src/app/(docs)/docs/filesystem/upload/page.mdx
0div Dec 13, 2024
424baec
Update apps/web/src/app/(docs)/docs/filesystem/upload/page.mdx
0div Dec 13, 2024
e73ad23
Update apps/web/src/app/(docs)/docs/filesystem/upload/page.mdx
0div Dec 13, 2024
d0fc09a
Update apps/web/src/app/(docs)/docs/filesystem/upload/page.mdx
0div Dec 13, 2024
1708cc0
Update apps/web/src/app/(docs)/docs/filesystem/upload/page.mdx
0div Dec 13, 2024
502c414
remove WriteData type in js-sdk
0div Dec 13, 2024
724fbc6
remove WriteData type in python-sdk
0div Dec 13, 2024
5b3f58b
Update e2b.toml
jakubno Jan 24, 2025
4520df8
Update e2b.toml
jakubno Jan 24, 2025
fd45f0e
Handle exception when iterating over process output
jakubno Jan 24, 2025
1829790
Merge branch 'main' into process-time-out-returns-incorrect-exception…
jakubno Jan 24, 2025
ae61d83
Add test
jakubno Jan 24, 2025
54c8315
add: skeletons + usage limits components
ben-fornefeld Jan 10, 2025
6d9a278
improve: billing limit components
ben-fornefeld Jan 10, 2025
93a6b9b
add: billing limits states + api layer
ben-fornefeld Jan 10, 2025
673272e
fix account selector billing url retrieval
ben-fornefeld Jan 17, 2025
e644a98
add billing api logic + improve billing alerts components
ben-fornefeld Jan 17, 2025
a742e8e
Update text for usage limits
mlejva Jan 18, 2025
eada5d4
Fix build error
mlejva Jan 19, 2025
1e45455
prevent form submission with empty values
ben-fornefeld Jan 20, 2025
379cc26
update fetch headers for billing alerts
ben-fornefeld Jan 24, 2025
062c6ca
add metrics docs page
0div Jan 24, 2025
c4a54fc
add metrics to routes
0div Jan 24, 2025
4450680
update docs based on feedback
0div Jan 24, 2025
295f4e2
fix code snippet and remove note
0div Jan 24, 2025
1337573
update teams retrieval for is_blocked & blocked_reason
ben-fornefeld Jan 24, 2025
830af85
add team blocked banner on page layout
ben-fornefeld Jan 24, 2025
04f223f
improve team blocked banner
ben-fornefeld Jan 24, 2025
fb0dcb7
Implement budget alerts & limit (#551)
ben-fornefeld Jan 24, 2025
3fcfd2b
Update wording for usage limits and alerts
mlejva Jan 25, 2025
525d053
Change button's text
mlejva Jan 25, 2025
8b5af4e
Update wording for usage limits and alerts (#553)
mlejva Jan 25, 2025
974d92e
update limitations section
0div Jan 25, 2025
61c7bfa
fix beta version typo and add private beta note
0div Jan 25, 2025
35c1837
use latest beta tag for npm installs
0div Jan 25, 2025
d3f0e37
Add sandbox metrics docs page (#552)
0div Jan 25, 2025
02b032a
fix thing
handlebauer Jan 25, 2025
43291b5
Fix typo in docs (#554)
mlejva Jan 26, 2025
5b3ef97
hide metrics in sidebar until it's shipped
mlejva Jan 27, 2025
90788ee
Hide metrics in sidebar (#555)
mlejva Jan 27, 2025
0524739
Add groq to docs
tizkovatereza Jan 27, 2025
aea3d3e
Add groq to docs (#557)
tizkovatereza Jan 27, 2025
90fb02c
uncomment metrics route
0div Jan 27, 2025
1e1d398
Show metrics in sidebar (#558)
0div Jan 27, 2025
25c4539
Add watch dir recursive option (missing python client regeneration)
dobrac Dec 2, 2024
d92a166
Update watch test file formatting
dobrac Dec 2, 2024
4adcfd8
Add python generated stubs
dobrac Dec 2, 2024
d35880c
Improve watchDir jsSDK default value
dobrac Dec 2, 2024
ca85407
Add recursive directory watching to the web documentation
dobrac Dec 2, 2024
225c156
Update documentation highlights
dobrac Dec 2, 2024
c8914c2
Improve recursive watch tests and folders naming
dobrac Dec 2, 2024
76b4ac0
Regenerated python stubs with version 5.28.3
dobrac Dec 2, 2024
6596eda
Test for recursive folder creation, add newline after code to docs
dobrac Dec 3, 2024
ea8f4ff
Run js tests cleanup using onTestFinished
dobrac Dec 3, 2024
f893f28
Add note to the recursive watch with rapid folder creation
dobrac Dec 19, 2024
29dc1a9
Add recursive watch envd version check
dobrac Dec 19, 2024
ec9288a
Add recursive watch envd version check python SDK, js SDK check
dobrac Dec 19, 2024
51e8f18
Cleanup tests only in debug
dobrac Dec 19, 2024
359d17d
Fix versions to match updated envd version
jakubno Jan 13, 2025
8c52e73
Lint
jakubno Jan 28, 2025
13f0c78
Add changeset
jakubno Jan 28, 2025
4fb4470
Add recursive directory watch (#488)
jakubno Jan 28, 2025
fe7ed15
Process time out returns incorrect exception in pythonsdk e2b 1476 (#…
jakubno Jan 28, 2025
dde83b7
[skip ci] Release new versions
github-actions[bot] Jan 28, 2025
e9480d5
Add X-Frame-Options header
jakubno Jan 29, 2025
b31dd7b
Fix type
jakubno Jan 29, 2025
e8e4cab
Fix X-Frame-Option to prevent clickjacking (#559)
jakubno Jan 29, 2025
9c6597b
Bump the pip group across 2 directories with 6 updates
dependabot[bot] Jan 29, 2025
892ae4f
Bump the npm_and_yarn group across 1 directory with 2 updates
dependabot[bot] Jan 29, 2025
7cd8781
Add page about start command
mlejva Jan 30, 2025
0e6610e
Remove legacy docs page
mlejva Jan 30, 2025
e3e9d38
Add page about start command (#566)
mlejva Jan 30, 2025
391f070
Fix socker closed build error
jakubno Jan 30, 2025
bf1e1b1
Add changeset
jakubno Jan 30, 2025
672622a
Fix Socket closed build error (#567)
jakubno Jan 30, 2025
d17bdaa
[skip ci] Release new versions
github-actions[bot] Jan 31, 2025
6728061
add assetPrefix to next config
ben-fornefeld Feb 2, 2025
8903a8b
limit asset prefixing to production
ben-fornefeld Feb 2, 2025
5e2a23f
Add assetPrefix to next config (#569)
jakubno Feb 2, 2025
6ebdb17
replace VERCEL_URL env variable with custom set NEXT_PUBLIC_DOMAIN in…
ben-fornefeld Feb 2, 2025
28748ee
update assetPrefix to new environment variable to not break vercel pr…
ben-fornefeld Feb 2, 2025
c47fdbd
Fix: assetPrefix refers to correct domains for production & preview e…
jakubno Feb 2, 2025
ffb6cef
redirect `/blog` to the new blog website
mlejva Feb 3, 2025
1fb2975
Redirect `/blog` to the new blog website (#572)
mlejva Feb 3, 2025
464db3f
Fix pnpm lock
jakubno Feb 4, 2025
5531185
Bump the npm_and_yarn group across 1 directory with 2 updates (#562)
jakubno Feb 4, 2025
7c38aa1
Bump the pip group across 2 directories with 6 updates (#561)
jakubno Feb 4, 2025
c68fa44
If in browser don't use process
jakubno Feb 5, 2025
5749120
Add changeset
jakubno Feb 5, 2025
0cc2b2f
Fix usage in browser (#574)
jakubno Feb 5, 2025
cff68d2
Fix check order
jakubno Feb 5, 2025
cd45aa4
[skip ci] Release new versions
github-actions[bot] Feb 5, 2025
c7b9fc2
Update vitest
jakubno Feb 5, 2025
8e3ead8
Update cross-spawn
jakubno Feb 5, 2025
0d09045
Update cross-spawn
jakubno Feb 5, 2025
66141ef
Update changeset to update cross-spawn
jakubno Feb 5, 2025
6e477ca
Update fast xml parser
jakubno Feb 5, 2025
67787e5
Bump packages
jakubno Feb 5, 2025
b91dbc1
Bump tsup
jakubno Feb 5, 2025
c84c76f
Bump braces
jakubno Feb 5, 2025
c9fa872
Update ip
jakubno Feb 5, 2025
3bbc0b9
Update packages
jakubno Feb 5, 2025
ee35b08
Bump postcss
jakubno Feb 5, 2025
cc8447b
Bump knip
jakubno Feb 5, 2025
6aee7bc
Bump knip
jakubno Feb 5, 2025
bbab1ff
Bump webpack
jakubno Feb 5, 2025
96a4557
Bump micromatch
jakubno Feb 5, 2025
7e88e33
Bump tar
jakubno Feb 5, 2025
71f6d36
Bump jose
jakubno Feb 5, 2025
3cffe51
Bump update notifier
jakubno Feb 5, 2025
5ccdc76
Bump es5-ext
jakubno Feb 5, 2025
eed7384
Drop support for Python 3.8
jakubno Feb 6, 2025
28fd11e
Drop support for Python 3.8 (#575)
jakubno Feb 6, 2025
e92aa08
add: gtm to apps/web
ben-fornefeld Feb 9, 2025
637f680
chore: remove chatlio-widget namespace declaration
ben-fornefeld Feb 9, 2025
db68947
E2B-1546: add google tag manager capibilites for /docs (#576)
ben-fornefeld Feb 9, 2025
c108afb
Update package manager to the latest 9.x version (9.15.5)
dobrac Feb 10, 2025
6d85cf5
Update package manager to the latest 9.x version (9.15.5) (#577)
jakubno Feb 11, 2025
26b2fe5
Fix browser test
jakubno Feb 11, 2025
429ec03
Load update notifier dynamically
jakubno Feb 11, 2025
3ef2046
update: middleware to rewrite /category
ben-fornefeld Feb 11, 2025
46aa4ec
fix incorrect code snippet
mlejva Feb 18, 2025
87b0110
Fix incorrect code snippets
mlejva Feb 18, 2025
f8d988e
Fix incorrect links
mlejva Feb 18, 2025
357be70
Fix code snippets
mlejva Feb 18, 2025
ff818fb
Fix incorrect code snippets and docs links (#583)
mlejva Feb 18, 2025
2a7efcd
feat: rewrite /blog/category/:path* to landing page categories
ben-fornefeld Feb 19, 2025
842f46c
improve: sitemap creation & category rewrite modifications
ben-fornefeld Feb 19, 2025
6a87672
test: fix sitemap runtime fs error by making sitemap cache on build time
ben-fornefeld Feb 19, 2025
b85d676
Merge branch 'main' into rewrite-category-in-middleware-e2b-1578
ben-fornefeld Feb 19, 2025
8aa8ef9
Rewrite /category in middleware (#579)
ben-fornefeld Feb 19, 2025
1653893
Update docs for persistence
mlejva Feb 21, 2025
e04c67d
Merge branch 'main' into update-persistence-docs
mlejva Feb 21, 2025
32e0b03
Update persistence docs (#585)
mlejva Feb 21, 2025
9ef3dcb
Explicitly mention docker commands when building template
mlejva Feb 21, 2025
d3945ac
Add changeset
mlejva Feb 21, 2025
fd6a80e
Merge branch 'main' into cli-better-messaging
mlejva Feb 21, 2025
cb6f762
Fix message
mlejva Feb 21, 2025
cd4880e
Explicitly mention Docker commands when building template (#586)
mlejva Feb 21, 2025
ec36840
update the update-notifier import to esm, remove it from the excluded…
dobrac Feb 21, 2025
aa7a2bf
add changeset
dobrac Feb 21, 2025
4bc7129
Fix CLI build issue (#578)
ValentaTomas Feb 21, 2025
f706f82
Bump package json version for the e2b/cli
dobrac Feb 21, 2025
6b71953
Bump package json version for the e2b/cli (#587)
ValentaTomas Feb 21, 2025
9f23f54
[skip ci] Release new versions
github-actions[bot] Feb 22, 2025
bc79f15
Fix broken link
mlejva Feb 22, 2025
2dc0580
Fix broken link in docs (#589)
mlejva Feb 22, 2025
4919993
Improve legacy banner visibility in /docs (#590)
ben-fornefeld Feb 22, 2025
fba2ce3
fix: filter shiki line highlight syntax when copying code blocks in docs
ben-fornefeld Feb 25, 2025
74de6eb
Filter Shiki HighlightLine syntax when copying code blocks in docs (#…
ben-fornefeld Feb 25, 2025
e419a61
Change twitter links to the new handle
jakubno Feb 26, 2025
e083bad
Change twitter links to the new handle (#595)
jakubno Feb 26, 2025
0619540
Display correct key name for api key (#594)
r33drichards Mar 1, 2025
ee90cd2
[skip ci] Release new versions
github-actions[bot] Mar 1, 2025
56ca5e0
Show resolvable url in error message (#596)
r33drichards Mar 4, 2025
ac87d9d
[skip ci] Release new versions
github-actions[bot] Mar 4, 2025
82fe7b3
Update CODEOWNERS (#601)
mlejva Mar 13, 2025
dad913e
pin typdoc and typedoc-markdown to non-breaking versions in js-sdk
0div Mar 13, 2025
cecb61d
Fix SDK reference generation in js-sdk (#602)
0div Mar 13, 2025
c04be45
update pnpm loockfile
0div Mar 13, 2025
81a8e8b
Update pnpm lockfile (#603)
0div Mar 13, 2025
2e637cb
Send releases notification to dedicated channel (#604)
jakubno Mar 14, 2025
e905d9b
Test sandbox closed port error (#597)
0div Mar 14, 2025
5c1a5a8
Update README.md (#606)
mlejva Mar 16, 2025
b357a40
Fix example code for the latest version of CrewAI
jamesmurdza Mar 18, 2025
cee8f00
Clean unused HTMLs (#610)
jakubno Mar 18, 2025
6a73f8d
fix: docs quick start issues
ben-fornefeld Mar 18, 2025
e5866ce
Fix docs quick start issues (#611)
ben-fornefeld Mar 18, 2025
71415ed
Fix flaky tests - Increase the timeout for pinging the server (#612)
jakubno Mar 18, 2025
e045df9
Fix comments in tests (#613)
jakubno Mar 18, 2025
43f3c12
Update apps/web/src/app/(docs)/docs/filesystem/read-write/page.mdx
0div Mar 18, 2025
a8e88de
fix merge conflict with main
0div Mar 18, 2025
ce8d130
Fix CrewAI example in the docs (#609)
jamesmurdza Mar 19, 2025
b3fe5e6
Add multi-file write support to the js and python sdks (#451)
0div Mar 19, 2025
ad73fab
Include build ID output for debugging purposes (#616)
r33drichards Mar 19, 2025
d26ff43
Fix release test for multi-file write support (#615)
0div Mar 19, 2025
82f1ba8
Add changeset (#618)
r33drichards Mar 19, 2025
60ece87
Run tests on prs to avoid broken builds (#619)
r33drichards Mar 19, 2025
dbcf014
doc: fix typo of file `apps/web/src/app/(docs)/docs/sandbox/internet-…
Mar 20, 2025
a45df0d
doc: fix type of file `apps/web/src/app/(docs)/docs/code-interpreting…
Mar 20, 2025
d56c19f
doc: fix typo (#620)
mlejva Mar 20, 2025
2904f3e
Fix failing write test (#621)
jakubno Mar 20, 2025
b402327
[skip ci] Release new versions
github-actions[bot] Mar 20, 2025
62eb819
add: docs redirects
ben-fornefeld Mar 21, 2025
51519ee
Merge branch 'main' into fix-docs-quickstart-e2b-1868
ben-fornefeld Mar 21, 2025
d939c04
Add temporary docs redirects (#623)
ben-fornefeld Mar 21, 2025
5c4d075
Add filtering option for listing sandboxes (#532)
jakubno Mar 21, 2025
b6b0ac6
[skip ci] Release new versions
github-actions[bot] Mar 21, 2025
e5a3f24
Fix code interpreting documentation snippets (#624)
ben-fornefeld Mar 21, 2025
dc0d161
make metrics test less flaky
0div Mar 21, 2025
d2f9415
Merge branch 'main' of https://github.com/e2b-dev/E2B into add-multif…
0div Mar 22, 2025
78180e6
fix typos and update lock and gens
0div Mar 22, 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
3 changes: 3 additions & 0 deletions .github/workflows/cli_tests.yml
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@ name: Test CLI

on:
workflow_call:
pull_request:
branches:
- main

permissions:
contents: read
3 changes: 3 additions & 0 deletions .github/workflows/js_sdk_tests.yml
Original file line number Diff line number Diff line change
@@ -5,6 +5,9 @@ on:
secrets:
E2B_API_KEY:
required: true
pull_request:
branches:
- main

permissions:
contents: read
3 changes: 3 additions & 0 deletions .github/workflows/python_sdk_tests.yml
Original file line number Diff line number Diff line change
@@ -5,6 +5,9 @@ on:
secrets:
E2B_API_KEY:
required: true
pull_request:
branches:
- main

permissions:
contents: read
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@
"autoprefixer": "^10.4.7",
"class-variance-authority": "^0.7.0",
"clsx": "^1.2.1",
"e2b": "^1.0.7",
"e2b": "^1.1.0",
"fast-glob": "^3.3.0",
"fast-xml-parser": "4.4.1",
"flexsearch": "^0.7.31",
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import imgChart from '@/images/analyze-data-chart.png'
You can use E2B Sandbox to run AI-generated code to analyze data. Here's how the AI data analysis workflow usually looks like:
1. Your user has a dataset in CSV format or other formats.
2. You prompt the LLM to generate code (usually Python) based on the user's data.
3. The sandbox runs the AU-generated code and returns the results.
3. The sandbox runs the AI-generated code and returns the results.
4. You display the results to the user.

---
34 changes: 31 additions & 3 deletions apps/web/src/app/(docs)/docs/filesystem/read-write/page.mdx
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

## Reading files

You can read files from the sandbox filesystem using the `files.reado()` method.
You can read files from the sandbox filesystem using the `files.read()` method.

<CodeGroup>
```js
@@ -18,20 +18,48 @@ file_content = sandbox.files.read('/path/to/file')
```
</CodeGroup>

## Writing files
## Writing single files

You can write files to the sandbox filesystem using the `files.write()` method.
You can write single files to the sandbox filesystem using the `files.write()` method.

<CodeGroup>
```js
import { Sandbox } from '@e2b/code-interpreter'
const sandbox = await Sandbox.create()

await sandbox.files.write('/path/to/file', 'file content')
```
```python
from e2b_code_interpreter import Sandbox

sandbox = Sandbox()

await sandbox.files.write('/path/to/file', 'file content')
```
</CodeGroup>

## Writing multiple files

You can also write multiple files to the sandbox filesystem using the `files.write()` method.

<CodeGroup>
```js
import { Sandbox } from '@e2b/code-interpreter'
const sandbox = await Sandbox.create()

await sandbox.files.write([
{ path: '/path/to/a', data: 'file content' },
{ path: '/another/path/to/b', data: 'file content' }
])
```
```python
from e2b_code_interpreter import Sandbox

sandbox = Sandbox()

await sandbox.files.write([
{ "path": "/path/to/a", "data": "file content" },
{ "path": "another/path/to/b", "data": "file content" }
])
```
</CodeGroup>
87 changes: 86 additions & 1 deletion apps/web/src/app/(docs)/docs/filesystem/upload/page.mdx
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

You can upload data to the sandbox using the `files.write()` method.

## Upload single file

<CodeGroup>
```js
import fs from 'fs'
@@ -22,6 +24,89 @@ sandbox = Sandbox()
# Read file from local filesystem
with open("path/to/local/file", "rb") as file:
# Upload file to sandbox
sandbox.files.write("/path/in/sandbox", file)
sandbox.files.write("/path/in/sandbox", file)
```
</CodeGroup>

## Upload directory / multiple files

<CodeGroup>
```js
const fs = require('fs');
const path = require('path');

import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

// Read all files in the directory and store their paths and contents in an array
const readDirectoryFiles = (directoryPath) => {
// Read all files in the local directory
const files = fs.readdirSync(directoryPath);

// Map files to objects with path and data
const filesArray = files
.filter(file => {
const fullPath = path.join(directoryPath, file);
// Skip if it's a directory
return fs.statSync(fullPath).isFile();
})
.map(file => {
const filePath = path.join(directoryPath, file);

// Read the content of each file
return {
path: filePath,
data: fs.readFileSync(filePath, 'utf8')
};
});

return filesArray;
};

// Usage example
const files = readDirectoryContents('/local/dir');
console.log(files);
// [
// { path: '/local/dir/file1.txt', data: 'File 1 contents...' },
// { path: '/local/dir/file2.txt', data: 'File 2 contents...' },
// ...
// ]

await sandbox.files.write(files)
```
```python
import os
from e2b_code_interpreter import Sandbox

sandbox = Sandbox()

def read_directory_files(directory_path):
files = []

# Iterate through all files in the directory
for filename in os.listdir(directory_path):
file_path = os.path.join(directory_path, filename)

# Skip if it's a directory
if os.path.isfile(file_path):
# Read file contents in binary mode
with open(file_path, "rb") as file:
files.append({
'path': file_path,
'data': file.read()
})

return files

files = read_directory_files("/local/dir")
print(files)
# [
# {"'path": "/local/dir/file1.txt", "data": "File 1 contents..." },
# { "path": "/local/dir/file2.txt", "data": "File 2 contents..." },
# ...
# ]

sandbox.files.write(files)
```
</CodeGroup>
9 changes: 5 additions & 4 deletions apps/web/src/app/(docs)/docs/quickstart/connect-llms/page.mdx
Original file line number Diff line number Diff line change
@@ -497,13 +497,14 @@ console.log(text)

<CodeGroup>
```python
# pip install crewai crewai[tools] e2b-code-interpreter
from crewai_tools import tool
# pip install crewai e2b-code-interpreter
from crewai.tools import tool
from crewai import Agent, Task, Crew, LLM
from e2b_code_interpreter import Sandbox

@tool("Python interpreter tool")
def execute_python(code: str):
# Update tool definition using the decorator
@tool("Python Interpreter")
def execute_python(code: str) -> str:
"""
Execute Python code and return the results.
"""
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ Every sandbox has a public URL that can be used to access running services insid

<CodeGroup>
```js
import { Sandbox } from '@e2b/code-interpeter'
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

@@ -31,10 +31,10 @@ The code above will print something like this:

<CodeGroup>
```bash {{ language: 'js' }}
http://3000-i62mff4ahtrdfdkyn2esc-b0b684e9.e2b.dev
https://3000-i62mff4ahtrdfdkyn2esc-b0b684e9.e2b.dev
```
```bash {{ language: 'python' }}
http://3000-i62mff4ahtrdfdkyn2esc-b0b684e9.e2b.dev
https://3000-i62mff4ahtrdfdkyn2esc-b0b684e9.e2b.dev
```
</CodeGroup>

@@ -47,7 +47,7 @@ In this example we will start a simple HTTP server that listens on port 3000 and

<CodeGroup>
```js
import { Sandbox } from '@e2b/code-interpeter'
import { Sandbox } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## e2b auth


authentication commands

### Usage

```bash
e2b auth [options] [command]
```
## e2b auth login


log in to CLI

### Usage

```bash
e2b auth login [options]
```


## e2b auth logout


log out of CLI

### Usage

```bash
e2b auth logout [options]
```


## e2b auth info


get information about the current user

### Usage

```bash
e2b auth info [options]
```


## e2b auth configure


configure user

### Usage

```bash
e2b auth configure [options]
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
## e2b sandbox


work with sandboxes

### Usage

```bash
e2b sandbox [options] [command]
```
## e2b sandbox connect


connect terminal to already running sandbox

### Usage

```bash
e2b sandbox connect [options] <sandboxID>
```


## e2b sandbox list


list all running sandboxes

### Usage

```bash
e2b sandbox list [options]
```


## e2b sandbox kill


kill sandbox

### Usage

```bash
e2b sandbox kill [options] [sandboxID]
```

### Options


- `-a, --all: kill all running sandboxes `


## e2b sandbox spawn


spawn sandbox and connect terminal to it

### Usage

```bash
e2b sandbox spawn [options] [template]
```

### Options


- `-p, --path <path>: change root directory where command is executed to <path> directory `
- `--config <e2b-toml>: specify path to the E2B config toml. By default E2B tries to find ./e2b.toml in root directory. `


## e2b sandbox logs


show logs for sandbox

### Usage

```bash
e2b sandbox logs [options] <sandboxID>
```

### Options


- `--level <level>: filter logs by level (DEBUG, INFO, WARN, ERROR). The logs with the higher levels will be also shown. [default: INFO]`
- `-f, --follow: keep streaming logs until the sandbox is closed `
- `--format <format>: specify format for printing logs (json, pretty) [default: pretty]`
- `--loggers [loggers]: filter logs by loggers. Specify multiple loggers by separating them with a comma. `


Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
## e2b template


manage sandbox templates

### Usage

```bash
e2b template [options] [command]
```
## e2b template build


build sandbox template defined by ./e2b.Dockerfile or ./Dockerfile in root directory. By default the root directory is the current working directory. This command also creates e2b.toml config.

### Usage

```bash
e2b template build [options] [template]
```

### Options


- `-p, --path <path>: change root directory where command is executed to <path> directory `
- `-d, --dockerfile <file>: specify path to Dockerfile. By default E2B tries to find e2b.Dockerfile or Dockerfile in root directory. `
- `-n, --name <template-name>: specify sandbox template name. You can use the template name to start the sandbox with SDK. The template name must be lowercase and contain only letters, numbers, dashes and underscores. `
- `-c, --cmd <start-command>: specify command that will be executed when the sandbox is started. `
- `-t, --team <team-id>: specify the team ID that the operation will be associated with. You can find team ID in the team settings in the E2B dashboard (https://e2b.dev/dashboard?tab=team). `
- `--config <e2b-toml>: specify path to the E2B config toml. By default E2B tries to find ./e2b.toml in root directory. `
- `--cpu-count <cpu-count>: specify the number of CPUs that will be used to run the sandbox. The default value is 2. `
- `--memory-mb <memory-mb>: specify the amount of memory in megabytes that will be used to run the sandbox. Must be an even number. The default value is 512. `
- `--build-arg <args...>: specify additional build arguments for the build command. The format should be <varname>=<value>. `


## e2b template list


list sandbox templates

### Usage

```bash
e2b template list [options]
```

### Options


- `-t, --team <team-id>: specify the team ID that the operation will be associated with. You can find team ID in the team settings in the E2B dashboard (https://e2b.dev/dashboard?tab=team). `


## e2b template init


create basic E2B Dockerfile (./e2b.Dockerfile) in root directory. You can then run e2b template build to build sandbox template from this Dockerfile

### Usage

```bash
e2b template init [options]
```

### Options


- `-p, --path <path>: change root directory where command is executed to <path> directory `


## e2b template delete


delete sandbox template and e2b.toml config

### Usage

```bash
e2b template delete [options] [template]
```

### Options


- `-p, --path <path>: change root directory where command is executed to <path> directory `
- `--config <e2b-toml>: specify path to the E2B config toml. By default E2B tries to find ./e2b.toml in root directory. `
- `-s, --select: select sandbox template from interactive list `
- `-t, --team <team-id>: specify the team ID that the operation will be associated with. You can find team ID in the team settings in the E2B dashboard (https://e2b.dev/dashboard?tab=team). `
- `-y, --yes: skip manual delete confirmation `


## e2b template publish


publish sandbox template

### Usage

```bash
e2b template publish [options] [template]
```

### Options


- `-p, --path <path>: change root directory where command is executed to <path> directory `
- `--config <e2b-toml>: specify path to the E2B config toml. By default E2B tries to find ./e2b.toml in root directory. `
- `-s, --select: select sandbox template from interactive list `
- `-t, --team <team-id>: specify the team ID that the operation will be associated with. You can find team ID in the team settings in the E2B dashboard (https://e2b.dev/dashboard?tab=team). `
- `-y, --yes: skip manual publish confirmation `


## e2b template unpublish


unpublish sandbox template

### Usage

```bash
e2b template unpublish [options] [template]
```

### Options


- `-p, --path <path>: change root directory where command is executed to <path> directory `
- `--config <e2b-toml>: specify path to the E2B config toml. By default E2B tries to find ./e2b.toml in root directory. `
- `-s, --select: select sandbox template from interactive list `
- `-t, --team <team-id>: specify the team ID that the operation will be associated with. You can find team ID in the team settings in the E2B dashboard (https://e2b.dev/dashboard?tab=team). `
- `-y, --yes: skip manual unpublish confirmation `


Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
### AuthenticationError

Thrown when authentication fails.

#### Constructors

```ts
new AuthenticationError(message: any): AuthenticationError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `any` |

###### Returns

`AuthenticationError`

***

### InvalidArgumentError

Thrown when an invalid argument is provided.

#### Constructors

```ts
new InvalidArgumentError(message: string): InvalidArgumentError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

###### Returns

`InvalidArgumentError`

***

### NotEnoughSpaceError

Thrown when there is not enough disk space.

#### Constructors

```ts
new NotEnoughSpaceError(message: string): NotEnoughSpaceError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

###### Returns

`NotEnoughSpaceError`

***

### NotFoundError

Thrown when a resource is not found.

#### Constructors

```ts
new NotFoundError(message: string): NotFoundError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

###### Returns

`NotFoundError`

***

### RateLimitError

Thrown when the API rate limit is exceeded.

#### Constructors

```ts
new RateLimitError(message: any): RateLimitError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `any` |

###### Returns

`RateLimitError`

***

### SandboxError

Base class for all sandbox errors.

Thrown when general sandbox errors occur.

#### Extended by

- `TimeoutError`
- `InvalidArgumentError`
- `NotEnoughSpaceError`
- `NotFoundError`
- `AuthenticationError`
- `TemplateError`
- `RateLimitError`

#### Constructors

```ts
new SandboxError(message: any): SandboxError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `any` |

###### Returns

`SandboxError`

***

### TemplateError

Thrown when the template uses old envd version. It isn't compatible with the new SDK.

#### Constructors

```ts
new TemplateError(message: string): TemplateError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

###### Returns

`TemplateError`

***

### TimeoutError

Thrown when a timeout error occurs.

The [unavailable] error type is caused by sandbox timeout.

The [canceled] error type is caused by exceeding request timeout.

The [deadline_exceeded] error type is caused by exceeding the timeout for command execution, watch, etc.

The [unknown] error type is sometimes caused by the sandbox timeout when the request is not processed correctly.

#### Constructors

```ts
new TimeoutError(message: string): TimeoutError
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

###### Returns

`TimeoutError`

## Functions

### formatSandboxTimeoutError()

```ts
function formatSandboxTimeoutError(message: string): TimeoutError
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

#### Returns

`TimeoutError`

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@



## SandboxException

```python
class SandboxException(Exception)
```

Base class for all sandbox errors.

Raised when a general sandbox exception occurs.


## TimeoutException

```python
class TimeoutException(SandboxException)
```

Raised when a timeout occurs.

The `unavailable` exception type is caused by sandbox timeout.

The `canceled` exception type is caused by exceeding request timeout.

The `deadline_exceeded` exception type is caused by exceeding the timeout for process, watch, etc.

The `unknown` exception type is sometimes caused by the sandbox timeout when the request is not processed correctly.


## InvalidArgumentException

```python
class InvalidArgumentException(SandboxException)
```

Raised when an invalid argument is provided.


## NotEnoughSpaceException

```python
class NotEnoughSpaceException(SandboxException)
```

Raised when there is not enough disk space.


## NotFoundException

```python
class NotFoundException(SandboxException)
```

Raised when a resource is not found.


## AuthenticationException

```python
class AuthenticationException(SandboxException)
```

Raised when authentication fails.


## TemplateException

```python
class TemplateException(SandboxException)
```

Exception raised when the template uses old envd version. It isn't compatible with the new SDK.


## RateLimitException

```python
class RateLimitException(SandboxException)
```

Raised when the API rate limit is exceeded.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@e2b/cli",
"version": "1.2.2",
"version": "1.2.3",
"description": "CLI for managing e2b sandbox templates",
"homepage": "https://e2b.dev",
"license": "MIT",
@@ -73,7 +73,7 @@
"command-exists": "^1.2.9",
"commander": "^11.1.0",
"console-table-printer": "^2.11.2",
"e2b": "^1.0.7",
"e2b": "^1.1.0",
"inquirer": "^9.2.12",
"open": "^9.1.0",
"strip-ansi": "^7.1.0",
2 changes: 1 addition & 1 deletion packages/cli/src/commands/template/build.ts
Original file line number Diff line number Diff line change
@@ -397,7 +397,7 @@ export const buildCommand = new commander.Command('build')
console.log(
`> Triggered build for the sandbox template ${asFormattedSandboxTemplate(
template
)} `
)} with build ID: ${template.buildID}`
)

console.log('Waiting for build to finish...')
2 changes: 1 addition & 1 deletion packages/js-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "e2b",
"version": "1.0.7",
"version": "1.1.0",
"description": "E2B SDK that give agents cloud environments",
"homepage": "https://e2b.dev",
"license": "MIT",
25 changes: 11 additions & 14 deletions packages/js-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
export { ApiClient } from './api'
export type { components, paths } from './api'

export { ConnectionConfig } from './connectionConfig'
export type { ConnectionOpts, Username } from './connectionConfig'
export {
AuthenticationError,
SandboxError,
TimeoutError,
NotFoundError,
NotEnoughSpaceError,
InvalidArgumentError,
NotEnoughSpaceError,
NotFoundError,
SandboxError,
TemplateError,
TimeoutError,
} from './errors'
export { ConnectionConfig } from './connectionConfig'
export type { Logger } from './logs'
export type { ConnectionOpts, Username } from './connectionConfig'

export { FilesystemEventType } from './sandbox/filesystem/watchHandle'
export type {
FilesystemEvent,
WatchHandle,
} from './sandbox/filesystem/watchHandle'
export type { EntryInfo, Filesystem, WatchOpts } from './sandbox/filesystem'
export { FileType } from './sandbox/filesystem'
export type { EntryInfo, Filesystem } from './sandbox/filesystem'
export { FilesystemEventType } from './sandbox/filesystem/watchHandle'
export type { FilesystemEvent, WatchHandle } from './sandbox/filesystem/watchHandle'

export { CommandExitError } from './sandbox/commands/commandHandle'
export type {
@@ -41,8 +38,8 @@ export type {
Pty,
} from './sandbox/commands'

export type { SandboxInfo } from './sandbox/sandboxApi'
export type { SandboxOpts } from './sandbox'
import { Sandbox } from './sandbox'
export type { SandboxInfo } from './sandbox/sandboxApi'
export { Sandbox }
import { Sandbox } from './sandbox'
export default Sandbox
110 changes: 89 additions & 21 deletions packages/js-sdk/src/sandbox/filesystem/index.ts
Original file line number Diff line number Diff line change
@@ -7,20 +7,23 @@ import {
} from '@connectrpc/connect'
import {
ConnectionConfig,
defaultUsername,
Username,
ConnectionOpts,
KEEPALIVE_PING_INTERVAL_SEC,
defaultUsername,
KEEPALIVE_PING_HEADER,
KEEPALIVE_PING_INTERVAL_SEC,
Username,
} from '../../connectionConfig'

import { handleEnvdApiError, handleWatchDirStartEvent } from '../../envd/api'
import { authenticationHeader, handleRpcError } from '../../envd/rpc'

import { EnvdApiClient } from '../../envd/api'
import { FileType as FsFileType, Filesystem as FilesystemService } from '../../envd/filesystem/filesystem_pb'
import {
FileType as FsFileType,
Filesystem as FilesystemService,
} from '../../envd/filesystem/filesystem_pb'

import { WatchHandle, FilesystemEvent } from './watchHandle'
import { FilesystemEvent, WatchHandle } from './watchHandle'

import { compareVersions } from 'compare-versions'
import { TemplateError } from '../../errors'
@@ -58,6 +61,11 @@ export const enum FileType {
DIR = 'dir',
}

export type WriteEntry = {
path: string
data: string | ArrayBuffer | Blob | ReadableStream
}

function mapFileType(fileType: FsFileType) {
switch (fileType) {
case FsFileType.DIRECTORY:
@@ -233,22 +241,75 @@ export class Filesystem {
path: string,
data: string | ArrayBuffer | Blob | ReadableStream,
opts?: FilesystemRequestOpts
): Promise<EntryInfo> {
const blob = await new Response(data).blob()
): Promise<EntryInfo>
async write(
files: WriteEntry[],
opts?: FilesystemRequestOpts
): Promise<EntryInfo[]>
async write(
pathOrFiles: string | WriteEntry[],
dataOrOpts?:
| string
| ArrayBuffer
| Blob
| ReadableStream
| FilesystemRequestOpts,
opts?: FilesystemRequestOpts
): Promise<EntryInfo | EntryInfo[]> {
if (typeof pathOrFiles !== 'string' && !Array.isArray(pathOrFiles)) {
throw new Error('Path or files are required')
}

if (typeof pathOrFiles === 'string' && Array.isArray(dataOrOpts)) {
throw new Error(
'Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files.'
)
}

const { path, writeOpts, writeFiles } =
typeof pathOrFiles === 'string'
? {
path: pathOrFiles,
writeOpts: opts as FilesystemRequestOpts,
writeFiles: [
{
data: dataOrOpts as
| string
| ArrayBuffer
| Blob
| ReadableStream,
},
],
}
: {
path: undefined,
writeOpts: dataOrOpts as FilesystemRequestOpts,
writeFiles: pathOrFiles as WriteEntry[],
}

if (writeFiles.length === 0) return [] as EntryInfo[]

const blobs = await Promise.all(
writeFiles.map((f) => new Response(f.data).blob())
)

const res = await this.envdApi.api.POST('/files', {
params: {
query: {
path,
username: opts?.user || defaultUsername,
username: writeOpts?.user || defaultUsername,
},
},
bodySerializer() {
const fd = new FormData()

fd.append('file', blob)

return fd
return blobs.reduce((fd, blob, i) => {
// Important: RFC 7578, Section 4.2 requires that if a filename is provided,
// the directory path information must not be used.
// BUT in our case we need to use the directory path information with a custom
// muktipart part name getter in envd.
fd.append('file', blob, writeFiles[i].path)

return fd
}, new FormData())
},
body: {},
headers: {
@@ -262,12 +323,12 @@ export class Filesystem {
throw err
}

const files = res.data
if (!files || files.length === 0) {
const files = res.data as EntryInfo[]
if (!files) {
throw new Error('Expected to receive information about written file')
}

return files[0] as EntryInfo
return files.length === 1 && path ? files[0] : files
}

/**
@@ -441,12 +502,19 @@ export class Filesystem {
async watchDir(
path: string,
onEvent: (event: FilesystemEvent) => void | Promise<void>,
opts?: WatchOpts
opts?: WatchOpts & {
timeout?: number
onExit?: (err?: Error) => void | Promise<void>
}
): Promise<WatchHandle> {
if (opts?.recursive && this.envdApi.version && compareVersions(this.envdApi.version, ENVD_VERSION_RECURSIVE_WATCH) < 0) {
if (
opts?.recursive &&
this.envdApi.version &&
compareVersions(this.envdApi.version, ENVD_VERSION_RECURSIVE_WATCH) < 0
) {
throw new TemplateError(
'You need to update the template to use recursive watching. ' +
'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.'
)
}

@@ -457,8 +525,8 @@ export class Filesystem {

const reqTimeout = requestTimeoutMs
? setTimeout(() => {
controller.abort()
}, requestTimeoutMs)
controller.abort()
}, requestTimeoutMs)
: undefined

const events = this.rpc.watchDir(
4 changes: 2 additions & 2 deletions packages/js-sdk/tests/sandbox/closed_port.test.ts
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ test.skipIf(isDebug)('closed port in SDK', async () => {

let res2 = await fetch(`${isDebug ? 'http' : 'https'}://${goodHost}`)

for (let i = 0; i < 10; i++) {
for (let i = 0; i < 20; i++) {
if (res2.status === 200) {
break
}
@@ -62,7 +62,7 @@ test.skipIf(isDebug)('closed port in browser ', async () => {

let res2 = await fetch(`${isDebug ? 'http' : 'https'}://${goodHost}`)

for (let i = 0; i < 10; i++) {
for (let i = 0; i < 20; i++) {
if (res2.status === 200) {
break
}
117 changes: 115 additions & 2 deletions packages/js-sdk/tests/sandbox/files/write.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,125 @@
import { assert } from 'vitest'
import path from 'path'
import { assert, onTestFinished } from 'vitest'

import { sandboxTest } from '../../setup.js'
import { WriteEntry } from '../../../src/sandbox/filesystem'
import { isDebug, sandboxTest } from '../../setup.js'

sandboxTest('write file', async ({ sandbox }) => {
const filename = 'test_write.txt'
const content = 'This is a test file.'

// Attempt to write with undefined path and content
await sandbox.files
// @ts-ignore
.write(undefined, content)
.then((e) => {
assert.isUndefined(e)
})
.catch((err) => {
assert.instanceOf(err, Error)
assert.include(err.message, 'Path or files are required')
})

const info = await sandbox.files.write(filename, content)
assert.isFalse(Array.isArray(info))
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)
})

sandboxTest('write multiple files', async ({ sandbox }) => {
// Attempt to write with empty files array
const emptyInfo = await sandbox.files.write([])
assert.isTrue(Array.isArray(emptyInfo))
assert.equal(emptyInfo.length, 0)

// Attempt to write with undefined path and file array
await sandbox.files
// @ts-ignore
.write(undefined, [{ path: 'one_test_file.txt', data: 'This is a test file.' }])
.then((e) => {
assert.isUndefined(e)
})
.catch((err) => {
assert.instanceOf(err, Error)
assert.include(err.message, 'Path or files are required')
})

// Attempt to write with path and file array
await sandbox.files
// @ts-ignore
.write('/path/to/file', [{ path: 'one_test_file.txt', data: 'This is a test file.' }])
.then((e) => {
assert.isUndefined(e)
})
.catch((err) => {
assert.instanceOf(err, Error)
assert.include(
err.message,
'Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files.'
)
})

// Attempt to write with one file in array
const info = await sandbox.files.write([{ path: 'one_test_file.txt', data: 'This is a test file.' }])
assert.isTrue(Array.isArray(info))
assert.equal(info[0].name, 'one_test_file.txt')
assert.equal(info[0].type, 'file')
assert.equal(info[0].path, '/home/user/one_test_file.txt')

// Attempt to write with multiple files in array
const files: WriteEntry[] = []

for (let i = 0; i < 10; i++) {
let path = ''
if (i % 2 == 0) {
path = `/${i}/multi_test_file${i}.txt`
} else {
path = `/home/user/multi_test_file${i}.txt`
}

if (isDebug) {
onTestFinished(async () => await sandbox.files.remove(path))
}

files.push({
path: path,
data: `This is a test file ${i}.`,
})
}

const infos = await sandbox.files.write(files)

assert.isTrue(Array.isArray(infos))
assert.equal(infos.length, files.length)

// Attempt to write with multiple files in array
for (let i = 0; i < files.length; i++) {
const file = files[i]
const info = infos[i]

assert.equal(info.name, path.basename(file.path))
assert.equal(info.path, file.path)
assert.equal(info.type, 'file')

const exists = await sandbox.files.exists(file.path)
assert.isTrue(exists)
const readContent = await sandbox.files.read(file.path)
assert.equal(readContent, file.data)
}
})

sandboxTest('write file', async ({ sandbox }) => {
const filename = 'test_write.txt'
const content = 'This is a test file.'

const info = await sandbox.files.write(filename, content)
assert.isFalse(Array.isArray(info))
assert.equal(info.name, filename)
assert.equal(info.type, 'file')
assert.equal(info.path, `/home/user/${filename}`)
2 changes: 1 addition & 1 deletion packages/js-sdk/tests/sandbox/host.test.ts
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ sandboxTest('ping server in sandbox', async ({ sandbox }) => {

let res = await fetch(`${isDebug ? 'http' : 'https'}://${host}`)

for (let i = 0; i < 10; i++) {
for (let i = 0; i < 20; i++) {
if (res.status === 200) {
break
}
8 changes: 4 additions & 4 deletions packages/python-sdk/e2b/exceptions.py
Original file line number Diff line number Diff line change
@@ -30,10 +30,10 @@ class TimeoutException(SandboxException):
"""
Raised when a timeout occurs.
The [unavailable] exception type is caused by sandbox timeout.\n
The [canceled] exception type is caused by exceeding request timeout.\n
The [deadline_exceeded] exception type is caused by exceeding the timeout for process, watch, etc.\n
The [unknown] exception type is sometimes caused by the sandbox timeout when the request is not processed correctly.\n
The `unavailable` exception type is caused by sandbox timeout.\n
The `canceled` exception type is caused by exceeding request timeout.\n
The `deadline_exceeded` exception type is caused by exceeding the timeout for process, watch, etc.\n
The `unknown` exception type is sometimes caused by the sandbox timeout when the request is not processed correctly.\n
"""

pass
14 changes: 12 additions & 2 deletions packages/python-sdk/e2b/sandbox/filesystem/filesystem.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import Enum
from dataclasses import dataclass
from typing import Optional
from enum import Enum
from typing import IO, Optional, Union

from e2b.envd.filesystem import filesystem_pb2

@@ -45,3 +45,13 @@ class EntryInfo:
"""
Path to the filesystem object.
"""


dataclass
class WriteEntry:
"""
Contains path and data of the file to be written to the filesystem.
"""

path: str
data: Union[str, bytes, IO]
80 changes: 71 additions & 9 deletions packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,8 @@
import httpx
from io import TextIOBase
from packaging.version import Version
from typing import IO, AsyncIterator, List, Literal, Optional, Union, overload
from typing import AsyncIterator, IO, List, Literal, Optional, overload, Union
from e2b.sandbox.filesystem.filesystem import WriteEntry

import e2b_connect as connect
from e2b.connection_config import (
@@ -133,6 +134,7 @@ async def read(
elif format == "stream":
return r.aiter_bytes()

@overload
async def write(
self,
path: str,
@@ -156,27 +158,87 @@ async def write(
:return: Information about the written file
"""
if isinstance(data, TextIOBase):
data = data.read().encode()

@overload
async def write(
self,
files: List[WriteEntry],
user: Optional[Username] = "user",
request_timeout: Optional[float] = None,
) -> List[EntryInfo]:
"""
Writes multiple files.
:param files: list of files to write
:param user: Run the operation as this user
:param request_timeout: Timeout for the request
:return: Information about the written files
"""

async def write(
self,
path_or_files: Union[str, List[WriteEntry]],
data_or_user: Union[str, bytes, IO, Username] = "user",
user_or_request_timeout: Optional[Union[float, Username]] = None,
request_timeout_or_none: Optional[float] = None
) -> Union[EntryInfo, List[EntryInfo]]:
"""
Writes content to a file on the path.
When writing to a file that doesn't exist, the file will get created.
When writing to a file that already exists, the file will get overwritten.
When writing to a file that's in a directory that doesn't exist, you'll get an error.
"""
path, write_files, user, request_timeout = None, [], "user", None
if isinstance(path_or_files, str):
if isinstance(data_or_user, list):
raise Exception("Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files.")
path, write_files, user, request_timeout = \
path_or_files, [{"path": path_or_files, "data": data_or_user}], user_or_request_timeout or "user", request_timeout_or_none
else:
if path_or_files is None:
raise Exception("Path or files are required")
path, write_files, user, request_timeout = \
None, path_or_files, data_or_user, user_or_request_timeout

# Prepare the files for the multipart/form-data request
httpx_files = []
for file in write_files:
file_path, file_data = file['path'], file['data']
if isinstance(file_data, str) or isinstance(file_data, bytes):
httpx_files.append(('file', (file_path, file_data)))
elif isinstance(file_data, TextIOBase):
httpx_files.append(('file', (file_path, file_data.read())))
else:
raise ValueError(f"Unsupported data type for file {file_path}")

# Allow passing empty list of files
if len(httpx_files) == 0: return []

params = {"username": user}
if path is not None: params["path"] = path

r = await self._envd_api.post(
ENVD_API_FILES_ROUTE,
files={"file": data},
params={"path": path, "username": user},
files=httpx_files,
params=params,
timeout=self._connection_config.get_request_timeout(request_timeout),
)

err = await ahandle_envd_api_exception(r)
if err:
raise err

files = r.json()
write_files = r.json()

if not isinstance(files, list) or len(files) == 0:
if not isinstance(write_files, list) or len(write_files) == 0:
raise Exception("Expected to receive information about written file")

file = files[0]
return EntryInfo(**file)
if len(write_files) == 1 and path:
file = write_files[0]
return EntryInfo(**file)
else:
return [EntryInfo(**file) for file in write_files]


async def list(
self,
76 changes: 67 additions & 9 deletions packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from io import TextIOBase
from typing import IO, Iterator, List, Literal, Optional, Union, overload
from typing import IO, Iterator, List, Literal, Optional, overload, Union
from e2b.sandbox.filesystem.filesystem import WriteEntry

import e2b_connect
import httpcore
@@ -132,6 +133,7 @@ def read(
elif format == "stream":
return r.iter_bytes()

@overload
def write(
self,
path: str,
@@ -155,27 +157,83 @@ def write(
:return: Information about the written file
"""
if isinstance(data, TextIOBase):
data = data.read().encode()

@overload
def write(
self,
files: List[WriteEntry],
user: Optional[Username] = "user",
request_timeout: Optional[float] = None,
) -> List[EntryInfo]:
"""
Writes a list of files to the filesystem.
When writing to a file that doesn't exist, the file will get created.
When writing to a file that already exists, the file will get overwritten.
When writing to a file that's in a directory that doesn't exist, you'll get an error.
:param files: list of files to write
:param user: Run the operation as this user
:param request_timeout: Timeout for the request
:return: Information about the written files
"""

def write(
self,
path_or_files: Union[str, List[WriteEntry]],
data_or_user: Union[str, bytes, IO, Username] = "user",
user_or_request_timeout: Optional[Union[float, Username]] = None,
request_timeout_or_none: Optional[float] = None
) -> Union[EntryInfo, List[EntryInfo]]:
path, write_files, user, request_timeout = None, [], "user", None
if isinstance(path_or_files, str):
if isinstance(data_or_user, list):
raise Exception("Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files.")
path, write_files, user, request_timeout = \
path_or_files, [{"path": path_or_files, "data": data_or_user}], user_or_request_timeout or "user", request_timeout_or_none
else:
if path_or_files is None:
raise Exception("Path or files are required")
path, write_files, user, request_timeout = \
None, path_or_files, data_or_user, user_or_request_timeout

# Prepare the files for the multipart/form-data request
httpx_files = []
for file in write_files:
file_path, file_data = file['path'], file['data']
if isinstance(file_data, str) or isinstance(file_data, bytes):
httpx_files.append(('file', (file_path, file_data)))
elif isinstance(file_data, TextIOBase):
httpx_files.append(('file', (file_path, file_data.read())))
else:
raise ValueError(f"Unsupported data type for file {file_path}")

# Allow passing empty list of files
if len(httpx_files) == 0: return []

params = {"username": user}
if path is not None: params["path"] = path

r = self._envd_api.post(
ENVD_API_FILES_ROUTE,
files={"file": data},
params={"path": path, "username": user},
files=httpx_files,
params=params,
timeout=self._connection_config.get_request_timeout(request_timeout),
)

err = handle_envd_api_exception(r)
if err:
raise err

files = r.json()
write_files = r.json()

if not isinstance(files, list) or len(files) == 0:
if not isinstance(write_files, list) or len(write_files) == 0:
raise Exception("Expected to receive information about written file")

file = files[0]
return EntryInfo(**file)
if len(write_files) == 1 and path:
file = write_files[0]
return EntryInfo(**file)
else:
return [EntryInfo(**file) for file in write_files]

def list(
self,
2 changes: 1 addition & 1 deletion packages/python-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@e2b/python-sdk",
"private": true,
"version": "1.1.0",
"version": "1.2.0",
"scripts": {
"example": "poetry run python example.py",
"test": "poetry run pytest -n 4 --verbose -x",
2 changes: 1 addition & 1 deletion packages/python-sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "e2b"
version = "1.1.0"
version = "1.2.0"
description = "E2B SDK that give agents cloud environments"
authors = ["e2b <hello@e2b.dev>"]
license = "MIT"
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from e2b import AsyncSandbox

from e2b.sandbox_async.filesystem.filesystem import EntryInfo

async def test_write_file(async_sandbox: AsyncSandbox):
filename = "test_write.txt"
content = "This is a test file."

# Attempt to write without path
try:
await async_sandbox.files.write(None, content)
except Exception as e:
assert "Path or files are required" in str(e)

info = await async_sandbox.files.write(filename, content)
assert info.path == f"/home/user/{filename}"

@@ -14,6 +20,55 @@ async def test_write_file(async_sandbox: AsyncSandbox):
read_content = await async_sandbox.files.read(filename)
assert read_content == content

async def test_write_multiple_files(async_sandbox: AsyncSandbox):
# Attempt to write with empty files array
empty_info = await async_sandbox.files.write([])
assert isinstance(empty_info, list)
assert len(empty_info) == 0

# Attempt to write with None path and empty files array
try:
await async_sandbox.files.write(None, [])
except Exception as e:
assert "Path or files are required" in str(e)

# Attempt to write with path and file array
try:
await async_sandbox.files.write("/path/to/file", [{ "path": "one_test_file.txt", "data": "This is a test file." }])
except Exception as e:
assert "Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files." in str(e)

# Attempt to write with one file in array
info = await async_sandbox.files.write([{ "path": "one_test_file.txt", "data": "This is a test file." }])
assert isinstance(info, list)
assert len(info) == 1
info = info[0]
assert isinstance(info, EntryInfo)
assert info.path == "/home/user/one_test_file.txt"
exists = await async_sandbox.files.exists(info.path)
assert exists

read_content = await async_sandbox.files.read(info.path)
assert read_content == "This is a test file."

# Attempt to write with multiple files in array
files = []
for i in range(10):
path = f"test_write_{i}.txt"
content = f"This is a test file {i}."
files.append({"path": path, "data": content})

infos = await async_sandbox.files.write(files)
assert isinstance(infos, list)
assert len(infos) == len(files)
for i, info in enumerate(infos):
assert isinstance(info, EntryInfo)
assert info.path == f"/home/user/test_write_{i}.txt"
exists = await async_sandbox.files.exists(path)
assert exists

read_content = await async_sandbox.files.read(info.path)
assert read_content == files[i]["data"]

async def test_overwrite_file(async_sandbox: AsyncSandbox):
filename = "test_overwrite.txt"
2 changes: 1 addition & 1 deletion packages/python-sdk/tests/async/sandbox_async/test_host.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ async def test_ping_server(async_sandbox: AsyncSandbox, debug):

status_code = None
async with httpx.AsyncClient() as client:
for _ in range(5):
for _ in range(20):
res = await client.get(f"{'http' if debug else 'https'}://{host}")
status_code = res.status_code
if res.status_code == 200:
Original file line number Diff line number Diff line change
@@ -9,19 +9,19 @@ async def test_port_closed(template):
sbx = await AsyncSandbox.create(template, timeout=60)
try:
assert await sbx.is_running()
good_port = 8000
# Start a Python HTTP server on port 8000

good_port = 8002
# Start a Python HTTP server on port 8002
await sbx.commands.run(
f"python -m http.server {good_port}",
background=True,
)
await asyncio.sleep(1) # Wait for server to start
# Test good port (8000)

# Test good port (8002)
good_host = sbx.get_host(good_port)
async with httpx.AsyncClient() as client:
for _ in range(10):
for _ in range(20):
try:
response = await client.get(f"https://{good_host}")
if response.status_code == 200:
@@ -35,7 +35,7 @@ async def test_port_closed(template):
bad_port = 3000
bad_host = sbx.get_host(bad_port)
async with httpx.AsyncClient() as client:
for _ in range(10):
for _ in range(20):
try:
response = await client.get(f"https://{bad_host}")
if response.status_code == 502:
Original file line number Diff line number Diff line change
@@ -8,20 +8,15 @@ def test_watch_directory_changes(sandbox: Sandbox):
filename = "test_watch.txt"
content = "This file will be watched."

sandbox.files.remove(dirname)
sandbox.files.make_dir(dirname)
sandbox.files.write(f"{dirname}/{filename}", content)

handle = sandbox.files.watch_dir(dirname)
sandbox.files.write(f"{dirname}/{filename}", content)

events = handle.get_new_events()
assert len(events) == 3
assert events[0].type == FilesystemEventType.CREATE
assert events[0].type == FilesystemEventType.WRITE
assert events[0].name == filename
assert events[1].type == FilesystemEventType.CHMOD
assert events[1].name == filename
assert events[2].type == FilesystemEventType.WRITE
assert events[2].name == filename

handle.stop()

57 changes: 57 additions & 0 deletions packages/python-sdk/tests/sync/sandbox_sync/files/test_write.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from e2b.sandbox.filesystem.filesystem import EntryInfo

def test_write_file(sandbox):
filename = "test_write.txt"
content = "This is a test file."

# Attempt to write without path
try:
sandbox.files.write(None, content)
except Exception as e:
assert "Path or files are required" in str(e)

info = sandbox.files.write(filename, content)
assert info.path == f"/home/user/{filename}"

@@ -11,6 +19,55 @@ def test_write_file(sandbox):
read_content = sandbox.files.read(filename)
assert read_content == content

def test_write_multiple_files(sandbox):
# Attempt to write with empty files array
empty_info = sandbox.files.write([])
assert isinstance(empty_info, list)
assert len(empty_info) == 0

# Attempt to write with None path and empty files array
try:
sandbox.files.write(None, [])
except Exception as e:
assert "Path or files are required" in str(e)

# Attempt to write with path and file array
try:
sandbox.files.write("/path/to/file", [{ "path": "one_test_file.txt", "data": "This is a test file." }])
except Exception as e:
assert "Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files." in str(e)

# Attempt to write with one file in array
info = sandbox.files.write([{ "path": "one_test_file.txt", "data": "This is a test file." }])
assert isinstance(info, list)
assert len(info) == 1
info = info[0]
assert isinstance(info, EntryInfo)
assert info.path == "/home/user/one_test_file.txt"
exists = sandbox.files.exists(info.path)
assert exists

read_content = sandbox.files.read(info.path)
assert read_content == "This is a test file."

# Attempt to write with multiple files in array
files = []
for i in range(10):
path = f"test_write_{i}.txt"
content = f"This is a test file {i}."
files.append({"path": path, "data": content})

infos = sandbox.files.write(files)
assert isinstance(infos, list)
assert len(infos) == len(files)
for i, info in enumerate(infos):
assert isinstance(info, EntryInfo)
assert info.path == f"/home/user/test_write_{i}.txt"
exists = sandbox.files.exists(path)
assert exists

read_content = sandbox.files.read(info.path)
assert read_content == files[i]["data"]

def test_overwrite_file(sandbox):
filename = "test_overwrite.txt"
2 changes: 1 addition & 1 deletion packages/python-sdk/tests/sync/sandbox_sync/test_host.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ def test_ping_server(sandbox, debug):
try:
host = sandbox.get_host(8001)
status_code = None
for _ in range(5):
for _ in range(20):
res = httpx.get(f"{'http' if debug else 'https'}://{host}")
status_code = res.status_code
if res.status_code == 200:
10 changes: 5 additions & 5 deletions packages/python-sdk/tests/sync/sandbox_sync/test_port_closed.py
Original file line number Diff line number Diff line change
@@ -9,16 +9,16 @@ def test_port_closed(template):
sbx = Sandbox(template, timeout=60)
try:
assert sbx.is_running()
good_port = 8000
# Start a Python HTTP server on port 8000

good_port = 8004
# Start a Python HTTP server on port 8004
sbx.commands.run(
f"python -m http.server {good_port}",
background=True,
)
time.sleep(1) # Wait for server to start
# Test good port (8000)

# Test good port (8004)
good_host = sbx.get_host(good_port)
with httpx.Client() as client:
for _ in range(10):
24 changes: 12 additions & 12 deletions pnpm-lock.yaml