Skip to content

fixed how array fields are sent in multipart form POSTs - #249

Closed
ben5516 wants to merge 4 commits into
openai:mainfrom
ben5516:fix-array-multipart-form-fields
Closed

fixed how array fields are sent in multipart form POSTs#249
ben5516 wants to merge 4 commits into
openai:mainfrom
ben5516:fix-array-multipart-form-fields

Conversation

@ben5516

@ben5516 ben5516 commented Feb 20, 2026

Copy link
Copy Markdown

This is a naive approach to properly supporting array fields in multipart encoded POST requests.

The issue I was having was that this code was not correctly sending timestamp_granularities to the server (see example):

response = client.audio.transcriptions.create(
          model:                    "whisper-1",
          file:                     File.open(mp3_filepath.to_s, "rb"),
          response_format:          :verbose_json,
          timestamp_granularities:  [ :word, :segment ],
          language:                 "en"
 )

The server is expecting an array field, where the key is timestamp_granularities[]. However, the url_encode sanitization pass encodes the square brackets and the server doesn't decode it. Sanitization should be less aggressive, and only escape LF, CR, ", and \ I believe.

It’s not necessary to url_encode the field keys, and that breaks timestamp_granularities[], among other things
@ben5516
ben5516 requested a review from a team as a code owner February 20, 2026 21:16

@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: 9d94e79a2b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread lib/openai/internal/util.rb Outdated

Copy link
Copy Markdown
Contributor

Thanks for reporting this and for working through the timestamp_granularities[] wire-name issue. This PR identified the core array-field behavior we needed to preserve.

We’ve consolidated this fix with file-array and nested-object multipart handling in #348, implemented once in the shared serializer and covered by generated-model and wire-format tests. I’m closing this PR in favor of #348 so the remaining review and discussion have a single home. Really appreciate the contribution.

jbeckwith-oai added a commit to fallintoplace/openai-ruby that referenced this pull request Aug 10, 2026
## Summary

- encode multipart arrays recursively as `field[]`, including both
primitive and file arrays
- flatten nested multipart objects with bracket notation, including
arrays of nested objects
- preserve scalar field names and streaming file behavior
- escape both `name` and `filename` `Content-Disposition` parameters
- keep the fix entirely in the openai-ruby runtime; no generated model,
OpenAPI, or Castiron changes are needed

## Root cause

The shared multipart writer repeated top-level primitive and file arrays
using the scalar field name and serialized nested hashes as JSON parts.
The API's form parser expects the same global brackets format used by
openai-python and by the transformed OpenAPI examples.

Before:

- `timestamp_granularities=word` and `timestamp_granularities=segment`
- duplicate scalar `image` file parts
- one JSON `expires_after` part

After:

- `timestamp_granularities[]=word` and
`timestamp_granularities[]=segment`
- duplicate `image[]` file parts, while a single image remains `image`
- `expires_after[anchor]=created_at` and `expires_after[seconds]=3600`

The implementation replaces the old top-level array special case with
one recursive field writer. It preserves insertion order, array order,
duplicate values, and streaming file contents without materializing a
flattened body.

This consolidates and supersedes openai#249, openai#311, and openai#339.

Fixes openai#201.
Fixes openai#212.
Fixes openai#253.

## Validation

- `mise exec ruby@4.0.6 -- bundle exec ruby -Itest
test/openai/internal/util_test.rb` — 45 runs, 178 assertions
- `mise exec ruby@4.0.6 -- ./scripts/test` — 522 runs, 1,709 assertions
- `mise exec ruby@4.0.6 -- bundle exec rake lint` — 2,596 files, no
RuboCop offenses; Sorbet clean; 1,211 RBS files valid
- `mise exec ruby@4.0.6 -- bundle exec rake build:gem` — built
`openai-0.78.0.gem`
- RBI and RBS formatting plus `git diff --check`
- thermo-nuclear maintainability review and a separate
correctness/readability/architecture/security/performance review,
repeated after findings until clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

generator Touches generated SDK files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants