diff --git a/README.md b/README.md index 7971a08f..ea9b1dae 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ You'll need the following components: - Redis database for caching and session management - Daytona sandbox for secure agent execution - Python 3.11 for the API backend -- API keys for LLM providers (Anthropic) +- API keys for LLM providers (Anthropic, OpenRouter) - Tavily API key for enhanced search capabilities - Firecrawl API key for web scraping capabilities @@ -99,23 +99,16 @@ You'll need the following components: - Save your project's API URL, anon key, and service role key for later use - Install the [Supabase CLI](https://supabase.com/docs/guides/cli/getting-started) -2. **Redis**: Set up a Redis instance using one of these options: - - [Upstash Redis](https://upstash.com/) (recommended for cloud deployments) - - Local installation: - - [Mac](https://formulae.brew.sh/formula/redis): `brew install redis` - - [Linux](https://redis.io/docs/getting-started/installation/install-redis-on-linux/): Follow distribution-specific instructions - - [Windows](https://redis.io/docs/getting-started/installation/install-redis-on-windows/): Use WSL2 or Docker - - Docker Compose (included in our setup): - - If you're using our Docker Compose setup, Redis is included and configured automatically - - No additional installation is needed - - Save your Redis connection details for later use (not needed if using Docker Compose) +2. **Redis**: + - Go to the `/backend` folder + - Run `docker compose up redis` 3. **Daytona**: - Create an account on [Daytona](https://app.daytona.io/) - Generate an API key from your account settings - Go to [Images](https://app.daytona.io/dashboard/images) - Click "Add Image" - - Enter `adamcohenhillel/kortix-suna:0.0.20` as the image name + - Enter `kortix/suna:0.1` as the image name - Set `/usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf` as the Entrypoint 4. **LLM API Keys**: diff --git a/backend/sandbox/README.md b/backend/sandbox/README.md new file mode 100644 index 00000000..cafa4b7b --- /dev/null +++ b/backend/sandbox/README.md @@ -0,0 +1,32 @@ +# Agent 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 +- Web server for serving content (port 8080) -> loading html files from the /workspace directory +- Full file system access +- Full sudo access + +## Customizing the Sandbox + +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: + ``` + cd backend/sandbox/docker + docker-compose build + ``` +3. Test your changes locally using docker-compose + +## Using a Custom Image + +To use your custom sandbox image: + +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 diff --git a/backend/sandbox/api.py b/backend/sandbox/api.py index cd9edfe5..a0610f2b 100644 --- a/backend/sandbox/api.py +++ b/backend/sandbox/api.py @@ -7,7 +7,6 @@ from pydantic import BaseModel from utils.logger import logger from utils.auth_utils import get_optional_user_id -from sandbox.sandbox import get_or_start_sandbox from services.supabase import DBConnection from agent.api import get_or_create_project_sandbox @@ -97,14 +96,8 @@ async def get_sandbox_by_id_safely(client, sandbox_id: str): try: # Get the sandbox - sandbox, retrieved_sandbox_id, sandbox_pass = await get_or_create_project_sandbox(client, project_id) - - # Verify we got the right sandbox - if retrieved_sandbox_id != sandbox_id: - logger.warning(f"Retrieved sandbox ID {retrieved_sandbox_id} doesn't match requested ID {sandbox_id} for project {project_id}") - # Fall back to the direct method if IDs don't match (shouldn't happen but just in case) - sandbox = await get_or_start_sandbox(sandbox_id) - + sandbox = await get_or_create_project_sandbox(client, project_id) + return sandbox except Exception as e: logger.error(f"Error retrieving sandbox {sandbox_id}: {str(e)}") diff --git a/backend/sandbox/docker/browser_api.py b/backend/sandbox/docker/browser_api.py index 471fc6b0..d45d95f7 100644 --- a/backend/sandbox/docker/browser_api.py +++ b/backend/sandbox/docker/browser_api.py @@ -1,10 +1,11 @@ from fastapi import FastAPI, APIRouter, HTTPException, Body -from playwright.async_api import async_playwright, Browser, Page +from playwright.async_api import async_playwright, Browser, Page, ElementHandle from pydantic import BaseModel -from typing import Optional, List, Dict, Any +from typing import Optional, List, Dict, Any, Union import asyncio import json import logging +import re import base64 from dataclasses import dataclass, field from datetime import datetime @@ -356,13 +357,13 @@ class BrowserAutomation: self.current_page_index = 0 except Exception as page_error: print(f"Error finding existing page, creating new one. ( {page_error})") - page = await self.browser.new_page() + # page = await self.browser.new_page() print("New page created successfully") - self.pages.append(page) + # self.pages.append(page) self.current_page_index = 0 # Navigate to about:blank to ensure page is ready # await page.goto("google.com", timeout=30000) - print("Navigated to google.com") + # print("Navigated to google.com") print("Browser initialization completed successfully") except Exception as e: diff --git a/backend/sandbox/docker/docker-compose.yml b/backend/sandbox/docker/docker-compose.yml index 64298126..a8863c0b 100644 --- a/backend/sandbox/docker/docker-compose.yml +++ b/backend/sandbox/docker/docker-compose.yml @@ -6,7 +6,7 @@ services: dockerfile: ${DOCKERFILE:-Dockerfile} args: TARGETPLATFORM: ${TARGETPLATFORM:-linux/amd64} - image: adamcohenhillel/kortix-suna:0.0.20 + image: kortix/suna:0.1 ports: - "6080:6080" # noVNC web interface - "5901:5901" # VNC port diff --git a/backend/sandbox/docker/requirements.txt b/backend/sandbox/docker/requirements.txt index fae0febe..a9a8abca 100644 --- a/backend/sandbox/docker/requirements.txt +++ b/backend/sandbox/docker/requirements.txt @@ -1,6 +1,5 @@ -fastapi==0.115.12 -uvicorn==0.34.0 -pyautogui==0.9.54 -pillow==10.2.0 -pydantic==2.6.1 -pytesseract==0.3.13 \ No newline at end of file +fastapi +uvicorn +pillow +pydantic +pytesseract diff --git a/backend/sandbox/sandbox.py b/backend/sandbox/sandbox.py index 1886c747..d63252e8 100644 --- a/backend/sandbox/sandbox.py +++ b/backend/sandbox/sandbox.py @@ -97,7 +97,7 @@ def create_sandbox(password: str, project_id: str = None): labels = {'id': project_id} params = CreateSandboxParams( - image="adamcohenhillel/kortix-suna:0.0.20", + image="kortix/suna:0.1", public=True, labels=labels, env_vars={ @@ -169,7 +169,7 @@ class SandboxToolsBase(Tool): self._sandbox_pass = sandbox_info.get('pass') # Get or start the sandbox - self._sandbox = await get_or_start_sandbox(self._sandbox_id) + # self._sandbox = await get_or_start_sandbox(self._sandbox_id) # # Log URLs if not already printed # if not SandboxToolsBase._urls_printed: