2025-04-09 04:09:45 +08:00
|
|
|
import os
|
|
|
|
|
2025-04-18 23:58:08 +08:00
|
|
|
from daytona_sdk import Daytona, DaytonaConfig, CreateSandboxParams, Sandbox, SessionExecuteRequest
|
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
|
|
|
|
2025-04-11 01:13:12 +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)
|
2025-04-18 23:58:08 +08:00
|
|
|
|
|
|
|
# Start supervisord in a session when restarting
|
|
|
|
start_supervisord_session(sandbox)
|
2025-04-11 00:02:21 +08:00
|
|
|
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-18 23:58:08 +08:00
|
|
|
def start_supervisord_session(sandbox: Sandbox):
|
|
|
|
"""Start supervisord in a session."""
|
|
|
|
session_id = "supervisord-session"
|
|
|
|
try:
|
|
|
|
logger.info(f"Creating session {session_id} for supervisord")
|
|
|
|
sandbox.process.create_session(session_id)
|
|
|
|
|
|
|
|
# Execute supervisord command
|
|
|
|
sandbox.process.execute_session_command(session_id, SessionExecuteRequest(
|
|
|
|
command="exec /usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf",
|
|
|
|
var_async=True
|
|
|
|
))
|
|
|
|
logger.info(f"Supervisord started in session {session_id}")
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(f"Error starting supervisord session: {str(e)}")
|
|
|
|
raise e
|
|
|
|
|
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-21 11:24:20 +08:00
|
|
|
image="adamcohenhillel/kortix-suna:0.0.18",
|
|
|
|
public=True,
|
2025-04-09 04:09:45 +08:00
|
|
|
env_vars={
|
|
|
|
"CHROME_PERSISTENT_SESSION": "true",
|
2025-04-20 08:27:32 +08:00
|
|
|
"RESOLUTION": "1280x720x24",
|
|
|
|
"RESOLUTION_WIDTH": "1280",
|
|
|
|
"RESOLUTION_HEIGHT": "720",
|
2025-04-09 04:09:45 +08:00
|
|
|
"VNC_PASSWORD": password,
|
|
|
|
"ANONYMIZED_TELEMETRY": "false",
|
|
|
|
"CHROME_PATH": "",
|
|
|
|
"CHROME_USER_DATA": "",
|
|
|
|
"CHROME_DEBUGGING_PORT": "9222",
|
|
|
|
"CHROME_DEBUGGING_HOST": "localhost",
|
|
|
|
"CHROME_CDP": ""
|
|
|
|
},
|
2025-04-21 08:06:10 +08:00
|
|
|
ports=[
|
|
|
|
6080, # noVNC web interface
|
2025-04-21 11:24:20 +08:00
|
|
|
5900, # VNC port
|
|
|
|
5901, # VNC port
|
|
|
|
9222, # Chrome remote debugging port
|
2025-04-22 01:27:12 +08:00
|
|
|
8080, # HTTP website port
|
2025-04-21 11:24:20 +08:00
|
|
|
8002, # The browser api port
|
2025-04-21 08:06:10 +08:00
|
|
|
]
|
2025-04-09 04:09:45 +08:00
|
|
|
))
|
2025-04-10 00:44:17 +08:00
|
|
|
logger.info(f"Sandbox created with ID: {sandbox.id}")
|
|
|
|
|
2025-04-18 23:58:08 +08:00
|
|
|
# Start supervisord in a session for new sandbox
|
|
|
|
start_supervisord_session(sandbox)
|
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-21 20:00:31 +08:00
|
|
|
# Get preview links
|
|
|
|
vnc_link = self.sandbox.get_preview_link(6080)
|
2025-04-22 01:27:12 +08:00
|
|
|
website_link = self.sandbox.get_preview_link(8080)
|
2025-04-10 00:44:17 +08:00
|
|
|
|
2025-04-21 20:00:31 +08:00
|
|
|
# Extract the actual URLs from the preview link objects
|
|
|
|
vnc_url = vnc_link.url if hasattr(vnc_link, 'url') else str(vnc_link)
|
|
|
|
website_url = website_link.url if hasattr(website_link, 'url') else str(website_link)
|
|
|
|
|
|
|
|
# Log the actual URLs
|
2025-04-18 23:58:08 +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}")
|
2025-04-18 23:58:08 +08:00
|
|
|
return cleaned_path
|