From 07718c577e4ef78054263814ffb563dac1ead09f Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 9 Jul 2026 11:14:42 +0200 Subject: [PATCH 1/5] adding .withHeaders method --- docs/release_notes.md | 3 ++- .../sdk/orchestration/OrchestrationClient.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/release_notes.md b/docs/release_notes.md index 298ba27fa..5e442ea8e 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -12,13 +12,14 @@ ### ✨ New Functionality -- Support for OpenAI Responses API with the new `AiCoreOpenAiClient`. +- [OpenAI] Support for OpenAI Responses API with the new `AiCoreOpenAiClient`. The following endpoints are currently supported: - create() - createStreaming() - retrieve() - delete() - cancel() +- [Orchestration] You can now add multiple custom headers to an `OrchestrationClient` at once via `.withHeaders()`. ### 📈 Improvements diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java index 2e568a60b..955a04def 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java @@ -343,6 +343,24 @@ public OrchestrationClient withHeader(@Nonnull final String key, @Nonnull final return newClient; } + /** + * Create a new orchestration client with multiple custom headers added to every call made with + * this client + * + * @param headers a map of key value pairs for the custom headers to add + * @return a new client. + * @since 1.22.0 + */ + @Nonnull + public OrchestrationClient withHeaders(@Nonnull final Map headers) { + final var newClient = new OrchestrationClient(this.executor); + newClient.customHeaders.addAll(this.customHeaders); + headers.entrySet().stream() + .map(e -> new Header(e.getKey(), e.getValue())) + .forEach(newClient.customHeaders::add); + return newClient; + } + /** * Create a new orchestration client for the given resource group and scenario. * From 61bcdac884df3e8c30214b5fec9976af4365b170 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 9 Jul 2026 11:17:26 +0200 Subject: [PATCH 2/5] unit tests --- .../orchestration/OrchestrationUnitTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java index bd49304d8..478dfc3f1 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java @@ -196,6 +196,62 @@ void testCustomHeaders() { .withHeader("foot", equalTo("baz"))); } + @Test + void testWithHeadersAddsAllHeaders() { + stubFor( + post(urlPathEqualTo("/v2/completion")) + .willReturn( + aResponse() + .withBodyFile("templatingResponse.json") + .withHeader("Content-Type", "application/json"))); + + final var result = + client + .withHeaders(Map.of("x-custom-one", "value1", "x-custom-two", "value2")) + .chatCompletion(prompt, config); + assertThat(result).isNotNull(); + + verify( + postRequestedFor(urlPathEqualTo("/v2/completion")) + .withHeader("x-custom-one", equalTo("value1")) + .withHeader("x-custom-two", equalTo("value2"))); + } + + @Test + void testWithHeadersCarriesOverPreviousHeaders() { + stubFor( + post(urlPathEqualTo("/v2/completion")) + .willReturn( + aResponse() + .withBodyFile("templatingResponse.json") + .withHeader("Content-Type", "application/json"))); + + client + .withHeader("x-existing", "existing-value") + .withHeaders(Map.of("x-new", "new-value")) + .chatCompletion(prompt, config); + + verify( + postRequestedFor(urlPathEqualTo("/v2/completion")) + .withHeader("x-existing", equalTo("existing-value")) + .withHeader("x-new", equalTo("new-value"))); + } + + @Test + void testWithHeadersDoesNotMutateOriginalClient() { + stubFor( + post(urlPathEqualTo("/v2/completion")) + .willReturn( + aResponse() + .withBodyFile("templatingResponse.json") + .withHeader("Content-Type", "application/json"))); + + client.withHeaders(Map.of("x-should-not-appear", "value")); + client.chatCompletion(prompt, config); + + verify(postRequestedFor(urlPathEqualTo("/v2/completion")).withoutHeader("x-should-not-appear")); + } + @Test void testGrounding() throws IOException { stubFor( From 495851c3897c49e9aeab7544a16689f17b79443e Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 9 Jul 2026 11:39:24 +0200 Subject: [PATCH 3/5] add also to OpenAiClient --- .../foundationmodels/openai/OpenAiClient.java | 19 ++++++++ .../openai/OpenAiClientTest.java | 47 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index ce981fa2a..0fba074b4 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -151,6 +152,24 @@ public OpenAiClient withHeader(@Nonnull final String key, @Nonnull final String return newClient; } + /** + * Create a new OpenAI client with multiple custom headers added to every call made with this + * client + * + * @param headers a map of key value pairs for the custom headers to add + * @return a new client. + * @since 1.22.0 + */ + @Nonnull + public OpenAiClient withHeaders(@Nonnull final Map headers) { + final var newClient = new OpenAiClient(this.destination); + newClient.customHeaders.addAll(this.customHeaders); + headers.entrySet().stream() + .map(e -> new Header(e.getKey(), e.getValue())) + .forEach(newClient.customHeaders::add); + return newClient; + } + /** * Generate a completion for the given string prompt as user. * diff --git a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java index 882e24b19..5f9341204 100644 --- a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java +++ b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java @@ -507,4 +507,51 @@ void testCustomHeaders() { .withHeader("Header-For-Both", equalTo("value")) .withHeader("foot", equalTo("baz"))); } + + @Test + void testWithHeadersAddsAllHeaders() { + stubForChatCompletion(); + final var request = + new OpenAiChatCompletionRequest("Hello World! Why is this phrase so famous?"); + + final var result = + client + .withHeaders(Map.of("x-custom-one", "value1", "x-custom-two", "value2")) + .chatCompletion(request); + assertThat(result).isNotNull(); + + verify( + postRequestedFor(anyUrl()) + .withHeader("x-custom-one", equalTo("value1")) + .withHeader("x-custom-two", equalTo("value2"))); + } + + @Test + void testWithHeadersCarriesOverPreviousHeaders() { + stubForChatCompletion(); + final var request = + new OpenAiChatCompletionRequest("Hello World! Why is this phrase so famous?"); + + client + .withHeader("x-existing", "existing-value") + .withHeaders(Map.of("x-new", "new-value")) + .chatCompletion(request); + + verify( + postRequestedFor(anyUrl()) + .withHeader("x-existing", equalTo("existing-value")) + .withHeader("x-new", equalTo("new-value"))); + } + + @Test + void testWithHeadersDoesNotMutateOriginalClient() { + stubForChatCompletion(); + final var request = + new OpenAiChatCompletionRequest("Hello World! Why is this phrase so famous?"); + + client.withHeaders(Map.of("x-should-not-appear", "value")); + client.chatCompletion(request); + + verify(postRequestedFor(anyUrl()).withoutHeader("x-should-not-appear")); + } } From 3a4bb51f9e469767dc72d9ace576b1a3b78404aa Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 9 Jul 2026 11:40:41 +0200 Subject: [PATCH 4/5] release_notes --- docs/release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release_notes.md b/docs/release_notes.md index 5e442ea8e..331d0643b 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -19,6 +19,7 @@ - retrieve() - delete() - cancel() +- [OpenAI] You can now add multiple custom headers to an `OpenAiClient` at once via `.withHeaders()`. - [Orchestration] You can now add multiple custom headers to an `OrchestrationClient` at once via `.withHeaders()`. ### 📈 Improvements From 5def21c24080a4dd50b7be87324a47566f86dcf3 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 9 Jul 2026 13:56:36 +0200 Subject: [PATCH 5/5] remove OpenAiClient.withHeaders again --- docs/release_notes.md | 1 - .../foundationmodels/openai/OpenAiClient.java | 19 -------- .../openai/OpenAiClientTest.java | 47 ------------------- 3 files changed, 67 deletions(-) diff --git a/docs/release_notes.md b/docs/release_notes.md index 331d0643b..5e442ea8e 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -19,7 +19,6 @@ - retrieve() - delete() - cancel() -- [OpenAI] You can now add multiple custom headers to an `OpenAiClient` at once via `.withHeaders()`. - [Orchestration] You can now add multiple custom headers to an `OrchestrationClient` at once via `.withHeaders()`. ### 📈 Improvements diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index 0fba074b4..ce981fa2a 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -34,7 +34,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -152,24 +151,6 @@ public OpenAiClient withHeader(@Nonnull final String key, @Nonnull final String return newClient; } - /** - * Create a new OpenAI client with multiple custom headers added to every call made with this - * client - * - * @param headers a map of key value pairs for the custom headers to add - * @return a new client. - * @since 1.22.0 - */ - @Nonnull - public OpenAiClient withHeaders(@Nonnull final Map headers) { - final var newClient = new OpenAiClient(this.destination); - newClient.customHeaders.addAll(this.customHeaders); - headers.entrySet().stream() - .map(e -> new Header(e.getKey(), e.getValue())) - .forEach(newClient.customHeaders::add); - return newClient; - } - /** * Generate a completion for the given string prompt as user. * diff --git a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java index 5f9341204..882e24b19 100644 --- a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java +++ b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java @@ -507,51 +507,4 @@ void testCustomHeaders() { .withHeader("Header-For-Both", equalTo("value")) .withHeader("foot", equalTo("baz"))); } - - @Test - void testWithHeadersAddsAllHeaders() { - stubForChatCompletion(); - final var request = - new OpenAiChatCompletionRequest("Hello World! Why is this phrase so famous?"); - - final var result = - client - .withHeaders(Map.of("x-custom-one", "value1", "x-custom-two", "value2")) - .chatCompletion(request); - assertThat(result).isNotNull(); - - verify( - postRequestedFor(anyUrl()) - .withHeader("x-custom-one", equalTo("value1")) - .withHeader("x-custom-two", equalTo("value2"))); - } - - @Test - void testWithHeadersCarriesOverPreviousHeaders() { - stubForChatCompletion(); - final var request = - new OpenAiChatCompletionRequest("Hello World! Why is this phrase so famous?"); - - client - .withHeader("x-existing", "existing-value") - .withHeaders(Map.of("x-new", "new-value")) - .chatCompletion(request); - - verify( - postRequestedFor(anyUrl()) - .withHeader("x-existing", equalTo("existing-value")) - .withHeader("x-new", equalTo("new-value"))); - } - - @Test - void testWithHeadersDoesNotMutateOriginalClient() { - stubForChatCompletion(); - final var request = - new OpenAiChatCompletionRequest("Hello World! Why is this phrase so famous?"); - - client.withHeaders(Map.of("x-should-not-appear", "value")); - client.chatCompletion(request); - - verify(postRequestedFor(anyUrl()).withoutHeader("x-should-not-appear")); - } }