Migrate to Vercel Sandbox using OIDC

This commit is contained in:
MFCo
2025-08-26 13:01:46 +02:00
parent cad43d5fca
commit 415179c234
20 changed files with 972 additions and 1621 deletions
+1
View File
@@ -56,3 +56,4 @@ e2b-template-*
*.temp *.temp
repomix-output.txt repomix-output.txt
bun.lockb bun.lockb
.env*.local
+1 -1
View File
@@ -18,7 +18,7 @@ npm install
2. **Add `.env.local`** 2. **Add `.env.local`**
```env ```env
# Required # Required
E2B_API_KEY=your_e2b_api_key # Get from https://e2b.dev (Sandboxes) VERCEL_TEAM_ID=your_team_id # Your Vercel team ID for sandbox access
FIRECRAWL_API_KEY=your_firecrawl_api_key # Get from https://firecrawl.dev (Web scraping) FIRECRAWL_API_KEY=your_firecrawl_api_key # Get from https://firecrawl.dev (Web scraping)
# Optional (need at least one AI provider) # Optional (need at least one AI provider)
+45 -34
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { Sandbox } from '@e2b/code-interpreter'; import { Sandbox } from '@vercel/sandbox';
import type { SandboxState } from '@/types/sandbox'; import type { SandboxState } from '@/types/sandbox';
import type { ConversationState } from '@/types/conversation'; import type { ConversationState } from '@/types/conversation';
@@ -525,7 +525,6 @@ export async function POST(request: NextRequest) {
normalizedPath = 'src/' + normalizedPath; normalizedPath = 'src/' + normalizedPath;
} }
const fullPath = `/home/user/app/${normalizedPath}`;
const isUpdate = global.existingFiles.has(normalizedPath); const isUpdate = global.existingFiles.has(normalizedPath);
// Remove any CSS imports from JSX/JS files (we're using Tailwind) // Remove any CSS imports from JSX/JS files (we're using Tailwind)
@@ -534,19 +533,20 @@ export async function POST(request: NextRequest) {
fileContent = fileContent.replace(/import\s+['"]\.\/[^'"]+\.css['"];?\s*\n?/g, ''); fileContent = fileContent.replace(/import\s+['"]\.\/[^'"]+\.css['"];?\s*\n?/g, '');
} }
// Write the file using Python (code-interpreter SDK) // Create directory if needed
const escapedContent = fileContent const dirPath = normalizedPath.includes('/') ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/')) : '';
.replace(/\\/g, '\\\\') if (dirPath) {
.replace(/"""/g, '\\"\\"\\"') await sandboxInstance.runCommand({
.replace(/\$/g, '\\$'); cmd: 'mkdir',
args: ['-p', dirPath]
});
}
await sandboxInstance.runCode(` // Write the file using Vercel Sandbox writeFiles
import os await sandboxInstance.writeFiles([{
os.makedirs(os.path.dirname("${fullPath}"), exist_ok=True) path: normalizedPath,
with open("${fullPath}", 'w') as f: content: Buffer.from(fileContent)
f.write("""${escapedContent}""") }]);
print(f"File written: ${fullPath}")
`);
// Update file cache // Update file cache
if (global.sandboxState?.fileCache) { if (global.sandboxState?.fileCache) {
@@ -599,28 +599,39 @@ print(f"File written: ${fullPath}")
action: 'executing' action: 'executing'
}); });
// Use E2B commands.run() for cleaner execution // Parse command and arguments for Vercel Sandbox
const result = await sandboxInstance.commands.run(cmd, { const commandParts = cmd.trim().split(/\s+/);
cwd: '/home/user/app', const cmdName = commandParts[0];
timeout: 60, const args = commandParts.slice(1);
on_stdout: async (data: string) => {
await sendProgress({ // Use Vercel Sandbox runCommand
type: 'command-output', const result = await sandboxInstance.runCommand({
command: cmd, cmd: cmdName,
output: data, args
stream: 'stdout'
});
},
on_stderr: async (data: string) => {
await sendProgress({
type: 'command-output',
command: cmd,
output: data,
stream: 'stderr'
});
}
}); });
// Get command output
const stdout = await result.stdout();
const stderr = await result.stderr();
if (stdout) {
await sendProgress({
type: 'command-output',
command: cmd,
output: stdout,
stream: 'stdout'
});
}
if (stderr) {
await sendProgress({
type: 'command-output',
command: cmd,
output: stderr,
stream: 'stderr'
});
}
if (results.commandsExecuted) { if (results.commandsExecuted) {
results.commandsExecuted.push(cmd); results.commandsExecuted.push(cmd);
} }
+32 -27
View File
@@ -432,15 +432,12 @@ function App() {
export default App;`; export default App;`;
try { try {
await global.activeSandbox.runCode(` await global.activeSandbox.writeFiles([{
file_path = "/home/user/app/src/App.jsx" path: 'src/App.jsx',
file_content = """${appContent.replace(/"/g, '\\"').replace(/\n/g, '\\n')}""" content: Buffer.from(appContent)
}]);
with open(file_path, 'w') as f:
f.write(file_content) console.log('Auto-generated: src/App.jsx');
print(f"Auto-generated: {file_path}")
`);
results.filesCreated.push('src/App.jsx (auto-generated)'); results.filesCreated.push('src/App.jsx (auto-generated)');
} catch (error) { } catch (error) {
results.errors.push(`Failed to create App.jsx: ${(error as Error).message}`); results.errors.push(`Failed to create App.jsx: ${(error as Error).message}`);
@@ -459,9 +456,7 @@ print(f"Auto-generated: {file_path}")
if (!isEdit && !indexCssInParsed && !indexCssExists) { if (!isEdit && !indexCssInParsed && !indexCssExists) {
try { try {
await global.activeSandbox.runCode(` const indexCssContent = `@tailwind base;
file_path = "/home/user/app/src/index.css"
file_content = """@tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@@ -483,13 +478,14 @@ body {
margin: 0; margin: 0;
min-width: 320px; min-width: 320px;
min-height: 100vh; min-height: 100vh;
}""" }`;
with open(file_path, 'w') as f: await global.activeSandbox.writeFiles([{
f.write(file_content) path: 'src/index.css',
content: Buffer.from(indexCssContent)
print(f"Auto-generated: {file_path}") }]);
`);
console.log('Auto-generated: src/index.css');
results.filesCreated.push('src/index.css (with Tailwind)'); results.filesCreated.push('src/index.css (with Tailwind)');
} catch (error) { } catch (error) {
results.errors.push('Failed to create index.css with Tailwind'); results.errors.push('Failed to create index.css with Tailwind');
@@ -500,15 +496,24 @@ print(f"Auto-generated: {file_path}")
// Execute commands // Execute commands
for (const cmd of parsed.commands) { for (const cmd of parsed.commands) {
try { try {
await global.activeSandbox.runCode(` // Parse command and arguments
import subprocess const commandParts = cmd.trim().split(/\s+/);
os.chdir('/home/user/app') const cmdName = commandParts[0];
result = subprocess.run(${JSON.stringify(cmd.split(' '))}, capture_output=True, text=True) const args = commandParts.slice(1);
print(f"Executed: ${cmd}")
print(result.stdout) // Execute command using Vercel Sandbox
if result.stderr: const result = await global.activeSandbox.runCommand({
print(f"Errors: {result.stderr}") cmd: cmdName,
`); args
});
console.log(`Executed: ${cmd}`);
const stdout = await result.stdout();
const stderr = await result.stderr();
if (stdout) console.log(stdout);
if (stderr) console.log(`Errors: ${stderr}`);
results.commandsExecuted.push(cmd); results.commandsExecuted.push(cmd);
} catch (error) { } catch (error) {
results.errors.push(`Failed to execute ${cmd}: ${(error as Error).message}`); results.errors.push(`Failed to execute ${cmd}: ${(error as Error).message}`);
+135 -189
View File
@@ -1,5 +1,5 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { Sandbox } from '@e2b/code-interpreter'; import { Sandbox } from '@vercel/sandbox';
import type { SandboxState } from '@/types/sandbox'; import type { SandboxState } from '@/types/sandbox';
import { appConfig } from '@/config/app.config'; import { appConfig } from '@/config/app.config';
@@ -15,15 +15,15 @@ export async function POST() {
let sandbox: any = null; let sandbox: any = null;
try { try {
console.log('[create-ai-sandbox] Creating base sandbox...'); console.log('[create-ai-sandbox] Creating Vercel sandbox...');
// Kill existing sandbox if any // Kill existing sandbox if any
if (global.activeSandbox) { if (global.activeSandbox) {
console.log('[create-ai-sandbox] Killing existing sandbox...'); console.log('[create-ai-sandbox] Stopping existing sandbox...');
try { try {
await global.activeSandbox.kill(); await global.activeSandbox.stop();
} catch (e) { } catch (e) {
console.error('Failed to close existing sandbox:', e); console.error('Failed to stop existing sandbox:', e);
} }
global.activeSandbox = null; global.activeSandbox = null;
} }
@@ -35,81 +35,86 @@ export async function POST() {
global.existingFiles = new Set<string>(); global.existingFiles = new Set<string>();
} }
// Create base sandbox - we'll set up Vite ourselves for full control // Create Vercel sandbox
console.log(`[create-ai-sandbox] Creating base E2B sandbox with ${appConfig.e2b.timeoutMinutes} minute timeout...`); console.log(`[create-ai-sandbox] Creating Vercel sandbox with ${appConfig.vercelSandbox.timeoutMinutes} minute timeout...`);
sandbox = await Sandbox.create({ sandbox = await Sandbox.create({
apiKey: process.env.E2B_API_KEY, timeout: appConfig.vercelSandbox.timeoutMs,
timeoutMs: appConfig.e2b.timeoutMs runtime: appConfig.vercelSandbox.runtime,
ports: [appConfig.vercelSandbox.devPort]
}); });
const sandboxId = (sandbox as any).sandboxId || Date.now().toString(); const sandboxId = sandbox.sandboxId;
const host = (sandbox as any).getHost(appConfig.e2b.vitePort);
console.log(`[create-ai-sandbox] Sandbox created: ${sandboxId}`); console.log(`[create-ai-sandbox] Sandbox created: ${sandboxId}`);
console.log(`[create-ai-sandbox] Sandbox host: ${host}`);
// Set up a basic Vite React app using Python to write files // Set up a basic Vite React app
console.log('[create-ai-sandbox] Setting up Vite React app...'); console.log('[create-ai-sandbox] Setting up Vite React app...');
// Write all files in a single Python script to avoid multiple executions // First, change to the working directory
const setupScript = ` await sandbox.runCommand('pwd');
import os const workDir = appConfig.vercelSandbox.workingDirectory;
import json
// Get the sandbox URL using the correct Vercel Sandbox API
const sandboxUrl = sandbox.domain(appConfig.vercelSandbox.devPort);
// Extract the hostname from the sandbox URL for Vite config
const sandboxHostname = new URL(sandboxUrl).hostname;
console.log(`[create-ai-sandbox] Sandbox hostname: ${sandboxHostname}`);
print('Setting up React app with Vite and Tailwind...') // Create the Vite config content with the proper hostname (using string concatenation)
const viteConfigContent = `import { defineConfig } from 'vite'
# Create directory structure
os.makedirs('/home/user/app/src', exist_ok=True)
# Package.json
package_json = {
"name": "sandbox-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.0.0",
"vite": "^4.3.9",
"tailwindcss": "^3.3.0",
"postcss": "^8.4.31",
"autoprefixer": "^10.4.16"
}
}
with open('/home/user/app/package.json', 'w') as f:
json.dump(package_json, f, indent=2)
print('✓ package.json')
# Vite config for E2B - with allowedHosts
vite_config = """import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
// E2B-compatible Vite configuration // Vercel Sandbox compatible Vite configuration
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
port: 5173, port: ${appConfig.vercelSandbox.devPort},
strictPort: true, strictPort: true,
hmr: false, hmr: true,
allowedHosts: ['.e2b.app', 'localhost', '127.0.0.1'] allowedHosts: [
'localhost',
'127.0.0.1',
'` + sandboxHostname + `', // Allow the Vercel Sandbox domain
'.vercel.run', // Allow all Vercel sandbox domains
'.vercel-sandbox.dev' // Fallback pattern
]
} }
})""" })`;
with open('/home/user/app/vite.config.js', 'w') as f: // Create the project files (now we have the sandbox hostname)
f.write(vite_config) const projectFiles = [
print('✓ vite.config.js') {
path: 'package.json',
# Tailwind config - standard without custom design tokens content: Buffer.from(JSON.stringify({
tailwind_config = """/** @type {import('tailwindcss').Config} */ "name": "sandbox-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --host --port 3000",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.0.0",
"vite": "^4.3.9",
"tailwindcss": "^3.3.0",
"postcss": "^8.4.31",
"autoprefixer": "^10.4.16"
}
}, null, 2))
},
{
path: 'vite.config.js',
content: Buffer.from(viteConfigContent)
},
{
path: 'tailwind.config.js',
content: Buffer.from(`/** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
"./index.html", "./index.html",
@@ -119,26 +124,20 @@ export default {
extend: {}, extend: {},
}, },
plugins: [], plugins: [],
}""" }`)
},
with open('/home/user/app/tailwind.config.js', 'w') as f: {
f.write(tailwind_config) path: 'postcss.config.js',
print('✓ tailwind.config.js') content: Buffer.from(`export default {
# PostCSS config
postcss_config = """export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}""" }`)
},
with open('/home/user/app/postcss.config.js', 'w') as f: {
f.write(postcss_config) path: 'index.html',
print('✓ postcss.config.js') content: Buffer.from(`<!DOCTYPE html>
# Index.html
index_html = """<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -149,14 +148,11 @@ index_html = """<!DOCTYPE html>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.jsx"></script> <script type="module" src="/src/main.jsx"></script>
</body> </body>
</html>""" </html>`)
},
with open('/home/user/app/index.html', 'w') as f: {
f.write(index_html) path: 'src/main.jsx',
print('✓ index.html') content: Buffer.from(`import React from 'react'
# Main.jsx
main_jsx = """import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import App from './App.jsx' import App from './App.jsx'
import './index.css' import './index.css'
@@ -165,19 +161,18 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
)""" )`)
},
with open('/home/user/app/src/main.jsx', 'w') as f: {
f.write(main_jsx) path: 'src/App.jsx',
print('✓ src/main.jsx') content: Buffer.from(`function App() {
# App.jsx with explicit Tailwind test
app_jsx = """function App() {
return ( return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center p-4"> <div className="min-h-screen bg-gray-900 text-white flex items-center justify-center p-4">
<div className="text-center max-w-2xl"> <div className="text-center max-w-2xl">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-blue-500 to-purple-600 bg-clip-text text-transparent">
Sandbox Ready
</h1>
<p className="text-lg text-gray-400"> <p className="text-lg text-gray-400">
Sandbox Ready<br/>
Start building your React app with Vite and Tailwind CSS! Start building your React app with Vite and Tailwind CSS!
</p> </p>
</div> </div>
@@ -185,14 +180,11 @@ app_jsx = """function App() {
) )
} }
export default App""" export default App`)
},
with open('/home/user/app/src/App.jsx', 'w') as f: {
f.write(app_jsx) path: 'src/index.css',
print('✓ src/App.jsx') content: Buffer.from(`@tailwind base;
# Index.css with explicit Tailwind directives
index_css = """@tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@@ -216,99 +208,53 @@ index_css = """@tailwind base;
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background-color: rgb(17 24 39); background-color: rgb(17 24 39);
}""" }`)
}
];
with open('/home/user/app/src/index.css', 'w') as f: // Create directory structure first
f.write(index_css) await sandbox.runCommand({
print('✓ src/index.css') cmd: 'mkdir',
args: ['-p', 'src']
print('\\nAll files created successfully!') });
`;
// Write all files
// Execute the setup script await sandbox.writeFiles(projectFiles);
await sandbox.runCode(setupScript); console.log('[create-ai-sandbox] ✓ Project files created');
// Install dependencies // Install dependencies
console.log('[create-ai-sandbox] Installing dependencies...'); console.log('[create-ai-sandbox] Installing dependencies...');
await sandbox.runCode(` const installResult = await sandbox.runCommand({
import subprocess cmd: 'npm',
import sys args: ['install', '--loglevel', 'info']
});
print('Installing npm packages...') if (installResult.exitCode === 0) {
result = subprocess.run( console.log('[create-ai-sandbox] ✓ Dependencies installed successfully');
['npm', 'install'], } else {
cwd='/home/user/app', console.log('[create-ai-sandbox] ⚠ Warning: npm install had issues but continuing...');
capture_output=True, }
text=True
)
if result.returncode == 0:
print('✓ Dependencies installed successfully')
else:
print(f'⚠ Warning: npm install had issues: {result.stderr}')
# Continue anyway as it might still work
`);
// Start Vite dev server // Start Vite dev server in detached mode
console.log('[create-ai-sandbox] Starting Vite dev server...'); console.log('[create-ai-sandbox] Starting Vite dev server...');
await sandbox.runCode(` const viteProcess = await sandbox.runCommand({
import subprocess cmd: 'npm',
import os args: ['run', 'dev'],
import time detached: true
});
os.chdir('/home/user/app')
console.log('[create-ai-sandbox] ✓ Vite dev server started');
# Kill any existing Vite processes
subprocess.run(['pkill', '-f', 'vite'], capture_output=True)
time.sleep(1)
# Start Vite dev server
env = os.environ.copy()
env['FORCE_COLOR'] = '0'
process = subprocess.Popen(
['npm', 'run', 'dev'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env
)
print(f'✓ Vite dev server started with PID: {process.pid}')
print('Waiting for server to be ready...')
`);
// Wait for Vite to be fully ready // Wait for Vite to be fully ready
await new Promise(resolve => setTimeout(resolve, appConfig.e2b.viteStartupDelay)); await new Promise(resolve => setTimeout(resolve, appConfig.vercelSandbox.devServerStartupDelay));
// Force Tailwind CSS to rebuild by touching the CSS file
await sandbox.runCode(`
import os
import time
# Touch the CSS file to trigger rebuild
css_file = '/home/user/app/src/index.css'
if os.path.exists(css_file):
os.utime(css_file, None)
print('✓ Triggered CSS rebuild')
# Also ensure PostCSS processes it
time.sleep(2)
print('✓ Tailwind CSS should be loaded')
`);
// Store sandbox globally // Store sandbox globally
global.activeSandbox = sandbox; global.activeSandbox = sandbox;
global.sandboxData = { global.sandboxData = {
sandboxId, sandboxId,
url: `https://${host}` url: sandboxUrl,
viteProcess
}; };
// Set extended timeout on the sandbox instance if method available
if (typeof sandbox.setTimeout === 'function') {
sandbox.setTimeout(appConfig.e2b.timeoutMs);
console.log(`[create-ai-sandbox] Set sandbox timeout to ${appConfig.e2b.timeoutMinutes} minutes`);
}
// Initialize sandbox state // Initialize sandbox state
global.sandboxState = { global.sandboxState = {
fileCache: { fileCache: {
@@ -319,7 +265,7 @@ print('✓ Tailwind CSS should be loaded')
sandbox, sandbox,
sandboxData: { sandboxData: {
sandboxId, sandboxId,
url: `https://${host}` url: sandboxUrl
} }
}; };
@@ -333,13 +279,13 @@ print('✓ Tailwind CSS should be loaded')
global.existingFiles.add('tailwind.config.js'); global.existingFiles.add('tailwind.config.js');
global.existingFiles.add('postcss.config.js'); global.existingFiles.add('postcss.config.js');
console.log('[create-ai-sandbox] Sandbox ready at:', `https://${host}`); console.log('[create-ai-sandbox] Sandbox ready at:', sandboxUrl);
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
sandboxId, sandboxId,
url: `https://${host}`, url: sandboxUrl,
message: 'Sandbox created and Vite React app initialized' message: 'Vercel sandbox created and Vite React app initialized'
}); });
} catch (error) { } catch (error) {
@@ -348,9 +294,9 @@ print('✓ Tailwind CSS should be loaded')
// Clean up on error // Clean up on error
if (sandbox) { if (sandbox) {
try { try {
await sandbox.kill(); await sandbox.stop();
} catch (e) { } catch (e) {
console.error('Failed to close sandbox on error:', e); console.error('Failed to stop sandbox on error:', e);
} }
} }
+36 -37
View File
@@ -15,41 +15,37 @@ export async function POST(request: NextRequest) {
console.log('[create-zip] Creating project zip...'); console.log('[create-zip] Creating project zip...');
// Create zip file in sandbox // Create zip file in sandbox using standard commands
const result = await global.activeSandbox.runCode(` const zipResult = await global.activeSandbox.runCommand({
import zipfile cmd: 'bash',
import os args: ['-c', `zip -r /tmp/project.zip . -x "node_modules/*" ".git/*" ".next/*" "dist/*" "build/*" "*.log"`]
import json });
os.chdir('/home/user/app') if (zipResult.exitCode !== 0) {
const error = await zipResult.stderr();
# Create zip file throw new Error(`Failed to create zip: ${error}`);
with zipfile.ZipFile('/tmp/project.zip', 'w', zipfile.ZIP_DEFLATED) as zipf: }
for root, dirs, files in os.walk('.'):
# Skip node_modules and .git const sizeResult = await global.activeSandbox.runCommand({
dirs[:] = [d for d in dirs if d not in ['node_modules', '.git', '.next', 'dist']] cmd: 'bash',
args: ['-c', `ls -la /tmp/project.zip | awk '{print $5}'`]
for file in files: });
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, '.') const fileSize = await sizeResult.stdout();
zipf.write(file_path, arcname) console.log(`[create-zip] Created project.zip (${fileSize.trim()} bytes)`);
# Get file size
file_size = os.path.getsize('/tmp/project.zip')
print(f" Created project.zip ({file_size} bytes)")
`);
// Read the zip file and convert to base64 // Read the zip file and convert to base64
const readResult = await global.activeSandbox.runCode(` const readResult = await global.activeSandbox.runCommand({
import base64 cmd: 'base64',
args: ['/tmp/project.zip']
with open('/tmp/project.zip', 'rb') as f: });
content = f.read()
encoded = base64.b64encode(content).decode('utf-8')
print(encoded)
`);
const base64Content = readResult.logs.stdout.join('').trim(); if (readResult.exitCode !== 0) {
const error = await readResult.stderr();
throw new Error(`Failed to read zip file: ${error}`);
}
const base64Content = (await readResult.stdout()).trim();
// Create a data URL for download // Create a data URL for download
const dataUrl = `data:application/zip;base64,${base64Content}`; const dataUrl = `data:application/zip;base64,${base64Content}`;
@@ -57,15 +53,18 @@ with open('/tmp/project.zip', 'rb') as f:
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
dataUrl, dataUrl,
fileName: 'e2b-project.zip', fileName: 'vercel-sandbox-project.zip',
message: 'Zip file created successfully' message: 'Zip file created successfully'
}); });
} catch (error) { } catch (error) {
console.error('[create-zip] Error:', error); console.error('[create-zip] Error:', error);
return NextResponse.json({ return NextResponse.json(
success: false, {
error: (error as Error).message success: false,
}, { status: 500 }); error: (error as Error).message
},
{ status: 500 }
);
} }
} }
+64 -136
View File
@@ -64,15 +64,7 @@ export async function POST(request: NextRequest) {
const builtins = ['fs', 'path', 'http', 'https', 'crypto', 'stream', 'util', 'os', 'url', 'querystring', 'child_process']; const builtins = ['fs', 'path', 'http', 'https', 'crypto', 'stream', 'util', 'os', 'url', 'querystring', 'child_process'];
if (builtins.includes(imp)) return false; if (builtins.includes(imp)) return false;
// Extract package name (handle scoped packages and subpaths) return true;
const parts = imp.split('/');
if (imp.startsWith('@')) {
// Scoped package like @vitejs/plugin-react
return true;
} else {
// Regular package, return just the first part
return true;
}
}); });
// Extract just the package names (without subpaths) // Extract just the package names (without subpaths)
@@ -101,153 +93,89 @@ export async function POST(request: NextRequest) {
} }
// Check which packages are already installed // Check which packages are already installed
const checkResult = await global.activeSandbox.runCode(` const installed: string[] = [];
import os const missing: string[] = [];
import json
installed = []
missing = []
packages = ${JSON.stringify(uniquePackages)}
for package in packages:
# Handle scoped packages
if package.startswith('@'):
package_path = f"/home/user/app/node_modules/{package}"
else:
package_path = f"/home/user/app/node_modules/{package}"
if os.path.exists(package_path): for (const packageName of uniquePackages) {
installed.append(package) try {
else: const checkResult = await global.activeSandbox.runCommand({
missing.append(package) cmd: 'test',
args: ['-d', `node_modules/${packageName}`]
});
if (checkResult.exitCode === 0) {
installed.push(packageName);
} else {
missing.push(packageName);
}
} catch (error) {
// If test command fails, assume package is missing
missing.push(packageName);
}
}
result = { console.log('[detect-and-install-packages] Package status:', { installed, missing });
'installed': installed,
'missing': missing
}
print(json.dumps(result)) if (missing.length === 0) {
`);
const status = JSON.parse(checkResult.logs.stdout.join(''));
console.log('[detect-and-install-packages] Package status:', status);
if (status.missing.length === 0) {
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
packagesInstalled: [], packagesInstalled: [],
packagesAlreadyInstalled: status.installed, packagesAlreadyInstalled: installed,
message: 'All packages already installed' message: 'All packages already installed'
}); });
} }
// Install missing packages // Install missing packages
console.log('[detect-and-install-packages] Installing packages:', status.missing); console.log('[detect-and-install-packages] Installing packages:', missing);
const installResult = await global.activeSandbox.runCode(` const installResult = await global.activeSandbox.runCommand({
import subprocess cmd: 'npm',
import os args: ['install', '--save', ...missing]
import json });
os.chdir('/home/user/app') const stdout = await installResult.stdout();
packages_to_install = ${JSON.stringify(status.missing)} const stderr = await installResult.stderr();
# Join packages into a single install command
packages_str = ' '.join(packages_to_install)
cmd = f'npm install {packages_str} --save'
print(f"Running: {cmd}")
# Run npm install with explicit save flag
result = subprocess.run(['npm', 'install', '--save'] + packages_to_install,
capture_output=True,
text=True,
cwd='/home/user/app',
timeout=60)
print("stdout:", result.stdout)
if result.stderr:
print("stderr:", result.stderr)
# Verify installation
installed = []
failed = []
for package in packages_to_install:
# Handle scoped packages correctly
if package.startswith('@'):
# For scoped packages like @heroicons/react
package_path = f"/home/user/app/node_modules/{package}"
else:
package_path = f"/home/user/app/node_modules/{package}"
if os.path.exists(package_path): console.log('[detect-and-install-packages] Install stdout:', stdout);
installed.append(package) if (stderr) {
print(f"✓ Verified installation of {package}") console.log('[detect-and-install-packages] Install stderr:', stderr);
else:
# Check if it's a submodule of an installed package
base_package = package.split('/')[0]
if package.startswith('@'):
# For @scope/package, the base is @scope/package
base_package = '/'.join(package.split('/')[:2])
base_path = f"/home/user/app/node_modules/{base_package}"
if os.path.exists(base_path):
installed.append(package)
print(f"✓ Verified installation of {package} (via {base_package})")
else:
failed.append(package)
print(f"✗ Failed to verify installation of {package}")
result_data = {
'installed': installed,
'failed': failed,
'returncode': result.returncode
}
print("\\nResult:", json.dumps(result_data))
`, { timeout: 60000 });
// Parse the result more safely
let installStatus;
try {
const stdout = installResult.logs.stdout.join('');
const resultMatch = stdout.match(/Result:\s*({.*})/);
if (resultMatch) {
installStatus = JSON.parse(resultMatch[1]);
} else {
// Fallback parsing
const lines = stdout.split('\n');
const resultLine = lines.find((line: string) => line.includes('Result:'));
if (resultLine) {
installStatus = JSON.parse(resultLine.split('Result:')[1].trim());
} else {
throw new Error('Could not find Result in output');
}
}
} catch (parseError) {
console.error('[detect-and-install-packages] Failed to parse install result:', parseError);
console.error('[detect-and-install-packages] stdout:', installResult.logs.stdout.join(''));
// Fallback to assuming all packages were installed
installStatus = {
installed: status.missing,
failed: [],
returncode: 0
};
} }
if (installStatus.failed.length > 0) { // Verify installation
console.error('[detect-and-install-packages] Failed to install:', installStatus.failed); const finalInstalled: string[] = [];
const failed: string[] = [];
for (const packageName of missing) {
try {
const verifyResult = await global.activeSandbox.runCommand({
cmd: 'test',
args: ['-d', `node_modules/${packageName}`]
});
if (verifyResult.exitCode === 0) {
finalInstalled.push(packageName);
console.log(`✓ Verified installation of ${packageName}`);
} else {
failed.push(packageName);
console.log(`✗ Failed to verify installation of ${packageName}`);
}
} catch (error) {
failed.push(packageName);
console.log(`✗ Error verifying ${packageName}:`, error);
}
}
if (failed.length > 0) {
console.error('[detect-and-install-packages] Failed to install:', failed);
} }
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
packagesInstalled: installStatus.installed, packagesInstalled: finalInstalled,
packagesFailed: installStatus.failed, packagesFailed: failed,
packagesAlreadyInstalled: status.installed, packagesAlreadyInstalled: installed,
message: `Installed ${installStatus.installed.length} packages`, message: `Installed ${finalInstalled.length} packages`,
logs: installResult.logs.stdout.join('\n') logs: stdout
}); });
} catch (error) { } catch (error) {
+47 -5
View File
@@ -11,6 +11,9 @@ import { FileManifest } from '@/types/file-manifest';
import type { ConversationState, ConversationMessage, ConversationEdit } from '@/types/conversation'; import type { ConversationState, ConversationMessage, ConversationEdit } from '@/types/conversation';
import { appConfig } from '@/config/app.config'; import { appConfig } from '@/config/app.config';
// Force dynamic route to enable streaming
export const dynamic = 'force-dynamic';
const groq = createGroq({ const groq = createGroq({
apiKey: process.env.GROQ_API_KEY, apiKey: process.env.GROQ_API_KEY,
}); });
@@ -1156,9 +1159,21 @@ CRITICAL: When files are provided in the context:
const isGoogle = model.startsWith('google/'); const isGoogle = model.startsWith('google/');
const isOpenAI = model.startsWith('openai/gpt-5'); const isOpenAI = model.startsWith('openai/gpt-5');
const modelProvider = isAnthropic ? anthropic : (isOpenAI ? openai : (isGoogle ? googleGenerativeAI : groq)); const modelProvider = isAnthropic ? anthropic : (isOpenAI ? openai : (isGoogle ? googleGenerativeAI : groq));
const actualModel = isAnthropic ? model.replace('anthropic/', '') :
(model === 'openai/gpt-5') ? 'gpt-5' : // Fix model name transformation for different providers
(isGoogle ? model.replace('google/', '') : model); let actualModel: string;
if (isAnthropic) {
actualModel = model.replace('anthropic/', '');
} else if (model === 'openai/gpt-5') {
actualModel = 'gpt-5';
} else if (isGoogle) {
// Google uses specific model names - convert our naming to theirs
actualModel = model.replace('google/', '');
} else {
actualModel = model;
}
console.log(`[generate-ai-code-stream] Using provider: ${isAnthropic ? 'Anthropic' : isGoogle ? 'Google' : isOpenAI ? 'OpenAI' : 'Groq'}, model: ${actualModel}`);
// Make streaming API call with appropriate provider // Make streaming API call with appropriate provider
const streamOptions: any = { const streamOptions: any = {
@@ -1243,7 +1258,28 @@ It's better to have 3 complete files than 10 incomplete files.`
}; };
} }
const result = await streamText(streamOptions); let result;
try {
result = await streamText(streamOptions);
} catch (streamError) {
console.error('[generate-ai-code-stream] Error calling streamText:', streamError);
// Send specific error for debugging
await sendProgress({
type: 'error',
message: `Failed to initialize ${isGoogle ? 'Gemini' : isAnthropic ? 'Claude' : isOpenAI ? 'GPT-5' : 'Groq'} streaming: ${(streamError as Error).message}`
});
// If this is a Google model error, provide helpful info
if (isGoogle) {
await sendProgress({
type: 'info',
message: 'Tip: Make sure your GEMINI_API_KEY is set correctly and has proper permissions.'
});
}
throw streamError;
}
// Stream the response and parse in real-time // Stream the response and parse in real-time
let generatedCode = ''; let generatedCode = '';
@@ -1715,12 +1751,18 @@ Provide the complete file content without any truncation. Include all necessary
} }
})(); })();
// Return the stream // Return the stream with proper headers for streaming support
return new Response(stream.readable, { return new Response(stream.readable, {
headers: { headers: {
'Content-Type': 'text/event-stream', 'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
'Connection': 'keep-alive', 'Connection': 'keep-alive',
'Transfer-Encoding': 'chunked',
'Content-Encoding': 'none', // Prevent compression that can break streaming
'X-Accel-Buffering': 'no', // Disable nginx buffering
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}, },
}); });
+79 -56
View File
@@ -18,58 +18,81 @@ export async function GET() {
console.log('[get-sandbox-files] Fetching and analyzing file structure...'); console.log('[get-sandbox-files] Fetching and analyzing file structure...');
// Get all React/JS/CSS files // Get list of all relevant files
const result = await global.activeSandbox.runCode(` const findResult = await global.activeSandbox.runCommand({
import os cmd: 'find',
import json args: [
'.',
def get_files_content(directory='/home/user/app', extensions=['.jsx', '.js', '.tsx', '.ts', '.css', '.json']): '-name', 'node_modules', '-prune', '-o',
files_content = {} '-name', '.git', '-prune', '-o',
'-name', 'dist', '-prune', '-o',
'-name', 'build', '-prune', '-o',
'-type', 'f',
'(',
'-name', '*.jsx',
'-o', '-name', '*.js',
'-o', '-name', '*.tsx',
'-o', '-name', '*.ts',
'-o', '-name', '*.css',
'-o', '-name', '*.json',
')',
'-print'
]
});
for root, dirs, files in os.walk(directory): if (findResult.exitCode !== 0) {
# Skip node_modules and other unwanted directories throw new Error('Failed to list files');
dirs[:] = [d for d in dirs if d not in ['node_modules', '.git', 'dist', 'build']] }
const fileList = (await findResult.stdout()).split('\n').filter(f => f.trim());
console.log('[get-sandbox-files] Found', fileList.length, 'files');
// Read content of each file (limit to reasonable sizes)
const filesContent: Record<string, string> = {};
for (const filePath of fileList) {
try {
// Check file size first
const statResult = await global.activeSandbox.runCommand({
cmd: 'stat',
args: ['-f', '%z', filePath]
});
for file in files: if (statResult.exitCode === 0) {
if any(file.endswith(ext) for ext in extensions): const fileSize = parseInt(await statResult.stdout());
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, '/home/user/app') // Only read files smaller than 10KB
if (fileSize < 10000) {
try: const catResult = await global.activeSandbox.runCommand({
with open(file_path, 'r') as f: cmd: 'cat',
content = f.read() args: [filePath]
# Only include files under 10KB to avoid huge responses });
if len(content) < 10000:
files_content[relative_path] = content if (catResult.exitCode === 0) {
except: const content = await catResult.stdout();
pass // Remove leading './' from path
const relativePath = filePath.replace(/^\.\//, '');
filesContent[relativePath] = content;
}
}
}
} catch (error) {
// Skip files that can't be read
continue;
}
}
return files_content // Get directory structure
const treeResult = await global.activeSandbox.runCommand({
# Get the files cmd: 'find',
files = get_files_content() args: ['.', '-type', 'd', '-not', '-path', '*/node_modules*', '-not', '-path', '*/.git*']
});
# Also get the directory structure
structure = [] let structure = '';
for root, dirs, files in os.walk('/home/user/app'): if (treeResult.exitCode === 0) {
level = root.replace('/home/user/app', '').count(os.sep) const dirs = (await treeResult.stdout()).split('\n').filter(d => d.trim());
indent = ' ' * 2 * level structure = dirs.slice(0, 50).join('\n'); // Limit to 50 lines
structure.append(f"{indent}{os.path.basename(root)}/") }
sub_indent = ' ' * 2 * (level + 1)
for file in files:
if not any(skip in root for skip in ['node_modules', '.git', 'dist', 'build']):
structure.append(f"{sub_indent}{file}")
result = {
'files': files,
'structure': '\\n'.join(structure[:50]) # Limit structure to 50 lines
}
print(json.dumps(result))
`);
const output = result.logs.stdout.join('');
const parsedResult = JSON.parse(output);
// Build enhanced file manifest // Build enhanced file manifest
const fileManifest: FileManifest = { const fileManifest: FileManifest = {
@@ -82,12 +105,12 @@ print(json.dumps(result))
}; };
// Process each file // Process each file
for (const [relativePath, content] of Object.entries(parsedResult.files)) { for (const [relativePath, content] of Object.entries(filesContent)) {
const fullPath = `/home/user/app/${relativePath}`; const fullPath = `/${relativePath}`;
// Create base file info // Create base file info
const fileInfo: FileInfo = { const fileInfo: FileInfo = {
content: content as string, content: content,
type: 'utility', type: 'utility',
path: fullPath, path: fullPath,
relativePath, relativePath,
@@ -96,7 +119,7 @@ print(json.dumps(result))
// Parse JavaScript/JSX files // Parse JavaScript/JSX files
if (relativePath.match(/\.(jsx?|tsx?)$/)) { if (relativePath.match(/\.(jsx?|tsx?)$/)) {
const parseResult = parseJavaScriptFile(content as string, fullPath); const parseResult = parseJavaScriptFile(content, fullPath);
Object.assign(fileInfo, parseResult); Object.assign(fileInfo, parseResult);
// Identify entry point // Identify entry point
@@ -132,9 +155,9 @@ print(json.dumps(result))
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
files: parsedResult.files, files: filesContent,
structure: parsedResult.structure, structure,
fileCount: Object.keys(parsedResult.files).length, fileCount: Object.keys(filesContent).length,
manifest: fileManifest, manifest: fileManifest,
}); });
+121 -219
View File
@@ -1,5 +1,4 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { Sandbox } from '@e2b/code-interpreter';
declare global { declare global {
var activeSandbox: any; var activeSandbox: any;
@@ -36,23 +35,8 @@ export async function POST(request: NextRequest) {
console.log(`[install-packages] Cleaned:`, validPackages); console.log(`[install-packages] Cleaned:`, validPackages);
} }
// Try to get sandbox - either from global or reconnect // Get active sandbox
let sandbox = global.activeSandbox; const sandbox = global.activeSandbox;
if (!sandbox && sandboxId) {
console.log(`[install-packages] Reconnecting to sandbox ${sandboxId}...`);
try {
sandbox = await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY });
global.activeSandbox = sandbox;
console.log(`[install-packages] Successfully reconnected to sandbox ${sandboxId}`);
} catch (error) {
console.error(`[install-packages] Failed to reconnect to sandbox:`, error);
return NextResponse.json({
success: false,
error: `Failed to reconnect to sandbox: ${(error as Error).message}`
}, { status: 500 });
}
}
if (!sandbox) { if (!sandbox) {
return NextResponse.json({ return NextResponse.json({
@@ -61,7 +45,7 @@ export async function POST(request: NextRequest) {
}, { status: 400 }); }, { status: 400 });
} }
console.log('[install-packages] Installing packages:', packages); console.log('[install-packages] Installing packages:', validPackages);
// Create a response stream for real-time updates // Create a response stream for real-time updates
const encoder = new TextEncoder(); const encoder = new TextEncoder();
@@ -83,23 +67,20 @@ export async function POST(request: NextRequest) {
packages: validPackages packages: validPackages
}); });
// Kill any existing Vite process first // Stop any existing development server first
await sendProgress({ type: 'status', message: 'Stopping development server...' }); await sendProgress({ type: 'status', message: 'Stopping development server...' });
await sandboxInstance.runCode(` try {
import subprocess // Try to kill any running dev server processes
import os await sandboxInstance.runCommand({
import signal cmd: 'pkill',
args: ['-f', 'vite']
# Try to kill any existing Vite process });
try: await new Promise(resolve => setTimeout(resolve, 1000)); // Wait a bit
with open('/tmp/vite-process.pid', 'r') as f: } catch (error) {
pid = int(f.read().strip()) // It's OK if no process is found
os.kill(pid, signal.SIGTERM) console.log('[install-packages] No existing dev server found');
print("Stopped existing Vite process") }
except:
print("No existing Vite process found")
`);
// Check which packages are already installed // Check which packages are already installed
await sendProgress({ await sendProgress({
@@ -107,70 +88,51 @@ except:
message: 'Checking installed packages...' message: 'Checking installed packages...'
}); });
const checkResult = await sandboxInstance.runCode(`
import os
import json
os.chdir('/home/user/app')
# Read package.json to check installed packages
try:
with open('package.json', 'r') as f:
package_json = json.load(f)
dependencies = package_json.get('dependencies', {})
dev_dependencies = package_json.get('devDependencies', {})
all_deps = {**dependencies, **dev_dependencies}
# Check which packages need to be installed
packages_to_check = ${JSON.stringify(validPackages)}
already_installed = []
need_install = []
for pkg in packages_to_check:
# Handle scoped packages
if pkg.startswith('@'):
pkg_name = pkg
else:
# Extract package name without version
pkg_name = pkg.split('@')[0]
if pkg_name in all_deps:
already_installed.append(pkg_name)
else:
need_install.append(pkg)
print(f"Already installed: {already_installed}")
print(f"Need to install: {need_install}")
print(f"NEED_INSTALL:{json.dumps(need_install)}")
except Exception as e:
print(f"Error checking packages: {e}")
print(f"NEED_INSTALL:{json.dumps(packages_to_check)}")
`);
// Parse packages that need installation
let packagesToInstall = validPackages; let packagesToInstall = validPackages;
// Check if checkResult has the expected structure try {
if (checkResult && checkResult.results && checkResult.results[0] && checkResult.results[0].text) { // Read package.json to check existing dependencies
const outputLines = checkResult.results[0].text.split('\n'); const catResult = await sandboxInstance.runCommand({
for (const line of outputLines) { cmd: 'cat',
if (line.startsWith('NEED_INSTALL:')) { args: ['package.json']
try { });
packagesToInstall = JSON.parse(line.substring('NEED_INSTALL:'.length)); if (catResult.exitCode === 0) {
} catch (e) { const packageJsonContent = await catResult.stdout();
console.error('Failed to parse packages to install:', e); const packageJson = JSON.parse(packageJsonContent);
const dependencies = packageJson.dependencies || {};
const devDependencies = packageJson.devDependencies || {};
const allDeps = { ...dependencies, ...devDependencies };
const alreadyInstalled = [];
const needInstall = [];
for (const pkg of validPackages) {
// Handle scoped packages
const pkgName = pkg.startsWith('@') ? pkg : pkg.split('@')[0];
if (allDeps[pkgName]) {
alreadyInstalled.push(pkgName);
} else {
needInstall.push(pkg);
} }
} }
packagesToInstall = needInstall;
if (alreadyInstalled.length > 0) {
await sendProgress({
type: 'info',
message: `Already installed: ${alreadyInstalled.join(', ')}`
});
}
} }
} else { } catch (error) {
console.error('[install-packages] Invalid checkResult structure:', checkResult); console.error('[install-packages] Error checking existing packages:', error);
// If we can't check, just try to install all packages // If we can't check, just try to install all packages
packagesToInstall = validPackages; packagesToInstall = validPackages;
} }
if (packagesToInstall.length === 0) { if (packagesToInstall.length === 0) {
await sendProgress({ await sendProgress({
type: 'success', type: 'success',
@@ -178,164 +140,104 @@ except Exception as e:
installedPackages: [], installedPackages: [],
alreadyInstalled: validPackages alreadyInstalled: validPackages
}); });
// Restart dev server
await sendProgress({ type: 'status', message: 'Restarting development server...' });
const devServerProcess = await sandboxInstance.runCommand({
cmd: 'npm',
args: ['run', 'dev'],
detached: true
});
await sendProgress({
type: 'complete',
message: 'Dev server restarted!',
installedPackages: []
});
return; return;
} }
// Install only packages that aren't already installed // Install only packages that aren't already installed
const packageList = packagesToInstall.join(' ');
// Only send the npm install command message if we're actually installing new packages
await sendProgress({ await sendProgress({
type: 'info', type: 'info',
message: `Installing ${packagesToInstall.length} new package(s): ${packagesToInstall.join(', ')}` message: `Installing ${packagesToInstall.length} new package(s): ${packagesToInstall.join(', ')}`
}); });
const installResult = await sandboxInstance.runCode(` // Run npm install
import subprocess const installArgs = ['install', '--legacy-peer-deps', ...packagesToInstall];
import os const installResult = await sandboxInstance.runCommand({
cmd: 'npm',
os.chdir('/home/user/app') args: installArgs
});
# Run npm install with output capture
packages_to_install = ${JSON.stringify(packagesToInstall)}
cmd_args = ['npm', 'install', '--legacy-peer-deps'] + packages_to_install
print(f"Running command: {' '.join(cmd_args)}")
process = subprocess.Popen(
cmd_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Stream output
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip())
# Get the return code
rc = process.poll()
# Capture any stderr
stderr = process.stderr.read()
if stderr:
print("STDERR:", stderr)
if 'ERESOLVE' in stderr:
print("ERESOLVE_ERROR: Dependency conflict detected - using --legacy-peer-deps flag")
print(f"\\nInstallation completed with code: {rc}")
# Verify packages were installed
import json
with open('/home/user/app/package.json', 'r') as f:
package_json = json.load(f)
installed = []
for pkg in ${JSON.stringify(packagesToInstall)}:
if pkg in package_json.get('dependencies', {}):
installed.append(pkg)
print(f"✓ Verified {pkg}")
else:
print(f"✗ Package {pkg} not found in dependencies")
print(f"\\nVerified installed packages: {installed}") // Get install output
`, { timeout: 60000 }); // 60 second timeout for npm install const stdout = await installResult.stdout();
const stderr = await installResult.stderr();
// Send npm output if (stdout) {
const output = installResult?.output || installResult?.logs?.stdout?.join('\n') || ''; const lines = stdout.split('\n').filter(line => line.trim());
const npmOutputLines = output.split('\n').filter((line: string) => line.trim()); for (const line of lines) {
for (const line of npmOutputLines) { if (line.includes('npm WARN')) {
if (line.includes('STDERR:')) { await sendProgress({ type: 'warning', message: line });
const errorMsg = line.replace('STDERR:', '').trim(); } else if (line.trim()) {
if (errorMsg && errorMsg !== 'undefined') { await sendProgress({ type: 'output', message: line });
await sendProgress({ type: 'error', message: errorMsg });
} }
} else if (line.includes('ERESOLVE_ERROR:')) {
const msg = line.replace('ERESOLVE_ERROR:', '').trim();
await sendProgress({
type: 'warning',
message: `Dependency conflict resolved with --legacy-peer-deps: ${msg}`
});
} else if (line.includes('npm WARN')) {
await sendProgress({ type: 'warning', message: line });
} else if (line.trim() && !line.includes('undefined')) {
await sendProgress({ type: 'output', message: line });
} }
} }
// Check if installation was successful if (stderr) {
const installedMatch = output.match(/Verified installed packages: \[(.*?)\]/); const errorLines = stderr.split('\n').filter(line => line.trim());
let installedPackages: string[] = []; for (const line of errorLines) {
if (line.includes('ERESOLVE')) {
if (installedMatch && installedMatch[1]) { await sendProgress({
installedPackages = installedMatch[1] type: 'warning',
.split(',') message: `Dependency conflict resolved with --legacy-peer-deps: ${line}`
.map((p: string) => p.trim().replace(/'/g, '')) });
.filter((p: string) => p.length > 0); } else if (line.trim()) {
await sendProgress({ type: 'error', message: line });
}
}
} }
if (installedPackages.length > 0) { if (installResult.exitCode === 0) {
await sendProgress({ await sendProgress({
type: 'success', type: 'success',
message: `Successfully installed: ${installedPackages.join(', ')}`, message: `Successfully installed: ${packagesToInstall.join(', ')}`,
installedPackages installedPackages: packagesToInstall
}); });
} else { } else {
await sendProgress({ await sendProgress({
type: 'error', type: 'error',
message: 'Failed to verify package installation' message: 'Package installation failed'
}); });
} }
// Restart Vite dev server // Restart development server
await sendProgress({ type: 'status', message: 'Restarting development server...' }); await sendProgress({ type: 'status', message: 'Restarting development server...' });
await sandboxInstance.runCode(` try {
import subprocess const devServerProcess = await sandboxInstance.runCommand({
import os cmd: 'npm',
import time args: ['run', 'dev'],
detached: true
os.chdir('/home/user/app') });
# Kill any existing Vite processes // Wait a bit for the server to start
subprocess.run(['pkill', '-f', 'vite'], capture_output=True) await new Promise(resolve => setTimeout(resolve, 3000));
time.sleep(1)
await sendProgress({
# Start Vite dev server type: 'complete',
env = os.environ.copy() message: 'Package installation complete and dev server restarted!',
env['FORCE_COLOR'] = '0' installedPackages: packagesToInstall
});
process = subprocess.Popen( } catch (error) {
['npm', 'run', 'dev'], await sendProgress({
stdout=subprocess.PIPE, type: 'error',
stderr=subprocess.PIPE, message: `Failed to restart dev server: ${(error as Error).message}`
env=env });
) }
print(f'✓ Vite dev server restarted with PID: {process.pid}')
# Store process info for later
with open('/tmp/vite-process.pid', 'w') as f:
f.write(str(process.pid))
# Wait a bit for Vite to start up
time.sleep(3)
# Touch files to trigger Vite reload
subprocess.run(['touch', '/home/user/app/package.json'])
subprocess.run(['touch', '/home/user/app/vite.config.js'])
print("Vite restarted and should now recognize all packages")
`);
await sendProgress({
type: 'complete',
message: 'Package installation complete and dev server restarted!',
installedPackages
});
} catch (error) { } catch (error) {
const errorMessage = (error as Error).message; const errorMessage = (error as Error).message;
+5 -5
View File
@@ -8,18 +8,18 @@ declare global {
export async function POST() { export async function POST() {
try { try {
console.log('[kill-sandbox] Killing active sandbox...'); console.log('[kill-sandbox] Stopping active sandbox...');
let sandboxKilled = false; let sandboxKilled = false;
// Kill existing sandbox if any // Stop existing sandbox if any
if (global.activeSandbox) { if (global.activeSandbox) {
try { try {
await global.activeSandbox.close(); await global.activeSandbox.stop();
sandboxKilled = true; sandboxKilled = true;
console.log('[kill-sandbox] Sandbox closed successfully'); console.log('[kill-sandbox] Sandbox stopped successfully');
} catch (e) { } catch (e) {
console.error('[kill-sandbox] Failed to close sandbox:', e); console.error('[kill-sandbox] Failed to stop sandbox:', e);
} }
global.activeSandbox = null; global.activeSandbox = null;
global.sandboxData = null; global.sandboxData = null;
+88 -85
View File
@@ -15,97 +15,100 @@ export async function GET() {
console.log('[monitor-vite-logs] Checking Vite process logs...'); console.log('[monitor-vite-logs] Checking Vite process logs...');
// Check both the error file and recent logs const errors: any[] = [];
const result = await global.activeSandbox.runCode(`
import json
import subprocess
import re
errors = []
# First check the error file
try:
with open('/tmp/vite-errors.json', 'r') as f:
data = json.load(f)
errors.extend(data.get('errors', []))
except:
pass
# Also check if we can get recent Vite logs
try:
# Try to get the Vite process PID
with open('/tmp/vite-process.pid', 'r') as f:
pid = int(f.read().strip())
# Check if process is still running and get its logs // Check if there's an error file from previous runs
# This is a bit hacky but works for our use case try {
result = subprocess.run(['ps', '-p', str(pid)], capture_output=True, text=True) const catResult = await global.activeSandbox.runCommand({
if result.returncode == 0: cmd: 'cat',
# Process is running, try to check for errors in output args: ['/tmp/vite-errors.json']
# Note: We can't easily get stdout/stderr from a running process });
# but we can check if there are new errors
pass if (catResult.exitCode === 0) {
except: const errorFileContent = await catResult.stdout();
pass const data = JSON.parse(errorFileContent);
errors.push(...(data.errors || []));
# Also scan the current console output for any HMR errors }
# This won't catch everything but helps with recent errors } catch (error) {
try: // No error file exists, that's OK
# Check if there's a log file we can read }
import os
log_files = []
for root, dirs, files in os.walk('/tmp'):
for file in files:
if 'vite' in file.lower() and file.endswith('.log'):
log_files.append(os.path.join(root, file))
for log_file in log_files[:5]: # Check up to 5 log files // Look for any Vite-related log files that might contain errors
try: try {
with open(log_file, 'r') as f: const findResult = await global.activeSandbox.runCommand({
content = f.read() cmd: 'find',
# Look for import errors args: ['/tmp', '-name', '*vite*', '-type', 'f']
import_errors = re.findall(r'Failed to resolve import "([^"]+)"', content) });
for pkg in import_errors:
if not pkg.startswith('.'): if (findResult.exitCode === 0) {
# Extract base package name const logFiles = (await findResult.stdout()).split('\n').filter(f => f.trim());
if pkg.startswith('@'):
parts = pkg.split('/') for (const logFile of logFiles.slice(0, 3)) {
final_pkg = '/'.join(parts[:2]) if len(parts) >= 2 else pkg try {
else: const grepResult = await global.activeSandbox.runCommand({
final_pkg = pkg.split('/')[0] cmd: 'grep',
args: ['-i', 'failed to resolve import', logFile]
error_obj = { });
"type": "npm-missing",
"package": final_pkg, if (grepResult.exitCode === 0) {
"message": f"Failed to resolve import \\"{pkg}\\"", const errorLines = (await grepResult.stdout()).split('\n').filter(line => line.trim());
"file": "Unknown"
} for (const line of errorLines) {
// Extract package name from error line
# Avoid duplicates const importMatch = line.match(/"([^"]+)"/);
if not any(e['package'] == error_obj['package'] for e in errors): if (importMatch) {
errors.append(error_obj) const importPath = importMatch[1];
except:
pass // Skip relative imports
except Exception as e: if (!importPath.startsWith('.')) {
print(f"Error scanning logs: {e}") // Extract base package name
let packageName;
# Deduplicate errors if (importPath.startsWith('@')) {
unique_errors = [] const parts = importPath.split('/');
seen_packages = set() packageName = parts.length >= 2 ? parts.slice(0, 2).join('/') : importPath;
for error in errors: } else {
if error.get('package') and error['package'] not in seen_packages: packageName = importPath.split('/')[0];
seen_packages.add(error['package']) }
unique_errors.append(error)
const errorObj = {
print(json.dumps({"errors": unique_errors})) type: "npm-missing",
`, { timeout: 5000 }); package: packageName,
message: `Failed to resolve import "${importPath}"`,
file: "Unknown"
};
// Avoid duplicates
if (!errors.some(e => e.package === errorObj.package)) {
errors.push(errorObj);
}
}
}
}
}
} catch (error) {
// Skip if grep fails
}
}
}
} catch (error) {
// No log files found, that's OK
}
const data = JSON.parse(result.output || '{"errors": []}'); // Deduplicate errors by package name
const uniqueErrors: any[] = [];
const seenPackages = new Set<string>();
for (const error of errors) {
if (error.package && !seenPackages.has(error.package)) {
seenPackages.add(error.package);
uniqueErrors.push(error);
}
}
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
hasErrors: data.errors.length > 0, hasErrors: uniqueErrors.length > 0,
errors: data.errors errors: uniqueErrors
}); });
} catch (error) { } catch (error) {
+36 -106
View File
@@ -15,115 +15,45 @@ export async function POST() {
console.log('[restart-vite] Forcing Vite restart...'); console.log('[restart-vite] Forcing Vite restart...');
// Kill existing Vite process and restart // Kill existing Vite processes
const result = await global.activeSandbox.runCode(` try {
import subprocess await global.activeSandbox.runCommand({
import os cmd: 'pkill',
import signal args: ['-f', 'vite']
import time });
import threading console.log('[restart-vite] Killed existing Vite processes');
import json
import sys // Wait a moment for processes to terminate
await new Promise(resolve => setTimeout(resolve, 2000));
# Kill existing Vite process } catch (error) {
try: console.log('[restart-vite] No existing Vite processes found');
with open('/tmp/vite-process.pid', 'r') as f: }
pid = int(f.read().strip())
os.kill(pid, signal.SIGTERM) // Clear any error tracking files
print("Killed existing Vite process") try {
time.sleep(1) await global.activeSandbox.runCommand({
except: cmd: 'bash',
print("No existing Vite process found") args: ['-c', 'echo \'{"errors": [], "lastChecked": '+ Date.now() +'}\' > /tmp/vite-errors.json']
});
os.chdir('/home/user/app') } catch (error) {
// Ignore if this fails
# Clear error file }
error_file = '/tmp/vite-errors.json'
with open(error_file, 'w') as f: // Start Vite dev server in detached mode
json.dump({"errors": [], "lastChecked": time.time()}, f) const viteProcess = await global.activeSandbox.runCommand({
cmd: 'npm',
# Function to monitor Vite output for errors args: ['run', 'dev'],
def monitor_output(proc, error_file): detached: true
while True: });
line = proc.stderr.readline()
if not line: console.log('[restart-vite] Vite dev server restarted');
break
// Wait for Vite to start up
sys.stdout.write(line) # Also print to console await new Promise(resolve => setTimeout(resolve, 3000));
# Check for import resolution errors
if "Failed to resolve import" in line:
try:
# Extract package name from error
import_match = line.find('"')
if import_match != -1:
end_match = line.find('"', import_match + 1)
if end_match != -1:
package_name = line[import_match + 1:end_match]
# Skip relative imports
if not package_name.startswith('.'):
with open(error_file, 'r') as f:
data = json.load(f)
# Handle scoped packages correctly
if package_name.startswith('@'):
# For @scope/package, keep the scope
pkg_parts = package_name.split('/')
if len(pkg_parts) >= 2:
final_package = '/'.join(pkg_parts[:2])
else:
final_package = package_name
else:
# For regular packages, just take the first part
final_package = package_name.split('/')[0]
error_obj = {
"type": "npm-missing",
"package": final_package,
"message": line.strip(),
"timestamp": time.time()
}
# Avoid duplicates
if not any(e['package'] == error_obj['package'] for e in data['errors']):
data['errors'].append(error_obj)
with open(error_file, 'w') as f:
json.dump(data, f)
print(f"WARNING: Detected missing package: {error_obj['package']}")
except Exception as e:
print(f"Error parsing Vite error: {e}")
# Start Vite with error monitoring
process = subprocess.Popen(
['npm', 'run', 'dev'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
# Start monitoring thread
monitor_thread = threading.Thread(target=monitor_output, args=(process, error_file))
monitor_thread.daemon = True
monitor_thread.start()
print("Vite restarted successfully!")
# Store process info for later
with open('/tmp/vite-process.pid', 'w') as f:
f.write(str(process.pid))
# Wait for Vite to fully start
time.sleep(5)
print("Vite is ready")
`);
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
message: 'Vite restarted successfully', message: 'Vite restarted successfully'
output: result.output
}); });
} catch (error) { } catch (error) {
+21 -20
View File
@@ -1,5 +1,4 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { Sandbox } from '@e2b/code-interpreter';
// Get active sandbox from global state (in production, use a proper state management solution) // Get active sandbox from global state (in production, use a proper state management solution)
declare global { declare global {
@@ -26,30 +25,32 @@ export async function POST(request: NextRequest) {
console.log(`[run-command] Executing: ${command}`); console.log(`[run-command] Executing: ${command}`);
const result = await global.activeSandbox.runCode(` // Parse command and arguments
import subprocess const commandParts = command.trim().split(/\s+/);
import os const cmd = commandParts[0];
const args = commandParts.slice(1);
os.chdir('/home/user/app')
result = subprocess.run(${JSON.stringify(command.split(' '))},
capture_output=True,
text=True,
shell=False)
print("STDOUT:")
print(result.stdout)
if result.stderr:
print("\\nSTDERR:")
print(result.stderr)
print(f"\\nReturn code: {result.returncode}")
`);
const output = result.logs.stdout.join('\n'); // Execute command using Vercel Sandbox
const result = await global.activeSandbox.runCommand({
cmd,
args
});
// Get output streams
const stdout = await result.stdout();
const stderr = await result.stderr();
const output = [
stdout ? `STDOUT:\n${stdout}` : '',
stderr ? `\nSTDERR:\n${stderr}` : '',
`\nExit code: ${result.exitCode}`
].filter(Boolean).join('');
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
output, output,
message: 'Command executed successfully' exitCode: result.exitCode,
message: result.exitCode === 0 ? 'Command executed successfully' : 'Command completed with non-zero exit code'
}); });
} catch (error) { } catch (error) {
+60 -45
View File
@@ -15,55 +15,70 @@ export async function GET(request: NextRequest) {
console.log('[sandbox-logs] Fetching Vite dev server logs...'); console.log('[sandbox-logs] Fetching Vite dev server logs...');
// Get the last N lines of the Vite dev server output // Check if Vite processes are running
const result = await global.activeSandbox.runCode(` const psResult = await global.activeSandbox.runCommand({
import subprocess cmd: 'ps',
import os args: ['aux']
});
# Try to get the Vite process output
try:
# Read the last 100 lines of any log files
log_content = []
# Check if there are any node processes running let viteRunning = false;
ps_result = subprocess.run(['ps', 'aux'], capture_output=True, text=True) let logContent: string[] = [];
vite_processes = [line for line in ps_result.stdout.split('\\n') if 'vite' in line.lower()]
if vite_processes: if (psResult.exitCode === 0) {
log_content.append("Vite is running") const psOutput = await psResult.stdout();
else: const viteProcesses = psOutput.split('\n').filter(line =>
log_content.append("Vite process not found") line.toLowerCase().includes('vite') ||
line.toLowerCase().includes('npm run dev')
# Try to capture recent console output (this is a simplified approach) );
# In a real implementation, you'd want to capture the Vite process output directly
print(json.dumps({ viteRunning = viteProcesses.length > 0;
"hasErrors": False,
"logs": log_content, if (viteRunning) {
"status": "running" if vite_processes else "stopped" logContent.push("Vite is running");
})) logContent.push(...viteProcesses.slice(0, 3)); // Show first 3 processes
except Exception as e: } else {
print(json.dumps({ logContent.push("Vite process not found");
"hasErrors": True, }
"logs": [str(e)],
"status": "error"
}))
`);
try {
const logData = JSON.parse(result.output || '{}');
return NextResponse.json({
success: true,
...logData
});
} catch {
return NextResponse.json({
success: true,
hasErrors: false,
logs: [result.output],
status: 'unknown'
});
} }
// Try to read any recent log files
try {
const findResult = await global.activeSandbox.runCommand({
cmd: 'find',
args: ['/tmp', '-name', '*vite*', '-name', '*.log', '-type', 'f']
});
if (findResult.exitCode === 0) {
const logFiles = (await findResult.stdout()).split('\n').filter(f => f.trim());
for (const logFile of logFiles.slice(0, 2)) {
try {
const catResult = await global.activeSandbox.runCommand({
cmd: 'tail',
args: ['-n', '10', logFile]
});
if (catResult.exitCode === 0) {
const logFileContent = await catResult.stdout();
logContent.push(`--- ${logFile} ---`);
logContent.push(logFileContent);
}
} catch (error) {
// Skip if can't read log file
}
}
}
} catch (error) {
// No log files found, that's OK
}
return NextResponse.json({
success: true,
hasErrors: false,
logs: logContent,
status: viteRunning ? 'running' : 'stopped'
});
} catch (error) { } catch (error) {
console.error('[sandbox-logs] Error:', error); console.error('[sandbox-logs] Error:', error);
return NextResponse.json({ return NextResponse.json({
+4 -3
View File
@@ -22,9 +22,10 @@ export default function SandboxPreview({
useEffect(() => { useEffect(() => {
if (sandboxId && type !== 'console') { if (sandboxId && type !== 'console') {
// In production, this would be the actual E2B sandbox URL // For Vercel Sandbox, we'll receive the full URL from the API
// Format: https://{sandboxId}-{port}.e2b.dev // The URL format is determined by Vercel Sandbox's domain() method
setPreviewUrl(`https://${sandboxId}-${port}.e2b.dev`); // This is just a fallback format - actual URL comes from sandbox.domain(port)
setPreviewUrl(`https://${sandboxId}.vercel-sandbox.dev`);
} }
}, [sandboxId, port, type]); }, [sandboxId, port, type]);
+14 -11
View File
@@ -2,27 +2,30 @@
// This file contains all configurable settings for the application // This file contains all configurable settings for the application
export const appConfig = { export const appConfig = {
// E2B Sandbox Configuration // Vercel Sandbox Configuration
e2b: { vercelSandbox: {
// Sandbox timeout in minutes // Sandbox timeout in minutes
timeoutMinutes: 15, timeoutMinutes: 15,
// Convert to milliseconds for E2B API // Convert to milliseconds for Vercel Sandbox API
get timeoutMs() { get timeoutMs() {
return this.timeoutMinutes * 60 * 1000; return this.timeoutMinutes * 60 * 1000;
}, },
// Vite development server port // Development server port (Vercel Sandbox typically uses 3000 for Next.js/React)
vitePort: 5173, devPort: 3000,
// Time to wait for Vite to be ready (in milliseconds) // Time to wait for dev server to be ready (in milliseconds)
viteStartupDelay: 7000, devServerStartupDelay: 7000,
// Time to wait for CSS rebuild (in milliseconds) // Time to wait for CSS rebuild (in milliseconds)
cssRebuildDelay: 2000, cssRebuildDelay: 2000,
// Default sandbox template (if using templates) // Working directory in sandbox
defaultTemplate: undefined, // or specify a template ID workingDirectory: '/app',
// Default runtime for sandbox
runtime: 'node22' // Available: node22, python3.13, v0-next-shadcn, cua-ubuntu-xfce
}, },
// AI Model Configuration // AI Model Configuration
@@ -35,7 +38,7 @@ export const appConfig = {
'openai/gpt-5', 'openai/gpt-5',
'moonshotai/kimi-k2-instruct', 'moonshotai/kimi-k2-instruct',
'anthropic/claude-sonnet-4-20250514', 'anthropic/claude-sonnet-4-20250514',
'google/gemini-2.5-pro' 'google/gemini-2.0-flash-exp'
], ],
// Model display names // Model display names
@@ -43,7 +46,7 @@ export const appConfig = {
'openai/gpt-5': 'GPT-5', 'openai/gpt-5': 'GPT-5',
'moonshotai/kimi-k2-instruct': 'Kimi K2 Instruct', 'moonshotai/kimi-k2-instruct': 'Kimi K2 Instruct',
'anthropic/claude-sonnet-4-20250514': 'Sonnet 4', 'anthropic/claude-sonnet-4-20250514': 'Sonnet 4',
'google/gemini-2.5-pro': 'Gemini 2.5 Pro' 'google/gemini-2.0-flash-exp': 'Gemini 2.0 Flash (Experimental)'
}, },
// Temperature settings for non-reasoning models // Temperature settings for non-reasoning models
+21 -27
View File
@@ -1,10 +1,10 @@
# Package Detection and Installation Guide # Package Detection and Installation Guide
This document explains how to use the XML-based package detection and installation mechanism in the E2B sandbox environment. This document explains how to use the XML-based package detection and installation mechanism in the Vercel Sandbox environment.
## Overview ## Overview
The E2B sandbox can automatically detect and install packages from XML tags in AI-generated code responses. This mechanism works alongside the existing file detection system. The Vercel Sandbox can automatically detect and install packages from XML tags in AI-generated code responses. This mechanism works alongside the existing file detection system.
## XML Tag Formats ## XML Tag Formats
@@ -196,43 +196,37 @@ Directly installs packages in the sandbox.
3. **Order matters**: Packages are installed before files are created 3. **Order matters**: Packages are installed before files are created
4. **Use commands** for post-installation tasks like building or testing 4. **Use commands** for post-installation tasks like building or testing
## Integration with E2B Sandbox ## Integration with Vercel Sandbox
The package detection mechanism integrates seamlessly with the E2B sandbox: The package detection mechanism integrates seamlessly with the Vercel Sandbox:
1. Packages are installed in `/home/user/app/node_modules` 1. Packages are installed in the sandbox's working directory
2. The Vite dev server is automatically restarted after package installation 2. The development server is automatically restarted after package installation
3. All npm operations run within the sandbox environment 3. All npm operations run within the sandbox environment
4. Package.json is automatically updated with new dependencies 4. Package.json is automatically updated with new dependencies
## E2B Command Execution Methods ## Vercel Sandbox Command Execution Methods
### Method 1: Using runCode() with Python subprocess ### Using runCommand() (Recommended)
```javascript ```javascript
// Current implementation pattern // Direct command execution using Vercel Sandbox API
await global.activeSandbox.runCode(` const result = await global.activeSandbox.runCommand({
import subprocess cmd: 'npm',
import os args: ['install', 'axios']
os.chdir('/home/user/app')
result = subprocess.run(['npm', 'install', 'axios'], capture_output=True, text=True)
print(result.stdout)
`);
```
### Method 2: Using commands.run() directly (Recommended)
```javascript
// Direct command execution - cleaner approach
const result = await global.activeSandbox.commands.run('npm install axios', {
cwd: '/home/user/app',
timeout: 60000
}); });
console.log(result.stdout); const stdout = await result.stdout();
const stderr = await result.stderr();
console.log(stdout);
``` ```
### Command Execution Options ### Command Execution Options
When using `sandbox.commands.run()`, you can specify: When using `sandbox.runCommand()`, you can specify:
- `cmd`: The command to execute
- `args`: Array of arguments
- `detached`: Run in background (for long-running processes)
- `stdout`: Stream for capturing stdout
- `stderr`: Stream for capturing stderr
- `cmd`: Command string to execute - `cmd`: Command string to execute
- `background`: Run in background (true) or wait for completion (false) - `background`: Run in background (true) or wait for completion (false)
- `envs`: Environment variables as key-value pairs - `envs`: Environment variables as key-value pairs
+159 -612
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -8,7 +8,7 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"test:integration": "node tests/e2b-integration.test.js", "test:integration": "node tests/vercel-sandbox-integration.test.js",
"test:api": "node tests/api-endpoints.test.js", "test:api": "node tests/api-endpoints.test.js",
"test:code": "node tests/code-execution.test.js", "test:code": "node tests/code-execution.test.js",
"test:all": "npm run test:integration && npm run test:api && npm run test:code" "test:all": "npm run test:integration && npm run test:api && npm run test:code"
@@ -19,7 +19,7 @@
"@ai-sdk/groq": "^2.0.0", "@ai-sdk/groq": "^2.0.0",
"@ai-sdk/openai": "^2.0.4", "@ai-sdk/openai": "^2.0.4",
"@anthropic-ai/sdk": "^0.57.0", "@anthropic-ai/sdk": "^0.57.0",
"@e2b/code-interpreter": "^1.5.1", "@vercel/sandbox": "^0.0.17",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-switch": "^1.2.5",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
@@ -29,7 +29,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"e2b": "^1.13.2",
"framer-motion": "^12.23.12", "framer-motion": "^12.23.12",
"groq-sdk": "^0.29.0", "groq-sdk": "^0.29.0",
"lucide-react": "^0.532.0", "lucide-react": "^0.532.0",