add README and delete sandbox_id
This commit is contained in:
+53
-57
@@ -1,11 +1,14 @@
|
||||
import json
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
|
||||
import daytona
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
from app.agent.browser import BrowserContextHelper
|
||||
from app.agent.toolcall import ToolCallAgent
|
||||
from app.config import config
|
||||
from app.daytona.sandbox import create_sandbox, delete_sandbox, get_or_start_sandbox
|
||||
from app.daytona.tool_base import SandboxToolsBase
|
||||
from app.logger import logger
|
||||
from app.prompt.manus import NEXT_STEP_PROMPT, SYSTEM_PROMPT
|
||||
from app.schema import Message
|
||||
@@ -14,20 +17,20 @@ from app.tool.ask_human import AskHuman
|
||||
from app.tool.browser_use_tool import BrowserUseTool
|
||||
from app.tool.mcp import MCPClients, MCPClientTool
|
||||
from app.tool.python_execute import PythonExecute
|
||||
from app.tool.sb_browser_tool import SandboxBrowserTool
|
||||
from app.tool.sb_files_tool import SandboxFilesTool
|
||||
from app.tool.sb_shell_tool import SandboxShellTool
|
||||
from app.tool.sb_vision_tool import SandboxVisionTool
|
||||
from app.tool.str_replace_editor import StrReplaceEditor
|
||||
|
||||
from app.tool.sb_browser_tool import SandboxBrowserTool
|
||||
from app.daytona.sandbox import create_sandbox,get_or_start_sandbox
|
||||
import daytona
|
||||
|
||||
class SandboxManus(ToolCallAgent):
|
||||
"""A versatile general-purpose agent with support for both local and MCP tools."""
|
||||
|
||||
name: str = "SandboxManus"
|
||||
description: str = "A versatile agent that can solve various tasks using multiple sandbox-tools including MCP-based tools"
|
||||
description: str = (
|
||||
"A versatile agent that can solve various tasks using multiple sandbox-tools including MCP-based tools"
|
||||
)
|
||||
|
||||
system_prompt: str = SYSTEM_PROMPT.format(directory=config.workspace_root)
|
||||
next_step_prompt: str = NEXT_STEP_PROMPT
|
||||
@@ -74,66 +77,41 @@ class SandboxManus(ToolCallAgent):
|
||||
instance._initialized = True
|
||||
return instance
|
||||
|
||||
async def initialize_sandbox_tools(self, sandbox_id: str = config.daytona.sandbox_id, password:str = config.daytona.VNC_password) -> None:
|
||||
async def initialize_sandbox_tools(
|
||||
self,
|
||||
password: str = config.daytona.VNC_password,
|
||||
) -> None:
|
||||
try:
|
||||
if sandbox_id:
|
||||
sandbox = await get_or_start_sandbox(sandbox_id)
|
||||
|
||||
# Initialize sandbox_link if not exists
|
||||
if not self.sandbox_link:
|
||||
self.sandbox_link = {}
|
||||
|
||||
# Check if URLs are already cached
|
||||
if sandbox_id in self.sandbox_link:
|
||||
vnc_url = self.sandbox_link[sandbox_id]["vnc"]
|
||||
website_url = self.sandbox_link[sandbox_id]["website"]
|
||||
logger.info(f"Using cached URLs - VNC: {vnc_url}, Website: {website_url}")
|
||||
else:
|
||||
# Get URLs from sandbox and cache them
|
||||
vnc_link = sandbox.get_preview_link(6080)
|
||||
website_link = 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)
|
||||
|
||||
# Cache the URLs
|
||||
self.sandbox_link[sandbox_id] = {
|
||||
"vnc": str(vnc_url),
|
||||
"website": str(website_url)
|
||||
}
|
||||
logger.info(f"Cached new URLs - VNC: {vnc_url}, Website: {website_url}")
|
||||
|
||||
logger.info(f"VNC URL: {vnc_url}")
|
||||
logger.info(f"Website URL: {website_url}")
|
||||
# 创建新沙箱
|
||||
if password:
|
||||
sandbox = create_sandbox(password=password)
|
||||
self.sandbox = sandbox
|
||||
else:
|
||||
# 创建新沙箱
|
||||
if password:
|
||||
sandbox = create_sandbox(password=password)
|
||||
else:
|
||||
raise ValueError("Sandbox ID or password must be provided")
|
||||
vnc_link = sandbox.get_preview_link(6080)
|
||||
website_link = 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)
|
||||
|
||||
# Get the actual sandbox_id from the created sandbox
|
||||
actual_sandbox_id = sandbox.id if hasattr(sandbox, 'id') else 'new_sandbox'
|
||||
if not self.sandbox_link:
|
||||
self.sandbox_link = {}
|
||||
self.sandbox_link[actual_sandbox_id] = {
|
||||
"vnc": vnc_url,
|
||||
"website": website_url,
|
||||
}
|
||||
logger.info(f"VNC URL: {vnc_url}")
|
||||
logger.info(f"Website URL: {website_url}")
|
||||
raise ValueError("password must be provided")
|
||||
vnc_link = sandbox.get_preview_link(6080)
|
||||
website_link = 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)
|
||||
)
|
||||
|
||||
# Get the actual sandbox_id from the created sandbox
|
||||
actual_sandbox_id = sandbox.id if hasattr(sandbox, "id") else "new_sandbox"
|
||||
if not self.sandbox_link:
|
||||
self.sandbox_link = {}
|
||||
self.sandbox_link[actual_sandbox_id] = {
|
||||
"vnc": vnc_url,
|
||||
"website": website_url,
|
||||
}
|
||||
logger.info(f"VNC URL: {vnc_url}")
|
||||
logger.info(f"Website URL: {website_url}")
|
||||
SandboxToolsBase._urls_printed = True
|
||||
sb_tools = [
|
||||
SandboxBrowserTool(sandbox),
|
||||
SandboxFilesTool(sandbox),
|
||||
SandboxShellTool(sandbox),
|
||||
SandboxVisionTool(sandbox)
|
||||
SandboxVisionTool(sandbox),
|
||||
]
|
||||
# for sb_tool in sb_tools:
|
||||
# self.available_tools.add_tool(sb_tool)
|
||||
self.available_tools.add_tools(*sb_tools)
|
||||
|
||||
except Exception as e:
|
||||
@@ -204,6 +182,17 @@ class SandboxManus(ToolCallAgent):
|
||||
self.available_tools = ToolCollection(*base_tools)
|
||||
self.available_tools.add_tools(*self.mcp_clients.tools)
|
||||
|
||||
async def delete_sandbox(self, sandbox_id: str) -> None:
|
||||
"""Delete a sandbox by ID."""
|
||||
try:
|
||||
await delete_sandbox(sandbox_id)
|
||||
logger.info(f"Sandbox {sandbox_id} deleted successfully")
|
||||
if sandbox_id in self.sandbox_link:
|
||||
del self.sandbox_link[sandbox_id]
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting sandbox {sandbox_id}: {e}")
|
||||
raise e
|
||||
|
||||
async def cleanup(self):
|
||||
"""Clean up Manus agent resources."""
|
||||
if self.browser_context_helper:
|
||||
@@ -211,6 +200,7 @@ class SandboxManus(ToolCallAgent):
|
||||
# Disconnect from all MCP servers only if we were initialized
|
||||
if self._initialized:
|
||||
await self.disconnect_mcp_server()
|
||||
await self.delete_sandbox(self.sandbox.id if self.sandbox else "unknown")
|
||||
self._initialized = False
|
||||
|
||||
async def think(self) -> bool:
|
||||
@@ -239,3 +229,9 @@ class SandboxManus(ToolCallAgent):
|
||||
self.next_step_prompt = original_prompt
|
||||
|
||||
return result
|
||||
return result
|
||||
return result
|
||||
return result
|
||||
return result
|
||||
return result
|
||||
return result
|
||||
|
||||
+17
-6
@@ -107,12 +107,22 @@ class SandboxSettings(BaseModel):
|
||||
|
||||
class DaytonaSettings(BaseModel):
|
||||
daytona_api_key: str
|
||||
daytona_server_url: Optional[str] = Field("https://app.daytona.io/api", description="")
|
||||
daytona_target: Optional[str] = Field("us", description="enum ['asia', 'eu', 'us']")
|
||||
sandbox_image_name: Optional[str]= Field("kortix/suna:0.1.3", description="")
|
||||
sandbox_entrypoint: Optional[str]= Field("/usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf", description="")
|
||||
sandbox_id: Optional[str] = Field(None, description="ID of the daytona sandbox to use, if any")
|
||||
VNC_password: Optional[str] = Field("123456", description="VNC password for the vnc service in sandbox")
|
||||
daytona_server_url: Optional[str] = Field(
|
||||
"https://app.daytona.io/api", description=""
|
||||
)
|
||||
daytona_target: Optional[str] = Field("us", description="enum ['eu', 'us']")
|
||||
sandbox_image_name: Optional[str] = Field("whitezxj/sandbox:0.1.0", description="")
|
||||
sandbox_entrypoint: Optional[str] = Field(
|
||||
"/usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf",
|
||||
description="",
|
||||
)
|
||||
# sandbox_id: Optional[str] = Field(
|
||||
# None, description="ID of the daytona sandbox to use, if any"
|
||||
# )
|
||||
VNC_password: Optional[str] = Field(
|
||||
"123456", description="VNC password for the vnc service in sandbox"
|
||||
)
|
||||
|
||||
|
||||
class MCPServerConfig(BaseModel):
|
||||
"""Configuration for a single MCP server"""
|
||||
@@ -325,6 +335,7 @@ class Config:
|
||||
@property
|
||||
def sandbox(self) -> SandboxSettings:
|
||||
return self._config.sandbox
|
||||
|
||||
@property
|
||||
def daytona(self) -> DaytonaSettings:
|
||||
return self._config.daytona_config
|
||||
|
||||
+44
-25
@@ -1,39 +1,58 @@
|
||||
# Agent Sandbox
|
||||
# Agent with Daytona sandbox
|
||||
|
||||
This directory contains the agent sandbox implementation - a Docker-based virtual environment that agents use as their own computer to execute tasks, access the web, and manipulate files.
|
||||
|
||||
## Overview
|
||||
|
||||
The sandbox provides a complete containerized Linux environment with:
|
||||
- Chrome browser for web interactions
|
||||
- VNC server for accessing the Web User
|
||||
- Full file system access
|
||||
- Full sudo access
|
||||
|
||||
## Customizing the Sandbox
|
||||
## Prerequisites
|
||||
- conda activate 'Your OpenManus python env'
|
||||
- pip install daytona==0.21.8 structlog==25.4.0
|
||||
|
||||
You can modify the sandbox environment for development or to add new capabilities:
|
||||
|
||||
1. Edit files in the `docker/` directory
|
||||
2. Build a custom image:
|
||||
|
||||
## Setup & Running
|
||||
|
||||
1. daytona config :
|
||||
```bash
|
||||
cd OpenManus
|
||||
cp config/config.example-daytona.toml config/config.toml
|
||||
```
|
||||
cd daytona/docker
|
||||
docker build
|
||||
2. get daytona apikey :
|
||||
goto https://app.daytona.io/dashboard/keys and create your apikey
|
||||
|
||||
3. set your apikey in config.toml
|
||||
```toml
|
||||
# daytona config
|
||||
[daytona]
|
||||
daytona_api_key = ""
|
||||
#daytona_server_url = "https://app.daytona.io/api"
|
||||
#daytona_target = "us" #Daytona is currently available in the following regions:United States (us)、Europe (eu)
|
||||
#sandbox_image_name = "whitezxj/sandbox:0.1.0" #If you don't use this default image,sandbox tools may be useless
|
||||
#sandbox_entrypoint = "/usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf" #If you change this entrypoint,server in sandbox may be useless
|
||||
#VNC_password = #The password you set to log in sandbox by VNC,it will be 123456 if you don't set
|
||||
```
|
||||
3. Test your changes locally using docker-compose
|
||||
2. Run :
|
||||
|
||||
## Using a Custom Image
|
||||
```bash
|
||||
cd OpenManus
|
||||
python sandbox_main.py
|
||||
```
|
||||
|
||||
To use your custom sandbox image:
|
||||
3. Send tasks to Agent
|
||||
You can sent tasks to Agent by terminate,agent will use sandbox tools to handle your tasks.
|
||||
|
||||
1. Change the `image` parameter in `docker-compose.yml` (that defines the image name `kortix/suna:___`)
|
||||
2. Update the same image name in `backend/sandbox/sandbox.py` in the `create_sandbox` function
|
||||
3. If using Daytona for deployment, update the image reference there as well
|
||||
4. See results
|
||||
If agent use sb_browser_use tool, you can see the operations by VNC link, The VNC link will print in the termination,e.g.:https://6080-sandbox-123456.h7890.daytona.work.
|
||||
If agent use sb_shell tool, you can see the results by terminate of sandbox in https://app.daytona.io/dashboard/sandboxes.
|
||||
Agent can use sb_files tool to operate files to sandbox.
|
||||
|
||||
## Publishing New Versions
|
||||
|
||||
When publishing a new version of the sandbox:
|
||||
## Example
|
||||
|
||||
You can send task e.g.:"帮我在https://hk.trip.com/travel-guide/guidebook/nanjing-9696/?ishideheader=true&isHideNavBar=YES&disableFontScaling=1&catalogId=514634&locale=zh-HK查询相关信息上制定一份南京旅游攻略,并在工作区保存为index.html"
|
||||
|
||||
Then you can see the agent's browser action in VNC link(https://6080-sandbox-123456.h7890.proxy.daytona.work) and you can see the html made by agent in Website URL(https://8080-sandbox-123456.h7890.proxy.daytona.work).
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Daytona Documentation](https://www.daytona.io/docs/)
|
||||
|
||||
4. Update all references to the image version in:
|
||||
- Daytona images
|
||||
- Any other services using this image
|
||||
@@ -1,3 +0,0 @@
|
||||
structlog
|
||||
daytona
|
||||
litellm
|
||||
+39
-22
@@ -1,36 +1,44 @@
|
||||
from daytona import Daytona, DaytonaConfig, CreateSandboxFromImageParams, Sandbox, SessionExecuteRequest, Resources, SandboxState
|
||||
from daytona import (
|
||||
CreateSandboxFromImageParams,
|
||||
Daytona,
|
||||
DaytonaConfig,
|
||||
Resources,
|
||||
Sandbox,
|
||||
SandboxState,
|
||||
SessionExecuteRequest,
|
||||
)
|
||||
from dotenv import load_dotenv
|
||||
from app.utils.logger import logger
|
||||
# from app.utils.config import config
|
||||
# from utils.config import config
|
||||
# from app.utils.config import Configuration
|
||||
|
||||
from app.config import config
|
||||
from app.utils.logger import logger
|
||||
|
||||
# load_dotenv()
|
||||
daytona_settings=config.daytona
|
||||
logger.debug("Initializing Daytona sandbox configuration")
|
||||
daytona_settings = config.daytona
|
||||
logger.info("Initializing Daytona sandbox configuration")
|
||||
daytona_config = DaytonaConfig(
|
||||
api_key=daytona_settings.daytona_api_key,
|
||||
server_url=daytona_settings.daytona_server_url,
|
||||
target=daytona_settings.daytona_target
|
||||
target=daytona_settings.daytona_target,
|
||||
)
|
||||
|
||||
if daytona_config.api_key:
|
||||
logger.debug("Daytona API key configured successfully")
|
||||
logger.info("Daytona API key configured successfully")
|
||||
else:
|
||||
logger.warning("No Daytona API key found in environment variables")
|
||||
|
||||
if daytona_config.server_url:
|
||||
logger.debug(f"Daytona server URL set to: {daytona_config.server_url}")
|
||||
logger.info(f"Daytona server URL set to: {daytona_config.server_url}")
|
||||
else:
|
||||
logger.warning("No Daytona server URL found in environment variables")
|
||||
|
||||
if daytona_config.target:
|
||||
logger.debug(f"Daytona target set to: {daytona_config.target}")
|
||||
logger.info(f"Daytona target set to: {daytona_config.target}")
|
||||
else:
|
||||
logger.warning("No Daytona target found in environment variables")
|
||||
|
||||
daytona = Daytona(daytona_config)
|
||||
logger.debug("Daytona client initialized")
|
||||
logger.info("Daytona client initialized")
|
||||
|
||||
|
||||
async def get_or_start_sandbox(sandbox_id: str):
|
||||
"""Retrieve a sandbox by ID, check its state, and start it if needed."""
|
||||
@@ -41,7 +49,10 @@ async def get_or_start_sandbox(sandbox_id: str):
|
||||
sandbox = daytona.get(sandbox_id)
|
||||
|
||||
# Check if sandbox needs to be started
|
||||
if sandbox.state == SandboxState.ARCHIVED or sandbox.state == SandboxState.STOPPED:
|
||||
if (
|
||||
sandbox.state == SandboxState.ARCHIVED
|
||||
or sandbox.state == SandboxState.STOPPED
|
||||
):
|
||||
logger.info(f"Sandbox is in {sandbox.state} state. Starting...")
|
||||
try:
|
||||
daytona.start(sandbox)
|
||||
@@ -63,6 +74,7 @@ async def get_or_start_sandbox(sandbox_id: str):
|
||||
logger.error(f"Error retrieving or starting sandbox: {str(e)}")
|
||||
raise e
|
||||
|
||||
|
||||
def start_supervisord_session(sandbox: Sandbox):
|
||||
"""Start supervisord in a session."""
|
||||
session_id = "supervisord-session"
|
||||
@@ -71,25 +83,29 @@ def start_supervisord_session(sandbox: Sandbox):
|
||||
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
|
||||
))
|
||||
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
|
||||
|
||||
|
||||
def create_sandbox(password: str, project_id: str = None):
|
||||
"""Create a new sandbox with all required services configured and running."""
|
||||
|
||||
logger.debug("Creating new Daytona sandbox environment")
|
||||
logger.debug("Configuring sandbox with browser-use image and environment variables")
|
||||
logger.info("Creating new Daytona sandbox environment")
|
||||
logger.info("Configuring sandbox with browser-use image and environment variables")
|
||||
|
||||
labels = None
|
||||
if project_id:
|
||||
logger.debug(f"Using sandbox_id as label: {project_id}")
|
||||
labels = {'id': project_id}
|
||||
logger.info(f"Using sandbox_id as label: {project_id}")
|
||||
labels = {"id": project_id}
|
||||
|
||||
params = CreateSandboxFromImageParams(
|
||||
image=daytona_settings.sandbox_image_name,
|
||||
@@ -106,7 +122,7 @@ def create_sandbox(password: str, project_id: str = None):
|
||||
"CHROME_USER_DATA": "",
|
||||
"CHROME_DEBUGGING_PORT": "9222",
|
||||
"CHROME_DEBUGGING_HOST": "localhost",
|
||||
"CHROME_CDP": ""
|
||||
"CHROME_CDP": "",
|
||||
},
|
||||
resources=Resources(
|
||||
cpu=2,
|
||||
@@ -127,6 +143,7 @@ def create_sandbox(password: str, project_id: str = None):
|
||||
logger.debug(f"Sandbox environment successfully initialized")
|
||||
return sandbox
|
||||
|
||||
|
||||
async def delete_sandbox(sandbox_id: str):
|
||||
"""Delete a sandbox by its ID."""
|
||||
logger.info(f"Deleting sandbox with ID: {sandbox_id}")
|
||||
|
||||
+74
-48
@@ -1,25 +1,46 @@
|
||||
import asyncio
|
||||
|
||||
from dataclasses import field, dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Optional, ClassVar, Dict, Any
|
||||
from typing import Any, ClassVar, Dict, Optional
|
||||
|
||||
from daytona import Daytona, DaytonaConfig, Sandbox, SandboxState
|
||||
from pydantic import Field
|
||||
|
||||
from app.config import config
|
||||
from app.daytona.sandbox import (
|
||||
create_sandbox,
|
||||
get_or_start_sandbox,
|
||||
start_supervisord_session,
|
||||
)
|
||||
|
||||
# from app.agentpress.thread_manager import ThreadManager
|
||||
from app.tool.base import BaseTool, ToolResult
|
||||
from daytona import Sandbox
|
||||
from app.utils.logger import logger
|
||||
from app.utils.files_utils import clean_path
|
||||
from app.utils.logger import logger
|
||||
|
||||
# load_dotenv()
|
||||
daytona_settings = config.daytona
|
||||
daytona_config = DaytonaConfig(
|
||||
api_key=daytona_settings.daytona_api_key,
|
||||
server_url=daytona_settings.daytona_server_url,
|
||||
target=daytona_settings.daytona_target,
|
||||
)
|
||||
daytona = Daytona(daytona_config)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ThreadMessage:
|
||||
"""
|
||||
Represents a message to be added to a thread.
|
||||
"""
|
||||
|
||||
type: str
|
||||
content: Dict[str, Any]
|
||||
is_llm_message: bool = False
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
timestamp: Optional[float] = field(default_factory=lambda: datetime.now().timestamp())
|
||||
timestamp: Optional[float] = field(
|
||||
default_factory=lambda: datetime.now().timestamp()
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert the message to a dictionary for API calls"""
|
||||
@@ -28,9 +49,10 @@ class ThreadMessage:
|
||||
"content": self.content,
|
||||
"is_llm_message": self.is_llm_message,
|
||||
"metadata": self.metadata or {},
|
||||
"timestamp": self.timestamp
|
||||
"timestamp": self.timestamp,
|
||||
}
|
||||
|
||||
|
||||
class SandboxToolsBase(BaseTool):
|
||||
"""Base class for all sandbox tools that provides project-based sandbox access."""
|
||||
|
||||
@@ -48,53 +70,55 @@ class SandboxToolsBase(BaseTool):
|
||||
workspace_path: str = Field(default="/workspace", exclude=True)
|
||||
_sessions: dict[str, str] = {}
|
||||
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True # Allow non-pydantic types like ThreadManager
|
||||
underscore_attrs_are_private = True
|
||||
|
||||
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 Exception
|
||||
# Get or start the sandbox
|
||||
try:
|
||||
self._sandbox = create_sandbox(password=config.daytona.VNC_password)
|
||||
# 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 or starting sandbox: {str(e)}")
|
||||
raise e
|
||||
else:
|
||||
if (
|
||||
self._sandbox.state == SandboxState.ARCHIVED
|
||||
or self._sandbox.state == SandboxState.STOPPED
|
||||
):
|
||||
logger.info(f"Sandbox is in {self._sandbox.state} state. Starting...")
|
||||
try:
|
||||
daytona.start(self._sandbox)
|
||||
# Wait a moment for the sandbox to initialize
|
||||
# sleep(5)
|
||||
# Refresh sandbox state after starting
|
||||
|
||||
# Start supervisord in a session when restarting
|
||||
start_supervisord_session(self._sandbox)
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting sandbox: {e}")
|
||||
raise e
|
||||
return self._sandbox
|
||||
|
||||
@property
|
||||
@@ -108,7 +132,9 @@ class SandboxToolsBase(BaseTool):
|
||||
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.")
|
||||
raise RuntimeError(
|
||||
"Sandbox ID not initialized. Call _ensure_sandbox() first."
|
||||
)
|
||||
return self._sandbox_id
|
||||
|
||||
def clean_path(self, path: str) -> str:
|
||||
|
||||
+116
-53
@@ -1,18 +1,20 @@
|
||||
import traceback
|
||||
import json
|
||||
import base64
|
||||
import io
|
||||
from PIL import Image
|
||||
|
||||
import json
|
||||
import traceback
|
||||
from typing import Optional # Add this import for Optional
|
||||
|
||||
from PIL import Image
|
||||
from pydantic import Field
|
||||
from app.daytona.tool_base import Sandbox, ThreadMessage # Ensure Sandbox is imported correctly
|
||||
|
||||
from app.daytona.tool_base import ( # Ensure Sandbox is imported correctly
|
||||
Sandbox,
|
||||
SandboxToolsBase,
|
||||
ThreadMessage,
|
||||
)
|
||||
from app.tool.base import ToolResult
|
||||
from app.daytona.tool_base import SandboxToolsBase
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
|
||||
# Context = TypeVar("Context")
|
||||
_BROWSER_DESCRIPTION = """\
|
||||
A sandbox-based browser automation tool that allows interaction with web pages through various actions.
|
||||
@@ -55,7 +57,7 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
"get_dropdown_options",
|
||||
"select_dropdown_option",
|
||||
"click_coordinates",
|
||||
"drag_drop"
|
||||
"drag_drop",
|
||||
],
|
||||
"description": "The browser action to perform",
|
||||
},
|
||||
@@ -102,7 +104,7 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
"element_target": {
|
||||
"type": "string",
|
||||
"description": "Target element for drag and drop",
|
||||
}
|
||||
},
|
||||
},
|
||||
"required": ["action"],
|
||||
"dependencies": {
|
||||
@@ -124,13 +126,17 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
}
|
||||
browser_message: Optional[ThreadMessage] = Field(default=None, exclude=True)
|
||||
|
||||
def __init__(self, sandbox: Optional[Sandbox] = None, thread_id: Optional[str] = None, **data):
|
||||
def __init__(
|
||||
self, sandbox: Optional[Sandbox] = None, thread_id: Optional[str] = None, **data
|
||||
):
|
||||
"""Initialize with optional sandbox and thread_id."""
|
||||
super().__init__(**data)
|
||||
if sandbox is not None:
|
||||
self._sandbox = sandbox # Directly set the base class private attribute
|
||||
|
||||
def _validate_base64_image(self, base64_string: str, max_size_mb: int = 10) -> tuple[bool, str]:
|
||||
def _validate_base64_image(
|
||||
self, base64_string: str, max_size_mb: int = 10
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
Validate base64 image data.
|
||||
Args:
|
||||
@@ -142,13 +148,14 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
try:
|
||||
if not base64_string or len(base64_string) < 10:
|
||||
return False, "Base64 string is empty or too short"
|
||||
if base64_string.startswith('data:'):
|
||||
if base64_string.startswith("data:"):
|
||||
try:
|
||||
base64_string = base64_string.split(',', 1)[1]
|
||||
base64_string = base64_string.split(",", 1)[1]
|
||||
except (IndexError, ValueError):
|
||||
return False, "Invalid data URL format"
|
||||
import re
|
||||
if not re.match(r'^[A-Za-z0-9+/]*={0,2}$', base64_string):
|
||||
|
||||
if not re.match(r"^[A-Za-z0-9+/]*={0,2}$", base64_string):
|
||||
return False, "Invalid base64 characters detected"
|
||||
if len(base64_string) % 4 != 0:
|
||||
return False, "Invalid base64 string length"
|
||||
@@ -163,7 +170,7 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
image_stream = io.BytesIO(image_data)
|
||||
with Image.open(image_stream) as img:
|
||||
img.verify()
|
||||
supported_formats = {'JPEG', 'PNG', 'GIF', 'BMP', 'WEBP', 'TIFF'}
|
||||
supported_formats = {"JPEG", "PNG", "GIF", "BMP", "WEBP", "TIFF"}
|
||||
if img.format not in supported_formats:
|
||||
return False, f"Unsupported image format: {img.format}"
|
||||
image_stream.seek(0)
|
||||
@@ -171,7 +178,10 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
width, height = img_check.size
|
||||
max_dimension = 8192
|
||||
if width > max_dimension or height > max_dimension:
|
||||
return False, f"Image dimensions exceed limit ({max_dimension}x{max_dimension})"
|
||||
return (
|
||||
False,
|
||||
f"Image dimensions exceed limit ({max_dimension}x{max_dimension})",
|
||||
)
|
||||
if width < 1 or height < 1:
|
||||
return False, f"Invalid image dimensions: {width}x{height}"
|
||||
except Exception as e:
|
||||
@@ -180,16 +190,24 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during base64 image validation: {e}")
|
||||
return False, f"Validation error: {str(e)}"
|
||||
async def _execute_browser_action(self, endpoint: str, params: dict = None, method: str = "POST") -> ToolResult:
|
||||
|
||||
async def _execute_browser_action(
|
||||
self, endpoint: str, params: dict = None, method: str = "POST"
|
||||
) -> ToolResult:
|
||||
"""Execute a browser automation action through the sandbox API."""
|
||||
try:
|
||||
await self._ensure_sandbox()
|
||||
url = f"http://localhost:8003/api/automation/{endpoint}"
|
||||
if method == "GET" and params:
|
||||
query_params = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
url = f"{url}?{query_params}"
|
||||
curl_cmd = f"curl -s -X {method} '{url}' -H 'Content-Type: application/json'"
|
||||
curl_cmd = (
|
||||
f"curl -s -X {method} '{url}' -H 'Content-Type: application/json'"
|
||||
)
|
||||
else:
|
||||
curl_cmd = f"curl -s -X {method} '{url}' -H 'Content-Type: application/json'"
|
||||
curl_cmd = (
|
||||
f"curl -s -X {method} '{url}' -H 'Content-Type: application/json'"
|
||||
)
|
||||
if params:
|
||||
json_data = json.dumps(params)
|
||||
curl_cmd += f" -d '{json_data}'"
|
||||
@@ -202,9 +220,13 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
result.setdefault("role", "assistant")
|
||||
if "screenshot_base64" in result:
|
||||
screenshot_data = result["screenshot_base64"]
|
||||
is_valid, validation_message = self._validate_base64_image(screenshot_data)
|
||||
is_valid, validation_message = self._validate_base64_image(
|
||||
screenshot_data
|
||||
)
|
||||
if not is_valid:
|
||||
logger.warning(f"Screenshot validation failed: {validation_message}")
|
||||
logger.warning(
|
||||
f"Screenshot validation failed: {validation_message}"
|
||||
)
|
||||
result["image_validation_error"] = validation_message
|
||||
del result["screenshot_base64"]
|
||||
|
||||
@@ -215,33 +237,43 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
# is_llm_message=False
|
||||
# )
|
||||
message = ThreadMessage(
|
||||
type="browser_state",
|
||||
content=result,
|
||||
is_llm_message=False
|
||||
type="browser_state", content=result, is_llm_message=False
|
||||
)
|
||||
self.browser_message = message
|
||||
success_response = {
|
||||
"success": result.get("success", False),
|
||||
"message": result.get("message", "Browser action completed")
|
||||
"message": result.get("message", "Browser action completed"),
|
||||
}
|
||||
# if added_message and 'message_id' in added_message:
|
||||
# success_response['message_id'] = added_message['message_id']
|
||||
for field in ["url", "title", "element_count", "pixels_below", "ocr_text", "image_url"]:
|
||||
# if added_message and 'message_id' in added_message:
|
||||
# success_response['message_id'] = added_message['message_id']
|
||||
for field in [
|
||||
"url",
|
||||
"title",
|
||||
"element_count",
|
||||
"pixels_below",
|
||||
"ocr_text",
|
||||
"image_url",
|
||||
]:
|
||||
if field in result:
|
||||
success_response[field] = result[field]
|
||||
return (result,self.success_response(success_response)) if success_response["success"] else self.fail_response(success_response)
|
||||
return (
|
||||
(result, self.success_response(success_response))
|
||||
if success_response["success"]
|
||||
else self.fail_response(success_response)
|
||||
)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse response JSON: {e}")
|
||||
return self.fail_response(f"Failed to parse response JSON: {e}")
|
||||
else:
|
||||
logger.error(f"Browser automation request failed: {response}")
|
||||
return self.fail_response(f"Browser automation request failed: {response}")
|
||||
return self.fail_response(
|
||||
f"Browser automation request failed: {response}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing browser action: {e}")
|
||||
logger.debug(traceback.format_exc())
|
||||
return self.fail_response(f"Error executing browser action: {e}")
|
||||
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
action: str,
|
||||
@@ -256,7 +288,7 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
y: Optional[int] = None,
|
||||
element_source: Optional[str] = None,
|
||||
element_target: Optional[str] = None,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
) -> ToolResult:
|
||||
"""
|
||||
Execute a browser action in the sandbox environment.
|
||||
@@ -278,7 +310,7 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
"""
|
||||
# async with self.lock:
|
||||
try:
|
||||
# Navigation actions
|
||||
# Navigation actions
|
||||
if action == "navigate_to":
|
||||
if not url:
|
||||
return self.fail_response("URL is required for navigation")
|
||||
@@ -289,11 +321,17 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
elif action == "click_element":
|
||||
if index is None:
|
||||
return self.fail_response("Index is required for click_element")
|
||||
return await self._execute_browser_action("click_element", {"index": index})
|
||||
return await self._execute_browser_action(
|
||||
"click_element", {"index": index}
|
||||
)
|
||||
elif action == "input_text":
|
||||
if index is None or not text:
|
||||
return self.fail_response("Index and text are required for input_text")
|
||||
return await self._execute_browser_action("input_text", {"index": index, "text": text})
|
||||
return self.fail_response(
|
||||
"Index and text are required for input_text"
|
||||
)
|
||||
return await self._execute_browser_action(
|
||||
"input_text", {"index": index, "text": text}
|
||||
)
|
||||
elif action == "send_keys":
|
||||
if not keys:
|
||||
return self.fail_response("Keys are required for send_keys")
|
||||
@@ -302,11 +340,15 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
elif action == "switch_tab":
|
||||
if page_id is None:
|
||||
return self.fail_response("Page ID is required for switch_tab")
|
||||
return await self._execute_browser_action("switch_tab", {"page_id": page_id})
|
||||
return await self._execute_browser_action(
|
||||
"switch_tab", {"page_id": page_id}
|
||||
)
|
||||
elif action == "close_tab":
|
||||
if page_id is None:
|
||||
return self.fail_response("Page ID is required for close_tab")
|
||||
return await self._execute_browser_action("close_tab", {"page_id": page_id})
|
||||
return await self._execute_browser_action(
|
||||
"close_tab", {"page_id": page_id}
|
||||
)
|
||||
# Scrolling actions
|
||||
elif action == "scroll_down":
|
||||
params = {"amount": amount} if amount is not None else {}
|
||||
@@ -317,32 +359,53 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
elif action == "scroll_to_text":
|
||||
if not text:
|
||||
return self.fail_response("Text is required for scroll_to_text")
|
||||
return await self._execute_browser_action("scroll_to_text", {"text": text})
|
||||
return await self._execute_browser_action(
|
||||
"scroll_to_text", {"text": text}
|
||||
)
|
||||
# Dropdown actions
|
||||
elif action == "get_dropdown_options":
|
||||
if index is None:
|
||||
return self.fail_response("Index is required for get_dropdown_options")
|
||||
return await self._execute_browser_action("get_dropdown_options", {"index": index})
|
||||
return self.fail_response(
|
||||
"Index is required for get_dropdown_options"
|
||||
)
|
||||
return await self._execute_browser_action(
|
||||
"get_dropdown_options", {"index": index}
|
||||
)
|
||||
elif action == "select_dropdown_option":
|
||||
if index is None or not text:
|
||||
return self.fail_response("Index and text are required for select_dropdown_option")
|
||||
return await self._execute_browser_action("select_dropdown_option", {"index": index, "text": text})
|
||||
return self.fail_response(
|
||||
"Index and text are required for select_dropdown_option"
|
||||
)
|
||||
return await self._execute_browser_action(
|
||||
"select_dropdown_option", {"index": index, "text": text}
|
||||
)
|
||||
# Coordinate-based actions
|
||||
elif action == "click_coordinates":
|
||||
if x is None or y is None:
|
||||
return self.fail_response("X and Y coordinates are required for click_coordinates")
|
||||
return await self._execute_browser_action("click_coordinates", {"x": x, "y": y})
|
||||
return self.fail_response(
|
||||
"X and Y coordinates are required for click_coordinates"
|
||||
)
|
||||
return await self._execute_browser_action(
|
||||
"click_coordinates", {"x": x, "y": y}
|
||||
)
|
||||
elif action == "drag_drop":
|
||||
if not element_source or not element_target:
|
||||
return self.fail_response("Source and target elements are required for drag_drop")
|
||||
return await self._execute_browser_action("drag_drop", {
|
||||
"element_source": element_source,
|
||||
"element_target": element_target
|
||||
})
|
||||
return self.fail_response(
|
||||
"Source and target elements are required for drag_drop"
|
||||
)
|
||||
return await self._execute_browser_action(
|
||||
"drag_drop",
|
||||
{
|
||||
"element_source": element_source,
|
||||
"element_target": element_target,
|
||||
},
|
||||
)
|
||||
# Utility actions
|
||||
elif action == "wait":
|
||||
seconds_to_wait = seconds if seconds is not None else 3
|
||||
return await self._execute_browser_action("wait", {"seconds": seconds_to_wait})
|
||||
return await self._execute_browser_action(
|
||||
"wait", {"seconds": seconds_to_wait}
|
||||
)
|
||||
else:
|
||||
return self.fail_response(f"Unknown action: {action}")
|
||||
except Exception as e:
|
||||
@@ -365,7 +428,7 @@ class SandboxBrowserTool(SandboxToolsBase):
|
||||
screenshot = state.get("screenshot_base64")
|
||||
# Build the state info with all required fields
|
||||
state_info = {
|
||||
"url": state.get("url",""),
|
||||
"url": state.get("url", ""),
|
||||
"title": state.get("title", ""),
|
||||
"tabs": [tab.model_dump() for tab in state.get("tabs", [])],
|
||||
"pixels_above": getattr(state, "pixels_above", 0),
|
||||
|
||||
@@ -96,14 +96,13 @@ temperature = 0.0 # Controls randomness for vision mode
|
||||
#network_enabled = true
|
||||
|
||||
# Daytona configuration
|
||||
#[daytona]
|
||||
#daytona_api_key = ""
|
||||
[daytona]
|
||||
daytona_api_key = ""
|
||||
#daytona_server_url = "https://app.daytona.io/api"
|
||||
#daytona_target = "us"
|
||||
#sandbox_image_name = "kortix/suna:0.1.3"
|
||||
#sandbox_entrypoint =
|
||||
#sandbox_id =
|
||||
#VNC_password =
|
||||
#daytona_target = "us" #Daytona is currently available in the following regions:United States (us)、Europe (eu)
|
||||
#sandbox_image_name = "whitezxj/sandbox:0.1.0" #If you don't use this default image,sandbox tools may be useless
|
||||
#sandbox_entrypoint = "/usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf" #If you change this entrypoint,server in sandbox may be useless
|
||||
#VNC_password = #The password you set to log in sandbox by VNC,it will be 123456 if you don't set
|
||||
|
||||
# MCP (Model Context Protocol) configuration
|
||||
[mcp]
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
from daytona import Daytona, DaytonaConfig,CreateSandboxFromImageParams,Resources, Image
|
||||
from daytona import (
|
||||
CreateSandboxFromImageParams,
|
||||
Daytona,
|
||||
DaytonaConfig,
|
||||
Image,
|
||||
Resources,
|
||||
)
|
||||
|
||||
# Using environment variables (DAYTONA_API_KEY, DAYTONA_API_URL, DAYTONA_TARGET)
|
||||
# daytona = Daytona()
|
||||
# Using explicit configuration
|
||||
config = DaytonaConfig(
|
||||
api_key="dtn_bedf8ed9953f0b5c410c042090e1002a56ba8129b573c92f5607aef04b08c82a",
|
||||
api_url="https://app.daytona.io/api",
|
||||
target="us"
|
||||
)
|
||||
config = DaytonaConfig(api_key="", api_url="https://app.daytona.io/api", target="us")
|
||||
daytona = Daytona(config)
|
||||
params = CreateSandboxFromImageParams(
|
||||
image="kortix/suna:0.1.3",
|
||||
image="",
|
||||
# image=Image.debian_slim("3.12"),
|
||||
public=True,
|
||||
labels=None,
|
||||
env_vars={
|
||||
"CHROME_PERSISTENT_SESSION": "true",
|
||||
"RESOLUTION": "1024x768x24",
|
||||
"RESOLUTION_WIDTH": "1024",
|
||||
"RESOLUTION_HEIGHT": "768",
|
||||
"VNC_PASSWORD": "123456",
|
||||
"ANONYMIZED_TELEMETRY": "false",
|
||||
"CHROME_PATH": "",
|
||||
"CHROME_USER_DATA": "",
|
||||
"CHROME_DEBUGGING_PORT": "9222",
|
||||
"CHROME_DEBUGGING_HOST": "localhost",
|
||||
"CHROME_CDP": ""
|
||||
},
|
||||
"CHROME_PERSISTENT_SESSION": "true",
|
||||
"RESOLUTION": "1024x768x24",
|
||||
"RESOLUTION_WIDTH": "1024",
|
||||
"RESOLUTION_HEIGHT": "768",
|
||||
"VNC_PASSWORD": "123456",
|
||||
"ANONYMIZED_TELEMETRY": "false",
|
||||
"CHROME_PATH": "",
|
||||
"CHROME_USER_DATA": "",
|
||||
"CHROME_DEBUGGING_PORT": "9222",
|
||||
"CHROME_DEBUGGING_HOST": "localhost",
|
||||
"CHROME_CDP": "",
|
||||
},
|
||||
resources=Resources(
|
||||
cpu=1,
|
||||
memory=1,
|
||||
disk=1,
|
||||
),
|
||||
cpu=1,
|
||||
memory=1,
|
||||
disk=1,
|
||||
),
|
||||
auto_stop_interval=15,
|
||||
auto_archive_interval=24 * 60,
|
||||
)
|
||||
# Create the sandbox
|
||||
)
|
||||
# Create the sandbox
|
||||
sandbox = daytona.create(params)
|
||||
print(f"Sandbox created with ID: {sandbox.id}")
|
||||
|
||||
@@ -58,7 +61,7 @@ print(f"Sandbox created with ID: {sandbox.id}")
|
||||
|
||||
# Define the configuration
|
||||
|
||||
# config = DaytonaConfig(api_key="dtn_bedf8ed9953f0b5c410c042090e1002a56ba8129b573c92f5607aef04b08c82a")
|
||||
# config = DaytonaConfig(api_key="")
|
||||
|
||||
# # Initialize the Daytona client
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
from app.tool.sb_browser_tool import SandboxBrowserTool
|
||||
from app.daytona.sandbox import create_sandbox,start_supervisord_session
|
||||
import asyncio
|
||||
from app.utils.logger import logger
|
||||
from daytona import DaytonaConfig, Daytona
|
||||
import json
|
||||
|
||||
from daytona import Daytona, DaytonaConfig
|
||||
|
||||
from app.daytona.sandbox import create_sandbox, start_supervisord_session
|
||||
from app.tool.sb_browser_tool import SandboxBrowserTool
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
async def main():
|
||||
# 创建沙箱和工具
|
||||
sandbox = create_sandbox(password="123456")
|
||||
# config = DaytonaConfig(
|
||||
# api_key="dtn_bedf8ed9953f0b5c410c042090e1002a56ba8129b573c92f5607aef04b08c82a",
|
||||
# api_key="",
|
||||
# api_url="https://app.daytona.io/api",
|
||||
# target="us"
|
||||
# )
|
||||
@@ -42,7 +46,9 @@ async def main():
|
||||
tool = SandboxBrowserTool(sandbox)
|
||||
|
||||
# # 执行截图操作
|
||||
result,tooresult = await tool.execute(action="navigate_to",url="https://www.github.com")
|
||||
result, tooresult = await tool.execute(
|
||||
action="navigate_to", url="https://www.github.com"
|
||||
)
|
||||
print(result)
|
||||
|
||||
# endpoint = "navigate_to"
|
||||
@@ -58,11 +64,23 @@ async def main():
|
||||
# response = sandbox.process.exec(curl_cmd, timeout=30)
|
||||
# print(f"Response: {response}")
|
||||
|
||||
|
||||
# response = sandbox.process.exec("ls -la")
|
||||
# print(response.result)
|
||||
# 清理资源(可选)
|
||||
# await computer_tool.cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main()) # 运行异步主函数
|
||||
# await computer_tool.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main()) # 运行异步主函数
|
||||
# await computer_tool.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main()) # 运行异步主函数
|
||||
# await computer_tool.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main()) # 运行异步主函数
|
||||
|
||||
Reference in New Issue
Block a user