diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 0c2ecec6..86b0e83d 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.20.0"
+ ".": "0.21.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 6a8ca6de..c811d3a7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 60
+configured_endpoints: 61
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai-6204952a29973265b9c0d66fc67ffaf53c6a90ae4d75cdacf9d147676f5274c9.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd4d60a3..89a50806 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 0.21.0 (2025-02-05)
+
+Full Changelog: [v0.20.0...v0.21.0](https://github.com/openai/openai-java/compare/v0.20.0...v0.21.0)
+
+### Features
+
+* **api:** add file content endpoint ([#198](https://github.com/openai/openai-java/issues/198)) ([3dd469f](https://github.com/openai/openai-java/commit/3dd469f393b1c33348e6e709687d328154d0aa84))
+* **client:** send client-side timeout headers ([#196](https://github.com/openai/openai-java/issues/196)) ([03706d4](https://github.com/openai/openai-java/commit/03706d4a1f37936e83cb97dd53d815ce12933061))
+
+
+### Bug Fixes
+
+* add deploymentModel ([38e173d](https://github.com/openai/openai-java/commit/38e173d262dae86658355c69052a135398192d80))
+
## 0.20.0 (2025-01-31)
Full Changelog: [v0.19.0...v0.20.0](https://github.com/openai/openai-java/compare/v0.19.0...v0.20.0)
diff --git a/README.md b/README.md
index 141044ac..8e7571fd 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,8 @@
-[](https://central.sonatype.com/artifact/com.openai/openai-java/0.20.0)
-[](https://javadoc.io/doc/com.openai/openai-java/0.20.0)
+[](https://central.sonatype.com/artifact/com.openai/openai-java/0.21.0)
+[](https://javadoc.io/doc/com.openai/openai-java/0.21.0)
@@ -25,7 +25,7 @@ The REST API documentation can be found on [platform.openai.com](https://platfor
### Gradle
```kotlin
-implementation("com.openai:openai-java:0.20.0")
+implementation("com.openai:openai-java:0.21.0")
```
### Maven
@@ -34,7 +34,7 @@ implementation("com.openai:openai-java:0.20.0")
com.openai
openai-java
- 0.20.0
+ 0.21.0
```
diff --git a/build.gradle.kts b/build.gradle.kts
index a89788a5..54cf9e4c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "com.openai"
- version = "0.20.0" // x-release-please-version
+ version = "0.21.0" // x-release-please-version
}
subprojects {
diff --git a/openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OkHttpClient.kt b/openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OkHttpClient.kt
index 1efd82e5..6cbaeacc 100644
--- a/openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OkHttpClient.kt
+++ b/openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OkHttpClient.kt
@@ -31,38 +31,11 @@ class OkHttpClient
private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val baseUrl: HttpUrl) :
HttpClient {
- private fun getClient(requestOptions: RequestOptions): okhttp3.OkHttpClient {
- val clientBuilder = okHttpClient.newBuilder()
-
- val logLevel =
- when (System.getenv("OPENAI_LOG")?.lowercase()) {
- "info" -> HttpLoggingInterceptor.Level.BASIC
- "debug" -> HttpLoggingInterceptor.Level.BODY
- else -> null
- }
- if (logLevel != null) {
- clientBuilder.addNetworkInterceptor(
- HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("Authorization") }
- )
- }
-
- val timeout = requestOptions.timeout
- if (timeout != null) {
- clientBuilder
- .connectTimeout(timeout)
- .readTimeout(timeout)
- .writeTimeout(timeout)
- .callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
- }
-
- return clientBuilder.build()
- }
-
override fun execute(
request: HttpRequest,
requestOptions: RequestOptions,
): HttpResponse {
- val call = getClient(requestOptions).newCall(request.toRequest())
+ val call = newCall(request, requestOptions)
return try {
call.execute().toResponse()
@@ -81,18 +54,18 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
request.body?.run { future.whenComplete { _, _ -> close() } }
- val call = getClient(requestOptions).newCall(request.toRequest())
- call.enqueue(
- object : Callback {
- override fun onResponse(call: Call, response: Response) {
- future.complete(response.toResponse())
- }
+ newCall(request, requestOptions)
+ .enqueue(
+ object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ future.complete(response.toResponse())
+ }
- override fun onFailure(call: Call, e: IOException) {
- future.completeExceptionally(OpenAIIoException("Request failed", e))
+ override fun onFailure(call: Call, e: IOException) {
+ future.completeExceptionally(OpenAIIoException("Request failed", e))
+ }
}
- }
- )
+ )
return future
}
@@ -103,7 +76,35 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
okHttpClient.cache?.close()
}
- private fun HttpRequest.toRequest(): Request {
+ private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
+ val clientBuilder = okHttpClient.newBuilder()
+
+ val logLevel =
+ when (System.getenv("OPENAI_LOG")?.lowercase()) {
+ "info" -> HttpLoggingInterceptor.Level.BASIC
+ "debug" -> HttpLoggingInterceptor.Level.BODY
+ else -> null
+ }
+ if (logLevel != null) {
+ clientBuilder.addNetworkInterceptor(
+ HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("Authorization") }
+ )
+ }
+
+ val timeout = requestOptions.timeout
+ if (timeout != null) {
+ clientBuilder
+ .connectTimeout(timeout)
+ .readTimeout(timeout)
+ .writeTimeout(timeout)
+ .callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
+ }
+
+ val client = clientBuilder.build()
+ return client.newCall(request.toRequest(client))
+ }
+
+ private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient): Request {
var body: RequestBody? = body?.toRequestBody()
// OkHttpClient always requires a request body for PUT and POST methods.
if (body == null && (method == HttpMethod.PUT || method == HttpMethod.POST)) {
@@ -115,6 +116,21 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
headers.values(name).forEach { builder.header(name, it) }
}
+ if (
+ !headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
+ ) {
+ builder.header(
+ "X-Stainless-Read-Timeout",
+ Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString()
+ )
+ }
+ if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
+ builder.header(
+ "X-Stainless-Timeout",
+ Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString()
+ )
+ }
+
return builder.build()
}
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/FileContentParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/FileContentParams.kt
new file mode 100644
index 00000000..a519f950
--- /dev/null
+++ b/openai-java-core/src/main/kotlin/com/openai/models/FileContentParams.kt
@@ -0,0 +1,179 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.openai.models
+
+import com.openai.core.NoAutoDetect
+import com.openai.core.Params
+import com.openai.core.checkRequired
+import com.openai.core.http.Headers
+import com.openai.core.http.QueryParams
+import java.util.Objects
+
+/** Returns the contents of the specified file. */
+class FileContentParams
+private constructor(
+ private val fileId: String,
+ private val additionalHeaders: Headers,
+ private val additionalQueryParams: QueryParams,
+) : Params {
+
+ fun fileId(): String = fileId
+
+ fun _additionalHeaders(): Headers = additionalHeaders
+
+ fun _additionalQueryParams(): QueryParams = additionalQueryParams
+
+ override fun _headers(): Headers = additionalHeaders
+
+ override fun _queryParams(): QueryParams = additionalQueryParams
+
+ fun getPathParam(index: Int): String {
+ return when (index) {
+ 0 -> fileId
+ else -> ""
+ }
+ }
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [FileContentParams]. */
+ @NoAutoDetect
+ class Builder internal constructor() {
+
+ private var fileId: String? = null
+ private var additionalHeaders: Headers.Builder = Headers.builder()
+ private var additionalQueryParams: QueryParams.Builder = QueryParams.builder()
+
+ @JvmSynthetic
+ internal fun from(fileContentParams: FileContentParams) = apply {
+ fileId = fileContentParams.fileId
+ additionalHeaders = fileContentParams.additionalHeaders.toBuilder()
+ additionalQueryParams = fileContentParams.additionalQueryParams.toBuilder()
+ }
+
+ fun fileId(fileId: String) = apply { this.fileId = fileId }
+
+ fun additionalHeaders(additionalHeaders: Headers) = apply {
+ this.additionalHeaders.clear()
+ putAllAdditionalHeaders(additionalHeaders)
+ }
+
+ fun additionalHeaders(additionalHeaders: Map>) = apply {
+ this.additionalHeaders.clear()
+ putAllAdditionalHeaders(additionalHeaders)
+ }
+
+ fun putAdditionalHeader(name: String, value: String) = apply {
+ additionalHeaders.put(name, value)
+ }
+
+ fun putAdditionalHeaders(name: String, values: Iterable) = apply {
+ additionalHeaders.put(name, values)
+ }
+
+ fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply {
+ this.additionalHeaders.putAll(additionalHeaders)
+ }
+
+ fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply {
+ this.additionalHeaders.putAll(additionalHeaders)
+ }
+
+ fun replaceAdditionalHeaders(name: String, value: String) = apply {
+ additionalHeaders.replace(name, value)
+ }
+
+ fun replaceAdditionalHeaders(name: String, values: Iterable) = apply {
+ additionalHeaders.replace(name, values)
+ }
+
+ fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply {
+ this.additionalHeaders.replaceAll(additionalHeaders)
+ }
+
+ fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply {
+ this.additionalHeaders.replaceAll(additionalHeaders)
+ }
+
+ fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) }
+
+ fun removeAllAdditionalHeaders(names: Set) = apply {
+ additionalHeaders.removeAll(names)
+ }
+
+ fun additionalQueryParams(additionalQueryParams: QueryParams) = apply {
+ this.additionalQueryParams.clear()
+ putAllAdditionalQueryParams(additionalQueryParams)
+ }
+
+ fun additionalQueryParams(additionalQueryParams: Map>) = apply {
+ this.additionalQueryParams.clear()
+ putAllAdditionalQueryParams(additionalQueryParams)
+ }
+
+ fun putAdditionalQueryParam(key: String, value: String) = apply {
+ additionalQueryParams.put(key, value)
+ }
+
+ fun putAdditionalQueryParams(key: String, values: Iterable) = apply {
+ additionalQueryParams.put(key, values)
+ }
+
+ fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply {
+ this.additionalQueryParams.putAll(additionalQueryParams)
+ }
+
+ fun putAllAdditionalQueryParams(additionalQueryParams: Map>) =
+ apply {
+ this.additionalQueryParams.putAll(additionalQueryParams)
+ }
+
+ fun replaceAdditionalQueryParams(key: String, value: String) = apply {
+ additionalQueryParams.replace(key, value)
+ }
+
+ fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply {
+ additionalQueryParams.replace(key, values)
+ }
+
+ fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply {
+ this.additionalQueryParams.replaceAll(additionalQueryParams)
+ }
+
+ fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) =
+ apply {
+ this.additionalQueryParams.replaceAll(additionalQueryParams)
+ }
+
+ fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) }
+
+ fun removeAllAdditionalQueryParams(keys: Set) = apply {
+ additionalQueryParams.removeAll(keys)
+ }
+
+ fun build(): FileContentParams =
+ FileContentParams(
+ checkRequired("fileId", fileId),
+ additionalHeaders.build(),
+ additionalQueryParams.build(),
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return /* spotless:off */ other is FileContentParams && fileId == other.fileId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ }
+
+ override fun hashCode(): Int = /* spotless:off */ Objects.hash(fileId, additionalHeaders, additionalQueryParams) /* spotless:on */
+
+ override fun toString() =
+ "FileContentParams{fileId=$fileId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
+}
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsync.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsync.kt
index d186f68c..4dcad1cd 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsync.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsync.kt
@@ -5,6 +5,8 @@
package com.openai.services.async
import com.openai.core.RequestOptions
+import com.openai.core.http.HttpResponse
+import com.openai.models.FileContentParams
import com.openai.models.FileDeleteParams
import com.openai.models.FileDeleted
import com.openai.models.FileListPageAsync
@@ -35,4 +37,11 @@ interface FileServiceAsync {
params: FileDeleteParams,
requestOptions: RequestOptions = RequestOptions.none()
): CompletableFuture
+
+ /** Returns the contents of the specified file. */
+ @JvmOverloads
+ fun content(
+ params: FileContentParams,
+ requestOptions: RequestOptions = RequestOptions.none()
+ ): CompletableFuture
}
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsyncImpl.kt
index 8b331e29..c76c52b2 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsyncImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/async/FileServiceAsyncImpl.kt
@@ -9,10 +9,12 @@ import com.openai.core.handlers.jsonHandler
import com.openai.core.handlers.withErrorHandler
import com.openai.core.http.HttpMethod
import com.openai.core.http.HttpRequest
+import com.openai.core.http.HttpResponse
import com.openai.core.http.HttpResponse.Handler
import com.openai.core.json
import com.openai.core.prepareAsync
import com.openai.errors.OpenAIError
+import com.openai.models.FileContentParams
import com.openai.models.FileDeleteParams
import com.openai.models.FileDeleted
import com.openai.models.FileListPageAsync
@@ -111,4 +113,20 @@ internal constructor(
}
}
}
+
+ /** Returns the contents of the specified file. */
+ override fun content(
+ params: FileContentParams,
+ requestOptions: RequestOptions
+ ): CompletableFuture {
+ val request =
+ HttpRequest.builder()
+ .method(HttpMethod.GET)
+ .addPathSegments("files", params.getPathParam(0), "content")
+ .build()
+ .prepareAsync(clientOptions, params, deploymentModel = null)
+ return request.thenComposeAsync {
+ clientOptions.httpClient.executeAsync(it, requestOptions)
+ }
+ }
}
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileService.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileService.kt
index f664ba23..13e95c5b 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileService.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileService.kt
@@ -5,6 +5,8 @@
package com.openai.services.blocking
import com.openai.core.RequestOptions
+import com.openai.core.http.HttpResponse
+import com.openai.models.FileContentParams
import com.openai.models.FileDeleteParams
import com.openai.models.FileDeleted
import com.openai.models.FileListPage
@@ -34,4 +36,11 @@ interface FileService {
params: FileDeleteParams,
requestOptions: RequestOptions = RequestOptions.none()
): FileDeleted
+
+ /** Returns the contents of the specified file. */
+ @JvmOverloads
+ fun content(
+ params: FileContentParams,
+ requestOptions: RequestOptions = RequestOptions.none()
+ ): HttpResponse
}
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileServiceImpl.kt
index 21245a63..e69137ed 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileServiceImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/FileServiceImpl.kt
@@ -9,10 +9,12 @@ import com.openai.core.handlers.jsonHandler
import com.openai.core.handlers.withErrorHandler
import com.openai.core.http.HttpMethod
import com.openai.core.http.HttpRequest
+import com.openai.core.http.HttpResponse
import com.openai.core.http.HttpResponse.Handler
import com.openai.core.json
import com.openai.core.prepare
import com.openai.errors.OpenAIError
+import com.openai.models.FileContentParams
import com.openai.models.FileDeleteParams
import com.openai.models.FileDeleted
import com.openai.models.FileListPage
@@ -94,4 +96,15 @@ internal constructor(
}
}
}
+
+ /** Returns the contents of the specified file. */
+ override fun content(params: FileContentParams, requestOptions: RequestOptions): HttpResponse {
+ val request =
+ HttpRequest.builder()
+ .method(HttpMethod.GET)
+ .addPathSegments("files", params.getPathParam(0), "content")
+ .build()
+ .prepare(clientOptions, params, deploymentModel = null)
+ return clientOptions.httpClient.execute(request, requestOptions)
+ }
}
diff --git a/openai-java-core/src/test/kotlin/com/openai/models/FileContentParamsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/FileContentParamsTest.kt
new file mode 100644
index 00000000..423cded0
--- /dev/null
+++ b/openai-java-core/src/test/kotlin/com/openai/models/FileContentParamsTest.kt
@@ -0,0 +1,24 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.openai.models
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class FileContentParamsTest {
+
+ @Test
+ fun createFileContentParams() {
+ FileContentParams.builder().fileId("file_id").build()
+ }
+
+ @Test
+ fun getPathParam() {
+ val params = FileContentParams.builder().fileId("file_id").build()
+ assertThat(params).isNotNull
+ // path param "fileId"
+ assertThat(params.getPathParam(0)).isEqualTo("file_id")
+ // out-of-bound path param
+ assertThat(params.getPathParam(1)).isEqualTo("")
+ }
+}
diff --git a/openai-java-core/src/test/kotlin/com/openai/services/blocking/FileServiceTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/blocking/FileServiceTest.kt
index 2b108d90..6c101c7a 100644
--- a/openai-java-core/src/test/kotlin/com/openai/services/blocking/FileServiceTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/services/blocking/FileServiceTest.kt
@@ -2,15 +2,25 @@
package com.openai.services.blocking
+import com.github.tomakehurst.wiremock.client.WireMock.anyUrl
+import com.github.tomakehurst.wiremock.client.WireMock.get
+import com.github.tomakehurst.wiremock.client.WireMock.ok
+import com.github.tomakehurst.wiremock.client.WireMock.stubFor
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
+import com.github.tomakehurst.wiremock.junit5.WireMockTest
import com.openai.TestServerExtension
import com.openai.client.okhttp.OpenAIOkHttpClient
+import com.openai.core.http.HttpResponse
+import com.openai.models.FileContentParams
import com.openai.models.FileDeleteParams
import com.openai.models.FileListParams
import com.openai.models.FileRetrieveParams
+import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestServerExtension::class)
+@WireMockTest
class FileServiceTest {
@Test
@@ -52,4 +62,19 @@ class FileServiceTest {
println(fileDeleted)
fileDeleted.validate()
}
+
+ @Test
+ fun callContent(wmRuntimeInfo: WireMockRuntimeInfo) {
+ val client =
+ OpenAIOkHttpClient.builder()
+ .baseUrl(wmRuntimeInfo.getHttpBaseUrl())
+ .apiKey("My API Key")
+ .build()
+ stubFor(get(anyUrl()).willReturn(ok().withBody("abc")))
+ val fileService = client.files()
+ val response = fileService.content(FileContentParams.builder().fileId("file_id").build())
+ println(response)
+ assertThat(response).isInstanceOf(HttpResponse::class.java)
+ assertThat(response.body()).hasContent("abc")
+ }
}