Skip to content

Commit 4aa110c

Browse files
Merge pull request #132 from xticriss/fix/zip-download-sandbox-provider
Fix: ZIP download fails with 'No active sandbox' error for E2B provider
2 parents 9b662c9 + ed570d6 commit 4aa110c

File tree

3 files changed

+278
-55
lines changed

3 files changed

+278
-55
lines changed

.test

Whitespace-only changes.

app/api/create-zip/route.ts

Lines changed: 277 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,294 @@ import { NextResponse } from 'next/server';
22

33
declare global {
44
var activeSandbox: any;
5+
var activeSandboxProvider: any;
56
}
67

78
export async function POST() {
89
try {
9-
if (!global.activeSandbox) {
10-
return NextResponse.json({
11-
success: false,
12-
error: 'No active sandbox'
10+
// Check both V2 provider (new) and V1 sandbox (legacy) patterns
11+
const provider = global.activeSandboxProvider;
12+
const sandbox = global.activeSandbox;
13+
14+
if (!provider && !sandbox) {
15+
return NextResponse.json({
16+
success: false,
17+
error: 'No active sandbox'
1318
}, { status: 400 });
1419
}
15-
20+
1621
console.log('[create-zip] Creating project zip...');
17-
18-
// Create zip file in sandbox using standard commands
19-
const zipResult = await global.activeSandbox.runCommand({
20-
cmd: 'bash',
21-
args: ['-c', `zip -r /tmp/project.zip . -x "node_modules/*" ".git/*" ".next/*" "dist/*" "build/*" "*.log"`]
22-
});
23-
24-
if (zipResult.exitCode !== 0) {
25-
const error = await zipResult.stderr();
26-
throw new Error(`Failed to create zip: ${error}`);
27-
}
28-
29-
const sizeResult = await global.activeSandbox.runCommand({
30-
cmd: 'bash',
31-
args: ['-c', `ls -la /tmp/project.zip | awk '{print $5}'`]
32-
});
33-
34-
const fileSize = await sizeResult.stdout();
35-
console.log(`[create-zip] Created project.zip (${fileSize.trim()} bytes)`);
36-
37-
// Read the zip file and convert to base64
38-
const readResult = await global.activeSandbox.runCommand({
39-
cmd: 'base64',
40-
args: ['/tmp/project.zip']
41-
});
42-
43-
if (readResult.exitCode !== 0) {
44-
const error = await readResult.stderr();
45-
throw new Error(`Failed to read zip file: ${error}`);
22+
23+
// Detect provider type
24+
const isE2B = provider && provider.constructor.name === 'E2BProvider';
25+
const isVercel = provider && provider.constructor.name === 'VercelProvider';
26+
const isV1Sandbox = !provider && sandbox;
27+
28+
console.log('[create-zip] Provider type:', { isE2B, isVercel, isV1Sandbox });
29+
30+
if (isE2B && provider.sandbox) {
31+
// E2B Provider - use Python code execution to avoid command parsing issues
32+
try {
33+
console.log('[create-zip] Using E2B Python-based zip creation');
34+
35+
// Create zip using Python's zipfile module
36+
const zipCreationResult = await provider.sandbox.runCode(`
37+
import zipfile
38+
import os
39+
import base64
40+
from pathlib import Path
41+
42+
# Change to app directory
43+
os.chdir('/home/user/app')
44+
45+
# Create zip file
46+
zip_path = '/tmp/project.zip'
47+
exclude_patterns = ['node_modules', '.git', '.next', 'dist', 'build', '*.log', '__pycache__', '*.pyc']
48+
49+
def should_exclude(path):
50+
path_str = str(path)
51+
for pattern in exclude_patterns:
52+
if pattern in path_str:
53+
return True
54+
if pattern.startswith('*') and path_str.endswith(pattern[1:]):
55+
return True
56+
return False
57+
58+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
59+
for root, dirs, files in os.walk('.'):
60+
# Filter out excluded directories
61+
dirs[:] = [d for d in dirs if not should_exclude(d)]
62+
63+
for file in files:
64+
file_path = os.path.join(root, file)
65+
if not should_exclude(file_path):
66+
# Add file to zip with relative path
67+
arcname = os.path.relpath(file_path, '.')
68+
zipf.write(file_path, arcname)
69+
70+
# Get file size
71+
file_size = os.path.getsize(zip_path)
72+
print(f"ZIP_SIZE:{file_size}")
73+
74+
# Read and encode to base64
75+
with open(zip_path, 'rb') as f:
76+
zip_content = f.read()
77+
base64_content = base64.b64encode(zip_content).decode('utf-8')
78+
print(f"BASE64_START:{base64_content}:BASE64_END")
79+
`);
80+
81+
// Parse the output to extract base64 content
82+
const output = zipCreationResult.logs.stdout.join('\n');
83+
84+
// Extract file size
85+
const sizeMatch = output.match(/ZIP_SIZE:(\d+)/);
86+
const fileSize = sizeMatch ? sizeMatch[1] : 'unknown';
87+
console.log(`[create-zip] Created project.zip (${fileSize} bytes)`);
88+
89+
// Extract base64 content (using [\s\S] instead of 's' flag for compatibility)
90+
const base64Match = output.match(/BASE64_START:([\s\S]*?):BASE64_END/);
91+
if (!base64Match) {
92+
throw new Error('Failed to extract base64 content from Python output');
93+
}
94+
95+
const base64Content = base64Match[1].trim();
96+
97+
// Create a data URL for download
98+
const dataUrl = `data:application/zip;base64,${base64Content}`;
99+
100+
return NextResponse.json({
101+
success: true,
102+
dataUrl,
103+
fileName: 'e2b-sandbox-project.zip',
104+
message: 'Zip file created successfully'
105+
});
106+
107+
} catch (error) {
108+
console.error('[create-zip] E2B Provider error:', error);
109+
throw error;
110+
}
111+
112+
} else if (isVercel && provider) {
113+
// Vercel Provider - use correct working directory
114+
try {
115+
console.log('[create-zip] Using Vercel Provider with /vercel/sandbox path');
116+
117+
// Install zip utility using dnf package manager with sudo
118+
console.log('[create-zip] Installing zip utility...');
119+
const installResult = await provider.sandbox.runCommand({
120+
cmd: 'dnf',
121+
args: ['install', '-y', 'zip'],
122+
sudo: true
123+
});
124+
125+
// Create zip file
126+
const zipResult = await provider.sandbox.runCommand({
127+
cmd: 'zip',
128+
args: ['-r', '/tmp/project.zip', '.', '-x', 'node_modules/*', '.git/*', '.next/*', 'dist/*', 'build/*', '*.log'],
129+
cwd: '/vercel/sandbox'
130+
});
131+
132+
// Handle stdout and stderr - they might be functions in Vercel SDK
133+
let stderr = '';
134+
try {
135+
if (typeof zipResult.stderr === 'function') {
136+
stderr = await zipResult.stderr();
137+
} else {
138+
stderr = zipResult.stderr || '';
139+
}
140+
} catch (e) {
141+
stderr = '';
142+
}
143+
144+
if (zipResult.exitCode !== 0) {
145+
throw new Error(`Failed to create zip: ${stderr}`);
146+
}
147+
148+
const sizeResult = await provider.sandbox.runCommand({
149+
cmd: 'sh',
150+
args: ['-c', 'ls -la /tmp/project.zip | awk \'{print $5}\'']
151+
});
152+
153+
let fileSize = '';
154+
try {
155+
if (typeof sizeResult.stdout === 'function') {
156+
fileSize = (await sizeResult.stdout()).trim();
157+
} else {
158+
fileSize = (sizeResult.stdout || '').trim();
159+
}
160+
} catch (e) {
161+
fileSize = 'unknown';
162+
}
163+
console.log(`[create-zip] Created project.zip (${fileSize} bytes)`);
164+
165+
// Read the zip file and convert to base64
166+
const readResult = await provider.sandbox.runCommand({
167+
cmd: 'base64',
168+
args: ['/tmp/project.zip']
169+
});
170+
171+
let readStderr = '';
172+
try {
173+
if (typeof readResult.stderr === 'function') {
174+
readStderr = await readResult.stderr();
175+
} else {
176+
readStderr = readResult.stderr || '';
177+
}
178+
} catch (e) {
179+
readStderr = '';
180+
}
181+
182+
if (readResult.exitCode !== 0) {
183+
throw new Error(`Failed to read zip file: ${readStderr}`);
184+
}
185+
186+
let base64Content = '';
187+
try {
188+
if (typeof readResult.stdout === 'function') {
189+
base64Content = (await readResult.stdout()).trim();
190+
} else {
191+
base64Content = (readResult.stdout || '').trim();
192+
}
193+
} catch (e) {
194+
throw new Error('Failed to get base64 content from command result');
195+
}
196+
197+
// Create a data URL for download
198+
const dataUrl = `data:application/zip;base64,${base64Content}`;
199+
200+
return NextResponse.json({
201+
success: true,
202+
dataUrl,
203+
fileName: 'vercel-sandbox-project.zip',
204+
message: 'Zip file created successfully'
205+
});
206+
207+
} catch (error) {
208+
console.error('[create-zip] Vercel Provider error:', error);
209+
throw error;
210+
}
211+
212+
} else if (isV1Sandbox) {
213+
// V1 Sandbox pattern - uses object with cmd/args (legacy)
214+
try {
215+
const zipResult = await sandbox.runCommand({
216+
cmd: 'bash',
217+
args: ['-c', `zip -r /tmp/project.zip . -x "node_modules/*" ".git/*" ".next/*" "dist/*" "build/*" "*.log"`]
218+
});
219+
220+
// Handle potential function-based stdout/stderr (Vercel SDK pattern)
221+
const exitCode = zipResult.exitCode;
222+
let stderr = '';
223+
224+
if (typeof zipResult.stderr === 'function') {
225+
stderr = await zipResult.stderr();
226+
} else {
227+
stderr = zipResult.stderr || '';
228+
}
229+
230+
if (exitCode !== 0) {
231+
throw new Error(`Failed to create zip: ${stderr}`);
232+
}
233+
234+
const sizeResult = await sandbox.runCommand({
235+
cmd: 'bash',
236+
args: ['-c', `ls -la /tmp/project.zip | awk '{print $5}'`]
237+
});
238+
239+
let fileSize = '';
240+
if (typeof sizeResult.stdout === 'function') {
241+
fileSize = await sizeResult.stdout();
242+
} else {
243+
fileSize = sizeResult.stdout || '';
244+
}
245+
console.log(`[create-zip] Created project.zip (${fileSize.trim()} bytes)`);
246+
247+
// Read the zip file and convert to base64
248+
const readResult = await sandbox.runCommand({
249+
cmd: 'base64',
250+
args: ['/tmp/project.zip']
251+
});
252+
253+
if (readResult.exitCode !== 0) {
254+
let error = '';
255+
if (typeof readResult.stderr === 'function') {
256+
error = await readResult.stderr();
257+
} else {
258+
error = readResult.stderr || 'Unknown error';
259+
}
260+
throw new Error(`Failed to read zip file: ${error}`);
261+
}
262+
263+
let base64Content = '';
264+
if (typeof readResult.stdout === 'function') {
265+
base64Content = (await readResult.stdout()).trim();
266+
} else {
267+
base64Content = (readResult.stdout || '').trim();
268+
}
269+
270+
// Create a data URL for download
271+
const dataUrl = `data:application/zip;base64,${base64Content}`;
272+
273+
return NextResponse.json({
274+
success: true,
275+
dataUrl,
276+
fileName: 'vercel-sandbox-project.zip',
277+
message: 'Zip file created successfully'
278+
});
279+
280+
} catch (error) {
281+
console.error('[create-zip] V1 Sandbox error:', error);
282+
throw error;
283+
}
46284
}
47-
48-
const base64Content = (await readResult.stdout()).trim();
49-
50-
// Create a data URL for download
51-
const dataUrl = `data:application/zip;base64,${base64Content}`;
52-
53-
return NextResponse.json({
54-
success: true,
55-
dataUrl,
56-
fileName: 'vercel-sandbox-project.zip',
57-
message: 'Zip file created successfully'
58-
});
59-
285+
60286
} catch (error) {
61287
console.error('[create-zip] Error:', error);
62288
return NextResponse.json(
63-
{
64-
success: false,
65-
error: (error as Error).message
66-
},
289+
{
290+
success: false,
291+
error: (error as Error).message
292+
},
67293
{ status: 500 }
68294
);
69295
}

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
"dev": "next dev --turbopack",
88
"build": "next build",
99
"start": "next start",
10-
"lint": "next lint",
11-
"test:api": "node tests/api-endpoints.test.js",
12-
"test:code": "node tests/code-execution.test.js",
13-
"test:all": "npm run test:integration && npm run test:api && npm run test:code"
10+
"lint": "next lint"
1411
},
1512
"dependencies": {
1613
"@ai-sdk/anthropic": "^2.0.1",

0 commit comments

Comments
 (0)