Skip to content

Feature: opt-in per-provider request byte budget that downscales then prunes inline images #2511

Description

@NotWizard

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:

  1. Codex sends images at original resolution. A Retina screenshot is 2–4 MB of base64. There is no client-side byte budget.
  2. 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.
  3. 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:

  1. Downscale oversized inline images (re-encode to a bounded longest-edge JPEG). Cheap, lossy only in resolution, keeps every image present.
  2. 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

// ~/.opencodex/config.json
{
  "providers": {
    "bailian": {
      "adapter": "openai-responses",
      "maxRequestBytes": 9000000,      // unset = today's behaviour (no slimming)
      "imageDownscaleMaxEdge": 1600    // optional; omit to skip pass 1
    }
  }
}

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

  • I searched existing issues and documentation.
  • This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
  • I removed secrets and personal data.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestproviderProvider adapters, OpenAI-compat presets, upstream API quirksproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions