import { h as __exportAll } from "./pi-embedded-utils-CPkknmhn.js";
import { i as logDebug, s as logWarn } from "./exec-DJFSSNca.js";
import { t as wrapToolWithTracking } from "./tool-tracking-D1HWsfb6.js";
import { n as normalizeSecretInput } from "./normalize-secret-input-DHzK5SJW.js";
import { a as jsonResult, o as readNumberParam, u as readStringParam } from "./common-D7lLwt9D.js";
import { t as SsrFBlockedError } from "./ssrf-BPZndZer.js";
import { t as fetchWithSsrFGuard } from "./fetch-guard-DkWDyNHr.js";
import { n as wrapExternalContent, r as wrapWebContent } from "./external-content-Ptl4MUui.js";
import { i as stringEnum } from "./typebox-Dqngjw-j.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-CVOdQoRW.js";
import { Type } from "@sinclair/typebox";

//#region src/compression/llmlingua2-client.ts
/**
* LLMLingua-2 document compression client.
*
* Calls the LLMLingua-2 Python microservice (MIN-576) to compress retrieved
* documents (web pages, email threads, tool outputs) before context injection.
* Uses XLM-RoBERTa 125M to score token retention probability — 2–5× compression
* at 95%+ answer preservation, preserving original text unlike summarization.
*
* Activated by setting LLMLINGUA_URL. Falls back silently on any error so the
* pipeline is never blocked by compression failures.
*
* Microservice API:
*   POST /compress
*   Body: { text: string; targetRatio: number }
*   Response: { compressed: string; originalTokens: number; compressedTokens: number; ratio: number }
*/
const DEFAULT_TARGET_RATIO = .5;
const DEFAULT_MIN_CHARS = 500;
const DEFAULT_TIMEOUT_MS = 15e3;
/**
* Resolve LLMLingua-2 config from environment variables.
* Returns undefined when LLMLINGUA_URL is not set (compression disabled).
*/
function resolveLLMLingua2Config() {
	const url = process.env["LLMLINGUA_URL"]?.trim();
	if (!url) return void 0;
	const enabled = process.env["LLMLINGUA_ENABLED"];
	if (enabled === "false" || enabled === "0") return void 0;
	const targetRatio = parseFloat(process.env["LLMLINGUA_TARGET_RATIO"] ?? "");
	const minChars = parseInt(process.env["LLMLINGUA_MIN_CHARS"] ?? "", 10);
	const timeoutMs = parseInt(process.env["LLMLINGUA_TIMEOUT_MS"] ?? "", 10);
	return {
		url,
		targetRatio: isFinite(targetRatio) && targetRatio > 0 && targetRatio < 1 ? targetRatio : DEFAULT_TARGET_RATIO,
		minChars: isFinite(minChars) && minChars > 0 ? minChars : DEFAULT_MIN_CHARS,
		timeoutMs: isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS
	};
}
/**
* Compress a text document using LLMLingua-2.
* Returns the original text on any error (fail-soft — never blocks the pipeline).
*/
async function compressDocument(text, cfg) {
	if (text.length < cfg.minChars) return text;
	const controller = new AbortController();
	const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
	try {
		const res = await fetch(`${cfg.url}/compress`, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({
				text,
				targetRatio: cfg.targetRatio
			}),
			signal: controller.signal
		});
		if (!res.ok) {
			logWarn(`[llmlingua2] compress failed (${res.status}); using original`);
			return text;
		}
		const data = await res.json();
		const compressed = data.compressed ?? text;
		logDebug(`[llmlingua2] compressed ${data.originalTokens ?? "?"} → ${data.compressedTokens ?? "?"} tokens (ratio ${data.ratio?.toFixed(2) ?? cfg.targetRatio})`);
		return compressed;
	} catch {
		return text;
	} finally {
		clearTimeout(timer);
	}
}
/**
* Compress a document if LLMLingua-2 is configured.
* Convenience wrapper: resolves config and compresses in one call.
* Returns the original text when compression is disabled or unavailable.
*/
async function maybeCompressDocument(text, cfg) {
	if (!cfg) return text;
	return compressDocument(text, cfg);
}

