From 18d4dd0272f990c9bb6061a1080665a659e6cc4d Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Wed, 18 Feb 2026 15:09:02 +0000 Subject: [PATCH] fix: non-streaming chat response (fixes #1174, #819, #431, #1184) --- .../completions/CompletionRequestService.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index bc284f51..3cf2b6f8 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -13,6 +13,7 @@ import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest; import ee.carlrobert.llm.client.codegpt.request.InlineEditRequest; import ee.carlrobert.llm.client.codegpt.request.chat.ChatCompletionRequest; import ee.carlrobert.llm.client.google.completion.GoogleCompletionRequest; +import ee.carlrobert.llm.client.openai.completion.ErrorDetails; import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionEventSourceListener; import ee.carlrobert.llm.client.openai.completion.OpenAITextCompletionEventSourceListener; import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest; @@ -27,9 +28,15 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.stream.Stream; +import okhttp3.Call; +import okhttp3.Callback; import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import okhttp3.sse.EventSource; import okhttp3.sse.EventSources; +import okio.Buffer; +import org.jetbrains.annotations.NotNull; @Service public final class CompletionRequestService { @@ -53,12 +60,87 @@ public final class CompletionRequestService { public EventSource getCustomOpenAIChatCompletionAsync( Request customRequest, CompletionEventListener eventListener) { + if (!isStreamingRequest(customRequest)) { + return getCustomOpenAINonStreamingChatCompletionAsync(customRequest, eventListener); + } + var httpClient = CompletionClientProvider.getDefaultClientBuilder().build(); return EventSources.createFactory(httpClient).newEventSource( customRequest, new OpenAIChatCompletionEventSourceListener(eventListener)); } + private EventSource getCustomOpenAINonStreamingChatCompletionAsync( + Request customRequest, + CompletionEventListener eventListener) { + var httpClient = CompletionClientProvider.getDefaultClientBuilder().build(); + var call = httpClient.newCall(customRequest); + + eventListener.onOpen(); + call.enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + if (call.isCanceled()) { + eventListener.onCancelled(new StringBuilder()); + return; + } + eventListener.onError(new ErrorDetails(e.getMessage()), e); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + try (response) { + var completion = + DeserializationUtil.mapResponse(response, OpenAIChatCompletionResponse.class); + var content = tryExtractContent(completion).orElse(""); + eventListener.onComplete(new StringBuilder(content)); + } catch (Exception e) { + eventListener.onError(new ErrorDetails(e.getMessage()), e); + } + } + }); + + return new EventSource() { + @Override + public @NotNull Request request() { + return customRequest; + } + + @Override + public void cancel() { + call.cancel(); + } + }; + } + + private boolean isStreamingRequest(Request customRequest) { + RequestBody body = customRequest.body(); + if (body == null) { + return true; + } + + try { + var buffer = new Buffer(); + body.writeTo(buffer); + var json = DeserializationUtil.OBJECT_MAPPER.readTree(buffer.readUtf8()); + var stream = json.get("stream"); + + if (stream == null || stream.isNull()) { + return true; + } + if (stream.isBoolean()) { + return stream.booleanValue(); + } + if (stream.isTextual()) { + return Boolean.parseBoolean(stream.asText()); + } + } catch (Exception ignored) { + return true; + } + + return true; + } + public String getLookupCompletion(LookupCompletionParameters params) { var serviceType = ModelSelectionService.getInstance().getServiceForFeature(FeatureType.LOOKUP);