Jaspers Hub

jaspers / agents

pluginOfficialReviewed★ 0↓ 1

Research Agents: Agents you define that answer research chats from SEC filings, your documents, and the web, citing every passage.

1.6 KB
1import type * as PdfJs from 'pdfjs-dist'
2import { 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
11let loading: Promise<typeof PdfJs> | null = null
12
13function 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
21export 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}