import "./paths-D4_LjrY7.js";
import "./utils-BnlaT0eO.js";
import "./theme-CG-K7m8u.js";
import "./subsystem-ocBR131w.js";
import "./runtime-CRvfzclG.js";
import "./exec-CZr_EJHt.js";
import "./agent-scope-CVnlY0oG.js";
import "./model-selection-p6J3l4qF.js";
import "./github-copilot-token-CjSKohyA.js";
import { t as formatCliCommand } from "./command-format-Dh6J8CvS.js";
import { n as normalizeSecretInput } from "./normalize-secret-input-MYQBY-k5.js";
import "./emitter-oHo8y9ow.js";
import "./sqlite-wYNyDR1B.js";
import "./reliability-Dl7hbRzF.js";
import "./boolean-DGGu1sGA.js";
import "./env-CvUd308i.js";
import "./validation-p1IK70TM.js";
import { o as loadConfig } from "./config-DJ3A5x_1.js";
import "./manifest-registry-U2EDFVP4.js";
import "./redact-DcYCKU6u.js";
import "./chrome-BQkAVfz8.js";
import "./tailscale-CcUMoDkd.js";
import "./auth-BxKMeEw-.js";
import { i as resolveBrowserConfig } from "./server-context-s_zNpSdX.js";
import "./routes-CT0DmotX.js";
import "./errors-DbYSuCgv.js";
import "./paths-Ct4sqLyX.js";
import "./ssrf-tneKFjA6.js";
import "./image-ops-C0D77XHD.js";
import "./store-Cdm3Z5cQ.js";
import "./ports-DvLDsgl0.js";
import "./trash-BRBQoy9m.js";
import "./tool-images-CRPqucMe.js";
import { t as wrapToolWithTracking } from "./tool-tracking-BeyzFfAx.js";
import { a as jsonResult, o as readNumberParam, u as readStringParam } from "./common-oaZdjdal.js";
import { o as wrapWebContent } from "./external-content-CNcZgk3W.js";
import { a as browserSnapshot, m as browserNavigate, o as browserStart, s as browserStatus } from "./client-DCieELsZ.js";
import "./control-service-QcAZen75.js";
import "./dispatcher-DjXns0ia.js";
import { a as readResponseText, c as withTimeout, i as readCache, l as writeCache, n as DEFAULT_TIMEOUT_SECONDS, o as resolveCacheTtlMs, r as normalizeCacheKey, s as resolveTimeoutSeconds, t as DEFAULT_CACHE_TTL_MINUTES } from "./web-shared-B3KVe77v.js";
import { Type } from "@sinclair/typebox";

