diff --git a/CHANGELOG.md b/CHANGELOG.md index 089668ae..0c6eb740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/website/configuration.md b/docs/website/configuration.md index a69b7187..32afc848 100644 --- a/docs/website/configuration.md +++ b/docs/website/configuration.md @@ -86,11 +86,11 @@ 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_` | 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_` | 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 @@ -98,7 +98,7 @@ The **Free** services need no account, card or key, but they are best effort: Br 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_` 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. diff --git a/include/agentty/domain/web_search_config.hpp b/include/agentty/domain/web_search_config.hpp index 425cabe9..6304c325 100644 --- a/include/agentty/domain/web_search_config.hpp +++ b/include/agentty/domain/web_search_config.hpp @@ -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; @@ -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"; diff --git a/third_party/mcp-cpp b/third_party/mcp-cpp index 3e1a57f5..8663d3c8 160000 --- a/third_party/mcp-cpp +++ b/third_party/mcp-cpp @@ -1 +1 @@ -Subproject commit 3e1a57f58898638d05b85770b563ac44aaa2029f +Subproject commit 8663d3c8bd5b25a8302db7d404e1a75fa25a46d1