Add Keenable Search tool node (keyless by default)#6542
Add Keenable Search tool node (keyless by default)#6542ilya-bogin-keenable wants to merge 2 commits into
Conversation
Keenable is a web search API built for AI agents. Unlike the other search nodes, it works without an API key by default (keyless public endpoint); providing a key uses the authenticated endpoint and lifts rate limits. - packages/components/nodes/tools/KeenableSearch/ (custom Tool via secureFetch, follows the SearXNG node shape) - realtime mode validated to require a key Usage: drop in the Keenable Search tool; no API key required.
There was a problem hiding this comment.
Code Review
This pull request introduces a new Keenable Search tool integration, allowing AI agents to perform web searches using the Keenable API. It includes the tool implementation, configuration options, and an SVG icon. Feedback on the implementation highlights an issue where the search results are formatted as a comma-separated list of JSON strings instead of a valid JSON array, which could cause parsing errors for LLMs. A correction was suggested to stringify the entire array of mapped results.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| return results | ||
| .slice(0, this.maxResults) | ||
| .filter((r) => r && r.url) | ||
| .map((r) => JSON.stringify({ title: r.title || '', link: r.url, snippet: r.description || '' })) | ||
| .toString() |
There was a problem hiding this comment.
The current implementation maps each result to a JSON string and then calls .toString() on the array. This produces a comma-separated list of JSON objects (e.g., {"title":"..."},{"title":"..."}) instead of a valid JSON array (e.g., [{"title":"..."},{"title":"..."}]). This violates the tool's description ("Output is a JSON array of results") and will cause parsing errors for LLMs or agents expecting valid JSON.
Instead, map the results to plain objects first, and then use JSON.stringify() on the entire array.
return JSON.stringify(
results
.slice(0, this.maxResults)
.filter((r) => r && r.url)
.map((r) => ({ title: r.title || '', link: r.url, snippet: r.description || '' }))
)_call mapped each result to a JSON string then .toString()'d the array, which emitted a comma-joined list rather than a JSON array. Build plain objects and JSON.stringify the whole array, matching the tool's documented output.
|
Good catch — fixed. |
Adds a Keenable Search tool node, alongside the existing
SearXNG/Tavily/Brave/Exa/Serper search nodes.
The difference from the other search nodes is that it doesn't require an API
key. By default it uses Keenable's keyless public endpoint, so it works out of
the box. Providing a key (in the node config) switches to the authenticated
endpoint and lifts the rate limits.
Changes
packages/components/nodes/tools/KeenableSearch/: a customTool(via theSSRF-safe
secureFetch), following the SearXNG node shape. Maps results to{title, link, snippet}JSON.mode(pro/realtime), base-URL override, max results.realtimemode is validated to require a key.Config
https://api.keenable.ai).Verified the node's search path against the public endpoint (keyless).