feat(web): register Keenable provider in WebUI, docs, and tests

Bring the Keenable search provider in line with the established
multi-file provider pattern so it surfaces everywhere the others do:

- settings_api.py: register in the web-search provider options so it
  appears in the WebUI settings dropdown (credential: api_key, optional).
- WebUI provider-brand: add keenable brand entry + brand test.
- docs/configuration.md: provider table row + config example.
- Harden _search_keenable: honor config.timeout and return explicit
  messages on HTTP status errors (429 / other), matching peer providers.
- Add env-key and HTTP-error tests; add the websocket settings whitelist
  assertion.

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 4f6e5e9cb8
commit fa3e902ee8
7 changed files with 57 additions and 2 deletions
+6 -2
View File
@@ -501,7 +501,7 @@ class WebSearchTool(Tool):
"https://api.keenable.ai/v1/search",
headers=headers,
json={"query": query},
timeout=15.0,
timeout=float(self.config.timeout),
)
r.raise_for_status()
items = [
@@ -513,8 +513,12 @@ class WebSearchTool(Tool):
for x in r.json().get("results", [])
]
return _format_results(query, items, n)
except httpx.HTTPStatusError as e:
if e.response.status_code == 429:
return "Error: Keenable search rate limited. Try again later or reduce search frequency."
return f"Error: Keenable search failed ({e.response.status_code}): {e}"
except Exception as e:
return f"Error: {e}"
return f"Error: Keenable search failed: {e}"
async def _search_searxng(self, query: str, n: int) -> str:
base_url = (self.config.base_url or os.environ.get("SEARXNG_BASE_URL", "")).strip()
+1
View File
@@ -90,6 +90,7 @@ _WEB_SEARCH_PROVIDER_OPTIONS: tuple[dict[str, str], ...] = (
{"name": "olostep", "label": "Olostep", "credential": "api_key"},
{"name": "bocha", "label": "Bocha", "credential": "api_key"},
{"name": "volcengine", "label": "Volcengine Search", "credential": "api_key"},
{"name": "keenable", "label": "Keenable", "credential": "api_key"},
)
_WEB_SEARCH_PROVIDER_BY_NAME = {
provider["name"]: provider for provider in _WEB_SEARCH_PROVIDER_OPTIONS