Repository navigation
Add the read_document tool; send PDF attachments as text and page images - #55
Merged
Merged
Conversation
…images read_document reads PDFs and images inside the granted folders through the sandboxed DocumentWorkerHost: text page by page with a continuation hint, drawn/scanned pages and photos as ToolResult.images, page ranges, `start`, zoomed regions and GPS only with include_location (audited as includeLocation/locationRead). Same guard and verified open as read_file; read-only and audited. HEIC/AVIF/Office answer "not supported yet"; reader refusals and worker failures become clean tool errors. Adds ToolImage / ToolResult.images (shared contract with feat/provider-images); defineTool passes images through and merges RunResult.audit into the entry. read_file's binary refusal points PDFs and images to read_document. PDF attachments are converted when sending (fenced, untrusted), with drawn pages as image blocks on the Claude API and a note elsewhere; results are cached per attachment id and PDF chips are no longer greyed. One host per app, disposed on quit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
…eep e2e bundle out of the installer - Tools' audit facts now go under args.extra and only allowlisted small scalars (kind, imageCount, locationRead) survive; a tool can no longer overwrite its summarized args or log arbitrary values. - Test the 8x region zoom cap through the real PDF reader (exactly 8x, and a region needing 16x capped at 8x) instead of a junk input field. - Exclude out/main/tools.js (the e2e-only entry) from app.asar; afterPack now fails the build if a test-only bundle is packaged. - e2e: use the native realpath for the sandbox so Windows 8.3 temp names (RUNNER~1) match the guard's view. Co-authored-by: omnigent <noreply@omnigent.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the e2e tools entry, the job launcher (and the pin compiled into it) moves into out/main/chunks, which the afterPack check did not search. Co-authored-by: omnigent <noreply@omnigent.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
afterPack accepted the pin if any .js under out/main held it, so a stray or decoy file could satisfy the check. Now it starts from package.json `main`, follows the static requires/imports of local chunks (resolved inside out/main, symlinks included), refuses test-only entries, and requires exactly one JOB_RUN_SHA256 definition there, equal to the packaged job-run.exe's hash. It fails on zero or several pins and on imports it cannot resolve. Co-authored-by: omnigent <noreply@omnigent.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The main build now writes out/main/.build-manifest.json (each chunk's static and dynamic chunk imports, straight from Rollup) and compiles the pin in as the string `job-run.exe-sha256:<digest>`. bundledPin walks only that manifest from the production entry, fails closed on a missing manifest or entry, a listed chunk that is missing or outside out/main, a reachable test-only entry, or a computed require()/import(); and requires the marker exactly once in the graph and nowhere else under out/main. Text in comments or strings no longer adds edges, and the constant's (renamed) identifier no longer matters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
Drop the computed-load regex and its method exemption: loader patterns (globalThis.require, module.require, (0, require), createRequire aliases) no longer matter. bundledPin() now fails on any symlink under out/main, scans every file there for `job-run.exe-sha256:`, requires exactly one occurrence with a valid 64-hex digest, and requires that file to be a chunk reachable from the production entry in the Rollup manifest graph. Any decoy anywhere, however it is loaded, is a second marker. Leave out/main/.build-manifest.json out of the package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
Resolve the conflicts with #56 and send PDF page images through its vision path: - canReceive(kind, vision): PDFs always (as text), images only with vision. - toModelConversation takes #56's `vision` plus `readPdf`; a PDF's drawn pages go as image blocks unless vision is false, checked with isSendableImage and built with imageBlock, then budgeted and encoded by each client like any other image. The Claude-API-only sendsImages flag is gone. - Tests: PDF pages reach a vision OpenAI-compatible connection as image_url and a non-vision one as a note; read_document's images reach the Claude API tool_result and the OpenAI-compatible user message against fake servers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
… can use - cachedPdfReading keeps at most 16 views and 64 MB in all (least recently used out first); a view larger than the cap is used once and not kept. - A connection known not to see images reads its PDFs as text only (no page renders). A cached text-only view is read again with its pages when a later send can use them. - toModelConversation reads image and PDF attachments newest first, as withImageBudget orders them, and asks each PDF for no more page images than the request's budget (20) has left, 8 at most; older PDFs get text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design PR 3: the
read_documentagent tool, built on the sandboxedDocumentWorkerHostfrom #52.What changed
Shared contract (
src/main/tools/types.ts):ToolImageandToolResult.images, the same edit asfeat/provider-images. This PR only produces images. Provider code is untouched.read_document(src/main/tools/read-document.ts, registered for every connection)path, plus one ofpages("3","2-5","10-"),start(continue from a page) orregion({page,x,y,width,height}, zoom up to 8x). Optionalinclude_location.read_file(granted folders, realpath, secret/protected blocklist) andopenVerified(no links, single hard link, handle re-checked). The worker gets a copy of the bytes, never the path.content: a header (path, type, size, pages, range shown, title/author), then the text page by page, then notes. The last line gives either astart: Nhint or "End of document".images: rendered drawing/scan pages (label: "page 3"), the zoomed region, or the photo (upright, scaled, EXIF/GPS stripped). Capture time and camera come back as text. Location only withinclude_location.includeLocationwhen it was asked for andlocationRead: truewhen a location was actually returned, plus the kind and the image count (newRunResult.audit, merged bydefineTool).unsupported). Text files are pointed toread_file. Reader refusals and worker failures (timeout, crash, memory, a misbehaving worker) become clean error results with an audit category (unreadable,out-of-range,timeout,worker-failed).read_file: the binary refusal now saysX is a PDF, not text; use read_document to read it.for PDFs and images.Attachments: when a chat is sent, a PDF attachment is read by the reader:
sendsImages);canReceive).Image attachments are left for the other PR.
Lifecycle: one
DocumentWorkerHostper app (src/main/index.ts), shared by the tool and attachments and disposed onwill-quit.Verification
pnpm typecheck,pnpm lintandpnpm format:check: clean.pnpm test(Vitest, whole repo): 125 files passed, 2 skipped. 2673 test cases passed, 34 skipped.src/main/tools/read-document.test.tsuses a fake host. It covers output format, ranges/start/regions, input validation, the photo GPS gate and its audit fields, unsupported types, error mapping, and the guards (outside the grant,.env, protected userData, symlink out of the grant, hard link, folder, missing file, over 100 MB). It also covers the newread_filemessages.src/main/chat/attachments.test.ts: fenced untrusted text, images vs notes, caching, failure placeholders, and session wiring.pnpm build && xvfb-run -a pnpm test:e2e: 129 passed. That includes the newe2e/read-document.spec.ts(6 tests), which runs the real tools (out/main/tools.js, a new build entry) over the real worker:starthint, a zoomed region;locationReadaudited;Notes for merging
src/main/tools/types.tsmatches the contract exactly.feat/provider-imagesmay also touchcanReceiveinsrc/renderer/src/attachments/model.ts(for images), which could give a one-line conflict there.imagesreach no model. Eachcontentis written to make sense on its own.🤖 Generated with Claude Code