deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:SeerableOfficial/dsh-anydoc-markdown
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
Document → Markdown + vision image description plugin for DeepSeek Harness (dsh).
Note: This plugin has only been tested against Deepseek harness version v0.1.1-rc.2. It might not work on the latest version. Ask agent to modify the plugin if it is incompatible with your version.
Converts documents (Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, PDF)
into clean GitHub-Flavored Markdown using the Rust
firecrawl-anydoc crate, and describes
every embedded image with a vision model so a text-only model can reason about
the document's imagery.
Design target. anydoc is a pure local, dependency-free Rust converter: its core has no network calls and no
llm_clientparameter. But it exposesanydoc.to_document(), which returns the full document model plus the embedded binary assets (images, MIME types, and alt-text markers) onDocument.assets. This plugin replicates the MarkItDownllm_client+llm_modelpattern for anydoc: intercept the image assets, pass them to a VLM (OpenAI'sgpt-4oor a local OpenAI-compatible model), and substitute the image placeholders in the output Markdown.
.
Images the converter drops (empty alt text) are appended as a described list.maxImagesPerRequest
(default 10) and each batch is a separate request, so vision performance
is not degraded by a giant multi-image prompt. Configurable.convert_document tool for agents — the agent converts any document to
Markdown on demand (offset/limit-free; returns the full Markdown, capped).markdownOutputDir is set,
convert_document creates a subfolder there named after the Markdown file
(e.g. report/) and saves both the .md file and every extracted embedded
image as a PNG inside it. The Markdown references those on-disk PNGs by
relative filename, so the .md and its images are portable together.convert_document on each. The converter saves the Markdown (and its images)
to markdownOutputDir and returns the .md file path.@deepseek-ai/* imports (always
loads), and the client half rides the standard __ModuleLoader__ slot/RPC
seams.pip install -r converter/requirements.txt # firecrawl-anydoc + pdfplumber + Pillow
dsh plugin --profile web add dsh-anydoc-markdown
# restart dsh web
pdfplumber + Pillow are only needed to describe images embedded in PDFs.
If they're unavailable, PDFs still convert (text only) and the rest of the vision
pipeline is unaffected.
convert_document tool with the path to a document:
convert_document(file_path: "report.pptx").converter/anydoc_vlm.py) against
anydoc, then describes every embedded image via the VLM in batches.Documents attach through the same whole-page drag the harness uses for
images: drag any number of documents (.docx .pdf .pptx .xlsx .odt .rtf .epub .csv .txt .md .html) onto the page and release. The harness's own drop overlay
(the one that says "Drag images and documents here to add them") invites the
drop, and the plugin shows the attached documents as cards in the composer's
input dock strip (a line above the composer card). Images dropped at the same
time keep using the harness's built-in image attach.
attach.upload RPC channel (/dsh-anydoc by default). Each uploaded
document is written to attachmentsDir and shown as a card (name + size +
remove) in the dock strip. The cards clear automatically once you submit
the message — the documents are already saved to disk, so the agent still
gets their paths on the next step.convert_document on each
path below"). It never injects the converted Markdown.convert_document(file_path: "<source path>") on each. The
tool converts the document, saves the Markdown to markdownOutputDir, and
returns the .md file path (savedPath) alongside the Markdown.All fields have sensible defaults — install and use without touching any. Tune only what you need. Every knob is a validated
cordis.patch.ymlfield; none are hardcoded.
| Field | Default | Description |
|---|---|---|
converterPython |
python |
Python interpreter used to run the bundled wrapper. |
converterPath |
converter/anydoc_vlm.py |
Path to the bundled wrapper (in this dir). |
maxImagesPerRequest |
10 |
Max images per vision request. Setup requirement — never lump all images into one request. |
maxChars |
60000 |
Markdown truncation cap returned to the model. |
timeoutMs |
120000 |
Cooperative tool-call timeout budget. |
visionEndpoint |
'' |
OpenAI-compatible vision endpoint; empty = offline metadata descriptions. |
visionModel |
'' |
Vision model id (e.g. gpt-4o); empty = offline metadata descriptions. |
visionApiKeyEnv |
OPENAI_API_KEY |
Env var supplying the vision API key. |
attachmentChannel |
/dsh-anydoc |
Generic RPC channel the composer uses to attach documents. |
attachmentsDir |
<DSH_HOME>/attachments |
Directory uploaded SOURCE documents are written to; the agent is handed these paths. |
markdownOutputDir |
'' |
Directory where convert_document creates a subfolder named after the Markdown file (e.g. report/) and saves both the .md file and every extracted embedded image (as a PNG) inside it, returning the .md file path (savedPath) plus imageDir/savedImages. Empty = return the Markdown inline and save nothing. |
maxFileBytes |
26214400 (25 MiB) |
Per-upload byte cap for document attachments (decoded size). |
allowedAttachExtensions |
['.docx','.doc','.pdf','.pptx','.ppt','.xlsx','.xls','.odt','.rtf','.epub','.csv','.txt','.md','.html','.htm'] |
Document extensions the composer accepts. |
- id: dsh-anydoc-markdown
config:
maxImagesPerRequest: 10
visionEndpoint: https://api.openai.com/v1
visionModel: gpt-4o
| Detected image | What happens |
|---|---|
Embedded asset (bytes in Document.assets) |
described by the VLM and substituted as . When markdownOutputDir is set it is also re-encoded to a PNG and saved into the per-document subfolder, and the reference points at that file. |
| Embedded, empty alt (anydoc drops it) | appended as a described reference list at the end. Also persisted as a PNG when markdownOutputDir is set. |
| PDF embedded raster | PDFs have no anydoc document model, so each raster is recovered from the PDF bytes with pdfplumber, re-encoded, and appended as a described reference list. Also persisted as a PNG when markdownOutputDir is set. |
| External URL image | left as an ordinary Markdown image. |
The VLM call is OpenAI-compatible; point visionEndpoint/visionModel at any
endpoint speaking the chat-completions wire format (OpenAI, a local
Ollama VL model, an OpenAI-compatible proxy, etc.). With
visionEndpoint: '' (default) the wrapper uses offline metadata descriptions.
The Python wrapper converter/anydoc_vlm.py is where
the conversion + image description happens (see
converter/README.md for the full write-up):
describe_many() slices them
into groups of maxImagesPerRequest (default 10) and issues one
chat-completions request per group, so 23 images yield [10, 10, 3] across 3
requests rather than one giant multi-image prompt.N descriptions. Array responses wrapped in prose or a
fenced JSON block are still parsed. Only if every retry fails does that batch
degrade to an offline metadata description — and the note then states why
(vision request failed after retries / vision unavailable after retries)
instead of claiming the model is unconfigured.pdfplumber and described the
same way. Needs pdfplumber + Pillow; fails closed to text-only Markdown if
they're missing. Scanned pages need --ocr hosted (Firecrawl Parse).[Offline metadata description - no vision model was configured.]). No network call, no key required.python converter/anydoc_vlm.py <path> \
[--format NAME] [--ocr reject|hosted] [--max-images N] \
[--vision-endpoint URL] [--vision-model MODEL] [--api-key KEY] \
[--save-images-dir DIR] [--mock-vision] [--json] [--out FILE]
| Option | Default | Description |
|---|---|---|
--format |
auto | Explicit format name (only needed for signature-less formats like CSV). |
--ocr |
reject |
hosted routes scanned PDF pages to Firecrawl Parse. |
--max-images |
10 |
Max images per vision request. |
--vision-endpoint |
env ANYDOC_VISION_ENDPOINT |
OpenAI-compatible vision endpoint. |
--vision-model |
env ANYDOC_VISION_MODEL |
Vision model id (e.g. gpt-4o). |
--api-key |
env OPENAI_API_KEY |
Vision API key. |
--save-images-dir |
unset | Persist every extracted embedded image as a PNG in this directory and rewrite the Markdown image references to point at it. |
--mock-vision |
off | Force the offline description (for testing). |
--json |
off | Emit {"markdown", "meta"} instead of plain Markdown. |
--out |
stdout | Write the Markdown to a file. |
Verify the batching with the bundled test document (23 images, max 10):
python converter/anydoc_vlm.py tests/multi.docx --mock-vision --max-images 10 --json
# vision_batch_sizes: [10, 10, 3], vision_requests: 3
dsh-anydoc-markdown/
├── index.js # host plugin: `convert_document` tool + `attach.upload` RPC channel
├── lib/client.js # web client: document drop + dock-strip cards (no tool-row button/overlay)
├── cordis.patch.yml # bundle config (all knobs)
├── package.json # dsh.bundle.patch + dsh.client, files
├── converter/
│ ├── anydoc_vlm.py # anydoc + batched-vision engine (the "llm_client" seam)
│ ├── requirements.txt # firecrawl-anydoc + pdfplumber + Pillow (PDF images)
│ └── README.md # wrapper docs + how the batching works
└── README.md
The Python wrapper is where the real work happens (see
converter/README.md): anydoc.to_document() yields the
embedded assets, VisionClient.describe_many() batches them (≤ 10 per request),
and substitute() swaps the image placeholders for the descriptions.
python converter/anydoc_vlm.py tests/multi.docx --mock-vision --max-images 10 --json
node --check index.js
python -m py_compile converter/anydoc_vlm.py
--ocr hosted leave the machine (Firecrawl Parse). The vision call itself goes
only to the configured visionEndpoint.attachmentsDir (persistent) and the
converted Markdown (plus its extracted image PNGs) to a per-document
subfolder inside markdownOutputDir; both are configurable and written only
when you configure them. Filenames are sanitized (no path traversal),
non-allowlisted extensions are rejected, and files are capped by
maxFileBytes.MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。