//#region src/agents/tools/web/web-search-browser.ts
/**
* Browser-based DuckDuckGo search fallback for environments without a search API key.
* Uses the headless browser to navigate DuckDuckGo Lite and parse results.
*/
const PROFILE = "minion";
const DDG_LITE_BASE = "https://lite.duckduckgo.com/lite/";
const SNAPSHOT_MAX_CHARS = 3e4;
const NAVIGATE_SETTLE_MS = 1500;
const cache = /* @__PURE__ */ new Map();
/** Check whether the browser fallback can be used (browser.enabled in config). */
function isBrowserSearchAvailable() {
	try {
		const cfg = loadConfig();
		return resolveBrowserConfig(cfg.browser, cfg).enabled;
	} catch {
		return false;
	}
}
/** Ensure the minion browser profile is running, starting it if needed. */
async function ensureBrowserRunning() {
	try {
		if ((await browserStatus(void 0, { profile: PROFILE })).running) return;
	} catch {}
	await browserStart(void 0, { profile: PROFILE });
	await new Promise((r) => setTimeout(r, 1e3));
}
/**
* Parse DuckDuckGo Lite AI snapshot text into structured search results.
*
* DDG Lite renders plain HTML tables. The AI snapshot typically produces
* numbered text blocks like:
*   1. Result Title
*      https://example.com/path
*      Description snippet...
*
* We split on numbered lines and extract title, URL, and description.
*/
function parseDuckDuckGoLiteSnapshot(text, maxResults) {
	const results = [];
	const entryRegex = /(?:^|\n)\s*\d+\.\s+(.+?)(?=\n\s*\d+\.\s+|\s*$)/gs;
	let match;
	while ((match = entryRegex.exec(text)) !== null) {
		if (results.length >= maxResults) break;
		const content = match[1];
		if (!content) continue;
		const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
		if (lines.length === 0) continue;
		const title = lines[0];
		const urlMatch = content.match(/https?:\/\/[^\s)\]]+/);
		const url = urlMatch ? urlMatch[0] : "";
		const description = lines.slice(1).filter((l) => l !== url).join(" ").trim();
		let siteName = "";
		try {
			siteName = new URL(url).hostname.replace(/^www\./, "");
		} catch {}
		if (title && url) results.push({
			title: wrapWebContent(title, "web_search"),
			url,
			description: description ? wrapWebContent(description, "web_search") : "",
			siteName
		});
	}
	return results;
}
/**
* Run a web search via DuckDuckGo Lite using the headless browser.
*/
async function runBrowserSearch(opts) {
	const { query, count, cacheTtlMs } = opts;
	const cacheKey = normalizeCacheKey(`browser:${query}:${count}`);
	const cached = readCache(cache, cacheKey);
	if (cached) return cached.value;
	const t0 = Date.now();
	await ensureBrowserRunning();
	await browserNavigate(void 0, {
		url: `${DDG_LITE_BASE}?q=${encodeURIComponent(query)}`,
		profile: PROFILE
	});
	await new Promise((r) => setTimeout(r, NAVIGATE_SETTLE_MS));
	const snapshot = await browserSnapshot(void 0, {
		format: "ai",
		maxChars: SNAPSHOT_MAX_CHARS,
		profile: PROFILE
	});
	const snapshotText = snapshot.ok && snapshot.format === "ai" ? snapshot.snapshot : "";
	const tookMs = Date.now() - t0;
	const results = parseDuckDuckGoLiteSnapshot(snapshotText, count);
	const response = {
		query,
		provider: "browser",
		count: results.length,
		tookMs,
		results,
		...results.length === 0 ? { rawContent: snapshotText.slice(0, 5e3) } : {},
		externalContent: {
			untrusted: true,
			source: "web_search",
			provider: "browser",
			wrapped: true
		}
	};
	writeCache(cache, cacheKey, response, cacheTtlMs);
	return response;
}

//#endregion
//#region src/agents/tools/web/web-search.ts
const DEFAULT_SEARCH_COUNT = 5;
const MAX_SEARCH_COUNT = 10;
const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
const DEFAULT_PERPLEXITY_BASE_URL = "https://openrouter.ai/api/v1";
const PERPLEXITY_DIRECT_BASE_URL = "https://api.perplexity.ai";
const DEFAULT_PERPLEXITY_MODEL = "perplexity/sonar-pro";
const PERPLEXITY_KEY_PREFIXES = ["pplx-"];
const OPENROUTER_KEY_PREFIXES = ["sk-or-"];
const XAI_API_ENDPOINT = "https://api.x.ai/v1/responses";
const DEFAULT_GROK_MODEL = "grok-4-1-fast";
const SEARCH_CACHE = /* @__PURE__ */ new Map();
const BRAVE_FRESHNESS_SHORTCUTS = new Set([
	"pd",
	"pw",
	"pm",
	"py"
]);
const BRAVE_FRESHNESS_RANGE = /^(\d{4}-\d{2}-\d{2})to(\d{4}-\d{2}-\d{2})$/;
const WebSearchSchema = Type.Object({
	query: Type.String({ description: "Search query string." }),
	count: Type.Optional(Type.Number({
		description: "Number of results to return (1-10).",
		minimum: 1,
		maximum: MAX_SEARCH_COUNT
	})),
	country: Type.Optional(Type.String({ description: "2-letter country code for region-specific results (e.g., 'DE', 'US', 'ALL'). Default: 'US'." })),
	search_lang: Type.Optional(Type.String({ description: "ISO language code for search results (e.g., 'de', 'en', 'fr')." })),
	ui_lang: Type.Optional(Type.String({ description: "ISO language code for UI elements." })),
	freshness: Type.Optional(Type.String({ description: "Filter results by discovery time. Brave supports 'pd', 'pw', 'pm', 'py', and date range 'YYYY-MM-DDtoYYYY-MM-DD'. Perplexity supports 'pd', 'pw', 'pm', and 'py'." }))
});
function extractGrokContent(data) {
	for (const output of data.output ?? []) {
		if (output.type !== "message") continue;
		for (const block of output.content ?? []) if (block.type === "output_text" && typeof block.text === "string" && block.text) {
			const urls = (block.annotations ?? []).filter((a) => a.type === "url_citation" && typeof a.url === "string").map((a) => a.url);
			return {
				text: block.text,
				annotationCitations: [...new Set(urls)]
			};
		}
	}
	return {
		text: typeof data.output_text === "string" ? data.output_text : void 0,
		annotationCitations: []
	};
}
function resolveSearchConfig(cfg) {
	const search = cfg?.tools?.web?.search;
	if (!search || typeof search !== "object") return;
	return search;
}
function resolveSearchEnabled(params) {
	if (typeof params.search?.enabled === "boolean") return params.search.enabled;
	if (params.sandboxed) return true;
	return true;
}
function resolveSearchApiKey(search) {
	const fromConfig = search && "apiKey" in search && typeof search.apiKey === "string" ? normalizeSecretInput(search.apiKey) : "";
	const fromEnv = normalizeSecretInput(process.env.BRAVE_API_KEY);
	return fromConfig || fromEnv || void 0;
}
function missingSearchKeyPayload(provider) {
	if (provider === "perplexity") return {
		error: "missing_perplexity_api_key",
		message: "web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.",
		docs: "https://docs.openclaw.ai/tools/web"
	};
	if (provider === "gemini") return {
		error: "missing_google_api_key",
		message: "web_search (gemini) needs a Google API key. Set GOOGLE_API_KEY in the Gateway environment, or configure tools.web.search.gemini.apiKey.",
		docs: "https://docs.openclaw.ai/tools/web"
	};
	if (provider === "grok") return {
		error: "missing_xai_api_key",
		message: "web_search (grok) needs an xAI API key. Set XAI_API_KEY in the Gateway environment, or configure tools.web.search.grok.apiKey.",
		docs: "https://docs.openclaw.ai/tools/web"
	};
	return {
		error: "missing_brave_api_key",
		message: `web_search needs a Brave Search API key. Run \`${formatCliCommand("openclaw configure --section web")}\` to store it, or set BRAVE_API_KEY in the Gateway environment.`,
		docs: "https://docs.openclaw.ai/tools/web"
	};
}
function resolveSearchProvider(search) {
	const raw = search && "provider" in search && typeof search.provider === "string" ? search.provider.trim().toLowerCase() : "";
	if (raw === "perplexity") return "perplexity";
	if (raw === "gemini") return "gemini";
	if (raw === "grok") return "grok";
	if (raw === "brave") return "brave";
	if (normalizeApiKey(process.env.BRAVE_API_KEY)) return "brave";
	if (normalizeApiKey(process.env.GOOGLE_API_KEY)) return "gemini";
	return "brave";
}
function resolvePerplexityConfig(search) {
	if (!search || typeof search !== "object") return {};
	const perplexity = "perplexity" in search ? search.perplexity : void 0;
	if (!perplexity || typeof perplexity !== "object") return {};
	return perplexity;
}
function resolvePerplexityApiKey(perplexity) {
	const fromConfig = normalizeApiKey(perplexity?.apiKey);
	if (fromConfig) return {
		apiKey: fromConfig,
		source: "config"
	};
	const fromEnvPerplexity = normalizeApiKey(process.env.PERPLEXITY_API_KEY);
	if (fromEnvPerplexity) return {
		apiKey: fromEnvPerplexity,
		source: "perplexity_env"
	};
	const fromEnvOpenRouter = normalizeApiKey(process.env.OPENROUTER_API_KEY);
	if (fromEnvOpenRouter) return {
		apiKey: fromEnvOpenRouter,
		source: "openrouter_env"
	};
	return {
		apiKey: void 0,
		source: "none"
	};
}
function normalizeApiKey(key) {
	return normalizeSecretInput(key);
}
function inferPerplexityBaseUrlFromApiKey(apiKey) {
	if (!apiKey) return;
	const normalized = apiKey.toLowerCase();
	if (PERPLEXITY_KEY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return "direct";
	if (OPENROUTER_KEY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return "openrouter";
}
function resolvePerplexityBaseUrl(perplexity, apiKeySource = "none", apiKey) {
	const fromConfig = perplexity && "baseUrl" in perplexity && typeof perplexity.baseUrl === "string" ? perplexity.baseUrl.trim() : "";
	if (fromConfig) return fromConfig;
	if (apiKeySource === "perplexity_env") return PERPLEXITY_DIRECT_BASE_URL;
	if (apiKeySource === "openrouter_env") return DEFAULT_PERPLEXITY_BASE_URL;
	if (apiKeySource === "config") {
		const inferred = inferPerplexityBaseUrlFromApiKey(apiKey);
		if (inferred === "direct") return PERPLEXITY_DIRECT_BASE_URL;
		if (inferred === "openrouter") return DEFAULT_PERPLEXITY_BASE_URL;
	}
	return DEFAULT_PERPLEXITY_BASE_URL;
}
function resolvePerplexityModel(perplexity) {
	return (perplexity && "model" in perplexity && typeof perplexity.model === "string" ? perplexity.model.trim() : "") || DEFAULT_PERPLEXITY_MODEL;
}
function isDirectPerplexityBaseUrl(baseUrl) {
	const trimmed = baseUrl.trim();
	if (!trimmed) return false;
	try {
		return new URL(trimmed).hostname.toLowerCase() === "api.perplexity.ai";
	} catch {
		return false;
	}
}
function resolvePerplexityRequestModel(baseUrl, model) {
	if (!isDirectPerplexityBaseUrl(baseUrl)) return model;
	return model.startsWith("perplexity/") ? model.slice(11) : model;
}
function resolveGrokConfig(search) {
	if (!search || typeof search !== "object") return {};
	const grok = "grok" in search ? search.grok : void 0;
	if (!grok || typeof grok !== "object") return {};
	return grok;
}
function resolveGrokApiKey(grok) {
	const fromConfig = normalizeApiKey(grok?.apiKey);
	if (fromConfig) return fromConfig;
	return normalizeApiKey(process.env.XAI_API_KEY) || void 0;
}
function resolveGrokModel(grok) {
	return (grok && "model" in grok && typeof grok.model === "string" ? grok.model.trim() : "") || DEFAULT_GROK_MODEL;
}
function resolveGrokInlineCitations(grok) {
	return grok?.inlineCitations === true;
}
const GEMINI_API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models";
const DEFAULT_GEMINI_MODEL = "gemini-2.0-flash";
function resolveGeminiConfig(search) {
	if (!search || typeof search !== "object") return {};
	const gemini = "gemini" in search ? search.gemini : void 0;
	if (!gemini || typeof gemini !== "object") return {};
	return gemini;
}
function resolveGeminiApiKey(gemini) {
	const fromConfig = normalizeApiKey(gemini?.apiKey);
	if (fromConfig) return fromConfig;
	return normalizeApiKey(process.env.GOOGLE_API_KEY) || void 0;
}
function resolveGeminiModel(gemini) {
	return (gemini && "model" in gemini && typeof gemini.model === "string" ? gemini.model.trim() : "") || DEFAULT_GEMINI_MODEL;
}
async function runGeminiSearch(params) {
	const endpoint = `${GEMINI_API_ENDPOINT}/${params.model}:generateContent?key=${params.apiKey}`;
	const body = {
		contents: [{ parts: [{ text: params.query }] }],
		tools: [{ googleSearch: {} }]
	};
	const res = await fetch(endpoint, {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify(body),
		signal: withTimeout(void 0, params.timeoutSeconds * 1e3)
	});
	if (!res.ok) {
		const detail = (await readResponseText(res, { maxBytes: 64e3 })).text;
		throw new Error(`Gemini API error (${res.status}): ${detail || res.statusText}`);
	}
	const candidate = (await res.json()).candidates?.[0];
	const text = candidate?.content?.parts?.map((p) => p.text).join("") ?? "No response";
	const citations = (candidate?.groundingMetadata?.groundingChunks ?? []).map((chunk) => chunk.web?.uri).filter((uri) => !!uri);
	return {
		content: text,
		citations: [...new Set(citations)]
	};
}
function resolveSearchCount(value, fallback) {
	const parsed = typeof value === "number" && Number.isFinite(value) ? value : fallback;
	return Math.max(1, Math.min(MAX_SEARCH_COUNT, Math.floor(parsed)));
}
function normalizeFreshness(value) {
	if (!value) return;
	const trimmed = value.trim();
	if (!trimmed) return;
	const lower = trimmed.toLowerCase();
	if (BRAVE_FRESHNESS_SHORTCUTS.has(lower)) return lower;
	const match = trimmed.match(BRAVE_FRESHNESS_RANGE);
	if (!match) return;
	const [, start, end] = match;
	if (!isValidIsoDate(start) || !isValidIsoDate(end)) return;
	if (start > end) return;
	return `${start}to${end}`;
}
/**
* Map normalized freshness values (pd/pw/pm/py) to Perplexity's
* search_recency_filter values (day/week/month/year).
*/
function freshnessToPerplexityRecency(freshness) {
	if (!freshness) return;
	return {
		pd: "day",
		pw: "week",
		pm: "month",
		py: "year"
	}[freshness] ?? void 0;
}
function isValidIsoDate(value) {
	if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
	const [year, month, day] = value.split("-").map((part) => Number.parseInt(part, 10));
	if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return false;
	const date = new Date(Date.UTC(year, month - 1, day));
	return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
}
function resolveSiteName(url) {
	if (!url) return;
	try {
		return new URL(url).hostname;
	} catch {
		return;
	}
}
async function runPerplexitySearch(params) {
	const baseUrl = params.baseUrl.trim().replace(/\/$/, "");
	const endpoint = `${baseUrl}/chat/completions`;
	const body = {
		model: resolvePerplexityRequestModel(baseUrl, params.model),
		messages: [{
			role: "user",
			content: params.query
		}]
	};
	const recencyFilter = freshnessToPerplexityRecency(params.freshness);
	if (recencyFilter) body.search_recency_filter = recencyFilter;
	const res = await fetch(endpoint, {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${params.apiKey}`,
			"HTTP-Referer": "https://openclaw.ai",
			"X-Title": "OpenClaw Web Search"
		},
		body: JSON.stringify(body),
		signal: withTimeout(void 0, params.timeoutSeconds * 1e3)
	});
	if (!res.ok) {
		const detail = (await readResponseText(res, { maxBytes: 64e3 })).text;
		throw new Error(`Perplexity API error (${res.status}): ${detail || res.statusText}`);
	}
	const data = await res.json();
	return {
		content: data.choices?.[0]?.message?.content ?? "No response",
		citations: data.citations ?? []
	};
}
async function runGrokSearch(params) {
	const body = {
		model: params.model,
		input: [{
			role: "user",
			content: params.query
		}],
		tools: [{ type: "web_search" }]
	};
	const res = await fetch(XAI_API_ENDPOINT, {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${params.apiKey}`
		},
		body: JSON.stringify(body),
		signal: withTimeout(void 0, params.timeoutSeconds * 1e3)
	});
	if (!res.ok) {
		const detail = (await readResponseText(res, { maxBytes: 64e3 })).text;
		throw new Error(`xAI API error (${res.status}): ${detail || res.statusText}`);
	}
	const data = await res.json();
	const { text: extractedText, annotationCitations } = extractGrokContent(data);
	return {
		content: extractedText ?? "No response",
		citations: (data.citations ?? []).length > 0 ? data.citations : annotationCitations,
		inlineCitations: data.inline_citations
	};
}
async function runWebSearch(params) {
	const cacheKey = normalizeCacheKey(params.provider === "brave" ? `${params.provider}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || "default"}:${params.ui_lang || "default"}:${params.freshness || "default"}` : params.provider === "perplexity" ? `${params.provider}:${params.query}:${params.perplexityBaseUrl ?? DEFAULT_PERPLEXITY_BASE_URL}:${params.perplexityModel ?? DEFAULT_PERPLEXITY_MODEL}:${params.freshness || "default"}` : params.provider === "gemini" ? `${params.provider}:${params.query}:${params.geminiModel ?? DEFAULT_GEMINI_MODEL}` : `${params.provider}:${params.query}:${params.grokModel ?? DEFAULT_GROK_MODEL}:${String(params.grokInlineCitations ?? false)}`);
	const cached = readCache(SEARCH_CACHE, cacheKey);
	if (cached) return {
		...cached.value,
		cached: true
	};
	const start = Date.now();
	if (params.provider === "perplexity") {
		const { content, citations } = await runPerplexitySearch({
			query: params.query,
			apiKey: params.apiKey,
			baseUrl: params.perplexityBaseUrl ?? DEFAULT_PERPLEXITY_BASE_URL,
			model: params.perplexityModel ?? DEFAULT_PERPLEXITY_MODEL,
			timeoutSeconds: params.timeoutSeconds,
			freshness: params.freshness
		});
		const payload = {
			query: params.query,
			provider: params.provider,
			model: params.perplexityModel ?? DEFAULT_PERPLEXITY_MODEL,
			tookMs: Date.now() - start,
			externalContent: {
				untrusted: true,
				source: "web_search",
				provider: params.provider,
				wrapped: true
			},
			content: wrapWebContent(content),
			citations
		};
		writeCache(SEARCH_CACHE, cacheKey, payload, params.cacheTtlMs);
		return payload;
	}
	if (params.provider === "grok") {
		const { content, citations, inlineCitations } = await runGrokSearch({
			query: params.query,
			apiKey: params.apiKey,
			model: params.grokModel ?? DEFAULT_GROK_MODEL,
			timeoutSeconds: params.timeoutSeconds,
			inlineCitations: params.grokInlineCitations ?? false
		});
		const payload = {
			query: params.query,
			provider: params.provider,
			model: params.grokModel ?? DEFAULT_GROK_MODEL,
			tookMs: Date.now() - start,
			externalContent: {
				untrusted: true,
				source: "web_search",
				provider: params.provider,
				wrapped: true
			},
			content: wrapWebContent(content),
			citations,
			inlineCitations
		};
		writeCache(SEARCH_CACHE, cacheKey, payload, params.cacheTtlMs);
		return payload;
	}
	if (params.provider === "gemini") {
		const { content, citations } = await runGeminiSearch({
			query: params.query,
			apiKey: params.apiKey,
			model: params.geminiModel ?? DEFAULT_GEMINI_MODEL,
			timeoutSeconds: params.timeoutSeconds
		});
		const payload = {
			query: params.query,
			provider: params.provider,
			model: params.geminiModel ?? DEFAULT_GEMINI_MODEL,
			tookMs: Date.now() - start,
			externalContent: {
				untrusted: true,
				source: "web_search",
				provider: params.provider,
				wrapped: true
			},
			content: wrapWebContent(content),
			citations
		};
		writeCache(SEARCH_CACHE, cacheKey, payload, params.cacheTtlMs);
		return payload;
	}
	if (params.provider !== "brave") throw new Error("Unsupported web search provider.");
	const url = new URL(BRAVE_SEARCH_ENDPOINT);
	url.searchParams.set("q", params.query);
	url.searchParams.set("count", String(params.count));
	if (params.country) url.searchParams.set("country", params.country);
	if (params.search_lang) url.searchParams.set("search_lang", params.search_lang);
	if (params.ui_lang) url.searchParams.set("ui_lang", params.ui_lang);
	if (params.freshness) url.searchParams.set("freshness", params.freshness);
	const res = await fetch(url.toString(), {
		method: "GET",
		headers: {
			Accept: "application/json",
			"X-Subscription-Token": params.apiKey
		},
		signal: withTimeout(void 0, params.timeoutSeconds * 1e3)
	});
	if (!res.ok) {
		const detail = (await readResponseText(res, { maxBytes: 64e3 })).text;
		throw new Error(`Brave Search API error (${res.status}): ${detail || res.statusText}`);
	}
	const data = await res.json();
	const mapped = (Array.isArray(data.web?.results) ? data.web?.results ?? [] : []).map((entry) => {
		const description = entry.description ?? "";
		const title = entry.title ?? "";
		const url = entry.url ?? "";
		const rawSiteName = resolveSiteName(url);
		return {
			title: title ? wrapWebContent(title, "web_search") : "",
			url,
			description: description ? wrapWebContent(description, "web_search") : "",
			published: entry.age || void 0,
			siteName: rawSiteName || void 0
		};
	});
	const payload = {
		query: params.query,
		provider: params.provider,
		count: mapped.length,
		tookMs: Date.now() - start,
		externalContent: {
			untrusted: true,
			source: "web_search",
			provider: params.provider,
			wrapped: true
		},
		results: mapped
	};
	writeCache(SEARCH_CACHE, cacheKey, payload, params.cacheTtlMs);
	return payload;
}
function createWebSearchTool(options) {
	const search = resolveSearchConfig(options?.config);
	if (!resolveSearchEnabled({
		search,
		sandboxed: options?.sandboxed
	})) return null;
	const provider = resolveSearchProvider(search);
	const perplexityConfig = resolvePerplexityConfig(search);
	const grokConfig = resolveGrokConfig(search);
	const geminiConfig = resolveGeminiConfig(search);
	return wrapToolWithTracking({
		label: "Web Search",
		name: "web_search",
		description: provider === "perplexity" ? "Search the web using Perplexity Sonar (direct or via OpenRouter). Returns AI-synthesized answers with citations from real-time web search." : provider === "gemini" ? "Search the web using Google Gemini with Search grounding. Returns AI-synthesized answers with citations from Google Search." : provider === "grok" ? "Search the web using xAI Grok. Returns AI-synthesized answers with citations from real-time web search." : "Search the web using Brave Search API. Supports region-specific and localized search via country and language parameters. Returns titles, URLs, and snippets for fast research.",
		parameters: WebSearchSchema,
		execute: async (_toolCallId, args) => {
			const perplexityAuth = provider === "perplexity" ? resolvePerplexityApiKey(perplexityConfig) : void 0;
			const apiKey = provider === "perplexity" ? perplexityAuth?.apiKey : provider === "gemini" ? resolveGeminiApiKey(geminiConfig) : provider === "grok" ? resolveGrokApiKey(grokConfig) : resolveSearchApiKey(search);
			const params = args;
			if (!apiKey) {
				if (search?.browserFallback !== false && isBrowserSearchAvailable()) try {
					return jsonResult(await runBrowserSearch({
						query: readStringParam(params, "query", { required: true }),
						count: resolveSearchCount(readNumberParam(params, "count", { integer: true }) ?? search?.maxResults ?? void 0, DEFAULT_SEARCH_COUNT),
						cacheTtlMs: resolveCacheTtlMs(search?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES)
					}));
				} catch (browserErr) {
					return jsonResult({
						...missingSearchKeyPayload(provider),
						browserFallbackError: String(browserErr)
					});
				}
				return jsonResult(missingSearchKeyPayload(provider));
			}
			const query = readStringParam(params, "query", { required: true });
			const count = readNumberParam(params, "count", { integer: true }) ?? search?.maxResults ?? void 0;
			const country = readStringParam(params, "country");
			const search_lang = readStringParam(params, "search_lang");
			const ui_lang = readStringParam(params, "ui_lang");
			const rawFreshness = readStringParam(params, "freshness");
			if (rawFreshness && provider !== "brave" && provider !== "perplexity") return jsonResult({
				error: "unsupported_freshness",
				message: "freshness is only supported by the Brave and Perplexity web_search providers.",
				docs: "https://docs.openclaw.ai/tools/web"
			});
			const freshness = rawFreshness ? normalizeFreshness(rawFreshness) : void 0;
			if (rawFreshness && !freshness) return jsonResult({
				error: "invalid_freshness",
				message: "freshness must be one of pd, pw, pm, py, or a range like YYYY-MM-DDtoYYYY-MM-DD.",
				docs: "https://docs.openclaw.ai/tools/web"
			});
			return jsonResult(await runWebSearch({
				query,
				count: resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
				apiKey,
				timeoutSeconds: resolveTimeoutSeconds(search?.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS),
				cacheTtlMs: resolveCacheTtlMs(search?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
				provider,
				country,
				search_lang,
				ui_lang,
				freshness,
				perplexityBaseUrl: resolvePerplexityBaseUrl(perplexityConfig, perplexityAuth?.source, perplexityAuth?.apiKey),
				perplexityModel: resolvePerplexityModel(perplexityConfig),
				grokModel: resolveGrokModel(grokConfig),
				grokInlineCitations: resolveGrokInlineCitations(grokConfig),
				geminiModel: resolveGeminiModel(geminiConfig)
			}));
		}
	}, "builtin:web_search");
}
const __testing = {
	inferPerplexityBaseUrlFromApiKey,
	resolvePerplexityBaseUrl,
	isDirectPerplexityBaseUrl,
	resolvePerplexityRequestModel,
	normalizeFreshness,
	freshnessToPerplexityRecency,
	resolveGrokApiKey,
	resolveGrokModel,
	resolveGrokInlineCitations,
	extractGrokContent
};

//#endregion
export { __testing, createWebSearchTool };