Repository navigation
[Bug] Custom-provider vision capability is ambiguous when model metadata is missing #1024
Description
Client or integration
Direct HTTP/API client through the OpenCodex proxy (/v1/responses, /v1/models).
Area
Proxy and routing · Catalog / models · Vision sidecar · Custom providers
Summary
This issue now tracks only the remaining TokenRouter case:
TR/moonshotai/kimi-k3-free
The previously reported OpenCode Zen vision cases were investigated and resolved by #1070. That PR classified the Zen models individually, including confirming that mimo-v2.5-free is natively vision-capable and that nemotron-3-ultra-free is text-only and should use the vision fallback.
The remaining failure is different because TR is a custom TokenRouter provider in the reporter configuration rather than a built-in OpenCodex registry provider. OpenCodex therefore has no canonical registry metadata from which to determine whether moonshotai/kimi-k3-free supports image input or requires the vision sidecar.
In the reporter's configuration, the model returns HTTP 200 but does not correctly identify a deterministic image, while another model on the same TokenRouter provider succeeds with the same request.
Reproduction
-
Configure the custom
TRprovider for TokenRouter. -
Start OpenCodex.
-
Send a
/v1/responsesrequest using:TR/moonshotai/kimi-k3-free -
Include a 16×16 opaque red PNG as the only
input_image. -
Ask the model to identify the dominant color.
-
Observe that the request returns HTTP 200 but does not correctly answer
RED. -
Repeat the same request with:
TR/qwen/qwen3.8-max -
Observe that the control model correctly processes the same image.
Version
Originally reproduced on @bitkyc08/opencodex 2.10.0.
The OpenCode Zen portion of the original report has since been resolved on dev by #1070. This issue remains open only for the custom TokenRouter route.
Operating system
Windows 11, using the Codex runtime through the local OpenCodex proxy.
The remaining behavior is expected to be provider/routing-specific rather than Windows-specific.
Provider and model
Custom provider:
TR → TokenRouter
Remaining affected model:
moonshotai/kimi-k3-free
Known working control on the same custom provider:
qwen/qwen3.8-max
Remaining problem
Custom-provider model rows do not necessarily carry authoritative modality metadata.
For a built-in provider, OpenCodex can maintain model-specific capability information such as:
- native image-input support;
- known text-only models;
- vision-sidecar requirements.
For a custom provider such as TR, OpenCodex cannot safely infer those properties from the provider name or model lineage alone.
The remaining question is therefore not whether the already-fixed Zen classifications should be expanded. It is how OpenCodex should handle missing or uncertain image capability metadata for custom-provider models without either:
- sending images directly to a text-only upstream, or
- unnecessarily replacing native image input with a sidecar for a model that actually supports vision.
Expected behavior
OpenCodex should have a deterministic policy for custom-provider models whose image-input capability is unknown.
A valid solution could include one or more of:
- preserving authoritative modality metadata returned by the custom provider's model catalog;
- allowing explicit per-model
inputModalities/ vision capability configuration; - allowing explicit per-model vision-sidecar classification for custom providers;
- exposing unknown modality state clearly rather than treating unknown as proven vision support;
- ensuring the runtime and
/v1/modelsexpose the same effective capability decision.
The implementation must not globally classify models by name or vendor lineage when the actual custom endpoint may expose different capabilities.
Acceptance criteria
TR/moonshotai/kimi-k3-freereceives deterministic image handling based on actual or explicitly configured capability evidence.- A custom-provider model with confirmed native vision support continues receiving the original image directly.
- A custom-provider model explicitly classified as text-only uses the vision fallback rather than receiving unsupported image content.
- Unknown capability is not silently converted into false capability certainty.
/v1/modelsand request-time routing agree on the model's effective image capability.- Regression coverage includes at least:
- one custom-provider text-only model;
- one custom-provider native-vision model;
- one custom-provider model with unknown capability.
Resolved portions of the original report
- OpenCode Zen model classification: resolved by fix(zen): strip images for the Zen models that reject them (#1043) #1070.
opencode-zen/mimo-v2.5-free: verified vision-capable.opencode-zen/nemotron-3-ultra-free: classified text-only.- The broader Zen
noVisionModelsgap: resolved by fix(zen): strip images for the Zen models that reject them (#1043) #1070.
Those cases should not be considered acceptance criteria for this issue anymore.
Cross-references
- [Bug]: Vision sidecar never triggers for NVIDIA NIM text-only models (registry
nvidiaentry missingnoVisionModels) #956 / stack 7/7: NIM vision classification, service repair, and the qwen3.8-max rename #980 — NVIDIA NIM modality classification work. - fix(zen): strip images for the Zen models that reject them (#1043) #1070 — resolves the OpenCode Zen portion of this report.
- [Bug]: No management-plane support for provider
headers— custom providers needing client fingerprints (Agent Router) 401 after headers are lost #959 / feat(providers): manage provider headers via PATCH and ocx provider edit --headers (#959) #1033 — custom-provider header management; related management surface but separate from modality capability.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Aug 4, 2026
Automated translation bookkeeping — detected language: English.
github-actions commentedon Aug 4, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
brunoflma commentedon Aug 4, 2026
Client or integration
Codex runtime via the local OpenCodex proxy:
/v1/responsesfor execution and/v1/modelsfor catalog visibility.Area
Proxy and routing · Catalog / models · Vision sidecar · Provider adapter
Summary
Following the NVIDIA-specific fix recorded in #956 (PR #980 merged into
devas7343f0ba5914a96aa9eab0a1397bfa5454c286bc), a similar class of regression persists for other text-only or capability-uncertain models. These models do not advertise reliable modality coverage, yet they occasionally attempt to answer image requests without any sidecar guidance and therefore deliver either empty responses, hallucinated confidence, or explicit inability statements.Four models in my environment are confirmed to produce HTTP 200 responses without recognizing the deterministic image:
nvidia/nvidia/nemotron-3-ultra-550b-a55bopencode-zen/mimo-v2.5-freeopencode-zen/nemotron-3-ultra-freeTR/moonshotai/kimi-k3-freeControls on the same payload and agents answer "RED" correctly:
anthropic/claude-sonnet-5,anthropic/claude-opus-5,DW/devworld/deepseek-v4-pro-r,TR/qwen/qwen3.8-max,google-antigravity/gemini-3.6-flash, plus both Agent Router transportsAGR-OAIandAGR. The general request-build, authentication, and proxy routing paths work; the divergence is isolated to capability attribution and sidecar invocation for the emerging providers.This sits alongside #956 rather than duplicating it: the NVIDIA path was repaired, but the corresponding decision surface now depends on per-provider hints that are not visibly synchronized with catalog state, registry confidence, or the exported configuration consumed by the Codex client.
Reproduction
@bitkyc08/opencodex2.10.0 and start the proxy (ocx start).data:image/png;base64,inside a/v1/responsescall and ask for the dominant color.REDresponse under the same conditions.Version and runtime
nvidiaentry missingnoVisionModels) #956 merged and tested on thedevbranch — this issue therefore tracks what remains uncovered after that stack.Evidence supporting root-cause hypothesis
/v1/modelsrows for the four failing models showinputModalities: ["text"]or omit the field, so Codex grips only text-side affordances before any sidecar can intervene.inputModalitiesunless explicitly written, while the server-side registry keeps hint fields private to its import path — causing an off-by-one between runtime behavior and what users see in dashboards or clients.[vision]/sidecar traces, so no arbitration agent is invoked before the upstream rejection or hallucination.Impact
headers— custom providers needing client fingerprints (Agent Router) 401 after headers are lost #959’s header work resolves transport for secrets, but does not address this capability-versus-catalog shadow state.Suggested fix
noVisionModels, and catalog export outputs, and make the runtime and export interfaces both read from it./v1/modelsand/v1/responsesfor every known provider family in the local catalog build, so missing modality flags are caught before release.Cross-references
nvidiaentry missingnoVisionModels) #956 — NVIDIA/NIM vision classification fix (26 text-only, 16 vision-capable audited models).headers— custom providers needing client fingerprints (Agent Router) 401 after headers are lost #959 — Provider headers management gap merged under feat: manage provider custom headers via PATCH and ocx provider edit --headers #961; excludes vision capability semantics discussed here.Checks
[-]Post-#956: vision sidecar gating divergence for kimi-k3/mimo/nemotron models (ably silent response when noVisionModels is semi-recognised)[/-][+]Post-#956 vision sidecar divergence: mimo-v2.5, kimi-k3-free, nemotron probes fail to leverage noVisionModels (controls work)[/+][-]Post-#956 vision sidecar divergence: mimo-v2.5, kimi-k3-free, nemotron probes fail to leverage noVisionModels (controls work)[/-][+][Bug]: post-#956 vision sidecar gaps on nemotron/mimo/kimi probes (controls deterministic)[/+]docs(devlog): triage every open bug issue and PR against dev
docs(devlog): fold four audit rounds into the triage unit
docs(devlog): move three inferred PR-issue links out of the literal l…
fix(zen): strip images for the Zen models that reject them (lidge-jun…
lidge-jun commentedon Aug 6, 2026
Sorry for the delay on your follow-up — it deserved a per-model answer rather than a general one, so here it is against what actually shipped in v2.10.2 (now current on npm).
Your four probes have three different dispositions:
opencode-zen/nemotron-3-ultra-free— fixed. It is in the text-only exception list now, so the proxy strips the image and the sidecar describes it instead of forwarding a part the endpoint rejects. That came from a live probe againstopencode.ai/zen/v1on 2026-08-05, recorded indevlog/_plan/260805_bug_fix_stack/002_zen_modality_probe.md. Please re-test on v2.10.2.opencode-zen/mimo-v2.5-free— deliberately excluded, and I want to check your reading against ours. That same probe foundmimo-v2.5-freeaccepts images, so it is intentionally absent from the list, with a negative assertion intests/provider-registry-parity.test.tsto keep it that way. The reasoning: adding it would silently replace a real image with a caption, which is worse than a loud 400.Your report describes it returning 200 while not recognizing the red-image probe — that is a different failure from rejecting the image. If the model accepts the part but answers as though it were blind, the honest classification is "accepts images, poor vision", and stripping the image would make our behavior worse, not better. If you disagree, the thing that would change my mind is a capture showing the raw request we sent and the model's response — specifically whether the image part was present on the wire.
nvidia/nvidia/nemotron-3-ultra-550b-a55b— the NVIDIA member was addressed by the #956 fix you cited; re-test on v2.10.2 and tell me if it still fails.TR/moonshotai/kimi-k3-free— still blocked on you.TR(TokenRouter) is not a built-in registry provider, so its behavior comes entirely from your own provider config. Without that block, redacted, I cannot tell whether the models list,noVisionModels, or the input modality metadata is the gap. This is the one item where I genuinely cannot progress without you.To be direct about the state: this issue is now partly fixed, partly a disagreement about classification, and partly waiting on one config paste. Keeping it open for the
TRhalf. Your probe methodology — deterministic red image, text control first — is what made the Zen list possible, so thank you for that.brunoflma commentedon Aug 6, 2026
Retested on v2.10.2 (npm, Windows 11, Codex 0.146.1, same 16×16 opaque red PNG, text control first, 5 trials each). Three of the four are resolved, and one of them resolves against me — details below, including the wire capture you asked for.
1.
opencode-zen/nemotron-3-ultra-free— confirmed fixedText control
RED, image probe 5/5RED. Closing this one from my side.2.
nvidia/nvidia/nemotron-3-ultra-550b-a55b— confirmed fixed by #956Image probe 5/5
RED. My first control run returned a transient upstream 503 (ResourceExhausted: Worker local total request limit reached (32/32)) — NIM capacity, not modality; a clean re-run gave controlRED, image1/1 RED. Closing this one too.3.
opencode-zen/mimo-v2.5-free— you were right, and my original report was wrongYou asked what would change your mind: a capture showing whether the image part was on the wire. Here it is, and it vindicates your classification.
The cause was on my side. My
config.jsoncarried a local override:That entry predates the report — it is byte-identical in my 2026-08-03 config backup, one day before I filed this. So on the original probe my own config stripped the image before it ever reached MiMo, and the sidecar caption is what the model answered from. "200 but doesn't recognize the red image" was my configuration, not the model being blind. I should have diffed my local overrides against the registry before filing that half.
After removing the local override,
mimo-v2.5-freereturns 5/5REDwith the image forwarded natively.Wire capture from
ocx debug provider, same probe, two models side by side:The probe's
data:URL is exactly 130 characters. The cleanest comparison is the two image rows against each other — identical prompt, identicalmessageCount=1, same adapter, same host: 407 vs 307 bytes. That ~100-byte gap is the image payload. MiMo carries it;nemotron-3-ultra-freedoes not, because it was stripped and a caption put in its place. (Comparing each model to its own control is muddier — my image prompt is ~22 characters longer than my text control prompt — so the model-to-model comparison is the one to read.)So: the registry is right, the negative assertion in
tests/provider-registry-parity.test.tsshould stay, andmimo-v2.5-freeshould remain absent from the text-only list. No disagreement left here.4.
TR/moonshotai/kimi-k3-free— config, plus a diagnostic that narrows itFull
TRblock, redacted:There is no
modelskey and nomodelInputModalities— theTRroster is discovered live, so the only modality metadata in play is thatnoVisionModelslist.Two things narrow this considerably:
The sidecar path demonstrably works on TokenRouter.
TR/qwen/qwen3.8-maxis in the samenoVisionModelsarray askimi-k3-free, and it was one of the six healthy controls that passed in my original report. So wasDW/devworld/deepseek-v4-pro-r, which is also anoVisionModelsentry on its provider. Two of my six "healthy controls" were themselves sidecar-routed — one of them on TokenRouter, through this exact block. Whatever is wrong withkimi-k3-freeis not the shape of myTRconfig, because the identical config makesqwen3.8-maxpass.The proxy is doing the right thing on the wire for
kimi-k3-freetoo. Same debug capture:309bytes againstnemotron-3-ultra-free's307for the same prompt — the same stripped-and-captioned signature, ~100 bytes below the407of a request that actually carries the image. Classification and strip are both correct here; the gap is in what TokenRouter'skimi-k3-freedoes with the resulting text-only request.And the failure mode has changed since I filed this — it is now a hang, not a bad answer. v2.10.2, 5 trials:
The text-only control on
kimi-k3-freeanswers in 3.2 s. The moment the request carries the sidecar caption instead, TokenRouter accepts the POST and then never responds — five for five, hard client timeout at 180 s. My original report said "HTTP 200 but never answers correctly"; for this model that no longer reproduces on v2.10.2, and I am correcting it: there is no 200, there is no response at all.Since the same
TRblock, same key pool and same sidecar makeqwen/qwen3.8-maxanswer in ~2.4 s, my current reading is that this is TokenRouter-side behavior for themoonshotai/kimi-k3-freeroute rather than an opencodex classification gap — the proxy sends a well-formed text-only request and the upstream stalls on it. Tell me what else would be useful: I can capture the full outbound body (not justbodyBytes), try the same request against TokenRouter directly withcurlto take opencodex out of the path, or dropkimi-k3-freefrom mynoVisionModelsto see whether the native image request also stalls. Happy to run whichever you'd trust most.Config cleanup on my side
For the record, since it affects any future report I file: I removed the local
noVisionModelsentries that v2.10.2's registry already covers, so my config stops shadowing yours.Both retests above were re-run after this cleanup and still pass 5/5, which confirms the union in
router.ts(mergeStringArray(registryEntry.noVisionModels, provider.noVisionModels)) is doing the work — the registry seed alone is sufficient. Worth noting for other users: a stale localnoVisionModelsnever breaks a registry entry, but it silently overrides a deliberate registry exclusion, which is precisely how I produced a false bug report. A startup warning when a confignoVisionModelsid collides with a registry text-only decision would have caught this in one line of output.Happy for you to keep this open for the
TRhalf only.[-][Bug]: post-#956 vision sidecar gaps on nemotron/mimo/kimi probes (controls deterministic)[/-][+][Bug] Custom-provider vision capability is ambiguous when model metadata is missing[/+]5 remaining items
fix(catalog): advertise image input for modalities-declared sidecar rows
fix(catalog): advertise image input for modalities-declared sidecar rows
fix(catalog): advertise image input for modalities-declared sidecar rows
fix(catalog): advertise image input for modalities-declared sidecar rows
fix(catalog): advertise image input for modalities-declared sidecar r…
fix(catalog): advertise image input for modalities-declared sidecar r…
docs(devlog): triage every open bug issue and PR against dev
docs(devlog): fold four audit rounds into the triage unit
docs(devlog): move three inferred PR-issue links out of the literal l…
fix(zen): strip images for the Zen models that reject them (lidge-jun…
docs(devlog): 260808 bug campaign - dispositions, execution, and faults
docs(devlog): add decade docs for lidge-jun#1562 GUI preflight and li…
fix(vision): respect modelInputModalities for text-only detection (li…
fix(catalog): advertise image input for modalities-declared sidecar r…