Documentation
¶
Overview ¶
Package search provides reference SearchProvider adapters that travel with the importable engine core. fakesearch.go is the deterministic, OFFLINE fake used by the agent loop's tests (and any engine consumer's tests) to exercise the WebSearch tool without any network access — the mockllm of web search: scripted, deterministic, safe for concurrent use.
It lives under engine/adapter/* so core test files may import it under the layering rules (the depguard core rules exclude $test; the DAG test reads non-test imports only). It is stdlib-only.
Package search holds the heavy-adapter implementations of the engine tool.SearchProvider seam. httpsearch.go is a VENDOR-NEUTRAL HTTP JSON search adapter behind operator config (a base URL + optional API key/header), OFF by default — the harness's first outbound-network capability, so it is conservative:
- It carries its OWN per-call timeout (honoring ctx) AND a concurrency limiter, because the read-parallel dispatcher fans out N concurrent WebSearch calls per turn → N egress requests. Egress is bounded HERE, never in the dispatcher or the tool.
- It passes the query VERBATIM. Secret-scanning of the query is the guardrails layer's responsibility, not this adapter's — it must never inspect/rewrite the query, and it never sends the configured secret as part of the query.
This file graduated from internal/adapter/search into the importable engine module (issue #363) — it sits alongside engine/adapter/search's fakesearch.go (the offline reference fake) so engine consumers get working web search by import.
Index ¶
Constants ¶
const MaxOutputBytes = 25_000
MaxOutputBytes caps the byte length of a single tool's textual result. It is the fstools output cap; tools append a truncation marker (see truncate) when they trim to it.
mirrors internal/adapter/toolkit.MaxOutputBytes EXACTLY — keep byte-identical; carried because engine must not import the root module (fstools precedent, #269).
const TruncationMarker = "\n... [output truncated: exceeded 25000 bytes]"
TruncationMarker is the suffix truncate appends when it trims a body to the byte cap.
mirrors internal/adapter/toolkit.TruncationMarker EXACTLY — keep byte-identical; carried because engine must not import the root module (fstools precedent, #269).
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BackendDown ¶
type BackendDown struct{}
BackendDown is the SearchProvider the composition root installs when an operator CONFIGURED a backend but its construction FAILED (a bad --websearch-url / SEARXNG_URL, or invalid Brave config). It is distinct from the engine's fakesearch.Unavailable: a construction failure means "a backend was intended but is not usable" — backend-down semantics — NOT the operator kill switch. Every Search returns tool.ErrSearchBackendDown, so the WebSearch tool surfaces the backend-down message (naming the upgrade path) rather than the "disabled by operator" message, which would mis-attribute the cause.
func (BackendDown) Search ¶
func (BackendDown) Search(_ context.Context, _ tool.SearchQuery) ([]tool.SearchResult, error)
Search always returns tool.ErrSearchBackendDown.
type ExaConfig ¶
type ExaConfig struct {
// Endpoint overrides the Exa MCP endpoint (default https://mcp.exa.ai/mcp).
Endpoint string
// APIKey, when set (from EXA_API_KEY), upgrades to the paid tier: it is
// appended to the endpoint as the ?exaApiKey= query parameter (Exa's documented
// scheme). The assembled URL is SECRET-BEARING and is NEVER logged or passed to
// diagnostics — only the fixed base endpoint + a boolean "paid tier" flag are.
APIKey string
// Timeout bounds one outbound call across all three POSTs (default 10s). Always
// also honored via ctx.
Timeout time.Duration
// MaxConcurrent bounds simultaneous in-flight egress across the process
// (default 4). <=0 uses the default.
MaxConcurrent int
// HTTPClient is an injectable transport (the test seam: an httptest.Server
// client). nil uses a default client with Timeout.
HTTPClient *http.Client
}
ExaConfig configures the Exa anonymous-default search provider. Only the zero-value is needed for the anonymous default; the fields tune the endpoint, the optional paid-tier key, and the safety envelope.
type ExaProvider ¶
type ExaProvider struct {
// contains filtered or unexported fields
}
ExaProvider is a tool.SearchProvider over Exa's anonymous streamable-HTTP MCP endpoint. It is safe for concurrent use; egress is bounded by an internal semaphore and a per-call timeout.
func NewExaProvider ¶
func NewExaProvider(cfg ExaConfig) *ExaProvider
NewExaProvider constructs an ExaProvider from cfg, applying defaults. It never returns an error: the anonymous default needs no configuration, and a missing key simply leaves the client on the free tier.
func (*ExaProvider) BaseEndpoint ¶
func (p *ExaProvider) BaseEndpoint() string
BaseEndpoint returns the fixed, LOG-SAFE base endpoint (never the secret-bearing request URL). PaidTier reports whether an API key is in use. Composition uses these to narrate without ever logging the key.
func (*ExaProvider) PaidTier ¶
func (p *ExaProvider) PaidTier() bool
PaidTier reports whether an EXA_API_KEY is configured (paid tier).
func (*ExaProvider) Search ¶
func (p *ExaProvider) Search(ctx context.Context, q tool.SearchQuery) ([]tool.SearchResult, error)
Search performs one Exa web search via the three-POST MCP handshake. Any closed/ auth/rate-limit/5xx status, transport error, JSON-RPC error object, or malformed/empty result is mapped to tool.ErrSearchBackendDown (wrapped with context) — the mandatory-degradation signal.
type Fake ¶
type Fake struct {
// contains filtered or unexported fields
}
Fake is a deterministic, scripted tool.SearchProvider for offline tests. It returns a fixed result set (or a scripted error) and records the last SearchQuery it received so a test can assert site/freshness/limit passthrough. It is safe for concurrent use (the read-parallel dispatcher fans out concurrent WebSearch calls).
func NewFake ¶
func NewFake(results ...tool.SearchResult) *Fake
NewFake constructs a Fake scripted to return results (and no error). Use the option-style setters (WithError / WithResults) or the constructor variants for other cases.
func NewFakeError ¶
NewFakeError constructs a Fake scripted to return err from every Search call (e.g. tool.ErrSearchUnavailable to drive the not-configured path, or an arbitrary backend fault).
func (*Fake) LastQuery ¶
func (f *Fake) LastQuery() tool.SearchQuery
LastQuery returns the most recent SearchQuery the fake received. Safe for concurrent use.
func (*Fake) Search ¶
func (f *Fake) Search(_ context.Context, q tool.SearchQuery) ([]tool.SearchResult, error)
Search returns the scripted results (or the scripted error), recording q so a test can later assert what the tool passed through.
type HTTPConfig ¶
type HTTPConfig struct {
// BaseURL is the search endpoint (e.g. a SearXNG "/search" URL or any generic
// JSON search API). Required.
BaseURL string
// APIKey, when set, is sent in the AuthHeader (default "Authorization" with a
// "Bearer " prefix). It is NEVER placed in the query string or logged.
APIKey string
// AuthHeader overrides the header the API key is sent in (default
// "Authorization"). When it is "Authorization" the value is "Bearer <key>";
// for any other header the raw key is sent (e.g. "X-API-Key").
AuthHeader string
// QueryParam is the URL query parameter the search string is placed in (default
// "q"). Generic over SearXNG ("q") and most JSON search APIs.
QueryParam string
// Method is the HTTP method ("GET" default, or "POST"). For POST the query
// params are sent as a form-encoded body.
Method string
// Timeout bounds one request (default 10s). Always also honored via ctx.
Timeout time.Duration
// MaxConcurrent bounds simultaneous egress (default 4). <=0 uses the default.
MaxConcurrent int
// HTTPClient is an injectable transport (the test seam: an httptest.Server
// client). nil uses a default client with Timeout.
HTTPClient *http.Client
}
HTTPConfig is the operator configuration for the HTTP JSON search adapter. Only BaseURL is required; the rest tune auth, the request method, and the safety envelope.
type HTTPProvider ¶
type HTTPProvider struct {
// contains filtered or unexported fields
}
HTTPProvider is a vendor-neutral tool.SearchProvider over a JSON HTTP endpoint. It is safe for concurrent use; egress is bounded by an internal semaphore and a per-call timeout.
func NewHTTPProvider ¶
func NewHTTPProvider(cfg HTTPConfig) (*HTTPProvider, error)
NewHTTPProvider constructs an HTTPProvider from cfg, applying defaults. It returns an error if BaseURL is empty (a misconfigured adapter must fail loudly, not pass silently — the composition root only builds this when the operator set the URL).
func (*HTTPProvider) Search ¶
func (p *HTTPProvider) Search(ctx context.Context, q tool.SearchQuery) ([]tool.SearchResult, error)
Search performs one outbound JSON search. It bounds egress with the internal semaphore + a per-call timeout, sends the query VERBATIM (never the secret in the query), parses the generic JSON response, and returns up to q.Limit results.
type Unavailable ¶
type Unavailable struct{}
Unavailable is the honest "no search backend configured" SearchProvider: every Search returns tool.ErrSearchUnavailable. The composition root installs it when no operator search backend is configured, so the always-present WebSearch tool surfaces an honest "ask the operator" message rather than vanishing from the catalog (the silent-disable aversion). It is the SearchProvider analogue of the no-shell CommandRunner.
func (Unavailable) Search ¶
func (Unavailable) Search(_ context.Context, _ tool.SearchQuery) ([]tool.SearchResult, error)
Search always returns tool.ErrSearchUnavailable.
type WebSearchTool ¶
type WebSearchTool struct {
// contains filtered or unexported fields
}
WebSearchTool is the read-only web-search core tool. It holds a tool.SearchProvider injected by the composition root; a nil provider (or one that returns tool.ErrSearchUnavailable) yields an honest "no search provider configured" model-facing message rather than vanishing from the catalog (the silent-disable aversion). It is read-only (outward read, no mutation), so it runs in the loop's read-parallel batch alongside Read/Grep/WebFetch.
func NewWebSearchTool ¶
func NewWebSearchTool(provider tool.SearchProvider) WebSearchTool
NewWebSearchTool constructs a WebSearchTool over provider. A nil provider is tolerated and behaves like a not-configured backend (honest model-facing message), so the tool is always registrable.
func (WebSearchTool) Execute ¶
func (t WebSearchTool) Execute(ctx context.Context, in session.ToolCall, _ tool.Environment) (session.ToolResult, error)
Execute parses+validates the call, clamps the limit, invokes the provider, and formats bounded, FENCED, source-attributed results. Recoverable failures (a missing query, a not-configured provider, a backend fault, no results) are returned as model-facing tool results (NewToolResult / NewToolError), never a Go error — the Go error return is reserved for harness-level faults, of which this tool has none.
func (WebSearchTool) ReadOnly ¶
func (WebSearchTool) ReadOnly() bool
ReadOnly reports that WebSearch is a read-only operation (an outward read, no state mutation), so it slots into the read-parallel dispatch path.
func (WebSearchTool) Spec ¶
func (WebSearchTool) Spec() tool.ToolSpec
Spec returns the model-facing specification of the WebSearch tool.