v0.6.0 — AcroForm extraction + PdfDocument fill/save SDK - #5
Merged
Conversation
PDFs with AcroForm fields are now first-class. They snapshot as
kind: 'form' with each field exposed as an AgentMark action; the new
PdfDocument SDK class lets agents fill, save, and flatten them with the
same execute() shape as the web Page SDK.
AcroForm extraction
- Reads fields via pdfjs-dist's getFieldObjects() and merges with page
annotations to recover Required/ReadOnly flags (which getFieldObjects
doesn't surface in pdfjs-dist v4+).
- Maps AcroForm field types to AgentMark action types:
text (single + multi-line) → type: 'type'
text (password flag) → type: 'type', label: '(redacted)'
checkbox → type: 'check'
radio group → type: 'select' with options
dropdown (combo) → type: 'select' with options
listbox single → type: 'select'
listbox multi (multipleSelection) → type: 'multi_select'
signature → type: 'click', disabled
push button → type: 'click'
- Sensitive field-name redaction (password, ssn, credit_card, cvv,
account_num, token, secret, csrf, session, auth) — labels become
'(redacted)' and values are dropped.
- Field-name humanization: applicant.first_name / firstName /
first-name all → "First Name".
- Action IDs synthesized as act_field_N to satisfy AgentMark schema
regardless of source-name irregularity. Original field names
preserved in the binding map for fill operations.
- Filters out parent fields (empty type with kidIds) — only leaf
widgets with real metadata are processed.
- Dedicated unit-tested coercion for checkbox values (PDF's "Yes"/"Off"
→ boolean, fallback paths for other PDF generators).
PdfDocument SDK class (new public API)
- openPdfDocument({ data, sourceUrl, ... }) factory mirrors the web
Page SDK shape so callers' agent loops are uniform.
- snapshot() — capture current form state (cached)
- execute(actionId, value) — queue a field value with type validation
- save({ flatten? }) — write a new PDF with all queued values
applied; flatten bakes values into the
page content (no longer fillable)
- reset() — discard queued values
- close() — release resources, idempotent
- fields, pending, snapshotCache — read-only accessors
- All execute() type checks throw the existing AgentMark error
hierarchy: ActionTypeError, ActionDisabledError, ActionNotFoundError,
ExecutionError. Read-only and signature fields auto-refused.
- Defensive copy of input bytes — multiple snapshot/save calls work.
Internal type rename
- The internal extraction-result interface PdfDocument was renamed to
ExtractedPdf to free the PdfDocument name for the new public class.
Internal-only — no consumer code depended on the old name through
the public API surface.
Optional peer dependency
- pdf-lib added as an optional peer dep. Reading + extracting fields
uses pdfjs-dist (already installed); writing requires pdf-lib.
Surface a clean SnapshotError with install instructions if missing.
Tests (199 unit + 10 real-Chromium integration = 209 total)
- 12 new acroform-extractor tests (all field types, redaction,
required/read-only flags, humanization, ID schema compliance).
- 11 new PdfDocument round-trip tests (fill → save → re-extract for
every field type, error semantics, flatten path, close idempotency).
Known limitations (deferred)
- pdfjs-dist's getFieldObjects() reports only the first selected value
for multi-select listboxes. AgentMark's saved PDF DOES contain all
values (verified via direct pdf-lib reading) — only the snapshot
view under-reports. Wait for pdfjs-dist upstream support.
- Form-structure inference for non-AcroForm PDFs (the FB renewal case
— visual field labels but no AcroForm dictionary) is deferred to
a later release.
- Signature fields surface as disabled actions; AgentMark refuses to
fulfill them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
This was referenced May 10, 2026
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.
Summary
PDFs with AcroForm fields are now first-class. They snapshot as `kind: 'form'` with each field exposed as an AgentMark action; the new `PdfDocument` SDK class lets agents fill, save, and flatten them with the same `execute()` shape as the web `Page` SDK.
Stacked on top of #4 (v0.5 OCR). Merge #2 → #3 → #4 → this PR in order.
What's new
AcroForm extraction
`convertPdf()` now automatically reads form fields and sets `kind: 'form'` when they exist. Each AcroForm field becomes an `ActionDefinition` with the right AgentMark type:
PdfDocument SDK class
Mirrors the web `Page` SDK so callers' agent loops are identical regardless of surface.
Field-flag handling
`Required` and `ReadOnly` are read from page widget annotations (where pdfjs-dist surfaces `fieldFlags`) since `getFieldObjects()` doesn't expose them in v4+.
Sensitive-name redaction
Field names matching common patterns (`password`, `ssn`, `credit_card`, `cvv`, `account_num`, `token`, `secret`, `csrf`, `session`, `auth`) get `(redacted)` labels and `undefined` values.
Humanized labels
`applicant.first_name` / `firstName` / `first-name` all → `"First Name"`.
Internal type rename
The internal extraction-result interface `PdfDocument` was renamed to `ExtractedPdf` to free the `PdfDocument` name for the new public class. Internal-only — no consumer code referenced the old name through the public API.
Optional peer dependency
`pdf-lib` added as optional peer. Reading uses `pdfjs-dist` (already installed); writing requires `pdf-lib`. Throws `SnapshotError` with install instructions if missing.
Tests
Known limitations
Test plan
🤖 Generated with Claude Code