Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/gatekeeper-google/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,9 @@ A folder URL carrying `?resourcekey=` is not supported: the key is dropped, and

The agent-facing `GoogleDriveReadSession` covers account and exact-file bindings. `GoogleDriveFolderSession` is positioned at the selected root and exposes only its current folder's direct children: `list()`, provider-side structured `search()`, `getEntry()`, native Doc/Sheet/Slides opens, `readPdf()` and `readFile()`, and `openFolder()` for one live direct child. Listing and search return disposable RPC cursors; child folders and native content sessions are independently disposable capabilities. There is no built-in recursive folder search, traversal pager, raw Drive `q`, file write, shortcut traversal, or export of Google-native files.

Both sessions offer `readPdf(fileId)`, which downloads an in-scope PDF and returns its text as Markdown, converted by Workers AI's `toMarkdown()`. It downloads with the Drive resources' `drive.readonly`; on an account connected before they requested it, `readPdf()` fails asking the user to reconnect. The file is checked against the binding's scope, and must be a PDF, before anything is downloaded, and a folder binding re-proves it is a live direct child after the download, as for a native read: the token's `drive.readonly` reaches any Drive file, so scope is enforced in code. Neither the PDF nor its text is held in memory: the download streams straight into the conversion request (through the `AI` binding's own `fetch`, since the binding's `toMarkdown()` takes a whole `Blob`), and `text` is a `ReadableStream` of UTF-8 Markdown read from the conversion's response as it is converted, so the agent bounds how much it reads. `readPdf()` reads the response only until the text begins before it returns, so `hasText` is already known. Pages are separated by `### Page N` headings, and pages before the first with text are left out. There is no download or text cap: a PDF Workers AI cannot convert, including one too large for it, fails. Nothing is OCR'd, so a scanned or image-only PDF returns `hasText: false` and an empty `text`. Conversion runs under a fixed file name with its images left undescribed, so no Drive file name reaches Workers AI and no billed model call is made.
Both sessions offer `readPdf(fileId)`, which downloads an in-scope PDF and returns its text as Markdown, converted by Workers AI's `toMarkdown()`. It downloads with the Drive resources' `drive.readonly`; on an account connected before they requested it, `readPdf()` fails asking the user to reconnect. The file is checked against the binding's scope, and must be a PDF, before anything is downloaded, and a folder binding re-proves it is a live direct child after the download, as for a native read: the token's `drive.readonly` reaches any Drive file, so scope is enforced in code. Neither the PDF nor its text is held in memory: the download streams straight into the conversion request (through the `AI` binding's own `fetch`, since the binding's `toMarkdown()` takes a whole `Blob`), and `text` is a `ReadableStream` of UTF-8 Markdown read from the conversion's response as it is converted, so the agent bounds how much it reads. `readPdf()` reads the response only until the text begins before it returns, so `hasText` is already known. Pages are separated by `### Page N` headings, and pages before the first with text are left out. There is no download or text cap: a PDF Workers AI cannot convert, including one too large for it, fails. A scanned or image-only PDF has no text to convert, so it returns `hasText: false` and an empty `text` unless the caller asks for it to be transcribed. Conversion runs under a fixed file name with its images left undescribed, so no Drive file name reaches Workers AI and the conversion makes no billed model call.

`readPdf(fileId, { ocr: true })` transcribes a PDF that proves to have no text, and returns `transcribed: true` with the transcription as `text`; a PDF with any text of its own is returned as that text, and no model is called. Each page is one billed call to the Workers AI vision model `@cf/google/gemma-4-26b-a4b-it` (about 320 input and a few hundred output tokens for a page of prose), made with no budget or approval of its own, through the same AI Gateway as conversion when `CF_AI_GATEWAY` is set; a gateway that stores its logs then holds the page images and their transcriptions. Nothing is held here: as the PDF streams into the conversion, a scanner (`src/pdf-images.ts`) notes where each page image sits in the file, and a page is then read from Drive by range, base64-encoded into the model's request as it arrives, and its text passed on as the model produces it. That happens only as `text` is read, one page at a time, so an agent that stops reading, or cancels, stops paying; cancelling ends the page in progress. Every page read is a content read of its own, checked against the binding's scope, re-proved on a folder binding and reported as `drive/content/<fileId>`, and it fails if the file is no longer the size it was converted at, or, on a folder binding, if its modification time has moved. Pages come under `### Page N` headings numbered in the order the file stores their images, which is page order for scanner output but is not guaranteed, since the page tree is not followed. Any image at least 200 pixels on each side that is not a mask counts as a page, so a PDF of pictures and no text has each picture read as one. JPEG page images are sent as they are stored, and losslessly compressed grey or RGB ones are wrapped as PNG on the way without being decoded (`src/pdf-png.ts`). A scanned PDF is refused, in the gatekeeper's words and before any model call, when it is encrypted, when a page is stored as fax (CCITT), JBIG2 or JPEG 2000, or as an image PNG cannot hold (a palette, CMYK), when a page image is over 12 MiB (Workers AI refuses a request somewhere between that and 16 MiB), is not where the file says it is, or when there are more than 2,000 of them. The model reads a whole page at a fixed resolution whatever the scan's, so small print is where it errs: in testing it got 98 to 100% of a page's words right, and its misses were codes, numbers and names in 6 to 8 point type, which is why the types tell the agent a transcription is not the document's own text. A page the model is stopped partway through ends with `[The transcription of this page was cut off.]`, a blank page has a heading and no text, and a page that fails errors the stream. Thinking is turned off for the model: it tripled a page's tokens and time and held the text back, without reading a poor scan any better. Logs carry page counts, page numbers, sizes, encodings and status codes, never text.

The gatekeeper's `AI` binding is what `readPdf()` converts with. It runs remotely, so start the dev server with `pnpm dev-server -- --use-workers-ai-binding` (which needs a Cloudflare login) to read PDFs locally; without the binding `readPdf()` says PDF reading is not available, and everything else works. When `CF_AI_GATEWAY` is set on this Worker, conversion goes through the AI Gateway it names, which must be in the Worker's own account; otherwise it calls Workers AI directly.

Expand Down
Loading
Loading