mirror of https://github.com/kortix-ai/suna.git
project_id to get sandbox id
This commit is contained in:
parent
aaf405272f
commit
74b043eec3
|
@ -10,10 +10,9 @@ from utils.logger import logger
|
|||
class SandboxBrowserTool(SandboxToolsBase):
|
||||
"""Tool for executing tasks in a Daytona sandbox with browser-use capabilities."""
|
||||
|
||||
def __init__(self, sandbox: Sandbox, thread_id: str, thread_manager: ThreadManager):
|
||||
super().__init__(sandbox)
|
||||
def __init__(self, project_id: str, thread_id: str, thread_manager: ThreadManager):
|
||||
super().__init__(project_id, thread_manager)
|
||||
self.thread_id = thread_id
|
||||
self.thread_manager = thread_manager
|
||||
|
||||
async def _execute_browser_action(self, endpoint: str, params: dict = None, method: str = "POST") -> ToolResult:
|
||||
"""Execute a browser automation action through the API
|
||||
|
@ -27,6 +26,9 @@ class SandboxBrowserTool(SandboxToolsBase):
|
|||
ToolResult: Result of the execution
|
||||
"""
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
# Build the curl command
|
||||
url = f"http://localhost:8002/api/automation/{endpoint}"
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from agentpress.tool import ToolResult, openapi_schema, xml_schema
|
|||
from sandbox.sandbox import SandboxToolsBase, Sandbox
|
||||
from utils.files_utils import clean_path
|
||||
from agent.tools.sb_shell_tool import SandboxShellTool
|
||||
from agentpress.thread_manager import ThreadManager
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
@ -11,11 +12,11 @@ load_dotenv()
|
|||
class SandboxDeployTool(SandboxToolsBase):
|
||||
"""Tool for deploying static websites from a Daytona sandbox to Cloudflare Pages."""
|
||||
|
||||
def __init__(self, sandbox: Sandbox):
|
||||
super().__init__(sandbox)
|
||||
def __init__(self, project_id: str, thread_manager: ThreadManager):
|
||||
super().__init__(project_id, thread_manager)
|
||||
self.workspace_path = "/workspace" # Ensure we're always operating in /workspace
|
||||
self.cloudflare_api_token = os.getenv("CLOUDFLARE_API_TOKEN")
|
||||
self.shell_tool = SandboxShellTool(sandbox)
|
||||
self.shell_tool = SandboxShellTool(project_id, thread_manager)
|
||||
|
||||
def clean_path(self, path: str) -> str:
|
||||
"""Clean and normalize a path to be relative to /workspace"""
|
||||
|
@ -76,6 +77,9 @@ class SandboxDeployTool(SandboxToolsBase):
|
|||
- Failure: Error message if deployment fails
|
||||
"""
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
directory_path = self.clean_path(directory_path)
|
||||
full_path = f"{self.workspace_path}/{directory_path}"
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from typing import Optional
|
||||
from agentpress.tool import ToolResult, openapi_schema, xml_schema
|
||||
from sandbox.sandbox import SandboxToolsBase, Sandbox
|
||||
from agentpress.thread_manager import ThreadManager
|
||||
|
||||
class SandboxExposeTool(SandboxToolsBase):
|
||||
"""Tool for exposing and retrieving preview URLs for sandbox ports."""
|
||||
|
||||
def __init__(self, sandbox: Sandbox):
|
||||
super().__init__(sandbox)
|
||||
self.workspace_path = "/workspace"
|
||||
def __init__(self, project_id: str, thread_manager: ThreadManager):
|
||||
super().__init__(project_id, thread_manager)
|
||||
|
||||
@openapi_schema({
|
||||
"type": "function",
|
||||
|
@ -61,6 +61,9 @@ class SandboxExposeTool(SandboxToolsBase):
|
|||
)
|
||||
async def expose_port(self, port: int) -> ToolResult:
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
# Convert port to integer if it's a string
|
||||
port = int(port)
|
||||
|
||||
|
|
|
@ -4,13 +4,14 @@ from typing import Optional
|
|||
from agentpress.tool import ToolResult, openapi_schema, xml_schema
|
||||
from sandbox.sandbox import SandboxToolsBase, Sandbox
|
||||
from utils.files_utils import EXCLUDED_FILES, EXCLUDED_DIRS, EXCLUDED_EXT, should_exclude_file, clean_path
|
||||
from agentpress.thread_manager import ThreadManager
|
||||
import os
|
||||
|
||||
class SandboxFilesTool(SandboxToolsBase):
|
||||
"""Tool for executing file system operations in a Daytona sandbox. All operations are performed relative to the /workspace directory."""
|
||||
|
||||
def __init__(self, sandbox: Sandbox):
|
||||
super().__init__(sandbox)
|
||||
def __init__(self, project_id: str, thread_manager: ThreadManager):
|
||||
super().__init__(project_id, thread_manager)
|
||||
self.SNIPPET_LINES = 4 # Number of context lines to show around edits
|
||||
self.workspace_path = "/workspace" # Ensure we're always operating in /workspace
|
||||
|
||||
|
@ -34,6 +35,9 @@ class SandboxFilesTool(SandboxToolsBase):
|
|||
"""Get the current workspace state by reading all files"""
|
||||
files_state = {}
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
files = self.sandbox.fs.list_files(self.workspace_path)
|
||||
for file_info in files:
|
||||
rel_path = file_info.name
|
||||
|
@ -101,8 +105,11 @@ class SandboxFilesTool(SandboxToolsBase):
|
|||
'''
|
||||
)
|
||||
async def create_file(self, file_path: str, file_contents: str, permissions: str = "644") -> ToolResult:
|
||||
file_path = self.clean_path(file_path)
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
file_path = self.clean_path(file_path)
|
||||
full_path = f"{self.workspace_path}/{file_path}"
|
||||
if self._file_exists(full_path):
|
||||
return self.fail_response(f"File '{file_path}' already exists. Use update_file to modify existing files.")
|
||||
|
@ -161,6 +168,9 @@ class SandboxFilesTool(SandboxToolsBase):
|
|||
)
|
||||
async def str_replace(self, file_path: str, old_str: str, new_str: str) -> ToolResult:
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
file_path = self.clean_path(file_path)
|
||||
full_path = f"{self.workspace_path}/{file_path}"
|
||||
if not self._file_exists(full_path):
|
||||
|
@ -235,6 +245,9 @@ class SandboxFilesTool(SandboxToolsBase):
|
|||
)
|
||||
async def full_file_rewrite(self, file_path: str, file_contents: str, permissions: str = "644") -> ToolResult:
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
file_path = self.clean_path(file_path)
|
||||
full_path = f"{self.workspace_path}/{file_path}"
|
||||
if not self._file_exists(full_path):
|
||||
|
@ -276,6 +289,9 @@ class SandboxFilesTool(SandboxToolsBase):
|
|||
)
|
||||
async def delete_file(self, file_path: str) -> ToolResult:
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
file_path = self.clean_path(file_path)
|
||||
full_path = f"{self.workspace_path}/{file_path}"
|
||||
if not self._file_exists(full_path):
|
||||
|
|
|
@ -2,13 +2,14 @@ from typing import Optional, Dict, List
|
|||
from uuid import uuid4
|
||||
from agentpress.tool import ToolResult, openapi_schema, xml_schema
|
||||
from sandbox.sandbox import SandboxToolsBase, Sandbox
|
||||
from agentpress.thread_manager import ThreadManager
|
||||
|
||||
class SandboxShellTool(SandboxToolsBase):
|
||||
"""Tool for executing tasks in a Daytona sandbox with browser-use capabilities.
|
||||
Uses sessions for maintaining state between commands and provides comprehensive process management."""
|
||||
|
||||
def __init__(self, sandbox: Sandbox):
|
||||
super().__init__(sandbox)
|
||||
def __init__(self, project_id: str, thread_manager: ThreadManager):
|
||||
super().__init__(project_id, thread_manager)
|
||||
self._sessions: Dict[str, str] = {} # Maps session names to session IDs
|
||||
self.workspace_path = "/workspace" # Ensure we're always operating in /workspace
|
||||
|
||||
|
@ -17,6 +18,7 @@ class SandboxShellTool(SandboxToolsBase):
|
|||
if session_name not in self._sessions:
|
||||
session_id = str(uuid4())
|
||||
try:
|
||||
await self._ensure_sandbox() # Ensure sandbox is initialized
|
||||
self.sandbox.process.create_session(session_id)
|
||||
self._sessions[session_name] = session_id
|
||||
except Exception as e:
|
||||
|
@ -27,6 +29,7 @@ class SandboxShellTool(SandboxToolsBase):
|
|||
"""Clean up a session if it exists."""
|
||||
if session_name in self._sessions:
|
||||
try:
|
||||
await self._ensure_sandbox() # Ensure sandbox is initialized
|
||||
self.sandbox.process.delete_session(self._sessions[session_name])
|
||||
del self._sessions[session_name]
|
||||
except Exception as e:
|
||||
|
@ -135,6 +138,9 @@ class SandboxShellTool(SandboxToolsBase):
|
|||
timeout: int = 60
|
||||
) -> ToolResult:
|
||||
try:
|
||||
# Ensure sandbox is initialized
|
||||
await self._ensure_sandbox()
|
||||
|
||||
# Ensure session exists
|
||||
session_id = await self._ensure_session(session_name)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from typing import Optional
|
||||
|
||||
from daytona_sdk import Daytona, DaytonaConfig, CreateSandboxParams, Sandbox, SessionExecuteRequest
|
||||
from daytona_api_client.models.workspace_state import WorkspaceState
|
||||
|
@ -7,6 +8,7 @@ from dotenv import load_dotenv
|
|||
from agentpress.tool import Tool
|
||||
from utils.logger import logger
|
||||
from utils.files_utils import clean_path
|
||||
from utils.thread_manager import ThreadManager
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
@ -138,48 +140,81 @@ def create_sandbox(password: str, sandbox_id: str = None):
|
|||
|
||||
|
||||
class SandboxToolsBase(Tool):
|
||||
"""Tool for executing tasks in a Daytona sandbox with browser-use capabilities."""
|
||||
"""Base class for all sandbox tools that provides project-based sandbox access."""
|
||||
|
||||
# Class variable to track if sandbox URLs have been printed
|
||||
_urls_printed = False
|
||||
|
||||
def __init__(self, sandbox: Sandbox):
|
||||
def __init__(self, project_id: str, thread_manager: Optional[ThreadManager] = None):
|
||||
super().__init__()
|
||||
self.sandbox = sandbox
|
||||
self.daytona = daytona
|
||||
self.project_id = project_id
|
||||
self.thread_manager = thread_manager
|
||||
self.workspace_path = "/workspace"
|
||||
self._sandbox = None
|
||||
self._sandbox_id = None
|
||||
self._sandbox_pass = None
|
||||
|
||||
self.sandbox_id = sandbox.id
|
||||
# logger.info(f"Initializing SandboxToolsBase with sandbox ID: {sandbox_id}")
|
||||
async def _ensure_sandbox(self) -> Sandbox:
|
||||
"""Ensure we have a valid sandbox instance, retrieving it from the project if needed."""
|
||||
if self._sandbox is None:
|
||||
try:
|
||||
# Get database client
|
||||
client = await self.thread_manager.db.client
|
||||
|
||||
# Get project data
|
||||
project = await client.table('projects').select('*').eq('project_id', self.project_id).execute()
|
||||
if not project.data or len(project.data) == 0:
|
||||
raise ValueError(f"Project {self.project_id} not found")
|
||||
|
||||
project_data = project.data[0]
|
||||
sandbox_info = project_data.get('sandbox', {})
|
||||
|
||||
if not sandbox_info.get('id'):
|
||||
raise ValueError(f"No sandbox found for project {self.project_id}")
|
||||
|
||||
# Store sandbox info
|
||||
self._sandbox_id = sandbox_info['id']
|
||||
self._sandbox_pass = sandbox_info.get('pass')
|
||||
|
||||
# Get or start the sandbox
|
||||
self._sandbox = await get_or_start_sandbox(self._sandbox_id)
|
||||
|
||||
# Log URLs if not already printed
|
||||
if not SandboxToolsBase._urls_printed:
|
||||
vnc_link = self._sandbox.get_preview_link(6080)
|
||||
website_link = self._sandbox.get_preview_link(8080)
|
||||
|
||||
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)
|
||||
|
||||
print("\033[95m***")
|
||||
print(f"VNC URL: {vnc_url}")
|
||||
print(f"Website URL: {website_url}")
|
||||
print("***\033[0m")
|
||||
SandboxToolsBase._urls_printed = True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving sandbox for project {self.project_id}: {str(e)}", exc_info=True)
|
||||
raise e
|
||||
|
||||
try:
|
||||
logger.debug(f"Retrieving sandbox with ID: {self.sandbox_id}")
|
||||
self.sandbox = self.daytona.get_current_sandbox(self.sandbox_id)
|
||||
# logger.info(f"Successfully retrieved sandbox: {self.sandbox.id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving sandbox: {str(e)}", exc_info=True)
|
||||
raise e
|
||||
return self._sandbox
|
||||
|
||||
# Get preview links
|
||||
vnc_link = self.sandbox.get_preview_link(6080)
|
||||
website_link = self.sandbox.get_preview_link(8080)
|
||||
|
||||
# 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
|
||||
# logger.info(f"Sandbox VNC URL: {vnc_url}")
|
||||
# logger.info(f"Sandbox Website URL: {website_url}")
|
||||
|
||||
# if not SandboxToolsBase._urls_printed:
|
||||
# print("\033[95m***")
|
||||
# print(vnc_url)
|
||||
# print(website_url)
|
||||
# print("***\033[0m")
|
||||
# SandboxToolsBase._urls_printed = True
|
||||
@property
|
||||
def sandbox(self) -> Sandbox:
|
||||
"""Get the sandbox instance, ensuring it exists."""
|
||||
if self._sandbox is None:
|
||||
raise RuntimeError("Sandbox not initialized. Call _ensure_sandbox() first.")
|
||||
return self._sandbox
|
||||
|
||||
@property
|
||||
def sandbox_id(self) -> str:
|
||||
"""Get the sandbox ID, ensuring it exists."""
|
||||
if self._sandbox_id is None:
|
||||
raise RuntimeError("Sandbox ID not initialized. Call _ensure_sandbox() first.")
|
||||
return self._sandbox_id
|
||||
|
||||
def clean_path(self, path: str) -> str:
|
||||
"""Clean and normalize a path to be relative to /workspace."""
|
||||
cleaned_path = clean_path(path, self.workspace_path)
|
||||
logger.debug(f"Cleaned path: {path} -> {cleaned_path}")
|
||||
return cleaned_path
|
Loading…
Reference in New Issue