//#endregion
//#region src/agents/tools/web/web-fetch-utils.ts
const READABILITY_MAX_HTML_CHARS = 1e6;
const READABILITY_MAX_ESTIMATED_NESTING_DEPTH = 3e3;
let readabilityDepsPromise;
async function loadReadabilityDeps() {
	if (!readabilityDepsPromise) readabilityDepsPromise = Promise.all([import("@mozilla/readability"), import("linkedom")]).then(([readability, linkedom]) => ({
		Readability: readability.Readability,
		parseHTML: linkedom.parseHTML
	}));
	try {
		return await readabilityDepsPromise;
	} catch (error) {
		readabilityDepsPromise = void 0;
		throw error;
	}
}
function decodeEntities(value) {
	return value.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&quot;/gi, "\"").replace(/&#39;/gi, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))).replace(/&#(\d+);/gi, (_, dec) => String.fromCharCode(Number.parseInt(dec, 10)));
}
function stripTags(value) {
	return decodeEntities(value.replace(/<[^>]+>/g, ""));
}
function normalizeWhitespace(value) {
	return value.replace(/\r/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/[ \t]{2,}/g, " ").trim();
}
function htmlToMarkdown(html) {
	const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
	const title = titleMatch ? normalizeWhitespace(stripTags(titleMatch[1])) : void 0;
	let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
	text = text.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, body) => {
		const label = normalizeWhitespace(stripTags(body));
		if (!label) return href;
		return `[${label}](${href})`;
	});
	text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, body) => {
		return `\n${"#".repeat(Math.max(1, Math.min(6, Number.parseInt(level, 10))))} ${normalizeWhitespace(stripTags(body))}\n`;
	});
	text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, body) => {
		const label = normalizeWhitespace(stripTags(body));
		return label ? `\n- ${label}` : "";
	});
	text = text.replace(/<(br|hr)\s*\/?>/gi, "\n").replace(/<\/(p|div|section|article|header|footer|table|tr|ul|ol)>/gi, "\n");
	text = stripTags(text);
	text = normalizeWhitespace(text);
	return {
		text,
		title
	};
}
function markdownToText(markdown) {
	let text = markdown;
	text = text.replace(/!\[[^\]]*]\([^)]+\)/g, "");
	text = text.replace(/\[([^\]]+)]\([^)]+\)/g, "$1");
	text = text.replace(/```[\s\S]*?```/g, (block) => block.replace(/```[^\n]*\n?/g, "").replace(/```/g, ""));
	text = text.replace(/`([^`]+)`/g, "$1");
	text = text.replace(/^#{1,6}\s+/gm, "");
	text = text.replace(/^\s*[-*+]\s+/gm, "");
	text = text.replace(/^\s*\d+\.\s+/gm, "");
	return normalizeWhitespace(text);
}
function truncateText(value, maxChars) {
	if (value.length <= maxChars) return {
		text: value,
		truncated: false
	};
	return {
		text: value.slice(0, maxChars),
		truncated: true
	};
}
function exceedsEstimatedHtmlNestingDepth(html, maxDepth) {
	const voidTags = new Set([
		"area",
		"base",
		"br",
		"col",
		"embed",
		"hr",
		"img",
		"input",
		"link",
		"meta",
		"param",
		"source",
		"track",
		"wbr"
	]);
	let depth = 0;
	const len = html.length;
	for (let i = 0; i < len; i++) {
		if (html.charCodeAt(i) !== 60) continue;
		const next = html.charCodeAt(i + 1);
		if (next === 33 || next === 63) continue;
		let j = i + 1;
		let closing = false;
		if (html.charCodeAt(j) === 47) {
			closing = true;
			j += 1;
		}
		while (j < len && html.charCodeAt(j) <= 32) j += 1;
		const nameStart = j;
		while (j < len) {
			const c = html.charCodeAt(j);
			if (!(c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 58 || c === 45)) break;
			j += 1;
		}
		const tagName = html.slice(nameStart, j).toLowerCase();
		if (!tagName) continue;
		if (closing) {
			depth = Math.max(0, depth - 1);
			continue;
		}
		if (voidTags.has(tagName)) continue;
		let selfClosing = false;
		for (let k = j; k < len && k < j + 200; k++) if (html.charCodeAt(k) === 62) {
			if (html.charCodeAt(k - 1) === 47) selfClosing = true;
			break;
		}
		if (selfClosing) continue;
		depth += 1;
		if (depth > maxDepth) return true;
	}
	return false;
}
async function extractReadableContent(params) {
	const fallback = () => {
		const rendered = htmlToMarkdown(params.html);
		if (params.extractMode === "text") return {
			text: markdownToText(rendered.text) || normalizeWhitespace(stripTags(params.html)),
			title: rendered.title
		};
		return rendered;
	};
	if (params.html.length > READABILITY_MAX_HTML_CHARS || exceedsEstimatedHtmlNestingDepth(params.html, READABILITY_MAX_ESTIMATED_NESTING_DEPTH)) return fallback();
	try {
		const { Readability, parseHTML } = await loadReadabilityDeps();
		const { document } = parseHTML(params.html);
		try {
			document.baseURI = params.url;
		} catch {}
		const parsed = new Readability(document, { charThreshold: 0 }).parse();
		if (!parsed?.content) return fallback();
		const title = parsed.title || void 0;
		if (params.extractMode === "text") {
			const text = normalizeWhitespace(parsed.textContent ?? "");
			return text ? {
				text,
				title
			} : fallback();
		}
		const rendered = htmlToMarkdown(parsed.content);
		return {
			text: rendered.text,
			title: title ?? rendered.title
		};
	} catch {
		return fallback();
	}
}

//#endregion
//#region src/agents/tools/web/web-fetch.ts
var web_fetch_exports = /* @__PURE__ */ __exportAll({
	createWebFetchTool: () => createWebFetchTool,
	extractReadableContent: () => extractReadableContent,
	fetchFirecrawlContent: () => fetchFirecrawlContent
});
const EXTRACT_MODES = ["markdown", "text"];
const DEFAULT_FETCH_MAX_CHARS = 5e4;
const DEFAULT_FETCH_MAX_RESPONSE_BYTES = 2e6;
const FETCH_MAX_RESPONSE_BYTES_MIN = 32e3;
const FETCH_MAX_RESPONSE_BYTES_MAX = 1e7;
const DEFAULT_FETCH_MAX_REDIRECTS = 3;
const DEFAULT_ERROR_MAX_CHARS = 4e3;
const DEFAULT_ERROR_MAX_BYTES = 64e3;
const DEFAULT_FIRECRAWL_BASE_URL = "https://api.firecrawl.dev";
const DEFAULT_FIRECRAWL_MAX_AGE_MS = 1728e5;
const DEFAULT_FETCH_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
const FETCH_CACHE = /* @__PURE__ */ new Map();
const WebFetchSchema = Type.Object({
	url: Type.String({ description: "HTTP or HTTPS URL to fetch." }),
	extractMode: Type.Optional(stringEnum(EXTRACT_MODES, {
		description: "Extraction mode (\"markdown\" or \"text\").",
		default: "markdown"
	})),
	maxChars: Type.Optional(Type.Number({
		description: "Maximum characters to return (truncates when exceeded).",
		minimum: 100
	}))
});
function resolveFetchConfig(cfg) {
	const fetch = cfg?.tools?.web?.fetch;
	if (!fetch || typeof fetch !== "object") return;
	return fetch;
}
function resolveFetchEnabled(params) {
	if (typeof params.fetch?.enabled === "boolean") return params.fetch.enabled;
	return true;
}
function resolveFetchReadabilityEnabled(fetch) {
	if (typeof fetch?.readability === "boolean") return fetch.readability;
	return true;
}
function resolveFetchMaxCharsCap(fetch) {
	const raw = fetch && "maxCharsCap" in fetch && typeof fetch.maxCharsCap === "number" ? fetch.maxCharsCap : void 0;
	if (typeof raw !== "number" || !Number.isFinite(raw)) return DEFAULT_FETCH_MAX_CHARS;
	return Math.max(100, Math.floor(raw));
}
function resolveFetchMaxResponseBytes(fetch) {
	const raw = fetch && "maxResponseBytes" in fetch && typeof fetch.maxResponseBytes === "number" ? fetch.maxResponseBytes : void 0;
	if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return DEFAULT_FETCH_MAX_RESPONSE_BYTES;
	const value = Math.floor(raw);
	return Math.min(FETCH_MAX_RESPONSE_BYTES_MAX, Math.max(FETCH_MAX_RESPONSE_BYTES_MIN, value));
}
function resolveFirecrawlConfig(fetch) {
	if (!fetch || typeof fetch !== "object") return;
	const firecrawl = "firecrawl" in fetch ? fetch.firecrawl : void 0;
	if (!firecrawl || typeof firecrawl !== "object") return;
	return firecrawl;
}
function resolveFirecrawlApiKey(firecrawl) {
	const fromConfig = firecrawl && "apiKey" in firecrawl && typeof firecrawl.apiKey === "string" ? normalizeSecretInput(firecrawl.apiKey) : "";
	const fromEnv = normalizeSecretInput(process.env.FIRECRAWL_API_KEY);
	return fromConfig || fromEnv || void 0;
}
function resolveFirecrawlEnabled(params) {
	if (typeof params.firecrawl?.enabled === "boolean") return params.firecrawl.enabled;
	return Boolean(params.apiKey);
}
function resolveFirecrawlBaseUrl(firecrawl) {
	return (firecrawl && "baseUrl" in firecrawl && typeof firecrawl.baseUrl === "string" ? firecrawl.baseUrl.trim() : "") || DEFAULT_FIRECRAWL_BASE_URL;
}
function resolveFirecrawlOnlyMainContent(firecrawl) {
	if (typeof firecrawl?.onlyMainContent === "boolean") return firecrawl.onlyMainContent;
	return true;
}
function resolveFirecrawlMaxAgeMs(firecrawl) {
	const raw = firecrawl && "maxAgeMs" in firecrawl && typeof firecrawl.maxAgeMs === "number" ? firecrawl.maxAgeMs : void 0;
	if (typeof raw !== "number" || !Number.isFinite(raw)) return;
	const parsed = Math.max(0, Math.floor(raw));
	return parsed > 0 ? parsed : void 0;
}
function resolveFirecrawlMaxAgeMsOrDefault(firecrawl) {
	const resolved = resolveFirecrawlMaxAgeMs(firecrawl);
	if (typeof resolved === "number") return resolved;
	return DEFAULT_FIRECRAWL_MAX_AGE_MS;
}
function resolveMaxChars(value, fallback, cap) {
	const parsed = typeof value === "number" && Number.isFinite(value) ? value : fallback;
	const clamped = Math.max(100, Math.floor(parsed));
	return Math.min(clamped, cap);
}
function resolveMaxRedirects(value, fallback) {
	const parsed = typeof value === "number" && Number.isFinite(value) ? value : fallback;
	return Math.max(0, Math.floor(parsed));
}
function looksLikeHtml(value) {
	const trimmed = value.trimStart();
	if (!trimmed) return false;
	const head = trimmed.slice(0, 256).toLowerCase();
	return head.startsWith("<!doctype html") || head.startsWith("<html");
}
function formatWebFetchErrorDetail(params) {
	const { detail, contentType, maxChars } = params;
	if (!detail) return "";
	let text = detail;
	if ((contentType?.toLowerCase())?.includes("text/html") || looksLikeHtml(detail)) {
		const rendered = htmlToMarkdown(detail);
		text = markdownToText(rendered.title ? `${rendered.title}\n${rendered.text}` : rendered.text);
	}
	return truncateText(text.trim(), maxChars).text;
}
function redactUrlForDebugLog(rawUrl) {
	try {
		const parsed = new URL(rawUrl);
		return parsed.pathname && parsed.pathname !== "/" ? `${parsed.origin}/...` : parsed.origin;
	} catch {
		return "[invalid-url]";
	}
}
const WEB_FETCH_WRAPPER_WITH_WARNING_OVERHEAD = wrapWebContent("", "web_fetch").length;
const WEB_FETCH_WRAPPER_NO_WARNING_OVERHEAD = wrapExternalContent("", {
	source: "web_fetch",
	includeWarning: false
}).length;
function wrapWebFetchContent(value, maxChars) {
	if (maxChars <= 0) return {
		text: "",
		truncated: true,
		rawLength: 0,
		wrappedLength: 0
	};
	const includeWarning = maxChars >= WEB_FETCH_WRAPPER_WITH_WARNING_OVERHEAD;
	const wrapperOverhead = includeWarning ? WEB_FETCH_WRAPPER_WITH_WARNING_OVERHEAD : WEB_FETCH_WRAPPER_NO_WARNING_OVERHEAD;
	if (wrapperOverhead > maxChars) {
		const truncatedWrapper = truncateText(includeWarning ? wrapWebContent("", "web_fetch") : wrapExternalContent("", {
			source: "web_fetch",
			includeWarning: false
		}), maxChars);
		return {
			text: truncatedWrapper.text,
			truncated: true,
			rawLength: 0,
			wrappedLength: truncatedWrapper.text.length
		};
	}
	const maxInner = Math.max(0, maxChars - wrapperOverhead);
	let truncated = truncateText(value, maxInner);
	let wrappedText = includeWarning ? wrapWebContent(truncated.text, "web_fetch") : wrapExternalContent(truncated.text, {
		source: "web_fetch",
		includeWarning: false
	});
	if (wrappedText.length > maxChars) {
		const excess = wrappedText.length - maxChars;
		truncated = truncateText(value, Math.max(0, maxInner - excess));
		wrappedText = includeWarning ? wrapWebContent(truncated.text, "web_fetch") : wrapExternalContent(truncated.text, {
			source: "web_fetch",
			includeWarning: false
		});
	}
	return {
		text: wrappedText,
		truncated: truncated.truncated,
		rawLength: truncated.text.length,
		wrappedLength: wrappedText.length
	};
}
function wrapWebFetchField(value) {
	if (!value) return value;
	return wrapExternalContent(value, {
		source: "web_fetch",
		includeWarning: false
	});
}
function buildFirecrawlWebFetchPayload(params) {
	const wrapped = wrapWebFetchContent(params.firecrawl.text, params.maxChars);
	const wrappedTitle = params.firecrawl.title ? wrapWebFetchField(params.firecrawl.title) : void 0;
	return {
		url: params.rawUrl,
		finalUrl: params.firecrawl.finalUrl || params.finalUrlFallback,
		status: params.firecrawl.status ?? params.statusFallback,
		contentType: "text/markdown",
		title: wrappedTitle,
		extractMode: params.extractMode,
		extractor: "firecrawl",
		externalContent: {
			untrusted: true,
			source: "web_fetch",
			wrapped: true
		},
		truncated: wrapped.truncated,
		length: wrapped.wrappedLength,
		rawLength: wrapped.rawLength,
		wrappedLength: wrapped.wrappedLength,
		fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
		tookMs: params.tookMs,
		text: wrapped.text,
		warning: wrapWebFetchField(params.firecrawl.warning)
	};
}
function normalizeContentType(value) {
	if (!value) return;
	const [raw] = value.split(";");
	return raw?.trim() || void 0;
}
async function fetchFirecrawlContent(params) {
	const endpoint = resolveFirecrawlEndpoint(params.baseUrl);
	const body = {
		url: params.url,
		formats: ["markdown"],
		onlyMainContent: params.onlyMainContent,
		timeout: params.timeoutSeconds * 1e3,
		maxAge: params.maxAgeMs,
		proxy: params.proxy,
		storeInCache: params.storeInCache
	};
	const res = await fetch(endpoint, {
		method: "POST",
		headers: {
			Authorization: `Bearer ${params.apiKey}`,
			"Content-Type": "application/json"
		},
		body: JSON.stringify(body),
		signal: withTimeout(void 0, params.timeoutSeconds * 1e3)
	});
	const payload = await res.json();
	if (!res.ok || payload?.success === false) {
		const detail = payload?.error ?? "";
		throw new Error(`Firecrawl fetch failed (${res.status}): ${wrapWebContent(detail || res.statusText, "web_fetch")}`.trim());
	}
	const data = payload?.data ?? {};
	const rawText = typeof data.markdown === "string" ? data.markdown : typeof data.content === "string" ? data.content : "";
	return {
		text: params.extractMode === "text" ? markdownToText(rawText) : rawText,
		title: data.metadata?.title,
		finalUrl: data.metadata?.sourceURL,
		status: data.metadata?.statusCode,
		warning: payload?.warning
	};
}
function toFirecrawlContentParams(params) {
	if (!params.firecrawlEnabled || !params.firecrawlApiKey) return null;
	return {
		url: params.url,
		extractMode: params.extractMode,
		apiKey: params.firecrawlApiKey,
		baseUrl: params.firecrawlBaseUrl,
		onlyMainContent: params.firecrawlOnlyMainContent,
		maxAgeMs: params.firecrawlMaxAgeMs,
		proxy: params.firecrawlProxy,
		storeInCache: params.firecrawlStoreInCache,
		timeoutSeconds: params.firecrawlTimeoutSeconds
	};
}
async function maybeFetchFirecrawlWebFetchPayload(params) {
	const firecrawlParams = toFirecrawlContentParams({
		...params,
		url: params.urlToFetch,
		extractMode: params.extractMode
	});
	if (!firecrawlParams) return null;
	const payload = buildFirecrawlWebFetchPayload({
		firecrawl: await fetchFirecrawlContent(firecrawlParams),
		rawUrl: params.url,
		finalUrlFallback: params.finalUrlFallback,
		statusFallback: params.statusFallback,
		extractMode: params.extractMode,
		maxChars: params.maxChars,
		tookMs: params.tookMs
	});
	writeCache(FETCH_CACHE, params.cacheKey, payload, params.cacheTtlMs);
	return payload;
}
async function runWebFetch(params) {
	const cacheKey = normalizeCacheKey(`fetch:${params.url}:${params.extractMode}:${params.maxChars}`);
	const cached = readCache(FETCH_CACHE, cacheKey);
	if (cached) return {
		...cached.value,
		cached: true
	};
	let parsedUrl;
	try {
		parsedUrl = new URL(params.url);
	} catch {
		throw new Error("Invalid URL: must be http or https");
	}
	if (!["http:", "https:"].includes(parsedUrl.protocol)) throw new Error("Invalid URL: must be http or https");
	const start = Date.now();
	let res;
	let release = null;
	let finalUrl = params.url;
	try {
		const result = await fetchWithSsrFGuard({
			url: params.url,
			maxRedirects: params.maxRedirects,
			timeoutMs: params.timeoutSeconds * 1e3,
			init: { headers: {
				Accept: "text/markdown, text/html;q=0.9, */*;q=0.1",
				"User-Agent": params.userAgent,
				"Accept-Language": "en-US,en;q=0.9"
			} }
		});
		res = result.response;
		finalUrl = result.finalUrl;
		release = result.release;
		const markdownTokens = res.headers.get("x-markdown-tokens");
		if (markdownTokens) logDebug(`[web-fetch] x-markdown-tokens: ${markdownTokens} (${redactUrlForDebugLog(finalUrl)})`);
	} catch (error) {
		if (error instanceof SsrFBlockedError) throw error;
		const payload = await maybeFetchFirecrawlWebFetchPayload({
			...params,
			urlToFetch: finalUrl,
			finalUrlFallback: finalUrl,
			statusFallback: 200,
			cacheKey,
			tookMs: Date.now() - start
		});
		if (payload) return payload;
		throw error;
	}
	try {
		if (!res.ok) {
			const payload = await maybeFetchFirecrawlWebFetchPayload({
				...params,
				urlToFetch: params.url,
				finalUrlFallback: finalUrl,
				statusFallback: res.status,
				cacheKey,
				tookMs: Date.now() - start
			});
			if (payload) return payload;
			const rawDetail = (await readResponseText(res, { maxBytes: DEFAULT_ERROR_MAX_BYTES })).text;
			const wrappedDetail = wrapWebFetchContent(formatWebFetchErrorDetail({
				detail: rawDetail,
				contentType: res.headers.get("content-type"),
				maxChars: DEFAULT_ERROR_MAX_CHARS
			}) || res.statusText, DEFAULT_ERROR_MAX_CHARS);
			throw new Error(`Web fetch failed (${res.status}): ${wrappedDetail.text}`);
		}
		const contentType = res.headers.get("content-type") ?? "application/octet-stream";
		const normalizedContentType = normalizeContentType(contentType) ?? "application/octet-stream";
		const bodyResult = await readResponseText(res, { maxBytes: params.maxResponseBytes });
		const body = bodyResult.text;
		const responseTruncatedWarning = bodyResult.truncated ? `Response body truncated after ${params.maxResponseBytes} bytes.` : void 0;
		let title;
		let extractor = "raw";
		let text = body;
		if (contentType.includes("text/markdown")) {
			extractor = "cf-markdown";
			if (params.extractMode === "text") text = markdownToText(body);
		} else if (contentType.includes("text/html")) if (params.readabilityEnabled) {
			const readable = await extractReadableContent({
				html: body,
				url: finalUrl,
				extractMode: params.extractMode
			});
			if (readable?.text) {
				text = readable.text;
				title = readable.title;
				extractor = "readability";
			} else {
				const firecrawl = await tryFirecrawlFallback({
					...params,
					url: finalUrl
				});
				if (firecrawl) {
					text = firecrawl.text;
					title = firecrawl.title;
					extractor = "firecrawl";
				} else throw new Error("Web fetch extraction failed: Readability and Firecrawl returned no content.");
			}
		} else throw new Error("Web fetch extraction failed: Readability disabled and Firecrawl unavailable.");
		else if (contentType.includes("application/json")) try {
			text = JSON.stringify(JSON.parse(body), null, 2);
			extractor = "json";
		} catch {
			text = body;
			extractor = "raw";
		}
		text = await maybeCompressDocument(text, resolveLLMLingua2Config());
		const wrapped = wrapWebFetchContent(text, params.maxChars);
		const wrappedTitle = title ? wrapWebFetchField(title) : void 0;
		const wrappedWarning = wrapWebFetchField(responseTruncatedWarning);
		const payload = {
			url: params.url,
			finalUrl,
			status: res.status,
			contentType: normalizedContentType,
			title: wrappedTitle,
			extractMode: params.extractMode,
			extractor,
			externalContent: {
				untrusted: true,
				source: "web_fetch",
				wrapped: true
			},
			truncated: wrapped.truncated,
			length: wrapped.wrappedLength,
			rawLength: wrapped.rawLength,
			wrappedLength: wrapped.wrappedLength,
			fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
			tookMs: Date.now() - start,
			text: wrapped.text,
			warning: wrappedWarning
		};
		writeCache(FETCH_CACHE, cacheKey, payload, params.cacheTtlMs);
		return payload;
	} finally {
		if (release) await release();
	}
}
async function tryFirecrawlFallback(params) {
	const firecrawlParams = toFirecrawlContentParams(params);
	if (!firecrawlParams) return null;
	try {
		const firecrawl = await fetchFirecrawlContent(firecrawlParams);
		return {
			text: firecrawl.text,
			title: firecrawl.title
		};
	} catch {
		return null;
	}
}
function resolveFirecrawlEndpoint(baseUrl) {
	const trimmed = baseUrl.trim();
	if (!trimmed) return `${DEFAULT_FIRECRAWL_BASE_URL}/v2/scrape`;
	try {
		const url = new URL(trimmed);
		if (url.pathname && url.pathname !== "/") return url.toString();
		url.pathname = "/v2/scrape";
		return url.toString();
	} catch {
		return `${DEFAULT_FIRECRAWL_BASE_URL}/v2/scrape`;
	}
}
function createWebFetchTool(options) {
	const fetch = resolveFetchConfig(options?.config);
	if (!resolveFetchEnabled({
		fetch,
		sandboxed: options?.sandboxed
	})) return null;
	const readabilityEnabled = resolveFetchReadabilityEnabled(fetch);
	const firecrawl = resolveFirecrawlConfig(fetch);
	const firecrawlApiKey = resolveFirecrawlApiKey(firecrawl);
	const firecrawlEnabled = resolveFirecrawlEnabled({
		firecrawl,
		apiKey: firecrawlApiKey
	});
	const firecrawlBaseUrl = resolveFirecrawlBaseUrl(firecrawl);
	const firecrawlOnlyMainContent = resolveFirecrawlOnlyMainContent(firecrawl);
	const firecrawlMaxAgeMs = resolveFirecrawlMaxAgeMsOrDefault(firecrawl);
	const firecrawlTimeoutSeconds = resolveTimeoutSeconds(firecrawl?.timeoutSeconds ?? fetch?.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS);
	const userAgent = fetch && "userAgent" in fetch && typeof fetch.userAgent === "string" && fetch.userAgent || DEFAULT_FETCH_USER_AGENT;
	const maxResponseBytes = resolveFetchMaxResponseBytes(fetch);
	return wrapToolWithTracking({
		label: "Web Fetch",
		name: "web_fetch",
		description: "Fetch and extract readable content from a URL (HTML → markdown/text). Use for lightweight page access without browser automation.",
		parameters: WebFetchSchema,
		execute: async (_toolCallId, args) => {
			const params = args;
			const url = readStringParam(params, "url", { required: true });
			const extractMode = readStringParam(params, "extractMode") === "text" ? "text" : "markdown";
			const maxChars = readNumberParam(params, "maxChars", { integer: true });
			const maxCharsCap = resolveFetchMaxCharsCap(fetch);
			return jsonResult(await runWebFetch({
				url,
				extractMode,
				maxChars: resolveMaxChars(maxChars ?? fetch?.maxChars, DEFAULT_FETCH_MAX_CHARS, maxCharsCap),
				maxResponseBytes,
				maxRedirects: resolveMaxRedirects(fetch?.maxRedirects, DEFAULT_FETCH_MAX_REDIRECTS),
				timeoutSeconds: resolveTimeoutSeconds(fetch?.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS),
				cacheTtlMs: resolveCacheTtlMs(fetch?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
				userAgent,
				readabilityEnabled,
				firecrawlEnabled,
				firecrawlApiKey,
				firecrawlBaseUrl,
				firecrawlOnlyMainContent,
				firecrawlMaxAgeMs,
				firecrawlProxy: "auto",
				firecrawlStoreInCache: true,
				firecrawlTimeoutSeconds
			}));
		}
	}, "builtin:web_fetch");
}

//#endregion
export { web_fetch_exports as n, createWebFetchTool as t };