Fix Codex OAuth reasoning effort forwarding
Normalize the legacy reasoning_effort field at the Codex Responses proxy boundary so it cannot reach the upstream endpoint in an unsupported shape. Preserve an explicit native reasoning object and cover alias conversion and precedence with regression tests.
Alessandro committed
Jul 27, 2026 at 12:08 UTC
ed7b0800011df2bea5b1e2e9dae93780351fe4bc
3 files changed
+28
plugins/_oauth/AGENTS.md
+1
@@ -44,6 +44,7 @@
44
- Browser callback providers must support manual callback paste when the browser cannot reach the local callback route.
45
- Local proxy routes must remain loopback or token protected and must not add broad CORS access.
46
- Codex Responses proxy requests must include Codex client metadata and compatibility headers such as `client_metadata`, `x-codex-installation-id`, `originator`, `session-id`, and `thread-id`, and must forward `input` as a list for upstream Codex compatibility.
47
+- Codex Responses proxy requests must translate the legacy top-level `reasoning_effort` field to `reasoning.effort`; an explicit native `reasoning` field takes precedence.
48
49
## Work Guidance
50
plugins/_oauth/helpers/codex.py
+3
@@ -666,6 +666,9 @@ def fetch_models() -> list[str]:
666
667
def prepare_responses_body(body: dict[str, Any], *, force_stream: bool) -> dict[str, Any]:
668
normalized = dict(body)
669
+ reasoning_effort = normalized.pop("reasoning_effort", None)
670
+ if reasoning_effort is not None and "reasoning" not in normalized:
671
+ normalized["reasoning"] = {"effort": reasoning_effort}
672
input_value = normalized.get("input")
673
if isinstance(input_value, str):
674
normalized["input"] = (
tests/test_oauth_codex.py
+24
@@ -207,6 +207,30 @@ def test_prepare_responses_body_adds_codex_client_metadata(monkeypatch):
207
assert body["include"] == ["output_text", "reasoning.encrypted_content"]
208
209
210
+@pytest.mark.parametrize(
211
+ ("request_reasoning", "expected"),
212
+ [
213
+ ({"reasoning_effort": "xhigh"}, {"effort": "xhigh"}),
214
+ (
215
+ {"reasoning": {"effort": "medium"}, "reasoning_effort": "xhigh"},
216
+ {"effort": "medium"},
217
+ ),
218
+ ],
219
+)
220
+def test_prepare_responses_body_normalizes_reasoning_effort(
221
+ monkeypatch, request_reasoning, expected
222
+):
223
+ monkeypatch.setattr(codex, "build_client_metadata", lambda: {})
224
+
225
+ body = codex.prepare_responses_body(
226
+ {"model": "gpt-5.5", "input": "hello", **request_reasoning},
227
+ force_stream=True,
228
+ )
229
+
230
+ assert body["reasoning"] == expected
231
+ assert "reasoning_effort" not in body
232
+
233
+
234
def test_prepare_responses_body_sends_empty_continuation_input_as_list(monkeypatch):
235
monkeypatch.setattr(codex, "build_client_metadata", lambda: {})
236