Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions apps/web/app/(app)/api/check-sandbox/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextResponse } from 'next/server'
import { Sandbox } from '@e2b/code-interpreter'
import { getSandboxProvider } from '@react-native-vibe-code/sandbox/lib'

export async function POST(request: Request) {
try {
Expand All @@ -11,14 +11,15 @@ export async function POST(request: Request) {

console.log('[check-sandbox] Checking sandbox container:', sandboxId)

// Check if the sandbox container is alive using E2B SDK
// Check if the sandbox container is alive using the active provider
try {
const sandbox = await Sandbox.connect(sandboxId)

const sandbox = await getSandboxProvider().connect(sandboxId)
console.log('[check-sandbox] Sandbox container is alive:', sandboxId)

// Note: E2B Sandbox doesn't need explicit closing for resume operations
// The sandbox will remain active and timeout according to E2B's policies
// Close connection if supported
if (sandbox.close) {
await sandbox.close().catch(() => {})
}

return NextResponse.json({ isAlive: true })
} catch (error) {
Expand Down
20 changes: 11 additions & 9 deletions apps/web/app/(app)/api/create-container/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { db } from '@/lib/db'
import { projects, convexProjectCredentials } from '@react-native-vibe-code/database'
import { Sandbox } from '@e2b/code-interpreter'
import { getSandboxProvider, type ISandbox } from '@react-native-vibe-code/sandbox/lib'
import { Octokit } from '@octokit/rest'
import { GitHubService } from '@/lib/github-service'
import { eq, and } from 'drizzle-orm'
Expand Down Expand Up @@ -113,7 +113,7 @@ function sendConvexError(projectId: string, logData: string): void {
async function setupConvex(params: {
projectId: string
userId: string
sandbox: Sandbox
sandbox: ISandbox
appName: string
}): Promise<void> {
// Check if Convex is configured
Expand Down Expand Up @@ -210,7 +210,7 @@ async function setupConvex(params: {
/**
* Write EXPO_PUBLIC_CONVEX_URL to sandbox .env.local
*/
async function writeConvexUrlToSandbox(sandbox: Sandbox, deploymentUrl: string): Promise<void> {
async function writeConvexUrlToSandbox(sandbox: ISandbox, deploymentUrl: string): Promise<void> {
try {
const { updateSandboxEnvFile } = await import('@/lib/convex/sandbox-utils')
await updateSandboxEnvFile(sandbox, 'EXPO_PUBLIC_CONVEX_URL', deploymentUrl)
Expand All @@ -225,7 +225,7 @@ async function writeConvexUrlToSandbox(sandbox: Sandbox, deploymentUrl: string):
*/
async function startConvexDevServer(params: {
projectId: string
sandbox: Sandbox
sandbox: ISandbox
adminKey: string
deploymentUrl: string
}): Promise<void> {
Expand Down Expand Up @@ -304,7 +304,8 @@ export async function POST(req: NextRequest) {

// Check if project already exists with active sandbox
let project = null
let sandbox: Sandbox | null = null
const sandboxProvider = getSandboxProvider()
let sandbox: ISandbox | null = null

try {
const existingProjects = await db
Expand All @@ -328,7 +329,7 @@ export async function POST(req: NextRequest) {
// Try to connect to the existing sandbox
if (project.sandboxId) {
try {
sandbox = await Sandbox.connect(project.sandboxId)
sandbox = await sandboxProvider.connect(project.sandboxId)
console.log(`Connected to existing sandbox: ${sandbox.sandboxId}`)

// Set API Base URL for the sandbox app
Expand Down Expand Up @@ -387,15 +388,16 @@ export async function POST(req: NextRequest) {

console.log(`Using template: ${templateSelection} (ID: ${templateId[templateSelection]})`)

sandbox = await Sandbox.create(templateId[templateSelection], {

sandbox = await sandboxProvider.create({
templateId: templateId[templateSelection],
image: process.env.DAYTONA_IMAGE,
metadata: {
template: templateId[templateSelection],
userID: userID,
teamID: teamID || '',
projectId,
},
timeoutMs: parseInt(process.env.E2B_SANDBOX_TIMEOUT_MS || '3600000'), // Use env var, default to 1 hour
timeoutMs: parseInt(process.env.SANDBOX_TIMEOUT_MS || process.env.E2B_SANDBOX_TIMEOUT_MS || '3600000'),
})

console.log(`Created new sandbox: ${sandbox.sandboxId}`)
Expand Down
51 changes: 28 additions & 23 deletions apps/web/app/(app)/api/sandbox/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { db } from '@/lib/db'
import { projects } from '@react-native-vibe-code/database'
import { FragmentSchema } from '@/lib/schema'
import { ExecutionResultInterpreter, ExecutionResultWeb } from '@/lib/types'
import { Sandbox } from '@e2b/code-interpreter'
import { getSandboxProvider, type ISandbox } from '@react-native-vibe-code/sandbox/lib'
import { eq, and } from 'drizzle-orm'
import { globalFileWatcher } from '@/lib/sandbox-file-watcher'
import { globalFileChangeStream } from '@/lib/file-change-stream'

const sandboxTimeout = parseInt(process.env.E2B_SANDBOX_TIMEOUT_MS || '3600000') // Use env var, default to 1 hour
const sandboxTimeout = parseInt(process.env.SANDBOX_TIMEOUT_MS || process.env.E2B_SANDBOX_TIMEOUT_MS || '3600000') // Use env var, default to 1 hour

export const maxDuration = 120

Expand Down Expand Up @@ -122,7 +122,9 @@ export async function POST(req: Request) {
}

let project = null
let sbx = null
let sbx: ISandbox | null = null

const sandboxProvider = getSandboxProvider()

// Check if this is a follow-up message for an existing project
if (projectId && !isFirstMessage) {
Expand All @@ -148,21 +150,14 @@ export async function POST(req: Request) {
// Try to connect to the existing sandbox
if (project.sandboxId) {
try {
sbx = await Sandbox.connect(project.sandboxId)
sbx = await sandboxProvider.connect(project.sandboxId)
console.log(`Connected to sandbox: ${sbx.sandboxId}`)
} catch (error) {
console.log(`Failed to connect to sandbox ${project.sandboxId}:`, error)
// Try to connect to existing sandbox instead
try {
sbx = await Sandbox.connect(project.sandboxId)
console.log(`Connected to existing sandbox: ${sbx.sandboxId}`)
} catch (connectError) {
console.log(
`Failed to connect to sandbox ${project.sandboxId}:`,
connectError,
)
// If both resume and connect fail, we'll create a new sandbox below
}
} catch (connectError) {
console.log(
`Failed to connect to sandbox ${project.sandboxId}:`,
connectError,
)
// If connect fails, we'll create a new sandbox below
}
}
}
Expand All @@ -178,7 +173,9 @@ export async function POST(req: Request) {
? 'sm3r39vktkmu37lna0qa'
: fragment.template

sbx = await Sandbox.create(templateId, {
sbx = await sandboxProvider.create({
templateId,
image: process.env.DAYTONA_IMAGE,
metadata: {
template: templateId,
userID: userID ?? '',
Expand Down Expand Up @@ -310,18 +307,26 @@ export async function POST(req: Request) {
console.log(`Still waiting for webBundled... ${waitTime}ms elapsed`)
}

// Get the public URL using e2b's getHost method
const publicHost = sbx.getHost(8081)
publicUrl = `https://${publicHost}`
// Get the public URL - try async getPreviewUrl first, fall back to sync getHost
if (sbx.getPreviewUrl) {
publicUrl = await sbx.getPreviewUrl(8081)
} else {
publicUrl = `https://${sbx.getHost(8081)}`
}

console.log('E2B public URL:', publicUrl)
console.log('Sandbox public URL:', publicUrl)

if (!webBundled) {
console.warn('WebBundled not detected, but proceeding with URL')
}
} else {
// For other templates, use the standard port
publicUrl = `https://${sbx?.getHost(fragment.port || 80)}`
const port = fragment.port || 80
if (sbx?.getPreviewUrl) {
publicUrl = await sbx.getPreviewUrl(port)
} else {
publicUrl = `https://${sbx?.getHost(port)}`
}
}

// Install packages
Expand Down
12 changes: 6 additions & 6 deletions apps/web/app/(app)/api/start-server/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { db } from '@/lib/db'
import { projects } from '@react-native-vibe-code/database'
import { startExpoServer } from '@/lib/server-utils'
import { Sandbox } from '@e2b/code-interpreter'
import { getSandboxProvider } from '@react-native-vibe-code/sandbox/lib'
import { eq, and } from 'drizzle-orm'
import { NextRequest } from 'next/server'

Expand Down Expand Up @@ -66,8 +66,8 @@ export async function POST(req: NextRequest) {
// Verify the sandbox is still active before returning cached URLs
try {
console.log('Verifying sandbox is still active:', sandboxId)
const testSandbox = await Sandbox.connect(sandboxId)
await testSandbox.close() // Close immediately after verification
const testSandbox = await getSandboxProvider().connect(sandboxId)
if (testSandbox.close) await testSandbox.close() // Close immediately after verification

console.log(
'Server already running and sandbox active, returning existing URLs:',
Expand Down Expand Up @@ -95,10 +95,10 @@ export async function POST(req: NextRequest) {
)
}

// Connect to sandbox
let sandbox: Sandbox
// Connect to sandbox using the active provider
let sandbox: Awaited<ReturnType<typeof getSandboxProvider>['connect']>
try {
sandbox = await Sandbox.connect(sandboxId)
sandbox = await getSandboxProvider().connect(sandboxId)
console.log(`Connected to sandbox for server start: ${sandbox.sandboxId}`)
} catch (error) {
console.error(`Failed to resume sandbox ${sandboxId}:`, error)
Expand Down
8 changes: 4 additions & 4 deletions apps/web/lib/claude-code-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ClaudeCodeService } from '@/lib/claude-code-service'
import { db } from '@/lib/db'
import { projects } from '@react-native-vibe-code/database'
import { UsageTracker } from '@/lib/usage-tracking'
import { Sandbox } from '@e2b/code-interpreter'
import { getSandboxProvider, type ISandbox } from '@react-native-vibe-code/sandbox/lib'
import { eq, and } from 'drizzle-orm'
import { getSkillTemplate, getSkillFilePath } from '@/lib/skills/templates'
import { validateSkillIds } from '@/lib/skills'
Expand Down Expand Up @@ -62,7 +62,7 @@ export async function handleClaudeCodeGeneration(

// Get existing project
let project: any = null
let sandbox: Sandbox | null = null
let sandbox: ISandbox | null = null

// Helper function to fetch project from database
const fetchProject = async () => {
Expand Down Expand Up @@ -133,8 +133,8 @@ export async function handleClaudeCodeGeneration(
}
}

// Connect to sandbox
sandbox = await Sandbox.connect(targetSandboxId)
// Connect to sandbox using the active provider
sandbox = await getSandboxProvider().connect(targetSandboxId)
console.log(`[Claude Code Handler] Connected to sandbox: ${sandbox.sandboxId}`)
} catch (error) {
console.error('[Claude Code Handler] Error checking for existing project:', error)
Expand Down
6 changes: 3 additions & 3 deletions apps/web/lib/claude-code-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { pusherServer } from './pusher'
import { Sandbox} from '@e2b/code-interpreter'
import type { ISandbox } from '@react-native-vibe-code/sandbox/lib'
import { prompt, getPromptWithCloudStatus } from '@react-native-vibe-code/prompt-engine'
import { db } from '@/lib/db'
import { projects } from '@react-native-vibe-code/database'
Expand Down Expand Up @@ -59,7 +59,7 @@ export class ClaudeCodeService {

async generateAppStreaming(
request: AppGenerationRequest,
sandbox: Sandbox,
sandbox: ISandbox,
callbacks: StreamingCallbacks,
): Promise<void> {
try {
Expand Down Expand Up @@ -621,7 +621,7 @@ export class ClaudeCodeService {

private async getConversationContext(
request: AppGenerationRequest,
sandbox: Sandbox,
sandbox: ISandbox,
): Promise<ConversationContext> {
// Load previous conversation history from database if exists
const previousMessages = await this.loadConversationHistory(
Expand Down
18 changes: 16 additions & 2 deletions packages/convex/src/sandbox-utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
/**
* Convex Sandbox Utilities
* Functions for managing Convex configuration in E2B sandboxes
* Functions for managing Convex configuration in sandboxes.
* Compatible with any sandbox provider (E2B, Daytona, etc.)
*/

import type { Sandbox } from '@e2b/code-interpreter'
/**
* Minimal sandbox interface required by Convex sandbox utilities.
* This avoids a circular dependency with @react-native-vibe-code/sandbox.
*/
interface SandboxLike {
files: {
exists(path: string): Promise<boolean>
read(path: string): Promise<string>
write(path: string, content: string): Promise<void>
}
}

/** @deprecated Use SandboxLike directly. Kept for backwards compatibility. */
type Sandbox = SandboxLike
import { db, convexProjectCredentials, eq } from '@react-native-vibe-code/database'
import { detectAndNotifyConvexError } from '@react-native-vibe-code/error-manager/server'

Expand Down
1 change: 1 addition & 0 deletions packages/publish/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"access": "public"
},
"dependencies": {
"@react-native-vibe-code/sandbox": "workspace:*",
"@e2b/code-interpreter": "2.1.0",
"cloudflare": "^5.2.0",
"nanoid": "^5.1.5"
Expand Down
6 changes: 3 additions & 3 deletions packages/publish/src/cloudflare/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @see README.md for required environment variables
*/

import { Sandbox } from '@e2b/code-interpreter'
import { getSandboxProvider } from '@react-native-vibe-code/sandbox/lib'
import { customAlphabet } from 'nanoid'
import { addCustomDomain, verifyCustomDomain } from './custom-domain'
import type { CloudflareDeployOptions, CloudflareDeployResult } from '../types'
Expand Down Expand Up @@ -99,8 +99,8 @@ export async function deployToCloudflare(
console.log(`[Cloudflare Deploy] Sandbox ID received: ${sandboxId}`)
console.log(`[Cloudflare Deploy] About to connect to sandbox...`)

// Connect to the sandbox
const sandbox = await Sandbox.connect(sandboxId)
// Connect to the sandbox using the active provider
const sandbox = await getSandboxProvider().connect(sandboxId)
console.log(`[Cloudflare Deploy] Successfully connected to sandbox: ${sandbox.sandboxId}`)

// Step 1: Build the web app
Expand Down
2 changes: 1 addition & 1 deletion packages/publish/src/config/app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* - wrangler.toml (Cloudflare Workers configuration)
*/

import type { Sandbox } from '@e2b/code-interpreter'
import type { ISandbox as Sandbox } from '@react-native-vibe-code/sandbox/lib'

/**
* Update app.json in the sandbox with the fantasy name
Expand Down
13 changes: 8 additions & 5 deletions packages/remix/src/api/remix-handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Sandbox } from '@e2b/code-interpreter'
import { getSandboxProvider } from '@react-native-vibe-code/sandbox/lib'
import { Octokit } from '@octokit/rest'
import { eq, sql } from 'drizzle-orm'
import { v4 as uuidv4 } from 'uuid'
Expand Down Expand Up @@ -82,7 +82,8 @@ export async function createRemix(params: {
console.log(`[Remix] User ${userId} remixing project ${sourceProjectId} to ${newProjectId}`)

// Create sandbox first
let sandbox: Sandbox | null = null
const sandboxProvider = getSandboxProvider()
let sandbox: Awaited<ReturnType<typeof sandboxProvider.create>> | null = null
try {
const templateId = {
expo: 'sm3r39vktkmu37lna0qa',
Expand All @@ -91,14 +92,16 @@ export async function createRemix(params: {
const templateSelection: keyof typeof templateId =
(process.env.TEMPLATE_SELECTION as keyof typeof templateId) || 'expo'

sandbox = await Sandbox.create(templateId[templateSelection], {
sandbox = await sandboxProvider.create({
templateId: templateId[templateSelection],
image: process.env.DAYTONA_IMAGE,
metadata: {
template: sourceProject.template,
userID: userId,
projectId: newProjectId,
forkedFrom: sourceProjectId,
},
timeoutMs: parseInt(process.env.E2B_SANDBOX_TIMEOUT_MS || '3600000'),
timeoutMs: parseInt(process.env.SANDBOX_TIMEOUT_MS || process.env.E2B_SANDBOX_TIMEOUT_MS || '3600000'),
})

console.log(`[Remix] Created sandbox: ${sandbox.sandboxId}`)
Expand Down Expand Up @@ -291,7 +294,7 @@ export async function createRemix(params: {
}

// Connect to source sandbox and push its code to GitHub
const sourceSandbox = await Sandbox.connect(sourceProject.sandboxId)
const sourceSandbox = await sandboxProvider.connect(sourceProject.sandboxId)
console.log(`[Remix] Connected to source sandbox to push code`)

const pushSourceScript = `#!/bin/bash
Expand Down
Loading
Loading