Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ All notable changes to agentty. Versions follow [SemVer](https://semver.org/).
keys, [[Home]]/[[End]], [[Backspace]] and [[Delete]] edit it, [[Enter]]
saves and [[Esc]] cancels. A title is one line of up to 60 bytes, and the
thread keeps its place in the list.
- **Serply as a web search service.** `serply-api` joins the API key
services in Settings > Web Search: Google results from
[Serply](https://serply.io) ([docs](https://serply.io/docs)), with
Country and Freshness, up to 10 results a search. Country defaults to
United States. The key comes from the key store or
`AGENTTY_WEB_SEARCH_KEY_SERPLY_API`.
## [0.9.27] - 2026-10-17

### Fixed
Expand Down
10 changes: 5 additions & 5 deletions docs/website/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@ The Smart Mode *feature* toggles (which layers run) live in the `Ctrl+S` overlay
|----------|---------|
| `AGENTTY_WEB_SEARCH` | `auto` (default), `on`, or `off`. `auto` advertises `web_search` and leaves the result count to the model, as agentty did before the Web Search pane. `on` applies your `AGENTTY_WEB_SEARCH_COUNT` and `_MAX`. `off` removes `web_search` from every request, from the tool list `agentty mcp-serve` offers to other MCP clients, and refuses any call to it (a replayed thread, a call injected by an ACP client). `0`/`false`/`no` mean `off`; `1`/`true`/`yes` mean `auto` (search available, the model picks the count). |
| `AGENTTY_WEB_SEARCH_COUNT` | Results when the model does not say how many. Applies only in `on`. Default `10`; range 1–20. |
| `AGENTTY_WEB_SEARCH_MAX` | Most results the model may ask for in one search; a larger request is clamped, and `0` or a negative count becomes `1`. Applies only in `on`. Default `20`; range 1–50. A search returns at most one result page from the service that answers, and each service caps it: 20 on Brave, Tavily, Serper and Perplexity, 30 on DuckDuckGo, 100 on Firecrawl and Exa. A larger request is cut to that cap. |
| `AGENTTY_WEB_SEARCH_EXCLUDE` | Domains never to return, space- or comma-separated (`pinterest.com w3schools.com`). Pasted URLs and `www.` are reduced to the host. Sent as `-site:` operators to Brave, DuckDuckGo and Serper, and as the service's own domain filter to Tavily, Firecrawl, Exa and Perplexity, so an excluded site never comes back and its slot goes to another result. Up to 20 sites go in the filter; any beyond that are added to the query as `-site:`. |
| `AGENTTY_WEB_SEARCH_PRIMARY`, `_SECONDARY`, `_FALLBACK` | Which service answers first, second and last; the first to succeed wins. A service id: `brave-free`, `ddg-free`, `tavily-free`, `firecrawl-free` (no account, card or key) or `brave-api`, `tavily-api`, `firecrawl-api`, `exa-api`, `serper-api`, `perplexity-api` (need an API key). `_SECONDARY` and `_FALLBACK` also take `none`. Defaults: `brave-free`, `ddg-free`, `tavily-free`. |
| `AGENTTY_WEB_SEARCH_MAX` | Most results the model may ask for in one search; a larger request is clamped, and `0` or a negative count becomes `1`. Applies only in `on`. Default `20`; range 1–50. A search returns at most one result page from the service that answers, and each service caps it: 10 on Serply, 20 on Brave, Tavily, Serper and Perplexity, 30 on DuckDuckGo, 100 on Firecrawl and Exa. A larger request is cut to that cap. |
| `AGENTTY_WEB_SEARCH_EXCLUDE` | Domains never to return, space- or comma-separated (`pinterest.com w3schools.com`). Pasted URLs and `www.` are reduced to the host. Sent as `-site:` operators to Brave, DuckDuckGo, Serper and Serply, and as the service's own domain filter to Tavily, Firecrawl, Exa and Perplexity, so an excluded site never comes back and its slot goes to another result. Up to 20 sites go in the filter; any beyond that are added to the query as `-site:`. |
| `AGENTTY_WEB_SEARCH_PRIMARY`, `_SECONDARY`, `_FALLBACK` | Which service answers first, second and last; the first to succeed wins. A service id: `brave-free`, `ddg-free`, `tavily-free`, `firecrawl-free` (no account, card or key) or `brave-api`, `tavily-api`, `firecrawl-api`, `exa-api`, `serper-api`, `serply-api`, `perplexity-api` (need an API key). `_SECONDARY` and `_FALLBACK` also take `none`. Defaults: `brave-free`, `ddg-free`, `tavily-free`. |
| `AGENTTY_WEB_SEARCH_LANGUAGE` | `auto` (default) or a two-letter code (`en`, `de`, `ja`, ...) sent to the services that take one: Tavily, Brave API, Serper, Perplexity. The others ignore it. |
| `AGENTTY_WEB_SEARCH_KEY_<SERVICE>` | An API key, taken from the environment instead of the key store: `AGENTTY_WEB_SEARCH_KEY_EXA_API`, `_BRAVE_API`, `_TAVILY_API`, `_FIRECRAWL_API`, `_SERPER_API`, `_PERPLEXITY_API`. Wins over a stored key and locks that row in the pane. Never written to disk. |
| `AGENTTY_WEB_SEARCH_KEY_<SERVICE>` | An API key, taken from the environment instead of the key store: `AGENTTY_WEB_SEARCH_KEY_EXA_API`, `_BRAVE_API`, `_TAVILY_API`, `_FIRECRAWL_API`, `_SERPER_API`, `_SERPLY_API`, `_PERPLEXITY_API`. Wins over a stored key and locks that row in the pane. Never written to disk. |

#### Choosing the services

The **Free** services need no account, card or key, but they are best effort: Brave Free and DuckDuckGo Free read a public results page, and Tavily Free and Firecrawl Free use the vendors' key-less tiers. Any of them can be blocked, rate-limited or withdrawn without notice. In particular, many networks are served a captcha by DuckDuckGo and Brave answers `429` after a burst of roughly ten searches. The report from a failed search names each service and why it did not answer (blocked, rate limited, quota used up, key refused, unreadable reply) rather than a bare error.

The **API key** services are the vendors' documented interfaces and do not depend on a page layout. Paste a key into the row under the slot that uses it and press Enter. It is stored encrypted (the OS keystore when `AGENTTY_USE_KEYSTORE` is set, otherwise a machine-bound sealed file) and is never written to `settings.json`; the row then reads `✓ key saved` and stays locked until *Remove key* forgets it. A key from `AGENTTY_WEB_SEARCH_KEY_<SERVICE>` reads `● key from` the variable and is locked for as long as it is set. A keyed service chosen with no key is reported as such in the search result and the next slot is tried.

Each service offers up to seven extra settings which appear under its slot, and none for a service that has none. Most are a list to pick from: freshness, region, safe search, spell correction, exact phrases, sort order, and so on. Two are typed: Brave API's *Goggle* takes the `https://` URL of a hosted Goggle (a rules file on GitHub, GitLab or a Gist, registered at [search.brave.com/goggles/create](https://search.brave.com/goggles/create)) that re-ranks Brave's results, and Firecrawl's *Location* takes a place such as `San Francisco,California,United States`. A typed value is saved when you leave the row, and only if it is one the service accepts; otherwise the row goes back to what was saved. Empty the row to clear it. Settings are saved per service in `settings.json` under `web_search.dials`, so a choice survives moving the service between slots. Some only take effect together with another: Tavily's *Country* applies with the General topic, its *Safe search* not with the fast depths, and *Language match* only when a language is set.
Each service offers up to seven extra settings which appear under its slot, and none for a service that has none. Most are a list to pick from: freshness, region, safe search, spell correction, exact phrases, sort order, and so on. Two are typed: Brave API's *Goggle* takes the `https://` URL of a hosted Goggle (a rules file on GitHub, GitLab or a Gist, registered at [search.brave.com/goggles/create](https://search.brave.com/goggles/create)) that re-ranks Brave's results, and Firecrawl's *Location* takes a place such as `San Francisco,California,United States`. A typed value is saved when you leave the row, and only if it is one the service accepts; otherwise the row goes back to what was saved. Empty the row to clear it. Settings are saved per service in `settings.json` under `web_search.dials`, so a choice survives moving the service between slots. Some only take effect together with another: Tavily's *Country* applies with the General topic, its *Safe search* not with the fast depths, and *Language match* only when a language is set. Serply's *Country* defaults to United States: with no country, Serply answers from whichever region is free, and the results can change country from one search to the next.

Kagi, Mojeek and Linkup are not offered yet: their request and response formats could not be verified against documentation or a real reply.

Expand Down
11 changes: 7 additions & 4 deletions include/agentty/domain/web_search_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ namespace agentty::web_search_cfg {
//
// What a search can actually RETURN is capped per service: mcp-cpp asks the
// service that answers for one result page and clamps the count to that
// service's documented maximum (WebSearchService::max_count: 20 for Brave, Tavily,
// Serper and Perplexity, 30 for DuckDuckGo, 100 for Firecrawl and Exa). So a
// service's documented maximum (WebSearchService::max_count: 10 for Serply, 20
// for Brave, Tavily, Serper and Perplexity, 30 for DuckDuckGo, 100 for Firecrawl
// and Exa). So a
// ceiling above a service's cap is accepted and clamped correctly, but yields
// no more than that service gives. The ceiling is a limit, not a promise.
inline constexpr int kCountMin = 1;
Expand All @@ -71,10 +72,12 @@ inline constexpr int kMaxCountDefault = 20;
// the two ever disagree. Primary cannot be empty; the other two can.
inline constexpr std::string_view kServiceIds =
"brave-free|ddg-free|tavily-free|firecrawl-free|"
"brave-api|tavily-api|firecrawl-api|exa-api|serper-api|perplexity-api";
"brave-api|tavily-api|firecrawl-api|exa-api|serper-api|serply-api|"
"perplexity-api";
inline constexpr std::string_view kOptionalServiceIds =
"none|brave-free|ddg-free|tavily-free|firecrawl-free|"
"brave-api|tavily-api|firecrawl-api|exa-api|serper-api|perplexity-api";
"brave-api|tavily-api|firecrawl-api|exa-api|serper-api|serply-api|"
"perplexity-api";
// ISO 639-1, the languages the keyed services document support for.
inline constexpr std::string_view kLanguages =
"auto|en|de|fr|es|it|pt|nl|sv|da|fi|pl|cs|ru|uk|tr|ja|ko|zh|ar|he|hi";
Expand Down
2 changes: 1 addition & 1 deletion third_party/mcp-cpp
Loading