Research Agents: Agents you define that answer research chats from SEC filings, your documents, and the web, citing every passage.
| 1 | import type * as PdfJs from 'pdfjs-dist' |
| 2 | import { pdfText, type PdfItem } from './docs.ts' |
| 3 | |
| 4 | // A PDF's text, read in the view with pdf.js. The view's frame allows no worker, no eval, and no |
| 5 | // network, so pdf.js runs in this thread (its worker's handler set on globalThis, which it looks for |
| 6 | // before it would start a worker), with font loading off, from the bytes it is handed; pdf.js 6 has |
| 7 | // no eval. Both |
| 8 | // halves load on the first PDF: they are most of this plugin's size, and the plugin's definitions, |
| 9 | // which are this same code evaluated without a page, never reach them. |
| 10 | |
| 11 | let loading: Promise<typeof PdfJs> | null = null |
| 12 | |
| 13 | function pdfjs(): Promise<typeof PdfJs> { |
| 14 | loading ??= (async () => { |
| 15 | ;(globalThis as { pdfjsWorker?: unknown }).pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.min.mjs') |
| 16 | return await import('pdfjs-dist/build/pdf.min.mjs') |
| 17 | })() |
| 18 | return loading |
| 19 | } |
| 20 | |
| 21 | export async function readPdf(bytes: Uint8Array, name: string): Promise<string> { |
| 22 | const lib = await pdfjs() |
| 23 | const task = lib.getDocument({ data: bytes, disableFontFace: true }) |
| 24 | try { |
| 25 | const doc = await task.promise.catch((err: unknown) => { |
| 26 | if ((err as { name?: string }).name === 'PasswordException') throw new Error(`${name} is password-protected.`) |
| 27 | throw err |
| 28 | }) |
| 29 | const pages: PdfItem[][] = [] |
| 30 | for (let n = 1; n <= doc.numPages; n++) { |
| 31 | const page = await doc.getPage(n) |
| 32 | pages.push((await page.getTextContent()).items as PdfItem[]) |
| 33 | page.cleanup() |
| 34 | } |
| 35 | const text = pdfText(pages) |
| 36 | if (!text) throw new Error(`${name} has no text layer, as a scan has none; scans are not read yet.`) |
| 37 | return text |
| 38 | } finally { |
| 39 | await task.destroy() |
| 40 | } |
| 41 | } |