From 07a86dda6a2cad66148dde5a58ee4be4f7c63567 Mon Sep 17 00:00:00 2001 From: Saptarshi Bhattacharjee Date: Thu, 7 May 2026 02:05:52 -0700 Subject: [PATCH] fix(files): support filename for stream and byte-array multipart uploads --- .../containers/files/FileCreateParams.kt | 21 +++++-- .../openai/models/files/FileCreateParams.kt | 21 +++++-- .../containers/files/FileCreateParamsTest.kt | 54 +++++++++++++++++- .../models/files/FileCreateParamsTest.kt | 57 ++++++++++++++++++- 4 files changed, 142 insertions(+), 11 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/containers/files/FileCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/containers/files/FileCreateParams.kt index f3403d16b..72638a64d 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/containers/files/FileCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/containers/files/FileCreateParams.kt @@ -118,7 +118,10 @@ private constructor( fun body(body: Body) = apply { this.body = body.toBuilder() } /** The File object (not file name) to be uploaded. */ - fun file(file: InputStream) = apply { body.file(file) } + fun file(file: InputStream) = apply { body.file(file, "file.bin") } + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: InputStream, filename: String) = apply { body.file(file, filename) } /** * Sets [Builder.file] to an arbitrary multipart value. @@ -130,7 +133,10 @@ private constructor( fun file(file: MultipartField) = apply { body.file(file) } /** The File object (not file name) to be uploaded. */ - fun file(file: ByteArray) = apply { body.file(file) } + fun file(file: ByteArray) = apply { body.file(file, "file.bin") } + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: ByteArray, filename: String) = apply { body.file(file, filename) } /** The File object (not file name) to be uploaded. */ fun file(path: Path) = apply { body.file(path) } @@ -362,7 +368,11 @@ private constructor( } /** The File object (not file name) to be uploaded. */ - fun file(file: InputStream) = file(MultipartField.of(file)) + fun file(file: InputStream) = file(file, "file.bin") + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: InputStream, filename: String) = + file(MultipartField.builder().value(file).filename(filename).build()) /** * Sets [Builder.file] to an arbitrary multipart value. @@ -374,7 +384,10 @@ private constructor( fun file(file: MultipartField) = apply { this.file = file } /** The File object (not file name) to be uploaded. */ - fun file(file: ByteArray) = file(file.inputStream()) + fun file(file: ByteArray) = file(file, "file.bin") + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: ByteArray, filename: String) = file(file.inputStream(), filename) /** The File object (not file name) to be uploaded. */ fun file(path: Path) = diff --git a/openai-java-core/src/main/kotlin/com/openai/models/files/FileCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/files/FileCreateParams.kt index 9f5bf9895..074e044e4 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/files/FileCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/files/FileCreateParams.kt @@ -153,7 +153,10 @@ private constructor( fun body(body: Body) = apply { this.body = body.toBuilder() } /** The File object (not file name) to be uploaded. */ - fun file(file: InputStream) = apply { body.file(file) } + fun file(file: InputStream) = apply { body.file(file, "file.bin") } + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: InputStream, filename: String) = apply { body.file(file, filename) } /** * Sets [Builder.file] to an arbitrary multipart value. @@ -165,7 +168,10 @@ private constructor( fun file(file: MultipartField) = apply { body.file(file) } /** The File object (not file name) to be uploaded. */ - fun file(file: ByteArray) = apply { body.file(file) } + fun file(file: ByteArray) = apply { body.file(file, "file.bin") } + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: ByteArray, filename: String) = apply { body.file(file, filename) } /** The File object (not file name) to be uploaded. */ fun file(path: Path) = apply { body.file(path) } @@ -459,7 +465,11 @@ private constructor( } /** The File object (not file name) to be uploaded. */ - fun file(file: InputStream) = file(MultipartField.of(file)) + fun file(file: InputStream) = file(file, "file.bin") + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: InputStream, filename: String) = + file(MultipartField.builder().value(file).filename(filename).build()) /** * Sets [Builder.file] to an arbitrary multipart value. @@ -471,7 +481,10 @@ private constructor( fun file(file: MultipartField) = apply { this.file = file } /** The File object (not file name) to be uploaded. */ - fun file(file: ByteArray) = file(file.inputStream()) + fun file(file: ByteArray) = file(file, "file.bin") + + /** The File object (not file name) to be uploaded, with an explicit filename. */ + fun file(file: ByteArray, filename: String) = file(file.inputStream(), filename) /** The File object (not file name) to be uploaded. */ fun file(path: Path) = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/containers/files/FileCreateParamsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/containers/files/FileCreateParamsTest.kt index 8e2ae4167..0cc138c4a 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/containers/files/FileCreateParamsTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/containers/files/FileCreateParamsTest.kt @@ -48,7 +48,11 @@ internal class FileCreateParamsTest { ) .isEqualTo( mapOf( - "file" to MultipartField.of("Example data".byteInputStream()), + "file" to + MultipartField.builder() + .value("Example data".byteInputStream()) + .filename("file.bin") + .build(), "file_id" to MultipartField.of("file_id"), ) .mapValues { (_, field) -> @@ -65,4 +69,52 @@ internal class FileCreateParamsTest { assertThat(body.filterValues { !it.value.isNull() }).isEmpty() } + + @Test + fun fileWithInputStreamUsesDefaultFilename() { + val params = + FileCreateParams.builder() + .containerId("container_id") + .file("Example data".byteInputStream()) + .build() + + assertThat(params._file().filename()).contains("file.bin") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } + + @Test + fun fileWithBytesUsesDefaultFilename() { + val params = + FileCreateParams.builder() + .containerId("container_id") + .file("Example data".toByteArray()) + .build() + + assertThat(params._file().filename()).contains("file.bin") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } + + @Test + fun fileWithInputStreamAndFilename() { + val params = + FileCreateParams.builder() + .containerId("container_id") + .file("Example data".byteInputStream(), "container-input.txt") + .build() + + assertThat(params._file().filename()).contains("container-input.txt") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } + + @Test + fun fileWithBytesAndFilename() { + val params = + FileCreateParams.builder() + .containerId("container_id") + .file("Example data".toByteArray(), "container-input.txt") + .build() + + assertThat(params._file().filename()).contains("container-input.txt") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } } diff --git a/openai-java-core/src/test/kotlin/com/openai/models/files/FileCreateParamsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/files/FileCreateParamsTest.kt index d0e25ed1b..35a0f70e8 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/files/FileCreateParamsTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/files/FileCreateParamsTest.kt @@ -39,7 +39,11 @@ internal class FileCreateParamsTest { ) .isEqualTo( mapOf( - "file" to MultipartField.of("Example data".byteInputStream()), + "file" to + MultipartField.builder() + .value("Example data".byteInputStream()) + .filename("file.bin") + .build(), "purpose" to MultipartField.of(FilePurpose.ASSISTANTS), "expires_after" to MultipartField.of( @@ -72,7 +76,11 @@ internal class FileCreateParamsTest { ) .isEqualTo( mapOf( - "file" to MultipartField.of("Example data".byteInputStream()), + "file" to + MultipartField.builder() + .value("Example data".byteInputStream()) + .filename("file.bin") + .build(), "purpose" to MultipartField.of(FilePurpose.ASSISTANTS), ) .mapValues { (_, field) -> @@ -80,4 +88,49 @@ internal class FileCreateParamsTest { } ) } + + @Test + fun fileWithInputStreamUsesDefaultFilename() { + val params = + FileCreateParams.builder() + .file("Example data".byteInputStream()) + .purpose(FilePurpose.BATCH) + .build() + + assertThat(params._file().filename()).contains("file.bin") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } + + @Test + fun fileWithBytesUsesDefaultFilename() { + val params = + FileCreateParams.builder().file("Example data".toByteArray()).purpose(FilePurpose.BATCH).build() + + assertThat(params._file().filename()).contains("file.bin") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } + + @Test + fun fileWithInputStreamAndFilename() { + val params = + FileCreateParams.builder() + .file("Example data".byteInputStream(), "input.jsonl") + .purpose(FilePurpose.BATCH) + .build() + + assertThat(params._file().filename()).contains("input.jsonl") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } + + @Test + fun fileWithBytesAndFilename() { + val params = + FileCreateParams.builder() + .file("Example data".toByteArray(), "input.jsonl") + .purpose(FilePurpose.BATCH) + .build() + + assertThat(params._file().filename()).contains("input.jsonl") + assertThat(params._file().contentType).isEqualTo("application/octet-stream") + } }