Repository navigation
Releases: BlockRunAI/dsh-clawrouter
Release list
v0.6.0 — vision, and errors that used to arrive as answers
0.6.0 — 2026-08-14
Fixed
- An upstream error the gateway relays as assistant text is now a failure, not an answer. Measured: an image request to
anthropic/claude-sonnet-5orclaude-opus-5returns HTTP 200 and streams[Error: 400 {"message":"Could not process image"}]as the model's reply. The harness sees an ordinary successful turn, the call is paid for, and the agent acts on the error string as though the model wrote it. Detection is anchored to the whole message, so an answer that merely mentions an error, or a turn that also called a tool, is untouched. The relayed status maps exactly as it would have if it had arrived as a real one. - A completed response with no content finishes with
EMPTY_RESPONSErather than as a successful empty message, matching the first-party DeepSeek adapter. Previously only a stream with no finish reason at all was caught.
Corrected
- 0.5.0 described the Anthropic vision failure wrongly. It said those models "charged, then streamed nothing — no error, no content". They stream a great deal: the upstream error, as the answer. The original observation was truncated by
headbecause the relayed text begins with two newlines, and the blank line got read as an empty response. The corrected behaviour is worse than what was published, not better.
Fixed
- A bare string was passed where the e2e vision test needed a branded
AttachmentId. The test typecheck reported it and 0.5.0 was published anyway — the check is only worth having if publishing waits for it, soprepublishOnlynow runs both typecheck programs and the unit suite.
Added
- Vision. DeepSeek serves no vision model, so this is capability rather than savings. A user message carrying an image now serializes to OpenAI content parts with the attachment inlined as a
data:URL; a message without one still serializes to a bare string, so text traffic is unchanged on the wire. Images are read through the optional attachment service, resolved per request, and several in one turn resolve concurrently. visionModelsselects which models may receive an image, defaulting to the four measured to work.
Measured
- The gateway's
visiontag cannot be trusted, and the failures are paid. Thirty-five entries carry it; ten were sent the same inline PNG. Google's three andmoonshot/kimi-k3answered correctly. OpenAI's three returned HTTP 400 after taking payment,xai/grok-4.5returned 503, and both Anthropic models charged and streamed nothing at all — no error and no content, which downstream is indistinguishable from a model that had nothing to say. A model is therefore offered image input only when the gateway tags itvisionand it appears invisionModels; the tag alone over-claims, and the list alone would keep claiming vision after the gateway retags a model.
Added
- A from-zero Docker smoke test (
npm run test:docker). Installs the published package from npm into a container that has never seen this project and asserts eleven things: that the profile composes, that the gate ships disarmed, thatlib/is in the tarball andsrc/is not, and that the npm description's model count matches the README's. Passes onlinux/arm64andlinux/amd64. Needs no wallet and no key.
Documented
dshneedspython3,makeandg++on a slim Node image. Itsnode-ptydependency has no prebuild fornode:22-slimon either architecture, so npm rebuilds from source and the install dies atCould not find any Python installation to use— a message that names neither dsh nor this plugin. Found by running the install in a clean container rather than reasoning about it; it had always worked on a developer machine.
Fixed
- The install's six
✕ missing peerlines are now explained before a user meets them. Walked the first-run path on a cleanDSH_HOMEagainst the published package:dsh plugin addprints six missing-peer errors, and the profile then composes correctly and lists both rows under--dump-config. The warnings are cosmetic — the harness supplies those packages at runtime, and every first-party bundle declares its peers the same way — but six red marks during the very first command read as a failed install.
Fixed
- npm advertised 70 models while every README said 67.
package.json's description carries the same count on the package page, where an HTML marker cannot go, so filling the markers in 0.3.13 moved the stale copy somewhere less visible rather than removing it.npm run sync:modelsowns that string now and fails loudly if the description stops matching.
Added
- Two gates that make "does the README need updating?" mechanical. Every command the plugin registers must be documented in both READMEs, read from the source so a new command cannot ship undocumented; and the npm description's model count must equal the READMEs'. Both were fed their bug and rejected it.
Added
/gate— check the safety net is actually up. The gate can be off while everything a user can see looks correct:enableddefaults tofalse, a patch layer replaces a row's wholeconfigrather than merging keys, and/reviewregisters either way — so a working/reviewis evidence the plugin loaded and no evidence at all that tool calls are inspected./gateis registered whether or not the gate is armed, which is the point; a command that appeared only when the gate worked could never report the one state worth asking about./gate drillsendsrm -rf / --no-preserve-rootthrough the risk matcher and the live reviewer — never to a tool — and reports each stage separately. A rule that stopped matching is a policy problem; an unreachable reviewer is a wallet or model problem. At runtime both collapse into "escalate", which is indistinguishable from the gate working, so the drill deliberately does not reuse the gate's own error folding.- Tests are typechecked.
tsconfig.test.jsoncoverstests/andscripts/, which the base program excluded because it emitssrcintolibwith arootDir.
Fixed
- Four latent type errors in tests that had never been checked, including a fixture using
kind: 'error'against a'reply' | 'throw' | 'hang'union — it matched no branch, silently ran the default behaviour, and passed the wrong scenario.
Fixed
- The model count said 70; the catalog exposes 67. Eight sites across both READMEs carried
<!-- br:models.chatVisible -->markers — the notation for generated content — but the script that fills them was never written, so the number froze at whatever was true the day it was typed.
Added
npm run sync:modelsrewrites every marker from the live catalog, counting throughprojectCatalogso the figure is what this route exposes rather than what the gateway lists. It refuses to write0.- Two gates, split by what each can know. An offline test asserts every marker agrees with the others, catching a hand-edit of one site; a live e2e test asserts the count matches the gateway and names
sync:modelsin its failure message. Both were fed their bug and rejected it.
Changed
tsxis a devDependency, so a maintenance script can import the plugin's own catalog projection instead of restating the filter in JavaScript.
Fixed
- "$5 covers thousands of calls" quoted only the flattering end of a range this document measures. True at the $0.002 floor, where $5 buys about 2,500 gate reviews. At the other end of the same table it buys about five 100K-context calls on Opus — a 500x spread, given as one number, in the paragraph where a reader decides how much to fund. Both figures are stated now, with the advice to fund for intended use rather than for the floor.
Added
- The funding advice is checked as arithmetic. Each promised call count must equal the funding amount divided by a price the pricing table states. The gate rejects the old slogan and, separately, a version that quotes an accurate 2,500 without its counterweight — a correct number can still mislead by being the only one offered.
Fixed
- A third pricing claim disagreed with the measured table. The compaction section said a ~100K-token summarization runs $0.50 on Claude Opus 5 and $0.014 on DeepSeek V4 Flash. Live 402 quotes at that size read $0.901 and $0.0262 — both understated by about 1.8x, the signature of the per-token estimate this project disproved in 0.3.2. The argument for moving compaction to a cheap model gets stronger, not weaker: the real gap is 34x.
Added
-
The README's prose is now checked against its own pricing table. Three releases in a row shipped a dollar figure in prose that contradicted the table a few sections above it, so the check is mechanical rather than editorial. Above the flat floor the quote is linear in input size — 100K/112K measured 0.89 for Opus and 0.90 for DeepSeek against an input ratio of 0.893 — which gives a tight enough bound to catch a stale estimate.
The first version of this gate asserted only that the 100K figure fell between the 22K and 112K quotes. Fed the actual bug, it passed: $0.50 sits inside that band. A bound that admits the defect it was written for is worse than no bound, because it reports the claim as verified. Both stale figures are now used as fixtures, per language.
Fixed
-
The READMEs contradicted themselves about pricing. One section said, correctly since 0.3.2, that this route is priced per request rather than per token. The "Honest notes" section still carried the original 0.1.0 claim — "provider cost plus a flat $0.001/request" — which 0.3.2 disproved. The correction had been applied in one place and not the other, for eleven releases, in the section named for being honest.
The conclusion was never wrong: do not route a cache-warm loop through this gateway. The reasoning now matches the measurements, and the comparison is starker than the old one — ...