UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

98 lines (97 loc) 3.22 kB
//#region extensions/document-extract/document-extractor.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, mimeType }) { return { type: "image", data: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64"), mimeType }; } function isPdfPasswordError(err) { return Boolean(err && typeof err === "object" && err.code === "password"); } async function openPdfDocument(params) { try { return params.password ? await params.engine.open(params.input, { password: params.password }) : await params.engine.open(params.input); } catch (err) { if (isPdfPasswordError(err)) throw new Error("PDF requires a password or password is incorrect.", { cause: err }); throw err; } } async function extractPdfContent(request) { const pdf = await openPdfDocument({ engine: await loadPdfEngine(), input: request.buffer, ...request.password ? { password: request.password } : {} }); try { 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 pageSelection = pages ? { pages } : { maxPages: request.maxPages }; const text = (await pdf.extract({ mode: "text", ...pageSelection, maxTextChars: MAX_EXTRACTED_TEXT_CHARS })).text; if (text.trim().length >= request.minTextChars) return { text, images: [] }; const imagePages = pages ?? Array.from({ length: Math.min(pdf.pageCount, request.maxPages) }, (_, i) => i + 1); try { const images = []; let remainingPixels = request.maxPixels; for (const [index, pageNumber] of imagePages.entries()) { if (remainingPixels <= 0) break; const pagesRemaining = imagePages.length - index; const maxPixelsPerPage = Math.max(1, Math.ceil(remainingPixels / pagesRemaining)); const imageResult = await pdf.extract({ mode: "images", pages: [pageNumber], image: { maxDimension: MAX_RENDER_DIMENSION, maxPixels: maxPixelsPerPage, forms: true } }); for (const image of imageResult.images) { images.push(toDocumentImage(image)); remainingPixels -= image.width * image.height; } } return { text, images }; } catch (err) { request.onImageExtractionError?.(err); if (!text.trim()) throw new Error("PDF image extraction failed with no extractable text.", { cause: err }); return { text, images: [] }; } } finally { pdf.destroy(); } } function createPdfDocumentExtractor() { return { id: "pdf", label: "PDF", mimeTypes: ["application/pdf"], autoDetectOrder: 10, extract: extractPdfContent }; } //#endregion export { createPdfDocumentExtractor };