mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
This commit is contained in:
@@ -423,7 +423,7 @@ class TestBuildNousSubscriptionPrompt:
|
||||
"web": NousFeatureState("web", "Web tools", True, True, True, True, False, True, "firecrawl"),
|
||||
"image_gen": NousFeatureState("image_gen", "Image generation", True, True, True, True, False, True, "Nous Subscription"),
|
||||
"tts": NousFeatureState("tts", "OpenAI TTS", True, True, True, True, False, True, "OpenAI TTS"),
|
||||
"browser": NousFeatureState("browser", "Browser automation", True, True, True, True, False, True, "Browserbase"),
|
||||
"browser": NousFeatureState("browser", "Browser automation", True, True, True, True, False, True, "Browser Use"),
|
||||
"modal": NousFeatureState("modal", "Modal execution", False, True, False, False, False, True, "local"),
|
||||
},
|
||||
),
|
||||
@@ -431,9 +431,9 @@ class TestBuildNousSubscriptionPrompt:
|
||||
|
||||
prompt = build_nous_subscription_prompt({"web_search", "browser_navigate"})
|
||||
|
||||
assert "Browserbase" in prompt
|
||||
assert "Browser Use" in prompt
|
||||
assert "Modal execution is optional" in prompt
|
||||
assert "do not ask the user for Firecrawl, FAL, OpenAI TTS, or Browserbase API keys" in prompt
|
||||
assert "do not ask the user for Firecrawl, FAL, OpenAI TTS, or Browser-Use API keys" in prompt
|
||||
|
||||
def test_non_subscriber_prompt_includes_relevant_upgrade_guidance(self, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "1")
|
||||
|
||||
@@ -44,7 +44,62 @@ def test_get_nous_subscription_features_prefers_managed_modal_in_auto_mode(monke
|
||||
assert features.modal.direct_override is False
|
||||
|
||||
|
||||
def test_get_nous_subscription_features_prefers_camofox_over_managed_browserbase(monkeypatch):
|
||||
def test_get_nous_subscription_features_marks_browser_use_as_managed_when_gateway_ready(monkeypatch):
|
||||
monkeypatch.setattr(ns, "get_env_value", lambda name: "")
|
||||
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {"logged_in": True})
|
||||
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "browser")
|
||||
monkeypatch.setattr(ns, "_has_agent_browser", lambda: True)
|
||||
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
||||
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: False)
|
||||
monkeypatch.setattr(
|
||||
ns,
|
||||
"is_managed_tool_gateway_ready",
|
||||
lambda vendor: vendor == "browser-use",
|
||||
)
|
||||
|
||||
features = ns.get_nous_subscription_features(
|
||||
{"browser": {"cloud_provider": "browser-use"}}
|
||||
)
|
||||
|
||||
assert features.browser.available is True
|
||||
assert features.browser.active is True
|
||||
assert features.browser.managed_by_nous is True
|
||||
assert features.browser.direct_override is False
|
||||
assert features.browser.current_provider == "Browser Use"
|
||||
|
||||
|
||||
def test_get_nous_subscription_features_uses_direct_browserbase_when_no_managed_gateway(monkeypatch):
|
||||
"""When direct Browserbase keys are set and no managed gateway is available,
|
||||
the unconfigured fallback should pick Browserbase as a direct provider."""
|
||||
env = {
|
||||
"BROWSERBASE_API_KEY": "bb-key",
|
||||
"BROWSERBASE_PROJECT_ID": "bb-project",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(ns, "get_env_value", lambda name: env.get(name, ""))
|
||||
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {"logged_in": True})
|
||||
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "browser")
|
||||
monkeypatch.setattr(ns, "_has_agent_browser", lambda: True)
|
||||
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
||||
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: False)
|
||||
monkeypatch.setattr(
|
||||
ns,
|
||||
"is_managed_tool_gateway_ready",
|
||||
lambda vendor: False, # No managed gateway available
|
||||
)
|
||||
|
||||
features = ns.get_nous_subscription_features({})
|
||||
|
||||
assert features.browser.available is True
|
||||
assert features.browser.active is True
|
||||
assert features.browser.managed_by_nous is False
|
||||
assert features.browser.direct_override is True
|
||||
assert features.browser.current_provider == "Browserbase"
|
||||
|
||||
|
||||
def test_get_nous_subscription_features_prefers_camofox_over_managed_browser_use(monkeypatch):
|
||||
env = {"CAMOFOX_URL": "http://localhost:9377"}
|
||||
|
||||
monkeypatch.setattr(ns, "get_env_value", lambda name: env.get(name, ""))
|
||||
@@ -57,11 +112,11 @@ def test_get_nous_subscription_features_prefers_camofox_over_managed_browserbase
|
||||
monkeypatch.setattr(
|
||||
ns,
|
||||
"is_managed_tool_gateway_ready",
|
||||
lambda vendor: vendor == "browserbase",
|
||||
lambda vendor: vendor == "browser-use",
|
||||
)
|
||||
|
||||
features = ns.get_nous_subscription_features(
|
||||
{"browser": {"cloud_provider": "browserbase"}}
|
||||
{"browser": {"cloud_provider": "browser-use"}}
|
||||
)
|
||||
|
||||
assert features.browser.available is True
|
||||
|
||||
@@ -88,7 +88,7 @@ def test_show_status_reports_managed_nous_features(monkeypatch, capsys, tmp_path
|
||||
"web": NousFeatureState("web", "Web tools", True, True, True, True, False, True, "firecrawl"),
|
||||
"image_gen": NousFeatureState("image_gen", "Image generation", True, True, True, True, False, True, "Nous Subscription"),
|
||||
"tts": NousFeatureState("tts", "OpenAI TTS", True, True, True, True, False, True, "OpenAI TTS"),
|
||||
"browser": NousFeatureState("browser", "Browser automation", True, True, True, True, False, True, "Browserbase"),
|
||||
"browser": NousFeatureState("browser", "Browser automation", True, True, True, True, False, True, "Browser Use"),
|
||||
"modal": NousFeatureState("modal", "Modal execution", False, True, False, False, False, True, "local"),
|
||||
},
|
||||
),
|
||||
|
||||
@@ -330,7 +330,7 @@ def test_first_install_nous_auto_configures_managed_defaults(monkeypatch):
|
||||
|
||||
assert config["web"]["backend"] == "firecrawl"
|
||||
assert config["tts"]["provider"] == "openai"
|
||||
assert config["browser"]["cloud_provider"] == "browserbase"
|
||||
assert config["browser"]["cloud_provider"] == "browser-use"
|
||||
assert configured == []
|
||||
|
||||
# ── Platform / toolset consistency ────────────────────────────────────────────
|
||||
|
||||
@@ -45,3 +45,35 @@ class TestResolveCdpOverride:
|
||||
|
||||
with patch("tools.browser_tool.requests.get", side_effect=RuntimeError("boom")):
|
||||
assert _resolve_cdp_override(HTTP_URL) == HTTP_URL
|
||||
|
||||
def test_normalizes_provider_returned_http_cdp_url_when_creating_session(self, monkeypatch):
|
||||
import tools.browser_tool as browser_tool
|
||||
|
||||
provider = Mock()
|
||||
provider.create_session.return_value = {
|
||||
"session_name": "cloud-session",
|
||||
"bb_session_id": "bu_123",
|
||||
"cdp_url": "https://cdp.browser-use.example/session",
|
||||
"features": {"browser_use": True},
|
||||
}
|
||||
|
||||
response = Mock()
|
||||
response.raise_for_status.return_value = None
|
||||
response.json.return_value = {"webSocketDebuggerUrl": WS_URL}
|
||||
|
||||
monkeypatch.setattr(browser_tool, "_active_sessions", {})
|
||||
monkeypatch.setattr(browser_tool, "_session_last_activity", {})
|
||||
monkeypatch.setattr(browser_tool, "_start_browser_cleanup_thread", lambda: None)
|
||||
monkeypatch.setattr(browser_tool, "_update_session_activity", lambda task_id: None)
|
||||
monkeypatch.setattr(browser_tool, "_get_cdp_override", lambda: "")
|
||||
monkeypatch.setattr(browser_tool, "_get_cloud_provider", lambda: provider)
|
||||
|
||||
with patch("tools.browser_tool.requests.get", return_value=response) as mock_get:
|
||||
session_info = browser_tool._get_session_info("task-browser-use")
|
||||
|
||||
assert session_info["cdp_url"] == WS_URL
|
||||
provider.create_session.assert_called_once_with("task-browser-use")
|
||||
mock_get.assert_called_once_with(
|
||||
"https://cdp.browser-use.example/session/json/version",
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
@@ -113,16 +113,15 @@ def _install_fake_tools_package():
|
||||
sys.modules["tools.environments.managed_modal"] = types.SimpleNamespace(ManagedModalEnvironment=_DummyEnvironment)
|
||||
|
||||
|
||||
def test_browserbase_explicit_local_mode_stays_local_even_when_managed_gateway_is_ready(tmp_path):
|
||||
def test_browser_use_explicit_local_mode_stays_local_even_when_managed_gateway_is_ready(tmp_path):
|
||||
_install_fake_tools_package()
|
||||
(tmp_path / "config.yaml").write_text("browser:\n cloud_provider: local\n", encoding="utf-8")
|
||||
env = os.environ.copy()
|
||||
env.pop("BROWSERBASE_API_KEY", None)
|
||||
env.pop("BROWSERBASE_PROJECT_ID", None)
|
||||
env.pop("BROWSER_USE_API_KEY", None)
|
||||
env.update({
|
||||
"HERMES_HOME": str(tmp_path),
|
||||
"TOOL_GATEWAY_USER_TOKEN": "nous-token",
|
||||
"BROWSERBASE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
"BROWSER_USE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
})
|
||||
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
@@ -135,7 +134,7 @@ def test_browserbase_explicit_local_mode_stays_local_even_when_managed_gateway_i
|
||||
assert provider is None
|
||||
|
||||
|
||||
def test_browserbase_managed_gateway_adds_idempotency_key_and_persists_external_call_id():
|
||||
def test_browserbase_does_not_use_gateway_only_configuration():
|
||||
_install_fake_tools_package()
|
||||
env = os.environ.copy()
|
||||
env.pop("BROWSERBASE_API_KEY", None)
|
||||
@@ -145,104 +144,124 @@ def test_browserbase_managed_gateway_adds_idempotency_key_and_persists_external_
|
||||
"BROWSERBASE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
})
|
||||
|
||||
class _Response:
|
||||
status_code = 200
|
||||
ok = True
|
||||
text = ""
|
||||
headers = {"x-external-call-id": "call-browserbase-1"}
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"id": "bb_local_session_1",
|
||||
"connectUrl": "wss://connect.browserbase.example/session",
|
||||
}
|
||||
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
browserbase_module = _load_tool_module(
|
||||
"tools.browser_providers.browserbase",
|
||||
"browser_providers/browserbase.py",
|
||||
)
|
||||
|
||||
with patch.object(browserbase_module.requests, "post", return_value=_Response()) as post:
|
||||
provider = browserbase_module.BrowserbaseProvider()
|
||||
session = provider.create_session("task-browserbase-managed")
|
||||
|
||||
sent_headers = post.call_args.kwargs["headers"]
|
||||
assert sent_headers["X-BB-API-Key"] == "nous-token"
|
||||
assert sent_headers["X-Idempotency-Key"].startswith("browserbase-session-create:")
|
||||
assert session["external_call_id"] == "call-browserbase-1"
|
||||
|
||||
|
||||
def test_browserbase_managed_gateway_reuses_pending_idempotency_key_after_timeout():
|
||||
_install_fake_tools_package()
|
||||
env = os.environ.copy()
|
||||
env.pop("BROWSERBASE_API_KEY", None)
|
||||
env.pop("BROWSERBASE_PROJECT_ID", None)
|
||||
env.update({
|
||||
"TOOL_GATEWAY_USER_TOKEN": "nous-token",
|
||||
"BROWSERBASE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
})
|
||||
|
||||
class _Response:
|
||||
status_code = 200
|
||||
ok = True
|
||||
text = ""
|
||||
headers = {"x-external-call-id": "call-browserbase-2"}
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"id": "bb_local_session_2",
|
||||
"connectUrl": "wss://connect.browserbase.example/session2",
|
||||
}
|
||||
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
browserbase_module = _load_tool_module(
|
||||
"tools.browser_providers.browserbase",
|
||||
"browser_providers/browserbase.py",
|
||||
)
|
||||
provider = browserbase_module.BrowserbaseProvider()
|
||||
timeout = browserbase_module.requests.Timeout("timed out")
|
||||
|
||||
assert provider.is_configured() is False
|
||||
|
||||
|
||||
def test_browser_use_managed_gateway_adds_idempotency_key_and_persists_external_call_id():
|
||||
_install_fake_tools_package()
|
||||
env = os.environ.copy()
|
||||
env.pop("BROWSER_USE_API_KEY", None)
|
||||
env.update({
|
||||
"TOOL_GATEWAY_USER_TOKEN": "nous-token",
|
||||
"BROWSER_USE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
})
|
||||
|
||||
class _Response:
|
||||
status_code = 200
|
||||
ok = True
|
||||
text = ""
|
||||
headers = {"x-external-call-id": "call-browser-use-1"}
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"id": "bu_local_session_1",
|
||||
"connectUrl": "wss://connect.browser-use.example/session",
|
||||
}
|
||||
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
browser_use_module = _load_tool_module(
|
||||
"tools.browser_providers.browser_use",
|
||||
"browser_providers/browser_use.py",
|
||||
)
|
||||
|
||||
with patch.object(browser_use_module.requests, "post", return_value=_Response()) as post:
|
||||
provider = browser_use_module.BrowserUseProvider()
|
||||
session = provider.create_session("task-browser-use-managed")
|
||||
|
||||
sent_headers = post.call_args.kwargs["headers"]
|
||||
assert sent_headers["X-Browser-Use-API-Key"] == "nous-token"
|
||||
assert sent_headers["X-Idempotency-Key"].startswith("browser-use-session-create:")
|
||||
sent_payload = post.call_args.kwargs["json"]
|
||||
assert sent_payload["timeout"] == 5
|
||||
assert sent_payload["proxyCountryCode"] == "us"
|
||||
assert session["external_call_id"] == "call-browser-use-1"
|
||||
|
||||
|
||||
def test_browser_use_managed_gateway_reuses_pending_idempotency_key_after_timeout():
|
||||
_install_fake_tools_package()
|
||||
env = os.environ.copy()
|
||||
env.pop("BROWSER_USE_API_KEY", None)
|
||||
env.update({
|
||||
"TOOL_GATEWAY_USER_TOKEN": "nous-token",
|
||||
"BROWSER_USE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
})
|
||||
|
||||
class _Response:
|
||||
status_code = 200
|
||||
ok = True
|
||||
text = ""
|
||||
headers = {"x-external-call-id": "call-browser-use-2"}
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"id": "bu_local_session_2",
|
||||
"connectUrl": "wss://connect.browser-use.example/session2",
|
||||
}
|
||||
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
browser_use_module = _load_tool_module(
|
||||
"tools.browser_providers.browser_use",
|
||||
"browser_providers/browser_use.py",
|
||||
)
|
||||
provider = browser_use_module.BrowserUseProvider()
|
||||
timeout = browser_use_module.requests.Timeout("timed out")
|
||||
|
||||
with patch.object(
|
||||
browserbase_module.requests,
|
||||
browser_use_module.requests,
|
||||
"post",
|
||||
side_effect=[timeout, _Response()],
|
||||
) as post:
|
||||
try:
|
||||
provider.create_session("task-browserbase-timeout")
|
||||
except browserbase_module.requests.Timeout:
|
||||
provider.create_session("task-browser-use-timeout")
|
||||
except browser_use_module.requests.Timeout:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("Expected Browserbase create_session to propagate timeout")
|
||||
raise AssertionError("Expected Browser Use create_session to propagate timeout")
|
||||
|
||||
provider.create_session("task-browserbase-timeout")
|
||||
provider.create_session("task-browser-use-timeout")
|
||||
|
||||
first_headers = post.call_args_list[0].kwargs["headers"]
|
||||
second_headers = post.call_args_list[1].kwargs["headers"]
|
||||
assert first_headers["X-Idempotency-Key"] == second_headers["X-Idempotency-Key"]
|
||||
|
||||
|
||||
def test_browserbase_managed_gateway_preserves_pending_idempotency_key_for_in_progress_conflicts():
|
||||
def test_browser_use_managed_gateway_preserves_pending_idempotency_key_for_in_progress_conflicts():
|
||||
_install_fake_tools_package()
|
||||
env = os.environ.copy()
|
||||
env.pop("BROWSERBASE_API_KEY", None)
|
||||
env.pop("BROWSERBASE_PROJECT_ID", None)
|
||||
env.pop("BROWSER_USE_API_KEY", None)
|
||||
env.update({
|
||||
"TOOL_GATEWAY_USER_TOKEN": "nous-token",
|
||||
"BROWSERBASE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
"BROWSER_USE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
})
|
||||
|
||||
class _ConflictResponse:
|
||||
status_code = 409
|
||||
ok = False
|
||||
text = '{"error":{"code":"CONFLICT","message":"Managed Browserbase session creation is already in progress for this idempotency key"}}'
|
||||
text = '{"error":{"code":"CONFLICT","message":"Managed Browser Use session creation is already in progress for this idempotency key"}}'
|
||||
headers = {}
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"error": {
|
||||
"code": "CONFLICT",
|
||||
"message": "Managed Browserbase session creation is already in progress for this idempotency key",
|
||||
"message": "Managed Browser Use session creation is already in progress for this idempotency key",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,72 +269,71 @@ def test_browserbase_managed_gateway_preserves_pending_idempotency_key_for_in_pr
|
||||
status_code = 200
|
||||
ok = True
|
||||
text = ""
|
||||
headers = {"x-external-call-id": "call-browserbase-4"}
|
||||
headers = {"x-external-call-id": "call-browser-use-4"}
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"id": "bb_local_session_4",
|
||||
"connectUrl": "wss://connect.browserbase.example/session4",
|
||||
"id": "bu_local_session_4",
|
||||
"connectUrl": "wss://connect.browser-use.example/session4",
|
||||
}
|
||||
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
browserbase_module = _load_tool_module(
|
||||
"tools.browser_providers.browserbase",
|
||||
"browser_providers/browserbase.py",
|
||||
browser_use_module = _load_tool_module(
|
||||
"tools.browser_providers.browser_use",
|
||||
"browser_providers/browser_use.py",
|
||||
)
|
||||
provider = browserbase_module.BrowserbaseProvider()
|
||||
provider = browser_use_module.BrowserUseProvider()
|
||||
|
||||
with patch.object(
|
||||
browserbase_module.requests,
|
||||
browser_use_module.requests,
|
||||
"post",
|
||||
side_effect=[_ConflictResponse(), _SuccessResponse()],
|
||||
) as post:
|
||||
try:
|
||||
provider.create_session("task-browserbase-conflict")
|
||||
provider.create_session("task-browser-use-conflict")
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("Expected Browserbase create_session to propagate the in-progress conflict")
|
||||
raise AssertionError("Expected Browser Use create_session to propagate the in-progress conflict")
|
||||
|
||||
provider.create_session("task-browserbase-conflict")
|
||||
provider.create_session("task-browser-use-conflict")
|
||||
|
||||
first_headers = post.call_args_list[0].kwargs["headers"]
|
||||
second_headers = post.call_args_list[1].kwargs["headers"]
|
||||
assert first_headers["X-Idempotency-Key"] == second_headers["X-Idempotency-Key"]
|
||||
|
||||
|
||||
def test_browserbase_managed_gateway_uses_new_idempotency_key_for_a_new_session_after_success():
|
||||
def test_browser_use_managed_gateway_uses_new_idempotency_key_for_a_new_session_after_success():
|
||||
_install_fake_tools_package()
|
||||
env = os.environ.copy()
|
||||
env.pop("BROWSERBASE_API_KEY", None)
|
||||
env.pop("BROWSERBASE_PROJECT_ID", None)
|
||||
env.pop("BROWSER_USE_API_KEY", None)
|
||||
env.update({
|
||||
"TOOL_GATEWAY_USER_TOKEN": "nous-token",
|
||||
"BROWSERBASE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
"BROWSER_USE_GATEWAY_URL": "http://127.0.0.1:3009",
|
||||
})
|
||||
|
||||
class _Response:
|
||||
status_code = 200
|
||||
ok = True
|
||||
text = ""
|
||||
headers = {"x-external-call-id": "call-browserbase-3"}
|
||||
headers = {"x-external-call-id": "call-browser-use-3"}
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"id": "bb_local_session_3",
|
||||
"connectUrl": "wss://connect.browserbase.example/session3",
|
||||
"id": "bu_local_session_3",
|
||||
"connectUrl": "wss://connect.browser-use.example/session3",
|
||||
}
|
||||
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
browserbase_module = _load_tool_module(
|
||||
"tools.browser_providers.browserbase",
|
||||
"browser_providers/browserbase.py",
|
||||
browser_use_module = _load_tool_module(
|
||||
"tools.browser_providers.browser_use",
|
||||
"browser_providers/browser_use.py",
|
||||
)
|
||||
provider = browserbase_module.BrowserbaseProvider()
|
||||
provider = browser_use_module.BrowserUseProvider()
|
||||
|
||||
with patch.object(browserbase_module.requests, "post", side_effect=[_Response(), _Response()]) as post:
|
||||
provider.create_session("task-browserbase-new")
|
||||
provider.create_session("task-browserbase-new")
|
||||
with patch.object(browser_use_module.requests, "post", side_effect=[_Response(), _Response()]) as post:
|
||||
provider.create_session("task-browser-use-new")
|
||||
provider.create_session("task-browser-use-new")
|
||||
|
||||
first_headers = post.call_args_list[0].kwargs["headers"]
|
||||
second_headers = post.call_args_list[1].kwargs["headers"]
|
||||
|
||||
@@ -40,17 +40,17 @@ def test_resolve_managed_tool_gateway_uses_vendor_specific_override():
|
||||
os.environ,
|
||||
{
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS": "1",
|
||||
"BROWSERBASE_GATEWAY_URL": "http://browserbase-gateway.localhost:3009/",
|
||||
"BROWSER_USE_GATEWAY_URL": "http://browser-use-gateway.localhost:3009/",
|
||||
},
|
||||
clear=False,
|
||||
):
|
||||
result = resolve_managed_tool_gateway(
|
||||
"browserbase",
|
||||
"browser-use",
|
||||
token_reader=lambda: "nous-token",
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.gateway_origin == "http://browserbase-gateway.localhost:3009"
|
||||
assert result.gateway_origin == "http://browser-use-gateway.localhost:3009"
|
||||
|
||||
|
||||
def test_resolve_managed_tool_gateway_is_inactive_without_nous_token():
|
||||
|
||||
Reference in New Issue
Block a user