1111import com .openai .core .http .HttpResponse ;
1212import com .openai .errors .OpenAIIoException ;
1313import com .sap .ai .sdk .core .AiCoreService ;
14- import com .sap .ai .sdk .core .AiModel ;
1514import com .sap .ai .sdk .core .DeploymentResolutionException ;
1615import com .sap .cloud .sdk .cloudplatform .connectivity .ApacheHttpClient5Accessor ;
1716import com .sap .cloud .sdk .cloudplatform .connectivity .HttpDestination ;
2019import java .io .ByteArrayOutputStream ;
2120import java .io .IOException ;
2221import java .io .InputStream ;
23- import java .net .URI ;
24- import java .net .URISyntaxException ;
2522import java .util .Locale ;
23+ import java .util .Map ;
2624import java .util .Objects ;
2725import java .util .Optional ;
2826import java .util .Set ;
3836import org .apache .hc .core5 .http .io .entity .ByteArrayEntity ;
3937import org .apache .hc .core5 .http .io .entity .EntityUtils ;
4038import org .apache .hc .core5 .http .message .BasicClassicHttpRequest ;
41- import org .apache .hc .core5 .net .URIBuilder ;
4239
4340/**
4441 * Factory for creating OpenAI SDK clients configured for SAP AI Core deployments.
@@ -64,7 +61,7 @@ public final class AiCoreOpenAiClient {
6461 * @throws DeploymentResolutionException If no running deployment is found for the model.
6562 */
6663 @ Nonnull
67- public static OpenAIClient forModel (@ Nonnull final AiModel model ) {
64+ public static OpenAIClient forModel (@ Nonnull final OpenAiModel model ) {
6865 return forModel (model , DEFAULT_RESOURCE_GROUP );
6966 }
7067
@@ -79,7 +76,7 @@ public static OpenAIClient forModel(@Nonnull final AiModel model) {
7976 */
8077 @ Nonnull
8178 public static OpenAIClient forModel (
82- @ Nonnull final AiModel model , @ Nonnull final String resourceGroup ) {
79+ @ Nonnull final OpenAiModel model , @ Nonnull final String resourceGroup ) {
8380 final HttpDestination destination =
8481 new AiCoreService ().getInferenceDestination (resourceGroup ).forModel (model );
8582
@@ -114,19 +111,19 @@ static final class AiCoreHttpClientImpl implements HttpClient {
114111 private final HttpDestination destination ;
115112
116113 private static final String SSE_MEDIA_TYPE = "text/event-stream" ;
117- private static final Set <String > ALLOWED_PATHS =
118- Set .of (
119- "/chat/completions" ,
120- "/responses" ,
121- "/responses/[^/]+" ,
122- "/responses/[^/]+/input_items" ,
123- "/responses/[^/]+/cancel" );
114+ private static final Map < String , Set <String >> ALLOWED_OPERATIONS =
115+ Map .of (
116+ "/chat/completions" , Set . of ( "POST" ),
117+ "/responses" , Set . of ( "GET" , "POST" ),
118+ "/responses/[^/]+" , Set . of ( "GET" , "DELETE" ),
119+ "/responses/[^/]+/compact" , Set . of ( "POST" ) ,
120+ "/responses/[^/]+/cancel" , Set . of ( "POST" ) );
124121
125122 @ Override
126123 @ Nonnull
127124 public HttpResponse execute (
128125 @ Nonnull final HttpRequest request , @ Nonnull final RequestOptions requestOptions ) {
129- validateAllowedEndpoint (request );
126+ validateAllowedOperation (request );
130127 final var apacheClient = ApacheHttpClient5Accessor .getHttpClient (destination );
131128 final var apacheRequest = toApacheRequest (request );
132129
@@ -159,20 +156,33 @@ public void close() {
159156 // Apache HttpClient lifecycle is managed by Cloud SDK's ApacheHttpClient5Cache
160157 }
161158
162- private static void validateAllowedEndpoint (@ Nonnull final HttpRequest request ) {
159+ private static void validateAllowedOperation (@ Nonnull final HttpRequest request ) {
163160 final var endpoint = "/" + String .join ("/" , request .pathSegments ());
164- if (ALLOWED_PATHS .stream ().noneMatch (endpoint ::matches )) {
161+ final var method = request .method ().name ();
162+
163+ // Find matching path pattern
164+ final var matchingEntry =
165+ ALLOWED_OPERATIONS .entrySet ().stream ()
166+ .filter (entry -> endpoint .matches (entry .getKey ()))
167+ .findFirst ()
168+ .orElseThrow (
169+ () ->
170+ new UnsupportedOperationException (
171+ String .format ("Endpoint %s is not supported in AI Core" , endpoint )));
172+
173+ // Validate method
174+ if (!matchingEntry .getValue ().contains (method )) {
165175 throw new UnsupportedOperationException (
166176 String .format (
167- "Only requests to the following endpoints are allowed: %s. " , ALLOWED_PATHS ));
177+ "HTTP %s method is not supported on endpoint %s in AI Core " , method , endpoint ));
168178 }
169179 }
170180
171181 @ Nonnull
172182 private ClassicHttpRequest toApacheRequest (@ Nonnull final HttpRequest request ) {
173- final var fullUri = buildUrlWithQueryParams ( request );
183+ final var fullUri = request . url ( );
174184 final var method = request .method ();
175- final var apacheRequest = new BasicClassicHttpRequest (method .name (), fullUri . toString () );
185+ final var apacheRequest = new BasicClassicHttpRequest (method .name (), fullUri );
176186 applyRequestHeaders (request , apacheRequest );
177187
178188 try (var requestBody = request .body ()) {
@@ -197,23 +207,6 @@ private ClassicHttpRequest toApacheRequest(@Nonnull final HttpRequest request) {
197207 return apacheRequest ;
198208 }
199209
200- private static URI buildUrlWithQueryParams (@ Nonnull final HttpRequest request ) {
201- try {
202- final var uriBuilder = new URIBuilder (request .url ());
203- final var queryParams = request .queryParams ();
204-
205- for (final var key : queryParams .keys ()) {
206- for (final var value : queryParams .values (key )) {
207- uriBuilder .addParameter (key , value );
208- }
209- }
210-
211- return uriBuilder .build ();
212- } catch (URISyntaxException e ) {
213- throw new OpenAIIoException ("Failed to build URI with query parameters" , e );
214- }
215- }
216-
217210 private static void applyRequestHeaders (
218211 @ Nonnull final HttpRequest request , @ Nonnull final BasicClassicHttpRequest apacheRequest ) {
219212 final var headers = request .headers ();
0 commit comments