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.
This commit is contained in:
Alessandro 2026-07-09 02:41:03 +02:00
parent a7255d9773
commit 5f14435093
3 changed files with 25 additions and 1 deletions

View file

@ -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

View file

@ -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"))

View file

@ -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(