Summary
Two related problems in the 3.8.52 capability gate:
- False negative — a combo is rejected with
capability_mismatcheven when one of its members demonstrably handles the image request (200 when called directly). - Error surface — the gate's generic messages replace the upstream's actionable ones, and any 404 renders as the literal string
Model not found.
Net effect: image requests that used to fail with a clear "this profile cannot handle images" now fail with "No target has confirmed vision support" or "Model not found", which reads like the gateway is broken.
Environment
- OmniRoute Desktop 3.8.52, Windows 11
- Upstream is a local OpenAI-compatible bridge network (JetHub pools on
127.0.0.1:64646, plus a separate WorkBuddy bridge on127.0.0.1:47825)
Repro A — combo rejects a member that works
Combo deepseek v4.1 flash (strategy=priority), members in order:
workbuddy/global:deepseek-v4.1-flashworkbuddy/cn:deepseek-v4.1-flashcodearts/deepseek-v4.1-flashraccoon/sn-deepseek-v4-1-flashlobster/deepseek-flashloomy/DeepSeek-V4.1-Flashtrae_cn/deepseek-v4.1-flash
Direct call to member 1 with a 1×1 PNG (image_url, data:image/png;base64,…):
POST /v1/chat/completions model=workbuddy/global:deepseek-v4.1-flash
→ 200Same image, through the combo:
POST /v1/chat/completions model=deepseek v4.1 flash
→ 400 {"error":{"message":"No target in combo deepseek v4.1 flash has confirmed vision support for this image request","type":"invalid_request_error","code":"capability_mismatch"}}The member that works is filtered out.
Compare combo glm-5.3 flash, whose first member is workbuddy/cn:glm-5.3-flash — same shape, same image:
POST /v1/chat/completions model=glm-5.3 flash
→ 200The only difference between the two working members is the global: vs cn: prefix on the workbuddy model, i.e. one resolves a vision capability and the other does not.
Repro B — the gate hides the actionable upstream message
Direct call to codearts/deepseek-v4.1-flash with an image (bypassing the combo gate) returns the upstream's own, actionable text:
400 unsupported_content
当前 profile 未装附件处理器 @deepseek-ai/dsh-attachment-local,无法处理图片
("this profile has no attachment handler installed; cannot process images")Through the combo, the operator never sees that — the gate answers first.
Before 3.8.52 the same request surfaced the upstream message directly. From this instance's log, five days before the upgrade:
2026-10-15T08:19:12.805Z 400 loomy/MiniMax-M3 key=loomy
当前 profile 未装附件处理器 @deepseek-ai/dsh-attachment-local,无法处理图片The underlying upstream gap is old. What changed is that the operator can no longer see it.
Repro C — 404 renders as a fixed string
The status→message table is literal:
r = { 400:"Bad request", 401:"Invalid API key provided", 402:"Payment required",
403:"Permission denied", 404:"Model not found", 406:"Model not supported",
410:"Model has been shut down", 429:"Rate limit exceeded",
499:"Client disconnected", 500:"Internal server error",
502:"Bad gateway - upstream provider error", 503:"Service temporarily unavailable", … }with
404: { type: "invalid_request_error", code: "model_not_found" }So any 404 from any cause — upstream 404, catalog miss, retired model, routing miss — surfaces as:
404: {"message":"Model not found","type":"invalid_request_error","code":"model_not_found"}which points the operator at the wrong thing entirely.
Requests
- Do not filter out a target that can actually serve the request. When vision cannot be confirmed from metadata but the target would accept the image, prefer attempting it (or keep it as a last-resort candidate) over rejecting the whole combo. A metadata gap should degrade to "try it", not "refuse it".
- Let the upstream error through when the gate rejects for capability. If the request is going to fail anyway, the upstream's own message (
unsupported_content, naming the missing handler) is strictly more useful thancapability_mismatch. - Make 404 say what was actually missing — the model id, the combo, and which lookup failed — instead of the constant "Model not found".
- Register vision for
workbuddy/global:*the wayworkbuddy/cn:*is registered. 3.8.52 already did this for DeepSeek API Flash models (#16865) and Command Code; on this instanceworkbuddy/global:deepseek-v4.1-flashhandles images fine but is not advertised as vision-capable.
Why A matters most
A turns a per-member limitation into a whole-combo outage. 5 of the 7 members genuinely lack image support upstream (pre-existing — their profile has no @deepseek-ai/dsh-attachment-local), but the 2 members that do work are excluded as well, so the operator gets a hard failure where a working target existed all along.
Thanks for the very detailed report. Findings on release/v3.8.53. A: reproduced.
workbuddy/global:deepseek-v4.1-flashandworkbuddy/cn:deepseek-v4.1-flashboth resolve vision=null, and the combo gate treats null as unsupported, so the whole combo is rejected. Theglobal:/cn:prefix is not the difference. The glm-5.3 combo passes because the generic vision heuristic recognises theglm-5.3id and has no entry fordeepseek-v4.1-flash. This is not covered by PR #17243, which only aliases the official DeepSeek provider's model. We will fix it so that an unconfirmed (unknown) vision target is tried as a last resort instead of failing the whole combo, and so that the registered capability for WorkBuddy-serveddeepseek-v4.1-flashis explicit (not a global id fragment, since codearts/loomy serve the same id without image support). B: this follows from A, because the gate runs before the upstream is called; the plan is for the gate not to reject a pool that still has unknown-capability targets, so the upstream's ownunsupported_contentmessage reaches you. C: the literal "Model not found" is only the fallback when the upstream 404 has an empty message. If you can share the raw log line of a 404 you saw (provider, model, whether the body was empty), we will make it name the model id and lookup. Workaround meanwhile: mark the model as "Vision capable" in the custom model settings.