Class: Nexo::Tools::WebSearch
- Inherits:
-
RubyLLM::Tool
- Object
- RubyLLM::Tool
- Nexo::Tools::WebSearch
- Defined in:
- lib/nexo/tools/web_search.rb
Overview
Vendor-neutral web search, gated by the :search capability (denied by
default, like +:fetch+/+:shell+). Follows the Tools::Fetch shape exactly:
authorize first, act, and rescue Permissions::Denied into { error: ... }
so the loop never crashes.
Nexo ships NO search provider. The query is delegated to a host-injected
backend responding to #search(query, **opts) that returns an Enumerable
of {title:, url:, snippet:} rows (Hashes or objects responding to #to_h).
Results are count- and snippet-capped so untrusted provider output can't blow
the context window. It pairs with Tools::Fetch: search finds URLs, fetch
reads one.
Backend results are UNTRUSTED model input (prompt-injection risk in snippets); the backend is trust-bearing and runs in the host process, not the sandbox.
Return shape: success is { results: [{title:, url:, snippet:}, …] } (≤8
rows); any denial or error is { error: <message> }.
Constant Summary collapse
- MAX_RESULTS =
At most this many result rows are returned.
8- MAX_SNIPPET =
Each result snippet is truncated to this many characters.
300
Instance Method Summary collapse
-
#execute(query:) ⇒ Object
Order of operations (every denial/error returns
{ error: }, never raises into the loop): capability gate → delegate to the backend (query-only) → normalize and cap. -
#initialize(sandbox:, permissions:, backend:) ⇒ WebSearch
constructor
permissionsgates the:searchcapability;backendis the injected, host-owned provider.
Constructor Details
#initialize(sandbox:, permissions:, backend:) ⇒ WebSearch
permissions gates the :search capability; backend is the injected,
host-owned provider. sandbox: is accepted for signature parity with the
other tools even though search runs in the host process, not the sandbox.
34 35 36 37 38 |
# File 'lib/nexo/tools/web_search.rb', line 34 def initialize(sandbox:, permissions:, backend:) @permissions = @backend = backend super() end |
Instance Method Details
#execute(query:) ⇒ Object
Order of operations (every denial/error returns { error: }, never raises
into the loop): capability gate → delegate to the backend (query-only) →
normalize and cap. The gate runs before the backend, so a denied search
never touches it.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/nexo/tools/web_search.rb', line 44 def execute(query:) @permissions.(:search, query) rows = Array(@backend.search(query)).first(MAX_RESULTS) {results: rows.map { |r| normalize(r) }} rescue Permissions::Denied => e {error: e.} rescue Nexo::ApprovalRequired # Durable approval (Spec 16): the :approve gate on authorize!(:search, query) # must PAUSE the run, not become a tool error. Re-raise past the broad # rescue below so run_agent can catch it and suspend. (Denied above still # becomes {error:} — that is the intended deny contract.) raise rescue => e {error: "search failed: #{e.}"} end |