From 092b07c7aae0e3ce3c50bc087fe7f8f89bd67094 Mon Sep 17 00:00:00 2001 From: Ilya Gusev Date: Mon, 15 Jun 2026 11:59:52 +0000 Subject: [PATCH] feat(web): use shared user-agent and send X-Keenable-Title Drop the bespoke nanobot/ User-Agent in favor of self.user_agent for consistency with every other search provider, and add an X-Keenable-Title: nanobot header so Keenable can attribute traffic. Co-Authored-By: Claude Opus 4.8 (1M context) --- nanobot/agent/tools/web.py | 8 +++++--- tests/tools/test_web_search_tool.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nanobot/agent/tools/web.py b/nanobot/agent/tools/web.py index 6d4de138..78ec89f4 100644 --- a/nanobot/agent/tools/web.py +++ b/nanobot/agent/tools/web.py @@ -489,10 +489,12 @@ class WebSearchTool(Tool): return f"Error: {e}" async def _search_keenable(self, query: str, n: int) -> str: - from nanobot import __version__ api_key = self.config.api_key or os.environ.get("KEENABLE_API_KEY", "") - # First-party API: identify honestly instead of the spoofed browser UA. - headers = {"Content-Type": "application/json", "User-Agent": f"nanobot/{__version__}"} + headers = { + "Content-Type": "application/json", + "User-Agent": self.user_agent, + "X-Keenable-Title": "nanobot", + } if api_key: headers["X-API-Key"] = api_key try: diff --git a/tests/tools/test_web_search_tool.py b/tests/tools/test_web_search_tool.py index 169295e2..4e46f90f 100644 --- a/tests/tools/test_web_search_tool.py +++ b/tests/tools/test_web_search_tool.py @@ -143,7 +143,8 @@ async def test_keenable_search(monkeypatch): async def mock_post(self, url, **kw): assert "keenable" in url assert kw["headers"]["X-API-Key"] == "keen-key" - assert kw["headers"]["User-Agent"].startswith("nanobot/") + assert kw["headers"]["User-Agent"] == "nanobot-search-test" + assert kw["headers"]["X-Keenable-Title"] == "nanobot" return _response(json={ "results": [{"title": "Keen", "url": "https://keenable.ai", "description": "short", "snippet": "longer excerpt"}] })