Skip to content

feat: support streaming file uploads#1970

Open
HAYDEN-OAI wants to merge 2 commits into
mainfrom
codex/streaming-file-uploads
Open

feat: support streaming file uploads#1970
HAYDEN-OAI wants to merge 2 commits into
mainfrom
codex/streaming-file-uploads

Conversation

@HAYDEN-OAI

@HAYDEN-OAI HAYDEN-OAI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a public toStreamingFile() helper for web, Node, and cloud-storage streams
  • lazily encode multipart request bodies whenever a streaming upload is present
  • keep the existing toFile() behavior unchanged for callers that need a real web File
  • document the streaming upload path and add regression coverage

Root cause

toFile() creates a web File, and the File constructor has to consume stream contents up front. The existing multipart path also converted async streams into a Blob before sending, so large uploads were fully buffered in memory.

Impact

Callers can now provide a filename and optional MIME type without buffering the source stream:

import { toStreamingFile } from 'openai';

await client.audio.transcriptions.create({
  file: toStreamingFile(myReadableStream, 'audio.webm', { type: 'audio/webm' }),
  model: 'whisper-1',
});

The SDK emits a bounded, lazy ReadableStream multipart body and preserves the existing FormData path for non-streaming uploads.

Validation

  • pnpm run format
  • pnpm exec tsc --noEmit
  • pnpm exec jest tests/form.test.ts tests/uploads.test.ts --runInBand

Resolves #418
Resolves #1052

@HAYDEN-OAI HAYDEN-OAI marked this pull request as ready for review July 6, 2026 18:02
@HAYDEN-OAI HAYDEN-OAI requested a review from a team as a code owner July 6, 2026 18:02
@HAYDEN-OAI HAYDEN-OAI requested a review from apcha-oai July 6, 2026 18:02
@openai-sdks

openai-sdks Bot commented Jul 6, 2026

Copy link
Copy Markdown

OkTest Summary

237/237 SDK tests passed in 10.481s for Node SDK PR #1970.

Test results — 42 files
Test Result Time
tests/chat-completions-complex-body.test.ts ✅ Passed 142ms
tests/chat-completions-create.test.ts ✅ Passed 245ms
tests/chat-completions-stream.test.ts ✅ Passed 212ms
tests/files-content-binary.test.ts ✅ Passed 196ms
tests/files-create-multipart.test.ts ✅ Passed 133ms
tests/files-list-pagination.test.ts ✅ Passed 161ms
tests/initialize-config.test.ts ✅ Passed 194ms
tests/instance-isolation.test.ts ✅ Passed 106ms
tests/models-list.test.ts ✅ Passed 133ms
tests/responses-background-lifecycle.test.ts ✅ Passed 178ms
tests/responses-body-method-errors.test.ts ✅ Passed 336ms
tests/responses-cancel-timeout.test.ts ✅ Passed 248ms
tests/responses-cancel.test.ts ✅ Passed 210ms
tests/responses-compact-retries.test.ts ✅ Passed 286ms
tests/responses-compact.test.ts ✅ Passed 204ms
tests/responses-create-advanced-stream.test.ts ✅ Passed 129ms
tests/responses-create-advanced.test.ts ✅ Passed 131ms
tests/responses-create-disconnect.test.ts ✅ Passed 1.199s
tests/responses-create-errors.test.ts ✅ Passed 189ms
tests/responses-create-malformed-api-responses.test.ts ✅ Passed 156ms
tests/responses-create-retries.test.ts ✅ Passed 281ms
tests/responses-create-stream-failures.test.ts ✅ Passed 217ms
tests/responses-create-stream-timeout.test.ts ✅ Passed 2.18s
tests/responses-create-stream-wire.test.ts ✅ Passed 2.749s
tests/responses-create-stream.test.ts ✅ Passed 75ms
tests/responses-create-terminal-states.test.ts ✅ Passed 202ms
tests/responses-create-timeout.test.ts ✅ Passed 220ms
tests/responses-create.test.ts ✅ Passed 139ms
tests/responses-delete.test.ts ✅ Passed 148ms
tests/responses-input-items-errors.test.ts ✅ Passed 236ms
tests/responses-input-items-list.test.ts ✅ Passed 161ms
tests/responses-input-items-options.test.ts ✅ Passed 158ms
tests/responses-input-tokens-count-timeout.test.ts ✅ Passed 282ms
tests/responses-input-tokens-count.test.ts ✅ Passed 192ms
tests/responses-malformed-inputs.test.ts ✅ Passed 2.316s
tests/responses-not-found-errors.test.ts ✅ Passed 248ms
tests/responses-parse.test.ts ✅ Passed 184ms
tests/responses-retrieve-retries.test.ts ✅ Passed 263ms
tests/responses-retrieve.test.ts ✅ Passed 216ms
tests/responses-stored-method-errors.test.ts ✅ Passed 452ms
tests/retry-behavior.test.ts ✅ Passed 3.003s
tests/sdk-error-shape.test.ts ✅ Passed 278ms

