pdf-server: add save_as interact action (#580)
* pdf-server: add save_as interact action
Adds a model-callable save_as action to the interact tool that writes the
viewer's current annotated state (annotations + form values) to a new file.
Reuses the get_pages request/reply pattern: model's interact call blocks
while the viewer builds bytes via getAnnotatedPdfBytes() and posts them
back through a new submit_save_data app-only tool.
Validation runs synchronously before bothering the viewer:
- path must be absolute or file://
- isWritablePath gate (same as save_pdf)
- existence check; fails with a descriptive error unless overwrite: true
The viewer can report build errors back via submit_save_data({error}),
so the model gets a real message instead of the 45s timeout.
* pdf-server: warn model off app-only tools in descriptions
Hosts that don't filter visibility:["app"] tools from the model's tool
list will still surface these. The save_pdf one in particular is a
near-miss for the new save_as interact action — point the model at the
right one.
* pdf-server: save_as without path overwrites original
When path is omitted, save_as falls back to the displayed PDF's local
path (tracked per-viewUUID by display_pdf). Same writability gate as
the viewer's save button: isWritablePath + OS-level W_OK probe, so it
refuses on read-only mounts before the viewer roundtrip.
Requires explicit overwrite: true since the original always exists —
the model can't accidentally clobber it.
Remote PDFs have no source path stored; the error tells the model to
pass an explicit path.
This commit is contained in:
parent
38700569e7
commit
4b3f6252a4
1 changed files with 557 additions and 19 deletions
|
|
@ -188,13 +188,13 @@ When roots are ignored the server logs:
|
|||
|
||||
## Tools
|
||||
|
||||
| Tool | Visibility | Purpose |
|
||||
| ---------------- | ---------- | ----------------------------------------------------- |
|
||||
| `list_pdfs` | Model | List available local files and origins |
|
||||
| `display_pdf` | Model + UI | Display interactive viewer |
|
||||
| `interact`¹ | Model | Navigate, annotate, search, extract pages, fill forms |
|
||||
| `read_pdf_bytes` | App only | Stream PDF data in chunks |
|
||||
| `save_pdf` | App only | Save annotated PDF back to local file |
|
||||
| Tool | Visibility | Purpose |
|
||||
| ---------------- | ---------- | ------------------------------------------------------------------- |
|
||||
| `list_pdfs` | Model | List available local files and origins |
|
||||
| `display_pdf` | Model + UI | Display interactive viewer |
|
||||
| `interact`¹ | Model | Navigate, annotate, search, extract pages, fill forms, save to file |
|
||||
| `read_pdf_bytes` | App only | Stream PDF data in chunks |
|
||||
| `save_pdf` | App only | Save annotated PDF back to local file |
|
||||
|
||||
¹ stdio only by default; in HTTP mode requires `--enable-interact` — see [Deployment](#deployment).
|
||||
|
||||
|
|
@ -250,6 +250,16 @@ After the model calls `display_pdf`, it receives the `viewUUID` and a descriptio
|
|||
>
|
||||
> _Model calls `interact` with action `fill_form`, fields `[{name:"Name", value:"Alice"}, {name:"Date", value:"2026-02-26"}]`_
|
||||
|
||||
### Saving
|
||||
|
||||
> **User:** Save the annotated version as `/docs/contract-signed.pdf`.
|
||||
>
|
||||
> _Model calls `interact` with action `save_as`, path `/docs/contract-signed.pdf`. Fails if the file exists; pass `overwrite: true` to replace. The target path must be under a mounted directory root._
|
||||
|
||||
> **User:** Save my changes back to the file.
|
||||
>
|
||||
> _Model calls `interact` with action `save_as`, `overwrite: true` (no `path`). Overwrites the original — same writability gate as the viewer's save button. Only works for local files; remote PDFs need an explicit `path`._
|
||||
|
||||
## Testing
|
||||
|
||||
### E2E Tests (Playwright)
|
||||
|
|
|
|||
Loading…
Reference in a new issue