diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dddd11f6..942d55a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ jsoup = "1.21.2" jtokkit = "1.1.0" junit = "5.13.4" kotlin = "2.2.10" -llm-client = "0.8.49" +llm-client = "0.8.52" okio = "3.15.0" tree-sitter = "0.24.5" grpc = "1.73.0" diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index 6f501587..e3569f4e 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -25,6 +25,7 @@ public final class Icons { public static final Icon Meta = IconLoader.getIcon("/icons/meta.svg", Icons.class); public static final Icon Mistral = IconLoader.getIcon("/icons/mistral.svg", Icons.class); public static final Icon Moonshot = IconLoader.getIcon("/icons/moonshot.svg", Icons.class); + public static final Icon Inception = IconLoader.getIcon("/icons/inception.svg", Icons.class); public static final Icon Send = IconLoader.getIcon("/icons/send.svg", Icons.class); public static final Icon Sparkle = IconLoader.getIcon("/icons/sparkle.svg", Icons.class); public static final Icon Ollama = IconLoader.getIcon("/icons/ollama.svg", Icons.class); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index 696ba99c..aacbebba 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -13,6 +13,7 @@ import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import ee.carlrobert.llm.client.anthropic.ClaudeClient; import ee.carlrobert.llm.client.codegpt.CodeGPTClient; import ee.carlrobert.llm.client.google.GoogleClient; +import ee.carlrobert.llm.client.inception.InceptionClient; import ee.carlrobert.llm.client.llama.LlamaClient; import ee.carlrobert.llm.client.mistral.MistralClient; import ee.carlrobert.llm.client.ollama.OllamaClient; @@ -78,6 +79,11 @@ public class CompletionClientProvider { return new MistralClient(getCredential(CredentialKey.MistralApiKey.INSTANCE), getDefaultClientBuilder()); } + public static InceptionClient getInceptionClient() { + return new InceptionClient.Builder(getCredential(CredentialKey.InceptionApiKey.INSTANCE)) + .build(getDefaultClientBuilder()); + } + public static OkHttpClient.Builder getDefaultClientBuilder() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); CertificateManager certificateManager = CertificateManager.getInstance(); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index ca28ba7d..08a1e2c3 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -1,5 +1,7 @@ package ee.carlrobert.codegpt.completions; +import static ee.carlrobert.codegpt.settings.service.ServiceType.INCEPTION; + import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; import com.intellij.openapi.diagnostic.Logger; @@ -14,7 +16,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.llama.completion.LlamaCompletionRequest; +import ee.carlrobert.llm.client.inception.request.InceptionApplyRequest; 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; @@ -75,12 +77,17 @@ public final class CompletionRequestService { public EventSource autoApplyAsync( AutoApplyParameters params, CompletionEventListener eventListener) { - var serviceType = + var selectedService = ModelSelectionService.getInstance().getServiceForFeature(FeatureType.AUTO_APPLY); + + if (selectedService == INCEPTION) { + return null; + } + var request = CompletionRequestFactory - .getFactory(serviceType) + .getFactory(selectedService) .createAutoApplyRequest(params); - return getChatCompletionAsync(request, eventListener, serviceType, FeatureType.AUTO_APPLY); + return getChatCompletionAsync(request, eventListener, selectedService, FeatureType.AUTO_APPLY); } public EventSource getCommitMessageAsync( @@ -129,6 +136,10 @@ public final class CompletionRequestService { .getChatCompletionAsync(completionRequest, eventListener); case MISTRAL -> CompletionClientProvider.getMistralClient() .getChatCompletionAsync(completionRequest, eventListener); + case LLAMA_CPP -> CompletionClientProvider.getLlamaClient() + .getChatCompletionAsync(completionRequest, eventListener); + case INCEPTION -> CompletionClientProvider.getInceptionClient() + .getChatCompletionAsync(completionRequest, eventListener); default -> throw new RuntimeException("Unknown service selected"); }; } @@ -150,11 +161,6 @@ public final class CompletionRequestService { ModelSelectionService.getInstance().getModelForFeature(featureType, null), eventListener); } - if (request instanceof LlamaCompletionRequest completionRequest) { - return CompletionClientProvider.getLlamaClient().getChatCompletionAsync( - completionRequest, - eventListener); - } throw new IllegalStateException("Unknown request type: " + request.getClass()); } @@ -169,6 +175,10 @@ public final class CompletionRequestService { .getChatCompletion(completionRequest); case MISTRAL -> CompletionClientProvider.getMistralClient() .getChatCompletion(completionRequest); + case LLAMA_CPP -> CompletionClientProvider.getLlamaClient() + .getChatCompletion(completionRequest); + case INCEPTION -> CompletionClientProvider.getInceptionClient() + .getChatCompletion(completionRequest); default -> throw new RuntimeException("Unknown service selected"); }; return tryExtractContent(response).orElseThrow(); @@ -205,11 +215,6 @@ public final class CompletionRequestService { .getContent().getParts().get(0) .getText(); } - if (request instanceof LlamaCompletionRequest completionRequest) { - return CompletionClientProvider.getLlamaClient() - .getChatCompletion(completionRequest) - .getContent(); - } throw new IllegalStateException("Unknown request type: " + request.getClass()); } @@ -235,6 +240,8 @@ public final class CompletionRequestService { case GOOGLE -> CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.GoogleApiKey.INSTANCE); case MISTRAL -> CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.MistralApiKey.INSTANCE); + case INCEPTION -> + CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.InceptionApiKey.INSTANCE); case PROXYAI, CUSTOM_OPENAI, LLAMA_CPP, OLLAMA -> true; }; } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java index 29bc8319..c6fc3024 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java @@ -12,7 +12,8 @@ public enum ServiceType { GOOGLE("GOOGLE", "service.google.title", "google.chat.completion"), MISTRAL("MISTRAL", "service.mistral.title", "mistral.chat.completion"), LLAMA_CPP("LLAMA_CPP", "service.llama.title", "llama.chat.completion"), - OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion"); + OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion"), + INCEPTION("INCEPTION", "service.inception.title", "inception.chat.completion"); private final String code; private final String label; diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java index 927fd30b..f9eedc0c 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java @@ -9,6 +9,7 @@ import static ee.carlrobert.codegpt.settings.service.ServiceType.MISTRAL; import static ee.carlrobert.codegpt.settings.service.ServiceType.OLLAMA; import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI; import static ee.carlrobert.codegpt.settings.service.ServiceType.PROXYAI; +import static ee.carlrobert.codegpt.settings.service.ServiceType.INCEPTION; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.ActionUpdateThread; @@ -242,6 +243,13 @@ public class ModelComboBoxAction extends ComboBoxAction { actionGroup.add(mistralGroup); } + if (availableProviders.contains(INCEPTION)) { + var inceptionGroup = DefaultActionGroup.createPopupGroup(() -> "Inception"); + inceptionGroup.getTemplatePresentation().setIcon(Icons.Inception); + inceptionGroup.add(createInceptionModelAction(presentation)); + actionGroup.add(inceptionGroup); + } + if (availableProviders.contains(LLAMA_CPP) || availableProviders.contains(OLLAMA)) { actionGroup.addSeparator("Offline"); @@ -344,6 +352,11 @@ public class ModelComboBoxAction extends ComboBoxAction { templatePresentation.setText(getMistralPresentationText()); templatePresentation.setIcon(Icons.Mistral); break; + case INCEPTION: + templatePresentation.setIcon(Icons.Inception); + var inceptionModelName = ModelRegistry.getInstance().getModelDisplayName(INCEPTION, modelCode); + templatePresentation.setText(inceptionModelName); + break; default: break; } @@ -525,6 +538,18 @@ public class ModelComboBoxAction extends ComboBoxAction { .setModel(featureType, modelCode, MISTRAL)); } + private AnAction createInceptionModelAction(Presentation comboBoxPresentation) { + var modelCode = ModelRegistry.MERCURY_CODER; + var modelName = ModelRegistry.getInstance().getModelDisplayName(INCEPTION, modelCode); + return createModelAction( + INCEPTION, + modelName, + Icons.Inception, + comboBoxPresentation, + () -> ApplicationManager.getApplication().getService(ModelSettings.class) + .setModel(featureType, modelCode, INCEPTION)); + } + private String getMistralPresentationText() { var chatModel = ApplicationManager.getApplication().getService(ModelSettings.class).getState() .getModelSelection(featureType); diff --git a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt index c5fa0e5b..116eb021 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt @@ -14,6 +14,7 @@ import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings import ee.carlrobert.codegpt.settings.service.mistral.MistralSettings +import ee.carlrobert.codegpt.settings.service.inception.InceptionSettings abstract class CodeCompletionFeatureToggleActions( private val enableFeatureAction: Boolean @@ -50,6 +51,10 @@ abstract class CodeCompletionFeatureToggleActions( MistralSettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction } + INCEPTION -> { + service().state.codeCompletionsEnabled = enableFeatureAction + } + ANTHROPIC, GOOGLE -> { } @@ -68,7 +73,8 @@ abstract class CodeCompletionFeatureToggleActions( CUSTOM_OPENAI, LLAMA_CPP, OLLAMA, - MISTRAL -> true + MISTRAL, + INCEPTION -> true ANTHROPIC, GOOGLE -> false diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.kt index 734801ff..b171257a 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.kt @@ -10,11 +10,15 @@ import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.TextRange import ee.carlrobert.codegpt.CodeGPTKeys +import ee.carlrobert.codegpt.codecompletions.edit.NextEditCoordinator import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService +import ee.carlrobert.codegpt.predictions.CodeSuggestionDiffViewer import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings import ee.carlrobert.codegpt.settings.service.FeatureType import ee.carlrobert.codegpt.settings.service.ModelSelectionService import ee.carlrobert.codegpt.settings.service.ServiceType +import ee.carlrobert.codegpt.settings.service.ServiceType.INCEPTION +import ee.carlrobert.codegpt.settings.service.ServiceType.PROXYAI import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings import ee.carlrobert.codegpt.treesitter.CodeCompletionParserFactory import ee.carlrobert.codegpt.ui.OverlayUtil.showNotification @@ -27,7 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean class CodeCompletionEventListener( private val editor: Editor, - private val channel: ProducerScope + private val channel: ProducerScope, ) : CompletionEventListener { companion object { @@ -178,10 +182,12 @@ class CodeCompletionEventListener( setLoading(false) if (messageBuilder.isEmpty()) { - editor.project?.service()?.getNextEdit( + NextEditCoordinator.requestNextEdit( editor, prefix + suffix, - runReadAction { editor.caretModel.offset }) + runReadAction { editor.caretModel.offset }, + false + ) } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertHandler.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertHandler.kt index 45f8b522..cdbb630d 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertHandler.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertHandler.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.service import ee.carlrobert.codegpt.CodeGPTKeys -import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService +import ee.carlrobert.codegpt.codecompletions.edit.NextEditCoordinator import ee.carlrobert.codegpt.predictions.CodeSuggestionDiffViewer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -32,23 +32,23 @@ class CodeCompletionInsertHandler : InlineCompletionInsertHandler { val prefix = editor.document.text.substring(0, caretOffset) val suffix = editor.document.text.substring(caretOffset) CoroutineScope(SupervisorJob() + Dispatchers.IO).launch { - editor.project?.service() - ?.getNextEdit( - editor, - prefix + remainingCompletion.partialCompletion + suffix, - caretOffset + remainingCompletion.partialCompletion.length, - true - ) + NextEditCoordinator.requestNextEdit( + editor, + prefix + remainingCompletion.partialCompletion + suffix, + caretOffset + remainingCompletion.partialCompletion.length, + true + ) } return } else { if (CodeGPTKeys.REMAINING_PREDICTION_RESPONSE.get(editor) == null) { val caretOffset = runReadAction { editor.caretModel.offset } CoroutineScope(SupervisorJob() + Dispatchers.IO).launch { - editor.project?.service()?.getNextEdit( + NextEditCoordinator.requestNextEdit( editor, editor.document.text, caretOffset, + false ) } return @@ -64,4 +64,4 @@ class CodeCompletionInsertHandler : InlineCompletionInsertHandler { } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt index 6c119dc9..3851aaf6 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt @@ -13,6 +13,7 @@ import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings +import ee.carlrobert.llm.client.inception.request.InceptionFIMRequest import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest import ee.carlrobert.llm.client.ollama.completion.request.OllamaCompletionRequest import ee.carlrobert.llm.client.ollama.completion.request.OllamaParameters @@ -237,6 +238,16 @@ object CodeCompletionRequestFactory { .build() } + fun buildInceptionRequest(details: InfillRequest): InceptionFIMRequest { + val model = service().getModelForFeature(FeatureType.CODE_COMPLETION) + return InceptionFIMRequest.Builder() + .setPrompt(details.prefix) + .setSuffix(details.suffix) + .setModel(model) + .setStream(true) + .build() + } + private fun getLlamaInfillPromptTemplate(settings: LlamaSettingsState): InfillPromptTemplate { if (settings.isUseCustomModel) { return settings.localModelInfillPromptTemplate diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt index 2a693580..810427e0 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt @@ -5,6 +5,7 @@ import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildChatBasedFIMHttpRequest import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildCustomRequest +import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildInceptionRequest import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildLlamaRequest import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildOllamaRequest import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildOpenAIRequest @@ -17,6 +18,7 @@ import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.settings.service.ServiceType.* import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings +import ee.carlrobert.codegpt.settings.service.inception.InceptionSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.mistral.MistralSettings import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings @@ -46,9 +48,11 @@ class CodeCompletionService(private val project: Project) { .customServiceStateForFeatureType(FeatureType.CODE_COMPLETION) .codeCompletionSettings .codeCompletionsEnabled + MISTRAL -> MistralSettings.getCurrentState().isCodeCompletionsEnabled LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible() OLLAMA -> service().state.codeCompletionsEnabled + INCEPTION -> service().state.codeCompletionsEnabled else -> false } @@ -73,7 +77,11 @@ class CodeCompletionService(private val project: Project) { val isChatBasedFIM = customSettings.infillTemplate == InfillPromptTemplate.CHAT_COMPLETION if (isChatBasedFIM) { - val credential = getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(activeService.id))) + val credential = getCredential( + CredentialKey.CustomServiceApiKeyById( + requireNotNull(activeService.id) + ) + ) createFactory( CompletionClientProvider.getDefaultClientBuilder().build() ).newEventSource( @@ -107,7 +115,10 @@ class CodeCompletionService(private val project: Project) { .getCompletionAsync(buildOllamaRequest(infillRequest), eventListener) LLAMA_CPP -> CompletionClientProvider.getLlamaClient() - .getChatCompletionAsync(buildLlamaRequest(infillRequest), eventListener) + .getCodeCompletionAsync(buildLlamaRequest(infillRequest), eventListener) + + INCEPTION -> CompletionClientProvider.getInceptionClient() + .getFimCompletionAsync(buildInceptionRequest(infillRequest), eventListener) else -> throw IllegalArgumentException("Code completion not supported for ${selectedService.name}") } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt index deeb46b0..0d52bf5b 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt @@ -13,6 +13,7 @@ import ee.carlrobert.codegpt.settings.service.ModelSelectionService import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings +import ee.carlrobert.codegpt.settings.service.inception.InceptionSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.mistral.MistralSettings import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings @@ -117,6 +118,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { ServiceType.LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible() ServiceType.OLLAMA -> service().state.codeCompletionsEnabled ServiceType.MISTRAL -> MistralSettings.getCurrentState().isCodeCompletionsEnabled + ServiceType.INCEPTION -> service().state.codeCompletionsEnabled ServiceType.ANTHROPIC, ServiceType.GOOGLE, null -> false diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/CodeCompletionStreamObserver.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/CodeCompletionStreamObserver.kt index 3169c655..44168a7c 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/CodeCompletionStreamObserver.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/CodeCompletionStreamObserver.kt @@ -4,6 +4,7 @@ import com.intellij.codeInsight.inline.completion.elements.InlineCompletionEleme import com.intellij.notification.NotificationType import com.intellij.openapi.diagnostic.thisLogger import ee.carlrobert.codegpt.codecompletions.CodeCompletionEventListener +import ee.carlrobert.llm.client.openai.completion.ErrorDetails import ee.carlrobert.codegpt.ui.OverlayUtil import ee.carlrobert.service.PartialCodeCompletionResponse import io.grpc.Status @@ -45,10 +46,11 @@ class CodeCompletionStreamObserver( NotificationType.ERROR ) } + eventListener.onError(ErrorDetails(t?.message ?: "Code completion error"), t ?: Throwable()) channel.close(t) } override fun onCompleted() { eventListener.onComplete(messageBuilder) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt index f8614661..849b2cc0 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt @@ -31,8 +31,7 @@ import java.util.concurrent.TimeUnit class GrpcClientService(private val project: Project) : Disposable { private var channel: ManagedChannel? = null - private var codeCompletionStub: CodeCompletionServiceImplGrpc.CodeCompletionServiceImplStub? = - null + private var codeCompletionStub: CodeCompletionServiceImplGrpc.CodeCompletionServiceImplStub? = null private var codeCompletionObserver: CodeCompletionStreamObserver? = null private var nextEditStub: NextEditServiceImplGrpc.NextEditServiceImplStub? = null private var nextEditStreamObserver: NextEditStreamObserver? = null @@ -63,7 +62,7 @@ class GrpcClientService(private val project: Project) : Disposable { caretOffset: Int, addToQueue: Boolean = false, ) { - if (service().getServiceForFeature(FeatureType.CODE_COMPLETION) != ServiceType.PROXYAI + if (service().getServiceForFeature(FeatureType.NEXT_EDIT) != ServiceType.PROXYAI || !service().state.nextEditsEnabled ) { return @@ -199,4 +198,4 @@ class GrpcClientService(private val project: Project) : Disposable { } channel = null } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/InceptionNextEditRunner.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/InceptionNextEditRunner.kt new file mode 100644 index 00000000..40681395 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/InceptionNextEditRunner.kt @@ -0,0 +1,130 @@ +package ee.carlrobert.codegpt.codecompletions.edit + +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import ee.carlrobert.codegpt.EncodingManager +import ee.carlrobert.codegpt.completions.CompletionClientProvider +import ee.carlrobert.codegpt.settings.models.ModelRegistry +import ee.carlrobert.codegpt.settings.service.FeatureType +import ee.carlrobert.codegpt.settings.service.ModelSelectionService +import ee.carlrobert.codegpt.settings.service.ServiceType +import ee.carlrobert.codegpt.toolwindow.chat.parser.SseMessageParser +import ee.carlrobert.codegpt.toolwindow.chat.parser.SearchReplace +import ee.carlrobert.codegpt.util.GitUtil +import ee.carlrobert.service.NextEditResponse +import ee.carlrobert.llm.client.inception.request.InceptionNextEditRequest +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage +import java.util.* + +object InceptionNextEditRunner { + + private val logger = thisLogger() + + fun isEnabled(): Boolean { + return service().getServiceForFeature(FeatureType.NEXT_EDIT) == ServiceType.INCEPTION + } + + fun run(editor: Editor, fileContent: String, caretOffset: Int, addToQueue: Boolean = false, onResult: (NextEditResponse) -> Unit) { + try { + val safeOffset = caretOffset.coerceIn(0, fileContent.length) + val codeWithCursor = buildString { + append(fileContent.substring(0, safeOffset)) + append("<|cursor|>") + append(fileContent.substring(safeOffset)) + } + + val content = buildString { + append("<|recently_viewed_code_snippets|>\n") + append("<|/recently_viewed_code_snippets|>\n") + append("<|current_file_content|>\n") + append(fileContent) + append("\n<|code_to_edit|>\n") + append(codeWithCursor) + append("\n<|/code_to_edit|>") + append("<|/current_file_content|>\n") + append("<|edit_diff_history|>\n") + append(buildEditDiffHistory(editor.project)) + append("\n<|/edit_diff_history|>") + } + + val message = OpenAIChatCompletionStandardMessage("user", content) + val request = InceptionNextEditRequest.Builder() + .setModel(ModelRegistry.MERCURY_CODER) + .setMessages(listOf(message)) + .build() + + val response = CompletionClientProvider.getInceptionClient().getNextEditCompletion(request) + val text = response.choices?.firstOrNull()?.message?.content ?: return + + val next = extractNextRevision(text, fileContent) + val result = NextEditResponse.newBuilder() + .setId(UUID.randomUUID().toString()) + .setOldRevision(fileContent) + .setNextRevision(next) + .build() + onResult(result) + } catch (ex: Exception) { + logger.error("Something went wrong while retrieving next edit completion", ex) + } + } + + private fun extractNextRevision(message: String, source: String): String { + // If model returned a fenced full file, extract it directly + extractTripleBacktickCode(message)?.let { fenced -> + if (fenced.isNotBlank()) return fenced + } + + val parser = SseMessageParser() + val segments = parser.parse(message) + var result = source + segments.forEach { seg -> + if (seg is SearchReplace) { + val search = seg.search.trim() + val replace = seg.replace + if (search.isNotEmpty() && result.contains(search)) { + result = result.replaceFirst(search, replace) + } + } + } + return result + } + + private fun extractTripleBacktickCode(message: String): String? { + val fence = "```" + val start = message.indexOf(fence) + if (start == -1) return null + // Skip optional language id on the opening fence + var contentStart = start + fence.length + if (contentStart < message.length && message[contentStart] == '\n') { + contentStart += 1 + } else { + val nl = message.indexOf('\n', contentStart) + if (nl != -1) contentStart = nl + 1 + } + val end = message.indexOf(fence, contentStart) + if (end == -1) return null + return message.substring(contentStart, end).trim() + } +} + +private fun buildEditDiffHistory(project: Project?): String { + if (project == null) return "" + + val sb = StringBuilder() + + // Unstaged/staged current changes first + try { + GitUtil.getCurrentChanges(project)?.let { diff -> + if (diff.isNotBlank()) { + sb.append(diff.trim()).append('\n') + } + } + } catch (_: Exception) { + // ignore + } + + val combined = sb.toString().trim() + return service().truncateText(combined, 2048, true) +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/NextEditCoordinator.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/NextEditCoordinator.kt new file mode 100644 index 00000000..74ed0706 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/NextEditCoordinator.kt @@ -0,0 +1,65 @@ +package ee.carlrobert.codegpt.codecompletions.edit + +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.components.service +import com.intellij.openapi.editor.Editor +import ee.carlrobert.codegpt.CodeGPTKeys +import ee.carlrobert.codegpt.predictions.CodeSuggestionDiffViewer +import ee.carlrobert.codegpt.settings.service.FeatureType +import ee.carlrobert.codegpt.settings.service.ModelSelectionService +import ee.carlrobert.codegpt.settings.service.ServiceType.INCEPTION +import ee.carlrobert.codegpt.settings.service.ServiceType.PROXYAI +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings +import ee.carlrobert.codegpt.settings.service.inception.InceptionSettings + +object NextEditCoordinator { + + fun requestNextEdit( + editor: Editor, + fileContent: String, + caretOffset: Int = runReadAction { editor.caretModel.offset }, + addToQueue: Boolean = false, + ) { + val provider = service().getServiceForFeature(FeatureType.NEXT_EDIT) + when (provider) { + PROXYAI -> { + if (!service().state.nextEditsEnabled) { + return + } + + editor.project?.service()?.getNextEdit( + editor, + fileContent, + caretOffset, + addToQueue + ) + } + + INCEPTION -> { + if (!service().state.nextEditsEnabled) { + return + } + + InceptionNextEditRunner.run( + editor, + fileContent, + caretOffset, + addToQueue + ) { response -> + if (addToQueue) { + CodeGPTKeys.REMAINING_PREDICTION_RESPONSE.set(editor, response) + } else { + runInEdt { + if (editor.document.text == response.oldRevision) { + CodeSuggestionDiffViewer.displayInlineDiff(editor, response) + } + } + } + } + } + + else -> null + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestFactory.kt index b1c283f9..66bee13b 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestFactory.kt @@ -33,6 +33,7 @@ interface CompletionRequestFactory { ServiceType.MISTRAL -> MistralRequestFactory() ServiceType.OLLAMA -> OllamaRequestFactory() ServiceType.LLAMA_CPP -> LlamaRequestFactory() + ServiceType.INCEPTION -> InceptionRequestFactory() } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/InceptionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/InceptionRequestFactory.kt new file mode 100644 index 00000000..497fc9af --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/InceptionRequestFactory.kt @@ -0,0 +1,106 @@ +package ee.carlrobert.codegpt.completions.factory + +import com.intellij.openapi.components.service +import ee.carlrobert.codegpt.completions.* +import ee.carlrobert.codegpt.settings.prompts.CoreActionsState +import ee.carlrobert.codegpt.settings.prompts.PromptsSettings +import ee.carlrobert.codegpt.settings.service.FeatureType +import ee.carlrobert.codegpt.settings.service.ModelSelectionService +import ee.carlrobert.codegpt.util.EditorUtil +import ee.carlrobert.llm.client.inception.request.InceptionApplyRequest +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage +import ee.carlrobert.llm.completion.CompletionRequest + +class InceptionRequestFactory : BaseRequestFactory() { + + override fun createChatRequest(params: ChatCompletionParameters): OpenAIChatCompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(FeatureType.CHAT) + val messages = OpenAIRequestFactory.buildOpenAIMessages( + model = model, + callParameters = params + ) + return OpenAIChatCompletionRequest.Builder(messages) + .setModel(model) + .setStream(true) + .build() + } + + override fun createInlineEditRequest(params: InlineEditCompletionParameters): OpenAIChatCompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(FeatureType.INLINE_EDIT) + val prepared = prepareInlineEditPrompts(params) + val messages = OpenAIRequestFactory.buildInlineEditMessages(prepared, params.conversation) + return OpenAIChatCompletionRequest.Builder(messages) + .setModel(model) + .setStream(true) + .build() + } + + override fun createAutoApplyRequest(params: AutoApplyParameters): CompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(FeatureType.AUTO_APPLY) + val prompt = + "<|original_code|>\n" + EditorUtil.getFileContent(params.destination) + "\n<|/original_code|>\n\n<|update_snippet|>\n// ... existing code ...\n" + params.source + "\n// ... existing code ...\n<|/update_snippet|>" + + return InceptionApplyRequest.Builder() + .setModel(model) + .setMessages( + listOf( + OpenAIChatCompletionStandardMessage("user", prompt) + ) + ) + .build() + } + + override fun createBasicCompletionRequest( + systemPrompt: String, + userPrompt: String, + maxTokens: Int, + stream: Boolean, + featureType: FeatureType + ): CompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(featureType) + return OpenAIChatCompletionRequest.Builder( + listOf( + OpenAIChatCompletionStandardMessage("system", systemPrompt), + OpenAIChatCompletionStandardMessage("user", userPrompt) + ) + ) + .setModel(model) + .setStream(stream) + .build() + } + + override fun createCommitMessageRequest(params: CommitMessageCompletionParameters): OpenAIChatCompletionRequest { + val model = + ModelSelectionService.getInstance().getModelForFeature(FeatureType.COMMIT_MESSAGE) + val (gitDiff, systemPrompt) = params + return OpenAIChatCompletionRequest.Builder( + listOf( + OpenAIChatCompletionStandardMessage("system", systemPrompt), + OpenAIChatCompletionStandardMessage("user", gitDiff) + ) + ) + .setModel(model) + .setStream(true) + .build() + } + + override fun createLookupRequest(params: LookupCompletionParameters): OpenAIChatCompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(FeatureType.LOOKUP) + val (prompt) = params + val systemPrompt = + service().state.coreActions.generateNameLookups.instructions + ?: CoreActionsState.DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT + + return OpenAIChatCompletionRequest.Builder( + listOf( + OpenAIChatCompletionStandardMessage("system", systemPrompt), + OpenAIChatCompletionStandardMessage("user", prompt) + ) + ) + .setModel(model) + .setStream(false) + .build() + } +} + diff --git a/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/LlamaRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/LlamaRequestFactory.kt index a21ae44b..acfec859 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/LlamaRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/LlamaRequestFactory.kt @@ -2,42 +2,28 @@ package ee.carlrobert.codegpt.completions.factory import com.intellij.openapi.components.service import ee.carlrobert.codegpt.completions.BaseRequestFactory -import ee.carlrobert.codegpt.completions.InlineEditCompletionParameters import ee.carlrobert.codegpt.completions.ChatCompletionParameters -import ee.carlrobert.codegpt.completions.ConversationType -import ee.carlrobert.codegpt.completions.llama.LlamaModel -import ee.carlrobert.codegpt.completions.llama.PromptTemplate +import ee.carlrobert.codegpt.completions.InlineEditCompletionParameters import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings -import ee.carlrobert.codegpt.settings.prompts.FilteredPromptsService -import ee.carlrobert.codegpt.settings.prompts.PromptsSettings -import ee.carlrobert.codegpt.settings.prompts.addProjectPath import ee.carlrobert.codegpt.settings.service.FeatureType -import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings -import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest +import ee.carlrobert.codegpt.settings.service.ModelSelectionService +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage class LlamaRequestFactory : BaseRequestFactory() { - override fun createChatRequest(params: ChatCompletionParameters): LlamaCompletionRequest { - val promptTemplate = getPromptTemplate() - var systemPrompt = - if (params.conversationType == ConversationType.FIX_COMPILE_ERRORS) { - service().state.coreActions.fixCompileErrors.instructions - } else { - service().state.personas.selectedPersona.let { - if (it.disabled) null else service().getFilteredPersonaPrompt( - params.chatMode - ).addProjectPath() - } - } - systemPrompt = systemPrompt?.let { service().applyClickableLinks(it) } - - val prompt = promptTemplate.buildPrompt( - systemPrompt, - getPromptWithFilesContext(params), - params.conversation.messages + override fun createChatRequest(params: ChatCompletionParameters): OpenAIChatCompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(FeatureType.CHAT) + val configuration = service().state + return OpenAIChatCompletionRequest.Builder( + OpenAIRequestFactory.buildOpenAIMessages(model, params) ) - - return buildLlamaRequest(prompt, promptTemplate.stopTokens, true) + .setModel(model) + .setStream(true) + .setMaxTokens(null) + .setMaxCompletionTokens(configuration.maxTokens) + .setTemperature(configuration.temperature.toDouble()) + .build() } override fun createBasicCompletionRequest( @@ -46,46 +32,32 @@ class LlamaRequestFactory : BaseRequestFactory() { maxTokens: Int, stream: Boolean, featureType: FeatureType - ): LlamaCompletionRequest { - val promptTemplate = getPromptTemplate(featureType) - val finalPrompt = - promptTemplate.buildPrompt(systemPrompt, userPrompt, listOf()) - - return buildLlamaRequest(finalPrompt, emptyList(), stream) - } - - override fun createInlineEditRequest(params: InlineEditCompletionParameters): LlamaCompletionRequest { - val prepared = prepareInlineEditPrompts(params) - val promptTemplate = getPromptTemplate(FeatureType.INLINE_EDIT) - val history = params.conversation?.messages?.filter { !it.response.isNullOrBlank() } ?: listOf() - val finalPrompt = promptTemplate.buildPrompt(prepared.systemPrompt, prepared.userPrompt, history) - return buildLlamaRequest(finalPrompt, emptyList(), stream = true) - } - - private fun getPromptTemplate(featureType: FeatureType? = null): PromptTemplate { - val settings = service().state - return if (settings.isUseCustomModel) - settings.localModelPromptTemplate - else - LlamaModel.findByHuggingFaceModel(settings.huggingFaceModel).promptTemplate - } - - private fun buildLlamaRequest( - prompt: String, - stopTokens: List, - stream: Boolean = false - ): LlamaCompletionRequest { - val configSettings = service().state - val llamaSettings = service().state - return LlamaCompletionRequest.Builder(prompt) - .setN_predict(configSettings.maxTokens) - .setTemperature(configSettings.temperature.toDouble()) - .setTop_k(llamaSettings.topK) - .setTop_p(llamaSettings.topP) - .setMin_p(llamaSettings.minP) - .setRepeat_penalty(llamaSettings.repeatPenalty) - .setStop(stopTokens) + ): OpenAIChatCompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(featureType) + val configuration = service().state + return OpenAIChatCompletionRequest.Builder( + listOf( + OpenAIChatCompletionStandardMessage("system", systemPrompt), + OpenAIChatCompletionStandardMessage("user", userPrompt) + ) + ) + .setModel(model) .setStream(stream) + .setMaxTokens(null) + .setMaxCompletionTokens(maxTokens) + .setTemperature(configuration.temperature.toDouble()) + .build() + } + + override fun createInlineEditRequest(params: InlineEditCompletionParameters): OpenAIChatCompletionRequest { + val model = ModelSelectionService.getInstance().getModelForFeature(FeatureType.INLINE_EDIT) + val prepared = prepareInlineEditPrompts(params) + val messages = OpenAIRequestFactory.buildInlineEditMessages(prepared, params.conversation) + val configuration = service().state + return OpenAIChatCompletionRequest.Builder(messages) + .setModel(model) + .setStream(true) + .setTemperature(configuration.temperature.toDouble()) .build() } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt index 2a28fc13..0ba9e274 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt @@ -77,5 +77,9 @@ object CredentialsStore { data object MistralApiKey : CredentialKey() { override val value: String = "MISTRAL_API_KEY" } + + data object InceptionApiKey : CredentialKey() { + override val value: String = "INCEPTION_API_KEY" + } } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt b/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt index b9a0cdca..6e391d84 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt @@ -34,6 +34,8 @@ import com.intellij.util.ui.JBUI import com.intellij.util.ui.components.BorderLayoutPanel import ee.carlrobert.codegpt.CodeGPTKeys import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService +import ee.carlrobert.codegpt.codecompletions.edit.NextEditCoordinator +import ee.carlrobert.codegpt.telemetry.ui.preferences.TelemetryConfigurable import ee.carlrobert.service.NextEditResponse import java.awt.BorderLayout import java.awt.Dimension @@ -128,10 +130,11 @@ class CodeSuggestionDiffViewer( popup.dispose() application.executeOnPooledThread { - grpcService?.getNextEdit( + NextEditCoordinator.requestNextEdit( mainEditor, mainEditor.document.text, runReadAction { mainEditor.caretModel.offset }, + false ) } } @@ -194,8 +197,8 @@ class CodeSuggestionDiffViewer( ) if (popup.isVisible && !popup.isDisposed) { - adjustPopupSize(popup, myEditor) popup.setLocation(adjustedLocation) + adjustPopupSize(popup, myEditor) } } } @@ -386,4 +389,4 @@ fun createSuggestionDiffPopup(content: JComponent): JBPopup { .setCancelOnWindowDeactivation(false) .setCancelOnOtherWindowOpen(false) .createPopup() -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt b/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt index e8f5e5bd..eb896b2b 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt @@ -12,7 +12,7 @@ import com.intellij.testFramework.LightVirtualFile import com.intellij.util.application import ee.carlrobert.codegpt.CodeGPTKeys import ee.carlrobert.codegpt.codecompletions.CompletionProgressNotifier -import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService +import ee.carlrobert.codegpt.codecompletions.edit.NextEditCoordinator import ee.carlrobert.codegpt.util.EditorDiffUtil.createDiffRequest import kotlin.coroutines.cancellation.CancellationException @@ -26,6 +26,7 @@ class PredictionService { fun acceptPrediction(editor: Editor) { val diffViewer = editor.getUserData(CodeGPTKeys.EDITOR_PREDICTION_DIFF_VIEWER) if (diffViewer != null && diffViewer.isVisible()) { + diffViewer.applyChanges() return } @@ -36,10 +37,11 @@ class PredictionService { try { application.executeOnPooledThread { CompletionProgressNotifier.update(project, true) - project.service().getNextEdit( + NextEditCoordinator.requestNextEdit( editor, editor.document.text, runReadAction { editor.caretModel.offset }, + false ) } } catch (e: CancellationException) { @@ -57,4 +59,4 @@ class PredictionService { service().showDiff(project, diffRequest) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt b/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt index ec6c1574..bfc01180 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt @@ -22,8 +22,12 @@ class TriggerCustomPredictionAction : EditorAction(Handler()), HintManagerImpl.A private class Handler : EditorWriteActionHandler() { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - if (ModelSelectionService.getInstance() - .getServiceForFeature(FeatureType.CODE_COMPLETION) != ServiceType.PROXYAI + val nextEditModelProvider = ModelSelectionService.getInstance() + .getServiceForFeature(FeatureType.NEXT_EDIT) + if (!listOf( + ServiceType.PROXYAI, + ServiceType.INCEPTION + ).contains(nextEditModelProvider) ) { return } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/migration/LegacySettingsMigration.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/migration/LegacySettingsMigration.kt index 1c0c43fc..8daf4e1a 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/migration/LegacySettingsMigration.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/migration/LegacySettingsMigration.kt @@ -104,6 +104,10 @@ object LegacySettingsMigration { ServiceType.MISTRAL -> { ModelRegistry.DEVSTRAL_MEDIUM_2507 } + + ServiceType.INCEPTION -> { + ModelRegistry.MERCURY_CODER + } } } catch (e: Exception) { logger.warn("Failed to get legacy chat model for $serviceType", e) @@ -152,10 +156,14 @@ object LegacySettingsMigration { ServiceType.MISTRAL -> { ModelRegistry.CODESTRAL_LATEST } + + ServiceType.INCEPTION -> { + ModelRegistry.MERCURY_CODER + } } } catch (e: Exception) { logger.warn("Failed to get legacy code model for $serviceType", e) null } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelIcons.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelIcons.kt index dcb5b477..2e84605d 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelIcons.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelIcons.kt @@ -37,6 +37,7 @@ object ModelIcons { ServiceType.OLLAMA -> Icons.Ollama ServiceType.CUSTOM_OPENAI -> Icons.OpenAI ServiceType.LLAMA_CPP -> Icons.Llama + ServiceType.INCEPTION -> Icons.Inception } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistry.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistry.kt index dcfe1318..47961751 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistry.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistry.kt @@ -101,6 +101,14 @@ class ModelRegistry { FeatureType.CHAT, FeatureType.CODE_COMPLETION, FeatureType.AUTO_APPLY, FeatureType.COMMIT_MESSAGE, FeatureType.INLINE_EDIT, FeatureType.LOOKUP ) + ), + ServiceType.INCEPTION to ModelCapability( + ServiceType.INCEPTION, + setOf( + FeatureType.CODE_COMPLETION, + FeatureType.AUTO_APPLY, + FeatureType.NEXT_EDIT + ) ) ) @@ -272,6 +280,7 @@ class ModelRegistry { addAll(getLlamaModels()) addAll(getOllamaModels()) addAll(getCustomOpenAIModels()) + addAll(getInceptionModels()) } } @@ -283,6 +292,7 @@ class ModelRegistry { addAll(getLlamaModels()) addAll(getCustomOpenAICodeModels()) addAll(getOllamaModels()) + addAll(getInceptionModels()) } } @@ -307,7 +317,16 @@ class ModelRegistry { } private fun getNextEditModels(): List { - return listOf(ModelSelection(ServiceType.PROXYAI, ZETA, "Zeta")) + return listOf( + ModelSelection(ServiceType.PROXYAI, ZETA, "Zeta"), + ModelSelection(ServiceType.INCEPTION, MERCURY_CODER, "Mercury Coder") + ) + } + + private fun getInceptionModels(): List { + return listOf( + ModelSelection(ServiceType.INCEPTION, MERCURY_CODER, "Mercury Coder") + ) } fun getProxyAIChatModels(): List { @@ -604,9 +623,11 @@ class ModelRegistry { // Llama.cpp default models const val LLAMA_3_2_3B_INSTRUCT = "llama-3.2-3b-instruct" + const val MERCURY_CODER = "mercury-coder" + @JvmStatic fun getInstance(): ModelRegistry { return ApplicationManager.getApplication().getService(ModelRegistry::class.java) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelSettingsForm.kt index dbbcdde3..27141fe7 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelSettingsForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/ModelSettingsForm.kt @@ -202,7 +202,8 @@ class ModelSettingsForm( private fun updateNextEditButtonState(codeCompletionProvider: ServiceType) { val nextEditButton = modelButtons[FeatureType.NEXT_EDIT] ?: return - nextEditButton.isEnabled = codeCompletionProvider == ServiceType.PROXYAI + nextEditButton.isEnabled = + codeCompletionProvider == ServiceType.PROXYAI || codeCompletionProvider == ServiceType.INCEPTION } fun isModified(): Boolean { @@ -312,4 +313,4 @@ class ModelSettingsForm( override fun dispose() { messageBusConnection.disconnect() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/SettingsModelComboBoxAction.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/SettingsModelComboBoxAction.kt index 24d03875..98f905c2 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/models/SettingsModelComboBoxAction.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/models/SettingsModelComboBoxAction.kt @@ -68,7 +68,8 @@ class SettingsModelComboBoxAction( ServiceType.OPENAI, ServiceType.CUSTOM_OPENAI, ServiceType.GOOGLE, - ServiceType.MISTRAL + ServiceType.MISTRAL, + ServiceType.INCEPTION ) val hasCloudProviders = cloudProviders.any { groupedModels.containsKey(it) } @@ -245,4 +246,4 @@ class SettingsModelComboBoxAction( val llamaModel = LlamaModel.findByHuggingFaceModel(huggingFaceModel) return "${llamaModel.label} (${huggingFaceModel.parameterSize}B) / Q${huggingFaceModel.quantization}" } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/InceptionServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/InceptionServiceConfigurable.kt new file mode 100644 index 00000000..5f5b6508 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/InceptionServiceConfigurable.kt @@ -0,0 +1,35 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.InceptionApiKey +import ee.carlrobert.codegpt.settings.service.inception.InceptionSettingsForm +import javax.swing.JComponent + +class InceptionServiceConfigurable : Configurable { + + private lateinit var component: InceptionSettingsForm + + override fun getDisplayName(): String { + return "ProxyAI: Inception Service" + } + + override fun createComponent(): JComponent { + component = InceptionSettingsForm() + component.setApiKey(CredentialsStore.getCredential(InceptionApiKey)) + return component.getForm() + } + + override fun isModified(): Boolean { + return component.getApiKey() != CredentialsStore.getCredential(InceptionApiKey) + } + + override fun apply() { + CredentialsStore.setCredential(InceptionApiKey, component.getApiKey()) + ModelReplacementDialog.showDialogIfNeeded(ServiceType.INCEPTION) + } + + override fun reset() { + component.setApiKey(CredentialsStore.getCredential(InceptionApiKey)) + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt index d3a91128..553898d6 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt @@ -39,6 +39,7 @@ class ServiceConfigurableComponent { "Mistral" to MistralServiceConfigurable::class.java, "LLaMA C/C++" to LlamaServiceConfigurable::class.java, "Ollama" to OllamaSettingsConfigurable::class.java, + "Inception" to InceptionServiceConfigurable::class.java, ).entries.forEach { (name, configurableClass) -> formBuilder.addComponent(ActionLink(name) { val context = service().getDataContext(it.source as ActionLink) @@ -47,4 +48,4 @@ class ServiceConfigurableComponent { }) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/inception/InceptionSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/inception/InceptionSettings.kt new file mode 100644 index 00000000..7eba184b --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/inception/InceptionSettings.kt @@ -0,0 +1,15 @@ +package ee.carlrobert.codegpt.settings.service.inception + +import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.SimplePersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage + +@State(name = "CodeGPT_InceptionSettings", storages = [Storage("CodeGPT_InceptionSettings.xml")]) +class InceptionSettings : SimplePersistentStateComponent(InceptionSettingsState()) + +class InceptionSettingsState : BaseState() { + var codeCompletionsEnabled by property(true) + var nextEditsEnabled by property(true) +} + diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/inception/InceptionSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/inception/InceptionSettingsForm.kt new file mode 100644 index 00000000..f8fa8d05 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/inception/InceptionSettingsForm.kt @@ -0,0 +1,22 @@ +package ee.carlrobert.codegpt.settings.service.inception + +import com.intellij.ui.components.JBPasswordField +import com.intellij.util.ui.FormBuilder +import javax.swing.JComponent +import javax.swing.JPanel + +class InceptionSettingsForm { + private val apiKeyField = JBPasswordField() + private val panel: JPanel = FormBuilder.createFormBuilder() + .addLabeledComponent("API Key:", apiKeyField, 1, false) + .addComponentFillVertically(JPanel(), 0) + .panel + + fun getForm(): JComponent = panel + + fun getApiKey(): String? = String(apiKeyField.password) + + fun setApiKey(value: String?) { + apiKeyField.text = value ?: "" + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.kt index b94aac4c..5a921a64 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditorPanel.kt @@ -105,15 +105,6 @@ class ResponseEditorPanel( replaceEditor(oldEditor, newState.editor) } - fun removeCurrentEditor() { - runInEdt { - removeAll() - stateManager.clearCurrentState() - revalidate() - repaint() - } - } - fun applyCodeAsync(content: String, virtualFile: VirtualFile, editor: EditorEx, headerPanel: DefaultHeaderPanel? = null) { val eventSource = CompletionRequestService.getInstance().autoApplyAsync( AutoApplyParameters(content, virtualFile), diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/AutoApplyAction.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/AutoApplyAction.kt index adc9a340..7df10fb4 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/AutoApplyAction.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/AutoApplyAction.kt @@ -12,6 +12,9 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.readText import com.intellij.ui.components.AnActionLink import com.intellij.util.ui.JBUI +import ee.carlrobert.codegpt.settings.service.FeatureType +import ee.carlrobert.codegpt.settings.service.ModelSelectionService +import ee.carlrobert.codegpt.settings.service.ServiceType.INCEPTION import ee.carlrobert.codegpt.util.EditorUtil import javax.swing.JComponent @@ -33,6 +36,15 @@ class AutoApplyAction( } override fun update(e: AnActionEvent) { + val autoApplyProvider = + ModelSelectionService.getInstance().getServiceForFeature(FeatureType.AUTO_APPLY) + if (autoApplyProvider == INCEPTION) { + anActionLink.text = "Apply" + anActionLink.isEnabled = false + anActionLink.toolTipText = "Auto Apply is temporarily disabled for Inception provider" + return + } + if (virtualFile != null) { anActionLink.text = "Apply" anActionLink.isEnabled = true diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/GitUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/GitUtil.kt index 51887178..ecdaf792 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/util/GitUtil.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/GitUtil.kt @@ -43,7 +43,7 @@ object GitUtil { .filter { change -> change.virtualFile?.let { !it.fileType.isBinary } ?: false } - .sortedBy { it.virtualFile?.timeStamp } + .sortedByDescending { it.virtualFile?.timeStamp } val patches = IdeaTextPatchBuilder.buildPatch( project, changes, repoRootPath, false, true @@ -52,7 +52,7 @@ object GitUtil { UnifiedDiffWriter.write( null, repoRootPath, patches, diffWriter, "\n\n", null, null ) - diffWriter.toString().cleanDiff().truncateText(1024, false) + diffWriter.toString().cleanDiff().truncateText(1024, true) } catch (e: VcsException) { logger.error("Failed to get git context", e) null diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 37d5cc96..373c1abb 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -56,6 +56,9 @@ + @@ -87,6 +90,7 @@ + diff --git a/src/main/resources/icons/inception.svg b/src/main/resources/icons/inception.svg new file mode 100644 index 00000000..e390c697 --- /dev/null +++ b/src/main/resources/icons/inception.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/main/resources/icons/inception_dark.svg b/src/main/resources/icons/inception_dark.svg new file mode 100644 index 00000000..b612cc13 --- /dev/null +++ b/src/main/resources/icons/inception_dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index e4d43d84..01b72b66 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -254,6 +254,7 @@ service.anthropic.title=Anthropic service.azure.title=Azure service.google.title=Google service.mistral.title=Mistral +service.inception.title=Inception Labs service.llama.title=LLaMA C/C++ service.ollama.title=Ollama validation.error.model.notExists='%s' is not available, please select another model diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultToolwindowChatCompletionRequestHandlerTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultToolwindowChatCompletionRequestHandlerTest.kt index ebde2d48..0d55932c 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultToolwindowChatCompletionRequestHandlerTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultToolwindowChatCompletionRequestHandlerTest.kt @@ -77,31 +77,31 @@ class DefaultToolwindowChatCompletionRequestHandlerTest : IntegrationTest() { val conversation = ConversationService.getInstance().startConversation(project) conversation.addMessage(Message("Ping", "Pong")) expectLlama(StreamHttpExchange { request: RequestEntity -> - assertThat(request.uri.path).isEqualTo("/completion") + assertThat(request.uri.path).isEqualTo("/v1/chat/completions") val guidelines = getResourceContent("/prompts/persona/psi-navigation-guidelines.txt") val expectedSystem = "TEST_SYSTEM_PROMPT\n$guidelines" assertThat(request.body) .extracting( - "prompt", - "n_predict", - "stream" + "model", + "messages" ) .containsExactly( - LLAMA.buildPrompt( - expectedSystem, - "TEST_PROMPT", - conversation.messages - ), - 99, - true + HuggingFaceModel.CODE_LLAMA_7B_Q4.code, + listOf( + mapOf("role" to "system", "content" to expectedSystem), + mapOf("role" to "user", "content" to "Ping"), + mapOf("role" to "assistant", "content" to "Pong"), + mapOf("role" to "user", "content" to "TEST_PROMPT") + ) ) - listOf( - jsonMapResponse("content", "Hel"), - jsonMapResponse("content", "lo!"), + listOf( jsonMapResponse( - e("content", ""), - e("stop", true) - ) + "choices", + jsonArray(jsonMap("delta", jsonMap("role", "assistant"))) + ), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "lo")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!")))) ) }) val requestHandler = diff --git a/src/test/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistryTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistryTest.kt index cf967daa..16204717 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistryTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/settings/models/ModelRegistryTest.kt @@ -115,15 +115,16 @@ class ModelRegistryTest : IntegrationTest() { ServiceType.MISTRAL, ServiceType.OLLAMA, ServiceType.LLAMA_CPP, - ServiceType.CUSTOM_OPENAI + ServiceType.CUSTOM_OPENAI, + ServiceType.INCEPTION ) assertThat(result).doesNotContain(ServiceType.ANTHROPIC, ServiceType.GOOGLE) } - fun `test getProvidersForFeature with next edit returns proxyai only`() { + fun `test getProvidersForFeature with next edit returns proxyai and inception`() { val result = modelRegistry.getProvidersForFeature(FeatureType.NEXT_EDIT) - assertThat(result).containsExactly(ServiceType.PROXYAI) + assertThat(result).containsExactlyInAnyOrder(ServiceType.PROXYAI, ServiceType.INCEPTION) } fun `test isFeatureSupportedByProvider with valid chat provider returns true`() { @@ -182,4 +183,4 @@ class ModelRegistryTest : IntegrationTest() { assertThat(result).isNull() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelTest.kt index a24ac3fa..88b36476 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelTest.kt @@ -401,41 +401,30 @@ class ChatToolWindowTabPanelTest : IntegrationTest() { val conversation = ConversationService.getInstance().startConversation(project) val panel = ChatToolWindowTabPanel(project, conversation) expectLlama(StreamHttpExchange { request: RequestEntity -> - assertThat(request.uri.path).isEqualTo("/completion") + assertThat(request.uri.path).isEqualTo("/v1/chat/completions") + val expectedSystem = getResourceContent("/prompts/persona/psi-navigation-guidelines.txt").let { g -> + "TEST_SYSTEM_PROMPT\n$g" + } assertThat(request.body) .extracting( - "prompt", - "n_predict", - "stream", - "temperature", - "top_k", - "top_p", - "min_p", - "repeat_penalty" + "model", + "messages" ) .containsExactly( - LLAMA.buildPrompt( - (getResourceContent("/prompts/persona/psi-navigation-guidelines.txt").let { g -> - "TEST_SYSTEM_PROMPT\n$g" - }), - "TEST_PROMPT", - conversation.messages - ), - configurationState.maxTokens, - true, - configurationState.temperature.toDouble(), - llamaSettings.topK, - llamaSettings.topP, - llamaSettings.minP, - llamaSettings.repeatPenalty + HuggingFaceModel.CODE_LLAMA_7B_Q4.code, + listOf( + mapOf("role" to "system", "content" to expectedSystem), + mapOf("role" to "user", "content" to "TEST_PROMPT") + ) ) - listOf( - jsonMapResponse("content", "Hel"), - jsonMapResponse("content", "lo!"), + listOf( jsonMapResponse( - e("content", ""), - e("stop", true) - ) + "choices", + jsonArray(jsonMap("delta", jsonMap("role", "assistant"))) + ), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "lo")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!")))) ) })