diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 59acac47..0ac3c2a5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.27.0" + ".": "0.27.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e467bd35..4dc9e9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.27.1 (2025-09-29) + +Full Changelog: [v0.27.0...v0.27.1](https://github.com/openai/openai-ruby/compare/v0.27.0...v0.27.1) + +### Bug Fixes + +* always send `filename=...` for multipart requests where a file is expected ([99849fd](https://github.com/openai/openai-ruby/commit/99849fddf478869b57b77cbe208efd13d9a8246e)) + ## 0.27.0 (2025-09-26) Full Changelog: [v0.26.0...v0.27.0](https://github.com/openai/openai-ruby/compare/v0.26.0...v0.27.0) diff --git a/Gemfile.lock b/Gemfile.lock index 00a44544..a6273fa9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ GIT PATH remote: . specs: - openai (0.27.0) + openai (0.27.1) connection_pool GEM diff --git a/README.md b/README.md index 7a05c316..642344a2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application ```ruby -gem "openai", "~> 0.27.0" +gem "openai", "~> 0.27.1" ``` diff --git a/lib/openai/file_part.rb b/lib/openai/file_part.rb index f853ca4d..da661b25 100644 --- a/lib/openai/file_part.rb +++ b/lib/openai/file_part.rb @@ -38,18 +38,21 @@ def to_json(*a) = read.to_json(*a) def to_yaml(*a) = read.to_yaml(*a) # @param content [Pathname, StringIO, IO, String] - # @param filename [String, nil] + # @param filename [Pathname, String, nil] # @param content_type [String, nil] def initialize(content, filename: nil, content_type: nil) - @content = content + @content_type = content_type @filename = - case content - in Pathname - filename.nil? ? content.basename.to_path : ::File.basename(filename) + case [filename, (@content = content)] + in [String | Pathname, _] + ::File.basename(filename) + in [nil, Pathname] + content.basename.to_path + in [nil, IO] + content.to_path else - filename.nil? ? nil : ::File.basename(filename) + filename end - @content_type = content_type end end end diff --git a/lib/openai/internal/type/file_input.rb b/lib/openai/internal/type/file_input.rb index a5dcdef7..194cd38b 100644 --- a/lib/openai/internal/type/file_input.rb +++ b/lib/openai/internal/type/file_input.rb @@ -82,17 +82,20 @@ def coerce(value, state:) # # @return [Pathname, StringIO, IO, String, Object] def dump(value, state:) - # rubocop:disable Lint/DuplicateBranch case value + in StringIO | String + # https://datatracker.ietf.org/doc/html/rfc7578#section-4.2 + # while not required, a filename is recommended, and in practice many servers do expect this + OpenAI::FilePart.new(value, filename: "upload") in IO state[:can_retry] = false + value.to_path.nil? ? OpenAI::FilePart.new(value, filename: "upload") : value in OpenAI::FilePart if value.content.is_a?(IO) state[:can_retry] = false + value else + value end - # rubocop:enable Lint/DuplicateBranch - - value end # @api private diff --git a/lib/openai/version.rb b/lib/openai/version.rb index 472922d8..c1472d01 100644 --- a/lib/openai/version.rb +++ b/lib/openai/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenAI - VERSION = "0.27.0" + VERSION = "0.27.1" end diff --git a/rbi/openai/file_part.rbi b/rbi/openai/file_part.rbi index 5df7359c..07a8b7a6 100644 --- a/rbi/openai/file_part.rbi +++ b/rbi/openai/file_part.rbi @@ -27,7 +27,7 @@ module OpenAI sig do params( content: T.any(Pathname, StringIO, IO, String), - filename: T.nilable(String), + filename: T.nilable(T.any(Pathname, String)), content_type: T.nilable(String) ).returns(T.attached_class) end diff --git a/sig/openai/file_part.rbs b/sig/openai/file_part.rbs index c517bee8..73892c6a 100644 --- a/sig/openai/file_part.rbs +++ b/sig/openai/file_part.rbs @@ -14,7 +14,7 @@ module OpenAI def initialize: ( Pathname | StringIO | IO | String content, - ?filename: String?, + ?filename: (Pathname | String)?, ?content_type: String? ) -> void end diff --git a/test/openai/internal/util_test.rb b/test/openai/internal/util_test.rb index 26bacf49..786e7c45 100644 --- a/test/openai/internal/util_test.rb +++ b/test/openai/internal/util_test.rb @@ -227,20 +227,24 @@ def test_encoding_length def test_file_encode file = Pathname(__FILE__) + fileinput = OpenAI::Internal::Type::Converter.dump(OpenAI::Internal::Type::FileInput, "abc") headers = {"content-type" => "multipart/form-data"} cases = { - "abc" => "abc", - StringIO.new("abc") => "abc", - OpenAI::FilePart.new("abc") => "abc", - OpenAI::FilePart.new(StringIO.new("abc")) => "abc", - file => /^class OpenAI/, - OpenAI::FilePart.new(file) => /^class OpenAI/ + "abc" => ["", "abc"], + StringIO.new("abc") => ["", "abc"], + fileinput => %w[upload abc], + OpenAI::FilePart.new(StringIO.new("abc")) => ["", "abc"], + file => [file.basename.to_path, /^class OpenAI/], + OpenAI::FilePart.new(file, filename: "d o g") => ["d%20o%20g", /^class OpenAI/] } - cases.each do |body, val| + cases.each do |body, testcase| + filename, val = testcase encoded = OpenAI::Internal::Util.encode_content(headers, body) cgi = FakeCGI.new(*encoded) + io = cgi[""] assert_pattern do - cgi[""].read => ^val + io.original_filename => ^filename + io.read => ^val end end end @@ -261,7 +265,14 @@ def test_hash_encode cgi = FakeCGI.new(*encoded) testcase.each do |key, val| assert_pattern do - cgi[key] => ^val + parsed = + case (p = cgi[key]) + in StringIO + p.read + else + p + end + parsed => ^val end end end