Repository navigation
[Provider compatibility] openai-chat gateways without a server-side reasoning parser render the whole <think> chain of thought as the answer #5204
Description
Client or integration
Codex App
Provider or upstream service
A user-configured openai-chat gateway (reproduced against GonkaRouter, https://api.gonkarouter.io/v1)
OpenCodex version
2.59.0
Endpoint or capability
/v1/chat/completions (reasoning metadata, streaming and non-streaming)
Current behaviour
reasoningTextFrom maps provider thinking to Codex reasoning only from reasoning_content or a plain reasoning field, and reasoning_details covers the structured MiniMax shape. A gateway that serves a thinking model on an inference backend with no server-side reasoning parser sends none of those: the chain of thought arrives inline in delta.content as a <think>...</think> block. The openai-chat adapter forwards it verbatim, so Codex renders the entire chain of thought as the assistant's answer.
The same deployment can be inconsistent per model, which is what makes this a gateway problem rather than a provider preset problem. On the same base URL, same API key, same request shape, today:
| Model | Serving backend (system_fingerprint) |
Reasoning field | Result in Codex |
|---|---|---|---|
zai-org/GLM-5.3-Flash |
vllm-0.28.0.dev0+glm53...-tp4 |
reasoning |
correct, already handled |
MiniMaxAI/MiniMax-M2.7 |
vllm-0.25.1-tp2 |
none | chain of thought shown as the answer |
Requests that would move thinking into its own field are ignored by this upstream: reasoning_split: true, reasoning: { effort } and chat_template_kwargs all return the same inline-tag body. So the recovery can only happen on the proxy side.
Expected behaviour
For a model known to be served without a reasoning parser, OpenCodex should split the leading <think> / <thinking> / <reasoning> block out of visible content and emit it as reasoning, leaving the answer clean. This is the same recovery the Kiro adapter already performs in src/adapters/kiro-thinking.ts; the openai-chat adapter is the natural second home for it.
Because 66 registry providers share the openai-chat adapter, and because a gateway that does parse reasoning must keep its visible content byte-exact, this should be opt-in per model rather than a global content rewrite.
Minimal redacted request or reproduction
curl -s https://api.gonkarouter.io/v1/chat/completions \
-H "Authorization: Bearer <redacted>" \
-H 'content-type: application/json' \
-d '{"model":"MiniMaxAI/MiniMax-M2.7","messages":[{"role":"user","content":"Think briefly, then reply with exactly: OCX_THINK_OK"}],"stream":true}'Actual response or error
# MiniMaxAI/MiniMax-M2.7 — thinking arrives inside content, no reasoning field anywhere in the stream
data: {"choices":[{"delta":{"content":"","role":"assistant"},"finish_reason":null,"index":0}], ...}
data: {"choices":[{"delta":{"content":"\u003cthink\u003eWe"},"finish_reason":null,"index":0}], ...}
data: {"choices":[{"delta":{"content":" need"},"finish_reason":null,"index":0}], ...}
...
data: {"choices":[],"usage":{"completion_tokens":121,"prompt_tokens":51,"total_tokens":172},
"system_fingerprint":"vllm-0.25.1-tp2-<redacted>"}
data: [DONE]
# grep -c 'reasoning' <stream> -> 0
# the stream contains both \u003cthink\u003e and \u003c/think\u003e
# zai-org/GLM-5.3-Flash on the SAME gateway, for contrast — already correct
data: {"choices":[{"delta":{"reasoning":"The"},"finish_reason":null,"index":0}], ...}Upstream documentation
There is no public specification for this: it is the absence of a server-side reasoning parser in the model's vLLM deployment, so the model's raw chat template output (which wraps thinking in <think> tags) reaches the client unparsed. The concrete client requirement is the OpenAI Chat Completions reasoning convention that OpenCodex already implements, namely thinking in reasoning_content / reasoning / reasoning_details rather than in content.
Suggested mapping or implementation notes
A narrow, opt-in provider option, following the existing reasoningSplitModels / reasoningDetailsModels shape:
"providers": {
"my-gateway": {
"adapter": "openai-chat",
"inlineThinkTagModels": ["MiniMaxAI/MiniMax-M2.7"]
}
}Suggested guarantees:
- off by default, so every other openai-chat provider keeps byte-exact visible content;
- even when enabled, engage only for a response that OPENS with a thinking tag, so an answer that merely mentions
<think>is never rewritten; - once engaged, keep splitting later blocks, because M-series models interleave thinking with answer segments;
- flush an unterminated block as reasoning at end of stream rather than dropping it;
- reuse the existing Kiro parser instead of adding a second one.
I have a working implementation of exactly this and will open a PR against dev referencing this issue.
Additional context and attachments
Reproduced on OpenCodex 2.59.0 (macOS, Bun 1.3.14) on 2026-09-20. The per-model split above was captured minutes apart against the same base URL and key.
Checks
- I searched existing provider and compatibility issues.
- The request and response were redacted.
- The expected behaviour is based on an upstream specification or a concrete client requirement.
Activity
Ingwannu commentedon Sep 20, 2026
Verified issue; PR #5205 is the current opt-in implementation candidate. It remains draft/conflicting, so this issue should stay open.
The safe boundary is model-scoped and off by default. Streaming and buffered paths must share one state machine, engage only when the response begins with an allowed reasoning tag, bound buffered text, preserve ordinary literal tag mentions, flush malformed/unterminated input without data loss, and never reinterpret tool arguments or structured JSON as reasoning. Close only after the PR is rebased and exact-head CI covers split tags across chunks, interleaved reasoning/answer segments, and non-enabled models byte-for-byte.
lidge-jun commentedon Sep 23, 2026
This is on
devthrough #5619 (commit e964387, which carried #5566 consolidating #5205): openai-chat providers now have an opt-ininlineThinkTagModelslist. For a listed model, a leading<think>,<thinking>or<reasoning>block is split out as reasoning in both streamed and buffered replies instead of rendering as the answer. It is off by default, so add the gateway's model ids toinlineThinkTagModelson the provider. Confirmed again during the 260923 bundle round (lane F2, #5674). Please reopen with the provider config and a sample reply if a listed model still shows its chain of thought.