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("