diff --git a/agent/tools/web_search/web_search.py b/agent/tools/web_search/web_search.py index 2fe0c71c..06097704 100644 --- a/agent/tools/web_search/web_search.py +++ b/agent/tools/web_search/web_search.py @@ -114,14 +114,27 @@ def _get_api_key(provider: str) -> str: return "" +def anonymous_opt_in(value) -> bool: + """Whether a config value explicitly enables keyless search. Anything not + clearly "on" (e.g. the string "false") stays off, since it sends queries + to a third party.""" + if isinstance(value, bool): + return value + if isinstance(value, int): + return value == 1 + if isinstance(value, str): + return value.strip().lower() in ("true", "1", "yes", "on") + return False + + def _anysearch_anonymous_enabled() -> bool: """Check if AnySearch anonymous mode is enabled via config.""" - return bool(_tools_web_search_conf().get("anysearch_anonymous")) + return anonymous_opt_in(_tools_web_search_conf().get("anysearch_anonymous")) def _keenable_anonymous_enabled() -> bool: """Check if Keenable anonymous (keyless) mode is enabled via config.""" - return bool(_tools_web_search_conf().get("keenable_anonymous")) + return anonymous_opt_in(_tools_web_search_conf().get("keenable_anonymous")) def _get_searxng_url() -> str: diff --git a/channel/web/api/models.py b/channel/web/api/models.py index b4d47a52..a5d9b0dd 100644 --- a/channel/web/api/models.py +++ b/channel/web/api/models.py @@ -1489,8 +1489,10 @@ class ModelsHandler: if not isinstance(ws_cfg, dict): ws_cfg = {} - anonymous_on = bool(ws_cfg.get("anysearch_anonymous")) - keenable_anonymous_on = bool(ws_cfg.get("keenable_anonymous")) + from agent.tools.web_search.web_search import anonymous_opt_in + + anonymous_on = anonymous_opt_in(ws_cfg.get("anysearch_anonymous")) + keenable_anonymous_on = anonymous_opt_in(ws_cfg.get("keenable_anonymous")) searxng_url = (ws_cfg.get("searxng_url") or "").strip() if isinstance(ws_cfg, dict) else "" providers = [] configured_ids = [] diff --git a/tests/test_web_search_explicit_anonymous.py b/tests/test_web_search_explicit_anonymous.py new file mode 100644 index 00000000..6083eafe --- /dev/null +++ b/tests/test_web_search_explicit_anonymous.py @@ -0,0 +1,30 @@ +import pytest +from agent.tools.web_search import web_search as ws + + +@pytest.mark.parametrize('value', ['false', 'off', '0', '', None, 0, [], {'invalid': True}]) +def test_anonymous_search_requires_boolean_opt_in(monkeypatch, value): + monkeypatch.setattr(ws, '_get_api_key', lambda provider: '') + monkeypatch.setattr(ws, '_get_searxng_url', lambda: '') + monkeypatch.setattr(ws, 'conf', lambda: {'tools': {'web_search': {'anysearch_anonymous': value, 'keenable_anonymous': value}}}) + assert ws.configured_providers() == [] + + +def test_explicit_opt_in_and_api_key_configuration_remain_supported(monkeypatch): + monkeypatch.setattr(ws, '_get_api_key', lambda provider: 'fake-key' if provider == 'tavily' else '') + monkeypatch.setattr(ws, '_get_searxng_url', lambda: '') + monkeypatch.setattr(ws, 'conf', lambda: {'tools': {'web_search': {'anysearch_anonymous': True, 'keenable_anonymous': True}}}) + assert ws.configured_providers() == ['anysearch', 'tavily', 'keenable'] + + +@pytest.mark.parametrize('value', [True, 'true', 'True', 'yes', 'on', '1', 1]) +def test_explicit_on_values_enable_anonymous_search(value): + assert ws.anonymous_opt_in(value) is True + + +def test_console_status_matches_the_tool(monkeypatch): + from channel.web.api.models import ModelsHandler + cfg = {'tools': {'web_search': {'anysearch_anonymous': 'false', 'keenable_anonymous': 'false'}}} + providers = {p['id']: p for p in ModelsHandler._search_capability(cfg)['providers']} + assert not providers['anysearch'].get('configured') + assert not providers['keenable'].get('configured')