Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,9 @@ protected SandboxSnapshotSpec snapshotSpec() {
protected WorkspaceSpec workspaceSpec() {
return defaultWorkspaceSpec;
}

@Override
public E2bSandboxFilesystem createFilesystem() {
return new E2bSandboxFilesystem();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package io.agentscope.extensions.sandbox.e2b;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.harness.agent.sandbox.AbstractBaseSandbox;
import io.agentscope.harness.agent.sandbox.ExecResult;
Expand All @@ -25,7 +24,7 @@
import io.agentscope.harness.agent.sandbox.WorkspaceMountSupport;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Base64;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -42,7 +41,6 @@ public class E2bSandbox extends AbstractBaseSandbox {
private static final Logger log = LoggerFactory.getLogger(E2bSandbox.class);

private static final int TAR_TIMEOUT_SECONDS = 300;
private static final int B64_CHUNK = 4000;

private final E2bSandboxState e2bState;
private final E2bSandboxClientOptions opt;
Expand Down Expand Up @@ -97,17 +95,42 @@ protected InputStream doPersistWorkspace() throws Exception {
return new ByteArrayInputStream(E2bSnapshotRefs.encodeSnapshotId(id));
}
String root = e2bState.getWorkspaceRoot();
String tarPath = "/tmp/agentscope-ws-" + UUID.randomUUID() + ".tar";
StringBuilder script = new StringBuilder("tar ");
for (String ex :
WorkspaceMountSupport.tarExcludeArgsForBindMounts(e2bState.getWorkspaceSpec())) {
script.append(ex).append(' ');
}
script.append("-cf - -C ").append(shellSingleQuote(root)).append(" .");
String cmd = script.toString();
byte[] tar = envd().runShellBinaryStdout(e2bState, root, cmd, TAR_TIMEOUT_SECONDS);
script.append("-cf ")
.append(tarPath)
.append(" -C ")
.append(shellSingleQuote(root))
.append(" .");
envd().runShell(e2bState, root, script.toString(), TAR_TIMEOUT_SECONDS);
byte[] tar = downloadFile(tarPath);
return new ByteArrayInputStream(tar);
}

/**
* Upload a file to the sandbox filesystem via the E2B Filesystem REST API.
*
* @param remotePath absolute path in the sandbox
* @param data file content
*/
public void uploadFile(String remotePath, byte[] data) throws Exception {
envd().uploadFile(e2bState, remotePath, data);
}

/**
* Download a file from the sandbox filesystem via the E2B Filesystem REST API.
*
* @param remotePath absolute path in the sandbox
* @return file content
*/
public byte[] downloadFile(String remotePath) throws Exception {
return envd().downloadFile(e2bState, remotePath);
}

@Override
protected void doHydrateWorkspace(InputStream archive) throws Exception {
byte[] all = archive.readAllBytes();
Expand All @@ -116,29 +139,16 @@ protected void doHydrateWorkspace(InputStream archive) throws Exception {
restoreSandboxFromSnapshotTemplate(nativeId);
return;
}
String tarPath = "/tmp/agentscope-ws-" + UUID.randomUUID() + ".tar";
envd().uploadFile(e2bState, tarPath, all);
String root = e2bState.getWorkspaceRoot();
String b64 = Base64.getEncoder().encodeToString(all);
envd().runShell(e2bState, root, "rm -f /tmp/agentscope-ws.b64", 30);
ObjectMapper om = new ObjectMapper();
for (int i = 0; i < b64.length(); i += B64_CHUNK) {
String chunk = b64.substring(i, Math.min(b64.length(), i + B64_CHUNK));
String lit = om.writeValueAsString(chunk);
String py =
"import pathlib; pathlib.Path('/tmp/agentscope-ws.b64').open('a').write("
+ lit
+ ")";
envd().runShell(e2bState, root, "python3 -c " + shellSingleQuote(py), 120);
StringBuilder script = new StringBuilder("tar ");
for (String ex :
WorkspaceMountSupport.tarExcludeArgsForBindMounts(e2bState.getWorkspaceSpec())) {
script.append(ex).append(' ');
}
String pyFin =
"import base64,pathlib,subprocess; d="
+ om.writeValueAsString(root)
+ "; raw=base64.standard_b64decode(pathlib.Path('/tmp/agentscope-ws.b64').read_text());"
+ " subprocess.run(['tar','xf','-','-C',d],input=raw,check=True)";
envd().runShell(
e2bState,
root,
"python3 -c " + shellSingleQuote(pyFin),
TAR_TIMEOUT_SECONDS);
script.append("xf ").append(tarPath).append(" -C ").append(shellSingleQuote(root));
envd().runShell(e2bState, root, script.toString(), TAR_TIMEOUT_SECONDS);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.agentscope.extensions.sandbox.e2b;

import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.harness.agent.filesystem.model.FileDownloadResponse;
import io.agentscope.harness.agent.filesystem.model.FileUploadResponse;
import io.agentscope.harness.agent.filesystem.sandbox.SandboxBackedFilesystem;
import io.agentscope.harness.agent.sandbox.Sandbox;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* An E2B-optimized filesystem that uses the Filesystem REST API for upload/download, avoiding
* base64 overhead and the 512 KB output truncation of shell-based transfers.
*/
public class E2bSandboxFilesystem extends SandboxBackedFilesystem {

private static final Logger log = LoggerFactory.getLogger(E2bSandboxFilesystem.class);

@Override
public List<FileUploadResponse> uploadFiles(
RuntimeContext runtimeContext, List<Map.Entry<String, byte[]>> files) {
Sandbox s = getSandbox();
if (!(s instanceof E2bSandbox e2b)) {
return super.uploadFiles(runtimeContext, files);
}
List<FileUploadResponse> results = new ArrayList<>(files.size());
for (Map.Entry<String, byte[]> file : files) {
String path = file.getKey();
try {
e2b.uploadFile(path, file.getValue());
results.add(FileUploadResponse.success(path));
} catch (Exception e) {
log.warn("[e2b-sandbox-fs] upload failed for path: {}", path, e);
results.add(FileUploadResponse.fail(path, e.getMessage()));
}
}
return results;
}

@Override
public List<FileDownloadResponse> downloadFiles(
RuntimeContext runtimeContext, List<String> paths) {
Sandbox s = getSandbox();
if (!(s instanceof E2bSandbox e2b)) {
return super.downloadFiles(runtimeContext, paths);
}
List<FileDownloadResponse> results = new ArrayList<>(paths.size());
for (String path : paths) {
try {
byte[] content = e2b.downloadFile(path);
results.add(FileDownloadResponse.success(path, content));
} catch (Exception e) {
log.warn("[e2b-sandbox-fs] download failed for path: {}", path, e);
results.add(FileDownloadResponse.fail(path, e.getMessage()));
}
}
return results;
}
}
Loading
Loading