fix(web): require API key for Keenable, fall back to DuckDuckGo

Manual testing against the live API showed the Keenable REST endpoint
(/v1/search) returns 401 without a key — the keyless "free tier" applies
only to the CLI, not the HTTP API. Treat Keenable like every other
key-based provider: fall back to DuckDuckGo when no key is configured,
and drop the now-inaccurate free-tier wording from the docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilya Gusev
2026-06-18 00:08:52 +08:00
committed by Xubin Ren
co-authored by Claude Opus 4.8
parent 092b07c7aa
commit d7280da17c
3 changed files with 23 additions and 19 deletions
+6 -3
View File
@@ -318,7 +318,8 @@ class WebSearchTool(Tool):
)
return "volcengine" if api_key else "duckduckgo"
if provider == "keenable":
return "keenable" # free tier works without a key; never fall back
api_key = self.config.api_key or os.environ.get("KEENABLE_API_KEY", "")
return "keenable" if api_key else "duckduckgo"
return provider
@property
@@ -490,13 +491,15 @@ class WebSearchTool(Tool):
async def _search_keenable(self, query: str, n: int) -> str:
api_key = self.config.api_key or os.environ.get("KEENABLE_API_KEY", "")
if not api_key:
logger.warning("KEENABLE_API_KEY not set, falling back to DuckDuckGo")
return await self._search_duckduckgo(query, n)
headers = {
"Content-Type": "application/json",
"User-Agent": self.user_agent,
"X-Keenable-Title": "nanobot",
"X-API-Key": api_key,
}
if api_key:
headers["X-API-Key"] = api_key
try:
async with httpx.AsyncClient(proxy=self.proxy) as client:
r = await client.post(