@argszero/cordis-plugin-read-image-guidance
v0.1.1
Published
Actionable read_image refusal for dsh: when the harness refuses to read an image because the resolved model route does not declare image input, turn that opaque 'switch to an image-capable model' message into concrete config guidance (declare "input: [tex
Maintainers
Readme
@argszero/cordis-plugin-read-image-guidance
Actionable read_image refusal for dsh.
When the harness refuses to read an image because the resolved model route does
not declare image input, it currently tells you to switch models — but the
model is usually fine. The declaration is what's missing. This plugin turns
that opaque refusal into concrete config guidance: declare
input: [text, image] on the model's models entry (or extend its catalog
entry), instead of sending you on a model-swap hunt.
The gap
read_image's capability gate (assertImageCapableRoute in dsh-tool-fs)
reads the declared inputModalities of the resolved route. For custom
providers with no catalog entry, the resolution chain
(declaredInput ?? base?.input ?? defaultInput) falls back to ['text'], so a
vision-capable model is refused with:
cannot read "" as an image: model "" does not declare image input; switch to an image-capable model to read images
The correct fix is input: [text, image] on the model entry — not a model swap.
This is the request-① gap of discussion #6049 (the inputModalities family,
6th member: the tool-read-side capability gate).
How it works
It wraps tools/execute (the same around-dispatch seam as the in-tree
guard/timeout-policy). When a read_image result carries the exact refusal
signature, it rewrites the model-facing message into actionable guidance while
preserving the isError flag and the structured error (so retry/replay code
paths stay routable). Every other tool result passes through untouched.
Usage
Mount as a dsh bundle plugin (defaults are sane, rewrite on):
dsh plugin --profile web add github:argszero/cordis-plugin-read-image-guidanceTune via a profile layer:
- set:
- id: read-image-guidance
config:
rewrite: false # observe-only, no rewriteConfig
| field | type | default | description |
|-------|------|---------|-------------|
| rewrite | boolean | true | Set false to observe-only (no rewrite). |
Scope
- Rewrites only the exact
read_imagerefusal marker (does not declare image input; switch to an image-capable model to read images). - Never drops the refusal, never touches
isError, never touches other tools. - No native/OCR dependency in v0.1 (an OCR-fallback form is a separate, heavier candidate; this one is the "declaration detection + actionable error" half).
Compatibility
- dsh
0.1.5-alpha.1(verified againstpackages/fs/tool-fs/src/read-image.tsandpackages/core/tools/src/index.ts). peerDependencies:@deepseek-ai/cordis ^4.0.2,@deepseek-ai/dsh-tools >=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0.
A note on the peer range
>=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0Every dsh release published today is a prerelease, and a semver comparator admits
prereleases only when they share its own major.minor.patch tuple. That makes two forms
wrong, and both have to be avoided:
// Matches nothing at all: 0.1.2-rc.1 sorts below 0.1.2, and every other
// prerelease has a different tuple. -> ETARGET, the package cannot be installed.
">=0.1.2"
// Only 0.1.2-rc.1: a user on the 0.1.5 line gets ERESOLVE.
">=0.1.2-rc.1 <0.2.0"
// What we ship: one comparator per supported tuple line.
">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"The npm latest tag for @deepseek-ai/dsh is on the 0.1.2 line while next/alpha
point at 0.1.5, so both comparators are needed. test/peer-range.test.mjs fails if
either bad form comes back.
Test
npm testMIT.
