Area
Proxy and routing
What are you trying to accomplish?
I want image-heavy Codex sessions to survive against providers whose gateway enforces a hard request-body size limit, instead of the thread becoming permanently unusable.
Concretely: I attach screenshots and let Codex read rendered PDF/PPT pages while working. After enough images accumulate in the thread, every subsequent turn fails with a provider-side body-size rejection, and the thread can never recover — including its own compaction attempt, because that request also carries the history.
What prevents this today?
Three things combine:
- Codex sends images at original resolution. A Retina screenshot is 2–4 MB of base64. There is no client-side byte budget.
- OpenCodex replays the full history.
expandPreviousResponseInput (src/responses/state.ts) materializes the stored previous_response_id state back into input, so every turn re-sends every image ever attached. This is correct for cross-provider routing, but it means the payload only grows.
- Codex auto-compaction cannot save the thread. Compaction triggers on tokens, while the gateway limit is bytes. Images are byte-heavy and token-light (a 1 MB screenshot is ~1.4 MB base64 but only ~1–2.5k vision tokens), so the byte ceiling is reached long before the token threshold. Nothing in the pipeline watches serialized body size.
Measured limits on Alibaba Cloud Bailian (Model Studio), OpenAI-compatible endpoints:
| Endpoint |
Body cap |
Error |
public dashscope.aliyuncs.com/compatible-mode/v1 |
6 MB |
BadRequest.TooLarge: Exceeded limit on max bytes to request body : 6291456 |
dedicated Bailian host (*.maas.aliyuncs.com) |
10 MB |
RequestEntityTooLarge: Request body size exceeds maximum allowed size: 10485760 bytes |
Bailian's Responses API also has no files/file-id upload mechanism, and its input_image field is documented as taking a public URL — but Codex only produces inline base64, so that escape hatch is unavailable.
What should OpenCodex do?
Add an opt-in, per-provider request byte budget in the responses pipeline. When the serialized body exceeds the budget, slim it in two ordered passes before forwarding:
- Downscale oversized inline images (re-encode to a bounded longest-edge JPEG). Cheap, lossy only in resolution, keeps every image present.
- Prune oldest-first if still over budget: replace the oldest
input_image parts with an explicit input_text marker (e.g. [image removed: request size limit]) so both the model and the user know the image was dropped rather than silently ignored. The newest turn's images survive longest.
Requirements that matter for correctness:
- Must walk the whole payload, not just
input[].content[] — Codex also embeds images inside tool-output items (function_call_output). Scanning only the obvious path silently no-ops.
- Must serialize compactly when measuring; a pretty-printed re-serialization can make the body larger.
- Disabled by default, so providers with generous limits are unaffected.
Example usage or interface
Log line when it engages:
[opencodex] slimmed body 38729496 -> 5887546 bytes (downscaled=2, pruned=0)
Alternatives or workarounds
I ran a ~230-line stdlib-only local proxy implementing exactly this design in front of Bailian, to check the approach holds end to end:
- Synthetic 38.7 MB two-image request → slimmed to 5.9 MB → upstream 200 with a normal completion. The identical body sent directly upstream returns 413.
- Real Codex turn viewing four 2400×2400 images (66 MB on disk) → completed normally, no error.
- Regressions checked: plain-text turn fine, SSE streaming still incremental,
previous_response_id continuation intact.
Workarounds that do not work:
- Switching endpoints only trades 10 MB for 6 MB; the cap is a gateway property, not a wire-format one.
- Instructing the agent (via
AGENTS.md) to downscale before viewing helps but is best-effort, and cannot cover images the user pastes directly.
/compact requires a successful model call, which is exactly what is failing.
Additional context
Checks
Area
Proxy and routing
What are you trying to accomplish?
I want image-heavy Codex sessions to survive against providers whose gateway enforces a hard request-body size limit, instead of the thread becoming permanently unusable.
Concretely: I attach screenshots and let Codex read rendered PDF/PPT pages while working. After enough images accumulate in the thread, every subsequent turn fails with a provider-side body-size rejection, and the thread can never recover — including its own compaction attempt, because that request also carries the history.
What prevents this today?
Three things combine:
expandPreviousResponseInput(src/responses/state.ts) materializes the storedprevious_response_idstate back intoinput, so every turn re-sends every image ever attached. This is correct for cross-provider routing, but it means the payload only grows.Measured limits on Alibaba Cloud Bailian (Model Studio), OpenAI-compatible endpoints:
dashscope.aliyuncs.com/compatible-mode/v1BadRequest.TooLarge: Exceeded limit on max bytes to request body : 6291456*.maas.aliyuncs.com)RequestEntityTooLarge: Request body size exceeds maximum allowed size: 10485760 bytesBailian's Responses API also has no files/file-id upload mechanism, and its
input_imagefield is documented as taking a public URL — but Codex only produces inline base64, so that escape hatch is unavailable.What should OpenCodex do?
Add an opt-in, per-provider request byte budget in the responses pipeline. When the serialized body exceeds the budget, slim it in two ordered passes before forwarding:
input_imageparts with an explicitinput_textmarker (e.g.[image removed: request size limit]) so both the model and the user know the image was dropped rather than silently ignored. The newest turn's images survive longest.Requirements that matter for correctness:
input[].content[]— Codex also embeds images inside tool-output items (function_call_output). Scanning only the obvious path silently no-ops.Example usage or interface
Log line when it engages:
Alternatives or workarounds
I ran a ~230-line stdlib-only local proxy implementing exactly this design in front of Bailian, to check the approach holds end to end:
previous_response_idcontinuation intact.Workarounds that do not work:
AGENTS.md) to downscale before viewing helps but is best-effort, and cannot cover images the user pastes directly./compactrequires a successful model call, which is exactly what is failing.Additional context
maxRequestBytesseems more useful than a Bailian-specific branch.Checks