Provider
Other (self-hosted llama.cpp server behind the openai-chat adapter)
Endpoint or base URL
http://<host>:<port>/v1 (llama.cpp llama-server, OpenAI-compatible mode)
What does not work
A llama.cpp server truthfully advertises both multimodality and its served context length, but neither signal reaches routing evidence, so the model is treated as image-blind with an unknown context window.
Two independent gaps cause this.
1. The capability token multimodal is not recognized.
modelInputModalities() (src/codex/catalog/provider-fetch.ts:991) accepts capabilities.vision === true and the capability strings vision, image-input, image_input. llama.cpp and Ollama-compatible servers report vision as multimodal, which is in none of those sets, so the row yields undefined.
2. The image and context signals live in different envelopes.
The server returns a dual-shape body — an Ollama-style models[] array alongside an OpenAI-style data[] array. The multimodal token is in models[]; meta.n_ctx is in data[].
extractProviderModelItems() (src/providers/model-discovery.ts:337) deliberately reads only data envelopes or top-level arrays, and its comment says a stray models key on openai-chat responses must not be trusted. That conservatism looks correct in general, but it means the two halves of this server's metadata can never meet: the surviving data[] item has no capability list at all.
Observed response
{ "models": [ { "name": "<model>", "model": "<model>",
"capabilities": ["completion", "multimodal"],
"details": { "format": "gguf" } } ],
"object": "list",
"data": [ { "id": "<model>", "object": "model", "owned_by": "llamacpp",
"meta": { "n_ctx": 262144, "n_ctx_train": 262144,
"n_vocab": 248320, "n_embd": 5120 } } ] }
Feeding the surviving data[] item to catalogHintsFromModelsApiItem() returns {} — no context, no modalities. Hand-merging the capability list in still returns no inputModalities, because of gap 1:
catalogHintsFromModelsApiItem("<provider>", {
meta: { n_ctx: 262144 }, capabilities: ["completion", "multimodal"] })
=> { "capabilities": ["completion", "multimodal"] }
Expected behavior
A server that advertises multimodal should produce inputModalities: ["text", "image"], and meta.n_ctx should be usable as a context source — preferring the served n_ctx over the trained n_ctx_train, since routing must not promise a window the running server will refuse.
Version
2.21.0
Additional context
Two constraints any fix must respect:
input_modalities is a closed enum of text | image | audio. One out-of-enum value makes Codex reject the entire catalog file, taking down plugins, apps, and MCP servers — the existing normalization comment documents this after a real incident with a provider advertising video. Mapping multimodal must therefore emit ["text", "image"], never the raw token.
- Any
models[] to data[] join needs identity-safe matching by model id and bounded input, precisely because the current code refuses to trust a stray models key.
Context-source ingestion (meta.n_ctx) is being handled separately as a pure addition to the existing precedence list; this issue tracks the two harder halves: the multimodal mapping and the cross-envelope join.
Related: #1796 (routing discards catalog rows entirely, which masks this end to end).
Checks
Provider
Other (self-hosted llama.cpp server behind the
openai-chatadapter)Endpoint or base URL
http://<host>:<port>/v1(llama.cppllama-server, OpenAI-compatible mode)What does not work
A llama.cpp server truthfully advertises both multimodality and its served context length, but neither signal reaches routing evidence, so the model is treated as image-blind with an unknown context window.
Two independent gaps cause this.
1. The capability token
multimodalis not recognized.modelInputModalities()(src/codex/catalog/provider-fetch.ts:991) acceptscapabilities.vision === trueand the capability stringsvision,image-input,image_input. llama.cpp and Ollama-compatible servers report vision asmultimodal, which is in none of those sets, so the row yieldsundefined.2. The image and context signals live in different envelopes.
The server returns a dual-shape body — an Ollama-style
models[]array alongside an OpenAI-styledata[]array. Themultimodaltoken is inmodels[];meta.n_ctxis indata[].extractProviderModelItems()(src/providers/model-discovery.ts:337) deliberately reads onlydataenvelopes or top-level arrays, and its comment says a straymodelskey onopenai-chatresponses must not be trusted. That conservatism looks correct in general, but it means the two halves of this server's metadata can never meet: the survivingdata[]item has no capability list at all.Observed response
{ "models": [ { "name": "<model>", "model": "<model>", "capabilities": ["completion", "multimodal"], "details": { "format": "gguf" } } ], "object": "list", "data": [ { "id": "<model>", "object": "model", "owned_by": "llamacpp", "meta": { "n_ctx": 262144, "n_ctx_train": 262144, "n_vocab": 248320, "n_embd": 5120 } } ] }Feeding the surviving
data[]item tocatalogHintsFromModelsApiItem()returns{}— no context, no modalities. Hand-merging the capability list in still returns noinputModalities, because of gap 1:Expected behavior
A server that advertises
multimodalshould produceinputModalities: ["text", "image"], andmeta.n_ctxshould be usable as a context source — preferring the servedn_ctxover the trainedn_ctx_train, since routing must not promise a window the running server will refuse.Version
2.21.0
Additional context
Two constraints any fix must respect:
input_modalitiesis a closed enum oftext | image | audio. One out-of-enum value makes Codex reject the entire catalog file, taking down plugins, apps, and MCP servers — the existing normalization comment documents this after a real incident with a provider advertisingvideo. Mappingmultimodalmust therefore emit["text", "image"], never the raw token.models[]todata[]join needs identity-safe matching by model id and bounded input, precisely because the current code refuses to trust a straymodelskey.Context-source ingestion (
meta.n_ctx) is being handled separately as a pure addition to the existing precedence list; this issue tracks the two harder halves: themultimodalmapping and the cross-envelope join.Related: #1796 (routing discards catalog rows entirely, which masks this end to end).
Checks