Normalize Codex Responses input shape
Ensure the Codex OAuth Responses proxy forwards list-shaped input for empty continuation turns while preserving string prompts as user input items. Add regression coverage for the gpt-5.5 continuation case and document the proxy contract.
Alessandro committed
Jul 9, 2026 at 02:41 UTC
5f144350936c58f07cb1aef8d0b5b83babe94786
3 files changed
+25
-1
plugins/_oauth/AGENTS.md
+1
-1
@@ -40,7 +40,7 @@
40
- Stored upstream base URLs and OAuth token endpoints must be validated against provider-owned allowlists before sending bearer or refresh tokens.
41
- Browser callback providers must support manual callback paste when the browser cannot reach the local callback route.
42
- Local proxy routes must remain loopback or token protected and must not add broad CORS access.
43
-- 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`.
43
+- 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.
44
45
## Work Guidance
46
plugins/_oauth/helpers/codex.py
+7
@@ -666,6 +666,13 @@ 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
+ input_value = normalized.get("input")
670
+ if isinstance(input_value, str):
671
+ normalized["input"] = (
672
+ [{"role": "user", "content": input_value}] if input_value else []
673
+ )
674
+ elif not isinstance(input_value, list):
675
+ normalized["input"] = []
676
normalized.setdefault("instructions", "")
677
normalized.setdefault("store", False)
678
normalized["client_metadata"] = merge_client_metadata(normalized.get("client_metadata"))
tests/test_oauth_codex.py
+17
@@ -202,10 +202,27 @@ def test_prepare_responses_body_adds_codex_client_metadata(monkeypatch):
202
"thread_id": "thread-1",
203
"x-codex-window-id": "agent-zero",
204
}
205
+ assert body["input"] == [{"role": "user", "content": "hello"}]
206
assert body["stream"] is True
207
assert body["include"] == ["output_text", "reasoning.encrypted_content"]
208
209
210
+def test_prepare_responses_body_sends_empty_continuation_input_as_list(monkeypatch):
211
+ monkeypatch.setattr(codex, "build_client_metadata", lambda: {})
212
+
213
+ body = codex.prepare_responses_body(
214
+ {
215
+ "model": "gpt-5.5",
216
+ "input": "",
217
+ "previous_response_id": "resp_1",
218
+ },
219
+ force_stream=True,
220
+ )
221
+
222
+ assert body["input"] == []
223
+ assert body["previous_response_id"] == "resp_1"
224
+
225
+
226
def test_request_codex_sends_current_codex_headers_from_body(monkeypatch):
227
calls = []
228
body = json.dumps(