View OkTest run #28812918638

SDK merge (9144df3ef1ff) · head (85872a1d88bb) · base (95b54e589491) · OkTest (7fc645091b1a)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d7d60a5d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/internal/uploads.ts
Comment thread src/internal/uploads.ts Outdated
@HAYDEN-OAI HAYDEN-OAI changed the title [codex] support streaming file uploads feat: support streaming file uploads Jul 6, 2026

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, thank you!

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comprehensive review completed against 85872a1d.

I traced this back to #418 and #1052 and reproduced the root cause locally with a 64 MiB Node stream: toFile() consumed all 64 chunks before returning and retained 64 MiB of ArrayBuffer storage (about +123 MiB RSS in that run). The new toStreamingFile() wrapper consumed zero chunks before request transmission and added no measurable ArrayBuffer/RSS usage before send.

I also exercised the change through a real local HTTP server rather than only materializing the generated body. For a four-chunk, 4 MiB async source delayed by 120 ms/chunk, the server received its first bytes at 13 ms while the producer finished at 499 ms; the complete request contained the expected filename, MIME type, scalar field, and 4,194,524-byte multipart envelope. This validates that the SDK/fetch path preserves bounded, incremental delivery.

Validation performed:

  • pnpm exec jest tests/form.test.ts tests/uploads.test.ts tests/index.test.ts --runInBand: 79/79 tests passed, including streaming multipart, Blob chunk, and no-retry coverage.
  • pnpm exec tsc --noEmit: passed.
  • Prettier, ESLint, package build, generated CJS/ESM import checks, type-package validation, and publint all passed locally. The final JSR dry-run step could not write to the sandboxed Deno cache, an environment restriction after the code/package checks had already succeeded.
  • Existing CI is green across Node 20/22/24/26, build, ecosystem, breaking-change, Agents SDK, CodeQL, and 237/237 OkTest cases.

Compatibility review:

  • Existing non-streaming File/Blob/Response uploads retain the native FormData path, and toFile() is unchanged.
  • Node streams, web ReadableStreams, async iterables, nested/array form fields, filenames, MIME types, and plain Blob chunks are handled by the lazy encoder.
  • Streaming bodies are correctly marked one-shot and automatic retries (including workload-identity replay) are suppressed rather than reusing a disturbed body. This intentionally means existing direct fs.ReadStream uploads no longer get buffered/replayable retry behavior; that is the necessary reliability tradeoff for transparent streaming and the explicit helper makes the behavior clear for new callers.
  • Cancellation/backpressure use the existing stream shims, and Node fetch receives duplex: "half".

I found no blocking correctness, API, type, runtime, or regression issue.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memory Leak in vectorStores.fileBatches.uploadAndPoll Support for web ReadableStream without buffering the whole file.

2 participants