mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Add core config to disable browser environment (#9570)
This commit is contained in:
parent
b98615bc1c
commit
a6301075ec
@ -49,6 +49,9 @@
|
|||||||
# Maximum file size for uploads, in megabytes
|
# Maximum file size for uploads, in megabytes
|
||||||
#file_uploads_max_file_size_mb = 0
|
#file_uploads_max_file_size_mb = 0
|
||||||
|
|
||||||
|
# Enable the browser environment
|
||||||
|
#enable_browser = true
|
||||||
|
|
||||||
# Maximum budget per task, 0.0 means no limit
|
# Maximum budget per task, 0.0 means no limit
|
||||||
#max_budget_per_task = 0.0
|
#max_budget_per_task = 0.0
|
||||||
|
|
||||||
@ -226,6 +229,7 @@ model = "gpt-4o"
|
|||||||
[agent]
|
[agent]
|
||||||
|
|
||||||
# Whether the browsing tool is enabled
|
# Whether the browsing tool is enabled
|
||||||
|
# Note: when this is set to true, enable_browser in the core config must also be true
|
||||||
enable_browsing = true
|
enable_browsing = true
|
||||||
|
|
||||||
# Whether the LLM draft editor is enabled
|
# Whether the LLM draft editor is enabled
|
||||||
|
|||||||
@ -34,6 +34,7 @@ class OpenHandsConfig(BaseModel):
|
|||||||
file_store_path: Path to the file store.
|
file_store_path: Path to the file store.
|
||||||
file_store_web_hook_url: Optional url for file store web hook
|
file_store_web_hook_url: Optional url for file store web hook
|
||||||
file_store_web_hook_headers: Optional headers for file_store web hook
|
file_store_web_hook_headers: Optional headers for file_store web hook
|
||||||
|
enable_browser: Whether to enable the browser environment
|
||||||
save_trajectory_path: Either a folder path to store trajectories with auto-generated filenames, or a designated trajectory file path.
|
save_trajectory_path: Either a folder path to store trajectories with auto-generated filenames, or a designated trajectory file path.
|
||||||
save_screenshots_in_trajectory: Whether to save screenshots in trajectory (in encoded image format).
|
save_screenshots_in_trajectory: Whether to save screenshots in trajectory (in encoded image format).
|
||||||
replay_trajectory_path: Path to load trajectory and replay. If provided, trajectory would be replayed first before user's instruction.
|
replay_trajectory_path: Path to load trajectory and replay. If provided, trajectory would be replayed first before user's instruction.
|
||||||
@ -68,6 +69,7 @@ class OpenHandsConfig(BaseModel):
|
|||||||
file_store_path: str = Field(default='~/.openhands')
|
file_store_path: str = Field(default='~/.openhands')
|
||||||
file_store_web_hook_url: str | None = Field(default=None)
|
file_store_web_hook_url: str | None = Field(default=None)
|
||||||
file_store_web_hook_headers: dict | None = Field(default=None)
|
file_store_web_hook_headers: dict | None = Field(default=None)
|
||||||
|
enable_browser: bool = Field(default=True)
|
||||||
save_trajectory_path: str | None = Field(default=None)
|
save_trajectory_path: str | None = Field(default=None)
|
||||||
save_screenshots_in_trajectory: bool = Field(default=False)
|
save_screenshots_in_trajectory: bool = Field(default=False)
|
||||||
replay_trajectory_path: str | None = Field(default=None)
|
replay_trajectory_path: str | None = Field(default=None)
|
||||||
|
|||||||
@ -172,6 +172,7 @@ class ActionExecutor:
|
|||||||
work_dir: str,
|
work_dir: str,
|
||||||
username: str,
|
username: str,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
|
enable_browser: bool,
|
||||||
browsergym_eval_env: str | None,
|
browsergym_eval_env: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.plugins_to_load = plugins_to_load
|
self.plugins_to_load = plugins_to_load
|
||||||
@ -188,10 +189,16 @@ class ActionExecutor:
|
|||||||
self.lock = asyncio.Lock()
|
self.lock = asyncio.Lock()
|
||||||
self.plugins: dict[str, Plugin] = {}
|
self.plugins: dict[str, Plugin] = {}
|
||||||
self.file_editor = OHEditor(workspace_root=self._initial_cwd)
|
self.file_editor = OHEditor(workspace_root=self._initial_cwd)
|
||||||
|
self.enable_browser = enable_browser
|
||||||
self.browser: BrowserEnv | None = None
|
self.browser: BrowserEnv | None = None
|
||||||
self.browser_init_task: asyncio.Task | None = None
|
self.browser_init_task: asyncio.Task | None = None
|
||||||
self.browsergym_eval_env = browsergym_eval_env
|
self.browsergym_eval_env = browsergym_eval_env
|
||||||
|
|
||||||
|
if (not self.enable_browser) and self.browsergym_eval_env:
|
||||||
|
raise BrowserUnavailableException(
|
||||||
|
'Browser environment is not enabled in config, but browsergym_eval_env is set'
|
||||||
|
)
|
||||||
|
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.last_execution_time = self.start_time
|
self.last_execution_time = self.start_time
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
@ -219,6 +226,10 @@ class ActionExecutor:
|
|||||||
|
|
||||||
async def _init_browser_async(self):
|
async def _init_browser_async(self):
|
||||||
"""Initialize the browser asynchronously."""
|
"""Initialize the browser asynchronously."""
|
||||||
|
if not self.enable_browser:
|
||||||
|
logger.info('Browser environment is not enabled in config')
|
||||||
|
return
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
logger.warning('Browser environment not supported on windows')
|
logger.warning('Browser environment not supported on windows')
|
||||||
return
|
return
|
||||||
@ -596,7 +607,7 @@ class ActionExecutor:
|
|||||||
async def browse(self, action: BrowseURLAction) -> Observation:
|
async def browse(self, action: BrowseURLAction) -> Observation:
|
||||||
if self.browser is None:
|
if self.browser is None:
|
||||||
return ErrorObservation(
|
return ErrorObservation(
|
||||||
'Browser functionality is not supported on Windows.'
|
'Browser functionality is not supported or disabled.'
|
||||||
)
|
)
|
||||||
await self._ensure_browser_ready()
|
await self._ensure_browser_ready()
|
||||||
return await browse(action, self.browser, self.initial_cwd)
|
return await browse(action, self.browser, self.initial_cwd)
|
||||||
@ -604,7 +615,7 @@ class ActionExecutor:
|
|||||||
async def browse_interactive(self, action: BrowseInteractiveAction) -> Observation:
|
async def browse_interactive(self, action: BrowseInteractiveAction) -> Observation:
|
||||||
if self.browser is None:
|
if self.browser is None:
|
||||||
return ErrorObservation(
|
return ErrorObservation(
|
||||||
'Browser functionality is not supported on Windows.'
|
'Browser functionality is not supported or disabled.'
|
||||||
)
|
)
|
||||||
await self._ensure_browser_ready()
|
await self._ensure_browser_ready()
|
||||||
browser_observation = await browse(action, self.browser, self.initial_cwd)
|
browser_observation = await browse(action, self.browser, self.initial_cwd)
|
||||||
@ -666,6 +677,12 @@ if __name__ == '__main__':
|
|||||||
'--username', type=str, help='User to run as', default='openhands'
|
'--username', type=str, help='User to run as', default='openhands'
|
||||||
)
|
)
|
||||||
parser.add_argument('--user-id', type=int, help='User ID to run as', default=1000)
|
parser.add_argument('--user-id', type=int, help='User ID to run as', default=1000)
|
||||||
|
parser.add_argument(
|
||||||
|
'--enable-browser',
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=True,
|
||||||
|
help='Enable the browser environment',
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--browsergym-eval-env',
|
'--browsergym-eval-env',
|
||||||
type=str,
|
type=str,
|
||||||
@ -703,6 +720,7 @@ if __name__ == '__main__':
|
|||||||
work_dir=args.working_dir,
|
work_dir=args.working_dir,
|
||||||
username=args.username,
|
username=args.username,
|
||||||
user_id=args.user_id,
|
user_id=args.user_id,
|
||||||
|
enable_browser=args.enable_browser,
|
||||||
browsergym_eval_env=args.browsergym_eval_env,
|
browsergym_eval_env=args.browsergym_eval_env,
|
||||||
)
|
)
|
||||||
await client.ainit()
|
await client.ainit()
|
||||||
|
|||||||
@ -360,6 +360,8 @@ class DockerRuntime(ActionExecutionClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
command = self.get_action_execution_server_startup_command()
|
command = self.get_action_execution_server_startup_command()
|
||||||
|
self.log('info', f'Starting server with command: {command}')
|
||||||
|
|
||||||
if self.config.sandbox.enable_gpu:
|
if self.config.sandbox.enable_gpu:
|
||||||
gpu_ids = self.config.sandbox.cuda_visible_devices
|
gpu_ids = self.config.sandbox.cuda_visible_devices
|
||||||
if gpu_ids is None:
|
if gpu_ids is None:
|
||||||
|
|||||||
@ -73,7 +73,7 @@ def get_user_info() -> tuple[int, str | None]:
|
|||||||
return os.getuid(), username
|
return os.getuid(), username
|
||||||
|
|
||||||
|
|
||||||
def check_dependencies(code_repo_path: str, env_root_path: str) -> None:
|
def check_dependencies(code_repo_path: str, check_browser: bool) -> None:
|
||||||
ERROR_MESSAGE = 'Please follow the instructions in https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md to install OpenHands.'
|
ERROR_MESSAGE = 'Please follow the instructions in https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md to install OpenHands.'
|
||||||
if not os.path.exists(code_repo_path):
|
if not os.path.exists(code_repo_path):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -91,7 +91,6 @@ def check_dependencies(code_repo_path: str, env_root_path: str) -> None:
|
|||||||
raise ValueError('Jupyter is not properly installed. ' + ERROR_MESSAGE)
|
raise ValueError('Jupyter is not properly installed. ' + ERROR_MESSAGE)
|
||||||
|
|
||||||
# Check libtmux is installed (skip on Windows)
|
# Check libtmux is installed (skip on Windows)
|
||||||
|
|
||||||
if sys.platform != 'win32':
|
if sys.platform != 'win32':
|
||||||
logger.debug('Checking dependencies: libtmux')
|
logger.debug('Checking dependencies: libtmux')
|
||||||
import libtmux
|
import libtmux
|
||||||
@ -108,15 +107,12 @@ def check_dependencies(code_repo_path: str, env_root_path: str) -> None:
|
|||||||
if 'test' not in pane_output:
|
if 'test' not in pane_output:
|
||||||
raise ValueError('libtmux is not properly installed. ' + ERROR_MESSAGE)
|
raise ValueError('libtmux is not properly installed. ' + ERROR_MESSAGE)
|
||||||
|
|
||||||
# Skip browser environment check on Windows
|
if check_browser:
|
||||||
if sys.platform != 'win32':
|
|
||||||
logger.debug('Checking dependencies: browser')
|
logger.debug('Checking dependencies: browser')
|
||||||
from openhands.runtime.browser.browser_env import BrowserEnv
|
from openhands.runtime.browser.browser_env import BrowserEnv
|
||||||
|
|
||||||
browser = BrowserEnv()
|
browser = BrowserEnv()
|
||||||
browser.close()
|
browser.close()
|
||||||
else:
|
|
||||||
logger.warning('Running on Windows - browser environment check skipped.')
|
|
||||||
|
|
||||||
|
|
||||||
class LocalRuntime(ActionExecutionClient):
|
class LocalRuntime(ActionExecutionClient):
|
||||||
@ -289,7 +285,7 @@ class LocalRuntime(ActionExecutionClient):
|
|||||||
override_username=self._username,
|
override_username=self._username,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.log('debug', f'Starting server with command: {cmd}')
|
self.log('info', f'Starting server with command: {cmd}')
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
# Get the code repo path
|
# Get the code repo path
|
||||||
code_repo_path = os.path.dirname(os.path.dirname(openhands.__file__))
|
code_repo_path = os.path.dirname(os.path.dirname(openhands.__file__))
|
||||||
@ -303,7 +299,6 @@ class LocalRuntime(ActionExecutionClient):
|
|||||||
# Derive environment paths using sys.executable
|
# Derive environment paths using sys.executable
|
||||||
interpreter_path = sys.executable
|
interpreter_path = sys.executable
|
||||||
python_bin_path = os.path.dirname(interpreter_path)
|
python_bin_path = os.path.dirname(interpreter_path)
|
||||||
env_root_path = os.path.dirname(python_bin_path)
|
|
||||||
|
|
||||||
# Prepend the interpreter's bin directory to PATH for subprocesses
|
# Prepend the interpreter's bin directory to PATH for subprocesses
|
||||||
env['PATH'] = f'{python_bin_path}{os.pathsep}{env.get("PATH", "")}'
|
env['PATH'] = f'{python_bin_path}{os.pathsep}{env.get("PATH", "")}'
|
||||||
@ -311,7 +306,8 @@ class LocalRuntime(ActionExecutionClient):
|
|||||||
|
|
||||||
# Check dependencies using the derived env_root_path if not skipped
|
# Check dependencies using the derived env_root_path if not skipped
|
||||||
if os.getenv('SKIP_DEPENDENCY_CHECK', '') != '1':
|
if os.getenv('SKIP_DEPENDENCY_CHECK', '') != '1':
|
||||||
check_dependencies(code_repo_path, env_root_path)
|
check_browser = self.config.enable_browser and sys.platform != 'win32'
|
||||||
|
check_dependencies(code_repo_path, check_browser)
|
||||||
|
|
||||||
self.server_process = subprocess.Popen( # noqa: S603
|
self.server_process = subprocess.Popen( # noqa: S603
|
||||||
cmd,
|
cmd,
|
||||||
|
|||||||
@ -60,4 +60,7 @@ def get_action_execution_server_startup_command(
|
|||||||
*browsergym_args,
|
*browsergym_args,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if not app_config.enable_browser:
|
||||||
|
base_cmd.append('--no-enable-browser')
|
||||||
|
|
||||||
return base_cmd
|
return base_cmd
|
||||||
|
|||||||
@ -212,6 +212,7 @@ def _load_runtime(
|
|||||||
runtime_startup_env_vars: dict[str, str] | None = None,
|
runtime_startup_env_vars: dict[str, str] | None = None,
|
||||||
docker_runtime_kwargs: dict[str, str] | None = None,
|
docker_runtime_kwargs: dict[str, str] | None = None,
|
||||||
override_mcp_config: MCPConfig | None = None,
|
override_mcp_config: MCPConfig | None = None,
|
||||||
|
enable_browser: bool = True,
|
||||||
) -> tuple[Runtime, OpenHandsConfig]:
|
) -> tuple[Runtime, OpenHandsConfig]:
|
||||||
sid = 'rt_' + str(random.randint(100000, 999999))
|
sid = 'rt_' + str(random.randint(100000, 999999))
|
||||||
|
|
||||||
@ -221,6 +222,7 @@ def _load_runtime(
|
|||||||
|
|
||||||
config = load_openhands_config()
|
config = load_openhands_config()
|
||||||
config.run_as_openhands = run_as_openhands
|
config.run_as_openhands = run_as_openhands
|
||||||
|
config.enable_browser = enable_browser
|
||||||
config.sandbox.force_rebuild_runtime = force_rebuild_runtime
|
config.sandbox.force_rebuild_runtime = force_rebuild_runtime
|
||||||
config.sandbox.keep_runtime_alive = False
|
config.sandbox.keep_runtime_alive = False
|
||||||
config.sandbox.docker_runtime_kwargs = docker_runtime_kwargs
|
config.sandbox.docker_runtime_kwargs = docker_runtime_kwargs
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from openhands.events.action import (
|
|||||||
from openhands.events.observation import (
|
from openhands.events.observation import (
|
||||||
BrowserOutputObservation,
|
BrowserOutputObservation,
|
||||||
CmdOutputObservation,
|
CmdOutputObservation,
|
||||||
|
ErrorObservation,
|
||||||
FileDownloadObservation,
|
FileDownloadObservation,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -122,6 +123,26 @@ def find_element_by_tag_and_attributes(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def test_browser_disabled(temp_dir, runtime_cls, run_as_openhands):
|
||||||
|
runtime, _ = _load_runtime(
|
||||||
|
temp_dir, runtime_cls, run_as_openhands, enable_browser=False
|
||||||
|
)
|
||||||
|
|
||||||
|
action_cmd = CmdRunAction(command='python3 -m http.server 8000 > server.log 2>&1 &')
|
||||||
|
logger.info(action_cmd, extra={'msg_type': 'ACTION'})
|
||||||
|
obs = runtime.run_action(action_cmd)
|
||||||
|
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||||
|
|
||||||
|
action_browse = BrowseURLAction(url='http://localhost:8000', return_axtree=False)
|
||||||
|
logger.info(action_browse, extra={'msg_type': 'ACTION'})
|
||||||
|
obs = runtime.run_action(action_browse)
|
||||||
|
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||||
|
assert isinstance(obs, ErrorObservation)
|
||||||
|
assert 'Browser functionality is not supported or disabled' in obs.content
|
||||||
|
|
||||||
|
_close_test_runtime(runtime)
|
||||||
|
|
||||||
|
|
||||||
def test_simple_browse(temp_dir, runtime_cls, run_as_openhands):
|
def test_simple_browse(temp_dir, runtime_cls, run_as_openhands):
|
||||||
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
|
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user