Skip to content

OpenAI/Responses requests now upload chunked without Content-Length (on-demand body from #854) #3847

Description

@acoliver

Background

Since #854, OpenAI chat and Responses HTTP request bodies are produced on demand from disk segments as the upload proceeds. The bytes match what main sent. Because the length is not known before the body is produced, requests now go out with chunked transfer encoding and no Content-Length header. Main sent Content-Length.

The Kimi cache transport test (packages/providers/src/openai/OpenAIProvider.kimiCacheTransport.test.ts) now asserts that Content-Length is absent and the wire body is non-empty.

Risk

Some proxies, gateways and self-hosted inference servers reject chunked request bodies or buffer them differently. None has been reported. The providers tested so far accept chunked uploads: Codex/Responses, Anthropic, DeepSeek through the OpenAI chat path, and the local test servers.

Options if it becomes a problem

Computing the exact length up front needs a full pass over the content before upload. The disk route JSON-escapes UTF-16LE segments chunk by chunk, and the progressive route converts rows while it uploads. Either way the content is read twice. A counting pre-pass that streams the body once to measure it, without keeping it, would do that. It doubles read I/O but adds no memory. It could be opt-in per provider or base URL.

Acceptance (if implemented)

  • A test proves the declared length equals the bytes sent for text, media and tool-heavy requests.
  • The body is never fully materialized in memory to compute the length.

Follow-up from #854. Only worth doing if a real endpoint rejects chunked uploads.

Activity

  1. added
    Provider Supportthings that support an inference provider but not a particular model
    on Oct 10, 2026
  2. github-actions commented on Oct 10, 2026

    @github-actions
    Contributor

    LLxprt Issue Planner — infrastructure failure

    The autonomous planner failed with exit code 1. Please inspect the workflow logs (Run planner agent section) and re-run once resolved.

  3. added theissue type on Oct 13, 2026
  4. added this to the 0.12.0 milestone on Oct 13, 2026
  5. added a commit that references this issue on Oct 14, 2026
    798fe12
  6. acoliver commented on Oct 14, 2026

    @acoliver
    CollaboratorAuthor

    Fixed in PR #3852: every OpenAI chat, OpenAI Responses and Anthropic request route now sends Content-Length equal to the bytes it uploads. None of them goes out chunked any more.

    At the start of the fix only the openai-responses provider sent chunked uploads. The chat, openai-provider Responses and Anthropic routes already had a length because they built a bounded array body. When those routes moved to request-scoped disk bodies, each of them kept the header by declaring the sealed size of the spool or segment files before the first byte.

    Routes covered, with the commit that put the header on each:

    • openai-responses provider, row-backed calls (disk text route): 798fe12 (A3).
    • openai-responses provider, calls with no request rows (the old lazy and progressive body): 253a7d5 (C2), which stages the rows on scratch disk and sends them through the same route. The lazy body type is deleted in 6106d45 (C3).
    • openai provider Responses route, rows and one-shot streams: c25f528 (C1) and 253a7d5 (C2).
    • OpenAI chat (plain, Kimi, Mistral formats): 5f7e7aa (D3a).
    • OpenAI chat continuation after tool calls, which sends a second spool: 327f7f1 (D3c and D4).
    • Anthropic (cache on and cache off): e166759 (E4). The image-dimension recovery retry rebuilds the spool and sends it the same way: f49d462 (E6).

    How it is checked:

    • The golden wire-body corpus (wireBodyGolden.test.ts, 315 cases) records Content-Length for 7 routes, each fed from a row selection and from a one-shot stream. All 14 combinations are present, and the recorder throws if the value differs from the body byte count. The fixtures were recorded before this work began, and the request bytes are unchanged for every case. The only fixture change in the series is the header flipping from absent to present on openai-responses.
    • disk-text-content-length.test.ts covers text, inline media, media references, a 120-pair tool-heavy history, and UTF-16 instructions with lone surrogates. A body that serves more or fewer bytes than declared fails the request.
    • Each sendPeak.* run asserts that Content-Length equals the bytes sent. In the final run this held for chat (8,596,228 and 11,774,659 bytes), openai-provider Responses (8,596,212 and 11,582,131), openai-responses (8,596,212 and 11,582,131) and Anthropic (8,596,378 and 11,541,325).
    • The length comes from the sealed spool size or the file sizes, so the body is never held in memory to measure it. Heap growth from a 1 MiB to an 8 MiB body stays under 1 MiB on every route.

    Not covered: the Codex WebSocket path sends frames and has no HTTP length header. Vercel and the Gemini plugin are separate (#3869).

    The Kimi cache transport test now asserts that Content-Length equals the body bytes for both the row-backed and the contents-only Responses request. This issue closes when #3852 merges.

  7. moved this from Todo to In Progress in LLxprt Roadmapon Oct 15, 2026
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

    Provider Supportthings that support an inference provider but not a particular model

    Type

    Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions