diff --git a/lib/anthropic/internal/util.rb b/lib/anthropic/internal/util.rb index d33935ba..f2dd310d 100644 --- a/lib/anthropic/internal/util.rb +++ b/lib/anthropic/internal/util.rb @@ -550,14 +550,16 @@ def encode_query_params(query) y, val: val.content, closing: closing, - content_type: val.content_type + content_type: val.content_type || infer_multipart_content_type(val.filename) ) in Pathname + content_type ||= infer_multipart_content_type(val) y << format(content_line, content_type || "application/octet-stream") io = val.open(binmode: true) closing << io.method(:close) IO.copy_stream(io, y) in IO + content_type ||= infer_multipart_content_type(val.to_path) y << format(content_line, content_type || "application/octet-stream") IO.copy_stream(val, y) in StringIO @@ -573,6 +575,20 @@ def encode_query_params(query) y << "\r\n" end + # @api private + # + # @param filename [Pathname, String, nil] + # + # @return [String, nil] + private def infer_multipart_content_type(filename) + case ::File.extname(filename.to_s).downcase + when ".pdf" + "application/pdf" + when ".txt" + "text/plain" + end + end + # @api private # # @param y [Enumerator::Yielder] diff --git a/test/anthropic/internal/util_test.rb b/test/anthropic/internal/util_test.rb index 133445e3..353e4aa9 100644 --- a/test/anthropic/internal/util_test.rb +++ b/test/anthropic/internal/util_test.rb @@ -278,6 +278,26 @@ def test_multipart_filename_quoting refute_includes(body, "\r\nEvil:") end + def test_multipart_document_content_type_inference + headers = {"content-type" => "multipart/form-data"} + cases = { + Anthropic::FilePart.new(StringIO.new("x"), filename: "document.pdf") => "application/pdf", + Anthropic::FilePart.new(StringIO.new("x"), filename: "document.txt") => "text/plain", + Anthropic::FilePart.new( + StringIO.new("x"), + filename: "document.pdf", + content_type: "application/custom" + ) => "application/custom" + } + + cases.each do |file, content_type| + _headers, stream = Anthropic::Internal::Util.encode_content(headers, {file: file}) + body = stream.respond_to?(:read) ? stream.read : stream.to_a.join + + assert_includes(body, "Content-Type: #{content_type}\r\n\r\n") + end + end + def test_hash_encode headers = {"content-type" => "multipart/form-data"} cases = {