From 415179c23447b38ae4f3a0b3a574b1d67b9f018f Mon Sep 17 00:00:00 2001 From: MFCo Date: Tue, 26 Aug 2025 13:01:46 +0200 Subject: [PATCH] Migrate to Vercel Sandbox using OIDC --- .gitignore | 1 + README.md | 2 +- app/api/apply-ai-code-stream/route.ts | 79 +- app/api/apply-ai-code/route.ts | 59 +- app/api/create-ai-sandbox/route.ts | 324 ++++---- app/api/create-zip/route.ts | 73 +- app/api/detect-and-install-packages/route.ts | 200 ++--- app/api/generate-ai-code-stream/route.ts | 52 +- app/api/get-sandbox-files/route.ts | 135 ++-- app/api/install-packages/route.ts | 340 +++----- app/api/kill-sandbox/route.ts | 10 +- app/api/monitor-vite-logs/route.ts | 173 +++-- app/api/restart-vite/route.ts | 142 +--- app/api/run-command/route.ts | 41 +- app/api/sandbox-logs/route.ts | 105 +-- components/SandboxPreview.tsx | 7 +- config/app.config.ts | 25 +- docs/PACKAGE_DETECTION_GUIDE.md | 48 +- package-lock.json | 771 ++++--------------- package.json | 6 +- 20 files changed, 972 insertions(+), 1621 deletions(-) diff --git a/.gitignore b/.gitignore index ac59fa8..79f47d8 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ e2b-template-* *.temp repomix-output.txt bun.lockb +.env*.local diff --git a/README.md b/README.md index 803cc92..b7a51d1 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ npm install 2. **Add `.env.local`** ```env # 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) # Optional (need at least one AI provider) diff --git a/app/api/apply-ai-code-stream/route.ts b/app/api/apply-ai-code-stream/route.ts index c91bf11..ac382d6 100644 --- a/app/api/apply-ai-code-stream/route.ts +++ b/app/api/apply-ai-code-stream/route.ts @@ -1,5 +1,5 @@ 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 { ConversationState } from '@/types/conversation'; @@ -525,7 +525,6 @@ export async function POST(request: NextRequest) { normalizedPath = 'src/' + normalizedPath; } - const fullPath = `/home/user/app/${normalizedPath}`; const isUpdate = global.existingFiles.has(normalizedPath); // 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, ''); } - // Write the file using Python (code-interpreter SDK) - const escapedContent = fileContent - .replace(/\\/g, '\\\\') - .replace(/"""/g, '\\"\\"\\"') - .replace(/\$/g, '\\$'); + // Create directory if needed + const dirPath = normalizedPath.includes('/') ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/')) : ''; + if (dirPath) { + await sandboxInstance.runCommand({ + cmd: 'mkdir', + args: ['-p', dirPath] + }); + } - await sandboxInstance.runCode(` -import os -os.makedirs(os.path.dirname("${fullPath}"), exist_ok=True) -with open("${fullPath}", 'w') as f: - f.write("""${escapedContent}""") -print(f"File written: ${fullPath}") - `); + // Write the file using Vercel Sandbox writeFiles + await sandboxInstance.writeFiles([{ + path: normalizedPath, + content: Buffer.from(fileContent) + }]); // Update file cache if (global.sandboxState?.fileCache) { @@ -599,28 +599,39 @@ print(f"File written: ${fullPath}") action: 'executing' }); - // Use E2B commands.run() for cleaner execution - const result = await sandboxInstance.commands.run(cmd, { - cwd: '/home/user/app', - timeout: 60, - on_stdout: async (data: string) => { - await sendProgress({ - type: 'command-output', - command: cmd, - output: data, - stream: 'stdout' - }); - }, - on_stderr: async (data: string) => { - await sendProgress({ - type: 'command-output', - command: cmd, - output: data, - stream: 'stderr' - }); - } + // Parse command and arguments for Vercel Sandbox + const commandParts = cmd.trim().split(/\s+/); + const cmdName = commandParts[0]; + const args = commandParts.slice(1); + + // Use Vercel Sandbox runCommand + const result = await sandboxInstance.runCommand({ + cmd: cmdName, + args }); + // 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) { results.commandsExecuted.push(cmd); } diff --git a/app/api/apply-ai-code/route.ts b/app/api/apply-ai-code/route.ts index f00f08a..f051da4 100644 --- a/app/api/apply-ai-code/route.ts +++ b/app/api/apply-ai-code/route.ts @@ -432,15 +432,12 @@ function App() { export default App;`; try { - await global.activeSandbox.runCode(` -file_path = "/home/user/app/src/App.jsx" -file_content = """${appContent.replace(/"/g, '\\"').replace(/\n/g, '\\n')}""" - -with open(file_path, 'w') as f: - f.write(file_content) - -print(f"Auto-generated: {file_path}") - `); + await global.activeSandbox.writeFiles([{ + path: 'src/App.jsx', + content: Buffer.from(appContent) + }]); + + console.log('Auto-generated: src/App.jsx'); results.filesCreated.push('src/App.jsx (auto-generated)'); } catch (error) { 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) { try { - await global.activeSandbox.runCode(` -file_path = "/home/user/app/src/index.css" -file_content = """@tailwind base; + const indexCssContent = `@tailwind base; @tailwind components; @tailwind utilities; @@ -483,13 +478,14 @@ body { margin: 0; min-width: 320px; min-height: 100vh; -}""" +}`; -with open(file_path, 'w') as f: - f.write(file_content) - -print(f"Auto-generated: {file_path}") - `); + await global.activeSandbox.writeFiles([{ + path: 'src/index.css', + content: Buffer.from(indexCssContent) + }]); + + console.log('Auto-generated: src/index.css'); results.filesCreated.push('src/index.css (with Tailwind)'); } catch (error) { results.errors.push('Failed to create index.css with Tailwind'); @@ -500,15 +496,24 @@ print(f"Auto-generated: {file_path}") // Execute commands for (const cmd of parsed.commands) { try { - await global.activeSandbox.runCode(` -import subprocess -os.chdir('/home/user/app') -result = subprocess.run(${JSON.stringify(cmd.split(' '))}, capture_output=True, text=True) -print(f"Executed: ${cmd}") -print(result.stdout) -if result.stderr: - print(f"Errors: {result.stderr}") - `); + // Parse command and arguments + const commandParts = cmd.trim().split(/\s+/); + const cmdName = commandParts[0]; + const args = commandParts.slice(1); + + // Execute command using Vercel Sandbox + const result = await global.activeSandbox.runCommand({ + 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); } catch (error) { results.errors.push(`Failed to execute ${cmd}: ${(error as Error).message}`); diff --git a/app/api/create-ai-sandbox/route.ts b/app/api/create-ai-sandbox/route.ts index 257ce1d..3bb1bcb 100644 --- a/app/api/create-ai-sandbox/route.ts +++ b/app/api/create-ai-sandbox/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import { Sandbox } from '@e2b/code-interpreter'; +import { Sandbox } from '@vercel/sandbox'; import type { SandboxState } from '@/types/sandbox'; import { appConfig } from '@/config/app.config'; @@ -15,15 +15,15 @@ export async function POST() { let sandbox: any = null; try { - console.log('[create-ai-sandbox] Creating base sandbox...'); + console.log('[create-ai-sandbox] Creating Vercel sandbox...'); // Kill existing sandbox if any if (global.activeSandbox) { - console.log('[create-ai-sandbox] Killing existing sandbox...'); + console.log('[create-ai-sandbox] Stopping existing sandbox...'); try { - await global.activeSandbox.kill(); + await global.activeSandbox.stop(); } catch (e) { - console.error('Failed to close existing sandbox:', e); + console.error('Failed to stop existing sandbox:', e); } global.activeSandbox = null; } @@ -35,81 +35,86 @@ export async function POST() { global.existingFiles = new Set(); } - // Create base sandbox - we'll set up Vite ourselves for full control - console.log(`[create-ai-sandbox] Creating base E2B sandbox with ${appConfig.e2b.timeoutMinutes} minute timeout...`); - sandbox = await Sandbox.create({ - apiKey: process.env.E2B_API_KEY, - timeoutMs: appConfig.e2b.timeoutMs + // Create Vercel sandbox + console.log(`[create-ai-sandbox] Creating Vercel sandbox with ${appConfig.vercelSandbox.timeoutMinutes} minute timeout...`); + sandbox = await Sandbox.create({ + timeout: appConfig.vercelSandbox.timeoutMs, + runtime: appConfig.vercelSandbox.runtime, + ports: [appConfig.vercelSandbox.devPort] }); - const sandboxId = (sandbox as any).sandboxId || Date.now().toString(); - const host = (sandbox as any).getHost(appConfig.e2b.vitePort); - + const sandboxId = sandbox.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...'); - // Write all files in a single Python script to avoid multiple executions - const setupScript = ` -import os -import json + // First, change to the working directory + await sandbox.runCommand('pwd'); + const workDir = appConfig.vercelSandbox.workingDirectory; + + // 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 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' + // Create the Vite config content with the proper hostname (using string concatenation) + const viteConfigContent = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -// E2B-compatible Vite configuration +// Vercel Sandbox compatible Vite configuration export default defineConfig({ plugins: [react()], server: { host: '0.0.0.0', - port: 5173, + port: ${appConfig.vercelSandbox.devPort}, strictPort: true, - hmr: false, - allowedHosts: ['.e2b.app', 'localhost', '127.0.0.1'] + hmr: true, + 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: - f.write(vite_config) -print('✓ vite.config.js') - -# Tailwind config - standard without custom design tokens -tailwind_config = """/** @type {import('tailwindcss').Config} */ + // Create the project files (now we have the sandbox hostname) + const projectFiles = [ + { + path: 'package.json', + content: Buffer.from(JSON.stringify({ + "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 { content: [ "./index.html", @@ -119,26 +124,20 @@ export default { extend: {}, }, plugins: [], -}""" - -with open('/home/user/app/tailwind.config.js', 'w') as f: - f.write(tailwind_config) -print('✓ tailwind.config.js') - -# PostCSS config -postcss_config = """export default { +}`) + }, + { + path: 'postcss.config.js', + content: Buffer.from(`export default { plugins: { tailwindcss: {}, autoprefixer: {}, }, -}""" - -with open('/home/user/app/postcss.config.js', 'w') as f: - f.write(postcss_config) -print('✓ postcss.config.js') - -# Index.html -index_html = """ +}`) + }, + { + path: 'index.html', + content: Buffer.from(` @@ -149,14 +148,11 @@ index_html = """
-""" - -with open('/home/user/app/index.html', 'w') as f: - f.write(index_html) -print('✓ index.html') - -# Main.jsx -main_jsx = """import React from 'react' +`) + }, + { + path: 'src/main.jsx', + content: Buffer.from(`import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.jsx' import './index.css' @@ -165,19 +161,18 @@ ReactDOM.createRoot(document.getElementById('root')).render( , -)""" - -with open('/home/user/app/src/main.jsx', 'w') as f: - f.write(main_jsx) -print('✓ src/main.jsx') - -# App.jsx with explicit Tailwind test -app_jsx = """function App() { +)`) + }, + { + path: 'src/App.jsx', + content: Buffer.from(`function App() { return (
+

+ Sandbox Ready +

- Sandbox Ready
Start building your React app with Vite and Tailwind CSS!

@@ -185,14 +180,11 @@ app_jsx = """function App() { ) } -export default App""" - -with open('/home/user/app/src/App.jsx', 'w') as f: - f.write(app_jsx) -print('✓ src/App.jsx') - -# Index.css with explicit Tailwind directives -index_css = """@tailwind base; +export default App`) + }, + { + path: 'src/index.css', + content: Buffer.from(`@tailwind base; @tailwind components; @tailwind utilities; @@ -216,99 +208,53 @@ index_css = """@tailwind base; body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background-color: rgb(17 24 39); -}""" +}`) + } + ]; -with open('/home/user/app/src/index.css', 'w') as f: - f.write(index_css) -print('✓ src/index.css') - -print('\\nAll files created successfully!') -`; - - // Execute the setup script - await sandbox.runCode(setupScript); + // Create directory structure first + await sandbox.runCommand({ + cmd: 'mkdir', + args: ['-p', 'src'] + }); + + // Write all files + await sandbox.writeFiles(projectFiles); + console.log('[create-ai-sandbox] ✓ Project files created'); // Install dependencies console.log('[create-ai-sandbox] Installing dependencies...'); - await sandbox.runCode(` -import subprocess -import sys - -print('Installing npm packages...') -result = subprocess.run( - ['npm', 'install'], - cwd='/home/user/app', - 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 - `); + const installResult = await sandbox.runCommand({ + cmd: 'npm', + args: ['install', '--loglevel', 'info'] + }); + if (installResult.exitCode === 0) { + console.log('[create-ai-sandbox] ✓ Dependencies installed successfully'); + } else { + console.log('[create-ai-sandbox] ⚠ Warning: npm install had issues but continuing...'); + } - // Start Vite dev server + // Start Vite dev server in detached mode console.log('[create-ai-sandbox] Starting Vite dev server...'); - await sandbox.runCode(` -import subprocess -import os -import time - -os.chdir('/home/user/app') - -# 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...') - `); + const viteProcess = await sandbox.runCommand({ + cmd: 'npm', + args: ['run', 'dev'], + detached: true + }); + + console.log('[create-ai-sandbox] ✓ Vite dev server started'); // Wait for Vite to be fully ready - await new Promise(resolve => setTimeout(resolve, appConfig.e2b.viteStartupDelay)); - - // 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') - `); + await new Promise(resolve => setTimeout(resolve, appConfig.vercelSandbox.devServerStartupDelay)); // Store sandbox globally global.activeSandbox = sandbox; global.sandboxData = { 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 global.sandboxState = { fileCache: { @@ -319,7 +265,7 @@ print('✓ Tailwind CSS should be loaded') sandbox, sandboxData: { 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('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({ success: true, sandboxId, - url: `https://${host}`, - message: 'Sandbox created and Vite React app initialized' + url: sandboxUrl, + message: 'Vercel sandbox created and Vite React app initialized' }); } catch (error) { @@ -348,9 +294,9 @@ print('✓ Tailwind CSS should be loaded') // Clean up on error if (sandbox) { try { - await sandbox.kill(); + await sandbox.stop(); } catch (e) { - console.error('Failed to close sandbox on error:', e); + console.error('Failed to stop sandbox on error:', e); } } diff --git a/app/api/create-zip/route.ts b/app/api/create-zip/route.ts index 221c843..2030a39 100644 --- a/app/api/create-zip/route.ts +++ b/app/api/create-zip/route.ts @@ -15,41 +15,37 @@ export async function POST(request: NextRequest) { console.log('[create-zip] Creating project zip...'); - // Create zip file in sandbox - const result = await global.activeSandbox.runCode(` -import zipfile -import os -import json - -os.chdir('/home/user/app') - -# Create zip file -with zipfile.ZipFile('/tmp/project.zip', 'w', zipfile.ZIP_DEFLATED) as zipf: - for root, dirs, files in os.walk('.'): - # Skip node_modules and .git - dirs[:] = [d for d in dirs if d not in ['node_modules', '.git', '.next', 'dist']] - - for file in files: - file_path = os.path.join(root, file) - arcname = os.path.relpath(file_path, '.') - zipf.write(file_path, arcname) - -# Get file size -file_size = os.path.getsize('/tmp/project.zip') -print(f" Created project.zip ({file_size} bytes)") - `); + // Create zip file in sandbox using standard commands + const zipResult = await global.activeSandbox.runCommand({ + cmd: 'bash', + args: ['-c', `zip -r /tmp/project.zip . -x "node_modules/*" ".git/*" ".next/*" "dist/*" "build/*" "*.log"`] + }); + + if (zipResult.exitCode !== 0) { + const error = await zipResult.stderr(); + throw new Error(`Failed to create zip: ${error}`); + } + + const sizeResult = await global.activeSandbox.runCommand({ + cmd: 'bash', + args: ['-c', `ls -la /tmp/project.zip | awk '{print $5}'`] + }); + + const fileSize = await sizeResult.stdout(); + console.log(`[create-zip] Created project.zip (${fileSize.trim()} bytes)`); // Read the zip file and convert to base64 - const readResult = await global.activeSandbox.runCode(` -import base64 - -with open('/tmp/project.zip', 'rb') as f: - content = f.read() - encoded = base64.b64encode(content).decode('utf-8') - print(encoded) - `); + const readResult = await global.activeSandbox.runCommand({ + cmd: 'base64', + args: ['/tmp/project.zip'] + }); - 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 const dataUrl = `data:application/zip;base64,${base64Content}`; @@ -57,15 +53,18 @@ with open('/tmp/project.zip', 'rb') as f: return NextResponse.json({ success: true, dataUrl, - fileName: 'e2b-project.zip', + fileName: 'vercel-sandbox-project.zip', message: 'Zip file created successfully' }); } catch (error) { console.error('[create-zip] Error:', error); - return NextResponse.json({ - success: false, - error: (error as Error).message - }, { status: 500 }); + return NextResponse.json( + { + success: false, + error: (error as Error).message + }, + { status: 500 } + ); } } \ No newline at end of file diff --git a/app/api/detect-and-install-packages/route.ts b/app/api/detect-and-install-packages/route.ts index 12211b6..facbd51 100644 --- a/app/api/detect-and-install-packages/route.ts +++ b/app/api/detect-and-install-packages/route.ts @@ -64,15 +64,7 @@ export async function POST(request: NextRequest) { const builtins = ['fs', 'path', 'http', 'https', 'crypto', 'stream', 'util', 'os', 'url', 'querystring', 'child_process']; if (builtins.includes(imp)) return false; - // Extract package name (handle scoped packages and subpaths) - 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; - } + return true; }); // Extract just the package names (without subpaths) @@ -101,153 +93,89 @@ export async function POST(request: NextRequest) { } // Check which packages are already installed - const checkResult = await global.activeSandbox.runCode(` -import os -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}" + const installed: string[] = []; + const missing: string[] = []; - if os.path.exists(package_path): - installed.append(package) - else: - missing.append(package) + for (const packageName of uniquePackages) { + try { + const checkResult = await global.activeSandbox.runCommand({ + 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 = { - 'installed': installed, - 'missing': missing -} + console.log('[detect-and-install-packages] Package status:', { installed, missing }); -print(json.dumps(result)) - `); - - const status = JSON.parse(checkResult.logs.stdout.join('')); - console.log('[detect-and-install-packages] Package status:', status); - - if (status.missing.length === 0) { + if (missing.length === 0) { return NextResponse.json({ success: true, packagesInstalled: [], - packagesAlreadyInstalled: status.installed, + packagesAlreadyInstalled: installed, message: 'All packages already installed' }); } // 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(` -import subprocess -import os -import json + const installResult = await global.activeSandbox.runCommand({ + cmd: 'npm', + args: ['install', '--save', ...missing] + }); -os.chdir('/home/user/app') -packages_to_install = ${JSON.stringify(status.missing)} - -# 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}" + const stdout = await installResult.stdout(); + const stderr = await installResult.stderr(); - if os.path.exists(package_path): - installed.append(package) - print(f"✓ Verified installation of {package}") - 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 - }; + console.log('[detect-and-install-packages] Install stdout:', stdout); + if (stderr) { + console.log('[detect-and-install-packages] Install stderr:', stderr); } - if (installStatus.failed.length > 0) { - console.error('[detect-and-install-packages] Failed to install:', installStatus.failed); + // Verify installation + 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({ success: true, - packagesInstalled: installStatus.installed, - packagesFailed: installStatus.failed, - packagesAlreadyInstalled: status.installed, - message: `Installed ${installStatus.installed.length} packages`, - logs: installResult.logs.stdout.join('\n') + packagesInstalled: finalInstalled, + packagesFailed: failed, + packagesAlreadyInstalled: installed, + message: `Installed ${finalInstalled.length} packages`, + logs: stdout }); } catch (error) { diff --git a/app/api/generate-ai-code-stream/route.ts b/app/api/generate-ai-code-stream/route.ts index eaae15d..9a7fa4a 100644 --- a/app/api/generate-ai-code-stream/route.ts +++ b/app/api/generate-ai-code-stream/route.ts @@ -11,6 +11,9 @@ import { FileManifest } from '@/types/file-manifest'; import type { ConversationState, ConversationMessage, ConversationEdit } from '@/types/conversation'; import { appConfig } from '@/config/app.config'; +// Force dynamic route to enable streaming +export const dynamic = 'force-dynamic'; + const groq = createGroq({ apiKey: process.env.GROQ_API_KEY, }); @@ -1156,9 +1159,21 @@ CRITICAL: When files are provided in the context: const isGoogle = model.startsWith('google/'); const isOpenAI = model.startsWith('openai/gpt-5'); const modelProvider = isAnthropic ? anthropic : (isOpenAI ? openai : (isGoogle ? googleGenerativeAI : groq)); - const actualModel = isAnthropic ? model.replace('anthropic/', '') : - (model === 'openai/gpt-5') ? 'gpt-5' : - (isGoogle ? model.replace('google/', '') : model); + + // Fix model name transformation for different providers + 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 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 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, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', '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', }, }); diff --git a/app/api/get-sandbox-files/route.ts b/app/api/get-sandbox-files/route.ts index d892046..9dad8b1 100644 --- a/app/api/get-sandbox-files/route.ts +++ b/app/api/get-sandbox-files/route.ts @@ -18,58 +18,81 @@ export async function GET() { console.log('[get-sandbox-files] Fetching and analyzing file structure...'); - // Get all React/JS/CSS files - const result = await global.activeSandbox.runCode(` -import os -import json - -def get_files_content(directory='/home/user/app', extensions=['.jsx', '.js', '.tsx', '.ts', '.css', '.json']): - files_content = {} + // Get list of all relevant files + const findResult = await global.activeSandbox.runCommand({ + cmd: 'find', + args: [ + '.', + '-name', 'node_modules', '-prune', '-o', + '-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): - # Skip node_modules and other unwanted directories - dirs[:] = [d for d in dirs if d not in ['node_modules', '.git', 'dist', 'build']] + if (findResult.exitCode !== 0) { + throw new Error('Failed to list files'); + } + + 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 = {}; + + 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 any(file.endswith(ext) for ext in extensions): - file_path = os.path.join(root, file) - relative_path = os.path.relpath(file_path, '/home/user/app') - - try: - with open(file_path, 'r') as f: - content = f.read() - # Only include files under 10KB to avoid huge responses - if len(content) < 10000: - files_content[relative_path] = content - except: - pass + if (statResult.exitCode === 0) { + const fileSize = parseInt(await statResult.stdout()); + + // Only read files smaller than 10KB + if (fileSize < 10000) { + const catResult = await global.activeSandbox.runCommand({ + cmd: 'cat', + args: [filePath] + }); + + if (catResult.exitCode === 0) { + const content = await catResult.stdout(); + // 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 the files -files = get_files_content() - -# Also get the directory structure -structure = [] -for root, dirs, files in os.walk('/home/user/app'): - level = root.replace('/home/user/app', '').count(os.sep) - indent = ' ' * 2 * level - 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); + // Get directory structure + const treeResult = await global.activeSandbox.runCommand({ + cmd: 'find', + args: ['.', '-type', 'd', '-not', '-path', '*/node_modules*', '-not', '-path', '*/.git*'] + }); + + let structure = ''; + if (treeResult.exitCode === 0) { + const dirs = (await treeResult.stdout()).split('\n').filter(d => d.trim()); + structure = dirs.slice(0, 50).join('\n'); // Limit to 50 lines + } // Build enhanced file manifest const fileManifest: FileManifest = { @@ -82,12 +105,12 @@ print(json.dumps(result)) }; // Process each file - for (const [relativePath, content] of Object.entries(parsedResult.files)) { - const fullPath = `/home/user/app/${relativePath}`; + for (const [relativePath, content] of Object.entries(filesContent)) { + const fullPath = `/${relativePath}`; // Create base file info const fileInfo: FileInfo = { - content: content as string, + content: content, type: 'utility', path: fullPath, relativePath, @@ -96,7 +119,7 @@ print(json.dumps(result)) // Parse JavaScript/JSX files if (relativePath.match(/\.(jsx?|tsx?)$/)) { - const parseResult = parseJavaScriptFile(content as string, fullPath); + const parseResult = parseJavaScriptFile(content, fullPath); Object.assign(fileInfo, parseResult); // Identify entry point @@ -132,9 +155,9 @@ print(json.dumps(result)) return NextResponse.json({ success: true, - files: parsedResult.files, - structure: parsedResult.structure, - fileCount: Object.keys(parsedResult.files).length, + files: filesContent, + structure, + fileCount: Object.keys(filesContent).length, manifest: fileManifest, }); diff --git a/app/api/install-packages/route.ts b/app/api/install-packages/route.ts index 59d305e..dd8eb82 100644 --- a/app/api/install-packages/route.ts +++ b/app/api/install-packages/route.ts @@ -1,5 +1,4 @@ import { NextRequest, NextResponse } from 'next/server'; -import { Sandbox } from '@e2b/code-interpreter'; declare global { var activeSandbox: any; @@ -36,23 +35,8 @@ export async function POST(request: NextRequest) { console.log(`[install-packages] Cleaned:`, validPackages); } - // Try to get sandbox - either from global or reconnect - let 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 }); - } - } + // Get active sandbox + const sandbox = global.activeSandbox; if (!sandbox) { return NextResponse.json({ @@ -61,7 +45,7 @@ export async function POST(request: NextRequest) { }, { status: 400 }); } - console.log('[install-packages] Installing packages:', packages); + console.log('[install-packages] Installing packages:', validPackages); // Create a response stream for real-time updates const encoder = new TextEncoder(); @@ -83,23 +67,20 @@ export async function POST(request: NextRequest) { packages: validPackages }); - // Kill any existing Vite process first + // Stop any existing development server first await sendProgress({ type: 'status', message: 'Stopping development server...' }); - await sandboxInstance.runCode(` -import subprocess -import os -import signal - -# Try to kill any existing Vite process -try: - with open('/tmp/vite-process.pid', 'r') as f: - pid = int(f.read().strip()) - os.kill(pid, signal.SIGTERM) - print("Stopped existing Vite process") -except: - print("No existing Vite process found") - `); + try { + // Try to kill any running dev server processes + await sandboxInstance.runCommand({ + cmd: 'pkill', + args: ['-f', 'vite'] + }); + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait a bit + } catch (error) { + // It's OK if no process is found + console.log('[install-packages] No existing dev server found'); + } // Check which packages are already installed await sendProgress({ @@ -107,70 +88,51 @@ except: 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; - // Check if checkResult has the expected structure - if (checkResult && checkResult.results && checkResult.results[0] && checkResult.results[0].text) { - const outputLines = checkResult.results[0].text.split('\n'); - for (const line of outputLines) { - if (line.startsWith('NEED_INSTALL:')) { - try { - packagesToInstall = JSON.parse(line.substring('NEED_INSTALL:'.length)); - } catch (e) { - console.error('Failed to parse packages to install:', e); + try { + // Read package.json to check existing dependencies + const catResult = await sandboxInstance.runCommand({ + cmd: 'cat', + args: ['package.json'] + }); + if (catResult.exitCode === 0) { + const packageJsonContent = await catResult.stdout(); + 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 { - console.error('[install-packages] Invalid checkResult structure:', checkResult); + } catch (error) { + console.error('[install-packages] Error checking existing packages:', error); // If we can't check, just try to install all packages packagesToInstall = validPackages; } - if (packagesToInstall.length === 0) { await sendProgress({ type: 'success', @@ -178,164 +140,104 @@ except Exception as e: installedPackages: [], 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; } // 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({ type: 'info', message: `Installing ${packagesToInstall.length} new package(s): ${packagesToInstall.join(', ')}` }); - const installResult = await sandboxInstance.runCode(` -import subprocess -import os - -os.chdir('/home/user/app') - -# 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") + // Run npm install + const installArgs = ['install', '--legacy-peer-deps', ...packagesToInstall]; + const installResult = await sandboxInstance.runCommand({ + cmd: 'npm', + args: installArgs + }); -print(f"\\nVerified installed packages: {installed}") - `, { timeout: 60000 }); // 60 second timeout for npm install + // Get install output + const stdout = await installResult.stdout(); + const stderr = await installResult.stderr(); - // Send npm output - const output = installResult?.output || installResult?.logs?.stdout?.join('\n') || ''; - const npmOutputLines = output.split('\n').filter((line: string) => line.trim()); - for (const line of npmOutputLines) { - if (line.includes('STDERR:')) { - const errorMsg = line.replace('STDERR:', '').trim(); - if (errorMsg && errorMsg !== 'undefined') { - await sendProgress({ type: 'error', message: errorMsg }); + if (stdout) { + const lines = stdout.split('\n').filter(line => line.trim()); + for (const line of lines) { + if (line.includes('npm WARN')) { + await sendProgress({ type: 'warning', message: line }); + } else if (line.trim()) { + await sendProgress({ type: 'output', message: line }); } - } 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 - const installedMatch = output.match(/Verified installed packages: \[(.*?)\]/); - let installedPackages: string[] = []; - - if (installedMatch && installedMatch[1]) { - installedPackages = installedMatch[1] - .split(',') - .map((p: string) => p.trim().replace(/'/g, '')) - .filter((p: string) => p.length > 0); + if (stderr) { + const errorLines = stderr.split('\n').filter(line => line.trim()); + for (const line of errorLines) { + if (line.includes('ERESOLVE')) { + await sendProgress({ + type: 'warning', + message: `Dependency conflict resolved with --legacy-peer-deps: ${line}` + }); + } else if (line.trim()) { + await sendProgress({ type: 'error', message: line }); + } + } } - if (installedPackages.length > 0) { + if (installResult.exitCode === 0) { await sendProgress({ type: 'success', - message: `Successfully installed: ${installedPackages.join(', ')}`, - installedPackages + message: `Successfully installed: ${packagesToInstall.join(', ')}`, + installedPackages: packagesToInstall }); } else { await sendProgress({ 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 sandboxInstance.runCode(` -import subprocess -import os -import time - -os.chdir('/home/user/app') - -# 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 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 - }); + try { + const devServerProcess = await sandboxInstance.runCommand({ + cmd: 'npm', + args: ['run', 'dev'], + detached: true + }); + + // Wait a bit for the server to start + await new Promise(resolve => setTimeout(resolve, 3000)); + + await sendProgress({ + type: 'complete', + message: 'Package installation complete and dev server restarted!', + installedPackages: packagesToInstall + }); + } catch (error) { + await sendProgress({ + type: 'error', + message: `Failed to restart dev server: ${(error as Error).message}` + }); + } } catch (error) { const errorMessage = (error as Error).message; diff --git a/app/api/kill-sandbox/route.ts b/app/api/kill-sandbox/route.ts index 70d005a..accaf7e 100644 --- a/app/api/kill-sandbox/route.ts +++ b/app/api/kill-sandbox/route.ts @@ -8,18 +8,18 @@ declare global { export async function POST() { try { - console.log('[kill-sandbox] Killing active sandbox...'); + console.log('[kill-sandbox] Stopping active sandbox...'); let sandboxKilled = false; - // Kill existing sandbox if any + // Stop existing sandbox if any if (global.activeSandbox) { try { - await global.activeSandbox.close(); + await global.activeSandbox.stop(); sandboxKilled = true; - console.log('[kill-sandbox] Sandbox closed successfully'); + console.log('[kill-sandbox] Sandbox stopped successfully'); } catch (e) { - console.error('[kill-sandbox] Failed to close sandbox:', e); + console.error('[kill-sandbox] Failed to stop sandbox:', e); } global.activeSandbox = null; global.sandboxData = null; diff --git a/app/api/monitor-vite-logs/route.ts b/app/api/monitor-vite-logs/route.ts index ef537f0..3cb2a9b 100644 --- a/app/api/monitor-vite-logs/route.ts +++ b/app/api/monitor-vite-logs/route.ts @@ -15,97 +15,100 @@ export async function GET() { console.log('[monitor-vite-logs] Checking Vite process logs...'); - // Check both the error file and recent logs - 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()) + const errors: any[] = []; - # Check if process is still running and get its logs - # This is a bit hacky but works for our use case - result = subprocess.run(['ps', '-p', str(pid)], capture_output=True, text=True) - if result.returncode == 0: - # Process is running, try to check for errors in output - # Note: We can't easily get stdout/stderr from a running process - # but we can check if there are new errors - pass -except: - pass - -# Also scan the current console output for any HMR errors -# This won't catch everything but helps with recent errors -try: - # 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)) + // Check if there's an error file from previous runs + try { + const catResult = await global.activeSandbox.runCommand({ + cmd: 'cat', + args: ['/tmp/vite-errors.json'] + }); + + if (catResult.exitCode === 0) { + const errorFileContent = await catResult.stdout(); + const data = JSON.parse(errorFileContent); + errors.push(...(data.errors || [])); + } + } catch (error) { + // No error file exists, that's OK + } - for log_file in log_files[:5]: # Check up to 5 log files - try: - with open(log_file, 'r') as f: - content = f.read() - # Look for import errors - import_errors = re.findall(r'Failed to resolve import "([^"]+)"', content) - for pkg in import_errors: - if not pkg.startswith('.'): - # Extract base package name - if pkg.startswith('@'): - parts = pkg.split('/') - final_pkg = '/'.join(parts[:2]) if len(parts) >= 2 else pkg - else: - final_pkg = pkg.split('/')[0] - - error_obj = { - "type": "npm-missing", - "package": final_pkg, - "message": f"Failed to resolve import \\"{pkg}\\"", - "file": "Unknown" - } - - # Avoid duplicates - if not any(e['package'] == error_obj['package'] for e in errors): - errors.append(error_obj) - except: - pass -except Exception as e: - print(f"Error scanning logs: {e}") - -# Deduplicate errors -unique_errors = [] -seen_packages = set() -for error in errors: - if error.get('package') and error['package'] not in seen_packages: - seen_packages.add(error['package']) - unique_errors.append(error) - -print(json.dumps({"errors": unique_errors})) - `, { timeout: 5000 }); + // Look for any Vite-related log files that might contain errors + try { + const findResult = await global.activeSandbox.runCommand({ + cmd: 'find', + args: ['/tmp', '-name', '*vite*', '-type', 'f'] + }); + + if (findResult.exitCode === 0) { + const logFiles = (await findResult.stdout()).split('\n').filter(f => f.trim()); + + for (const logFile of logFiles.slice(0, 3)) { + try { + const grepResult = await global.activeSandbox.runCommand({ + cmd: 'grep', + args: ['-i', 'failed to resolve import', logFile] + }); + + if (grepResult.exitCode === 0) { + const errorLines = (await grepResult.stdout()).split('\n').filter(line => line.trim()); + + for (const line of errorLines) { + // Extract package name from error line + const importMatch = line.match(/"([^"]+)"/); + if (importMatch) { + const importPath = importMatch[1]; + + // Skip relative imports + if (!importPath.startsWith('.')) { + // Extract base package name + let packageName; + if (importPath.startsWith('@')) { + const parts = importPath.split('/'); + packageName = parts.length >= 2 ? parts.slice(0, 2).join('/') : importPath; + } else { + packageName = importPath.split('/')[0]; + } + + const errorObj = { + type: "npm-missing", + 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(); + + for (const error of errors) { + if (error.package && !seenPackages.has(error.package)) { + seenPackages.add(error.package); + uniqueErrors.push(error); + } + } return NextResponse.json({ success: true, - hasErrors: data.errors.length > 0, - errors: data.errors + hasErrors: uniqueErrors.length > 0, + errors: uniqueErrors }); } catch (error) { diff --git a/app/api/restart-vite/route.ts b/app/api/restart-vite/route.ts index ca6b4ba..64bf973 100644 --- a/app/api/restart-vite/route.ts +++ b/app/api/restart-vite/route.ts @@ -15,115 +15,45 @@ export async function POST() { console.log('[restart-vite] Forcing Vite restart...'); - // Kill existing Vite process and restart - const result = await global.activeSandbox.runCode(` -import subprocess -import os -import signal -import time -import threading -import json -import sys - -# Kill existing Vite process -try: - with open('/tmp/vite-process.pid', 'r') as f: - pid = int(f.read().strip()) - os.kill(pid, signal.SIGTERM) - print("Killed existing Vite process") - time.sleep(1) -except: - print("No existing Vite process found") - -os.chdir('/home/user/app') - -# Clear error file -error_file = '/tmp/vite-errors.json' -with open(error_file, 'w') as f: - json.dump({"errors": [], "lastChecked": time.time()}, f) - -# Function to monitor Vite output for errors -def monitor_output(proc, error_file): - while True: - line = proc.stderr.readline() - if not line: - break - - sys.stdout.write(line) # Also print to console - - # 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") - `); + // Kill existing Vite processes + try { + await global.activeSandbox.runCommand({ + cmd: 'pkill', + args: ['-f', 'vite'] + }); + console.log('[restart-vite] Killed existing Vite processes'); + + // Wait a moment for processes to terminate + await new Promise(resolve => setTimeout(resolve, 2000)); + } catch (error) { + console.log('[restart-vite] No existing Vite processes found'); + } + + // Clear any error tracking files + try { + await global.activeSandbox.runCommand({ + cmd: 'bash', + args: ['-c', 'echo \'{"errors": [], "lastChecked": '+ Date.now() +'}\' > /tmp/vite-errors.json'] + }); + } catch (error) { + // Ignore if this fails + } + + // Start Vite dev server in detached mode + const viteProcess = await global.activeSandbox.runCommand({ + cmd: 'npm', + args: ['run', 'dev'], + detached: true + }); + + console.log('[restart-vite] Vite dev server restarted'); + + // Wait for Vite to start up + await new Promise(resolve => setTimeout(resolve, 3000)); return NextResponse.json({ success: true, - message: 'Vite restarted successfully', - output: result.output + message: 'Vite restarted successfully' }); } catch (error) { diff --git a/app/api/run-command/route.ts b/app/api/run-command/route.ts index 53e7e7b..76ffaff 100644 --- a/app/api/run-command/route.ts +++ b/app/api/run-command/route.ts @@ -1,5 +1,4 @@ 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) declare global { @@ -26,30 +25,32 @@ export async function POST(request: NextRequest) { console.log(`[run-command] Executing: ${command}`); - const result = await global.activeSandbox.runCode(` -import subprocess -import os - -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}") - `); + // Parse command and arguments + const commandParts = command.trim().split(/\s+/); + const cmd = commandParts[0]; + const args = commandParts.slice(1); - 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({ success: true, output, - message: 'Command executed successfully' + exitCode: result.exitCode, + message: result.exitCode === 0 ? 'Command executed successfully' : 'Command completed with non-zero exit code' }); } catch (error) { diff --git a/app/api/sandbox-logs/route.ts b/app/api/sandbox-logs/route.ts index 84d0208..177c370 100644 --- a/app/api/sandbox-logs/route.ts +++ b/app/api/sandbox-logs/route.ts @@ -15,55 +15,70 @@ export async function GET(request: NextRequest) { console.log('[sandbox-logs] Fetching Vite dev server logs...'); - // Get the last N lines of the Vite dev server output - const result = await global.activeSandbox.runCode(` -import subprocess -import os - -# Try to get the Vite process output -try: - # Read the last 100 lines of any log files - log_content = [] + // Check if Vite processes are running + const psResult = await global.activeSandbox.runCommand({ + cmd: 'ps', + args: ['aux'] + }); - # Check if there are any node processes running - ps_result = subprocess.run(['ps', 'aux'], capture_output=True, text=True) - vite_processes = [line for line in ps_result.stdout.split('\\n') if 'vite' in line.lower()] + let viteRunning = false; + let logContent: string[] = []; - if vite_processes: - log_content.append("Vite is running") - else: - log_content.append("Vite process not found") - - # 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({ - "hasErrors": False, - "logs": log_content, - "status": "running" if vite_processes else "stopped" - })) -except Exception as e: - print(json.dumps({ - "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' - }); + if (psResult.exitCode === 0) { + const psOutput = await psResult.stdout(); + const viteProcesses = psOutput.split('\n').filter(line => + line.toLowerCase().includes('vite') || + line.toLowerCase().includes('npm run dev') + ); + + viteRunning = viteProcesses.length > 0; + + if (viteRunning) { + logContent.push("Vite is running"); + logContent.push(...viteProcesses.slice(0, 3)); // Show first 3 processes + } else { + logContent.push("Vite process not found"); + } } + // 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) { console.error('[sandbox-logs] Error:', error); return NextResponse.json({ diff --git a/components/SandboxPreview.tsx b/components/SandboxPreview.tsx index 3808c26..d5009ca 100644 --- a/components/SandboxPreview.tsx +++ b/components/SandboxPreview.tsx @@ -22,9 +22,10 @@ export default function SandboxPreview({ useEffect(() => { if (sandboxId && type !== 'console') { - // In production, this would be the actual E2B sandbox URL - // Format: https://{sandboxId}-{port}.e2b.dev - setPreviewUrl(`https://${sandboxId}-${port}.e2b.dev`); + // For Vercel Sandbox, we'll receive the full URL from the API + // The URL format is determined by Vercel Sandbox's domain() method + // This is just a fallback format - actual URL comes from sandbox.domain(port) + setPreviewUrl(`https://${sandboxId}.vercel-sandbox.dev`); } }, [sandboxId, port, type]); diff --git a/config/app.config.ts b/config/app.config.ts index 500777d..1a85a3a 100644 --- a/config/app.config.ts +++ b/config/app.config.ts @@ -2,27 +2,30 @@ // This file contains all configurable settings for the application export const appConfig = { - // E2B Sandbox Configuration - e2b: { + // Vercel Sandbox Configuration + vercelSandbox: { // Sandbox timeout in minutes timeoutMinutes: 15, - // Convert to milliseconds for E2B API + // Convert to milliseconds for Vercel Sandbox API get timeoutMs() { return this.timeoutMinutes * 60 * 1000; }, - // Vite development server port - vitePort: 5173, + // Development server port (Vercel Sandbox typically uses 3000 for Next.js/React) + devPort: 3000, - // Time to wait for Vite to be ready (in milliseconds) - viteStartupDelay: 7000, + // Time to wait for dev server to be ready (in milliseconds) + devServerStartupDelay: 7000, // Time to wait for CSS rebuild (in milliseconds) cssRebuildDelay: 2000, - // Default sandbox template (if using templates) - defaultTemplate: undefined, // or specify a template ID + // Working directory in sandbox + workingDirectory: '/app', + + // Default runtime for sandbox + runtime: 'node22' // Available: node22, python3.13, v0-next-shadcn, cua-ubuntu-xfce }, // AI Model Configuration @@ -35,7 +38,7 @@ export const appConfig = { 'openai/gpt-5', 'moonshotai/kimi-k2-instruct', 'anthropic/claude-sonnet-4-20250514', - 'google/gemini-2.5-pro' + 'google/gemini-2.0-flash-exp' ], // Model display names @@ -43,7 +46,7 @@ export const appConfig = { 'openai/gpt-5': 'GPT-5', 'moonshotai/kimi-k2-instruct': 'Kimi K2 Instruct', '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 diff --git a/docs/PACKAGE_DETECTION_GUIDE.md b/docs/PACKAGE_DETECTION_GUIDE.md index 2a89c36..79243c6 100644 --- a/docs/PACKAGE_DETECTION_GUIDE.md +++ b/docs/PACKAGE_DETECTION_GUIDE.md @@ -1,10 +1,10 @@ # 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 -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 @@ -196,43 +196,37 @@ Directly installs packages in the sandbox. 3. **Order matters**: Packages are installed before files are created 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` -2. The Vite dev server is automatically restarted after package installation +1. Packages are installed in the sandbox's working directory +2. The development server is automatically restarted after package installation 3. All npm operations run within the sandbox environment 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 -// Current implementation pattern -await global.activeSandbox.runCode(` -import subprocess -import os - -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 +// Direct command execution using Vercel Sandbox API +const result = await global.activeSandbox.runCommand({ + cmd: 'npm', + args: ['install', 'axios'] }); -console.log(result.stdout); +const stdout = await result.stdout(); +const stderr = await result.stderr(); +console.log(stdout); ``` ### 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 - `background`: Run in background (true) or wait for completion (false) - `envs`: Environment variables as key-value pairs diff --git a/package-lock.json b/package-lock.json index 385ab79..c463119 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,20 @@ "version": "0.1.0", "dependencies": { "@ai-sdk/anthropic": "^2.0.1", + "@ai-sdk/google": "^2.0.4", "@ai-sdk/groq": "^2.0.0", "@ai-sdk/openai": "^2.0.4", "@anthropic-ai/sdk": "^0.57.0", - "@e2b/code-interpreter": "^1.5.1", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@types/react-syntax-highlighter": "^15.5.13", + "@vercel/sandbox": "^0.0.17", "ai": "^5.0.0", "autoprefixer": "^10.4.21", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cors": "^2.8.5", "dotenv": "^17.2.1", - "e2b": "^1.13.2", - "express": "^5.1.0", "framer-motion": "^12.23.12", "groq-sdk": "^0.29.0", "lucide-react": "^0.532.0", @@ -98,6 +97,39 @@ "zod": "^3.25.76 || ^4" } }, + "node_modules/@ai-sdk/google": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.9.tgz", + "integrity": "sha512-sWhuiRzsVEBS3HOyvqgRiMahLxK2OEBs+x6RZMnibHt4nwHOl8AjJ7UVstJtJHo4U2YX8iaMSJMU+qhX1wRPTA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, + "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.6.tgz", + "integrity": "sha512-s1+9okDSqbxKvwf1mqyyqtOY27/RV9O+XTzaRKEamVKbmVBM7BiCSfui7vH7A/1EETECtTJkS2MUL5D3Pw5GFw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, "node_modules/@ai-sdk/groq": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@ai-sdk/groq/-/groq-2.0.0.tgz", @@ -218,43 +250,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bufbuild/protobuf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.6.2.tgz", - "integrity": "sha512-vLu7SRY84CV/Dd+NUdgtidn2hS5hSMUC1vDBY0VcviTdgRYkU43vIz3vIFbmx14cX1r+mM7WjzE5Fl1fGEM0RQ==", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "node_modules/@connectrpc/connect": { - "version": "2.0.0-rc.3", - "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0-rc.3.tgz", - "integrity": "sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^2.2.0" - } - }, - "node_modules/@connectrpc/connect-web": { - "version": "2.0.0-rc.3", - "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.0.0-rc.3.tgz", - "integrity": "sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^2.2.0", - "@connectrpc/connect": "2.0.0-rc.3" - } - }, - "node_modules/@e2b/code-interpreter": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@e2b/code-interpreter/-/code-interpreter-1.5.1.tgz", - "integrity": "sha512-mkyKjAW2KN5Yt0R1I+1lbH3lo+W/g/1+C2lnwlitXk5wqi/g94SEO41XKdmDf5WWpKG3mnxWDR5d6S/lyjmMEw==", - "license": "MIT", - "dependencies": { - "e2b": "^1.4.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "dev": true, @@ -996,6 +991,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.9", "license": "MIT", @@ -1324,6 +1325,42 @@ "darwin" ] }, + "node_modules/@vercel/oidc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-2.0.2.tgz", + "integrity": "sha512-59PBFx3T+k5hLTEWa3ggiMpGRz1OVvl9eN8SUai+A43IsqiOuAe7qPBf+cray/Fj6mkgnxm/D7IAtjc8zSHi7g==", + "license": "Apache-2.0", + "dependencies": { + "@types/ms": "2.1.0", + "ms": "2.1.3" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@vercel/sandbox": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/@vercel/sandbox/-/sandbox-0.0.17.tgz", + "integrity": "sha512-oLfYqYKMjwNopjO2BKbVd0uA1dL43jk6yqaeIgquLJG/Jo01FZb5sd6XfGIfFa21XDxXGM61eomX7fSps/hvYg==", + "license": "ISC", + "dependencies": { + "@vercel/oidc": "^2.0.0", + "async-retry": "1.3.3", + "jsonlines": "0.1.1", + "ms": "2.1.3", + "tar-stream": "3.1.7", + "zod": "3.24.4" + } + }, + "node_modules/@vercel/sandbox/node_modules/zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1336,19 +1373,6 @@ "node": ">=6.5" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.15.0", "dev": true, @@ -1597,6 +1621,15 @@ "node": ">= 0.4" } }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1670,30 +1703,23 @@ "node": ">= 0.4" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "dev": true, "license": "MIT" }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } + "node_modules/bare-events": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==", + "license": "Apache-2.0", + "optional": true }, "node_modules/brace-expansion": { "version": "1.1.12", @@ -1747,15 +1773,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind": { "version": "1.0.8", "dev": true, @@ -1786,6 +1803,7 @@ }, "node_modules/call-bound": { "version": "1.0.4", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -1961,56 +1979,11 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -2096,6 +2069,7 @@ }, "node_modules/debug": { "version": "4.4.1", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2155,15 +2129,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/detect-libc": { "version": "2.0.4", "devOptional": true, @@ -2207,29 +2172,6 @@ "node": ">= 0.4" } }, - "node_modules/e2b": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/e2b/-/e2b-1.13.2.tgz", - "integrity": "sha512-m8acE/MzMAJo1A57DakR2X1Sl5Mt1tcQO2aJfygNaQHLXby/4xsjF0UeJUB70jF7xntiR41pAMbZEHnkzrT9tw==", - "license": "MIT", - "dependencies": { - "@bufbuild/protobuf": "^2.6.2", - "@connectrpc/connect": "2.0.0-rc.3", - "@connectrpc/connect-web": "2.0.0-rc.3", - "compare-versions": "^6.1.0", - "openapi-fetch": "^0.9.7", - "platform": "^1.3.6" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, "node_modules/electron-to-chromium": { "version": "1.5.192", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", @@ -2241,15 +2183,6 @@ "dev": true, "license": "MIT" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/enhanced-resolve": { "version": "5.18.2", "dev": true, @@ -2428,12 +2361,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "dev": true, @@ -2814,15 +2741,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -2841,53 +2759,17 @@ "node": ">=20.0.0" } }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.1", "dev": true, @@ -2980,23 +2862,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/find-up": { "version": "5.0.0", "dev": true, @@ -3107,15 +2972,6 @@ "node": ">= 12.20" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3156,15 +3012,6 @@ } } }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", @@ -3465,31 +3312,6 @@ "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", "license": "CC0-1.0" }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -3499,18 +3321,6 @@ "ms": "^2.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "dev": true, @@ -3542,12 +3352,6 @@ "node": ">=0.8.19" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, "node_modules/internal-slot": { "version": "1.1.0", "dev": true, @@ -3561,15 +3365,6 @@ "node": ">= 0.4" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -3852,12 +3647,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, "node_modules/is-regex": { "version": "1.2.1", "dev": true, @@ -4067,6 +3856,12 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonlines": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz", + "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==", + "license": "MIT" + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "dev": true, @@ -4231,27 +4026,6 @@ "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge2": { "version": "1.4.1", "dev": true, @@ -4283,27 +4057,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "dev": true, @@ -4410,15 +4163,6 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/next": { "version": "15.4.3", "license": "MIT", @@ -4559,6 +4303,7 @@ }, "node_modules/object-inspect": { "version": "1.13.4", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4655,42 +4400,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openapi-fetch": { - "version": "0.9.8", - "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.8.tgz", - "integrity": "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==", - "license": "MIT", - "dependencies": { - "openapi-typescript-helpers": "^0.0.8" - } - }, - "node_modules/openapi-typescript-helpers": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz", - "integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g==", - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "dev": true, @@ -4780,15 +4489,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "dev": true, @@ -4810,15 +4510,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -4834,12 +4525,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", - "license": "MIT" - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "dev": true, @@ -4920,19 +4605,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "dev": true, @@ -4941,21 +4613,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -4975,30 +4632,6 @@ ], "license": "MIT" }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { "version": "19.1.0", "license": "MIT", @@ -5143,6 +4776,15 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "dev": true, @@ -5152,22 +4794,6 @@ "node": ">=0.10.0" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -5208,26 +4834,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-push-apply": { "version": "1.0.0", "dev": true, @@ -5259,12 +4865,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.26.0", "license": "MIT" @@ -5277,43 +4877,6 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "dev": true, @@ -5357,12 +4920,6 @@ "node": ">= 0.4" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, "node_modules/sharp": { "version": "0.34.3", "hasInstallScript": true, @@ -5436,6 +4993,7 @@ }, "node_modules/side-channel": { "version": "1.1.0", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5453,6 +5011,7 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5467,6 +5026,7 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5483,6 +5043,7 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5528,15 +5089,6 @@ "dev": true, "license": "MIT" }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "dev": true, @@ -5549,6 +5101,19 @@ "node": ">= 0.4" } }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "dev": true, @@ -5761,6 +5326,26 @@ "node": ">=18" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/tinyglobby": { "version": "0.2.14", "dev": true, @@ -5787,15 +5372,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -5839,20 +5415,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "dev": true, @@ -5956,15 +5518,6 @@ "version": "6.21.0", "license": "MIT" }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unrs-resolver": { "version": "1.11.1", "dev": true, @@ -6173,12 +5726,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 818cf29..e9c8a66 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "next build", "start": "next start", "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:code": "node tests/code-execution.test.js", "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/openai": "^2.0.4", "@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-switch": "^1.2.5", "@types/react-syntax-highlighter": "^15.5.13", @@ -29,7 +29,7 @@ "clsx": "^2.1.1", "cors": "^2.8.5", "dotenv": "^17.2.1", - "e2b": "^1.13.2", + "framer-motion": "^12.23.12", "groq-sdk": "^0.29.0", "lucide-react": "^0.532.0",