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. * 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(