import type { RequestInit as UndiciRequestInit } from "undici"; import { validateUrl } from "./lib/url-guard.js"; import { getSsrfAgent } from "./lib/ssrf-agent.js"; import { htmlToMarkdown } from "./lib/html2md.js"; import { extractContent } from "./lib/extract.js"; import { findAlternates, ALLOWED_ALTERNATE_TYPES } from "./lib/alternates.js"; import { pdfToText } from "./lib/pdf.js"; import { ACCEPT_HEADER, BROWSER_UA, FETCH_TIMEOUT_MS, MAX_CHARS_DEFAULT, MAX_CHARS_HARD_CAP, MAX_RESPONSE_BYTES, OPENCODE_UA, } from "./lib/headers.js"; import { Type } from "@mariozechner/pi-ai"; import { defineTool, keyHint } from "@mariozechner/pi-coding-agent"; import { ensureText, type FormatterTheme } from "./lib/render.js"; export interface FetchInput { url: string; max_chars?: number; /** * Character offset into the extracted markdown. Default 0. Used to * page through documents whose extracted size exceeds * MAX_CHARS_HARD_CAP. The next-offset value is reported in the * truncation footer; callers thread it back here on the next call. * See issue #132. */ offset?: number; } const HTML_MIMES = ["text/html", "application/xhtml+xml"]; const HTML_SNIFF_BYTES = 1024; type BodyKind = "html" | "json" | "text" | "pdf"; function parseCharset(contentType: string): string | undefined { const m = /;\s*charset\s*=\s*"?([^";\s]+)"?/i.exec(contentType); return m?.[1]; } // Sniff a charset declaration in the first HTML_SNIFF_BYTES of the body. // Catches both and . // We tokenize each tag's attributes, so a charset= substring sitting inside an unrelated // quoted attribute value (e.g. ) cannot win. // HTML comments are stripped first; an unterminated /g, ""); const unterminated = text.indexOf("