From 5f144350936c58f07cb1aef8d0b5b83babe94786 Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Thu, 9 Jul 2026 02:41:03 +0200 Subject: [PATCH] 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. --- plugins/_oauth/AGENTS.md | 2 +- plugins/_oauth/helpers/codex.py | 7 +++++++ tests/test_oauth_codex.py | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/plugins/_oauth/AGENTS.md b/plugins/_oauth/AGENTS.md index 7bf1f3348..ebcf18c36 100644 --- a/plugins/_oauth/AGENTS.md +++ b/plugins/_oauth/AGENTS.md @@ -40,7 +40,7 @@ - Stored upstream base URLs and OAuth token endpoints must be validated against provider-owned allowlists before sending bearer or refresh tokens. - Browser callback providers must support manual callback paste when the browser cannot reach the local callback route. - Local proxy routes must remain loopback or token protected and must not add broad CORS access. -- 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`. +- 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. ## Work Guidance diff --git a/plugins/_oauth/helpers/codex.py b/plugins/_oauth/helpers/codex.py index a0fc20c5b..adcfdaec6 100644 --- a/plugins/_oauth/helpers/codex.py +++ b/plugins/_oauth/helpers/codex.py @@ -666,6 +666,13 @@ def fetch_models() -> list[str]: def prepare_responses_body(body: dict[str, Any], *, force_stream: bool) -> dict[str, Any]: normalized = dict(body) + input_value = normalized.get("input") + if isinstance(input_value, str): + normalized["input"] = ( + [{"role": "user", "content": input_value}] if input_value else [] + ) + elif not isinstance(input_value, list): + normalized["input"] = [] normalized.setdefault("instructions", "") normalized.setdefault("store", False) normalized["client_metadata"] = merge_client_metadata(normalized.get("client_metadata")) diff --git a/tests/test_oauth_codex.py b/tests/test_oauth_codex.py index 396584c18..fc4a3159f 100644 --- a/tests/test_oauth_codex.py +++ b/tests/test_oauth_codex.py @@ -202,10 +202,27 @@ def test_prepare_responses_body_adds_codex_client_metadata(monkeypatch): "thread_id": "thread-1", "x-codex-window-id": "agent-zero", } + assert body["input"] == [{"role": "user", "content": "hello"}] assert body["stream"] is True assert body["include"] == ["output_text", "reasoning.encrypted_content"] +def test_prepare_responses_body_sends_empty_continuation_input_as_list(monkeypatch): + monkeypatch.setattr(codex, "build_client_metadata", lambda: {}) + + body = codex.prepare_responses_body( + { + "model": "gpt-5.5", + "input": "", + "previous_response_id": "resp_1", + }, + force_stream=True, + ) + + assert body["input"] == [] + assert body["previous_response_id"] == "resp_1" + + def test_request_codex_sends_current_codex_headers_from_body(monkeypatch): calls = [] body = json.dumps(