Bundle
dsh-tool-pdf
DeepSeek Harness (dsh) plugin — read_pdf tool for PDF text extraction. Read and extract text from PDF files with AI agents / LLM tools, built on PDF.js (unpdf).
- Source
- Jeffine322
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 2 days ago
Readme
# dsh-tool-pdf
English | [中文](README.zh.md)
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) plugin that adds a **`read_pdf` tool** for **PDF text extraction** — lets an AI agent (LLM) read, extract, and summarize PDF files, page by page.
Built on [unpdf](https://github.com/unjs/unpdf) (a serverless build of Mozilla [PDF.js](https://mozilla.github.io/pdf.js/)). It reads PDF bytes through the harness filesystem seam (`ctx.fs`), so it obeys the same workspace and sandbox policy as the built-in `read` tool.
**Keywords:** DeepSeek Harness · dsh · dsh plugin · read_pdf · PDF reader · PDF text extraction · extract text from PDF · AI agent tool · LLM tool · PDF.js · unpdf
> **`read_pdf` is a model-facing tool, not a UI button.** It does not appear as a menu item or a settings entry. It shows up in the conversation when the model actually reads a PDF — you trigger it by asking the model to read one.
## Quick start
### 1. Install into the `web` profile
`dsh web` boots the **`web` profile**, so install the plugin there (not into a new profile):
```sh
dsh plugin --profile web add github:Jeffine322/dsh-tool-pdf
```
### 2. Start the Web UI
```sh
dsh web
```
### 3. Configure a model and a workspace
In the browser: **Settings → Models** (enter your DeepSeek API key, then save), then **Choose workspace** and select the directory that contains your PDFs.
### 4. Ask the model to read a PDF
Start a session and say:
> Read `/path/to/report.pdf` and summarize it.
The model calls `read_pdf({ file_path: "/path/to/report.pdf" })`, and you see the extracted text in the conversation.
### Verify it is installed
```sh
dsh web --dump-config
```
If the output contains a `tool-pdf` row (`- id: tool-pdf`, `name: dsh-tool-pdf`), the plugin is mounted.
## Install details
Requires a `dsh` installation with the `dsh` CLI on your PATH.
```sh
# From this repo's git URL
dsh plugin --profile <name> add github:Jeffine322/dsh-tool-pdf
# Or from a local checkout
dsh plugin --profile <name> add ./dsh-tool-pdf
```
The package declares `dsh.bundle`, so `dsh plugin` appends it to the profile's bundle layers automatically. The built `dist/index.mjs` is committed to this repo, so a git install needs no build step and no `allowBuilds` approval.
## How it works
PDF is a binary format, not plain text: a page's visible characters are stored as **glyph codes** in a content stream, mapped back to Unicode through each font's encoding table. The built-in `read` tool therefore refuses PDFs as binary — `read_pdf` decodes them.
Parsing is delegated to [unpdf](https://github.com/unjs/unpdf), which bundles Mozilla's [PDF.js](https://mozilla.github.io/pdf.js/) (the engine Firefox uses), so this plugin never touches PDF's binary internals. The pipeline is:
```text
read_pdf({ file_path })
→ extension gate (must be .pdf)
→ ctx.fs.resolve + stat # regular file; missing/dir → typed error
→ ctx.fs.readBytes(…, maxFileBytes) # obeys workspace/sandbox policy
→ re-view as plain Uint8Array # pdf.js rejects a Node Buffer
→ getDocumentProxy(bytes) # load the document
→ extractText(pdf, { mergePages: false }) # text per page
→ capPages(…, maxOutputChars) # bound the total output
→ formatPdfReadOutput(…) # <path>/<pages>/<content> envelope
→ return text to the model + emit fs/observed
```
Two bounds keep a large PDF from blowing up the model context: `maxFileBytes` (the file read) and `maxOutputChars` (the extracted text). It extracts plain text only — no layout, tables, or images (see [Limitations](#limitations)).
## Config
| Field | Default | Meaning |
|---|---|---|
| `maxFileBytes` | `52428800` (50 MiB) | Maximum PDF file bytes read per call; larger files fail with `FS_TOO_LARGE`. |
| `maxOutputChars` | `100000` | Maximum extracted-text characters returned per call; overflow truncates the last page. |
To change them, override the row in your profile's `cordis.patch.yml` (a patch replaces the whole config, so restate every key you change):
```yaml
- id: tool-pdf
name: dsh-tool-pdf
config:
maxFileBytes: 10485760
maxOutputChars: 50000
```
## Development
```sh
pnpm install
pnpm build # tsdown bundles src/*.ts → dist/index.mjs (committed)
```
Structure:
```
src/index.ts # the plugin: name/inject/Config/apply, registers read_pdf
src/extract.ts # unpdf extraction + output capping + envelope formatting
cordis.patch.yml # bundle layer: inserts the plugin row
```
The plugin keeps `@deepseek-ai/dsh-tools`, `@deepseek-ai/dsh-fs`, and `@deepseek-ai/cordis` as **external imports** (via tsdown's `deps.neverBundle`) rather than bundled copies, so at runtime it shares the running harness's single service instances. They are therefore not declared as npm dependencies: Node resolves them against the dsh installation's `node_modules` when the plugin loads. `unpdf` and `@deepseek-ai/schemastery` are ordinary dependencies.
## Limitations
- **No layout or table structure** — extraction returns plain text in reading order; tables, columns, and images are not reconstructed.
- **Scanned PDFs have no text layer** — raster-only pages extract to empty text; OCR is out of scope.
- **Encrypted PDFs** — password-protected documents are reported as not readable.
## License
MIT
Install
dsh plugin --profile web add github:Jeffine322/dsh-tool-pdf#6fe621eccace53321e9fc5ce4be51fa65aca5051
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-tool-pdf from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.