From 9d94e79a2b774506a5acf6de0246402732a3fe9b Mon Sep 17 00:00:00 2001 From: Ben Govero Date: Fri, 20 Feb 2026 15:15:10 -0600 Subject: [PATCH 1/4] fixed how array fields are sent in multipart form POSTs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not necessary to url_encode the field keys, and that breaks timestamp_granularities[], among other things --- lib/openai/internal/util.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/openai/internal/util.rb b/lib/openai/internal/util.rb index 429fbaf7..9f1e6d10 100644 --- a/lib/openai/internal/util.rb +++ b/lib/openai/internal/util.rb @@ -540,8 +540,7 @@ class << self y << "Content-Disposition: form-data" unless key.nil? - name = ERB::Util.url_encode(key.to_s) - y << "; name=\"#{name}\"" + y << "; name=\"#{key}\"" end case val @@ -577,7 +576,7 @@ class << self case val in Array if val.all? { primitive?(_1) } val.each do |v| - write_multipart_chunk(y, boundary: boundary, key: key, val: v, closing: closing) + write_multipart_chunk(y, boundary: boundary, key: "#{key}[]", val: v, closing: closing) end else write_multipart_chunk(y, boundary: boundary, key: key, val: val, closing: closing) From 28ab6db1085d77d9ad6030edd96fd0313038ef76 Mon Sep 17 00:00:00 2001 From: Ben Govero Date: Thu, 26 Feb 2026 16:41:41 -0600 Subject: [PATCH 2/4] Took a more surgical approach to array field keys --- lib/openai/internal/util.rb | 8 +++++--- sig/openai/internal/util.rbs | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/openai/internal/util.rb b/lib/openai/internal/util.rb index 9f1e6d10..8cd791bf 100644 --- a/lib/openai/internal/util.rb +++ b/lib/openai/internal/util.rb @@ -535,12 +535,14 @@ class << self # @param key [Symbol, String] # @param val [Object] # @param closing [Array] - private def write_multipart_chunk(y, boundary:, key:, val:, closing:) + private def write_multipart_chunk(y, boundary:, key:, val:, closing:, is_array: false) y << "--#{boundary}\r\n" y << "Content-Disposition: form-data" unless key.nil? - y << "; name=\"#{key}\"" + name = ERB::Util.url_encode(key.to_s) + name = "#{name}[]" if is_array + y << "; name=\"#{name}\"" end case val @@ -576,7 +578,7 @@ class << self case val in Array if val.all? { primitive?(_1) } val.each do |v| - write_multipart_chunk(y, boundary: boundary, key: "#{key}[]", val: v, closing: closing) + write_multipart_chunk(y, boundary: boundary, key: "#{key}", val: v, closing: closing, is_array: true) end else write_multipart_chunk(y, boundary: boundary, key: key, val: val, closing: closing) diff --git a/sig/openai/internal/util.rbs b/sig/openai/internal/util.rbs index ec425e9f..5378fa7d 100644 --- a/sig/openai/internal/util.rbs +++ b/sig/openai/internal/util.rbs @@ -117,6 +117,7 @@ module OpenAI Enumerator::Yielder y, boundary: String, key: Symbol | String, + ?is_array: bool, val: top, closing: ::Array[^-> void] ) -> void From 1f5cb00b26b1db100c3d6a2b8ae3f8c029bcf1c1 Mon Sep 17 00:00:00 2001 From: Ben Govero Date: Thu, 26 Feb 2026 16:45:29 -0600 Subject: [PATCH 3/4] ran linter --- lib/openai/internal/util.rb | 9 ++++++++- sig/openai/internal/util.rbs | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/openai/internal/util.rb b/lib/openai/internal/util.rb index 8cd791bf..3f20340b 100644 --- a/lib/openai/internal/util.rb +++ b/lib/openai/internal/util.rb @@ -578,7 +578,14 @@ class << self case val in Array if val.all? { primitive?(_1) } val.each do |v| - write_multipart_chunk(y, boundary: boundary, key: "#{key}", val: v, closing: closing, is_array: true) + write_multipart_chunk( + y, + boundary: boundary, + key: key, + val: v, + closing: closing, + is_array: true + ) end else write_multipart_chunk(y, boundary: boundary, key: key, val: val, closing: closing) diff --git a/sig/openai/internal/util.rbs b/sig/openai/internal/util.rbs index 5378fa7d..98dcf392 100644 --- a/sig/openai/internal/util.rbs +++ b/sig/openai/internal/util.rbs @@ -117,9 +117,9 @@ module OpenAI Enumerator::Yielder y, boundary: String, key: Symbol | String, - ?is_array: bool, val: top, - closing: ::Array[^-> void] + closing: ::Array[^-> void], + ?is_array: bool ) -> void def self?.encode_multipart_streaming: ( From 7dfc722fcf54ee6c38e68cc9eaade2e030c6cabb Mon Sep 17 00:00:00 2001 From: Ben Govero Date: Thu, 26 Feb 2026 16:55:36 -0600 Subject: [PATCH 4/4] fixed tests --- test/openai/internal/util_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/openai/internal/util_test.rb b/test/openai/internal/util_test.rb index 560d786d..289d8922 100644 --- a/test/openai/internal/util_test.rb +++ b/test/openai/internal/util_test.rb @@ -264,7 +264,7 @@ def test_hash_encode cases = { {a: 2, b: 3} => {"a" => "2", "b" => "3"}, {a: 2, b: nil} => {"a" => "2", "b" => "null"}, - {a: 2, b: [1, 2, 3]} => {"a" => "2", "b" => "1"}, + {a: 2, b: [1, 2, 3]} => {"a" => "2", "b[]" => "1"}, {strio: StringIO.new("a")} => {"strio" => "a"}, {strio: OpenAI::FilePart.new("a")} => {"strio" => "a"}, {pathname: Pathname(__FILE__)} => {"pathname" => -> { _1.read in /^class OpenAI/ }},