Promote beta core package quickstart

This commit is contained in:
Gregor Žunič
2026-06-08 12:59:51 -07:00
parent 636470a1aa
commit 7a888eb9a4
9 changed files with 730 additions and 642 deletions
@@ -1,12 +1,12 @@
# Rust Agent Integration Feature/Proof Ledger
This branch keeps the Python `Agent` unchanged unless callers explicitly import
`from browser_use.rust import Agent`.
`from browser_use.beta import Agent`.
## Current Features
1. Rust SDK server execution path
- `browser_use.rust.Agent` now runs normal tasks through the terminal SDK server
- `browser_use.beta.Agent` now runs normal tasks through the terminal SDK server
(`browser-use-terminal sdk-server --transport stdio`) using the normalized
`agent.run_task` request/response protocol.
- Follow-up tasks reuse the returned SDK `agent_id`, `session_id`, and `browser_id`
@@ -64,7 +64,7 @@ This branch keeps the Python `Agent` unchanged unless callers explicitly import
- Terminal Rust runs only advertise sub-agent tools when the run has a
configured child-agent runner, and SDK JSON-RPC runs now attach the same
runtime-backed child runner as CLI runs. This enables terminal's advanced
sub-agent system in `browser_use.rust.Agent` evals without exposing
sub-agent system in `browser_use.beta.Agent` evals without exposing
spawn tools on unsupported run surfaces.
- Eval GitHub runners now refresh Convex progress during long `run_agent`
and `evaluate` stages, so slow Rust or Agent SDK judge calls stay visible
@@ -176,7 +176,7 @@ This branch keeps the Python `Agent` unchanged unless callers explicitly import
traces against that combined stream, so sub-agent model calls contribute to
run token/cost totals without letting child `session.done` events override
the parent final answer.
- `browser_use.rust.Agent` no longer treats `_run_process` monkeypatches as an
- `browser_use.beta.Agent` no longer treats `_run_process` monkeypatches as an
alternate runtime. Production `run()` and `follow_up()` now require the
Rust SDK server path, so the legacy terminal CLI adapter cannot silently
replace the new server protocol while the normal Python `browser_use.Agent`
@@ -193,7 +193,7 @@ This branch keeps the Python `Agent` unchanged unless callers explicitly import
telemetry payloads when the terminal server returns both live and projected
event streams.
- Browser-use no longer sends a Rust SDK `tool_allowlist` override. The
terminal SDK server now owns its tool registry for `browser_use.rust.Agent`
terminal SDK server now owns its tool registry for `browser_use.beta.Agent`
runs, so local search, web/search helpers, and v2 sub-agent controls are
available in evals when the Rust core registers them.
- Reconstructed browser history ignores internal browser-control endpoint
@@ -244,21 +244,21 @@ This branch keeps the Python `Agent` unchanged unless callers explicitly import
- terminal `cargo test -p browser-use-cli sdk_json_rpc_agent_run_task_executes_fake_backend_with_normalized_history -- --nocapture`
- terminal `cargo test -p browser-use-llm stream_ -- --nocapture`
- terminal `cargo test -p browser-use-cli sdk_provider_run_config_maps_browser_use_options_to_rust_core -- --nocapture`
- browser-use `uv run python -m py_compile browser_use/rust/service.py`
- browser-use `uv run pytest tests/ci/test_rust_agent.py -k 'browser_script_lifecycle_outputs_as_result or initial_actions_pre_navigate_existing_cdp_session or run_hands_off_completed_initial_navigation_as_context' -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py -k 'printed_browser_script_page_info_as_state or browser_script_lifecycle_outputs_as_result or initial_actions_pre_navigate_existing_cdp_session or run_hands_off_completed_initial_navigation_as_context' -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_history_surfaces_running_browser_script_observe_instruction -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py -k 'initial_actions_pre_navigate_existing_cdp_session or run_executes_initial_actions_before_sdk or run_hands_off_completed_initial_navigation_as_context' -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py`
- browser-use `uv run pytest tests/ci/test_rust_agent.py -k 'sdk_client_reads_large_json_rpc_lines or sdk_and_reuses_session or translates_browser_use_args_to_terminal'`
- browser-use `uv run pytest tests/ci/test_rust_agent.py -k 'sdk_client_queues_agent_notifications_before_response or sdk_client_reads_large_json_rpc_lines or sdk_and_reuses_session or translates_browser_use_args_to_terminal'`
- browser-use `uv run pytest tests/ci/test_rust_agent.py -k 'rust_sdk_client_reads_large_json_rpc_lines or rust_sdk_client_queues_agent_notifications_before_response' -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_sdk_client_reads_large_json_rpc_lines tests/ci/test_rust_agent.py::test_rust_agent_run_leaves_initial_navigation_for_sdk_by_default tests/ci/test_rust_agent.py::test_rust_agent_initial_actions_can_pre_navigate_existing_cdp_session tests/ci/test_rust_agent.py::test_rust_agent_translates_browser_use_args_to_terminal -q`
- browser-use `PYTHONPATH=. uv run pytest tests/ci/test_rust_agent.py -q -k 'pre_navigates_cdp_session_before_sdk_by_default or initial_actions_can_pre_navigate_existing_cdp_session or direct_initial_navigation_defaults_on_for_cdp or direct_initial_navigation_can_be_disabled'`
- browser-use `uv run pytest -q tests/ci/test_rust_agent.py -k "pre_navigates_cdp_session_before_sdk_by_default or keeps_initial_navigation_when_direct_state_mismatches or initial_actions_can_pre_navigate_existing_cdp_session or direct_initial_navigation_defaults_on_for_cdp"`
- browser-use `uv run pytest -q tests/ci/test_rust_agent.py -k "terminal_token_count_usage or sums_token_count_last_usage_when_latest_total_underreports or terminal_usage_prices_token_count_events or terminal_usage_sums_token_count_cache_creation"`
- browser-use `uv run pytest -q tests/ci/test_rust_agent.py -k "terminal_nested_model_usage or token_count_does_not_shrink_model_usage_totals or terminal_usage_prices_token_count_events or terminal_usage_prices_anthropic_raw_cache_reads or terminal_usage_sums_token_count_cache_creation or priced_summary_sums_cache_read_tokens or mixed_events_do_not_shrink_totals or priced_usage_prefers_model_usage_over_token_count or sums_token_count_last_usage_when_latest_total_underreports"`
- browser-use `python -m py_compile browser_use/rust/service.py`
- browser-use `uv run python -m py_compile browser_use/beta/service.py`
- browser-use `uv run pytest tests/ci/test_beta_agent.py -k 'browser_script_lifecycle_outputs_as_result or initial_actions_pre_navigate_existing_cdp_session or run_hands_off_completed_initial_navigation_as_context' -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py -k 'printed_browser_script_page_info_as_state or browser_script_lifecycle_outputs_as_result or initial_actions_pre_navigate_existing_cdp_session or run_hands_off_completed_initial_navigation_as_context' -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_rust_history_surfaces_running_browser_script_observe_instruction -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py -k 'initial_actions_pre_navigate_existing_cdp_session or run_executes_initial_actions_before_sdk or run_hands_off_completed_initial_navigation_as_context' -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py`
- browser-use `uv run pytest tests/ci/test_beta_agent.py -k 'sdk_client_reads_large_json_rpc_lines or sdk_and_reuses_session or translates_browser_use_args_to_terminal'`
- browser-use `uv run pytest tests/ci/test_beta_agent.py -k 'sdk_client_queues_agent_notifications_before_response or sdk_client_reads_large_json_rpc_lines or sdk_and_reuses_session or translates_browser_use_args_to_terminal'`
- browser-use `uv run pytest tests/ci/test_beta_agent.py -k 'rust_sdk_client_reads_large_json_rpc_lines or rust_sdk_client_queues_agent_notifications_before_response' -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_rust_sdk_client_reads_large_json_rpc_lines tests/ci/test_beta_agent.py::test_beta_agent_run_leaves_initial_navigation_for_sdk_by_default tests/ci/test_beta_agent.py::test_beta_agent_initial_actions_can_pre_navigate_existing_cdp_session tests/ci/test_beta_agent.py::test_beta_agent_translates_browser_use_args_to_terminal -q`
- browser-use `PYTHONPATH=. uv run pytest tests/ci/test_beta_agent.py -q -k 'pre_navigates_cdp_session_before_sdk_by_default or initial_actions_can_pre_navigate_existing_cdp_session or direct_initial_navigation_defaults_on_for_cdp or direct_initial_navigation_can_be_disabled'`
- browser-use `uv run pytest -q tests/ci/test_beta_agent.py -k "pre_navigates_cdp_session_before_sdk_by_default or keeps_initial_navigation_when_direct_state_mismatches or initial_actions_can_pre_navigate_existing_cdp_session or direct_initial_navigation_defaults_on_for_cdp"`
- browser-use `uv run pytest -q tests/ci/test_beta_agent.py -k "terminal_token_count_usage or sums_token_count_last_usage_when_latest_total_underreports or terminal_usage_prices_token_count_events or terminal_usage_sums_token_count_cache_creation"`
- browser-use `uv run pytest -q tests/ci/test_beta_agent.py -k "terminal_nested_model_usage or token_count_does_not_shrink_model_usage_totals or terminal_usage_prices_token_count_events or terminal_usage_prices_anthropic_raw_cache_reads or terminal_usage_sums_token_count_cache_creation or priced_summary_sums_cache_read_tokens or mixed_events_do_not_shrink_totals or priced_usage_prefers_model_usage_over_token_count or sums_token_count_last_usage_when_latest_total_underreports"`
- browser-use `python -m py_compile browser_use/beta/service.py`
- terminal `cargo test -p browser-use-cli sdk_transport -- --nocapture`
- terminal `cargo test -p browser-use-providers server_overloaded -- --nocapture`
- terminal `cargo test -p browser-use-agent observe_timeout -- --nocapture`
@@ -269,22 +269,22 @@ This branch keeps the Python `Agent` unchanged unless callers explicitly import
- terminal `cargo test -p browser-use-agent search -- --nocapture`
- terminal `cargo test -p browser-use-agent dispatcher -- --nocapture`
- terminal `cargo test -p browser-use-cli sdk_json_rpc_agent_run_returns_child_usage_events_separately -- --nocapture`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_runs_through_sdk_and_reuses_session_for_followup tests/ci/test_rust_agent.py::test_rust_agent_recovers_final_result_from_sdk_notifications_after_transport_error tests/ci/test_rust_agent.py::test_rust_agent_preserves_sdk_notification_history_on_cancel -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_recovers_final_result_from_sdk_notifications_after_transport_error tests/ci/test_rust_agent.py::test_rust_agent_recovers_nested_sdk_notification_events tests/ci/test_rust_agent.py::test_rust_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_recovers_nested_sdk_notification_events tests/ci/test_rust_agent.py::test_rust_agent_prefers_notification_final_when_response_history_lacks_result tests/ci/test_rust_agent.py::test_rust_agent_uses_sdk_history_usage_when_events_do_not_include_usage tests/ci/test_rust_agent.py::test_rust_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_recovers_final_result_from_sdk_notifications_after_transport_error tests/ci/test_rust_agent.py::test_rust_agent_recovers_nested_sdk_notification_events tests/ci/test_rust_agent.py::test_rust_agent_recovers_projected_sdk_final_events tests/ci/test_rust_agent.py::test_rust_agent_prefers_notification_final_when_response_history_lacks_result tests/ci/test_rust_agent.py::test_rust_agent_uses_sdk_history_usage_when_events_do_not_include_usage tests/ci/test_rust_agent.py::test_rust_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_runs_through_sdk_and_reuses_session_for_followup -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_runs_through_sdk_and_reuses_session_for_followup tests/ci/test_beta_agent.py::test_beta_agent_recovers_final_result_from_sdk_notifications_after_transport_error tests/ci/test_beta_agent.py::test_beta_agent_preserves_sdk_notification_history_on_cancel -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_recovers_final_result_from_sdk_notifications_after_transport_error tests/ci/test_beta_agent.py::test_beta_agent_recovers_nested_sdk_notification_events tests/ci/test_beta_agent.py::test_beta_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_recovers_nested_sdk_notification_events tests/ci/test_beta_agent.py::test_beta_agent_prefers_notification_final_when_response_history_lacks_result tests/ci/test_beta_agent.py::test_beta_agent_uses_sdk_history_usage_when_events_do_not_include_usage tests/ci/test_beta_agent.py::test_beta_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_recovers_final_result_from_sdk_notifications_after_transport_error tests/ci/test_beta_agent.py::test_beta_agent_recovers_nested_sdk_notification_events tests/ci/test_beta_agent.py::test_beta_agent_recovers_projected_sdk_final_events tests/ci/test_beta_agent.py::test_beta_agent_prefers_notification_final_when_response_history_lacks_result tests/ci/test_beta_agent.py::test_beta_agent_uses_sdk_history_usage_when_events_do_not_include_usage tests/ci/test_beta_agent.py::test_beta_agent_prices_sdk_child_usage_events_without_overriding_parent_result -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_runs_through_sdk_and_reuses_session_for_followup -q`
- terminal `cargo fmt --check`
- terminal `cargo test -p browser-use-cli sdk_json_rpc_browser_create_preserves_browser_use_settings -- --nocapture`
- terminal `cargo test -p browser-use-cli sdk_provider_run_config_maps_browser_use_options_to_rust_core -- --nocapture`
- terminal `cargo test -p browser-use-agent stored_cloud_profile_uses_sdk_proxy_country_env_when_connecting -- --nocapture`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_sdk_browser_payload_includes_profile_domains_window_and_proxy -q`
- browser-use `uv run python -m py_compile browser_use/rust/service.py`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_sdk_event_dedupe_removes_projected_usage_duplicates tests/ci/test_rust_agent.py::test_rust_history_ignores_internal_browser_connection_url tests/ci/test_rust_agent.py::test_rust_agent_default_llm_matches_browser_use_default tests/ci/test_rust_agent.py::test_rust_agent_default_llm_respects_default_llm_env tests/ci/test_rust_agent.py::test_rust_agent_exposes_logging_helper_methods tests/ci/test_rust_agent.py::test_rust_agent_telemetry_filters_empty_reconstructed_urls -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py::test_rust_agent_translates_browser_use_args_to_terminal tests/ci/test_rust_agent.py::test_rust_agent_sdk_params_leave_terminal_tools_unrestricted -q`
- browser-use `uv run pytest tests/ci/test_rust_agent.py -q`
- browser-use `uv run ruff check browser_use/rust/service.py tests/ci/test_rust_agent.py tests/ci/models/test_llm_model_factory.py`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_sdk_browser_payload_includes_profile_domains_window_and_proxy -q`
- browser-use `uv run python -m py_compile browser_use/beta/service.py`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_rust_sdk_event_dedupe_removes_projected_usage_duplicates tests/ci/test_beta_agent.py::test_rust_history_ignores_internal_browser_connection_url tests/ci/test_beta_agent.py::test_beta_agent_default_llm_matches_browser_use_default tests/ci/test_beta_agent.py::test_beta_agent_default_llm_respects_default_llm_env tests/ci/test_beta_agent.py::test_beta_agent_exposes_logging_helper_methods tests/ci/test_beta_agent.py::test_beta_agent_telemetry_filters_empty_reconstructed_urls -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py::test_beta_agent_translates_browser_use_args_to_terminal tests/ci/test_beta_agent.py::test_beta_agent_sdk_params_leave_terminal_tools_unrestricted -q`
- browser-use `uv run pytest tests/ci/test_beta_agent.py -q`
- browser-use `uv run ruff check browser_use/beta/service.py tests/ci/test_beta_agent.py tests/ci/models/test_llm_model_factory.py`
- terminal `cargo test -p browser-use-browser browser_script_http_get_many_preserves_order_and_errors -- --nocapture`
- terminal `cargo test -p browser-use-browser browser_script_browser_fetch_single_returns_structured_errors_by_default -- --nocapture`
- terminal `cargo test -p browser-use-browser browser_script_js_accepts_anonymous_function_snippets -- --nocapture`
+22 -34
View File
@@ -49,18 +49,20 @@
# 👋 Human Quickstart
**1. Create environment and install Browser-Use with [uv](https://docs.astral.sh/uv/) (Python>=3.11):**
```bash
uv init && uv add browser-use && uv sync
# uvx browser-use install # Run if you don't have Chromium installed
Browser Use 0.13 introduces a new beta agent powered by a Rust core and a browser harness built for current frontier models. It gives the model a real browser/computer action space, persistent tools, and recovery loops inspired by coding agents.
```text
Python API -> Rust core -> Browser harness -> Web task done
```
**Experimental Rust agent:** install Browser Use Terminal for `from browser_use.rust import Agent`:
**1. Install Browser Use with the native core runtime (Python>=3.11):**
```bash
curl -fsSL https://browser-use.com/terminal/install.sh | sh
browser
uv add "browser-use[core]"
# or: pip install "browser-use[core]"
```
The `[core]` extra installs the native Browser Use runtime for your platform.
**2. [Optional] Get your API key from [Browser Use Cloud](https://cloud.browser-use.com/new-api-key?utm_source=github&utm_medium=readme-quickstart-api-key):**
```
# .env
@@ -71,39 +73,23 @@ BROWSER_USE_API_KEY=your-key
**3. Run your first agent:**
```python
from browser_use import Agent, Browser, ChatBrowserUse
# from browser_use import ChatGoogle # ChatGoogle(model='gemini-3-flash-preview')
# from browser_use import ChatAnthropic # ChatAnthropic(model='claude-sonnet-4-6')
import asyncio
async def main():
browser = Browser(
# use_cloud=True, # Use a stealth browser on Browser Use Cloud
)
agent = Agent(
task="Find the number of stars of the browser-use repo",
llm=ChatBrowserUse(),
# llm=ChatGoogle(model='gemini-3-flash-preview'),
# llm=ChatAnthropic(model='claude-sonnet-4-6'),
browser=browser,
)
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
```
**4. [Optional] Try the experimental Rust core:**
```python
from browser_use import ChatBrowserUse
from browser_use.rust import Agent
from browser_use.beta import Agent, BrowserProfile, ChatBrowserUse
# from browser_use.beta import ChatOpenAI # ChatOpenAI(model='gpt-5.5')
# from browser_use.beta import ChatGoogle # ChatGoogle(model='gemini-3.1-pro-preview')
# from browser_use.beta import ChatAnthropic # ChatAnthropic(model='claude-opus-4-8')
import asyncio
async def main():
agent = Agent(
task="Find the number of stars of the browser-use repo",
llm=ChatBrowserUse(),
# llm=ChatOpenAI(model='gpt-5.5'),
# llm=ChatGoogle(model='gemini-3.1-pro-preview'),
# llm=ChatAnthropic(model='claude-opus-4-8'), # Sonnet also works well.
browser_profile=BrowserProfile(
headless=False,
allowed_domains=["*.github.com"],
),
)
history = await agent.run()
print(history.final_result())
@@ -112,6 +98,8 @@ if __name__ == "__main__":
asyncio.run(main())
```
Existing Python agent users can keep using `from browser_use import Agent`. The new Rust-powered beta agent is `from browser_use.beta import Agent`.
Check out the [library docs](https://docs.browser-use.com/open-source/introduction) and the [cloud docs](https://docs.cloud.browser-use.com?utm_source=github&utm_medium=readme-cloud-docs) for more!
<br/>
+3 -3
View File
@@ -6,11 +6,11 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from browser_use.agent.service import Agent
from browser_use.rust.service import RustAgentError
from browser_use.beta.service import BetaAgentError
_LAZY_IMPORTS = {
'Agent': ('browser_use.agent.service', 'Agent'),
'RustAgentError': ('browser_use.rust.service', 'RustAgentError'),
'BetaAgentError': ('browser_use.beta.service', 'BetaAgentError'),
}
@@ -28,5 +28,5 @@ def __getattr__(name: str):
__all__ = [
'Agent',
'RustAgentError',
'BetaAgentError',
]
+72
View File
@@ -0,0 +1,72 @@
"""Beta Browser Use integration."""
from __future__ import annotations
from typing import TYPE_CHECKING
from browser_use.beta.service import Agent, BetaAgentError, find_browser_use_terminal_binary
if TYPE_CHECKING:
from browser_use.browser import BrowserProfile, BrowserSession
from browser_use.browser import BrowserSession as Browser
from browser_use.llm.anthropic.chat import ChatAnthropic
from browser_use.llm.azure.chat import ChatAzureOpenAI
from browser_use.llm.browser_use.chat import ChatBrowserUse
from browser_use.llm.google.chat import ChatGoogle
from browser_use.llm.groq.chat import ChatGroq
from browser_use.llm.litellm.chat import ChatLiteLLM
from browser_use.llm.mistral.chat import ChatMistral
from browser_use.llm.oci_raw.chat import ChatOCIRaw
from browser_use.llm.ollama.chat import ChatOllama
from browser_use.llm.openai.chat import ChatOpenAI
from browser_use.llm.vercel.chat import ChatVercel
_LAZY_IMPORTS = {
'Browser': ('browser_use.browser', 'BrowserSession'),
'BrowserProfile': ('browser_use.browser', 'BrowserProfile'),
'BrowserSession': ('browser_use.browser', 'BrowserSession'),
'ChatOpenAI': ('browser_use.llm.openai.chat', 'ChatOpenAI'),
'ChatGoogle': ('browser_use.llm.google.chat', 'ChatGoogle'),
'ChatAnthropic': ('browser_use.llm.anthropic.chat', 'ChatAnthropic'),
'ChatBrowserUse': ('browser_use.llm.browser_use.chat', 'ChatBrowserUse'),
'ChatGroq': ('browser_use.llm.groq.chat', 'ChatGroq'),
'ChatLiteLLM': ('browser_use.llm.litellm.chat', 'ChatLiteLLM'),
'ChatMistral': ('browser_use.llm.mistral.chat', 'ChatMistral'),
'ChatAzureOpenAI': ('browser_use.llm.azure.chat', 'ChatAzureOpenAI'),
'ChatOCIRaw': ('browser_use.llm.oci_raw.chat', 'ChatOCIRaw'),
'ChatOllama': ('browser_use.llm.ollama.chat', 'ChatOllama'),
'ChatVercel': ('browser_use.llm.vercel.chat', 'ChatVercel'),
}
def __getattr__(name: str):
if name in _LAZY_IMPORTS:
module_path, attr_name = _LAZY_IMPORTS[name]
from importlib import import_module
module = import_module(module_path)
attr = getattr(module, attr_name)
globals()[name] = attr
return attr
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
__all__ = [
'Agent',
'BetaAgentError',
'Browser',
'BrowserProfile',
'BrowserSession',
'ChatAnthropic',
'ChatAzureOpenAI',
'ChatBrowserUse',
'ChatGoogle',
'ChatGroq',
'ChatLiteLLM',
'ChatMistral',
'ChatOCIRaw',
'ChatOllama',
'ChatOpenAI',
'ChatVercel',
'find_browser_use_terminal_binary',
]
@@ -94,8 +94,8 @@ except ImportError:
Laminar = None # type: ignore
class RustAgentError(RuntimeError):
"""Raised when the Rust terminal core cannot run a task."""
class BetaAgentError(RuntimeError):
"""Raised when the beta agent cannot run a task."""
def _laminar_ready() -> bool:
@@ -203,7 +203,7 @@ def find_browser_use_terminal_binary() -> str:
path_binary = shutil.which('browser-use-terminal')
if path_binary:
return path_binary
raise RustAgentError(
raise BetaAgentError(
f'Could not find browser-use-terminal. Install Browser Use Terminal with `{TERMINAL_INSTALL_COMMAND}`, '
'install browser-use-core, or set BROWSER_USE_TERMINAL_BINARY to a built terminal CLI.'
)
@@ -220,7 +220,7 @@ def _find_packaged_browser_use_terminal_binary() -> str | None:
return None
class RustSdkJsonRpcError(RustAgentError):
class RustSdkJsonRpcError(BetaAgentError):
"""Raised when the Rust SDK server returns a JSON-RPC error."""
def __init__(self, code: int, message: str) -> None:
@@ -262,7 +262,7 @@ class RustSdkClient:
)
except (FileNotFoundError, PermissionError) as exc:
command = self.command[0] if self.command else 'browser-use-terminal'
raise RustAgentError(
raise BetaAgentError(
f'Could not start Rust SDK server command {command!r}. '
f'Install Browser Use Terminal with `{TERMINAL_INSTALL_COMMAND}`, '
'or set BROWSER_USE_TERMINAL_BINARY to a built terminal CLI.'
@@ -273,7 +273,7 @@ class RustSdkClient:
async def call(self, method: str, params: dict[str, Any] | None = None) -> Any:
await self.start()
if self.process is None or self.process.stdin is None:
raise RustAgentError('Rust SDK server stdin is unavailable')
raise BetaAgentError('Rust SDK server stdin is unavailable')
loop = asyncio.get_running_loop()
async with self._write_lock:
request_id = self._next_id
@@ -291,7 +291,7 @@ class RustSdkClient:
await self.process.stdin.drain()
except (BrokenPipeError, ConnectionResetError) as exc:
self._pending.pop(request_id, None)
raise RustAgentError(f'Rust SDK server pipe closed while sending {method}') from exc
raise BetaAgentError(f'Rust SDK server pipe closed while sending {method}') from exc
try:
return await future
except asyncio.CancelledError:
@@ -320,7 +320,7 @@ class RustSdkClient:
*(task for task in (self._reader_task, self._stderr_task) if task is not None and task is not asyncio.current_task()),
return_exceptions=True,
)
self._fail_all(RustAgentError('Rust SDK server closed'))
self._fail_all(BetaAgentError('Rust SDK server closed'))
self.process = None
async def _read_stdout(self) -> None:
@@ -344,19 +344,19 @@ class RustSdkClient:
if not self._handle_stdout_line(raw_line):
return
if len(buffer) > self.max_line_bytes:
self._fail_all(RustAgentError(f'Rust SDK JSON-RPC line exceeded {self.max_line_bytes} bytes without newline'))
self._fail_all(BetaAgentError(f'Rust SDK JSON-RPC line exceeded {self.max_line_bytes} bytes without newline'))
return
except Exception as exc:
message = f'Rust SDK stdout reader failed: {exc}'
self.stderr_lines.append(message)
self._fail_all(RustAgentError(message))
self._fail_all(BetaAgentError(message))
finally:
if self._pending:
detail = '\n'.join(self.stderr_lines[-20:])
message = 'Rust SDK server exited before responding'
if detail:
message = f'{message}: {detail}'
self._fail_all(RustAgentError(message))
self._fail_all(BetaAgentError(message))
def _handle_stdout_line(self, raw_line: bytes) -> bool:
line = raw_line.decode('utf-8', errors='replace').strip()
@@ -365,7 +365,7 @@ class RustSdkClient:
try:
message = json.loads(line)
except json.JSONDecodeError as exc:
self._fail_all(RustAgentError(f'Invalid Rust SDK JSON-RPC line: {line}: {exc}'))
self._fail_all(BetaAgentError(f'Invalid Rust SDK JSON-RPC line: {line}: {exc}'))
return False
self._handle_message(message)
return True
@@ -379,7 +379,7 @@ class RustSdkClient:
def _handle_message(self, message: Any) -> None:
if not isinstance(message, dict):
self._fail_all(RustAgentError(f'Rust SDK server emitted non-object JSON-RPC message: {message!r}'))
self._fail_all(BetaAgentError(f'Rust SDK server emitted non-object JSON-RPC message: {message!r}'))
return
method = message.get('method')
if method in {'agent.event', 'agent.projected_event'}:
@@ -395,7 +395,7 @@ class RustSdkClient:
return
request_id = message.get('id')
if not isinstance(request_id, int):
self._fail_all(RustAgentError('Rust SDK JSON-RPC response id must be an integer'))
self._fail_all(BetaAgentError('Rust SDK JSON-RPC response id must be an integer'))
return
future = self._pending.pop(request_id, None)
if future is None or future.done():
@@ -3743,7 +3743,7 @@ def _record_laminar_terminal_tool_spans(events: list[dict[str, Any]], *, max_spa
with _laminar_start_span(f'rust_core.tool.{tool_call.get("name") or "tool"}', input=input_messages, span_type='TOOL'):
_laminar_set_span_attributes(
{
'runtime': 'browser_use.rust',
'runtime': 'browser_use.beta',
'tool_name': tool_call.get('name'),
'tool_call_id': tool_call_id,
'tool_index': span_index,
@@ -3798,7 +3798,7 @@ def _record_laminar_terminal_llm_spans(
span_output_messages = _terminal_laminar_span_output_messages(span_output)
_laminar_set_span_attributes(
{
'runtime': 'browser_use.rust',
'runtime': 'browser_use.beta',
'model': str(span_input.get('model') or default_model),
'provider': str(span_input.get('provider') or default_provider),
'turn_index': span_index,
@@ -3904,7 +3904,7 @@ def _load_rust_history(file_path: str | Path) -> AgentHistoryList:
with open(file_path, encoding='utf-8') as history_file:
data = json.load(history_file)
if not isinstance(data, dict):
raise RustAgentError(f'Invalid Browser Use history file: {file_path}')
raise BetaAgentError(f'Invalid Browser Use history file: {file_path}')
for item in data.get('history', []):
if isinstance(item, dict):
item['model_output'] = None
@@ -4669,7 +4669,7 @@ class Agent(Generic[Context, AgentStructuredOutput]):
model = getattr(self.llm, 'model', None) or self.model
provider = getattr(self.llm, 'provider', None) or 'rust-terminal'
summary = {
'runtime': 'browser_use.rust',
'runtime': 'browser_use.beta',
'model': model,
'provider': provider,
'max_steps': max_steps,
@@ -4914,7 +4914,7 @@ class Agent(Generic[Context, AgentStructuredOutput]):
started=started,
finished=finished,
output_model_schema=self.output_model_schema,
process_error='Rust agent stopped before terminal run.',
process_error='Beta agent stopped before terminal run.',
)
self.history = self.result
await self._apply_terminal_usage_costs([])
@@ -4922,9 +4922,9 @@ class Agent(Generic[Context, AgentStructuredOutput]):
self._record_laminar_run_observability(
max_steps=max_steps,
duration_seconds=finished - started,
process_error='Rust agent stopped before terminal run.',
process_error='Beta agent stopped before terminal run.',
)
self._record_run_telemetry(max_steps=max_steps, agent_run_error='Rust agent stopped before terminal run.')
self._record_run_telemetry(max_steps=max_steps, agent_run_error='Beta agent stopped before terminal run.')
self._dispatch_run_update_event()
self._log_final_outcome_messages()
await self._finalize_run_cleanup()
@@ -4944,7 +4944,7 @@ class Agent(Generic[Context, AgentStructuredOutput]):
started=started,
finished=finished,
output_model_schema=self.output_model_schema,
process_error='Rust agent stopped before terminal run.',
process_error='Beta agent stopped before terminal run.',
)
self.history = self.result
await self._apply_terminal_usage_costs([])
@@ -4952,9 +4952,9 @@ class Agent(Generic[Context, AgentStructuredOutput]):
self._record_laminar_run_observability(
max_steps=max_steps,
duration_seconds=finished - started,
process_error='Rust agent stopped before terminal run.',
process_error='Beta agent stopped before terminal run.',
)
self._record_run_telemetry(max_steps=max_steps, agent_run_error='Rust agent stopped before terminal run.')
self._record_run_telemetry(max_steps=max_steps, agent_run_error='Beta agent stopped before terminal run.')
self._dispatch_run_update_event()
self._log_final_outcome_messages()
await self._finalize_run_cleanup()
@@ -4994,7 +4994,7 @@ class Agent(Generic[Context, AgentStructuredOutput]):
enqueue_timeout: int | None = None,
) -> AgentHistoryList[AgentStructuredOutput]:
if not self.terminal_session_id or not self._sdk_agent_id:
raise RustAgentError('No active Rust session. Call run() before follow_up().')
raise BetaAgentError('No active Rust session. Call run() before follow_up().')
resolved_max_steps = max_steps if max_steps is not None else self.kwargs.get('max_steps', 100)
self.add_new_task(task)
self.state.follow_up_task = False
@@ -5913,7 +5913,7 @@ class Agent(Generic[Context, AgentStructuredOutput]):
self.logger.debug('Initial actions completed')
async def _execute_direct_initial_navigation_actions(self) -> list[ActionResult] | None:
"""Pre-navigate existing CDP-backed browser sessions before the Rust agent starts."""
"""Pre-navigate existing CDP-backed browser sessions before the Beta agent starts."""
self._completed_initial_navigation_urls = []
self._completed_initial_navigation_states = []
if not _direct_initial_navigation_enabled():
@@ -6268,13 +6268,13 @@ class Agent(Generic[Context, AgentStructuredOutput]):
except Exception as exc:
await self._sdk_client.close()
self._sdk_client = None
raise RustAgentError(
raise BetaAgentError(
'Failed to negotiate browser-use-terminal SDK protocol via runtime.ping. '
'Install a compatible browser-use-core package or set BROWSER_USE_TERMINAL_BINARY to a compatible binary.'
) from exc
await self._sdk_client.close()
self._sdk_client = None
raise RustAgentError(
raise BetaAgentError(
f'Unsupported browser-use-terminal SDK protocol {protocol_version!r}; expected 1. '
'Install a compatible browser-use-core package or set BROWSER_USE_TERMINAL_BINARY to a compatible binary.'
)
@@ -6450,7 +6450,7 @@ class Agent(Generic[Context, AgentStructuredOutput]):
def _conversation_snapshot(self) -> dict[str, Any]:
return {
'agent': 'browser_use.rust.Agent',
'agent': 'browser_use.beta.Agent',
'task_id': self.task_id,
'session_id': self.session_id,
'terminal_session_id': self.terminal_session_id,
@@ -6598,12 +6598,12 @@ def _align_browser_use_agent_signatures() -> None:
for name, browser_use_method in vars(_PythonAgent).items():
if name.startswith('__') and name != '__init__':
continue
rust_method = getattr(Agent, name, None)
if rust_method is None or not callable(browser_use_method) or not callable(rust_method):
beta_method = getattr(Agent, name, None)
if beta_method is None or not callable(browser_use_method) or not callable(beta_method):
continue
try:
rust_method.__signature__ = inspect.signature(browser_use_method)
rust_method.__annotations__ = dict(getattr(browser_use_method, '__annotations__', {}))
beta_method.__signature__ = inspect.signature(browser_use_method)
beta_method.__annotations__ = dict(getattr(browser_use_method, '__annotations__', {}))
except (TypeError, ValueError, AttributeError):
continue
try:
@@ -6612,12 +6612,12 @@ def _align_browser_use_agent_signatures() -> None:
pass
agent_hook_func = Callable[[_PythonAgent], Awaitable[None]]
for name in ('run', 'run_sync'):
rust_method = getattr(Agent, name, None)
if rust_method is None:
beta_method = getattr(Agent, name, None)
if beta_method is None:
continue
try:
rust_method.__annotations__['on_step_start'] = agent_hook_func | None
rust_method.__annotations__['on_step_end'] = agent_hook_func | None
beta_method.__annotations__['on_step_start'] = agent_hook_func | None
beta_method.__annotations__['on_step_end'] = agent_hook_func | None
except (TypeError, AttributeError, KeyError):
continue
-9
View File
@@ -1,9 +0,0 @@
"""Rust-core Browser Use integration."""
from browser_use.rust.service import Agent, RustAgentError, find_browser_use_terminal_binary
__all__ = [
'Agent',
'RustAgentError',
'find_browser_use_terminal_binary',
]
@@ -7,8 +7,11 @@ Set BU_CDP_URL or BROWSER_USE_CDP_URL to attach to a remote Browser Use cloud br
import asyncio
import os
from browser_use import BrowserSession
from browser_use.rust import Agent
from browser_use.beta import Agent, BrowserSession, ChatBrowserUse
# from browser_use.beta import ChatOpenAI # ChatOpenAI(model='gpt-5.5')
# from browser_use.beta import ChatGoogle # ChatGoogle(model='gemini-3.1-pro-preview')
# from browser_use.beta import ChatAnthropic # ChatAnthropic(model='claude-opus-4-8')
async def main() -> None:
@@ -17,7 +20,14 @@ async def main() -> None:
task = os.environ.get('BU_TASK', 'Open https://example.com and report the page title.')
max_steps = int(os.environ.get('BU_MAX_STEPS', '20'))
agent = Agent(task=task, browser_session=browser_session)
agent = Agent(
task=task,
llm=ChatBrowserUse(),
# llm=ChatOpenAI(model='gpt-5.5'),
# llm=ChatGoogle(model='gemini-3.1-pro-preview'),
# llm=ChatAnthropic(model='claude-opus-4-8'), # Sonnet also works well.
browser_session=browser_session,
)
history = await agent.run(max_steps=max_steps)
print(history.final_result() or '(no final result)')
+5 -5
View File
@@ -2,7 +2,7 @@
name = "browser-use"
description = "Make websites accessible for AI agents"
authors = [{ name = "Gregor Zunic" }]
version = "0.13.0rc2"
version = "0.13.0rc3"
readme = "README.md"
requires-python = ">=3.11,<4.0"
classifiers = [
@@ -59,7 +59,7 @@ dependencies = [
[project.optional-dependencies]
cli = ["textual==7.4.0"]
rust = [
core = [
"browser-use-core==0.13.0rc2; sys_platform == 'darwin' and platform_machine == 'arm64'",
"browser-use-core==0.13.0rc2; sys_platform == 'darwin' and platform_machine == 'x86_64'",
"browser-use-core==0.13.0rc2; sys_platform == 'linux' and platform_machine == 'x86_64'",
@@ -161,9 +161,9 @@ exclude = [
"browser_use/llm/oci_raw/*",
"browser_use/llm/tests/test_chat_models.py",
"browser_use/llm/tests/test_single_step.py",
# Dynamic Rust compatibility wrapper; covered by tests/ci/test_rust_agent.py.
"browser_use/rust/service.py",
"tests/ci/test_rust_agent.py",
# Dynamic beta agent wrapper; covered by tests/ci/test_beta_agent.py.
"browser_use/beta/service.py",
"tests/ci/test_beta_agent.py",
"product_extraction.py",
"discover/",
"list/",
File diff suppressed because it is too large Load Diff