You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Token meter prices images by reference length, so image-heavy sessions never compact and die on provider request limits
#13063
On a vision route whose adapter does not declare image pricing (imageRequestPricing() returns undefined), ctx.tokenMeter prices each image block from its JSON reference length — roughly 60 tokens — while the actual request carries the image bytes inline as base64. Image-heavy sessions therefore read as almost empty on the context meter, automatic compaction never fires, and the request keeps growing until the provider closes the stream. The turn then dies with an opaque transport error (fetch failed, code UNKNOWN).
Measurements
Same account, same model, same session history, replaying through the adapter:
Request
Result
1.99M chars of text only (≈945k input tokens)
accepted
20 images, 6.3 MB raw (≈8.4 MB base64)
accepted, first token at 15.5 s
50 images, 15.7 MB raw
stream cut at 15.1 s (NGHTTP2_ENHANCE_YOUR_CALM)
real workload after 46 min: 47 images / 12.8 MB in the surface
every subsequent turn failed
For those 47 images the meter prices roughly 47 × 60 ≈ 2.8k tokens. At thresholdRatio: 0.8 of a 1,000,000-token window, compaction-basic never triggers — the pressure number only ever reflects the text.
Route pricing replaces this only when the adapter's imageRequestPricing() returns a value (_routeImagePricing() in measure()). When it returns undefined — which is the default for third-party adapters — the fixed reference-length heuristic stands, and the real payload size is invisible to every consumer of measure(): the context indicator, compaction-basic pressure, and the retention/target budgets.
Suggested direction
Byte-based fallback: when the route declares no image pricing, price a retained image from its declared bytes (e.g. ceil(bytes / 4) for base64 density, or a configurable bytes-per-token ratio) instead of the JSON reference length. That alone makes the pressure number reflect reality for any adapter.
Make imageRequestPricing mandatory for vision routes: refuse registration when inputModalities includes image and imageRequestPricing() returns undefined, so an adapter cannot silently opt out of pressure accounting.
Request-size budget: token pressure is the only trigger today. Some providers cut on request-body size rather than token count, so a byte budget (or a REQUEST_SIZE_EXCEEDED signal alongside CONTEXT_WINDOW_EXCEEDED) would let compaction/recovery act before the provider kills the stream.
Impact
Screenshot / OCR / page-scanning workflows on routes without explicit image pricing run blind: the context display shows a few percent, compaction never fires, and the turn dies with a transport error that names nothing. The only workaround is a manual /compact every ~40 minutes.
Environment
@deepseek-ai/dsh 0.2.2-rc.1 (also reproduced on 0.2.1-rc.2)
Linux x64, Node 22 / 24
vision route: third-party adapter (dsh-qwenwork-connector 0.1.0, qwenwork/frontier-1), which returns undefined from imageRequestPricing()
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
What happens
On a vision route whose adapter does not declare image pricing (
imageRequestPricing()returnsundefined),ctx.tokenMeterprices each image block from its JSON reference length — roughly 60 tokens — while the actual request carries the image bytes inline as base64. Image-heavy sessions therefore read as almost empty on the context meter, automatic compaction never fires, and the request keeps growing until the provider closes the stream. The turn then dies with an opaque transport error (fetch failed, codeUNKNOWN).Measurements
Same account, same model, same session history, replaying through the adapter:
NGHTTP2_ENHANCE_YOUR_CALM)For those 47 images the meter prices roughly
47 × 60 ≈ 2.8ktokens. AtthresholdRatio: 0.8of a 1,000,000-token window,compaction-basicnever triggers — the pressure number only ever reflects the text.Where
@deepseek-ai/dsh-token-meter,estimate.js:Route pricing replaces this only when the adapter's
imageRequestPricing()returns a value (_routeImagePricing()inmeasure()). When it returnsundefined— which is the default for third-party adapters — the fixed reference-length heuristic stands, and the real payload size is invisible to every consumer ofmeasure(): the context indicator,compaction-basicpressure, and the retention/target budgets.Suggested direction
bytes(e.g.ceil(bytes / 4)for base64 density, or a configurable bytes-per-token ratio) instead of the JSON reference length. That alone makes the pressure number reflect reality for any adapter.imageRequestPricingmandatory for vision routes: refuse registration wheninputModalitiesincludesimageandimageRequestPricing()returnsundefined, so an adapter cannot silently opt out of pressure accounting.REQUEST_SIZE_EXCEEDEDsignal alongsideCONTEXT_WINDOW_EXCEEDED) would let compaction/recovery act before the provider kills the stream.Impact
Screenshot / OCR / page-scanning workflows on routes without explicit image pricing run blind: the context display shows a few percent, compaction never fires, and the turn dies with a transport error that names nothing. The only workaround is a manual
/compactevery ~40 minutes.Environment
@deepseek-ai/dsh0.2.2-rc.1 (also reproduced on 0.2.1-rc.2)dsh-qwenwork-connector0.1.0,qwenwork/frontier-1), which returnsundefinedfromimageRequestPricing()All reactions