Symptom
Every route's GET /v1/models entry declares input_modalities: ["text"], hardcoded, regardless of the resolved target. Codex trusts that declaration and drops an attached image before sending, so routing a vision-capable model through Switchyard silently loses images.
Reproduction
One passthrough route to any OpenAI-Responses target, then a Codex turn with an image:
switchyard-server --config routes.toml --host 127.0.0.1 --port 4100
codex exec -p <profile-pointing-at-switchyard> -i diagram.png -- 'Describe the image.'
routes.toml:
schema_version = 1
[llm_clients.upstream]
format = "openai_responses"
base_url = "http://127.0.0.1:4999/v1"
[targets.t]
id = "some-vision-model"
llm_client = "upstream"
[routes.r]
id = "r"
type = "passthrough"
target = "t"
Expected vs. actual
- Expected: the
input_image item reaches the upstream, as it does with no proxy in the path.
- Actual: Codex substitutes a placeholder before the request ever leaves the client:
{"type": "input_text", "text": "image content omitted because you do not support image input"}
Measured at the wire with a stand-in upstream logging the request body:
| Run |
Outbound body |
Last user-message content |
| Codex → upstream, no proxy (control) |
759,745 B |
input_image (543,102-char data URI) |
Codex → Switchyard, input_modalities: ["text"] |
248,385 B |
60-char placeholder |
Codex → Switchyard, input_modalities: ["text","image"] |
739,155 B |
input_image, same as control |
The response is 200 and no diagnostic is emitted anywhere; the only other signal is a correspondingly smaller prompt-token count.
⚠ Note the failure is entirely client-side, so it cannot be seen by inspecting Switchyard's request path — the proxy never receives an image. The placeholder string is in the codex binary (6 occurrences in 0.150.1) and not in Switchyard.
Cause
crates/switchyard-server/src/lib.rs, in codex_model_entry_json:
"input_modalities": ["text"],
The surrounding comment already anticipates this class of problem — "Every field below is either derived from the route's declared capabilities or a required ModelInfo field the server has no better value for", with a TODO about sourcing capabilities from the backend. input_modalities is neither derived nor declarable.
Related: supports_image_detail_original is hardcoded false on the same entry.
Environment
- Commit:
7f3b2fe9 (main), also present at v0.2.0
- Client:
codex-cli 0.150.1, wire_api = "responses"
Suggested fix
Add a vision route capability beside the existing tool_calling and reasoning, failing closed. Failing closed matters here: a route may resolve to a target with no vision, so declaring image support unconditionally would send images where the backend cannot read them.
PR: see linked pull request.
Symptom
Every route's
GET /v1/modelsentry declaresinput_modalities: ["text"], hardcoded, regardless of the resolved target. Codex trusts that declaration and drops an attached image before sending, so routing a vision-capable model through Switchyard silently loses images.Reproduction
One passthrough route to any OpenAI-Responses target, then a Codex turn with an image:
routes.toml:Expected vs. actual
input_imageitem reaches the upstream, as it does with no proxy in the path.{"type": "input_text", "text": "image content omitted because you do not support image input"}Measured at the wire with a stand-in upstream logging the request body:
input_image(543,102-char data URI)input_modalities: ["text"]input_modalities: ["text","image"]input_image, same as controlThe response is
200and no diagnostic is emitted anywhere; the only other signal is a correspondingly smaller prompt-token count.⚠ Note the failure is entirely client-side, so it cannot be seen by inspecting Switchyard's request path — the proxy never receives an image. The placeholder string is in the
codexbinary (6 occurrences in 0.150.1) and not in Switchyard.Cause
crates/switchyard-server/src/lib.rs, incodex_model_entry_json:The surrounding comment already anticipates this class of problem — "Every field below is either derived from the route's declared capabilities or a required
ModelInfofield the server has no better value for", with a TODO about sourcing capabilities from the backend.input_modalitiesis neither derived nor declarable.Related:
supports_image_detail_originalis hardcodedfalseon the same entry.Environment
7f3b2fe9(main), also present atv0.2.0codex-cli0.150.1,wire_api = "responses"Suggested fix
Add a
visionroute capability beside the existingtool_callingandreasoning, failing closed. Failing closed matters here: a route may resolve to a target with no vision, so declaring image support unconditionally would send images where the backend cannot read them.PR: see linked pull request.