v1.11.8: web_search + web_fetch tools via SearXNG

Adds two new tools registered through the existing ALL_TOOLS registry:
  - web_search hits SearXNG's JSON API (Fathom, internal Tailscale URL,
    no auth) and returns top results
  - web_fetch retrieves a URL's text content, gated by isPublicUrl
    (url_guard.ts) which blocks loopback / RFC1918 / Tailscale CGNAT /
    link-local / .local / .internal / non-http schemes

Both tools are opt-in via the existing session.web_search_enabled flag
(plumbed in v1.9, activated here). Default off. UI labels updated to
"Enable web search and fetch" / "Web search and fetch" since fetch joins
the same store. Counts against the v1.8.2 per-turn budget; covered by
the v1.11.6 doom-loop guard.

Native Node 20 fetch — no new prod dep. HTML stripping via regex (script
and style content elided wholesale). 5MB body cap, 15s fetch timeout,
8000-char default output, 32000-char cap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 21:38:02 +00:00
parent 863452ae07
commit 2fdbb05477
10 changed files with 709 additions and 6 deletions

View File

@@ -6,6 +6,8 @@ import { isSecretPath, SecretBlockedError, filterSecretEntries } from './secret_
import { grep as fileOpsGrep, findFiles as fileOpsFindFiles } from './file_ops.js';
import { getGitMeta } from './git_meta.js';
import { findSkills, getSkillBody, getSkillResource } from './skills.js';
import { webSearch } from './web_search.js';
import { webFetch } from './web_fetch.js';
const MAX_FILE_BYTES = 5 * 1024 * 1024;
const DEFAULT_VIEW_LINES = 200;
@@ -522,6 +524,11 @@ export const ALL_TOOLS: ReadonlyArray<ToolDef<unknown>> = [
skillUse as ToolDef<unknown>,
skillResource as ToolDef<unknown>,
askUserInput as ToolDef<unknown>,
// v1.11.8: web tools. Gated per-chat via session.web_search_enabled
// (with project default fallback) — see effectiveTools filter in
// services/inference.ts.
webSearch as ToolDef<unknown>,
webFetch as ToolDef<unknown>,
];
// v1.8.2: forward-compatible read-only whitelist. An agent whose `tools` is
@@ -542,6 +549,11 @@ export const READ_ONLY_TOOL_NAMES = [
'skill_use',
'skill_resource',
'ask_user_input',
// v1.11.8: web tools don't mutate project state; counted as read-only
// for the budget-tier calculation (BUDGET_READ_ONLY=30) when an agent's
// toolset is fully contained in this list.
'web_search',
'web_fetch',
] as const;
export const TOOLS_BY_NAME: Record<string, ToolDef<unknown>> = Object.fromEntries(