TinySearch is a loadable TinyBus module for web search. tinysearch-bus is the
transport-free wire contract; tinysearch is the module and provider dispatch
layer. Hosts pass credentials in private module initialization configuration,
which TinyBus can refresh through reinitialization.
The bus serves ai.tinyhumans.tinysearch.Search at
/ai/tinyhumans/tinysearch/Search. ListTools returns currently available
model-facing declarations; ExecuteTool invokes a declared tool and returns
normalized results, citations, an optional answer, status, and optional provider
data. The contract version is 2.0.
The default presentation, roles, exposes one generic tool per capability
role that has at least one usable provider:
| Role | Tool | Arguments | Providers (default order) |
|---|---|---|---|
search |
web_search_tool |
query, max_results? (1-20), provider? |
exa, brave, tavily, parallel, querit, seltz, searxng, tinyfish |
answer |
web_answer_tool |
query, depth? (quick or deep), provider? |
gemini, gemini_deep_research, exa, parallel |
contents |
web_contents_tool |
urls (1-10), query?, provider? |
exa, tavily, parallel, tinyfish |
presentation.roles sets an ordered provider list per role; an absent or empty
list uses the default order above. The first usable provider answers. When it
fails with insufficient_balance, rate_limited, or provider_unavailable,
the next one is tried; invalid_arguments and unclassified failures stop the
call. The response carries role and fallback_from, the providers that
failed before the one that answered. An explicit provider argument pins the
call to that provider with no fallback; its schema enum lists the usable
providers. depth: "deep" prefers Gemini Deep Research, which serves only deep
answers, and falls back to the grounded answer providers; quick never uses it.
Parallel serves only quick answers, so a deep call skips it, and pinning
provider: "parallel" with depth: "deep" is rejected as invalid arguments.
all_tools exposes every available provider tool, one_provider exposes one
provider's tools, and router exposes one search tool that runs the chosen
provider's first tool, defaulting to the search role's first usable provider.
Failed ExecuteTool calls return a TinyBus method error. When the module can
classify the failure, the message starts with tinysearch.<code>: , and
tinysearch_bus::errors::code_of extracts the code:
| Code | Cause |
|---|---|
insufficient_balance |
HTTP 402 (or Tavily 432), or a backend insufficient-credits rejection |
rate_limited |
HTTP 429 |
provider_unavailable |
HTTP 408 or 5xx, transport failure, timeout, or an unreadable response |
invalid_arguments |
Arguments rejected by the schema or by the provider (HTTP 400/422) |
A direct provider requires its own credential unless it is explicitly keyless.
A backend route requires a backend credential. Search can be disabled globally.
Provider configuration includes a route, optional base URL, credential,
max_results, timeout_secs, and a SearXNG default_language.
Debug output redacts credentials.
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo build --all-targets --all-features
cargo test --all-featuresThe module crate is crates/tinysearch; its compiled cdylib can be loaded by
TinyBus. The contract crate is crates/tinysearch-bus and has no transport or
HTTP dependencies. vendor/tinybus is a pinned git submodule.
A provider appears only when the host configures it explicitly and enables it.
Exa and Gemini (BACKEND_PROVIDERS) can use route: "backend",
which requires a backend credential; a backend credential alone enables
nothing. backend.auth_mode is session (Authorization bearer) or api_key
(x-api-key); only backend requests receive x-sdk-name. Backend responses
are unwrapped from the {success, data} envelope.
Exa offers exa_search, exa_find_similar, exa_get_contents, and
exa_answer. Directly, they call Exa's /search, /findSimilar, /contents,
and /answer with x-api-key. On the backend route they call
/agent-integrations/exa/{search,findSimilar,contents,answer}. The backend's
search takes only {objective, searchQueries}, so the backend exa_search
schema accepts only query, sent as both; the other routes forward Exa's own
request bodies.
Gemini's gemini_agentic_search calls generateContent with Google Search
grounding: directly with x-goog-api-key, or through
/agent-integrations/gemini/models/{model}/generate-content, whose schema
limits model to the backend's allowlist (default gemini-3.8-flash). The
answer joins the candidate's text parts, and citations list the grounding
chunks that groundingSupports reference first, then the rest.
gemini_deep_research calls the direct asynchronous Interactions API; it can
be resumed with interaction_id when the bounded poll returns in_progress.
Direct Google calls never receive backend attribution or credentials.
TinyFish is bring-your-own-key only (the managed backend does not proxy it).
Every TinyFish call carries the user's key as X-API-Key: tinyfish_search
is GET https://api.search.tinyfish.ai (query, location?, language?,
page?), tinyfish_fetch is POST https://api.fetch.tinyfish.ai (urls
1-10, format?), and tinyfish_agent_run is
POST https://agent.tinyfish.ai/v1/automation/run (url, goal, optional
browser/proxy/vault fields), with its own 300 s timeout because a browser run
takes minutes; a FAILED run is a provider error. A base_url override
replaces the host for all three.
Parallel is bring-your-own-key only: there is no managed Parallel, so it is not
in BACKEND_PROVIDERS, and a route: "backend" entry leaves it unavailable
even with a backend credential. With route: "direct", its own credential and
an optional base_url, it offers parallel_search, parallel_extract,
parallel_chat, parallel_research, parallel_enrich, and parallel_dataset.
Requests send x-api-key to Parallel's /v1/search, /v1/extract,
/v1beta/chat/completions, /v1/tasks/runs, and /v1beta/findall/runs.
Research and enrichment create Task runs and dataset creates a FindAll run;
they return in_progress with run_id or findall_id in provider_data.
parallel_research_status, parallel_enrich_status, and
parallel_dataset_status take that ID, check the run, and fetch completed
results. Every call has a bounded timeout. The search schema lists Parallel's
current modes (turbo, fast, basic, advanced). In roles mode Parallel
serves search (query becomes the objective and the single entry of
search_queries; max_results becomes num_results), quick answers
(parallel_chat with the speed model and the query as the user message),
and contents (urls, with query as the objective and full_content on).
Brave web, news, image, and video search, Querit search, and Tavily search and
extract use route: "direct" with a provider credential. Their base_url can
target a controlled endpoint for testing. These providers do not have managed
backend routes; unsupported routes and tool arguments are rejected.
Seltz (seltz_search) also requires a direct credential. It posts to
https://api.seltz.ai/v1/search with x-api-key, supports domain and date
filters plus news scope, and returns up to 20 results. SearXNG
(searxng_search) is keyless and appears only when the host explicitly enables
it with a base_url and a direct route. It requests /search?format=json,
maps web to the general category, uses the configured default language,
and returns up to 50 results with their source names in provider_data.sources.
Every response bounds results, citations, snippets, answers, and retained provider metadata. Upstream error bodies are not returned or logged. Provider base URL overrides are intended for local testing and controlled deployments.