UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

153 lines (152 loc) • 5.45 kB
import { i as extractErrorCode } from "./error-coercion-CvJK4lmH.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import "./error-runtime-BmospGvZ.mjs"; import "./string-coerce-runtime-50qKeWeL.mjs"; //#region extensions/document-extract/document-extractor.runtime.ts const MAX_EXTRACTED_TEXT_CHARS = 2e5; const MAX_RENDER_DIMENSION = 1e4; let pdfEnginePromise = null; async function loadPdfEngine() { if (!pdfEnginePromise) pdfEnginePromise = import("clawpdf").then(({ createEngine }) => createEngine()).catch((err) => { pdfEnginePromise = null; throw new Error("Dependency clawpdf is required for PDF extraction", { cause: err }); }); return pdfEnginePromise; } function toDocumentImage(bytes) { return { type: "image", data: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64"), mimeType: "image/png" }; } function pageRenderOptions(width, height, maxPixels) { if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null; const defaultWidth = Math.ceil(width * (96 / 72)); const defaultHeight = Math.ceil(height * (96 / 72)); if (defaultWidth <= MAX_RENDER_DIMENSION && defaultHeight <= MAX_RENDER_DIMENSION && defaultWidth * defaultHeight <= maxPixels) return { options: { dpi: 96, forms: true }, reduced: false }; const landscape = width >= height; const longer = landscape ? width : height; const shorter = landscape ? height : width; let low = 1; let high = Math.min(MAX_RENDER_DIMENSION, Math.ceil(longer * (96 / 72))); let size = 1; while (low <= high) { const candidate = Math.floor((low + high) / 2); if (candidate * Math.max(1, Math.ceil(shorter * (candidate / longer))) <= maxPixels) { size = candidate; low = candidate + 1; } else high = candidate - 1; } return { options: landscape ? { width: size, forms: true } : { height: size, forms: true }, reduced: true }; } async function extractPdfContent(request, control) { const engine = await loadPdfEngine(); control.throwIfCancelled(); let pdf; try { pdf = request.password ? await engine.open(request.buffer, { password: request.password }) : await engine.open(request.buffer); } catch (err) { if (extractErrorCode(err) === "password") throw new Error("PDF requires a password or password is incorrect.", { cause: err }); throw err; } try { control.throwIfCancelled(); const pages = request.pageNumbers ? request.pageNumbers.filter((p) => Number.isInteger(p) && p >= 1 && p <= pdf.pageCount).slice(0, request.maxPages) : void 0; if (request.pageNumbers?.length && pages?.length === 0) throw new Error(`No requested PDF pages exist in this ${pdf.pageCount}-page document.`); const selectedPages = pages ?? Array.from({ length: Math.min(pdf.pageCount, request.maxPages) }, (_, i) => i + 1); const metadata = { pages: { processed: selectedPages, total: pdf.pageCount, selection: request.pageNumbers ? "explicit" : "automatic", truncated: request.pageNumbers ? request.pageNumbers.length > selectedPages.length : pdf.pageCount > request.maxPages }, textTruncated: false, imagesTruncated: false }; const imagePages = []; let text = ""; for (const pageNumber of selectedPages) { control.throwIfCancelled(); const pageText = pdf.page(pageNumber).text(); if (pageText.trim().length < request.minTextChars) imagePages.push(pageNumber); const separator = text ? "\n\n" : ""; const remaining = MAX_EXTRACTED_TEXT_CHARS - text.length - separator.length; const prefix = truncateUtf16Safe(pageText, Math.max(0, remaining)); metadata.textTruncated ||= prefix.length < pageText.length; if (prefix) text += separator + prefix; } control.throwIfCancelled(); if (imagePages.length === 0) return { text, images: [], metadata }; try { const { encodePng, PdfError } = await import("clawpdf"); const images = []; let remainingPixels = request.maxPixels; for (const [index, pageNumber] of imagePages.entries()) { control.throwIfCancelled(); if (remainingPixels <= 0) { metadata.imagesTruncated = true; break; } const pagesRemaining = imagePages.length - index; const maxPixelsPerPage = Math.max(1, Math.ceil(remainingPixels / pagesRemaining)); if (!Number.isFinite(maxPixelsPerPage)) throw new PdfError("budget", "maxPixels must be a finite positive number"); const page = pdf.page(pageNumber); const plan = pageRenderOptions(page.width, page.height, maxPixelsPerPage); if (!plan) { metadata.imagesTruncated = true; continue; } metadata.imagesTruncated ||= plan.reduced; const rendered = page.render(plan.options); control.throwIfCancelled(); const bytes = await control.runNativeSection(() => encodePng(rendered.rgba, { width: rendered.width, height: rendered.height })); control.throwIfCancelled(); images.push(toDocumentImage(bytes)); remainingPixels -= rendered.width * rendered.height; } return { text, images, metadata }; } catch (err) { control.throwIfCancelled(); metadata.imagesTruncated = true; request.onImageExtractionError?.(err); if (!text.trim()) throw new Error("PDF image extraction failed with no extractable text.", { cause: err }); return { text, images: [], metadata }; } } finally { pdf.destroy(); } } //#endregion export { extractPdfContent as t };