From 9fd1fcd8aa462a86f064f96da3aab632ede8a8eb Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:50:45 +0100 Subject: [PATCH 1/8] fix: expose response headers on invalid data errors --- .../com/openai/errors/OpenAIInvalidDataException.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt index 9c3f4e6da..cf8268535 100644 --- a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt +++ b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt @@ -1,5 +1,15 @@ package com.openai.errors +import com.openai.core.http.Headers +import java.util.Optional + class OpenAIInvalidDataException @JvmOverloads -constructor(message: String? = null, cause: Throwable? = null) : OpenAIException(message, cause) +constructor( + message: String? = null, + cause: Throwable? = null, + private val headers: Headers? = null, +) : OpenAIException(message, cause) { + + fun headers(): Optional = Optional.ofNullable(headers) +} From fe333a8b957c3c46ed7dada82fa1ac43fab4f189 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:50:52 +0100 Subject: [PATCH 2/8] fix: expose response headers on I/O errors --- .../kotlin/com/openai/errors/OpenAIIoException.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt index 5df9c4a3d..673233882 100644 --- a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt +++ b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt @@ -1,5 +1,15 @@ package com.openai.errors +import com.openai.core.http.Headers +import java.util.Optional + class OpenAIIoException @JvmOverloads -constructor(message: String? = null, cause: Throwable? = null) : OpenAIException(message, cause) +constructor( + message: String? = null, + cause: Throwable? = null, + private val headers: Headers? = null, +) : OpenAIException(message, cause) { + + fun headers(): Optional = Optional.ofNullable(headers) +} From 931de3035b7bb6b6a71dd03ed938821004814212 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:50:59 +0100 Subject: [PATCH 3/8] fix: retain headers on JSON parse failures --- .../src/main/kotlin/com/openai/core/handlers/JsonHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/core/handlers/JsonHandler.kt b/openai-java-core/src/main/kotlin/com/openai/core/handlers/JsonHandler.kt index a4b4d62bc..f9d760a0f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/handlers/JsonHandler.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/handlers/JsonHandler.kt @@ -15,6 +15,6 @@ internal inline fun jsonHandler(jsonMapper: JsonMapper): Handler try { jsonMapper.readValue(response.body(), jacksonTypeRef()) } catch (e: Exception) { - throw OpenAIInvalidDataException("Error reading response", e) + throw OpenAIInvalidDataException("Error reading response", e, response.headers()) } } From 1a3f184b6ef217bf5a311dc21eb8a4fbe3e7f328 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:51:13 +0100 Subject: [PATCH 4/8] fix: retain headers on streaming I/O failures --- .../kotlin/com/openai/core/handlers/StreamHandler.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/core/handlers/StreamHandler.kt b/openai-java-core/src/main/kotlin/com/openai/core/handlers/StreamHandler.kt index 13a1949a6..26a10e94d 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/handlers/StreamHandler.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/handlers/StreamHandler.kt @@ -2,6 +2,7 @@ package com.openai.core.handlers +import com.openai.core.http.Headers import com.openai.core.http.HttpResponse import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.PhantomReachableClosingStreamResponse @@ -30,7 +31,7 @@ internal fun streamHandler( // We wrap the `lines` instead of the top-level sequence because // we only want to catch `IOException` from the reader; not from // the user's own code. - IOExceptionWrappingSequence(lines), + IOExceptionWrappingSequence(lines, response.headers()), ) } } @@ -53,7 +54,10 @@ internal fun streamHandler( } /** A sequence that catches, wraps, and rethrows [IOException] as [OpenAIIoException]. */ -private class IOExceptionWrappingSequence(private val sequence: Sequence) : Sequence { +private class IOExceptionWrappingSequence( + private val sequence: Sequence, + private val headers: Headers, +) : Sequence { override fun iterator(): Iterator { val iterator = sequence.iterator() @@ -63,14 +67,14 @@ private class IOExceptionWrappingSequence(private val sequence: Sequence) try { iterator.next() } catch (e: IOException) { - throw OpenAIIoException("Stream failed", e) + throw OpenAIIoException("Stream failed", e, headers) } override fun hasNext(): Boolean = try { iterator.hasNext() } catch (e: IOException) { - throw OpenAIIoException("Stream failed", e) + throw OpenAIIoException("Stream failed", e, headers) } } } From 3380b3fc9793568424594ced7def839519871dcc Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:51:26 +0100 Subject: [PATCH 5/8] test: preserve headers on streaming failures --- .../com/openai/core/handlers/StreamHandlerTest.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openai-java-core/src/test/kotlin/com/openai/core/handlers/StreamHandlerTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/handlers/StreamHandlerTest.kt index 1ebcb16d7..9d66858a5 100644 --- a/openai-java-core/src/test/kotlin/com/openai/core/handlers/StreamHandlerTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/core/handlers/StreamHandlerTest.kt @@ -43,12 +43,15 @@ internal class StreamHandlerTest { @Test fun streamHandler_whenReaderThrowsIOException_wrapsException() { + val headers = Headers.builder().put("x-request-id", "req_123").build() val handler = streamHandler { _, lines -> lines.forEach {} } - val streamResponse = handler.handle(httpResponse("a\nb\nc\n".byteInputStream().throwing())) + val streamResponse = + handler.handle(httpResponse("a\nb\nc\n".byteInputStream().throwing(), headers)) val e = assertThrows { streamResponse.stream().forEach {} } assertThat(e).hasMessage("Stream failed") assertThat(e).hasCauseInstanceOf(IOException::class.java) + assertThat(e.headers()).contains(headers) } @Test @@ -68,12 +71,15 @@ internal class StreamHandlerTest { assertThat(e).isSameAs(ioException) } - private fun httpResponse(body: InputStream): HttpResponse = + private fun httpResponse( + body: InputStream, + headers: Headers = Headers.builder().build(), + ): HttpResponse = object : HttpResponse { override fun statusCode(): Int = 0 - override fun headers(): Headers = Headers.builder().build() + override fun headers(): Headers = headers override fun body(): InputStream = body From df0e205eff33716e929a3c5013cbbbf7f10c5bdc Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:51:34 +0100 Subject: [PATCH 6/8] test: preserve headers on JSON parse failures --- .../openai/core/handlers/JsonHandlerTest.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 openai-java-core/src/test/kotlin/com/openai/core/handlers/JsonHandlerTest.kt diff --git a/openai-java-core/src/test/kotlin/com/openai/core/handlers/JsonHandlerTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/handlers/JsonHandlerTest.kt new file mode 100644 index 000000000..8d5e853c4 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/core/handlers/JsonHandlerTest.kt @@ -0,0 +1,39 @@ +package com.openai.core.handlers + +import com.fasterxml.jackson.databind.json.JsonMapper +import com.openai.core.http.Headers +import com.openai.core.http.HttpResponse +import com.openai.errors.OpenAIInvalidDataException +import java.io.InputStream +import kotlin.test.Test +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.assertThrows + +internal class JsonHandlerTest { + + @Test + fun jsonHandler_whenBodyCannotBeRead_exposesResponseHeaders() { + val headers = Headers.builder().put("x-request-id", "req_123").build() + val handler = jsonHandler>(JsonMapper.builder().build()) + + val error = + assertThrows { + handler.handle(httpResponse("{".byteInputStream(), headers)) + } + + assertThat(error).hasMessage("Error reading response") + assertThat(error.headers()).contains(headers) + } + + private fun httpResponse(body: InputStream, headers: Headers): HttpResponse = + object : HttpResponse { + + override fun statusCode(): Int = 200 + + override fun headers(): Headers = headers + + override fun body(): InputStream = body + + override fun close() {} + } +} From d8f3f8b3bc38bb2e7251965658b1e7bbcbb3478b Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:05:07 +0100 Subject: [PATCH 7/8] fix: preserve invalid data exception constructor ABI --- .../openai/errors/OpenAIInvalidDataException.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt index cf8268535..fd91fcc32 100644 --- a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt +++ b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIInvalidDataException.kt @@ -5,11 +5,13 @@ import java.util.Optional class OpenAIInvalidDataException @JvmOverloads -constructor( - message: String? = null, - cause: Throwable? = null, - private val headers: Headers? = null, -) : OpenAIException(message, cause) { +constructor(message: String? = null, cause: Throwable? = null) : OpenAIException(message, cause) { - fun headers(): Optional = Optional.ofNullable(headers) + private var responseHeaders: Headers? = null + + internal constructor(message: String?, cause: Throwable?, headers: Headers) : this(message, cause) { + responseHeaders = headers + } + + fun headers(): Optional = Optional.ofNullable(responseHeaders) } From 45e47a5bf4ee609e4502771a51ebaab52deef9bb Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:05:14 +0100 Subject: [PATCH 8/8] fix: preserve I/O exception constructor ABI --- .../kotlin/com/openai/errors/OpenAIIoException.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt index 673233882..5c88b2997 100644 --- a/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt +++ b/openai-java-core/src/main/kotlin/com/openai/errors/OpenAIIoException.kt @@ -5,11 +5,13 @@ import java.util.Optional class OpenAIIoException @JvmOverloads -constructor( - message: String? = null, - cause: Throwable? = null, - private val headers: Headers? = null, -) : OpenAIException(message, cause) { +constructor(message: String? = null, cause: Throwable? = null) : OpenAIException(message, cause) { - fun headers(): Optional = Optional.ofNullable(headers) + private var responseHeaders: Headers? = null + + internal constructor(message: String?, cause: Throwable?, headers: Headers) : this(message, cause) { + responseHeaders = headers + } + + fun headers(): Optional = Optional.ofNullable(responseHeaders) }