/** * The in-process extractor contract for `read_doc`. * * Each format is extracted by a JavaScript library that reads the file itself. * This module owns the file read, the abort check, and the * {@link FormatExtractor} type, so the format modules stay tiny and testable * through real fixtures. * * Imports of the extraction libraries are lazy (`await import(...)` inside each * extractor): the extension registers on every session, but a parser only loads * when a read actually needs it, matching `web-access`'s lazy `unpdf` import. */ import { readFile } from "node:fs/promises"; /** What an extractor receives: the real, root-confined absolute path. */ export interface ExtractInput { /** Absolute, symlink-resolved path inside the effective working root. */ path: string; /** The active turn's abort signal, checked before any work. */ signal?: AbortSignal; } /** Extract plain text for one file. */ export type FormatExtractor = (input: ExtractInput) => Promise; /** Read the whole file, refusing to start once the turn is aborted. */ export async function readFormatBytes(input: ExtractInput): Promise { input.signal?.throwIfAborted(); const bytes = await readFile(input.path); input.signal?.throwIfAborted(); return new Uint8Array(bytes); } /** * Normalize extracted body text: CRLF to LF, collapse runs of blank lines to * one, strip trailing spaces and newlines. Pure. */ export function normalizeBodyText(text: string): string { return text .replace(/\r\n?/g, "\n") .replace(/[ \t]+\n/g, "\n") .replace(/\n{3,}/g, "\n\n") .replace(/\n+$/, ""); }