suna/backend/sandbox/sandbox.py

152 lines
5.3 KiB
Python
Raw Normal View History

2025-04-09 04:09:45 +08:00
import os
from daytona_sdk import Daytona, DaytonaConfig, CreateSandboxParams, Sandbox
2025-04-11 00:02:21 +08:00
from daytona_api_client.models.workspace_state import WorkspaceState
2025-04-09 07:37:06 +08:00
from dotenv import load_dotenv
2025-04-09 04:09:45 +08:00
from agentpress.tool import Tool
from utils.logger import logger
2025-04-13 05:40:01 +08:00
from utils.files_utils import clean_path
2025-04-09 04:09:45 +08:00
2025-04-09 07:37:06 +08:00
load_dotenv()
2025-04-11 00:02:21 +08:00
logger.debug("Initializing Daytona sandbox configuration")
2025-04-09 04:09:45 +08:00
config = DaytonaConfig(
api_key=os.getenv("DAYTONA_API_KEY"),
server_url=os.getenv("DAYTONA_SERVER_URL"),
target=os.getenv("DAYTONA_TARGET")
)
2025-04-10 00:44:17 +08:00
if config.api_key:
logger.debug("Daytona API key configured successfully")
else:
logger.warning("No Daytona API key found in environment variables")
if config.server_url:
logger.debug(f"Daytona server URL set to: {config.server_url}")
else:
logger.warning("No Daytona server URL found in environment variables")
if config.target:
logger.debug(f"Daytona target set to: {config.target}")
else:
logger.warning("No Daytona target found in environment variables")
2025-04-09 04:09:45 +08:00
daytona = Daytona(config)
2025-04-10 00:44:17 +08:00
logger.debug("Daytona client initialized")
2025-04-09 04:09:45 +08:00
async def get_or_start_sandbox(sandbox_id: str):
2025-04-13 05:40:01 +08:00
"""Retrieve a sandbox by ID, check its state, and start it if needed."""
2025-04-11 00:02:21 +08:00
logger.info(f"Getting or starting sandbox with ID: {sandbox_id}")
try:
sandbox = daytona.get_current_sandbox(sandbox_id)
# Check if sandbox needs to be started
if sandbox.instance.state == WorkspaceState.ARCHIVED or sandbox.instance.state == WorkspaceState.STOPPED:
logger.info(f"Sandbox is in {sandbox.instance.state} state. Starting...")
try:
daytona.start(sandbox)
# Wait a moment for the sandbox to initialize
2025-04-13 05:40:01 +08:00
# sleep(5)
2025-04-11 00:02:21 +08:00
# Refresh sandbox state after starting
sandbox = daytona.get_current_sandbox(sandbox_id)
except Exception as e:
logger.error(f"Error starting sandbox: {e}")
raise e
logger.info(f"Sandbox {sandbox_id} is ready")
return sandbox
except Exception as e:
logger.error(f"Error retrieving or starting sandbox: {str(e)}")
raise e
2025-04-09 07:37:06 +08:00
2025-04-09 04:09:45 +08:00
def create_sandbox(password: str):
2025-04-11 00:02:21 +08:00
"""Create a new sandbox with all required services configured and running."""
2025-04-10 00:44:17 +08:00
logger.info("Creating new Daytona sandbox environment")
logger.debug("Configuring sandbox with browser-use image and environment variables")
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
logger.warning("OPENAI_API_KEY not found in environment variables")
else:
logger.debug("OPENAI_API_KEY configured for sandbox")
2025-04-09 04:09:45 +08:00
sandbox = daytona.create(CreateSandboxParams(
2025-04-16 01:20:15 +08:00
image="adamcohenhillel/kortix-suna:0.0.13",
2025-04-12 22:37:35 +08:00
public=True,
2025-04-09 04:09:45 +08:00
env_vars={
"CHROME_PERSISTENT_SESSION": "true",
"RESOLUTION": "1920x1080x24",
"RESOLUTION_WIDTH": "1920",
"RESOLUTION_HEIGHT": "1080",
"VNC_PASSWORD": password,
"ANONYMIZED_TELEMETRY": "false",
"CHROME_PATH": "",
"CHROME_USER_DATA": "",
"CHROME_DEBUGGING_PORT": "9222",
"CHROME_DEBUGGING_HOST": "localhost",
"CHROME_CDP": ""
},
ports=[
7788, # Gradio default port
6080, # noVNC web interface
2025-04-12 09:22:28 +08:00
5900, # VNC port
2025-04-09 04:09:45 +08:00
5901, # VNC port
9222, # Chrome remote debugging port
8080 # HTTP website port
]
))
2025-04-10 00:44:17 +08:00
logger.info(f"Sandbox created with ID: {sandbox.id}")
2025-04-13 05:40:01 +08:00
# HTTP server is now started automatically by Docker
2025-04-09 04:09:45 +08:00
2025-04-10 00:44:17 +08:00
logger.info(f"Sandbox environment successfully initialized")
2025-04-09 04:09:45 +08:00
return sandbox
class SandboxToolsBase(Tool):
"""Tool for executing tasks in a Daytona sandbox with browser-use capabilities."""
2025-04-11 00:02:21 +08:00
# Class variable to track if sandbox URLs have been printed
_urls_printed = False
2025-04-12 06:07:35 +08:00
def __init__(self, sandbox: Sandbox):
2025-04-09 04:09:45 +08:00
super().__init__()
2025-04-12 06:07:35 +08:00
self.sandbox = sandbox
2025-04-09 04:09:45 +08:00
self.daytona = daytona
2025-04-09 20:46:13 +08:00
self.workspace_path = "/workspace"
2025-04-09 04:09:45 +08:00
2025-04-12 06:07:35 +08:00
self.sandbox_id = sandbox.id
2025-04-11 00:02:21 +08:00
# logger.info(f"Initializing SandboxToolsBase with sandbox ID: {sandbox_id}")
2025-04-10 00:44:17 +08:00
2025-04-09 04:09:45 +08:00
try:
2025-04-12 06:07:35 +08:00
logger.debug(f"Retrieving sandbox with ID: {self.sandbox_id}")
2025-04-09 04:09:45 +08:00
self.sandbox = self.daytona.get_current_sandbox(self.sandbox_id)
2025-04-11 00:02:21 +08:00
# logger.info(f"Successfully retrieved sandbox: {self.sandbox.id}")
2025-04-09 04:09:45 +08:00
except Exception as e:
2025-04-10 00:44:17 +08:00
logger.error(f"Error retrieving sandbox: {str(e)}", exc_info=True)
2025-04-09 04:09:45 +08:00
raise e
2025-04-10 00:44:17 +08:00
# Get and log preview links
vnc_url = self.sandbox.get_preview_link(6080)
website_url = self.sandbox.get_preview_link(8080)
2025-04-11 00:02:21 +08:00
# logger.info(f"Sandbox VNC URL: {vnc_url}")
# logger.info(f"Sandbox Website URL: {website_url}")
2025-04-09 04:09:45 +08:00
2025-04-11 00:02:21 +08:00
if not SandboxToolsBase._urls_printed:
print("\033[95m***")
print(vnc_url)
print(website_url)
print("***\033[0m")
SandboxToolsBase._urls_printed = True
2025-04-09 20:46:13 +08:00
def clean_path(self, path: str) -> str:
2025-04-13 05:40:01 +08:00
cleaned_path = clean_path(path, self.workspace_path)
2025-04-10 00:44:17 +08:00
logger.debug(f"Cleaned path: {path} -> {cleaned_path}")
return cleaned_path