diff --git a/app/agent/sandbox_agent.py b/app/agent/sandbox_agent.py index ba1cb33..6eccb3c 100644 --- a/app/agent/sandbox_agent.py +++ b/app/agent/sandbox_agent.py @@ -17,7 +17,8 @@ from app.tool.python_execute import PythonExecute from app.tool.str_replace_editor import StrReplaceEditor from app.tool.sb_browser_tool import SandboxBrowserTool -from app.daytona.sandbox import create_sandbox +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.""" @@ -53,6 +54,7 @@ class SandboxManus(ToolCallAgent): default_factory=dict ) # server_id -> url/command _initialized: bool = False + sandbox_link: Optional[dict[str, dict[str, str]]] = Field(default_factory=dict) @model_validator(mode="after") def initialize_helper(self) -> "SandboxManus": @@ -65,16 +67,67 @@ class SandboxManus(ToolCallAgent): """Factory method to create and properly initialize a Manus instance.""" instance = cls(**kwargs) await instance.initialize_mcp_servers() - instance.initialize_sandbox_tools() + await instance.initialize_sandbox_tools() instance._initialized = True return instance - def initialize_sandbox_tools(self,password="123456") -> None: - sandbox = create_sandbox(password=password) - print(f"VNC link: {sandbox.get_preview_link(6080)}") - computer_tool = SandboxBrowserTool.create_with_sandbox(sandbox) - sandbox_tools=[computer_tool] - self.available_tools.add_tools(*sandbox_tools) + async def initialize_sandbox_tools(self, sandbox_id: str = config.daytona.sandbox_id, 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}") + 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}") + computer_tool = SandboxBrowserTool.create_with_sandbox(sandbox) + self.available_tools.add_tools(computer_tool) + + except Exception as e: + logger.error(f"Error initializing sandbox tools: {e}") + raise async def initialize_mcp_servers(self) -> None: """Initialize connections to configured MCP servers.""" diff --git a/app/config.py b/app/config.py index fd2c8fd..3a360be 100644 --- a/app/config.py +++ b/app/config.py @@ -111,6 +111,8 @@ class DaytonaSettings(BaseModel): 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") class MCPServerConfig(BaseModel): """Configuration for a single MCP server""" diff --git a/config/config.example-daytona.toml b/config/config.example-daytona.toml new file mode 100644 index 0000000..de065eb --- /dev/null +++ b/config/config.example-daytona.toml @@ -0,0 +1,115 @@ +# Global LLM configuration +[llm] +model = "claude-3-7-sonnet-20250219" # The LLM model to use +base_url = "https://api.anthropic.com/v1/" # API endpoint URL +api_key = "YOUR_API_KEY" # Your API key +max_tokens = 8192 # Maximum number of tokens in the response +temperature = 0.0 # Controls randomness + +# [llm] # Amazon Bedrock +# api_type = "aws" # Required +# model = "us.anthropic.claude-3-7-sonnet-20250219-v1:0" # Bedrock supported modelID +# base_url = "bedrock-runtime.us-west-2.amazonaws.com" # Not used now +# max_tokens = 8192 +# temperature = 1.0 +# api_key = "bear" # Required but not used for Bedrock + +# [llm] #AZURE OPENAI: +# api_type= 'azure' +# model = "YOUR_MODEL_NAME" #"gpt-4o-mini" +# base_url = "{YOUR_AZURE_ENDPOINT.rstrip('/')}/openai/deployments/{AZURE_DEPLOYMENT_ID}" +# api_key = "AZURE API KEY" +# max_tokens = 8096 +# temperature = 0.0 +# api_version="AZURE API VERSION" #"2024-08-01-preview" + +# [llm] #OLLAMA: +# api_type = 'ollama' +# model = "llama3.2" +# base_url = "http://localhost:11434/v1" +# api_key = "ollama" +# max_tokens = 4096 +# temperature = 0.0 + +# Optional configuration for specific LLM models +[llm.vision] +model = "claude-3-7-sonnet-20250219" # The vision model to use +base_url = "https://api.anthropic.com/v1/" # API endpoint URL for vision model +api_key = "YOUR_API_KEY" # Your API key for vision model +max_tokens = 8192 # Maximum number of tokens in the response +temperature = 0.0 # Controls randomness for vision model + +# [llm.vision] #OLLAMA VISION: +# api_type = 'ollama' +# model = "llama3.2-vision" +# base_url = "http://localhost:11434/v1" +# api_key = "ollama" +# max_tokens = 4096 +# temperature = 0.0 + +# Optional configuration for specific browser configuration +# [browser] +# Whether to run browser in headless mode (default: false) +#headless = false +# Disable browser security features (default: true) +#disable_security = true +# Extra arguments to pass to the browser +#extra_chromium_args = [] +# Path to a Chrome instance to use to connect to your normal browser +# e.g. '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' +#chrome_instance_path = "" +# Connect to a browser instance via WebSocket +#wss_url = "" +# Connect to a browser instance via CDP +#cdp_url = "" + +# Optional configuration, Proxy settings for the browser +# [browser.proxy] +# server = "http://proxy-server:port" +# username = "proxy-username" +# password = "proxy-password" + +# Optional configuration, Search settings. +# [search] +# Search engine for agent to use. Default is "Google", can be set to "Baidu" or "DuckDuckGo" or "Bing". +#engine = "Google" +# Fallback engine order. Default is ["DuckDuckGo", "Baidu", "Bing"] - will try in this order after primary engine fails. +#fallback_engines = ["DuckDuckGo", "Baidu", "Bing"] +# Seconds to wait before retrying all engines again when they all fail due to rate limits. Default is 60. +#retry_delay = 60 +# Maximum number of times to retry all engines when all fail. Default is 3. +#max_retries = 3 +# Language code for search results. Options: "en" (English), "zh" (Chinese), etc. +#lang = "en" +# Country code for search results. Options: "us" (United States), "cn" (China), etc. +#country = "us" + + +## Sandbox configuration +#[sandbox] +#use_sandbox = false +#image = "python:3.12-slim" +#work_dir = "/workspace" +#memory_limit = "1g" # 512m +#cpu_limit = 2.0 +#timeout = 300 +#network_enabled = true + +# Daytona configuration +#[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 = + +# MCP (Model Context Protocol) configuration +[mcp] +server_reference = "app.mcp.server" # default server module reference + +# Optional Runflow configuration +# Your can add additional agents into run-flow workflow to solve different-type tasks. +[runflow] +use_data_analysis_agent = false # The Data Analysi Agent to solve various data analysis tasks diff --git a/config/config.example.toml b/config/config.example.toml index 0b9e130..7693ee8 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -95,14 +95,6 @@ temperature = 0.0 # Controls randomness for vision mode #timeout = 300 #network_enabled = true -# Daytona configuration -[daytona] -daytona_api_key = "" -daytona_server_url = "https://app.daytona.io/api" -daytona_target = "us" -#sandbox_image_name = -#sandbox_entrypoint = - # MCP (Model Context Protocol) configuration [mcp] server_reference = "app.mcp.server" # default server module reference